diff --git a/.circleci/config.yml b/.circleci/config.yml
index 8ecee2b19d8d372a0b2c4e22d88eab3907eb6597..67a3b66b7f759be472dd6c13b93712d2aeef0388 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -43,7 +43,7 @@ jobs:
             - v1-SRB2-APT
       - run:
           name: Install SDK
-          command: apt-get -qq -y --no-install-recommends install git build-essential nasm libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client
+          command: apt-get -qq -y --no-install-recommends install git build-essential nasm libpng-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 libcurl4-openssl-dev:i386 libopenmpt-dev:i386 gettext ccache wget gcc-multilib upx openssh-client
 
       - save_cache:
           key: v1-SRB2-APT
@@ -71,4 +71,4 @@ jobs:
       - save_cache:
           key: v1-SRB2-{{ .Branch }}-{{ checksum "objs/Linux/SDL/Release/depend.dep" }}
           paths:
-            - /root/.ccache
\ No newline at end of file
+            - /root/.ccache
diff --git a/.travis.yml b/.travis.yml
index 9d91b77dfc937a48da16ead5cf80298711e92da5..789426be7f3792c46cb04a4115d8169d2aaff31a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,6 +26,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.4
           compiler: gcc-4.4
@@ -43,6 +44,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.6
           compiler: gcc-4.6
@@ -60,6 +62,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.7
           compiler: gcc-4.7
@@ -83,6 +86,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -101,6 +105,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-7
           compiler: gcc-7
@@ -119,6 +124,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-8
           compiler: gcc-8
@@ -141,6 +147,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - clang-3.5
           compiler: clang-3.5
@@ -159,6 +166,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - clang-3.6
           compiler: clang-3.6
@@ -177,6 +185,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - clang-3.7
           compiler: clang-3.7
@@ -195,6 +204,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - clang-3.8
           compiler: clang-3.8
@@ -213,6 +223,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - clang-3.9
           compiler: clang-3.9
@@ -323,6 +334,7 @@ matrix:
               - libpng-dev
               - libgl1-mesa-dev
               - libgme-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -468,6 +480,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -495,6 +508,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -522,6 +536,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -549,6 +564,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -576,6 +592,7 @@ matrix:
               - libgl1-mesa-dev
               - libgme-dev
               - libopenmpt-dev
+              - libcurl4-openssl-dev
               - p7zip-full
               - gcc-4.8
           compiler: gcc-4.8
@@ -619,6 +636,7 @@ addons:
     - libgme-dev
     - zlib1g-dev
     - libopenmpt-dev
+    - libcurl4-openssl-dev
     - p7zip-full
   homebrew:
     taps:
@@ -629,6 +647,7 @@ addons:
     - p7zip
     - libopenmpt
     - cmake
+    - curl
     update: true
 
 
diff --git a/Doxyfile b/Doxyfile
index d11fa2bbc6d0e2d61697759c6be6023e944af95b..eb8c6ae9eb53f8b20fe8d7f0902c16cf39d166b2 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -109,13 +109,10 @@ FILE_PATTERNS          = *.c \
                          *.mm \
                          *.dox
 RECURSIVE              = YES
-EXCLUDE                = ./src/djgppdos/internal.h \
-                         ./src/djgppdos/setup.c \
-                         ./src/sdl/IMG_xpm.c \
+EXCLUDE                = ./src/sdl/IMG_xpm.c \
                          ./src/sdl/SRB2DC/scramble.c
 EXCLUDE_SYMLINKS       = NO
 EXCLUDE_PATTERNS       = */src/hardware/*/* \
-                         */src/djgppdos/bcd.? \
                          */src/sdl/SDL_main/* \
                          */src/*/*_private.h \
                          */src/sdl/*/*help.? \
diff --git a/appveyor.yml b/appveyor.yml
index 820c77e8b0c5a05b7ff6bdaa441e7048a23c86fa..2acc2f71235be24cd7190e511ee5106f16bcc295 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.2.6.{branch}-{build}
+version: 2.2.8.{branch}-{build}
 os: MinGW
 
 environment:
diff --git a/debian-template/control b/debian-template/control
index 74d11ae9023215f91120d549078042bd5bdfade0..88334f132ecaff78d534183bf23a3d862720dd61 100644
--- a/debian-template/control
+++ b/debian-template/control
@@ -11,6 +11,7 @@ Build-Depends: debhelper (>= 7.0.50~),
  zlib1g-dev,
  libgme-dev,
  libopenmpt-dev,
+ libcurl4-openssl-dev,
  libglu1-dev | libglu-dev,
  libosmesa6-dev | libgl-dev,
  nasm [i386]
diff --git a/dep/.gitignore b/dep/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..fb941664fc5718a31185a0cf67ccd2cfd68a2be8
--- /dev/null
+++ b/dep/.gitignore
@@ -0,0 +1,2 @@
+#All folders
+*.d
diff --git a/bin/Dos/Debug/.gitignore b/dep/FreeBSD/SDL/Debug/.gitignore
similarity index 100%
rename from bin/Dos/Debug/.gitignore
rename to dep/FreeBSD/SDL/Debug/.gitignore
diff --git a/bin/Dos/Release/.gitignore b/dep/FreeBSD/SDL/Release/.gitignore
similarity index 100%
rename from bin/Dos/Release/.gitignore
rename to dep/FreeBSD/SDL/Release/.gitignore
diff --git a/objs/djgppdos/Debug/.gitignore b/dep/Linux/SDL/Debug/.gitignore
similarity index 100%
rename from objs/djgppdos/Debug/.gitignore
rename to dep/Linux/SDL/Debug/.gitignore
diff --git a/objs/djgppdos/Release/.gitignore b/dep/Linux/SDL/Release/.gitignore
similarity index 100%
rename from objs/djgppdos/Release/.gitignore
rename to dep/Linux/SDL/Release/.gitignore
diff --git a/dep/Linux64/SDL/Debug/.gitignore b/dep/Linux64/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Linux64/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Linux64/SDL/Release/.gitignore b/dep/Linux64/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Linux64/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/MasterClient/.gitignore b/dep/MasterClient/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/MasterClient/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/MasterServer/.gitignore b/dep/MasterServer/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/MasterServer/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/Debug/.gitignore b/dep/Mingw/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/Release/.gitignore b/dep/Mingw/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/SDL/Debug/.gitignore b/dep/Mingw/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw/SDL/Release/.gitignore b/dep/Mingw/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/Debug/.gitignore b/dep/Mingw64/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/Release/.gitignore b/dep/Mingw64/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/SDL/Debug/.gitignore b/dep/Mingw64/SDL/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/SDL/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/Mingw64/SDL/Release/.gitignore b/dep/Mingw64/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/Mingw64/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/SDL/Release/.gitignore b/dep/SDL/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/SDL/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/VC/.gitignore b/dep/VC/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/VC/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/VC9/.gitignore b/dep/VC9/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/VC9/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/cygwin/Debug/.gitignore b/dep/cygwin/Debug/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/cygwin/Debug/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/cygwin/Release/.gitignore b/dep/cygwin/Release/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/cygwin/Release/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/dep/dummy/.gitignore b/dep/dummy/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..42c6dc2c662642792a8860e166dfd81126695e8f
--- /dev/null
+++ b/dep/dummy/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/extras/conf/udb/Includes/Game_SRB222.cfg b/extras/conf/udb/Includes/Game_SRB222.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..2ad0dc61d0ae7cafca568ec6257a2f293afb4903
--- /dev/null
+++ b/extras/conf/udb/Includes/Game_SRB222.cfg
@@ -0,0 +1,77 @@
+// Default lump name for new map
+defaultlumpname = "MAP01";
+//GZDB specific. Don't try to load lumps that don't exist.
+basegame = "Doom";
+
+//Sky textures for vanilla maps
+defaultskytextures
+{
+	SKY1 = "MAP01,MAP02,MAP03,MAP33,MAP50,MAP60,MAPF0,MAPM0";
+	SKY2 = "MAPM7,MAPMB";
+	SKY4 = "MAP04,MAP06,MAP61,MAPF6,MAPM1";
+	SKY6 = "MAP05,MAP51,MAPMA";
+	SKY7 = "MAPM2,MAPM5";
+	SKY8 = "MAP07,MAP08,MAP09,MAP52,MAP62,MAPF1";
+	SKY10 = "MAP10,MAP12,MAP53,MAP63,MAPM3";
+	SKY11 = "MAP11,MAPF7";
+	SKY13 = "MAP13,MAP64";
+	SKY14 = "MAP14";
+	SKY15 = "MAP15,MAP54";
+	SKY17 = "MAP70";
+	SKY20 = "MAP32,MAP55,MAP65,MAPF2,MAPF5";
+	SKY21 = "MAPM4";
+	SKY22 = "MAP22,MAP23,MAP25,MAP26,MAP27,MAP56,MAP66,MAPF4,MAPM6";
+	SKY30 = "MAP30";
+	SKY31 = "MAP31";
+	SKY35 = "MAP42";
+	SKY40 = "MAP41,MAP71,MAPM9";
+	SKY55 = "MAPF3,MAPM8";
+	SKY68 = "MAPF8";
+	SKY99 = "MAP57,MAPZ0";
+	SKY159 = "MAP16";
+	SKY172 = "MAP40";
+	SKY300 = "MAP72";
+	SKY301 = "MAP73";
+}
+
+// Skill levels
+skills
+{
+	1 = "Normal";
+}
+
+// Skins
+skins
+{
+	Sonic;
+	Tails;
+	Knuckles;
+	Amy;
+	Fang;
+	Metalsonic;
+}
+
+// Gametypes
+gametypes
+{
+	-1 = "Single Player";
+	0 = "Co-op";
+	1 = "Competition";
+	2 = "Race";
+	3 = "Match";
+	4 = "Team Match";
+	5 = "Tag";
+	6 = "Hide and Seek";
+	7 = "CTF";
+}
+
+// Texture loading options
+defaultwalltexture = "GFZROCK";
+defaultfloortexture = "GFZFLR01";
+defaultceilingtexture = "F_SKY1";
+
+// Default texture sets
+// (these are not required, but useful for new users)
+texturesets
+{
+}
\ No newline at end of file
diff --git a/extras/conf/udb/Includes/SRB222_common.cfg b/extras/conf/udb/Includes/SRB222_common.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..d67835aeb3c67a6b8dee404664aa0bafd6cf312e
--- /dev/null
+++ b/extras/conf/udb/Includes/SRB222_common.cfg
@@ -0,0 +1,297 @@
+common
+{
+	// Simulate Doom brightness levels (turn this off for linear lighting)
+	doomlightlevels = true;
+
+	// Enables support for long (> 8 chars) texture names
+	// WARNING: this should only be enabled for UDMF game configurations!
+	// WARNING: enabling this will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3!
+	longtexturenames = false;
+
+	// These directory names are ignored when loading PK3/PK7/Directory resources
+	ignoreddirectories = ".svn .git";
+
+	// Files with these extensions are ignored when loading PK3/PK7/Directory resources
+	ignoredextensions = "wad pk3 pk7 bak backup1 backup2 backup3 zip rar 7z";
+
+	// Default testing parameters
+	testparameters = "-file \"%AP\" \"%F\" -warp %L";
+	testshortpaths = true;
+
+	// Action special help
+	actionspecialhelp = "https://wiki.srb2.org/wiki/Linedef_type_%K";
+
+	// Generalized actions
+	generalizedlinedefs = false;
+	generalizedsectors = true;
+
+	// Maximum safe map size check (0 means skip check)
+	safeboundary = 1;
+
+	// Map boundaries. Map objects can only be placed within these boundaries
+	leftboundary = -32768;
+	rightboundary = 32767;
+	topboundary = 32767;
+	bottomboundary = -32768;
+
+	// Texture loading options
+	mixtexturesflats = true;
+	defaulttexturescale = 1.0f;
+	defaultflatscale = 1.0f;
+	scaledtextureoffsets = true;
+
+	// Thing number for start position in 3D Mode
+	start3dmode = 3328;
+
+	// Texture sources
+	textures
+	{
+		include("SRB222_misc.cfg", "textures");
+	}
+
+	// Patch sources
+	patches
+	{
+		include("SRB222_misc.cfg", "patches");
+	}
+
+	// Sprite sources
+	sprites
+	{
+		include("SRB222_misc.cfg", "sprites");
+	}
+
+	// Flat sources
+	flats
+	{
+		include("SRB222_misc.cfg", "flats");
+	}
+}
+
+mapformat_doom
+{
+	// The format interface handles the map data format
+	formatinterface = "DoomMapSetIO";
+
+	// Default nodebuilder configurations
+	defaultsavecompiler = "zennode_normal";
+	defaulttestcompiler = "zennode_fast";
+
+	/*
+	GAME DETECT PATTERN
+	Used to guess the game for which a WAD file is made.
+
+	1 = One of these lumps must exist
+	2 = None of these lumps must exist
+	3 = All of these lumps must exist
+	*/
+
+	gamedetect
+	{
+		EXTENDED = 2;
+
+
+		BEHAVIOR = 2;
+
+		E#M# = 2;
+
+		MAP?? = 1;
+	}
+
+	/*
+	MAP LUMP NAMES
+	Map lumps are loaded with the map as long as they are right after each other. When the editor
+	meets a lump which is not defined in this list it will ignore the map if not satisfied.
+	The order of items defines the order in which lumps will be written to WAD file on save.
+	To indicate the map header lump, use ~MAP
+
+	Legenda:
+	required = Lump is required to exist.
+	blindcopy = Lump will be copied along with the map blindly. (usefull for lumps Doom Builder doesn't use)
+	nodebuild = The nodebuilder generates this lump.
+	allowempty = The nodebuilder is allowed to leave this lump empty.
+	script = This lump is a text-based script. Specify the filename of the script configuration to use.
+	*/
+
+	maplumpnames
+	{
+		include("SRB222_misc.cfg", "doommaplumpnames");
+	}
+
+	// When this is set to true, sectors with the same tag will light up when a line is highlighted
+	linetagindicatesectors = true;
+
+	// Special linedefs
+	include("SRB222_misc.cfg", "speciallinedefs");
+
+	// Default flags for first new thing
+	defaultthingflags
+	{
+	}
+
+	// DEFAULT SECTOR BRIGHTNESS LEVELS
+	sectorbrightness
+	{
+		include("SRB222_misc.cfg", "sectorbrightness");
+	}
+
+	// SECTOR TYPES
+	sectortypes
+	{
+		include("SRB222_sectors.cfg", "sectortypes");
+	}
+
+	// GENERALISED SECTOR TYPES
+	gen_sectortypes
+	{
+		include("SRB222_sectors.cfg", "gen_sectortypes");
+	}
+
+	// LINEDEF FLAGS
+	linedefflags
+	{
+		include("SRB222_misc.cfg", "linedefflags");
+	}
+
+	// Linedef flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	linedefflagstranslation
+	{
+		include("SRB222_misc.cfg", "linedefflagstranslation");
+	}
+
+	// LINEDEF ACTIVATIONS
+	linedefactivations
+	{
+	}
+
+	// LINEDEF TYPES
+	linedeftypes
+	{
+		include("SRB222_linedefs.cfg", "doom");
+	}
+
+	// THING FLAGS
+	thingflags
+	{
+		include("SRB222_misc.cfg", "thingflags");
+	}
+
+	// Thing flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	thingflagstranslation
+	{
+		include("SRB222_misc.cfg", "thingflagstranslation");
+	}
+
+	// THING FLAGS ERROR MASK
+	// Mask for the thing flags which indicates the options
+	// that make the same thing appear in the same modes
+	thingflagsmask1 = 7;	// 1 + 2 + 4
+	thingflagsmask2 = 0;
+}
+
+mapformat_udmf
+{
+	// The format interface handles the map data format
+	formatinterface = "UniversalMapSetIO";
+
+	// Default nodebuilder configurations
+	defaultsavecompiler = "zdbsp_udmf_normal";
+	defaulttestcompiler = "zdbsp_udmf_fast";
+
+	// Determines the textmap namespace
+	engine = "srb2";
+
+	maplumpnames
+	{
+		include("UDMF_misc.cfg", "udmfmaplumpnames_begin");
+		include("SRB222_misc.cfg", "udmfmaplumpnames");
+		include("UDMF_misc.cfg", "udmfmaplumpnames_end");
+	}
+
+	universalfields
+	{
+		include("SRB222_misc.cfg", "universalfields");
+	}
+
+	// When this is set to true, sectors with the same tag will light up when a line is highlighted
+	linetagindicatesectors = false;
+
+	// Special linedefs
+	include("SRB222_misc.cfg", "speciallinedefs_udmf");
+
+	// Default flags for first new thing
+	defaultthingflags
+	{
+	}
+
+	// SECTOR FLAGS
+	sectorflags
+	{
+		include("SRB222_misc.cfg", "sectorflags");
+	}
+
+	// DEFAULT SECTOR BRIGHTNESS LEVELS
+	sectorbrightness
+	{
+		include("SRB222_misc.cfg", "sectorbrightness");
+	}
+
+	// SECTOR TYPES
+	sectortypes
+	{
+		include("SRB222_sectors.cfg", "sectortypes");
+	}
+
+	// GENERALISED SECTOR TYPES
+	gen_sectortypes
+	{
+		include("SRB222_sectors.cfg", "gen_sectortypes");
+	}
+
+	// LINEDEF FLAGS
+	linedefflags
+	{
+		include("SRB222_misc.cfg", "linedefflags_udmf");
+	}
+
+	linedefflagstranslation
+	{
+		include("SRB222_misc.cfg", "linedefflagstranslation");
+	}
+
+	// LINEDEF RENDERSTYLES
+	/*linedefrenderstyles
+	{
+		include("SRB222_misc.cfg", "linedefrenderstyles");
+	}*/
+
+	// THING FLAGS
+	thingflags
+	{
+		include("SRB222_misc.cfg", "thingflags_udmf");
+	}
+
+	// Thing flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	thingflagstranslation
+	{
+		include("SRB222_misc.cfg", "thingflagstranslation");
+	}
+
+	// How to compare thing flags (for the stuck things error checker)
+	thingflagscompare
+	{
+		include("UDMF_misc.cfg", "thingflagscompare");
+	}
+
+	// LINEDEF TYPES
+	linedeftypes
+	{
+		include("SRB222_linedefs.cfg", "udmf");
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..1f87c2c3a5aeb8185ac96370f37e1fcf03fe55b4
--- /dev/null
+++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg
@@ -0,0 +1,1901 @@
+doom
+{
+	misc
+	{
+		title = "Miscellaneous";
+
+		0
+		{
+			title = "None";
+			prefix = "(0)";
+		}
+		1
+		{
+			title = "Per-Sector Gravity";
+			prefix = "(1)";
+		}
+		5
+		{
+			title = "Camera Scanner";
+			prefix = "(5)";
+		}
+		7
+		{
+			title = "Sector Flat Alignment";
+			prefix = "(7)";
+		}
+		10
+		{
+			title = "Culling Plane";
+			prefix = "(10)";
+		}
+		13
+		{
+			title = "Heat Wave Effect";
+			prefix = "(13)";
+		}
+		40
+		{
+			title = "Visual Portal Between Tagged Linedefs";
+			prefix = "(40)";
+		}
+		41
+		{
+			title = "Horizon Effect";
+			prefix = "(41)";
+		}
+		50
+		{
+			title = "Instantly Lower Floor on Level Load";
+			prefix = "(50)";
+		}
+		51
+		{
+			title = "Instantly Raise Ceiling on Level Load";
+			prefix = "(51)";
+		}
+		63
+		{
+			title = "Fake Floor/Ceiling Planes";
+			prefix = "(63)";
+		}
+		540
+		{
+			title = "Floor Friction";
+			prefix = "(540)";
+		}
+	}
+
+	parameters
+	{
+		title = "Parameters";
+
+		2
+		{
+			title = "Custom Exit";
+			prefix = "(2)";
+		}
+		3
+		{
+			title = "Zoom Tube Parameters";
+			prefix = "(3)";
+		}
+		4
+		{
+			title = "Speed Pad Parameters";
+			prefix = "(4)";
+		}
+		8
+		{
+			title = "Special Sector Properties";
+			prefix = "(8)";
+		}
+		9
+		{
+			title = "Chain Parameters";
+			prefix = "(9)";
+		}
+		11
+		{
+			title = "Rope Hang Parameters";
+			prefix = "(11)";
+		}
+		12
+		{
+			title = "Rock Spawner Parameters";
+			prefix = "(12)";
+		}
+		14
+		{
+			title = "Bustable Block Parameters";
+			prefix = "(14)";
+		}
+		15
+		{
+			title = "Fan Particle Spawner Parameters";
+			prefix = "(15)";
+		}
+		16
+		{
+			title = "Minecart Parameters";
+			prefix = "(16)";
+		}
+		64
+		{
+			title = "Continuously Appearing/Disappearing FOF";
+			prefix = "(64)";
+		}
+		65
+		{
+			title = "Bridge Thinker <disabled>";
+			prefix = "(65)";
+		}
+	}
+
+	polyobject
+	{
+		title = "PolyObject";
+
+		20
+		{
+			title = "First Line";
+			prefix = "(20)";
+		}
+		21
+		{
+			title = "Explicitly Include Line <disabled>";
+			prefix = "(21)";
+		}
+		22
+		{
+			title = "Parameters";
+			prefix = "(22)";
+		}
+		30
+		{
+			title = "Waving Flag";
+			prefix = "(30)";
+		}
+		31
+		{
+			title = "Displacement by Front Sector";
+			prefix = "(31)";
+		}
+		32
+		{
+			title = "Angular Displacement by Front Sector";
+			prefix = "(32)";
+		}
+	}
+
+	planemove
+	{
+		title = "Plane Movement";
+
+		52
+		{
+			title = "Continuously Falling Sector";
+			prefix = "(52)";
+		}
+		53
+		{
+			title = "Continuous Floor/Ceiling Mover";
+			prefix = "(53)";
+		}
+		54
+		{
+			title = "Continuous Floor Mover";
+			prefix = "(54)";
+		}
+		55
+		{
+			title = "Continuous Ceiling Mover";
+			prefix = "(55)";
+		}
+		56
+		{
+			title = "Continuous Two-Speed Floor/Ceiling Mover";
+			prefix = "(56)";
+		}
+		57
+		{
+			title = "Continuous Two-Speed Floor Mover";
+			prefix = "(57)";
+		}
+		58
+		{
+			title = "Continuous Two-Speed Ceiling Mover";
+			prefix = "(58)";
+		}
+		59
+		{
+			title = "Activate Moving Platform";
+			prefix = "(59)";
+		}
+		60
+		{
+			title = "Activate Moving Platform (Adjustable Speed)";
+			prefix = "(60)";
+		}
+		61
+		{
+			title = "Crusher (Ceiling to Floor)";
+			prefix = "(61)";
+		}
+		62
+		{
+			title = "Crusher (Floor to Ceiling)";
+			prefix = "(62)";
+		}
+		66
+		{
+			title = "Move Floor by Displacement";
+			prefix = "(66)";
+		}
+		67
+		{
+			title = "Move Ceiling by Displacement";
+			prefix = "(67)";
+		}
+		68
+		{
+			title = "Move Floor and Ceiling by Displacement";
+			prefix = "(68)";
+		}
+	}
+
+	fofsolid
+	{
+		title = "FOF (solid)";
+
+		100
+		{
+			title = "Solid, Opaque";
+			prefix = "(100)";
+		}
+		101
+		{
+			title = "Solid, Opaque, No Shadow";
+			prefix = "(101)";
+		}
+		102
+		{
+			title = "Solid, Translucent";
+			prefix = "(102)";
+		}
+		103
+		{
+			title = "Solid, Sides Only";
+			prefix = "(103)";
+		}
+		104
+		{
+			title = "Solid, No Sides";
+			prefix = "(104)";
+		}
+		105
+		{
+			title = "Solid, Invisible";
+			prefix = "(105)";
+		}
+		140
+		{
+			title = "Intangible from Bottom, Opaque";
+			prefix = "(140)";
+		}
+		141
+		{
+			title = "Intangible from Bottom, Translucent";
+			prefix = "(141)";
+		}
+		142
+		{
+			title = "Intangible from Bottom, Translucent, No Sides";
+			prefix = "(142)";
+		}
+		143
+		{
+			title = "Intangible from Top, Opaque";
+			prefix = "(143)";
+		}
+		144
+		{
+			title = "Intangible from Top, Translucent";
+			prefix = "(144)";
+		}
+		145
+		{
+			title = "Intangible from Top, Translucent, No Sides";
+			prefix = "(145)";
+		}
+		146
+		{
+			title = "Only Tangible from Sides";
+			prefix = "(146)";
+		}
+	}
+
+	fofintangible
+	{
+		title = "FOF (intangible)";
+
+		120
+		{
+			title = "Water, Opaque";
+			prefix = "(120)";
+		}
+		121
+		{
+			title = "Water, Translucent";
+			prefix = "(121)";
+		}
+		122
+		{
+			title = "Water, Opaque, No Sides";
+			prefix = "(122)";
+		}
+		123
+		{
+			title = "Water, Translucent, No Sides";
+			prefix = "(123)";
+		}
+		124
+		{
+			title = "Goo Water, Translucent";
+			prefix = "(124)";
+		}
+		125
+		{
+			title = "Goo Water, Translucent, No Sides";
+			prefix = "(125)";
+		}
+		220
+		{
+			title = "Intangible, Opaque";
+			prefix = "(220)";
+		}
+		221
+		{
+			title = "Intangible, Translucent";
+			prefix = "(221)";
+		}
+		222
+		{
+			title = "Intangible, Sides Only";
+			prefix = "(222)";
+		}
+		223
+		{
+			title = "Intangible, Invisible";
+			prefix = "(223)";
+		}
+	}
+
+	fofmoving
+	{
+		title = "FOF (moving)";
+
+		150
+		{
+			title = "Air Bobbing";
+			prefix = "(150)";
+		}
+		151
+		{
+			title = "Air Bobbing (Adjustable)";
+			prefix = "(151)";
+		}
+		152
+		{
+			title = "Reverse Air Bobbing (Adjustable)";
+			prefix = "(152)";
+		}
+		153
+		{
+			title = "Dynamically Sinking Platform";
+			prefix = "(153)";
+		}
+		160
+		{
+			title = "Floating, Bobbing";
+			prefix = "(160)";
+		}
+		190
+		{
+			title = "Rising Platform, Solid, Opaque";
+			prefix = "(190)";
+		}
+		191
+		{
+			title = "Rising Platform, Solid, Opaque, No Shadow";
+			prefix = "(191)";
+		}
+		192
+		{
+			title = "Rising Platform, Solid, Translucent";
+			prefix = "(192)";
+		}
+		193
+		{
+			title = "Rising Platform, Solid, Invisible";
+			prefix = "(193)";
+		}
+		194
+		{
+			title = "Rising Platform, Intangible from Bottom, Opaque";
+			prefix = "(194)";
+		}
+		195
+		{
+			title = "Rising Platform, Intangible from Bottom, Translucent";
+			prefix = "(195)";
+		}
+	}
+
+	fofcrumbling
+	{
+		title = "FOF (crumbling)";
+
+		170
+		{
+			title = "Crumbling, Respawn";
+			prefix = "(170)";
+		}
+		171
+		{
+			title = "Crumbling, No Respawn";
+			prefix = "(171)";
+		}
+		172
+		{
+			title = "Crumbling, Respawn, Intangible from Bottom";
+			prefix = "(172)";
+		}
+		173
+		{
+			title = "Crumbling, No Respawn, Intangible from Bottom";
+			prefix = "(173)";
+		}
+		174
+		{
+			title = "Crumbling, Respawn, Int. from Bottom, Translucent";
+			prefix = "(174)";
+		}
+		175
+		{
+			title = "Crumbling, No Respawn, Int. from Bottom, Translucent";
+			prefix = "(175)";
+		}
+		176
+		{
+			title = "Crumbling, Respawn, Floating, Bobbing";
+			prefix = "(176)";
+		}
+		177
+		{
+			title = "Crumbling, No Respawn, Floating, Bobbing";
+			prefix = "(177)";
+		}
+		178
+		{
+			title = "Crumbling, Respawn, Floating";
+			prefix = "(178)";
+		}
+		179
+		{
+			title = "Crumbling, No Respawn, Floating";
+			prefix = "(179)";
+		}
+		180
+		{
+			title = "Crumbling, Respawn, Air Bobbing";
+			prefix = "(180)";
+		}
+	}
+
+	fofspecial
+	{
+		title = "FOF (special)";
+
+		200
+		{
+			title = "Light Block";
+			prefix = "(200)";
+		}
+		201
+		{
+			title = "Half Light Block";
+			prefix = "(201)";
+		}
+		202
+		{
+			title = "Fog Block";
+			prefix = "(202)";
+		}
+		250
+		{
+			title = "Mario Block";
+			prefix = "(250)";
+		}
+		251
+		{
+			title = "Thwomp Block";
+			prefix = "(251)";
+		}
+		252
+		{
+			title = "Shatter Block";
+			prefix = "(252)";
+		}
+		253
+		{
+			title = "Shatter Block, Translucent";
+			prefix = "(253)";
+		}
+		254
+		{
+			title = "Bustable Block";
+			prefix = "(254)";
+		}
+		255
+		{
+			title = "Spin-Bustable Block";
+			prefix = "(255)";
+		}
+		256
+		{
+			title = "Spin-Bustable Block, Translucent";
+			prefix = "(256)";
+		}
+		257
+		{
+			title = "Quicksand";
+			prefix = "(257)";
+		}
+		258
+		{
+			title = "Laser";
+			prefix = "(258)";
+		}
+		259
+		{
+			title = "Custom FOF";
+			prefix = "(259)";
+		}
+	}
+
+	linedeftrigger
+	{
+		title = "Linedef Executor Trigger";
+
+		300
+		{
+			title = "Continuous";
+			prefix = "(300)";
+		}
+		301
+		{
+			title = "Each Time";
+			prefix = "(301)";
+		}
+		302
+		{
+			title = "Once";
+			prefix = "(302)";
+		}
+		303
+		{
+			title = "Ring Count - Continuous";
+			prefix = "(303)";
+		}
+		304
+		{
+			title = "Ring Count - Once";
+			prefix = "(304)";
+		}
+		305
+		{
+			title = "Character Ability - Continuous";
+			prefix = "(305)";
+		}
+		306
+		{
+			title = "Character Ability - Each Time";
+			prefix = "(306)";
+		}
+		307
+		{
+			title = "Character Ability - Once";
+			prefix = "(307)";
+		}
+		308
+		{
+			title = "Race Only - Once";
+			prefix = "(308)";
+		}
+		309
+		{
+			title = "CTF Red Team - Continuous";
+			prefix = "(309)";
+		}
+		310
+		{
+			title = "CTF Red Team - Each Time";
+			prefix = "(310)";
+		}
+		311
+		{
+			title = "CTF Blue Team - Continuous";
+			prefix = "(311)";
+		}
+		312
+		{
+			title = "CTF Blue Team - Each Time";
+			prefix = "(312)";
+		}
+		313
+		{
+			title = "No More Enemies - Once";
+			prefix = "(313)";
+		}
+		314
+		{
+			title = "Number of Pushables - Continuous";
+			prefix = "(314)";
+		}
+		315
+		{
+			title = "Number of Pushables - Once";
+			prefix = "(315)";
+		}
+		317
+		{
+			title = "Condition Set Trigger - Continuous";
+			prefix = "(317)";
+		}
+		318
+		{
+			title = "Condition Set Trigger - Once";
+			prefix = "(318)";
+		}
+		319
+		{
+			title = "Unlockable - Continuous";
+			prefix = "(319)";
+		}
+		320
+		{
+			title = "Unlockable - Once";
+			prefix = "(320)";
+		}
+		321
+		{
+			title = "Trigger After X Calls - Continuous";
+			prefix = "(321)";
+		}
+		322
+		{
+			title = "Trigger After X Calls - Each Time";
+			prefix = "(322)";
+		}
+		323
+		{
+			title = "NiGHTSerize - Each Time";
+			prefix = "(323)";
+		}
+		324
+		{
+			title = "NiGHTSerize - Once";
+			prefix = "(324)";
+		}
+		325
+		{
+			title = "De-NiGHTSerize - Each Time";
+			prefix = "(325)";
+		}
+		326
+		{
+			title = "De-NiGHTSerize - Once";
+			prefix = "(326)";
+		}
+		327
+		{
+			title = "NiGHTS Lap - Each Time";
+			prefix = "(327)";
+		}
+		328
+		{
+			title = "NiGHTS Lap - Once";
+			prefix = "(328)";
+		}
+		329
+		{
+			title = "Ideya Capture Touch - Each Time";
+			prefix = "(329)";
+		}
+		330
+		{
+			title = "Ideya Capture Touch - Once";
+			prefix = "(330)";
+		}
+		331
+		{
+			title = "Player Skin - Continuous";
+			flags64text = "[6] Disable for this skin";
+			prefix = "(331)";
+		}
+		332
+		{
+			title = "Player Skin - Each Time";
+			prefix = "(332)";
+		}
+		333
+		{
+			title = "Player Skin - Once";
+			prefix = "(333)";
+		}
+		399
+		{
+			title = "Level Load";
+			prefix = "(399)";
+		}
+	}
+
+	linedefexecsector
+	{
+		title = "Linedef Executor (sector)";
+
+		400
+		{
+			title = "Set Tagged Sector's Floor Height/Texture";
+			prefix = "(400)";
+		}
+		401
+		{
+			title = "Set Tagged Sector's Ceiling Height/Texture";
+			prefix = "(401)";
+		}
+		402
+		{
+			title = "Set Tagged Sector's Light Level";
+			prefix = "(402)";
+		}
+		409
+		{
+			title = "Change Tagged Sector's Tag";
+			prefix = "(409)";
+		}
+		410
+		{
+			title = "Change Front Sector's Tag";
+			prefix = "(410)";
+		}
+		416
+		{
+			title = "Start Adjustable Flickering Light";
+			prefix = "(416)";
+		}
+		417
+		{
+			title = "Start Adjustable Pulsating Light";
+			prefix = "(417)";
+		}
+		418
+		{
+			title = "Start Adjustable Blinking Light (unsynchronized)";
+			prefix = "(418)";
+		}
+		419
+		{
+			title = "Start Adjustable Blinking Light (synchronized)";
+			prefix = "(419)";
+		}
+		420
+		{
+			title = "Fade Light Level";
+			prefix = "(420)";
+		}
+		421
+		{
+			title = "Stop Lighting Effect";
+			prefix = "(421)";
+		}
+		435
+		{
+			title = "Change Plane Scroller Direction";
+			prefix = "(435)";
+		}
+	}
+
+	linedefexecplane
+	{
+		title = "Linedef Executor (plane movement)";
+
+		403
+		{
+			title = "Move Tagged Sector's Floor";
+			prefix = "(403)";
+		}
+		404
+		{
+			title = "Move Tagged Sector's Ceiling";
+			prefix = "(404)";
+		}
+		405
+		{
+			title = "Move Floor According to Front Texture Offsets";
+			prefix = "(405)";
+		}
+		407
+		{
+			title = "Move Ceiling According to Front Texture Offsets";
+			prefix = "(407)";
+		}
+		411
+		{
+			title = "Stop Plane Movement";
+			prefix = "(411)";
+		}
+		428
+		{
+			title = "Start Platform Movement";
+			prefix = "(428)";
+		}
+		429
+		{
+			title = "Crush Ceiling Once";
+			prefix = "(429)";
+		}
+		430
+		{
+			title = "Crush Floor Once";
+			prefix = "(430)";
+		}
+		431
+		{
+			title = "Crush Floor and Ceiling Once";
+			prefix = "(431)";
+		}
+	}
+
+	linedefexecplayer
+	{
+		title = "Linedef Executor (player/object)";
+
+		412
+		{
+			title = "Teleporter";
+			prefix = "(412)";
+		}
+		425
+		{
+			title = "Change Object State";
+			prefix = "(425)";
+		}
+		426
+		{
+			title = "Stop Object";
+			prefix = "(426)";
+		}
+		427
+		{
+			title = "Award Score";
+			prefix = "(427)";
+		}
+		432
+		{
+			title = "Enable/Disable 2D Mode";
+			prefix = "(432)";
+		}
+		433
+		{
+			title = "Enable/Disable Gravity Flip";
+			prefix = "(433)";
+		}
+		434
+		{
+			title = "Award Power-Up";
+			prefix = "(434)";
+		}
+		437
+		{
+			title = "Disable Player Control";
+			prefix = "(437)";
+		}
+		438
+		{
+			title = "Change Object Size";
+			prefix = "(438)";
+		}
+		442
+		{
+			title = "Change Object Type State";
+			prefix = "(442)";
+		}
+		457
+		{
+			title = "Track Object's Angle";
+			prefix = "(457)";
+		}
+		458
+		{
+			title = "Stop Tracking Object's Angle";
+			prefix = "(458)";
+		}
+		460
+		{
+			title = "Award Rings";
+			prefix = "(460)";
+		}
+		461
+		{
+			title = "Spawn Object";
+			prefix = "(461)";
+		}
+		462
+		{
+			title = "Stop Timer/Exit Stage in Record Attack";
+			prefix = "(462)";
+		}
+	}
+
+	linedefexecmisc
+	{
+		title = "Linedef Executor (misc.)";
+
+		413
+		{
+			title = "Change Music";
+			prefix = "(413)";
+		}
+		414
+		{
+			title = "Play Sound Effect";
+			prefix = "(414)";
+		}
+		415
+		{
+			title = "Run Script";
+			prefix = "(415)";
+		}
+		422
+		{
+			title = "Switch to Cut-Away View";
+			prefix = "(422)";
+		}
+		423
+		{
+			title = "Change Sky";
+			prefix = "(423)";
+		}
+		424
+		{
+			title = "Change Weather";
+			prefix = "(424)";
+		}
+		436
+		{
+			title = "Shatter FOF";
+			prefix = "(436)";
+		}
+		439
+		{
+			title = "Change Tagged Linedef's Textures";
+			prefix = "(439)";
+		}
+		440
+		{
+			title = "Start Metal Sonic Race";
+			prefix = "(440)";
+		}
+		441
+		{
+			title = "Condition Set Trigger";
+			prefix = "(441)";
+		}
+		443
+		{
+			title = "Call Lua Function";
+			prefix = "(443)";
+		}
+		444
+		{
+			title = "Earthquake";
+			prefix = "(444)";
+		}
+		445
+		{
+			title = "Make FOF Disappear/Reappear";
+			prefix = "(445)";
+		}
+		446
+		{
+			title = "Make FOF Crumble";
+			prefix = "(446)";
+		}
+		447
+		{
+			title = "Change Tagged Sector's Colormap";
+			prefix = "(447)";
+		}
+		448
+		{
+			title = "Change Skybox";
+			prefix = "(448)";
+		}
+		449
+		{
+			title = "Enable Bosses with Parameter";
+			prefix = "(449)";
+		}
+		450
+		{
+			title = "Execute Linedef Executor (specific tag)";
+			prefix = "(450)";
+		}
+		451
+		{
+			title = "Execute Linedef Executor (random tag in range)";
+			prefix = "(451)";
+		}
+		452
+		{
+			title = "Set FOF Translucency";
+			prefix = "(452)";
+		}
+		453
+		{
+			title = "Fade FOF";
+			prefix = "(453)";
+		}
+		454
+		{
+			title = "Stop Fading FOF";
+			prefix = "(454)";
+		}
+		455
+		{
+			title = "Fade Tagged Sector's Colormap";
+			prefix = "(455)";
+		}
+		456
+		{
+			title = "Stop Fading Tagged Sector's Colormap";
+			prefix = "(456)";
+		}
+		459
+		{
+			title = "Control Text Prompt";
+			prefix = "(459)";
+		}
+	}
+
+	linedefexecpoly
+	{
+		title = "Linedef Executor (polyobject)";
+
+		480
+		{
+			title = "Door Slide";
+			prefix = "(480)";
+		}
+		481
+		{
+			title = "Door Swing";
+			prefix = "(481)";
+		}
+		482
+		{
+			title = "Move";
+			prefix = "(482)";
+		}
+		483
+		{
+			title = "Move, Override";
+			prefix = "(483)";
+		}
+		484
+		{
+			title = "Rotate Right";
+			prefix = "(484)";
+		}
+		485
+		{
+			title = "Rotate Right, Override";
+			prefix = "(485)";
+		}
+		486
+		{
+			title = "Rotate Left";
+			prefix = "(486)";
+		}
+		487
+		{
+			title = "Rotate Left, Override";
+			prefix = "(487)";
+		}
+		488
+		{
+			title = "Move by Waypoints";
+			prefix = "(488)";
+		}
+		489
+		{
+			title = "Turn Invisible, Intangible";
+			prefix = "(489)";
+		}
+		490
+		{
+			title = "Turn Visible, Tangible";
+			prefix = "(490)";
+		}
+		491
+		{
+			title = "Set Translucency";
+			prefix = "(491)";
+		}
+		492
+		{
+			title = "Fade Translucency";
+			prefix = "(492)";
+		}
+	}
+
+	wallscroll
+	{
+		title = "Wall Scrolling";
+
+		500
+		{
+			title = "Scroll Wall Front Side Left";
+			prefix = "(500)";
+		}
+		501
+		{
+			title = "Scroll Wall Front Side Right";
+			prefix = "(501)";
+		}
+		502
+		{
+			title = "Scroll Wall According to Linedef";
+			prefix = "(502)";
+		}
+		503
+		{
+			title = "Scroll Wall According to Linedef (Accelerative)";
+			prefix = "(503)";
+		}
+		504
+		{
+			title = "Scroll Wall According to Linedef (Displacement)";
+			prefix = "(504)";
+		}
+		505
+		{
+			title = "Scroll Texture by Front Side Offsets";
+			prefix = "(505)";
+		}
+		506
+		{
+			title = "Scroll Texture by Back Side Offsets";
+			prefix = "(506)";
+		}
+	}
+
+	planescroll
+	{
+		title = "Plane Scrolling";
+
+		510
+		{
+			title = "Scroll Floor Texture";
+			prefix = "(510)";
+		}
+		511
+		{
+			title = "Scroll Floor Texture (Accelerative)";
+			prefix = "(511)";
+		}
+		512
+		{
+			title = "Scroll Floor Texture (Displacement)";
+			prefix = "(512)";
+		}
+		513
+		{
+			title = "Scroll Ceiling Texture";
+			prefix = "(513)";
+		}
+		514
+		{
+			title = "Scroll Ceiling Texture (Accelerative)";
+			prefix = "(514)";
+		}
+		515
+		{
+			title = "Scroll Ceiling Texture (Displacement)";
+			prefix = "(515)";
+		}
+		520
+		{
+			title = "Carry Objects on Floor";
+			prefix = "(520)";
+		}
+		521
+		{
+			title = "Carry Objects on Floor (Accelerative)";
+			prefix = "(521)";
+		}
+		522
+		{
+			title = "Carry Objects on Floor (Displacement)";
+			prefix = "(522)";
+		}
+		523
+		{
+			title = "Carry Objects on Ceiling";
+			prefix = "(523)";
+		}
+		524
+		{
+			title = "Carry Objects on Ceiling (Accelerative)";
+			prefix = "(524)";
+		}
+		525
+		{
+			title = "Carry Objects on Ceiling (Displacement)";
+			prefix = "(525)";
+		}
+		530
+		{
+			title = "Scroll Floor Texture and Carry Objects";
+			prefix = "(530)";
+		}
+		531
+		{
+			title = "Scroll Floor Texture and Carry Objects (Accelerative)";
+			prefix = "(531)";
+		}
+		532
+		{
+			title = "Scroll Floor Texture and Carry Objects (Displacement)";
+			prefix = "(532)";
+		}
+		533
+		{
+			title = "Scroll Ceiling Texture and Carry Objects";
+			prefix = "(533)";
+		}
+		534
+		{
+			title = "Scroll Ceiling Texture and Carry Objects (Accelerative)";
+			prefix = "(534)";
+		}
+		535
+		{
+			title = "Scroll Ceiling Texture and Carry Objects (Displacement)";
+			prefix = "(535)";
+		}
+	}
+
+	pusher
+	{
+		title = "Pusher";
+
+		541
+		{
+			title = "Wind";
+			prefix = "(541)";
+		}
+		542
+		{
+			title = "Upwards Wind";
+			prefix = "(542)";
+		}
+		543
+		{
+			title = "Downwards Wind";
+			prefix = "(543)";
+		}
+		544
+		{
+			title = "Current";
+			prefix = "(544)";
+		}
+		545
+		{
+			title = "Upwards Current";
+			prefix = "(545)";
+		}
+		546
+		{
+			title = "Downwards Current";
+			prefix = "(546)";
+		}
+		547
+		{
+			title = "Push/Pull";
+			prefix = "(547)";
+		}
+	}
+
+	light
+	{
+		title = "Lighting";
+
+		600
+		{
+			title = "Floor Lighting";
+			prefix = "(600)";
+		}
+		601
+		{
+			title = "Ceiling Lighting";
+			prefix = "(601)";
+		}
+		602
+		{
+			title = "Adjustable Pulsating Light";
+			prefix = "(602)";
+		}
+		603
+		{
+			title = "Adjustable Flickering Light";
+			prefix = "(603)";
+		}
+		604
+		{
+			title = "Adjustable Blinking Light (unsynchronized)";
+			prefix = "(604)";
+		}
+		605
+		{
+			title = "Adjustable Blinking Light (synchronized)";
+			prefix = "(605)";
+		}
+		606
+		{
+			title = "Colormap";
+			prefix = "(606)";
+		}
+	}
+
+	slope
+	{
+		title = "Slope";
+
+		700
+		{
+			title = "Slope Frontside Floor";
+			prefix = "(700)";
+		}
+		701
+		{
+			title = "Slope Frontside Ceiling";
+			prefix = "(701)";
+		}
+		702
+		{
+			title = "Slope Frontside Floor and Ceiling";
+			prefix = "(702)";
+		}
+		703
+		{
+			title = "Slope Frontside Floor and Backside Ceiling";
+			prefix = "(703)";
+ยด		}
+		704
+		{
+			title = "Slope Frontside Floor by 3 Tagged Vertex Things";
+			prefix = "(704)";
+		}
+		705
+		{
+			title = "Slope Frontside Ceiling by 3 Tagged Vertex Things";
+			prefix = "(705)";
+		}
+		710
+		{
+			title = "Slope Backside Floor";
+			prefix = "(710)";
+		}
+		711
+		{
+			title = "Slope Backside Ceiling";
+			prefix = "(711)";
+		}
+		712
+		{
+			title = "Slope Backside Floor and Ceiling";
+			prefix = "(712)";
+		}
+		713
+		{
+			title = "Slope Backside Floor and Frontside Ceiling";
+			prefix = "(713)";
+		}
+		714
+		{
+			title = "Slope Backside Floor by 3 Tagged Vertex Things";
+			prefix = "(714)";
+		}
+		715
+		{
+			title = "Slope Backside Ceiling by 3 Tagged Vertex Things";
+			prefix = "(715)";
+		}
+		720
+		{
+			title = "Copy Frontside Floor Slope from Line Tag";
+			prefix = "(720)";
+		}
+		721
+		{
+			title = "Copy Frontside Ceiling Slope from Line Tag";
+			prefix = "(721)";
+		}
+		722
+		{
+			title = "Copy Frontside Floor and Ceiling Slope from Line Tag";
+			prefix = "(722)";
+		}
+		799
+		{
+			title = "Set Tagged Dynamic Slope Vertex to Front Sector Height";
+			prefix = "(799)";
+		}
+	}
+
+	transwall
+	{
+		title = "Translucent Wall";
+
+		900
+		{
+			title = "90% Opaque";
+			prefix = "(900)";
+		}
+		901
+		{
+			title = "80% Opaque";
+			prefix = "(901)";
+		}
+		902
+		{
+			title = "70% Opaque";
+			prefix = "(902)";
+		}
+		903
+		{
+			title = "60% Opaque";
+			prefix = "(903)";
+		}
+		904
+		{
+			title = "50% Opaque";
+			prefix = "(904)";
+		}
+		905
+		{
+			title = "40% Opaque";
+			prefix = "(905)";
+		}
+		906
+		{
+			title = "30% Opaque";
+			prefix = "(906)";
+		}
+		907
+		{
+			title = "20% Opaque";
+			prefix = "(907)";
+		}
+		908
+		{
+			title = "10% Opaque";
+			prefix = "(908)";
+		}
+		909
+		{
+			title = "Fog Wall";
+			prefix = "(909)";
+		}
+	}
+}
+
+udmf
+{
+	misc
+	{
+		title = "Miscellaneous";
+
+		0
+		{
+			title = "None";
+			prefix = "(0)";
+		}
+	}
+
+	polyobject
+	{
+		title = "PolyObject";
+
+		20
+		{
+			title = "First Line";
+			prefix = "(20)";
+			arg0
+			{
+				title = "PolyObject ID";
+				type = 14;
+			}
+			arg1
+			{
+				title = "Parent ID";
+				type = 14;
+			}
+			arg2
+			{
+				title = "Translucency";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Don't render insides";
+					2 = "Intangible";
+					4 = "Stopped by pushables";
+					8 = "Don't render planes";
+					16 = "Trigger linedef executor on touch";
+					32 = "Crush player";
+					64 = "Cut cyan flat pixels";
+				}
+			}
+			arg4
+			{
+				title = "Trigger linedef tag";
+				type = 15;
+			}
+		}
+	}
+
+	fof
+	{
+		title = "FOF";
+
+		100
+		{
+			title = "Solid";
+			prefix = "(100)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Visibility";
+				type = 12;
+				enum
+				{
+					1 = "Don't render planes";
+					2 = "Don't render sides";
+				}
+			}
+			arg2
+			{
+				title = "Translucency";
+				type = 11;
+				enum
+				{
+					0 = "Opaque";
+					1 = "Translucent, no insides";
+					2 = "Translucent, render insides";
+				}
+			}
+			arg3
+			{
+				title = "Tangibility";
+				type = 12;
+				enum = "tangibility";
+			}
+			arg4
+			{
+				title = "Cast shadow?";
+				type = 11;
+				enum = "yesno";
+			}
+		}
+
+		120
+		{
+			title = "Water";
+			prefix = "(120)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+				    1 = "Opaque";
+					2 = "Don't render sides";
+					4 = "Render separate light level";
+					8 = "Use target light level";
+					16 = "No ripple effect";
+					32 = "Goo physics";
+				}
+			}
+		}
+	}
+
+	linedefexecmisc
+	{
+		title = "Linedef Executor (misc.)";
+
+		443
+		{
+			title = "Call Lua Function";
+			prefix = "(443)";
+			stringarg0
+			{
+				title = "Function name";
+				type = 2;
+			}
+		}
+
+		447
+		{
+			title = "Change Tagged Sector's Colormap";
+			prefix = "(447)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Colormap sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to existing colormap";
+					2 = "Subtract light R";
+					4 = "Subtract light G";
+					8 = "Subtract light B";
+					16 = "Subtract light A";
+					32 = "Subtract fade R";
+					64 = "Subtract fade G";
+					128 = "Subtract fade B";
+					256 = "Subtract fade A";
+					512 = "Subtract fadestart";
+					1024 = "Subtract fadeend";
+					2048 = "Ignore flags";
+				}
+			}
+		}
+
+		455
+		{
+			title = "Fade Tagged Sector's Colormap";
+			prefix = "(455)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Colormap sector tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Fade duration";
+			}
+			arg3
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Add to existing colormap";
+					2 = "Subtract light R";
+					4 = "Subtract light G";
+					8 = "Subtract light B";
+					16 = "Subtract light A";
+					32 = "Subtract fade R";
+					64 = "Subtract fade G";
+					128 = "Subtract fade B";
+					256 = "Subtract fade A";
+					512 = "Subtract fadestart";
+					1024 = "Subtract fadeend";
+					2048 = "Ignore flags";
+					4096 = "Fade from invisible black";
+					8192 = "Interrupt ongoing fades";
+				}
+			}
+		}
+
+		456
+		{
+			title = "Stop Fading Tagged Sector's Colormap";
+			prefix = "(456)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+		}
+
+		465
+		{
+			title = "Set Linedef Executor Delay";
+			prefix = "(465)";
+			arg0
+			{
+				title = "Linedef tag";
+				type = 15;
+			}
+			arg1
+			{
+				title = "Value";
+			}
+			arg2
+			{
+				title = "Set/add?";
+				type = 11;
+				enum
+				{
+					0 = "Set";
+					1 = "Add";
+				}
+			}
+		}
+	}
+
+	light
+	{
+		606
+		{
+			title = "Copy Colormap";
+			prefix = "(606)";
+			arg0
+			{
+				title = "Target sector tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Colormap sector tag";
+				type = 13;
+			}
+		}
+	}
+
+	slope
+	{
+		title = "Slope";
+
+		700
+		{
+			title = "Create Sector-Based Slope";
+			prefix = "(700)";
+			id = "plane_align";
+			arg0
+			{
+				title = "Floor";
+				type = 11;
+				enum = "frontback";
+			}
+			arg1
+			{
+				title = "Ceiling";
+				type = 11;
+				enum = "frontback";
+			}
+			arg2
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "No physics";
+					2 = "Dynamic";
+				}
+			}
+		}
+
+		704
+		{
+			title = "Create Vertex-Based Slope";
+			prefix = "(704)";
+			arg0
+			{
+				title = "Plane";
+				type = 11;
+				enum
+				{
+					0 = "Front floor";
+					1 = "Front ceiling";
+					2 = "Back floor";
+					3 = "Back ceiling";
+				}
+			}
+			arg1
+			{
+				title = "Vertex 1 tag";
+				type = 14;
+			}
+			arg2
+			{
+				title = "Vertex 2 tag";
+				type = 14;
+			}
+			arg3
+			{
+				title = "Vertex 3 tag";
+				type = 14;
+			}
+			arg4
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "No physics";
+					2 = "Dynamic";
+				}
+			}
+		}
+
+		720
+		{
+			title = "Copy Slope";
+			prefix = "(720)";
+			arg0
+			{
+				title = "Front floor tag";
+				type = 13;
+			}
+			arg1
+			{
+				title = "Front ceiling tag";
+				type = 13;
+			}
+			arg2
+			{
+				title = "Back floor tag";
+				type = 13;
+			}
+			arg3
+			{
+				title = "Back ceiling tag";
+				type = 13;
+			}
+			arg4
+			{
+				title = "Share slope";
+				type = 12;
+				enum
+				{
+					1 = "Front floor to back sector";
+					2 = "Back floor to front sector";
+					4 = "Front ceiling to back sector";
+					8 = "Back ceiling to front sector";
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..68629149e1b1bccb71e7d3752af271c3441031a7
--- /dev/null
+++ b/extras/conf/udb/Includes/SRB222_misc.cfg
@@ -0,0 +1,625 @@
+linedefflags
+{
+	1 = "[0] Impassable";
+	2 = "[1] Block Enemies";
+	4 = "[2] Double-Sided";
+	8 = "[3] Upper Unpegged";
+	16 = "[4] Lower Unpegged";
+	32 = "[5] Slope Skew (E1)";
+	64 = "[6] Not Climbable";
+	128 = "[7] No Midtexture Skew (E2)";
+	256 = "[8] Peg Midtexture (E3)";
+	512 = "[9] Solid Midtexture (E4)";
+	1024 = "[10] Repeat Midtexture (E5)";
+	2048 = "[11] Netgame Only";
+	4096 = "[12] No Netgame";
+	8192 = "[13] Effect 6";
+	16384 = "[14] Bouncy Wall";
+	32768 = "[15] Transfer Line";
+}
+
+
+// Linedef flags UDMF translation table
+// This is needed for copy/paste and prefabs to work properly
+// When the UDMF field name is prefixed with ! it is inverted
+linedefflagstranslation
+{
+	1 = "blocking";
+	2 = "blockmonsters";
+	4 = "twosided";
+	8 = "dontpegtop";
+	16 = "dontpegbottom";
+	32 = "skewtd";
+	64 = "noclimb";
+	128 = "noskew";
+	256 = "midpeg";
+	512 = "midsolid";
+	1024 = "wrapmidtex";
+	2048 = "netonly";
+	4096 = "nonet";
+	8192 = "effect6";
+	16384 = "bouncy";
+	32768 = "transfer";
+}
+
+
+linedefflags_udmf
+{
+	blocking = "Impassable";
+	blockmonsters = "Block Enemies";
+	twosided = "Double-Sided";
+	dontpegtop = "Upper Unpegged";
+	dontpegbottom = "Lower Unpegged";
+	skewtd = "Slope Skew";
+	noclimb = "Not Climbable";
+	noskew = "No Midtexture Skew";
+	midpeg = "Peg Midtexture";
+	midsolid = "Solid Midtexture";
+	wrapmidtex = "Repeat Midtexture";
+	netonly = "Netgame Only";
+	nonet = "No Netgame";
+	effect6 = "Effect 6";
+	bouncy = "Bouncy Wall";
+	transfer = "Transfer Line";
+}
+
+/*linedefrenderstyles
+{
+	translucent = "Translucent";
+	fog = "Fog";
+}*/
+
+sectorflags
+{
+	colormapfog = "Fog Planes in Colormap";
+	colormapfadesprites = "Fade Fullbright in Colormap";
+	colormapprotected = "Protected Colormap";
+}
+
+thingflags
+{
+	1 = "[1] Extra";
+	2 = "[2] Flip";
+	4 = "[4] Special";
+	8 = "[8] Ambush";
+}
+
+// THING FLAGS
+thingflags_udmf
+{
+	extra = "Extra";
+	flip = "Flip";
+	special = "Special";
+	ambush = "Ambush";
+}
+
+
+// Thing flags UDMF translation table
+// This is needed for copy/paste and prefabs to work properly
+// When the UDMF field name is prefixed with ! it is inverted
+thingflagstranslation
+{
+	1 = "extra";
+	2 = "flip";
+	4 = "special";
+	8 = "ambush";
+}
+
+
+// DEFAULT SECTOR BRIGHTNESS LEVELS
+sectorbrightness
+{
+	255;
+	248;
+	240;
+	232;
+	224;
+	216;
+	208;
+	200;
+	192;
+	184;
+	176;
+	168;
+	160;
+	152;
+	144;
+	136;
+	128;
+	120;
+	112;
+	104;
+	96;
+	88;
+	80;
+	72;
+	64;
+	56;
+	48;
+	40;
+	32;
+	24;
+	16;
+	8;
+	0;
+}
+
+/*
+TEXTURES AND FLAT SOURCES
+This tells Doom Builder where to find the information for textures
+and flats in the IWAD file, Addition WAD file and Map WAD file.
+
+Start and end lumps must be given in a structure (of which the
+key name doesnt matter) and any textures or flats in between them
+are loaded in either the textures category or flats category.
+
+For textures: PNAMES, TEXTURE1 and TEXTURE2 are loaded by default.
+*/
+textures
+{
+	zdoom1
+	{
+		start = "TX_START";
+		end = "TX_END";
+	}
+}
+
+/*
+ADDITIONAL UNIVERSAL DOOM MAP FORMAT FIELD DEFINITIONS
+Only add fields here that Doom Builder does not edit with its own user-interface!
+The "default" field must match the UDMF specifications!
+
+Field data types:
+0 = integer *
+1 = float
+2 = string
+3 = bool
+4 = linedef action (integer) *
+5 = sector effect (integer) *
+6 = texture (string)
+7 = flat (string)
+8 = angle in degrees (integer)
+9 = angle in radians (float)
+10 = XXRRGGBB color (integer)
+11 = enum option (integer) *
+12 = enum bits (integer) *
+13 = sector tag (integer) *
+14 = thing tag (integer) *
+15 = linedef tag (integer) *
+16 = enum option (string)
+17 = angle in degrees (float)
+22 = byte angle (integer)
+*/
+universalfields
+{
+	sector
+	{
+		lightalpha
+		{
+			type = 0;
+			default = 25;
+		}
+
+		fadealpha
+		{
+			type = 0;
+			default = 25;
+		}
+
+		fadestart
+		{
+			type = 0;
+			default = 0;
+		}
+
+		fadeend
+		{
+			type = 0;
+			default = 33;
+		}
+
+		foglighting
+		{
+			type = 3;
+			default = false;
+		}
+	}
+
+	linedef
+	{
+		arg5
+		{
+			type = 0;
+			default = 0;
+		}
+		stringarg0
+		{
+			type = 2;
+			default = "";
+		}
+		stringarg1
+		{
+			type = 2;
+			default = "";
+		}
+		executordelay
+		{
+			type = 0;
+			default = 0;
+		}
+	}
+
+	sidedef
+	{
+		repeatcnt
+		{
+			type = 0;
+			default = 0;
+		}
+	}
+
+	thing
+	{
+	}
+}
+
+/*
+MAP LUMP NAMES
+Map lumps are loaded with the map as long as they are right after each other. When the editor
+meets a lump which is not defined in this list it will ignore the map if not satisfied.
+The order of items defines the order in which lumps will be written to WAD file on save.
+To indicate the map header lump, use ~MAP
+
+Legenda:
+required = Lump is required to exist.
+blindcopy = Lump will be copied along with the map blindly. (useful for lumps Doom Builder doesn't use)
+nodebuild = The nodebuilder generates this lump.
+allowempty = The nodebuilder is allowed to leave this lump empty.
+scriptbuild = This lump is a text-based script, which should be compiled using current script compiler;
+script = This lump is a text-based script. Specify the filename of the script configuration to use.
+*/
+
+doommaplumpnames
+{
+	~MAP
+	{
+		required = true;
+		blindcopy = true;
+		nodebuild = false;
+	}
+
+	THINGS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = true;
+	}
+
+	LINEDEFS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SIDEDEFS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	VERTEXES
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SEGS
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SSECTORS
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	NODES
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SECTORS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	REJECT
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	BLOCKMAP
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = true;
+	}
+}
+
+udmfmaplumpnames
+{
+	ZNODES
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	REJECT
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	BLOCKMAP
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = true;
+	}
+}
+
+// ENUMERATIONS
+// These are enumerated lists for linedef types and UDMF fields.
+// Reserved names are: angledeg, anglerad, color, texture, flat
+enums
+{
+	falsetrue
+	{
+		0 = "False";
+		1 = "True";
+	}
+
+	yesno
+	{
+		0 = "Yes";
+		1 = "No";
+	}
+
+	noyes
+	{
+		0 = "No";
+		1 = "Yes";
+	}
+
+	onoff
+	{
+		0 = "On";
+		1 = "Off";
+	}
+
+	offon
+	{
+		0 = "Off";
+		1 = "On";
+	}
+
+	updown
+	{
+		0 = "Up";
+		1 = "Down";
+	}
+
+	downup
+	{
+		0 = "Down";
+		1 = "Up";
+	}
+
+	frontback
+	{
+		0 = "None";
+		1 = "Front";
+		2 = "Back";
+	}
+
+	tangibility
+	{
+		1 = "Intangible from top";
+		2 = "Intangible from bottom";
+		4 = "Don't block players";
+		8 = "Don't block non-players";
+	}
+}
+
+//Default things filters
+thingsfilters
+{
+
+	filter0
+	{
+		name = "Player starts";
+		category = "starts";
+		type = -1;
+	}
+
+
+	filter1
+	{
+		name = "Enemies";
+		category = "enemies";
+		type = -1;
+
+	}
+
+
+	filter2
+	{
+		name = "NiGHTS Track";
+		category = "nightstrk";
+		type = -1;
+
+	}
+
+
+	filter3
+	{
+		name = "Normal Gravity";
+		category = "";
+		type = -1;
+
+		fields
+		{
+			2 = false;
+		}
+
+	}
+
+
+	filter4
+	{
+		name = "Reverse Gravity";
+		category = "";
+		type = -1;
+
+		fields
+		{
+			2 = true;
+		}
+
+	}
+}
+
+// Special linedefs
+speciallinedefs
+{
+	soundlinedefflag = 64;	// See linedefflags
+	singlesidedflag = 1;	// See linedefflags
+	doublesidedflag = 4;	// See linedefflags
+	impassableflag = 1;
+	upperunpeggedflag = 8;
+	lowerunpeggedflag = 16;
+	repeatmidtextureflag = 1024;
+	pegmidtextureflag = 256;
+}
+
+speciallinedefs_udmf
+{
+	soundlinedefflag = "noclimb";
+	singlesidedflag = "blocking";
+	doublesidedflag = "twosided";
+	impassableflag = "blocking";
+	upperunpeggedflag = "dontpegtop";
+	lowerunpeggedflag = "dontpegbottom";
+	repeatmidtextureflag = "wrapmidtex";
+	pegmidtextureflag = "midpeg";
+}
+
+scriptlumpnames
+{
+	MAINCFG
+	{
+		script = "SOC.cfg";
+	}
+
+	OBJCTCFG
+	{
+		script = "SOC.cfg";
+	}
+
+	SOC_
+	{
+		script = "SOC.cfg";
+		isprefix = true;
+	}
+
+	LUA_
+	{
+		script = "Lua.cfg";
+		isprefix = true;
+	}
+}
+
+// Texture sources
+textures
+{
+	zdoom1
+	{
+		start = "TX_START";
+		end = "TX_END";
+	}
+}
+
+// Patch sources
+patches
+{
+	standard1
+	{
+		start = "P_START";
+		end = "P_END";
+	}
+
+	standard2
+	{
+		start = "PP_START";
+		end = "PP_END";
+	}
+}
+
+// Sprite sources
+sprites
+{
+	standard1
+	{
+		start = "S_START";
+		end = "S_END";
+	}
+
+	standard2
+	{
+		start = "SS_START";
+		end = "SS_END";
+	}
+}
+
+// Flat sources
+flats
+{
+	standard1
+	{
+		start = "F_START";
+		end = "F_END";
+	}
+
+	standard2
+	{
+		start = "FF_START";
+		end = "FF_END";
+	}
+
+	standard3
+	{
+		start = "FF_START";
+		end = "F_END";
+	}
+
+	standard4
+	{
+		start = "F_START";
+		end = "FF_END";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/udb/Includes/SRB222_sectors.cfg b/extras/conf/udb/Includes/SRB222_sectors.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..5cc14ad0fb1a6b58c7d9ab29e23045b4a7ff8b1e
--- /dev/null
+++ b/extras/conf/udb/Includes/SRB222_sectors.cfg
@@ -0,0 +1,109 @@
+sectortypes
+{
+	0 = "Normal";
+	1 = "Damage";
+	2 = "Damage (Water)";
+	3 = "Damage (Fire)";
+	4 = "Damage (Electrical)";
+	5 = "Spikes";
+	6 = "Death Pit (Camera Tilt)";
+	7 = "Death Pit (No Camera Tilt)";
+	8 = "Instant Kill";
+	9 = "Ring Drainer (Floor Touch)";
+	10 = "Ring Drainer (Anywhere in Sector)";
+	11 = "Special Stage Damage";
+	12 = "Space Countdown";
+	13 = "Ramp Sector (double step-up/down)";
+	14 = "Non-Ramp Sector (no step-down)";
+	15 = "Bouncy FOF";
+	16 = "Trigger Line Ex. (Pushable Objects)";
+	32 = "Trigger Line Ex. (Anywhere, All Players)";
+	48 = "Trigger Line Ex. (Floor Touch, All Players)";
+	64 = "Trigger Line Ex. (Anywhere in Sector)";
+	80 = "Trigger Line Ex. (Floor Touch)";
+	96 = "Trigger Line Ex. (Emerald Check)";
+	112 = "Trigger Line Ex. (NiGHTS Mare)";
+	128 = "Check for Linedef Executor on FOFs";
+	144 = "Egg Capsule";
+	160 = "Special Stage Time/Spheres Parameters";
+	176 = "Custom Global Gravity";
+	512 = "Wind/Current";
+	1024 = "Conveyor Belt";
+	1280 = "Speed Pad";
+	4096 = "Star Post Activator";
+	8192 = "Exit/Special Stage Pit/Return Flag";
+	12288 = "CTF Red Team Base";
+	16384 = "CTF Blue Team Base";
+	20480 = "Fan Sector";
+	24576 = "Super Sonic Transform";
+	28672 = "Force Spin";
+	32768 = "Zoom Tube Start";
+	36864 = "Zoom Tube End";
+	40960 = "Circuit Finish Line";
+	45056 = "Rope Hang";
+	49152 = "Intangible to the Camera";
+}
+
+gen_sectortypes
+{
+	first
+	{
+		0 = "Normal";
+		1 = "Damage";
+		2 = "Damage (Water)";
+		3 = "Damage (Fire)";
+		4 = "Damage (Electrical)";
+		5 = "Spikes";
+		6 = "Death Pit (Camera Tilt)";
+		7 = "Death Pit (No Camera Tilt)";
+		8 = "Instant Kill";
+		9 = "Ring Drainer (Floor Touch)";
+		10 = "Ring Drainer (Anywhere in Sector)";
+		11 = "Special Stage Damage";
+		12 = "Space Countdown";
+		13 = "Ramp Sector (double step-up/down)";
+		14 = "Non-Ramp Sector (no step-down)";
+		15 = "Bouncy FOF";
+	}
+
+	second
+	{
+		0 = "Normal";
+		16 = "Trigger Line Ex. (Pushable Objects)";
+		32 = "Trigger Line Ex. (Anywhere, All Players)";
+		48 = "Trigger Line Ex. (Floor Touch, All Players)";
+		64 = "Trigger Line Ex. (Anywhere in Sector)";
+		80 = "Trigger Line Ex. (Floor Touch)";
+		96 = "Trigger Line Ex. (Emerald Check)";
+		112 = "Trigger Line Ex. (NiGHTS Mare)";
+		128 = "Check for Linedef Executor on FOFs";
+		144 = "Egg Capsule";
+		160 = "Special Stage Time/Spheres Parameters";
+		176 = "Custom Global Gravity";
+	}
+
+	third
+	{
+		0 = "Normal";
+		512 = "Wind/Current";
+		1024 = "Conveyor Belt";
+		1280 = "Speed Pad";
+	}
+
+	fourth
+	{
+		0 = "Normal";
+		4096 = "Star Post Activator";
+		8192 = "Exit/Special Stage Pit/Return Flag";
+		12288 = "CTF Red Team Base";
+		16384 = "CTF Blue Team Base";
+		20480 = "Fan Sector";
+		24576 = "Super Sonic Transform";
+		28672 = "Force Spin";
+		32768 = "Zoom Tube Start";
+		36864 = "Zoom Tube End";
+		40960 = "Circuit Finish Line";
+		45056 = "Rope Hang";
+		49152 = "Intangible to the Camera";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..113c1a4c26eb08a7109da2afeba747dfb1cdda14
--- /dev/null
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -0,0 +1,3142 @@
+// THING TYPES------------------------------------------------------------------
+// Color values: 1-Dark_Blue 2-Dark_Green 3-Turqoise 4-Dark_Red 5-Purple 6-Brown 7-Gray
+// 8-Dark_Gray 9-Blue 10-Green 11-Cyan 12-Red 13-Magenta
+// 14-Yellow 15-White 16-Pink 17-Orange 18-Gold 19-Cream
+
+editor
+{
+	color = 15; // White
+	arrow = 1;
+	title = "<Editor Things>";
+	error = -1;
+	width = 8;
+	height = 16;
+	sort = 1;
+
+	3328 = "3D Mode Start";
+}
+
+starts
+{
+	color = 1; // Blue
+	arrow = 1;
+	title = "Player Starts";
+	width = 16;
+	height = 48;
+	sprite = "PLAYA0";
+
+	1
+	{
+		title = "Player 01 Start";
+		sprite = "PLAYA0";
+	}
+	2
+	{
+		title = "Player 02 Start";
+		sprite = "PLAYA0";
+	}
+	3
+	{
+		title = "Player 03 Start";
+		sprite = "PLAYA0";
+	}
+	4
+	{
+		title = "Player 04 Start";
+		sprite = "PLAYA0";
+	}
+	5
+	{
+		title = "Player 05 Start";
+		sprite = "PLAYA0";
+	}
+	6
+	{
+		title = "Player 06 Start";
+		sprite = "PLAYA0";
+	}
+	7
+	{
+		title = "Player 07 Start";
+		sprite = "PLAYA0";
+	}
+	8
+	{
+		title = "Player 08 Start";
+		sprite = "PLAYA0";
+	}
+	9
+	{
+		title = "Player 09 Start";
+		sprite = "PLAYA0";
+	}
+	10
+	{
+		title = "Player 10 Start";
+		sprite = "PLAYA0";
+	}
+	11
+	{
+		title = "Player 11 Start";
+		sprite = "PLAYA0";
+	}
+	12
+	{
+		title = "Player 12 Start";
+		sprite = "PLAYA0";
+	}
+	13
+	{
+		title = "Player 13 Start";
+		sprite = "PLAYA0";
+	}
+	14
+	{
+		title = "Player 14 Start";
+		sprite = "PLAYA0";
+	}
+	15
+	{
+		title = "Player 15 Start";
+		sprite = "PLAYA0";
+	}
+	16
+	{
+		title = "Player 16 Start";
+		sprite = "PLAYA0";
+	}
+	17
+	{
+		title = "Player 17 Start";
+		sprite = "PLAYA0";
+	}
+	18
+	{
+		title = "Player 18 Start";
+		sprite = "PLAYA0";
+	}
+	19
+	{
+		title = "Player 19 Start";
+		sprite = "PLAYA0";
+	}
+	20
+	{
+		title = "Player 20 Start";
+		sprite = "PLAYA0";
+	}
+	21
+	{
+		title = "Player 21 Start";
+		sprite = "PLAYA0";
+	}
+	22
+	{
+		title = "Player 22 Start";
+		sprite = "PLAYA0";
+	}
+	23
+	{
+		title = "Player 23 Start";
+		sprite = "PLAYA0";
+	}
+	24
+	{
+		title = "Player 24 Start";
+		sprite = "PLAYA0";
+	}
+	25
+	{
+		title = "Player 25 Start";
+		sprite = "PLAYA0";
+	}
+	26
+	{
+		title = "Player 26 Start";
+		sprite = "PLAYA0";
+	}
+	27
+	{
+		title = "Player 27 Start";
+		sprite = "PLAYA0";
+	}
+	28
+	{
+		title = "Player 28 Start";
+		sprite = "PLAYA0";
+	}
+	29
+	{
+		title = "Player 29 Start";
+		sprite = "PLAYA0";
+	}
+	30
+	{
+		title = "Player 30 Start";
+		sprite = "PLAYA0";
+	}
+	31
+	{
+		title = "Player 31 Start";
+		sprite = "PLAYA0";
+	}
+	32
+	{
+		title = "Player 32 Start";
+		sprite = "PLAYA0";
+	}
+	33
+	{
+		title = "Match Start";
+		sprite = "NDRNA2A8";
+	}
+	34
+	{
+		title = "CTF Red Team Start";
+		sprite = "SIGNG0";
+	}
+	35
+	{
+		title = "CTF Blue Team Start";
+		sprite = "SIGNE0";
+	}
+}
+
+enemies
+{
+	color = 9; // Light_Blue
+	arrow = 1;
+	title = "Enemies";
+
+	100
+	{
+		title = "Crawla (Blue)";
+		sprite = "POSSA1";
+		width = 24;
+		height = 32;
+	}
+	101
+	{
+		title = "Crawla (Red)";
+		sprite = "SPOSA1";
+		width = 24;
+		height = 32;
+	}
+	102
+	{
+		title = "Stupid Dumb Unnamed RoboFish";
+		sprite = "FISHA0";
+		width = 8;
+		height = 28;
+	}
+	103
+	{
+		title = "Buzz (Gold)";
+		sprite = "BUZZA1";
+		width = 28;
+		height = 40;
+	}
+	104
+	{
+		title = "Buzz (Red)";
+		sprite = "RBUZA1";
+		width = 28;
+		height = 40;
+	}
+	108
+	{
+		title = "Deton";
+		sprite = "DETNA1";
+		width = 20;
+		height = 32;
+	}
+	110
+	{
+		title = "Turret";
+		sprite = "TRETA1";
+		width = 16;
+		height = 32;
+	}
+	111
+	{
+		title = "Pop-up Turret";
+		sprite = "TURRI1";
+		width = 12;
+		height = 64;
+	}
+	122
+	{
+		title = "Spring Shell (Green)";
+		sprite = "SSHLA1";
+		width = 24;
+		height = 40;
+	}
+	125
+	{
+		title = "Spring Shell (Yellow)";
+		sprite = "SSHLI1";
+		width = 24;
+		height = 40;
+	}
+	109
+	{
+		title = "Skim";
+		sprite = "SKIMA1";
+		width = 16;
+		height = 24;
+	}
+	113
+	{
+		title = "Jet Jaw";
+		sprite = "JJAWA3A7";
+		width = 12;
+		height = 20;
+	}
+	126
+	{
+		title = "Crushstacean";
+		sprite = "CRABA0";
+		width = 24;
+		height = 32;
+	}
+	138
+	{
+		title = "Banpyura";
+		sprite = "CR2BA0";
+		width = 24;
+		height = 32;
+	}
+	117
+	{
+		title = "Robo-Hood";
+		sprite = "ARCHA1";
+		width = 24;
+		height = 32;
+	}
+	118
+	{
+		title = "Lance-a-Bot";
+		sprite = "CBFSA1";
+		width = 32;
+		height = 72;
+	}
+	1113
+	{
+		title = "Suspicious Lance-a-Bot Statue";
+		sprite = "CBBSA1";
+		width = 32;
+		height = 72;
+	}
+	119
+	{
+		title = "Egg Guard";
+		sprite = "ESHIA1";
+		width = 16;
+		height = 48;
+	}
+	115
+	{
+		title = "Bird Aircraft Strike Hazard";
+		sprite = "VLTRF1";
+		width = 12;
+		height = 24;
+	}
+	120
+	{
+		title = "Green Snapper";
+		sprite = "GSNPA1";
+		width = 24;
+		height = 24;
+	}
+	121
+	{
+		title = "Minus";
+		sprite = "MNUSA0";
+		width = 24;
+		height = 32;
+	}
+	134
+	{
+		title = "Canarivore";
+		sprite = "CANAA0";
+		width = 12;
+		height = 80;
+		hangs = 1;
+	}
+	123
+	{
+		title = "Unidus";
+		sprite = "UNIDA1";
+		width = 18;
+		height = 36;
+	}
+	135
+	{
+		title = "Pterabyte Spawner";
+		sprite = "PTERA2A8";
+		width = 16;
+		height = 16;
+	}
+	136
+	{
+		title = "Pyre Fly";
+		sprite = "PYREA0";
+		width = 24;
+		height = 34;
+	}
+	137
+	{
+		title = "Dragonbomber";
+		sprite = "DRABA1";
+		width = 28;
+		height = 48;
+	}
+	105
+	{
+		title = "Jetty-Syn Bomber";
+		sprite = "JETBB1";
+		width = 20;
+		height = 50;
+	}
+	106
+	{
+		title = "Jetty-Syn Gunner";
+		sprite = "JETGB1";
+		width = 20;
+		height = 48;
+	}
+	112
+	{
+		title = "Spincushion";
+		sprite = "SHRPA1";
+		width = 16;
+		height = 24;
+	}
+	114
+	{
+		title = "Snailer";
+		sprite = "SNLRA3A7";
+		width = 24;
+		height = 48;
+	}
+	129
+	{
+		title = "Penguinator";
+		sprite = "PENGA1";
+		width = 24;
+		height = 32;
+	}
+	130
+	{
+		title = "Pophat";
+		sprite = "POPHA1";
+		width = 24;
+		height = 32;
+	}
+	107
+	{
+		title = "Crawla Commander";
+		sprite = "CCOMA1";
+		width = 16;
+		height = 32;
+	}
+	131
+	{
+		title = "Spinbobert";
+		sprite = "SBOBB0";
+		width = 32;
+		height = 32;
+	}
+	132
+	{
+		title = "Cacolantern";
+		sprite = "CACOA0";
+		width = 32;
+		height = 32;
+	}
+	133
+	{
+		title = "Hangster";
+		sprite = "HBATC1";
+		width = 24;
+		height = 24;
+		hangs = 1;
+	}
+	127
+	{
+		title = "Hive Elemental";
+		sprite = "HIVEA0";
+		width = 32;
+		height = 80;
+	}
+	128
+	{
+		title = "Bumblebore";
+		sprite = "BUMBA1";
+		width = 16;
+		height = 32;
+	}
+	124
+	{
+		title = "Buggle";
+		sprite = "BBUZA1";
+		width = 20;
+		height = 24;
+	}
+	116
+	{
+		title = "Pointy";
+		sprite = "PNTYA1";
+		width = 8;
+		height = 16;
+	}
+}
+
+bosses
+{
+	color = 8; // Dark_Gray
+	arrow = 1;
+	title = "Bosses";
+
+	200
+	{
+		title = "Egg Mobile";
+		sprite = "EGGMA1";
+		width = 24;
+		height = 76;
+	}
+	201
+	{
+		title = "Egg Slimer";
+		sprite = "EGGNA1";
+		width = 24;
+		height = 76;
+	}
+	202
+	{
+		title = "Sea Egg";
+		sprite = "EGGOA1";
+		width = 32;
+		height = 116;
+	}
+	203
+	{
+		title = "Egg Colosseum";
+		sprite = "EGGPA1";
+		width = 24;
+		height = 76;
+	}
+	204
+	{
+		title = "Fang";
+		sprite = "FANGA1";
+		width = 24;
+		height = 60;
+	}
+	206
+	{
+		title = "Brak Eggman (Old)";
+		sprite = "BRAKB1";
+		width = 48;
+		height = 160;
+	}
+	207
+	{
+		title = "Metal Sonic (Race)";
+		sprite = "METLI1";
+		width = 16;
+		height = 48;
+	}
+	208
+	{
+		title = "Metal Sonic (Battle)";
+		sprite = "METLC1";
+		width = 16;
+		height = 48;
+	}
+	209
+	{
+		title = "Brak Eggman";
+		sprite = "BRAK01";
+		width = 48;
+		height = 160;
+	}
+	290
+	{
+		arrow = 0;
+		title = "Boss Escape Point";
+		width = 8;
+		height = 16;
+		sprite = "internal:eggmanend";
+	}
+	291
+	{
+		arrow = 0;
+		title = "Egg Capsule Center";
+		width = 8;
+		height = 16;
+		sprite = "internal:capsule";
+	}
+	292
+	{
+		arrow = 0;
+		title = "Boss Waypoint";
+		width = 8;
+		height = 16;
+		sprite = "internal:eggmanway";
+	}
+	293
+	{
+		title = "Metal Sonic Gather Point";
+		sprite = "internal:metal";
+		width = 8;
+		height = 16;
+	}
+	294
+	{
+		title = "Fang Waypoint";
+		sprite = "internal:eggmanway";
+		width = 8;
+		height = 16;
+	}
+}
+
+rings
+{
+	color = 14; // Yellow
+	title = "Rings and Weapon Panels";
+	width = 24;
+	height = 24;
+	sprite = "RINGA0";
+
+	300
+	{
+		title = "Ring";
+		sprite = "RINGA0";
+		width = 16;
+	}
+	301
+	{
+		title = "Bounce Ring";
+		sprite = "internal:RNGBA0";
+	}
+	302
+	{
+		title = "Rail Ring";
+		sprite = "internal:RNGRA0";
+	}
+	303
+	{
+		title = "Infinity Ring";
+		sprite = "internal:RNGIA0";
+	}
+	304
+	{
+		title = "Automatic Ring";
+		sprite = "internal:RNGAA0";
+	}
+	305
+	{
+		title = "Explosion Ring";
+		sprite = "internal:RNGEA0";
+	}
+	306
+	{
+		title = "Scatter Ring";
+		sprite = "internal:RNGSA0";
+	}
+	307
+	{
+		title = "Grenade Ring";
+		sprite = "internal:RNGGA0";
+	}
+	308
+	{
+		title = "CTF Team Ring (Red)";
+		sprite = "internal:RRNGA0";
+		width = 16;
+	}
+	309
+	{
+		title = "CTF Team Ring (Blue)";
+		sprite = "internal:BRNGA0";
+		width = 16;
+	}
+	330
+	{
+		title = "Bounce Ring Panel";
+		sprite = "internal:PIKBA0";
+	}
+	331
+	{
+		title = "Rail Ring Panel";
+		sprite = "internal:PIKRA0";
+	}
+	332
+	{
+		title = "Automatic Ring Panel";
+		sprite = "internal:PIKAA0";
+	}
+	333
+	{
+		title = "Explosion Ring Panel";
+		sprite = "internal:PIKEA0";
+	}
+	334
+	{
+		title = "Scatter Ring Panel";
+		sprite = "internal:PIKSA0";
+	}
+	335
+	{
+		title = "Grenade Ring Panel";
+		sprite = "internal:PIKGA0";
+	}
+}
+
+collectibles
+{
+	color = 10; // Light_Green
+	title = "Other Collectibles";
+	width = 16;
+	height = 32;
+	sort = 1;
+	sprite = "CEMGA0";
+
+	310
+	{
+		title = "CTF Red Flag";
+		sprite = "RFLGA0";
+		width = 24;
+		height = 64;
+	}
+	311
+	{
+		title = "CTF Blue Flag";
+		sprite = "BFLGA0";
+		width = 24;
+		height = 64;
+	}
+	312
+	{
+		title = "Emerald Token";
+		sprite = "TOKEA0";
+		width = 16;
+		height = 32;
+	}
+	313
+	{
+		title = "Chaos Emerald 1 (Green)";
+		sprite = "CEMGA0";
+	}
+	314
+	{
+		title = "Chaos Emerald 2 (Purple)";
+		sprite = "CEMGB0";
+	}
+	315
+	{
+		title = "Chaos Emerald 3 (Blue)";
+		sprite = "CEMGC0";
+	}
+	316
+	{
+		title = "Chaos Emerald 4 (Cyan)";
+		sprite = "CEMGD0";
+	}
+	317
+	{
+		title = "Chaos Emerald 5 (Orange)";
+		sprite = "CEMGE0";
+	}
+	318
+	{
+		title = "Chaos Emerald 6 (Red)";
+		sprite = "CEMGF0";
+	}
+	319
+	{
+		title = "Chaos Emerald 7 (Gray)";
+		sprite = "CEMGG0";
+	}
+	320
+	{
+		title = "Emerald Hunt Location";
+		sprite = "SHRDA0";
+	}
+	321
+	{
+		title = "Match Chaos Emerald Spawn";
+		sprite = "CEMGA0";
+	}
+	322
+	{
+		title = "Emblem";
+		sprite = "EMBMA0";
+		width = 16;
+		height = 30;
+	}
+}
+
+boxes
+{
+	color = 7; // Gray
+	blocking = 2;
+	title = "Monitors";
+	width = 18;
+	height = 40;
+
+	400
+	{
+		title = "Super Ring (10 Rings)";
+		sprite = "TVRIA0";
+	}
+	401
+	{
+		title = "Pity Shield";
+		sprite = "TVPIA0";
+	}
+	402
+	{
+		title = "Attraction Shield";
+		sprite = "TVATA0";
+	}
+	403
+	{
+		title = "Force Shield";
+		sprite = "TVFOA0";
+	}
+	404
+	{
+		title = "Armageddon Shield";
+		sprite = "TVARA0";
+	}
+	405
+	{
+		title = "Whirlwind Shield";
+		sprite = "TVWWA0";
+	}
+	406
+	{
+		title = "Elemental Shield";
+		sprite = "TVELA0";
+	}
+	407
+	{
+		title = "Super Sneakers";
+		sprite = "TVSSA0";
+	}
+	408
+	{
+		title = "Invincibility";
+		sprite = "TVIVA0";
+	}
+	409
+	{
+		title = "Extra Life";
+		sprite = "TV1UA0";
+	}
+	410
+	{
+		title = "Eggman";
+		sprite = "TVEGA0";
+	}
+	411
+	{
+		title = "Teleporter";
+		sprite = "TVMXA0";
+	}
+	413
+	{
+		title = "Gravity Boots";
+		sprite = "TVGVA0";
+	}
+	414
+	{
+		title = "CTF Team Ring Monitor (Red)";
+		sprite = "TRRIA0";
+	}
+	415
+	{
+		title = "CTF Team Ring Monitor (Blue)";
+		sprite = "TBRIA0";
+	}
+	416
+	{
+		title = "Recycler";
+		sprite = "TVRCA0";
+	}
+	418
+	{
+		title = "Score (1,000 Points)";
+		sprite = "TV1KA0";
+	}
+	419
+	{
+		title = "Score (10,000 Points)";
+		sprite = "TVTKA0";
+	}
+	420
+	{
+		title = "Flame Shield";
+		sprite = "TVFLA0";
+	}
+	421
+	{
+		title = "Water Shield";
+		sprite = "TVBBA0";
+	}
+	422
+	{
+		title = "Lightning Shield";
+		sprite = "TVZPA0";
+	}
+}
+
+boxes2
+{
+	color = 18; // Gold
+	blocking = 2;
+	title = "Monitors (Respawning)";
+	width = 20;
+	height = 44;
+
+	431
+	{
+		title = "Pity Shield (Respawn)";
+		sprite = "TVPIB0";
+	}
+	432
+	{
+		title = "Attraction Shield (Respawn)";
+		sprite = "TVATB0";
+	}
+	433
+	{
+		title = "Force Shield (Respawn)";
+		sprite = "TVFOB0";
+	}
+	434
+	{
+		title = "Armageddon Shield (Respawn)";
+		sprite = "TVARB0";
+	}
+	435
+	{
+		title = "Whirlwind Shield (Respawn)";
+		sprite = "TVWWB0";
+	}
+	436
+	{
+		title = "Elemental Shield (Respawn)";
+		sprite = "TVELB0";
+	}
+	437
+	{
+		title = "Super Sneakers (Respawn)";
+		sprite = "TVSSB0";
+	}
+	438
+	{
+		title = "Invincibility (Respawn)";
+		sprite = "TVIVB0";
+	}
+	440
+	{
+		title = "Eggman (Respawn)";
+		sprite = "TVEGB0";
+	}
+	443
+	{
+		title = "Gravity Boots (Respawn)";
+		sprite = "TVGVB0";
+	}
+	450
+	{
+		title = "Flame Shield (Respawn)";
+		sprite = "TVFLB0";
+	}
+	451
+	{
+		title = "Water Shield (Respawn)";
+		sprite = "TVBBB0";
+	}
+	452
+	{
+		title = "Lightning Shield (Respawn)";
+		sprite = "TVZPB0";
+	}
+}
+
+generic
+{
+	color = 11; // Light_Cyan
+	title = "Generic Items & Hazards";
+
+	500
+	{
+		title = "Air Bubble Patch";
+		sprite = "BUBLE0";
+		width = 8;
+		height = 16;
+	}
+	501
+	{
+		title = "Signpost";
+		sprite = "SIGND0";
+		width = 8;
+		height = 32;
+	}
+	502
+	{
+		arrow = 1;
+		title = "Star Post";
+		sprite = "STPTA0M0";
+		width = 64;
+		height = 128;
+	}
+	520
+	{
+		title = "Bomb Sphere";
+		sprite = "SPHRD0";
+		width = 16;
+		height = 24;
+	}
+	521
+	{
+		title = "Spikeball";
+		sprite = "SPIKA0";
+		width = 12;
+		height = 8;
+	}
+	522
+	{
+		title = "Wall Spike";
+		sprite = "WSPKALAR";
+		width = 16;
+		height = 14;
+		arrow = 1;
+	}
+	523
+	{
+		title = "Spike";
+		sprite = "USPKA0";
+		width = 8;
+		height = 32;
+	}
+	1130
+	{
+		title = "Small Mace";
+		sprite = "SMCEA0";
+		width = 17;
+		height = 34;
+	}
+	1131
+	{
+		title = "Big Mace";
+		sprite = "BMCEA0";
+		width = 34;
+		height = 68;
+	}
+	1136
+	{
+		title = "Small Fireball";
+		sprite = "SFBRA0";
+		width = 17;
+		height = 34;
+	}
+	1137
+	{
+		title = "Large Fireball";
+		sprite = "BFBRA0";
+		width = 34;
+		height = 68;
+	}
+}
+
+springs
+{
+	color = 12; // Light_Red
+	title = "Springs and Fans";
+	width = 20;
+	height = 16;
+	sprite = "RSPRD2";
+
+	540
+	{
+		title = "Fan";
+		sprite = "FANSA0D0";
+		width = 16;
+		height = 8;
+	}
+	541
+	{
+		title = "Gas Jet";
+		sprite = "STEMD0";
+		width = 32;
+	}
+	542
+	{
+		title = "Bumper";
+		sprite = "BUMPA0";
+		width = 32;
+		height = 64;
+	}
+	543
+	{
+		title = "Balloon";
+		sprite = "BLONA0";
+		width = 32;
+		height = 64;
+	}
+	550
+	{
+		title = "Yellow Spring";
+		sprite = "SPRYA0";
+	}
+	551
+	{
+		title = "Red Spring";
+		sprite = "SPRRA0";
+	}
+	552
+	{
+		title = "Blue Spring";
+		sprite = "SPRBA0";
+	}
+	555
+	{
+		arrow = 1;
+		title = "Diagonal Yellow Spring";
+		sprite = "YSPRD2";
+		width = 16;
+	}
+	556
+	{
+		arrow = 1;
+		title = "Diagonal Red Spring";
+		sprite = "RSPRD2";
+		width = 16;
+	}
+	557
+	{
+		arrow = 1;
+		title = "Diagonal Blue Spring";
+		sprite = "BSPRD2";
+		width = 16;
+	}
+	558
+	{
+		arrow = 1;
+		title = "Horizontal Yellow Spring";
+		sprite = "SSWYD2D8";
+		width = 16;
+		height = 32;
+	}
+	559
+	{
+		arrow = 1;
+		title = "Horizontal Red Spring";
+		sprite = "SSWRD2D8";
+		width = 16;
+		height = 32;
+	}
+	560
+	{
+		arrow = 1;
+		title = "Horizontal Blue Spring";
+		sprite = "SSWBD2D8";
+		width = 16;
+		height = 32;
+	}
+	1134
+	{
+		title = "Yellow Spring Ball";
+		sprite = "YSPBA0";
+		width = 17;
+		height = 34;
+	}
+	1135
+	{
+		title = "Red Spring Ball";
+		sprite = "RSPBA0";
+		width = 17;
+		height = 34;
+	}
+	544
+	{
+		arrow = 1;
+		title = "Yellow Boost Panel";
+		sprite = "BSTYA0";
+		width = 28;
+		height = 2;
+	}
+	545
+	{
+		arrow = 1;
+		title = "Red Boost Panel";
+		sprite = "BSTRA0";
+		width = 28;
+		height = 2;
+	}
+}
+
+patterns
+{
+	color = 5; // Magenta
+	arrow = 1;
+	title = "Special Placement Patterns";
+	width = 16;
+	height = 384;
+	sprite = "RINGA0";
+
+	600
+	{
+		arrow = 0;
+		title = "5 Vertical Rings (Yellow Spring)";
+		sprite = "RINGA0";
+	}
+	601
+	{
+		arrow = 0;
+		title = "5 Vertical Rings (Red Spring)";
+		sprite = "RINGA0";
+		height = 1024;
+	}
+	602
+	{
+		title = "5 Diagonal Rings (Yellow Spring)";
+		sprite = "RINGA0";
+		height = 32;
+	}
+	603
+	{
+		title = "10 Diagonal Rings (Red Spring)";
+		sprite = "RINGA0";
+		height = 32;
+	}
+	604
+	{
+		title = "Circle of Rings";
+		sprite = "RINGA0";
+		width = 96;
+		height = 192;
+	}
+	605
+	{
+		title = "Circle of Rings (Big)";
+		sprite = "RINGA0";
+		width = 192;
+	}
+	606
+	{
+		title = "Circle of Blue Spheres";
+		sprite = "SPHRA0";
+		width = 96;
+		height = 192;
+	}
+	607
+	{
+		title = "Circle of Blue Spheres (Big)";
+		sprite = "SPHRA0";
+		width = 192;
+	}
+	608
+	{
+		title = "Circle of Rings and Spheres";
+		sprite = "SPHRA0";
+		width = 96;
+		height = 192;
+	}
+	609
+	{
+		title = "Circle of Rings and Spheres (Big)";
+		sprite = "SPHRA0";
+		width = 192;
+	}
+}
+
+invisible
+{
+	color = 15; // White
+	title = "Misc. Invisible";
+	width = 0;
+	height = 0;
+	sprite = "UNKNA0";
+	sort = 1;
+	fixedsize = true;
+	blocking = 0;
+
+	700
+	{
+		title = "Water Ambience A (Large)";
+		sprite = "internal:ambiance";
+	}
+
+	701
+	{
+		title = "Water Ambience B (Large)";
+		sprite = "internal:ambiance";
+	}
+
+	702
+	{
+		title = "Water Ambience C (Medium)";
+		sprite = "internal:ambiance";
+	}
+
+	703
+	{
+		title = "Water Ambience D (Medium)";
+		sprite = "internal:ambiance";
+	}
+
+	704
+	{
+		title = "Water Ambience E (Small)";
+		sprite = "internal:ambiance";
+	}
+
+	705
+	{
+		title = "Water Ambience F (Small)";
+		sprite = "internal:ambiance";
+	}
+
+	706
+	{
+		title = "Water Ambience G (Extra Large)";
+		sprite = "internal:ambiance";
+	}
+
+	707
+	{
+		title = "Water Ambience H (Extra Large)";
+		sprite = "internal:ambiance";
+	}
+
+	708
+	{
+		title = "Disco Ambience";
+		sprite = "internal:ambiance";
+	}
+
+	709
+	{
+		title = "Volcano Ambience";
+		sprite = "internal:ambiance";
+	}
+
+	710
+	{
+		title = "Machine Ambience";
+		sprite = "internal:ambiance";
+	}
+
+	750
+	{
+		title = "Slope Vertex";
+		sprite = "internal:vertexslope";
+	}
+
+	751
+	{
+		arrow = 1;
+		title = "Teleport Destination";
+		sprite = "internal:tele";
+	}
+
+	752
+	{
+		arrow = 1;
+		title = "Alternate View Point";
+		sprite = "internal:view";
+	}
+
+	753
+	{
+		title = "Zoom Tube Waypoint";
+		sprite = "internal:zoom";
+	}
+
+	754
+	{
+		title = "Push Point";
+		sprite = "GWLGA0";
+	}
+	755
+	{
+		title = "Pull Point";
+		sprite = "GWLRA0";
+	}
+	756
+	{
+		title = "Blast Linedef Executor";
+		sprite = "TOADA0";
+		width = 32;
+		height = 16;
+	}
+	757
+	{
+		title = "Fan Particle Generator";
+		sprite = "PRTLA0";
+		width = 8;
+		height = 16;
+	}
+	758
+	{
+		title = "Object Angle Anchor";
+		sprite = "internal:view";
+	}
+	760
+	{
+		title = "PolyObject Anchor";
+		sprite = "internal:polyanchor";
+	}
+	761
+	{
+		title = "PolyObject Spawn Point";
+		sprite = "internal:polycenter";
+	}
+	762
+	{
+		title = "PolyObject Spawn Point (Crush)";
+		sprite = "internal:polycentercrush";
+	}
+	780
+	{
+		title = "Skybox View Point";
+		sprite = "internal:skyb";
+	}
+}
+
+greenflower
+{
+	color = 10; // Green
+	title = "Greenflower";
+
+	800
+	{
+		title = "GFZ Flower";
+		sprite = "FWR1A0";
+		width = 16;
+		height = 40;
+	}
+	801
+	{
+		title = "Sunflower";
+		sprite = "FWR2A0";
+		width = 16;
+		height = 96;
+	}
+	802
+	{
+		title = "Budding Flower";
+		sprite = "FWR3A0";
+		width = 8;
+		height = 32;
+	}
+	803
+	{
+		title = "Blueberry Bush";
+		sprite = "BUS3A0";
+		width = 16;
+		height = 32;
+	}
+	804
+	{
+		title = "Berry Bush";
+		sprite = "BUS1A0";
+		width = 16;
+		height = 32;
+	}
+	805
+	{
+		title = "Bush";
+		sprite = "BUS2A0";
+		width = 16;
+		height = 32;
+	}
+	806
+	{
+		title = "GFZ Tree";
+		sprite = "TRE1A0";
+		width = 20;
+		height = 128;
+	}
+	807
+	{
+		title = "GFZ Berry Tree";
+		sprite = "TRE1B0";
+		width = 20;
+		height = 128;
+	}
+	808
+	{
+		title = "GFZ Cherry Tree";
+		sprite = "TRE1C0";
+		width = 20;
+		height = 128;
+	}
+	809
+	{
+		title = "Checkered Tree";
+		sprite = "TRE2A0";
+		width = 20;
+		height = 200;
+	}
+	810
+	{
+		title = "Checkered Tree (Sunset)";
+		sprite = "TRE2B0";
+		width = 20;
+		height = 200;
+	}
+	811
+	{
+		title = "Polygon Tree";
+		sprite = "TRE4A0";
+		width = 20;
+		height = 200;
+	}
+	812
+	{
+		title = "Bush Tree";
+		sprite = "TRE5A0";
+		width = 20;
+		height = 200;
+	}
+	813
+	{
+		title = "Red Bush Tree";
+		sprite = "TRE5B0";
+		width = 20;
+		height = 200;
+	}
+}
+
+technohill
+{
+	color = 10; // Green
+	title = "Techno Hill";
+
+	900
+	{
+		title = "THZ Steam Flower";
+		sprite = "THZPA0";
+		width = 8;
+		height = 32;
+	}
+	901
+	{
+		title = "Alarm";
+		sprite = "ALRMA0";
+		width = 8;
+		height = 16;
+		hangs = 1;
+	}
+	902
+	{
+		title = "THZ Spin Flower (Red)";
+		sprite = "FWR5A0";
+		width = 16;
+		height = 64;
+	}
+	903
+	{
+		title = "THZ Spin Flower (Yellow)";
+		sprite = "FWR6A0";
+		width = 16;
+		height = 64;
+	}
+	904
+	{
+		arrow = 1;
+		title = "Whistlebush";
+		sprite = "THZTA0";
+		width = 16;
+		height = 64;
+	}
+}
+
+deepsea
+{
+	color = 10; // Green
+	title = "Deep Sea";
+
+	1000
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Gargoyle";
+		sprite = "GARGA1";
+		width = 16;
+		height = 40;
+	}
+	1009
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Gargoyle (Big)";
+		sprite = "GARGB1";
+		width = 32;
+		height = 80;
+	}
+	1001
+	{
+		title = "Seaweed";
+		sprite = "SEWEA0";
+		width = 24;
+		height = 56;
+	}
+	1002
+	{
+		title = "Dripping Water";
+		sprite = "DRIPD0";
+		width = 8;
+		height = 16;
+		hangs = 1;
+	}
+	1003
+	{
+		title = "Coral (Green)";
+		sprite = "CORLA0";
+		width = 29;
+		height = 40;
+	}
+	1004
+	{
+		title = "Coral (Red)";
+		sprite = "CORLB0";
+		width = 30;
+		height = 53;
+	}
+	1005
+	{
+		title = "Coral (Orange)";
+		sprite = "CORLC0";
+		width = 28;
+		height = 41;
+	}
+	1006
+	{
+		title = "Blue Crystal";
+		sprite = "BCRYA1";
+		width = 8;
+		height = 16;
+	}
+	1007
+	{
+		title = "Kelp";
+		sprite = "KELPA0";
+		width = 16;
+		height = 292;
+	}
+	1008
+	{
+		title = "Stalagmite (DSZ1)";
+		sprite = "DSTGA0";
+		width = 8;
+		height = 116;
+	}
+	1010
+	{
+		arrow = 1;
+		title = "Light Beam";
+		sprite = "LIBEARAL";
+		width = 16;
+		height = 16;
+	}
+	1011
+	{
+		title = "Stalagmite (DSZ2)";
+		sprite = "DSTGA0";
+		width = 8;
+		height = 116;
+	}
+	1012
+	{
+		arrow = 1;
+		title = "Big Floating Mine";
+		width = 28;
+		height = 56;
+		sprite = "BMNEA1";
+	}
+	1013
+	{
+		title = "Animated Kelp";
+		sprite = "ALGAA0";
+		width = 48;
+		height = 120;
+	}
+	1014
+	{
+		title = "Large Coral (Brown)";
+		sprite = "CORLD0";
+		width = 56;
+		height = 112;
+	}
+	1015
+	{
+		title = "Large Coral (Beige)";
+		sprite = "CORLE0";
+		width = 56;
+		height = 112;
+	}
+}
+
+castleeggman
+{
+	color = 10; // Green
+	title = "Castle Eggman";
+
+	1100
+	{
+		title = "Chain (Decorative)";
+		sprite = "CHANA0";
+		width = 4;
+		height = 128;
+		hangs = 1;
+	}
+	1101
+	{
+		title = "Torch";
+		sprite = "FLAMA0E0";
+		width = 8;
+		height = 32;
+	}
+	1102
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Eggman Statue";
+		sprite = "ESTAA1";
+		width = 32;
+		height = 240;
+	}
+	1103
+	{
+		title = "CEZ Flower";
+		sprite = "FWR4A0";
+		width = 16;
+		height = 40;
+	}
+	1104
+	{
+		title = "Mace Spawnpoint";
+		sprite = "SMCEA0";
+		width = 17;
+		height = 34;
+	}
+	1105
+	{
+		title = "Chain with Maces Spawnpoint";
+		sprite = "SMCEA0";
+		width = 17;
+		height = 34;
+	}
+	1106
+	{
+		title = "Chained Spring Spawnpoint";
+		sprite = "YSPBA0";
+		width = 17;
+		height = 34;
+	}
+	1107
+	{
+		title = "Chain Spawnpoint";
+		sprite = "BMCHA0";
+		width = 17;
+		height = 34;
+	}
+	1108
+	{
+		arrow = 1;
+		title = "Hidden Chain Spawnpoint";
+		sprite = "internal:chain3";
+		width = 17;
+		height = 34;
+	}
+	1109
+	{
+		title = "Firebar Spawnpoint";
+		sprite = "BFBRA0";
+		width = 17;
+		height = 34;
+	}
+	1110
+	{
+		title = "Custom Mace Spawnpoint";
+		sprite = "SMCEA0";
+		width = 17;
+		height = 34;
+	}
+	1111
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Crawla Statue";
+		sprite = "CSTAA1";
+		width = 16;
+		height = 40;
+	}
+	1112
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Lance-a-Bot Statue";
+		sprite = "CBBSA1";
+		width = 32;
+		height = 72;
+	}
+	1114
+	{
+		title = "Pine Tree";
+		sprite = "PINEA0";
+		width = 16;
+		height = 628;
+	}
+	1115
+	{
+		title = "CEZ Shrub (Small)";
+		sprite = "CEZBA0";
+		width = 16;
+		height = 24;
+	}
+	1116
+	{
+		title = "CEZ Shrub (Large)";
+		sprite = "CEZBB0";
+		width = 32;
+		height = 48;
+	}
+	1117
+	{
+		arrow = 1;
+		title = "Pole Banner (Red)";
+		sprite = "BANRA0";
+		width = 40;
+		height = 224;
+	}
+	1118
+	{
+		arrow = 1;
+		title = "Pole Banner (Blue)";
+		sprite = "BANRA0";
+		width = 40;
+		height = 224;
+	}
+	1119
+	{
+		title = "Candle";
+		sprite = "CNDLA0";
+		width = 8;
+		height = 48;
+	}
+	1120
+	{
+		title = "Candle Pricket";
+		sprite = "CNDLB0";
+		width = 8;
+		height = 176;
+	}
+	1121
+	{
+		title = "Flame Holder";
+		sprite = "FLMHA0";
+		width = 24;
+		height = 80;
+	}
+	1122
+	{
+		title = "Fire Torch";
+		sprite = "CTRCA0";
+		width = 16;
+		height = 80;
+	}
+	1123
+	{
+		title = "Cannonball Launcher";
+		sprite = "internal:cannonball";
+		width = 8;
+		height = 16;
+	}
+	1124
+	{
+		blocking = 2;
+		title = "Cannonball";
+		sprite = "CBLLA0";
+		width = 20;
+		height = 40;
+	}
+	1125
+	{
+		title = "Brambles";
+		sprite = "CABRALAR";
+		width = 48;
+		height = 32;
+	}
+	1126
+	{
+		title = "Invisible Lockon Object";
+		sprite = "LCKNC0";
+		width = 16;
+		height = 32;
+	}
+	1127
+	{
+		title = "Spectator Eggrobo";
+		sprite = "EGR1A1";
+		width = 20;
+		height = 72;
+	}
+	1128
+	{
+		arrow = 1;
+		title = "Waving Flag (Red)";
+		sprite = "CFLGA0";
+		width = 8;
+		height = 208;
+	}
+	1129
+	{
+		arrow = 1;
+		title = "Waving Flag (Blue)";
+		sprite = "CFLGA0";
+		width = 8;
+		height = 208;
+	}
+}
+
+aridcanyon
+{
+	color = 10; // Green
+	title = "Arid Canyon";
+
+	1200
+	{
+		title = "Tumbleweed (Big)";
+		sprite = "BTBLA0";
+		width = 24;
+		height = 48;
+	}
+	1201
+	{
+		title = "Tumbleweed (Small)";
+		sprite = "STBLA0";
+		width = 12;
+		height = 24;
+	}
+	1202
+	{
+		arrow = 1;
+		title = "Rock Spawner";
+		sprite = "ROIAA0";
+		width = 8;
+		height = 16;
+	}
+	1203
+	{
+		title = "Tiny Red Flower Cactus";
+		sprite = "CACTA0";
+		width = 13;
+		height = 24;
+	}
+	1204
+	{
+		title = "Small Red Flower Cactus";
+		sprite = "CACTB0";
+		width = 15;
+		height = 52;
+	}
+	1205
+	{
+		title = "Tiny Blue Flower Cactus";
+		sprite = "CACTC0";
+		width = 13;
+		height = 24;
+	}
+	1206
+	{
+		title = "Small Blue Flower Cactus";
+		sprite = "CACTD0";
+		width = 15;
+		height = 52;
+	}
+	1207
+	{
+		title = "Prickly Pear";
+		sprite = "CACTE0";
+		width = 32;
+		height = 96;
+	}
+	1208
+	{
+		title = "Barrel Cactus";
+		sprite = "CACTF0";
+		width = 20;
+		height = 128;
+	}
+	1209
+	{
+		title = "Tall Barrel Cactus";
+		sprite = "CACTG0";
+		width = 24;
+		height = 224;
+	}
+	1210
+	{
+		title = "Armed Cactus";
+		sprite = "CACTH0";
+		width = 24;
+		height = 256;
+	}
+	1211
+	{
+		title = "Ball Cactus";
+		sprite = "CACTI0";
+		width = 48;
+		height = 96;
+	}
+	1212
+	{
+		title = "Caution Sign";
+		sprite = "WWSGAR";
+		width = 22;
+		height = 64;
+	}
+	1213
+	{
+		title = "Cacti Sign";
+		sprite = "WWS2AR";
+		width = 22;
+		height = 64;
+	}
+	1214
+	{
+		title = "Sharp Turn Sign";
+		sprite = "WWS3ALAR";
+		width = 16;
+		height = 192;
+	}
+	1215
+	{
+		title = "Mine Oil Lamp";
+		sprite = "OILLA0";
+		width = 22;
+		height = 64;
+		hangs = 1;
+	}
+	1216
+	{
+		title = "TNT Barrel";
+		sprite = "BARRA1";
+		width = 24;
+		height = 63;
+	}
+	1217
+	{
+		title = "TNT Proximity Shell";
+		sprite = "REMTA0";
+		width = 64;
+		height = 40;
+	}
+	1218
+	{
+		title = "Dust Devil";
+		sprite = "TAZDCR";
+		width = 80;
+		height = 416;
+	}
+	1219
+	{
+		title = "Minecart Spawner";
+		sprite = "MCRTCLFR";
+		width = 22;
+		height = 32;
+	}
+	1220
+	{
+		title = "Minecart Stopper";
+		sprite = "MCRTIR";
+		width = 32;
+		height = 32;
+	}
+	1221
+	{
+		title = "Minecart Saloon Door";
+		sprite = "SALDARAL";
+		width = 96;
+		height = 160;
+	}
+	1222
+	{
+		title = "Train Cameo Spawner";
+		sprite = "TRAEBRBL";
+		width = 28;
+		height = 32;
+	}
+	1223
+	{
+		title = "Train Dust Spawner";
+		sprite = "ADSTA0";
+		width = 4;
+		height = 4;
+	}
+	1224
+	{
+		title = "Train Steam Spawner";
+		sprite = "STEAA0";
+		width = 4;
+		height = 4;
+	}
+	1229
+	{
+		title = "Minecart Switch Point";
+		sprite = "internal:zoom";
+		width = 8;
+		height = 16;
+	}
+	1230
+	{
+		title = "Tiny Cactus";
+		sprite = "CACTJ0";
+		width = 13;
+		height = 28;
+	}
+	1231
+	{
+		title = "Small Cactus";
+		sprite = "CACTK0";
+		width = 15;
+		height = 60;
+	}
+}
+
+redvolcano
+{
+	color = 10; // Green
+	title = "Red Volcano";
+
+	1300
+	{
+		arrow = 1;
+		title = "Flame Jet (Horizontal)";
+		sprite = "internal:flameh";
+		width = 16;
+		height = 40;
+	}
+	1301
+	{
+		title = "Flame Jet (Vertical)";
+		sprite = "internal:flamev";
+		width = 16;
+		height = 40;
+	}
+	1302
+	{
+		title = "Spinning Flame Jet (Counter-Clockwise)";
+		sprite = "internal:flame2";
+		width = 16;
+		height = 24;
+	}
+	1303
+	{
+		title = "Spinning Flame Jet (Clockwise)";
+		sprite = "internal:flame1";
+		width = 16;
+		height = 24;
+	}
+	1304
+	{
+		title = "Lavafall";
+		sprite = "LFALF0";
+		width = 30;
+		height = 32;
+	}
+	1305
+	{
+		title = "Rollout Rock";
+		sprite = "PUMIA1A5";
+		width = 30;
+		height = 60;
+	}
+	1306
+	{
+		title = "Big Fern";
+		sprite = "JPLAB0";
+		width = 32;
+		height = 48;
+	}
+	1307
+	{
+		title = "Jungle Palm";
+		sprite = "JPLAC0";
+		width = 32;
+		height = 48;
+	}
+	1308
+	{
+		title = "Torch Flower";
+		sprite = "TFLOA0";
+		width = 14;
+		height = 110;
+	}
+	1309
+	{
+		title = "RVZ1 Wall Vine (Long)";
+		sprite = "WVINALAR";
+		width = 1;
+		height = 288;
+	}
+	1310
+	{
+		title = "RVZ1 Wall Vine (Short)";
+		sprite = "WVINBLBR";
+		width = 1;
+		height = 288;
+	}
+}
+
+botanicserenity
+{
+	color = 10; // Green
+	title = "Botanic Serenity";
+	width = 16;
+	height = 32;
+	sprite = "BSZ1A0";
+	1400
+	{
+		title = "Tall Flower (Red)";
+		sprite = "BSZ1A0";
+	}
+	1401
+	{
+		title = "Tall Flower (Purple)";
+		sprite = "BSZ1B0";
+	}
+	1402
+	{
+		title = "Tall Flower (Blue)";
+		sprite = "BSZ1C0";
+	}
+	1403
+	{
+		title = "Tall Flower (Cyan)";
+		sprite = "BSZ1D0";
+	}
+	1404
+	{
+		title = "Tall Flower (Yellow)";
+		sprite = "BSZ1E0";
+	}
+	1405
+	{
+		title = "Tall Flower (Orange)";
+		sprite = "BSZ1F0";
+	}
+	1410
+	{
+		title = "Medium Flower (Red)";
+		sprite = "BSZ2A0";
+	}
+	1411
+	{
+		title = "Medium Flower (Purple)";
+		sprite = "BSZ2B0";
+	}
+	1412
+	{
+		title = "Medium Flower (Blue)";
+		sprite = "BSZ2C0";
+	}
+	1413
+	{
+		title = "Medium Flower (Cyan)";
+		sprite = "BSZ2D0";
+	}
+	1414
+	{
+		title = "Medium Flower (Yellow)";
+		sprite = "BSZ2E0";
+	}
+	1415
+	{
+		title = "Medium Flower (Orange)";
+		sprite = "BSZ2F0";
+	}
+	1420
+	{
+		title = "Short Flower (Red)";
+		sprite = "BSZ3A0";
+	}
+	1421
+	{
+		title = "Short Flower (Purple)";
+		sprite = "BSZ3B0";
+	}
+	1422
+	{
+		title = "Short Flower (Blue)";
+		sprite = "BSZ3C0";
+	}
+	1423
+	{
+		title = "Short Flower (Cyan)";
+		sprite = "BSZ3D0";
+	}
+	1424
+	{
+		title = "Short Flower (Yellow)";
+		sprite = "BSZ3E0";
+	}
+	1425
+	{
+		title = "Short Flower (Orange)";
+		sprite = "BSZ3F0";
+	}
+	1430
+	{
+		title = "Tulip (Red)";
+		sprite = "BST1A0";
+	}
+	1431
+	{
+		title = "Tulip (Purple)";
+		sprite = "BST2A0";
+	}
+	1432
+	{
+		title = "Tulip (Blue)";
+		sprite = "BST3A0";
+	}
+	1433
+	{
+		title = "Tulip (Cyan)";
+		sprite = "BST4A0";
+	}
+	1434
+	{
+		title = "Tulip (Yellow)";
+		sprite = "BST5A0";
+	}
+	1435
+	{
+		title = "Tulip (Orange)";
+		sprite = "BST6A0";
+	}
+	1440
+	{
+		title = "Cluster (Red)";
+		sprite = "BSZ5A0";
+	}
+	1441
+	{
+		title = "Cluster (Purple)";
+		sprite = "BSZ5B0";
+	}
+	1442
+	{
+		title = "Cluster (Blue)";
+		sprite = "BSZ5C0";
+	}
+	1443
+	{
+		title = "Cluster (Cyan)";
+		sprite = "BSZ5D0";
+	}
+	1444
+	{
+		title = "Cluster (Yellow)";
+		sprite = "BSZ5E0";
+	}
+	1445
+	{
+		title = "Cluster (Orange)";
+		sprite = "BSZ5F0";
+	}
+	1450
+	{
+		title = "Bush (Red)";
+		sprite = "BSZ6A0";
+	}
+	1451
+	{
+		title = "Bush (Purple)";
+		sprite = "BSZ6B0";
+	}
+	1452
+	{
+		title = "Bush (Blue)";
+		sprite = "BSZ6C0";
+	}
+	1453
+	{
+		title = "Bush (Cyan)";
+		sprite = "BSZ6D0";
+	}
+	1454
+	{
+		title = "Bush (Yellow)";
+		sprite = "BSZ6E0";
+	}
+	1455
+	{
+		title = "Bush (Orange)";
+		sprite = "BSZ6F0";
+	}
+	1460
+	{
+		title = "Vine (Red)";
+		sprite = "BSZ7A0";
+	}
+	1461
+	{
+		title = "Vine (Purple)";
+		sprite = "BSZ7B0";
+	}
+	1462
+	{
+		title = "Vine (Blue)";
+		sprite = "BSZ7C0";
+	}
+	1463
+	{
+		title = "Vine (Cyan)";
+		sprite = "BSZ7D0";
+	}
+	1464
+	{
+		title = "Vine (Yellow)";
+		sprite = "BSZ7E0";
+	}
+	1465
+	{
+		title = "Vine (Orange)";
+		sprite = "BSZ7F0";
+	}
+	1470
+	{
+		title = "BSZ Shrub";
+		sprite = "BSZ8A0";
+	}
+	1471
+	{
+		title = "BSZ Clover";
+		sprite = "BSZ8B0";
+	}
+	1473
+	{
+		title = "Palm Tree (Big)";
+		width = 16;
+		height = 160;
+		sprite = "BSZ8D0";
+	}
+	1475
+	{
+		title = "Palm Tree (Small)";
+		width = 16;
+		height = 80;
+		sprite = "BSZ8F0";
+	}
+}
+
+azuretemple
+{
+	color = 10; // Green
+	title = "Azure Temple";
+
+	1500
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Glaregoyle";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+	}
+	1501
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Glaregoyle (Up)";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+	}
+	1502
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Glaregoyle (Down)";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+	}
+	1503
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Glaregoyle (Long)";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+	}
+	1504
+	{
+		title = "ATZ Target";
+		sprite = "RCRYB0";
+		width = 24;
+		height = 32;
+	}
+	1505
+	{
+		title = "Green Flame";
+		sprite = "CFLMA0E0";
+		width = 8;
+		height = 32;
+	}
+	1506
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Blue Gargoyle";
+		sprite = "BGARD1";
+		width = 16;
+		height = 40;
+	}
+}
+
+dreamhill
+{
+	color = 10; // Green
+	title = "Dream Hill";
+
+	1600
+	{
+		title = "Spring Tree";
+		sprite = "TRE6A0";
+		width = 16;
+		height = 32;
+	}
+	1601
+	{
+		title = "Shleep";
+		sprite = "SHLPA0";
+		width = 24;
+		height = 32;
+	}
+	1602
+	{
+		title = "Pian";
+		sprite = "NTPNALAR";
+		width = 16;
+		height = 32;
+	}
+}
+
+nightstrk
+{
+	color = 13; // Pink
+	title = "NiGHTS Track";
+	width = 8;
+	height = 4096;
+	sprite = "UNKNA0";
+
+	1700
+	{
+		title = "Axis";
+		sprite = "internal:axis1";
+		circle = 1;
+	}
+	1701
+	{
+		title = "Axis Transfer";
+		sprite = "internal:axis2";
+	}
+	1702
+	{
+		title = "Axis Transfer Line";
+		sprite = "internal:axis3";
+	}
+	1710
+	{
+		title = "Ideya Capture";
+		sprite = "CAPSA0";
+		width = 72;
+		height = 144;
+	}
+}
+
+nights
+{
+	color = 13; // Pink
+	title = "NiGHTS Items";
+	width = 16;
+	height = 32;
+
+	1703
+	{
+		title = "Ideya Drone";
+		sprite = "NDRNA1";
+		width = 16;
+		height = 56;
+	}
+	1704
+	{
+		arrow = 1;
+		title = "NiGHTS Bumper";
+		sprite = "NBMPG3G7";
+		width = 32;
+		height = 64;
+	}
+	1705
+	{
+		arrow = 1;
+		title = "Hoop (Generic)";
+		sprite = "HOOPA0";
+		width = 80;
+		height = 160;
+	}
+	1706
+	{
+		title = "Blue Sphere";
+		sprite = "SPHRA0";
+		width = 16;
+		height = 24;
+	}
+	1707
+	{
+		title = "Super Paraloop";
+		sprite = "NPRUA0";
+	}
+	1708
+	{
+		title = "Drill Refill";
+		sprite = "NPRUB0";
+	}
+	1709
+	{
+		title = "Nightopian Helper";
+		sprite = "NPRUC0";
+	}
+	1711
+	{
+		title = "Extra Time";
+		sprite = "NPRUD0";
+	}
+	1712
+	{
+		title = "Link Freeze";
+		sprite = "NPRUE0";
+	}
+	1713
+	{
+		arrow = 1;
+		title = "Hoop (Customizable)";
+		sprite = "HOOPA0";
+		width = 80;
+		height = 160;
+	}
+	1714
+	{
+		title = "Ideya Anchor Point";
+		sprite = "internal:axis1";
+		width = 8;
+		height = 16;
+	}
+}
+
+mario
+{
+	color = 6; // Brown
+	title = "Mario";
+
+	1800
+	{
+		title = "Coin";
+		sprite = "COINA0";
+		width = 16;
+		height = 24;
+	}
+	1801
+	{
+		arrow = 1;
+		title = "Goomba";
+		sprite = "GOOMA0";
+		width = 24;
+		height = 32;
+	}
+	1802
+	{
+		arrow = 1;
+		title = "Goomba (Blue)";
+		sprite = "BGOMA0";
+		width = 24;
+		height = 32;
+	}
+	1803
+	{
+		title = "Fire Flower";
+		sprite = "FFWRB0";
+		width = 16;
+		height = 32;
+	}
+	1804
+	{
+		title = "Koopa Shell";
+		sprite = "SHLLA1";
+		width = 16;
+		height = 20;
+	}
+	1805
+	{
+		title = "Puma (Jumping Fireball)";
+		sprite = "PUMAA0";
+		width = 8;
+		height = 16;
+	}
+	1806
+	{
+		title = "King Bowser";
+		sprite = "KOOPA0";
+		width = 16;
+		height = 48;
+	}
+	1807
+	{
+		title = "Axe";
+		sprite = "MAXEA0";
+		width = 8;
+		height = 16;
+	}
+	1808
+	{
+		title = "Bush (Short)";
+		sprite = "MUS1A0";
+		width = 16;
+		height = 32;
+	}
+	1809
+	{
+		title = "Bush (Tall)";
+		sprite = "MUS2A0";
+		width = 16;
+		height = 32;
+	}
+	1810
+	{
+		title = "Toad";
+		sprite = "TOADA0";
+		width = 8;
+		height = 32;
+	}
+}
+
+christmasdisco
+{
+	color = 10; // Green
+	title = "Christmas & Disco";
+
+	1850
+	{
+		title = "Christmas Pole";
+		sprite = "XMS1A0";
+		width = 16;
+		height = 40;
+	}
+	1851
+	{
+		title = "Candy Cane";
+		sprite = "XMS2A0";
+		width = 8;
+		height = 32;
+	}
+	1852
+	{
+		blocking = 2;
+		title = "Snowman";
+		sprite = "XMS3A0";
+		width = 16;
+		height = 64;
+	}
+	1853
+	{
+		blocking = 2;
+		title = "Snowman (With Hat)";
+		sprite = "XMS3B0";
+		width = 16;
+		height = 80;
+	}
+	1854
+	{
+		title = "Lamp Post";
+		sprite = "XMS4A0";
+		width = 8;
+		height = 120;
+	}
+	1855
+	{
+		title = "Lamp Post (Snow)";
+		sprite = "XMS4B0";
+		width = 8;
+		height = 120;
+	}
+	1856
+	{
+		title = "Hanging Star";
+		sprite = "XMS5A0";
+		width = 4;
+		height = 80;
+		hangs = 1;
+	}
+	1857
+	{
+		title = "Berry Bush (Snow)";
+		sprite = "BUS1B0";
+		width = 16;
+		height = 32;
+	}
+	1858
+	{
+		title = "Bush (Snow)";
+		sprite = "BUS2B0";
+		width = 16;
+		height = 32;
+	}
+	1859
+	{
+		title = "Blueberry Bush (Snow)";
+		sprite = "BUS3B0";
+		width = 16;
+		height = 32;
+	}
+	1875
+	{
+		title = "Disco Ball";
+		sprite = "DBALA0";
+		width = 16;
+		height = 54;
+		hangs = 1;
+	}
+	1876
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Eggman Disco Statue";
+		sprite = "ESTAB1";
+		width = 20;
+		height = 96;
+	}
+}
+
+stalagmites
+{
+	color = 10; // Green
+	title = "Stalagmites";
+	width = 16;
+	height = 40;
+
+	1900
+	{
+		title = "Brown Stalagmite (Tall)";
+		sprite = "STLGA0";
+		width = 16;
+		height = 40;
+	}
+	1901
+	{
+		title = "Brown Stalagmite";
+		sprite = "STLGB0";
+		width = 16;
+		height = 40;
+	}
+	1902
+	{
+		title = "Orange Stalagmite (Tall)";
+		sprite = "STLGC0";
+		width = 16;
+		height = 40;
+	}
+	1903
+	{
+		title = "Orange Stalagmite";
+		sprite = "STLGD0";
+		width = 16;
+		height = 40;
+	}
+	1904
+	{
+		title = "Red Stalagmite (Tall)";
+		sprite = "STLGE0";
+		width = 16;
+		height = 40;
+	}
+	1905
+	{
+		title = "Red Stalagmite";
+		sprite = "STLGF0";
+		width = 16;
+		height = 40;
+	}
+	1906
+	{
+		title = "Gray Stalagmite (Tall)";
+		sprite = "STLGG0";
+		width = 24;
+		height = 96;
+	}
+	1907
+	{
+		title = "Gray Stalagmite";
+		sprite = "STLGH0";
+		width = 16;
+		height = 40;
+	}
+	1908
+	{
+		title = "Blue Stalagmite (Tall)";
+		sprite = "STLGI0";
+		width = 16;
+		height = 40;
+	}
+	1909
+	{
+		title = "Blue Stalagmite";
+		sprite = "STLGJ0";
+		width = 16;
+		height = 40;
+	}
+}
+
+hauntedheights
+{
+	color = 10; // Green
+	title = "Haunted Heights";
+
+	2000
+	{
+		title = "Smashing Spikeball";
+		sprite = "FMCEA0";
+		width = 18;
+		height = 28;
+	}
+	2001
+	{
+		title = "HHZ Grass";
+		sprite = "HHZMA0";
+		width = 16;
+		height = 40;
+	}
+	2002
+	{
+		title = "HHZ Tentacle 1";
+		sprite = "HHZMB0";
+		width = 16;
+		height = 40;
+	}
+	2003
+	{
+		title = "HHZ Tentacle 2";
+		sprite = "HHZMC0";
+		width = 16;
+		height = 40;
+	}
+	2004
+	{
+		title = "HHZ Stalagmite (Tall)";
+		sprite = "HHZME0";
+		width = 16;
+		height = 40;
+	}
+	2005
+	{
+		title = "HHZ Stalagmite (Short)";
+		sprite = "HHZMF0";
+		width = 16;
+		height = 40;
+	}
+	2006
+	{
+		title = "Jack-o'-lantern 1";
+		sprite = "PUMKA0";
+		width = 16;
+		height = 40;
+	}
+	2007
+	{
+		title = "Jack-o'-lantern 2";
+		sprite = "PUMKB0";
+		width = 16;
+		height = 40;
+	}
+	2008
+	{
+		title = "Jack-o'-lantern 3";
+		sprite = "PUMKC0";
+		width = 16;
+		height = 40;
+	}
+	2009
+	{
+		title = "Purple Mushroom";
+		sprite = "SHRMD0";
+		width = 16;
+		height = 48;
+	}
+	2010
+	{
+		title = "HHZ Tree";
+		sprite = "HHPLC0";
+		width = 12;
+		height = 40;
+	}
+}
+
+frozenhillside
+{
+	color = 10; // Green
+	title = "Frozen Hillside";
+
+	2100
+	{
+		title = "Ice Shard (Small)";
+		sprite = "FHZIA0";
+		width = 8;
+		height = 32;
+	}
+	2101
+	{
+		title = "Ice Shard (Large)";
+		sprite = "FHZIB0";
+		width = 8;
+		height = 32;
+	}
+	2102
+	{
+		title = "Crystal Tree (Aqua)";
+		sprite = "TRE3A0";
+		width = 20;
+		height = 200;
+	}
+	2103
+	{
+		title = "Crystal Tree (Pink)";
+		sprite = "TRE3B0";
+		width = 20;
+		height = 200;
+	}
+	2104
+	{
+		title = "Amy Cameo";
+		sprite = "ROSYA1";
+		width = 16;
+		height = 48;
+	}
+	2105
+	{
+		title = "Mistletoe";
+		sprite = "XMS6A0";
+		width = 52;
+		height = 106;
+	}
+}
+
+flickies
+{
+	color = 10; // Green
+	title = "Flickies";
+	width = 8;
+	height = 20;
+
+	2200
+	{
+		title = "Bluebird";
+		sprite = "FL01A1";
+	}
+	2201
+	{
+		title = "Rabbit";
+		sprite = "FL02A1";
+	}
+	2202
+	{
+		title = "Chicken";
+		sprite = "FL03A1";
+	}
+	2203
+	{
+		title = "Seal";
+		sprite = "FL04A1";
+	}
+	2204
+	{
+		title = "Pig";
+		sprite = "FL05A1";
+	}
+	2205
+	{
+		title = "Chipmunk";
+		sprite = "FL06A1";
+	}
+	2206
+	{
+		title = "Penguin";
+		sprite = "FL07A1";
+	}
+	2207
+	{
+		title = "Fish";
+		sprite = "FL08A1";
+	}
+	2208
+	{
+		title = "Ram";
+		sprite = "FL09A1";
+	}
+	2209
+	{
+		title = "Puffin";
+		sprite = "FL10A1";
+	}
+	2210
+	{
+		title = "Cow";
+		sprite = "FL11A1";
+	}
+	2211
+	{
+		title = "Rat";
+		sprite = "FL12A1";
+	}
+	2212
+	{
+		title = "Bear";
+		sprite = "FL13A1";
+	}
+	2213
+	{
+		title = "Dove";
+		sprite = "FL14A1";
+	}
+	2214
+	{
+		title = "Cat";
+		sprite = "FL15A1";
+	}
+	2215
+	{
+		title = "Canary";
+		sprite = "FL16A1";
+	}
+	2216
+	{
+		title = "Spider";
+		sprite = "FS01A1";
+	}
+	2217
+	{
+		title = "Bat";
+		sprite = "FS02A0";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/udb/SRB2_22Doom.cfg b/extras/conf/udb/SRB2_22Doom.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..891b9d507fefd1ba1eb5c7334cdafc91f3be10ec
--- /dev/null
+++ b/extras/conf/udb/SRB2_22Doom.cfg
@@ -0,0 +1,38 @@
+/************************************************************************\
+	Ultimate Doom Builder Game Configuration for Sonic Robo Blast 2 Version 2.2
+\************************************************************************/
+
+// This is required to prevent accidental use of a different configuration
+type = "Doom Builder 2 Game Configuration";
+
+// This is the title to show for this game
+game = "Sonic Robo Blast 2 - 2.2 (Doom format)";
+
+// This is the simplified game engine/sourceport name
+engine = "zdoom";
+
+// Settings common to all games and all map formats
+include("Includes\\SRB222_common.cfg", "common");
+
+// Settings common to Doom map format
+include("Includes\\SRB222_common.cfg", "mapformat_doom");
+
+include("Includes\\Game_SRB222.cfg");
+
+// Script lumps detection
+scriptlumpnames
+{
+	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
+}
+
+// THING TYPES
+thingtypes
+{
+	include("Includes\\SRB222_things.cfg");
+}
+
+//Default things filters
+thingsfilters
+{
+	include("Includes\\SRB222_misc.cfg", "thingsfilters");
+}
\ No newline at end of file
diff --git a/extras/conf/udb/SRB2_22UDMF.cfg b/extras/conf/udb/SRB2_22UDMF.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..749cf499abd98560e0be78aa40b1b75b1bd7935a
--- /dev/null
+++ b/extras/conf/udb/SRB2_22UDMF.cfg
@@ -0,0 +1,47 @@
+/************************************************************************\
+	Ultimate Doom Builder Game Configuration for Sonic Robo Blast 2 Version 2.2
+\************************************************************************/
+
+// This is required to prevent accidental use of a different configuration
+type = "Doom Builder 2 Game Configuration";
+
+// This is the title to show for this game
+game = "Sonic Robo Blast 2 - 2.2 (UDMF)";
+
+// This is the simplified game engine/sourceport name
+engine = "zdoom";
+
+// Settings common to all games and all map formats
+include("Includes\\SRB222_common.cfg", "common");
+
+// Settings common to text map format
+include("Includes\\SRB222_common.cfg", "mapformat_udmf");
+
+include("Includes\\Game_SRB222.cfg");
+
+// Script lumps detection
+scriptlumpnames
+{
+	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
+}
+
+// THING TYPES
+thingtypes
+{
+	include("Includes\\SRB222_things.cfg");
+}
+
+//Default things filters
+thingsfilters
+{
+	include("Includes\\SRB222_misc.cfg", "thingsfilters");
+}
+
+// ENUMERATIONS
+// Each engine has its own additional thing types
+// These are enumerated lists for linedef types and UDMF fields.
+enums
+{
+	// Basic game enums
+	include("Includes\\SRB222_misc.cfg", "enums");
+}
\ No newline at end of file
diff --git a/libs/curl/include/curl/curl.h b/libs/curl/include/curl/curl.h
index ee9754da8cc75ff0e9b48bfeefac46b3f111ad11..b7cb30a581b031e1fd575e874a1395fc3c0cc1e5 100644
--- a/libs/curl/include/curl/curl.h
+++ b/libs/curl/include/curl/curl.h
@@ -1,5 +1,5 @@
-#ifndef __CURL_CURL_H
-#define __CURL_CURL_H
+#ifndef CURLINC_CURL_H
+#define CURLINC_CURL_H
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
@@ -7,11 +7,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -24,23 +24,26 @@
 
 /*
  * If you have libcurl problems, all docs and details are found here:
- *   http://curl.haxx.se/libcurl/
+ *   https://curl.haxx.se/libcurl/
  *
  * curl-library mailing list subscription and unsubscription web interface:
- *   http://cool.haxx.se/mailman/listinfo/curl-library/
+ *   https://cool.haxx.se/mailman/listinfo/curl-library/
  */
 
+#ifdef CURL_NO_OLDIES
+#define CURL_STRICTER
+#endif
+
 #include "curlver.h"         /* libcurl version defines   */
-#include "curlbuild.h"       /* libcurl build definitions */
-#include "curlrules.h"       /* libcurl rules enforcement */
+#include "system.h"          /* determine things run-time */
 
 /*
- * Define WIN32 when build target is Win32 API
+ * Define CURL_WIN32 when build target is Win32 API
  */
 
-#if (defined(_WIN32) || defined(__WIN32__)) && \
-     !defined(WIN32) && !defined(__SYMBIAN32__)
-#define WIN32
+#if (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) &&        \
+  !defined(__SYMBIAN32__)
+#define CURL_WIN32
 #endif
 
 #include <stdio.h>
@@ -55,74 +58,79 @@
 #include <sys/types.h>
 #include <time.h>
 
-#if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__GNUC__) && \
-  !defined(__CYGWIN__) || defined(__MINGW32__)
-#if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H))
+#if defined(CURL_WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__)
+#if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H) || \
+      defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H))
 /* The check above prevents the winsock2 inclusion if winsock.h already was
    included, since they can't co-exist without problems */
 #include <winsock2.h>
 #include <ws2tcpip.h>
 #endif
-#else
+#endif
 
 /* HP-UX systems version 9, 10 and 11 lack sys/select.h and so does oldish
-   libc5-based Linux systems. Only include it on system that are known to
+   libc5-based Linux systems. Only include it on systems that are known to
    require it! */
 #if defined(_AIX) || defined(__NOVELL_LIBC__) || defined(__NetBSD__) || \
     defined(__minix) || defined(__SYMBIAN32__) || defined(__INTEGRITY) || \
-    defined(ANDROID) || \
+    defined(ANDROID) || defined(__ANDROID__) || defined(__OpenBSD__) || \
+    defined(__CYGWIN__) || \
    (defined(__FreeBSD_version) && (__FreeBSD_version < 800000))
 #include <sys/select.h>
 #endif
 
-#ifndef _WIN32_WCE
+#if !defined(CURL_WIN32) && !defined(_WIN32_WCE)
 #include <sys/socket.h>
 #endif
-#if !defined(WIN32) && !defined(__WATCOMC__) && !defined(__VXWORKS__)
+
+#if !defined(CURL_WIN32) && !defined(__WATCOMC__) && !defined(__VXWORKS__)
 #include <sys/time.h>
 #endif
-#include <sys/types.h>
-#endif
 
 #ifdef __BEOS__
 #include <support/SupportDefs.h>
 #endif
 
+/* Compatibility for non-Clang compilers */
+#ifndef __has_declspec_attribute
+#  define __has_declspec_attribute(x) 0
+#endif
+
 #ifdef  __cplusplus
 extern "C" {
 #endif
 
-typedef void CURL;
-
-/*
- * Decorate exportable functions for Win32 and Symbian OS DLL linking.
- * This avoids using a .def file for building libcurl.dll.
- */
-#if (defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__)) && \
-     !defined(CURL_STATICLIB)
-#if defined(BUILDING_LIBCURL)
-#define CURL_EXTERN  __declspec(dllexport)
+#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER)
+typedef struct Curl_easy CURL;
+typedef struct Curl_share CURLSH;
 #else
-#define CURL_EXTERN  __declspec(dllimport)
+typedef void CURL;
+typedef void CURLSH;
 #endif
-#else
 
-#ifdef CURL_HIDDEN_SYMBOLS
 /*
- * This definition is used to make external definitions visible in the
- * shared library when symbols are hidden by default.  It makes no
- * difference when compiling applications whether this is set or not,
- * only when compiling the library.
+ * libcurl external API function linkage decorations.
  */
-#define CURL_EXTERN CURL_EXTERN_SYMBOL
+
+#ifdef CURL_STATICLIB
+#  define CURL_EXTERN
+#elif defined(CURL_WIN32) || defined(__SYMBIAN32__) || \
+     (__has_declspec_attribute(dllexport) && \
+      __has_declspec_attribute(dllimport))
+#  if defined(BUILDING_LIBCURL)
+#    define CURL_EXTERN  __declspec(dllexport)
+#  else
+#    define CURL_EXTERN  __declspec(dllimport)
+#  endif
+#elif defined(BUILDING_LIBCURL) && defined(CURL_HIDDEN_SYMBOLS)
+#  define CURL_EXTERN CURL_EXTERN_SYMBOL
 #else
-#define CURL_EXTERN
-#endif
+#  define CURL_EXTERN
 #endif
 
 #ifndef curl_socket_typedef
 /* socket typedef */
-#ifdef WIN32
+#if defined(CURL_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H)
 typedef SOCKET curl_socket_t;
 #define CURL_SOCKET_BAD INVALID_SOCKET
 #else
@@ -132,46 +140,103 @@ typedef int curl_socket_t;
 #define curl_socket_typedef
 #endif /* curl_socket_typedef */
 
+/* enum for the different supported SSL backends */
+typedef enum {
+  CURLSSLBACKEND_NONE = 0,
+  CURLSSLBACKEND_OPENSSL = 1,
+  CURLSSLBACKEND_GNUTLS = 2,
+  CURLSSLBACKEND_NSS = 3,
+  CURLSSLBACKEND_OBSOLETE4 = 4,  /* Was QSOSSL. */
+  CURLSSLBACKEND_GSKIT = 5,
+  CURLSSLBACKEND_POLARSSL = 6,
+  CURLSSLBACKEND_WOLFSSL = 7,
+  CURLSSLBACKEND_SCHANNEL = 8,
+  CURLSSLBACKEND_SECURETRANSPORT = 9,
+  CURLSSLBACKEND_AXTLS = 10, /* never used since 7.63.0 */
+  CURLSSLBACKEND_MBEDTLS = 11,
+  CURLSSLBACKEND_MESALINK = 12,
+  CURLSSLBACKEND_BEARSSL = 13
+} curl_sslbackend;
+
+/* aliases for library clones and renames */
+#define CURLSSLBACKEND_LIBRESSL CURLSSLBACKEND_OPENSSL
+#define CURLSSLBACKEND_BORINGSSL CURLSSLBACKEND_OPENSSL
+
+/* deprecated names: */
+#define CURLSSLBACKEND_CYASSL CURLSSLBACKEND_WOLFSSL
+#define CURLSSLBACKEND_DARWINSSL CURLSSLBACKEND_SECURETRANSPORT
+
 struct curl_httppost {
   struct curl_httppost *next;       /* next entry in the list */
   char *name;                       /* pointer to allocated name */
   long namelength;                  /* length of name length */
   char *contents;                   /* pointer to allocated data contents */
-  long contentslength;              /* length of contents field */
+  long contentslength;              /* length of contents field, see also
+                                       CURL_HTTPPOST_LARGE */
   char *buffer;                     /* pointer to allocated buffer contents */
   long bufferlength;                /* length of buffer field */
   char *contenttype;                /* Content-Type */
-  struct curl_slist* contentheader; /* list of extra headers for this form */
+  struct curl_slist *contentheader; /* list of extra headers for this form */
   struct curl_httppost *more;       /* if one field name has more than one
                                        file, this link should link to following
                                        files */
   long flags;                       /* as defined below */
-#define HTTPPOST_FILENAME (1<<0)    /* specified content is a file name */
-#define HTTPPOST_READFILE (1<<1)    /* specified content is a file name */
-#define HTTPPOST_PTRNAME (1<<2)     /* name is only stored pointer
-                                       do not free in formfree */
-#define HTTPPOST_PTRCONTENTS (1<<3) /* contents is only stored pointer
-                                       do not free in formfree */
-#define HTTPPOST_BUFFER (1<<4)      /* upload file from buffer */
-#define HTTPPOST_PTRBUFFER (1<<5)   /* upload file from pointer contents */
-#define HTTPPOST_CALLBACK (1<<6)    /* upload file contents by using the
-                                       regular read callback to get the data
-                                       and pass the given pointer as custom
-                                       pointer */
+
+/* specified content is a file name */
+#define CURL_HTTPPOST_FILENAME (1<<0)
+/* specified content is a file name */
+#define CURL_HTTPPOST_READFILE (1<<1)
+/* name is only stored pointer do not free in formfree */
+#define CURL_HTTPPOST_PTRNAME (1<<2)
+/* contents is only stored pointer do not free in formfree */
+#define CURL_HTTPPOST_PTRCONTENTS (1<<3)
+/* upload file from buffer */
+#define CURL_HTTPPOST_BUFFER (1<<4)
+/* upload file from pointer contents */
+#define CURL_HTTPPOST_PTRBUFFER (1<<5)
+/* upload file contents by using the regular read callback to get the data and
+   pass the given pointer as custom pointer */
+#define CURL_HTTPPOST_CALLBACK (1<<6)
+/* use size in 'contentlen', added in 7.46.0 */
+#define CURL_HTTPPOST_LARGE (1<<7)
 
   char *showfilename;               /* The file name to show. If not set, the
                                        actual file name will be used (if this
                                        is a file part) */
   void *userp;                      /* custom pointer used for
                                        HTTPPOST_CALLBACK posts */
+  curl_off_t contentlen;            /* alternative length of contents
+                                       field. Used if CURL_HTTPPOST_LARGE is
+                                       set. Added in 7.46.0 */
 };
 
+
+/* This is a return code for the progress callback that, when returned, will
+   signal libcurl to continue executing the default progress function */
+#define CURL_PROGRESSFUNC_CONTINUE 0x10000001
+
+/* This is the CURLOPT_PROGRESSFUNCTION callback prototype. It is now
+   considered deprecated but was the only choice up until 7.31.0 */
 typedef int (*curl_progress_callback)(void *clientp,
                                       double dltotal,
                                       double dlnow,
                                       double ultotal,
                                       double ulnow);
 
+/* This is the CURLOPT_XFERINFOFUNCTION callback prototype. It was introduced
+   in 7.32.0, avoids the use of floating point numbers and provides more
+   detailed information. */
+typedef int (*curl_xferinfo_callback)(void *clientp,
+                                      curl_off_t dltotal,
+                                      curl_off_t dlnow,
+                                      curl_off_t ultotal,
+                                      curl_off_t ulnow);
+
+#ifndef CURL_MAX_READ_SIZE
+  /* The maximum receive buffer size configurable via CURLOPT_BUFFERSIZE. */
+#define CURL_MAX_READ_SIZE 524288
+#endif
+
 #ifndef CURL_MAX_WRITE_SIZE
   /* Tests have proven that 20K is a very bad buffer size for uploads on
      Windows, while 16K for some odd reason performed a lot better.
@@ -189,16 +254,18 @@ typedef int (*curl_progress_callback)(void *clientp,
 #define CURL_MAX_HTTP_HEADER (100*1024)
 #endif
 
-
 /* This is a magic return code for the write callback that, when returned,
    will signal libcurl to pause receiving on the current transfer. */
 #define CURL_WRITEFUNC_PAUSE 0x10000001
+
 typedef size_t (*curl_write_callback)(char *buffer,
                                       size_t size,
                                       size_t nitems,
                                       void *outstream);
 
-
+/* This callback will be called when a new resolver request is made */
+typedef int (*curl_resolver_start_callback)(void *resolver_state,
+                                            void *reserved, void *userdata);
 
 /* enumeration of file types */
 typedef enum {
@@ -223,14 +290,11 @@ typedef enum {
 #define CURLFINFOFLAG_KNOWN_SIZE        (1<<6)
 #define CURLFINFOFLAG_KNOWN_HLINKCOUNT  (1<<7)
 
-/* Content of this structure depends on information which is known and is
-   achievable (e.g. by FTP LIST parsing). Please see the url_easy_setopt(3) man
-   page for callbacks returning this structure -- some fields are mandatory,
-   some others are optional. The FLAG field has special meaning. */
+/* Information about a single file, used when doing FTP wildcard matching */
 struct curl_fileinfo {
   char *filename;
   curlfiletype filetype;
-  time_t time;
+  time_t time; /* always zero! */
   unsigned int perm;
   int uid;
   int gid;
@@ -249,7 +313,7 @@ struct curl_fileinfo {
   unsigned int flags;
 
   /* used internally */
-  char * b_data;
+  char *b_data;
   size_t b_size;
   size_t b_used;
 };
@@ -305,14 +369,25 @@ typedef int (*curl_seek_callback)(void *instream,
    signal libcurl to pause sending data on the current transfer. */
 #define CURL_READFUNC_PAUSE 0x10000001
 
+/* Return code for when the trailing headers' callback has terminated
+   without any errors*/
+#define CURL_TRAILERFUNC_OK 0
+/* Return code for when was an error in the trailing header's list and we
+  want to abort the request */
+#define CURL_TRAILERFUNC_ABORT 1
+
 typedef size_t (*curl_read_callback)(char *buffer,
                                       size_t size,
                                       size_t nitems,
                                       void *instream);
 
-typedef enum  {
-  CURLSOCKTYPE_IPCXN, /* socket created for a specific IP connection */
-  CURLSOCKTYPE_LAST   /* never use */
+typedef int (*curl_trailer_callback)(struct curl_slist **list,
+                                      void *userdata);
+
+typedef enum {
+  CURLSOCKTYPE_IPCXN,  /* socket created for a specific IP connection */
+  CURLSOCKTYPE_ACCEPT, /* socket created by accept() call */
+  CURLSOCKTYPE_LAST    /* never use */
 } curlsocktype;
 
 /* The return code from the sockopt_callback can signal information back
@@ -341,6 +416,9 @@ typedef curl_socket_t
                             curlsocktype purpose,
                             struct curl_sockaddr *address);
 
+typedef int
+(*curl_closesocket_callback)(void *clientp, curl_socket_t item);
+
 typedef enum {
   CURLIOE_OK,            /* I/O operation successful */
   CURLIOE_UNKNOWNCMD,    /* command was unknown to callback */
@@ -348,7 +426,7 @@ typedef enum {
   CURLIOE_LAST           /* never use */
 } curlioerr;
 
-typedef enum  {
+typedef enum {
   CURLIOCMD_NOP,         /* no operation */
   CURLIOCMD_RESTARTREAD, /* restart the read stream from start */
   CURLIOCMD_LAST         /* never use */
@@ -358,6 +436,7 @@ typedef curlioerr (*curl_ioctl_callback)(CURL *handle,
                                          int cmd,
                                          void *clientp);
 
+#ifndef CURL_DID_MEMORY_FUNC_TYPEDEFS
 /*
  * The following typedef's are signatures of malloc, free, realloc, strdup and
  * calloc respectively.  Function pointers of these types can be passed to the
@@ -370,6 +449,9 @@ typedef void *(*curl_realloc_callback)(void *ptr, size_t size);
 typedef char *(*curl_strdup_callback)(const char *str);
 typedef void *(*curl_calloc_callback)(size_t nmemb, size_t size);
 
+#define CURL_DID_MEMORY_FUNC_TYPEDEFS
+#endif
+
 /* the kind of data that is passed to information_callback*/
 typedef enum {
   CURLINFO_TEXT = 0,
@@ -406,17 +488,22 @@ typedef enum {
   CURLE_COULDNT_RESOLVE_PROXY,   /* 5 */
   CURLE_COULDNT_RESOLVE_HOST,    /* 6 */
   CURLE_COULDNT_CONNECT,         /* 7 */
-  CURLE_FTP_WEIRD_SERVER_REPLY,  /* 8 */
+  CURLE_WEIRD_SERVER_REPLY,      /* 8 */
   CURLE_REMOTE_ACCESS_DENIED,    /* 9 a service was denied by the server
                                     due to lack of access - when login fails
                                     this is not returned. */
-  CURLE_OBSOLETE10,              /* 10 - NOT USED */
+  CURLE_FTP_ACCEPT_FAILED,       /* 10 - [was obsoleted in April 2006 for
+                                    7.15.4, reused in Dec 2011 for 7.24.0]*/
   CURLE_FTP_WEIRD_PASS_REPLY,    /* 11 */
-  CURLE_OBSOLETE12,              /* 12 - NOT USED */
+  CURLE_FTP_ACCEPT_TIMEOUT,      /* 12 - timeout occurred accepting server
+                                    [was obsoleted in August 2007 for 7.17.0,
+                                    reused in Dec 2011 for 7.24.0]*/
   CURLE_FTP_WEIRD_PASV_REPLY,    /* 13 */
   CURLE_FTP_WEIRD_227_FORMAT,    /* 14 */
   CURLE_FTP_CANT_GET_HOST,       /* 15 */
-  CURLE_OBSOLETE16,              /* 16 - NOT USED */
+  CURLE_HTTP2,                   /* 16 - A problem in the http2 framing layer.
+                                    [was obsoleted in August 2007 for 7.17.0,
+                                    reused in July 2014 for 7.38.0] */
   CURLE_FTP_COULDNT_SET_TYPE,    /* 17 */
   CURLE_PARTIAL_FILE,            /* 18 */
   CURLE_FTP_COULDNT_RETR_FILE,   /* 19 */
@@ -445,18 +532,17 @@ typedef enum {
   CURLE_LDAP_CANNOT_BIND,        /* 38 */
   CURLE_LDAP_SEARCH_FAILED,      /* 39 */
   CURLE_OBSOLETE40,              /* 40 - NOT USED */
-  CURLE_FUNCTION_NOT_FOUND,      /* 41 */
+  CURLE_FUNCTION_NOT_FOUND,      /* 41 - NOT USED starting with 7.53.0 */
   CURLE_ABORTED_BY_CALLBACK,     /* 42 */
   CURLE_BAD_FUNCTION_ARGUMENT,   /* 43 */
   CURLE_OBSOLETE44,              /* 44 - NOT USED */
   CURLE_INTERFACE_FAILED,        /* 45 - CURLOPT_INTERFACE failed */
   CURLE_OBSOLETE46,              /* 46 - NOT USED */
-  CURLE_TOO_MANY_REDIRECTS ,     /* 47 - catch endless re-direct loops */
+  CURLE_TOO_MANY_REDIRECTS,      /* 47 - catch endless re-direct loops */
   CURLE_UNKNOWN_OPTION,          /* 48 - User specified an unknown option */
-  CURLE_TELNET_OPTION_SYNTAX ,   /* 49 - Malformed telnet option */
+  CURLE_TELNET_OPTION_SYNTAX,    /* 49 - Malformed telnet option */
   CURLE_OBSOLETE50,              /* 50 - NOT USED */
-  CURLE_PEER_FAILED_VERIFICATION, /* 51 - peer's certificate or fingerprint
-                                     wasn't verified fine */
+  CURLE_OBSOLETE51,              /* 51 - NOT USED */
   CURLE_GOT_NOTHING,             /* 52 - when this is a specific error */
   CURLE_SSL_ENGINE_NOTFOUND,     /* 53 - SSL crypto engine not found */
   CURLE_SSL_ENGINE_SETFAILED,    /* 54 - can not set SSL crypto engine as
@@ -466,7 +552,8 @@ typedef enum {
   CURLE_OBSOLETE57,              /* 57 - NOT IN USE */
   CURLE_SSL_CERTPROBLEM,         /* 58 - problem with the local certificate */
   CURLE_SSL_CIPHER,              /* 59 - couldn't use specified cipher */
-  CURLE_SSL_CACERT,              /* 60 - problem with the CA cert (path?) */
+  CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint
+                                     wasn't verified fine */
   CURLE_BAD_CONTENT_ENCODING,    /* 61 - Unrecognized/bad encoding */
   CURLE_LDAP_INVALID_URL,        /* 62 - Invalid LDAP URL */
   CURLE_FILESIZE_EXCEEDED,       /* 63 - Maximum file size exceeded */
@@ -507,18 +594,41 @@ typedef enum {
                                     7.19.0) */
   CURLE_FTP_PRET_FAILED,         /* 84 - a PRET command failed */
   CURLE_RTSP_CSEQ_ERROR,         /* 85 - mismatch of RTSP CSeq numbers */
-  CURLE_RTSP_SESSION_ERROR,      /* 86 - mismatch of RTSP Session Identifiers */
+  CURLE_RTSP_SESSION_ERROR,      /* 86 - mismatch of RTSP Session Ids */
   CURLE_FTP_BAD_FILE_LIST,       /* 87 - unable to parse FTP file list */
   CURLE_CHUNK_FAILED,            /* 88 - chunk callback reported error */
-
+  CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the
+                                    session will be queued */
+  CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not
+                                     match */
+  CURLE_SSL_INVALIDCERTSTATUS,   /* 91 - invalid certificate status */
+  CURLE_HTTP2_STREAM,            /* 92 - stream error in HTTP/2 framing layer
+                                    */
+  CURLE_RECURSIVE_API_CALL,      /* 93 - an api function was called from
+                                    inside a callback */
+  CURLE_AUTH_ERROR,              /* 94 - an authentication function returned an
+                                    error */
+  CURLE_HTTP3,                   /* 95 - An HTTP/3 layer problem */
+  CURLE_QUIC_CONNECT_ERROR,      /* 96 - QUIC connection error */
   CURL_LAST /* never use! */
 } CURLcode;
 
 #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all
                           the obsolete stuff removed! */
 
+/* Previously obsolete error code re-used in 7.38.0 */
+#define CURLE_OBSOLETE16 CURLE_HTTP2
+
+/* Previously obsolete error codes re-used in 7.24.0 */
+#define CURLE_OBSOLETE10 CURLE_FTP_ACCEPT_FAILED
+#define CURLE_OBSOLETE12 CURLE_FTP_ACCEPT_TIMEOUT
+
 /*  compatibility with older names */
 #define CURLOPT_ENCODING CURLOPT_ACCEPT_ENCODING
+#define CURLE_FTP_WEIRD_SERVER_REPLY CURLE_WEIRD_SERVER_REPLY
+
+/* The following were added in 7.62.0 */
+#define CURLE_SSL_CACERT CURLE_PEER_FAILED_VERIFICATION
 
 /* The following were added in 7.21.5, April 2011 */
 #define CURLE_UNKNOWN_TELNET_OPTION CURLE_UNKNOWN_OPTION
@@ -567,14 +677,26 @@ typedef enum {
    make programs break */
 #define CURLE_ALREADY_COMPLETE 99999
 
+/* Provide defines for really old option names */
+#define CURLOPT_FILE CURLOPT_WRITEDATA /* name changed in 7.9.7 */
+#define CURLOPT_INFILE CURLOPT_READDATA /* name changed in 7.9.7 */
+#define CURLOPT_WRITEHEADER CURLOPT_HEADERDATA
+
+/* Since long deprecated options with no code in the lib that does anything
+   with them. */
+#define CURLOPT_WRITEINFO CURLOPT_OBSOLETE40
+#define CURLOPT_CLOSEPOLICY CURLOPT_OBSOLETE72
+
 #endif /*!CURL_NO_OLDIES*/
 
 /* This prototype applies to all conversion callbacks */
 typedef CURLcode (*curl_conv_callback)(char *buffer, size_t length);
 
 typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl,    /* easy handle */
-                                          void *ssl_ctx, /* actually an
-                                                            OpenSSL SSL_CTX */
+                                          void *ssl_ctx, /* actually an OpenSSL
+                                                            or WolfSSL SSL_CTX,
+                                                            or an mbedTLS
+                                                          mbedtls_ssl_config */
                                           void *userptr);
 
 typedef enum {
@@ -582,6 +704,7 @@ typedef enum {
                            CONNECT HTTP/1.1 */
   CURLPROXY_HTTP_1_0 = 1,   /* added in 7.19.4, force to use CONNECT
                                HTTP/1.0  */
+  CURLPROXY_HTTPS = 2, /* added in 7.52.0 */
   CURLPROXY_SOCKS4 = 4, /* support added in 7.15.2, enum existed already
                            in 7.10 */
   CURLPROXY_SOCKS5 = 5, /* added in 7.10 */
@@ -591,17 +714,39 @@ typedef enum {
                                    in 7.18.0 */
 } curl_proxytype;  /* this enum was added in 7.10 */
 
-#define CURLAUTH_NONE         0       /* nothing */
-#define CURLAUTH_BASIC        (1<<0)  /* Basic (default) */
-#define CURLAUTH_DIGEST       (1<<1)  /* Digest */
-#define CURLAUTH_GSSNEGOTIATE (1<<2)  /* GSS-Negotiate */
-#define CURLAUTH_NTLM         (1<<3)  /* NTLM */
-#define CURLAUTH_DIGEST_IE    (1<<4)  /* Digest with IE flavour */
-#define CURLAUTH_ONLY         (1<<31) /* used together with a single other
-                                         type to force no auth or just that
-                                         single type */
-#define CURLAUTH_ANY (~CURLAUTH_DIGEST_IE)  /* all fine types set */
-#define CURLAUTH_ANYSAFE (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE))
+/*
+ * Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options:
+ *
+ * CURLAUTH_NONE         - No HTTP authentication
+ * CURLAUTH_BASIC        - HTTP Basic authentication (default)
+ * CURLAUTH_DIGEST       - HTTP Digest authentication
+ * CURLAUTH_NEGOTIATE    - HTTP Negotiate (SPNEGO) authentication
+ * CURLAUTH_GSSNEGOTIATE - Alias for CURLAUTH_NEGOTIATE (deprecated)
+ * CURLAUTH_NTLM         - HTTP NTLM authentication
+ * CURLAUTH_DIGEST_IE    - HTTP Digest authentication with IE flavour
+ * CURLAUTH_NTLM_WB      - HTTP NTLM authentication delegated to winbind helper
+ * CURLAUTH_BEARER       - HTTP Bearer token authentication
+ * CURLAUTH_ONLY         - Use together with a single other type to force no
+ *                         authentication or just that single type
+ * CURLAUTH_ANY          - All fine types set
+ * CURLAUTH_ANYSAFE      - All fine types except Basic
+ */
+
+#define CURLAUTH_NONE         ((unsigned long)0)
+#define CURLAUTH_BASIC        (((unsigned long)1)<<0)
+#define CURLAUTH_DIGEST       (((unsigned long)1)<<1)
+#define CURLAUTH_NEGOTIATE    (((unsigned long)1)<<2)
+/* Deprecated since the advent of CURLAUTH_NEGOTIATE */
+#define CURLAUTH_GSSNEGOTIATE CURLAUTH_NEGOTIATE
+/* Used for CURLOPT_SOCKS5_AUTH to stay terminologically correct */
+#define CURLAUTH_GSSAPI CURLAUTH_NEGOTIATE
+#define CURLAUTH_NTLM         (((unsigned long)1)<<3)
+#define CURLAUTH_DIGEST_IE    (((unsigned long)1)<<4)
+#define CURLAUTH_NTLM_WB      (((unsigned long)1)<<5)
+#define CURLAUTH_BEARER       (((unsigned long)1)<<6)
+#define CURLAUTH_ONLY         (((unsigned long)1)<<31)
+#define CURLAUTH_ANY          (~CURLAUTH_DIGEST_IE)
+#define CURLAUTH_ANYSAFE      (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE))
 
 #define CURLSSH_AUTH_ANY       ~0     /* all types supported by the server */
 #define CURLSSH_AUTH_NONE      0      /* none allowed, silly but complete */
@@ -609,20 +754,30 @@ typedef enum {
 #define CURLSSH_AUTH_PASSWORD  (1<<1) /* password */
 #define CURLSSH_AUTH_HOST      (1<<2) /* host key files */
 #define CURLSSH_AUTH_KEYBOARD  (1<<3) /* keyboard interactive */
+#define CURLSSH_AUTH_AGENT     (1<<4) /* agent (ssh-agent, pageant...) */
+#define CURLSSH_AUTH_GSSAPI    (1<<5) /* gssapi (kerberos, ...) */
 #define CURLSSH_AUTH_DEFAULT CURLSSH_AUTH_ANY
 
+#define CURLGSSAPI_DELEGATION_NONE        0      /* no delegation (default) */
+#define CURLGSSAPI_DELEGATION_POLICY_FLAG (1<<0) /* if permitted by policy */
+#define CURLGSSAPI_DELEGATION_FLAG        (1<<1) /* delegate always */
+
 #define CURL_ERROR_SIZE 256
 
+enum curl_khtype {
+  CURLKHTYPE_UNKNOWN,
+  CURLKHTYPE_RSA1,
+  CURLKHTYPE_RSA,
+  CURLKHTYPE_DSS,
+  CURLKHTYPE_ECDSA,
+  CURLKHTYPE_ED25519
+};
+
 struct curl_khkey {
   const char *key; /* points to a zero-terminated string encoded with base64
                       if len is zero, otherwise to the "raw" data */
   size_t len;
-  enum type {
-    CURLKHTYPE_UNKNOWN,
-    CURLKHTYPE_RSA1,
-    CURLKHTYPE_RSA,
-    CURLKHTYPE_DSS
-  } keytype;
+  enum curl_khtype keytype;
 };
 
 /* this is the set of return values expected from the curl_sshkeycallback
@@ -661,6 +816,31 @@ typedef enum {
   CURLUSESSL_LAST     /* not an option, never use */
 } curl_usessl;
 
+/* Definition of bits for the CURLOPT_SSL_OPTIONS argument: */
+
+/* - ALLOW_BEAST tells libcurl to allow the BEAST SSL vulnerability in the
+   name of improving interoperability with older servers. Some SSL libraries
+   have introduced work-arounds for this flaw but those work-arounds sometimes
+   make the SSL communication fail. To regain functionality with those broken
+   servers, a user can this way allow the vulnerability back. */
+#define CURLSSLOPT_ALLOW_BEAST (1<<0)
+
+/* - NO_REVOKE tells libcurl to disable certificate revocation checks for those
+   SSL backends where such behavior is present. */
+#define CURLSSLOPT_NO_REVOKE (1<<1)
+
+/* - NO_PARTIALCHAIN tells libcurl to *NOT* accept a partial certificate chain
+   if possible. The OpenSSL backend has this ability. */
+#define CURLSSLOPT_NO_PARTIALCHAIN (1<<2)
+
+/* The default connection attempt delay in milliseconds for happy eyeballs.
+   CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 and happy-eyeballs-timeout-ms.d document
+   this value, keep them in sync. */
+#define CURL_HET_DEFAULT 200L
+
+/* The default connection upkeep interval in milliseconds. */
+#define CURL_UPKEEP_INTERVAL_DEFAULT 60000L
+
 #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all
                           the obsolete stuff removed! */
 
@@ -711,6 +891,18 @@ typedef enum {
   CURLFTPMETHOD_LAST       /* not an option, never use */
 } curl_ftpmethod;
 
+/* bitmask defines for CURLOPT_HEADEROPT */
+#define CURLHEADER_UNIFIED  0
+#define CURLHEADER_SEPARATE (1<<0)
+
+/* CURLALTSVC_* are bits for the CURLOPT_ALTSVC_CTRL option */
+#define CURLALTSVC_IMMEDIATELY  (1<<0)
+
+#define CURLALTSVC_READONLYFILE (1<<2)
+#define CURLALTSVC_H1           (1<<3)
+#define CURLALTSVC_H2           (1<<4)
+#define CURLALTSVC_H3           (1<<5)
+
 /* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */
 #define CURLPROTO_HTTP   (1<<0)
 #define CURLPROTO_HTTPS  (1<<1)
@@ -738,6 +930,8 @@ typedef enum {
 #define CURLPROTO_RTMPS  (1<<23)
 #define CURLPROTO_RTMPTS (1<<24)
 #define CURLPROTO_GOPHER (1<<25)
+#define CURLPROTO_SMB    (1<<26)
+#define CURLPROTO_SMBS   (1<<27)
 #define CURLPROTO_ALL    (~0) /* enable everything */
 
 /* long may be 32 or 64 bits, but we should never depend on anything else
@@ -747,71 +941,61 @@ typedef enum {
 #define CURLOPTTYPE_FUNCTIONPOINT 20000
 #define CURLOPTTYPE_OFF_T         30000
 
-/* name is uppercase CURLOPT_<name>,
-   type is one of the defined CURLOPTTYPE_<type>
-   number is unique identifier */
-#ifdef CINIT
-#undef CINIT
-#endif
+/* *STRINGPOINT is an alias for OBJECTPOINT to allow tools to extract the
+   string options from the header file */
 
-#ifdef CURL_ISOCPP
-#define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number
-#else
-/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */
-#define LONG          CURLOPTTYPE_LONG
-#define OBJECTPOINT   CURLOPTTYPE_OBJECTPOINT
-#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT
-#define OFF_T         CURLOPTTYPE_OFF_T
-#define CINIT(name,type,number) CURLOPT_/**/name = type + number
-#endif
+
+#define CURLOPT(na,t,nu) na = t + nu
+
+/* handy aliases that make no run-time difference */
+#define CURLOPTTYPE_STRINGPOINT  CURLOPTTYPE_OBJECTPOINT
+#define CURLOPTTYPE_SLISTPOINT  CURLOPTTYPE_OBJECTPOINT
 
 /*
- * This macro-mania below setups the CURLOPT_[what] enum, to be used with
- * curl_easy_setopt(). The first argument in the CINIT() macro is the [what]
- * word.
+ * All CURLOPT_* values.
  */
 
 typedef enum {
   /* This is the FILE * or void * the regular output should be written to. */
-  CINIT(FILE, OBJECTPOINT, 1),
+  CURLOPT(CURLOPT_WRITEDATA, CURLOPTTYPE_OBJECTPOINT, 1),
 
   /* The full URL to get/put */
-  CINIT(URL,  OBJECTPOINT, 2),
+  CURLOPT(CURLOPT_URL, CURLOPTTYPE_STRINGPOINT, 2),
 
   /* Port number to connect to, if other than default. */
-  CINIT(PORT, LONG, 3),
+  CURLOPT(CURLOPT_PORT, CURLOPTTYPE_LONG, 3),
 
   /* Name of proxy to use. */
-  CINIT(PROXY, OBJECTPOINT, 4),
+  CURLOPT(CURLOPT_PROXY, CURLOPTTYPE_STRINGPOINT, 4),
 
-  /* "name:password" to use when fetching. */
-  CINIT(USERPWD, OBJECTPOINT, 5),
+  /* "user:password;options" to use when fetching. */
+  CURLOPT(CURLOPT_USERPWD, CURLOPTTYPE_STRINGPOINT, 5),
 
-  /* "name:password" to use with proxy. */
-  CINIT(PROXYUSERPWD, OBJECTPOINT, 6),
+  /* "user:password" to use with proxy. */
+  CURLOPT(CURLOPT_PROXYUSERPWD, CURLOPTTYPE_STRINGPOINT, 6),
 
   /* Range to get, specified as an ASCII string. */
-  CINIT(RANGE, OBJECTPOINT, 7),
+  CURLOPT(CURLOPT_RANGE, CURLOPTTYPE_STRINGPOINT, 7),
 
   /* not used */
 
   /* Specified file stream to upload from (use as input): */
-  CINIT(INFILE, OBJECTPOINT, 9),
+  CURLOPT(CURLOPT_READDATA, CURLOPTTYPE_OBJECTPOINT, 9),
 
   /* Buffer to receive error messages in, must be at least CURL_ERROR_SIZE
-   * bytes big. If this is not used, error messages go to stderr instead: */
-  CINIT(ERRORBUFFER, OBJECTPOINT, 10),
+   * bytes big. */
+  CURLOPT(CURLOPT_ERRORBUFFER, CURLOPTTYPE_OBJECTPOINT, 10),
 
   /* Function that will be called to store the output (instead of fwrite). The
    * parameters will use fwrite() syntax, make sure to follow them. */
-  CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11),
+  CURLOPT(CURLOPT_WRITEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 11),
 
   /* Function that will be called to read the input (instead of fread). The
    * parameters will use fread() syntax, make sure to follow them. */
-  CINIT(READFUNCTION, FUNCTIONPOINT, 12),
+  CURLOPT(CURLOPT_READFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 12),
 
   /* Time-out the read operation after this amount of seconds */
-  CINIT(TIMEOUT, LONG, 13),
+  CURLOPT(CURLOPT_TIMEOUT, CURLOPTTYPE_LONG, 13),
 
   /* If the CURLOPT_INFILE is used, this can be used to inform libcurl about
    * how large the file being sent really is. That allows better error
@@ -822,20 +1006,20 @@ typedef enum {
    * which takes an off_t type, allowing platforms with larger off_t
    * sizes to handle larger files.  See below for INFILESIZE_LARGE.
    */
-  CINIT(INFILESIZE, LONG, 14),
+  CURLOPT(CURLOPT_INFILESIZE, CURLOPTTYPE_LONG, 14),
 
   /* POST static input fields. */
-  CINIT(POSTFIELDS, OBJECTPOINT, 15),
+  CURLOPT(CURLOPT_POSTFIELDS, CURLOPTTYPE_OBJECTPOINT, 15),
 
   /* Set the referrer page (needed by some CGIs) */
-  CINIT(REFERER, OBJECTPOINT, 16),
+  CURLOPT(CURLOPT_REFERER, CURLOPTTYPE_STRINGPOINT, 16),
 
   /* Set the FTP PORT string (interface name, named or numerical IP address)
      Use i.e '-' to use default address. */
-  CINIT(FTPPORT, OBJECTPOINT, 17),
+  CURLOPT(CURLOPT_FTPPORT, CURLOPTTYPE_STRINGPOINT, 17),
 
   /* Set the User-Agent string (examined by some CGIs) */
-  CINIT(USERAGENT, OBJECTPOINT, 18),
+  CURLOPT(CURLOPT_USERAGENT, CURLOPTTYPE_STRINGPOINT, 18),
 
   /* If the download receives less than "low speed limit" bytes/second
    * during "low speed time" seconds, the operations is aborted.
@@ -844,10 +1028,10 @@ typedef enum {
    */
 
   /* Set the "low speed limit" */
-  CINIT(LOW_SPEED_LIMIT, LONG, 19),
+  CURLOPT(CURLOPT_LOW_SPEED_LIMIT, CURLOPTTYPE_LONG, 19),
 
   /* Set the "low speed time" */
-  CINIT(LOW_SPEED_TIME, LONG, 20),
+  CURLOPT(CURLOPT_LOW_SPEED_TIME, CURLOPTTYPE_LONG, 20),
 
   /* Set the continuation offset.
    *
@@ -855,47 +1039,48 @@ typedef enum {
    * off_t types, allowing for large file offsets on platforms which
    * use larger-than-32-bit off_t's.  Look below for RESUME_FROM_LARGE.
    */
-  CINIT(RESUME_FROM, LONG, 21),
+  CURLOPT(CURLOPT_RESUME_FROM, CURLOPTTYPE_LONG, 21),
 
   /* Set cookie in request: */
-  CINIT(COOKIE, OBJECTPOINT, 22),
+  CURLOPT(CURLOPT_COOKIE, CURLOPTTYPE_STRINGPOINT, 22),
 
-  /* This points to a linked list of headers, struct curl_slist kind */
-  CINIT(HTTPHEADER, OBJECTPOINT, 23),
+  /* This points to a linked list of headers, struct curl_slist kind. This
+     list is also used for RTSP (in spite of its name) */
+  CURLOPT(CURLOPT_HTTPHEADER, CURLOPTTYPE_SLISTPOINT, 23),
 
   /* This points to a linked list of post entries, struct curl_httppost */
-  CINIT(HTTPPOST, OBJECTPOINT, 24),
+  CURLOPT(CURLOPT_HTTPPOST, CURLOPTTYPE_OBJECTPOINT, 24),
 
   /* name of the file keeping your private SSL-certificate */
-  CINIT(SSLCERT, OBJECTPOINT, 25),
+  CURLOPT(CURLOPT_SSLCERT, CURLOPTTYPE_STRINGPOINT, 25),
 
   /* password for the SSL or SSH private key */
-  CINIT(KEYPASSWD, OBJECTPOINT, 26),
+  CURLOPT(CURLOPT_KEYPASSWD, CURLOPTTYPE_STRINGPOINT, 26),
 
   /* send TYPE parameter? */
-  CINIT(CRLF, LONG, 27),
+  CURLOPT(CURLOPT_CRLF, CURLOPTTYPE_LONG, 27),
 
   /* send linked-list of QUOTE commands */
-  CINIT(QUOTE, OBJECTPOINT, 28),
+  CURLOPT(CURLOPT_QUOTE, CURLOPTTYPE_SLISTPOINT, 28),
 
   /* send FILE * or void * to store headers to, if you use a callback it
      is simply passed to the callback unmodified */
-  CINIT(WRITEHEADER, OBJECTPOINT, 29),
+  CURLOPT(CURLOPT_HEADERDATA, CURLOPTTYPE_OBJECTPOINT, 29),
 
   /* point to a file to read the initial cookies from, also enables
      "cookie awareness" */
-  CINIT(COOKIEFILE, OBJECTPOINT, 31),
+  CURLOPT(CURLOPT_COOKIEFILE, CURLOPTTYPE_STRINGPOINT, 31),
 
   /* What version to specifically try to use.
      See CURL_SSLVERSION defines below. */
-  CINIT(SSLVERSION, LONG, 32),
+  CURLOPT(CURLOPT_SSLVERSION, CURLOPTTYPE_LONG, 32),
 
   /* What kind of HTTP time condition to use, see defines */
-  CINIT(TIMECONDITION, LONG, 33),
+  CURLOPT(CURLOPT_TIMECONDITION, CURLOPTTYPE_LONG, 33),
 
   /* Time to use with the above condition. Specified in number of seconds
      since 1 Jan 1970 */
-  CINIT(TIMEVALUE, LONG, 34),
+  CURLOPT(CURLOPT_TIMEVALUE, CURLOPTTYPE_LONG, 34),
 
   /* 35 = OBSOLETE */
 
@@ -903,304 +1088,326 @@ typedef enum {
      HTTP: DELETE, TRACE and others
      FTP: to use a different list command
      */
-  CINIT(CUSTOMREQUEST, OBJECTPOINT, 36),
+  CURLOPT(CURLOPT_CUSTOMREQUEST, CURLOPTTYPE_STRINGPOINT, 36),
 
-  /* HTTP request, for odd commands like DELETE, TRACE and others */
-  CINIT(STDERR, OBJECTPOINT, 37),
+  /* FILE handle to use instead of stderr */
+  CURLOPT(CURLOPT_STDERR, CURLOPTTYPE_OBJECTPOINT, 37),
 
   /* 38 is not used */
 
   /* send linked-list of post-transfer QUOTE commands */
-  CINIT(POSTQUOTE, OBJECTPOINT, 39),
+  CURLOPT(CURLOPT_POSTQUOTE, CURLOPTTYPE_SLISTPOINT, 39),
+
+   /* OBSOLETE, do not use! */
+  CURLOPT(CURLOPT_OBSOLETE40, CURLOPTTYPE_OBJECTPOINT, 40),
+
+  /* talk a lot */
+  CURLOPT(CURLOPT_VERBOSE, CURLOPTTYPE_LONG, 41),
+
+  /* throw the header out too */
+  CURLOPT(CURLOPT_HEADER, CURLOPTTYPE_LONG, 42),
+
+  /* shut off the progress meter */
+  CURLOPT(CURLOPT_NOPROGRESS, CURLOPTTYPE_LONG, 43),
+
+  /* use HEAD to get http document */
+  CURLOPT(CURLOPT_NOBODY, CURLOPTTYPE_LONG, 44),
+
+  /* no output on http error codes >= 400 */
+  CURLOPT(CURLOPT_FAILONERROR, CURLOPTTYPE_LONG, 45),
+
+  /* this is an upload */
+  CURLOPT(CURLOPT_UPLOAD, CURLOPTTYPE_LONG, 46),
 
-  /* Pass a pointer to string of the output using full variable-replacement
-     as described elsewhere. */
-  CINIT(WRITEINFO, OBJECTPOINT, 40),
+  /* HTTP POST method */
+  CURLOPT(CURLOPT_POST, CURLOPTTYPE_LONG, 47),
 
-  CINIT(VERBOSE, LONG, 41),      /* talk a lot */
-  CINIT(HEADER, LONG, 42),       /* throw the header out too */
-  CINIT(NOPROGRESS, LONG, 43),   /* shut off the progress meter */
-  CINIT(NOBODY, LONG, 44),       /* use HEAD to get http document */
-  CINIT(FAILONERROR, LONG, 45),  /* no output on http error codes >= 300 */
-  CINIT(UPLOAD, LONG, 46),       /* this is an upload */
-  CINIT(POST, LONG, 47),         /* HTTP POST method */
-  CINIT(DIRLISTONLY, LONG, 48),  /* return bare names when listing directories */
+  /* bare names when listing directories */
+  CURLOPT(CURLOPT_DIRLISTONLY, CURLOPTTYPE_LONG, 48),
 
-  CINIT(APPEND, LONG, 50),       /* Append instead of overwrite on upload! */
+  /* Append instead of overwrite on upload! */
+  CURLOPT(CURLOPT_APPEND, CURLOPTTYPE_LONG, 50),
 
   /* Specify whether to read the user+password from the .netrc or the URL.
    * This must be one of the CURL_NETRC_* enums below. */
-  CINIT(NETRC, LONG, 51),
+  CURLOPT(CURLOPT_NETRC, CURLOPTTYPE_LONG, 51),
 
-  CINIT(FOLLOWLOCATION, LONG, 52),  /* use Location: Luke! */
+  /* use Location: Luke! */
+  CURLOPT(CURLOPT_FOLLOWLOCATION, CURLOPTTYPE_LONG, 52),
 
-  CINIT(TRANSFERTEXT, LONG, 53), /* transfer data in text/ASCII format */
-  CINIT(PUT, LONG, 54),          /* HTTP PUT */
+   /* transfer data in text/ASCII format */
+  CURLOPT(CURLOPT_TRANSFERTEXT, CURLOPTTYPE_LONG, 53),
+
+  /* HTTP PUT */
+  CURLOPT(CURLOPT_PUT, CURLOPTTYPE_LONG, 54),
 
   /* 55 = OBSOLETE */
 
-  /* Function that will be called instead of the internal progress display
+  /* DEPRECATED
+   * Function that will be called instead of the internal progress display
    * function. This function should be defined as the curl_progress_callback
    * prototype defines. */
-  CINIT(PROGRESSFUNCTION, FUNCTIONPOINT, 56),
+  CURLOPT(CURLOPT_PROGRESSFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 56),
 
-  /* Data passed to the progress callback */
-  CINIT(PROGRESSDATA, OBJECTPOINT, 57),
+  /* Data passed to the CURLOPT_PROGRESSFUNCTION and CURLOPT_XFERINFOFUNCTION
+     callbacks */
+  CURLOPT(CURLOPT_PROGRESSDATA, CURLOPTTYPE_OBJECTPOINT, 57),
+#define CURLOPT_XFERINFODATA CURLOPT_PROGRESSDATA
 
   /* We want the referrer field set automatically when following locations */
-  CINIT(AUTOREFERER, LONG, 58),
+  CURLOPT(CURLOPT_AUTOREFERER, CURLOPTTYPE_LONG, 58),
 
   /* Port of the proxy, can be set in the proxy string as well with:
      "[host]:[port]" */
-  CINIT(PROXYPORT, LONG, 59),
+  CURLOPT(CURLOPT_PROXYPORT, CURLOPTTYPE_LONG, 59),
 
   /* size of the POST input data, if strlen() is not good to use */
-  CINIT(POSTFIELDSIZE, LONG, 60),
+  CURLOPT(CURLOPT_POSTFIELDSIZE, CURLOPTTYPE_LONG, 60),
 
   /* tunnel non-http operations through a HTTP proxy */
-  CINIT(HTTPPROXYTUNNEL, LONG, 61),
+  CURLOPT(CURLOPT_HTTPPROXYTUNNEL, CURLOPTTYPE_LONG, 61),
 
   /* Set the interface string to use as outgoing network interface */
-  CINIT(INTERFACE, OBJECTPOINT, 62),
+  CURLOPT(CURLOPT_INTERFACE, CURLOPTTYPE_STRINGPOINT, 62),
 
   /* Set the krb4/5 security level, this also enables krb4/5 awareness.  This
    * is a string, 'clear', 'safe', 'confidential' or 'private'.  If the string
    * is set but doesn't match one of these, 'private' will be used.  */
-  CINIT(KRBLEVEL, OBJECTPOINT, 63),
+  CURLOPT(CURLOPT_KRBLEVEL, CURLOPTTYPE_STRINGPOINT, 63),
 
   /* Set if we should verify the peer in ssl handshake, set 1 to verify. */
-  CINIT(SSL_VERIFYPEER, LONG, 64),
+  CURLOPT(CURLOPT_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 64),
 
   /* The CApath or CAfile used to validate the peer certificate
      this option is used only if SSL_VERIFYPEER is true */
-  CINIT(CAINFO, OBJECTPOINT, 65),
+  CURLOPT(CURLOPT_CAINFO, CURLOPTTYPE_STRINGPOINT, 65),
 
   /* 66 = OBSOLETE */
   /* 67 = OBSOLETE */
 
   /* Maximum number of http redirects to follow */
-  CINIT(MAXREDIRS, LONG, 68),
+  CURLOPT(CURLOPT_MAXREDIRS, CURLOPTTYPE_LONG, 68),
 
   /* Pass a long set to 1 to get the date of the requested document (if
      possible)! Pass a zero to shut it off. */
-  CINIT(FILETIME, LONG, 69),
+  CURLOPT(CURLOPT_FILETIME, CURLOPTTYPE_LONG, 69),
 
   /* This points to a linked list of telnet options */
-  CINIT(TELNETOPTIONS, OBJECTPOINT, 70),
+  CURLOPT(CURLOPT_TELNETOPTIONS, CURLOPTTYPE_SLISTPOINT, 70),
 
   /* Max amount of cached alive connections */
-  CINIT(MAXCONNECTS, LONG, 71),
+  CURLOPT(CURLOPT_MAXCONNECTS, CURLOPTTYPE_LONG, 71),
 
-  /* What policy to use when closing connections when the cache is filled
-     up */
-  CINIT(CLOSEPOLICY, LONG, 72),
+  /* OBSOLETE, do not use! */
+  CURLOPT(CURLOPT_OBSOLETE72, CURLOPTTYPE_LONG, 72),
 
   /* 73 = OBSOLETE */
 
   /* Set to explicitly use a new connection for the upcoming transfer.
      Do not use this unless you're absolutely sure of this, as it makes the
      operation slower and is less friendly for the network. */
-  CINIT(FRESH_CONNECT, LONG, 74),
+  CURLOPT(CURLOPT_FRESH_CONNECT, CURLOPTTYPE_LONG, 74),
 
   /* Set to explicitly forbid the upcoming transfer's connection to be re-used
      when done. Do not use this unless you're absolutely sure of this, as it
      makes the operation slower and is less friendly for the network. */
-  CINIT(FORBID_REUSE, LONG, 75),
+  CURLOPT(CURLOPT_FORBID_REUSE, CURLOPTTYPE_LONG, 75),
 
   /* Set to a file name that contains random data for libcurl to use to
      seed the random engine when doing SSL connects. */
-  CINIT(RANDOM_FILE, OBJECTPOINT, 76),
+  CURLOPT(CURLOPT_RANDOM_FILE, CURLOPTTYPE_STRINGPOINT, 76),
 
   /* Set to the Entropy Gathering Daemon socket pathname */
-  CINIT(EGDSOCKET, OBJECTPOINT, 77),
+  CURLOPT(CURLOPT_EGDSOCKET, CURLOPTTYPE_STRINGPOINT, 77),
 
-  /* Time-out connect operations after this amount of seconds, if connects
-     are OK within this time, then fine... This only aborts the connect
-     phase. [Only works on unix-style/SIGALRM operating systems] */
-  CINIT(CONNECTTIMEOUT, LONG, 78),
+  /* Time-out connect operations after this amount of seconds, if connects are
+     OK within this time, then fine... This only aborts the connect phase. */
+  CURLOPT(CURLOPT_CONNECTTIMEOUT, CURLOPTTYPE_LONG, 78),
 
   /* Function that will be called to store headers (instead of fwrite). The
    * parameters will use fwrite() syntax, make sure to follow them. */
-  CINIT(HEADERFUNCTION, FUNCTIONPOINT, 79),
+  CURLOPT(CURLOPT_HEADERFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 79),
 
   /* Set this to force the HTTP request to get back to GET. Only really usable
      if POST, PUT or a custom request have been used first.
    */
-  CINIT(HTTPGET, LONG, 80),
+  CURLOPT(CURLOPT_HTTPGET, CURLOPTTYPE_LONG, 80),
 
   /* Set if we should verify the Common name from the peer certificate in ssl
    * handshake, set 1 to check existence, 2 to ensure that it matches the
    * provided hostname. */
-  CINIT(SSL_VERIFYHOST, LONG, 81),
+  CURLOPT(CURLOPT_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 81),
 
   /* Specify which file name to write all known cookies in after completed
      operation. Set file name to "-" (dash) to make it go to stdout. */
-  CINIT(COOKIEJAR, OBJECTPOINT, 82),
+  CURLOPT(CURLOPT_COOKIEJAR, CURLOPTTYPE_STRINGPOINT, 82),
 
   /* Specify which SSL ciphers to use */
-  CINIT(SSL_CIPHER_LIST, OBJECTPOINT, 83),
+  CURLOPT(CURLOPT_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 83),
 
   /* Specify which HTTP version to use! This must be set to one of the
      CURL_HTTP_VERSION* enums set below. */
-  CINIT(HTTP_VERSION, LONG, 84),
+  CURLOPT(CURLOPT_HTTP_VERSION, CURLOPTTYPE_LONG, 84),
 
   /* Specifically switch on or off the FTP engine's use of the EPSV command. By
      default, that one will always be attempted before the more traditional
      PASV command. */
-  CINIT(FTP_USE_EPSV, LONG, 85),
+  CURLOPT(CURLOPT_FTP_USE_EPSV, CURLOPTTYPE_LONG, 85),
 
   /* type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") */
-  CINIT(SSLCERTTYPE, OBJECTPOINT, 86),
+  CURLOPT(CURLOPT_SSLCERTTYPE, CURLOPTTYPE_STRINGPOINT, 86),
 
   /* name of the file keeping your private SSL-key */
-  CINIT(SSLKEY, OBJECTPOINT, 87),
+  CURLOPT(CURLOPT_SSLKEY, CURLOPTTYPE_STRINGPOINT, 87),
 
   /* type of the file keeping your private SSL-key ("DER", "PEM", "ENG") */
-  CINIT(SSLKEYTYPE, OBJECTPOINT, 88),
+  CURLOPT(CURLOPT_SSLKEYTYPE, CURLOPTTYPE_STRINGPOINT, 88),
 
   /* crypto engine for the SSL-sub system */
-  CINIT(SSLENGINE, OBJECTPOINT, 89),
+  CURLOPT(CURLOPT_SSLENGINE, CURLOPTTYPE_STRINGPOINT, 89),
 
   /* set the crypto engine for the SSL-sub system as default
      the param has no meaning...
    */
-  CINIT(SSLENGINE_DEFAULT, LONG, 90),
+  CURLOPT(CURLOPT_SSLENGINE_DEFAULT, CURLOPTTYPE_LONG, 90),
 
   /* Non-zero value means to use the global dns cache */
-  CINIT(DNS_USE_GLOBAL_CACHE, LONG, 91), /* To become OBSOLETE soon */
+  /* DEPRECATED, do not use! */
+  CURLOPT(CURLOPT_DNS_USE_GLOBAL_CACHE, CURLOPTTYPE_LONG, 91),
 
   /* DNS cache timeout */
-  CINIT(DNS_CACHE_TIMEOUT, LONG, 92),
+  CURLOPT(CURLOPT_DNS_CACHE_TIMEOUT, CURLOPTTYPE_LONG, 92),
 
   /* send linked-list of pre-transfer QUOTE commands */
-  CINIT(PREQUOTE, OBJECTPOINT, 93),
+  CURLOPT(CURLOPT_PREQUOTE, CURLOPTTYPE_SLISTPOINT, 93),
 
   /* set the debug function */
-  CINIT(DEBUGFUNCTION, FUNCTIONPOINT, 94),
+  CURLOPT(CURLOPT_DEBUGFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 94),
 
   /* set the data for the debug function */
-  CINIT(DEBUGDATA, OBJECTPOINT, 95),
+  CURLOPT(CURLOPT_DEBUGDATA, CURLOPTTYPE_OBJECTPOINT, 95),
 
   /* mark this as start of a cookie session */
-  CINIT(COOKIESESSION, LONG, 96),
+  CURLOPT(CURLOPT_COOKIESESSION, CURLOPTTYPE_LONG, 96),
 
   /* The CApath directory used to validate the peer certificate
      this option is used only if SSL_VERIFYPEER is true */
-  CINIT(CAPATH, OBJECTPOINT, 97),
+  CURLOPT(CURLOPT_CAPATH, CURLOPTTYPE_STRINGPOINT, 97),
 
   /* Instruct libcurl to use a smaller receive buffer */
-  CINIT(BUFFERSIZE, LONG, 98),
+  CURLOPT(CURLOPT_BUFFERSIZE, CURLOPTTYPE_LONG, 98),
 
   /* Instruct libcurl to not use any signal/alarm handlers, even when using
      timeouts. This option is useful for multi-threaded applications.
      See libcurl-the-guide for more background information. */
-  CINIT(NOSIGNAL, LONG, 99),
+  CURLOPT(CURLOPT_NOSIGNAL, CURLOPTTYPE_LONG, 99),
 
   /* Provide a CURLShare for mutexing non-ts data */
-  CINIT(SHARE, OBJECTPOINT, 100),
+  CURLOPT(CURLOPT_SHARE, CURLOPTTYPE_OBJECTPOINT, 100),
 
   /* indicates type of proxy. accepted values are CURLPROXY_HTTP (default),
-     CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A and CURLPROXY_SOCKS5. */
-  CINIT(PROXYTYPE, LONG, 101),
+     CURLPROXY_HTTPS, CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A and
+     CURLPROXY_SOCKS5. */
+  CURLOPT(CURLOPT_PROXYTYPE, CURLOPTTYPE_LONG, 101),
 
   /* Set the Accept-Encoding string. Use this to tell a server you would like
      the response to be compressed. Before 7.21.6, this was known as
      CURLOPT_ENCODING */
-  CINIT(ACCEPT_ENCODING, OBJECTPOINT, 102),
+  CURLOPT(CURLOPT_ACCEPT_ENCODING, CURLOPTTYPE_STRINGPOINT, 102),
 
   /* Set pointer to private data */
-  CINIT(PRIVATE, OBJECTPOINT, 103),
+  CURLOPT(CURLOPT_PRIVATE, CURLOPTTYPE_OBJECTPOINT, 103),
 
   /* Set aliases for HTTP 200 in the HTTP Response header */
-  CINIT(HTTP200ALIASES, OBJECTPOINT, 104),
+  CURLOPT(CURLOPT_HTTP200ALIASES, CURLOPTTYPE_SLISTPOINT, 104),
 
   /* Continue to send authentication (user+password) when following locations,
      even when hostname changed. This can potentially send off the name
      and password to whatever host the server decides. */
-  CINIT(UNRESTRICTED_AUTH, LONG, 105),
+  CURLOPT(CURLOPT_UNRESTRICTED_AUTH, CURLOPTTYPE_LONG, 105),
 
-  /* Specifically switch on or off the FTP engine's use of the EPRT command ( it
-     also disables the LPRT attempt). By default, those ones will always be
+  /* Specifically switch on or off the FTP engine's use of the EPRT command (
+     it also disables the LPRT attempt). By default, those ones will always be
      attempted before the good old traditional PORT command. */
-  CINIT(FTP_USE_EPRT, LONG, 106),
+  CURLOPT(CURLOPT_FTP_USE_EPRT, CURLOPTTYPE_LONG, 106),
 
   /* Set this to a bitmask value to enable the particular authentications
      methods you like. Use this in combination with CURLOPT_USERPWD.
      Note that setting multiple bits may cause extra network round-trips. */
-  CINIT(HTTPAUTH, LONG, 107),
+  CURLOPT(CURLOPT_HTTPAUTH, CURLOPTTYPE_LONG, 107),
 
-  /* Set the ssl context callback function, currently only for OpenSSL ssl_ctx
-     in second argument. The function must be matching the
-     curl_ssl_ctx_callback proto. */
-  CINIT(SSL_CTX_FUNCTION, FUNCTIONPOINT, 108),
+  /* Set the ssl context callback function, currently only for OpenSSL or
+     WolfSSL ssl_ctx, or mbedTLS mbedtls_ssl_config in the second argument.
+     The function must match the curl_ssl_ctx_callback prototype. */
+  CURLOPT(CURLOPT_SSL_CTX_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 108),
 
   /* Set the userdata for the ssl context callback function's third
      argument */
-  CINIT(SSL_CTX_DATA, OBJECTPOINT, 109),
+  CURLOPT(CURLOPT_SSL_CTX_DATA, CURLOPTTYPE_OBJECTPOINT, 109),
 
   /* FTP Option that causes missing dirs to be created on the remote server.
      In 7.19.4 we introduced the convenience enums for this option using the
      CURLFTP_CREATE_DIR prefix.
   */
-  CINIT(FTP_CREATE_MISSING_DIRS, LONG, 110),
+  CURLOPT(CURLOPT_FTP_CREATE_MISSING_DIRS, CURLOPTTYPE_LONG, 110),
 
   /* Set this to a bitmask value to enable the particular authentications
      methods you like. Use this in combination with CURLOPT_PROXYUSERPWD.
      Note that setting multiple bits may cause extra network round-trips. */
-  CINIT(PROXYAUTH, LONG, 111),
+  CURLOPT(CURLOPT_PROXYAUTH, CURLOPTTYPE_LONG, 111),
 
   /* FTP option that changes the timeout, in seconds, associated with
      getting a response.  This is different from transfer timeout time and
      essentially places a demand on the FTP server to acknowledge commands
      in a timely manner. */
-  CINIT(FTP_RESPONSE_TIMEOUT, LONG, 112),
+  CURLOPT(CURLOPT_FTP_RESPONSE_TIMEOUT, CURLOPTTYPE_LONG, 112),
 #define CURLOPT_SERVER_RESPONSE_TIMEOUT CURLOPT_FTP_RESPONSE_TIMEOUT
 
   /* Set this option to one of the CURL_IPRESOLVE_* defines (see below) to
      tell libcurl to resolve names to those IP versions only. This only has
      affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */
-  CINIT(IPRESOLVE, LONG, 113),
+  CURLOPT(CURLOPT_IPRESOLVE, CURLOPTTYPE_LONG, 113),
 
   /* Set this option to limit the size of a file that will be downloaded from
      an HTTP or FTP server.
 
      Note there is also _LARGE version which adds large file support for
      platforms which have larger off_t sizes.  See MAXFILESIZE_LARGE below. */
-  CINIT(MAXFILESIZE, LONG, 114),
+  CURLOPT(CURLOPT_MAXFILESIZE, CURLOPTTYPE_LONG, 114),
 
   /* See the comment for INFILESIZE above, but in short, specifies
    * the size of the file being uploaded.  -1 means unknown.
    */
-  CINIT(INFILESIZE_LARGE, OFF_T, 115),
+  CURLOPT(CURLOPT_INFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 115),
 
-  /* Sets the continuation offset.  There is also a LONG version of this;
-   * look above for RESUME_FROM.
+  /* Sets the continuation offset.  There is also a CURLOPTTYPE_LONG version
+   * of this; look above for RESUME_FROM.
    */
-  CINIT(RESUME_FROM_LARGE, OFF_T, 116),
+  CURLOPT(CURLOPT_RESUME_FROM_LARGE, CURLOPTTYPE_OFF_T, 116),
 
   /* Sets the maximum size of data that will be downloaded from
    * an HTTP or FTP server.  See MAXFILESIZE above for the LONG version.
    */
-  CINIT(MAXFILESIZE_LARGE, OFF_T, 117),
+  CURLOPT(CURLOPT_MAXFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 117),
 
   /* Set this option to the file name of your .netrc file you want libcurl
      to parse (using the CURLOPT_NETRC option). If not set, libcurl will do
      a poor attempt to find the user's home directory and check for a .netrc
      file in there. */
-  CINIT(NETRC_FILE, OBJECTPOINT, 118),
+  CURLOPT(CURLOPT_NETRC_FILE, CURLOPTTYPE_STRINGPOINT, 118),
 
   /* Enable SSL/TLS for FTP, pick one of:
-     CURLFTPSSL_TRY     - try using SSL, proceed anyway otherwise
-     CURLFTPSSL_CONTROL - SSL for the control connection or fail
-     CURLFTPSSL_ALL     - SSL for all communication or fail
+     CURLUSESSL_TRY     - try using SSL, proceed anyway otherwise
+     CURLUSESSL_CONTROL - SSL for the control connection or fail
+     CURLUSESSL_ALL     - SSL for all communication or fail
   */
-  CINIT(USE_SSL, LONG, 119),
+  CURLOPT(CURLOPT_USE_SSL, CURLOPTTYPE_LONG, 119),
 
   /* The _LARGE version of the standard POSTFIELDSIZE option */
-  CINIT(POSTFIELDSIZE_LARGE, OFF_T, 120),
+  CURLOPT(CURLOPT_POSTFIELDSIZE_LARGE, CURLOPTTYPE_OFF_T, 120),
 
   /* Enable/disable the TCP Nagle algorithm */
-  CINIT(TCP_NODELAY, LONG, 121),
+  CURLOPT(CURLOPT_TCP_NODELAY, CURLOPTTYPE_LONG, 121),
 
   /* 122 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */
   /* 123 OBSOLETE. Gone in 7.16.0 */
@@ -1220,144 +1427,143 @@ typedef enum {
      CURLFTPAUTH_SSL     - try "AUTH SSL" first, then TLS
      CURLFTPAUTH_TLS     - try "AUTH TLS" first, then SSL
   */
-  CINIT(FTPSSLAUTH, LONG, 129),
+  CURLOPT(CURLOPT_FTPSSLAUTH, CURLOPTTYPE_LONG, 129),
 
-  CINIT(IOCTLFUNCTION, FUNCTIONPOINT, 130),
-  CINIT(IOCTLDATA, OBJECTPOINT, 131),
+  CURLOPT(CURLOPT_IOCTLFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 130),
+  CURLOPT(CURLOPT_IOCTLDATA, CURLOPTTYPE_OBJECTPOINT, 131),
 
   /* 132 OBSOLETE. Gone in 7.16.0 */
   /* 133 OBSOLETE. Gone in 7.16.0 */
 
   /* zero terminated string for pass on to the FTP server when asked for
      "account" info */
-  CINIT(FTP_ACCOUNT, OBJECTPOINT, 134),
+  CURLOPT(CURLOPT_FTP_ACCOUNT, CURLOPTTYPE_STRINGPOINT, 134),
 
-  /* feed cookies into cookie engine */
-  CINIT(COOKIELIST, OBJECTPOINT, 135),
+  /* feed cookie into cookie engine */
+  CURLOPT(CURLOPT_COOKIELIST, CURLOPTTYPE_STRINGPOINT, 135),
 
   /* ignore Content-Length */
-  CINIT(IGNORE_CONTENT_LENGTH, LONG, 136),
+  CURLOPT(CURLOPT_IGNORE_CONTENT_LENGTH, CURLOPTTYPE_LONG, 136),
 
   /* Set to non-zero to skip the IP address received in a 227 PASV FTP server
      response. Typically used for FTP-SSL purposes but is not restricted to
      that. libcurl will then instead use the same IP address it used for the
      control connection. */
-  CINIT(FTP_SKIP_PASV_IP, LONG, 137),
+  CURLOPT(CURLOPT_FTP_SKIP_PASV_IP, CURLOPTTYPE_LONG, 137),
 
   /* Select "file method" to use when doing FTP, see the curl_ftpmethod
      above. */
-  CINIT(FTP_FILEMETHOD, LONG, 138),
+  CURLOPT(CURLOPT_FTP_FILEMETHOD, CURLOPTTYPE_LONG, 138),
 
   /* Local port number to bind the socket to */
-  CINIT(LOCALPORT, LONG, 139),
+  CURLOPT(CURLOPT_LOCALPORT, CURLOPTTYPE_LONG, 139),
 
   /* Number of ports to try, including the first one set with LOCALPORT.
      Thus, setting it to 1 will make no additional attempts but the first.
   */
-  CINIT(LOCALPORTRANGE, LONG, 140),
+  CURLOPT(CURLOPT_LOCALPORTRANGE, CURLOPTTYPE_LONG, 140),
 
   /* no transfer, set up connection and let application use the socket by
      extracting it with CURLINFO_LASTSOCKET */
-  CINIT(CONNECT_ONLY, LONG, 141),
+  CURLOPT(CURLOPT_CONNECT_ONLY, CURLOPTTYPE_LONG, 141),
 
   /* Function that will be called to convert from the
      network encoding (instead of using the iconv calls in libcurl) */
-  CINIT(CONV_FROM_NETWORK_FUNCTION, FUNCTIONPOINT, 142),
+  CURLOPT(CURLOPT_CONV_FROM_NETWORK_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 142),
 
   /* Function that will be called to convert to the
      network encoding (instead of using the iconv calls in libcurl) */
-  CINIT(CONV_TO_NETWORK_FUNCTION, FUNCTIONPOINT, 143),
+  CURLOPT(CURLOPT_CONV_TO_NETWORK_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 143),
 
   /* Function that will be called to convert from UTF8
      (instead of using the iconv calls in libcurl)
      Note that this is used only for SSL certificate processing */
-  CINIT(CONV_FROM_UTF8_FUNCTION, FUNCTIONPOINT, 144),
+  CURLOPT(CURLOPT_CONV_FROM_UTF8_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 144),
 
   /* if the connection proceeds too quickly then need to slow it down */
   /* limit-rate: maximum number of bytes per second to send or receive */
-  CINIT(MAX_SEND_SPEED_LARGE, OFF_T, 145),
-  CINIT(MAX_RECV_SPEED_LARGE, OFF_T, 146),
+  CURLOPT(CURLOPT_MAX_SEND_SPEED_LARGE, CURLOPTTYPE_OFF_T, 145),
+  CURLOPT(CURLOPT_MAX_RECV_SPEED_LARGE, CURLOPTTYPE_OFF_T, 146),
 
   /* Pointer to command string to send if USER/PASS fails. */
-  CINIT(FTP_ALTERNATIVE_TO_USER, OBJECTPOINT, 147),
+  CURLOPT(CURLOPT_FTP_ALTERNATIVE_TO_USER, CURLOPTTYPE_STRINGPOINT, 147),
 
   /* callback function for setting socket options */
-  CINIT(SOCKOPTFUNCTION, FUNCTIONPOINT, 148),
-  CINIT(SOCKOPTDATA, OBJECTPOINT, 149),
+  CURLOPT(CURLOPT_SOCKOPTFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 148),
+  CURLOPT(CURLOPT_SOCKOPTDATA, CURLOPTTYPE_OBJECTPOINT, 149),
 
   /* set to 0 to disable session ID re-use for this transfer, default is
      enabled (== 1) */
-  CINIT(SSL_SESSIONID_CACHE, LONG, 150),
+  CURLOPT(CURLOPT_SSL_SESSIONID_CACHE, CURLOPTTYPE_LONG, 150),
 
   /* allowed SSH authentication methods */
-  CINIT(SSH_AUTH_TYPES, LONG, 151),
+  CURLOPT(CURLOPT_SSH_AUTH_TYPES, CURLOPTTYPE_LONG, 151),
 
   /* Used by scp/sftp to do public/private key authentication */
-  CINIT(SSH_PUBLIC_KEYFILE, OBJECTPOINT, 152),
-  CINIT(SSH_PRIVATE_KEYFILE, OBJECTPOINT, 153),
+  CURLOPT(CURLOPT_SSH_PUBLIC_KEYFILE, CURLOPTTYPE_STRINGPOINT, 152),
+  CURLOPT(CURLOPT_SSH_PRIVATE_KEYFILE, CURLOPTTYPE_STRINGPOINT, 153),
 
   /* Send CCC (Clear Command Channel) after authentication */
-  CINIT(FTP_SSL_CCC, LONG, 154),
+  CURLOPT(CURLOPT_FTP_SSL_CCC, CURLOPTTYPE_LONG, 154),
 
   /* Same as TIMEOUT and CONNECTTIMEOUT, but with ms resolution */
-  CINIT(TIMEOUT_MS, LONG, 155),
-  CINIT(CONNECTTIMEOUT_MS, LONG, 156),
+  CURLOPT(CURLOPT_TIMEOUT_MS, CURLOPTTYPE_LONG, 155),
+  CURLOPT(CURLOPT_CONNECTTIMEOUT_MS, CURLOPTTYPE_LONG, 156),
 
   /* set to zero to disable the libcurl's decoding and thus pass the raw body
      data to the application even when it is encoded/compressed */
-  CINIT(HTTP_TRANSFER_DECODING, LONG, 157),
-  CINIT(HTTP_CONTENT_DECODING, LONG, 158),
+  CURLOPT(CURLOPT_HTTP_TRANSFER_DECODING, CURLOPTTYPE_LONG, 157),
+  CURLOPT(CURLOPT_HTTP_CONTENT_DECODING, CURLOPTTYPE_LONG, 158),
 
   /* Permission used when creating new files and directories on the remote
      server for protocols that support it, SFTP/SCP/FILE */
-  CINIT(NEW_FILE_PERMS, LONG, 159),
-  CINIT(NEW_DIRECTORY_PERMS, LONG, 160),
+  CURLOPT(CURLOPT_NEW_FILE_PERMS, CURLOPTTYPE_LONG, 159),
+  CURLOPT(CURLOPT_NEW_DIRECTORY_PERMS, CURLOPTTYPE_LONG, 160),
 
   /* Set the behaviour of POST when redirecting. Values must be set to one
      of CURL_REDIR* defines below. This used to be called CURLOPT_POST301 */
-  CINIT(POSTREDIR, LONG, 161),
+  CURLOPT(CURLOPT_POSTREDIR, CURLOPTTYPE_LONG, 161),
 
   /* used by scp/sftp to verify the host's public key */
-  CINIT(SSH_HOST_PUBLIC_KEY_MD5, OBJECTPOINT, 162),
+  CURLOPT(CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, CURLOPTTYPE_STRINGPOINT, 162),
 
   /* Callback function for opening socket (instead of socket(2)). Optionally,
      callback is able change the address or refuse to connect returning
      CURL_SOCKET_BAD.  The callback should have type
      curl_opensocket_callback */
-  CINIT(OPENSOCKETFUNCTION, FUNCTIONPOINT, 163),
-  CINIT(OPENSOCKETDATA, OBJECTPOINT, 164),
+  CURLOPT(CURLOPT_OPENSOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 163),
+  CURLOPT(CURLOPT_OPENSOCKETDATA, CURLOPTTYPE_OBJECTPOINT, 164),
 
   /* POST volatile input fields. */
-  CINIT(COPYPOSTFIELDS, OBJECTPOINT, 165),
+  CURLOPT(CURLOPT_COPYPOSTFIELDS, CURLOPTTYPE_OBJECTPOINT, 165),
 
   /* set transfer mode (;type=<a|i>) when doing FTP via an HTTP proxy */
-  CINIT(PROXY_TRANSFER_MODE, LONG, 166),
+  CURLOPT(CURLOPT_PROXY_TRANSFER_MODE, CURLOPTTYPE_LONG, 166),
 
   /* Callback function for seeking in the input stream */
-  CINIT(SEEKFUNCTION, FUNCTIONPOINT, 167),
-  CINIT(SEEKDATA, OBJECTPOINT, 168),
+  CURLOPT(CURLOPT_SEEKFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 167),
+  CURLOPT(CURLOPT_SEEKDATA, CURLOPTTYPE_OBJECTPOINT, 168),
 
   /* CRL file */
-  CINIT(CRLFILE, OBJECTPOINT, 169),
+  CURLOPT(CURLOPT_CRLFILE, CURLOPTTYPE_STRINGPOINT, 169),
 
   /* Issuer certificate */
-  CINIT(ISSUERCERT, OBJECTPOINT, 170),
+  CURLOPT(CURLOPT_ISSUERCERT, CURLOPTTYPE_STRINGPOINT, 170),
 
   /* (IPv6) Address scope */
-  CINIT(ADDRESS_SCOPE, LONG, 171),
+  CURLOPT(CURLOPT_ADDRESS_SCOPE, CURLOPTTYPE_LONG, 171),
 
   /* Collect certificate chain info and allow it to get retrievable with
-     CURLINFO_CERTINFO after the transfer is complete. (Unfortunately) only
-     working with OpenSSL-powered builds. */
-  CINIT(CERTINFO, LONG, 172),
+     CURLINFO_CERTINFO after the transfer is complete. */
+  CURLOPT(CURLOPT_CERTINFO, CURLOPTTYPE_LONG, 172),
 
   /* "name" and "pwd" to use when fetching. */
-  CINIT(USERNAME, OBJECTPOINT, 173),
-  CINIT(PASSWORD, OBJECTPOINT, 174),
+  CURLOPT(CURLOPT_USERNAME, CURLOPTTYPE_STRINGPOINT, 173),
+  CURLOPT(CURLOPT_PASSWORD, CURLOPTTYPE_STRINGPOINT, 174),
 
     /* "name" and "pwd" to use with Proxy when fetching. */
-  CINIT(PROXYUSERNAME, OBJECTPOINT, 175),
-  CINIT(PROXYPASSWORD, OBJECTPOINT, 176),
+  CURLOPT(CURLOPT_PROXYUSERNAME, CURLOPTTYPE_STRINGPOINT, 175),
+  CURLOPT(CURLOPT_PROXYPASSWORD, CURLOPTTYPE_STRINGPOINT, 176),
 
   /* Comma separated list of hostnames defining no-proxy zones. These should
      match both hostnames directly, and hostnames within a domain. For
@@ -1366,103 +1572,103 @@ typedef enum {
      implementations of this, .local.com will be considered to be the same as
      local.com. A single * is the only valid wildcard, and effectively
      disables the use of proxy. */
-  CINIT(NOPROXY, OBJECTPOINT, 177),
+  CURLOPT(CURLOPT_NOPROXY, CURLOPTTYPE_STRINGPOINT, 177),
 
   /* block size for TFTP transfers */
-  CINIT(TFTP_BLKSIZE, LONG, 178),
+  CURLOPT(CURLOPT_TFTP_BLKSIZE, CURLOPTTYPE_LONG, 178),
 
   /* Socks Service */
-  CINIT(SOCKS5_GSSAPI_SERVICE, OBJECTPOINT, 179),
+  /* DEPRECATED, do not use! */
+  CURLOPT(CURLOPT_SOCKS5_GSSAPI_SERVICE, CURLOPTTYPE_STRINGPOINT, 179),
 
   /* Socks Service */
-  CINIT(SOCKS5_GSSAPI_NEC, LONG, 180),
+  CURLOPT(CURLOPT_SOCKS5_GSSAPI_NEC, CURLOPTTYPE_LONG, 180),
 
   /* set the bitmask for the protocols that are allowed to be used for the
      transfer, which thus helps the app which takes URLs from users or other
      external inputs and want to restrict what protocol(s) to deal
      with. Defaults to CURLPROTO_ALL. */
-  CINIT(PROTOCOLS, LONG, 181),
+  CURLOPT(CURLOPT_PROTOCOLS, CURLOPTTYPE_LONG, 181),
 
   /* set the bitmask for the protocols that libcurl is allowed to follow to,
      as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs
-     to be set in both bitmasks to be allowed to get redirected to. Defaults
-     to all protocols except FILE and SCP. */
-  CINIT(REDIR_PROTOCOLS, LONG, 182),
+     to be set in both bitmasks to be allowed to get redirected to. */
+  CURLOPT(CURLOPT_REDIR_PROTOCOLS, CURLOPTTYPE_LONG, 182),
 
   /* set the SSH knownhost file name to use */
-  CINIT(SSH_KNOWNHOSTS, OBJECTPOINT, 183),
+  CURLOPT(CURLOPT_SSH_KNOWNHOSTS, CURLOPTTYPE_STRINGPOINT, 183),
 
   /* set the SSH host key callback, must point to a curl_sshkeycallback
      function */
-  CINIT(SSH_KEYFUNCTION, FUNCTIONPOINT, 184),
+  CURLOPT(CURLOPT_SSH_KEYFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 184),
 
   /* set the SSH host key callback custom pointer */
-  CINIT(SSH_KEYDATA, OBJECTPOINT, 185),
+  CURLOPT(CURLOPT_SSH_KEYDATA, CURLOPTTYPE_OBJECTPOINT, 185),
 
   /* set the SMTP mail originator */
-  CINIT(MAIL_FROM, OBJECTPOINT, 186),
+  CURLOPT(CURLOPT_MAIL_FROM, CURLOPTTYPE_STRINGPOINT, 186),
 
-  /* set the SMTP mail receiver(s) */
-  CINIT(MAIL_RCPT, OBJECTPOINT, 187),
+  /* set the list of SMTP mail receiver(s) */
+  CURLOPT(CURLOPT_MAIL_RCPT, CURLOPTTYPE_SLISTPOINT, 187),
 
   /* FTP: send PRET before PASV */
-  CINIT(FTP_USE_PRET, LONG, 188),
+  CURLOPT(CURLOPT_FTP_USE_PRET, CURLOPTTYPE_LONG, 188),
 
   /* RTSP request method (OPTIONS, SETUP, PLAY, etc...) */
-  CINIT(RTSP_REQUEST, LONG, 189),
+  CURLOPT(CURLOPT_RTSP_REQUEST, CURLOPTTYPE_LONG, 189),
 
   /* The RTSP session identifier */
-  CINIT(RTSP_SESSION_ID, OBJECTPOINT, 190),
+  CURLOPT(CURLOPT_RTSP_SESSION_ID, CURLOPTTYPE_STRINGPOINT, 190),
 
   /* The RTSP stream URI */
-  CINIT(RTSP_STREAM_URI, OBJECTPOINT, 191),
+  CURLOPT(CURLOPT_RTSP_STREAM_URI, CURLOPTTYPE_STRINGPOINT, 191),
 
   /* The Transport: header to use in RTSP requests */
-  CINIT(RTSP_TRANSPORT, OBJECTPOINT, 192),
+  CURLOPT(CURLOPT_RTSP_TRANSPORT, CURLOPTTYPE_STRINGPOINT, 192),
 
   /* Manually initialize the client RTSP CSeq for this handle */
-  CINIT(RTSP_CLIENT_CSEQ, LONG, 193),
+  CURLOPT(CURLOPT_RTSP_CLIENT_CSEQ, CURLOPTTYPE_LONG, 193),
 
   /* Manually initialize the server RTSP CSeq for this handle */
-  CINIT(RTSP_SERVER_CSEQ, LONG, 194),
+  CURLOPT(CURLOPT_RTSP_SERVER_CSEQ, CURLOPTTYPE_LONG, 194),
 
   /* The stream to pass to INTERLEAVEFUNCTION. */
-  CINIT(INTERLEAVEDATA, OBJECTPOINT, 195),
+  CURLOPT(CURLOPT_INTERLEAVEDATA, CURLOPTTYPE_OBJECTPOINT, 195),
 
   /* Let the application define a custom write method for RTP data */
-  CINIT(INTERLEAVEFUNCTION, FUNCTIONPOINT, 196),
+  CURLOPT(CURLOPT_INTERLEAVEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 196),
 
   /* Turn on wildcard matching */
-  CINIT(WILDCARDMATCH, LONG, 197),
+  CURLOPT(CURLOPT_WILDCARDMATCH, CURLOPTTYPE_LONG, 197),
 
   /* Directory matching callback called before downloading of an
      individual file (chunk) started */
-  CINIT(CHUNK_BGN_FUNCTION, FUNCTIONPOINT, 198),
+  CURLOPT(CURLOPT_CHUNK_BGN_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 198),
 
   /* Directory matching callback called after the file (chunk)
      was downloaded, or skipped */
-  CINIT(CHUNK_END_FUNCTION, FUNCTIONPOINT, 199),
+  CURLOPT(CURLOPT_CHUNK_END_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 199),
 
   /* Change match (fnmatch-like) callback for wildcard matching */
-  CINIT(FNMATCH_FUNCTION, FUNCTIONPOINT, 200),
+  CURLOPT(CURLOPT_FNMATCH_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 200),
 
   /* Let the application define custom chunk data pointer */
-  CINIT(CHUNK_DATA, OBJECTPOINT, 201),
+  CURLOPT(CURLOPT_CHUNK_DATA, CURLOPTTYPE_OBJECTPOINT, 201),
 
   /* FNMATCH_FUNCTION user pointer */
-  CINIT(FNMATCH_DATA, OBJECTPOINT, 202),
+  CURLOPT(CURLOPT_FNMATCH_DATA, CURLOPTTYPE_OBJECTPOINT, 202),
 
   /* send linked-list of name:port:address sets */
-  CINIT(RESOLVE, OBJECTPOINT, 203),
+  CURLOPT(CURLOPT_RESOLVE, CURLOPTTYPE_SLISTPOINT, 203),
 
   /* Set a username for authenticated TLS */
-  CINIT(TLSAUTH_USERNAME, OBJECTPOINT, 204),
+  CURLOPT(CURLOPT_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 204),
 
   /* Set a password for authenticated TLS */
-  CINIT(TLSAUTH_PASSWORD, OBJECTPOINT, 205),
+  CURLOPT(CURLOPT_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 205),
 
   /* Set authentication type for authenticated TLS */
-  CINIT(TLSAUTH_TYPE, OBJECTPOINT, 206),
+  CURLOPT(CURLOPT_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 206),
 
   /* Set to 1 to enable the "TE:" header in HTTP requests to ask for
      compressed transfer-encoded responses. Set to 0 to disable the use of TE:
@@ -1474,7 +1680,274 @@ typedef enum {
      option is set to 1.
 
   */
-  CINIT(TRANSFER_ENCODING, LONG, 207),
+  CURLOPT(CURLOPT_TRANSFER_ENCODING, CURLOPTTYPE_LONG, 207),
+
+  /* Callback function for closing socket (instead of close(2)). The callback
+     should have type curl_closesocket_callback */
+  CURLOPT(CURLOPT_CLOSESOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 208),
+  CURLOPT(CURLOPT_CLOSESOCKETDATA, CURLOPTTYPE_OBJECTPOINT, 209),
+
+  /* allow GSSAPI credential delegation */
+  CURLOPT(CURLOPT_GSSAPI_DELEGATION, CURLOPTTYPE_LONG, 210),
+
+  /* Set the name servers to use for DNS resolution */
+  CURLOPT(CURLOPT_DNS_SERVERS, CURLOPTTYPE_STRINGPOINT, 211),
+
+  /* Time-out accept operations (currently for FTP only) after this amount
+     of milliseconds. */
+  CURLOPT(CURLOPT_ACCEPTTIMEOUT_MS, CURLOPTTYPE_LONG, 212),
+
+  /* Set TCP keepalive */
+  CURLOPT(CURLOPT_TCP_KEEPALIVE, CURLOPTTYPE_LONG, 213),
+
+  /* non-universal keepalive knobs (Linux, AIX, HP-UX, more) */
+  CURLOPT(CURLOPT_TCP_KEEPIDLE, CURLOPTTYPE_LONG, 214),
+  CURLOPT(CURLOPT_TCP_KEEPINTVL, CURLOPTTYPE_LONG, 215),
+
+  /* Enable/disable specific SSL features with a bitmask, see CURLSSLOPT_* */
+  CURLOPT(CURLOPT_SSL_OPTIONS, CURLOPTTYPE_LONG, 216),
+
+  /* Set the SMTP auth originator */
+  CURLOPT(CURLOPT_MAIL_AUTH, CURLOPTTYPE_STRINGPOINT, 217),
+
+  /* Enable/disable SASL initial response */
+  CURLOPT(CURLOPT_SASL_IR, CURLOPTTYPE_LONG, 218),
+
+  /* Function that will be called instead of the internal progress display
+   * function. This function should be defined as the curl_xferinfo_callback
+   * prototype defines. (Deprecates CURLOPT_PROGRESSFUNCTION) */
+  CURLOPT(CURLOPT_XFERINFOFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 219),
+
+  /* The XOAUTH2 bearer token */
+  CURLOPT(CURLOPT_XOAUTH2_BEARER, CURLOPTTYPE_STRINGPOINT, 220),
+
+  /* Set the interface string to use as outgoing network
+   * interface for DNS requests.
+   * Only supported by the c-ares DNS backend */
+  CURLOPT(CURLOPT_DNS_INTERFACE, CURLOPTTYPE_STRINGPOINT, 221),
+
+  /* Set the local IPv4 address to use for outgoing DNS requests.
+   * Only supported by the c-ares DNS backend */
+  CURLOPT(CURLOPT_DNS_LOCAL_IP4, CURLOPTTYPE_STRINGPOINT, 222),
+
+  /* Set the local IPv6 address to use for outgoing DNS requests.
+   * Only supported by the c-ares DNS backend */
+  CURLOPT(CURLOPT_DNS_LOCAL_IP6, CURLOPTTYPE_STRINGPOINT, 223),
+
+  /* Set authentication options directly */
+  CURLOPT(CURLOPT_LOGIN_OPTIONS, CURLOPTTYPE_STRINGPOINT, 224),
+
+  /* Enable/disable TLS NPN extension (http2 over ssl might fail without) */
+  CURLOPT(CURLOPT_SSL_ENABLE_NPN, CURLOPTTYPE_LONG, 225),
+
+  /* Enable/disable TLS ALPN extension (http2 over ssl might fail without) */
+  CURLOPT(CURLOPT_SSL_ENABLE_ALPN, CURLOPTTYPE_LONG, 226),
+
+  /* Time to wait for a response to a HTTP request containing an
+   * Expect: 100-continue header before sending the data anyway. */
+  CURLOPT(CURLOPT_EXPECT_100_TIMEOUT_MS, CURLOPTTYPE_LONG, 227),
+
+  /* This points to a linked list of headers used for proxy requests only,
+     struct curl_slist kind */
+  CURLOPT(CURLOPT_PROXYHEADER, CURLOPTTYPE_SLISTPOINT, 228),
+
+  /* Pass in a bitmask of "header options" */
+  CURLOPT(CURLOPT_HEADEROPT, CURLOPTTYPE_LONG, 229),
+
+  /* The public key in DER form used to validate the peer public key
+     this option is used only if SSL_VERIFYPEER is true */
+  CURLOPT(CURLOPT_PINNEDPUBLICKEY, CURLOPTTYPE_STRINGPOINT, 230),
+
+  /* Path to Unix domain socket */
+  CURLOPT(CURLOPT_UNIX_SOCKET_PATH, CURLOPTTYPE_STRINGPOINT, 231),
+
+  /* Set if we should verify the certificate status. */
+  CURLOPT(CURLOPT_SSL_VERIFYSTATUS, CURLOPTTYPE_LONG, 232),
+
+  /* Set if we should enable TLS false start. */
+  CURLOPT(CURLOPT_SSL_FALSESTART, CURLOPTTYPE_LONG, 233),
+
+  /* Do not squash dot-dot sequences */
+  CURLOPT(CURLOPT_PATH_AS_IS, CURLOPTTYPE_LONG, 234),
+
+  /* Proxy Service Name */
+  CURLOPT(CURLOPT_PROXY_SERVICE_NAME, CURLOPTTYPE_STRINGPOINT, 235),
+
+  /* Service Name */
+  CURLOPT(CURLOPT_SERVICE_NAME, CURLOPTTYPE_STRINGPOINT, 236),
+
+  /* Wait/don't wait for pipe/mutex to clarify */
+  CURLOPT(CURLOPT_PIPEWAIT, CURLOPTTYPE_LONG, 237),
+
+  /* Set the protocol used when curl is given a URL without a protocol */
+  CURLOPT(CURLOPT_DEFAULT_PROTOCOL, CURLOPTTYPE_STRINGPOINT, 238),
+
+  /* Set stream weight, 1 - 256 (default is 16) */
+  CURLOPT(CURLOPT_STREAM_WEIGHT, CURLOPTTYPE_LONG, 239),
+
+  /* Set stream dependency on another CURL handle */
+  CURLOPT(CURLOPT_STREAM_DEPENDS, CURLOPTTYPE_OBJECTPOINT, 240),
+
+  /* Set E-xclusive stream dependency on another CURL handle */
+  CURLOPT(CURLOPT_STREAM_DEPENDS_E, CURLOPTTYPE_OBJECTPOINT, 241),
+
+  /* Do not send any tftp option requests to the server */
+  CURLOPT(CURLOPT_TFTP_NO_OPTIONS, CURLOPTTYPE_LONG, 242),
+
+  /* Linked-list of host:port:connect-to-host:connect-to-port,
+     overrides the URL's host:port (only for the network layer) */
+  CURLOPT(CURLOPT_CONNECT_TO, CURLOPTTYPE_SLISTPOINT, 243),
+
+  /* Set TCP Fast Open */
+  CURLOPT(CURLOPT_TCP_FASTOPEN, CURLOPTTYPE_LONG, 244),
+
+  /* Continue to send data if the server responds early with an
+   * HTTP status code >= 300 */
+  CURLOPT(CURLOPT_KEEP_SENDING_ON_ERROR, CURLOPTTYPE_LONG, 245),
+
+  /* The CApath or CAfile used to validate the proxy certificate
+     this option is used only if PROXY_SSL_VERIFYPEER is true */
+  CURLOPT(CURLOPT_PROXY_CAINFO, CURLOPTTYPE_STRINGPOINT, 246),
+
+  /* The CApath directory used to validate the proxy certificate
+     this option is used only if PROXY_SSL_VERIFYPEER is true */
+  CURLOPT(CURLOPT_PROXY_CAPATH, CURLOPTTYPE_STRINGPOINT, 247),
+
+  /* Set if we should verify the proxy in ssl handshake,
+     set 1 to verify. */
+  CURLOPT(CURLOPT_PROXY_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 248),
+
+  /* Set if we should verify the Common name from the proxy certificate in ssl
+   * handshake, set 1 to check existence, 2 to ensure that it matches
+   * the provided hostname. */
+  CURLOPT(CURLOPT_PROXY_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 249),
+
+  /* What version to specifically try to use for proxy.
+     See CURL_SSLVERSION defines below. */
+  CURLOPT(CURLOPT_PROXY_SSLVERSION, CURLOPTTYPE_LONG, 250),
+
+  /* Set a username for authenticated TLS for proxy */
+  CURLOPT(CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 251),
+
+  /* Set a password for authenticated TLS for proxy */
+  CURLOPT(CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 252),
+
+  /* Set authentication type for authenticated TLS for proxy */
+  CURLOPT(CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 253),
+
+  /* name of the file keeping your private SSL-certificate for proxy */
+  CURLOPT(CURLOPT_PROXY_SSLCERT, CURLOPTTYPE_STRINGPOINT, 254),
+
+  /* type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") for
+     proxy */
+  CURLOPT(CURLOPT_PROXY_SSLCERTTYPE, CURLOPTTYPE_STRINGPOINT, 255),
+
+  /* name of the file keeping your private SSL-key for proxy */
+  CURLOPT(CURLOPT_PROXY_SSLKEY, CURLOPTTYPE_STRINGPOINT, 256),
+
+  /* type of the file keeping your private SSL-key ("DER", "PEM", "ENG") for
+     proxy */
+  CURLOPT(CURLOPT_PROXY_SSLKEYTYPE, CURLOPTTYPE_STRINGPOINT, 257),
+
+  /* password for the SSL private key for proxy */
+  CURLOPT(CURLOPT_PROXY_KEYPASSWD, CURLOPTTYPE_STRINGPOINT, 258),
+
+  /* Specify which SSL ciphers to use for proxy */
+  CURLOPT(CURLOPT_PROXY_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 259),
+
+  /* CRL file for proxy */
+  CURLOPT(CURLOPT_PROXY_CRLFILE, CURLOPTTYPE_STRINGPOINT, 260),
+
+  /* Enable/disable specific SSL features with a bitmask for proxy, see
+     CURLSSLOPT_* */
+  CURLOPT(CURLOPT_PROXY_SSL_OPTIONS, CURLOPTTYPE_LONG, 261),
+
+  /* Name of pre proxy to use. */
+  CURLOPT(CURLOPT_PRE_PROXY, CURLOPTTYPE_STRINGPOINT, 262),
+
+  /* The public key in DER form used to validate the proxy public key
+     this option is used only if PROXY_SSL_VERIFYPEER is true */
+  CURLOPT(CURLOPT_PROXY_PINNEDPUBLICKEY, CURLOPTTYPE_STRINGPOINT, 263),
+
+  /* Path to an abstract Unix domain socket */
+  CURLOPT(CURLOPT_ABSTRACT_UNIX_SOCKET, CURLOPTTYPE_STRINGPOINT, 264),
+
+  /* Suppress proxy CONNECT response headers from user callbacks */
+  CURLOPT(CURLOPT_SUPPRESS_CONNECT_HEADERS, CURLOPTTYPE_LONG, 265),
+
+  /* The request target, instead of extracted from the URL */
+  CURLOPT(CURLOPT_REQUEST_TARGET, CURLOPTTYPE_STRINGPOINT, 266),
+
+  /* bitmask of allowed auth methods for connections to SOCKS5 proxies */
+  CURLOPT(CURLOPT_SOCKS5_AUTH, CURLOPTTYPE_LONG, 267),
+
+  /* Enable/disable SSH compression */
+  CURLOPT(CURLOPT_SSH_COMPRESSION, CURLOPTTYPE_LONG, 268),
+
+  /* Post MIME data. */
+  CURLOPT(CURLOPT_MIMEPOST, CURLOPTTYPE_OBJECTPOINT, 269),
+
+  /* Time to use with the CURLOPT_TIMECONDITION. Specified in number of
+     seconds since 1 Jan 1970. */
+  CURLOPT(CURLOPT_TIMEVALUE_LARGE, CURLOPTTYPE_OFF_T, 270),
+
+  /* Head start in milliseconds to give happy eyeballs. */
+  CURLOPT(CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, CURLOPTTYPE_LONG, 271),
+
+  /* Function that will be called before a resolver request is made */
+  CURLOPT(CURLOPT_RESOLVER_START_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 272),
+
+  /* User data to pass to the resolver start callback. */
+  CURLOPT(CURLOPT_RESOLVER_START_DATA, CURLOPTTYPE_OBJECTPOINT, 273),
+
+  /* send HAProxy PROXY protocol header? */
+  CURLOPT(CURLOPT_HAPROXYPROTOCOL, CURLOPTTYPE_LONG, 274),
+
+  /* shuffle addresses before use when DNS returns multiple */
+  CURLOPT(CURLOPT_DNS_SHUFFLE_ADDRESSES, CURLOPTTYPE_LONG, 275),
+
+  /* Specify which TLS 1.3 ciphers suites to use */
+  CURLOPT(CURLOPT_TLS13_CIPHERS, CURLOPTTYPE_STRINGPOINT, 276),
+  CURLOPT(CURLOPT_PROXY_TLS13_CIPHERS, CURLOPTTYPE_STRINGPOINT, 277),
+
+  /* Disallow specifying username/login in URL. */
+  CURLOPT(CURLOPT_DISALLOW_USERNAME_IN_URL, CURLOPTTYPE_LONG, 278),
+
+  /* DNS-over-HTTPS URL */
+  CURLOPT(CURLOPT_DOH_URL, CURLOPTTYPE_STRINGPOINT, 279),
+
+  /* Preferred buffer size to use for uploads */
+  CURLOPT(CURLOPT_UPLOAD_BUFFERSIZE, CURLOPTTYPE_LONG, 280),
+
+  /* Time in ms between connection upkeep calls for long-lived connections. */
+  CURLOPT(CURLOPT_UPKEEP_INTERVAL_MS, CURLOPTTYPE_LONG, 281),
+
+  /* Specify URL using CURL URL API. */
+  CURLOPT(CURLOPT_CURLU, CURLOPTTYPE_OBJECTPOINT, 282),
+
+  /* add trailing data just after no more data is available */
+  CURLOPT(CURLOPT_TRAILERFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 283),
+
+  /* pointer to be passed to HTTP_TRAILER_FUNCTION */
+  CURLOPT(CURLOPT_TRAILERDATA, CURLOPTTYPE_OBJECTPOINT, 284),
+
+  /* set this to 1L to allow HTTP/0.9 responses or 0L to disallow */
+  CURLOPT(CURLOPT_HTTP09_ALLOWED, CURLOPTTYPE_LONG, 285),
+
+  /* alt-svc control bitmask */
+  CURLOPT(CURLOPT_ALTSVC_CTRL, CURLOPTTYPE_LONG, 286),
+
+  /* alt-svc cache file name to possibly read from/write to */
+  CURLOPT(CURLOPT_ALTSVC, CURLOPTTYPE_STRINGPOINT, 287),
+
+  /* maximum age of a connection to consider it for reuse (in seconds) */
+  CURLOPT(CURLOPT_MAXAGE_CONN, CURLOPTTYPE_LONG, 288),
+
+  /* SASL authorisation identity */
+  CURLOPT(CURLOPT_SASL_AUTHZID, CURLOPTTYPE_STRINGPOINT, 289),
+
+  /* allow RCPT TO command to fail for some recipients */
+  CURLOPT(CURLOPT_MAIL_RCPT_ALLLOWFAILS, CURLOPTTYPE_LONG, 290),
 
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
@@ -1512,13 +1985,10 @@ typedef enum {
      option might be handy to force libcurl to use a specific IP version. */
 #define CURL_IPRESOLVE_WHATEVER 0 /* default, resolves addresses to all IP
                                      versions that your system allows */
-#define CURL_IPRESOLVE_V4       1 /* resolve to ipv4 addresses */
-#define CURL_IPRESOLVE_V6       2 /* resolve to ipv6 addresses */
+#define CURL_IPRESOLVE_V4       1 /* resolve to IPv4 addresses */
+#define CURL_IPRESOLVE_V6       2 /* resolve to IPv6 addresses */
 
   /* three convenient "aliases" that follow the name scheme better */
-#define CURLOPT_WRITEDATA CURLOPT_FILE
-#define CURLOPT_READDATA  CURLOPT_INFILE
-#define CURLOPT_HEADERDATA CURLOPT_WRITEHEADER
 #define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER
 
   /* These enums are for use with the CURLOPT_HTTP_VERSION option. */
@@ -1528,10 +1998,20 @@ enum {
                              for us! */
   CURL_HTTP_VERSION_1_0,  /* please use HTTP 1.0 in the request */
   CURL_HTTP_VERSION_1_1,  /* please use HTTP 1.1 in the request */
-
+  CURL_HTTP_VERSION_2_0,  /* please use HTTP 2 in the request */
+  CURL_HTTP_VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */
+  CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE,  /* please use HTTP 2 without HTTP/1.1
+                                           Upgrade */
+  CURL_HTTP_VERSION_3 = 30, /* Makes use of explicit HTTP/3 without fallback.
+                               Use CURLOPT_ALTSVC to enable HTTP/3 upgrade */
   CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */
 };
 
+/* Convenience definition simple because the name of the version is HTTP/2 and
+   not 2.0. The 2_0 version of the enum name was set while the version was
+   still planned to be 2.0 and we stick to it for compatibility. */
+#define CURL_HTTP_VERSION_2 CURL_HTTP_VERSION_2_0
+
 /*
  * Public API enums for RTSP requests
  */
@@ -1565,13 +2045,29 @@ enum CURL_NETRC_OPTION {
 
 enum {
   CURL_SSLVERSION_DEFAULT,
-  CURL_SSLVERSION_TLSv1,
+  CURL_SSLVERSION_TLSv1, /* TLS 1.x */
   CURL_SSLVERSION_SSLv2,
   CURL_SSLVERSION_SSLv3,
+  CURL_SSLVERSION_TLSv1_0,
+  CURL_SSLVERSION_TLSv1_1,
+  CURL_SSLVERSION_TLSv1_2,
+  CURL_SSLVERSION_TLSv1_3,
 
   CURL_SSLVERSION_LAST /* never use, keep last */
 };
 
+enum {
+  CURL_SSLVERSION_MAX_NONE =     0,
+  CURL_SSLVERSION_MAX_DEFAULT =  (CURL_SSLVERSION_TLSv1   << 16),
+  CURL_SSLVERSION_MAX_TLSv1_0 =  (CURL_SSLVERSION_TLSv1_0 << 16),
+  CURL_SSLVERSION_MAX_TLSv1_1 =  (CURL_SSLVERSION_TLSv1_1 << 16),
+  CURL_SSLVERSION_MAX_TLSv1_2 =  (CURL_SSLVERSION_TLSv1_2 << 16),
+  CURL_SSLVERSION_MAX_TLSv1_3 =  (CURL_SSLVERSION_TLSv1_3 << 16),
+
+  /* never use, keep last */
+  CURL_SSLVERSION_MAX_LAST =     (CURL_SSLVERSION_LAST    << 16)
+};
+
 enum CURL_TLSAUTH {
   CURL_TLSAUTH_NONE,
   CURL_TLSAUTH_SRP,
@@ -1579,13 +2075,16 @@ enum CURL_TLSAUTH {
 };
 
 /* symbols to use with CURLOPT_POSTREDIR.
-   CURL_REDIR_POST_301 and CURL_REDIR_POST_302 can be bitwise ORed so that
-   CURL_REDIR_POST_301 | CURL_REDIR_POST_302 == CURL_REDIR_POST_ALL */
+   CURL_REDIR_POST_301, CURL_REDIR_POST_302 and CURL_REDIR_POST_303
+   can be bitwise ORed so that CURL_REDIR_POST_301 | CURL_REDIR_POST_302
+   | CURL_REDIR_POST_303 == CURL_REDIR_POST_ALL */
 
 #define CURL_REDIR_GET_ALL  0
 #define CURL_REDIR_POST_301 1
 #define CURL_REDIR_POST_302 2
-#define CURL_REDIR_POST_ALL (CURL_REDIR_POST_301|CURL_REDIR_POST_302)
+#define CURL_REDIR_POST_303 4
+#define CURL_REDIR_POST_ALL \
+    (CURL_REDIR_POST_301|CURL_REDIR_POST_302|CURL_REDIR_POST_303)
 
 typedef enum {
   CURL_TIMECOND_NONE,
@@ -1597,56 +2096,168 @@ typedef enum {
   CURL_TIMECOND_LAST
 } curl_TimeCond;
 
+/* Special size_t value signaling a zero-terminated string. */
+#define CURL_ZERO_TERMINATED ((size_t) -1)
 
 /* curl_strequal() and curl_strnequal() are subject for removal in a future
-   libcurl, see lib/README.curlx for details */
-CURL_EXTERN int (curl_strequal)(const char *s1, const char *s2);
-CURL_EXTERN int (curl_strnequal)(const char *s1, const char *s2, size_t n);
+   release */
+CURL_EXTERN int curl_strequal(const char *s1, const char *s2);
+CURL_EXTERN int curl_strnequal(const char *s1, const char *s2, size_t n);
 
-/* name is uppercase CURLFORM_<name> */
-#ifdef CFINIT
-#undef CFINIT
-#endif
+/* Mime/form handling support. */
+typedef struct curl_mime_s      curl_mime;      /* Mime context. */
+typedef struct curl_mimepart_s  curl_mimepart;  /* Mime part context. */
 
-#ifdef CURL_ISOCPP
-#define CFINIT(name) CURLFORM_ ## name
-#else
-/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */
-#define CFINIT(name) CURLFORM_/**/name
-#endif
+/*
+ * NAME curl_mime_init()
+ *
+ * DESCRIPTION
+ *
+ * Create a mime context and return its handle. The easy parameter is the
+ * target handle.
+ */
+CURL_EXTERN curl_mime *curl_mime_init(CURL *easy);
+
+/*
+ * NAME curl_mime_free()
+ *
+ * DESCRIPTION
+ *
+ * release a mime handle and its substructures.
+ */
+CURL_EXTERN void curl_mime_free(curl_mime *mime);
+
+/*
+ * NAME curl_mime_addpart()
+ *
+ * DESCRIPTION
+ *
+ * Append a new empty part to the given mime context and return a handle to
+ * the created part.
+ */
+CURL_EXTERN curl_mimepart *curl_mime_addpart(curl_mime *mime);
+
+/*
+ * NAME curl_mime_name()
+ *
+ * DESCRIPTION
+ *
+ * Set mime/form part name.
+ */
+CURL_EXTERN CURLcode curl_mime_name(curl_mimepart *part, const char *name);
+
+/*
+ * NAME curl_mime_filename()
+ *
+ * DESCRIPTION
+ *
+ * Set mime part remote file name.
+ */
+CURL_EXTERN CURLcode curl_mime_filename(curl_mimepart *part,
+                                        const char *filename);
+
+/*
+ * NAME curl_mime_type()
+ *
+ * DESCRIPTION
+ *
+ * Set mime part type.
+ */
+CURL_EXTERN CURLcode curl_mime_type(curl_mimepart *part, const char *mimetype);
+
+/*
+ * NAME curl_mime_encoder()
+ *
+ * DESCRIPTION
+ *
+ * Set mime data transfer encoder.
+ */
+CURL_EXTERN CURLcode curl_mime_encoder(curl_mimepart *part,
+                                       const char *encoding);
+
+/*
+ * NAME curl_mime_data()
+ *
+ * DESCRIPTION
+ *
+ * Set mime part data source from memory data,
+ */
+CURL_EXTERN CURLcode curl_mime_data(curl_mimepart *part,
+                                    const char *data, size_t datasize);
+
+/*
+ * NAME curl_mime_filedata()
+ *
+ * DESCRIPTION
+ *
+ * Set mime part data source from named file.
+ */
+CURL_EXTERN CURLcode curl_mime_filedata(curl_mimepart *part,
+                                        const char *filename);
+
+/*
+ * NAME curl_mime_data_cb()
+ *
+ * DESCRIPTION
+ *
+ * Set mime part data source from callback function.
+ */
+CURL_EXTERN CURLcode curl_mime_data_cb(curl_mimepart *part,
+                                       curl_off_t datasize,
+                                       curl_read_callback readfunc,
+                                       curl_seek_callback seekfunc,
+                                       curl_free_callback freefunc,
+                                       void *arg);
+
+/*
+ * NAME curl_mime_subparts()
+ *
+ * DESCRIPTION
+ *
+ * Set mime part data source from subparts.
+ */
+CURL_EXTERN CURLcode curl_mime_subparts(curl_mimepart *part,
+                                        curl_mime *subparts);
+/*
+ * NAME curl_mime_headers()
+ *
+ * DESCRIPTION
+ *
+ * Set mime part headers.
+ */
+CURL_EXTERN CURLcode curl_mime_headers(curl_mimepart *part,
+                                       struct curl_slist *headers,
+                                       int take_ownership);
 
 typedef enum {
-  CFINIT(NOTHING),        /********* the first one is unused ************/
-
-  /*  */
-  CFINIT(COPYNAME),
-  CFINIT(PTRNAME),
-  CFINIT(NAMELENGTH),
-  CFINIT(COPYCONTENTS),
-  CFINIT(PTRCONTENTS),
-  CFINIT(CONTENTSLENGTH),
-  CFINIT(FILECONTENT),
-  CFINIT(ARRAY),
-  CFINIT(OBSOLETE),
-  CFINIT(FILE),
-
-  CFINIT(BUFFER),
-  CFINIT(BUFFERPTR),
-  CFINIT(BUFFERLENGTH),
-
-  CFINIT(CONTENTTYPE),
-  CFINIT(CONTENTHEADER),
-  CFINIT(FILENAME),
-  CFINIT(END),
-  CFINIT(OBSOLETE2),
-
-  CFINIT(STREAM),
+  CURLFORM_NOTHING,        /********* the first one is unused ************/
+  CURLFORM_COPYNAME,
+  CURLFORM_PTRNAME,
+  CURLFORM_NAMELENGTH,
+  CURLFORM_COPYCONTENTS,
+  CURLFORM_PTRCONTENTS,
+  CURLFORM_CONTENTSLENGTH,
+  CURLFORM_FILECONTENT,
+  CURLFORM_ARRAY,
+  CURLFORM_OBSOLETE,
+  CURLFORM_FILE,
+
+  CURLFORM_BUFFER,
+  CURLFORM_BUFFERPTR,
+  CURLFORM_BUFFERLENGTH,
+
+  CURLFORM_CONTENTTYPE,
+  CURLFORM_CONTENTHEADER,
+  CURLFORM_FILENAME,
+  CURLFORM_END,
+  CURLFORM_OBSOLETE2,
+
+  CURLFORM_STREAM,
+  CURLFORM_CONTENTLEN, /* added in 7.46.0, provide a curl_off_t length */
 
   CURLFORM_LASTENTRY /* the last unused */
 } CURLformoption;
 
-#undef CFINIT /* done */
-
 /* structure to be used as parameter for CURLFORM_ARRAY */
 struct curl_forms {
   CURLformoption option;
@@ -1704,7 +2315,8 @@ CURL_EXTERN CURLFORMcode curl_formadd(struct curl_httppost **httppost,
  * Should return the buffer length passed to it as the argument "len" on
  *   success.
  */
-typedef size_t (*curl_formget_callback)(void *arg, const char *buf, size_t len);
+typedef size_t (*curl_formget_callback)(void *arg, const char *buf,
+                                        size_t len);
 
 /*
  * NAME curl_formget()
@@ -1842,6 +2454,47 @@ struct curl_slist {
   struct curl_slist *next;
 };
 
+/*
+ * NAME curl_global_sslset()
+ *
+ * DESCRIPTION
+ *
+ * When built with multiple SSL backends, curl_global_sslset() allows to
+ * choose one. This function can only be called once, and it must be called
+ * *before* curl_global_init().
+ *
+ * The backend can be identified by the id (e.g. CURLSSLBACKEND_OPENSSL). The
+ * backend can also be specified via the name parameter (passing -1 as id).
+ * If both id and name are specified, the name will be ignored. If neither id
+ * nor name are specified, the function will fail with
+ * CURLSSLSET_UNKNOWN_BACKEND and set the "avail" pointer to the
+ * NULL-terminated list of available backends.
+ *
+ * Upon success, the function returns CURLSSLSET_OK.
+ *
+ * If the specified SSL backend is not available, the function returns
+ * CURLSSLSET_UNKNOWN_BACKEND and sets the "avail" pointer to a NULL-terminated
+ * list of available SSL backends.
+ *
+ * The SSL backend can be set only once. If it has already been set, a
+ * subsequent attempt to change it will result in a CURLSSLSET_TOO_LATE.
+ */
+
+typedef struct {
+  curl_sslbackend id;
+  const char *name;
+} curl_ssl_backend;
+
+typedef enum {
+  CURLSSLSET_OK = 0,
+  CURLSSLSET_UNKNOWN_BACKEND,
+  CURLSSLSET_TOO_LATE,
+  CURLSSLSET_NO_BACKENDS /* libcurl was built without any SSL support */
+} CURLsslset;
+
+CURL_EXTERN CURLsslset curl_global_sslset(curl_sslbackend id, const char *name,
+                                          const curl_ssl_backend ***avail);
+
 /*
  * NAME curl_slist_append()
  *
@@ -1873,8 +2526,8 @@ CURL_EXTERN void curl_slist_free_all(struct curl_slist *);
  */
 CURL_EXTERN time_t curl_getdate(const char *p, const time_t *unused);
 
-/* info about the certificate chain, only for OpenSSL builds. Asked
-   for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */
+/* info about the certificate chain, only for OpenSSL, GnuTLS, Schannel, NSS
+   and GSKit builds. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */
 struct curl_certinfo {
   int num_of_certs;             /* number of certificates with information */
   struct curl_slist **certinfo; /* for each index in this array, there's a
@@ -1882,10 +2535,21 @@ struct curl_certinfo {
                                    format "name: value" */
 };
 
+/* Information about the SSL library used and the respective internal SSL
+   handle, which can be used to obtain further information regarding the
+   connection. Asked for with CURLINFO_TLS_SSL_PTR or CURLINFO_TLS_SESSION. */
+struct curl_tlssessioninfo {
+  curl_sslbackend backend;
+  void *internals;
+};
+
 #define CURLINFO_STRING   0x100000
 #define CURLINFO_LONG     0x200000
 #define CURLINFO_DOUBLE   0x300000
 #define CURLINFO_SLIST    0x400000
+#define CURLINFO_PTR      0x400000 /* same as SLIST */
+#define CURLINFO_SOCKET   0x500000
+#define CURLINFO_OFF_T    0x600000
 #define CURLINFO_MASK     0x0fffff
 #define CURLINFO_TYPEMASK 0xf00000
 
@@ -1898,15 +2562,22 @@ typedef enum {
   CURLINFO_CONNECT_TIME     = CURLINFO_DOUBLE + 5,
   CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
   CURLINFO_SIZE_UPLOAD      = CURLINFO_DOUBLE + 7,
+  CURLINFO_SIZE_UPLOAD_T    = CURLINFO_OFF_T  + 7,
   CURLINFO_SIZE_DOWNLOAD    = CURLINFO_DOUBLE + 8,
+  CURLINFO_SIZE_DOWNLOAD_T  = CURLINFO_OFF_T  + 8,
   CURLINFO_SPEED_DOWNLOAD   = CURLINFO_DOUBLE + 9,
+  CURLINFO_SPEED_DOWNLOAD_T = CURLINFO_OFF_T  + 9,
   CURLINFO_SPEED_UPLOAD     = CURLINFO_DOUBLE + 10,
+  CURLINFO_SPEED_UPLOAD_T   = CURLINFO_OFF_T  + 10,
   CURLINFO_HEADER_SIZE      = CURLINFO_LONG   + 11,
   CURLINFO_REQUEST_SIZE     = CURLINFO_LONG   + 12,
   CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG   + 13,
   CURLINFO_FILETIME         = CURLINFO_LONG   + 14,
+  CURLINFO_FILETIME_T       = CURLINFO_OFF_T  + 14,
   CURLINFO_CONTENT_LENGTH_DOWNLOAD   = CURLINFO_DOUBLE + 15,
+  CURLINFO_CONTENT_LENGTH_DOWNLOAD_T = CURLINFO_OFF_T  + 15,
   CURLINFO_CONTENT_LENGTH_UPLOAD     = CURLINFO_DOUBLE + 16,
+  CURLINFO_CONTENT_LENGTH_UPLOAD_T   = CURLINFO_OFF_T  + 16,
   CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
   CURLINFO_CONTENT_TYPE     = CURLINFO_STRING + 18,
   CURLINFO_REDIRECT_TIME    = CURLINFO_DOUBLE + 19,
@@ -1924,7 +2595,7 @@ typedef enum {
   CURLINFO_REDIRECT_URL     = CURLINFO_STRING + 31,
   CURLINFO_PRIMARY_IP       = CURLINFO_STRING + 32,
   CURLINFO_APPCONNECT_TIME  = CURLINFO_DOUBLE + 33,
-  CURLINFO_CERTINFO         = CURLINFO_SLIST  + 34,
+  CURLINFO_CERTINFO         = CURLINFO_PTR    + 34,
   CURLINFO_CONDITION_UNMET  = CURLINFO_LONG   + 35,
   CURLINFO_RTSP_SESSION_ID  = CURLINFO_STRING + 36,
   CURLINFO_RTSP_CLIENT_CSEQ = CURLINFO_LONG   + 37,
@@ -1933,9 +2604,27 @@ typedef enum {
   CURLINFO_PRIMARY_PORT     = CURLINFO_LONG   + 40,
   CURLINFO_LOCAL_IP         = CURLINFO_STRING + 41,
   CURLINFO_LOCAL_PORT       = CURLINFO_LONG   + 42,
+  CURLINFO_TLS_SESSION      = CURLINFO_PTR    + 43,
+  CURLINFO_ACTIVESOCKET     = CURLINFO_SOCKET + 44,
+  CURLINFO_TLS_SSL_PTR      = CURLINFO_PTR    + 45,
+  CURLINFO_HTTP_VERSION     = CURLINFO_LONG   + 46,
+  CURLINFO_PROXY_SSL_VERIFYRESULT = CURLINFO_LONG + 47,
+  CURLINFO_PROTOCOL         = CURLINFO_LONG   + 48,
+  CURLINFO_SCHEME           = CURLINFO_STRING + 49,
   /* Fill in new entries below here! */
 
-  CURLINFO_LASTONE          = 42
+  /* Preferably these would be defined conditionally based on the
+     sizeof curl_off_t being 64-bits */
+  CURLINFO_TOTAL_TIME_T     = CURLINFO_OFF_T + 50,
+  CURLINFO_NAMELOOKUP_TIME_T = CURLINFO_OFF_T + 51,
+  CURLINFO_CONNECT_TIME_T   = CURLINFO_OFF_T + 52,
+  CURLINFO_PRETRANSFER_TIME_T = CURLINFO_OFF_T + 53,
+  CURLINFO_STARTTRANSFER_TIME_T = CURLINFO_OFF_T + 54,
+  CURLINFO_REDIRECT_TIME_T  = CURLINFO_OFF_T + 55,
+  CURLINFO_APPCONNECT_TIME_T = CURLINFO_OFF_T + 56,
+  CURLINFO_RETRY_AFTER      = CURLINFO_OFF_T + 57,
+
+  CURLINFO_LASTONE          = 57
 } CURLINFO;
 
 /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as
@@ -1954,11 +2643,12 @@ typedef enum {
   CURLCLOSEPOLICY_LAST /* last, never use this */
 } curl_closepolicy;
 
-#define CURL_GLOBAL_SSL (1<<0)
+#define CURL_GLOBAL_SSL (1<<0) /* no purpose since since 7.57.0 */
 #define CURL_GLOBAL_WIN32 (1<<1)
 #define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32)
 #define CURL_GLOBAL_NOTHING 0
 #define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL
+#define CURL_GLOBAL_ACK_EINTR (1<<2)
 
 
 /*****************************************************************************
@@ -1977,6 +2667,7 @@ typedef enum {
   CURL_LOCK_DATA_DNS,
   CURL_LOCK_DATA_SSL_SESSION,
   CURL_LOCK_DATA_CONNECT,
+  CURL_LOCK_DATA_PSL,
   CURL_LOCK_DATA_LAST
 } curl_lock_data;
 
@@ -1996,15 +2687,15 @@ typedef void (*curl_unlock_function)(CURL *handle,
                                      curl_lock_data data,
                                      void *userptr);
 
-typedef void CURLSH;
 
 typedef enum {
   CURLSHE_OK,  /* all is fine */
   CURLSHE_BAD_OPTION, /* 1 */
   CURLSHE_IN_USE,     /* 2 */
   CURLSHE_INVALID,    /* 3 */
-  CURLSHE_NOMEM,      /* out of memory */
-  CURLSHE_LAST /* never use */
+  CURLSHE_NOMEM,      /* 4 out of memory */
+  CURLSHE_NOT_BUILT_IN, /* 5 feature not present in lib */
+  CURLSHE_LAST        /* never use */
 } CURLSHcode;
 
 typedef enum {
@@ -2031,6 +2722,8 @@ typedef enum {
   CURLVERSION_SECOND,
   CURLVERSION_THIRD,
   CURLVERSION_FOURTH,
+  CURLVERSION_FIFTH,
+  CURLVERSION_SIXTH,
   CURLVERSION_LAST /* never actually use this */
 } CURLversion;
 
@@ -2039,7 +2732,7 @@ typedef enum {
    meant to be a built-in version number for what kind of struct the caller
    expects. If the struct ever changes, we redefine the NOW to another enum
    from above. */
-#define CURLVERSION_NOW CURLVERSION_FOURTH
+#define CURLVERSION_NOW CURLVERSION_SIXTH
 
 typedef struct {
   CURLversion age;          /* age of the returned struct */
@@ -2067,25 +2760,54 @@ typedef struct {
 
   const char *libssh_version; /* human readable string */
 
+  /* These fields were added in CURLVERSION_FIFTH */
+  unsigned int brotli_ver_num; /* Numeric Brotli version
+                                  (MAJOR << 24) | (MINOR << 12) | PATCH */
+  const char *brotli_version; /* human readable string. */
+
+  /* These fields were added in CURLVERSION_SIXTH */
+  unsigned int nghttp2_ver_num; /* Numeric nghttp2 version
+                                   (MAJOR << 16) | (MINOR << 8) | PATCH */
+  const char *nghttp2_version; /* human readable string. */
+  const char *quic_version;    /* human readable quic (+ HTTP/3) library +
+                                  version or NULL */
 } curl_version_info_data;
 
-#define CURL_VERSION_IPV6      (1<<0)  /* IPv6-enabled */
-#define CURL_VERSION_KERBEROS4 (1<<1)  /* kerberos auth is supported */
-#define CURL_VERSION_SSL       (1<<2)  /* SSL options are present */
-#define CURL_VERSION_LIBZ      (1<<3)  /* libz features are present */
-#define CURL_VERSION_NTLM      (1<<4)  /* NTLM auth is supported */
-#define CURL_VERSION_GSSNEGOTIATE (1<<5) /* Negotiate auth support */
-#define CURL_VERSION_DEBUG     (1<<6)  /* built with debug capabilities */
-#define CURL_VERSION_ASYNCHDNS (1<<7)  /* asynchronous dns resolves */
-#define CURL_VERSION_SPNEGO    (1<<8)  /* SPNEGO auth */
-#define CURL_VERSION_LARGEFILE (1<<9)  /* supports files bigger than 2GB */
-#define CURL_VERSION_IDN       (1<<10) /* International Domain Names support */
-#define CURL_VERSION_SSPI      (1<<11) /* SSPI is supported */
-#define CURL_VERSION_CONV      (1<<12) /* character conversions supported */
-#define CURL_VERSION_CURLDEBUG (1<<13) /* debug memory tracking supported */
-#define CURL_VERSION_TLSAUTH_SRP (1<<14) /* TLS-SRP auth is supported */
-
-/*
+#define CURL_VERSION_IPV6         (1<<0)  /* IPv6-enabled */
+#define CURL_VERSION_KERBEROS4    (1<<1)  /* Kerberos V4 auth is supported
+                                             (deprecated) */
+#define CURL_VERSION_SSL          (1<<2)  /* SSL options are present */
+#define CURL_VERSION_LIBZ         (1<<3)  /* libz features are present */
+#define CURL_VERSION_NTLM         (1<<4)  /* NTLM auth is supported */
+#define CURL_VERSION_GSSNEGOTIATE (1<<5)  /* Negotiate auth is supported
+                                             (deprecated) */
+#define CURL_VERSION_DEBUG        (1<<6)  /* Built with debug capabilities */
+#define CURL_VERSION_ASYNCHDNS    (1<<7)  /* Asynchronous DNS resolves */
+#define CURL_VERSION_SPNEGO       (1<<8)  /* SPNEGO auth is supported */
+#define CURL_VERSION_LARGEFILE    (1<<9)  /* Supports files larger than 2GB */
+#define CURL_VERSION_IDN          (1<<10) /* Internationized Domain Names are
+                                             supported */
+#define CURL_VERSION_SSPI         (1<<11) /* Built against Windows SSPI */
+#define CURL_VERSION_CONV         (1<<12) /* Character conversions supported */
+#define CURL_VERSION_CURLDEBUG    (1<<13) /* Debug memory tracking supported */
+#define CURL_VERSION_TLSAUTH_SRP  (1<<14) /* TLS-SRP auth is supported */
+#define CURL_VERSION_NTLM_WB      (1<<15) /* NTLM delegation to winbind helper
+                                             is supported */
+#define CURL_VERSION_HTTP2        (1<<16) /* HTTP2 support built-in */
+#define CURL_VERSION_GSSAPI       (1<<17) /* Built against a GSS-API library */
+#define CURL_VERSION_KERBEROS5    (1<<18) /* Kerberos V5 auth is supported */
+#define CURL_VERSION_UNIX_SOCKETS (1<<19) /* Unix domain sockets support */
+#define CURL_VERSION_PSL          (1<<20) /* Mozilla's Public Suffix List, used
+                                             for cookie domain verification */
+#define CURL_VERSION_HTTPS_PROXY  (1<<21) /* HTTPS-proxy support built-in */
+#define CURL_VERSION_MULTI_SSL    (1<<22) /* Multiple SSL backends available */
+#define CURL_VERSION_BROTLI       (1<<23) /* Brotli features are present. */
+#define CURL_VERSION_ALTSVC       (1<<24) /* Alt-Svc handling built-in */
+#define CURL_VERSION_HTTP3        (1<<25) /* HTTP3 support built-in */
+
+#define CURL_VERSION_ESNI         (1<<26) /* ESNI support */
+
+ /*
  * NAME curl_version_info()
  *
  * DESCRIPTION
@@ -2145,6 +2867,7 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask);
   stuff before they can be included! */
 #include "easy.h" /* nothing in curl is fun without the easy stuff */
 #include "multi.h"
+#include "urlapi.h"
 
 /* the typechecker doesn't work in C++ (yet) */
 #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \
@@ -2163,4 +2886,4 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask);
 #endif /* __STDC__ >= 1 */
 #endif /* gcc >= 4.3 && !__cplusplus */
 
-#endif /* __CURL_CURL_H */
+#endif /* CURLINC_CURL_H */
diff --git a/libs/curl/include/curl/curlver.h b/libs/curl/include/curl/curlver.h
index 4db21857d833c3ddebdf8b9cf20b3429902d9b19..5264f19865ea3d2d14524851416547edc23fbea5 100644
--- a/libs/curl/include/curl/curlver.h
+++ b/libs/curl/include/curl/curlver.h
@@ -1,5 +1,5 @@
-#ifndef __CURL_CURLVER_H
-#define __CURL_CURLVER_H
+#ifndef CURLINC_CURLVER_H
+#define CURLINC_CURLVER_H
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
@@ -7,11 +7,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -26,17 +26,17 @@
    a script at release-time. This was made its own header file in 7.11.2 */
 
 /* This is the global package copyright */
-#define LIBCURL_COPYRIGHT "1996 - 2011 Daniel Stenberg, <daniel@haxx.se>."
+#define LIBCURL_COPYRIGHT "1996 - 2020 Daniel Stenberg, <daniel@haxx.se>."
 
 /* This is the version number of the libcurl package from which this header
    file origins: */
-#define LIBCURL_VERSION "7.21.6"
+#define LIBCURL_VERSION "7.69.0"
 
 /* The numeric version number is also available "in parts" by using these
    defines: */
 #define LIBCURL_VERSION_MAJOR 7
-#define LIBCURL_VERSION_MINOR 21
-#define LIBCURL_VERSION_PATCH 6
+#define LIBCURL_VERSION_MINOR 69
+#define LIBCURL_VERSION_PATCH 0
 
 /* This is the numeric version of the libcurl version number, meant for easier
    parsing and comparions by programs. The LIBCURL_VERSION_NUM define will
@@ -52,18 +52,26 @@
    This 6-digit (24 bits) hexadecimal number does not show pre-release number,
    and it is always a greater number in a more recent release. It makes
    comparisons with greater than and less than work.
+
+   Note: This define is the full hex number and _does not_ use the
+   CURL_VERSION_BITS() macro since curl's own configure script greps for it
+   and needs it to contain the full number.
 */
-#define LIBCURL_VERSION_NUM 0x071506
+#define LIBCURL_VERSION_NUM 0x074500
 
 /*
  * This is the date and time when the full source package was created. The
  * timestamp is not stored in git, as the timestamp is properly set in the
  * tarballs by the maketgz script.
  *
- * The format of the date should follow this template:
+ * The format of the date follows this template:
  *
- * "Mon Feb 12 11:35:33 UTC 2007"
+ * "2007-11-23"
  */
-#define LIBCURL_TIMESTAMP "Fri Apr 22 17:18:50 UTC 2011"
+#define LIBCURL_TIMESTAMP "2020-03-04"
+
+#define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z))
+#define CURL_AT_LEAST_VERSION(x,y,z) \
+  (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
 
-#endif /* __CURL_CURLVER_H */
+#endif /* CURLINC_CURLVER_H */
diff --git a/libs/curl/include/curl/easy.h b/libs/curl/include/curl/easy.h
index c1e3e76096e3925821da279a2f2eae67ae61e0e8..592f5d3c1c3ca110d5ddd02db67fd20dcfab265c 100644
--- a/libs/curl/include/curl/easy.h
+++ b/libs/curl/include/curl/easy.h
@@ -1,5 +1,5 @@
-#ifndef __CURL_EASY_H
-#define __CURL_EASY_H
+#ifndef CURLINC_EASY_H
+#define CURLINC_EASY_H
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
@@ -7,11 +7,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2008, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -58,7 +58,7 @@ CURL_EXTERN CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...);
  * curl_easy_duphandle() for each new thread to avoid a series of identical
  * curl_easy_setopt() invokes in every thread.
  */
-CURL_EXTERN CURL* curl_easy_duphandle(CURL *curl);
+CURL_EXTERN CURL *curl_easy_duphandle(CURL *curl);
 
 /*
  * NAME curl_easy_reset()
@@ -95,6 +95,16 @@ CURL_EXTERN CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen,
 CURL_EXTERN CURLcode curl_easy_send(CURL *curl, const void *buffer,
                                     size_t buflen, size_t *n);
 
+
+/*
+ * NAME curl_easy_upkeep()
+ *
+ * DESCRIPTION
+ *
+ * Performs connection upkeep for the given session handle.
+ */
+CURL_EXTERN CURLcode curl_easy_upkeep(CURL *curl);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/libs/curl/include/curl/mprintf.h b/libs/curl/include/curl/mprintf.h
index de7dd2f3c360e327e6280372e8a10f335fcf3504..f615ed7d63d9e047824bd1cda14fc75f26ff623a 100644
--- a/libs/curl/include/curl/mprintf.h
+++ b/libs/curl/include/curl/mprintf.h
@@ -1,5 +1,5 @@
-#ifndef __CURL_MPRINTF_H
-#define __CURL_MPRINTF_H
+#ifndef CURLINC_MPRINTF_H
+#define CURLINC_MPRINTF_H
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
@@ -7,11 +7,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -24,8 +24,7 @@
 
 #include <stdarg.h>
 #include <stdio.h> /* needed for FILE */
-
-#include "curl.h"
+#include "curl.h"  /* for CURL_EXTERN */
 
 #ifdef  __cplusplus
 extern "C" {
@@ -44,38 +43,8 @@ CURL_EXTERN int curl_mvsnprintf(char *buffer, size_t maxlength,
 CURL_EXTERN char *curl_maprintf(const char *format, ...);
 CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args);
 
-#ifdef _MPRINTF_REPLACE
-# undef printf
-# undef fprintf
-# undef sprintf
-# undef vsprintf
-# undef snprintf
-# undef vprintf
-# undef vfprintf
-# undef vsnprintf
-# undef aprintf
-# undef vaprintf
-# define printf curl_mprintf
-# define fprintf curl_mfprintf
-#ifdef CURLDEBUG
-/* When built with CURLDEBUG we define away the sprintf() functions since we
-   don't want internal code to be using them */
-# define sprintf sprintf_was_used
-# define vsprintf vsprintf_was_used
-#else
-# define sprintf curl_msprintf
-# define vsprintf curl_mvsprintf
-#endif
-# define snprintf curl_msnprintf
-# define vprintf curl_mvprintf
-# define vfprintf curl_mvfprintf
-# define vsnprintf curl_mvsnprintf
-# define aprintf curl_maprintf
-# define vaprintf curl_mvaprintf
-#endif
-
 #ifdef  __cplusplus
 }
 #endif
 
-#endif /* __CURL_MPRINTF_H */
+#endif /* CURLINC_MPRINTF_H */
diff --git a/libs/curl/include/curl/multi.h b/libs/curl/include/curl/multi.h
index f96566669c6771fbbf2e1735f826d8cb3a1fce11..bda9bb7b816c400e2cde1795f4dae8937afbfd0a 100644
--- a/libs/curl/include/curl/multi.h
+++ b/libs/curl/include/curl/multi.h
@@ -1,5 +1,5 @@
-#ifndef __CURL_MULTI_H
-#define __CURL_MULTI_H
+#ifndef CURLINC_MULTI_H
+#define CURLINC_MULTI_H
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
@@ -7,11 +7,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -52,7 +52,11 @@
 extern "C" {
 #endif
 
+#if defined(BUILDING_LIBCURL) || defined(CURL_STRICTER)
+typedef struct Curl_multi CURLM;
+#else
 typedef void CURLM;
+#endif
 
 typedef enum {
   CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or
@@ -64,6 +68,12 @@ typedef enum {
   CURLM_INTERNAL_ERROR,  /* this is a libcurl bug */
   CURLM_BAD_SOCKET,      /* the passed in socket argument did not match */
   CURLM_UNKNOWN_OPTION,  /* curl_multi_setopt() with unsupported option */
+  CURLM_ADDED_ALREADY,   /* an easy handle already added to a multi handle was
+                            attempted to get added - again */
+  CURLM_RECURSIVE_API_CALL, /* an api function was called from inside a
+                               callback */
+  CURLM_WAKEUP_FAILURE,  /* wakeup is unavailable or failed */
+  CURLM_BAD_FUNCTION_ARGUMENT,  /* function called with a bad parameter */
   CURLM_LAST
 } CURLMcode;
 
@@ -72,6 +82,11 @@ typedef enum {
    curl_multi_perform() and CURLM_CALL_MULTI_PERFORM */
 #define CURLM_CALL_MULTI_SOCKET CURLM_CALL_MULTI_PERFORM
 
+/* bitmask bits for CURLMOPT_PIPELINING */
+#define CURLPIPE_NOTHING   0L
+#define CURLPIPE_HTTP1     1L
+#define CURLPIPE_MULTIPLEX 2L
+
 typedef enum {
   CURLMSG_NONE, /* first, not used */
   CURLMSG_DONE, /* This easy handle has completed. 'result' contains
@@ -89,6 +104,19 @@ struct CURLMsg {
 };
 typedef struct CURLMsg CURLMsg;
 
+/* Based on poll(2) structure and values.
+ * We don't use pollfd and POLL* constants explicitly
+ * to cover platforms without poll(). */
+#define CURL_WAIT_POLLIN    0x0001
+#define CURL_WAIT_POLLPRI   0x0002
+#define CURL_WAIT_POLLOUT   0x0004
+
+struct curl_waitfd {
+  curl_socket_t fd;
+  short events;
+  short revents; /* not supported yet */
+};
+
 /*
  * Name:    curl_multi_init()
  *
@@ -133,6 +161,43 @@ CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle,
                                        fd_set *exc_fd_set,
                                        int *max_fd);
 
+/*
+ * Name:     curl_multi_wait()
+ *
+ * Desc:     Poll on all fds within a CURLM set as well as any
+ *           additional fds passed to the function.
+ *
+ * Returns:  CURLMcode type, general multi error code.
+ */
+CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle,
+                                      struct curl_waitfd extra_fds[],
+                                      unsigned int extra_nfds,
+                                      int timeout_ms,
+                                      int *ret);
+
+/*
+ * Name:     curl_multi_poll()
+ *
+ * Desc:     Poll on all fds within a CURLM set as well as any
+ *           additional fds passed to the function.
+ *
+ * Returns:  CURLMcode type, general multi error code.
+ */
+CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
+                                      struct curl_waitfd extra_fds[],
+                                      unsigned int extra_nfds,
+                                      int timeout_ms,
+                                      int *ret);
+
+/*
+ * Name:     curl_multi_wakeup()
+ *
+ * Desc:     wakes up a sleeping curl_multi_poll call.
+ *
+ * Returns:  CURLMcode type, general multi error code.
+ */
+CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
+
  /*
   * Name:    curl_multi_perform()
   *
@@ -146,8 +211,8 @@ CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle,
   *
   * Returns: CURLMcode type, general multi error code. *NOTE* that this only
   *          returns errors etc regarding the whole multi stack. There might
-  *          still have occurred problems on invidual transfers even when this
-  *          returns OK.
+  *          still have occurred problems on individual transfers even when
+  *          this returns OK.
   */
 CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle,
                                          int *running_handles);
@@ -180,7 +245,7 @@ CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle);
  *          curl_multi_cleanup().
  *
  *          The 'CURLMsg' struct is meant to be very simple and only contain
- *          very basic informations. If more involved information is wanted,
+ *          very basic information. If more involved information is wanted,
  *          we will provide the particular "transfer handle" in that struct
  *          and that should/could/would be used in subsequent
  *          curl_easy_getinfo() calls (or similar). The point being that we
@@ -279,37 +344,58 @@ CURL_EXTERN CURLMcode curl_multi_socket_all(CURLM *multi_handle,
 CURL_EXTERN CURLMcode curl_multi_timeout(CURLM *multi_handle,
                                          long *milliseconds);
 
-#undef CINIT /* re-using the same name as in curl.h */
-
-#ifdef CURL_ISOCPP
-#define CINIT(name,type,num) CURLMOPT_ ## name = CURLOPTTYPE_ ## type + num
-#else
-/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */
-#define LONG          CURLOPTTYPE_LONG
-#define OBJECTPOINT   CURLOPTTYPE_OBJECTPOINT
-#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT
-#define OFF_T         CURLOPTTYPE_OFF_T
-#define CINIT(name,type,number) CURLMOPT_/**/name = type + number
-#endif
-
 typedef enum {
   /* This is the socket callback function pointer */
-  CINIT(SOCKETFUNCTION, FUNCTIONPOINT, 1),
+  CURLOPT(CURLMOPT_SOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 1),
 
   /* This is the argument passed to the socket callback */
-  CINIT(SOCKETDATA, OBJECTPOINT, 2),
+  CURLOPT(CURLMOPT_SOCKETDATA, CURLOPTTYPE_OBJECTPOINT, 2),
 
     /* set to 1 to enable pipelining for this multi handle */
-  CINIT(PIPELINING, LONG, 3),
+  CURLOPT(CURLMOPT_PIPELINING, CURLOPTTYPE_LONG, 3),
 
    /* This is the timer callback function pointer */
-  CINIT(TIMERFUNCTION, FUNCTIONPOINT, 4),
+  CURLOPT(CURLMOPT_TIMERFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 4),
 
   /* This is the argument passed to the timer callback */
-  CINIT(TIMERDATA, OBJECTPOINT, 5),
+  CURLOPT(CURLMOPT_TIMERDATA, CURLOPTTYPE_OBJECTPOINT, 5),
 
   /* maximum number of entries in the connection cache */
-  CINIT(MAXCONNECTS, LONG, 6),
+  CURLOPT(CURLMOPT_MAXCONNECTS, CURLOPTTYPE_LONG, 6),
+
+  /* maximum number of (pipelining) connections to one host */
+  CURLOPT(CURLMOPT_MAX_HOST_CONNECTIONS, CURLOPTTYPE_LONG, 7),
+
+  /* maximum number of requests in a pipeline */
+  CURLOPT(CURLMOPT_MAX_PIPELINE_LENGTH, CURLOPTTYPE_LONG, 8),
+
+  /* a connection with a content-length longer than this
+     will not be considered for pipelining */
+  CURLOPT(CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, CURLOPTTYPE_OFF_T, 9),
+
+  /* a connection with a chunk length longer than this
+     will not be considered for pipelining */
+  CURLOPT(CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, CURLOPTTYPE_OFF_T, 10),
+
+  /* a list of site names(+port) that are blacklisted from
+     pipelining */
+  CURLOPT(CURLMOPT_PIPELINING_SITE_BL, CURLOPTTYPE_OBJECTPOINT, 11),
+
+  /* a list of server types that are blacklisted from
+     pipelining */
+  CURLOPT(CURLMOPT_PIPELINING_SERVER_BL, CURLOPTTYPE_OBJECTPOINT, 12),
+
+  /* maximum number of open connections in total */
+  CURLOPT(CURLMOPT_MAX_TOTAL_CONNECTIONS, CURLOPTTYPE_LONG, 13),
+
+   /* This is the server push callback function pointer */
+  CURLOPT(CURLMOPT_PUSHFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 14),
+
+  /* This is the argument passed to the server push callback */
+  CURLOPT(CURLMOPT_PUSHDATA, CURLOPTTYPE_OBJECTPOINT, 15),
+
+  /* maximum number of concurrent streams to support on a connection */
+  CURLOPT(CURLMOPT_MAX_CONCURRENT_STREAMS, CURLOPTTYPE_LONG, 16),
 
   CURLMOPT_LASTENTRY /* the last unused */
 } CURLMoption;
@@ -338,6 +424,31 @@ CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle,
 CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle,
                                         curl_socket_t sockfd, void *sockp);
 
+
+/*
+ * Name: curl_push_callback
+ *
+ * Desc: This callback gets called when a new stream is being pushed by the
+ *       server. It approves or denies the new stream.
+ *
+ * Returns: CURL_PUSH_OK or CURL_PUSH_DENY.
+ */
+#define CURL_PUSH_OK   0
+#define CURL_PUSH_DENY 1
+
+struct curl_pushheaders;  /* forward declaration only */
+
+CURL_EXTERN char *curl_pushheader_bynum(struct curl_pushheaders *h,
+                                        size_t num);
+CURL_EXTERN char *curl_pushheader_byname(struct curl_pushheaders *h,
+                                         const char *name);
+
+typedef int (*curl_push_callback)(CURL *parent,
+                                  CURL *easy,
+                                  size_t num_headers,
+                                  struct curl_pushheaders *headers,
+                                  void *userp);
+
 #ifdef __cplusplus
 } /* end of extern "C" */
 #endif
diff --git a/libs/curl/include/curl/stdcheaders.h b/libs/curl/include/curl/stdcheaders.h
index ad82ef6335d6167aecf8b9e49d68c462ea028bfd..a6bdc1a25ca3ee8e7dcea56575212a7e73239b9e 100644
--- a/libs/curl/include/curl/stdcheaders.h
+++ b/libs/curl/include/curl/stdcheaders.h
@@ -1,5 +1,5 @@
-#ifndef __STDC_HEADERS_H
-#define __STDC_HEADERS_H
+#ifndef CURLINC_STDCHEADERS_H
+#define CURLINC_STDCHEADERS_H
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
@@ -7,11 +7,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -24,10 +24,10 @@
 
 #include <sys/types.h>
 
-size_t fread (void *, size_t, size_t, FILE *);
-size_t fwrite (const void *, size_t, size_t, FILE *);
+size_t fread(void *, size_t, size_t, FILE *);
+size_t fwrite(const void *, size_t, size_t, FILE *);
 
 int strcasecmp(const char *, const char *);
 int strncasecmp(const char *, const char *, size_t);
 
-#endif /* __STDC_HEADERS_H */
+#endif /* CURLINC_STDCHEADERS_H */
diff --git a/libs/curl/include/curl/system.h b/libs/curl/include/curl/system.h
new file mode 100644
index 0000000000000000000000000000000000000000..867af614187227cba7734ace2df73eb85e48c6cb
--- /dev/null
+++ b/libs/curl/include/curl/system.h
@@ -0,0 +1,504 @@
+#ifndef CURLINC_SYSTEM_H
+#define CURLINC_SYSTEM_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+/*
+ * Try to keep one section per platform, compiler and architecture, otherwise,
+ * if an existing section is reused for a different one and later on the
+ * original is adjusted, probably the piggybacking one can be adversely
+ * changed.
+ *
+ * In order to differentiate between platforms/compilers/architectures use
+ * only compiler built in predefined preprocessor symbols.
+ *
+ * curl_off_t
+ * ----------
+ *
+ * For any given platform/compiler curl_off_t must be typedef'ed to a 64-bit
+ * wide signed integral data type. The width of this data type must remain
+ * constant and independent of any possible large file support settings.
+ *
+ * As an exception to the above, curl_off_t shall be typedef'ed to a 32-bit
+ * wide signed integral data type if there is no 64-bit type.
+ *
+ * As a general rule, curl_off_t shall not be mapped to off_t. This rule shall
+ * only be violated if off_t is the only 64-bit data type available and the
+ * size of off_t is independent of large file support settings. Keep your
+ * build on the safe side avoiding an off_t gating.  If you have a 64-bit
+ * off_t then take for sure that another 64-bit data type exists, dig deeper
+ * and you will find it.
+ *
+ */
+
+#if defined(__DJGPP__) || defined(__GO32__)
+#  if defined(__DJGPP__) && (__DJGPP__ > 1)
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  else
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__SALFORDC__)
+#  define CURL_TYPEOF_CURL_OFF_T     long
+#  define CURL_FORMAT_CURL_OFF_T     "ld"
+#  define CURL_FORMAT_CURL_OFF_TU    "lu"
+#  define CURL_SUFFIX_CURL_OFF_T     L
+#  define CURL_SUFFIX_CURL_OFF_TU    UL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__BORLANDC__)
+#  if (__BORLANDC__ < 0x520)
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  else
+#    define CURL_TYPEOF_CURL_OFF_T     __int64
+#    define CURL_FORMAT_CURL_OFF_T     "I64d"
+#    define CURL_FORMAT_CURL_OFF_TU    "I64u"
+#    define CURL_SUFFIX_CURL_OFF_T     i64
+#    define CURL_SUFFIX_CURL_OFF_TU    ui64
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__TURBOC__)
+#  define CURL_TYPEOF_CURL_OFF_T     long
+#  define CURL_FORMAT_CURL_OFF_T     "ld"
+#  define CURL_FORMAT_CURL_OFF_TU    "lu"
+#  define CURL_SUFFIX_CURL_OFF_T     L
+#  define CURL_SUFFIX_CURL_OFF_TU    UL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__WATCOMC__)
+#  if defined(__386__)
+#    define CURL_TYPEOF_CURL_OFF_T     __int64
+#    define CURL_FORMAT_CURL_OFF_T     "I64d"
+#    define CURL_FORMAT_CURL_OFF_TU    "I64u"
+#    define CURL_SUFFIX_CURL_OFF_T     i64
+#    define CURL_SUFFIX_CURL_OFF_TU    ui64
+#  else
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__POCC__)
+#  if (__POCC__ < 280)
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  elif defined(_MSC_VER)
+#    define CURL_TYPEOF_CURL_OFF_T     __int64
+#    define CURL_FORMAT_CURL_OFF_T     "I64d"
+#    define CURL_FORMAT_CURL_OFF_TU    "I64u"
+#    define CURL_SUFFIX_CURL_OFF_T     i64
+#    define CURL_SUFFIX_CURL_OFF_TU    ui64
+#  else
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__LCC__)
+#  if defined(__e2k__) /* MCST eLbrus C Compiler */
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#    define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#    define CURL_PULL_SYS_TYPES_H      1
+#    define CURL_PULL_SYS_SOCKET_H     1
+#  else                /* Local (or Little) C Compiler */
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#    define CURL_TYPEOF_CURL_SOCKLEN_T int
+#  endif
+
+#elif defined(__SYMBIAN32__)
+#  if defined(__EABI__) /* Treat all ARM compilers equally */
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  elif defined(__CW32__)
+#    pragma longlong on
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  elif defined(__VC32__)
+#    define CURL_TYPEOF_CURL_OFF_T     __int64
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int
+
+#elif defined(__MWERKS__)
+#  define CURL_TYPEOF_CURL_OFF_T     long long
+#  define CURL_FORMAT_CURL_OFF_T     "lld"
+#  define CURL_FORMAT_CURL_OFF_TU    "llu"
+#  define CURL_SUFFIX_CURL_OFF_T     LL
+#  define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(_WIN32_WCE)
+#  define CURL_TYPEOF_CURL_OFF_T     __int64
+#  define CURL_FORMAT_CURL_OFF_T     "I64d"
+#  define CURL_FORMAT_CURL_OFF_TU    "I64u"
+#  define CURL_SUFFIX_CURL_OFF_T     i64
+#  define CURL_SUFFIX_CURL_OFF_TU    ui64
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__MINGW32__)
+#  define CURL_TYPEOF_CURL_OFF_T     long long
+#  define CURL_FORMAT_CURL_OFF_T     "I64d"
+#  define CURL_FORMAT_CURL_OFF_TU    "I64u"
+#  define CURL_SUFFIX_CURL_OFF_T     LL
+#  define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#  define CURL_PULL_SYS_TYPES_H      1
+#  define CURL_PULL_WS2TCPIP_H       1
+
+#elif defined(__VMS)
+#  if defined(__VAX)
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  else
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int
+
+#elif defined(__OS400__)
+#  if defined(__ILEC400__)
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#    define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#    define CURL_PULL_SYS_TYPES_H      1
+#    define CURL_PULL_SYS_SOCKET_H     1
+#  endif
+
+#elif defined(__MVS__)
+#  if defined(__IBMC__) || defined(__IBMCPP__)
+#    if defined(_ILP32)
+#    elif defined(_LP64)
+#    endif
+#    if defined(_LONG_LONG)
+#      define CURL_TYPEOF_CURL_OFF_T     long long
+#      define CURL_FORMAT_CURL_OFF_T     "lld"
+#      define CURL_FORMAT_CURL_OFF_TU    "llu"
+#      define CURL_SUFFIX_CURL_OFF_T     LL
+#      define CURL_SUFFIX_CURL_OFF_TU    ULL
+#    elif defined(_LP64)
+#      define CURL_TYPEOF_CURL_OFF_T     long
+#      define CURL_FORMAT_CURL_OFF_T     "ld"
+#      define CURL_FORMAT_CURL_OFF_TU    "lu"
+#      define CURL_SUFFIX_CURL_OFF_T     L
+#      define CURL_SUFFIX_CURL_OFF_TU    UL
+#    else
+#      define CURL_TYPEOF_CURL_OFF_T     long
+#      define CURL_FORMAT_CURL_OFF_T     "ld"
+#      define CURL_FORMAT_CURL_OFF_TU    "lu"
+#      define CURL_SUFFIX_CURL_OFF_T     L
+#      define CURL_SUFFIX_CURL_OFF_TU    UL
+#    endif
+#    define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#    define CURL_PULL_SYS_TYPES_H      1
+#    define CURL_PULL_SYS_SOCKET_H     1
+#  endif
+
+#elif defined(__370__)
+#  if defined(__IBMC__) || defined(__IBMCPP__)
+#    if defined(_ILP32)
+#    elif defined(_LP64)
+#    endif
+#    if defined(_LONG_LONG)
+#      define CURL_TYPEOF_CURL_OFF_T     long long
+#      define CURL_FORMAT_CURL_OFF_T     "lld"
+#      define CURL_FORMAT_CURL_OFF_TU    "llu"
+#      define CURL_SUFFIX_CURL_OFF_T     LL
+#      define CURL_SUFFIX_CURL_OFF_TU    ULL
+#    elif defined(_LP64)
+#      define CURL_TYPEOF_CURL_OFF_T     long
+#      define CURL_FORMAT_CURL_OFF_T     "ld"
+#      define CURL_FORMAT_CURL_OFF_TU    "lu"
+#      define CURL_SUFFIX_CURL_OFF_T     L
+#      define CURL_SUFFIX_CURL_OFF_TU    UL
+#    else
+#      define CURL_TYPEOF_CURL_OFF_T     long
+#      define CURL_FORMAT_CURL_OFF_T     "ld"
+#      define CURL_FORMAT_CURL_OFF_TU    "lu"
+#      define CURL_SUFFIX_CURL_OFF_T     L
+#      define CURL_SUFFIX_CURL_OFF_TU    UL
+#    endif
+#    define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#    define CURL_PULL_SYS_TYPES_H      1
+#    define CURL_PULL_SYS_SOCKET_H     1
+#  endif
+
+#elif defined(TPF)
+#  define CURL_TYPEOF_CURL_OFF_T     long
+#  define CURL_FORMAT_CURL_OFF_T     "ld"
+#  define CURL_FORMAT_CURL_OFF_TU    "lu"
+#  define CURL_SUFFIX_CURL_OFF_T     L
+#  define CURL_SUFFIX_CURL_OFF_TU    UL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+#elif defined(__TINYC__) /* also known as tcc */
+#  define CURL_TYPEOF_CURL_OFF_T     long long
+#  define CURL_FORMAT_CURL_OFF_T     "lld"
+#  define CURL_FORMAT_CURL_OFF_TU    "llu"
+#  define CURL_SUFFIX_CURL_OFF_T     LL
+#  define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#  define CURL_PULL_SYS_TYPES_H      1
+#  define CURL_PULL_SYS_SOCKET_H     1
+
+#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) /* Oracle Solaris Studio */
+#  if !defined(__LP64) && (defined(__ILP32) ||                          \
+                           defined(__i386) ||                           \
+                           defined(__sparcv8) ||                        \
+                           defined(__sparcv8plus))
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  elif defined(__LP64) || \
+        defined(__amd64) || defined(__sparcv9)
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#  define CURL_PULL_SYS_TYPES_H      1
+#  define CURL_PULL_SYS_SOCKET_H     1
+
+#elif defined(__xlc__) /* IBM xlc compiler */
+#  if !defined(_LP64)
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  else
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#  define CURL_PULL_SYS_TYPES_H      1
+#  define CURL_PULL_SYS_SOCKET_H     1
+
+/* ===================================== */
+/*    KEEP MSVC THE PENULTIMATE ENTRY    */
+/* ===================================== */
+
+#elif defined(_MSC_VER)
+#  if (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64)
+#    define CURL_TYPEOF_CURL_OFF_T     __int64
+#    define CURL_FORMAT_CURL_OFF_T     "I64d"
+#    define CURL_FORMAT_CURL_OFF_TU    "I64u"
+#    define CURL_SUFFIX_CURL_OFF_T     i64
+#    define CURL_SUFFIX_CURL_OFF_TU    ui64
+#  else
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T int
+
+/* ===================================== */
+/*    KEEP GENERIC GCC THE LAST ENTRY    */
+/* ===================================== */
+
+#elif defined(__GNUC__) && !defined(_SCO_DS)
+#  if !defined(__LP64__) &&                                             \
+  (defined(__ILP32__) || defined(__i386__) || defined(__hppa__) ||      \
+   defined(__ppc__) || defined(__powerpc__) || defined(__arm__) ||      \
+   defined(__sparc__) || defined(__mips__) || defined(__sh__) ||        \
+   defined(__XTENSA__) ||                                               \
+   (defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 4)  ||               \
+   (defined(__LONG_MAX__) && __LONG_MAX__ == 2147483647L))
+#    define CURL_TYPEOF_CURL_OFF_T     long long
+#    define CURL_FORMAT_CURL_OFF_T     "lld"
+#    define CURL_FORMAT_CURL_OFF_TU    "llu"
+#    define CURL_SUFFIX_CURL_OFF_T     LL
+#    define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  elif defined(__LP64__) || \
+        defined(__x86_64__) || defined(__ppc64__) || defined(__sparc64__) || \
+        defined(__e2k__) || \
+        (defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 8) || \
+        (defined(__LONG_MAX__) && __LONG_MAX__ == 9223372036854775807L)
+#    define CURL_TYPEOF_CURL_OFF_T     long
+#    define CURL_FORMAT_CURL_OFF_T     "ld"
+#    define CURL_FORMAT_CURL_OFF_TU    "lu"
+#    define CURL_SUFFIX_CURL_OFF_T     L
+#    define CURL_SUFFIX_CURL_OFF_TU    UL
+#  endif
+#  define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#  define CURL_PULL_SYS_TYPES_H      1
+#  define CURL_PULL_SYS_SOCKET_H     1
+
+#else
+/* generic "safe guess" on old 32 bit style */
+# define CURL_TYPEOF_CURL_OFF_T     long
+# define CURL_FORMAT_CURL_OFF_T     "ld"
+# define CURL_FORMAT_CURL_OFF_TU    "lu"
+# define CURL_SUFFIX_CURL_OFF_T     L
+# define CURL_SUFFIX_CURL_OFF_TU    UL
+# define CURL_TYPEOF_CURL_SOCKLEN_T int
+#endif
+
+#ifdef _AIX
+/* AIX needs <sys/poll.h> */
+#define CURL_PULL_SYS_POLL_H
+#endif
+
+
+/* CURL_PULL_WS2TCPIP_H is defined above when inclusion of header file  */
+/* ws2tcpip.h is required here to properly make type definitions below. */
+#ifdef CURL_PULL_WS2TCPIP_H
+#  include <winsock2.h>
+#  include <windows.h>
+#  include <ws2tcpip.h>
+#endif
+
+/* CURL_PULL_SYS_TYPES_H is defined above when inclusion of header file  */
+/* sys/types.h is required here to properly make type definitions below. */
+#ifdef CURL_PULL_SYS_TYPES_H
+#  include <sys/types.h>
+#endif
+
+/* CURL_PULL_SYS_SOCKET_H is defined above when inclusion of header file  */
+/* sys/socket.h is required here to properly make type definitions below. */
+#ifdef CURL_PULL_SYS_SOCKET_H
+#  include <sys/socket.h>
+#endif
+
+/* CURL_PULL_SYS_POLL_H is defined above when inclusion of header file    */
+/* sys/poll.h is required here to properly make type definitions below.   */
+#ifdef CURL_PULL_SYS_POLL_H
+#  include <sys/poll.h>
+#endif
+
+/* Data type definition of curl_socklen_t. */
+#ifdef CURL_TYPEOF_CURL_SOCKLEN_T
+  typedef CURL_TYPEOF_CURL_SOCKLEN_T curl_socklen_t;
+#endif
+
+/* Data type definition of curl_off_t. */
+
+#ifdef CURL_TYPEOF_CURL_OFF_T
+  typedef CURL_TYPEOF_CURL_OFF_T curl_off_t;
+#endif
+
+/*
+ * CURL_ISOCPP and CURL_OFF_T_C definitions are done here in order to allow
+ * these to be visible and exported by the external libcurl interface API,
+ * while also making them visible to the library internals, simply including
+ * curl_setup.h, without actually needing to include curl.h internally.
+ * If some day this section would grow big enough, all this should be moved
+ * to its own header file.
+ */
+
+/*
+ * Figure out if we can use the ## preprocessor operator, which is supported
+ * by ISO/ANSI C and C++. Some compilers support it without setting __STDC__
+ * or  __cplusplus so we need to carefully check for them too.
+ */
+
+#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) || \
+  defined(__HP_aCC) || defined(__BORLANDC__) || defined(__LCC__) || \
+  defined(__POCC__) || defined(__SALFORDC__) || defined(__HIGHC__) || \
+  defined(__ILEC400__)
+  /* This compiler is believed to have an ISO compatible preprocessor */
+#define CURL_ISOCPP
+#else
+  /* This compiler is believed NOT to have an ISO compatible preprocessor */
+#undef CURL_ISOCPP
+#endif
+
+/*
+ * Macros for minimum-width signed and unsigned curl_off_t integer constants.
+ */
+
+#if defined(__BORLANDC__) && (__BORLANDC__ == 0x0551)
+#  define CURLINC_OFF_T_C_HLPR2(x) x
+#  define CURLINC_OFF_T_C_HLPR1(x) CURLINC_OFF_T_C_HLPR2(x)
+#  define CURL_OFF_T_C(Val)  CURLINC_OFF_T_C_HLPR1(Val) ## \
+                             CURLINC_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_T)
+#  define CURL_OFF_TU_C(Val) CURLINC_OFF_T_C_HLPR1(Val) ## \
+                             CURLINC_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_TU)
+#else
+#  ifdef CURL_ISOCPP
+#    define CURLINC_OFF_T_C_HLPR2(Val,Suffix) Val ## Suffix
+#  else
+#    define CURLINC_OFF_T_C_HLPR2(Val,Suffix) Val/**/Suffix
+#  endif
+#  define CURLINC_OFF_T_C_HLPR1(Val,Suffix) CURLINC_OFF_T_C_HLPR2(Val,Suffix)
+#  define CURL_OFF_T_C(Val)  CURLINC_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_T)
+#  define CURL_OFF_TU_C(Val) CURLINC_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_TU)
+#endif
+
+#endif /* CURLINC_SYSTEM_H */
diff --git a/libs/curl/include/curl/typecheck-gcc.h b/libs/curl/include/curl/typecheck-gcc.h
index 46f92d728be99e49927d8adeb6af6b6b39a507ec..03c84fc85b2d5b0de873ea02857e556589938b04 100644
--- a/libs/curl/include/curl/typecheck-gcc.h
+++ b/libs/curl/include/curl/typecheck-gcc.h
@@ -1,5 +1,5 @@
-#ifndef __CURL_TYPECHECK_GCC_H
-#define __CURL_TYPECHECK_GCC_H
+#ifndef CURLINC_TYPECHECK_GCC_H
+#define CURLINC_TYPECHECK_GCC_H
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
@@ -7,11 +7,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
@@ -25,10 +25,10 @@
 /* wraps curl_easy_setopt() with typechecking */
 
 /* To add a new kind of warning, add an
- *   if(_curl_is_sometype_option(_curl_opt))
- *     if(!_curl_is_sometype(value))
+ *   if(curlcheck_sometype_option(_curl_opt))
+ *     if(!curlcheck_sometype(value))
  *       _curl_easy_setopt_err_sometype();
- * block and define _curl_is_sometype_option, _curl_is_sometype and
+ * block and define curlcheck_sometype_option, curlcheck_sometype and
  * _curl_easy_setopt_err_sometype below
  *
  * NOTE: We use two nested 'if' statements here instead of the && operator, in
@@ -38,99 +38,115 @@
  * To add an option that uses the same type as an existing option, you'll just
  * need to extend the appropriate _curl_*_option macro
  */
-#define curl_easy_setopt(handle, option, value)                               \
-__extension__ ({                                                              \
-  __typeof__ (option) _curl_opt = option;                                     \
-  if (__builtin_constant_p(_curl_opt)) {                                      \
-    if (_curl_is_long_option(_curl_opt))                                      \
-      if (!_curl_is_long(value))                                              \
-        _curl_easy_setopt_err_long();                                         \
-    if (_curl_is_off_t_option(_curl_opt))                                     \
-      if (!_curl_is_off_t(value))                                             \
-        _curl_easy_setopt_err_curl_off_t();                                   \
-    if (_curl_is_string_option(_curl_opt))                                    \
-      if (!_curl_is_string(value))                                            \
-        _curl_easy_setopt_err_string();                                       \
-    if (_curl_is_write_cb_option(_curl_opt))                                  \
-      if (!_curl_is_write_cb(value))                                          \
-        _curl_easy_setopt_err_write_callback();                               \
-    if ((_curl_opt) == CURLOPT_READFUNCTION)                                  \
-      if (!_curl_is_read_cb(value))                                           \
-        _curl_easy_setopt_err_read_cb();                                      \
-    if ((_curl_opt) == CURLOPT_IOCTLFUNCTION)                                 \
-      if (!_curl_is_ioctl_cb(value))                                          \
-        _curl_easy_setopt_err_ioctl_cb();                                     \
-    if ((_curl_opt) == CURLOPT_SOCKOPTFUNCTION)                               \
-      if (!_curl_is_sockopt_cb(value))                                        \
-        _curl_easy_setopt_err_sockopt_cb();                                   \
-    if ((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION)                            \
-      if (!_curl_is_opensocket_cb(value))                                     \
-        _curl_easy_setopt_err_opensocket_cb();                                \
-    if ((_curl_opt) == CURLOPT_PROGRESSFUNCTION)                              \
-      if (!_curl_is_progress_cb(value))                                       \
-        _curl_easy_setopt_err_progress_cb();                                  \
-    if ((_curl_opt) == CURLOPT_DEBUGFUNCTION)                                 \
-      if (!_curl_is_debug_cb(value))                                          \
-        _curl_easy_setopt_err_debug_cb();                                     \
-    if ((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION)                              \
-      if (!_curl_is_ssl_ctx_cb(value))                                        \
-        _curl_easy_setopt_err_ssl_ctx_cb();                                   \
-    if (_curl_is_conv_cb_option(_curl_opt))                                   \
-      if (!_curl_is_conv_cb(value))                                           \
-        _curl_easy_setopt_err_conv_cb();                                      \
-    if ((_curl_opt) == CURLOPT_SEEKFUNCTION)                                  \
-      if (!_curl_is_seek_cb(value))                                           \
-        _curl_easy_setopt_err_seek_cb();                                      \
-    if (_curl_is_cb_data_option(_curl_opt))                                   \
-      if (!_curl_is_cb_data(value))                                           \
-        _curl_easy_setopt_err_cb_data();                                      \
-    if ((_curl_opt) == CURLOPT_ERRORBUFFER)                                   \
-      if (!_curl_is_error_buffer(value))                                      \
-        _curl_easy_setopt_err_error_buffer();                                 \
-    if ((_curl_opt) == CURLOPT_STDERR)                                        \
-      if (!_curl_is_FILE(value))                                              \
-        _curl_easy_setopt_err_FILE();                                         \
-    if (_curl_is_postfields_option(_curl_opt))                                \
-      if (!_curl_is_postfields(value))                                        \
-        _curl_easy_setopt_err_postfields();                                   \
-    if ((_curl_opt) == CURLOPT_HTTPPOST)                                      \
-      if (!_curl_is_arr((value), struct curl_httppost))                       \
-        _curl_easy_setopt_err_curl_httpost();                                 \
-    if (_curl_is_slist_option(_curl_opt))                                     \
-      if (!_curl_is_arr((value), struct curl_slist))                          \
-        _curl_easy_setopt_err_curl_slist();                                   \
-    if ((_curl_opt) == CURLOPT_SHARE)                                         \
-      if (!_curl_is_ptr((value), CURLSH))                                     \
-        _curl_easy_setopt_err_CURLSH();                                       \
-  }                                                                           \
-  curl_easy_setopt(handle, _curl_opt, value);                                 \
-})
+#define curl_easy_setopt(handle, option, value)                         \
+  __extension__({                                                       \
+      __typeof__(option) _curl_opt = option;                            \
+      if(__builtin_constant_p(_curl_opt)) {                             \
+        if(curlcheck_long_option(_curl_opt))                            \
+          if(!curlcheck_long(value))                                    \
+            _curl_easy_setopt_err_long();                               \
+        if(curlcheck_off_t_option(_curl_opt))                           \
+          if(!curlcheck_off_t(value))                                   \
+            _curl_easy_setopt_err_curl_off_t();                         \
+        if(curlcheck_string_option(_curl_opt))                          \
+          if(!curlcheck_string(value))                                  \
+            _curl_easy_setopt_err_string();                             \
+        if(curlcheck_write_cb_option(_curl_opt))                        \
+          if(!curlcheck_write_cb(value))                                \
+            _curl_easy_setopt_err_write_callback();                     \
+        if((_curl_opt) == CURLOPT_RESOLVER_START_FUNCTION)              \
+          if(!curlcheck_resolver_start_callback(value))                 \
+            _curl_easy_setopt_err_resolver_start_callback();            \
+        if((_curl_opt) == CURLOPT_READFUNCTION)                         \
+          if(!curlcheck_read_cb(value))                                 \
+            _curl_easy_setopt_err_read_cb();                            \
+        if((_curl_opt) == CURLOPT_IOCTLFUNCTION)                        \
+          if(!curlcheck_ioctl_cb(value))                                \
+            _curl_easy_setopt_err_ioctl_cb();                           \
+        if((_curl_opt) == CURLOPT_SOCKOPTFUNCTION)                      \
+          if(!curlcheck_sockopt_cb(value))                              \
+            _curl_easy_setopt_err_sockopt_cb();                         \
+        if((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION)                   \
+          if(!curlcheck_opensocket_cb(value))                           \
+            _curl_easy_setopt_err_opensocket_cb();                      \
+        if((_curl_opt) == CURLOPT_PROGRESSFUNCTION)                     \
+          if(!curlcheck_progress_cb(value))                             \
+            _curl_easy_setopt_err_progress_cb();                        \
+        if((_curl_opt) == CURLOPT_DEBUGFUNCTION)                        \
+          if(!curlcheck_debug_cb(value))                                \
+            _curl_easy_setopt_err_debug_cb();                           \
+        if((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION)                     \
+          if(!curlcheck_ssl_ctx_cb(value))                              \
+            _curl_easy_setopt_err_ssl_ctx_cb();                         \
+        if(curlcheck_conv_cb_option(_curl_opt))                         \
+          if(!curlcheck_conv_cb(value))                                 \
+            _curl_easy_setopt_err_conv_cb();                            \
+        if((_curl_opt) == CURLOPT_SEEKFUNCTION)                         \
+          if(!curlcheck_seek_cb(value))                                 \
+            _curl_easy_setopt_err_seek_cb();                            \
+        if(curlcheck_cb_data_option(_curl_opt))                         \
+          if(!curlcheck_cb_data(value))                                 \
+            _curl_easy_setopt_err_cb_data();                            \
+        if((_curl_opt) == CURLOPT_ERRORBUFFER)                          \
+          if(!curlcheck_error_buffer(value))                            \
+            _curl_easy_setopt_err_error_buffer();                       \
+        if((_curl_opt) == CURLOPT_STDERR)                               \
+          if(!curlcheck_FILE(value))                                    \
+            _curl_easy_setopt_err_FILE();                               \
+        if(curlcheck_postfields_option(_curl_opt))                      \
+          if(!curlcheck_postfields(value))                              \
+            _curl_easy_setopt_err_postfields();                         \
+        if((_curl_opt) == CURLOPT_HTTPPOST)                             \
+          if(!curlcheck_arr((value), struct curl_httppost))             \
+            _curl_easy_setopt_err_curl_httpost();                       \
+        if((_curl_opt) == CURLOPT_MIMEPOST)                             \
+          if(!curlcheck_ptr((value), curl_mime))                        \
+            _curl_easy_setopt_err_curl_mimepost();                      \
+        if(curlcheck_slist_option(_curl_opt))                           \
+          if(!curlcheck_arr((value), struct curl_slist))                \
+            _curl_easy_setopt_err_curl_slist();                         \
+        if((_curl_opt) == CURLOPT_SHARE)                                \
+          if(!curlcheck_ptr((value), CURLSH))                           \
+            _curl_easy_setopt_err_CURLSH();                             \
+      }                                                                 \
+      curl_easy_setopt(handle, _curl_opt, value);                       \
+    })
 
 /* wraps curl_easy_getinfo() with typechecking */
-/* FIXME: don't allow const pointers */
-#define curl_easy_getinfo(handle, info, arg)                                  \
-__extension__ ({                                                              \
-  __typeof__ (info) _curl_info = info;                                        \
-  if (__builtin_constant_p(_curl_info)) {                                     \
-    if (_curl_is_string_info(_curl_info))                                     \
-      if (!_curl_is_arr((arg), char *))                                       \
-        _curl_easy_getinfo_err_string();                                      \
-    if (_curl_is_long_info(_curl_info))                                       \
-      if (!_curl_is_arr((arg), long))                                         \
-        _curl_easy_getinfo_err_long();                                        \
-    if (_curl_is_double_info(_curl_info))                                     \
-      if (!_curl_is_arr((arg), double))                                       \
-        _curl_easy_getinfo_err_double();                                      \
-    if (_curl_is_slist_info(_curl_info))                                      \
-      if (!_curl_is_arr((arg), struct curl_slist *))                          \
-        _curl_easy_getinfo_err_curl_slist();                                  \
-  }                                                                           \
-  curl_easy_getinfo(handle, _curl_info, arg);                                 \
-})
-
-/* TODO: typechecking for curl_share_setopt() and curl_multi_setopt(),
- * for now just make sure that the functions are called with three
- * arguments
+#define curl_easy_getinfo(handle, info, arg)                            \
+  __extension__({                                                      \
+      __typeof__(info) _curl_info = info;                               \
+      if(__builtin_constant_p(_curl_info)) {                            \
+        if(curlcheck_string_info(_curl_info))                           \
+          if(!curlcheck_arr((arg), char *))                             \
+            _curl_easy_getinfo_err_string();                            \
+        if(curlcheck_long_info(_curl_info))                             \
+          if(!curlcheck_arr((arg), long))                               \
+            _curl_easy_getinfo_err_long();                              \
+        if(curlcheck_double_info(_curl_info))                           \
+          if(!curlcheck_arr((arg), double))                             \
+            _curl_easy_getinfo_err_double();                            \
+        if(curlcheck_slist_info(_curl_info))                            \
+          if(!curlcheck_arr((arg), struct curl_slist *))                \
+            _curl_easy_getinfo_err_curl_slist();                        \
+        if(curlcheck_tlssessioninfo_info(_curl_info))                   \
+          if(!curlcheck_arr((arg), struct curl_tlssessioninfo *))       \
+            _curl_easy_getinfo_err_curl_tlssesssioninfo();              \
+        if(curlcheck_certinfo_info(_curl_info))                         \
+          if(!curlcheck_arr((arg), struct curl_certinfo *))             \
+            _curl_easy_getinfo_err_curl_certinfo();                     \
+        if(curlcheck_socket_info(_curl_info))                           \
+          if(!curlcheck_arr((arg), curl_socket_t))                      \
+            _curl_easy_getinfo_err_curl_socket();                       \
+        if(curlcheck_off_t_info(_curl_info))                            \
+          if(!curlcheck_arr((arg), curl_off_t))                         \
+            _curl_easy_getinfo_err_curl_off_t();                        \
+      }                                                                 \
+      curl_easy_getinfo(handle, _curl_info, arg);                       \
+    })
+
+/*
+ * For now, just make sure that the functions are called with three arguments
  */
 #define curl_share_setopt(share,opt,param) curl_share_setopt(share,opt,param)
 #define curl_multi_setopt(handle,opt,param) curl_multi_setopt(handle,opt,param)
@@ -140,61 +156,84 @@ __extension__ ({                                                              \
  * functions */
 
 /* To define a new warning, use _CURL_WARNING(identifier, "message") */
-#define _CURL_WARNING(id, message)                                            \
-  static void __attribute__((warning(message))) __attribute__((unused))       \
-  __attribute__((noinline)) id(void) { __asm__(""); }
+#define CURLWARNING(id, message)                                        \
+  static void __attribute__((__warning__(message)))                     \
+  __attribute__((__unused__)) __attribute__((__noinline__))             \
+  id(void) { __asm__(""); }
 
-_CURL_WARNING(_curl_easy_setopt_err_long,
+CURLWARNING(_curl_easy_setopt_err_long,
   "curl_easy_setopt expects a long argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_curl_off_t,
+CURLWARNING(_curl_easy_setopt_err_curl_off_t,
   "curl_easy_setopt expects a curl_off_t argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_string,
-  "curl_easy_setopt expects a string (char* or char[]) argument for this option"
+CURLWARNING(_curl_easy_setopt_err_string,
+              "curl_easy_setopt expects a "
+              "string ('char *' or char[]) argument for this option"
   )
-_CURL_WARNING(_curl_easy_setopt_err_write_callback,
+CURLWARNING(_curl_easy_setopt_err_write_callback,
   "curl_easy_setopt expects a curl_write_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_read_cb,
+CURLWARNING(_curl_easy_setopt_err_resolver_start_callback,
+              "curl_easy_setopt expects a "
+              "curl_resolver_start_callback argument for this option"
+  )
+CURLWARNING(_curl_easy_setopt_err_read_cb,
   "curl_easy_setopt expects a curl_read_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_ioctl_cb,
+CURLWARNING(_curl_easy_setopt_err_ioctl_cb,
   "curl_easy_setopt expects a curl_ioctl_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_sockopt_cb,
+CURLWARNING(_curl_easy_setopt_err_sockopt_cb,
   "curl_easy_setopt expects a curl_sockopt_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_opensocket_cb,
-  "curl_easy_setopt expects a curl_opensocket_callback argument for this option"
+CURLWARNING(_curl_easy_setopt_err_opensocket_cb,
+              "curl_easy_setopt expects a "
+              "curl_opensocket_callback argument for this option"
   )
-_CURL_WARNING(_curl_easy_setopt_err_progress_cb,
+CURLWARNING(_curl_easy_setopt_err_progress_cb,
   "curl_easy_setopt expects a curl_progress_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_debug_cb,
+CURLWARNING(_curl_easy_setopt_err_debug_cb,
   "curl_easy_setopt expects a curl_debug_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_ssl_ctx_cb,
+CURLWARNING(_curl_easy_setopt_err_ssl_ctx_cb,
   "curl_easy_setopt expects a curl_ssl_ctx_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_conv_cb,
+CURLWARNING(_curl_easy_setopt_err_conv_cb,
   "curl_easy_setopt expects a curl_conv_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_seek_cb,
+CURLWARNING(_curl_easy_setopt_err_seek_cb,
   "curl_easy_setopt expects a curl_seek_callback argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_cb_data,
-  "curl_easy_setopt expects a private data pointer as argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_error_buffer,
-  "curl_easy_setopt expects a char buffer of CURL_ERROR_SIZE as argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_FILE,
-  "curl_easy_setopt expects a FILE* argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_postfields,
-  "curl_easy_setopt expects a void* or char* argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_curl_httpost,
-  "curl_easy_setopt expects a struct curl_httppost* argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_curl_slist,
-  "curl_easy_setopt expects a struct curl_slist* argument for this option")
-_CURL_WARNING(_curl_easy_setopt_err_CURLSH,
+CURLWARNING(_curl_easy_setopt_err_cb_data,
+              "curl_easy_setopt expects a "
+              "private data pointer as argument for this option")
+CURLWARNING(_curl_easy_setopt_err_error_buffer,
+              "curl_easy_setopt expects a "
+              "char buffer of CURL_ERROR_SIZE as argument for this option")
+CURLWARNING(_curl_easy_setopt_err_FILE,
+  "curl_easy_setopt expects a 'FILE *' argument for this option")
+CURLWARNING(_curl_easy_setopt_err_postfields,
+  "curl_easy_setopt expects a 'void *' or 'char *' argument for this option")
+CURLWARNING(_curl_easy_setopt_err_curl_httpost,
+              "curl_easy_setopt expects a 'struct curl_httppost *' "
+              "argument for this option")
+CURLWARNING(_curl_easy_setopt_err_curl_mimepost,
+              "curl_easy_setopt expects a 'curl_mime *' "
+              "argument for this option")
+CURLWARNING(_curl_easy_setopt_err_curl_slist,
+  "curl_easy_setopt expects a 'struct curl_slist *' argument for this option")
+CURLWARNING(_curl_easy_setopt_err_CURLSH,
   "curl_easy_setopt expects a CURLSH* argument for this option")
 
-_CURL_WARNING(_curl_easy_getinfo_err_string,
-  "curl_easy_getinfo expects a pointer to char * for this info")
-_CURL_WARNING(_curl_easy_getinfo_err_long,
+CURLWARNING(_curl_easy_getinfo_err_string,
+  "curl_easy_getinfo expects a pointer to 'char *' for this info")
+CURLWARNING(_curl_easy_getinfo_err_long,
   "curl_easy_getinfo expects a pointer to long for this info")
-_CURL_WARNING(_curl_easy_getinfo_err_double,
+CURLWARNING(_curl_easy_getinfo_err_double,
   "curl_easy_getinfo expects a pointer to double for this info")
-_CURL_WARNING(_curl_easy_getinfo_err_curl_slist,
-  "curl_easy_getinfo expects a pointer to struct curl_slist * for this info")
+CURLWARNING(_curl_easy_getinfo_err_curl_slist,
+  "curl_easy_getinfo expects a pointer to 'struct curl_slist *' for this info")
+CURLWARNING(_curl_easy_getinfo_err_curl_tlssesssioninfo,
+              "curl_easy_getinfo expects a pointer to "
+              "'struct curl_tlssessioninfo *' for this info")
+CURLWARNING(_curl_easy_getinfo_err_curl_certinfo,
+              "curl_easy_getinfo expects a pointer to "
+              "'struct curl_certinfo *' for this info")
+CURLWARNING(_curl_easy_getinfo_err_curl_socket,
+  "curl_easy_getinfo expects a pointer to curl_socket_t for this info")
+CURLWARNING(_curl_easy_getinfo_err_curl_off_t,
+  "curl_easy_getinfo expects a pointer to curl_off_t for this info")
 
 /* groups of curl_easy_setops options that take the same type of argument */
 
@@ -205,131 +244,188 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist,
  */
 
 /* evaluates to true if option takes a long argument */
-#define _curl_is_long_option(option)                                          \
+#define curlcheck_long_option(option)                   \
   (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT)
 
-#define _curl_is_off_t_option(option)                                         \
+#define curlcheck_off_t_option(option)          \
   ((option) > CURLOPTTYPE_OFF_T)
 
 /* evaluates to true if option takes a char* argument */
-#define _curl_is_string_option(option)                                        \
-  ((option) == CURLOPT_URL ||                                                 \
-   (option) == CURLOPT_PROXY ||                                               \
-   (option) == CURLOPT_INTERFACE ||                                           \
-   (option) == CURLOPT_NETRC_FILE ||                                          \
-   (option) == CURLOPT_USERPWD ||                                             \
-   (option) == CURLOPT_USERNAME ||                                            \
-   (option) == CURLOPT_PASSWORD ||                                            \
-   (option) == CURLOPT_PROXYUSERPWD ||                                        \
-   (option) == CURLOPT_PROXYUSERNAME ||                                       \
-   (option) == CURLOPT_PROXYPASSWORD ||                                       \
-   (option) == CURLOPT_NOPROXY ||                                             \
+#define curlcheck_string_option(option)                                       \
+  ((option) == CURLOPT_ABSTRACT_UNIX_SOCKET ||                                \
    (option) == CURLOPT_ACCEPT_ENCODING ||                                     \
-   (option) == CURLOPT_REFERER ||                                             \
-   (option) == CURLOPT_USERAGENT ||                                           \
+   (option) == CURLOPT_ALTSVC ||                                              \
+   (option) == CURLOPT_CAINFO ||                                              \
+   (option) == CURLOPT_CAPATH ||                                              \
    (option) == CURLOPT_COOKIE ||                                              \
    (option) == CURLOPT_COOKIEFILE ||                                          \
    (option) == CURLOPT_COOKIEJAR ||                                           \
    (option) == CURLOPT_COOKIELIST ||                                          \
+   (option) == CURLOPT_CRLFILE ||                                             \
+   (option) == CURLOPT_CUSTOMREQUEST ||                                       \
+   (option) == CURLOPT_DEFAULT_PROTOCOL ||                                    \
+   (option) == CURLOPT_DNS_INTERFACE ||                                       \
+   (option) == CURLOPT_DNS_LOCAL_IP4 ||                                       \
+   (option) == CURLOPT_DNS_LOCAL_IP6 ||                                       \
+   (option) == CURLOPT_DNS_SERVERS ||                                         \
+   (option) == CURLOPT_DOH_URL ||                                             \
+   (option) == CURLOPT_EGDSOCKET ||                                           \
    (option) == CURLOPT_FTPPORT ||                                             \
-   (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER ||                             \
    (option) == CURLOPT_FTP_ACCOUNT ||                                         \
-   (option) == CURLOPT_RANGE ||                                               \
-   (option) == CURLOPT_CUSTOMREQUEST ||                                       \
-   (option) == CURLOPT_SSLCERT ||                                             \
-   (option) == CURLOPT_SSLCERTTYPE ||                                         \
-   (option) == CURLOPT_SSLKEY ||                                              \
-   (option) == CURLOPT_SSLKEYTYPE ||                                          \
+   (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER ||                             \
+   (option) == CURLOPT_INTERFACE ||                                           \
+   (option) == CURLOPT_ISSUERCERT ||                                          \
    (option) == CURLOPT_KEYPASSWD ||                                           \
-   (option) == CURLOPT_SSLENGINE ||                                           \
-   (option) == CURLOPT_CAINFO ||                                              \
-   (option) == CURLOPT_CAPATH ||                                              \
-   (option) == CURLOPT_RANDOM_FILE ||                                         \
-   (option) == CURLOPT_EGDSOCKET ||                                           \
-   (option) == CURLOPT_SSL_CIPHER_LIST ||                                     \
    (option) == CURLOPT_KRBLEVEL ||                                            \
-   (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 ||                             \
-   (option) == CURLOPT_SSH_PUBLIC_KEYFILE ||                                  \
-   (option) == CURLOPT_SSH_PRIVATE_KEYFILE ||                                 \
-   (option) == CURLOPT_CRLFILE ||                                             \
-   (option) == CURLOPT_ISSUERCERT ||                                          \
-   (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE ||                               \
-   (option) == CURLOPT_SSH_KNOWNHOSTS ||                                      \
+   (option) == CURLOPT_LOGIN_OPTIONS ||                                       \
+   (option) == CURLOPT_MAIL_AUTH ||                                           \
    (option) == CURLOPT_MAIL_FROM ||                                           \
+   (option) == CURLOPT_NETRC_FILE ||                                          \
+   (option) == CURLOPT_NOPROXY ||                                             \
+   (option) == CURLOPT_PASSWORD ||                                            \
+   (option) == CURLOPT_PINNEDPUBLICKEY ||                                     \
+   (option) == CURLOPT_PRE_PROXY ||                                           \
+   (option) == CURLOPT_PROXY ||                                               \
+   (option) == CURLOPT_PROXYPASSWORD ||                                       \
+   (option) == CURLOPT_PROXYUSERNAME ||                                       \
+   (option) == CURLOPT_PROXYUSERPWD ||                                        \
+   (option) == CURLOPT_PROXY_CAINFO ||                                        \
+   (option) == CURLOPT_PROXY_CAPATH ||                                        \
+   (option) == CURLOPT_PROXY_CRLFILE ||                                       \
+   (option) == CURLOPT_PROXY_KEYPASSWD ||                                     \
+   (option) == CURLOPT_PROXY_PINNEDPUBLICKEY ||                               \
+   (option) == CURLOPT_PROXY_SERVICE_NAME ||                                  \
+   (option) == CURLOPT_PROXY_SSLCERT ||                                       \
+   (option) == CURLOPT_PROXY_SSLCERTTYPE ||                                   \
+   (option) == CURLOPT_PROXY_SSLKEY ||                                        \
+   (option) == CURLOPT_PROXY_SSLKEYTYPE ||                                    \
+   (option) == CURLOPT_PROXY_SSL_CIPHER_LIST ||                               \
+   (option) == CURLOPT_PROXY_TLS13_CIPHERS ||                                 \
+   (option) == CURLOPT_PROXY_TLSAUTH_PASSWORD ||                              \
+   (option) == CURLOPT_PROXY_TLSAUTH_TYPE ||                                  \
+   (option) == CURLOPT_PROXY_TLSAUTH_USERNAME ||                              \
+   (option) == CURLOPT_RANDOM_FILE ||                                         \
+   (option) == CURLOPT_RANGE ||                                               \
+   (option) == CURLOPT_REFERER ||                                             \
+   (option) == CURLOPT_REQUEST_TARGET ||                                      \
    (option) == CURLOPT_RTSP_SESSION_ID ||                                     \
    (option) == CURLOPT_RTSP_STREAM_URI ||                                     \
    (option) == CURLOPT_RTSP_TRANSPORT ||                                      \
+   (option) == CURLOPT_SASL_AUTHZID ||                                        \
+   (option) == CURLOPT_SERVICE_NAME ||                                        \
+   (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE ||                               \
+   (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 ||                             \
+   (option) == CURLOPT_SSH_KNOWNHOSTS ||                                      \
+   (option) == CURLOPT_SSH_PRIVATE_KEYFILE ||                                 \
+   (option) == CURLOPT_SSH_PUBLIC_KEYFILE ||                                  \
+   (option) == CURLOPT_SSLCERT ||                                             \
+   (option) == CURLOPT_SSLCERTTYPE ||                                         \
+   (option) == CURLOPT_SSLENGINE ||                                           \
+   (option) == CURLOPT_SSLKEY ||                                              \
+   (option) == CURLOPT_SSLKEYTYPE ||                                          \
+   (option) == CURLOPT_SSL_CIPHER_LIST ||                                     \
+   (option) == CURLOPT_TLS13_CIPHERS ||                                       \
+   (option) == CURLOPT_TLSAUTH_PASSWORD ||                                    \
+   (option) == CURLOPT_TLSAUTH_TYPE ||                                        \
+   (option) == CURLOPT_TLSAUTH_USERNAME ||                                    \
+   (option) == CURLOPT_UNIX_SOCKET_PATH ||                                    \
+   (option) == CURLOPT_URL ||                                                 \
+   (option) == CURLOPT_USERAGENT ||                                           \
+   (option) == CURLOPT_USERNAME ||                                            \
+   (option) == CURLOPT_USERPWD ||                                             \
+   (option) == CURLOPT_XOAUTH2_BEARER ||                                      \
    0)
 
 /* evaluates to true if option takes a curl_write_callback argument */
-#define _curl_is_write_cb_option(option)                                      \
-  ((option) == CURLOPT_HEADERFUNCTION ||                                      \
+#define curlcheck_write_cb_option(option)                               \
+  ((option) == CURLOPT_HEADERFUNCTION ||                                \
    (option) == CURLOPT_WRITEFUNCTION)
 
 /* evaluates to true if option takes a curl_conv_callback argument */
-#define _curl_is_conv_cb_option(option)                                       \
-  ((option) == CURLOPT_CONV_TO_NETWORK_FUNCTION ||                            \
-   (option) == CURLOPT_CONV_FROM_NETWORK_FUNCTION ||                          \
+#define curlcheck_conv_cb_option(option)                                \
+  ((option) == CURLOPT_CONV_TO_NETWORK_FUNCTION ||                      \
+   (option) == CURLOPT_CONV_FROM_NETWORK_FUNCTION ||                    \
    (option) == CURLOPT_CONV_FROM_UTF8_FUNCTION)
 
 /* evaluates to true if option takes a data argument to pass to a callback */
-#define _curl_is_cb_data_option(option)                                       \
-  ((option) == CURLOPT_WRITEDATA ||                                           \
-   (option) == CURLOPT_READDATA ||                                            \
+#define curlcheck_cb_data_option(option)                                      \
+  ((option) == CURLOPT_CHUNK_DATA ||                                          \
+   (option) == CURLOPT_CLOSESOCKETDATA ||                                     \
+   (option) == CURLOPT_DEBUGDATA ||                                           \
+   (option) == CURLOPT_FNMATCH_DATA ||                                        \
+   (option) == CURLOPT_HEADERDATA ||                                          \
+   (option) == CURLOPT_INTERLEAVEDATA ||                                      \
    (option) == CURLOPT_IOCTLDATA ||                                           \
-   (option) == CURLOPT_SOCKOPTDATA ||                                         \
    (option) == CURLOPT_OPENSOCKETDATA ||                                      \
+   (option) == CURLOPT_PRIVATE ||                                             \
    (option) == CURLOPT_PROGRESSDATA ||                                        \
-   (option) == CURLOPT_WRITEHEADER ||                                         \
-   (option) == CURLOPT_DEBUGDATA ||                                           \
-   (option) == CURLOPT_SSL_CTX_DATA ||                                        \
+   (option) == CURLOPT_READDATA ||                                            \
    (option) == CURLOPT_SEEKDATA ||                                            \
-   (option) == CURLOPT_PRIVATE ||                                             \
+   (option) == CURLOPT_SOCKOPTDATA ||                                         \
    (option) == CURLOPT_SSH_KEYDATA ||                                         \
-   (option) == CURLOPT_INTERLEAVEDATA ||                                      \
-   (option) == CURLOPT_CHUNK_DATA ||                                          \
-   (option) == CURLOPT_FNMATCH_DATA ||                                        \
+   (option) == CURLOPT_SSL_CTX_DATA ||                                        \
+   (option) == CURLOPT_WRITEDATA ||                                           \
+   (option) == CURLOPT_RESOLVER_START_DATA ||                                 \
+   (option) == CURLOPT_TRAILERDATA ||                                         \
    0)
 
 /* evaluates to true if option takes a POST data argument (void* or char*) */
-#define _curl_is_postfields_option(option)                                    \
+#define curlcheck_postfields_option(option)                                   \
   ((option) == CURLOPT_POSTFIELDS ||                                          \
    (option) == CURLOPT_COPYPOSTFIELDS ||                                      \
    0)
 
 /* evaluates to true if option takes a struct curl_slist * argument */
-#define _curl_is_slist_option(option)                                         \
-  ((option) == CURLOPT_HTTPHEADER ||                                          \
-   (option) == CURLOPT_HTTP200ALIASES ||                                      \
-   (option) == CURLOPT_QUOTE ||                                               \
+#define curlcheck_slist_option(option)                                        \
+  ((option) == CURLOPT_HTTP200ALIASES ||                                      \
+   (option) == CURLOPT_HTTPHEADER ||                                          \
+   (option) == CURLOPT_MAIL_RCPT ||                                           \
    (option) == CURLOPT_POSTQUOTE ||                                           \
    (option) == CURLOPT_PREQUOTE ||                                            \
+   (option) == CURLOPT_PROXYHEADER ||                                         \
+   (option) == CURLOPT_QUOTE ||                                               \
+   (option) == CURLOPT_RESOLVE ||                                             \
    (option) == CURLOPT_TELNETOPTIONS ||                                       \
-   (option) == CURLOPT_MAIL_RCPT ||                                           \
+   (option) == CURLOPT_CONNECT_TO ||                                          \
    0)
 
 /* groups of curl_easy_getinfo infos that take the same type of argument */
 
 /* evaluates to true if info expects a pointer to char * argument */
-#define _curl_is_string_info(info)                                            \
+#define curlcheck_string_info(info)                     \
   (CURLINFO_STRING < (info) && (info) < CURLINFO_LONG)
 
 /* evaluates to true if info expects a pointer to long argument */
-#define _curl_is_long_info(info)                                              \
+#define curlcheck_long_info(info)                       \
   (CURLINFO_LONG < (info) && (info) < CURLINFO_DOUBLE)
 
 /* evaluates to true if info expects a pointer to double argument */
-#define _curl_is_double_info(info)                                            \
+#define curlcheck_double_info(info)                     \
   (CURLINFO_DOUBLE < (info) && (info) < CURLINFO_SLIST)
 
 /* true if info expects a pointer to struct curl_slist * argument */
-#define _curl_is_slist_info(info)                                             \
-  (CURLINFO_SLIST < (info))
+#define curlcheck_slist_info(info)                                      \
+  (((info) == CURLINFO_SSL_ENGINES) || ((info) == CURLINFO_COOKIELIST))
+
+/* true if info expects a pointer to struct curl_tlssessioninfo * argument */
+#define curlcheck_tlssessioninfo_info(info)                              \
+  (((info) == CURLINFO_TLS_SSL_PTR) || ((info) == CURLINFO_TLS_SESSION))
+
+/* true if info expects a pointer to struct curl_certinfo * argument */
+#define curlcheck_certinfo_info(info) ((info) == CURLINFO_CERTINFO)
+
+/* true if info expects a pointer to struct curl_socket_t argument */
+#define curlcheck_socket_info(info)                     \
+  (CURLINFO_SOCKET < (info) && (info) < CURLINFO_OFF_T)
+
+/* true if info expects a pointer to curl_off_t argument */
+#define curlcheck_off_t_info(info)              \
+  (CURLINFO_OFF_T < (info))
 
 
 /* typecheck helpers -- check whether given expression has requested type*/
 
-/* For pointers, you can use the _curl_is_ptr/_curl_is_arr macros,
+/* For pointers, you can use the curlcheck_ptr/curlcheck_arr macros,
  * otherwise define a new macro. Search for __builtin_types_compatible_p
  * in the GCC manual.
  * NOTE: these macros MUST NOT EVALUATE their arguments! The argument is
@@ -338,36 +434,36 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist,
  * == or whatsoever.
  */
 
-/* XXX: should evaluate to true iff expr is a pointer */
-#define _curl_is_any_ptr(expr)                                                \
-  (sizeof(expr) == sizeof(void*))
+/* XXX: should evaluate to true if expr is a pointer */
+#define curlcheck_any_ptr(expr)                 \
+  (sizeof(expr) == sizeof(void *))
 
 /* evaluates to true if expr is NULL */
 /* XXX: must not evaluate expr, so this check is not accurate */
-#define _curl_is_NULL(expr)                                                   \
+#define curlcheck_NULL(expr)                                            \
   (__builtin_types_compatible_p(__typeof__(expr), __typeof__(NULL)))
 
 /* evaluates to true if expr is type*, const type* or NULL */
-#define _curl_is_ptr(expr, type)                                              \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), type *) ||                  \
+#define curlcheck_ptr(expr, type)                                       \
+  (curlcheck_NULL(expr) ||                                              \
+   __builtin_types_compatible_p(__typeof__(expr), type *) ||            \
    __builtin_types_compatible_p(__typeof__(expr), const type *))
 
 /* evaluates to true if expr is one of type[], type*, NULL or const type* */
-#define _curl_is_arr(expr, type)                                              \
-  (_curl_is_ptr((expr), type) ||                                              \
+#define curlcheck_arr(expr, type)                                       \
+  (curlcheck_ptr((expr), type) ||                                       \
    __builtin_types_compatible_p(__typeof__(expr), type []))
 
 /* evaluates to true if expr is a string */
-#define _curl_is_string(expr)                                                 \
-  (_curl_is_arr((expr), char) ||                                              \
-   _curl_is_arr((expr), signed char) ||                                       \
-   _curl_is_arr((expr), unsigned char))
+#define curlcheck_string(expr)                                          \
+  (curlcheck_arr((expr), char) ||                                       \
+   curlcheck_arr((expr), signed char) ||                                \
+   curlcheck_arr((expr), unsigned char))
 
 /* evaluates to true if expr is a long (no matter the signedness)
  * XXX: for now, int is also accepted (and therefore short and char, which
  * are promoted to int when passed to a variadic function) */
-#define _curl_is_long(expr)                                                   \
+#define curlcheck_long(expr)                                                  \
   (__builtin_types_compatible_p(__typeof__(expr), long) ||                    \
    __builtin_types_compatible_p(__typeof__(expr), signed long) ||             \
    __builtin_types_compatible_p(__typeof__(expr), unsigned long) ||           \
@@ -382,175 +478,194 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist,
    __builtin_types_compatible_p(__typeof__(expr), unsigned char))
 
 /* evaluates to true if expr is of type curl_off_t */
-#define _curl_is_off_t(expr)                                                  \
+#define curlcheck_off_t(expr)                                   \
   (__builtin_types_compatible_p(__typeof__(expr), curl_off_t))
 
 /* evaluates to true if expr is abuffer suitable for CURLOPT_ERRORBUFFER */
 /* XXX: also check size of an char[] array? */
-#define _curl_is_error_buffer(expr)                                           \
-  (__builtin_types_compatible_p(__typeof__(expr), char *) ||                  \
+#define curlcheck_error_buffer(expr)                                    \
+  (curlcheck_NULL(expr) ||                                              \
+   __builtin_types_compatible_p(__typeof__(expr), char *) ||            \
    __builtin_types_compatible_p(__typeof__(expr), char[]))
 
 /* evaluates to true if expr is of type (const) void* or (const) FILE* */
 #if 0
-#define _curl_is_cb_data(expr)                                                \
-  (_curl_is_ptr((expr), void) ||                                              \
-   _curl_is_ptr((expr), FILE))
+#define curlcheck_cb_data(expr)                                         \
+  (curlcheck_ptr((expr), void) ||                                       \
+   curlcheck_ptr((expr), FILE))
 #else /* be less strict */
-#define _curl_is_cb_data(expr)                                                \
-  _curl_is_any_ptr(expr)
+#define curlcheck_cb_data(expr)                 \
+  curlcheck_any_ptr(expr)
 #endif
 
 /* evaluates to true if expr is of type FILE* */
-#define _curl_is_FILE(expr)                                                   \
-  (__builtin_types_compatible_p(__typeof__(expr), FILE *))
+#define curlcheck_FILE(expr)                                            \
+  (curlcheck_NULL(expr) ||                                              \
+   (__builtin_types_compatible_p(__typeof__(expr), FILE *)))
 
 /* evaluates to true if expr can be passed as POST data (void* or char*) */
-#define _curl_is_postfields(expr)                                             \
-  (_curl_is_ptr((expr), void) ||                                              \
-   _curl_is_arr((expr), char))
+#define curlcheck_postfields(expr)                                      \
+  (curlcheck_ptr((expr), void) ||                                       \
+   curlcheck_arr((expr), char) ||                                       \
+   curlcheck_arr((expr), unsigned char))
 
-/* FIXME: the whole callback checking is messy...
- * The idea is to tolerate char vs. void and const vs. not const
- * pointers in arguments at least
- */
 /* helper: __builtin_types_compatible_p distinguishes between functions and
  * function pointers, hide it */
-#define _curl_callback_compatible(func, type)                                 \
-  (__builtin_types_compatible_p(__typeof__(func), type) ||                    \
-   __builtin_types_compatible_p(__typeof__(func), type*))
+#define curlcheck_cb_compatible(func, type)                             \
+  (__builtin_types_compatible_p(__typeof__(func), type) ||              \
+   __builtin_types_compatible_p(__typeof__(func) *, type))
+
+/* evaluates to true if expr is of type curl_resolver_start_callback */
+#define curlcheck_resolver_start_callback(expr)       \
+  (curlcheck_NULL(expr) || \
+   curlcheck_cb_compatible((expr), curl_resolver_start_callback))
 
 /* evaluates to true if expr is of type curl_read_callback or "similar" */
-#define _curl_is_read_cb(expr)                                          \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), __typeof__(fread)) ||       \
-   __builtin_types_compatible_p(__typeof__(expr), curl_read_callback) ||      \
-   _curl_callback_compatible((expr), _curl_read_callback1) ||                 \
-   _curl_callback_compatible((expr), _curl_read_callback2) ||                 \
-   _curl_callback_compatible((expr), _curl_read_callback3) ||                 \
-   _curl_callback_compatible((expr), _curl_read_callback4) ||                 \
-   _curl_callback_compatible((expr), _curl_read_callback5) ||                 \
-   _curl_callback_compatible((expr), _curl_read_callback6))
-typedef size_t (_curl_read_callback1)(char *, size_t, size_t, void*);
-typedef size_t (_curl_read_callback2)(char *, size_t, size_t, const void*);
-typedef size_t (_curl_read_callback3)(char *, size_t, size_t, FILE*);
-typedef size_t (_curl_read_callback4)(void *, size_t, size_t, void*);
-typedef size_t (_curl_read_callback5)(void *, size_t, size_t, const void*);
-typedef size_t (_curl_read_callback6)(void *, size_t, size_t, FILE*);
+#define curlcheck_read_cb(expr)                                         \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), __typeof__(fread) *) ||              \
+   curlcheck_cb_compatible((expr), curl_read_callback) ||               \
+   curlcheck_cb_compatible((expr), _curl_read_callback1) ||             \
+   curlcheck_cb_compatible((expr), _curl_read_callback2) ||             \
+   curlcheck_cb_compatible((expr), _curl_read_callback3) ||             \
+   curlcheck_cb_compatible((expr), _curl_read_callback4) ||             \
+   curlcheck_cb_compatible((expr), _curl_read_callback5) ||             \
+   curlcheck_cb_compatible((expr), _curl_read_callback6))
+typedef size_t (*_curl_read_callback1)(char *, size_t, size_t, void *);
+typedef size_t (*_curl_read_callback2)(char *, size_t, size_t, const void *);
+typedef size_t (*_curl_read_callback3)(char *, size_t, size_t, FILE *);
+typedef size_t (*_curl_read_callback4)(void *, size_t, size_t, void *);
+typedef size_t (*_curl_read_callback5)(void *, size_t, size_t, const void *);
+typedef size_t (*_curl_read_callback6)(void *, size_t, size_t, FILE *);
 
 /* evaluates to true if expr is of type curl_write_callback or "similar" */
-#define _curl_is_write_cb(expr)                                               \
-  (_curl_is_read_cb(expr) ||                                            \
-   __builtin_types_compatible_p(__typeof__(expr), __typeof__(fwrite)) ||      \
-   __builtin_types_compatible_p(__typeof__(expr), curl_write_callback) ||     \
-   _curl_callback_compatible((expr), _curl_write_callback1) ||                \
-   _curl_callback_compatible((expr), _curl_write_callback2) ||                \
-   _curl_callback_compatible((expr), _curl_write_callback3) ||                \
-   _curl_callback_compatible((expr), _curl_write_callback4) ||                \
-   _curl_callback_compatible((expr), _curl_write_callback5) ||                \
-   _curl_callback_compatible((expr), _curl_write_callback6))
-typedef size_t (_curl_write_callback1)(const char *, size_t, size_t, void*);
-typedef size_t (_curl_write_callback2)(const char *, size_t, size_t,
-                                       const void*);
-typedef size_t (_curl_write_callback3)(const char *, size_t, size_t, FILE*);
-typedef size_t (_curl_write_callback4)(const void *, size_t, size_t, void*);
-typedef size_t (_curl_write_callback5)(const void *, size_t, size_t,
-                                       const void*);
-typedef size_t (_curl_write_callback6)(const void *, size_t, size_t, FILE*);
+#define curlcheck_write_cb(expr)                                        \
+  (curlcheck_read_cb(expr) ||                                           \
+   curlcheck_cb_compatible((expr), __typeof__(fwrite) *) ||             \
+   curlcheck_cb_compatible((expr), curl_write_callback) ||              \
+   curlcheck_cb_compatible((expr), _curl_write_callback1) ||            \
+   curlcheck_cb_compatible((expr), _curl_write_callback2) ||            \
+   curlcheck_cb_compatible((expr), _curl_write_callback3) ||            \
+   curlcheck_cb_compatible((expr), _curl_write_callback4) ||            \
+   curlcheck_cb_compatible((expr), _curl_write_callback5) ||            \
+   curlcheck_cb_compatible((expr), _curl_write_callback6))
+typedef size_t (*_curl_write_callback1)(const char *, size_t, size_t, void *);
+typedef size_t (*_curl_write_callback2)(const char *, size_t, size_t,
+                                       const void *);
+typedef size_t (*_curl_write_callback3)(const char *, size_t, size_t, FILE *);
+typedef size_t (*_curl_write_callback4)(const void *, size_t, size_t, void *);
+typedef size_t (*_curl_write_callback5)(const void *, size_t, size_t,
+                                       const void *);
+typedef size_t (*_curl_write_callback6)(const void *, size_t, size_t, FILE *);
 
 /* evaluates to true if expr is of type curl_ioctl_callback or "similar" */
-#define _curl_is_ioctl_cb(expr)                                         \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_ioctl_callback) ||     \
-   _curl_callback_compatible((expr), _curl_ioctl_callback1) ||                \
-   _curl_callback_compatible((expr), _curl_ioctl_callback2) ||                \
-   _curl_callback_compatible((expr), _curl_ioctl_callback3) ||                \
-   _curl_callback_compatible((expr), _curl_ioctl_callback4))
-typedef curlioerr (_curl_ioctl_callback1)(CURL *, int, void*);
-typedef curlioerr (_curl_ioctl_callback2)(CURL *, int, const void*);
-typedef curlioerr (_curl_ioctl_callback3)(CURL *, curliocmd, void*);
-typedef curlioerr (_curl_ioctl_callback4)(CURL *, curliocmd, const void*);
+#define curlcheck_ioctl_cb(expr)                                        \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_ioctl_callback) ||              \
+   curlcheck_cb_compatible((expr), _curl_ioctl_callback1) ||            \
+   curlcheck_cb_compatible((expr), _curl_ioctl_callback2) ||            \
+   curlcheck_cb_compatible((expr), _curl_ioctl_callback3) ||            \
+   curlcheck_cb_compatible((expr), _curl_ioctl_callback4))
+typedef curlioerr (*_curl_ioctl_callback1)(CURL *, int, void *);
+typedef curlioerr (*_curl_ioctl_callback2)(CURL *, int, const void *);
+typedef curlioerr (*_curl_ioctl_callback3)(CURL *, curliocmd, void *);
+typedef curlioerr (*_curl_ioctl_callback4)(CURL *, curliocmd, const void *);
 
 /* evaluates to true if expr is of type curl_sockopt_callback or "similar" */
-#define _curl_is_sockopt_cb(expr)                                       \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_sockopt_callback) ||   \
-   _curl_callback_compatible((expr), _curl_sockopt_callback1) ||              \
-   _curl_callback_compatible((expr), _curl_sockopt_callback2))
-typedef int (_curl_sockopt_callback1)(void *, curl_socket_t, curlsocktype);
-typedef int (_curl_sockopt_callback2)(const void *, curl_socket_t,
+#define curlcheck_sockopt_cb(expr)                                      \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_sockopt_callback) ||            \
+   curlcheck_cb_compatible((expr), _curl_sockopt_callback1) ||          \
+   curlcheck_cb_compatible((expr), _curl_sockopt_callback2))
+typedef int (*_curl_sockopt_callback1)(void *, curl_socket_t, curlsocktype);
+typedef int (*_curl_sockopt_callback2)(const void *, curl_socket_t,
                                       curlsocktype);
 
-/* evaluates to true if expr is of type curl_opensocket_callback or "similar" */
-#define _curl_is_opensocket_cb(expr)                                    \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_opensocket_callback) ||\
-   _curl_callback_compatible((expr), _curl_opensocket_callback1) ||           \
-   _curl_callback_compatible((expr), _curl_opensocket_callback2) ||           \
-   _curl_callback_compatible((expr), _curl_opensocket_callback3) ||           \
-   _curl_callback_compatible((expr), _curl_opensocket_callback4))
-typedef curl_socket_t (_curl_opensocket_callback1)
+/* evaluates to true if expr is of type curl_opensocket_callback or
+   "similar" */
+#define curlcheck_opensocket_cb(expr)                                   \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_opensocket_callback) ||         \
+   curlcheck_cb_compatible((expr), _curl_opensocket_callback1) ||       \
+   curlcheck_cb_compatible((expr), _curl_opensocket_callback2) ||       \
+   curlcheck_cb_compatible((expr), _curl_opensocket_callback3) ||       \
+   curlcheck_cb_compatible((expr), _curl_opensocket_callback4))
+typedef curl_socket_t (*_curl_opensocket_callback1)
   (void *, curlsocktype, struct curl_sockaddr *);
-typedef curl_socket_t (_curl_opensocket_callback2)
+typedef curl_socket_t (*_curl_opensocket_callback2)
   (void *, curlsocktype, const struct curl_sockaddr *);
-typedef curl_socket_t (_curl_opensocket_callback3)
+typedef curl_socket_t (*_curl_opensocket_callback3)
   (const void *, curlsocktype, struct curl_sockaddr *);
-typedef curl_socket_t (_curl_opensocket_callback4)
+typedef curl_socket_t (*_curl_opensocket_callback4)
   (const void *, curlsocktype, const struct curl_sockaddr *);
 
 /* evaluates to true if expr is of type curl_progress_callback or "similar" */
-#define _curl_is_progress_cb(expr)                                      \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_progress_callback) ||  \
-   _curl_callback_compatible((expr), _curl_progress_callback1) ||             \
-   _curl_callback_compatible((expr), _curl_progress_callback2))
-typedef int (_curl_progress_callback1)(void *,
+#define curlcheck_progress_cb(expr)                                     \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_progress_callback) ||           \
+   curlcheck_cb_compatible((expr), _curl_progress_callback1) ||         \
+   curlcheck_cb_compatible((expr), _curl_progress_callback2))
+typedef int (*_curl_progress_callback1)(void *,
     double, double, double, double);
-typedef int (_curl_progress_callback2)(const void *,
+typedef int (*_curl_progress_callback2)(const void *,
     double, double, double, double);
 
 /* evaluates to true if expr is of type curl_debug_callback or "similar" */
-#define _curl_is_debug_cb(expr)                                         \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_debug_callback) ||     \
-   _curl_callback_compatible((expr), _curl_debug_callback1) ||                \
-   _curl_callback_compatible((expr), _curl_debug_callback2) ||                \
-   _curl_callback_compatible((expr), _curl_debug_callback3) ||                \
-   _curl_callback_compatible((expr), _curl_debug_callback4))
-typedef int (_curl_debug_callback1) (CURL *,
+#define curlcheck_debug_cb(expr)                                        \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_debug_callback) ||              \
+   curlcheck_cb_compatible((expr), _curl_debug_callback1) ||            \
+   curlcheck_cb_compatible((expr), _curl_debug_callback2) ||            \
+   curlcheck_cb_compatible((expr), _curl_debug_callback3) ||            \
+   curlcheck_cb_compatible((expr), _curl_debug_callback4) ||            \
+   curlcheck_cb_compatible((expr), _curl_debug_callback5) ||            \
+   curlcheck_cb_compatible((expr), _curl_debug_callback6) ||            \
+   curlcheck_cb_compatible((expr), _curl_debug_callback7) ||            \
+   curlcheck_cb_compatible((expr), _curl_debug_callback8))
+typedef int (*_curl_debug_callback1) (CURL *,
     curl_infotype, char *, size_t, void *);
-typedef int (_curl_debug_callback2) (CURL *,
+typedef int (*_curl_debug_callback2) (CURL *,
     curl_infotype, char *, size_t, const void *);
-typedef int (_curl_debug_callback3) (CURL *,
+typedef int (*_curl_debug_callback3) (CURL *,
     curl_infotype, const char *, size_t, void *);
-typedef int (_curl_debug_callback4) (CURL *,
+typedef int (*_curl_debug_callback4) (CURL *,
     curl_infotype, const char *, size_t, const void *);
+typedef int (*_curl_debug_callback5) (CURL *,
+    curl_infotype, unsigned char *, size_t, void *);
+typedef int (*_curl_debug_callback6) (CURL *,
+    curl_infotype, unsigned char *, size_t, const void *);
+typedef int (*_curl_debug_callback7) (CURL *,
+    curl_infotype, const unsigned char *, size_t, void *);
+typedef int (*_curl_debug_callback8) (CURL *,
+    curl_infotype, const unsigned char *, size_t, const void *);
 
 /* evaluates to true if expr is of type curl_ssl_ctx_callback or "similar" */
 /* this is getting even messier... */
-#define _curl_is_ssl_ctx_cb(expr)                                       \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_ssl_ctx_callback) ||   \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback1) ||              \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback2) ||              \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback3) ||              \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback4) ||              \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback5) ||              \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback6) ||              \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback7) ||              \
-   _curl_callback_compatible((expr), _curl_ssl_ctx_callback8))
-typedef CURLcode (_curl_ssl_ctx_callback1)(CURL *, void *, void *);
-typedef CURLcode (_curl_ssl_ctx_callback2)(CURL *, void *, const void *);
-typedef CURLcode (_curl_ssl_ctx_callback3)(CURL *, const void *, void *);
-typedef CURLcode (_curl_ssl_ctx_callback4)(CURL *, const void *, const void *);
+#define curlcheck_ssl_ctx_cb(expr)                                      \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_ssl_ctx_callback) ||            \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback1) ||          \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback2) ||          \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback3) ||          \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback4) ||          \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback5) ||          \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback6) ||          \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback7) ||          \
+   curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback8))
+typedef CURLcode (*_curl_ssl_ctx_callback1)(CURL *, void *, void *);
+typedef CURLcode (*_curl_ssl_ctx_callback2)(CURL *, void *, const void *);
+typedef CURLcode (*_curl_ssl_ctx_callback3)(CURL *, const void *, void *);
+typedef CURLcode (*_curl_ssl_ctx_callback4)(CURL *, const void *,
+                                            const void *);
 #ifdef HEADER_SSL_H
 /* hack: if we included OpenSSL's ssl.h, we know about SSL_CTX
  * this will of course break if we're included before OpenSSL headers...
  */
-typedef CURLcode (_curl_ssl_ctx_callback5)(CURL *, SSL_CTX, void *);
-typedef CURLcode (_curl_ssl_ctx_callback6)(CURL *, SSL_CTX, const void *);
-typedef CURLcode (_curl_ssl_ctx_callback7)(CURL *, const SSL_CTX, void *);
-typedef CURLcode (_curl_ssl_ctx_callback8)(CURL *, const SSL_CTX, const void *);
+typedef CURLcode (*_curl_ssl_ctx_callback5)(CURL *, SSL_CTX, void *);
+typedef CURLcode (*_curl_ssl_ctx_callback6)(CURL *, SSL_CTX, const void *);
+typedef CURLcode (*_curl_ssl_ctx_callback7)(CURL *, const SSL_CTX, void *);
+typedef CURLcode (*_curl_ssl_ctx_callback8)(CURL *, const SSL_CTX,
+                                           const void *);
 #else
 typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback5;
 typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback6;
@@ -559,26 +674,26 @@ typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback8;
 #endif
 
 /* evaluates to true if expr is of type curl_conv_callback or "similar" */
-#define _curl_is_conv_cb(expr)                                          \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_conv_callback) ||      \
-   _curl_callback_compatible((expr), _curl_conv_callback1) ||                 \
-   _curl_callback_compatible((expr), _curl_conv_callback2) ||                 \
-   _curl_callback_compatible((expr), _curl_conv_callback3) ||                 \
-   _curl_callback_compatible((expr), _curl_conv_callback4))
+#define curlcheck_conv_cb(expr)                                         \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_conv_callback) ||               \
+   curlcheck_cb_compatible((expr), _curl_conv_callback1) ||             \
+   curlcheck_cb_compatible((expr), _curl_conv_callback2) ||             \
+   curlcheck_cb_compatible((expr), _curl_conv_callback3) ||             \
+   curlcheck_cb_compatible((expr), _curl_conv_callback4))
 typedef CURLcode (*_curl_conv_callback1)(char *, size_t length);
 typedef CURLcode (*_curl_conv_callback2)(const char *, size_t length);
 typedef CURLcode (*_curl_conv_callback3)(void *, size_t length);
 typedef CURLcode (*_curl_conv_callback4)(const void *, size_t length);
 
 /* evaluates to true if expr is of type curl_seek_callback or "similar" */
-#define _curl_is_seek_cb(expr)                                          \
-  (_curl_is_NULL(expr) ||                                                     \
-   __builtin_types_compatible_p(__typeof__(expr), curl_seek_callback) ||      \
-   _curl_callback_compatible((expr), _curl_seek_callback1) ||                 \
-   _curl_callback_compatible((expr), _curl_seek_callback2))
+#define curlcheck_seek_cb(expr)                                         \
+  (curlcheck_NULL(expr) ||                                              \
+   curlcheck_cb_compatible((expr), curl_seek_callback) ||               \
+   curlcheck_cb_compatible((expr), _curl_seek_callback1) ||             \
+   curlcheck_cb_compatible((expr), _curl_seek_callback2))
 typedef CURLcode (*_curl_seek_callback1)(void *, curl_off_t, int);
 typedef CURLcode (*_curl_seek_callback2)(const void *, curl_off_t, int);
 
 
-#endif /* __CURL_TYPECHECK_GCC_H */
+#endif /* CURLINC_TYPECHECK_GCC_H */
diff --git a/libs/curl/include/curl/urlapi.h b/libs/curl/include/curl/urlapi.h
new file mode 100644
index 0000000000000000000000000000000000000000..f2d06770dc88fdd1b6c9a4d8b7dbcce2a6bd2c93
--- /dev/null
+++ b/libs/curl/include/curl/urlapi.h
@@ -0,0 +1,125 @@
+#ifndef CURLINC_URLAPI_H
+#define CURLINC_URLAPI_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2018 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+/* the error codes for the URL API */
+typedef enum {
+  CURLUE_OK,
+  CURLUE_BAD_HANDLE,          /* 1 */
+  CURLUE_BAD_PARTPOINTER,     /* 2 */
+  CURLUE_MALFORMED_INPUT,     /* 3 */
+  CURLUE_BAD_PORT_NUMBER,     /* 4 */
+  CURLUE_UNSUPPORTED_SCHEME,  /* 5 */
+  CURLUE_URLDECODE,           /* 6 */
+  CURLUE_OUT_OF_MEMORY,       /* 7 */
+  CURLUE_USER_NOT_ALLOWED,    /* 8 */
+  CURLUE_UNKNOWN_PART,        /* 9 */
+  CURLUE_NO_SCHEME,           /* 10 */
+  CURLUE_NO_USER,             /* 11 */
+  CURLUE_NO_PASSWORD,         /* 12 */
+  CURLUE_NO_OPTIONS,          /* 13 */
+  CURLUE_NO_HOST,             /* 14 */
+  CURLUE_NO_PORT,             /* 15 */
+  CURLUE_NO_QUERY,            /* 16 */
+  CURLUE_NO_FRAGMENT          /* 17 */
+} CURLUcode;
+
+typedef enum {
+  CURLUPART_URL,
+  CURLUPART_SCHEME,
+  CURLUPART_USER,
+  CURLUPART_PASSWORD,
+  CURLUPART_OPTIONS,
+  CURLUPART_HOST,
+  CURLUPART_PORT,
+  CURLUPART_PATH,
+  CURLUPART_QUERY,
+  CURLUPART_FRAGMENT,
+  CURLUPART_ZONEID /* added in 7.65.0 */
+} CURLUPart;
+
+#define CURLU_DEFAULT_PORT (1<<0)       /* return default port number */
+#define CURLU_NO_DEFAULT_PORT (1<<1)    /* act as if no port number was set,
+                                           if the port number matches the
+                                           default for the scheme */
+#define CURLU_DEFAULT_SCHEME (1<<2)     /* return default scheme if
+                                           missing */
+#define CURLU_NON_SUPPORT_SCHEME (1<<3) /* allow non-supported scheme */
+#define CURLU_PATH_AS_IS (1<<4)         /* leave dot sequences */
+#define CURLU_DISALLOW_USER (1<<5)      /* no user+password allowed */
+#define CURLU_URLDECODE (1<<6)          /* URL decode on get */
+#define CURLU_URLENCODE (1<<7)          /* URL encode on set */
+#define CURLU_APPENDQUERY (1<<8)        /* append a form style part */
+#define CURLU_GUESS_SCHEME (1<<9)       /* legacy curl-style guessing */
+#define CURLU_NO_AUTHORITY (1<<10)      /* Allow empty authority when the
+                                           scheme is unknown. */
+
+typedef struct Curl_URL CURLU;
+
+/*
+ * curl_url() creates a new CURLU handle and returns a pointer to it.
+ * Must be freed with curl_url_cleanup().
+ */
+CURL_EXTERN CURLU *curl_url(void);
+
+/*
+ * curl_url_cleanup() frees the CURLU handle and related resources used for
+ * the URL parsing. It will not free strings previously returned with the URL
+ * API.
+ */
+CURL_EXTERN void curl_url_cleanup(CURLU *handle);
+
+/*
+ * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new
+ * handle must also be freed with curl_url_cleanup().
+ */
+CURL_EXTERN CURLU *curl_url_dup(CURLU *in);
+
+/*
+ * curl_url_get() extracts a specific part of the URL from a CURLU
+ * handle. Returns error code. The returned pointer MUST be freed with
+ * curl_free() afterwards.
+ */
+CURL_EXTERN CURLUcode curl_url_get(CURLU *handle, CURLUPart what,
+                                   char **part, unsigned int flags);
+
+/*
+ * curl_url_set() sets a specific part of the URL in a CURLU handle. Returns
+ * error code. The passed in string will be copied. Passing a NULL instead of
+ * a part string, clears that part.
+ */
+CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what,
+                                   const char *part, unsigned int flags);
+
+
+#ifdef __cplusplus
+} /* end of extern "C" */
+#endif
+
+#endif /* CURLINC_URLAPI_H */
diff --git a/libs/curl/lib32/libcurl.a b/libs/curl/lib32/libcurl.a
index 2f5a29f62a792824da80e7fc18e6de2e00088c81..4e4907c5bf0637e86738c31f2d65488d4cf87548 100644
Binary files a/libs/curl/lib32/libcurl.a and b/libs/curl/lib32/libcurl.a differ
diff --git a/libs/curl/lib32/libcurl.dll b/libs/curl/lib32/libcurl.dll
new file mode 100644
index 0000000000000000000000000000000000000000..61925326fda809e108b050fd47012ecd0d0c7216
Binary files /dev/null and b/libs/curl/lib32/libcurl.dll differ
diff --git a/libs/curl/lib32/libcurl.dll.a b/libs/curl/lib32/libcurl.dll.a
new file mode 100644
index 0000000000000000000000000000000000000000..ff1003b6e0a6b62bafa9d713a8e3152f00fe7da8
Binary files /dev/null and b/libs/curl/lib32/libcurl.dll.a differ
diff --git a/libs/curl/lib64/libcurl-x64.dll b/libs/curl/lib64/libcurl-x64.dll
new file mode 100644
index 0000000000000000000000000000000000000000..7dab52541745f2e5127394e5878d70ed5fb2b9c3
Binary files /dev/null and b/libs/curl/lib64/libcurl-x64.dll differ
diff --git a/libs/curl/lib64/libcurl.a b/libs/curl/lib64/libcurl.a
index 2112cd8f0dffcc3918d8b479dc764fd6eb3195eb..d30b71aca2ae49bce3291375e00e741e2e4d5b77 100644
Binary files a/libs/curl/lib64/libcurl.a and b/libs/curl/lib64/libcurl.a differ
diff --git a/libs/curl/lib64/libcurl.dll.a b/libs/curl/lib64/libcurl.dll.a
new file mode 100644
index 0000000000000000000000000000000000000000..d83f092d57f0b13a103603c807977e88c8db7c46
Binary files /dev/null and b/libs/curl/lib64/libcurl.dll.a differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ea8d9c6088d7763b6d8adaee1fc0476e992616ce..454277fdbb6934732ae28fd7c2ef4eff14e9af75 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,6 +13,9 @@ set(SRB2_CORE_SOURCES
 	d_netcmd.c
 	d_netfil.c
 	dehacked.c
+	deh_soc.c
+	deh_lua.c
+	deh_tables.c
 	f_finale.c
 	f_wipe.c
 	filesrch.c
@@ -32,10 +35,12 @@ set(SRB2_CORE_SOURCES
 	m_fixed.c
 	m_menu.c
 	m_misc.c
+	m_perfstats.c
 	m_queue.c
 	m_random.c
 	md5.c
 	mserv.c
+	http-mserv.c
 	s_sound.c
 	screen.c
 	sounds.c
@@ -64,6 +69,9 @@ set(SRB2_CORE_HEADERS
 	d_think.h
 	d_ticcmd.h
 	dehacked.h
+	deh_soc.h
+	deh_lua.h
+	deh_tables.h
 	doomdata.h
 	doomdef.h
 	doomstat.h
@@ -96,6 +104,7 @@ set(SRB2_CORE_HEADERS
 	m_fixed.h
 	m_menu.h
 	m_misc.h
+	m_perfstats.h
 	m_queue.h
 	m_random.h
 	m_swap.h
@@ -126,7 +135,10 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_sky.c
 	r_splats.c
 	r_things.c
+	r_textures.c
 	r_patch.c
+	r_patchrotation.c
+	r_picformats.c
 	r_portal.c
 
 	r_bsp.h
@@ -142,7 +154,10 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_splats.h
 	r_state.h
 	r_things.h
+	r_textures.h
 	r_patch.h
+	r_patchrotation.h
+	r_picformats.h
 	r_portal.h
 )
 
@@ -165,6 +180,7 @@ set(SRB2_CORE_GAME_SOURCES
 	p_telept.c
 	p_tick.c
 	p_user.c
+	taglist.c
 
 	p_local.h
 	p_maputl.h
@@ -176,6 +192,7 @@ set(SRB2_CORE_GAME_SOURCES
 	p_slopes.h
 	p_spec.h
 	p_tick.h
+	taglist.h
 )
 
 if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
@@ -227,6 +244,10 @@ set(SRB2_CONFIG_HAVE_GME ON CACHE BOOL
 	"Enable GME support.")
 set(SRB2_CONFIG_HAVE_OPENMPT ON CACHE BOOL
 	"Enable OpenMPT support.")
+set(SRB2_CONFIG_HAVE_CURL ON CACHE BOOL
+	"Enable curl support.")
+set(SRB2_CONFIG_HAVE_THREADS ON CACHE BOOL
+	"Enable multithreading support.")
 if(${CMAKE_SYSTEM} MATCHES Windows)
 	set(SRB2_CONFIG_HAVE_MIXERX ON CACHE BOOL
 		"Enable SDL Mixer X support.")
@@ -256,9 +277,11 @@ set(SRB2_LUA_SOURCES
 	lua_hudlib.c
 	lua_infolib.c
 	lua_maplib.c
+	lua_taglib.c
 	lua_mathlib.c
 	lua_mobjlib.c
 	lua_playerlib.c
+	lua_polyobjlib.c
 	lua_script.c
 	lua_skinlib.c
 	lua_thinkerlib.c
@@ -446,6 +469,32 @@ if(${SRB2_CONFIG_HAVE_PNG} AND ${SRB2_CONFIG_HAVE_ZLIB})
 	endif()
 endif()
 
+if(${SRB2_CONFIG_HAVE_CURL})
+	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+		set(CURL_FOUND ON)
+		set(CURL_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/curl/include)
+		if(${SRB2_SYSTEM_BITS} EQUAL 64)
+			set(CURL_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/curl/lib64 -lcurl")
+		else() # 32-bit
+			set(CURL_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/curl/lib32 -lcurl")
+		endif()
+	else()
+		find_package(CURL)
+	endif()
+	if(${CURL_FOUND})
+		set(SRB2_HAVE_CURL ON)
+		add_definitions(-DHAVE_CURL)
+	else()
+		message(WARNING "You have specified that CURL is available but it was not found. SRB2 may not compile correctly.")
+	endif()
+endif()
+
+if(${SRB2_CONFIG_HAVE_THREADS})
+	set(SRB2_HAVE_THREADS ON)
+	set(SRB2_CORE_HEADERS ${SRB2_CORE_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/i_threads.h)
+	add_definitions(-DHAVE_THREADS)
+endif()
+
 if(${SRB2_CONFIG_HWRENDER})
 	add_definitions(-DHWRENDER)
 	set(SRB2_HWRENDER_SOURCES
@@ -470,7 +519,6 @@ if(${SRB2_CONFIG_HWRENDER})
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_defs.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_dll.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_drv.h
-		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_glide.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_glob.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_light.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_main.h
@@ -518,6 +566,7 @@ if(${SRB2_CONFIG_USEASM})
 	endif()
 	set(SRB2_USEASM ON)
 	add_definitions(-DUSEASM)
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse3 -mfpmath=sse")
 else()
 	set(SRB2_USEASM OFF)
 	add_definitions(-DNONX86 -DNORUSEASM)
diff --git a/src/Makefile b/src/Makefile
index 28688db00b4c936bdb0ae86e6bb2953f9b681e5a..85e1262365a4a3cd411af0732962b8455aa524a0 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2,21 +2,18 @@
 #     GNU Make makefile for SRB2
 #############################################################################
 # Copyright (C) 1998-2000 by DooM Legacy Team.
-# Copyright (C) 2003-2020 by Sonic Team Junior.
+# Copyright (C) 2003-2021 by Sonic Team Junior.
 #
 # This program is free software distributed under the
 # terms of the GNU General Public License, version 2.
 # See the 'LICENSE' file for more details.
 #
-#     -DPC_DOS    -> use DOS specific code (eg:textmode stuff)...
 #     -DLINUX     -> use for the GNU/Linux specific
 #     -D_WINDOWS  -> use for the Win32/DirectX specific
 #     -DHAVE_SDL  -> use for the SDL interface
 #
 # Sets:
-#     Compile the DGJPP/DOS version with 'make WATTCP=1'
-#     Compile the DirectX/Mingw version with 'make MINGW=1'
-#     Compile the SDL/Mingw version with 'make MINGW=1 SDL=1'
+#     Compile the SDL/Mingw version with 'make MINGW=1'
 #     Compile the SDL/Linux version with 'make LINUX=1'
 #     Compile the SDL/Solaris version with 'make SOLARIS=1'
 #     Compile the SDL/FreeBSD version with 'gmake FREEBSD=1'
@@ -27,7 +24,9 @@
 #     clean
 #       Remove all object files
 #     cleandep
-#       Remove depend.dep
+#       Remove dependency files
+#     distclean
+#       Remove autogenerated files
 #     dll
 #       compile primary HW render DLL/SO
 #     all_dll
@@ -82,6 +81,17 @@
 #
 #############################################################################
 
+,=,
+
+ifeq (,$(filter-out cleandep clean distclean,$(or $(MAKECMDGOALS),all)))
+CLEANONLY=1
+else ifndef SILENT
+echo=@echo "$(1)"
+ifndef MAKE_RESTARTS
+print=$(info $(1))
+endif
+endif
+
 ALL_SYSTEMS=\
 	PANDORA\
 	LINUX64\
@@ -101,11 +111,10 @@ ALL_SYSTEMS=\
 ifeq (,$(filter $(ALL_SYSTEMS),$(.VARIABLES)))
 ifeq ($(OS),Windows_NT) # all windows are Windows_NT...
 
- $(info Detected a Windows system, compiling for 32-bit MinGW SDL2...)
+ $(call print,Detected a Windows system$(,) compiling for 32-bit MinGW SDL2...)
 
  # go for a 32-bit sdl mingw exe by default
  MINGW=1
- SDL=1
  WINDOWSHELL=1
 
 else # if you on the *nix
@@ -127,7 +136,7 @@ else # if you on the *nix
  new_system:=$(new_system)64
  endif
 
- $(info Detected $(system) ($(new_system))...)
+ $(call print,Detected $(system) ($(new_system))...)
  $(new_system)=1
 
 endif
@@ -196,10 +205,6 @@ NONX86=1
 NOHW=1
 endif
 
-ifdef DJGPPDOS
-include djgppdos/Makefile.cfg
-endif
-
 ifndef NOOPENMPT
 HAVE_OPENMPT=1
 endif
@@ -245,6 +250,12 @@ endif
 
 MSGFMT?=msgfmt
 
+ifdef WINDOWSHELL
+	COMPTIME=-..\comptime.bat
+else
+	COMPTIME=-../comptime.sh
+endif
+
 ifndef ECHO
 	NASM:=@$(NASM)
 	REMOVE:=@$(REMOVE)
@@ -259,10 +270,12 @@ ifndef ECHO
 	MSGFMT:=@$(MSGFMT)
 	UPX:=@$(UPX)
 	UPX_OPTS+=-q
+	COMPTIME:=@$(COMPTIME)
 endif
 
 ifdef NONET
 	OPTS+=-DNONET
+	NOCURL=1
 else
 ifdef NO_IPV6
 	OPTS+=-DNO_IPV6
@@ -282,7 +295,7 @@ OPTS += -DCOMPVERSION
 
 ifndef NONX86
 ifndef GCC29
-	ARCHOPTS?=-march=pentium
+	ARCHOPTS?=-msse3 -mfpmath=sse
 else
 	ARCHOPTS?=-mpentium
 endif
@@ -365,6 +378,16 @@ else
 NOPNG=1
 endif
 
+ifndef NOCURL
+OPTS+=-DHAVE_CURL
+CURLCONFIG?=curl-config
+CURL_CFLAGS?=$(shell $(CURLCONFIG) --cflags)
+CURL_LDFLAGS?=$(shell $(CURLCONFIG) --libs)
+
+LIBS+=$(CURL_LDFLAGS)
+CFLAGS+=$(CURL_CFLAGS)
+endif
+
 ifdef STATIC
 LIBS:=-static $(LIBS)
 endif
@@ -412,7 +435,7 @@ ifdef GCC48
 else
 	CFLAGS+=-O0
 endif
-	CFLAGS+= -Wall -DPARANOIA -DRANGECHECK -DPACKETDROP -DMOBJCONSISTANCY
+	CFLAGS+= -Wall -DPARANOIA -DRANGECHECK -DPACKETDROP
 else
 
 
@@ -452,7 +475,6 @@ DBGNAME?=$(EXENAME).debug
 
 # not too sophisticated dependency
 OBJS:=$(i_main_o) \
-		$(OBJDIR)/comptime.o \
 		$(OBJDIR)/string.o   \
 		$(OBJDIR)/d_main.o   \
 		$(OBJDIR)/d_clisrv.o \
@@ -460,6 +482,9 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/d_netfil.o \
 		$(OBJDIR)/d_netcmd.o \
 		$(OBJDIR)/dehacked.o \
+		$(OBJDIR)/deh_soc.o  \
+		$(OBJDIR)/deh_lua.o  \
+		$(OBJDIR)/deh_tables.o \
 		$(OBJDIR)/z_zone.o   \
 		$(OBJDIR)/f_finale.o \
 		$(OBJDIR)/f_wipe.o   \
@@ -481,6 +506,7 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/m_fixed.o  \
 		$(OBJDIR)/m_menu.o   \
 		$(OBJDIR)/m_misc.o   \
+		$(OBJDIR)/m_perfstats.o \
 		$(OBJDIR)/m_random.o \
 		$(OBJDIR)/m_queue.o  \
 		$(OBJDIR)/info.o     \
@@ -513,25 +539,42 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/r_sky.o    \
 		$(OBJDIR)/r_splats.o \
 		$(OBJDIR)/r_things.o \
+		$(OBJDIR)/r_textures.o \
 		$(OBJDIR)/r_patch.o \
+		$(OBJDIR)/r_patchrotation.o \
+		$(OBJDIR)/r_picformats.o \
 		$(OBJDIR)/r_portal.o \
 		$(OBJDIR)/screen.o   \
+		$(OBJDIR)/taglist.o  \
 		$(OBJDIR)/v_video.o  \
 		$(OBJDIR)/s_sound.o  \
 		$(OBJDIR)/sounds.o   \
 		$(OBJDIR)/w_wad.o    \
 		$(OBJDIR)/filesrch.o \
 		$(OBJDIR)/mserv.o    \
+		$(OBJDIR)/http-mserv.o\
 		$(OBJDIR)/i_tcp.o    \
 		$(OBJDIR)/lzf.o	     \
 		$(OBJDIR)/vid_copy.o \
 		$(OBJDIR)/b_bot.o \
-		$(i_cdmus_o)    \
 		$(i_net_o)      \
 		$(i_system_o)   \
 		$(i_sound_o)    \
 		$(OBJS)
 
+DEPS:=$(patsubst $(OBJDIR)/%.o,$(DEPDIR)/%.d,$(filter %.o,$(OBJS)))
+OBJS+=$(OBJDIR)/comptime.o
+
+ifndef SILENT
+ifndef ECHO
+ifndef NOECHOFILENAMES
+define echoName =
+	@echo -- $< ...
+endef
+endif
+endif
+endif
+
 # List of languages to compile.
 # For reference, this is the command I use to build a srb2.pot file from the source code.
 # (The listed source files are the ones containing translated strings).
@@ -542,23 +585,13 @@ POS:=$(BIN)/en.mo
 OPTS+=-DGETTEXT
 endif
 
-ifdef DJGPPDOS
-all:	 pre-build $(BIN)/$(EXENAME)
-endif
-
 ifdef PANDORA
-all:	pre-build $(BIN)/$(PNDNAME)
+all: $(BIN)/$(PNDNAME)
 endif
 
 
-ifdef MINGW
-ifndef SDL
-all:	 pre-build $(BIN)/$(EXENAME) dll
-endif
-endif
-
 ifdef SDL
-all:	 pre-build $(BIN)/$(EXENAME)
+all: $(BIN)/$(EXENAME)
 endif
 
 ifdef DUMMY
@@ -566,20 +599,15 @@ all:	$(BIN)/$(EXENAME)
 endif
 
 cleandep:
-	$(REMOVE) $(OBJDIR)/depend.dep
+	$(REMOVE) $(DEPS)
 	$(REMOVE) comptime.h
 
-pre-build:
-ifdef WINDOWSHELL
-	-..\comptime.bat .
-else
-	-@../comptime.sh .
-endif
-
 clean:
 	$(REMOVE) *~ *.flc
 	$(REMOVE) $(OBJDIR)/*.o
 
+distclean: clean cleandep
+
 ifdef MINGW
 	$(REMOVE) $(OBJDIR)/*.res
 endif
@@ -599,14 +627,15 @@ asm:
 
 $(BIN)/$(EXENAME): $(POS) $(OBJS)
 	-$(MKDIR) $(BIN)
-	@echo Linking $(EXENAME)...
+	$(call echo,Linking $(EXENAME)...)
 	$(LD) $(LDFLAGS) $(OBJS) -o $(BIN)/$(EXENAME) $(LIBS)
 ifndef VALGRIND
 ifndef NOOBJDUMP
-	@echo Dumping debugging info
+	$(call echo,Dumping debugging info)
 	$(OBJDUMP) $(OBJDUMP_OPTS) $(BIN)/$(EXENAME) > $(BIN)/$(DBGNAME).txt
+ifdef WINDOWSHELL
 	-$(GZIP) $(GZIP_OPTS) $(BIN)/$(DBGNAME).txt
-ifndef WINDOWSHELL
+else
 	-$(GZIP) $(GZIP_OPT2) $(BIN)/$(DBGNAME).txt
 endif
 endif
@@ -621,210 +650,172 @@ ifndef NOUPX
 	-$(UPX) $(UPX_OPTS) $(BIN)/$(EXENAME)
 endif
 endif
-	@echo Build is done, please look for $(EXENAME) in $(BIN), \(checking for post steps\)
+	$(call echo,Build is done$(,) please look for $(EXENAME) in $(BIN)$(,) (checking for post steps))
 
 reobjdump:
-	@echo Redumping debugging info
+	$(call echo,Redumping debugging info)
 	$(OBJDUMP) $(OBJDUMP_OPTS) $(BIN)/$(DBGNAME) > $(BIN)/$(DBGNAME).txt
+ifdef WINDOWSHELL
 	-$(GZIP) $(GZIP_OPTS) $(BIN)/$(DBGNAME).txt
-ifndef WINDOWSHELL
+else
 	-$(GZIP) $(GZIP_OPT2) $(BIN)/$(DBGNAME).txt
 endif
 
 $(OBJDIR):
 	-$(MKDIR) $(OBJDIR)
 
-ifndef SDL
-ifdef NOHW
-dll :
-else
-dll : opengl_dll
-endif
-ifdef MINGW
-all_dll: opengl_dll ds3d_dll fmod_dll openal_dll
-
-opengl_dll: $(BIN)/r_opengl.dll
-$(BIN)/r_opengl.dll: $(OBJDIR)/ogl_win.o $(OBJDIR)/r_opengl.o
-	-$(MKDIR) $(BIN)
-	@echo Linking R_OpenGL.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lgdi32 -static-libgcc
-ifndef NOUPX
-	-$(UPX) $(UPX_OPTS) $@
-endif
-
-ds3d_dll: $(BIN)/s_ds3d.dll
-$(BIN)/s_ds3d.dll: $(OBJDIR)/s_ds3d.o
-	@echo Linking S_DS3d.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -ldsound -luuid
-
-fmod_dll: $(BIN)/s_fmod.dll
-$(BIN)/s_fmod.dll: $(OBJDIR)/s_fmod.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_FMOD.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lfmod
-
-openal_dll: $(BIN)/s_openal.dll
-$(BIN)/s_openal.dll: $(OBJDIR)/s_openal.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_OpenAL.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lopenal32
-else
-all_dll: fmod_so openal_so
-
-fmod_so: $(BIN)/s_fmod.so
-$(BIN)/s_fmod.so: $(OBJDIR)/s_fmod.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_FMOD.so...
-	$(CC) --shared $^ -o $@ -g --nostartfiles -lm -lfmod
-
-openal_so: $(BIN)/s_openal.so
-$(BIN)/s_openal.so: $(OBJDIR)/s_openal.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_OpenAL.so...
-	$(CC) --shared $^ -o $@ -g --nostartfiles -lm -lopenal
-endif
-
-else
 ifdef SDL
 ifdef MINGW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
- command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
+ command.h hardware/hw_data.h hardware/hw_defs.h \
  hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
  hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
  am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 else
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
- command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
+ command.h hardware/hw_data.h hardware/hw_defs.h \
  hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
  hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
  am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -I/usr/X11R6/include -c $< -o $@
 endif
 endif
 
-endif
-
 #dependecy made by gcc itself !
-$(OBJS):
 ifndef DUMMY
--include $(OBJDIR)/depend.dep
+ifndef CLEANONLY
+$(call print,Checking dependency files...)
+-include $(DEPS)
+endif
 endif
 
-$(OBJDIR)/depend.dep:
-	@echo "Creating dependency file, depend.dep"
-	@echo > comptime.h
-	-$(MKDIR) $(OBJDIR)
-	$(CC) $(CFLAGS) -MM *.c > $(OBJDIR)/depend.ped
-	$(CC) $(CFLAGS) -MM $(INTERFACE)/*.c >> $(OBJDIR)/depend.ped
-ifndef NOHW
-	$(CC) $(CFLAGS) -MM hardware/*.c >> $(OBJDIR)/depend.ped
+undefine deps_rule
+
+# windows makes it too hard !
+ifndef WINDOWSHELL
+ifdef echoName
+define deps_rule =
+	@printf "%-20.20s\r" $<
+
+endef
+endif
 endif
-	$(CC) $(CFLAGS) -MM blua/*.c >> $(OBJDIR)/depend.ped
-	@sed -e 's,\(.*\)\.o: ,$(subst /,\/,$(OBJDIR))\/&,g' < $(OBJDIR)/depend.ped > $(OBJDIR)/depend.dep
-	$(REMOVE) $(OBJDIR)/depend.ped
-	@echo "Created dependency file, depend.dep"
+
+define deps_rule +=
+	$(CC) $(CFLAGS) -M -MF $@ -MT $(OBJDIR)/$< $<
+endef
+
+$(DEPDIR)/%.d: %.c
+	$(deps_rule)
+
+$(DEPDIR)/%.d: $(INTERFACE)/%.c
+	$(deps_rule)
+
+$(DEPDIR)/%.d: hardware/%.c
+	$(deps_rule)
+
+$(DEPDIR)/%.d: blua/%.c
+	$(deps_rule)
 
 ifdef VALGRIND
 $(OBJDIR)/z_zone.o: z_zone.c
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -DHAVE_VALGRIND $(VALGRIND_CFLAGS) -c $< -o $@
 endif
 
-$(OBJDIR)/comptime.o: comptime.c pre-build
-	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
+$(OBJDIR)/comptime.o::
+ifdef echoName
+	@echo -- comptime.c ...
+endif
+	$(COMPTIME) .
+	$(CC) $(CFLAGS) $(WFLAGS) -c comptime.c -o $@
 
 $(BIN)/%.mo: locale/%.po
 	-$(MKDIR) $(BIN)
+	$(echoName)
 	$(MSGFMT) -f -o $@ $<
 
 $(OBJDIR)/%.o: %.c
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 
 $(OBJDIR)/%.o: $(INTERFACE)/%.c
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 
 ifdef MACOSX
 $(OBJDIR)/%.o: sdl/macosx/%.c
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 endif
 
 $(OBJDIR)/%.o: hardware/%.c
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 
 $(OBJDIR)/%.o: blua/%.c
+	$(echoName)
 	$(CC) $(CFLAGS) $(LUA_CFLAGS) $(WFLAGS) -c $< -o $@
 
 $(OBJDIR)/%.o: %.nas
+	$(echoName)
 	$(NASM) $(NASMOPTS) -o $@ -f $(NASMFORMAT) $<
 
 $(OBJDIR)/vid_copy.o: vid_copy.s asm_defs.inc
+	$(echoName)
 	$(CC) $(OPTS) $(ASFLAGS) -x assembler-with-cpp -c $< -o $@
 
 $(OBJDIR)/%.o: %.s
+	$(echoName)
 	$(CC) $(OPTS) -x assembler-with-cpp -c $< -o $@
 
 $(OBJDIR)/SRB2.res: win32/Srb2win.rc win32/afxres.h win32/resource.h
+	$(echoName)
 	$(WINDRES) -i $< -O rc $(WINDRESFLAGS) --include-dir=win32 -o $@ -O coff
 
 
-ifdef MINGW
-ifndef SDL
-ifndef NOHW
-$(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
- command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
-
-$(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
- command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
-endif
-
-endif
-endif
-
 ifdef SDL
 
 ifdef MINGW
 $(OBJDIR)/win_dbg.o: win32/win_dbg.c
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 endif
 
 ifdef STATICHS
 $(OBJDIR)/s_openal.o: hardware/s_openal/s_openal.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 
 $(OBJDIR)/s_fmod.o: hardware/s_fmod/s_fmod.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 
 ifdef MINGW
 $(OBJDIR)/s_ds3d.o: hardware/s_ds3d/s_ds3d.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
+	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 endif
 else
 
 $(OBJDIR)/s_fmod.o: hardware/s_fmod/s_fmod.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
+	$(echoName)
 	$(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_fmod.o -DHW3SOUND -DUNIXCOMMON -shared -nostartfiles -c hardware/s_fmod/s_fmod.c
 
 $(OBJDIR)/s_openal.o: hardware/s_openal/s_openal.c hardware/hw3dsdrv.h \
  hardware/hw_dll.h
+	$(echoName)
 	$(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_openal.o -DHW3SOUND -DUNIXCOMMON -shared -nostartfiles -c hardware/s_openal/s_openal.c
 endif
 endif
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 86d602438a662ef185be4fe0a6d9384a26b2d9ce..075cd2d3a8defa3fd7d11feb431d2ad9cc67a704 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -47,7 +47,8 @@ ifdef MACOSX
 endif
 
 # Automatically set version flag, but not if one was manually set
-ifeq   (,$(filter GCC%,$(.VARIABLES)))
+# And don't bother if this is a clean only run
+ifeq   (,$(filter GCC% CLEANONLY,$(.VARIABLES)))
  version:=$(shell $(CC) --version)
  # check if this is in fact GCC
  ifneq (,$(or $(findstring gcc,$(version)),$(findstring GCC,$(version))))
@@ -60,12 +61,14 @@ ifeq   (,$(filter GCC%,$(.VARIABLES)))
 
   # If this version is not in the list, default to the latest supported
   ifeq (,$(filter $(v),$(SUPPORTED_GCC_VERSIONS)))
-   $(info\
-		Your compiler version, GCC $(version), is not supported by the Makefile.\
-		The Makefile will assume GCC $(LATEST_GCC_VERSION).)
+	define line =
+	Your compiler version, GCC $(version), is not supported by the Makefile.
+	The Makefile will assume GCC $(LATEST_GCC_VERSION).))
+	endef
+   $(call print,$(line))
    GCC$(subst .,,$(LATEST_GCC_VERSION))=1
   else
-   $(info Detected GCC $(version) (GCC$(v)))
+   $(call print,Detected GCC $(version) (GCC$(v)))
    GCC$(v)=1
   endif
  endif
@@ -344,7 +347,7 @@ ifndef MINGW
 ifndef MINGW64
 ifndef SDL
 ifndef DUMMY
-	DJGPPDOS=1
+$(error No interface or platform flag defined)
 endif
 endif
 endif
@@ -354,7 +357,6 @@ endif
 endif
 
 #determine the interface directory (where you put all i_*.c)
-i_cdmus_o=$(OBJDIR)/i_cdmus.o
 i_net_o=$(OBJDIR)/i_net.o
 i_system_o=$(OBJDIR)/i_system.o
 i_sound_o=$(OBJDIR)/i_sound.o
@@ -362,6 +364,7 @@ i_main_o=$(OBJDIR)/i_main.o
 #set OBJDIR and BIN's starting place
 OBJDIR=../objs
 BIN=../bin
+DEPDIR=../dep
 #Nasm ASM and rm
 ifdef YASM
 NASM?=yasm
@@ -380,20 +383,11 @@ UPX_OPTS+=-q
 endif
 
 #Interface Setup
-ifdef DJGPPDOS
-	INTERFACE=djgppdos
-	NASMFORMAT=coff
-	OBJDIR:=$(OBJDIR)/djgppdos
-ifdef WATTCP
-	OBJDIR:=$(OBJDIR)/wattcp
-endif
-	WFLAGS+=-Wno-format
-	BIN:=$(BIN)/Dos
-else
 ifdef DUMMY
 	INTERFACE=dummy
 	OBJDIR:=$(OBJDIR)/dummy
 	BIN:=$(BIN)/dummy
+	DEPDIR:=$(DEPDIR)/dummy
 else
 ifdef LINUX
 	NASMFORMAT=elf -DLINUX
@@ -401,9 +395,11 @@ ifdef LINUX
 ifdef LINUX64
 	OBJDIR:=$(OBJDIR)/Linux64
 	BIN:=$(BIN)/Linux64
+	DEPDIR:=$(DEPDIR)/Linux64
 else
 	OBJDIR:=$(OBJDIR)/Linux
 	BIN:=$(BIN)/Linux
+	DEPDIR:=$(DEPDIR)/Linux
 endif
 else
 ifdef FREEBSD
@@ -413,6 +409,7 @@ ifdef FREEBSD
 
 	OBJDIR:=$(OBJDIR)/FreeBSD
 	BIN:=$(BIN)/FreeBSD
+	DEPDIR:=$(DEPDIR)/Linux
 else
 ifdef SOLARIS
 	INTERFACE=sdl
@@ -421,6 +418,7 @@ ifdef SOLARIS
 
 	OBJDIR:=$(OBJDIR)/Solaris
 	BIN:=$(BIN)/Solaris
+	DEPDIR:=$(DEPDIR)/Solaris
 else
 ifdef CYGWIN32
 	INTERFACE=sdl
@@ -429,19 +427,21 @@ ifdef CYGWIN32
 
 	OBJDIR:=$(OBJDIR)/cygwin
 	BIN:=$(BIN)/Cygwin
+	DEPDIR:=$(DEPDIR)/Cygwin
 else
 ifdef MINGW64
-	INTERFACE=win32
 	#NASMFORMAT=win64
+	SDL=1
 	OBJDIR:=$(OBJDIR)/Mingw64
 	BIN:=$(BIN)/Mingw64
+	DEPDIR:=$(DEPDIR)/Mingw64
 else
 ifdef MINGW
-	INTERFACE=win32
 	NASMFORMAT=win32
+	SDL=1
 	OBJDIR:=$(OBJDIR)/Mingw
 	BIN:=$(BIN)/Mingw
-endif
+	DEPDIR:=$(DEPDIR)/Mingw
 endif
 endif
 endif
@@ -453,6 +453,7 @@ endif
 ifdef ARCHNAME
 	OBJDIR:=$(OBJDIR)/$(ARCHNAME)
 	BIN:=$(BIN)/$(ARCHNAME)
+	DEPDIR:=$(DEPDIR)/$(ARCHNAME)
 endif
 
 OBJDUMP_OPTS?=--wide --source --line-numbers
@@ -461,14 +462,17 @@ LD=$(CC)
 ifdef SDL
 	INTERFACE=sdl
 	OBJDIR:=$(OBJDIR)/SDL
+	DEPDIR:=$(DEPDIR)/SDL
 endif
 
 ifndef DUMMY
 ifdef DEBUGMODE
 	OBJDIR:=$(OBJDIR)/Debug
 	BIN:=$(BIN)/Debug
+	DEPDIR:=$(DEPDIR)/Debug
 else
 	OBJDIR:=$(OBJDIR)/Release
 	BIN:=$(BIN)/Release
+	DEPDIR:=$(DEPDIR)/Release
 endif
 endif
diff --git a/src/android/i_cdmus.c b/src/android/i_cdmus.c
index 426bc5dc9ebdbf130614725e96fadbba42f6dd2c..12063745b3fa9adef15155d780664eee4dd4a2aa 100644
--- a/src/android/i_cdmus.c
+++ b/src/android/i_cdmus.c
@@ -8,8 +8,8 @@
 
 UINT8 cdaudio_started = 0;
 
-consvar_t cd_volume = {"cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cd_volume = CVAR_INIT ("cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL);
+consvar_t cdUpdate  = CVAR_INIT ("cd_update","1",CV_SAVE, NULL, NULL);
 
 
 void I_InitCD(void){}
diff --git a/src/android/i_video.c b/src/android/i_video.c
index 1909cd71afbb5e2acf1efdfd5a218b44cef09316..bf0decb74118385ff2b776d8d470e5ea3a03a2ba 100644
--- a/src/android/i_video.c
+++ b/src/android/i_video.c
@@ -9,6 +9,7 @@
 #include "utils/Log.h"
 
 rendermode_t rendermode = render_soft;
+rendermode_t chosenrendermode = render_none;
 
 boolean highcolor = false;
 
@@ -16,7 +17,7 @@ boolean allow_fullscreen = false;
 
 
 
-consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, NULL);
 
 void I_StartupGraphics(void){}
 void I_ShutdownGraphics(void){}
@@ -52,8 +53,15 @@ INT32 VID_SetMode(INT32 modenum)
   return 0;
 }
 
-void VID_CheckRenderer(void) {}
-void VID_CheckGLLoaded(rendermode_t oldrender) {}
+boolean VID_CheckRenderer(void)
+{
+	return false;
+}
+
+void VID_CheckGLLoaded(rendermode_t oldrender)
+{
+	(void)oldrender;
+}
 
 const char *VID_GetModeName(INT32 modenum)
 {
diff --git a/src/b_bot.c b/src/b_bot.c
index 4397938c1ae42e90807493b8edbfac81225c12bd..d3635f32c5d75ca1ad56c199241ebfa91c79ea0f 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -193,7 +193,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 		{
 			cmd->forwardmove = pcmd->forwardmove;
 			cmd->sidemove = pcmd->sidemove;
-			if (pcmd->buttons & BT_USE)
+			if (pcmd->buttons & BT_SPIN)
 			{
 				spin = true;
 				jump = false;
@@ -441,7 +441,7 @@ void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward
 	if (jump)
 		cmd->buttons |= BT_JUMP;
 	if (spin)
-		cmd->buttons |= BT_USE;
+		cmd->buttons |= BT_SPIN;
 }
 
 void B_MoveBlocked(player_t *player)
diff --git a/src/blua/Makefile.cfg b/src/blua/Makefile.cfg
index 12ea064b417fc60ad14fd1a3520d6b0280b6fce8..3a2962e659e24cbb34f9690afe52348393a31ed3 100644
--- a/src/blua/Makefile.cfg
+++ b/src/blua/Makefile.cfg
@@ -47,5 +47,7 @@ OBJS:=$(OBJS) \
 	$(OBJDIR)/lua_skinlib.o \
 	$(OBJDIR)/lua_thinkerlib.o \
 	$(OBJDIR)/lua_maplib.o \
+	$(OBJDIR)/lua_taglib.o \
+	$(OBJDIR)/lua_polyobjlib.o \
 	$(OBJDIR)/lua_blockmaplib.o \
 	$(OBJDIR)/lua_hudlib.o
diff --git a/src/blua/lcode.c b/src/blua/lcode.c
index 5c7fed4541a4442d9d40691663965250de434619..fd4aaff24c33ae99e936497bac797d9bce95b061 100644
--- a/src/blua/lcode.c
+++ b/src/blua/lcode.c
@@ -686,6 +686,15 @@ static void codearith (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) {
 }
 
 
+static void codeunaryarith (FuncState *fs, OpCode op, expdesc *e) {
+  expdesc e2;
+  e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0;
+  if (op == OP_LEN || !isnumeral(e))
+    luaK_exp2anyreg(fs, e);  /* cannot operate on non-numeric constants */
+  codearith(fs, op, e, &e2);
+}
+
+
 static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1,
                                                           expdesc *e2) {
   int o1 = luaK_exp2RK(fs, e1);
@@ -703,27 +712,11 @@ static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1,
 
 
 void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e) {
-  expdesc e2;
-  e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0;
   switch (op) {
-    case OPR_MINUS: {
-      if (!isnumeral(e))
-        luaK_exp2anyreg(fs, e);  /* cannot operate on non-numeric constants */
-      codearith(fs, OP_UNM, e, &e2);
-      break;
-    }
-    case OPR_BNOT: {
-      if (e->k == VK)
-        luaK_exp2anyreg(fs, e);  /* cannot operate on non-numeric constants */
-      codearith(fs, OP_BNOT, e, &e2);
-      break;
-    }
+    case OPR_MINUS: codeunaryarith(fs, OP_UNM, e); break;
+    case OPR_BNOT: codeunaryarith(fs, OP_BNOT, e); break;
     case OPR_NOT: codenot(fs, e); break;
-    case OPR_LEN: {
-      luaK_exp2anyreg(fs, e);  /* cannot operate on constants */
-      codearith(fs, OP_LEN, e, &e2);
-      break;
-    }
+    case OPR_LEN: codeunaryarith(fs, OP_LEN, e); break;
     default: lua_assert(0);
   }
 }
diff --git a/src/blua/liolib.c b/src/blua/liolib.c
index a055aad3f600a4482502da148467bfe622751fe3..5eec97fb4b7b4e5e4f47637b34b0444c1750a334 100644
--- a/src/blua/liolib.c
+++ b/src/blua/liolib.c
@@ -277,6 +277,9 @@ void Got_LuaFile(UINT8 **cp, INT32 playernum)
 	if (!luafiletransfers)
 		I_Error("No Lua file transfer\n");
 
+	lua_settop(gL, 0); // Just in case...
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
 	// Retrieve the callback and push it on the stack
 	lua_pushfstring(gL, FMT_FILECALLBACKID, luafiletransfers->id);
 	lua_gettable(gL, LUA_REGISTRYINDEX);
@@ -304,7 +307,8 @@ void Got_LuaFile(UINT8 **cp, INT32 playernum)
 	lua_pushstring(gL, luafiletransfers->filename);
 
 	// Call the callback
-	LUA_Call(gL, 2);
+	LUA_Call(gL, 2, 0, 1);
+	lua_settop(gL, 0);
 
 	if (success)
 	{
diff --git a/src/blua/lstrlib.c b/src/blua/lstrlib.c
index 297504e95bf93cd0dbcc46ab1acd8f65a874e1b5..af933d25a297887b057f4709edbcaa856a50211f 100644
--- a/src/blua/lstrlib.c
+++ b/src/blua/lstrlib.c
@@ -19,6 +19,7 @@
 #include "lauxlib.h"
 #include "lualib.h"
 
+#include "../m_fixed.h"
 
 /* macro to `unsign' a character */
 #define uchar(c)        ((unsigned char)(c))
@@ -790,7 +791,7 @@ static int str_format (lua_State *L) {
         case 'e':  case 'E': case 'f':
         case 'g': case 'G': {
 					lua_Number n = luaL_checknumber(L, arg);
-          sprintf(buff, form, (double)n);
+          sprintf(buff, form, (double)n / FRACUNIT);
           break;
         }
         case 'q': {
diff --git a/src/blua/lvm.c b/src/blua/lvm.c
index b654613f422550956782923e5a348b21acf1a821..46a015c1eb254bc86a59d726d041e5b6461e30d2 100644
--- a/src/blua/lvm.c
+++ b/src/blua/lvm.c
@@ -322,8 +322,8 @@ static void Arith (lua_State *L, StkId ra, TValue *rb,
       case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break;
       case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break;
       case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break;
-      case TM_DIV: if (nc == 0) { lua_pushliteral(L, "divide by zero error"); lua_error(L); } else setnvalue(ra, luai_numdiv(nb, nc)); break;
-      case TM_MOD: if (nc == 0) { lua_pushliteral(L, "modulo by zero error"); lua_error(L); } else setnvalue(ra, luai_nummod(nb, nc)); break;
+      case TM_DIV: if (nc == 0) { luaG_runerror(L, "divide by zero error"); } else setnvalue(ra, luai_numdiv(nb, nc)); break;
+      case TM_MOD: if (nc == 0) { luaG_runerror(L, "modulo by zero error"); } else setnvalue(ra, luai_nummod(nb, nc)); break;
       case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break;
       case TM_UNM: setnvalue(ra, luai_numunm(nb)); break;
       case TM_AND: setnvalue(ra, luai_numand(nb, nc)); break;
@@ -492,8 +492,7 @@ void luaV_execute (lua_State *L, int nexeccalls) {
         if (ttisnumber(rb) && ttisnumber(rc)) {
           lua_Number nb = nvalue(rb), nc = nvalue(rc);
           if (nc == 0) {
-            lua_pushliteral(L, "divide by zero error");
-            lua_error(L);
+            luaG_runerror(L, "divide by zero error");
           }
           else
             setnvalue(ra, luai_numdiv(nb, nc));
@@ -508,8 +507,7 @@ void luaV_execute (lua_State *L, int nexeccalls) {
         if (ttisnumber(rb) && ttisnumber(rc)) {
           lua_Number nb = nvalue(rb), nc = nvalue(rc);
           if (nc == 0) {
-            lua_pushliteral(L, "modulo by zero error");
-            lua_error(L);
+            luaG_runerror(L, "modulo by zero error");
           }
           else
             setnvalue(ra, luai_nummod(nb, nc));
diff --git a/src/byteptr.h b/src/byteptr.h
index 933c2af34b847ba1c5c10ef48fec205872aad4c6..01a6293b41401f9b663b6b672986a286b85e449a 100644
--- a/src/byteptr.h
+++ b/src/byteptr.h
@@ -150,15 +150,15 @@ FUNCINLINE static ATTRINLINE UINT32 readulong(void *ptr)
 
 #undef DEALIGNED
 
-#define WRITESTRINGN(p,s,n) { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');}
-#define WRITESTRING(p,s)    { size_t tmp_i = 0; for (;              s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');}
-#define WRITEMEM(p,s,n)     { memcpy(p, s, n); p += n; }
+#define WRITESTRINGN(p,s,n) do { size_t tmp_i = 0; for (; tmp_i < n && s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); if (tmp_i < n) WRITECHAR(p, '\0');} while (0)
+#define WRITESTRING(p,s)    do { size_t tmp_i = 0; for (;              s[tmp_i] != '\0'; tmp_i++) WRITECHAR(p, s[tmp_i]); WRITECHAR(p, '\0');} while (0)
+#define WRITEMEM(p,s,n)     do { memcpy(p, s, n); p += n; } while (0)
 
 #define SKIPSTRING(p)       while (READCHAR(p) != '\0')
 
-#define READSTRINGN(p,s,n)  { size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';}
-#define READSTRING(p,s)     { size_t tmp_i = 0; for (;              (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';}
-#define READMEM(p,s,n)      { memcpy(s, p, n); p += n; }
+#define READSTRINGN(p,s,n)  ({ size_t tmp_i = 0; for (; tmp_i < n && (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
+#define READSTRING(p,s)     ({ size_t tmp_i = 0; for (;              (s[tmp_i] = READCHAR(p)) != '\0'; tmp_i++); s[tmp_i] = '\0';})
+#define READMEM(p,s,n)      ({ memcpy(s, p, n); p += n; })
 
 #if 0 // old names
 #define WRITEBYTE(p,b)      WRITEUINT8(p,b)
diff --git a/src/command.c b/src/command.c
index 091dbd8f124826105089b1b24e2a8158052eb5e3..58434ef8983a1a0ed1816a9522783214896351b1 100644
--- a/src/command.c
+++ b/src/command.c
@@ -33,6 +33,7 @@
 #include "p_setup.h"
 #include "lua_script.h"
 #include "d_netfil.h" // findfile
+#include "r_data.h" // Color_cons_t
 
 //========
 // protos.
@@ -56,7 +57,13 @@ static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
 static boolean CV_Command(void);
 consvar_t *CV_FindVar(const char *name);
 static const char *CV_StringValue(const char *var_name);
+
 static consvar_t *consvar_vars; // list of registered console variables
+static UINT16     consvar_number_of_netids = 0;
+
+#ifdef OLD22DEMOCOMPAT
+static old_demo_var_t *consvar_old_demo_vars;
+#endif
 
 static char com_token[1024];
 static char *COM_Parse(char *data);
@@ -72,7 +79,7 @@ CV_PossibleValue_t CV_Natural[] = {{1, "MIN"}, {999999999, "MAX"}, {0, NULL}};
 // First implementation is 26 (2.1.21), so earlier configs default at 25 (2.1.20)
 // Also set CV_HIDEN during runtime, after config is loaded
 static boolean execversion_enabled = false;
-consvar_t cv_execversion = {"execversion","25",CV_CALL,CV_Unsigned, CV_EnforceExecVersion, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_execversion = CVAR_INIT ("execversion","25",CV_CALL,CV_Unsigned, CV_EnforceExecVersion);
 
 // for default joyaxis detection
 static boolean joyaxis_default = false;
@@ -158,6 +165,8 @@ void COM_BufAddTextEx(const char *ptext, int flags)
   */
 void COM_BufInsertTextEx(const char *ptext, int flags)
 {
+	const INT32 old_wait = com_wait;
+
 	char *temp = NULL;
 	size_t templen;
 
@@ -169,10 +178,14 @@ void COM_BufInsertTextEx(const char *ptext, int flags)
 		VS_Clear(&com_text);
 	}
 
+	com_wait = 0;
+
 	// add the entire text of the file (or alias)
 	COM_BufAddTextEx(ptext, flags);
 	COM_BufExecute(); // do it right away
 
+	com_wait += old_wait;
+
 	// add the copied off data
 	if (templen)
 	{
@@ -553,7 +566,7 @@ static boolean COM_Exists(const char *com_name)
   * \param partial The partial name of the command (potentially).
   * \param skips   Number of commands to skip.
   * \return The complete command name, or NULL.
-  * \sa CV_CompleteVar
+  * \sa CV_CompleteAlias, CV_CompleteVar
   */
 const char *COM_CompleteCommand(const char *partial, INT32 skips)
 {
@@ -574,6 +587,32 @@ const char *COM_CompleteCommand(const char *partial, INT32 skips)
 	return NULL;
 }
 
+/** Completes the name of an alias.
+  *
+  * \param partial The partial name of the alias (potentially).
+  * \param skips   Number of aliases to skip.
+  * \return The complete alias name, or NULL.
+  * \sa CV_CompleteCommand, CV_CompleteVar
+  */
+const char *COM_CompleteAlias(const char *partial, INT32 skips)
+{
+	cmdalias_t *a;
+	size_t len;
+
+	len = strlen(partial);
+
+	if (!len)
+		return NULL;
+
+	// check functions
+	for (a = com_alias; a; a = a->next)
+		if (!strncmp(partial, a->name, len))
+			if (!skips--)
+				return a->name;
+
+	return NULL;
+}
+
 /** Parses a single line of text into arguments and tries to execute it.
   * The text can come from the command buffer, a remote client, or stdin.
   *
@@ -620,7 +659,7 @@ static void COM_ExecuteString(char *ptext)
 
 	// check cvars
 	// Hurdler: added at Ebola's request ;)
-	// (don't flood the console in software mode with bad gr_xxx command)
+	// (don't flood the console in software mode with bad gl_xxx command)
 	if (!CV_Command() && con_destlines)
 		CONS_Printf(M_GetText("Unknown command '%s'\n"), COM_Argv(0));
 }
@@ -812,6 +851,18 @@ static void COM_Help_f(void)
 					CONS_Printf("  Yes or No (On or Off, 1 or 0)\n");
 				else if (cvar->PossibleValue == CV_OnOff)
 					CONS_Printf("  On or Off (Yes or No, 1 or 0)\n");
+				else if (cvar->PossibleValue == Color_cons_t)
+				{
+					for (i = 1; i < numskincolors; ++i)
+					{
+						if (skincolors[i].accessible)
+						{
+							CONS_Printf("  %-2d : %s\n", i, skincolors[i].name);
+							if (i == cvar->value)
+								cvalue = skincolors[i].name;
+						}
+					}
+				}
 				else
 				{
 #define MINVAL 0
@@ -856,6 +907,9 @@ static void COM_Help_f(void)
 				CONS_Printf(" Current value: %s\n", cvar->string);
 			else
 				CONS_Printf(" Current value: %d\n", cvar->value);
+
+			if (cvar->revert.v.string != NULL && strcmp(cvar->revert.v.string, cvar->string) != 0)
+				CONS_Printf(" Value before netgame: %s\n", cvar->revert.v.string);
 		}
 		else
 		{
@@ -1121,14 +1175,16 @@ consvar_t *CV_FindVar(const char *name)
 	return NULL;
 }
 
-/** Builds a unique Net Variable identifier number, which is used
-  * in network packets instead of the full name.
+#ifdef OLD22DEMOCOMPAT
+/** Builds a unique Net Variable identifier number, which was used
+  * in network packets and demos instead of the full name.
+  *
+  * This function only still exists to keep compatibility with old demos.
   *
   * \param s Name of the variable.
   * \return A new unique identifier.
-  * \sa CV_FindNetVar
   */
-static inline UINT16 CV_ComputeNetid(const char *s)
+static inline UINT16 CV_ComputeOldDemoID(const char *s)
 {
 	UINT16 ret = 0, i = 0;
 	static UINT16 premiers[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
@@ -1142,16 +1198,47 @@ static inline UINT16 CV_ComputeNetid(const char *s)
 	return ret;
 }
 
+/** Finds a net variable based on its old style hash. If a hash collides, a
+  * warning is printed and this function returns NULL.
+  *
+  * \param chk The variable's old style hash.
+  * \return A pointer to the variable itself if found, or NULL.
+  */
+static old_demo_var_t *CV_FindOldDemoVar(UINT16 chk)
+{
+	old_demo_var_t *demovar;
+
+	for (demovar = consvar_old_demo_vars; demovar; demovar = demovar->next)
+	{
+		if (demovar->checksum == chk)
+		{
+			if (demovar->collides)
+			{
+				CONS_Alert(CONS_WARNING,
+						"Old demo netvar id %hu is a collision\n", chk);
+				return NULL;
+			}
+
+			return demovar;
+		}
+	}
+
+	return NULL;
+}
+#endif/*OLD22DEMOCOMPAT*/
+
 /** Finds a net variable based on its identifier number.
   *
   * \param netid The variable's identifier number.
   * \return A pointer to the variable itself if found, or NULL.
-  * \sa CV_ComputeNetid
   */
 static consvar_t *CV_FindNetVar(UINT16 netid)
 {
 	consvar_t *cvar;
 
+	if (netid > consvar_number_of_netids)
+		return NULL;
+
 	for (cvar = consvar_vars; cvar; cvar = cvar->next)
 		if (cvar->netid == netid)
 			return cvar;
@@ -1161,6 +1248,32 @@ static consvar_t *CV_FindNetVar(UINT16 netid)
 
 static void Setvalue(consvar_t *var, const char *valstr, boolean stealth);
 
+#ifdef OLD22DEMOCOMPAT
+/* Sets up a netvar for compatibility with old demos. */
+static void CV_RegisterOldDemoVar(consvar_t *variable)
+{
+	old_demo_var_t *demovar;
+	UINT16 old_demo_id;
+
+	old_demo_id = CV_ComputeOldDemoID(variable->name);
+
+	demovar = CV_FindOldDemoVar(old_demo_id);
+
+	if (demovar)
+		demovar->collides = true;
+	else
+	{
+		demovar = ZZ_Calloc(sizeof *demovar);
+
+		demovar->checksum = old_demo_id;
+		demovar->cvar = variable;
+
+		demovar->next = consvar_old_demo_vars;
+		consvar_old_demo_vars = demovar;
+	}
+}
+#endif
+
 /** Registers a variable for later use from the console.
   *
   * \param variable The variable to register.
@@ -1184,11 +1297,15 @@ void CV_RegisterVar(consvar_t *variable)
 	// check net variables
 	if (variable->flags & CV_NETVAR)
 	{
-		const consvar_t *netvar;
-		variable->netid = CV_ComputeNetid(variable->name);
-		netvar = CV_FindNetVar(variable->netid);
-		if (netvar)
-			I_Error("Variables %s and %s have same netid\n", variable->name, netvar->name);
+		/* in case of overflow... */
+		if (consvar_number_of_netids == UINT16_MAX)
+			I_Error("Way too many netvars");
+
+		variable->netid = ++consvar_number_of_netids;
+
+#ifdef OLD22DEMOCOMPAT
+		CV_RegisterOldDemoVar(variable);
+#endif
 	}
 
 	// link the variable in
@@ -1198,6 +1315,7 @@ void CV_RegisterVar(consvar_t *variable)
 		consvar_vars = variable;
 	}
 	variable->string = variable->zstring = NULL;
+	memset(&variable->revert, 0, sizeof variable->revert);
 	variable->changed = 0; // new variable has not been modified by the user
 
 #ifdef PARANOIA
@@ -1239,7 +1357,7 @@ static const char *CV_StringValue(const char *var_name)
   * \param partial The partial name of the variable (potentially).
   * \param skips   Number of variables to skip.
   * \return The complete variable name, or NULL.
-  * \sa COM_CompleteCommand
+  * \sa COM_CompleteCommand, CV_CompleteAlias
   */
 const char *CV_CompleteVar(char *partial, INT32 skips)
 {
@@ -1310,6 +1428,18 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 			for (i = MAXVAL+1; var->PossibleValue[i].strvalue; i++)
 				if (v == var->PossibleValue[i].value || !stricmp(var->PossibleValue[i].strvalue, valstr))
 				{
+					if (client && execversion_enabled)
+					{
+						if (var->revert.allocated)
+						{
+							Z_Free(var->revert.v.string);
+						}
+
+						var->revert.v.const_munge = var->PossibleValue[i].strvalue;
+
+						return;
+					}
+
 					var->value = var->PossibleValue[i].value;
 					var->string = var->PossibleValue[i].strvalue;
 					goto finish;
@@ -1370,12 +1500,36 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 			// ...or not.
 			goto badinput;
 found:
+			if (client && execversion_enabled)
+			{
+				if (var->revert.allocated)
+				{
+					Z_Free(var->revert.v.string);
+				}
+
+				var->revert.v.const_munge = var->PossibleValue[i].strvalue;
+
+				return;
+			}
+
 			var->value = var->PossibleValue[i].value;
 			var->string = var->PossibleValue[i].strvalue;
 			goto finish;
 		}
 	}
 
+	if (client && execversion_enabled)
+	{
+		if (var->revert.allocated)
+		{
+			Z_Free(var->revert.v.string);
+		}
+
+		var->revert.v.string = Z_StrDup(valstr);
+
+		return;
+	}
+
 	// free the old value string
 	Z_Free(var->zstring);
 
@@ -1448,12 +1602,100 @@ badinput:
 
 static boolean serverloading = false;
 
+static consvar_t *
+ReadNetVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	UINT16  netid;
+	char   *val;
+	boolean stealth;
+
+	consvar_t *cvar;
+
+	netid   = READUINT16 (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	cvar = CV_FindNetVar(netid);
+
+	if (cvar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+
+		DEBFILE(va("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, val));
+	}
+	else
+		CONS_Alert(CONS_WARNING, "Netvar not found with netid %hu\n", netid);
+
+	return cvar;
+}
+
+#ifdef OLD22DEMOCOMPAT
+static consvar_t *
+ReadOldDemoVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	UINT16  id;
+	char   *val;
+	boolean stealth;
+
+	old_demo_var_t *demovar;
+
+	id      = READUINT16 (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	demovar = CV_FindOldDemoVar(id);
+
+	if (demovar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+
+		return demovar->cvar;
+	}
+	else
+	{
+		CONS_Alert(CONS_WARNING, "Netvar not found with old demo id %hu\n", id);
+		return NULL;
+	}
+}
+#endif/*OLD22DEMOCOMPAT*/
+
+static consvar_t *
+ReadDemoVar (UINT8 **p, char **return_value, boolean *return_stealth)
+{
+	char   *name;
+	char   *val;
+	boolean stealth;
+
+	consvar_t *cvar;
+
+	name    = (char *)*p;
+	SKIPSTRING (*p);
+	val     = (char *)*p;
+	SKIPSTRING (*p);
+	stealth = READUINT8  (*p);
+
+	cvar = CV_FindVar(name);
+
+	if (cvar)
+	{
+		(*return_value)   = val;
+		(*return_stealth) = stealth;
+	}
+	else
+		CONS_Alert(CONS_WARNING, "Netvar not found with name %s\n", name);
+
+	return cvar;
+}
+
 static void Got_NetVar(UINT8 **p, INT32 playernum)
 {
 	consvar_t *cvar;
-	UINT16 netid;
 	char *svalue;
-	UINT8 stealth = false;
+	boolean stealth;
 
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum) && !serverloading)
 	{
@@ -1463,23 +1705,14 @@ static void Got_NetVar(UINT8 **p, INT32 playernum)
 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
-	netid = READUINT16(*p);
-	cvar = CV_FindNetVar(netid);
-	svalue = (char *)*p;
-	SKIPSTRING(*p);
-	stealth = READUINT8(*p);
 
-	if (!cvar)
-	{
-		CONS_Alert(CONS_WARNING, "Netvar not found with netid %hu\n", netid);
-		return;
-	}
-	DEBFILE(va("Netvar received: %s [netid=%d] value %s\n", cvar->name, netid, svalue));
+	cvar = ReadNetVar(p, &svalue, &stealth);
 
-	Setvalue(cvar, svalue, stealth);
+	if (cvar)
+		Setvalue(cvar, svalue, stealth);
 }
 
-void CV_SaveNetVars(UINT8 **p)
+void CV_SaveVars(UINT8 **p, boolean in_demo)
 {
 	consvar_t *cvar;
 	UINT8 *count_p = *p;
@@ -1491,7 +1724,10 @@ void CV_SaveNetVars(UINT8 **p)
 	for (cvar = consvar_vars; cvar; cvar = cvar->next)
 		if ((cvar->flags & CV_NETVAR) && !CV_IsSetToDefault(cvar))
 		{
-			WRITEUINT16(*p, cvar->netid);
+			if (in_demo)
+				WRITESTRING(*p, cvar->name);
+			else
+				WRITEUINT16(*p, cvar->netid);
 			WRITESTRING(*p, cvar->string);
 			WRITEUINT8(*p, false);
 			++count;
@@ -1499,25 +1735,82 @@ void CV_SaveNetVars(UINT8 **p)
 	WRITEUINT16(count_p, count);
 }
 
-void CV_LoadNetVars(UINT8 **p)
+static void CV_LoadVars(UINT8 **p,
+		consvar_t *(*got)(UINT8 **p, char **ret_value, boolean *ret_stealth))
 {
 	consvar_t *cvar;
 	UINT16 count;
 
+	char *val;
+	boolean stealth;
+
 	// prevent "invalid command received"
 	serverloading = true;
 
 	for (cvar = consvar_vars; cvar; cvar = cvar->next)
+	{
 		if (cvar->flags & CV_NETVAR)
+		{
+			if (client && cvar->revert.v.string == NULL)
+			{
+				cvar->revert.v.const_munge = cvar->string;
+				cvar->revert.allocated = ( cvar->zstring != NULL );
+				cvar->zstring = NULL;/* don't free this */
+			}
+
 			Setvalue(cvar, cvar->defaultvalue, true);
+		}
+	}
 
 	count = READUINT16(*p);
 	while (count--)
-		Got_NetVar(p, 0);
+	{
+		cvar = (*got)(p, &val, &stealth);
+
+		if (cvar)
+			Setvalue(cvar, val, stealth);
+	}
 
 	serverloading = false;
 }
 
+void CV_RevertNetVars(void)
+{
+	consvar_t * cvar;
+
+	for (cvar = consvar_vars; cvar; cvar = cvar->next)
+	{
+		if (cvar->revert.v.string != NULL)
+		{
+			Setvalue(cvar, cvar->revert.v.string, false);
+
+			if (cvar->revert.allocated)
+			{
+				Z_Free(cvar->revert.v.string);
+			}
+
+			cvar->revert.v.string = NULL;
+		}
+	}
+}
+
+void CV_LoadNetVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadNetVar);
+}
+
+#ifdef OLD22DEMOCOMPAT
+void CV_LoadOldDemoVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadOldDemoVar);
+}
+#endif
+
+void CV_LoadDemoVars(UINT8 **p)
+{
+	CV_LoadVars(p, ReadDemoVar);
+}
+
 static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth);
 
 void CV_ResetCheatNetVars(void)
@@ -1574,6 +1867,14 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
 		// send the value of the variable
 		UINT8 buf[128];
 		UINT8 *p = buf;
+
+		// Loading from a config in a netgame? Set revert value.
+		if (client && execversion_enabled)
+		{
+			Setvalue(var, value, true);
+			return;
+		}
+
 		if (!(server || (addedtogame && IsPlayerAdmin(consoleplayer))))
 		{
 			CONS_Printf(M_GetText("Only the server or admin can change: %s %s\n"), var->name, var->string);
@@ -2107,18 +2408,43 @@ void CV_SaveVariables(FILE *f)
 		{
 			char stringtowrite[MAXTEXTCMD+1];
 
-			// Silly hack for Min/Max vars
-			if (!strcmp(cvar->string, "MAX") || !strcmp(cvar->string, "MIN"))
+			const char * string;
+
+			if (cvar->revert.v.string != NULL)
 			{
-				if (cvar->flags & CV_FLOAT)
-					sprintf(stringtowrite, "%f", FIXED_TO_FLOAT(cvar->value));
-				else
-					sprintf(stringtowrite, "%d", cvar->value);
+				string = cvar->revert.v.string;
 			}
 			else
-				strcpy(stringtowrite, cvar->string);
+			{
+				string = cvar->string;
+			}
 
-			fprintf(f, "%s \"%s\"\n", cvar->name, stringtowrite);
+			// Silly hack for Min/Max vars
+#define MINVAL 0
+#define MAXVAL 1
+			if (
+					cvar->PossibleValue != NULL &&
+					cvar->PossibleValue[0].strvalue &&
+					stricmp(cvar->PossibleValue[0].strvalue, "MIN") == 0
+			){ // bounded cvar
+				int which = stricmp(string, "MAX") == 0;
+
+				if (which || stricmp(string, "MIN") == 0)
+				{
+					INT32 value = cvar->PossibleValue[which].value;
+
+					if (cvar->flags & CV_FLOAT)
+						sprintf(stringtowrite, "%f", FIXED_TO_FLOAT(value));
+					else
+						sprintf(stringtowrite, "%d", value);
+
+					string = stringtowrite;
+				}
+			}
+#undef MINVAL
+#undef MAXVAL
+
+			fprintf(f, "%s \"%s\"\n", cvar->name, string);
 		}
 }
 
@@ -2180,15 +2506,6 @@ skipwhite:
 		}
 	}
 
-	// parse single characters
-	if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'')
-	{
-		com_token[len] = c;
-		len++;
-		com_token[len] = 0;
-		return data + 1;
-	}
-
 	// parse a regular word
 	do
 	{
@@ -2208,8 +2525,6 @@ skipwhite:
 			len++;
 			c = *data;
 		}
-		if (c == '{' || c == '}' || c == ')'|| c == '(' || c == '\'')
-			break;
 	} while (c > 32);
 
 	com_token[len] = 0;
diff --git a/src/command.h b/src/command.h
index 404052ce4775e301cb819931cfaad9ebe369e1f4..d4033e6efe963db1523b709de045012f3c381900 100644
--- a/src/command.h
+++ b/src/command.h
@@ -49,6 +49,8 @@ size_t COM_FirstOption(void);
 // match existing command or NULL
 const char *COM_CompleteCommand(const char *partial, INT32 skips);
 
+const char *COM_CompleteAlias(const char *partial, INT32 skips);
+
 // insert at queu (at end of other command)
 #define COM_BufAddText(s) COM_BufAddTextEx(s, 0)
 void COM_BufAddTextEx(const char *btext, int flags);
@@ -138,12 +140,39 @@ typedef struct consvar_s //NULL, NULL, 0, NULL, NULL |, 0, NULL, NULL, 0, 0, NUL
 	const char *string;   // value in string
 	char *zstring;        // Either NULL or same as string.
 	                      // If non-NULL, must be Z_Free'd later.
+	struct
+	{
+		char allocated; // whether to Z_Free
+		union
+		{
+			char       * string;
+			const char * const_munge;
+		} v;
+	} revert;             // value of netvar before joining netgame
+
 	UINT16 netid; // used internaly : netid for send end receive
 	                      // used only with CV_NETVAR
 	char changed;         // has variable been changed by the user? 0 = no, 1 = yes
 	struct consvar_s *next;
 } consvar_t;
 
+/* name, defaultvalue, flags, PossibleValue, func */
+#define CVAR_INIT( ... ) \
+{ __VA_ARGS__, 0, NULL, NULL, {0, {NULL}}, 0U, (char)0, NULL }
+
+#ifdef OLD22DEMOCOMPAT
+typedef struct old_demo_var old_demo_var_t;
+
+struct old_demo_var
+{
+	UINT16  checksum;
+	boolean collides;/* this var is a collision of multiple hashes */
+
+	consvar_t      *cvar;
+	old_demo_var_t *next;
+};
+#endif/*OLD22DEMOCOMPAT*/
+
 extern CV_PossibleValue_t CV_OnOff[];
 extern CV_PossibleValue_t CV_YesNo[];
 extern CV_PossibleValue_t CV_Unsigned[];
@@ -184,9 +213,21 @@ void CV_AddValue(consvar_t *var, INT32 increment);
 void CV_SaveVariables(FILE *f);
 
 // load/save gamesate (load and save option and for network join in game)
-void CV_SaveNetVars(UINT8 **p);
+void CV_SaveVars(UINT8 **p, boolean in_demo);
+
+#define CV_SaveNetVars(p) CV_SaveVars(p, false)
 void CV_LoadNetVars(UINT8 **p);
 
+// then revert after leaving a netgame
+void CV_RevertNetVars(void);
+
+#define CV_SaveDemoVars(p) CV_SaveVars(p, true)
+void CV_LoadDemoVars(UINT8 **p);
+
+#ifdef OLD22DEMOCOMPAT
+void CV_LoadOldDemoVars(UINT8 **p);
+#endif
+
 // reset cheat netvars after cheats is deactivated
 void CV_ResetCheatNetVars(void);
 
diff --git a/src/config.h.in b/src/config.h.in
index 595bea7b388f01de7375c78db36adb025b0dc9ae..a6f43a7d7b6ab1df4f2abc00e110b97c4290a0cd 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -32,12 +32,14 @@
  * Last updated 2020 / 05 / 11 - v2.2.4 - patch.pk3
  * Last updated 2020 / 07 / 07 - v2.2.5 - player.dta & patch.pk3
  * Last updated 2020 / 07 / 10 - v2.2.6 - player.dta & patch.pk3
+ * Last updated 2020 / 09 / 27 - v2.2.7 - patch.pk3
+ * Last updated 2020 / 10 / 02 - v2.2.8 - patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "0277c9416756627004e83cbb5b2e3e28"
 #define ASSET_HASH_ZONES_PK3  "f7e88afb6af7996a834c7d663144bead"
 #define ASSET_HASH_PLAYER_DTA "49dad7b24634c89728cc3e0b689e12bb"
 #ifdef USE_PATCH_DTA
-#define ASSET_HASH_PATCH_PK3  "ecf00060f03c76b3e49c6ae3925b627f"
+#define ASSET_HASH_PATCH_PK3  "466cdf60075262b3f5baa5e07f0999e8"
 #endif
 
 #endif
diff --git a/src/console.c b/src/console.c
index aac94d473c7cd2469c3700bd9f25cdf3c6b7d961..121605b10ea53c7f42d5b6c6462dd8145dc77a3a 100644
--- a/src/console.c
+++ b/src/console.c
@@ -29,6 +29,7 @@
 #include "i_video.h"
 #include "z_zone.h"
 #include "i_system.h"
+#include "i_threads.h"
 #include "d_main.h"
 #include "m_menu.h"
 #include "filesrch.h"
@@ -44,8 +45,19 @@
 
 #define MAXHUDLINES 20
 
+#ifdef HAVE_THREADS
+I_mutex con_mutex;
+
+#  define Lock_state()    I_lock_mutex(&con_mutex)
+#  define Unlock_state() I_unlock_mutex(con_mutex)
+#else/*HAVE_THREADS*/
+#  define Lock_state()
+#  define Unlock_state()
+#endif/*HAVE_THREADS*/
+
 static boolean con_started = false; // console has been initialised
-       boolean con_startup = false; // true at game startup, screen need refreshing
+       boolean con_startup = false; // true at game startup
+       boolean con_refresh = false; // screen needs refreshing
 static boolean con_forcepic = true; // at startup toggle console translucency when first off
        boolean con_recalc;          // set true when screen size has changed
 
@@ -113,22 +125,22 @@ static void CONS_backcolor_Change(void);
 static char con_buffer[CON_BUFFERSIZE];
 
 // how many seconds the hud messages lasts on the screen
-static consvar_t cons_msgtimeout = {"con_hudtime", "5", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cons_msgtimeout = CVAR_INIT ("con_hudtime", "5", CV_SAVE, CV_Unsigned, NULL);
 
 // number of lines displayed on the HUD
-static consvar_t cons_hudlines = {"con_hudlines", "5", CV_CALL|CV_SAVE, CV_Unsigned, CONS_hudlines_Change, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cons_hudlines = CVAR_INIT ("con_hudlines", "5", CV_CALL|CV_SAVE, CV_Unsigned, CONS_hudlines_Change);
 
 // number of lines console move per frame
 // (con_speed needs a limit, apparently)
 static CV_PossibleValue_t speed_cons_t[] = {{0, "MIN"}, {64, "MAX"}, {0, NULL}};
-static consvar_t cons_speed = {"con_speed", "8", CV_SAVE, speed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cons_speed = CVAR_INIT ("con_speed", "8", CV_SAVE, speed_cons_t, NULL);
 
 // percentage of screen height to use for console
-static consvar_t cons_height = {"con_height", "50", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cons_height = CVAR_INIT ("con_height", "50", CV_SAVE, CV_Unsigned, NULL);
 
 static CV_PossibleValue_t backpic_cons_t[] = {{0, "translucent"}, {1, "picture"}, {0, NULL}};
 // whether to use console background picture, or translucent mode
-static consvar_t cons_backpic = {"con_backpic", "translucent", CV_SAVE, backpic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cons_backpic = CVAR_INIT ("con_backpic", "translucent", CV_SAVE, backpic_cons_t, NULL);
 
 static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, 		{1, "Black"},		{2, "Sepia"},
 												{3, "Brown"},		{4, "Pink"},		{5, "Raspberry"},
@@ -140,7 +152,7 @@ static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, 		{1, "Black"},		{
 												{0, NULL}};
 
 
-consvar_t cons_backcolor = {"con_backcolor", "Green", CV_CALL|CV_SAVE, backcolor_cons_t, CONS_backcolor_Change, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cons_backcolor = CVAR_INIT ("con_backcolor", "Green", CV_CALL|CV_SAVE, backcolor_cons_t, CONS_backcolor_Change);
 
 static void CON_Print(char *msg);
 
@@ -150,6 +162,8 @@ static void CONS_hudlines_Change(void)
 {
 	INT32 i;
 
+	Lock_state();
+
 	// Clear the currently displayed lines
 	for (i = 0; i < con_hudlines; i++)
 		con_hudtime[i] = 0;
@@ -161,6 +175,8 @@ static void CONS_hudlines_Change(void)
 
 	con_hudlines = cons_hudlines.value;
 
+	Unlock_state();
+
 	CONS_Printf(M_GetText("Number of console HUD lines is now %d\n"), con_hudlines);
 }
 
@@ -168,12 +184,16 @@ static void CONS_hudlines_Change(void)
 //
 static void CONS_Clear_f(void)
 {
+	Lock_state();
+
 	memset(con_buffer, 0, CON_BUFFERSIZE);
 
 	con_cx = 0;
 	con_cy = con_totallines-1;
 	con_line = &con_buffer[con_cy*con_width];
 	con_scrollup = 0;
+
+	Unlock_state();
 }
 
 // Choose english keymap
@@ -340,30 +360,48 @@ static void CON_SetupColormaps(void)
 	for (i = 0; i < (256*15); i++, ++memorysrc)
 		*memorysrc = (UINT8)(i & 0xFF); // remap each color to itself...
 
-#define colset(map, a, b, c) \
-	map[1] = (UINT8)a;\
-	map[3] = (UINT8)b;\
-	map[9] = (UINT8)c
-
-	colset(magentamap, 177, 178, 184);
-	colset(yellowmap,   82,  73,  66);
-	colset(lgreenmap,   97,  98, 106);
-	colset(bluemap,    146, 147, 155);
-	colset(redmap,     210,  32,  39);
-	colset(graymap,      6,  8,   14);
-	colset(orangemap,   51,  52,  57);
-	colset(skymap,     129, 130, 133);
-	colset(purplemap,  160, 161, 163);
-	colset(aquamap,    120, 121, 123);
-	colset(peridotmap,  88, 188, 190);
-	colset(azuremap,   144, 145, 170);
-	colset(brownmap,   219, 221, 224);
-	colset(rosymap,    200, 201, 203);
-	colset(invertmap,   27,  26,  22);
-	invertmap[26] = (UINT8)3;
+#define colset(map, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
+	map[0x0] = (UINT8)a;\
+	map[0x1] = (UINT8)b;\
+	map[0x2] = (UINT8)c;\
+	map[0x3] = (UINT8)d;\
+	map[0x4] = (UINT8)e;\
+	map[0x5] = (UINT8)f;\
+	map[0x6] = (UINT8)g;\
+	map[0x7] = (UINT8)h;\
+	map[0x8] = (UINT8)i;\
+	map[0x9] = (UINT8)j;\
+	map[0xA] = (UINT8)k;\
+	map[0xB] = (UINT8)l;\
+	map[0xC] = (UINT8)m;\
+	map[0xD] = (UINT8)n;\
+	map[0xE] = (UINT8)o;\
+	map[0xF] = (UINT8)p;
+
+	// Tried to keep the colors vanilla while adding some shades in between them ~SonicX8000
+
+	//                      0x1       0x3                           0x9                           0xF
+	colset(magentamap, 177, 177, 178, 178, 178, 180, 180, 180, 182, 182, 182, 182, 184, 184, 184, 185);
+	colset(yellowmap,   82,  82,  73,  73,  73,  64,  64,  64,  66,  66,  66,  66,  67,  67,  67,  68);
+	colset(lgreenmap,   96,  96,  98,  98,  98, 101, 101, 101, 104, 104, 104, 104, 106, 106, 106, 107);
+	colset(bluemap,    146, 146, 147, 147, 147, 149, 149, 149, 152, 152, 152, 152, 155, 155, 155, 157);
+	colset(redmap,      32,  32,  33,  33,  33,  35,  35,  35,  39,  39,  39,  39,  42,  42,  42,  44);
+	colset(graymap,      8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23);
+	colset(orangemap,   50,  50,  52,  52,  52,  54,  54,  54,  56,  56,  56,  56,  59,  59,  59,  60);
+	colset(skymap,     129, 129, 130, 130, 130, 131, 131, 131, 133, 133, 133, 133, 135, 135, 135, 136);
+	colset(purplemap,  160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 163, 164, 164, 164, 165);
+	colset(aquamap,    120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 123, 124, 124, 124, 125);
+	colset(peridotmap,  72,  72, 188, 188, 189, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191,  94);
+	colset(azuremap,   144, 144, 145, 145, 145, 146, 146, 146, 170, 170, 170, 170, 171, 171, 171, 172);
+	colset(brownmap,   219, 219, 221, 221, 221, 222, 222, 222, 224, 224, 224, 224, 227, 227, 227, 229);
+	colset(rosymap,    200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 203, 204, 204, 204, 205);
 
 #undef colset
 
+	// Yeah just straight up invert it like a normal person
+	for (i = 0x00; i <= 0x1F; i++)
+		invertmap[0x1F - i] = i;
+
 	// Init back colormap
 	CON_SetupBackColormap();
 }
@@ -377,20 +415,29 @@ void CON_Init(void)
 	for (i = 0; i < NUMINPUTS; i++)
 		bindtable[i] = NULL;
 
+	Lock_state();
+
 	// clear all lines
 	memset(con_buffer, 0, CON_BUFFERSIZE);
 
 	// make sure it is ready for the loading screen
 	con_width = 0;
+
+	Unlock_state();
+
 	CON_RecalcSize();
 
 	CON_SetupColormaps();
 
+	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();
+
 	// setup console input filtering
 	CON_InputInit();
 
@@ -399,15 +446,24 @@ void CON_Init(void)
 	COM_AddCommand("cls", CONS_Clear_f);
 	//COM_AddCommand("english", CONS_English_f);
 	// set console full screen for game startup MAKE SURE VID_Init() done !!!
+	Lock_state();
+
 	con_destlines = vid.height;
 	con_curlines = vid.height;
 
+	Unlock_state();
 
 	if (!dedicated)
 	{
+		Lock_state();
+
 		con_started = true;
-		con_startup = true; // need explicit screen refresh until we are in Doom loop
+		con_startup = true;
+		con_refresh = true; // needs explicit screen refresh until we are in the main game loop
 		consoletoggle = false;
+
+		Unlock_state();
+
 		CV_RegisterVar(&cons_msgtimeout);
 		CV_RegisterVar(&cons_hudlines);
 		CV_RegisterVar(&cons_speed);
@@ -418,19 +474,28 @@ void CON_Init(void)
 	}
 	else
 	{
+		Lock_state();
+
 		con_started = true;
-		con_startup = false; // need explicit screen refresh until we are in Doom loop
+		con_startup = false;
+		con_refresh = false; // disable explicit screen refresh
 		consoletoggle = true;
+
+		Unlock_state();
 	}
 }
 // Console input initialization
 //
 static void CON_InputInit(void)
 {
+	Lock_state();
+
 	// prepare the first prompt line
 	memset(inputlines, 0, sizeof (inputlines));
 	inputline = 0;
 	input_cur = input_sel = input_len = 0;
+
+	Unlock_state();
 }
 
 //======================================================================
@@ -446,6 +511,8 @@ static void CON_RecalcSize(void)
 	char *tmp_buffer;
 	char *string;
 
+	Lock_state();
+
 	switch (cv_constextsize.value)
 	{
 	case V_NOSCALEPATCH:
@@ -483,11 +550,18 @@ static void CON_RecalcSize(void)
 
 	// check for change of video width
 	if (conw == con_width)
+	{
+		Unlock_state();
 		return; // didn't change
+	}
+
+	Unlock_state();
 
 	tmp_buffer = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL);
 	string = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL); // BP: it is a line but who know
 
+	Lock_state();
+
 	oldcon_width = con_width;
 	oldnumlines = con_totallines;
 	oldcon_cy = con_cy;
@@ -508,6 +582,8 @@ static void CON_RecalcSize(void)
 	con_line = &con_buffer[con_cy*con_width];
 	con_scrollup = 0;
 
+	Unlock_state();
+
 	// re-arrange console text buffer to keep text
 	if (oldcon_width) // not the first time
 	{
@@ -532,7 +608,11 @@ static void CON_RecalcSize(void)
 
 static void CON_ChangeHeight(void)
 {
-	INT32 minheight = 20 * con_scalefactor;	// 20 = 8+8+4
+	INT32 minheight;
+
+	Lock_state();
+
+	minheight = 20 * con_scalefactor;	// 20 = 8+8+4
 
 	// toggle console in
 	con_destlines = (cons_height.value*vid.height)/100;
@@ -542,13 +622,19 @@ static void CON_ChangeHeight(void)
 		con_destlines = vid.height;
 
 	con_destlines &= ~0x3; // multiple of text row height
+
+	Unlock_state();
 }
 
 // Handles Console moves in/out of screen (per frame)
 //
 static void CON_MoveConsole(void)
 {
-	const fixed_t conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT);
+	fixed_t conspeed;
+
+	Lock_state();
+
+	conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT);
 
 	// instant
 	if (!cons_speed.value)
@@ -570,6 +656,8 @@ static void CON_MoveConsole(void)
 		if (con_curlines < con_destlines)
 			con_curlines = con_destlines;
 	}
+
+	Unlock_state();
 }
 
 // Clear time of console heads up messages
@@ -578,16 +666,25 @@ void CON_ClearHUD(void)
 {
 	INT32 i;
 
+	Lock_state();
+
 	for (i = 0; i < con_hudlines; i++)
 		con_hudtime[i] = 0;
+
+	Unlock_state();
 }
 
 // Force console to move out immediately
 // note: con_ticker will set consoleready false
 void CON_ToggleOff(void)
 {
+	Lock_state();
+
 	if (!con_destlines)
+	{
+		Unlock_state();
 		return;
+	}
 
 	con_destlines = 0;
 	con_curlines = 0;
@@ -596,11 +693,19 @@ void CON_ToggleOff(void)
 	con_clipviewtop = -1; // remove console clipping of view
 
 	I_UpdateMouseGrab();
+
+	Unlock_state();
 }
 
 boolean CON_Ready(void)
 {
-	return consoleready;
+	boolean ready;
+	Lock_state();
+	{
+		ready = consoleready;
+	}
+	Unlock_state();
+	return ready;
 }
 
 // Console ticker: handles console move in/out, cursor blinking
@@ -608,7 +713,11 @@ boolean CON_Ready(void)
 void CON_Ticker(void)
 {
 	INT32 i;
-	INT32 minheight = 20 * con_scalefactor;	// 20 = 8+8+4
+	INT32 minheight;
+
+	Lock_state();
+
+	minheight = 20 * con_scalefactor;	// 20 = 8+8+4
 
 	// cursor blinking
 	con_tick++;
@@ -659,6 +768,8 @@ void CON_Ticker(void)
 		if (con_hudtime[i] < 0)
 			con_hudtime[i] = 0;
 	}
+
+	Unlock_state();
 }
 
 //
@@ -670,32 +781,57 @@ void CON_Ticker(void)
 
 static void CON_InputClear(void)
 {
+	Lock_state();
+
 	memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
 	input_cur = input_sel = input_len = 0;
+
+	Unlock_state();
 }
 
 static void CON_InputSetString(const char *c)
 {
+	Lock_state();
+
 	memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
 	strcpy(inputlines[inputline], c);
 	input_cur = input_sel = input_len = strlen(c);
+
+	Unlock_state();
 }
 
 static void CON_InputAddString(const char *c)
 {
 	size_t csize = strlen(c);
+
+	Lock_state();
+
 	if (input_len + csize > CON_MAXPROMPTCHARS-1)
+	{
+		Unlock_state();
 		return;
+	}
 	if (input_cur != input_len)
 		memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur);
 	memcpy(&inputlines[inputline][input_cur], c, csize);
 	input_len += csize;
 	input_sel = (input_cur += csize);
+
+	Unlock_state();
 }
 
 static void CON_InputDelSelection(void)
 {
 	size_t start, end, len;
+
+	Lock_state();
+
+	if (!input_cur)
+	{
+		Unlock_state();
+		return;
+	}
+
 	if (input_cur > input_sel)
 	{
 		start = input_sel;
@@ -714,27 +850,39 @@ static void CON_InputDelSelection(void)
 
 	input_len -= len;
 	input_sel = input_cur = start;
+
+	Unlock_state();
 }
 
 static void CON_InputAddChar(char c)
 {
 	if (input_len >= CON_MAXPROMPTCHARS-1)
 		return;
+
+	Lock_state();
+
 	if (input_cur != input_len)
 		memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur);
 	inputlines[inputline][input_cur++] = c;
 	inputlines[inputline][++input_len] = 0;
 	input_sel = input_cur;
+
+	Unlock_state();
 }
 
 static void CON_InputDelChar(void)
 {
 	if (!input_cur)
 		return;
+
+	Lock_state();
+
 	if (input_cur != input_len)
 		memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur);
 	inputlines[inputline][--input_len] = 0;
 	input_sel = --input_cur;
+
+	Unlock_state();
 }
 
 //
@@ -749,9 +897,14 @@ boolean CON_Responder(event_t *ev)
 
 	// sequential completions a la 4dos
 	static char completion[80];
-	static INT32 comskips, varskips;
 
-	const char *cmd = "";
+	static INT32 skips;
+
+	static INT32   com_skips;
+	static INT32   var_skips;
+	static INT32 alias_skips;
+
+	const char *cmd = NULL;
 	INT32 key;
 
 	if (chat_on)
@@ -835,6 +988,44 @@ boolean CON_Responder(event_t *ev)
 		return true;
 	}
 
+	// backspace and delete command prompt
+	if (input_sel != input_cur)
+	{
+		if (key == KEY_BACKSPACE || key == KEY_DEL)
+		{
+			CON_InputDelSelection();
+			return true;
+		}
+	}
+	else if (key == KEY_BACKSPACE)
+	{
+		if (ctrldown)
+		{
+			input_sel = M_JumpWordReverse(inputlines[inputline], input_cur);
+			CON_InputDelSelection();
+		}
+		else
+			CON_InputDelChar();
+		return true;
+	}
+	else if (key == KEY_DEL)
+	{
+		if (input_cur == input_len)
+			return true;
+
+		if (ctrldown)
+		{
+			input_sel = input_cur + M_JumpWord(&inputlines[inputline][input_cur]);
+			CON_InputDelSelection();
+		}
+		else
+		{
+			++input_cur;
+			CON_InputDelChar();
+		}
+		return true;
+	}
+
 	// ctrl modifier -- changes behavior, adds shortcuts
 	if (ctrldown)
 	{
@@ -848,7 +1039,6 @@ boolean CON_Responder(event_t *ev)
 				if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
 					return true;
 				strcpy(completion, inputlines[inputline]);
-				comskips = varskips = 0;
 			}
 			len = strlen(completion);
 
@@ -864,6 +1054,14 @@ boolean CON_Responder(event_t *ev)
 				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
 			if (i == 0) CONS_Printf("  (none)\n");
 
+			//and finally aliases
+			CONS_Printf("Aliases:\n");
+			for (i = 0, cmd = COM_CompleteAlias(completion, i); cmd; cmd = COM_CompleteAlias(completion, ++i))
+				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
+			if (i == 0) CONS_Printf("  (none)\n");
+
+			completion[0] = 0;
+
 			return true;
 		}
 		// ---
@@ -932,43 +1130,64 @@ boolean CON_Responder(event_t *ev)
 			if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
 				return true;
 			strcpy(completion, inputlines[inputline]);
-			comskips = varskips = 0;
+			skips       = 0;
+			com_skips   = 0;
+			var_skips   = 0;
+			alias_skips = 0;
 		}
 		else
 		{
 			if (shiftdown)
 			{
-				if (comskips < 0)
-				{
-					if (--varskips < 0)
-						comskips = -comskips - 2;
-				}
-				else if (comskips > 0) comskips--;
+				if (skips > 0)
+					skips--;
 			}
 			else
 			{
-				if (comskips < 0) varskips++;
-				else              comskips++;
+				skips++;
 			}
 		}
 
-		if (comskips >= 0)
+		if (skips <= com_skips)
 		{
-			cmd = COM_CompleteCommand(completion, comskips);
-			if (!cmd) // dirty: make sure if comskips is zero, to have a neg value
-				comskips = -comskips - 1;
+			cmd = COM_CompleteCommand(completion, skips);
+
+			if (cmd && skips == com_skips)
+			{
+				com_skips  ++;
+				var_skips  ++;
+				alias_skips++;
+			}
+		}
+
+		if (!cmd && skips <= var_skips)
+		{
+			cmd = CV_CompleteVar(completion, skips - com_skips);
+
+			if (cmd && skips == var_skips)
+			{
+				var_skips  ++;
+				alias_skips++;
+			}
+		}
+
+		if (!cmd && skips <= alias_skips)
+		{
+			cmd = COM_CompleteAlias(completion, skips - var_skips);
+
+			if (cmd && skips == alias_skips)
+			{
+				alias_skips++;
+			}
 		}
-		if (comskips < 0)
-			cmd = CV_CompleteVar(completion, varskips);
 
 		if (cmd)
+		{
 			CON_InputSetString(va("%s ", cmd));
+		}
 		else
 		{
-			if (comskips > 0)
-				comskips--;
-			else if (varskips > 0)
-				varskips--;
+			skips--;
 		}
 
 		return true;
@@ -1025,29 +1244,6 @@ boolean CON_Responder(event_t *ev)
 		return true;
 	}
 
-	// backspace and delete command prompt
-	if (input_sel != input_cur)
-	{
-		if (key == KEY_BACKSPACE || key == KEY_DEL)
-		{
-			CON_InputDelSelection();
-			return true;
-		}
-	}
-	else if (key == KEY_BACKSPACE)
-	{
-		CON_InputDelChar();
-		return true;
-	}
-	else if (key == KEY_DEL)
-	{
-		if (input_cur == input_len)
-			return true;
-		++input_cur;
-		CON_InputDelChar();
-		return true;
-	}
-
 	// move back in input history
 	if (key == KEY_UPARROW)
 	{
@@ -1153,6 +1349,8 @@ static void CON_Print(char *msg)
 		S_StartSound(NULL, sfx_radio);
 	}
 
+	Lock_state();
+
 	if (!(*msg & 0x80))
 	{
 		con_line[con_cx++] = '\x80';
@@ -1213,7 +1411,10 @@ static void CON_Print(char *msg)
 		}
 
 		if (*msg == '\0')
+		{
+			Unlock_state();
 			return;
+		}
 
 		// printable character
 		for (l = 0; l < (con_width-11) && msg[l] > ' '; l++)
@@ -1231,6 +1432,8 @@ static void CON_Print(char *msg)
 		for (; l > 0; l--)
 			con_line[con_cx++] = *(msg++);
 	}
+
+	Unlock_state();
 }
 
 void CON_LogMessage(const char *msg)
@@ -1262,6 +1465,7 @@ void CONS_Printf(const char *fmt, ...)
 {
 	va_list argptr;
 	static char *txt = NULL;
+	boolean refresh;
 
 	if (txt == NULL)
 		txt = malloc(8192);
@@ -1273,41 +1477,25 @@ void CONS_Printf(const char *fmt, ...)
 	// echo console prints to log file
 	DEBFILE(txt);
 
-	if (!con_started)
-	{
-#ifdef PC_DOS
-		CON_LogMessage(txt);
-		free(txt);
-		return;
-#endif
-	}
-	else
-		// write message in con text buffer
+	// write message in con text buffer
+	if (con_started)
 		CON_Print(txt);
 
-#ifndef PC_DOS
 	CON_LogMessage(txt);
-#endif
+
+	Lock_state();
 
 	// make sure new text is visible
 	con_scrollup = 0;
+	refresh = con_refresh;
+
+	Unlock_state();
 
 	// if not in display loop, force screen update
-	if (con_startup && (!setrenderneeded))
+	if (refresh)
 	{
-#ifdef _WINDOWS
-		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_PATCH);
-
-		// Jimita: CON_DrawBackpic just called V_DrawScaledPatch
-		V_DrawScaledPatch(0, 0, 0, con_backpic);
-
-		W_UnlockCachedPatch(con_backpic);
-		I_LoadingScreen(txt);				// Win32/OS2 only
-#else
-		// here we display the console text
-		CON_Drawer();
+		CON_Drawer(); // here we display the console text
 		I_FinishUpdate(); // page flip or blit buffer
-#endif
 	}
 }
 
@@ -1369,7 +1557,7 @@ void CONS_Debug(INT32 debugflags, const char *fmt, ...)
 //
 void CONS_Error(const char *msg)
 {
-#ifdef RPC_NO_WINDOWS_H
+#if defined(RPC_NO_WINDOWS_H) && defined(_WINDOWS)
 	if (!graphics_started)
 	{
 		MessageBoxA(vid.WndParent, msg, "SRB2 Warning", MB_OK);
@@ -1518,7 +1706,7 @@ static void CON_DrawHudlines(void)
 				;//charwidth = 4 * con_scalefactor;
 			else
 			{
-				//charwidth = SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
+				//charwidth = (hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
 				V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
 			}
 		}
@@ -1538,13 +1726,17 @@ static void CON_DrawBackpic(void)
 	lumpnum_t piclump;
 	int x, w, h;
 
-	// Get the lumpnum for CONSBACK, or fallback into MISSING.
-	piclump = W_CheckNumForName("CONSBACK");
+	// Get the lumpnum for CONSBACK, STARTUP (Only during game startup) or fallback into MISSING.
+	if (con_startup)
+		piclump = W_CheckNumForName("STARTUP");
+	else
+		piclump = W_CheckNumForName("CONSBACK");
+
 	if (piclump == LUMPERROR)
 		piclump = W_GetNumForName("MISSING");
 
-	// Cache the Software patch.
-	con_backpic = W_CacheSoftwarePatchNum(piclump, PU_PATCH);
+	// Cache the patch.
+	con_backpic = W_CachePatchNum(piclump, PU_PATCH);
 
 	// Center the backpic, and draw a vertically cropped patch.
 	w = (con_backpic->width * vid.dupx);
@@ -1555,7 +1747,7 @@ static void CON_DrawBackpic(void)
 	// then fill the sides with a solid color.
 	if (x > 0)
 	{
-		column_t *column = (column_t *)((UINT8 *)(con_backpic) + LONG(con_backpic->columnofs[0]));
+		column_t *column = (column_t *)((UINT8 *)(con_backpic->columns) + (con_backpic->columnofs[0]));
 		if (!column->topdelta)
 		{
 			UINT8 *source = (UINT8 *)(column) + 3;
@@ -1567,8 +1759,7 @@ static void CON_DrawBackpic(void)
 		}
 	}
 
-	// Cache the patch normally.
-	con_backpic = W_CachePatchNum(piclump, PU_PATCH);
+	// Draw the patch.
 	V_DrawCroppedPatch(x << FRACBITS, 0, FRACUNIT, V_NOSCALESTART, con_backpic,
 			0, ( BASEVIDHEIGHT - h ), BASEVIDWIDTH, h);
 
@@ -1645,11 +1836,13 @@ static void CON_DrawConsole(void)
 //
 void CON_Drawer(void)
 {
+	Lock_state();
+
 	if (!con_started || !graphics_started)
+	{
+		Unlock_state();
 		return;
-
-	if (needpatchrecache)
-		HU_LoadGraphics();
+	}
 
 	if (con_recalc)
 	{
@@ -1664,4 +1857,6 @@ void CON_Drawer(void)
 	|| gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
 	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
 		CON_DrawHudlines();
+
+	Unlock_state();
 }
diff --git a/src/console.h b/src/console.h
index 2be92d62b8474011000522173bbbf91cdae4c68c..0296f4f6e658e82a01d78a2ae05f636d90e411ed 100644
--- a/src/console.h
+++ b/src/console.h
@@ -12,16 +12,25 @@
 
 #include "d_event.h"
 #include "command.h"
+#include "i_threads.h"
 
 void CON_Init(void);
 
 boolean CON_Responder(event_t *ev);
 
+#ifdef HAVE_THREADS
+extern I_mutex con_mutex;
+#endif
+
 // set true when screen size has changed, to adapt console
 extern boolean con_recalc;
 
+// console being displayed at game startup
 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;
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index e92341a664ee2cf250e5ecd9800db4e768eda01d..40d8611bcb270bf8979af1a01bde1ee3e8878283 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -44,6 +44,7 @@
 #include "lua_script.h"
 #include "lua_hook.h"
 #include "md5.h"
+#include "m_perfstats.h"
 
 #ifndef NONET
 // cl loading screen
@@ -83,6 +84,8 @@ char playeraddress[MAXPLAYERS][64];
 // The actual timeout will be longer depending on the savegame length
 tic_t jointimeout = (10*TICRATE);
 static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
+static boolean resendingsavegame[MAXNETNODES]; // Are we resending the savegame?
+static tic_t savegameresendcooldown[MAXNETNODES]; // How long before we can resend again?
 static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
 
 // Incremented by cv_joindelay when a client joins, decremented each tic.
@@ -106,15 +109,8 @@ static tic_t maketic;
 
 static INT16 consistancy[BACKUPTICS];
 
-// Resynching shit!
-static UINT32 resynch_score[MAXNETNODES]; // "score" for kicking -- if this gets too high then cfail kick
-static UINT16 resynch_delay[MAXNETNODES]; // delay time before the player can be considered to have desynched
-static UINT32 resynch_status[MAXNETNODES]; // 0 bit means synched for that player, 1 means possibly desynched
-static UINT8 resynch_sent[MAXNETNODES][MAXPLAYERS]; // what synch packets have we attempted to send to the player
-static UINT8 resynch_inprogress[MAXNETNODES];
-static UINT8 resynch_local_inprogress = false; // WE are desynched and getting packets to fix it.
 static UINT8 player_joining = false;
-UINT8 hu_resynching = 0;
+UINT8 hu_redownloadinggamestate = 0;
 
 UINT8 adminpassmd5[16];
 boolean adminpasswordset = false;
@@ -125,6 +121,7 @@ static ticcmd_t localcmds2;
 static boolean cl_packetmissed;
 // here it is for the secondary local player (splitscreen)
 static UINT8 mynode; // my address pointofview server
+static boolean cl_redownloadinggamestate = false;
 
 static UINT8 localtextcmd[MAXTEXTCMD];
 static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
@@ -157,10 +154,10 @@ ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
 static textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL};
 
 
-consvar_t cv_showjoinaddress = {"showjoinaddress", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_showjoinaddress = CVAR_INIT ("showjoinaddress", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
 
 static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}};
-consvar_t cv_playbackspeed = {"playbackspeed", "1", 0, playbackspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_playbackspeed = CVAR_INIT ("playbackspeed", "1", 0, playbackspeed_cons_t, NULL);
 
 static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n)
 {
@@ -508,603 +505,6 @@ void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum)
 // end extra data function for lmps
 // -----------------------------------------------------------------
 
-// -----------------------------------------------------------------
-// resynch player data
-// -----------------------------------------------------------------
-static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
-{
-	size_t j;
-
-	rsp->playernum = (UINT8)i;
-
-	// Do not send anything visual related.
-	// Only send data that we need to know for physics.
-	rsp->playerstate = (UINT8)players[i].playerstate; //playerstate_t
-	rsp->pflags = (UINT32)LONG(players[i].pflags); //pflags_t
-	rsp->panim  = (UINT8)players[i].panim; //panim_t
-
-	rsp->angleturn = (INT16)SHORT(players[i].angleturn);
-	rsp->oldrelangleturn = (INT16)SHORT(players[i].oldrelangleturn);
-
-	rsp->aiming = (angle_t)LONG(players[i].aiming);
-	rsp->currentweapon = LONG(players[i].currentweapon);
-	rsp->ringweapons = LONG(players[i].ringweapons);
-
-	rsp->ammoremoval = (UINT16)SHORT(players[i].ammoremoval);
-	rsp->ammoremovaltimer = (tic_t)LONG(players[i].ammoremovaltimer);
-	rsp->ammoremovalweapon = LONG(players[i].ammoremovalweapon);
-
-	for (j = 0; j < NUMPOWERS; ++j)
-		rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]);
-
-	// Score is resynched in the rspfirm resync packet
-	rsp->rings = SHORT(players[i].rings);
-	rsp->spheres = SHORT(players[i].spheres);
-	rsp->lives = players[i].lives;
-	rsp->continues = players[i].continues;
-	rsp->scoreadd = players[i].scoreadd;
-	rsp->xtralife = players[i].xtralife;
-	rsp->pity = players[i].pity;
-
-	rsp->skincolor = players[i].skincolor;
-	rsp->skin = LONG(players[i].skin);
-	rsp->availabilities = LONG(players[i].availabilities);
-	// Just in case Lua does something like
-	// modify these at runtime
-	rsp->camerascale = (fixed_t)LONG(players[i].camerascale);
-	rsp->shieldscale = (fixed_t)LONG(players[i].shieldscale);
-	rsp->normalspeed = (fixed_t)LONG(players[i].normalspeed);
-	rsp->runspeed = (fixed_t)LONG(players[i].runspeed);
-	rsp->thrustfactor = players[i].thrustfactor;
-	rsp->accelstart = players[i].accelstart;
-	rsp->acceleration = players[i].acceleration;
-	rsp->charability = players[i].charability;
-	rsp->charability2 = players[i].charability2;
-	rsp->charflags = (UINT32)LONG(players[i].charflags);
-	rsp->thokitem = (UINT32)LONG(players[i].thokitem); //mobjtype_t
-	rsp->spinitem = (UINT32)LONG(players[i].spinitem); //mobjtype_t
-	rsp->revitem = (UINT32)LONG(players[i].revitem); //mobjtype_t
-	rsp->followitem = (UINT32)LONG(players[i].followitem); //mobjtype_t
-	rsp->actionspd = (fixed_t)LONG(players[i].actionspd);
-	rsp->mindash = (fixed_t)LONG(players[i].mindash);
-	rsp->maxdash = (fixed_t)LONG(players[i].maxdash);
-	rsp->jumpfactor = (fixed_t)LONG(players[i].jumpfactor);
-	rsp->playerheight = (fixed_t)LONG(players[i].height);
-	rsp->playerspinheight = (fixed_t)LONG(players[i].spinheight);
-
-	rsp->speed = (fixed_t)LONG(players[i].speed);
-	rsp->secondjump = players[i].secondjump;
-	rsp->fly1 = players[i].fly1;
-	rsp->glidetime = (tic_t)LONG(players[i].glidetime);
-	rsp->climbing = players[i].climbing;
-	rsp->deadtimer = players[i].deadtimer;
-	rsp->exiting = (tic_t)LONG(players[i].exiting);
-	rsp->homing = players[i].homing;
-	rsp->dashmode = (tic_t)LONG(players[i].dashmode);
-	rsp->skidtime = (tic_t)LONG(players[i].skidtime);
-	rsp->cmomx = (fixed_t)LONG(players[i].cmomx);
-	rsp->cmomy = (fixed_t)LONG(players[i].cmomy);
-	rsp->rmomx = (fixed_t)LONG(players[i].rmomx);
-	rsp->rmomy = (fixed_t)LONG(players[i].rmomy);
-
-	rsp->weapondelay = LONG(players[i].weapondelay);
-	rsp->tossdelay = LONG(players[i].tossdelay);
-
-	rsp->starpostx = SHORT(players[i].starpostx);
-	rsp->starposty = SHORT(players[i].starposty);
-	rsp->starpostz = SHORT(players[i].starpostz);
-	rsp->starpostnum = LONG(players[i].starpostnum);
-	rsp->starposttime = (tic_t)LONG(players[i].starposttime);
-	rsp->starpostangle = (angle_t)LONG(players[i].starpostangle);
-	rsp->starpostscale = (fixed_t)LONG(players[i].starpostscale);
-
-	rsp->maxlink = LONG(players[i].maxlink);
-	rsp->dashspeed = (fixed_t)LONG(players[i].dashspeed);
-	rsp->angle_pos = (angle_t)LONG(players[i].angle_pos);
-	rsp->old_angle_pos = (angle_t)LONG(players[i].old_angle_pos);
-	rsp->bumpertime = (tic_t)LONG(players[i].bumpertime);
-	rsp->flyangle = LONG(players[i].flyangle);
-	rsp->drilltimer = (tic_t)LONG(players[i].drilltimer);
-	rsp->linkcount = LONG(players[i].linkcount);
-	rsp->linktimer = (tic_t)LONG(players[i].linktimer);
-	rsp->anotherflyangle = LONG(players[i].anotherflyangle);
-	rsp->nightstime = (tic_t)LONG(players[i].nightstime);
-	rsp->drillmeter = LONG(players[i].drillmeter);
-	rsp->drilldelay = players[i].drilldelay;
-	rsp->bonustime = players[i].bonustime;
-	rsp->mare = players[i].mare;
-	rsp->lastsidehit = SHORT(players[i].lastsidehit);
-	rsp->lastlinehit = SHORT(players[i].lastlinehit);
-
-	rsp->losstime = (tic_t)LONG(players[i].losstime);
-	rsp->timeshit = players[i].timeshit;
-	rsp->onconveyor = LONG(players[i].onconveyor);
-
-	rsp->hasmo = false;
-	//Transfer important mo information if the player has a body.
-	//This lets us resync players even if they are dead.
-	if (!players[i].mo)
-		return;
-	rsp->hasmo = true;
-
-	rsp->health = LONG(players[i].mo->health);
-	rsp->angle = (angle_t)LONG(players[i].mo->angle);
-	rsp->rollangle = (angle_t)LONG(players[i].mo->rollangle);
-	rsp->x = LONG(players[i].mo->x);
-	rsp->y = LONG(players[i].mo->y);
-	rsp->z = LONG(players[i].mo->z);
-	rsp->momx = LONG(players[i].mo->momx);
-	rsp->momy = LONG(players[i].mo->momy);
-	rsp->momz = LONG(players[i].mo->momz);
-	rsp->friction = LONG(players[i].mo->friction);
-	rsp->movefactor = LONG(players[i].mo->movefactor);
-
-	rsp->sprite = (spritenum_t)LONG(players[i].mo->sprite);
-	rsp->frame = LONG(players[i].mo->frame);
-	rsp->sprite2 = players[i].mo->sprite2;
-	rsp->anim_duration = SHORT(players[i].mo->anim_duration);
-	rsp->tics = LONG(players[i].mo->tics);
-	rsp->statenum = (statenum_t)LONG(players[i].mo->state-states); // :(
-	rsp->eflags = (UINT16)SHORT(players[i].mo->eflags);
-	rsp->flags = LONG(players[i].mo->flags);
-	rsp->flags2 = LONG(players[i].mo->flags2);
-
-	rsp->radius = LONG(players[i].mo->radius);
-	rsp->height = LONG(players[i].mo->height);
-	rsp->scale = LONG(players[i].mo->scale);
-	rsp->destscale = LONG(players[i].mo->destscale);
-	rsp->scalespeed = LONG(players[i].mo->scalespeed);
-}
-
-static void resynch_read_player(resynch_pak *rsp)
-{
-	INT32 i = rsp->playernum, j;
-	mobj_t *savedmo = players[i].mo;
-
-	// Do not send anything visual related.
-	// Only send data that we need to know for physics.
-	players[i].playerstate = (UINT8)rsp->playerstate; //playerstate_t
-	players[i].pflags = (UINT32)LONG(rsp->pflags); //pflags_t
-	players[i].panim  = (UINT8)rsp->panim; //panim_t
-
-	players[i].angleturn = (INT16)SHORT(rsp->angleturn);
-	players[i].oldrelangleturn = (INT16)SHORT(rsp->oldrelangleturn);
-
-	players[i].aiming = (angle_t)LONG(rsp->aiming);
-	players[i].currentweapon = LONG(rsp->currentweapon);
-	players[i].ringweapons = LONG(rsp->ringweapons);
-
-	players[i].ammoremoval = (UINT16)SHORT(rsp->ammoremoval);
-	players[i].ammoremovaltimer = (tic_t)LONG(rsp->ammoremovaltimer);
-	players[i].ammoremovalweapon = LONG(rsp->ammoremovalweapon);
-
-	for (j = 0; j < NUMPOWERS; ++j)
-		players[i].powers[j] = (UINT16)SHORT(rsp->powers[j]);
-
-	// Score is resynched in the rspfirm resync packet
-	players[i].rings = SHORT(rsp->rings);
-	players[i].spheres = SHORT(rsp->spheres);
-	players[i].lives = rsp->lives;
-	players[i].continues = rsp->continues;
-	players[i].scoreadd = rsp->scoreadd;
-	players[i].xtralife = rsp->xtralife;
-	players[i].pity = rsp->pity;
-
-	players[i].skincolor = rsp->skincolor;
-	players[i].skin = LONG(rsp->skin);
-	players[i].availabilities = LONG(rsp->availabilities);
-	// Just in case Lua does something like
-	// modify these at runtime
-	players[i].camerascale = (fixed_t)LONG(rsp->camerascale);
-	players[i].shieldscale = (fixed_t)LONG(rsp->shieldscale);
-	players[i].normalspeed = (fixed_t)LONG(rsp->normalspeed);
-	players[i].runspeed = (fixed_t)LONG(rsp->runspeed);
-	players[i].thrustfactor = rsp->thrustfactor;
-	players[i].accelstart = rsp->accelstart;
-	players[i].acceleration = rsp->acceleration;
-	players[i].charability = rsp->charability;
-	players[i].charability2 = rsp->charability2;
-	players[i].charflags = (UINT32)LONG(rsp->charflags);
-	players[i].thokitem = (UINT32)LONG(rsp->thokitem); //mobjtype_t
-	players[i].spinitem = (UINT32)LONG(rsp->spinitem); //mobjtype_t
-	players[i].revitem = (UINT32)LONG(rsp->revitem); //mobjtype_t
-	players[i].followitem = (UINT32)LONG(rsp->followitem); //mobjtype_t
-	players[i].actionspd = (fixed_t)LONG(rsp->actionspd);
-	players[i].mindash = (fixed_t)LONG(rsp->mindash);
-	players[i].maxdash = (fixed_t)LONG(rsp->maxdash);
-	players[i].jumpfactor = (fixed_t)LONG(rsp->jumpfactor);
-	players[i].height = (fixed_t)LONG(rsp->playerheight);
-	players[i].spinheight = (fixed_t)LONG(rsp->playerspinheight);
-
-	players[i].speed = (fixed_t)LONG(rsp->speed);
-	players[i].secondjump = rsp->secondjump;
-	players[i].fly1 = rsp->fly1;
-	players[i].glidetime = (tic_t)LONG(rsp->glidetime);
-	players[i].climbing = rsp->climbing;
-	players[i].deadtimer = rsp->deadtimer;
-	players[i].exiting = (tic_t)LONG(rsp->exiting);
-	players[i].homing = rsp->homing;
-	players[i].dashmode = (tic_t)LONG(rsp->dashmode);
-	players[i].skidtime = (tic_t)LONG(rsp->skidtime);
-	players[i].cmomx = (fixed_t)LONG(rsp->cmomx);
-	players[i].cmomy = (fixed_t)LONG(rsp->cmomy);
-	players[i].rmomx = (fixed_t)LONG(rsp->rmomx);
-	players[i].rmomy = (fixed_t)LONG(rsp->rmomy);
-
-	players[i].weapondelay = LONG(rsp->weapondelay);
-	players[i].tossdelay = LONG(rsp->tossdelay);
-
-	players[i].starpostx = SHORT(rsp->starpostx);
-	players[i].starposty = SHORT(rsp->starposty);
-	players[i].starpostz = SHORT(rsp->starpostz);
-	players[i].starpostnum = LONG(rsp->starpostnum);
-	players[i].starposttime = (tic_t)LONG(rsp->starposttime);
-	players[i].starpostangle = (angle_t)LONG(rsp->starpostangle);
-	players[i].starpostscale = (fixed_t)LONG(rsp->starpostscale);
-
-	players[i].maxlink = LONG(rsp->maxlink);
-	players[i].dashspeed = (fixed_t)LONG(rsp->dashspeed);
-	players[i].angle_pos = (angle_t)LONG(rsp->angle_pos);
-	players[i].old_angle_pos = (angle_t)LONG(rsp->old_angle_pos);
-	players[i].bumpertime = (tic_t)LONG(rsp->bumpertime);
-	players[i].flyangle = LONG(rsp->flyangle);
-	players[i].drilltimer = (tic_t)LONG(rsp->drilltimer);
-	players[i].linkcount = LONG(rsp->linkcount);
-	players[i].linktimer = (tic_t)LONG(rsp->linktimer);
-	players[i].anotherflyangle = LONG(rsp->anotherflyangle);
-	players[i].nightstime = (tic_t)LONG(rsp->nightstime);
-	players[i].drillmeter = LONG(rsp->drillmeter);
-	players[i].drilldelay = rsp->drilldelay;
-	players[i].bonustime = rsp->bonustime;
-	players[i].mare = rsp->mare;
-	players[i].lastsidehit = SHORT(rsp->lastsidehit);
-	players[i].lastlinehit = SHORT(rsp->lastlinehit);
-
-	players[i].losstime = (tic_t)LONG(rsp->losstime);
-	players[i].timeshit = rsp->timeshit;
-	players[i].onconveyor = LONG(rsp->onconveyor);
-
-	//We get a packet for each player in game.
-	if (!playeringame[i])
-		return;
-
-	//...but keep old mo even if it is corrupt or null!
-	players[i].mo = savedmo;
-
-	//Transfer important mo information if they have a valid mo.
-	if (!rsp->hasmo)
-		return;
-
-	//server thinks player has a body.
-	//Give them a new body that can be then manipulated by the server's info.
-	if (!players[i].mo) //client thinks it has no body.
-		P_SpawnPlayer(i);
-
-	//At this point, the player should have a body, whether they were respawned or not.
-	P_UnsetThingPosition(players[i].mo);
-	players[i].mo->angle = (angle_t)LONG(rsp->angle);
-	players[i].mo->rollangle = (angle_t)LONG(rsp->rollangle);
-	players[i].mo->eflags = (UINT16)SHORT(rsp->eflags);
-	players[i].mo->flags = LONG(rsp->flags);
-	players[i].mo->flags2 = LONG(rsp->flags2);
-	players[i].mo->friction = LONG(rsp->friction);
-	players[i].mo->health = LONG(rsp->health);
-	players[i].mo->momx = LONG(rsp->momx);
-	players[i].mo->momy = LONG(rsp->momy);
-	players[i].mo->momz = LONG(rsp->momz);
-	players[i].mo->movefactor = LONG(rsp->movefactor);
-
-	// Don't use P_SetMobjStateNF to restore state, write/read all the values manually!
-	// This should stop those stupid console errors, hopefully.
-	// -- Monster Iestyn
-	players[i].mo->sprite = (spritenum_t)LONG(rsp->sprite);
-	players[i].mo->frame = LONG(rsp->frame);
-	players[i].mo->sprite2 = rsp->sprite2;
-	players[i].mo->anim_duration = SHORT(rsp->anim_duration);
-	players[i].mo->tics = LONG(rsp->tics);
-	players[i].mo->state = &states[LONG(rsp->statenum)];
-
-	players[i].mo->x = LONG(rsp->x);
-	players[i].mo->y = LONG(rsp->y);
-	players[i].mo->z = LONG(rsp->z);
-	players[i].mo->radius = LONG(rsp->radius);
-	players[i].mo->height = LONG(rsp->height);
-	// P_SetScale is redundant for this, as all related variables are already restored properly.
-	players[i].mo->scale = LONG(rsp->scale);
-	players[i].mo->destscale = LONG(rsp->destscale);
-	players[i].mo->scalespeed = LONG(rsp->scalespeed);
-
-	// And finally, SET THE MOBJ SKIN damn it.
-	if ((players[i].powers[pw_carry] == CR_NIGHTSMODE) && (skins[players[i].skin].sprites[SPR2_NFLY].numframes == 0))
-	{
-		players[i].mo->skin = &skins[DEFAULTNIGHTSSKIN];
-		players[i].mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor; // this will be corrected by thinker to super flash
-	}
-	else
-	{
-		players[i].mo->skin = &skins[players[i].skin];
-		players[i].mo->color = players[i].skincolor; // this will be corrected by thinker to super flash/mario star
-	}
-
-	P_SetThingPosition(players[i].mo);
-}
-
-static inline void resynch_write_ctf(resynchend_pak *rst)
-{
-	mobj_t *mflag;
-	UINT8 i, j;
-
-	for (i = 0, mflag = redflag; i < 2; ++i, mflag = blueflag)
-	{
-		rst->flagx[i] = rst->flagy[i] = rst->flagz[i] = 0;
-		rst->flagloose[i] = rst->flagflags[i] = 0;
-		rst->flagplayer[i] = -1;
-
-		if (!mflag)
-		{
-			// Should be held by a player
-			for (j = 0; j < MAXPLAYERS; ++j)
-			{
-				// GF_REDFLAG is 1, GF_BLUEFLAG is 2
-				// redflag handling is i=0, blueflag is i=1
-				// so check for gotflag == (i+1)
-				if (!playeringame[j] || players[j].gotflag != (i+1))
-					continue;
-				rst->flagplayer[i] = (SINT8)j;
-				break;
-			}
-			if (j == MAXPLAYERS) // fine, no I_Error
-			{
-				CONS_Alert(CONS_ERROR, "One of the flags has gone completely missing...\n");
-				rst->flagplayer[i] = -2;
-			}
-			continue;
-		}
-
-		rst->flagx[i] = (fixed_t)LONG(mflag->x);
-		rst->flagy[i] = (fixed_t)LONG(mflag->y);
-		rst->flagz[i] = (fixed_t)LONG(mflag->z);
-		rst->flagflags[i] = LONG(mflag->flags2);
-		rst->flagloose[i] = LONG(mflag->fuse); // Dropped or not?
-	}
-}
-
-static inline void resynch_read_ctf(resynchend_pak *p)
-{
-	UINT8 i;
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-		players[i].gotflag = 0;
-
-	// Red flag
-	if (p->flagplayer[0] == -2)
-		; // The server doesn't even know what happened to it...
-	else if (p->flagplayer[0] != -1) // Held by a player
-	{
-		if (!playeringame[p->flagplayer[0]])
-			 I_Error("Invalid red flag player %d who isn't in the game!", (INT32)p->flagplayer[0]);
-		players[p->flagplayer[0]].gotflag = GF_REDFLAG;
-		if (redflag)
-		{
-			P_RemoveMobj(redflag);
-			redflag = NULL;
-		}
-	}
-	else
-	{
-		if (!redflag)
-			redflag = P_SpawnMobj(0,0,0,MT_REDFLAG);
-
-		P_UnsetThingPosition(redflag);
-		redflag->x = (fixed_t)LONG(p->flagx[0]);
-		redflag->y = (fixed_t)LONG(p->flagy[0]);
-		redflag->z = (fixed_t)LONG(p->flagz[0]);
-		redflag->flags2 = LONG(p->flagflags[0]);
-		redflag->fuse = LONG(p->flagloose[0]);
-		P_SetThingPosition(redflag);
-	}
-
-	// Blue flag
-	if (p->flagplayer[1] == -2)
-		; // The server doesn't even know what happened to it...
-	else if (p->flagplayer[1] != -1) // Held by a player
-	{
-		if (!playeringame[p->flagplayer[1]])
-			 I_Error("Invalid blue flag player %d who isn't in the game!", (INT32)p->flagplayer[1]);
-		players[p->flagplayer[1]].gotflag = GF_BLUEFLAG;
-		if (blueflag)
-		{
-			P_RemoveMobj(blueflag);
-			blueflag = NULL;
-		}
-	}
-	else
-	{
-		if (!blueflag)
-			blueflag = P_SpawnMobj(0,0,0,MT_BLUEFLAG);
-
-		P_UnsetThingPosition(blueflag);
-		blueflag->x = (fixed_t)LONG(p->flagx[1]);
-		blueflag->y = (fixed_t)LONG(p->flagy[1]);
-		blueflag->z = (fixed_t)LONG(p->flagz[1]);
-		blueflag->flags2 = LONG(p->flagflags[1]);
-		blueflag->fuse = LONG(p->flagloose[1]);
-		P_SetThingPosition(blueflag);
-	}
-}
-
-static inline void resynch_write_others(resynchend_pak *rst)
-{
-	UINT8 i;
-
-	rst->ingame = 0;
-	rst->outofcoop = 0;
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		if (!playeringame[i])
-		{
-			rst->ctfteam[i] = 0;
-			rst->score[i] = 0;
-			rst->numboxes[i] = 0;
-			rst->totalring[i] = 0;
-			rst->realtime[i] = 0;
-			rst->laps[i] = 0;
-			continue;
-		}
-
-		if (!players[i].spectator)
-			rst->ingame |= (1<<i);
-		if (players[i].outofcoop)
-			rst->outofcoop |= (1<<i);
-		rst->ctfteam[i] = (INT32)LONG(players[i].ctfteam);
-		rst->score[i] = (UINT32)LONG(players[i].score);
-		rst->numboxes[i] = SHORT(players[i].numboxes);
-		rst->totalring[i] = SHORT(players[i].totalring);
-		rst->realtime[i] = (tic_t)LONG(players[i].realtime);
-		rst->laps[i] = players[i].laps;
-	}
-
-	// endian safeness
-	rst->ingame = (UINT32)LONG(rst->ingame);
-}
-
-static inline void resynch_read_others(resynchend_pak *p)
-{
-	UINT8 i;
-	UINT32 loc_ingame = (UINT32)LONG(p->ingame);
-	UINT32 loc_outofcoop = (UINT32)LONG(p->outofcoop);
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		// We don't care if they're in the game or not, just write all the data.
-		players[i].spectator = !(loc_ingame & (1<<i));
-		players[i].outofcoop = (loc_outofcoop & (1<<i));
-		players[i].ctfteam = (INT32)LONG(p->ctfteam[i]); // no, 0 does not mean spectator, at least not in Match
-		players[i].score = (UINT32)LONG(p->score[i]);
-		players[i].numboxes = SHORT(p->numboxes[i]);
-		players[i].totalring = SHORT(p->totalring[i]);
-		players[i].realtime = (tic_t)LONG(p->realtime[i]);
-		players[i].laps = p->laps[i];
-	}
-}
-
-static void SV_InitResynchVars(INT32 node)
-{
-	resynch_delay[node] = TICRATE; // initial one second delay
-	resynch_score[node] = 0; // clean slate
-	resynch_status[node] = 0x00;
-	resynch_inprogress[node] = false;
-	memset(resynch_sent[node], 0, MAXPLAYERS);
-}
-
-static void SV_RequireResynch(INT32 node)
-{
-	INT32 i;
-
-	resynch_delay[node] = 10; // Delay before you can fail sync again
-	resynch_score[node] += 200; // Add score for initial desynch
-	resynch_status[node] = 0xFFFFFFFF; // No players assumed synched
-	resynch_inprogress[node] = true; // so we know to send a PT_RESYNCHEND after sync
-
-	// Initial setup
-	memset(resynch_sent[node], 0, MAXPLAYERS);
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		if (!playeringame[i]) // Player not in game so just drop it from required synch
-			resynch_status[node] &= ~(1<<i);
-		else if (playernode[i] == node); // instantly update THEIR position
-		else // Send at random times based on num players
-			resynch_sent[node][i] = M_RandomKey(D_NumPlayers()>>1)+1;
-	}
-}
-
-static void SV_SendResynch(INT32 node)
-{
-	INT32 i, j;
-
-	if (!nodeingame[node])
-	{
-		// player left during resynch
-		// so obviously we don't need to do any of this anymore
-		resynch_inprogress[node] = false;
-		return;
-	}
-
-	// resynched?
-	if (!resynch_status[node])
-	{
-		// you are now synched
-		resynch_inprogress[node] = false;
-
-		netbuffer->packettype = PT_RESYNCHEND;
-
-		netbuffer->u.resynchend.randomseed = P_GetRandSeed();
-		if (gametyperules & GTR_TEAMFLAGS)
-			resynch_write_ctf(&netbuffer->u.resynchend);
-		resynch_write_others(&netbuffer->u.resynchend);
-
-		HSendPacket(node, true, 0, (sizeof(resynchend_pak)));
-		return;
-	}
-
-	netbuffer->packettype = PT_RESYNCHING;
-	for (i = 0, j = 0; i < MAXPLAYERS; ++i)
-	{
-		// if already synched don't bother
-		if (!(resynch_status[node] & 1<<i))
-			continue;
-
-		// waiting for a reply or just waiting in general
-		if (resynch_sent[node][i])
-		{
-			--resynch_sent[node][i];
-			continue;
-		}
-
-		resynch_write_player(&netbuffer->u.resynchpak, i);
-		HSendPacket(node, false, 0, (sizeof(resynch_pak)));
-
-		resynch_sent[node][i] = TICRATE;
-		resynch_score[node] += 2; // penalty for send
-
-		if (++j > 3)
-			break;
-	}
-
-	if (resynch_score[node] > (unsigned)cv_resynchattempts.value*250)
-	{
-		SendKick(nodetoplayer[node], KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-		resynch_score[node] = 0;
-	}
-}
-
-static void CL_AcknowledgeResynch(resynch_pak *rsp)
-{
-	resynch_read_player(rsp);
-
-	netbuffer->packettype = PT_RESYNCHGET;
-	netbuffer->u.resynchgot = rsp->playernum;
-	HSendPacket(servernode, true, 0, sizeof(UINT8));
-}
-
-static void SV_AcknowledgeResynchAck(INT32 node, UINT8 rsg)
-{
-	if (rsg >= MAXPLAYERS)
-		resynch_score[node] += 16384; // lol.
-	else
-	{
-		resynch_status[node] &= ~(1<<rsg);
-		--resynch_score[node]; // unpenalize
-	}
-
-	// Don't let resynch cause a timeout
-	freezetimeout[node] = I_GetTime() + connectiontimeout;
-}
-// -----------------------------------------------------------------
-// end resynch
-// -----------------------------------------------------------------
-
 static INT16 Consistancy(void);
 
 typedef enum
@@ -1122,40 +522,6 @@ static void GetPackets(void);
 
 static cl_mode_t cl_mode = CL_SEARCHING;
 
-// Player name send/load
-
-static void CV_SavePlayerNames(UINT8 **p)
-{
-	INT32 i = 0;
-	// Players in game only.
-	for (; i < MAXPLAYERS; ++i)
-	{
-		if (!playeringame[i])
-		{
-			WRITEUINT8(*p, 0);
-			continue;
-		}
-		WRITESTRING(*p, player_names[i]);
-	}
-}
-
-static void CV_LoadPlayerNames(UINT8 **p)
-{
-	INT32 i = 0;
-	char tmp_name[MAXPLAYERNAME+1];
-	tmp_name[MAXPLAYERNAME] = 0;
-
-	for (; i < MAXPLAYERS; ++i)
-	{
-		READSTRING(*p, tmp_name);
-		if (tmp_name[0] == 0)
-			continue;
-		if (tmp_name[MAXPLAYERNAME]) // overflow detected
-			I_Error("Received bad server config packet when trying to join");
-		memcpy(player_names[i], tmp_name, MAXPLAYERNAME+1);
-	}
-}
-
 #ifndef NONET
 #define SNAKE_SPEED 5
 
@@ -1269,19 +635,25 @@ static UINT8 Snake_GetOppositeDir(UINT8 dir)
 		return 12 + 5 - dir;
 }
 
-static void Snake_FindFreeSlot(UINT8 *x, UINT8 *y, UINT8 headx, UINT8 heady)
+static void Snake_FindFreeSlot(UINT8 *freex, UINT8 *freey, UINT8 headx, UINT8 heady)
 {
+	UINT8 x, y;
 	UINT16 i;
 
 	do
 	{
-		*x = M_RandomKey(SNAKE_NUM_BLOCKS_X);
-		*y = M_RandomKey(SNAKE_NUM_BLOCKS_Y);
+		x = M_RandomKey(SNAKE_NUM_BLOCKS_X);
+		y = M_RandomKey(SNAKE_NUM_BLOCKS_Y);
 
 		for (i = 0; i < snake->snakelength; i++)
-			if (*x == snake->snakex[i] && *y == snake->snakey[i])
+			if (x == snake->snakex[i] && y == snake->snakey[i])
 				break;
-	} while (i < snake->snakelength || (*x == headx && *y == heady));
+	} while (i < snake->snakelength || (x == headx && y == heady)
+		|| (x == snake->applex && y == snake->appley)
+		|| (snake->bonustype != SNAKE_BONUS_NONE && x == snake->bonusx && y == snake->bonusy));
+
+	*freex = x;
+	*freey = y;
 }
 
 static void Snake_Handle(void)
@@ -1412,7 +784,7 @@ static void Snake_Handle(void)
 	// Check collision with apple
 	if (x == snake->applex && y == snake->appley)
 	{
-		if (snake->snakelength + 1 < SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y)
+		if (snake->snakelength + 3 < SNAKE_NUM_BLOCKS_X * SNAKE_NUM_BLOCKS_Y)
 		{
 			snake->snakelength++;
 			snake->snakex  [snake->snakelength - 1] = snake->snakex  [snake->snakelength - 2];
@@ -1968,8 +1340,6 @@ static void SV_SendPlayerInfo(INT32 node)
   */
 static boolean SV_SendServerConfig(INT32 node)
 {
-	INT32 i;
-	UINT8 *p, *op;
 	boolean waspacketsent;
 
 	netbuffer->packettype = PT_SERVERCFG;
@@ -1985,32 +1355,10 @@ static boolean SV_SendServerConfig(INT32 node)
 	netbuffer->u.servercfg.gametype = (UINT8)gametype;
 	netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame;
 
-	// we fill these structs with FFs so that any players not in game get sent as 0xFFFF
-	// which is nice and easy for us to detect
-	memset(netbuffer->u.servercfg.playerskins, 0xFF, sizeof(netbuffer->u.servercfg.playerskins));
-	memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor));
-	memset(netbuffer->u.servercfg.playeravailabilities, 0xFF, sizeof(netbuffer->u.servercfg.playeravailabilities));
-
-	memset(netbuffer->u.servercfg.adminplayers, -1, sizeof(netbuffer->u.servercfg.adminplayers));
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		netbuffer->u.servercfg.adminplayers[i] = (SINT8)adminplayers[i];
-
-		if (!playeringame[i])
-			continue;
-		netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin;
-		netbuffer->u.servercfg.playercolor[i] = (UINT16)players[i].skincolor;
-		netbuffer->u.servercfg.playeravailabilities[i] = (UINT32)LONG(players[i].availabilities);
-	}
-
 	memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
-	op = p = netbuffer->u.servercfg.varlengthinputs;
 
-	CV_SavePlayerNames(&p);
-	CV_SaveNetVars(&p);
 	{
-		const size_t len = sizeof (serverconfig_pak) + (size_t)(p - op);
+		const size_t len = sizeof (serverconfig_pak);
 
 #ifdef DEBUGFILE
 		if (debugfile)
@@ -2043,7 +1391,17 @@ static boolean SV_SendServerConfig(INT32 node)
 #ifndef NONET
 #define SAVEGAMESIZE (2048*1024) //(768*1024)
 
-static void SV_SendSaveGame(INT32 node)
+static boolean SV_ResendingSavegameToAnyone(void)
+{
+	INT32 i;
+
+	for (i = 0; i < MAXNETNODES; i++)
+		if (resendingsavegame[i])
+			return true;
+	return false;
+}
+
+static void SV_SendSaveGame(INT32 node, boolean resending)
 {
 	size_t length, compressedlen;
 	UINT8 *savebuffer;
@@ -2061,7 +1419,7 @@ static void SV_SendSaveGame(INT32 node)
 	// Leave room for the uncompressed length.
 	save_p = savebuffer + sizeof(UINT32);
 
-	P_SaveNetGame();
+	P_SaveNetGame(resending);
 
 	length = save_p - savebuffer;
 	if (length > SAVEGAMESIZE)
@@ -2113,7 +1471,7 @@ static void SV_SendSaveGame(INT32 node)
 
 #ifdef DUMPCONSISTENCY
 #define TMPSAVENAME "badmath.sav"
-static consvar_t cv_dumpconsistency = {"dumpconsistency", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
 
 static void SV_SavedGame(void)
 {
@@ -2134,7 +1492,7 @@ static void SV_SavedGame(void)
 		return;
 	}
 
-	P_SaveNetGame();
+	P_SaveNetGame(false);
 
 	length = save_p - savebuffer;
 	if (length > SAVEGAMESIZE)
@@ -2157,7 +1515,7 @@ static void SV_SavedGame(void)
 #define TMPSAVENAME "$$$.sav"
 
 
-static void CL_LoadReceivedSavegame(void)
+static void CL_LoadReceivedSavegame(boolean reloading)
 {
 	UINT8 *savebuffer = NULL;
 	size_t length, decompressedlen;
@@ -2193,7 +1551,7 @@ static void CL_LoadReceivedSavegame(void)
 	automapactive = false;
 
 	// load a base level
-	if (P_LoadNetGame())
+	if (P_LoadNetGame(reloading))
 	{
 		const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
 		CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
@@ -2224,11 +1582,48 @@ static void CL_LoadReceivedSavegame(void)
 		CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave);
 	consistancy[gametic%BACKUPTICS] = Consistancy();
 	CON_ToggleOff();
+
+	// Tell the server we have received and reloaded the gamestate
+	// so they know they can resume the game
+	netbuffer->packettype = PT_RECEIVEDGAMESTATE;
+	HSendPacket(servernode, true, 0, 0);
+}
+
+static void CL_ReloadReceivedSavegame(void)
+{
+	INT32 i;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		LUA_InvalidatePlayer(&players[i]);
+		sprintf(player_names[i], "Player %d", i + 1);
+	}
+
+	CL_LoadReceivedSavegame(true);
+
+	if (neededtic < gametic)
+		neededtic = gametic;
+	maketic = neededtic;
+
+	ticcmd_oldangleturn[0] = players[consoleplayer].oldrelangleturn;
+	P_ForceLocalAngle(&players[consoleplayer], (angle_t)(players[consoleplayer].angleturn << 16));
+	if (splitscreen)
+	{
+		ticcmd_oldangleturn[1] = players[secondarydisplayplayer].oldrelangleturn;
+		P_ForceLocalAngle(&players[secondarydisplayplayer], (angle_t)(players[secondarydisplayplayer].angleturn << 16));
+	}
+
+	camera.subsector = R_PointInSubsector(camera.x, camera.y);
+	camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
+
+	cl_redownloadinggamestate = false;
+
+	CONS_Printf(M_GetText("Game state reloaded\n"));
 }
 #endif
 
 #ifndef NONET
-static void SendAskInfo(INT32 node, boolean viams)
+static void SendAskInfo(INT32 node)
 {
 	const tic_t asktime = I_GetTime();
 	netbuffer->packettype = PT_ASKINFO;
@@ -2239,10 +1634,6 @@ static void SendAskInfo(INT32 node, boolean viams)
 	// now allowed traffic from the host to us in, so once the MS relays
 	// our address to the host, it'll be able to speak to us.
 	HSendPacket(node, false, 0, sizeof (askinfo_pak));
-
-	// Also speak to the MS.
-	if (viams && node != 0 && node != BROADCASTADDR)
-		SendAskInfoViaMS(node, asktime);
 }
 
 serverelem_t serverlist[MAXSERVERLIST];
@@ -2310,13 +1701,95 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
 	M_SortServerList();
 }
 
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+struct Fetch_servers_ctx
+{
+	int room;
+	int id;
+};
+
+static void
+Fetch_servers_thread (struct Fetch_servers_ctx *ctx)
+{
+	msg_server_t *server_list;
+
+	server_list = GetShortServersList(ctx->room, ctx->id);
+
+	if (server_list)
+	{
+		I_lock_mutex(&ms_QueryId_mutex);
+		{
+			if (ctx->id != ms_QueryId)
+			{
+				free(server_list);
+				server_list = NULL;
+			}
+		}
+		I_unlock_mutex(ms_QueryId_mutex);
+
+		if (server_list)
+		{
+			I_lock_mutex(&m_menu_mutex);
+			{
+				if (m_waiting_mode == M_WAITING_SERVERS)
+					m_waiting_mode = M_NOT_WAITING;
+			}
+			I_unlock_mutex(m_menu_mutex);
+
+			I_lock_mutex(&ms_ServerList_mutex);
+			{
+				ms_ServerList = server_list;
+			}
+			I_unlock_mutex(ms_ServerList_mutex);
+		}
+	}
+
+	free(ctx);
+}
+#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
+
+void CL_QueryServerList (msg_server_t *server_list)
+{
+	INT32 i;
+
+	for (i = 0; server_list[i].header.buffer[0]; i++)
+	{
+		// Make sure MS version matches our own, to
+		// thwart nefarious servers who lie to the MS.
+
+		/* lol bruh, that version COMES from the servers */
+		//if (strcmp(version, server_list[i].version) == 0)
+		{
+			INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port);
+			if (node == -1)
+				break; // no more node free
+			SendAskInfo(node);
+			// Force close the connection so that servers can't eat
+			// up nodes forever if we never get a reply back from them
+			// (usually when they've not forwarded their ports).
+			//
+			// Don't worry, we'll get in contact with the working
+			// servers again when they send SERVERINFO to us later!
+			//
+			// (Note: as a side effect this probably means every
+			// server in the list will probably be using the same node (e.g. node 1),
+			// not that it matters which nodes they use when
+			// the connections are closed afterwards anyway)
+			// -- Monster Iestyn 12/11/18
+			Net_CloseConnection(node|FORCECLOSE);
+		}
+	}
+}
+
 void CL_UpdateServerList(boolean internetsearch, INT32 room)
 {
+	(void)internetsearch;
+	(void)room;
+
 	SL_ClearServerList(0);
 
 	if (!netgame && I_NetOpenSocket)
 	{
-		MSCloseUDPSocket();		// Tidy up before wiping the slate.
 		if (I_NetOpenSocket())
 		{
 			netgame = true;
@@ -2326,71 +1799,54 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room)
 
 	// search for local servers
 	if (netgame)
-		SendAskInfo(BROADCASTADDR, false);
+		SendAskInfo(BROADCASTADDR);
 
+#ifdef MASTERSERVER
 	if (internetsearch)
 	{
-		const msg_server_t *server_list;
-		INT32 i = -1;
-		server_list = GetShortServersList(room);
-		if (server_list)
-		{
-			char version[8] = "";
-#ifndef DEVELOP
-			strcpy(version, SRB2VERSION);
-#else
-			strcpy(version, GetRevisionString());
-#endif
-			version[sizeof (version) - 1] = '\0';
+#ifdef HAVE_THREADS
+		struct Fetch_servers_ctx *ctx;
 
-			for (i = 0; server_list[i].header.buffer[0]; i++)
-			{
-				// Make sure MS version matches our own, to
-				// thwart nefarious servers who lie to the MS.
+		ctx = malloc(sizeof *ctx);
 
-				if (strcmp(version, server_list[i].version) == 0)
-				{
-					INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port);
-					if (node == -1)
-						break; // no more node free
-					SendAskInfo(node, true);
-					// Force close the connection so that servers can't eat
-					// up nodes forever if we never get a reply back from them
-					// (usually when they've not forwarded their ports).
-					//
-					// Don't worry, we'll get in contact with the working
-					// servers again when they send SERVERINFO to us later!
-					//
-					// (Note: as a side effect this probably means every
-					// server in the list will probably be using the same node (e.g. node 1),
-					// not that it matters which nodes they use when
-					// the connections are closed afterwards anyway)
-					// -- Monster Iestyn 12/11/18
-					Net_CloseConnection(node|FORCECLOSE);
-				}
-			}
+		/* This called from M_Refresh so I don't use a mutex */
+		m_waiting_mode = M_WAITING_SERVERS;
+
+		I_lock_mutex(&ms_QueryId_mutex);
+		{
+			ctx->id = ms_QueryId;
 		}
+		I_unlock_mutex(ms_QueryId_mutex);
+
+		ctx->room = room;
+
+		I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx);
+#else
+		msg_server_t *server_list;
+
+		server_list = GetShortServersList(room, 0);
 
-		//no server list?(-1) or no servers?(0)
-		if (!i)
+		if (server_list)
 		{
-			; /// TODO: display error or warning?
+			CL_QueryServerList(server_list);
+			free(server_list);
 		}
+#endif
 	}
+#endif/*MASTERSERVER*/
 }
 
 #endif // ifndef NONET
 
 /** Called by CL_ServerConnectionTicker
   *
-  * \param viams ???
-  * \param asksent ???
+  * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit.
   * \return False if the connection was aborted
   * \sa CL_ServerConnectionTicker
   * \sa CL_ConnectToServer
   *
   */
-static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
+static boolean CL_ServerConnectionSearchTicker(tic_t *asksent)
 {
 #ifndef NONET
 	INT32 i;
@@ -2452,11 +1908,11 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 				CL_Reset();
 				D_StartTitle();
 				M_StartMessage(M_GetText(
-					"You have WAD files loaded or have\n"
-					"modified the game in some way, and\n"
-					"your file list does not match\n"
-					"the server's file list.\n"
-					"Please restart SRB2 before connecting.\n\n"
+					"You have the wrong addons loaded.\n\n"
+					"To play on this server, restart\n"
+					"the game and don't load any addons.\n"
+					"SRB2 will automatically add\n"
+					"everything you need when you join.\n\n"
 					"Press ESC\n"
 				), NULL, MM_NOTHING);
 				return false;
@@ -2501,11 +1957,10 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 	// Ask the info to the server (askinfo packet)
 	if (*asksent + NEWTICRATE < I_GetTime())
 	{
-		SendAskInfo(servernode, viams);
+		SendAskInfo(servernode);
 		*asksent = I_GetTime();
 	}
 #else
-	(void)viams;
 	(void)asksent;
 	// No netgames, so we skip this state.
 	cl_mode = CL_ASKJOIN;
@@ -2516,7 +1971,6 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 
 /** Called by CL_ConnectToServer
   *
-  * \param viams ???
   * \param tmpsave The name of the gamestate file???
   * \param oldtic Used for knowing when to poll events and redraw
   * \param asksent ???
@@ -2525,7 +1979,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
   * \sa CL_ConnectToServer
   *
   */
-static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic_t *oldtic, tic_t *asksent)
+static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent)
 {
 	boolean waitmore;
 	INT32 i;
@@ -2537,7 +1991,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
 	switch (cl_mode)
 	{
 		case CL_SEARCHING:
-			if (!CL_ServerConnectionSearchTicker(viams, asksent))
+			if (!CL_ServerConnectionSearchTicker(asksent))
 				return false;
 			break;
 
@@ -2582,7 +2036,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
 			if (fileneeded[0].status == FS_FOUND)
 			{
 				// Gamestate is now handled within CL_LoadReceivedSavegame()
-				CL_LoadReceivedSavegame();
+				CL_LoadReceivedSavegame(false);
 				cl_mode = CL_CONNECTED;
 			} // don't break case continue to CL_CONNECTED
 			else
@@ -2673,11 +2127,10 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
 
 /** Use adaptive send using net_bandwidth and stat.sendbytes
   *
-  * \param viams ???
   * \todo Better description...
   *
   */
-static void CL_ConnectToServer(boolean viams)
+static void CL_ConnectToServer(void)
 {
 	INT32 pnumnodes, nodewaited = doomcom->numnodes, i;
 	tic_t oldtic;
@@ -2737,9 +2190,9 @@ static void CL_ConnectToServer(boolean viams)
 	{
 		// If the connection was aborted for some reason, leave
 #ifndef NONET
-		if (!CL_ServerConnectionTicker(viams, tmpsave, &oldtic, &asksent))
+		if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent))
 #else
-		if (!CL_ServerConnectionTicker(viams, (char*)NULL, &oldtic, (tic_t *)NULL))
+		if (!CL_ServerConnectionTicker((char*)NULL, &oldtic, (tic_t *)NULL))
 #endif
 			return;
 
@@ -2805,11 +2258,15 @@ void D_SaveBan(void)
 	size_t i;
 	banreason_t *reasonlist = reasonhead;
 	const char *address, *mask;
+	const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt");
 
 	if (!reasonhead)
+	{
+		remove(path);
 		return;
+	}
 
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "w");
+	f = fopen(path, "w");
 
 	if (!f)
 	{
@@ -2853,16 +2310,14 @@ static void Ban_Add(const char *reason)
 	reasontail = reasonlist;
 }
 
-static void Command_ClearBans(void)
+static void Ban_Clear(void)
 {
 	banreason_t *temp;
 
-	if (!I_ClearBans)
-		return;
-
 	I_ClearBans();
-	D_SaveBan();
+
 	reasontail = NULL;
+
 	while (reasonhead)
 	{
 		temp = reasonhead->next;
@@ -2872,6 +2327,15 @@ static void Command_ClearBans(void)
 	}
 }
 
+static void Command_ClearBans(void)
+{
+	if (!I_ClearBans)
+		return;
+
+	Ban_Clear();
+	D_SaveBan();
+}
+
 static void Ban_Load_File(boolean warning)
 {
 	FILE *f;
@@ -2879,6 +2343,9 @@ static void Ban_Load_File(boolean warning)
 	const char *address, *mask;
 	char buffer[MAX_WADPATH];
 
+	if (!I_ClearBans)
+		return;
+
 	f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r");
 
 	if (!f)
@@ -2888,13 +2355,7 @@ static void Ban_Load_File(boolean warning)
 		return;
 	}
 
-	if (I_ClearBans)
-		Command_ClearBans();
-	else
-	{
-		fclose(f);
-		return;
-	}
+	Ban_Clear();
 
 	for (i=0; fgets(buffer, (int)sizeof(buffer), f); i++)
 	{
@@ -2916,9 +2377,6 @@ static void Command_ReloadBan(void)  //recheck ban.txt
 
 static void Command_connect(void)
 {
-	// Assume we connect directly.
-	boolean viams = false;
-
 	if (COM_Argc() < 2 || *COM_Argv(1) == 0)
 	{
 		CONS_Printf(M_GetText(
@@ -2954,9 +2412,6 @@ static void Command_connect(void)
 		if (netgame && !stricmp(COM_Argv(1), "node"))
 		{
 			servernode = (SINT8)atoi(COM_Argv(2));
-
-			// Use MS to traverse NAT firewalls.
-			viams = true;
 		}
 		else if (netgame)
 		{
@@ -2965,7 +2420,6 @@ static void Command_connect(void)
 		}
 		else if (I_NetOpenSocket)
 		{
-			MSCloseUDPSocket(); // Tidy up before wiping the slate.
 			I_NetOpenSocket();
 			netgame = true;
 			multiplayer = true;
@@ -2994,7 +2448,7 @@ static void Command_connect(void)
 	SplitScreen_OnChange();
 	botingame = false;
 	botskin = 0;
-	CL_ConnectToServer(viams);
+	CL_ConnectToServer();
 }
 #endif
 
@@ -3056,10 +2510,14 @@ static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 		}
 
 		count--;
-		spheres = players[playernum].spheres;
-		rings = players[playernum].rings;
-		sincrement = spheres/count;
-		rincrement = rings/count;
+		sincrement = spheres = players[playernum].spheres;
+		rincrement = rings = players[playernum].rings;
+
+		if (count)
+		{
+			sincrement /= count;
+			rincrement /= count;
+		}
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
@@ -3113,6 +2571,8 @@ static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 	// Reset the name
 	sprintf(player_names[playernum], "Player %d", playernum+1);
 
+	player_name_changes[playernum] = 0;
+
 	if (IsPlayerAdmin(playernum))
 	{
 		RemoveAdminPlayer(playernum); // don't stay admin after you're gone
@@ -3147,11 +2607,11 @@ void CL_Reset(void)
 	multiplayer = false;
 	servernode = 0;
 	server = true;
-	resynch_local_inprogress = false;
 	doomcom->numnodes = 1;
 	doomcom->numslots = 1;
 	SV_StopServer();
 	SV_ResetServer();
+	CV_RevertNetVars();
 
 	// make sure we don't leave any fileneeded gunk over from a failed join
 	fileneedednum = 0;
@@ -3575,8 +3035,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 
 	if (pnum == consoleplayer)
 	{
-		if (Playing())
-			LUAh_GameQuit();
+		LUAh_GameQuit(false);
 #ifdef DUMPCONSISTENCY
 		if (msg == KICK_MSG_CON_FAIL) SV_SavedGame();
 #endif
@@ -3618,27 +3077,58 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		CL_RemovePlayer(pnum, kickreason);
 }
 
-consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
-consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
+static void Command_ResendGamestate(void)
+{
+	SINT8 playernum;
+
+	if (COM_Argc() == 1)
+	{
+		CONS_Printf(M_GetText("resendgamestate <playername/playernum>: resend the game state to a player\n"));
+		return;
+	}
+	else if (client)
+	{
+		CONS_Printf(M_GetText("Only the server can use this.\n"));
+		return;
+	}
+
+	playernum = nametonum(COM_Argv(1));
+	if (playernum == -1 || playernum == 0)
+		return;
+
+	// Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on
+	netbuffer->packettype = PT_WILLRESENDGAMESTATE;
+	if (!HSendPacket(playernode[playernum], true, 0, 0))
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n"));
+		return;
+	}
+}
+
+static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}};
+consvar_t cv_netticbuffer = CVAR_INIT ("netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL);
+
+consvar_t cv_allownewplayer = CVAR_INIT ("allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_joinnextround = CVAR_INIT ("joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); /// \todo not done
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
-consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR, maxplayers_cons_t, NULL);
 static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}};
-consvar_t cv_joindelay = {"joindelay", "10", CV_SAVE, joindelay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL);
 static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
-consvar_t cv_rejointimeout = {"rejointimeout", "Off", CV_SAVE|CV_FLOAT, rejointimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_rejointimeout = CVAR_INIT ("rejointimeout", "2", CV_SAVE|CV_NETVAR|CV_FLOAT, rejointimeout_cons_t, NULL);
 
 static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}};
-consvar_t cv_resynchattempts = {"resynchattempts", "10", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
-consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
+consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "10", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL);
+consvar_t cv_blamecfail = CVAR_INIT ("blamecfail", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
 
 // max file size to send to a player (in kilobytes)
 static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}};
-consvar_t cv_maxsend = {"maxsend", "4096", CV_SAVE, maxsend_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_noticedownload = {"noticedownload", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_maxsend = CVAR_INIT ("maxsend", "4096", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL);
+consvar_t cv_noticedownload = CVAR_INIT ("noticedownload", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
 
 // Speed of file downloading (in packets per tic)
 static CV_PossibleValue_t downloadspeed_cons_t[] = {{0, "MIN"}, {32, "MAX"}, {0, NULL}};
-consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_downloadspeed = CVAR_INIT ("downloadspeed", "16", CV_SAVE|CV_NETVAR, downloadspeed_cons_t, NULL);
 
 static void Got_AddPlayer(UINT8 **p, INT32 playernum);
 
@@ -3658,6 +3148,7 @@ void D_ClientServerInit(void)
 	COM_AddCommand("reloadbans", Command_ReloadBan);
 	COM_AddCommand("connect", Command_connect);
 	COM_AddCommand("nodes", Command_Nodes);
+	COM_AddCommand("resendgamestate", Command_ResendGamestate);
 #ifdef PACKETDROP
 	COM_AddCommand("drop", Command_Drop);
 	COM_AddCommand("droprate", Command_Droprate);
@@ -3689,14 +3180,18 @@ void D_ClientServerInit(void)
 static void ResetNode(INT32 node)
 {
 	nodeingame[node] = false;
-	nodetoplayer[node] = -1;
-	nodetoplayer2[node] = -1;
+	nodewaiting[node] = 0;
+
 	nettics[node] = gametic;
 	supposedtics[node] = gametic;
-	nodewaiting[node] = 0;
+
+	nodetoplayer[node] = -1;
+	nodetoplayer2[node] = -1;
 	playerpernode[node] = 0;
+
 	sendingsavegame[node] = false;
-	SV_InitResynchVars(node);
+	resendingsavegame[node] = false;
+	savegameresendcooldown[node] = 0;
 }
 
 void SV_ResetServer(void)
@@ -3726,8 +3221,11 @@ void SV_ResetServer(void)
 		adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
 	}
 
+	memset(player_name_changes, 0, sizeof player_name_changes);
+
 	mynode = 0;
 	cl_packetmissed = false;
+	cl_redownloadinggamestate = false;
 
 	if (dedicated)
 	{
@@ -3791,8 +3289,10 @@ void D_QuitNetGame(void)
 		for (i = 0; i < MAXNETNODES; i++)
 			if (nodeingame[i])
 				HSendPacket(i, true, 0, 0);
+#ifdef MASTERSERVER
 		if (serverrunning && ms_RoomId > 0)
 			UnregisterServer();
+#endif
 	}
 	else if (servernode > 0 && servernode < MAXNETNODES && nodeingame[(UINT8)servernode])
 	{
@@ -4055,15 +3555,16 @@ boolean SV_SpawnServer(void)
 		SV_GenContext();
 		if (netgame && I_NetOpenSocket)
 		{
-			MSCloseUDPSocket();		// Tidy up before wiping the slate.
 			I_NetOpenSocket();
+#ifdef MASTERSERVER
 			if (ms_RoomId > 0)
 				RegisterServer();
+#endif
 		}
 
 		// non dedicated server just connect to itself
 		if (!dedicated)
-			CL_ConnectToServer(false);
+			CL_ConnectToServer();
 		else doomcom->numslots = 1;
 	}
 
@@ -4194,12 +3695,6 @@ static void HandleConnect(SINT8 node)
 #endif
 			SV_AddNode(node);
 
-			/// \note Wait what???
-			///       What if the gamestate takes more than one second to get downloaded?
-			///       Or if a lagspike happens?
-			// you get a free second before desynch checks. use it wisely.
-			SV_InitResynchVars(node);
-
 			if (cv_joinnextround.value && gameaction == ga_nothing)
 				G_SetGamestate(GS_WAITINGPLAYERS);
 			if (!SV_SendServerConfig(node))
@@ -4221,7 +3716,7 @@ static void HandleConnect(SINT8 node)
 		{
 			if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) && newnode)
 			{
-				SV_SendSaveGame(node); // send a complete game state
+				SV_SendSaveGame(node, false); // send a complete game state
 				DEBFILE("send savegame\n");
 			}
 			SV_AddWaitingPlayers(names[0], names[1]);
@@ -4240,8 +3735,7 @@ static void HandleConnect(SINT8 node)
 static void HandleShutdown(SINT8 node)
 {
 	(void)node;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(false);
 	D_QuitNetGame();
 	CL_Reset();
 	D_StartTitle();
@@ -4256,8 +3750,7 @@ static void HandleShutdown(SINT8 node)
 static void HandleTimeout(SINT8 node)
 {
 	(void)node;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(false);
 	D_QuitNetGame();
 	CL_Reset();
 	D_StartTitle();
@@ -4288,6 +3781,43 @@ static void HandleServerInfo(SINT8 node)
 }
 #endif
 
+static void PT_WillResendGamestate(void)
+{
+	char tmpsave[256];
+
+	if (server || cl_redownloadinggamestate)
+		return;
+
+	// Send back a PT_CANRECEIVEGAMESTATE packet to the server
+	// so they know they can start sending the game state
+	netbuffer->packettype = PT_CANRECEIVEGAMESTATE;
+	if (!HSendPacket(servernode, true, 0, 0))
+		return;
+
+	CONS_Printf(M_GetText("Reloading game state...\n"));
+
+	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
+
+	// Don't get a corrupt savegame error because tmpsave already exists
+	if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
+		I_Error("Can't delete %s\n", tmpsave);
+
+	CL_PrepareDownloadSaveGame(tmpsave);
+
+	cl_redownloadinggamestate = true;
+}
+
+static void PT_CanReceiveGamestate(SINT8 node)
+{
+	if (client || sendingsavegame[node])
+		return;
+
+	CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]);
+
+	SV_SendSaveGame(node, true); // Resend a complete game state
+	resendingsavegame[node] = true;
+}
+
 /** Handles a packet received from a node that isn't in game
   *
   * \param node The packet sender
@@ -4378,9 +3908,6 @@ static void HandlePacketFromAwayNode(SINT8 node)
 
 		case PT_SERVERCFG: // Positive response of client join request
 		{
-			INT32 j;
-			UINT8 *scp;
-
 			if (server && serverrunning && node != servernode)
 			{ // but wait I thought I'm the server?
 				Net_CloseConnection(node);
@@ -4396,8 +3923,6 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
 				G_SetGametype(netbuffer->u.servercfg.gametype);
 				modifiedgame = netbuffer->u.servercfg.modifiedgame;
-				for (j = 0; j < MAXPLAYERS; j++)
-					adminplayers[j] = netbuffer->u.servercfg.adminplayers[j];
 				memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
 			}
 
@@ -4416,23 +3941,6 @@ static void HandlePacketFromAwayNode(SINT8 node)
 #endif
 			DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
 
-			memset(playeringame, 0, sizeof(playeringame));
-			for (j = 0; j < MAXPLAYERS; j++)
-			{
-				if (netbuffer->u.servercfg.playerskins[j] == 0xFF
-				 && netbuffer->u.servercfg.playercolor[j] == 0xFFFF
-				 && netbuffer->u.servercfg.playeravailabilities[j] == 0xFFFFFFFF)
-					continue; // not in game
-
-				playeringame[j] = true;
-				players[j].availabilities = (UINT32)LONG(netbuffer->u.servercfg.playeravailabilities[j]);
-				SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]);
-				players[j].skincolor = netbuffer->u.servercfg.playercolor[j];
-			}
-
-			scp = netbuffer->u.servercfg.varlengthinputs;
-			CV_LoadPlayerNames(&scp);
-			CV_LoadNetVars(&scp);
 #ifndef NONET
 			/// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook?
 			///       Shouldn't them be downloaded even at intermission time?
@@ -4532,11 +4040,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 	switch (netbuffer->packettype)
 	{
 // -------------------------------------------- SERVER RECEIVE ----------
-		case PT_RESYNCHGET:
-			if (client)
-				break;
-			SV_AcknowledgeResynchAck(node, netbuffer->u.resynchgot);
-			break;
 		case PT_CLIENTCMD:
 		case PT_CLIENT2CMD:
 		case PT_CLIENTMIS:
@@ -4546,10 +4049,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 			if (client)
 				break;
 
-			// Ignore tics from those not synched
-			if (resynch_inprogress[node] && nettics[node] == gametic)
-				break;
-
 			// To save bytes, only the low byte of tic numbers are sent
 			// Use ExpandTics to figure out what the rest of the bytes are
 			realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node);
@@ -4576,9 +4075,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 				|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
 				break;
 
-			// If a client sends a ticcmd it should mean they are done receiving the savegame
-			sendingsavegame[node] = false;
-
 			// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
 			/// \todo Use a separate cvar for that kind of timeout?
 			freezetimeout[node] = I_GetTime() + connectiontimeout;
@@ -4603,21 +4099,20 @@ static void HandlePacketFromPlayer(SINT8 node)
 				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
 					&netbuffer->u.client2pak.cmd2, 1);
 
-			// A delay before we check resynching
-			// Used on join or just after a synch fail
-			if (resynch_delay[node])
-			{
-				--resynch_delay[node];
-				break;
-			}
 			// Check player consistancy during the level
-			if (realstart <= gametic && realstart > gametic - BACKUPTICS+1 && gamestate == GS_LEVEL
-				&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy))
+			if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL
+				&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy)
+				&& !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime()
+				&& !SV_ResendingSavegameToAnyone())
 			{
-				SV_RequireResynch(node);
-
-				if (cv_resynchattempts.value && resynch_score[node] <= (unsigned)cv_resynchattempts.value*250)
+				if (cv_resynchattempts.value)
 				{
+					// Tell the client we are about to resend them the gamestate
+					netbuffer->packettype = PT_WILLRESENDGAMESTATE;
+					HSendPacket(node, true, 0, 0);
+
+					resendingsavegame[node] = true;
+
 					if (cv_blamecfail.value)
 						CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
 							netconsole+1, player_names[netconsole],
@@ -4637,8 +4132,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 					break;
 				}
 			}
-			else if (resynch_score[node])
-				--resynch_score[node];
 			break;
 		case PT_TEXTCMD2: // splitscreen special
 			netconsole = nodetoplayer2[node];
@@ -4764,6 +4257,9 @@ static void HandlePacketFromPlayer(SINT8 node)
 			Net_CloseConnection(node);
 			nodeingame[node] = false;
 			break;
+		case PT_CANRECEIVEGAMESTATE:
+			PT_CanReceiveGamestate(node);
+			break;
 		case PT_ASKLUAFILE:
 			if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
 				AddLuaFileToSendQueue(node, luafiletransfers->realfilename);
@@ -4772,25 +4268,12 @@ static void HandlePacketFromPlayer(SINT8 node)
 			if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING)
 				SV_HandleLuaFileSent(node);
 			break;
-// -------------------------------------------- CLIENT RECEIVE ----------
-		case PT_RESYNCHEND:
-			// Only accept PT_RESYNCHEND from the server.
-			if (node != servernode)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node);
-				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-				break;
-			}
-			resynch_local_inprogress = false;
-
-			P_SetRandSeed(netbuffer->u.resynchend.randomseed);
-
-			if (gametyperules & GTR_TEAMFLAGS)
-				resynch_read_ctf(&netbuffer->u.resynchend);
-			resynch_read_others(&netbuffer->u.resynchend);
-
+		case PT_RECEIVEDGAMESTATE:
+			sendingsavegame[node] = false;
+			resendingsavegame[node] = false;
+			savegameresendcooldown[node] = I_GetTime() + 15 * TICRATE;
 			break;
+// -------------------------------------------- CLIENT RECEIVE ----------
 		case PT_SERVERTICS:
 			// Only accept PT_SERVERTICS from the server.
 			if (node != servernode)
@@ -4851,18 +4334,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 							"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
 			}
 			break;
-		case PT_RESYNCHING:
-			// Only accept PT_RESYNCHING from the server.
-			if (node != servernode)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node);
-				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-				break;
-			}
-			resynch_local_inprogress = true;
-			CL_AcknowledgeResynch(&netbuffer->u.resynchpak);
-			break;
 		case PT_PING:
 			// Only accept PT_PING from the server.
 			if (node != servernode)
@@ -4907,6 +4378,9 @@ static void HandlePacketFromPlayer(SINT8 node)
 			if (server)
 				PT_FileReceived();
 			break;
+		case PT_WILLRESENDGAMESTATE:
+			PT_WillResendGamestate();
+			break;
 		case PT_SENDINGLUAFILE:
 			if (client)
 				CL_PrepareDownloadLuaFile();
@@ -5009,70 +4483,73 @@ static INT16 Consistancy(void)
 		ret += P_GetRandSeed();
 
 #ifdef MOBJCONSISTANCY
-	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	if (gamestate == GS_LEVEL)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-			continue;
-
-		mo = (mobj_t *)th;
-
-		if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			ret -= mo->type;
-			ret += mo->x;
-			ret -= mo->y;
-			ret += mo->z;
-			ret -= mo->momx;
-			ret += mo->momy;
-			ret -= mo->momz;
-			ret += mo->angle;
-			ret -= mo->flags;
-			ret += mo->flags2;
-			ret -= mo->eflags;
-			if (mo->target)
-			{
-				ret += mo->target->type;
-				ret -= mo->target->x;
-				ret += mo->target->y;
-				ret -= mo->target->z;
-				ret += mo->target->momx;
-				ret -= mo->target->momy;
-				ret += mo->target->momz;
-				ret -= mo->target->angle;
-				ret += mo->target->flags;
-				ret -= mo->target->flags2;
-				ret += mo->target->eflags;
-				ret -= mo->target->state - states;
-				ret += mo->target->tics;
-				ret -= mo->target->sprite;
-				ret += mo->target->frame;
-			}
-			else
-				ret ^= 0x3333;
-			if (mo->tracer && mo->tracer->type != MT_OVERLAY)
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+	
+			mo = (mobj_t *)th;
+	
+			if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
 			{
-				ret += mo->tracer->type;
-				ret -= mo->tracer->x;
-				ret += mo->tracer->y;
-				ret -= mo->tracer->z;
-				ret += mo->tracer->momx;
-				ret -= mo->tracer->momy;
-				ret += mo->tracer->momz;
-				ret -= mo->tracer->angle;
-				ret += mo->tracer->flags;
-				ret -= mo->tracer->flags2;
-				ret += mo->tracer->eflags;
-				ret -= mo->tracer->state - states;
-				ret += mo->tracer->tics;
-				ret -= mo->tracer->sprite;
-				ret += mo->tracer->frame;
+				ret -= mo->type;
+				ret += mo->x;
+				ret -= mo->y;
+				ret += mo->z;
+				ret -= mo->momx;
+				ret += mo->momy;
+				ret -= mo->momz;
+				ret += mo->angle;
+				ret -= mo->flags;
+				ret += mo->flags2;
+				ret -= mo->eflags;
+				if (mo->target)
+				{
+					ret += mo->target->type;
+					ret -= mo->target->x;
+					ret += mo->target->y;
+					ret -= mo->target->z;
+					ret += mo->target->momx;
+					ret -= mo->target->momy;
+					ret += mo->target->momz;
+					ret -= mo->target->angle;
+					ret += mo->target->flags;
+					ret -= mo->target->flags2;
+					ret += mo->target->eflags;
+					ret -= mo->target->state - states;
+					ret += mo->target->tics;
+					ret -= mo->target->sprite;
+					ret += mo->target->frame;
+				}
+				else
+					ret ^= 0x3333;
+				if (mo->tracer && mo->tracer->type != MT_OVERLAY)
+				{
+					ret += mo->tracer->type;
+					ret -= mo->tracer->x;
+					ret += mo->tracer->y;
+					ret -= mo->tracer->z;
+					ret += mo->tracer->momx;
+					ret -= mo->tracer->momy;
+					ret += mo->tracer->momz;
+					ret -= mo->tracer->angle;
+					ret += mo->tracer->flags;
+					ret -= mo->tracer->flags2;
+					ret += mo->tracer->eflags;
+					ret -= mo->tracer->state - states;
+					ret += mo->tracer->tics;
+					ret -= mo->tracer->sprite;
+					ret += mo->tracer->frame;
+				}
+				else
+					ret ^= 0xAAAA;
+				ret -= mo->state - states;
+				ret += mo->tics;
+				ret -= mo->sprite;
+				ret += mo->frame;
 			}
-			else
-				ret ^= 0xAAAA;
-			ret -= mo->state - states;
-			ret += mo->tics;
-			ret -= mo->sprite;
-			ret += mo->frame;
 		}
 	}
 #endif
@@ -5364,7 +4841,7 @@ void TryRunTics(tic_t realtics)
 	if (player_joining)
 		return;
 
-	if (neededtic > gametic && !resynch_local_inprogress)
+	if (neededtic > gametic)
 	{
 		if (advancedemo)
 		{
@@ -5379,10 +4856,18 @@ void TryRunTics(tic_t realtics)
 			{
 				DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
 
+				ps_tictime = I_GetPreciseTime();
+
 				G_Ticker((gametic % NEWTICRATERATIO) == 0);
 				ExtraDataTicker();
 				gametic++;
 				consistancy[gametic%BACKUPTICS] = Consistancy();
+
+				ps_tictime = I_GetPreciseTime() - ps_tictime;
+
+				// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
+				if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
+					break;
 			}
 	}
 }
@@ -5516,13 +5001,18 @@ void NetUpdate(void)
 	// client send the command after a receive of the server
 	// the server send before because in single player is beter
 
+#ifdef MASTERSERVER
 	MasterClient_Ticker(); // Acking the Master Server
+#endif
 
 	if (client)
 	{
-		if (!resynch_local_inprogress)
-			CL_SendClientCmd(); // Send tic cmd
-		hu_resynching = resynch_local_inprogress;
+		// If the client just finished redownloading the game state, load it
+		if (cl_redownloadinggamestate && fileneeded[0].status == FS_FOUND)
+			CL_ReloadReceivedSavegame();
+
+		CL_SendClientCmd(); // Send tic cmd
+		hu_redownloadinggamestate = cl_redownloadinggamestate;
 	}
 	else
 	{
@@ -5530,46 +5020,33 @@ void NetUpdate(void)
 		{
 			INT32 counts;
 
-			hu_resynching = false;
+			hu_redownloadinggamestate = false;
 
 			firstticstosend = gametic;
 			for (i = 0; i < MAXNETNODES; i++)
 				if (nodeingame[i] && nettics[i] < firstticstosend)
+				{
 					firstticstosend = nettics[i];
 
+					if (maketic + 1 >= nettics[i] + BACKUPTICS)
+						Net_ConnectionTimeout(i);
+				}
+
 			// Don't erase tics not acknowledged
 			counts = realtics;
 
-			for (i = 0; i < MAXNETNODES; ++i)
-				if (resynch_inprogress[i])
-				{
-					if (!nodeingame[i] || nettics[i] == gametic)
-					{
-						SV_SendResynch(i);
-						counts = -666;
-					}
-					else
-						counts = 0; // Let the client catch up with the server
-				}
-
-			// Do not make tics while resynching
-			if (counts != -666)
-			{
-				if (maketic + counts >= firstticstosend + BACKUPTICS)
-					counts = firstticstosend+BACKUPTICS-maketic-1;
+			if (maketic + counts >= firstticstosend + BACKUPTICS)
+				counts = firstticstosend+BACKUPTICS-maketic-1;
 
-				for (i = 0; i < counts; i++)
-					SV_Maketic(); // Create missed tics and increment maketic
+			for (i = 0; i < counts; i++)
+				SV_Maketic(); // Create missed tics and increment maketic
 
-				for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged
-					D_Clearticcmd(tictoclear);                    // Clear the maketic the new tic
+			for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged
+				D_Clearticcmd(tictoclear);                    // Clear the maketic the new tic
 
-				SV_SendTics();
+			SV_SendTics();
 
-				neededtic = maketic; // The server is a client too
-			}
-			else
-				hu_resynching = true;
+			neededtic = maketic; // The server is a client too
 		}
 	}
 
@@ -5591,7 +5068,13 @@ void NetUpdate(void)
 	if (nowtime > resptime)
 	{
 		resptime = nowtime;
+#ifdef HAVE_THREADS
+		I_lock_mutex(&m_menu_mutex);
+#endif
 		M_Ticker();
+#ifdef HAVE_THREADS
+		I_unlock_mutex(m_menu_mutex);
+#endif
 		CON_Ticker();
 	}
 
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index ed72eef4b95c636882f98b0d15a452552675ac11..9735fd6b261a67bc142736113fc679a5786d2880 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -14,10 +14,12 @@
 #define __D_CLISRV__
 
 #include "d_ticcmd.h"
+#include "d_net.h"
 #include "d_netcmd.h"
 #include "d_net.h"
 #include "tables.h"
 #include "d_player.h"
+#include "mserv.h"
 
 /*
 The 'packet version' is used to distinguish packet formats.
@@ -62,8 +64,10 @@ typedef enum
 	PT_REQUESTFILE,   // Client requests a file transfer
 	PT_ASKINFOVIAMS,  // Packet from the MS requesting info be sent to new client.
 	                  // If this ID changes, update masterserver definition.
-	PT_RESYNCHEND,    // Player is now resynched and is being requested to remake the gametic
-	PT_RESYNCHGET,    // Player got resynch packet
+
+	PT_WILLRESENDGAMESTATE, // Hey Client, I am about to resend you the gamestate!
+	PT_CANRECEIVEGAMESTATE, // Okay Server, I'm ready to receive it, you can go ahead.
+	PT_RECEIVEDGAMESTATE,   // Thank you Server, I am ready to play again!
 
 	PT_SENDINGLUAFILE, // Server telling a client Lua needs to open a file
 	PT_ASKLUAFILE,     // Client telling the server they don't have the file
@@ -83,8 +87,6 @@ typedef enum
 	PT_TEXTCMD2,      // Splitscreen text commands.
 	PT_CLIENTJOIN,    // Client wants to join; used in start game.
 	PT_NODETIMEOUT,   // Packet sent to self if the connection times out.
-	PT_RESYNCHING,    // Packet sent to resync players.
-	                  // Blocks game advance until synched.
 
 	PT_LOGIN,         // Login attempt from the client.
 
@@ -137,168 +139,6 @@ typedef struct
 	ticcmd_t cmds[45]; // Normally [BACKUPTIC][MAXPLAYERS] but too large
 } ATTRPACK servertics_pak;
 
-// Sent to client when all consistency data
-// for players has been restored
-typedef struct
-{
-	UINT32 randomseed;
-
-	// CTF flag stuff
-	SINT8 flagplayer[2];
-	INT32 flagloose[2];
-	INT32 flagflags[2];
-	fixed_t flagx[2];
-	fixed_t flagy[2];
-	fixed_t flagz[2];
-
-	UINT32 ingame;  // Spectator bit for each player
-	UINT32 outofcoop;  // outofcoop bit for each player
-	INT32 ctfteam[MAXPLAYERS]; // Which team? (can't be 1 bit, since in regular Match there are no teams)
-
-	// Resynch game scores and the like all at once
-	UINT32 score[MAXPLAYERS]; // Everyone's score
-	INT16 numboxes[MAXPLAYERS];
-	INT16 totalring[MAXPLAYERS];
-	tic_t realtime[MAXPLAYERS];
-	UINT8 laps[MAXPLAYERS];
-} ATTRPACK resynchend_pak;
-
-typedef struct
-{
-	// Player stuff
-	UINT8 playernum;
-
-	// Do not send anything visual related.
-	// Only send data that we need to know for physics.
-	UINT8 playerstate; // playerstate_t
-	UINT32 pflags; // pflags_t
-	UINT8 panim; // panim_t
-
-	INT16 angleturn;
-	INT16 oldrelangleturn;
-
-	angle_t aiming;
-	INT32 currentweapon;
-	INT32 ringweapons;
-	UINT16 ammoremoval;
-	tic_t ammoremovaltimer;
-	INT32 ammoremovalweapon;
-	UINT16 powers[NUMPOWERS];
-
-	// Score is resynched in the confirm resync packet
-	INT16 rings;
-	INT16 spheres;
-	SINT8 lives;
-	SINT8 continues;
-	UINT8 scoreadd;
-	SINT8 xtralife;
-	SINT8 pity;
-
-	UINT16 skincolor;
-	INT32 skin;
-	UINT32 availabilities;
-	// Just in case Lua does something like
-	// modify these at runtime
-	fixed_t camerascale;
-	fixed_t shieldscale;
-	fixed_t normalspeed;
-	fixed_t runspeed;
-	UINT8 thrustfactor;
-	UINT8 accelstart;
-	UINT8 acceleration;
-	UINT8 charability;
-	UINT8 charability2;
-	UINT32 charflags;
-	UINT32 thokitem; // mobjtype_t
-	UINT32 spinitem; // mobjtype_t
-	UINT32 revitem; // mobjtype_t
-	UINT32 followitem; // mobjtype_t
-	fixed_t actionspd;
-	fixed_t mindash;
-	fixed_t maxdash;
-	fixed_t jumpfactor;
-	fixed_t playerheight;
-	fixed_t playerspinheight;
-
-	fixed_t speed;
-	UINT8 secondjump;
-	UINT8 fly1;
-	tic_t glidetime;
-	UINT8 climbing;
-	INT32 deadtimer;
-	tic_t exiting;
-	UINT8 homing;
-	tic_t dashmode;
-	tic_t skidtime;
-	fixed_t cmomx;
-	fixed_t cmomy;
-	fixed_t rmomx;
-	fixed_t rmomy;
-
-	INT32 weapondelay;
-	INT32 tossdelay;
-
-	INT16 starpostx;
-	INT16 starposty;
-	INT16 starpostz;
-	INT32 starpostnum;
-	tic_t starposttime;
-	angle_t starpostangle;
-	fixed_t starpostscale;
-
-	INT32 maxlink;
-	fixed_t dashspeed;
-	angle_t angle_pos;
-	angle_t old_angle_pos;
-	tic_t bumpertime;
-	INT32 flyangle;
-	tic_t drilltimer;
-	INT32 linkcount;
-	tic_t linktimer;
-	INT32 anotherflyangle;
-	tic_t nightstime;
-	INT32 drillmeter;
-	UINT8 drilldelay;
-	UINT8 bonustime;
-	UINT8 mare;
-	INT16 lastsidehit, lastlinehit;
-
-	tic_t losstime;
-	UINT8 timeshit;
-	INT32 onconveyor;
-
-	//player->mo stuff
-	UINT8 hasmo; // Boolean
-
-	INT32 health;
-	angle_t angle;
-	angle_t rollangle;
-	fixed_t x;
-	fixed_t y;
-	fixed_t z;
-	fixed_t momx;
-	fixed_t momy;
-	fixed_t momz;
-	fixed_t friction;
-	fixed_t movefactor;
-
-	spritenum_t sprite;
-	UINT32 frame;
-	UINT8 sprite2;
-	UINT16 anim_duration;
-	INT32 tics;
-	statenum_t statenum;
-	UINT32 flags;
-	UINT32 flags2;
-	UINT16 eflags;
-
-	fixed_t radius;
-	fixed_t height;
-	fixed_t scale;
-	fixed_t destscale;
-	fixed_t scalespeed;
-} ATTRPACK resynch_pak;
-
 typedef struct
 {
 	UINT8 version; // Different versions don't work
@@ -312,18 +152,10 @@ typedef struct
 	UINT8 clientnode;
 	UINT8 gamestate;
 
-	// 0xFF == not in game; else player skin num
-	UINT8 playerskins[MAXPLAYERS];
-	UINT16 playercolor[MAXPLAYERS];
-	UINT32 playeravailabilities[MAXPLAYERS];
-
 	UINT8 gametype;
 	UINT8 modifiedgame;
-	SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed
 
 	char server_context[8]; // Unique context id, generated at server startup.
-
-	UINT8 varlengthinputs[0]; // Playernames and netvars
 } ATTRPACK serverconfig_pak;
 
 typedef struct
@@ -460,9 +292,6 @@ typedef struct
 		client2cmd_pak client2pak;          //         200 bytes
 		servertics_pak serverpak;           //      132495 bytes (more around 360, no?)
 		serverconfig_pak servercfg;         //         773 bytes
-		resynchend_pak resynchend;          //
-		resynch_pak resynchpak;             //
-		UINT8 resynchgot;                   //
 		UINT8 textcmd[MAXTEXTCMD+1];        //       66049 bytes (wut??? 64k??? More like 257 bytes...)
 		filetx_pak filetxpak;               //         139 bytes
 		fileack_pak fileack;
@@ -483,7 +312,7 @@ typedef struct
 #pragma pack()
 #endif
 
-#define MAXSERVERLIST 64 // Depends only on the display
+#define MAXSERVERLIST (MAXNETNODES-1)
 typedef struct
 {
 	SINT8 node;
@@ -525,7 +354,12 @@ typedef enum
 
 } kickreason_t;
 
+/* the max number of name changes in some time period */
+#define MAXNAMECHANGES (5)
+#define NAMECHANGERATE (60*TICRATE)
+
 extern boolean server;
+extern boolean serverrunning;
 #define client (!server)
 extern boolean dedicated; // For dedicated server
 extern UINT16 software_MAXPACKETLENGTH;
@@ -540,7 +374,7 @@ extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 extern tic_t servermaxping;
 
-extern consvar_t cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_joindelay, cv_rejointimeout;
+extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_joindelay, cv_rejointimeout;
 extern consvar_t cv_resynchattempts, cv_blamecfail;
 extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
 
@@ -565,6 +399,7 @@ void CL_AddSplitscreenPlayer(void);
 void CL_RemoveSplitscreenPlayer(void);
 void CL_Reset(void);
 void CL_ClearPlayer(INT32 playernum);
+void CL_QueryServerList(msg_server_t *list);
 void CL_UpdateServerList(boolean internetsearch, INT32 room);
 // Is there a game running
 boolean Playing(void);
@@ -598,7 +433,7 @@ UINT8 GetFreeXCmdSize(void);
 
 void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest);
 
-extern UINT8 hu_resynching;
+extern UINT8 hu_redownloadinggamestate;
 
 extern UINT8 adminpassmd5[16];
 extern boolean adminpasswordset;
diff --git a/src/d_main.c b/src/d_main.c
index 4cdb6b94b5029460200cbe7c4acbc2fa9e910ccc..3331de040b959db669dc3fe9572f96864feaefeb 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -24,12 +24,6 @@
 #include <unistd.h> // for getcwd
 #endif
 
-#ifdef PC_DOS
-#include <stdio.h> // for snprintf
-int	snprintf(char *str, size_t n, const char *fmt, ...);
-//int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
-#endif
-
 #ifdef _WIN32
 #include <direct.h>
 #include <malloc.h>
@@ -46,6 +40,7 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 #include "hu_stuff.h"
 #include "i_sound.h"
 #include "i_system.h"
+#include "i_threads.h"
 #include "i_video.h"
 #include "m_argv.h"
 #include "m_menu.h"
@@ -66,12 +61,13 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 #include "p_local.h" // chasecam
 #include "mserv.h" // ms_RoomId
 #include "m_misc.h" // screenshot functionality
-#include "dehacked.h" // Dehacked list test
+#include "deh_tables.h" // Dehacked list test
 #include "m_cond.h" // condition initialization
 #include "fastcmp.h"
 #include "keys.h"
 #include "filesrch.h" // refreshdirmenu, mainwadstally
 #include "g_input.h" // tutorial mode control scheming
+#include "m_perfstats.h"
 
 #ifdef CMAKECONFIG
 #include "config.h"
@@ -104,14 +100,13 @@ UINT8 window_notinfocus = false;
 // DEMO LOOP
 //
 static char *startupwadfiles[MAX_WADFILES];
+static char *startuppwads[MAX_WADFILES];
 
 boolean devparm = false; // started game with -devparm
 
 boolean singletics = false; // timedemo
 boolean lastdraw = false;
 
-static void D_CheckRendererState(void);
-
 postimg_t postimgtype = postimg_none;
 INT32 postimgparam;
 postimg_t postimgtype2 = postimg_none;
@@ -162,10 +157,6 @@ void D_PostEvent(const event_t *ev)
 	events[eventhead] = *ev;
 	eventhead = (eventhead+1) & (MAXEVENTS-1);
 }
-// just for lock this function
-#if defined (PC_DOS) && !defined (DOXYGEN)
-void D_PostEvent_end(void) {};
-#endif
 
 // modifier keys
 // Now handled in I_OsPolling
@@ -182,6 +173,8 @@ void D_ProcessEvents(void)
 {
 	event_t *ev;
 
+	boolean eaten;
+
 	for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
 	{
 		ev = &events[eventtail];
@@ -197,11 +190,31 @@ void D_ProcessEvents(void)
 		}
 
 		// Menu input
-		if (M_Responder(ev))
+#ifdef HAVE_THREADS
+		I_lock_mutex(&m_menu_mutex);
+#endif
+		{
+			eaten = M_Responder(ev);
+		}
+#ifdef HAVE_THREADS
+		I_unlock_mutex(m_menu_mutex);
+#endif
+
+		if (eaten)
 			continue; // menu ate the event
 
 		// console input
-		if (CON_Responder(ev))
+#ifdef HAVE_THREADS
+		I_lock_mutex(&con_mutex);
+#endif
+		{
+			eaten = CON_Responder(ev);
+		}
+#ifdef HAVE_THREADS
+		I_unlock_mutex(con_mutex);
+#endif
+
+		if (eaten)
 			continue; // ate the event
 
 		G_Responder(ev);
@@ -222,7 +235,6 @@ INT16 wipetypepost = -1;
 
 static void D_Display(void)
 {
-	INT32 setrenderstillneeded = 0;
 	boolean forcerefresh = false;
 	static boolean wipe = false;
 	INT32 wipedefindex = 0;
@@ -245,48 +257,28 @@ static void D_Display(void)
 	//    create plane polygons, if necessary.
 	// 3. Functions related to switching video
 	//    modes (resolution) are called.
-	// 4. Patch data is freed from memory,
-	//    and recached if necessary.
-	// 5. The frame is ready to be drawn!
-
-	// stop movie if needs to change renderer
-	if (setrenderneeded && (moviemode == MM_APNG))
-		M_StopMovie();
+	// 4. The frame is ready to be drawn!
 
-	// check for change of renderer or screen size (video mode)
+	// Check for change of renderer or screen size (video mode)
 	if ((setrenderneeded || setmodeneeded) && !wipe)
-	{
-		if (setrenderneeded)
-		{
-			CONS_Debug(DBG_RENDER, "setrenderneeded set (%d)\n", setrenderneeded);
-			setrenderstillneeded = setrenderneeded;
-		}
 		SCR_SetMode(); // change video mode
-	}
 
-	if (vid.recalc || setrenderstillneeded)
-	{
+	// Recalc the screen
+	if (vid.recalc)
 		SCR_Recalc(); // NOTE! setsizeneeded is set by SCR_Recalc()
-#ifdef HWRENDER
-		// Shoot! The screen texture was flushed!
-		if ((rendermode == render_opengl) && (gamestate == GS_INTERMISSION))
-			usebuffer = false;
-#endif
-	}
 
+	// View morph
 	if (rendermode == render_soft && !splitscreen)
 		R_CheckViewMorph();
 
-	// change the view size if needed
-	if (setsizeneeded || setrenderstillneeded)
+	// Change the view size if needed
+	// Set by changing video mode or renderer
+	if (setsizeneeded)
 	{
 		R_ExecuteSetViewSize();
 		forcerefresh = true; // force background redraw
 	}
 
-	// Lactozilla: Renderer switching
-	D_CheckRendererState();
-
 	// draw buffered stuff to screen
 	// Used only by linux GGI version
 	I_UpdateNoBlit();
@@ -421,7 +413,7 @@ static void D_Display(void)
 
 			if (!automapactive && !dedicated && cv_renderview.value)
 			{
-				rs_rendercalltime = I_GetTimeMicros();
+				ps_rendercalltime = I_GetPreciseTime();
 				if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
 				{
 					topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
@@ -468,7 +460,7 @@ static void D_Display(void)
 					if (postimgtype2)
 						V_DoPostProcessor(1, postimgtype2, postimgparam2);
 				}
-				rs_rendercalltime = I_GetTimeMicros() - rs_rendercalltime;
+				ps_rendercalltime = I_GetPreciseTime() - ps_rendercalltime;
 			}
 
 			if (lastdraw)
@@ -482,6 +474,8 @@ static void D_Display(void)
 				lastdraw = false;
 			}
 
+			ps_uitime = I_GetPreciseTime();
+
 			if (gamestate == GS_LEVEL)
 			{
 				ST_Drawer();
@@ -491,6 +485,10 @@ static void D_Display(void)
 			else
 				F_TitleScreenDrawer();
 		}
+		else
+		{
+			ps_uitime = I_GetPreciseTime();
+		}
 	}
 
 	// change gamma if needed
@@ -509,7 +507,7 @@ static void D_Display(void)
 		else
 			py = viewwindowy + 4;
 		patch = W_CachePatchName("M_PAUSE", PU_PATCH);
-		V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - SHORT(patch->width))/2, py, 0, patch);
+		V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - patch->width)/2, py, 0, patch);
 #else
 		INT32 y = ((automapactive) ? (32) : (BASEVIDHEIGHT/2));
 		M_DrawTextBox((BASEVIDWIDTH/2) - (60), y - (16), 13, 2);
@@ -520,11 +518,19 @@ static void D_Display(void)
 	// vid size change is now finished if it was on...
 	vid.recalc = 0;
 
+#ifdef HAVE_THREADS
+	I_lock_mutex(&m_menu_mutex);
+#endif
 	M_Drawer(); // menu is drawn even on top of everything
+#ifdef HAVE_THREADS
+	I_unlock_mutex(m_menu_mutex);
+#endif
 	// focus lost moved to M_Drawer
 
 	CON_Drawer();
 
+	ps_uitime = I_GetPreciseTime() - ps_uitime;
+
 	//
 	// wipe update
 	//
@@ -604,103 +610,15 @@ static void D_Display(void)
 			V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-10, V_YELLOWMAP, s);
 		}
 
-		if (cv_renderstats.value)
+		if (cv_perfstats.value)
 		{
-			char s[50];
-			int frametime = I_GetTimeMicros() - rs_prevframetime;
-			int divisor = 1;
-			rs_prevframetime = I_GetTimeMicros();
-
-			if (rs_rendercalltime > 10000) divisor = 1000;
-
-			snprintf(s, sizeof s - 1, "ft   %d", frametime / divisor);
-			V_DrawThinString(30, 10, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "rtot %d", rs_rendercalltime / divisor);
-			V_DrawThinString(30, 20, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "bsp  %d", rs_bsptime / divisor);
-			V_DrawThinString(30, 30, V_MONOSPACE | V_YELLOWMAP, s);
-			snprintf(s, sizeof s - 1, "nbsp %d", rs_numbspcalls);
-			V_DrawThinString(80, 10, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "nspr %d", rs_numsprites);
-			V_DrawThinString(80, 20, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "nnod %d", rs_numdrawnodes);
-			V_DrawThinString(80, 30, V_MONOSPACE | V_BLUEMAP, s);
-			snprintf(s, sizeof s - 1, "npob %d", rs_numpolyobjects);
-			V_DrawThinString(80, 40, V_MONOSPACE | V_BLUEMAP, s);
-			if (rendermode == render_opengl) // OpenGL specific stats
-			{
-#ifdef HWRENDER
-				snprintf(s, sizeof s - 1, "nsrt %d", rs_hw_nodesorttime / divisor);
-				V_DrawThinString(30, 40, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "ndrw %d", rs_hw_nodedrawtime / divisor);
-				V_DrawThinString(30, 50, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "ssrt %d", rs_hw_spritesorttime / divisor);
-				V_DrawThinString(30, 60, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "sdrw %d", rs_hw_spritedrawtime / divisor);
-				V_DrawThinString(30, 70, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "fin  %d", rs_swaptime / divisor);
-				V_DrawThinString(30, 80, V_MONOSPACE | V_YELLOWMAP, s);
-				if (cv_grbatching.value)
-				{
-					snprintf(s, sizeof s - 1, "bsrt %d", rs_hw_batchsorttime / divisor);
-					V_DrawThinString(80, 55, V_MONOSPACE | V_REDMAP, s);
-					snprintf(s, sizeof s - 1, "bdrw %d", rs_hw_batchdrawtime / divisor);
-					V_DrawThinString(80, 65, V_MONOSPACE | V_REDMAP, s);
-
-					snprintf(s, sizeof s - 1, "npol %d", rs_hw_numpolys);
-					V_DrawThinString(130, 10, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "ndc  %d", rs_hw_numcalls);
-					V_DrawThinString(130, 20, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "nshd %d", rs_hw_numshaders);
-					V_DrawThinString(130, 30, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "nvrt %d", rs_hw_numverts);
-					V_DrawThinString(130, 40, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "ntex %d", rs_hw_numtextures);
-					V_DrawThinString(185, 10, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "npf  %d", rs_hw_numpolyflags);
-					V_DrawThinString(185, 20, V_MONOSPACE | V_PURPLEMAP, s);
-					snprintf(s, sizeof s - 1, "ncol %d", rs_hw_numcolors);
-					V_DrawThinString(185, 30, V_MONOSPACE | V_PURPLEMAP, s);
-				}
-#endif
-			}
-			else // software specific stats
-			{
-				snprintf(s, sizeof s - 1, "prtl %d", rs_sw_portaltime / divisor);
-				V_DrawThinString(30, 40, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "plns %d", rs_sw_planetime / divisor);
-				V_DrawThinString(30, 50, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "mskd %d", rs_sw_maskedtime / divisor);
-				V_DrawThinString(30, 60, V_MONOSPACE | V_YELLOWMAP, s);
-				snprintf(s, sizeof s - 1, "fin  %d", rs_swaptime / divisor);
-				V_DrawThinString(30, 70, V_MONOSPACE | V_YELLOWMAP, s);
-			}
+			M_DrawPerfStats();
 		}
 
-		rs_swaptime = I_GetTimeMicros();
+		ps_swaptime = I_GetPreciseTime();
 		I_FinishUpdate(); // page flip or blit buffer
-		rs_swaptime = I_GetTimeMicros() - rs_swaptime;
+		ps_swaptime = I_GetPreciseTime() - ps_swaptime;
 	}
-
-	needpatchflush = false;
-	needpatchrecache = false;
-}
-
-// Check the renderer's state
-// after a possible renderer switch.
-void D_CheckRendererState(void)
-{
-	// flush all patches from memory
-	if (needpatchflush)
-	{
-		Z_FlushCachedPatches();
-		needpatchflush = false;
-	}
-
-	// some patches have been freed,
-	// so cache them again
-	if (needpatchrecache)
-		R_ReloadHUDGraphics();
 }
 
 // =========================================================================
@@ -712,6 +630,7 @@ tic_t rendergametic;
 void D_SRB2Loop(void)
 {
 	tic_t oldentertics = 0, entertic = 0, realtics = 0, rendertimeout = INFTICS;
+	static lumpnum_t gstartuplumpnum;
 
 	if (dedicated)
 		server = true;
@@ -726,12 +645,15 @@ void D_SRB2Loop(void)
 	oldentertics = I_GetTime();
 
 	// end of loading screen: CONS_Printf() will no more call FinishUpdate()
+	con_refresh = false;
 	con_startup = false;
 
 	// make sure to do a d_display to init mode _before_ load a level
 	SCR_SetMode(); // change video mode
 	SCR_Recalc();
 
+	chosenrendermode = render_none;
+
 	// Check and print which version is executed.
 	// Use this as the border between setup and the main game loop being entered.
 	CONS_Printf(
@@ -751,7 +673,12 @@ void D_SRB2Loop(void)
 	*/
 	/* Smells like a hack... Don't fade Sonic's ass into the title screen. */
 	if (gamestate != GS_TITLESCREEN)
-		V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(W_GetNumForName("CONSBACK"), PU_PATCH));
+	{
+		gstartuplumpnum = W_CheckNumForName("STARTUP");
+		if (gstartuplumpnum == LUMPERROR)
+			gstartuplumpnum = W_GetNumForName("MISSING");
+		V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(gstartuplumpnum, PU_PATCH));
+	}
 
 	for (;;)
 	{
@@ -827,9 +754,6 @@ void D_SRB2Loop(void)
 		S_UpdateSounds(); // move positional sounds
 		S_UpdateClosedCaptions();
 
-		// check for media change, loop music..
-		I_UpdateCD();
-
 #ifdef HW3SOUND
 		HW3S_EndFrameUpdate();
 #endif
@@ -939,12 +863,12 @@ void D_StartTitle(void)
 //
 // D_AddFile
 //
-static void D_AddFile(const char *file)
+static void D_AddFile(char **list, const char *file)
 {
 	size_t pnumwadfiles;
 	char *newfile;
 
-	for (pnumwadfiles = 0; startupwadfiles[pnumwadfiles]; pnumwadfiles++)
+	for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
 		;
 
 	newfile = malloc(strlen(file) + 1);
@@ -954,16 +878,16 @@ static void D_AddFile(const char *file)
 	}
 	strcpy(newfile, file);
 
-	startupwadfiles[pnumwadfiles] = newfile;
+	list[pnumwadfiles] = newfile;
 }
 
-static inline void D_CleanFile(void)
+static inline void D_CleanFile(char **list)
 {
 	size_t pnumwadfiles;
-	for (pnumwadfiles = 0; startupwadfiles[pnumwadfiles]; pnumwadfiles++)
+	for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
 	{
-		free(startupwadfiles[pnumwadfiles]);
-		startupwadfiles[pnumwadfiles] = NULL;
+		free(list[pnumwadfiles]);
+		list[pnumwadfiles] = NULL;
 	}
 }
 
@@ -1048,7 +972,7 @@ static void IdentifyVersion(void)
 
 	// Load the IWAD
 	if (srb2wad != NULL && FIL_ReadFileOK(srb2wad))
-		D_AddFile(srb2wad);
+		D_AddFile(startupwadfiles, srb2wad);
 	else
 		I_Error("srb2.pk3 not found! Expected in %s, ss file: %s\n", srb2waddir, srb2wad);
 
@@ -1059,14 +983,14 @@ static void IdentifyVersion(void)
 	// checking in D_SRB2Main
 
 	// Add the maps
-	D_AddFile(va(pandf,srb2waddir,"zones.pk3"));
+	D_AddFile(startupwadfiles, va(pandf,srb2waddir,"zones.pk3"));
 
 	// Add the players
-	D_AddFile(va(pandf,srb2waddir, "player.dta"));
+	D_AddFile(startupwadfiles, va(pandf,srb2waddir, "player.dta"));
 
 #ifdef USE_PATCH_DTA
 	// Add our crappy patches to fix our bugs
-	D_AddFile(va(pandf,srb2waddir,"patch.pk3"));
+	D_AddFile(startupwadfiles, va(pandf,srb2waddir,"patch.pk3"));
 #endif
 
 #if !defined (HAVE_SDL) || defined (HAVE_MIXER)
@@ -1074,9 +998,9 @@ static void IdentifyVersion(void)
 #define MUSICTEST(str) \
 		{\
 			const char *musicpath = va(pandf,srb2waddir,str);\
-			int ms = W_VerifyNMUSlumps(musicpath); \
+			int ms = W_VerifyNMUSlumps(musicpath, false); \
 			if (ms == 1) \
-				D_AddFile(musicpath); \
+				D_AddFile(startupwadfiles, musicpath); \
 			else if (ms == 0) \
 				I_Error("File "str" has been modified with non-music/sound lumps"); \
 		}
@@ -1090,64 +1014,6 @@ static void IdentifyVersion(void)
 #endif
 }
 
-#ifdef PC_DOS
-/* ======================================================================== */
-// Code for printing SRB2's title bar in DOS
-/* ======================================================================== */
-
-//
-// Center the title string, then add the date and time of compilation.
-//
-static inline void D_MakeTitleString(char *s)
-{
-	char temp[82];
-	char *t;
-	const char *u;
-	INT32 i;
-
-	for (i = 0, t = temp; i < 82; i++)
-		*t++=' ';
-
-	for (t = temp + (80-strlen(s))/2, u = s; *u != '\0' ;)
-		*t++ = *u++;
-
-	u = compdate;
-	for (t = temp + 1, i = 11; i-- ;)
-		*t++ = *u++;
-	u = comptime;
-	for (t = temp + 71, i = 8; i-- ;)
-		*t++ = *u++;
-
-	temp[80] = '\0';
-	strcpy(s, temp);
-}
-
-static inline void D_Titlebar(void)
-{
-	char title1[82]; // srb2 title banner
-	char title2[82];
-
-	strcpy(title1, "Sonic Robo Blast 2");
-	strcpy(title2, "Sonic Robo Blast 2");
-
-	D_MakeTitleString(title1);
-
-	// SRB2 banner
-	clrscr();
-	textattr((BLUE<<4)+WHITE);
-	clreol();
-	cputs(title1);
-
-	// standard srb2 banner
-	textattr((RED<<4)+WHITE);
-	clreol();
-	gotoxy((80-strlen(title2))/2, 2);
-	cputs(title2);
-	normvideo();
-	gotoxy(1,3);
-}
-#endif
-
 static void
 D_ConvertVersionNumbers (void)
 {
@@ -1191,7 +1057,7 @@ void D_SRB2Main(void)
 	"in this program.\n\n");
 
 	// keep error messages until the final flush(stderr)
-#if !defined (PC_DOS) && !defined(NOTERMIOS)
+#if !defined(NOTERMIOS)
 	if (setvbuf(stderr, NULL, _IOFBF, 1000))
 		I_OutputMsg("setvbuf didnt work\n");
 #endif
@@ -1206,7 +1072,7 @@ void D_SRB2Main(void)
 	G_LoadGameSettings();
 
 	// Test Dehacked lists
-	DEH_Check();
+	DEH_TableCheck();
 
 	// Netgame URL special case: change working dir to EXE folder.
 	ChangeDirForUrlHandler();
@@ -1229,10 +1095,6 @@ void D_SRB2Main(void)
 	dedicated = M_CheckParm("-dedicated") != 0;
 #endif
 
-#ifdef PC_DOS
-	D_Titlebar();
-#endif
-
 	if (devparm)
 		CONS_Printf(M_GetText("Development mode ON.\n"));
 
@@ -1325,11 +1187,7 @@ void D_SRB2Main(void)
 				const char *s = M_GetNextParm();
 
 				if (s) // Check for NULL?
-				{
-					if (!W_VerifyNMUSlumps(s))
-						G_SetGameModified(true);
-					D_AddFile(s);
-				}
+					D_AddFile(startuppwads, s);
 			}
 		}
 	}
@@ -1369,8 +1227,8 @@ void D_SRB2Main(void)
 
 	// load wad, including the main wad file
 	CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
-	W_InitMultipleFiles(startupwadfiles, mainwads);
-	D_CleanFile();
+	W_InitMultipleFiles(startupwadfiles);
+	D_CleanFile(startupwadfiles);
 
 #ifndef DEVELOP // md5s last updated 22/02/20 (ddmmyy)
 
@@ -1406,8 +1264,6 @@ void D_SRB2Main(void)
 	// setup loading screen
 	SCR_Startup();
 
-	// we need the font of the console
-	CONS_Printf("HU_Init(): Setting up heads up display.\n");
 	HU_Init();
 
 	CON_Init();
@@ -1419,6 +1275,13 @@ void D_SRB2Main(void)
 
 	I_RegisterSysCommands();
 
+	CONS_Printf("W_InitMultipleFiles(): Adding extra PWADs.\n");
+	W_InitMultipleFiles(startuppwads);
+	D_CleanFile(startuppwads);
+
+	CONS_Printf("HU_LoadGraphics()...\n");
+	HU_LoadGraphics();
+
 	//--------------------------------------------------------- CONFIG.CFG
 	M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()"
 
@@ -1431,24 +1294,6 @@ void D_SRB2Main(void)
 	// set user default mode or mode set at cmdline
 	SCR_CheckDefaultMode();
 
-	// Lactozilla: Does the render mode need to change?
-	if ((setrenderneeded != 0) && (setrenderneeded != rendermode))
-	{
-		CONS_Printf(M_GetText("Switching the renderer...\n"));
-		Z_PreparePatchFlush();
-
-		// set needpatchflush / needpatchrecache true for D_CheckRendererState
-		needpatchflush = true;
-		needpatchrecache = true;
-
-		// Set cv_renderer to the new render mode
-		VID_CheckRenderer();
-		SCR_ChangeRendererCVars(rendermode);
-
-		// check the renderer's state
-		D_CheckRendererState();
-	}
-
 	wipegamestate = gamestate;
 
 	savedata.lives = 0; // flag this as not-used
@@ -1472,10 +1317,6 @@ void D_SRB2Main(void)
 		}
 	}
 
-	// Initialize CD-Audio
-	if (M_CheckParm("-usecd") && !dedicated)
-		I_InitCD();
-
 	if (M_CheckParm("-noupload"))
 		COM_BufAddText("downloading 0\n");
 
@@ -1611,6 +1452,12 @@ void D_SRB2Main(void)
 		ultimatemode = true;
 	}
 
+	if (M_CheckParm("-splitscreen"))
+	{
+		autostart = true;
+		splitscreen = true;
+	}
+
 	// rei/miru: bootmap (Idea: starts the game on a predefined map)
 	if (bootmap && !(M_CheckParm("-warp") && M_IsNextParm()))
 	{
@@ -1689,7 +1536,7 @@ void D_SRB2Main(void)
 	{
 		levelstarttic = gametic;
 		G_SetGamestate(GS_LEVEL);
-		if (!P_LoadLevel(&players[consoleplayer], false, false))
+		if (!P_LoadLevel(&players[consoleplayer], false, false, false))
 			I_Quit(); // fail so reset game stuff
 	}
 }
diff --git a/src/d_net.c b/src/d_net.c
index 2823ce2191ebcde53dc1c2a21d1a8ff9e03bef08..d534b1b081360da6f7274f33e14cb178c1d2f632 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -798,8 +798,9 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"REQUESTFILE",
 	"ASKINFOVIAMS",
 
-	"RESYNCHEND",
-	"RESYNCHGET",
+	"WILLRESENDGAMESTATE",
+	"CANRECEIVEGAMESTATE",
+	"RECEIVEDGAMESTATE",
 
 	"SENDINGLUAFILE",
 	"ASKLUAFILE",
@@ -813,7 +814,6 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"TEXTCMD2",
 	"CLIENTJOIN",
 	"NODETIMEOUT",
-	"RESYNCHING",
 	"LOGIN",
 	"PING"
 };
diff --git a/src/d_net.h b/src/d_net.h
index ed4f6628476caca25493d0f66ed09f33cb021f00..ea6b5d4d9a58e6b5d8fd8f62a3ca980e2986b4e6 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -19,7 +19,9 @@
 #define __D_NET__
 
 // Max computers in a game
-#define MAXNETNODES (MAXPLAYERS+4)
+// 127 is probably as high as this can go, because
+// SINT8 is used for nodes sometimes >:(
+#define MAXNETNODES 127
 #define BROADCASTADDR MAXNETNODES
 #define MAXSPLITSCREENPLAYERS 2 // Max number of players on a single computer
 //#define NETSPLITSCREEN // Kart's splitscreen netgame feature
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1490784e171d2f42c3f9e772c5a702cad962168d..e435f1e50ba621d619987eb7ab8f65ffdaa47274 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -198,186 +198,182 @@ static CV_PossibleValue_t matchboxes_cons_t[] = {{0, "Normal"}, {1, "Mystery"},
 static CV_PossibleValue_t chances_cons_t[] = {{0, "MIN"}, {9, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t pause_cons_t[] = {{0, "Server"}, {1, "All"}, {0, NULL}};
 
-consvar_t cv_showinputjoy = {"showinputjoy", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_showinputjoy = CVAR_INIT ("showinputjoy", "Off", 0, CV_OnOff, NULL);
 
 #ifdef NETGAME_DEVMODE
-static consvar_t cv_fishcake = {"fishcake", "Off", CV_CALL|CV_NOSHOWHELP|CV_RESTRICT, CV_OnOff, Fishcake_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_fishcake = CVAR_INIT ("fishcake", "Off", CV_CALL|CV_NOSHOWHELP|CV_RESTRICT, CV_OnOff, Fishcake_OnChange);
 #endif
-static consvar_t cv_dummyconsvar = {"dummyconsvar", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff,
-	DummyConsvar_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_dummyconsvar = CVAR_INIT ("dummyconsvar", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, DummyConsvar_OnChange);
 
-consvar_t cv_restrictskinchange = {"restrictskinchange", "Yes", CV_NETVAR|CV_CHEAT, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_allowteamchange = {"allowteamchange", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_restrictskinchange = CVAR_INIT ("restrictskinchange", "Yes", CV_SAVE|CV_NETVAR|CV_CHEAT, CV_YesNo, NULL);
+consvar_t cv_allowteamchange = CVAR_INIT ("allowteamchange", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
 
-consvar_t cv_startinglives = {"startinglives", "3", CV_NETVAR|CV_CHEAT, startingliveslimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_startinglives = CVAR_INIT ("startinglives", "3", CV_SAVE|CV_NETVAR|CV_CHEAT, startingliveslimit_cons_t, NULL);
 
 static CV_PossibleValue_t respawntime_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "Off"}, {0, NULL}};
-consvar_t cv_respawntime = {"respawndelay", "3", CV_NETVAR|CV_CHEAT, respawntime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_respawntime = CVAR_INIT ("respawndelay", "3", CV_SAVE|CV_NETVAR|CV_CHEAT, respawntime_cons_t, NULL);
 
-consvar_t cv_competitionboxes = {"competitionboxes", "Mystery", CV_NETVAR|CV_CHEAT, competitionboxes_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_competitionboxes = CVAR_INIT ("competitionboxes", "Mystery", CV_SAVE|CV_NETVAR|CV_CHEAT, competitionboxes_cons_t, NULL);
 
-#ifdef SEENAMES
 static CV_PossibleValue_t seenames_cons_t[] = {{0, "Off"}, {1, "Colorless"}, {2, "Team"}, {3, "Ally/Foe"}, {0, NULL}};
-consvar_t cv_seenames = {"seenames", "Ally/Foe", CV_SAVE, seenames_cons_t, 0, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_allowseenames = {"allowseenames", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
-#endif
+consvar_t cv_seenames = CVAR_INIT ("seenames", "Ally/Foe", CV_SAVE, seenames_cons_t, 0);
+consvar_t cv_allowseenames = CVAR_INIT ("allowseenames", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
 
 // names
-consvar_t cv_playername = {"name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_playername2 = {"name2", "Tails", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_playername = CVAR_INIT ("name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange);
+consvar_t cv_playername2 = CVAR_INIT ("name2", "Tails", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name2_OnChange);
 // player colors
 UINT16 lastgoodcolor = SKINCOLOR_BLUE, lastgoodcolor2 = SKINCOLOR_BLUE;
-consvar_t cv_playercolor = {"color", "Blue", CV_CALL|CV_NOINIT, Color_cons_t, Color_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_playercolor2 = {"color2", "Orange", CV_CALL|CV_NOINIT, Color_cons_t, Color2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_playercolor = CVAR_INIT ("color", "Blue", CV_CALL|CV_NOINIT, Color_cons_t, Color_OnChange);
+consvar_t cv_playercolor2 = CVAR_INIT ("color2", "Orange", CV_CALL|CV_NOINIT, Color_cons_t, Color2_OnChange);
 // player's skin, saved for commodity, when using a favorite skins wad..
-consvar_t cv_skin = {"skin", DEFAULTSKIN, CV_CALL|CV_NOINIT, NULL, Skin_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_skin2 = {"skin2", DEFAULTSKIN2, CV_CALL|CV_NOINIT, NULL, Skin2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_skin = CVAR_INIT ("skin", DEFAULTSKIN, CV_CALL|CV_NOINIT, NULL, Skin_OnChange);
+consvar_t cv_skin2 = CVAR_INIT ("skin2", DEFAULTSKIN2, CV_CALL|CV_NOINIT, NULL, Skin2_OnChange);
 
 // saved versions of the above six
-consvar_t cv_defaultplayercolor = {"defaultcolor", "Blue", CV_SAVE, Color_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_defaultplayercolor2 = {"defaultcolor2", "Orange", CV_SAVE, Color_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_defaultskin = {"defaultskin", DEFAULTSKIN, CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_defaultskin2 = {"defaultskin2", DEFAULTSKIN2, CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_defaultplayercolor = CVAR_INIT ("defaultcolor", "Blue", CV_SAVE, Color_cons_t, NULL);
+consvar_t cv_defaultplayercolor2 = CVAR_INIT ("defaultcolor2", "Orange", CV_SAVE, Color_cons_t, NULL);
+consvar_t cv_defaultskin = CVAR_INIT ("defaultskin", DEFAULTSKIN, CV_SAVE, NULL, NULL);
+consvar_t cv_defaultskin2 = CVAR_INIT ("defaultskin2", DEFAULTSKIN2, CV_SAVE, NULL, NULL);
 
-consvar_t cv_skipmapcheck = {"skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_skipmapcheck = CVAR_INIT ("skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL);
 
 INT32 cv_debug;
 
-consvar_t cv_usemouse = {"use_mouse", "On", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usemouse2 = {"use_mouse2", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse2, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_usemouse = CVAR_INIT ("use_mouse", "On", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse);
+consvar_t cv_usemouse2 = CVAR_INIT ("use_mouse2", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse2);
 
-consvar_t cv_usejoystick = {"use_gamepad", "1", CV_SAVE|CV_CALL, usejoystick_cons_t,
-	I_InitJoystick, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usejoystick2 = {"use_gamepad2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t,
-	I_InitJoystick2, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_usejoystick = CVAR_INIT ("use_gamepad", "1", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick);
+consvar_t cv_usejoystick2 = CVAR_INIT ("use_gamepad2", "2", CV_SAVE|CV_CALL, usejoystick_cons_t, I_InitJoystick2);
 #if (defined (LJOYSTICK) || defined (HAVE_SDL))
 #ifdef LJOYSTICK
-consvar_t cv_joyport = {"padport", "/dev/js0", CV_SAVE, joyport_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_joyport2 = {"padport2", "/dev/js0", CV_SAVE, joyport_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; //Alam: for later
+consvar_t cv_joyport = CVAR_INIT ("padport", "/dev/js0", CV_SAVE, joyport_cons_t, NULL);
+consvar_t cv_joyport2 = CVAR_INIT ("padport2", "/dev/js0", CV_SAVE, joyport_cons_t, NULL); //Alam: for later
 #endif
-consvar_t cv_joyscale = {"padscale", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_joyscale2 = {"padscale2", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale2, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_joyscale = CVAR_INIT ("padscale", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale);
+consvar_t cv_joyscale2 = CVAR_INIT ("padscale2", "1", CV_SAVE|CV_CALL, NULL, I_JoyScale2);
 #else
-consvar_t cv_joyscale = {"padscale", "1", CV_SAVE|CV_HIDEN, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}; //Alam: Dummy for save
-consvar_t cv_joyscale2 = {"padscale2", "1", CV_SAVE|CV_HIDEN, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}; //Alam: Dummy for save
+consvar_t cv_joyscale = CVAR_INIT ("padscale", "1", CV_SAVE|CV_HIDEN, NULL, NULL); //Alam: Dummy for save
+consvar_t cv_joyscale2 = CVAR_INIT ("padscale2", "1", CV_SAVE|CV_HIDEN, NULL, NULL); //Alam: Dummy for save
 #endif
 #if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
-consvar_t cv_mouse2port = {"mouse2port", "/dev/gpmdata", CV_SAVE, mouse2port_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouse2opt = {"mouse2opt", "0", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouse2port = CVAR_INIT ("mouse2port", "/dev/gpmdata", CV_SAVE, mouse2port_cons_t, NULL);
+consvar_t cv_mouse2opt = CVAR_INIT ("mouse2opt", "0", CV_SAVE, NULL, NULL);
 #else
-consvar_t cv_mouse2port = {"mouse2port", "COM2", CV_SAVE, mouse2port_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouse2port = CVAR_INIT ("mouse2port", "COM2", CV_SAVE, mouse2port_cons_t, NULL);
 #endif
 
-consvar_t cv_matchboxes = {"matchboxes", "Normal", CV_NETVAR|CV_CHEAT, matchboxes_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_specialrings = {"specialrings", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_powerstones = {"powerstones", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_recycler =      {"tv_recycler",      "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_teleporters =   {"tv_teleporter",    "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_superring =     {"tv_superring",     "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_supersneakers = {"tv_supersneaker",  "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_invincibility = {"tv_invincibility", "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_jumpshield =    {"tv_jumpshield",    "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_watershield =   {"tv_watershield",   "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ringshield =    {"tv_ringshield",    "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_forceshield =   {"tv_forceshield",   "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_bombshield =    {"tv_bombshield",    "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_1up =           {"tv_1up",           "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_eggmanbox =     {"tv_eggman",        "5", CV_NETVAR|CV_CHEAT, chances_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_ringslinger = {"ringslinger", "No", CV_NETVAR|CV_NOSHOWHELP|CV_CALL|CV_CHEAT, CV_YesNo,
-	Ringslinger_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_gravity = {"gravity", "0.5", CV_RESTRICT|CV_FLOAT|CV_CALL, NULL, Gravity_OnChange, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_soundtest = {"soundtest", "0", CV_CALL, NULL, SoundTest_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_matchboxes = CVAR_INIT ("matchboxes", "Normal", CV_SAVE|CV_NETVAR|CV_CHEAT, matchboxes_cons_t, NULL);
+consvar_t cv_specialrings = CVAR_INIT ("specialrings", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_powerstones = CVAR_INIT ("powerstones", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
+
+consvar_t cv_recycler =      CVAR_INIT ("tv_recycler",      "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_teleporters =   CVAR_INIT ("tv_teleporter",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_superring =     CVAR_INIT ("tv_superring",     "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_supersneakers = CVAR_INIT ("tv_supersneaker",  "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_invincibility = CVAR_INIT ("tv_invincibility", "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_jumpshield =    CVAR_INIT ("tv_jumpshield",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_watershield =   CVAR_INIT ("tv_watershield",   "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_ringshield =    CVAR_INIT ("tv_ringshield",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_forceshield =   CVAR_INIT ("tv_forceshield",   "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_bombshield =    CVAR_INIT ("tv_bombshield",    "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_1up =           CVAR_INIT ("tv_1up",           "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+consvar_t cv_eggmanbox =     CVAR_INIT ("tv_eggman",        "5", CV_SAVE|CV_NETVAR|CV_CHEAT, chances_cons_t, NULL);
+
+consvar_t cv_ringslinger = CVAR_INIT ("ringslinger", "No", CV_NETVAR|CV_NOSHOWHELP|CV_CALL|CV_CHEAT, CV_YesNo, Ringslinger_OnChange);
+consvar_t cv_gravity = CVAR_INIT ("gravity", "0.5", CV_RESTRICT|CV_FLOAT|CV_CALL, NULL, Gravity_OnChange);
+
+consvar_t cv_soundtest = CVAR_INIT ("soundtest", "0", CV_CALL, NULL, SoundTest_OnChange);
 
 static CV_PossibleValue_t minitimelimit_cons_t[] = {{15, "MIN"}, {9999, "MAX"}, {0, NULL}};
-consvar_t cv_countdowntime = {"countdowntime", "60", CV_NETVAR|CV_CHEAT, minitimelimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_countdowntime = CVAR_INIT ("countdowntime", "60", CV_SAVE|CV_NETVAR|CV_CHEAT, minitimelimit_cons_t, NULL);
 
-consvar_t cv_touchtag = {"touchtag", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_hidetime = {"hidetime", "30", CV_NETVAR|CV_CALL, minitimelimit_cons_t, Hidetime_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_touchtag = CVAR_INIT ("touchtag", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_hidetime = CVAR_INIT ("hidetime", "30", CV_SAVE|CV_NETVAR|CV_CALL, minitimelimit_cons_t, Hidetime_OnChange);
 
-consvar_t cv_autobalance = {"autobalance", "Off", CV_NETVAR|CV_CALL, CV_OnOff, AutoBalance_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_teamscramble = {"teamscramble", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, teamscramble_cons_t, TeamScramble_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_scrambleonchange = {"scrambleonchange", "Off", CV_NETVAR, teamscramble_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_autobalance = CVAR_INIT ("autobalance", "Off", CV_SAVE|CV_NETVAR|CV_CALL, CV_OnOff, AutoBalance_OnChange);
+consvar_t cv_teamscramble = CVAR_INIT ("teamscramble", "Off", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, teamscramble_cons_t, TeamScramble_OnChange);
+consvar_t cv_scrambleonchange = CVAR_INIT ("scrambleonchange", "Off", CV_SAVE|CV_NETVAR, teamscramble_cons_t, NULL);
 
-consvar_t cv_friendlyfire = {"friendlyfire", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_itemfinder = {"itemfinder", "Off", CV_CALL, CV_OnOff, ItemFinder_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_friendlyfire = CVAR_INIT ("friendlyfire", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_itemfinder = CVAR_INIT ("itemfinder", "Off", CV_CALL, CV_OnOff, ItemFinder_OnChange);
 
 // Scoring type options
-consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_overtime = CVAR_INIT ("overtime", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
 
-consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_rollingdemos = CVAR_INIT ("rollingdemos", "On", CV_SAVE, CV_OnOff, NULL);
 
 static CV_PossibleValue_t timetic_cons_t[] = {{0, "Classic"}, {1, "Centiseconds"}, {2, "Mania"}, {3, "Tics"}, {0, NULL}};
-consvar_t cv_timetic = {"timerres", "Classic", CV_SAVE, timetic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_timetic = CVAR_INIT ("timerres", "Classic", CV_SAVE, timetic_cons_t, NULL);
 
 static CV_PossibleValue_t powerupdisplay_cons_t[] = {{0, "Never"}, {1, "First-person only"}, {2, "Always"}, {0, NULL}};
-consvar_t cv_powerupdisplay = {"powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_powerupdisplay = CVAR_INIT ("powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL);
 
 static CV_PossibleValue_t pointlimit_cons_t[] = {{1, "MIN"}, {MAXSCORE, "MAX"}, {0, "None"}, {0, NULL}};
-consvar_t cv_pointlimit = {"pointlimit", "None", CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t,
-	PointLimit_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_pointlimit = CVAR_INIT ("pointlimit", "None", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t, PointLimit_OnChange);
 static CV_PossibleValue_t timelimit_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "None"}, {0, NULL}};
-consvar_t cv_timelimit = {"timelimit", "None", CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t,
-	TimeLimit_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_timelimit = CVAR_INIT ("timelimit", "None", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t, TimeLimit_OnChange);
 static CV_PossibleValue_t numlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, NULL}};
-consvar_t cv_numlaps = {"numlaps", "4", CV_NETVAR|CV_CALL|CV_NOINIT, numlaps_cons_t,
-	NumLaps_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_numlaps = CVAR_INIT ("numlaps", "4", CV_NETVAR|CV_CALL|CV_NOINIT, numlaps_cons_t, NumLaps_OnChange);
 static CV_PossibleValue_t basenumlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, "Map default"}, {0, NULL}};
-consvar_t cv_basenumlaps = {"basenumlaps", "Map default", CV_NETVAR|CV_CALL|CV_CHEAT, basenumlaps_cons_t, BaseNumLaps_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_basenumlaps = CVAR_INIT ("basenumlaps", "Map default", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT, basenumlaps_cons_t, BaseNumLaps_OnChange);
 
 // Point and time limits for every gametype
 INT32 pointlimits[NUMGAMETYPES];
 INT32 timelimits[NUMGAMETYPES];
 
 // log elemental hazards -- not a netvar, is local to current player
-consvar_t cv_hazardlog = {"hazardlog", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_hazardlog = CVAR_INIT ("hazardlog", "Yes", 0, CV_YesNo, NULL);
 
-consvar_t cv_forceskin = {"forceskin", "None", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_downloading = {"downloading", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_allowexitlevel = {"allowexitlevel", "No", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_forceskin = CVAR_INIT ("forceskin", "None", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange);
+consvar_t cv_downloading = CVAR_INIT ("downloading", "On", 0, CV_OnOff, NULL);
+consvar_t cv_allowexitlevel = CVAR_INIT ("allowexitlevel", "No", CV_SAVE|CV_NETVAR, CV_YesNo, NULL);
 
-consvar_t cv_killingdead = {"killingdead", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_killingdead = CVAR_INIT ("killingdead", "Off", CV_NETVAR, CV_OnOff, NULL);
 
-consvar_t cv_netstat = {"netstat", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // show bandwidth statistics
+consvar_t cv_netstat = CVAR_INIT ("netstat", "Off", 0, CV_OnOff, NULL); // show bandwidth statistics
 static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
-consvar_t cv_nettimeout = {"nettimeout", "350", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_nettimeout = CVAR_INIT ("nettimeout", "350", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange);
 static CV_PossibleValue_t jointimeout_cons_t[] = {{5*TICRATE, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
-consvar_t cv_jointimeout = {"jointimeout", "350", CV_CALL|CV_SAVE, jointimeout_cons_t, JoinTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_jointimeout = CVAR_INIT ("jointimeout", "350", CV_CALL|CV_SAVE|CV_NETVAR, jointimeout_cons_t, JoinTimeout_OnChange);
+consvar_t cv_maxping = CVAR_INIT ("maxping", "0", CV_SAVE|CV_NETVAR, CV_Unsigned, NULL);
 
 static CV_PossibleValue_t pingtimeout_cons_t[] = {{8, "MIN"}, {120, "MAX"}, {0, NULL}};
-consvar_t cv_pingtimeout = {"pingtimeout", "10", CV_SAVE, pingtimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_pingtimeout = CVAR_INIT ("pingtimeout", "10", CV_SAVE|CV_NETVAR, pingtimeout_cons_t, NULL);
 
 // show your ping on the HUD next to framerate. Defaults to warning only (shows up if your ping is > maxping)
 static CV_PossibleValue_t showping_cons_t[] = {{0, "Off"}, {1, "Always"}, {2, "Warning"}, {0, NULL}};
-consvar_t cv_showping = {"showping", "Warning", CV_SAVE, showping_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_showping = CVAR_INIT ("showping", "Warning", CV_SAVE, showping_cons_t, NULL);
 
 // Intermission time Tails 04-19-2002
 static CV_PossibleValue_t inttime_cons_t[] = {{0, "MIN"}, {3600, "MAX"}, {0, NULL}};
-consvar_t cv_inttime = {"inttime", "10", CV_NETVAR, inttime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_inttime = CVAR_INIT ("inttime", "10", CV_SAVE|CV_NETVAR, inttime_cons_t, NULL);
 
 static CV_PossibleValue_t coopstarposts_cons_t[] = {{0, "Per-player"}, {1, "Shared"}, {2, "Teamwork"}, {0, NULL}};
-consvar_t cv_coopstarposts = {"coopstarposts", "Per-player", CV_NETVAR|CV_CALL, coopstarposts_cons_t, CoopStarposts_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_coopstarposts = CVAR_INIT ("coopstarposts", "Per-player", CV_SAVE|CV_NETVAR|CV_CALL, coopstarposts_cons_t, CoopStarposts_OnChange);
 
 static CV_PossibleValue_t cooplives_cons_t[] = {{0, "Infinite"}, {1, "Per-player"}, {2, "Avoid Game Over"}, {3, "Single pool"}, {0, NULL}};
-consvar_t cv_cooplives = {"cooplives", "Avoid Game Over", CV_NETVAR|CV_CALL|CV_CHEAT, cooplives_cons_t, CoopLives_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cooplives = CVAR_INIT ("cooplives", "Avoid Game Over", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT, cooplives_cons_t, CoopLives_OnChange);
 
 static CV_PossibleValue_t advancemap_cons_t[] = {{0, "Off"}, {1, "Next"}, {2, "Random"}, {0, NULL}};
-consvar_t cv_advancemap = {"advancemap", "Next", CV_NETVAR, advancemap_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_advancemap = CVAR_INIT ("advancemap", "Next", CV_SAVE|CV_NETVAR, advancemap_cons_t, NULL);
 
 static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "1/4"}, {2, "Half"}, {3, "3/4"}, {4, "All"}, {0, NULL}};
-consvar_t cv_playersforexit = {"playersforexit", "All", CV_NETVAR, playersforexit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_playersforexit = CVAR_INIT ("playersforexit", "All", CV_SAVE|CV_NETVAR, playersforexit_cons_t, NULL);
+
+consvar_t cv_exitmove = CVAR_INIT ("exitmove", "On", CV_SAVE|CV_NETVAR|CV_CALL, CV_OnOff, ExitMove_OnChange);
 
-consvar_t cv_exitmove = {"exitmove", "On", CV_NETVAR|CV_CALL, CV_OnOff, ExitMove_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_runscripts = CVAR_INIT ("runscripts", "Yes", 0, CV_YesNo, NULL);
 
-consvar_t cv_runscripts = {"runscripts", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_pause = CVAR_INIT ("pausepermission", "Server", CV_SAVE|CV_NETVAR, pause_cons_t, NULL);
+consvar_t cv_mute = CVAR_INIT ("mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange);
 
-consvar_t cv_pause = {"pausepermission", "Server", CV_NETVAR, pause_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mute = {"mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_sleep = CVAR_INIT ("cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL);
 
-consvar_t cv_sleep = {"cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t perfstats_cons_t[] = {
+	{0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {0, NULL}};
+consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", 0, perfstats_cons_t, NULL);
+consvar_t cv_freedemocamera = CVAR_INIT("freedemocamera", "Off", CV_SAVE, CV_OnOff, NULL);
 
 char timedemo_name[256];
 boolean timedemo_csv;
@@ -603,9 +599,7 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_pingtimeout);
 	CV_RegisterVar(&cv_showping);
 
-#ifdef SEENAMES
-	 CV_RegisterVar(&cv_allowseenames);
-#endif
+	CV_RegisterVar(&cv_allowseenames);
 
 	CV_RegisterVar(&cv_dummyconsvar);
 }
@@ -676,16 +670,13 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_zlib_strategya);
 	CV_RegisterVar(&cv_zlib_window_bitsa);
 	CV_RegisterVar(&cv_apng_delay);
+	CV_RegisterVar(&cv_apng_downscale);
 	// GIF variables
 	CV_RegisterVar(&cv_gif_optimize);
 	CV_RegisterVar(&cv_gif_downscale);
 	CV_RegisterVar(&cv_gif_dynamicdelay);
 	CV_RegisterVar(&cv_gif_localcolortable);
 
-#ifdef WALLSPLATS
-	CV_RegisterVar(&cv_splats);
-#endif
-
 	// register these so it is saved to config
 	CV_RegisterVar(&cv_playername);
 	CV_RegisterVar(&cv_playercolor);
@@ -700,11 +691,10 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_defaultplayercolor2);
 	CV_RegisterVar(&cv_defaultskin2);
 
-#ifdef SEENAMES
 	CV_RegisterVar(&cv_seenames);
-#endif
 	CV_RegisterVar(&cv_rollingdemos);
 	CV_RegisterVar(&cv_netstat);
+	CV_RegisterVar(&cv_netticbuffer);
 
 #ifdef NETGAME_DEVMODE
 	CV_RegisterVar(&cv_fishcake);
@@ -864,23 +854,18 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_midimusicvolume);
 	CV_RegisterVar(&cv_numChannels);
 
-	// i_cdmus.c
-	CV_RegisterVar(&cd_volume);
-	CV_RegisterVar(&cdUpdate);
-
 	// screen.c
 	CV_RegisterVar(&cv_fullscreen);
 	CV_RegisterVar(&cv_renderview);
 	CV_RegisterVar(&cv_renderer);
-#ifdef HWRENDER
-	CV_RegisterVar(&cv_newrenderer);
-#endif
 	CV_RegisterVar(&cv_scr_depth);
 	CV_RegisterVar(&cv_scr_width);
 	CV_RegisterVar(&cv_scr_height);
 
 	CV_RegisterVar(&cv_soundtest);
 
+	CV_RegisterVar(&cv_perfstats);
+
 	// ingame object placing
 	COM_AddCommand("objectplace", Command_ObjectPlace_f);
 	COM_AddCommand("writethings", Command_Writethings_f);
@@ -891,6 +876,8 @@ void D_RegisterClientCommands(void)
 //	CV_RegisterVar(&cv_grid);
 //	CV_RegisterVar(&cv_snapto);
 
+	CV_RegisterVar(&cv_freedemocamera);
+
 	// add cheat commands
 	COM_AddCommand("noclip", Command_CheatNoClip_f);
 	COM_AddCommand("god", Command_CheatGod_f);
@@ -1133,6 +1120,8 @@ static void SetPlayerName(INT32 playernum, char *newname)
 			if (netgame)
 				HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false);
 
+			player_name_changes[playernum]++;
+
 			strcpy(player_names[playernum], newname);
 		}
 	}
@@ -1309,7 +1298,12 @@ static void SendNameAndColor(void)
 	snacpending++;
 
 	// Don't change name if muted
-	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
+	if (player_name_changes[consoleplayer] >= MAXNAMECHANGES)
+	{
+		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
+		HU_AddChatText("\x85*You must wait to change your name again", false);
+	}
+	else if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
 	else // Cleanup name if changing it
 		CleanupPlayerName(consoleplayer, cv_playername.zstring);
@@ -1470,8 +1464,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 	skin = READUINT8(*cp);
 
 	// set name
-	if (strcasecmp(player_names[playernum], name) != 0)
-		SetPlayerName(playernum, name);
+	if (player_name_changes[playernum] < MAXNAMECHANGES)
+	{
+		if (strcasecmp(player_names[playernum], name) != 0)
+			SetPlayerName(playernum, name);
+	}
 
 	// set color
 	p->skincolor = color % numskincolors;
@@ -2188,7 +2185,7 @@ static void Command_Pause(void)
 
 	if (cv_pause.value || server || (IsPlayerAdmin(consoleplayer)))
 	{
-		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
+		if (modeattacking || !(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) || (marathonmode && gamestate == GS_INTERMISSION))
 		{
 			CONS_Printf(M_GetText("You can't pause here.\n"));
 			return;
@@ -3299,97 +3296,136 @@ static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum)
   */
 static void Command_Addfile(void)
 {
-	const char *fn, *p;
-	char buf[256];
-	char *buf_p = buf;
-	INT32 i;
-	int musiconly; // W_VerifyNMUSlumps isn't boolean
+	size_t argc = COM_Argc(); // amount of arguments total
+	size_t curarg; // current argument index
 
-	if (COM_Argc() != 2)
+	const char *addedfiles[argc]; // list of filenames already processed
+	size_t numfilesadded = 0; // the amount of filenames processed
+
+	if (argc < 2)
 	{
-		CONS_Printf(M_GetText("addfile <wadfile.wad>: load wad file\n"));
+		CONS_Printf(M_GetText("addfile <filename.pk3/wad/lua/soc> [filename2...] [...]: Load add-ons\n"));
 		return;
 	}
-	else
-		fn = COM_Argv(1);
 
-	// Disallow non-printing characters and semicolons.
-	for (i = 0; fn[i] != '\0'; i++)
-		if (!isprint(fn[i]) || fn[i] == ';')
-			return;
+	// start at one to skip command name
+	for (curarg = 1; curarg < argc; curarg++)
+	{
+		const char *fn, *p;
+		char buf[256];
+		char *buf_p = buf;
+		INT32 i;
+		size_t ii;
+		int musiconly; // W_VerifyNMUSlumps isn't boolean
+		boolean fileadded = false;
 
-	musiconly = W_VerifyNMUSlumps(fn);
+		fn = COM_Argv(curarg);
 
-	if (!musiconly)
-	{
-		// ... But only so long as they contain nothing more then music and sprites.
-		if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
+		// For the amount of filenames previously processed...
+		for (ii = 0; ii < numfilesadded; ii++)
 		{
-			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
-			return;
+			// If this is one of them, don't try to add it.
+			if (!strcmp(fn, addedfiles[ii]))
+			{
+				fileadded = true;
+				break;
+			}
 		}
-		G_SetGameModified(multiplayer);
-	}
 
-	// Add file on your client directly if it is trivial, or you aren't in a netgame.
-	if (!(netgame || multiplayer) || musiconly)
-	{
-		P_AddWadFile(fn);
-		return;
-	}
+		// If we've added this one, skip to the next one.
+		if (fileadded)
+		{
+			CONS_Alert(CONS_WARNING, M_GetText("Already processed %s, skipping\n"), fn);
+			continue;
+		}
 
-	p = fn+strlen(fn);
-	while(--p >= fn)
-		if (*p == '\\' || *p == '/' || *p == ':')
-			break;
-	++p;
+		// Disallow non-printing characters and semicolons.
+		for (i = 0; fn[i] != '\0'; i++)
+			if (!isprint(fn[i]) || fn[i] == ';')
+				return;
 
-	// check total packet size and no of files currently loaded
-	// See W_LoadWadFile in w_wad.c
-	if ((numwadfiles >= MAX_WADFILES)
-	|| ((packetsizetally + nameonlylength(fn) + 22) > MAXFILENEEDED*sizeof(UINT8)))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn);
-		return;
-	}
+		musiconly = W_VerifyNMUSlumps(fn, false);
 
-	WRITESTRINGN(buf_p,p,240);
+		if (musiconly == -1)
+		{
+			addedfiles[numfilesadded++] = fn;
+			continue;
+		}
 
-	// calculate and check md5
-	{
-		UINT8 md5sum[16];
-#ifdef NOMD5
-		memset(md5sum,0,16);
-#else
-		FILE *fhandle;
+		if (!musiconly)
+		{
+			// ... But only so long as they contain nothing more then music and sprites.
+			if (netgame && !(server || IsPlayerAdmin(consoleplayer)))
+			{
+				CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
+				continue;
+			}
+			G_SetGameModified(multiplayer);
+		}
 
-		if ((fhandle = W_OpenWadFile(&fn, true)) != NULL)
+		// Add file on your client directly if it is trivial, or you aren't in a netgame.
+		if (!(netgame || multiplayer) || musiconly)
 		{
-			tic_t t = I_GetTime();
-			CONS_Debug(DBG_SETUP, "Making MD5 for %s\n",fn);
-			md5_stream(fhandle, md5sum);
-			CONS_Debug(DBG_SETUP, "MD5 calc for %s took %f second\n", fn, (float)(I_GetTime() - t)/TICRATE);
-			fclose(fhandle);
+			P_AddWadFile(fn);
+			addedfiles[numfilesadded++] = fn;
+			continue;
 		}
-		else // file not found
+
+		p = fn+strlen(fn);
+		while(--p >= fn)
+			if (*p == '\\' || *p == '/' || *p == ':')
+				break;
+		++p;
+
+		// check total packet size and no of files currently loaded
+		// See W_LoadWadFile in w_wad.c
+		if ((numwadfiles >= MAX_WADFILES)
+		|| ((packetsizetally + nameonlylength(fn) + 22) > MAXFILENEEDED*sizeof(UINT8)))
+		{
+			CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn);
 			return;
+		}
+
+		WRITESTRINGN(buf_p,p,240);
 
-		for (i = 0; i < numwadfiles; i++)
+		// calculate and check md5
 		{
-			if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
+			UINT8 md5sum[16];
+#ifdef NOMD5
+			memset(md5sum,0,16);
+#else
+			FILE *fhandle;
+
+			if ((fhandle = W_OpenWadFile(&fn, true)) != NULL)
 			{
-				CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), fn);
-				return;
+				tic_t t = I_GetTime();
+				CONS_Debug(DBG_SETUP, "Making MD5 for %s\n",fn);
+				md5_stream(fhandle, md5sum);
+				CONS_Debug(DBG_SETUP, "MD5 calc for %s took %f second\n", fn, (float)(I_GetTime() - t)/TICRATE);
+				fclose(fhandle);
+			}
+			else // file not found
+				continue;
+
+			for (i = 0; i < numwadfiles; i++)
+			{
+				if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
+				{
+					CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), fn);
+					continue;
+				}
 			}
-		}
 #endif
-		WRITEMEM(buf_p, md5sum, 16);
-	}
+			WRITEMEM(buf_p, md5sum, 16);
+		}
 
-	if (IsPlayerAdmin(consoleplayer) && (!server)) // Request to add file
-		SendNetXCmd(XD_REQADDFILE, buf, buf_p - buf);
-	else
-		SendNetXCmd(XD_ADDFILE, buf, buf_p - buf);
+		addedfiles[numfilesadded++] = fn;
+
+		if (IsPlayerAdmin(consoleplayer) && (!server)) // Request to add file
+			SendNetXCmd(XD_REQADDFILE, buf, buf_p - buf);
+		else
+			SendNetXCmd(XD_ADDFILE, buf, buf_p - buf);
+	}
 }
 
 static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
@@ -3626,8 +3662,7 @@ static void Command_Playintro_f(void)
   */
 FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void)
 {
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(true);
 	I_Quit();
 }
 
@@ -4289,8 +4324,7 @@ void Command_ExitGame_f(void)
 {
 	INT32 i;
 
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(false);
 
 	D_QuitNetGame();
 	CL_Reset();
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 674c59d7898d3c56026041f88a05428906e67d00..f6e5e513854ea0d8c2921e878b2c43cae4478e91 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -31,9 +31,7 @@ extern consvar_t cv_defaultskin;
 extern consvar_t cv_defaultplayercolor2;
 extern consvar_t cv_defaultskin2;
 
-#ifdef SEENAMES
 extern consvar_t cv_seenames, cv_allowseenames;
-#endif
 extern consvar_t cv_usemouse;
 extern consvar_t cv_usejoystick;
 extern consvar_t cv_usejoystick2;
@@ -75,9 +73,6 @@ extern consvar_t cv_teamscramble;
 extern consvar_t cv_scrambleonchange;
 
 extern consvar_t cv_netstat;
-#ifdef WALLSPLATS
-extern consvar_t cv_splats;
-#endif
 
 extern consvar_t cv_countdowntime;
 extern consvar_t cv_runscripts;
@@ -114,11 +109,15 @@ extern consvar_t cv_skipmapcheck;
 
 extern consvar_t cv_sleep;
 
+extern consvar_t cv_perfstats;
+
 extern char timedemo_name[256];
 extern boolean timedemo_csv;
 extern char timedemo_csv_id[256];
 extern boolean timedemo_quit;
 
+extern consvar_t cv_freedemocamera;
+
 typedef enum
 {
 	XD_NAMEANDCOLOR = 1,
diff --git a/src/d_player.h b/src/d_player.h
index 86a375fe91ae08aca88d99115286dc30cc03310f..7ede1e4f14f48a15d98b330bb1282631da2374fb 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -51,6 +51,9 @@ typedef enum
 	SF_NONIGHTSSUPER    = 1<<15, // Disable super colors for NiGHTS (if you have SF_SUPER)
 	SF_NOSUPERSPRITES   = 1<<16, // Don't use super sprites while super
 	SF_NOSUPERJUMPBOOST = 1<<17, // Disable the jump boost given while super (i.e. Knuckles)
+	SF_CANBUSTWALLS     = 1<<18, // Can naturally bust walls on contact? (i.e. Knuckles)
+	SF_NOSHIELDABILITY  = 1<<19, // Disable shield abilities
+
 	// free up to and including 1<<31
 } skinflags_t;
 
@@ -115,7 +118,7 @@ typedef enum
 
 	// True if button down last tic.
 	PF_ATTACKDOWN = 1<<7,
-	PF_USEDOWN    = 1<<8,
+	PF_SPINDOWN   = 1<<8,
 	PF_JUMPDOWN   = 1<<9,
 	PF_WPNDOWN    = 1<<10,
 
diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h
index 0a8012bb15820eb3b9b151729b522d0910ab95af..2a5ef09818f05fb001b0b03200ca092e47b77607 100644
--- a/src/d_ticcmd.h
+++ b/src/d_ticcmd.h
@@ -31,7 +31,7 @@ typedef enum
 	BT_WEAPONPREV = 1<<5,
 
 	BT_ATTACK     = 1<<6, // shoot rings
-	BT_USE        = 1<<7, // spin
+	BT_SPIN       = 1<<7,
 	BT_CAMLEFT    = 1<<8, // turn camera left
 	BT_CAMRIGHT   = 1<<9, // turn camera right
 	BT_TOSSFLAG   = 1<<10,
diff --git a/src/deh_lua.c b/src/deh_lua.c
new file mode 100644
index 0000000000000000000000000000000000000000..e6a436421cc14096bb1bc02689455a48df499dc1
--- /dev/null
+++ b/src/deh_lua.c
@@ -0,0 +1,697 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  deh_lua.c
+/// \brief Lua SOC library
+
+#include "g_game.h"
+#include "s_sound.h"
+#include "z_zone.h"
+#include "m_menu.h"
+#include "m_misc.h"
+#include "p_local.h"
+#include "st_stuff.h"
+#include "fastcmp.h"
+#include "lua_script.h"
+#include "lua_libs.h"
+
+#include "dehacked.h"
+#include "deh_lua.h"
+#include "deh_tables.h"
+
+#ifdef MUSICSLOT_COMPATIBILITY
+#include "deh_soc.h" // for get_mus
+#endif
+
+// freeslot takes a name (string only!)
+// and allocates it to the appropriate free slot.
+// Returns the slot number allocated for it or nil if failed.
+// ex. freeslot("MT_MYTHING","S_MYSTATE1","S_MYSTATE2")
+// TODO: Error checking! @.@; There's currently no way to know which ones failed and why!
+//
+static inline int lib_freeslot(lua_State *L)
+{
+	int n = lua_gettop(L);
+	int r = 0; // args returned
+	char *s, *type,*word;
+
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+
+	while (n-- > 0)
+	{
+		s = Z_StrDup(luaL_checkstring(L,1));
+		type = strtok(s, "_");
+		if (type)
+			strupr(type);
+		else {
+			Z_Free(s);
+			return luaL_error(L, "Unknown enum type in '%s'\n", luaL_checkstring(L, 1));
+		}
+
+		word = strtok(NULL, "\n");
+		if (word)
+			strupr(word);
+		else {
+			Z_Free(s);
+			return luaL_error(L, "Missing enum name in '%s'\n", luaL_checkstring(L, 1));
+		}
+		if (fastcmp(type, "SFX")) {
+			sfxenum_t sfx;
+			strlwr(word);
+			CONS_Printf("Sound sfx_%s allocated.\n",word);
+			sfx = S_AddSoundFx(word, false, 0, false);
+			if (sfx != sfx_None) {
+				lua_pushinteger(L, sfx);
+				r++;
+			} else
+				CONS_Alert(CONS_WARNING, "Ran out of free SFX slots!\n");
+		}
+		else if (fastcmp(type, "SPR"))
+		{
+			char wad;
+			spritenum_t j;
+			lua_getfield(L, LUA_REGISTRYINDEX, "WAD");
+			wad = (char)lua_tointeger(L, -1);
+			lua_pop(L, 1);
+			for (j = SPR_FIRSTFREESLOT; j <= SPR_LASTFREESLOT; j++)
+			{
+				if (used_spr[(j-SPR_FIRSTFREESLOT)/8] & (1<<(j%8)))
+				{
+					if (!sprnames[j][4] && memcmp(sprnames[j],word,4)==0)
+						sprnames[j][4] = wad;
+					continue; // Already allocated, next.
+				}
+				// Found a free slot!
+				CONS_Printf("Sprite SPR_%s allocated.\n",word);
+				strncpy(sprnames[j],word,4);
+				//sprnames[j][4] = 0;
+				used_spr[(j-SPR_FIRSTFREESLOT)/8] |= 1<<(j%8); // Okay, this sprite slot has been named now.
+				lua_pushinteger(L, j);
+				r++;
+				break;
+			}
+			if (j > SPR_LASTFREESLOT)
+				CONS_Alert(CONS_WARNING, "Ran out of free sprite slots!\n");
+		}
+		else if (fastcmp(type, "S"))
+		{
+			statenum_t i;
+			for (i = 0; i < NUMSTATEFREESLOTS; i++)
+				if (!FREE_STATES[i]) {
+					CONS_Printf("State S_%s allocated.\n",word);
+					FREE_STATES[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+					strcpy(FREE_STATES[i],word);
+					lua_pushinteger(L, S_FIRSTFREESLOT + i);
+					r++;
+					break;
+				}
+			if (i == NUMSTATEFREESLOTS)
+				CONS_Alert(CONS_WARNING, "Ran out of free State slots!\n");
+		}
+		else if (fastcmp(type, "MT"))
+		{
+			mobjtype_t i;
+			for (i = 0; i < NUMMOBJFREESLOTS; i++)
+				if (!FREE_MOBJS[i]) {
+					CONS_Printf("MobjType MT_%s allocated.\n",word);
+					FREE_MOBJS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+					strcpy(FREE_MOBJS[i],word);
+					lua_pushinteger(L, MT_FIRSTFREESLOT + i);
+					r++;
+					break;
+				}
+			if (i == NUMMOBJFREESLOTS)
+				CONS_Alert(CONS_WARNING, "Ran out of free MobjType slots!\n");
+		}
+		else if (fastcmp(type, "SKINCOLOR"))
+		{
+			skincolornum_t i;
+			for (i = 0; i < NUMCOLORFREESLOTS; i++)
+				if (!FREE_SKINCOLORS[i]) {
+					CONS_Printf("Skincolor SKINCOLOR_%s allocated.\n",word);
+					FREE_SKINCOLORS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+					strcpy(FREE_SKINCOLORS[i],word);
+					M_AddMenuColor(numskincolors++);
+					lua_pushinteger(L, SKINCOLOR_FIRSTFREESLOT + i);
+					r++;
+					break;
+				}
+			if (i == NUMCOLORFREESLOTS)
+				CONS_Alert(CONS_WARNING, "Ran out of free skincolor slots!\n");
+		}
+		else if (fastcmp(type, "SPR2"))
+		{
+			// Search if we already have an SPR2 by that name...
+			playersprite_t i;
+			for (i = SPR2_FIRSTFREESLOT; i < free_spr2; i++)
+				if (memcmp(spr2names[i],word,4) == 0)
+					break;
+			// We don't, so allocate a new one.
+			if (i >= free_spr2) {
+				if (free_spr2 < NUMPLAYERSPRITES)
+				{
+					CONS_Printf("Sprite SPR2_%s allocated.\n",word);
+					strncpy(spr2names[free_spr2],word,4);
+					spr2defaults[free_spr2] = 0;
+					lua_pushinteger(L, free_spr2);
+					r++;
+					spr2names[free_spr2++][4] = 0;
+				} else
+					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
+			}
+		}
+		else if (fastcmp(type, "TOL"))
+		{
+			// Search if we already have a typeoflevel by that name...
+			int i;
+			for (i = 0; TYPEOFLEVEL[i].name; i++)
+				if (fastcmp(word, TYPEOFLEVEL[i].name))
+					break;
+
+			// We don't, so allocate a new one.
+			if (TYPEOFLEVEL[i].name == NULL) {
+				if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags.
+					CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n");
+				else {
+					CONS_Printf("TypeOfLevel TOL_%s allocated.\n",word);
+					G_AddTOL(lastcustomtol, word);
+					lua_pushinteger(L, lastcustomtol);
+					lastcustomtol <<= 1;
+					r++;
+				}
+			}
+		}
+		Z_Free(s);
+		lua_remove(L, 1);
+		continue;
+	}
+	return r;
+}
+
+// Wrapper for ALL A_Action functions.
+// Arguments: mobj_t actor, int var1, int var2
+static int action_call(lua_State *L)
+{
+	//actionf_t *action = lua_touserdata(L,lua_upvalueindex(1));
+	actionf_t *action = *((actionf_t **)luaL_checkudata(L, 1, META_ACTION));
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+	var1 = (INT32)luaL_optinteger(L, 3, 0);
+	var2 = (INT32)luaL_optinteger(L, 4, 0);
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	action->acp1(actor);
+	return 0;
+}
+
+// Hardcoded A_Action name to call for super() or NULL if super() would be invalid.
+// Set in lua_infolib.
+const char *superactions[MAXRECURSION];
+UINT8 superstack = 0;
+
+static int lib_dummysuper(lua_State *L)
+{
+	return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions being called by state changes!"); // convoluted, I know. @_@;;
+}
+
+static inline int lib_getenum(lua_State *L)
+{
+	const char *word, *p;
+	fixed_t i;
+	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
+	if (lua_type(L,2) != LUA_TSTRING)
+		return 0;
+	word = lua_tostring(L,2);
+	if (strlen(word) == 1) { // Assume sprite frame if length 1.
+		if (*word >= 'A' && *word <= '~')
+		{
+			lua_pushinteger(L, *word-'A');
+			return 1;
+		}
+		if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
+		return 0;
+	}
+	else if (fastncmp("MF_", word, 3)) {
+		p = word+3;
+		for (i = 0; MOBJFLAG_LIST[i]; i++)
+			if (fastcmp(p, MOBJFLAG_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "mobjflag '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("MF2_", word, 4)) {
+		p = word+4;
+		for (i = 0; MOBJFLAG2_LIST[i]; i++)
+			if (fastcmp(p, MOBJFLAG2_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "mobjflag2 '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("MFE_", word, 4)) {
+		p = word+4;
+		for (i = 0; MOBJEFLAG_LIST[i]; i++)
+			if (fastcmp(p, MOBJEFLAG_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "mobjeflag '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("MTF_", word, 4)) {
+		p = word+4;
+		for (i = 0; i < 4; i++)
+			if (MAPTHINGFLAG_LIST[i] && fastcmp(p, MAPTHINGFLAG_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "mapthingflag '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("PF_", word, 3)) {
+		p = word+3;
+		for (i = 0; PLAYERFLAG_LIST[i]; i++)
+			if (fastcmp(p, PLAYERFLAG_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (fastcmp(p, "FULLSTASIS"))
+		{
+			lua_pushinteger(L, (lua_Integer)PF_FULLSTASIS);
+			return 1;
+		}
+		else if (fastcmp(p, "USEDOWN")) // Remove case when 2.3 nears release...
+		{
+			lua_pushinteger(L, (lua_Integer)PF_SPINDOWN);
+			return 1;
+		}
+		if (mathlib) return luaL_error(L, "playerflag '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("GT_", word, 3)) {
+		p = word;
+		for (i = 0; Gametype_ConstantNames[i]; i++)
+			if (fastcmp(p, Gametype_ConstantNames[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("GTR_", word, 4)) {
+		p = word+4;
+		for (i = 0; GAMETYPERULE_LIST[i]; i++)
+			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "game type rule '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("TOL_", word, 4)) {
+		p = word+4;
+		for (i = 0; TYPEOFLEVEL[i].name; i++)
+			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
+				lua_pushinteger(L, TYPEOFLEVEL[i].flag);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "typeoflevel '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("ML_", word, 3)) {
+		p = word+3;
+		for (i = 0; i < 16; i++)
+			if (ML_LIST[i] && fastcmp(p, ML_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "linedef flag '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("S_",word,2)) {
+		p = word+2;
+		for (i = 0; i < NUMSTATEFREESLOTS; i++) {
+			if (!FREE_STATES[i])
+				break;
+			if (fastcmp(p, FREE_STATES[i])) {
+				lua_pushinteger(L, S_FIRSTFREESLOT+i);
+				return 1;
+			}
+		}
+		for (i = 0; i < S_FIRSTFREESLOT; i++)
+			if (fastcmp(p, STATE_LIST[i]+2)) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		return luaL_error(L, "state '%s' does not exist.\n", word);
+	}
+	else if (fastncmp("MT_",word,3)) {
+		p = word+3;
+		for (i = 0; i < NUMMOBJFREESLOTS; i++) {
+			if (!FREE_MOBJS[i])
+				break;
+			if (fastcmp(p, FREE_MOBJS[i])) {
+				lua_pushinteger(L, MT_FIRSTFREESLOT+i);
+				return 1;
+			}
+		}
+		for (i = 0; i < MT_FIRSTFREESLOT; i++)
+			if (fastcmp(p, MOBJTYPE_LIST[i]+3)) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		return luaL_error(L, "mobjtype '%s' does not exist.\n", word);
+	}
+	else if (fastncmp("SPR_",word,4)) {
+		p = word+4;
+		for (i = 0; i < NUMSPRITES; i++)
+			if (!sprnames[i][4] && fastncmp(p,sprnames[i],4)) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "sprite '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("SPR2_",word,5)) {
+		p = word+5;
+		for (i = 0; i < (fixed_t)free_spr2; i++)
+			if (!spr2names[i][4])
+			{
+				// special 3-char cases, e.g. SPR2_RUN
+				// the spr2names entry will have "_" on the end, as in "RUN_"
+				if (spr2names[i][3] == '_' && !p[3]) {
+					if (fastncmp(p,spr2names[i],3)) {
+						lua_pushinteger(L, i);
+						return 1;
+					}
+				}
+				else if (fastncmp(p,spr2names[i],4)) {
+					lua_pushinteger(L, i);
+					return 1;
+				}
+			}
+		if (mathlib) return luaL_error(L, "player sprite '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (!mathlib && fastncmp("sfx_",word,4)) {
+		p = word+4;
+		for (i = 0; i < NUMSFX; i++)
+			if (S_sfx[i].name && fastcmp(p, S_sfx[i].name)) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		return 0;
+	}
+	else if (mathlib && fastncmp("SFX_",word,4)) { // SOCs are ALL CAPS!
+		p = word+4;
+		for (i = 0; i < NUMSFX; i++)
+			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		return luaL_error(L, "sfx '%s' could not be found.\n", word);
+	}
+	else if (mathlib && fastncmp("DS",word,2)) {
+		p = word+2;
+		for (i = 0; i < NUMSFX; i++)
+			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "sfx '%s' could not be found.\n", word);
+		return 0;
+	}
+#ifdef MUSICSLOT_COMPATIBILITY
+	else if (!mathlib && fastncmp("mus_",word,4)) {
+		p = word+4;
+		if ((i = get_mus(p, false)) == 0)
+			return 0;
+		lua_pushinteger(L, i);
+		return 1;
+	}
+	else if (mathlib && fastncmp("MUS_",word,4)) { // SOCs are ALL CAPS!
+		p = word+4;
+		if ((i = get_mus(p, false)) == 0)
+			return luaL_error(L, "music '%s' could not be found.\n", word);
+		lua_pushinteger(L, i);
+		return 1;
+	}
+	else if (mathlib && (fastncmp("O_",word,2) || fastncmp("D_",word,2))) {
+		p = word+2;
+		if ((i = get_mus(p, false)) == 0)
+			return luaL_error(L, "music '%s' could not be found.\n", word);
+		lua_pushinteger(L, i);
+		return 1;
+	}
+#endif
+	else if (!mathlib && fastncmp("pw_",word,3)) {
+		p = word+3;
+		for (i = 0; i < NUMPOWERS; i++)
+			if (fasticmp(p, POWERS_LIST[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		return 0;
+	}
+	else if (mathlib && fastncmp("PW_",word,3)) { // SOCs are ALL CAPS!
+		p = word+3;
+		for (i = 0; i < NUMPOWERS; i++)
+			if (fastcmp(p, POWERS_LIST[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		return luaL_error(L, "power '%s' could not be found.\n", word);
+	}
+	else if (fastncmp("HUD_",word,4)) {
+		p = word+4;
+		for (i = 0; i < NUMHUDITEMS; i++)
+			if (fastcmp(p, HUDITEMS_LIST[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "huditem '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("SKINCOLOR_",word,10)) {
+		p = word+10;
+		for (i = 0; i < NUMCOLORFREESLOTS; i++) {
+			if (!FREE_SKINCOLORS[i])
+				break;
+			if (fastcmp(p, FREE_SKINCOLORS[i])) {
+				lua_pushinteger(L, SKINCOLOR_FIRSTFREESLOT+i);
+				return 1;
+			}
+		}
+		for (i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++)
+			if (fastcmp(p, COLOR_ENUMS[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		return luaL_error(L, "skincolor '%s' could not be found.\n", word);
+	}
+	else if (fastncmp("GRADE_",word,6))
+	{
+		p = word+6;
+		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
+			if (*p == NIGHTSGRADE_LIST[i])
+			{
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "NiGHTS grade '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("MN_",word,3)) {
+		p = word+3;
+		for (i = 0; i < NUMMENUTYPES; i++)
+			if (fastcmp(p, MENUTYPES_LIST[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "menutype '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (!mathlib && fastncmp("A_",word,2)) {
+		char *caps;
+		// Try to get a Lua action first.
+		/// \todo Push a closure that sets superactions[] and superstack.
+		lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
+		// actions are stored in all uppercase.
+		caps = Z_StrDup(word);
+		strupr(caps);
+		lua_getfield(L, -1, caps);
+		Z_Free(caps);
+		if (!lua_isnil(L, -1))
+			return 1; // Success! :D That was easy.
+		// Welp, that failed.
+		lua_pop(L, 2); // pop nil and LREG_ACTIONS
+
+		// Hardcoded actions as callable Lua functions!
+		// Retrieving them from this metatable allows them to be case-insensitive!
+		for (i = 0; actionpointers[i].name; i++)
+			if (fasticmp(word, actionpointers[i].name)) {
+				// We push the actionf_t* itself as userdata!
+				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
+				return 1;
+			}
+		return 0;
+	}
+	else if (!mathlib && fastcmp("super",word))
+	{
+		if (!superstack)
+		{
+			lua_pushcfunction(L, lib_dummysuper);
+			return 1;
+		}
+		for (i = 0; actionpointers[i].name; i++)
+			if (fasticmp(superactions[superstack-1], actionpointers[i].name)) {
+				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
+				return 1;
+			}
+		return 0;
+	}
+
+	if (fastcmp(word, "BT_USE")) // Remove case when 2.3 nears release...
+	{
+		lua_pushinteger(L, (lua_Integer)BT_SPIN);
+		return 1;
+	}
+
+	for (i = 0; INT_CONST[i].n; i++)
+		if (fastcmp(word,INT_CONST[i].n)) {
+			lua_pushinteger(L, INT_CONST[i].v);
+			return 1;
+		}
+
+	if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
+
+	// DYNAMIC variables too!!
+	// Try not to add anything that would break netgames or timeattack replays here.
+	// You know, like consoleplayer, displayplayer, secondarydisplayplayer, or gametime.
+	return LUA_PushGlobals(L, word);
+}
+
+int LUA_EnumLib(lua_State *L)
+{
+	if (lua_gettop(L) == 0)
+		lua_pushboolean(L, 0);
+
+	// Set the global metatable
+	lua_createtable(L, 0, 1);
+	lua_pushvalue(L, 1); // boolean passed to LUA_EnumLib as first argument.
+	lua_pushcclosure(L, lib_getenum, 1);
+	lua_setfield(L, -2, "__index");
+	lua_setmetatable(L, LUA_GLOBALSINDEX);
+	return 0;
+}
+
+// getActionName(action) -> return action's string name
+static int lib_getActionName(lua_State *L)
+{
+	if (lua_isuserdata(L, 1)) // arg 1 is built-in action, expect action userdata
+	{
+		actionf_t *action = *((actionf_t **)luaL_checkudata(L, 1, META_ACTION));
+		const char *name = NULL;
+		if (!action)
+			return luaL_error(L, "not a valid action?");
+		name = LUA_GetActionName(action);
+		if (!name) // that can't be right?
+			return luaL_error(L, "no name string could be found for this action");
+		lua_pushstring(L, name);
+		return 1;
+	}
+	else if (lua_isfunction(L, 1)) // arg 1 is a function (either C or Lua)
+	{
+		lua_settop(L, 1); // set top of stack to 1 (removing any extra args, which there shouldn't be)
+		// get the name for this action, if possible.
+		lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
+		lua_pushnil(L);
+		// Lua stack at this point:
+		//  1   ...       -2              -1
+		// arg  ...   LREG_ACTIONS        nil
+		while (lua_next(L, -2))
+		{
+			// Lua stack at this point:
+			//  1   ...       -3              -2           -1
+			// arg  ...   LREG_ACTIONS    "A_ACTION"    function
+			if (lua_rawequal(L, -1, 1)) // is this the same as the arg?
+			{
+				// make sure the key (i.e. "A_ACTION") is a string first
+				// (note: we don't use lua_isstring because it also returns true for numbers)
+				if (lua_type(L, -2) == LUA_TSTRING)
+				{
+					lua_pushvalue(L, -2); // push "A_ACTION" string to top of stack
+					return 1;
+				}
+				lua_pop(L, 2); // pop the name and function
+				break; // probably should have succeeded but we didn't, so end the loop
+			}
+			lua_pop(L, 1);
+		}
+		lua_pop(L, 1); // pop LREG_ACTIONS
+		return 0; // return nothing (don't error)
+	}
+
+	return luaL_typerror(L, 1, "action userdata or Lua function");
+}
+
+
+
+int LUA_SOCLib(lua_State *L)
+{
+	lua_register(L,"freeslot",lib_freeslot);
+	lua_register(L,"getActionName",lib_getActionName);
+
+	luaL_newmetatable(L, META_ACTION);
+		lua_pushcfunction(L, action_call);
+		lua_setfield(L, -2, "__call");
+	lua_pop(L, 1);
+
+	return 0;
+}
+
+const char *LUA_GetActionName(void *action)
+{
+	actionf_t *act = (actionf_t *)action;
+	size_t z;
+	for (z = 0; actionpointers[z].name; z++)
+	{
+		if (actionpointers[z].action.acv == act->acv)
+			return actionpointers[z].name;
+	}
+	return NULL;
+}
+
+void LUA_SetActionByName(void *state, const char *actiontocompare)
+{
+	state_t *st = (state_t *)state;
+	size_t z;
+	for (z = 0; actionpointers[z].name; z++)
+	{
+		if (fasticmp(actiontocompare, actionpointers[z].name))
+		{
+			st->action = actionpointers[z].action;
+			st->action.acv = actionpointers[z].action.acv; // assign
+			st->action.acp1 = actionpointers[z].action.acp1;
+			return;
+		}
+	}
+}
+
+enum actionnum LUA_GetActionNumByName(const char *actiontocompare)
+{
+	size_t z;
+	for (z = 0; actionpointers[z].name; z++)
+		if (fasticmp(actiontocompare, actionpointers[z].name))
+			return z;
+	return z;
+}
diff --git a/src/deh_lua.h b/src/deh_lua.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd927b9fd51bb98f3d6674dd129d9df29726ae33
--- /dev/null
+++ b/src/deh_lua.h
@@ -0,0 +1,21 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  deh_lua.h
+/// \brief Lua SOC library
+
+#ifndef __DEH_LUA_H__
+#define __DEH_LUA_H__
+
+boolean LUA_SetLuaAction(void *state, const char *actiontocompare);
+const char *LUA_GetActionName(void *action);
+void LUA_SetActionByName(void *state, const char *actiontocompare);
+enum actionnum LUA_GetActionNumByName(const char *actiontocompare);
+
+#endif
diff --git a/src/deh_soc.c b/src/deh_soc.c
new file mode 100644
index 0000000000000000000000000000000000000000..5b12ea1b0b9b0339890b094516e0593e252d6556
--- /dev/null
+++ b/src/deh_soc.c
@@ -0,0 +1,4527 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  deh_soc.c
+/// \brief Load SOC file and change tables and text
+
+#include "doomdef.h"
+#include "d_main.h" // for srb2home
+#include "g_game.h"
+#include "sounds.h"
+#include "info.h"
+#include "d_think.h"
+#include "m_argv.h"
+#include "z_zone.h"
+#include "w_wad.h"
+#include "y_inter.h"
+#include "m_menu.h"
+#include "m_misc.h"
+#include "f_finale.h"
+#include "st_stuff.h"
+#include "i_system.h"
+#include "p_setup.h"
+#include "r_data.h"
+#include "r_textures.h"
+#include "r_draw.h"
+#include "r_picformats.h"
+#include "r_things.h" // R_Char2Frame
+#include "r_sky.h"
+#include "fastcmp.h"
+#include "lua_script.h" // Reluctantly included for LUA_EvalMath
+#include "d_clisrv.h"
+
+#ifdef HWRENDER
+#include "hardware/hw_light.h"
+#endif
+
+#include "m_cond.h"
+
+#include "dehacked.h"
+#include "deh_soc.h"
+#include "deh_lua.h" // included due to some LUA_SetLuaAction hack smh
+#include "deh_tables.h"
+
+// Loops through every constant and operation in word and performs its calculations, returning the final value.
+fixed_t get_number(const char *word)
+{
+	return LUA_EvalMath(word);
+
+	/*// DESPERATELY NEEDED: Order of operations support! :x
+	fixed_t i = find_const(&word);
+	INT32 o;
+	while(*word) {
+		o = operation_pad(&word);
+		if (o != -1)
+			i = OPERATIONS[o].v(i,find_const(&word));
+		else
+			break;
+	}
+	return i;*/
+}
+
+#define PARAMCHECK(n) do { if (!params[n]) { deh_warning("Too few parameters, need %d", n); return; }} while (0)
+
+/* ======================================================================== */
+// Load a dehacked file format
+/* ======================================================================== */
+/* a sample to see
+                   Thing 1 (Player)       {           // MT_PLAYER
+INT32 doomednum;     ID # = 3232              -1,             // doomednum
+INT32 spawnstate;    Initial frame = 32       "PLAY",         // spawnstate
+INT32 spawnhealth;   Hit points = 3232        100,            // spawnhealth
+INT32 seestate;      First moving frame = 32  "PLAY_RUN1",    // seestate
+INT32 seesound;      Alert sound = 32         sfx_None,       // seesound
+INT32 reactiontime;  Reaction time = 3232     0,              // reactiontime
+INT32 attacksound;   Attack sound = 32        sfx_None,       // attacksound
+INT32 painstate;     Injury frame = 32        "PLAY_PAIN",    // painstate
+INT32 painchance;    Pain chance = 3232       255,            // painchance
+INT32 painsound;     Pain sound = 32          sfx_plpain,     // painsound
+INT32 meleestate;    Close attack frame = 32  "NULL",         // meleestate
+INT32 missilestate;  Far attack frame = 32    "PLAY_ATK1",    // missilestate
+INT32 deathstate;    Death frame = 32         "PLAY_DIE1",    // deathstate
+INT32 xdeathstate;   Exploding frame = 32     "PLAY_XDIE1",   // xdeathstate
+INT32 deathsound;    Death sound = 32         sfx_pldeth,     // deathsound
+INT32 speed;         Speed = 3232             0,              // speed
+INT32 radius;        Width = 211812352        16*FRACUNIT,    // radius
+INT32 height;        Height = 211812352       56*FRACUNIT,    // height
+INT32 dispoffset;    DispOffset = 0           0,              // dispoffset
+INT32 mass;          Mass = 3232              100,            // mass
+INT32 damage;        Missile damage = 3232    0,              // damage
+INT32 activesound;   Action sound = 32        sfx_None,       // activesound
+INT32 flags;         Bits = 3232              MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH,
+INT32 raisestate;    Respawn frame = 32       S_NULL          // raisestate
+                                         }, */
+
+#ifdef HWRENDER
+static INT32 searchvalue(const char *s)
+{
+	while (s[0] != '=' && s[0])
+		s++;
+	if (s[0] == '=')
+		return atoi(&s[1]);
+	else
+	{
+		deh_warning("No value found");
+		return 0;
+	}
+}
+
+static float searchfvalue(const char *s)
+{
+	while (s[0] != '=' && s[0])
+		s++;
+	if (s[0] == '=')
+		return (float)atof(&s[1]);
+	else
+	{
+		deh_warning("No value found");
+		return 0;
+	}
+}
+#endif
+
+// These are for clearing all of various things
+void clear_conditionsets(void)
+{
+	UINT8 i;
+	for (i = 0; i < MAXCONDITIONSETS; ++i)
+		M_ClearConditionSet(i+1);
+}
+
+void clear_levels(void)
+{
+	INT16 i;
+
+	// This is potentially dangerous but if we're resetting these headers,
+	// we may as well try to save some memory, right?
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		if (!mapheaderinfo[i] || i == (tutorialmap-1))
+			continue;
+
+		// Custom map header info
+		// (no need to set num to 0, we're freeing the entire header shortly)
+		Z_Free(mapheaderinfo[i]->customopts);
+
+		P_DeleteFlickies(i);
+		P_DeleteGrades(i);
+
+		Z_Free(mapheaderinfo[i]);
+		mapheaderinfo[i] = NULL;
+	}
+
+	// Realloc the one for the current gamemap as a safeguard
+	P_AllocMapHeader(gamemap-1);
+}
+
+static boolean findFreeSlot(INT32 *num)
+{
+	// Send the character select entry to a free slot.
+	while (*num < MAXSKINS && (description[*num].used))
+		*num = *num+1;
+
+	// No more free slots. :(
+	if (*num >= MAXSKINS)
+		return false;
+
+	// Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
+	description[*num].picname[0] = '\0';
+	description[*num].nametag[0] = '\0';
+	description[*num].displayname[0] = '\0';
+	description[*num].oppositecolor = SKINCOLOR_NONE;
+	description[*num].tagtextcolor = SKINCOLOR_NONE;
+	description[*num].tagoutlinecolor = SKINCOLOR_NONE;
+
+	// Found one! ^_^
+	return (description[*num].used = true);
+}
+
+// Reads a player.
+// For modifying the character select screen
+void readPlayer(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	char *displayname = ZZ_Alloc(MAXLINELEN+1);
+	INT32 i;
+	boolean slotfound = false;
+
+	#define SLOTFOUND \
+		if (!slotfound && (slotfound = findFreeSlot(&num)) == false) \
+			goto done;
+
+	displayname[MAXLINELEN] = '\0';
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			for (i = 0; i < MAXLINELEN-3; i++)
+			{
+				char *tmp;
+				if (s[i] == '=')
+				{
+					tmp = &s[i+2];
+					strncpy(displayname, tmp, SKINNAMESIZE);
+					break;
+				}
+			}
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			if (fastcmp(word, "PLAYERTEXT"))
+			{
+				char *playertext = NULL;
+
+				SLOTFOUND
+
+				for (i = 0; i < MAXLINELEN-3; i++)
+				{
+					if (s[i] == '=')
+					{
+						playertext = &s[i+2];
+						break;
+					}
+				}
+				if (playertext)
+				{
+					strcpy(description[num].notes, playertext);
+					strcat(description[num].notes, myhashfgets(playertext, sizeof (description[num].notes), f));
+				}
+				else
+					strcpy(description[num].notes, "");
+
+				// For some reason, cutting the string did not work above. Most likely due to strcpy or strcat...
+				// It works down here, though.
+				{
+					INT32 numline = 0;
+					for (i = 0; (size_t)i < sizeof(description[num].notes)-1; i++)
+					{
+						if (numline < 20 && description[num].notes[i] == '\n')
+							numline++;
+
+						if (numline >= 20 || description[num].notes[i] == '\0' || description[num].notes[i] == '#')
+							break;
+					}
+				}
+				description[num].notes[strlen(description[num].notes)-1] = '\0';
+				description[num].notes[i] = '\0';
+				continue;
+			}
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+			i = atoi(word2);
+
+			if (fastcmp(word, "PICNAME"))
+			{
+				SLOTFOUND
+				strncpy(description[num].picname, word2, 8);
+			}
+			// new character select
+			else if (fastcmp(word, "DISPLAYNAME"))
+			{
+				SLOTFOUND
+				// replace '#' with line breaks
+				// (also remove any '\n')
+				{
+					char *cur = NULL;
+
+					// remove '\n'
+					cur = strchr(displayname, '\n');
+					if (cur)
+						*cur = '\0';
+
+					// turn '#' into '\n'
+					cur = strchr(displayname, '#');
+					while (cur)
+					{
+						*cur = '\n';
+						cur = strchr(cur, '#');
+					}
+				}
+				// copy final string
+				strncpy(description[num].displayname, displayname, SKINNAMESIZE);
+			}
+			else if (fastcmp(word, "OPPOSITECOLOR") || fastcmp(word, "OPPOSITECOLOUR"))
+			{
+				SLOTFOUND
+				description[num].oppositecolor = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "NAMETAG") || fastcmp(word, "TAGNAME"))
+			{
+				SLOTFOUND
+				strncpy(description[num].nametag, word2, 8);
+			}
+			else if (fastcmp(word, "TAGTEXTCOLOR") || fastcmp(word, "TAGTEXTCOLOUR"))
+			{
+				SLOTFOUND
+				description[num].tagtextcolor = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "TAGOUTLINECOLOR") || fastcmp(word, "TAGOUTLINECOLOUR"))
+			{
+				SLOTFOUND
+				description[num].tagoutlinecolor = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "STATUS"))
+			{
+				/*
+					You MAY disable previous entries if you so desire...
+					But try to enable something that's already enabled and you will be sent to a free slot.
+
+					Because of this, you are allowed to edit any previous entries you like, but only if you
+					signal that you are purposely doing so by disabling and then reenabling the slot.
+				*/
+				if (i && !slotfound && (slotfound = findFreeSlot(&num)) == false)
+					goto done;
+
+				description[num].used = (!!i);
+			}
+			else if (fastcmp(word, "SKINNAME"))
+			{
+				// Send to free slot.
+				SLOTFOUND
+				strlcpy(description[num].skinname, word2, sizeof description[num].skinname);
+				strlwr(description[num].skinname);
+			}
+			else
+				deh_warning("readPlayer %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+	#undef SLOTFOUND
+done:
+	Z_Free(displayname);
+	Z_Free(s);
+}
+
+// TODO: Figure out how to do undolines for this....
+// TODO: Warnings for running out of freeslots
+void readfreeslots(MYFILE *f)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word,*type;
+	char *tmp;
+	int i;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			type = strtok(s, "_");
+			if (type)
+				strupr(type);
+			else
+				break;
+
+			word = strtok(NULL, "\n");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			// TODO: Check for existing freeslot mobjs/states/etc. and make errors.
+			// TODO: Out-of-slots warnings/errors.
+			// TODO: Name too long (truncated) warnings.
+			if (fastcmp(type, "SFX"))
+				S_AddSoundFx(word, false, 0, false);
+			else if (fastcmp(type, "SPR"))
+			{
+				for (i = SPR_FIRSTFREESLOT; i <= SPR_LASTFREESLOT; i++)
+				{
+					if (used_spr[(i-SPR_FIRSTFREESLOT)/8] & (1<<(i%8)))
+					{
+						if (!sprnames[i][4] && memcmp(sprnames[i],word,4)==0)
+							sprnames[i][4] = (char)f->wad;
+						continue; // Already allocated, next.
+					}
+					// Found a free slot!
+					strncpy(sprnames[i],word,4);
+					//sprnames[i][4] = 0;
+					used_spr[(i-SPR_FIRSTFREESLOT)/8] |= 1<<(i%8); // Okay, this sprite slot has been named now.
+					break;
+				}
+			}
+			else if (fastcmp(type, "S"))
+			{
+				for (i = 0; i < NUMSTATEFREESLOTS; i++)
+					if (!FREE_STATES[i]) {
+						FREE_STATES[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+						strcpy(FREE_STATES[i],word);
+						break;
+					}
+			}
+			else if (fastcmp(type, "MT"))
+			{
+				for (i = 0; i < NUMMOBJFREESLOTS; i++)
+					if (!FREE_MOBJS[i]) {
+						FREE_MOBJS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+						strcpy(FREE_MOBJS[i],word);
+						break;
+					}
+			}
+			else if (fastcmp(type, "SKINCOLOR"))
+			{
+				for (i = 0; i < NUMCOLORFREESLOTS; i++)
+					if (!FREE_SKINCOLORS[i]) {
+						FREE_SKINCOLORS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
+						strcpy(FREE_SKINCOLORS[i],word);
+						M_AddMenuColor(numskincolors++);
+						break;
+					}
+			}
+			else if (fastcmp(type, "SPR2"))
+			{
+				// Search if we already have an SPR2 by that name...
+				for (i = SPR2_FIRSTFREESLOT; i < (int)free_spr2; i++)
+					if (memcmp(spr2names[i],word,4) == 0)
+						break;
+				// We found it? (Two mods using the same SPR2 name?) Then don't allocate another one.
+				if (i < (int)free_spr2)
+					continue;
+				// Copy in the spr2 name and increment free_spr2.
+				if (free_spr2 < NUMPLAYERSPRITES) {
+					strncpy(spr2names[free_spr2],word,4);
+					spr2defaults[free_spr2] = 0;
+					spr2names[free_spr2++][4] = 0;
+				} else
+					deh_warning("Ran out of free SPR2 slots!\n");
+			}
+			else if (fastcmp(type, "TOL"))
+			{
+				// Search if we already have a typeoflevel by that name...
+				for (i = 0; TYPEOFLEVEL[i].name; i++)
+					if (fastcmp(word, TYPEOFLEVEL[i].name))
+						break;
+
+				// We found it? Then don't allocate another one.
+				if (TYPEOFLEVEL[i].name)
+					continue;
+
+				// We don't, so freeslot it.
+				if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags.
+					deh_warning("Ran out of free typeoflevel slots!\n");
+				else
+				{
+					G_AddTOL(lastcustomtol, word);
+					lastcustomtol <<= 1;
+				}
+			}
+			else
+				deh_warning("Freeslots: unknown enum class '%s' for '%s_%s'", type, type, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readthing(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word, *word2;
+	char *tmp;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+
+			if (fastcmp(word, "MAPTHINGNUM") || fastcmp(word, "DOOMEDNUM"))
+			{
+				mobjinfo[num].doomednum = (INT32)atoi(word2);
+			}
+			else if (fastcmp(word, "SPAWNSTATE"))
+			{
+				mobjinfo[num].spawnstate = get_number(word2);
+			}
+			else if (fastcmp(word, "SPAWNHEALTH"))
+			{
+				mobjinfo[num].spawnhealth = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word, "SEESTATE"))
+			{
+				mobjinfo[num].seestate = get_number(word2);
+			}
+			else if (fastcmp(word, "SEESOUND"))
+			{
+				mobjinfo[num].seesound = get_number(word2);
+			}
+			else if (fastcmp(word, "REACTIONTIME"))
+			{
+				mobjinfo[num].reactiontime = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word, "ATTACKSOUND"))
+			{
+				mobjinfo[num].attacksound = get_number(word2);
+			}
+			else if (fastcmp(word, "PAINSTATE"))
+			{
+				mobjinfo[num].painstate = get_number(word2);
+			}
+			else if (fastcmp(word, "PAINCHANCE"))
+			{
+				mobjinfo[num].painchance = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word, "PAINSOUND"))
+			{
+				mobjinfo[num].painsound = get_number(word2);
+			}
+			else if (fastcmp(word, "MELEESTATE"))
+			{
+				mobjinfo[num].meleestate = get_number(word2);
+			}
+			else if (fastcmp(word, "MISSILESTATE"))
+			{
+				mobjinfo[num].missilestate = get_number(word2);
+			}
+			else if (fastcmp(word, "DEATHSTATE"))
+			{
+				mobjinfo[num].deathstate = get_number(word2);
+			}
+			else if (fastcmp(word, "DEATHSOUND"))
+			{
+				mobjinfo[num].deathsound = get_number(word2);
+			}
+			else if (fastcmp(word, "XDEATHSTATE"))
+			{
+				mobjinfo[num].xdeathstate = get_number(word2);
+			}
+			else if (fastcmp(word, "SPEED"))
+			{
+				mobjinfo[num].speed = get_number(word2);
+			}
+			else if (fastcmp(word, "RADIUS"))
+			{
+				mobjinfo[num].radius = get_number(word2);
+			}
+			else if (fastcmp(word, "HEIGHT"))
+			{
+				mobjinfo[num].height = get_number(word2);
+			}
+			else if (fastcmp(word, "DISPOFFSET"))
+			{
+				mobjinfo[num].dispoffset = get_number(word2);
+			}
+			else if (fastcmp(word, "MASS"))
+			{
+				mobjinfo[num].mass = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word, "DAMAGE"))
+			{
+				mobjinfo[num].damage = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word, "ACTIVESOUND"))
+			{
+				mobjinfo[num].activesound = get_number(word2);
+			}
+			else if (fastcmp(word, "FLAGS"))
+			{
+				mobjinfo[num].flags = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word, "RAISESTATE"))
+			{
+				mobjinfo[num].raisestate = get_number(word2);
+			}
+			else
+				deh_warning("Thing %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readskincolor(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+
+	Color_cons_t[num].value = num;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+
+			if (fastcmp(word, "NAME"))
+			{
+				size_t namesize = sizeof(skincolors[num].name);
+				char truncword[namesize];
+				UINT16 dupecheck;
+
+				deh_strlcpy(truncword, word2, namesize, va("Skincolor %d: name", num)); // truncate here to check for dupes
+				dupecheck = R_GetColorByName(truncword);
+				if (truncword[0] != '\0' && (!stricmp(truncword, skincolors[SKINCOLOR_NONE].name) || (dupecheck && dupecheck != num)))
+				{
+					size_t lastchar = strlen(truncword);
+					char oldword[lastchar+1];
+					char dupenum = '1';
+
+					strlcpy(oldword, truncword, lastchar+1);
+					lastchar--;
+					if (lastchar == namesize-2) // exactly max length, replace last character with 0
+						truncword[lastchar] = '0';
+					else // append 0
+					{
+						strcat(truncword, "0");
+						lastchar++;
+					}
+
+					while (R_GetColorByName(truncword))
+					{
+						truncword[lastchar] = dupenum;
+						if (dupenum == '9')
+							dupenum = 'A';
+						else if (dupenum == 'Z') // give up :?
+							break;
+						else
+							dupenum++;
+					}
+
+					deh_warning("Skincolor %d: name %s is a duplicate of another skincolor's name - renamed to %s", num, oldword, truncword);
+				}
+
+				strlcpy(skincolors[num].name, truncword, namesize); // already truncated
+			}
+			else if (fastcmp(word, "RAMP"))
+			{
+				UINT8 i;
+				tmp = strtok(word2,",");
+				for (i = 0; i < COLORRAMPSIZE; i++) {
+					skincolors[num].ramp[i] = (UINT8)get_number(tmp);
+					if ((tmp = strtok(NULL,",")) == NULL)
+						break;
+				}
+				skincolor_modified[num] = true;
+			}
+			else if (fastcmp(word, "INVCOLOR"))
+			{
+				UINT16 v = (UINT16)get_number(word2);
+				if (v < numskincolors)
+					skincolors[num].invcolor = v;
+				else
+					skincolors[num].invcolor = SKINCOLOR_GREEN;
+			}
+			else if (fastcmp(word, "INVSHADE"))
+			{
+				skincolors[num].invshade = get_number(word2)%COLORRAMPSIZE;
+			}
+			else if (fastcmp(word, "CHATCOLOR"))
+			{
+				skincolors[num].chatcolor = get_number(word2);
+			}
+			else if (fastcmp(word, "ACCESSIBLE"))
+			{
+				if (num > FIRSTSUPERCOLOR)
+					skincolors[num].accessible = (boolean)(atoi(word2) || word2[0] == 'T' || word2[0] == 'Y');
+			}
+			else
+				deh_warning("Skincolor %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+#ifdef HWRENDER
+void readlight(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *tmp;
+	INT32 value;
+	float fvalue;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			fvalue = searchfvalue(s);
+			value = searchvalue(s);
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			if (fastcmp(word, "TYPE"))
+			{
+				lspr[num].type = (UINT16)value;
+			}
+			else if (fastcmp(word, "OFFSETX"))
+			{
+				lspr[num].light_xoffset = fvalue;
+			}
+			else if (fastcmp(word, "OFFSETY"))
+			{
+				lspr[num].light_yoffset = fvalue;
+			}
+			else if (fastcmp(word, "CORONACOLOR"))
+			{
+				lspr[num].corona_color = value;
+			}
+			else if (fastcmp(word, "CORONARADIUS"))
+			{
+				lspr[num].corona_radius = fvalue;
+			}
+			else if (fastcmp(word, "DYNAMICCOLOR"))
+			{
+				lspr[num].dynamic_color = value;
+			}
+			else if (fastcmp(word, "DYNAMICRADIUS"))
+			{
+				lspr[num].dynamic_radius = fvalue;
+
+				/// \note Update the sqrradius! unnecessary?
+				lspr[num].dynamic_sqrradius = fvalue * fvalue;
+			}
+			else
+				deh_warning("Light %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+#endif // HWRENDER
+
+static void readspriteframe(MYFILE *f, spriteinfo_t *sprinfo, UINT8 frame)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word, *word2;
+	char *tmp;
+	INT32 value;
+	char *lastline;
+
+	do
+	{
+		lastline = f->curpos;
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Set / reset word
+			word = s;
+			while ((*word == '\t') || (*word == ' '))
+				word++;
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+			{
+				*(tmp-1) = '\0';
+				// Now get the part after
+				word2 = tmp += 2;
+			}
+			else
+			{
+				// Get the part before the " "
+				tmp = strchr(s, ' ');
+				if (tmp)
+				{
+					*tmp = '\0';
+					// Now get the part after
+					tmp++;
+					word2 = tmp;
+				}
+				else
+					break;
+			}
+			strupr(word);
+			value = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "XPIVOT"))
+				sprinfo->pivot[frame].x = value;
+			else if (fastcmp(word, "YPIVOT"))
+				sprinfo->pivot[frame].y = value;
+			else if (fastcmp(word, "ROTAXIS"))
+				sprinfo->pivot[frame].rotaxis = value;
+			else
+			{
+				f->curpos = lastline;
+				break;
+			}
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+	Z_Free(s);
+}
+
+void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word, *word2;
+	char *tmp;
+#ifdef HWRENDER
+	INT32 value;
+#endif
+	char *lastline;
+	INT32 skinnumbers[MAXSKINS];
+	INT32 foundskins = 0;
+
+	// allocate a spriteinfo
+	spriteinfo_t *info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL);
+	info->available = true;
+
+	do
+	{
+		lastline = f->curpos;
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Set / reset word
+			word = s;
+			while ((*word == '\t') || (*word == ' '))
+				word++;
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+			{
+				*(tmp-1) = '\0';
+				// Now get the part after
+				word2 = tmp += 2;
+			}
+			else
+			{
+				// Get the part before the " "
+				tmp = strchr(s, ' ');
+				if (tmp)
+				{
+					*tmp = '\0';
+					// Now get the part after
+					tmp++;
+					word2 = tmp;
+				}
+				else
+					break;
+			}
+			strupr(word);
+#ifdef HWRENDER
+			value = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "LIGHTTYPE"))
+			{
+				if (sprite2)
+					deh_warning("Sprite2 %s: invalid word '%s'", spr2names[num], word);
+				else
+				{
+					INT32 oldvar;
+					for (oldvar = 0; t_lspr[num] != &lspr[oldvar]; oldvar++)
+						;
+					t_lspr[num] = &lspr[value];
+				}
+			}
+			else
+#endif
+			if (fastcmp(word, "SKIN"))
+			{
+				INT32 skinnum = -1;
+				if (!sprite2)
+				{
+					deh_warning("Sprite %s: %s keyword found outside of SPRITE2INFO block, ignoring", spr2names[num], word);
+					continue;
+				}
+
+				// make lowercase
+				strlwr(word2);
+				skinnum = R_SkinAvailable(word2);
+				if (skinnum == -1)
+				{
+					deh_warning("Sprite2 %s: unknown skin %s", spr2names[num], word2);
+					break;
+				}
+
+				skinnumbers[foundskins] = skinnum;
+				foundskins++;
+			}
+			else if (fastcmp(word, "DEFAULT"))
+			{
+				if (!sprite2)
+				{
+					deh_warning("Sprite %s: %s keyword found outside of SPRITE2INFO block, ignoring", spr2names[num], word);
+					continue;
+				}
+				if (num < (INT32)free_spr2 && num >= (INT32)SPR2_FIRSTFREESLOT)
+					spr2defaults[num] = get_number(word2);
+				else
+				{
+					deh_warning("Sprite2 %s: out of range (%d - %d), ignoring", spr2names[num], SPR2_FIRSTFREESLOT, free_spr2-1);
+					continue;
+				}
+			}
+			else if (fastcmp(word, "FRAME"))
+			{
+				UINT8 frame = R_Char2Frame(word2[0]);
+				// frame number too high
+				if (frame >= 64)
+				{
+					if (sprite2)
+						deh_warning("Sprite2 %s: invalid frame %s", spr2names[num], word2);
+					else
+						deh_warning("Sprite %s: invalid frame %s", sprnames[num], word2);
+					break;
+				}
+
+				// read sprite frame and store it in the spriteinfo_t struct
+				readspriteframe(f, info, frame);
+				if (sprite2)
+				{
+					INT32 i;
+					if (!foundskins)
+					{
+						deh_warning("Sprite2 %s: no skins specified", spr2names[num]);
+						break;
+					}
+					for (i = 0; i < foundskins; i++)
+					{
+						size_t skinnum = skinnumbers[i];
+						skin_t *skin = &skins[skinnum];
+						spriteinfo_t *sprinfo = skin->sprinfo;
+						M_Memcpy(&sprinfo[num], info, sizeof(spriteinfo_t));
+					}
+				}
+				else
+					M_Memcpy(&spriteinfo[num], info, sizeof(spriteinfo_t));
+			}
+			else
+			{
+				//deh_warning("Sprite %s: unknown word '%s'", sprnames[num], word);
+				f->curpos = lastline;
+				break;
+			}
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+	Z_Free(info);
+}
+
+void readsprite2(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word, *word2;
+	char *tmp;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+
+			if (fastcmp(word, "DEFAULT"))
+				spr2defaults[num] = get_number(word2);
+			else
+				deh_warning("Sprite2 %s: unknown word '%s'", spr2names[num], word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+// copypasted from readPlayer :]
+void readgametype(MYFILE *f, char *gtname)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2, *word2lwr = NULL;
+	char *tmp;
+	INT32 i, j;
+
+	INT16 newgtidx = 0;
+	UINT32 newgtrules = 0;
+	UINT32 newgttol = 0;
+	INT32 newgtpointlimit = 0;
+	INT32 newgttimelimit = 0;
+	UINT8 newgtleftcolor = 0;
+	UINT8 newgtrightcolor = 0;
+	INT16 newgtrankingstype = -1;
+	int newgtinttype = 0;
+	char gtdescription[441];
+	char gtconst[MAXLINELEN];
+
+	// Empty strings.
+	gtdescription[0] = '\0';
+	gtconst[0] = '\0';
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			if (fastcmp(word, "DESCRIPTION"))
+			{
+				char *descr = NULL;
+
+				for (i = 0; i < MAXLINELEN-3; i++)
+				{
+					if (s[i] == '=')
+					{
+						descr = &s[i+2];
+						break;
+					}
+				}
+				if (descr)
+				{
+					strcpy(gtdescription, descr);
+					strcat(gtdescription, myhashfgets(descr, sizeof (gtdescription), f));
+				}
+				else
+					strcpy(gtdescription, "");
+
+				// For some reason, cutting the string did not work above. Most likely due to strcpy or strcat...
+				// It works down here, though.
+				{
+					INT32 numline = 0;
+					for (i = 0; (size_t)i < sizeof(gtdescription)-1; i++)
+					{
+						if (numline < 20 && gtdescription[i] == '\n')
+							numline++;
+
+						if (numline >= 20 || gtdescription[i] == '\0' || gtdescription[i] == '#')
+							break;
+					}
+				}
+				gtdescription[strlen(gtdescription)-1] = '\0';
+				gtdescription[i] = '\0';
+				continue;
+			}
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+			{
+				if (!word2lwr)
+					word2lwr = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+				strcpy(word2lwr, word2);
+				strupr(word2);
+			}
+			else
+				break;
+
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+			i = atoi(word2);
+
+			// Game type rules
+			if (fastcmp(word, "RULES"))
+			{
+				// GTR_
+				newgtrules = (UINT32)get_number(word2);
+			}
+			// Identifier
+			else if (fastcmp(word, "IDENTIFIER"))
+			{
+				// GT_
+				strncpy(gtconst, word2, MAXLINELEN);
+			}
+			// Point and time limits
+			else if (fastcmp(word, "DEFAULTPOINTLIMIT"))
+				newgtpointlimit = (INT32)i;
+			else if (fastcmp(word, "DEFAULTTIMELIMIT"))
+				newgttimelimit = (INT32)i;
+			// Level platter
+			else if (fastcmp(word, "HEADERCOLOR") || fastcmp(word, "HEADERCOLOUR"))
+				newgtleftcolor = newgtrightcolor = (UINT8)get_number(word2);
+			else if (fastcmp(word, "HEADERLEFTCOLOR") || fastcmp(word, "HEADERLEFTCOLOUR"))
+				newgtleftcolor = (UINT8)get_number(word2);
+			else if (fastcmp(word, "HEADERRIGHTCOLOR") || fastcmp(word, "HEADERRIGHTCOLOUR"))
+				newgtrightcolor = (UINT8)get_number(word2);
+			// Rankings type
+			else if (fastcmp(word, "RANKINGTYPE"))
+			{
+				// Case insensitive
+				newgtrankingstype = (int)get_number(word2);
+			}
+			// Intermission type
+			else if (fastcmp(word, "INTERMISSIONTYPE"))
+			{
+				// Case sensitive
+				newgtinttype = (int)get_number(word2lwr);
+			}
+			// Type of level
+			else if (fastcmp(word, "TYPEOFLEVEL"))
+			{
+				if (i) // it's just a number
+					newgttol = (UINT32)i;
+				else
+				{
+					UINT32 tol = 0;
+					tmp = strtok(word2,",");
+					do {
+						for (i = 0; TYPEOFLEVEL[i].name; i++)
+							if (fasticmp(tmp, TYPEOFLEVEL[i].name))
+								break;
+						if (!TYPEOFLEVEL[i].name)
+							deh_warning("readgametype %s: unknown typeoflevel flag %s\n", gtname, tmp);
+						tol |= TYPEOFLEVEL[i].flag;
+					} while((tmp = strtok(NULL,",")) != NULL);
+					newgttol = tol;
+				}
+			}
+			// The SOC probably provided gametype rules as words,
+			// instead of using the RULES keyword.
+			// Like for example "NOSPECTATORSPAWN = TRUE".
+			// This is completely valid, and looks better anyway.
+			else
+			{
+				UINT32 wordgt = 0;
+				for (j = 0; GAMETYPERULE_LIST[j]; j++)
+					if (fastcmp(word, GAMETYPERULE_LIST[j])) {
+						wordgt |= (1<<j);
+						if (i || word2[0] == 'T' || word2[0] == 'Y')
+							newgtrules |= wordgt;
+						break;
+					}
+				if (!wordgt)
+					deh_warning("readgametype %s: unknown word '%s'", gtname, word);
+			}
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	// Free strings.
+	Z_Free(s);
+	if (word2lwr)
+		Z_Free(word2lwr);
+
+	// Ran out of gametype slots
+	if (gametypecount == NUMGAMETYPEFREESLOTS)
+	{
+		CONS_Alert(CONS_WARNING, "Ran out of free gametype slots!\n");
+		return;
+	}
+
+	// Add the new gametype
+	newgtidx = G_AddGametype(newgtrules);
+	G_AddGametypeTOL(newgtidx, newgttol);
+	G_SetGametypeDescription(newgtidx, gtdescription, newgtleftcolor, newgtrightcolor);
+
+	// Not covered by G_AddGametype alone.
+	if (newgtrankingstype == -1)
+		newgtrankingstype = newgtidx;
+	gametyperankings[newgtidx] = newgtrankingstype;
+	intermissiontypes[newgtidx] = newgtinttype;
+	pointlimits[newgtidx] = newgtpointlimit;
+	timelimits[newgtidx] = newgttimelimit;
+
+	// Write the new gametype name.
+	Gametype_Names[newgtidx] = Z_StrDup((const char *)gtname);
+
+	// Write the constant name.
+	if (gtconst[0] == '\0')
+		strncpy(gtconst, gtname, MAXLINELEN);
+	G_AddGametypeConstant(newgtidx, (const char *)gtconst);
+
+	// Update gametype_cons_t accordingly.
+	G_UpdateGametypeSelections();
+
+	CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]);
+}
+
+void readlevelheader(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	//char *word3; // Non-uppercase version of word2
+	char *tmp;
+	INT32 i;
+
+	// Reset all previous map header information
+	P_AllocMapHeader((INT16)(num-1));
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Set / reset word, because some things (Lua.) move it
+			word = s;
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+			i = atoi(word2); // used for numerical settings
+
+
+			if (fastcmp(word, "LEVELNAME"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
+					sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
+				strlcpy(mapheaderinfo[num-1]->selectheading, word2, sizeof(mapheaderinfo[num-1]->selectheading)); // not deh_ so only complains once
+				continue;
+			}
+			// CHEAP HACK: move this over here for lowercase subtitles
+			if (fastcmp(word, "SUBTITLE"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->subttl, word2,
+					sizeof(mapheaderinfo[num-1]->subttl), va("Level header %d: subtitle", num));
+				continue;
+			}
+
+			// Lua custom options also go above, contents may be case sensitive.
+			if (fastncmp(word, "LUA.", 4))
+			{
+				UINT8 j;
+				customoption_t *modoption;
+
+				// Note: we actualy strlwr word here, so things are made a little easier for Lua
+				strlwr(word);
+				word += 4; // move past "lua."
+
+				// ... and do a simple name sanity check; the name must start with a letter
+				if (*word < 'a' || *word > 'z')
+				{
+					deh_warning("Level header %d: invalid custom option name \"%s\"", num, word);
+					continue;
+				}
+
+				// Sanity limit of 128 params
+				if (mapheaderinfo[num-1]->numCustomOptions == 128)
+				{
+					deh_warning("Level header %d: too many custom parameters", num);
+					continue;
+				}
+				j = mapheaderinfo[num-1]->numCustomOptions++;
+
+				mapheaderinfo[num-1]->customopts =
+					Z_Realloc(mapheaderinfo[num-1]->customopts,
+						sizeof(customoption_t) * mapheaderinfo[num-1]->numCustomOptions, PU_STATIC, NULL);
+
+				// Newly allocated
+				modoption = &mapheaderinfo[num-1]->customopts[j];
+
+				strncpy(modoption->option, word,  31);
+				modoption->option[31] = '\0';
+				strncpy(modoption->value,  word2, 255);
+				modoption->value[255] = '\0';
+				continue;
+			}
+
+			// Now go to uppercase
+			strupr(word2);
+
+			// List of flickies that are be freed in this map
+			if (fastcmp(word, "FLICKYLIST") || fastcmp(word, "ANIMALLIST"))
+			{
+				if (fastcmp(word2, "NONE"))
+					P_DeleteFlickies(num-1);
+				else if (fastcmp(word2, "DEMO"))
+					P_SetDemoFlickies(num-1);
+				else if (fastcmp(word2, "ALL"))
+				{
+					mobjtype_t tmpflickies[MAXFLICKIES];
+
+					for (mapheaderinfo[num-1]->numFlickies = 0;
+					((mapheaderinfo[num-1]->numFlickies < MAXFLICKIES) && FLICKYTYPES[mapheaderinfo[num-1]->numFlickies].type);
+					mapheaderinfo[num-1]->numFlickies++)
+						tmpflickies[mapheaderinfo[num-1]->numFlickies] = FLICKYTYPES[mapheaderinfo[num-1]->numFlickies].type;
+
+					if (mapheaderinfo[num-1]->numFlickies) // just in case...
+					{
+						size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num-1]->numFlickies;
+						mapheaderinfo[num-1]->flickies = Z_Realloc(mapheaderinfo[num-1]->flickies, newsize, PU_STATIC, NULL);
+						M_Memcpy(mapheaderinfo[num-1]->flickies, tmpflickies, newsize);
+					}
+				}
+				else
+				{
+					mobjtype_t tmpflickies[MAXFLICKIES];
+					mapheaderinfo[num-1]->numFlickies = 0;
+					tmp = strtok(word2,",");
+					// get up to the first MAXFLICKIES flickies
+					do {
+						if (mapheaderinfo[num-1]->numFlickies == MAXFLICKIES) // never going to get above that number
+						{
+							deh_warning("Level header %d: too many flickies\n", num);
+							break;
+						}
+
+						if (fastncmp(tmp, "MT_", 3)) // support for specified mobjtypes...
+						{
+							i = get_mobjtype(tmp);
+							if (!i)
+							{
+								//deh_warning("Level header %d: unknown flicky mobj type %s\n", num, tmp); -- no need for this line as get_mobjtype complains too
+								continue;
+							}
+							tmpflickies[mapheaderinfo[num-1]->numFlickies] = i;
+						}
+						else // ...or a quick, limited selection of default flickies!
+						{
+							for (i = 0; FLICKYTYPES[i].name; i++)
+								if (fastcmp(tmp, FLICKYTYPES[i].name))
+									break;
+
+							if (!FLICKYTYPES[i].name)
+							{
+								deh_warning("Level header %d: unknown flicky selection %s\n", num, tmp);
+								continue;
+							}
+							tmpflickies[mapheaderinfo[num-1]->numFlickies] = FLICKYTYPES[i].type;
+						}
+						mapheaderinfo[num-1]->numFlickies++;
+					} while ((tmp = strtok(NULL,",")) != NULL);
+
+					if (mapheaderinfo[num-1]->numFlickies)
+					{
+						size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num-1]->numFlickies;
+						mapheaderinfo[num-1]->flickies = Z_Realloc(mapheaderinfo[num-1]->flickies, newsize, PU_STATIC, NULL);
+						// now we add them to the list!
+						M_Memcpy(mapheaderinfo[num-1]->flickies, tmpflickies, newsize);
+					}
+					else
+						deh_warning("Level header %d: no valid flicky types found\n", num);
+				}
+			}
+
+			// NiGHTS grades
+			else if (fastncmp(word, "GRADES", 6))
+			{
+				UINT8 mare = (UINT8)atoi(word + 6);
+
+				if (mare <= 0 || mare > 8)
+				{
+					deh_warning("Level header %d: unknown word '%s'", num, word);
+					continue;
+				}
+
+				P_AddGradesForMare((INT16)(num-1), mare-1, word2);
+			}
+
+			// Strings that can be truncated
+			else if (fastcmp(word, "SELECTHEADING"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->selectheading, word2,
+					sizeof(mapheaderinfo[num-1]->selectheading), va("Level header %d: selectheading", num));
+			}
+			else if (fastcmp(word, "SCRIPTNAME"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->scriptname, word2,
+					sizeof(mapheaderinfo[num-1]->scriptname), va("Level header %d: scriptname", num));
+			}
+			else if (fastcmp(word, "RUNSOC"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->runsoc, word2,
+					sizeof(mapheaderinfo[num-1]->runsoc), va("Level header %d: runsoc", num));
+			}
+			else if (fastcmp(word, "ACT"))
+			{
+				if (i >= 0 && i <= 99) // 0 for no act number
+					mapheaderinfo[num-1]->actnum = (UINT8)i;
+				else
+					deh_warning("Level header %d: invalid act number %d", num, i);
+			}
+			else if (fastcmp(word, "NEXTLEVEL"))
+			{
+				if      (fastcmp(word2, "TITLE"))      i = 1100;
+				else if (fastcmp(word2, "EVALUATION")) i = 1101;
+				else if (fastcmp(word2, "CREDITS"))    i = 1102;
+				else if (fastcmp(word2, "ENDING"))     i = 1103;
+				else
+				// Support using the actual map name,
+				// i.e., Nextlevel = AB, Nextlevel = FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
+					i = M_MapNumber(word2[0], word2[1]);
+
+				mapheaderinfo[num-1]->nextlevel = (INT16)i;
+			}
+			else if (fastcmp(word, "MARATHONNEXT"))
+			{
+				if      (fastcmp(word2, "TITLE"))      i = 1100;
+				else if (fastcmp(word2, "EVALUATION")) i = 1101;
+				else if (fastcmp(word2, "CREDITS"))    i = 1102;
+				else if (fastcmp(word2, "ENDING"))     i = 1103;
+				else
+				// Support using the actual map name,
+				// i.e., MarathonNext = AB, MarathonNext = FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
+					i = M_MapNumber(word2[0], word2[1]);
+
+				mapheaderinfo[num-1]->marathonnext = (INT16)i;
+			}
+			else if (fastcmp(word, "TYPEOFLEVEL"))
+			{
+				if (i) // it's just a number
+					mapheaderinfo[num-1]->typeoflevel = (UINT32)i;
+				else
+				{
+					UINT32 tol = 0;
+					tmp = strtok(word2,",");
+					do {
+						for (i = 0; TYPEOFLEVEL[i].name; i++)
+							if (fastcmp(tmp, TYPEOFLEVEL[i].name))
+								break;
+						if (!TYPEOFLEVEL[i].name)
+							deh_warning("Level header %d: unknown typeoflevel flag %s\n", num, tmp);
+						tol |= TYPEOFLEVEL[i].flag;
+					} while((tmp = strtok(NULL,",")) != NULL);
+					mapheaderinfo[num-1]->typeoflevel = tol;
+				}
+			}
+			else if (fastcmp(word, "KEYWORDS"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->keywords, word2,
+						sizeof(mapheaderinfo[num-1]->keywords), va("Level header %d: keywords", num));
+			}
+			else if (fastcmp(word, "MUSIC"))
+			{
+				if (fastcmp(word2, "NONE"))
+					mapheaderinfo[num-1]->musname[0] = 0; // becomes empty string
+				else
+				{
+					deh_strlcpy(mapheaderinfo[num-1]->musname, word2,
+						sizeof(mapheaderinfo[num-1]->musname), va("Level header %d: music", num));
+				}
+			}
+#ifdef MUSICSLOT_COMPATIBILITY
+			else if (fastcmp(word, "MUSICSLOT"))
+			{
+				i = get_mus(word2, true);
+				if (i && i <= 1035)
+					snprintf(mapheaderinfo[num-1]->musname, 7, "%sM", G_BuildMapName(i));
+				else if (i && i <= 1050)
+					strncpy(mapheaderinfo[num-1]->musname, compat_special_music_slots[i - 1036], 7);
+				else
+					mapheaderinfo[num-1]->musname[0] = 0; // becomes empty string
+				mapheaderinfo[num-1]->musname[6] = 0;
+			}
+#endif
+			else if (fastcmp(word, "MUSICTRACK"))
+				mapheaderinfo[num-1]->mustrack = ((UINT16)i - 1);
+			else if (fastcmp(word, "MUSICPOS"))
+				mapheaderinfo[num-1]->muspos = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICINTERFADEOUT"))
+				mapheaderinfo[num-1]->musinterfadeout = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICINTER"))
+				deh_strlcpy(mapheaderinfo[num-1]->musintername, word2,
+					sizeof(mapheaderinfo[num-1]->musintername), va("Level header %d: intermission music", num));
+			else if (fastcmp(word, "MUSICPOSTBOSS"))
+				deh_strlcpy(mapheaderinfo[num-1]->muspostbossname, word2,
+					sizeof(mapheaderinfo[num-1]->muspostbossname), va("Level header %d: post-boss music", num));
+			else if (fastcmp(word, "MUSICPOSTBOSSTRACK"))
+				mapheaderinfo[num-1]->muspostbosstrack = ((UINT16)i - 1);
+			else if (fastcmp(word, "MUSICPOSTBOSSPOS"))
+				mapheaderinfo[num-1]->muspostbosspos = (UINT32)get_number(word2);
+			else if (fastcmp(word, "MUSICPOSTBOSSFADEIN"))
+				mapheaderinfo[num-1]->muspostbossfadein = (UINT32)get_number(word2);
+			else if (fastcmp(word, "FORCERESETMUSIC"))
+			{
+				// This is a weird one because "FALSE"/"NO" could either apply to "leave to default preference" (cv_resetmusic)
+				// or "force off". Let's assume it means "force off", and let an unspecified value mean "default preference"
+				if      (fastcmp(word2, "OFF") || word2[0] == 'F' || word2[0] == 'N')  i = 0;
+				else if (fastcmp(word2, "ON") || word2[0] == 'T' || word2[0] == 'Y')   i = 1;
+				else i = -1; // (fastcmp(word2, "DEFAULT"))
+
+				if (i >= -1 && i <= 1) // -1 to force off, 1 to force on, 0 to honor default.
+					// This behavior can be disabled with cv_resetmusicbyheader
+					mapheaderinfo[num-1]->musforcereset = (SINT8)i;
+				else
+					deh_warning("Level header %d: invalid forceresetmusic option %d", num, i);
+			}
+			else if (fastcmp(word, "FORCECHARACTER"))
+			{
+				strlcpy(mapheaderinfo[num-1]->forcecharacter, word2, SKINNAMESIZE+1);
+				strlwr(mapheaderinfo[num-1]->forcecharacter); // skin names are lowercase
+			}
+			else if (fastcmp(word, "WEATHER"))
+				mapheaderinfo[num-1]->weather = (UINT8)get_number(word2);
+			else if (fastcmp(word, "SKYNUM"))
+				mapheaderinfo[num-1]->skynum = (INT16)i;
+			else if (fastcmp(word, "INTERSCREEN"))
+				strncpy(mapheaderinfo[num-1]->interscreen, word2, 8);
+			else if (fastcmp(word, "PRECUTSCENENUM"))
+				mapheaderinfo[num-1]->precutscenenum = (UINT8)i;
+			else if (fastcmp(word, "CUTSCENENUM"))
+				mapheaderinfo[num-1]->cutscenenum = (UINT8)i;
+			else if (fastcmp(word, "COUNTDOWN"))
+				mapheaderinfo[num-1]->countdown = (INT16)i;
+			else if (fastcmp(word, "PALETTE"))
+				mapheaderinfo[num-1]->palette = (UINT16)i;
+			else if (fastcmp(word, "NUMLAPS"))
+				mapheaderinfo[num-1]->numlaps = (UINT8)i;
+			else if (fastcmp(word, "UNLOCKABLE"))
+			{
+				if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something
+					mapheaderinfo[num-1]->unlockrequired = (SINT8)i - 1;
+				else
+					deh_warning("Level header %d: invalid unlockable number %d", num, i);
+			}
+			else if (fastcmp(word, "LEVELSELECT"))
+				mapheaderinfo[num-1]->levelselect = (UINT8)i;
+			else if (fastcmp(word, "SKYBOXSCALE"))
+				mapheaderinfo[num-1]->skybox_scalex = mapheaderinfo[num-1]->skybox_scaley = mapheaderinfo[num-1]->skybox_scalez = (INT16)i;
+			else if (fastcmp(word, "SKYBOXSCALEX"))
+				mapheaderinfo[num-1]->skybox_scalex = (INT16)i;
+			else if (fastcmp(word, "SKYBOXSCALEY"))
+				mapheaderinfo[num-1]->skybox_scaley = (INT16)i;
+			else if (fastcmp(word, "SKYBOXSCALEZ"))
+				mapheaderinfo[num-1]->skybox_scalez = (INT16)i;
+
+			else if (fastcmp(word, "BONUSTYPE"))
+			{
+				if      (fastcmp(word2, "NONE"))   i = -1;
+				else if (fastcmp(word2, "NORMAL")) i =  0;
+				else if (fastcmp(word2, "BOSS"))   i =  1;
+				else if (fastcmp(word2, "ERZ3"))   i =  2;
+				else if (fastcmp(word2, "NIGHTS")) i =  3;
+				else if (fastcmp(word2, "NIGHTSLINK")) i = 4;
+
+				if (i >= -1 && i <= 4) // -1 for no bonus. Max is 4.
+					mapheaderinfo[num-1]->bonustype = (SINT8)i;
+				else
+					deh_warning("Level header %d: invalid bonus type number %d", num, i);
+			}
+
+			// Title card
+			else if (fastcmp(word, "TITLECARDZIGZAG"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->ltzzpatch, word2,
+					sizeof(mapheaderinfo[num-1]->ltzzpatch), va("Level header %d: title card zigzag patch name", num));
+			}
+			else if (fastcmp(word, "TITLECARDZIGZAGTEXT"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->ltzztext, word2,
+					sizeof(mapheaderinfo[num-1]->ltzztext), va("Level header %d: title card zigzag text patch name", num));
+			}
+			else if (fastcmp(word, "TITLECARDACTDIAMOND"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->ltactdiamond, word2,
+					sizeof(mapheaderinfo[num-1]->ltactdiamond), va("Level header %d: title card act diamond patch name", num));
+			}
+
+			else if (fastcmp(word, "MAXBONUSLIVES"))
+				mapheaderinfo[num-1]->maxbonuslives = (SINT8)i;
+			else if (fastcmp(word, "LEVELFLAGS"))
+				mapheaderinfo[num-1]->levelflags = (UINT16)i;
+			else if (fastcmp(word, "MENUFLAGS"))
+				mapheaderinfo[num-1]->menuflags = (UINT8)i;
+
+			// Individual triggers for level flags, for ease of use (and 2.0 compatibility)
+			else if (fastcmp(word, "SCRIPTISFILE"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_SCRIPTISFILE;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_SCRIPTISFILE;
+			}
+			else if (fastcmp(word, "SPEEDMUSIC"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_SPEEDMUSIC;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_SPEEDMUSIC;
+			}
+			else if (fastcmp(word, "NOSSMUSIC"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_NOSSMUSIC;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_NOSSMUSIC;
+			}
+			else if (fastcmp(word, "NORELOAD"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_NORELOAD;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_NORELOAD;
+			}
+			else if (fastcmp(word, "NOZONE"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_NOZONE;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_NOZONE;
+			}
+			else if (fastcmp(word, "SAVEGAME"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_SAVEGAME;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_SAVEGAME;
+			}
+			else if (fastcmp(word, "MIXNIGHTSCOUNTDOWN"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_MIXNIGHTSCOUNTDOWN;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_MIXNIGHTSCOUNTDOWN;
+			}
+			else if (fastcmp(word, "WARNINGTITLE"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_WARNINGTITLE;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_WARNINGTITLE;
+			}
+			else if (fastcmp(word, "NOTITLECARD"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_NOTITLECARD;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARD;
+			}
+			else if (fastcmp(word, "SHOWTITLECARDFOR"))
+			{
+				mapheaderinfo[num-1]->levelflags |= LF_NOTITLECARD;
+				tmp = strtok(word2,",");
+				do {
+					if (fastcmp(tmp, "FIRST"))
+						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARDFIRST;
+					else if (fastcmp(tmp, "RESPAWN"))
+						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARDRESPAWN;
+					else if (fastcmp(tmp, "RECORDATTACK"))
+						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARDRECORDATTACK;
+					else if (fastcmp(tmp, "ALL"))
+						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARD;
+					else if (!fastcmp(tmp, "NONE"))
+						deh_warning("Level header %d: unknown titlecard show option %s\n", num, tmp);
+
+				} while((tmp = strtok(NULL,",")) != NULL);
+			}
+
+			// Individual triggers for menu flags
+			else if (fastcmp(word, "HIDDEN"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->menuflags |= LF2_HIDEINMENU;
+				else
+					mapheaderinfo[num-1]->menuflags &= ~LF2_HIDEINMENU;
+			}
+			else if (fastcmp(word, "HIDEINSTATS"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->menuflags |= LF2_HIDEINSTATS;
+				else
+					mapheaderinfo[num-1]->menuflags &= ~LF2_HIDEINSTATS;
+			}
+			else if (fastcmp(word, "RECORDATTACK") || fastcmp(word, "TIMEATTACK"))
+			{ // TIMEATTACK is an accepted alias
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->menuflags |= LF2_RECORDATTACK;
+				else
+					mapheaderinfo[num-1]->menuflags &= ~LF2_RECORDATTACK;
+			}
+			else if (fastcmp(word, "NIGHTSATTACK"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->menuflags |= LF2_NIGHTSATTACK;
+				else
+					mapheaderinfo[num-1]->menuflags &= LF2_NIGHTSATTACK;
+			}
+			else if (fastcmp(word, "NOVISITNEEDED"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->menuflags |= LF2_NOVISITNEEDED;
+				else
+					mapheaderinfo[num-1]->menuflags &= ~LF2_NOVISITNEEDED;
+			}
+			else if (fastcmp(word, "WIDEICON"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->menuflags |= LF2_WIDEICON;
+				else
+					mapheaderinfo[num-1]->menuflags &= ~LF2_WIDEICON;
+			}
+			else if (fastcmp(word, "STARTRINGS"))
+				mapheaderinfo[num-1]->startrings = (UINT16)i;
+			else if (fastcmp(word, "SPECIALSTAGETIME"))
+				mapheaderinfo[num-1]->sstimer = i;
+			else if (fastcmp(word, "SPECIALSTAGESPHERES"))
+				mapheaderinfo[num-1]->ssspheres = i;
+			else if (fastcmp(word, "GRAVITY"))
+				mapheaderinfo[num-1]->gravity = FLOAT_TO_FIXED(atof(word2));
+			else
+				deh_warning("Level header %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
+{
+	char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	INT32 i;
+	UINT16 usi;
+	UINT8 picid;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			if (fastcmp(word, "SCENETEXT"))
+			{
+				char *scenetext = NULL;
+				char *buffer;
+				const int bufferlen = 4096;
+
+				for (i = 0; i < MAXLINELEN; i++)
+				{
+					if (s[i] == '=')
+					{
+						scenetext = &s[i+2];
+						break;
+					}
+				}
+
+				if (!scenetext)
+				{
+					Z_Free(cutscenes[num]->scene[scenenum].text);
+					cutscenes[num]->scene[scenenum].text = NULL;
+					continue;
+				}
+
+				for (i = 0; i < MAXLINELEN; i++)
+				{
+					if (s[i] == '\0')
+					{
+						s[i] = '\n';
+						s[i+1] = '\0';
+						break;
+					}
+				}
+
+				buffer = Z_Malloc(4096, PU_STATIC, NULL);
+				strcpy(buffer, scenetext);
+
+				strcat(buffer,
+					myhashfgets(scenetext, bufferlen
+					- strlen(buffer) - 1, f));
+
+				// A cutscene overwriting another one...
+				Z_Free(cutscenes[num]->scene[scenenum].text);
+
+				cutscenes[num]->scene[scenenum].text = Z_StrDup(buffer);
+
+				Z_Free(buffer);
+
+				continue;
+			}
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+			i = atoi(word2);
+			usi = (UINT16)i;
+
+
+			if (fastcmp(word, "NUMBEROFPICS"))
+			{
+				cutscenes[num]->scene[scenenum].numpics = (UINT8)i;
+			}
+			else if (fastncmp(word, "PIC", 3))
+			{
+				picid = (UINT8)atoi(word + 3);
+				if (picid > 8 || picid == 0)
+				{
+					deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
+					continue;
+				}
+				--picid;
+
+				if (fastcmp(word+4, "NAME"))
+				{
+					strncpy(cutscenes[num]->scene[scenenum].picname[picid], word2, 8);
+				}
+				else if (fastcmp(word+4, "HIRES"))
+				{
+					cutscenes[num]->scene[scenenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+				}
+				else if (fastcmp(word+4, "DURATION"))
+				{
+					cutscenes[num]->scene[scenenum].picduration[picid] = usi;
+				}
+				else if (fastcmp(word+4, "XCOORD"))
+				{
+					cutscenes[num]->scene[scenenum].xcoord[picid] = usi;
+				}
+				else if (fastcmp(word+4, "YCOORD"))
+				{
+					cutscenes[num]->scene[scenenum].ycoord[picid] = usi;
+				}
+				else
+					deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
+			}
+			else if (fastcmp(word, "MUSIC"))
+			{
+				strncpy(cutscenes[num]->scene[scenenum].musswitch, word2, 7);
+				cutscenes[num]->scene[scenenum].musswitch[6] = 0;
+			}
+#ifdef MUSICSLOT_COMPATIBILITY
+			else if (fastcmp(word, "MUSICSLOT"))
+			{
+				i = get_mus(word2, true);
+				if (i && i <= 1035)
+					snprintf(cutscenes[num]->scene[scenenum].musswitch, 7, "%sM", G_BuildMapName(i));
+				else if (i && i <= 1050)
+					strncpy(cutscenes[num]->scene[scenenum].musswitch, compat_special_music_slots[i - 1036], 7);
+				else
+					cutscenes[num]->scene[scenenum].musswitch[0] = 0; // becomes empty string
+				cutscenes[num]->scene[scenenum].musswitch[6] = 0;
+			}
+#endif
+			else if (fastcmp(word, "MUSICTRACK"))
+			{
+				cutscenes[num]->scene[scenenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK;
+			}
+			else if (fastcmp(word, "MUSICPOS"))
+			{
+				cutscenes[num]->scene[scenenum].musswitchposition = (UINT32)get_number(word2);
+			}
+			else if (fastcmp(word, "MUSICLOOP"))
+			{
+				cutscenes[num]->scene[scenenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+			}
+			else if (fastcmp(word, "TEXTXPOS"))
+			{
+				cutscenes[num]->scene[scenenum].textxpos = usi;
+			}
+			else if (fastcmp(word, "TEXTYPOS"))
+			{
+				cutscenes[num]->scene[scenenum].textypos = usi;
+			}
+			else if (fastcmp(word, "FADEINID"))
+			{
+				cutscenes[num]->scene[scenenum].fadeinid = (UINT8)i;
+			}
+			else if (fastcmp(word, "FADEOUTID"))
+			{
+				cutscenes[num]->scene[scenenum].fadeoutid = (UINT8)i;
+			}
+			else if (fastcmp(word, "FADECOLOR"))
+			{
+				cutscenes[num]->scene[scenenum].fadecolor = (UINT8)i;
+			}
+			else
+				deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readcutscene(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	// Allocate memory for this cutscene if we don't yet have any
+	if (!cutscenes[num])
+		cutscenes[num] = Z_Calloc(sizeof (cutscene_t), PU_STATIC, NULL);
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " ");
+			if (word2)
+				value = atoi(word2);
+			else
+			{
+				deh_warning("No value for token %s", word);
+				continue;
+			}
+
+			if (fastcmp(word, "NUMSCENES"))
+			{
+				cutscenes[num]->numscenes = value;
+			}
+			else if (fastcmp(word, "SCENE"))
+			{
+				if (1 <= value && value <= 128)
+				{
+					readcutscenescene(f, num, value - 1);
+				}
+				else
+					deh_warning("Scene number %d out of range (1 - 128)", value);
+
+			}
+			else
+				deh_warning("Cutscene %d: unknown word '%s', Scene <num> expected.", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
+{
+	char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	INT32 i;
+	UINT16 usi;
+	UINT8 picid;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			if (fastcmp(word, "PAGETEXT"))
+			{
+				char *pagetext = NULL;
+				char *buffer;
+				const int bufferlen = 4096;
+
+				for (i = 0; i < MAXLINELEN; i++)
+				{
+					if (s[i] == '=')
+					{
+						pagetext = &s[i+2];
+						break;
+					}
+				}
+
+				if (!pagetext)
+				{
+					Z_Free(textprompts[num]->page[pagenum].text);
+					textprompts[num]->page[pagenum].text = NULL;
+					continue;
+				}
+
+				for (i = 0; i < MAXLINELEN; i++)
+				{
+					if (s[i] == '\0')
+					{
+						s[i] = '\n';
+						s[i+1] = '\0';
+						break;
+					}
+				}
+
+				buffer = Z_Malloc(4096, PU_STATIC, NULL);
+				strcpy(buffer, pagetext);
+
+				// \todo trim trailing whitespace before the #
+				// and also support # at the end of a PAGETEXT with no line break
+
+				strcat(buffer,
+					myhashfgets(pagetext, bufferlen
+					- strlen(buffer) - 1, f));
+
+				// A text prompt overwriting another one...
+				Z_Free(textprompts[num]->page[pagenum].text);
+
+				textprompts[num]->page[pagenum].text = Z_StrDup(buffer);
+
+				Z_Free(buffer);
+
+				continue;
+			}
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+			i = atoi(word2);
+			usi = (UINT16)i;
+
+			// copypasta from readcutscenescene
+			if (fastcmp(word, "NUMBEROFPICS"))
+			{
+				textprompts[num]->page[pagenum].numpics = (UINT8)i;
+			}
+			else if (fastcmp(word, "PICMODE"))
+			{
+				UINT8 picmode = 0; // PROMPT_PIC_PERSIST
+				if (usi == 1 || word2[0] == 'L') picmode = PROMPT_PIC_LOOP;
+				else if (usi == 2 || word2[0] == 'D' || word2[0] == 'H') picmode = PROMPT_PIC_DESTROY;
+				textprompts[num]->page[pagenum].picmode = picmode;
+			}
+			else if (fastcmp(word, "PICTOLOOP"))
+				textprompts[num]->page[pagenum].pictoloop = (UINT8)i;
+			else if (fastcmp(word, "PICTOSTART"))
+				textprompts[num]->page[pagenum].pictostart = (UINT8)i;
+			else if (fastcmp(word, "PICSMETAPAGE"))
+			{
+				if (usi && usi <= textprompts[num]->numpages)
+				{
+					UINT8 metapagenum = usi - 1;
+
+					textprompts[num]->page[pagenum].numpics = textprompts[num]->page[metapagenum].numpics;
+					textprompts[num]->page[pagenum].picmode = textprompts[num]->page[metapagenum].picmode;
+					textprompts[num]->page[pagenum].pictoloop = textprompts[num]->page[metapagenum].pictoloop;
+					textprompts[num]->page[pagenum].pictostart = textprompts[num]->page[metapagenum].pictostart;
+
+					for (picid = 0; picid < MAX_PROMPT_PICS; picid++)
+					{
+						strncpy(textprompts[num]->page[pagenum].picname[picid], textprompts[num]->page[metapagenum].picname[picid], 8);
+						textprompts[num]->page[pagenum].pichires[picid] = textprompts[num]->page[metapagenum].pichires[picid];
+						textprompts[num]->page[pagenum].picduration[picid] = textprompts[num]->page[metapagenum].picduration[picid];
+						textprompts[num]->page[pagenum].xcoord[picid] = textprompts[num]->page[metapagenum].xcoord[picid];
+						textprompts[num]->page[pagenum].ycoord[picid] = textprompts[num]->page[metapagenum].ycoord[picid];
+					}
+				}
+			}
+			else if (fastncmp(word, "PIC", 3))
+			{
+				picid = (UINT8)atoi(word + 3);
+				if (picid > MAX_PROMPT_PICS || picid == 0)
+				{
+					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
+					continue;
+				}
+				--picid;
+
+				if (fastcmp(word+4, "NAME"))
+				{
+					strncpy(textprompts[num]->page[pagenum].picname[picid], word2, 8);
+				}
+				else if (fastcmp(word+4, "HIRES"))
+				{
+					textprompts[num]->page[pagenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+				}
+				else if (fastcmp(word+4, "DURATION"))
+				{
+					textprompts[num]->page[pagenum].picduration[picid] = usi;
+				}
+				else if (fastcmp(word+4, "XCOORD"))
+				{
+					textprompts[num]->page[pagenum].xcoord[picid] = usi;
+				}
+				else if (fastcmp(word+4, "YCOORD"))
+				{
+					textprompts[num]->page[pagenum].ycoord[picid] = usi;
+				}
+				else
+					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
+			}
+			else if (fastcmp(word, "MUSIC"))
+			{
+				strncpy(textprompts[num]->page[pagenum].musswitch, word2, 7);
+				textprompts[num]->page[pagenum].musswitch[6] = 0;
+			}
+#ifdef MUSICSLOT_COMPATIBILITY
+			else if (fastcmp(word, "MUSICSLOT"))
+			{
+				i = get_mus(word2, true);
+				if (i && i <= 1035)
+					snprintf(textprompts[num]->page[pagenum].musswitch, 7, "%sM", G_BuildMapName(i));
+				else if (i && i <= 1050)
+					strncpy(textprompts[num]->page[pagenum].musswitch, compat_special_music_slots[i - 1036], 7);
+				else
+					textprompts[num]->page[pagenum].musswitch[0] = 0; // becomes empty string
+				textprompts[num]->page[pagenum].musswitch[6] = 0;
+			}
+#endif
+			else if (fastcmp(word, "MUSICTRACK"))
+			{
+				textprompts[num]->page[pagenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK;
+			}
+			else if (fastcmp(word, "MUSICLOOP"))
+			{
+				textprompts[num]->page[pagenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+			}
+			// end copypasta from readcutscenescene
+			else if (fastcmp(word, "NAME"))
+			{
+				if (*word2 != '\0')
+				{
+					INT32 j;
+
+					// HACK: Add yellow control char now
+					// so the drawing function doesn't call it repeatedly
+					char name[34];
+					name[0] = '\x82'; // color yellow
+					name[1] = 0;
+					strncat(name, word2, 33);
+					name[33] = 0;
+
+					// Replace _ with ' '
+					for (j = 0; j < 32 && name[j]; j++)
+					{
+						if (name[j] == '_')
+							name[j] = ' ';
+					}
+
+					strncpy(textprompts[num]->page[pagenum].name, name, 32);
+				}
+				else
+					*textprompts[num]->page[pagenum].name = '\0';
+			}
+			else if (fastcmp(word, "ICON"))
+				strncpy(textprompts[num]->page[pagenum].iconname, word2, 8);
+			else if (fastcmp(word, "ICONALIGN"))
+				textprompts[num]->page[pagenum].rightside = (i || word2[0] == 'R');
+			else if (fastcmp(word, "ICONFLIP"))
+				textprompts[num]->page[pagenum].iconflip = (i || word2[0] == 'T' || word2[0] == 'Y');
+			else if (fastcmp(word, "LINES"))
+				textprompts[num]->page[pagenum].lines = usi;
+			else if (fastcmp(word, "BACKCOLOR"))
+			{
+				INT32 backcolor;
+				if      (i == 0 || fastcmp(word2, "WHITE")) backcolor = 0;
+				else if (i == 1 || fastcmp(word2, "GRAY") || fastcmp(word2, "GREY") ||
+					fastcmp(word2, "BLACK")) backcolor = 1;
+				else if (i == 2 || fastcmp(word2, "SEPIA")) backcolor = 2;
+				else if (i == 3 || fastcmp(word2, "BROWN")) backcolor = 3;
+				else if (i == 4 || fastcmp(word2, "PINK")) backcolor = 4;
+				else if (i == 5 || fastcmp(word2, "RASPBERRY")) backcolor = 5;
+				else if (i == 6 || fastcmp(word2, "RED")) backcolor = 6;
+				else if (i == 7 || fastcmp(word2, "CREAMSICLE")) backcolor = 7;
+				else if (i == 8 || fastcmp(word2, "ORANGE")) backcolor = 8;
+				else if (i == 9 || fastcmp(word2, "GOLD")) backcolor = 9;
+				else if (i == 10 || fastcmp(word2, "YELLOW")) backcolor = 10;
+				else if (i == 11 || fastcmp(word2, "EMERALD")) backcolor = 11;
+				else if (i == 12 || fastcmp(word2, "GREEN")) backcolor = 12;
+				else if (i == 13 || fastcmp(word2, "CYAN") || fastcmp(word2, "AQUA")) backcolor = 13;
+				else if (i == 14 || fastcmp(word2, "STEEL")) backcolor = 14;
+				else if (i == 15 || fastcmp(word2, "PERIWINKLE")) backcolor = 15;
+				else if (i == 16 || fastcmp(word2, "BLUE")) backcolor = 16;
+				else if (i == 17 || fastcmp(word2, "PURPLE")) backcolor = 17;
+				else if (i == 18 || fastcmp(word2, "LAVENDER")) backcolor = 18;
+				else if (i >= 256 && i < 512) backcolor = i; // non-transparent palette index
+				else if (i < 0) backcolor = INT32_MAX; // CONS_BACKCOLOR user-configured
+				else backcolor = 1; // default gray
+				textprompts[num]->page[pagenum].backcolor = backcolor;
+			}
+			else if (fastcmp(word, "ALIGN"))
+			{
+				UINT8 align = 0; // left
+				if (usi == 1 || word2[0] == 'R') align = 1;
+				else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2;
+				textprompts[num]->page[pagenum].align = align;
+			}
+			else if (fastcmp(word, "VERTICALALIGN"))
+			{
+				UINT8 align = 0; // top
+				if (usi == 1 || word2[0] == 'B') align = 1;
+				else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2;
+				textprompts[num]->page[pagenum].verticalalign = align;
+			}
+			else if (fastcmp(word, "TEXTSPEED"))
+				textprompts[num]->page[pagenum].textspeed = get_number(word2);
+			else if (fastcmp(word, "TEXTSFX"))
+				textprompts[num]->page[pagenum].textsfx = get_number(word2);
+			else if (fastcmp(word, "HIDEHUD"))
+			{
+				UINT8 hidehud = 0;
+				if ((word2[0] == 'F' && (word2[1] == 'A' || !word2[1])) || word2[0] == 'N') hidehud = 0; // false
+				else if (usi == 1 || word2[0] == 'T' || word2[0] == 'Y') hidehud = 1; // true (hide appropriate HUD elements)
+				else if (usi == 2 || word2[0] == 'A' || (word2[0] == 'F' && word2[1] == 'O')) hidehud = 2; // force (hide all HUD elements)
+				textprompts[num]->page[pagenum].hidehud = hidehud;
+			}
+			else if (fastcmp(word, "METAPAGE"))
+			{
+				if (usi && usi <= textprompts[num]->numpages)
+				{
+					UINT8 metapagenum = usi - 1;
+
+					strncpy(textprompts[num]->page[pagenum].name, textprompts[num]->page[metapagenum].name, 32);
+					strncpy(textprompts[num]->page[pagenum].iconname, textprompts[num]->page[metapagenum].iconname, 8);
+					textprompts[num]->page[pagenum].rightside = textprompts[num]->page[metapagenum].rightside;
+					textprompts[num]->page[pagenum].iconflip = textprompts[num]->page[metapagenum].iconflip;
+					textprompts[num]->page[pagenum].lines = textprompts[num]->page[metapagenum].lines;
+					textprompts[num]->page[pagenum].backcolor = textprompts[num]->page[metapagenum].backcolor;
+					textprompts[num]->page[pagenum].align = textprompts[num]->page[metapagenum].align;
+					textprompts[num]->page[pagenum].verticalalign = textprompts[num]->page[metapagenum].verticalalign;
+					textprompts[num]->page[pagenum].textspeed = textprompts[num]->page[metapagenum].textspeed;
+					textprompts[num]->page[pagenum].textsfx = textprompts[num]->page[metapagenum].textsfx;
+					textprompts[num]->page[pagenum].hidehud = textprompts[num]->page[metapagenum].hidehud;
+
+					// music: don't copy, else each page change may reset the music
+				}
+			}
+			else if (fastcmp(word, "TAG"))
+				strncpy(textprompts[num]->page[pagenum].tag, word2, 33);
+			else if (fastcmp(word, "NEXTPROMPT"))
+				textprompts[num]->page[pagenum].nextprompt = usi;
+			else if (fastcmp(word, "NEXTPAGE"))
+				textprompts[num]->page[pagenum].nextpage = usi;
+			else if (fastcmp(word, "NEXTTAG"))
+				strncpy(textprompts[num]->page[pagenum].nexttag, word2, 33);
+			else if (fastcmp(word, "TIMETONEXT"))
+				textprompts[num]->page[pagenum].timetonext = get_number(word2);
+			else
+				deh_warning("PromptPage %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readtextprompt(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	// Allocate memory for this prompt if we don't yet have any
+	if (!textprompts[num])
+		textprompts[num] = Z_Calloc(sizeof (textprompt_t), PU_STATIC, NULL);
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " ");
+			if (word2)
+				value = atoi(word2);
+			else
+			{
+				deh_warning("No value for token %s", word);
+				continue;
+			}
+
+			if (fastcmp(word, "NUMPAGES"))
+			{
+				textprompts[num]->numpages = min(max(value, 0), MAX_PAGES);
+			}
+			else if (fastcmp(word, "PAGE"))
+			{
+				if (1 <= value && value <= MAX_PAGES)
+				{
+					textprompts[num]->page[value - 1].backcolor = 1; // default to gray
+					textprompts[num]->page[value - 1].hidehud = 1; // hide appropriate HUD elements
+					readtextpromptpage(f, num, value - 1);
+				}
+				else
+					deh_warning("Page number %d out of range (1 - %d)", value, MAX_PAGES);
+
+			}
+			else
+				deh_warning("Prompt %d: unknown word '%s', Page <num> expected.", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readmenu(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = (tmp += 2);
+			strupr(word2);
+
+			value = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "BACKGROUNDNAME"))
+			{
+				strncpy(menupres[num].bgname, word2, 8);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "HIDEBACKGROUND"))
+			{
+				menupres[num].bghide = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "BACKGROUNDCOLOR"))
+			{
+				menupres[num].bgcolor = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS") || fastcmp(word, "TITLEPICSHIDE"))
+			{
+				// true by default, except MM_MAIN
+				menupres[num].hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSMODE"))
+			{
+				if (fastcmp(word2, "USER"))
+					menupres[num].ttmode = TTMODE_USER;
+				else if (fastcmp(word2, "ALACROIX"))
+					menupres[num].ttmode = TTMODE_ALACROIX;
+				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
+				{
+					menupres[num].ttmode = TTMODE_USER;
+					menupres[num].ttname[0] = 0;
+					menupres[num].hidetitlepics = true;
+				}
+				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
+					menupres[num].ttmode = TTMODE_OLD;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALE"))
+			{
+				// Don't handle Alacroix special case here; see Maincfg section.
+				menupres[num].ttscale = max(1, min(8, (UINT8)get_number(word2)));
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSNAME"))
+			{
+				strncpy(menupres[num].ttname, word2, 9);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSX"))
+			{
+				menupres[num].ttx = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSY"))
+			{
+				menupres[num].tty = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSLOOP"))
+			{
+				menupres[num].ttloop = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSTICS"))
+			{
+				menupres[num].tttics = (UINT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED")
+				|| fastcmp(word, "SCROLLSPEED") || fastcmp(word, "SCROLLXSPEED"))
+			{
+				menupres[num].titlescrollxspeed = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLESCROLLYSPEED") || fastcmp(word, "SCROLLYSPEED"))
+			{
+				menupres[num].titlescrollyspeed = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "MUSIC"))
+			{
+				strncpy(menupres[num].musname, word2, 7);
+				menupres[num].musname[6] = 0;
+				titlechanged = true;
+			}
+#ifdef MUSICSLOT_COMPATIBILITY
+			else if (fastcmp(word, "MUSICSLOT"))
+			{
+				value = get_mus(word2, true);
+				if (value && value <= 1035)
+					snprintf(menupres[num].musname, 7, "%sM", G_BuildMapName(value));
+				else if (value && value <= 1050)
+					strncpy(menupres[num].musname, compat_special_music_slots[value - 1036], 7);
+				else
+					menupres[num].musname[0] = 0; // becomes empty string
+				menupres[num].musname[6] = 0;
+				titlechanged = true;
+			}
+#endif
+			else if (fastcmp(word, "MUSICTRACK"))
+			{
+				menupres[num].mustrack = ((UINT16)value - 1);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "MUSICLOOP"))
+			{
+				// true by default except MM_MAIN
+				menupres[num].muslooping = (value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "NOMUSIC"))
+			{
+				menupres[num].musstop = (value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "IGNOREMUSIC"))
+			{
+				menupres[num].musignore = (value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "FADESTRENGTH"))
+			{
+				// one-based, <= 0 means use default value. 1-32
+				menupres[num].fadestrength = get_number(word2)-1;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "NOENTERBUBBLE"))
+			{
+				menupres[num].enterbubble = !(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "NOEXITBUBBLE"))
+			{
+				menupres[num].exitbubble = !(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "ENTERTAG"))
+			{
+				menupres[num].entertag = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "EXITTAG"))
+			{
+				menupres[num].exittag = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "ENTERWIPE"))
+			{
+				menupres[num].enterwipe = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "EXITWIPE"))
+			{
+				menupres[num].exitwipe = get_number(word2);
+				titlechanged = true;
+			}
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readhuditem(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	INT32 i;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+			strupr(word2);
+
+			i = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "X"))
+			{
+				hudinfo[num].x = i;
+			}
+			else if (fastcmp(word, "Y"))
+			{
+				hudinfo[num].y = i;
+			}
+			else if (fastcmp(word, "F"))
+			{
+				hudinfo[num].f = i;
+			}
+			else
+				deh_warning("Level header %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readframe(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word1;
+	char *word2 = NULL;
+	char *tmp;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word1 = strtok(s, " ");
+			if (word1)
+				strupr(word1);
+			else
+				break;
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+				strupr(word2);
+			else
+				break;
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+
+			if (fastcmp(word1, "SPRITENUMBER") || fastcmp(word1, "SPRITENAME"))
+			{
+				states[num].sprite = get_sprite(word2);
+			}
+			else if (fastcmp(word1, "SPRITESUBNUMBER") || fastcmp(word1, "SPRITEFRAME"))
+			{
+				states[num].frame = (INT32)get_number(word2); // So the FF_ flags get calculated
+			}
+			else if (fastcmp(word1, "DURATION"))
+			{
+				states[num].tics = (INT32)get_number(word2); // So TICRATE can be used
+			}
+			else if (fastcmp(word1, "NEXT"))
+			{
+				states[num].nextstate = get_state(word2);
+			}
+			else if (fastcmp(word1, "VAR1"))
+			{
+				states[num].var1 = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word1, "VAR2"))
+			{
+				states[num].var2 = (INT32)get_number(word2);
+			}
+			else if (fastcmp(word1, "ACTION"))
+			{
+				size_t z;
+				boolean found = false;
+				char actiontocompare[32];
+
+				memset(actiontocompare, 0x00, sizeof(actiontocompare));
+				strlcpy(actiontocompare, word2, sizeof (actiontocompare));
+				strupr(actiontocompare);
+
+				for (z = 0; z < 32; z++)
+				{
+					if (actiontocompare[z] == '\n' || actiontocompare[z] == '\r')
+					{
+						actiontocompare[z] = '\0';
+						break;
+					}
+				}
+
+				for (z = 0; actionpointers[z].name; z++)
+				{
+					if (actionpointers[z].action.acv == states[num].action.acv)
+						break;
+				}
+
+				z = 0;
+				found = LUA_SetLuaAction(&states[num], actiontocompare);
+				if (!found)
+					while (actionpointers[z].name)
+					{
+						if (fastcmp(actiontocompare, actionpointers[z].name))
+						{
+							states[num].action = actionpointers[z].action;
+							states[num].action.acv = actionpointers[z].action.acv; // assign
+							states[num].action.acp1 = actionpointers[z].action.acp1;
+							found = true;
+							break;
+						}
+						z++;
+					}
+
+				if (!found)
+					deh_warning("Unknown action %s", actiontocompare);
+			}
+			else
+				deh_warning("Frame %d: unknown word '%s'", num, word1);
+		}
+	} while (!myfeof(f));
+
+	Z_Free(s);
+}
+
+void readsound(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			word2 = strtok(NULL, " ");
+			if (word2)
+				value = atoi(word2);
+			else
+			{
+				deh_warning("No value for token %s", word);
+				continue;
+			}
+
+			if (fastcmp(word, "SINGULAR"))
+			{
+				S_sfx[num].singularity = value;
+			}
+			else if (fastcmp(word, "PRIORITY"))
+			{
+				S_sfx[num].priority = value;
+			}
+			else if (fastcmp(word, "FLAGS"))
+			{
+				S_sfx[num].pitch = value;
+			}
+			else if (fastcmp(word, "CAPTION") || fastcmp(word, "DESCRIPTION"))
+			{
+				deh_strlcpy(S_sfx[num].caption, word2,
+					sizeof(S_sfx[num].caption), va("Sound effect %d: caption", num));
+			}
+			else
+				deh_warning("Sound %d : unknown word '%s'",num,word);
+		}
+	} while (!myfeof(f));
+
+	Z_Free(s);
+}
+
+/** Checks if a game data file name for a mod is good.
+ * "Good" means that it contains only alphanumerics, _, and -;
+ * ends in ".dat"; has at least one character before the ".dat";
+ * and is not "gamedata.dat" (tested case-insensitively).
+ *
+ * Assumption: that gamedata.dat is the only .dat file that will
+ * ever be treated specially by the game.
+ *
+ * Note: Check for the tail ".dat" case-insensitively since at
+ * present, we get passed the filename in all uppercase.
+ *
+ * \param s Filename string to check.
+ * \return True if the filename is good.
+ * \sa readmaincfg()
+ * \author Graue <graue@oceanbase.org>
+ */
+static boolean GoodDataFileName(const char *s)
+{
+	const char *p;
+	const char *tail = ".dat";
+
+	for (p = s; *p != '\0'; p++)
+		if (!isalnum(*p) && *p != '_' && *p != '-' && *p != '.')
+			return false;
+
+	p = s + strlen(s) - strlen(tail);
+	if (p <= s) return false; // too short
+	if (!fasticmp(p, tail)) return false; // doesn't end in .dat
+	if (fasticmp(s, "gamedata.dat")) return false;
+
+	return true;
+}
+
+void reademblemdata(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	memset(&emblemlocations[num-1], 0, sizeof(emblem_t));
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+			value = atoi(word2); // used for numerical settings
+
+			// Up here to allow lowercase in hints
+			if (fastcmp(word, "HINT"))
+			{
+				while ((tmp = strchr(word2, '\\')))
+					*tmp = '\n';
+				deh_strlcpy(emblemlocations[num-1].hint, word2, sizeof (emblemlocations[num-1].hint), va("Emblem %d: hint", num));
+				continue;
+			}
+			strupr(word2);
+
+			if (fastcmp(word, "TYPE"))
+			{
+				if (fastcmp(word2, "GLOBAL"))
+					emblemlocations[num-1].type = ET_GLOBAL;
+				else if (fastcmp(word2, "SKIN"))
+					emblemlocations[num-1].type = ET_SKIN;
+				else if (fastcmp(word2, "SCORE"))
+					emblemlocations[num-1].type = ET_SCORE;
+				else if (fastcmp(word2, "TIME"))
+					emblemlocations[num-1].type = ET_TIME;
+				else if (fastcmp(word2, "RINGS"))
+					emblemlocations[num-1].type = ET_RINGS;
+				else if (fastcmp(word2, "MAP"))
+					emblemlocations[num-1].type = ET_MAP;
+				else if (fastcmp(word2, "NGRADE"))
+					emblemlocations[num-1].type = ET_NGRADE;
+				else if (fastcmp(word2, "NTIME"))
+					emblemlocations[num-1].type = ET_NTIME;
+				else
+					emblemlocations[num-1].type = (UINT8)value;
+			}
+			else if (fastcmp(word, "TAG"))
+				emblemlocations[num-1].tag = (INT16)value;
+			else if (fastcmp(word, "MAPNUM"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+
+				emblemlocations[num-1].level = (INT16)value;
+			}
+			else if (fastcmp(word, "SPRITE"))
+			{
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = word2[0];
+				else
+					value += 'A'-1;
+
+				if (value < 'A' || value > 'Z')
+					deh_warning("Emblem %d: sprite must be from A - Z (1 - 26)", num);
+				else
+					emblemlocations[num-1].sprite = (UINT8)value;
+			}
+			else if (fastcmp(word, "COLOR"))
+				emblemlocations[num-1].color = get_number(word2);
+			else if (fastcmp(word, "VAR"))
+				emblemlocations[num-1].var = get_number(word2);
+			else
+				deh_warning("Emblem %d: unknown word '%s'", num, word);
+		}
+	} while (!myfeof(f));
+
+	// Default sprite and color definitions for lazy people like me
+	if (!emblemlocations[num-1].sprite) switch (emblemlocations[num-1].type)
+	{
+		case ET_RINGS:
+			emblemlocations[num-1].sprite = 'R'; break;
+		case ET_SCORE: case ET_NGRADE:
+			emblemlocations[num-1].sprite = 'S'; break;
+		case ET_TIME: case ET_NTIME:
+			emblemlocations[num-1].sprite = 'T'; break;
+		default:
+			emblemlocations[num-1].sprite = 'A'; break;
+	}
+	if (!emblemlocations[num-1].color) switch (emblemlocations[num-1].type)
+	{
+		case ET_RINGS:
+			emblemlocations[num-1].color = SKINCOLOR_GOLD; break;
+		case ET_SCORE:
+			emblemlocations[num-1].color = SKINCOLOR_BROWN; break;
+		case ET_NGRADE:
+			emblemlocations[num-1].color = SKINCOLOR_TEAL; break;
+		case ET_TIME: case ET_NTIME:
+			emblemlocations[num-1].color = SKINCOLOR_GREY; break;
+		default:
+			emblemlocations[num-1].color = SKINCOLOR_BLUE; break;
+	}
+
+	Z_Free(s);
+}
+
+void readextraemblemdata(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	memset(&extraemblems[num-1], 0, sizeof(extraemblem_t));
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+
+			value = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "NAME"))
+				deh_strlcpy(extraemblems[num-1].name, word2,
+					sizeof (extraemblems[num-1].name), va("Extra emblem %d: name", num));
+			else if (fastcmp(word, "OBJECTIVE"))
+				deh_strlcpy(extraemblems[num-1].description, word2,
+					sizeof (extraemblems[num-1].description), va("Extra emblem %d: objective", num));
+			else if (fastcmp(word, "CONDITIONSET"))
+				extraemblems[num-1].conditionset = (UINT8)value;
+			else if (fastcmp(word, "SHOWCONDITIONSET"))
+				extraemblems[num-1].showconditionset = (UINT8)value;
+			else
+			{
+				strupr(word2);
+				if (fastcmp(word, "SPRITE"))
+				{
+					if (word2[0] >= 'A' && word2[0] <= 'Z')
+						value = word2[0];
+					else
+						value += 'A'-1;
+
+					if (value < 'A' || value > 'Z')
+						deh_warning("Emblem %d: sprite must be from A - Z (1 - 26)", num);
+					else
+						extraemblems[num-1].sprite = (UINT8)value;
+				}
+				else if (fastcmp(word, "COLOR"))
+					extraemblems[num-1].color = get_number(word2);
+				else
+					deh_warning("Extra emblem %d: unknown word '%s'", num, word);
+			}
+		}
+	} while (!myfeof(f));
+
+	if (!extraemblems[num-1].sprite)
+		extraemblems[num-1].sprite = 'X';
+	if (!extraemblems[num-1].color)
+		extraemblems[num-1].color = SKINCOLOR_BLUE;
+
+	Z_Free(s);
+}
+
+void readunlockable(MYFILE *f, INT32 num)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	INT32 i;
+
+	memset(&unlockables[num], 0, sizeof(unlockable_t));
+	unlockables[num].objective[0] = '/';
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+
+			i = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "NAME"))
+				deh_strlcpy(unlockables[num].name, word2,
+					sizeof (unlockables[num].name), va("Unlockable %d: name", num));
+			else if (fastcmp(word, "OBJECTIVE"))
+				deh_strlcpy(unlockables[num].objective, word2,
+					sizeof (unlockables[num].objective), va("Unlockable %d: objective", num));
+			else
+			{
+				strupr(word2);
+				if (fastcmp(word, "HEIGHT"))
+					unlockables[num].height = (UINT16)i;
+				else if (fastcmp(word, "CONDITIONSET"))
+					unlockables[num].conditionset = (UINT8)i;
+				else if (fastcmp(word, "SHOWCONDITIONSET"))
+					unlockables[num].showconditionset = (UINT8)i;
+				else if (fastcmp(word, "NOCECHO"))
+					unlockables[num].nocecho = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+				else if (fastcmp(word, "NOCHECKLIST"))
+					unlockables[num].nochecklist = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
+				else if (fastcmp(word, "TYPE"))
+				{
+					if (fastcmp(word2, "NONE"))
+						unlockables[num].type = SECRET_NONE;
+					else if (fastcmp(word2, "ITEMFINDER"))
+						unlockables[num].type = SECRET_ITEMFINDER;
+					else if (fastcmp(word2, "EMBLEMHINTS"))
+						unlockables[num].type = SECRET_EMBLEMHINTS;
+					else if (fastcmp(word2, "PANDORA"))
+						unlockables[num].type = SECRET_PANDORA;
+					else if (fastcmp(word2, "CREDITS"))
+						unlockables[num].type = SECRET_CREDITS;
+					else if (fastcmp(word2, "RECORDATTACK"))
+						unlockables[num].type = SECRET_RECORDATTACK;
+					else if (fastcmp(word2, "NIGHTSMODE"))
+						unlockables[num].type = SECRET_NIGHTSMODE;
+					else if (fastcmp(word2, "HEADER"))
+						unlockables[num].type = SECRET_HEADER;
+					else if (fastcmp(word2, "LEVELSELECT"))
+						unlockables[num].type = SECRET_LEVELSELECT;
+					else if (fastcmp(word2, "WARP"))
+						unlockables[num].type = SECRET_WARP;
+					else if (fastcmp(word2, "SOUNDTEST"))
+						unlockables[num].type = SECRET_SOUNDTEST;
+					else
+						unlockables[num].type = (INT16)i;
+				}
+				else if (fastcmp(word, "VAR"))
+				{
+					// Support using the actual map name,
+					// i.e., Level AB, Level FZ, etc.
+
+					// Convert to map number
+					if (word2[0] >= 'A' && word2[0] <= 'Z')
+						i = M_MapNumber(word2[0], word2[1]);
+
+					unlockables[num].variable = (INT16)i;
+				}
+				else
+					deh_warning("Unlockable %d: unknown word '%s'", num+1, word);
+			}
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+static void readcondition(UINT8 set, UINT32 id, char *word2)
+{
+	INT32 i;
+	char *params[4]; // condition, requirement, extra info, extra info
+	char *spos;
+
+	conditiontype_t ty;
+	INT32 re;
+	INT16 x1 = 0, x2 = 0;
+
+	INT32 offset = 0;
+
+	spos = strtok(word2, " ");
+
+	for (i = 0; i < 4; ++i)
+	{
+		if (spos != NULL)
+		{
+			params[i] = spos;
+			spos = strtok(NULL, " ");
+		}
+		else
+			params[i] = NULL;
+	}
+
+	if (!params[0])
+	{
+		deh_warning("condition line is empty");
+		return;
+	}
+
+	if (fastcmp(params[0], "PLAYTIME"))
+	{
+		PARAMCHECK(1);
+		ty = UC_PLAYTIME;
+		re = atoi(params[1]);
+	}
+	else if        (fastcmp(params[0], "GAMECLEAR")
+	|| (++offset && fastcmp(params[0], "ALLEMERALDS"))
+	|| (++offset && fastcmp(params[0], "ULTIMATECLEAR")))
+	{
+		ty = UC_GAMECLEAR + offset;
+		re = (params[1]) ? atoi(params[1]) : 1;
+	}
+	else if ((offset=0) || fastcmp(params[0], "OVERALLSCORE")
+	||        (++offset && fastcmp(params[0], "OVERALLTIME"))
+	||        (++offset && fastcmp(params[0], "OVERALLRINGS")))
+	{
+		PARAMCHECK(1);
+		ty = UC_OVERALLSCORE + offset;
+		re = atoi(params[1]);
+	}
+	else if ((offset=0) || fastcmp(params[0], "MAPVISITED")
+	||        (++offset && fastcmp(params[0], "MAPBEATEN"))
+	||        (++offset && fastcmp(params[0], "MAPALLEMERALDS"))
+	||        (++offset && fastcmp(params[0], "MAPULTIMATE"))
+	||        (++offset && fastcmp(params[0], "MAPPERFECT")))
+	{
+		PARAMCHECK(1);
+		ty = UC_MAPVISITED + offset;
+
+		// Convert to map number if it appears to be one
+		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
+			re = M_MapNumber(params[1][0], params[1][1]);
+		else
+			re = atoi(params[1]);
+
+		if (re < 0 || re >= NUMMAPS)
+		{
+			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
+			return;
+		}
+	}
+	else if ((offset=0) || fastcmp(params[0], "MAPSCORE")
+	||        (++offset && fastcmp(params[0], "MAPTIME"))
+	||        (++offset && fastcmp(params[0], "MAPRINGS")))
+	{
+		PARAMCHECK(2);
+		ty = UC_MAPSCORE + offset;
+		re = atoi(params[2]);
+
+		// Convert to map number if it appears to be one
+		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
+			x1 = (INT16)M_MapNumber(params[1][0], params[1][1]);
+		else
+			x1 = (INT16)atoi(params[1]);
+
+		if (x1 < 0 || x1 >= NUMMAPS)
+		{
+			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
+			return;
+		}
+	}
+	else if ((offset=0) || fastcmp(params[0], "NIGHTSSCORE")
+	||        (++offset && fastcmp(params[0], "NIGHTSTIME"))
+	||        (++offset && fastcmp(params[0], "NIGHTSGRADE")))
+	{
+		PARAMCHECK(2); // one optional one
+
+		ty = UC_NIGHTSSCORE + offset;
+		i = (params[3] ? 3 : 2);
+		if (fastncmp("GRADE_",params[i],6))
+		{
+			char *p = params[i]+6;
+			for (re = 0; NIGHTSGRADE_LIST[re]; re++)
+				if (*p == NIGHTSGRADE_LIST[re])
+					break;
+			if (!NIGHTSGRADE_LIST[re])
+			{
+				deh_warning("Invalid NiGHTS grade %s\n", params[i]);
+				return;
+			}
+		}
+		else
+			re = atoi(params[i]);
+
+		// Convert to map number if it appears to be one
+		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
+			x1 = (INT16)M_MapNumber(params[1][0], params[1][1]);
+		else
+			x1 = (INT16)atoi(params[1]);
+
+		if (x1 < 0 || x1 >= NUMMAPS)
+		{
+			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
+			return;
+		}
+
+		// Mare number (0 for overall)
+		if (params[3]) // Only if we actually got 3 params (so the second one == mare and not requirement)
+			x2 = (INT16)atoi(params[2]);
+		else
+			x2 = 0;
+
+	}
+	else if (fastcmp(params[0], "TRIGGER"))
+	{
+		PARAMCHECK(1);
+		ty = UC_TRIGGER;
+		re = atoi(params[1]);
+
+		// constrained by 32 bits
+		if (re < 0 || re > 31)
+		{
+			deh_warning("Trigger ID %d out of range (0 - 31)", re);
+			return;
+		}
+	}
+	else if (fastcmp(params[0], "TOTALEMBLEMS"))
+	{
+		PARAMCHECK(1);
+		ty = UC_TOTALEMBLEMS;
+		re = atoi(params[1]);
+	}
+	else if (fastcmp(params[0], "EMBLEM"))
+	{
+		PARAMCHECK(1);
+		ty = UC_EMBLEM;
+		re = atoi(params[1]);
+
+		if (re <= 0 || re > MAXEMBLEMS)
+		{
+			deh_warning("Emblem %d out of range (1 - %d)", re, MAXEMBLEMS);
+			return;
+		}
+	}
+	else if (fastcmp(params[0], "EXTRAEMBLEM"))
+	{
+		PARAMCHECK(1);
+		ty = UC_EXTRAEMBLEM;
+		re = atoi(params[1]);
+
+		if (re <= 0 || re > MAXEXTRAEMBLEMS)
+		{
+			deh_warning("Extra emblem %d out of range (1 - %d)", re, MAXEXTRAEMBLEMS);
+			return;
+		}
+	}
+	else if (fastcmp(params[0], "CONDITIONSET"))
+	{
+		PARAMCHECK(1);
+		ty = UC_CONDITIONSET;
+		re = atoi(params[1]);
+
+		if (re <= 0 || re > MAXCONDITIONSETS)
+		{
+			deh_warning("Condition set %d out of range (1 - %d)", re, MAXCONDITIONSETS);
+			return;
+		}
+	}
+	else
+	{
+		deh_warning("Invalid condition name %s", params[0]);
+		return;
+	}
+
+	M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2);
+}
+
+void readconditionset(MYFILE *f, UINT8 setnum)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	UINT8 id;
+	UINT8 previd = 0;
+
+	M_ClearConditionSet(setnum);
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+			strupr(word2);
+
+			if (fastncmp(word, "CONDITION", 9))
+			{
+				id = (UINT8)atoi(word + 9);
+				if (id == 0)
+				{
+					deh_warning("Condition set %d: unknown word '%s'", setnum, word);
+					continue;
+				}
+				else if (previd > id)
+				{
+					// out of order conditions can cause problems, so enforce proper order
+					deh_warning("Condition set %d: conditions are out of order, ignoring this line", setnum);
+					continue;
+				}
+				previd = id;
+
+				readcondition(setnum, id, word2);
+			}
+			else
+				deh_warning("Condition set %d: unknown word '%s'", setnum, word);
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	Z_Free(s);
+}
+
+void readmaincfg(MYFILE *f)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *word2;
+	char *tmp;
+	INT32 value;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+			strupr(word2);
+
+			value = atoi(word2); // used for numerical settings
+
+			if (fastcmp(word, "EXECCFG"))
+			{
+				if (strchr(word2, '.'))
+					COM_BufAddText(va("exec %s\n", word2));
+				else
+				{
+					lumpnum_t lumpnum;
+					char newname[9];
+
+					strncpy(newname, word2, 8);
+
+					newname[8] = '\0';
+
+					lumpnum = W_CheckNumForName(newname);
+
+					if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
+						CONS_Debug(DBG_SETUP, "SOC Error: script lump %s not found/not valid.\n", newname);
+					else
+						COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
+				}
+			}
+
+			else if (fastcmp(word, "SPSTAGE_START"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				spstage_start = spmarathon_start = (INT16)value;
+			}
+			else if (fastcmp(word, "SPMARATHON_START"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				spmarathon_start = (INT16)value;
+			}
+			else if (fastcmp(word, "SSTAGE_START"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				sstage_start = (INT16)value;
+				sstage_end = (INT16)(sstage_start+7); // 7 special stages total plus one weirdo
+			}
+			else if (fastcmp(word, "SMPSTAGE_START"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				smpstage_start = (INT16)value;
+				smpstage_end = (INT16)(smpstage_start+6); // 7 special stages total
+			}
+			else if (fastcmp(word, "REDTEAM"))
+			{
+				skincolor_redteam = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "BLUETEAM"))
+			{
+				skincolor_blueteam = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "REDRING"))
+			{
+				skincolor_redring = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "BLUERING"))
+			{
+				skincolor_bluering = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "INVULNTICS"))
+			{
+				invulntics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "SNEAKERTICS"))
+			{
+				sneakertics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "FLASHINGTICS"))
+			{
+				flashingtics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "TAILSFLYTICS"))
+			{
+				tailsflytics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "UNDERWATERTICS"))
+			{
+				underwatertics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "SPACETIMETICS"))
+			{
+				spacetimetics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "EXTRALIFETICS"))
+			{
+				extralifetics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "NIGHTSLINKTICS"))
+			{
+				nightslinktics = (UINT16)get_number(word2);
+			}
+			else if (fastcmp(word, "GAMEOVERTICS"))
+			{
+				gameovertics = get_number(word2);
+			}
+			else if (fastcmp(word, "AMMOREMOVALTICS"))
+			{
+				ammoremovaltics = get_number(word2);
+			}
+			else if (fastcmp(word, "INTROTOPLAY"))
+			{
+				introtoplay = (UINT8)get_number(word2);
+				// range check, you morons.
+				if (introtoplay > 128)
+					introtoplay = 128;
+				introchanged = true;
+			}
+			else if (fastcmp(word, "CREDITSCUTSCENE"))
+			{
+				creditscutscene = (UINT8)get_number(word2);
+				// range check, you morons.
+				if (creditscutscene > 128)
+					creditscutscene = 128;
+			}
+			else if (fastcmp(word, "USEBLACKROCK"))
+			{
+				useBlackRock = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+			}
+			else if (fastcmp(word, "LOOPTITLE"))
+			{
+				looptitle = (value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEMAP"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				titlemap = (INT16)value;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE"))
+			{
+				hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSMODE"))
+			{
+				if (fastcmp(word2, "USER"))
+					ttmode = TTMODE_USER;
+				else if (fastcmp(word2, "ALACROIX"))
+					ttmode = TTMODE_ALACROIX;
+				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
+				{
+					ttmode = TTMODE_USER;
+					ttname[0] = 0;
+					hidetitlepics = true;
+				}
+				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
+					ttmode = TTMODE_OLD;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALE"))
+			{
+				ttscale = max(1, min(8, (UINT8)get_number(word2)));
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALESAVAILABLE"))
+			{
+				// SPECIAL CASE for Alacroix: Comma-separated list of resolutions that are available
+				// for gfx loading.
+				ttavailable[0] = ttavailable[1] = ttavailable[2] = ttavailable[3] =\
+					ttavailable[4] = ttavailable[5] = false;
+
+				if (strstr(word2, "1") != NULL)
+					ttavailable[0] = true;
+				if (strstr(word2, "2") != NULL)
+					ttavailable[1] = true;
+				if (strstr(word2, "3") != NULL)
+					ttavailable[2] = true;
+				if (strstr(word2, "4") != NULL)
+					ttavailable[3] = true;
+				if (strstr(word2, "5") != NULL)
+					ttavailable[4] = true;
+				if (strstr(word2, "6") != NULL)
+					ttavailable[5] = true;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSNAME"))
+			{
+				strncpy(ttname, word2, 9);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSX"))
+			{
+				ttx = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSY"))
+			{
+				tty = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSLOOP"))
+			{
+				ttloop = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSTICS"))
+			{
+				tttics = (UINT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED"))
+			{
+				titlescrollxspeed = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLESCROLLYSPEED"))
+			{
+				titlescrollyspeed = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "DISABLESPEEDADJUST"))
+			{
+				disableSpeedAdjust = (value || word2[0] == 'T' || word2[0] == 'Y');
+			}
+			else if (fastcmp(word, "NUMDEMOS"))
+			{
+				numDemos = (UINT8)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "DEMODELAYTIME"))
+			{
+				demoDelayTime = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "DEMOIDLETIME"))
+			{
+				demoIdleTime = get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "USE1UPSOUND"))
+			{
+				use1upSound = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+			}
+			else if (fastcmp(word, "MAXXTRALIFE"))
+			{
+				maxXtraLife = (UINT8)get_number(word2);
+			}
+			else if (fastcmp(word, "USECONTINUES"))
+			{
+				useContinues = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+			}
+
+			else if (fastcmp(word, "GAMEDATA"))
+			{
+				size_t filenamelen;
+
+				// Check the data filename so that mods
+				// can't write arbitrary files.
+				if (!GoodDataFileName(word2))
+					I_Error("Maincfg: bad data file name '%s'\n", word2);
+
+				G_SaveGameData();
+				strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
+				strlwr(gamedatafilename);
+				savemoddata = true;
+
+				// Also save a time attack folder
+				filenamelen = strlen(gamedatafilename)-4;  // Strip off the extension
+				strncpy(timeattackfolder, gamedatafilename, min(filenamelen, sizeof (timeattackfolder)));
+				timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0';
+
+				strcpy(savegamename, timeattackfolder);
+				strlcat(savegamename, "%u.ssg", sizeof(savegamename));
+				// can't use sprintf since there is %u in savegamename
+				strcatbf(savegamename, srb2home, PATHSEP);
+
+				strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder));
+				strcatbf(liveeventbackup, srb2home, PATHSEP);
+
+				gamedataadded = true;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "RESETDATA"))
+			{
+				P_ResetData(value);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "CUSTOMVERSION"))
+			{
+				strlcpy(customversionstring, word2, sizeof (customversionstring));
+				//titlechanged = true;
+			}
+			else if (fastcmp(word, "BOOTMAP"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				bootmap = (INT16)value;
+				//titlechanged = true;
+			}
+			else if (fastcmp(word, "STARTCHAR"))
+			{
+				startchar = (INT16)value;
+				char_on = -1;
+			}
+			else if (fastcmp(word, "TUTORIALMAP"))
+			{
+				// Support using the actual map name,
+				// i.e., Level AB, Level FZ, etc.
+
+				// Convert to map number
+				if (word2[0] >= 'A' && word2[0] <= 'Z')
+					value = M_MapNumber(word2[0], word2[1]);
+				else
+					value = get_number(word2);
+
+				tutorialmap = (INT16)value;
+			}
+			else
+				deh_warning("Maincfg: unknown word '%s'", word);
+		}
+	} while (!myfeof(f));
+
+	Z_Free(s);
+}
+
+void readwipes(MYFILE *f)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word = s;
+	char *pword = word;
+	char *word2;
+	char *tmp;
+	INT32 value;
+	INT32 wipeoffset;
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			// First remove trailing newline, if there is one
+			tmp = strchr(s, '\n');
+			if (tmp)
+				*tmp = '\0';
+
+			tmp = strchr(s, '#');
+			if (tmp)
+				*tmp = '\0';
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
+
+			// Get the part before the " = "
+			tmp = strchr(s, '=');
+			if (tmp)
+				*(tmp-1) = '\0';
+			else
+				break;
+			strupr(word);
+
+			// Now get the part after
+			word2 = tmp += 2;
+			value = atoi(word2); // used for numerical settings
+
+			if (value < -1 || value > 99)
+			{
+				deh_warning("Wipes: bad value '%s'", word2);
+				continue;
+			}
+			else if (value == -1)
+				value = UINT8_MAX;
+
+			// error catching
+			wipeoffset = -1;
+
+			if (fastncmp(word, "LEVEL_", 6))
+			{
+				pword = word + 6;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_level_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_level_final;
+			}
+			else if (fastncmp(word, "INTERMISSION_", 13))
+			{
+				pword = word + 13;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_intermission_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_intermission_final;
+			}
+			else if (fastncmp(word, "SPECINTER_", 10))
+			{
+				pword = word + 10;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_specinter_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_specinter_final;
+			}
+			else if (fastncmp(word, "MULTINTER_", 10))
+			{
+				pword = word + 10;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_multinter_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_multinter_final;
+			}
+			else if (fastncmp(word, "CONTINUING_", 11))
+			{
+				pword = word + 11;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_continuing_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_continuing_final;
+			}
+			else if (fastncmp(word, "TITLESCREEN_", 12))
+			{
+				pword = word + 12;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_titlescreen_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_titlescreen_final;
+			}
+			else if (fastncmp(word, "TIMEATTACK_", 11))
+			{
+				pword = word + 11;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_timeattack_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_timeattack_final;
+			}
+			else if (fastncmp(word, "CREDITS_", 8))
+			{
+				pword = word + 8;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_credits_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_credits_final;
+				else if (fastcmp(pword, "INTERMEDIATE"))
+					wipeoffset = wipe_credits_intermediate;
+			}
+			else if (fastncmp(word, "EVALUATION_", 11))
+			{
+				pword = word + 11;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_evaluation_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_evaluation_final;
+			}
+			else if (fastncmp(word, "GAMEEND_", 8))
+			{
+				pword = word + 8;
+				if (fastcmp(pword, "TOBLACK"))
+					wipeoffset = wipe_gameend_toblack;
+				else if (fastcmp(pword, "FINAL"))
+					wipeoffset = wipe_gameend_final;
+			}
+			else if (fastncmp(word, "SPECLEVEL_", 10))
+			{
+				pword = word + 10;
+				if (fastcmp(pword, "TOWHITE"))
+					wipeoffset = wipe_speclevel_towhite;
+			}
+
+			if (wipeoffset < 0)
+			{
+				deh_warning("Wipes: unknown word '%s'", word);
+				continue;
+			}
+
+			if (value == UINT8_MAX
+			 && (wipeoffset <= wipe_level_toblack || wipeoffset >= wipe_speclevel_towhite))
+			{
+				 // Cannot disable non-toblack wipes
+				 // (or the level toblack wipe, or the special towhite wipe)
+				deh_warning("Wipes: can't disable wipe of type '%s'", word);
+				continue;
+			}
+
+			wipedefs[wipeoffset] = (UINT8)value;
+		}
+	} while (!myfeof(f));
+
+	Z_Free(s);
+}
+
+mobjtype_t get_mobjtype(const char *word)
+{ // Returns the value of MT_ enumerations
+	mobjtype_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("MT_",word,3))
+		word += 3; // take off the MT_
+	for (i = 0; i < NUMMOBJFREESLOTS; i++) {
+		if (!FREE_MOBJS[i])
+			break;
+		if (fastcmp(word, FREE_MOBJS[i]))
+			return MT_FIRSTFREESLOT+i;
+	}
+	for (i = 0; i < MT_FIRSTFREESLOT; i++)
+		if (fastcmp(word, MOBJTYPE_LIST[i]+3))
+			return i;
+	deh_warning("Couldn't find mobjtype named 'MT_%s'",word);
+	return MT_NULL;
+}
+
+statenum_t get_state(const char *word)
+{ // Returns the value of S_ enumerations
+	statenum_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("S_",word,2))
+		word += 2; // take off the S_
+	for (i = 0; i < NUMSTATEFREESLOTS; i++) {
+		if (!FREE_STATES[i])
+			break;
+		if (fastcmp(word, FREE_STATES[i]))
+			return S_FIRSTFREESLOT+i;
+	}
+	for (i = 0; i < S_FIRSTFREESLOT; i++)
+		if (fastcmp(word, STATE_LIST[i]+2))
+			return i;
+	deh_warning("Couldn't find state named 'S_%s'",word);
+	return S_NULL;
+}
+
+skincolornum_t get_skincolor(const char *word)
+{ // Returns the value of SKINCOLOR_ enumerations
+	skincolornum_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("SKINCOLOR_",word,10))
+		word += 10; // take off the SKINCOLOR_
+	for (i = 0; i < NUMCOLORFREESLOTS; i++) {
+		if (!FREE_SKINCOLORS[i])
+			break;
+		if (fastcmp(word, FREE_SKINCOLORS[i]))
+			return SKINCOLOR_FIRSTFREESLOT+i;
+	}
+	for (i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++)
+		if (fastcmp(word, COLOR_ENUMS[i]))
+			return i;
+	deh_warning("Couldn't find skincolor named 'SKINCOLOR_%s'",word);
+	return SKINCOLOR_GREEN;
+}
+
+spritenum_t get_sprite(const char *word)
+{ // Returns the value of SPR_ enumerations
+	spritenum_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("SPR_",word,4))
+		word += 4; // take off the SPR_
+	for (i = 0; i < NUMSPRITES; i++)
+		if (!sprnames[i][4] && memcmp(word,sprnames[i],4)==0)
+			return i;
+	deh_warning("Couldn't find sprite named 'SPR_%s'",word);
+	return SPR_NULL;
+}
+
+playersprite_t get_sprite2(const char *word)
+{ // Returns the value of SPR2_ enumerations
+	playersprite_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("SPR2_",word,5))
+		word += 5; // take off the SPR2_
+	for (i = 0; i < NUMPLAYERSPRITES; i++)
+		if (!spr2names[i][4] && memcmp(word,spr2names[i],4)==0)
+			return i;
+	deh_warning("Couldn't find sprite named 'SPR2_%s'",word);
+	return SPR2_STND;
+}
+
+sfxenum_t get_sfx(const char *word)
+{ // Returns the value of SFX_ enumerations
+	sfxenum_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("SFX_",word,4))
+		word += 4; // take off the SFX_
+	else if (fastncmp("DS",word,2))
+		word += 2; // take off the DS
+	for (i = 0; i < NUMSFX; i++)
+		if (S_sfx[i].name && fasticmp(word, S_sfx[i].name))
+			return i;
+	deh_warning("Couldn't find sfx named 'SFX_%s'",word);
+	return sfx_None;
+}
+
+#ifdef MUSICSLOT_COMPATIBILITY
+UINT16 get_mus(const char *word, UINT8 dehacked_mode)
+{ // Returns the value of MUS_ enumerations
+	UINT16 i;
+	char lumptmp[4];
+
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (!word[2] && toupper(word[0]) >= 'A' && toupper(word[0]) <= 'Z')
+		return (UINT16)M_MapNumber(word[0], word[1]);
+
+	if (fastncmp("MUS_",word,4))
+		word += 4; // take off the MUS_
+	else if (fastncmp("O_",word,2) || fastncmp("D_",word,2))
+		word += 2; // take off the O_ or D_
+
+	strncpy(lumptmp, word, 4);
+	lumptmp[3] = 0;
+	if (fasticmp("MAP",lumptmp))
+	{
+		word += 3;
+		if (toupper(word[0]) >= 'A' && toupper(word[0]) <= 'Z')
+			return (UINT16)M_MapNumber(word[0], word[1]);
+		else if ((i = atoi(word)))
+			return i;
+
+		word -= 3;
+		if (dehacked_mode)
+			deh_warning("Couldn't find music named 'MUS_%s'",word);
+		return 0;
+	}
+	for (i = 0; compat_special_music_slots[i][0]; ++i)
+		if (fasticmp(word, compat_special_music_slots[i]))
+			return i + 1036;
+	if (dehacked_mode)
+		deh_warning("Couldn't find music named 'MUS_%s'",word);
+	return 0;
+}
+#endif
+
+hudnum_t get_huditem(const char *word)
+{ // Returns the value of HUD_ enumerations
+	hudnum_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("HUD_",word,4))
+		word += 4; // take off the HUD_
+	for (i = 0; i < NUMHUDITEMS; i++)
+		if (fastcmp(word, HUDITEMS_LIST[i]))
+			return i;
+	deh_warning("Couldn't find huditem named 'HUD_%s'",word);
+	return HUD_LIVES;
+}
+
+menutype_t get_menutype(const char *word)
+{ // Returns the value of MN_ enumerations
+	menutype_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("MN_",word,3))
+		word += 3; // take off the MN_
+	for (i = 0; i < NUMMENUTYPES; i++)
+		if (fastcmp(word, MENUTYPES_LIST[i]))
+			return i;
+	deh_warning("Couldn't find menutype named 'MN_%s'",word);
+	return MN_NONE;
+}
+
+/*static INT16 get_gametype(const char *word)
+{ // Returns the value of GT_ enumerations
+	INT16 i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("GT_",word,3))
+		word += 3; // take off the GT_
+	for (i = 0; i < NUMGAMETYPES; i++)
+		if (fastcmp(word, Gametype_ConstantNames[i]+3))
+			return i;
+	deh_warning("Couldn't find gametype named 'GT_%s'",word);
+	return GT_COOP;
+}
+
+static powertype_t get_power(const char *word)
+{ // Returns the value of pw_ enumerations
+	powertype_t i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("PW_",word,3))
+		word += 3; // take off the pw_
+	for (i = 0; i < NUMPOWERS; i++)
+		if (fastcmp(word, POWERS_LIST[i]))
+			return i;
+	deh_warning("Couldn't find power named 'pw_%s'",word);
+	return pw_invulnerability;
+}*/
+
+/// \todo Make ANY of this completely over-the-top math craziness obey the order of operations.
+static fixed_t op_mul(fixed_t a, fixed_t b) { return a*b; }
+static fixed_t op_div(fixed_t a, fixed_t b) { return a/b; }
+static fixed_t op_add(fixed_t a, fixed_t b) { return a+b; }
+static fixed_t op_sub(fixed_t a, fixed_t b) { return a-b; }
+static fixed_t op_or(fixed_t a, fixed_t b) { return a|b; }
+static fixed_t op_and(fixed_t a, fixed_t b) { return a&b; }
+static fixed_t op_lshift(fixed_t a, fixed_t b) { return a<<b; }
+static fixed_t op_rshift(fixed_t a, fixed_t b) { return a>>b; }
+
+struct {
+	const char c;
+	fixed_t (*v)(fixed_t,fixed_t);
+} OPERATIONS[] = {
+	{'*',op_mul},
+	{'/',op_div},
+	{'+',op_add},
+	{'-',op_sub},
+	{'|',op_or},
+	{'&',op_and},
+	{'<',op_lshift},
+	{'>',op_rshift},
+	{0,NULL}
+};
+
+// Returns the full word, cut at the first symbol or whitespace
+/*static char *read_word(const char *line)
+{
+	// Part 1: You got the start of the word, now find the end.
+  const char *p;
+	INT32 i;
+	for (p = line+1; *p; p++) {
+		if (*p == ' ' || *p == '\t')
+			break;
+		for (i = 0; OPERATIONS[i].c; i++)
+			if (*p == OPERATIONS[i].c) {
+				i = -1;
+				break;
+			}
+		if (i == -1)
+			break;
+	}
+
+	// Part 2: Make a copy of the word and return it.
+	{
+		size_t len = (p-line);
+		char *word = malloc(len+1);
+		M_Memcpy(word,line,len);
+		word[len] = '\0';
+		return word;
+	}
+}
+
+static INT32 operation_pad(const char **word)
+{ // Brings word the next operation and returns the operation number.
+	INT32 i;
+	for (; **word; (*word)++) {
+		if (**word == ' ' || **word == '\t')
+			continue;
+		for (i = 0; OPERATIONS[i].c; i++)
+			if (**word == OPERATIONS[i].c)
+			{
+				if ((**word == '<' && *(*word+1) == '<') || (**word == '>' && *(*word+1) == '>')) (*word)++; // These operations are two characters long.
+				else if (**word == '<' || **word == '>') continue; // ... do not accept one character long.
+				(*word)++;
+				return i;
+			}
+		deh_warning("Unknown operation '%c'",**word);
+		return -1;
+	}
+	return -1;
+}
+
+static void const_warning(const char *type, const char *word)
+{
+	deh_warning("Couldn't find %s named '%s'",type,word);
+}
+
+static fixed_t find_const(const char **rword)
+{ // Finds the value of constants and returns it, bringing word to the next operation.
+	INT32 i;
+	fixed_t r;
+	char *word = read_word(*rword);
+	*rword += strlen(word);
+	if ((*word >= '0' && *word <= '9') || *word == '-') { // Parse a number
+		r = atoi(word);
+		free(word);
+		return r;
+	}
+	if (!*(word+1) && // Turn a single A-z symbol into numbers, like sprite frames.
+	 ((*word >= 'A' && *word <= 'Z') || (*word >= 'a' && *word <= 'z'))) {
+		r = R_Char2Frame(*word);
+		free(word);
+		return r;
+	}
+	if (fastncmp("MF_", word, 3)) {
+		char *p = word+3;
+		for (i = 0; MOBJFLAG_LIST[i]; i++)
+			if (fastcmp(p, MOBJFLAG_LIST[i])) {
+				free(word);
+				return (1<<i);
+			}
+
+		// Not found error
+		const_warning("mobj flag",word);
+		free(word);
+		return 0;
+	}
+	else if (fastncmp("MF2_", word, 4)) {
+		char *p = word+4;
+		for (i = 0; MOBJFLAG2_LIST[i]; i++)
+			if (fastcmp(p, MOBJFLAG2_LIST[i])) {
+				free(word);
+				return (1<<i);
+			}
+
+		// Not found error
+		const_warning("mobj flag2",word);
+		free(word);
+		return 0;
+	}
+	else if (fastncmp("MFE_", word, 4)) {
+		char *p = word+4;
+		for (i = 0; MOBJEFLAG_LIST[i]; i++)
+			if (fastcmp(p, MOBJEFLAG_LIST[i])) {
+				free(word);
+				return (1<<i);
+			}
+
+		// Not found error
+		const_warning("mobj eflag",word);
+		free(word);
+		return 0;
+	}
+	else if (fastncmp("PF_", word, 3)) {
+		char *p = word+3;
+		for (i = 0; PLAYERFLAG_LIST[i]; i++)
+			if (fastcmp(p, PLAYERFLAG_LIST[i])) {
+				free(word);
+				return (1<<i);
+			}
+		if (fastcmp(p, "FULLSTASIS"))
+			return PF_FULLSTASIS;
+
+		// Not found error
+		const_warning("player flag",word);
+		free(word);
+		return 0;
+	}
+	else if (fastncmp("S_",word,2)) {
+		r = get_state(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("SKINCOLOR_",word,10)) {
+		r = get_skincolor(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("MT_",word,3)) {
+		r = get_mobjtype(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("SPR_",word,4)) {
+		r = get_sprite(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("SFX_",word,4) || fastncmp("DS",word,2)) {
+		r = get_sfx(word);
+		free(word);
+		return r;
+	}
+#ifdef MUSICSLOT_COMPATIBILITY
+	else if (fastncmp("MUS_",word,4) || fastncmp("O_",word,2)) {
+		r = get_mus(word, true);
+		free(word);
+		return r;
+	}
+#endif
+	else if (fastncmp("PW_",word,3)) {
+		r = get_power(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("MN_",word,3)) {
+		r = get_menutype(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("GT_",word,3)) {
+		r = get_gametype(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("GTR_", word, 4)) {
+		char *p = word+4;
+		for (i = 0; GAMETYPERULE_LIST[i]; i++)
+			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
+				free(word);
+				return (1<<i);
+			}
+
+		// Not found error
+		const_warning("game type rule",word);
+		free(word);
+		return 0;
+	}
+	else if (fastncmp("TOL_", word, 4)) {
+		char *p = word+4;
+		for (i = 0; TYPEOFLEVEL[i].name; i++)
+			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
+				free(word);
+				return TYPEOFLEVEL[i].flag;
+			}
+
+		// Not found error
+		const_warning("typeoflevel",word);
+		free(word);
+		return 0;
+	}
+	else if (fastncmp("HUD_",word,4)) {
+		r = get_huditem(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("GRADE_",word,6))
+	{
+		char *p = word+6;
+		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
+			if (*p == NIGHTSGRADE_LIST[i])
+			{
+				free(word);
+				return i;
+			}
+		const_warning("NiGHTS grade",word);
+		free(word);
+		return 0;
+	}
+	for (i = 0; INT_CONST[i].n; i++)
+		if (fastcmp(word,INT_CONST[i].n)) {
+			free(word);
+			return INT_CONST[i].v;
+		}
+
+	// Not found error.
+	const_warning("constant",word);
+	free(word);
+	return 0;
+}*/
diff --git a/src/deh_soc.h b/src/deh_soc.h
new file mode 100644
index 0000000000000000000000000000000000000000..2bcb52e709e9ef1003753fdf68ee6647af9c3e60
--- /dev/null
+++ b/src/deh_soc.h
@@ -0,0 +1,89 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  deh_soc.h
+/// \brief Load SOC file and change tables and text
+
+#ifndef __DEH_SOC_H__
+#define __DEH_SOC_H__
+
+#include "doomdef.h"
+#include "g_game.h"
+#include "sounds.h"
+#include "info.h"
+#include "d_think.h"
+#include "m_argv.h"
+#include "z_zone.h"
+#include "w_wad.h"
+#include "m_menu.h"
+#include "m_misc.h"
+#include "f_finale.h"
+#include "st_stuff.h"
+#include "i_system.h"
+#include "p_setup.h"
+#include "r_data.h"
+#include "r_textures.h"
+#include "r_draw.h"
+#include "r_picformats.h"
+#include "r_things.h" // R_Char2Frame
+#include "r_sky.h"
+#include "fastcmp.h"
+#include "lua_script.h" // Reluctantly included for LUA_EvalMath
+#include "d_clisrv.h"
+
+#ifdef HWRENDER
+#include "hardware/hw_light.h"
+#endif
+
+#include "info.h"
+#include "dehacked.h"
+#include "doomdef.h" // MUSICSLOT_COMPATIBILITY, HWRENDER
+
+// Crazy word-reading stuff
+/// \todo Put these in a seperate file or something.
+mobjtype_t get_mobjtype(const char *word);
+statenum_t get_state(const char *word);
+spritenum_t get_sprite(const char *word);
+playersprite_t get_sprite2(const char *word);
+sfxenum_t get_sfx(const char *word);
+#ifdef MUSICSLOT_COMPATIBILITY
+UINT16 get_mus(const char *word, UINT8 dehacked_mode);
+#endif
+hudnum_t get_huditem(const char *word);
+menutype_t get_menutype(const char *word);
+//INT16 get_gametype(const char *word);
+//powertype_t get_power(const char *word);
+skincolornum_t get_skincolor(const char *word);
+
+void readwipes(MYFILE *f);
+void readmaincfg(MYFILE *f);
+void readconditionset(MYFILE *f, UINT8 setnum);
+void readunlockable(MYFILE *f, INT32 num);
+void readextraemblemdata(MYFILE *f, INT32 num);
+void reademblemdata(MYFILE *f, INT32 num);
+void readsound(MYFILE *f, INT32 num);
+void readframe(MYFILE *f, INT32 num);
+void readhuditem(MYFILE *f, INT32 num);
+void readmenu(MYFILE *f, INT32 num);
+void readtextprompt(MYFILE *f, INT32 num);
+void readcutscene(MYFILE *f, INT32 num);
+void readlevelheader(MYFILE *f, INT32 num);
+void readgametype(MYFILE *f, char *gtname);
+void readsprite2(MYFILE *f, INT32 num);
+void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2);
+#ifdef HWRENDER
+void readlight(MYFILE *f, INT32 num);
+#endif
+void readskincolor(MYFILE *f, INT32 num);
+void readthing(MYFILE *f, INT32 num);
+void readfreeslots(MYFILE *f);
+void readPlayer(MYFILE *f, INT32 num);
+void clear_levels(void);
+void clear_conditionsets(void);
+#endif
diff --git a/src/deh_tables.c b/src/deh_tables.c
new file mode 100644
index 0000000000000000000000000000000000000000..dd6d7d69ff722bedd843456a4b5a7963889a1269
--- /dev/null
+++ b/src/deh_tables.c
@@ -0,0 +1,5483 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  deh_tables.c
+/// \brief Define DeHackEd tables.
+
+#include "doomdef.h" // Constants
+#include "s_sound.h" // Sound constants
+#include "info.h" // Mobj, state, sprite, etc constants
+#include "m_menu.h" // Menu constants
+#include "y_inter.h" // Intermission constants
+#include "p_local.h" // some more constants
+#include "r_draw.h" // Colormap constants
+#include "lua_script.h" // Lua stuff
+#include "m_cond.h" // Emblem constants
+#include "v_video.h" // video flags (for lua)
+#include "i_sound.h" // musictype_t (for lua)
+#include "g_state.h" // gamestate_t (for lua)
+
+#include "deh_tables.h"
+
+char *FREE_STATES[NUMSTATEFREESLOTS];
+char *FREE_MOBJS[NUMMOBJFREESLOTS];
+char *FREE_SKINCOLORS[NUMCOLORFREESLOTS];
+UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway.
+
+const char NIGHTSGRADE_LIST[] = {
+	'F', // GRADE_F
+	'E', // GRADE_E
+	'D', // GRADE_D
+	'C', // GRADE_C
+	'B', // GRADE_B
+	'A', // GRADE_A
+	'S', // GRADE_S
+	'\0'
+};
+
+struct flickytypes_s FLICKYTYPES[] = {
+	{"BLUEBIRD", MT_FLICKY_01}, // Flicky (Flicky)
+	{"RABBIT",   MT_FLICKY_02}, // Pocky (1)
+	{"CHICKEN",  MT_FLICKY_03}, // Cucky (1)
+	{"SEAL",     MT_FLICKY_04}, // Rocky (1)
+	{"PIG",      MT_FLICKY_05}, // Picky (1)
+	{"CHIPMUNK", MT_FLICKY_06}, // Ricky (1)
+	{"PENGUIN",  MT_FLICKY_07}, // Pecky (1)
+	{"FISH",     MT_FLICKY_08}, // Nicky (CD)
+	{"RAM",      MT_FLICKY_09}, // Flocky (CD)
+	{"PUFFIN",   MT_FLICKY_10}, // Wicky (CD)
+	{"COW",      MT_FLICKY_11}, // Macky (SRB2)
+	{"RAT",      MT_FLICKY_12}, // Micky (2)
+	{"BEAR",     MT_FLICKY_13}, // Becky (2)
+	{"DOVE",     MT_FLICKY_14}, // Docky (CD)
+	{"CAT",      MT_FLICKY_15}, // Nyannyan (Flicky)
+	{"CANARY",   MT_FLICKY_16}, // Lucky (CD)
+	{"a", 0}, // End of normal flickies - a lower case character so will never fastcmp valid with uppercase tmp
+	//{"FLICKER",          MT_FLICKER}, // Flacky (SRB2)
+	{"SPIDER",   MT_SECRETFLICKY_01}, // Sticky (SRB2)
+	{"BAT",      MT_SECRETFLICKY_02}, // Backy (SRB2)
+	{"SEED",                MT_SEED}, // Seed (CD)
+	{NULL, 0}
+};
+
+// IMPORTANT!
+// DO NOT FORGET TO SYNC THIS LIST WITH THE ACTIONNUM ENUM IN INFO.H
+actionpointer_t actionpointers[] =
+{
+	{{A_Explode},                "A_EXPLODE"},
+	{{A_Pain},                   "A_PAIN"},
+	{{A_Fall},                   "A_FALL"},
+	{{A_MonitorPop},             "A_MONITORPOP"},
+	{{A_GoldMonitorPop},         "A_GOLDMONITORPOP"},
+	{{A_GoldMonitorRestore},     "A_GOLDMONITORRESTORE"},
+	{{A_GoldMonitorSparkle},     "A_GOLDMONITORSPARKLE"},
+	{{A_Look},                   "A_LOOK"},
+	{{A_Chase},                  "A_CHASE"},
+	{{A_FaceStabChase},          "A_FACESTABCHASE"},
+	{{A_FaceStabRev},            "A_FACESTABREV"},
+	{{A_FaceStabHurl},           "A_FACESTABHURL"},
+	{{A_FaceStabMiss},           "A_FACESTABMISS"},
+	{{A_StatueBurst},            "A_STATUEBURST"},
+	{{A_FaceTarget},             "A_FACETARGET"},
+	{{A_FaceTracer},             "A_FACETRACER"},
+	{{A_Scream},                 "A_SCREAM"},
+	{{A_BossDeath},              "A_BOSSDEATH"},
+	{{A_CustomPower},            "A_CUSTOMPOWER"},
+	{{A_GiveWeapon},             "A_GIVEWEAPON"},
+	{{A_RingBox},                "A_RINGBOX"},
+	{{A_Invincibility},          "A_INVINCIBILITY"},
+	{{A_SuperSneakers},          "A_SUPERSNEAKERS"},
+	{{A_BunnyHop},               "A_BUNNYHOP"},
+	{{A_BubbleSpawn},            "A_BUBBLESPAWN"},
+	{{A_FanBubbleSpawn},         "A_FANBUBBLESPAWN"},
+	{{A_BubbleRise},             "A_BUBBLERISE"},
+	{{A_BubbleCheck},            "A_BUBBLECHECK"},
+	{{A_AwardScore},             "A_AWARDSCORE"},
+	{{A_ExtraLife},              "A_EXTRALIFE"},
+	{{A_GiveShield},             "A_GIVESHIELD"},
+	{{A_GravityBox},             "A_GRAVITYBOX"},
+	{{A_ScoreRise},              "A_SCORERISE"},
+	{{A_AttractChase},           "A_ATTRACTCHASE"},
+	{{A_DropMine},               "A_DROPMINE"},
+	{{A_FishJump},               "A_FISHJUMP"},
+	{{A_ThrownRing},             "A_THROWNRING"},
+	{{A_SetSolidSteam},          "A_SETSOLIDSTEAM"},
+	{{A_UnsetSolidSteam},        "A_UNSETSOLIDSTEAM"},
+	{{A_SignSpin},               "A_SIGNSPIN"},
+	{{A_SignPlayer},             "A_SIGNPLAYER"},
+	{{A_OverlayThink},           "A_OVERLAYTHINK"},
+	{{A_JetChase},               "A_JETCHASE"},
+	{{A_JetbThink},              "A_JETBTHINK"},
+	{{A_JetgThink},              "A_JETGTHINK"},
+	{{A_JetgShoot},              "A_JETGSHOOT"},
+	{{A_ShootBullet},            "A_SHOOTBULLET"},
+	{{A_MinusDigging},           "A_MINUSDIGGING"},
+	{{A_MinusPopup},             "A_MINUSPOPUP"},
+	{{A_MinusCheck},             "A_MINUSCHECK"},
+	{{A_ChickenCheck},           "A_CHICKENCHECK"},
+	{{A_MouseThink},             "A_MOUSETHINK"},
+	{{A_DetonChase},             "A_DETONCHASE"},
+	{{A_CapeChase},              "A_CAPECHASE"},
+	{{A_RotateSpikeBall},        "A_ROTATESPIKEBALL"},
+	{{A_SlingAppear},            "A_SLINGAPPEAR"},
+	{{A_UnidusBall},             "A_UNIDUSBALL"},
+	{{A_RockSpawn},              "A_ROCKSPAWN"},
+	{{A_SetFuse},                "A_SETFUSE"},
+	{{A_CrawlaCommanderThink},   "A_CRAWLACOMMANDERTHINK"},
+	{{A_SmokeTrailer},           "A_SMOKETRAILER"},
+	{{A_RingExplode},            "A_RINGEXPLODE"},
+	{{A_OldRingExplode},         "A_OLDRINGEXPLODE"},
+	{{A_MixUp},                  "A_MIXUP"},
+	{{A_RecyclePowers},          "A_RECYCLEPOWERS"},
+	{{A_Boss1Chase},             "A_BOSS1CHASE"},
+	{{A_FocusTarget},            "A_FOCUSTARGET"},
+	{{A_Boss2Chase},             "A_BOSS2CHASE"},
+	{{A_Boss2Pogo},              "A_BOSS2POGO"},
+	{{A_BossZoom},               "A_BOSSZOOM"},
+	{{A_BossScream},             "A_BOSSSCREAM"},
+	{{A_Boss2TakeDamage},        "A_BOSS2TAKEDAMAGE"},
+	{{A_Boss7Chase},             "A_BOSS7CHASE"},
+	{{A_GoopSplat},              "A_GOOPSPLAT"},
+	{{A_Boss2PogoSFX},           "A_BOSS2POGOSFX"},
+	{{A_Boss2PogoTarget},        "A_BOSS2POGOTARGET"},
+	{{A_BossJetFume},            "A_BOSSJETFUME"},
+	{{A_EggmanBox},              "A_EGGMANBOX"},
+	{{A_TurretFire},             "A_TURRETFIRE"},
+	{{A_SuperTurretFire},        "A_SUPERTURRETFIRE"},
+	{{A_TurretStop},             "A_TURRETSTOP"},
+	{{A_JetJawRoam},             "A_JETJAWROAM"},
+	{{A_JetJawChomp},            "A_JETJAWCHOMP"},
+	{{A_PointyThink},            "A_POINTYTHINK"},
+	{{A_CheckBuddy},             "A_CHECKBUDDY"},
+	{{A_HoodFire},               "A_HOODFIRE"},
+	{{A_HoodThink},              "A_HOODTHINK"},
+	{{A_HoodFall},               "A_HOODFALL"},
+	{{A_ArrowBonks},             "A_ARROWBONKS"},
+	{{A_SnailerThink},           "A_SNAILERTHINK"},
+	{{A_SharpChase},             "A_SHARPCHASE"},
+	{{A_SharpSpin},              "A_SHARPSPIN"},
+	{{A_SharpDecel},             "A_SHARPDECEL"},
+	{{A_CrushstaceanWalk},       "A_CRUSHSTACEANWALK"},
+	{{A_CrushstaceanPunch},      "A_CRUSHSTACEANPUNCH"},
+	{{A_CrushclawAim},           "A_CRUSHCLAWAIM"},
+	{{A_CrushclawLaunch},        "A_CRUSHCLAWLAUNCH"},
+	{{A_VultureVtol},            "A_VULTUREVTOL"},
+	{{A_VultureCheck},           "A_VULTURECHECK"},
+	{{A_VultureHover},           "A_VULTUREHOVER"},
+	{{A_VultureBlast},           "A_VULTUREBLAST"},
+	{{A_VultureFly},             "A_VULTUREFLY"},
+	{{A_SkimChase},              "A_SKIMCHASE"},
+	{{A_1upThinker},             "A_1UPTHINKER"},
+	{{A_SkullAttack},            "A_SKULLATTACK"},
+	{{A_LobShot},                "A_LOBSHOT"},
+	{{A_FireShot},               "A_FIRESHOT"},
+	{{A_SuperFireShot},          "A_SUPERFIRESHOT"},
+	{{A_BossFireShot},           "A_BOSSFIRESHOT"},
+	{{A_Boss7FireMissiles},      "A_BOSS7FIREMISSILES"},
+	{{A_Boss1Laser},             "A_BOSS1LASER"},
+	{{A_Boss4Reverse},           "A_BOSS4REVERSE"},
+	{{A_Boss4SpeedUp},           "A_BOSS4SPEEDUP"},
+	{{A_Boss4Raise},             "A_BOSS4RAISE"},
+	{{A_SparkFollow},            "A_SPARKFOLLOW"},
+	{{A_BuzzFly},                "A_BUZZFLY"},
+	{{A_GuardChase},             "A_GUARDCHASE"},
+	{{A_EggShield},              "A_EGGSHIELD"},
+	{{A_SetReactionTime},        "A_SETREACTIONTIME"},
+	{{A_Boss1Spikeballs},        "A_BOSS1SPIKEBALLS"},
+	{{A_Boss3TakeDamage},        "A_BOSS3TAKEDAMAGE"},
+	{{A_Boss3Path},              "A_BOSS3PATH"},
+	{{A_Boss3ShockThink},        "A_BOSS3SHOCKTHINK"},
+	{{A_LinedefExecute},         "A_LINEDEFEXECUTE"},
+	{{A_PlaySeeSound},           "A_PLAYSEESOUND"},
+	{{A_PlayAttackSound},        "A_PLAYATTACKSOUND"},
+	{{A_PlayActiveSound},        "A_PLAYACTIVESOUND"},
+	{{A_SpawnObjectAbsolute},    "A_SPAWNOBJECTABSOLUTE"},
+	{{A_SpawnObjectRelative},    "A_SPAWNOBJECTRELATIVE"},
+	{{A_ChangeAngleRelative},    "A_CHANGEANGLERELATIVE"},
+	{{A_ChangeAngleAbsolute},    "A_CHANGEANGLEABSOLUTE"},
+	{{A_RollAngle},              "A_ROLLANGLE"},
+	{{A_ChangeRollAngleRelative},"A_CHANGEROLLANGLERELATIVE"},
+	{{A_ChangeRollAngleAbsolute},"A_CHANGEROLLANGLEABSOLUTE"},
+	{{A_PlaySound},              "A_PLAYSOUND"},
+	{{A_FindTarget},             "A_FINDTARGET"},
+	{{A_FindTracer},             "A_FINDTRACER"},
+	{{A_SetTics},                "A_SETTICS"},
+	{{A_SetRandomTics},          "A_SETRANDOMTICS"},
+	{{A_ChangeColorRelative},    "A_CHANGECOLORRELATIVE"},
+	{{A_ChangeColorAbsolute},    "A_CHANGECOLORABSOLUTE"},
+	{{A_Dye},                    "A_DYE"},
+	{{A_MoveRelative},           "A_MOVERELATIVE"},
+	{{A_MoveAbsolute},           "A_MOVEABSOLUTE"},
+	{{A_Thrust},                 "A_THRUST"},
+	{{A_ZThrust},                "A_ZTHRUST"},
+	{{A_SetTargetsTarget},       "A_SETTARGETSTARGET"},
+	{{A_SetObjectFlags},         "A_SETOBJECTFLAGS"},
+	{{A_SetObjectFlags2},        "A_SETOBJECTFLAGS2"},
+	{{A_RandomState},            "A_RANDOMSTATE"},
+	{{A_RandomStateRange},       "A_RANDOMSTATERANGE"},
+	{{A_DualAction},             "A_DUALACTION"},
+	{{A_RemoteAction},           "A_REMOTEACTION"},
+	{{A_ToggleFlameJet},         "A_TOGGLEFLAMEJET"},
+	{{A_OrbitNights},            "A_ORBITNIGHTS"},
+	{{A_GhostMe},                "A_GHOSTME"},
+	{{A_SetObjectState},         "A_SETOBJECTSTATE"},
+	{{A_SetObjectTypeState},     "A_SETOBJECTTYPESTATE"},
+	{{A_KnockBack},              "A_KNOCKBACK"},
+	{{A_PushAway},               "A_PUSHAWAY"},
+	{{A_RingDrain},              "A_RINGDRAIN"},
+	{{A_SplitShot},              "A_SPLITSHOT"},
+	{{A_MissileSplit},           "A_MISSILESPLIT"},
+	{{A_MultiShot},              "A_MULTISHOT"},
+	{{A_InstaLoop},              "A_INSTALOOP"},
+	{{A_Custom3DRotate},         "A_CUSTOM3DROTATE"},
+	{{A_SearchForPlayers},       "A_SEARCHFORPLAYERS"},
+	{{A_CheckRandom},            "A_CHECKRANDOM"},
+	{{A_CheckTargetRings},       "A_CHECKTARGETRINGS"},
+	{{A_CheckRings},             "A_CHECKRINGS"},
+	{{A_CheckTotalRings},        "A_CHECKTOTALRINGS"},
+	{{A_CheckHealth},            "A_CHECKHEALTH"},
+	{{A_CheckRange},             "A_CHECKRANGE"},
+	{{A_CheckHeight},            "A_CHECKHEIGHT"},
+	{{A_CheckTrueRange},         "A_CHECKTRUERANGE"},
+	{{A_CheckThingCount},        "A_CHECKTHINGCOUNT"},
+	{{A_CheckAmbush},            "A_CHECKAMBUSH"},
+	{{A_CheckCustomValue},       "A_CHECKCUSTOMVALUE"},
+	{{A_CheckCusValMemo},        "A_CHECKCUSVALMEMO"},
+	{{A_SetCustomValue},         "A_SETCUSTOMVALUE"},
+	{{A_UseCusValMemo},          "A_USECUSVALMEMO"},
+	{{A_RelayCustomValue},       "A_RELAYCUSTOMVALUE"},
+	{{A_CusValAction},           "A_CUSVALACTION"},
+	{{A_ForceStop},              "A_FORCESTOP"},
+	{{A_ForceWin},               "A_FORCEWIN"},
+	{{A_SpikeRetract},           "A_SPIKERETRACT"},
+	{{A_InfoState},              "A_INFOSTATE"},
+	{{A_Repeat},                 "A_REPEAT"},
+	{{A_SetScale},               "A_SETSCALE"},
+	{{A_RemoteDamage},           "A_REMOTEDAMAGE"},
+	{{A_HomingChase},            "A_HOMINGCHASE"},
+	{{A_TrapShot},               "A_TRAPSHOT"},
+	{{A_VileTarget},             "A_VILETARGET"},
+	{{A_VileAttack},             "A_VILEATTACK"},
+	{{A_VileFire},               "A_VILEFIRE"},
+	{{A_BrakChase},              "A_BRAKCHASE"},
+	{{A_BrakFireShot},           "A_BRAKFIRESHOT"},
+	{{A_BrakLobShot},            "A_BRAKLOBSHOT"},
+	{{A_NapalmScatter},          "A_NAPALMSCATTER"},
+	{{A_SpawnFreshCopy},         "A_SPAWNFRESHCOPY"},
+	{{A_FlickySpawn},            "A_FLICKYSPAWN"},
+	{{A_FlickyCenter},           "A_FLICKYCENTER"},
+	{{A_FlickyAim},              "A_FLICKYAIM"},
+	{{A_FlickyFly},              "A_FLICKYFLY"},
+	{{A_FlickySoar},             "A_FLICKYSOAR"},
+	{{A_FlickyCoast},            "A_FLICKYCOAST"},
+	{{A_FlickyHop},              "A_FLICKYHOP"},
+	{{A_FlickyFlounder},         "A_FLICKYFLOUNDER"},
+	{{A_FlickyCheck},            "A_FLICKYCHECK"},
+	{{A_FlickyHeightCheck},      "A_FLICKYHEIGHTCHECK"},
+	{{A_FlickyFlutter},          "A_FLICKYFLUTTER"},
+	{{A_FlameParticle},          "A_FLAMEPARTICLE"},
+	{{A_FadeOverlay},            "A_FADEOVERLAY"},
+	{{A_Boss5Jump},              "A_BOSS5JUMP"},
+	{{A_LightBeamReset},         "A_LIGHTBEAMRESET"},
+	{{A_MineExplode},            "A_MINEEXPLODE"},
+	{{A_MineRange},              "A_MINERANGE"},
+	{{A_ConnectToGround},        "A_CONNECTTOGROUND"},
+	{{A_SpawnParticleRelative},  "A_SPAWNPARTICLERELATIVE"},
+	{{A_MultiShotDist},          "A_MULTISHOTDIST"},
+	{{A_WhoCaresIfYourSonIsABee},"A_WHOCARESIFYOURSONISABEE"},
+	{{A_ParentTriesToSleep},     "A_PARENTTRIESTOSLEEP"},
+	{{A_CryingToMomma},          "A_CRYINGTOMOMMA"},
+	{{A_CheckFlags2},            "A_CHECKFLAGS2"},
+	{{A_Boss5FindWaypoint},      "A_BOSS5FINDWAYPOINT"},
+	{{A_DoNPCSkid},              "A_DONPCSKID"},
+	{{A_DoNPCPain},              "A_DONPCPAIN"},
+	{{A_PrepareRepeat},          "A_PREPAREREPEAT"},
+	{{A_Boss5ExtraRepeat},       "A_BOSS5EXTRAREPEAT"},
+	{{A_Boss5Calm},              "A_BOSS5CALM"},
+	{{A_Boss5CheckOnGround},     "A_BOSS5CHECKONGROUND"},
+	{{A_Boss5CheckFalling},      "A_BOSS5CHECKFALLING"},
+	{{A_Boss5PinchShot},         "A_BOSS5PINCHSHOT"},
+	{{A_Boss5MakeItRain},        "A_BOSS5MAKEITRAIN"},
+	{{A_Boss5MakeJunk},          "A_BOSS5MAKEJUNK"},
+	{{A_LookForBetter},          "A_LOOKFORBETTER"},
+	{{A_Boss5BombExplode},       "A_BOSS5BOMBEXPLODE"},
+	{{A_DustDevilThink},         "A_DUSTDEVILTHINK"},
+	{{A_TNTExplode},             "A_TNTEXPLODE"},
+	{{A_DebrisRandom},           "A_DEBRISRANDOM"},
+	{{A_TrainCameo},             "A_TRAINCAMEO"},
+	{{A_TrainCameo2},            "A_TRAINCAMEO2"},
+	{{A_CanarivoreGas},          "A_CANARIVOREGAS"},
+	{{A_KillSegments},           "A_KILLSEGMENTS"},
+	{{A_SnapperSpawn},           "A_SNAPPERSPAWN"},
+	{{A_SnapperThinker},         "A_SNAPPERTHINKER"},
+	{{A_SaloonDoorSpawn},        "A_SALOONDOORSPAWN"},
+	{{A_MinecartSparkThink},     "A_MINECARTSPARKTHINK"},
+	{{A_ModuloToState},          "A_MODULOTOSTATE"},
+	{{A_LavafallRocks},          "A_LAVAFALLROCKS"},
+	{{A_LavafallLava},           "A_LAVAFALLLAVA"},
+	{{A_FallingLavaCheck},       "A_FALLINGLAVACHECK"},
+	{{A_FireShrink},             "A_FIRESHRINK"},
+	{{A_SpawnPterabytes},        "A_SPAWNPTERABYTES"},
+	{{A_PterabyteHover},         "A_PTERABYTEHOVER"},
+	{{A_RolloutSpawn},           "A_ROLLOUTSPAWN"},
+	{{A_RolloutRock},            "A_ROLLOUTROCK"},
+	{{A_DragonbomberSpawn},      "A_DRAGONBOMBERSPAWN"},
+	{{A_DragonWing},             "A_DRAGONWING"},
+	{{A_DragonSegment},          "A_DRAGONSEGMENT"},
+	{{A_ChangeHeight},           "A_CHANGEHEIGHT"},
+	{{NULL},                     "NONE"},
+
+	// This NULL entry must be the last in the list
+	{{NULL},                   NULL},
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// CRAZY LIST OF STATE NAMES AND ALL FROM HERE DOWN
+// TODO: Make this all a seperate file or something, like part of info.c??
+// TODO: Read the list from a text lump in a WAD as necessary instead
+// or something, don't just keep it all in memory like this.
+// TODO: Make the lists public so we can start using actual mobj
+// and state names in warning and error messages! :D
+
+// RegEx to generate this from info.h: ^\tS_([^,]+), --> \t"S_\1",
+// I am leaving the prefixes solely for clarity to programmers,
+// because sadly no one remembers this place while searching for full state names.
+const char *const STATE_LIST[] = { // array length left dynamic for sanity testing later.
+	"S_NULL",
+	"S_UNKNOWN",
+	"S_INVISIBLE", // state for invisible sprite
+
+	"S_SPAWNSTATE",
+	"S_SEESTATE",
+	"S_MELEESTATE",
+	"S_MISSILESTATE",
+	"S_DEATHSTATE",
+	"S_XDEATHSTATE",
+	"S_RAISESTATE",
+
+	// Thok
+	"S_THOK",
+
+	// Player
+	"S_PLAY_STND",
+	"S_PLAY_WAIT",
+	"S_PLAY_WALK",
+	"S_PLAY_SKID",
+	"S_PLAY_RUN",
+	"S_PLAY_DASH",
+	"S_PLAY_PAIN",
+	"S_PLAY_STUN",
+	"S_PLAY_DEAD",
+	"S_PLAY_DRWN",
+	"S_PLAY_ROLL",
+	"S_PLAY_GASP",
+	"S_PLAY_JUMP",
+	"S_PLAY_SPRING",
+	"S_PLAY_FALL",
+	"S_PLAY_EDGE",
+	"S_PLAY_RIDE",
+
+	// CA2_SPINDASH
+	"S_PLAY_SPINDASH",
+
+	// CA_FLY/SWIM
+	"S_PLAY_FLY",
+	"S_PLAY_SWIM",
+	"S_PLAY_FLY_TIRED",
+
+	// CA_GLIDEANDCLIMB
+	"S_PLAY_GLIDE",
+	"S_PLAY_GLIDE_LANDING",
+	"S_PLAY_CLING",
+	"S_PLAY_CLIMB",
+
+	// CA_FLOAT/CA_SLOWFALL
+	"S_PLAY_FLOAT",
+	"S_PLAY_FLOAT_RUN",
+
+	// CA_BOUNCE
+	"S_PLAY_BOUNCE",
+	"S_PLAY_BOUNCE_LANDING",
+
+	// CA2_GUNSLINGER
+	"S_PLAY_FIRE",
+	"S_PLAY_FIRE_FINISH",
+
+	// CA_TWINSPIN
+	"S_PLAY_TWINSPIN",
+
+	// CA2_MELEE
+	"S_PLAY_MELEE",
+	"S_PLAY_MELEE_FINISH",
+	"S_PLAY_MELEE_LANDING",
+
+	// SF_SUPER
+	"S_PLAY_SUPER_TRANS1",
+	"S_PLAY_SUPER_TRANS2",
+	"S_PLAY_SUPER_TRANS3",
+	"S_PLAY_SUPER_TRANS4",
+	"S_PLAY_SUPER_TRANS5",
+	"S_PLAY_SUPER_TRANS6",
+
+	// technically the player goes here but it's an infinite tic state
+	"S_OBJPLACE_DUMMY",
+
+	// 1-Up Box Sprites overlay (uses player sprite)
+	"S_PLAY_BOX1",
+	"S_PLAY_BOX2",
+	"S_PLAY_ICON1",
+	"S_PLAY_ICON2",
+	"S_PLAY_ICON3",
+
+	// Level end sign overlay (uses player sprite)
+	"S_PLAY_SIGN",
+
+	// NiGHTS character (uses player sprite)
+	"S_PLAY_NIGHTS_TRANS1",
+	"S_PLAY_NIGHTS_TRANS2",
+	"S_PLAY_NIGHTS_TRANS3",
+	"S_PLAY_NIGHTS_TRANS4",
+	"S_PLAY_NIGHTS_TRANS5",
+	"S_PLAY_NIGHTS_TRANS6",
+	"S_PLAY_NIGHTS_STAND",
+	"S_PLAY_NIGHTS_FLOAT",
+	"S_PLAY_NIGHTS_FLY",
+	"S_PLAY_NIGHTS_DRILL",
+	"S_PLAY_NIGHTS_STUN",
+	"S_PLAY_NIGHTS_PULL",
+	"S_PLAY_NIGHTS_ATTACK",
+
+	// c:
+	"S_TAILSOVERLAY_STAND",
+	"S_TAILSOVERLAY_0DEGREES",
+	"S_TAILSOVERLAY_PLUS30DEGREES",
+	"S_TAILSOVERLAY_PLUS60DEGREES",
+	"S_TAILSOVERLAY_MINUS30DEGREES",
+	"S_TAILSOVERLAY_MINUS60DEGREES",
+	"S_TAILSOVERLAY_RUN",
+	"S_TAILSOVERLAY_FLY",
+	"S_TAILSOVERLAY_TIRE",
+	"S_TAILSOVERLAY_PAIN",
+	"S_TAILSOVERLAY_GASP",
+	"S_TAILSOVERLAY_EDGE",
+	"S_TAILSOVERLAY_DASH",
+
+	// [:
+	"S_JETFUMEFLASH",
+
+	// Blue Crawla
+	"S_POSS_STND",
+	"S_POSS_RUN1",
+	"S_POSS_RUN2",
+	"S_POSS_RUN3",
+	"S_POSS_RUN4",
+	"S_POSS_RUN5",
+	"S_POSS_RUN6",
+
+	// Red Crawla
+	"S_SPOS_STND",
+	"S_SPOS_RUN1",
+	"S_SPOS_RUN2",
+	"S_SPOS_RUN3",
+	"S_SPOS_RUN4",
+	"S_SPOS_RUN5",
+	"S_SPOS_RUN6",
+
+	// Greenflower Fish
+	"S_FISH1",
+	"S_FISH2",
+	"S_FISH3",
+	"S_FISH4",
+
+	// Buzz (Gold)
+	"S_BUZZLOOK1",
+	"S_BUZZLOOK2",
+	"S_BUZZFLY1",
+	"S_BUZZFLY2",
+
+	// Buzz (Red)
+	"S_RBUZZLOOK1",
+	"S_RBUZZLOOK2",
+	"S_RBUZZFLY1",
+	"S_RBUZZFLY2",
+
+	// Jetty-Syn Bomber
+	"S_JETBLOOK1",
+	"S_JETBLOOK2",
+	"S_JETBZOOM1",
+	"S_JETBZOOM2",
+
+	// Jetty-Syn Gunner
+	"S_JETGLOOK1",
+	"S_JETGLOOK2",
+	"S_JETGZOOM1",
+	"S_JETGZOOM2",
+	"S_JETGSHOOT1",
+	"S_JETGSHOOT2",
+
+	// Crawla Commander
+	"S_CCOMMAND1",
+	"S_CCOMMAND2",
+	"S_CCOMMAND3",
+	"S_CCOMMAND4",
+
+	// Deton
+	"S_DETON1",
+	"S_DETON2",
+	"S_DETON3",
+	"S_DETON4",
+	"S_DETON5",
+	"S_DETON6",
+	"S_DETON7",
+	"S_DETON8",
+	"S_DETON9",
+	"S_DETON10",
+	"S_DETON11",
+	"S_DETON12",
+	"S_DETON13",
+	"S_DETON14",
+	"S_DETON15",
+
+	// Skim Mine Dropper
+	"S_SKIM1",
+	"S_SKIM2",
+	"S_SKIM3",
+	"S_SKIM4",
+
+	// THZ Turret
+	"S_TURRET",
+	"S_TURRETFIRE",
+	"S_TURRETSHOCK1",
+	"S_TURRETSHOCK2",
+	"S_TURRETSHOCK3",
+	"S_TURRETSHOCK4",
+	"S_TURRETSHOCK5",
+	"S_TURRETSHOCK6",
+	"S_TURRETSHOCK7",
+	"S_TURRETSHOCK8",
+	"S_TURRETSHOCK9",
+
+	// Popup Turret
+	"S_TURRETLOOK",
+	"S_TURRETSEE",
+	"S_TURRETPOPUP1",
+	"S_TURRETPOPUP2",
+	"S_TURRETPOPUP3",
+	"S_TURRETPOPUP4",
+	"S_TURRETPOPUP5",
+	"S_TURRETPOPUP6",
+	"S_TURRETPOPUP7",
+	"S_TURRETPOPUP8",
+	"S_TURRETSHOOT",
+	"S_TURRETPOPDOWN1",
+	"S_TURRETPOPDOWN2",
+	"S_TURRETPOPDOWN3",
+	"S_TURRETPOPDOWN4",
+	"S_TURRETPOPDOWN5",
+	"S_TURRETPOPDOWN6",
+	"S_TURRETPOPDOWN7",
+	"S_TURRETPOPDOWN8",
+
+	// Spincushion
+	"S_SPINCUSHION_LOOK",
+	"S_SPINCUSHION_CHASE1",
+	"S_SPINCUSHION_CHASE2",
+	"S_SPINCUSHION_CHASE3",
+	"S_SPINCUSHION_CHASE4",
+	"S_SPINCUSHION_AIM1",
+	"S_SPINCUSHION_AIM2",
+	"S_SPINCUSHION_AIM3",
+	"S_SPINCUSHION_AIM4",
+	"S_SPINCUSHION_AIM5",
+	"S_SPINCUSHION_SPIN1",
+	"S_SPINCUSHION_SPIN2",
+	"S_SPINCUSHION_SPIN3",
+	"S_SPINCUSHION_SPIN4",
+	"S_SPINCUSHION_STOP1",
+	"S_SPINCUSHION_STOP2",
+	"S_SPINCUSHION_STOP3",
+	"S_SPINCUSHION_STOP4",
+
+	// Crushstacean
+	"S_CRUSHSTACEAN_ROAM1",
+	"S_CRUSHSTACEAN_ROAM2",
+	"S_CRUSHSTACEAN_ROAM3",
+	"S_CRUSHSTACEAN_ROAM4",
+	"S_CRUSHSTACEAN_ROAMPAUSE",
+	"S_CRUSHSTACEAN_PUNCH1",
+	"S_CRUSHSTACEAN_PUNCH2",
+	"S_CRUSHCLAW_AIM",
+	"S_CRUSHCLAW_OUT",
+	"S_CRUSHCLAW_STAY",
+	"S_CRUSHCLAW_IN",
+	"S_CRUSHCLAW_WAIT",
+	"S_CRUSHCHAIN",
+
+	// Banpyura
+	"S_BANPYURA_ROAM1",
+	"S_BANPYURA_ROAM2",
+	"S_BANPYURA_ROAM3",
+	"S_BANPYURA_ROAM4",
+	"S_BANPYURA_ROAMPAUSE",
+	"S_CDIAG1",
+	"S_CDIAG2",
+	"S_CDIAG3",
+	"S_CDIAG4",
+	"S_CDIAG5",
+	"S_CDIAG6",
+	"S_CDIAG7",
+	"S_CDIAG8",
+
+	// Jet Jaw
+	"S_JETJAW_ROAM1",
+	"S_JETJAW_ROAM2",
+	"S_JETJAW_ROAM3",
+	"S_JETJAW_ROAM4",
+	"S_JETJAW_ROAM5",
+	"S_JETJAW_ROAM6",
+	"S_JETJAW_ROAM7",
+	"S_JETJAW_ROAM8",
+	"S_JETJAW_CHOMP1",
+	"S_JETJAW_CHOMP2",
+	"S_JETJAW_CHOMP3",
+	"S_JETJAW_CHOMP4",
+	"S_JETJAW_CHOMP5",
+	"S_JETJAW_CHOMP6",
+	"S_JETJAW_CHOMP7",
+	"S_JETJAW_CHOMP8",
+	"S_JETJAW_CHOMP9",
+	"S_JETJAW_CHOMP10",
+	"S_JETJAW_CHOMP11",
+	"S_JETJAW_CHOMP12",
+	"S_JETJAW_CHOMP13",
+	"S_JETJAW_CHOMP14",
+	"S_JETJAW_CHOMP15",
+	"S_JETJAW_CHOMP16",
+	"S_JETJAW_SOUND",
+
+	// Snailer
+	"S_SNAILER1",
+	"S_SNAILER_FLICKY",
+
+	// Vulture
+	"S_VULTURE_STND",
+	"S_VULTURE_DRIFT",
+	"S_VULTURE_ZOOM1",
+	"S_VULTURE_ZOOM2",
+	"S_VULTURE_STUNNED",
+
+	// Pointy
+	"S_POINTY1",
+	"S_POINTYBALL1",
+
+	// Robo-Hood
+	"S_ROBOHOOD_LOOK",
+	"S_ROBOHOOD_STAND",
+	"S_ROBOHOOD_FIRE1",
+	"S_ROBOHOOD_FIRE2",
+	"S_ROBOHOOD_JUMP1",
+	"S_ROBOHOOD_JUMP2",
+	"S_ROBOHOOD_JUMP3",
+
+	// Castlebot Facestabber
+	"S_FACESTABBER_STND1",
+	"S_FACESTABBER_STND2",
+	"S_FACESTABBER_STND3",
+	"S_FACESTABBER_STND4",
+	"S_FACESTABBER_STND5",
+	"S_FACESTABBER_STND6",
+	"S_FACESTABBER_CHARGE1",
+	"S_FACESTABBER_CHARGE2",
+	"S_FACESTABBER_CHARGE3",
+	"S_FACESTABBER_CHARGE4",
+	"S_FACESTABBER_PAIN",
+	"S_FACESTABBER_DIE1",
+	"S_FACESTABBER_DIE2",
+	"S_FACESTABBER_DIE3",
+	"S_FACESTABBERSPEAR",
+
+	// Egg Guard
+	"S_EGGGUARD_STND",
+	"S_EGGGUARD_WALK1",
+	"S_EGGGUARD_WALK2",
+	"S_EGGGUARD_WALK3",
+	"S_EGGGUARD_WALK4",
+	"S_EGGGUARD_MAD1",
+	"S_EGGGUARD_MAD2",
+	"S_EGGGUARD_MAD3",
+	"S_EGGGUARD_RUN1",
+	"S_EGGGUARD_RUN2",
+	"S_EGGGUARD_RUN3",
+	"S_EGGGUARD_RUN4",
+
+	// Egg Shield for Egg Guard
+	"S_EGGSHIELD",
+	"S_EGGSHIELDBREAK",
+
+	// Green Snapper
+	"S_SNAPPER_SPAWN",
+	"S_SNAPPER_SPAWN2",
+	"S_GSNAPPER_STND",
+	"S_GSNAPPER1",
+	"S_GSNAPPER2",
+	"S_GSNAPPER3",
+	"S_GSNAPPER4",
+	"S_SNAPPER_XPLD",
+	"S_SNAPPER_LEG",
+	"S_SNAPPER_LEGRAISE",
+	"S_SNAPPER_HEAD",
+
+	// Minus
+	"S_MINUS_INIT",
+	"S_MINUS_STND",
+	"S_MINUS_DIGGING1",
+	"S_MINUS_DIGGING2",
+	"S_MINUS_DIGGING3",
+	"S_MINUS_DIGGING4",
+	"S_MINUS_BURST0",
+	"S_MINUS_BURST1",
+	"S_MINUS_BURST2",
+	"S_MINUS_BURST3",
+	"S_MINUS_BURST4",
+	"S_MINUS_BURST5",
+	"S_MINUS_POPUP",
+	"S_MINUS_AERIAL1",
+	"S_MINUS_AERIAL2",
+	"S_MINUS_AERIAL3",
+	"S_MINUS_AERIAL4",
+
+	// Minus dirt
+	"S_MINUSDIRT1",
+	"S_MINUSDIRT2",
+	"S_MINUSDIRT3",
+	"S_MINUSDIRT4",
+	"S_MINUSDIRT5",
+	"S_MINUSDIRT6",
+	"S_MINUSDIRT7",
+
+	// Spring Shell
+	"S_SSHELL_STND",
+	"S_SSHELL_RUN1",
+	"S_SSHELL_RUN2",
+	"S_SSHELL_RUN3",
+	"S_SSHELL_RUN4",
+	"S_SSHELL_SPRING1",
+	"S_SSHELL_SPRING2",
+	"S_SSHELL_SPRING3",
+	"S_SSHELL_SPRING4",
+
+	// Spring Shell (yellow)
+	"S_YSHELL_STND",
+	"S_YSHELL_RUN1",
+	"S_YSHELL_RUN2",
+	"S_YSHELL_RUN3",
+	"S_YSHELL_RUN4",
+	"S_YSHELL_SPRING1",
+	"S_YSHELL_SPRING2",
+	"S_YSHELL_SPRING3",
+	"S_YSHELL_SPRING4",
+
+	// Unidus
+	"S_UNIDUS_STND",
+	"S_UNIDUS_RUN",
+	"S_UNIDUS_BALL",
+
+	// Canarivore
+	"S_CANARIVORE_LOOK",
+	"S_CANARIVORE_AWAKEN1",
+	"S_CANARIVORE_AWAKEN2",
+	"S_CANARIVORE_AWAKEN3",
+	"S_CANARIVORE_GAS1",
+	"S_CANARIVORE_GAS2",
+	"S_CANARIVORE_GAS3",
+	"S_CANARIVORE_GAS4",
+	"S_CANARIVORE_GAS5",
+	"S_CANARIVORE_GASREPEAT",
+	"S_CANARIVORE_CLOSE1",
+	"S_CANARIVORE_CLOSE2",
+	"S_CANARIVOREGAS_1",
+	"S_CANARIVOREGAS_2",
+	"S_CANARIVOREGAS_3",
+	"S_CANARIVOREGAS_4",
+	"S_CANARIVOREGAS_5",
+	"S_CANARIVOREGAS_6",
+	"S_CANARIVOREGAS_7",
+	"S_CANARIVOREGAS_8",
+
+	// Pyre Fly
+	"S_PYREFLY_FLY",
+	"S_PYREFLY_BURN",
+	"S_PYREFIRE1",
+	"S_PYREFIRE2",
+
+	// Pterabyte
+	"S_PTERABYTESPAWNER",
+	"S_PTERABYTEWAYPOINT",
+	"S_PTERABYTE_FLY1",
+	"S_PTERABYTE_FLY2",
+	"S_PTERABYTE_FLY3",
+	"S_PTERABYTE_FLY4",
+	"S_PTERABYTE_SWOOPDOWN",
+	"S_PTERABYTE_SWOOPUP",
+
+	// Dragonbomber
+	"S_DRAGONBOMBER",
+	"S_DRAGONWING1",
+	"S_DRAGONWING2",
+	"S_DRAGONWING3",
+	"S_DRAGONWING4",
+	"S_DRAGONTAIL_LOADED",
+	"S_DRAGONTAIL_EMPTY",
+	"S_DRAGONTAIL_EMPTYLOOP",
+	"S_DRAGONTAIL_RELOAD",
+	"S_DRAGONMINE",
+	"S_DRAGONMINE_LAND1",
+	"S_DRAGONMINE_LAND2",
+	"S_DRAGONMINE_SLOWFLASH1",
+	"S_DRAGONMINE_SLOWFLASH2",
+	"S_DRAGONMINE_SLOWLOOP",
+	"S_DRAGONMINE_FASTFLASH1",
+	"S_DRAGONMINE_FASTFLASH2",
+	"S_DRAGONMINE_FASTLOOP",
+
+	// Boss Explosion
+	"S_BOSSEXPLODE",
+
+	// S3&K Boss Explosion
+	"S_SONIC3KBOSSEXPLOSION1",
+	"S_SONIC3KBOSSEXPLOSION2",
+	"S_SONIC3KBOSSEXPLOSION3",
+	"S_SONIC3KBOSSEXPLOSION4",
+	"S_SONIC3KBOSSEXPLOSION5",
+	"S_SONIC3KBOSSEXPLOSION6",
+
+	"S_JETFUME1",
+
+	// Boss 1
+	"S_EGGMOBILE_STND",
+	"S_EGGMOBILE_ROFL",
+	"S_EGGMOBILE_LATK1",
+	"S_EGGMOBILE_LATK2",
+	"S_EGGMOBILE_LATK3",
+	"S_EGGMOBILE_LATK4",
+	"S_EGGMOBILE_LATK5",
+	"S_EGGMOBILE_LATK6",
+	"S_EGGMOBILE_LATK7",
+	"S_EGGMOBILE_LATK8",
+	"S_EGGMOBILE_LATK9",
+	"S_EGGMOBILE_RATK1",
+	"S_EGGMOBILE_RATK2",
+	"S_EGGMOBILE_RATK3",
+	"S_EGGMOBILE_RATK4",
+	"S_EGGMOBILE_RATK5",
+	"S_EGGMOBILE_RATK6",
+	"S_EGGMOBILE_RATK7",
+	"S_EGGMOBILE_RATK8",
+	"S_EGGMOBILE_RATK9",
+	"S_EGGMOBILE_PANIC1",
+	"S_EGGMOBILE_PANIC2",
+	"S_EGGMOBILE_PANIC3",
+	"S_EGGMOBILE_PANIC4",
+	"S_EGGMOBILE_PANIC5",
+	"S_EGGMOBILE_PANIC6",
+	"S_EGGMOBILE_PANIC7",
+	"S_EGGMOBILE_PANIC8",
+	"S_EGGMOBILE_PANIC9",
+	"S_EGGMOBILE_PANIC10",
+	"S_EGGMOBILE_PANIC11",
+	"S_EGGMOBILE_PANIC12",
+	"S_EGGMOBILE_PANIC13",
+	"S_EGGMOBILE_PANIC14",
+	"S_EGGMOBILE_PANIC15",
+	"S_EGGMOBILE_PAIN",
+	"S_EGGMOBILE_PAIN2",
+	"S_EGGMOBILE_DIE1",
+	"S_EGGMOBILE_DIE2",
+	"S_EGGMOBILE_DIE3",
+	"S_EGGMOBILE_DIE4",
+	"S_EGGMOBILE_FLEE1",
+	"S_EGGMOBILE_FLEE2",
+	"S_EGGMOBILE_BALL",
+	"S_EGGMOBILE_TARGET",
+
+	"S_BOSSEGLZ1",
+	"S_BOSSEGLZ2",
+
+	// Boss 2
+	"S_EGGMOBILE2_STND",
+	"S_EGGMOBILE2_POGO1",
+	"S_EGGMOBILE2_POGO2",
+	"S_EGGMOBILE2_POGO3",
+	"S_EGGMOBILE2_POGO4",
+	"S_EGGMOBILE2_POGO5",
+	"S_EGGMOBILE2_POGO6",
+	"S_EGGMOBILE2_POGO7",
+	"S_EGGMOBILE2_PAIN",
+	"S_EGGMOBILE2_PAIN2",
+	"S_EGGMOBILE2_DIE1",
+	"S_EGGMOBILE2_DIE2",
+	"S_EGGMOBILE2_DIE3",
+	"S_EGGMOBILE2_DIE4",
+	"S_EGGMOBILE2_FLEE1",
+	"S_EGGMOBILE2_FLEE2",
+
+	"S_BOSSTANK1",
+	"S_BOSSTANK2",
+	"S_BOSSSPIGOT",
+
+	// Boss 2 Goop
+	"S_GOOP1",
+	"S_GOOP2",
+	"S_GOOP3",
+	"S_GOOPTRAIL",
+
+	// Boss 3
+	"S_EGGMOBILE3_STND",
+	"S_EGGMOBILE3_SHOCK",
+	"S_EGGMOBILE3_ATK1",
+	"S_EGGMOBILE3_ATK2",
+	"S_EGGMOBILE3_ATK3A",
+	"S_EGGMOBILE3_ATK3B",
+	"S_EGGMOBILE3_ATK3C",
+	"S_EGGMOBILE3_ATK3D",
+	"S_EGGMOBILE3_ATK4",
+	"S_EGGMOBILE3_ATK5",
+	"S_EGGMOBILE3_ROFL",
+	"S_EGGMOBILE3_PAIN",
+	"S_EGGMOBILE3_PAIN2",
+	"S_EGGMOBILE3_DIE1",
+	"S_EGGMOBILE3_DIE2",
+	"S_EGGMOBILE3_DIE3",
+	"S_EGGMOBILE3_DIE4",
+	"S_EGGMOBILE3_FLEE1",
+	"S_EGGMOBILE3_FLEE2",
+
+	// Boss 3 Pinch
+	"S_FAKEMOBILE_INIT",
+	"S_FAKEMOBILE",
+	"S_FAKEMOBILE_ATK1",
+	"S_FAKEMOBILE_ATK2",
+	"S_FAKEMOBILE_ATK3A",
+	"S_FAKEMOBILE_ATK3B",
+	"S_FAKEMOBILE_ATK3C",
+	"S_FAKEMOBILE_ATK3D",
+	"S_FAKEMOBILE_DIE1",
+	"S_FAKEMOBILE_DIE2",
+
+	"S_BOSSSEBH1",
+	"S_BOSSSEBH2",
+
+	// Boss 3 Shockwave
+	"S_SHOCKWAVE1",
+	"S_SHOCKWAVE2",
+
+	// Boss 4
+	"S_EGGMOBILE4_STND",
+	"S_EGGMOBILE4_LATK1",
+	"S_EGGMOBILE4_LATK2",
+	"S_EGGMOBILE4_LATK3",
+	"S_EGGMOBILE4_LATK4",
+	"S_EGGMOBILE4_LATK5",
+	"S_EGGMOBILE4_LATK6",
+	"S_EGGMOBILE4_RATK1",
+	"S_EGGMOBILE4_RATK2",
+	"S_EGGMOBILE4_RATK3",
+	"S_EGGMOBILE4_RATK4",
+	"S_EGGMOBILE4_RATK5",
+	"S_EGGMOBILE4_RATK6",
+	"S_EGGMOBILE4_RAISE1",
+	"S_EGGMOBILE4_RAISE2",
+	"S_EGGMOBILE4_PAIN1",
+	"S_EGGMOBILE4_PAIN2",
+	"S_EGGMOBILE4_DIE1",
+	"S_EGGMOBILE4_DIE2",
+	"S_EGGMOBILE4_DIE3",
+	"S_EGGMOBILE4_DIE4",
+	"S_EGGMOBILE4_FLEE1",
+	"S_EGGMOBILE4_FLEE2",
+	"S_EGGMOBILE4_MACE",
+	"S_EGGMOBILE4_MACE_DIE1",
+	"S_EGGMOBILE4_MACE_DIE2",
+	"S_EGGMOBILE4_MACE_DIE3",
+
+	// Boss 4 jet flame
+	"S_JETFLAME",
+
+	// Boss 4 Spectator Eggrobo
+	"S_EGGROBO1_STND",
+	"S_EGGROBO1_BSLAP1",
+	"S_EGGROBO1_BSLAP2",
+	"S_EGGROBO1_PISSED",
+
+	// Boss 4 Spectator Eggrobo jet flame
+	"S_EGGROBOJET",
+
+	// Boss 5
+	"S_FANG_SETUP",
+	"S_FANG_INTRO0",
+	"S_FANG_INTRO1",
+	"S_FANG_INTRO2",
+	"S_FANG_INTRO3",
+	"S_FANG_INTRO4",
+	"S_FANG_INTRO5",
+	"S_FANG_INTRO6",
+	"S_FANG_INTRO7",
+	"S_FANG_INTRO8",
+	"S_FANG_INTRO9",
+	"S_FANG_INTRO10",
+	"S_FANG_INTRO11",
+	"S_FANG_INTRO12",
+	"S_FANG_CLONE1",
+	"S_FANG_CLONE2",
+	"S_FANG_CLONE3",
+	"S_FANG_CLONE4",
+	"S_FANG_IDLE0",
+	"S_FANG_IDLE1",
+	"S_FANG_IDLE2",
+	"S_FANG_IDLE3",
+	"S_FANG_IDLE4",
+	"S_FANG_IDLE5",
+	"S_FANG_IDLE6",
+	"S_FANG_IDLE7",
+	"S_FANG_IDLE8",
+	"S_FANG_PAIN1",
+	"S_FANG_PAIN2",
+	"S_FANG_PATHINGSTART1",
+	"S_FANG_PATHINGSTART2",
+	"S_FANG_PATHING",
+	"S_FANG_BOUNCE1",
+	"S_FANG_BOUNCE2",
+	"S_FANG_BOUNCE3",
+	"S_FANG_BOUNCE4",
+	"S_FANG_FALL1",
+	"S_FANG_FALL2",
+	"S_FANG_CHECKPATH1",
+	"S_FANG_CHECKPATH2",
+	"S_FANG_PATHINGCONT1",
+	"S_FANG_PATHINGCONT2",
+	"S_FANG_PATHINGCONT3",
+	"S_FANG_SKID1",
+	"S_FANG_SKID2",
+	"S_FANG_SKID3",
+	"S_FANG_CHOOSEATTACK",
+	"S_FANG_FIRESTART1",
+	"S_FANG_FIRESTART2",
+	"S_FANG_FIRE1",
+	"S_FANG_FIRE2",
+	"S_FANG_FIRE3",
+	"S_FANG_FIRE4",
+	"S_FANG_FIREREPEAT",
+	"S_FANG_LOBSHOT0",
+	"S_FANG_LOBSHOT1",
+	"S_FANG_LOBSHOT2",
+	"S_FANG_WAIT1",
+	"S_FANG_WAIT2",
+	"S_FANG_WALLHIT",
+	"S_FANG_PINCHPATHINGSTART1",
+	"S_FANG_PINCHPATHINGSTART2",
+	"S_FANG_PINCHPATHING",
+	"S_FANG_PINCHBOUNCE0",
+	"S_FANG_PINCHBOUNCE1",
+	"S_FANG_PINCHBOUNCE2",
+	"S_FANG_PINCHBOUNCE3",
+	"S_FANG_PINCHBOUNCE4",
+	"S_FANG_PINCHFALL0",
+	"S_FANG_PINCHFALL1",
+	"S_FANG_PINCHFALL2",
+	"S_FANG_PINCHSKID1",
+	"S_FANG_PINCHSKID2",
+	"S_FANG_PINCHLOBSHOT0",
+	"S_FANG_PINCHLOBSHOT1",
+	"S_FANG_PINCHLOBSHOT2",
+	"S_FANG_PINCHLOBSHOT3",
+	"S_FANG_PINCHLOBSHOT4",
+	"S_FANG_DIE1",
+	"S_FANG_DIE2",
+	"S_FANG_DIE3",
+	"S_FANG_DIE4",
+	"S_FANG_DIE5",
+	"S_FANG_DIE6",
+	"S_FANG_DIE7",
+	"S_FANG_DIE8",
+	"S_FANG_FLEEPATHING1",
+	"S_FANG_FLEEPATHING2",
+	"S_FANG_FLEEBOUNCE1",
+	"S_FANG_FLEEBOUNCE2",
+	"S_FANG_KO",
+
+	"S_BROKENROBOTRANDOM",
+	"S_BROKENROBOTA",
+	"S_BROKENROBOTB",
+	"S_BROKENROBOTC",
+	"S_BROKENROBOTD",
+	"S_BROKENROBOTE",
+	"S_BROKENROBOTF",
+
+	"S_ALART1",
+	"S_ALART2",
+
+	"S_VWREF",
+	"S_VWREB",
+
+	"S_PROJECTORLIGHT1",
+	"S_PROJECTORLIGHT2",
+	"S_PROJECTORLIGHT3",
+	"S_PROJECTORLIGHT4",
+	"S_PROJECTORLIGHT5",
+
+	"S_FBOMB1",
+	"S_FBOMB2",
+	"S_FBOMB_EXPL1",
+	"S_FBOMB_EXPL2",
+	"S_FBOMB_EXPL3",
+	"S_FBOMB_EXPL4",
+	"S_FBOMB_EXPL5",
+	"S_FBOMB_EXPL6",
+	"S_TNTDUST_1",
+	"S_TNTDUST_2",
+	"S_TNTDUST_3",
+	"S_TNTDUST_4",
+	"S_TNTDUST_5",
+	"S_TNTDUST_6",
+	"S_TNTDUST_7",
+	"S_TNTDUST_8",
+	"S_FSGNA",
+	"S_FSGNB",
+	"S_FSGNC",
+	"S_FSGND",
+
+	// Black Eggman (Boss 7)
+	"S_BLACKEGG_STND",
+	"S_BLACKEGG_STND2",
+	"S_BLACKEGG_WALK1",
+	"S_BLACKEGG_WALK2",
+	"S_BLACKEGG_WALK3",
+	"S_BLACKEGG_WALK4",
+	"S_BLACKEGG_WALK5",
+	"S_BLACKEGG_WALK6",
+	"S_BLACKEGG_SHOOT1",
+	"S_BLACKEGG_SHOOT2",
+	"S_BLACKEGG_PAIN1",
+	"S_BLACKEGG_PAIN2",
+	"S_BLACKEGG_PAIN3",
+	"S_BLACKEGG_PAIN4",
+	"S_BLACKEGG_PAIN5",
+	"S_BLACKEGG_PAIN6",
+	"S_BLACKEGG_PAIN7",
+	"S_BLACKEGG_PAIN8",
+	"S_BLACKEGG_PAIN9",
+	"S_BLACKEGG_PAIN10",
+	"S_BLACKEGG_PAIN11",
+	"S_BLACKEGG_PAIN12",
+	"S_BLACKEGG_PAIN13",
+	"S_BLACKEGG_PAIN14",
+	"S_BLACKEGG_PAIN15",
+	"S_BLACKEGG_PAIN16",
+	"S_BLACKEGG_PAIN17",
+	"S_BLACKEGG_PAIN18",
+	"S_BLACKEGG_PAIN19",
+	"S_BLACKEGG_PAIN20",
+	"S_BLACKEGG_PAIN21",
+	"S_BLACKEGG_PAIN22",
+	"S_BLACKEGG_PAIN23",
+	"S_BLACKEGG_PAIN24",
+	"S_BLACKEGG_PAIN25",
+	"S_BLACKEGG_PAIN26",
+	"S_BLACKEGG_PAIN27",
+	"S_BLACKEGG_PAIN28",
+	"S_BLACKEGG_PAIN29",
+	"S_BLACKEGG_PAIN30",
+	"S_BLACKEGG_PAIN31",
+	"S_BLACKEGG_PAIN32",
+	"S_BLACKEGG_PAIN33",
+	"S_BLACKEGG_PAIN34",
+	"S_BLACKEGG_PAIN35",
+	"S_BLACKEGG_HITFACE1",
+	"S_BLACKEGG_HITFACE2",
+	"S_BLACKEGG_HITFACE3",
+	"S_BLACKEGG_HITFACE4",
+	"S_BLACKEGG_DIE1",
+	"S_BLACKEGG_DIE2",
+	"S_BLACKEGG_DIE3",
+	"S_BLACKEGG_DIE4",
+	"S_BLACKEGG_DIE5",
+	"S_BLACKEGG_MISSILE1",
+	"S_BLACKEGG_MISSILE2",
+	"S_BLACKEGG_MISSILE3",
+	"S_BLACKEGG_GOOP",
+	"S_BLACKEGG_JUMP1",
+	"S_BLACKEGG_JUMP2",
+	"S_BLACKEGG_DESTROYPLAT1",
+	"S_BLACKEGG_DESTROYPLAT2",
+	"S_BLACKEGG_DESTROYPLAT3",
+
+	"S_BLACKEGG_HELPER", // Collision helper
+
+	"S_BLACKEGG_GOOP1",
+	"S_BLACKEGG_GOOP2",
+	"S_BLACKEGG_GOOP3",
+	"S_BLACKEGG_GOOP4",
+	"S_BLACKEGG_GOOP5",
+	"S_BLACKEGG_GOOP6",
+	"S_BLACKEGG_GOOP7",
+
+	"S_BLACKEGG_MISSILE",
+
+	// New Very-Last-Minute 2.1 Brak Eggman (Cy-Brak-demon)
+	"S_CYBRAKDEMON_IDLE",
+	"S_CYBRAKDEMON_WALK1",
+	"S_CYBRAKDEMON_WALK2",
+	"S_CYBRAKDEMON_WALK3",
+	"S_CYBRAKDEMON_WALK4",
+	"S_CYBRAKDEMON_WALK5",
+	"S_CYBRAKDEMON_WALK6",
+	"S_CYBRAKDEMON_CHOOSE_ATTACK1",
+	"S_CYBRAKDEMON_MISSILE_ATTACK1", // Aim
+	"S_CYBRAKDEMON_MISSILE_ATTACK2", // Fire
+	"S_CYBRAKDEMON_MISSILE_ATTACK3", // Aim
+	"S_CYBRAKDEMON_MISSILE_ATTACK4", // Fire
+	"S_CYBRAKDEMON_MISSILE_ATTACK5", // Aim
+	"S_CYBRAKDEMON_MISSILE_ATTACK6", // Fire
+	"S_CYBRAKDEMON_FLAME_ATTACK1", // Reset
+	"S_CYBRAKDEMON_FLAME_ATTACK2", // Aim
+	"S_CYBRAKDEMON_FLAME_ATTACK3", // Fire
+	"S_CYBRAKDEMON_FLAME_ATTACK4", // Loop
+	"S_CYBRAKDEMON_CHOOSE_ATTACK2",
+	"S_CYBRAKDEMON_VILE_ATTACK1",
+	"S_CYBRAKDEMON_VILE_ATTACK2",
+	"S_CYBRAKDEMON_VILE_ATTACK3",
+	"S_CYBRAKDEMON_VILE_ATTACK4",
+	"S_CYBRAKDEMON_VILE_ATTACK5",
+	"S_CYBRAKDEMON_VILE_ATTACK6",
+	"S_CYBRAKDEMON_NAPALM_ATTACK1",
+	"S_CYBRAKDEMON_NAPALM_ATTACK2",
+	"S_CYBRAKDEMON_NAPALM_ATTACK3",
+	"S_CYBRAKDEMON_FINISH_ATTACK1", // If just attacked, remove MF2_FRET w/out going back to spawnstate
+	"S_CYBRAKDEMON_FINISH_ATTACK2", // Force a delay between attacks so you don't get bombarded with them back-to-back
+	"S_CYBRAKDEMON_PAIN1",
+	"S_CYBRAKDEMON_PAIN2",
+	"S_CYBRAKDEMON_PAIN3",
+	"S_CYBRAKDEMON_DIE1",
+	"S_CYBRAKDEMON_DIE2",
+	"S_CYBRAKDEMON_DIE3",
+	"S_CYBRAKDEMON_DIE4",
+	"S_CYBRAKDEMON_DIE5",
+	"S_CYBRAKDEMON_DIE6",
+	"S_CYBRAKDEMON_DIE7",
+	"S_CYBRAKDEMON_DIE8",
+	"S_CYBRAKDEMON_DEINVINCIBLERIZE",
+	"S_CYBRAKDEMON_INVINCIBLERIZE",
+
+	"S_CYBRAKDEMONMISSILE",
+	"S_CYBRAKDEMONMISSILE_EXPLODE1",
+	"S_CYBRAKDEMONMISSILE_EXPLODE2",
+	"S_CYBRAKDEMONMISSILE_EXPLODE3",
+
+	"S_CYBRAKDEMONFLAMESHOT_FLY1",
+	"S_CYBRAKDEMONFLAMESHOT_FLY2",
+	"S_CYBRAKDEMONFLAMESHOT_FLY3",
+	"S_CYBRAKDEMONFLAMESHOT_DIE",
+
+	"S_CYBRAKDEMONFLAMEREST",
+
+	"S_CYBRAKDEMONELECTRICBARRIER_INIT1",
+	"S_CYBRAKDEMONELECTRICBARRIER_INIT2",
+	"S_CYBRAKDEMONELECTRICBARRIER_PLAYSOUND",
+	"S_CYBRAKDEMONELECTRICBARRIER1",
+	"S_CYBRAKDEMONELECTRICBARRIER2",
+	"S_CYBRAKDEMONELECTRICBARRIER3",
+	"S_CYBRAKDEMONELECTRICBARRIER4",
+	"S_CYBRAKDEMONELECTRICBARRIER5",
+	"S_CYBRAKDEMONELECTRICBARRIER6",
+	"S_CYBRAKDEMONELECTRICBARRIER7",
+	"S_CYBRAKDEMONELECTRICBARRIER8",
+	"S_CYBRAKDEMONELECTRICBARRIER9",
+	"S_CYBRAKDEMONELECTRICBARRIER10",
+	"S_CYBRAKDEMONELECTRICBARRIER11",
+	"S_CYBRAKDEMONELECTRICBARRIER12",
+	"S_CYBRAKDEMONELECTRICBARRIER13",
+	"S_CYBRAKDEMONELECTRICBARRIER14",
+	"S_CYBRAKDEMONELECTRICBARRIER15",
+	"S_CYBRAKDEMONELECTRICBARRIER16",
+	"S_CYBRAKDEMONELECTRICBARRIER17",
+	"S_CYBRAKDEMONELECTRICBARRIER18",
+	"S_CYBRAKDEMONELECTRICBARRIER19",
+	"S_CYBRAKDEMONELECTRICBARRIER20",
+	"S_CYBRAKDEMONELECTRICBARRIER21",
+	"S_CYBRAKDEMONELECTRICBARRIER22",
+	"S_CYBRAKDEMONELECTRICBARRIER23",
+	"S_CYBRAKDEMONELECTRICBARRIER24",
+	"S_CYBRAKDEMONELECTRICBARRIER_DIE1",
+	"S_CYBRAKDEMONELECTRICBARRIER_DIE2",
+	"S_CYBRAKDEMONELECTRICBARRIER_DIE3",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMCHECK",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMSUCCESS",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMCHOOSE",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM1",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM2",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM3",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM4",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM5",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM6",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM7",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM8",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM9",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM10",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM11",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM12",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMFAIL",
+	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMLOOP",
+	"S_CYBRAKDEMONELECTRICBARRIER_REVIVE1",
+	"S_CYBRAKDEMONELECTRICBARRIER_REVIVE2",
+	"S_CYBRAKDEMONELECTRICBARRIER_REVIVE3",
+
+	"S_CYBRAKDEMONTARGETRETICULE1",
+	"S_CYBRAKDEMONTARGETRETICULE2",
+	"S_CYBRAKDEMONTARGETRETICULE3",
+	"S_CYBRAKDEMONTARGETRETICULE4",
+	"S_CYBRAKDEMONTARGETRETICULE5",
+	"S_CYBRAKDEMONTARGETRETICULE6",
+	"S_CYBRAKDEMONTARGETRETICULE7",
+	"S_CYBRAKDEMONTARGETRETICULE8",
+	"S_CYBRAKDEMONTARGETRETICULE9",
+	"S_CYBRAKDEMONTARGETRETICULE10",
+	"S_CYBRAKDEMONTARGETRETICULE11",
+	"S_CYBRAKDEMONTARGETRETICULE12",
+	"S_CYBRAKDEMONTARGETRETICULE13",
+	"S_CYBRAKDEMONTARGETRETICULE14",
+
+	"S_CYBRAKDEMONTARGETDOT",
+
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY1",
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY2",
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY3",
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY4",
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE1", // Explode
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE2", // Outer ring
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE3", // Center
+	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE4", // Sound
+
+	"S_CYBRAKDEMONNAPALMBOMBSMALL",
+	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE1", // Explode
+	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE2", // Outer ring
+	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE3", // Inner ring
+	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE4", // Center
+	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE5", // Sound
+
+	"S_CYBRAKDEMONNAPALMFLAME_FLY1",
+	"S_CYBRAKDEMONNAPALMFLAME_FLY2",
+	"S_CYBRAKDEMONNAPALMFLAME_FLY3",
+	"S_CYBRAKDEMONNAPALMFLAME_FLY4",
+	"S_CYBRAKDEMONNAPALMFLAME_FLY5",
+	"S_CYBRAKDEMONNAPALMFLAME_FLY6",
+	"S_CYBRAKDEMONNAPALMFLAME_DIE",
+
+	"S_CYBRAKDEMONVILEEXPLOSION1",
+	"S_CYBRAKDEMONVILEEXPLOSION2",
+	"S_CYBRAKDEMONVILEEXPLOSION3",
+
+	// Metal Sonic (Race)
+	"S_METALSONIC_RACE",
+	// Metal Sonic (Battle)
+	"S_METALSONIC_FLOAT",
+	"S_METALSONIC_VECTOR",
+	"S_METALSONIC_STUN",
+	"S_METALSONIC_RAISE",
+	"S_METALSONIC_GATHER",
+	"S_METALSONIC_DASH",
+	"S_METALSONIC_BOUNCE",
+	"S_METALSONIC_BADBOUNCE",
+	"S_METALSONIC_SHOOT",
+	"S_METALSONIC_PAIN",
+	"S_METALSONIC_DEATH1",
+	"S_METALSONIC_DEATH2",
+	"S_METALSONIC_DEATH3",
+	"S_METALSONIC_DEATH4",
+	"S_METALSONIC_FLEE1",
+	"S_METALSONIC_FLEE2",
+
+	"S_MSSHIELD_F1",
+	"S_MSSHIELD_F2",
+
+	// Ring
+	"S_RING",
+
+	// Blue Sphere for special stages
+	"S_BLUESPHERE",
+	"S_BLUESPHEREBONUS",
+	"S_BLUESPHERESPARK",
+
+	// Bomb Sphere
+	"S_BOMBSPHERE1",
+	"S_BOMBSPHERE2",
+	"S_BOMBSPHERE3",
+	"S_BOMBSPHERE4",
+
+	// NiGHTS Chip
+	"S_NIGHTSCHIP",
+	"S_NIGHTSCHIPBONUS",
+
+	// NiGHTS Star
+	"S_NIGHTSSTAR",
+	"S_NIGHTSSTARXMAS",
+
+	// Gravity Wells for special stages
+	"S_GRAVWELLGREEN",
+	"S_GRAVWELLRED",
+
+	// Individual Team Rings
+	"S_TEAMRING",
+
+	// Special Stage Token
+	"S_TOKEN",
+
+	// CTF Flags
+	"S_REDFLAG",
+	"S_BLUEFLAG",
+
+	// Emblem
+	"S_EMBLEM1",
+	"S_EMBLEM2",
+	"S_EMBLEM3",
+	"S_EMBLEM4",
+	"S_EMBLEM5",
+	"S_EMBLEM6",
+	"S_EMBLEM7",
+	"S_EMBLEM8",
+	"S_EMBLEM9",
+	"S_EMBLEM10",
+	"S_EMBLEM11",
+	"S_EMBLEM12",
+	"S_EMBLEM13",
+	"S_EMBLEM14",
+	"S_EMBLEM15",
+	"S_EMBLEM16",
+	"S_EMBLEM17",
+	"S_EMBLEM18",
+	"S_EMBLEM19",
+	"S_EMBLEM20",
+	"S_EMBLEM21",
+	"S_EMBLEM22",
+	"S_EMBLEM23",
+	"S_EMBLEM24",
+	"S_EMBLEM25",
+	"S_EMBLEM26",
+
+	// Chaos Emeralds
+	"S_CEMG1",
+	"S_CEMG2",
+	"S_CEMG3",
+	"S_CEMG4",
+	"S_CEMG5",
+	"S_CEMG6",
+	"S_CEMG7",
+
+	// Emerald hunt shards
+	"S_SHRD1",
+	"S_SHRD2",
+	"S_SHRD3",
+
+	// Bubble Source
+	"S_BUBBLES1",
+	"S_BUBBLES2",
+	"S_BUBBLES3",
+	"S_BUBBLES4",
+
+	// Level End Sign
+	"S_SIGN",
+	"S_SIGNSPIN1",
+	"S_SIGNSPIN2",
+	"S_SIGNSPIN3",
+	"S_SIGNSPIN4",
+	"S_SIGNSPIN5",
+	"S_SIGNSPIN6",
+	"S_SIGNPLAYER",
+	"S_SIGNSLOW",
+	"S_SIGNSTOP",
+	"S_SIGNBOARD",
+	"S_EGGMANSIGN",
+	"S_CLEARSIGN",
+
+	// Spike Ball
+	"S_SPIKEBALL1",
+	"S_SPIKEBALL2",
+	"S_SPIKEBALL3",
+	"S_SPIKEBALL4",
+	"S_SPIKEBALL5",
+	"S_SPIKEBALL6",
+	"S_SPIKEBALL7",
+	"S_SPIKEBALL8",
+
+	// Elemental Shield's Spawn
+	"S_SPINFIRE1",
+	"S_SPINFIRE2",
+	"S_SPINFIRE3",
+	"S_SPINFIRE4",
+	"S_SPINFIRE5",
+	"S_SPINFIRE6",
+
+	"S_TEAM_SPINFIRE1",
+	"S_TEAM_SPINFIRE2",
+	"S_TEAM_SPINFIRE3",
+	"S_TEAM_SPINFIRE4",
+	"S_TEAM_SPINFIRE5",
+	"S_TEAM_SPINFIRE6",
+
+	// Spikes
+	"S_SPIKE1",
+	"S_SPIKE2",
+	"S_SPIKE3",
+	"S_SPIKE4",
+	"S_SPIKE5",
+	"S_SPIKE6",
+	"S_SPIKED1",
+	"S_SPIKED2",
+
+	// Wall spikes
+	"S_WALLSPIKE1",
+	"S_WALLSPIKE2",
+	"S_WALLSPIKE3",
+	"S_WALLSPIKE4",
+	"S_WALLSPIKE5",
+	"S_WALLSPIKE6",
+	"S_WALLSPIKEBASE",
+	"S_WALLSPIKED1",
+	"S_WALLSPIKED2",
+
+	// Starpost
+	"S_STARPOST_IDLE",
+	"S_STARPOST_FLASH",
+	"S_STARPOST_STARTSPIN",
+	"S_STARPOST_SPIN",
+	"S_STARPOST_ENDSPIN",
+
+	// Big floating mine
+	"S_BIGMINE_IDLE",
+	"S_BIGMINE_ALERT1",
+	"S_BIGMINE_ALERT2",
+	"S_BIGMINE_ALERT3",
+	"S_BIGMINE_SET1",
+	"S_BIGMINE_SET2",
+	"S_BIGMINE_SET3",
+	"S_BIGMINE_BLAST1",
+	"S_BIGMINE_BLAST2",
+	"S_BIGMINE_BLAST3",
+	"S_BIGMINE_BLAST4",
+	"S_BIGMINE_BLAST5",
+
+	// Cannon Launcher
+	"S_CANNONLAUNCHER1",
+	"S_CANNONLAUNCHER2",
+	"S_CANNONLAUNCHER3",
+
+	// Monitor Miscellany
+	"S_BOXSPARKLE1",
+	"S_BOXSPARKLE2",
+	"S_BOXSPARKLE3",
+	"S_BOXSPARKLE4",
+
+	"S_BOX_FLICKER",
+	"S_BOX_POP1",
+	"S_BOX_POP2",
+
+	"S_GOLDBOX_FLICKER",
+	"S_GOLDBOX_OFF1",
+	"S_GOLDBOX_OFF2",
+	"S_GOLDBOX_OFF3",
+	"S_GOLDBOX_OFF4",
+	"S_GOLDBOX_OFF5",
+	"S_GOLDBOX_OFF6",
+	"S_GOLDBOX_OFF7",
+
+	// Monitor States (one per box)
+	"S_MYSTERY_BOX",
+	"S_RING_BOX",
+	"S_PITY_BOX",
+	"S_ATTRACT_BOX",
+	"S_FORCE_BOX",
+	"S_ARMAGEDDON_BOX",
+	"S_WHIRLWIND_BOX",
+	"S_ELEMENTAL_BOX",
+	"S_SNEAKERS_BOX",
+	"S_INVULN_BOX",
+	"S_1UP_BOX",
+	"S_EGGMAN_BOX",
+	"S_MIXUP_BOX",
+	"S_GRAVITY_BOX",
+	"S_RECYCLER_BOX",
+	"S_SCORE1K_BOX",
+	"S_SCORE10K_BOX",
+	"S_FLAMEAURA_BOX",
+	"S_BUBBLEWRAP_BOX",
+	"S_THUNDERCOIN_BOX",
+
+	// Gold Repeat Monitor States (one per box)
+	"S_PITY_GOLDBOX",
+	"S_ATTRACT_GOLDBOX",
+	"S_FORCE_GOLDBOX",
+	"S_ARMAGEDDON_GOLDBOX",
+	"S_WHIRLWIND_GOLDBOX",
+	"S_ELEMENTAL_GOLDBOX",
+	"S_SNEAKERS_GOLDBOX",
+	"S_INVULN_GOLDBOX",
+	"S_EGGMAN_GOLDBOX",
+	"S_GRAVITY_GOLDBOX",
+	"S_FLAMEAURA_GOLDBOX",
+	"S_BUBBLEWRAP_GOLDBOX",
+	"S_THUNDERCOIN_GOLDBOX",
+
+	// Team Ring Boxes (these are special)
+	"S_RING_REDBOX1",
+	"S_RING_REDBOX2",
+	"S_REDBOX_POP1",
+	"S_REDBOX_POP2",
+
+	"S_RING_BLUEBOX1",
+	"S_RING_BLUEBOX2",
+	"S_BLUEBOX_POP1",
+	"S_BLUEBOX_POP2",
+
+	// Box Icons -- 2 states each, animation and action
+	"S_RING_ICON1",
+	"S_RING_ICON2",
+
+	"S_PITY_ICON1",
+	"S_PITY_ICON2",
+
+	"S_ATTRACT_ICON1",
+	"S_ATTRACT_ICON2",
+
+	"S_FORCE_ICON1",
+	"S_FORCE_ICON2",
+
+	"S_ARMAGEDDON_ICON1",
+	"S_ARMAGEDDON_ICON2",
+
+	"S_WHIRLWIND_ICON1",
+	"S_WHIRLWIND_ICON2",
+
+	"S_ELEMENTAL_ICON1",
+	"S_ELEMENTAL_ICON2",
+
+	"S_SNEAKERS_ICON1",
+	"S_SNEAKERS_ICON2",
+
+	"S_INVULN_ICON1",
+	"S_INVULN_ICON2",
+
+	"S_1UP_ICON1",
+	"S_1UP_ICON2",
+
+	"S_EGGMAN_ICON1",
+	"S_EGGMAN_ICON2",
+
+	"S_MIXUP_ICON1",
+	"S_MIXUP_ICON2",
+
+	"S_GRAVITY_ICON1",
+	"S_GRAVITY_ICON2",
+
+	"S_RECYCLER_ICON1",
+	"S_RECYCLER_ICON2",
+
+	"S_SCORE1K_ICON1",
+	"S_SCORE1K_ICON2",
+
+	"S_SCORE10K_ICON1",
+	"S_SCORE10K_ICON2",
+
+	"S_FLAMEAURA_ICON1",
+	"S_FLAMEAURA_ICON2",
+
+	"S_BUBBLEWRAP_ICON1",
+	"S_BUBBLEWRAP_ICON2",
+
+	"S_THUNDERCOIN_ICON1",
+	"S_THUNDERCOIN_ICON2",
+
+	// ---
+
+	"S_ROCKET",
+
+	"S_LASER",
+	"S_LASER2",
+	"S_LASERFLASH",
+
+	"S_LASERFLAME1",
+	"S_LASERFLAME2",
+	"S_LASERFLAME3",
+	"S_LASERFLAME4",
+	"S_LASERFLAME5",
+
+	"S_TORPEDO",
+
+	"S_ENERGYBALL1",
+	"S_ENERGYBALL2",
+
+	// Skim Mine, also used by Jetty-Syn bomber
+	"S_MINE1",
+	"S_MINE_BOOM1",
+	"S_MINE_BOOM2",
+	"S_MINE_BOOM3",
+	"S_MINE_BOOM4",
+
+	// Jetty-Syn Bullet
+	"S_JETBULLET1",
+	"S_JETBULLET2",
+
+	"S_TURRETLASER",
+	"S_TURRETLASEREXPLODE1",
+	"S_TURRETLASEREXPLODE2",
+
+	// Cannonball
+	"S_CANNONBALL1",
+
+	// Arrow
+	"S_ARROW",
+	"S_ARROWBONK",
+
+	// Glaregoyle Demon fire
+	"S_DEMONFIRE",
+
+	// The letter
+	"S_LETTER",
+
+	// GFZ flowers
+	"S_GFZFLOWERA",
+	"S_GFZFLOWERB",
+	"S_GFZFLOWERC",
+
+	"S_BLUEBERRYBUSH",
+	"S_BERRYBUSH",
+	"S_BUSH",
+
+	// Trees (both GFZ and misc)
+	"S_GFZTREE",
+	"S_GFZBERRYTREE",
+	"S_GFZCHERRYTREE",
+	"S_CHECKERTREE",
+	"S_CHECKERSUNSETTREE",
+	"S_FHZTREE", // Frozen Hillside
+	"S_FHZPINKTREE",
+	"S_POLYGONTREE",
+	"S_BUSHTREE",
+	"S_BUSHREDTREE",
+	"S_SPRINGTREE",
+
+	// THZ flowers
+	"S_THZFLOWERA", // THZ1 Steam flower
+	"S_THZFLOWERB", // THZ1 Spin flower (red)
+	"S_THZFLOWERC", // THZ1 Spin flower (yellow)
+
+	// THZ Steam Whistle tree/bush
+	"S_THZTREE",
+	"S_THZTREEBRANCH1",
+	"S_THZTREEBRANCH2",
+	"S_THZTREEBRANCH3",
+	"S_THZTREEBRANCH4",
+	"S_THZTREEBRANCH5",
+	"S_THZTREEBRANCH6",
+	"S_THZTREEBRANCH7",
+	"S_THZTREEBRANCH8",
+	"S_THZTREEBRANCH9",
+	"S_THZTREEBRANCH10",
+	"S_THZTREEBRANCH11",
+	"S_THZTREEBRANCH12",
+	"S_THZTREEBRANCH13",
+
+	// THZ Alarm
+	"S_ALARM1",
+
+	// Deep Sea Gargoyle
+	"S_GARGOYLE",
+	"S_BIGGARGOYLE",
+
+	// DSZ Seaweed
+	"S_SEAWEED1",
+	"S_SEAWEED2",
+	"S_SEAWEED3",
+	"S_SEAWEED4",
+	"S_SEAWEED5",
+	"S_SEAWEED6",
+
+	// Dripping Water
+	"S_DRIPA1",
+	"S_DRIPA2",
+	"S_DRIPA3",
+	"S_DRIPA4",
+	"S_DRIPB1",
+	"S_DRIPC1",
+	"S_DRIPC2",
+
+	// Coral
+	"S_CORAL1",
+	"S_CORAL2",
+	"S_CORAL3",
+	"S_CORAL4",
+	"S_CORAL5",
+
+	// Blue Crystal
+	"S_BLUECRYSTAL1",
+
+	// Kelp,
+	"S_KELP",
+
+	// Animated algae
+	"S_ANIMALGAETOP1",
+	"S_ANIMALGAETOP2",
+	"S_ANIMALGAESEG",
+
+	// DSZ Stalagmites
+	"S_DSZSTALAGMITE",
+	"S_DSZ2STALAGMITE",
+
+	// DSZ Light beam
+	"S_LIGHTBEAM1",
+	"S_LIGHTBEAM2",
+	"S_LIGHTBEAM3",
+	"S_LIGHTBEAM4",
+	"S_LIGHTBEAM5",
+	"S_LIGHTBEAM6",
+	"S_LIGHTBEAM7",
+	"S_LIGHTBEAM8",
+	"S_LIGHTBEAM9",
+	"S_LIGHTBEAM10",
+	"S_LIGHTBEAM11",
+	"S_LIGHTBEAM12",
+
+	// CEZ Chain
+	"S_CEZCHAIN",
+
+	// Flame
+	"S_FLAME",
+	"S_FLAMEPARTICLE",
+	"S_FLAMEREST",
+
+	// Eggman Statue
+	"S_EGGSTATUE1",
+
+	// CEZ hidden sling
+	"S_SLING1",
+	"S_SLING2",
+
+	// CEZ maces and chains
+	"S_SMALLMACECHAIN",
+	"S_BIGMACECHAIN",
+	"S_SMALLMACE",
+	"S_BIGMACE",
+	"S_SMALLGRABCHAIN",
+	"S_BIGGRABCHAIN",
+
+	// Yellow spring on a ball
+	"S_YELLOWSPRINGBALL",
+	"S_YELLOWSPRINGBALL2",
+	"S_YELLOWSPRINGBALL3",
+	"S_YELLOWSPRINGBALL4",
+	"S_YELLOWSPRINGBALL5",
+
+	// Red spring on a ball
+	"S_REDSPRINGBALL",
+	"S_REDSPRINGBALL2",
+	"S_REDSPRINGBALL3",
+	"S_REDSPRINGBALL4",
+	"S_REDSPRINGBALL5",
+
+	// Small Firebar
+	"S_SMALLFIREBAR1",
+	"S_SMALLFIREBAR2",
+	"S_SMALLFIREBAR3",
+	"S_SMALLFIREBAR4",
+	"S_SMALLFIREBAR5",
+	"S_SMALLFIREBAR6",
+	"S_SMALLFIREBAR7",
+	"S_SMALLFIREBAR8",
+	"S_SMALLFIREBAR9",
+	"S_SMALLFIREBAR10",
+	"S_SMALLFIREBAR11",
+	"S_SMALLFIREBAR12",
+	"S_SMALLFIREBAR13",
+	"S_SMALLFIREBAR14",
+	"S_SMALLFIREBAR15",
+	"S_SMALLFIREBAR16",
+
+	// Big Firebar
+	"S_BIGFIREBAR1",
+	"S_BIGFIREBAR2",
+	"S_BIGFIREBAR3",
+	"S_BIGFIREBAR4",
+	"S_BIGFIREBAR5",
+	"S_BIGFIREBAR6",
+	"S_BIGFIREBAR7",
+	"S_BIGFIREBAR8",
+	"S_BIGFIREBAR9",
+	"S_BIGFIREBAR10",
+	"S_BIGFIREBAR11",
+	"S_BIGFIREBAR12",
+	"S_BIGFIREBAR13",
+	"S_BIGFIREBAR14",
+	"S_BIGFIREBAR15",
+	"S_BIGFIREBAR16",
+
+	"S_CEZFLOWER",
+	"S_CEZPOLE",
+	"S_CEZBANNER1",
+	"S_CEZBANNER2",
+	"S_PINETREE",
+	"S_CEZBUSH1",
+	"S_CEZBUSH2",
+	"S_CANDLE",
+	"S_CANDLEPRICKET",
+	"S_FLAMEHOLDER",
+	"S_FIRETORCH",
+	"S_WAVINGFLAG",
+	"S_WAVINGFLAGSEG1",
+	"S_WAVINGFLAGSEG2",
+	"S_CRAWLASTATUE",
+	"S_FACESTABBERSTATUE",
+	"S_SUSPICIOUSFACESTABBERSTATUE_WAIT",
+	"S_SUSPICIOUSFACESTABBERSTATUE_BURST1",
+	"S_SUSPICIOUSFACESTABBERSTATUE_BURST2",
+	"S_BRAMBLES",
+
+	// Big Tumbleweed
+	"S_BIGTUMBLEWEED",
+	"S_BIGTUMBLEWEED_ROLL1",
+	"S_BIGTUMBLEWEED_ROLL2",
+	"S_BIGTUMBLEWEED_ROLL3",
+	"S_BIGTUMBLEWEED_ROLL4",
+	"S_BIGTUMBLEWEED_ROLL5",
+	"S_BIGTUMBLEWEED_ROLL6",
+	"S_BIGTUMBLEWEED_ROLL7",
+	"S_BIGTUMBLEWEED_ROLL8",
+
+	// Little Tumbleweed
+	"S_LITTLETUMBLEWEED",
+	"S_LITTLETUMBLEWEED_ROLL1",
+	"S_LITTLETUMBLEWEED_ROLL2",
+	"S_LITTLETUMBLEWEED_ROLL3",
+	"S_LITTLETUMBLEWEED_ROLL4",
+	"S_LITTLETUMBLEWEED_ROLL5",
+	"S_LITTLETUMBLEWEED_ROLL6",
+	"S_LITTLETUMBLEWEED_ROLL7",
+	"S_LITTLETUMBLEWEED_ROLL8",
+
+	// Cacti
+	"S_CACTI1",
+	"S_CACTI2",
+	"S_CACTI3",
+	"S_CACTI4",
+	"S_CACTI5",
+	"S_CACTI6",
+	"S_CACTI7",
+	"S_CACTI8",
+	"S_CACTI9",
+	"S_CACTI10",
+	"S_CACTI11",
+	"S_CACTITINYSEG",
+	"S_CACTISMALLSEG",
+
+	// Warning signs
+	"S_ARIDSIGN_CAUTION",
+	"S_ARIDSIGN_CACTI",
+	"S_ARIDSIGN_SHARPTURN",
+
+	// Oil lamp
+	"S_OILLAMP",
+	"S_OILLAMPFLARE",
+
+	// TNT barrel
+	"S_TNTBARREL_STND1",
+	"S_TNTBARREL_EXPL1",
+	"S_TNTBARREL_EXPL2",
+	"S_TNTBARREL_EXPL3",
+	"S_TNTBARREL_EXPL4",
+	"S_TNTBARREL_EXPL5",
+	"S_TNTBARREL_EXPL6",
+	"S_TNTBARREL_EXPL7",
+	"S_TNTBARREL_FLYING",
+
+	// TNT proximity shell
+	"S_PROXIMITY_TNT",
+	"S_PROXIMITY_TNT_TRIGGER1",
+	"S_PROXIMITY_TNT_TRIGGER2",
+	"S_PROXIMITY_TNT_TRIGGER3",
+	"S_PROXIMITY_TNT_TRIGGER4",
+	"S_PROXIMITY_TNT_TRIGGER5",
+	"S_PROXIMITY_TNT_TRIGGER6",
+	"S_PROXIMITY_TNT_TRIGGER7",
+	"S_PROXIMITY_TNT_TRIGGER8",
+	"S_PROXIMITY_TNT_TRIGGER9",
+	"S_PROXIMITY_TNT_TRIGGER10",
+	"S_PROXIMITY_TNT_TRIGGER11",
+	"S_PROXIMITY_TNT_TRIGGER12",
+	"S_PROXIMITY_TNT_TRIGGER13",
+	"S_PROXIMITY_TNT_TRIGGER14",
+	"S_PROXIMITY_TNT_TRIGGER15",
+	"S_PROXIMITY_TNT_TRIGGER16",
+	"S_PROXIMITY_TNT_TRIGGER17",
+	"S_PROXIMITY_TNT_TRIGGER18",
+	"S_PROXIMITY_TNT_TRIGGER19",
+	"S_PROXIMITY_TNT_TRIGGER20",
+	"S_PROXIMITY_TNT_TRIGGER21",
+	"S_PROXIMITY_TNT_TRIGGER22",
+	"S_PROXIMITY_TNT_TRIGGER23",
+
+	// Dust devil
+	"S_DUSTDEVIL",
+	"S_DUSTLAYER1",
+	"S_DUSTLAYER2",
+	"S_DUSTLAYER3",
+	"S_DUSTLAYER4",
+	"S_DUSTLAYER5",
+	"S_ARIDDUST1",
+	"S_ARIDDUST2",
+	"S_ARIDDUST3",
+
+	// Minecart
+	"S_MINECART_IDLE",
+	"S_MINECART_DTH1",
+	"S_MINECARTEND",
+	"S_MINECARTSEG_FRONT",
+	"S_MINECARTSEG_BACK",
+	"S_MINECARTSEG_LEFT",
+	"S_MINECARTSEG_RIGHT",
+	"S_MINECARTSIDEMARK1",
+	"S_MINECARTSIDEMARK2",
+	"S_MINECARTSPARK",
+
+	// Saloon door
+	"S_SALOONDOOR",
+	"S_SALOONDOORCENTER",
+
+	// Train cameo
+	"S_TRAINCAMEOSPAWNER_1",
+	"S_TRAINCAMEOSPAWNER_2",
+	"S_TRAINCAMEOSPAWNER_3",
+	"S_TRAINCAMEOSPAWNER_4",
+	"S_TRAINCAMEOSPAWNER_5",
+	"S_TRAINPUFFMAKER",
+
+	// Train
+	"S_TRAINDUST",
+	"S_TRAINSTEAM",
+
+	// Flame jet
+	"S_FLAMEJETSTND",
+	"S_FLAMEJETSTART",
+	"S_FLAMEJETSTOP",
+	"S_FLAMEJETFLAME1",
+	"S_FLAMEJETFLAME2",
+	"S_FLAMEJETFLAME3",
+	"S_FLAMEJETFLAME4",
+	"S_FLAMEJETFLAME5",
+	"S_FLAMEJETFLAME6",
+	"S_FLAMEJETFLAME7",
+	"S_FLAMEJETFLAME8",
+	"S_FLAMEJETFLAME9",
+
+	// Spinning flame jets
+	"S_FJSPINAXISA1", // Counter-clockwise
+	"S_FJSPINAXISA2",
+	"S_FJSPINAXISB1", // Clockwise
+	"S_FJSPINAXISB2",
+
+	// Blade's flame
+	"S_FLAMEJETFLAMEB1",
+	"S_FLAMEJETFLAMEB2",
+	"S_FLAMEJETFLAMEB3",
+
+	// Lavafall
+	"S_LAVAFALL_DORMANT",
+	"S_LAVAFALL_TELL",
+	"S_LAVAFALL_SHOOT",
+	"S_LAVAFALL_LAVA1",
+	"S_LAVAFALL_LAVA2",
+	"S_LAVAFALL_LAVA3",
+	"S_LAVAFALLROCK",
+
+	// Rollout Rock
+	"S_ROLLOUTSPAWN",
+	"S_ROLLOUTROCK",
+
+	// RVZ scenery
+	"S_BIGFERNLEAF",
+	"S_BIGFERN1",
+	"S_BIGFERN2",
+	"S_JUNGLEPALM",
+	"S_TORCHFLOWER",
+	"S_WALLVINE_LONG",
+	"S_WALLVINE_SHORT",
+
+	// Glaregoyles
+	"S_GLAREGOYLE",
+	"S_GLAREGOYLE_CHARGE",
+	"S_GLAREGOYLE_BLINK",
+	"S_GLAREGOYLE_HOLD",
+	"S_GLAREGOYLE_FIRE",
+	"S_GLAREGOYLE_LOOP",
+	"S_GLAREGOYLE_COOLDOWN",
+	"S_GLAREGOYLEUP",
+	"S_GLAREGOYLEUP_CHARGE",
+	"S_GLAREGOYLEUP_BLINK",
+	"S_GLAREGOYLEUP_HOLD",
+	"S_GLAREGOYLEUP_FIRE",
+	"S_GLAREGOYLEUP_LOOP",
+	"S_GLAREGOYLEUP_COOLDOWN",
+	"S_GLAREGOYLEDOWN",
+	"S_GLAREGOYLEDOWN_CHARGE",
+	"S_GLAREGOYLEDOWN_BLINK",
+	"S_GLAREGOYLEDOWN_HOLD",
+	"S_GLAREGOYLEDOWN_FIRE",
+	"S_GLAREGOYLEDOWN_LOOP",
+	"S_GLAREGOYLEDOWN_COOLDOWN",
+	"S_GLAREGOYLELONG",
+	"S_GLAREGOYLELONG_CHARGE",
+	"S_GLAREGOYLELONG_BLINK",
+	"S_GLAREGOYLELONG_HOLD",
+	"S_GLAREGOYLELONG_FIRE",
+	"S_GLAREGOYLELONG_LOOP",
+	"S_GLAREGOYLELONG_COOLDOWN",
+
+	// ATZ's Red Crystal/Target
+	"S_TARGET_IDLE",
+	"S_TARGET_HIT1",
+	"S_TARGET_HIT2",
+	"S_TARGET_RESPAWN",
+	"S_TARGET_ALLDONE",
+
+	// ATZ's green flame
+	"S_GREENFLAME",
+
+	// ATZ Blue Gargoyle
+	"S_BLUEGARGOYLE",
+
+	// Stalagmites
+	"S_STG0",
+	"S_STG1",
+	"S_STG2",
+	"S_STG3",
+	"S_STG4",
+	"S_STG5",
+	"S_STG6",
+	"S_STG7",
+	"S_STG8",
+	"S_STG9",
+
+	// Xmas-specific stuff
+	"S_XMASPOLE",
+	"S_CANDYCANE",
+	"S_SNOWMAN",    // normal
+	"S_SNOWMANHAT", // with hat + scarf
+	"S_LAMPPOST1",  // normal
+	"S_LAMPPOST2",  // with snow
+	"S_HANGSTAR",
+	"S_MISTLETOE",
+	// Xmas GFZ bushes
+	"S_XMASBLUEBERRYBUSH",
+	"S_XMASBERRYBUSH",
+	"S_XMASBUSH",
+	// FHZ
+	"S_FHZICE1",
+	"S_FHZICE2",
+	"S_ROSY_IDLE1",
+	"S_ROSY_IDLE2",
+	"S_ROSY_IDLE3",
+	"S_ROSY_IDLE4",
+	"S_ROSY_JUMP",
+	"S_ROSY_WALK",
+	"S_ROSY_HUG",
+	"S_ROSY_PAIN",
+	"S_ROSY_STND",
+	"S_ROSY_UNHAPPY",
+
+	// Halloween Scenery
+	// Pumpkins
+	"S_JACKO1",
+	"S_JACKO1OVERLAY_1",
+	"S_JACKO1OVERLAY_2",
+	"S_JACKO1OVERLAY_3",
+	"S_JACKO1OVERLAY_4",
+	"S_JACKO2",
+	"S_JACKO2OVERLAY_1",
+	"S_JACKO2OVERLAY_2",
+	"S_JACKO2OVERLAY_3",
+	"S_JACKO2OVERLAY_4",
+	"S_JACKO3",
+	"S_JACKO3OVERLAY_1",
+	"S_JACKO3OVERLAY_2",
+	"S_JACKO3OVERLAY_3",
+	"S_JACKO3OVERLAY_4",
+	// Dr Seuss Trees
+	"S_HHZTREE_TOP",
+	"S_HHZTREE_TRUNK",
+	"S_HHZTREE_LEAF",
+	// Mushroom
+	"S_HHZSHROOM_1",
+	"S_HHZSHROOM_2",
+	"S_HHZSHROOM_3",
+	"S_HHZSHROOM_4",
+	"S_HHZSHROOM_5",
+	"S_HHZSHROOM_6",
+	"S_HHZSHROOM_7",
+	"S_HHZSHROOM_8",
+	"S_HHZSHROOM_9",
+	"S_HHZSHROOM_10",
+	"S_HHZSHROOM_11",
+	"S_HHZSHROOM_12",
+	"S_HHZSHROOM_13",
+	"S_HHZSHROOM_14",
+	"S_HHZSHROOM_15",
+	"S_HHZSHROOM_16",
+	// Misc
+	"S_HHZGRASS",
+	"S_HHZTENT1",
+	"S_HHZTENT2",
+	"S_HHZSTALAGMITE_TALL",
+	"S_HHZSTALAGMITE_SHORT",
+
+	// Botanic Serenity's loads of scenery states
+	"S_BSZTALLFLOWER_RED",
+	"S_BSZTALLFLOWER_PURPLE",
+	"S_BSZTALLFLOWER_BLUE",
+	"S_BSZTALLFLOWER_CYAN",
+	"S_BSZTALLFLOWER_YELLOW",
+	"S_BSZTALLFLOWER_ORANGE",
+	"S_BSZFLOWER_RED",
+	"S_BSZFLOWER_PURPLE",
+	"S_BSZFLOWER_BLUE",
+	"S_BSZFLOWER_CYAN",
+	"S_BSZFLOWER_YELLOW",
+	"S_BSZFLOWER_ORANGE",
+	"S_BSZSHORTFLOWER_RED",
+	"S_BSZSHORTFLOWER_PURPLE",
+	"S_BSZSHORTFLOWER_BLUE",
+	"S_BSZSHORTFLOWER_CYAN",
+	"S_BSZSHORTFLOWER_YELLOW",
+	"S_BSZSHORTFLOWER_ORANGE",
+	"S_BSZTULIP_RED",
+	"S_BSZTULIP_PURPLE",
+	"S_BSZTULIP_BLUE",
+	"S_BSZTULIP_CYAN",
+	"S_BSZTULIP_YELLOW",
+	"S_BSZTULIP_ORANGE",
+	"S_BSZCLUSTER_RED",
+	"S_BSZCLUSTER_PURPLE",
+	"S_BSZCLUSTER_BLUE",
+	"S_BSZCLUSTER_CYAN",
+	"S_BSZCLUSTER_YELLOW",
+	"S_BSZCLUSTER_ORANGE",
+	"S_BSZBUSH_RED",
+	"S_BSZBUSH_PURPLE",
+	"S_BSZBUSH_BLUE",
+	"S_BSZBUSH_CYAN",
+	"S_BSZBUSH_YELLOW",
+	"S_BSZBUSH_ORANGE",
+	"S_BSZVINE_RED",
+	"S_BSZVINE_PURPLE",
+	"S_BSZVINE_BLUE",
+	"S_BSZVINE_CYAN",
+	"S_BSZVINE_YELLOW",
+	"S_BSZVINE_ORANGE",
+	"S_BSZSHRUB",
+	"S_BSZCLOVER",
+	"S_BIG_PALMTREE_TRUNK",
+	"S_BIG_PALMTREE_TOP",
+	"S_PALMTREE_TRUNK",
+	"S_PALMTREE_TOP",
+
+	"S_DBALL1",
+	"S_DBALL2",
+	"S_DBALL3",
+	"S_DBALL4",
+	"S_DBALL5",
+	"S_DBALL6",
+	"S_EGGSTATUE2",
+
+	// Shield Orb
+	"S_ARMA1",
+	"S_ARMA2",
+	"S_ARMA3",
+	"S_ARMA4",
+	"S_ARMA5",
+	"S_ARMA6",
+	"S_ARMA7",
+	"S_ARMA8",
+	"S_ARMA9",
+	"S_ARMA10",
+	"S_ARMA11",
+	"S_ARMA12",
+	"S_ARMA13",
+	"S_ARMA14",
+	"S_ARMA15",
+	"S_ARMA16",
+
+	"S_ARMF1",
+	"S_ARMF2",
+	"S_ARMF3",
+	"S_ARMF4",
+	"S_ARMF5",
+	"S_ARMF6",
+	"S_ARMF7",
+	"S_ARMF8",
+	"S_ARMF9",
+	"S_ARMF10",
+	"S_ARMF11",
+	"S_ARMF12",
+	"S_ARMF13",
+	"S_ARMF14",
+	"S_ARMF15",
+	"S_ARMF16",
+	"S_ARMF17",
+	"S_ARMF18",
+	"S_ARMF19",
+	"S_ARMF20",
+	"S_ARMF21",
+	"S_ARMF22",
+	"S_ARMF23",
+	"S_ARMF24",
+	"S_ARMF25",
+	"S_ARMF26",
+	"S_ARMF27",
+	"S_ARMF28",
+	"S_ARMF29",
+	"S_ARMF30",
+	"S_ARMF31",
+	"S_ARMF32",
+
+	"S_ARMB1",
+	"S_ARMB2",
+	"S_ARMB3",
+	"S_ARMB4",
+	"S_ARMB5",
+	"S_ARMB6",
+	"S_ARMB7",
+	"S_ARMB8",
+	"S_ARMB9",
+	"S_ARMB10",
+	"S_ARMB11",
+	"S_ARMB12",
+	"S_ARMB13",
+	"S_ARMB14",
+	"S_ARMB15",
+	"S_ARMB16",
+	"S_ARMB17",
+	"S_ARMB18",
+	"S_ARMB19",
+	"S_ARMB20",
+	"S_ARMB21",
+	"S_ARMB22",
+	"S_ARMB23",
+	"S_ARMB24",
+	"S_ARMB25",
+	"S_ARMB26",
+	"S_ARMB27",
+	"S_ARMB28",
+	"S_ARMB29",
+	"S_ARMB30",
+	"S_ARMB31",
+	"S_ARMB32",
+
+	"S_WIND1",
+	"S_WIND2",
+	"S_WIND3",
+	"S_WIND4",
+	"S_WIND5",
+	"S_WIND6",
+	"S_WIND7",
+	"S_WIND8",
+
+	"S_MAGN1",
+	"S_MAGN2",
+	"S_MAGN3",
+	"S_MAGN4",
+	"S_MAGN5",
+	"S_MAGN6",
+	"S_MAGN7",
+	"S_MAGN8",
+	"S_MAGN9",
+	"S_MAGN10",
+	"S_MAGN11",
+	"S_MAGN12",
+	"S_MAGN13",
+
+	"S_FORC1",
+	"S_FORC2",
+	"S_FORC3",
+	"S_FORC4",
+	"S_FORC5",
+	"S_FORC6",
+	"S_FORC7",
+	"S_FORC8",
+	"S_FORC9",
+	"S_FORC10",
+
+	"S_FORC11",
+	"S_FORC12",
+	"S_FORC13",
+	"S_FORC14",
+	"S_FORC15",
+	"S_FORC16",
+	"S_FORC17",
+	"S_FORC18",
+	"S_FORC19",
+	"S_FORC20",
+
+	"S_FORC21",
+
+	"S_ELEM1",
+	"S_ELEM2",
+	"S_ELEM3",
+	"S_ELEM4",
+	"S_ELEM5",
+	"S_ELEM6",
+	"S_ELEM7",
+	"S_ELEM8",
+	"S_ELEM9",
+	"S_ELEM10",
+	"S_ELEM11",
+	"S_ELEM12",
+
+	"S_ELEM13",
+	"S_ELEM14",
+
+	"S_ELEMF1",
+	"S_ELEMF2",
+	"S_ELEMF3",
+	"S_ELEMF4",
+	"S_ELEMF5",
+	"S_ELEMF6",
+	"S_ELEMF7",
+	"S_ELEMF8",
+	"S_ELEMF9",
+	"S_ELEMF10",
+
+	"S_PITY1",
+	"S_PITY2",
+	"S_PITY3",
+	"S_PITY4",
+	"S_PITY5",
+	"S_PITY6",
+	"S_PITY7",
+	"S_PITY8",
+	"S_PITY9",
+	"S_PITY10",
+	"S_PITY11",
+	"S_PITY12",
+
+	"S_FIRS1",
+	"S_FIRS2",
+	"S_FIRS3",
+	"S_FIRS4",
+	"S_FIRS5",
+	"S_FIRS6",
+	"S_FIRS7",
+	"S_FIRS8",
+	"S_FIRS9",
+
+	"S_FIRS10",
+	"S_FIRS11",
+
+	"S_FIRSB1",
+	"S_FIRSB2",
+	"S_FIRSB3",
+	"S_FIRSB4",
+	"S_FIRSB5",
+	"S_FIRSB6",
+	"S_FIRSB7",
+	"S_FIRSB8",
+	"S_FIRSB9",
+
+	"S_FIRSB10",
+
+	"S_BUBS1",
+	"S_BUBS2",
+	"S_BUBS3",
+	"S_BUBS4",
+	"S_BUBS5",
+	"S_BUBS6",
+	"S_BUBS7",
+	"S_BUBS8",
+	"S_BUBS9",
+
+	"S_BUBS10",
+	"S_BUBS11",
+
+	"S_BUBSB1",
+	"S_BUBSB2",
+	"S_BUBSB3",
+	"S_BUBSB4",
+
+	"S_BUBSB5",
+	"S_BUBSB6",
+
+	"S_ZAPS1",
+	"S_ZAPS2",
+	"S_ZAPS3",
+	"S_ZAPS4",
+	"S_ZAPS5",
+	"S_ZAPS6",
+	"S_ZAPS7",
+	"S_ZAPS8",
+	"S_ZAPS9",
+	"S_ZAPS10",
+	"S_ZAPS11",
+	"S_ZAPS12",
+	"S_ZAPS13", // blank frame
+	"S_ZAPS14",
+	"S_ZAPS15",
+	"S_ZAPS16",
+
+	"S_ZAPSB1", // blank frame
+	"S_ZAPSB2",
+	"S_ZAPSB3",
+	"S_ZAPSB4",
+	"S_ZAPSB5",
+	"S_ZAPSB6",
+	"S_ZAPSB7",
+	"S_ZAPSB8",
+	"S_ZAPSB9",
+	"S_ZAPSB10",
+	"S_ZAPSB11", // blank frame
+
+	//Thunder spark
+	"S_THUNDERCOIN_SPARK",
+
+	// Invincibility Sparkles
+	"S_IVSP",
+
+	// Super Sonic Spark
+	"S_SSPK1",
+	"S_SSPK2",
+	"S_SSPK3",
+	"S_SSPK4",
+	"S_SSPK5",
+
+	// Flicky-sized bubble
+	"S_FLICKY_BUBBLE",
+
+	// Bluebird
+	"S_FLICKY_01_OUT",
+	"S_FLICKY_01_FLAP1",
+	"S_FLICKY_01_FLAP2",
+	"S_FLICKY_01_FLAP3",
+	"S_FLICKY_01_STAND",
+	"S_FLICKY_01_CENTER",
+
+	// Rabbit
+	"S_FLICKY_02_OUT",
+	"S_FLICKY_02_AIM",
+	"S_FLICKY_02_HOP",
+	"S_FLICKY_02_UP",
+	"S_FLICKY_02_DOWN",
+	"S_FLICKY_02_STAND",
+	"S_FLICKY_02_CENTER",
+
+	// Chicken
+	"S_FLICKY_03_OUT",
+	"S_FLICKY_03_AIM",
+	"S_FLICKY_03_HOP",
+	"S_FLICKY_03_UP",
+	"S_FLICKY_03_FLAP1",
+	"S_FLICKY_03_FLAP2",
+	"S_FLICKY_03_STAND",
+	"S_FLICKY_03_CENTER",
+
+	// Seal
+	"S_FLICKY_04_OUT",
+	"S_FLICKY_04_AIM",
+	"S_FLICKY_04_HOP",
+	"S_FLICKY_04_UP",
+	"S_FLICKY_04_DOWN",
+	"S_FLICKY_04_SWIM1",
+	"S_FLICKY_04_SWIM2",
+	"S_FLICKY_04_SWIM3",
+	"S_FLICKY_04_SWIM4",
+	"S_FLICKY_04_STAND",
+	"S_FLICKY_04_CENTER",
+
+	// Pig
+	"S_FLICKY_05_OUT",
+	"S_FLICKY_05_AIM",
+	"S_FLICKY_05_HOP",
+	"S_FLICKY_05_UP",
+	"S_FLICKY_05_DOWN",
+	"S_FLICKY_05_STAND",
+	"S_FLICKY_05_CENTER",
+
+	// Chipmunk
+	"S_FLICKY_06_OUT",
+	"S_FLICKY_06_AIM",
+	"S_FLICKY_06_HOP",
+	"S_FLICKY_06_UP",
+	"S_FLICKY_06_DOWN",
+	"S_FLICKY_06_STAND",
+	"S_FLICKY_06_CENTER",
+
+	// Penguin
+	"S_FLICKY_07_OUT",
+	"S_FLICKY_07_AIML",
+	"S_FLICKY_07_HOPL",
+	"S_FLICKY_07_UPL",
+	"S_FLICKY_07_DOWNL",
+	"S_FLICKY_07_AIMR",
+	"S_FLICKY_07_HOPR",
+	"S_FLICKY_07_UPR",
+	"S_FLICKY_07_DOWNR",
+	"S_FLICKY_07_SWIM1",
+	"S_FLICKY_07_SWIM2",
+	"S_FLICKY_07_SWIM3",
+	"S_FLICKY_07_STAND",
+	"S_FLICKY_07_CENTER",
+
+	// Fish
+	"S_FLICKY_08_OUT",
+	"S_FLICKY_08_AIM",
+	"S_FLICKY_08_HOP",
+	"S_FLICKY_08_FLAP1",
+	"S_FLICKY_08_FLAP2",
+	"S_FLICKY_08_FLAP3",
+	"S_FLICKY_08_FLAP4",
+	"S_FLICKY_08_SWIM1",
+	"S_FLICKY_08_SWIM2",
+	"S_FLICKY_08_SWIM3",
+	"S_FLICKY_08_SWIM4",
+	"S_FLICKY_08_STAND",
+	"S_FLICKY_08_CENTER",
+
+	// Ram
+	"S_FLICKY_09_OUT",
+	"S_FLICKY_09_AIM",
+	"S_FLICKY_09_HOP",
+	"S_FLICKY_09_UP",
+	"S_FLICKY_09_DOWN",
+	"S_FLICKY_09_STAND",
+	"S_FLICKY_09_CENTER",
+
+	// Puffin
+	"S_FLICKY_10_OUT",
+	"S_FLICKY_10_FLAP1",
+	"S_FLICKY_10_FLAP2",
+	"S_FLICKY_10_STAND",
+	"S_FLICKY_10_CENTER",
+
+	// Cow
+	"S_FLICKY_11_OUT",
+	"S_FLICKY_11_AIM",
+	"S_FLICKY_11_RUN1",
+	"S_FLICKY_11_RUN2",
+	"S_FLICKY_11_RUN3",
+	"S_FLICKY_11_STAND",
+	"S_FLICKY_11_CENTER",
+
+	// Rat
+	"S_FLICKY_12_OUT",
+	"S_FLICKY_12_AIM",
+	"S_FLICKY_12_RUN1",
+	"S_FLICKY_12_RUN2",
+	"S_FLICKY_12_RUN3",
+	"S_FLICKY_12_STAND",
+	"S_FLICKY_12_CENTER",
+
+	// Bear
+	"S_FLICKY_13_OUT",
+	"S_FLICKY_13_AIM",
+	"S_FLICKY_13_HOP",
+	"S_FLICKY_13_UP",
+	"S_FLICKY_13_DOWN",
+	"S_FLICKY_13_STAND",
+	"S_FLICKY_13_CENTER",
+
+	// Dove
+	"S_FLICKY_14_OUT",
+	"S_FLICKY_14_FLAP1",
+	"S_FLICKY_14_FLAP2",
+	"S_FLICKY_14_FLAP3",
+	"S_FLICKY_14_STAND",
+	"S_FLICKY_14_CENTER",
+
+	// Cat
+	"S_FLICKY_15_OUT",
+	"S_FLICKY_15_AIM",
+	"S_FLICKY_15_HOP",
+	"S_FLICKY_15_UP",
+	"S_FLICKY_15_DOWN",
+	"S_FLICKY_15_STAND",
+	"S_FLICKY_15_CENTER",
+
+	// Canary
+	"S_FLICKY_16_OUT",
+	"S_FLICKY_16_FLAP1",
+	"S_FLICKY_16_FLAP2",
+	"S_FLICKY_16_FLAP3",
+	"S_FLICKY_16_STAND",
+	"S_FLICKY_16_CENTER",
+
+	// Spider
+	"S_SECRETFLICKY_01_OUT",
+	"S_SECRETFLICKY_01_AIM",
+	"S_SECRETFLICKY_01_HOP",
+	"S_SECRETFLICKY_01_UP",
+	"S_SECRETFLICKY_01_DOWN",
+	"S_SECRETFLICKY_01_STAND",
+	"S_SECRETFLICKY_01_CENTER",
+
+	// Bat
+	"S_SECRETFLICKY_02_OUT",
+	"S_SECRETFLICKY_02_FLAP1",
+	"S_SECRETFLICKY_02_FLAP2",
+	"S_SECRETFLICKY_02_FLAP3",
+	"S_SECRETFLICKY_02_STAND",
+	"S_SECRETFLICKY_02_CENTER",
+
+	// Fan
+	"S_FAN",
+	"S_FAN2",
+	"S_FAN3",
+	"S_FAN4",
+	"S_FAN5",
+
+	// Steam Riser
+	"S_STEAM1",
+	"S_STEAM2",
+	"S_STEAM3",
+	"S_STEAM4",
+	"S_STEAM5",
+	"S_STEAM6",
+	"S_STEAM7",
+	"S_STEAM8",
+
+	// Bumpers
+	"S_BUMPER",
+	"S_BUMPERHIT",
+
+	// Balloons
+	"S_BALLOON",
+	"S_BALLOONPOP1",
+	"S_BALLOONPOP2",
+	"S_BALLOONPOP3",
+	"S_BALLOONPOP4",
+	"S_BALLOONPOP5",
+	"S_BALLOONPOP6",
+
+	// Yellow Spring
+	"S_YELLOWSPRING",
+	"S_YELLOWSPRING2",
+	"S_YELLOWSPRING3",
+	"S_YELLOWSPRING4",
+	"S_YELLOWSPRING5",
+
+	// Red Spring
+	"S_REDSPRING",
+	"S_REDSPRING2",
+	"S_REDSPRING3",
+	"S_REDSPRING4",
+	"S_REDSPRING5",
+
+	// Blue Spring
+	"S_BLUESPRING",
+	"S_BLUESPRING2",
+	"S_BLUESPRING3",
+	"S_BLUESPRING4",
+	"S_BLUESPRING5",
+
+	// Yellow Diagonal Spring
+	"S_YDIAG1",
+	"S_YDIAG2",
+	"S_YDIAG3",
+	"S_YDIAG4",
+	"S_YDIAG5",
+	"S_YDIAG6",
+	"S_YDIAG7",
+	"S_YDIAG8",
+
+	// Red Diagonal Spring
+	"S_RDIAG1",
+	"S_RDIAG2",
+	"S_RDIAG3",
+	"S_RDIAG4",
+	"S_RDIAG5",
+	"S_RDIAG6",
+	"S_RDIAG7",
+	"S_RDIAG8",
+
+	// Blue Diagonal Spring
+	"S_BDIAG1",
+	"S_BDIAG2",
+	"S_BDIAG3",
+	"S_BDIAG4",
+	"S_BDIAG5",
+	"S_BDIAG6",
+	"S_BDIAG7",
+	"S_BDIAG8",
+
+	// Yellow Side Spring
+	"S_YHORIZ1",
+	"S_YHORIZ2",
+	"S_YHORIZ3",
+	"S_YHORIZ4",
+	"S_YHORIZ5",
+	"S_YHORIZ6",
+	"S_YHORIZ7",
+	"S_YHORIZ8",
+
+	// Red Side Spring
+	"S_RHORIZ1",
+	"S_RHORIZ2",
+	"S_RHORIZ3",
+	"S_RHORIZ4",
+	"S_RHORIZ5",
+	"S_RHORIZ6",
+	"S_RHORIZ7",
+	"S_RHORIZ8",
+
+	// Blue Side Spring
+	"S_BHORIZ1",
+	"S_BHORIZ2",
+	"S_BHORIZ3",
+	"S_BHORIZ4",
+	"S_BHORIZ5",
+	"S_BHORIZ6",
+	"S_BHORIZ7",
+	"S_BHORIZ8",
+
+	// Booster
+	"S_BOOSTERSOUND",
+	"S_YELLOWBOOSTERROLLER",
+	"S_YELLOWBOOSTERSEG_LEFT",
+	"S_YELLOWBOOSTERSEG_RIGHT",
+	"S_YELLOWBOOSTERSEG_FACE",
+	"S_REDBOOSTERROLLER",
+	"S_REDBOOSTERSEG_LEFT",
+	"S_REDBOOSTERSEG_RIGHT",
+	"S_REDBOOSTERSEG_FACE",
+
+	// Rain
+	"S_RAIN1",
+	"S_RAINRETURN",
+
+	// Snowflake
+	"S_SNOW1",
+	"S_SNOW2",
+	"S_SNOW3",
+
+	// Water Splish
+	"S_SPLISH1",
+	"S_SPLISH2",
+	"S_SPLISH3",
+	"S_SPLISH4",
+	"S_SPLISH5",
+	"S_SPLISH6",
+	"S_SPLISH7",
+	"S_SPLISH8",
+	"S_SPLISH9",
+
+	// Lava Splish
+	"S_LAVASPLISH",
+
+	// added water splash
+	"S_SPLASH1",
+	"S_SPLASH2",
+	"S_SPLASH3",
+
+	// lava/slime damage burn smoke
+	"S_SMOKE1",
+	"S_SMOKE2",
+	"S_SMOKE3",
+	"S_SMOKE4",
+	"S_SMOKE5",
+
+	// Bubbles
+	"S_SMALLBUBBLE",
+	"S_MEDIUMBUBBLE",
+	"S_LARGEBUBBLE1",
+	"S_LARGEBUBBLE2",
+	"S_EXTRALARGEBUBBLE", // breathable
+
+	"S_POP1", // Extra Large bubble goes POP!
+
+	"S_WATERZAP",
+
+	// Spindash dust
+	"S_SPINDUST1",
+	"S_SPINDUST2",
+	"S_SPINDUST3",
+	"S_SPINDUST4",
+	"S_SPINDUST_BUBBLE1",
+	"S_SPINDUST_BUBBLE2",
+	"S_SPINDUST_BUBBLE3",
+	"S_SPINDUST_BUBBLE4",
+	"S_SPINDUST_FIRE1",
+	"S_SPINDUST_FIRE2",
+	"S_SPINDUST_FIRE3",
+	"S_SPINDUST_FIRE4",
+
+	"S_FOG1",
+	"S_FOG2",
+	"S_FOG3",
+	"S_FOG4",
+	"S_FOG5",
+	"S_FOG6",
+	"S_FOG7",
+	"S_FOG8",
+	"S_FOG9",
+	"S_FOG10",
+	"S_FOG11",
+	"S_FOG12",
+	"S_FOG13",
+	"S_FOG14",
+
+	"S_SEED",
+
+	"S_PARTICLE",
+
+	// Score Logos
+	"S_SCRA", // 100
+	"S_SCRB", // 200
+	"S_SCRC", // 500
+	"S_SCRD", // 1000
+	"S_SCRE", // 10000
+	"S_SCRF", // 400 (mario)
+	"S_SCRG", // 800 (mario)
+	"S_SCRH", // 2000 (mario)
+	"S_SCRI", // 4000 (mario)
+	"S_SCRJ", // 8000 (mario)
+	"S_SCRK", // 1UP (mario)
+	"S_SCRL", // 10
+
+	// Drowning Timer Numbers
+	"S_ZERO1",
+	"S_ONE1",
+	"S_TWO1",
+	"S_THREE1",
+	"S_FOUR1",
+	"S_FIVE1",
+
+	"S_ZERO2",
+	"S_ONE2",
+	"S_TWO2",
+	"S_THREE2",
+	"S_FOUR2",
+	"S_FIVE2",
+
+	"S_FLIGHTINDICATOR",
+
+	"S_LOCKON1",
+	"S_LOCKON2",
+	"S_LOCKON3",
+	"S_LOCKON4",
+	"S_LOCKONINF1",
+	"S_LOCKONINF2",
+	"S_LOCKONINF3",
+	"S_LOCKONINF4",
+
+	// Tag Sign
+	"S_TTAG",
+
+	// Got Flag Sign
+	"S_GOTFLAG",
+
+	// Finish flag
+	"S_FINISHFLAG",
+
+	"S_CORK",
+	"S_LHRT",
+
+	// Red Ring
+	"S_RRNG1",
+	"S_RRNG2",
+	"S_RRNG3",
+	"S_RRNG4",
+	"S_RRNG5",
+	"S_RRNG6",
+	"S_RRNG7",
+
+	// Weapon Ring Ammo
+	"S_BOUNCERINGAMMO",
+	"S_RAILRINGAMMO",
+	"S_INFINITYRINGAMMO",
+	"S_AUTOMATICRINGAMMO",
+	"S_EXPLOSIONRINGAMMO",
+	"S_SCATTERRINGAMMO",
+	"S_GRENADERINGAMMO",
+
+	// Weapon pickup
+	"S_BOUNCEPICKUP",
+	"S_BOUNCEPICKUPFADE1",
+	"S_BOUNCEPICKUPFADE2",
+	"S_BOUNCEPICKUPFADE3",
+	"S_BOUNCEPICKUPFADE4",
+	"S_BOUNCEPICKUPFADE5",
+	"S_BOUNCEPICKUPFADE6",
+	"S_BOUNCEPICKUPFADE7",
+	"S_BOUNCEPICKUPFADE8",
+
+	"S_RAILPICKUP",
+	"S_RAILPICKUPFADE1",
+	"S_RAILPICKUPFADE2",
+	"S_RAILPICKUPFADE3",
+	"S_RAILPICKUPFADE4",
+	"S_RAILPICKUPFADE5",
+	"S_RAILPICKUPFADE6",
+	"S_RAILPICKUPFADE7",
+	"S_RAILPICKUPFADE8",
+
+	"S_AUTOPICKUP",
+	"S_AUTOPICKUPFADE1",
+	"S_AUTOPICKUPFADE2",
+	"S_AUTOPICKUPFADE3",
+	"S_AUTOPICKUPFADE4",
+	"S_AUTOPICKUPFADE5",
+	"S_AUTOPICKUPFADE6",
+	"S_AUTOPICKUPFADE7",
+	"S_AUTOPICKUPFADE8",
+
+	"S_EXPLODEPICKUP",
+	"S_EXPLODEPICKUPFADE1",
+	"S_EXPLODEPICKUPFADE2",
+	"S_EXPLODEPICKUPFADE3",
+	"S_EXPLODEPICKUPFADE4",
+	"S_EXPLODEPICKUPFADE5",
+	"S_EXPLODEPICKUPFADE6",
+	"S_EXPLODEPICKUPFADE7",
+	"S_EXPLODEPICKUPFADE8",
+
+	"S_SCATTERPICKUP",
+	"S_SCATTERPICKUPFADE1",
+	"S_SCATTERPICKUPFADE2",
+	"S_SCATTERPICKUPFADE3",
+	"S_SCATTERPICKUPFADE4",
+	"S_SCATTERPICKUPFADE5",
+	"S_SCATTERPICKUPFADE6",
+	"S_SCATTERPICKUPFADE7",
+	"S_SCATTERPICKUPFADE8",
+
+	"S_GRENADEPICKUP",
+	"S_GRENADEPICKUPFADE1",
+	"S_GRENADEPICKUPFADE2",
+	"S_GRENADEPICKUPFADE3",
+	"S_GRENADEPICKUPFADE4",
+	"S_GRENADEPICKUPFADE5",
+	"S_GRENADEPICKUPFADE6",
+	"S_GRENADEPICKUPFADE7",
+	"S_GRENADEPICKUPFADE8",
+
+	// Thrown Weapon Rings
+	"S_THROWNBOUNCE1",
+	"S_THROWNBOUNCE2",
+	"S_THROWNBOUNCE3",
+	"S_THROWNBOUNCE4",
+	"S_THROWNBOUNCE5",
+	"S_THROWNBOUNCE6",
+	"S_THROWNBOUNCE7",
+	"S_THROWNINFINITY1",
+	"S_THROWNINFINITY2",
+	"S_THROWNINFINITY3",
+	"S_THROWNINFINITY4",
+	"S_THROWNINFINITY5",
+	"S_THROWNINFINITY6",
+	"S_THROWNINFINITY7",
+	"S_THROWNAUTOMATIC1",
+	"S_THROWNAUTOMATIC2",
+	"S_THROWNAUTOMATIC3",
+	"S_THROWNAUTOMATIC4",
+	"S_THROWNAUTOMATIC5",
+	"S_THROWNAUTOMATIC6",
+	"S_THROWNAUTOMATIC7",
+	"S_THROWNEXPLOSION1",
+	"S_THROWNEXPLOSION2",
+	"S_THROWNEXPLOSION3",
+	"S_THROWNEXPLOSION4",
+	"S_THROWNEXPLOSION5",
+	"S_THROWNEXPLOSION6",
+	"S_THROWNEXPLOSION7",
+	"S_THROWNGRENADE1",
+	"S_THROWNGRENADE2",
+	"S_THROWNGRENADE3",
+	"S_THROWNGRENADE4",
+	"S_THROWNGRENADE5",
+	"S_THROWNGRENADE6",
+	"S_THROWNGRENADE7",
+	"S_THROWNGRENADE8",
+	"S_THROWNGRENADE9",
+	"S_THROWNGRENADE10",
+	"S_THROWNGRENADE11",
+	"S_THROWNGRENADE12",
+	"S_THROWNGRENADE13",
+	"S_THROWNGRENADE14",
+	"S_THROWNGRENADE15",
+	"S_THROWNGRENADE16",
+	"S_THROWNGRENADE17",
+	"S_THROWNGRENADE18",
+	"S_THROWNSCATTER",
+
+	"S_RINGEXPLODE",
+
+	"S_COIN1",
+	"S_COIN2",
+	"S_COIN3",
+	"S_COINSPARKLE1",
+	"S_COINSPARKLE2",
+	"S_COINSPARKLE3",
+	"S_COINSPARKLE4",
+	"S_GOOMBA1",
+	"S_GOOMBA1B",
+	"S_GOOMBA2",
+	"S_GOOMBA3",
+	"S_GOOMBA4",
+	"S_GOOMBA5",
+	"S_GOOMBA6",
+	"S_GOOMBA7",
+	"S_GOOMBA8",
+	"S_GOOMBA9",
+	"S_GOOMBA_DEAD",
+	"S_BLUEGOOMBA1",
+	"S_BLUEGOOMBA1B",
+	"S_BLUEGOOMBA2",
+	"S_BLUEGOOMBA3",
+	"S_BLUEGOOMBA4",
+	"S_BLUEGOOMBA5",
+	"S_BLUEGOOMBA6",
+	"S_BLUEGOOMBA7",
+	"S_BLUEGOOMBA8",
+	"S_BLUEGOOMBA9",
+	"S_BLUEGOOMBA_DEAD",
+
+	// Mario-specific stuff
+	"S_FIREFLOWER1",
+	"S_FIREFLOWER2",
+	"S_FIREFLOWER3",
+	"S_FIREFLOWER4",
+	"S_FIREBALL",
+	"S_FIREBALLTRAIL1",
+	"S_FIREBALLTRAIL2",
+	"S_SHELL",
+	"S_PUMA_START1",
+	"S_PUMA_START2",
+	"S_PUMA_UP1",
+	"S_PUMA_UP2",
+	"S_PUMA_UP3",
+	"S_PUMA_DOWN1",
+	"S_PUMA_DOWN2",
+	"S_PUMA_DOWN3",
+	"S_PUMATRAIL1",
+	"S_PUMATRAIL2",
+	"S_PUMATRAIL3",
+	"S_PUMATRAIL4",
+	"S_HAMMER",
+	"S_KOOPA1",
+	"S_KOOPA2",
+	"S_KOOPAFLAME1",
+	"S_KOOPAFLAME2",
+	"S_KOOPAFLAME3",
+	"S_AXE1",
+	"S_AXE2",
+	"S_AXE3",
+	"S_MARIOBUSH1",
+	"S_MARIOBUSH2",
+	"S_TOAD",
+
+	// Nights-specific stuff
+	"S_NIGHTSDRONE_MAN1",
+	"S_NIGHTSDRONE_MAN2",
+	"S_NIGHTSDRONE_SPARKLING1",
+	"S_NIGHTSDRONE_SPARKLING2",
+	"S_NIGHTSDRONE_SPARKLING3",
+	"S_NIGHTSDRONE_SPARKLING4",
+	"S_NIGHTSDRONE_SPARKLING5",
+	"S_NIGHTSDRONE_SPARKLING6",
+	"S_NIGHTSDRONE_SPARKLING7",
+	"S_NIGHTSDRONE_SPARKLING8",
+	"S_NIGHTSDRONE_SPARKLING9",
+	"S_NIGHTSDRONE_SPARKLING10",
+	"S_NIGHTSDRONE_SPARKLING11",
+	"S_NIGHTSDRONE_SPARKLING12",
+	"S_NIGHTSDRONE_SPARKLING13",
+	"S_NIGHTSDRONE_SPARKLING14",
+	"S_NIGHTSDRONE_SPARKLING15",
+	"S_NIGHTSDRONE_SPARKLING16",
+	"S_NIGHTSDRONE_GOAL1",
+	"S_NIGHTSDRONE_GOAL2",
+	"S_NIGHTSDRONE_GOAL3",
+	"S_NIGHTSDRONE_GOAL4",
+
+	"S_NIGHTSPARKLE1",
+	"S_NIGHTSPARKLE2",
+	"S_NIGHTSPARKLE3",
+	"S_NIGHTSPARKLE4",
+	"S_NIGHTSPARKLESUPER1",
+	"S_NIGHTSPARKLESUPER2",
+	"S_NIGHTSPARKLESUPER3",
+	"S_NIGHTSPARKLESUPER4",
+	"S_NIGHTSLOOPHELPER",
+
+	// NiGHTS bumper
+	"S_NIGHTSBUMPER1",
+	"S_NIGHTSBUMPER2",
+	"S_NIGHTSBUMPER3",
+	"S_NIGHTSBUMPER4",
+	"S_NIGHTSBUMPER5",
+	"S_NIGHTSBUMPER6",
+	"S_NIGHTSBUMPER7",
+	"S_NIGHTSBUMPER8",
+	"S_NIGHTSBUMPER9",
+	"S_NIGHTSBUMPER10",
+	"S_NIGHTSBUMPER11",
+	"S_NIGHTSBUMPER12",
+
+	"S_HOOP",
+	"S_HOOP_XMASA",
+	"S_HOOP_XMASB",
+
+	"S_NIGHTSCORE10",
+	"S_NIGHTSCORE20",
+	"S_NIGHTSCORE30",
+	"S_NIGHTSCORE40",
+	"S_NIGHTSCORE50",
+	"S_NIGHTSCORE60",
+	"S_NIGHTSCORE70",
+	"S_NIGHTSCORE80",
+	"S_NIGHTSCORE90",
+	"S_NIGHTSCORE100",
+	"S_NIGHTSCORE10_2",
+	"S_NIGHTSCORE20_2",
+	"S_NIGHTSCORE30_2",
+	"S_NIGHTSCORE40_2",
+	"S_NIGHTSCORE50_2",
+	"S_NIGHTSCORE60_2",
+	"S_NIGHTSCORE70_2",
+	"S_NIGHTSCORE80_2",
+	"S_NIGHTSCORE90_2",
+	"S_NIGHTSCORE100_2",
+
+	// NiGHTS Paraloop Powerups
+	"S_NIGHTSSUPERLOOP",
+	"S_NIGHTSDRILLREFILL",
+	"S_NIGHTSHELPER",
+	"S_NIGHTSEXTRATIME",
+	"S_NIGHTSLINKFREEZE",
+	"S_EGGCAPSULE",
+
+	// Orbiting Chaos Emeralds
+	"S_ORBITEM1",
+	"S_ORBITEM2",
+	"S_ORBITEM3",
+	"S_ORBITEM4",
+	"S_ORBITEM5",
+	"S_ORBITEM6",
+	"S_ORBITEM7",
+	"S_ORBITEM8",
+	"S_ORBIDYA1",
+	"S_ORBIDYA2",
+	"S_ORBIDYA3",
+	"S_ORBIDYA4",
+	"S_ORBIDYA5",
+
+	// "Flicky" helper
+	"S_NIGHTOPIANHELPER1",
+	"S_NIGHTOPIANHELPER2",
+	"S_NIGHTOPIANHELPER3",
+	"S_NIGHTOPIANHELPER4",
+	"S_NIGHTOPIANHELPER5",
+	"S_NIGHTOPIANHELPER6",
+	"S_NIGHTOPIANHELPER7",
+	"S_NIGHTOPIANHELPER8",
+	"S_NIGHTOPIANHELPER9",
+
+	// Nightopian
+	"S_PIAN0",
+	"S_PIAN1",
+	"S_PIAN2",
+	"S_PIAN3",
+	"S_PIAN4",
+	"S_PIAN5",
+	"S_PIAN6",
+	"S_PIANSING",
+
+	// Shleep
+	"S_SHLEEP1",
+	"S_SHLEEP2",
+	"S_SHLEEP3",
+	"S_SHLEEP4",
+	"S_SHLEEPBOUNCE1",
+	"S_SHLEEPBOUNCE2",
+	"S_SHLEEPBOUNCE3",
+
+	// Secret badniks and hazards, shhhh
+	"S_PENGUINATOR_LOOK",
+	"S_PENGUINATOR_WADDLE1",
+	"S_PENGUINATOR_WADDLE2",
+	"S_PENGUINATOR_WADDLE3",
+	"S_PENGUINATOR_WADDLE4",
+	"S_PENGUINATOR_SLIDE1",
+	"S_PENGUINATOR_SLIDE2",
+	"S_PENGUINATOR_SLIDE3",
+	"S_PENGUINATOR_SLIDE4",
+	"S_PENGUINATOR_SLIDE5",
+
+	"S_POPHAT_LOOK",
+	"S_POPHAT_SHOOT1",
+	"S_POPHAT_SHOOT2",
+	"S_POPHAT_SHOOT3",
+	"S_POPHAT_SHOOT4",
+	"S_POPSHOT",
+	"S_POPSHOT_TRAIL",
+
+	"S_HIVEELEMENTAL_LOOK",
+	"S_HIVEELEMENTAL_PREPARE1",
+	"S_HIVEELEMENTAL_PREPARE2",
+	"S_HIVEELEMENTAL_SHOOT1",
+	"S_HIVEELEMENTAL_SHOOT2",
+	"S_HIVEELEMENTAL_DORMANT",
+	"S_HIVEELEMENTAL_PAIN",
+	"S_HIVEELEMENTAL_DIE1",
+	"S_HIVEELEMENTAL_DIE2",
+	"S_HIVEELEMENTAL_DIE3",
+
+	"S_BUMBLEBORE_SPAWN",
+	"S_BUMBLEBORE_LOOK1",
+	"S_BUMBLEBORE_LOOK2",
+	"S_BUMBLEBORE_FLY1",
+	"S_BUMBLEBORE_FLY2",
+	"S_BUMBLEBORE_RAISE",
+	"S_BUMBLEBORE_FALL1",
+	"S_BUMBLEBORE_FALL2",
+	"S_BUMBLEBORE_STUCK1",
+	"S_BUMBLEBORE_STUCK2",
+	"S_BUMBLEBORE_DIE",
+
+	"S_BUGGLEIDLE",
+	"S_BUGGLEFLY",
+
+	"S_SMASHSPIKE_FLOAT",
+	"S_SMASHSPIKE_EASE1",
+	"S_SMASHSPIKE_EASE2",
+	"S_SMASHSPIKE_FALL",
+	"S_SMASHSPIKE_STOMP1",
+	"S_SMASHSPIKE_STOMP2",
+	"S_SMASHSPIKE_RISE1",
+	"S_SMASHSPIKE_RISE2",
+
+	"S_CACO_LOOK",
+	"S_CACO_WAKE1",
+	"S_CACO_WAKE2",
+	"S_CACO_WAKE3",
+	"S_CACO_WAKE4",
+	"S_CACO_ROAR",
+	"S_CACO_CHASE",
+	"S_CACO_CHASE_REPEAT",
+	"S_CACO_RANDOM",
+	"S_CACO_PREPARE_SOUND",
+	"S_CACO_PREPARE1",
+	"S_CACO_PREPARE2",
+	"S_CACO_PREPARE3",
+	"S_CACO_SHOOT_SOUND",
+	"S_CACO_SHOOT1",
+	"S_CACO_SHOOT2",
+	"S_CACO_CLOSE",
+	"S_CACO_DIE_FLAGS",
+	"S_CACO_DIE_GIB1",
+	"S_CACO_DIE_GIB2",
+	"S_CACO_DIE_SCREAM",
+	"S_CACO_DIE_SHATTER",
+	"S_CACO_DIE_FALL",
+	"S_CACOSHARD_RANDOMIZE",
+	"S_CACOSHARD1_1",
+	"S_CACOSHARD1_2",
+	"S_CACOSHARD2_1",
+	"S_CACOSHARD2_2",
+	"S_CACOFIRE1",
+	"S_CACOFIRE2",
+	"S_CACOFIRE3",
+	"S_CACOFIRE_EXPLODE1",
+	"S_CACOFIRE_EXPLODE2",
+	"S_CACOFIRE_EXPLODE3",
+	"S_CACOFIRE_EXPLODE4",
+
+	"S_SPINBOBERT_MOVE_FLIPUP",
+	"S_SPINBOBERT_MOVE_UP",
+	"S_SPINBOBERT_MOVE_FLIPDOWN",
+	"S_SPINBOBERT_MOVE_DOWN",
+	"S_SPINBOBERT_FIRE_MOVE",
+	"S_SPINBOBERT_FIRE_GHOST",
+	"S_SPINBOBERT_FIRE_TRAIL1",
+	"S_SPINBOBERT_FIRE_TRAIL2",
+	"S_SPINBOBERT_FIRE_TRAIL3",
+
+	"S_HANGSTER_LOOK",
+	"S_HANGSTER_SWOOP1",
+	"S_HANGSTER_SWOOP2",
+	"S_HANGSTER_ARC1",
+	"S_HANGSTER_ARC2",
+	"S_HANGSTER_ARC3",
+	"S_HANGSTER_FLY1",
+	"S_HANGSTER_FLY2",
+	"S_HANGSTER_FLY3",
+	"S_HANGSTER_FLY4",
+	"S_HANGSTER_FLYREPEAT",
+	"S_HANGSTER_ARCUP1",
+	"S_HANGSTER_ARCUP2",
+	"S_HANGSTER_ARCUP3",
+	"S_HANGSTER_RETURN1",
+	"S_HANGSTER_RETURN2",
+	"S_HANGSTER_RETURN3",
+
+	"S_CRUMBLE1",
+	"S_CRUMBLE2",
+
+	// Spark
+	"S_SPRK1",
+	"S_SPRK2",
+	"S_SPRK3",
+
+	// Robot Explosion
+	"S_XPLD_FLICKY",
+	"S_XPLD1",
+	"S_XPLD2",
+	"S_XPLD3",
+	"S_XPLD4",
+	"S_XPLD5",
+	"S_XPLD6",
+	"S_XPLD_EGGTRAP",
+
+	// Underwater Explosion
+	"S_WPLD1",
+	"S_WPLD2",
+	"S_WPLD3",
+	"S_WPLD4",
+	"S_WPLD5",
+	"S_WPLD6",
+
+	"S_DUST1",
+	"S_DUST2",
+	"S_DUST3",
+	"S_DUST4",
+
+	"S_ROCKSPAWN",
+
+	"S_ROCKCRUMBLEA",
+	"S_ROCKCRUMBLEB",
+	"S_ROCKCRUMBLEC",
+	"S_ROCKCRUMBLED",
+	"S_ROCKCRUMBLEE",
+	"S_ROCKCRUMBLEF",
+	"S_ROCKCRUMBLEG",
+	"S_ROCKCRUMBLEH",
+	"S_ROCKCRUMBLEI",
+	"S_ROCKCRUMBLEJ",
+	"S_ROCKCRUMBLEK",
+	"S_ROCKCRUMBLEL",
+	"S_ROCKCRUMBLEM",
+	"S_ROCKCRUMBLEN",
+	"S_ROCKCRUMBLEO",
+	"S_ROCKCRUMBLEP",
+
+	// Level debris
+	"S_GFZDEBRIS",
+	"S_BRICKDEBRIS",
+	"S_WOODDEBRIS",
+	"S_REDBRICKDEBRIS",
+	"S_BLUEBRICKDEBRIS",
+	"S_YELLOWBRICKDEBRIS",
+
+	"S_NAMECHECK",
+};
+
+// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
+// I am leaving the prefixes solely for clarity to programmers,
+// because sadly no one remembers this place while searching for full state names.
+const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity testing later.
+	"MT_NULL",
+	"MT_UNKNOWN",
+
+	"MT_THOK", // Thok! mobj
+	"MT_PLAYER",
+	"MT_TAILSOVERLAY", // c:
+	"MT_METALJETFUME",
+
+	// Enemies
+	"MT_BLUECRAWLA", // Crawla (Blue)
+	"MT_REDCRAWLA", // Crawla (Red)
+	"MT_GFZFISH", // SDURF
+	"MT_GOLDBUZZ", // Buzz (Gold)
+	"MT_REDBUZZ", // Buzz (Red)
+	"MT_JETTBOMBER", // Jetty-Syn Bomber
+	"MT_JETTGUNNER", // Jetty-Syn Gunner
+	"MT_CRAWLACOMMANDER", // Crawla Commander
+	"MT_DETON", // Deton
+	"MT_SKIM", // Skim mine dropper
+	"MT_TURRET", // Industrial Turret
+	"MT_POPUPTURRET", // Pop-Up Turret
+	"MT_SPINCUSHION", // Spincushion
+	"MT_CRUSHSTACEAN", // Crushstacean
+	"MT_CRUSHCLAW", // Big meaty claw
+	"MT_CRUSHCHAIN", // Chain
+	"MT_BANPYURA", // Banpyura
+	"MT_BANPSPRING", // Banpyura spring
+	"MT_JETJAW", // Jet Jaw
+	"MT_SNAILER", // Snailer
+	"MT_VULTURE", // BASH
+	"MT_POINTY", // Pointy
+	"MT_POINTYBALL", // Pointy Ball
+	"MT_ROBOHOOD", // Robo-Hood
+	"MT_FACESTABBER", // Castlebot Facestabber
+	"MT_FACESTABBERSPEAR", // Castlebot Facestabber spear aura
+	"MT_EGGGUARD", // Egg Guard
+	"MT_EGGSHIELD", // Egg Guard's shield
+	"MT_GSNAPPER", // Green Snapper
+	"MT_SNAPPER_LEG", // Green Snapper leg
+	"MT_SNAPPER_HEAD", // Green Snapper head
+	"MT_MINUS", // Minus
+	"MT_MINUSDIRT", // Minus dirt
+	"MT_SPRINGSHELL", // Spring Shell
+	"MT_YELLOWSHELL", // Spring Shell (yellow)
+	"MT_UNIDUS", // Unidus
+	"MT_UNIBALL", // Unidus Ball
+	"MT_CANARIVORE", // Canarivore
+	"MT_CANARIVORE_GAS", // Canarivore gas
+	"MT_PYREFLY", // Pyre Fly
+	"MT_PYREFLY_FIRE", // Pyre Fly fire
+	"MT_PTERABYTESPAWNER", // Pterabyte spawner
+	"MT_PTERABYTEWAYPOINT", // Pterabyte waypoint
+	"MT_PTERABYTE", // Pterabyte
+	"MT_DRAGONBOMBER", // Dragonbomber
+	"MT_DRAGONWING", // Dragonbomber wing
+	"MT_DRAGONTAIL", // Dragonbomber tail segment
+	"MT_DRAGONMINE", // Dragonbomber mine
+
+	// Generic Boss Items
+	"MT_BOSSEXPLODE",
+	"MT_SONIC3KBOSSEXPLODE",
+	"MT_BOSSFLYPOINT",
+	"MT_EGGTRAP",
+	"MT_BOSS3WAYPOINT",
+	"MT_BOSS9GATHERPOINT",
+	"MT_BOSSJUNK",
+
+	// Boss 1
+	"MT_EGGMOBILE",
+	"MT_JETFUME1",
+	"MT_EGGMOBILE_BALL",
+	"MT_EGGMOBILE_TARGET",
+	"MT_EGGMOBILE_FIRE",
+
+	// Boss 2
+	"MT_EGGMOBILE2",
+	"MT_EGGMOBILE2_POGO",
+	"MT_GOOP",
+	"MT_GOOPTRAIL",
+
+	// Boss 3
+	"MT_EGGMOBILE3",
+	"MT_FAKEMOBILE",
+	"MT_SHOCKWAVE",
+
+	// Boss 4
+	"MT_EGGMOBILE4",
+	"MT_EGGMOBILE4_MACE",
+	"MT_JETFLAME",
+	"MT_EGGROBO1",
+	"MT_EGGROBO1JET",
+
+	// Boss 5
+	"MT_FANG",
+	"MT_BROKENROBOT",
+	"MT_VWREF",
+	"MT_VWREB",
+	"MT_PROJECTORLIGHT",
+	"MT_FBOMB",
+	"MT_TNTDUST", // also used by barrel
+	"MT_FSGNA",
+	"MT_FSGNB",
+	"MT_FANGWAYPOINT",
+
+	// Black Eggman (Boss 7)
+	"MT_BLACKEGGMAN",
+	"MT_BLACKEGGMAN_HELPER",
+	"MT_BLACKEGGMAN_GOOPFIRE",
+	"MT_BLACKEGGMAN_MISSILE",
+
+	// New Very-Last-Minute 2.1 Brak Eggman (Cy-Brak-demon)
+	"MT_CYBRAKDEMON",
+	"MT_CYBRAKDEMON_ELECTRIC_BARRIER",
+	"MT_CYBRAKDEMON_MISSILE",
+	"MT_CYBRAKDEMON_FLAMESHOT",
+	"MT_CYBRAKDEMON_FLAMEREST",
+	"MT_CYBRAKDEMON_TARGET_RETICULE",
+	"MT_CYBRAKDEMON_TARGET_DOT",
+	"MT_CYBRAKDEMON_NAPALM_BOMB_LARGE",
+	"MT_CYBRAKDEMON_NAPALM_BOMB_SMALL",
+	"MT_CYBRAKDEMON_NAPALM_FLAMES",
+	"MT_CYBRAKDEMON_VILE_EXPLOSION",
+
+	// Metal Sonic (Boss 9)
+	"MT_METALSONIC_RACE",
+	"MT_METALSONIC_BATTLE",
+	"MT_MSSHIELD_FRONT",
+	"MT_MSGATHER",
+
+	// Collectible Items
+	"MT_RING",
+	"MT_FLINGRING", // Lost ring
+	"MT_BLUESPHERE",  // Blue sphere for special stages
+	"MT_FLINGBLUESPHERE", // Lost blue sphere
+	"MT_BOMBSPHERE",
+	"MT_REDTEAMRING",  //Rings collectable by red team.
+	"MT_BLUETEAMRING", //Rings collectable by blue team.
+	"MT_TOKEN", // Special Stage token for special stage
+	"MT_REDFLAG", // Red CTF Flag
+	"MT_BLUEFLAG", // Blue CTF Flag
+	"MT_EMBLEM",
+	"MT_EMERALD1",
+	"MT_EMERALD2",
+	"MT_EMERALD3",
+	"MT_EMERALD4",
+	"MT_EMERALD5",
+	"MT_EMERALD6",
+	"MT_EMERALD7",
+	"MT_EMERHUNT", // Emerald Hunt
+	"MT_EMERALDSPAWN", // Emerald spawner w/ delay
+	"MT_FLINGEMERALD", // Lost emerald
+
+	// Springs and others
+	"MT_FAN",
+	"MT_STEAM",
+	"MT_BUMPER",
+	"MT_BALLOON",
+
+	"MT_YELLOWSPRING",
+	"MT_REDSPRING",
+	"MT_BLUESPRING",
+	"MT_YELLOWDIAG",
+	"MT_REDDIAG",
+	"MT_BLUEDIAG",
+	"MT_YELLOWHORIZ",
+	"MT_REDHORIZ",
+	"MT_BLUEHORIZ",
+
+	"MT_BOOSTERSEG",
+	"MT_BOOSTERROLLER",
+	"MT_YELLOWBOOSTER",
+	"MT_REDBOOSTER",
+
+	// Interactive Objects
+	"MT_BUBBLES", // Bubble source
+	"MT_SIGN", // Level end sign
+	"MT_SPIKEBALL", // Spike Ball
+	"MT_SPINFIRE",
+	"MT_SPIKE",
+	"MT_WALLSPIKE",
+	"MT_WALLSPIKEBASE",
+	"MT_STARPOST",
+	"MT_BIGMINE",
+	"MT_BLASTEXECUTOR",
+	"MT_CANNONLAUNCHER",
+
+	// Monitor miscellany
+	"MT_BOXSPARKLE",
+
+	// Monitor boxes -- regular
+	"MT_RING_BOX",
+	"MT_PITY_BOX",
+	"MT_ATTRACT_BOX",
+	"MT_FORCE_BOX",
+	"MT_ARMAGEDDON_BOX",
+	"MT_WHIRLWIND_BOX",
+	"MT_ELEMENTAL_BOX",
+	"MT_SNEAKERS_BOX",
+	"MT_INVULN_BOX",
+	"MT_1UP_BOX",
+	"MT_EGGMAN_BOX",
+	"MT_MIXUP_BOX",
+	"MT_MYSTERY_BOX",
+	"MT_GRAVITY_BOX",
+	"MT_RECYCLER_BOX",
+	"MT_SCORE1K_BOX",
+	"MT_SCORE10K_BOX",
+	"MT_FLAMEAURA_BOX",
+	"MT_BUBBLEWRAP_BOX",
+	"MT_THUNDERCOIN_BOX",
+
+	// Monitor boxes -- repeating (big) boxes
+	"MT_PITY_GOLDBOX",
+	"MT_ATTRACT_GOLDBOX",
+	"MT_FORCE_GOLDBOX",
+	"MT_ARMAGEDDON_GOLDBOX",
+	"MT_WHIRLWIND_GOLDBOX",
+	"MT_ELEMENTAL_GOLDBOX",
+	"MT_SNEAKERS_GOLDBOX",
+	"MT_INVULN_GOLDBOX",
+	"MT_EGGMAN_GOLDBOX",
+	"MT_GRAVITY_GOLDBOX",
+	"MT_FLAMEAURA_GOLDBOX",
+	"MT_BUBBLEWRAP_GOLDBOX",
+	"MT_THUNDERCOIN_GOLDBOX",
+
+	// Monitor boxes -- special
+	"MT_RING_REDBOX",
+	"MT_RING_BLUEBOX",
+
+	// Monitor icons
+	"MT_RING_ICON",
+	"MT_PITY_ICON",
+	"MT_ATTRACT_ICON",
+	"MT_FORCE_ICON",
+	"MT_ARMAGEDDON_ICON",
+	"MT_WHIRLWIND_ICON",
+	"MT_ELEMENTAL_ICON",
+	"MT_SNEAKERS_ICON",
+	"MT_INVULN_ICON",
+	"MT_1UP_ICON",
+	"MT_EGGMAN_ICON",
+	"MT_MIXUP_ICON",
+	"MT_GRAVITY_ICON",
+	"MT_RECYCLER_ICON",
+	"MT_SCORE1K_ICON",
+	"MT_SCORE10K_ICON",
+	"MT_FLAMEAURA_ICON",
+	"MT_BUBBLEWRAP_ICON",
+	"MT_THUNDERCOIN_ICON",
+
+	// Projectiles
+	"MT_ROCKET",
+	"MT_LASER",
+	"MT_TORPEDO",
+	"MT_TORPEDO2", // silent
+	"MT_ENERGYBALL",
+	"MT_MINE", // Skim/Jetty-Syn mine
+	"MT_JETTBULLET", // Jetty-Syn Bullet
+	"MT_TURRETLASER",
+	"MT_CANNONBALL", // Cannonball
+	"MT_CANNONBALLDECOR", // Decorative/still cannonball
+	"MT_ARROW", // Arrow
+	"MT_DEMONFIRE", // Glaregoyle fire
+
+	// The letter
+	"MT_LETTER",
+
+	// Greenflower Scenery
+	"MT_GFZFLOWER1",
+	"MT_GFZFLOWER2",
+	"MT_GFZFLOWER3",
+
+	"MT_BLUEBERRYBUSH",
+	"MT_BERRYBUSH",
+	"MT_BUSH",
+
+	// Trees (both GFZ and misc)
+	"MT_GFZTREE",
+	"MT_GFZBERRYTREE",
+	"MT_GFZCHERRYTREE",
+	"MT_CHECKERTREE",
+	"MT_CHECKERSUNSETTREE",
+	"MT_FHZTREE", // Frozen Hillside
+	"MT_FHZPINKTREE",
+	"MT_POLYGONTREE",
+	"MT_BUSHTREE",
+	"MT_BUSHREDTREE",
+	"MT_SPRINGTREE",
+
+	// Techno Hill Scenery
+	"MT_THZFLOWER1",
+	"MT_THZFLOWER2",
+	"MT_THZFLOWER3",
+	"MT_THZTREE", // Steam whistle tree/bush
+	"MT_THZTREEBRANCH", // branch of said tree
+	"MT_ALARM",
+
+	// Deep Sea Scenery
+	"MT_GARGOYLE", // Deep Sea Gargoyle
+	"MT_BIGGARGOYLE", // Deep Sea Gargoyle (Big)
+	"MT_SEAWEED", // DSZ Seaweed
+	"MT_WATERDRIP", // Dripping Water source
+	"MT_WATERDROP", // Water drop from dripping water
+	"MT_CORAL1", // Coral
+	"MT_CORAL2",
+	"MT_CORAL3",
+	"MT_CORAL4",
+	"MT_CORAL5",
+	"MT_BLUECRYSTAL", // Blue Crystal
+	"MT_KELP", // Kelp
+	"MT_ANIMALGAETOP", // Animated algae top
+	"MT_ANIMALGAESEG", // Animated algae segment
+	"MT_DSZSTALAGMITE", // Deep Sea 1 Stalagmite
+	"MT_DSZ2STALAGMITE", // Deep Sea 2 Stalagmite
+	"MT_LIGHTBEAM", // DSZ Light beam
+
+	// Castle Eggman Scenery
+	"MT_CHAIN", // CEZ Chain
+	"MT_FLAME", // Flame (has corona)
+	"MT_FLAMEPARTICLE",
+	"MT_EGGSTATUE", // Eggman Statue
+	"MT_MACEPOINT", // Mace rotation point
+	"MT_CHAINMACEPOINT", // Combination of chains and maces point
+	"MT_SPRINGBALLPOINT", // Spring ball point
+	"MT_CHAINPOINT", // Mace chain
+	"MT_HIDDEN_SLING", // Spin mace chain (activatable)
+	"MT_FIREBARPOINT", // Firebar
+	"MT_CUSTOMMACEPOINT", // Custom mace
+	"MT_SMALLMACECHAIN", // Small Mace Chain
+	"MT_BIGMACECHAIN", // Big Mace Chain
+	"MT_SMALLMACE", // Small Mace
+	"MT_BIGMACE", // Big Mace
+	"MT_SMALLGRABCHAIN", // Small Grab Chain
+	"MT_BIGGRABCHAIN", // Big Grab Chain
+	"MT_YELLOWSPRINGBALL", // Yellow spring on a ball
+	"MT_REDSPRINGBALL", // Red spring on a ball
+	"MT_SMALLFIREBAR", // Small Firebar
+	"MT_BIGFIREBAR", // Big Firebar
+	"MT_CEZFLOWER", // Flower
+	"MT_CEZPOLE1", // Pole (with red banner)
+	"MT_CEZPOLE2", // Pole (with blue banner)
+	"MT_CEZBANNER1", // Banner (red)
+	"MT_CEZBANNER2", // Banner (blue)
+	"MT_PINETREE", // Pine Tree
+	"MT_CEZBUSH1", // Bush 1
+	"MT_CEZBUSH2", // Bush 2
+	"MT_CANDLE", // Candle
+	"MT_CANDLEPRICKET", // Candle pricket
+	"MT_FLAMEHOLDER", // Flame holder
+	"MT_FIRETORCH", // Fire torch
+	"MT_WAVINGFLAG1", // Waving flag (red)
+	"MT_WAVINGFLAG2", // Waving flag (blue)
+	"MT_WAVINGFLAGSEG1", // Waving flag segment (red)
+	"MT_WAVINGFLAGSEG2", // Waving flag segment (blue)
+	"MT_CRAWLASTATUE", // Crawla statue
+	"MT_FACESTABBERSTATUE", // Facestabber statue
+	"MT_SUSPICIOUSFACESTABBERSTATUE", // :eggthinking:
+	"MT_BRAMBLES", // Brambles
+
+	// Arid Canyon Scenery
+	"MT_BIGTUMBLEWEED",
+	"MT_LITTLETUMBLEWEED",
+	"MT_CACTI1", // Tiny Red Flower Cactus
+	"MT_CACTI2", // Small Red Flower Cactus
+	"MT_CACTI3", // Tiny Blue Flower Cactus
+	"MT_CACTI4", // Small Blue Flower Cactus
+	"MT_CACTI5", // Prickly Pear
+	"MT_CACTI6", // Barrel Cactus
+	"MT_CACTI7", // Tall Barrel Cactus
+	"MT_CACTI8", // Armed Cactus
+	"MT_CACTI9", // Ball Cactus
+	"MT_CACTI10", // Tiny Cactus
+	"MT_CACTI11", // Small Cactus
+	"MT_CACTITINYSEG", // Tiny Cactus Segment
+	"MT_CACTISMALLSEG", // Small Cactus Segment
+	"MT_ARIDSIGN_CAUTION", // Caution Sign
+	"MT_ARIDSIGN_CACTI", // Cacti Sign
+	"MT_ARIDSIGN_SHARPTURN", // Sharp Turn Sign
+	"MT_OILLAMP",
+	"MT_TNTBARREL",
+	"MT_PROXIMITYTNT",
+	"MT_DUSTDEVIL",
+	"MT_DUSTLAYER",
+	"MT_ARIDDUST",
+	"MT_MINECART",
+	"MT_MINECARTSEG",
+	"MT_MINECARTSPAWNER",
+	"MT_MINECARTEND",
+	"MT_MINECARTENDSOLID",
+	"MT_MINECARTSIDEMARK",
+	"MT_MINECARTSPARK",
+	"MT_SALOONDOOR",
+	"MT_SALOONDOORCENTER",
+	"MT_TRAINCAMEOSPAWNER",
+	"MT_TRAINSEG",
+	"MT_TRAINDUSTSPAWNER",
+	"MT_TRAINSTEAMSPAWNER",
+	"MT_MINECARTSWITCHPOINT",
+
+	// Red Volcano Scenery
+	"MT_FLAMEJET",
+	"MT_VERTICALFLAMEJET",
+	"MT_FLAMEJETFLAME",
+
+	"MT_FJSPINAXISA", // Counter-clockwise
+	"MT_FJSPINAXISB", // Clockwise
+
+	"MT_FLAMEJETFLAMEB", // Blade's flame
+
+	"MT_LAVAFALL",
+	"MT_LAVAFALL_LAVA",
+	"MT_LAVAFALLROCK",
+
+	"MT_ROLLOUTSPAWN",
+	"MT_ROLLOUTROCK",
+
+	"MT_BIGFERNLEAF",
+	"MT_BIGFERN",
+	"MT_JUNGLEPALM",
+	"MT_TORCHFLOWER",
+	"MT_WALLVINE_LONG",
+	"MT_WALLVINE_SHORT",
+
+	// Dark City Scenery
+
+	// Egg Rock Scenery
+
+	// Azure Temple Scenery
+	"MT_GLAREGOYLE",
+	"MT_GLAREGOYLEUP",
+	"MT_GLAREGOYLEDOWN",
+	"MT_GLAREGOYLELONG",
+	"MT_TARGET", // AKA Red Crystal
+	"MT_GREENFLAME",
+	"MT_BLUEGARGOYLE",
+
+	// Stalagmites
+	"MT_STALAGMITE0",
+	"MT_STALAGMITE1",
+	"MT_STALAGMITE2",
+	"MT_STALAGMITE3",
+	"MT_STALAGMITE4",
+	"MT_STALAGMITE5",
+	"MT_STALAGMITE6",
+	"MT_STALAGMITE7",
+	"MT_STALAGMITE8",
+	"MT_STALAGMITE9",
+
+	// Christmas Scenery
+	"MT_XMASPOLE",
+	"MT_CANDYCANE",
+	"MT_SNOWMAN",    // normal
+	"MT_SNOWMANHAT", // with hat + scarf
+	"MT_LAMPPOST1",  // normal
+	"MT_LAMPPOST2",  // with snow
+	"MT_HANGSTAR",
+	"MT_MISTLETOE",
+	// Xmas GFZ bushes
+	"MT_XMASBLUEBERRYBUSH",
+	"MT_XMASBERRYBUSH",
+	"MT_XMASBUSH",
+	// FHZ
+	"MT_FHZICE1",
+	"MT_FHZICE2",
+	"MT_ROSY",
+	"MT_CDLHRT",
+
+	// Halloween Scenery
+	// Pumpkins
+	"MT_JACKO1",
+	"MT_JACKO2",
+	"MT_JACKO3",
+	// Dr Seuss Trees
+	"MT_HHZTREE_TOP",
+	"MT_HHZTREE_PART",
+	// Misc
+	"MT_HHZSHROOM",
+	"MT_HHZGRASS",
+	"MT_HHZTENTACLE1",
+	"MT_HHZTENTACLE2",
+	"MT_HHZSTALAGMITE_TALL",
+	"MT_HHZSTALAGMITE_SHORT",
+
+	// Botanic Serenity scenery
+	"MT_BSZTALLFLOWER_RED",
+	"MT_BSZTALLFLOWER_PURPLE",
+	"MT_BSZTALLFLOWER_BLUE",
+	"MT_BSZTALLFLOWER_CYAN",
+	"MT_BSZTALLFLOWER_YELLOW",
+	"MT_BSZTALLFLOWER_ORANGE",
+	"MT_BSZFLOWER_RED",
+	"MT_BSZFLOWER_PURPLE",
+	"MT_BSZFLOWER_BLUE",
+	"MT_BSZFLOWER_CYAN",
+	"MT_BSZFLOWER_YELLOW",
+	"MT_BSZFLOWER_ORANGE",
+	"MT_BSZSHORTFLOWER_RED",
+	"MT_BSZSHORTFLOWER_PURPLE",
+	"MT_BSZSHORTFLOWER_BLUE",
+	"MT_BSZSHORTFLOWER_CYAN",
+	"MT_BSZSHORTFLOWER_YELLOW",
+	"MT_BSZSHORTFLOWER_ORANGE",
+	"MT_BSZTULIP_RED",
+	"MT_BSZTULIP_PURPLE",
+	"MT_BSZTULIP_BLUE",
+	"MT_BSZTULIP_CYAN",
+	"MT_BSZTULIP_YELLOW",
+	"MT_BSZTULIP_ORANGE",
+	"MT_BSZCLUSTER_RED",
+	"MT_BSZCLUSTER_PURPLE",
+	"MT_BSZCLUSTER_BLUE",
+	"MT_BSZCLUSTER_CYAN",
+	"MT_BSZCLUSTER_YELLOW",
+	"MT_BSZCLUSTER_ORANGE",
+	"MT_BSZBUSH_RED",
+	"MT_BSZBUSH_PURPLE",
+	"MT_BSZBUSH_BLUE",
+	"MT_BSZBUSH_CYAN",
+	"MT_BSZBUSH_YELLOW",
+	"MT_BSZBUSH_ORANGE",
+	"MT_BSZVINE_RED",
+	"MT_BSZVINE_PURPLE",
+	"MT_BSZVINE_BLUE",
+	"MT_BSZVINE_CYAN",
+	"MT_BSZVINE_YELLOW",
+	"MT_BSZVINE_ORANGE",
+	"MT_BSZSHRUB",
+	"MT_BSZCLOVER",
+	"MT_BIG_PALMTREE_TRUNK",
+	"MT_BIG_PALMTREE_TOP",
+	"MT_PALMTREE_TRUNK",
+	"MT_PALMTREE_TOP",
+
+	// Misc scenery
+	"MT_DBALL",
+	"MT_EGGSTATUE2",
+
+	// Powerup Indicators
+	"MT_ELEMENTAL_ORB", // Elemental shield mobj
+	"MT_ATTRACT_ORB", // Attract shield mobj
+	"MT_FORCE_ORB", // Force shield mobj
+	"MT_ARMAGEDDON_ORB", // Armageddon shield mobj
+	"MT_WHIRLWIND_ORB", // Whirlwind shield mobj
+	"MT_PITY_ORB", // Pity shield mobj
+	"MT_FLAMEAURA_ORB", // Flame shield mobj
+	"MT_BUBBLEWRAP_ORB", // Bubble shield mobj
+	"MT_THUNDERCOIN_ORB", // Thunder shield mobj
+	"MT_THUNDERCOIN_SPARK", // Thunder spark
+	"MT_IVSP", // Invincibility sparkles
+	"MT_SUPERSPARK", // Super Sonic Spark
+
+	// Flickies
+	"MT_FLICKY_01", // Bluebird
+	"MT_FLICKY_01_CENTER",
+	"MT_FLICKY_02", // Rabbit
+	"MT_FLICKY_02_CENTER",
+	"MT_FLICKY_03", // Chicken
+	"MT_FLICKY_03_CENTER",
+	"MT_FLICKY_04", // Seal
+	"MT_FLICKY_04_CENTER",
+	"MT_FLICKY_05", // Pig
+	"MT_FLICKY_05_CENTER",
+	"MT_FLICKY_06", // Chipmunk
+	"MT_FLICKY_06_CENTER",
+	"MT_FLICKY_07", // Penguin
+	"MT_FLICKY_07_CENTER",
+	"MT_FLICKY_08", // Fish
+	"MT_FLICKY_08_CENTER",
+	"MT_FLICKY_09", // Ram
+	"MT_FLICKY_09_CENTER",
+	"MT_FLICKY_10", // Puffin
+	"MT_FLICKY_10_CENTER",
+	"MT_FLICKY_11", // Cow
+	"MT_FLICKY_11_CENTER",
+	"MT_FLICKY_12", // Rat
+	"MT_FLICKY_12_CENTER",
+	"MT_FLICKY_13", // Bear
+	"MT_FLICKY_13_CENTER",
+	"MT_FLICKY_14", // Dove
+	"MT_FLICKY_14_CENTER",
+	"MT_FLICKY_15", // Cat
+	"MT_FLICKY_15_CENTER",
+	"MT_FLICKY_16", // Canary
+	"MT_FLICKY_16_CENTER",
+	"MT_SECRETFLICKY_01", // Spider
+	"MT_SECRETFLICKY_01_CENTER",
+	"MT_SECRETFLICKY_02", // Bat
+	"MT_SECRETFLICKY_02_CENTER",
+	"MT_SEED",
+
+	// Environmental Effects
+	"MT_RAIN", // Rain
+	"MT_SNOWFLAKE", // Snowflake
+	"MT_SPLISH", // Water splish!
+	"MT_LAVASPLISH", // Lava splish!
+	"MT_SMOKE",
+	"MT_SMALLBUBBLE", // small bubble
+	"MT_MEDIUMBUBBLE", // medium bubble
+	"MT_EXTRALARGEBUBBLE", // extra large bubble
+	"MT_WATERZAP",
+	"MT_SPINDUST", // Spindash dust
+	"MT_TFOG",
+	"MT_PARTICLE",
+	"MT_PARTICLEGEN", // For fans, etc.
+
+	// Game Indicators
+	"MT_SCORE", // score logo
+	"MT_DROWNNUMBERS", // Drowning Timer
+	"MT_GOTEMERALD", // Chaos Emerald (intangible)
+	"MT_LOCKON", // Target
+	"MT_LOCKONINF", // In-level Target
+	"MT_TAG", // Tag Sign
+	"MT_GOTFLAG", // Got Flag sign
+	"MT_FINISHFLAG", // Finish flag
+
+	// Ambient Sounds
+	"MT_AWATERA", // Ambient Water Sound 1
+	"MT_AWATERB", // Ambient Water Sound 2
+	"MT_AWATERC", // Ambient Water Sound 3
+	"MT_AWATERD", // Ambient Water Sound 4
+	"MT_AWATERE", // Ambient Water Sound 5
+	"MT_AWATERF", // Ambient Water Sound 6
+	"MT_AWATERG", // Ambient Water Sound 7
+	"MT_AWATERH", // Ambient Water Sound 8
+	"MT_RANDOMAMBIENT",
+	"MT_RANDOMAMBIENT2",
+	"MT_MACHINEAMBIENCE",
+
+	"MT_CORK",
+	"MT_LHRT",
+
+	// Ring Weapons
+	"MT_REDRING",
+	"MT_BOUNCERING",
+	"MT_RAILRING",
+	"MT_INFINITYRING",
+	"MT_AUTOMATICRING",
+	"MT_EXPLOSIONRING",
+	"MT_SCATTERRING",
+	"MT_GRENADERING",
+
+	"MT_BOUNCEPICKUP",
+	"MT_RAILPICKUP",
+	"MT_AUTOPICKUP",
+	"MT_EXPLODEPICKUP",
+	"MT_SCATTERPICKUP",
+	"MT_GRENADEPICKUP",
+
+	"MT_THROWNBOUNCE",
+	"MT_THROWNINFINITY",
+	"MT_THROWNAUTOMATIC",
+	"MT_THROWNSCATTER",
+	"MT_THROWNEXPLOSION",
+	"MT_THROWNGRENADE",
+
+	// Mario-specific stuff
+	"MT_COIN",
+	"MT_FLINGCOIN",
+	"MT_GOOMBA",
+	"MT_BLUEGOOMBA",
+	"MT_FIREFLOWER",
+	"MT_FIREBALL",
+	"MT_FIREBALLTRAIL",
+	"MT_SHELL",
+	"MT_PUMA",
+	"MT_PUMATRAIL",
+	"MT_HAMMER",
+	"MT_KOOPA",
+	"MT_KOOPAFLAME",
+	"MT_AXE",
+	"MT_MARIOBUSH1",
+	"MT_MARIOBUSH2",
+	"MT_TOAD",
+
+	// NiGHTS Stuff
+	"MT_AXIS",
+	"MT_AXISTRANSFER",
+	"MT_AXISTRANSFERLINE",
+	"MT_NIGHTSDRONE",
+	"MT_NIGHTSDRONE_MAN",
+	"MT_NIGHTSDRONE_SPARKLING",
+	"MT_NIGHTSDRONE_GOAL",
+	"MT_NIGHTSPARKLE",
+	"MT_NIGHTSLOOPHELPER",
+	"MT_NIGHTSBUMPER", // NiGHTS Bumper
+	"MT_HOOP",
+	"MT_HOOPCOLLIDE", // Collision detection for NiGHTS hoops
+	"MT_HOOPCENTER", // Center of a hoop
+	"MT_NIGHTSCORE",
+	"MT_NIGHTSCHIP", // NiGHTS Chip
+	"MT_FLINGNIGHTSCHIP", // Lost NiGHTS Chip
+	"MT_NIGHTSSTAR", // NiGHTS Star
+	"MT_FLINGNIGHTSSTAR", // Lost NiGHTS Star
+	"MT_NIGHTSSUPERLOOP",
+	"MT_NIGHTSDRILLREFILL",
+	"MT_NIGHTSHELPER",
+	"MT_NIGHTSEXTRATIME",
+	"MT_NIGHTSLINKFREEZE",
+	"MT_EGGCAPSULE",
+	"MT_IDEYAANCHOR",
+	"MT_NIGHTOPIANHELPER", // the actual helper object that orbits you
+	"MT_PIAN", // decorative singing friend
+	"MT_SHLEEP", // almost-decorative sleeping enemy
+
+	// Secret badniks and hazards, shhhh
+	"MT_PENGUINATOR",
+	"MT_POPHAT",
+	"MT_POPSHOT",
+	"MT_POPSHOT_TRAIL",
+
+	"MT_HIVEELEMENTAL",
+	"MT_BUMBLEBORE",
+
+	"MT_BUGGLE",
+
+	"MT_SMASHINGSPIKEBALL",
+	"MT_CACOLANTERN",
+	"MT_CACOSHARD",
+	"MT_CACOFIRE",
+	"MT_SPINBOBERT",
+	"MT_SPINBOBERT_FIRE1",
+	"MT_SPINBOBERT_FIRE2",
+	"MT_HANGSTER",
+
+	// Utility Objects
+	"MT_TELEPORTMAN",
+	"MT_ALTVIEWMAN",
+	"MT_CRUMBLEOBJ", // Sound generator for crumbling platform
+	"MT_TUBEWAYPOINT",
+	"MT_PUSH",
+	"MT_PULL",
+	"MT_GHOST",
+	"MT_OVERLAY",
+	"MT_ANGLEMAN",
+	"MT_POLYANCHOR",
+	"MT_POLYSPAWN",
+
+	// Skybox objects
+	"MT_SKYBOX",
+
+	// Debris
+	"MT_SPARK", //spark
+	"MT_EXPLODE", // Robot Explosion
+	"MT_UWEXPLODE", // Underwater Explosion
+	"MT_DUST",
+	"MT_ROCKSPAWNER",
+	"MT_FALLINGROCK",
+	"MT_ROCKCRUMBLE1",
+	"MT_ROCKCRUMBLE2",
+	"MT_ROCKCRUMBLE3",
+	"MT_ROCKCRUMBLE4",
+	"MT_ROCKCRUMBLE5",
+	"MT_ROCKCRUMBLE6",
+	"MT_ROCKCRUMBLE7",
+	"MT_ROCKCRUMBLE8",
+	"MT_ROCKCRUMBLE9",
+	"MT_ROCKCRUMBLE10",
+	"MT_ROCKCRUMBLE11",
+	"MT_ROCKCRUMBLE12",
+	"MT_ROCKCRUMBLE13",
+	"MT_ROCKCRUMBLE14",
+	"MT_ROCKCRUMBLE15",
+	"MT_ROCKCRUMBLE16",
+
+	// Level debris
+	"MT_GFZDEBRIS",
+	"MT_BRICKDEBRIS",
+	"MT_WOODDEBRIS",
+	"MT_REDBRICKDEBRIS",
+	"MT_BLUEBRICKDEBRIS",
+	"MT_YELLOWBRICKDEBRIS",
+
+	"MT_NAMECHECK",
+};
+
+const char *const MOBJFLAG_LIST[] = {
+	"SPECIAL",
+	"SOLID",
+	"SHOOTABLE",
+	"NOSECTOR",
+	"NOBLOCKMAP",
+	"PAPERCOLLISION",
+	"PUSHABLE",
+	"BOSS",
+	"SPAWNCEILING",
+	"NOGRAVITY",
+	"AMBIENT",
+	"SLIDEME",
+	"NOCLIP",
+	"FLOAT",
+	"BOXICON",
+	"MISSILE",
+	"SPRING",
+	"BOUNCE",
+	"MONITOR",
+	"NOTHINK",
+	"FIRE",
+	"NOCLIPHEIGHT",
+	"ENEMY",
+	"SCENERY",
+	"PAIN",
+	"STICKY",
+	"NIGHTSITEM",
+	"NOCLIPTHING",
+	"GRENADEBOUNCE",
+	"RUNSPAWNFUNC",
+	NULL
+};
+
+// \tMF2_(\S+).*// (.+) --> \t"\1", // \2
+const char *const MOBJFLAG2_LIST[] = {
+	"AXIS",			  // It's a NiGHTS axis! (For faster checking)
+	"TWOD",			  // Moves like it's in a 2D level
+	"DONTRESPAWN",	  // Don't respawn this object!
+	"DONTDRAW",		  // Don't generate a vissprite
+	"AUTOMATIC",	  // Thrown ring has automatic properties
+	"RAILRING",		  // Thrown ring has rail properties
+	"BOUNCERING",	  // Thrown ring has bounce properties
+	"EXPLOSION",	  // Thrown ring has explosive properties
+	"SCATTER",		  // Thrown ring has scatter properties
+	"BEYONDTHEGRAVE", // Source of this missile has died and has since respawned.
+	"SLIDEPUSH",	  // MF_PUSHABLE that pushes continuously.
+	"CLASSICPUSH",    // Drops straight down when object has negative momz.
+	"INVERTAIMABLE",  // Flips whether it's targetable by A_LookForEnemies (enemies no, decoys yes)
+	"INFLOAT",		  // Floating to a height for a move, don't auto float to target's height.
+	"DEBRIS",		  // Splash ring from explosion ring
+	"NIGHTSPULL",	  // Attracted from a paraloop
+	"JUSTATTACKED",	  // can be pushed by other moving mobjs
+	"FIRING",		  // turret fire
+	"SUPERFIRE",	  // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it.
+	"SHADOW",		  // Fuzzy draw, makes targeting harder.
+	"STRONGBOX",	  // Flag used for "strong" random monitors.
+	"OBJECTFLIP",	  // Flag for objects that always have flipped gravity.
+	"SKULLFLY",		  // Special handling: skull in flight.
+	"FRET",			  // Flashing from a previous hit
+	"BOSSNOTRAP",	  // No Egg Trap after boss
+	"BOSSFLEE",		  // Boss is fleeing!
+	"BOSSDEAD",		  // Boss is dead! (Not necessarily fleeing, if a fleeing point doesn't exist.)
+	"AMBUSH",         // Alternate behaviour typically set by MTF_AMBUSH
+	"LINKDRAW",       // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
+	"SHIELD",         // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
+	"SPLAT",          // Object is a splat
+	NULL
+};
+
+const char *const MOBJEFLAG_LIST[] = {
+	"ONGROUND", // The mobj stands on solid floor (not on another mobj or in air)
+	"JUSTHITFLOOR", // The mobj just hit the floor while falling, this is cleared on next frame
+	"TOUCHWATER", // The mobj stands in a sector with water, and touches the surface
+	"UNDERWATER", // The mobj stands in a sector with water, and his waist is BELOW the water surface
+	"JUSTSTEPPEDDOWN", // used for ramp sectors
+	"VERTICALFLIP", // Vertically flip sprite/allow upside-down physics
+	"GOOWATER", // Goo water
+	"TOUCHLAVA", // The mobj is touching a lava block
+	"PUSHED", // Mobj was already pushed this tic
+	"SPRUNG", // Mobj was already sprung this tic
+	"APPLYPMOMZ", // Platform movement
+	"TRACERANGLE", // Compute and trigger on mobj angle relative to tracer
+	NULL
+};
+
+const char *const MAPTHINGFLAG_LIST[4] = {
+	"EXTRA", // Extra flag for objects.
+	"OBJECTFLIP", // Reverse gravity flag for objects.
+	"OBJECTSPECIAL", // Special flag used with certain objects.
+	"AMBUSH" // Deaf monsters/do not react to sound.
+};
+
+const char *const PLAYERFLAG_LIST[] = {
+
+	// Cvars
+	"FLIPCAM", // Flip camera angle with gravity flip prefrence.
+	"ANALOGMODE", // Analog mode?
+	"DIRECTIONCHAR", // Directional character sprites?
+	"AUTOBRAKE", // Autobrake?
+
+	// Cheats
+	"GODMODE",
+	"NOCLIP",
+	"INVIS",
+
+	// True if button down last tic.
+	"ATTACKDOWN",
+	"SPINDOWN",
+	"JUMPDOWN",
+	"WPNDOWN",
+
+	// Unmoving states
+	"STASIS", // Player is not allowed to move
+	"JUMPSTASIS", // and that includes jumping.
+	// (we don't include FULLSTASIS here I guess because it's just those two together...?)
+
+	// Applying autobrake?
+	"APPLYAUTOBRAKE",
+
+	// Character action status
+	"STARTJUMP",
+	"JUMPED",
+	"NOJUMPDAMAGE",
+
+	"SPINNING",
+	"STARTDASH",
+
+	"THOKKED",
+	"SHIELDABILITY",
+	"GLIDING",
+	"BOUNCING",
+
+	// Sliding (usually in water) like Labyrinth/Oil Ocean
+	"SLIDING",
+
+	// NiGHTS stuff
+	"TRANSFERTOCLOSEST",
+	"DRILLING",
+
+	// Gametype-specific stuff
+	"GAMETYPEOVER", // Race time over, or H&S out-of-game
+	"TAGIT", // The player is it! For Tag Mode
+
+	/*** misc ***/
+	"FORCESTRAFE", // Translate turn inputs into strafe inputs
+	"CANCARRY", // Can carry?
+	"FINISHED",
+
+	NULL // stop loop here.
+};
+
+const char *const GAMETYPERULE_LIST[] = {
+	"CAMPAIGN",
+	"RINGSLINGER",
+	"SPECTATORS",
+	"LIVES",
+	"TEAMS",
+	"FIRSTPERSON",
+	"POWERSTONES",
+	"TEAMFLAGS",
+	"FRIENDLY",
+	"SPECIALSTAGES",
+	"EMERALDTOKENS",
+	"EMERALDHUNT",
+	"RACE",
+	"TAG",
+	"POINTLIMIT",
+	"TIMELIMIT",
+	"OVERTIME",
+	"HURTMESSAGES",
+	"FRIENDLYFIRE",
+	"STARTCOUNTDOWN",
+	"HIDEFROZEN",
+	"BLINDFOLDED",
+	"RESPAWNDELAY",
+	"PITYSHIELD",
+	"DEATHPENALTY",
+	"NOSPECTATORSPAWN",
+	"DEATHMATCHSTARTS",
+	"SPAWNINVUL",
+	"SPAWNENEMIES",
+	"ALLOWEXIT",
+	"NOTITLECARD",
+	"CUTSCENES",
+	NULL
+};
+
+// Linedef flags
+const char *const ML_LIST[16] = {
+	"IMPASSIBLE",
+	"BLOCKMONSTERS",
+	"TWOSIDED",
+	"DONTPEGTOP",
+	"DONTPEGBOTTOM",
+	"EFFECT1",
+	"NOCLIMB",
+	"EFFECT2",
+	"EFFECT3",
+	"EFFECT4",
+	"EFFECT5",
+	"NOSONIC",
+	"NOTAILS",
+	"NOKNUX",
+	"BOUNCY",
+	"TFERLINE"
+};
+
+const char *COLOR_ENUMS[] = {
+	"NONE",			// SKINCOLOR_NONE,
+
+	// Greyscale ranges
+	"WHITE",     	// SKINCOLOR_WHITE,
+	"BONE",     	// SKINCOLOR_BONE,
+	"CLOUDY",     	// SKINCOLOR_CLOUDY,
+	"GREY",     	// SKINCOLOR_GREY,
+	"SILVER",     	// SKINCOLOR_SILVER,
+	"CARBON",     	// SKINCOLOR_CARBON,
+	"JET",     		// SKINCOLOR_JET,
+	"BLACK",     	// SKINCOLOR_BLACK,
+
+	// Desaturated
+	"AETHER",     	// SKINCOLOR_AETHER,
+	"SLATE",     	// SKINCOLOR_SLATE,
+	"BLUEBELL",   	// SKINCOLOR_BLUEBELL,
+	"PINK",     	// SKINCOLOR_PINK,
+	"YOGURT",     	// SKINCOLOR_YOGURT,
+	"BROWN",     	// SKINCOLOR_BROWN,
+	"BRONZE",     	// SKINCOLOR_BRONZE,
+	"TAN",     		// SKINCOLOR_TAN,
+	"BEIGE",     	// SKINCOLOR_BEIGE,
+	"MOSS",     	// SKINCOLOR_MOSS,
+	"AZURE",     	// SKINCOLOR_AZURE,
+	"LAVENDER",     // SKINCOLOR_LAVENDER,
+
+	// Viv's vivid colours (toast 21/07/17)
+	"RUBY",     	// SKINCOLOR_RUBY,
+	"SALMON",     	// SKINCOLOR_SALMON,
+	"RED",     		// SKINCOLOR_RED,
+	"CRIMSON",     	// SKINCOLOR_CRIMSON,
+	"FLAME",     	// SKINCOLOR_FLAME,
+	"KETCHUP",     	// SKINCOLOR_KETCHUP,
+	"PEACHY",     	// SKINCOLOR_PEACHY,
+	"QUAIL",     	// SKINCOLOR_QUAIL,
+	"SUNSET",     	// SKINCOLOR_SUNSET,
+	"COPPER",     	// SKINCOLOR_COPPER,
+	"APRICOT",     	// SKINCOLOR_APRICOT,
+	"ORANGE",     	// SKINCOLOR_ORANGE,
+	"RUST",     	// SKINCOLOR_RUST,
+	"GOLD",     	// SKINCOLOR_GOLD,
+	"SANDY",     	// SKINCOLOR_SANDY,
+	"YELLOW",     	// SKINCOLOR_YELLOW,
+	"OLIVE",     	// SKINCOLOR_OLIVE,
+	"LIME",     	// SKINCOLOR_LIME,
+	"PERIDOT",     	// SKINCOLOR_PERIDOT,
+	"APPLE",     	// SKINCOLOR_APPLE,
+	"GREEN",     	// SKINCOLOR_GREEN,
+	"FOREST",     	// SKINCOLOR_FOREST,
+	"EMERALD",     	// SKINCOLOR_EMERALD,
+	"MINT",     	// SKINCOLOR_MINT,
+	"SEAFOAM",     	// SKINCOLOR_SEAFOAM,
+	"AQUA",     	// SKINCOLOR_AQUA,
+	"TEAL",     	// SKINCOLOR_TEAL,
+	"WAVE",     	// SKINCOLOR_WAVE,
+	"CYAN",     	// SKINCOLOR_CYAN,
+	"SKY",     		// SKINCOLOR_SKY,
+	"CERULEAN",     // SKINCOLOR_CERULEAN,
+	"ICY",     		// SKINCOLOR_ICY,
+	"SAPPHIRE",     // SKINCOLOR_SAPPHIRE,
+	"CORNFLOWER",   // SKINCOLOR_CORNFLOWER,
+	"BLUE",     	// SKINCOLOR_BLUE,
+	"COBALT",     	// SKINCOLOR_COBALT,
+	"VAPOR",     	// SKINCOLOR_VAPOR,
+	"DUSK",     	// SKINCOLOR_DUSK,
+	"PASTEL",     	// SKINCOLOR_PASTEL,
+	"PURPLE",     	// SKINCOLOR_PURPLE,
+	"BUBBLEGUM",    // SKINCOLOR_BUBBLEGUM,
+	"MAGENTA",     	// SKINCOLOR_MAGENTA,
+	"NEON",     	// SKINCOLOR_NEON,
+	"VIOLET",     	// SKINCOLOR_VIOLET,
+	"LILAC",     	// SKINCOLOR_LILAC,
+	"PLUM",     	// SKINCOLOR_PLUM,
+	"RASPBERRY",  	// SKINCOLOR_RASPBERRY,
+	"ROSY",     	// SKINCOLOR_ROSY,
+
+	// Super special awesome Super flashing colors!
+	"SUPERSILVER1",	// SKINCOLOR_SUPERSILVER1
+	"SUPERSILVER2",	// SKINCOLOR_SUPERSILVER2,
+	"SUPERSILVER3",	// SKINCOLOR_SUPERSILVER3,
+	"SUPERSILVER4",	// SKINCOLOR_SUPERSILVER4,
+	"SUPERSILVER5",	// SKINCOLOR_SUPERSILVER5,
+
+	"SUPERRED1",	// SKINCOLOR_SUPERRED1
+	"SUPERRED2",	// SKINCOLOR_SUPERRED2,
+	"SUPERRED3",	// SKINCOLOR_SUPERRED3,
+	"SUPERRED4",	// SKINCOLOR_SUPERRED4,
+	"SUPERRED5",	// SKINCOLOR_SUPERRED5,
+
+	"SUPERORANGE1",	// SKINCOLOR_SUPERORANGE1
+	"SUPERORANGE2",	// SKINCOLOR_SUPERORANGE2,
+	"SUPERORANGE3",	// SKINCOLOR_SUPERORANGE3,
+	"SUPERORANGE4",	// SKINCOLOR_SUPERORANGE4,
+	"SUPERORANGE5",	// SKINCOLOR_SUPERORANGE5,
+
+	"SUPERGOLD1",	// SKINCOLOR_SUPERGOLD1
+	"SUPERGOLD2",	// SKINCOLOR_SUPERGOLD2,
+	"SUPERGOLD3",	// SKINCOLOR_SUPERGOLD3,
+	"SUPERGOLD4",	// SKINCOLOR_SUPERGOLD4,
+	"SUPERGOLD5",	// SKINCOLOR_SUPERGOLD5,
+
+	"SUPERPERIDOT1",	// SKINCOLOR_SUPERPERIDOT1
+	"SUPERPERIDOT2",	// SKINCOLOR_SUPERPERIDOT2,
+	"SUPERPERIDOT3",	// SKINCOLOR_SUPERPERIDOT3,
+	"SUPERPERIDOT4",	// SKINCOLOR_SUPERPERIDOT4,
+	"SUPERPERIDOT5",	// SKINCOLOR_SUPERPERIDOT5,
+
+	"SUPERSKY1",	// SKINCOLOR_SUPERSKY1
+	"SUPERSKY2",	// SKINCOLOR_SUPERSKY2,
+	"SUPERSKY3",	// SKINCOLOR_SUPERSKY3,
+	"SUPERSKY4",	// SKINCOLOR_SUPERSKY4,
+	"SUPERSKY5",	// SKINCOLOR_SUPERSKY5,
+
+	"SUPERPURPLE1",	// SKINCOLOR_SUPERPURPLE1,
+	"SUPERPURPLE2",	// SKINCOLOR_SUPERPURPLE2,
+	"SUPERPURPLE3",	// SKINCOLOR_SUPERPURPLE3,
+	"SUPERPURPLE4",	// SKINCOLOR_SUPERPURPLE4,
+	"SUPERPURPLE5",	// SKINCOLOR_SUPERPURPLE5,
+
+	"SUPERRUST1",	// SKINCOLOR_SUPERRUST1
+	"SUPERRUST2",	// SKINCOLOR_SUPERRUST2,
+	"SUPERRUST3",	// SKINCOLOR_SUPERRUST3,
+	"SUPERRUST4",	// SKINCOLOR_SUPERRUST4,
+	"SUPERRUST5",	// SKINCOLOR_SUPERRUST5,
+
+	"SUPERTAN1",	// SKINCOLOR_SUPERTAN1
+	"SUPERTAN2",	// SKINCOLOR_SUPERTAN2,
+	"SUPERTAN3",	// SKINCOLOR_SUPERTAN3,
+	"SUPERTAN4",	// SKINCOLOR_SUPERTAN4,
+	"SUPERTAN5"		// SKINCOLOR_SUPERTAN5,
+};
+
+const char *const POWERS_LIST[] = {
+	"INVULNERABILITY",
+	"SNEAKERS",
+	"FLASHING",
+	"SHIELD",
+	"CARRY",
+	"TAILSFLY", // tails flying
+	"UNDERWATER", // underwater timer
+	"SPACETIME", // In space, no one can hear you spin!
+	"EXTRALIFE", // Extra Life timer
+	"PUSHING",
+	"JUSTSPRUNG",
+	"NOAUTOBRAKE",
+
+	"SUPER", // Are you super?
+	"GRAVITYBOOTS", // gravity boots
+
+	// Weapon ammunition
+	"INFINITYRING",
+	"AUTOMATICRING",
+	"BOUNCERING",
+	"SCATTERRING",
+	"GRENADERING",
+	"EXPLOSIONRING",
+	"RAILRING",
+
+	// Power Stones
+	"EMERALDS", // stored like global 'emeralds' variable
+
+	// NiGHTS powerups
+	"NIGHTS_SUPERLOOP",
+	"NIGHTS_HELPER",
+	"NIGHTS_LINKFREEZE",
+
+	//for linedef exec 427
+	"NOCONTROL",
+
+	//for dyes
+	"DYE",
+
+	"JUSTLAUNCHED",
+
+	"IGNORELATCH"
+};
+
+const char *const HUDITEMS_LIST[] = {
+	"LIVES",
+
+	"RINGS",
+	"RINGSNUM",
+	"RINGSNUMTICS",
+
+	"SCORE",
+	"SCORENUM",
+
+	"TIME",
+	"MINUTES",
+	"TIMECOLON",
+	"SECONDS",
+	"TIMETICCOLON",
+	"TICS",
+
+	"SS_TOTALRINGS",
+
+	"GETRINGS",
+	"GETRINGSNUM",
+	"TIMELEFT",
+	"TIMELEFTNUM",
+	"TIMEUP",
+	"HUNTPICS",
+	"POWERUPS"
+};
+
+const char *const MENUTYPES_LIST[] = {
+	"NONE",
+
+	"MAIN",
+
+	// Single Player
+	"SP_MAIN",
+
+	"SP_LOAD",
+	"SP_PLAYER",
+
+	"SP_LEVELSELECT",
+	"SP_LEVELSTATS",
+
+	"SP_TIMEATTACK",
+	"SP_TIMEATTACK_LEVELSELECT",
+	"SP_GUESTREPLAY",
+	"SP_REPLAY",
+	"SP_GHOST",
+
+	"SP_NIGHTSATTACK",
+	"SP_NIGHTS_LEVELSELECT",
+	"SP_NIGHTS_GUESTREPLAY",
+	"SP_NIGHTS_REPLAY",
+	"SP_NIGHTS_GHOST",
+
+	// Multiplayer
+	"MP_MAIN",
+	"MP_SPLITSCREEN", // SplitServer
+	"MP_SERVER",
+	"MP_CONNECT",
+	"MP_ROOM",
+	"MP_PLAYERSETUP", // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET
+	"MP_SERVER_OPTIONS",
+
+	// Options
+	"OP_MAIN",
+
+	"OP_P1CONTROLS",
+	"OP_CHANGECONTROLS", // OP_ChangeControlsDef shared with P2
+	"OP_P1MOUSE",
+	"OP_P1JOYSTICK",
+	"OP_JOYSTICKSET", // OP_JoystickSetDef shared with P2
+	"OP_P1CAMERA",
+
+	"OP_P2CONTROLS",
+	"OP_P2MOUSE",
+	"OP_P2JOYSTICK",
+	"OP_P2CAMERA",
+
+	"OP_PLAYSTYLE",
+
+	"OP_VIDEO",
+	"OP_VIDEOMODE",
+	"OP_COLOR",
+	"OP_OPENGL",
+	"OP_OPENGL_LIGHTING",
+
+	"OP_SOUND",
+
+	"OP_SERVER",
+	"OP_MONITORTOGGLE",
+
+	"OP_DATA",
+	"OP_ADDONS",
+	"OP_SCREENSHOTS",
+	"OP_ERASEDATA",
+
+	// Extras
+	"SR_MAIN",
+	"SR_PANDORA",
+	"SR_LEVELSELECT",
+	"SR_UNLOCKCHECKLIST",
+	"SR_EMBLEMHINT",
+	"SR_PLAYER",
+	"SR_SOUNDTEST",
+
+	// Addons (Part of MISC, but let's make it our own)
+	"AD_MAIN",
+
+	// MISC
+	// "MESSAGE",
+	// "SPAUSE",
+
+	// "MPAUSE",
+	// "SCRAMBLETEAM",
+	// "CHANGETEAM",
+	// "CHANGELEVEL",
+
+	// "MAPAUSE",
+	// "HELP",
+
+	"SPECIAL"
+};
+
+struct int_const_s const INT_CONST[] = {
+	// If a mod removes some variables here,
+	// please leave the names in-tact and just set
+	// the value to 0 or something.
+
+	// integer type limits, from doomtype.h
+	// INT64 and UINT64 limits not included, they're too big for most purposes anyway
+	// signed
+	{"INT8_MIN",INT8_MIN},
+	{"INT16_MIN",INT16_MIN},
+	{"INT32_MIN",INT32_MIN},
+	{"INT8_MAX",INT8_MAX},
+	{"INT16_MAX",INT16_MAX},
+	{"INT32_MAX",INT32_MAX},
+	// unsigned
+	{"UINT8_MAX",UINT8_MAX},
+	{"UINT16_MAX",UINT16_MAX},
+	{"UINT32_MAX",UINT32_MAX},
+
+	// fixed_t constants, from m_fixed.h
+	{"FRACUNIT",FRACUNIT},
+	{"FU"      ,FRACUNIT},
+	{"FRACBITS",FRACBITS},
+
+	// doomdef.h constants
+	{"TICRATE",TICRATE},
+	{"MUSICRATE",MUSICRATE},
+	{"RING_DIST",RING_DIST},
+	{"PUSHACCEL",PUSHACCEL},
+	{"MODID",MODID}, // I don't know, I just thought it would be cool for a wad to potentially know what mod it was loaded into.
+	{"MODVERSION",MODVERSION}, // or what version of the mod this is.
+	{"CODEBASE",CODEBASE}, // or what release of SRB2 this is.
+	{"NEWTICRATE",NEWTICRATE}, // TICRATE*NEWTICRATERATIO
+	{"NEWTICRATERATIO",NEWTICRATERATIO},
+
+	// Special linedef executor tag numbers!
+	{"LE_PINCHPHASE",LE_PINCHPHASE}, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
+	{"LE_ALLBOSSESDEAD",LE_ALLBOSSESDEAD}, // All bosses in the map are dead (Egg capsule raise)
+	{"LE_BOSSDEAD",LE_BOSSDEAD}, // A boss in the map died (Chaos mode boss tally)
+	{"LE_BOSS4DROP",LE_BOSS4DROP}, // CEZ boss dropped its cage
+	{"LE_BRAKVILEATACK",LE_BRAKVILEATACK}, // Brak's doing his LOS attack, oh noes
+	{"LE_TURRET",LE_TURRET}, // THZ turret
+	{"LE_BRAKPLATFORM",LE_BRAKPLATFORM}, // v2.0 Black Eggman destroys platform
+	{"LE_CAPSULE2",LE_CAPSULE2}, // Egg Capsule
+	{"LE_CAPSULE1",LE_CAPSULE1}, // Egg Capsule
+	{"LE_CAPSULE0",LE_CAPSULE0}, // Egg Capsule
+	{"LE_KOOPA",LE_KOOPA}, // Distant cousin to Gay Bowser
+	{"LE_AXE",LE_AXE}, // MKB Axe object
+	{"LE_PARAMWIDTH",LE_PARAMWIDTH},  // If an object that calls LinedefExecute has a nonzero parameter value, this times the parameter will be subtracted. (Mostly for the purpose of coexisting bosses...)
+
+	/// \todo Get all this stuff into its own sections, maybe. Maybe.
+
+	// Frame settings
+	{"FF_FRAMEMASK",FF_FRAMEMASK},
+	{"FF_SPR2SUPER",FF_SPR2SUPER},
+	{"FF_SPR2ENDSTATE",FF_SPR2ENDSTATE},
+	{"FF_SPR2MIDSTART",FF_SPR2MIDSTART},
+	{"FF_ANIMATE",FF_ANIMATE},
+	{"FF_RANDOMANIM",FF_RANDOMANIM},
+	{"FF_GLOBALANIM",FF_GLOBALANIM},
+	{"FF_FULLBRIGHT",FF_FULLBRIGHT},
+	{"FF_VERTICALFLIP",FF_VERTICALFLIP},
+	{"FF_HORIZONTALFLIP",FF_HORIZONTALFLIP},
+	{"FF_PAPERSPRITE",FF_PAPERSPRITE},
+	{"FF_TRANSMASK",FF_TRANSMASK},
+	{"FF_TRANSSHIFT",FF_TRANSSHIFT},
+	// new preshifted translucency (used in source)
+	{"FF_TRANS10",FF_TRANS10},
+	{"FF_TRANS20",FF_TRANS20},
+	{"FF_TRANS30",FF_TRANS30},
+	{"FF_TRANS40",FF_TRANS40},
+	{"FF_TRANS50",FF_TRANS50},
+	{"FF_TRANS60",FF_TRANS60},
+	{"FF_TRANS70",FF_TRANS70},
+	{"FF_TRANS80",FF_TRANS80},
+	{"FF_TRANS90",FF_TRANS90},
+	// compatibility
+	// Transparency for SOCs is pre-shifted
+	{"TR_TRANS10",tr_trans10<<FF_TRANSSHIFT},
+	{"TR_TRANS20",tr_trans20<<FF_TRANSSHIFT},
+	{"TR_TRANS30",tr_trans30<<FF_TRANSSHIFT},
+	{"TR_TRANS40",tr_trans40<<FF_TRANSSHIFT},
+	{"TR_TRANS50",tr_trans50<<FF_TRANSSHIFT},
+	{"TR_TRANS60",tr_trans60<<FF_TRANSSHIFT},
+	{"TR_TRANS70",tr_trans70<<FF_TRANSSHIFT},
+	{"TR_TRANS80",tr_trans80<<FF_TRANSSHIFT},
+	{"TR_TRANS90",tr_trans90<<FF_TRANSSHIFT},
+	// Transparency for Lua is not, unless capitalized as above.
+	{"tr_trans10",tr_trans10},
+	{"tr_trans20",tr_trans20},
+	{"tr_trans30",tr_trans30},
+	{"tr_trans40",tr_trans40},
+	{"tr_trans50",tr_trans50},
+	{"tr_trans60",tr_trans60},
+	{"tr_trans70",tr_trans70},
+	{"tr_trans80",tr_trans80},
+	{"tr_trans90",tr_trans90},
+	{"NUMTRANSMAPS",NUMTRANSMAPS},
+
+	// Alpha styles (blend modes)
+	{"AST_COPY",AST_COPY},
+	{"AST_TRANSLUCENT",AST_TRANSLUCENT},
+	{"AST_ADD",AST_ADD},
+	{"AST_SUBTRACT",AST_SUBTRACT},
+	{"AST_REVERSESUBTRACT",AST_REVERSESUBTRACT},
+	{"AST_MODULATE",AST_MODULATE},
+	{"AST_OVERLAY",AST_OVERLAY},
+
+	// Render flags
+	{"RF_HORIZONTALFLIP",RF_HORIZONTALFLIP},
+	{"RF_VERTICALFLIP",RF_VERTICALFLIP},
+	{"RF_ABSOLUTEOFFSETS",RF_ABSOLUTEOFFSETS},
+	{"RF_FLIPOFFSETS",RF_FLIPOFFSETS},
+	{"RF_SPLATMASK",RF_SPLATMASK},
+	{"RF_SLOPESPLAT",RF_SLOPESPLAT},
+	{"RF_OBJECTSLOPESPLAT",RF_OBJECTSLOPESPLAT},
+	{"RF_NOSPLATBILLBOARD",RF_NOSPLATBILLBOARD},
+	{"RF_NOSPLATROLLANGLE",RF_NOSPLATROLLANGLE},
+	{"RF_BLENDMASK",RF_BLENDMASK},
+	{"RF_FULLBRIGHT",RF_FULLBRIGHT},
+	{"RF_FULLDARK",RF_FULLDARK},
+	{"RF_NOCOLORMAPS",RF_NOCOLORMAPS},
+	{"RF_SPRITETYPEMASK",RF_SPRITETYPEMASK},
+	{"RF_PAPERSPRITE",RF_PAPERSPRITE},
+	{"RF_FLOORSPRITE",RF_FLOORSPRITE},
+	{"RF_SHADOWDRAW",RF_SHADOWDRAW},
+	{"RF_SHADOWEFFECTS",RF_SHADOWEFFECTS},
+	{"RF_DROPSHADOW",RF_DROPSHADOW},
+
+	// Level flags
+	{"LF_SCRIPTISFILE",LF_SCRIPTISFILE},
+	{"LF_SPEEDMUSIC",LF_SPEEDMUSIC},
+	{"LF_NOSSMUSIC",LF_NOSSMUSIC},
+	{"LF_NORELOAD",LF_NORELOAD},
+	{"LF_NOZONE",LF_NOZONE},
+	{"LF_SAVEGAME",LF_SAVEGAME},
+	{"LF_MIXNIGHTSCOUNTDOWN",LF_MIXNIGHTSCOUNTDOWN},
+	{"LF_NOTITLECARDFIRST",LF_NOTITLECARDFIRST},
+	{"LF_NOTITLECARDRESPAWN",LF_NOTITLECARDRESPAWN},
+	{"LF_NOTITLECARDRECORDATTACK",LF_NOTITLECARDRECORDATTACK},
+	{"LF_NOTITLECARD",LF_NOTITLECARD},
+	{"LF_WARNINGTITLE",LF_WARNINGTITLE},
+	// And map flags
+	{"LF2_HIDEINMENU",LF2_HIDEINMENU},
+	{"LF2_HIDEINSTATS",LF2_HIDEINSTATS},
+	{"LF2_RECORDATTACK",LF2_RECORDATTACK},
+	{"LF2_NIGHTSATTACK",LF2_NIGHTSATTACK},
+	{"LF2_NOVISITNEEDED",LF2_NOVISITNEEDED},
+	{"LF2_WIDEICON",LF2_WIDEICON},
+
+	// Emeralds
+	{"EMERALD1",EMERALD1},
+	{"EMERALD2",EMERALD2},
+	{"EMERALD3",EMERALD3},
+	{"EMERALD4",EMERALD4},
+	{"EMERALD5",EMERALD5},
+	{"EMERALD6",EMERALD6},
+	{"EMERALD7",EMERALD7},
+
+	// SKINCOLOR_ doesn't include these..!
+	{"MAXSKINCOLORS",MAXSKINCOLORS},
+	{"FIRSTSUPERCOLOR",FIRSTSUPERCOLOR},
+	{"NUMSUPERCOLORS",NUMSUPERCOLORS},
+
+	// Precipitation
+	{"PRECIP_NONE",PRECIP_NONE},
+	{"PRECIP_STORM",PRECIP_STORM},
+	{"PRECIP_SNOW",PRECIP_SNOW},
+	{"PRECIP_RAIN",PRECIP_RAIN},
+	{"PRECIP_BLANK",PRECIP_BLANK},
+	{"PRECIP_STORM_NORAIN",PRECIP_STORM_NORAIN},
+	{"PRECIP_STORM_NOSTRIKES",PRECIP_STORM_NOSTRIKES},
+
+	// Shields
+	{"SH_NONE",SH_NONE},
+	// Shield flags
+	{"SH_PROTECTFIRE",SH_PROTECTFIRE},
+	{"SH_PROTECTWATER",SH_PROTECTWATER},
+	{"SH_PROTECTELECTRIC",SH_PROTECTELECTRIC},
+	{"SH_PROTECTSPIKE",SH_PROTECTSPIKE},
+	// Indivisible shields
+	{"SH_PITY",SH_PITY},
+	{"SH_WHIRLWIND",SH_WHIRLWIND},
+	{"SH_ARMAGEDDON",SH_ARMAGEDDON},
+	{"SH_PINK",SH_PINK},
+	// normal shields that use flags
+	{"SH_ATTRACT",SH_ATTRACT},
+	{"SH_ELEMENTAL",SH_ELEMENTAL},
+	// Sonic 3 shields
+	{"SH_FLAMEAURA",SH_FLAMEAURA},
+	{"SH_BUBBLEWRAP",SH_BUBBLEWRAP},
+	{"SH_THUNDERCOIN",SH_THUNDERCOIN},
+	// The force shield uses the lower 8 bits to count how many extra hits are left.
+	{"SH_FORCE",SH_FORCE},
+	{"SH_FORCEHP",SH_FORCEHP}, // to be used as a bitmask only
+	// Mostly for use with Mario mode.
+	{"SH_FIREFLOWER",SH_FIREFLOWER},
+	{"SH_STACK",SH_STACK},
+	{"SH_NOSTACK",SH_NOSTACK},
+
+	// Carrying
+	{"CR_NONE",CR_NONE},
+	{"CR_GENERIC",CR_GENERIC},
+	{"CR_PLAYER",CR_PLAYER},
+	{"CR_NIGHTSMODE",CR_NIGHTSMODE},
+	{"CR_NIGHTSFALL",CR_NIGHTSFALL},
+	{"CR_BRAKGOOP",CR_BRAKGOOP},
+	{"CR_ZOOMTUBE",CR_ZOOMTUBE},
+	{"CR_ROPEHANG",CR_ROPEHANG},
+	{"CR_MACESPIN",CR_MACESPIN},
+	{"CR_MINECART",CR_MINECART},
+	{"CR_ROLLOUT",CR_ROLLOUT},
+	{"CR_PTERABYTE",CR_PTERABYTE},
+	{"CR_DUSTDEVIL",CR_DUSTDEVIL},
+
+	// Ring weapons (ringweapons_t)
+	// Useful for A_GiveWeapon
+	{"RW_AUTO",RW_AUTO},
+	{"RW_BOUNCE",RW_BOUNCE},
+	{"RW_SCATTER",RW_SCATTER},
+	{"RW_GRENADE",RW_GRENADE},
+	{"RW_EXPLODE",RW_EXPLODE},
+	{"RW_RAIL",RW_RAIL},
+
+	// Character flags (skinflags_t)
+	{"SF_SUPER",SF_SUPER},
+	{"SF_NOSUPERSPIN",SF_NOSUPERSPIN},
+	{"SF_NOSPINDASHDUST",SF_NOSPINDASHDUST},
+	{"SF_HIRES",SF_HIRES},
+	{"SF_NOSKID",SF_NOSKID},
+	{"SF_NOSPEEDADJUST",SF_NOSPEEDADJUST},
+	{"SF_RUNONWATER",SF_RUNONWATER},
+	{"SF_NOJUMPSPIN",SF_NOJUMPSPIN},
+	{"SF_NOJUMPDAMAGE",SF_NOJUMPDAMAGE},
+	{"SF_STOMPDAMAGE",SF_STOMPDAMAGE},
+	{"SF_MARIODAMAGE",SF_MARIODAMAGE},
+	{"SF_MACHINE",SF_MACHINE},
+	{"SF_DASHMODE",SF_DASHMODE},
+	{"SF_FASTEDGE",SF_FASTEDGE},
+	{"SF_MULTIABILITY",SF_MULTIABILITY},
+	{"SF_NONIGHTSROTATION",SF_NONIGHTSROTATION},
+	{"SF_NONIGHTSSUPER",SF_NONIGHTSSUPER},
+	{"SF_NOSUPERSPRITES",SF_NOSUPERSPRITES},
+	{"SF_NOSUPERJUMPBOOST",SF_NOSUPERJUMPBOOST},
+	{"SF_CANBUSTWALLS",SF_CANBUSTWALLS},
+	{"SF_NOSHIELDABILITY",SF_NOSHIELDABILITY},
+
+	// Dashmode constants
+	{"DASHMODE_THRESHOLD",DASHMODE_THRESHOLD},
+	{"DASHMODE_MAX",DASHMODE_MAX},
+
+	// Character abilities!
+	// Primary
+	{"CA_NONE",CA_NONE}, // now slot 0!
+	{"CA_THOK",CA_THOK},
+	{"CA_FLY",CA_FLY},
+	{"CA_GLIDEANDCLIMB",CA_GLIDEANDCLIMB},
+	{"CA_HOMINGTHOK",CA_HOMINGTHOK},
+	{"CA_DOUBLEJUMP",CA_DOUBLEJUMP},
+	{"CA_FLOAT",CA_FLOAT},
+	{"CA_SLOWFALL",CA_SLOWFALL},
+	{"CA_SWIM",CA_SWIM},
+	{"CA_TELEKINESIS",CA_TELEKINESIS},
+	{"CA_FALLSWITCH",CA_FALLSWITCH},
+	{"CA_JUMPBOOST",CA_JUMPBOOST},
+	{"CA_AIRDRILL",CA_AIRDRILL},
+	{"CA_JUMPTHOK",CA_JUMPTHOK},
+	{"CA_BOUNCE",CA_BOUNCE},
+	{"CA_TWINSPIN",CA_TWINSPIN},
+	// Secondary
+	{"CA2_NONE",CA2_NONE}, // now slot 0!
+	{"CA2_SPINDASH",CA2_SPINDASH},
+	{"CA2_GUNSLINGER",CA2_GUNSLINGER},
+	{"CA2_MELEE",CA2_MELEE},
+
+	// Sound flags
+	{"SF_TOTALLYSINGLE",SF_TOTALLYSINGLE},
+	{"SF_NOMULTIPLESOUND",SF_NOMULTIPLESOUND},
+	{"SF_OUTSIDESOUND",SF_OUTSIDESOUND},
+	{"SF_X4AWAYSOUND",SF_X4AWAYSOUND},
+	{"SF_X8AWAYSOUND",SF_X8AWAYSOUND},
+	{"SF_NOINTERRUPT",SF_NOINTERRUPT},
+	{"SF_X2AWAYSOUND",SF_X2AWAYSOUND},
+
+	// Global emblem var flags
+	{"GE_NIGHTSPULL",GE_NIGHTSPULL},
+	{"GE_NIGHTSITEM",GE_NIGHTSITEM},
+
+	// Map emblem var flags
+	{"ME_ALLEMERALDS",ME_ALLEMERALDS},
+	{"ME_ULTIMATE",ME_ULTIMATE},
+	{"ME_PERFECT",ME_PERFECT},
+
+	// p_local.h constants
+	{"FLOATSPEED",FLOATSPEED},
+	{"MAXSTEPMOVE",MAXSTEPMOVE},
+	{"USERANGE",USERANGE},
+	{"MELEERANGE",MELEERANGE},
+	{"MISSILERANGE",MISSILERANGE},
+	{"ONFLOORZ",ONFLOORZ}, // INT32_MIN
+	{"ONCEILINGZ",ONCEILINGZ}, //INT32_MAX
+	// for P_FlashPal
+	{"PAL_WHITE",PAL_WHITE},
+	{"PAL_MIXUP",PAL_MIXUP},
+	{"PAL_RECYCLE",PAL_RECYCLE},
+	{"PAL_NUKE",PAL_NUKE},
+	// for P_DamageMobj
+	//// Damage types
+	{"DMG_WATER",DMG_WATER},
+	{"DMG_FIRE",DMG_FIRE},
+	{"DMG_ELECTRIC",DMG_ELECTRIC},
+	{"DMG_SPIKE",DMG_SPIKE},
+	{"DMG_NUKE",DMG_NUKE},
+	//// Death types
+	{"DMG_INSTAKILL",DMG_INSTAKILL},
+	{"DMG_DROWNED",DMG_DROWNED},
+	{"DMG_SPACEDROWN",DMG_SPACEDROWN},
+	{"DMG_DEATHPIT",DMG_DEATHPIT},
+	{"DMG_CRUSHED",DMG_CRUSHED},
+	{"DMG_SPECTATOR",DMG_SPECTATOR},
+	//// Masks
+	{"DMG_CANHURTSELF",DMG_CANHURTSELF},
+	{"DMG_DEATHMASK",DMG_DEATHMASK},
+
+	// Intermission types
+	{"int_none",int_none},
+	{"int_coop",int_coop},
+	{"int_match",int_match},
+	{"int_teammatch",int_teammatch},
+	//{"int_tag",int_tag},
+	{"int_ctf",int_ctf},
+	{"int_spec",int_spec},
+	{"int_race",int_race},
+	{"int_comp",int_comp},
+
+	// Jingles (jingletype_t)
+	{"JT_NONE",JT_NONE},
+	{"JT_OTHER",JT_OTHER},
+	{"JT_MASTER",JT_MASTER},
+	{"JT_1UP",JT_1UP},
+	{"JT_SHOES",JT_SHOES},
+	{"JT_INV",JT_INV},
+	{"JT_MINV",JT_MINV},
+	{"JT_DROWN",JT_DROWN},
+	{"JT_SUPER",JT_SUPER},
+	{"JT_GOVER",JT_GOVER},
+	{"JT_NIGHTSTIMEOUT",JT_NIGHTSTIMEOUT},
+	{"JT_SSTIMEOUT",JT_SSTIMEOUT},
+	// {"JT_LCLEAR",JT_LCLEAR},
+	// {"JT_RACENT",JT_RACENT},
+	// {"JT_CONTSC",JT_CONTSC},
+
+	// Player state (playerstate_t)
+	{"PST_LIVE",PST_LIVE}, // Playing or camping.
+	{"PST_DEAD",PST_DEAD}, // Dead on the ground, view follows killer.
+	{"PST_REBORN",PST_REBORN}, // Ready to restart/respawn???
+
+	// Player animation (panim_t)
+	{"PA_ETC",PA_ETC},
+	{"PA_IDLE",PA_IDLE},
+	{"PA_EDGE",PA_EDGE},
+	{"PA_WALK",PA_WALK},
+	{"PA_RUN",PA_RUN},
+	{"PA_DASH",PA_DASH},
+	{"PA_PAIN",PA_PAIN},
+	{"PA_ROLL",PA_ROLL},
+	{"PA_JUMP",PA_JUMP},
+	{"PA_SPRING",PA_SPRING},
+	{"PA_FALL",PA_FALL},
+	{"PA_ABILITY",PA_ABILITY},
+	{"PA_ABILITY2",PA_ABILITY2},
+	{"PA_RIDE",PA_RIDE},
+
+	// Current weapon
+	{"WEP_AUTO",WEP_AUTO},
+	{"WEP_BOUNCE",WEP_BOUNCE},
+	{"WEP_SCATTER",WEP_SCATTER},
+	{"WEP_GRENADE",WEP_GRENADE},
+	{"WEP_EXPLODE",WEP_EXPLODE},
+	{"WEP_RAIL",WEP_RAIL},
+	{"NUM_WEAPONS",NUM_WEAPONS},
+
+	// Value for infinite lives
+	{"INFLIVES",INFLIVES},
+
+	// Got Flags, for player->gotflag!
+	// Used to be MF_ for some stupid reason, now they're GF_ to stop them looking like mobjflags
+	{"GF_REDFLAG",GF_REDFLAG},
+	{"GF_BLUEFLAG",GF_BLUEFLAG},
+
+	// Customisable sounds for Skins, from sounds.h
+	{"SKSSPIN",SKSSPIN},
+	{"SKSPUTPUT",SKSPUTPUT},
+	{"SKSPUDPUD",SKSPUDPUD},
+	{"SKSPLPAN1",SKSPLPAN1}, // Ouchies
+	{"SKSPLPAN2",SKSPLPAN2},
+	{"SKSPLPAN3",SKSPLPAN3},
+	{"SKSPLPAN4",SKSPLPAN4},
+	{"SKSPLDET1",SKSPLDET1}, // Deaths
+	{"SKSPLDET2",SKSPLDET2},
+	{"SKSPLDET3",SKSPLDET3},
+	{"SKSPLDET4",SKSPLDET4},
+	{"SKSPLVCT1",SKSPLVCT1}, // Victories
+	{"SKSPLVCT2",SKSPLVCT2},
+	{"SKSPLVCT3",SKSPLVCT3},
+	{"SKSPLVCT4",SKSPLVCT4},
+	{"SKSTHOK",SKSTHOK},
+	{"SKSSPNDSH",SKSSPNDSH},
+	{"SKSZOOM",SKSZOOM},
+	{"SKSSKID",SKSSKID},
+	{"SKSGASP",SKSGASP},
+	{"SKSJUMP",SKSJUMP},
+
+	// 3D Floor/Fake Floor/FOF/whatever flags
+	{"FF_EXISTS",FF_EXISTS},                   ///< Always set, to check for validity.
+	{"FF_BLOCKPLAYER",FF_BLOCKPLAYER},         ///< Solid to player, but nothing else
+	{"FF_BLOCKOTHERS",FF_BLOCKOTHERS},         ///< Solid to everything but player
+	{"FF_SOLID",FF_SOLID},                     ///< Clips things.
+	{"FF_RENDERSIDES",FF_RENDERSIDES},         ///< Renders the sides.
+	{"FF_RENDERPLANES",FF_RENDERPLANES},       ///< Renders the floor/ceiling.
+	{"FF_RENDERALL",FF_RENDERALL},             ///< Renders everything.
+	{"FF_SWIMMABLE",FF_SWIMMABLE},             ///< Is a water block.
+	{"FF_NOSHADE",FF_NOSHADE},                 ///< Messes with the lighting?
+	{"FF_CUTSOLIDS",FF_CUTSOLIDS},             ///< Cuts out hidden solid pixels.
+	{"FF_CUTEXTRA",FF_CUTEXTRA},               ///< Cuts out hidden translucent pixels.
+	{"FF_CUTLEVEL",FF_CUTLEVEL},               ///< Cuts out all hidden pixels.
+	{"FF_CUTSPRITES",FF_CUTSPRITES},           ///< Final step in making 3D water.
+	{"FF_BOTHPLANES",FF_BOTHPLANES},           ///< Render inside and outside planes.
+	{"FF_EXTRA",FF_EXTRA},                     ///< Gets cut by ::FF_CUTEXTRA.
+	{"FF_TRANSLUCENT",FF_TRANSLUCENT},         ///< See through!
+	{"FF_FOG",FF_FOG},                         ///< Fog "brush."
+	{"FF_INVERTPLANES",FF_INVERTPLANES},       ///< Only render inside planes.
+	{"FF_ALLSIDES",FF_ALLSIDES},               ///< Render inside and outside sides.
+	{"FF_INVERTSIDES",FF_INVERTSIDES},         ///< Only render inside sides.
+	{"FF_DOUBLESHADOW",FF_DOUBLESHADOW},       ///< Make two lightlist entries to reset light?
+	{"FF_FLOATBOB",FF_FLOATBOB},               ///< Floats on water and bobs if you step on it.
+	{"FF_NORETURN",FF_NORETURN},               ///< Used with ::FF_CRUMBLE. Will not return to its original position after falling.
+	{"FF_CRUMBLE",FF_CRUMBLE},                 ///< Falls 2 seconds after being stepped on, and randomly brings all touching crumbling 3dfloors down with it, providing their master sectors share the same tag (allows crumble platforms above or below, to also exist).
+	{"FF_SHATTERBOTTOM",FF_SHATTERBOTTOM},     ///< Used with ::FF_BUSTUP. Like FF_SHATTER, but only breaks from the bottom. Good for springing up through rubble.
+	{"FF_MARIO",FF_MARIO},                     ///< Acts like a question block when hit from underneath. Goodie spawned at top is determined by master sector.
+	{"FF_BUSTUP",FF_BUSTUP},                   ///< You can spin through/punch this block and it will crumble!
+	{"FF_QUICKSAND",FF_QUICKSAND},             ///< Quicksand!
+	{"FF_PLATFORM",FF_PLATFORM},               ///< You can jump up through this to the top.
+	{"FF_REVERSEPLATFORM",FF_REVERSEPLATFORM}, ///< A fall-through floor in normal gravity, a platform in reverse gravity.
+	{"FF_INTANGIBLEFLATS",FF_INTANGIBLEFLATS}, ///< Both flats are intangible, but the sides are still solid.
+	{"FF_INTANGABLEFLATS",FF_INTANGIBLEFLATS}, ///< Both flats are intangable, but the sides are still solid.
+	{"FF_SHATTER",FF_SHATTER},                 ///< Used with ::FF_BUSTUP. Bustable on mere touch.
+	{"FF_SPINBUST",FF_SPINBUST},               ///< Used with ::FF_BUSTUP. Also bustable if you're in your spinning frames.
+	{"FF_STRONGBUST",FF_STRONGBUST},           ///< Used with ::FF_BUSTUP. Only bustable by "strong" characters (Knuckles) and abilities (bouncing, twinspin, melee).
+	{"FF_RIPPLE",FF_RIPPLE},                   ///< Ripple the flats
+	{"FF_COLORMAPONLY",FF_COLORMAPONLY},       ///< Only copy the colormap, not the lightlevel
+	{"FF_GOOWATER",FF_GOOWATER},               ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
+
+	// PolyObject flags
+	{"POF_CLIPLINES",POF_CLIPLINES},               ///< Test against lines for collision
+	{"POF_CLIPPLANES",POF_CLIPPLANES},             ///< Test against tops and bottoms for collision
+	{"POF_SOLID",POF_SOLID},                       ///< Clips things.
+	{"POF_TESTHEIGHT",POF_TESTHEIGHT},             ///< Test line collision with heights
+	{"POF_RENDERSIDES",POF_RENDERSIDES},           ///< Renders the sides.
+	{"POF_RENDERTOP",POF_RENDERTOP},               ///< Renders the top.
+	{"POF_RENDERBOTTOM",POF_RENDERBOTTOM},         ///< Renders the bottom.
+	{"POF_RENDERPLANES",POF_RENDERPLANES},         ///< Renders top and bottom.
+	{"POF_RENDERALL",POF_RENDERALL},               ///< Renders everything.
+	{"POF_INVERT",POF_INVERT},                     ///< Inverts collision (like a cage).
+	{"POF_INVERTPLANES",POF_INVERTPLANES},         ///< Render inside planes.
+	{"POF_INVERTPLANESONLY",POF_INVERTPLANESONLY}, ///< Only render inside planes.
+	{"POF_PUSHABLESTOP",POF_PUSHABLESTOP},         ///< Pushables will stop movement.
+	{"POF_LDEXEC",POF_LDEXEC},                     ///< This PO triggers a linedef executor.
+	{"POF_ONESIDE",POF_ONESIDE},                   ///< Only use the first side of the linedef.
+	{"POF_NOSPECIALS",POF_NOSPECIALS},             ///< Don't apply sector specials.
+	{"POF_SPLAT",POF_SPLAT},                       ///< Use splat flat renderer (treat cyan pixels as invisible).
+
+#ifdef HAVE_LUA_SEGS
+	// Node flags
+	{"NF_SUBSECTOR",NF_SUBSECTOR}, // Indicate a leaf.
+#endif
+
+	// Slope flags
+	{"SL_NOPHYSICS",SL_NOPHYSICS},
+	{"SL_DYNAMIC",SL_DYNAMIC},
+
+	// Angles
+	{"ANG1",ANG1},
+	{"ANG2",ANG2},
+	{"ANG10",ANG10},
+	{"ANG15",ANG15},
+	{"ANG20",ANG20},
+	{"ANG30",ANG30},
+	{"ANG60",ANG60},
+	{"ANG64h",ANG64h},
+	{"ANG105",ANG105},
+	{"ANG210",ANG210},
+	{"ANG255",ANG255},
+	{"ANG340",ANG340},
+	{"ANG350",ANG350},
+	{"ANGLE_11hh",ANGLE_11hh},
+	{"ANGLE_22h",ANGLE_22h},
+	{"ANGLE_45",ANGLE_45},
+	{"ANGLE_67h",ANGLE_67h},
+	{"ANGLE_90",ANGLE_90},
+	{"ANGLE_112h",ANGLE_112h},
+	{"ANGLE_135",ANGLE_135},
+	{"ANGLE_157h",ANGLE_157h},
+	{"ANGLE_180",ANGLE_180},
+	{"ANGLE_202h",ANGLE_202h},
+	{"ANGLE_225",ANGLE_225},
+	{"ANGLE_247h",ANGLE_247h},
+	{"ANGLE_270",ANGLE_270},
+	{"ANGLE_292h",ANGLE_292h},
+	{"ANGLE_315",ANGLE_315},
+	{"ANGLE_337h",ANGLE_337h},
+	{"ANGLE_MAX",ANGLE_MAX},
+
+	// P_Chase directions (dirtype_t)
+	{"DI_NODIR",DI_NODIR},
+	{"DI_EAST",DI_EAST},
+	{"DI_NORTHEAST",DI_NORTHEAST},
+	{"DI_NORTH",DI_NORTH},
+	{"DI_NORTHWEST",DI_NORTHWEST},
+	{"DI_WEST",DI_WEST},
+	{"DI_SOUTHWEST",DI_SOUTHWEST},
+	{"DI_SOUTH",DI_SOUTH},
+	{"DI_SOUTHEAST",DI_SOUTHEAST},
+	{"NUMDIRS",NUMDIRS},
+
+	// Sprite rotation axis (rotaxis_t)
+	{"ROTAXIS_X",ROTAXIS_X},
+	{"ROTAXIS_Y",ROTAXIS_Y},
+	{"ROTAXIS_Z",ROTAXIS_Z},
+
+	// Buttons (ticcmd_t)
+	{"BT_WEAPONMASK",BT_WEAPONMASK}, //our first four bits.
+	{"BT_WEAPONNEXT",BT_WEAPONNEXT},
+	{"BT_WEAPONPREV",BT_WEAPONPREV},
+	{"BT_ATTACK",BT_ATTACK}, // shoot rings
+	{"BT_SPIN",BT_SPIN},
+	{"BT_CAMLEFT",BT_CAMLEFT}, // turn camera left
+	{"BT_CAMRIGHT",BT_CAMRIGHT}, // turn camera right
+	{"BT_TOSSFLAG",BT_TOSSFLAG},
+	{"BT_JUMP",BT_JUMP},
+	{"BT_FIRENORMAL",BT_FIRENORMAL}, // Fire a normal ring no matter what
+	{"BT_CUSTOM1",BT_CUSTOM1}, // Lua customizable
+	{"BT_CUSTOM2",BT_CUSTOM2}, // Lua customizable
+	{"BT_CUSTOM3",BT_CUSTOM3}, // Lua customizable
+
+	// Lua command registration flags
+	{"COM_ADMIN",COM_ADMIN},
+	{"COM_SPLITSCREEN",COM_SPLITSCREEN},
+	{"COM_LOCAL",COM_LOCAL},
+
+	// cvflags_t
+	{"CV_SAVE",CV_SAVE},
+	{"CV_CALL",CV_CALL},
+	{"CV_NETVAR",CV_NETVAR},
+	{"CV_NOINIT",CV_NOINIT},
+	{"CV_FLOAT",CV_FLOAT},
+	{"CV_NOTINNET",CV_NOTINNET},
+	{"CV_MODIFIED",CV_MODIFIED},
+	{"CV_SHOWMODIF",CV_SHOWMODIF},
+	{"CV_SHOWMODIFONETIME",CV_SHOWMODIFONETIME},
+	{"CV_NOSHOWHELP",CV_NOSHOWHELP},
+	{"CV_HIDEN",CV_HIDEN},
+	{"CV_HIDDEN",CV_HIDEN},
+	{"CV_CHEAT",CV_CHEAT},
+	{"CV_NOLUA",CV_NOLUA},
+
+	// v_video flags
+	{"V_NOSCALEPATCH",V_NOSCALEPATCH},
+	{"V_SMALLSCALEPATCH",V_SMALLSCALEPATCH},
+	{"V_MEDSCALEPATCH",V_MEDSCALEPATCH},
+	{"V_6WIDTHSPACE",V_6WIDTHSPACE},
+	{"V_OLDSPACING",V_OLDSPACING},
+	{"V_MONOSPACE",V_MONOSPACE},
+
+	{"V_MAGENTAMAP",V_MAGENTAMAP},
+	{"V_YELLOWMAP",V_YELLOWMAP},
+	{"V_GREENMAP",V_GREENMAP},
+	{"V_BLUEMAP",V_BLUEMAP},
+	{"V_REDMAP",V_REDMAP},
+	{"V_GRAYMAP",V_GRAYMAP},
+	{"V_ORANGEMAP",V_ORANGEMAP},
+	{"V_SKYMAP",V_SKYMAP},
+	{"V_PURPLEMAP",V_PURPLEMAP},
+	{"V_AQUAMAP",V_AQUAMAP},
+	{"V_PERIDOTMAP",V_PERIDOTMAP},
+	{"V_AZUREMAP",V_AZUREMAP},
+	{"V_BROWNMAP",V_BROWNMAP},
+	{"V_ROSYMAP",V_ROSYMAP},
+	{"V_INVERTMAP",V_INVERTMAP},
+
+	{"V_TRANSLUCENT",V_TRANSLUCENT},
+	{"V_10TRANS",V_10TRANS},
+	{"V_20TRANS",V_20TRANS},
+	{"V_30TRANS",V_30TRANS},
+	{"V_40TRANS",V_40TRANS},
+	{"V_50TRANS",V_TRANSLUCENT}, // alias
+	{"V_60TRANS",V_60TRANS},
+	{"V_70TRANS",V_70TRANS},
+	{"V_80TRANS",V_80TRANS},
+	{"V_90TRANS",V_90TRANS},
+	{"V_HUDTRANSHALF",V_HUDTRANSHALF},
+	{"V_HUDTRANS",V_HUDTRANS},
+	{"V_HUDTRANSDOUBLE",V_HUDTRANSDOUBLE},
+	{"V_AUTOFADEOUT",V_AUTOFADEOUT},
+	{"V_RETURN8",V_RETURN8},
+	{"V_OFFSET",V_OFFSET},
+	{"V_ALLOWLOWERCASE",V_ALLOWLOWERCASE},
+	{"V_FLIP",V_FLIP},
+	{"V_CENTERNAMETAG",V_CENTERNAMETAG},
+	{"V_SNAPTOTOP",V_SNAPTOTOP},
+	{"V_SNAPTOBOTTOM",V_SNAPTOBOTTOM},
+	{"V_SNAPTOLEFT",V_SNAPTOLEFT},
+	{"V_SNAPTORIGHT",V_SNAPTORIGHT},
+	{"V_WRAPX",V_WRAPX},
+	{"V_WRAPY",V_WRAPY},
+	{"V_NOSCALESTART",V_NOSCALESTART},
+	{"V_PERPLAYER",V_PERPLAYER},
+
+	{"V_PARAMMASK",V_PARAMMASK},
+	{"V_SCALEPATCHMASK",V_SCALEPATCHMASK},
+	{"V_SPACINGMASK",V_SPACINGMASK},
+	{"V_CHARCOLORMASK",V_CHARCOLORMASK},
+	{"V_ALPHAMASK",V_ALPHAMASK},
+
+	{"V_CHARCOLORSHIFT",V_CHARCOLORSHIFT},
+	{"V_ALPHASHIFT",V_ALPHASHIFT},
+
+	//Kick Reasons
+	{"KR_KICK",KR_KICK},
+	{"KR_PINGLIMIT",KR_PINGLIMIT},
+	{"KR_SYNCH",KR_SYNCH},
+	{"KR_TIMEOUT",KR_TIMEOUT},
+	{"KR_BAN",KR_BAN},
+	{"KR_LEAVE",KR_LEAVE},
+
+	// translation colormaps
+	{"TC_DEFAULT",TC_DEFAULT},
+	{"TC_BOSS",TC_BOSS},
+	{"TC_METALSONIC",TC_METALSONIC},
+	{"TC_ALLWHITE",TC_ALLWHITE},
+	{"TC_RAINBOW",TC_RAINBOW},
+	{"TC_BLINK",TC_BLINK},
+	{"TC_DASHMODE",TC_DASHMODE},
+
+	// marathonmode flags
+	{"MA_INIT",MA_INIT},
+	{"MA_RUNNING",MA_RUNNING},
+	{"MA_NOCUTSCENES",MA_NOCUTSCENES},
+	{"MA_INGAME",MA_INGAME},
+
+	// music types
+	{"MU_NONE", MU_NONE},
+	{"MU_WAV", MU_WAV},
+	{"MU_MOD", MU_MOD},
+	{"MU_MID", MU_MID},
+	{"MU_OGG", MU_OGG},
+	{"MU_MP3", MU_MP3},
+	{"MU_FLAC", MU_FLAC},
+	{"MU_GME", MU_GME},
+	{"MU_MOD_EX", MU_MOD_EX},
+	{"MU_MID_EX", MU_MID_EX},
+
+	// gamestates
+	{"GS_NULL",GS_NULL},
+	{"GS_LEVEL",GS_LEVEL},
+	{"GS_INTERMISSION",GS_INTERMISSION},
+	{"GS_CONTINUING",GS_CONTINUING},
+	{"GS_TITLESCREEN",GS_TITLESCREEN},
+	{"GS_TIMEATTACK",GS_TIMEATTACK},
+	{"GS_CREDITS",GS_CREDITS},
+	{"GS_EVALUATION",GS_EVALUATION},
+	{"GS_GAMEEND",GS_GAMEEND},
+	{"GS_INTRO",GS_INTRO},
+	{"GS_ENDING",GS_ENDING},
+	{"GS_CUTSCENE",GS_CUTSCENE},
+	{"GS_DEDICATEDSERVER",GS_DEDICATEDSERVER},
+	{"GS_WAITINGPLAYERS",GS_WAITINGPLAYERS},
+
+	{NULL,0}
+};
+
+// For this to work compile-time without being in this file,
+// this function would need to check sizes at runtime, without sizeof
+void DEH_TableCheck(void)
+{
+#if defined(_DEBUG) || defined(PARANOIA)
+	const size_t dehstates = sizeof(STATE_LIST)/sizeof(const char*);
+	const size_t dehmobjs  = sizeof(MOBJTYPE_LIST)/sizeof(const char*);
+	const size_t dehpowers = sizeof(POWERS_LIST)/sizeof(const char*);
+	const size_t dehcolors = sizeof(COLOR_ENUMS)/sizeof(const char*);
+
+	if (dehstates != S_FIRSTFREESLOT)
+		I_Error("You forgot to update the Dehacked states list, you dolt!\n(%d states defined, versus %s in the Dehacked list)\n", S_FIRSTFREESLOT, sizeu1(dehstates));
+
+	if (dehmobjs != MT_FIRSTFREESLOT)
+		I_Error("You forgot to update the Dehacked mobjtype list, you dolt!\n(%d mobj types defined, versus %s in the Dehacked list)\n", MT_FIRSTFREESLOT, sizeu1(dehmobjs));
+
+	if (dehpowers != NUMPOWERS)
+		I_Error("You forgot to update the Dehacked powers list, you dolt!\n(%d powers defined, versus %s in the Dehacked list)\n", NUMPOWERS, sizeu1(dehpowers));
+
+	if (dehcolors != SKINCOLOR_FIRSTFREESLOT)
+		I_Error("You forgot to update the Dehacked colors list, you dolt!\n(%d colors defined, versus %s in the Dehacked list)\n", SKINCOLOR_FIRSTFREESLOT, sizeu1(dehcolors));
+#endif
+}
diff --git a/src/deh_tables.h b/src/deh_tables.h
new file mode 100644
index 0000000000000000000000000000000000000000..d094bcbad4e74b736aa6aa423cfe94a408dc18f2
--- /dev/null
+++ b/src/deh_tables.h
@@ -0,0 +1,78 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  deh_tables.h
+/// \brief Define DeHackEd tables.
+
+#ifndef __DEH_TABLES_H__
+#define __DEH_TABLES_H__
+
+#include "doomdef.h" // Constants
+#include "d_think.h" // actionf_t
+#include "info.h" // Mobj, state, sprite, etc constants
+#include "lua_script.h"
+
+// Free slot names
+// The crazy word-reading stuff uses these.
+extern char *FREE_STATES[NUMSTATEFREESLOTS];
+extern char *FREE_MOBJS[NUMMOBJFREESLOTS];
+extern char *FREE_SKINCOLORS[NUMCOLORFREESLOTS];
+extern UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway.
+
+#define initfreeslots() {\
+	memset(FREE_STATES,0,sizeof(char *) * NUMSTATEFREESLOTS);\
+	memset(FREE_MOBJS,0,sizeof(char *) * NUMMOBJFREESLOTS);\
+	memset(FREE_SKINCOLORS,0,sizeof(char *) * NUMCOLORFREESLOTS);\
+	memset(used_spr,0,sizeof(UINT8) * ((NUMSPRITEFREESLOTS / 8) + 1));\
+}
+
+struct flickytypes_s {
+	const char *name;
+	const mobjtype_t type;
+};
+
+#define MAXFLICKIES 64
+
+/** Action pointer for reading actions from Dehacked lumps.
+  */
+typedef struct
+{
+	actionf_t action; ///< Function pointer corresponding to the actual action.
+	const char *name; ///< Name of the action in ALL CAPS.
+} actionpointer_t;
+
+struct int_const_s {
+	const char *n;
+	// has to be able to hold both fixed_t and angle_t, so drastic measure!!
+	lua_Integer v;
+};
+
+extern const char NIGHTSGRADE_LIST[];
+extern struct flickytypes_s FLICKYTYPES[];
+extern actionpointer_t actionpointers[]; // Array mapping action names to action functions.
+extern const char *const STATE_LIST[];
+extern const char *const MOBJTYPE_LIST[];
+extern const char *const MOBJFLAG_LIST[];
+extern const char *const MOBJFLAG2_LIST[]; // \tMF2_(\S+).*// (.+) --> \t"\1", // \2
+extern const char *const MOBJEFLAG_LIST[];
+extern const char *const MAPTHINGFLAG_LIST[4];
+extern const char *const PLAYERFLAG_LIST[];
+extern const char *const GAMETYPERULE_LIST[];
+extern const char *const ML_LIST[16]; // Linedef flags
+extern const char *COLOR_ENUMS[];
+extern const char *const POWERS_LIST[];
+extern const char *const HUDITEMS_LIST[];
+extern const char *const MENUTYPES_LIST[];
+
+extern struct int_const_s const INT_CONST[];
+
+// Moved to this file because it can't work compile-time otherwise
+void DEH_TableCheck(void);
+
+#endif
diff --git a/src/dehacked.c b/src/dehacked.c
index d02dc3d24ae797a5fe628bab54570e186d55bc79..c2ea28d27cef95d623c480b0259940e390f7cd2f 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -11,82 +11,45 @@
 /// \brief Load dehacked file and change tables and text
 
 #include "doomdef.h"
-#include "d_main.h" // for srb2home
-#include "g_game.h"
-#include "sounds.h"
-#include "info.h"
-#include "d_think.h"
-#include "m_argv.h"
-#include "z_zone.h"
-#include "w_wad.h"
-#include "m_menu.h"
-#include "m_misc.h"
-#include "f_finale.h"
-#include "y_inter.h"
-#include "dehacked.h"
-#include "st_stuff.h"
-#include "i_system.h"
-#include "p_local.h" // for var1 and var2, and some constants
-#include "p_setup.h"
-#include "r_data.h"
-#include "r_draw.h"
-#include "r_patch.h"
-#include "r_things.h" // R_Char2Frame
-#include "r_sky.h"
-#include "fastcmp.h"
-#include "lua_script.h"
-#include "lua_hook.h"
-#include "d_clisrv.h"
-
 #include "m_cond.h"
+#include "deh_soc.h"
+#include "deh_tables.h"
 
-#include "v_video.h" // video flags (for lua)
+boolean deh_loaded = false;
 
-#ifdef HWRENDER
-#include "hardware/hw_light.h"
-#endif
+boolean gamedataadded = false;
+boolean titlechanged = false;
+boolean introchanged = false;
 
-#ifdef PC_DOS
-#include <stdio.h> // for snprintf
-//int	snprintf(char *str, size_t n, const char *fmt, ...);
-int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
-#endif
+static int dbg_line;
+static INT32 deh_num_warning = 0;
 
-// Free slot names
-// The crazy word-reading stuff uses these.
-static char *FREE_STATES[NUMSTATEFREESLOTS];
-static char *FREE_MOBJS[NUMMOBJFREESLOTS];
-static char *FREE_SKINCOLORS[NUMCOLORFREESLOTS];
-static UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway.
-#define initfreeslots() {\
-memset(FREE_STATES,0,sizeof(char *) * NUMSTATEFREESLOTS);\
-memset(FREE_MOBJS,0,sizeof(char *) * NUMMOBJFREESLOTS);\
-memset(FREE_SKINCOLORS,0,sizeof(char *) * NUMCOLORFREESLOTS);\
-memset(used_spr,0,sizeof(UINT8) * ((NUMSPRITEFREESLOTS / 8) + 1));\
-}
+FUNCPRINTF void deh_warning(const char *first, ...)
+{
+	va_list argptr;
+	char *buf = Z_Malloc(1000, PU_STATIC, NULL);
 
-// Crazy word-reading stuff
-/// \todo Put these in a seperate file or something.
-static mobjtype_t get_mobjtype(const char *word);
-static statenum_t get_state(const char *word);
-static spritenum_t get_sprite(const char *word);
-static playersprite_t get_sprite2(const char *word);
-static sfxenum_t get_sfx(const char *word);
-#ifdef MUSICSLOT_COMPATIBILITY
-static UINT16 get_mus(const char *word, UINT8 dehacked_mode);
-#endif
-static hudnum_t get_huditem(const char *word);
-static menutype_t get_menutype(const char *word);
-//static INT16 get_gametype(const char *word);
-//static powertype_t get_power(const char *word);
-skincolornum_t get_skincolor(const char *word);
+	va_start(argptr, first);
+	vsnprintf(buf, 1000, first, argptr); // sizeof only returned 4 here. it didn't like that pointer.
+	va_end(argptr);
 
-boolean deh_loaded = false;
-static int dbg_line;
+	if(dbg_line == -1) // Not in a SOC, line number unknown.
+		CONS_Alert(CONS_WARNING, "%s\n", buf);
+	else
+		CONS_Alert(CONS_WARNING, "Line %u: %s\n", dbg_line, buf);
+
+	deh_num_warning++;
+
+	Z_Free(buf);
+}
 
-static boolean gamedataadded = false;
-static boolean titlechanged = false;
-static boolean introchanged = false;
+void deh_strlcpy(char *dst, const char *src, size_t size, const char *warntext)
+{
+	size_t len = strlen(src)+1; // Used to determine if truncation has been done
+	if (len > size)
+		deh_warning("%s exceeds max length of %s", warntext, sizeu1(size-1));
+	strlcpy(dst, src, size);
+}
 
 ATTRINLINE static FUNCINLINE char myfget_color(MYFILE *f)
 {
@@ -96,6 +59,12 @@ ATTRINLINE static FUNCINLINE char myfget_color(MYFILE *f)
 
 	if (c >= '0' && c <= '9')
 		return 0x80+(c-'0');
+
+	c = tolower(c);
+
+	if (c >= 'a' && c <= 'f')
+		return 0x80+10+(c-'a');
+
 	return 0x80; // Unhandled -- default to no color
 }
 
@@ -152,7 +121,7 @@ char *myfgets(char *buf, size_t bufsize, MYFILE *f)
 	return buf;
 }
 
-static char *myhashfgets(char *buf, size_t bufsize, MYFILE *f)
+char *myhashfgets(char *buf, size_t bufsize, MYFILE *f)
 {
 	size_t i = 0;
 	if (myfeof(f))
@@ -184,11013 +153,495 @@ static char *myhashfgets(char *buf, size_t bufsize, MYFILE *f)
 	return buf;
 }
 
-static INT32 deh_num_warning = 0;
-
-FUNCPRINTF static void deh_warning(const char *first, ...)
-{
-	va_list argptr;
-	char *buf = Z_Malloc(1000, PU_STATIC, NULL);
-
-	va_start(argptr, first);
-	vsnprintf(buf, 1000, first, argptr); // sizeof only returned 4 here. it didn't like that pointer.
-	va_end(argptr);
-
-	if(dbg_line == -1) // Not in a SOC, line number unknown.
-		CONS_Alert(CONS_WARNING, "%s\n", buf);
-	else
-		CONS_Alert(CONS_WARNING, "Line %u: %s\n", dbg_line, buf);
-
-	deh_num_warning++;
-
-	Z_Free(buf);
-}
-
-static void deh_strlcpy(char *dst, const char *src, size_t size, const char *warntext)
-{
-	size_t len = strlen(src)+1; // Used to determine if truncation has been done
-	if (len > size)
-		deh_warning("%s exceeds max length of %s", warntext, sizeu1(size-1));
-	strlcpy(dst, src, size);
-}
-
-/* ======================================================================== */
-// Load a dehacked file format
-/* ======================================================================== */
-/* a sample to see
-                   Thing 1 (Player)       {           // MT_PLAYER
-INT32 doomednum;     ID # = 3232              -1,             // doomednum
-INT32 spawnstate;    Initial frame = 32       "PLAY",         // spawnstate
-INT32 spawnhealth;   Hit points = 3232        100,            // spawnhealth
-INT32 seestate;      First moving frame = 32  "PLAY_RUN1",    // seestate
-INT32 seesound;      Alert sound = 32         sfx_None,       // seesound
-INT32 reactiontime;  Reaction time = 3232     0,              // reactiontime
-INT32 attacksound;   Attack sound = 32        sfx_None,       // attacksound
-INT32 painstate;     Injury frame = 32        "PLAY_PAIN",    // painstate
-INT32 painchance;    Pain chance = 3232       255,            // painchance
-INT32 painsound;     Pain sound = 32          sfx_plpain,     // painsound
-INT32 meleestate;    Close attack frame = 32  "NULL",         // meleestate
-INT32 missilestate;  Far attack frame = 32    "PLAY_ATK1",    // missilestate
-INT32 deathstate;    Death frame = 32         "PLAY_DIE1",    // deathstate
-INT32 xdeathstate;   Exploding frame = 32     "PLAY_XDIE1",   // xdeathstate
-INT32 deathsound;    Death sound = 32         sfx_pldeth,     // deathsound
-INT32 speed;         Speed = 3232             0,              // speed
-INT32 radius;        Width = 211812352        16*FRACUNIT,    // radius
-INT32 height;        Height = 211812352       56*FRACUNIT,    // height
-INT32 dispoffset;    DispOffset = 0           0,              // dispoffset
-INT32 mass;          Mass = 3232              100,            // mass
-INT32 damage;        Missile damage = 3232    0,              // damage
-INT32 activesound;   Action sound = 32        sfx_None,       // activesound
-INT32 flags;         Bits = 3232              MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH,
-INT32 raisestate;    Respawn frame = 32       S_NULL          // raisestate
-                                         }, */
-
-#ifdef HWRENDER
-static INT32 searchvalue(const char *s)
-{
-	while (s[0] != '=' && s[0])
-		s++;
-	if (s[0] == '=')
-		return atoi(&s[1]);
-	else
-	{
-		deh_warning("No value found");
-		return 0;
-	}
-}
-
-static float searchfvalue(const char *s)
-{
-	while (s[0] != '=' && s[0])
-		s++;
-	if (s[0] == '=')
-		return (float)atof(&s[1]);
-	else
-	{
-		deh_warning("No value found");
-		return 0;
-	}
-}
-#endif
-
-// These are for clearing all of various things
-static void clear_conditionsets(void)
-{
-	UINT8 i;
-	for (i = 0; i < MAXCONDITIONSETS; ++i)
-		M_ClearConditionSet(i+1);
-}
-
-static void clear_levels(void)
+// Used when you do something invalid like read a bad item number
+// to prevent extra unnecessary errors
+static void ignorelines(MYFILE *f)
 {
-	INT16 i;
-
-	// This is potentially dangerous but if we're resetting these headers,
-	// we may as well try to save some memory, right?
-	for (i = 0; i < NUMMAPS; ++i)
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	do
 	{
-		if (!mapheaderinfo[i] || i == (tutorialmap-1))
-			continue;
-
-		// Custom map header info
-		// (no need to set num to 0, we're freeing the entire header shortly)
-		Z_Free(mapheaderinfo[i]->customopts);
-
-		P_DeleteFlickies(i);
-		P_DeleteGrades(i);
-
-		Z_Free(mapheaderinfo[i]);
-		mapheaderinfo[i] = NULL;
-	}
-
-	// Realloc the one for the current gamemap as a safeguard
-	P_AllocMapHeader(gamemap-1);
-}
-
-static boolean findFreeSlot(INT32 *num)
-{
-	// Send the character select entry to a free slot.
-	while (*num < MAXSKINS && (description[*num].used))
-		*num = *num+1;
-
-	// No more free slots. :(
-	if (*num >= MAXSKINS)
-		return false;
-
-	// Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
-	description[*num].picname[0] = '\0';
-	description[*num].nametag[0] = '\0';
-	description[*num].displayname[0] = '\0';
-	description[*num].oppositecolor = SKINCOLOR_NONE;
-	description[*num].tagtextcolor = SKINCOLOR_NONE;
-	description[*num].tagoutlinecolor = SKINCOLOR_NONE;
-
-	// Found one! ^_^
-	return (description[*num].used = true);
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+		}
+	} while (!myfeof(f));
+	Z_Free(s);
 }
 
-// Reads a player.
-// For modifying the character select screen
-static void readPlayer(MYFILE *f, INT32 num)
+static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char textline[MAXLINELEN];
 	char *word;
 	char *word2;
-	char *displayname = ZZ_Alloc(MAXLINELEN+1);
 	INT32 i;
-	boolean slotfound = false;
 
-	#define SLOTFOUND \
-		if (!slotfound && (slotfound = findFreeSlot(&num)) == false) \
-			goto done;
+	if (!deh_loaded)
+		initfreeslots();
 
-	displayname[MAXLINELEN] = '\0';
+	deh_num_warning = 0;
 
-	do
+	gamedataadded = titlechanged = introchanged = false;
+
+	// it doesn't test the version of SRB2 and version of dehacked file
+	dbg_line = -1; // start at -1 so the first line is 0.
+	while (!myfeof(f))
 	{
-		if (myfgets(s, MAXLINELEN, f))
+		char origpos[128];
+		INT32 size = 0;
+		char *traverse;
+
+		myfgets(s, MAXLINELEN, f);
+		memcpy(textline, s, MAXLINELEN);
+		if (s[0] == '\n' || s[0] == '#')
+			continue;
+
+		traverse = s;
+
+		while (traverse[0] != '\n')
 		{
-			if (s[0] == '\n')
-				break;
+			traverse++;
+			size++;
+		}
+
+		strncpy(origpos, s, size);
+		origpos[size] = '\0';
 
-			for (i = 0; i < MAXLINELEN-3; i++)
+		if (NULL != (word = strtok(s, " "))) {
+			strupr(word);
+			if (word[strlen(word)-1] == '\n')
+				word[strlen(word)-1] = '\0';
+		}
+		if (word)
+		{
+			if (fastcmp(word, "FREESLOT"))
+			{
+				readfreeslots(f);
+				continue;
+			}
+			else if (fastcmp(word, "MAINCFG"))
+			{
+				readmaincfg(f);
+				continue;
+			}
+			else if (fastcmp(word, "WIPES"))
+			{
+				readwipes(f);
+				continue;
+			}
+			word2 = strtok(NULL, " ");
+			if (word2) {
+				strupr(word2);
+				if (word2[strlen(word2) - 1] == '\n')
+					word2[strlen(word2) - 1] = '\0';
+				i = atoi(word2);
+			}
+			else
+				i = 0;
+			if (fastcmp(word, "CHARACTER"))
 			{
-				char *tmp;
-				if (s[i] == '=')
+				if (i >= 0 && i < 32)
+					readPlayer(f, i);
+				else
 				{
-					tmp = &s[i+2];
-					strncpy(displayname, tmp, SKINNAMESIZE);
-					break;
+					deh_warning("Character %d out of range (0 - 31)", i);
+					ignorelines(f);
 				}
+				continue;
 			}
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			if (fastcmp(word, "PLAYERTEXT"))
+			else if (fastcmp(word, "EMBLEM"))
 			{
-				char *playertext = NULL;
-
-				SLOTFOUND
-
-				for (i = 0; i < MAXLINELEN-3; i++)
+				if (!mainfile && !gamedataadded)
+				{
+					deh_warning("You must define a custom gamedata to use \"%s\"", word);
+					ignorelines(f);
+				}
+				else
 				{
-					if (s[i] == '=')
+					if (!word2)
+						i = numemblems + 1;
+
+					if (i > 0 && i <= MAXEMBLEMS)
 					{
-						playertext = &s[i+2];
-						break;
+						if (numemblems < i)
+							numemblems = i;
+						reademblemdata(f, i);
+					}
+					else
+					{
+						deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS);
+						ignorelines(f);
 					}
 				}
-				if (playertext)
+				continue;
+			}
+			else if (fastcmp(word, "EXTRAEMBLEM"))
+			{
+				if (!mainfile && !gamedataadded)
 				{
-					strcpy(description[num].notes, playertext);
-					strcat(description[num].notes, myhashfgets(playertext, sizeof (description[num].notes), f));
+					deh_warning("You must define a custom gamedata to use \"%s\"", word);
+					ignorelines(f);
 				}
 				else
-					strcpy(description[num].notes, "");
-
-				// For some reason, cutting the string did not work above. Most likely due to strcpy or strcat...
-				// It works down here, though.
 				{
-					INT32 numline = 0;
-					for (i = 0; (size_t)i < sizeof(description[num].notes)-1; i++)
-					{
-						if (numline < 20 && description[num].notes[i] == '\n')
-							numline++;
+					if (!word2)
+						i = numextraemblems + 1;
 
-						if (numline >= 20 || description[num].notes[i] == '\0' || description[num].notes[i] == '#')
-							break;
+					if (i > 0 && i <= MAXEXTRAEMBLEMS)
+					{
+						if (numextraemblems < i)
+							numextraemblems = i;
+						readextraemblemdata(f, i);
+					}
+					else
+					{
+						deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS);
+						ignorelines(f);
 					}
 				}
-				description[num].notes[strlen(description[num].notes)-1] = '\0';
-				description[num].notes[i] = '\0';
 				continue;
 			}
-
-			word2 = strtok(NULL, " = ");
 			if (word2)
-				strupr(word2);
-			else
-				break;
-
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-			i = atoi(word2);
-
-			if (fastcmp(word, "PICNAME"))
 			{
-				SLOTFOUND
-				strncpy(description[num].picname, word2, 8);
-			}
-			// new character select
-			else if (fastcmp(word, "DISPLAYNAME"))
-			{
-				SLOTFOUND
-				// replace '#' with line breaks
-				// (also remove any '\n')
+				if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
 				{
-					char *cur = NULL;
-
-					// remove '\n'
-					cur = strchr(displayname, '\n');
-					if (cur)
-						*cur = '\0';
-
-					// turn '#' into '\n'
-					cur = strchr(displayname, '#');
-					while (cur)
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_mobjtype(word2); // find a thing by name
+					if (i < NUMMOBJTYPES && i > 0)
+						readthing(f, i);
+					else
 					{
-						*cur = '\n';
-						cur = strchr(cur, '#');
+						deh_warning("Thing %d out of range (1 - %d)", i, NUMMOBJTYPES-1);
+						ignorelines(f);
 					}
 				}
-				// copy final string
-				strncpy(description[num].displayname, displayname, SKINNAMESIZE);
-			}
-			else if (fastcmp(word, "OPPOSITECOLOR") || fastcmp(word, "OPPOSITECOLOUR"))
-			{
-				SLOTFOUND
-				description[num].oppositecolor = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "NAMETAG") || fastcmp(word, "TAGNAME"))
-			{
-				SLOTFOUND
-				strncpy(description[num].nametag, word2, 8);
-			}
-			else if (fastcmp(word, "TAGTEXTCOLOR") || fastcmp(word, "TAGTEXTCOLOUR"))
-			{
-				SLOTFOUND
-				description[num].tagtextcolor = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "TAGOUTLINECOLOR") || fastcmp(word, "TAGOUTLINECOLOUR"))
-			{
-				SLOTFOUND
-				description[num].tagoutlinecolor = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "STATUS"))
-			{
-				/*
-					You MAY disable previous entries if you so desire...
-					But try to enable something that's already enabled and you will be sent to a free slot.
-
-					Because of this, you are allowed to edit any previous entries you like, but only if you
-					signal that you are purposely doing so by disabling and then reenabling the slot.
-				*/
-				if (i && !slotfound && (slotfound = findFreeSlot(&num)) == false)
-					goto done;
-
-				description[num].used = (!!i);
-			}
-			else if (fastcmp(word, "SKINNAME"))
-			{
-				// Send to free slot.
-				SLOTFOUND
-				strlcpy(description[num].skinname, word2, sizeof description[num].skinname);
-				strlwr(description[num].skinname);
-			}
-			else
-				deh_warning("readPlayer %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-	#undef SLOTFOUND
-done:
-	Z_Free(displayname);
-	Z_Free(s);
-}
-
-// TODO: Figure out how to do undolines for this....
-// TODO: Warnings for running out of freeslots
-static void readfreeslots(MYFILE *f)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word,*type;
-	char *tmp;
-	int i;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			type = strtok(s, "_");
-			if (type)
-				strupr(type);
-			else
-				break;
-
-			word = strtok(NULL, "\n");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			// TODO: Check for existing freeslot mobjs/states/etc. and make errors.
-			// TODO: Out-of-slots warnings/errors.
-			// TODO: Name too long (truncated) warnings.
-			if (fastcmp(type, "SFX"))
-				S_AddSoundFx(word, false, 0, false);
-			else if (fastcmp(type, "SPR"))
-			{
-				for (i = SPR_FIRSTFREESLOT; i <= SPR_LASTFREESLOT; i++)
+				else if (fastcmp(word, "SKINCOLOR") || fastcmp(word, "COLOR"))
 				{
-					if (used_spr[(i-SPR_FIRSTFREESLOT)/8] & (1<<(i%8)))
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_skincolor(word2); // find a skincolor by name
+					if (i && i < numskincolors)
+						readskincolor(f, i);
+					else
 					{
-						if (!sprnames[i][4] && memcmp(sprnames[i],word,4)==0)
-							sprnames[i][4] = (char)f->wad;
-						continue; // Already allocated, next.
+						deh_warning("Skincolor %d out of range (1 - %d)", i, numskincolors-1);
+						ignorelines(f);
 					}
-					// Found a free slot!
-					strncpy(sprnames[i],word,4);
-					//sprnames[i][4] = 0;
-					used_spr[(i-SPR_FIRSTFREESLOT)/8] |= 1<<(i%8); // Okay, this sprite slot has been named now.
-					break;
 				}
-			}
-			else if (fastcmp(type, "S"))
-			{
-				for (i = 0; i < NUMSTATEFREESLOTS; i++)
-					if (!FREE_STATES[i]) {
-						FREE_STATES[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-						strcpy(FREE_STATES[i],word);
-						break;
+				else if (fastcmp(word, "SPRITE2"))
+				{
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_sprite2(word2); // find a sprite by name
+					if (i < (INT32)free_spr2 && i >= (INT32)SPR2_FIRSTFREESLOT)
+						readsprite2(f, i);
+					else
+					{
+						deh_warning("Sprite2 number %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
+						ignorelines(f);
 					}
-			}
-			else if (fastcmp(type, "MT"))
-			{
-				for (i = 0; i < NUMMOBJFREESLOTS; i++)
-					if (!FREE_MOBJS[i]) {
-						FREE_MOBJS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-						strcpy(FREE_MOBJS[i],word);
-						break;
+				}
+#ifdef HWRENDER
+				else if (fastcmp(word, "LIGHT"))
+				{
+					// TODO: Read lights by name
+					if (i > 0 && i < NUMLIGHTS)
+						readlight(f, i);
+					else
+					{
+						deh_warning("Light number %d out of range (1 - %d)", i, NUMLIGHTS-1);
+						ignorelines(f);
 					}
-			}
-			else if (fastcmp(type, "SKINCOLOR"))
-			{
-				for (i = 0; i < NUMCOLORFREESLOTS; i++)
-					if (!FREE_SKINCOLORS[i]) {
-						FREE_SKINCOLORS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-						strcpy(FREE_SKINCOLORS[i],word);
-						M_AddMenuColor(numskincolors++);
-						break;
+				}
+#endif
+				else if (fastcmp(word, "SPRITE") || fastcmp(word, "SPRITEINFO"))
+				{
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_sprite(word2); // find a sprite by name
+					if (i < NUMSPRITES && i > 0)
+						readspriteinfo(f, i, false);
+					else
+					{
+						deh_warning("Sprite number %d out of range (0 - %d)", i, NUMSPRITES-1);
+						ignorelines(f);
 					}
-			}
-			else if (fastcmp(type, "SPR2"))
-			{
-				// Search if we already have an SPR2 by that name...
-				for (i = SPR2_FIRSTFREESLOT; i < (int)free_spr2; i++)
-					if (memcmp(spr2names[i],word,4) == 0)
-						break;
-				// We found it? (Two mods using the same SPR2 name?) Then don't allocate another one.
-				if (i < (int)free_spr2)
-					continue;
-				// Copy in the spr2 name and increment free_spr2.
-				if (free_spr2 < NUMPLAYERSPRITES) {
-					strncpy(spr2names[free_spr2],word,4);
-					spr2defaults[free_spr2] = 0;
-					spr2names[free_spr2++][4] = 0;
-				} else
-					deh_warning("Ran out of free SPR2 slots!\n");
-			}
-			else if (fastcmp(type, "TOL"))
-			{
-				// Search if we already have a typeoflevel by that name...
-				for (i = 0; TYPEOFLEVEL[i].name; i++)
-					if (fastcmp(word, TYPEOFLEVEL[i].name))
-						break;
-
-				// We found it? Then don't allocate another one.
-				if (TYPEOFLEVEL[i].name)
-					continue;
-
-				// We don't, so freeslot it.
-				if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags.
-					deh_warning("Ran out of free typeoflevel slots!\n");
-				else
+				}
+				else if (fastcmp(word, "SPRITE2INFO"))
 				{
-					G_AddTOL(lastcustomtol, word);
-					lastcustomtol <<= 1;
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_sprite2(word2); // find a sprite by name
+					if (i < NUMPLAYERSPRITES && i >= 0)
+						readspriteinfo(f, i, true);
+					else
+					{
+						deh_warning("Sprite2 number %d out of range (0 - %d)", i, NUMPLAYERSPRITES-1);
+						ignorelines(f);
+					}
 				}
-			}
-			else
-				deh_warning("Freeslots: unknown enum class '%s' for '%s_%s'", type, type, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readthing(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word, *word2;
-	char *tmp;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " = ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-
-			if (fastcmp(word, "MAPTHINGNUM") || fastcmp(word, "DOOMEDNUM"))
-			{
-				mobjinfo[num].doomednum = (INT32)atoi(word2);
-			}
-			else if (fastcmp(word, "SPAWNSTATE"))
-			{
-				mobjinfo[num].spawnstate = get_number(word2);
-			}
-			else if (fastcmp(word, "SPAWNHEALTH"))
-			{
-				mobjinfo[num].spawnhealth = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word, "SEESTATE"))
-			{
-				mobjinfo[num].seestate = get_number(word2);
-			}
-			else if (fastcmp(word, "SEESOUND"))
-			{
-				mobjinfo[num].seesound = get_number(word2);
-			}
-			else if (fastcmp(word, "REACTIONTIME"))
-			{
-				mobjinfo[num].reactiontime = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word, "ATTACKSOUND"))
-			{
-				mobjinfo[num].attacksound = get_number(word2);
-			}
-			else if (fastcmp(word, "PAINSTATE"))
-			{
-				mobjinfo[num].painstate = get_number(word2);
-			}
-			else if (fastcmp(word, "PAINCHANCE"))
-			{
-				mobjinfo[num].painchance = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word, "PAINSOUND"))
-			{
-				mobjinfo[num].painsound = get_number(word2);
-			}
-			else if (fastcmp(word, "MELEESTATE"))
-			{
-				mobjinfo[num].meleestate = get_number(word2);
-			}
-			else if (fastcmp(word, "MISSILESTATE"))
-			{
-				mobjinfo[num].missilestate = get_number(word2);
-			}
-			else if (fastcmp(word, "DEATHSTATE"))
-			{
-				mobjinfo[num].deathstate = get_number(word2);
-			}
-			else if (fastcmp(word, "DEATHSOUND"))
-			{
-				mobjinfo[num].deathsound = get_number(word2);
-			}
-			else if (fastcmp(word, "XDEATHSTATE"))
-			{
-				mobjinfo[num].xdeathstate = get_number(word2);
-			}
-			else if (fastcmp(word, "SPEED"))
-			{
-				mobjinfo[num].speed = get_number(word2);
-			}
-			else if (fastcmp(word, "RADIUS"))
-			{
-				mobjinfo[num].radius = get_number(word2);
-			}
-			else if (fastcmp(word, "HEIGHT"))
-			{
-				mobjinfo[num].height = get_number(word2);
-			}
-			else if (fastcmp(word, "DISPOFFSET"))
-			{
-				mobjinfo[num].dispoffset = get_number(word2);
-			}
-			else if (fastcmp(word, "MASS"))
-			{
-				mobjinfo[num].mass = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word, "DAMAGE"))
-			{
-				mobjinfo[num].damage = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word, "ACTIVESOUND"))
-			{
-				mobjinfo[num].activesound = get_number(word2);
-			}
-			else if (fastcmp(word, "FLAGS"))
-			{
-				mobjinfo[num].flags = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word, "RAISESTATE"))
-			{
-				mobjinfo[num].raisestate = get_number(word2);
-			}
-			else
-				deh_warning("Thing %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readskincolor(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-
-	Color_cons_t[num].value = num;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
+				else if (fastcmp(word, "LEVEL"))
+				{
+					// Support using the actual map name,
+					// i.e., Level AB, Level FZ, etc.
 
-			// Now get the part after
-			word2 = tmp += 2;
+					// Convert to map number
+					if (word2[0] >= 'A' && word2[0] <= 'Z')
+						i = M_MapNumber(word2[0], word2[1]);
 
-			if (fastcmp(word, "NAME"))
-			{
-				deh_strlcpy(skincolors[num].name, word2,
-					sizeof (skincolors[num].name), va("Skincolor %d: name", num));
-			}
-			else if (fastcmp(word, "RAMP"))
-			{
-				UINT8 i;
-				tmp = strtok(word2,",");
-				for (i = 0; i < COLORRAMPSIZE; i++) {
-					skincolors[num].ramp[i] = (UINT8)get_number(tmp);
-					if ((tmp = strtok(NULL,",")) == NULL)
-						break;
+					if (i > 0 && i <= NUMMAPS)
+						readlevelheader(f, i);
+					else
+					{
+						deh_warning("Level number %d out of range (1 - %d)", i, NUMMAPS);
+						ignorelines(f);
+					}
 				}
-			}
-			else if (fastcmp(word, "INVCOLOR"))
-			{
-				skincolors[num].invcolor = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "INVSHADE"))
-			{
-				skincolors[num].invshade = get_number(word2)%COLORRAMPSIZE;
-			}
-			else if (fastcmp(word, "CHATCOLOR"))
-			{
-				skincolors[num].chatcolor = get_number(word2);
-			}
-			else if (fastcmp(word, "ACCESSIBLE"))
-			{
-				if (num > FIRSTSUPERCOLOR)
-					skincolors[num].accessible = (boolean)(atoi(word2) || word2[0] == 'T' || word2[0] == 'Y');
-			}
-			else
-				deh_warning("Skincolor %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-#ifdef HWRENDER
-static void readlight(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *tmp;
-	INT32 value;
-	float fvalue;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			fvalue = searchfvalue(s);
-			value = searchvalue(s);
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			if (fastcmp(word, "TYPE"))
-			{
-				lspr[num].type = (UINT16)value;
-			}
-			else if (fastcmp(word, "OFFSETX"))
-			{
-				lspr[num].light_xoffset = fvalue;
-			}
-			else if (fastcmp(word, "OFFSETY"))
-			{
-				lspr[num].light_yoffset = fvalue;
-			}
-			else if (fastcmp(word, "CORONACOLOR"))
-			{
-				lspr[num].corona_color = value;
-			}
-			else if (fastcmp(word, "CORONARADIUS"))
-			{
-				lspr[num].corona_radius = fvalue;
-			}
-			else if (fastcmp(word, "DYNAMICCOLOR"))
-			{
-				lspr[num].dynamic_color = value;
-			}
-			else if (fastcmp(word, "DYNAMICRADIUS"))
-			{
-				lspr[num].dynamic_radius = fvalue;
-
-				/// \note Update the sqrradius! unnecessary?
-				lspr[num].dynamic_sqrradius = fvalue * fvalue;
-			}
-			else
-				deh_warning("Light %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-#endif // HWRENDER
-
-static void readspriteframe(MYFILE *f, spriteinfo_t *sprinfo, UINT8 frame)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word, *word2;
-	char *tmp;
-	INT32 value;
-	char *lastline;
-
-	do
-	{
-		lastline = f->curpos;
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Set / reset word
-			word = s;
-			while ((*word == '\t') || (*word == ' '))
-				word++;
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-			{
-				*(tmp-1) = '\0';
-				// Now get the part after
-				word2 = tmp += 2;
-			}
-			else
-			{
-				// Get the part before the " "
-				tmp = strchr(s, ' ');
-				if (tmp)
-				{
-					*tmp = '\0';
-					// Now get the part after
-					tmp++;
-					word2 = tmp;
-				}
-				else
-					break;
-			}
-			strupr(word);
-			value = atoi(word2); // used for numerical settings
-
-			if (fastcmp(word, "XPIVOT"))
-				sprinfo->pivot[frame].x = value;
-			else if (fastcmp(word, "YPIVOT"))
-				sprinfo->pivot[frame].y = value;
-			else if (fastcmp(word, "ROTAXIS"))
-				sprinfo->pivot[frame].rotaxis = value;
-			else
-			{
-				f->curpos = lastline;
-				break;
-			}
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-	Z_Free(s);
-}
-
-static void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word, *word2;
-	char *tmp;
-#ifdef HWRENDER
-	INT32 value;
-#endif
-	char *lastline;
-	INT32 skinnumbers[MAXSKINS];
-	INT32 foundskins = 0;
-
-	// allocate a spriteinfo
-	spriteinfo_t *info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL);
-	info->available = true;
-
-#ifdef ROTSPRITE
-	if ((sprites != NULL) && (!sprite2))
-		R_FreeSingleRotSprite(&sprites[num]);
-#endif
-
-	do
-	{
-		lastline = f->curpos;
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Set / reset word
-			word = s;
-			while ((*word == '\t') || (*word == ' '))
-				word++;
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-			{
-				*(tmp-1) = '\0';
-				// Now get the part after
-				word2 = tmp += 2;
-			}
-			else
-			{
-				// Get the part before the " "
-				tmp = strchr(s, ' ');
-				if (tmp)
-				{
-					*tmp = '\0';
-					// Now get the part after
-					tmp++;
-					word2 = tmp;
-				}
-				else
-					break;
-			}
-			strupr(word);
-#ifdef HWRENDER
-			value = atoi(word2); // used for numerical settings
-
-			if (fastcmp(word, "LIGHTTYPE"))
-			{
-				if (sprite2)
-					deh_warning("Sprite2 %s: invalid word '%s'", spr2names[num], word);
-				else
-				{
-					INT32 oldvar;
-					for (oldvar = 0; t_lspr[num] != &lspr[oldvar]; oldvar++)
-						;
-					t_lspr[num] = &lspr[value];
-				}
-			}
-			else
-#endif
-			if (fastcmp(word, "SKIN"))
-			{
-				INT32 skinnum = -1;
-				if (!sprite2)
-				{
-					deh_warning("Sprite %s: %s keyword found outside of SPRITE2INFO block, ignoring", spr2names[num], word);
-					continue;
-				}
-
-				// make lowercase
-				strlwr(word2);
-				skinnum = R_SkinAvailable(word2);
-				if (skinnum == -1)
-				{
-					deh_warning("Sprite2 %s: unknown skin %s", spr2names[num], word2);
-					break;
-				}
-
-				skinnumbers[foundskins] = skinnum;
-				foundskins++;
-			}
-			else if (fastcmp(word, "DEFAULT"))
-			{
-				if (!sprite2)
-				{
-					deh_warning("Sprite %s: %s keyword found outside of SPRITE2INFO block, ignoring", spr2names[num], word);
-					continue;
-				}
-				if (num < (INT32)free_spr2 && num >= (INT32)SPR2_FIRSTFREESLOT)
-					spr2defaults[num] = get_number(word2);
-				else
+				else if (fastcmp(word, "GAMETYPE"))
 				{
-					deh_warning("Sprite2 %s: out of range (%d - %d), ignoring", spr2names[num], SPR2_FIRSTFREESLOT, free_spr2-1);
-					continue;
+					// Get the gametype name from textline
+					// instead of word2, so that gametype names
+					// aren't allcaps
+					INT32 c;
+					for (c = 0; c < MAXLINELEN; c++)
+					{
+						if (textline[c] == '\0')
+							break;
+						if (textline[c] == ' ')
+						{
+							char *gtname = (textline+c+1);
+							if (gtname)
+							{
+								// remove funny characters
+								INT32 j;
+								for (j = 0; j < (MAXLINELEN - c); j++)
+								{
+									if (gtname[j] == '\0')
+										break;
+									if (gtname[j] < 32)
+										gtname[j] = '\0';
+								}
+								readgametype(f, gtname);
+							}
+							break;
+						}
+					}
 				}
-			}
-			else if (fastcmp(word, "FRAME"))
-			{
-				UINT8 frame = R_Char2Frame(word2[0]);
-				// frame number too high
-				if (frame >= 64)
+				else if (fastcmp(word, "CUTSCENE"))
 				{
-					if (sprite2)
-						deh_warning("Sprite2 %s: invalid frame %s", spr2names[num], word2);
+					if (i > 0 && i < 129)
+						readcutscene(f, i - 1);
 					else
-						deh_warning("Sprite %s: invalid frame %s", sprnames[num], word2);
-					break;
-				}
-
-				// read sprite frame and store it in the spriteinfo_t struct
-				readspriteframe(f, info, frame);
-				if (sprite2)
-				{
-					INT32 i;
-					if (!foundskins)
-					{
-						deh_warning("Sprite2 %s: no skins specified", spr2names[num]);
-						break;
-					}
-					for (i = 0; i < foundskins; i++)
 					{
-						size_t skinnum = skinnumbers[i];
-						skin_t *skin = &skins[skinnum];
-						spriteinfo_t *sprinfo = skin->sprinfo;
-#ifdef ROTSPRITE
-						R_FreeSkinRotSprite(skinnum);
-#endif
-						M_Memcpy(&sprinfo[num], info, sizeof(spriteinfo_t));
+						deh_warning("Cutscene number %d out of range (1 - 128)", i);
+						ignorelines(f);
 					}
 				}
-				else
-					M_Memcpy(&spriteinfo[num], info, sizeof(spriteinfo_t));
-			}
-			else
-			{
-				//deh_warning("Sprite %s: unknown word '%s'", sprnames[num], word);
-				f->curpos = lastline;
-				break;
-			}
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-	Z_Free(info);
-}
-
-static void readsprite2(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word, *word2;
-	char *tmp;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " = ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-
-			if (fastcmp(word, "DEFAULT"))
-				spr2defaults[num] = get_number(word2);
-			else
-				deh_warning("Sprite2 %s: unknown word '%s'", spr2names[num], word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-// copypasted from readPlayer :]
-static const char *const GAMETYPERULE_LIST[];
-static void readgametype(MYFILE *f, char *gtname)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2, *word2lwr = NULL;
-	char *tmp;
-	INT32 i, j;
-
-	INT16 newgtidx = 0;
-	UINT32 newgtrules = 0;
-	UINT32 newgttol = 0;
-	INT32 newgtpointlimit = 0;
-	INT32 newgttimelimit = 0;
-	UINT8 newgtleftcolor = 0;
-	UINT8 newgtrightcolor = 0;
-	INT16 newgtrankingstype = -1;
-	int newgtinttype = 0;
-	char gtdescription[441];
-	char gtconst[MAXLINELEN];
-
-	// Empty strings.
-	gtdescription[0] = '\0';
-	gtconst[0] = '\0';
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			if (fastcmp(word, "DESCRIPTION"))
-			{
-				char *descr = NULL;
-
-				for (i = 0; i < MAXLINELEN-3; i++)
+				else if (fastcmp(word, "PROMPT"))
 				{
-					if (s[i] == '=')
+					if (i > 0 && i < MAX_PROMPTS)
+						readtextprompt(f, i - 1);
+					else
 					{
-						descr = &s[i+2];
-						break;
+						deh_warning("Prompt number %d out of range (1 - %d)", i, MAX_PROMPTS);
+						ignorelines(f);
 					}
 				}
-				if (descr)
+				else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE"))
 				{
-					strcpy(gtdescription, descr);
-					strcat(gtdescription, myhashfgets(descr, sizeof (gtdescription), f));
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_state(word2); // find a state by name
+					if (i < NUMSTATES && i >= 0)
+						readframe(f, i);
+					else
+					{
+						deh_warning("Frame %d out of range (0 - %d)", i, NUMSTATES-1);
+						ignorelines(f);
+					}
 				}
-				else
-					strcpy(gtdescription, "");
-
-				// For some reason, cutting the string did not work above. Most likely due to strcpy or strcat...
-				// It works down here, though.
+				else if (fastcmp(word, "SOUND"))
 				{
-					INT32 numline = 0;
-					for (i = 0; (size_t)i < sizeof(gtdescription)-1; i++)
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_sfx(word2); // find a sound by name
+					if (i < NUMSFX && i > 0)
+						readsound(f, i);
+					else
 					{
-						if (numline < 20 && gtdescription[i] == '\n')
-							numline++;
-
-						if (numline >= 20 || gtdescription[i] == '\0' || gtdescription[i] == '#')
-							break;
+						deh_warning("Sound %d out of range (1 - %d)", i, NUMSFX-1);
+						ignorelines(f);
 					}
 				}
-				gtdescription[strlen(gtdescription)-1] = '\0';
-				gtdescription[i] = '\0';
-				continue;
-			}
-
-			word2 = strtok(NULL, " = ");
-			if (word2)
-			{
-				if (!word2lwr)
-					word2lwr = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-				strcpy(word2lwr, word2);
-				strupr(word2);
-			}
-			else
-				break;
-
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-			i = atoi(word2);
-
-			// Game type rules
-			if (fastcmp(word, "RULES"))
-			{
-				// GTR_
-				newgtrules = (UINT32)get_number(word2);
-			}
-			// Identifier
-			else if (fastcmp(word, "IDENTIFIER"))
-			{
-				// GT_
-				strncpy(gtconst, word2, MAXLINELEN);
-			}
-			// Point and time limits
-			else if (fastcmp(word, "DEFAULTPOINTLIMIT"))
-				newgtpointlimit = (INT32)i;
-			else if (fastcmp(word, "DEFAULTTIMELIMIT"))
-				newgttimelimit = (INT32)i;
-			// Level platter
-			else if (fastcmp(word, "HEADERCOLOR") || fastcmp(word, "HEADERCOLOUR"))
-				newgtleftcolor = newgtrightcolor = (UINT8)get_number(word2);
-			else if (fastcmp(word, "HEADERLEFTCOLOR") || fastcmp(word, "HEADERLEFTCOLOUR"))
-				newgtleftcolor = (UINT8)get_number(word2);
-			else if (fastcmp(word, "HEADERRIGHTCOLOR") || fastcmp(word, "HEADERRIGHTCOLOUR"))
-				newgtrightcolor = (UINT8)get_number(word2);
-			// Rankings type
-			else if (fastcmp(word, "RANKINGTYPE"))
-			{
-				// Case insensitive
-				newgtrankingstype = (int)get_number(word2);
-			}
-			// Intermission type
-			else if (fastcmp(word, "INTERMISSIONTYPE"))
-			{
-				// Case sensitive
-				newgtinttype = (int)get_number(word2lwr);
-			}
-			// Type of level
-			else if (fastcmp(word, "TYPEOFLEVEL"))
-			{
-				if (i) // it's just a number
-					newgttol = (UINT32)i;
-				else
+				else if (fastcmp(word, "HUDITEM"))
 				{
-					UINT32 tol = 0;
-					tmp = strtok(word2,",");
-					do {
-						for (i = 0; TYPEOFLEVEL[i].name; i++)
-							if (fasticmp(tmp, TYPEOFLEVEL[i].name))
-								break;
-						if (!TYPEOFLEVEL[i].name)
-							deh_warning("readgametype %s: unknown typeoflevel flag %s\n", gtname, tmp);
-						tol |= TYPEOFLEVEL[i].flag;
-					} while((tmp = strtok(NULL,",")) != NULL);
-					newgttol = tol;
-				}
-			}
-			// The SOC probably provided gametype rules as words,
-			// instead of using the RULES keyword.
-			// Like for example "NOSPECTATORSPAWN = TRUE".
-			// This is completely valid, and looks better anyway.
-			else
-			{
-				UINT32 wordgt = 0;
-				for (j = 0; GAMETYPERULE_LIST[j]; j++)
-					if (fastcmp(word, GAMETYPERULE_LIST[j])) {
-						wordgt |= (1<<j);
-						if (i || word2[0] == 'T' || word2[0] == 'Y')
-							newgtrules |= wordgt;
-						break;
-					}
-				if (!wordgt)
-					deh_warning("readgametype %s: unknown word '%s'", gtname, word);
-			}
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	// Free strings.
-	Z_Free(s);
-	if (word2lwr)
-		Z_Free(word2lwr);
-
-	// Ran out of gametype slots
-	if (gametypecount == NUMGAMETYPEFREESLOTS)
-	{
-		CONS_Alert(CONS_WARNING, "Ran out of free gametype slots!\n");
-		return;
-	}
-
-	// Add the new gametype
-	newgtidx = G_AddGametype(newgtrules);
-	G_AddGametypeTOL(newgtidx, newgttol);
-	G_SetGametypeDescription(newgtidx, gtdescription, newgtleftcolor, newgtrightcolor);
-
-	// Not covered by G_AddGametype alone.
-	if (newgtrankingstype == -1)
-		newgtrankingstype = newgtidx;
-	gametyperankings[newgtidx] = newgtrankingstype;
-	intermissiontypes[newgtidx] = newgtinttype;
-	pointlimits[newgtidx] = newgtpointlimit;
-	timelimits[newgtidx] = newgttimelimit;
-
-	// Write the new gametype name.
-	Gametype_Names[newgtidx] = Z_StrDup((const char *)gtname);
-
-	// Write the constant name.
-	if (gtconst[0] == '\0')
-		strncpy(gtconst, gtname, MAXLINELEN);
-	G_AddGametypeConstant(newgtidx, (const char *)gtconst);
-
-	// Update gametype_cons_t accordingly.
-	G_UpdateGametypeSelections();
-
-	CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]);
-}
-
-static const struct {
-	const char *name;
-	const mobjtype_t type;
-} FLICKYTYPES[] = {
-	{"BLUEBIRD", MT_FLICKY_01}, // Flicky (Flicky)
-	{"RABBIT",   MT_FLICKY_02}, // Pocky (1)
-	{"CHICKEN",  MT_FLICKY_03}, // Cucky (1)
-	{"SEAL",     MT_FLICKY_04}, // Rocky (1)
-	{"PIG",      MT_FLICKY_05}, // Picky (1)
-	{"CHIPMUNK", MT_FLICKY_06}, // Ricky (1)
-	{"PENGUIN",  MT_FLICKY_07}, // Pecky (1)
-	{"FISH",     MT_FLICKY_08}, // Nicky (CD)
-	{"RAM",      MT_FLICKY_09}, // Flocky (CD)
-	{"PUFFIN",   MT_FLICKY_10}, // Wicky (CD)
-	{"COW",      MT_FLICKY_11}, // Macky (SRB2)
-	{"RAT",      MT_FLICKY_12}, // Micky (2)
-	{"BEAR",     MT_FLICKY_13}, // Becky (2)
-	{"DOVE",     MT_FLICKY_14}, // Docky (CD)
-	{"CAT",      MT_FLICKY_15}, // Nyannyan (Flicky)
-	{"CANARY",   MT_FLICKY_16}, // Lucky (CD)
-	{"a", 0}, // End of normal flickies - a lower case character so will never fastcmp valid with uppercase tmp
-	//{"FLICKER",          MT_FLICKER}, // Flacky (SRB2)
-	{"SPIDER",   MT_SECRETFLICKY_01}, // Sticky (SRB2)
-	{"BAT",      MT_SECRETFLICKY_02}, // Backy (SRB2)
-	{"SEED",                MT_SEED}, // Seed (CD)
-	{NULL, 0}
-};
-
-#define MAXFLICKIES 64
-
-static void readlevelheader(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	//char *word3; // Non-uppercase version of word2
-	char *tmp;
-	INT32 i;
-
-	// Reset all previous map header information
-	P_AllocMapHeader((INT16)(num-1));
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Set / reset word, because some things (Lua.) move it
-			word = s;
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-			i = atoi(word2); // used for numerical settings
-
-
-			if (fastcmp(word, "LEVELNAME"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
-					sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
-				strlcpy(mapheaderinfo[num-1]->selectheading, word2, sizeof(mapheaderinfo[num-1]->selectheading)); // not deh_ so only complains once
-				continue;
-			}
-			// CHEAP HACK: move this over here for lowercase subtitles
-			if (fastcmp(word, "SUBTITLE"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->subttl, word2,
-					sizeof(mapheaderinfo[num-1]->subttl), va("Level header %d: subtitle", num));
-				continue;
-			}
-
-			// Lua custom options also go above, contents may be case sensitive.
-			if (fastncmp(word, "LUA.", 4))
-			{
-				UINT8 j;
-				customoption_t *modoption;
-
-				// Note: we actualy strlwr word here, so things are made a little easier for Lua
-				strlwr(word);
-				word += 4; // move past "lua."
-
-				// ... and do a simple name sanity check; the name must start with a letter
-				if (*word < 'a' || *word > 'z')
-				{
-					deh_warning("Level header %d: invalid custom option name \"%s\"", num, word);
-					continue;
-				}
-
-				// Sanity limit of 128 params
-				if (mapheaderinfo[num-1]->numCustomOptions == 128)
-				{
-					deh_warning("Level header %d: too many custom parameters", num);
-					continue;
-				}
-				j = mapheaderinfo[num-1]->numCustomOptions++;
-
-				mapheaderinfo[num-1]->customopts =
-					Z_Realloc(mapheaderinfo[num-1]->customopts,
-						sizeof(customoption_t) * mapheaderinfo[num-1]->numCustomOptions, PU_STATIC, NULL);
-
-				// Newly allocated
-				modoption = &mapheaderinfo[num-1]->customopts[j];
-
-				strncpy(modoption->option, word,  31);
-				modoption->option[31] = '\0';
-				strncpy(modoption->value,  word2, 255);
-				modoption->value[255] = '\0';
-				continue;
-			}
-
-			// Now go to uppercase
-			strupr(word2);
-
-			// List of flickies that are be freed in this map
-			if (fastcmp(word, "FLICKYLIST") || fastcmp(word, "ANIMALLIST"))
-			{
-				if (fastcmp(word2, "NONE"))
-					P_DeleteFlickies(num-1);
-				else if (fastcmp(word2, "DEMO"))
-					P_SetDemoFlickies(num-1);
-				else if (fastcmp(word2, "ALL"))
-				{
-					mobjtype_t tmpflickies[MAXFLICKIES];
-
-					for (mapheaderinfo[num-1]->numFlickies = 0;
-					((mapheaderinfo[num-1]->numFlickies < MAXFLICKIES) && FLICKYTYPES[mapheaderinfo[num-1]->numFlickies].type);
-					mapheaderinfo[num-1]->numFlickies++)
-						tmpflickies[mapheaderinfo[num-1]->numFlickies] = FLICKYTYPES[mapheaderinfo[num-1]->numFlickies].type;
-
-					if (mapheaderinfo[num-1]->numFlickies) // just in case...
-					{
-						size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num-1]->numFlickies;
-						mapheaderinfo[num-1]->flickies = Z_Realloc(mapheaderinfo[num-1]->flickies, newsize, PU_STATIC, NULL);
-						M_Memcpy(mapheaderinfo[num-1]->flickies, tmpflickies, newsize);
-					}
-				}
-				else
-				{
-					mobjtype_t tmpflickies[MAXFLICKIES];
-					mapheaderinfo[num-1]->numFlickies = 0;
-					tmp = strtok(word2,",");
-					// get up to the first MAXFLICKIES flickies
-					do {
-						if (mapheaderinfo[num-1]->numFlickies == MAXFLICKIES) // never going to get above that number
-						{
-							deh_warning("Level header %d: too many flickies\n", num);
-							break;
-						}
-
-						if (fastncmp(tmp, "MT_", 3)) // support for specified mobjtypes...
-						{
-							i = get_mobjtype(tmp);
-							if (!i)
-							{
-								//deh_warning("Level header %d: unknown flicky mobj type %s\n", num, tmp); -- no need for this line as get_mobjtype complains too
-								continue;
-							}
-							tmpflickies[mapheaderinfo[num-1]->numFlickies] = i;
-						}
-						else // ...or a quick, limited selection of default flickies!
-						{
-							for (i = 0; FLICKYTYPES[i].name; i++)
-								if (fastcmp(tmp, FLICKYTYPES[i].name))
-									break;
-
-							if (!FLICKYTYPES[i].name)
-							{
-								deh_warning("Level header %d: unknown flicky selection %s\n", num, tmp);
-								continue;
-							}
-							tmpflickies[mapheaderinfo[num-1]->numFlickies] = FLICKYTYPES[i].type;
-						}
-						mapheaderinfo[num-1]->numFlickies++;
-					} while ((tmp = strtok(NULL,",")) != NULL);
-
-					if (mapheaderinfo[num-1]->numFlickies)
-					{
-						size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num-1]->numFlickies;
-						mapheaderinfo[num-1]->flickies = Z_Realloc(mapheaderinfo[num-1]->flickies, newsize, PU_STATIC, NULL);
-						// now we add them to the list!
-						M_Memcpy(mapheaderinfo[num-1]->flickies, tmpflickies, newsize);
-					}
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_huditem(word2); // find a huditem by name
+					if (i >= 0 && i < NUMHUDITEMS)
+						readhuditem(f, i);
 					else
-						deh_warning("Level header %d: no valid flicky types found\n", num);
-				}
-			}
-
-			// NiGHTS grades
-			else if (fastncmp(word, "GRADES", 6))
-			{
-				UINT8 mare = (UINT8)atoi(word + 6);
-
-				if (mare <= 0 || mare > 8)
-				{
-					deh_warning("Level header %d: unknown word '%s'", num, word);
-					continue;
-				}
-
-				P_AddGradesForMare((INT16)(num-1), mare-1, word2);
-			}
-
-			// Strings that can be truncated
-			else if (fastcmp(word, "SELECTHEADING"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->selectheading, word2,
-					sizeof(mapheaderinfo[num-1]->selectheading), va("Level header %d: selectheading", num));
-			}
-			else if (fastcmp(word, "SCRIPTNAME"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->scriptname, word2,
-					sizeof(mapheaderinfo[num-1]->scriptname), va("Level header %d: scriptname", num));
-			}
-			else if (fastcmp(word, "RUNSOC"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->runsoc, word2,
-					sizeof(mapheaderinfo[num-1]->runsoc), va("Level header %d: runsoc", num));
-			}
-			else if (fastcmp(word, "ACT"))
-			{
-				if (i >= 0 && i <= 99) // 0 for no act number
-					mapheaderinfo[num-1]->actnum = (UINT8)i;
-				else
-					deh_warning("Level header %d: invalid act number %d", num, i);
-			}
-			else if (fastcmp(word, "NEXTLEVEL"))
-			{
-				if      (fastcmp(word2, "TITLE"))      i = 1100;
-				else if (fastcmp(word2, "EVALUATION")) i = 1101;
-				else if (fastcmp(word2, "CREDITS"))    i = 1102;
-				else if (fastcmp(word2, "ENDING"))     i = 1103;
-				else
-				// Support using the actual map name,
-				// i.e., Nextlevel = AB, Nextlevel = FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
-					i = M_MapNumber(word2[0], word2[1]);
-
-				mapheaderinfo[num-1]->nextlevel = (INT16)i;
-			}
-			else if (fastcmp(word, "MARATHONNEXT"))
-			{
-				if      (fastcmp(word2, "TITLE"))      i = 1100;
-				else if (fastcmp(word2, "EVALUATION")) i = 1101;
-				else if (fastcmp(word2, "CREDITS"))    i = 1102;
-				else if (fastcmp(word2, "ENDING"))     i = 1103;
-				else
-				// Support using the actual map name,
-				// i.e., MarathonNext = AB, MarathonNext = FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
-					i = M_MapNumber(word2[0], word2[1]);
-
-				mapheaderinfo[num-1]->marathonnext = (INT16)i;
-			}
-			else if (fastcmp(word, "TYPEOFLEVEL"))
-			{
-				if (i) // it's just a number
-					mapheaderinfo[num-1]->typeoflevel = (UINT32)i;
-				else
-				{
-					UINT32 tol = 0;
-					tmp = strtok(word2,",");
-					do {
-						for (i = 0; TYPEOFLEVEL[i].name; i++)
-							if (fastcmp(tmp, TYPEOFLEVEL[i].name))
-								break;
-						if (!TYPEOFLEVEL[i].name)
-							deh_warning("Level header %d: unknown typeoflevel flag %s\n", num, tmp);
-						tol |= TYPEOFLEVEL[i].flag;
-					} while((tmp = strtok(NULL,",")) != NULL);
-					mapheaderinfo[num-1]->typeoflevel = tol;
-				}
-			}
-			else if (fastcmp(word, "KEYWORDS"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->keywords, word2,
-						sizeof(mapheaderinfo[num-1]->keywords), va("Level header %d: keywords", num));
-			}
-			else if (fastcmp(word, "MUSIC"))
-			{
-				if (fastcmp(word2, "NONE"))
-					mapheaderinfo[num-1]->musname[0] = 0; // becomes empty string
-				else
-				{
-					deh_strlcpy(mapheaderinfo[num-1]->musname, word2,
-						sizeof(mapheaderinfo[num-1]->musname), va("Level header %d: music", num));
-				}
-			}
-#ifdef MUSICSLOT_COMPATIBILITY
-			else if (fastcmp(word, "MUSICSLOT"))
-			{
-				i = get_mus(word2, true);
-				if (i && i <= 1035)
-					snprintf(mapheaderinfo[num-1]->musname, 7, "%sM", G_BuildMapName(i));
-				else if (i && i <= 1050)
-					strncpy(mapheaderinfo[num-1]->musname, compat_special_music_slots[i - 1036], 7);
-				else
-					mapheaderinfo[num-1]->musname[0] = 0; // becomes empty string
-				mapheaderinfo[num-1]->musname[6] = 0;
-			}
-#endif
-			else if (fastcmp(word, "MUSICTRACK"))
-				mapheaderinfo[num-1]->mustrack = ((UINT16)i - 1);
-			else if (fastcmp(word, "MUSICPOS"))
-				mapheaderinfo[num-1]->muspos = (UINT32)get_number(word2);
-			else if (fastcmp(word, "MUSICINTERFADEOUT"))
-				mapheaderinfo[num-1]->musinterfadeout = (UINT32)get_number(word2);
-			else if (fastcmp(word, "MUSICINTER"))
-				deh_strlcpy(mapheaderinfo[num-1]->musintername, word2,
-					sizeof(mapheaderinfo[num-1]->musintername), va("Level header %d: intermission music", num));
-			else if (fastcmp(word, "MUSICPOSTBOSS"))
-				deh_strlcpy(mapheaderinfo[num-1]->muspostbossname, word2,
-					sizeof(mapheaderinfo[num-1]->muspostbossname), va("Level header %d: post-boss music", num));
-			else if (fastcmp(word, "MUSICPOSTBOSSTRACK"))
-				mapheaderinfo[num-1]->muspostbosstrack = ((UINT16)i - 1);
-			else if (fastcmp(word, "MUSICPOSTBOSSPOS"))
-				mapheaderinfo[num-1]->muspostbosspos = (UINT32)get_number(word2);
-			else if (fastcmp(word, "MUSICPOSTBOSSFADEIN"))
-				mapheaderinfo[num-1]->muspostbossfadein = (UINT32)get_number(word2);
-			else if (fastcmp(word, "FORCERESETMUSIC"))
-			{
-				// This is a weird one because "FALSE"/"NO" could either apply to "leave to default preference" (cv_resetmusic)
-				// or "force off". Let's assume it means "force off", and let an unspecified value mean "default preference"
-				if      (fastcmp(word2, "OFF") || word2[0] == 'F' || word2[0] == 'N')  i = 0;
-				else if (fastcmp(word2, "ON") || word2[0] == 'T' || word2[0] == 'Y')   i = 1;
-				else i = -1; // (fastcmp(word2, "DEFAULT"))
-
-				if (i >= -1 && i <= 1) // -1 to force off, 1 to force on, 0 to honor default.
-					// This behavior can be disabled with cv_resetmusicbyheader
-					mapheaderinfo[num-1]->musforcereset = (SINT8)i;
-				else
-					deh_warning("Level header %d: invalid forceresetmusic option %d", num, i);
-			}
-			else if (fastcmp(word, "FORCECHARACTER"))
-			{
-				strlcpy(mapheaderinfo[num-1]->forcecharacter, word2, SKINNAMESIZE+1);
-				strlwr(mapheaderinfo[num-1]->forcecharacter); // skin names are lowercase
-			}
-			else if (fastcmp(word, "WEATHER"))
-				mapheaderinfo[num-1]->weather = (UINT8)get_number(word2);
-			else if (fastcmp(word, "SKYNUM"))
-				mapheaderinfo[num-1]->skynum = (INT16)i;
-			else if (fastcmp(word, "INTERSCREEN"))
-				strncpy(mapheaderinfo[num-1]->interscreen, word2, 8);
-			else if (fastcmp(word, "PRECUTSCENENUM"))
-				mapheaderinfo[num-1]->precutscenenum = (UINT8)i;
-			else if (fastcmp(word, "CUTSCENENUM"))
-				mapheaderinfo[num-1]->cutscenenum = (UINT8)i;
-			else if (fastcmp(word, "COUNTDOWN"))
-				mapheaderinfo[num-1]->countdown = (INT16)i;
-			else if (fastcmp(word, "PALETTE"))
-				mapheaderinfo[num-1]->palette = (UINT16)i;
-			else if (fastcmp(word, "NUMLAPS"))
-				mapheaderinfo[num-1]->numlaps = (UINT8)i;
-			else if (fastcmp(word, "UNLOCKABLE"))
-			{
-				if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something
-					mapheaderinfo[num-1]->unlockrequired = (SINT8)i - 1;
-				else
-					deh_warning("Level header %d: invalid unlockable number %d", num, i);
-			}
-			else if (fastcmp(word, "LEVELSELECT"))
-				mapheaderinfo[num-1]->levelselect = (UINT8)i;
-			else if (fastcmp(word, "SKYBOXSCALE"))
-				mapheaderinfo[num-1]->skybox_scalex = mapheaderinfo[num-1]->skybox_scaley = mapheaderinfo[num-1]->skybox_scalez = (INT16)i;
-			else if (fastcmp(word, "SKYBOXSCALEX"))
-				mapheaderinfo[num-1]->skybox_scalex = (INT16)i;
-			else if (fastcmp(word, "SKYBOXSCALEY"))
-				mapheaderinfo[num-1]->skybox_scaley = (INT16)i;
-			else if (fastcmp(word, "SKYBOXSCALEZ"))
-				mapheaderinfo[num-1]->skybox_scalez = (INT16)i;
-
-			else if (fastcmp(word, "BONUSTYPE"))
-			{
-				if      (fastcmp(word2, "NONE"))   i = -1;
-				else if (fastcmp(word2, "NORMAL")) i =  0;
-				else if (fastcmp(word2, "BOSS"))   i =  1;
-				else if (fastcmp(word2, "ERZ3"))   i =  2;
-				else if (fastcmp(word2, "NIGHTS")) i =  3;
-				else if (fastcmp(word2, "NIGHTSLINK")) i = 4;
-
-				if (i >= -1 && i <= 4) // -1 for no bonus. Max is 4.
-					mapheaderinfo[num-1]->bonustype = (SINT8)i;
-				else
-					deh_warning("Level header %d: invalid bonus type number %d", num, i);
-			}
-
-			// Title card
-			else if (fastcmp(word, "TITLECARDZIGZAG"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->ltzzpatch, word2,
-					sizeof(mapheaderinfo[num-1]->ltzzpatch), va("Level header %d: title card zigzag patch name", num));
-			}
-			else if (fastcmp(word, "TITLECARDZIGZAGTEXT"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->ltzztext, word2,
-					sizeof(mapheaderinfo[num-1]->ltzztext), va("Level header %d: title card zigzag text patch name", num));
-			}
-			else if (fastcmp(word, "TITLECARDACTDIAMOND"))
-			{
-				deh_strlcpy(mapheaderinfo[num-1]->ltactdiamond, word2,
-					sizeof(mapheaderinfo[num-1]->ltactdiamond), va("Level header %d: title card act diamond patch name", num));
-			}
-
-			else if (fastcmp(word, "MAXBONUSLIVES"))
-				mapheaderinfo[num-1]->maxbonuslives = (SINT8)i;
-			else if (fastcmp(word, "LEVELFLAGS"))
-				mapheaderinfo[num-1]->levelflags = (UINT16)i;
-			else if (fastcmp(word, "MENUFLAGS"))
-				mapheaderinfo[num-1]->menuflags = (UINT8)i;
-
-			// Individual triggers for level flags, for ease of use (and 2.0 compatibility)
-			else if (fastcmp(word, "SCRIPTISFILE"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_SCRIPTISFILE;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_SCRIPTISFILE;
-			}
-			else if (fastcmp(word, "SPEEDMUSIC"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_SPEEDMUSIC;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_SPEEDMUSIC;
-			}
-			else if (fastcmp(word, "NOSSMUSIC"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_NOSSMUSIC;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_NOSSMUSIC;
-			}
-			else if (fastcmp(word, "NORELOAD"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_NORELOAD;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_NORELOAD;
-			}
-			else if (fastcmp(word, "NOZONE"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_NOZONE;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_NOZONE;
-			}
-			else if (fastcmp(word, "SAVEGAME"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_SAVEGAME;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_SAVEGAME;
-			}
-			else if (fastcmp(word, "MIXNIGHTSCOUNTDOWN"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_MIXNIGHTSCOUNTDOWN;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_MIXNIGHTSCOUNTDOWN;
-			}
-			else if (fastcmp(word, "WARNINGTITLE"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_WARNINGTITLE;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_WARNINGTITLE;
-			}
-			else if (fastcmp(word, "NOTITLECARD"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->levelflags |= LF_NOTITLECARD;
-				else
-					mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARD;
-			}
-			else if (fastcmp(word, "SHOWTITLECARDFOR"))
-			{
-				mapheaderinfo[num-1]->levelflags |= LF_NOTITLECARD;
-				tmp = strtok(word2,",");
-				do {
-					if (fastcmp(tmp, "FIRST"))
-						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARDFIRST;
-					else if (fastcmp(tmp, "RESPAWN"))
-						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARDRESPAWN;
-					else if (fastcmp(tmp, "RECORDATTACK"))
-						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARDRECORDATTACK;
-					else if (fastcmp(tmp, "ALL"))
-						mapheaderinfo[num-1]->levelflags &= ~LF_NOTITLECARD;
-					else if (!fastcmp(tmp, "NONE"))
-						deh_warning("Level header %d: unknown titlecard show option %s\n", num, tmp);
-
-				} while((tmp = strtok(NULL,",")) != NULL);
-			}
-
-			// Individual triggers for menu flags
-			else if (fastcmp(word, "HIDDEN"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->menuflags |= LF2_HIDEINMENU;
-				else
-					mapheaderinfo[num-1]->menuflags &= ~LF2_HIDEINMENU;
-			}
-			else if (fastcmp(word, "HIDEINSTATS"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->menuflags |= LF2_HIDEINSTATS;
-				else
-					mapheaderinfo[num-1]->menuflags &= ~LF2_HIDEINSTATS;
-			}
-			else if (fastcmp(word, "RECORDATTACK") || fastcmp(word, "TIMEATTACK"))
-			{ // TIMEATTACK is an accepted alias
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->menuflags |= LF2_RECORDATTACK;
-				else
-					mapheaderinfo[num-1]->menuflags &= ~LF2_RECORDATTACK;
-			}
-			else if (fastcmp(word, "NIGHTSATTACK"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->menuflags |= LF2_NIGHTSATTACK;
-				else
-					mapheaderinfo[num-1]->menuflags &= LF2_NIGHTSATTACK;
-			}
-			else if (fastcmp(word, "NOVISITNEEDED"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->menuflags |= LF2_NOVISITNEEDED;
-				else
-					mapheaderinfo[num-1]->menuflags &= ~LF2_NOVISITNEEDED;
-			}
-			else if (fastcmp(word, "WIDEICON"))
-			{
-				if (i || word2[0] == 'T' || word2[0] == 'Y')
-					mapheaderinfo[num-1]->menuflags |= LF2_WIDEICON;
-				else
-					mapheaderinfo[num-1]->menuflags &= ~LF2_WIDEICON;
-			}
-			else if (fastcmp(word, "STARTRINGS"))
-				mapheaderinfo[num-1]->startrings = (UINT16)i;
-			else if (fastcmp(word, "SPECIALSTAGETIME"))
-				mapheaderinfo[num-1]->sstimer = i;
-			else if (fastcmp(word, "SPECIALSTAGESPHERES"))
-				mapheaderinfo[num-1]->ssspheres = i;
-			else if (fastcmp(word, "GRAVITY"))
-				mapheaderinfo[num-1]->gravity = FLOAT_TO_FIXED(atof(word2));
-			else
-				deh_warning("Level header %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-#undef MAXFLICKIES
-
-static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
-{
-	char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	INT32 i;
-	UINT16 usi;
-	UINT8 picid;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			if (fastcmp(word, "SCENETEXT"))
-			{
-				char *scenetext = NULL;
-				char *buffer;
-				const int bufferlen = 4096;
-
-				for (i = 0; i < MAXLINELEN; i++)
-				{
-					if (s[i] == '=')
-					{
-						scenetext = &s[i+2];
-						break;
-					}
-				}
-
-				if (!scenetext)
-				{
-					Z_Free(cutscenes[num]->scene[scenenum].text);
-					cutscenes[num]->scene[scenenum].text = NULL;
-					continue;
-				}
-
-				for (i = 0; i < MAXLINELEN; i++)
-				{
-					if (s[i] == '\0')
 					{
-						s[i] = '\n';
-						s[i+1] = '\0';
-						break;
-					}
-				}
-
-				buffer = Z_Malloc(4096, PU_STATIC, NULL);
-				strcpy(buffer, scenetext);
-
-				strcat(buffer,
-					myhashfgets(scenetext, bufferlen
-					- strlen(buffer) - 1, f));
-
-				// A cutscene overwriting another one...
-				Z_Free(cutscenes[num]->scene[scenenum].text);
-
-				cutscenes[num]->scene[scenenum].text = Z_StrDup(buffer);
-
-				Z_Free(buffer);
-
-				continue;
-			}
-
-			word2 = strtok(NULL, " = ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-			i = atoi(word2);
-			usi = (UINT16)i;
-
-
-			if (fastcmp(word, "NUMBEROFPICS"))
-			{
-				cutscenes[num]->scene[scenenum].numpics = (UINT8)i;
-			}
-			else if (fastncmp(word, "PIC", 3))
-			{
-				picid = (UINT8)atoi(word + 3);
-				if (picid > 8 || picid == 0)
-				{
-					deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
-					continue;
-				}
-				--picid;
-
-				if (fastcmp(word+4, "NAME"))
-				{
-					strncpy(cutscenes[num]->scene[scenenum].picname[picid], word2, 8);
-				}
-				else if (fastcmp(word+4, "HIRES"))
-				{
-					cutscenes[num]->scene[scenenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
-				}
-				else if (fastcmp(word+4, "DURATION"))
-				{
-					cutscenes[num]->scene[scenenum].picduration[picid] = usi;
-				}
-				else if (fastcmp(word+4, "XCOORD"))
-				{
-					cutscenes[num]->scene[scenenum].xcoord[picid] = usi;
-				}
-				else if (fastcmp(word+4, "YCOORD"))
-				{
-					cutscenes[num]->scene[scenenum].ycoord[picid] = usi;
-				}
-				else
-					deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
-			}
-			else if (fastcmp(word, "MUSIC"))
-			{
-				strncpy(cutscenes[num]->scene[scenenum].musswitch, word2, 7);
-				cutscenes[num]->scene[scenenum].musswitch[6] = 0;
-			}
-#ifdef MUSICSLOT_COMPATIBILITY
-			else if (fastcmp(word, "MUSICSLOT"))
-			{
-				i = get_mus(word2, true);
-				if (i && i <= 1035)
-					snprintf(cutscenes[num]->scene[scenenum].musswitch, 7, "%sM", G_BuildMapName(i));
-				else if (i && i <= 1050)
-					strncpy(cutscenes[num]->scene[scenenum].musswitch, compat_special_music_slots[i - 1036], 7);
-				else
-					cutscenes[num]->scene[scenenum].musswitch[0] = 0; // becomes empty string
-				cutscenes[num]->scene[scenenum].musswitch[6] = 0;
-			}
-#endif
-			else if (fastcmp(word, "MUSICTRACK"))
-			{
-				cutscenes[num]->scene[scenenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK;
-			}
-			else if (fastcmp(word, "MUSICPOS"))
-			{
-				cutscenes[num]->scene[scenenum].musswitchposition = (UINT32)get_number(word2);
-			}
-			else if (fastcmp(word, "MUSICLOOP"))
-			{
-				cutscenes[num]->scene[scenenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
-			}
-			else if (fastcmp(word, "TEXTXPOS"))
-			{
-				cutscenes[num]->scene[scenenum].textxpos = usi;
-			}
-			else if (fastcmp(word, "TEXTYPOS"))
-			{
-				cutscenes[num]->scene[scenenum].textypos = usi;
-			}
-			else if (fastcmp(word, "FADEINID"))
-			{
-				cutscenes[num]->scene[scenenum].fadeinid = (UINT8)i;
-			}
-			else if (fastcmp(word, "FADEOUTID"))
-			{
-				cutscenes[num]->scene[scenenum].fadeoutid = (UINT8)i;
-			}
-			else if (fastcmp(word, "FADECOLOR"))
-			{
-				cutscenes[num]->scene[scenenum].fadecolor = (UINT8)i;
-			}
-			else
-				deh_warning("CutSceneScene %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readcutscene(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	char *tmp;
-	INT32 value;
-
-	// Allocate memory for this cutscene if we don't yet have any
-	if (!cutscenes[num])
-		cutscenes[num] = Z_Calloc(sizeof (cutscene_t), PU_STATIC, NULL);
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " ");
-			if (word2)
-				value = atoi(word2);
-			else
-			{
-				deh_warning("No value for token %s", word);
-				continue;
-			}
-
-			if (fastcmp(word, "NUMSCENES"))
-			{
-				cutscenes[num]->numscenes = value;
-			}
-			else if (fastcmp(word, "SCENE"))
-			{
-				if (1 <= value && value <= 128)
-				{
-					readcutscenescene(f, num, value - 1);
-				}
-				else
-					deh_warning("Scene number %d out of range (1 - 128)", value);
-
-			}
-			else
-				deh_warning("Cutscene %d: unknown word '%s', Scene <num> expected.", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum)
-{
-	char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	INT32 i;
-	UINT16 usi;
-	UINT8 picid;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			if (fastcmp(word, "PAGETEXT"))
-			{
-				char *pagetext = NULL;
-				char *buffer;
-				const int bufferlen = 4096;
-
-				for (i = 0; i < MAXLINELEN; i++)
-				{
-					if (s[i] == '=')
-					{
-						pagetext = &s[i+2];
-						break;
-					}
-				}
-
-				if (!pagetext)
-				{
-					Z_Free(textprompts[num]->page[pagenum].text);
-					textprompts[num]->page[pagenum].text = NULL;
-					continue;
-				}
-
-				for (i = 0; i < MAXLINELEN; i++)
-				{
-					if (s[i] == '\0')
-					{
-						s[i] = '\n';
-						s[i+1] = '\0';
-						break;
-					}
-				}
-
-				buffer = Z_Malloc(4096, PU_STATIC, NULL);
-				strcpy(buffer, pagetext);
-
-				// \todo trim trailing whitespace before the #
-				// and also support # at the end of a PAGETEXT with no line break
-
-				strcat(buffer,
-					myhashfgets(pagetext, bufferlen
-					- strlen(buffer) - 1, f));
-
-				// A text prompt overwriting another one...
-				Z_Free(textprompts[num]->page[pagenum].text);
-
-				textprompts[num]->page[pagenum].text = Z_StrDup(buffer);
-
-				Z_Free(buffer);
-
-				continue;
-			}
-
-			word2 = strtok(NULL, " = ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-			i = atoi(word2);
-			usi = (UINT16)i;
-
-			// copypasta from readcutscenescene
-			if (fastcmp(word, "NUMBEROFPICS"))
-			{
-				textprompts[num]->page[pagenum].numpics = (UINT8)i;
-			}
-			else if (fastcmp(word, "PICMODE"))
-			{
-				UINT8 picmode = 0; // PROMPT_PIC_PERSIST
-				if (usi == 1 || word2[0] == 'L') picmode = PROMPT_PIC_LOOP;
-				else if (usi == 2 || word2[0] == 'D' || word2[0] == 'H') picmode = PROMPT_PIC_DESTROY;
-				textprompts[num]->page[pagenum].picmode = picmode;
-			}
-			else if (fastcmp(word, "PICTOLOOP"))
-				textprompts[num]->page[pagenum].pictoloop = (UINT8)i;
-			else if (fastcmp(word, "PICTOSTART"))
-				textprompts[num]->page[pagenum].pictostart = (UINT8)i;
-			else if (fastcmp(word, "PICSMETAPAGE"))
-			{
-				if (usi && usi <= textprompts[num]->numpages)
-				{
-					UINT8 metapagenum = usi - 1;
-
-					textprompts[num]->page[pagenum].numpics = textprompts[num]->page[metapagenum].numpics;
-					textprompts[num]->page[pagenum].picmode = textprompts[num]->page[metapagenum].picmode;
-					textprompts[num]->page[pagenum].pictoloop = textprompts[num]->page[metapagenum].pictoloop;
-					textprompts[num]->page[pagenum].pictostart = textprompts[num]->page[metapagenum].pictostart;
-
-					for (picid = 0; picid < MAX_PROMPT_PICS; picid++)
-					{
-						strncpy(textprompts[num]->page[pagenum].picname[picid], textprompts[num]->page[metapagenum].picname[picid], 8);
-						textprompts[num]->page[pagenum].pichires[picid] = textprompts[num]->page[metapagenum].pichires[picid];
-						textprompts[num]->page[pagenum].picduration[picid] = textprompts[num]->page[metapagenum].picduration[picid];
-						textprompts[num]->page[pagenum].xcoord[picid] = textprompts[num]->page[metapagenum].xcoord[picid];
-						textprompts[num]->page[pagenum].ycoord[picid] = textprompts[num]->page[metapagenum].ycoord[picid];
-					}
-				}
-			}
-			else if (fastncmp(word, "PIC", 3))
-			{
-				picid = (UINT8)atoi(word + 3);
-				if (picid > MAX_PROMPT_PICS || picid == 0)
-				{
-					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
-					continue;
-				}
-				--picid;
-
-				if (fastcmp(word+4, "NAME"))
-				{
-					strncpy(textprompts[num]->page[pagenum].picname[picid], word2, 8);
-				}
-				else if (fastcmp(word+4, "HIRES"))
-				{
-					textprompts[num]->page[pagenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
-				}
-				else if (fastcmp(word+4, "DURATION"))
-				{
-					textprompts[num]->page[pagenum].picduration[picid] = usi;
-				}
-				else if (fastcmp(word+4, "XCOORD"))
-				{
-					textprompts[num]->page[pagenum].xcoord[picid] = usi;
-				}
-				else if (fastcmp(word+4, "YCOORD"))
-				{
-					textprompts[num]->page[pagenum].ycoord[picid] = usi;
-				}
-				else
-					deh_warning("textpromptscene %d: unknown word '%s'", num, word);
-			}
-			else if (fastcmp(word, "MUSIC"))
-			{
-				strncpy(textprompts[num]->page[pagenum].musswitch, word2, 7);
-				textprompts[num]->page[pagenum].musswitch[6] = 0;
-			}
-#ifdef MUSICSLOT_COMPATIBILITY
-			else if (fastcmp(word, "MUSICSLOT"))
-			{
-				i = get_mus(word2, true);
-				if (i && i <= 1035)
-					snprintf(textprompts[num]->page[pagenum].musswitch, 7, "%sM", G_BuildMapName(i));
-				else if (i && i <= 1050)
-					strncpy(textprompts[num]->page[pagenum].musswitch, compat_special_music_slots[i - 1036], 7);
-				else
-					textprompts[num]->page[pagenum].musswitch[0] = 0; // becomes empty string
-				textprompts[num]->page[pagenum].musswitch[6] = 0;
-			}
-#endif
-			else if (fastcmp(word, "MUSICTRACK"))
-			{
-				textprompts[num]->page[pagenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK;
-			}
-			else if (fastcmp(word, "MUSICLOOP"))
-			{
-				textprompts[num]->page[pagenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
-			}
-			// end copypasta from readcutscenescene
-			else if (fastcmp(word, "NAME"))
-			{
-				if (*word2 != '\0')
-				{
-					INT32 j;
-
-					// HACK: Add yellow control char now
-					// so the drawing function doesn't call it repeatedly
-					char name[34];
-					name[0] = '\x82'; // color yellow
-					name[1] = 0;
-					strncat(name, word2, 33);
-					name[33] = 0;
-
-					// Replace _ with ' '
-					for (j = 0; j < 32 && name[j]; j++)
-					{
-						if (name[j] == '_')
-							name[j] = ' ';
-					}
-
-					strncpy(textprompts[num]->page[pagenum].name, name, 32);
-				}
-				else
-					*textprompts[num]->page[pagenum].name = '\0';
-			}
-			else if (fastcmp(word, "ICON"))
-				strncpy(textprompts[num]->page[pagenum].iconname, word2, 8);
-			else if (fastcmp(word, "ICONALIGN"))
-				textprompts[num]->page[pagenum].rightside = (i || word2[0] == 'R');
-			else if (fastcmp(word, "ICONFLIP"))
-				textprompts[num]->page[pagenum].iconflip = (i || word2[0] == 'T' || word2[0] == 'Y');
-			else if (fastcmp(word, "LINES"))
-				textprompts[num]->page[pagenum].lines = usi;
-			else if (fastcmp(word, "BACKCOLOR"))
-			{
-				INT32 backcolor;
-				if      (i == 0 || fastcmp(word2, "WHITE")) backcolor = 0;
-				else if (i == 1 || fastcmp(word2, "GRAY") || fastcmp(word2, "GREY") ||
-					fastcmp(word2, "BLACK")) backcolor = 1;
-				else if (i == 2 || fastcmp(word2, "SEPIA")) backcolor = 2;
-				else if (i == 3 || fastcmp(word2, "BROWN")) backcolor = 3;
-				else if (i == 4 || fastcmp(word2, "PINK")) backcolor = 4;
-				else if (i == 5 || fastcmp(word2, "RASPBERRY")) backcolor = 5;
-				else if (i == 6 || fastcmp(word2, "RED")) backcolor = 6;
-				else if (i == 7 || fastcmp(word2, "CREAMSICLE")) backcolor = 7;
-				else if (i == 8 || fastcmp(word2, "ORANGE")) backcolor = 8;
-				else if (i == 9 || fastcmp(word2, "GOLD")) backcolor = 9;
-				else if (i == 10 || fastcmp(word2, "YELLOW")) backcolor = 10;
-				else if (i == 11 || fastcmp(word2, "EMERALD")) backcolor = 11;
-				else if (i == 12 || fastcmp(word2, "GREEN")) backcolor = 12;
-				else if (i == 13 || fastcmp(word2, "CYAN") || fastcmp(word2, "AQUA")) backcolor = 13;
-				else if (i == 14 || fastcmp(word2, "STEEL")) backcolor = 14;
-				else if (i == 15 || fastcmp(word2, "PERIWINKLE")) backcolor = 15;
-				else if (i == 16 || fastcmp(word2, "BLUE")) backcolor = 16;
-				else if (i == 17 || fastcmp(word2, "PURPLE")) backcolor = 17;
-				else if (i == 18 || fastcmp(word2, "LAVENDER")) backcolor = 18;
-				else if (i >= 256 && i < 512) backcolor = i; // non-transparent palette index
-				else if (i < 0) backcolor = INT32_MAX; // CONS_BACKCOLOR user-configured
-				else backcolor = 1; // default gray
-				textprompts[num]->page[pagenum].backcolor = backcolor;
-			}
-			else if (fastcmp(word, "ALIGN"))
-			{
-				UINT8 align = 0; // left
-				if (usi == 1 || word2[0] == 'R') align = 1;
-				else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2;
-				textprompts[num]->page[pagenum].align = align;
-			}
-			else if (fastcmp(word, "VERTICALALIGN"))
-			{
-				UINT8 align = 0; // top
-				if (usi == 1 || word2[0] == 'B') align = 1;
-				else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2;
-				textprompts[num]->page[pagenum].verticalalign = align;
-			}
-			else if (fastcmp(word, "TEXTSPEED"))
-				textprompts[num]->page[pagenum].textspeed = get_number(word2);
-			else if (fastcmp(word, "TEXTSFX"))
-				textprompts[num]->page[pagenum].textsfx = get_number(word2);
-			else if (fastcmp(word, "HIDEHUD"))
-			{
-				UINT8 hidehud = 0;
-				if ((word2[0] == 'F' && (word2[1] == 'A' || !word2[1])) || word2[0] == 'N') hidehud = 0; // false
-				else if (usi == 1 || word2[0] == 'T' || word2[0] == 'Y') hidehud = 1; // true (hide appropriate HUD elements)
-				else if (usi == 2 || word2[0] == 'A' || (word2[0] == 'F' && word2[1] == 'O')) hidehud = 2; // force (hide all HUD elements)
-				textprompts[num]->page[pagenum].hidehud = hidehud;
-			}
-			else if (fastcmp(word, "METAPAGE"))
-			{
-				if (usi && usi <= textprompts[num]->numpages)
-				{
-					UINT8 metapagenum = usi - 1;
-
-					strncpy(textprompts[num]->page[pagenum].name, textprompts[num]->page[metapagenum].name, 32);
-					strncpy(textprompts[num]->page[pagenum].iconname, textprompts[num]->page[metapagenum].iconname, 8);
-					textprompts[num]->page[pagenum].rightside = textprompts[num]->page[metapagenum].rightside;
-					textprompts[num]->page[pagenum].iconflip = textprompts[num]->page[metapagenum].iconflip;
-					textprompts[num]->page[pagenum].lines = textprompts[num]->page[metapagenum].lines;
-					textprompts[num]->page[pagenum].backcolor = textprompts[num]->page[metapagenum].backcolor;
-					textprompts[num]->page[pagenum].align = textprompts[num]->page[metapagenum].align;
-					textprompts[num]->page[pagenum].verticalalign = textprompts[num]->page[metapagenum].verticalalign;
-					textprompts[num]->page[pagenum].textspeed = textprompts[num]->page[metapagenum].textspeed;
-					textprompts[num]->page[pagenum].textsfx = textprompts[num]->page[metapagenum].textsfx;
-					textprompts[num]->page[pagenum].hidehud = textprompts[num]->page[metapagenum].hidehud;
-
-					// music: don't copy, else each page change may reset the music
-				}
-			}
-			else if (fastcmp(word, "TAG"))
-				strncpy(textprompts[num]->page[pagenum].tag, word2, 33);
-			else if (fastcmp(word, "NEXTPROMPT"))
-				textprompts[num]->page[pagenum].nextprompt = usi;
-			else if (fastcmp(word, "NEXTPAGE"))
-				textprompts[num]->page[pagenum].nextpage = usi;
-			else if (fastcmp(word, "NEXTTAG"))
-				strncpy(textprompts[num]->page[pagenum].nexttag, word2, 33);
-			else if (fastcmp(word, "TIMETONEXT"))
-				textprompts[num]->page[pagenum].timetonext = get_number(word2);
-			else
-				deh_warning("PromptPage %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readtextprompt(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	char *tmp;
-	INT32 value;
-
-	// Allocate memory for this prompt if we don't yet have any
-	if (!textprompts[num])
-		textprompts[num] = Z_Calloc(sizeof (textprompt_t), PU_STATIC, NULL);
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " ");
-			if (word2)
-				value = atoi(word2);
-			else
-			{
-				deh_warning("No value for token %s", word);
-				continue;
-			}
-
-			if (fastcmp(word, "NUMPAGES"))
-			{
-				textprompts[num]->numpages = min(max(value, 0), MAX_PAGES);
-			}
-			else if (fastcmp(word, "PAGE"))
-			{
-				if (1 <= value && value <= MAX_PAGES)
-				{
-					textprompts[num]->page[value - 1].backcolor = 1; // default to gray
-					textprompts[num]->page[value - 1].hidehud = 1; // hide appropriate HUD elements
-					readtextpromptpage(f, num, value - 1);
-				}
-				else
-					deh_warning("Page number %d out of range (1 - %d)", value, MAX_PAGES);
-
-			}
-			else
-				deh_warning("Prompt %d: unknown word '%s', Page <num> expected.", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readmenu(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-	INT32 value;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = (tmp += 2);
-			strupr(word2);
-
-			value = atoi(word2); // used for numerical settings
-
-			if (fastcmp(word, "BACKGROUNDNAME"))
-			{
-				strncpy(menupres[num].bgname, word2, 8);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "HIDEBACKGROUND"))
-			{
-				menupres[num].bghide = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "BACKGROUNDCOLOR"))
-			{
-				menupres[num].bgcolor = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS") || fastcmp(word, "TITLEPICSHIDE"))
-			{
-				// true by default, except MM_MAIN
-				menupres[num].hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSMODE"))
-			{
-				if (fastcmp(word2, "USER"))
-					menupres[num].ttmode = TTMODE_USER;
-				else if (fastcmp(word2, "ALACROIX"))
-					menupres[num].ttmode = TTMODE_ALACROIX;
-				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
-				{
-					menupres[num].ttmode = TTMODE_USER;
-					menupres[num].ttname[0] = 0;
-					menupres[num].hidetitlepics = true;
-				}
-				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
-					menupres[num].ttmode = TTMODE_OLD;
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSSCALE"))
-			{
-				// Don't handle Alacroix special case here; see Maincfg section.
-				menupres[num].ttscale = max(1, min(8, (UINT8)get_number(word2)));
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSNAME"))
-			{
-				strncpy(menupres[num].ttname, word2, 9);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSX"))
-			{
-				menupres[num].ttx = (INT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSY"))
-			{
-				menupres[num].tty = (INT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSLOOP"))
-			{
-				menupres[num].ttloop = (INT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSTICS"))
-			{
-				menupres[num].tttics = (UINT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED")
-				|| fastcmp(word, "SCROLLSPEED") || fastcmp(word, "SCROLLXSPEED"))
-			{
-				menupres[num].titlescrollxspeed = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLESCROLLYSPEED") || fastcmp(word, "SCROLLYSPEED"))
-			{
-				menupres[num].titlescrollyspeed = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "MUSIC"))
-			{
-				strncpy(menupres[num].musname, word2, 7);
-				menupres[num].musname[6] = 0;
-				titlechanged = true;
-			}
-#ifdef MUSICSLOT_COMPATIBILITY
-			else if (fastcmp(word, "MUSICSLOT"))
-			{
-				value = get_mus(word2, true);
-				if (value && value <= 1035)
-					snprintf(menupres[num].musname, 7, "%sM", G_BuildMapName(value));
-				else if (value && value <= 1050)
-					strncpy(menupres[num].musname, compat_special_music_slots[value - 1036], 7);
-				else
-					menupres[num].musname[0] = 0; // becomes empty string
-				menupres[num].musname[6] = 0;
-				titlechanged = true;
-			}
-#endif
-			else if (fastcmp(word, "MUSICTRACK"))
-			{
-				menupres[num].mustrack = ((UINT16)value - 1);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "MUSICLOOP"))
-			{
-				// true by default except MM_MAIN
-				menupres[num].muslooping = (value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "NOMUSIC"))
-			{
-				menupres[num].musstop = (value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "IGNOREMUSIC"))
-			{
-				menupres[num].musignore = (value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "FADESTRENGTH"))
-			{
-				// one-based, <= 0 means use default value. 1-32
-				menupres[num].fadestrength = get_number(word2)-1;
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "NOENTERBUBBLE"))
-			{
-				menupres[num].enterbubble = !(value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "NOEXITBUBBLE"))
-			{
-				menupres[num].exitbubble = !(value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "ENTERTAG"))
-			{
-				menupres[num].entertag = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "EXITTAG"))
-			{
-				menupres[num].exittag = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "ENTERWIPE"))
-			{
-				menupres[num].enterwipe = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "EXITWIPE"))
-			{
-				menupres[num].exitwipe = get_number(word2);
-				titlechanged = true;
-			}
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readhuditem(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-	INT32 i;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-			strupr(word2);
-
-			i = atoi(word2); // used for numerical settings
-
-			if (fastcmp(word, "X"))
-			{
-				hudinfo[num].x = i;
-			}
-			else if (fastcmp(word, "Y"))
-			{
-				hudinfo[num].y = i;
-			}
-			else if (fastcmp(word, "F"))
-			{
-				hudinfo[num].f = i;
-			}
-			else
-				deh_warning("Level header %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-/*
-Sprite number = 10
-Sprite subnumber = 32968
-Duration = 200
-Next frame = 200
-*/
-
-/** Action pointer for reading actions from Dehacked lumps.
-  */
-typedef struct
-{
-	actionf_t action; ///< Function pointer corresponding to the actual action.
-	const char *name; ///< Name of the action in ALL CAPS.
-} actionpointer_t;
-
-/** Array mapping action names to action functions.
-  * Names must be in ALL CAPS for case insensitive comparisons.
-  */
-static actionpointer_t actionpointers[] =
-{
-	{{A_Explode},                "A_EXPLODE"},
-	{{A_Pain},                   "A_PAIN"},
-	{{A_Fall},                   "A_FALL"},
-	{{A_MonitorPop},             "A_MONITORPOP"},
-	{{A_GoldMonitorPop},         "A_GOLDMONITORPOP"},
-	{{A_GoldMonitorRestore},     "A_GOLDMONITORRESTORE"},
-	{{A_GoldMonitorSparkle},     "A_GOLDMONITORSPARKLE"},
-	{{A_Look},                   "A_LOOK"},
-	{{A_Chase},                  "A_CHASE"},
-	{{A_FaceStabChase},          "A_FACESTABCHASE"},
-	{{A_FaceStabRev},            "A_FACESTABREV"},
-	{{A_FaceStabHurl},           "A_FACESTABHURL"},
-	{{A_FaceStabMiss},           "A_FACESTABMISS"},
-	{{A_StatueBurst},            "A_STATUEBURST"},
-	{{A_FaceTarget},             "A_FACETARGET"},
-	{{A_FaceTracer},             "A_FACETRACER"},
-	{{A_Scream},                 "A_SCREAM"},
-	{{A_BossDeath},              "A_BOSSDEATH"},
-	{{A_CustomPower},            "A_CUSTOMPOWER"},
-	{{A_GiveWeapon},             "A_GIVEWEAPON"},
-	{{A_RingBox},                "A_RINGBOX"},
-	{{A_Invincibility},          "A_INVINCIBILITY"},
-	{{A_SuperSneakers},          "A_SUPERSNEAKERS"},
-	{{A_BunnyHop},               "A_BUNNYHOP"},
-	{{A_BubbleSpawn},            "A_BUBBLESPAWN"},
-	{{A_FanBubbleSpawn},         "A_FANBUBBLESPAWN"},
-	{{A_BubbleRise},             "A_BUBBLERISE"},
-	{{A_BubbleCheck},            "A_BUBBLECHECK"},
-	{{A_AwardScore},             "A_AWARDSCORE"},
-	{{A_ExtraLife},              "A_EXTRALIFE"},
-	{{A_GiveShield},             "A_GIVESHIELD"},
-	{{A_GravityBox},             "A_GRAVITYBOX"},
-	{{A_ScoreRise},              "A_SCORERISE"},
-	{{A_AttractChase},           "A_ATTRACTCHASE"},
-	{{A_DropMine},               "A_DROPMINE"},
-	{{A_FishJump},               "A_FISHJUMP"},
-	{{A_ThrownRing},             "A_THROWNRING"},
-	{{A_SetSolidSteam},          "A_SETSOLIDSTEAM"},
-	{{A_UnsetSolidSteam},        "A_UNSETSOLIDSTEAM"},
-	{{A_SignSpin},               "A_SIGNSPIN"},
-	{{A_SignPlayer},             "A_SIGNPLAYER"},
-	{{A_OverlayThink},           "A_OVERLAYTHINK"},
-	{{A_JetChase},               "A_JETCHASE"},
-	{{A_JetbThink},              "A_JETBTHINK"},
-	{{A_JetgThink},              "A_JETGTHINK"},
-	{{A_JetgShoot},              "A_JETGSHOOT"},
-	{{A_ShootBullet},            "A_SHOOTBULLET"},
-	{{A_MinusDigging},           "A_MINUSDIGGING"},
-	{{A_MinusPopup},             "A_MINUSPOPUP"},
-	{{A_MinusCheck},             "A_MINUSCHECK"},
-	{{A_ChickenCheck},           "A_CHICKENCHECK"},
-	{{A_MouseThink},             "A_MOUSETHINK"},
-	{{A_DetonChase},             "A_DETONCHASE"},
-	{{A_CapeChase},              "A_CAPECHASE"},
-	{{A_RotateSpikeBall},        "A_ROTATESPIKEBALL"},
-	{{A_SlingAppear},            "A_SLINGAPPEAR"},
-	{{A_UnidusBall},             "A_UNIDUSBALL"},
-	{{A_RockSpawn},              "A_ROCKSPAWN"},
-	{{A_SetFuse},                "A_SETFUSE"},
-	{{A_CrawlaCommanderThink},   "A_CRAWLACOMMANDERTHINK"},
-	{{A_SmokeTrailer},           "A_SMOKETRAILER"},
-	{{A_RingExplode},            "A_RINGEXPLODE"},
-	{{A_OldRingExplode},         "A_OLDRINGEXPLODE"},
-	{{A_MixUp},                  "A_MIXUP"},
-	{{A_RecyclePowers},          "A_RECYCLEPOWERS"},
-	{{A_Boss1Chase},             "A_BOSS1CHASE"},
-	{{A_FocusTarget},            "A_FOCUSTARGET"},
-	{{A_Boss2Chase},             "A_BOSS2CHASE"},
-	{{A_Boss2Pogo},              "A_BOSS2POGO"},
-	{{A_BossZoom},               "A_BOSSZOOM"},
-	{{A_BossScream},             "A_BOSSSCREAM"},
-	{{A_Boss2TakeDamage},        "A_BOSS2TAKEDAMAGE"},
-	{{A_Boss7Chase},             "A_BOSS7CHASE"},
-	{{A_GoopSplat},              "A_GOOPSPLAT"},
-	{{A_Boss2PogoSFX},           "A_BOSS2POGOSFX"},
-	{{A_Boss2PogoTarget},        "A_BOSS2POGOTARGET"},
-	{{A_BossJetFume},            "A_BOSSJETFUME"},
-	{{A_EggmanBox},              "A_EGGMANBOX"},
-	{{A_TurretFire},             "A_TURRETFIRE"},
-	{{A_SuperTurretFire},        "A_SUPERTURRETFIRE"},
-	{{A_TurretStop},             "A_TURRETSTOP"},
-	{{A_JetJawRoam},             "A_JETJAWROAM"},
-	{{A_JetJawChomp},            "A_JETJAWCHOMP"},
-	{{A_PointyThink},            "A_POINTYTHINK"},
-	{{A_CheckBuddy},             "A_CHECKBUDDY"},
-	{{A_HoodFire},               "A_HOODFIRE"},
-	{{A_HoodThink},              "A_HOODTHINK"},
-	{{A_HoodFall},               "A_HOODFALL"},
-	{{A_ArrowBonks},             "A_ARROWBONKS"},
-	{{A_SnailerThink},           "A_SNAILERTHINK"},
-	{{A_SharpChase},             "A_SHARPCHASE"},
-	{{A_SharpSpin},              "A_SHARPSPIN"},
-	{{A_SharpDecel},             "A_SHARPDECEL"},
-	{{A_CrushstaceanWalk},       "A_CRUSHSTACEANWALK"},
-	{{A_CrushstaceanPunch},      "A_CRUSHSTACEANPUNCH"},
-	{{A_CrushclawAim},           "A_CRUSHCLAWAIM"},
-	{{A_CrushclawLaunch},        "A_CRUSHCLAWLAUNCH"},
-	{{A_VultureVtol},            "A_VULTUREVTOL"},
-	{{A_VultureCheck},           "A_VULTURECHECK"},
-	{{A_VultureHover},           "A_VULTUREHOVER"},
-	{{A_VultureBlast},           "A_VULTUREBLAST"},
-	{{A_VultureFly},             "A_VULTUREFLY"},
-	{{A_SkimChase},              "A_SKIMCHASE"},
-	{{A_1upThinker},             "A_1UPTHINKER"},
-	{{A_SkullAttack},            "A_SKULLATTACK"},
-	{{A_LobShot},                "A_LOBSHOT"},
-	{{A_FireShot},               "A_FIRESHOT"},
-	{{A_SuperFireShot},          "A_SUPERFIRESHOT"},
-	{{A_BossFireShot},           "A_BOSSFIRESHOT"},
-	{{A_Boss7FireMissiles},      "A_BOSS7FIREMISSILES"},
-	{{A_Boss1Laser},             "A_BOSS1LASER"},
-	{{A_Boss4Reverse},           "A_BOSS4REVERSE"},
-	{{A_Boss4SpeedUp},           "A_BOSS4SPEEDUP"},
-	{{A_Boss4Raise},             "A_BOSS4RAISE"},
-	{{A_SparkFollow},            "A_SPARKFOLLOW"},
-	{{A_BuzzFly},                "A_BUZZFLY"},
-	{{A_GuardChase},             "A_GUARDCHASE"},
-	{{A_EggShield},              "A_EGGSHIELD"},
-	{{A_SetReactionTime},        "A_SETREACTIONTIME"},
-	{{A_Boss1Spikeballs},        "A_BOSS1SPIKEBALLS"},
-	{{A_Boss3TakeDamage},        "A_BOSS3TAKEDAMAGE"},
-	{{A_Boss3Path},              "A_BOSS3PATH"},
-	{{A_Boss3ShockThink},        "A_BOSS3SHOCKTHINK"},
-	{{A_LinedefExecute},         "A_LINEDEFEXECUTE"},
-	{{A_PlaySeeSound},           "A_PLAYSEESOUND"},
-	{{A_PlayAttackSound},        "A_PLAYATTACKSOUND"},
-	{{A_PlayActiveSound},        "A_PLAYACTIVESOUND"},
-	{{A_SpawnObjectAbsolute},    "A_SPAWNOBJECTABSOLUTE"},
-	{{A_SpawnObjectRelative},    "A_SPAWNOBJECTRELATIVE"},
-	{{A_ChangeAngleRelative},    "A_CHANGEANGLERELATIVE"},
-	{{A_ChangeAngleAbsolute},    "A_CHANGEANGLEABSOLUTE"},
-	{{A_RollAngle},              "A_ROLLANGLE"},
-	{{A_ChangeRollAngleRelative},"A_CHANGEROLLANGLERELATIVE"},
-	{{A_ChangeRollAngleAbsolute},"A_CHANGEROLLANGLEABSOLUTE"},
-	{{A_PlaySound},              "A_PLAYSOUND"},
-	{{A_FindTarget},             "A_FINDTARGET"},
-	{{A_FindTracer},             "A_FINDTRACER"},
-	{{A_SetTics},                "A_SETTICS"},
-	{{A_SetRandomTics},          "A_SETRANDOMTICS"},
-	{{A_ChangeColorRelative},    "A_CHANGECOLORRELATIVE"},
-	{{A_ChangeColorAbsolute},    "A_CHANGECOLORABSOLUTE"},
-	{{A_Dye},                    "A_DYE"},
-	{{A_MoveRelative},           "A_MOVERELATIVE"},
-	{{A_MoveAbsolute},           "A_MOVEABSOLUTE"},
-	{{A_Thrust},                 "A_THRUST"},
-	{{A_ZThrust},                "A_ZTHRUST"},
-	{{A_SetTargetsTarget},       "A_SETTARGETSTARGET"},
-	{{A_SetObjectFlags},         "A_SETOBJECTFLAGS"},
-	{{A_SetObjectFlags2},        "A_SETOBJECTFLAGS2"},
-	{{A_RandomState},            "A_RANDOMSTATE"},
-	{{A_RandomStateRange},       "A_RANDOMSTATERANGE"},
-	{{A_DualAction},             "A_DUALACTION"},
-	{{A_RemoteAction},           "A_REMOTEACTION"},
-	{{A_ToggleFlameJet},         "A_TOGGLEFLAMEJET"},
-	{{A_OrbitNights},            "A_ORBITNIGHTS"},
-	{{A_GhostMe},                "A_GHOSTME"},
-	{{A_SetObjectState},         "A_SETOBJECTSTATE"},
-	{{A_SetObjectTypeState},     "A_SETOBJECTTYPESTATE"},
-	{{A_KnockBack},              "A_KNOCKBACK"},
-	{{A_PushAway},               "A_PUSHAWAY"},
-	{{A_RingDrain},              "A_RINGDRAIN"},
-	{{A_SplitShot},              "A_SPLITSHOT"},
-	{{A_MissileSplit},           "A_MISSILESPLIT"},
-	{{A_MultiShot},              "A_MULTISHOT"},
-	{{A_InstaLoop},              "A_INSTALOOP"},
-	{{A_Custom3DRotate},         "A_CUSTOM3DROTATE"},
-	{{A_SearchForPlayers},       "A_SEARCHFORPLAYERS"},
-	{{A_CheckRandom},            "A_CHECKRANDOM"},
-	{{A_CheckTargetRings},       "A_CHECKTARGETRINGS"},
-	{{A_CheckRings},             "A_CHECKRINGS"},
-	{{A_CheckTotalRings},        "A_CHECKTOTALRINGS"},
-	{{A_CheckHealth},            "A_CHECKHEALTH"},
-	{{A_CheckRange},             "A_CHECKRANGE"},
-	{{A_CheckHeight},            "A_CHECKHEIGHT"},
-	{{A_CheckTrueRange},         "A_CHECKTRUERANGE"},
-	{{A_CheckThingCount},        "A_CHECKTHINGCOUNT"},
-	{{A_CheckAmbush},            "A_CHECKAMBUSH"},
-	{{A_CheckCustomValue},       "A_CHECKCUSTOMVALUE"},
-	{{A_CheckCusValMemo},        "A_CHECKCUSVALMEMO"},
-	{{A_SetCustomValue},         "A_SETCUSTOMVALUE"},
-	{{A_UseCusValMemo},          "A_USECUSVALMEMO"},
-	{{A_RelayCustomValue},       "A_RELAYCUSTOMVALUE"},
-	{{A_CusValAction},           "A_CUSVALACTION"},
-	{{A_ForceStop},              "A_FORCESTOP"},
-	{{A_ForceWin},               "A_FORCEWIN"},
-	{{A_SpikeRetract},           "A_SPIKERETRACT"},
-	{{A_InfoState},              "A_INFOSTATE"},
-	{{A_Repeat},                 "A_REPEAT"},
-	{{A_SetScale},               "A_SETSCALE"},
-	{{A_RemoteDamage},           "A_REMOTEDAMAGE"},
-	{{A_HomingChase},            "A_HOMINGCHASE"},
-	{{A_TrapShot},               "A_TRAPSHOT"},
-	{{A_VileTarget},             "A_VILETARGET"},
-	{{A_VileAttack},             "A_VILEATTACK"},
-	{{A_VileFire},               "A_VILEFIRE"},
-	{{A_BrakChase},              "A_BRAKCHASE"},
-	{{A_BrakFireShot},           "A_BRAKFIRESHOT"},
-	{{A_BrakLobShot},            "A_BRAKLOBSHOT"},
-	{{A_NapalmScatter},          "A_NAPALMSCATTER"},
-	{{A_SpawnFreshCopy},         "A_SPAWNFRESHCOPY"},
-	{{A_FlickySpawn},            "A_FLICKYSPAWN"},
-	{{A_FlickyCenter},           "A_FLICKYCENTER"},
-	{{A_FlickyAim},              "A_FLICKYAIM"},
-	{{A_FlickyFly},              "A_FLICKYFLY"},
-	{{A_FlickySoar},             "A_FLICKYSOAR"},
-	{{A_FlickyCoast},            "A_FLICKYCOAST"},
-	{{A_FlickyHop},              "A_FLICKYHOP"},
-	{{A_FlickyFlounder},         "A_FLICKYFLOUNDER"},
-	{{A_FlickyCheck},            "A_FLICKYCHECK"},
-	{{A_FlickyHeightCheck},      "A_FLICKYHEIGHTCHECK"},
-	{{A_FlickyFlutter},          "A_FLICKYFLUTTER"},
-	{{A_FlameParticle},          "A_FLAMEPARTICLE"},
-	{{A_FadeOverlay},            "A_FADEOVERLAY"},
-	{{A_Boss5Jump},              "A_BOSS5JUMP"},
-	{{A_LightBeamReset},         "A_LIGHTBEAMRESET"},
-	{{A_MineExplode},            "A_MINEEXPLODE"},
-	{{A_MineRange},              "A_MINERANGE"},
-	{{A_ConnectToGround},        "A_CONNECTTOGROUND"},
-	{{A_SpawnParticleRelative},  "A_SPAWNPARTICLERELATIVE"},
-	{{A_MultiShotDist},          "A_MULTISHOTDIST"},
-	{{A_WhoCaresIfYourSonIsABee},"A_WHOCARESIFYOURSONISABEE"},
-	{{A_ParentTriesToSleep},     "A_PARENTTRIESTOSLEEP"},
-	{{A_CryingToMomma},          "A_CRYINGTOMOMMA"},
-	{{A_CheckFlags2},            "A_CHECKFLAGS2"},
-	{{A_Boss5FindWaypoint},      "A_BOSS5FINDWAYPOINT"},
-	{{A_DoNPCSkid},              "A_DONPCSKID"},
-	{{A_DoNPCPain},              "A_DONPCPAIN"},
-	{{A_PrepareRepeat},          "A_PREPAREREPEAT"},
-	{{A_Boss5ExtraRepeat},       "A_BOSS5EXTRAREPEAT"},
-	{{A_Boss5Calm},              "A_BOSS5CALM"},
-	{{A_Boss5CheckOnGround},     "A_BOSS5CHECKONGROUND"},
-	{{A_Boss5CheckFalling},      "A_BOSS5CHECKFALLING"},
-	{{A_Boss5PinchShot},         "A_BOSS5PINCHSHOT"},
-	{{A_Boss5MakeItRain},        "A_BOSS5MAKEITRAIN"},
-	{{A_Boss5MakeJunk},          "A_BOSS5MAKEJUNK"},
-	{{A_LookForBetter},          "A_LOOKFORBETTER"},
-	{{A_Boss5BombExplode},       "A_BOSS5BOMBEXPLODE"},
-	{{A_DustDevilThink},         "A_DUSTDEVILTHINK"},
-	{{A_TNTExplode},             "A_TNTEXPLODE"},
-	{{A_DebrisRandom},           "A_DEBRISRANDOM"},
-	{{A_TrainCameo},             "A_TRAINCAMEO"},
-	{{A_TrainCameo2},            "A_TRAINCAMEO2"},
-	{{A_CanarivoreGas},          "A_CANARIVOREGAS"},
-	{{A_KillSegments},           "A_KILLSEGMENTS"},
-	{{A_SnapperSpawn},           "A_SNAPPERSPAWN"},
-	{{A_SnapperThinker},         "A_SNAPPERTHINKER"},
-	{{A_SaloonDoorSpawn},        "A_SALOONDOORSPAWN"},
-	{{A_MinecartSparkThink},     "A_MINECARTSPARKTHINK"},
-	{{A_ModuloToState},          "A_MODULOTOSTATE"},
-	{{A_LavafallRocks},          "A_LAVAFALLROCKS"},
-	{{A_LavafallLava},           "A_LAVAFALLLAVA"},
-	{{A_FallingLavaCheck},       "A_FALLINGLAVACHECK"},
-	{{A_FireShrink},             "A_FIRESHRINK"},
-	{{A_SpawnPterabytes},        "A_SPAWNPTERABYTES"},
-	{{A_PterabyteHover},         "A_PTERABYTEHOVER"},
-	{{A_RolloutSpawn},           "A_ROLLOUTSPAWN"},
-	{{A_RolloutRock},            "A_ROLLOUTROCK"},
-	{{A_DragonbomberSpawn},      "A_DRAGONBOMERSPAWN"},
-	{{A_DragonWing},             "A_DRAGONWING"},
-	{{A_DragonSegment},          "A_DRAGONSEGMENT"},
-	{{A_ChangeHeight},           "A_CHANGEHEIGHT"},
-	{{NULL},                     "NONE"},
-
-	// This NULL entry must be the last in the list
-	{{NULL},                   NULL},
-};
-
-static void readframe(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word1;
-	char *word2 = NULL;
-	char *tmp;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			word1 = strtok(s, " ");
-			if (word1)
-				strupr(word1);
-			else
-				break;
-
-			word2 = strtok(NULL, " = ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-
-			if (fastcmp(word1, "SPRITENUMBER") || fastcmp(word1, "SPRITENAME"))
-			{
-				states[num].sprite = get_sprite(word2);
-			}
-			else if (fastcmp(word1, "SPRITESUBNUMBER") || fastcmp(word1, "SPRITEFRAME"))
-			{
-				states[num].frame = (INT32)get_number(word2); // So the FF_ flags get calculated
-			}
-			else if (fastcmp(word1, "DURATION"))
-			{
-				states[num].tics = (INT32)get_number(word2); // So TICRATE can be used
-			}
-			else if (fastcmp(word1, "NEXT"))
-			{
-				states[num].nextstate = get_state(word2);
-			}
-			else if (fastcmp(word1, "VAR1"))
-			{
-				states[num].var1 = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word1, "VAR2"))
-			{
-				states[num].var2 = (INT32)get_number(word2);
-			}
-			else if (fastcmp(word1, "ACTION"))
-			{
-				size_t z;
-				boolean found = false;
-				char actiontocompare[32];
-
-				memset(actiontocompare, 0x00, sizeof(actiontocompare));
-				strlcpy(actiontocompare, word2, sizeof (actiontocompare));
-				strupr(actiontocompare);
-
-				for (z = 0; z < 32; z++)
-				{
-					if (actiontocompare[z] == '\n' || actiontocompare[z] == '\r')
-					{
-						actiontocompare[z] = '\0';
-						break;
-					}
-				}
-
-				for (z = 0; actionpointers[z].name; z++)
-				{
-					if (actionpointers[z].action.acv == states[num].action.acv)
-						break;
-				}
-
-				z = 0;
-				found = LUA_SetLuaAction(&states[num], actiontocompare);
-				if (!found)
-					while (actionpointers[z].name)
-					{
-						if (fastcmp(actiontocompare, actionpointers[z].name))
-						{
-							states[num].action = actionpointers[z].action;
-							states[num].action.acv = actionpointers[z].action.acv; // assign
-							states[num].action.acp1 = actionpointers[z].action.acp1;
-							found = true;
-							break;
-						}
-						z++;
-					}
-
-				if (!found)
-					deh_warning("Unknown action %s", actiontocompare);
-			}
-			else
-				deh_warning("Frame %d: unknown word '%s'", num, word1);
-		}
-	} while (!myfeof(f));
-
-	Z_Free(s);
-}
-
-static void readsound(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	char *tmp;
-	INT32 value;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " ");
-			if (word2)
-				value = atoi(word2);
-			else
-			{
-				deh_warning("No value for token %s", word);
-				continue;
-			}
-
-			if (fastcmp(word, "SINGULAR"))
-			{
-				S_sfx[num].singularity = value;
-			}
-			else if (fastcmp(word, "PRIORITY"))
-			{
-				S_sfx[num].priority = value;
-			}
-			else if (fastcmp(word, "FLAGS"))
-			{
-				S_sfx[num].pitch = value;
-			}
-			else if (fastcmp(word, "CAPTION") || fastcmp(word, "DESCRIPTION"))
-			{
-				deh_strlcpy(S_sfx[num].caption, word2,
-					sizeof(S_sfx[num].caption), va("Sound effect %d: caption", num));
-			}
-			else
-				deh_warning("Sound %d : unknown word '%s'",num,word);
-		}
-	} while (!myfeof(f));
-
-	Z_Free(s);
-}
-
-/** Checks if a game data file name for a mod is good.
- * "Good" means that it contains only alphanumerics, _, and -;
- * ends in ".dat"; has at least one character before the ".dat";
- * and is not "gamedata.dat" (tested case-insensitively).
- *
- * Assumption: that gamedata.dat is the only .dat file that will
- * ever be treated specially by the game.
- *
- * Note: Check for the tail ".dat" case-insensitively since at
- * present, we get passed the filename in all uppercase.
- *
- * \param s Filename string to check.
- * \return True if the filename is good.
- * \sa readmaincfg()
- * \author Graue <graue@oceanbase.org>
- */
-static boolean GoodDataFileName(const char *s)
-{
-	const char *p;
-	const char *tail = ".dat";
-
-	for (p = s; *p != '\0'; p++)
-		if (!isalnum(*p) && *p != '_' && *p != '-' && *p != '.')
-			return false;
-
-	p = s + strlen(s) - strlen(tail);
-	if (p <= s) return false; // too short
-	if (!fasticmp(p, tail)) return false; // doesn't end in .dat
-	if (fasticmp(s, "gamedata.dat")) return false;
-
-	return true;
-}
-
-static void reademblemdata(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-	INT32 value;
-
-	memset(&emblemlocations[num-1], 0, sizeof(emblem_t));
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-			value = atoi(word2); // used for numerical settings
-
-			// Up here to allow lowercase in hints
-			if (fastcmp(word, "HINT"))
-			{
-				while ((tmp = strchr(word2, '\\')))
-					*tmp = '\n';
-				deh_strlcpy(emblemlocations[num-1].hint, word2, sizeof (emblemlocations[num-1].hint), va("Emblem %d: hint", num));
-				continue;
-			}
-			strupr(word2);
-
-			if (fastcmp(word, "TYPE"))
-			{
-				if (fastcmp(word2, "GLOBAL"))
-					emblemlocations[num-1].type = ET_GLOBAL;
-				else if (fastcmp(word2, "SKIN"))
-					emblemlocations[num-1].type = ET_SKIN;
-				else if (fastcmp(word2, "SCORE"))
-					emblemlocations[num-1].type = ET_SCORE;
-				else if (fastcmp(word2, "TIME"))
-					emblemlocations[num-1].type = ET_TIME;
-				else if (fastcmp(word2, "RINGS"))
-					emblemlocations[num-1].type = ET_RINGS;
-				else if (fastcmp(word2, "MAP"))
-					emblemlocations[num-1].type = ET_MAP;
-				else if (fastcmp(word2, "NGRADE"))
-					emblemlocations[num-1].type = ET_NGRADE;
-				else if (fastcmp(word2, "NTIME"))
-					emblemlocations[num-1].type = ET_NTIME;
-				else
-					emblemlocations[num-1].type = (UINT8)value;
-			}
-			else if (fastcmp(word, "TAG"))
-				emblemlocations[num-1].tag = (INT16)value;
-			else if (fastcmp(word, "MAPNUM"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-
-				emblemlocations[num-1].level = (INT16)value;
-			}
-			else if (fastcmp(word, "SPRITE"))
-			{
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = word2[0];
-				else
-					value += 'A'-1;
-
-				if (value < 'A' || value > 'Z')
-					deh_warning("Emblem %d: sprite must be from A - Z (1 - 26)", num);
-				else
-					emblemlocations[num-1].sprite = (UINT8)value;
-			}
-			else if (fastcmp(word, "COLOR"))
-				emblemlocations[num-1].color = get_number(word2);
-			else if (fastcmp(word, "VAR"))
-				emblemlocations[num-1].var = get_number(word2);
-			else
-				deh_warning("Emblem %d: unknown word '%s'", num, word);
-		}
-	} while (!myfeof(f));
-
-	// Default sprite and color definitions for lazy people like me
-	if (!emblemlocations[num-1].sprite) switch (emblemlocations[num-1].type)
-	{
-		case ET_RINGS:
-			emblemlocations[num-1].sprite = 'R'; break;
-		case ET_SCORE: case ET_NGRADE:
-			emblemlocations[num-1].sprite = 'S'; break;
-		case ET_TIME: case ET_NTIME:
-			emblemlocations[num-1].sprite = 'T'; break;
-		default:
-			emblemlocations[num-1].sprite = 'A'; break;
-	}
-	if (!emblemlocations[num-1].color) switch (emblemlocations[num-1].type)
-	{
-		case ET_RINGS:
-			emblemlocations[num-1].color = SKINCOLOR_GOLD; break;
-		case ET_SCORE:
-			emblemlocations[num-1].color = SKINCOLOR_BROWN; break;
-		case ET_NGRADE:
-			emblemlocations[num-1].color = SKINCOLOR_TEAL; break;
-		case ET_TIME: case ET_NTIME:
-			emblemlocations[num-1].color = SKINCOLOR_GREY; break;
-		default:
-			emblemlocations[num-1].color = SKINCOLOR_BLUE; break;
-	}
-
-	Z_Free(s);
-}
-
-static void readextraemblemdata(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-	INT32 value;
-
-	memset(&extraemblems[num-1], 0, sizeof(extraemblem_t));
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-
-			value = atoi(word2); // used for numerical settings
-
-			if (fastcmp(word, "NAME"))
-				deh_strlcpy(extraemblems[num-1].name, word2,
-					sizeof (extraemblems[num-1].name), va("Extra emblem %d: name", num));
-			else if (fastcmp(word, "OBJECTIVE"))
-				deh_strlcpy(extraemblems[num-1].description, word2,
-					sizeof (extraemblems[num-1].description), va("Extra emblem %d: objective", num));
-			else if (fastcmp(word, "CONDITIONSET"))
-				extraemblems[num-1].conditionset = (UINT8)value;
-			else if (fastcmp(word, "SHOWCONDITIONSET"))
-				extraemblems[num-1].showconditionset = (UINT8)value;
-			else
-			{
-				strupr(word2);
-				if (fastcmp(word, "SPRITE"))
-				{
-					if (word2[0] >= 'A' && word2[0] <= 'Z')
-						value = word2[0];
-					else
-						value += 'A'-1;
-
-					if (value < 'A' || value > 'Z')
-						deh_warning("Emblem %d: sprite must be from A - Z (1 - 26)", num);
-					else
-						extraemblems[num-1].sprite = (UINT8)value;
-				}
-				else if (fastcmp(word, "COLOR"))
-					extraemblems[num-1].color = get_number(word2);
-				else
-					deh_warning("Extra emblem %d: unknown word '%s'", num, word);
-			}
-		}
-	} while (!myfeof(f));
-
-	if (!extraemblems[num-1].sprite)
-		extraemblems[num-1].sprite = 'X';
-	if (!extraemblems[num-1].color)
-		extraemblems[num-1].color = SKINCOLOR_BLUE;
-
-	Z_Free(s);
-}
-
-static void readunlockable(MYFILE *f, INT32 num)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-	INT32 i;
-
-	memset(&unlockables[num], 0, sizeof(unlockable_t));
-	unlockables[num].objective[0] = '/';
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-
-			i = atoi(word2); // used for numerical settings
-
-			if (fastcmp(word, "NAME"))
-				deh_strlcpy(unlockables[num].name, word2,
-					sizeof (unlockables[num].name), va("Unlockable %d: name", num));
-			else if (fastcmp(word, "OBJECTIVE"))
-				deh_strlcpy(unlockables[num].objective, word2,
-					sizeof (unlockables[num].objective), va("Unlockable %d: objective", num));
-			else
-			{
-				strupr(word2);
-				if (fastcmp(word, "HEIGHT"))
-					unlockables[num].height = (UINT16)i;
-				else if (fastcmp(word, "CONDITIONSET"))
-					unlockables[num].conditionset = (UINT8)i;
-				else if (fastcmp(word, "SHOWCONDITIONSET"))
-					unlockables[num].showconditionset = (UINT8)i;
-				else if (fastcmp(word, "NOCECHO"))
-					unlockables[num].nocecho = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
-				else if (fastcmp(word, "NOCHECKLIST"))
-					unlockables[num].nochecklist = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
-				else if (fastcmp(word, "TYPE"))
-				{
-					if (fastcmp(word2, "NONE"))
-						unlockables[num].type = SECRET_NONE;
-					else if (fastcmp(word2, "ITEMFINDER"))
-						unlockables[num].type = SECRET_ITEMFINDER;
-					else if (fastcmp(word2, "EMBLEMHINTS"))
-						unlockables[num].type = SECRET_EMBLEMHINTS;
-					else if (fastcmp(word2, "PANDORA"))
-						unlockables[num].type = SECRET_PANDORA;
-					else if (fastcmp(word2, "CREDITS"))
-						unlockables[num].type = SECRET_CREDITS;
-					else if (fastcmp(word2, "RECORDATTACK"))
-						unlockables[num].type = SECRET_RECORDATTACK;
-					else if (fastcmp(word2, "NIGHTSMODE"))
-						unlockables[num].type = SECRET_NIGHTSMODE;
-					else if (fastcmp(word2, "HEADER"))
-						unlockables[num].type = SECRET_HEADER;
-					else if (fastcmp(word2, "LEVELSELECT"))
-						unlockables[num].type = SECRET_LEVELSELECT;
-					else if (fastcmp(word2, "WARP"))
-						unlockables[num].type = SECRET_WARP;
-					else if (fastcmp(word2, "SOUNDTEST"))
-						unlockables[num].type = SECRET_SOUNDTEST;
-					else
-						unlockables[num].type = (INT16)i;
-				}
-				else if (fastcmp(word, "VAR"))
-				{
-					// Support using the actual map name,
-					// i.e., Level AB, Level FZ, etc.
-
-					// Convert to map number
-					if (word2[0] >= 'A' && word2[0] <= 'Z')
-						i = M_MapNumber(word2[0], word2[1]);
-
-					unlockables[num].variable = (INT16)i;
-				}
-				else
-					deh_warning("Unlockable %d: unknown word '%s'", num+1, word);
-			}
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static const char NIGHTSGRADE_LIST[] = {
-	'F', // GRADE_F
-	'E', // GRADE_E
-	'D', // GRADE_D
-	'C', // GRADE_C
-	'B', // GRADE_B
-	'A', // GRADE_A
-	'S', // GRADE_S
-	'\0'
-};
-
-#define PARAMCHECK(n) do { if (!params[n]) { deh_warning("Too few parameters, need %d", n); return; }} while (0)
-static void readcondition(UINT8 set, UINT32 id, char *word2)
-{
-	INT32 i;
-	char *params[4]; // condition, requirement, extra info, extra info
-	char *spos;
-
-	conditiontype_t ty;
-	INT32 re;
-	INT16 x1 = 0, x2 = 0;
-
-	INT32 offset = 0;
-
-	spos = strtok(word2, " ");
-
-	for (i = 0; i < 4; ++i)
-	{
-		if (spos != NULL)
-		{
-			params[i] = spos;
-			spos = strtok(NULL, " ");
-		}
-		else
-			params[i] = NULL;
-	}
-
-	if (!params[0])
-	{
-		deh_warning("condition line is empty");
-		return;
-	}
-
-	if (fastcmp(params[0], "PLAYTIME"))
-	{
-		PARAMCHECK(1);
-		ty = UC_PLAYTIME;
-		re = atoi(params[1]);
-	}
-	else if        (fastcmp(params[0], "GAMECLEAR")
-	|| (++offset && fastcmp(params[0], "ALLEMERALDS"))
-	|| (++offset && fastcmp(params[0], "ULTIMATECLEAR")))
-	{
-		ty = UC_GAMECLEAR + offset;
-		re = (params[1]) ? atoi(params[1]) : 1;
-	}
-	else if ((offset=0) || fastcmp(params[0], "OVERALLSCORE")
-	||        (++offset && fastcmp(params[0], "OVERALLTIME"))
-	||        (++offset && fastcmp(params[0], "OVERALLRINGS")))
-	{
-		PARAMCHECK(1);
-		ty = UC_OVERALLSCORE + offset;
-		re = atoi(params[1]);
-	}
-	else if ((offset=0) || fastcmp(params[0], "MAPVISITED")
-	||        (++offset && fastcmp(params[0], "MAPBEATEN"))
-	||        (++offset && fastcmp(params[0], "MAPALLEMERALDS"))
-	||        (++offset && fastcmp(params[0], "MAPULTIMATE"))
-	||        (++offset && fastcmp(params[0], "MAPPERFECT")))
-	{
-		PARAMCHECK(1);
-		ty = UC_MAPVISITED + offset;
-
-		// Convert to map number if it appears to be one
-		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
-			re = M_MapNumber(params[1][0], params[1][1]);
-		else
-			re = atoi(params[1]);
-
-		if (re < 0 || re >= NUMMAPS)
-		{
-			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
-			return;
-		}
-	}
-	else if ((offset=0) || fastcmp(params[0], "MAPSCORE")
-	||        (++offset && fastcmp(params[0], "MAPTIME"))
-	||        (++offset && fastcmp(params[0], "MAPRINGS")))
-	{
-		PARAMCHECK(2);
-		ty = UC_MAPSCORE + offset;
-		re = atoi(params[2]);
-
-		// Convert to map number if it appears to be one
-		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
-			x1 = (INT16)M_MapNumber(params[1][0], params[1][1]);
-		else
-			x1 = (INT16)atoi(params[1]);
-
-		if (x1 < 0 || x1 >= NUMMAPS)
-		{
-			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
-			return;
-		}
-	}
-	else if ((offset=0) || fastcmp(params[0], "NIGHTSSCORE")
-	||        (++offset && fastcmp(params[0], "NIGHTSTIME"))
-	||        (++offset && fastcmp(params[0], "NIGHTSGRADE")))
-	{
-		PARAMCHECK(2); // one optional one
-
-		ty = UC_NIGHTSSCORE + offset;
-		i = (params[3] ? 3 : 2);
-		if (fastncmp("GRADE_",params[i],6))
-		{
-			char *p = params[i]+6;
-			for (re = 0; NIGHTSGRADE_LIST[re]; re++)
-				if (*p == NIGHTSGRADE_LIST[re])
-					break;
-			if (!NIGHTSGRADE_LIST[re])
-			{
-				deh_warning("Invalid NiGHTS grade %s\n", params[i]);
-				return;
-			}
-		}
-		else
-			re = atoi(params[i]);
-
-		// Convert to map number if it appears to be one
-		if (params[1][0] >= 'A' && params[1][0] <= 'Z')
-			x1 = (INT16)M_MapNumber(params[1][0], params[1][1]);
-		else
-			x1 = (INT16)atoi(params[1]);
-
-		if (x1 < 0 || x1 >= NUMMAPS)
-		{
-			deh_warning("Level number %d out of range (1 - %d)", re, NUMMAPS);
-			return;
-		}
-
-		// Mare number (0 for overall)
-		if (params[3]) // Only if we actually got 3 params (so the second one == mare and not requirement)
-			x2 = (INT16)atoi(params[2]);
-		else
-			x2 = 0;
-
-	}
-	else if (fastcmp(params[0], "TRIGGER"))
-	{
-		PARAMCHECK(1);
-		ty = UC_TRIGGER;
-		re = atoi(params[1]);
-
-		// constrained by 32 bits
-		if (re < 0 || re > 31)
-		{
-			deh_warning("Trigger ID %d out of range (0 - 31)", re);
-			return;
-		}
-	}
-	else if (fastcmp(params[0], "TOTALEMBLEMS"))
-	{
-		PARAMCHECK(1);
-		ty = UC_TOTALEMBLEMS;
-		re = atoi(params[1]);
-	}
-	else if (fastcmp(params[0], "EMBLEM"))
-	{
-		PARAMCHECK(1);
-		ty = UC_EMBLEM;
-		re = atoi(params[1]);
-
-		if (re <= 0 || re > MAXEMBLEMS)
-		{
-			deh_warning("Emblem %d out of range (1 - %d)", re, MAXEMBLEMS);
-			return;
-		}
-	}
-	else if (fastcmp(params[0], "EXTRAEMBLEM"))
-	{
-		PARAMCHECK(1);
-		ty = UC_EXTRAEMBLEM;
-		re = atoi(params[1]);
-
-		if (re <= 0 || re > MAXEXTRAEMBLEMS)
-		{
-			deh_warning("Extra emblem %d out of range (1 - %d)", re, MAXEXTRAEMBLEMS);
-			return;
-		}
-	}
-	else if (fastcmp(params[0], "CONDITIONSET"))
-	{
-		PARAMCHECK(1);
-		ty = UC_CONDITIONSET;
-		re = atoi(params[1]);
-
-		if (re <= 0 || re > MAXCONDITIONSETS)
-		{
-			deh_warning("Condition set %d out of range (1 - %d)", re, MAXCONDITIONSETS);
-			return;
-		}
-	}
-	else
-	{
-		deh_warning("Invalid condition name %s", params[0]);
-		return;
-	}
-
-	M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2);
-}
-#undef PARAMCHECK
-
-static void readconditionset(MYFILE *f, UINT8 setnum)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-	UINT8 id;
-	UINT8 previd = 0;
-
-	M_ClearConditionSet(setnum);
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-			strupr(word2);
-
-			if (fastncmp(word, "CONDITION", 9))
-			{
-				id = (UINT8)atoi(word + 9);
-				if (id == 0)
-				{
-					deh_warning("Condition set %d: unknown word '%s'", setnum, word);
-					continue;
-				}
-				else if (previd > id)
-				{
-					// out of order conditions can cause problems, so enforce proper order
-					deh_warning("Condition set %d: conditions are out of order, ignoring this line", setnum);
-					continue;
-				}
-				previd = id;
-
-				readcondition(setnum, id, word2);
-			}
-			else
-				deh_warning("Condition set %d: unknown word '%s'", setnum, word);
-		}
-	} while (!myfeof(f)); // finish when the line is empty
-
-	Z_Free(s);
-}
-
-static void readmaincfg(MYFILE *f)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *word2;
-	char *tmp;
-	INT32 value;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-			strupr(word2);
-
-			value = atoi(word2); // used for numerical settings
-
-			if (fastcmp(word, "EXECCFG"))
-			{
-				if (strchr(word2, '.'))
-					COM_BufAddText(va("exec %s\n", word2));
-				else
-				{
-					lumpnum_t lumpnum;
-					char newname[9];
-
-					strncpy(newname, word2, 8);
-
-					newname[8] = '\0';
-
-					lumpnum = W_CheckNumForName(newname);
-
-					if (lumpnum == LUMPERROR || W_LumpLength(lumpnum) == 0)
-						CONS_Debug(DBG_SETUP, "SOC Error: script lump %s not found/not valid.\n", newname);
-					else
-						COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE));
-				}
-			}
-
-			else if (fastcmp(word, "SPSTAGE_START"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-				else
-					value = get_number(word2);
-
-				spstage_start = spmarathon_start = (INT16)value;
-			}
-			else if (fastcmp(word, "SPMARATHON_START"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-				else
-					value = get_number(word2);
-
-				spmarathon_start = (INT16)value;
-			}
-			else if (fastcmp(word, "SSTAGE_START"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-				else
-					value = get_number(word2);
-
-				sstage_start = (INT16)value;
-				sstage_end = (INT16)(sstage_start+7); // 7 special stages total plus one weirdo
-			}
-			else if (fastcmp(word, "SMPSTAGE_START"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-				else
-					value = get_number(word2);
-
-				smpstage_start = (INT16)value;
-				smpstage_end = (INT16)(smpstage_start+6); // 7 special stages total
-			}
-			else if (fastcmp(word, "REDTEAM"))
-			{
-				skincolor_redteam = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "BLUETEAM"))
-			{
-				skincolor_blueteam = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "REDRING"))
-			{
-				skincolor_redring = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "BLUERING"))
-			{
-				skincolor_bluering = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "INVULNTICS"))
-			{
-				invulntics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "SNEAKERTICS"))
-			{
-				sneakertics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "FLASHINGTICS"))
-			{
-				flashingtics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "TAILSFLYTICS"))
-			{
-				tailsflytics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "UNDERWATERTICS"))
-			{
-				underwatertics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "SPACETIMETICS"))
-			{
-				spacetimetics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "EXTRALIFETICS"))
-			{
-				extralifetics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "NIGHTSLINKTICS"))
-			{
-				nightslinktics = (UINT16)get_number(word2);
-			}
-			else if (fastcmp(word, "GAMEOVERTICS"))
-			{
-				gameovertics = get_number(word2);
-			}
-			else if (fastcmp(word, "AMMOREMOVALTICS"))
-			{
-				ammoremovaltics = get_number(word2);
-			}
-			else if (fastcmp(word, "INTROTOPLAY"))
-			{
-				introtoplay = (UINT8)get_number(word2);
-				// range check, you morons.
-				if (introtoplay > 128)
-					introtoplay = 128;
-				introchanged = true;
-			}
-			else if (fastcmp(word, "CREDITSCUTSCENE"))
-			{
-				creditscutscene = (UINT8)get_number(word2);
-				// range check, you morons.
-				if (creditscutscene > 128)
-					creditscutscene = 128;
-			}
-			else if (fastcmp(word, "USEBLACKROCK"))
-			{
-				useBlackRock = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
-			}
-			else if (fastcmp(word, "LOOPTITLE"))
-			{
-				looptitle = (value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEMAP"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-				else
-					value = get_number(word2);
-
-				titlemap = (INT16)value;
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE"))
-			{
-				hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSMODE"))
-			{
-				if (fastcmp(word2, "USER"))
-					ttmode = TTMODE_USER;
-				else if (fastcmp(word2, "ALACROIX"))
-					ttmode = TTMODE_ALACROIX;
-				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
-				{
-					ttmode = TTMODE_USER;
-					ttname[0] = 0;
-					hidetitlepics = true;
-				}
-				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
-					ttmode = TTMODE_OLD;
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSSCALE"))
-			{
-				ttscale = max(1, min(8, (UINT8)get_number(word2)));
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSSCALESAVAILABLE"))
-			{
-				// SPECIAL CASE for Alacroix: Comma-separated list of resolutions that are available
-				// for gfx loading.
-				ttavailable[0] = ttavailable[1] = ttavailable[2] = ttavailable[3] =\
-					ttavailable[4] = ttavailable[5] = false;
-
-				if (strstr(word2, "1") != NULL)
-					ttavailable[0] = true;
-				if (strstr(word2, "2") != NULL)
-					ttavailable[1] = true;
-				if (strstr(word2, "3") != NULL)
-					ttavailable[2] = true;
-				if (strstr(word2, "4") != NULL)
-					ttavailable[3] = true;
-				if (strstr(word2, "5") != NULL)
-					ttavailable[4] = true;
-				if (strstr(word2, "6") != NULL)
-					ttavailable[5] = true;
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSNAME"))
-			{
-				strncpy(ttname, word2, 9);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSX"))
-			{
-				ttx = (INT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSY"))
-			{
-				tty = (INT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSLOOP"))
-			{
-				ttloop = (INT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLEPICSTICS"))
-			{
-				tttics = (UINT16)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED"))
-			{
-				titlescrollxspeed = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "TITLESCROLLYSPEED"))
-			{
-				titlescrollyspeed = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "DISABLESPEEDADJUST"))
-			{
-				disableSpeedAdjust = (value || word2[0] == 'T' || word2[0] == 'Y');
-			}
-			else if (fastcmp(word, "NUMDEMOS"))
-			{
-				numDemos = (UINT8)get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "DEMODELAYTIME"))
-			{
-				demoDelayTime = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "DEMOIDLETIME"))
-			{
-				demoIdleTime = get_number(word2);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "USE1UPSOUND"))
-			{
-				use1upSound = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
-			}
-			else if (fastcmp(word, "MAXXTRALIFE"))
-			{
-				maxXtraLife = (UINT8)get_number(word2);
-			}
-			else if (fastcmp(word, "USECONTINUES"))
-			{
-				useContinues = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
-			}
-
-			else if (fastcmp(word, "GAMEDATA"))
-			{
-				size_t filenamelen;
-
-				// Check the data filename so that mods
-				// can't write arbitrary files.
-				if (!GoodDataFileName(word2))
-					I_Error("Maincfg: bad data file name '%s'\n", word2);
-
-				G_SaveGameData();
-				strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
-				strlwr(gamedatafilename);
-				savemoddata = true;
-
-				// Also save a time attack folder
-				filenamelen = strlen(gamedatafilename)-4;  // Strip off the extension
-				strncpy(timeattackfolder, gamedatafilename, min(filenamelen, sizeof (timeattackfolder)));
-				timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0';
-
-				strcpy(savegamename, timeattackfolder);
-				strlcat(savegamename, "%u.ssg", sizeof(savegamename));
-				// can't use sprintf since there is %u in savegamename
-				strcatbf(savegamename, srb2home, PATHSEP);
-
-				strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder));
-				strcatbf(liveeventbackup, srb2home, PATHSEP);
-
-				gamedataadded = true;
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "RESETDATA"))
-			{
-				P_ResetData(value);
-				titlechanged = true;
-			}
-			else if (fastcmp(word, "CUSTOMVERSION"))
-			{
-				strlcpy(customversionstring, word2, sizeof (customversionstring));
-				//titlechanged = true;
-			}
-			else if (fastcmp(word, "BOOTMAP"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-				else
-					value = get_number(word2);
-
-				bootmap = (INT16)value;
-				//titlechanged = true;
-			}
-			else if (fastcmp(word, "STARTCHAR"))
-			{
-				startchar = (INT16)value;
-				char_on = -1;
-			}
-			else if (fastcmp(word, "TUTORIALMAP"))
-			{
-				// Support using the actual map name,
-				// i.e., Level AB, Level FZ, etc.
-
-				// Convert to map number
-				if (word2[0] >= 'A' && word2[0] <= 'Z')
-					value = M_MapNumber(word2[0], word2[1]);
-				else
-					value = get_number(word2);
-
-				tutorialmap = (INT16)value;
-			}
-			else
-				deh_warning("Maincfg: unknown word '%s'", word);
-		}
-	} while (!myfeof(f));
-
-	Z_Free(s);
-}
-
-static void readwipes(MYFILE *f)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word = s;
-	char *pword = word;
-	char *word2;
-	char *tmp;
-	INT32 value;
-	INT32 wipeoffset;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			// First remove trailing newline, if there is one
-			tmp = strchr(s, '\n');
-			if (tmp)
-				*tmp = '\0';
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			if (s == tmp)
-				continue; // Skip comment lines, but don't break.
-
-			// Get the part before the " = "
-			tmp = strchr(s, '=');
-			if (tmp)
-				*(tmp-1) = '\0';
-			else
-				break;
-			strupr(word);
-
-			// Now get the part after
-			word2 = tmp += 2;
-			value = atoi(word2); // used for numerical settings
-
-			if (value < -1 || value > 99)
-			{
-				deh_warning("Wipes: bad value '%s'", word2);
-				continue;
-			}
-			else if (value == -1)
-				value = UINT8_MAX;
-
-			// error catching
-			wipeoffset = -1;
-
-			if (fastncmp(word, "LEVEL_", 6))
-			{
-				pword = word + 6;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_level_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_level_final;
-			}
-			else if (fastncmp(word, "INTERMISSION_", 13))
-			{
-				pword = word + 13;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_intermission_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_intermission_final;
-			}
-			else if (fastncmp(word, "SPECINTER_", 10))
-			{
-				pword = word + 10;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_specinter_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_specinter_final;
-			}
-			else if (fastncmp(word, "MULTINTER_", 10))
-			{
-				pword = word + 10;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_multinter_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_multinter_final;
-			}
-			else if (fastncmp(word, "CONTINUING_", 11))
-			{
-				pword = word + 11;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_continuing_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_continuing_final;
-			}
-			else if (fastncmp(word, "TITLESCREEN_", 12))
-			{
-				pword = word + 12;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_titlescreen_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_titlescreen_final;
-			}
-			else if (fastncmp(word, "TIMEATTACK_", 11))
-			{
-				pword = word + 11;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_timeattack_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_timeattack_final;
-			}
-			else if (fastncmp(word, "CREDITS_", 8))
-			{
-				pword = word + 8;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_credits_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_credits_final;
-				else if (fastcmp(pword, "INTERMEDIATE"))
-					wipeoffset = wipe_credits_intermediate;
-			}
-			else if (fastncmp(word, "EVALUATION_", 11))
-			{
-				pword = word + 11;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_evaluation_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_evaluation_final;
-			}
-			else if (fastncmp(word, "GAMEEND_", 8))
-			{
-				pword = word + 8;
-				if (fastcmp(pword, "TOBLACK"))
-					wipeoffset = wipe_gameend_toblack;
-				else if (fastcmp(pword, "FINAL"))
-					wipeoffset = wipe_gameend_final;
-			}
-			else if (fastncmp(word, "SPECLEVEL_", 10))
-			{
-				pword = word + 10;
-				if (fastcmp(pword, "TOWHITE"))
-					wipeoffset = wipe_speclevel_towhite;
-			}
-
-			if (wipeoffset < 0)
-			{
-				deh_warning("Wipes: unknown word '%s'", word);
-				continue;
-			}
-
-			if (value == UINT8_MAX
-			 && (wipeoffset <= wipe_level_toblack || wipeoffset >= wipe_speclevel_towhite))
-			{
-				 // Cannot disable non-toblack wipes
-				 // (or the level toblack wipe, or the special towhite wipe)
-				deh_warning("Wipes: can't disable wipe of type '%s'", word);
-				continue;
-			}
-
-			wipedefs[wipeoffset] = (UINT8)value;
-		}
-	} while (!myfeof(f));
-
-	Z_Free(s);
-}
-
-// Used when you do something invalid like read a bad item number
-// to prevent extra unnecessary errors
-static void ignorelines(MYFILE *f)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-		}
-	} while (!myfeof(f));
-	Z_Free(s);
-}
-
-static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char textline[MAXLINELEN];
-	char *word;
-	char *word2;
-	INT32 i;
-
-	if (!deh_loaded)
-		initfreeslots();
-
-	deh_num_warning = 0;
-
-	gamedataadded = titlechanged = introchanged = false;
-
-	// it doesn't test the version of SRB2 and version of dehacked file
-	dbg_line = -1; // start at -1 so the first line is 0.
-	while (!myfeof(f))
-	{
-		char origpos[128];
-		INT32 size = 0;
-		char *traverse;
-
-		myfgets(s, MAXLINELEN, f);
-		memcpy(textline, s, MAXLINELEN);
-		if (s[0] == '\n' || s[0] == '#')
-			continue;
-
-		traverse = s;
-
-		while (traverse[0] != '\n')
-		{
-			traverse++;
-			size++;
-		}
-
-		strncpy(origpos, s, size);
-		origpos[size] = '\0';
-
-		if (NULL != (word = strtok(s, " "))) {
-			strupr(word);
-			if (word[strlen(word)-1] == '\n')
-				word[strlen(word)-1] = '\0';
-		}
-		if (word)
-		{
-			if (fastcmp(word, "FREESLOT"))
-			{
-				readfreeslots(f);
-				continue;
-			}
-			else if (fastcmp(word, "MAINCFG"))
-			{
-				readmaincfg(f);
-				continue;
-			}
-			else if (fastcmp(word, "WIPES"))
-			{
-				readwipes(f);
-				continue;
-			}
-			word2 = strtok(NULL, " ");
-			if (word2) {
-				strupr(word2);
-				if (word2[strlen(word2) - 1] == '\n')
-					word2[strlen(word2) - 1] = '\0';
-				i = atoi(word2);
-			}
-			else
-				i = 0;
-			if (fastcmp(word, "CHARACTER"))
-			{
-				if (i >= 0 && i < 32)
-					readPlayer(f, i);
-				else
-				{
-					deh_warning("Character %d out of range (0 - 31)", i);
-					ignorelines(f);
-				}
-				continue;
-			}
-			else if (fastcmp(word, "EMBLEM"))
-			{
-				if (!mainfile && !gamedataadded)
-				{
-					deh_warning("You must define a custom gamedata to use \"%s\"", word);
-					ignorelines(f);
-				}
-				else
-				{
-					if (!word2)
-						i = numemblems + 1;
-
-					if (i > 0 && i <= MAXEMBLEMS)
-					{
-						if (numemblems < i)
-							numemblems = i;
-						reademblemdata(f, i);
-					}
-					else
-					{
-						deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS);
-						ignorelines(f);
-					}
-				}
-				continue;
-			}
-			else if (fastcmp(word, "EXTRAEMBLEM"))
-			{
-				if (!mainfile && !gamedataadded)
-				{
-					deh_warning("You must define a custom gamedata to use \"%s\"", word);
-					ignorelines(f);
-				}
-				else
-				{
-					if (!word2)
-						i = numextraemblems + 1;
-
-					if (i > 0 && i <= MAXEXTRAEMBLEMS)
-					{
-						if (numextraemblems < i)
-							numextraemblems = i;
-						readextraemblemdata(f, i);
-					}
-					else
-					{
-						deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS);
-						ignorelines(f);
-					}
-				}
-				continue;
-			}
-			if (word2)
-			{
-				if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_mobjtype(word2); // find a thing by name
-					if (i < NUMMOBJTYPES && i > 0)
-						readthing(f, i);
-					else
-					{
-						deh_warning("Thing %d out of range (1 - %d)", i, NUMMOBJTYPES-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "SKINCOLOR") || fastcmp(word, "COLOR"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_skincolor(word2); // find a skincolor by name
-					if (i < numskincolors && i >= (INT32)SKINCOLOR_FIRSTFREESLOT)
-						readskincolor(f, i);
-					else
-					{
-						deh_warning("Skincolor %d out of range (%d - %d)", i, SKINCOLOR_FIRSTFREESLOT, numskincolors-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "SPRITE2"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_sprite2(word2); // find a sprite by name
-					if (i < (INT32)free_spr2 && i >= (INT32)SPR2_FIRSTFREESLOT)
-						readsprite2(f, i);
-					else
-					{
-						deh_warning("Sprite2 number %d out of range (%d - %d)", i, SPR2_FIRSTFREESLOT, free_spr2-1);
-						ignorelines(f);
-					}
-				}
-#ifdef HWRENDER
-				else if (fastcmp(word, "LIGHT"))
-				{
-					// TODO: Read lights by name
-					if (i > 0 && i < NUMLIGHTS)
-						readlight(f, i);
-					else
-					{
-						deh_warning("Light number %d out of range (1 - %d)", i, NUMLIGHTS-1);
-						ignorelines(f);
-					}
-				}
-#endif
-				else if (fastcmp(word, "SPRITE") || fastcmp(word, "SPRITEINFO"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_sprite(word2); // find a sprite by name
-					if (i < NUMSPRITES && i > 0)
-						readspriteinfo(f, i, false);
-					else
-					{
-						deh_warning("Sprite number %d out of range (0 - %d)", i, NUMSPRITES-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "SPRITE2INFO"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_sprite2(word2); // find a sprite by name
-					if (i < NUMPLAYERSPRITES && i >= 0)
-						readspriteinfo(f, i, true);
-					else
-					{
-						deh_warning("Sprite2 number %d out of range (0 - %d)", i, NUMPLAYERSPRITES-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "LEVEL"))
-				{
-					// Support using the actual map name,
-					// i.e., Level AB, Level FZ, etc.
-
-					// Convert to map number
-					if (word2[0] >= 'A' && word2[0] <= 'Z')
-						i = M_MapNumber(word2[0], word2[1]);
-
-					if (i > 0 && i <= NUMMAPS)
-						readlevelheader(f, i);
-					else
-					{
-						deh_warning("Level number %d out of range (1 - %d)", i, NUMMAPS);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "GAMETYPE"))
-				{
-					// Get the gametype name from textline
-					// instead of word2, so that gametype names
-					// aren't allcaps
-					INT32 c;
-					for (c = 0; c < MAXLINELEN; c++)
-					{
-						if (textline[c] == '\0')
-							break;
-						if (textline[c] == ' ')
-						{
-							char *gtname = (textline+c+1);
-							if (gtname)
-							{
-								// remove funny characters
-								INT32 j;
-								for (j = 0; j < (MAXLINELEN - c); j++)
-								{
-									if (gtname[j] == '\0')
-										break;
-									if (gtname[j] < 32)
-										gtname[j] = '\0';
-								}
-								readgametype(f, gtname);
-							}
-							break;
-						}
-					}
-				}
-				else if (fastcmp(word, "CUTSCENE"))
-				{
-					if (i > 0 && i < 129)
-						readcutscene(f, i - 1);
-					else
-					{
-						deh_warning("Cutscene number %d out of range (1 - 128)", i);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "PROMPT"))
-				{
-					if (i > 0 && i < MAX_PROMPTS)
-						readtextprompt(f, i - 1);
-					else
-					{
-						deh_warning("Prompt number %d out of range (1 - %d)", i, MAX_PROMPTS);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_state(word2); // find a state by name
-					if (i < NUMSTATES && i >= 0)
-						readframe(f, i);
-					else
-					{
-						deh_warning("Frame %d out of range (0 - %d)", i, NUMSTATES-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "SOUND"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_sfx(word2); // find a sound by name
-					if (i < NUMSFX && i > 0)
-						readsound(f, i);
-					else
-					{
-						deh_warning("Sound %d out of range (1 - %d)", i, NUMSFX-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "HUDITEM"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_huditem(word2); // find a huditem by name
-					if (i >= 0 && i < NUMHUDITEMS)
-						readhuditem(f, i);
-					else
-					{
-						deh_warning("HUD item number %d out of range (0 - %d)", i, NUMHUDITEMS-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "MENU"))
-				{
-					if (i == 0 && word2[0] != '0') // If word2 isn't a number
-						i = get_menutype(word2); // find a huditem by name
-					if (i >= 1 && i < NUMMENUTYPES)
-						readmenu(f, i);
-					else
-					{
-						// zero-based, but let's start at 1
-						deh_warning("Menu number %d out of range (1 - %d)", i, NUMMENUTYPES-1);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "UNLOCKABLE"))
-				{
-					if (!mainfile && !gamedataadded)
-					{
-						deh_warning("You must define a custom gamedata to use \"%s\"", word);
-						ignorelines(f);
-					}
-					else if (i > 0 && i <= MAXUNLOCKABLES)
-						readunlockable(f, i - 1);
-					else
-					{
-						deh_warning("Unlockable number %d out of range (1 - %d)", i, MAXUNLOCKABLES);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "CONDITIONSET"))
-				{
-					if (!mainfile && !gamedataadded)
-					{
-						deh_warning("You must define a custom gamedata to use \"%s\"", word);
-						ignorelines(f);
-					}
-					else if (i > 0 && i <= MAXCONDITIONSETS)
-						readconditionset(f, (UINT8)i);
-					else
-					{
-						deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS);
-						ignorelines(f);
-					}
-				}
-				else if (fastcmp(word, "SRB2"))
-				{
-					if (isdigit(word2[0]))
-					{
-						i = atoi(word2);
-						if (i != PATCHVERSION)
-						{
-							deh_warning(
-									"Patch is for SRB2 version %d, "
-									"only version %d is supported",
-									i,
-									PATCHVERSION
-							);
-						}
-					}
-					else
-					{
-						deh_warning(
-								"SRB2 version definition has incorrect format, "
-								"use \"SRB2 %d\"",
-								PATCHVERSION
-						);
-					}
-				}
-				// Clear all data in certain locations (mostly for unlocks)
-				// Unless you REALLY want to piss people off,
-				// define a custom gamedata /before/ doing this!!
-				// (then again, modifiedgame will prevent game data saving anyway)
-				else if (fastcmp(word, "CLEAR"))
-				{
-					boolean clearall = (fastcmp(word2, "ALL"));
-
-					if (!mainfile && !gamedataadded)
-					{
-						deh_warning("You must define a custom gamedata to use \"%s\"", word);
-						continue;
-					}
-
-					if (clearall || fastcmp(word2, "UNLOCKABLES"))
-						memset(&unlockables, 0, sizeof(unlockables));
-
-					if (clearall || fastcmp(word2, "EMBLEMS"))
-					{
-						memset(&emblemlocations, 0, sizeof(emblemlocations));
-						numemblems = 0;
-					}
-
-					if (clearall || fastcmp(word2, "EXTRAEMBLEMS"))
-					{
-						memset(&extraemblems, 0, sizeof(extraemblems));
-						numextraemblems = 0;
-					}
-
-					if (clearall || fastcmp(word2, "CONDITIONSETS"))
-						clear_conditionsets();
-
-					if (clearall || fastcmp(word2, "LEVELS"))
-						clear_levels();
-				}
-				else
-					deh_warning("Unknown word: %s", word);
-			}
-			else
-				deh_warning("missing argument for '%s'", word);
-		}
-		else
-			deh_warning("No word in this line: %s", s);
-	} // end while
-
-	if (gamedataadded)
-		G_LoadGameData();
-
-	if (gamestate == GS_TITLESCREEN)
-	{
-		if (introchanged)
-		{
-			menuactive = false;
-			I_UpdateMouseGrab();
-			COM_BufAddText("playintro");
-		}
-		else if (titlechanged)
-		{
-			menuactive = false;
-			I_UpdateMouseGrab();
-			COM_BufAddText("exitgame"); // Command_ExitGame_f() but delayed
-		}
-	}
-
-	dbg_line = -1;
-	if (deh_num_warning)
-	{
-		CONS_Printf(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s");
-		if (devparm) {
-			I_Error("%s%s",va(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"), M_GetText("See log.txt for details.\n"));
-			//while (!I_GetKey())
-				//I_OsPolling();
-		}
-	}
-
-	deh_loaded = true;
-	Z_Free(s);
-}
-
-// read dehacked lump in a wad (there is special trick for for deh
-// file that are converted to wad in w_wad.c)
-void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump, boolean mainfile)
-{
-	MYFILE f;
-	f.wad = wad;
-	f.size = W_LumpLengthPwad(wad, lump);
-	f.data = Z_Malloc(f.size + 1, PU_STATIC, NULL);
-	W_ReadLumpPwad(wad, lump, f.data);
-	f.curpos = f.data;
-	f.data[f.size] = 0;
-	DEH_LoadDehackedFile(&f, mainfile);
-	Z_Free(f.data);
-}
-
-void DEH_LoadDehackedLump(lumpnum_t lumpnum)
-{
-	DEH_LoadDehackedLumpPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum), false);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// CRAZY LIST OF STATE NAMES AND ALL FROM HERE DOWN
-// TODO: Make this all a seperate file or something, like part of info.c??
-// TODO: Read the list from a text lump in a WAD as necessary instead
-// or something, don't just keep it all in memory like this.
-// TODO: Make the lists public so we can start using actual mobj
-// and state names in warning and error messages! :D
-
-// RegEx to generate this from info.h: ^\tS_([^,]+), --> \t"S_\1",
-// I am leaving the prefixes solely for clarity to programmers,
-// because sadly no one remembers this place while searching for full state names.
-static const char *const STATE_LIST[] = { // array length left dynamic for sanity testing later.
-	"S_NULL",
-	"S_UNKNOWN",
-	"S_INVISIBLE", // state for invisible sprite
-
-	"S_SPAWNSTATE",
-	"S_SEESTATE",
-	"S_MELEESTATE",
-	"S_MISSILESTATE",
-	"S_DEATHSTATE",
-	"S_XDEATHSTATE",
-	"S_RAISESTATE",
-
-	// Thok
-	"S_THOK",
-
-	// Player
-	"S_PLAY_STND",
-	"S_PLAY_WAIT",
-	"S_PLAY_WALK",
-	"S_PLAY_SKID",
-	"S_PLAY_RUN",
-	"S_PLAY_DASH",
-	"S_PLAY_PAIN",
-	"S_PLAY_STUN",
-	"S_PLAY_DEAD",
-	"S_PLAY_DRWN",
-	"S_PLAY_ROLL",
-	"S_PLAY_GASP",
-	"S_PLAY_JUMP",
-	"S_PLAY_SPRING",
-	"S_PLAY_FALL",
-	"S_PLAY_EDGE",
-	"S_PLAY_RIDE",
-
-	// CA2_SPINDASH
-	"S_PLAY_SPINDASH",
-
-	// CA_FLY/SWIM
-	"S_PLAY_FLY",
-	"S_PLAY_SWIM",
-	"S_PLAY_FLY_TIRED",
-
-	// CA_GLIDEANDCLIMB
-	"S_PLAY_GLIDE",
-	"S_PLAY_GLIDE_LANDING",
-	"S_PLAY_CLING",
-	"S_PLAY_CLIMB",
-
-	// CA_FLOAT/CA_SLOWFALL
-	"S_PLAY_FLOAT",
-	"S_PLAY_FLOAT_RUN",
-
-	// CA_BOUNCE
-	"S_PLAY_BOUNCE",
-	"S_PLAY_BOUNCE_LANDING",
-
-	// CA2_GUNSLINGER
-	"S_PLAY_FIRE",
-	"S_PLAY_FIRE_FINISH",
-
-	// CA_TWINSPIN
-	"S_PLAY_TWINSPIN",
-
-	// CA2_MELEE
-	"S_PLAY_MELEE",
-	"S_PLAY_MELEE_FINISH",
-	"S_PLAY_MELEE_LANDING",
-
-	// SF_SUPER
-	"S_PLAY_SUPER_TRANS1",
-	"S_PLAY_SUPER_TRANS2",
-	"S_PLAY_SUPER_TRANS3",
-	"S_PLAY_SUPER_TRANS4",
-	"S_PLAY_SUPER_TRANS5",
-	"S_PLAY_SUPER_TRANS6",
-
-	// technically the player goes here but it's an infinite tic state
-	"S_OBJPLACE_DUMMY",
-
-	// 1-Up Box Sprites overlay (uses player sprite)
-	"S_PLAY_BOX1",
-	"S_PLAY_BOX2",
-	"S_PLAY_ICON1",
-	"S_PLAY_ICON2",
-	"S_PLAY_ICON3",
-
-	// Level end sign overlay (uses player sprite)
-	"S_PLAY_SIGN",
-
-	// NiGHTS character (uses player sprite)
-	"S_PLAY_NIGHTS_TRANS1",
-	"S_PLAY_NIGHTS_TRANS2",
-	"S_PLAY_NIGHTS_TRANS3",
-	"S_PLAY_NIGHTS_TRANS4",
-	"S_PLAY_NIGHTS_TRANS5",
-	"S_PLAY_NIGHTS_TRANS6",
-	"S_PLAY_NIGHTS_STAND",
-	"S_PLAY_NIGHTS_FLOAT",
-	"S_PLAY_NIGHTS_FLY",
-	"S_PLAY_NIGHTS_DRILL",
-	"S_PLAY_NIGHTS_STUN",
-	"S_PLAY_NIGHTS_PULL",
-	"S_PLAY_NIGHTS_ATTACK",
-
-	// c:
-	"S_TAILSOVERLAY_STAND",
-	"S_TAILSOVERLAY_0DEGREES",
-	"S_TAILSOVERLAY_PLUS30DEGREES",
-	"S_TAILSOVERLAY_PLUS60DEGREES",
-	"S_TAILSOVERLAY_MINUS30DEGREES",
-	"S_TAILSOVERLAY_MINUS60DEGREES",
-	"S_TAILSOVERLAY_RUN",
-	"S_TAILSOVERLAY_FLY",
-	"S_TAILSOVERLAY_TIRE",
-	"S_TAILSOVERLAY_PAIN",
-	"S_TAILSOVERLAY_GASP",
-	"S_TAILSOVERLAY_EDGE",
-
-	// [:
-	"S_JETFUMEFLASH",
-
-	// Blue Crawla
-	"S_POSS_STND",
-	"S_POSS_RUN1",
-	"S_POSS_RUN2",
-	"S_POSS_RUN3",
-	"S_POSS_RUN4",
-	"S_POSS_RUN5",
-	"S_POSS_RUN6",
-
-	// Red Crawla
-	"S_SPOS_STND",
-	"S_SPOS_RUN1",
-	"S_SPOS_RUN2",
-	"S_SPOS_RUN3",
-	"S_SPOS_RUN4",
-	"S_SPOS_RUN5",
-	"S_SPOS_RUN6",
-
-	// Greenflower Fish
-	"S_FISH1",
-	"S_FISH2",
-	"S_FISH3",
-	"S_FISH4",
-
-	// Buzz (Gold)
-	"S_BUZZLOOK1",
-	"S_BUZZLOOK2",
-	"S_BUZZFLY1",
-	"S_BUZZFLY2",
-
-	// Buzz (Red)
-	"S_RBUZZLOOK1",
-	"S_RBUZZLOOK2",
-	"S_RBUZZFLY1",
-	"S_RBUZZFLY2",
-
-	// Jetty-Syn Bomber
-	"S_JETBLOOK1",
-	"S_JETBLOOK2",
-	"S_JETBZOOM1",
-	"S_JETBZOOM2",
-
-	// Jetty-Syn Gunner
-	"S_JETGLOOK1",
-	"S_JETGLOOK2",
-	"S_JETGZOOM1",
-	"S_JETGZOOM2",
-	"S_JETGSHOOT1",
-	"S_JETGSHOOT2",
-
-	// Crawla Commander
-	"S_CCOMMAND1",
-	"S_CCOMMAND2",
-	"S_CCOMMAND3",
-	"S_CCOMMAND4",
-
-	// Deton
-	"S_DETON1",
-	"S_DETON2",
-	"S_DETON3",
-	"S_DETON4",
-	"S_DETON5",
-	"S_DETON6",
-	"S_DETON7",
-	"S_DETON8",
-	"S_DETON9",
-	"S_DETON10",
-	"S_DETON11",
-	"S_DETON12",
-	"S_DETON13",
-	"S_DETON14",
-	"S_DETON15",
-
-	// Skim Mine Dropper
-	"S_SKIM1",
-	"S_SKIM2",
-	"S_SKIM3",
-	"S_SKIM4",
-
-	// THZ Turret
-	"S_TURRET",
-	"S_TURRETFIRE",
-	"S_TURRETSHOCK1",
-	"S_TURRETSHOCK2",
-	"S_TURRETSHOCK3",
-	"S_TURRETSHOCK4",
-	"S_TURRETSHOCK5",
-	"S_TURRETSHOCK6",
-	"S_TURRETSHOCK7",
-	"S_TURRETSHOCK8",
-	"S_TURRETSHOCK9",
-
-	// Popup Turret
-	"S_TURRETLOOK",
-	"S_TURRETSEE",
-	"S_TURRETPOPUP1",
-	"S_TURRETPOPUP2",
-	"S_TURRETPOPUP3",
-	"S_TURRETPOPUP4",
-	"S_TURRETPOPUP5",
-	"S_TURRETPOPUP6",
-	"S_TURRETPOPUP7",
-	"S_TURRETPOPUP8",
-	"S_TURRETSHOOT",
-	"S_TURRETPOPDOWN1",
-	"S_TURRETPOPDOWN2",
-	"S_TURRETPOPDOWN3",
-	"S_TURRETPOPDOWN4",
-	"S_TURRETPOPDOWN5",
-	"S_TURRETPOPDOWN6",
-	"S_TURRETPOPDOWN7",
-	"S_TURRETPOPDOWN8",
-
-	// Spincushion
-	"S_SPINCUSHION_LOOK",
-	"S_SPINCUSHION_CHASE1",
-	"S_SPINCUSHION_CHASE2",
-	"S_SPINCUSHION_CHASE3",
-	"S_SPINCUSHION_CHASE4",
-	"S_SPINCUSHION_AIM1",
-	"S_SPINCUSHION_AIM2",
-	"S_SPINCUSHION_AIM3",
-	"S_SPINCUSHION_AIM4",
-	"S_SPINCUSHION_AIM5",
-	"S_SPINCUSHION_SPIN1",
-	"S_SPINCUSHION_SPIN2",
-	"S_SPINCUSHION_SPIN3",
-	"S_SPINCUSHION_SPIN4",
-	"S_SPINCUSHION_STOP1",
-	"S_SPINCUSHION_STOP2",
-	"S_SPINCUSHION_STOP3",
-	"S_SPINCUSHION_STOP4",
-
-	// Crushstacean
-	"S_CRUSHSTACEAN_ROAM1",
-	"S_CRUSHSTACEAN_ROAM2",
-	"S_CRUSHSTACEAN_ROAM3",
-	"S_CRUSHSTACEAN_ROAM4",
-	"S_CRUSHSTACEAN_ROAMPAUSE",
-	"S_CRUSHSTACEAN_PUNCH1",
-	"S_CRUSHSTACEAN_PUNCH2",
-	"S_CRUSHCLAW_AIM",
-	"S_CRUSHCLAW_OUT",
-	"S_CRUSHCLAW_STAY",
-	"S_CRUSHCLAW_IN",
-	"S_CRUSHCLAW_WAIT",
-	"S_CRUSHCHAIN",
-
-	// Banpyura
-	"S_BANPYURA_ROAM1",
-	"S_BANPYURA_ROAM2",
-	"S_BANPYURA_ROAM3",
-	"S_BANPYURA_ROAM4",
-	"S_BANPYURA_ROAMPAUSE",
-	"S_CDIAG1",
-	"S_CDIAG2",
-	"S_CDIAG3",
-	"S_CDIAG4",
-	"S_CDIAG5",
-	"S_CDIAG6",
-	"S_CDIAG7",
-	"S_CDIAG8",
-
-	// Jet Jaw
-	"S_JETJAW_ROAM1",
-	"S_JETJAW_ROAM2",
-	"S_JETJAW_ROAM3",
-	"S_JETJAW_ROAM4",
-	"S_JETJAW_ROAM5",
-	"S_JETJAW_ROAM6",
-	"S_JETJAW_ROAM7",
-	"S_JETJAW_ROAM8",
-	"S_JETJAW_CHOMP1",
-	"S_JETJAW_CHOMP2",
-	"S_JETJAW_CHOMP3",
-	"S_JETJAW_CHOMP4",
-	"S_JETJAW_CHOMP5",
-	"S_JETJAW_CHOMP6",
-	"S_JETJAW_CHOMP7",
-	"S_JETJAW_CHOMP8",
-	"S_JETJAW_CHOMP9",
-	"S_JETJAW_CHOMP10",
-	"S_JETJAW_CHOMP11",
-	"S_JETJAW_CHOMP12",
-	"S_JETJAW_CHOMP13",
-	"S_JETJAW_CHOMP14",
-	"S_JETJAW_CHOMP15",
-	"S_JETJAW_CHOMP16",
-	"S_JETJAW_SOUND",
-
-	// Snailer
-	"S_SNAILER1",
-	"S_SNAILER_FLICKY",
-
-	// Vulture
-	"S_VULTURE_STND",
-	"S_VULTURE_DRIFT",
-	"S_VULTURE_ZOOM1",
-	"S_VULTURE_ZOOM2",
-	"S_VULTURE_STUNNED",
-
-	// Pointy
-	"S_POINTY1",
-	"S_POINTYBALL1",
-
-	// Robo-Hood
-	"S_ROBOHOOD_LOOK",
-	"S_ROBOHOOD_STAND",
-	"S_ROBOHOOD_FIRE1",
-	"S_ROBOHOOD_FIRE2",
-	"S_ROBOHOOD_JUMP1",
-	"S_ROBOHOOD_JUMP2",
-	"S_ROBOHOOD_JUMP3",
-
-	// Castlebot Facestabber
-	"S_FACESTABBER_STND1",
-	"S_FACESTABBER_STND2",
-	"S_FACESTABBER_STND3",
-	"S_FACESTABBER_STND4",
-	"S_FACESTABBER_STND5",
-	"S_FACESTABBER_STND6",
-	"S_FACESTABBER_CHARGE1",
-	"S_FACESTABBER_CHARGE2",
-	"S_FACESTABBER_CHARGE3",
-	"S_FACESTABBER_CHARGE4",
-	"S_FACESTABBER_PAIN",
-	"S_FACESTABBER_DIE1",
-	"S_FACESTABBER_DIE2",
-	"S_FACESTABBER_DIE3",
-	"S_FACESTABBERSPEAR",
-
-	// Egg Guard
-	"S_EGGGUARD_STND",
-	"S_EGGGUARD_WALK1",
-	"S_EGGGUARD_WALK2",
-	"S_EGGGUARD_WALK3",
-	"S_EGGGUARD_WALK4",
-	"S_EGGGUARD_MAD1",
-	"S_EGGGUARD_MAD2",
-	"S_EGGGUARD_MAD3",
-	"S_EGGGUARD_RUN1",
-	"S_EGGGUARD_RUN2",
-	"S_EGGGUARD_RUN3",
-	"S_EGGGUARD_RUN4",
-
-	// Egg Shield for Egg Guard
-	"S_EGGSHIELD",
-	"S_EGGSHIELDBREAK",
-
-	// Green Snapper
-	"S_SNAPPER_SPAWN",
-	"S_SNAPPER_SPAWN2",
-	"S_GSNAPPER_STND",
-	"S_GSNAPPER1",
-	"S_GSNAPPER2",
-	"S_GSNAPPER3",
-	"S_GSNAPPER4",
-	"S_SNAPPER_XPLD",
-	"S_SNAPPER_LEG",
-	"S_SNAPPER_LEGRAISE",
-	"S_SNAPPER_HEAD",
-
-	// Minus
-	"S_MINUS_INIT",
-	"S_MINUS_STND",
-	"S_MINUS_DIGGING1",
-	"S_MINUS_DIGGING2",
-	"S_MINUS_DIGGING3",
-	"S_MINUS_DIGGING4",
-	"S_MINUS_BURST0",
-	"S_MINUS_BURST1",
-	"S_MINUS_BURST2",
-	"S_MINUS_BURST3",
-	"S_MINUS_BURST4",
-	"S_MINUS_BURST5",
-	"S_MINUS_POPUP",
-	"S_MINUS_AERIAL1",
-	"S_MINUS_AERIAL2",
-	"S_MINUS_AERIAL3",
-	"S_MINUS_AERIAL4",
-
-	// Minus dirt
-	"S_MINUSDIRT1",
-	"S_MINUSDIRT2",
-	"S_MINUSDIRT3",
-	"S_MINUSDIRT4",
-	"S_MINUSDIRT5",
-	"S_MINUSDIRT6",
-	"S_MINUSDIRT7",
-
-	// Spring Shell
-	"S_SSHELL_STND",
-	"S_SSHELL_RUN1",
-	"S_SSHELL_RUN2",
-	"S_SSHELL_RUN3",
-	"S_SSHELL_RUN4",
-	"S_SSHELL_SPRING1",
-	"S_SSHELL_SPRING2",
-	"S_SSHELL_SPRING3",
-	"S_SSHELL_SPRING4",
-
-	// Spring Shell (yellow)
-	"S_YSHELL_STND",
-	"S_YSHELL_RUN1",
-	"S_YSHELL_RUN2",
-	"S_YSHELL_RUN3",
-	"S_YSHELL_RUN4",
-	"S_YSHELL_SPRING1",
-	"S_YSHELL_SPRING2",
-	"S_YSHELL_SPRING3",
-	"S_YSHELL_SPRING4",
-
-	// Unidus
-	"S_UNIDUS_STND",
-	"S_UNIDUS_RUN",
-	"S_UNIDUS_BALL",
-
-	// Canarivore
-	"S_CANARIVORE_LOOK",
-	"S_CANARIVORE_AWAKEN1",
-	"S_CANARIVORE_AWAKEN2",
-	"S_CANARIVORE_AWAKEN3",
-	"S_CANARIVORE_GAS1",
-	"S_CANARIVORE_GAS2",
-	"S_CANARIVORE_GAS3",
-	"S_CANARIVORE_GAS4",
-	"S_CANARIVORE_GAS5",
-	"S_CANARIVORE_GASREPEAT",
-	"S_CANARIVORE_CLOSE1",
-	"S_CANARIVORE_CLOSE2",
-	"S_CANARIVOREGAS_1",
-	"S_CANARIVOREGAS_2",
-	"S_CANARIVOREGAS_3",
-	"S_CANARIVOREGAS_4",
-	"S_CANARIVOREGAS_5",
-	"S_CANARIVOREGAS_6",
-	"S_CANARIVOREGAS_7",
-	"S_CANARIVOREGAS_8",
-
-	// Pyre Fly
-	"S_PYREFLY_FLY",
-	"S_PYREFLY_BURN",
-	"S_PYREFIRE1",
-	"S_PYREFIRE2",
-
-	// Pterabyte
-	"S_PTERABYTESPAWNER",
-	"S_PTERABYTEWAYPOINT",
-	"S_PTERABYTE_FLY1",
-	"S_PTERABYTE_FLY2",
-	"S_PTERABYTE_FLY3",
-	"S_PTERABYTE_FLY4",
-	"S_PTERABYTE_SWOOPDOWN",
-	"S_PTERABYTE_SWOOPUP",
-
-	// Dragonbomber
-	"S_DRAGONBOMBER",
-	"S_DRAGONWING1",
-	"S_DRAGONWING2",
-	"S_DRAGONWING3",
-	"S_DRAGONWING4",
-	"S_DRAGONTAIL_LOADED",
-	"S_DRAGONTAIL_EMPTY",
-	"S_DRAGONTAIL_EMPTYLOOP",
-	"S_DRAGONTAIL_RELOAD",
-	"S_DRAGONMINE",
-	"S_DRAGONMINE_LAND1",
-	"S_DRAGONMINE_LAND2",
-	"S_DRAGONMINE_SLOWFLASH1",
-	"S_DRAGONMINE_SLOWFLASH2",
-	"S_DRAGONMINE_SLOWLOOP",
-	"S_DRAGONMINE_FASTFLASH1",
-	"S_DRAGONMINE_FASTFLASH2",
-	"S_DRAGONMINE_FASTLOOP",
-
-	// Boss Explosion
-	"S_BOSSEXPLODE",
-
-	// S3&K Boss Explosion
-	"S_SONIC3KBOSSEXPLOSION1",
-	"S_SONIC3KBOSSEXPLOSION2",
-	"S_SONIC3KBOSSEXPLOSION3",
-	"S_SONIC3KBOSSEXPLOSION4",
-	"S_SONIC3KBOSSEXPLOSION5",
-	"S_SONIC3KBOSSEXPLOSION6",
-
-	"S_JETFUME1",
-
-	// Boss 1
-	"S_EGGMOBILE_STND",
-	"S_EGGMOBILE_ROFL",
-	"S_EGGMOBILE_LATK1",
-	"S_EGGMOBILE_LATK2",
-	"S_EGGMOBILE_LATK3",
-	"S_EGGMOBILE_LATK4",
-	"S_EGGMOBILE_LATK5",
-	"S_EGGMOBILE_LATK6",
-	"S_EGGMOBILE_LATK7",
-	"S_EGGMOBILE_LATK8",
-	"S_EGGMOBILE_LATK9",
-	"S_EGGMOBILE_RATK1",
-	"S_EGGMOBILE_RATK2",
-	"S_EGGMOBILE_RATK3",
-	"S_EGGMOBILE_RATK4",
-	"S_EGGMOBILE_RATK5",
-	"S_EGGMOBILE_RATK6",
-	"S_EGGMOBILE_RATK7",
-	"S_EGGMOBILE_RATK8",
-	"S_EGGMOBILE_RATK9",
-	"S_EGGMOBILE_PANIC1",
-	"S_EGGMOBILE_PANIC2",
-	"S_EGGMOBILE_PANIC3",
-	"S_EGGMOBILE_PANIC4",
-	"S_EGGMOBILE_PANIC5",
-	"S_EGGMOBILE_PANIC6",
-	"S_EGGMOBILE_PANIC7",
-	"S_EGGMOBILE_PANIC8",
-	"S_EGGMOBILE_PANIC9",
-	"S_EGGMOBILE_PANIC10",
-	"S_EGGMOBILE_PANIC11",
-	"S_EGGMOBILE_PANIC12",
-	"S_EGGMOBILE_PANIC13",
-	"S_EGGMOBILE_PANIC14",
-	"S_EGGMOBILE_PANIC15",
-	"S_EGGMOBILE_PAIN",
-	"S_EGGMOBILE_PAIN2",
-	"S_EGGMOBILE_DIE1",
-	"S_EGGMOBILE_DIE2",
-	"S_EGGMOBILE_DIE3",
-	"S_EGGMOBILE_DIE4",
-	"S_EGGMOBILE_FLEE1",
-	"S_EGGMOBILE_FLEE2",
-	"S_EGGMOBILE_BALL",
-	"S_EGGMOBILE_TARGET",
-
-	"S_BOSSEGLZ1",
-	"S_BOSSEGLZ2",
-
-	// Boss 2
-	"S_EGGMOBILE2_STND",
-	"S_EGGMOBILE2_POGO1",
-	"S_EGGMOBILE2_POGO2",
-	"S_EGGMOBILE2_POGO3",
-	"S_EGGMOBILE2_POGO4",
-	"S_EGGMOBILE2_POGO5",
-	"S_EGGMOBILE2_POGO6",
-	"S_EGGMOBILE2_POGO7",
-	"S_EGGMOBILE2_PAIN",
-	"S_EGGMOBILE2_PAIN2",
-	"S_EGGMOBILE2_DIE1",
-	"S_EGGMOBILE2_DIE2",
-	"S_EGGMOBILE2_DIE3",
-	"S_EGGMOBILE2_DIE4",
-	"S_EGGMOBILE2_FLEE1",
-	"S_EGGMOBILE2_FLEE2",
-
-	"S_BOSSTANK1",
-	"S_BOSSTANK2",
-	"S_BOSSSPIGOT",
-
-	// Boss 2 Goop
-	"S_GOOP1",
-	"S_GOOP2",
-	"S_GOOP3",
-	"S_GOOPTRAIL",
-
-	// Boss 3
-	"S_EGGMOBILE3_STND",
-	"S_EGGMOBILE3_SHOCK",
-	"S_EGGMOBILE3_ATK1",
-	"S_EGGMOBILE3_ATK2",
-	"S_EGGMOBILE3_ATK3A",
-	"S_EGGMOBILE3_ATK3B",
-	"S_EGGMOBILE3_ATK3C",
-	"S_EGGMOBILE3_ATK3D",
-	"S_EGGMOBILE3_ATK4",
-	"S_EGGMOBILE3_ATK5",
-	"S_EGGMOBILE3_ROFL",
-	"S_EGGMOBILE3_PAIN",
-	"S_EGGMOBILE3_PAIN2",
-	"S_EGGMOBILE3_DIE1",
-	"S_EGGMOBILE3_DIE2",
-	"S_EGGMOBILE3_DIE3",
-	"S_EGGMOBILE3_DIE4",
-	"S_EGGMOBILE3_FLEE1",
-	"S_EGGMOBILE3_FLEE2",
-
-	// Boss 3 Pinch
-	"S_FAKEMOBILE_INIT",
-	"S_FAKEMOBILE",
-	"S_FAKEMOBILE_ATK1",
-	"S_FAKEMOBILE_ATK2",
-	"S_FAKEMOBILE_ATK3A",
-	"S_FAKEMOBILE_ATK3B",
-	"S_FAKEMOBILE_ATK3C",
-	"S_FAKEMOBILE_ATK3D",
-	"S_FAKEMOBILE_DIE1",
-	"S_FAKEMOBILE_DIE2",
-
-	"S_BOSSSEBH1",
-	"S_BOSSSEBH2",
-
-	// Boss 3 Shockwave
-	"S_SHOCKWAVE1",
-	"S_SHOCKWAVE2",
-
-	// Boss 4
-	"S_EGGMOBILE4_STND",
-	"S_EGGMOBILE4_LATK1",
-	"S_EGGMOBILE4_LATK2",
-	"S_EGGMOBILE4_LATK3",
-	"S_EGGMOBILE4_LATK4",
-	"S_EGGMOBILE4_LATK5",
-	"S_EGGMOBILE4_LATK6",
-	"S_EGGMOBILE4_RATK1",
-	"S_EGGMOBILE4_RATK2",
-	"S_EGGMOBILE4_RATK3",
-	"S_EGGMOBILE4_RATK4",
-	"S_EGGMOBILE4_RATK5",
-	"S_EGGMOBILE4_RATK6",
-	"S_EGGMOBILE4_RAISE1",
-	"S_EGGMOBILE4_RAISE2",
-	"S_EGGMOBILE4_PAIN1",
-	"S_EGGMOBILE4_PAIN2",
-	"S_EGGMOBILE4_DIE1",
-	"S_EGGMOBILE4_DIE2",
-	"S_EGGMOBILE4_DIE3",
-	"S_EGGMOBILE4_DIE4",
-	"S_EGGMOBILE4_FLEE1",
-	"S_EGGMOBILE4_FLEE2",
-	"S_EGGMOBILE4_MACE",
-	"S_EGGMOBILE4_MACE_DIE1",
-	"S_EGGMOBILE4_MACE_DIE2",
-	"S_EGGMOBILE4_MACE_DIE3",
-
-	// Boss 4 jet flame
-	"S_JETFLAME",
-
-	// Boss 4 Spectator Eggrobo
-	"S_EGGROBO1_STND",
-	"S_EGGROBO1_BSLAP1",
-	"S_EGGROBO1_BSLAP2",
-	"S_EGGROBO1_PISSED",
-
-	// Boss 4 Spectator Eggrobo jet flame
-	"S_EGGROBOJET",
-
-	// Boss 5
-	"S_FANG_SETUP",
-	"S_FANG_INTRO0",
-	"S_FANG_INTRO1",
-	"S_FANG_INTRO2",
-	"S_FANG_INTRO3",
-	"S_FANG_INTRO4",
-	"S_FANG_INTRO5",
-	"S_FANG_INTRO6",
-	"S_FANG_INTRO7",
-	"S_FANG_INTRO8",
-	"S_FANG_INTRO9",
-	"S_FANG_INTRO10",
-	"S_FANG_INTRO11",
-	"S_FANG_INTRO12",
-	"S_FANG_CLONE1",
-	"S_FANG_CLONE2",
-	"S_FANG_CLONE3",
-	"S_FANG_CLONE4",
-	"S_FANG_IDLE0",
-	"S_FANG_IDLE1",
-	"S_FANG_IDLE2",
-	"S_FANG_IDLE3",
-	"S_FANG_IDLE4",
-	"S_FANG_IDLE5",
-	"S_FANG_IDLE6",
-	"S_FANG_IDLE7",
-	"S_FANG_IDLE8",
-	"S_FANG_PAIN1",
-	"S_FANG_PAIN2",
-	"S_FANG_PATHINGSTART1",
-	"S_FANG_PATHINGSTART2",
-	"S_FANG_PATHING",
-	"S_FANG_BOUNCE1",
-	"S_FANG_BOUNCE2",
-	"S_FANG_BOUNCE3",
-	"S_FANG_BOUNCE4",
-	"S_FANG_FALL1",
-	"S_FANG_FALL2",
-	"S_FANG_CHECKPATH1",
-	"S_FANG_CHECKPATH2",
-	"S_FANG_PATHINGCONT1",
-	"S_FANG_PATHINGCONT2",
-	"S_FANG_PATHINGCONT3",
-	"S_FANG_SKID1",
-	"S_FANG_SKID2",
-	"S_FANG_SKID3",
-	"S_FANG_CHOOSEATTACK",
-	"S_FANG_FIRESTART1",
-	"S_FANG_FIRESTART2",
-	"S_FANG_FIRE1",
-	"S_FANG_FIRE2",
-	"S_FANG_FIRE3",
-	"S_FANG_FIRE4",
-	"S_FANG_FIREREPEAT",
-	"S_FANG_LOBSHOT0",
-	"S_FANG_LOBSHOT1",
-	"S_FANG_LOBSHOT2",
-	"S_FANG_WAIT1",
-	"S_FANG_WAIT2",
-	"S_FANG_WALLHIT",
-	"S_FANG_PINCHPATHINGSTART1",
-	"S_FANG_PINCHPATHINGSTART2",
-	"S_FANG_PINCHPATHING",
-	"S_FANG_PINCHBOUNCE0",
-	"S_FANG_PINCHBOUNCE1",
-	"S_FANG_PINCHBOUNCE2",
-	"S_FANG_PINCHBOUNCE3",
-	"S_FANG_PINCHBOUNCE4",
-	"S_FANG_PINCHFALL0",
-	"S_FANG_PINCHFALL1",
-	"S_FANG_PINCHFALL2",
-	"S_FANG_PINCHSKID1",
-	"S_FANG_PINCHSKID2",
-	"S_FANG_PINCHLOBSHOT0",
-	"S_FANG_PINCHLOBSHOT1",
-	"S_FANG_PINCHLOBSHOT2",
-	"S_FANG_PINCHLOBSHOT3",
-	"S_FANG_PINCHLOBSHOT4",
-	"S_FANG_DIE1",
-	"S_FANG_DIE2",
-	"S_FANG_DIE3",
-	"S_FANG_DIE4",
-	"S_FANG_DIE5",
-	"S_FANG_DIE6",
-	"S_FANG_DIE7",
-	"S_FANG_DIE8",
-	"S_FANG_FLEEPATHING1",
-	"S_FANG_FLEEPATHING2",
-	"S_FANG_FLEEBOUNCE1",
-	"S_FANG_FLEEBOUNCE2",
-	"S_FANG_KO",
-
-	"S_BROKENROBOTRANDOM",
-	"S_BROKENROBOTA",
-	"S_BROKENROBOTB",
-	"S_BROKENROBOTC",
-	"S_BROKENROBOTD",
-	"S_BROKENROBOTE",
-	"S_BROKENROBOTF",
-
-	"S_ALART1",
-	"S_ALART2",
-
-	"S_VWREF",
-	"S_VWREB",
-
-	"S_PROJECTORLIGHT1",
-	"S_PROJECTORLIGHT2",
-	"S_PROJECTORLIGHT3",
-	"S_PROJECTORLIGHT4",
-	"S_PROJECTORLIGHT5",
-
-	"S_FBOMB1",
-	"S_FBOMB2",
-	"S_FBOMB_EXPL1",
-	"S_FBOMB_EXPL2",
-	"S_FBOMB_EXPL3",
-	"S_FBOMB_EXPL4",
-	"S_FBOMB_EXPL5",
-	"S_FBOMB_EXPL6",
-	"S_TNTDUST_1",
-	"S_TNTDUST_2",
-	"S_TNTDUST_3",
-	"S_TNTDUST_4",
-	"S_TNTDUST_5",
-	"S_TNTDUST_6",
-	"S_TNTDUST_7",
-	"S_TNTDUST_8",
-	"S_FSGNA",
-	"S_FSGNB",
-	"S_FSGNC",
-	"S_FSGND",
-
-	// Black Eggman (Boss 7)
-	"S_BLACKEGG_STND",
-	"S_BLACKEGG_STND2",
-	"S_BLACKEGG_WALK1",
-	"S_BLACKEGG_WALK2",
-	"S_BLACKEGG_WALK3",
-	"S_BLACKEGG_WALK4",
-	"S_BLACKEGG_WALK5",
-	"S_BLACKEGG_WALK6",
-	"S_BLACKEGG_SHOOT1",
-	"S_BLACKEGG_SHOOT2",
-	"S_BLACKEGG_PAIN1",
-	"S_BLACKEGG_PAIN2",
-	"S_BLACKEGG_PAIN3",
-	"S_BLACKEGG_PAIN4",
-	"S_BLACKEGG_PAIN5",
-	"S_BLACKEGG_PAIN6",
-	"S_BLACKEGG_PAIN7",
-	"S_BLACKEGG_PAIN8",
-	"S_BLACKEGG_PAIN9",
-	"S_BLACKEGG_PAIN10",
-	"S_BLACKEGG_PAIN11",
-	"S_BLACKEGG_PAIN12",
-	"S_BLACKEGG_PAIN13",
-	"S_BLACKEGG_PAIN14",
-	"S_BLACKEGG_PAIN15",
-	"S_BLACKEGG_PAIN16",
-	"S_BLACKEGG_PAIN17",
-	"S_BLACKEGG_PAIN18",
-	"S_BLACKEGG_PAIN19",
-	"S_BLACKEGG_PAIN20",
-	"S_BLACKEGG_PAIN21",
-	"S_BLACKEGG_PAIN22",
-	"S_BLACKEGG_PAIN23",
-	"S_BLACKEGG_PAIN24",
-	"S_BLACKEGG_PAIN25",
-	"S_BLACKEGG_PAIN26",
-	"S_BLACKEGG_PAIN27",
-	"S_BLACKEGG_PAIN28",
-	"S_BLACKEGG_PAIN29",
-	"S_BLACKEGG_PAIN30",
-	"S_BLACKEGG_PAIN31",
-	"S_BLACKEGG_PAIN32",
-	"S_BLACKEGG_PAIN33",
-	"S_BLACKEGG_PAIN34",
-	"S_BLACKEGG_PAIN35",
-	"S_BLACKEGG_HITFACE1",
-	"S_BLACKEGG_HITFACE2",
-	"S_BLACKEGG_HITFACE3",
-	"S_BLACKEGG_HITFACE4",
-	"S_BLACKEGG_DIE1",
-	"S_BLACKEGG_DIE2",
-	"S_BLACKEGG_DIE3",
-	"S_BLACKEGG_DIE4",
-	"S_BLACKEGG_DIE5",
-	"S_BLACKEGG_MISSILE1",
-	"S_BLACKEGG_MISSILE2",
-	"S_BLACKEGG_MISSILE3",
-	"S_BLACKEGG_GOOP",
-	"S_BLACKEGG_JUMP1",
-	"S_BLACKEGG_JUMP2",
-	"S_BLACKEGG_DESTROYPLAT1",
-	"S_BLACKEGG_DESTROYPLAT2",
-	"S_BLACKEGG_DESTROYPLAT3",
-
-	"S_BLACKEGG_HELPER", // Collision helper
-
-	"S_BLACKEGG_GOOP1",
-	"S_BLACKEGG_GOOP2",
-	"S_BLACKEGG_GOOP3",
-	"S_BLACKEGG_GOOP4",
-	"S_BLACKEGG_GOOP5",
-	"S_BLACKEGG_GOOP6",
-	"S_BLACKEGG_GOOP7",
-
-	"S_BLACKEGG_MISSILE",
-
-	// New Very-Last-Minute 2.1 Brak Eggman (Cy-Brak-demon)
-	"S_CYBRAKDEMON_IDLE",
-	"S_CYBRAKDEMON_WALK1",
-	"S_CYBRAKDEMON_WALK2",
-	"S_CYBRAKDEMON_WALK3",
-	"S_CYBRAKDEMON_WALK4",
-	"S_CYBRAKDEMON_WALK5",
-	"S_CYBRAKDEMON_WALK6",
-	"S_CYBRAKDEMON_CHOOSE_ATTACK1",
-	"S_CYBRAKDEMON_MISSILE_ATTACK1", // Aim
-	"S_CYBRAKDEMON_MISSILE_ATTACK2", // Fire
-	"S_CYBRAKDEMON_MISSILE_ATTACK3", // Aim
-	"S_CYBRAKDEMON_MISSILE_ATTACK4", // Fire
-	"S_CYBRAKDEMON_MISSILE_ATTACK5", // Aim
-	"S_CYBRAKDEMON_MISSILE_ATTACK6", // Fire
-	"S_CYBRAKDEMON_FLAME_ATTACK1", // Reset
-	"S_CYBRAKDEMON_FLAME_ATTACK2", // Aim
-	"S_CYBRAKDEMON_FLAME_ATTACK3", // Fire
-	"S_CYBRAKDEMON_FLAME_ATTACK4", // Loop
-	"S_CYBRAKDEMON_CHOOSE_ATTACK2",
-	"S_CYBRAKDEMON_VILE_ATTACK1",
-	"S_CYBRAKDEMON_VILE_ATTACK2",
-	"S_CYBRAKDEMON_VILE_ATTACK3",
-	"S_CYBRAKDEMON_VILE_ATTACK4",
-	"S_CYBRAKDEMON_VILE_ATTACK5",
-	"S_CYBRAKDEMON_VILE_ATTACK6",
-	"S_CYBRAKDEMON_NAPALM_ATTACK1",
-	"S_CYBRAKDEMON_NAPALM_ATTACK2",
-	"S_CYBRAKDEMON_NAPALM_ATTACK3",
-	"S_CYBRAKDEMON_FINISH_ATTACK1", // If just attacked, remove MF2_FRET w/out going back to spawnstate
-	"S_CYBRAKDEMON_FINISH_ATTACK2", // Force a delay between attacks so you don't get bombarded with them back-to-back
-	"S_CYBRAKDEMON_PAIN1",
-	"S_CYBRAKDEMON_PAIN2",
-	"S_CYBRAKDEMON_PAIN3",
-	"S_CYBRAKDEMON_DIE1",
-	"S_CYBRAKDEMON_DIE2",
-	"S_CYBRAKDEMON_DIE3",
-	"S_CYBRAKDEMON_DIE4",
-	"S_CYBRAKDEMON_DIE5",
-	"S_CYBRAKDEMON_DIE6",
-	"S_CYBRAKDEMON_DIE7",
-	"S_CYBRAKDEMON_DIE8",
-	"S_CYBRAKDEMON_DEINVINCIBLERIZE",
-	"S_CYBRAKDEMON_INVINCIBLERIZE",
-
-	"S_CYBRAKDEMONMISSILE",
-	"S_CYBRAKDEMONMISSILE_EXPLODE1",
-	"S_CYBRAKDEMONMISSILE_EXPLODE2",
-	"S_CYBRAKDEMONMISSILE_EXPLODE3",
-
-	"S_CYBRAKDEMONFLAMESHOT_FLY1",
-	"S_CYBRAKDEMONFLAMESHOT_FLY2",
-	"S_CYBRAKDEMONFLAMESHOT_FLY3",
-	"S_CYBRAKDEMONFLAMESHOT_DIE",
-
-	"S_CYBRAKDEMONFLAMEREST",
-
-	"S_CYBRAKDEMONELECTRICBARRIER_INIT1",
-	"S_CYBRAKDEMONELECTRICBARRIER_INIT2",
-	"S_CYBRAKDEMONELECTRICBARRIER_PLAYSOUND",
-	"S_CYBRAKDEMONELECTRICBARRIER1",
-	"S_CYBRAKDEMONELECTRICBARRIER2",
-	"S_CYBRAKDEMONELECTRICBARRIER3",
-	"S_CYBRAKDEMONELECTRICBARRIER4",
-	"S_CYBRAKDEMONELECTRICBARRIER5",
-	"S_CYBRAKDEMONELECTRICBARRIER6",
-	"S_CYBRAKDEMONELECTRICBARRIER7",
-	"S_CYBRAKDEMONELECTRICBARRIER8",
-	"S_CYBRAKDEMONELECTRICBARRIER9",
-	"S_CYBRAKDEMONELECTRICBARRIER10",
-	"S_CYBRAKDEMONELECTRICBARRIER11",
-	"S_CYBRAKDEMONELECTRICBARRIER12",
-	"S_CYBRAKDEMONELECTRICBARRIER13",
-	"S_CYBRAKDEMONELECTRICBARRIER14",
-	"S_CYBRAKDEMONELECTRICBARRIER15",
-	"S_CYBRAKDEMONELECTRICBARRIER16",
-	"S_CYBRAKDEMONELECTRICBARRIER17",
-	"S_CYBRAKDEMONELECTRICBARRIER18",
-	"S_CYBRAKDEMONELECTRICBARRIER19",
-	"S_CYBRAKDEMONELECTRICBARRIER20",
-	"S_CYBRAKDEMONELECTRICBARRIER21",
-	"S_CYBRAKDEMONELECTRICBARRIER22",
-	"S_CYBRAKDEMONELECTRICBARRIER23",
-	"S_CYBRAKDEMONELECTRICBARRIER24",
-	"S_CYBRAKDEMONELECTRICBARRIER_DIE1",
-	"S_CYBRAKDEMONELECTRICBARRIER_DIE2",
-	"S_CYBRAKDEMONELECTRICBARRIER_DIE3",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMCHECK",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMSUCCESS",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMCHOOSE",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM1",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM2",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM3",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM4",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM5",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM6",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM7",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM8",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM9",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM10",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM11",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOM12",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMFAIL",
-	"S_CYBRAKDEMONELECTRICBARRIER_SPARK_RANDOMLOOP",
-	"S_CYBRAKDEMONELECTRICBARRIER_REVIVE1",
-	"S_CYBRAKDEMONELECTRICBARRIER_REVIVE2",
-	"S_CYBRAKDEMONELECTRICBARRIER_REVIVE3",
-
-	"S_CYBRAKDEMONTARGETRETICULE1",
-	"S_CYBRAKDEMONTARGETRETICULE2",
-	"S_CYBRAKDEMONTARGETRETICULE3",
-	"S_CYBRAKDEMONTARGETRETICULE4",
-	"S_CYBRAKDEMONTARGETRETICULE5",
-	"S_CYBRAKDEMONTARGETRETICULE6",
-	"S_CYBRAKDEMONTARGETRETICULE7",
-	"S_CYBRAKDEMONTARGETRETICULE8",
-	"S_CYBRAKDEMONTARGETRETICULE9",
-	"S_CYBRAKDEMONTARGETRETICULE10",
-	"S_CYBRAKDEMONTARGETRETICULE11",
-	"S_CYBRAKDEMONTARGETRETICULE12",
-	"S_CYBRAKDEMONTARGETRETICULE13",
-	"S_CYBRAKDEMONTARGETRETICULE14",
-
-	"S_CYBRAKDEMONTARGETDOT",
-
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY1",
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY2",
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY3",
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_FLY4",
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE1", // Explode
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE2", // Outer ring
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE3", // Center
-	"S_CYBRAKDEMONNAPALMBOMBLARGE_DIE4", // Sound
-
-	"S_CYBRAKDEMONNAPALMBOMBSMALL",
-	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE1", // Explode
-	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE2", // Outer ring
-	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE3", // Inner ring
-	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE4", // Center
-	"S_CYBRAKDEMONNAPALMBOMBSMALL_DIE5", // Sound
-
-	"S_CYBRAKDEMONNAPALMFLAME_FLY1",
-	"S_CYBRAKDEMONNAPALMFLAME_FLY2",
-	"S_CYBRAKDEMONNAPALMFLAME_FLY3",
-	"S_CYBRAKDEMONNAPALMFLAME_FLY4",
-	"S_CYBRAKDEMONNAPALMFLAME_FLY5",
-	"S_CYBRAKDEMONNAPALMFLAME_FLY6",
-	"S_CYBRAKDEMONNAPALMFLAME_DIE",
-
-	"S_CYBRAKDEMONVILEEXPLOSION1",
-	"S_CYBRAKDEMONVILEEXPLOSION2",
-	"S_CYBRAKDEMONVILEEXPLOSION3",
-
-	// Metal Sonic (Race)
-	"S_METALSONIC_RACE",
-	// Metal Sonic (Battle)
-	"S_METALSONIC_FLOAT",
-	"S_METALSONIC_VECTOR",
-	"S_METALSONIC_STUN",
-	"S_METALSONIC_RAISE",
-	"S_METALSONIC_GATHER",
-	"S_METALSONIC_DASH",
-	"S_METALSONIC_BOUNCE",
-	"S_METALSONIC_BADBOUNCE",
-	"S_METALSONIC_SHOOT",
-	"S_METALSONIC_PAIN",
-	"S_METALSONIC_DEATH1",
-	"S_METALSONIC_DEATH2",
-	"S_METALSONIC_DEATH3",
-	"S_METALSONIC_DEATH4",
-	"S_METALSONIC_FLEE1",
-	"S_METALSONIC_FLEE2",
-
-	"S_MSSHIELD_F1",
-	"S_MSSHIELD_F2",
-
-	// Ring
-	"S_RING",
-
-	// Blue Sphere for special stages
-	"S_BLUESPHERE",
-	"S_BLUESPHEREBONUS",
-	"S_BLUESPHERESPARK",
-
-	// Bomb Sphere
-	"S_BOMBSPHERE1",
-	"S_BOMBSPHERE2",
-	"S_BOMBSPHERE3",
-	"S_BOMBSPHERE4",
-
-	// NiGHTS Chip
-	"S_NIGHTSCHIP",
-	"S_NIGHTSCHIPBONUS",
-
-	// NiGHTS Star
-	"S_NIGHTSSTAR",
-	"S_NIGHTSSTARXMAS",
-
-	// Gravity Wells for special stages
-	"S_GRAVWELLGREEN",
-	"S_GRAVWELLRED",
-
-	// Individual Team Rings
-	"S_TEAMRING",
-
-	// Special Stage Token
-	"S_TOKEN",
-
-	// CTF Flags
-	"S_REDFLAG",
-	"S_BLUEFLAG",
-
-	// Emblem
-	"S_EMBLEM1",
-	"S_EMBLEM2",
-	"S_EMBLEM3",
-	"S_EMBLEM4",
-	"S_EMBLEM5",
-	"S_EMBLEM6",
-	"S_EMBLEM7",
-	"S_EMBLEM8",
-	"S_EMBLEM9",
-	"S_EMBLEM10",
-	"S_EMBLEM11",
-	"S_EMBLEM12",
-	"S_EMBLEM13",
-	"S_EMBLEM14",
-	"S_EMBLEM15",
-	"S_EMBLEM16",
-	"S_EMBLEM17",
-	"S_EMBLEM18",
-	"S_EMBLEM19",
-	"S_EMBLEM20",
-	"S_EMBLEM21",
-	"S_EMBLEM22",
-	"S_EMBLEM23",
-	"S_EMBLEM24",
-	"S_EMBLEM25",
-	"S_EMBLEM26",
-
-	// Chaos Emeralds
-	"S_CEMG1",
-	"S_CEMG2",
-	"S_CEMG3",
-	"S_CEMG4",
-	"S_CEMG5",
-	"S_CEMG6",
-	"S_CEMG7",
-
-	// Emerald hunt shards
-	"S_SHRD1",
-	"S_SHRD2",
-	"S_SHRD3",
-
-	// Bubble Source
-	"S_BUBBLES1",
-	"S_BUBBLES2",
-	"S_BUBBLES3",
-	"S_BUBBLES4",
-
-	// Level End Sign
-	"S_SIGN",
-	"S_SIGNSPIN1",
-	"S_SIGNSPIN2",
-	"S_SIGNSPIN3",
-	"S_SIGNSPIN4",
-	"S_SIGNSPIN5",
-	"S_SIGNSPIN6",
-	"S_SIGNPLAYER",
-	"S_SIGNSLOW",
-	"S_SIGNSTOP",
-	"S_SIGNBOARD",
-	"S_EGGMANSIGN",
-	"S_CLEARSIGN",
-
-	// Spike Ball
-	"S_SPIKEBALL1",
-	"S_SPIKEBALL2",
-	"S_SPIKEBALL3",
-	"S_SPIKEBALL4",
-	"S_SPIKEBALL5",
-	"S_SPIKEBALL6",
-	"S_SPIKEBALL7",
-	"S_SPIKEBALL8",
-
-	// Elemental Shield's Spawn
-	"S_SPINFIRE1",
-	"S_SPINFIRE2",
-	"S_SPINFIRE3",
-	"S_SPINFIRE4",
-	"S_SPINFIRE5",
-	"S_SPINFIRE6",
-
-	// Spikes
-	"S_SPIKE1",
-	"S_SPIKE2",
-	"S_SPIKE3",
-	"S_SPIKE4",
-	"S_SPIKE5",
-	"S_SPIKE6",
-	"S_SPIKED1",
-	"S_SPIKED2",
-
-	// Wall spikes
-	"S_WALLSPIKE1",
-	"S_WALLSPIKE2",
-	"S_WALLSPIKE3",
-	"S_WALLSPIKE4",
-	"S_WALLSPIKE5",
-	"S_WALLSPIKE6",
-	"S_WALLSPIKEBASE",
-	"S_WALLSPIKED1",
-	"S_WALLSPIKED2",
-
-	// Starpost
-	"S_STARPOST_IDLE",
-	"S_STARPOST_FLASH",
-	"S_STARPOST_STARTSPIN",
-	"S_STARPOST_SPIN",
-	"S_STARPOST_ENDSPIN",
-
-	// Big floating mine
-	"S_BIGMINE_IDLE",
-	"S_BIGMINE_ALERT1",
-	"S_BIGMINE_ALERT2",
-	"S_BIGMINE_ALERT3",
-	"S_BIGMINE_SET1",
-	"S_BIGMINE_SET2",
-	"S_BIGMINE_SET3",
-	"S_BIGMINE_BLAST1",
-	"S_BIGMINE_BLAST2",
-	"S_BIGMINE_BLAST3",
-	"S_BIGMINE_BLAST4",
-	"S_BIGMINE_BLAST5",
-
-	// Cannon Launcher
-	"S_CANNONLAUNCHER1",
-	"S_CANNONLAUNCHER2",
-	"S_CANNONLAUNCHER3",
-
-	// Monitor Miscellany
-	"S_BOXSPARKLE1",
-	"S_BOXSPARKLE2",
-	"S_BOXSPARKLE3",
-	"S_BOXSPARKLE4",
-
-	"S_BOX_FLICKER",
-	"S_BOX_POP1",
-	"S_BOX_POP2",
-
-	"S_GOLDBOX_FLICKER",
-	"S_GOLDBOX_OFF1",
-	"S_GOLDBOX_OFF2",
-	"S_GOLDBOX_OFF3",
-	"S_GOLDBOX_OFF4",
-	"S_GOLDBOX_OFF5",
-	"S_GOLDBOX_OFF6",
-	"S_GOLDBOX_OFF7",
-
-	// Monitor States (one per box)
-	"S_MYSTERY_BOX",
-	"S_RING_BOX",
-	"S_PITY_BOX",
-	"S_ATTRACT_BOX",
-	"S_FORCE_BOX",
-	"S_ARMAGEDDON_BOX",
-	"S_WHIRLWIND_BOX",
-	"S_ELEMENTAL_BOX",
-	"S_SNEAKERS_BOX",
-	"S_INVULN_BOX",
-	"S_1UP_BOX",
-	"S_EGGMAN_BOX",
-	"S_MIXUP_BOX",
-	"S_GRAVITY_BOX",
-	"S_RECYCLER_BOX",
-	"S_SCORE1K_BOX",
-	"S_SCORE10K_BOX",
-	"S_FLAMEAURA_BOX",
-	"S_BUBBLEWRAP_BOX",
-	"S_THUNDERCOIN_BOX",
-
-	// Gold Repeat Monitor States (one per box)
-	"S_PITY_GOLDBOX",
-	"S_ATTRACT_GOLDBOX",
-	"S_FORCE_GOLDBOX",
-	"S_ARMAGEDDON_GOLDBOX",
-	"S_WHIRLWIND_GOLDBOX",
-	"S_ELEMENTAL_GOLDBOX",
-	"S_SNEAKERS_GOLDBOX",
-	"S_INVULN_GOLDBOX",
-	"S_EGGMAN_GOLDBOX",
-	"S_GRAVITY_GOLDBOX",
-	"S_FLAMEAURA_GOLDBOX",
-	"S_BUBBLEWRAP_GOLDBOX",
-	"S_THUNDERCOIN_GOLDBOX",
-
-	// Team Ring Boxes (these are special)
-	"S_RING_REDBOX1",
-	"S_RING_REDBOX2",
-	"S_REDBOX_POP1",
-	"S_REDBOX_POP2",
-
-	"S_RING_BLUEBOX1",
-	"S_RING_BLUEBOX2",
-	"S_BLUEBOX_POP1",
-	"S_BLUEBOX_POP2",
-
-	// Box Icons -- 2 states each, animation and action
-	"S_RING_ICON1",
-	"S_RING_ICON2",
-
-	"S_PITY_ICON1",
-	"S_PITY_ICON2",
-
-	"S_ATTRACT_ICON1",
-	"S_ATTRACT_ICON2",
-
-	"S_FORCE_ICON1",
-	"S_FORCE_ICON2",
-
-	"S_ARMAGEDDON_ICON1",
-	"S_ARMAGEDDON_ICON2",
-
-	"S_WHIRLWIND_ICON1",
-	"S_WHIRLWIND_ICON2",
-
-	"S_ELEMENTAL_ICON1",
-	"S_ELEMENTAL_ICON2",
-
-	"S_SNEAKERS_ICON1",
-	"S_SNEAKERS_ICON2",
-
-	"S_INVULN_ICON1",
-	"S_INVULN_ICON2",
-
-	"S_1UP_ICON1",
-	"S_1UP_ICON2",
-
-	"S_EGGMAN_ICON1",
-	"S_EGGMAN_ICON2",
-
-	"S_MIXUP_ICON1",
-	"S_MIXUP_ICON2",
-
-	"S_GRAVITY_ICON1",
-	"S_GRAVITY_ICON2",
-
-	"S_RECYCLER_ICON1",
-	"S_RECYCLER_ICON2",
-
-	"S_SCORE1K_ICON1",
-	"S_SCORE1K_ICON2",
-
-	"S_SCORE10K_ICON1",
-	"S_SCORE10K_ICON2",
-
-	"S_FLAMEAURA_ICON1",
-	"S_FLAMEAURA_ICON2",
-
-	"S_BUBBLEWRAP_ICON1",
-	"S_BUBBLEWRAP_ICON2",
-
-	"S_THUNDERCOIN_ICON1",
-	"S_THUNDERCOIN_ICON2",
-
-	// ---
-
-	"S_ROCKET",
-
-	"S_LASER",
-	"S_LASER2",
-	"S_LASERFLASH",
-
-	"S_LASERFLAME1",
-	"S_LASERFLAME2",
-	"S_LASERFLAME3",
-	"S_LASERFLAME4",
-	"S_LASERFLAME5",
-
-	"S_TORPEDO",
-
-	"S_ENERGYBALL1",
-	"S_ENERGYBALL2",
-
-	// Skim Mine, also used by Jetty-Syn bomber
-	"S_MINE1",
-	"S_MINE_BOOM1",
-	"S_MINE_BOOM2",
-	"S_MINE_BOOM3",
-	"S_MINE_BOOM4",
-
-	// Jetty-Syn Bullet
-	"S_JETBULLET1",
-	"S_JETBULLET2",
-
-	"S_TURRETLASER",
-	"S_TURRETLASEREXPLODE1",
-	"S_TURRETLASEREXPLODE2",
-
-	// Cannonball
-	"S_CANNONBALL1",
-
-	// Arrow
-	"S_ARROW",
-	"S_ARROWBONK",
-
-	// Glaregoyle Demon fire
-	"S_DEMONFIRE",
-
-	// The letter
-	"S_LETTER",
-
-	// GFZ flowers
-	"S_GFZFLOWERA",
-	"S_GFZFLOWERB",
-	"S_GFZFLOWERC",
-
-	"S_BLUEBERRYBUSH",
-	"S_BERRYBUSH",
-	"S_BUSH",
-
-	// Trees (both GFZ and misc)
-	"S_GFZTREE",
-	"S_GFZBERRYTREE",
-	"S_GFZCHERRYTREE",
-	"S_CHECKERTREE",
-	"S_CHECKERSUNSETTREE",
-	"S_FHZTREE", // Frozen Hillside
-	"S_FHZPINKTREE",
-	"S_POLYGONTREE",
-	"S_BUSHTREE",
-	"S_BUSHREDTREE",
-	"S_SPRINGTREE",
-
-	// THZ flowers
-	"S_THZFLOWERA", // THZ1 Steam flower
-	"S_THZFLOWERB", // THZ1 Spin flower (red)
-	"S_THZFLOWERC", // THZ1 Spin flower (yellow)
-
-	// THZ Steam Whistle tree/bush
-	"S_THZTREE",
-	"S_THZTREEBRANCH1",
-	"S_THZTREEBRANCH2",
-	"S_THZTREEBRANCH3",
-	"S_THZTREEBRANCH4",
-	"S_THZTREEBRANCH5",
-	"S_THZTREEBRANCH6",
-	"S_THZTREEBRANCH7",
-	"S_THZTREEBRANCH8",
-	"S_THZTREEBRANCH9",
-	"S_THZTREEBRANCH10",
-	"S_THZTREEBRANCH11",
-	"S_THZTREEBRANCH12",
-	"S_THZTREEBRANCH13",
-
-	// THZ Alarm
-	"S_ALARM1",
-
-	// Deep Sea Gargoyle
-	"S_GARGOYLE",
-	"S_BIGGARGOYLE",
-
-	// DSZ Seaweed
-	"S_SEAWEED1",
-	"S_SEAWEED2",
-	"S_SEAWEED3",
-	"S_SEAWEED4",
-	"S_SEAWEED5",
-	"S_SEAWEED6",
-
-	// Dripping Water
-	"S_DRIPA1",
-	"S_DRIPA2",
-	"S_DRIPA3",
-	"S_DRIPA4",
-	"S_DRIPB1",
-	"S_DRIPC1",
-	"S_DRIPC2",
-
-	// Coral
-	"S_CORAL1",
-	"S_CORAL2",
-	"S_CORAL3",
-	"S_CORAL4",
-	"S_CORAL5",
-
-	// Blue Crystal
-	"S_BLUECRYSTAL1",
-
-	// Kelp,
-	"S_KELP",
-
-	// Animated algae
-	"S_ANIMALGAETOP1",
-	"S_ANIMALGAETOP2",
-	"S_ANIMALGAESEG",
-
-	// DSZ Stalagmites
-	"S_DSZSTALAGMITE",
-	"S_DSZ2STALAGMITE",
-
-	// DSZ Light beam
-	"S_LIGHTBEAM1",
-	"S_LIGHTBEAM2",
-	"S_LIGHTBEAM3",
-	"S_LIGHTBEAM4",
-	"S_LIGHTBEAM5",
-	"S_LIGHTBEAM6",
-	"S_LIGHTBEAM7",
-	"S_LIGHTBEAM8",
-	"S_LIGHTBEAM9",
-	"S_LIGHTBEAM10",
-	"S_LIGHTBEAM11",
-	"S_LIGHTBEAM12",
-
-	// CEZ Chain
-	"S_CEZCHAIN",
-
-	// Flame
-	"S_FLAME",
-	"S_FLAMEPARTICLE",
-	"S_FLAMEREST",
-
-	// Eggman Statue
-	"S_EGGSTATUE1",
-
-	// CEZ hidden sling
-	"S_SLING1",
-	"S_SLING2",
-
-	// CEZ maces and chains
-	"S_SMALLMACECHAIN",
-	"S_BIGMACECHAIN",
-	"S_SMALLMACE",
-	"S_BIGMACE",
-	"S_SMALLGRABCHAIN",
-	"S_BIGGRABCHAIN",
-
-	// Yellow spring on a ball
-	"S_YELLOWSPRINGBALL",
-	"S_YELLOWSPRINGBALL2",
-	"S_YELLOWSPRINGBALL3",
-	"S_YELLOWSPRINGBALL4",
-	"S_YELLOWSPRINGBALL5",
-
-	// Red spring on a ball
-	"S_REDSPRINGBALL",
-	"S_REDSPRINGBALL2",
-	"S_REDSPRINGBALL3",
-	"S_REDSPRINGBALL4",
-	"S_REDSPRINGBALL5",
-
-	// Small Firebar
-	"S_SMALLFIREBAR1",
-	"S_SMALLFIREBAR2",
-	"S_SMALLFIREBAR3",
-	"S_SMALLFIREBAR4",
-	"S_SMALLFIREBAR5",
-	"S_SMALLFIREBAR6",
-	"S_SMALLFIREBAR7",
-	"S_SMALLFIREBAR8",
-	"S_SMALLFIREBAR9",
-	"S_SMALLFIREBAR10",
-	"S_SMALLFIREBAR11",
-	"S_SMALLFIREBAR12",
-	"S_SMALLFIREBAR13",
-	"S_SMALLFIREBAR14",
-	"S_SMALLFIREBAR15",
-	"S_SMALLFIREBAR16",
-
-	// Big Firebar
-	"S_BIGFIREBAR1",
-	"S_BIGFIREBAR2",
-	"S_BIGFIREBAR3",
-	"S_BIGFIREBAR4",
-	"S_BIGFIREBAR5",
-	"S_BIGFIREBAR6",
-	"S_BIGFIREBAR7",
-	"S_BIGFIREBAR8",
-	"S_BIGFIREBAR9",
-	"S_BIGFIREBAR10",
-	"S_BIGFIREBAR11",
-	"S_BIGFIREBAR12",
-	"S_BIGFIREBAR13",
-	"S_BIGFIREBAR14",
-	"S_BIGFIREBAR15",
-	"S_BIGFIREBAR16",
-
-	"S_CEZFLOWER",
-	"S_CEZPOLE",
-	"S_CEZBANNER1",
-	"S_CEZBANNER2",
-	"S_PINETREE",
-	"S_CEZBUSH1",
-	"S_CEZBUSH2",
-	"S_CANDLE",
-	"S_CANDLEPRICKET",
-	"S_FLAMEHOLDER",
-	"S_FIRETORCH",
-	"S_WAVINGFLAG",
-	"S_WAVINGFLAGSEG1",
-	"S_WAVINGFLAGSEG2",
-	"S_CRAWLASTATUE",
-	"S_FACESTABBERSTATUE",
-	"S_SUSPICIOUSFACESTABBERSTATUE_WAIT",
-	"S_SUSPICIOUSFACESTABBERSTATUE_BURST1",
-	"S_SUSPICIOUSFACESTABBERSTATUE_BURST2",
-	"S_BRAMBLES",
-
-	// Big Tumbleweed
-	"S_BIGTUMBLEWEED",
-	"S_BIGTUMBLEWEED_ROLL1",
-	"S_BIGTUMBLEWEED_ROLL2",
-	"S_BIGTUMBLEWEED_ROLL3",
-	"S_BIGTUMBLEWEED_ROLL4",
-	"S_BIGTUMBLEWEED_ROLL5",
-	"S_BIGTUMBLEWEED_ROLL6",
-	"S_BIGTUMBLEWEED_ROLL7",
-	"S_BIGTUMBLEWEED_ROLL8",
-
-	// Little Tumbleweed
-	"S_LITTLETUMBLEWEED",
-	"S_LITTLETUMBLEWEED_ROLL1",
-	"S_LITTLETUMBLEWEED_ROLL2",
-	"S_LITTLETUMBLEWEED_ROLL3",
-	"S_LITTLETUMBLEWEED_ROLL4",
-	"S_LITTLETUMBLEWEED_ROLL5",
-	"S_LITTLETUMBLEWEED_ROLL6",
-	"S_LITTLETUMBLEWEED_ROLL7",
-	"S_LITTLETUMBLEWEED_ROLL8",
-
-	// Cacti
-	"S_CACTI1",
-	"S_CACTI2",
-	"S_CACTI3",
-	"S_CACTI4",
-	"S_CACTI5",
-	"S_CACTI6",
-	"S_CACTI7",
-	"S_CACTI8",
-	"S_CACTI9",
-	"S_CACTI10",
-	"S_CACTI11",
-	"S_CACTITINYSEG",
-	"S_CACTISMALLSEG",
-
-	// Warning signs
-	"S_ARIDSIGN_CAUTION",
-	"S_ARIDSIGN_CACTI",
-	"S_ARIDSIGN_SHARPTURN",
-
-	// Oil lamp
-	"S_OILLAMP",
-	"S_OILLAMPFLARE",
-
-	// TNT barrel
-	"S_TNTBARREL_STND1",
-	"S_TNTBARREL_EXPL1",
-	"S_TNTBARREL_EXPL2",
-	"S_TNTBARREL_EXPL3",
-	"S_TNTBARREL_EXPL4",
-	"S_TNTBARREL_EXPL5",
-	"S_TNTBARREL_EXPL6",
-	"S_TNTBARREL_EXPL7",
-	"S_TNTBARREL_FLYING",
-
-	// TNT proximity shell
-	"S_PROXIMITY_TNT",
-	"S_PROXIMITY_TNT_TRIGGER1",
-	"S_PROXIMITY_TNT_TRIGGER2",
-	"S_PROXIMITY_TNT_TRIGGER3",
-	"S_PROXIMITY_TNT_TRIGGER4",
-	"S_PROXIMITY_TNT_TRIGGER5",
-	"S_PROXIMITY_TNT_TRIGGER6",
-	"S_PROXIMITY_TNT_TRIGGER7",
-	"S_PROXIMITY_TNT_TRIGGER8",
-	"S_PROXIMITY_TNT_TRIGGER9",
-	"S_PROXIMITY_TNT_TRIGGER10",
-	"S_PROXIMITY_TNT_TRIGGER11",
-	"S_PROXIMITY_TNT_TRIGGER12",
-	"S_PROXIMITY_TNT_TRIGGER13",
-	"S_PROXIMITY_TNT_TRIGGER14",
-	"S_PROXIMITY_TNT_TRIGGER15",
-	"S_PROXIMITY_TNT_TRIGGER16",
-	"S_PROXIMITY_TNT_TRIGGER17",
-	"S_PROXIMITY_TNT_TRIGGER18",
-	"S_PROXIMITY_TNT_TRIGGER19",
-	"S_PROXIMITY_TNT_TRIGGER20",
-	"S_PROXIMITY_TNT_TRIGGER21",
-	"S_PROXIMITY_TNT_TRIGGER22",
-	"S_PROXIMITY_TNT_TRIGGER23",
-
-	// Dust devil
-	"S_DUSTDEVIL",
-	"S_DUSTLAYER1",
-	"S_DUSTLAYER2",
-	"S_DUSTLAYER3",
-	"S_DUSTLAYER4",
-	"S_DUSTLAYER5",
-	"S_ARIDDUST1",
-	"S_ARIDDUST2",
-	"S_ARIDDUST3",
-
-	// Minecart
-	"S_MINECART_IDLE",
-	"S_MINECART_DTH1",
-	"S_MINECARTEND",
-	"S_MINECARTSEG_FRONT",
-	"S_MINECARTSEG_BACK",
-	"S_MINECARTSEG_LEFT",
-	"S_MINECARTSEG_RIGHT",
-	"S_MINECARTSIDEMARK1",
-	"S_MINECARTSIDEMARK2",
-	"S_MINECARTSPARK",
-
-	// Saloon door
-	"S_SALOONDOOR",
-	"S_SALOONDOORCENTER",
-
-	// Train cameo
-	"S_TRAINCAMEOSPAWNER_1",
-	"S_TRAINCAMEOSPAWNER_2",
-	"S_TRAINCAMEOSPAWNER_3",
-	"S_TRAINCAMEOSPAWNER_4",
-	"S_TRAINCAMEOSPAWNER_5",
-	"S_TRAINPUFFMAKER",
-
-	// Train
-	"S_TRAINDUST",
-	"S_TRAINSTEAM",
-
-	// Flame jet
-	"S_FLAMEJETSTND",
-	"S_FLAMEJETSTART",
-	"S_FLAMEJETSTOP",
-	"S_FLAMEJETFLAME1",
-	"S_FLAMEJETFLAME2",
-	"S_FLAMEJETFLAME3",
-	"S_FLAMEJETFLAME4",
-	"S_FLAMEJETFLAME5",
-	"S_FLAMEJETFLAME6",
-	"S_FLAMEJETFLAME7",
-	"S_FLAMEJETFLAME8",
-	"S_FLAMEJETFLAME9",
-
-	// Spinning flame jets
-	"S_FJSPINAXISA1", // Counter-clockwise
-	"S_FJSPINAXISA2",
-	"S_FJSPINAXISB1", // Clockwise
-	"S_FJSPINAXISB2",
-
-	// Blade's flame
-	"S_FLAMEJETFLAMEB1",
-	"S_FLAMEJETFLAMEB2",
-	"S_FLAMEJETFLAMEB3",
-
-	// Lavafall
-	"S_LAVAFALL_DORMANT",
-	"S_LAVAFALL_TELL",
-	"S_LAVAFALL_SHOOT",
-	"S_LAVAFALL_LAVA1",
-	"S_LAVAFALL_LAVA2",
-	"S_LAVAFALL_LAVA3",
-	"S_LAVAFALLROCK",
-
-	// Rollout Rock
-	"S_ROLLOUTSPAWN",
-	"S_ROLLOUTROCK",
-
-	// RVZ scenery
-	"S_BIGFERNLEAF",
-	"S_BIGFERN1",
-	"S_BIGFERN2",
-	"S_JUNGLEPALM",
-	"S_TORCHFLOWER",
-	"S_WALLVINE_LONG",
-	"S_WALLVINE_SHORT",
-
-	// Glaregoyles
-	"S_GLAREGOYLE",
-	"S_GLAREGOYLE_CHARGE",
-	"S_GLAREGOYLE_BLINK",
-	"S_GLAREGOYLE_HOLD",
-	"S_GLAREGOYLE_FIRE",
-	"S_GLAREGOYLE_LOOP",
-	"S_GLAREGOYLE_COOLDOWN",
-	"S_GLAREGOYLEUP",
-	"S_GLAREGOYLEUP_CHARGE",
-	"S_GLAREGOYLEUP_BLINK",
-	"S_GLAREGOYLEUP_HOLD",
-	"S_GLAREGOYLEUP_FIRE",
-	"S_GLAREGOYLEUP_LOOP",
-	"S_GLAREGOYLEUP_COOLDOWN",
-	"S_GLAREGOYLEDOWN",
-	"S_GLAREGOYLEDOWN_CHARGE",
-	"S_GLAREGOYLEDOWN_BLINK",
-	"S_GLAREGOYLEDOWN_HOLD",
-	"S_GLAREGOYLEDOWN_FIRE",
-	"S_GLAREGOYLEDOWN_LOOP",
-	"S_GLAREGOYLEDOWN_COOLDOWN",
-	"S_GLAREGOYLELONG",
-	"S_GLAREGOYLELONG_CHARGE",
-	"S_GLAREGOYLELONG_BLINK",
-	"S_GLAREGOYLELONG_HOLD",
-	"S_GLAREGOYLELONG_FIRE",
-	"S_GLAREGOYLELONG_LOOP",
-	"S_GLAREGOYLELONG_COOLDOWN",
-
-	// ATZ's Red Crystal/Target
-	"S_TARGET_IDLE",
-	"S_TARGET_HIT1",
-	"S_TARGET_HIT2",
-	"S_TARGET_RESPAWN",
-	"S_TARGET_ALLDONE",
-
-	// ATZ's green flame
-	"S_GREENFLAME",
-
-	// ATZ Blue Gargoyle
-	"S_BLUEGARGOYLE",
-
-	// Stalagmites
-	"S_STG0",
-	"S_STG1",
-	"S_STG2",
-	"S_STG3",
-	"S_STG4",
-	"S_STG5",
-	"S_STG6",
-	"S_STG7",
-	"S_STG8",
-	"S_STG9",
-
-	// Xmas-specific stuff
-	"S_XMASPOLE",
-	"S_CANDYCANE",
-	"S_SNOWMAN",    // normal
-	"S_SNOWMANHAT", // with hat + scarf
-	"S_LAMPPOST1",  // normal
-	"S_LAMPPOST2",  // with snow
-	"S_HANGSTAR",
-	"S_MISTLETOE",
-	// Xmas GFZ bushes
-	"S_XMASBLUEBERRYBUSH",
-	"S_XMASBERRYBUSH",
-	"S_XMASBUSH",
-	// FHZ
-	"S_FHZICE1",
-	"S_FHZICE2",
-	"S_ROSY_IDLE1",
-	"S_ROSY_IDLE2",
-	"S_ROSY_IDLE3",
-	"S_ROSY_IDLE4",
-	"S_ROSY_JUMP",
-	"S_ROSY_WALK",
-	"S_ROSY_HUG",
-	"S_ROSY_PAIN",
-	"S_ROSY_STND",
-	"S_ROSY_UNHAPPY",
-
-	// Halloween Scenery
-	// Pumpkins
-	"S_JACKO1",
-	"S_JACKO1OVERLAY_1",
-	"S_JACKO1OVERLAY_2",
-	"S_JACKO1OVERLAY_3",
-	"S_JACKO1OVERLAY_4",
-	"S_JACKO2",
-	"S_JACKO2OVERLAY_1",
-	"S_JACKO2OVERLAY_2",
-	"S_JACKO2OVERLAY_3",
-	"S_JACKO2OVERLAY_4",
-	"S_JACKO3",
-	"S_JACKO3OVERLAY_1",
-	"S_JACKO3OVERLAY_2",
-	"S_JACKO3OVERLAY_3",
-	"S_JACKO3OVERLAY_4",
-	// Dr Seuss Trees
-	"S_HHZTREE_TOP",
-	"S_HHZTREE_TRUNK",
-	"S_HHZTREE_LEAF",
-	// Mushroom
-	"S_HHZSHROOM_1",
-	"S_HHZSHROOM_2",
-	"S_HHZSHROOM_3",
-	"S_HHZSHROOM_4",
-	"S_HHZSHROOM_5",
-	"S_HHZSHROOM_6",
-	"S_HHZSHROOM_7",
-	"S_HHZSHROOM_8",
-	"S_HHZSHROOM_9",
-	"S_HHZSHROOM_10",
-	"S_HHZSHROOM_11",
-	"S_HHZSHROOM_12",
-	"S_HHZSHROOM_13",
-	"S_HHZSHROOM_14",
-	"S_HHZSHROOM_15",
-	"S_HHZSHROOM_16",
-	// Misc
-	"S_HHZGRASS",
-	"S_HHZTENT1",
-	"S_HHZTENT2",
-	"S_HHZSTALAGMITE_TALL",
-	"S_HHZSTALAGMITE_SHORT",
-
-	// Botanic Serenity's loads of scenery states
-	"S_BSZTALLFLOWER_RED",
-	"S_BSZTALLFLOWER_PURPLE",
-	"S_BSZTALLFLOWER_BLUE",
-	"S_BSZTALLFLOWER_CYAN",
-	"S_BSZTALLFLOWER_YELLOW",
-	"S_BSZTALLFLOWER_ORANGE",
-	"S_BSZFLOWER_RED",
-	"S_BSZFLOWER_PURPLE",
-	"S_BSZFLOWER_BLUE",
-	"S_BSZFLOWER_CYAN",
-	"S_BSZFLOWER_YELLOW",
-	"S_BSZFLOWER_ORANGE",
-	"S_BSZSHORTFLOWER_RED",
-	"S_BSZSHORTFLOWER_PURPLE",
-	"S_BSZSHORTFLOWER_BLUE",
-	"S_BSZSHORTFLOWER_CYAN",
-	"S_BSZSHORTFLOWER_YELLOW",
-	"S_BSZSHORTFLOWER_ORANGE",
-	"S_BSZTULIP_RED",
-	"S_BSZTULIP_PURPLE",
-	"S_BSZTULIP_BLUE",
-	"S_BSZTULIP_CYAN",
-	"S_BSZTULIP_YELLOW",
-	"S_BSZTULIP_ORANGE",
-	"S_BSZCLUSTER_RED",
-	"S_BSZCLUSTER_PURPLE",
-	"S_BSZCLUSTER_BLUE",
-	"S_BSZCLUSTER_CYAN",
-	"S_BSZCLUSTER_YELLOW",
-	"S_BSZCLUSTER_ORANGE",
-	"S_BSZBUSH_RED",
-	"S_BSZBUSH_PURPLE",
-	"S_BSZBUSH_BLUE",
-	"S_BSZBUSH_CYAN",
-	"S_BSZBUSH_YELLOW",
-	"S_BSZBUSH_ORANGE",
-	"S_BSZVINE_RED",
-	"S_BSZVINE_PURPLE",
-	"S_BSZVINE_BLUE",
-	"S_BSZVINE_CYAN",
-	"S_BSZVINE_YELLOW",
-	"S_BSZVINE_ORANGE",
-	"S_BSZSHRUB",
-	"S_BSZCLOVER",
-	"S_BIG_PALMTREE_TRUNK",
-	"S_BIG_PALMTREE_TOP",
-	"S_PALMTREE_TRUNK",
-	"S_PALMTREE_TOP",
-
-	"S_DBALL1",
-	"S_DBALL2",
-	"S_DBALL3",
-	"S_DBALL4",
-	"S_DBALL5",
-	"S_DBALL6",
-	"S_EGGSTATUE2",
-
-	// Shield Orb
-	"S_ARMA1",
-	"S_ARMA2",
-	"S_ARMA3",
-	"S_ARMA4",
-	"S_ARMA5",
-	"S_ARMA6",
-	"S_ARMA7",
-	"S_ARMA8",
-	"S_ARMA9",
-	"S_ARMA10",
-	"S_ARMA11",
-	"S_ARMA12",
-	"S_ARMA13",
-	"S_ARMA14",
-	"S_ARMA15",
-	"S_ARMA16",
-
-	"S_ARMF1",
-	"S_ARMF2",
-	"S_ARMF3",
-	"S_ARMF4",
-	"S_ARMF5",
-	"S_ARMF6",
-	"S_ARMF7",
-	"S_ARMF8",
-	"S_ARMF9",
-	"S_ARMF10",
-	"S_ARMF11",
-	"S_ARMF12",
-	"S_ARMF13",
-	"S_ARMF14",
-	"S_ARMF15",
-	"S_ARMF16",
-	"S_ARMF17",
-	"S_ARMF18",
-	"S_ARMF19",
-	"S_ARMF20",
-	"S_ARMF21",
-	"S_ARMF22",
-	"S_ARMF23",
-	"S_ARMF24",
-	"S_ARMF25",
-	"S_ARMF26",
-	"S_ARMF27",
-	"S_ARMF28",
-	"S_ARMF29",
-	"S_ARMF30",
-	"S_ARMF31",
-	"S_ARMF32",
-
-	"S_ARMB1",
-	"S_ARMB2",
-	"S_ARMB3",
-	"S_ARMB4",
-	"S_ARMB5",
-	"S_ARMB6",
-	"S_ARMB7",
-	"S_ARMB8",
-	"S_ARMB9",
-	"S_ARMB10",
-	"S_ARMB11",
-	"S_ARMB12",
-	"S_ARMB13",
-	"S_ARMB14",
-	"S_ARMB15",
-	"S_ARMB16",
-	"S_ARMB17",
-	"S_ARMB18",
-	"S_ARMB19",
-	"S_ARMB20",
-	"S_ARMB21",
-	"S_ARMB22",
-	"S_ARMB23",
-	"S_ARMB24",
-	"S_ARMB25",
-	"S_ARMB26",
-	"S_ARMB27",
-	"S_ARMB28",
-	"S_ARMB29",
-	"S_ARMB30",
-	"S_ARMB31",
-	"S_ARMB32",
-
-	"S_WIND1",
-	"S_WIND2",
-	"S_WIND3",
-	"S_WIND4",
-	"S_WIND5",
-	"S_WIND6",
-	"S_WIND7",
-	"S_WIND8",
-
-	"S_MAGN1",
-	"S_MAGN2",
-	"S_MAGN3",
-	"S_MAGN4",
-	"S_MAGN5",
-	"S_MAGN6",
-	"S_MAGN7",
-	"S_MAGN8",
-	"S_MAGN9",
-	"S_MAGN10",
-	"S_MAGN11",
-	"S_MAGN12",
-	"S_MAGN13",
-
-	"S_FORC1",
-	"S_FORC2",
-	"S_FORC3",
-	"S_FORC4",
-	"S_FORC5",
-	"S_FORC6",
-	"S_FORC7",
-	"S_FORC8",
-	"S_FORC9",
-	"S_FORC10",
-
-	"S_FORC11",
-	"S_FORC12",
-	"S_FORC13",
-	"S_FORC14",
-	"S_FORC15",
-	"S_FORC16",
-	"S_FORC17",
-	"S_FORC18",
-	"S_FORC19",
-	"S_FORC20",
-
-	"S_FORC21",
-
-	"S_ELEM1",
-	"S_ELEM2",
-	"S_ELEM3",
-	"S_ELEM4",
-	"S_ELEM5",
-	"S_ELEM6",
-	"S_ELEM7",
-	"S_ELEM8",
-	"S_ELEM9",
-	"S_ELEM10",
-	"S_ELEM11",
-	"S_ELEM12",
-
-	"S_ELEM13",
-	"S_ELEM14",
-
-	"S_ELEMF1",
-	"S_ELEMF2",
-	"S_ELEMF3",
-	"S_ELEMF4",
-	"S_ELEMF5",
-	"S_ELEMF6",
-	"S_ELEMF7",
-	"S_ELEMF8",
-	"S_ELEMF9",
-	"S_ELEMF10",
-
-	"S_PITY1",
-	"S_PITY2",
-	"S_PITY3",
-	"S_PITY4",
-	"S_PITY5",
-	"S_PITY6",
-	"S_PITY7",
-	"S_PITY8",
-	"S_PITY9",
-	"S_PITY10",
-	"S_PITY11",
-	"S_PITY12",
-
-	"S_FIRS1",
-	"S_FIRS2",
-	"S_FIRS3",
-	"S_FIRS4",
-	"S_FIRS5",
-	"S_FIRS6",
-	"S_FIRS7",
-	"S_FIRS8",
-	"S_FIRS9",
-
-	"S_FIRS10",
-	"S_FIRS11",
-
-	"S_FIRSB1",
-	"S_FIRSB2",
-	"S_FIRSB3",
-	"S_FIRSB4",
-	"S_FIRSB5",
-	"S_FIRSB6",
-	"S_FIRSB7",
-	"S_FIRSB8",
-	"S_FIRSB9",
-
-	"S_FIRSB10",
-
-	"S_BUBS1",
-	"S_BUBS2",
-	"S_BUBS3",
-	"S_BUBS4",
-	"S_BUBS5",
-	"S_BUBS6",
-	"S_BUBS7",
-	"S_BUBS8",
-	"S_BUBS9",
-
-	"S_BUBS10",
-	"S_BUBS11",
-
-	"S_BUBSB1",
-	"S_BUBSB2",
-	"S_BUBSB3",
-	"S_BUBSB4",
-
-	"S_BUBSB5",
-	"S_BUBSB6",
-
-	"S_ZAPS1",
-	"S_ZAPS2",
-	"S_ZAPS3",
-	"S_ZAPS4",
-	"S_ZAPS5",
-	"S_ZAPS6",
-	"S_ZAPS7",
-	"S_ZAPS8",
-	"S_ZAPS9",
-	"S_ZAPS10",
-	"S_ZAPS11",
-	"S_ZAPS12",
-	"S_ZAPS13", // blank frame
-	"S_ZAPS14",
-	"S_ZAPS15",
-	"S_ZAPS16",
-
-	"S_ZAPSB1", // blank frame
-	"S_ZAPSB2",
-	"S_ZAPSB3",
-	"S_ZAPSB4",
-	"S_ZAPSB5",
-	"S_ZAPSB6",
-	"S_ZAPSB7",
-	"S_ZAPSB8",
-	"S_ZAPSB9",
-	"S_ZAPSB10",
-	"S_ZAPSB11", // blank frame
-
-	//Thunder spark
-	"S_THUNDERCOIN_SPARK",
-
-	// Invincibility Sparkles
-	"S_IVSP",
-
-	// Super Sonic Spark
-	"S_SSPK1",
-	"S_SSPK2",
-	"S_SSPK3",
-	"S_SSPK4",
-	"S_SSPK5",
-
-	// Flicky-sized bubble
-	"S_FLICKY_BUBBLE",
-
-	// Bluebird
-	"S_FLICKY_01_OUT",
-	"S_FLICKY_01_FLAP1",
-	"S_FLICKY_01_FLAP2",
-	"S_FLICKY_01_FLAP3",
-	"S_FLICKY_01_STAND",
-	"S_FLICKY_01_CENTER",
-
-	// Rabbit
-	"S_FLICKY_02_OUT",
-	"S_FLICKY_02_AIM",
-	"S_FLICKY_02_HOP",
-	"S_FLICKY_02_UP",
-	"S_FLICKY_02_DOWN",
-	"S_FLICKY_02_STAND",
-	"S_FLICKY_02_CENTER",
-
-	// Chicken
-	"S_FLICKY_03_OUT",
-	"S_FLICKY_03_AIM",
-	"S_FLICKY_03_HOP",
-	"S_FLICKY_03_UP",
-	"S_FLICKY_03_FLAP1",
-	"S_FLICKY_03_FLAP2",
-	"S_FLICKY_03_STAND",
-	"S_FLICKY_03_CENTER",
-
-	// Seal
-	"S_FLICKY_04_OUT",
-	"S_FLICKY_04_AIM",
-	"S_FLICKY_04_HOP",
-	"S_FLICKY_04_UP",
-	"S_FLICKY_04_DOWN",
-	"S_FLICKY_04_SWIM1",
-	"S_FLICKY_04_SWIM2",
-	"S_FLICKY_04_SWIM3",
-	"S_FLICKY_04_SWIM4",
-	"S_FLICKY_04_STAND",
-	"S_FLICKY_04_CENTER",
-
-	// Pig
-	"S_FLICKY_05_OUT",
-	"S_FLICKY_05_AIM",
-	"S_FLICKY_05_HOP",
-	"S_FLICKY_05_UP",
-	"S_FLICKY_05_DOWN",
-	"S_FLICKY_05_STAND",
-	"S_FLICKY_05_CENTER",
-
-	// Chipmunk
-	"S_FLICKY_06_OUT",
-	"S_FLICKY_06_AIM",
-	"S_FLICKY_06_HOP",
-	"S_FLICKY_06_UP",
-	"S_FLICKY_06_DOWN",
-	"S_FLICKY_06_STAND",
-	"S_FLICKY_06_CENTER",
-
-	// Penguin
-	"S_FLICKY_07_OUT",
-	"S_FLICKY_07_AIML",
-	"S_FLICKY_07_HOPL",
-	"S_FLICKY_07_UPL",
-	"S_FLICKY_07_DOWNL",
-	"S_FLICKY_07_AIMR",
-	"S_FLICKY_07_HOPR",
-	"S_FLICKY_07_UPR",
-	"S_FLICKY_07_DOWNR",
-	"S_FLICKY_07_SWIM1",
-	"S_FLICKY_07_SWIM2",
-	"S_FLICKY_07_SWIM3",
-	"S_FLICKY_07_STAND",
-	"S_FLICKY_07_CENTER",
-
-	// Fish
-	"S_FLICKY_08_OUT",
-	"S_FLICKY_08_AIM",
-	"S_FLICKY_08_HOP",
-	"S_FLICKY_08_FLAP1",
-	"S_FLICKY_08_FLAP2",
-	"S_FLICKY_08_FLAP3",
-	"S_FLICKY_08_FLAP4",
-	"S_FLICKY_08_SWIM1",
-	"S_FLICKY_08_SWIM2",
-	"S_FLICKY_08_SWIM3",
-	"S_FLICKY_08_SWIM4",
-	"S_FLICKY_08_STAND",
-	"S_FLICKY_08_CENTER",
-
-	// Ram
-	"S_FLICKY_09_OUT",
-	"S_FLICKY_09_AIM",
-	"S_FLICKY_09_HOP",
-	"S_FLICKY_09_UP",
-	"S_FLICKY_09_DOWN",
-	"S_FLICKY_09_STAND",
-	"S_FLICKY_09_CENTER",
-
-	// Puffin
-	"S_FLICKY_10_OUT",
-	"S_FLICKY_10_FLAP1",
-	"S_FLICKY_10_FLAP2",
-	"S_FLICKY_10_STAND",
-	"S_FLICKY_10_CENTER",
-
-	// Cow
-	"S_FLICKY_11_OUT",
-	"S_FLICKY_11_AIM",
-	"S_FLICKY_11_RUN1",
-	"S_FLICKY_11_RUN2",
-	"S_FLICKY_11_RUN3",
-	"S_FLICKY_11_STAND",
-	"S_FLICKY_11_CENTER",
-
-	// Rat
-	"S_FLICKY_12_OUT",
-	"S_FLICKY_12_AIM",
-	"S_FLICKY_12_RUN1",
-	"S_FLICKY_12_RUN2",
-	"S_FLICKY_12_RUN3",
-	"S_FLICKY_12_STAND",
-	"S_FLICKY_12_CENTER",
-
-	// Bear
-	"S_FLICKY_13_OUT",
-	"S_FLICKY_13_AIM",
-	"S_FLICKY_13_HOP",
-	"S_FLICKY_13_UP",
-	"S_FLICKY_13_DOWN",
-	"S_FLICKY_13_STAND",
-	"S_FLICKY_13_CENTER",
-
-	// Dove
-	"S_FLICKY_14_OUT",
-	"S_FLICKY_14_FLAP1",
-	"S_FLICKY_14_FLAP2",
-	"S_FLICKY_14_FLAP3",
-	"S_FLICKY_14_STAND",
-	"S_FLICKY_14_CENTER",
-
-	// Cat
-	"S_FLICKY_15_OUT",
-	"S_FLICKY_15_AIM",
-	"S_FLICKY_15_HOP",
-	"S_FLICKY_15_UP",
-	"S_FLICKY_15_DOWN",
-	"S_FLICKY_15_STAND",
-	"S_FLICKY_15_CENTER",
-
-	// Canary
-	"S_FLICKY_16_OUT",
-	"S_FLICKY_16_FLAP1",
-	"S_FLICKY_16_FLAP2",
-	"S_FLICKY_16_FLAP3",
-	"S_FLICKY_16_STAND",
-	"S_FLICKY_16_CENTER",
-
-	// Spider
-	"S_SECRETFLICKY_01_OUT",
-	"S_SECRETFLICKY_01_AIM",
-	"S_SECRETFLICKY_01_HOP",
-	"S_SECRETFLICKY_01_UP",
-	"S_SECRETFLICKY_01_DOWN",
-	"S_SECRETFLICKY_01_STAND",
-	"S_SECRETFLICKY_01_CENTER",
-
-	// Bat
-	"S_SECRETFLICKY_02_OUT",
-	"S_SECRETFLICKY_02_FLAP1",
-	"S_SECRETFLICKY_02_FLAP2",
-	"S_SECRETFLICKY_02_FLAP3",
-	"S_SECRETFLICKY_02_STAND",
-	"S_SECRETFLICKY_02_CENTER",
-
-	// Fan
-	"S_FAN",
-	"S_FAN2",
-	"S_FAN3",
-	"S_FAN4",
-	"S_FAN5",
-
-	// Steam Riser
-	"S_STEAM1",
-	"S_STEAM2",
-	"S_STEAM3",
-	"S_STEAM4",
-	"S_STEAM5",
-	"S_STEAM6",
-	"S_STEAM7",
-	"S_STEAM8",
-
-	// Bumpers
-	"S_BUMPER",
-	"S_BUMPERHIT",
-
-	// Balloons
-	"S_BALLOON",
-	"S_BALLOONPOP1",
-	"S_BALLOONPOP2",
-	"S_BALLOONPOP3",
-	"S_BALLOONPOP4",
-	"S_BALLOONPOP5",
-	"S_BALLOONPOP6",
-
-	// Yellow Spring
-	"S_YELLOWSPRING",
-	"S_YELLOWSPRING2",
-	"S_YELLOWSPRING3",
-	"S_YELLOWSPRING4",
-	"S_YELLOWSPRING5",
-
-	// Red Spring
-	"S_REDSPRING",
-	"S_REDSPRING2",
-	"S_REDSPRING3",
-	"S_REDSPRING4",
-	"S_REDSPRING5",
-
-	// Blue Spring
-	"S_BLUESPRING",
-	"S_BLUESPRING2",
-	"S_BLUESPRING3",
-	"S_BLUESPRING4",
-	"S_BLUESPRING5",
-
-	// Yellow Diagonal Spring
-	"S_YDIAG1",
-	"S_YDIAG2",
-	"S_YDIAG3",
-	"S_YDIAG4",
-	"S_YDIAG5",
-	"S_YDIAG6",
-	"S_YDIAG7",
-	"S_YDIAG8",
-
-	// Red Diagonal Spring
-	"S_RDIAG1",
-	"S_RDIAG2",
-	"S_RDIAG3",
-	"S_RDIAG4",
-	"S_RDIAG5",
-	"S_RDIAG6",
-	"S_RDIAG7",
-	"S_RDIAG8",
-
-	// Blue Diagonal Spring
-	"S_BDIAG1",
-	"S_BDIAG2",
-	"S_BDIAG3",
-	"S_BDIAG4",
-	"S_BDIAG5",
-	"S_BDIAG6",
-	"S_BDIAG7",
-	"S_BDIAG8",
-
-	// Yellow Side Spring
-	"S_YHORIZ1",
-	"S_YHORIZ2",
-	"S_YHORIZ3",
-	"S_YHORIZ4",
-	"S_YHORIZ5",
-	"S_YHORIZ6",
-	"S_YHORIZ7",
-	"S_YHORIZ8",
-
-	// Red Side Spring
-	"S_RHORIZ1",
-	"S_RHORIZ2",
-	"S_RHORIZ3",
-	"S_RHORIZ4",
-	"S_RHORIZ5",
-	"S_RHORIZ6",
-	"S_RHORIZ7",
-	"S_RHORIZ8",
-
-	// Blue Side Spring
-	"S_BHORIZ1",
-	"S_BHORIZ2",
-	"S_BHORIZ3",
-	"S_BHORIZ4",
-	"S_BHORIZ5",
-	"S_BHORIZ6",
-	"S_BHORIZ7",
-	"S_BHORIZ8",
-
-	// Booster
-	"S_BOOSTERSOUND",
-	"S_YELLOWBOOSTERROLLER",
-	"S_YELLOWBOOSTERSEG_LEFT",
-	"S_YELLOWBOOSTERSEG_RIGHT",
-	"S_YELLOWBOOSTERSEG_FACE",
-	"S_REDBOOSTERROLLER",
-	"S_REDBOOSTERSEG_LEFT",
-	"S_REDBOOSTERSEG_RIGHT",
-	"S_REDBOOSTERSEG_FACE",
-
-	// Rain
-	"S_RAIN1",
-	"S_RAINRETURN",
-
-	// Snowflake
-	"S_SNOW1",
-	"S_SNOW2",
-	"S_SNOW3",
-
-	// Water Splish
-	"S_SPLISH1",
-	"S_SPLISH2",
-	"S_SPLISH3",
-	"S_SPLISH4",
-	"S_SPLISH5",
-	"S_SPLISH6",
-	"S_SPLISH7",
-	"S_SPLISH8",
-	"S_SPLISH9",
-
-	// Lava Splish
-	"S_LAVASPLISH",
-
-	// added water splash
-	"S_SPLASH1",
-	"S_SPLASH2",
-	"S_SPLASH3",
-
-	// lava/slime damage burn smoke
-	"S_SMOKE1",
-	"S_SMOKE2",
-	"S_SMOKE3",
-	"S_SMOKE4",
-	"S_SMOKE5",
-
-	// Bubbles
-	"S_SMALLBUBBLE",
-	"S_MEDIUMBUBBLE",
-	"S_LARGEBUBBLE1",
-	"S_LARGEBUBBLE2",
-	"S_EXTRALARGEBUBBLE", // breathable
-
-	"S_POP1", // Extra Large bubble goes POP!
-
-	"S_WATERZAP",
-
-	// Spindash dust
-	"S_SPINDUST1",
-	"S_SPINDUST2",
-	"S_SPINDUST3",
-	"S_SPINDUST4",
-	"S_SPINDUST_BUBBLE1",
-	"S_SPINDUST_BUBBLE2",
-	"S_SPINDUST_BUBBLE3",
-	"S_SPINDUST_BUBBLE4",
-	"S_SPINDUST_FIRE1",
-	"S_SPINDUST_FIRE2",
-	"S_SPINDUST_FIRE3",
-	"S_SPINDUST_FIRE4",
-
-	"S_FOG1",
-	"S_FOG2",
-	"S_FOG3",
-	"S_FOG4",
-	"S_FOG5",
-	"S_FOG6",
-	"S_FOG7",
-	"S_FOG8",
-	"S_FOG9",
-	"S_FOG10",
-	"S_FOG11",
-	"S_FOG12",
-	"S_FOG13",
-	"S_FOG14",
-
-	"S_SEED",
-
-	"S_PARTICLE",
-
-	// Score Logos
-	"S_SCRA", // 100
-	"S_SCRB", // 200
-	"S_SCRC", // 500
-	"S_SCRD", // 1000
-	"S_SCRE", // 10000
-	"S_SCRF", // 400 (mario)
-	"S_SCRG", // 800 (mario)
-	"S_SCRH", // 2000 (mario)
-	"S_SCRI", // 4000 (mario)
-	"S_SCRJ", // 8000 (mario)
-	"S_SCRK", // 1UP (mario)
-	"S_SCRL", // 10
-
-	// Drowning Timer Numbers
-	"S_ZERO1",
-	"S_ONE1",
-	"S_TWO1",
-	"S_THREE1",
-	"S_FOUR1",
-	"S_FIVE1",
-
-	"S_ZERO2",
-	"S_ONE2",
-	"S_TWO2",
-	"S_THREE2",
-	"S_FOUR2",
-	"S_FIVE2",
-
-	"S_FLIGHTINDICATOR",
-
-	"S_LOCKON1",
-	"S_LOCKON2",
-	"S_LOCKON3",
-	"S_LOCKON4",
-	"S_LOCKONINF1",
-	"S_LOCKONINF2",
-	"S_LOCKONINF3",
-	"S_LOCKONINF4",
-
-	// Tag Sign
-	"S_TTAG",
-
-	// Got Flag Sign
-	"S_GOTFLAG",
-
-	// Finish flag
-	"S_FINISHFLAG",
-
-	"S_CORK",
-	"S_LHRT",
-
-	// Red Ring
-	"S_RRNG1",
-	"S_RRNG2",
-	"S_RRNG3",
-	"S_RRNG4",
-	"S_RRNG5",
-	"S_RRNG6",
-	"S_RRNG7",
-
-	// Weapon Ring Ammo
-	"S_BOUNCERINGAMMO",
-	"S_RAILRINGAMMO",
-	"S_INFINITYRINGAMMO",
-	"S_AUTOMATICRINGAMMO",
-	"S_EXPLOSIONRINGAMMO",
-	"S_SCATTERRINGAMMO",
-	"S_GRENADERINGAMMO",
-
-	// Weapon pickup
-	"S_BOUNCEPICKUP",
-	"S_BOUNCEPICKUPFADE1",
-	"S_BOUNCEPICKUPFADE2",
-	"S_BOUNCEPICKUPFADE3",
-	"S_BOUNCEPICKUPFADE4",
-	"S_BOUNCEPICKUPFADE5",
-	"S_BOUNCEPICKUPFADE6",
-	"S_BOUNCEPICKUPFADE7",
-	"S_BOUNCEPICKUPFADE8",
-
-	"S_RAILPICKUP",
-	"S_RAILPICKUPFADE1",
-	"S_RAILPICKUPFADE2",
-	"S_RAILPICKUPFADE3",
-	"S_RAILPICKUPFADE4",
-	"S_RAILPICKUPFADE5",
-	"S_RAILPICKUPFADE6",
-	"S_RAILPICKUPFADE7",
-	"S_RAILPICKUPFADE8",
-
-	"S_AUTOPICKUP",
-	"S_AUTOPICKUPFADE1",
-	"S_AUTOPICKUPFADE2",
-	"S_AUTOPICKUPFADE3",
-	"S_AUTOPICKUPFADE4",
-	"S_AUTOPICKUPFADE5",
-	"S_AUTOPICKUPFADE6",
-	"S_AUTOPICKUPFADE7",
-	"S_AUTOPICKUPFADE8",
-
-	"S_EXPLODEPICKUP",
-	"S_EXPLODEPICKUPFADE1",
-	"S_EXPLODEPICKUPFADE2",
-	"S_EXPLODEPICKUPFADE3",
-	"S_EXPLODEPICKUPFADE4",
-	"S_EXPLODEPICKUPFADE5",
-	"S_EXPLODEPICKUPFADE6",
-	"S_EXPLODEPICKUPFADE7",
-	"S_EXPLODEPICKUPFADE8",
-
-	"S_SCATTERPICKUP",
-	"S_SCATTERPICKUPFADE1",
-	"S_SCATTERPICKUPFADE2",
-	"S_SCATTERPICKUPFADE3",
-	"S_SCATTERPICKUPFADE4",
-	"S_SCATTERPICKUPFADE5",
-	"S_SCATTERPICKUPFADE6",
-	"S_SCATTERPICKUPFADE7",
-	"S_SCATTERPICKUPFADE8",
-
-	"S_GRENADEPICKUP",
-	"S_GRENADEPICKUPFADE1",
-	"S_GRENADEPICKUPFADE2",
-	"S_GRENADEPICKUPFADE3",
-	"S_GRENADEPICKUPFADE4",
-	"S_GRENADEPICKUPFADE5",
-	"S_GRENADEPICKUPFADE6",
-	"S_GRENADEPICKUPFADE7",
-	"S_GRENADEPICKUPFADE8",
-
-	// Thrown Weapon Rings
-	"S_THROWNBOUNCE1",
-	"S_THROWNBOUNCE2",
-	"S_THROWNBOUNCE3",
-	"S_THROWNBOUNCE4",
-	"S_THROWNBOUNCE5",
-	"S_THROWNBOUNCE6",
-	"S_THROWNBOUNCE7",
-	"S_THROWNINFINITY1",
-	"S_THROWNINFINITY2",
-	"S_THROWNINFINITY3",
-	"S_THROWNINFINITY4",
-	"S_THROWNINFINITY5",
-	"S_THROWNINFINITY6",
-	"S_THROWNINFINITY7",
-	"S_THROWNAUTOMATIC1",
-	"S_THROWNAUTOMATIC2",
-	"S_THROWNAUTOMATIC3",
-	"S_THROWNAUTOMATIC4",
-	"S_THROWNAUTOMATIC5",
-	"S_THROWNAUTOMATIC6",
-	"S_THROWNAUTOMATIC7",
-	"S_THROWNEXPLOSION1",
-	"S_THROWNEXPLOSION2",
-	"S_THROWNEXPLOSION3",
-	"S_THROWNEXPLOSION4",
-	"S_THROWNEXPLOSION5",
-	"S_THROWNEXPLOSION6",
-	"S_THROWNEXPLOSION7",
-	"S_THROWNGRENADE1",
-	"S_THROWNGRENADE2",
-	"S_THROWNGRENADE3",
-	"S_THROWNGRENADE4",
-	"S_THROWNGRENADE5",
-	"S_THROWNGRENADE6",
-	"S_THROWNGRENADE7",
-	"S_THROWNGRENADE8",
-	"S_THROWNGRENADE9",
-	"S_THROWNGRENADE10",
-	"S_THROWNGRENADE11",
-	"S_THROWNGRENADE12",
-	"S_THROWNGRENADE13",
-	"S_THROWNGRENADE14",
-	"S_THROWNGRENADE15",
-	"S_THROWNGRENADE16",
-	"S_THROWNGRENADE17",
-	"S_THROWNGRENADE18",
-	"S_THROWNSCATTER",
-
-	"S_RINGEXPLODE",
-
-	"S_COIN1",
-	"S_COIN2",
-	"S_COIN3",
-	"S_COINSPARKLE1",
-	"S_COINSPARKLE2",
-	"S_COINSPARKLE3",
-	"S_COINSPARKLE4",
-	"S_GOOMBA1",
-	"S_GOOMBA1B",
-	"S_GOOMBA2",
-	"S_GOOMBA3",
-	"S_GOOMBA4",
-	"S_GOOMBA5",
-	"S_GOOMBA6",
-	"S_GOOMBA7",
-	"S_GOOMBA8",
-	"S_GOOMBA9",
-	"S_GOOMBA_DEAD",
-	"S_BLUEGOOMBA1",
-	"S_BLUEGOOMBA1B",
-	"S_BLUEGOOMBA2",
-	"S_BLUEGOOMBA3",
-	"S_BLUEGOOMBA4",
-	"S_BLUEGOOMBA5",
-	"S_BLUEGOOMBA6",
-	"S_BLUEGOOMBA7",
-	"S_BLUEGOOMBA8",
-	"S_BLUEGOOMBA9",
-	"S_BLUEGOOMBA_DEAD",
-
-	// Mario-specific stuff
-	"S_FIREFLOWER1",
-	"S_FIREFLOWER2",
-	"S_FIREFLOWER3",
-	"S_FIREFLOWER4",
-	"S_FIREBALL",
-	"S_FIREBALLTRAIL1",
-	"S_FIREBALLTRAIL2",
-	"S_SHELL",
-	"S_PUMA_START1",
-	"S_PUMA_START2",
-	"S_PUMA_UP1",
-	"S_PUMA_UP2",
-	"S_PUMA_UP3",
-	"S_PUMA_DOWN1",
-	"S_PUMA_DOWN2",
-	"S_PUMA_DOWN3",
-	"S_PUMATRAIL1",
-	"S_PUMATRAIL2",
-	"S_PUMATRAIL3",
-	"S_PUMATRAIL4",
-	"S_HAMMER",
-	"S_KOOPA1",
-	"S_KOOPA2",
-	"S_KOOPAFLAME1",
-	"S_KOOPAFLAME2",
-	"S_KOOPAFLAME3",
-	"S_AXE1",
-	"S_AXE2",
-	"S_AXE3",
-	"S_MARIOBUSH1",
-	"S_MARIOBUSH2",
-	"S_TOAD",
-
-	// Nights-specific stuff
-	"S_NIGHTSDRONE_MAN1",
-	"S_NIGHTSDRONE_MAN2",
-	"S_NIGHTSDRONE_SPARKLING1",
-	"S_NIGHTSDRONE_SPARKLING2",
-	"S_NIGHTSDRONE_SPARKLING3",
-	"S_NIGHTSDRONE_SPARKLING4",
-	"S_NIGHTSDRONE_SPARKLING5",
-	"S_NIGHTSDRONE_SPARKLING6",
-	"S_NIGHTSDRONE_SPARKLING7",
-	"S_NIGHTSDRONE_SPARKLING8",
-	"S_NIGHTSDRONE_SPARKLING9",
-	"S_NIGHTSDRONE_SPARKLING10",
-	"S_NIGHTSDRONE_SPARKLING11",
-	"S_NIGHTSDRONE_SPARKLING12",
-	"S_NIGHTSDRONE_SPARKLING13",
-	"S_NIGHTSDRONE_SPARKLING14",
-	"S_NIGHTSDRONE_SPARKLING15",
-	"S_NIGHTSDRONE_SPARKLING16",
-	"S_NIGHTSDRONE_GOAL1",
-	"S_NIGHTSDRONE_GOAL2",
-	"S_NIGHTSDRONE_GOAL3",
-	"S_NIGHTSDRONE_GOAL4",
-
-	"S_NIGHTSPARKLE1",
-	"S_NIGHTSPARKLE2",
-	"S_NIGHTSPARKLE3",
-	"S_NIGHTSPARKLE4",
-	"S_NIGHTSPARKLESUPER1",
-	"S_NIGHTSPARKLESUPER2",
-	"S_NIGHTSPARKLESUPER3",
-	"S_NIGHTSPARKLESUPER4",
-	"S_NIGHTSLOOPHELPER",
-
-	// NiGHTS bumper
-	"S_NIGHTSBUMPER1",
-	"S_NIGHTSBUMPER2",
-	"S_NIGHTSBUMPER3",
-	"S_NIGHTSBUMPER4",
-	"S_NIGHTSBUMPER5",
-	"S_NIGHTSBUMPER6",
-	"S_NIGHTSBUMPER7",
-	"S_NIGHTSBUMPER8",
-	"S_NIGHTSBUMPER9",
-	"S_NIGHTSBUMPER10",
-	"S_NIGHTSBUMPER11",
-	"S_NIGHTSBUMPER12",
-
-	"S_HOOP",
-	"S_HOOP_XMASA",
-	"S_HOOP_XMASB",
-
-	"S_NIGHTSCORE10",
-	"S_NIGHTSCORE20",
-	"S_NIGHTSCORE30",
-	"S_NIGHTSCORE40",
-	"S_NIGHTSCORE50",
-	"S_NIGHTSCORE60",
-	"S_NIGHTSCORE70",
-	"S_NIGHTSCORE80",
-	"S_NIGHTSCORE90",
-	"S_NIGHTSCORE100",
-	"S_NIGHTSCORE10_2",
-	"S_NIGHTSCORE20_2",
-	"S_NIGHTSCORE30_2",
-	"S_NIGHTSCORE40_2",
-	"S_NIGHTSCORE50_2",
-	"S_NIGHTSCORE60_2",
-	"S_NIGHTSCORE70_2",
-	"S_NIGHTSCORE80_2",
-	"S_NIGHTSCORE90_2",
-	"S_NIGHTSCORE100_2",
-
-	// NiGHTS Paraloop Powerups
-	"S_NIGHTSSUPERLOOP",
-	"S_NIGHTSDRILLREFILL",
-	"S_NIGHTSHELPER",
-	"S_NIGHTSEXTRATIME",
-	"S_NIGHTSLINKFREEZE",
-	"S_EGGCAPSULE",
-
-	// Orbiting Chaos Emeralds
-	"S_ORBITEM1",
-	"S_ORBITEM2",
-	"S_ORBITEM3",
-	"S_ORBITEM4",
-	"S_ORBITEM5",
-	"S_ORBITEM6",
-	"S_ORBITEM7",
-	"S_ORBITEM8",
-	"S_ORBIDYA1",
-	"S_ORBIDYA2",
-	"S_ORBIDYA3",
-	"S_ORBIDYA4",
-	"S_ORBIDYA5",
-
-	// "Flicky" helper
-	"S_NIGHTOPIANHELPER1",
-	"S_NIGHTOPIANHELPER2",
-	"S_NIGHTOPIANHELPER3",
-	"S_NIGHTOPIANHELPER4",
-	"S_NIGHTOPIANHELPER5",
-	"S_NIGHTOPIANHELPER6",
-	"S_NIGHTOPIANHELPER7",
-	"S_NIGHTOPIANHELPER8",
-	"S_NIGHTOPIANHELPER9",
-
-	// Nightopian
-	"S_PIAN0",
-	"S_PIAN1",
-	"S_PIAN2",
-	"S_PIAN3",
-	"S_PIAN4",
-	"S_PIAN5",
-	"S_PIAN6",
-	"S_PIANSING",
-
-	// Shleep
-	"S_SHLEEP1",
-	"S_SHLEEP2",
-	"S_SHLEEP3",
-	"S_SHLEEP4",
-	"S_SHLEEPBOUNCE1",
-	"S_SHLEEPBOUNCE2",
-	"S_SHLEEPBOUNCE3",
-
-	// Secret badniks and hazards, shhhh
-	"S_PENGUINATOR_LOOK",
-	"S_PENGUINATOR_WADDLE1",
-	"S_PENGUINATOR_WADDLE2",
-	"S_PENGUINATOR_WADDLE3",
-	"S_PENGUINATOR_WADDLE4",
-	"S_PENGUINATOR_SLIDE1",
-	"S_PENGUINATOR_SLIDE2",
-	"S_PENGUINATOR_SLIDE3",
-	"S_PENGUINATOR_SLIDE4",
-	"S_PENGUINATOR_SLIDE5",
-
-	"S_POPHAT_LOOK",
-	"S_POPHAT_SHOOT1",
-	"S_POPHAT_SHOOT2",
-	"S_POPHAT_SHOOT3",
-	"S_POPHAT_SHOOT4",
-	"S_POPSHOT",
-	"S_POPSHOT_TRAIL",
-
-	"S_HIVEELEMENTAL_LOOK",
-	"S_HIVEELEMENTAL_PREPARE1",
-	"S_HIVEELEMENTAL_PREPARE2",
-	"S_HIVEELEMENTAL_SHOOT1",
-	"S_HIVEELEMENTAL_SHOOT2",
-	"S_HIVEELEMENTAL_DORMANT",
-	"S_HIVEELEMENTAL_PAIN",
-	"S_HIVEELEMENTAL_DIE1",
-	"S_HIVEELEMENTAL_DIE2",
-	"S_HIVEELEMENTAL_DIE3",
-
-	"S_BUMBLEBORE_SPAWN",
-	"S_BUMBLEBORE_LOOK1",
-	"S_BUMBLEBORE_LOOK2",
-	"S_BUMBLEBORE_FLY1",
-	"S_BUMBLEBORE_FLY2",
-	"S_BUMBLEBORE_RAISE",
-	"S_BUMBLEBORE_FALL1",
-	"S_BUMBLEBORE_FALL2",
-	"S_BUMBLEBORE_STUCK1",
-	"S_BUMBLEBORE_STUCK2",
-	"S_BUMBLEBORE_DIE",
-
-	"S_BUGGLEIDLE",
-	"S_BUGGLEFLY",
-
-	"S_SMASHSPIKE_FLOAT",
-	"S_SMASHSPIKE_EASE1",
-	"S_SMASHSPIKE_EASE2",
-	"S_SMASHSPIKE_FALL",
-	"S_SMASHSPIKE_STOMP1",
-	"S_SMASHSPIKE_STOMP2",
-	"S_SMASHSPIKE_RISE1",
-	"S_SMASHSPIKE_RISE2",
-
-	"S_CACO_LOOK",
-	"S_CACO_WAKE1",
-	"S_CACO_WAKE2",
-	"S_CACO_WAKE3",
-	"S_CACO_WAKE4",
-	"S_CACO_ROAR",
-	"S_CACO_CHASE",
-	"S_CACO_CHASE_REPEAT",
-	"S_CACO_RANDOM",
-	"S_CACO_PREPARE_SOUND",
-	"S_CACO_PREPARE1",
-	"S_CACO_PREPARE2",
-	"S_CACO_PREPARE3",
-	"S_CACO_SHOOT_SOUND",
-	"S_CACO_SHOOT1",
-	"S_CACO_SHOOT2",
-	"S_CACO_CLOSE",
-	"S_CACO_DIE_FLAGS",
-	"S_CACO_DIE_GIB1",
-	"S_CACO_DIE_GIB2",
-	"S_CACO_DIE_SCREAM",
-	"S_CACO_DIE_SHATTER",
-	"S_CACO_DIE_FALL",
-	"S_CACOSHARD_RANDOMIZE",
-	"S_CACOSHARD1_1",
-	"S_CACOSHARD1_2",
-	"S_CACOSHARD2_1",
-	"S_CACOSHARD2_2",
-	"S_CACOFIRE1",
-	"S_CACOFIRE2",
-	"S_CACOFIRE3",
-	"S_CACOFIRE_EXPLODE1",
-	"S_CACOFIRE_EXPLODE2",
-	"S_CACOFIRE_EXPLODE3",
-	"S_CACOFIRE_EXPLODE4",
-
-	"S_SPINBOBERT_MOVE_FLIPUP",
-	"S_SPINBOBERT_MOVE_UP",
-	"S_SPINBOBERT_MOVE_FLIPDOWN",
-	"S_SPINBOBERT_MOVE_DOWN",
-	"S_SPINBOBERT_FIRE_MOVE",
-	"S_SPINBOBERT_FIRE_GHOST",
-	"S_SPINBOBERT_FIRE_TRAIL1",
-	"S_SPINBOBERT_FIRE_TRAIL2",
-	"S_SPINBOBERT_FIRE_TRAIL3",
-
-	"S_HANGSTER_LOOK",
-	"S_HANGSTER_SWOOP1",
-	"S_HANGSTER_SWOOP2",
-	"S_HANGSTER_ARC1",
-	"S_HANGSTER_ARC2",
-	"S_HANGSTER_ARC3",
-	"S_HANGSTER_FLY1",
-	"S_HANGSTER_FLY2",
-	"S_HANGSTER_FLY3",
-	"S_HANGSTER_FLY4",
-	"S_HANGSTER_FLYREPEAT",
-	"S_HANGSTER_ARCUP1",
-	"S_HANGSTER_ARCUP2",
-	"S_HANGSTER_ARCUP3",
-	"S_HANGSTER_RETURN1",
-	"S_HANGSTER_RETURN2",
-	"S_HANGSTER_RETURN3",
-
-	"S_CRUMBLE1",
-	"S_CRUMBLE2",
-
-	// Spark
-	"S_SPRK1",
-	"S_SPRK2",
-	"S_SPRK3",
-
-	// Robot Explosion
-	"S_XPLD_FLICKY",
-	"S_XPLD1",
-	"S_XPLD2",
-	"S_XPLD3",
-	"S_XPLD4",
-	"S_XPLD5",
-	"S_XPLD6",
-	"S_XPLD_EGGTRAP",
-
-	// Underwater Explosion
-	"S_WPLD1",
-	"S_WPLD2",
-	"S_WPLD3",
-	"S_WPLD4",
-	"S_WPLD5",
-	"S_WPLD6",
-
-	"S_DUST1",
-	"S_DUST2",
-	"S_DUST3",
-	"S_DUST4",
-
-	"S_ROCKSPAWN",
-
-	"S_ROCKCRUMBLEA",
-	"S_ROCKCRUMBLEB",
-	"S_ROCKCRUMBLEC",
-	"S_ROCKCRUMBLED",
-	"S_ROCKCRUMBLEE",
-	"S_ROCKCRUMBLEF",
-	"S_ROCKCRUMBLEG",
-	"S_ROCKCRUMBLEH",
-	"S_ROCKCRUMBLEI",
-	"S_ROCKCRUMBLEJ",
-	"S_ROCKCRUMBLEK",
-	"S_ROCKCRUMBLEL",
-	"S_ROCKCRUMBLEM",
-	"S_ROCKCRUMBLEN",
-	"S_ROCKCRUMBLEO",
-	"S_ROCKCRUMBLEP",
-
-	// Level debris
-	"S_GFZDEBRIS",
-	"S_BRICKDEBRIS",
-	"S_WOODDEBRIS",
-	"S_REDBRICKDEBRIS",
-	"S_BLUEBRICKDEBRIS",
-	"S_YELLOWBRICKDEBRIS",
-
-#ifdef SEENAMES
-	"S_NAMECHECK",
-#endif
-};
-
-// RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1",
-// I am leaving the prefixes solely for clarity to programmers,
-// because sadly no one remembers this place while searching for full state names.
-static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity testing later.
-	"MT_NULL",
-	"MT_UNKNOWN",
-
-	"MT_THOK", // Thok! mobj
-	"MT_PLAYER",
-	"MT_TAILSOVERLAY", // c:
-	"MT_METALJETFUME",
-
-	// Enemies
-	"MT_BLUECRAWLA", // Crawla (Blue)
-	"MT_REDCRAWLA", // Crawla (Red)
-	"MT_GFZFISH", // SDURF
-	"MT_GOLDBUZZ", // Buzz (Gold)
-	"MT_REDBUZZ", // Buzz (Red)
-	"MT_JETTBOMBER", // Jetty-Syn Bomber
-	"MT_JETTGUNNER", // Jetty-Syn Gunner
-	"MT_CRAWLACOMMANDER", // Crawla Commander
-	"MT_DETON", // Deton
-	"MT_SKIM", // Skim mine dropper
-	"MT_TURRET", // Industrial Turret
-	"MT_POPUPTURRET", // Pop-Up Turret
-	"MT_SPINCUSHION", // Spincushion
-	"MT_CRUSHSTACEAN", // Crushstacean
-	"MT_CRUSHCLAW", // Big meaty claw
-	"MT_CRUSHCHAIN", // Chain
-	"MT_BANPYURA", // Banpyura
-	"MT_BANPSPRING", // Banpyura spring
-	"MT_JETJAW", // Jet Jaw
-	"MT_SNAILER", // Snailer
-	"MT_VULTURE", // BASH
-	"MT_POINTY", // Pointy
-	"MT_POINTYBALL", // Pointy Ball
-	"MT_ROBOHOOD", // Robo-Hood
-	"MT_FACESTABBER", // Castlebot Facestabber
-	"MT_FACESTABBERSPEAR", // Castlebot Facestabber spear aura
-	"MT_EGGGUARD", // Egg Guard
-	"MT_EGGSHIELD", // Egg Guard's shield
-	"MT_GSNAPPER", // Green Snapper
-	"MT_SNAPPER_LEG", // Green Snapper leg
-	"MT_SNAPPER_HEAD", // Green Snapper head
-	"MT_MINUS", // Minus
-	"MT_MINUSDIRT", // Minus dirt
-	"MT_SPRINGSHELL", // Spring Shell
-	"MT_YELLOWSHELL", // Spring Shell (yellow)
-	"MT_UNIDUS", // Unidus
-	"MT_UNIBALL", // Unidus Ball
-	"MT_CANARIVORE", // Canarivore
-	"MT_CANARIVORE_GAS", // Canarivore gas
-	"MT_PYREFLY", // Pyre Fly
-	"MT_PYREFLY_FIRE", // Pyre Fly fire
-	"MT_PTERABYTESPAWNER", // Pterabyte spawner
-	"MT_PTERABYTEWAYPOINT", // Pterabyte waypoint
-	"MT_PTERABYTE", // Pterabyte
-	"MT_DRAGONBOMBER", // Dragonbomber
-	"MT_DRAGONWING", // Dragonbomber wing
-	"MT_DRAGONTAIL", // Dragonbomber tail segment
-	"MT_DRAGONMINE", // Dragonbomber mine
-
-	// Generic Boss Items
-	"MT_BOSSEXPLODE",
-	"MT_SONIC3KBOSSEXPLODE",
-	"MT_BOSSFLYPOINT",
-	"MT_EGGTRAP",
-	"MT_BOSS3WAYPOINT",
-	"MT_BOSS9GATHERPOINT",
-	"MT_BOSSJUNK",
-
-	// Boss 1
-	"MT_EGGMOBILE",
-	"MT_JETFUME1",
-	"MT_EGGMOBILE_BALL",
-	"MT_EGGMOBILE_TARGET",
-	"MT_EGGMOBILE_FIRE",
-
-	// Boss 2
-	"MT_EGGMOBILE2",
-	"MT_EGGMOBILE2_POGO",
-	"MT_GOOP",
-	"MT_GOOPTRAIL",
-
-	// Boss 3
-	"MT_EGGMOBILE3",
-	"MT_FAKEMOBILE",
-	"MT_SHOCKWAVE",
-
-	// Boss 4
-	"MT_EGGMOBILE4",
-	"MT_EGGMOBILE4_MACE",
-	"MT_JETFLAME",
-	"MT_EGGROBO1",
-	"MT_EGGROBO1JET",
-
-	// Boss 5
-	"MT_FANG",
-	"MT_BROKENROBOT",
-	"MT_VWREF",
-	"MT_VWREB",
-	"MT_PROJECTORLIGHT",
-	"MT_FBOMB",
-	"MT_TNTDUST", // also used by barrel
-	"MT_FSGNA",
-	"MT_FSGNB",
-	"MT_FANGWAYPOINT",
-
-	// Black Eggman (Boss 7)
-	"MT_BLACKEGGMAN",
-	"MT_BLACKEGGMAN_HELPER",
-	"MT_BLACKEGGMAN_GOOPFIRE",
-	"MT_BLACKEGGMAN_MISSILE",
-
-	// New Very-Last-Minute 2.1 Brak Eggman (Cy-Brak-demon)
-	"MT_CYBRAKDEMON",
-	"MT_CYBRAKDEMON_ELECTRIC_BARRIER",
-	"MT_CYBRAKDEMON_MISSILE",
-	"MT_CYBRAKDEMON_FLAMESHOT",
-	"MT_CYBRAKDEMON_FLAMEREST",
-	"MT_CYBRAKDEMON_TARGET_RETICULE",
-	"MT_CYBRAKDEMON_TARGET_DOT",
-	"MT_CYBRAKDEMON_NAPALM_BOMB_LARGE",
-	"MT_CYBRAKDEMON_NAPALM_BOMB_SMALL",
-	"MT_CYBRAKDEMON_NAPALM_FLAMES",
-	"MT_CYBRAKDEMON_VILE_EXPLOSION",
-
-	// Metal Sonic (Boss 9)
-	"MT_METALSONIC_RACE",
-	"MT_METALSONIC_BATTLE",
-	"MT_MSSHIELD_FRONT",
-	"MT_MSGATHER",
-
-	// Collectible Items
-	"MT_RING",
-	"MT_FLINGRING", // Lost ring
-	"MT_BLUESPHERE",  // Blue sphere for special stages
-	"MT_FLINGBLUESPHERE", // Lost blue sphere
-	"MT_BOMBSPHERE",
-	"MT_REDTEAMRING",  //Rings collectable by red team.
-	"MT_BLUETEAMRING", //Rings collectable by blue team.
-	"MT_TOKEN", // Special Stage token for special stage
-	"MT_REDFLAG", // Red CTF Flag
-	"MT_BLUEFLAG", // Blue CTF Flag
-	"MT_EMBLEM",
-	"MT_EMERALD1",
-	"MT_EMERALD2",
-	"MT_EMERALD3",
-	"MT_EMERALD4",
-	"MT_EMERALD5",
-	"MT_EMERALD6",
-	"MT_EMERALD7",
-	"MT_EMERHUNT", // Emerald Hunt
-	"MT_EMERALDSPAWN", // Emerald spawner w/ delay
-	"MT_FLINGEMERALD", // Lost emerald
-
-	// Springs and others
-	"MT_FAN",
-	"MT_STEAM",
-	"MT_BUMPER",
-	"MT_BALLOON",
-
-	"MT_YELLOWSPRING",
-	"MT_REDSPRING",
-	"MT_BLUESPRING",
-	"MT_YELLOWDIAG",
-	"MT_REDDIAG",
-	"MT_BLUEDIAG",
-	"MT_YELLOWHORIZ",
-	"MT_REDHORIZ",
-	"MT_BLUEHORIZ",
-
-	"MT_BOOSTERSEG",
-	"MT_BOOSTERROLLER",
-	"MT_YELLOWBOOSTER",
-	"MT_REDBOOSTER",
-
-	// Interactive Objects
-	"MT_BUBBLES", // Bubble source
-	"MT_SIGN", // Level end sign
-	"MT_SPIKEBALL", // Spike Ball
-	"MT_SPINFIRE",
-	"MT_SPIKE",
-	"MT_WALLSPIKE",
-	"MT_WALLSPIKEBASE",
-	"MT_STARPOST",
-	"MT_BIGMINE",
-	"MT_BLASTEXECUTOR",
-	"MT_CANNONLAUNCHER",
-
-	// Monitor miscellany
-	"MT_BOXSPARKLE",
-
-	// Monitor boxes -- regular
-	"MT_RING_BOX",
-	"MT_PITY_BOX",
-	"MT_ATTRACT_BOX",
-	"MT_FORCE_BOX",
-	"MT_ARMAGEDDON_BOX",
-	"MT_WHIRLWIND_BOX",
-	"MT_ELEMENTAL_BOX",
-	"MT_SNEAKERS_BOX",
-	"MT_INVULN_BOX",
-	"MT_1UP_BOX",
-	"MT_EGGMAN_BOX",
-	"MT_MIXUP_BOX",
-	"MT_MYSTERY_BOX",
-	"MT_GRAVITY_BOX",
-	"MT_RECYCLER_BOX",
-	"MT_SCORE1K_BOX",
-	"MT_SCORE10K_BOX",
-	"MT_FLAMEAURA_BOX",
-	"MT_BUBBLEWRAP_BOX",
-	"MT_THUNDERCOIN_BOX",
-
-	// Monitor boxes -- repeating (big) boxes
-	"MT_PITY_GOLDBOX",
-	"MT_ATTRACT_GOLDBOX",
-	"MT_FORCE_GOLDBOX",
-	"MT_ARMAGEDDON_GOLDBOX",
-	"MT_WHIRLWIND_GOLDBOX",
-	"MT_ELEMENTAL_GOLDBOX",
-	"MT_SNEAKERS_GOLDBOX",
-	"MT_INVULN_GOLDBOX",
-	"MT_EGGMAN_GOLDBOX",
-	"MT_GRAVITY_GOLDBOX",
-	"MT_FLAMEAURA_GOLDBOX",
-	"MT_BUBBLEWRAP_GOLDBOX",
-	"MT_THUNDERCOIN_GOLDBOX",
-
-	// Monitor boxes -- special
-	"MT_RING_REDBOX",
-	"MT_RING_BLUEBOX",
-
-	// Monitor icons
-	"MT_RING_ICON",
-	"MT_PITY_ICON",
-	"MT_ATTRACT_ICON",
-	"MT_FORCE_ICON",
-	"MT_ARMAGEDDON_ICON",
-	"MT_WHIRLWIND_ICON",
-	"MT_ELEMENTAL_ICON",
-	"MT_SNEAKERS_ICON",
-	"MT_INVULN_ICON",
-	"MT_1UP_ICON",
-	"MT_EGGMAN_ICON",
-	"MT_MIXUP_ICON",
-	"MT_GRAVITY_ICON",
-	"MT_RECYCLER_ICON",
-	"MT_SCORE1K_ICON",
-	"MT_SCORE10K_ICON",
-	"MT_FLAMEAURA_ICON",
-	"MT_BUBBLEWRAP_ICON",
-	"MT_THUNDERCOIN_ICON",
-
-	// Projectiles
-	"MT_ROCKET",
-	"MT_LASER",
-	"MT_TORPEDO",
-	"MT_TORPEDO2", // silent
-	"MT_ENERGYBALL",
-	"MT_MINE", // Skim/Jetty-Syn mine
-	"MT_JETTBULLET", // Jetty-Syn Bullet
-	"MT_TURRETLASER",
-	"MT_CANNONBALL", // Cannonball
-	"MT_CANNONBALLDECOR", // Decorative/still cannonball
-	"MT_ARROW", // Arrow
-	"MT_DEMONFIRE", // Glaregoyle fire
-
-	// The letter
-	"MT_LETTER",
-
-	// Greenflower Scenery
-	"MT_GFZFLOWER1",
-	"MT_GFZFLOWER2",
-	"MT_GFZFLOWER3",
-
-	"MT_BLUEBERRYBUSH",
-	"MT_BERRYBUSH",
-	"MT_BUSH",
-
-	// Trees (both GFZ and misc)
-	"MT_GFZTREE",
-	"MT_GFZBERRYTREE",
-	"MT_GFZCHERRYTREE",
-	"MT_CHECKERTREE",
-	"MT_CHECKERSUNSETTREE",
-	"MT_FHZTREE", // Frozen Hillside
-	"MT_FHZPINKTREE",
-	"MT_POLYGONTREE",
-	"MT_BUSHTREE",
-	"MT_BUSHREDTREE",
-	"MT_SPRINGTREE",
-
-	// Techno Hill Scenery
-	"MT_THZFLOWER1",
-	"MT_THZFLOWER2",
-	"MT_THZFLOWER3",
-	"MT_THZTREE", // Steam whistle tree/bush
-	"MT_THZTREEBRANCH", // branch of said tree
-	"MT_ALARM",
-
-	// Deep Sea Scenery
-	"MT_GARGOYLE", // Deep Sea Gargoyle
-	"MT_BIGGARGOYLE", // Deep Sea Gargoyle (Big)
-	"MT_SEAWEED", // DSZ Seaweed
-	"MT_WATERDRIP", // Dripping Water source
-	"MT_WATERDROP", // Water drop from dripping water
-	"MT_CORAL1", // Coral
-	"MT_CORAL2",
-	"MT_CORAL3",
-	"MT_CORAL4",
-	"MT_CORAL5",
-	"MT_BLUECRYSTAL", // Blue Crystal
-	"MT_KELP", // Kelp
-	"MT_ANIMALGAETOP", // Animated algae top
-	"MT_ANIMALGAESEG", // Animated algae segment
-	"MT_DSZSTALAGMITE", // Deep Sea 1 Stalagmite
-	"MT_DSZ2STALAGMITE", // Deep Sea 2 Stalagmite
-	"MT_LIGHTBEAM", // DSZ Light beam
-
-	// Castle Eggman Scenery
-	"MT_CHAIN", // CEZ Chain
-	"MT_FLAME", // Flame (has corona)
-	"MT_FLAMEPARTICLE",
-	"MT_EGGSTATUE", // Eggman Statue
-	"MT_MACEPOINT", // Mace rotation point
-	"MT_CHAINMACEPOINT", // Combination of chains and maces point
-	"MT_SPRINGBALLPOINT", // Spring ball point
-	"MT_CHAINPOINT", // Mace chain
-	"MT_HIDDEN_SLING", // Spin mace chain (activatable)
-	"MT_FIREBARPOINT", // Firebar
-	"MT_CUSTOMMACEPOINT", // Custom mace
-	"MT_SMALLMACECHAIN", // Small Mace Chain
-	"MT_BIGMACECHAIN", // Big Mace Chain
-	"MT_SMALLMACE", // Small Mace
-	"MT_BIGMACE", // Big Mace
-	"MT_SMALLGRABCHAIN", // Small Grab Chain
-	"MT_BIGGRABCHAIN", // Big Grab Chain
-	"MT_YELLOWSPRINGBALL", // Yellow spring on a ball
-	"MT_REDSPRINGBALL", // Red spring on a ball
-	"MT_SMALLFIREBAR", // Small Firebar
-	"MT_BIGFIREBAR", // Big Firebar
-	"MT_CEZFLOWER", // Flower
-	"MT_CEZPOLE1", // Pole (with red banner)
-	"MT_CEZPOLE2", // Pole (with blue banner)
-	"MT_CEZBANNER1", // Banner (red)
-	"MT_CEZBANNER2", // Banner (blue)
-	"MT_PINETREE", // Pine Tree
-	"MT_CEZBUSH1", // Bush 1
-	"MT_CEZBUSH2", // Bush 2
-	"MT_CANDLE", // Candle
-	"MT_CANDLEPRICKET", // Candle pricket
-	"MT_FLAMEHOLDER", // Flame holder
-	"MT_FIRETORCH", // Fire torch
-	"MT_WAVINGFLAG1", // Waving flag (red)
-	"MT_WAVINGFLAG2", // Waving flag (blue)
-	"MT_WAVINGFLAGSEG1", // Waving flag segment (red)
-	"MT_WAVINGFLAGSEG2", // Waving flag segment (blue)
-	"MT_CRAWLASTATUE", // Crawla statue
-	"MT_FACESTABBERSTATUE", // Facestabber statue
-	"MT_SUSPICIOUSFACESTABBERSTATUE", // :eggthinking:
-	"MT_BRAMBLES", // Brambles
-
-	// Arid Canyon Scenery
-	"MT_BIGTUMBLEWEED",
-	"MT_LITTLETUMBLEWEED",
-	"MT_CACTI1", // Tiny Red Flower Cactus
-	"MT_CACTI2", // Small Red Flower Cactus
-	"MT_CACTI3", // Tiny Blue Flower Cactus
-	"MT_CACTI4", // Small Blue Flower Cactus
-	"MT_CACTI5", // Prickly Pear
-	"MT_CACTI6", // Barrel Cactus
-	"MT_CACTI7", // Tall Barrel Cactus
-	"MT_CACTI8", // Armed Cactus
-	"MT_CACTI9", // Ball Cactus
-	"MT_CACTI10", // Tiny Cactus
-	"MT_CACTI11", // Small Cactus
-	"MT_CACTITINYSEG", // Tiny Cactus Segment
-	"MT_CACTISMALLSEG", // Small Cactus Segment
-	"MT_ARIDSIGN_CAUTION", // Caution Sign
-	"MT_ARIDSIGN_CACTI", // Cacti Sign
-	"MT_ARIDSIGN_SHARPTURN", // Sharp Turn Sign
-	"MT_OILLAMP",
-	"MT_TNTBARREL",
-	"MT_PROXIMITYTNT",
-	"MT_DUSTDEVIL",
-	"MT_DUSTLAYER",
-	"MT_ARIDDUST",
-	"MT_MINECART",
-	"MT_MINECARTSEG",
-	"MT_MINECARTSPAWNER",
-	"MT_MINECARTEND",
-	"MT_MINECARTENDSOLID",
-	"MT_MINECARTSIDEMARK",
-	"MT_MINECARTSPARK",
-	"MT_SALOONDOOR",
-	"MT_SALOONDOORCENTER",
-	"MT_TRAINCAMEOSPAWNER",
-	"MT_TRAINSEG",
-	"MT_TRAINDUSTSPAWNER",
-	"MT_TRAINSTEAMSPAWNER",
-	"MT_MINECARTSWITCHPOINT",
-
-	// Red Volcano Scenery
-	"MT_FLAMEJET",
-	"MT_VERTICALFLAMEJET",
-	"MT_FLAMEJETFLAME",
-
-	"MT_FJSPINAXISA", // Counter-clockwise
-	"MT_FJSPINAXISB", // Clockwise
-
-	"MT_FLAMEJETFLAMEB", // Blade's flame
-
-	"MT_LAVAFALL",
-	"MT_LAVAFALL_LAVA",
-	"MT_LAVAFALLROCK",
-
-	"MT_ROLLOUTSPAWN",
-	"MT_ROLLOUTROCK",
-
-	"MT_BIGFERNLEAF",
-	"MT_BIGFERN",
-	"MT_JUNGLEPALM",
-	"MT_TORCHFLOWER",
-	"MT_WALLVINE_LONG",
-	"MT_WALLVINE_SHORT",
-
-	// Dark City Scenery
-
-	// Egg Rock Scenery
-
-	// Azure Temple Scenery
-	"MT_GLAREGOYLE",
-	"MT_GLAREGOYLEUP",
-	"MT_GLAREGOYLEDOWN",
-	"MT_GLAREGOYLELONG",
-	"MT_TARGET", // AKA Red Crystal
-	"MT_GREENFLAME",
-	"MT_BLUEGARGOYLE",
-
-	// Stalagmites
-	"MT_STALAGMITE0",
-	"MT_STALAGMITE1",
-	"MT_STALAGMITE2",
-	"MT_STALAGMITE3",
-	"MT_STALAGMITE4",
-	"MT_STALAGMITE5",
-	"MT_STALAGMITE6",
-	"MT_STALAGMITE7",
-	"MT_STALAGMITE8",
-	"MT_STALAGMITE9",
-
-	// Christmas Scenery
-	"MT_XMASPOLE",
-	"MT_CANDYCANE",
-	"MT_SNOWMAN",    // normal
-	"MT_SNOWMANHAT", // with hat + scarf
-	"MT_LAMPPOST1",  // normal
-	"MT_LAMPPOST2",  // with snow
-	"MT_HANGSTAR",
-	"MT_MISTLETOE",
-	// Xmas GFZ bushes
-	"MT_XMASBLUEBERRYBUSH",
-	"MT_XMASBERRYBUSH",
-	"MT_XMASBUSH",
-	// FHZ
-	"MT_FHZICE1",
-	"MT_FHZICE2",
-	"MT_ROSY",
-	"MT_CDLHRT",
-
-	// Halloween Scenery
-	// Pumpkins
-	"MT_JACKO1",
-	"MT_JACKO2",
-	"MT_JACKO3",
-	// Dr Seuss Trees
-	"MT_HHZTREE_TOP",
-	"MT_HHZTREE_PART",
-	// Misc
-	"MT_HHZSHROOM",
-	"MT_HHZGRASS",
-	"MT_HHZTENTACLE1",
-	"MT_HHZTENTACLE2",
-	"MT_HHZSTALAGMITE_TALL",
-	"MT_HHZSTALAGMITE_SHORT",
-
-	// Botanic Serenity scenery
-	"MT_BSZTALLFLOWER_RED",
-	"MT_BSZTALLFLOWER_PURPLE",
-	"MT_BSZTALLFLOWER_BLUE",
-	"MT_BSZTALLFLOWER_CYAN",
-	"MT_BSZTALLFLOWER_YELLOW",
-	"MT_BSZTALLFLOWER_ORANGE",
-	"MT_BSZFLOWER_RED",
-	"MT_BSZFLOWER_PURPLE",
-	"MT_BSZFLOWER_BLUE",
-	"MT_BSZFLOWER_CYAN",
-	"MT_BSZFLOWER_YELLOW",
-	"MT_BSZFLOWER_ORANGE",
-	"MT_BSZSHORTFLOWER_RED",
-	"MT_BSZSHORTFLOWER_PURPLE",
-	"MT_BSZSHORTFLOWER_BLUE",
-	"MT_BSZSHORTFLOWER_CYAN",
-	"MT_BSZSHORTFLOWER_YELLOW",
-	"MT_BSZSHORTFLOWER_ORANGE",
-	"MT_BSZTULIP_RED",
-	"MT_BSZTULIP_PURPLE",
-	"MT_BSZTULIP_BLUE",
-	"MT_BSZTULIP_CYAN",
-	"MT_BSZTULIP_YELLOW",
-	"MT_BSZTULIP_ORANGE",
-	"MT_BSZCLUSTER_RED",
-	"MT_BSZCLUSTER_PURPLE",
-	"MT_BSZCLUSTER_BLUE",
-	"MT_BSZCLUSTER_CYAN",
-	"MT_BSZCLUSTER_YELLOW",
-	"MT_BSZCLUSTER_ORANGE",
-	"MT_BSZBUSH_RED",
-	"MT_BSZBUSH_PURPLE",
-	"MT_BSZBUSH_BLUE",
-	"MT_BSZBUSH_CYAN",
-	"MT_BSZBUSH_YELLOW",
-	"MT_BSZBUSH_ORANGE",
-	"MT_BSZVINE_RED",
-	"MT_BSZVINE_PURPLE",
-	"MT_BSZVINE_BLUE",
-	"MT_BSZVINE_CYAN",
-	"MT_BSZVINE_YELLOW",
-	"MT_BSZVINE_ORANGE",
-	"MT_BSZSHRUB",
-	"MT_BSZCLOVER",
-	"MT_BIG_PALMTREE_TRUNK",
-	"MT_BIG_PALMTREE_TOP",
-	"MT_PALMTREE_TRUNK",
-	"MT_PALMTREE_TOP",
-
-	// Misc scenery
-	"MT_DBALL",
-	"MT_EGGSTATUE2",
-
-	// Powerup Indicators
-	"MT_ELEMENTAL_ORB", // Elemental shield mobj
-	"MT_ATTRACT_ORB", // Attract shield mobj
-	"MT_FORCE_ORB", // Force shield mobj
-	"MT_ARMAGEDDON_ORB", // Armageddon shield mobj
-	"MT_WHIRLWIND_ORB", // Whirlwind shield mobj
-	"MT_PITY_ORB", // Pity shield mobj
-	"MT_FLAMEAURA_ORB", // Flame shield mobj
-	"MT_BUBBLEWRAP_ORB", // Bubble shield mobj
-	"MT_THUNDERCOIN_ORB", // Thunder shield mobj
-	"MT_THUNDERCOIN_SPARK", // Thunder spark
-	"MT_IVSP", // Invincibility sparkles
-	"MT_SUPERSPARK", // Super Sonic Spark
-
-	// Flickies
-	"MT_FLICKY_01", // Bluebird
-	"MT_FLICKY_01_CENTER",
-	"MT_FLICKY_02", // Rabbit
-	"MT_FLICKY_02_CENTER",
-	"MT_FLICKY_03", // Chicken
-	"MT_FLICKY_03_CENTER",
-	"MT_FLICKY_04", // Seal
-	"MT_FLICKY_04_CENTER",
-	"MT_FLICKY_05", // Pig
-	"MT_FLICKY_05_CENTER",
-	"MT_FLICKY_06", // Chipmunk
-	"MT_FLICKY_06_CENTER",
-	"MT_FLICKY_07", // Penguin
-	"MT_FLICKY_07_CENTER",
-	"MT_FLICKY_08", // Fish
-	"MT_FLICKY_08_CENTER",
-	"MT_FLICKY_09", // Ram
-	"MT_FLICKY_09_CENTER",
-	"MT_FLICKY_10", // Puffin
-	"MT_FLICKY_10_CENTER",
-	"MT_FLICKY_11", // Cow
-	"MT_FLICKY_11_CENTER",
-	"MT_FLICKY_12", // Rat
-	"MT_FLICKY_12_CENTER",
-	"MT_FLICKY_13", // Bear
-	"MT_FLICKY_13_CENTER",
-	"MT_FLICKY_14", // Dove
-	"MT_FLICKY_14_CENTER",
-	"MT_FLICKY_15", // Cat
-	"MT_FLICKY_15_CENTER",
-	"MT_FLICKY_16", // Canary
-	"MT_FLICKY_16_CENTER",
-	"MT_SECRETFLICKY_01", // Spider
-	"MT_SECRETFLICKY_01_CENTER",
-	"MT_SECRETFLICKY_02", // Bat
-	"MT_SECRETFLICKY_02_CENTER",
-	"MT_SEED",
-
-	// Environmental Effects
-	"MT_RAIN", // Rain
-	"MT_SNOWFLAKE", // Snowflake
-	"MT_SPLISH", // Water splish!
-	"MT_LAVASPLISH", // Lava splish!
-	"MT_SMOKE",
-	"MT_SMALLBUBBLE", // small bubble
-	"MT_MEDIUMBUBBLE", // medium bubble
-	"MT_EXTRALARGEBUBBLE", // extra large bubble
-	"MT_WATERZAP",
-	"MT_SPINDUST", // Spindash dust
-	"MT_TFOG",
-	"MT_PARTICLE",
-	"MT_PARTICLEGEN", // For fans, etc.
-
-	// Game Indicators
-	"MT_SCORE", // score logo
-	"MT_DROWNNUMBERS", // Drowning Timer
-	"MT_GOTEMERALD", // Chaos Emerald (intangible)
-	"MT_LOCKON", // Target
-	"MT_LOCKONINF", // In-level Target
-	"MT_TAG", // Tag Sign
-	"MT_GOTFLAG", // Got Flag sign
-	"MT_FINISHFLAG", // Finish flag
-
-	// Ambient Sounds
-	"MT_AWATERA", // Ambient Water Sound 1
-	"MT_AWATERB", // Ambient Water Sound 2
-	"MT_AWATERC", // Ambient Water Sound 3
-	"MT_AWATERD", // Ambient Water Sound 4
-	"MT_AWATERE", // Ambient Water Sound 5
-	"MT_AWATERF", // Ambient Water Sound 6
-	"MT_AWATERG", // Ambient Water Sound 7
-	"MT_AWATERH", // Ambient Water Sound 8
-	"MT_RANDOMAMBIENT",
-	"MT_RANDOMAMBIENT2",
-	"MT_MACHINEAMBIENCE",
-
-	"MT_CORK",
-	"MT_LHRT",
-
-	// Ring Weapons
-	"MT_REDRING",
-	"MT_BOUNCERING",
-	"MT_RAILRING",
-	"MT_INFINITYRING",
-	"MT_AUTOMATICRING",
-	"MT_EXPLOSIONRING",
-	"MT_SCATTERRING",
-	"MT_GRENADERING",
-
-	"MT_BOUNCEPICKUP",
-	"MT_RAILPICKUP",
-	"MT_AUTOPICKUP",
-	"MT_EXPLODEPICKUP",
-	"MT_SCATTERPICKUP",
-	"MT_GRENADEPICKUP",
-
-	"MT_THROWNBOUNCE",
-	"MT_THROWNINFINITY",
-	"MT_THROWNAUTOMATIC",
-	"MT_THROWNSCATTER",
-	"MT_THROWNEXPLOSION",
-	"MT_THROWNGRENADE",
-
-	// Mario-specific stuff
-	"MT_COIN",
-	"MT_FLINGCOIN",
-	"MT_GOOMBA",
-	"MT_BLUEGOOMBA",
-	"MT_FIREFLOWER",
-	"MT_FIREBALL",
-	"MT_FIREBALLTRAIL",
-	"MT_SHELL",
-	"MT_PUMA",
-	"MT_PUMATRAIL",
-	"MT_HAMMER",
-	"MT_KOOPA",
-	"MT_KOOPAFLAME",
-	"MT_AXE",
-	"MT_MARIOBUSH1",
-	"MT_MARIOBUSH2",
-	"MT_TOAD",
-
-	// NiGHTS Stuff
-	"MT_AXIS",
-	"MT_AXISTRANSFER",
-	"MT_AXISTRANSFERLINE",
-	"MT_NIGHTSDRONE",
-	"MT_NIGHTSDRONE_MAN",
-	"MT_NIGHTSDRONE_SPARKLING",
-	"MT_NIGHTSDRONE_GOAL",
-	"MT_NIGHTSPARKLE",
-	"MT_NIGHTSLOOPHELPER",
-	"MT_NIGHTSBUMPER", // NiGHTS Bumper
-	"MT_HOOP",
-	"MT_HOOPCOLLIDE", // Collision detection for NiGHTS hoops
-	"MT_HOOPCENTER", // Center of a hoop
-	"MT_NIGHTSCORE",
-	"MT_NIGHTSCHIP", // NiGHTS Chip
-	"MT_FLINGNIGHTSCHIP", // Lost NiGHTS Chip
-	"MT_NIGHTSSTAR", // NiGHTS Star
-	"MT_FLINGNIGHTSSTAR", // Lost NiGHTS Star
-	"MT_NIGHTSSUPERLOOP",
-	"MT_NIGHTSDRILLREFILL",
-	"MT_NIGHTSHELPER",
-	"MT_NIGHTSEXTRATIME",
-	"MT_NIGHTSLINKFREEZE",
-	"MT_EGGCAPSULE",
-	"MT_IDEYAANCHOR",
-	"MT_NIGHTOPIANHELPER", // the actual helper object that orbits you
-	"MT_PIAN", // decorative singing friend
-	"MT_SHLEEP", // almost-decorative sleeping enemy
-
-	// Secret badniks and hazards, shhhh
-	"MT_PENGUINATOR",
-	"MT_POPHAT",
-	"MT_POPSHOT",
-	"MT_POPSHOT_TRAIL",
-
-	"MT_HIVEELEMENTAL",
-	"MT_BUMBLEBORE",
-
-	"MT_BUGGLE",
-
-	"MT_SMASHINGSPIKEBALL",
-	"MT_CACOLANTERN",
-	"MT_CACOSHARD",
-	"MT_CACOFIRE",
-	"MT_SPINBOBERT",
-	"MT_SPINBOBERT_FIRE1",
-	"MT_SPINBOBERT_FIRE2",
-	"MT_HANGSTER",
-
-	// Utility Objects
-	"MT_TELEPORTMAN",
-	"MT_ALTVIEWMAN",
-	"MT_CRUMBLEOBJ", // Sound generator for crumbling platform
-	"MT_TUBEWAYPOINT",
-	"MT_PUSH",
-	"MT_PULL",
-	"MT_GHOST",
-	"MT_OVERLAY",
-	"MT_ANGLEMAN",
-	"MT_POLYANCHOR",
-	"MT_POLYSPAWN",
-	"MT_POLYSPAWNCRUSH",
-
-	// Skybox objects
-	"MT_SKYBOX",
-
-	// Debris
-	"MT_SPARK", //spark
-	"MT_EXPLODE", // Robot Explosion
-	"MT_UWEXPLODE", // Underwater Explosion
-	"MT_DUST",
-	"MT_ROCKSPAWNER",
-	"MT_FALLINGROCK",
-	"MT_ROCKCRUMBLE1",
-	"MT_ROCKCRUMBLE2",
-	"MT_ROCKCRUMBLE3",
-	"MT_ROCKCRUMBLE4",
-	"MT_ROCKCRUMBLE5",
-	"MT_ROCKCRUMBLE6",
-	"MT_ROCKCRUMBLE7",
-	"MT_ROCKCRUMBLE8",
-	"MT_ROCKCRUMBLE9",
-	"MT_ROCKCRUMBLE10",
-	"MT_ROCKCRUMBLE11",
-	"MT_ROCKCRUMBLE12",
-	"MT_ROCKCRUMBLE13",
-	"MT_ROCKCRUMBLE14",
-	"MT_ROCKCRUMBLE15",
-	"MT_ROCKCRUMBLE16",
-
-	// Level debris
-	"MT_GFZDEBRIS",
-	"MT_BRICKDEBRIS",
-	"MT_WOODDEBRIS",
-	"MT_REDBRICKDEBRIS",
-	"MT_BLUEBRICKDEBRIS",
-	"MT_YELLOWBRICKDEBRIS",
-
-#ifdef SEENAMES
-	"MT_NAMECHECK",
-#endif
-};
-
-static const char *const MOBJFLAG_LIST[] = {
-	"SPECIAL",
-	"SOLID",
-	"SHOOTABLE",
-	"NOSECTOR",
-	"NOBLOCKMAP",
-	"PAPERCOLLISION",
-	"PUSHABLE",
-	"BOSS",
-	"SPAWNCEILING",
-	"NOGRAVITY",
-	"AMBIENT",
-	"SLIDEME",
-	"NOCLIP",
-	"FLOAT",
-	"BOXICON",
-	"MISSILE",
-	"SPRING",
-	"BOUNCE",
-	"MONITOR",
-	"NOTHINK",
-	"FIRE",
-	"NOCLIPHEIGHT",
-	"ENEMY",
-	"SCENERY",
-	"PAIN",
-	"STICKY",
-	"NIGHTSITEM",
-	"NOCLIPTHING",
-	"GRENADEBOUNCE",
-	"RUNSPAWNFUNC",
-	NULL
-};
-
-// \tMF2_(\S+).*// (.+) --> \t"\1", // \2
-static const char *const MOBJFLAG2_LIST[] = {
-	"AXIS",			  // It's a NiGHTS axis! (For faster checking)
-	"TWOD",			  // Moves like it's in a 2D level
-	"DONTRESPAWN",	  // Don't respawn this object!
-	"DONTDRAW",		  // Don't generate a vissprite
-	"AUTOMATIC",	  // Thrown ring has automatic properties
-	"RAILRING",		  // Thrown ring has rail properties
-	"BOUNCERING",	  // Thrown ring has bounce properties
-	"EXPLOSION",	  // Thrown ring has explosive properties
-	"SCATTER",		  // Thrown ring has scatter properties
-	"BEYONDTHEGRAVE", // Source of this missile has died and has since respawned.
-	"SLIDEPUSH",	  // MF_PUSHABLE that pushes continuously.
-	"CLASSICPUSH",    // Drops straight down when object has negative momz.
-	"INVERTAIMABLE",  // Flips whether it's targetable by A_LookForEnemies (enemies no, decoys yes)
-	"INFLOAT",		  // Floating to a height for a move, don't auto float to target's height.
-	"DEBRIS",		  // Splash ring from explosion ring
-	"NIGHTSPULL",	  // Attracted from a paraloop
-	"JUSTATTACKED",	  // can be pushed by other moving mobjs
-	"FIRING",		  // turret fire
-	"SUPERFIRE",	  // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it.
-	"SHADOW",		  // Fuzzy draw, makes targeting harder.
-	"STRONGBOX",	  // Flag used for "strong" random monitors.
-	"OBJECTFLIP",	  // Flag for objects that always have flipped gravity.
-	"SKULLFLY",		  // Special handling: skull in flight.
-	"FRET",			  // Flashing from a previous hit
-	"BOSSNOTRAP",	  // No Egg Trap after boss
-	"BOSSFLEE",		  // Boss is fleeing!
-	"BOSSDEAD",		  // Boss is dead! (Not necessarily fleeing, if a fleeing point doesn't exist.)
-	"AMBUSH",         // Alternate behaviour typically set by MTF_AMBUSH
-	"LINKDRAW",       // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
-	"SHIELD",         // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
-	NULL
-};
-
-static const char *const MOBJEFLAG_LIST[] = {
-	"ONGROUND", // The mobj stands on solid floor (not on another mobj or in air)
-	"JUSTHITFLOOR", // The mobj just hit the floor while falling, this is cleared on next frame
-	"TOUCHWATER", // The mobj stands in a sector with water, and touches the surface
-	"UNDERWATER", // The mobj stands in a sector with water, and his waist is BELOW the water surface
-	"JUSTSTEPPEDDOWN", // used for ramp sectors
-	"VERTICALFLIP", // Vertically flip sprite/allow upside-down physics
-	"GOOWATER", // Goo water
-	"TOUCHLAVA", // The mobj is touching a lava block
-	"PUSHED", // Mobj was already pushed this tic
-	"SPRUNG", // Mobj was already sprung this tic
-	"APPLYPMOMZ", // Platform movement
-	"TRACERANGLE", // Compute and trigger on mobj angle relative to tracer
-	NULL
-};
-
-static const char *const MAPTHINGFLAG_LIST[4] = {
-	"EXTRA", // Extra flag for objects.
-	"OBJECTFLIP", // Reverse gravity flag for objects.
-	"OBJECTSPECIAL", // Special flag used with certain objects.
-	"AMBUSH" // Deaf monsters/do not react to sound.
-};
-
-static const char *const PLAYERFLAG_LIST[] = {
-
-	// Cvars
-	"FLIPCAM", // Flip camera angle with gravity flip prefrence.
-	"ANALOGMODE", // Analog mode?
-	"DIRECTIONCHAR", // Directional character sprites?
-	"AUTOBRAKE", // Autobrake?
-
-	// Cheats
-	"GODMODE",
-	"NOCLIP",
-	"INVIS",
-
-	// True if button down last tic.
-	"ATTACKDOWN",
-	"USEDOWN",
-	"JUMPDOWN",
-	"WPNDOWN",
-
-	// Unmoving states
-	"STASIS", // Player is not allowed to move
-	"JUMPSTASIS", // and that includes jumping.
-	// (we don't include FULLSTASIS here I guess because it's just those two together...?)
-
-	// Applying autobrake?
-	"APPLYAUTOBRAKE",
-
-	// Character action status
-	"STARTJUMP",
-	"JUMPED",
-	"NOJUMPDAMAGE",
-
-	"SPINNING",
-	"STARTDASH",
-
-	"THOKKED",
-	"SHIELDABILITY",
-	"GLIDING",
-	"BOUNCING",
-
-	// Sliding (usually in water) like Labyrinth/Oil Ocean
-	"SLIDING",
-
-	// NiGHTS stuff
-	"TRANSFERTOCLOSEST",
-	"DRILLING",
-
-	// Gametype-specific stuff
-	"GAMETYPEOVER", // Race time over, or H&S out-of-game
-	"TAGIT", // The player is it! For Tag Mode
-
-	/*** misc ***/
-	"FORCESTRAFE", // Translate turn inputs into strafe inputs
-	"CANCARRY", // Can carry?
-	"FINISHED",
-
-	NULL // stop loop here.
-};
-
-static const char *const GAMETYPERULE_LIST[] = {
-	"CAMPAIGN",
-	"RINGSLINGER",
-	"SPECTATORS",
-	"LIVES",
-	"TEAMS",
-	"FIRSTPERSON",
-	"POWERSTONES",
-	"TEAMFLAGS",
-	"FRIENDLY",
-	"SPECIALSTAGES",
-	"EMERALDTOKENS",
-	"EMERALDHUNT",
-	"RACE",
-	"TAG",
-	"POINTLIMIT",
-	"TIMELIMIT",
-	"OVERTIME",
-	"HURTMESSAGES",
-	"FRIENDLYFIRE",
-	"STARTCOUNTDOWN",
-	"HIDEFROZEN",
-	"BLINDFOLDED",
-	"RESPAWNDELAY",
-	"PITYSHIELD",
-	"DEATHPENALTY",
-	"NOSPECTATORSPAWN",
-	"DEATHMATCHSTARTS",
-	"SPAWNINVUL",
-	"SPAWNENEMIES",
-	"ALLOWEXIT",
-	"NOTITLECARD",
-	"CUTSCENES",
-	NULL
-};
-
-// Linedef flags
-static const char *const ML_LIST[16] = {
-	"IMPASSIBLE",
-	"BLOCKMONSTERS",
-	"TWOSIDED",
-	"DONTPEGTOP",
-	"DONTPEGBOTTOM",
-	"EFFECT1",
-	"NOCLIMB",
-	"EFFECT2",
-	"EFFECT3",
-	"EFFECT4",
-	"EFFECT5",
-	"NOSONIC",
-	"NOTAILS",
-	"NOKNUX",
-	"BOUNCY",
-	"TFERLINE"
-};
-
-static const char *COLOR_ENUMS[] = {
-	"NONE",			// SKINCOLOR_NONE,
-
-	// Greyscale ranges
-	"WHITE",     	// SKINCOLOR_WHITE,
-	"BONE",     	// SKINCOLOR_BONE,
-	"CLOUDY",     	// SKINCOLOR_CLOUDY,
-	"GREY",     	// SKINCOLOR_GREY,
-	"SILVER",     	// SKINCOLOR_SILVER,
-	"CARBON",     	// SKINCOLOR_CARBON,
-	"JET",     		// SKINCOLOR_JET,
-	"BLACK",     	// SKINCOLOR_BLACK,
-
-	// Desaturated
-	"AETHER",     	// SKINCOLOR_AETHER,
-	"SLATE",     	// SKINCOLOR_SLATE,
-	"BLUEBELL",   	// SKINCOLOR_BLUEBELL,
-	"PINK",     	// SKINCOLOR_PINK,
-	"YOGURT",     	// SKINCOLOR_YOGURT,
-	"BROWN",     	// SKINCOLOR_BROWN,
-	"BRONZE",     	// SKINCOLOR_BRONZE,
-	"TAN",     		// SKINCOLOR_TAN,
-	"BEIGE",     	// SKINCOLOR_BEIGE,
-	"MOSS",     	// SKINCOLOR_MOSS,
-	"AZURE",     	// SKINCOLOR_AZURE,
-	"LAVENDER",     // SKINCOLOR_LAVENDER,
-
-	// Viv's vivid colours (toast 21/07/17)
-	"RUBY",     	// SKINCOLOR_RUBY,
-	"SALMON",     	// SKINCOLOR_SALMON,
-	"RED",     		// SKINCOLOR_RED,
-	"CRIMSON",     	// SKINCOLOR_CRIMSON,
-	"FLAME",     	// SKINCOLOR_FLAME,
-	"KETCHUP",     	// SKINCOLOR_KETCHUP,
-	"PEACHY",     	// SKINCOLOR_PEACHY,
-	"QUAIL",     	// SKINCOLOR_QUAIL,
-	"SUNSET",     	// SKINCOLOR_SUNSET,
-	"COPPER",     	// SKINCOLOR_COPPER,
-	"APRICOT",     	// SKINCOLOR_APRICOT,
-	"ORANGE",     	// SKINCOLOR_ORANGE,
-	"RUST",     	// SKINCOLOR_RUST,
-	"GOLD",     	// SKINCOLOR_GOLD,
-	"SANDY",     	// SKINCOLOR_SANDY,
-	"YELLOW",     	// SKINCOLOR_YELLOW,
-	"OLIVE",     	// SKINCOLOR_OLIVE,
-	"LIME",     	// SKINCOLOR_LIME,
-	"PERIDOT",     	// SKINCOLOR_PERIDOT,
-	"APPLE",     	// SKINCOLOR_APPLE,
-	"GREEN",     	// SKINCOLOR_GREEN,
-	"FOREST",     	// SKINCOLOR_FOREST,
-	"EMERALD",     	// SKINCOLOR_EMERALD,
-	"MINT",     	// SKINCOLOR_MINT,
-	"SEAFOAM",     	// SKINCOLOR_SEAFOAM,
-	"AQUA",     	// SKINCOLOR_AQUA,
-	"TEAL",     	// SKINCOLOR_TEAL,
-	"WAVE",     	// SKINCOLOR_WAVE,
-	"CYAN",     	// SKINCOLOR_CYAN,
-	"SKY",     		// SKINCOLOR_SKY,
-	"CERULEAN",     // SKINCOLOR_CERULEAN,
-	"ICY",     		// SKINCOLOR_ICY,
-	"SAPPHIRE",     // SKINCOLOR_SAPPHIRE,
-	"CORNFLOWER",   // SKINCOLOR_CORNFLOWER,
-	"BLUE",     	// SKINCOLOR_BLUE,
-	"COBALT",     	// SKINCOLOR_COBALT,
-	"VAPOR",     	// SKINCOLOR_VAPOR,
-	"DUSK",     	// SKINCOLOR_DUSK,
-	"PASTEL",     	// SKINCOLOR_PASTEL,
-	"PURPLE",     	// SKINCOLOR_PURPLE,
-	"BUBBLEGUM",    // SKINCOLOR_BUBBLEGUM,
-	"MAGENTA",     	// SKINCOLOR_MAGENTA,
-	"NEON",     	// SKINCOLOR_NEON,
-	"VIOLET",     	// SKINCOLOR_VIOLET,
-	"LILAC",     	// SKINCOLOR_LILAC,
-	"PLUM",     	// SKINCOLOR_PLUM,
-	"RASPBERRY",  	// SKINCOLOR_RASPBERRY,
-	"ROSY",     	// SKINCOLOR_ROSY,
-
-	// Super special awesome Super flashing colors!
-	"SUPERSILVER1",	// SKINCOLOR_SUPERSILVER1
-	"SUPERSILVER2",	// SKINCOLOR_SUPERSILVER2,
-	"SUPERSILVER3",	// SKINCOLOR_SUPERSILVER3,
-	"SUPERSILVER4",	// SKINCOLOR_SUPERSILVER4,
-	"SUPERSILVER5",	// SKINCOLOR_SUPERSILVER5,
-
-	"SUPERRED1",	// SKINCOLOR_SUPERRED1
-	"SUPERRED2",	// SKINCOLOR_SUPERRED2,
-	"SUPERRED3",	// SKINCOLOR_SUPERRED3,
-	"SUPERRED4",	// SKINCOLOR_SUPERRED4,
-	"SUPERRED5",	// SKINCOLOR_SUPERRED5,
-
-	"SUPERORANGE1",	// SKINCOLOR_SUPERORANGE1
-	"SUPERORANGE2",	// SKINCOLOR_SUPERORANGE2,
-	"SUPERORANGE3",	// SKINCOLOR_SUPERORANGE3,
-	"SUPERORANGE4",	// SKINCOLOR_SUPERORANGE4,
-	"SUPERORANGE5",	// SKINCOLOR_SUPERORANGE5,
-
-	"SUPERGOLD1",	// SKINCOLOR_SUPERGOLD1
-	"SUPERGOLD2",	// SKINCOLOR_SUPERGOLD2,
-	"SUPERGOLD3",	// SKINCOLOR_SUPERGOLD3,
-	"SUPERGOLD4",	// SKINCOLOR_SUPERGOLD4,
-	"SUPERGOLD5",	// SKINCOLOR_SUPERGOLD5,
-
-	"SUPERPERIDOT1",	// SKINCOLOR_SUPERPERIDOT1
-	"SUPERPERIDOT2",	// SKINCOLOR_SUPERPERIDOT2,
-	"SUPERPERIDOT3",	// SKINCOLOR_SUPERPERIDOT3,
-	"SUPERPERIDOT4",	// SKINCOLOR_SUPERPERIDOT4,
-	"SUPERPERIDOT5",	// SKINCOLOR_SUPERPERIDOT5,
-
-	"SUPERSKY1",	// SKINCOLOR_SUPERSKY1
-	"SUPERSKY2",	// SKINCOLOR_SUPERSKY2,
-	"SUPERSKY3",	// SKINCOLOR_SUPERSKY3,
-	"SUPERSKY4",	// SKINCOLOR_SUPERSKY4,
-	"SUPERSKY5",	// SKINCOLOR_SUPERSKY5,
-
-	"SUPERPURPLE1",	// SKINCOLOR_SUPERPURPLE1,
-	"SUPERPURPLE2",	// SKINCOLOR_SUPERPURPLE2,
-	"SUPERPURPLE3",	// SKINCOLOR_SUPERPURPLE3,
-	"SUPERPURPLE4",	// SKINCOLOR_SUPERPURPLE4,
-	"SUPERPURPLE5",	// SKINCOLOR_SUPERPURPLE5,
-
-	"SUPERRUST1",	// SKINCOLOR_SUPERRUST1
-	"SUPERRUST2",	// SKINCOLOR_SUPERRUST2,
-	"SUPERRUST3",	// SKINCOLOR_SUPERRUST3,
-	"SUPERRUST4",	// SKINCOLOR_SUPERRUST4,
-	"SUPERRUST5",	// SKINCOLOR_SUPERRUST5,
-
-	"SUPERTAN1",	// SKINCOLOR_SUPERTAN1
-	"SUPERTAN2",	// SKINCOLOR_SUPERTAN2,
-	"SUPERTAN3",	// SKINCOLOR_SUPERTAN3,
-	"SUPERTAN4",	// SKINCOLOR_SUPERTAN4,
-	"SUPERTAN5"		// SKINCOLOR_SUPERTAN5,
-};
-
-static const char *const POWERS_LIST[] = {
-	"INVULNERABILITY",
-	"SNEAKERS",
-	"FLASHING",
-	"SHIELD",
-	"CARRY",
-	"TAILSFLY", // tails flying
-	"UNDERWATER", // underwater timer
-	"SPACETIME", // In space, no one can hear you spin!
-	"EXTRALIFE", // Extra Life timer
-	"PUSHING",
-	"JUSTSPRUNG",
-	"NOAUTOBRAKE",
-
-	"SUPER", // Are you super?
-	"GRAVITYBOOTS", // gravity boots
-
-	// Weapon ammunition
-	"INFINITYRING",
-	"AUTOMATICRING",
-	"BOUNCERING",
-	"SCATTERRING",
-	"GRENADERING",
-	"EXPLOSIONRING",
-	"RAILRING",
-
-	// Power Stones
-	"EMERALDS", // stored like global 'emeralds' variable
-
-	// NiGHTS powerups
-	"NIGHTS_SUPERLOOP",
-	"NIGHTS_HELPER",
-	"NIGHTS_LINKFREEZE",
-
-	//for linedef exec 427
-	"NOCONTROL",
-
-	//for dyes
-	"DYE",
-
-	"JUSTLAUNCHED",
-
-	"IGNORELATCH"
-};
-
-static const char *const HUDITEMS_LIST[] = {
-	"LIVES",
-
-	"RINGS",
-	"RINGSNUM",
-	"RINGSNUMTICS",
-
-	"SCORE",
-	"SCORENUM",
-
-	"TIME",
-	"MINUTES",
-	"TIMECOLON",
-	"SECONDS",
-	"TIMETICCOLON",
-	"TICS",
-
-	"SS_TOTALRINGS",
-
-	"GETRINGS",
-	"GETRINGSNUM",
-	"TIMELEFT",
-	"TIMELEFTNUM",
-	"TIMEUP",
-	"HUNTPICS",
-	"POWERUPS"
-};
-
-static const char *const MENUTYPES_LIST[] = {
-	"NONE",
-
-	"MAIN",
-
-	// Single Player
-	"SP_MAIN",
-
-	"SP_LOAD",
-	"SP_PLAYER",
-
-	"SP_LEVELSELECT",
-	"SP_LEVELSTATS",
-
-	"SP_TIMEATTACK",
-	"SP_TIMEATTACK_LEVELSELECT",
-	"SP_GUESTREPLAY",
-	"SP_REPLAY",
-	"SP_GHOST",
-
-	"SP_NIGHTSATTACK",
-	"SP_NIGHTS_LEVELSELECT",
-	"SP_NIGHTS_GUESTREPLAY",
-	"SP_NIGHTS_REPLAY",
-	"SP_NIGHTS_GHOST",
-
-	// Multiplayer
-	"MP_MAIN",
-	"MP_SPLITSCREEN", // SplitServer
-	"MP_SERVER",
-	"MP_CONNECT",
-	"MP_ROOM",
-	"MP_PLAYERSETUP", // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET
-	"MP_SERVER_OPTIONS",
-
-	// Options
-	"OP_MAIN",
-
-	"OP_P1CONTROLS",
-	"OP_CHANGECONTROLS", // OP_ChangeControlsDef shared with P2
-	"OP_P1MOUSE",
-	"OP_P1JOYSTICK",
-	"OP_JOYSTICKSET", // OP_JoystickSetDef shared with P2
-	"OP_P1CAMERA",
-
-	"OP_P2CONTROLS",
-	"OP_P2MOUSE",
-	"OP_P2JOYSTICK",
-	"OP_P2CAMERA",
-
-	"OP_PLAYSTYLE",
-
-	"OP_VIDEO",
-	"OP_VIDEOMODE",
-	"OP_COLOR",
-	"OP_OPENGL",
-	"OP_OPENGL_LIGHTING",
-
-	"OP_SOUND",
-
-	"OP_SERVER",
-	"OP_MONITORTOGGLE",
-
-	"OP_DATA",
-	"OP_ADDONS",
-	"OP_SCREENSHOTS",
-	"OP_ERASEDATA",
-
-	// Extras
-	"SR_MAIN",
-	"SR_PANDORA",
-	"SR_LEVELSELECT",
-	"SR_UNLOCKCHECKLIST",
-	"SR_EMBLEMHINT",
-	"SR_PLAYER",
-	"SR_SOUNDTEST",
-
-	// Addons (Part of MISC, but let's make it our own)
-	"AD_MAIN",
-
-	// MISC
-	// "MESSAGE",
-	// "SPAUSE",
-
-	// "MPAUSE",
-	// "SCRAMBLETEAM",
-	// "CHANGETEAM",
-	// "CHANGELEVEL",
-
-	// "MAPAUSE",
-	// "HELP",
-
-	"SPECIAL"
-};
-
-struct {
-	const char *n;
-	// has to be able to hold both fixed_t and angle_t, so drastic measure!!
-	lua_Integer v;
-} const INT_CONST[] = {
-	// If a mod removes some variables here,
-	// please leave the names in-tact and just set
-	// the value to 0 or something.
-
-	// integer type limits, from doomtype.h
-	// INT64 and UINT64 limits not included, they're too big for most purposes anyway
-	// signed
-	{"INT8_MIN",INT8_MIN},
-	{"INT16_MIN",INT16_MIN},
-	{"INT32_MIN",INT32_MIN},
-	{"INT8_MAX",INT8_MAX},
-	{"INT16_MAX",INT16_MAX},
-	{"INT32_MAX",INT32_MAX},
-	// unsigned
-	{"UINT8_MAX",UINT8_MAX},
-	{"UINT16_MAX",UINT16_MAX},
-	{"UINT32_MAX",UINT32_MAX},
-
-	// fixed_t constants, from m_fixed.h
-	{"FRACUNIT",FRACUNIT},
-	{"FRACBITS",FRACBITS},
-
-	// doomdef.h constants
-	{"TICRATE",TICRATE},
-	{"MUSICRATE",MUSICRATE},
-	{"RING_DIST",RING_DIST},
-	{"PUSHACCEL",PUSHACCEL},
-	{"MODID",MODID}, // I don't know, I just thought it would be cool for a wad to potentially know what mod it was loaded into.
-	{"CODEBASE",CODEBASE}, // or what release of SRB2 this is.
-	{"NEWTICRATE",NEWTICRATE}, // TICRATE*NEWTICRATERATIO
-	{"NEWTICRATERATIO",NEWTICRATERATIO},
-
-	// Special linedef executor tag numbers!
-	{"LE_PINCHPHASE",LE_PINCHPHASE}, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
-	{"LE_ALLBOSSESDEAD",LE_ALLBOSSESDEAD}, // All bosses in the map are dead (Egg capsule raise)
-	{"LE_BOSSDEAD",LE_BOSSDEAD}, // A boss in the map died (Chaos mode boss tally)
-	{"LE_BOSS4DROP",LE_BOSS4DROP}, // CEZ boss dropped its cage
-	{"LE_BRAKVILEATACK",LE_BRAKVILEATACK}, // Brak's doing his LOS attack, oh noes
-	{"LE_TURRET",LE_TURRET}, // THZ turret
-	{"LE_BRAKPLATFORM",LE_BRAKPLATFORM}, // v2.0 Black Eggman destroys platform
-	{"LE_CAPSULE2",LE_CAPSULE2}, // Egg Capsule
-	{"LE_CAPSULE1",LE_CAPSULE1}, // Egg Capsule
-	{"LE_CAPSULE0",LE_CAPSULE0}, // Egg Capsule
-	{"LE_KOOPA",LE_KOOPA}, // Distant cousin to Gay Bowser
-	{"LE_AXE",LE_AXE}, // MKB Axe object
-	{"LE_PARAMWIDTH",LE_PARAMWIDTH},  // If an object that calls LinedefExecute has a nonzero parameter value, this times the parameter will be subtracted. (Mostly for the purpose of coexisting bosses...)
-
-	/// \todo Get all this stuff into its own sections, maybe. Maybe.
-
-	// Frame settings
-	{"FF_FRAMEMASK",FF_FRAMEMASK},
-	{"FF_SPR2SUPER",FF_SPR2SUPER},
-	{"FF_SPR2ENDSTATE",FF_SPR2ENDSTATE},
-	{"FF_SPR2MIDSTART",FF_SPR2MIDSTART},
-	{"FF_ANIMATE",FF_ANIMATE},
-	{"FF_RANDOMANIM",FF_RANDOMANIM},
-	{"FF_GLOBALANIM",FF_GLOBALANIM},
-	{"FF_FULLBRIGHT",FF_FULLBRIGHT},
-	{"FF_VERTICALFLIP",FF_VERTICALFLIP},
-	{"FF_HORIZONTALFLIP",FF_HORIZONTALFLIP},
-	{"FF_PAPERSPRITE",FF_PAPERSPRITE},
-	{"FF_TRANSMASK",FF_TRANSMASK},
-	{"FF_TRANSSHIFT",FF_TRANSSHIFT},
-	// new preshifted translucency (used in source)
-	{"FF_TRANS10",FF_TRANS10},
-	{"FF_TRANS20",FF_TRANS20},
-	{"FF_TRANS30",FF_TRANS30},
-	{"FF_TRANS40",FF_TRANS40},
-	{"FF_TRANS50",FF_TRANS50},
-	{"FF_TRANS60",FF_TRANS60},
-	{"FF_TRANS70",FF_TRANS70},
-	{"FF_TRANS80",FF_TRANS80},
-	{"FF_TRANS90",FF_TRANS90},
-	// compatibility
-	// Transparency for SOCs is pre-shifted
-	{"TR_TRANS10",tr_trans10<<FF_TRANSSHIFT},
-	{"TR_TRANS20",tr_trans20<<FF_TRANSSHIFT},
-	{"TR_TRANS30",tr_trans30<<FF_TRANSSHIFT},
-	{"TR_TRANS40",tr_trans40<<FF_TRANSSHIFT},
-	{"TR_TRANS50",tr_trans50<<FF_TRANSSHIFT},
-	{"TR_TRANS60",tr_trans60<<FF_TRANSSHIFT},
-	{"TR_TRANS70",tr_trans70<<FF_TRANSSHIFT},
-	{"TR_TRANS80",tr_trans80<<FF_TRANSSHIFT},
-	{"TR_TRANS90",tr_trans90<<FF_TRANSSHIFT},
-	// Transparency for Lua is not, unless capitalized as above.
-	{"tr_trans10",tr_trans10},
-	{"tr_trans20",tr_trans20},
-	{"tr_trans30",tr_trans30},
-	{"tr_trans40",tr_trans40},
-	{"tr_trans50",tr_trans50},
-	{"tr_trans60",tr_trans60},
-	{"tr_trans70",tr_trans70},
-	{"tr_trans80",tr_trans80},
-	{"tr_trans90",tr_trans90},
-	{"NUMTRANSMAPS",NUMTRANSMAPS},
-
-	// Level flags
-	{"LF_SCRIPTISFILE",LF_SCRIPTISFILE},
-	{"LF_SPEEDMUSIC",LF_SPEEDMUSIC},
-	{"LF_NOSSMUSIC",LF_NOSSMUSIC},
-	{"LF_NORELOAD",LF_NORELOAD},
-	{"LF_NOZONE",LF_NOZONE},
-	{"LF_SAVEGAME",LF_SAVEGAME},
-	{"LF_MIXNIGHTSCOUNTDOWN",LF_MIXNIGHTSCOUNTDOWN},
-	{"LF_NOTITLECARDFIRST",LF_NOTITLECARDFIRST},
-	{"LF_NOTITLECARDRESPAWN",LF_NOTITLECARDRESPAWN},
-	{"LF_NOTITLECARDRECORDATTACK",LF_NOTITLECARDRECORDATTACK},
-	{"LF_NOTITLECARD",LF_NOTITLECARD},
-	{"LF_WARNINGTITLE",LF_WARNINGTITLE},
-	// And map flags
-	{"LF2_HIDEINMENU",LF2_HIDEINMENU},
-	{"LF2_HIDEINSTATS",LF2_HIDEINSTATS},
-	{"LF2_RECORDATTACK",LF2_RECORDATTACK},
-	{"LF2_NIGHTSATTACK",LF2_NIGHTSATTACK},
-	{"LF2_NOVISITNEEDED",LF2_NOVISITNEEDED},
-	{"LF2_WIDEICON",LF2_WIDEICON},
-
-	// Emeralds
-	{"EMERALD1",EMERALD1},
-	{"EMERALD2",EMERALD2},
-	{"EMERALD3",EMERALD3},
-	{"EMERALD4",EMERALD4},
-	{"EMERALD5",EMERALD5},
-	{"EMERALD6",EMERALD6},
-	{"EMERALD7",EMERALD7},
-
-	// SKINCOLOR_ doesn't include these..!
-	{"MAXSKINCOLORS",MAXSKINCOLORS},
-	{"FIRSTSUPERCOLOR",FIRSTSUPERCOLOR},
-	{"NUMSUPERCOLORS",NUMSUPERCOLORS},
-
-	// Precipitation
-	{"PRECIP_NONE",PRECIP_NONE},
-	{"PRECIP_STORM",PRECIP_STORM},
-	{"PRECIP_SNOW",PRECIP_SNOW},
-	{"PRECIP_RAIN",PRECIP_RAIN},
-	{"PRECIP_BLANK",PRECIP_BLANK},
-	{"PRECIP_STORM_NORAIN",PRECIP_STORM_NORAIN},
-	{"PRECIP_STORM_NOSTRIKES",PRECIP_STORM_NOSTRIKES},
-
-	// Shields
-	{"SH_NONE",SH_NONE},
-	// Shield flags
-	{"SH_PROTECTFIRE",SH_PROTECTFIRE},
-	{"SH_PROTECTWATER",SH_PROTECTWATER},
-	{"SH_PROTECTELECTRIC",SH_PROTECTELECTRIC},
-	{"SH_PROTECTSPIKE",SH_PROTECTSPIKE},
-	// Indivisible shields
-	{"SH_PITY",SH_PITY},
-	{"SH_WHIRLWIND",SH_WHIRLWIND},
-	{"SH_ARMAGEDDON",SH_ARMAGEDDON},
-	{"SH_PINK",SH_PINK},
-	// normal shields that use flags
-	{"SH_ATTRACT",SH_ATTRACT},
-	{"SH_ELEMENTAL",SH_ELEMENTAL},
-	// Sonic 3 shields
-	{"SH_FLAMEAURA",SH_FLAMEAURA},
-	{"SH_BUBBLEWRAP",SH_BUBBLEWRAP},
-	{"SH_THUNDERCOIN",SH_THUNDERCOIN},
-	// The force shield uses the lower 8 bits to count how many extra hits are left.
-	{"SH_FORCE",SH_FORCE},
-	{"SH_FORCEHP",SH_FORCEHP}, // to be used as a bitmask only
-	// Mostly for use with Mario mode.
-	{"SH_FIREFLOWER",SH_FIREFLOWER},
-	{"SH_STACK",SH_STACK},
-	{"SH_NOSTACK",SH_NOSTACK},
-
-	// Carrying
-	{"CR_NONE",CR_NONE},
-	{"CR_GENERIC",CR_GENERIC},
-	{"CR_PLAYER",CR_PLAYER},
-	{"CR_NIGHTSMODE",CR_NIGHTSMODE},
-	{"CR_NIGHTSFALL",CR_NIGHTSFALL},
-	{"CR_BRAKGOOP",CR_BRAKGOOP},
-	{"CR_ZOOMTUBE",CR_ZOOMTUBE},
-	{"CR_ROPEHANG",CR_ROPEHANG},
-	{"CR_MACESPIN",CR_MACESPIN},
-	{"CR_MINECART",CR_MINECART},
-	{"CR_ROLLOUT",CR_ROLLOUT},
-	{"CR_PTERABYTE",CR_PTERABYTE},
-	{"CR_DUSTDEVIL",CR_DUSTDEVIL},
-
-	// Ring weapons (ringweapons_t)
-	// Useful for A_GiveWeapon
-	{"RW_AUTO",RW_AUTO},
-	{"RW_BOUNCE",RW_BOUNCE},
-	{"RW_SCATTER",RW_SCATTER},
-	{"RW_GRENADE",RW_GRENADE},
-	{"RW_EXPLODE",RW_EXPLODE},
-	{"RW_RAIL",RW_RAIL},
-
-	// Character flags (skinflags_t)
-	{"SF_SUPER",SF_SUPER},
-	{"SF_NOSUPERSPIN",SF_NOSUPERSPIN},
-	{"SF_NOSPINDASHDUST",SF_NOSPINDASHDUST},
-	{"SF_HIRES",SF_HIRES},
-	{"SF_NOSKID",SF_NOSKID},
-	{"SF_NOSPEEDADJUST",SF_NOSPEEDADJUST},
-	{"SF_RUNONWATER",SF_RUNONWATER},
-	{"SF_NOJUMPSPIN",SF_NOJUMPSPIN},
-	{"SF_NOJUMPDAMAGE",SF_NOJUMPDAMAGE},
-	{"SF_STOMPDAMAGE",SF_STOMPDAMAGE},
-	{"SF_MARIODAMAGE",SF_MARIODAMAGE},
-	{"SF_MACHINE",SF_MACHINE},
-	{"SF_DASHMODE",SF_DASHMODE},
-	{"SF_FASTEDGE",SF_FASTEDGE},
-	{"SF_MULTIABILITY",SF_MULTIABILITY},
-	{"SF_NONIGHTSROTATION",SF_NONIGHTSROTATION},
-	{"SF_NONIGHTSSUPER",SF_NONIGHTSSUPER},
-	{"SF_NOSUPERSPRITES",SF_NOSUPERSPRITES},
-	{"SF_NOSUPERJUMPBOOST",SF_NOSUPERJUMPBOOST},
-
-	// Dashmode constants
-	{"DASHMODE_THRESHOLD",DASHMODE_THRESHOLD},
-	{"DASHMODE_MAX",DASHMODE_MAX},
-
-	// Character abilities!
-	// Primary
-	{"CA_NONE",CA_NONE}, // now slot 0!
-	{"CA_THOK",CA_THOK},
-	{"CA_FLY",CA_FLY},
-	{"CA_GLIDEANDCLIMB",CA_GLIDEANDCLIMB},
-	{"CA_HOMINGTHOK",CA_HOMINGTHOK},
-	{"CA_DOUBLEJUMP",CA_DOUBLEJUMP},
-	{"CA_FLOAT",CA_FLOAT},
-	{"CA_SLOWFALL",CA_SLOWFALL},
-	{"CA_SWIM",CA_SWIM},
-	{"CA_TELEKINESIS",CA_TELEKINESIS},
-	{"CA_FALLSWITCH",CA_FALLSWITCH},
-	{"CA_JUMPBOOST",CA_JUMPBOOST},
-	{"CA_AIRDRILL",CA_AIRDRILL},
-	{"CA_JUMPTHOK",CA_JUMPTHOK},
-	{"CA_BOUNCE",CA_BOUNCE},
-	{"CA_TWINSPIN",CA_TWINSPIN},
-	// Secondary
-	{"CA2_NONE",CA2_NONE}, // now slot 0!
-	{"CA2_SPINDASH",CA2_SPINDASH},
-	{"CA2_GUNSLINGER",CA2_GUNSLINGER},
-	{"CA2_MELEE",CA2_MELEE},
-
-	// Sound flags
-	{"SF_TOTALLYSINGLE",SF_TOTALLYSINGLE},
-	{"SF_NOMULTIPLESOUND",SF_NOMULTIPLESOUND},
-	{"SF_OUTSIDESOUND",SF_OUTSIDESOUND},
-	{"SF_X4AWAYSOUND",SF_X4AWAYSOUND},
-	{"SF_X8AWAYSOUND",SF_X8AWAYSOUND},
-	{"SF_NOINTERRUPT",SF_NOINTERRUPT},
-	{"SF_X2AWAYSOUND",SF_X2AWAYSOUND},
-
-	// Global emblem var flags
-	{"GE_NIGHTSPULL",GE_NIGHTSPULL},
-	{"GE_NIGHTSITEM",GE_NIGHTSITEM},
-
-	// Map emblem var flags
-	{"ME_ALLEMERALDS",ME_ALLEMERALDS},
-	{"ME_ULTIMATE",ME_ULTIMATE},
-	{"ME_PERFECT",ME_PERFECT},
-
-	// p_local.h constants
-	{"FLOATSPEED",FLOATSPEED},
-	{"MAXSTEPMOVE",MAXSTEPMOVE},
-	{"USERANGE",USERANGE},
-	{"MELEERANGE",MELEERANGE},
-	{"MISSILERANGE",MISSILERANGE},
-	{"ONFLOORZ",ONFLOORZ}, // INT32_MIN
-	{"ONCEILINGZ",ONCEILINGZ}, //INT32_MAX
-	// for P_FlashPal
-	{"PAL_WHITE",PAL_WHITE},
-	{"PAL_MIXUP",PAL_MIXUP},
-	{"PAL_RECYCLE",PAL_RECYCLE},
-	{"PAL_NUKE",PAL_NUKE},
-	// for P_DamageMobj
-	//// Damage types
-	{"DMG_WATER",DMG_WATER},
-	{"DMG_FIRE",DMG_FIRE},
-	{"DMG_ELECTRIC",DMG_ELECTRIC},
-	{"DMG_SPIKE",DMG_SPIKE},
-	{"DMG_NUKE",DMG_NUKE},
-	//// Death types
-	{"DMG_INSTAKILL",DMG_INSTAKILL},
-	{"DMG_DROWNED",DMG_DROWNED},
-	{"DMG_SPACEDROWN",DMG_SPACEDROWN},
-	{"DMG_DEATHPIT",DMG_DEATHPIT},
-	{"DMG_CRUSHED",DMG_CRUSHED},
-	{"DMG_SPECTATOR",DMG_SPECTATOR},
-	//// Masks
-	{"DMG_CANHURTSELF",DMG_CANHURTSELF},
-	{"DMG_DEATHMASK",DMG_DEATHMASK},
-
-	// Intermission types
-	{"int_none",int_none},
-	{"int_coop",int_coop},
-	{"int_match",int_match},
-	{"int_teammatch",int_teammatch},
-	//{"int_tag",int_tag},
-	{"int_ctf",int_ctf},
-	{"int_spec",int_spec},
-	{"int_race",int_race},
-	{"int_comp",int_comp},
-
-	// Jingles (jingletype_t)
-	{"JT_NONE",JT_NONE},
-	{"JT_OTHER",JT_OTHER},
-	{"JT_MASTER",JT_MASTER},
-	{"JT_1UP",JT_1UP},
-	{"JT_SHOES",JT_SHOES},
-	{"JT_INV",JT_INV},
-	{"JT_MINV",JT_MINV},
-	{"JT_DROWN",JT_DROWN},
-	{"JT_SUPER",JT_SUPER},
-	{"JT_GOVER",JT_GOVER},
-	{"JT_NIGHTSTIMEOUT",JT_NIGHTSTIMEOUT},
-	{"JT_SSTIMEOUT",JT_SSTIMEOUT},
-	// {"JT_LCLEAR",JT_LCLEAR},
-	// {"JT_RACENT",JT_RACENT},
-	// {"JT_CONTSC",JT_CONTSC},
-
-	// Player state (playerstate_t)
-	{"PST_LIVE",PST_LIVE}, // Playing or camping.
-	{"PST_DEAD",PST_DEAD}, // Dead on the ground, view follows killer.
-	{"PST_REBORN",PST_REBORN}, // Ready to restart/respawn???
-
-	// Player animation (panim_t)
-	{"PA_ETC",PA_ETC},
-	{"PA_IDLE",PA_IDLE},
-	{"PA_EDGE",PA_EDGE},
-	{"PA_WALK",PA_WALK},
-	{"PA_RUN",PA_RUN},
-	{"PA_DASH",PA_DASH},
-	{"PA_PAIN",PA_PAIN},
-	{"PA_ROLL",PA_ROLL},
-	{"PA_JUMP",PA_JUMP},
-	{"PA_SPRING",PA_SPRING},
-	{"PA_FALL",PA_FALL},
-	{"PA_ABILITY",PA_ABILITY},
-	{"PA_ABILITY2",PA_ABILITY2},
-	{"PA_RIDE",PA_RIDE},
-
-	// Current weapon
-	{"WEP_AUTO",WEP_AUTO},
-	{"WEP_BOUNCE",WEP_BOUNCE},
-	{"WEP_SCATTER",WEP_SCATTER},
-	{"WEP_GRENADE",WEP_GRENADE},
-	{"WEP_EXPLODE",WEP_EXPLODE},
-	{"WEP_RAIL",WEP_RAIL},
-	{"NUM_WEAPONS",NUM_WEAPONS},
-
-	// Value for infinite lives
-	{"INFLIVES",INFLIVES},
-
-	// Got Flags, for player->gotflag!
-	// Used to be MF_ for some stupid reason, now they're GF_ to stop them looking like mobjflags
-	{"GF_REDFLAG",GF_REDFLAG},
-	{"GF_BLUEFLAG",GF_BLUEFLAG},
-
-	// Customisable sounds for Skins, from sounds.h
-	{"SKSSPIN",SKSSPIN},
-	{"SKSPUTPUT",SKSPUTPUT},
-	{"SKSPUDPUD",SKSPUDPUD},
-	{"SKSPLPAN1",SKSPLPAN1}, // Ouchies
-	{"SKSPLPAN2",SKSPLPAN2},
-	{"SKSPLPAN3",SKSPLPAN3},
-	{"SKSPLPAN4",SKSPLPAN4},
-	{"SKSPLDET1",SKSPLDET1}, // Deaths
-	{"SKSPLDET2",SKSPLDET2},
-	{"SKSPLDET3",SKSPLDET3},
-	{"SKSPLDET4",SKSPLDET4},
-	{"SKSPLVCT1",SKSPLVCT1}, // Victories
-	{"SKSPLVCT2",SKSPLVCT2},
-	{"SKSPLVCT3",SKSPLVCT3},
-	{"SKSPLVCT4",SKSPLVCT4},
-	{"SKSTHOK",SKSTHOK},
-	{"SKSSPNDSH",SKSSPNDSH},
-	{"SKSZOOM",SKSZOOM},
-	{"SKSSKID",SKSSKID},
-	{"SKSGASP",SKSGASP},
-	{"SKSJUMP",SKSJUMP},
-
-	// 3D Floor/Fake Floor/FOF/whatever flags
-	{"FF_EXISTS",FF_EXISTS},                   ///< Always set, to check for validity.
-	{"FF_BLOCKPLAYER",FF_BLOCKPLAYER},         ///< Solid to player, but nothing else
-	{"FF_BLOCKOTHERS",FF_BLOCKOTHERS},         ///< Solid to everything but player
-	{"FF_SOLID",FF_SOLID},                     ///< Clips things.
-	{"FF_RENDERSIDES",FF_RENDERSIDES},         ///< Renders the sides.
-	{"FF_RENDERPLANES",FF_RENDERPLANES},       ///< Renders the floor/ceiling.
-	{"FF_RENDERALL",FF_RENDERALL},             ///< Renders everything.
-	{"FF_SWIMMABLE",FF_SWIMMABLE},             ///< Is a water block.
-	{"FF_NOSHADE",FF_NOSHADE},                 ///< Messes with the lighting?
-	{"FF_CUTSOLIDS",FF_CUTSOLIDS},             ///< Cuts out hidden solid pixels.
-	{"FF_CUTEXTRA",FF_CUTEXTRA},               ///< Cuts out hidden translucent pixels.
-	{"FF_CUTLEVEL",FF_CUTLEVEL},               ///< Cuts out all hidden pixels.
-	{"FF_CUTSPRITES",FF_CUTSPRITES},           ///< Final step in making 3D water.
-	{"FF_BOTHPLANES",FF_BOTHPLANES},           ///< Render inside and outside planes.
-	{"FF_EXTRA",FF_EXTRA},                     ///< Gets cut by ::FF_CUTEXTRA.
-	{"FF_TRANSLUCENT",FF_TRANSLUCENT},         ///< See through!
-	{"FF_FOG",FF_FOG},                         ///< Fog "brush."
-	{"FF_INVERTPLANES",FF_INVERTPLANES},       ///< Only render inside planes.
-	{"FF_ALLSIDES",FF_ALLSIDES},               ///< Render inside and outside sides.
-	{"FF_INVERTSIDES",FF_INVERTSIDES},         ///< Only render inside sides.
-	{"FF_DOUBLESHADOW",FF_DOUBLESHADOW},       ///< Make two lightlist entries to reset light?
-	{"FF_FLOATBOB",FF_FLOATBOB},               ///< Floats on water and bobs if you step on it.
-	{"FF_NORETURN",FF_NORETURN},               ///< Used with ::FF_CRUMBLE. Will not return to its original position after falling.
-	{"FF_CRUMBLE",FF_CRUMBLE},                 ///< Falls 2 seconds after being stepped on, and randomly brings all touching crumbling 3dfloors down with it, providing their master sectors share the same tag (allows crumble platforms above or below, to also exist).
-	{"FF_SHATTERBOTTOM",FF_SHATTERBOTTOM},     ///< Used with ::FF_BUSTUP. Like FF_SHATTER, but only breaks from the bottom. Good for springing up through rubble.
-	{"FF_MARIO",FF_MARIO},                     ///< Acts like a question block when hit from underneath. Goodie spawned at top is determined by master sector.
-	{"FF_BUSTUP",FF_BUSTUP},                   ///< You can spin through/punch this block and it will crumble!
-	{"FF_QUICKSAND",FF_QUICKSAND},             ///< Quicksand!
-	{"FF_PLATFORM",FF_PLATFORM},               ///< You can jump up through this to the top.
-	{"FF_REVERSEPLATFORM",FF_REVERSEPLATFORM}, ///< A fall-through floor in normal gravity, a platform in reverse gravity.
-	{"FF_INTANGIBLEFLATS",FF_INTANGIBLEFLATS}, ///< Both flats are intangible, but the sides are still solid.
-	{"FF_INTANGABLEFLATS",FF_INTANGIBLEFLATS}, ///< Both flats are intangable, but the sides are still solid.
-	{"FF_SHATTER",FF_SHATTER},                 ///< Used with ::FF_BUSTUP. Bustable on mere touch.
-	{"FF_SPINBUST",FF_SPINBUST},               ///< Used with ::FF_BUSTUP. Also bustable if you're in your spinning frames.
-	{"FF_STRONGBUST",FF_STRONGBUST},           ///< Used with ::FF_BUSTUP. Only bustable by "strong" characters (Knuckles) and abilities (bouncing, twinspin, melee).
-	{"FF_RIPPLE",FF_RIPPLE},                   ///< Ripple the flats
-	{"FF_COLORMAPONLY",FF_COLORMAPONLY},       ///< Only copy the colormap, not the lightlevel
-	{"FF_GOOWATER",FF_GOOWATER},               ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
-
-#ifdef HAVE_LUA_SEGS
-	// Node flags
-	{"NF_SUBSECTOR",NF_SUBSECTOR}, // Indicate a leaf.
-#endif
-
-	// Slope flags
-	{"SL_NOPHYSICS",SL_NOPHYSICS},
-	{"SL_DYNAMIC",SL_DYNAMIC},
-
-	// Angles
-	{"ANG1",ANG1},
-	{"ANG2",ANG2},
-	{"ANG10",ANG10},
-	{"ANG15",ANG15},
-	{"ANG20",ANG20},
-	{"ANG30",ANG30},
-	{"ANG60",ANG60},
-	{"ANG64h",ANG64h},
-	{"ANG105",ANG105},
-	{"ANG210",ANG210},
-	{"ANG255",ANG255},
-	{"ANG340",ANG340},
-	{"ANG350",ANG350},
-	{"ANGLE_11hh",ANGLE_11hh},
-	{"ANGLE_22h",ANGLE_22h},
-	{"ANGLE_45",ANGLE_45},
-	{"ANGLE_67h",ANGLE_67h},
-	{"ANGLE_90",ANGLE_90},
-	{"ANGLE_112h",ANGLE_112h},
-	{"ANGLE_135",ANGLE_135},
-	{"ANGLE_157h",ANGLE_157h},
-	{"ANGLE_180",ANGLE_180},
-	{"ANGLE_202h",ANGLE_202h},
-	{"ANGLE_225",ANGLE_225},
-	{"ANGLE_247h",ANGLE_247h},
-	{"ANGLE_270",ANGLE_270},
-	{"ANGLE_292h",ANGLE_292h},
-	{"ANGLE_315",ANGLE_315},
-	{"ANGLE_337h",ANGLE_337h},
-	{"ANGLE_MAX",ANGLE_MAX},
-
-	// P_Chase directions (dirtype_t)
-	{"DI_NODIR",DI_NODIR},
-	{"DI_EAST",DI_EAST},
-	{"DI_NORTHEAST",DI_NORTHEAST},
-	{"DI_NORTH",DI_NORTH},
-	{"DI_NORTHWEST",DI_NORTHWEST},
-	{"DI_WEST",DI_WEST},
-	{"DI_SOUTHWEST",DI_SOUTHWEST},
-	{"DI_SOUTH",DI_SOUTH},
-	{"DI_SOUTHEAST",DI_SOUTHEAST},
-	{"NUMDIRS",NUMDIRS},
-
-	// Sprite rotation axis (rotaxis_t)
-	{"ROTAXIS_X",ROTAXIS_X},
-	{"ROTAXIS_Y",ROTAXIS_Y},
-	{"ROTAXIS_Z",ROTAXIS_Z},
-
-	// Buttons (ticcmd_t)
-	{"BT_WEAPONMASK",BT_WEAPONMASK}, //our first four bits.
-	{"BT_WEAPONNEXT",BT_WEAPONNEXT},
-	{"BT_WEAPONPREV",BT_WEAPONPREV},
-	{"BT_ATTACK",BT_ATTACK}, // shoot rings
-	{"BT_USE",BT_USE}, // spin
-	{"BT_CAMLEFT",BT_CAMLEFT}, // turn camera left
-	{"BT_CAMRIGHT",BT_CAMRIGHT}, // turn camera right
-	{"BT_TOSSFLAG",BT_TOSSFLAG},
-	{"BT_JUMP",BT_JUMP},
-	{"BT_FIRENORMAL",BT_FIRENORMAL}, // Fire a normal ring no matter what
-	{"BT_CUSTOM1",BT_CUSTOM1}, // Lua customizable
-	{"BT_CUSTOM2",BT_CUSTOM2}, // Lua customizable
-	{"BT_CUSTOM3",BT_CUSTOM3}, // Lua customizable
-
-	// Lua command registration flags
-	{"COM_ADMIN",COM_ADMIN},
-	{"COM_SPLITSCREEN",COM_SPLITSCREEN},
-	{"COM_LOCAL",COM_LOCAL},
-
-	// cvflags_t
-	{"CV_SAVE",CV_SAVE},
-	{"CV_CALL",CV_CALL},
-	{"CV_NETVAR",CV_NETVAR},
-	{"CV_NOINIT",CV_NOINIT},
-	{"CV_FLOAT",CV_FLOAT},
-	{"CV_NOTINNET",CV_NOTINNET},
-	{"CV_MODIFIED",CV_MODIFIED},
-	{"CV_SHOWMODIF",CV_SHOWMODIF},
-	{"CV_SHOWMODIFONETIME",CV_SHOWMODIFONETIME},
-	{"CV_NOSHOWHELP",CV_NOSHOWHELP},
-	{"CV_HIDEN",CV_HIDEN},
-	{"CV_HIDDEN",CV_HIDEN},
-	{"CV_CHEAT",CV_CHEAT},
-	{"CV_NOLUA",CV_NOLUA},
-
-	// v_video flags
-	{"V_NOSCALEPATCH",V_NOSCALEPATCH},
-	{"V_SMALLSCALEPATCH",V_SMALLSCALEPATCH},
-	{"V_MEDSCALEPATCH",V_MEDSCALEPATCH},
-	{"V_6WIDTHSPACE",V_6WIDTHSPACE},
-	{"V_OLDSPACING",V_OLDSPACING},
-	{"V_MONOSPACE",V_MONOSPACE},
-
-	{"V_MAGENTAMAP",V_MAGENTAMAP},
-	{"V_YELLOWMAP",V_YELLOWMAP},
-	{"V_GREENMAP",V_GREENMAP},
-	{"V_BLUEMAP",V_BLUEMAP},
-	{"V_REDMAP",V_REDMAP},
-	{"V_GRAYMAP",V_GRAYMAP},
-	{"V_ORANGEMAP",V_ORANGEMAP},
-	{"V_SKYMAP",V_SKYMAP},
-	{"V_PURPLEMAP",V_PURPLEMAP},
-	{"V_AQUAMAP",V_AQUAMAP},
-	{"V_PERIDOTMAP",V_PERIDOTMAP},
-	{"V_AZUREMAP",V_AZUREMAP},
-	{"V_BROWNMAP",V_BROWNMAP},
-	{"V_ROSYMAP",V_ROSYMAP},
-	{"V_INVERTMAP",V_INVERTMAP},
-
-	{"V_TRANSLUCENT",V_TRANSLUCENT},
-	{"V_10TRANS",V_10TRANS},
-	{"V_20TRANS",V_20TRANS},
-	{"V_30TRANS",V_30TRANS},
-	{"V_40TRANS",V_40TRANS},
-	{"V_50TRANS",V_TRANSLUCENT}, // alias
-	{"V_60TRANS",V_60TRANS},
-	{"V_70TRANS",V_70TRANS},
-	{"V_80TRANS",V_80TRANS},
-	{"V_90TRANS",V_90TRANS},
-	{"V_HUDTRANSHALF",V_HUDTRANSHALF},
-	{"V_HUDTRANS",V_HUDTRANS},
-	{"V_HUDTRANSDOUBLE",V_HUDTRANSDOUBLE},
-	{"V_AUTOFADEOUT",V_AUTOFADEOUT},
-	{"V_RETURN8",V_RETURN8},
-	{"V_OFFSET",V_OFFSET},
-	{"V_ALLOWLOWERCASE",V_ALLOWLOWERCASE},
-	{"V_FLIP",V_FLIP},
-	{"V_CENTERNAMETAG",V_CENTERNAMETAG},
-	{"V_SNAPTOTOP",V_SNAPTOTOP},
-	{"V_SNAPTOBOTTOM",V_SNAPTOBOTTOM},
-	{"V_SNAPTOLEFT",V_SNAPTOLEFT},
-	{"V_SNAPTORIGHT",V_SNAPTORIGHT},
-	{"V_WRAPX",V_WRAPX},
-	{"V_WRAPY",V_WRAPY},
-	{"V_NOSCALESTART",V_NOSCALESTART},
-	{"V_PERPLAYER",V_PERPLAYER},
-
-	{"V_PARAMMASK",V_PARAMMASK},
-	{"V_SCALEPATCHMASK",V_SCALEPATCHMASK},
-	{"V_SPACINGMASK",V_SPACINGMASK},
-	{"V_CHARCOLORMASK",V_CHARCOLORMASK},
-	{"V_ALPHAMASK",V_ALPHAMASK},
-
-	{"V_CHARCOLORSHIFT",V_CHARCOLORSHIFT},
-	{"V_ALPHASHIFT",V_ALPHASHIFT},
-
-	//Kick Reasons
-	{"KR_KICK",KR_KICK},
-	{"KR_PINGLIMIT",KR_PINGLIMIT},
-	{"KR_SYNCH",KR_SYNCH},
-	{"KR_TIMEOUT",KR_TIMEOUT},
-	{"KR_BAN",KR_BAN},
-	{"KR_LEAVE",KR_LEAVE},
-
-	// translation colormaps
-	{"TC_DEFAULT",TC_DEFAULT},
-	{"TC_BOSS",TC_BOSS},
-	{"TC_METALSONIC",TC_METALSONIC},
-	{"TC_ALLWHITE",TC_ALLWHITE},
-	{"TC_RAINBOW",TC_RAINBOW},
-	{"TC_BLINK",TC_BLINK},
-	{"TC_DASHMODE",TC_DASHMODE},
-
-	// marathonmode flags
-	{"MA_INIT",MA_INIT},
-	{"MA_RUNNING",MA_RUNNING},
-	{"MA_NOCUTSCENES",MA_NOCUTSCENES},
-	{"MA_INGAME",MA_INGAME},
-
-	{NULL,0}
-};
-
-static mobjtype_t get_mobjtype(const char *word)
-{ // Returns the value of MT_ enumerations
-	mobjtype_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("MT_",word,3))
-		word += 3; // take off the MT_
-	for (i = 0; i < NUMMOBJFREESLOTS; i++) {
-		if (!FREE_MOBJS[i])
-			break;
-		if (fastcmp(word, FREE_MOBJS[i]))
-			return MT_FIRSTFREESLOT+i;
-	}
-	for (i = 0; i < MT_FIRSTFREESLOT; i++)
-		if (fastcmp(word, MOBJTYPE_LIST[i]+3))
-			return i;
-	deh_warning("Couldn't find mobjtype named 'MT_%s'",word);
-	return MT_NULL;
-}
-
-static statenum_t get_state(const char *word)
-{ // Returns the value of S_ enumerations
-	statenum_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("S_",word,2))
-		word += 2; // take off the S_
-	for (i = 0; i < NUMSTATEFREESLOTS; i++) {
-		if (!FREE_STATES[i])
-			break;
-		if (fastcmp(word, FREE_STATES[i]))
-			return S_FIRSTFREESLOT+i;
-	}
-	for (i = 0; i < S_FIRSTFREESLOT; i++)
-		if (fastcmp(word, STATE_LIST[i]+2))
-			return i;
-	deh_warning("Couldn't find state named 'S_%s'",word);
-	return S_NULL;
-}
-
-skincolornum_t get_skincolor(const char *word)
-{ // Returns the value of SKINCOLOR_ enumerations
-	skincolornum_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("SKINCOLOR_",word,10))
-		word += 10; // take off the SKINCOLOR_
-	for (i = 0; i < NUMCOLORFREESLOTS; i++) {
-		if (!FREE_SKINCOLORS[i])
-			break;
-		if (fastcmp(word, FREE_SKINCOLORS[i]))
-			return SKINCOLOR_FIRSTFREESLOT+i;
-	}
-	for (i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++)
-		if (fastcmp(word, COLOR_ENUMS[i]))
-			return i;
-	deh_warning("Couldn't find skincolor named 'SKINCOLOR_%s'",word);
-	return SKINCOLOR_GREEN;
-}
-
-static spritenum_t get_sprite(const char *word)
-{ // Returns the value of SPR_ enumerations
-	spritenum_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("SPR_",word,4))
-		word += 4; // take off the SPR_
-	for (i = 0; i < NUMSPRITES; i++)
-		if (!sprnames[i][4] && memcmp(word,sprnames[i],4)==0)
-			return i;
-	deh_warning("Couldn't find sprite named 'SPR_%s'",word);
-	return SPR_NULL;
-}
-
-static playersprite_t get_sprite2(const char *word)
-{ // Returns the value of SPR2_ enumerations
-	playersprite_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("SPR2_",word,5))
-		word += 5; // take off the SPR2_
-	for (i = 0; i < NUMPLAYERSPRITES; i++)
-		if (!spr2names[i][4] && memcmp(word,spr2names[i],4)==0)
-			return i;
-	deh_warning("Couldn't find sprite named 'SPR2_%s'",word);
-	return SPR2_STND;
-}
-
-static sfxenum_t get_sfx(const char *word)
-{ // Returns the value of SFX_ enumerations
-	sfxenum_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("SFX_",word,4))
-		word += 4; // take off the SFX_
-	else if (fastncmp("DS",word,2))
-		word += 2; // take off the DS
-	for (i = 0; i < NUMSFX; i++)
-		if (S_sfx[i].name && fasticmp(word, S_sfx[i].name))
-			return i;
-	deh_warning("Couldn't find sfx named 'SFX_%s'",word);
-	return sfx_None;
-}
-
-#ifdef MUSICSLOT_COMPATIBILITY
-static UINT16 get_mus(const char *word, UINT8 dehacked_mode)
-{ // Returns the value of MUS_ enumerations
-	UINT16 i;
-	char lumptmp[4];
-
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (!word[2] && toupper(word[0]) >= 'A' && toupper(word[0]) <= 'Z')
-		return (UINT16)M_MapNumber(word[0], word[1]);
-
-	if (fastncmp("MUS_",word,4))
-		word += 4; // take off the MUS_
-	else if (fastncmp("O_",word,2) || fastncmp("D_",word,2))
-		word += 2; // take off the O_ or D_
-
-	strncpy(lumptmp, word, 4);
-	lumptmp[3] = 0;
-	if (fasticmp("MAP",lumptmp))
-	{
-		word += 3;
-		if (toupper(word[0]) >= 'A' && toupper(word[0]) <= 'Z')
-			return (UINT16)M_MapNumber(word[0], word[1]);
-		else if ((i = atoi(word)))
-			return i;
-
-		word -= 3;
-		if (dehacked_mode)
-			deh_warning("Couldn't find music named 'MUS_%s'",word);
-		return 0;
-	}
-	for (i = 0; compat_special_music_slots[i][0]; ++i)
-		if (fasticmp(word, compat_special_music_slots[i]))
-			return i + 1036;
-	if (dehacked_mode)
-		deh_warning("Couldn't find music named 'MUS_%s'",word);
-	return 0;
-}
-#endif
-
-static hudnum_t get_huditem(const char *word)
-{ // Returns the value of HUD_ enumerations
-	hudnum_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("HUD_",word,4))
-		word += 4; // take off the HUD_
-	for (i = 0; i < NUMHUDITEMS; i++)
-		if (fastcmp(word, HUDITEMS_LIST[i]))
-			return i;
-	deh_warning("Couldn't find huditem named 'HUD_%s'",word);
-	return HUD_LIVES;
-}
-
-static menutype_t get_menutype(const char *word)
-{ // Returns the value of MN_ enumerations
-	menutype_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("MN_",word,3))
-		word += 3; // take off the MN_
-	for (i = 0; i < NUMMENUTYPES; i++)
-		if (fastcmp(word, MENUTYPES_LIST[i]))
-			return i;
-	deh_warning("Couldn't find menutype named 'MN_%s'",word);
-	return MN_NONE;
-}
-
-/*static INT16 get_gametype(const char *word)
-{ // Returns the value of GT_ enumerations
-	INT16 i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("GT_",word,3))
-		word += 3; // take off the GT_
-	for (i = 0; i < NUMGAMETYPES; i++)
-		if (fastcmp(word, Gametype_ConstantNames[i]+3))
-			return i;
-	deh_warning("Couldn't find gametype named 'GT_%s'",word);
-	return GT_COOP;
-}
-
-static powertype_t get_power(const char *word)
-{ // Returns the value of pw_ enumerations
-	powertype_t i;
-	if (*word >= '0' && *word <= '9')
-		return atoi(word);
-	if (fastncmp("PW_",word,3))
-		word += 3; // take off the pw_
-	for (i = 0; i < NUMPOWERS; i++)
-		if (fastcmp(word, POWERS_LIST[i]))
-			return i;
-	deh_warning("Couldn't find power named 'pw_%s'",word);
-	return pw_invulnerability;
-}*/
-
-/// \todo Make ANY of this completely over-the-top math craziness obey the order of operations.
-static fixed_t op_mul(fixed_t a, fixed_t b) { return a*b; }
-static fixed_t op_div(fixed_t a, fixed_t b) { return a/b; }
-static fixed_t op_add(fixed_t a, fixed_t b) { return a+b; }
-static fixed_t op_sub(fixed_t a, fixed_t b) { return a-b; }
-static fixed_t op_or(fixed_t a, fixed_t b) { return a|b; }
-static fixed_t op_and(fixed_t a, fixed_t b) { return a&b; }
-static fixed_t op_lshift(fixed_t a, fixed_t b) { return a<<b; }
-static fixed_t op_rshift(fixed_t a, fixed_t b) { return a>>b; }
-
-struct {
-	const char c;
-	fixed_t (*v)(fixed_t,fixed_t);
-} OPERATIONS[] = {
-	{'*',op_mul},
-	{'/',op_div},
-	{'+',op_add},
-	{'-',op_sub},
-	{'|',op_or},
-	{'&',op_and},
-	{'<',op_lshift},
-	{'>',op_rshift},
-	{0,NULL}
-};
-
-// Returns the full word, cut at the first symbol or whitespace
-/*static char *read_word(const char *line)
-{
-	// Part 1: You got the start of the word, now find the end.
-  const char *p;
-	INT32 i;
-	for (p = line+1; *p; p++) {
-		if (*p == ' ' || *p == '\t')
-			break;
-		for (i = 0; OPERATIONS[i].c; i++)
-			if (*p == OPERATIONS[i].c) {
-				i = -1;
-				break;
-			}
-		if (i == -1)
-			break;
-	}
-
-	// Part 2: Make a copy of the word and return it.
-	{
-		size_t len = (p-line);
-		char *word = malloc(len+1);
-		M_Memcpy(word,line,len);
-		word[len] = '\0';
-		return word;
-	}
-}
-
-static INT32 operation_pad(const char **word)
-{ // Brings word the next operation and returns the operation number.
-	INT32 i;
-	for (; **word; (*word)++) {
-		if (**word == ' ' || **word == '\t')
-			continue;
-		for (i = 0; OPERATIONS[i].c; i++)
-			if (**word == OPERATIONS[i].c)
-			{
-				if ((**word == '<' && *(*word+1) == '<') || (**word == '>' && *(*word+1) == '>')) (*word)++; // These operations are two characters long.
-				else if (**word == '<' || **word == '>') continue; // ... do not accept one character long.
-				(*word)++;
-				return i;
-			}
-		deh_warning("Unknown operation '%c'",**word);
-		return -1;
-	}
-	return -1;
-}
-
-static void const_warning(const char *type, const char *word)
-{
-	deh_warning("Couldn't find %s named '%s'",type,word);
-}
-
-static fixed_t find_const(const char **rword)
-{ // Finds the value of constants and returns it, bringing word to the next operation.
-	INT32 i;
-	fixed_t r;
-	char *word = read_word(*rword);
-	*rword += strlen(word);
-	if ((*word >= '0' && *word <= '9') || *word == '-') { // Parse a number
-		r = atoi(word);
-		free(word);
-		return r;
-	}
-	if (!*(word+1) && // Turn a single A-z symbol into numbers, like sprite frames.
-	 ((*word >= 'A' && *word <= 'Z') || (*word >= 'a' && *word <= 'z'))) {
-		r = R_Char2Frame(*word);
-		free(word);
-		return r;
-	}
-	if (fastncmp("MF_", word, 3)) {
-		char *p = word+3;
-		for (i = 0; MOBJFLAG_LIST[i]; i++)
-			if (fastcmp(p, MOBJFLAG_LIST[i])) {
-				free(word);
-				return (1<<i);
-			}
-
-		// Not found error
-		const_warning("mobj flag",word);
-		free(word);
-		return 0;
-	}
-	else if (fastncmp("MF2_", word, 4)) {
-		char *p = word+4;
-		for (i = 0; MOBJFLAG2_LIST[i]; i++)
-			if (fastcmp(p, MOBJFLAG2_LIST[i])) {
-				free(word);
-				return (1<<i);
-			}
-
-		// Not found error
-		const_warning("mobj flag2",word);
-		free(word);
-		return 0;
-	}
-	else if (fastncmp("MFE_", word, 4)) {
-		char *p = word+4;
-		for (i = 0; MOBJEFLAG_LIST[i]; i++)
-			if (fastcmp(p, MOBJEFLAG_LIST[i])) {
-				free(word);
-				return (1<<i);
-			}
-
-		// Not found error
-		const_warning("mobj eflag",word);
-		free(word);
-		return 0;
-	}
-	else if (fastncmp("PF_", word, 3)) {
-		char *p = word+3;
-		for (i = 0; PLAYERFLAG_LIST[i]; i++)
-			if (fastcmp(p, PLAYERFLAG_LIST[i])) {
-				free(word);
-				return (1<<i);
-			}
-		if (fastcmp(p, "FULLSTASIS"))
-			return PF_FULLSTASIS;
-
-		// Not found error
-		const_warning("player flag",word);
-		free(word);
-		return 0;
-	}
-	else if (fastncmp("S_",word,2)) {
-		r = get_state(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("SKINCOLOR_",word,10)) {
-		r = get_skincolor(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("MT_",word,3)) {
-		r = get_mobjtype(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("SPR_",word,4)) {
-		r = get_sprite(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("SFX_",word,4) || fastncmp("DS",word,2)) {
-		r = get_sfx(word);
-		free(word);
-		return r;
-	}
-#ifdef MUSICSLOT_COMPATIBILITY
-	else if (fastncmp("MUS_",word,4) || fastncmp("O_",word,2)) {
-		r = get_mus(word, true);
-		free(word);
-		return r;
-	}
-#endif
-	else if (fastncmp("PW_",word,3)) {
-		r = get_power(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("MN_",word,3)) {
-		r = get_menutype(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("GT_",word,3)) {
-		r = get_gametype(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("GTR_", word, 4)) {
-		char *p = word+4;
-		for (i = 0; GAMETYPERULE_LIST[i]; i++)
-			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
-				free(word);
-				return (1<<i);
-			}
-
-		// Not found error
-		const_warning("game type rule",word);
-		free(word);
-		return 0;
-	}
-	else if (fastncmp("TOL_", word, 4)) {
-		char *p = word+4;
-		for (i = 0; TYPEOFLEVEL[i].name; i++)
-			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
-				free(word);
-				return TYPEOFLEVEL[i].flag;
-			}
-
-		// Not found error
-		const_warning("typeoflevel",word);
-		free(word);
-		return 0;
-	}
-	else if (fastncmp("HUD_",word,4)) {
-		r = get_huditem(word);
-		free(word);
-		return r;
-	}
-	else if (fastncmp("GRADE_",word,6))
-	{
-		char *p = word+6;
-		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
-			if (*p == NIGHTSGRADE_LIST[i])
-			{
-				free(word);
-				return i;
-			}
-		const_warning("NiGHTS grade",word);
-		free(word);
-		return 0;
-	}
-	for (i = 0; INT_CONST[i].n; i++)
-		if (fastcmp(word,INT_CONST[i].n)) {
-			free(word);
-			return INT_CONST[i].v;
-		}
-
-	// Not found error.
-	const_warning("constant",word);
-	free(word);
-	return 0;
-}*/
-
-// Loops through every constant and operation in word and performs its calculations, returning the final value.
-fixed_t get_number(const char *word)
-{
-	return LUA_EvalMath(word);
-
-	/*// DESPERATELY NEEDED: Order of operations support! :x
-	fixed_t i = find_const(&word);
-	INT32 o;
-	while(*word) {
-		o = operation_pad(&word);
-		if (o != -1)
-			i = OPERATIONS[o].v(i,find_const(&word));
-		else
-			break;
-	}
-	return i;*/
-}
-
-void DEH_Check(void)
-{
-#if defined(_DEBUG) || defined(PARANOIA)
-	const size_t dehstates = sizeof(STATE_LIST)/sizeof(const char*);
-	const size_t dehmobjs  = sizeof(MOBJTYPE_LIST)/sizeof(const char*);
-	const size_t dehpowers = sizeof(POWERS_LIST)/sizeof(const char*);
-	const size_t dehcolors = sizeof(COLOR_ENUMS)/sizeof(const char*);
-
-	if (dehstates != S_FIRSTFREESLOT)
-		I_Error("You forgot to update the Dehacked states list, you dolt!\n(%d states defined, versus %s in the Dehacked list)\n", S_FIRSTFREESLOT, sizeu1(dehstates));
-
-	if (dehmobjs != MT_FIRSTFREESLOT)
-		I_Error("You forgot to update the Dehacked mobjtype list, you dolt!\n(%d mobj types defined, versus %s in the Dehacked list)\n", MT_FIRSTFREESLOT, sizeu1(dehmobjs));
-
-	if (dehpowers != NUMPOWERS)
-		I_Error("You forgot to update the Dehacked powers list, you dolt!\n(%d powers defined, versus %s in the Dehacked list)\n", NUMPOWERS, sizeu1(dehpowers));
-
-	if (dehcolors != SKINCOLOR_FIRSTFREESLOT)
-		I_Error("You forgot to update the Dehacked colors list, you dolt!\n(%d colors defined, versus %s in the Dehacked list)\n", SKINCOLOR_FIRSTFREESLOT, sizeu1(dehcolors));
-#endif
-}
-
-#include "lua_script.h"
-#include "lua_libs.h"
-
-// freeslot takes a name (string only!)
-// and allocates it to the appropriate free slot.
-// Returns the slot number allocated for it or nil if failed.
-// ex. freeslot("MT_MYTHING","S_MYSTATE1","S_MYSTATE2")
-// TODO: Error checking! @.@; There's currently no way to know which ones failed and why!
-//
-static inline int lib_freeslot(lua_State *L)
-{
-	int n = lua_gettop(L);
-	int r = 0; // args returned
-	char *s, *type,*word;
-
-	if (!lua_lumploading)
-		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
-
-	while (n-- > 0)
-	{
-		s = Z_StrDup(luaL_checkstring(L,1));
-		type = strtok(s, "_");
-		if (type)
-			strupr(type);
-		else {
-			Z_Free(s);
-			return luaL_error(L, "Unknown enum type in '%s'\n", luaL_checkstring(L, 1));
-		}
-
-		word = strtok(NULL, "\n");
-		if (word)
-			strupr(word);
-		else {
-			Z_Free(s);
-			return luaL_error(L, "Missing enum name in '%s'\n", luaL_checkstring(L, 1));
-		}
-		if (fastcmp(type, "SFX")) {
-			sfxenum_t sfx;
-			strlwr(word);
-			CONS_Printf("Sound sfx_%s allocated.\n",word);
-			sfx = S_AddSoundFx(word, false, 0, false);
-			if (sfx != sfx_None) {
-				lua_pushinteger(L, sfx);
-				r++;
-			} else
-				CONS_Alert(CONS_WARNING, "Ran out of free SFX slots!\n");
-		}
-		else if (fastcmp(type, "SPR"))
-		{
-			char wad;
-			spritenum_t j;
-			lua_getfield(L, LUA_REGISTRYINDEX, "WAD");
-			wad = (char)lua_tointeger(L, -1);
-			lua_pop(L, 1);
-			for (j = SPR_FIRSTFREESLOT; j <= SPR_LASTFREESLOT; j++)
-			{
-				if (used_spr[(j-SPR_FIRSTFREESLOT)/8] & (1<<(j%8)))
-				{
-					if (!sprnames[j][4] && memcmp(sprnames[j],word,4)==0)
-						sprnames[j][4] = wad;
-					continue; // Already allocated, next.
+						deh_warning("HUD item number %d out of range (0 - %d)", i, NUMHUDITEMS-1);
+						ignorelines(f);
+					}
 				}
-				// Found a free slot!
-				CONS_Printf("Sprite SPR_%s allocated.\n",word);
-				strncpy(sprnames[j],word,4);
-				//sprnames[j][4] = 0;
-				used_spr[(j-SPR_FIRSTFREESLOT)/8] |= 1<<(j%8); // Okay, this sprite slot has been named now.
-				lua_pushinteger(L, j);
-				r++;
-				break;
-			}
-			if (j > SPR_LASTFREESLOT)
-				CONS_Alert(CONS_WARNING, "Ran out of free sprite slots!\n");
-		}
-		else if (fastcmp(type, "S"))
-		{
-			statenum_t i;
-			for (i = 0; i < NUMSTATEFREESLOTS; i++)
-				if (!FREE_STATES[i]) {
-					CONS_Printf("State S_%s allocated.\n",word);
-					FREE_STATES[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-					strcpy(FREE_STATES[i],word);
-					lua_pushinteger(L, i);
-					r++;
-					break;
+				else if (fastcmp(word, "MENU"))
+				{
+					if (i == 0 && word2[0] != '0') // If word2 isn't a number
+						i = get_menutype(word2); // find a huditem by name
+					if (i >= 1 && i < NUMMENUTYPES)
+						readmenu(f, i);
+					else
+					{
+						// zero-based, but let's start at 1
+						deh_warning("Menu number %d out of range (1 - %d)", i, NUMMENUTYPES-1);
+						ignorelines(f);
+					}
 				}
-			if (i == NUMSTATEFREESLOTS)
-				CONS_Alert(CONS_WARNING, "Ran out of free State slots!\n");
-		}
-		else if (fastcmp(type, "MT"))
-		{
-			mobjtype_t i;
-			for (i = 0; i < NUMMOBJFREESLOTS; i++)
-				if (!FREE_MOBJS[i]) {
-					CONS_Printf("MobjType MT_%s allocated.\n",word);
-					FREE_MOBJS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-					strcpy(FREE_MOBJS[i],word);
-					lua_pushinteger(L, i);
-					r++;
-					break;
+				else if (fastcmp(word, "UNLOCKABLE"))
+				{
+					if (!mainfile && !gamedataadded)
+					{
+						deh_warning("You must define a custom gamedata to use \"%s\"", word);
+						ignorelines(f);
+					}
+					else if (i > 0 && i <= MAXUNLOCKABLES)
+						readunlockable(f, i - 1);
+					else
+					{
+						deh_warning("Unlockable number %d out of range (1 - %d)", i, MAXUNLOCKABLES);
+						ignorelines(f);
+					}
 				}
-			if (i == NUMMOBJFREESLOTS)
-				CONS_Alert(CONS_WARNING, "Ran out of free MobjType slots!\n");
-		}
-		else if (fastcmp(type, "SKINCOLOR"))
-		{
-			skincolornum_t i;
-			for (i = 0; i < NUMCOLORFREESLOTS; i++)
-				if (!FREE_SKINCOLORS[i]) {
-					CONS_Printf("Skincolor SKINCOLOR_%s allocated.\n",word);
-					FREE_SKINCOLORS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
-					strcpy(FREE_SKINCOLORS[i],word);
-					M_AddMenuColor(numskincolors++);
-					lua_pushinteger(L, i);
-					r++;
-					break;
+				else if (fastcmp(word, "CONDITIONSET"))
+				{
+					if (!mainfile && !gamedataadded)
+					{
+						deh_warning("You must define a custom gamedata to use \"%s\"", word);
+						ignorelines(f);
+					}
+					else if (i > 0 && i <= MAXCONDITIONSETS)
+						readconditionset(f, (UINT8)i);
+					else
+					{
+						deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS);
+						ignorelines(f);
+					}
 				}
-			if (i == NUMCOLORFREESLOTS)
-				CONS_Alert(CONS_WARNING, "Ran out of free skincolor slots!\n");
-		}
-		else if (fastcmp(type, "SPR2"))
-		{
-			// Search if we already have an SPR2 by that name...
-			playersprite_t i;
-			for (i = SPR2_FIRSTFREESLOT; i < free_spr2; i++)
-				if (memcmp(spr2names[i],word,4) == 0)
-					break;
-			// We don't, so allocate a new one.
-			if (i >= free_spr2) {
-				if (free_spr2 < NUMPLAYERSPRITES)
+				else if (fastcmp(word, "SRB2"))
 				{
-					CONS_Printf("Sprite SPR2_%s allocated.\n",word);
-					strncpy(spr2names[free_spr2],word,4);
-					spr2defaults[free_spr2] = 0;
-					spr2names[free_spr2++][4] = 0;
-				} else
-					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
-			}
-			r++;
-		}
-		else if (fastcmp(type, "TOL"))
-		{
-			// Search if we already have a typeoflevel by that name...
-			int i;
-			for (i = 0; TYPEOFLEVEL[i].name; i++)
-				if (fastcmp(word, TYPEOFLEVEL[i].name))
-					break;
-
-			// We don't, so allocate a new one.
-			if (TYPEOFLEVEL[i].name == NULL) {
-				if (lastcustomtol == (UINT32)MAXTOL) // Unless you have way too many, since they're flags.
-					CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n");
-				else {
-					CONS_Printf("TypeOfLevel TOL_%s allocated.\n",word);
-					G_AddTOL(lastcustomtol, word);
-					lua_pushinteger(L, lastcustomtol);
-					lastcustomtol <<= 1;
-					r++;
+					if (isdigit(word2[0]))
+					{
+						i = atoi(word2);
+						if (i != PATCHVERSION)
+						{
+							deh_warning(
+									"Patch is for SRB2 version %d, "
+									"only version %d is supported",
+									i,
+									PATCHVERSION
+							);
+						}
+					}
+					else
+					{
+						deh_warning(
+								"SRB2 version definition has incorrect format, "
+								"use \"SRB2 %d\"",
+								PATCHVERSION
+						);
+					}
 				}
-			}
-		}
-		Z_Free(s);
-		lua_remove(L, 1);
-		continue;
-	}
-	return r;
-}
+				// Clear all data in certain locations (mostly for unlocks)
+				// Unless you REALLY want to piss people off,
+				// define a custom gamedata /before/ doing this!!
+				// (then again, modifiedgame will prevent game data saving anyway)
+				else if (fastcmp(word, "CLEAR"))
+				{
+					boolean clearall = (fastcmp(word2, "ALL"));
 
-// Wrapper for ALL A_Action functions.
-// Arguments: mobj_t actor, int var1, int var2
-static int action_call(lua_State *L)
-{
-	//actionf_t *action = lua_touserdata(L,lua_upvalueindex(1));
-	actionf_t *action = *((actionf_t **)luaL_checkudata(L, 1, META_ACTION));
-	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
-	var1 = (INT32)luaL_optinteger(L, 3, 0);
-	var2 = (INT32)luaL_optinteger(L, 4, 0);
-	if (!actor)
-		return LUA_ErrInvalid(L, "mobj_t");
-	action->acp1(actor);
-	return 0;
-}
+					if (!mainfile && !gamedataadded)
+					{
+						deh_warning("You must define a custom gamedata to use \"%s\"", word);
+						continue;
+					}
 
-// Hardcoded A_Action name to call for super() or NULL if super() would be invalid.
-// Set in lua_infolib.
-const char *superactions[MAXRECURSION];
-UINT8 superstack = 0;
+					if (clearall || fastcmp(word2, "UNLOCKABLES"))
+						memset(&unlockables, 0, sizeof(unlockables));
 
-static int lib_dummysuper(lua_State *L)
-{
-	return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions being called by state changes!"); // convoluted, I know. @_@;;
-}
+					if (clearall || fastcmp(word2, "EMBLEMS"))
+					{
+						memset(&emblemlocations, 0, sizeof(emblemlocations));
+						numemblems = 0;
+					}
 
-static inline int lib_getenum(lua_State *L)
-{
-	const char *word, *p;
-	fixed_t i;
-	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
-	if (lua_type(L,2) != LUA_TSTRING)
-		return 0;
-	word = lua_tostring(L,2);
-	if (strlen(word) == 1) { // Assume sprite frame if length 1.
-		if (*word >= 'A' && *word <= '~')
-		{
-			lua_pushinteger(L, *word-'A');
-			return 1;
-		}
-		if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
-		return 0;
-	}
-	else if (fastncmp("MF_", word, 3)) {
-		p = word+3;
-		for (i = 0; MOBJFLAG_LIST[i]; i++)
-			if (fastcmp(p, MOBJFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "mobjflag '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("MF2_", word, 4)) {
-		p = word+4;
-		for (i = 0; MOBJFLAG2_LIST[i]; i++)
-			if (fastcmp(p, MOBJFLAG2_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "mobjflag2 '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("MFE_", word, 4)) {
-		p = word+4;
-		for (i = 0; MOBJEFLAG_LIST[i]; i++)
-			if (fastcmp(p, MOBJEFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "mobjeflag '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("MTF_", word, 4)) {
-		p = word+4;
-		for (i = 0; i < 4; i++)
-			if (MAPTHINGFLAG_LIST[i] && fastcmp(p, MAPTHINGFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "mapthingflag '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("PF_", word, 3)) {
-		p = word+3;
-		for (i = 0; PLAYERFLAG_LIST[i]; i++)
-			if (fastcmp(p, PLAYERFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
-				return 1;
-			}
-		if (fastcmp(p, "FULLSTASIS"))
-		{
-			lua_pushinteger(L, (lua_Integer)PF_FULLSTASIS);
-			return 1;
-		}
-		if (mathlib) return luaL_error(L, "playerflag '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("GT_", word, 3)) {
-		p = word;
-		for (i = 0; Gametype_ConstantNames[i]; i++)
-			if (fastcmp(p, Gametype_ConstantNames[i])) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("GTR_", word, 4)) {
-		p = word+4;
-		for (i = 0; GAMETYPERULE_LIST[i]; i++)
-			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "game type rule '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("TOL_", word, 4)) {
-		p = word+4;
-		for (i = 0; TYPEOFLEVEL[i].name; i++)
-			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
-				lua_pushinteger(L, TYPEOFLEVEL[i].flag);
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "typeoflevel '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("ML_", word, 3)) {
-		p = word+3;
-		for (i = 0; i < 16; i++)
-			if (ML_LIST[i] && fastcmp(p, ML_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "linedef flag '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("S_",word,2)) {
-		p = word+2;
-		for (i = 0; i < NUMSTATEFREESLOTS; i++) {
-			if (!FREE_STATES[i])
-				break;
-			if (fastcmp(p, FREE_STATES[i])) {
-				lua_pushinteger(L, S_FIRSTFREESLOT+i);
-				return 1;
-			}
-		}
-		for (i = 0; i < S_FIRSTFREESLOT; i++)
-			if (fastcmp(p, STATE_LIST[i]+2)) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		return luaL_error(L, "state '%s' does not exist.\n", word);
-	}
-	else if (fastncmp("MT_",word,3)) {
-		p = word+3;
-		for (i = 0; i < NUMMOBJFREESLOTS; i++) {
-			if (!FREE_MOBJS[i])
-				break;
-			if (fastcmp(p, FREE_MOBJS[i])) {
-				lua_pushinteger(L, MT_FIRSTFREESLOT+i);
-				return 1;
-			}
-		}
-		for (i = 0; i < MT_FIRSTFREESLOT; i++)
-			if (fastcmp(p, MOBJTYPE_LIST[i]+3)) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		return luaL_error(L, "mobjtype '%s' does not exist.\n", word);
-	}
-	else if (fastncmp("SPR_",word,4)) {
-		p = word+4;
-		for (i = 0; i < NUMSPRITES; i++)
-			if (!sprnames[i][4] && fastncmp(p,sprnames[i],4)) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "sprite '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("SPR2_",word,5)) {
-		p = word+5;
-		for (i = 0; i < (fixed_t)free_spr2; i++)
-			if (!spr2names[i][4])
-			{
-				// special 3-char cases, e.g. SPR2_RUN
-				// the spr2names entry will have "_" on the end, as in "RUN_"
-				if (spr2names[i][3] == '_' && !p[3]) {
-					if (fastncmp(p,spr2names[i],3)) {
-						lua_pushinteger(L, i);
-						return 1;
+					if (clearall || fastcmp(word2, "EXTRAEMBLEMS"))
+					{
+						memset(&extraemblems, 0, sizeof(extraemblems));
+						numextraemblems = 0;
 					}
+
+					if (clearall || fastcmp(word2, "CONDITIONSETS"))
+						clear_conditionsets();
+
+					if (clearall || fastcmp(word2, "LEVELS"))
+						clear_levels();
 				}
-				else if (fastncmp(p,spr2names[i],4)) {
-					lua_pushinteger(L, i);
-					return 1;
-				}
-			}
-		if (mathlib) return luaL_error(L, "player sprite '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (!mathlib && fastncmp("sfx_",word,4)) {
-		p = word+4;
-		for (i = 0; i < NUMSFX; i++)
-			if (S_sfx[i].name && fastcmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		return 0;
-	}
-	else if (mathlib && fastncmp("SFX_",word,4)) { // SOCs are ALL CAPS!
-		p = word+4;
-		for (i = 0; i < NUMSFX; i++)
-			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		return luaL_error(L, "sfx '%s' could not be found.\n", word);
-	}
-	else if (mathlib && fastncmp("DS",word,2)) {
-		p = word+2;
-		for (i = 0; i < NUMSFX; i++)
-			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "sfx '%s' could not be found.\n", word);
-		return 0;
-	}
-#ifdef MUSICSLOT_COMPATIBILITY
-	else if (!mathlib && fastncmp("mus_",word,4)) {
-		p = word+4;
-		if ((i = get_mus(p, false)) == 0)
-			return 0;
-		lua_pushinteger(L, i);
-		return 1;
-	}
-	else if (mathlib && fastncmp("MUS_",word,4)) { // SOCs are ALL CAPS!
-		p = word+4;
-		if ((i = get_mus(p, false)) == 0)
-			return luaL_error(L, "music '%s' could not be found.\n", word);
-		lua_pushinteger(L, i);
-		return 1;
-	}
-	else if (mathlib && (fastncmp("O_",word,2) || fastncmp("D_",word,2))) {
-		p = word+2;
-		if ((i = get_mus(p, false)) == 0)
-			return luaL_error(L, "music '%s' could not be found.\n", word);
-		lua_pushinteger(L, i);
-		return 1;
-	}
-#endif
-	else if (!mathlib && fastncmp("pw_",word,3)) {
-		p = word+3;
-		for (i = 0; i < NUMPOWERS; i++)
-			if (fasticmp(p, POWERS_LIST[i])) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		return 0;
-	}
-	else if (mathlib && fastncmp("PW_",word,3)) { // SOCs are ALL CAPS!
-		p = word+3;
-		for (i = 0; i < NUMPOWERS; i++)
-			if (fastcmp(p, POWERS_LIST[i])) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		return luaL_error(L, "power '%s' could not be found.\n", word);
-	}
-	else if (fastncmp("HUD_",word,4)) {
-		p = word+4;
-		for (i = 0; i < NUMHUDITEMS; i++)
-			if (fastcmp(p, HUDITEMS_LIST[i])) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "huditem '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("SKINCOLOR_",word,10)) {
-		p = word+10;
-		for (i = 0; i < NUMCOLORFREESLOTS; i++) {
-			if (!FREE_SKINCOLORS[i])
-				break;
-			if (fastcmp(p, FREE_SKINCOLORS[i])) {
-				lua_pushinteger(L, SKINCOLOR_FIRSTFREESLOT+i);
-				return 1;
+				else
+					deh_warning("Unknown word: %s", word);
 			}
+			else
+				deh_warning("missing argument for '%s'", word);
 		}
-		for (i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++)
-			if (fastcmp(p, COLOR_ENUMS[i])) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		return luaL_error(L, "skincolor '%s' could not be found.\n", word);
-	}
-	else if (fastncmp("GRADE_",word,6))
-	{
-		p = word+6;
-		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
-			if (*p == NIGHTSGRADE_LIST[i])
-			{
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "NiGHTS grade '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (fastncmp("MN_",word,3)) {
-		p = word+3;
-		for (i = 0; i < NUMMENUTYPES; i++)
-			if (fastcmp(p, MENUTYPES_LIST[i])) {
-				lua_pushinteger(L, i);
-				return 1;
-			}
-		if (mathlib) return luaL_error(L, "menutype '%s' could not be found.\n", word);
-		return 0;
-	}
-	else if (!mathlib && fastncmp("A_",word,2)) {
-		char *caps;
-		// Try to get a Lua action first.
-		/// \todo Push a closure that sets superactions[] and superstack.
-		lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
-		// actions are stored in all uppercase.
-		caps = Z_StrDup(word);
-		strupr(caps);
-		lua_getfield(L, -1, caps);
-		Z_Free(caps);
-		if (!lua_isnil(L, -1))
-			return 1; // Success! :D That was easy.
-		// Welp, that failed.
-		lua_pop(L, 2); // pop nil and LREG_ACTIONS
+		else
+			deh_warning("No word in this line: %s", s);
+	} // end while
 
-		// Hardcoded actions as callable Lua functions!
-		// Retrieving them from this metatable allows them to be case-insensitive!
-		for (i = 0; actionpointers[i].name; i++)
-			if (fasticmp(word, actionpointers[i].name)) {
-				// We push the actionf_t* itself as userdata!
-				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
-				return 1;
-			}
-		return 0;
-	}
-	else if (!mathlib && fastcmp("super",word))
+	if (gamedataadded)
+		G_LoadGameData();
+
+	if (gamestate == GS_TITLESCREEN)
 	{
-		if (!superstack)
+		if (introchanged)
 		{
-			lua_pushcfunction(L, lib_dummysuper);
-			return 1;
+			menuactive = false;
+			I_UpdateMouseGrab();
+			COM_BufAddText("playintro");
 		}
-		for (i = 0; actionpointers[i].name; i++)
-			if (fasticmp(superactions[superstack-1], actionpointers[i].name)) {
-				LUA_PushUserdata(L, &actionpointers[i].action, META_ACTION);
-				return 1;
-			}
-		return 0;
-	}
-
-	for (i = 0; INT_CONST[i].n; i++)
-		if (fastcmp(word,INT_CONST[i].n)) {
-			lua_pushinteger(L, INT_CONST[i].v);
-			return 1;
+		else if (titlechanged)
+		{
+			menuactive = false;
+			I_UpdateMouseGrab();
+			COM_BufAddText("exitgame"); // Command_ExitGame_f() but delayed
 		}
-
-	if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
-
-	// DYNAMIC variables too!!
-	// Try not to add anything that would break netgames or timeattack replays here.
-	// You know, like consoleplayer, displayplayer, secondarydisplayplayer, or gametime.
-	return LUA_PushGlobals(L, word);
-}
-
-int LUA_EnumLib(lua_State *L)
-{
-	if (lua_gettop(L) == 0)
-		lua_pushboolean(L, 0);
-
-	// Set the global metatable
-	lua_createtable(L, 0, 1);
-	lua_pushvalue(L, 1); // boolean passed to LUA_EnumLib as first argument.
-	lua_pushcclosure(L, lib_getenum, 1);
-	lua_setfield(L, -2, "__index");
-	lua_setmetatable(L, LUA_GLOBALSINDEX);
-	return 0;
-}
-
-// getActionName(action) -> return action's string name
-static int lib_getActionName(lua_State *L)
-{
-	if (lua_isuserdata(L, 1)) // arg 1 is built-in action, expect action userdata
-	{
-		actionf_t *action = *((actionf_t **)luaL_checkudata(L, 1, META_ACTION));
-		const char *name = NULL;
-		if (!action)
-			return luaL_error(L, "not a valid action?");
-		name = LUA_GetActionName(action);
-		if (!name) // that can't be right?
-			return luaL_error(L, "no name string could be found for this action");
-		lua_pushstring(L, name);
-		return 1;
 	}
-	else if (lua_isfunction(L, 1)) // arg 1 is a function (either C or Lua)
+
+	dbg_line = -1;
+	if (deh_num_warning)
 	{
-		lua_settop(L, 1); // set top of stack to 1 (removing any extra args, which there shouldn't be)
-		// get the name for this action, if possible.
-		lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
-		lua_pushnil(L);
-		// Lua stack at this point:
-		//  1   ...       -2              -1
-		// arg  ...   LREG_ACTIONS        nil
-		while (lua_next(L, -2))
-		{
-			// Lua stack at this point:
-			//  1   ...       -3              -2           -1
-			// arg  ...   LREG_ACTIONS    "A_ACTION"    function
-			if (lua_rawequal(L, -1, 1)) // is this the same as the arg?
-			{
-				// make sure the key (i.e. "A_ACTION") is a string first
-				// (note: we don't use lua_isstring because it also returns true for numbers)
-				if (lua_type(L, -2) == LUA_TSTRING)
-				{
-					lua_pushvalue(L, -2); // push "A_ACTION" string to top of stack
-					return 1;
-				}
-				lua_pop(L, 2); // pop the name and function
-				break; // probably should have succeeded but we didn't, so end the loop
-			}
-			lua_pop(L, 1);
+		CONS_Printf(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s");
+		if (devparm) {
+			I_Error("%s%s",va(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"), M_GetText("See log.txt for details.\n"));
+			//while (!I_GetKey())
+				//I_OsPolling();
 		}
-		lua_pop(L, 1); // pop LREG_ACTIONS
-		return 0; // return nothing (don't error)
 	}
 
-	return luaL_typerror(L, 1, "action userdata or Lua function");
-}
-
-
-
-int LUA_SOCLib(lua_State *L)
-{
-	lua_register(L,"freeslot",lib_freeslot);
-	lua_register(L,"getActionName",lib_getActionName);
-
-	luaL_newmetatable(L, META_ACTION);
-		lua_pushcfunction(L, action_call);
-		lua_setfield(L, -2, "__call");
-	lua_pop(L, 1);
-
-	return 0;
+	deh_loaded = true;
+	Z_Free(s);
 }
 
-const char *LUA_GetActionName(void *action)
+// read dehacked lump in a wad (there is special trick for for deh
+// file that are converted to wad in w_wad.c)
+void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump, boolean mainfile)
 {
-	actionf_t *act = (actionf_t *)action;
-	size_t z;
-	for (z = 0; actionpointers[z].name; z++)
-	{
-		if (actionpointers[z].action.acv == act->acv)
-			return actionpointers[z].name;
-	}
-	return NULL;
+	MYFILE f;
+	f.wad = wad;
+	f.size = W_LumpLengthPwad(wad, lump);
+	f.data = Z_Malloc(f.size + 1, PU_STATIC, NULL);
+	W_ReadLumpPwad(wad, lump, f.data);
+	f.curpos = f.data;
+	f.data[f.size] = 0;
+	DEH_LoadDehackedFile(&f, mainfile);
+	Z_Free(f.data);
 }
 
-void LUA_SetActionByName(void *state, const char *actiontocompare)
+void DEH_LoadDehackedLump(lumpnum_t lumpnum)
 {
-	state_t *st = (state_t *)state;
-	size_t z;
-	for (z = 0; actionpointers[z].name; z++)
-	{
-		if (fasticmp(actiontocompare, actionpointers[z].name))
-		{
-			st->action = actionpointers[z].action;
-			st->action.acv = actionpointers[z].action.acv; // assign
-			st->action.acp1 = actionpointers[z].action.acp1;
-			return;
-		}
-	}
+	DEH_LoadDehackedLumpPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum), false);
 }
diff --git a/src/dehacked.h b/src/dehacked.h
index 54225f36e8c851b67e968b3b25167ac1acf61306..1620314caaba5bbabafded19233b0c162f148f84 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -30,16 +30,16 @@ typedef enum
 void DEH_LoadDehackedLump(lumpnum_t lumpnum);
 void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump, boolean mainfile);
 
-void DEH_Check(void);
-
 fixed_t get_number(const char *word);
-
-boolean LUA_SetLuaAction(void *state, const char *actiontocompare);
-const char *LUA_GetActionName(void *action);
-void LUA_SetActionByName(void *state, const char *actiontocompare);
+FUNCPRINTF void deh_warning(const char *first, ...);
+void deh_strlcpy(char *dst, const char *src, size_t size, const char *warntext);
 
 extern boolean deh_loaded;
 
+extern boolean gamedataadded;
+extern boolean titlechanged;
+extern boolean introchanged;
+
 #define MAXRECURSION 30
 extern const char *superactions[MAXRECURSION];
 extern UINT8 superstack;
@@ -60,4 +60,5 @@ typedef struct
 } MYFILE;
 #define myfeof(a) (a->data + a->size <= a->curpos)
 char *myfgets(char *buf, size_t bufsize, MYFILE *f);
+char *myhashfgets(char *buf, size_t bufsize, MYFILE *f);
 #endif
diff --git a/src/djgppdos/Makefile.cfg b/src/djgppdos/Makefile.cfg
deleted file mode 100644
index 857a7267bc88bfe08038b3c4bb7f575223997412..0000000000000000000000000000000000000000
--- a/src/djgppdos/Makefile.cfg
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# djgppdos/makefile.cfg for SRB2/DOS
-#
-
-#
-#now for the DOS stuff, go DOS!
-#
-
-	# options
-	OPTS=-DPC_DOS
-	WFLAGS+=-Wno-cast-qual
-	NOHW=1
-	NOHS=1
-	PNG_CFLAGS=
-	PNG_LDFLAGS=-lpng -lz
-
-ifdef WATTCP
-	OPTS+=-DWATTCP
-	NOOBJDUMP=1
-endif
-
-#ifdef DEBUGMODE
-	LIBS=-lalld
-#else
-#	LIBS=-lalleg
-#endif
-
-ifndef NONET
-ifdef WATTCP
-	LIBS+=-lwatt
-else
-	LIBS+=-lsocket
-endif
-endif
-
-ifdef RDB
-	LIBS+=-lgdbst -ldzcom
-	OPTS+=-DREMOTE_DEBUGGING
-endif
-
-	OBJS=$(OBJDIR)/i_video.o $(OBJDIR)/vid_vesa.o
-
-	# name of the exefile
-ifdef WATTCP
-	EXENAME?=srb2dos.exe
-else
-	EXENAME?=srb2w16.exe
-endif
diff --git a/src/djgppdos/bcd.c b/src/djgppdos/bcd.c
deleted file mode 100644
index 6a91c707fa4bbc4b8591281bbd3c38214f7a78a6..0000000000000000000000000000000000000000
--- a/src/djgppdos/bcd.c
+++ /dev/null
@@ -1,755 +0,0 @@
-/* bcd.c -- Brennan's CD-ROM Audio Playing Library
-   by Brennan Underwood, http://brennan.home.ml.org/ */
-#include <dos.h>
-#include <dpmi.h>
-#include <go32.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <malloc.h>
-#include <unistd.h>
-#include <strings.h>
-#ifdef STANDALONE
-#include <conio.h> /* for getch() */
-#endif
-
-#include "bcd.h"
-
-typedef struct {
-  int is_audio;
-  int start, end, len;
-} Track;
-
-static int mscdex_version;
-static int first_drive;
-static int num_tracks;
-static int lowest_track, highest_track;
-static int audio_length;
-
-#ifdef STATIC_TRACKS
-static Track tracks[99];
-#else
-static Track *tracks;
-#endif
-
-static int dos_mem_segment, dos_mem_selector = -1;
-
-int _status, _error, _error_code;
-const char *_bcd_error = NULL;
-
-#define RESET_ERROR (_error = _error_code = 0)
-#define ERROR_BIT (1 << 15)
-#define BUSY_BIT (1 << 9)
-#ifndef TRUE
-#define TRUE 1
-#define FALSE 0
-#endif
-
-#pragma pack(1)
-
-/* I know 'typedef struct {} bleh' is a bad habit, but... */
-typedef struct {
-  unsigned char len;
-  unsigned char unit;
-  unsigned char command;
-  unsigned short status;
-  unsigned char reserved[8];
-} ATTRPACK RequestHeader;
-
-typedef struct {
-  RequestHeader request_header;
-  unsigned char descriptor;
-  unsigned long address;
-  unsigned short len;
-  unsigned short secnum;
-  unsigned long ptr;
-} ATTRPACK IOCTLI;
-
-typedef struct {
-  unsigned char control;
-  unsigned char lowest;
-  unsigned char highest;
-  char total[4];
-} ATTRPACK DiskInfo;
-
-typedef struct {
-  unsigned char control;
-  unsigned char track_number;
-  char start[4];
-  unsigned char info;
-} ATTRPACK TrackInfo;
-
-typedef struct {
-  RequestHeader request;
-  unsigned char mode;
-  unsigned long start;
-  unsigned long len;
-} ATTRPACK PlayRequest;
-
-typedef struct {
-  RequestHeader request;
-} ATTRPACK StopRequest;
-
-typedef struct {
-  RequestHeader request;
-} ATTRPACK ResumeRequest;
-
-typedef struct {
-  unsigned char control;
-  unsigned char input0;
-  unsigned char volume0;
-  unsigned char input1;
-  unsigned char volume1;
-  unsigned char input2;
-  unsigned char volume2;
-  unsigned char input3;
-  unsigned char volume3;
-} ATTRPACK VolumeRequest;
-
-typedef struct {
-  unsigned char control;
-  unsigned char fn;
-} ATTRPACK LockRequest;
-
-typedef struct {
-  unsigned char control;
-  unsigned char mbyte;
-} ATTRPACK MediaChangedRequest;
-
-typedef struct {
-  unsigned char control;
-  unsigned long status;
-} ATTRPACK StatusRequest;
-
-typedef struct {
-  unsigned char control;
-  unsigned char mode;
-  unsigned long loc;
-} ATTRPACK PositionRequest;
-
-#pragma pack()
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-const char *bcd_error(void) {
-  static char retstr[132];
-  const char *errorcodes[] = {
-    "Write-protect violation",
-    "Unknown unit",
-    "Drive not ready",
-    "Unknown command",
-    "CRC error",
-    "Bad drive request structure length",
-    "Seek error",
-    "Unknown media",
-    "Sector not found",
-    "Printer out of paper: world coming to an end",/* I mean really, on a CD? */
-    "Write fault",
-    "Read fault",
-    "General failure",
-    "Reserved",
-    "Reserved",
-    "Invalid disk change"
-  };
-  *retstr = 0;
-  if (_error != 0) {
-    strcat(retstr, "Device error: ");
-    if (_error_code < 0 || _error_code > 0xf)
-      strcat(retstr, "Invalid error");
-    else
-      strcat(retstr, errorcodes[_error_code]);
-    strcat(retstr, "  ");
-  }
-  if (_bcd_error != NULL) {
-    if (*retstr) strcat(retstr, ", ");
-    strcat(retstr, "BCD error: ");
-    strcat(retstr, _bcd_error);
-  }
-  return retstr;
-}
-
-/* DOS IOCTL w/ command block */
-static void bcd_ioctl(IOCTLI *ioctli, void *command, int len) {
-  int ioctli_len = sizeof (IOCTLI);
-  unsigned long command_address = dos_mem_segment << 4;
-  __dpmi_regs regs;
-
-  memset(&regs, 0, sizeof regs);
-  regs.x.es = (__tb >> 4) & 0xffff;
-  regs.x.ax = 0x1510;
-  regs.x.bx = __tb & 0xf;
-  regs.x.cx = first_drive;
-  ioctli->address = dos_mem_segment << 16;
-  ioctli->len = len;
-  dosmemput(ioctli, ioctli_len, __tb);		/* put ioctl into dos area */
-  dosmemput(command, len, command_address);	/* and command too */
-  if (__dpmi_int(0x2f, &regs) == -1) {
-    _bcd_error = "__dpmi_int() failed";
-    return;
-  }
-  dosmemget(__tb, ioctli_len, ioctli);		/* retrieve results */
-  dosmemget(command_address, len, command);
-  _status = ioctli->request_header.status;
-  if (_status & ERROR_BIT) {
-    _error = TRUE;
-    _error_code = _status & 0xff;
-  } else {
-    _error = FALSE;
-    _error_code = 0;
-  }
-}
-
-/* no command block */
-FUNCINLINE static ATTRINLINE void bcd_ioctl2(void *cmd, int len) {
-  __dpmi_regs regs;
-  memset(&regs, 0, sizeof regs);
-  regs.x.es = (__tb >> 4) & 0xffff;
-  regs.x.ax = 0x1510;
-  regs.x.bx = __tb & 0xf;
-  regs.x.cx = first_drive;
-  dosmemput(cmd, len, __tb); /* put ioctl block in dos arena */
-  if (__dpmi_int(0x2f, &regs) == -1) {
-    _bcd_error = "__dpmi_int() failed";
-    return;
-  }
-  /* I hate to have no error capability for ioctl2 but the command block
-     doesn't necessarily have a status field */
-  RESET_ERROR;
-}
-
-FUNCINLINE static ATTRINLINE int red2hsg(char *r) {
-  return r[0] + r[1]*75 + r[2]*4500 - 150;
-}
-
-int bcd_now_playing(void) {
-  int i, loc = bcd_audio_position();
-  _bcd_error = NULL;
-  if (!bcd_audio_busy()) {
-    _bcd_error = "Audio not playing";
-    return 0;
-  }
-  if (
-#ifndef STATIC_TRACKS
-  tracks == NULL &&
-#endif
-   !bcd_get_audio_info())
-    return 0;
-  for (i = lowest_track; i <= highest_track; i++) {
-    if (loc >= tracks[i].start && loc <= tracks[i].end) return i;
-  }
-  /* some bizarre location? */
-  _bcd_error = "Head outside of bounds";
-  return 0;
-}
-
-/* handles the setup for CD-ROM audio interface */
-int bcd_open(void) {
-  __dpmi_regs regs;
-  _bcd_error = NULL;
-
-  /* disk I/O wouldn't work anyway if you set sizeof tb this low, but... */
-  if (_go32_info_block.size_of_transfer_buffer < 4096) {
-    _bcd_error = "Transfer buffer too small";
-    return 0;
-  }
-
-  memset(&regs, 0, sizeof regs);
-  regs.x.ax = 0x1500;
-  regs.x.bx = 0x0;
-  __dpmi_int(0x2f, &regs);
-  if (regs.x.bx == 0) {	/* abba no longer lives */
-    _bcd_error = "MSCDEX not found";
-    return 0;
-  }
-
-  first_drive = regs.x.cx; /* use the first drive */
-
-  /* check for mscdex at least 2.0 */
-  memset(&regs, 0, sizeof regs);
-  regs.x.ax = 0x150C;
-  __dpmi_int(0x2f, &regs);
-  if (regs.x.bx == 0) {
-    _bcd_error = "MSCDEX version < 2.0";
-    return 0;
-  }
-  mscdex_version = regs.x.bx;
-
-  /* allocate 256 bytes of dos memory for the command blocks */
-  if ((dos_mem_segment = __dpmi_allocate_dos_memory(16, &dos_mem_selector))<0) {
-    _bcd_error = "Could not allocate 256 bytes of DOS memory";
-    return 0;
-  }
-
-  return mscdex_version;
-}
-
-/* Shuts down CD-ROM audio interface */
-int bcd_close(void) {
-  _bcd_error = NULL;
-  if (dos_mem_selector != -1) {
-    __dpmi_free_dos_memory(dos_mem_selector);
-    dos_mem_selector = -1;
-  }
-#ifndef STATIC_TRACKS
-  if (tracks) free(tracks);
-  tracks = NULL;
-#endif
-  RESET_ERROR;
-  return 1;
-}
-
-int bcd_open_door(void) {
-  IOCTLI ioctli;
-  char eject = 0;
-  _bcd_error = NULL;
-  memset(&ioctli, 0, sizeof ioctli);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 12;
-  ioctli.len = 1;
-  bcd_ioctl(&ioctli, &eject, sizeof eject);
-  if (_error) return 0;
-  return 1;
-}
-
-int bcd_close_door(void) {
-  IOCTLI ioctli;
-  char closeit = 5;
-  _bcd_error = NULL;
-  memset(&ioctli, 0, sizeof ioctli);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 12;
-  ioctli.len = 1;
-  bcd_ioctl(&ioctli, &closeit, sizeof closeit);
-  if (_error) return 0;
-  return 1;
-}
-
-int bcd_lock(int fn) {
-  IOCTLI ioctli;
-  LockRequest req;
-  _bcd_error = NULL;
-  memset(&ioctli, 0, sizeof ioctli);
-  memset(&req, 0, sizeof req);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 12;
-  ioctli.len = sizeof req;
-  req.control = 1;
-  req.fn = fn ? 1 : 0;
-  bcd_ioctl(&ioctli, &req, sizeof req);
-  if (_error) return 0;
-  return 1;
-}
-
-
-int bcd_disc_changed(void) {
-  IOCTLI ioctli;
-  MediaChangedRequest req;
-  _bcd_error = NULL;
-  memset(&ioctli, 0, sizeof ioctli);
-  memset(&req, 0, sizeof req);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 3;
-  ioctli.len = sizeof req;
-  req.control = 9;
-  bcd_ioctl(&ioctli, &req, sizeof req);
-  return req.mbyte;
-}
-
-int bcd_reset(void) {
-  IOCTLI ioctli;
-  char reset = 2;
-  _bcd_error = NULL;
-
-  memset(&ioctli, 0, sizeof ioctli);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 12;
-  ioctli.len = 1;
-  bcd_ioctl(&ioctli, &reset, sizeof reset);
-  if (_error) return 0;
-  return 1;
-}
-
-int bcd_device_status(void) {
-  IOCTLI ioctli;
-  StatusRequest req;
-  _bcd_error = NULL;
-  memset(&ioctli, 0, sizeof ioctli);
-  memset(&req, 0, sizeof req);
-  ioctli.request_header.len = sizeof ioctli; // ok
-  ioctli.request_header.command = 3;
-  ioctli.len = sizeof req;
-  req.control = 6;
-  bcd_ioctl(&ioctli, &req, sizeof req);
-  return req.status;
-}
-
-static inline int bcd_get_status_word(void) {
-  IOCTLI ioctli;
-  DiskInfo disk_info;
-
-  /* get cd info as an excuse to get a look at the status word */
-  memset(&disk_info, 0, sizeof disk_info);
-  memset(&ioctli, 0, sizeof ioctli);
-
-  ioctli.request_header.len = 26;
-  ioctli.request_header.command = 3;
-  ioctli.len = 7;
-  disk_info.control = 10;
-  bcd_ioctl(&ioctli, &disk_info, sizeof disk_info);
-  return _status;
-}
-
-int bcd_audio_busy(void) {
-  _bcd_error = NULL;
-  /* If the door is open, then the head is busy, and so the busy bit is
-     on. It is not, however, playing audio. */
-  if (bcd_device_status() & BCD_DOOR_OPEN)
-    return 0;
-
-  bcd_get_status_word();
-  if (_error) return -1;
-  return (_status & BUSY_BIT) ? 1 : 0;
-}
-
-int bcd_audio_position(void) {
-  IOCTLI ioctli;
-  PositionRequest req;
-  _bcd_error = NULL;
-  memset(&ioctli, 0, sizeof ioctli);
-  memset(&req, 0, sizeof req);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 3;
-  ioctli.len = sizeof req;
-  req.control = 1;
-  bcd_ioctl(&ioctli, &req, sizeof req);
-  return req.loc;
-}
-
-/* Internal function to get track info */
-static inline void bcd_get_track_info(int n, Track *t) {
-  IOCTLI ioctli;
-  TrackInfo info;
-
-  memset(&ioctli, 0, sizeof ioctli);
-  memset(&info, 0, sizeof info);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 3;
-  info.control = 11;
-  info.track_number = n;
-  bcd_ioctl(&ioctli, &info, sizeof info);
-  t->start = red2hsg(info.start);
-  if (info.info & 64)
-    t->is_audio = 0;
-  else
-    t->is_audio = 1;
-}
-
-int bcd_get_audio_info(void) {
-  IOCTLI ioctli;
-  DiskInfo disk_info;
-  int i;
-
-
-  _bcd_error = NULL;
-#ifndef STATIC_TRACKS
-  if (tracks) free(tracks);
-  tracks = NULL;
-#endif
-
-  memset(&disk_info, 0, sizeof disk_info);
-  memset(&ioctli, 0, sizeof ioctli);
-
-  ioctli.request_header.len = 26;
-  ioctli.request_header.command = 3;
-  ioctli.len = 7;
-  disk_info.control = 10;
-  bcd_ioctl(&ioctli, &disk_info, sizeof disk_info);
-  if (_error) return 0;
-
-  lowest_track = disk_info.lowest;
-  highest_track = disk_info.highest;
-  num_tracks = disk_info.highest - disk_info.lowest + 1;
-
-#ifndef STATIC_TRACKS
-  //tracks = calloc(num_tracks, sizeof (Track));
-  /* alloc max space in order to attempt to avoid possible overrun bug */
-  tracks = calloc(highest_track+1, sizeof (Track));
-  if (tracks == NULL) {
-    _bcd_error = "Out of memory allocating tracks\n";
-    return 0;
-  }
-#endif
-
-  /* get track starts */
-  for (i = lowest_track; i <= highest_track; i++)
-    bcd_get_track_info(i, tracks+i);
-
-  /* figure out track ends */
-  for (i = lowest_track; i < highest_track; i++)
-    tracks[i].end = tracks[i+1].start-1;
-  audio_length = red2hsg(disk_info.total);
-  tracks[i].end = audio_length;
-  for (i = lowest_track; i <= highest_track; i++)
-    tracks[i].len = tracks[i].end - tracks[i].start;
-
-  return num_tracks;
-}
-
-int bcd_get_track_address(int trackno, int *start, int *len) {
-  _bcd_error = NULL;
-  //if (trackno >= num_tracks+1 || trackno <= 0) {
-  if (trackno < lowest_track || trackno > highest_track) {
-    _bcd_error  = "Track out of range";
-    *start = *len = 0;
-    return 0;
-  }
-  *start = tracks[trackno].start;
-  *len = tracks[trackno].len;
-  return 1;
-}
-
-int bcd_track_is_audio(int trackno) {
-  //if (trackno >= num_tracks+1 || trackno <= 0) {
-  if (trackno < lowest_track || trackno > highest_track) {
-    _bcd_error = "Track out of range";
-    return 0;
-  }
-  return tracks[trackno].is_audio;
-}
-
-int bcd_set_volume(int volume) {
-  IOCTLI ioctli;
-  VolumeRequest v;
-
-  _bcd_error = NULL;
-  if (volume > 255) volume = 255;
-  else if (volume < 0) volume = 0;
-  memset(&ioctli, 0, sizeof ioctli);
-  ioctli.request_header.len = sizeof ioctli;
-  ioctli.request_header.command = 12;
-  ioctli.len = sizeof v;
-  v.control = 3;
-  v.volume0 = volume;
-  v.input0 = 0;
-  v.volume1 = volume;
-  v.input1 = 1;
-  v.volume2 = volume;
-  v.input2 = 2;
-  v.volume3 = volume;
-  v.input3 = 3;
-
-  bcd_ioctl(&ioctli, &v, sizeof v);
-  if (_error) return 0;
-  return 1;
-}
-
-int bcd_play(int location, int frames) {
-  PlayRequest cmd;
-  memset(&cmd, 0, sizeof cmd);
-
-  _bcd_error = NULL;
-  /* the following should be in user code, but it'll fail otherwise */
-  if (bcd_audio_busy())
-    bcd_stop();
-
-  cmd.request.len = sizeof cmd;
-  cmd.request.command = 132;
-  cmd.start = location;
-  cmd.len = frames;
-  bcd_ioctl2(&cmd, sizeof cmd);
-  if (_error) return 0;
-  return 1;
-}
-
-int bcd_play_track(int trackno) {
-  _bcd_error = NULL;
-  if (!bcd_get_audio_info()) return 0;
-
-  if (trackno < lowest_track || trackno > highest_track) {
-    _bcd_error = "Track out of range";
-    return 0;
-  }
-
-  if (! tracks[trackno].is_audio) {
-    _bcd_error = "Not an audio track";
-    return 0;
-  }
-
-  return bcd_play(tracks[trackno].start, tracks[trackno].len);
-}
-
-int bcd_stop(void) {
-  StopRequest cmd;
-  _bcd_error = NULL;
-  memset(&cmd, 0, sizeof cmd);
-  cmd.request.len = sizeof cmd;
-  cmd.request.command = 133;
-  bcd_ioctl2(&cmd, sizeof cmd);
-  if (_error) return 0;
-  return 1;
-}
-
-int bcd_resume(void) {
-  ResumeRequest cmd;
-  _bcd_error = NULL;
-  memset(&cmd, 0, sizeof cmd);
-  cmd.request.len = sizeof cmd;
-  cmd.request.command = 136;
-  bcd_ioctl2(&cmd, sizeof cmd);
-  if (_error) return 0;
-  return 1;
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#ifdef STANDALONE
-static char *card(int c) {
-  return c == 1 ? "" : "s";
-}
-
-static void print_hsg(int hsg) {
-  int hours, minutes, seconds;
-  seconds = hsg / 75;
-  minutes = seconds / 60;
-  seconds %= 60;
-  hours = minutes / 60;
-  minutes %= 60;
-  printf("%2d:%02d:%02d", hours, minutes, seconds);
-}
-
-static void print_binary(int v, int len) {
-  for (;len;len--)
-    printf("%d", (v & (1 << len)) ? 1 : 0);
-}
-
-int main(int argc, char *argv[]) {
-  int i, n1, n2, t;
-
-  if (!bcd_open()) {
-    fprintf(stderr, "Couldn't open CD-ROM drive. %s\n", bcd_error());
-    exit(0);
-  }
-
-  for (i = 1; i < argc; i++) {
-    if (*argv[i] == '-') strcpy(argv[i], argv[i]+1);
-    if (!strcmp(argv[i], "open") || !strcmp(argv[i], "eject")) {
-      bcd_open_door();
-    } else if (!strcmp(argv[i], "close")) {
-      bcd_close_door();
-    } else if (!strcmp(argv[i], "sleep")) {
-      if (++i >= argc) break;
-      sleep(atoi(argv[i]));
-    } else if (!strcmp(argv[i], "list")) {
-      int nd = 0, na = 0, audio_time = 0;
-      if (!bcd_get_audio_info()) {
-        printf("Error getting audio info\n");
-      } else if (lowest_track == 0) {
-        printf("No audio tracks\n");
-      } else {
-        for (t = lowest_track; t <= highest_track; t++) {
-          printf("Track %2d: ", t);
-          print_hsg(tracks[t].start);
-          printf(" -> ");
-          print_hsg(tracks[t].end);
-          printf(" (");
-          print_hsg(tracks[t].len);
-          if (tracks[t].is_audio) {
-            na++;
-            printf(") audio");
-            audio_time += tracks[t].len;
-          } else {
-            nd++;
-            printf(") data ");
-          }
-          printf(" (HSG: %06d->%06d)\n", tracks[t].start, tracks[t].end);
-        }
-        printf("%d audio track%s, %d data track%s\n", na, card(na), nd, card(nd));
-        if (audio_time) {
-          printf("Audio time: ");
-          print_hsg(audio_time);
-          printf("\n");
-        }
-      }
-    } else if (!strcmp(argv[i], "lock")) {
-      bcd_lock(1);
-    } else if (!strcmp(argv[i], "pladdr")) {
-      if (++i >= argc) break;
-      n1 = atoi(argv[i]);
-      if (++i >= argc) break;
-      n2 = atoi(argv[i]);
-      printf("Playing frame %d to frame %d\n", n1, n2);
-      bcd_play(n1, n2-n1);
-    } else if (!strcmp(argv[i], "play")) {
-      if (++i >= argc) break;
-      if (bcd_audio_busy()) {
-        bcd_stop();
-        delay(1000);
-      }
-      n1 = atoi(argv[i]);
-      printf("Playing track %d\n", n1);
-      bcd_play_track(n1);
-    } else if (!strcmp(argv[i], "reset")) {
-      bcd_reset();
-    } else if (!strcmp(argv[i], "resume")) {
-      bcd_resume();
-    } else if (!strcmp(argv[i], "status")) {
-      int s;
-      s = bcd_device_status();
-      printf("MSCDEX version %d.%d\n", mscdex_version >> 8,
-             mscdex_version & 0xff);
-      printf("Device status word '");
-      print_binary(s, 16);
-      printf("'\nDoor is %sopen\n", s & BCD_DOOR_OPEN ? "" : "not ");
-      printf("Door is %slocked\n", s & BCD_DOOR_UNLOCKED ? "not " : "");
-      printf("Audio is %sbusy\n", bcd_audio_busy() ? "" : "not ");
-      s = bcd_disc_changed();
-      if (s == BCD_DISC_UNKNOWN) printf("Media change status unknown\n");
-      else printf("Media has %schanged\n",
-           (s == BCD_DISC_CHANGED) ? "" : "not ");
-    } else if (!strcmp(argv[i], "stop")) {
-      bcd_stop();
-    } else if (!strcmp(argv[i], "unlock")) {
-      bcd_lock(0);
-    } else if (!strcmp(argv[i], "volume")) {
-      bcd_set_volume(atoi(argv[++i]));
-    } else if (!strcmp(argv[i], "wait")) {
-      while (bcd_audio_busy()) {
-        int n = bcd_now_playing();
-        if (n == 0) break;
-        printf("%2d: ", n);
-        print_hsg(bcd_audio_position() - tracks[n].start);
-        printf("\r");
-        fflush(stdout);
-        delay(100);
-        if (kbhit() && getch() == 27) break;
-      }
-      printf("\n");
-    } else if (!strcmp(argv[i], "help") || !strcmp(argv[i], "usage")) {
-      printf("BCD version %x.%x\n" \
-             "Usage: BCD {commands}\n" \
-             "Valid commands:\n" \
-             "\tclose		- close door/tray\n" \
-             "\tdelay {n}	- delay {n} seconds\n" \
-             "\tlist		- list track info\n" \
-             "\tlock		- lock door/tray\n" \
-             "\topen		- open door/tray\n" \
-             "\tpladdr {n1} {n2}- play frame {n1} to {n2}\n" \
-             "\tplay {n}	- play track {n}\n" \
-             "\treset		- reset the drive\n" \
-             "\tresume		- resume from last stop\n" \
-             "\tstatus		- show drive status\n" \
-             "\tstop		- stop audio playback\n" \
-             "\tunlock		- unlock door/tray\n" \
-             "\tvolume {n}	- set volume to {n} where 0 <= {n} <= 255\n",
-             BCD_VERSION >> 8, BCD_VERSION & 0xff);
-    } else
-      printf("Unknown command '%s'\n", argv[i]);
-    if (_error || _bcd_error) printf("%s\n", bcd_error());
-  }
-  bcd_close();
-  exit(0);
-}
-#endif
diff --git a/src/djgppdos/bcd.h b/src/djgppdos/bcd.h
deleted file mode 100644
index 3997128a2715e52fc6e284bf93b8a6a0738ebaea..0000000000000000000000000000000000000000
--- a/src/djgppdos/bcd.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/* bcd.h -- header file for BCD, a CD-ROM audio playing library for DJGPP
-   by Brennan Underwood, http://brennan.home.ml.org/ */
-#ifndef _BCD_H
-#define _BCD_H
-
-#define BCD_VERSION 0x0103
-
-/* Installation and setup functions */
-/* Call this first! */
-int bcd_open(void);
-/* Call before exit. */
-int bcd_close(void);
-
-/* open door, unlocking first if necessary */
-int bcd_open_door(void);
-/* close door */
-int bcd_close_door(void);
-
-/* pass 1 to lock door, 0 to unlock */
-int bcd_lock(int);
-
-/* returns one of the following 3 #defined symbols */
-int bcd_disc_changed(void);
-#define BCD_DISC_CHANGED	0xff
-#define BCD_DISC_NOT_CHANGED	1
-#define BCD_DISC_UNKNOWN	0
-
-/* perform a device reset */
-int bcd_reset(void);
-
-/* compare the returned status int to the following bits */
-int bcd_device_status(void);
-#define BCD_DOOR_OPEN		1
-#define BCD_DOOR_UNLOCKED	2
-#define BCD_SUPPORT_COOKED	4
-#define BCD_READ_ONLY		8
-#define BCD_DATA_READ_ONLY	16
-#define BCD_SUPPORT_INTERLEAVE	32
-
-/* returns 1 if audio is currently playing, 0 otherwise. -1 on error */
-int bcd_audio_busy(void);
-/* current head position in frames */
-int bcd_audio_position(void);
-/* convenience function, if audio busy, returns track# playing now */
-int bcd_now_playing(void);
-
-/* query MSCDEX for track list when disc changed or just starting up */
-int bcd_get_audio_info(void);
-/* get a particular track's info */
-int bcd_get_track_address(int trackno, int *start, int *len);
-/* check for track's audio/data status */
-int bcd_track_is_audio(int trackno);
-/* play a particular track from beginning to end */
-int bcd_play_track(int tracknum);
-/* play an arbitrary section of audio for an arbitrary length of time */
-int bcd_play(int start, int len);
-/* set the output volume. pass a parameter from 0-255 */
-int bcd_set_volume(int);
-/* stop and pause are equivalent */
-int bcd_stop(void);
-#define bcd_pause bcd_stop
-int bcd_resume(void);
-
-/* Troubleshooting */
-/* Returns a human readable description of the last error encountered */
-const char *bcd_error(void);
-extern int _error_code;
-/* If you are mad enough play the Rach 3, I mean parse _error_code yourself */
-#define BCD_DE_WRITE_PROTECT	0
-#define BCD_DE_UNKNOWN_UNIT	1
-#define BCD_DE_DRIVE_NOT_READY	2
-#define BCD_DE_UNKNOWN_COMMAND	3
-#define BCD_DE_CRC_ERROR	4
-#define BCD_DE_STRUCT_LEN	5
-#define BCD_DE_SEEK_ERROR	6
-#define BCD_DE_UNKNOWN_MEDIA	7
-#define BCD_DE_SECTOR_NOT_FOUND	8
-#define BCD_DE_OUT_OF_PAPER	9
-#define BCD_DE_WRITE_FAULT	10
-#define BCD_DE_READ_FAULT	11
-#define BCD_DE_GENERAL_FAILURE	12
-#define BCD_DE_INVALID_DISK_CHANGE	15
-/* set by BCD itself, for stuff like "Out of memory" */
-extern const char *_bcd_error;
-
-/* uncomment this line to force BCD to use a statically allocated
-   Track array instead of using malloc */
-#define STATIC_TRACKS
-
-#endif
diff --git a/src/djgppdos/i_cdmus.c b/src/djgppdos/i_cdmus.c
deleted file mode 100644
index 2a629ca1738a1ad7e5d36ae6c83a16fdcd2c22e8..0000000000000000000000000000000000000000
--- a/src/djgppdos/i_cdmus.c
+++ /dev/null
@@ -1,445 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief cd music interface (uses bcd library)
-
-// Alam_GBC: I hate you, Brennan Underwood :)
-#include "../doomtype.h"
-#include "bcd.c"
-//#include "bcd.h"                // CD-Audio library by Brennan Underwood
-
-#include "../doomdef.h"
-#include "../i_sound.h"
-#include "../command.h"
-#include "../i_system.h"
-#include "../s_sound.h"
-
-// ------
-// protos
-// ------
-static void Command_Cd_f (void);
-
-
-//======================================================================
-//                   CD AUDIO MUSIC SUBSYSTEM
-//======================================================================
-
-UINT8  cdaudio_started=0;   // for system startup/shutdown
-
-#define MAX_CD_TRACKS 256
-static boolean cdPlaying = false;
-static int     cdPlayTrack;         // when cdPlaying is true
-static boolean cdLooping = false;
-static UINT8   cdRemap[MAX_CD_TRACKS];
-static boolean cdEnabled=true;      // cd info available
-static boolean cdValid;             // true when last cd audio info was ok
-static boolean wasPlaying;
-static int     cdVolume=0;          // current cd volume (0-31)
-
-// 0-31 like Music & Sfx, though CD hardware volume is 0-255.
-consvar_t cd_volume = {"cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-// allow Update for next/loop track
-// some crap cd drivers take up to
-// a second for a simple 'busy' check..
-// (on those Update can be disabled)
-consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-
-// hour,minutes,seconds
-FUNCINLINE static ATTRINLINE char *hms(int hsg)
-{
-	int hours, minutes, seconds;
-	static char s[9];
-
-	seconds = hsg / 75;
-	minutes = seconds / 60;
-	seconds %= 60;
-	hours = minutes / 60;
-	minutes %= 60;
-	if (hours>0)
-		sprintf (s, "%d:%02d:%02d", hours, minutes, seconds);
-	else
-		sprintf (s, "%2d:%02d", minutes, seconds);
-	return s;
-}
-
-static void Command_Cd_f (void)
-{
-	const char *    s;
-	int       i,j;
-
-	if (!cdaudio_started)
-		return;
-
-	if (COM_Argc()<2)
-	{
-		CONS_Printf ("cd [on] [off] [remap] [reset] [open]\n"
-		             "   [info] [play <track>] [loop <track>]\n"
-		             "   [stop] [resume]\n");
-		return;
-	}
-
-	s = COM_Argv(1);
-
-	// activate cd music
-	if (!strncmp(s,"on",2))
-	{
-		cdEnabled = true;
-		return;
-	}
-
-	// stop/deactivate cd music
-	if (!strncmp(s,"off",3))
-	{
-		if (cdPlaying)
-			I_StopCD ();
-		cdEnabled = false;
-		return;
-	}
-
-	// remap tracks
-	if (!strncmp(s,"remap",5))
-	{
-		i = COM_Argc() - 2;
-		if (i <= 0)
-		{
-			CONS_Printf ("CD tracks remapped in that order :\n");
-			for (j = 1; j < MAX_CD_TRACKS; j++)
-				if (cdRemap[j] != j)
-					CONS_Printf (" %2d -> %2d\n", j, cdRemap[j]);
-			return;
-		}
-		for (j = 1; j <= i; j++)
-			cdRemap[j] = atoi (COM_Argv (j+1));
-		return;
-	}
-
-	// reset the CD driver, useful on some odd cd's
-	if (!strncmp(s,"reset",5))
-	{
-		cdEnabled = true;
-		if (cdPlaying)
-			I_StopCD ();
-		for (i = 0; i < MAX_CD_TRACKS; i++)
-			cdRemap[i] = i;
-		bcd_reset ();
-		cdValid = bcd_get_audio_info ();
-		return;
-	}
-
-	// any other command is not allowed until we could retrieve cd information
-	if (!cdValid)
-	{
-		CONS_Printf ("CD is not ready.\n");
-		return;
-	}
-
-	if (!strncmp(s,"open",4))
-	{
-		if (cdPlaying)
-			I_StopCD ();
-		bcd_open_door();
-		cdValid = false;
-		return;
-	}
-
-	if (!strncmp(s,"info",4))
-	{
-		int totaltime = 0;
-
-		if (!bcd_get_audio_info())
-		{
-			CONS_Printf ("Error getting audio info: %s\n",bcd_error());
-			cdValid = false;
-			return;
-		}
-
-		cdValid = true;
-
-		if (lowest_track == 0)
-			CONS_Printf ("No audio tracks\n");
-		else
-		{
-			// display list of tracks
-			// highlight current playing track
-			for (i=lowest_track; i <= highest_track; i++)
-			{
-				CONS_Printf    ("%s%2d. %s  %s\n",
-								cdPlaying && (cdPlayTrack == i) ? "\2 " : " ",
-								i, tracks[i].is_audio ? "audio" : "data ",
-								hms(tracks[i].len));
-				if (tracks[i].is_audio)
-					totaltime += tracks[i].len;
-			}
-
-			if (totaltime)
-				CONS_Printf ("\2Total time : %s\n", hms(totaltime));
-		}
-		if (cdPlaying)
-		{
-			CONS_Printf ("%s track : %d\n", cdLooping ? "looping" : "playing",
-			             cdPlayTrack);
-		}
-		return;
-	}
-
-	if (!strncmp(s,"play",4))
-	{
-		I_PlayCD ((UINT8)atoi (COM_Argv (2)), false);
-		return;
-	}
-
-	if (!strncmp(s,"stop",4))
-	{
-		I_StopCD ();
-		return;
-	}
-
-	if (!strncmp(s,"loop",4))
-	{
-		I_PlayCD ((UINT8)atoi (COM_Argv (2)), true);
-		return;
-	}
-
-	if (!strncmp(s,"resume",4))
-	{
-		I_ResumeCD ();
-		return;
-	}
-
-	CONS_Printf ("cd command '%s' unknown\n", s);
-}
-
-
-// pause cd music
-void I_StopCD (void)
-{
-	if (!cdaudio_started || !cdEnabled)
-		return;
-
-	bcd_stop();
-
-	wasPlaying = cdPlaying;
-	cdPlaying = false;
-}
-
-// continue after a pause
-void I_ResumeCD (void)
-{
-	if (!cdaudio_started || !cdEnabled)
-		return;
-
-	if (!cdValid)
-		return;
-
-	if (!wasPlaying)
-		return;
-
-	bcd_resume ();
-	cdPlaying = true;
-}
-
-
-void I_ShutdownCD (void)
-{
-	int rc;
-
-	if (!cdaudio_started)
-		return;
-
-	I_StopCD ();
-
-	rc = bcd_close ();
-	if (!rc)
-		CONS_Printf ("Error shuting down cd\n");
-}
-
-void I_InitCD (void)
-{
-	int rc;
-	int i;
-
-	rc = bcd_open ();
-
-	if (rc>=0x200)
-	{
-		CONS_Printf ("MSCDEX version %d.%d\n", rc>>8, rc&255);
-
-		I_AddExitFunc (I_ShutdownCD);
-		cdaudio_started = true;
-	}
-	else
-	{
-		if (!rc)
-			CONS_Printf ("%s\n", bcd_error() );
-
-		cdaudio_started = false;
-		return;
-	}
-
-	// last saved in config.cfg
-	i = cd_volume.value;
-	I_SetVolumeCD (0);   // initialize to 0 for some odd cd drivers
-	I_SetVolumeCD (i);   // now set the last saved volume
-
-	for (i = 0; i < MAX_CD_TRACKS; i++)
-		cdRemap[i] = i;
-
-	if (!bcd_get_audio_info())
-	{
-		CONS_Printf("\2CD Init: No CD in player.\n");
-		cdEnabled = false;
-		cdValid = false;
-	}
-	else
-	{
-		cdEnabled = true;
-		cdValid = true;
-	}
-
-	COM_AddCommand ("cd", Command_Cd_f);
-}
-
-
-
-// loop/go to next track when track is finished (if cd_update var is true)
-// update the volume when it has changed (from console/menu)
-/// \todo check for cd change and restart music ?
-
-void I_UpdateCD (void)
-{
-	int     newVolume;
-	int     now;
-	static  int last;     //game tics (35th of second)
-
-	if (!cdaudio_started)
-		return;
-
-	now = I_GetTime ();
-	if ((now - last) < 10)        // about 1/4 second
-		return;
-	last = now;
-
-	// update cd volume changed at console/menu
-	newVolume = cd_volume.value & 31;
-
-	if (cdVolume != newVolume)
-		I_SetVolumeCD (newVolume);
-
-	// slow drivers exit here
-	if (!cdUpdate.value)
-		return;
-
-	if (cdPlaying)
-	{
-		if (!bcd_audio_busy())
-		{
-			cdPlaying = false;
-			if (cdLooping)
-				I_PlayCD (cdPlayTrack, true);
-			else
-			{
-				// play the next cd track, or restart the first
-				cdPlayTrack++;
-				if (cdPlayTrack > highest_track)
-					cdPlayTrack = lowest_track;
-				while (!tracks[cdPlayTrack].is_audio && cdPlayTrack<highest_track)
-					cdPlayTrack++;
-				I_PlayCD (cdPlayTrack, true);
-			}
-		}
-	}
-
-}
-
-
-//
-void I_PlayCD (UINT8 track, UINT8 looping)
-{
-	if (!cdaudio_started || !cdEnabled)
-		return;
-
-	if (!cdValid)
-		return;
-
-	track = cdRemap[track];
-
-	if (cdPlaying)
-	{
-		if (cdPlayTrack == track)
-			return;
-		I_StopCD ();
-	}
-
-	if (track < lowest_track || track > highest_track)
-	{
-		//CONS_Printf ("\2CD Audio: wrong track number %d\n", track);
-		// suppose there are not enough tracks for game levels..
-		// then do a modulo so we always get something to hear
-		track = (track % (highest_track-lowest_track+1)) + 1;
-		//return;
-	}
-
-	cdPlayTrack = track;
-
-	if (!tracks[track].is_audio)
-	{
-		CONS_Printf ("\2CD Play: not an audio track\n");
-		return;
-	}
-
-	cdLooping = looping;
-
-	if (!bcd_play_track (track))
-	{
-		CONS_Printf ("CD Play: could not play track %d\n", track);
-		cdValid = false;
-		cdPlaying = false;
-		return;
-	}
-
-	cdPlaying = true;
-
-}
-
-
-// volume : logical cd audio volume 0-31 (hardware is 0-255)
-boolean I_SetVolumeCD (INT32 volume)
-{
-	int  hardvol;
-
-	if (!cdaudio_started || !cdEnabled)
-		return false;
-
-	// translate to hardware volume
-	volume &= 31;
-
-	hardvol = ((volume+1)<<3)-1;  //highest volume is 255
-	if (hardvol<=8)
-		hardvol=0;                //lowest volume is ZERO
-
-	cdVolume = volume;
-
-	if (bcd_set_volume (hardvol))
-	{
-		CV_SetValue (&cd_volume, volume);
-
-		return true;
-	}
-	else
-		return false;
-}
diff --git a/src/djgppdos/i_main.c b/src/djgppdos/i_main.c
deleted file mode 100644
index 1b8894470d30219dfa63dfc5fb0162323fd64131..0000000000000000000000000000000000000000
--- a/src/djgppdos/i_main.c
+++ /dev/null
@@ -1,78 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief Main program, simply calls D_SRB2Main high level loop.
-
-#include "../doomdef.h"
-
-#include "../m_argv.h"
-#include "../d_main.h"
-
-#include "../i_system.h"
-
-#ifdef REMOTE_DEBUGGING
-#include <i386-stub.h>
-#include "rdb.h"
-#endif
-
-//### let's try with Allegro ###
-#define  alleg_mouse_unused
-#define  alleg_timer_unused
-#define  ALLEGRO_NO_KEY_DEFINES
-#define  alleg_keyboard_unused
-#define  alleg_joystick_unused
-#define  alleg_gfx_driver_unused
-#define  alleg_palette_unused
-#define  alleg_graphics_unused
-#define  alleg_vidmem_unused
-#define  alleg_flic_unused
-#define  alleg_sound_unused
-#define  alleg_file_unused
-#define  alleg_datafile_unused
-#define  alleg_math_unused
-#define  alleg_gui_unused
-#include <allegro.h>
-//### end of Allegro include ###
-
-int main (int argc, char **argv)
-{
-	myargc = argc;
-	myargv = argv;
-
-	{
-		//added:03-01-98:
-		//       Setup signal handlers and other stuff BEFORE ANYTHING ELSE!
-		I_StartupSystem();
-#ifdef REMOTE_DEBUGGING
-		/* Only setup if remote debugging  is to be done, Muhahahaha!*/
-		gdb_serial_init(DEBUG_COM_PORT,DEBUG_COM_PORT_SPEED);
-		gdb_target_init();
-		breakpoint();
-#endif
-
-		D_SRB2Main();
-		D_SRB2Loop();
-
-	}
-	//added:03-01-98:
-	//       hmmm... it will never go here.
-
-	return 0;
-}
-#if ALLEGRO_VERSION == 4
-END_OF_MAIN()
-#endif
diff --git a/src/djgppdos/i_net.c b/src/djgppdos/i_net.c
deleted file mode 100644
index 866aa8d8b9ed821fe8f4677d41628b3c01b539f5..0000000000000000000000000000000000000000
--- a/src/djgppdos/i_net.c
+++ /dev/null
@@ -1,113 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Portions Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief doomcom network interface
-
-
-#include <netinet/in.h>
-#include <errno.h>
-#include <unistd.h>
-
-#include <go32.h>
-#include <pc.h>
-#include <dpmi.h>
-#include <dos.h>
-#include <sys/nearptr.h>
-
-#include "../doomdef.h"
-
-#include "../i_system.h"
-#include "../d_event.h"
-#include "../d_net.h"
-#include "../m_argv.h"
-
-#include "../doomstat.h"
-#include "../z_zone.h"
-#include "../i_net.h"
-#include "../i_tcp.h"
-
-//
-// NETWORKING
-//
-
-typedef enum
-{
-	CMD_SEND     = 1,
-	CMD_GET      = 2,
-} command_t;
-
-static void External_Driver_Get(void);
-static void External_Driver_Send(void);
-static void External_Driver_FreeNode(INT32 nodenum);
-
-static inline boolean External_Driver_OpenSocket(void)
-{
-	I_NetGet  = External_Driver_Get;
-	I_NetSend = External_Driver_Send;
-	I_NetCloseSocket = NULL;
-	I_NetFreeNodenum = External_Driver_FreeNode;
-
-	return true;
-}
-
-//
-// I_InitNetwork
-//
-boolean I_InitNetwork (void)
-{
-	int netgamepar;
-
-	netgamepar = M_CheckParm ("-net");
-	if (!netgamepar)
-		return false;
-
-	// externals drivers specific
-
-	__djgpp_nearptr_enable();
-
-	// set up for network
-	doomcom=(doomcom_t *)(__djgpp_conventional_base+atoi(myargv[netgamepar+1]));
-	CONS_Printf("I_DosNet : Using int 0x%x for communication\n",doomcom->intnum);
-
-	server = (doomcom->consoleplayer == 0);
-	if (!server)
-		COM_BufAddText("connect any\n");
-
-	// ipx + time + 4 (padding)
-	packetheaderlength=30+4+4;
-
-	hardware_MAXPACKETLENGTH = 512;
-
-	I_NetOpenSocket = External_Driver_OpenSocket;
-	return true;
-}
-
-FUNCNORETURN static ATTRNORETURN void External_Driver_Get(void)
-{
-	I_Error("External_Driver_Get not supported at this time");
-}
-
-FUNCNORETURN static ATTRNORETURN void External_Driver_Send(void)
-{
-	I_Error("External_Driver_Send not supported at this time");
-}
-
-FUNCNORETURN static ATTRNORETURN void External_Driver_FreeNode(INT32 nodenum)
-{
-	nodenum = 0;
-	I_Error("External_Driver_FreeNode not supported at this time");
-}
diff --git a/src/djgppdos/i_sound.c b/src/djgppdos/i_sound.c
deleted file mode 100644
index 847853a89a5af711e3f58e2230bedacf416b8912..0000000000000000000000000000000000000000
--- a/src/djgppdos/i_sound.c
+++ /dev/null
@@ -1,620 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Portions Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief interface level code for sound
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-
-#include <math.h>
-
-#include "../doomdef.h"
-#include "../doomstat.h"
-#include "../i_system.h"
-#include "../i_sound.h"
-#include "../z_zone.h"
-#include "../m_argv.h"
-#include "../m_misc.h"
-#include "../w_wad.h"
-#include "../s_sound.h"
-#include "../console.h"
-
-//### let's try with Allegro ###
-#define  alleg_mouse_unused
-#define  alleg_timer_unused
-#define  ALLEGRO_NO_KEY_DEFINES
-#define  alleg_keyboard_unused
-#define  alleg_joystick_unused
-#define  alleg_gfx_driver_unused
-#define  alleg_palette_unused
-#define  alleg_graphics_unused
-#define  alleg_vidmem_unused
-#define  alleg_flic_unused
-//#define  alleg_sound_unused    we use it
-#define  alleg_file_unused
-#define  alleg_datafile_unused
-#define  alleg_math_unused
-#define  alleg_gui_unused
-#include <allegro.h>
-//### end of Allegro include ###
-
-//allegro has 256 virtual voices
-// warning should by a power of 2
-#define VIRTUAL_VOICES 256
-#define VOICESSHIFT 8
-
-// Needed for calling the actual sound output.
-#define SAMPLECOUNT    512
-
-
-
-//
-// this function converts raw 11khz, 8-bit data to a SAMPLE* that allegro uses
-// it is need cuz allegro only loads samples from wavs and vocs
-//added:11-01-98: now reads the frequency from the rawdata header.
-//   dsdata points a 4 UINT16 header:
-//    +0 : value 3 what does it mean?
-//    +2 : sample rate, either 11025 or 22050.
-//    +4 : number of samples, each sample is a single byte since it's 8bit
-//    +6 : value 0
-static inline SAMPLE *raw2SAMPLE(UINT8 *dsdata, size_t len)
-{
-	SAMPLE *spl;
-
-	spl=Z_Malloc(sizeof (SAMPLE),PU_SOUND,NULL);
-	if (spl==NULL)
-		I_Error("Raw2Sample : no more free mem");
-	spl->bits = 8;
-	spl->stereo = 0;
-	spl->freq = *((UINT16 *)dsdata+1);   //mostly 11025, but some at 22050.
-	spl->len = len-8;
-	spl->priority = 255;                //priority;
-	spl->loop_start = 0;
-	spl->loop_end = len-8;
-	spl->param = -1;
-	spl->data=(void *)(dsdata+8);       //skip the 8bytes header
-
-	return spl;
-}
-
-
-//  This function loads the sound data from the WAD lump,
-//  for single sound.
-//
-void *I_GetSfx (sfxinfo_t * sfx)
-{
-	UINT8 *dssfx;
-
-	if (sfx->lumpnum == LUMPERROR)
-		sfx->lumpnum = S_GetSfxLumpNum (sfx);
-
-	sfx->length = W_LumpLength (sfx->lumpnum);
-
-	dssfx = (UINT8 *) W_CacheLumpNum (sfx->lumpnum, PU_SOUND);
-	//_go32_dpmi_lock_data(dssfx,size);
-
-	// convert raw data and header from Doom sfx to a SAMPLE for Allegro
-	return (void *)raw2SAMPLE (dssfx, sfx->length);
-}
-
-
-void I_FreeSfx (sfxinfo_t *sfx)
-{
-	if (sfx->lumpnum == LUMPERROR)
-		return;
-
-	// free sample data
-	if ( sfx->data )
-		Z_Free((UINT8 *) ((SAMPLE *)sfx->data)->data - 8);
-	Z_Free(sfx->data); // Allegro SAMPLE structure
-	sfx->data = NULL;
-	sfx->lumpnum = LUMPERROR;
-}
-
-FUNCINLINE static ATTRINLINE int Volset(int vol)
-{
-	return (vol*255/31);
-}
-
-
-void I_SetSfxVolume(INT32 volume)
-{
-	if (sound_disabled)
-		return;
-
-	set_volume (Volset(volume),-1);
-}
-
-//
-// Starting a sound means adding it
-//  to the current list of active sounds
-//  in the internal channels.
-// As the SFX info struct contains
-//  e.g. a pointer to the raw data,
-//  it is ignored.
-// As our sound handling does not handle
-//  priority, it is ignored.
-// Pitching (that is, increased speed of playback)
-//  is set, but currently not used by mixing.
-//
-INT32 I_StartSound ( sfxenum_t     id,
-                   INT32         vol,
-                   INT32         sep,
-                   INT32         pitch,
-                   INT32         priority,
-				   INT32         channel)
-{
-	int voice;
-	(void)channel;
-
-	if (sound_disabled)
-	return 0;
-
-	// UNUSED
-	priority = 0;
-
-	pitch = (pitch-128)/2+128;
-	voice = play_sample(S_sfx[id].data,vol,sep,(pitch*1000)/128,0);
-
-	// Returns a handle
-	return (id<<VOICESSHIFT)+voice;
-}
-
-void I_StopSound (INT32 handle)
-{
-	// You need the handle returned by StartSound.
-	// Would be looping all channels,
-	//  tracking down the handle,
-	//  an setting the channel to zero.
-	int voice=handle & (VIRTUAL_VOICES-1);
-
-	if (sound_disabled)
-		return;
-
-	if (voice_check(voice)==S_sfx[handle>>VOICESSHIFT].data)
-		deallocate_voice(voice);
-}
-
-INT32 I_SoundIsPlaying(INT32 handle)
-{
-	if (sound_disabled)
-		return FALSE;
-
-	if (voice_check(handle & (VIRTUAL_VOICES-1))==S_sfx[handle>>VOICESSHIFT].data)
-		return TRUE;
-
-	return FALSE;
-}
-
-// cut and past from ALLEGRO he don't share it :(
-static inline int absolute_freq(int freq, SAMPLE *spl)
-{
-	if (freq == 1000)
-		return spl->freq;
-	else
-		return (spl->freq * freq) / 1000;
-}
-
-void I_UpdateSoundParams( INT32 handle,
-                          INT32 vol,
-                          INT32 sep,
-                          INT32 pitch)
-{
-	// I fail too see that this is used.
-	// Would be using the handle to identify
-	//  on which channel the sound might be active,
-	//  and resetting the channel parameters.
-	int voice=handle & (VIRTUAL_VOICES-1);
-	int numsfx=handle>>VOICESSHIFT;
-
-	if (sound_disabled)
-		return;
-
-	if (voice_check(voice)==S_sfx[numsfx].data)
-	{
-		voice_set_volume(voice, vol);
-		voice_set_pan(voice, sep);
-		voice_set_frequency(voice, absolute_freq(pitch*1000/128,
-		                    S_sfx[numsfx].data));
-	}
-}
-
-
-void I_ShutdownSound(void)
-{
-	// Wait till all pending sounds are finished.
-
-	//added:03-01-98:
-	if ( !sound_started )
-		return;
-
-	//added:08-01-98: remove_sound() explicitly because we don't use
-	//                Allegro's allegro_exit();
-	remove_sound();
-	sound_started = false;
-}
-
-static char soundheader[] = "sound";
-#if ALLEGRO_VERSION == 3
-static char soundvar[] = "sb_freq";
-#else
-static char soundvar[] = "sound_freq";
-#endif
-
-void I_StartupSound(void)
-{
-	int    sfxcard,midicard;
-#if ALLEGRO_VERSION == 3
-	char   err[255];
-#endif
-
-	if (sound_disabled)
-		sfxcard=DIGI_NONE;
-	else
-		sfxcard=DIGI_AUTODETECT;
-
-	if (midi_disabled)
-		midicard=MIDI_NONE;
-	else
-		midicard=MIDI_AUTODETECT; //DetectMusicCard();
-
-	digital_disabled=true; //Alam: No OGG/MP3/IT/MOD support
-
-	// Secure and configure sound device first.
-	CONS_Printf("I_StartupSound: ");
-
-	//Fab:25-04-98:note:install_sound will check for sound settings
-	//    in the sound.cfg or allegro.cfg, in the current directory,
-	//    or the directory pointed by 'ALLEGRO' env var.
-#if ALLEGRO_VERSION == 3
-	if (install_sound(sfxcard,midicard,NULL)!=0)
-	{
-		sprintf (err,"Sound init error : %s\n",allegro_error);
-		CONS_Error (err);
-		sound_disabled=true;
-		midi_disabled=true;
-	}
-	else
-	{
-		CONS_Printf(" configured audio device\n" );
-	}
-
-	//added:08-01-98:we use a similar startup/shutdown scheme as Allegro.
-	I_AddExitFunc(I_ShutdownSound);
-#endif
-	sound_started = true;
-	CV_SetValue(&cv_samplerate,get_config_int(soundheader,soundvar,cv_samplerate.value));
-}
-
-
-
-
-//
-// MUSIC API.
-// Still no music done.
-// Remains. Dummies.
-//
-
-static MIDI* currsong;   //im assuming only 1 song will be played at once
-static int      islooping=0;
-static int      musicdies=-1;
-UINT8           music_started=0;
-boolean         songpaused=false;
-
-/// ------------------------
-//  MUSIC SYSTEM
-/// ------------------------
-
-/* load_midi_mem:
- *  Loads a standard MIDI file from memory, returning a pointer to
- *  a MIDI structure, *  or NULL on error.
- *  It is the load_midi from Allegro modified to load it from memory
- */
-static MIDI *load_midi_mem(char *mempointer,int *e)
-{
-	int c = *e;
-	long data=0;
-	unsigned char *fp;
-	MIDI *midi;
-	int num_tracks=0;
-
-	fp = (void *)mempointer;
-	if (!fp)
-		return NULL;
-
-	midi = malloc(sizeof (MIDI));              /* get some memory */
-	if (!midi)
-		return NULL;
-
-	for (c=0; c<MIDI_TRACKS; c++)
-	{
-		midi->track[c].data = NULL;
-		midi->track[c].len = 0;
-	}
-
-	fp+=4+4;   // header size + 'chunk' size
-
-	swab(fp,&data,2);     // convert to intel-endian
-	fp+=2;                                      /* MIDI file type */
-	if ((data != 0) && (data != 1)) // only type 0 and 1 are suported
-		return NULL;
-
-	swab(fp,&num_tracks,2);                     /* number of tracks */
-	fp+=2;
-	if ((num_tracks < 1) || (num_tracks > MIDI_TRACKS))
-		return NULL;
-
-	swab(fp,&data,2);                          /* beat divisions */
-	fp+=2;
-	midi->divisions = ABS(data);
-
-	for (c=0; c<num_tracks; c++)
-	{            /* read each track */
-		if (memcmp(fp, "MTrk", 4))
-			return NULL;
-		fp+=4;
-
-		//swab(fp,&data,4);       don't work !!!!??
-		((char *)&data)[0]=fp[3];
-		((char *)&data)[1]=fp[2];
-		((char *)&data)[2]=fp[1];
-		((char *)&data)[3]=fp[0];
-		fp+=4;
-
-		midi->track[c].len = data;
-
-		midi->track[c].data = fp;
-		fp+=data;
-	}
-
-	lock_midi(midi);
-	return midi;
-}
-
-void I_InitMusic(void)
-{
-	if (midi_disabled)
-		return;
-
-	I_AddExitFunc(I_ShutdownMusic);
-	music_started = true;
-	songpaused = false;
-}
-
-void I_ShutdownMusic(void)
-{
-	if ( !music_started )
-		return;
-
-	I_StopSong();
-
-	music_started=false;
-}
-
-/// ------------------------
-//  MUSIC PROPERTIES
-/// ------------------------
-
-musictype_t I_SongType(void)
-{
-	if (currsong)
-		return MU_MID;
-	else
-		return MU_NONE;
-}
-
-boolean I_SongPlaying()
-{
-	return (boolean)currsong;
-}
-
-boolean I_SongPaused()
-{
-	return songpaused;
-}
-
-/// ------------------------
-//  MUSIC EFFECTS
-/// ------------------------
-
-boolean I_SetSongSpeed(float speed)
-{
-	(void)speed;
-	return false;
-}
-
-/// ------------------------
-// MUSIC SEEKING
-/// ------------------------
-
-UINT32 I_GetSongLength(void)
-{
-	return 0;
-}
-
-boolean I_SetSongLoopPoint(UINT32 looppoint)
-{
-        (void)looppoint;
-        return false;
-}
-
-UINT32 I_GetSongLoopPoint(void)
-{
-	return 0;
-}
-
-boolean I_SetSongPosition(UINT32 position)
-{
-    (void)position;
-    return false;
-}
-
-UINT32 I_GetSongPosition(void)
-{
-    return 0;
-}
-
-/// ------------------------
-//  MUSIC PLAYBACK
-/// ------------------------
-
-boolean I_LoadSong(char *data, size_t len)
-{
-	int e = len; //Alam: For error
-	if (midi_disabled)
-		return 0;
-
-	if (memcmp(data,"MThd",4)==0) // support mid file in WAD !!!
-	{
-		currsong=load_midi_mem(data,&e);
-	}
-	else
-	{
-		CONS_Printf("Music Lump is not a MIDI lump\n");
-		return 0;
-	}
-
-	if (currsong==NULL)
-	{
-		CONS_Printf("Not a valid mid file : %d\n",e);
-		return 0;
-	}
-
-	return 1;
-}
-
-void I_UnloadSong(void)
-{
-	handle = 0;
-	if (midi_disabled)
-		return;
-
-	//destroy_midi(currsong);
-}
-
-boolean I_PlaySong(boolean looping)
-{
-	handle = 0;
-	if (midi_disabled)
-		return false;
-
-	islooping = looping;
-	musicdies = gametic + NEWTICRATE*30;
-	if (play_midi(currsong,looping)==0)
-		return true;
-	return false;
-}
-
-void I_StopSong(void)
-{
-	handle = 0;
-	if (midi_disabled)
-		return;
-
-	islooping = 0;
-	musicdies = 0;
-	stop_midi();
-	songpaused = false;
-}
-
-void I_PauseSong (INT32 handle)
-{
-	handle = 0;
-	if (midi_disabled)
-		return;
-	midi_pause();
-	songpaused = true;
-}
-
-void I_ResumeSong (INT32 handle)
-{
-	handle = 0;
-	if (midi_disabled)
-		return;
-	midi_resume();
-	songpaused = false;
-}
-
-
-void I_SetMusicVolume(INT32 volume)
-{
-	if (midi_disabled)
-		return;
-
-	// Now set volume on output device.
-	set_volume (-1, Volset(volume));
-}
-
-boolean I_SetSongTrack(INT32 track)
-{
-	(void)track;
-	return false;
-}
-
-// Is the song playing?
-#if 0
-int I_QrySongPlaying(int handle)
-{
-	if (midi_disabled)
-		return 0;
-
-	//return islooping || musicdies > gametic;
-	return (midi_pos==-1);
-}
-#endif
-
-/// ------------------------
-// MUSIC FADING
-/// ------------------------
-
-void I_SetInternalMusicVolume(UINT8 volume)
-{
-	(void)volume;
-}
-
-void I_StopFadingSong(void)
-{
-}
-
-boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void));
-{
-	(void)target_volume;
-	(void)source_volume;
-	(void)ms;
-	return false;
-}
-
-boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
-{
-	(void)target_volume;
-	(void)ms;
-	return false;
-}
-
-boolean I_FadeOutStopSong(UINT32 ms)
-{
-	(void)ms;
-	return false;
-}
-
-boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
-{
-        (void)ms;
-        (void)looping;
-        return false;
-}
diff --git a/src/djgppdos/i_system.c b/src/djgppdos/i_system.c
deleted file mode 100644
index 9f6972fa67663065e65fb29973d99ee60711d1aa..0000000000000000000000000000000000000000
--- a/src/djgppdos/i_system.c
+++ /dev/null
@@ -1,1772 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Portions Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief Misc. stuff
-///
-///	Startup & Shutdown routines for music,sound,timer,keyboard,
-///	Signal handler to trap errors and exit cleanly.
-
-#include <stdlib.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <io.h>
-#include <stdarg.h>
-#include <sys/time.h>
-#include <fcntl.h>
-
-#ifdef DJGPP
- #include <dpmi.h>
- #include <go32.h>
- #include <pc.h>
- #include <dos.h>
- #include <crt0.h>
- #include <sys/segments.h>
- #include <sys/nearptr.h>
-
- #include <keys.h>
-#endif
-
-
-#include "../doomdef.h"
-#include "../m_misc.h"
-#include "../i_video.h"
-#include "../i_sound.h"
-#include "../i_system.h"
-#include "../d_net.h"
-#include "../g_game.h"
-
-#include "../d_main.h"
-
-#include "../m_argv.h"
-
-#include "../w_wad.h"
-#include "../z_zone.h"
-#include "../g_input.h"
-
-#include "../console.h"
-
-#include "../m_menu.h"
-
-#ifdef __GNUG__
- #pragma implementation "../i_system.h"
-#endif
-
-#include "../i_joy.h"
-
-//### let's try with Allegro ###
-#define  alleg_mouse_unused
-//#define  alleg_timer_unused
-#define  alleg_keyboard_unused
-#define  ALLEGRO_NO_KEY_DEFINES
-//#define  alleg_joystick_unused
-#define  alleg_gfx_driver_unused
-#define  alleg_palette_unused
-#define  alleg_graphics_unused
-#define  alleg_vidmem_unused
-#define  alleg_flic_unused
-#define  alleg_sound_unused
-#define  alleg_file_unused
-#define  alleg_datafile_unused
-#define  alleg_math_unused
-#define  alleg_gui_unused
-#include <allegro.h>
-//### end of Allegro include ###
-
-#ifndef DOXYGEN
-
-#ifndef MAX_JOYSTICKS
-#define MAX_JOYSTICKS 4
-#endif
-
-#ifndef MAX_JOYSTICK_AXIS
-#define MAX_JOYSTICK_AXIS 3
-#endif
-
-#ifndef MAX_JOYSTICK_STICKS
-#define MAX_JOYSTICK_STICKS 4
-#endif
-
-#ifndef MAX_JOYSTICK_BUTTONS
-#define MAX_JOYSTICK_BUTTONS 12
-#endif
-
-#endif
-
-#if ALLEGRO_VERSION == 4
-static char JOYFILE[] = "allegro4.cfg";
-#elif ALLEGRO_VERSION == 3
-static char JOYFILE[] = "allegro.cfg";
-#endif
-
-/// \brief max number of joystick buttons
-#define JOYBUTTONS_MAX MAX_JOYSTICK_BUTTONS // <allegro/joystick.h>
-/// \brief max number of joystick button events
-#define JOYBUTTONS_MIN min((JOYBUTTONS),(JOYBUTTONS_MAX))
-
-/// \brief max number of joysick axies
-#define JOYAXISSET_MAX MAX_JOYSTICK_STICKS
-// \brief max number ofjoystick axis events
-#define JOYAXISSET_MIN min((JOYAXISSET),(JOYAXISSET_MAX))
-
-/// \brief max number of joystick hats
-#define JOYHATS_MAX MAX_JOYSTICK_STICKS
-/// \brief max number of joystick hat events
-#define JOYHATS_MIN min((JOYHATS),(JOYHATS_MAX))
-
-/// \brief max number of mouse buttons
-#define MOUSEBUTTONS_MAX 16 // 16 bit of BL
-/// \brief max number of muse button events
-#define MOUSEBUTTONS_MIN min((MOUSEBUTTONS),(MOUSEBUTTONS_MAX))
-
-// Do not execute cleanup code more than once. See Shutdown_xxx() routines.
-UINT8 graphics_started = false;
-UINT8 keyboard_started = false;
-UINT8 sound_started    = false;
-static UINT8 timer_started    = false;
-
-/* Mouse stuff */
-static UINT8 mouse_detected   = false;
-static UINT8 wheel_detected   = false;
-
-static volatile tic_t ticcount;   //returned by I_GetTime(), updated by timer interrupt
-
-
-void I_Tactile(FFType Type, const JoyFF_t *Effect)
-{
-	// UNUSED.
-	Type = EvilForce;
-	Effect = NULL;
-}
-
-void I_Tactile2(FFType Type, const JoyFF_t *Effect)
-{
-	// UNUSED.
-	Type = EvilForce;
-	Effect = NULL;
-}
-
-static ticcmd_t        emptycmd;
-ticcmd_t *      I_BaseTiccmd(void)
-{
-	return &emptycmd;
-}
-
-static ticcmd_t        emptycmd2;
-ticcmd_t *      I_BaseTiccmd2(void)
-{
-	return &emptycmd2;
-}
-
-void I_SetupMumble(void)
-{
-}
-
-#ifndef NOMUMBLE
-void I_UpdateMumble(const mobj_t *mobj, const listener_t listener)
-{
-	(void)mobj;
-	(void)listener;
-}
-#endif
-
-//
-//  Allocates the base zone memory,
-//  this function returns a valid pointer and size,
-//  else it should interrupt the program immediately.
-//
-//added:11-02-98: now checks if mem could be allocated, this is still
-//    prehistoric... there's a lot to do here: memory locking, detection
-//    of win95 etc...
-//
-
-#if ALLEGRO_VERSION == 3
-extern int os_type;
-extern int windows_version;
-extern int windows_sub_version;
-extern int i_love_bill;
-#elif ALLEGRO_VERSION == 4
-#endif
-
-static void I_DetectOS (void)
-{
-#if ALLEGRO_VERSION == 3
-	char buf[16];
-	__dpmi_regs r;
-	union REGS regs;
-
-	/* check which OS we are running under */
-	r.x.ax = 0x1600;
-	__dpmi_int(0x2F, &r);
-	if ((r.h.al != 0) && (r.h.al != 1) && (r.h.al != 0x80) && (r.h.al != 0xFF))
-	{
-		/* win 3.1 or 95 */
-		if (r.h.al == 4)
-		{
-			if (r.h.ah < 10)
-			{
-				os_type = OSTYPE_WIN95;
-			}
-			else
-			{
-				os_type = OSTYPE_WIN98;
-			}
-		}
-		else
-		{
-			os_type = OSTYPE_WIN3;
-		}
-
-		windows_version = r.h.al;
-		windows_sub_version = r.h.ah;
-		i_love_bill = TRUE;
-	}
-	else
-	{
-		if (_get_dos_version(1) == 0x0532)
-		{
-			/* win NT */
-			os_type = OSTYPE_WINNT;
-			windows_version = 0x100;
-			windows_sub_version = 0;
-			i_love_bill = TRUE;
-		}
-		else
-		{
-			/* see if OS/2 is present */
-			r.x.ax = 0x4010;
-			__dpmi_int(0x2F, &r);
-			if (r.x.ax != 0x4010)
-			{
-				if (r.x.ax == 0x0000)
-				{
-					/* OS/2 Warp 3 */
-					os_type = OSTYPE_WARP;
-					i_love_bill = TRUE;
-				}
-				else
-				{
-					/* no Warp, but previous OS/2 is available */
-					os_type = OSTYPE_OS2;
-					i_love_bill = TRUE;
-				}
-			}
-			else
-			{
-				/* check if running under Linux DOSEMU */
-				dosmemget(0xFFFF5, 10, buf);
-				buf[8] = 0;
-				if (!strcmp(buf, "02/25/93"))
-				{
-					regs.x.ax = 0;
-					int86(0xE6, &regs, &regs);
-					if (regs.x.ax == 0xAA55)
-					{
-						os_type = OSTYPE_DOSEMU;
-						windows_version = -1;
-						windows_sub_version = -1;
-						i_love_bill = TRUE;     /* (evil chortle) */
-					}
-				}
-				else
-				{
-					/* check if running under OpenDOS */
-					r.x.ax = 0x4452;
-					__dpmi_int(0x21, &r);
-					if ((r.x.ax >= 0x1072) && !(r.x.flags & 1))
-					{
-						os_type = OSTYPE_OPENDOS;
-						/* now check for OpenDOS EMM386.EXE */
-						r.x.ax = 0x12FF;
-						r.x.bx = 0x0106;
-						__dpmi_int(0x2F, &r);
-						if ((r.x.ax == 0x0) && (r.x.bx == 0xEDC0))
-						{
-							i_love_bill = TRUE;
-						}
-					}
-				}
-			}
-		}
-	}
-#elif ALLEGRO_VERSION == 4
-/// \todo: add Allegro 4 version
-#endif
-}
-
-UINT32 I_GetFreeMem(UINT32 *total)
-{
-	__dpmi_free_mem_info     info;
-
-	__dpmi_get_free_memory_information(&info);
-	if ( total )
-		*total = info.total_number_of_physical_pages<<12; // <<12 for convert page to byte
-	return info.total_number_of_free_pages<<12;
-}
-
-
-/*==========================================================================*/
-// I_GetTime ()
-/*==========================================================================*/
-tic_t I_GetTime (void)
-{
-	return ticcount;
-}
-
-
-void I_Sleep(void)
-{
-	if (cv_sleep.value > 0)
-		rest(cv_sleep.value);
-}
-
-
-static UINT8 joystick_detected = false;
-static UINT8 joystick2_detected = false;
-
-//
-// I_Init
-//
-
-
-FUNCINLINE static ATTRINLINE int I_WaitJoyButton (int js)
-{
-	CON_Drawer ();
-	I_FinishUpdate ();        // page flip or blit buffer
-
-	do
-	{
-		if (I_GetKey())
-			return false;
-		poll_joystick();
-	} while (!(joy[js].button[0].b || joy[js].button[1].b));
-
-	return true;
-}
-
-/**	\brief Joystick 1 buttons states
-*/
-static INT64 lastjoybuttons = 0;
-/**	\brief Joystick 1 hats state
-*/
-static INT64 lastjoyhats = 0;
-
-void I_InitJoystick (void)
-{
-	//init the joystick
-	if (joystick_detected && !joystick2_detected)
-		remove_joystick();
-	joystick_detected=0;
-	if (M_CheckParm("-nojoy"))
-		return;
-	load_joystick_data(JOYFILE);
-	if (cv_usejoystick.value)
-	{
-		if (cv_usejoystick.value > MAX_JOYSTICKS)
-			cv_usejoystick.value = MAX_JOYSTICKS;
-		if (install_joystick(JOY_TYPE_AUTODETECT) == 0)
-		{
-			int js = cv_usejoystick.value -1;
-			// only gamepadstyle joysticks
-			if (joy[js].stick[0].flags & JOYFLAG_DIGITAL) Joystick.bGamepadStyle=true;
-
-			while (joy[js].flags & JOYFLAG_CALIBRATE)
-			{
-				const char *msg = calibrate_joystick_name(js);
-				CONS_Printf("%s, and press a button\n", msg);
-				if (I_WaitJoyButton(js))
-					calibrate_joystick(js);
-				else
-				{
-					if (joystick_detected && !joystick2_detected)
-						remove_joystick();
-					joystick_detected=0;
-					CV_SetValue(&cv_usejoystick, 0);
-					return;
-				}
-			}
-			joystick_detected=1;
-			save_joystick_data(JOYFILE);
-		}
-		else
-		{
-			CONS_Printf("\2No Joystick detected.\n");
-		}
-	}
-	else
-	{
-		int i;
-		event_t event;
-		event.type=ev_keyup;
-		event.data2 = 0;
-		event.data3 = 0;
-
-		lastjoybuttons = lastjoyhats = 0;
-
-		// emulate the up of all joystick buttons
-		for (i=0;i<JOYBUTTONS;i++)
-		{
-			event.data1=KEY_JOY1+i;
-			D_PostEvent(&event);
-		}
-
-		// emulate the up of all joystick hats
-		for (i=0;i<JOYHATS*4;i++)
-		{
-			event.data1=KEY_HAT1+i;
-			D_PostEvent(&event);
-		}
-
-		// reset joystick position
-		event.type = ev_joystick;
-		for (i=0;i<JOYAXISSET; i++)
-		{
-			event.data1 = i;
-			D_PostEvent(&event);
-		}
-	}
-}
-
-/**	\brief Joystick 2 buttons states
-*/
-static INT64 lastjoy2buttons = 0;
-/**	\brief Joystick 2 hats state
-*/
-static INT64 lastjoy2hats = 0;
-
-void I_InitJoystick2 (void)
-{
-	//init the joystick
-	if (joystick2_detected && !joystick_detected)
-		remove_joystick();
-	joystick2_detected=0;
-	if (M_CheckParm("-nojoy"))
-		return;
-	if (cv_usejoystick2.value)
-	{
-		if (cv_usejoystick2.value > MAX_JOYSTICKS)
-			cv_usejoystick2.value = MAX_JOYSTICKS;
-		if (install_joystick(JOY_TYPE_AUTODETECT) == 0)
-		{
-			int js = cv_usejoystick2.value -1;
-			// only gamepadstyle joysticks
-			load_joystick_data(JOYFILE);
-			if (joy[js].stick[0].flags & JOYFLAG_DIGITAL) Joystick2.bGamepadStyle=true;
-
-			while (joy[js].flags & JOYFLAG_CALIBRATE)
-			{
-				const char *msg = calibrate_joystick_name(js);
-				CONS_Printf("%s, and press a button\n", msg);
-				if (I_WaitJoyButton(js))
-					calibrate_joystick(js);
-				else
-				{
-					if (joystick2_detected && !joystick_detected)
-						remove_joystick();
-					joystick2_detected=0;
-					CV_SetValue(&cv_usejoystick2, 0);
-					return;
-				}
-			}
-			joystick2_detected=1;
-			save_joystick_data(JOYFILE);
-		}
-		else
-		{
-			CONS_Printf("\2No Joystick detected.\n");
-		}
-	}
-	else
-	{
-		int i;
-		event_t event;
-		event.type=ev_keyup;
-		event.data2 = 0;
-		event.data3 = 0;
-
-		lastjoy2buttons = lastjoy2hats = 0;
-
-		// emulate the up of all joystick buttons
-		for (i=0;i<JOYBUTTONS;i++)
-		{
-			event.data1=KEY_2JOY1+i;
-			D_PostEvent(&event);
-		}
-
-		// emulate the up of all joystick hats
-		for (i=0;i<JOYHATS*4;i++)
-		{
-			event.data1=KEY_2HAT1+i;
-			D_PostEvent(&event);
-		}
-
-		// reset joystick position
-		event.type = ev_joystick2;
-		for (i=0;i<JOYAXISSET; i++)
-		{
-			event.data1 = i;
-			D_PostEvent(&event);
-		}
-	}
-}
-
-//added:18-02-98: put an error message (with format) on stderr
-void I_OutputMsg (const char *error, ...)
-{
-	va_list     argptr;
-
-	va_start (argptr,error);
-	vfprintf (stderr,error,argptr);
-	va_end (argptr);
-
-	// dont flush the message!
-}
-
-static int errorcount=0; // fuck recursive errors
-static int shutdowning=false;
-
-//
-// I_Error
-//
-//added 31-12-97 : display error messy after shutdowngfx
-void I_Error (const char *error, ...)
-{
-	va_list     argptr;
-	// added 11-2-98 recursive error detecting
-
-	M_SaveConfig (NULL);   //save game config, cvars..
-#ifndef NONET
-	D_SaveBan(); // save the ban list
-#endif
-	G_SaveGameData(); // Tails 12-08-2002
-	if (demorecording)
-		G_CheckDemoStatus();
-	D_QuitNetGame ();
-	M_FreePlayerSetupColors();
-
-	if (shutdowning)
-	{
-		errorcount++;
-		if (errorcount==5)
-			I_ShutdownGraphics();
-		if (errorcount==6)
-			I_ShutdownSystem();
-		if (errorcount>7)
-			exit(-1);       // recursive errors detected
-	}
-	shutdowning=true;
-
-	//added:18-02-98: save one time is enough!
-	if (!errorcount)
-	{
-		M_SaveConfig (NULL);   //save game config, cvars..
-		G_SaveGameData(); // Tails 12-08-2002
-	}
-
-	//added:16-02-98: save demo, could be useful for debug
-	//                NOTE: demos are normally not saved here.
-	I_ShutdownMusic();
-	I_ShutdownSound();
-	I_ShutdownCD();
-	I_ShutdownGraphics();
-	I_ShutdownSystem();
-
-	// put message to stderr
-	va_start (argptr,error);
-	fprintf (stderr, "Error: ");
-	vfprintf (stderr,error,argptr);
-#ifdef DEBUGFILE
-	if (debugfile)
-	{
-		fprintf (debugfile,"I_Error :");
-		vfprintf (debugfile,error,argptr);
-	}
-#endif
-
-	va_end (argptr);
-
-	fprintf (stderr, "\nPress ENTER");
-	fflush( stderr );
-	getchar();
-	W_Shutdown();
-	exit(-1);
-}
-
-
-//
-// I_Quit : shutdown everything cleanly, in reverse order of Startup.
-//
-void I_Quit (void)
-{
-	UINT8 *endoom = NULL;
-
-	//added:16-02-98: when recording a demo, should exit using 'q' key,
-	//        but sometimes we forget and use 'F10'.. so save here too.
-	M_SaveConfig (NULL);   //save game config, cvars..
-#ifndef NONET
-	D_SaveBan(); // save the ban list
-#endif
-	G_SaveGameData(); // Tails 12-08-2002
-	if (demorecording)
-		G_CheckDemoStatus();
-	D_QuitNetGame ();
-	M_FreePlayerSetupColors();
-	I_ShutdownMusic();
-	I_ShutdownSound();
-	I_ShutdownCD();
-	I_ShutdownGraphics();
-	I_ShutdownSystem();
-
-	if (W_CheckNumForName("ENDOOM")!=LUMPERROR) endoom = W_CacheLumpName("ENDOOM",PU_CACHE);
-
-
-	//added:03-01-98: maybe it needs that the ticcount continues,
-	// or something else that will be finished by ShutdownSystem()
-	// so I do it before.
-
-	if (endoom)
-	{
-		puttext(1,1,80,25,endoom);
-		gotoxy(1,24);
-		Z_Free(endoom);
-	}
-
-	if (shutdowning || errorcount)
-		I_Error("Error detected (%d)",errorcount);
-
-	fflush(stderr);
-	W_Shutdown();
-	exit(0);
-}
-
-
-//added:12-02-98: does want to work!!!! rhaaahahha
-void I_WaitVBL(INT32 count)
-{
-	while (count-->0);
-	{
-		do { } while (inportb(0x3DA) & 8);
-		do { } while (!(inportb(0x3DA) & 8));
-	}
-}
-
-//  Fab: this is probably to activate the 'loading' disc icon
-//       it should set a flag, that I_FinishUpdate uses to know
-//       whether it draws a small 'loading' disc icon on the screen or not
-//
-//  also it should explicitly draw the disc because the screen is
-//  possibly not refreshed while loading
-//
-void I_BeginRead (void)
-{
-}
-
-//  Fab: see above, end the 'loading' disc icon, set the flag false
-//
-void I_EndRead (void)
-{
-}
-
-#define MOUSE2
-/* Secondary Mouse*/
-#ifdef MOUSE2
-static _go32_dpmi_seginfo oldmouseinfo,newmouseinfo;
-static boolean mouse2_started=0;
-static UINT16  mouse2port;
-static UINT8   mouse2irq;
-static volatile int     handlermouse2buttons;
-static volatile int     handlermouse2x,handlermouse2y;
-// internal use
-static volatile int     bytenum;
-static volatile UINT8   combytes[8];
-
-//
-// support a secondary mouse without mouse driver !
-//
-// take from the PC-GPE by Mark Feldman
-static void I_MicrosoftMouseIntHandler(void)
-{
-	char   dx,dy;
-	UINT8  inbyte;
-
-	// Get the port byte
-	inbyte = inportb(mouse2port);
-
-	// Make sure we are properly "synched"
-	if ((inbyte & 64)== 64 || bytenum>7)
-		bytenum = 0;
-
-	// Store the byte and adjust bytenum
-	combytes[bytenum] = inbyte;
-	bytenum++;
-
-	// Have we received all 3 bytes?
-	if (bytenum==3)
-	{
-		// Yes, so process them
-		dx = ((combytes[0] & 3) << 6) + combytes[1];
-		dy = ((combytes[0] & 12) << 4) + combytes[2];
-		handlermouse2x+= dx;
-		handlermouse2y+= dy;
-		handlermouse2buttons = (combytes[0] & (32+16)) >>4;
-	}
-	else if (bytenum==4) // for logitech 3 buttons
-	{
-		if (combytes[3] & 32)
-			handlermouse2buttons |= 4;
-		else
-			handlermouse2buttons &= ~4;
-	}
-
-	// Acknowledge the interrupt
-	outportb(0x20,0x20);
-}
-#ifndef DOXYGEN
-static END_OF_FUNCTION(I_MicrosoftMouseIntHandler);
-#endif
-
-// wait ms milliseconde
-FUNCINLINE static ATTRINLINE void I_Delay(int ms)
-{
-	tic_t  starttime;
-
-	if (timer_started)
-	{
-		starttime=I_GetTime()+(NEWTICRATE*ms)/1000;
-		while (starttime>=I_GetTime())
-			I_Sleep();
-	}
-	else
-		delay(ms);
-}
-
-//
-//  Removes the mouse2 handler.
-//
-static void I_ShutdownMouse2(void)
-{
-	event_t event;
-	int i;
-
-	if ( !mouse2_started )
-		return;
-
-	outportb(mouse2port+4,0x00);   // shutdown mouse (DTR & RTS = 0)
-	I_Delay(1);
-	outportb(mouse2port+1,0x00);   // disable COM interuption
-
-	asm("cli");
-	_go32_dpmi_set_protected_mode_interrupt_vector(mouse2irq, &oldmouseinfo);
-	_go32_dpmi_free_iret_wrapper(&newmouseinfo);
-	asm("sti");
-
-	handlermouse2x=handlermouse2y=handlermouse2buttons=0;
-	// emulate the up of all mouse buttons
-	for (i=0;i<MOUSEBUTTONS;i++)
-	{
-		event.type=ev_keyup;
-		event.data1=KEY_2MOUSE1+i;
-		D_PostEvent(&event);
-	}
-
-	mouse2_started=false;
-}
-
-static UINT8  ComIrq[4]={0x0c,0x0b,0x0c,0x0b};
-static UINT16 ComPort[4]={0x3F8,0x2F8,0x3E8,0x2E8};
-//
-//  Installs the mouse2 handler.
-//
-void I_StartupMouse2(void)
-{
-	tic_t i;
-	boolean  found;
-	__dpmi_regs r;
-
-	if ( mouse2_started )
-		I_ShutdownMouse2();
-
-	if (!cv_usemouse2.value)
-		return;
-
-	handlermouse2x=handlermouse2y=handlermouse2buttons=0;
-
-	mouse2irq =ComIrq[cv_mouse2port.value-1];
-	mouse2port=ComPort[cv_mouse2port.value-1];
-	CONS_Printf("Using %s (irq %d, port 0x%x)\n",cv_mouse2port.string,mouse2irq-8,mouse2port);
-	r.x.ax=0x24;
-	__dpmi_int(0x33,&r);
-	if (r.h.cl+8==mouse2irq)
-	{
-		CONS_Printf("Irq conflict with mouse 1\n"
-					"Use mouse2port to change the port\n");
-		return;
-	}
-
-	// install irq wrapper
-	asm("cli");
-	_go32_dpmi_get_protected_mode_interrupt_vector(mouse2irq, &oldmouseinfo);
-	newmouseinfo.pm_selector=_go32_my_cs();
-	newmouseinfo.pm_offset=(int)I_MicrosoftMouseIntHandler;
-	_go32_dpmi_allocate_iret_wrapper(&newmouseinfo);
-	_go32_dpmi_set_protected_mode_interrupt_vector(mouse2irq, &newmouseinfo);
-
-	LOCK_VARIABLE(bytenum);
-	LOCK_VARIABLE(handlermouse2x);
-	LOCK_VARIABLE(handlermouse2y);
-	LOCK_VARIABLE(handlermouse2buttons);
-	LOCK_VARIABLE(mouse2port);
-	_go32_dpmi_lock_data(&combytes,sizeof (combytes));
-	LOCK_FUNCTION(I_MicrosoftMouseIntHandler);
-	asm("sti");
-
-	outportb(mouse2port+4,0   );   // shutdown mouse (DTR & RTS = 0)
-	I_Delay(1);
-	outportb(mouse2port+1,0   );   // disable COM interuption
-	I_Delay(1);
-	outportb(mouse2port+3,0x80);   // change status of port +0 et +1
-	I_Delay(1);                    // for baudrate programmation
-	outportb(mouse2port  ,0x60);   // 1200 LSB
-	I_Delay(1);
-	outportb(mouse2port+1,0   );   // 1200 MSB
-	I_Delay(1);
-	outportb(mouse2port+3,0x02);   // set port protocol 7N1
-	I_Delay(1);
-	outportb(mouse2port+1,0x01);   // enable COM interuption
-	I_Delay(1);
-	outportb(0x21,0x0);
-
-	// wait to be sure the mouse have shutdown
-	I_Delay(100);
-
-	outportb(mouse2port+4,0x0b);   // restart mouse
-	i=I_GetTime()+NEWTICRATE;
-	found=cv_usemouse2.value==2;
-	while (I_GetTime()<i || !found)
-		if (combytes[0]!='M')
-			found=true;
-
-	if (found || cv_usemouse2.value==2)
-	{
-		CONS_Printf("Microsoft compatible Secondary Mouse detected\n");
-
-		//register shutdown mouse2 code.
-		I_AddExitFunc(I_ShutdownMouse2);
-		mouse2_started = true;
-	}
-	else
-	{
-		CONS_Printf("Secondary Mouse not found\n");
-		// remove irq wraper
-		I_ShutdownMouse2();
-	}
-}
-#endif
-
-//  Initialise the mouse. Doesnt need to be shutdown.
-//
-void I_StartupMouse (void)
-{
-	__dpmi_regs r;
-
-	// mouse detection may be skipped by setting usemouse false
-	if (cv_usemouse.value == 0)
-	{
-		mouse_detected=false;
-		I_ShutdownMouse2();
-		return;
-	}
-
-	//detect mouse presence
-	r.x.ax=0;
-	__dpmi_int(0x33,&r);
-
-	//added:03-01-98:
-	if ( r.x.ax == 0 && cv_usemouse.value != 2)
-	{
-		mouse_detected=false;
-		CONS_Printf("\2I_StartupMouse: mouse not present.\n");
-	}
-	else
-	{
-		mouse_detected=true;
-
-		// Check for CTMOUSE wheel
-		r.x.ax = 0x11;
-		__dpmi_int(0x33,&r);
-		if ( r.x.ax == 0x574D && r.x.cx & 0x1) // check for "MW" in AX and wheel mouse in CX
-		{
-			wheel_detected=true;
-		}
-
-		//hide cursor
-		r.x.ax=0x02;
-		__dpmi_int(0x33,&r);
-
-		//reset mickey count
-		r.x.ax=0x0b;
-		__dpmi_int(0x33,&r);
-	}
-}
-
-void I_GetJoystickEvents(void)
-{
-	event_t event;
-	INT64 joybuttons = 0;
-	INT64 joyhats = 0;
-	int s = 0, i;
-	int js = cv_usejoystick.value - 1;
-
-	if (!joystick_detected)
-		return;
-
-	// I assume that true is 1
-	for (i = JOYBUTTONS_MIN - 1; i >= 0; i--)
-	{
-		joybuttons <<= 1;
-		if (joy[js].button[i].b)
-			joybuttons |= 1;
-	}
-
-	for (i = JOYHATS_MIN -1; i >=0;)
-	{
-		if (joy[js].stick[s].flags &  JOYFLAG_DIGITAL)
-		{
-			if (joy[js].stick[s].axis[1].d1) joyhats |= 1<<(0 + 4*i);
-			if (joy[js].stick[s].axis[1].d2) joyhats |= 1<<(1 + 4*i);
-			if (joy[js].stick[s].axis[0].d1) joyhats |= 1<<(2 + 4*i);
-			if (joy[js].stick[s].axis[0].d2) joyhats |= 1<<(3 + 4*i);
-			i--;
-		}
-		if (s == JOYHATS_MAX) i = -1;
-		s++;
-	}
-
-	// post key event for buttons
-	if (joybuttons!=lastjoybuttons)
-	{
-		INT64 j = 1; // only changed bit to 1
-		INT64 k = (joybuttons ^ lastjoybuttons);
-		lastjoybuttons=joybuttons;
-
-		for (i=0;i<JOYBUTTONS && i<JOYBUTTONS_MAX;i++,j<<=1)
-			if (k & j)          // test the eatch bit and post the corresponding event
-			{
-				if (joybuttons & j)
-					event.type=ev_keydown;
-				else
-					event.type=ev_keyup;
-				event.data1=KEY_JOY1+i;
-				D_PostEvent(&event);
-			}
-	}
-
-	// post key event for hats
-	if (joyhats!=lastjoyhats)
-	{
-		INT64 j = 1; // only changed bit to 1
-		INT64 k = (joyhats ^ lastjoyhats);
-		lastjoyhats=joyhats;
-
-		for (i=0;i<JOYHATS && i<JOYHATS_MAX;i++,j<<=1)
-			if (k & j)          // test the eatch bit and post the corresponding event
-			{
-				if (joyhats & j)
-					event.type=ev_keydown;
-				else
-					event.type=ev_keyup;
-				event.data1=KEY_HAT1+i;
-				D_PostEvent(&event);
-			}
-	}
-
-	event.type=ev_joystick;
-	s = 0;
-
-	for (i = JOYAXISSET_MIN -1; i >=0;)
-	{
-		event.data1 = i;
-		event.data2 = event.data3 = 0;
-		if (joy[js].stick[s].flags &  JOYFLAG_DIGITAL)
-		{
-			if (joy[js].stick[s].axis[0].d1)
-				event.data2=-1;
-			if (joy[js].stick[s].axis[0].d2)
-				event.data2=1;
-			if (joy[js].stick[s].axis[1].d1)
-				event.data3=-1;
-			if (joy[js].stick[s].axis[1].d2)
-				event.data3=1;
-			D_PostEvent(&event);
-			i++;
-		}
-		else if (joy[js].stick[s].flags &  JOYFLAG_ANALOGUE)
-		{
-			event.data2 = joy[js].stick[s].axis[0].pos*32;
-			event.data3 = joy[js].stick[s].axis[1].pos*32;
-			D_PostEvent(&event);
-			i++;
-		}
-		if (s == JOYAXISSET_MAX*2) i = -1;
-		s++;
-	}
-}
-
-void I_GetJoystick2Events(void)
-{
-	event_t event;
-	INT64 joybuttons= 0;
-	INT64 joyhats = 0;
-	int s = 0, i;
-	int js = cv_usejoystick2.value - 1;
-
-	if (!joystick2_detected)
-		return;
-
-	// I assume that true is 1
-	for (i = JOYBUTTONS_MIN - 1; i >= 0; i--)
-	{
-		joybuttons <<= 1;
-		if (joy[js].button[i].b)
-			joybuttons |= 1;
-	}
-
-	for (i = JOYHATS_MIN -1; i >=0;)
-	{
-		if (joy[js].stick[s].flags &  JOYFLAG_DIGITAL)
-		{
-			if (joy[js].stick[s].axis[1].d1) joyhats |= 1<<(0 + 4*i);
-			if (joy[js].stick[s].axis[1].d2) joyhats |= 1<<(1 + 4*i);
-			if (joy[js].stick[s].axis[0].d1) joyhats |= 1<<(2 + 4*i);
-			if (joy[js].stick[s].axis[0].d2) joyhats |= 1<<(3 + 4*i);
-			i--;
-		}
-		if (s == JOYHATS_MAX) i = -1;
-		s++;
-	}
-
-	// post key event for buttons
-	if (joybuttons!=lastjoy2buttons)
-	{
-		INT64 j = 1; // only changed bit to 1
-		INT64 k = (joybuttons ^ lastjoy2buttons);
-		lastjoy2buttons=joybuttons;
-
-		for (i=0;i<JOYBUTTONS && i<JOYBUTTONS_MAX;i++,j<<=1)
-			if (k & j)          // test the eatch bit and post the corresponding event
-			{
-				if (joybuttons & j)
-					event.type=ev_keydown;
-				else
-					event.type=ev_keyup;
-				event.data1=KEY_2JOY1+i;
-				D_PostEvent(&event);
-			}
-	}
-
-	// post key event for hats
-	if (joyhats!=lastjoy2hats)
-	{
-		INT64 j=1; // only changed bit to 1
-		INT64 k = (joyhats ^ lastjoy2hats);
-		lastjoy2hats=joyhats;
-
-		for (i=0;i<JOYHATS && i<JOYHATS_MAX;i++,j<<=1)
-			if (k & j)          // test the eatch bit and post the corresponding event
-			{
-				if (joyhats & j)
-					event.type=ev_keydown;
-				else
-					event.type=ev_keyup;
-				event.data1=KEY_2HAT1+i;
-				D_PostEvent(&event);
-			}
-	}
-
-	event.type=ev_joystick2;
-	s = 0;
-
-	for (i = JOYAXISSET_MIN - 1; i >=0;)
-	{
-		event.data1 = i;
-		event.data2 = event.data3 = 0;
-		if (joy[js].stick[s].flags &  JOYFLAG_DIGITAL)
-		{
-			if (joy[js].stick[s].axis[0].d1)
-				event.data2=-1;
-			if (joy[js].stick[s].axis[0].d2)
-				event.data2=1;
-			if (joy[js].stick[s].axis[1].d1)
-				event.data3=-1;
-			if (joy[js].stick[s].axis[1].d2)
-				event.data3=1;
-			D_PostEvent(&event);
-			i++;
-		}
-		else if (joy[js].stick[s].flags &  JOYFLAG_ANALOGUE)
-		{
-			event.data2 = joy[js].stick[s].axis[0].pos*32;
-			event.data3 = joy[js].stick[s].axis[1].pos*32;
-			D_PostEvent(&event);
-			i++;
-		}
-		if (s == JOYAXISSET_MAX*2) i = -1;
-		s++;
-	}
-}
-
-void I_GetMouseEvents(void)
-{
-	//mouse movement
-	event_t event;
-	int xmickeys,ymickeys,buttons,wheels = 0;
-	static int lastbuttons=0;
-	__dpmi_regs r;
-
-	if (!mouse_detected)
-		return;
-
-	r.x.ax=0x0b;           // ask the mouvement not the position
-	__dpmi_int(0x33,&r);
-	xmickeys=(INT16)r.x.cx;
-	ymickeys=(INT16)r.x.dx;
-	r.x.ax=0x03;
-	__dpmi_int(0x33,&r);
-	buttons=r.h.bl;
-	if (wheel_detected)
-		wheels=(signed char)r.h.bh;
-
-	// post key event for buttons
-	if (buttons!=lastbuttons)
-	{
-		int j=1,k,i;
-		k=(buttons ^ lastbuttons); // only changed bit to 1
-		lastbuttons=buttons;
-
-		for (i=0;i<MOUSEBUTTONS;i++,j<<=1)
-			if (k & j)
-			{
-				if (buttons & j)
-					event.type=ev_keydown;
-				else
-					event.type=ev_keyup;
-				event.data1=KEY_MOUSE1+i;
-				D_PostEvent(&event);
-			}
-	}
-
-	event.type=ev_keyup;
-	event.data1 = 0;
-	if (wheels > 0)
-		event.data1 = KEY_MOUSEWHEELUP;
-	else if (wheels < 0)
-		event.data1 = KEY_MOUSEWHEELDOWN;
-	if (event.data1)
-			D_PostEvent(&event);
-
-
-	if ((xmickeys!=0)||(ymickeys!=0))
-	{
-		event.type=ev_mouse;
-		event.data1=0;
-		//event.data1=buttons;    // not needed
-		event.data2=xmickeys;
-		event.data3=-ymickeys;
-
-		D_PostEvent(&event);
-	}
-
-	//reset wheel like in win32, I don't understand it but works
-	gamekeydown[KEY_MOUSEWHEELDOWN] = gamekeydown[KEY_MOUSEWHEELUP] = 0;
-
-}
-
-void I_GetEvent (void)
-{
-#ifdef MOUSE2
-	// mouse may be disabled during the game by setting usemouse false
-	if (mouse2_started)
-	{
-		event_t event;
-		//mouse movement
-		static UINT8 lastbuttons2=0;
-
-		// post key event for buttons
-		if (handlermouse2buttons!=lastbuttons2)
-		{
-			int j=1,k,i;
-			k=(handlermouse2buttons ^ lastbuttons2); // only changed bit to 1
-			lastbuttons2=handlermouse2buttons;
-
-			for (i=0;i<MOUSEBUTTONS;i++,j<<=1)
-				if (k & j)
-				{
-					if (handlermouse2buttons & j)
-						event.type=ev_keydown;
-					else
-						event.type=ev_keyup;
-					event.data1=KEY_2MOUSE1+i;
-					D_PostEvent(&event);
-				}
-		}
-
-		if ((handlermouse2x!=0)||(handlermouse2y!=0))
-		{
-			event.type=ev_mouse2;
-			event.data1=0;
-			//event.data1=buttons;    // not needed
-			event.data2=handlermouse2x<<1;
-			event.data3=-handlermouse2y<<1;
-
-			D_PostEvent(&event);
-			handlermouse2x=0;
-			handlermouse2y=0;
-		}
-
-	}
-#endif
-
-	//mouse
-	I_GetMouseEvents();
-
-	//joystick
-	if (joystick_detected || joystick2_detected)
-		poll_joystick();
-
-	//joystick1
-	I_GetJoystickEvents();
-
-	//joystick2
-	I_GetJoystick2Events();
-}
-
-INT32 I_NumJoys(void)
-{
-	return MAX_JOYSTICKS;
-}
-
-const char *I_GetJoyName(INT32 joyindex)
-{
-#if MAX_JOYSTICKS > 8
-"More Joystick Names?"
-#endif
-
-	     if (joyindex == 1) return "Joystick A";
-	else if (joyindex == 2) return "Joystick B";
-	else if (joyindex == 3) return "Joystick C";
-	else if (joyindex == 4) return "Joystick D";
-	else if (joyindex == 5) return "Joystick E";
-	else if (joyindex == 6) return "Joystick F";
-	else if (joyindex == 7) return "Joystick G";
-	else if (joyindex == 8) return "Joystick H";
-	else return NULL;
-
-}
-
-//
-//  Timer user routine called at ticrate.
-//
-static void I_TimerISR (void)
-{
-	//  IO_PlayerInput();      // old doom did that
-	ticcount++;
-
-}
-#ifndef DOXYGEN
-static END_OF_FUNCTION(I_TimerISR);
-#endif
-
-
-//added:08-01-98: we don't use allegro_exit() so we have to do it ourselves.
-static inline void I_ShutdownTimer (void)
-{
-	if ( !timer_started )
-		return;
-	remove_timer();
-}
-
-
-//
-//  Installs the timer interrupt handler with timer speed as NEWTICRATE.
-//
-void I_StartupTimer(void)
-{
-	ticcount = 0;
-
-	//lock this from being swapped to disk! BEFORE INSTALLING
-	LOCK_VARIABLE(ticcount);
-	LOCK_FUNCTION(I_TimerISR);
-
-	if ( install_timer() != 0 )
-		I_Error("I_StartupTimer: could not install timer.");
-
-	if ( install_int_ex( I_TimerISR, BPS_TO_TIMER(NEWTICRATE) ) != 0 )
-		//should never happen since we use only one.
-		I_Error("I_StartupTimer: no room for callback routine.");
-
-	//added:08-01-98: remove the timer explicitly because we don't use
-	//                Allegro 's allegro_exit() shutdown code.
-	I_AddExitFunc(I_ShutdownTimer);
-	timer_started = true;
-}
-
-
-//added:07-02-98:
-//
-//
-static UINT8 ASCIINames[128] =
-{
-//        0           1              2            3
-//        4           5              6            7
-//        8           9              A            B
-//        C           D              E            F
-          0,         27,           '1',         '2',
-        '3',        '4',           '5',         '6',
-        '7',        '8',           '9',         '0',
-  KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE,     KEY_TAB,
-        'q',        'w',           'e',        'r',
-        't',        'y',           'u',        'i',
-        'o',        'p',           '[',        ']',
-  KEY_ENTER,  KEY_LCTRL,           'a',        's',
-        'd',        'f',           'g',        'h',
-        'j',        'k',           'l',        ';',
-       '\'',        '`',    KEY_LSHIFT,       '\\',
-        'z',        'x',           'c',        'v',
-        'b',        'n',           'm',        ',',
-        '.',        '/',    KEY_RSHIFT,        '*',
-   KEY_LALT,  KEY_SPACE,  KEY_CAPSLOCK,     KEY_F1,
-     KEY_F2,     KEY_F3,        KEY_F4,     KEY_F5,
-     KEY_F6,     KEY_F7,        KEY_F8,     KEY_F9,
-    KEY_F10,KEY_NUMLOCK,KEY_SCROLLLOCK,KEY_KEYPAD7,
-KEY_KEYPAD8,KEY_KEYPAD9,  KEY_MINUSPAD,KEY_KEYPAD4,
-KEY_KEYPAD5,KEY_KEYPAD6,   KEY_PLUSPAD,KEY_KEYPAD1,
-KEY_KEYPAD2,KEY_KEYPAD3,   KEY_KEYPAD0,KEY_KPADDEL,
-          0,          0,             0,    KEY_F11,
-    KEY_F12,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0,
-          0,          0,             0,          0
-};
-
-static volatile int pausepressed=0;
-static volatile char nextkeyextended;
-
-static void I_KeyboardHandler(void)
-{
-	UINT8 ch;
-	event_t       event;
-
-	ch=inportb(0x60);
-
-	if (pausepressed>0)
-		pausepressed--;
-	else if (ch==0xE1) // pause key
-	{
-		event.type=ev_keydown;
-		event.data1=KEY_PAUSE;
-		D_PostEvent(&event);
-		pausepressed=5;
-	}
-	else if (ch==0xE0) // extended key handled at next call
-	{
-		nextkeyextended=1;
-	}
-	else
-	{
-		if ((ch&0x80)==0)
-			event.type=ev_keydown;
-		else
-			event.type=ev_keyup;
-
-		ch&=0x7f;
-
-		if (nextkeyextended)
-		{
-			nextkeyextended=0;
-
-			if (ch==70)  // crtl-break
-			{
-				asm ("movb $0x79, %%al\ncall ___djgpp_hw_exception"
-				     : : :"%eax","%ebx","%ecx","%edx","%esi","%edi","memory");
-			}
-
-			// remap lonely keypad slash
-			if (ch==53)
-				event.data1 = KEY_KPADSLASH;
-			else if (ch>=91 && ch<=93) // remap the bill gates keys...
-				event.data1 = ch + 0x80;    // leftwin, rightwin, menu
-			else if (ch>=71 && ch<=83) // remap non-keypad extended keys to a value<128, but
-				event.data1 = 0x80 + ch + 30; // make them different than the KEYPAD keys.
-			else if (ch==28)
-				event.data1 = KEY_ENTER;    // keypad enter -> return key
-			else if (ch==29)
-				event.data1 = KEY_RCTRL;     // rctrl -> lctrl
-			else if (ch==56)
-				event.data1 = KEY_RALT;      // ralt -> lalt
-			else
-				ch = 0;
-			if (ch)
-				D_PostEvent(&event);
-		}
-		else
-		{
-			if (ASCIINames[ch]!=0)
-				event.data1=ASCIINames[ch];
-			else
-				event.data1=ch+0x80;
-			D_PostEvent(&event);
-		}
-	}
-
-	outportb(0x20,0x20);
-}
-#ifndef DOXYGEN
-static END_OF_FUNCTION(I_KeyboardHandler);
-#endif
-
-//  Return a key that has been pushed, or 0
-//  (replace getchar() at game startup)
-//
-INT32 I_GetKey (void)
-{
-	int rc=0;
-	if ( keyboard_started )
-	{
-		event_t   *ev;
-
-		// return the first keypress from the event queue
-		for ( ; eventtail != eventhead ; eventtail = (eventtail+1)&(MAXEVENTS-1) )
-		{
-			ev = &events[eventtail];
-			if (ev->type == ev_keydown || ev->type == ev_console)
-			{
-				rc = ev->data1;
-				continue;
-			}
-		}
-		return rc;
-	}
-
-	// keyboard not started use the bios call trouth djgpp
-	if (_conio_kbhit())
-	{
-		rc=getch();
-		if (rc==0) rc=getch()+256;
-	}
-	else
-		rc = 0;
-
-	return rc;
-}
-
-/* Keyboard handler stuff */
-static _go32_dpmi_seginfo oldkeyinfo,newkeyinfo;
-
-//
-//  Removes the keyboard handler.
-//
-static inline void I_ShutdownKeyboard(void)
-{
-	if ( !keyboard_started )
-		return;
-
-	asm("cli");
-	_go32_dpmi_set_protected_mode_interrupt_vector(9, &oldkeyinfo);
-	_go32_dpmi_free_iret_wrapper(&newkeyinfo);
-	asm("sti");
-
-	keyboard_started=false;
-}
-
-//
-//  Installs the keyboard handler.
-//
-void I_StartupKeyboard(void)
-{
-	if (keyboard_started)
-		return;
-
-	nextkeyextended=0;
-
-	asm("cli");
-	_go32_dpmi_get_protected_mode_interrupt_vector(9, &oldkeyinfo);
-	newkeyinfo.pm_offset=(int)I_KeyboardHandler;
-	newkeyinfo.pm_selector=_go32_my_cs();
-	_go32_dpmi_allocate_iret_wrapper(&newkeyinfo);
-	_go32_dpmi_set_protected_mode_interrupt_vector(9, &newkeyinfo);
-
-	LOCK_VARIABLE(nextkeyextended);
-	LOCK_VARIABLE(pausepressed);
-	_go32_dpmi_lock_data(ASCIINames,sizeof (ASCIINames));
-	LOCK_FUNCTION(I_KeyboardHandler);
-
-	_go32_dpmi_lock_data(events,sizeof (events));
-	LOCK_VARIABLE(eventhead);
-	LOCK_FUNCTION(D_PostEvent);
-
-	asm("sti");
-
-	//added:08-01-98:register shutdown keyboard code.
-	I_AddExitFunc(I_ShutdownKeyboard);
-	keyboard_started = true;
-}
-
-
-
-
-//added:08-01-98:
-//
-//  Clean Startup & Shutdown handling, as does Allegro.
-//  We need this services for ourselves too, and we don't want to mix
-//  with Allegro, because someone might not use Allegro.
-//  (all 'exit' was renamed to 'quit')
-//
-#define MAX_QUIT_FUNCS     16
-static quitfuncptr quit_funcs[MAX_QUIT_FUNCS] =
-			{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-			  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
-
-
-//added:08-01-98:
-//
-//  Adds a function to the list that need to be called by I_SystemShutdown().
-//
-void I_AddExitFunc(void (*func)())
-{
-	int c;
-
-	for (c=0; c<MAX_QUIT_FUNCS; c++)
-	{
-		if (!quit_funcs[c])
-		{
-			quit_funcs[c] = func;
-			break;
-		}
-	}
-}
-
-
-//added:08-01-98:
-//
-//  Removes a function from the list that need to be called by
-//   I_SystemShutdown().
-//
-void I_RemoveExitFunc(void (*func)())
-{
-	int c;
-
-	for (c=0; c<MAX_QUIT_FUNCS; c++)
-	{
-		if (quit_funcs[c] == func)
-		{
-			while (c<MAX_QUIT_FUNCS-1)
-			{
-				quit_funcs[c] = quit_funcs[c+1];
-				c++;
-			}
-			quit_funcs[MAX_QUIT_FUNCS-1] = NULL;
-			break;
-		}
-	}
-}
-
-
-
- //added:03-01-98:
-//
-// signal_handler:
-//  Used to trap various signals, to make sure things get shut down cleanly.
-//
-static inline void exception_handler(int num)
-{
-	static char msg[255];
-	sprintf(msg,"Sonic Robo Blast 2 "VERSIONSTRING"\r\n"
-	        "This is a error of SRB2, try to send the following info to programmers\r\n");
-
-	//D_QuitNetGame ();  //say 'byebye' to other players when your machine
-						// crashes?... hmm... do they have to die with you???
-
-	I_ShutdownSystem();
-
-	_write(STDERR_FILENO, msg, strlen(msg));
-
-	signal(num, SIG_DFL);
-	raise(num);
-	/// \todo write it in a log !!
-}
-
-static inline void break_handler(int num)
-{
-static char msg[] = "Oh no! Back to reality!\r\n";
-
-	//D_QuitNetGame ();  //say 'byebye' to other players when your machine
-						// crashes?... hmm... do they have to die with you???
-
-	I_ShutdownSystem();
-
-	_write(STDERR_FILENO, msg, sizeof (msg)-1);
-
-	signal(num, SIG_DFL);
-	raise(num);
-}
-
-
-//added:08-01-98: now this replaces allegro_init()
-//
-// REMEMBER: THIS ROUTINE MUST BE STARTED IN i_main.c BEFORE D_SRB2Main()
-//
-// This stuff should get rid of the exception and page faults when
-// SRB2 bugs out with an error. Now it should exit cleanly.
-//
-INT32 I_StartupSystem(void)
-{
-	I_DetectOS();
-	check_cpu();
-
-	// some 'more globals than globals' things to initialize here ?
-	graphics_started = false;
-	keyboard_started = false;
-	sound_started = false;
-	timer_started = false;
-	cdaudio_started = false;
-
-	// check for OS type and version here ?
-
-
-	signal(SIGABRT, exception_handler);
-	signal(SIGFPE , exception_handler);
-	signal(SIGILL , exception_handler);
-	signal(SIGSEGV, exception_handler);
-	signal(SIGINT , break_handler);
-	signal(SIGKILL, break_handler);
-	signal(SIGQUIT, break_handler);
-
-	return 0;
-}
-
-
-//added:08-01-98:
-//
-//  Closes down everything. This includes restoring the initial
-//  pallete and video mode, and removing whatever mouse, keyboard, and
-//  timer routines have been installed.
-//
-//  NOTE : Shutdown user funcs. are effectively called in reverse order.
-//
-void I_ShutdownSystem(void)
-{
-	int c;
-
-	for (c=MAX_QUIT_FUNCS-1; c>=0; c--)
-		if (quit_funcs[c])
-			(*quit_funcs[c])();
-}
-
-void I_GetDiskFreeSpace(INT64 *freespace)
-{
-	struct diskfree_t df;
-	if (_dos_getdiskfree(0,&df))
-		*freespace = (unsigned long)df.avail_clusters *
-		             (unsigned long)df.bytes_per_sector *
-		             (unsigned long)df.sectors_per_cluster;
-	else
-		*freespace = INT32_MAX;
-}
-
-char *I_GetUserName(void)
-{
-	static char username[MAXPLAYERNAME];
-	char  *p;
-	if ((p=getenv("USER"))==NULL)
-		if ((p=getenv("user"))==NULL)
-			if ((p=getenv("USERNAME"))==NULL)
-				if ((p=getenv("username"))==NULL)
-					return NULL;
-	strncpy(username,p,MAXPLAYERNAME);
-
-	if (strcmp(username,"")==0 )
-		return NULL;
-	return username;
-}
-
-INT32 I_mkdir(const char *pdirname, INT32 unixright)
-{
-	return mkdir(pdirname, unixright);
-}
-
-char * I_GetEnv(const char *name)
-{
-	return getenv(name);
-}
-
-INT32 I_PutEnv(char *variable)
-{
-	return putenv(variable);
-}
-
-INT32 I_ClipboardCopy(const char *data, size_t size)
-{
-	(void)data;
-	(void)size;
-	return -1;
-}
-
-char *I_ClipboardPaste(void)
-{
-	return NULL;
-}
-
-const CPUInfoFlags *I_CPUInfo(void)
-{
-	static CPUInfoFlags DOS_CPUInfo;
-	memset(&DOS_CPUInfo,0,sizeof (DOS_CPUInfo));
-#if ALLEGRO_VERSION == 3
-	if (!cpu_cpuid) return NULL;
-	DOS_CPUInfo.CPUID       = true;
-	DOS_CPUInfo.MMX         = cpu_mmx;
-	DOS_CPUInfo.AMD3DNow    = cpu_3dnow;
-#else
-	DOS_CPUInfo.CPUID       = ((cpu_capabilities&CPU_ID)       ==       CPU_ID);
-	DOS_CPUInfo.FPU         = ((cpu_capabilities&CPU_FPU)      ==      CPU_FPU);
-#ifdef CPU_IA64
-	DOS_CPUInfo.IA64        = ((cpu_capabilities&CPU_IA64)     ==     CPU_IA64);
-#endif
-#ifdef CPU_AMD64
-	DOS_CPUInfo.AMD64       = ((cpu_capabilities&CPU_AMD64)    ==    CPU_AMD64);
-#endif
-	DOS_CPUInfo.MMX         = ((cpu_capabilities&CPU_MMX)      ==      CPU_MMX);
-	DOS_CPUInfo.MMXExt      = ((cpu_capabilities&CPU_MMXPLUS)  ==  CPU_MMXPLUS);
-	DOS_CPUInfo.SSE         = ((cpu_capabilities&CPU_SSE)      ==      CPU_SSE);
-	DOS_CPUInfo.SSE2        = ((cpu_capabilities&CPU_SSE2)     ==     CPU_SSE2);
-#ifdef CPU_SEE3
-	DOS_CPUInfo.SSE3        = ((cpu_capabilities&CPU_SSE3)     ==     CPU_SSE3);
-#endif
-	DOS_CPUInfo.AMD3DNow    = ((cpu_capabilities&CPU_3DNOW)    ==    CPU_3DNOW);
-	DOS_CPUInfo.AMD3DNowExt = ((cpu_capabilities&CPU_ENH3DNOW) == CPU_ENH3DNOW);
-	DOS_CPUInfo.CMOV        = ((cpu_capabilities&CPU_CMOV)     ==     CPU_CMOV);
-#endif
-	return &DOS_CPUInfo;
-}
-
-void I_RegisterSysCommands(void) {}
diff --git a/src/djgppdos/i_video.c b/src/djgppdos/i_video.c
deleted file mode 100644
index d2483e318654f692d68a5270979c8c11c65326a7..0000000000000000000000000000000000000000
--- a/src/djgppdos/i_video.c
+++ /dev/null
@@ -1,345 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Portions Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief hardware and software level, screen and video i/o, refresh,
-///	setup ... a big mess. Got to clean that up!
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdarg.h>
-#include <sys/time.h>
-#include <sys/types.h>
-//#include <sys/socket.h>
-
-#include <netinet/in.h>
-//#include <errnos.h>
-#include <signal.h>
-
-#include <go32.h>
-#include <pc.h>
-#include <dpmi.h>
-#include <dos.h>
-#include <sys/nearptr.h>
-
-#include "../doomdef.h"
-#include "../i_system.h"
-#include "../v_video.h"
-#include "../m_argv.h"
-#include "vid_vesa.h"
-#include "../i_video.h"
-
-
-//dosstuff -newly added
-static unsigned long dascreen;
-static int gfx_use_vesa1;
-
-boolean    highcolor;
-
-#define SCREENDEPTH   1     // bytes per pixel, do NOT change.
-
-rendermode_t    rendermode=render_soft;
-
-//
-// I_OsPolling
-//
-void I_OsPolling(void)
-{
-	I_GetEvent();
-	//i dont think i have to do anything else here
-}
-
-
-//
-// I_UpdateNoBlit
-//
-void I_UpdateNoBlit (void)
-{
-	// what is this?
-}
-
-
-//profile stuff ---------------------------------------------------------
-//added:16-01-98:wanted to profile the VID_BlitLinearScreen() asm code.
-//#define TIMING      //uncomment this to enable profiling
-#ifdef TIMING
-#include "../p5prof.h"
-static   INT64 mycount;
-static   INT64 mytotal = 0;
-static   unsigned long  nombre = NEWTICRATE*10;
-//static   char runtest[10][80];
-#endif
-//profile stuff ---------------------------------------------------------
-
-//
-// I_FinishUpdate
-//
-static void I_BlitScreenVesa1(void);   //see later
-void I_FinishUpdate (void)
-{
-	if (marathonmode)
-		SCR_DisplayMarathonInfo();
-
-	// draw captions if enabled
-	if (cv_closedcaptioning.value)
-		SCR_ClosedCaptions();
-
-	// draw FPS if enabled
-	if (cv_ticrate.value)
-		SCR_DisplayTicRate();
-
-	if (cv_showping.value && netgame && consoleplayer != serverplayer)
-		SCR_DisplayLocalPing();
-
-	//blast it to the screen
-	// this code sucks
-	//memcpy(dascreen,screens[0],screenwidth*screenheight);
-
-	//added:03-01-98: I tried to I_WaitVBL(1) here, but it slows down
-	//  the game when the view becomes complicated, it looses ticks
-	if (cv_vidwait.value)
-		I_WaitVBL(1);
-
-
-//added:16-01-98:profile screen blit.
-#ifdef TIMING
-	ProfZeroTimer();
-#endif
-	//added:08-01-98: support vesa1 bank change, without Allegro's BITMAP screen.
-	if ( gfx_use_vesa1 )
-	{
-		I_Error("Banked screen update not finished for dynamic res\n");
-		I_BlitScreenVesa1();    //blast virtual to physical screen.
-	}
-	else
-	{
-		//added:16-01-98:use quickie asm routine, last 2 args are
-		//                   src and dest rowbytes
-		//                   (memcpy is as fast as this one...)
-		VID_BlitLinearScreen(screens[0], vid.direct,
-		                     vid.width*vid.bpp, vid.height,
-		                     vid.width*vid.bpp, vid.rowbytes );
-	}
-#ifdef TIMING
-	RDMSR(0x10,&mycount);
-	mytotal += mycount;   //64bit add
-
-	if (nombre--==0)
-		I_Error("ScreenBlit CPU Spy reports: 0x%d %d\n", *((int *)&mytotal+1),
-		        (int)mytotal );
-#endif
-
-}
-
-//
-// I_UpdateNoVsync
-//
-void I_UpdateNoVsync(void)
-{
-	int real_vidwait = cv_vidwait.value;
-	cv_vidwait.value = 0;
-	I_FinishUpdate();
-	cv_vidwait.value = real_vidwait;
-}
-
-//
-// I_ReadScreen
-//
-void I_ReadScreen (UINT8 *scr)
-{
-	VID_BlitLinearScreen(screens[0], scr,
-	                     vid.width*vid.bpp, vid.height,
-	                     vid.width*vid.bpp, vid.rowbytes );
-}
-
-
-void I_SetPalette (RGBA_t *palette)
-{
-	int i;
-
-	outportb(0x3c8,0);
-	for (i=0;i<256;i++,palette++)
-	{
-		outportb(0x3c9,palette->s.red>>2);
-		outportb(0x3c9,palette->s.green>>2);
-		outportb(0x3c9,palette->s.blue>>2);
-	}
-}
-
-
-//added 29-12-1997
-/*==========================================================================*/
-// I_BlastScreen : copy the virtual screen buffer to the physical screen mem
-//                 using bank switching if needed.
-/*==========================================================================*/
-static void I_BlitScreenVesa1(void)
-{
-#define VIDBANKSIZE     (1<<16)
-#define VIDBANKSIZEMASK (VIDBANKSIZE-1)   // defines ahoy!
-
-	__dpmi_regs r;
-	UINT8 *p_src;
-	long     i;
-	long     virtualsize;
-
-	// virtual screen buffer size
-	virtualsize = vid.rowbytes * vid.height * SCREENDEPTH;
-
-	p_src  = screens[0];
-
-	for (i=0; virtualsize > 0; i++ )
-	{
-		r.x.ax = 0x4f05;
-		r.x.bx = 0x0;
-		r.x.cx = 0x0;
-		r.x.dx = i;
-		__dpmi_int(0x10,&r);      //set bank
-
-		M_Memcpy((UINT8 *)dascreen,p_src,(virtualsize < VIDBANKSIZE) ? virtualsize : VIDBANKSIZE );
-
-		p_src += VIDBANKSIZE;
-		virtualsize -= VIDBANKSIZE;
-	}
-
-}
-
-
-//added:08-01-98: now we use Allegro's set_gfx_mode, but we want to
-//                restore the exact text mode that was before.
-static INT16  myOldVideoMode;
-static inline void I_SaveOldVideoMode(void)
-{
-	__dpmi_regs r;
-	r.x.ax = 0x4f03;                 // Return current video mode
-	__dpmi_int(0x10,&r);
-	if ( r.x.ax != 0x4f )
-		myOldVideoMode = -1;
-	else
-		myOldVideoMode = r.x.bx;
-}
-
-
-//
-//  Close the screen, restore previous video mode.
-//
-void I_ShutdownGraphics (void)
-{
-	__dpmi_regs r;
-
-	rendermode = render_none;
-	if ( !graphics_started )
-		return;
-
-	// free the last video mode screen buffers
-	if (vid.buffer)
-		free (vid.buffer);
-
-	/* Restore old video mode */
-	if (myOldVideoMode!=-1)
-	{
-		/* Restore old video mode */
-		r.x.ax = 0x4f02;                 // Set Super VGA video mode
-		r.x.bx = myOldVideoMode;
-		__dpmi_int(0x10,&r);
-
-		// Boris: my s3 don't do a cls because "win95" :<
-		clrscr();
-	}
-	else  // no vesa put the normal video mode
-	{
-		r.x.ax = 0x03;
-		__dpmi_int(0x10,&r);
-	}
-
-	graphics_started = false;
-}
-
-
-//added:08-01-98:
-//  Set VESA1 video mode, coz Allegro set_gfx_mode a larger screenwidth...
-//
-#if 0
-int set_vesa1_mode( int width, int height )
-{
-	__dpmi_regs r;
-
-	// setup video mode.
-	r.x.ax = 0x4f02;
-	if ( ( width==320 )&&( height==200 ) && ( SCREENDEPTH==1 ) )
-		r.x.bx   = 0x13;                             // 320x 200x1 (256 colors)
-	else
-	if ( ( width==320 )&&( height==240 ) && ( SCREENDEPTH==1 ) )
-		r.x.bx   = 0x154;                            // 320x 240x1 (256 colors)
-	else
-	if ( ( width==320 )&&( height==400 ) && ( SCREENDEPTH==1 ) )
-		r.x.bx   = 0x155;                            // 320x 400x1 (256 colors)
-	else
-	if ( ( width==640 )&&( height==400 ) && ( SCREENDEPTH==1 ) )
-		r.x.bx   = 0x100;                            // 640x 400x1 (256 colors)
-	else
-	if ( ( width==640 )&&( height==480 ) && ( SCREENDEPTH==1 ) )
-		r.x.bx   = 0x101;                            // 640x 480x1 (256 colors)
-	else
-	if ( ( width==800 )&&( height==600 ) && ( SCREENDEPTH==1 ) )
-		r.x.bx   = 0x103;                            // 800x 600x1 (256 colors)
-	else
-	if ( ( width==1024)&&( height==768 ) && ( SCREENDEPTH==1 ) )
-		r.x.bx   = 0x105;                            //1024x 768x1 (256 colors)
-	else
-		I_Error("I_SetVesa1Mode: video mode not supported.");
-
-	// enter graphics mode.
-	__dpmi_int(0x10,&r);
-
-	if ( r.x.ax != 0x4f )
-		I_Error("I_SetVesa1Mode: init video mode failed !");
-
-	return 0;
-}
-#endif
-
-
-//added:08-01-98: now uses Allegro to setup Linear Frame Buffer video modes.
-//
-//  Initialize video mode, setup dynamic screen size variables,
-//  and allocate screens.
-//
-void I_StartupGraphics(void)
-{
-	//added:26-01-98: VID_Init() must be done only once,
-	//                use VID_SetMode() to change vid mode while in the game.
-	if ( graphics_started )
-		return;
-
-	// remember the exact screen mode we were...
-	I_SaveOldVideoMode();
-
-	CONS_Printf("Vid_Init...");
-
-	// 0 for 256 color, else use highcolor modes
-	highcolor = M_CheckParm ("-highcolor");
-
-	VID_Init();
-
-	//gfx_use_vesa1 = false;
-
-	//added:03-01-98: register exit code for graphics
-	I_AddExitFunc(I_ShutdownGraphics);
-	graphics_started = true;
-
-}
-
-void VID_StartupOpenGL(void) {}
diff --git a/src/djgppdos/internal.h b/src/djgppdos/internal.h
deleted file mode 100644
index 94c1a052b4f7adc5e3c2c567b233ced12da3919e..0000000000000000000000000000000000000000
--- a/src/djgppdos/internal.h
+++ /dev/null
@@ -1,773 +0,0 @@
-/*         ______   ___    ___ 
- *        /\  _  \ /\_ \  /\_ \ 
- *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
- *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
- *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
- *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
- *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
- *                                           /\____/
- *                                           \_/__/
- *
- *      Some definitions for internal use by the library code.
- *      This should not be included by user programs.
- *
- *      By Shawn Hargreaves.
- *
- *      See readme.txt for copyright information.
- */
-
-
-#ifndef INTERNAL_H
-#define INTERNAL_H
-
-#include "allegro.h"
-
-/*         ______   ___    ___ 
- *        /\  _  \ /\_ \  /\_ \ 
- *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
- *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
- *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
- *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
- *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
- *                                           /\____/
- *                                           \_/__/
- *
- *      Some definitions for internal use by the library code.
- *      This should not be included by user programs.
- *
- *      By Shawn Hargreaves.
- *
- *      See readme.txt for copyright information.
- */
-
-
-#ifndef INTERNDJ_H
-#define INTERNDJ_H
-
-#ifndef DJGPP
-#error This file should only be used by the djgpp version of Allegro
-#endif
-
-
-#include <dos.h>
-
-
-/* file access macros */
-#define FILE_OPEN(filename, handle)             handle = open(filename, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR)
-#define FILE_CREATE(filename, handle)           handle = open(filename, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)
-#define FILE_CLOSE(handle)                      close(handle)
-#define FILE_READ(handle, buf, size, sz)        sz = read(handle, buf, size)
-#define FILE_WRITE(handle, buf, size, sz)       sz = write(handle, buf, size) 
-#define FILE_SEARCH_STRUCT                      struct ffblk
-#define FILE_FINDFIRST(filename, attrib, dta)   findfirst(filename, dta, attrib)
-#define FILE_FINDNEXT(dta)                      findnext(dta)
-#define FILE_ATTRIB                             ff_attrib
-#define FILE_SIZE                               ff_fsize
-#define FILE_NAME                               ff_name
-#define FILE_TIME                               ff_ftime
-#define FILE_DATE                               ff_fdate
-
-
-/* macros to enable and disable interrupts */
-#define DISABLE()   asm volatile ("cli")
-#define ENABLE()    asm volatile ("sti")
-
-
-__INLINE__ void enter_critical(void) 
-{
-   if (windows_version >= 3) {
-      __dpmi_regs r;
-      r.x.ax = 0x1681; 
-      __dpmi_int(0x2F, &r);
-   }
-
-   DISABLE();
-}
-
-
-__INLINE__ void exit_critical(void) 
-{
-   if (windows_version >= 3) {
-      __dpmi_regs r;
-      r.x.ax = 0x1682; 
-      __dpmi_int(0x2F, &r);
-   }
-
-   ENABLE();
-}
-
-
-/* interrupt hander stuff */
-#define _map_irq(irq)   (((irq)>7) ? ((irq)+104) : ((irq)+8))
-
-int _install_irq(int num, int (*handler)(void));
-void _remove_irq(int num);
-void _restore_irq(int irq);
-void _enable_irq(int irq);
-void _disable_irq(int irq);
-
-#define _eoi(irq) { outportb(0x20, 0x20); if ((irq)>7) outportb(0xA0, 0x20); }
-
-typedef struct _IRQ_HANDLER
-{
-   int (*handler)(void);         /* our C handler */
-   int number;                   /* irq number */
-   __dpmi_paddr old_vector;      /* original protected mode vector */
-} _IRQ_HANDLER;
-
-
-/* DPMI memory mapping routines */
-int _create_physical_mapping(unsigned long *linear, int *segment, unsigned long physaddr, int size);
-void _remove_physical_mapping(unsigned long *linear, int *segment);
-int _create_linear_mapping(unsigned long *linear, unsigned long physaddr, int size);
-void _remove_linear_mapping(unsigned long *linear);
-int _create_selector(int *segment, unsigned long linear, int size);
-void _remove_selector(int *segment);
-void _unlock_dpmi_data(void *addr, int size);
-
-
-/* bank switching routines */
-void _accel_bank_stub(void);
-void _accel_bank_stub_end(void);
-
-void _accel_bank_switch (void);
-void _accel_bank_switch_end(void);
-
-void _vesa_window_1(void);
-void _vesa_window_1_end(void);
-void _vesa_window_2(void);
-void _vesa_window_2_end(void);
-
-void _vesa_pm_window_1(void);
-void _vesa_pm_window_1_end(void);
-void _vesa_pm_window_2(void);
-void _vesa_pm_window_2_end(void);
-
-void _vesa_pm_es_window_1(void);
-void _vesa_pm_es_window_1_end(void);
-void _vesa_pm_es_window_2(void);
-void _vesa_pm_es_window_2_end(void);
-
-
-/* stuff for the VESA and VBE/AF drivers */
-extern __dpmi_regs _dpmi_reg;
-
-extern int _window_2_offset;
-
-extern void (*_pm_vesa_switcher)(void);
-extern void (*_pm_vesa_scroller)(void);
-extern void (*_pm_vesa_pallete)(void);
-
-extern int _mmio_segment;
-
-extern void *_accel_driver;
-
-extern int _accel_active;
-
-extern void *_accel_set_bank;
-extern void *_accel_idle;
-
-void _fill_vbeaf_libc_exports(void *ptr);
-void _fill_vbeaf_pmode_exports(void *ptr);
-
-
-/* sound lib stuff */
-extern int _fm_port;
-extern int _mpu_port;
-extern int _mpu_irq;
-extern int _sb_freq;
-extern int _sb_port; 
-extern int _sb_dma; 
-extern int _sb_irq; 
-
-int _sb_read_dsp_version(void);
-int _sb_reset_dsp(int data);
-void _sb_voice(int state);
-int _sb_set_mixer(int digi_volume, int midi_volume);
-
-void _mpu_poll(void);
-
-int _dma_allocate_mem(int bytes, int *sel, unsigned long *phys);
-void _dma_start(int channel, unsigned long addr, int size, int auto_init, int input);
-void _dma_stop(int channel);
-unsigned long _dma_todo(int channel);
-void _dma_lock_mem(void);
-
-
-#endif          /* ifndef INTERNDJ_H */
-
-
-/* flag for how many times we have been initialised */
-extern int _allegro_count;
-
-
-/* some Allegro functions need a block of scratch memory */
-extern void *_scratch_mem;
-extern int _scratch_mem_size;
-
-__INLINE__ void _grow_scratch_mem(int size)
-{
-   if (size > _scratch_mem_size) {
-      size = (size+1023) & 0xFFFFFC00;
-      _scratch_mem = realloc(_scratch_mem, size);
-      _scratch_mem_size = size;
-   }
-}
-
-
-/* list of functions to call at program cleanup */
-void _add_exit_func(void (*func)(void));
-void _remove_exit_func(void (*func)(void));
-
-
-/* reads a translation file into memory */
-void _load_config_text(void);
-
-
-/* various bits of mouse stuff */
-void _set_mouse_range(void);
-extern BITMAP *_mouse_screen;
-extern BITMAP *_mouse_sprite, *_mouse_pointer;
-extern int _mouse_x_focus, _mouse_y_focus;
-extern int _mouse_width, _mouse_height;
-
-
-/* various bits of timer stuff */
-extern int _timer_use_retrace;
-extern volatile int _retrace_hpp_value;
-
-
-/* caches and tables for svga bank switching */
-extern int _last_bank_1, _last_bank_2; 
-extern int *_gfx_bank; 
-
-
-/* bank switching routines */
-void _stub_bank_switch (void);
-void _stub_bank_switch_end(void);
-
-
-/* stuff for setting up bitmaps */
-void _check_gfx_virginity(void);
-BITMAP *_make_bitmap(int w, int h, unsigned long addr, GFX_DRIVER *driver, int color_depth, int bpl);
-void _sort_out_virtual_width(int *width, GFX_DRIVER *driver);
-
-GFX_VTABLE *_get_vtable(int color_depth);
-
-extern GFX_VTABLE _screen_vtable;
-
-extern int _sub_bitmap_id_count;
-
-extern int _textmode;
-
-#define BYTES_PER_PIXEL(bpp)     (((int)(bpp) + 7) / 8)
-
-int _color_load_depth(int depth);
-
-extern int _color_conv;
-
-BITMAP *_fixup_loaded_bitmap(BITMAP *bmp, PALETTE pal, int bpp);
-
-
-/* VGA register access routines */
-void _vga_vsync(void);
-void _vga_set_pallete_range(PALLETE p, int from, int to, int vsync);
-
-extern int _crtc;
-
-
-/* _read_vga_register:
- *  Reads the contents of a VGA register.
- */
-__INLINE__ int _read_vga_register(int port, int index)
-{
-   if (port==0x3C0)
-      inportb(_crtc+6); 
-
-   outportb(port, index);
-   return inportb(port+1);
-}
-
-
-/* _write_vga_register:
- *  Writes a byte to a VGA register.
- */
-__INLINE__ void _write_vga_register(int port, int index, int v) 
-{
-   if (port==0x3C0) {
-      inportb(_crtc+6);
-      outportb(port, index);
-      outportb(port, v);
-   }
-   else {
-      outportb(port, index);
-      outportb(port+1, v);
-   }
-}
-
-
-/* _alter_vga_register:
- *  Alters specific bits of a VGA register.
- */
-__INLINE__ void _alter_vga_register(int port, int index, int mask, int v)
-{
-   int temp;
-   temp = _read_vga_register(port, index);
-   temp &= (~mask);
-   temp |= (v & mask);
-   _write_vga_register(port, index, temp);
-}
-
-
-/* _vsync_out_h:
- *  Waits until the VGA is not in either a vertical or horizontal retrace.
- */
-__INLINE__ void _vsync_out_h(void)
-{
-   do {
-   } while (inportb(0x3DA) & 1);
-}
-
-
-/* _vsync_out_v:
- *  Waits until the VGA is not in a vertical retrace.
- */
-__INLINE__ void _vsync_out_v(void)
-{
-   do {
-   } while (inportb(0x3DA) & 8);
-}
-
-
-/* _vsync_in:
- *  Waits until the VGA is in the vertical retrace period.
- */
-__INLINE__ void _vsync_in(void)
-{
-   if (_timer_use_retrace) {
-      int t = retrace_count; 
-
-      do {
-      } while (t == retrace_count);
-   }
-   else {
-      do {
-      } while (!(inportb(0x3DA) & 8));
-   }
-}
-
-
-/* _write_hpp:
- *  Writes to the VGA pelpan register.
- */
-__INLINE__ void _write_hpp(int value)
-{
-   if (_timer_use_retrace) {
-      _retrace_hpp_value = value;
-
-      do {
-      } while (_retrace_hpp_value == value);
-   }
-   else {
-      do {
-      } while (!(inportb(0x3DA) & 8));
-
-      _write_vga_register(0x3C0, 0x33, value);
-   }
-}
-
-
-void _set_vga_virtual_width(int old_width, int new_width);
-
-
-/* current drawing mode */
-extern int _drawing_mode;
-extern BITMAP *_drawing_pattern;
-extern int _drawing_x_anchor;
-extern int _drawing_y_anchor;
-extern unsigned int _drawing_x_mask;
-extern unsigned int _drawing_y_mask;
-
-
-/* graphics drawing routines */
-void _normal_line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color);
-void _normal_rectfill(BITMAP *bmp, int x1, int y1, int x2, int y2, int color);
-
-int  _linear_getpixel8(struct BITMAP *bmp, int x, int y);
-void _linear_putpixel8(struct BITMAP *bmp, int x, int y, int color);
-void _linear_vline8(struct BITMAP *bmp, int x, int y1, int y2, int color);
-void _linear_hline8(struct BITMAP *bmp, int x1, int y, int x2, int color);
-void _linear_draw_sprite8(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_v_flip8(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_h_flip8(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_vh_flip8(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_trans_sprite8(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_lit_sprite8(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_draw_rle_sprite8(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_trans_rle_sprite8(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_lit_rle_sprite8(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y, int color);
-void _linear_draw_character8(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_textout_fixed8(struct BITMAP *bmp, void *f, int h, unsigned char *str, int x, int y, int color);
-void _linear_blit8(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_blit_backward8(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_masked_blit8(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_clear_to_color8(struct BITMAP *bitmap, int color);
-
-#ifdef ALLEGRO_COLOR16
-
-void _linear_putpixel15(struct BITMAP *bmp, int x, int y, int color);
-void _linear_vline15(struct BITMAP *bmp, int x, int y1, int y2, int color);
-void _linear_hline15(struct BITMAP *bmp, int x1, int y, int x2, int color);
-void _linear_draw_trans_sprite15(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_lit_sprite15(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_draw_rle_sprite15(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_trans_rle_sprite15(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_lit_rle_sprite15(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y, int color);
-
-int  _linear_getpixel16(struct BITMAP *bmp, int x, int y);
-void _linear_putpixel16(struct BITMAP *bmp, int x, int y, int color);
-void _linear_vline16(struct BITMAP *bmp, int x, int y1, int y2, int color);
-void _linear_hline16(struct BITMAP *bmp, int x1, int y, int x2, int color);
-void _linear_draw_sprite16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_256_sprite16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_v_flip16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_h_flip16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_vh_flip16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_trans_sprite16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_lit_sprite16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_draw_rle_sprite16(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_trans_rle_sprite16(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_lit_rle_sprite16(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y, int color);
-void _linear_draw_character16(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_textout_fixed16(struct BITMAP *bmp, void *f, int h, unsigned char *str, int x, int y, int color);
-void _linear_blit16(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_blit_backward16(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_masked_blit16(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_clear_to_color16(struct BITMAP *bitmap, int color);
-
-#endif
-
-#ifdef ALLEGRO_COLOR24
-
-int  _linear_getpixel24(struct BITMAP *bmp, int x, int y);
-void _linear_putpixel24(struct BITMAP *bmp, int x, int y, int color);
-void _linear_vline24(struct BITMAP *bmp, int x, int y1, int y2, int color);
-void _linear_hline24(struct BITMAP *bmp, int x1, int y, int x2, int color);
-void _linear_draw_sprite24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_256_sprite24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_v_flip24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_h_flip24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_vh_flip24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_trans_sprite24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_lit_sprite24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_draw_rle_sprite24(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_trans_rle_sprite24(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_lit_rle_sprite24(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y, int color);
-void _linear_draw_character24(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_textout_fixed24(struct BITMAP *bmp, void *f, int h, unsigned char *str, int x, int y, int color);
-void _linear_blit24(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_blit_backward24(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_masked_blit24(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_clear_to_color24(struct BITMAP *bitmap, int color);
-
-#endif
-
-#ifdef ALLEGRO_COLOR32
-
-int  _linear_getpixel32(struct BITMAP *bmp, int x, int y);
-void _linear_putpixel32(struct BITMAP *bmp, int x, int y, int color);
-void _linear_vline32(struct BITMAP *bmp, int x, int y1, int y2, int color);
-void _linear_hline32(struct BITMAP *bmp, int x1, int y, int x2, int color);
-void _linear_draw_sprite32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_256_sprite32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_v_flip32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_h_flip32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_sprite_vh_flip32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_trans_sprite32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _linear_draw_lit_sprite32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_draw_rle_sprite32(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_trans_rle_sprite32(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _linear_draw_lit_rle_sprite32(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y, int color);
-void _linear_draw_character32(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _linear_textout_fixed32(struct BITMAP *bmp, void *f, int h, unsigned char *str, int x, int y, int color);
-void _linear_blit32(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_blit_backward32(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_masked_blit32(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _linear_clear_to_color32(struct BITMAP *bitmap, int color);
-
-#endif
-
-int  _x_getpixel(struct BITMAP *bmp, int x, int y);
-void _x_putpixel(struct BITMAP *bmp, int x, int y, int color);
-void _x_vline(struct BITMAP *bmp, int x, int y1, int y2, int color);
-void _x_hline(struct BITMAP *bmp, int x1, int y, int x2, int color);
-void _x_draw_sprite(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _x_draw_sprite_v_flip(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _x_draw_sprite_h_flip(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _x_draw_sprite_vh_flip(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _x_draw_trans_sprite(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y);
-void _x_draw_lit_sprite(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _x_draw_rle_sprite(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _x_draw_trans_rle_sprite(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y);
-void _x_draw_lit_rle_sprite(struct BITMAP *bmp, struct RLE_SPRITE *sprite, int x, int y, int color);
-void _x_draw_character(struct BITMAP *bmp, struct BITMAP *sprite, int x, int y, int color);
-void _x_textout_fixed(struct BITMAP *bmp, void *f, int h, unsigned char *str, int x, int y, int color);
-void _x_blit_from_memory(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _x_blit_to_memory(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _x_blit(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _x_blit_forward(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _x_blit_backward(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _x_masked_blit(struct BITMAP *source, struct BITMAP *dest, int source_x, int source_y, int dest_x, int dest_y, int width, int height);
-void _x_clear_to_color(struct BITMAP *bitmap, int color);
-
-
-/* asm helper for stretch_blit() */
-void _do_stretch(BITMAP *source, BITMAP *dest, void *drawer, int sx, fixed sy, fixed syd, int dx, int dy, int dh, int color_depth);
-
-
-/* number of fractional bits used by the polygon rasteriser */
-#define POLYGON_FIX_SHIFT     18
-
-
-/* bitfield specifying which polygon attributes need interpolating */
-#define INTERP_FLAT           1
-#define INTERP_1COL           2
-#define INTERP_3COL           4
-#define INTERP_FIX_UV         8
-#define INTERP_Z              16
-#define INTERP_FLOAT_UV       32
-#define OPT_FLOAT_UV_TO_FIX   64
-#define COLOR_TO_RGB          128
-
-
-/* information for polygon scanline fillers */
-typedef struct POLYGON_SEGMENT
-{
-   fixed u, v, du, dv;              /* fixed point u/v coordinates */
-   fixed c, dc;                     /* single color gouraud shade values */
-   fixed r, g, b, dr, dg, db;       /* RGB gouraud shade values */
-   float z, dz;                     /* polygon depth (1/z) */
-   float fu, fv, dfu, dfv;          /* floating point u/v coordinates */
-   unsigned char *texture;          /* the texture map */
-   int umask, vmask, vshift;        /* texture map size information */
-   int seg;                         /* destination bitmap selector */
-} POLYGON_SEGMENT;
-
-
-/* an active polygon edge */
-typedef struct POLYGON_EDGE 
-{
-   int top;                         /* top y position */
-   int bottom;                      /* bottom y position */
-   fixed x, dx;                     /* fixed point x position and gradient */
-   fixed w;                         /* width of line segment */
-   POLYGON_SEGMENT dat;             /* texture/gouraud information */
-   struct POLYGON_EDGE *prev;       /* doubly linked list */
-   struct POLYGON_EDGE *next;
-} POLYGON_EDGE;
-
-
-/* prototype for the scanline filler functions */
-typedef void (*SCANLINE_FILLER)(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-
-/* polygon helper functions */
-extern SCANLINE_FILLER _optim_alternative_drawer;
-POLYGON_EDGE *_add_edge(POLYGON_EDGE *list, POLYGON_EDGE *edge, int sort_by_x);
-POLYGON_EDGE *_remove_edge(POLYGON_EDGE *list, POLYGON_EDGE *edge);
-void _fill_3d_edge_structure(POLYGON_EDGE *edge, V3D *v1, V3D *v2, int flags, BITMAP *bmp);
-void _fill_3d_edge_structure_f(POLYGON_EDGE *edge, V3D_f *v1, V3D_f *v2, int flags, BITMAP *bmp);
-SCANLINE_FILLER _get_scanline_filler(int type, int *flags, POLYGON_SEGMENT *info, BITMAP *texture, BITMAP *bmp);
-void _clip_polygon_segment(POLYGON_SEGMENT *info, int gap, int flags);
-
-
-/* polygon scanline filler functions */
-void _poly_scanline_gcol8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_grgb8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit8(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb8x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb15(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask15(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask15(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit15(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit15(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit15(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit15(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb15x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit15x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit15x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit15x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit15x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_ptex_lit15d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit15d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit16(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb16x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit16x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit16x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit16x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit16x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_ptex_lit16d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit16d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit24(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb24x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit24x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit24x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit24x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit24x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_ptex_lit24d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit24d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit32(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_grgb32x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_lit32x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_lit32x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_atex_mask_lit32x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit32x(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-void _poly_scanline_ptex_lit32d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-void _poly_scanline_ptex_mask_lit32d(unsigned long addr, int w, POLYGON_SEGMENT *info);
-
-
-/* sound lib stuff */
-extern int _digi_volume;
-extern int _midi_volume;
-extern int _flip_pan; 
-extern int _sound_hq;
-
-extern int (*_midi_init)(void);
-extern void (*_midi_exit)(void);
-
-int _midi_allocate_voice(int min, int max);
-
-extern volatile long _midi_tick;
-
-int _digmid_find_patches(char *dir, char *file);
-
-#define VIRTUAL_VOICES  256
-
-
-typedef struct          /* a virtual (as seen by the user) soundcard voice */
-{
-   SAMPLE *sample;      /* which sample are we playing? (NULL = free) */
-   int num;             /* physical voice number (-1 = been killed off) */
-   int autokill;        /* set to free the voice when the sample finishes */
-   long time;           /* when we were started (for voice allocation) */
-   int priority;        /* how important are we? */
-} VOICE;
-
-extern VOICE _voice[VIRTUAL_VOICES];
-
-
-typedef struct          /* a physical (as used by hardware) soundcard voice */
-{
-   int num;             /* the virtual voice currently using me (-1 = free) */
-   int playmode;        /* are we looping? */
-   int vol;             /* current volume (fixed point .12) */
-   int dvol;            /* volume delta, for ramping */
-   int target_vol;      /* target volume, for ramping */
-   int pan;             /* current pan (fixed point .12) */
-   int dpan;            /* pan delta, for sweeps */
-   int target_pan;      /* target pan, for sweeps */
-   int freq;            /* current frequency (fixed point .12) */
-   int dfreq;           /* frequency delta, for sweeps */
-   int target_freq;     /* target frequency, for sweeps */
-} PHYS_VOICE;
-
-extern PHYS_VOICE _phys_voice[DIGI_VOICES];
-
-
-#define MIXER_DEF_SFX               8
-#define MIXER_MAX_SFX               64
-
-int _mixer_init(int bufsize, int freq, int stereo, int is16bit, int *voices);
-void _mixer_exit(void);
-void _mix_some_samples(unsigned long buf, unsigned short seg, int issigned);
-
-void _mixer_init_voice(int voice, SAMPLE *sample);
-void _mixer_release_voice(int voice);
-void _mixer_start_voice(int voice);
-void _mixer_stop_voice(int voice);
-void _mixer_loop_voice(int voice, int loopmode);
-int  _mixer_get_position(int voice);
-void _mixer_set_position(int voice, int position);
-int  _mixer_get_volume(int voice);
-void _mixer_set_volume(int voice, int volume);
-void _mixer_ramp_volume(int voice, int time, int endvol);
-void _mixer_stop_volume_ramp(int voice);
-int  _mixer_get_frequency(int voice);
-void _mixer_set_frequency(int voice, int frequency);
-void _mixer_sweep_frequency(int voice, int time, int endfreq);
-void _mixer_stop_frequency_sweep(int voice);
-int  _mixer_get_pan(int voice);
-void _mixer_set_pan(int voice, int pan);
-void _mixer_sweep_pan(int voice, int time, int endpan);
-void _mixer_stop_pan_sweep(int voice);
-void _mixer_set_echo(int voice, int strength, int delay);
-void _mixer_set_tremolo(int voice, int rate, int depth);
-void _mixer_set_vibrato(int voice, int rate, int depth);
-
-/* dummy functions for the NoSound drivers */
-int  _dummy_detect(int input);
-int  _dummy_init(int input, int voices);
-void _dummy_exit(int input);
-int  _dummy_mixer_volume(int volume);
-void _dummy_init_voice(int voice, SAMPLE *sample);
-void _dummy_noop1(int p);
-void _dummy_noop2(int p1, int p2);
-void _dummy_noop3(int p1, int p2, int p3);
-int  _dummy_get_position(int voice);
-int  _dummy_get(int voice);
-void _dummy_raw_midi(unsigned char data);
-int  _dummy_load_patches(char *patches, char *drums);
-void _dummy_adjust_patches(char *patches, char *drums);
-void _dummy_key_on(int inst, int note, int bend, int vol, int pan);
-
-
-/* from djgpp's libc, needed to find which directory we were run from */
-extern int __crt0_argc;
-extern char **__crt0_argv;
-
-
-#endif          /* ifndef INTERNAL_H */
diff --git a/src/djgppdos/rdb-s.h b/src/djgppdos/rdb-s.h
deleted file mode 100644
index 2d460c9357022f8f23c7764cf76803f1091ab525..0000000000000000000000000000000000000000
--- a/src/djgppdos/rdb-s.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 2005-2020 by Sonic Team Junior.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief Set Com port and speed for GDBStubs for DJGGP
-///
-///	copy and rename as rdb.h and set the defines below as needed
-
-#define DEBUG_COM_PORT 2
-#define  DEBUG_COM_PORT_SPEED 9600
diff --git a/src/djgppdos/vid_vesa.c b/src/djgppdos/vid_vesa.c
deleted file mode 100644
index 61ed18e4b6858eb8a9374b7edfe957f06975cce0..0000000000000000000000000000000000000000
--- a/src/djgppdos/vid_vesa.c
+++ /dev/null
@@ -1,903 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief extended vesa VESA2.0 video modes i/o
-
-#include <stdlib.h>
-
-#include "../i_system.h"        //I_Error()
-#include "vid_vesa.h"
-#include "../doomdef.h"         //MAXVIDWIDTH, MAXVIDHEIGHT
-#include "../screen.h"
-
-#include <dpmi.h>
-#include <go32.h>
-#include <sys/farptr.h>
-#include <sys/movedata.h>
-#include <sys/segments.h>
-#include <sys/nearptr.h>
-
-#include "../console.h"
-#include "../command.h"            //added:21-03-98: vid_xxx commands
-#include "../i_video.h"
-
-
-// PROTOS
-static vmode_t *VID_GetModePtr (int modenum);
-static int  VID_VesaGetModeInfo (int modenum);
-static void VID_VesaGetExtraModes (void);
-static INT32  VID_VesaInitMode (viddef_t *lvid, vmode_t *pcurrentmode);
-
-static void VID_Command_NumModes_f (void);
-static void VID_Command_ModeInfo_f (void);
-static void VID_Command_ModeList_f (void);
-static void VID_Command_Mode_f (void);
-
-consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_stretch = {"stretch", "On", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-#define VBEVERSION      2       // we need vesa2 or higher
-
-// -----------------------------------------------------
-#define MASK_LINEAR(addr)     (addr & 0x000FFFFF)
-#define RM_TO_LINEAR(addr)    (((addr & 0xFFFF0000) >> 12) + (addr & 0xFFFF))
-#define RM_OFFSET(addr)       (addr & 0xF)
-#define RM_SEGMENT(addr)      ((addr >> 4) & 0xFFFF)
-// -----------------------------------------------------
-
-static int totalvidmem;
-
-static vmode_t      vesa_modes[MAX_VESA_MODES] = {{NULL, NULL, 0, 0, 0, 0, 0, 0, NULL, NULL, 0}};
-static vesa_extra_t vesa_extra[MAX_VESA_MODES];
-
-//this is the only supported non-vesa mode : standard 320x200x256c.
-#define NUMVGAVIDMODES  1
-static INT32 VGA_InitMode (viddef_t *lvid, vmode_t *pcurrentmode);
-static char vgamode1[] ="320x200";
-static vmode_t      vgavidmodes[NUMVGAVIDMODES] = {
-  {
-	NULL,
-	vgamode1,
-	320, 200,  //(200.0/320.0)*(320.0/240.0),
-	320, 1,    // rowbytes, bytes per pixel
-	0, 1,
-	NULL,
-	VGA_InitMode, 0
-  }
-};
-
-static char         names[MAX_VESA_MODES][10];
-
-//----------------------------i_video.c------------------------------------
-// these ones should go to i_video.c, but I prefer keep them away from the
-// doom sources until the vesa stuff is ok.
-static int     numvidmodes;   //total number of video modes, vga, vesa1, vesa2.
-static vmode_t *pvidmodes;    //start of videomodes list.
-static vmode_t *pcurrentmode; // the current active videomode.
-//----------------------------i_video.c------------------------------------
-
-
-
-// table des modes videos.
-// seul le mode 320x200x256c standard VGA est support๏ฟฝ sans le VESA.
-// ce mode est le mode num๏ฟฝro 0 dans la liste.
-typedef struct
-{
-	int modenum;            // vesa vbe2.0 modenum
-	int mode_attributes;
-	int winasegment;
-	int winbsegment;
-	int bytes_per_scanline; // bytes per logical scanline (+16)
-	int win;                // window number (A=0, B=1)
-	int win_size;           // window size (+6)
-	int granularity;        // how finely i can set the window in vid mem (+4)
-	int width, height;      // displayed width and height (+18, +20)
-	int bits_per_pixel;     // er, better be 8, 15, 16, 24, or 32 (+25)
-	int bytes_per_pixel;    // er, better be 1, 2, or 4
-	int memory_model;       // and better be 4 or 6, packed or direct color (+27)
-	int num_pages;          // number of complete frame buffer pages (+29)
-	int red_width;          // the # of bits in the red component (+31)
-	int red_pos;            // the bit position of the red component (+32)
-	int green_width;        // etc.. (+33)
-	int green_pos;          // (+34)
-	int blue_width;         // (+35)
-	int blue_pos;           // (+36)
-	int pptr;
-	int pagesize;
-	int numpages;
-} modeinfo_t;
-
-static vbeinfoblock_t vesainfo;
-static vesamodeinfo_t vesamodeinfo;
-
-// ------------------------------------------------------------------------
-// DOS stuff
-// ------------------------------------------------------------------------
-static unsigned long conventional_memory = (unsigned long)-1;
-
-FUNCINLINE static ATTRINLINE void map_in_conventional_memory(void)
-{
-	if (conventional_memory == (unsigned long)-1)
-	{
-		if (__djgpp_nearptr_enable())
-		{
-			conventional_memory = __djgpp_conventional_base;
-		}
-	}
-}
-
-// Converts a flat 32 bit ptr to a realmode 0x12345 type ptr (seg<<4 + offs)
-#if 0
-unsigned int ptr2real(void *ptr)
-{
-	map_in_conventional_memory();
-	return (int)ptr - conventional_memory;
-}
-#endif
-
-// Converts 0x12345 (seg<<4+offs) realmode ptr to a flat 32bit ptr
-FUNCINLINE static ATTRINLINE void *real2ptr(unsigned int real)
-{
-	map_in_conventional_memory();
-	return (void *) (real + conventional_memory);
-}
-
-// ------------------------------------------------------------------------
-
-
-/* ======================================================================== */
-// Add the standard VGA video modes (only one now) to the video modes list.
-/* ======================================================================== */
-static inline void VGA_Init(void)
-{
-	vgavidmodes[NUMVGAVIDMODES-1].pnext = pvidmodes;
-	pvidmodes = &vgavidmodes[0];
-	numvidmodes += NUMVGAVIDMODES;
-}
-
-
-//added:30-01-98: return number of video modes in pvidmodes list
-INT32 VID_NumModes(void)
-{
-	return numvidmodes;
-}
-
-//added:21-03-98: return info on video mode
-FUNCINLINE static ATTRINLINE const char *VID_ModeInfo (int modenum, char **ppheader)
-{
-	static const char *badmodestr = "Bad video mode number\n";
-	vmode_t     *pv;
-
-	pv = VID_GetModePtr (modenum);
-
-	if (!pv)
-	{
-		if (ppheader)
-			*ppheader = NULL;
-		return badmodestr;
-	}
-	else
-	{
-		//if (ppheader)
-		//    *ppheader = pv->header;
-		return pv->name;
-	}
-}
-
-
-
-//added:03-02-98: return a video mode number from the dimensions
-INT32 VID_GetModeForSize( INT32 w, INT32 h)
-{
-	vmode_t *pv;
-	int modenum;
-
-	pv = pvidmodes;
-	for (modenum=0; pv!=NULL; pv=pv->pnext,modenum++ )
-	{
-		if ( pv->width==(unsigned)w && pv->height==(unsigned)h )
-			return modenum;
-	}
-
-	return 0;
-}
-
-
-/* ======================================================================== */
-//
-/* ======================================================================== */
-void    VID_Init (void)
-{
-	COM_AddCommand ("vid_nummodes", VID_Command_NumModes_f);
-	COM_AddCommand ("vid_modeinfo", VID_Command_ModeInfo_f);
-	COM_AddCommand ("vid_modelist", VID_Command_ModeList_f);
-	COM_AddCommand ("vid_mode", VID_Command_Mode_f);
-	CV_RegisterVar (&cv_vidwait);
-	CV_RegisterVar (&cv_stretch);
-
-	//setup the videmodes list,
-	// note that mode 0 must always be VGA mode 0x13
-	pvidmodes = NULL;
-	pcurrentmode = NULL;
-	numvidmodes = 0;
-	// setup the vesa_modes list
-	VID_VesaGetExtraModes ();
-
-	// the game boots in 320x200 standard VGA, but
-	// we need a highcolor mode to run the game in highcolor
-	if (highcolor && numvidmodes==0)
-		I_Error ("No 15bit highcolor VESA2 video mode found, cannot run in highcolor.\n");
-
-	// add the vga modes at the start of the modes list
-	VGA_Init();
-
-
-#ifdef DEBUG
-	CONS_Printf("VID_SetMode(%d)\n",vid.modenum);
-#endif
-	VID_SetMode (0); //vid.modenum);
-
-
-#ifdef DEBUG
-	CONS_Printf("after VID_SetMode\n");
-	CONS_Printf("vid.width    %d\n",vid.width);
-	CONS_Printf("vid.height   %d\n",vid.height);
-	CONS_Printf("vid.buffer   %x\n",vid.buffer);
-	CONS_Printf("vid.rowbytes %d\n",vid.rowbytes);
-	CONS_Printf("vid.numpages %d\n",vid.numpages);
-	CONS_Printf("vid.recalc   %d\n",vid.recalc);
-	CONS_Printf("vid.direct   %x\n",vid.direct);
-#endif
-
-}
-
-
-// ========================================================================
-// Returns a vmode_t from the video modes list, given a video mode number.
-// ========================================================================
-vmode_t *VID_GetModePtr (int modenum)
-{
-	vmode_t *pv;
-
-	pv = pvidmodes;
-	if (!pv)
-		I_Error ("VID_error 1\n");
-
-	while (modenum--)
-	{
-		pv = pv->pnext;
-		if (!pv)
-			I_Error ("VID_error 2\n");
-	}
-
-	return pv;
-}
-
-
-//added:30-01-98:return the name of a video mode
-const char *VID_GetModeName (INT32 modenum)
-{
-	return (VID_GetModePtr(modenum))->name;
-}
-
-
-// ========================================================================
-// Sets a video mode
-// ========================================================================
-INT32 VID_SetMode (INT32 modenum)  //, UINT8 *palette)
-{
-	int     vstat;
-	vmode_t *pnewmode, *poldmode;
-
-	if ((modenum >= numvidmodes) || (modenum < 0))
-	{
-		if (pcurrentmode == NULL)
-		{
-			modenum = 0;    // mode hasn't been set yet, so initialize to base
-							//  mode since they gave us an invalid initial mode
-		}
-		else
-		{
-			//nomodecheck = true;
-			I_Error ("Unknown video mode: %d\n", modenum);
-			//nomodecheck = false;
-			return 0;
-		}
-	}
-
-	pnewmode = VID_GetModePtr (modenum);
-
-	if (pnewmode == pcurrentmode)
-		return 1;   // already in the desired mode
-
-	// initialize the new mode
-	poldmode = pcurrentmode;
-	pcurrentmode = pnewmode;
-
-	// initialize vidbuffer size for setmode
-	vid.width  = pcurrentmode->width;
-	vid.height = pcurrentmode->height;
-	//vid.aspect = pcurrentmode->aspect;
-	vid.rowbytes = pcurrentmode->rowbytes;
-	vid.bpp      = pcurrentmode->bytesperpixel;
-
-	//debug
-	//if (vid.rowbytes != vid.width)
-	//    I_Error("vidrowbytes (%d) <> vidwidth(%d)\n",vid.rowbytes,vid.width);
-
-	vstat = (*pcurrentmode->setmode) (&vid, pcurrentmode);
-
-	if (vstat < 1)
-	{
-		if (vstat == 0)
-		{
-			// harware could not setup mode
-			//if (!VID_SetMode (vid.modenum))
-			//    I_Error ("VID_SetMode: couldn't set video mode (hard failure)");
-			I_Error("Couldn't set video mode %d\n", modenum);
-		}
-		else
-		if (vstat == -1)
-		{
-			CONS_Printf ("Not enough mem for VID_SetMode...\n");
-
-			// not enough memory; just put things back the way they were
-			pcurrentmode = poldmode;
-			vid.width = pcurrentmode->width;
-			vid.height = pcurrentmode->height;
-			vid.rowbytes = pcurrentmode->rowbytes;
-			vid.bpp      = pcurrentmode->bytesperpixel;
-			return 0;
-		}
-	}
-
-	vid.modenum = modenum;
-
-	//printf ("%s\n", VID_ModeInfo (vid.modenum, NULL));
-
-	//added:20-01-98: recalc all tables and realloc buffers based on
-	//                vid values.
-	vid.recalc = 1;
-
-	if (!cv_stretch.value && (float)vid.width/vid.height != ((float)BASEVIDWIDTH/BASEVIDHEIGHT))
-		vid.height = (int)(vid.width * ((float)BASEVIDHEIGHT/BASEVIDWIDTH));// Adjust the height to match
-
-	return 1;
-}
-
-void VID_CheckRenderer(void) {}
-void VID_CheckGLLoaded(rendermode_t oldrender) {}
-
-
-
-// converts a segm:offs 32bit pair to a 32bit flat ptr
-#if 0
-void *VID_ExtraFarToLinear (void *ptr)
-{
-	int     temp;
-
-	temp = (int)ptr;
-	return real2ptr (((temp & 0xFFFF0000) >> 12) + (temp & 0xFFFF));
-}
-#endif
-
-
-
-// ========================================================================
-// Helper function for VID_VesaGetExtraModes
-// In:  vesa mode number, from the vesa videomodenumbers list
-// Out: false, if no info for given modenum
-// ========================================================================
-int VID_VesaGetModeInfo (int modenum)
-{
-	int     bytes_per_pixel;
-	unsigned int          i;
-	__dpmi_regs regs;
-
-	for (i=0; i<sizeof (vesamodeinfo_t); i++)
-		_farpokeb(_dos_ds, MASK_LINEAR(__tb)+i, 0);
-
-	regs.x.ax = 0x4f01;
-	regs.x.di = RM_OFFSET(__tb);
-	regs.x.es = RM_SEGMENT(__tb);
-	regs.x.cx = modenum;
-	__dpmi_int(0x10, &regs);
-	if (regs.h.ah)
-		return false;
-	else
-	{
-		dosmemget (MASK_LINEAR(__tb), sizeof (vesamodeinfo_t), &vesamodeinfo);
-
-		bytes_per_pixel = (vesamodeinfo.BitsPerPixel+1)/8;
-
-		// we add either highcolor or lowcolor video modes, not the two
-		if (highcolor && (vesamodeinfo.BitsPerPixel != 15))
-			return false;
-		if (!highcolor && (vesamodeinfo.BitsPerPixel != 8))
-			return false;
-
-		if ((bytes_per_pixel > 2) ||
-			(vesamodeinfo.XResolution > MAXVIDWIDTH) ||
-			(vesamodeinfo.YResolution > MAXVIDHEIGHT))
-		{
-			return false;
-		}
-
-		// we only want color graphics modes that are supported by the hardware
-		if ((vesamodeinfo.ModeAttributes &
-			(MODE_SUPPORTED_IN_HW | COLOR_MODE | GRAPHICS_MODE) ) !=
-			 (MODE_SUPPORTED_IN_HW | COLOR_MODE | GRAPHICS_MODE))
-		{
-			return false;
-		}
-
-		// we only work with linear frame buffers, except for 320x200,
-		// which is linear when banked at 0xA000
-		if (!(vesamodeinfo.ModeAttributes & LINEAR_FRAME_BUFFER))
-		{
-			if ((vesamodeinfo.XResolution != 320) ||
-				(vesamodeinfo.YResolution != 200))
-			{
-				return false;
-			}
-		}
-
-		// pagesize
-		if ((vesamodeinfo.BytesPerScanLine * vesamodeinfo.YResolution)
-		    > totalvidmem)
-		{
-			return false;
-		}
-
-		vesamodeinfo.NumberOfImagePages = 1;
-
-
-#ifdef DEBUG
-		CONS_Printf("VID: (VESA) info for mode 0x%x\n", modeinfo.modenum);
-		CONS_Printf("  mode attrib = 0x%0x\n", modeinfo.mode_attributes);
-		CONS_Printf("  win a attrib = 0x%0x\n", *(UINT8 *)(infobuf+2));
-		CONS_Printf("  win b attrib = 0x%0x\n", *(UINT8 *)(infobuf+3));
-		CONS_Printf("  win a seg 0x%0x\n", (int) modeinfo.winasegment);
-		CONS_Printf("  win b seg 0x%0x\n", (int) modeinfo.winbsegment);
-		CONS_Printf("  bytes per scanline = %d\n",
-				modeinfo.bytes_per_scanline);
-		CONS_Printf("  width = %d, height = %d\n", modeinfo.width,
-				modeinfo.height);
-		CONS_Printf("  win = %c\n", 'A' + modeinfo.win);
-		CONS_Printf("  win granularity = %d\n", modeinfo.granularity);
-		CONS_Printf("  win size = %d\n", modeinfo.win_size);
-		CONS_Printf("  bits per pixel = %d\n", modeinfo.bits_per_pixel);
-		CONS_Printf("  bytes per pixel = %d\n", modeinfo.bytes_per_pixel);
-		CONS_Printf("  memory model = 0x%x\n", modeinfo.memory_model);
-		CONS_Printf("  num pages = %d\n", modeinfo.num_pages);
-		CONS_Printf("  red width = %d\n", modeinfo.red_width);
-		CONS_Printf("  red pos = %d\n", modeinfo.red_pos);
-		CONS_Printf("  green width = %d\n", modeinfo.green_width);
-		CONS_Printf("  green pos = %d\n", modeinfo.green_pos);
-		CONS_Printf("  blue width = %d\n", modeinfo.blue_width);
-		CONS_Printf("  blue pos = %d\n", modeinfo.blue_pos);
-		CONS_Printf("  phys mem = %x\n", modeinfo.pptr);
-#endif
-	}
-
-	return true;
-}
-
-
-// ========================================================================
-// Get extended VESA modes information, keep the ones that we support,
-// so they'll be available in the game Video menu.
-// ========================================================================
-#define MAXVESADESC 100
-static char vesadesc[MAXVESADESC] = "";
-
-void VID_VesaGetExtraModes (void)
-{
-	unsigned int    i;
-	unsigned long   addr;
-	int             nummodes;
-	__dpmi_meminfo  phys_mem_info;
-	unsigned long   mode_ptr;
-	__dpmi_regs     regs;
-
-	// make a copy of the video modes list! else trash in __tb
-	UINT16          vmode[MAX_VESA_MODES+1];
-	int             numvmodes;
-	UINT16          vesamode;
-
-	// new ugly stuff...
-	for (i=0; i<sizeof (vbeinfoblock_t); i++)
-		_farpokeb(_dos_ds, MASK_LINEAR(__tb)+i, 0);
-
-	dosmemput("VBE2", 4, MASK_LINEAR(__tb));
-
-	// see if VESA support is available
-	regs.x.ax = 0x4f00;
-	regs.x.di = RM_OFFSET(__tb);
-	regs.x.es = RM_SEGMENT(__tb);
-	__dpmi_int(0x10, &regs);
-	if (regs.h.ah)
-		goto no_vesa;
-
-	dosmemget(MASK_LINEAR(__tb), sizeof (vbeinfoblock_t), &vesainfo);
-
-	if (strncmp((void *)vesainfo.VESASignature, "VESA", 4) != 0)
-	{
-no_vesa:
-		CONS_Printf ("No VESA driver\n");
-		return;
-	}
-
-	if (vesainfo.VESAVersion < (VBEVERSION<<8))
-	{
-		CONS_Printf ("VESA VBE %d.0 not available\n", VBEVERSION);
-		return;
-	}
-
-	//
-	// vesa version number
-	//
-	CONS_Printf ("%4.4s %d.%d (", vesainfo.VESASignature,
-	             vesainfo.VESAVersion>>8,
-	             vesainfo.VESAVersion&0xFF);
-
-	//
-	// vesa description string
-	//
-	i = 0;
-	addr = RM_TO_LINEAR(vesainfo.OemStringPtr);
-	_farsetsel(_dos_ds);
-	while (_farnspeekb(addr) != 0)
-	{
-		vesadesc[i++] = _farnspeekb(addr++);
-		if (i == MAXVESADESC-1)
-			break;
-	}
-	vesadesc[i]=0;
-	CONS_Printf ("%s)\n",vesadesc);
-
-	totalvidmem = vesainfo.TotalMemory << 16;
-
-   //
-   // find 8 bit modes
-   //
-	numvmodes = 0;
-	mode_ptr = RM_TO_LINEAR(vesainfo.VideoModePtr);
-	while ((vmode[numvmodes] = _farpeekw(_dos_ds, mode_ptr)) != 0xFFFF)
-	{
-		numvmodes++;
-		if ( numvmodes == MAX_VESA_MODES )
-			break;
-		mode_ptr += 2;
-	}
-	vmode[numvmodes] = 0xffff;
-
-	nummodes = 0;       // number of video modes accepted for the game
-
-	numvmodes=0;  // go again through vmodes table
-	while ( ((vesamode=vmode[numvmodes++]) != 0xFFFF) && (nummodes < MAX_VESA_MODES) )
-	{
-		//fill the modeinfo struct.
-		if (VID_VesaGetModeInfo (vesamode))
-		{
-			vesa_modes[nummodes].pnext = &vesa_modes[nummodes+1];
-			if (vesamodeinfo.XResolution > 999)
-			{
-				if (vesamodeinfo.YResolution > 999)
-				{
-					sprintf (&names[nummodes][0], "%4dx%4d", vesamodeinfo.XResolution,
-					         vesamodeinfo.YResolution);
-					names[nummodes][9] = 0;
-				}
-				else
-				{
-					sprintf (&names[nummodes][0], "%4dx%3d", vesamodeinfo.XResolution,
-					         vesamodeinfo.YResolution);
-					names[nummodes][8] = 0;
-				}
-			}
-			else
-			{
-				if (vesamodeinfo.YResolution > 999)
-				{
-					sprintf (&names[nummodes][0], "%3dx%4d", vesamodeinfo.XResolution,
-					         vesamodeinfo.YResolution);
-					names[nummodes][8] = 0;
-				}
-				else
-				{
-					sprintf (&names[nummodes][0], "%3dx%3d", vesamodeinfo.XResolution,
-					         vesamodeinfo.YResolution);
-					names[nummodes][7] = 0;
-				}
-			}
-
-			vesa_modes[nummodes].name = &names[nummodes][0];
-			vesa_modes[nummodes].width = vesamodeinfo.XResolution;
-			vesa_modes[nummodes].height = vesamodeinfo.YResolution;
-
-			//added:20-01-98:aspect ratio to be implemented...
-			vesa_modes[nummodes].rowbytes = vesamodeinfo.BytesPerScanLine;
-			vesa_modes[nummodes].windowed = 0;
-			vesa_modes[nummodes].pextradata = &vesa_extra[nummodes];
-			vesa_modes[nummodes].setmode = VID_VesaInitMode;
-
-			if (vesamodeinfo.ModeAttributes & LINEAR_FRAME_BUFFER)
-			{
-			// add linear bit to mode for linear modes
-				vesa_extra[nummodes].vesamode = vesamode | LINEAR_MODE;
-				vesa_modes[nummodes].numpages = 1; //vesamodeinfo.NumberOfImagePages;
-
-				phys_mem_info.address = (int)vesamodeinfo.PhysBasePtr;
-				phys_mem_info.size = 0x400000;
-
-				// returns -1 on error
-				if (__dpmi_physical_address_mapping(&phys_mem_info))
-				{
-					//skip this mode, it doesnt work
-					continue;
-				}
-
-				// if physical mapping was ok... convert the selector:offset
-				vesa_extra[nummodes].plinearmem =
-				 real2ptr (phys_mem_info.address);
-
-				// lock the region
-				__dpmi_lock_linear_region (&phys_mem_info);
-			}
-			else
-			{
-			// banked at 0xA0000
-				vesa_extra[nummodes].vesamode = vesamode;
-				//vesa_extra[nummodes].pages[0] = 0;
-				vesa_extra[nummodes].plinearmem =
-				 real2ptr (vesamodeinfo.WinASegment<<4);
-
-				vesa_modes[nummodes].numpages = 1; //modeinfo.numpages;
-			}
-
-			vesa_modes[nummodes].bytesperpixel = (vesamodeinfo.BitsPerPixel+1)/8;
-
-			nummodes++;
-		}
-	}
-
-// add the VESA modes at the start of the mode list (if there are any)
-	if (nummodes)
-	{
-		vesa_modes[nummodes-1].pnext = NULL; //pvidmodes;
-		pvidmodes = &vesa_modes[0];
-		numvidmodes += nummodes;
-	}
-
-}
-
-
-// ========================================================================
-// Free the video buffer of the last video mode,
-// allocate a new buffer for the video mode to set.
-// ========================================================================
-static boolean VID_FreeAndAllocVidbuffer (viddef_t *lvid)
-{
-	int  vidbuffersize;
-
-	vidbuffersize = (lvid->width * lvid->height * lvid->bpp * NUMSCREENS);  //status bar
-
-	// free allocated buffer for previous video mode
-	if (lvid->buffer!=NULL)
-	{
-		free(lvid->buffer);
-	}
-
-	// allocate the new screen buffer
-	if ( (lvid->buffer = (UINT8 *) malloc(vidbuffersize))==NULL )
-		return false;
-
-	// initially clear the video buffer
-	memset (lvid->buffer, 0x00, vidbuffersize);
-
-#ifdef DEBUG
-	CONS_Printf("VID_FreeAndAllocVidbuffer done, vidbuffersize: %x\n",vidbuffersize);
-#endif
-	return true;
-}
-
-
-// ========================================================================
-// Set video mode routine for STANDARD VGA MODES (NO HIGHCOLOR)
-// Out: 1 ok,
-//      0 hardware could not set mode,
-//     -1 no mem
-// ========================================================================
-static INT32 VGA_InitMode (viddef_t *lvid, vmode_t *currentmodep)
-{
-	__dpmi_regs   regs;
-
-	if (!VID_FreeAndAllocVidbuffer (lvid))
-		return -1;                  //no mem
-
-	//added:26-01-98: should clear video mem here
-
-	//set mode 0x13
-	regs.h.ah = 0;
-	regs.h.al = 0x13;
-	__dpmi_int(0x10, &regs);
-
-	// here it is the standard VGA 64k window, not an LFB
-	// (you could have 320x200x256c with LFB in the vesa modes)
-	lvid->direct = (UINT8 *) real2ptr (0xa0000);
-	lvid->u.numpages = 1;
-	lvid->bpp = currentmodep->bytesperpixel;
-
-	return 1;
-}
-
-
-// ========================================================================
-// Set video mode routine for VESA video modes, see VID_SetMode()
-// Out: 1 ok,
-//      0 hardware could not set mode,
-//     -1 no mem
-// ========================================================================
-INT32 VID_VesaInitMode (viddef_t *lvid, vmode_t *currentmodep)
-{
-	vesa_extra_t    *pextra;
-	__dpmi_regs     regs;
-
-	pextra = currentmodep->pextradata;
-
-#ifdef DEBUG
-	CONS_Printf("VID_VesaInitMode...\n");
-	CONS_Printf(" currentmodep->name %s\n",currentmodep->name);
-	CONS_Printf("               width %d\n",currentmodep->width);
-	CONS_Printf("               height %d\n",currentmodep->height);
-	CONS_Printf("               rowbytes %d\n",currentmodep->rowbytes);
-	CONS_Printf("               windowed %d\n",currentmodep->windowed);
-	CONS_Printf("               numpages %d\n",currentmodep->numpages);
-	CONS_Printf(" currentmodep->pextradata :\n");
-	CONS_Printf("                ->vesamode %x\n",pextra->vesamode);
-	CONS_Printf("                ->plinearmem %x\n\n",pextra->plinearmem);
-#endif
-
-	//added:20-01-98:no page flipping now... TO DO!!!
-	lvid->u.numpages = 1;
-
-	// clean up any old vid buffer lying around, alloc new if needed
-	if (!VID_FreeAndAllocVidbuffer (lvid))
-		return -1;                  //no mem
-
-
-	//added:20-01-98: should clear video mem here
-
-
-	// set the mode
-	regs.x.ax = 0x4f02;
-	regs.x.bx = pextra->vesamode;
-	__dpmi_int (0x10, &regs);
-
-	if (regs.x.ax != 0x4f)
-		return 0;               // could not set mode
-
-//added:20-01-98: should setup wait_vsync flag, currentpage here...
-//                plus check for display_enable bit
-
-//added:20-01-98: here we should set the page if page flipping...
-
-	// points to LFB, or the start of VGA mem.
-	lvid->direct = pextra->plinearmem;
-	lvid->bpp    = currentmodep->bytesperpixel;
-
-	return 1;
-}
-
-// ========================================================================
-//                     VIDEO MODE CONSOLE COMMANDS
-// ========================================================================
-
-
-//  vid_nummodes
-//
-//added:21-03-98:
-void VID_Command_NumModes_f (void)
-{
-	int     nummodes;
-
-	nummodes = VID_NumModes ();
-	CONS_Printf ("%d video mode(s) available(s)\n", nummodes);
-}
-
-
-//  vid_modeinfo <modenum>
-//
-void VID_Command_ModeInfo_f (void)
-{
-	vmode_t     *pv;
-	int         modenum;
-
-	if (COM_Argc()!=2)
-		modenum = vid.modenum;          // describe the current mode
-	else
-		modenum = atoi (COM_Argv(1));   //    .. the given mode number
-
-	if (modenum >= VID_NumModes())
-	{
-		CONS_Printf ("No such video mode\n");
-		return;
-	}
-
-	pv = VID_GetModePtr (modenum);
-
-	CONS_Printf ("%s\n", VID_ModeInfo (modenum, NULL));
-	CONS_Printf ("width : %d\n"
-	             "height: %d\n"
-	             "bytes per scanline: %d\n"
-	             "bytes per pixel: %d\n"
-	             "numpages: %d\n",
-	             pv->width,
-	             pv->height,
-	             pv->rowbytes,
-	             pv->bytesperpixel,
-	             pv->numpages );
-}
-
-
-//  vid_modelist
-//
-void VID_Command_ModeList_f (void)
-{
-	int         i, nummodes;
-	const char  *pinfo;
-	char        *pheader;
-	vmode_t     *pv;
-	boolean     na;
-
-	na = false;
-
-	nummodes = VID_NumModes ();
-	for (i=0 ; i<nummodes ; i++)
-	{
-		pv = VID_GetModePtr (i);
-		pinfo = VID_ModeInfo (i, &pheader);
-
-		if (i==0 || pv->bytesperpixel==1)
-			CONS_Printf ("%d: %s\n", i, pinfo);
-		else
-			CONS_Printf ("%d: %s (hicolor)\n", i, pinfo);
-	}
-
-}
-
-
-//  vid_mode <modenum>
-//
-void VID_Command_Mode_f (void)
-{
-	int         modenum;
-
-	if (COM_Argc()!=2)
-	{
-		CONS_Printf ("vid_mode <modenum> : set video mode\n");
-		return;
-	}
-
-	modenum = atoi(COM_Argv(1));
-
-	if (modenum >= VID_NumModes())
-		CONS_Printf ("No such video mode\n");
-	else
-		// request vid mode change
-		setmodeneeded = modenum+1;
-}
diff --git a/src/djgppdos/vid_vesa.h b/src/djgppdos/vid_vesa.h
deleted file mode 100644
index b704336e8f121e0ffd345710fcca08fdc07becac..0000000000000000000000000000000000000000
--- a/src/djgppdos/vid_vesa.h
+++ /dev/null
@@ -1,109 +0,0 @@
-// Emacs style mode select   -*- C++ -*-
-//-----------------------------------------------------------------------------
-//
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//-----------------------------------------------------------------------------
-/// \file
-/// \brief VESA extra modes.
-
-#include "../doomdef.h"
-#include "../screen.h"
-
-
-
-#define MODE_SUPPORTED_IN_HW    0x0001
-#define COLOR_MODE              0x0008
-#define GRAPHICS_MODE           0x0010
-#define VGA_INCOMPATIBLE        0x0020
-#define LINEAR_FRAME_BUFFER     0x0080
-#define LINEAR_MODE             0x4000
-
-#define MAX_VESA_MODES          30  // we'll just take the first 30 if there
-
-
-// VESA information block structure
-typedef struct vbeinfoblock_s
-{
-	UINT8          VESASignature[4];
-	UINT16         VESAVersion;
-	unsigned long  OemStringPtr;
-	UINT8          Capabilities[4];
-	unsigned long  VideoModePtr;
-	UINT16         TotalMemory;
-	UINT8          OemSoftwareRev[2];
-	UINT8          OemVendorNamePtr[4];
-	UINT8          OemProductNamePtr[4];
-	UINT8          OemProductRevPtr[4];
-	UINT8          Reserved[222];
-	UINT8          OemData[256];
-}  ATTRPACK vbeinfoblock_t;
-
-
-// VESA information for a specific mode
-typedef struct vesamodeinfo_s
-{
-	UINT16         ModeAttributes;
-	UINT8          WinAAttributes;
-	UINT8          WinBAttributes;
-	UINT16         WinGranularity;
-	UINT16         WinSize;
-	UINT16         WinASegment;
-	UINT16         WinBSegment;
-	unsigned long  WinFuncPtr;
-	UINT16         BytesPerScanLine;
-	UINT16         XResolution;
-	UINT16         YResolution;
-	UINT8          XCharSize;
-	UINT8          YCharSize;
-	UINT8          NumberOfPlanes;
-	UINT8          BitsPerPixel;
-	UINT8          NumberOfBanks;
-	UINT8          MemoryModel;
-	UINT8          BankSize;
-	UINT8          NumberOfImagePages;
-	UINT8          Reserved_page;
-	UINT8          RedMaskSize;
-	UINT8          RedMaskPos;
-	UINT8          GreenMaskSize;
-	UINT8          GreenMaskPos;
-	UINT8          BlueMaskSize;
-	UINT8          BlueMaskPos;
-	UINT8          ReservedMaskSize;
-	UINT8          ReservedMaskPos;
-	UINT8          DirectColorModeInfo;
-
-	/* VBE 2.0 extensions */
-	unsigned long  PhysBasePtr;
-	unsigned long  OffScreenMemOffset;
-	UINT16         OffScreenMemSize;
-
-	/* VBE 3.0 extensions */
-	UINT16         LinBytesPerScanLine;
-	UINT8          BnkNumberOfPages;
-	UINT8          LinNumberOfPages;
-	UINT8          LinRedMaskSize;
-	UINT8          LinRedFieldPos;
-	UINT8          LinGreenMaskSize;
-	UINT8          LinGreenFieldPos;
-	UINT8          LinBlueMaskSize;
-	UINT8          LinBlueFieldPos;
-	UINT8          LinRsvdMaskSize;
-	UINT8          LinRsvdFieldPos;
-	unsigned long  MaxPixelClock;
-
-	UINT8          Reserved[190];
-} ATTRPACK vesamodeinfo_t;
-
-
-// setup standard VGA + VESA modes list, activate the default video mode.
-void VID_Init (void);
diff --git a/src/doomdata.h b/src/doomdata.h
index 77ed56bc23e853f825159ccbc60b1c7d3f98e823..b3f7f5c4dbcc4ea5ce9ee8ff1cdf34a1845cdafa 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -23,6 +23,9 @@
 // Some global defines, that configure the game.
 #include "doomdef.h"
 
+#include "taglist.h"
+#include "m_fixed.h" // See the mapthing_t scale.
+
 //
 // Map level types.
 // The following data structures define the persistent format
@@ -193,16 +196,23 @@ typedef struct
 #pragma pack()
 #endif
 
+#define NUMMAPTHINGARGS 6
+#define NUMMAPTHINGSTRINGARGS 2
+
 // Thing definition, position, orientation and type,
 // plus visibility flags and attributes.
 typedef struct
 {
 	INT16 x, y;
-	INT16 angle;
+	INT16 angle, pitch, roll;
 	UINT16 type;
 	UINT16 options;
 	INT16 z;
 	UINT8 extrainfo;
+	taglist_t tags;
+	fixed_t scale;
+	INT32 args[NUMMAPTHINGARGS];
+	char *stringargs[NUMMAPTHINGSTRINGARGS];
 	struct mobj_s *mobj;
 } mapthing_t;
 
diff --git a/src/doomdef.h b/src/doomdef.h
index c46e458e122c61fab3b075b82d9d07301548aa95..c6172c42896ee6199608d7982010d136d70076d2 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -104,10 +104,6 @@
 #include <io.h>
 #endif
 
-#ifdef PC_DOS
-#include <conio.h>
-#endif
-
 //#define NOMD5
 
 // Uncheck this to compile debugging code
@@ -133,8 +129,15 @@ extern char logfilename[1024];
 #define VERSIONSTRING "Development EXE"
 // most interface strings are ignored in development mode.
 // we use comprevision and compbranch instead.
+// VERSIONSTRING_RC is for the resource-definition script used by windows builds
+#else
+#ifdef BETAVERSION
+#define VERSIONSTRING "v"SRB2VERSION" "BETAVERSION
+#define VERSIONSTRING_RC SRB2VERSION " " BETAVERSION "\0"
 #else
 #define VERSIONSTRING "v"SRB2VERSION
+#define VERSIONSTRING_RC SRB2VERSION "\0"
+#endif
 // Hey! If you change this, add 1 to the MODVERSION below!
 // Otherwise we can't force updates!
 #endif
@@ -157,7 +160,9 @@ extern char logfilename[1024];
 // the other options the same.
 
 // Comment out this line to completely disable update alerts (recommended for testing, but not for release)
+#ifndef BETAVERSION
 #define UPDATE_ALERT
+#endif
 
 // The string used in the alert that pops up in the event of an update being available.
 // Please change to apply to your modification (we don't want everyone asking where your mod is on SRB2.org!).
@@ -556,8 +561,8 @@ INT32 I_GetKey(void);
 #endif
 
 // The character that separates pathnames. Forward slash on
-// most systems, but reverse solidus (\) on Windows and DOS.
-#if defined (PC_DOS) || defined (_WIN32)
+// most systems, but reverse solidus (\) on Windows.
+#if defined (_WIN32)
 	#define PATHSEP "\\"
 #else
 	#define PATHSEP "/"
@@ -581,9 +586,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 ///	Dumps the contents of a network save game upon consistency failure for debugging.
 //#define DUMPCONSISTENCY
 
-///	See name of player in your crosshair
-#define SEENAMES
-
 ///	Who put weights on my recycler?  ... Inuyasha did.
 ///	\note	XMOD port.
 //#define WEIGHTEDRECYCLER
@@ -627,9 +629,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// \note   Required for proper collision with moving sloped surfaces that have sector specials on them.
 #define SECTORSPECIALSAFTERTHINK
 
-/// Cache patches in Lua in a way that renderer switching will work flawlessly.
-//#define LUA_PATCH_SAFETY
-
 /// Sprite rotation
 #define ROTSPRITE
 #define ROTANGLES 72 // Needs to be a divisor of 360 (45, 60, 90, 120...)
@@ -643,4 +642,13 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// Render flats on walls
 #define WALLFLATS
 
+/// Maintain compatibility with older 2.2 demos
+#define OLD22DEMOCOMPAT
+
+#if defined (HAVE_CURL) && ! defined (NONET)
+#define MASTERSERVER
+#else
+#undef UPDATE_ALERT
+#endif
+
 #endif // __DOOMDEF__
diff --git a/src/doomstat.h b/src/doomstat.h
index aef9541221799f62f9d49c0ee3f030748efabeae..aeea796fab7063eb891ae77fcc9c7926d02698e8 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -409,7 +409,7 @@ enum GameType
 	GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1,
 	NUMGAMETYPES
 };
-// If you alter this list, update dehacked.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c
+// If you alter this list, update deh_tables.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c
 
 // Gametype rules
 enum GameTypeRules
diff --git a/src/doomtype.h b/src/doomtype.h
index 0aa3e23e05b74613b690993450e87b5dad6e800d..950f50856b7679e035c268e036660fe505d8dbab 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -371,4 +371,38 @@ typedef UINT32 tic_t;
 #define WSTRING2(s) L ## s
 #define WSTRING(s) WSTRING2 (s)
 
+/*
+A hack by Monster Iestyn: Return a pointer to a field of
+a struct from a pointer to another field in the struct.
+Needed for some lua shenanigans.
+*/
+#define FIELDFROM( type, field, have, want ) \
+	(void *)((intptr_t)(field) - offsetof (type, have) + offsetof (type, want))
+
+typedef UINT8 bitarray_t;
+
+#define BIT_ARRAY_SIZE(n) (((n) + 7) >> 3)
+
+static inline int
+in_bit_array (const bitarray_t * const array, const int value)
+{
+	return (array[value >> 3] & (1<<(value & 7)));
+}
+
+static inline void
+set_bit_array (bitarray_t * const array, const int value)
+{
+	array[value >> 3] |= (1<<(value & 7));
+}
+
+static inline void
+unset_bit_array (bitarray_t * const array, const int value)
+{
+	array[value >> 3] &= ~(1<<(value & 7));
+}
+
+#ifdef HAVE_SDL
+typedef UINT64 precise_t;
+#endif
+
 #endif //__DOOMTYPE__
diff --git a/src/dummy/i_cdmus.c b/src/dummy/i_cdmus.c
index fc35eb9cf0bec5ac8532c6ae6ec4f685592b51ab..94b8fa30ed150ca99e2e8d320d77c7322af39697 100644
--- a/src/dummy/i_cdmus.c
+++ b/src/dummy/i_cdmus.c
@@ -8,8 +8,8 @@
 
 UINT8 cdaudio_started = 0;
 
-consvar_t cd_volume = {"cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cd_volume = CVAR_INIT ("cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL);
+consvar_t cdUpdate  = CVAR_INIT ("cd_update","1",CV_SAVE, NULL, NULL);
 
 
 void I_InitCD(void){}
diff --git a/src/dummy/i_video.c b/src/dummy/i_video.c
index 56ead3672ae8ded9510814b20c766061a5d1d668..3b0a12a328df587e1cd20d0312cf96ce6c8df847 100644
--- a/src/dummy/i_video.c
+++ b/src/dummy/i_video.c
@@ -3,12 +3,13 @@
 #include "../i_video.h"
 
 rendermode_t rendermode = render_none;
+rendermode_t chosenrendermode = render_none;
 
 boolean highcolor = false;
 
 boolean allow_fullscreen = false;
 
-consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, NULL);
 
 void I_StartupGraphics(void){}
 void I_ShutdownGraphics(void){}
@@ -40,8 +41,15 @@ INT32 VID_SetMode(INT32 modenum)
 	return 0;
 }
 
-void VID_CheckRenderer(void) {}
-void VID_CheckGLLoaded(rendermode_t oldrender) {}
+boolean VID_CheckRenderer(void)
+{
+	return false;
+}
+
+void VID_CheckGLLoaded(rendermode_t oldrender)
+{
+	(void)oldrender;
+}
 
 const char *VID_GetModeName(INT32 modenum)
 {
diff --git a/src/f_finale.c b/src/f_finale.c
index 477c6373c5470c5cf19ed482770f17fbeac8bc67..a33dfc5f77c70f4ade15502b6ab4394845c4d26d 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -25,6 +25,7 @@
 #include "w_wad.h"
 #include "z_zone.h"
 #include "i_system.h"
+#include "i_threads.h"
 #include "m_menu.h"
 #include "dehacked.h"
 #include "g_input.h"
@@ -224,6 +225,9 @@ static INT32 cutscene_textspeed = 0;
 static UINT8 cutscene_boostspeed = 0;
 static tic_t cutscene_lasttextwrite = 0;
 
+// STJR Intro
+char stjrintro[9] = "STJRI000";
+
 //
 // This alters the text string cutscene_disptext.
 // Use the typical string drawing functions to display it.
@@ -311,7 +315,7 @@ const char *introtext[NUMINTROSCENES];
 
 static tic_t introscenetime[NUMINTROSCENES] =
 {
-	 7*TICRATE + (TICRATE/2),	// STJr Presents
+	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...
@@ -526,80 +530,81 @@ static void F_IntroDrawScene(void)
 	switch (intro_scenenum)
 	{
 		case 0:
+			bgxoffs = 28;
 			break;
 		case 1:
-			background = W_CachePatchName("INTRO1", PU_PATCH);
+			background = W_CachePatchName("INTRO1", PU_PATCH_LOWPRIORITY);
 			break;
 		case 2:
-			background = W_CachePatchName("INTRO2", PU_PATCH);
+			background = W_CachePatchName("INTRO2", PU_PATCH_LOWPRIORITY);
 			break;
 		case 3:
-			background = W_CachePatchName("INTRO3", PU_PATCH);
+			background = W_CachePatchName("INTRO3", PU_PATCH_LOWPRIORITY);
 			break;
 		case 4:
-			background = W_CachePatchName("INTRO4", PU_PATCH);
+			background = W_CachePatchName("INTRO4", PU_PATCH_LOWPRIORITY);
 			break;
 		case 5:
 			if (intro_curtime >= 5*TICRATE)
-				background = W_CachePatchName("RADAR", PU_PATCH);
+				background = W_CachePatchName("RADAR", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DRAT", PU_PATCH);
+				background = W_CachePatchName("DRAT", PU_PATCH_LOWPRIORITY);
 			break;
 		case 6:
-			background = W_CachePatchName("INTRO6", PU_PATCH);
+			background = W_CachePatchName("INTRO6", PU_PATCH_LOWPRIORITY);
 			cx = 180;
 			cy = 8;
 			break;
 		case 7:
 		{
 			if (intro_curtime >= 7*TICRATE + ((TICRATE/7)*2))
-				background = W_CachePatchName("SGRASS5", PU_PATCH);
+				background = W_CachePatchName("SGRASS5", PU_PATCH_LOWPRIORITY);
 			else if (intro_curtime >= 7*TICRATE + (TICRATE/7))
-				background = W_CachePatchName("SGRASS4", PU_PATCH);
+				background = W_CachePatchName("SGRASS4", PU_PATCH_LOWPRIORITY);
 			else if (intro_curtime >= 7*TICRATE)
-				background = W_CachePatchName("SGRASS3", PU_PATCH);
+				background = W_CachePatchName("SGRASS3", PU_PATCH_LOWPRIORITY);
 			else if (intro_curtime >= 6*TICRATE)
-				background = W_CachePatchName("SGRASS2", PU_PATCH);
+				background = W_CachePatchName("SGRASS2", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("SGRASS1", PU_PATCH);
+				background = W_CachePatchName("SGRASS1", PU_PATCH_LOWPRIORITY);
 			break;
 		}
 		case 8:
-			background = W_CachePatchName("WATCHING", PU_PATCH);
+			background = W_CachePatchName("WATCHING", PU_PATCH_LOWPRIORITY);
 			break;
 		case 9:
-			background = W_CachePatchName("ZOOMING", PU_PATCH);
+			background = W_CachePatchName("ZOOMING", PU_PATCH_LOWPRIORITY);
 			break;
 		case 10:
 			break;
 		case 11:
-			background = W_CachePatchName("INTRO5", PU_PATCH);
+			background = W_CachePatchName("INTRO5", PU_PATCH_LOWPRIORITY);
 			break;
 		case 12:
-			background = W_CachePatchName("REVENGE", PU_PATCH);
+			background = W_CachePatchName("REVENGE", PU_PATCH_LOWPRIORITY);
 			cx = 208;
 			cy = 8;
 			break;
 		case 13:
-			background = W_CachePatchName("CONFRONT", PU_PATCH);
+			background = W_CachePatchName("CONFRONT", PU_PATCH_LOWPRIORITY);
 			cy += 48;
 			break;
 		case 14:
-			background = W_CachePatchName("TAILSSAD", PU_PATCH);
+			background = W_CachePatchName("TAILSSAD", PU_PATCH_LOWPRIORITY);
 			bgxoffs = 144;
 			cx = 8;
 			cy = 8;
 			break;
 		case 15:
 			if (intro_curtime >= 7*TICRATE)
-				background = W_CachePatchName("SONICDO2", PU_PATCH);
+				background = W_CachePatchName("SONICDO2", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("SONICDO1", PU_PATCH);
+				background = W_CachePatchName("SONICDO1", PU_PATCH_LOWPRIORITY);
 			cx = 224;
 			cy = 8;
 			break;
 		case 16:
-			background = W_CachePatchName("INTRO7", PU_PATCH);
+			background = W_CachePatchName("INTRO7", PU_PATCH_LOWPRIORITY);
 			break;
 		default:
 			break;
@@ -616,97 +621,34 @@ static void F_IntroDrawScene(void)
 	}
 	else if (intro_scenenum == 0) // STJr presents
 	{
-		// "Waaaaaaah" intro
-		if (finalecount-TICRATE/2 < 4*TICRATE+23) {
-			// aspect is FRACUNIT/2 for 4:3 (source) resolutions, smaller for 16:10 (SRB2) resolutions
-			fixed_t aspect = (FRACUNIT + (FRACUNIT*4/3 - FRACUNIT*vid.width/vid.height)/2)>>1;
-			fixed_t x,y;
-			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 2);
-			if (finalecount < 30) { // Cry!
-				if (finalecount < 4)
-					S_StopMusic();
-				if (finalecount == 4)
-					S_ChangeMusicInternal("_stjr", false);
-				x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(334<<FRACBITS, aspect)/2;
-				y = (BASEVIDHEIGHT<<FRACBITS)/2 - FixedMul(358<<FRACBITS, aspect)/2;
-				V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH1", PU_PATCH)), aspect);
-				W_UnlockCachedPatch(patch);
-				if (finalecount > 6) {
-					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH2", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-				}
-				if (finalecount > 10) {
-					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH3", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-				}
-				if (finalecount > 14) {
-					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH4", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-				}
-			}
-			else if (finalecount-30 < 20) { // Big eggy
-				background = W_CachePatchName("FEEDIN", PU_PATCH);
-				x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(560<<FRACBITS, aspect)/2;
-				y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(477<<FRACBITS, aspect);
-				V_DrawSciencePatch(x, y, V_SNAPTOBOTTOM, background, aspect);
-			}
-			else if (finalecount-50 < 30) { // Zoom out
-				fixed_t scale = FixedDiv(aspect, FixedDiv((finalecount-50)<<FRACBITS, (15<<FRACBITS))+FRACUNIT);
-				background = W_CachePatchName("FEEDIN", PU_PATCH);
-				x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(560<<FRACBITS, aspect)/2 + (FixedMul(560<<FRACBITS, aspect) - FixedMul(560<<FRACBITS, scale));
-				y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(477<<FRACBITS, scale);
-				V_DrawSciencePatch(x, y, V_SNAPTOBOTTOM, background, scale);
-			}
-			else
+		if (intro_curtime > 1 && intro_curtime < (INT32)introscenetime[intro_scenenum])
+		{
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
+			if (intro_curtime < TICRATE-5) // Make the text shine!
+				sprintf(stjrintro, "STJRI%03u", intro_curtime-1);
+			else if (intro_curtime >= TICRATE-6 && intro_curtime < 2*TICRATE-20) // Pause on black screen for just a second
+				return;
+			else if (intro_curtime == 2*TICRATE-19)
 			{
-				{
-					// Draw tiny eggy
-					fixed_t scale = FixedMul(FRACUNIT/3, aspect);
-					background = W_CachePatchName("FEEDIN", PU_PATCH);
-					x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(560<<FRACBITS, aspect)/2 + (FixedMul(560<<FRACBITS, aspect) - FixedMul(560<<FRACBITS, scale));
-					y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(477<<FRACBITS, scale);
-					V_DrawSciencePatch(x, y, V_SNAPTOBOTTOM, background, scale);
-				}
+				// Fade in the text
+				// The text fade out is automatically handled when switching to a new intro scene
+				strncpy(stjrintro, "STJRI029", 9);
+				S_ChangeMusicInternal("_stjr", false);
 
-				if (finalecount-84 < 58) { // Pure Fat is driving up!
-					int ftime = (finalecount-84);
-					x = (-189*FRACUNIT) + (FixedMul((6<<FRACBITS)+FRACUNIT/3, ftime<<FRACBITS) - FixedMul((6<<FRACBITS)+FRACUNIT/3, FixedDiv(FixedMul(ftime<<FRACBITS, ftime<<FRACBITS), 120<<FRACBITS)));
-					y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(417<<FRACBITS, aspect);
-					// Draw the body
-					V_DrawSciencePatch(x, y, V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT1", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-					// Draw the door
-					V_DrawSciencePatch(x+FixedMul(344<<FRACBITS, aspect), y+FixedMul(292<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT2", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-					// Draw the wheel
-					V_DrawSciencePatch(x+FixedMul(178<<FRACBITS, aspect), y+FixedMul(344<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName(va("TYRE%02u",(abs(finalecount-144)/3)%16), PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-					// Draw the wheel cover
-					V_DrawSciencePatch(x+FixedMul(88<<FRACBITS, aspect), y+FixedMul(238<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT3", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-				} else { // Pure Fat has stopped!
-					y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(417<<FRACBITS, aspect);
-					// Draw the body
-					V_DrawSciencePatch(0, y, V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT1", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-					// Draw the wheel
-					V_DrawSciencePatch(FixedMul(178<<FRACBITS, aspect), y+FixedMul(344<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("TYRE00", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-					// Draw the wheel cover
-					V_DrawSciencePatch(FixedMul(88<<FRACBITS, aspect), y+FixedMul(238<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT3", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-					// Draw the door
-					if (finalecount-TICRATE/2 > 4*TICRATE) { // Door is being raised!
-						int ftime = (finalecount-TICRATE/2-4*TICRATE);
-						y -= FixedDiv((ftime*ftime)<<FRACBITS, 23<<FRACBITS);
-					}
-					V_DrawSciencePatch(FixedMul(344<<FRACBITS, aspect), y+FixedMul(292<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT2", PU_PATCH)), aspect);
-					W_UnlockCachedPatch(patch);
-				}
+				background = W_CachePatchName(stjrintro, PU_PATCH_LOWPRIORITY);
+				wipestyleflags = WSF_FADEIN;
+				F_WipeStartScreen();
+				F_TryColormapFade(31);
+				V_DrawSmallScaledPatch(bgxoffs, 84, 0, background);
+				F_WipeEndScreen();
+				F_RunWipe(0,true);
+			}
+
+			if (!WipeInAction) // Draw the patch if not in a wipe
+			{
+				background = W_CachePatchName(stjrintro, PU_PATCH_LOWPRIORITY);
+				V_DrawSmallScaledPatch(bgxoffs, 84, 0, background);
 			}
-		} else {
-			V_DrawCreditString((160 - V_CreditStringWidth("SONIC TEAM JR")/2)<<FRACBITS, 80<<FRACBITS, 0, "SONIC TEAM JR");
-			V_DrawCreditString((160 - V_CreditStringWidth("PRESENTS")/2)<<FRACBITS, 96<<FRACBITS, 0, "PRESENTS");
 		}
 	}
 	else if (intro_scenenum == 10) // Sky Runner
@@ -714,27 +656,27 @@ static void F_IntroDrawScene(void)
 		if (timetonext > 5*TICRATE && timetonext < 6*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG1", PU_PATCH);
+				background = W_CachePatchName("BRITEGG1", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DARKEGG1", PU_PATCH);
+				background = W_CachePatchName("DARKEGG1", PU_PATCH_LOWPRIORITY);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
 		else if (timetonext > 3*TICRATE && timetonext < 4*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG2", PU_PATCH);
+				background = W_CachePatchName("BRITEGG2", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DARKEGG2", PU_PATCH);
+				background = W_CachePatchName("DARKEGG2", PU_PATCH_LOWPRIORITY);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
 		else if (timetonext > 1*TICRATE && timetonext < 2*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG3", PU_PATCH);
+				background = W_CachePatchName("BRITEGG3", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DARKEGG3", PU_PATCH);
+				background = W_CachePatchName("DARKEGG3", PU_PATCH_LOWPRIORITY);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
@@ -766,79 +708,79 @@ static void F_IntroDrawScene(void)
 			knucklesx += sonicx;
 			sonicx += P_ReturnThrustX(NULL, finalecount * ANG10, 3);
 
-			V_DrawSmallScaledPatch(skyx, 0, 0, (patch = W_CachePatchName("INTROSKY", PU_PATCH)));
+			V_DrawSmallScaledPatch(skyx, 0, 0, (patch = W_CachePatchName("INTROSKY", PU_PATCH_LOWPRIORITY)));
 			V_DrawSmallScaledPatch(skyx - 320, 0, 0, patch);
 			W_UnlockCachedPatch(patch);
-			V_DrawSmallScaledPatch(grassx, 0, 0, (patch = W_CachePatchName("INTROGRS", PU_PATCH)));
+			V_DrawSmallScaledPatch(grassx, 0, 0, (patch = W_CachePatchName("INTROGRS", PU_PATCH_LOWPRIORITY)));
 			V_DrawSmallScaledPatch(grassx - 320, 0, 0, patch);
 			W_UnlockCachedPatch(patch);
 
 			if (finalecount & 1)
 			{
 				// Sonic
-				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN2", PU_PATCH)));
+				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN2", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Appendages
 				if (finalecount & 2)
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT4", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT4", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 				else
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT2", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT2", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 
 				// Tails
-				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY2", PU_PATCH)));
+				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY2", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Knuckles
-				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE2", PU_PATCH)));
+				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE2", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 			}
 			else
 			{
 				// Sonic
-				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN1", PU_PATCH)));
+				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN1", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Appendages
 				if (finalecount & 2)
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT3", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT3", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 				else
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT1", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT1", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 
 				// Tails
-				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY1", PU_PATCH)));
+				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY1", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Knuckles
-				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE1", PU_PATCH)));
+				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE1", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 			}
 
@@ -871,8 +813,8 @@ static void F_IntroDrawScene(void)
 				y += (30*(FRACUNIT-scale));
 			}
 
-			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_PATCH);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_PATCH);
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_PATCH_LOWPRIORITY);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_PATCH_LOWPRIORITY);
 
 			if (worktics >= 5)
 				trans = (worktics-5)>>1;
@@ -959,7 +901,13 @@ void F_IntroDrawer(void)
 
 					I_OsPolling();
 					I_UpdateNoBlit();
+#ifdef HAVE_THREADS
+					I_lock_mutex(&m_menu_mutex);
+#endif
 					M_Drawer(); // menu is drawn even on top of wipes
+#ifdef HAVE_THREADS
+					I_unlock_mutex(m_menu_mutex);
+#endif
 					I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
 
 					if (moviemode) // make sure we save frames for the white hold too
@@ -986,7 +934,7 @@ void F_IntroDrawer(void)
 	{
 		if (intro_scenenum == 5 && intro_curtime == 5*TICRATE)
 		{
-			patch_t *radar = W_CachePatchName("RADAR", PU_PATCH);
+			patch_t *radar = W_CachePatchName("RADAR", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -999,7 +947,7 @@ void F_IntroDrawer(void)
 		}
 		else if (intro_scenenum == 7 && intro_curtime == 6*TICRATE) // Force a wipe here
 		{
-			patch_t *grass = W_CachePatchName("SGRASS2", PU_PATCH);
+			patch_t *grass = W_CachePatchName("SGRASS2", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1010,9 +958,9 @@ void F_IntroDrawer(void)
 			F_WipeEndScreen();
 			F_RunWipe(99,true);
 		}
-		/*else if (intro_scenenum == 12 && intro_curtime == 7*TICRATE)
+		/*else if (intro_scenenum == 11 && intro_curtime == 7*TICRATE)
 		{
-			patch_t *confront = W_CachePatchName("CONFRONT", PU_PATCH);
+			patch_t *confront = W_CachePatchName("CONFRONT", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1025,7 +973,7 @@ void F_IntroDrawer(void)
 		}*/
 		if (intro_scenenum == 15 && intro_curtime == 7*TICRATE)
 		{
-			patch_t *sdo = W_CachePatchName("SONICDO2", PU_PATCH);
+			patch_t *sdo = W_CachePatchName("SONICDO2", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1119,61 +1067,60 @@ static const char *credits[] = {
 	"\1Credits",
 	"",
 	"\1Game Design",
-	"Ben \"Mystic\" Geyer",
+	"Sonic Team Junior",
 	"\"SSNTails\"",
 	"Johnny \"Sonikku\" Wallbank",
 	"",
 	"\1Programming",
 	"Alam \"GBC\" Arias",
 	"Logan \"GBA\" Arias",
+	"Zolton \"Zippy_Zolton\" Auburn",
+	"Colette \"fickleheart\" Bordelon",
+	"Andrew \"orospakr\" Clunis",
+	"Sally \"TehRealSalt\" Cochenour",
+	"Gregor \"Oogaland\" Dick",
 	"Callum Dickinson",
 	"Scott \"Graue\" Feeney",
 	"Victor \"SteelT\" Fuentes",
 	"Nathan \"Jazz\" Giroux",
+	"\"Golden\"",
 	"Vivian \"toaster\" Grannell",
+	"Julio \"Chaos Zero 64\" Guir",
+	"\"Hannu_Hanhi\"", // For many OpenGL performance improvements!
 	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
 	"\"james\"",
 	"Iestyn \"Monster Iestyn\" Jealous",
 	"\"Jimita\"",
-	"Ronald \"Furyhunter\" Kinard", // The SDL2 port
-	"Louis-Antoine \"LJ Sonic\" de Moulins", // de Rochefort doesn't quite fit on the screen sorry lol
-	"John \"JTE\" Muniz",
-	"Ehab \"Wolfy\" Saeed",
-	"Jonas \"MascaraSnake\" Sauer",
 	"\"Kaito Sinclaire\"",
-	"\"SSNTails\"",
-	"Lachlan \"Lach\" Wright",
-	"Marco \"mazmazz\" Zafra",
-	"",
-	"\1Programming",
-	"\1Assistance",
-	"Colette \"fickleheart\" Bordelon",
-	"\"chi.miru\"", // helped port slope drawing code from ZDoom
-	"Andrew \"orospakr\" Clunis",
-	"Sally \"TehRealSalt\" Cochenour",
-	"Gregor \"Oogaland\" Dick",
-	"Julio \"Chaos Zero 64\" Guir",
-	"\"Hannu_Hanhi\"", // For many OpenGL performance improvements!
 	"\"Kalaron\"", // Coded some of Sryder13's collection of OpenGL fixes, especially fog
+	"Ronald \"Furyhunter\" Kinard", // The SDL2 port
 	"\"Lat'\"", // SRB2-CHAT, the chat window from Kart
 	"Matthew \"Shuffle\" Marsalko",
 	"Steven \"StroggOnMeth\" McGranahan",
 	"\"Morph\"", // For SRB2Morphed stuff
+	"Louis-Antoine \"LJ Sonic\" de Moulins", // de Rochefort doesn't quite fit on the screen sorry lol
+	"John \"JTE\" Muniz",
 	"Colin \"Sonict\" Pfaff",
 	"Sean \"Sryder13\" Ryder",
+	"Ehab \"Wolfy\" Saeed",
 	"Tasos \"tatokis\" Sahanidis", // Corrected C FixedMul, making 64-bit builds netplay compatible
+	"Riku \"Ors\" Salminen", // Demo consistency improvements
+	"Jonas \"MascaraSnake\" Sauer",
 	"Wessel \"sphere\" Smit",
-	"Ben \"Cue\" Woodford",
+	"\"SSNTails\"",
+	"\"Varren\"",
 	"\"VelocitOni\"", // Wrote the original dashmode script
 	"Ikaro \"Tatsuru\" Vinhas",
-	// Git contributors with 5+ approved merges of substantive quality,
-	// or contributors with at least one groundbreaking merge, may be named.
-	// Everyone else is acknowledged under "Special Thanks > SRB2 Community Contributors".
+	"Ben \"Cue\" Woodford",
+	"Lachlan \"Lach\" Wright",
+	"Marco \"mazmazz\" Zafra",
 	"",
 	"\1Art",
 	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araรบjo -- sorry for our limited font! D:
+	"\"Arrietty\"",
 	"Ryan \"Blaze Hedgehog\" Bloom",
+	"Graeme P. \"SuperPhanto\" Caldwell", // for the new brak render
 	"\"ChrispyPixels\"",
 	"Paul \"Boinciel\" Clempson",
 	"Sally \"TehRealSalt\" Cochenour",
@@ -1181,6 +1128,8 @@ static const char *credits[] = {
 	"Desmond \"Blade\" DesJardins",
 	"Sherman \"CoatRack\" DesJardins",
 	"\"DirkTheHusky\"",
+	"Jesse \"Jeck Jims\" Emerick",
+	"\"Fighter_Builder\"", // for the CEZ3 button debris
 	"Buddy \"KinkaJoy\" Fischer",
 	"Vivian \"toaster\" Grannell",
 	"James \"SwitchKaze\" Hale",
@@ -1189,10 +1138,12 @@ static const char *credits[] = {
 	"Iestyn \"Monster Iestyn\" Jealous",
 	"William \"GuyWithThePie\" Kloppenberg",
 	"Alice \"Alacroix\" de Lemos",
+	"Logan \"Hyperchaotix\" McCloud",
 	"Alexander \"DrTapeworm\" Moench-Ford",
 	"Andrew \"Senku Niola\" Moran",
 	"\"MotorRoach\"",
 	"Phillip \"TelosTurntable\" Robinson",
+	"\"Scizor300\"",
 	"Wessel \"sphere\" Smit",
 	"David \"Instant Sonic\" Spencer Jr.",
 	"\"SSNTails\"",
@@ -1206,9 +1157,9 @@ static const char *credits[] = {
 	"Malcolm \"RedXVI\" Brown",
 	"Dave \"DemonTomatoDave\" Bulmer",
 	"Paul \"Boinciel\" Clempson",
+	"\"Cyan Helkaraxe\"",
 	"Shane \"CobaltBW\" Ellis",
 	"James \"SeventhSentinel\" Hall",
-	"Cyan Helkaraxe",
 	"Kepa \"Nev3r\" Iceta",
 	"Iestyn \"Monster Iestyn\" Jealous",
 	"Jarel \"Arrow\" Jones",
@@ -1235,8 +1186,9 @@ static const char *credits[] = {
 	"James \"SeventhSentinel\" Hall",
 	"Kepa \"Nev3r\" Iceta",
 	"Thomas \"Shadow Hog\" Igoe",
-	"Alexander \"DrTapeworm\" Moench-Ford",
 	"\"Kaito Sinclaire\"",
+	"Alexander \"DrTapeworm\" Moench-Ford",
+	"\"Revan\"",
 	"Anna \"QueenDelta\" Sandlin",
 	"Wessel \"sphere\" Smit",
 	"\"Spazzo\"",
@@ -1259,7 +1211,7 @@ static const char *credits[] = {
 	"\1Testing",
 	"Discord Community Testers",
 	"Hank \"FuriousFox\" Brannock",
-	"Cody \"SRB2 Playah\" Koester",
+	"Cody \"Playah\" Koester",
 	"Skye \"OmegaVelocity\" Meredith",
 	"Stephen \"HEDGESMFG\" Moellering",
 	"Rosalie \"ST218\" Molina",
@@ -1276,13 +1228,6 @@ static const char *credits[] = {
 	"Pascal \"CodeImp\" vd Heiden", // Doom Builder developer
 	"Randi Heit (<!>)", // For their MSPaint <!> sprite that we nicked
 	"Simon \"sirjuddington\" Judd", // SLADE developer
-	// Acknowledged here are the following:
-	// Minor merge request authors, see guideline above
-	// - Golden - Expanded thin font
-	// Creators of small quantities of sprite/texture assets
-	// - Arietty - New Green Hill-styled textures
-	// - Scizor300 - the only other contributor to the 2.0 SRB2 Asset Pack
-	// - Revan/Icefox - the new Nimbus Ruins skybox
 	"SRB2 Community Contributors",
 	"",
 	"\1Produced By",
@@ -1361,14 +1306,14 @@ void F_CreditDrawer(void)
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	// Zig Zagz
-	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH));
-	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH));
+	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
+	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
+	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
+	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
 
 	// Draw background pictures first
 	for (i = 0; credits_pics[i].patch; i++)
-		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (280<<FRACBITS) + (((i*credits_height)<<FRACBITS)/(credits_numpics)) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_PATCH), FRACUNIT>>1);
+		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (280<<FRACBITS) + (((i*credits_height)<<FRACBITS)/(credits_numpics)) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_PATCH_LOWPRIORITY), FRACUNIT>>1);
 
 	// Dim the background
 	V_DrawFadeScreen(0xFF00, 16);
@@ -1577,14 +1522,14 @@ void F_GameEvaluationDrawer(void)
 
 		if (goodending)
 		{
-			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_PATCH);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_PATCH);
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_PATCH_LOWPRIORITY);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_PATCH_LOWPRIORITY);
 			x -= FRACUNIT;
 		}
 		else
 		{
-			rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_PATCH);
+			rockpat = W_CachePatchName("ROID0000", PU_PATCH_LOWPRIORITY);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_PATCH_LOWPRIORITY);
 		}
 
 		if (finalecount >= 5)
@@ -1616,20 +1561,20 @@ void F_GameEvaluationDrawer(void)
 					// if j == 0 - alternate between 0 and 1
 					//         1 -                   1 and 2
 					//         2 -                   2 and not rendered
-					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_PATCH), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
+					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_PATCH_LOWPRIORITY), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
 				}
 				j--;
 			}
 		}
 		else
 		{
-			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_PATCH);
+			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_PATCH_LOWPRIORITY);
 			V_DrawFixedPatch(x, y, scale, 0, eggrock, colormap[0]);
 			if (trans < 10)
 				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, eggrock, colormap[1]);
 			else if (sparklloop)
 				V_DrawFixedPatch(x, y, scale, (10-sparklloop)<<V_ALPHASHIFT,
-					W_CachePatchName("ENDEGRK0", PU_PATCH), colormap[1]);
+					W_CachePatchName("ENDEGRK0", PU_PATCH_LOWPRIORITY), colormap[1]);
 		}
 	}
 
@@ -1643,7 +1588,7 @@ void F_GameEvaluationDrawer(void)
 		eemeralds_cur += (360<<FRACBITS)/7;
 
 		patchname[4] = 'A'+(char)i;
-		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_PATCH), NULL);
+		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY), NULL);
 	}
 
 	V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext);
@@ -1684,7 +1629,7 @@ void F_GameEvaluationDrawer(void)
 			endingtext = va("%s & %s, %s%s", skins[players[consoleplayer].skin].realname, skins[botskin-1].realname, rtatext, cuttext);
 		else
 			endingtext = va("%s, %s%s", skins[players[consoleplayer].skin].realname, rtatext, cuttext);
-		V_DrawCenteredString(BASEVIDWIDTH/2, 182, (ultimatemode ? V_REDMAP : V_YELLOWMAP), endingtext);
+		V_DrawCenteredString(BASEVIDWIDTH/2, 182, V_SNAPTOBOTTOM|(ultimatemode ? V_REDMAP : V_YELLOWMAP), endingtext);
 	}
 }
 
@@ -1772,32 +1717,32 @@ void F_GameEvaluationTicker(void)
 
 static void F_CacheEnding(void)
 {
-	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_PATCH);
+	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_PATCH_LOWPRIORITY);
 
-	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_PATCH);
-	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_PATCH);
+	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_PATCH_LOWPRIORITY);
+	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_PATCH_LOWPRIORITY);
 
-	endglow[0] = W_CachePatchName("ENDGLOW0", PU_PATCH);
-	endglow[1] = W_CachePatchName("ENDGLOW1", PU_PATCH);
+	endglow[0] = W_CachePatchName("ENDGLOW0", PU_PATCH_LOWPRIORITY);
+	endglow[1] = W_CachePatchName("ENDGLOW1", PU_PATCH_LOWPRIORITY);
 
-	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_PATCH);
-	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_PATCH);
-	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_PATCH);
+	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_PATCH_LOWPRIORITY);
+	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_PATCH_LOWPRIORITY);
+	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_PATCH_LOWPRIORITY);
 
-	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_PATCH);
-	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_PATCH);
-	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_PATCH);
+	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_PATCH_LOWPRIORITY);
+	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_PATCH_LOWPRIORITY);
+	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_PATCH_LOWPRIORITY);
 
-	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_PATCH);
-	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_PATCH);
-	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_PATCH);
-	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_PATCH);
+	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_PATCH_LOWPRIORITY);
+	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_PATCH_LOWPRIORITY);
+	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_PATCH_LOWPRIORITY);
+	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_PATCH_LOWPRIORITY);
 
-	endescp[0] = W_CachePatchName("ENDESCP0", PU_PATCH);
-	endescp[1] = W_CachePatchName("ENDESCP1", PU_PATCH);
-	endescp[2] = W_CachePatchName("ENDESCP2", PU_PATCH);
-	endescp[3] = W_CachePatchName("ENDESCP3", PU_PATCH);
-	endescp[4] = W_CachePatchName("ENDESCP4", PU_PATCH);
+	endescp[0] = W_CachePatchName("ENDESCP0", PU_PATCH_LOWPRIORITY);
+	endescp[1] = W_CachePatchName("ENDESCP1", PU_PATCH_LOWPRIORITY);
+	endescp[2] = W_CachePatchName("ENDESCP2", PU_PATCH_LOWPRIORITY);
+	endescp[3] = W_CachePatchName("ENDESCP3", PU_PATCH_LOWPRIORITY);
+	endescp[4] = W_CachePatchName("ENDESCP4", PU_PATCH_LOWPRIORITY);
 
 	// so we only need to check once
 	if ((goodending = ALL7EMERALDS(emeralds)))
@@ -1810,41 +1755,41 @@ static void F_CacheEnding(void)
 			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
 			// character head, skin specific
 			sprframe = &sprdef->spriteframes[XTRA_ENDING];
-			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
+			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
 			sprframe = &sprdef->spriteframes[XTRA_ENDING+1];
-			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
+			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
 			sprframe = &sprdef->spriteframes[XTRA_ENDING+2];
-			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
+			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
 		}
 		else // Show a star if your character doesn't have an ending firework display. (Basically the MISSINGs for this)
 		{
-			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_PATCH);
-			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_PATCH);
-			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_PATCH);
+			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_PATCH_LOWPRIORITY);
+			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_PATCH_LOWPRIORITY);
+			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_PATCH_LOWPRIORITY);
 		}
 
-		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_PATCH);
+		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_PATCH_LOWPRIORITY);
 	}
 	else
 	{
 		// eggman, skin nonspecific
-		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_PATCH);
-		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_PATCH);
-		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_PATCH);
+		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_PATCH_LOWPRIORITY);
+		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_PATCH_LOWPRIORITY);
+		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_PATCH_LOWPRIORITY);
 
-		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_LEVEL);
+		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_PATCH_LOWPRIORITY);
 	}
 }
 
 static void F_CacheGoodEnding(void)
 {
-	endegrk[0] = W_CachePatchName("ENDEGRK2", PU_PATCH);
-	endegrk[1] = W_CachePatchName("ENDEGRK3", PU_PATCH);
+	endegrk[0] = W_CachePatchName("ENDEGRK2", PU_PATCH_LOWPRIORITY);
+	endegrk[1] = W_CachePatchName("ENDEGRK3", PU_PATCH_LOWPRIORITY);
 
-	endglow[0] = W_CachePatchName("ENDGLOW2", PU_PATCH);
-	endglow[1] = W_CachePatchName("ENDGLOW3", PU_PATCH);
+	endglow[0] = W_CachePatchName("ENDGLOW2", PU_PATCH_LOWPRIORITY);
+	endglow[1] = W_CachePatchName("ENDGLOW3", PU_PATCH_LOWPRIORITY);
 
-	endxpld[0] = W_CachePatchName("ENDEGRK4", PU_PATCH);
+	endxpld[0] = W_CachePatchName("ENDEGRK4", PU_PATCH_LOWPRIORITY);
 }
 
 void F_StartEnding(void)
@@ -1901,17 +1846,10 @@ void F_EndingDrawer(void)
 	INT32 x, y, i, j, parallaxticker;
 	patch_t *rockpat;
 
-	if (needpatchrecache)
-	{
-		F_CacheEnding();
-		if (goodending && finalecount >= INFLECTIONPOINT) // time to swap some assets
-			F_CacheGoodEnding();
-	}
-
 	if (!goodending || finalecount < INFLECTIONPOINT)
-		rockpat = W_CachePatchName("ROID0000", PU_PATCH);
+		rockpat = W_CachePatchName("ROID0000", PU_PATCH_LOWPRIORITY);
 	else
-		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_PATCH);
+		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_PATCH_LOWPRIORITY);
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
@@ -2248,7 +2186,7 @@ void F_EndingDrawer(void)
 				eemeralds_cur[0] += (360<<FRACBITS)/7;
 
 				patchname[4] = 'A'+(char)i;
-				V_DrawFixedPatch(x, y, FRACUNIT, 0, W_CachePatchName(patchname, PU_LEVEL), NULL);
+				V_DrawFixedPatch(x, y, FRACUNIT, 0, W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY), NULL);
 			}
 		} // if (goodending...
 	} // (finalecount > 20)
@@ -2395,14 +2333,14 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 
 	if (!scrollxspeed && !scrollyspeed)
 	{
-		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH));
+		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY));
 		return;
 	}
 
-	pat = W_CachePatchName(patchname, PU_PATCH);
+	pat = W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY);
 
-	patwidth = SHORT(pat->width);
-	patheight = SHORT(pat->height);
+	patwidth = pat->width;
+	patheight = pat->height;
 	pw = patwidth * dupz;
 	ph = patheight * dupz;
 
@@ -2438,7 +2376,7 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 lumpnum = W_CheckNumForName(name); \
 if (lumpnum != LUMPERROR) \
 { \
-	arr[0] = W_CachePatchName(name, PU_LEVEL); \
+	arr[0] = W_CachePatchName(name, PU_PATCH_LOWPRIORITY); \
 	arr[min(1, maxf-1)] = 0; \
 } \
 else if (strlen(name) <= 6) \
@@ -2451,7 +2389,7 @@ else if (strlen(name) <= 6) \
 		lumpname[8] = 0; \
 		lumpnum = W_CheckNumForName(lumpname); \
 		if (lumpnum != LUMPERROR) \
-			arr[i] = W_CachePatchName(lumpname, PU_LEVEL); \
+			arr[i] = W_CachePatchName(lumpname, PU_PATCH_LOWPRIORITY); \
 		else \
 			break; \
 	} \
@@ -2466,21 +2404,21 @@ static void F_CacheTitleScreen(void)
 	{
 		case TTMODE_OLD:
 		case TTMODE_NONE:
-			ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
-			ttwing = W_CachePatchName("TTWING", PU_LEVEL);
-			ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
-			ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
-			ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
-			ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
-			ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
-			ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
-			ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
-			ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
-			ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
-			ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
-			ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
-			ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
-			ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
+			ttbanner = W_CachePatchName("TTBANNER", PU_PATCH_LOWPRIORITY);
+			ttwing = W_CachePatchName("TTWING", PU_PATCH_LOWPRIORITY);
+			ttsonic = W_CachePatchName("TTSONIC", PU_PATCH_LOWPRIORITY);
+			ttswave1 = W_CachePatchName("TTSWAVE1", PU_PATCH_LOWPRIORITY);
+			ttswave2 = W_CachePatchName("TTSWAVE2", PU_PATCH_LOWPRIORITY);
+			ttswip1 = W_CachePatchName("TTSWIP1", PU_PATCH_LOWPRIORITY);
+			ttsprep1 = W_CachePatchName("TTSPREP1", PU_PATCH_LOWPRIORITY);
+			ttsprep2 = W_CachePatchName("TTSPREP2", PU_PATCH_LOWPRIORITY);
+			ttspop1 = W_CachePatchName("TTSPOP1", PU_PATCH_LOWPRIORITY);
+			ttspop2 = W_CachePatchName("TTSPOP2", PU_PATCH_LOWPRIORITY);
+			ttspop3 = W_CachePatchName("TTSPOP3", PU_PATCH_LOWPRIORITY);
+			ttspop4 = W_CachePatchName("TTSPOP4", PU_PATCH_LOWPRIORITY);
+			ttspop5 = W_CachePatchName("TTSPOP5", PU_PATCH_LOWPRIORITY);
+			ttspop6 = W_CachePatchName("TTSPOP6", PU_PATCH_LOWPRIORITY);
+			ttspop7 = W_CachePatchName("TTSPOP7", PU_PATCH_LOWPRIORITY);
 			break;
 
 		// don't load alacroix gfx yet; we do that upon first draw.
@@ -2601,7 +2539,7 @@ void F_StartTitleScreen(void)
 
 static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
 {
-	// This all gets freed by PU_LEVEL when exiting the menus.
+	// This all gets freed by PU_PATCH_LOWPRIORITY when exiting the menus.
 	// When re-visiting the menus (e.g., from exiting in-game), the gfx are force-reloaded.
 	// So leftover addresses here should not be a problem.
 
@@ -2609,28 +2547,28 @@ static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
 	oldttscale--; // zero-based index
 	for (i = 0; i < TTMAX_ALACROIX; i++)
 	{
-		if(ttembl[oldttscale][i]) { Z_Free(ttembl[oldttscale][i]); ttembl[oldttscale][i] = 0; }
-		if(ttribb[oldttscale][i]) { Z_Free(ttribb[oldttscale][i]); ttribb[oldttscale][i] = 0; }
-		if(ttsont[oldttscale][i]) { Z_Free(ttsont[oldttscale][i]); ttsont[oldttscale][i] = 0; }
-		if(ttrobo[oldttscale][i]) { Z_Free(ttrobo[oldttscale][i]); ttrobo[oldttscale][i] = 0; }
-		if(tttwot[oldttscale][i]) { Z_Free(tttwot[oldttscale][i]); tttwot[oldttscale][i] = 0; }
-		if(ttrbtx[oldttscale][i]) { Z_Free(ttrbtx[oldttscale][i]); ttrbtx[oldttscale][i] = 0; }
-		if(ttsoib[oldttscale][i]) { Z_Free(ttsoib[oldttscale][i]); ttsoib[oldttscale][i] = 0; }
-		if(ttsoif[oldttscale][i]) { Z_Free(ttsoif[oldttscale][i]); ttsoif[oldttscale][i] = 0; }
-		if(ttsoba[oldttscale][i]) { Z_Free(ttsoba[oldttscale][i]); ttsoba[oldttscale][i] = 0; }
-		if(ttsobk[oldttscale][i]) { Z_Free(ttsobk[oldttscale][i]); ttsobk[oldttscale][i] = 0; }
-		if(ttsodh[oldttscale][i]) { Z_Free(ttsodh[oldttscale][i]); ttsodh[oldttscale][i] = 0; }
-		if(tttaib[oldttscale][i]) { Z_Free(tttaib[oldttscale][i]); tttaib[oldttscale][i] = 0; }
-		if(tttaif[oldttscale][i]) { Z_Free(tttaif[oldttscale][i]); tttaif[oldttscale][i] = 0; }
-		if(tttaba[oldttscale][i]) { Z_Free(tttaba[oldttscale][i]); tttaba[oldttscale][i] = 0; }
-		if(tttabk[oldttscale][i]) { Z_Free(tttabk[oldttscale][i]); tttabk[oldttscale][i] = 0; }
-		if(tttabt[oldttscale][i]) { Z_Free(tttabt[oldttscale][i]); tttabt[oldttscale][i] = 0; }
-		if(tttaft[oldttscale][i]) { Z_Free(tttaft[oldttscale][i]); tttaft[oldttscale][i] = 0; }
-		if(ttknib[oldttscale][i]) { Z_Free(ttknib[oldttscale][i]); ttknib[oldttscale][i] = 0; }
-		if(ttknif[oldttscale][i]) { Z_Free(ttknif[oldttscale][i]); ttknif[oldttscale][i] = 0; }
-		if(ttknba[oldttscale][i]) { Z_Free(ttknba[oldttscale][i]); ttknba[oldttscale][i] = 0; }
-		if(ttknbk[oldttscale][i]) { Z_Free(ttknbk[oldttscale][i]); ttknbk[oldttscale][i] = 0; }
-		if(ttkndh[oldttscale][i]) { Z_Free(ttkndh[oldttscale][i]); ttkndh[oldttscale][i] = 0; }
+		if(ttembl[oldttscale][i]) { Patch_Free(ttembl[oldttscale][i]); ttembl[oldttscale][i] = 0; }
+		if(ttribb[oldttscale][i]) { Patch_Free(ttribb[oldttscale][i]); ttribb[oldttscale][i] = 0; }
+		if(ttsont[oldttscale][i]) { Patch_Free(ttsont[oldttscale][i]); ttsont[oldttscale][i] = 0; }
+		if(ttrobo[oldttscale][i]) { Patch_Free(ttrobo[oldttscale][i]); ttrobo[oldttscale][i] = 0; }
+		if(tttwot[oldttscale][i]) { Patch_Free(tttwot[oldttscale][i]); tttwot[oldttscale][i] = 0; }
+		if(ttrbtx[oldttscale][i]) { Patch_Free(ttrbtx[oldttscale][i]); ttrbtx[oldttscale][i] = 0; }
+		if(ttsoib[oldttscale][i]) { Patch_Free(ttsoib[oldttscale][i]); ttsoib[oldttscale][i] = 0; }
+		if(ttsoif[oldttscale][i]) { Patch_Free(ttsoif[oldttscale][i]); ttsoif[oldttscale][i] = 0; }
+		if(ttsoba[oldttscale][i]) { Patch_Free(ttsoba[oldttscale][i]); ttsoba[oldttscale][i] = 0; }
+		if(ttsobk[oldttscale][i]) { Patch_Free(ttsobk[oldttscale][i]); ttsobk[oldttscale][i] = 0; }
+		if(ttsodh[oldttscale][i]) { Patch_Free(ttsodh[oldttscale][i]); ttsodh[oldttscale][i] = 0; }
+		if(tttaib[oldttscale][i]) { Patch_Free(tttaib[oldttscale][i]); tttaib[oldttscale][i] = 0; }
+		if(tttaif[oldttscale][i]) { Patch_Free(tttaif[oldttscale][i]); tttaif[oldttscale][i] = 0; }
+		if(tttaba[oldttscale][i]) { Patch_Free(tttaba[oldttscale][i]); tttaba[oldttscale][i] = 0; }
+		if(tttabk[oldttscale][i]) { Patch_Free(tttabk[oldttscale][i]); tttabk[oldttscale][i] = 0; }
+		if(tttabt[oldttscale][i]) { Patch_Free(tttabt[oldttscale][i]); tttabt[oldttscale][i] = 0; }
+		if(tttaft[oldttscale][i]) { Patch_Free(tttaft[oldttscale][i]); tttaft[oldttscale][i] = 0; }
+		if(ttknib[oldttscale][i]) { Patch_Free(ttknib[oldttscale][i]); ttknib[oldttscale][i] = 0; }
+		if(ttknif[oldttscale][i]) { Patch_Free(ttknif[oldttscale][i]); ttknif[oldttscale][i] = 0; }
+		if(ttknba[oldttscale][i]) { Patch_Free(ttknba[oldttscale][i]); ttknba[oldttscale][i] = 0; }
+		if(ttknbk[oldttscale][i]) { Patch_Free(ttknbk[oldttscale][i]); ttknbk[oldttscale][i] = 0; }
+		if(ttkndh[oldttscale][i]) { Patch_Free(ttkndh[oldttscale][i]); ttkndh[oldttscale][i] = 0; }
 	}
 	ttloaded[oldttscale] = false;
 }
@@ -2707,17 +2645,12 @@ static void F_FigureActiveTtScale(void)
 	SINT8 newttscale = max(1, min(6, vid.dupx));
 	SINT8 oldttscale = activettscale;
 
-	if (needpatchrecache)
-		ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] = 0;
-	else
-	{
-		if (newttscale == testttscale)
-			return;
+	if (newttscale == testttscale)
+		return;
 
-		// We have a new ttscale, so load gfx
-		if(oldttscale > 0)
-			F_UnloadAlacroixGraphics(oldttscale);
-	}
+	// We have a new ttscale, so load gfx
+	if(oldttscale > 0)
+		F_UnloadAlacroixGraphics(oldttscale);
 
 	testttscale = newttscale;
 
@@ -2751,9 +2684,6 @@ void F_TitleScreenDrawer(void)
 	if (modeattacking)
 		return; // We likely came here from retrying. Don't do a damn thing.
 
-	if (needpatchrecache && (curttmode != TTMODE_ALACROIX))
-		F_CacheTitleScreen();
-
 	// Draw that sky!
 	if (curbgcolor >= 0)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
@@ -3717,7 +3647,7 @@ void F_ContinueDrawer(void)
 	V_DrawLevelTitle(x - (V_LevelNameWidth("Continue?")>>1), 16, 0, "Continue?");
 
 	// Two stars...
-	patch = W_CachePatchName("CONTSTAR", PU_PATCH);
+	patch = W_CachePatchName("CONTSTAR", PU_PATCH_LOWPRIORITY);
 	V_DrawScaledPatch(x-32, 160, 0, patch);
 	V_DrawScaledPatch(x+32, 160, 0, patch);
 
@@ -3725,14 +3655,14 @@ void F_ContinueDrawer(void)
 	if (timeleft > 9)
 	{
 		numbuf[7] = '1';
-		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
+		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
 		numbuf[7] = '0';
-		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
+		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
 	}
 	else
 	{
 		numbuf[7] = '0'+timeleft;
-		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
+		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
 	}
 
 	// Draw the continue markers! Show continues.
@@ -3761,7 +3691,7 @@ void F_ContinueDrawer(void)
 	}
 
 	// Spotlight
-	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_PATCH));
+	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_PATCH_LOWPRIORITY));
 
 	// warping laser
 	if (continuetime)
@@ -3798,7 +3728,7 @@ void F_ContinueDrawer(void)
 #define drawchar(dx, dy, n)	{\
 								sprdef = &contskins[n]->sprites[cont_spr2[n][0]];\
 								sprframe = &sprdef->spriteframes[cont_spr2[n][1]];\
-								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_PATCH);\
+								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_PATCH_LOWPRIORITY);\
 								V_DrawFixedPatch((dx), (dy), contskins[n]->highresscale, (sprframe->flip & (1<<cont_spr2[n][2])) ? V_FLIP : 0, patch, contcolormaps[n]);\
 							}
 
@@ -4063,10 +3993,10 @@ void F_CutsceneDrawer(void)
 	{
 		if (cutscenes[cutnum]->scene[scenenum].pichires[picnum])
 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
-				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 		else
 			V_DrawScaledPatch(picxpos,picypos, 0,
-				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 	}
 
 	if (dofadenow && rendermode != render_none)
@@ -4098,7 +4028,7 @@ void F_CutsceneTicker(void)
 		if (netgame && i != serverplayer && !IsPlayerAdmin(i))
 			continue;
 
-		if (players[i].cmd.buttons & BT_USE)
+		if (players[i].cmd.buttons & BT_SPIN)
 		{
 			keypressed = false;
 			cutscene_boostspeed = 1;
@@ -4438,11 +4368,11 @@ static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length)
 	else if (!strncmp(tag, "TAJ", 3)) // Jump
 		gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump);
 	else if (!strncmp(tag, "TAS", 3)) // Spin
-		gcs = G_GetControlScheme(gamecontrol, gcl_use, num_gcl_use);
+		gcs = G_GetControlScheme(gamecontrol, gcl_spin, num_gcl_spin);
 	else if (!strncmp(tag, "TAA", 3)) // Char ability
 		gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump);
 	else if (!strncmp(tag, "TAW", 3)) // Shield ability
-		gcs = G_GetControlScheme(gamecontrol, gcl_jump_use, num_gcl_jump_use);
+		gcs = G_GetControlScheme(gamecontrol, gcl_jump_spin, num_gcl_jump_spin);
 	else
 		gcs = G_GetControlScheme(gamecontrol, gcl_tutorial_used, num_gcl_tutorial_used);
 
@@ -4552,10 +4482,10 @@ void F_TextPromptDrawer(void)
 	{
 		if (textprompts[cutnum]->page[scenenum].pichires[picnum])
 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
-				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 		else
 			V_DrawScaledPatch(picxpos,picypos, 0,
-				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 	}
 
 	// Draw background
@@ -4565,7 +4495,7 @@ void F_TextPromptDrawer(void)
 	if (iconlump != LUMPERROR)
 	{
 		INT32 iconx, icony, scale, scaledsize;
-		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_PATCH);
+		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_PATCH_LOWPRIORITY);
 
 		// scale and center
 		if (patch->width > patch->height)
@@ -4699,7 +4629,7 @@ void F_TextPromptTicker(void)
 				else
 					continue;
 
-				if ((players[i].cmd.buttons & BT_USE) || (players[i].cmd.buttons & BT_JUMP))
+				if ((players[i].cmd.buttons & BT_SPIN) || (players[i].cmd.buttons & BT_JUMP))
 				{
 					if (timetonext > 1)
 						timetonext--;
@@ -4722,7 +4652,7 @@ void F_TextPromptTicker(void)
 					}
 					keypressed = true; // prevent repeat events
 				}
-				else if (!(players[i].cmd.buttons & BT_USE) && !(players[i].cmd.buttons & BT_JUMP))
+				else if (!(players[i].cmd.buttons & BT_SPIN) && !(players[i].cmd.buttons & BT_JUMP))
 					keypressed = false;
 
 				if (!splitscreen)
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 01b45b0c2927e6fe6e1f054c156bd0e016a0ed48..6afb8a6a7934709c90b1428fcd5f5a6f55d54c20 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -25,6 +25,7 @@
 #include "z_zone.h"
 
 #include "i_system.h"
+#include "i_threads.h"
 #include "m_menu.h"
 #include "console.h"
 #include "d_main.h"
@@ -292,7 +293,7 @@ static void F_DoWipe(fademask_t *fademask)
 			else
 			{
 				// pointer to transtable that this mask would use
-				transtbl = transtables + ((9 - *mask)<<FF_TRANSSHIFT);
+				transtbl = R_GetTranslucencyTable((9 - *mask) + 1);
 
 				// DRAWING LOOP
 				while (draw_linestogo--)
@@ -595,7 +596,15 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 		I_UpdateNoBlit();
 
 		if (drawMenu)
+		{
+#ifdef HAVE_THREADS
+			I_lock_mutex(&m_menu_mutex);
+#endif
 			M_Drawer(); // menu is drawn even on top of wipes
+#ifdef HAVE_THREADS
+			I_unlock_mutex(m_menu_mutex);
+#endif
+		}
 
 		I_FinishUpdate(); // page flip or blit buffer
 
diff --git a/src/filesrch.c b/src/filesrch.c
index 13d73b6f49845b5b693ff69080b2a779e1364b9d..cb53d07be958fe1ab7ad42ffac3efc0ee768e44f 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -312,18 +312,18 @@ static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"},
 #endif
 													{3, "CUSTOM"}, {0, NULL}};
 
-consvar_t cv_addons_option = {"addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_addons_folder = {"addons_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange);
+consvar_t cv_addons_folder = CVAR_INIT ("addons_folder", "", CV_SAVE, NULL, NULL);
 
 static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}};
-consvar_t cv_addons_md5 = {"addons_md5", "Name", CV_SAVE, addons_md5_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_addons_md5 = CVAR_INIT ("addons_md5", "Name", CV_SAVE, addons_md5_cons_t, NULL);
 
-consvar_t cv_addons_showall = {"addons_showall", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_addons_showall = CVAR_INIT ("addons_showall", "No", CV_SAVE, CV_YesNo, NULL);
 
-consvar_t cv_addons_search_case = {"addons_search_case", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_addons_search_case = CVAR_INIT ("addons_search_case", "No", CV_SAVE, CV_YesNo, NULL);
 
 static CV_PossibleValue_t addons_search_type_cons_t[] = {{0, "Start"}, {1, "Anywhere"}, {0, NULL}};
-consvar_t cv_addons_search_type = {"addons_search_type", "Anywhere", CV_SAVE, addons_search_type_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_addons_search_type = CVAR_INIT ("addons_search_type", "Anywhere", CV_SAVE, addons_search_type_cons_t, NULL);
 
 char menupath[1024];
 size_t menupathindex[menudepth];
diff --git a/src/g_demo.c b/src/g_demo.c
index 0746f1479bf9b7274edb3f2d92b962dcc0f138ea..d3643a3c0bee2f5e44ec3cca820ce8e1b6352431 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -94,7 +94,7 @@ demoghost *ghosts = NULL;
 // DEMO RECORDING
 //
 
-#define DEMOVERSION 0x000d
+#define DEMOVERSION 0x000e
 #define DEMOHEADER  "\xF0" "SRB2Replay" "\x0F"
 
 #define DF_GHOST        0x01 // This demo contains ghost data too!
@@ -345,32 +345,29 @@ void G_WriteGhostTic(mobj_t *ghost)
 	else
 	{
 		// For moving normally:
-		// Store one full byte of movement, plus one byte of fractional movement.
-		INT16 momx = (INT16)((ghost->x-oldghost.x)>>8);
-		INT16 momy = (INT16)((ghost->y-oldghost.y)>>8);
+		fixed_t momx = ghost->x-oldghost.x;
+		fixed_t momy = ghost->y-oldghost.y;
 		if (momx != oldghost.momx
 		|| momy != oldghost.momy)
 		{
 			oldghost.momx = momx;
 			oldghost.momy = momy;
 			ziptic |= GZT_MOMXY;
-			WRITEINT16(demo_p,momx);
-			WRITEINT16(demo_p,momy);
+			WRITEFIXED(demo_p,momx);
+			WRITEFIXED(demo_p,momy);
 		}
-		momx = (INT16)((ghost->z-oldghost.z)>>8);
+		momx = ghost->z-oldghost.z;
 		if (momx != oldghost.momz)
 		{
 			oldghost.momz = momx;
 			ziptic |= GZT_MOMZ;
-			WRITEINT16(demo_p,momx);
+			WRITEFIXED(demo_p,momx);
 		}
 
 		// This SHOULD set oldghost.x/y/z to match ghost->x/y/z
-		// but it keeps the fractional loss of one byte,
-		// so it will hopefully be made up for in future tics.
-		oldghost.x += oldghost.momx<<8;
-		oldghost.y += oldghost.momy<<8;
-		oldghost.z += oldghost.momz<<8;
+		oldghost.x += oldghost.momx;
+		oldghost.y += oldghost.momy;
+		oldghost.z += oldghost.momz;
 	}
 
 	#undef MAXMOM
@@ -456,15 +453,14 @@ void G_WriteGhostTic(mobj_t *ghost)
 			WRITEUINT16(demo_p,oldghost.sprite);
 		if (ghostext.flags & EZT_HEIGHT)
 		{
-			height >>= FRACBITS;
-			WRITEINT16(demo_p, height);
+			WRITEFIXED(demo_p, height);
 		}
 		ghostext.flags = 0;
 	}
 
 	if (ghost->player && ghost->player->followmobj && !(ghost->player->followmobj->sprite == SPR_NULL || (ghost->player->followmobj->flags2 & MF2_DONTDRAW))) // bloats tails runs but what can ya do
 	{
-		INT16 temp;
+		fixed_t temp;
 		UINT8 *followtic_p = demo_p++;
 		UINT8 followtic = 0;
 
@@ -492,12 +488,12 @@ void G_WriteGhostTic(mobj_t *ghost)
 			WRITEFIXED(demo_p,ghost->player->followmobj->scale);
 		}
 
-		temp = (INT16)((ghost->player->followmobj->x-ghost->x)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((ghost->player->followmobj->y-ghost->y)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((ghost->player->followmobj->z-ghost->z)>>8);
-		WRITEINT16(demo_p,temp);
+		temp = ghost->player->followmobj->x-ghost->x;
+		WRITEFIXED(demo_p,temp);
+		temp = ghost->player->followmobj->y-ghost->y;
+		WRITEFIXED(demo_p,temp);
+		temp = ghost->player->followmobj->z-ghost->z;
+		WRITEFIXED(demo_p,temp);
 		if (followtic & FZT_SKIN)
 			WRITEUINT8(demo_p,ghost->player->followmobj->sprite2);
 		WRITEUINT16(demo_p,ghost->player->followmobj->sprite);
@@ -547,11 +543,11 @@ void G_ConsGhostTic(void)
 	{
 		if (ziptic & GZT_MOMXY)
 		{
-			oldghost.momx = READINT16(demo_p)<<8;
-			oldghost.momy = READINT16(demo_p)<<8;
+			oldghost.momx = (demoversion < 0x000e) ? READINT16(demo_p)<<8 : READFIXED(demo_p);
+			oldghost.momy = (demoversion < 0x000e) ? READINT16(demo_p)<<8 : READFIXED(demo_p);
 		}
 		if (ziptic & GZT_MOMZ)
-			oldghost.momz = READINT16(demo_p)<<8;
+			oldghost.momz = (demoversion < 0x000e) ? READINT16(demo_p)<<8 : READFIXED(demo_p);
 		oldghost.x += oldghost.momx;
 		oldghost.y += oldghost.momy;
 		oldghost.z += oldghost.momz;
@@ -613,7 +609,7 @@ void G_ConsGhostTic(void)
 		if (xziptic & EZT_SPRITE)
 			demo_p += sizeof(UINT16);
 		if (xziptic & EZT_HEIGHT)
-			demo_p += sizeof(INT16);
+			demo_p += (demoversion < 0x000e) ? sizeof(INT16) : sizeof(fixed_t);
 	}
 
 	if (ziptic & GZT_FOLLOW)
@@ -627,9 +623,8 @@ void G_ConsGhostTic(void)
 		}
 		if (followtic & FZT_SCALE)
 			demo_p += sizeof(fixed_t);
-		demo_p += sizeof(INT16);
-		demo_p += sizeof(INT16);
-		demo_p += sizeof(INT16);
+		// momx, momy and momz
+		demo_p += (demoversion < 0x000e) ? sizeof(INT16) * 3 : sizeof(fixed_t) * 3;
 		if (followtic & FZT_SKIN)
 			demo_p++;
 		demo_p += sizeof(UINT16);
@@ -697,11 +692,11 @@ void G_GhostTicker(void)
 		{
 			if (ziptic & GZT_MOMXY)
 			{
-				g->oldmo.momx = READINT16(g->p)<<8;
-				g->oldmo.momy = READINT16(g->p)<<8;
+				g->oldmo.momx = (g->version < 0x000e) ? READINT16(g->p)<<8 : READFIXED(g->p);
+				g->oldmo.momy = (g->version < 0x000e) ? READINT16(g->p)<<8 : READFIXED(g->p);
 			}
 			if (ziptic & GZT_MOMZ)
-				g->oldmo.momz = READINT16(g->p)<<8;
+				g->oldmo.momz = (g->version < 0x000e) ? READINT16(g->p)<<8 : READFIXED(g->p);
 			g->oldmo.x += g->oldmo.momx;
 			g->oldmo.y += g->oldmo.momy;
 			g->oldmo.z += g->oldmo.momz;
@@ -846,7 +841,7 @@ void G_GhostTicker(void)
 				g->mo->sprite = READUINT16(g->p);
 			if (xziptic & EZT_HEIGHT)
 			{
-				fixed_t temp = READINT16(g->p)<<FRACBITS;
+				fixed_t temp = (g->version < 0x000e) ? READINT16(g->p)<<FRACBITS : READFIXED(g->p);
 				g->mo->height = FixedMul(temp, g->mo->scale);
 			}
 		}
@@ -905,11 +900,11 @@ void G_GhostTicker(void)
 					P_SetScale(follow, follow->destscale);
 
 				P_UnsetThingPosition(follow);
-				temp = READINT16(g->p)<<8;
+				temp = (g->version < 0x000e) ? READINT16(g->p)<<8 : READFIXED(g->p);
 				follow->x = g->mo->x + temp;
-				temp = READINT16(g->p)<<8;
+				temp = (g->version < 0x000e) ? READINT16(g->p)<<8 : READFIXED(g->p);
 				follow->y = g->mo->y + temp;
-				temp = READINT16(g->p)<<8;
+				temp = (g->version < 0x000e) ? READINT16(g->p)<<8 : READFIXED(g->p);
 				follow->z = g->mo->z + temp;
 				P_SetThingPosition(follow);
 				if (followtic & FZT_SKIN)
@@ -1010,11 +1005,11 @@ void G_ReadMetalTic(mobj_t *metal)
 	{
 		if (ziptic & GZT_MOMXY)
 		{
-			oldmetal.momx = READINT16(metal_p)<<8;
-			oldmetal.momy = READINT16(metal_p)<<8;
+			oldmetal.momx = (metalversion < 0x000e) ? READINT16(metal_p)<<8 : READFIXED(metal_p);
+			oldmetal.momy = (metalversion < 0x000e) ? READINT16(metal_p)<<8 : READFIXED(metal_p);
 		}
 		if (ziptic & GZT_MOMZ)
-			oldmetal.momz = READINT16(metal_p)<<8;
+			oldmetal.momz = (metalversion < 0x000e) ? READINT16(metal_p)<<8 : READFIXED(metal_p);
 		oldmetal.x += oldmetal.momx;
 		oldmetal.y += oldmetal.momy;
 		oldmetal.z += oldmetal.momz;
@@ -1110,7 +1105,7 @@ void G_ReadMetalTic(mobj_t *metal)
 			metal->sprite = READUINT16(metal_p);
 		if (xziptic & EZT_HEIGHT)
 		{
-			fixed_t temp = READINT16(metal_p)<<FRACBITS;
+			fixed_t temp = (metalversion < 0x000e) ? READINT16(metal_p)<<FRACBITS : READFIXED(metal_p);
 			metal->height = FixedMul(temp, metal->scale);
 		}
 	}
@@ -1149,11 +1144,11 @@ void G_ReadMetalTic(mobj_t *metal)
 					P_SetScale(follow, follow->destscale);
 
 				P_UnsetThingPosition(follow);
-				temp = READINT16(metal_p)<<8;
+				temp = (metalversion < 0x000e) ? READINT16(metal_p)<<8 : READFIXED(metal_p);
 				follow->x = metal->x + temp;
-				temp = READINT16(metal_p)<<8;
+				temp = (metalversion < 0x000e) ? READINT16(metal_p)<<8 : READFIXED(metal_p);
 				follow->y = metal->y + temp;
-				temp = READINT16(metal_p)<<8;
+				temp = (metalversion < 0x000e) ? READINT16(metal_p)<<8 : READFIXED(metal_p);
 				follow->z = metal->z + temp;
 				P_SetThingPosition(follow);
 				if (followtic & FZT_SKIN)
@@ -1213,32 +1208,30 @@ void G_WriteMetalTic(mobj_t *metal)
 	else
 	{
 		// For moving normally:
-		// Store one full byte of movement, plus one byte of fractional movement.
-		INT16 momx = (INT16)((metal->x-oldmetal.x)>>8);
-		INT16 momy = (INT16)((metal->y-oldmetal.y)>>8);
+		// Store movement as a fixed value
+		fixed_t momx = metal->x-oldmetal.x;
+		fixed_t momy = metal->y-oldmetal.y;
 		if (momx != oldmetal.momx
 		|| momy != oldmetal.momy)
 		{
 			oldmetal.momx = momx;
 			oldmetal.momy = momy;
 			ziptic |= GZT_MOMXY;
-			WRITEINT16(demo_p,momx);
-			WRITEINT16(demo_p,momy);
+			WRITEFIXED(demo_p,momx);
+			WRITEFIXED(demo_p,momy);
 		}
-		momx = (INT16)((metal->z-oldmetal.z)>>8);
+		momx = metal->z-oldmetal.z;
 		if (momx != oldmetal.momz)
 		{
 			oldmetal.momz = momx;
 			ziptic |= GZT_MOMZ;
-			WRITEINT16(demo_p,momx);
+			WRITEFIXED(demo_p,momx);
 		}
 
 		// This SHOULD set oldmetal.x/y/z to match metal->x/y/z
-		// but it keeps the fractional loss of one byte,
-		// so it will hopefully be made up for in future tics.
-		oldmetal.x += oldmetal.momx<<8;
-		oldmetal.y += oldmetal.momy<<8;
-		oldmetal.z += oldmetal.momz<<8;
+		oldmetal.x += oldmetal.momx;
+		oldmetal.y += oldmetal.momy;
+		oldmetal.z += oldmetal.momz;
 	}
 
 	#undef MAXMOM
@@ -1299,15 +1292,14 @@ void G_WriteMetalTic(mobj_t *metal)
 			WRITEUINT16(demo_p,oldmetal.sprite);
 		if (ghostext.flags & EZT_HEIGHT)
 		{
-			height >>= FRACBITS;
-			WRITEINT16(demo_p, height);
+			WRITEFIXED(demo_p, height);
 		}
 		ghostext.flags = 0;
 	}
 
 	if (metal->player && metal->player->followmobj && !(metal->player->followmobj->sprite == SPR_NULL || (metal->player->followmobj->flags2 & MF2_DONTDRAW)))
 	{
-		INT16 temp;
+		fixed_t temp;
 		UINT8 *followtic_p = demo_p++;
 		UINT8 followtic = 0;
 
@@ -1335,12 +1327,12 @@ void G_WriteMetalTic(mobj_t *metal)
 			WRITEFIXED(demo_p,metal->player->followmobj->scale);
 		}
 
-		temp = (INT16)((metal->player->followmobj->x-metal->x)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((metal->player->followmobj->y-metal->y)>>8);
-		WRITEINT16(demo_p,temp);
-		temp = (INT16)((metal->player->followmobj->z-metal->z)>>8);
-		WRITEINT16(demo_p,temp);
+		temp = metal->player->followmobj->x-metal->x;
+		WRITEFIXED(demo_p,temp);
+		temp = metal->player->followmobj->y-metal->y;
+		WRITEFIXED(demo_p,temp);
+		temp = metal->player->followmobj->z-metal->z;
+		WRITEFIXED(demo_p,temp);
 		if (followtic & FZT_SKIN)
 			WRITEUINT8(demo_p,metal->player->followmobj->sprite2);
 		WRITEUINT16(demo_p,metal->player->followmobj->sprite);
@@ -1480,8 +1472,8 @@ void G_BeginRecording(void)
 	WRITEUINT8(demo_p,player->thrustfactor);
 	WRITEUINT8(demo_p,player->accelstart);
 	WRITEUINT8(demo_p,player->acceleration);
-	WRITEUINT8(demo_p,player->height>>FRACBITS);
-	WRITEUINT8(demo_p,player->spinheight>>FRACBITS);
+	WRITEFIXED(demo_p,player->height);
+	WRITEFIXED(demo_p,player->spinheight);
 	WRITEUINT8(demo_p,player->camerascale>>FRACBITS);
 	WRITEUINT8(demo_p,player->shieldscale>>FRACBITS);
 
@@ -1525,7 +1517,7 @@ void G_BeginRecording(void)
 	}
 
 	// Save netvar data
-	CV_SaveNetVars(&demo_p);
+	CV_SaveDemoVars(&demo_p);
 
 	memset(&oldcmd,0,sizeof(oldcmd));
 	memset(&oldghost,0,sizeof(oldghost));
@@ -1756,6 +1748,9 @@ void G_DoPlayDemo(char *defdemoname)
 	UINT32 randseed, followitem;
 	fixed_t camerascale,shieldscale,actionspd,mindash,maxdash,normalspeed,runspeed,jumpfactor,height,spinheight;
 	char msg[1024];
+#ifdef OLD22DEMOCOMPAT
+	boolean use_old_demo_vars = false;
+#endif
 
 	skin[16] = '\0';
 	color[MAXCOLORNAME] = '\0';
@@ -1815,13 +1810,17 @@ void G_DoPlayDemo(char *defdemoname)
 	demoversion = READUINT16(demo_p);
 	switch(demoversion)
 	{
+	case 0x000d:
 	case DEMOVERSION: // latest always supported
 		cnamelen = MAXCOLORNAME;
 		break;
+#ifdef OLD22DEMOCOMPAT
 	// all that changed between then and now was longer color name
 	case 0x000c:
 		cnamelen = 16;
+		use_old_demo_vars = true;
 		break;
+#endif
 	// too old, cannot support.
 	default:
 		snprintf(msg, 1024, M_GetText("%s is an incompatible replay format and cannot be played.\n"), pdemoname);
@@ -1900,8 +1899,8 @@ void G_DoPlayDemo(char *defdemoname)
 	thrustfactor = READUINT8(demo_p);
 	accelstart = READUINT8(demo_p);
 	acceleration = READUINT8(demo_p);
-	height = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	spinheight = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	height = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	spinheight = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	camerascale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
 	shieldscale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
 	jumpfactor = READFIXED(demo_p);
@@ -1923,7 +1922,12 @@ void G_DoPlayDemo(char *defdemoname)
 	}
 
 	// net var data
-	CV_LoadNetVars(&demo_p);
+#ifdef OLD22DEMOCOMPAT
+	if (use_old_demo_vars)
+		CV_LoadOldDemoVars(&demo_p);
+	else
+#endif
+		CV_LoadDemoVars(&demo_p);
 
 	// Sigh ... it's an empty demo.
 	if (*demo_p == DEMOMARKER)
@@ -1952,9 +1956,7 @@ void G_DoPlayDemo(char *defdemoname)
 	// Set skin
 	SetPlayerSkin(0, skin);
 
-#ifdef HAVE_BLUA
 	LUAh_MapChange(gamemap);
-#endif
 	displayplayer = consoleplayer = 0;
 	memset(playeringame,0,sizeof(playeringame));
 	playeringame[0] = true;
@@ -2061,6 +2063,7 @@ void G_AddGhost(char *defdemoname)
 	ghostversion = READUINT16(p);
 	switch(ghostversion)
 	{
+	case 0x000d:
 	case DEMOVERSION: // latest always supported
 		cnamelen = MAXCOLORNAME;
 		break;
@@ -2143,8 +2146,7 @@ void G_AddGhost(char *defdemoname)
 	p++; // thrustfactor
 	p++; // accelstart
 	p++; // acceleration
-	p++; // height
-	p++; // spinheight
+	p += (ghostversion < 0x000e) ? 2 : 2 * sizeof(fixed_t); // height and spinheight
 	p++; // camerascale
 	p++; // shieldscale
 	p += 4; // jumpfactor
@@ -2156,7 +2158,7 @@ void G_AddGhost(char *defdemoname)
 	count = READUINT16(p);
 	while (count--)
 	{
-		p += 2;
+		SKIPSTRING(p);
 		SKIPSTRING(p);
 		p++;
 	}
@@ -2312,6 +2314,7 @@ void G_DoPlayMetal(void)
 	switch(metalversion)
 	{
 	case DEMOVERSION: // latest always supported
+	case 0x000d: // There are checks wheter the momentum is from older demo versions or not
 	case 0x000c: // all that changed between then and now was longer color name
 		break;
 	// too old, cannot support.
@@ -2370,9 +2373,12 @@ static void WriteDemoChecksum(void)
 static void G_StopDemoRecording(void)
 {
 	boolean saved = false;
-	WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
-	WriteDemoChecksum();
-	saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
+	if (demo_p)
+	{
+		WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
+		WriteDemoChecksum();
+		saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
+	}
 	free(demobuffer);
 	demorecording = false;
 
diff --git a/src/g_game.c b/src/g_game.c
index 405df674e4f2f4e5d77a3a70eedabddf535252e1..a2b91888e769499351b8fc649e379dfc524cd5bc 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -297,100 +297,100 @@ static CV_PossibleValue_t joyaxis_cons_t[] = {{0, "None"},
 // don't mind me putting these here, I was lazy to figure out where else I could put those without blowing up the compiler.
 
 // it automatically becomes compact with 20+ players, but if you like it, I guess you can turn that on!
-consvar_t cv_compactscoreboard= {"compactscoreboard", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_compactscoreboard= CVAR_INIT ("compactscoreboard", "Off", CV_SAVE, CV_OnOff, NULL);
 
 // chat timer thingy
 static CV_PossibleValue_t chattime_cons_t[] = {{5, "MIN"}, {999, "MAX"}, {0, NULL}};
-consvar_t cv_chattime = {"chattime", "8", CV_SAVE, chattime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chattime = CVAR_INIT ("chattime", "8", CV_SAVE, chattime_cons_t, NULL);
 
 // chatwidth
 static CV_PossibleValue_t chatwidth_cons_t[] = {{64, "MIN"}, {300, "MAX"}, {0, NULL}};
-consvar_t cv_chatwidth = {"chatwidth", "150", CV_SAVE, chatwidth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatwidth = CVAR_INIT ("chatwidth", "150", CV_SAVE, chatwidth_cons_t, NULL);
 
 // chatheight
 static CV_PossibleValue_t chatheight_cons_t[] = {{6, "MIN"}, {22, "MAX"}, {0, NULL}};
-consvar_t cv_chatheight= {"chatheight", "8", CV_SAVE, chatheight_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatheight= CVAR_INIT ("chatheight", "8", CV_SAVE, chatheight_cons_t, NULL);
 
 // chat notifications (do you want to hear beeps? I'd understand if you didn't.)
-consvar_t cv_chatnotifications= {"chatnotifications", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatnotifications= CVAR_INIT ("chatnotifications", "On", CV_SAVE, CV_OnOff, NULL);
 
 // chat spam protection (why would you want to disable that???)
-consvar_t cv_chatspamprotection= {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatspamprotection= CVAR_INIT ("chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL);
 
 // minichat text background
-consvar_t cv_chatbacktint = {"chatbacktint", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chatbacktint = CVAR_INIT ("chatbacktint", "On", CV_SAVE, CV_OnOff, NULL);
 
 // old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
 static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Window"}, {1, "Console"}, {2, "Window (Hidden)"}, {0, NULL}};
-consvar_t cv_consolechat = {"chatmode", "Window", CV_SAVE, consolechat_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_consolechat = CVAR_INIT ("chatmode", "Window", CV_SAVE, consolechat_cons_t, NULL);
 
 // Pause game upon window losing focus
-consvar_t cv_pauseifunfocused = {"pauseifunfocused", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_crosshair = {"crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_crosshair2 = {"crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_invertmouse = {"invertmouse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_alwaysfreelook = {"alwaysmlook", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_invertmouse2 = {"invertmouse2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_alwaysfreelook2 = {"alwaysmlook2", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_chasefreelook = {"chasemlook", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_chasefreelook2 = {"chasemlook2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousemove = {"mousemove", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousemove2 = {"mousemove2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_pauseifunfocused = CVAR_INIT ("pauseifunfocused", "Yes", CV_SAVE, CV_YesNo, NULL);
+
+consvar_t cv_crosshair = CVAR_INIT ("crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL);
+consvar_t cv_crosshair2 = CVAR_INIT ("crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL);
+consvar_t cv_invertmouse = CVAR_INIT ("invertmouse", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_alwaysfreelook = CVAR_INIT ("alwaysmlook", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_invertmouse2 = CVAR_INIT ("invertmouse2", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_alwaysfreelook2 = CVAR_INIT ("alwaysmlook2", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_chasefreelook = CVAR_INIT ("chasemlook", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_chasefreelook2 = CVAR_INIT ("chasemlook2", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_mousemove = CVAR_INIT ("mousemove", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_mousemove2 = CVAR_INIT ("mousemove2", "Off", CV_SAVE, CV_OnOff, NULL);
 
 // previously "analog", "analog2", "useranalog", and "useranalog2", invalidating 2.1-era copies of config.cfg
 // changed because it'd be nice to see people try out our actually good controls with gamepads now autobrake exists
 consvar_t cv_analog[2] = {
-	{"sessionanalog", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog_OnChange, 0, NULL, NULL, 0, 0, NULL},
-	{"sessionanalog2", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog2_OnChange, 0, NULL, NULL, 0, 0, NULL}
+	CVAR_INIT ("sessionanalog", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog_OnChange),
+	CVAR_INIT ("sessionanalog2", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog2_OnChange),
 };
 consvar_t cv_useranalog[2] = {
-	{"configanalog", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog_OnChange, 0, NULL, NULL, 0, 0, NULL},
-	{"configanalog2", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog2_OnChange, 0, NULL, NULL, 0, 0, NULL}
+	CVAR_INIT ("configanalog", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog_OnChange),
+	CVAR_INIT ("configanalog2", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog2_OnChange),
 };
 
 // deez New User eXperiences
 static CV_PossibleValue_t directionchar_cons_t[] = {{0, "Camera"}, {1, "Movement"}, {2, "Simple Locked"}, {0, NULL}};
 consvar_t cv_directionchar[2] = {
-	{"directionchar", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar_OnChange, 0, NULL, NULL, 0, 0, NULL},
-	{"directionchar2", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar2_OnChange, 0, NULL, NULL, 0, 0, NULL}
+	CVAR_INIT ("directionchar", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar_OnChange),
+	CVAR_INIT ("directionchar2", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar2_OnChange),
 };
-consvar_t cv_autobrake = {"autobrake", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_autobrake2 = {"autobrake2", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_autobrake = CVAR_INIT ("autobrake", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake_OnChange);
+consvar_t cv_autobrake2 = CVAR_INIT ("autobrake2", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake2_OnChange);
 
 // hi here's some new controls
 static CV_PossibleValue_t zerotoone_cons_t[] = {{0, "MIN"}, {FRACUNIT, "MAX"}, {0, NULL}};
 consvar_t cv_cam_shiftfacing[2] = {
-	{"cam_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 consvar_t cv_cam_turnfacing[2] = {
-	{"cam_turnfacingchar", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_turnfacingchar", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_turnfacingchar", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_turnfacingchar", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 consvar_t cv_cam_turnfacingability[2] = {
-	{"cam_turnfacingability", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_turnfacingability", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_turnfacingability", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_turnfacingability", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 consvar_t cv_cam_turnfacingspindash[2] = {
-	{"cam_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 consvar_t cv_cam_turnfacinginput[2] = {
-	{"cam_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
+	CVAR_INIT ("cam2_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL),
 };
 
 static CV_PossibleValue_t centertoggle_cons_t[] = {{0, "Hold"}, {1, "Toggle"}, {2, "Sticky Hold"}, {0, NULL}};
 consvar_t cv_cam_centertoggle[2] = {
-	{"cam_centertoggle", "Hold", CV_SAVE, centertoggle_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_centertoggle", "Hold", CV_SAVE, centertoggle_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_centertoggle", "Hold", CV_SAVE, centertoggle_cons_t, NULL),
+	CVAR_INIT ("cam2_centertoggle", "Hold", CV_SAVE, centertoggle_cons_t, NULL),
 };
 
 static CV_PossibleValue_t lockedinput_cons_t[] = {{0, "Strafe"}, {1, "Turn"}, {0, NULL}};
 consvar_t cv_cam_lockedinput[2] = {
-	{"cam_lockedinput", "Strafe", CV_SAVE, lockedinput_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_lockedinput", "Strafe", CV_SAVE, lockedinput_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_lockedinput", "Strafe", CV_SAVE, lockedinput_cons_t, NULL),
+	CVAR_INIT ("cam2_lockedinput", "Strafe", CV_SAVE, lockedinput_cons_t, NULL),
 };
 
 static CV_PossibleValue_t lockedassist_cons_t[] = {
@@ -402,8 +402,8 @@ static CV_PossibleValue_t lockedassist_cons_t[] = {
 	{0, NULL}
 };
 consvar_t cv_cam_lockonboss[2] = {
-	{"cam_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
-	{"cam2_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	CVAR_INIT ("cam_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL),
+	CVAR_INIT ("cam2_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL),
 };
 
 typedef enum
@@ -422,36 +422,36 @@ typedef enum
 	AXISFIRENORMAL,
 } axis_input_e;
 
-consvar_t cv_turnaxis = {"joyaxis_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_moveaxis = {"joyaxis_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_sideaxis = {"joyaxis_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_lookaxis = {"joyaxis_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_jumpaxis = {"joyaxis_jump", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_spinaxis = {"joyaxis_spin", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_fireaxis = {"joyaxis_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_firenaxis = {"joyaxis_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_deadzone = {"joy_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_digitaldeadzone = {"joy_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_turnaxis2 = {"joyaxis2_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_moveaxis2 = {"joyaxis2_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_sideaxis2 = {"joyaxis2_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_lookaxis2 = {"joyaxis2_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_jumpaxis2 = {"joyaxis2_jump", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_spinaxis2 = {"joyaxis2_spin", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_fireaxis2 = {"joyaxis2_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_firenaxis2 = {"joyaxis2_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_deadzone2 = {"joy_deadzone2", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_digitaldeadzone2 = {"joy_digdeadzone2", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-#ifdef SEENAMES
+consvar_t cv_turnaxis = CVAR_INIT ("joyaxis_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_moveaxis = CVAR_INIT ("joyaxis_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_sideaxis = CVAR_INIT ("joyaxis_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_lookaxis = CVAR_INIT ("joyaxis_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_jumpaxis = CVAR_INIT ("joyaxis_jump", "None", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_spinaxis = CVAR_INIT ("joyaxis_spin", "None", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_fireaxis = CVAR_INIT ("joyaxis_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_firenaxis = CVAR_INIT ("joyaxis_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_deadzone = CVAR_INIT ("joy_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
+consvar_t cv_digitaldeadzone = CVAR_INIT ("joy_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
+
+consvar_t cv_turnaxis2 = CVAR_INIT ("joyaxis2_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_moveaxis2 = CVAR_INIT ("joyaxis2_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_sideaxis2 = CVAR_INIT ("joyaxis2_side", "X-Axis", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_lookaxis2 = CVAR_INIT ("joyaxis2_look", "Y-Rudder-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_jumpaxis2 = CVAR_INIT ("joyaxis2_jump", "None", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_spinaxis2 = CVAR_INIT ("joyaxis2_spin", "None", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_fireaxis2 = CVAR_INIT ("joyaxis2_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_firenaxis2 = CVAR_INIT ("joyaxis2_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL);
+consvar_t cv_deadzone2 = CVAR_INIT ("joy_deadzone2", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
+consvar_t cv_digitaldeadzone2 = CVAR_INIT ("joy_digdeadzone2", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL);
+
 player_t *seenplayer; // player we're aiming at right now
-#endif
 
 // now automatically allocated in D_RegisterClientCommands
 // so that it doesn't have to be updated depending on the value of MAXPLAYERS
 char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
 
+INT32 player_name_changes[MAXPLAYERS];
+
 INT16 rw_maximums[NUM_WEAPONS] =
 {
 	800, // MAX_INFINITY
@@ -1340,8 +1340,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 
 	// use with any button/key
 	axis = PlayerJoyAxis(ssplayer, AXISSPIN);
-	if (PLAYERINPUTDOWN(ssplayer, gc_use) || (usejoystick && axis > 0))
-		cmd->buttons |= BT_USE;
+	if (PLAYERINPUTDOWN(ssplayer, gc_spin) || (usejoystick && axis > 0))
+		cmd->buttons |= BT_SPIN;
 
 	// Centerview can be a toggle in simple mode!
 	{
@@ -1675,6 +1675,26 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 		}
 	}
 
+	// At this point, cmd doesn't contain the final angle yet,
+	// So we need to temporarily transform it so Lua scripters
+	// don't need to handle it differently than in other hooks.
+	if (addedtogame && gamestate == GS_LEVEL)
+	{
+		INT16 extra = ticcmd_oldangleturn[forplayer] - player->oldrelangleturn;
+		INT16 origangle = cmd->angleturn;
+		INT16 orighookangle = (INT16)(origangle + player->angleturn + extra);
+		INT16 origaiming = cmd->aiming;
+
+		cmd->angleturn = orighookangle;
+
+		LUAh_PlayerCmd(player, cmd);
+
+		extra = cmd->angleturn - orighookangle;
+		cmd->angleturn = origangle + extra;
+		*myangle += extra << 16;
+		*myaiming += (cmd->aiming - origaiming) << 16;
+	}
+
 	//Reset away view if a command is given.
 	if (ssplayer == 1 && (cmd->forwardmove || cmd->sidemove || cmd->buttons)
 		&& displayplayer != consoleplayer)
@@ -1839,7 +1859,7 @@ void G_DoLoadLevel(player_t *player, boolean addworld, boolean resetplayer)
 		player->playerstate = PST_REBORN;
 
 	// Setup the level.
-	if (!P_LoadLevel(player, addworld, false)) // this never returns false?
+	if (!P_LoadLevel(player, addworld, false, false)) // this never returns false?
 	{
 		// fail so reset game stuff
 		Command_ExitGame_f();
@@ -2037,7 +2057,9 @@ boolean G_Responder(event_t *ev)
 		if (F_CreditResponder(ev))
 		{
 			// Skip credits for everyone
-			if (!netgame || server || IsPlayerAdmin(consoleplayer))
+			if (! netgame)
+				F_StartGameEvaluation();
+			else if (server || IsPlayerAdmin(consoleplayer))
 				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
 			return true;
 		}
@@ -2232,8 +2254,35 @@ void G_Ticker(boolean run)
 				// Costs a life to retry ... unless the player in question is dead already, or you haven't even touched the first starpost in marathon run.
 				if (marathonmode && gamemap == spmarathon_start && !players[consoleplayer].starposttime)
 				{
+					player_t *p = &players[consoleplayer];
 					marathonmode |= MA_INIT;
 					marathontime = 0;
+
+					numgameovers = tokenlist = token = 0;
+					countdown = countdown2 = exitfadestarted = 0;
+
+					p->playerstate = PST_REBORN;
+					p->starpostx = p->starposty = p->starpostz = 0;
+
+					p->lives = startinglivesbalance[0];
+					p->continues = 1;
+
+					p->score = 0;
+
+					// The latter two should clear by themselves, but just in case
+					p->pflags &= ~(PF_TAGIT|PF_GAMETYPEOVER|PF_FULLSTASIS);
+
+					// Clear cheatcodes too, just in case.
+					p->pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS);
+
+					p->xtralife = 0;
+
+					// Reset unlockable triggers
+					unlocktriggers = 0;
+
+					emeralds = 0;
+
+					memset(&luabanks, 0, sizeof(luabanks));
 				}
 				else if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES)
 					players[consoleplayer].lives -= 1;
@@ -2266,11 +2315,21 @@ void G_Ticker(boolean run)
 	{
 		if (playeringame[i])
 		{
+			INT16 received;
+
 			G_CopyTiccmd(&players[i].cmd, &netcmds[buf][i], 1);
 
+			received = (players[i].cmd.angleturn & TICCMD_RECEIVED);
+
 			players[i].angleturn += players[i].cmd.angleturn - players[i].oldrelangleturn;
 			players[i].oldrelangleturn = players[i].cmd.angleturn;
-			players[i].cmd.angleturn = players[i].angleturn;
+			if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
+				P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
+			else
+				players[i].cmd.angleturn = players[i].angleturn;
+
+			players[i].cmd.angleturn &= ~TICCMD_RECEIVED;
+			players[i].cmd.angleturn |= received;
 		}
 	}
 
@@ -2364,6 +2423,11 @@ void G_Ticker(boolean run)
 
 		if (camtoggledelay2)
 			camtoggledelay2--;
+
+		if (gametic % NAMECHANGERATE == 0)
+		{
+			memset(player_name_changes, 0, sizeof player_name_changes);
+		}
 	}
 }
 
@@ -2595,7 +2659,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	p->spheres = spheres;
 
 	// Don't do anything immediately
-	p->pflags |= PF_USEDOWN;
+	p->pflags |= PF_SPINDOWN;
 	p->pflags |= PF_ATTACKDOWN;
 	p->pflags |= PF_JUMPDOWN;
 
@@ -3533,7 +3597,7 @@ INT32 G_GetGametypeByName(const char *gametypestr)
 //
 boolean G_IsSpecialStage(INT32 mapnum)
 {
-	if (gametype != GT_COOP || modeattacking == ATTACKING_RECORD)
+	if (modeattacking == ATTACKING_RECORD)
 		return false;
 	if (mapnum >= sstage_start && mapnum <= sstage_end)
 		return true;
@@ -3809,7 +3873,7 @@ void G_SetNextMap(boolean usespec, boolean inspec)
 	// a map of the proper gametype -- skip levels that don't support
 	// the current gametype. (Helps avoid playing boss levels in Race,
 	// for instance).
-	if (!inspec)
+	if (!inspec || nextmapoverride)
 	{
 		if (nextmap >= 0 && nextmap < NUMMAPS)
 		{
@@ -3861,7 +3925,8 @@ void G_SetNextMap(boolean usespec, boolean inspec)
 		if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1)
 			I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1);
 
-		lastmap = nextmap; // Remember last map for when you come out of the special stage.
+		if (!inspec)
+			lastmap = nextmap; // Remember last map for when you come out of the special stage.
 	}
 
 	if ((gottoken = (usespec && token)))
@@ -3882,7 +3947,7 @@ void G_SetNextMap(boolean usespec, boolean inspec)
 		}
 	}
 
-	if (inspec && !gottoken)
+	if (inspec && !gottoken && !nextmapoverride)
 		nextmap = lastmap; // Exiting from a special stage? Go back to the game. Tails 08-11-2001
 }
 
@@ -4542,7 +4607,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum)
 		{
 			UINT32 writetime = marathontime;
 			if (!(marathonmode & MA_INGAME))
-				marathontime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map
+				writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map
 			WRITEUINT32(save_p, writetime);
 			WRITEUINT8(save_p, (marathonmode & ~MA_INIT));
 		}
diff --git a/src/g_game.h b/src/g_game.h
index f77630c4014cb0481a3600d8b5141a7032d576e2..dd57487870f8f350bd8ded3ae4797db16a6c2760 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -18,16 +18,16 @@
 #include "doomstat.h"
 #include "d_event.h"
 #include "g_demo.h"
+#include "m_cheat.h" // objectplacing
 
 extern char gamedatafilename[64];
 extern char timeattackfolder[64];
 extern char customversionstring[32];
 #define GAMEDATASIZE (4*8192)
 
-#ifdef SEENAMES
 extern player_t *seenplayer;
-#endif
-extern char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
+extern char  player_names[MAXPLAYERS][MAXPLAYERNAME+1];
+extern INT32 player_name_changes[MAXPLAYERS];
 
 extern player_t players[MAXPLAYERS];
 extern boolean playeringame[MAXPLAYERS];
@@ -64,7 +64,7 @@ typedef enum {
 	CS_STANDARD,
 	CS_SIMPLE = CS_LMAOGALOG|CS_STANDARD,
 } controlstyle_e;
-#define G_ControlStyle(ssplayer) (cv_directionchar[(ssplayer)-1].value == 3 ? CS_LMAOGALOG : ((cv_analog[(ssplayer)-1].value ? CS_LMAOGALOG : 0) | (cv_directionchar[(ssplayer)-1].value ? CS_STANDARD : 0)))
+#define G_ControlStyle(ssplayer) (cv_directionchar[(ssplayer)-1].value == 3 ? CS_LMAOGALOG : ((!objectplacing && cv_analog[(ssplayer)-1].value ? CS_LMAOGALOG : 0) | (cv_directionchar[(ssplayer)-1].value ? CS_STANDARD : 0)))
 #define P_ControlStyle(player) ((((player)->pflags & PF_ANALOGMODE) ? CS_LMAOGALOG : 0) | (((player)->pflags & PF_DIRECTIONCHAR) ? CS_STANDARD : 0))
 
 extern consvar_t cv_autobrake, cv_autobrake2;
diff --git a/src/g_input.c b/src/g_input.c
index ecce4d83c8444457d0db4f632e5921959c44685e..d3c21e774c4359d4f2433379dcbbe6e7fdc79436 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -25,11 +25,11 @@ static CV_PossibleValue_t mousesens_cons_t[] = {{1, "MIN"}, {MAXMOUSESENSITIVITY
 static CV_PossibleValue_t onecontrolperkey_cons_t[] = {{1, "One"}, {2, "Several"}, {0, NULL}};
 
 // mouse values are used once
-consvar_t cv_mousesens = {"mousesens", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousesens2 = {"mousesens2", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens = {"mouseysens", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens2 = {"mouseysens2", "20", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_controlperkey = {"controlperkey", "One", CV_SAVE, onecontrolperkey_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousesens = CVAR_INIT ("mousesens", "20", CV_SAVE, mousesens_cons_t, NULL);
+consvar_t cv_mousesens2 = CVAR_INIT ("mousesens2", "20", CV_SAVE, mousesens_cons_t, NULL);
+consvar_t cv_mouseysens = CVAR_INIT ("mouseysens", "20", CV_SAVE, mousesens_cons_t, NULL);
+consvar_t cv_mouseysens2 = CVAR_INIT ("mouseysens2", "20", CV_SAVE, mousesens_cons_t, NULL);
+consvar_t cv_controlperkey = CVAR_INIT ("controlperkey", "One", CV_SAVE, onecontrolperkey_cons_t, NULL);
 
 INT32 mousex, mousey;
 INT32 mlooky; // like mousey but with a custom sensitivity for mlook
@@ -57,13 +57,13 @@ const INT32 gcl_tutorial_check[num_gcl_tutorial_check] = {
 const INT32 gcl_tutorial_used[num_gcl_tutorial_used] = {
 	gc_forward, gc_backward, gc_strafeleft, gc_straferight,
 	gc_turnleft, gc_turnright,
-	gc_jump, gc_use
+	gc_jump, gc_spin
 };
 
 const INT32 gcl_tutorial_full[num_gcl_tutorial_full] = {
 	gc_forward, gc_backward, gc_strafeleft, gc_straferight,
 	gc_lookup, gc_lookdown, gc_turnleft, gc_turnright, gc_centerview,
-	gc_jump, gc_use,
+	gc_jump, gc_spin,
 	gc_fire, gc_firenormal
 };
 
@@ -82,10 +82,10 @@ const INT32 gcl_movement_camera[num_gcl_movement_camera] = {
 
 const INT32 gcl_jump[num_gcl_jump] = { gc_jump };
 
-const INT32 gcl_use[num_gcl_use] = { gc_use };
+const INT32 gcl_spin[num_gcl_spin] = { gc_spin };
 
-const INT32 gcl_jump_use[num_gcl_jump_use] = {
-	gc_jump, gc_use
+const INT32 gcl_jump_spin[num_gcl_jump_spin] = {
+	gc_jump, gc_spin
 };
 
 typedef struct
@@ -583,7 +583,7 @@ static const char *gamecontrolname[num_gamecontrols] =
 	"fire",
 	"firenormal",
 	"tossflag",
-	"use",
+	"spin",
 	"camtoggle",
 	"camreset",
 	"lookup",
@@ -692,7 +692,7 @@ void G_DefineDefaultControls(void)
 	gamecontroldefault[gcs_fps][gc_turnright  ][0] = KEY_RIGHTARROW;
 	gamecontroldefault[gcs_fps][gc_centerview ][0] = KEY_END;
 	gamecontroldefault[gcs_fps][gc_jump       ][0] = KEY_SPACE;
-	gamecontroldefault[gcs_fps][gc_use        ][0] = KEY_LSHIFT;
+	gamecontroldefault[gcs_fps][gc_spin       ][0] = KEY_LSHIFT;
 	gamecontroldefault[gcs_fps][gc_fire       ][0] = KEY_RCTRL;
 	gamecontroldefault[gcs_fps][gc_fire       ][1] = KEY_MOUSE1+0;
 	gamecontroldefault[gcs_fps][gc_firenormal ][0] = 'c';
@@ -708,7 +708,7 @@ void G_DefineDefaultControls(void)
 	gamecontroldefault[gcs_platform][gc_turnright  ][0] = KEY_RIGHTARROW;
 	gamecontroldefault[gcs_platform][gc_centerview ][0] = KEY_END;
 	gamecontroldefault[gcs_platform][gc_jump       ][0] = KEY_SPACE;
-	gamecontroldefault[gcs_platform][gc_use        ][0] = KEY_LSHIFT;
+	gamecontroldefault[gcs_platform][gc_spin       ][0] = KEY_LSHIFT;
 	gamecontroldefault[gcs_platform][gc_fire       ][0] = 's';
 	gamecontroldefault[gcs_platform][gc_fire       ][1] = KEY_MOUSE1+0;
 	gamecontroldefault[gcs_platform][gc_firenormal ][0] = 'w';
@@ -743,7 +743,7 @@ void G_DefineDefaultControls(void)
 		gamecontroldefault[i][gc_weaponnext ][1] = KEY_JOY1+1; // B
 		gamecontroldefault[i][gc_weaponprev ][1] = KEY_JOY1+2; // X
 		gamecontroldefault[i][gc_tossflag   ][1] = KEY_JOY1+0; // A
-		gamecontroldefault[i][gc_use        ][1] = KEY_JOY1+4; // LB
+		gamecontroldefault[i][gc_spin       ][1] = KEY_JOY1+4; // LB
 		gamecontroldefault[i][gc_camtoggle  ][1] = KEY_HAT1+0; // D-Pad Up
 		gamecontroldefault[i][gc_camreset   ][1] = KEY_JOY1+3; // Y
 		gamecontroldefault[i][gc_centerview ][1] = KEY_JOY1+9; // Right Stick
@@ -758,7 +758,7 @@ void G_DefineDefaultControls(void)
 		gamecontrolbisdefault[i][gc_weaponnext][0] = KEY_2JOY1+1; // B
 		gamecontrolbisdefault[i][gc_weaponprev][0] = KEY_2JOY1+2; // X
 		gamecontrolbisdefault[i][gc_tossflag  ][0] = KEY_2JOY1+0; // A
-		gamecontrolbisdefault[i][gc_use       ][0] = KEY_2JOY1+4; // LB
+		gamecontrolbisdefault[i][gc_spin      ][0] = KEY_2JOY1+4; // LB
 		gamecontrolbisdefault[i][gc_camreset  ][0] = KEY_2JOY1+3; // Y
 		gamecontrolbisdefault[i][gc_centerview][0] = KEY_2JOY1+9; // Right Stick
 		gamecontrolbisdefault[i][gc_jump      ][0] = KEY_2JOY1+5; // RB
@@ -890,7 +890,7 @@ static INT32 G_FilterKeyByVersion(INT32 numctrl, INT32 keyidx, INT32 player, INT
 
 	if (GETMAJOREXECVERSION(cv_execversion.value) < 27 && ( // v2.1.22
 		numctrl == gc_weaponnext || numctrl == gc_weaponprev || numctrl == gc_tossflag ||
-		numctrl == gc_use || numctrl == gc_camreset || numctrl == gc_jump ||
+		numctrl == gc_spin || numctrl == gc_camreset || numctrl == gc_jump ||
 		numctrl == gc_pause || numctrl == gc_systemmenu || numctrl == gc_camtoggle ||
 		numctrl == gc_screenshot || numctrl == gc_talkkey || numctrl == gc_scores ||
 		numctrl == gc_centerview
@@ -996,7 +996,9 @@ static void setcontrol(INT32 (*gc)[2])
 	INT32 player = ((void*)gc == (void*)&gamecontrolbis ? 1 : 0);
 	boolean nestedoverride = false;
 
-	namectrl = COM_Argv(1);
+	// Update me for 2.3
+	namectrl = (stricmp(COM_Argv(1), "use")) ? COM_Argv(1) : "spin";
+
 	for (numctrl = 0; numctrl < num_gamecontrols && stricmp(namectrl, gamecontrolname[numctrl]);
 		numctrl++)
 		;
diff --git a/src/g_input.h b/src/g_input.h
index a7484c7adba4be4ad6a996b60677a1135f471e22..ce38f6ba9d68a623b880361d868aeebdd18eb135 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -80,7 +80,7 @@ typedef enum
 	gc_fire,
 	gc_firenormal,
 	gc_tossflag,
-	gc_use,
+	gc_spin,
 	gc_camtoggle,
 	gc_camreset,
 	gc_lookup,
@@ -141,8 +141,8 @@ extern INT32 gamecontrolbisdefault[num_gamecontrolschemes][num_gamecontrols][2];
 #define num_gcl_camera 2
 #define num_gcl_movement_camera 6
 #define num_gcl_jump 1
-#define num_gcl_use 1
-#define num_gcl_jump_use 2
+#define num_gcl_spin 1
+#define num_gcl_jump_spin 2
 
 extern const INT32 gcl_tutorial_check[num_gcl_tutorial_check];
 extern const INT32 gcl_tutorial_used[num_gcl_tutorial_used];
@@ -151,8 +151,8 @@ extern const INT32 gcl_movement[num_gcl_movement];
 extern const INT32 gcl_camera[num_gcl_camera];
 extern const INT32 gcl_movement_camera[num_gcl_movement_camera];
 extern const INT32 gcl_jump[num_gcl_jump];
-extern const INT32 gcl_use[num_gcl_use];
-extern const INT32 gcl_jump_use[num_gcl_jump_use];
+extern const INT32 gcl_spin[num_gcl_spin];
+extern const INT32 gcl_jump_spin[num_gcl_jump_spin];
 
 // peace to my little coder fingers!
 // check a gamecontrol being active or not
diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c
index 5c5379f6ddee431a9e3eb0948898a51838d3b099..fb3417158a76a53abd38698da07425541ffddb02 100644
--- a/src/hardware/hw_batching.c
+++ b/src/hardware/hw_batching.c
@@ -1,7 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 2020 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -235,13 +234,13 @@ void HWR_RenderBatches(void)
 	currently_batching = false;// no longer collecting batches
 	if (!polygonArraySize)
 	{
-		rs_hw_numpolys = rs_hw_numcalls = rs_hw_numshaders = rs_hw_numtextures = rs_hw_numpolyflags = rs_hw_numcolors = 0;
+		ps_hw_numpolys = ps_hw_numcalls = ps_hw_numshaders = ps_hw_numtextures = ps_hw_numpolyflags = ps_hw_numcolors = 0;
 		return;// nothing to draw
 	}
 	// init stats vars
-	rs_hw_numpolys = polygonArraySize;
-	rs_hw_numcalls = rs_hw_numverts = 0;
-	rs_hw_numshaders = rs_hw_numtextures = rs_hw_numpolyflags = rs_hw_numcolors = 1;
+	ps_hw_numpolys = polygonArraySize;
+	ps_hw_numcalls = ps_hw_numverts = 0;
+	ps_hw_numshaders = ps_hw_numtextures = ps_hw_numpolyflags = ps_hw_numcolors = 1;
 	// init polygonIndexArray
 	for (i = 0; i < polygonArraySize; i++)
 	{
@@ -249,12 +248,12 @@ void HWR_RenderBatches(void)
 	}
 
 	// sort polygons
-	rs_hw_batchsorttime = I_GetTimeMicros();
-	if (cv_grshaders.value && gr_shadersavailable)
+	ps_hw_batchsorttime = I_GetPreciseTime();
+	if (cv_glshaders.value && gl_shadersavailable)
 		qsort(polygonIndexArray, polygonArraySize, sizeof(unsigned int), comparePolygons);
 	else
 		qsort(polygonIndexArray, polygonArraySize, sizeof(unsigned int), comparePolygonsNoShaders);
-	rs_hw_batchsorttime = I_GetTimeMicros() - rs_hw_batchsorttime;
+	ps_hw_batchsorttime = I_GetPreciseTime() - ps_hw_batchsorttime;
 	// sort order
 	// 1. shader
 	// 2. texture
@@ -262,7 +261,7 @@ void HWR_RenderBatches(void)
 	// 4. colors + light level
 	// not sure about what order of the last 2 should be, or if it even matters
 
-	rs_hw_batchdrawtime = I_GetTimeMicros();
+	ps_hw_batchdrawtime = I_GetPreciseTime();
 
 	currentShader = polygonArray[polygonIndexArray[0]].shader;
 	currentTexture = polygonArray[polygonIndexArray[0]].texture;
@@ -272,8 +271,8 @@ void HWR_RenderBatches(void)
 	// and a color array could replace the color calls.
 
 	// set state for first batch
-	
-	if (cv_grshaders.value && gr_shadersavailable)
+
+	if (cv_glshaders.value && gl_shadersavailable)
 	{
 		HWD.pfnSetShader(currentShader);
 	}
@@ -355,7 +354,7 @@ void HWR_RenderBatches(void)
 			nextSurfaceInfo = polygonArray[nextIndex].surf;
 			if (nextPolyFlags & PF_NoTexture)
 				nextTexture = 0;
-			if (currentShader != nextShader && cv_grshaders.value && gr_shadersavailable)
+			if (currentShader != nextShader && cv_glshaders.value && gl_shadersavailable)
 			{
 				changeState = true;
 				changeShader = true;
@@ -370,7 +369,7 @@ void HWR_RenderBatches(void)
 				changeState = true;
 				changePolyFlags = true;
 			}
-			if (cv_grshaders.value && gr_shadersavailable)
+			if (cv_glshaders.value && gl_shadersavailable)
 			{
 				if (currentSurfaceInfo.PolyColor.rgba != nextSurfaceInfo.PolyColor.rgba ||
 					currentSurfaceInfo.TintColor.rgba != nextSurfaceInfo.TintColor.rgba ||
@@ -398,8 +397,8 @@ void HWR_RenderBatches(void)
 			// execute draw call
             HWD.pfnDrawIndexedTriangles(&currentSurfaceInfo, finalVertexArray, finalIndexWritePos, currentPolyFlags, finalVertexIndexArray);
 			// update stats
-			rs_hw_numcalls++;
-			rs_hw_numverts += finalIndexWritePos;
+			ps_hw_numcalls++;
+			ps_hw_numverts += finalIndexWritePos;
 			// reset write positions
 			finalVertexWritePos = 0;
 			finalIndexWritePos = 0;
@@ -416,7 +415,7 @@ void HWR_RenderBatches(void)
 			currentShader = nextShader;
 			changeShader = false;
 
-			rs_hw_numshaders++;
+			ps_hw_numshaders++;
 		}
 		if (changeTexture)
 		{
@@ -425,21 +424,21 @@ void HWR_RenderBatches(void)
 			currentTexture = nextTexture;
 			changeTexture = false;
 
-			rs_hw_numtextures++;
+			ps_hw_numtextures++;
 		}
 		if (changePolyFlags)
 		{
 			currentPolyFlags = nextPolyFlags;
 			changePolyFlags = false;
 
-			rs_hw_numpolyflags++;
+			ps_hw_numpolyflags++;
 		}
 		if (changeSurfaceInfo)
 		{
 			currentSurfaceInfo = nextSurfaceInfo;
 			changeSurfaceInfo = false;
 
-			rs_hw_numcolors++;
+			ps_hw_numcolors++;
 		}
 		// and that should be it?
 	}
@@ -447,7 +446,7 @@ void HWR_RenderBatches(void)
 	polygonArraySize = 0;
 	unsortedVertexArraySize = 0;
 
-	rs_hw_batchdrawtime = I_GetTimeMicros() - rs_hw_batchdrawtime;
+	ps_hw_batchdrawtime = I_GetPreciseTime() - ps_hw_batchdrawtime;
 }
 
 
diff --git a/src/hardware/hw_batching.h b/src/hardware/hw_batching.h
index 7c108a4bd2a18e2cee24487afe8c79b4b856c28b..42291a0dfd261731ce6d40c24e06de63260dc132 100644
--- a/src/hardware/hw_batching.h
+++ b/src/hardware/hw_batching.h
@@ -1,7 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C) 2020 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -17,7 +16,7 @@
 #include "hw_data.h"
 #include "hw_drv.h"
 
-typedef struct 
+typedef struct
 {
 	FSurfaceInfo surf;// surf also has its own polyflags for some reason, but it seems unused
 	unsigned int vertsIndex;// location of verts in unsortedVertexArray
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 6987e9d01c6adaab73016fa8becd403b2d9a3dd7..4db69ff8b130b00eb46c403f8a47a8c86e535947 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -61,17 +61,17 @@ static INT32 totalsubsecpolys = 0;
 /// \todo check out how much is used
 static size_t POLYPOOLSIZE = 1024000;
 
-static UINT8 *gr_polypool = NULL;
-static UINT8 *gr_ppcurrent;
-static size_t gr_ppfree;
+static UINT8 *gl_polypool = NULL;
+static UINT8 *gl_ppcurrent;
+static size_t gl_ppfree;
 #endif
 
 // only between levels, clear poly pool
 static void HWR_ClearPolys(void)
 {
 #ifndef ZPLANALLOC
-	gr_ppcurrent = gr_polypool;
-	gr_ppfree = POLYPOOLSIZE;
+	gl_ppcurrent = gl_polypool;
+	gl_ppfree = POLYPOOLSIZE;
 #endif
 }
 
@@ -86,8 +86,8 @@ void HWR_InitPolyPool(void)
 		POLYPOOLSIZE = atoi(myargv[pnum+1])*1024; // (in kb)
 
 	CONS_Debug(DBG_RENDER, "HWR_InitPolyPool(): allocating %d bytes\n", POLYPOOLSIZE);
-	gr_polypool = malloc(POLYPOOLSIZE);
-	if (!gr_polypool)
+	gl_polypool = malloc(POLYPOOLSIZE);
+	if (!gl_polypool)
 		I_Error("HWR_InitPolyPool(): couldn't malloc polypool\n");
 	HWR_ClearPolys();
 #endif
@@ -96,9 +96,9 @@ void HWR_InitPolyPool(void)
 void HWR_FreePolyPool(void)
 {
 #ifndef ZPLANALLOC
-	if (gr_polypool)
-		free(gr_polypool);
-	gr_polypool = NULL;
+	if (gl_polypool)
+		free(gl_polypool);
+	gl_polypool = NULL;
 #endif
 }
 
@@ -110,19 +110,19 @@ static poly_t *HWR_AllocPoly(INT32 numpts)
 	p = Z_Malloc(size, PU_HWRPLANE, NULL);
 #else
 #ifdef PARANOIA
-	if (!gr_polypool)
-		I_Error("Used gr_polypool without init!\n");
-	if (!gr_ppcurrent)
-		I_Error("gr_ppcurrent == NULL!\n");
+	if (!gl_polypool)
+		I_Error("Used gl_polypool without init!\n");
+	if (!gl_ppcurrent)
+		I_Error("gl_ppcurrent == NULL!\n");
 #endif
 
-	if (gr_ppfree < size)
+	if (gl_ppfree < size)
 		I_Error("HWR_AllocPoly(): no more memory %u bytes left, %u bytes needed\n\n%s\n",
-		        gr_ppfree, size, "You can try the param -polypoolsize 2048 (or higher if needed)");
+		        gl_ppfree, size, "You can try the param -polypoolsize 2048 (or higher if needed)");
 
-	p = (poly_t *)gr_ppcurrent;
-	gr_ppcurrent += size;
-	gr_ppfree -= size;
+	p = (poly_t *)gl_ppcurrent;
+	gl_ppcurrent += size;
+	gl_ppfree -= size;
 #endif
 	p->numpts = numpts;
 	return p;
@@ -135,13 +135,13 @@ static polyvertex_t *HWR_AllocVertex(void)
 #ifdef ZPLANALLOC
 	p = Z_Malloc(size, PU_HWRPLANE, NULL);
 #else
-	if (gr_ppfree < size)
+	if (gl_ppfree < size)
 		I_Error("HWR_AllocVertex(): no more memory %u bytes left, %u bytes needed\n\n%s\n",
-		        gr_ppfree, size, "You can try the param -polypoolsize 2048 (or higher if needed)");
+		        gl_ppfree, size, "You can try the param -polypoolsize 2048 (or higher if needed)");
 
-	p = (polyvertex_t *)gr_ppcurrent;
-	gr_ppcurrent += size;
-	gr_ppfree -= size;
+	p = (polyvertex_t *)gl_ppcurrent;
+	gl_ppcurrent += size;
+	gl_ppfree -= size;
 #endif
 	return p;
 }
@@ -829,7 +829,7 @@ static INT32 SolveTProblem(void)
 	INT32 i;
 	size_t l;
 
-	if (cv_grsolvetjoin.value == 0)
+	if (cv_glsolvetjoin.value == 0)
 		return 0;
 
 	CONS_Debug(DBG_RENDER, "Solving T-joins. This may take a while. Please wait...\n");
@@ -983,9 +983,9 @@ void HWR_CreatePlanePolygons(INT32 bspnum)
 		I_Error("couldn't malloc extrasubsectors totsubsectors %s\n", sizeu1(totsubsectors));
 
 	// allocate table for back to front drawing of subsectors
-	/*gr_drawsubsectors = (INT16 *)malloc(sizeof (*gr_drawsubsectors) * totsubsectors);
-	if (!gr_drawsubsectors)
-		I_Error("couldn't malloc gr_drawsubsectors\n");*/
+	/*gl_drawsubsectors = (INT16 *)malloc(sizeof (*gl_drawsubsectors) * totsubsectors);
+	if (!gl_drawsubsectors)
+		I_Error("couldn't malloc gl_drawsubsectors\n");*/
 
 	// number of the first new subsector that might be added
 	addsubsector = numsubsectors;
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index ab9a50dd5d884ba39ee9acf7c5f850d9589d1f67..83a4e2e03d9b56cd503092762b7ac48fe52a5611 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -20,34 +20,27 @@
 #include "../doomstat.h"    //gamemode
 #include "../i_video.h"     //rendermode
 #include "../r_data.h"
+#include "../r_textures.h"
 #include "../w_wad.h"
 #include "../z_zone.h"
 #include "../v_video.h"
 #include "../r_draw.h"
 #include "../r_patch.h"
+#include "../r_picformats.h"
 #include "../p_setup.h"
 
-INT32 patchformat = GR_TEXFMT_AP_88; // use alpha for holes
-INT32 textureformat = GR_TEXFMT_P_8; // use chromakey for hole
+INT32 patchformat = GL_TEXFMT_AP_88; // use alpha for holes
+INT32 textureformat = GL_TEXFMT_P_8; // use chromakey for hole
 
-static const INT32 format2bpp[16] =
+static INT32 format2bpp(GLTextureFormat_t format)
 {
-	0, //0
-	0, //1
-	1, //2  GR_TEXFMT_ALPHA_8
-	1, //3  GR_TEXFMT_INTENSITY_8
-	1, //4  GR_TEXFMT_ALPHA_INTENSITY_44
-	1, //5  GR_TEXFMT_P_8
-	4, //6  GR_RGBA
-	0, //7
-	0, //8
-	0, //9
-	2, //10 GR_TEXFMT_RGB_565
-	2, //11 GR_TEXFMT_ARGB_1555
-	2, //12 GR_TEXFMT_ARGB_4444
-	2, //13 GR_TEXFMT_ALPHA_INTENSITY_88
-	2, //14 GR_TEXFMT_AP_88
-};
+	if (format == GL_TEXFMT_RGBA)
+		return 4;
+	else if (format == GL_TEXFMT_ALPHA_INTENSITY_88 || format == GL_TEXFMT_AP_88)
+		return 2;
+	else
+		return 1;
+}
 
 // This code was originally placed directly in HWR_DrawPatchInCache.
 // It is now split from it for my sanity! (and the sanity of others)
@@ -108,16 +101,14 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 			count--;
 
 			texel = source[yfrac>>FRACBITS];
+			alpha = 0xFF;
+			// Make pixel transparent if chroma keyed
+			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
+				alpha = 0x00;
 
 			//Hurdler: 25/04/2000: now support colormap in hardware mode
 			if (mipmap->colormap)
-				texel = mipmap->colormap[texel];
-
-			// If the mipmap is chromakeyed, check if the texel's color
-			// is equivalent to the chroma key's color index.
-			alpha = 0xff;
-			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
-				alpha = 0x00;
+				texel = mipmap->colormap->data[texel];
 
 			// hope compiler will get this switch out of the loops (dreams...)
 			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
@@ -126,7 +117,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 			{
 				case 2 : // uhhhhhhhh..........
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+							 texel = ASTBlendPaletteIndexes(*(dest+1), texel, originPatch->style, originPatch->alpha);
 						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
@@ -135,7 +126,7 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
@@ -145,14 +136,14 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
 				default:
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+							 *dest = ASTBlendPaletteIndexes(*dest, texel, originPatch->style, originPatch->alpha);
 						 else
 							 *dest = texel;
 						 break;
@@ -220,16 +211,14 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 			count--;
 
 			texel = source[yfrac>>FRACBITS];
+			alpha = 0xFF;
+			// Make pixel transparent if chroma keyed
+			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
+				alpha = 0x00;
 
 			//Hurdler: 25/04/2000: now support colormap in hardware mode
 			if (mipmap->colormap)
-				texel = mipmap->colormap[texel];
-
-			// If the mipmap is chromakeyed, check if the texel's color
-			// is equivalent to the chroma key's color index.
-			alpha = 0xff;
-			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
-				alpha = 0x00;
+				texel = mipmap->colormap->data[texel];
 
 			// hope compiler will get this switch out of the loops (dreams...)
 			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
@@ -238,7 +227,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 			{
 				case 2 : // uhhhhhhhh..........
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 texel = ASTBlendPixel_8bpp(*(dest+1), texel, originPatch->style, originPatch->alpha);
+							 texel = ASTBlendPaletteIndexes(*(dest+1), texel, originPatch->style, originPatch->alpha);
 						 texelu16 = (UINT16)((alpha<<8) | texel);
 						 memcpy(dest, &texelu16, sizeof(UINT16));
 						 break;
@@ -247,7 +236,7 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
 						 break;
@@ -257,14 +246,14 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 						 {
 							 RGBA_t rgbatexel;
 							 rgbatexel.rgba = *(UINT32 *)dest;
-							 colortemp.rgba = ASTBlendPixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
+							 colortemp.rgba = ASTBlendTexturePixel(rgbatexel, colortemp, originPatch->style, originPatch->alpha);
 						 }
 						 memcpy(dest, &colortemp, sizeof(RGBA_t));
 						 break;
 				// default is 1
 				default:
 						 if ((originPatch != NULL) && (originPatch->style != AST_COPY))
-							 *dest = ASTBlendPixel_8bpp(*dest, texel, originPatch->style, originPatch->alpha);
+							 *dest = ASTBlendPaletteIndexes(*dest, texel, originPatch->style, originPatch->alpha);
 						 else
 							 *dest = texel;
 						 break;
@@ -292,7 +281,7 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	fixed_t xfrac, xfracstep;
 	fixed_t yfracstep, scale_y;
 	const column_t *patchcol;
-	UINT8 *block = mipmap->grInfo.data;
+	UINT8 *block = mipmap->data;
 	INT32 bpp;
 	INT32 blockmodulo;
 
@@ -307,7 +296,7 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	yfracstep = FRACUNIT;
 	scale_y   = FRACUNIT;
 
-	bpp = format2bpp[mipmap->grInfo.format];
+	bpp = format2bpp(mipmap->format);
 
 	if (bpp < 1 || bpp > 4)
 		I_Error("HWR_DrawPatchInCache: no drawer defined for this bpp (%d)\n",bpp);
@@ -318,7 +307,7 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	// Draw each column to the block cache
 	for (; ncols--; block += bpp, xfrac += xfracstep)
 	{
-		patchcol = (const column_t *)((const UINT8 *)realpatch + LONG(realpatch->columnofs[xfrac>>FRACBITS]));
+		patchcol = (const column_t *)((const UINT8 *)realpatch->columns + (realpatch->columnofs[xfrac>>FRACBITS]));
 
 		HWR_DrawColumnInCache(patchcol, block, mipmap,
 								pblockheight, blockmodulo,
@@ -332,14 +321,14 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 	INT32 pblockwidth, INT32 pblockheight,
 	texture_t *texture, texpatch_t *patch,
-	const patch_t *realpatch)
+	const softwarepatch_t *realpatch)
 {
 	INT32 x, x1, x2;
 	INT32 col, ncols;
 	fixed_t xfrac, xfracstep;
 	fixed_t yfracstep, scale_y;
 	const column_t *patchcol;
-	UINT8 *block = mipmap->grInfo.data;
+	UINT8 *block = mipmap->data;
 	INT32 bpp;
 	INT32 blockmodulo;
 	INT32 width, height;
@@ -400,10 +389,10 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 	yfracstep = (texture->height<< FRACBITS) / pblockheight;
 	scale_y   = (pblockheight  << FRACBITS) / texture->height;
 
-	bpp = format2bpp[mipmap->grInfo.format];
+	bpp = format2bpp(mipmap->format);
 
 	if (bpp < 1 || bpp > 4)
-		I_Error("HWR_DrawPatchInCache: no drawer defined for this bpp (%d)\n",bpp);
+		I_Error("HWR_DrawTexturePatchInCache: no drawer defined for this bpp (%d)\n",bpp);
 
 	// NOTE: should this actually be pblockwidth*bpp?
 	blockmodulo = pblockwidth*bpp;
@@ -431,8 +420,8 @@ static UINT8 *MakeBlock(GLMipmap_t *grMipmap)
 	UINT16 bu16 = ((0x00 <<8) | HWR_PATCHES_CHROMAKEY_COLORINDEX);
 	INT32 blocksize = (grMipmap->width * grMipmap->height);
 
-	bpp =  format2bpp[grMipmap->grInfo.format];
-	block = Z_Malloc(blocksize*bpp, PU_HWRCACHE, &(grMipmap->grInfo.data));
+	bpp =  format2bpp(grMipmap->format);
+	block = Z_Malloc(blocksize*bpp, PU_HWRCACHE, &(grMipmap->data));
 
 	switch (bpp)
 	{
@@ -453,12 +442,12 @@ static UINT8 *MakeBlock(GLMipmap_t *grMipmap)
 // Create a composite texture from patches, adapt the texture size to a power of 2
 // height and width for the hardware texture cache.
 //
-static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
+static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 {
 	UINT8 *block;
 	texture_t *texture;
 	texpatch_t *patch;
-	patch_t *realpatch;
+	softwarepatch_t *realpatch;
 	UINT8 *pdata;
 	INT32 blockwidth, blockheight, blocksize;
 
@@ -483,7 +472,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 
 	grtex->mipmap.width = (UINT16)texture->width;
 	grtex->mipmap.height = (UINT16)texture->height;
-	grtex->mipmap.grInfo.format = textureformat;
+	grtex->mipmap.format = textureformat;
 
 	blockwidth = texture->width;
 	blockheight = texture->height;
@@ -514,16 +503,16 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 		boolean dealloc = true;
 		size_t lumplength = W_LumpLengthPwad(patch->wad, patch->lump);
 		pdata = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
-		realpatch = (patch_t *)pdata;
+		realpatch = (softwarepatch_t *)pdata;
 
 #ifndef NO_PNG_LUMPS
-		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL);
+		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
+			realpatch = (softwarepatch_t *)Picture_PNGConvert(pdata, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0);
 		else
 #endif
 #ifdef WALLFLATS
 		if (texture->type == TEXTURETYPE_FLAT)
-			realpatch = R_FlatToPatch(pdata, texture->width, texture->height, 0, 0, NULL, false);
+			realpatch = (softwarepatch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_DOOMPATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
 		else
 #endif
 		{
@@ -537,7 +526,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 			Z_Unlock(realpatch);
 	}
 	//Hurdler: not efficient at all but I don't remember exactly how HWR_DrawPatchInCache works :(
-	if (format2bpp[grtex->mipmap.grInfo.format]==4)
+	if (format2bpp(grtex->mipmap.format)==4)
 	{
 		for (i = 3; i < blocksize*4; i += 4) // blocksize*4 because blocksize doesn't include the bpp
 		{
@@ -556,40 +545,24 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 // patch may be NULL if grMipmap has been initialised already and makebitmap is false
 void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap)
 {
-#ifndef NO_PNG_LUMPS
-	// lump is a png so convert it
-	size_t len = W_LumpLengthPwad(grPatch->wadnum, grPatch->lumpnum);
-	if ((patch != NULL) && R_IsLumpPNG((const UINT8 *)patch, len))
-		patch = R_PNGToPatch((const UINT8 *)patch, len, NULL);
-#endif
-
-	// don't do it twice (like a cache)
 	if (grMipmap->width == 0)
 	{
-		// save the original patch header so that the GLPatch can be casted
-		// into a standard patch_t struct and the existing code can get the
-		// orginal patch dimensions and offsets.
-		grPatch->width = SHORT(patch->width);
-		grPatch->height = SHORT(patch->height);
-		grPatch->leftoffset = SHORT(patch->leftoffset);
-		grPatch->topoffset = SHORT(patch->topoffset);
-
 		grMipmap->width = grMipmap->height = 1;
-		while (grMipmap->width < grPatch->width) grMipmap->width <<= 1;
-		while (grMipmap->height < grPatch->height) grMipmap->height <<= 1;
+		while (grMipmap->width < patch->width) grMipmap->width <<= 1;
+		while (grMipmap->height < patch->height) grMipmap->height <<= 1;
 
 		// no wrap around, no chroma key
 		grMipmap->flags = 0;
+
 		// setup the texture info
-		grMipmap->grInfo.format = patchformat;
+		grMipmap->format = patchformat;
 
-		//grPatch->max_s = grPatch->max_t = 1.0f;
-		grPatch->max_s = (float)grPatch->width / (float)grMipmap->width;
-		grPatch->max_t = (float)grPatch->height / (float)grMipmap->height;
+		grPatch->max_s = (float)patch->width / (float)grMipmap->width;
+		grPatch->max_t = (float)patch->height / (float)grMipmap->height;
 	}
 
-	Z_Free(grMipmap->grInfo.data);
-	grMipmap->grInfo.data = NULL;
+	Z_Free(grMipmap->data);
+	grMipmap->data = NULL;
 
 	if (makebitmap)
 	{
@@ -597,7 +570,7 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 
 		HWR_DrawPatchInCache(grMipmap,
 			grMipmap->width, grMipmap->height,
-			grPatch->width, grPatch->height,
+			patch->width, patch->height,
 			patch);
 	}
 }
@@ -607,23 +580,59 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 //             CACHING HANDLING
 // =================================================
 
-static size_t gr_numtextures = 0; // Texture count
-static GLTexture_t *gr_textures; // For all textures
-static GLTexture_t *gr_flats; // For all (texture) flats, as normal flats don't need to be cached
+static size_t gl_numtextures = 0; // Texture count
+static GLMapTexture_t *gl_textures; // For all textures
+static GLMapTexture_t *gl_flats; // For all (texture) flats, as normal flats don't need to be cached
+boolean gl_maptexturesloaded = false;
 
-void HWR_InitTextureCache(void)
+void HWR_FreeTextureData(patch_t *patch)
 {
-	gr_textures = NULL;
-	gr_flats = NULL;
+	GLPatch_t *grPatch;
+
+	if (!patch || !patch->hardware)
+		return;
+
+	grPatch = patch->hardware;
+
+	if (vid.glstate == VID_GL_LIBRARY_LOADED)
+		HWD.pfnDeleteTexture(grPatch->mipmap);
+	if (grPatch->mipmap->data)
+		Z_Free(grPatch->mipmap->data);
 }
 
-// Callback function for HWR_FreeTextureCache.
-static void FreeMipmapColormap(INT32 patchnum, void *patch)
+void HWR_FreeTexture(patch_t *patch)
 {
-	GLPatch_t* const pat = patch;
-	(void)patchnum; //unused
+	if (!patch)
+		return;
+
+	if (patch->hardware)
+	{
+		GLPatch_t *grPatch = patch->hardware;
+
+		HWR_FreeTextureColormaps(patch);
+
+		if (grPatch->mipmap)
+		{
+			HWR_FreeTextureData(patch);
+			Z_Free(grPatch->mipmap);
+		}
+
+		Z_Free(patch->hardware);
+	}
+
+	patch->hardware = NULL;
+}
+
+// Called by HWR_FreePatchCache.
+void HWR_FreeTextureColormaps(patch_t *patch)
+{
+	GLPatch_t *pat;
 
 	// The patch must be valid, obviously
+	if (!patch)
+		return;
+
+	pat = patch->hardware;
 	if (!pat)
 		return;
 
@@ -639,7 +648,7 @@ static void FreeMipmapColormap(INT32 patchnum, void *patch)
 		if (!pat->mipmap)
 			break;
 
-		// No colormap mipmap either.
+		// No colormap mipmaps either.
 		if (!pat->mipmap->nextcolormap)
 			break;
 
@@ -648,63 +657,106 @@ static void FreeMipmapColormap(INT32 patchnum, void *patch)
 		pat->mipmap->nextcolormap = next->nextcolormap;
 
 		// Free image data from memory.
-		if (next->grInfo.data)
-			Z_Free(next->grInfo.data);
-		next->grInfo.data = NULL;
+		if (next->data)
+			Z_Free(next->data);
+		if (next->colormap)
+			Z_Free(next->colormap);
+		next->data = NULL;
+		next->colormap = NULL;
+		HWD.pfnDeleteTexture(next);
 
 		// Free the old colormap mipmap from memory.
 		free(next);
 	}
 }
 
-void HWR_FreeMipmapCache(void)
+static boolean FreeTextureCallback(void *mem)
 {
-	INT32 i;
+	patch_t *patch = (patch_t *)mem;
+	HWR_FreeTexture(patch);
+	return false;
+}
+
+static boolean FreeColormapsCallback(void *mem)
+{
+	patch_t *patch = (patch_t *)mem;
+	HWR_FreeTextureColormaps(patch);
+	return false;
+}
+
+static void HWR_FreePatchCache(boolean freeall)
+{
+	boolean (*callback)(void *mem) = FreeTextureCallback;
+
+	if (!freeall)
+		callback = FreeColormapsCallback;
+
+	Z_IterateTags(PU_PATCH, PU_PATCH_ROTATED, callback);
+	Z_IterateTags(PU_SPRITE, PU_HUDGFX, callback);
+}
+
+// free all textures after each level
+void HWR_ClearAllTextures(void)
+{
+	HWD.pfnClearMipMapCache(); // free references to the textures
+	HWR_FreePatchCache(true);
+}
 
-	// free references to the textures
-	HWD.pfnClearMipMapCache();
+void HWR_FreeColormapCache(void)
+{
+	HWR_FreePatchCache(false);
+}
 
-	// free all hardware-converted graphics cached in the heap
-	// our gool is only the textures since user of the texture is the texture cache
-	Z_FreeTag(PU_HWRCACHE);
-	Z_FreeTag(PU_HWRCACHE_UNLOCKED);
+void HWR_InitMapTextures(void)
+{
+	gl_textures = NULL;
+	gl_flats = NULL;
+	gl_maptexturesloaded = false;
+}
 
-	// Alam: free the Z_Blocks before freeing it's users
-	// free all patch colormaps after each level: must be done after ClearMipMapCache!
-	for (i = 0; i < numwadfiles; i++)
-		M_AATreeIterate(wadfiles[i]->hwrcache, FreeMipmapColormap);
+static void FreeMapTexture(GLMapTexture_t *tex)
+{
+	HWD.pfnDeleteTexture(&tex->mipmap);
+	if (tex->mipmap.data)
+		Z_Free(tex->mipmap.data);
+	tex->mipmap.data = NULL;
 }
 
-void HWR_FreeTextureCache(void)
+void HWR_FreeMapTextures(void)
 {
-	// free references to the textures
-	HWR_FreeMipmapCache();
+	size_t i;
+
+	for (i = 0; i < gl_numtextures; i++)
+	{
+		FreeMapTexture(&gl_textures[i]);
+		FreeMapTexture(&gl_flats[i]);
+	}
 
 	// now the heap don't have any 'user' pointing to our
 	// texturecache info, we can free it
-	if (gr_textures)
-		free(gr_textures);
-	if (gr_flats)
-		free(gr_flats);
-	gr_textures = NULL;
-	gr_flats = NULL;
-	gr_numtextures = 0;
+	if (gl_textures)
+		free(gl_textures);
+	if (gl_flats)
+		free(gl_flats);
+	gl_textures = NULL;
+	gl_flats = NULL;
+	gl_numtextures = 0;
+	gl_maptexturesloaded = false;
 }
 
-void HWR_LoadTextures(size_t pnumtextures)
+void HWR_LoadMapTextures(size_t pnumtextures)
 {
-	// we must free it since numtextures changed
-	HWR_FreeTextureCache();
-
-	// Why not Z_Malloc?
-	gr_numtextures = pnumtextures;
-	gr_textures = calloc(gr_numtextures, sizeof(*gr_textures));
-	gr_flats = calloc(gr_numtextures, sizeof(*gr_flats));
-
-	// Doesn't tell you which it _is_, but hopefully
-	// should never ever happen (right?!)
-	if ((gr_textures == NULL) || (gr_flats == NULL))
-		I_Error("HWR_LoadTextures: ran out of memory for OpenGL textures. Sad!");
+	// we must free it since numtextures may have changed
+	HWR_FreeMapTextures();
+
+	gl_numtextures = pnumtextures;
+	gl_textures = calloc(gl_numtextures, sizeof(*gl_textures));
+	gl_flats = calloc(gl_numtextures, sizeof(*gl_flats));
+
+	if ((gl_textures == NULL) || (gl_flats == NULL))
+		I_Error("HWR_LoadMapTextures: ran out of memory for OpenGL textures");
+
+	gl_maptexturesloaded = true;
 }
 
 void HWR_SetPalette(RGBA_t *palette)
@@ -713,7 +765,7 @@ void HWR_SetPalette(RGBA_t *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 == GR_RGBA || textureformat == GR_RGBA)
+	if (patchformat == GL_TEXFMT_RGBA || textureformat == GL_TEXFMT_RGBA)
 	{
 		Z_FreeTag(PU_HWRCACHE);
 		Z_FreeTag(PU_HWRCACHE_UNLOCKED);
@@ -723,30 +775,29 @@ void HWR_SetPalette(RGBA_t *palette)
 // --------------------------------------------------------------------------
 // Make sure texture is downloaded and set it as the source
 // --------------------------------------------------------------------------
-GLTexture_t *HWR_GetTexture(INT32 tex)
+GLMapTexture_t *HWR_GetTexture(INT32 tex)
 {
-	GLTexture_t *grtex;
+	GLMapTexture_t *grtex;
 #ifdef PARANOIA
-	if ((unsigned)tex >= gr_numtextures)
+	if ((unsigned)tex >= gl_numtextures)
 		I_Error("HWR_GetTexture: tex >= numtextures\n");
 #endif
 
 	// Every texture in memory, stored in the
 	// hardware renderer's bit depth format. Wow!
-	grtex = &gr_textures[tex];
+	grtex = &gl_textures[tex];
 
 	// Generate texture if missing from the cache
-	if (!grtex->mipmap.grInfo.data && !grtex->mipmap.downloaded)
+	if (!grtex->mipmap.data && !grtex->mipmap.downloaded)
 		HWR_GenerateTexture(tex, grtex);
 
 	// If hardware does not have the texture, then call pfnSetTexture to upload it
 	if (!grtex->mipmap.downloaded)
 		HWD.pfnSetTexture(&grtex->mipmap);
-	
 	HWR_SetCurrentTexture(&grtex->mipmap);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(grtex->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(grtex->mipmap.data, PU_HWRCACHE_UNLOCKED);
 
 	return grtex;
 }
@@ -756,7 +807,7 @@ static void HWR_CacheFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 	size_t size, pflatsize;
 
 	// setup the texture info
-	grMipmap->grInfo.format = GR_TEXFMT_P_8;
+	grMipmap->format = GL_TEXFMT_P_8;
 	grMipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
 
 	size = W_LumpLength(flatlumpnum);
@@ -791,45 +842,50 @@ static void HWR_CacheFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 
 	// the flat raw data needn't be converted with palettized textures
 	W_ReadLump(flatlumpnum, Z_Malloc(W_LumpLength(flatlumpnum),
-		PU_HWRCACHE, &grMipmap->grInfo.data));
+		PU_HWRCACHE, &grMipmap->data));
 }
 
 static void HWR_CacheTextureAsFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 {
 	UINT8 *flat;
+	UINT8 *converted;
+	size_t size;
 
 	// setup the texture info
-	grMipmap->grInfo.format = GR_TEXFMT_P_8;
+	grMipmap->format = GL_TEXFMT_P_8;
 	grMipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
 
 	grMipmap->width  = (UINT16)textures[texturenum]->width;
 	grMipmap->height = (UINT16)textures[texturenum]->height;
+	size = (grMipmap->width * grMipmap->height);
 
-	flat = Z_Malloc(grMipmap->width * grMipmap->height, PU_HWRCACHE, &grMipmap->grInfo.data);
-	memset(flat, TRANSPARENTPIXEL, grMipmap->width * grMipmap->height);
-
-	R_TextureToFlat(texturenum, flat);
+	flat = Z_Malloc(size, PU_HWRCACHE, &grMipmap->data);
+	converted = (UINT8 *)Picture_TextureToFlat(texturenum);
+	M_Memcpy(flat, converted, size);
+	Z_Free(converted);
 }
 
 // Download a Doom 'flat' to the hardware cache and make it ready for use
-void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum)
+void HWR_GetRawFlat(lumpnum_t flatlumpnum)
 {
 	GLMipmap_t *grmip;
+	patch_t *patch;
+
 	if (flatlumpnum == LUMPERROR)
 		return;
 
-	grmip = HWR_GetCachedGLPatch(flatlumpnum)->mipmap;
-	if (!grmip->downloaded && !grmip->grInfo.data)
+	patch = HWR_GetCachedGLPatch(flatlumpnum);
+	grmip = ((GLPatch_t *)Patch_AllocateHardwarePatch(patch))->mipmap;
+	if (!grmip->downloaded && !grmip->data)
 		HWR_CacheFlat(grmip, flatlumpnum);
 
 	// If hardware does not have the texture, then call pfnSetTexture to upload it
 	if (!grmip->downloaded)
 		HWD.pfnSetTexture(grmip);
-	
 	HWR_SetCurrentTexture(grmip);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(grmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(grmip->data, PU_HWRCACHE_UNLOCKED);
 }
 
 void HWR_GetLevelFlat(levelflat_t *levelflat)
@@ -839,14 +895,14 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		return;
 
 	if (levelflat->type == LEVELFLAT_FLAT)
-		HWR_LiterallyGetFlat(levelflat->u.flat.lumpnum);
+		HWR_GetRawFlat(levelflat->u.flat.lumpnum);
 	else if (levelflat->type == LEVELFLAT_TEXTURE)
 	{
-		GLTexture_t *grtex;
+		GLMapTexture_t *grtex;
 		INT32 texturenum = levelflat->u.texture.num;
 #ifdef PARANOIA
-		if ((unsigned)texturenum >= gr_numtextures)
-			I_Error("HWR_GetLevelFlat: texturenum >= numtextures\n");
+		if ((unsigned)texturenum >= gl_numtextures)
+			I_Error("HWR_GetLevelFlat: texturenum >= numtextures");
 #endif
 
 		// Who knows?
@@ -854,108 +910,159 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 			return;
 
 		// Every texture in memory, stored as a 8-bit flat. Wow!
-		grtex = &gr_flats[texturenum];
+		grtex = &gl_flats[texturenum];
 
 		// Generate flat if missing from the cache
-		if (!grtex->mipmap.grInfo.data && !grtex->mipmap.downloaded)
+		if (!grtex->mipmap.data && !grtex->mipmap.downloaded)
 			HWR_CacheTextureAsFlat(&grtex->mipmap, texturenum);
 
 		// If hardware does not have the texture, then call pfnSetTexture to upload it
 		if (!grtex->mipmap.downloaded)
 			HWD.pfnSetTexture(&grtex->mipmap);
-		
 		HWR_SetCurrentTexture(&grtex->mipmap);
 
 		// The system-memory data can be purged now.
-		Z_ChangeTag(grtex->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+		Z_ChangeTag(grtex->mipmap.data, PU_HWRCACHE_UNLOCKED);
+	}
+	else if (levelflat->type == LEVELFLAT_PATCH)
+	{
+		patch_t *patch = W_CachePatchNum(levelflat->u.flat.lumpnum, PU_CACHE);
+		levelflat->width = (UINT16)(patch->width);
+		levelflat->height = (UINT16)(patch->height);
+		HWR_GetPatch(patch);
+	}
+#ifndef NO_PNG_LUMPS
+	else if (levelflat->type == LEVELFLAT_PNG)
+	{
+		GLMipmap_t *mipmap = levelflat->mipmap;
+
+		// Cache the picture.
+		if (!levelflat->mippic)
+		{
+			INT32 pngwidth = 0, pngheight = 0;
+			void *pic = Picture_PNGConvert(W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, 0);
+
+			Z_ChangeTag(pic, PU_LEVEL);
+			Z_SetUser(pic, &levelflat->mippic);
+
+			levelflat->width = (UINT16)pngwidth;
+			levelflat->height = (UINT16)pngheight;
+		}
+
+		// Make the mipmap.
+		if (mipmap == NULL)
+		{
+			mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_STATIC, NULL);
+			mipmap->format = GL_TEXFMT_P_8;
+			mipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
+			levelflat->mipmap = mipmap;
+		}
+
+		if (!mipmap->data && !mipmap->downloaded)
+		{
+			UINT8 *flat;
+			size_t size;
+
+			if (levelflat->mippic == NULL)
+				I_Error("HWR_GetLevelFlat: levelflat->mippic == NULL");
+
+			mipmap->width = levelflat->width;
+			mipmap->height = levelflat->height;
+
+			size = (mipmap->width * mipmap->height);
+			flat = Z_Malloc(size, PU_LEVEL, &mipmap->data);
+			M_Memcpy(flat, levelflat->mippic, size);
+		}
+
+		// Tell the hardware driver to bind the current texture to the flat's mipmap
+		HWR_SetCurrentTexture(mipmap);
 	}
+#endif
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
 }
 
-//
-// HWR_LoadMappedPatch(): replace the skin color of the sprite in cache
-//                          : load it first in doom cache if not already
-//
-static void HWR_LoadMappedPatch(GLMipmap_t *grmip, GLPatch_t *gpatch)
+// --------------------+
+// HWR_LoadPatchMipmap : Generates a patch into a mipmap, usually the mipmap inside the patch itself
+// --------------------+
+static void HWR_LoadPatchMipmap(patch_t *patch, GLMipmap_t *grMipmap)
 {
-	if (!grmip->downloaded && !grmip->grInfo.data)
-	{
-		patch_t *patch = gpatch->rawpatch;
-		if (!patch)
-			patch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-		HWR_MakePatch(patch, gpatch, grmip, true);
-
-		// You can't free rawpatch for some reason?
-		// (Obviously I can't, sprite rotation needs that...)
-		if (!gpatch->rawpatch)
-			Z_Free(patch);
-	}
+	GLPatch_t *grPatch = patch->hardware;
+	if (!grMipmap->downloaded && !grMipmap->data)
+		HWR_MakePatch(patch, grPatch, grMipmap, true);
 
 	// If hardware does not have the texture, then call pfnSetTexture to upload it
-	if (!grmip->downloaded)
-		HWD.pfnSetTexture(grmip);
-	
-	HWR_SetCurrentTexture(grmip);
+	if (!grMipmap->downloaded)
+		HWD.pfnSetTexture(grMipmap);
+	HWR_SetCurrentTexture(grMipmap);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(grmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(grMipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
-// -----------------+
-// HWR_GetPatch     : Download a patch to the hardware cache and make it ready for use
-// -----------------+
-void HWR_GetPatch(GLPatch_t *gpatch)
+// ----------------------+
+// HWR_UpdatePatchMipmap : Updates a mipmap.
+// ----------------------+
+static void HWR_UpdatePatchMipmap(patch_t *patch, GLMipmap_t *grMipmap)
 {
-	// is it in hardware cache
-	if (!gpatch->mipmap->downloaded && !gpatch->mipmap->grInfo.data)
-	{
-		// load the software patch, PU_STATIC or the Z_Malloc for hardware patch will
-		// flush the software patch before the conversion! oh yeah I suffered
-		patch_t *ptr = gpatch->rawpatch;
-		if (!ptr)
-			ptr = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-		HWR_MakePatch(ptr, gpatch, gpatch->mipmap, true);
-
-		// this is inefficient.. but the hardware patch in heap is purgeable so it should
-		// not fragment memory, and besides the REAL cache here is the hardware memory
-		if (!gpatch->rawpatch)
-			Z_Free(ptr);
-	}
+	GLPatch_t *grPatch = patch->hardware;
+	HWR_MakePatch(patch, grPatch, grMipmap, true);
 
 	// If hardware does not have the texture, then call pfnSetTexture to upload it
-	if (!gpatch->mipmap->downloaded)
-		HWD.pfnSetTexture(gpatch->mipmap);
-
-	HWR_SetCurrentTexture(gpatch->mipmap);
+	// If it does have the texture, then call pfnUpdateTexture to update it
+	if (!grMipmap->downloaded)
+		HWD.pfnSetTexture(grMipmap);
+	else
+		HWD.pfnUpdateTexture(grMipmap);
+	HWR_SetCurrentTexture(grMipmap);
 
-	// The system-memory patch data can be purged now.
-	Z_ChangeTag(gpatch->mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
+	// The system-memory data can be purged now.
+	Z_ChangeTag(grMipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
+// -----------------+
+// HWR_GetPatch     : Downloads a patch to the hardware cache and make it ready for use
+// -----------------+
+void HWR_GetPatch(patch_t *patch)
+{
+	if (!patch->hardware)
+		Patch_CreateGL(patch);
+	HWR_LoadPatchMipmap(patch, ((GLPatch_t *)patch->hardware)->mipmap);
+}
 
 // -------------------+
 // HWR_GetMappedPatch : Same as HWR_GetPatch for sprite color
 // -------------------+
-void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap)
+void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap)
 {
-	GLMipmap_t *grmip, *newmip;
+	GLPatch_t *grPatch;
+	GLMipmap_t *grMipmap, *newMipmap;
+
+	if (!patch->hardware)
+		Patch_CreateGL(patch);
+	grPatch = patch->hardware;
 
 	if (colormap == colormaps || colormap == NULL)
 	{
-		// Load the default (green) color in doom cache (temporary?) AND hardware cache
-		HWR_GetPatch(gpatch);
+		// Load the default (green) color in hardware cache
+		HWR_GetPatch(patch);
 		return;
 	}
 
-	// search for the mimmap
+	// search for the mipmap
 	// skip the first (no colormap translated)
-	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
+	for (grMipmap = grPatch->mipmap; grMipmap->nextcolormap; )
 	{
-		grmip = grmip->nextcolormap;
-		if (grmip->colormap == colormap)
+		grMipmap = grMipmap->nextcolormap;
+		if (grMipmap->colormap && grMipmap->colormap->source == colormap)
 		{
-			HWR_LoadMappedPatch(grmip, gpatch);
+			if (memcmp(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8)))
+			{
+				M_Memcpy(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
+				HWR_UpdatePatchMipmap(patch, grMipmap);
+			}
+			else
+				HWR_LoadPatchMipmap(patch, grMipmap);
 			return;
 		}
 	}
@@ -964,15 +1071,18 @@ void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap)
 
 	//BP: WARNING: don't free it manually without clearing the cache of harware renderer
 	//              (it have a liste of mipmap)
-	//    this malloc is cleared in HWR_FreeTextureCache
+	//    this malloc is cleared in HWR_FreeColormapCache
 	//    (...) unfortunately z_malloc fragment alot the memory :(so malloc is better
-	newmip = calloc(1, sizeof (*newmip));
-	if (newmip == NULL)
+	newMipmap = calloc(1, sizeof (*newMipmap));
+	if (newMipmap == NULL)
 		I_Error("%s: Out of memory", "HWR_GetMappedPatch");
-	grmip->nextcolormap = newmip;
+	grMipmap->nextcolormap = newMipmap;
 
-	newmip->colormap = colormap;
-	HWR_LoadMappedPatch(newmip, gpatch);
+	newMipmap->colormap = Z_Calloc(sizeof(*newMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL);
+	newMipmap->colormap->source = colormap;
+	M_Memcpy(newMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
+
+	HWR_LoadPatchMipmap(patch, newMipmap);
 }
 
 void HWR_UnlockCachedPatch(GLPatch_t *gpatch)
@@ -980,17 +1090,16 @@ void HWR_UnlockCachedPatch(GLPatch_t *gpatch)
 	if (!gpatch)
 		return;
 
-	Z_ChangeTag(gpatch->mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
-	Z_ChangeTag(gpatch, PU_HWRPATCHINFO_UNLOCKED);
+	Z_ChangeTag(gpatch->mipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
 static const INT32 picmode2GR[] =
 {
-	GR_TEXFMT_P_8,                // PALETTE
+	GL_TEXFMT_P_8,                // PALETTE
 	0,                            // INTENSITY          (unsupported yet)
-	GR_TEXFMT_ALPHA_INTENSITY_88, // INTENSITY_ALPHA    (corona use this)
+	GL_TEXFMT_ALPHA_INTENSITY_88, // INTENSITY_ALPHA    (corona use this)
 	0,                            // RGB24              (unsupported yet)
-	GR_RGBA,                      // RGBA32             (opengl only)
+	GL_TEXFMT_RGBA,               // RGBA32             (opengl only)
 };
 
 static void HWR_DrawPicInCache(UINT8 *block, INT32 pblockwidth, INT32 pblockheight,
@@ -1005,7 +1114,7 @@ static void HWR_DrawPicInCache(UINT8 *block, INT32 pblockwidth, INT32 pblockheig
 
 	stepy = ((INT32)SHORT(pic->height)<<FRACBITS)/pblockheight;
 	stepx = ((INT32)SHORT(pic->width)<<FRACBITS)/pblockwidth;
-	picbpp = format2bpp[picmode2GR[pic->mode]];
+	picbpp = format2bpp(picmode2GR[pic->mode]);
 	posy = 0;
 	for (j = 0; j < pblockheight; j++)
 	{
@@ -1062,79 +1171,73 @@ static void HWR_DrawPicInCache(UINT8 *block, INT32 pblockwidth, INT32 pblockheig
 // HWR_GetPic       : Download a Doom pic (raw row encoded with no 'holes')
 // Returns          :
 // -----------------+
-GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
+patch_t *HWR_GetPic(lumpnum_t lumpnum)
 {
-	GLPatch_t *grpatch = HWR_GetCachedGLPatch(lumpnum);
-	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
+	patch_t *patch = HWR_GetCachedGLPatch(lumpnum);
+	GLPatch_t *grPatch = (GLPatch_t *)(patch->hardware);
+
+	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
 	{
 		pic_t *pic;
 		UINT8 *block;
 		size_t len;
 
 		pic = W_CacheLumpNum(lumpnum, PU_CACHE);
-		grpatch->width = SHORT(pic->width);
-		grpatch->height = SHORT(pic->height);
+		patch->width = SHORT(pic->width);
+		patch->height = SHORT(pic->height);
 		len = W_LumpLength(lumpnum) - sizeof (pic_t);
 
-		grpatch->leftoffset = 0;
-		grpatch->topoffset = 0;
-
-		grpatch->mipmap->width = (UINT16)grpatch->width;
-		grpatch->mipmap->height = (UINT16)grpatch->height;
+		grPatch->mipmap->width = (UINT16)patch->width;
+		grPatch->mipmap->height = (UINT16)patch->height;
 
 		if (pic->mode == PALETTE)
-			grpatch->mipmap->grInfo.format = textureformat; // can be set by driver
+			grPatch->mipmap->format = textureformat; // can be set by driver
 		else
-			grpatch->mipmap->grInfo.format = picmode2GR[pic->mode];
+			grPatch->mipmap->format = picmode2GR[pic->mode];
 
-		Z_Free(grpatch->mipmap->grInfo.data);
+		Z_Free(grPatch->mipmap->data);
 
 		// allocate block
-		block = MakeBlock(grpatch->mipmap);
+		block = MakeBlock(grPatch->mipmap);
 
-		if (grpatch->width  == SHORT(pic->width) &&
-			grpatch->height == SHORT(pic->height) &&
-			format2bpp[grpatch->mipmap->grInfo.format] == format2bpp[picmode2GR[pic->mode]])
+		if (patch->width  == SHORT(pic->width) &&
+			patch->height == SHORT(pic->height) &&
+			format2bpp(grPatch->mipmap->format) == format2bpp(picmode2GR[pic->mode]))
 		{
 			// no conversion needed
-			M_Memcpy(grpatch->mipmap->grInfo.data, pic->data,len);
+			M_Memcpy(grPatch->mipmap->data, pic->data,len);
 		}
 		else
 			HWR_DrawPicInCache(block, SHORT(pic->width), SHORT(pic->height),
-			                   SHORT(pic->width)*format2bpp[grpatch->mipmap->grInfo.format],
+			                   SHORT(pic->width)*format2bpp(grPatch->mipmap->format),
 			                   pic,
-			                   format2bpp[grpatch->mipmap->grInfo.format]);
+			                   format2bpp(grPatch->mipmap->format));
 
 		Z_Unlock(pic);
 		Z_ChangeTag(block, PU_HWRCACHE_UNLOCKED);
 
-		grpatch->mipmap->flags = 0;
-		grpatch->max_s = grpatch->max_t = 1.0f;
+		grPatch->mipmap->flags = 0;
+		grPatch->max_s = grPatch->max_t = 1.0f;
 	}
-	HWD.pfnSetTexture(grpatch->mipmap);
-	//CONS_Debug(DBG_RENDER, "picloaded at %x as texture %d\n",grpatch->mipmap.grInfo.data, grpatch->mipmap.downloaded);
+	HWD.pfnSetTexture(grPatch->mipmap);
+	//CONS_Debug(DBG_RENDER, "picloaded at %x as texture %d\n",grPatch->mipmap->data, grPatch->mipmap->downloaded);
 
-	return grpatch;
+	return patch;
 }
 
-GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
+patch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
 {
-	aatree_t *hwrcache = wadfiles[wadnum]->hwrcache;
-	GLPatch_t *grpatch;
-
-	if (!(grpatch = M_AATreeGet(hwrcache, lumpnum)))
+	lumpcache_t *lumpcache = wadfiles[wadnum]->patchcache;
+	if (!lumpcache[lumpnum])
 	{
-		grpatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
-		grpatch->wadnum = wadnum;
-		grpatch->lumpnum = lumpnum;
-		grpatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
-		M_AATreeSet(hwrcache, lumpnum, grpatch);
+		void *ptr = Z_Calloc(sizeof(patch_t), PU_PATCH, &lumpcache[lumpnum]);
+		Patch_Create(NULL, 0, ptr);
+		Patch_AllocateHardwarePatch(ptr);
 	}
-
-	return grpatch;
+	return (patch_t *)(lumpcache[lumpnum]);
 }
 
-GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum)
+patch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum)
 {
 	return HWR_GetCachedGLPatchPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
 }
@@ -1145,7 +1248,7 @@ static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32
 {
 	INT32 i,j;
 	fixed_t posx, posy, stepx, stepy;
-	UINT8 *block = mipmap->grInfo.data; // places the data directly into here
+	UINT8 *block = mipmap->data; // places the data directly into here
 	UINT8 *flat;
 	UINT8 *dest, *src, texel;
 	RGBA_t col;
@@ -1154,8 +1257,8 @@ static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32
 	W_ReadLump(fademasklumpnum, Z_Malloc(W_LumpLength(fademasklumpnum),
 		PU_HWRCACHE, &flat));
 
-	stepy = ((INT32)SHORT(fmheight)<<FRACBITS)/pblockheight;
-	stepx = ((INT32)SHORT(fmwidth)<<FRACBITS)/pblockwidth;
+	stepy = ((INT32)fmheight<<FRACBITS)/pblockheight;
+	stepx = ((INT32)fmwidth<<FRACBITS)/pblockwidth;
 	posy = 0;
 	for (j = 0; j < pblockheight; j++)
 	{
@@ -1184,7 +1287,7 @@ static void HWR_CacheFadeMask(GLMipmap_t *grMipmap, lumpnum_t fademasklumpnum)
 	UINT16 fmheight = 0, fmwidth = 0;
 
 	// setup the texture info
-	grMipmap->grInfo.format = GR_TEXFMT_ALPHA_8; // put the correct alpha levels straight in so I don't need to convert it later
+	grMipmap->format = GL_TEXFMT_ALPHA_8; // put the correct alpha levels straight in so I don't need to convert it later
 	grMipmap->flags = 0;
 
 	size = W_LumpLength(fademasklumpnum);
@@ -1227,14 +1330,15 @@ static void HWR_CacheFadeMask(GLMipmap_t *grMipmap, lumpnum_t fademasklumpnum)
 
 void HWR_GetFadeMask(lumpnum_t fademasklumpnum)
 {
-	GLMipmap_t *grmip = HWR_GetCachedGLPatch(fademasklumpnum)->mipmap;
-	if (!grmip->downloaded && !grmip->grInfo.data)
+	patch_t *patch = HWR_GetCachedGLPatch(fademasklumpnum);
+	GLMipmap_t *grmip = ((GLPatch_t *)Patch_AllocateHardwarePatch(patch))->mipmap;
+	if (!grmip->downloaded && !grmip->data)
 		HWR_CacheFadeMask(grmip, fademasklumpnum);
 
 	HWD.pfnSetTexture(grmip);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(grmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(grmip->data, PU_HWRCACHE_UNLOCKED);
 }
 
 #endif //HWRENDER
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index 686d522a0d96cb284c057a5fc826d16d9023a54b..7e56a14d0f71b9d575046ccb9877487169c3877d 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -20,8 +20,6 @@
 #endif
 
 #include "../doomdef.h"
-//THIS MUST DISAPPEAR!!!
-#include "hw_glide.h"
 #include "../screen.h"
 
 
@@ -29,56 +27,65 @@
 //                                                               TEXTURE INFO
 // ==========================================================================
 
-// grInfo.data holds the address of the graphics data cached in heap memory
-//                NULL if the texture is not in Doom heap cache.
+typedef enum GLTextureFormat_e
+{
+	GL_TEXFMT_P_8                 = 0x01, /* 8-bit palette */
+	GL_TEXFMT_AP_88               = 0x02, /* 8-bit alpha, 8-bit palette */
+
+	GL_TEXFMT_RGBA                = 0x10, /* 32 bit RGBA! */
+
+	GL_TEXFMT_ALPHA_8             = 0x20, /* (0..0xFF) alpha     */
+	GL_TEXFMT_INTENSITY_8         = 0x21, /* (0..0xFF) intensity */
+	GL_TEXFMT_ALPHA_INTENSITY_88  = 0x22,
+} GLTextureFormat_t;
+
+// Colormap structure for mipmaps.
+struct GLColormap_s
+{
+	const UINT8 *source;
+	UINT8 data[256];
+};
+typedef struct GLColormap_s GLColormap_t;
+
+
+// Texture information (misleadingly named "mipmap" all over the code.)
+// The *data pointer holds the address of the graphics data cached in heap memory.
+// NULL if the texture is not in SRB2's heap cache.
 struct GLMipmap_s
 {
-	GrTexInfo       grInfo;         //for TexDownloadMipMap
-	FxU32           flags;
-	UINT16          height;
-	UINT16          width;
-	UINT32          downloaded;     // the dll driver have it in there cache ?
+	// for UpdateTexture
+	GLTextureFormat_t     format;
+	void                 *data;
 
-	struct GLMipmap_s    *nextcolormap;
-	const UINT8          *colormap;
+	UINT32                flags;
+	UINT16                height;
+	UINT16                width;
+	UINT32                downloaded; // The GPU has this texture.
 
-	// opengl
-	struct GLMipmap_s *nextmipmap; // opengl : liste of all texture in opengl driver
+	struct GLMipmap_s    *nextcolormap;
+	struct GLColormap_s  *colormap;
 };
 typedef struct GLMipmap_s GLMipmap_t;
 
 
 //
-// Doom texture info, as cached for hardware rendering
+// Level textures, as cached for hardware rendering.
 //
-struct GLTexture_s
+struct GLMapTexture_s
 {
 	GLMipmap_t  mipmap;
-	float       scaleX;             //used for scaling textures on walls
+	float       scaleX; // Used for scaling textures on walls
 	float       scaleY;
 };
-typedef struct GLTexture_s GLTexture_t;
-
+typedef struct GLMapTexture_s GLMapTexture_t;
 
-// a cached patch as converted to hardware format, holding the original patch_t
-// header so that the existing code can retrieve ->width, ->height as usual
-// This is returned by W_CachePatchNum()/W_CachePatchName(), when rendermode
-// is 'render_opengl'. Else it returns the normal patch_t data.
 
+// Patch information for the hardware renderer.
 struct GLPatch_s
 {
-	// the 4 first fields come right away from the original patch_t
-	INT16               width;
-	INT16               height;
-	INT16               leftoffset;     // pixels to the left of origin
-	INT16               topoffset;      // pixels below the origin
-	//
-	float               max_s,max_t;
-	UINT16              wadnum;      // the software patch lump num for when the hardware patch
-	UINT16              lumpnum;     // was flushed, and we need to re-create it
-	void                *rawpatch;   // :^)
-	GLMipmap_t          *mipmap;
-} ATTRPACK;
+	GLMipmap_t *mipmap; // Texture data. Allocated whenever the patch is.
+	float       max_s, max_t;
+};
 typedef struct GLPatch_s GLPatch_t;
 
 #endif //_HWR_DATA_
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 715c45ef3ea5d28da8d4b241fa38fdd960a2d3a4..bd6afc74fa8631bd912664470d9f137652f1a4a8 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -132,6 +132,77 @@ typedef struct
 	FLOAT       t;            // t texture ordinate (t over w)
 } FOutVector;
 
+#ifdef GL_SHADERS
+// Predefined shader types
+enum
+{
+	SHADER_DEFAULT = 0,
+
+	SHADER_FLOOR,
+	SHADER_WALL,
+	SHADER_SPRITE,
+	SHADER_MODEL, SHADER_MODEL_LIGHTING,
+	SHADER_WATER,
+	SHADER_FOG,
+	SHADER_SKY,
+
+	NUMBASESHADERS,
+};
+
+// 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;
+
+// Custom shader reference table
+typedef struct
+{
+	const char *type;
+	INT32 id;
+} customshaderxlat_t;
+
+#endif
+
+typedef struct vbo_vertex_s
+{
+	float x, y, z;
+	float u, v;
+	unsigned char r, g, b, a;
+} gl_skyvertex_t;
+
+typedef enum gl_skyloopmode_e
+{
+	HWD_SKYLOOP_FAN,
+	HWD_SKYLOOP_STRIP
+} gl_skyloopmode_t;
+
+typedef struct
+{
+	gl_skyloopmode_t mode;
+	int vertexcount;
+	int vertexindex;
+	boolean use_texture;
+} gl_skyloopdef_t;
+
+typedef struct
+{
+	unsigned int vbo;
+	int rows, columns;
+	int loopcount;
+
+	int detail, vertex_count;
+	int texture, width, height;
+	boolean rebuild; // VBO needs to be rebuilt
+
+	gl_skyloopdef_t *loops;
+	gl_skyvertex_t *data;
+} gl_sky_t;
 
 // ==========================================================================
 //                                                               RENDER MODES
@@ -141,35 +212,32 @@ typedef struct
 // You pass a combination of these flags to DrawPolygon()
 enum EPolyFlags
 {
-		// the first 5 are mutually exclusive
-
-	PF_Masked           = 0x00000001,   // Poly is alpha scaled and 0 alpha pels are discarded (holes in texture)
+	// Mutually exclusive blend flags
+	PF_Masked           = 0x00000001,   // Poly is alpha scaled and 0 alpha pixels are discarded (holes in texture)
 	PF_Translucent      = 0x00000002,   // Poly is transparent, alpha = level of transparency
-	PF_Additive         = 0x00000004,   // Poly is added to the frame buffer
-	PF_Environment      = 0x00000008,   // Poly should be drawn environment mapped.
-	                                    // Hurdler: used for text drawing
-	PF_Substractive     = 0x00000010,   // for splat
-	PF_NoAlphaTest      = 0x00000020,   // hiden param
-	PF_Fog              = 0x00000040,   // Fog blocks
-	PF_Blending         = (PF_Environment|PF_Additive|PF_Translucent|PF_Masked|PF_Substractive|PF_Fog)&~PF_NoAlphaTest,
-
-		// other flag bits
-
-	PF_Occlude          = 0x00000100,   // Update the depth buffer
-	PF_NoDepthTest      = 0x00000200,   // Disable the depth test mode
-	PF_Invisible        = 0x00000400,   // Disable write to color buffer
-	PF_Decal            = 0x00000800,   // Enable polygon offset
+	PF_Environment      = 0x00000004,   // Poly should be drawn environment mapped. (Hurdler: used for text drawing)
+	PF_Additive         = 0x00000008,   // Additive color blending
+	PF_AdditiveSource   = 0x00000010,   // Source blending factor is additive. This is the opposite of regular additive blending.
+	PF_Subtractive      = 0x00000020,   // Subtractive color blending
+	PF_ReverseSubtract  = 0x00000040,   // Reverse subtract, used in wall splats (decals)
+	PF_Multiplicative   = 0x00000080,   // Multiplicative color blending
+	PF_Fog              = 0x20000000,   // Fog blocks
+	PF_NoAlphaTest      = 0x40000000,   // Disables alpha testing
+	PF_Blending         = (PF_Masked|PF_Translucent|PF_Environment|PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative|PF_Fog) & ~PF_NoAlphaTest,
+
+	// other flag bits
+	PF_Occlude          = 0x00000100,   // Updates the depth buffer
+	PF_NoDepthTest      = 0x00000200,   // Disables the depth test mode
+	PF_Invisible        = 0x00000400,   // Disables write to color buffer
+	PF_Decal            = 0x00000800,   // Enables polygon offset
 	PF_Modulated        = 0x00001000,   // Modulation (multiply output with constant ARGB)
 	                                    // When set, pass the color constant into the FSurfaceInfo -> PolyColor
-	PF_NoTexture        = 0x00002000,   // Use the small white texture
-	PF_Corona           = 0x00004000,   // Tell the rendrer we are drawing a corona
-	PF_Ripple           = 0x00008000,   // Water shader effect
-	PF_RemoveYWrap      = 0x00010000,   // Force clamp texture on Y
-	PF_ForceWrapX       = 0x00020000,   // Force repeat texture on X
-	PF_ForceWrapY       = 0x00040000,   // Force repeat texture on Y
-	PF_Clip             = 0x40000000,   // clip to frustum and nearz plane (glide only, automatic in opengl)
-	PF_NoZClip          = 0x20000000,   // in conjonction with PF_Clip
-	PF_Debug            = 0x80000000    // print debug message in driver :)
+	PF_NoTexture        = 0x00002000,   // Disables texturing
+	PF_Corona           = 0x00004000,   // Tells the renderer we are drawing a corona
+	PF_Ripple           = 0x00008000,   // Water effect shader
+	PF_RemoveYWrap      = 0x00010000,   // Forces clamp texture on Y
+	PF_ForceWrapX       = 0x00020000,   // Forces repeat texture on X
+	PF_ForceWrapY       = 0x00040000    // Forces repeat texture on Y
 };
 
 
@@ -187,7 +255,16 @@ enum ETextureFlags
 	TF_TRANSPARENT = 0x00000040,        // texture with some alpha == 0
 };
 
-typedef struct GLMipmap_s FTextureInfo;
+struct FTextureInfo
+{
+	UINT32 width, height;
+	UINT32 downloaded;
+	UINT32 format;
+
+	struct GLMipmap_s *texture;
+	struct FTextureInfo *prev, *next;
+};
+typedef struct FTextureInfo FTextureInfo;
 
 // jimita 14032019
 struct FLightInfo
@@ -224,6 +301,16 @@ enum hwdsetspecialstate
 
 typedef enum hwdsetspecialstate hwdspecialstate_t;
 
+// Lactozilla: Shader options
+enum hwdshaderoption
+{
+	HWD_SHADEROPTION_OFF,
+	HWD_SHADEROPTION_ON,
+	HWD_SHADEROPTION_NOCUSTOM,
+};
+
+typedef enum hwdshaderoption hwdshaderoption_t;
+
 // Lactozilla: Shader info
 // Generally set at the start of the frame.
 enum hwdshaderinfo
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 3d20cd9b077ea9e0fe06328f00f9385a5da6b244..8c92c6709252a0b91107279ea32d374b306a2ed4 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -68,10 +68,11 @@ static UINT8 softwaretranstogl_lo[11] = {  0, 12, 24, 36, 48, 60, 71, 83, 95,111
 // Notes            : x,y : positions relative to the original Doom resolution
 //                  : textes(console+score) + menus + status bar
 // -----------------+
-void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
+void HWR_DrawPatch(patch_t *gpatch, INT32 x, INT32 y, INT32 option)
 {
 	FOutVector v[4];
 	FBITFIELD flags;
+	GLPatch_t *hwrPatch;
 
 //  3--2
 //  | /|
@@ -84,6 +85,7 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 
 	// make patch ready in hardware cache
 	HWR_GetPatch(gpatch);
+	hwrPatch = ((GLPatch_t *)gpatch->hardware);
 
 	switch (option & V_SCALEPATCHMASK)
 	{
@@ -103,17 +105,17 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 	if (option & V_NOSCALESTART)
 		sdupx = sdupy = 2.0f;
 
-	v[0].x = v[3].x = (x*sdupx-SHORT(gpatch->leftoffset)*pdupx)/vid.width - 1;
-	v[2].x = v[1].x = (x*sdupx+(SHORT(gpatch->width)-SHORT(gpatch->leftoffset))*pdupx)/vid.width - 1;
-	v[0].y = v[1].y = 1-(y*sdupy-SHORT(gpatch->topoffset)*pdupy)/vid.height;
-	v[2].y = v[3].y = 1-(y*sdupy+(SHORT(gpatch->height)-SHORT(gpatch->topoffset))*pdupy)/vid.height;
+	v[0].x = v[3].x = (x*sdupx-(gpatch->leftoffset)*pdupx)/vid.width - 1;
+	v[2].x = v[1].x = (x*sdupx+(gpatch->width-gpatch->leftoffset)*pdupx)/vid.width - 1;
+	v[0].y = v[1].y = 1-(y*sdupy-(gpatch->topoffset)*pdupy)/vid.height;
+	v[2].y = v[3].y = 1-(y*sdupy+(gpatch->height-gpatch->topoffset)*pdupy)/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 = gpatch->max_s;
+	v[2].s = v[1].s = hwrPatch->max_s;
 	v[0].t = v[1].t = 0.0f;
-	v[2].t = v[3].t = gpatch->max_t;
+	v[2].t = v[3].t = hwrPatch->max_t;
 
 	flags = PF_Translucent|PF_NoDepthTest;
 
@@ -126,13 +128,14 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 	HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
-void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap)
+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];
 	FBITFIELD flags;
 	float cx = FIXED_TO_FLOAT(x);
 	float cy = FIXED_TO_FLOAT(y);
 	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
+	GLPatch_t *hwrPatch;
 
 //  3--2
 //  | /|
@@ -151,6 +154,8 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 	else
 		HWR_GetMappedPatch(gpatch, colormap);
 
+	hwrPatch = ((GLPatch_t *)gpatch->hardware);
+
 	dupx = (float)vid.dupx;
 	dupy = (float)vid.dupy;
 
@@ -181,13 +186,13 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 
 		// left offset
 		if (option & V_FLIP)
-			offsetx = (float)(SHORT(gpatch->width) - SHORT(gpatch->leftoffset)) * fscalew;
+			offsetx = (float)(gpatch->width - gpatch->leftoffset) * fscalew;
 		else
-			offsetx = (float)SHORT(gpatch->leftoffset) * fscalew;
+			offsetx = (float)(gpatch->leftoffset) * fscalew;
 
 		// top offset
 		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
-		offsety = (float)SHORT(gpatch->topoffset) * fscaleh;
+		offsety = (float)(gpatch->topoffset) * fscaleh;
 
 		if ((option & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
 		{
@@ -277,17 +282,14 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			// cx and cy are possibly *slightly* off from float maths
 			// This is done before here compared to software because we directly alter cx and cy to centre
-			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
+			if (cx >= -0.1f && cx <= 0.1f && gpatch->width == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && gpatch->height == BASEVIDHEIGHT)
 			{
-				// Need to temporarily cache the real patch to get the colour of the top left pixel
-				patch_t *realpatch = W_CacheSoftwarePatchNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				const column_t *column = (const column_t *)((const UINT8 *)(gpatch->columns) + (gpatch->columnofs[0]));
 				if (!column->topdelta)
 				{
 					const UINT8 *source = (const UINT8 *)(column) + 3;
 					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
 				}
-				Z_Free(realpatch);
 			}
 			// centre screen
 			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
@@ -317,13 +319,13 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 
 	if (pscale != FRACUNIT || (splitscreen && option & V_PERPLAYER))
 	{
-		fwidth = (float)SHORT(gpatch->width) * fscalew * dupx;
-		fheight = (float)SHORT(gpatch->height) * fscaleh * dupy;
+		fwidth = (float)(gpatch->width) * fscalew * dupx;
+		fheight = (float)(gpatch->height) * fscaleh * dupy;
 	}
 	else
 	{
-		fwidth = (float)SHORT(gpatch->width) * dupx;
-		fheight = (float)SHORT(gpatch->height) * dupy;
+		fwidth = (float)(gpatch->width) * dupx;
+		fheight = (float)(gpatch->height) * dupy;
 	}
 
 	// positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1
@@ -345,17 +347,17 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 
 	if (option & V_FLIP)
 	{
-		v[0].s = v[3].s = gpatch->max_s;
+		v[0].s = v[3].s = hwrPatch->max_s;
 		v[2].s = v[1].s = 0.0f;
 	}
 	else
 	{
 		v[0].s = v[3].s = 0.0f;
-		v[2].s = v[1].s = gpatch->max_s;
+		v[2].s = v[1].s = hwrPatch->max_s;
 	}
 
 	v[0].t = v[1].t = 0.0f;
-	v[2].t = v[3].t = gpatch->max_t;
+	v[2].t = v[3].t = hwrPatch->max_t;
 
 	flags = PF_Translucent|PF_NoDepthTest;
 
@@ -380,13 +382,14 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 		HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
-void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
+void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
 {
 	FOutVector v[4];
 	FBITFIELD flags;
 	float cx = FIXED_TO_FLOAT(x);
 	float cy = FIXED_TO_FLOAT(y);
 	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
+	GLPatch_t *hwrPatch;
 
 //  3--2
 //  | /|
@@ -399,6 +402,7 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 
 	// make patch ready in hardware cache
 	HWR_GetPatch(gpatch);
+	hwrPatch = ((GLPatch_t *)gpatch->hardware);
 
 	dupx = (float)vid.dupx;
 	dupy = (float)vid.dupy;
@@ -423,8 +427,8 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 
 	// fuck it, no GL support for croppedpatch v_perplayer right now. it's not like it's accessible to Lua or anything, and we only use it for menus...
 
-	cy -= (float)SHORT(gpatch->topoffset) * fscale;
-	cx -= (float)SHORT(gpatch->leftoffset) * fscale;
+	cy -= (float)(gpatch->topoffset) * fscale;
+	cx -= (float)(gpatch->leftoffset) * fscale;
 
 	if (!(option & V_NOSCALESTART))
 	{
@@ -436,17 +440,14 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			// cx and cy are possibly *slightly* off from float maths
 			// This is done before here compared to software because we directly alter cx and cy to centre
-			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
+			if (cx >= -0.1f && cx <= 0.1f && gpatch->width == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && gpatch->height == BASEVIDHEIGHT)
 			{
-				// Need to temporarily cache the real patch to get the colour of the top left pixel
-				patch_t *realpatch = W_CacheSoftwarePatchNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				const column_t *column = (const column_t *)((const UINT8 *)(gpatch->columns) + (gpatch->columnofs[0]));
 				if (!column->topdelta)
 				{
 					const UINT8 *source = (const UINT8 *)(column) + 3;
 					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
 				}
-				Z_Free(realpatch);
 			}
 			// centre screen
 			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
@@ -469,11 +470,11 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 	fwidth = w;
 	fheight = h;
 
-	if (fwidth > SHORT(gpatch->width))
-		fwidth = SHORT(gpatch->width);
+	if (fwidth > gpatch->width)
+		fwidth = gpatch->width;
 
-	if (fheight > SHORT(gpatch->height))
-		fheight = SHORT(gpatch->height);
+	if (fheight > gpatch->height)
+		fheight = gpatch->height;
 
 	if (pscale != FRACUNIT)
 	{
@@ -503,17 +504,17 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
-	v[0].s = v[3].s = ((sx  )/(float)SHORT(gpatch->width) )*gpatch->max_s;
-	if (sx + w > SHORT(gpatch->width))
-		v[2].s = v[1].s = gpatch->max_s;
+	v[0].s = v[3].s = ((sx)/(float)(gpatch->width))*hwrPatch->max_s;
+	if (sx + w > gpatch->width)
+		v[2].s = v[1].s = hwrPatch->max_s - ((sx+w)/(float)(gpatch->width))*hwrPatch->max_s;
 	else
-		v[2].s = v[1].s = ((sx+w)/(float)SHORT(gpatch->width) )*gpatch->max_s;
+		v[2].s = v[1].s = ((sx+w)/(float)(gpatch->width))*hwrPatch->max_s;
 
-	v[0].t = v[1].t = ((sy  )/(float)SHORT(gpatch->height))*gpatch->max_t;
-	if (sy + h > SHORT(gpatch->height))
-		v[2].t = v[3].t = gpatch->max_t;
+	v[0].t = v[1].t = ((sy)/(float)(gpatch->height))*hwrPatch->max_t;
+	if (sy + h > gpatch->height)
+		v[2].t = v[3].t = hwrPatch->max_t - ((sy+h)/(float)(gpatch->height))*hwrPatch->max_t;
 	else
-		v[2].t = v[3].t = ((sy+h)/(float)SHORT(gpatch->height))*gpatch->max_t;
+		v[2].t = v[3].t = ((sy+h)/(float)(gpatch->height))*hwrPatch->max_t;
 
 	flags = PF_Translucent|PF_NoDepthTest;
 
@@ -541,7 +542,7 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 void HWR_DrawPic(INT32 x, INT32 y, lumpnum_t lumpnum)
 {
 	FOutVector      v[4];
-	const GLPatch_t    *patch;
+	const patch_t    *patch;
 
 	// make pic ready in hardware cache
 	patch = HWR_GetPic(lumpnum);
@@ -558,10 +559,10 @@ void HWR_DrawPic(INT32 x, INT32 y, lumpnum_t lumpnum)
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
-	v[0].s = v[3].s =  0;
-	v[2].s = v[1].s =  patch->max_s;
-	v[0].t = v[1].t =  0;
-	v[2].t = v[3].t =  patch->max_t;
+	v[0].s = v[3].s = 0;
+	v[2].s = v[1].s = ((GLPatch_t *)patch->hardware)->max_s;
+	v[0].t = v[1].t = 0;
+	v[2].t = v[3].t = ((GLPatch_t *)patch->hardware)->max_t;
 
 
 	//Hurdler: Boris, the same comment as above... but maybe for pics
@@ -570,7 +571,7 @@ void HWR_DrawPic(INT32 x, INT32 y, lumpnum_t lumpnum)
 	// But then, the question is: why not 0 instead of PF_Masked ?
 	// or maybe PF_Environment ??? (like what I said above)
 	// BP: PF_Environment don't change anything ! and 0 is undifined
-	HWD.pfnDrawPolygon(NULL, v, 4, PF_Translucent | PF_NoDepthTest | PF_Clip | PF_NoZClip);
+	HWD.pfnDrawPolygon(NULL, v, 4, PF_Translucent | PF_NoDepthTest);
 }
 
 // ==========================================================================
@@ -638,7 +639,7 @@ void HWR_DrawFlatFill (INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum
 	v[0].t = v[1].t = (float)((y & flatflag)/dflatsize);
 	v[2].t = v[3].t = (float)(v[0].t + h/dflatsize);
 
-	HWR_LiterallyGetFlat(flatlumpnum);
+	HWR_GetRawFlat(flatlumpnum);
 
 	//Hurdler: Boris, the same comment as above... but maybe for pics
 	// it not a problem since they don't have any transparent pixel
@@ -934,19 +935,19 @@ void HWR_DrawViewBorder(INT32 clearlines)
 	INT32 top, side;
 	INT32 baseviewwidth, baseviewheight;
 	INT32 basewindowx, basewindowy;
-	GLPatch_t *patch;
+	patch_t *patch;
 
-//    if (gr_viewwidth == vid.width)
+//    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(gr_viewwidth), vid.fdupx)); //(cv_viewsize.value * BASEVIDWIDTH/10)&~7;
-	baseviewheight = FixedInt(FixedDiv(FLOAT_TO_FIXED(gr_viewheight), vid.fdupy));
-	top = FixedInt(FixedDiv(FLOAT_TO_FIXED(gr_baseviewwindowy), vid.fdupy));
-	side = FixedInt(FixedDiv(FLOAT_TO_FIXED(gr_viewwindowx), vid.fdupx));
+	baseviewwidth =  FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_viewwidth), vid.fdupx)); //(cv_viewsize.value * BASEVIDWIDTH/10)&~7;
+	baseviewheight = FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_viewheight), vid.fdupy));
+	top = FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_baseviewwindowy), vid.fdupy));
+	side = FixedInt(FixedDiv(FLOAT_TO_FIXED(gl_viewwindowx), vid.fdupx));
 
 	// top
 	HWR_DrawFlatFill(0, 0,
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 6f039cc3a0d0bad5791f5eeaabe4a79796e3f339..da4ee861435dee90e9cf7dc87ec22009b66128ff 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -37,11 +37,12 @@ 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);
 EXPORT void HWRAPI(DrawIndexedTriangles) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPts, FBITFIELD PolyFlags, UINT32 *IndexArray);
-EXPORT void HWRAPI(RenderSkyDome) (INT32 tex, INT32 texture_width, INT32 texture_height, FTransform transform);
+EXPORT void HWRAPI(RenderSkyDome) (gl_sky_t *sky);
 EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags);
 EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask, FBOOLEAN DepthMask, FRGBAFloat *ClearColor);
-EXPORT void HWRAPI(SetTexture) (FTextureInfo *TexInfo);
-EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *TexInfo);
+EXPORT void HWRAPI(SetTexture) (GLMipmap_t *TexInfo);
+EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *TexInfo);
+EXPORT void HWRAPI(DeleteTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 *dst_data);
 EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip);
 EXPORT void HWRAPI(ClearMipMapCache) (void);
@@ -68,14 +69,13 @@ EXPORT void HWRAPI(DrawScreenFinalTexture) (int width, int height);
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
 
 // jimita
-EXPORT boolean HWRAPI(LoadShaders) (void);
-EXPORT void HWRAPI(KillShaders) (void);
-EXPORT void HWRAPI(SetShader) (int shader);
+EXPORT boolean HWRAPI(CompileShaders) (void);
+EXPORT void HWRAPI(CleanShaders) (void);
+EXPORT void HWRAPI(SetShader) (int type);
 EXPORT void HWRAPI(UnSetShader) (void);
 
 EXPORT void HWRAPI(SetShaderInfo) (hwdshaderinfo_t info, INT32 value);
-EXPORT void HWRAPI(LoadCustomShader) (int number, char *shader, size_t size, boolean fragment);
-EXPORT boolean HWRAPI(InitCustomShaders) (void);
+EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boolean isfragment);
 
 // ==========================================================================
 //                                      HWR DRIVER OBJECT, FOR CLIENT PROGRAM
@@ -96,6 +96,7 @@ struct hwdriver_s
 	ClearBuffer         pfnClearBuffer;
 	SetTexture          pfnSetTexture;
 	UpdateTexture       pfnUpdateTexture;
+	DeleteTexture       pfnDeleteTexture;
 	ReadRect            pfnReadRect;
 	GClipRect           pfnGClipRect;
 	ClearMipMapCache    pfnClearMipMapCache;
@@ -120,14 +121,13 @@ struct hwdriver_s
 	MakeScreenFinalTexture  pfnMakeScreenFinalTexture;
 	DrawScreenFinalTexture  pfnDrawScreenFinalTexture;
 
-	LoadShaders         pfnLoadShaders;
-	KillShaders         pfnKillShaders;
+	CompileShaders      pfnCompileShaders;
+	CleanShaders        pfnCleanShaders;
 	SetShader           pfnSetShader;
 	UnSetShader         pfnUnSetShader;
 
 	SetShaderInfo       pfnSetShaderInfo;
 	LoadCustomShader    pfnLoadCustomShader;
-	InitCustomShaders   pfnInitCustomShaders;
 };
 
 extern struct hwdriver_s hwdriver;
diff --git a/src/hardware/hw_glide.h b/src/hardware/hw_glide.h
deleted file mode 100644
index d0eeebaeb7399fa5f4c97f13bd12bf1a59cce7eb..0000000000000000000000000000000000000000
--- a/src/hardware/hw_glide.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// SONIC ROBO BLAST 2
-//-----------------------------------------------------------------------------
-// Copyright (C) 1998-2000 by DooM Legacy Team.
-//
-// 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_glide.h
-/// \brief  Declaration needed by Glide renderer
-///	!!! To be replaced by our own def in the future !!!
-
-#ifndef _GLIDE_H_
-#define _GLIDE_H_
-
-#ifndef __GLIDE_H__
-
-typedef unsigned long   FxU32;
-typedef long            FxI32;
-
-typedef FxI32 GrTextureFormat_t;
-#define GR_TEXFMT_ALPHA_8               0x2 /* (0..0xFF) alpha     */
-#define GR_TEXFMT_INTENSITY_8           0x3 /* (0..0xFF) intensity */
-#define GR_TEXFMT_ALPHA_INTENSITY_44    0x4
-#define GR_TEXFMT_P_8                   0x5 /* 8-bit palette */
-#define GR_TEXFMT_RGB_565               0xa
-#define GR_TEXFMT_ARGB_1555             0xb
-#define GR_TEXFMT_ARGB_4444             0xc
-#define GR_TEXFMT_ALPHA_INTENSITY_88    0xd
-#define GR_TEXFMT_AP_88                 0xe /* 8-bit alpha 8-bit palette */
-#define GR_RGBA                         0x6 // 32 bit RGBA !
-
-typedef struct
-{
-	GrTextureFormat_t format;
-	void              *data;
-} GrTexInfo;
-
-#endif // __GLIDE_H__ (defined in <glide.h>)
-
-#endif // _GLIDE_H_
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index d8ea7c7a33b7655e9c1375dfb17f646df53bbf32..2aba622481d618a4e8f5d2d499e420e74123b9ce 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -59,22 +59,36 @@ typedef struct
 
 // needed for sprite rendering
 // equivalent of the software renderer's vissprites
-typedef struct gr_vissprite_s
+typedef struct gl_vissprite_s
 {
 	float x1, x2;
-	float tz, ty;
-	//lumpnum_t patchlumpnum;
-	GLPatch_t *gpatch;
-	boolean flip;
-	UINT8 translucency;       //alpha level 0-255
-	mobj_t *mobj;
+	float z1, z2;
+	float gz, gzt;
+
+	float tz;
+	float tracertz; // for MF2_LINKDRAW sprites, this contains tracer's tz for use in sorting
+
+	float scale;
+	float shadowheight, shadowscale;
+
+	float spritexscale, spriteyscale;
+	float spritexoffset, spriteyoffset;
+
+	UINT32 renderflags;
+	UINT8 rotateflags;
+
+	boolean flip, vflip;
 	boolean precip; // Tails 08-25-2002
-	boolean vflip;
-   //Hurdler: 25/04/2000: now support colormap in hardware mode
+	boolean rotated;
+	UINT8 translucency;       //alpha level 0-255
+
+	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	UINT8 *colormap;
 	INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing
-	float z1, z2;
-} gr_vissprite_t;
+
+	patch_t *gpatch;
+	mobj_t *mobj; // NOTE: This is a precipmobj_t if precip is true !!! Watch out.
+} gl_vissprite_t;
 
 // --------
 // hw_bsp.c
@@ -85,25 +99,36 @@ extern size_t addsubsector;
 void HWR_InitPolyPool(void);
 void HWR_FreePolyPool(void);
 
+void HWR_FreeExtraSubsectors(void);
+
 // --------
 // hw_cache.c
 // --------
-void HWR_InitTextureCache(void);
-void HWR_FreeTextureCache(void);
-void HWR_FreeMipmapCache(void);
-void HWR_FreeExtraSubsectors(void);
+void HWR_InitMapTextures(void);
+void HWR_LoadMapTextures(size_t pnumtextures);
+void HWR_FreeMapTextures(void);
+
+patch_t *HWR_GetCachedGLPatchPwad(UINT16 wad, UINT16 lump);
+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);
-void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum);
-GLTexture_t *HWR_GetTexture(INT32 tex);
-void HWR_GetPatch(GLPatch_t *gpatch);
-void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap);
+void HWR_GetRawFlat(lumpnum_t flatlumpnum);
+
+void HWR_FreeTexture(patch_t *patch);
+void HWR_FreeTextureData(patch_t *patch);
+void HWR_FreeTextureColormaps(patch_t *patch);
+void HWR_ClearAllTextures(void);
+void HWR_FreeColormapCache(void);
 void HWR_UnlockCachedPatch(GLPatch_t *gpatch);
-GLPatch_t *HWR_GetPic(lumpnum_t lumpnum);
+
 void HWR_SetPalette(RGBA_t *palette);
-GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wad, UINT16 lump);
-GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum);
-void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
+
 
 // --------
 // hw_draw.c
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index a685994ccd6275e92b9965303a7db669738e0187..93c61f4e7f9d2357cd323555f41631d3bd17bb09 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -35,8 +35,7 @@
 
 #define DL_HIGH_QUALITY
 //#define STATICLIGHT  //Hurdler: TODO!
-//#define LIGHTMAPFLAGS  (PF_Masked|PF_Clip|PF_NoAlphaTest)  // debug see overdraw
-#define LIGHTMAPFLAGS (PF_Modulated|PF_Additive|PF_Clip)
+#define LIGHTMAPFLAGS (PF_Modulated|PF_AdditiveSource)
 
 #ifdef ALAM_LIGHTING
 static dynlights_t view_dynlights[2]; // 2 players in splitscreen mode
@@ -254,6 +253,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_SIGN
 	&lspr[NOLIGHT],     // SPR_SPIK
 	&lspr[NOLIGHT],     // SPR_SFLM
+	&lspr[NOLIGHT],     // SPR_TFLM
 	&lspr[NOLIGHT],     // SPR_USPK
 	&lspr[NOLIGHT],     // SPR_WSPK
 	&lspr[NOLIGHT],     // SPR_WSPB
@@ -821,7 +821,7 @@ void HWR_WallLighting(FOutVector *wlVerts)
 {
 	int             i, j;
 
-	// dynlights->nb == 0 if cv_grdynamiclighting.value is not set
+	// dynlights->nb == 0 if cv_gldynamiclighting.value is not set
 	for (j = 0; j < dynlights->nb; j++)
 	{
 		FVector         inter;
@@ -970,7 +970,7 @@ static lumpnum_t coronalumpnum = LUMPERROR;
 // --------------------------------------------------------------------------
 // coronas lighting
 // --------------------------------------------------------------------------
-void HWR_DoCoronasLighting(FOutVector *outVerts, gr_vissprite_t *spr)
+void HWR_DoCoronasLighting(FOutVector *outVerts, gl_vissprite_t *spr)
 {
 	light_t   *p_lspr;
 
@@ -985,7 +985,7 @@ void HWR_DoCoronasLighting(FOutVector *outVerts, gr_vissprite_t *spr)
 		p_lspr = &lspr[ROCKETEXP_L];
 	}
 
-	if (cv_grcoronas.value && (p_lspr->type & CORONA_SPR))
+	if (cv_glcoronas.value && (p_lspr->type & CORONA_SPR))
 	{ // it's an object which emits light
 		FOutVector      light[4];
 		FSurfaceInfo    Surf;
@@ -1010,7 +1010,7 @@ void HWR_DoCoronasLighting(FOutVector *outVerts, gr_vissprite_t *spr)
 		}
 		if (size > p_lspr->corona_radius)
 			size = p_lspr->corona_radius;
-		size *= FIXED_TO_FLOAT(cv_grcoronasize.value<<1);
+		size *= FIXED_TO_FLOAT(cv_glcoronasize.value<<1);
 
 		// compute position doing average
 		for (i = 0; i < 4; i++)
@@ -1056,7 +1056,7 @@ void HWR_DoCoronasLighting(FOutVector *outVerts, gr_vissprite_t *spr)
 
 		HWR_GetPic(coronalumpnum);  /// \todo use different coronas
 
-		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_Additive | PF_Clip | PF_Corona | PF_NoDepthTest);
+		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_AdditiveSource | PF_Corona | PF_NoDepthTest);
 	}
 }
 #endif
@@ -1067,7 +1067,7 @@ void HWR_DrawCoronas(void)
 {
 	int       j;
 
-	if (!cv_grcoronas.value || dynlights->nb <= 0 || coronalumpnum == LUMPERROR)
+	if (!cv_glcoronas.value || dynlights->nb <= 0 || coronalumpnum == LUMPERROR)
 		return;
 
 	HWR_GetPic(coronalumpnum);  /// \todo use different coronas
@@ -1121,7 +1121,7 @@ void HWR_DrawCoronas(void)
 		}
 		if (size > p_lspr->corona_radius)
 			size = p_lspr->corona_radius;
-		size = (float)(FIXED_TO_FLOAT(cv_grcoronasize.value<<1)*size);
+		size = (float)(FIXED_TO_FLOAT(cv_glcoronasize.value<<1)*size);
 
 		// put light little forward the sprite so there is no
 		// z-buffer problem (coplanar polygons)
@@ -1144,7 +1144,7 @@ void HWR_DrawCoronas(void)
 		light[3].y = cy+size*1.33f;
 		light[3].s = 0.0f;   light[3].t = 1.0f;
 
-		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_Additive | PF_Clip | PF_NoDepthTest | PF_Corona);
+		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_AdditiveSource | PF_NoDepthTest | PF_Corona);
 	}
 }
 #endif
@@ -1170,13 +1170,13 @@ void HWR_SetLights(int viewnumber)
 // Add a light for dynamic lighting
 // The light position is already transformed execpt for mlook
 // --------------------------------------------------------------------------
-void HWR_DL_AddLight(gr_vissprite_t *spr, GLPatch_t *patch)
+void HWR_DL_AddLight(gl_vissprite_t *spr, GLPatch_t *patch)
 {
 	light_t   *p_lspr;
 
 	//Hurdler: moved here because it's better;-)
 	(void)patch;
-	if (!cv_grdynamiclighting.value)
+	if (!cv_gldynamiclighting.value)
 		return;
 
 	if (!spr->mobj)
@@ -1193,7 +1193,7 @@ void HWR_DL_AddLight(gr_vissprite_t *spr, GLPatch_t *patch)
 	p_lspr = t_lspr[spr->mobj->sprite];
 	if (!(p_lspr->type & DYNLIGHT_SPR))
 		return;
-	if ((p_lspr->type != LIGHT_SPR) || cv_grstaticlighting.value)
+	if ((p_lspr->type != LIGHT_SPR) || cv_glstaticlighting.value)
 		return;
 
 	LIGHT_POS(dynlights->nb).x = FIXED_TO_FLOAT(spr->mobj->x);
@@ -1229,10 +1229,10 @@ static void HWR_SetLight(void)
 {
 	int    i, j;
 
-	if (!lightmappatch.mipmap->downloaded && !lightmappatch.mipmap->grInfo.data)
+	if (!lightmappatch.mipmap->downloaded && !lightmappatch.mipmap->data)
 	{
 
-		UINT16 *Data = Z_Malloc(129*128*sizeof (UINT16), PU_HWRCACHE, &lightmappatch.mipmap->grInfo.data);
+		UINT16 *Data = Z_Malloc(129*128*sizeof (UINT16), PU_HWRCACHE, &lightmappatch.mipmap->data);
 
 		for (i = 0; i < 128; i++)
 		{
@@ -1245,7 +1245,7 @@ static void HWR_SetLight(void)
 					Data[i*128+j] = 0;
 			}
 		}
-		lightmappatch.mipmap->grInfo.format = GR_TEXFMT_ALPHA_INTENSITY_88;
+		lightmappatch.mipmap->format = GL_TEXFMT_ALPHA_INTENSITY_88;
 
 		lightmappatch.width = 128;
 		lightmappatch.height = 128;
@@ -1256,7 +1256,7 @@ static void HWR_SetLight(void)
 	HWD.pfnSetTexture(lightmappatch.mipmap);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(lightmappatch.mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(lightmappatch.mipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
 //**********************************************************
@@ -1265,8 +1265,8 @@ static void HWR_SetLight(void)
 
 #ifdef STATICLIGHT
 // is this really necessary?
-static sector_t *lgr_backsector;
-static seg_t *lgr_curline;
+static sector_t *lgl_backsector;
+static seg_t *lgl_curline;
 #endif
 
 // p1 et p2 c'est le deux bou du seg en float
@@ -1304,27 +1304,27 @@ static void HWR_AddLightMapForLine(int lightnum, seg_t *line)
 	*/
 	FVector             p1,p2;
 
-	lgr_curline = line;
-	lgr_backsector = line->backsector;
+	lgl_curline = line;
+	lgl_backsector = line->backsector;
 
 	// 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 ( lgr_backsector->ceilingpic == gr_frontsector->ceilingpic
-	  && lgr_backsector->floorpic == gr_frontsector->floorpic
-	  && lgr_backsector->lightlevel == gr_frontsector->lightlevel
-	  && lgr_curline->sidedef->midtexture == 0)
+	if ( lgl_backsector->ceilingpic == gl_frontsector->ceilingpic
+	  && lgl_backsector->floorpic == gl_frontsector->floorpic
+	  && lgl_backsector->lightlevel == gl_frontsector->lightlevel
+	  && lgl_curline->sidedef->midtexture == 0)
 	{
 		return;
 	}
 */
 
-	p1.y = FIXED_TO_FLOAT(lgr_curline->v1->y);
-	p1.x = FIXED_TO_FLOAT(lgr_curline->v1->x);
-	p2.y = FIXED_TO_FLOAT(lgr_curline->v2->y);
-	p2.x = FIXED_TO_FLOAT(lgr_curline->v2->x);
+	p1.y = FIXED_TO_FLOAT(lgl_curline->v1->y);
+	p1.x = FIXED_TO_FLOAT(lgl_curline->v1->x);
+	p2.y = FIXED_TO_FLOAT(lgl_curline->v2->y);
+	p2.x = FIXED_TO_FLOAT(lgl_curline->v2->x);
 
 	// check bbox of the seg
 //	if (CircleTouchBBox(&p1, &p2, &LIGHT_POS(lightnum), DL_RADIUS(lightnum))==false)
diff --git a/src/hardware/hw_light.h b/src/hardware/hw_light.h
index 3b12f9c8715e5de6acfca21b3e7953b1030af88a..fed7db47f2a67e6b81f82bfe2e97048594ce43be 100644
--- a/src/hardware/hw_light.h
+++ b/src/hardware/hw_light.h
@@ -24,7 +24,7 @@
 #define DL_MAX_LIGHT 256 // maximum number of lights (extra lights are ignored)
 
 void HWR_InitLight(void);
-void HWR_DL_AddLight(gr_vissprite_t *spr, GLPatch_t *patch);
+void HWR_DL_AddLight(gl_vissprite_t *spr, GLPatch_t *patch);
 void HWR_PlaneLighting(FOutVector *clVerts, int nrClipVerts);
 void HWR_WallLighting(FOutVector *wlVerts);
 void HWR_ResetLights(void);
@@ -33,7 +33,7 @@ void HWR_SetLights(int viewnumber);
 #ifdef NEWCORONAS
 void HWR_DrawCoronas(void);
 #else
-void HWR_DoCoronasLighting(FOutVector *outVerts, gr_vissprite_t *spr);
+void HWR_DoCoronasLighting(FOutVector *outVerts, gl_vissprite_t *spr);
 #endif
 
 typedef struct
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index ffa676e329e525119e0089f63332b3e02481ebff..8820027f177bae01941db7cdd16100ee5461e3c4 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -27,6 +27,7 @@
 #include "../p_world.h"
 #include "../r_local.h"
 #include "../r_patch.h"
+#include "../r_picformats.h"
 #include "../r_bsp.h"
 #include "../d_clisrv.h"
 #include "../w_wad.h"
@@ -78,18 +79,18 @@ boolean drawsky = true;
 #define FIELDOFVIEW ANGLE_90
 #define ABS(x) ((x) < 0 ? -(x) : (x))
 
-static angle_t gr_clipangle;
+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 gr_viewangletox[FINEANGLES/2];
+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 gr_xtoviewangle[MAXVIDWIDTH+1];
+static angle_t gl_xtoviewangle[MAXVIDWIDTH+1];
 
 // ==========================================================================
 //                                                                    GLOBALS
@@ -107,24 +108,24 @@ static angle_t gr_xtoviewangle[MAXVIDWIDTH+1];
 //#define NOCRAPPYMLOOK
 
 // base values set at SetViewSize
-static float gr_basecentery;
+static float gl_basecentery;
 
-float gr_baseviewwindowy, gr_basewindowcentery;
-float gr_viewwidth, gr_viewheight; // viewport clipping boundaries (screen coords)
-float gr_viewwindowx;
+float gl_baseviewwindowy, gl_basewindowcentery;
+float gl_viewwidth, gl_viewheight; // viewport clipping boundaries (screen coords)
+float gl_viewwindowx;
 
-static float gr_centerx, gr_centery;
-static float gr_viewwindowy; // top left corner of view window
-static float gr_windowcenterx; // center of view window, for projection
-static float gr_windowcentery;
+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 gr_pspritexscale, gr_pspriteyscale;
+static float gl_pspritexscale, gl_pspriteyscale;
 
-static seg_t *gr_curline;
-static side_t *gr_sidedef;
-static line_t *gr_linedef;
-static sector_t *gr_frontsector;
-static sector_t *gr_backsector;
+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
@@ -136,35 +137,38 @@ FTransform atransform;
 static fixed_t dup_viewx, dup_viewy, dup_viewz;
 static angle_t dup_viewangle;
 
-static float gr_viewx, gr_viewy, gr_viewz;
-static float gr_viewsin, gr_viewcos;
+static float gl_viewx, gl_viewy, gl_viewz;
+static float gl_viewsin, gl_viewcos;
 
 // Maybe not necessary with the new T&L code (needs to be checked!)
-static float gr_viewludsin, gr_viewludcos; // look up down kik test
-static float gr_fovlud;
+static float gl_viewludsin, gl_viewludcos; // look up down kik test
+static float gl_fovlud;
 
-static angle_t gr_aimingangle;
+static angle_t gl_aimingangle;
 static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean skybox);
 
 // Render stats
-int rs_hw_nodesorttime = 0;
-int rs_hw_nodedrawtime = 0;
-int rs_hw_spritesorttime = 0;
-int rs_hw_spritedrawtime = 0;
+precise_t ps_hw_skyboxtime = 0;
+precise_t ps_hw_nodesorttime = 0;
+precise_t ps_hw_nodedrawtime = 0;
+precise_t ps_hw_spritesorttime = 0;
+precise_t ps_hw_spritedrawtime = 0;
 
 // Render stats for batching
-int rs_hw_numpolys = 0;
-int rs_hw_numverts = 0;
-int rs_hw_numcalls = 0;
-int rs_hw_numshaders = 0;
-int rs_hw_numtextures = 0;
-int rs_hw_numpolyflags = 0;
-int rs_hw_numcolors = 0;
-int rs_hw_batchsorttime = 0;
-int rs_hw_batchdrawtime = 0;
-
-boolean gr_shadersavailable = true;
-
+int ps_hw_numpolys = 0;
+int ps_hw_numverts = 0;
+int ps_hw_numcalls = 0;
+int ps_hw_numshaders = 0;
+int ps_hw_numtextures = 0;
+int ps_hw_numpolyflags = 0;
+int ps_hw_numcolors = 0;
+precise_t ps_hw_batchsorttime = 0;
+precise_t ps_hw_batchdrawtime = 0;
+
+boolean gl_init = false;
+boolean gl_maploaded = false;
+boolean gl_sessioncommandsadded = false;
+boolean gl_shadersavailable = true;
 
 // ==========================================================================
 // Lighting
@@ -179,7 +183,7 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col
 	fade_color.rgba = (colormap != NULL) ? (UINT32)colormap->fadergba : GL_DEFAULTFOG;
 
 	// Crappy backup coloring if you can't do shaders
-	if (!cv_grshaders.value || !gr_shadersavailable)
+	if (!cv_glshaders.value || !gl_shadersavailable)
 	{
 		// be careful, this may get negative for high lightlevel values.
 		float tint_alpha, fade_alpha;
@@ -215,6 +219,9 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col
 		poly_color.s.blue = (UINT8)blue;
 	}
 
+	// Clamp the light level, since it can sometimes go out of the 0-255 range from animations
+	light_level = min(max(light_level, 0), 255);
+
 	Surface->PolyColor.rgba = poly_color.rgba;
 	Surface->TintColor.rgba = tint_color.rgba;
 	Surface->FadeColor.rgba = fade_color.rgba;
@@ -230,7 +237,7 @@ UINT8 HWR_FogBlockAlpha(INT32 light, extracolormap_t *colormap) // Let's see if
 
 	realcolor.rgba = (colormap != NULL) ? colormap->rgba : GL_DEFAULTMIX;
 
-	if (cv_grshaders.value && gr_shadersavailable)
+	if (cv_glshaders.value && gl_shadersavailable)
 	{
 		surfcolor.s.alpha = (255 - light);
 	}
@@ -257,12 +264,12 @@ static FUINT HWR_CalcWallLight(FUINT lightnum, fixed_t v1x, fixed_t v1y, fixed_t
 {
 	INT16 finallight = lightnum;
 
-	if (cv_grfakecontrast.value != 0)
+	if (cv_glfakecontrast.value != 0)
 	{
 		const UINT8 contrast = 8;
 		fixed_t extralight = 0;
 
-		if (cv_grfakecontrast.value == 2) // Smooth setting
+		if (cv_glfakecontrast.value == 2) // Smooth setting
 		{
 			extralight = (-(contrast<<FRACBITS) +
 			FixedDiv(AngleFixed(R_PointToAngle2(0, 0,
@@ -296,12 +303,12 @@ static FUINT HWR_CalcSlopeLight(FUINT lightnum, angle_t dir, fixed_t delta)
 {
 	INT16 finallight = lightnum;
 
-	if (cv_grfakecontrast.value != 0 && cv_grslopecontrast.value != 0)
+	if (cv_glfakecontrast.value != 0 && cv_glslopecontrast.value != 0)
 	{
 		const UINT8 contrast = 8;
 		fixed_t extralight = 0;
 
-		if (cv_grfakecontrast.value == 2) // Smooth setting
+		if (cv_glfakecontrast.value == 2) // Smooth setting
 		{
 			fixed_t dirmul = abs(FixedDiv(AngleFixed(dir) - (180<<FRACBITS), 180<<FRACBITS));
 
@@ -356,7 +363,6 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	float fflatwidth = 64.0f, fflatheight = 64.0f;
 	INT32 flatflag = 63;
 	boolean texflat = false;
-	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
 	FSurfaceInfo    Surf;
@@ -382,10 +388,10 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	}
 	else
 	{
-		if (gr_frontsector->f_slope && !isceiling)
-			slope = gr_frontsector->f_slope;
-		else if (gr_frontsector->c_slope && isceiling)
-			slope = gr_frontsector->c_slope;
+		if (gl_frontsector->f_slope && !isceiling)
+			slope = gl_frontsector->f_slope;
+		else if (gl_frontsector->c_slope && isceiling)
+			slope = gl_frontsector->c_slope;
 	}
 
 	// Set fixedheight to the slope's height from our viewpoint, if we have a slope
@@ -411,16 +417,9 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	// set texture for polygon
 	if (levelflat != NULL)
 	{
-		if (levelflat->type == LEVELFLAT_TEXTURE)
+		if (levelflat->type == LEVELFLAT_FLAT)
 		{
-			fflatwidth = textures[levelflat->u.texture.num]->width;
-			fflatheight = textures[levelflat->u.texture.num]->height;
-			texflat = true;
-		}
-		else if (levelflat->type == LEVELFLAT_FLAT)
-		{
-			len = W_LumpLength(levelflat->u.flat.lumpnum);
-
+			size_t len = W_LumpLength(levelflat->u.flat.lumpnum);
 			switch (len)
 			{
 				case 4194304: // 2048x2048 lump
@@ -445,9 +444,22 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 					fflatwidth = fflatheight = 64.0f;
 					break;
 			}
-
 			flatflag = ((INT32)fflatwidth)-1;
 		}
+		else
+		{
+			if (levelflat->type == LEVELFLAT_TEXTURE)
+			{
+				fflatwidth = textures[levelflat->u.texture.num]->width;
+				fflatheight = textures[levelflat->u.texture.num]->height;
+			}
+			else if (levelflat->type == LEVELFLAT_PATCH || levelflat->type == LEVELFLAT_PNG)
+			{
+				fflatwidth = levelflat->width;
+				fflatheight = levelflat->height;
+			}
+			texflat = true;
+		}
 	}
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
@@ -472,19 +484,19 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 			angle = FOFsector->ceilingpic_angle;
 		}
 	}
-	else if (gr_frontsector)
+	else if (gl_frontsector)
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->floor_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->floor_yoffs)/fflatheight;
-			angle = gr_frontsector->floorpic_angle;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->floor_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->floor_yoffs)/fflatheight;
+			angle = gl_frontsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->ceiling_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->ceiling_yoffs)/fflatheight;
-			angle = gr_frontsector->ceilingpic_angle;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceiling_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceiling_yoffs)/fflatheight;
+			angle = gl_frontsector->ceilingpic_angle;
 		}
 	}
 
@@ -492,7 +504,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	if (angle) // Only needs to be done if there's an altered angle
 	{
 
-		angle = (InvAngle(angle)+ANGLE_180)>>ANGLETOFINESHIFT;
+		angle = (InvAngle(angle))>>ANGLETOFINESHIFT;
 
 		// This needs to be done so that it scrolls in a different direction after rotation like software
 		/*tempxsow = FLOAT_TO_FIXED(scrollx);
@@ -526,8 +538,6 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 		{\
 			tempxsow = FLOAT_TO_FIXED(vert->s);\
 			tempytow = FLOAT_TO_FIXED(vert->t);\
-			if (texflat)\
-				tempytow = -tempytow;\
 			vert->s = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINECOSINE(angle)) - FixedMul(tempytow, FINESINE(angle))));\
 			vert->t = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINESINE(angle)) + FixedMul(tempytow, FINECOSINE(angle))));\
 		}\
@@ -560,11 +570,11 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 		PolyFlags |= PF_Masked|PF_Modulated;
 
 	if (PolyFlags & PF_Fog)
-		shader = 6;	// fog shader
+		shader = SHADER_FOG;	// fog shader
 	else if (PolyFlags & PF_Ripple)
-		shader = 5;	// water shader
+		shader = SHADER_WATER;	// water shader
 	else
-		shader = 1;	// floor shader
+		shader = SHADER_FLOOR;	// floor shader
 
 	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, PolyFlags, shader, false);
 
@@ -608,9 +618,9 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 					vy = y1 + yd * j / numplanes;
 					SETUP3DVERT((&horizonpts[1]), vx, vy);
 
-					dist = sqrtf(powf(vx - gr_viewx, 2) + powf(vy - gr_viewy, 2));
-					vx = (vx - gr_viewx) * renderdist / dist + gr_viewx;
-					vy = (vy - gr_viewy) * renderdist / dist + gr_viewy;
+					dist = sqrtf(powf(vx - gl_viewx, 2) + powf(vy - gl_viewy, 2));
+					vx = (vx - gl_viewx) * renderdist / dist + gl_viewx;
+					vy = (vy - gl_viewy) * renderdist / dist + gl_viewy;
 					SETUP3DVERT((&horizonpts[0]), vx, vy);
 
 					// Right side
@@ -618,21 +628,21 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 					vy = y1 + yd * (j+1) / numplanes;
 					SETUP3DVERT((&horizonpts[2]), vx, vy);
 
-					dist = sqrtf(powf(vx - gr_viewx, 2) + powf(vy - gr_viewy, 2));
-					vx = (vx - gr_viewx) * renderdist / dist + gr_viewx;
-					vy = (vy - gr_viewy) * renderdist / dist + gr_viewy;
+					dist = sqrtf(powf(vx - gl_viewx, 2) + powf(vy - gl_viewy, 2));
+					vx = (vx - gl_viewx) * renderdist / dist + gl_viewx;
+					vy = (vy - gl_viewy) * renderdist / dist + gl_viewy;
 					SETUP3DVERT((&horizonpts[3]), vx, vy);
 
 					// Horizon fills
-					vx = (horizonpts[0].x - gr_viewx) * farrenderdist / renderdist + gr_viewx;
-					vy = (horizonpts[0].z - gr_viewy) * farrenderdist / renderdist + gr_viewy;
+					vx = (horizonpts[0].x - gl_viewx) * farrenderdist / renderdist + gl_viewx;
+					vy = (horizonpts[0].z - gl_viewy) * farrenderdist / renderdist + gl_viewy;
 					SETUP3DVERT((&horizonpts[5]), vx, vy);
-					horizonpts[5].y = gr_viewz;
+					horizonpts[5].y = gl_viewz;
 
-					vx = (horizonpts[3].x - gr_viewx) * farrenderdist / renderdist + gr_viewx;
-					vy = (horizonpts[3].z - gr_viewy) * farrenderdist / renderdist + gr_viewy;
+					vx = (horizonpts[3].x - gl_viewx) * farrenderdist / renderdist + gl_viewx;
+					vy = (horizonpts[3].z - gl_viewy) * farrenderdist / renderdist + gl_viewy;
 					SETUP3DVERT((&horizonpts[4]), vx, vy);
-					horizonpts[4].y = gr_viewz;
+					horizonpts[4].y = gl_viewz;
 
 					// Draw
 					HWR_ProcessPolygon(&Surf, horizonpts, 6, PolyFlags, shader, true);
@@ -687,100 +697,73 @@ static void HWR_RenderSkyPlane(extrasubsector_t *xsub, fixed_t fixedheight)
 		v3d->z = pv->y;
 	}
 
-	HWD.pfnDrawPolygon(NULL, planeVerts, nrPlaneVerts,
-	 PF_Clip|PF_Invisible|PF_NoTexture|PF_Occlude);
+	HWD.pfnDrawPolygon(NULL, planeVerts, nrPlaneVerts, PF_Invisible|PF_NoTexture|PF_Occlude);
 }
 #endif //polysky
 
 #endif //doplanes
 
-/*
-   wallVerts order is :
-		3--2
-		| /|
-		|/ |
-		0--1
-*/
-#ifdef WALLSPLATS
-static void HWR_DrawSegsSplats(FSurfaceInfo * pSurf)
+FBITFIELD HWR_GetBlendModeFlag(INT32 ast)
 {
-	FOutVector wallVerts[4];
-	wallsplat_t *splat;
-	GLPatch_t *gpatch;
-	fixed_t i;
-	// seg bbox
-	fixed_t segbbox[4];
-
-	M_ClearBox(segbbox);
-	M_AddToBox(segbbox,
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->x),
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->y));
-	M_AddToBox(segbbox,
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->x),
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->y));
-
-	splat = (wallsplat_t *)gr_curline->linedef->splats;
-	for (; splat; splat = splat->next)
-	{
-		//BP: don't draw splat extern to this seg
-		//    this is quick fix best is explain in logboris.txt at 12-4-2000
-		if (!M_PointInBox(segbbox,splat->v1.x,splat->v1.y) && !M_PointInBox(segbbox,splat->v2.x,splat->v2.y))
-			continue;
-
-		gpatch = W_CachePatchNum(splat->patch, PU_PATCH);
-		HWR_GetPatch(gpatch);
-
-		wallVerts[0].x = wallVerts[3].x = FIXED_TO_FLOAT(splat->v1.x);
-		wallVerts[0].z = wallVerts[3].z = FIXED_TO_FLOAT(splat->v1.y);
-		wallVerts[2].x = wallVerts[1].x = FIXED_TO_FLOAT(splat->v2.x);
-		wallVerts[2].z = wallVerts[1].z = FIXED_TO_FLOAT(splat->v2.y);
+	switch (ast)
+	{
+		case AST_ADD:
+			return PF_Additive;
+		case AST_SUBTRACT:
+			return PF_Subtractive;
+		case AST_REVERSESUBTRACT:
+			return PF_ReverseSubtract;
+		case AST_MODULATE:
+			return PF_Multiplicative;
+		default:
+			return PF_Translucent;
+	}
 
-		i = splat->top;
-		if (splat->yoffset)
-			i += *splat->yoffset;
+	return 0;
+}
 
-		wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(i)+(gpatch->height>>1);
-		wallVerts[0].y = wallVerts[1].y = FIXED_TO_FLOAT(i)-(gpatch->height>>1);
+UINT8 HWR_GetTranstableAlpha(INT32 transtablenum)
+{
+	transtablenum = max(min(transtablenum, tr_trans90), 0);
 
-		wallVerts[3].s = wallVerts[3].t = wallVerts[2].s = wallVerts[0].t = 0.0f;
-		wallVerts[1].s = wallVerts[1].t = wallVerts[2].t = wallVerts[0].s = 1.0f;
+	switch (transtablenum)
+	{
+		case 0          : return 0xff;
+		case tr_trans10 : return 0xe6;
+		case tr_trans20 : return 0xcc;
+		case tr_trans30 : return 0xb3;
+		case tr_trans40 : return 0x99;
+		case tr_trans50 : return 0x80;
+		case tr_trans60 : return 0x66;
+		case tr_trans70 : return 0x4c;
+		case tr_trans80 : return 0x33;
+		case tr_trans90 : return 0x19;
+	}
 
-		switch (splat->flags & SPLATDRAWMODE_MASK)
-		{
-			case SPLATDRAWMODE_OPAQUE :
-				pSurf.PolyColor.s.alpha = 0xff;
-				i = PF_Translucent;
-				break;
-			case SPLATDRAWMODE_TRANS :
-				pSurf.PolyColor.s.alpha = 128;
-				i = PF_Translucent;
-				break;
-			case SPLATDRAWMODE_SHADE :
-				pSurf.PolyColor.s.alpha = 0xff;
-				i = PF_Substractive;
-				break;
-		}
+	return 0xff;
+}
 
-		HWD.pfnSetShader(2);	// wall shader
-		HWD.pfnDrawPolygon(&pSurf, wallVerts, 4, i|PF_Modulated|PF_Decal);
+FBITFIELD HWR_SurfaceBlend(INT32 style, INT32 transtablenum, FSurfaceInfo *pSurf)
+{
+	if (!transtablenum)
+	{
+		pSurf->PolyColor.s.alpha = 0xff;
+		return PF_Masked;
 	}
+
+	pSurf->PolyColor.s.alpha = HWR_GetTranstableAlpha(transtablenum);
+	return HWR_GetBlendModeFlag(style);
 }
-#endif
 
 FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf)
 {
-	switch (transtablenum)
+	if (!transtablenum)
 	{
-		case tr_trans10 : pSurf->PolyColor.s.alpha = 0xe6;return  PF_Translucent;
-		case tr_trans20 : pSurf->PolyColor.s.alpha = 0xcc;return  PF_Translucent;
-		case tr_trans30 : pSurf->PolyColor.s.alpha = 0xb3;return  PF_Translucent;
-		case tr_trans40 : pSurf->PolyColor.s.alpha = 0x99;return  PF_Translucent;
-		case tr_trans50 : pSurf->PolyColor.s.alpha = 0x80;return  PF_Translucent;
-		case tr_trans60 : pSurf->PolyColor.s.alpha = 0x66;return  PF_Translucent;
-		case tr_trans70 : pSurf->PolyColor.s.alpha = 0x4c;return  PF_Translucent;
-		case tr_trans80 : pSurf->PolyColor.s.alpha = 0x33;return  PF_Translucent;
-		case tr_trans90 : pSurf->PolyColor.s.alpha = 0x19;return  PF_Translucent;
+		pSurf->PolyColor.s.alpha = 0x00;
+		return PF_Masked;
 	}
+
+	pSurf->PolyColor.s.alpha = HWR_GetTranstableAlpha(transtablenum);
 	return PF_Translucent;
 }
 
@@ -790,19 +773,21 @@ static void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, I
 // Wall generation from subsector segs
 // ==========================================================================
 
+/*
+   wallVerts order is :
+		3--2
+		| /|
+		|/ |
+		0--1
+*/
+
 //
 // HWR_ProjectWall
 //
 static void HWR_ProjectWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blendmode, INT32 lightlevel, extracolormap_t *wallcolormap)
 {
 	HWR_Lighting(pSurf, lightlevel, wallcolormap);
-
-	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode|PF_Modulated|PF_Occlude, 2, false); // wall shader
-
-#ifdef WALLSPLATS
-	if (gr_curline->linedef->splats && cv_splats.value)
-		HWR_DrawSegsSplats(pSurf);
-#endif
+	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode|PF_Modulated|PF_Occlude, SHADER_WALL, false); // wall shader
 }
 
 // ==========================================================================
@@ -818,7 +803,7 @@ 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 = gr_xtoviewangle[x];
+	angle_t pclipangle = gl_xtoviewangle[x];
 
 	// a segment of a polygon
 	v1x  = v1->x;
@@ -838,7 +823,7 @@ static float HWR_ClipViewSegment(INT32 x, polyvertex_t *v1, polyvertex_t *v2)
 	// calc the frac along the polygon segment,
 	//num = (v2x - v1x)*v2dy + (v1y - v2y)*v2dx;
 	//num = -v1x * v2dy + v1y * v2dx;
-	num = (gr_viewx - v1x)*v2dy + (v1y - gr_viewy)*v2dx;
+	num = (gl_viewx - v1x)*v2dy + (v1y - gl_viewy)*v2dx;
 
 	return num / den;
 }
@@ -1056,9 +1041,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 	fixed_t worldhighslope = 0, worldlowslope = 0;
 	fixed_t v1x, v1y, v2x, v2y;
 
-	GLTexture_t *grTex = NULL;
+	GLMapTexture_t *grTex = NULL;
 	float cliplow = 0.0f, cliphigh = 0.0f;
-	INT32 gr_midtexture;
+	INT32 gl_midtexture;
 	fixed_t h, l; // 3D sides and 2s middle textures
 	fixed_t hS, lS;
 
@@ -1066,13 +1051,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 	extracolormap_t *colormap;
 	FSurfaceInfo Surf;
 
-	gr_sidedef = gr_curline->sidedef;
-	gr_linedef = gr_curline->linedef;
+	gl_sidedef = gl_curline->sidedef;
+	gl_linedef = gl_curline->linedef;
 
-	vs.x = ((polyvertex_t *)gr_curline->pv1)->x;
-	vs.y = ((polyvertex_t *)gr_curline->pv1)->y;
-	ve.x = ((polyvertex_t *)gr_curline->pv2)->x;
-	ve.y = ((polyvertex_t *)gr_curline->pv2)->y;
+	vs.x = ((polyvertex_t *)gl_curline->pv1)->x;
+	vs.y = ((polyvertex_t *)gl_curline->pv1)->y;
+	ve.x = ((polyvertex_t *)gl_curline->pv2)->x;
+	ve.y = ((polyvertex_t *)gl_curline->pv2)->y;
 
 	v1x = FLOAT_TO_FIXED(vs.x);
 	v1y = FLOAT_TO_FIXED(vs.y);
@@ -1083,8 +1068,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 	end1 = P_GetZAt(slope, v1x, v1y, normalheight); \
 	end2 = P_GetZAt(slope, v2x, v2y, normalheight);
 
-	SLOPEPARAMS(gr_frontsector->c_slope, worldtop,    worldtopslope,    gr_frontsector->ceilingheight)
-	SLOPEPARAMS(gr_frontsector->f_slope, worldbottom, worldbottomslope, gr_frontsector->floorheight)
+	SLOPEPARAMS(gl_frontsector->c_slope, worldtop,    worldtopslope,    gl_frontsector->ceilingheight)
+	SLOPEPARAMS(gl_frontsector->f_slope, worldbottom, worldbottomslope, gl_frontsector->floorheight)
 
 	// remember vertices ordering
 	//  3--2
@@ -1100,84 +1085,84 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 	// x offset the texture
 	{
-		fixed_t texturehpeg = gr_sidedef->textureoffset + gr_curline->offset;
+		fixed_t texturehpeg = gl_sidedef->textureoffset + gl_curline->offset;
 		cliplow = (float)texturehpeg;
-		cliphigh = (float)(texturehpeg + (gr_curline->flength*FRACUNIT));
+		cliphigh = (float)(texturehpeg + (gl_curline->flength*FRACUNIT));
 	}
 
-	lightnum = HWR_CalcWallLight(gr_frontsector->lightlevel, vs.x, vs.y, ve.x, ve.y);
-	colormap = gr_frontsector->extra_colormap;
+	lightnum = HWR_CalcWallLight(gl_frontsector->lightlevel, vs.x, vs.y, ve.x, ve.y);
+	colormap = gl_frontsector->extra_colormap;
 
-	if (gr_frontsector)
+	if (gl_frontsector)
 		Surf.PolyColor.s.alpha = 255;
 
-	if (gr_backsector)
+	if (gl_backsector)
 	{
-		INT32 gr_toptexture = 0, gr_bottomtexture = 0;
+		INT32 gl_toptexture = 0, gl_bottomtexture = 0;
 		// two sided line
 		boolean bothceilingssky = false; // turned on if both back and front ceilings are sky
 		boolean bothfloorssky = false; // likewise, but for floors
 
-		SLOPEPARAMS(gr_backsector->c_slope, worldhigh, worldhighslope, gr_backsector->ceilingheight)
-		SLOPEPARAMS(gr_backsector->f_slope, worldlow,  worldlowslope,  gr_backsector->floorheight)
+		SLOPEPARAMS(gl_backsector->c_slope, worldhigh, worldhighslope, gl_backsector->ceilingheight)
+		SLOPEPARAMS(gl_backsector->f_slope, worldlow,  worldlowslope,  gl_backsector->floorheight)
 #undef SLOPEPARAMS
 
 		// hack to allow height changes in outdoor areas
 		// This is what gets rid of the upper textures if there should be sky
-		if (gr_frontsector->ceilingpic == viewworld->skyflatnum
-			&& gr_backsector->ceilingpic == viewworld->skyflatnum)
+		if (gl_frontsector->ceilingpic == viewworld->skyflatnum
+			&& gl_backsector->ceilingpic  == viewworld->skyflatnum)
 		{
 			bothceilingssky = true;
 		}
 
 		// likewise, but for floors and upper textures
-		if (gr_frontsector->floorpic == viewworld->skyflatnum
-			&& gr_backsector->floorpic == viewworld->skyflatnum)
+		if (gl_frontsector->floorpic == viewworld->skyflatnum
+			&& gl_backsector->floorpic == viewworld->skyflatnum)
 		{
 			bothfloorssky = true;
 		}
 
 		if (!bothceilingssky)
-			gr_toptexture = R_GetTextureNum(gr_sidedef->toptexture);
+			gl_toptexture = R_GetTextureNum(gl_sidedef->toptexture);
 		if (!bothfloorssky)
-			gr_bottomtexture = R_GetTextureNum(gr_sidedef->bottomtexture);
+			gl_bottomtexture = R_GetTextureNum(gl_sidedef->bottomtexture);
 
 		// check TOP TEXTURE
-		if ((worldhighslope < worldtopslope || worldhigh < worldtop) && gr_toptexture)
+		if ((worldhighslope < worldtopslope || worldhigh < worldtop) && gl_toptexture)
 		{
 			{
 				fixed_t texturevpegtop; // top
 
-				grTex = HWR_GetTexture(gr_toptexture);
+				grTex = HWR_GetTexture(gl_toptexture);
 
 				// PEGGING
-				if (gr_linedef->flags & ML_DONTPEGTOP)
+				if (gl_linedef->flags & ML_DONTPEGTOP)
 					texturevpegtop = 0;
-				else if (gr_linedef->flags & ML_EFFECT1)
-					texturevpegtop = worldhigh + textureheight[gr_sidedef->toptexture] - worldtop;
+				else if (gl_linedef->flags & ML_EFFECT1)
+					texturevpegtop = worldhigh + textureheight[gl_sidedef->toptexture] - worldtop;
 				else
-					texturevpegtop = gr_backsector->ceilingheight + textureheight[gr_sidedef->toptexture] - gr_frontsector->ceilingheight;
+					texturevpegtop = gl_backsector->ceilingheight + textureheight[gl_sidedef->toptexture] - gl_frontsector->ceilingheight;
 
-				texturevpegtop += gr_sidedef->rowoffset;
+				texturevpegtop += gl_sidedef->rowoffset;
 
 				// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
-				texturevpegtop %= SHORT(textures[gr_toptexture]->height)<<FRACBITS;
+				texturevpegtop %= (textures[gl_toptexture]->height)<<FRACBITS;
 
 				wallVerts[3].t = wallVerts[2].t = texturevpegtop * grTex->scaleY;
-				wallVerts[0].t = wallVerts[1].t = (texturevpegtop + gr_frontsector->ceilingheight - gr_backsector->ceilingheight) * grTex->scaleY;
+				wallVerts[0].t = wallVerts[1].t = (texturevpegtop + gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * grTex->scaleY;
 				wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
 				wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
 
 				// Adjust t value for sloped walls
-				if (!(gr_linedef->flags & ML_EFFECT1))
+				if (!(gl_linedef->flags & ML_EFFECT1))
 				{
 					// Unskewed
-					wallVerts[3].t -= (worldtop - gr_frontsector->ceilingheight) * grTex->scaleY;
-					wallVerts[2].t -= (worldtopslope - gr_frontsector->ceilingheight) * grTex->scaleY;
-					wallVerts[0].t -= (worldhigh - gr_backsector->ceilingheight) * grTex->scaleY;
-					wallVerts[1].t -= (worldhighslope - gr_backsector->ceilingheight) * grTex->scaleY;
+					wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * grTex->scaleY;
+					wallVerts[2].t -= (worldtopslope - gl_frontsector->ceilingheight) * grTex->scaleY;
+					wallVerts[0].t -= (worldhigh - gl_backsector->ceilingheight) * grTex->scaleY;
+					wallVerts[1].t -= (worldhighslope - gl_backsector->ceilingheight) * grTex->scaleY;
 				}
-				else if (gr_linedef->flags & ML_DONTPEGTOP)
+				else if (gl_linedef->flags & ML_DONTPEGTOP)
 				{
 					// Skewed by top
 					wallVerts[0].t = (texturevpegtop + worldtop - worldhigh) * grTex->scaleY;
@@ -1198,10 +1183,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			wallVerts[2].y = FIXED_TO_FLOAT(worldtopslope);
 			wallVerts[1].y = FIXED_TO_FLOAT(worldhighslope);
 
-			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_toptexture, &Surf, FF_CUTLEVEL, NULL);
+			if (gl_frontsector->numlights)
+				HWR_SplitWall(gl_frontsector, wallVerts, gl_toptexture, &Surf, FF_CUTLEVEL, NULL);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
-				HWR_AddTransparentWall(wallVerts, &Surf, gr_toptexture, PF_Environment, false, lightnum, colormap);
+				HWR_AddTransparentWall(wallVerts, &Surf, gl_toptexture, PF_Environment, false, lightnum, colormap);
 			else
 				HWR_ProjectWall(wallVerts, &Surf, PF_Masked, lightnum, colormap);
 		}
@@ -1209,41 +1194,41 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		// check BOTTOM TEXTURE
 		if ((
 			worldlowslope > worldbottomslope ||
-            worldlow > worldbottom) && gr_bottomtexture) //only if VISIBLE!!!
+            worldlow > worldbottom) && gl_bottomtexture) //only if VISIBLE!!!
 		{
 			{
 				fixed_t texturevpegbottom = 0; // bottom
 
-				grTex = HWR_GetTexture(gr_bottomtexture);
+				grTex = HWR_GetTexture(gl_bottomtexture);
 
 				// PEGGING
-				if (!(gr_linedef->flags & ML_DONTPEGBOTTOM))
+				if (!(gl_linedef->flags & ML_DONTPEGBOTTOM))
 					texturevpegbottom = 0;
-				else if (gr_linedef->flags & ML_EFFECT1)
+				else if (gl_linedef->flags & ML_EFFECT1)
 					texturevpegbottom = worldbottom - worldlow;
 				else
-					texturevpegbottom = gr_frontsector->floorheight - gr_backsector->floorheight;
+					texturevpegbottom = gl_frontsector->floorheight - gl_backsector->floorheight;
 
-				texturevpegbottom += gr_sidedef->rowoffset;
+				texturevpegbottom += gl_sidedef->rowoffset;
 
 				// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
-				texturevpegbottom %= SHORT(textures[gr_bottomtexture]->height)<<FRACBITS;
+				texturevpegbottom %= (textures[gl_bottomtexture]->height)<<FRACBITS;
 
 				wallVerts[3].t = wallVerts[2].t = texturevpegbottom * grTex->scaleY;
-				wallVerts[0].t = wallVerts[1].t = (texturevpegbottom + gr_backsector->floorheight - gr_frontsector->floorheight) * grTex->scaleY;
+				wallVerts[0].t = wallVerts[1].t = (texturevpegbottom + gl_backsector->floorheight - gl_frontsector->floorheight) * grTex->scaleY;
 				wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
 				wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
 
 				// Adjust t value for sloped walls
-				if (!(gr_linedef->flags & ML_EFFECT1))
+				if (!(gl_linedef->flags & ML_EFFECT1))
 				{
 					// Unskewed
-					wallVerts[0].t -= (worldbottom - gr_frontsector->floorheight) * grTex->scaleY;
-					wallVerts[1].t -= (worldbottomslope - gr_frontsector->floorheight) * grTex->scaleY;
-					wallVerts[3].t -= (worldlow - gr_backsector->floorheight) * grTex->scaleY;
-					wallVerts[2].t -= (worldlowslope - gr_backsector->floorheight) * grTex->scaleY;
+					wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * grTex->scaleY;
+					wallVerts[1].t -= (worldbottomslope - gl_frontsector->floorheight) * grTex->scaleY;
+					wallVerts[3].t -= (worldlow - gl_backsector->floorheight) * grTex->scaleY;
+					wallVerts[2].t -= (worldlowslope - gl_backsector->floorheight) * grTex->scaleY;
 				}
-				else if (gr_linedef->flags & ML_DONTPEGBOTTOM)
+				else if (gl_linedef->flags & ML_DONTPEGBOTTOM)
 				{
 					// Skewed by bottom
 					wallVerts[0].t = wallVerts[1].t = (texturevpegbottom + worldlow - worldbottom) * grTex->scaleY;
@@ -1264,15 +1249,15 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			wallVerts[2].y = FIXED_TO_FLOAT(worldlowslope);
 			wallVerts[1].y = FIXED_TO_FLOAT(worldbottomslope);
 
-			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_bottomtexture, &Surf, FF_CUTLEVEL, NULL);
+			if (gl_frontsector->numlights)
+				HWR_SplitWall(gl_frontsector, wallVerts, gl_bottomtexture, &Surf, FF_CUTLEVEL, NULL);
 			else if (grTex->mipmap.flags & TF_TRANSPARENT)
-				HWR_AddTransparentWall(wallVerts, &Surf, gr_bottomtexture, PF_Environment, false, lightnum, colormap);
+				HWR_AddTransparentWall(wallVerts, &Surf, gl_bottomtexture, PF_Environment, false, lightnum, colormap);
 			else
 				HWR_ProjectWall(wallVerts, &Surf, PF_Masked, lightnum, colormap);
 		}
-		gr_midtexture = R_GetTextureNum(gr_sidedef->midtexture);
-		if (gr_midtexture)
+		gl_midtexture = R_GetTextureNum(gl_sidedef->midtexture);
+		if (gl_midtexture)
 		{
 			FBITFIELD blendmode;
 			sector_t *front, *back;
@@ -1280,19 +1265,19 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			fixed_t     texturevpeg = 0;
 			INT32 repeats;
 
-			if (gr_linedef->frontsector->heightsec != -1)
-				front = &sectors[gr_linedef->frontsector->heightsec];
+			if (gl_linedef->frontsector->heightsec != -1)
+				front = &sectors[gl_linedef->frontsector->heightsec];
 			else
-				front = gr_linedef->frontsector;
+				front = gl_linedef->frontsector;
 
-			if (gr_linedef->backsector->heightsec != -1)
-				back = &sectors[gr_linedef->backsector->heightsec];
+			if (gl_linedef->backsector->heightsec != -1)
+				back = &sectors[gl_linedef->backsector->heightsec];
 			else
-				back = gr_linedef->backsector;
+				back = gl_linedef->backsector;
 
-			if (gr_sidedef->repeatcnt)
-				repeats = 1 + gr_sidedef->repeatcnt;
-			else if (gr_linedef->flags & ML_EFFECT5)
+			if (gl_sidedef->repeatcnt)
+				repeats = 1 + gl_sidedef->repeatcnt;
+			else if (gl_linedef->flags & ML_EFFECT5)
 			{
 				fixed_t high, low;
 
@@ -1306,8 +1291,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				else
 					low = back->floorheight;
 
-				repeats = (high - low)/textureheight[gr_sidedef->midtexture];
-				if ((high-low)%textureheight[gr_sidedef->midtexture])
+				repeats = (high - low)/textureheight[gl_sidedef->midtexture];
+				if ((high-low)%textureheight[gl_sidedef->midtexture])
 					repeats++; // tile an extra time to fill the gap -- Monster Iestyn
 			}
 			else
@@ -1323,7 +1308,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			// NOTE: With polyobjects, whenever you need to check the properties of the polyobject sector it belongs to,
 			// you must use the linedef's backsector to be correct
 			// From CB
-			if (gr_curline->polyseg)
+			if (gl_curline->polyseg)
 			{
 				popentop = back->ceilingheight;
 				popenbottom = back->floorheight;
@@ -1334,33 +1319,33 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				popenbottom = max(worldbottom, worldlow);
 			}
 
-			if (gr_linedef->flags & ML_EFFECT2)
+			if (gl_linedef->flags & ML_EFFECT2)
 			{
-				if (!!(gr_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gr_linedef->flags & ML_EFFECT3))
+				if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
 				{
-					polybottom = max(front->floorheight, back->floorheight) + gr_sidedef->rowoffset;
-					polytop = polybottom + textureheight[gr_midtexture]*repeats;
+					polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset;
+					polytop = polybottom + textureheight[gl_midtexture]*repeats;
 				}
 				else
 				{
-					polytop = min(front->ceilingheight, back->ceilingheight) + gr_sidedef->rowoffset;
-					polybottom = polytop - textureheight[gr_midtexture]*repeats;
+					polytop = min(front->ceilingheight, back->ceilingheight) + gl_sidedef->rowoffset;
+					polybottom = polytop - textureheight[gl_midtexture]*repeats;
 				}
 			}
-			else if (!!(gr_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gr_linedef->flags & ML_EFFECT3))
+			else if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
 			{
-				polybottom = popenbottom + gr_sidedef->rowoffset;
-				polytop = polybottom + textureheight[gr_midtexture]*repeats;
+				polybottom = popenbottom + gl_sidedef->rowoffset;
+				polytop = polybottom + textureheight[gl_midtexture]*repeats;
 			}
 			else
 			{
-				polytop = popentop + gr_sidedef->rowoffset;
-				polybottom = polytop - textureheight[gr_midtexture]*repeats;
+				polytop = popentop + gl_sidedef->rowoffset;
+				polybottom = polytop - textureheight[gl_midtexture]*repeats;
 			}
 			// CB
 			// NOTE: With polyobjects, whenever you need to check the properties of the polyobject sector it belongs to,
 			// you must use the linedef's backsector to be correct
-			if (gr_curline->polyseg)
+			if (gl_curline->polyseg)
 			{
 				lowcut = polybottom;
 				highcut = polytop;
@@ -1377,12 +1362,12 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			{
 				// PEGGING
-				if (!!(gr_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gr_linedef->flags & ML_EFFECT3))
-					texturevpeg = textureheight[gr_sidedef->midtexture]*repeats - h + polybottom;
+				if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
+					texturevpeg = textureheight[gl_sidedef->midtexture]*repeats - h + polybottom;
 				else
 					texturevpeg = polytop - h;
 
-				grTex = HWR_GetTexture(gr_midtexture);
+				grTex = HWR_GetTexture(gl_midtexture);
 
 				wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 				wallVerts[0].t = wallVerts[1].t = (h - l + texturevpeg) * grTex->scaleY;
@@ -1400,9 +1385,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			{
 				fixed_t midtextureslant;
 
-				if (gr_linedef->flags & ML_EFFECT2)
+				if (gl_linedef->flags & ML_EFFECT2)
 					midtextureslant = 0;
-				else if (!!(gr_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gr_linedef->flags & ML_EFFECT3))
+				else if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
 					midtextureslant = worldlow < worldbottom
 							  ? worldbottomslope-worldbottom
 							  : worldlowslope-worldlow;
@@ -1427,8 +1412,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 				{
 					// PEGGING
-					if (!!(gr_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gr_linedef->flags & ML_EFFECT3))
-						texturevpeg = textureheight[gr_sidedef->midtexture]*repeats - h + polybottom;
+					if (!!(gl_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gl_linedef->flags & ML_EFFECT3))
+						texturevpeg = textureheight[gl_sidedef->midtexture]*repeats - h + polybottom;
 					else
 						texturevpeg = polytop - h;
 					wallVerts[2].t = texturevpeg * grTex->scaleY;
@@ -1439,37 +1424,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				wallVerts[1].y = FIXED_TO_FLOAT(l);
 			}
 
-			// set alpha for transparent walls (new boom and legacy linedef types)
+			// set alpha for transparent walls
 			// ooops ! this do not work at all because render order we should render it in backtofront order
-			switch (gr_linedef->special)
+			switch (gl_linedef->special)
 			{
-				case 900:
-					blendmode = HWR_TranstableToAlpha(tr_trans10, &Surf);
-					break;
-				case 901:
-					blendmode = HWR_TranstableToAlpha(tr_trans20, &Surf);
-					break;
-				case 902:
-					blendmode = HWR_TranstableToAlpha(tr_trans30, &Surf);
-					break;
-				case 903:
-					blendmode = HWR_TranstableToAlpha(tr_trans40, &Surf);
-					break;
-				case 904:
-					blendmode = HWR_TranstableToAlpha(tr_trans50, &Surf);
-					break;
-				case 905:
-					blendmode = HWR_TranstableToAlpha(tr_trans60, &Surf);
-					break;
-				case 906:
-					blendmode = HWR_TranstableToAlpha(tr_trans70, &Surf);
-					break;
-				case 907:
-					blendmode = HWR_TranstableToAlpha(tr_trans80, &Surf);
-					break;
-				case 908:
-					blendmode = HWR_TranstableToAlpha(tr_trans90, &Surf);
-					break;
 				//  Translucent
 				case 102:
 				case 121:
@@ -1490,43 +1448,46 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					blendmode = PF_Translucent;
 					break;
 				default:
-					blendmode = PF_Masked;
+					if (gl_linedef->alpha >= 0 && gl_linedef->alpha < FRACUNIT)
+						blendmode = HWR_TranstableToAlpha(R_GetLinedefTransTable(gl_linedef->alpha), &Surf);
+					else
+						blendmode = PF_Masked;
 					break;
 			}
 
-			if (gr_curline->polyseg && gr_curline->polyseg->translucency > 0)
+			if (gl_curline->polyseg && gl_curline->polyseg->translucency > 0)
 			{
-				if (gr_curline->polyseg->translucency >= NUMTRANSMAPS) // wall not drawn
+				if (gl_curline->polyseg->translucency >= NUMTRANSMAPS) // wall not drawn
 				{
 					Surf.PolyColor.s.alpha = 0x00; // This shouldn't draw anything regardless of blendmode
 					blendmode = PF_Masked;
 				}
 				else
-					blendmode = HWR_TranstableToAlpha(gr_curline->polyseg->translucency, &Surf);
+					blendmode = HWR_TranstableToAlpha(gl_curline->polyseg->translucency, &Surf);
 			}
 
-			if (gr_frontsector->numlights)
+			if (gl_frontsector->numlights)
 			{
 				if (!(blendmode & PF_Masked))
-					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_TRANSLUCENT, NULL);
+					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_TRANSLUCENT, NULL);
 				else
 				{
-					HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTLEVEL, NULL);
+					HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL);
 				}
 			}
 			else if (!(blendmode & PF_Masked))
-				HWR_AddTransparentWall(wallVerts, &Surf, gr_midtexture, blendmode, false, lightnum, colormap);
+				HWR_AddTransparentWall(wallVerts, &Surf, gl_midtexture, blendmode, false, lightnum, colormap);
 			else
 				HWR_ProjectWall(wallVerts, &Surf, blendmode, lightnum, colormap);
 		}
 
 		// Sky culling
 		// No longer so much a mess as before!
-		if (!gr_curline->polyseg) // Don't do it for polyobjects
+		if (!gl_curline->polyseg) // Don't do it for polyobjects
 		{
-			if (gr_frontsector->ceilingpic == viewworld->skyflatnum)
+			if (gl_frontsector->ceilingpic == viewworld->skyflatnum)
 			{
-				if (gr_backsector->ceilingpic != viewworld->skyflatnum) // don't cull if back sector is also sky
+				if (gl_backsector->ceilingpic != viewworld->skyflatnum) // don't cull if back sector is also sky
 				{
 					wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(INT32_MAX); // draw to top of map space
 					wallVerts[0].y = FIXED_TO_FLOAT(worldtop);
@@ -1535,9 +1496,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				}
 			}
 
-			if (gr_frontsector->floorpic == viewworld->skyflatnum)
+			if (gl_frontsector->floorpic == viewworld->skyflatnum)
 			{
-				if (gr_backsector->floorpic != viewworld->skyflatnum) // don't cull if back sector is also sky
+				if (gl_backsector->floorpic != viewworld->skyflatnum) // don't cull if back sector is also sky
 				{
 					wallVerts[3].y = FIXED_TO_FLOAT(worldbottom);
 					wallVerts[2].y = FIXED_TO_FLOAT(worldbottomslope);
@@ -1550,34 +1511,34 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 	else
 	{
 		// Single sided line... Deal only with the middletexture (if one exists)
-		gr_midtexture = R_GetTextureNum(gr_sidedef->midtexture);
-		if (gr_midtexture && gr_linedef->special != 41) // (Ignore horizon line for OGL)
+		gl_midtexture = R_GetTextureNum(gl_sidedef->midtexture);
+		if (gl_midtexture && gl_linedef->special != 41) // (Ignore horizon line for OGL)
 		{
 			{
 				fixed_t     texturevpeg;
 				// PEGGING
-				if ((gr_linedef->flags & (ML_DONTPEGBOTTOM|ML_EFFECT2)) == (ML_DONTPEGBOTTOM|ML_EFFECT2))
-					texturevpeg = gr_frontsector->floorheight + textureheight[gr_sidedef->midtexture] - gr_frontsector->ceilingheight + gr_sidedef->rowoffset;
-				else if (gr_linedef->flags & ML_DONTPEGBOTTOM)
-					texturevpeg = worldbottom + textureheight[gr_sidedef->midtexture] - worldtop + gr_sidedef->rowoffset;
+				if ((gl_linedef->flags & (ML_DONTPEGBOTTOM|ML_EFFECT2)) == (ML_DONTPEGBOTTOM|ML_EFFECT2))
+					texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset;
+				else if (gl_linedef->flags & ML_DONTPEGBOTTOM)
+					texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset;
 				else
 					// top of texture at top
-					texturevpeg = gr_sidedef->rowoffset;
+					texturevpeg = gl_sidedef->rowoffset;
 
-				grTex = HWR_GetTexture(gr_midtexture);
+				grTex = HWR_GetTexture(gl_midtexture);
 
 				wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
-				wallVerts[0].t = wallVerts[1].t = (texturevpeg + gr_frontsector->ceilingheight - gr_frontsector->floorheight) * grTex->scaleY;
+				wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * grTex->scaleY;
 				wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
 				wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
 
 				// Texture correction for slopes
-				if (gr_linedef->flags & ML_EFFECT2) {
-					wallVerts[3].t += (gr_frontsector->ceilingheight - worldtop) * grTex->scaleY;
-					wallVerts[2].t += (gr_frontsector->ceilingheight - worldtopslope) * grTex->scaleY;
-					wallVerts[0].t += (gr_frontsector->floorheight - worldbottom) * grTex->scaleY;
-					wallVerts[1].t += (gr_frontsector->floorheight - worldbottomslope) * grTex->scaleY;
-				} else if (gr_linedef->flags & ML_DONTPEGBOTTOM) {
+				if (gl_linedef->flags & ML_EFFECT2) {
+					wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * grTex->scaleY;
+					wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * grTex->scaleY;
+					wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * grTex->scaleY;
+					wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * grTex->scaleY;
+				} else if (gl_linedef->flags & ML_DONTPEGBOTTOM) {
 					wallVerts[3].t = wallVerts[0].t + (worldbottom-worldtop) * grTex->scaleY;
 					wallVerts[2].t = wallVerts[1].t + (worldbottomslope-worldtopslope) * grTex->scaleY;
 				} else {
@@ -1593,27 +1554,27 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			wallVerts[1].y = FIXED_TO_FLOAT(worldbottomslope);
 
 			// I don't think that solid walls can use translucent linedef types...
-			if (gr_frontsector->numlights)
-				HWR_SplitWall(gr_frontsector, wallVerts, gr_midtexture, &Surf, FF_CUTLEVEL, NULL);
+			if (gl_frontsector->numlights)
+				HWR_SplitWall(gl_frontsector, wallVerts, gl_midtexture, &Surf, FF_CUTLEVEL, NULL);
 			else
 			{
 				if (grTex->mipmap.flags & TF_TRANSPARENT)
-					HWR_AddTransparentWall(wallVerts, &Surf, gr_midtexture, PF_Environment, false, lightnum, colormap);
+					HWR_AddTransparentWall(wallVerts, &Surf, gl_midtexture, PF_Environment, false, lightnum, colormap);
 				else
 					HWR_ProjectWall(wallVerts, &Surf, PF_Masked, lightnum, colormap);
 			}
 		}
 
-		if (!gr_curline->polyseg)
+		if (!gl_curline->polyseg)
 		{
-			if (gr_frontsector->ceilingpic == viewworld->skyflatnum) // It's a single-sided line with sky for its sector
+			if (gl_frontsector->ceilingpic == viewworld->skyflatnum) // It's a single-sided line with sky for its sector
 			{
 				wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(INT32_MAX); // draw to top of map space
 				wallVerts[0].y = FIXED_TO_FLOAT(worldtop);
 				wallVerts[1].y = FIXED_TO_FLOAT(worldtopslope);
 				HWR_DrawSkyWall(wallVerts, &Surf);
 			}
-			if (gr_frontsector->floorpic == viewworld->skyflatnum)
+			if (gl_frontsector->floorpic == viewworld->skyflatnum)
 			{
 				wallVerts[3].y = FIXED_TO_FLOAT(worldbottom);
 				wallVerts[2].y = FIXED_TO_FLOAT(worldbottomslope);
@@ -1625,7 +1586,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 
 	//Hurdler: 3d-floors test
-	if (gr_frontsector && gr_backsector && gr_frontsector->tag != gr_backsector->tag && (gr_backsector->ffloors || gr_frontsector->ffloors))
+	if (gl_frontsector && gl_backsector && !Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags) && (gl_backsector->ffloors || gl_frontsector->ffloors))
 	{
 		ffloor_t * rover;
 		fixed_t    highcut = 0, lowcut = 0;
@@ -1635,13 +1596,25 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
         ///TODO add slope support (fixing cutoffs, proper wall clipping) - maybe just disable highcut/lowcut if either sector or FOF has a slope
         ///     to allow fun plane intersecting in OGL? But then people would abuse that and make software look bad. :C
-		highcut = gr_frontsector->ceilingheight < gr_backsector->ceilingheight ? gr_frontsector->ceilingheight : gr_backsector->ceilingheight;
-		lowcut = gr_frontsector->floorheight > gr_backsector->floorheight ? gr_frontsector->floorheight : gr_backsector->floorheight;
+		highcut = gl_frontsector->ceilingheight < gl_backsector->ceilingheight ? gl_frontsector->ceilingheight : gl_backsector->ceilingheight;
+		lowcut = gl_frontsector->floorheight > gl_backsector->floorheight ? gl_frontsector->floorheight : gl_backsector->floorheight;
 
-		if (gr_backsector->ffloors)
+		if (gl_backsector->ffloors)
 		{
-			for (rover = gr_backsector->ffloors; rover; rover = rover->next)
+			for (rover = gl_backsector->ffloors; rover; rover = rover->next)
 			{
+				boolean bothsides = false;
+				// Skip if it exists on both sectors.
+				ffloor_t * r2;
+				for (r2 = gl_frontsector->ffloors; r2; r2 = r2->next)
+					if (rover->master == r2->master)
+					{
+						bothsides = true;
+						break;
+					}
+
+				if (bothsides) continue;
+
 				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
 					continue;
 				if (!(rover->flags & FF_ALLSIDES) && rover->flags & FF_INVERTSIDES)
@@ -1653,7 +1626,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 				if (rover->master->flags & ML_TFERLINE)
 				{
-					size_t linenum = gr_curline->linedef-gr_backsector->lines[0];
+					size_t linenum = gl_curline->linedef-gl_backsector->lines[0];
 					newline = rover->master->frontsector->lines[0] + linenum;
 					texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
 				}
@@ -1662,9 +1635,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				hS = P_GetFFloorTopZAt   (rover, v2x, v2y);
 				l  = P_GetFFloorBottomZAt(rover, v1x, v1y);
 				lS = P_GetFFloorBottomZAt(rover, v2x, v2y);
-				if (!(*rover->t_slope) && !gr_frontsector->c_slope && !gr_backsector->c_slope && h > highcut)
+				if (!(*rover->t_slope) && !gl_frontsector->c_slope && !gl_backsector->c_slope && h > highcut)
 					h = hS = highcut;
-				if (!(*rover->b_slope) && !gr_frontsector->f_slope && !gr_backsector->f_slope && l < lowcut)
+				if (!(*rover->b_slope) && !gl_frontsector->f_slope && !gl_backsector->f_slope && l < lowcut)
 					l = lS = lowcut;
 				//Hurdler: HW code starts here
 				//FIXME: check if peging is correct
@@ -1699,7 +1672,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					else
 					{
 						texturevpeg = sides[rover->master->sidenum[0]].rowoffset;
-						attachtobottom = !!(gr_linedef->flags & ML_DONTPEGBOTTOM);
+						attachtobottom = !!(gl_linedef->flags & ML_DONTPEGBOTTOM);
 						slopeskew = !!(rover->master->flags & ML_DONTPEGTOP);
 					}
 
@@ -1744,8 +1717,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 					Surf.PolyColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel, rover->master->frontsector->extra_colormap);
 
-					if (gr_frontsector->numlights)
-						HWR_SplitWall(gr_frontsector, wallVerts, 0, &Surf, rover->flags, rover);
+					if (gl_frontsector->numlights)
+						HWR_SplitWall(gl_frontsector, wallVerts, 0, &Surf, rover->flags, rover);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -1759,8 +1732,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 						Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
 
-					if (gr_frontsector->numlights)
-						HWR_SplitWall(gr_frontsector, wallVerts, texnum, &Surf, rover->flags, rover);
+					if (gl_frontsector->numlights)
+						HWR_SplitWall(gl_frontsector, wallVerts, texnum, &Surf, rover->flags, rover);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -1772,10 +1745,22 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			}
 		}
 
-		if (gr_frontsector->ffloors) // Putting this seperate should allow 2 FOF sectors to be connected without too many errors? I think?
+		if (gl_frontsector->ffloors) // Putting this seperate should allow 2 FOF sectors to be connected without too many errors? I think?
 		{
-			for (rover = gr_frontsector->ffloors; rover; rover = rover->next)
+			for (rover = gl_frontsector->ffloors; rover; rover = rover->next)
 			{
+				boolean bothsides = false;
+				// Skip if it exists on both sectors.
+				ffloor_t * r2;
+				for (r2 = gl_backsector->ffloors; r2; r2 = r2->next)
+					if (rover->master == r2->master)
+					{
+						bothsides = true;
+						break;
+					}
+
+				if (bothsides) continue;
+
 				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
 					continue;
 				if (!(rover->flags & FF_ALLSIDES || rover->flags & FF_INVERTSIDES))
@@ -1787,7 +1772,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 				if (rover->master->flags & ML_TFERLINE)
 				{
-					size_t linenum = gr_curline->linedef-gr_backsector->lines[0];
+					size_t linenum = gl_curline->linedef-gl_backsector->lines[0];
 					newline = rover->master->frontsector->lines[0] + linenum;
 					texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
 				}
@@ -1795,9 +1780,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				hS = P_GetFFloorTopZAt   (rover, v2x, v2y);
 				l  = P_GetFFloorBottomZAt(rover, v1x, v1y);
 				lS = P_GetFFloorBottomZAt(rover, v2x, v2y);
-				if (!(*rover->t_slope) && !gr_frontsector->c_slope && !gr_backsector->c_slope && h > highcut)
+				if (!(*rover->t_slope) && !gl_frontsector->c_slope && !gl_backsector->c_slope && h > highcut)
 					h = hS = highcut;
-				if (!(*rover->b_slope) && !gr_frontsector->f_slope && !gr_backsector->f_slope && l < lowcut)
+				if (!(*rover->b_slope) && !gl_frontsector->f_slope && !gl_backsector->f_slope && l < lowcut)
 					l = lS = lowcut;
 				//Hurdler: HW code starts here
 				//FIXME: check if peging is correct
@@ -1844,8 +1829,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 					Surf.PolyColor.s.alpha = HWR_FogBlockAlpha(rover->master->frontsector->lightlevel, rover->master->frontsector->extra_colormap);
 
-					if (gr_backsector->numlights)
-						HWR_SplitWall(gr_backsector, wallVerts, 0, &Surf, rover->flags, rover);
+					if (gl_backsector->numlights)
+						HWR_SplitWall(gl_backsector, wallVerts, 0, &Surf, rover->flags, rover);
 					else
 						HWR_AddTransparentWall(wallVerts, &Surf, 0, blendmode, true, lightnum, colormap);
 				}
@@ -1859,8 +1844,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 						Surf.PolyColor.s.alpha = (UINT8)rover->alpha-1 > 255 ? 255 : rover->alpha-1;
 					}
 
-					if (gr_backsector->numlights)
-						HWR_SplitWall(gr_backsector, wallVerts, texnum, &Surf, rover->flags, rover);
+					if (gl_backsector->numlights)
+						HWR_SplitWall(gl_backsector, wallVerts, texnum, &Surf, rover->flags, rover);
 					else
 					{
 						if (blendmode != PF_Masked)
@@ -1899,10 +1884,10 @@ static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacks
 	if (afrontsector->f_slope || afrontsector->c_slope || abacksector->f_slope || abacksector->c_slope)
 	{
 		fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
-		v1x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->x);
-		v1y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->y);
-		v2x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->x);
-		v2y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->y);
+		v1x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->x);
+		v1y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->y);
+		v2x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->x);
+		v2y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->y);
 #define SLOPEPARAMS(slope, end1, end2, normalheight) \
 		end1 = P_GetZAt(slope, v1x, v1y, normalheight); \
 		end2 = P_GetZAt(slope, v2x, v2y, normalheight);
@@ -1973,17 +1958,17 @@ static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacks
 
 // hw_newend is one past the last valid seg
 static cliprange_t *   hw_newend;
-static cliprange_t     gr_solidsegs[MAXSEGS];
+static cliprange_t     gl_solidsegs[MAXSEGS];
 
 // needs fix: walls are incorrectly clipped one column less
-static consvar_t cv_grclipwalls = {"gr_clipwalls", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+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 = gr_solidsegs;start != hw_newend;start++)
+	for (start = gl_solidsegs;start != hw_newend;start++)
 	{
 		CONS_Debug(DBG_RENDER, "%d-%d|",start->first,start->last);
 	}
@@ -2001,7 +1986,7 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 
 	// Find the first range that touches the range
 	//  (adjacent pixels are touching).
-	start = gr_solidsegs;
+	start = gl_solidsegs;
 	while (start->last < first-1)
 		start++;
 
@@ -2029,14 +2014,14 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 		}
 
 		// There is a fragment above *start.
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{
 			if (!poorhack) HWR_StoreWallRange(first, last);
 			poorhack = true;
 		}
 		else
 		{
-			highfrac = HWR_ClipViewSegment(start->first+1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
+			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.
@@ -2053,15 +2038,15 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 	while (last >= (next+1)->first-1)
 	{
 		// There is a fragment between two posts.
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{
 			if (!poorhack) HWR_StoreWallRange(first,last);
 			poorhack = true;
 		}
 		else
 		{
-			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
-			highfrac = HWR_ClipViewSegment((next+1)->first+1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
+			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++;
@@ -2077,7 +2062,7 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 
 	if (first == next->first+1) // 1 line texture
 	{
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{
 			if (!poorhack) HWR_StoreWallRange(first,last);
 			poorhack = true;
@@ -2088,14 +2073,14 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 	else
 	{
 	// There is a fragment after *next.
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{
 			if (!poorhack) HWR_StoreWallRange(first,last);
 			poorhack = true;
 		}
 		else
 		{
-			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
+			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gl_curline->pv1, (polyvertex_t *)gl_curline->pv2);
 			HWR_StoreWallRange(lowfrac, 1);
 		}
 	}
@@ -2136,7 +2121,7 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 
 	// Find the first range that touches the range
 	//  (adjacent pixels are touching).
-	start = gr_solidsegs;
+	start = gl_solidsegs;
 	while (start->last < first - 1)
 		start++;
 
@@ -2150,7 +2135,7 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 		}
 
 		// There is a fragment above *start.
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{	//20/08/99: Changed by Hurdler (taken from faB's code)
 			if (!poorhack) HWR_StoreWallRange(0, 1);
 			poorhack = true;
@@ -2158,8 +2143,8 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 		else
 		{
 			highfrac = HWR_ClipViewSegment(min(start->first + 1,
-				start->last), (polyvertex_t *)gr_curline->pv1,
-				(polyvertex_t *)gr_curline->pv2);
+				start->last), (polyvertex_t *)gl_curline->pv1,
+				(polyvertex_t *)gl_curline->pv2);
 			HWR_StoreWallRange(0, highfrac);
 		}
 	}
@@ -2171,15 +2156,15 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 	while (last >= (start+1)->first-1)
 	{
 		// There is a fragment between two posts.
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{
 			if (!poorhack) HWR_StoreWallRange(0, 1);
 			poorhack = true;
 		}
 		else
 		{
-			lowfrac  = HWR_ClipViewSegment(max(start->last-1,start->first), (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
-			highfrac = HWR_ClipViewSegment(min((start+1)->first+1,(start+1)->last), (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
+			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++;
@@ -2190,7 +2175,7 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 
 	if (first == start->first+1) // 1 line texture
 	{
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{
 			if (!poorhack) HWR_StoreWallRange(0, 1);
 			poorhack = true;
@@ -2201,7 +2186,7 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 	else
 	{
 		// There is a fragment after *next.
-		if (!cv_grclipwalls.value)
+		if (!cv_glclipwalls.value)
 		{
 			if (!poorhack) HWR_StoreWallRange(0,1);
 			poorhack = true;
@@ -2209,8 +2194,8 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 		else
 		{
 			lowfrac = HWR_ClipViewSegment(max(start->last - 1,
-				start->first), (polyvertex_t *)gr_curline->pv1,
-				(polyvertex_t *)gr_curline->pv2);
+				start->first), (polyvertex_t *)gl_curline->pv1,
+				(polyvertex_t *)gl_curline->pv2);
 			HWR_StoreWallRange(lowfrac, 1);
 		}
 	}
@@ -2225,7 +2210,7 @@ static boolean HWR_ClipToSolidSegs(INT32 first, INT32 last)
 
 	// Find the first range that touches the range
 	//  (adjacent pixels are touching).
-	start = gr_solidsegs;
+	start = gl_solidsegs;
 	while (start->last < first-1)
 		start++;
 
@@ -2244,17 +2229,17 @@ static boolean HWR_ClipToSolidSegs(INT32 first, INT32 last)
 //
 static void HWR_ClearClipSegs(void)
 {
-	gr_solidsegs[0].first = -0x7fffffff;
-	gr_solidsegs[0].last = -1;
-	gr_solidsegs[1].first = vid.width; //viewwidth;
-	gr_solidsegs[1].last = 0x7fffffff;
-	hw_newend = gr_solidsegs+2;
+	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.
-// Notes            : gr_cursectorlight is set to the current subsector -> sector -> light value
+// Notes            : gl_cursectorlight is set to the current subsector -> sector -> light value
 //                  : (it may be mixed with the wall's own flat colour in the future ...)
 // -----------------+
 static void HWR_AddLine(seg_t * line)
@@ -2273,16 +2258,16 @@ static void HWR_AddLine(seg_t * line)
 	if (line->polyseg && !(line->polyseg->flags & POF_RENDERSIDES))
 		return;
 
-	gr_curline = line;
+	gl_curline = line;
 
-	v1x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->x);
-	v1y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->y);
-	v2x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->x);
-	v2y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->y);
+	v1x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->x);
+	v1y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->y);
+	v2x = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->x);
+	v2y = FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->y);
 
 	// OPTIMIZE: quickly reject orthogonal back sides.
-	angle1 = R_PointToAngle(v1x, v1y);
-	angle2 = R_PointToAngle(v2x, v2y);
+	angle1 = R_PointToAngle64(v1x, v1y);
+	angle2 = R_PointToAngle64(v2x, v2y);
 
 #ifdef NEWCLIP
 	 // PrBoom: Back side, i.e. backface culling - read: endAngle >= startAngle!
@@ -2310,27 +2295,27 @@ static void HWR_AddLine(seg_t * line)
 	angle1 -= dup_viewangle;
 	angle2 -= dup_viewangle;
 
-	tspan = angle1 + gr_clipangle;
-	if (tspan > 2*gr_clipangle)
+	tspan = angle1 + gl_clipangle;
+	if (tspan > 2*gl_clipangle)
 	{
-		tspan -= 2*gr_clipangle;
+		tspan -= 2*gl_clipangle;
 
 		// Totally off the left edge?
 		if (tspan >= span)
 			return;
 
-		angle1 = gr_clipangle;
+		angle1 = gl_clipangle;
 	}
-	tspan = gr_clipangle - angle2;
-	if (tspan > 2*gr_clipangle)
+	tspan = gl_clipangle - angle2;
+	if (tspan > 2*gl_clipangle)
 	{
-		tspan -= 2*gr_clipangle;
+		tspan -= 2*gl_clipangle;
 
 		// Totally off the left edge?
 		if (tspan >= span)
 			return;
 
-		angle2 = (angle_t)-(signed)gr_clipangle;
+		angle2 = (angle_t)-(signed)gl_clipangle;
 	}
 
 #if 0
@@ -2338,23 +2323,23 @@ static void HWR_AddLine(seg_t * line)
 		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-gr_viewx;
-		fy1 = ((polyvertex_t *)(line->pv1))->y-gr_viewy;
-		fy2 = (fx1 * gr_viewcos + fy1 * gr_viewsin);
+		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 = gr_windowcenterx + (fx1 * gr_viewsin - fy1 * gr_viewcos) * gr_centerx / fy2;
+			fx1 = gl_windowcenterx + (fx1 * gl_viewsin - fy1 * gl_viewcos) * gl_centerx / fy2;
 
-		fx2 = ((polyvertex_t *)(line->pv2))->x-gr_viewx;
-		fy2 = ((polyvertex_t *)(line->pv2))->y-gr_viewy;
-		fy1 = (fx2 * gr_viewcos + fy2 * gr_viewsin);
+		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 = gr_windowcenterx + (fx2 * gr_viewsin - fy2 * gr_viewcos) * gr_centerx / fy1;
+			fx2 = gl_windowcenterx + (fx2 * gl_viewsin - fy2 * gl_viewcos) * gl_centerx / fy1;
 
 		x1 = fx1+0.5f;
 		x2 = fx2+0.5f;
@@ -2365,8 +2350,8 @@ static void HWR_AddLine(seg_t * line)
 	angle1 = (angle1+ANGLE_90)>>ANGLETOFINESHIFT;
 	angle2 = (angle2+ANGLE_90)>>ANGLETOFINESHIFT;
 
-	x1 = gr_viewangletox[angle1];
-	x2 = gr_viewangletox[angle2];
+	x1 = gl_viewangletox[angle1];
+	x2 = gl_viewangletox[angle2];
 #endif
 	// Does not cross a pixel?
 //	if (x1 == x2)
@@ -2378,7 +2363,7 @@ static void HWR_AddLine(seg_t * line)
 */
 #endif
 
-	gr_backsector = line->backsector;
+	gl_backsector = line->backsector;
 
 #ifdef NEWCLIP
 	if (!line->backsector)
@@ -2389,26 +2374,26 @@ static void HWR_AddLine(seg_t * line)
     {
 		boolean bothceilingssky = false, bothfloorssky = false;
 
-		gr_backsector = R_FakeFlat(gr_backsector, &tempsec, NULL, NULL, true);
+		gl_backsector = R_FakeFlat(gl_backsector, &tempsec, NULL, NULL, true);
 
-		if (gr_backsector->ceilingpic == viewworld->skyflatnum && gr_frontsector->ceilingpic == viewworld->skyflatnum)
+		if (gl_backsector->ceilingpic == viewworld->skyflatnum && gl_frontsector->ceilingpic == viewworld->skyflatnum)
 			bothceilingssky = true;
-		if (gr_backsector->floorpic == viewworld->skyflatnum && gr_frontsector->floorpic == viewworld->skyflatnum)
+		if (gl_backsector->floorpic == viewworld->skyflatnum && gl_frontsector->floorpic == viewworld->skyflatnum)
 			bothfloorssky = true;
 
 		if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
 		{
 			if (!line->polyseg &&
 				!line->sidedef->midtexture
-				&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
-					|| (gr_frontsector->tag == gr_backsector->tag)))
+				&& ((!gl_frontsector->ffloors && !gl_backsector->ffloors)
+					|| Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags)))
 				return; // line is empty, don't even bother
 			// treat like wide open window instead
 			HWR_ProcessSeg(); // Doesn't need arguments because they're defined globally :D
 			return;
 		}
 
-		if (CheckClip(line, gr_frontsector, gr_backsector))
+		if (CheckClip(line, gl_frontsector, gl_backsector))
 		{
 			gld_clipper_SafeAddClipRange(angle2, angle1);
 			checkforemptylines = false;
@@ -2417,7 +2402,7 @@ static void HWR_AddLine(seg_t * line)
 		// Identical floor and ceiling on both sides,
 		//  identical light levels on both sides,
 		//  and no middle texture.
-		if (checkforemptylines && R_IsEmptyLine(line, gr_frontsector, gr_backsector))
+		if (checkforemptylines && R_IsEmptyLine(line, gl_frontsector, gl_backsector))
 			return;
     }
 
@@ -2425,28 +2410,28 @@ static void HWR_AddLine(seg_t * line)
 	return;
 #else
 	// Single sided line?
-	if (!gr_backsector)
+	if (!gl_backsector)
 		goto clipsolid;
 
-	gr_backsector = R_FakeFlat(gr_backsector, &tempsec, NULL, NULL, true);
+	gl_backsector = R_FakeFlat(gl_backsector, &tempsec, NULL, NULL, true);
 
-	if (gr_backsector->ceilingpic == viewworld->skyflatnum && gr_frontsector->ceilingpic == viewworld->skyflatnum)
+	if (gl_backsector->ceilingpic == viewworld->skyflatnum && gl_frontsector->ceilingpic == viewworld->skyflatnum)
 		bothceilingssky = true;
-	if (gr_backsector->floorpic == viewworld->skyflatnum && gr_frontsector->floorpic == viewworld->skyflatnum)
+	if (gl_backsector->floorpic == viewworld->skyflatnum && gl_frontsector->floorpic == viewworld->skyflatnum)
 		bothfloorssky = true;
 
 	if (bothceilingssky && bothfloorssky) // everything's sky? let's save us a bit of time then
 	{
 		if (!line->polyseg &&
 			!line->sidedef->midtexture
-			&& ((!gr_frontsector->ffloors && !gr_backsector->ffloors)
-				|| (gr_frontsector->tag == gr_backsector->tag)))
+			&& ((!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 (gr_frontsector->f_slope || gr_frontsector->c_slope || gr_backsector->f_slope || gr_backsector->c_slope)
+	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
@@ -2455,10 +2440,10 @@ static void HWR_AddLine(seg_t * line)
 		end1 = P_GetZAt(slope, v1x, v1y, normalheight); \
 		end2 = P_GetZAt(slope, v2x, v2y, normalheight);
 
-		SLOPEPARAMS(gr_frontsector->f_slope, frontf1, frontf2, gr_frontsector->  floorheight)
-		SLOPEPARAMS(gr_frontsector->c_slope, frontc1, frontc2, gr_frontsector->ceilingheight)
-		SLOPEPARAMS( gr_backsector->f_slope,  backf1,  backf2,  gr_backsector->  floorheight)
-		SLOPEPARAMS( gr_backsector->c_slope,  backc1,  backc2,  gr_backsector->ceilingheight)
+		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
@@ -2473,8 +2458,8 @@ static void HWR_AddLine(seg_t * line)
 
 			// Check for automap fix.
 			if (backc1 <= backf1 && backc2 <= backf2
-			&& ((backc1 >= frontc1 && backc2 >= frontc2) || gr_curline->sidedef->toptexture)
-			&& ((backf1 <= frontf1 && backf2 >= frontf2) || gr_curline->sidedef->bottomtexture))
+			&& ((backc1 >= frontc1 && backc2 >= frontc2) || gl_curline->sidedef->toptexture)
+			&& ((backf1 <= frontf1 && backf2 >= frontf2) || gl_curline->sidedef->bottomtexture))
 				goto clipsolid;
 		}
 
@@ -2493,23 +2478,23 @@ static void HWR_AddLine(seg_t * line)
 		if (!bothceilingssky && !bothfloorssky)
 		{
 			// Closed door.
-			if (gr_backsector->ceilingheight <= gr_frontsector->floorheight ||
-				gr_backsector->floorheight >= gr_frontsector->ceilingheight)
+			if (gl_backsector->ceilingheight <= gl_frontsector->floorheight ||
+				gl_backsector->floorheight >= gl_frontsector->ceilingheight)
 				goto clipsolid;
 
 			// Check for automap fix.
-			if (gr_backsector->ceilingheight <= gr_backsector->floorheight
-			&& ((gr_backsector->ceilingheight >= gr_frontsector->ceilingheight) || gr_curline->sidedef->toptexture)
-			&& ((gr_backsector->floorheight <= gr_backsector->floorheight) || gr_curline->sidedef->bottomtexture))
+			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 (gr_backsector->ceilingheight != gr_frontsector->ceilingheight)
+			if (gl_backsector->ceilingheight != gl_frontsector->ceilingheight)
 				goto clippass;
 		if (!bothfloorssky)	// floors are always the "same" when sky
-			if (gr_backsector->floorheight != gr_frontsector->floorheight)
+			if (gl_backsector->floorheight != gl_frontsector->floorheight)
 				goto clippass;
 	}
 
@@ -2517,7 +2502,7 @@ static void HWR_AddLine(seg_t * line)
 	// Identical floor and ceiling on both sides,
 	//  identical light levels on both sides,
 	//  and no middle texture.
-	if (R_IsEmptyLine(gr_curline, gr_frontsector, gr_backsector))
+	if (R_IsEmptyLine(gl_curline, gl_frontsector, gl_backsector))
 		return;
 
 clippass:
@@ -2575,8 +2560,8 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	py2 = bspcoord[checkcoord[boxpos][3]];
 
 #ifdef NEWCLIP
-	angle1 = R_PointToAngle(px1, py1);
-	angle2 = R_PointToAngle(px2, py2);
+	angle1 = R_PointToAngle64(px1, py1);
+	angle2 = R_PointToAngle64(px2, py2);
 	return gld_clipper_SafeCheckRange(angle2, angle1);
 #else
 	// check clip list for an open space
@@ -2589,28 +2574,28 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	if (span >= ANGLE_180)
 		return true;
 
-	tspan = angle1 + gr_clipangle;
+	tspan = angle1 + gl_clipangle;
 
-	if (tspan > 2*gr_clipangle)
+	if (tspan > 2*gl_clipangle)
 	{
-		tspan -= 2*gr_clipangle;
+		tspan -= 2*gl_clipangle;
 
 		// Totally off the left edge?
 		if (tspan >= span)
 			return false;
 
-		angle1 = gr_clipangle;
+		angle1 = gl_clipangle;
 	}
-	tspan = gr_clipangle - angle2;
-	if (tspan > 2*gr_clipangle)
+	tspan = gl_clipangle - angle2;
+	if (tspan > 2*gl_clipangle)
 	{
-		tspan -= 2*gr_clipangle;
+		tspan -= 2*gl_clipangle;
 
 		// Totally off the left edge?
 		if (tspan >= span)
 			return false;
 
-		angle2 = (angle_t)-(signed)gr_clipangle;
+		angle2 = (angle_t)-(signed)gl_clipangle;
 	}
 
 	// Find the first clippost
@@ -2618,8 +2603,8 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	//  (adjacent pixels are touching).
 	angle1 = (angle1+ANGLE_90)>>ANGLETOFINESHIFT;
 	angle2 = (angle2+ANGLE_90)>>ANGLETOFINESHIFT;
-	sx1 = gr_viewangletox[angle1];
-	sx2 = gr_viewangletox[angle2];
+	sx1 = gl_viewangletox[angle1];
+	sx2 = gl_viewangletox[angle2];
 
 	// Does not cross a pixel.
 	if (sx1 == sx2)
@@ -2639,7 +2624,7 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 static inline void HWR_AddPolyObjectSegs(void)
 {
 	size_t i, j;
-	seg_t *gr_fakeline = Z_Calloc(sizeof(seg_t), PU_STATIC, NULL);
+	seg_t *gl_fakeline = Z_Calloc(sizeof(seg_t), PU_STATIC, NULL);
 	polyvertex_t *pv1 = Z_Calloc(sizeof(polyvertex_t), PU_STATIC, NULL);
 	polyvertex_t *pv2 = Z_Calloc(sizeof(polyvertex_t), PU_STATIC, NULL);
 
@@ -2650,25 +2635,25 @@ static inline void HWR_AddPolyObjectSegs(void)
 		for (j = 0; j < po_ptrs[i]->segCount; ++j)
 		{
 			// Copy the info of a polyobject's seg, then convert it to OpenGL floating point
-			M_Memcpy(gr_fakeline, po_ptrs[i]->segs[j], sizeof(seg_t));
+			M_Memcpy(gl_fakeline, po_ptrs[i]->segs[j], sizeof(seg_t));
 
 			// Now convert the line to float and add it to be rendered
-			pv1->x = FIXED_TO_FLOAT(gr_fakeline->v1->x);
-			pv1->y = FIXED_TO_FLOAT(gr_fakeline->v1->y);
-			pv2->x = FIXED_TO_FLOAT(gr_fakeline->v2->x);
-			pv2->y = FIXED_TO_FLOAT(gr_fakeline->v2->y);
+			pv1->x = FIXED_TO_FLOAT(gl_fakeline->v1->x);
+			pv1->y = FIXED_TO_FLOAT(gl_fakeline->v1->y);
+			pv2->x = FIXED_TO_FLOAT(gl_fakeline->v2->x);
+			pv2->y = FIXED_TO_FLOAT(gl_fakeline->v2->y);
 
-			gr_fakeline->pv1 = pv1;
-			gr_fakeline->pv2 = pv2;
+			gl_fakeline->pv1 = pv1;
+			gl_fakeline->pv2 = pv2;
 
-			HWR_AddLine(gr_fakeline);
+			HWR_AddLine(gl_fakeline);
 		}
 	}
 
 	// Free temporary data no longer needed
 	Z_Free(pv2);
 	Z_Free(pv1);
-	Z_Free(gr_fakeline);
+	Z_Free(gl_fakeline);
 }
 
 static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
@@ -2682,7 +2667,6 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	float fflatwidth = 64.0f, fflatheight = 64.0f;
 	INT32 flatflag = 63;
 	boolean texflat = false;
-	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
 	FSurfaceInfo    Surf;
@@ -2716,16 +2700,9 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	// set texture for polygon
 	if (levelflat != NULL)
 	{
-		if (levelflat->type == LEVELFLAT_TEXTURE)
+		if (levelflat->type == LEVELFLAT_FLAT)
 		{
-			fflatwidth = textures[levelflat->u.texture.num]->width;
-			fflatheight = textures[levelflat->u.texture.num]->height;
-			texflat = true;
-		}
-		else if (levelflat->type == LEVELFLAT_FLAT)
-		{
-			len = W_LumpLength(levelflat->u.flat.lumpnum);
-
+			size_t len = W_LumpLength(levelflat->u.flat.lumpnum);
 			switch (len)
 			{
 				case 4194304: // 2048x2048 lump
@@ -2750,16 +2727,32 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 					fflatwidth = fflatheight = 64.0f;
 					break;
 			}
-
 			flatflag = ((INT32)fflatwidth)-1;
 		}
+		else
+		{
+			if (levelflat->type == LEVELFLAT_TEXTURE)
+			{
+				fflatwidth = textures[levelflat->u.texture.num]->width;
+				fflatheight = textures[levelflat->u.texture.num]->height;
+			}
+			else if (levelflat->type == LEVELFLAT_PATCH || levelflat->type == LEVELFLAT_PNG)
+			{
+				fflatwidth = levelflat->width;
+				fflatheight = levelflat->height;
+			}
+			texflat = true;
+		}
 	}
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
 
 	// reference point for flat texture coord for each vertex around the polygon
-	flatxref = (float)((polysector->origVerts[0].x & (~flatflag)) / fflatwidth);
-	flatyref = (float)((polysector->origVerts[0].y & (~flatflag)) / fflatheight);
+	flatxref = FIXED_TO_FLOAT(polysector->origVerts[0].x);
+	flatyref = FIXED_TO_FLOAT(polysector->origVerts[0].y);
+
+	flatxref = (float)(((fixed_t)flatxref & (~flatflag)) / fflatwidth);
+	flatyref = (float)(((fixed_t)flatyref & (~flatflag)) / fflatheight);
 
 	// transform
 	v3d = planeVerts;
@@ -2770,38 +2763,40 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 		{
 			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatwidth;
 			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatheight;
-			angle = FOFsector->floorpic_angle>>ANGLETOFINESHIFT;
+			angle = FOFsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
 			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatwidth;
 			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatheight;
-			angle = FOFsector->ceilingpic_angle>>ANGLETOFINESHIFT;
+			angle = FOFsector->ceilingpic_angle;
 		}
 	}
-	else if (gr_frontsector)
+	else if (gl_frontsector)
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->floor_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->floor_yoffs)/fflatheight;
-			angle = gr_frontsector->floorpic_angle>>ANGLETOFINESHIFT;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->floor_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->floor_yoffs)/fflatheight;
+			angle = gl_frontsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gr_frontsector->ceiling_xoffs)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gr_frontsector->ceiling_yoffs)/fflatheight;
-			angle = gr_frontsector->ceilingpic_angle>>ANGLETOFINESHIFT;
+			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceiling_xoffs)/fflatwidth;
+			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceiling_yoffs)/fflatheight;
+			angle = gl_frontsector->ceilingpic_angle;
 		}
 	}
 
 	if (angle) // Only needs to be done if there's an altered angle
 	{
+		angle = (InvAngle(angle))>>ANGLETOFINESHIFT;
+
 		// This needs to be done so that it scrolls in a different direction after rotation like software
-		tempxs = FLOAT_TO_FIXED(scrollx);
+		/*tempxs = FLOAT_TO_FIXED(scrollx);
 		tempyt = FLOAT_TO_FIXED(scrolly);
 		scrollx = (FIXED_TO_FLOAT(FixedMul(tempxs, FINECOSINE(angle)) - FixedMul(tempyt, FINESINE(angle))));
-		scrolly = (FIXED_TO_FLOAT(FixedMul(tempxs, FINESINE(angle)) + FixedMul(tempyt, FINECOSINE(angle))));
+		scrolly = (FIXED_TO_FLOAT(FixedMul(tempxs, FINESINE(angle)) + FixedMul(tempyt, FINECOSINE(angle))));*/
 
 		// This needs to be done so everything aligns after rotation
 		// It would be done so that rotation is done, THEN the translation, but I couldn't get it to rotate AND scroll like software does
@@ -2831,10 +2826,8 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 		{
 			tempxs = FLOAT_TO_FIXED(v3d->s);
 			tempyt = FLOAT_TO_FIXED(v3d->t);
-			if (texflat)
-				tempyt = -tempyt;
 			v3d->s = (FIXED_TO_FLOAT(FixedMul(tempxs, FINECOSINE(angle)) - FixedMul(tempyt, FINESINE(angle))));
-			v3d->t = (FIXED_TO_FLOAT(-FixedMul(tempxs, FINESINE(angle)) - FixedMul(tempyt, FINECOSINE(angle))));
+			v3d->t = (FIXED_TO_FLOAT(FixedMul(tempxs, FINESINE(angle)) + FixedMul(tempyt, FINECOSINE(angle))));
 		}
 
 		v3d->x = FIXED_TO_FLOAT(polysector->vertices[i]->x);
@@ -2848,12 +2841,12 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	if (blendmode & PF_Translucent)
 	{
 		Surf.PolyColor.s.alpha = (UINT8)alpha;
-		blendmode |= PF_Modulated|PF_Occlude|PF_Clip;
+		blendmode |= PF_Modulated|PF_Occlude;
 	}
 	else
-		blendmode |= PF_Masked|PF_Modulated|PF_Clip;
+		blendmode |= PF_Masked|PF_Modulated;
 
-	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, blendmode, 1, false); // floor shader
+	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, blendmode, SHADER_FLOOR, false); // floor shader
 }
 
 static void HWR_AddPolyObjectPlanes(void)
@@ -2875,11 +2868,11 @@ static void HWR_AddPolyObjectPlanes(void)
 		if (po_ptrs[i]->translucency >= NUMTRANSMAPS)
 			continue;
 
-		if (polyobjsector->floorheight <= gr_frontsector->ceilingheight
-			&& polyobjsector->floorheight >= gr_frontsector->floorheight
+		if (polyobjsector->floorheight <= gl_frontsector->ceilingheight
+			&& polyobjsector->floorheight >= gl_frontsector->floorheight
 			&& (viewz < polyobjsector->floorheight))
 		{
-			light = R_GetPlaneLight(gr_frontsector, polyobjsector->floorheight, true);
+			light = R_GetPlaneLight(gl_frontsector, polyobjsector->floorheight, true);
 			if (po_ptrs[i]->translucency > 0)
 			{
 				FSurfaceInfo Surf;
@@ -2887,22 +2880,22 @@ static void HWR_AddPolyObjectPlanes(void)
 				memset(&Surf, 0x00, sizeof(Surf));
 				blendmode = HWR_TranstableToAlpha(po_ptrs[i]->translucency, &Surf);
 				HWR_AddTransparentPolyobjectFloor(&viewworld->flats[polyobjsector->floorpic], po_ptrs[i], false, polyobjsector->floorheight,
-													(light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), Surf.PolyColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
+													(light == -1 ? gl_frontsector->lightlevel : *gl_frontsector->lightlist[light].lightlevel), Surf.PolyColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gl_frontsector->extra_colormap : *gl_frontsector->lightlist[light].extra_colormap));
 			}
 			else
 			{
 				HWR_GetLevelFlat(&viewworld->flats[polyobjsector->floorpic]);
 				HWR_RenderPolyObjectPlane(po_ptrs[i], false, polyobjsector->floorheight, PF_Occlude,
-										(light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), &viewworld->flats[polyobjsector->floorpic],
-										polyobjsector, 255, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
+										(light == -1 ? gl_frontsector->lightlevel : *gl_frontsector->lightlist[light].lightlevel), &viewworld->flats[polyobjsector->floorpic],
+										polyobjsector, 255, (light == -1 ? gl_frontsector->extra_colormap : *gl_frontsector->lightlist[light].extra_colormap));
 			}
 		}
 
-		if (polyobjsector->ceilingheight >= gr_frontsector->floorheight
-			&& polyobjsector->ceilingheight <= gr_frontsector->ceilingheight
+		if (polyobjsector->ceilingheight >= gl_frontsector->floorheight
+			&& polyobjsector->ceilingheight <= gl_frontsector->ceilingheight
 			&& (viewz > polyobjsector->ceilingheight))
 		{
-			light = R_GetPlaneLight(gr_frontsector, polyobjsector->ceilingheight, true);
+			light = R_GetPlaneLight(gl_frontsector, polyobjsector->ceilingheight, true);
 			if (po_ptrs[i]->translucency > 0)
 			{
 				FSurfaceInfo Surf;
@@ -2910,14 +2903,14 @@ static void HWR_AddPolyObjectPlanes(void)
 				memset(&Surf, 0x00, sizeof(Surf));
 				blendmode = HWR_TranstableToAlpha(po_ptrs[i]->translucency, &Surf);
 				HWR_AddTransparentPolyobjectFloor(&viewworld->flats[polyobjsector->ceilingpic], po_ptrs[i], true, polyobjsector->ceilingheight,
-				                                  (light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), Surf.PolyColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
+				                                  (light == -1 ? gl_frontsector->lightlevel : *gl_frontsector->lightlist[light].lightlevel), Surf.PolyColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gl_frontsector->extra_colormap : *gl_frontsector->lightlist[light].extra_colormap));
 			}
 			else
 			{
 				HWR_GetLevelFlat(&viewworld->flats[polyobjsector->ceilingpic]);
 				HWR_RenderPolyObjectPlane(po_ptrs[i], true, polyobjsector->ceilingheight, PF_Occlude,
-				                          (light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), &viewworld->flats[polyobjsector->ceilingpic],
-				                          polyobjsector, 255, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
+				                          (light == -1 ? gl_frontsector->lightlevel : *gl_frontsector->lightlist[light].lightlevel), &viewworld->flats[polyobjsector->ceilingpic],
+				                          polyobjsector, 255, (light == -1 ? gl_frontsector->extra_colormap : *gl_frontsector->lightlist[light].extra_colormap));
 			}
 		}
 	}
@@ -2927,7 +2920,7 @@ static void HWR_AddPolyObjectPlanes(void)
 // HWR_Subsector    : Determine floor/ceiling planes.
 //                  : Add sprites of things in sector.
 //                  : Draw one or more line segments.
-// Notes            : Sets gr_cursectorlight to the light of the parent sector, to modulate wall textures
+// Notes            : Sets gl_cursectorlight to the light of the parent sector, to modulate wall textures
 // -----------------+
 static void HWR_Subsector(size_t num)
 {
@@ -2959,7 +2952,7 @@ static void HWR_Subsector(size_t num)
 		// subsector
 		sub = &subsectors[num];
 		// sector
-		gr_frontsector = sub->sector;
+		gl_frontsector = sub->sector;
 		// how many linedefs
 		count = sub->numlines;
 		// first line seg
@@ -2969,72 +2962,73 @@ static void HWR_Subsector(size_t num)
 	{
 		// there are no segs but only planes
 		sub = &subsectors[0];
-		gr_frontsector = sub->sector;
+		gl_frontsector = sub->sector;
 		count = 0;
 		line = NULL;
 	}
 
 	//SoM: 4/7/2000: Test to make Boom water work in Hardware mode.
-	gr_frontsector = R_FakeFlat(gr_frontsector, &tempsec, &floorlightlevel,
+	gl_frontsector = R_FakeFlat(gl_frontsector, &tempsec, &floorlightlevel,
 								&ceilinglightlevel, false);
 	//FIXME: Use floorlightlevel and ceilinglightlevel insted of lightlevel.
 
-	floorcolormap = ceilingcolormap = gr_frontsector->extra_colormap;
+	floorcolormap = ceilingcolormap = gl_frontsector->extra_colormap;
 
 	// ------------------------------------------------------------------------
 	// sector lighting, DISABLED because it's done in HWR_StoreWallRange
 	// ------------------------------------------------------------------------
 	/// \todo store a RGBA instead of just intensity, allow coloured sector lighting
 	//light = (FUBYTE)(sub->sector->lightlevel & 0xFF) / 255.0f;
-	//gr_cursectorlight.red   = light;
-	//gr_cursectorlight.green = light;
-	//gr_cursectorlight.blue  = light;
-	//gr_cursectorlight.alpha = light;
+	//gl_cursectorlight.red   = light;
+	//gl_cursectorlight.green = light;
+	//gl_cursectorlight.blue  = light;
+	//gl_cursectorlight.alpha = light;
 
-	cullFloorHeight   = P_GetSectorFloorZAt  (gr_frontsector, viewx, viewy);
-	cullCeilingHeight = P_GetSectorCeilingZAt(gr_frontsector, viewx, viewy);
-	locFloorHeight    = P_GetSectorFloorZAt  (gr_frontsector, gr_frontsector->soundorg.x, gr_frontsector->soundorg.y);
-	locCeilingHeight  = P_GetSectorCeilingZAt(gr_frontsector, gr_frontsector->soundorg.x, gr_frontsector->soundorg.y);
+// ----- end special tricks -----
+	cullFloorHeight   = P_GetSectorFloorZAt  (gl_frontsector, viewx, viewy);
+	cullCeilingHeight = P_GetSectorCeilingZAt(gl_frontsector, viewx, viewy);
+	locFloorHeight    = P_GetSectorFloorZAt  (gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
+	locCeilingHeight  = P_GetSectorCeilingZAt(gl_frontsector, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
 
-	if (gr_frontsector->ffloors)
+	if (gl_frontsector->ffloors)
 	{
-		if (gr_frontsector->moved)
+		if (gl_frontsector->moved)
 		{
-			gr_frontsector->numlights = sub->sector->numlights = 0;
-			R_Prep3DFloors(gr_frontsector);
-			sub->sector->lightlist = gr_frontsector->lightlist;
-			sub->sector->numlights = gr_frontsector->numlights;
-			sub->sector->moved = gr_frontsector->moved = false;
+			gl_frontsector->numlights = sub->sector->numlights = 0;
+			R_Prep3DFloors(gl_frontsector);
+			sub->sector->lightlist = gl_frontsector->lightlist;
+			sub->sector->numlights = gl_frontsector->numlights;
+			sub->sector->moved = gl_frontsector->moved = false;
 		}
 
-		light = R_GetPlaneLight(gr_frontsector, locFloorHeight, false);
-		if (gr_frontsector->floorlightsec == -1)
-			floorlightlevel = *gr_frontsector->lightlist[light].lightlevel;
-		floorcolormap = *gr_frontsector->lightlist[light].extra_colormap;
+		light = R_GetPlaneLight(gl_frontsector, locFloorHeight, false);
+		if (gl_frontsector->floorlightsec == -1)
+			floorlightlevel = *gl_frontsector->lightlist[light].lightlevel;
+		floorcolormap = *gl_frontsector->lightlist[light].extra_colormap;
 
-		light = R_GetPlaneLight(gr_frontsector, locCeilingHeight, false);
-		if (gr_frontsector->ceilinglightsec == -1)
-			ceilinglightlevel = *gr_frontsector->lightlist[light].lightlevel;
-		ceilingcolormap = *gr_frontsector->lightlist[light].extra_colormap;
+		light = R_GetPlaneLight(gl_frontsector, locCeilingHeight, false);
+		if (gl_frontsector->ceilinglightsec == -1)
+			ceilinglightlevel = *gl_frontsector->lightlist[light].lightlevel;
+		ceilingcolormap = *gl_frontsector->lightlist[light].extra_colormap;
 	}
 
-	sub->sector->extra_colormap = gr_frontsector->extra_colormap;
+	sub->sector->extra_colormap = gl_frontsector->extra_colormap;
 
 	// render floor ?
 #ifdef DOPLANES
 	// yeah, easy backface cull! :)
 	if (cullFloorHeight < dup_viewz)
 	{
-		if (gr_frontsector->floorpic != viewworld->skyflatnum)
+		if (gl_frontsector->floorpic != viewworld->skyflatnum)
 		{
 			if (sub->validcount != validcount)
 			{
-				HWR_GetLevelFlat(&viewworld->flats[gr_frontsector->floorpic]);
+				HWR_GetLevelFlat(&viewworld->flats[gl_frontsector->floorpic]);
 				HWR_RenderPlane(sub, &extrasubsectors[num], false,
 					// Hack to make things continue to work around slopes.
-					locFloorHeight == cullFloorHeight ? locFloorHeight : gr_frontsector->floorheight,
+					locFloorHeight == cullFloorHeight ? locFloorHeight : gl_frontsector->floorheight,
 					// We now return you to your regularly scheduled rendering.
-					PF_Occlude, floorlightlevel, &viewworld->flats[gr_frontsector->floorpic], NULL, 255, floorcolormap);
+					PF_Occlude, floorlightlevel, &viewworld->flats[gl_frontsector->floorpic], NULL, 255, floorcolormap);
 			}
 		}
 		else
@@ -3047,16 +3041,16 @@ static void HWR_Subsector(size_t num)
 
 	if (cullCeilingHeight > dup_viewz)
 	{
-		if (gr_frontsector->ceilingpic != viewworld->skyflatnum)
+		if (gl_frontsector->ceilingpic != viewworld->skyflatnum)
 		{
 			if (sub->validcount != validcount)
 			{
-				HWR_GetLevelFlat(&viewworld->flats[gr_frontsector->ceilingpic]);
+				HWR_GetLevelFlat(&viewworld->flats[gl_frontsector->ceilingpic]);
 				HWR_RenderPlane(sub, &extrasubsectors[num], true,
 					// Hack to make things continue to work around slopes.
-					locCeilingHeight == cullCeilingHeight ? locCeilingHeight : gr_frontsector->ceilingheight,
+					locCeilingHeight == cullCeilingHeight ? locCeilingHeight : gl_frontsector->ceilingheight,
 					// We now return you to your regularly scheduled rendering.
-					PF_Occlude, ceilinglightlevel, &viewworld->flats[gr_frontsector->ceilingpic], NULL, 255, ceilingcolormap);
+					PF_Occlude, ceilinglightlevel, &viewworld->flats[gl_frontsector->ceilingpic], NULL, 255, ceilingcolormap);
 			}
 		}
 		else
@@ -3069,23 +3063,23 @@ static void HWR_Subsector(size_t num)
 
 #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 (gr_frontsector->ceilingpic == viewworld->skyflatnum || gr_frontsector->floorpic == viewworld->skyflatnum)
+	if (gl_frontsector->ceilingpic == viewworld->skyflatnum || gl_frontsector->floorpic == viewworld->skyflatnum)
 		drawsky = true;
 #endif
 
 #ifdef R_FAKEFLOORS
-	if (gr_frontsector->ffloors)
+	if (gl_frontsector->ffloors)
 	{
 		/// \todo fix light, xoffs, yoffs, extracolormap ?
 		ffloor_t * rover;
-		for (rover = gr_frontsector->ffloors;
+		for (rover = gl_frontsector->ffloors;
 			rover; rover = rover->next)
 		{
 			fixed_t cullHeight, centerHeight;
 
             // bottom plane
 			cullHeight   = P_GetFFloorBottomZAt(rover, viewx, viewy);
-			centerHeight = P_GetFFloorBottomZAt(rover, gr_frontsector->soundorg.x, gr_frontsector->soundorg.y);
+			centerHeight = P_GetFFloorBottomZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
 
 			if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERPLANES))
 				continue;
@@ -3101,41 +3095,41 @@ static void HWR_Subsector(size_t num)
 				{
 					UINT8 alpha;
 
-					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
 
 					HWR_AddTransparentFloor(0,
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
-					                       *gr_frontsector->lightlist[light].lightlevel,
+					                       *gl_frontsector->lightlist[light].lightlevel,
 					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
 				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256) // SoM: Flags are more efficient
 				{
-					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
 					HWR_AddTransparentFloor(&world->flats[*rover->bottompic],
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
-					                       *gr_frontsector->lightlist[light].lightlevel,
+					                       *gl_frontsector->lightlist[light].lightlevel,
 					                       rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Translucent,
-					                       false, *gr_frontsector->lightlist[light].extra_colormap);
+					                       false, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 				else
 				{
 					HWR_GetLevelFlat(&world->flats[*rover->bottompic]);
-					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &world->flats[*rover->bottompic],
-					                rover->master->frontsector, 255, *gr_frontsector->lightlist[light].extra_colormap);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &world->flats[*rover->bottompic],
+					                rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 			}
 
 			// top plane
 			cullHeight   = P_GetFFloorTopZAt(rover, viewx, viewy);
-			centerHeight = P_GetFFloorTopZAt(rover, gr_frontsector->soundorg.x, gr_frontsector->soundorg.y);
+			centerHeight = P_GetFFloorTopZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
 
 			if (centerHeight >= locFloorHeight &&
 			    centerHeight <= locCeilingHeight &&
@@ -3146,35 +3140,35 @@ static void HWR_Subsector(size_t num)
 				{
 					UINT8 alpha;
 
-					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
 
 					HWR_AddTransparentFloor(0,
 					                       &extrasubsectors[num],
 										   true,
 					                       *rover->topheight,
-					                       *gr_frontsector->lightlist[light].lightlevel,
+					                       *gl_frontsector->lightlist[light].lightlevel,
 					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, rover->master->frontsector->extra_colormap);
 				}
 				else if (rover->flags & FF_TRANSLUCENT && rover->alpha < 256)
 				{
-					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 
 					HWR_AddTransparentFloor(&world->flats[*rover->toppic],
 					                        &extrasubsectors[num],
 											true,
 					                        *rover->topheight,
-					                        *gr_frontsector->lightlist[light].lightlevel,
+					                        *gl_frontsector->lightlist[light].lightlevel,
 					                        rover->alpha-1 > 255 ? 255 : rover->alpha-1, rover->master->frontsector, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Translucent,
-					                        false, *gr_frontsector->lightlist[light].extra_colormap);
+					                        false, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 				else
 				{
 					HWR_GetLevelFlat(&world->flats[*rover->toppic]);
-					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &world->flats[*rover->toppic],
-					                  rover->master->frontsector, 255, *gr_frontsector->lightlist[light].extra_colormap);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, (rover->flags & FF_RIPPLE ? PF_Ripple : 0)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &world->flats[*rover->toppic],
+					                  rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 			}
 		}
@@ -3197,7 +3191,7 @@ static void HWR_Subsector(size_t num)
 		}
 
 		// for render stats
-		rs_numpolyobjects += numpolys;
+		ps_numpolyobjects += numpolys;
 
 		// Sort polyobjects
 		R_SortPolyObjects(sub);
@@ -3220,11 +3214,11 @@ static void HWR_Subsector(size_t num)
 	{
 		// draw sprites first, coz they are clipped to the solidsegs of
 		// subsectors more 'in front'
-		HWR_AddSprites(gr_frontsector);
+		HWR_AddSprites(gl_frontsector);
 
 		//Hurdler: at this point validcount must be the same, but is not because
-		//         gr_frontsector doesn't point anymore to sub->sector due to
-		//         the call gr_frontsector = R_FakeFlat(...)
+		//         gl_frontsector doesn't point anymore to sub->sector due to
+		//         the call gl_frontsector = R_FakeFlat(...)
 		//         if it's not done, the sprite is drawn more than once,
 		//         what looks really bad with translucency or dynamic light,
 		//         without talking about the overdraw of course.
@@ -3305,19 +3299,19 @@ static void HWR_RenderBSPNode(INT32 bspnum)
 	// Decide which side the view point is on
 	INT32 side;
 
-	rs_numbspcalls++;
+	ps_numbspcalls++;
 
 	// Found a subsector?
 	if (bspnum & NF_SUBSECTOR)
 	{
 		if (bspnum == -1)
 		{
-			//*(gr_drawsubsector_p++) = 0;
+			//*(gl_drawsubsector_p++) = 0;
 			HWR_Subsector(0);
 		}
 		else
 		{
-			//*(gr_drawsubsector_p++) = bspnum&(~NF_SUBSECTOR);
+			//*(gl_drawsubsector_p++) = bspnum&(~NF_SUBSECTOR);
 			HWR_Subsector(bspnum&(~NF_SUBSECTOR));
 		}
 		return;
@@ -3347,7 +3341,7 @@ static void HWR_RenderBSPNode(INT32 bspnum)
 //
 static void HWR_ClearDrawSubsectors(void)
 {
-	gr_drawsubsector_p = gr_drawsubsectors;
+	gl_drawsubsector_p = gl_drawsubsectors;
 }
 
 //
@@ -3355,7 +3349,7 @@ static void HWR_ClearDrawSubsectors(void)
 //
 static void HWR_RenderSubsectors(void)
 {
-	while (gr_drawsubsector_p > gr_drawsubsectors)
+	while (gl_drawsubsector_p > gl_drawsubsectors)
 	{
 		HWR_RenderBSPNode(
 		lastsubsec->nextsubsec = bspnum & (~NF_SUBSECTOR);
@@ -3409,7 +3403,7 @@ void HWR_InitTextureMapping(void)
 			else if (t > grviewwidth+1)
 				t = grviewwidth+1;
 		}
-		gr_viewangletox[i] = t;
+		gl_viewangletox[i] = t;
 	}
 
 	// Scan viewangletox[] to generate xtoviewangle[]:
@@ -3418,32 +3412,32 @@ void HWR_InitTextureMapping(void)
 	for (x = 0; x <= grviewwidth; x++)
 	{
 		i = 0;
-		while (gr_viewangletox[i]>x)
+		while (gl_viewangletox[i]>x)
 			i++;
-		gr_xtoviewangle[x] = (i<<ANGLETOFINESHIFT) - ANGLE_90;
+		gl_xtoviewangle[x] = (i<<ANGLETOFINESHIFT) - ANGLE_90;
 	}
 
 	// Take out the fencepost cases from viewangletox.
 	for (i = 0; i < FINEANGLES/2; i++)
 	{
-		if (gr_viewangletox[i] == -1)
-			gr_viewangletox[i] = 0;
-		else if (gr_viewangletox[i] == grviewwidth+1)
-			gr_viewangletox[i]  = grviewwidth;
+		if (gl_viewangletox[i] == -1)
+			gl_viewangletox[i] = 0;
+		else if (gl_viewangletox[i] == grviewwidth+1)
+			gl_viewangletox[i]  = grviewwidth;
 	}
 
-	gr_clipangle = gr_xtoviewangle[0];
+	gl_clipangle = gl_xtoviewangle[0];
 }
 
 // ==========================================================================
-// gr_things.c
+// gl_things.c
 // ==========================================================================
 
 // sprites are drawn after all wall and planes are rendered, so that
 // sprite translucency effects apply on the rendered view (instead of the background sky!!)
 
-static UINT32 gr_visspritecount;
-static gr_vissprite_t *gr_visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL};
+static UINT32 gl_visspritecount;
+static gl_vissprite_t *gl_visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL};
 
 // --------------------------------------------------------------------------
 // HWR_ClearSprites
@@ -3451,31 +3445,79 @@ static gr_vissprite_t *gr_visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] =
 // --------------------------------------------------------------------------
 static void HWR_ClearSprites(void)
 {
-	gr_visspritecount = 0;
+	gl_visspritecount = 0;
 }
 
 // --------------------------------------------------------------------------
 // HWR_NewVisSprite
 // --------------------------------------------------------------------------
-static gr_vissprite_t gr_overflowsprite;
+static gl_vissprite_t gl_overflowsprite;
 
-static gr_vissprite_t *HWR_GetVisSprite(UINT32 num)
+static gl_vissprite_t *HWR_GetVisSprite(UINT32 num)
 {
 		UINT32 chunk = num >> VISSPRITECHUNKBITS;
 
 		// Allocate chunk if necessary
-		if (!gr_visspritechunks[chunk])
-			Z_Malloc(sizeof(gr_vissprite_t) * VISSPRITESPERCHUNK, PU_LEVEL, &gr_visspritechunks[chunk]);
+		if (!gl_visspritechunks[chunk])
+			Z_Malloc(sizeof(gl_vissprite_t) * VISSPRITESPERCHUNK, PU_LEVEL, &gl_visspritechunks[chunk]);
 
-		return gr_visspritechunks[chunk] + (num & VISSPRITEINDEXMASK);
+		return gl_visspritechunks[chunk] + (num & VISSPRITEINDEXMASK);
 }
 
-static gr_vissprite_t *HWR_NewVisSprite(void)
+static gl_vissprite_t *HWR_NewVisSprite(void)
 {
-	if (gr_visspritecount == MAXVISSPRITES)
-		return &gr_overflowsprite;
+	if (gl_visspritecount == MAXVISSPRITES)
+		return &gl_overflowsprite;
 
-	return HWR_GetVisSprite(gr_visspritecount++);
+	return HWR_GetVisSprite(gl_visspritecount++);
+}
+
+// A hack solution for transparent surfaces appearing on top of linkdraw sprites.
+// Keep a list of linkdraw sprites and draw their shapes to the z-buffer after all other
+// sprite drawing is done. (effectively the z-buffer drawing of linkdraw sprites is delayed)
+// NOTE: This will no longer be necessary once full translucent sorting is implemented, where
+// translucent sprites and surfaces are sorted together.
+
+typedef struct
+{
+	FOutVector verts[4];
+	gl_vissprite_t *spr;
+} zbuffersprite_t;
+
+// this list is used to store data about linkdraw sprites
+zbuffersprite_t linkdrawlist[MAXVISSPRITES];
+UINT32 linkdrawcount = 0;
+
+// add the necessary data to the list for delayed z-buffer drawing
+static void HWR_LinkDrawHackAdd(FOutVector *verts, gl_vissprite_t *spr)
+{
+	if (linkdrawcount < MAXVISSPRITES)
+	{
+		memcpy(linkdrawlist[linkdrawcount].verts, verts, sizeof(FOutVector) * 4);
+		linkdrawlist[linkdrawcount].spr = spr;
+		linkdrawcount++;
+	}
+}
+
+// process and clear the list of sprites for delayed z-buffer drawing
+static void HWR_LinkDrawHackFinish(void)
+{
+	UINT32 i;
+	FSurfaceInfo surf;
+	surf.PolyColor.rgba = 0xFFFFFFFF;
+	surf.TintColor.rgba = 0xFFFFFFFF;
+	surf.FadeColor.rgba = 0xFFFFFFFF;
+	surf.LightInfo.light_level = 0;
+	surf.LightInfo.fade_start = 0;
+	surf.LightInfo.fade_end = 31;
+	for (i = 0; i < linkdrawcount; i++)
+	{
+		// draw sprite shape, only to z-buffer
+		HWR_GetPatch(linkdrawlist[i].spr->gpatch);
+		HWR_ProcessPolygon(&surf, linkdrawlist[i].verts, 4, PF_Translucent|PF_Occlude|PF_Invisible, 0, false);
+	}
+	// reset list
+	linkdrawcount = 0;
 }
 
 //
@@ -3520,7 +3562,7 @@ static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float v
 
 static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 {
-	GLPatch_t *gpatch;
+	patch_t *gpatch;
 	FOutVector shadowVerts[4];
 	FSurfaceInfo sSurf;
 	float fscale; float fx; float fy; float offset;
@@ -3538,7 +3580,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 
 	groundz = R_GetShadowZ(thing, &groundslope);
 
-	//if (abs(groundz - gr_viewz) / tz > 4) return; // Prevent stretchy shadows and possible crashes
+	//if (abs(groundz - gl_viewz) / tz > 4) return; // Prevent stretchy shadows and possible crashes
 
 	floordiff = abs((flip < 0 ? thing->height : 0) + thing->z - groundz);
 
@@ -3546,12 +3588,12 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	if (alpha >= 255) return;
 	alpha = 255 - alpha;
 
-	gpatch = (GLPatch_t *)W_CachePatchName("DSHADOW", PU_CACHE);
-	if (!(gpatch && gpatch->mipmap->grInfo.format)) return;
+	gpatch = (patch_t *)W_CachePatchName("DSHADOW", PU_SPRITE);
+	if (!(gpatch && ((GLPatch_t *)gpatch->hardware)->mipmap->format)) return;
 	HWR_GetPatch(gpatch);
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
-	scalemul = FixedMul(scalemul, (thing->radius*2) / SHORT(gpatch->height));
+	scalemul = FixedMul(scalemul, (thing->radius*2) / gpatch->height);
 
 	fscale = FIXED_TO_FLOAT(scalemul);
 	fx = FIXED_TO_FLOAT(thing->x);
@@ -3563,9 +3605,9 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	//  0--1
 
 	if (thing && fabsf(fscale - 1.0f) > 1.0E-36f)
-		offset = (SHORT(gpatch->height)/2) * fscale;
+		offset = ((gpatch->height)/2) * fscale;
 	else
-		offset = (float)(SHORT(gpatch->height)/2);
+		offset = (float)((gpatch->height)/2);
 
 	shadowVerts[2].x = shadowVerts[3].x = fx + offset;
 	shadowVerts[1].x = shadowVerts[0].x = fx - offset;
@@ -3576,8 +3618,8 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	{
 		float oldx = shadowVerts[i].x;
 		float oldy = shadowVerts[i].z;
-		shadowVerts[i].x = fx + ((oldx - fx) * gr_viewcos) - ((oldy - fy) * gr_viewsin);
-		shadowVerts[i].z = fy + ((oldx - fx) * gr_viewsin) + ((oldy - fy) * gr_viewcos);
+		shadowVerts[i].x = fx + ((oldx - fx) * gl_viewcos) - ((oldy - fy) * gl_viewsin);
+		shadowVerts[i].z = fy + ((oldx - fx) * gl_viewsin) + ((oldy - fy) * gl_viewcos);
 	}
 
 	if (groundslope)
@@ -3595,34 +3637,35 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	}
 
 	shadowVerts[0].s = shadowVerts[3].s = 0;
-	shadowVerts[2].s = shadowVerts[1].s = gpatch->max_s;
+	shadowVerts[2].s = shadowVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 
 	shadowVerts[3].t = shadowVerts[2].t = 0;
-	shadowVerts[0].t = shadowVerts[1].t = gpatch->max_t;
+	shadowVerts[0].t = shadowVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 
-	if (thing->subsector->sector->numlights)
+	if (!(thing->renderflags & RF_NOCOLORMAPS))
 	{
-		light = R_GetPlaneLight(thing->subsector->sector, groundz, false); // Always use the light at the top instead of whatever I was doing before
+		if (thing->subsector->sector->numlights)
+		{
+			// Always use the light at the top instead of whatever I was doing before
+			light = R_GetPlaneLight(thing->subsector->sector, groundz, false);
 
-		if (*thing->subsector->sector->lightlist[light].extra_colormap)
-			colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
-	}
-	else
-	{
-		if (thing->subsector->sector->extra_colormap)
+			if (*thing->subsector->sector->lightlist[light].extra_colormap)
+				colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
+		}
+		else if (thing->subsector->sector->extra_colormap)
 			colormap = thing->subsector->sector->extra_colormap;
 	}
 
 	HWR_Lighting(&sSurf, 0, colormap);
 	sSurf.PolyColor.s.alpha = alpha;
 
-	HWR_ProcessPolygon(&sSurf, shadowVerts, 4, PF_Translucent|PF_Modulated|PF_Clip, 3, false); // sprite shader
+	HWR_ProcessPolygon(&sSurf, shadowVerts, 4, PF_Translucent|PF_Modulated, SHADER_SPRITE, false); // sprite shader
 }
 
 // This is expecting a pointer to an array containing 4 wallVerts for a sprite
-static void HWR_RotateSpritePolyToAim(gr_vissprite_t *spr, FOutVector *wallVerts, const boolean precip)
+static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts, const boolean precip)
 {
-	if (cv_grspritebillboarding.value
+	if (cv_glspritebillboarding.value
 		&& spr && spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE)
 		&& wallVerts)
 	{
@@ -3636,34 +3679,36 @@ static void HWR_RotateSpritePolyToAim(gr_vissprite_t *spr, FOutVector *wallVerts
 		// X, Y, AND Z need to be manipulated for the polys to rotate around the
 		// origin, because of how the origin setting works I believe that should
 		// be mobj->z or mobj->z + mobj->height
-		wallVerts[2].y = wallVerts[3].y = (spr->ty - basey) * gr_viewludsin + basey;
-		wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gr_viewludsin + basey;
+		wallVerts[2].y = wallVerts[3].y = (spr->gzt - basey) * gl_viewludsin + basey;
+		wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gl_viewludsin + basey;
 		// translate back to be around 0 before translating back
-		wallVerts[3].x += ((spr->ty - basey) * gr_viewludcos) * gr_viewcos;
-		wallVerts[2].x += ((spr->ty - basey) * gr_viewludcos) * gr_viewcos;
+		wallVerts[3].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos;
+		wallVerts[2].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos;
 
-		wallVerts[0].x += ((lowy - basey) * gr_viewludcos) * gr_viewcos;
-		wallVerts[1].x += ((lowy - basey) * gr_viewludcos) * gr_viewcos;
+		wallVerts[0].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos;
+		wallVerts[1].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos;
 
-		wallVerts[3].z += ((spr->ty - basey) * gr_viewludcos) * gr_viewsin;
-		wallVerts[2].z += ((spr->ty - basey) * gr_viewludcos) * gr_viewsin;
+		wallVerts[3].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin;
+		wallVerts[2].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin;
 
-		wallVerts[0].z += ((lowy - basey) * gr_viewludcos) * gr_viewsin;
-		wallVerts[1].z += ((lowy - basey) * gr_viewludcos) * gr_viewsin;
+		wallVerts[0].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin;
+		wallVerts[1].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin;
 	}
 }
 
-static void HWR_SplitSprite(gr_vissprite_t *spr)
+static void HWR_SplitSprite(gl_vissprite_t *spr)
 {
-	float this_scale = 1.0f;
 	FOutVector wallVerts[4];
 	FOutVector baseWallVerts[4]; // This is what the verts should end up as
-	GLPatch_t *gpatch;
+	patch_t *gpatch;
 	FSurfaceInfo Surf;
-	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
-	extracolormap_t *colormap;
+	extracolormap_t *colormap = NULL;
 	FUINT lightlevel;
+	boolean lightset = true;
 	FBITFIELD blend = 0;
+	FBITFIELD occlusion;
+	boolean use_linkdraw_hack = false;
+	boolean splat = R_ThingIsFloorSprite(spr->mobj);
 	UINT8 alpha;
 
 	INT32 i;
@@ -3679,12 +3724,7 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 	fixed_t temp;
 	fixed_t v1x, v1y, v2x, v2y;
 
-	this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
-
-	if (hires)
-		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
-
-	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch;
 
 	// cache the patch in the graphics card memory
 	//12/12/99: Hurdler: same comment as above (for md2)
@@ -3696,11 +3736,8 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 	baseWallVerts[0].z = baseWallVerts[3].z = spr->z1;
 	baseWallVerts[1].z = baseWallVerts[2].z = spr->z2;
 
-	baseWallVerts[2].y = baseWallVerts[3].y = spr->ty;
-	if (spr->mobj && fabsf(this_scale - 1.0f) > 1.0E-36f)
-		baseWallVerts[0].y = baseWallVerts[1].y = spr->ty - gpatch->height * this_scale;
-	else
-		baseWallVerts[0].y = baseWallVerts[1].y = spr->ty - gpatch->height;
+	baseWallVerts[2].y = baseWallVerts[3].y = spr->gzt;
+	baseWallVerts[0].y = baseWallVerts[1].y = spr->gz;
 
 	v1x = FLOAT_TO_FIXED(spr->x1);
 	v1y = FLOAT_TO_FIXED(spr->z1);
@@ -3709,39 +3746,42 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 
 	if (spr->flip)
 	{
-		baseWallVerts[0].s = baseWallVerts[3].s = gpatch->max_s;
+		baseWallVerts[0].s = baseWallVerts[3].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 		baseWallVerts[2].s = baseWallVerts[1].s = 0;
 	}
 	else
 	{
 		baseWallVerts[0].s = baseWallVerts[3].s = 0;
-		baseWallVerts[2].s = baseWallVerts[1].s = gpatch->max_s;
+		baseWallVerts[2].s = baseWallVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 	}
 
 	// flip the texture coords (look familiar?)
 	if (spr->vflip)
 	{
-		baseWallVerts[3].t = baseWallVerts[2].t = gpatch->max_t;
+		baseWallVerts[3].t = baseWallVerts[2].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 		baseWallVerts[0].t = baseWallVerts[1].t = 0;
 	}
 	else
 	{
 		baseWallVerts[3].t = baseWallVerts[2].t = 0;
-		baseWallVerts[0].t = baseWallVerts[1].t = gpatch->max_t;
+		baseWallVerts[0].t = baseWallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 	}
 
-	// if it has a dispoffset, push it a little towards the camera
-	if (spr->dispoffset) {
-		float co = -gr_viewcos*(0.05f*spr->dispoffset);
-		float si = -gr_viewsin*(0.05f*spr->dispoffset);
-		baseWallVerts[0].z = baseWallVerts[3].z = baseWallVerts[0].z+si;
-		baseWallVerts[1].z = baseWallVerts[2].z = baseWallVerts[1].z+si;
-		baseWallVerts[0].x = baseWallVerts[3].x = baseWallVerts[0].x+co;
-		baseWallVerts[1].x = baseWallVerts[2].x = baseWallVerts[1].x+co;
-	}
+	if (!splat)
+	{
+		// if it has a dispoffset, push it a little towards the camera
+		if (spr->dispoffset) {
+			float co = -gl_viewcos*(0.05f*spr->dispoffset);
+			float si = -gl_viewsin*(0.05f*spr->dispoffset);
+			baseWallVerts[0].z = baseWallVerts[3].z = baseWallVerts[0].z+si;
+			baseWallVerts[1].z = baseWallVerts[2].z = baseWallVerts[1].z+si;
+			baseWallVerts[0].x = baseWallVerts[3].x = baseWallVerts[0].x+co;
+			baseWallVerts[1].x = baseWallVerts[2].x = baseWallVerts[1].x+co;
+		}
 
-	// Let dispoffset work first since this adjust each vertex
-	HWR_RotateSpritePolyToAim(spr, baseWallVerts, false);
+		// Let dispoffset work first since this adjust each vertex
+		HWR_RotateSpritePolyToAim(spr, baseWallVerts, false);
+	}
 
 	realtop = top = baseWallVerts[3].y;
 	realbot = bot = baseWallVerts[0].y;
@@ -3757,18 +3797,31 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 	// co-ordinates
 	memcpy(wallVerts, baseWallVerts, sizeof(baseWallVerts));
 
+	// if sprite has linkdraw, then dont write to z-buffer (by not using PF_Occlude)
+	// this will result in sprites drawn afterwards to be drawn on top like intended when using linkdraw.
+	if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+		occlusion = 0;
+	else
+		occlusion = PF_Occlude;
+
 	if (!cv_translucency.value) // translucency disabled
 	{
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = PF_Translucent|PF_Occlude;
+		blend = PF_Translucent|occlusion;
+		if (!occlusion) use_linkdraw_hack = true;
 	}
 	else if (spr->mobj->flags2 & MF2_SHADOW)
 	{
 		Surf.PolyColor.s.alpha = 0x40;
-		blend = PF_Translucent;
+		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode);
 	}
 	else if (spr->mobj->frame & FF_TRANSMASK)
-		blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+	{
+		INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
+		if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+			return;
+		blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
+	}
 	else
 	{
 		// BP: i agree that is little better in environement but it don't
@@ -3776,28 +3829,36 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 		// Hurdler: PF_Environement would be cool, but we need to fix
 		//          the issue with the fog before
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = PF_Translucent|PF_Occlude;
+		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|occlusion;
+		if (!occlusion) use_linkdraw_hack = true;
 	}
 
 	alpha = Surf.PolyColor.s.alpha;
 
 	// Start with the lightlevel and colormap from the top of the sprite
 	lightlevel = *list[sector->numlights - 1].lightlevel;
-	colormap = *list[sector->numlights - 1].extra_colormap;
+	if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+		colormap = *list[sector->numlights - 1].extra_colormap;
+
 	i = 0;
 	temp = FLOAT_TO_FIXED(realtop);
 
-	if (spr->mobj->frame & FF_FULLBRIGHT)
+	if (R_ThingIsFullBright(spr->mobj))
 		lightlevel = 255;
+	else if (R_ThingIsFullDark(spr->mobj))
+		lightlevel = 0;
+	else
+		lightset = false;
 
 	for (i = 1; i < sector->numlights; i++)
 	{
 		fixed_t h = P_GetLightZAt(&sector->lightlist[i], spr->mobj->x, spr->mobj->y);
 		if (h <= temp)
 		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!lightset)
 				lightlevel = *list[i-1].lightlevel > 255 ? 255 : *list[i-1].lightlevel;
-			colormap = *list[i-1].extra_colormap;
+			if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+				colormap = *list[i-1].extra_colormap;
 			break;
 		}
 	}
@@ -3810,9 +3871,10 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 		// even if we aren't changing colormap or lightlevel, we still need to continue drawing down the sprite
 		if (!(list[i].flags & FF_NOSHADE) && (list[i].flags & FF_CUTSPRITES))
 		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!lightset)
 				lightlevel = *list[i].lightlevel > 255 ? 255 : *list[i].lightlevel;
-			colormap = *list[i].extra_colormap;
+			if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+				colormap = *list[i].extra_colormap;
 		}
 
 		if (i + 1 < sector->numlights)
@@ -3852,7 +3914,7 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 		wallVerts[1].y = endbot;
 
 		// The x and y only need to be adjusted in the case that it's not a papersprite
-		if (cv_grspritebillboarding.value
+		if (cv_glspritebillboarding.value
 			&& spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE))
 		{
 			// Get the x and z of the vertices so billboarding draws correctly
@@ -3879,7 +3941,10 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 
 		Surf.PolyColor.s.alpha = alpha;
 
-		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, 3, false); // sprite shader
+		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+
+		if (use_linkdraw_hack)
+			HWR_LinkDrawHackAdd(wallVerts, spr);
 
 		top = bot;
 		endtop = endbot;
@@ -3905,7 +3970,10 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 
 	Surf.PolyColor.s.alpha = alpha;
 
-	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, 3, false); // sprite shader
+	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+
+	if (use_linkdraw_hack)
+		HWR_LinkDrawHackAdd(wallVerts, spr);
 }
 
 // -----------------+
@@ -3913,18 +3981,12 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 //                  : (monsters, bonuses, weapons, lights, ...)
 // Returns          :
 // -----------------+
-static void HWR_DrawSprite(gr_vissprite_t *spr)
+static void HWR_DrawSprite(gl_vissprite_t *spr)
 {
-	float this_scale = 1.0f;
 	FOutVector wallVerts[4];
-	GLPatch_t *gpatch; // sprite patch converted to hardware
+	patch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
-	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
-	//const boolean papersprite = (spr->mobj && (spr->mobj->frame & FF_PAPERSPRITE));
-	if (spr->mobj)
-		this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
-	if (hires)
-		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
+	const boolean splat = R_ThingIsFloorSprite(spr->mobj);
 
 	if (!spr->mobj)
 		return;
@@ -3932,7 +3994,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	if (!spr->mobj->subsector)
 		return;
 
-	if (spr->mobj->subsector->sector->numlights)
+	if (spr->mobj->subsector->sector->numlights && !splat)
 	{
 		HWR_SplitSprite(spr);
 		return;
@@ -3944,7 +4006,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	//          sure to do it the right way. So actually, we keep normal sprite
 	//          in memory and we add the md2 model if it exists for that sprite
 
-	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch;
 
 #ifdef ALAM_LIGHTING
 	if (!(spr->mobj->flags2 & MF2_DEBRIS) && (spr->mobj->sprite != SPR_PLAY ||
@@ -3959,37 +4021,144 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	//  |/ |
 	//  0--1
 
-	// these were already scaled in HWR_ProjectSprite
-	wallVerts[0].x = wallVerts[3].x = spr->x1;
-	wallVerts[2].x = wallVerts[1].x = spr->x2;
-	wallVerts[2].y = wallVerts[3].y = spr->ty;
-	if (spr->mobj && fabsf(this_scale - 1.0f) > 1.0E-36f)
-		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height * this_scale;
+	if (splat)
+	{
+		F2DCoord verts[4];
+		F2DCoord rotated[4];
+
+		angle_t angle;
+		float ca, sa;
+		float w, h;
+		float xscale, yscale;
+		float xoffset, yoffset;
+		float leftoffset, topoffset;
+		float scale = spr->scale;
+		float zoffset = (P_MobjFlip(spr->mobj) * 0.05f);
+		pslope_t *splatslope = NULL;
+		INT32 i;
+
+		renderflags_t renderflags = spr->renderflags;
+		if (renderflags & RF_SHADOWEFFECTS)
+			scale *= spr->shadowscale;
+
+		if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD)
+			angle = spr->mobj->angle;
+		else
+			angle = viewangle;
+
+		if (!spr->rotated)
+			angle += spr->mobj->rollangle;
+
+		angle = -angle;
+		angle += ANGLE_90;
+
+		topoffset = spr->spriteyoffset;
+		leftoffset = spr->spritexoffset;
+		if (spr->flip)
+			leftoffset = ((float)gpatch->width - leftoffset);
+
+		xscale = spr->scale * spr->spritexscale;
+		yscale = spr->scale * spr->spriteyscale;
+
+		xoffset = leftoffset * xscale;
+		yoffset = topoffset * yscale;
+
+		w = (float)gpatch->width * xscale;
+		h = (float)gpatch->height * yscale;
+
+		// Set positions
+
+		// 3--2
+		// |  |
+		// 0--1
+
+		verts[3].x = -xoffset;
+		verts[3].y = yoffset;
+
+		verts[2].x = w - xoffset;
+		verts[2].y = yoffset;
+
+		verts[1].x = w - xoffset;
+		verts[1].y = -h + yoffset;
+
+		verts[0].x = -xoffset;
+		verts[0].y = -h + yoffset;
+
+		ca = FIXED_TO_FLOAT(FINECOSINE((-angle)>>ANGLETOFINESHIFT));
+		sa = FIXED_TO_FLOAT(FINESINE((-angle)>>ANGLETOFINESHIFT));
+
+		// Rotate
+		for (i = 0; i < 4; i++)
+		{
+			rotated[i].x = (verts[i].x * ca) - (verts[i].y * sa);
+			rotated[i].y = (verts[i].x * sa) + (verts[i].y * ca);
+		}
+
+		// Translate
+		for (i = 0; i < 4; i++)
+		{
+			wallVerts[i].x = rotated[i].x + FIXED_TO_FLOAT(spr->mobj->x);
+			wallVerts[i].z = rotated[i].y + FIXED_TO_FLOAT(spr->mobj->y);
+		}
+
+		if (renderflags & (RF_SLOPESPLAT | RF_OBJECTSLOPESPLAT))
+		{
+			pslope_t *standingslope = spr->mobj->standingslope; // The slope that the object is standing on.
+
+			// The slope that was defined for the sprite.
+			if (renderflags & RF_SLOPESPLAT)
+				splatslope = spr->mobj->floorspriteslope;
+
+			if (standingslope && (renderflags & RF_OBJECTSLOPESPLAT))
+				splatslope = standingslope;
+		}
+
+		// Set vertical position
+		if (splatslope)
+		{
+			for (i = 0; i < 4; i++)
+			{
+				fixed_t slopez = P_GetSlopeZAt(splatslope, FLOAT_TO_FIXED(wallVerts[i].x), FLOAT_TO_FIXED(wallVerts[i].z));
+				wallVerts[i].y = FIXED_TO_FLOAT(slopez) + zoffset;
+			}
+		}
+		else
+		{
+			for (i = 0; i < 4; i++)
+				wallVerts[i].y = FIXED_TO_FLOAT(spr->mobj->z) + zoffset;
+		}
+	}
 	else
-		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height;
+	{
+		// these were already scaled in HWR_ProjectSprite
+		wallVerts[0].x = wallVerts[3].x = spr->x1;
+		wallVerts[2].x = wallVerts[1].x = spr->x2;
+		wallVerts[2].y = wallVerts[3].y = spr->gzt;
+		wallVerts[0].y = wallVerts[1].y = spr->gz;
 
-	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
-	// and the 2d map coords of start/end vertices
-	wallVerts[0].z = wallVerts[3].z = spr->z1;
-	wallVerts[1].z = wallVerts[2].z = spr->z2;
+		// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
+		// and the 2d map coords of start/end vertices
+		wallVerts[0].z = wallVerts[3].z = spr->z1;
+		wallVerts[1].z = wallVerts[2].z = spr->z2;
+	}
 
 	if (spr->flip)
 	{
-		wallVerts[0].s = wallVerts[3].s = gpatch->max_s;
+		wallVerts[0].s = wallVerts[3].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 		wallVerts[2].s = wallVerts[1].s = 0;
 	}else{
 		wallVerts[0].s = wallVerts[3].s = 0;
-		wallVerts[2].s = wallVerts[1].s = gpatch->max_s;
+		wallVerts[2].s = wallVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 	}
 
 	// flip the texture coords (look familiar?)
 	if (spr->vflip)
 	{
-		wallVerts[3].t = wallVerts[2].t = gpatch->max_t;
+		wallVerts[3].t = wallVerts[2].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 		wallVerts[0].t = wallVerts[1].t = 0;
 	}else{
 		wallVerts[3].t = wallVerts[2].t = 0;
-		wallVerts[0].t = wallVerts[1].t = gpatch->max_t;
+		wallVerts[0].t = wallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 	}
 
 	// cache the patch in the graphics card memory
@@ -3997,18 +4166,21 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	HWR_GetMappedPatch(gpatch, spr->colormap);
 
-	// if it has a dispoffset, push it a little towards the camera
-	if (spr->dispoffset) {
-		float co = -gr_viewcos*(0.05f*spr->dispoffset);
-		float si = -gr_viewsin*(0.05f*spr->dispoffset);
-		wallVerts[0].z = wallVerts[3].z = wallVerts[0].z+si;
-		wallVerts[1].z = wallVerts[2].z = wallVerts[1].z+si;
-		wallVerts[0].x = wallVerts[3].x = wallVerts[0].x+co;
-		wallVerts[1].x = wallVerts[2].x = wallVerts[1].x+co;
-	}
+	if (!splat)
+	{
+		// if it has a dispoffset, push it a little towards the camera
+		if (spr->dispoffset) {
+			float co = -gl_viewcos*(0.05f*spr->dispoffset);
+			float si = -gl_viewsin*(0.05f*spr->dispoffset);
+			wallVerts[0].z = wallVerts[3].z = wallVerts[0].z+si;
+			wallVerts[1].z = wallVerts[2].z = wallVerts[1].z+si;
+			wallVerts[0].x = wallVerts[3].x = wallVerts[0].x+co;
+			wallVerts[1].x = wallVerts[2].x = wallVerts[1].x+co;
+		}
 
-	// Let dispoffset work first since this adjust each vertex
-	HWR_RotateSpritePolyToAim(spr, wallVerts, false);
+		// Let dispoffset work first since this adjust each vertex
+		HWR_RotateSpritePolyToAim(spr, wallVerts, false);
+	}
 
 	// This needs to be AFTER the shadows so that the regular sprites aren't drawn completely black.
 	// sprite lighting by modulating the RGB components
@@ -4017,10 +4189,31 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	// colormap test
 	{
 		sector_t *sector = spr->mobj->subsector->sector;
-		UINT8 lightlevel = 255;
-		extracolormap_t *colormap = sector->extra_colormap;
+		UINT8 lightlevel = 0;
+		boolean lightset = true;
+		extracolormap_t *colormap = NULL;
+
+		if (R_ThingIsFullBright(spr->mobj))
+			lightlevel = 255;
+		else if (R_ThingIsFullDark(spr->mobj))
+			lightlevel = 0;
+		else
+			lightset = false;
 
-		if (!(spr->mobj->frame & FF_FULLBRIGHT))
+		if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+			colormap = sector->extra_colormap;
+
+		if (splat && sector->numlights)
+		{
+			INT32 light = R_GetPlaneLight(sector, spr->mobj->z, false);
+
+			if (!lightset)
+				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
+
+			if (*sector->lightlist[light].extra_colormap && !(spr->mobj->renderflags & RF_NOCOLORMAPS))
+				colormap = *sector->lightlist[light].extra_colormap;
+		}
+		else if (!lightset)
 			lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
 		HWR_Lighting(&Surf, lightlevel, colormap);
@@ -4028,18 +4221,34 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 
 	{
 		FBITFIELD blend = 0;
+		FBITFIELD occlusion;
+		boolean use_linkdraw_hack = false;
+
+		// if sprite has linkdraw, then dont write to z-buffer (by not using PF_Occlude)
+		// this will result in sprites drawn afterwards to be drawn on top like intended when using linkdraw.
+		if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+			occlusion = 0;
+		else
+			occlusion = PF_Occlude;
+
 		if (!cv_translucency.value) // translucency disabled
 		{
 			Surf.PolyColor.s.alpha = 0xFF;
-			blend = PF_Translucent|PF_Occlude;
+			blend = PF_Translucent|occlusion;
+			if (!occlusion) use_linkdraw_hack = true;
 		}
 		else if (spr->mobj->flags2 & MF2_SHADOW)
 		{
 			Surf.PolyColor.s.alpha = 0x40;
-			blend = PF_Translucent;
+			blend = HWR_GetBlendModeFlag(spr->mobj->blendmode);
 		}
 		else if (spr->mobj->frame & FF_TRANSMASK)
-			blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+		{
+			INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
+			if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+				return;
+			blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
+		}
 		else
 		{
 			// BP: i agree that is little better in environement but it don't
@@ -4047,20 +4256,36 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 			// Hurdler: PF_Environement would be cool, but we need to fix
 			//          the issue with the fog before
 			Surf.PolyColor.s.alpha = 0xFF;
-			blend = PF_Translucent|PF_Occlude;
+			blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|occlusion;
+			if (!occlusion) use_linkdraw_hack = true;
 		}
 
-		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, 3, false); // sprite shader
+		if (spr->renderflags & RF_SHADOWEFFECTS)
+		{
+			INT32 alpha = Surf.PolyColor.s.alpha;
+			alpha -= ((INT32)(spr->shadowheight / 4.0f)) + 75;
+			if (alpha < 1)
+				return;
+
+			Surf.PolyColor.s.alpha = (UINT8)(alpha);
+			blend = PF_Translucent|occlusion;
+			if (!occlusion) use_linkdraw_hack = true;
+		}
+
+		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
+
+		if (use_linkdraw_hack)
+			HWR_LinkDrawHackAdd(wallVerts, spr);
 	}
 }
 
 #ifdef HWPRECIP
 // Sprite drawer for precipitation
-static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
+static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 {
 	FBITFIELD blend = 0;
 	FOutVector wallVerts[4];
-	GLPatch_t *gpatch; // sprite patch converted to hardware
+	patch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
 
 	if (!spr->mobj)
@@ -4070,7 +4295,7 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 		return;
 
 	// cache sprite graphics
-	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch;
 
 	// create the sprite billboard
 	//
@@ -4080,8 +4305,8 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 	//  0--1
 	wallVerts[0].x = wallVerts[3].x = spr->x1;
 	wallVerts[2].x = wallVerts[1].x = spr->x2;
-	wallVerts[2].y = wallVerts[3].y = spr->ty;
-	wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height;
+	wallVerts[2].y = wallVerts[3].y = spr->gzt;
+	wallVerts[0].y = wallVerts[1].y = spr->gz;
 
 	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
 	// and the 2d map coords of start/end vertices
@@ -4092,10 +4317,10 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 	HWR_RotateSpritePolyToAim(spr, wallVerts, true);
 
 	wallVerts[0].s = wallVerts[3].s = 0;
-	wallVerts[2].s = wallVerts[1].s = gpatch->max_s;
+	wallVerts[2].s = wallVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 
 	wallVerts[3].t = wallVerts[2].t = 0;
-	wallVerts[0].t = wallVerts[1].t = gpatch->max_t;
+	wallVerts[0].t = wallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 
 	// cache the patch in the graphics card memory
 	//12/12/99: Hurdler: same comment as above (for md2)
@@ -4110,9 +4335,8 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 
 		if (sector->numlights)
 		{
-			INT32 light;
-
-			light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
+			// Always use the light at the top instead of whatever I was doing before
+			INT32 light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false);
 
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
 				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
@@ -4132,13 +4356,13 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 		HWR_Lighting(&Surf, lightlevel, colormap);
 	}
 
-	if (spr->mobj->flags2 & MF2_SHADOW)
+	if (spr->mobj->frame & FF_TRANSMASK)
 	{
-		Surf.PolyColor.s.alpha = 0x40;
-		blend = PF_Translucent;
+		INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
+		if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+			return;
+		blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
 	}
-	else if (spr->mobj->frame & FF_TRANSMASK)
-		blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
 	else
 	{
 		// BP: i agree that is little better in environement but it don't
@@ -4146,26 +4370,27 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 		// Hurdler: PF_Environement would be cool, but we need to fix
 		//          the issue with the fog before
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = PF_Translucent|PF_Occlude;
+		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|PF_Occlude;
 	}
 
-	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, 3, false); // sprite shader
+	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
 }
 #endif
 
 // --------------------------------------------------------------------------
 // Sort vissprites by distance
 // --------------------------------------------------------------------------
-gr_vissprite_t* gr_vsprorder[MAXVISSPRITES];
+gl_vissprite_t* gl_vsprorder[MAXVISSPRITES];
 
 // Note: For more correct transparency the transparent sprites would need to be
 // sorted and drawn together with transparent surfaces.
 static int CompareVisSprites(const void *p1, const void *p2)
 {
-	gr_vissprite_t* spr1 = *(gr_vissprite_t*const*)p1;
-	gr_vissprite_t* spr2 = *(gr_vissprite_t*const*)p2;
+	gl_vissprite_t* spr1 = *(gl_vissprite_t*const*)p1;
+	gl_vissprite_t* spr2 = *(gl_vissprite_t*const*)p2;
 	int idiff;
 	float fdiff;
+	float tz1, tz2;
 
 	// Make transparent sprites last. Comment from the previous sort implementation:
 	// Sryder:	Oh boy, while it's nice having ALL the sprites sorted properly, it fails when we bring MD2's into the
@@ -4173,12 +4398,53 @@ static int CompareVisSprites(const void *p1, const void *p2)
 	//			everything else, but still ordered of course, the depth buffer can handle the opaque ones plenty fine.
 	//			We just need to move all translucent ones to the end in order
 	// TODO:	Fully sort all sprites and MD2s with walls and floors, this part will be unnecessary after that
-	int transparency1 = (spr1->mobj->flags2 & MF2_SHADOW) || (spr1->mobj->frame & FF_TRANSMASK);
-	int transparency2 = (spr2->mobj->flags2 & MF2_SHADOW) || (spr2->mobj->frame & FF_TRANSMASK);
+	int transparency1;
+	int transparency2;
+
+	// check for precip first, because then sprX->mobj is actually a precipmobj_t and does not have flags2 or tracer
+	int linkdraw1 = !spr1->precip && (spr1->mobj->flags2 & MF2_LINKDRAW) && spr1->mobj->tracer;
+	int linkdraw2 = !spr2->precip && (spr2->mobj->flags2 & MF2_LINKDRAW) && spr2->mobj->tracer;
+
+	// ^ is the XOR operation
+	// if comparing a linkdraw and non-linkdraw sprite or 2 linkdraw sprites with different tracers, then use
+	// the tracer's properties instead of the main sprite's.
+	if ((linkdraw1 && linkdraw2 && spr1->mobj->tracer != spr2->mobj->tracer) || (linkdraw1 ^ linkdraw2))
+	{
+		if (linkdraw1)
+		{
+			tz1 = spr1->tracertz;
+			transparency1 = (spr1->mobj->tracer->flags2 & MF2_SHADOW) || (spr1->mobj->tracer->frame & FF_TRANSMASK);
+		}
+		else
+		{
+			tz1 = spr1->tz;
+			transparency1 = (!spr1->precip && (spr1->mobj->flags2 & MF2_SHADOW)) || (spr1->mobj->frame & FF_TRANSMASK);
+		}
+		if (linkdraw2)
+		{
+			tz2 = spr2->tracertz;
+			transparency2 = (spr2->mobj->tracer->flags2 & MF2_SHADOW) || (spr2->mobj->tracer->frame & FF_TRANSMASK);
+		}
+		else
+		{
+			tz2 = spr2->tz;
+			transparency2 = (!spr2->precip && (spr2->mobj->flags2 & MF2_SHADOW)) || (spr2->mobj->frame & FF_TRANSMASK);
+		}
+	}
+	else
+	{
+		tz1 = spr1->tz;
+		transparency1 = (!spr1->precip && (spr1->mobj->flags2 & MF2_SHADOW)) || (spr1->mobj->frame & FF_TRANSMASK);
+		tz2 = spr2->tz;
+		transparency2 = (!spr2->precip && (spr2->mobj->flags2 & MF2_SHADOW)) || (spr2->mobj->frame & FF_TRANSMASK);
+	}
+
+	// first compare transparency flags, then compare tz, then compare dispoffset
+
 	idiff = transparency1 - transparency2;
 	if (idiff != 0) return idiff;
 
-	fdiff = spr2->tz - spr1->tz; // this order seems correct when checking with apitrace. Back to front.
+	fdiff = tz2 - tz1; // this order seems correct when checking with apitrace. Back to front.
 	if (fabsf(fdiff) < 1.0E-36f)
 		return spr1->dispoffset - spr2->dispoffset; // smallest dispoffset first if sprites are at (almost) same location.
 	else if (fdiff > 0)
@@ -4190,11 +4456,11 @@ static int CompareVisSprites(const void *p1, const void *p2)
 static void HWR_SortVisSprites(void)
 {
 	UINT32 i;
-	for (i = 0; i < gr_visspritecount; i++)
+	for (i = 0; i < gl_visspritecount; i++)
 	{
-		gr_vsprorder[i] = HWR_GetVisSprite(i);
+		gl_vsprorder[i] = HWR_GetVisSprite(i);
 	}
-	qsort(gr_vsprorder, gr_visspritecount, sizeof(gr_vissprite_t*), CompareVisSprites);
+	qsort(gl_vsprorder, gl_visspritecount, sizeof(gl_vissprite_t*), CompareVisSprites);
 }
 
 // A drawnode is something that points to a 3D floor, 3D side, or masked
@@ -4254,16 +4520,16 @@ static size_t numpolyplanes = 0; // a list of transparent poyobject floors to be
 static polyplaneinfo_t *polyplaneinfo = NULL;
 
 //Hurdler: 3D water sutffs
-typedef struct gr_drawnode_s
+typedef struct gl_drawnode_s
 {
 	planeinfo_t *plane;
 	polyplaneinfo_t *polyplane;
 	wallinfo_t *wall;
-	gr_vissprite_t *sprite;
+	gl_vissprite_t *sprite;
 
-//	struct gr_drawnode_s *next;
-//	struct gr_drawnode_s *prev;
-} gr_drawnode_t;
+//	struct gl_drawnode_s *next;
+//	struct gl_drawnode_s *prev;
+} gl_drawnode_t;
 
 static INT32 drawcount = 0;
 
@@ -4329,7 +4595,7 @@ void HWR_AddTransparentPolyobjectFloor(levelflat_t *levelflat, polyobj_t *polyse
 }
 
 // putting sortindex and sortnode here so the comparator function can see them
-gr_drawnode_t *sortnode;
+gl_drawnode_t *sortnode;
 size_t *sortindex;
 
 static int CompareDrawNodes(const void *p1, const void *p2)
@@ -4389,7 +4655,7 @@ static void HWR_CreateDrawNodes(void)
 	// that is already lying around. This should all be in some sort of linked list or lists.
 	sortindex = Z_Calloc(sizeof(size_t) * (numplanes + numpolyplanes + numwalls), PU_STATIC, NULL);
 
-	rs_hw_nodesorttime = I_GetTimeMicros();
+	ps_hw_nodesorttime = I_GetPreciseTime();
 
 	for (i = 0; i < numplanes; i++, p++)
 	{
@@ -4409,7 +4675,7 @@ static void HWR_CreateDrawNodes(void)
 		sortindex[p] = p;
 	}
 
-	rs_numdrawnodes = p;
+	ps_numdrawnodes = p;
 
 	// p is the number of stuff to sort
 
@@ -4444,20 +4710,20 @@ static void HWR_CreateDrawNodes(void)
 		}
 	}
 
-	rs_hw_nodesorttime = I_GetTimeMicros() - rs_hw_nodesorttime;
+	ps_hw_nodesorttime = I_GetPreciseTime() - ps_hw_nodesorttime;
 
-	rs_hw_nodedrawtime = I_GetTimeMicros();
+	ps_hw_nodedrawtime = I_GetPreciseTime();
 
 	// Okay! Let's draw it all! Woo!
 	HWD.pfnSetTransform(&atransform);
-	HWD.pfnSetShader(0);
+	HWD.pfnSetShader(SHADER_DEFAULT);
 
 	for (i = 0; i < p; i++)
 	{
 		if (sortnode[sortindex[i]].plane)
 		{
-			// We aren't traversing the BSP tree, so make gr_frontsector null to avoid crashes.
-			gr_frontsector = NULL;
+			// We aren't traversing the BSP tree, so make gl_frontsector null to avoid crashes.
+			gl_frontsector = NULL;
 
 			if (!(sortnode[sortindex[i]].plane->blend & PF_NoTexture))
 				HWR_GetLevelFlat(sortnode[sortindex[i]].plane->levelflat);
@@ -4466,8 +4732,8 @@ static void HWR_CreateDrawNodes(void)
 		}
 		else if (sortnode[sortindex[i]].polyplane)
 		{
-			// We aren't traversing the BSP tree, so make gr_frontsector null to avoid crashes.
-			gr_frontsector = NULL;
+			// We aren't traversing the BSP tree, so make gl_frontsector null to avoid crashes.
+			gl_frontsector = NULL;
 
 			if (!(sortnode[sortindex[i]].polyplane->blend & PF_NoTexture))
 				HWR_GetLevelFlat(sortnode[sortindex[i]].polyplane->levelflat);
@@ -4483,7 +4749,7 @@ static void HWR_CreateDrawNodes(void)
 		}
 	}
 
-	rs_hw_nodedrawtime = I_GetTimeMicros() - rs_hw_nodedrawtime;
+	ps_hw_nodedrawtime = I_GetPreciseTime() - ps_hw_nodedrawtime;
 
 	numwalls = 0;
 	numplanes = 0;
@@ -4502,24 +4768,46 @@ static void HWR_CreateDrawNodes(void)
 static void HWR_DrawSprites(void)
 {
 	UINT32 i;
-	HWD.pfnSetSpecialState(HWD_SET_MODEL_LIGHTING, cv_grmodellighting.value);
-	for (i = 0; i < gr_visspritecount; i++)
+	boolean skipshadow = false; // skip shadow if it was drawn already for a linkdraw sprite encountered earlier in the list
+	HWD.pfnSetSpecialState(HWD_SET_MODEL_LIGHTING, cv_glmodellighting.value);
+	for (i = 0; i < gl_visspritecount; i++)
 	{
-		gr_vissprite_t *spr = gr_vsprorder[i];
+		gl_vissprite_t *spr = gl_vsprorder[i];
 #ifdef HWPRECIP
 		if (spr->precip)
 			HWR_DrawPrecipitationSprite(spr);
 		else
 #endif
 		{
-			if (spr->mobj && spr->mobj->shadowscale && cv_shadow.value)
+			if (spr->mobj && spr->mobj->shadowscale && cv_shadow.value && !skipshadow)
 			{
 				HWR_DrawDropShadow(spr->mobj, spr->mobj->shadowscale);
 			}
 
+			if ((spr->mobj->flags2 & MF2_LINKDRAW) && spr->mobj->tracer)
+			{
+				// If this linkdraw sprite is behind a sprite that has a shadow,
+				// then that shadow has to be drawn first, otherwise the shadow ends up on top of
+				// the linkdraw sprite because the linkdraw sprite does not modify the z-buffer.
+				// The !skipshadow check is there in case there are multiple linkdraw sprites connected
+				// to the same tracer, so the tracer's shadow only gets drawn once.
+				if (cv_shadow.value && !skipshadow && spr->dispoffset < 0 && spr->mobj->tracer->shadowscale)
+				{
+					HWR_DrawDropShadow(spr->mobj->tracer, spr->mobj->tracer->shadowscale);
+					skipshadow = true;
+					// The next sprite in this loop should be either another linkdraw sprite or the tracer.
+					// When the tracer is inevitably encountered, skipshadow will cause it's shadow
+					// to get skipped and skipshadow will get set to false by the 'else' clause below.
+				}
+			}
+			else
+			{
+				skipshadow = false;
+			}
+
 			if (spr->mobj && spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
 			{
-				if (!cv_grmodels.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
+				if (!cv_glmodels.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
 					HWR_DrawSprite(spr);
 				else
 				{
@@ -4529,7 +4817,7 @@ static void HWR_DrawSprites(void)
 			}
 			else
 			{
-				if (!cv_grmodels.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
+				if (!cv_glmodels.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
 					HWR_DrawSprite(spr);
 				else
 				{
@@ -4540,6 +4828,16 @@ static void HWR_DrawSprites(void)
 		}
 	}
 	HWD.pfnSetSpecialState(HWD_SET_MODEL_LIGHTING, 0);
+
+	// At the end of sprite drawing, draw shapes of linkdraw sprites to z-buffer, so they
+	// don't get drawn over by transparent surfaces.
+	HWR_LinkDrawHackFinish();
+	// Work around a r_opengl.c bug with PF_Invisible by making this SetBlend call
+	// where PF_Invisible is off and PF_Masked is on.
+	// (Other states probably don't matter. Here I left them same as in LinkDrawHackFinish)
+	// Without this workaround the rest of the draw calls in this frame (including UI, screen texture)
+	// can get drawn using an incorrect glBlendFunc, resulting in a occasional black screen.
+	HWD.pfnSetBlend(PF_Translucent|PF_Occlude|PF_Masked);
 }
 
 // --------------------------------------------------------------------------
@@ -4598,27 +4896,34 @@ static void HWR_AddSprites(sector_t *sec)
 // BP why not use xtoviexangle/viewangletox like in bsp ?....
 static void HWR_ProjectSprite(mobj_t *thing)
 {
-	gr_vissprite_t *vis;
+	gl_vissprite_t *vis;
 	float tr_x, tr_y;
 	float tz;
+	float tracertz = 0.0f;
 	float x1, x2;
 	float rightsin, rightcos;
-	float this_scale;
+	float this_scale, this_xscale, this_yscale;
+	float spritexscale, spriteyscale;
+	float shadowheight = 1.0f, shadowscale = 1.0f;
 	float gz, gzt;
 	spritedef_t *sprdef;
 	spriteframe_t *sprframe;
+#ifdef ROTSPRITE
 	spriteinfo_t *sprinfo;
+#endif
 	md2_t *md2;
 	size_t lumpoff;
 	unsigned rot;
 	UINT16 flip;
-	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
+	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !R_ThingVerticallyFlipped(thing));
 	boolean mirrored = thing->mirrored;
-	boolean hflip = (!(thing->frame & FF_HORIZONTALFLIP) != !mirrored);
+	boolean hflip = (!R_ThingHorizontallyFlipped(thing) != !mirrored);
+	INT32 dispoffset;
 
 	angle_t ang;
 	INT32 heightsec, phs;
-	const boolean papersprite = (thing->frame & FF_PAPERSPRITE);
+	const boolean papersprite = R_ThingIsPaperSprite(thing);
+	const boolean splat = R_ThingIsFloorSprite(thing);
 	angle_t mobjangle = (thing->player ? thing->player->drawangle : thing->angle);
 	float z1, z2;
 
@@ -4632,19 +4937,26 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (!thing)
 		return;
 
+	if (thing->spritexscale < 1 || thing->spriteyscale < 1)
+		return;
+
+	dispoffset = thing->info->dispoffset;
+
 	this_scale = FIXED_TO_FLOAT(thing->scale);
+	spritexscale = FIXED_TO_FLOAT(thing->spritexscale);
+	spriteyscale = FIXED_TO_FLOAT(thing->spriteyscale);
 
 	// transform the origin point
-	tr_x = FIXED_TO_FLOAT(thing->x) - gr_viewx;
-	tr_y = FIXED_TO_FLOAT(thing->y) - gr_viewy;
+	tr_x = FIXED_TO_FLOAT(thing->x) - gl_viewx;
+	tr_y = FIXED_TO_FLOAT(thing->y) - gl_viewy;
 
 	// rotation around vertical axis
-	tz = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
+	tz = (tr_x * gl_viewcos) + (tr_y * gl_viewsin);
 
 	// thing is behind view plane?
-	if (tz < ZCLIP_PLANE && !papersprite)
+	if (tz < ZCLIP_PLANE && !(papersprite || splat))
 	{
-		if (cv_grmodels.value) //Yellow: Only MD2's dont disappear
+		if (cv_glmodels.value) //Yellow: Only MD2's dont disappear
 		{
 			if (thing->skin && thing->sprite == SPR_PLAY)
 				md2 = &md2_playermodels[( (skin_t *)thing->skin - skins )];
@@ -4674,12 +4986,16 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (thing->skin && thing->sprite == SPR_PLAY)
 	{
 		sprdef = &((skin_t *)thing->skin)->sprites[thing->sprite2];
+#ifdef ROTSPRITE
 		sprinfo = &((skin_t *)thing->skin)->sprinfo[thing->sprite2];
+#endif
 	}
 	else
 	{
 		sprdef = &sprites[thing->sprite];
-		sprinfo = NULL;
+#ifdef ROTSPRITE
+		sprinfo = &spriteinfo[thing->sprite];
+#endif
 	}
 
 	if (rot >= sprdef->numframes)
@@ -4689,7 +5005,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		thing->sprite = states[S_UNKNOWN].sprite;
 		thing->frame = states[S_UNKNOWN].frame;
 		sprdef = &sprites[thing->sprite];
-		sprinfo = NULL;
+#ifdef ROTSPRITE
+		sprinfo = &spriteinfo[thing->sprite];
+#endif
 		rot = thing->frame&FF_FRAMEMASK;
 		thing->state->sprite = thing->sprite;
 		thing->state->frame = thing->frame;
@@ -4740,7 +5058,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	}
 
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
-		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale);
+		this_scale *= FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale);
 
 	spr_width = spritecachedinfo[lumpoff].width;
 	spr_height = spritecachedinfo[lumpoff].height;
@@ -4748,24 +5066,42 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	spr_topoffset = spritecachedinfo[lumpoff].topoffset;
 
 #ifdef ROTSPRITE
-	if (thing->rollangle)
+	if (thing->rollangle
+	&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)))
 	{
 		rollangle = R_GetRollAngle(thing->rollangle);
-		if (!(sprframe->rotsprite.cached & (1<<rot)))
-			R_CacheRotSprite(thing->sprite, (thing->frame & FF_FRAMEMASK), sprinfo, sprframe, rot, flip);
-		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
+		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
+
 		if (rotsprite != NULL)
 		{
-			spr_width = SHORT(rotsprite->width) << FRACBITS;
-			spr_height = SHORT(rotsprite->height) << FRACBITS;
-			spr_offset = SHORT(rotsprite->leftoffset) << FRACBITS;
-			spr_topoffset = SHORT(rotsprite->topoffset) << FRACBITS;
+			spr_width = rotsprite->width << FRACBITS;
+			spr_height = rotsprite->height << FRACBITS;
+			spr_offset = rotsprite->leftoffset << FRACBITS;
+			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			spr_topoffset += FEETADJUST;
+
 			// flip -> rotate, not rotate -> flip
 			flip = 0;
 		}
 	}
 #endif
 
+	if (thing->renderflags & RF_ABSOLUTEOFFSETS)
+	{
+		spr_offset = thing->spritexoffset;
+		spr_topoffset = thing->spriteyoffset;
+	}
+	else
+	{
+		SINT8 flipoffset = 1;
+
+		if ((thing->renderflags & RF_FLIPOFFSETS) && flip)
+			flipoffset = -1;
+
+		spr_offset += thing->spritexoffset * flipoffset;
+		spr_topoffset += thing->spriteyoffset * flipoffset;
+	}
+
 	if (papersprite)
 	{
 		rightsin = FIXED_TO_FLOAT(FINESINE((mobjangle)>>ANGLETOFINESHIFT));
@@ -4779,15 +5115,36 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	flip = !flip != !hflip;
 
+	if (thing->renderflags & RF_SHADOWEFFECTS)
+	{
+		mobj_t *caster = thing->target;
+
+		if (caster && !P_MobjWasRemoved(caster))
+		{
+			fixed_t groundz = R_GetShadowZ(thing, NULL);
+			fixed_t floordiff = abs(((thing->eflags & MFE_VERTICALFLIP) ? caster->height : 0) + caster->z - groundz);
+
+			shadowheight = FIXED_TO_FLOAT(floordiff);
+			shadowscale = FIXED_TO_FLOAT(FixedMul(FRACUNIT - floordiff/640, caster->scale));
+
+			if (splat)
+				spritexscale *= shadowscale;
+			spriteyscale *= shadowscale;
+		}
+	}
+
+	this_xscale = spritexscale * this_scale;
+	this_yscale = spriteyscale * this_scale;
+
 	if (flip)
 	{
-		x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_scale);
-		x2 = (FIXED_TO_FLOAT(spr_offset) * this_scale);
+		x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale);
+		x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale);
 	}
 	else
 	{
-		x1 = (FIXED_TO_FLOAT(spr_offset) * this_scale);
-		x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_scale);
+		x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale);
+		x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale);
 	}
 
 	// test if too close
@@ -4809,18 +5166,18 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if (vflip)
 	{
-		gz = FIXED_TO_FLOAT(thing->z+thing->height) - FIXED_TO_FLOAT(spr_topoffset) * this_scale;
-		gzt = gz + FIXED_TO_FLOAT(spr_height) * this_scale;
+		gz = FIXED_TO_FLOAT(thing->z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale);
+		gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale);
 	}
 	else
 	{
-		gzt = FIXED_TO_FLOAT(thing->z) + FIXED_TO_FLOAT(spr_topoffset) * this_scale;
-		gz = gzt - FIXED_TO_FLOAT(spr_height) * this_scale;
+		gzt = FIXED_TO_FLOAT(thing->z) + (FIXED_TO_FLOAT(spr_topoffset) * this_yscale);
+		gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale);
 	}
 
 	if (thing->subsector->sector->cullheight)
 	{
-		if (HWR_DoCulling(thing->subsector->sector->cullheight, viewsector->cullheight, gr_viewz, gz, gzt))
+		if (HWR_DoCulling(thing->subsector->sector->cullheight, viewsector->cullheight, gl_viewz, gz, gzt))
 			return;
 	}
 
@@ -4832,40 +5189,79 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if (heightsec != -1 && phs != -1) // only clip things which are in special sectors
 	{
-		if (gr_viewz < FIXED_TO_FLOAT(sectors[phs].floorheight) ?
+		if (gl_viewz < FIXED_TO_FLOAT(sectors[phs].floorheight) ?
 		FIXED_TO_FLOAT(thing->z) >= FIXED_TO_FLOAT(sectors[heightsec].floorheight) :
 		gzt < FIXED_TO_FLOAT(sectors[heightsec].floorheight))
 			return;
-		if (gr_viewz > FIXED_TO_FLOAT(sectors[phs].ceilingheight) ?
-		gzt < FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) && gr_viewz >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) :
+		if (gl_viewz > FIXED_TO_FLOAT(sectors[phs].ceilingheight) ?
+		gzt < FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) && gl_viewz >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight) :
 		FIXED_TO_FLOAT(thing->z) >= FIXED_TO_FLOAT(sectors[heightsec].ceilingheight))
 			return;
 	}
 
 	if ((thing->flags2 & MF2_LINKDRAW) && thing->tracer)
 	{
-		// bodge support - not nearly as comprehensive as r_things.c, but better than nothing
 		if (! R_ThingVisible(thing->tracer))
 			return;
+
+		// calculate tz for tracer, same way it is calculated for this sprite
+		// transform the origin point
+		tr_x = FIXED_TO_FLOAT(thing->tracer->x) - gl_viewx;
+		tr_y = FIXED_TO_FLOAT(thing->tracer->y) - gl_viewy;
+
+		// rotation around vertical axis
+		tracertz = (tr_x * gl_viewcos) + (tr_y * gl_viewsin);
+
+		// Software does not render the linkdraw sprite if the tracer is behind the view plane,
+		// so do the same check here.
+		// NOTE: This check has the same flaw as the view plane check at the beginning of HWR_ProjectSprite:
+		// the view aiming angle is not taken into account, leading to sprites disappearing too early when they
+		// can still be seen when looking down/up at steep angles.
+		if (tracertz < ZCLIP_PLANE)
+			return;
+
+		// if the sprite is behind the tracer, invert dispoffset, putting the sprite behind the tracer
+		if (tz > tracertz)
+			dispoffset *= -1;
 	}
 
 	// store information in a vissprite
 	vis = HWR_NewVisSprite();
 	vis->x1 = x1;
 	vis->x2 = x2;
+	vis->z1 = z1;
+	vis->z2 = z2;
+
 	vis->tz = tz; // Keep tz for the simple sprite sorting that happens
-	vis->dispoffset = thing->info->dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
-	//vis->patchlumpnum = sprframe->lumppat[rot];
+	vis->tracertz = tracertz;
+
+	vis->renderflags = thing->renderflags;
+	vis->rotateflags = sprframe->rotate;
+
+	vis->shadowheight = shadowheight;
+	vis->shadowscale = shadowscale;
+	vis->dispoffset = dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
+	vis->flip = flip;
+
+	vis->scale = this_scale;
+	vis->spritexscale = spritexscale;
+	vis->spriteyscale = spriteyscale;
+	vis->spritexoffset = FIXED_TO_FLOAT(spr_offset);
+	vis->spriteyoffset = FIXED_TO_FLOAT(spr_topoffset);
+
+	vis->rotated = false;
+
 #ifdef ROTSPRITE
 	if (rotsprite)
-		vis->gpatch = (GLPatch_t *)rotsprite;
+	{
+		vis->gpatch = (patch_t *)rotsprite;
+		vis->rotated = true;
+	}
 	else
 #endif
-		vis->gpatch = (GLPatch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
-	vis->flip = flip;
+		vis->gpatch = (patch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE);
+
 	vis->mobj = thing;
-	vis->z1 = z1;
-	vis->z2 = z2;
 
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	if ((vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
@@ -4900,10 +5296,11 @@ static void HWR_ProjectSprite(mobj_t *thing)
 			vis->colormap = R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color ? vis->mobj->color : SKINCOLOR_CYAN, GTC_CACHE);
 	}
 	else
-		vis->colormap = colormaps;
+		vis->colormap = NULL;
 
 	// set top/bottom coords
-	vis->ty = gzt;
+	vis->gzt = gzt;
+	vis->gz = gz;
 
 	//CONS_Debug(DBG_RENDER, "------------------\nH: sprite  : %d\nH: frame   : %x\nH: type    : %d\nH: sname   : %s\n\n",
 	//            thing->sprite, thing->frame, thing->type, sprnames[thing->sprite]);
@@ -4917,7 +5314,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 // Precipitation projector for hardware mode
 static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 {
-	gr_vissprite_t *vis;
+	gl_vissprite_t *vis;
 	float tr_x, tr_y;
 	float tz;
 	float x1, x2;
@@ -4930,11 +5327,11 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	UINT8 flip;
 
 	// transform the origin point
-	tr_x = FIXED_TO_FLOAT(thing->x) - gr_viewx;
-	tr_y = FIXED_TO_FLOAT(thing->y) - gr_viewy;
+	tr_x = FIXED_TO_FLOAT(thing->x) - gl_viewx;
+	tr_y = FIXED_TO_FLOAT(thing->y) - gl_viewy;
 
 	// rotation around vertical axis
-	tz = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
+	tz = (tr_x * gl_viewcos) + (tr_y * gl_viewsin);
 
 	// thing is behind view plane?
 	if (tz < ZCLIP_PLANE)
@@ -4996,15 +5393,15 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->z2 = z2;
 	vis->tz = tz;
 	vis->dispoffset = 0; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
-	//vis->patchlumpnum = sprframe->lumppat[rot];
-	vis->gpatch = (GLPatch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
+	vis->gpatch = (patch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE);
 	vis->flip = flip;
 	vis->mobj = (mobj_t *)thing;
 
-	vis->colormap = colormaps;
+	vis->colormap = NULL;
 
 	// set top/bottom coords
-	vis->ty = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset);
+	vis->gzt = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset);
+	vis->gz = vis->gzt - FIXED_TO_FLOAT(spritecachedinfo[lumpoff].height);
 
 	vis->precip = true;
 
@@ -5021,11 +5418,156 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 #endif
 
 // ==========================================================================
-//
+// Sky dome rendering, ported from PrBoom+
 // ==========================================================================
+
+static gl_sky_t gl_sky;
+
+static void HWR_SkyDomeVertex(gl_sky_t *sky, gl_skyvertex_t *vbo, int r, int c, signed char yflip, float delta, boolean foglayer)
+{
+	const float radians = (float)(M_PIl / 180.0f);
+	const float scale = 10000.0f;
+	const float maxSideAngle = 60.0f;
+
+	float topAngle = (c / (float)sky->columns * 360.0f);
+	float sideAngle = (maxSideAngle * (sky->rows - r) / sky->rows);
+	float height = (float)(sin(sideAngle * radians));
+	float realRadius = (float)(scale * cos(sideAngle * radians));
+	float x = (float)(realRadius * cos(topAngle * radians));
+	float y = (!yflip) ? scale * height : -scale * height;
+	float z = (float)(realRadius * sin(topAngle * radians));
+	float timesRepeat = (4 * (256.0f / sky->width));
+	if (fpclassify(timesRepeat) == FP_ZERO)
+		timesRepeat = 1.0f;
+
+	if (!foglayer)
+	{
+		vbo->r = 255;
+		vbo->g = 255;
+		vbo->b = 255;
+		vbo->a = (r == 0 ? 0 : 255);
+
+		// And the texture coordinates.
+		vbo->u = (-timesRepeat * c / (float)sky->columns);
+		if (!yflip)	// Flipped Y is for the lower hemisphere.
+			vbo->v = (r / (float)sky->rows) + 0.5f;
+		else
+			vbo->v = 1.0f + ((sky->rows - r) / (float)sky->rows) + 0.5f;
+	}
+
+	if (r != 4)
+		y += 300.0f;
+
+	// And finally the vertex.
+	vbo->x = x;
+	vbo->y = y + delta;
+	vbo->z = z;
+}
+
+// Clears the sky dome.
+void HWR_ClearSkyDome(void)
+{
+	gl_sky_t *sky = &gl_sky;
+
+	if (sky->loops)
+		free(sky->loops);
+	if (sky->data)
+		free(sky->data);
+
+	sky->loops = NULL;
+	sky->data = NULL;
+
+	sky->vbo = 0;
+	sky->rows = sky->columns = 0;
+	sky->loopcount = 0;
+
+	sky->detail = 0;
+	sky->texture = -1;
+	sky->width = sky->height = 0;
+
+	sky->rebuild = true;
+}
+
+void HWR_BuildSkyDome(void)
+{
+	int c, r;
+	signed char yflip;
+	int row_count = 4;
+	int col_count = 4;
+	float delta;
+
+	gl_sky_t *sky = &gl_sky;
+	gl_skyvertex_t *vertex_p;
+	texture_t *texture = textures[texturetranslation[skytexture]];
+
+	sky->detail = 16;
+	col_count *= sky->detail;
+
+	if ((sky->columns != col_count) || (sky->rows != row_count))
+		HWR_ClearSkyDome();
+
+	sky->columns = col_count;
+	sky->rows = row_count;
+	sky->vertex_count = 2 * sky->rows * (sky->columns * 2 + 2) + sky->columns * 2;
+
+	if (!sky->loops)
+		sky->loops = malloc((sky->rows * 2 + 2) * sizeof(sky->loops[0]));
+
+	// create vertex array
+	if (!sky->data)
+		sky->data = malloc(sky->vertex_count * sizeof(sky->data[0]));
+
+	sky->texture = texturetranslation[skytexture];
+	sky->width = texture->width;
+	sky->height = texture->height;
+
+	vertex_p = &sky->data[0];
+	sky->loopcount = 0;
+
+	for (yflip = 0; yflip < 2; yflip++)
+	{
+		sky->loops[sky->loopcount].mode = HWD_SKYLOOP_FAN;
+		sky->loops[sky->loopcount].vertexindex = vertex_p - &sky->data[0];
+		sky->loops[sky->loopcount].vertexcount = col_count;
+		sky->loops[sky->loopcount].use_texture = false;
+		sky->loopcount++;
+
+		delta = 0.0f;
+
+		for (c = 0; c < col_count; c++)
+		{
+			HWR_SkyDomeVertex(sky, vertex_p, 1, c, yflip, 0.0f, true);
+			vertex_p->r = 255;
+			vertex_p->g = 255;
+			vertex_p->b = 255;
+			vertex_p->a = 255;
+			vertex_p++;
+		}
+
+		delta = (yflip ? 5.0f : -5.0f) / 128.0f;
+
+		for (r = 0; r < row_count; r++)
+		{
+			sky->loops[sky->loopcount].mode = HWD_SKYLOOP_STRIP;
+			sky->loops[sky->loopcount].vertexindex = vertex_p - &sky->data[0];
+			sky->loops[sky->loopcount].vertexcount = 2 * col_count + 2;
+			sky->loops[sky->loopcount].use_texture = true;
+			sky->loopcount++;
+
+			for (c = 0; c <= col_count; c++)
+			{
+				HWR_SkyDomeVertex(sky, vertex_p++, r + (yflip ? 1 : 0), (c ? c : 0), yflip, delta, false);
+				HWR_SkyDomeVertex(sky, vertex_p++, r + (yflip ? 0 : 1), (c ? c : 0), yflip, delta, false);
+			}
+		}
+	}
+}
+
 static void HWR_DrawSkyBackground(player_t *player)
 {
-	if (cv_grskydome.value)
+	HWD.pfnSetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated);
+
+	if (cv_glskydome.value)
 	{
 		FTransform dometransform;
 		const float fpov = FIXED_TO_FLOAT(cv_fov.value+player->fovadd);
@@ -5039,7 +5581,7 @@ static void HWR_DrawSkyBackground(player_t *player)
 		memset(&dometransform, 0x00, sizeof(FTransform));
 
 		//04/01/2000: Hurdler: added for T&L
-		//                     It should replace all other gr_viewxxx when finished
+		//                     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);
 
@@ -5062,7 +5604,16 @@ static void HWR_DrawSkyBackground(player_t *player)
 		dometransform.splitscreen = splitscreen;
 
 		HWR_GetTexture(texturetranslation[skytexture]);
-		HWD.pfnRenderSkyDome(skytexture, textures[skytexture]->width, textures[skytexture]->height, dometransform);
+
+		if (gl_sky.texture != texturetranslation[skytexture])
+		{
+			HWR_ClearSkyDome();
+			HWR_BuildSkyDome();
+		}
+
+		HWD.pfnSetShader(SHADER_SKY); // sky shader
+		HWD.pfnSetTransform(&dometransform);
+		HWD.pfnRenderSkyDome(&gl_sky);
 	}
 	else
 	{
@@ -5097,7 +5648,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 + gr_xtoviewangle[0]);
+		angle = (dup_viewangle + gl_xtoviewangle[0]);
 
 		dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->width/256.0f);
 
@@ -5143,10 +5694,11 @@ static void HWR_DrawSkyBackground(player_t *player)
 			v[0].t = v[1].t -= ((float) angle / angleturn);
 		}
 
-		HWD.pfnSetShader(7); // sky shader
+		HWD.pfnUnSetShader();
 		HWD.pfnDrawPolygon(NULL, v, 4, 0);
-		HWD.pfnSetShader(0);
 	}
+
+	HWD.pfnSetShader(SHADER_DEFAULT);
 }
 
 
@@ -5162,10 +5714,10 @@ static inline void HWR_ClearView(void)
 
 	/// \bug faB - enable depth mask, disable color mask
 
-	HWD.pfnGClipRect((INT32)gr_viewwindowx,
-	                 (INT32)gr_viewwindowy,
-	                 (INT32)(gr_viewwindowx + gr_viewwidth),
-	                 (INT32)(gr_viewwindowy + gr_viewheight),
+	HWD.pfnGClipRect((INT32)gl_viewwindowx,
+	                 (INT32)gl_viewwindowy,
+	                 (INT32)(gl_viewwindowx + gl_viewwidth),
+	                 (INT32)(gl_viewwindowy + gl_viewheight),
 	                 ZCLIP_PLANE);
 	HWD.pfnClearBuffer(false, true, 0);
 
@@ -5181,30 +5733,30 @@ static inline void HWR_ClearView(void)
 void HWR_SetViewSize(void)
 {
 	// setup view size
-	gr_viewwidth = (float)vid.width;
-	gr_viewheight = (float)vid.height;
+	gl_viewwidth = (float)vid.width;
+	gl_viewheight = (float)vid.height;
 
 	if (splitscreen)
-		gr_viewheight /= 2;
+		gl_viewheight /= 2;
 
-	gr_centerx = gr_viewwidth / 2;
-	gr_basecentery = gr_viewheight / 2; //note: this is (gr_centerx * gr_viewheight / gr_viewwidth)
+	gl_centerx = gl_viewwidth / 2;
+	gl_basecentery = gl_viewheight / 2; //note: this is (gl_centerx * gl_viewheight / gl_viewwidth)
 
-	gr_viewwindowx = (vid.width - gr_viewwidth) / 2;
-	gr_windowcenterx = (float)(vid.width / 2);
-	if (fabsf(gr_viewwidth - vid.width) < 1.0E-36f)
+	gl_viewwindowx = (vid.width - gl_viewwidth) / 2;
+	gl_windowcenterx = (float)(vid.width / 2);
+	if (fabsf(gl_viewwidth - vid.width) < 1.0E-36f)
 	{
-		gr_baseviewwindowy = 0;
-		gr_basewindowcentery = gr_viewheight / 2;               // window top left corner at 0,0
+		gl_baseviewwindowy = 0;
+		gl_basewindowcentery = gl_viewheight / 2;               // window top left corner at 0,0
 	}
 	else
 	{
-		gr_baseviewwindowy = (vid.height-gr_viewheight) / 2;
-		gr_basewindowcentery = (float)(vid.height / 2);
+		gl_baseviewwindowy = (vid.height-gl_viewheight) / 2;
+		gl_basewindowcentery = (float)(vid.height / 2);
 	}
 
-	gr_pspritexscale = gr_viewwidth / BASEVIDWIDTH;
-	gr_pspriteyscale = ((vid.height*gr_pspritexscale*BASEVIDWIDTH)/BASEVIDHEIGHT)/vid.width;
+	gl_pspritexscale = gl_viewwidth / BASEVIDWIDTH;
+	gl_pspriteyscale = ((vid.height*gl_pspritexscale*BASEVIDWIDTH)/BASEVIDHEIGHT)/vid.width;
 
 	HWD.pfnFlushScreenTextures();
 }
@@ -5215,20 +5767,34 @@ static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean
 {
 	// 1 = always on
 	// 2 = chasecam only
-	if (cv_grshearing.value == 1 || (cv_grshearing.value == 2 && R_IsViewpointThirdPerson(player, skybox)))
+	if (cv_glshearing.value == 1 || (cv_glshearing.value == 2 && R_IsViewpointThirdPerson(player, skybox)))
 	{
 		fixed_t fixedaiming = AIMINGTODY(aimingangle);
 		trans->viewaiming = FIXED_TO_FLOAT(fixedaiming);
 		trans->shearing = true;
-		gr_aimingangle = 0;
+		gl_aimingangle = 0;
 	}
 	else
 	{
 		trans->shearing = false;
-		gr_aimingangle = aimingangle;
+		gl_aimingangle = aimingangle;
 	}
 
-	trans->anglex = (float)(gr_aimingangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
+	trans->anglex = (float)(gl_aimingangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
+}
+
+//
+// Sets the shader state.
+//
+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);
 }
 
 // ==========================================================================
@@ -5265,42 +5831,42 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	dup_viewangle = viewangle;
 
 	// set window position
-	gr_centery = gr_basecentery;
-	gr_viewwindowy = gr_baseviewwindowy;
-	gr_windowcentery = gr_basewindowcentery;
+	gl_centery = gl_basecentery;
+	gl_viewwindowy = gl_baseviewwindowy;
+	gl_windowcentery = gl_basewindowcentery;
 	if (splitscreen && viewnumber == 1)
 	{
-		gr_viewwindowy += (vid.height/2);
-		gr_windowcentery += (vid.height/2);
+		gl_viewwindowy += (vid.height/2);
+		gl_windowcentery += (vid.height/2);
 	}
 
 	// check for new console commands.
 	NetUpdate();
 
-	gr_viewx = FIXED_TO_FLOAT(dup_viewx);
-	gr_viewy = FIXED_TO_FLOAT(dup_viewy);
-	gr_viewz = FIXED_TO_FLOAT(dup_viewz);
-	gr_viewsin = FIXED_TO_FLOAT(viewsin);
-	gr_viewcos = FIXED_TO_FLOAT(viewcos);
+	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 gr_viewxxx when finished
+	//                     It should replace all other gl_viewxxx when finished
 	memset(&atransform, 0x00, sizeof(FTransform));
 
 	HWR_SetTransformAiming(&atransform, player, true);
 	atransform.angley = (float)(viewangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 
-	gr_viewludsin = FIXED_TO_FLOAT(FINECOSINE(gr_aimingangle>>ANGLETOFINESHIFT));
-	gr_viewludcos = FIXED_TO_FLOAT(-FINESINE(gr_aimingangle>>ANGLETOFINESHIFT));
+	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      = gr_viewx;  // FIXED_TO_FLOAT(viewx)
-	atransform.y      = gr_viewy;  // FIXED_TO_FLOAT(viewy)
-	atransform.z      = gr_viewz;  // FIXED_TO_FLOAT(viewz)
+	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;
@@ -5315,7 +5881,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	}
 	atransform.splitscreen = splitscreen;
 
-	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
+	gl_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
 
 	//------------------------------------------------------------------------
 	HWR_ClearView();
@@ -5333,7 +5899,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 #ifdef NEWCLIP
 	if (rendermode == render_opengl)
 	{
-		angle_t a1 = gld_FrustumAngle(gr_aimingangle);
+		angle_t a1 = gld_FrustumAngle(gl_aimingangle);
 		gld_clipper_Clear();
 		gld_clipper_SafeAddClipRange(viewangle + a1, viewangle - a1);
 #ifdef HAVE_SPHEREFRUSTRUM
@@ -5349,12 +5915,11 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	HWD.pfnSetTransform(&atransform);
 
 	// Reset the shader state.
-	HWD.pfnSetSpecialState(HWD_SET_SHADERS, cv_grshaders.value);
-	HWD.pfnSetShader(0);
+	HWR_SetShaderState();
 
 	validcount++;
 
-	if (cv_grbatching.value)
+	if (cv_glbatching.value)
 		HWR_StartBatching();
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
@@ -5367,14 +5932,14 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 		viewangle = localaiming2;
 
 	// Handle stuff when you are looking farther up or down.
-	if ((gr_aimingangle || cv_fov.value+player->fovadd > 90*FRACUNIT))
+	if ((gl_aimingangle || cv_fov.value+player->fovadd > 90*FRACUNIT))
 	{
 		dup_viewangle += ANGLE_90;
 		HWR_ClearClipSegs();
 		HWR_RenderBSPNode((INT32)numnodes-1); //left
 
 		dup_viewangle += ANGLE_90;
-		if (((INT32)gr_aimingangle > ANGLE_45 || (INT32)gr_aimingangle<-ANGLE_45))
+		if (((INT32)gl_aimingangle > ANGLE_45 || (INT32)gl_aimingangle<-ANGLE_45))
 		{
 			HWR_ClearClipSegs();
 			HWR_RenderBSPNode((INT32)numnodes-1); //back
@@ -5388,7 +5953,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	}
 #endif
 
-	if (cv_grbatching.value)
+	if (cv_glbatching.value)
 		HWR_RenderBatches();
 
 	// Check for new console commands.
@@ -5447,14 +6012,16 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	ClearColor.blue = 0.0f;
 	ClearColor.alpha = 1.0f;
 
-	if (cv_grshaders.value)
+	if (cv_glshaders.value)
 		HWD.pfnSetShaderInfo(HWD_SHADERINFO_LEVELTIME, (INT32)leveltime); // The water surface shader needs the leveltime.
 
 	if (viewnumber == 0) // Only do it if it's the first screen being rendered
 		HWD.pfnClearBuffer(true, false, &ClearColor); // Clear the Color Buffer, stops HOMs. Also seems to fix the skybox issue on Intel GPUs.
 
+	ps_hw_skyboxtime = I_GetPreciseTime();
 	if (skybox && drawsky) // If there's a skybox and we should be drawing the sky, draw the skybox
 		HWR_RenderSkyboxView(viewnumber, player); // This is drawn before everything else so it is placed behind
+	ps_hw_skyboxtime = I_GetPreciseTime() - ps_hw_skyboxtime;
 
 	{
 		// do we really need to save player (is it not the same)?
@@ -5478,42 +6045,42 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	dup_viewangle = viewangle;
 
 	// set window position
-	gr_centery = gr_basecentery;
-	gr_viewwindowy = gr_baseviewwindowy;
-	gr_windowcentery = gr_basewindowcentery;
+	gl_centery = gl_basecentery;
+	gl_viewwindowy = gl_baseviewwindowy;
+	gl_windowcentery = gl_basewindowcentery;
 	if (splitscreen && viewnumber == 1)
 	{
-		gr_viewwindowy += (vid.height/2);
-		gr_windowcentery += (vid.height/2);
+		gl_viewwindowy += (vid.height/2);
+		gl_windowcentery += (vid.height/2);
 	}
 
 	// check for new console commands.
 	NetUpdate();
 
-	gr_viewx = FIXED_TO_FLOAT(dup_viewx);
-	gr_viewy = FIXED_TO_FLOAT(dup_viewy);
-	gr_viewz = FIXED_TO_FLOAT(dup_viewz);
-	gr_viewsin = FIXED_TO_FLOAT(viewsin);
-	gr_viewcos = FIXED_TO_FLOAT(viewcos);
+	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 gr_viewxxx when finished
+	//                     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);
 
-	gr_viewludsin = FIXED_TO_FLOAT(FINECOSINE(gr_aimingangle>>ANGLETOFINESHIFT));
-	gr_viewludcos = FIXED_TO_FLOAT(-FINESINE(gr_aimingangle>>ANGLETOFINESHIFT));
+	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      = gr_viewx;  // FIXED_TO_FLOAT(viewx)
-	atransform.y      = gr_viewy;  // FIXED_TO_FLOAT(viewy)
-	atransform.z      = gr_viewz;  // FIXED_TO_FLOAT(viewz)
+	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;
@@ -5528,7 +6095,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	}
 	atransform.splitscreen = splitscreen;
 
-	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
+	gl_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
 
 	//------------------------------------------------------------------------
 	HWR_ClearView(); // Clears the depth buffer and resets the view I believe
@@ -5546,7 +6113,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 #ifdef NEWCLIP
 	if (rendermode == render_opengl)
 	{
-		angle_t a1 = gld_FrustumAngle(gr_aimingangle);
+		angle_t a1 = gld_FrustumAngle(gl_aimingangle);
 		gld_clipper_Clear();
 		gld_clipper_SafeAddClipRange(viewangle + a1, viewangle - a1);
 #ifdef HAVE_SPHEREFRUSTRUM
@@ -5562,16 +6129,15 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	HWD.pfnSetTransform(&atransform);
 
 	// Reset the shader state.
-	HWD.pfnSetSpecialState(HWD_SET_SHADERS, cv_grshaders.value);
-	HWD.pfnSetShader(0);
+	HWR_SetShaderState();
 
-	rs_numbspcalls = 0;
-	rs_numpolyobjects = 0;
-	rs_bsptime = I_GetTimeMicros();
+	ps_numbspcalls = 0;
+	ps_numpolyobjects = 0;
+	ps_bsptime = I_GetPreciseTime();
 
 	validcount++;
 
-	if (cv_grbatching.value)
+	if (cv_glbatching.value)
 		HWR_StartBatching();
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
@@ -5584,14 +6150,14 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 		viewangle = localaiming2;
 
 	// Handle stuff when you are looking farther up or down.
-	if ((gr_aimingangle || cv_fov.value+player->fovadd > 90*FRACUNIT))
+	if ((gl_aimingangle || cv_fov.value+player->fovadd > 90*FRACUNIT))
 	{
 		dup_viewangle += ANGLE_90;
 		HWR_ClearClipSegs();
 		HWR_RenderBSPNode((INT32)numnodes-1); //left
 
 		dup_viewangle += ANGLE_90;
-		if (((INT32)gr_aimingangle > ANGLE_45 || (INT32)gr_aimingangle<-ANGLE_45))
+		if (((INT32)gl_aimingangle > ANGLE_45 || (INT32)gl_aimingangle<-ANGLE_45))
 		{
 			HWR_ClearClipSegs();
 			HWR_RenderBSPNode((INT32)numnodes-1); //back
@@ -5605,9 +6171,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	}
 #endif
 
-	rs_bsptime = I_GetTimeMicros() - rs_bsptime;
+	ps_bsptime = I_GetPreciseTime() - ps_bsptime;
 
-	if (cv_grbatching.value)
+	if (cv_glbatching.value)
 		HWR_RenderBatches();
 
 	// Check for new console commands.
@@ -5620,22 +6186,22 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 #endif
 
 	// Draw MD2 and sprites
-	rs_numsprites = gr_visspritecount;
-	rs_hw_spritesorttime = I_GetTimeMicros();
+	ps_numsprites = gl_visspritecount;
+	ps_hw_spritesorttime = I_GetPreciseTime();
 	HWR_SortVisSprites();
-	rs_hw_spritesorttime = I_GetTimeMicros() - rs_hw_spritesorttime;
-	rs_hw_spritedrawtime = I_GetTimeMicros();
+	ps_hw_spritesorttime = I_GetPreciseTime() - ps_hw_spritesorttime;
+	ps_hw_spritedrawtime = I_GetPreciseTime();
 	HWR_DrawSprites();
-	rs_hw_spritedrawtime = I_GetTimeMicros() - rs_hw_spritedrawtime;
+	ps_hw_spritedrawtime = I_GetPreciseTime() - ps_hw_spritedrawtime;
 
 #ifdef NEWCORONAS
 	//Hurdler: they must be drawn before translucent planes, what about gl fog?
 	HWR_DrawCoronas();
 #endif
 
-	rs_numdrawnodes = 0;
-	rs_hw_nodesorttime = 0;
-	rs_hw_nodedrawtime = 0;
+	ps_numdrawnodes = 0;
+	ps_hw_nodesorttime = 0;
+	ps_hw_nodedrawtime = 0;
 	if (numplanes || numpolyplanes || numwalls) //Hurdler: render 3D water and transparent walls after everything
 	{
 		HWR_CreateDrawNodes();
@@ -5654,64 +6220,80 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
 }
 
+void HWR_LoadLevel(void)
+{
+#ifdef ALAM_LIGHTING
+	// BP: reset light between levels (we draw preview frame lights on current frame)
+	HWR_ResetLights();
+#endif
+
+	HWR_CreatePlanePolygons((INT32)numnodes - 1);
+
+	// Build the sky dome
+	HWR_ClearSkyDome();
+	HWR_BuildSkyDome();
+
+	gl_maploaded = true;
+}
+
 // ==========================================================================
 //                                                         3D ENGINE COMMANDS
 // ==========================================================================
 
-static CV_PossibleValue_t grmodelinterpolation_cons_t[] = {{0, "Off"}, {1, "Sometimes"}, {2, "Always"}, {0, NULL}};
-static CV_PossibleValue_t grfakecontrast_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Smooth"}, {0, NULL}};
-static CV_PossibleValue_t grshearing_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Third-person"}, {0, NULL}};
+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 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_grfiltermode_OnChange(void);
-static void CV_granisotropic_OnChange(void);
+static void CV_glfiltermode_OnChange(void);
+static void CV_glanisotropic_OnChange(void);
 
-static CV_PossibleValue_t grfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSAMPLED, "Nearest"},
+static CV_PossibleValue_t glfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSAMPLED, "Nearest"},
 	{HWD_SET_TEXTUREFILTER_BILINEAR, "Bilinear"}, {HWD_SET_TEXTUREFILTER_TRILINEAR, "Trilinear"},
 	{HWD_SET_TEXTUREFILTER_MIXED1, "Linear_Nearest"},
 	{HWD_SET_TEXTUREFILTER_MIXED2, "Nearest_Linear"},
 	{HWD_SET_TEXTUREFILTER_MIXED3, "Nearest_Mipmap"},
 	{0, NULL}};
-CV_PossibleValue_t granisotropicmode_cons_t[] = {{1, "MIN"}, {16, "MAX"}, {0, NULL}};
+CV_PossibleValue_t glanisotropicmode_cons_t[] = {{1, "MIN"}, {16, "MAX"}, {0, NULL}};
 
-consvar_t cv_grshaders = {"gr_shaders", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_fovchange = {"gr_fovchange", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_glshaders = CVAR_INIT ("gr_shaders", "On", CV_SAVE, glshaders_cons_t, NULL);
+consvar_t cv_glallowshaders = CVAR_INIT ("gr_allowclientshaders", "On", CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_fovchange = CVAR_INIT ("gr_fovchange", "Off", CV_SAVE, CV_OnOff, NULL);
 
 #ifdef ALAM_LIGHTING
-consvar_t cv_grdynamiclighting = {"gr_dynamiclighting", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grstaticlighting  = {"gr_staticlighting", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grcoronas = {"gr_coronas", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grcoronasize = {"gr_coronasize", "1", CV_SAVE|CV_FLOAT, 0, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_gldynamiclighting = CVAR_INIT ("gr_dynamiclighting", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_glstaticlighting  = CVAR_INIT ("gr_staticlighting", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_glcoronas = CVAR_INIT ("gr_coronas", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_glcoronasize = CVAR_INIT ("gr_coronasize", "1", CV_SAVE|CV_FLOAT, 0, NULL);
 #endif
 
-consvar_t cv_grmodels = {"gr_models", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grmodelinterpolation = {"gr_modelinterpolation", "Sometimes", CV_SAVE, grmodelinterpolation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grmodellighting = {"gr_modellighting", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+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_grshearing = {"gr_shearing", "Off", CV_SAVE, grshearing_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grspritebillboarding = {"gr_spritebillboarding", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grskydome = {"gr_skydome", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grfakecontrast = {"gr_fakecontrast", "Smooth", CV_SAVE, grfakecontrast_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grslopecontrast = {"gr_slopecontrast", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+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);
+consvar_t cv_glskydome = CVAR_INIT ("gr_skydome", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_glfakecontrast = CVAR_INIT ("gr_fakecontrast", "Smooth", CV_SAVE, glfakecontrast_cons_t, NULL);
+consvar_t cv_glslopecontrast = CVAR_INIT ("gr_slopecontrast", "Off", CV_SAVE, CV_OnOff, NULL);
 
-consvar_t cv_grfiltermode = {"gr_filtermode", "Nearest", CV_SAVE|CV_CALL, grfiltermode_cons_t,
-                             CV_grfiltermode_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_granisotropicmode = {"gr_anisotropicmode", "1", CV_CALL, granisotropicmode_cons_t,
-                             CV_granisotropic_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_glfiltermode = CVAR_INIT ("gr_filtermode", "Nearest", CV_SAVE|CV_CALL, glfiltermode_cons_t, CV_glfiltermode_OnChange);
+consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_CALL, glanisotropicmode_cons_t, CV_glanisotropic_OnChange);
 
-consvar_t cv_grsolvetjoin = {"gr_solvetjoin", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL);
 
-consvar_t cv_grbatching = {"gr_batching", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", 0, CV_OnOff, NULL);
 
-static void CV_grfiltermode_OnChange(void)
+static void CV_glfiltermode_OnChange(void)
 {
 	if (rendermode == render_opengl)
-		HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_grfiltermode.value);
+		HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_glfiltermode.value);
 }
 
-static void CV_granisotropic_OnChange(void)
+static void CV_glanisotropic_OnChange(void)
 {
 	if (rendermode == render_opengl)
-		HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_granisotropicmode.value);
+		HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_glanisotropicmode.value);
 }
 
 //added by Hurdler: console varibale that are saved
@@ -5720,42 +6302,39 @@ void HWR_AddCommands(void)
 	CV_RegisterVar(&cv_fovchange);
 
 #ifdef ALAM_LIGHTING
-	CV_RegisterVar(&cv_grstaticlighting);
-	CV_RegisterVar(&cv_grdynamiclighting);
-	CV_RegisterVar(&cv_grcoronasize);
-	CV_RegisterVar(&cv_grcoronas);
+	CV_RegisterVar(&cv_glstaticlighting);
+	CV_RegisterVar(&cv_gldynamiclighting);
+	CV_RegisterVar(&cv_glcoronasize);
+	CV_RegisterVar(&cv_glcoronas);
 #endif
 
-	CV_RegisterVar(&cv_grmodellighting);
-	CV_RegisterVar(&cv_grmodelinterpolation);
-	CV_RegisterVar(&cv_grmodels);
+	CV_RegisterVar(&cv_glmodellighting);
+	CV_RegisterVar(&cv_glmodelinterpolation);
+	CV_RegisterVar(&cv_glmodels);
 
-	CV_RegisterVar(&cv_grskydome);
-	CV_RegisterVar(&cv_grspritebillboarding);
-	CV_RegisterVar(&cv_grfakecontrast);
-	CV_RegisterVar(&cv_grshearing);
-	CV_RegisterVar(&cv_grshaders);
+	CV_RegisterVar(&cv_glskydome);
+	CV_RegisterVar(&cv_glspritebillboarding);
+	CV_RegisterVar(&cv_glfakecontrast);
+	CV_RegisterVar(&cv_glshearing);
+	CV_RegisterVar(&cv_glshaders);
+	CV_RegisterVar(&cv_glallowshaders);
 
-	CV_RegisterVar(&cv_grfiltermode);
-	CV_RegisterVar(&cv_grsolvetjoin);
+	CV_RegisterVar(&cv_glfiltermode);
+	CV_RegisterVar(&cv_glsolvetjoin);
 
-	CV_RegisterVar(&cv_renderstats);
-	CV_RegisterVar(&cv_grbatching);
+	CV_RegisterVar(&cv_glbatching);
 
 #ifndef NEWCLIP
-	CV_RegisterVar(&cv_grclipwalls);
+	CV_RegisterVar(&cv_glclipwalls);
 #endif
 }
 
 void HWR_AddSessionCommands(void)
 {
-	static boolean alreadycalled = false;
-	if (alreadycalled)
+	if (gl_sessioncommandsadded)
 		return;
-
-	CV_RegisterVar(&cv_granisotropicmode);
-
-	alreadycalled = true;
+	CV_RegisterVar(&cv_glanisotropicmode);
+	gl_sessioncommandsadded = true;
 }
 
 // --------------------------------------------------------------------------
@@ -5763,33 +6342,27 @@ void HWR_AddSessionCommands(void)
 // --------------------------------------------------------------------------
 void HWR_Startup(void)
 {
-	static boolean startupdone = false;
-
-	// do this once
-	if (!startupdone)
+	if (!gl_init)
 	{
-		INT32 i;
 		CONS_Printf("HWR_Startup()...\n");
 
 		HWR_InitPolyPool();
 		HWR_AddSessionCommands();
-		HWR_InitTextureCache();
+		HWR_InitMapTextures();
 		HWR_InitModels();
 #ifdef ALAM_LIGHTING
 		HWR_InitLight();
 #endif
 
-		// read every custom shader
-		for (i = 0; i < numwadfiles; i++)
-			HWR_ReadShaders(i, (wadfiles[i]->type == RET_PK3));
-		if (!HWR_LoadShaders())
-			gr_shadersavailable = false;
+		HWR_LoadAllCustomShaders();
+		if (!HWR_CompileShaders())
+			gl_shadersavailable = false;
 	}
 
 	if (rendermode == render_opengl)
-		textureformat = patchformat = GR_RGBA;
+		textureformat = patchformat = GL_TEXFMT_RGBA;
 
-	startupdone = true;
+	gl_init = true;
 }
 
 // --------------------------------------------------------------------------
@@ -5797,9 +6370,24 @@ void HWR_Startup(void)
 // --------------------------------------------------------------------------
 void HWR_Switch(void)
 {
+	// Add session commands
+	if (!gl_sessioncommandsadded)
+		HWR_AddSessionCommands();
+
 	// Set special states from CVARs
-	HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_grfiltermode.value);
-	HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_granisotropicmode.value);
+	HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_glfiltermode.value);
+	HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_glanisotropicmode.value);
+
+	// Load textures
+	if (!gl_maptexturesloaded)
+		HWR_LoadMapTextures(numtextures);
+
+	// Create plane polygons
+	if (!gl_maploaded && (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
+	{
+		HWR_ClearAllTextures();
+		HWR_LoadLevel();
+	}
 }
 
 // --------------------------------------------------------------------------
@@ -5810,7 +6398,7 @@ void HWR_Shutdown(void)
 	CONS_Printf("HWR_Shutdown()\n");
 	HWR_FreeExtraSubsectors();
 	HWR_FreePolyPool();
-	HWR_FreeTextureCache();
+	HWR_FreeMapTextures();
 	HWD.pfnFlushScreenTextures();
 }
 
@@ -5818,23 +6406,23 @@ void transform(float *cx, float *cy, float *cz)
 {
 	float tr_x,tr_y;
 	// translation
-	tr_x = *cx - gr_viewx;
-	tr_y = *cz - gr_viewy;
+	tr_x = *cx - gl_viewx;
+	tr_y = *cz - gl_viewy;
 //	*cy = *cy;
 
 	// rotation around vertical y axis
-	*cx = (tr_x * gr_viewsin) - (tr_y * gr_viewcos);
-	tr_x = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
+	*cx = (tr_x * gl_viewsin) - (tr_y * gl_viewcos);
+	tr_x = (tr_x * gl_viewcos) + (tr_y * gl_viewsin);
 
 	//look up/down ----TOTAL SUCKS!!!--- do the 2 in one!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-	tr_y = *cy - gr_viewz;
+	tr_y = *cy - gl_viewz;
 
-	*cy = (tr_x * gr_viewludcos) + (tr_y * gr_viewludsin);
-	*cz = (tr_x * gr_viewludsin) - (tr_y * gr_viewludcos);
+	*cy = (tr_x * gl_viewludcos) + (tr_y * gl_viewludsin);
+	*cz = (tr_x * gl_viewludsin) - (tr_y * gl_viewludcos);
 
 	//scale y before frustum so that frustum can be scaled to screen height
-	*cy *= ORIGINAL_ASPECT * gr_fovlud;
-	*cx *= gr_fovlud;
+	*cy *= ORIGINAL_ASPECT * gl_fovlud;
+	*cx *= gl_fovlud;
 }
 
 void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, INT32 texnum, FBITFIELD blend, boolean fogwall, INT32 lightlevel, extracolormap_t *wallcolormap)
@@ -5874,7 +6462,7 @@ void HWR_RenderWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blend,
 
 	pSurf->PolyColor.s.alpha = alpha; // put the alpha back after lighting
 
-	shader = 2;	// wall shader
+	shader = SHADER_WALL;	// wall shader
 
 	if (blend & PF_Environment)
 		blendmode |= PF_Occlude;	// PF_Occlude must be used for solid objects
@@ -5882,17 +6470,11 @@ void HWR_RenderWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blend,
 	if (fogwall)
 	{
 		blendmode |= PF_Fog;
-		shader = 6;	// fog shader
+		shader = SHADER_FOG;	// fog shader
 	}
 
 	blendmode |= PF_Modulated;	// No PF_Occlude means overlapping (incorrect) transparency
-
 	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode, shader, false);
-
-#ifdef WALLSPLATS
-	if (gr_curline->linedef->splats && cv_splats.value)
-		HWR_DrawSegsSplats(pSurf);
-#endif
 }
 
 INT32 HWR_GetTextureUsed(void)
@@ -5933,7 +6515,7 @@ void HWR_DoPostProcessor(player_t *player)
 
 		Surf.PolyColor.s.alpha = 0xc0; // match software mode
 
-		HWD.pfnDrawPolygon(&Surf, v, 4, PF_Modulated|PF_Additive|PF_NoTexture|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+		HWD.pfnDrawPolygon(&Surf, v, 4, PF_Modulated|PF_AdditiveSource|PF_NoTexture|PF_NoDepthTest);
 	}
 
 	// Capture the screen for intermission and screen waving
@@ -6067,13 +6649,7 @@ void HWR_DrawScreenFinalTexture(int width, int height)
 }
 
 // jimita 18032019
-typedef struct
-{
-	char type[16];
-	INT32 id;
-} shaderxlat_t;
-
-static inline UINT16 HWR_CheckShader(UINT16 wadnum)
+static inline UINT16 HWR_FindShaderDefs(UINT16 wadnum)
 {
 	UINT16 i;
 	lumpinfo_t *lump_p;
@@ -6086,12 +6662,34 @@ static inline UINT16 HWR_CheckShader(UINT16 wadnum)
 	return INT16_MAX;
 }
 
-boolean HWR_LoadShaders(void)
+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)
 {
-	return HWD.pfnInitCustomShaders();
+	INT32 i;
+
+	// read every custom shader
+	for (i = 0; i < numwadfiles; i++)
+		HWR_LoadCustomShadersFromFile(i, (wadfiles[i]->type == RET_PK3));
 }
 
-void HWR_ReadShaders(UINT16 wadnum, boolean PK3)
+void HWR_LoadCustomShadersFromFile(UINT16 wadnum, boolean PK3)
 {
 	UINT16 lump;
 	char *shaderdef, *line;
@@ -6102,19 +6700,7 @@ void HWR_ReadShaders(UINT16 wadnum, boolean PK3)
 	int shadertype = 0;
 	int i;
 
-	#define SHADER_TYPES 7
-	shaderxlat_t shaderxlat[SHADER_TYPES] =
-	{
-		{"Flat", 1},
-		{"WallTexture", 2},
-		{"Sprite", 3},
-		{"Model", 4},
-		{"WaterRipple", 5},
-		{"Fog", 6},
-		{"Sky", 7},
-	};
-
-	lump = HWR_CheckShader(wadnum);
+	lump = HWR_FindShaderDefs(wadnum);
 	if (lump == INT16_MAX)
 		return;
 
@@ -6140,7 +6726,7 @@ void HWR_ReadShaders(UINT16 wadnum, boolean PK3)
 			value = strtok(NULL, "\r\n ");
 			if (!value)
 			{
-				CONS_Alert(CONS_WARNING, "HWR_ReadShaders: Missing shader type (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
+				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;
 			}
@@ -6159,19 +6745,19 @@ skip_lump:
 			value = strtok(NULL, "\r\n= ");
 			if (!value)
 			{
-				CONS_Alert(CONS_WARNING, "HWR_ReadShaders: Missing shader target (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
+				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_ReadShaders: Missing shader type (file %s, line %d)\n", wadfiles[wadnum]->filename, linenum);
+				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; i < SHADER_TYPES; i++)
+			for (i = 0; shaderxlat[i].type; i++)
 			{
 				if (!stricmp(shaderxlat[i].type, stoken))
 				{
@@ -6197,7 +6783,7 @@ skip_lump:
 
 					if (shader_lumpnum == INT16_MAX)
 					{
-						CONS_Alert(CONS_ERROR, "HWR_ReadShaders: Missing shader source %s (file %s, line %d)\n", shader_lumpname, wadfiles[wadnum]->filename, linenum);
+						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;
 					}
@@ -6223,4 +6809,22 @@ skip_field:
 	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";
+}
+
 #endif // HWRENDER
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 7336bba0f524a41aaee196ab57e8834fe254c6a1..4ad09aa3d63b1612239243ca15a0a2d878ea7c8d 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -31,17 +31,18 @@ void HWR_DrawConsoleBack(UINT32 color, INT32 height);
 void HWR_DrawTutorialBack(UINT32 color, INT32 boxheight);
 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(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
-void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap);
-void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
+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 scale, INT32 option, 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);
 void HWR_CreatePlanePolygons(INT32 bspnum);
 void HWR_CreateStaticLightmaps(INT32 bspnum);
-void HWR_LoadTextures(size_t pnumtextures);
 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.
@@ -53,7 +54,6 @@ boolean HWR_Screenshot(const char *pathname);
 void HWR_AddCommands(void);
 void HWR_AddSessionCommands(void);
 void transform(float *cx, float *cy, float *cz);
-FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf);
 INT32 HWR_GetTextureUsed(void);
 void HWR_DoPostProcessor(player_t *player);
 void HWR_StartScreenWipe(void);
@@ -64,41 +64,51 @@ void HWR_DoTintedWipe(UINT8 wipenum, UINT8 scrnnum);
 void HWR_MakeScreenFinalTexture(void);
 void HWR_DrawScreenFinalTexture(int width, int height);
 
-// This stuff is put here so MD2's can use them
+// This stuff is put here so models can use them
 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
 
-void HWR_ReadShaders(UINT16 wadnum, boolean PK3);
-boolean HWR_LoadShaders(void);
+UINT8 HWR_GetTranstableAlpha(INT32 transtablenum);
+FBITFIELD HWR_GetBlendModeFlag(INT32 ast);
+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[];
 
-extern CV_PossibleValue_t granisotropicmode_cons_t[];
+extern CV_PossibleValue_t glanisotropicmode_cons_t[];
 
 #ifdef ALAM_LIGHTING
-extern consvar_t cv_grdynamiclighting;
-extern consvar_t cv_grstaticlighting;
-extern consvar_t cv_grcoronas;
-extern consvar_t cv_grcoronasize;
+extern consvar_t cv_gldynamiclighting;
+extern consvar_t cv_glstaticlighting;
+extern consvar_t cv_glcoronas;
+extern consvar_t cv_glcoronasize;
 #endif
 
-extern consvar_t cv_grshaders;
-extern consvar_t cv_grmodels;
-extern consvar_t cv_grmodelinterpolation;
-extern consvar_t cv_grmodellighting;
-extern consvar_t cv_grfiltermode;
-extern consvar_t cv_granisotropicmode;
+extern consvar_t cv_glshaders, cv_glallowshaders;
+extern consvar_t cv_glmodels;
+extern consvar_t cv_glmodelinterpolation;
+extern consvar_t cv_glmodellighting;
+extern consvar_t cv_glfiltermode;
+extern consvar_t cv_glanisotropicmode;
 extern consvar_t cv_fovchange;
-extern consvar_t cv_grsolvetjoin;
-extern consvar_t cv_grshearing;
-extern consvar_t cv_grspritebillboarding;
-extern consvar_t cv_grskydome;
-extern consvar_t cv_grfakecontrast;
-extern consvar_t cv_grslopecontrast;
+extern consvar_t cv_glsolvetjoin;
+extern consvar_t cv_glshearing;
+extern consvar_t cv_glspritebillboarding;
+extern consvar_t cv_glskydome;
+extern consvar_t cv_glfakecontrast;
+extern consvar_t cv_glslopecontrast;
 
-extern consvar_t cv_grbatching;
+extern consvar_t cv_glbatching;
 
-extern float gr_viewwidth, gr_viewheight, gr_baseviewwindowy;
+extern float gl_viewwidth, gl_viewheight, gl_baseviewwindowy;
 
-extern float gr_viewwindowx, gr_basewindowcentery;
+extern float gl_viewwindowx, gl_basewindowcentery;
 
 // BP: big hack for a test in lighting ref : 1249753487AB
 extern fixed_t *hwbbox;
@@ -106,22 +116,27 @@ extern FTransform atransform;
 
 
 // Render stats
-extern int rs_hw_nodesorttime;
-extern int rs_hw_nodedrawtime;
-extern int rs_hw_spritesorttime;
-extern int rs_hw_spritedrawtime;
+extern precise_t ps_hw_skyboxtime;
+extern precise_t ps_hw_nodesorttime;
+extern precise_t ps_hw_nodedrawtime;
+extern precise_t ps_hw_spritesorttime;
+extern precise_t ps_hw_spritedrawtime;
 
 // Render stats for batching
-extern int rs_hw_numpolys;
-extern int rs_hw_numverts;
-extern int rs_hw_numcalls;
-extern int rs_hw_numshaders;
-extern int rs_hw_numtextures;
-extern int rs_hw_numpolyflags;
-extern int rs_hw_numcolors;
-extern int rs_hw_batchsorttime;
-extern int rs_hw_batchdrawtime;
-
-extern boolean gr_shadersavailable;
+extern int ps_hw_numpolys;
+extern int ps_hw_numverts;
+extern int ps_hw_numcalls;
+extern int ps_hw_numshaders;
+extern int ps_hw_numtextures;
+extern int ps_hw_numpolyflags;
+extern int ps_hw_numcolors;
+extern precise_t ps_hw_batchsorttime;
+extern precise_t ps_hw_batchdrawtime;
+
+extern boolean gl_init;
+extern boolean gl_maploaded;
+extern boolean gl_maptexturesloaded;
+extern boolean gl_sessioncommandsadded;
+extern boolean gl_shadersavailable;
 
 #endif
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 80c01f98cb78e7f3b81aa47da9144798eed9860a..2e944d3e601c03f541bdf65d3ed57fda4c81d4a1 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -92,7 +92,13 @@ static void md2_freeModel (model_t *model)
 static model_t *md2_readModel(const char *filename)
 {
 	//Filename checking fixed ~Monster Iestyn and Golden
-	return LoadModel(va("%s"PATHSEP"%s", srb2home, filename), PU_STATIC);
+	if (FIL_FileExists(va("%s"PATHSEP"%s", srb2home, filename)))
+		return LoadModel(va("%s"PATHSEP"%s", srb2home, filename), PU_STATIC);
+
+	if (FIL_FileExists(va("%s"PATHSEP"%s", srb2path, filename)))
+		return LoadModel(va("%s"PATHSEP"%s", srb2path, filename), PU_STATIC);
+
+	return NULL;
 }
 
 static inline void md2_printModelInfo (model_t *model)
@@ -141,7 +147,7 @@ static void PNG_warn(png_structp PNG, png_const_charp pngtext)
 	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
 }
 
-static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_t *grpatch)
+static GLTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_t *grpatch)
 {
 	png_structp png_ptr;
 	png_infop png_info_ptr;
@@ -160,8 +166,12 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 	png_FILE = fopen(pngfilename, "rb");
 	if (!png_FILE)
 	{
+		pngfilename = va("%s"PATHSEP"models"PATHSEP"%s", srb2path, filename);
+		FIL_ForceExtension(pngfilename, ".png");
+		png_FILE = fopen(pngfilename, "rb");
 		//CONS_Debug(DBG_RENDER, "M_SavePNG: Error on opening %s for loading\n", filename);
-		return 0;
+		if (!png_FILE)
+			return 0;
 	}
 
 	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
@@ -191,7 +201,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
 		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
 		fclose(png_FILE);
-		Z_Free(grpatch->mipmap->grInfo.data);
+		Z_Free(grpatch->mipmap->data);
 		return 0;
 	}
 #ifdef USE_FAR_KEYWORD
@@ -232,7 +242,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 
 	{
 		png_uint_32 i, pitch = png_get_rowbytes(png_ptr, png_info_ptr);
-		png_bytep PNG_image = Z_Malloc(pitch*height, PU_HWRMODELTEXTURE, &grpatch->mipmap->grInfo.data);
+		png_bytep PNG_image = Z_Malloc(pitch*height, PU_HWRMODELTEXTURE, &grpatch->mipmap->data);
 		png_bytepp row_pointers = png_malloc(png_ptr, height * sizeof (png_bytep));
 		for (i = 0; i < height; i++)
 			row_pointers[i] = PNG_image + i*pitch;
@@ -245,7 +255,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 	fclose(png_FILE);
 	*w = (int)width;
 	*h = (int)height;
-	return GR_RGBA;
+	return GL_TEXFMT_RGBA;
 }
 #endif
 
@@ -271,7 +281,7 @@ typedef struct
 	UINT8 filler[54];
 } PcxHeader;
 
-static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
+static GLTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 	GLPatch_t *grpatch)
 {
 	PcxHeader header;
@@ -288,7 +298,13 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 	FIL_ForceExtension(pcxfilename, ".pcx");
 	file = fopen(pcxfilename, "rb");
 	if (!file)
-		return 0;
+	{
+		pcxfilename = va("%s"PATHSEP"models"PATHSEP"%s", srb2path, filename);
+		FIL_ForceExtension(pcxfilename, ".pcx");
+		file = fopen(pcxfilename, "rb");
+		if (!file)
+			return 0;
+	}
 
 	if (fread(&header, sizeof (PcxHeader), 1, file) != 1)
 	{
@@ -306,7 +322,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 
 	pw = *w = header.xmax - header.xmin + 1;
 	ph = *h = header.ymax - header.ymin + 1;
-	image = Z_Malloc(pw*ph*4, PU_HWRMODELTEXTURE, &grpatch->mipmap->grInfo.data);
+	image = Z_Malloc(pw*ph*4, PU_HWRMODELTEXTURE, &grpatch->mipmap->data);
 
 	if (fread(palette, sizeof (UINT8), PALSIZE, file) != PALSIZE)
 	{
@@ -340,7 +356,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 		}
 	}
 	fclose(file);
-	return GR_RGBA;
+	return GL_TEXFMT_RGBA;
 }
 
 // -----------------+
@@ -348,48 +364,53 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 // -----------------+
 static void md2_loadTexture(md2_t *model)
 {
-	GLPatch_t *grpatch;
+	patch_t *patch;
+	GLPatch_t *grPatch = NULL;
 	const char *filename = model->filename;
 
 	if (model->grpatch)
 	{
-		grpatch = model->grpatch;
-		Z_Free(grpatch->mipmap->grInfo.data);
+		patch = model->grpatch;
+		grPatch = (GLPatch_t *)(patch->hardware);
+		if (grPatch)
+			Z_Free(grPatch->mipmap->data);
 	}
 	else
-	{
-		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
-		                   &(model->grpatch));
-		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
-	}
+		model->grpatch = patch = Patch_Create(NULL, 0, NULL);
+
+	if (!patch->hardware)
+		Patch_AllocateHardwarePatch(patch);
 
-	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
+	if (grPatch == NULL)
+		grPatch = (GLPatch_t *)(patch->hardware);
+
+	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
 	{
 		int w = 0, h = 0;
 		UINT32 size;
 		RGBA_t *image;
 
 #ifdef HAVE_PNG
-		grpatch->mipmap->grInfo.format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->grInfo.format == 0)
+		grPatch->mipmap->format = PNG_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 #endif
-		grpatch->mipmap->grInfo.format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->grInfo.format == 0)
+		grPatch->mipmap->format = PCX_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 		{
 			model->notexturefile = true; // mark it so its not searched for again repeatedly
 			return;
 		}
 
-		grpatch->mipmap->downloaded = 0;
-		grpatch->mipmap->flags = 0;
+		grPatch->mipmap->downloaded = 0;
+		grPatch->mipmap->flags = 0;
 
-		grpatch->width = (INT16)w;
-		grpatch->height = (INT16)h;
-		grpatch->mipmap->width = (UINT16)w;
-		grpatch->mipmap->height = (UINT16)h;
+		patch->width = (INT16)w;
+		patch->height = (INT16)h;
+		grPatch->mipmap->width = (UINT16)w;
+		grPatch->mipmap->height = (UINT16)h;
 
 		// Lactozilla: Apply colour cube
-		image = grpatch->mipmap->grInfo.data;
+		image = grPatch->mipmap->data;
 		size = w*h;
 		while (size--)
 		{
@@ -397,7 +418,7 @@ static void md2_loadTexture(md2_t *model)
 			image++;
 		}
 	}
-	HWD.pfnSetTexture(grpatch->mipmap);
+	HWD.pfnSetTexture(grPatch->mipmap);
 }
 
 // -----------------+
@@ -405,48 +426,53 @@ static void md2_loadTexture(md2_t *model)
 // -----------------+
 static void md2_loadBlendTexture(md2_t *model)
 {
-	GLPatch_t *grpatch;
+	patch_t *patch;
+	GLPatch_t *grPatch = NULL;
 	char *filename = Z_Malloc(strlen(model->filename)+7, PU_STATIC, NULL);
-	strcpy(filename, model->filename);
 
+	strcpy(filename, model->filename);
 	FIL_ForceExtension(filename, "_blend.png");
 
 	if (model->blendgrpatch)
 	{
-		grpatch = model->blendgrpatch;
-		Z_Free(grpatch->mipmap->grInfo.data);
+		patch = model->blendgrpatch;
+		grPatch = (GLPatch_t *)(patch->hardware);
+		if (grPatch)
+			Z_Free(grPatch->mipmap->data);
 	}
 	else
-	{
-		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
-		                   &(model->blendgrpatch));
-		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
-	}
+		model->blendgrpatch = patch = Patch_Create(NULL, 0, NULL);
+
+	if (!patch->hardware)
+		Patch_AllocateHardwarePatch(patch);
 
-	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
+	if (grPatch == NULL)
+		grPatch = (GLPatch_t *)(patch->hardware);
+
+	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
 	{
 		int w = 0, h = 0;
 #ifdef HAVE_PNG
-		grpatch->mipmap->grInfo.format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->grInfo.format == 0)
+		grPatch->mipmap->format = PNG_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 #endif
-		grpatch->mipmap->grInfo.format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->grInfo.format == 0)
+		grPatch->mipmap->format = PCX_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 		{
 			model->noblendfile = true; // mark it so its not searched for again repeatedly
 			Z_Free(filename);
 			return;
 		}
 
-		grpatch->mipmap->downloaded = 0;
-		grpatch->mipmap->flags = 0;
+		grPatch->mipmap->downloaded = 0;
+		grPatch->mipmap->flags = 0;
 
-		grpatch->width = (INT16)w;
-		grpatch->height = (INT16)h;
-		grpatch->mipmap->width = (UINT16)w;
-		grpatch->mipmap->height = (UINT16)h;
+		patch->width = (INT16)w;
+		patch->height = (INT16)h;
+		grPatch->mipmap->width = (UINT16)w;
+		grPatch->mipmap->height = (UINT16)h;
 	}
-	HWD.pfnSetTexture(grpatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
+	HWD.pfnSetTexture(grPatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
 
 	Z_Free(filename);
 }
@@ -493,9 +519,13 @@ void HWR_InitModels(void)
 
 	if (!f)
 	{
-		CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno));
-		nomd2s = true;
-		return;
+		f = fopen(va("%s"PATHSEP"%s", srb2path, "models.dat"), "rt");
+		if (!f)
+		{
+			CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno));
+			nomd2s = true;
+			return;
+		}
 	}
 
 	// length of the player model prefix
@@ -569,9 +599,13 @@ void HWR_AddPlayerModel(int skin) // For skins that were added after startup
 
 	if (!f)
 	{
-		CONS_Printf("Error while loading models.dat\n");
-		nomd2s = true;
-		return;
+		f = fopen(va("%s"PATHSEP"%s", srb2path, "models.dat"), "rt");
+		if (!f)
+		{
+			CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno));
+			nomd2s = true;
+			return;
+		}
 	}
 
 	// length of the player model prefix
@@ -624,9 +658,13 @@ void HWR_AddSpriteModel(size_t spritenum) // For sprites that were added after s
 
 	if (!f)
 	{
-		CONS_Printf("Error while loading models.dat\n");
-		nomd2s = true;
-		return;
+		f = fopen(va("%s"PATHSEP"%s", srb2path, "models.dat"), "rt");
+		if (!f)
+		{
+			CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno));
+			nomd2s = true;
+			return;
+		}
 	}
 
 	// Check for any models that match the names of sprite names!
@@ -664,8 +702,10 @@ spritemodelfound:
 #define SETBRIGHTNESS(brightness,r,g,b) \
 	brightness = (UINT8)(((1063*(UINT16)(r))/5000) + ((3576*(UINT16)(g))/5000) + ((361*(UINT16)(b))/5000))
 
-static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, GLMipmap_t *grmip, INT32 skinnum, skincolornum_t color)
+static void HWR_CreateBlendedTexture(patch_t *gpatch, patch_t *blendgpatch, GLMipmap_t *grMipmap, INT32 skinnum, skincolornum_t color)
 {
+	GLPatch_t *hwrPatch = gpatch->hardware;
+	GLPatch_t *hwrBlendPatch = blendgpatch->hardware;
 	UINT16 w = gpatch->width, h = gpatch->height;
 	UINT32 size = w*h;
 	RGBA_t *image, *blendimage, *cur, blendcolor;
@@ -678,28 +718,29 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 	memset(translation, 0, sizeof(translation));
 	memset(cutoff, 0, sizeof(cutoff));
 
-	if (grmip->width == 0)
+	if (grMipmap->width == 0)
 	{
-		grmip->width = gpatch->width;
-		grmip->height = gpatch->height;
+		grMipmap->width = gpatch->width;
+		grMipmap->height = gpatch->height;
 
 		// no wrap around, no chroma key
-		grmip->flags = 0;
+		grMipmap->flags = 0;
+
 		// setup the texture info
-		grmip->grInfo.format = GR_RGBA;
+		grMipmap->format = GL_TEXFMT_RGBA;
 	}
 
-	if (grmip->grInfo.data)
+	if (grMipmap->data)
 	{
-		Z_Free(grmip->grInfo.data);
-		grmip->grInfo.data = NULL;
+		Z_Free(grMipmap->data);
+		grMipmap->data = NULL;
 	}
 
-	cur = Z_Malloc(size*4, PU_HWRMODELTEXTURE, &grmip->grInfo.data);
+	cur = Z_Malloc(size*4, PU_HWRMODELTEXTURE, &grMipmap->data);
 	memset(cur, 0x00, size*4);
 
-	image = gpatch->mipmap->grInfo.data;
-	blendimage = blendgpatch->mipmap->grInfo.data;
+	image = hwrPatch->mipmap->data;
+	blendimage = hwrBlendPatch->mipmap->data;
 
 	// TC_METALSONIC includes an actual skincolor translation, on top of its flashing.
 	if (skinnum == TC_METALSONIC)
@@ -1038,37 +1079,47 @@ skippixel:
 
 #undef SETBRIGHTNESS
 
-static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT32 skinnum, const UINT8 *colormap, skincolornum_t color)
+static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 skinnum, const UINT8 *colormap, skincolornum_t color)
 {
 	// mostly copied from HWR_GetMappedPatch, hence the similarities and comment
-	GLMipmap_t *grmip, *newmip;
+	GLPatch_t *grPatch = patch->hardware;
+	GLPatch_t *grBlendPatch = NULL;
+	GLMipmap_t *grMipmap, *newMipmap;
 
-	if (colormap == colormaps || colormap == NULL)
+	if (blendpatch == NULL || colormap == colormaps || colormap == NULL)
 	{
 		// Don't do any blending
-		HWD.pfnSetTexture(gpatch->mipmap);
+		HWD.pfnSetTexture(grPatch->mipmap);
 		return;
 	}
 
-	if ((blendgpatch && blendgpatch->mipmap->grInfo.format)
-		&& (gpatch->width != blendgpatch->width || gpatch->height != blendgpatch->height))
+	if ((blendpatch && (grBlendPatch = blendpatch->hardware) && grBlendPatch->mipmap->format)
+		&& (patch->width != blendpatch->width || patch->height != blendpatch->height))
 	{
 		// Blend image exists, but it's bad.
-		HWD.pfnSetTexture(gpatch->mipmap);
+		HWD.pfnSetTexture(grPatch->mipmap);
 		return;
 	}
 
 	// search for the mipmap
 	// skip the first (no colormap translated)
-	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
+	for (grMipmap = grPatch->mipmap; grMipmap->nextcolormap; )
 	{
-		grmip = grmip->nextcolormap;
-		if (grmip->colormap == colormap)
+		grMipmap = grMipmap->nextcolormap;
+		if (grMipmap->colormap && grMipmap->colormap->source == colormap)
 		{
-			if (grmip->downloaded && grmip->grInfo.data)
+			if (grMipmap->downloaded && grMipmap->data)
 			{
-				HWD.pfnSetTexture(grmip); // found the colormap, set it to the correct texture
-				Z_ChangeTag(grmip->grInfo.data, PU_HWRMODELTEXTURE_UNLOCKED);
+				if (memcmp(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8)))
+				{
+					M_Memcpy(grMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
+					HWR_CreateBlendedTexture(patch, blendpatch, grMipmap, skinnum, color);
+					HWD.pfnUpdateTexture(grMipmap);
+				}
+				else
+					HWD.pfnSetTexture(grMipmap); // found the colormap, set it to the correct texture
+
+				Z_ChangeTag(grMipmap->data, PU_HWRMODELTEXTURE_UNLOCKED);
 				return;
 			}
 		}
@@ -1079,18 +1130,21 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT
 
 	//BP: WARNING: don't free it manually without clearing the cache of harware renderer
 	//              (it have a liste of mipmap)
-	//    this malloc is cleared in HWR_FreeTextureCache
+	//    this malloc is cleared in HWR_FreeColormapCache
 	//    (...) unfortunately z_malloc fragment alot the memory :(so malloc is better
-	newmip = calloc(1, sizeof (*newmip));
-	if (newmip == NULL)
+	newMipmap = calloc(1, sizeof (*newMipmap));
+	if (newMipmap == NULL)
 		I_Error("%s: Out of memory", "HWR_GetBlendedTexture");
-	grmip->nextcolormap = newmip;
-	newmip->colormap = colormap;
+	grMipmap->nextcolormap = newMipmap;
 
-	HWR_CreateBlendedTexture(gpatch, blendgpatch, newmip, skinnum, color);
+	newMipmap->colormap = Z_Calloc(sizeof(*newMipmap->colormap), PU_HWRPATCHCOLMIPMAP, NULL);
+	newMipmap->colormap->source = colormap;
+	M_Memcpy(newMipmap->colormap->data, colormap, 256 * sizeof(UINT8));
 
-	HWD.pfnSetTexture(newmip);
-	Z_ChangeTag(newmip->grInfo.data, PU_HWRMODELTEXTURE_UNLOCKED);
+	HWR_CreateBlendedTexture(patch, blendpatch, newMipmap, skinnum, color);
+
+	HWD.pfnSetTexture(newMipmap);
+	Z_ChangeTag(newMipmap->data, PU_HWRMODELTEXTURE_UNLOCKED);
 }
 
 #define NORMALFOG 0x00000000
@@ -1108,14 +1162,14 @@ static boolean HWR_AllowModel(mobj_t *mobj)
 
 static boolean HWR_CanInterpolateModel(mobj_t *mobj, model_t *model)
 {
-	if (cv_grmodelinterpolation.value == 2) // Always interpolate
+	if (cv_glmodelinterpolation.value == 2) // Always interpolate
 		return true;
 	return model->interpolate[(mobj->frame & FF_FRAMEMASK)];
 }
 
 static boolean HWR_CanInterpolateSprite2(modelspr2frames_t *spr2frame)
 {
-	if (cv_grmodelinterpolation.value == 2) // Always interpolate
+	if (cv_glmodelinterpolation.value == 2) // Always interpolate
 		return true;
 	return spr2frame->interpolate;
 }
@@ -1177,39 +1231,53 @@ static UINT8 HWR_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t
 	return spr2;
 }
 
-static void adjustTextureCoords(model_t *model, GLPatch_t *gpatch)
+// Adjust texture coords of model to fit into a patch's max_s and max_t
+static void adjustTextureCoords(model_t *model, patch_t *patch)
 {
 	int i;
+	GLPatch_t *gpatch = ((GLPatch_t *)patch->hardware);
+
 	for (i = 0; i < model->numMeshes; i++)
 	{
 		int j;
 		mesh_t *mesh = &model->meshes[i];
 		int numVertices;
-		float *uvPtr = mesh->uvs;
+		float *uvReadPtr = mesh->originaluvs;
+		float *uvWritePtr;
 
 		// i dont know if this is actually possible, just logical conclusion of structure in CreateModelVBOs
-		if (!mesh->frames && !mesh->tinyframes) return;
+		if (!mesh->frames && !mesh->tinyframes) continue;
 
 		if (mesh->frames) // again CreateModelVBO and CreateModelVBOTiny iterate like this so I'm gonna do that too
 			numVertices = mesh->numTriangles * 3;
 		else
 			numVertices = mesh->numVertices;
 
+		// if originaluvs points to uvs, we need to allocate new memory for adjusted uvs
+		// the old uvs are kept around for use in possible readjustments
+		if (mesh->uvs == mesh->originaluvs)
+			mesh->uvs = Z_Malloc(numVertices * 2 * sizeof(float), PU_STATIC, NULL);
+
+		uvWritePtr = mesh->uvs;
+
 		// fix uvs (texture coordinates) to take into account that the actual texture
 		// has empty space added until the next power of two
 		for (j = 0; j < numVertices; j++)
 		{
-			*uvPtr++ *= gpatch->max_s;
-			*uvPtr++ *= gpatch->max_t;
+			*uvWritePtr++ = *uvReadPtr++ * gpatch->max_s;
+			*uvWritePtr++ = *uvReadPtr++ * gpatch->max_t;
 		}
 	}
+	// Save the values we adjusted the uvs for
+	model->max_s = gpatch->max_s;
+	model->max_t = gpatch->max_t;
 }
 
 //
 // HWR_DrawModel
 //
 
-boolean HWR_DrawModel(gr_vissprite_t *spr)
+boolean HWR_DrawModel(gl_vissprite_t *spr)
 {
 	md2_t *md2;
 
@@ -1220,12 +1288,16 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 	FTransform p;
 	FSurfaceInfo Surf;
 
-	if (!cv_grmodels.value)
+	if (!cv_glmodels.value)
 		return false;
 
 	if (spr->precip)
 		return false;
 
+	// Lactozilla: Disallow certain models from rendering
+	if (!HWR_AllowModel(spr->mobj))
+		return false;
+
 	memset(&p, 0x00, sizeof(FTransform));
 
 	// MD2 colormap fix
@@ -1264,7 +1336,8 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 
 	// Look at HWR_ProjectSprite for more
 	{
-		GLPatch_t *gpatch;
+		patch_t *gpatch, *blendgpatch;
+		GLPatch_t *hwrPatch = NULL, *hwrBlendPatch = NULL;
 		INT32 durs = spr->mobj->state->tics;
 		INT32 tics = spr->mobj->tics;
 		//mdlframe_t *next = NULL;
@@ -1282,15 +1355,16 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		//if (tics > durs)
 			//durs = tics;
 
-		if (spr->mobj->flags2 & MF2_SHADOW)
-			Surf.PolyColor.s.alpha = 0x40;
-		else if (spr->mobj->frame & FF_TRANSMASK)
-			HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+		if (spr->mobj->frame & FF_TRANSMASK)
+			Surf.PolyFlags = HWR_SurfaceBlend(spr->mobj->blendmode, (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
 		else
-			Surf.PolyColor.s.alpha = 0xFF;
+		{
+			Surf.PolyColor.s.alpha = (spr->mobj->flags2 & MF2_SHADOW) ? 0x40 : 0xff;
+			Surf.PolyFlags = HWR_GetBlendModeFlag(spr->mobj->blendmode);
+		}
 
-		// dont forget to enabled the depth test because we can't do this like
-		// before: polygons models are not sorted
+		// don't forget to enable the depth test because we can't do this
+		// like before: model polygons are not sorted
 
 		// 1. load model+texture if not already loaded
 		// 2. draw model with correct position, rotation,...
@@ -1309,14 +1383,26 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		// texture loading before model init, so it knows if sprite graphics are used, which
 		// means that texture coordinates have to be adjusted
 		gpatch = md2->grpatch;
-		if (!gpatch || ((!gpatch->mipmap->grInfo.format || !gpatch->mipmap->downloaded) && !md2->notexturefile))
+		if (gpatch)
+			hwrPatch = ((GLPatch_t *)gpatch->hardware);
+
+		if (!gpatch || !hwrPatch
+		|| ((!hwrPatch->mipmap->format || !hwrPatch->mipmap->downloaded) && !md2->notexturefile))
 			md2_loadTexture(md2);
-		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
 
-		if ((gpatch && gpatch->mipmap->grInfo.format) // don't load the blend texture if the base texture isn't available
-			&& (!md2->blendgrpatch
-			|| ((!((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded)
-			&& !md2->noblendfile)))
+		// Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
+		gpatch = md2->grpatch;
+		if (gpatch)
+			hwrPatch = ((GLPatch_t *)gpatch->hardware);
+
+		// Load blend texture
+		blendgpatch = md2->blendgrpatch;
+		if (blendgpatch)
+			hwrBlendPatch = ((GLPatch_t *)blendgpatch->hardware);
+
+		if ((gpatch && hwrPatch && hwrPatch->mipmap->format) // don't load the blend texture if the base texture isn't available
+			&& (!blendgpatch || !hwrBlendPatch
+			|| ((!hwrBlendPatch->mipmap->format || !hwrBlendPatch->mipmap->downloaded) && !md2->noblendfile)))
 			md2_loadBlendTexture(md2);
 
 		if (md2->error)
@@ -1330,10 +1416,13 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			if (md2->model)
 			{
 				md2_printModelInfo(md2->model);
-				// if model uses sprite patch as texture, then
+				// If model uses sprite patch as texture, then
 				// adjust texture coordinates to take power of two textures into account
-				if (!gpatch || !gpatch->mipmap->grInfo.format)
+				if (!gpatch || !hwrPatch->mipmap->format)
 					adjustTextureCoords(md2->model, spr->gpatch);
+				// note down the max_s and max_t that end up in the VBO
+				md2->model->vbo_max_s = md2->model->max_s;
+				md2->model->vbo_max_t = md2->model->max_t;
 				HWD.pfnCreateModelVBOs(md2->model);
 			}
 			else
@@ -1344,15 +1433,11 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			}
 		}
 
-		// Lactozilla: Disallow certain models from rendering
-		if (!HWR_AllowModel(spr->mobj))
-			return false;
-
 		//HWD.pfnSetBlend(blend); // This seems to actually break translucency?
 		finalscale = md2->scale;
 		//Hurdler: arf, I don't like that implementation at all... too much crappy
 
-		if (gpatch && gpatch->mipmap->grInfo.format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
+		if (gpatch && hwrPatch && hwrPatch->mipmap->format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
 		{
 			INT32 skinnum = TC_DEFAULT;
 
@@ -1385,13 +1470,19 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			}
 
 			// Translation or skin number found
-			HWR_GetBlendedTexture(gpatch, (GLPatch_t *)md2->blendgrpatch, skinnum, spr->colormap, (skincolornum_t)spr->mobj->color);
+			HWR_GetBlendedTexture(gpatch, blendgpatch, skinnum, spr->colormap, (skincolornum_t)spr->mobj->color);
 		}
-		else
+		else // Sprite
 		{
-			// Sprite
-			gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
-			HWR_GetMappedPatch(gpatch, spr->colormap);
+			// Check if sprite dimensions are different from previously used sprite.
+			// If so, uvs need to be readjusted.
+			// Comparing floats with the != operator here should be okay because they
+			// are just copies of glpatches' max_s and max_t values.
+			// Instead of the != operator, memcmp is used to avoid a compiler warning.
+			if (memcmp(&(hwrPatch->max_s), &(md2->model->max_s), sizeof(md2->model->max_s)) != 0 ||
+				memcmp(&(hwrPatch->max_t), &(md2->model->max_t), sizeof(md2->model->max_t)) != 0)
+				adjustTextureCoords(md2->model, spr->gpatch);
+			HWR_GetMappedPatch(spr->gpatch, spr->colormap);
 		}
 
 		if (spr->mobj->frame & FF_ANIMATE)
@@ -1423,7 +1514,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 
 #ifdef USE_MODEL_NEXTFRAME
 #define INTERPOLERATION_LIMIT TICRATE/4
-		if (cv_grmodelinterpolation.value && tics <= durs && tics <= INTERPOLERATION_LIMIT)
+		if (cv_glmodelinterpolation.value && tics <= durs && tics <= INTERPOLERATION_LIMIT)
 		{
 			if (durs > INTERPOLERATION_LIMIT)
 				durs = INTERPOLERATION_LIMIT;
@@ -1549,7 +1640,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		p.mirror = atransform.mirror; // from Kart
 #endif
 
-		HWD.pfnSetShader(4);	// model shader
+		HWD.pfnSetShader(SHADER_MODEL);	// model shader
 		HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, finalscale, flip, hflip, &Surf);
 	}
 
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 42a9d7c634d3643fd198372e87b5250d8c5f9068..0f4d2c7bc925f45005757c09a80cabaf103a8a3a 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -42,7 +42,7 @@ extern md2_t md2_playermodels[MAXSKINS];
 void HWR_InitModels(void);
 void HWR_AddPlayerModel(INT32 skin);
 void HWR_AddSpriteModel(size_t spritenum);
-boolean HWR_DrawModel(gr_vissprite_t *spr);
+boolean HWR_DrawModel(gl_vissprite_t *spr);
 
 #define PLAYERMODELPREFIX "PLAYER"
 
diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c
index ac73f8acac4b577f1ca18bd077e8bba837f320d8..4ed03744bfe072db597c0e2d120fd72ccd77a6ca 100644
--- a/src/hardware/hw_model.c
+++ b/src/hardware/hw_model.c
@@ -221,6 +221,15 @@ model_t *LoadModel(const char *filename, int ztag)
 		material->shininess = 25.0f;
 	}
 
+	// Set originaluvs to point to uvs
+	for (i = 0; i < model->numMeshes; i++)
+		model->meshes[i].originaluvs = model->meshes[i].uvs;
+
+	model->max_s = 1.0;
+	model->max_t = 1.0;
+	model->vbo_max_s = 1.0;
+	model->vbo_max_t = 1.0;
+
 	return model;
 }
 
diff --git a/src/hardware/hw_model.h b/src/hardware/hw_model.h
index 2a5240bdefbda6320fa2411b451a5bf86408124e..6b39eb24d90e9b27bc21704edceacdda15679efe 100644
--- a/src/hardware/hw_model.h
+++ b/src/hardware/hw_model.h
@@ -59,6 +59,11 @@ typedef struct mesh_s
 	int numTriangles;
 
 	float *uvs;
+	// if uv adjustment is needed, uvs is changed to point to adjusted ones and
+	// this one retains the originals
+	// note: this member has been added with the assumption that models are never freed.
+	// (UnloadModel is called by nobody at the time of writing.)
+	float *originaluvs;
 	float *lightuvs;
 
 	int numFrames;
@@ -99,6 +104,15 @@ typedef struct model_s
 	char *framenames;
 	boolean interpolate[256];
 	modelspr2frames_t *spr2frames;
+
+	// the max_s and max_t values that the uvs are currently adjusted to
+	// (if a sprite is used as a texture)
+	float max_s;
+	float max_t;
+	// These are the values that the uvs in the VBO have been adjusted to.
+	// If they are not same as max_s and max_t, then the VBO won't be used.
+	float vbo_max_s;
+	float vbo_max_t;
 } model_t;
 
 extern int numModels;
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index b7baa4eb0bdd0389338e61e58201bfa21bb6bd27..2568a7d08c2f6deb92c26d17a97c03ab8a3ea1e4 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -58,8 +58,9 @@ static  GLuint      tex_downloaded  = 0;
 static  GLfloat     fov             = 90.0f;
 static  FBITFIELD   CurrentPolyFlags;
 
-static  FTextureInfo *gr_cachetail = NULL;
-static  FTextureInfo *gr_cachehead = NULL;
+// Linked list of all textures.
+static FTextureInfo *TexCacheTail = NULL;
+static FTextureInfo *TexCacheHead = NULL;
 
 RGBA_t  myPaletteData[256];
 GLint   screen_width    = 0;               // used by Draw2DLine()
@@ -91,10 +92,6 @@ static GLuint startScreenWipe = 0;
 static GLuint endScreenWipe = 0;
 static GLuint finalScreenTexture = 0;
 
-// Lactozilla: Set shader programs and uniforms
-static void *Shader_Load(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade);
-static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade);
-
 // shortcut for ((float)1/i)
 static const GLfloat byte2float[256] = {
 	0.000000f, 0.003922f, 0.007843f, 0.011765f, 0.015686f, 0.019608f, 0.023529f, 0.027451f,
@@ -423,6 +420,10 @@ static PFNglBufferData pglBufferData;
 typedef void (APIENTRY * PFNglDeleteBuffers) (GLsizei n, const GLuint *buffers);
 static PFNglDeleteBuffers pglDeleteBuffers;
 
+/* 2.0 functions */
+typedef void (APIENTRY * PFNglBlendEquation) (GLenum mode);
+static PFNglBlendEquation pglBlendEquation;
+
 
 /* 1.2 Parms */
 /* GL_CLAMP_TO_EDGE_EXT */
@@ -530,8 +531,8 @@ boolean SetupGLfunc(void)
 	return true;
 }
 
-static boolean gl_allowshaders = false;
 static boolean gl_shadersenabled = false;
+static hwdshaderoption_t gl_allowshaders = HWD_SHADEROPTION_OFF;
 
 #ifdef GL_SHADERS
 typedef GLuint 	(APIENTRY *PFNglCreateShader)		(GLenum);
@@ -541,6 +542,7 @@ typedef void 	(APIENTRY *PFNglGetShaderiv)		(GLuint, GLenum, GLint*);
 typedef void 	(APIENTRY *PFNglGetShaderInfoLog)	(GLuint, GLsizei, GLsizei*, GLchar*);
 typedef void 	(APIENTRY *PFNglDeleteShader)		(GLuint);
 typedef GLuint 	(APIENTRY *PFNglCreateProgram)		(void);
+typedef void  	(APIENTRY *PFNglDeleteProgram)		(GLuint);
 typedef void 	(APIENTRY *PFNglAttachShader)		(GLuint, GLuint);
 typedef void 	(APIENTRY *PFNglLinkProgram)		(GLuint);
 typedef void 	(APIENTRY *PFNglGetProgramiv)		(GLuint, GLenum, GLint*);
@@ -562,6 +564,7 @@ static PFNglGetShaderiv pglGetShaderiv;
 static PFNglGetShaderInfoLog pglGetShaderInfoLog;
 static PFNglDeleteShader pglDeleteShader;
 static PFNglCreateProgram pglCreateProgram;
+static PFNglDeleteProgram pglDeleteProgram;
 static PFNglAttachShader pglAttachShader;
 static PFNglLinkProgram pglLinkProgram;
 static PFNglGetProgramiv pglGetProgramiv;
@@ -576,15 +579,6 @@ static PFNglUniform2fv pglUniform2fv;
 static PFNglUniform3fv pglUniform3fv;
 static PFNglGetUniformLocation pglGetUniformLocation;
 
-#define MAXSHADERS 16
-#define MAXSHADERPROGRAMS 16
-
-// 18032019
-static char *gl_customvertexshaders[MAXSHADERS];
-static char *gl_customfragmentshaders[MAXSHADERS];
-static GLuint gl_currentshaderprogram = 0;
-static boolean gl_shaderprogramchanged = true;
-
 // 13062019
 typedef enum
 {
@@ -602,23 +596,85 @@ typedef enum
 	gluniform_max,
 } gluniform_t;
 
-typedef struct gl_shaderprogram_s
+typedef struct gl_shader_s
 {
 	GLuint program;
-	boolean custom;
 	GLint uniforms[gluniform_max+1];
-} gl_shaderprogram_t;
-static gl_shaderprogram_t gl_shaderprograms[MAXSHADERPROGRAMS];
+	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];
+
+// 09102020
+typedef struct gl_shaderstate_s
+{
+	gl_shader_t *current;
+	GLuint type;
+	GLuint program;
+	boolean changed;
+} gl_shaderstate_t;
+static gl_shaderstate_t gl_shaderstate;
 
 // Shader info
 static INT32 shader_leveltime = 0;
 
-// ========================
-//  Fragment shader macros
-// ========================
+// Lactozilla: Shader functions
+static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i, const GLchar *vert_shader, const GLchar *frag_shader);
+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"
 
 //
-// GLSL Software fragment shader
+// Software fragment shader
 //
 
 #define GLSL_DOOM_COLORMAP \
@@ -678,6 +734,29 @@ static INT32 shader_leveltime = 0;
 		"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
 //
@@ -737,90 +816,52 @@ static INT32 shader_leveltime = 0;
 	"}\0"
 
 //
-// GLSL generic fragment shader
+// Sky fragment shader
+// Modulates poly_color with gl_Color
 //
-
-#define GLSL_DEFAULT_FRAGMENT_SHADER \
+#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) * poly_color;\n" \
+		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * gl_Color * poly_color;\n" \
 	"}\0"
 
-static const char *fragment_shaders[] = {
-	// Default fragment shader
-	GLSL_DEFAULT_FRAGMENT_SHADER,
+// ================
+//  Shader sources
+// ================
 
-	// Floor fragment shader
-	GLSL_SOFTWARE_FRAGMENT_SHADER,
+static struct {
+	const char *vertex;
+	const char *fragment;
+} const gl_shadersources[] = {
+	// Default shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_DEFAULT_FRAGMENT_SHADER},
 
-	// Wall fragment shader
-	GLSL_SOFTWARE_FRAGMENT_SHADER,
+	// Floor shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
 
-	// Sprite fragment shader
-	GLSL_SOFTWARE_FRAGMENT_SHADER,
+	// Wall shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
 
-	// Model fragment shader
-	GLSL_SOFTWARE_FRAGMENT_SHADER,
+	// Sprite shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
 
-	// Water fragment shader
-	GLSL_WATER_FRAGMENT_SHADER,
+	// Model shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SOFTWARE_FRAGMENT_SHADER},
 
-	// Fog fragment shader
-	GLSL_FOG_FRAGMENT_SHADER,
+	// Model shader + diffuse lighting from above
+	{GLSL_MODEL_LIGHTING_VERTEX_SHADER, GLSL_SOFTWARE_MODEL_LIGHTING_FRAGMENT_SHADER},
 
-	// Sky fragment shader
-	"uniform sampler2D tex;\n"
-	"void main(void) {\n"
-		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st);\n"
-	"}\0",
+	// Water shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_WATER_FRAGMENT_SHADER},
 
-	NULL,
-};
+	// Fog shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_FOG_FRAGMENT_SHADER},
 
-// ======================
-//  Vertex shader macros
-// ======================
+	// Sky shader
+	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SKY_FRAGMENT_SHADER},
 
-//
-// GLSL 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"
-
-static const char *vertex_shaders[] = {
-	// Default vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	// Floor vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	// Wall vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	// Sprite vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	// Model vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	// Water vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	// Fog vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	// Sky vertex shader
-	GLSL_DEFAULT_VERTEX_SHADER,
-
-	NULL,
+	{NULL, NULL},
 };
 
 #endif	// GL_SHADERS
@@ -838,6 +879,9 @@ void SetupGLFunc4(void)
 	pglBufferData = GetGLFunc("glBufferData");
 	pglDeleteBuffers = GetGLFunc("glDeleteBuffers");
 
+	/* 2.0 funcs */
+	pglBlendEquation = GetGLFunc("glBlendEquation");
+
 #ifdef GL_SHADERS
 	pglCreateShader = GetGLFunc("glCreateShader");
 	pglShaderSource = GetGLFunc("glShaderSource");
@@ -846,6 +890,7 @@ void SetupGLFunc4(void)
 	pglGetShaderInfoLog = GetGLFunc("glGetShaderInfoLog");
 	pglDeleteShader = GetGLFunc("glDeleteShader");
 	pglCreateProgram = GetGLFunc("glCreateProgram");
+	pglDeleteProgram = GetGLFunc("glDeleteProgram");
 	pglAttachShader = GetGLFunc("glAttachShader");
 	pglLinkProgram = GetGLFunc("glLinkProgram");
 	pglGetProgramiv = GetGLFunc("glGetProgramiv");
@@ -866,136 +911,63 @@ void SetupGLFunc4(void)
 }
 
 // jimita
-EXPORT boolean HWRAPI(LoadShaders) (void)
+EXPORT boolean HWRAPI(CompileShaders) (void)
 {
 #ifdef GL_SHADERS
-	GLuint gl_vertShader, gl_fragShader;
-	GLint i, result;
+	GLint i;
 
-	if (!pglUseProgram) return false;
+	if (!pglUseProgram)
+		return false;
 
-	gl_customvertexshaders[0] = NULL;
-	gl_customfragmentshaders[0] = NULL;
+	gl_customshaders[SHADER_DEFAULT].vertex = NULL;
+	gl_customshaders[SHADER_DEFAULT].fragment = NULL;
 
-	for (i = 0; vertex_shaders[i] && fragment_shaders[i]; i++)
+	for (i = 0; gl_shadersources[i].vertex && gl_shadersources[i].fragment; i++)
 	{
-		gl_shaderprogram_t *shader;
-		const GLchar* vert_shader = vertex_shaders[i];
-		const GLchar* frag_shader = fragment_shaders[i];
-		boolean custom = ((gl_customvertexshaders[i] || gl_customfragmentshaders[i]) && (i > 0));
-
-		// 18032019
-		if (gl_customvertexshaders[i])
-			vert_shader = gl_customvertexshaders[i];
-		if (gl_customfragmentshaders[i])
-			frag_shader = gl_customfragmentshaders[i];
+		gl_shader_t *shader, *usershader;
+		const GLchar *vert_shader = gl_shadersources[i].vertex;
+		const GLchar *frag_shader = gl_shadersources[i].fragment;
 
-		if (i >= MAXSHADERS)
+		if (i >= HWR_MAXSHADERS)
 			break;
-		if (i >= MAXSHADERPROGRAMS)
-			break;
-
-		shader = &gl_shaderprograms[i];
-		shader->program = 0;
-		shader->custom = custom;
-
-		//
-		// Load and compile vertex shader
-		//
-		gl_vertShader = pglCreateShader(GL_VERTEX_SHADER);
-		if (!gl_vertShader)
-		{
-			GL_MSG_Error("LoadShaders: Error creating vertex shader %d\n", i);
-			continue;
-		}
-
-		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)
-		{
-			GLchar* infoLog;
-			GLint logLength;
-
-			pglGetShaderiv(gl_vertShader, GL_INFO_LOG_LENGTH, &logLength);
-
-			infoLog = malloc(logLength);
-			pglGetShaderInfoLog(gl_vertShader, logLength, NULL, infoLog);
 
-			GL_MSG_Error("LoadShaders: Error compiling vertex shader %d\n%s", i, infoLog);
-			continue;
-		}
-
-		//
-		// Load and compile fragment shader
-		//
-		gl_fragShader = pglCreateShader(GL_FRAGMENT_SHADER);
-		if (!gl_fragShader)
-		{
-			GL_MSG_Error("LoadShaders: Error creating fragment shader %d\n", i);
-			continue;
-		}
-
-		pglShaderSource(gl_fragShader, 1, &frag_shader, NULL);
-		pglCompileShader(gl_fragShader);
+		shader = &gl_shaders[i];
+		usershader = &gl_usershaders[i];
 
-		// check for compile errors
-		pglGetShaderiv(gl_fragShader, GL_COMPILE_STATUS, &result);
-		if (result == GL_FALSE)
-		{
-			GLchar* infoLog;
-			GLint logLength;
+		if (shader->program)
+			pglDeleteProgram(shader->program);
+		if (usershader->program)
+			pglDeleteProgram(usershader->program);
 
-			pglGetShaderiv(gl_fragShader, GL_INFO_LOG_LENGTH, &logLength);
+		shader->program = 0;
+		usershader->program = 0;
 
-			infoLog = malloc(logLength);
-			pglGetShaderInfoLog(gl_fragShader, logLength, NULL, infoLog);
+		if (!Shader_CompileProgram(shader, i, vert_shader, frag_shader))
+			shader->program = 0;
 
-			GL_MSG_Error("LoadShaders: Error compiling fragment shader %d\n%s", i, infoLog);
+		// Compile custom shader
+		if ((i == SHADER_DEFAULT) || !(gl_customshaders[i].vertex || gl_customshaders[i].fragment))
 			continue;
-		}
-
-		shader->program = pglCreateProgram();
-		pglAttachShader(shader->program, gl_vertShader);
-		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);
+		// 18032019
+		if (gl_customshaders[i].vertex)
+			vert_shader = gl_customshaders[i].vertex;
+		if (gl_customshaders[i].fragment)
+			frag_shader = gl_customshaders[i].fragment;
 
-		// couldn't link?
-		if (result != GL_TRUE)
+		if (!Shader_CompileProgram(usershader, i, vert_shader, frag_shader))
 		{
-			shader->program = 0;
-			shader->custom = false;
-			GL_MSG_Error("LoadShaders: Error linking shader program %d\n", i);
-			continue;
+			GL_MSG_Warning("CompileShaders: Could not compile custom shader program for %s\n", HWR_GetShaderName(i));
+			usershader->program = 0;
 		}
+	}
 
-		// 13062019
-#define GETUNI(uniform) pglGetUniformLocation(shader->program, uniform);
-
-		// lighting
-		shader->uniforms[gluniform_poly_color] = GETUNI("poly_color");
-		shader->uniforms[gluniform_tint_color] = GETUNI("tint_color");
-		shader->uniforms[gluniform_fade_color] = GETUNI("fade_color");
-		shader->uniforms[gluniform_lighting] = GETUNI("lighting");
-		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");
+	SetShader(SHADER_DEFAULT);
 
-#undef GETUNI
-	}
-#endif
 	return true;
+#else
+	return false;
+#endif
 }
 
 //
@@ -1023,25 +995,34 @@ EXPORT void HWRAPI(SetShaderInfo) (hwdshaderinfo_t info, INT32 value)
 //
 // Custom shader loading
 //
-EXPORT void HWRAPI(LoadCustomShader) (int number, char *shader, size_t size, boolean fragment)
+EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boolean isfragment)
 {
 #ifdef GL_SHADERS
-	if (!pglUseProgram) return;
-	if (number < 1 || number > MAXSHADERS)
-		I_Error("LoadCustomShader(): cannot load shader %d (max %d)", number, MAXSHADERS);
+	shadersource_t *shader;
 
-	if (fragment)
-	{
-		gl_customfragmentshaders[number] = malloc(size+1);
-		strncpy(gl_customfragmentshaders[number], shader, size);
-		gl_customfragmentshaders[number][size] = 0;
+	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
-	{
-		gl_customvertexshaders[number] = malloc(size+1);
-		strncpy(gl_customvertexshaders[number], shader, size);
-		gl_customvertexshaders[number][size] = 0;
-	}
+		COPYSHADER(vertex)
+
 #else
 	(void)number;
 	(void)shader;
@@ -1050,29 +1031,45 @@ EXPORT void HWRAPI(LoadCustomShader) (int number, char *shader, size_t size, boo
 #endif
 }
 
-EXPORT boolean HWRAPI(InitCustomShaders) (void)
-{
-#ifdef GL_SHADERS
-	KillShaders();
-	return LoadShaders();
-#endif
-}
-
-EXPORT void HWRAPI(SetShader) (int shader)
+EXPORT void HWRAPI(SetShader) (int type)
 {
 #ifdef GL_SHADERS
-	if (gl_allowshaders)
+	if (gl_allowshaders != HWD_SHADEROPTION_OFF)
 	{
-		if ((GLuint)shader != gl_currentshaderprogram)
+		gl_shader_t *shader = gl_shaderstate.current;
+
+		// 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 ((shader == NULL) || (GLuint)type != gl_shaderstate.type)
 		{
-			gl_currentshaderprogram = shader;
-			gl_shaderprogramchanged = true;
+			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.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)shader;
+	(void)type;
 #endif
 	gl_shadersenabled = false;
 }
@@ -1080,16 +1077,34 @@ EXPORT void HWRAPI(SetShader) (int shader)
 EXPORT void HWRAPI(UnSetShader) (void)
 {
 #ifdef GL_SHADERS
-	gl_shadersenabled = false;
-	gl_currentshaderprogram = 0;
-	if (!pglUseProgram) return;
-	pglUseProgram(0);
+	gl_shaderstate.current = NULL;
+	gl_shaderstate.type = 0;
+	gl_shaderstate.program = 0;
+
+	if (pglUseProgram)
+		pglUseProgram(0);
 #endif
+
+	gl_shadersenabled = false;
 }
 
-EXPORT void HWRAPI(KillShaders) (void)
+EXPORT void HWRAPI(CleanShaders) (void)
 {
-	// unused.........................
+	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;
+	}
 }
 
 // -----------------+
@@ -1200,7 +1215,7 @@ void SetModelView(GLint w, GLint h)
 	pglLoadIdentity();
 
 	GLPerspective(fov, ASPECT_RATIO);
-	//pglScalef(1.0f, 320.0f/200.0f, 1.0f);  // gr_scalefrustum (ORIGINAL_ASPECT)
+	//pglScalef(1.0f, 320.0f/200.0f, 1.0f);  // gl_scalefrustum (ORIGINAL_ASPECT)
 
 	// added for new coronas' code (without depth buffer)
 	pglGetIntegerv(GL_VIEWPORT, viewport);
@@ -1227,6 +1242,7 @@ void SetStates(void)
 
 	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
+	pglEnable(GL_ALPHA_TEST);
 	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 
 	//pglBlendFunc(GL_ONE, GL_ZERO); // copy pixel to frame buffer (opaque)
@@ -1269,6 +1285,37 @@ void SetStates(void)
 }
 
 
+// -----------------+
+// DeleteTexture    : Deletes a texture from the GPU and frees its data
+// -----------------+
+EXPORT void HWRAPI(DeleteTexture) (GLMipmap_t *pTexInfo)
+{
+	FTextureInfo *head = TexCacheHead;
+
+	if (!pTexInfo)
+		return;
+	else if (pTexInfo->downloaded)
+		pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+
+	while (head)
+	{
+		if (head->downloaded == pTexInfo->downloaded)
+		{
+			if (head->next)
+				head->next->prev = head->prev;
+			if (head->prev)
+				head->prev->next = head->next;
+			free(head);
+			break;
+		}
+
+		head = head->next;
+	}
+
+	pTexInfo->downloaded = 0;
+}
+
+
 // -----------------+
 // Flush            : flush OpenGL textures
 //                  : Clear list of downloaded mipmaps
@@ -1277,15 +1324,25 @@ void Flush(void)
 {
 	//GL_DBG_Printf ("HWR_Flush()\n");
 
-	while (gr_cachehead)
+	while (TexCacheHead)
 	{
-		if (gr_cachehead->downloaded)
-			pglDeleteTextures(1, (GLuint *)&gr_cachehead->downloaded);
-		gr_cachehead->downloaded = 0;
-		gr_cachehead = gr_cachehead->nextmipmap;
+		FTextureInfo *pTexInfo = TexCacheHead;
+		GLMipmap_t *texture = pTexInfo->texture;
+
+		if (pTexInfo->downloaded)
+		{
+			pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+			pTexInfo->downloaded = 0;
+		}
+
+		if (texture)
+			texture->downloaded = 0;
+
+		TexCacheHead = pTexInfo->next;
+		free(pTexInfo);
 	}
-	gr_cachetail = gr_cachehead = NULL; //Hurdler: well, gr_cachehead is already NULL
 
+	TexCacheTail = TexCacheHead = NULL; //Hurdler: well, TexCacheHead is already NULL
 	tex_downloaded = 0;
 }
 
@@ -1486,64 +1543,110 @@ EXPORT void HWRAPI(Draw2DLine) (F2DCoord * v1,
 	pglEnable(GL_TEXTURE_2D);
 }
 
+
+// -----------------+
+// SetBlend         : Set render mode
+// -----------------+
+// PF_Masked - we could use an ALPHA_TEST of GL_EQUAL, and alpha ref of 0,
+//             is it faster when pixels are discarded ?
+
 static void Clamp2D(GLenum pname)
 {
 	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP); // fallback clamp
 	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP_TO_EDGE);
 }
 
+static void SetBlendEquation(GLenum mode)
+{
+	if (pglBlendEquation)
+		pglBlendEquation(mode);
+}
+
+static void SetBlendMode(FBITFIELD flags)
+{
+	// Set blending function
+	switch (flags)
+	{
+		case PF_Translucent & PF_Blending:
+			pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
+			break;
+		case PF_Masked & PF_Blending:
+			// Hurdler: does that mean lighting is only made by alpha src?
+			// it sounds ok, but not for polygonsmooth
+			pglBlendFunc(GL_SRC_ALPHA, GL_ZERO);                // 0 alpha = holes in texture
+			break;
+		case PF_Additive & PF_Blending:
+		case PF_Subtractive & PF_Blending:
+		case PF_ReverseSubtract & PF_Blending:
+		case PF_Environment & PF_Blending:
+			pglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+			break;
+		case PF_AdditiveSource & PF_Blending:
+			pglBlendFunc(GL_SRC_ALPHA, GL_ONE); // src * alpha + dest
+			break;
+		case PF_Multiplicative & PF_Blending:
+			pglBlendFunc(GL_DST_COLOR, GL_ZERO);
+			break;
+		case PF_Fog & PF_Fog:
+			// Sryder: Fog
+			// multiplies input colour by input alpha, and destination colour by input colour, then adds them
+			pglBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR);
+			break;
+		default: // must be 0, otherwise it's an error
+			// No blending
+			pglBlendFunc(GL_ONE, GL_ZERO);   // the same as no blending
+			break;
+	}
+
+	// Set blending equation
+	switch (flags)
+	{
+		case PF_Subtractive & PF_Blending:
+			SetBlendEquation(GL_FUNC_SUBTRACT);
+			break;
+		case PF_ReverseSubtract & PF_Blending:
+			// good for shadow
+			// not really but what else ?
+			SetBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
+			break;
+		default:
+			SetBlendEquation(GL_FUNC_ADD);
+			break;
+	}
+
+	// Alpha test
+	switch (flags)
+	{
+		case PF_Masked & PF_Blending:
+			pglAlphaFunc(GL_GREATER, 0.5f);
+			break;
+		case PF_Translucent & PF_Blending:
+		case PF_Additive & PF_Blending:
+		case PF_AdditiveSource & PF_Blending:
+		case PF_Subtractive & PF_Blending:
+		case PF_ReverseSubtract & PF_Blending:
+		case PF_Environment & PF_Blending:
+		case PF_Multiplicative & PF_Blending:
+			pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+			break;
+		case PF_Fog & PF_Fog:
+			pglAlphaFunc(GL_ALWAYS, 0.0f); // Don't discard zero alpha fragments
+			break;
+		default:
+			pglAlphaFunc(GL_GREATER, 0.5f);
+			break;
+	}
+}
 
-// -----------------+
-// SetBlend         : Set render mode
-// -----------------+
-// PF_Masked - we could use an ALPHA_TEST of GL_EQUAL, and alpha ref of 0,
-//             is it faster when pixels are discarded ?
 EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 {
 	FBITFIELD Xor;
 	Xor = CurrentPolyFlags^PolyFlags;
-	if (Xor & (PF_Blending|PF_RemoveYWrap|PF_ForceWrapX|PF_ForceWrapY|PF_Occlude|PF_NoTexture|PF_Modulated|PF_NoDepthTest|PF_Decal|PF_Invisible|PF_NoAlphaTest))
+	if (Xor & (PF_Blending|PF_RemoveYWrap|PF_ForceWrapX|PF_ForceWrapY|PF_Occlude|PF_NoTexture|PF_Modulated|PF_NoDepthTest|PF_Decal|PF_Invisible))
 	{
-		if (Xor&(PF_Blending)) // if blending mode must be changed
-		{
-			switch (PolyFlags & PF_Blending) {
-				case PF_Translucent & PF_Blending:
-					pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Masked & PF_Blending:
-					// Hurdler: does that mean lighting is only made by alpha src?
-					// it sounds ok, but not for polygonsmooth
-					pglBlendFunc(GL_SRC_ALPHA, GL_ZERO);                // 0 alpha = holes in texture
-					pglAlphaFunc(GL_GREATER, 0.5f);
-					break;
-				case PF_Additive & PF_Blending:
-					pglBlendFunc(GL_SRC_ALPHA, GL_ONE);                 // src * alpha + dest
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Environment & PF_Blending:
-					pglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Substractive & PF_Blending:
-					// good for shadow
-					// not really but what else ?
-					pglBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Fog & PF_Fog:
-					// Sryder: Fog
-					// multiplies input colour by input alpha, and destination colour by input colour, then adds them
-					pglBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR);
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				default : // must be 0, otherwise it's an error
-					// No blending
-					pglBlendFunc(GL_ONE, GL_ZERO);   // the same as no blending
-					pglAlphaFunc(GL_GREATER, 0.5f);
-					break;
-			}
-		}
+		if (Xor & PF_Blending) // if blending mode must be changed
+			SetBlendMode(PolyFlags & PF_Blending);
+
 		if (Xor & PF_NoAlphaTest)
 		{
 			if (PolyFlags & PF_NoAlphaTest)
@@ -1560,7 +1663,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 				pglDisable(GL_POLYGON_OFFSET_FILL);
 		}
 
-		if (Xor&PF_NoDepthTest)
+		if (Xor & PF_NoDepthTest)
 		{
 			if (PolyFlags & PF_NoDepthTest)
 				pglDepthFunc(GL_ALWAYS); //pglDisable(GL_DEPTH_TEST);
@@ -1568,25 +1671,25 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 				pglDepthFunc(GL_LEQUAL); //pglEnable(GL_DEPTH_TEST);
 		}
 
-		if (Xor&PF_RemoveYWrap)
+		if (Xor & PF_RemoveYWrap)
 		{
 			if (PolyFlags & PF_RemoveYWrap)
 				Clamp2D(GL_TEXTURE_WRAP_T);
 		}
 
-		if (Xor&PF_ForceWrapX)
+		if (Xor & PF_ForceWrapX)
 		{
 			if (PolyFlags & PF_ForceWrapX)
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 		}
 
-		if (Xor&PF_ForceWrapY)
+		if (Xor & PF_ForceWrapY)
 		{
 			if (PolyFlags & PF_ForceWrapY)
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 		}
 
-		if (Xor&PF_Modulated)
+		if (Xor & PF_Modulated)
 		{
 #if defined (__unix__) || defined (UNIXCOMMON)
 			if (oglflags & GLF_NOTEXENV)
@@ -1639,7 +1742,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 // -----------------+
 // UpdateTexture    : Updates the texture data.
 // -----------------+
-EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
+EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
 {
 	// Download a mipmap
 	boolean updatemipmap = true;
@@ -1657,15 +1760,15 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 	else
 		texnum = pTexInfo->downloaded;
 
-	//GL_DBG_Printf ("DownloadMipmap %d %x\n",(INT32)texnum,pTexInfo->grInfo.data);
+	//GL_DBG_Printf ("DownloadMipmap %d %x\n",(INT32)texnum,pTexInfo->data);
 
 	w = pTexInfo->width;
 	h = pTexInfo->height;
 
-	if ((pTexInfo->grInfo.format == GR_TEXFMT_P_8) ||
-		(pTexInfo->grInfo.format == GR_TEXFMT_AP_88))
+	if ((pTexInfo->format == GL_TEXFMT_P_8) ||
+		(pTexInfo->format == GL_TEXFMT_AP_88))
 	{
-		const GLubyte *pImgData = (const GLubyte *)pTexInfo->grInfo.data;
+		const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
 		INT32 i, j;
 
 		for (j = 0; j < h; j++)
@@ -1691,7 +1794,7 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 
 				pImgData++;
 
-				if (pTexInfo->grInfo.format == GR_TEXFMT_AP_88)
+				if (pTexInfo->format == GL_TEXFMT_AP_88)
 				{
 					if (!(pTexInfo->flags & TF_CHROMAKEYED))
 						tex[w*j+i].s.alpha = *pImgData;
@@ -1701,15 +1804,15 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 			}
 		}
 	}
-	else if (pTexInfo->grInfo.format == GR_RGBA)
+	else if (pTexInfo->format == GL_TEXFMT_RGBA)
 	{
 		// corona test : passed as ARGB 8888, which is not in glide formats
 		// Hurdler: not used for coronas anymore, just for dynamic lighting
-		ptex = pTexInfo->grInfo.data;
+		ptex = pTexInfo->data;
 	}
-	else if (pTexInfo->grInfo.format == GR_TEXFMT_ALPHA_INTENSITY_88)
+	else if (pTexInfo->format == GL_TEXFMT_ALPHA_INTENSITY_88)
 	{
-		const GLubyte *pImgData = (const GLubyte *)pTexInfo->grInfo.data;
+		const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
 		INT32 i, j;
 
 		for (j = 0; j < h; j++)
@@ -1725,9 +1828,9 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 			}
 		}
 	}
-	else if (pTexInfo->grInfo.format == GR_TEXFMT_ALPHA_8) // Used for fade masks
+	else if (pTexInfo->format == GL_TEXFMT_ALPHA_8) // Used for fade masks
 	{
-		const GLubyte *pImgData = (const GLubyte *)pTexInfo->grInfo.data;
+		const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
 		INT32 i, j;
 
 		for (j = 0; j < h; j++)
@@ -1743,7 +1846,7 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 		}
 	}
 	else
-		GL_MSG_Warning ("SetTexture(bad format) %ld\n", pTexInfo->grInfo.format);
+		GL_MSG_Warning ("SetTexture(bad format) %ld\n", pTexInfo->format);
 
 	// the texture number was already generated by pglGenTextures
 	pglBindTexture(GL_TEXTURE_2D, texnum);
@@ -1761,7 +1864,7 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
 	}
 
-	if (pTexInfo->grInfo.format == GR_TEXFMT_ALPHA_INTENSITY_88)
+	if (pTexInfo->format == GL_TEXFMT_ALPHA_INTENSITY_88)
 	{
 		//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		if (MipMap)
@@ -1782,7 +1885,7 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 				pglTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		}
 	}
-	else if (pTexInfo->grInfo.format == GR_TEXFMT_ALPHA_8)
+	else if (pTexInfo->format == GL_TEXFMT_ALPHA_8)
 	{
 		//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		if (MipMap)
@@ -1841,7 +1944,7 @@ EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *pTexInfo)
 // -----------------+
 // SetTexture       : The mipmap becomes the current texture source
 // -----------------+
-EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
+EXPORT void HWRAPI(SetTexture) (GLMipmap_t *pTexInfo)
 {
 	if (!pTexInfo)
 	{
@@ -1858,54 +1961,54 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 	}
 	else
 	{
+		FTextureInfo *newTex = calloc(1, sizeof (*newTex));
+
 		UpdateTexture(pTexInfo);
-		pTexInfo->nextmipmap = NULL;
-		if (gr_cachetail)
-		{ // insertion at the tail
-			gr_cachetail->nextmipmap = pTexInfo;
-			gr_cachetail = pTexInfo;
-		}
-		else // initialization of the linked list
-			gr_cachetail = gr_cachehead =  pTexInfo;
-	}
-}
 
-static void *Shader_Load(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade)
-{
-#ifdef GL_SHADERS
-	if (gl_shadersenabled && pglUseProgram)
-	{
-		gl_shaderprogram_t *shader = &gl_shaderprograms[gl_currentshaderprogram];
-		if (shader->program)
+		newTex->texture = pTexInfo;
+		newTex->downloaded = (UINT32)pTexInfo->downloaded;
+		newTex->width = (UINT32)pTexInfo->width;
+		newTex->height = (UINT32)pTexInfo->height;
+		newTex->format = (UINT32)pTexInfo->format;
+
+		// insertion at the tail
+		if (TexCacheTail)
 		{
-			if (gl_shaderprogramchanged)
-			{
-				pglUseProgram(gl_shaderprograms[gl_currentshaderprogram].program);
-				gl_shaderprogramchanged = false;
-			}
-			Shader_SetUniforms(Surface, poly, tint, fade);
-			return shader;
+			newTex->prev = TexCacheTail;
+			TexCacheTail->next = newTex;
+			TexCacheTail = newTex;
 		}
-		else
-			pglUseProgram(0);
+		else // initialization of the linked list
+			TexCacheTail = TexCacheHead = newTex;
 	}
-#else
-	(void)Surface;
-	(void)poly;
-	(void)tint;
-	(void)fade;
-#endif
-	return NULL;
 }
 
 static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade)
 {
 #ifdef GL_SHADERS
-	if (gl_shadersenabled)
+	gl_shader_t *shader = gl_shaderstate.current;
+
+	if (gl_shadersenabled && (shader != NULL) && pglUseProgram)
 	{
-		gl_shaderprogram_t *shader = &gl_shaderprograms[gl_currentshaderprogram];
 		if (!shader->program)
+		{
+			pglUseProgram(0);
 			return;
+		}
+
+		if (gl_shaderstate.changed)
+		{
+			pglUseProgram(shader->program);
+			gl_shaderstate.changed = false;
+		}
+
+		// Color uniforms can be left NULL and will be set to white (1.0f, 1.0f, 1.0f, 1.0f)
+		if (poly == NULL)
+			poly = &shader_defaultcolor;
+		if (tint == NULL)
+			tint = &shader_defaultcolor;
+		if (fade == NULL)
+			fade = &shader_defaultcolor;
 
 		#define UNIFORM_1(uniform, a, function) \
 			if (uniform != -1) \
@@ -1927,12 +2030,14 @@ static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAF
 		UNIFORM_4(shader->uniforms[gluniform_poly_color], poly->red, poly->green, poly->blue, poly->alpha, pglUniform4f);
 		UNIFORM_4(shader->uniforms[gluniform_tint_color], tint->red, tint->green, tint->blue, tint->alpha, pglUniform4f);
 		UNIFORM_4(shader->uniforms[gluniform_fade_color], fade->red, fade->green, fade->blue, fade->alpha, pglUniform4f);
+
 		if (Surface != NULL)
 		{
 			UNIFORM_1(shader->uniforms[gluniform_lighting], Surface->LightInfo.light_level, pglUniform1f);
 			UNIFORM_1(shader->uniforms[gluniform_fade_start], Surface->LightInfo.fade_start, pglUniform1f);
 			UNIFORM_1(shader->uniforms[gluniform_fade_end], Surface->LightInfo.fade_end, pglUniform1f);
 		}
+
 		UNIFORM_1(shader->uniforms[gluniform_leveltime], ((float)shader_leveltime) / TICRATE, pglUniform1f);
 
 		#undef UNIFORM_1
@@ -1948,6 +2053,116 @@ 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)
+{
+	GLuint gl_vertShader, gl_fragShader;
+	GLint result;
+
+	//
+	// 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)
+	{
+		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);
+
+	// 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);
+	pglLinkProgram(shader->program);
+
+	// check link status
+	pglGetProgramiv(shader->program, GL_LINK_STATUS, &result);
+
+	// delete the shader objects
+	pglDeleteShader(gl_vertShader);
+	pglDeleteShader(gl_fragShader);
+
+	// couldn't link?
+	if (result != GL_TRUE)
+	{
+		GL_MSG_Error("Shader_CompileProgram: Error linking shader program %s\n", HWR_GetShaderName(i));
+		pglDeleteProgram(shader->program);
+		return false;
+	}
+
+	// 13062019
+#define GETUNI(uniform) pglGetUniformLocation(shader->program, uniform);
+
+	// lighting
+	shader->uniforms[gluniform_poly_color] = GETUNI("poly_color");
+	shader->uniforms[gluniform_tint_color] = GETUNI("tint_color");
+	shader->uniforms[gluniform_fade_color] = GETUNI("fade_color");
+	shader->uniforms[gluniform_lighting] = GETUNI("lighting");
+	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");
+
+#undef GETUNI
+
+	return true;
+}
+
+static void Shader_CompileError(const char *message, GLuint program, INT32 shadernum)
+{
+	GLchar *infoLog = NULL;
+	GLint logLength;
+
+	pglGetShaderiv(program, GL_INFO_LOG_LENGTH, &logLength);
+
+	if (logLength)
+	{
+		infoLog = malloc(logLength);
+		pglGetShaderInfoLog(program, logLength, NULL, infoLog);
+	}
+
+	GL_MSG_Error("Shader_CompileProgram: %s (%s)\n%s", message, HWR_GetShaderName(shadernum), (infoLog ? infoLog : ""));
+
+	if (infoLog)
+		free(infoLog);
+}
+
 // code that is common between DrawPolygon and DrawIndexedTriangles
 // 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)
@@ -2052,7 +2267,7 @@ static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FBITFIELD
 		pglColor4ubv(c);
 	}
 
-	Shader_Load(pSurf, &poly, &tint, &fade);
+	Shader_SetUniforms(pSurf, &poly, &tint, &fade);
 }
 
 // -----------------+
@@ -2087,233 +2302,83 @@ EXPORT void HWRAPI(DrawIndexedTriangles) (FSurfaceInfo *pSurf, FOutVector *pOutV
 	// the DrawPolygon variant of this has some code about polyflags and wrapping here but havent noticed any problems from omitting it?
 }
 
-typedef struct vbo_vertex_s
-{
-	float x, y, z;
-	float u, v;
-	unsigned char r, g, b, a;
-} vbo_vertex_t;
-
-typedef struct
-{
-	int mode;
-	int vertexcount;
-	int vertexindex;
-	int use_texture;
-} GLSkyLoopDef;
-
-typedef struct
-{
-	unsigned int id;
-	int rows, columns;
-	int loopcount;
-	GLSkyLoopDef *loops;
-	vbo_vertex_t *data;
-} GLSkyVBO;
-
 static const boolean gl_ext_arb_vertex_buffer_object = true;
 
-#define NULL_VBO_VERTEX ((vbo_vertex_t*)NULL)
-#define sky_vbo_x (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->x : &vbo->data[0].x)
-#define sky_vbo_u (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->u : &vbo->data[0].u)
-#define sky_vbo_r (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->r : &vbo->data[0].r)
-
-// The texture offset to be applied to the texture coordinates in SkyVertex().
-static int rows, columns;
-static signed char yflip;
-static int texw, texh;
-static boolean foglayer;
-static float delta = 0.0f;
-
-static int gl_sky_detail = 16;
-
-static INT32 lasttex = -1;
-
-#define MAP_COEFF 128.0f
-
-static void SkyVertex(vbo_vertex_t *vbo, int r, int c)
-{
-	const float radians = (float)(M_PIl / 180.0f);
-	const float scale = 10000.0f;
-	const float maxSideAngle = 60.0f;
-
-	float topAngle = (c / (float)columns * 360.0f);
-	float sideAngle = (maxSideAngle * (rows - r) / rows);
-	float height = (float)(sin(sideAngle * radians));
-	float realRadius = (float)(scale * cos(sideAngle * radians));
-	float x = (float)(realRadius * cos(topAngle * radians));
-	float y = (!yflip) ? scale * height : -scale * height;
-	float z = (float)(realRadius * sin(topAngle * radians));
-	float timesRepeat = (4 * (256.0f / texw));
-	if (fpclassify(timesRepeat) == FP_ZERO)
-		timesRepeat = 1.0f;
-
-	if (!foglayer)
-	{
-		vbo->r = 255;
-		vbo->g = 255;
-		vbo->b = 255;
-		vbo->a = (r == 0 ? 0 : 255);
-
-		// And the texture coordinates.
-		vbo->u = (-timesRepeat * c / (float)columns);
-		if (!yflip)	// Flipped Y is for the lower hemisphere.
-			vbo->v = (r / (float)rows) + 0.5f;
-		else
-			vbo->v = 1.0f + ((rows - r) / (float)rows) + 0.5f;
-	}
-
-	if (r != 4)
-	{
-		y += 300.0f;
-	}
-
-	// And finally the vertex.
-	vbo->x = x;
-	vbo->y = y + delta;
-	vbo->z = z;
-}
+#define NULL_VBO_VERTEX ((gl_skyvertex_t*)NULL)
+#define sky_vbo_x (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->x : &sky->data[0].x)
+#define sky_vbo_u (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->u : &sky->data[0].u)
+#define sky_vbo_r (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->r : &sky->data[0].r)
 
-static GLSkyVBO sky_vbo;
-
-static void gld_BuildSky(int row_count, int col_count)
-{
-	int c, r;
-	vbo_vertex_t *vertex_p;
-	int vertex_count = 2 * row_count * (col_count * 2 + 2) + col_count * 2;
-
-	GLSkyVBO *vbo = &sky_vbo;
-
-	if ((vbo->columns != col_count) || (vbo->rows != row_count))
-	{
-		free(vbo->loops);
-		free(vbo->data);
-		memset(vbo, 0, sizeof(&vbo));
-	}
-
-	if (!vbo->data)
-	{
-		memset(vbo, 0, sizeof(&vbo));
-		vbo->loops = malloc((row_count * 2 + 2) * sizeof(vbo->loops[0]));
-		// create vertex array
-		vbo->data = malloc(vertex_count * sizeof(vbo->data[0]));
-	}
-
-	vbo->columns = col_count;
-	vbo->rows = row_count;
-
-	vertex_p = &vbo->data[0];
-	vbo->loopcount = 0;
-
-	for (yflip = 0; yflip < 2; yflip++)
-	{
-		vbo->loops[vbo->loopcount].mode = GL_TRIANGLE_FAN;
-		vbo->loops[vbo->loopcount].vertexindex = vertex_p - &vbo->data[0];
-		vbo->loops[vbo->loopcount].vertexcount = col_count;
-		vbo->loops[vbo->loopcount].use_texture = false;
-		vbo->loopcount++;
-
-		delta = 0.0f;
-		foglayer = true;
-		for (c = 0; c < col_count; c++)
-		{
-			SkyVertex(vertex_p, 1, c);
-			vertex_p->r = 255;
-			vertex_p->g = 255;
-			vertex_p->b = 255;
-			vertex_p->a = 255;
-			vertex_p++;
-		}
-		foglayer = false;
-
-		delta = (yflip ? 5.0f : -5.0f) / MAP_COEFF;
-
-		for (r = 0; r < row_count; r++)
-		{
-			vbo->loops[vbo->loopcount].mode = GL_TRIANGLE_STRIP;
-			vbo->loops[vbo->loopcount].vertexindex = vertex_p - &vbo->data[0];
-			vbo->loops[vbo->loopcount].vertexcount = 2 * col_count + 2;
-			vbo->loops[vbo->loopcount].use_texture = true;
-			vbo->loopcount++;
-
-			for (c = 0; c <= col_count; c++)
-			{
-				SkyVertex(vertex_p++, r + (yflip ? 1 : 0), (c ? c : 0));
-				SkyVertex(vertex_p++, r + (yflip ? 0 : 1), (c ? c : 0));
-			}
-		}
-	}
-}
-
-//-----------------------------------------------------------------------------
-//
-//
-//
-//-----------------------------------------------------------------------------
-
-static void RenderDome(INT32 skytexture)
+EXPORT void HWRAPI(RenderSkyDome) (gl_sky_t *sky)
 {
 	int i, j;
-	int vbosize;
-	GLSkyVBO *vbo = &sky_vbo;
 
-	rows = 4;
-	columns = 4 * gl_sky_detail;
-
-	vbosize = 2 * rows * (columns * 2 + 2) + columns * 2;
+	Shader_SetUniforms(NULL, NULL, NULL, NULL);
 
 	// Build the sky dome! Yes!
-	if (lasttex != skytexture)
+	if (sky->rebuild)
 	{
 		// delete VBO when already exists
 		if (gl_ext_arb_vertex_buffer_object)
 		{
-			if (vbo->id)
-				pglDeleteBuffers(1, &vbo->id);
+			if (sky->vbo)
+				pglDeleteBuffers(1, &sky->vbo);
 		}
 
-		lasttex = skytexture;
-		gld_BuildSky(rows, columns);
-
 		if (gl_ext_arb_vertex_buffer_object)
 		{
 			// generate a new VBO and get the associated ID
-			pglGenBuffers(1, &vbo->id);
+			pglGenBuffers(1, &sky->vbo);
 
 			// bind VBO in order to use
-			pglBindBuffer(GL_ARRAY_BUFFER, vbo->id);
+			pglBindBuffer(GL_ARRAY_BUFFER, sky->vbo);
 
 			// upload data to VBO
-			pglBufferData(GL_ARRAY_BUFFER, vbosize * sizeof(vbo->data[0]), vbo->data, GL_STATIC_DRAW);
+			pglBufferData(GL_ARRAY_BUFFER, sky->vertex_count * sizeof(sky->data[0]), sky->data, GL_STATIC_DRAW);
 		}
+
+		sky->rebuild = false;
 	}
 
 	// bind VBO in order to use
 	if (gl_ext_arb_vertex_buffer_object)
-		pglBindBuffer(GL_ARRAY_BUFFER, vbo->id);
+		pglBindBuffer(GL_ARRAY_BUFFER, sky->vbo);
 
 	// activate and specify pointers to arrays
-	pglVertexPointer(3, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_x);
-	pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_u);
-	pglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vbo->data[0]), sky_vbo_r);
+	pglVertexPointer(3, GL_FLOAT, sizeof(sky->data[0]), sky_vbo_x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(sky->data[0]), sky_vbo_u);
+	pglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(sky->data[0]), sky_vbo_r);
 
 	// activate color arrays
 	pglEnableClientState(GL_COLOR_ARRAY);
 
 	// set transforms
-	pglScalef(1.0f, (float)texh / 230.0f, 1.0f);
+	pglScalef(1.0f, (float)sky->height / 200.0f, 1.0f);
 	pglRotatef(270.0f, 0.0f, 1.0f, 0.0f);
 
 	for (j = 0; j < 2; j++)
 	{
-		for (i = 0; i < vbo->loopcount; i++)
+		for (i = 0; i < sky->loopcount; i++)
 		{
-			GLSkyLoopDef *loop = &vbo->loops[i];
+			gl_skyloopdef_t *loop = &sky->loops[i];
+			unsigned int mode = 0;
 
 			if (j == 0 ? loop->use_texture : !loop->use_texture)
 				continue;
 
-			pglDrawArrays(loop->mode, loop->vertexindex, loop->vertexcount);
+			switch (loop->mode)
+			{
+				case HWD_SKYLOOP_FAN:
+					mode = GL_TRIANGLE_FAN;
+					break;
+				case HWD_SKYLOOP_STRIP:
+					mode = GL_TRIANGLE_STRIP;
+					break;
+				default:
+					continue;
+			}
+
+			pglDrawArrays(mode, loop->vertexindex, loop->vertexcount);
 		}
 	}
 
@@ -2328,16 +2393,6 @@ static void RenderDome(INT32 skytexture)
 	pglDisableClientState(GL_COLOR_ARRAY);
 }
 
-EXPORT void HWRAPI(RenderSkyDome) (INT32 tex, INT32 texture_width, INT32 texture_height, FTransform transform)
-{
-	SetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated);
-	SetTransform(&transform);
-	texw = texture_width;
-	texh = texture_height;
-	RenderDome(tex);
-	SetBlend(0);
-}
-
 // ==========================================================================
 //
 // ==========================================================================
@@ -2350,15 +2405,7 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 			break;
 
 		case HWD_SET_SHADERS:
-			switch (Value)
-			{
-				case 1:
-					gl_allowshaders = true;
-					break;
-				default:
-					gl_allowshaders = false;
-					break;
-			}
+			gl_allowshaders = (hwdshaderoption_t)Value;
 			break;
 
 		case HWD_SET_TEXTUREFILTERMODE:
@@ -2626,6 +2673,9 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 
 	boolean useTinyFrames;
 
+	boolean useVBO = true;
+
+	FBITFIELD flags;
 	int i;
 
 	// Because otherwise, scaling the screen negatively vertically breaks the lighting
@@ -2660,38 +2710,39 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	poly.alpha  = byte2float[Surface->PolyColor.s.alpha];
 
 #ifdef GL_LIGHT_MODEL_AMBIENT
-	if (model_lighting && (!gl_shadersenabled)) // doesn't work with shaders anyway
-	{
-		ambient[0] = poly.red;
-		ambient[1] = poly.green;
-		ambient[2] = poly.blue;
-		ambient[3] = poly.alpha;
-
-		diffuse[0] = poly.red;
-		diffuse[1] = poly.green;
-		diffuse[2] = poly.blue;
-		diffuse[3] = poly.alpha;
-
-		if (ambient[0] > 0.75f)
-			ambient[0] = 0.75f;
-		if (ambient[1] > 0.75f)
-			ambient[1] = 0.75f;
-		if (ambient[2] > 0.75f)
-			ambient[2] = 0.75f;
-
-		pglLightfv(GL_LIGHT0, GL_POSITION, LightPos);
+	if (model_lighting)
+	{
+		if (!gl_shadersenabled)
+		{
+			ambient[0] = poly.red;
+			ambient[1] = poly.green;
+			ambient[2] = poly.blue;
+			ambient[3] = poly.alpha;
+
+			diffuse[0] = poly.red;
+			diffuse[1] = poly.green;
+			diffuse[2] = poly.blue;
+			diffuse[3] = poly.alpha;
+
+			if (ambient[0] > 0.75f)
+				ambient[0] = 0.75f;
+			if (ambient[1] > 0.75f)
+				ambient[1] = 0.75f;
+			if (ambient[2] > 0.75f)
+				ambient[2] = 0.75f;
+
+			pglLightfv(GL_LIGHT0, GL_POSITION, LightPos);
+
+			pglEnable(GL_LIGHTING);
+			pglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
+			pglMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
+		}
 		pglShadeModel(GL_SMOOTH);
-
-		pglEnable(GL_LIGHTING);
-		pglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
-		pglMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
 	}
 #endif
 	else
 		pglColor4ubv((GLubyte*)&Surface->PolyColor.s);
 
-	SetBlend((poly.alpha < 1 ? PF_Translucent : (PF_Masked|PF_Occlude))|PF_Modulated);
-
 	tint.red   = byte2float[Surface->TintColor.s.red];
 	tint.green = byte2float[Surface->TintColor.s.green];
 	tint.blue  = byte2float[Surface->TintColor.s.blue];
@@ -2702,7 +2753,14 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	fade.blue  = byte2float[Surface->FadeColor.s.blue];
 	fade.alpha = byte2float[Surface->FadeColor.s.alpha];
 
-	Shader_Load(Surface, &poly, &tint, &fade);
+	flags = (Surface->PolyFlags | PF_Modulated);
+	if (Surface->PolyFlags & (PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative))
+		flags |= PF_Occlude;
+	else if (Surface->PolyColor.s.alpha == 0xFF)
+		flags |= (PF_Occlude | PF_Masked);
+
+	SetBlend(flags);
+	Shader_SetUniforms(Surface, &poly, &tint, &fade);
 
 	pglEnable(GL_CULL_FACE);
 	pglEnable(GL_NORMALIZE);
@@ -2766,6 +2824,15 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	if (useTinyFrames)
 		pglScalef(1 / 64.0f, 1 / 64.0f, 1 / 64.0f);
 
+	// Don't use the VBO if it does not have the correct texture coordinates.
+	// (Can happen when model uses a sprite as a texture and the sprite changes)
+	// Comparing floats with the != operator here should be okay because they
+	// are just copies of glpatches' max_s and max_t values.
+	// Instead of the != operator, memcmp is used to avoid a compiler warning.
+	if (memcmp(&(model->vbo_max_s), &(model->max_s), sizeof(model->max_s)) != 0 ||
+		memcmp(&(model->vbo_max_t), &(model->max_t), sizeof(model->max_t)) != 0)
+		useVBO = false;
+
 	pglEnableClientState(GL_NORMAL_ARRAY);
 
 	for (i = 0; i < model->numMeshes; i++)
@@ -2782,13 +2849,23 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 
 			if (!nextframe || fpclassify(pol) == FP_ZERO)
 			{
-				pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
-				pglVertexPointer(3, GL_SHORT, sizeof(vbotiny_t), BUFFER_OFFSET(0));
-				pglNormalPointer(GL_BYTE, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short)*3));
-				pglTexCoordPointer(2, GL_FLOAT, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short) * 3 + sizeof(char) * 6));
+				if (useVBO)
+				{
+					pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+					pglVertexPointer(3, GL_SHORT, sizeof(vbotiny_t), BUFFER_OFFSET(0));
+					pglNormalPointer(GL_BYTE, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short)*3));
+					pglTexCoordPointer(2, GL_FLOAT, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short) * 3 + sizeof(char) * 6));
 
-				pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
-				pglBindBuffer(GL_ARRAY_BUFFER, 0);
+					pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+					pglBindBuffer(GL_ARRAY_BUFFER, 0);
+				}
+				else
+				{
+					pglVertexPointer(3, GL_SHORT, 0, frame->vertices);
+					pglNormalPointer(GL_BYTE, 0, frame->normals);
+					pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+					pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+				}
 			}
 			else
 			{
@@ -2824,21 +2901,25 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 
 			if (!nextframe || fpclassify(pol) == FP_ZERO)
 			{
-				// Zoom! Take advantage of just shoving the entire arrays to the GPU.
-/*				pglVertexPointer(3, GL_FLOAT, 0, frame->vertices);
-				pglNormalPointer(GL_FLOAT, 0, frame->normals);
-				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
-				pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);*/
-
-				pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
-				pglVertexPointer(3, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(0));
-				pglNormalPointer(GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 3));
-				pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 6));
-
-				pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
-				// No tinyframes, no mesh indices
-				//pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
-				pglBindBuffer(GL_ARRAY_BUFFER, 0);
+				if (useVBO)
+				{
+					pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+					pglVertexPointer(3, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(0));
+					pglNormalPointer(GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 3));
+					pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 6));
+
+					pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+					// No tinyframes, no mesh indices
+					//pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+					pglBindBuffer(GL_ARRAY_BUFFER, 0);
+				}
+				else
+				{
+					pglVertexPointer(3, GL_FLOAT, 0, frame->vertices);
+					pglNormalPointer(GL_FLOAT, 0, frame->normals);
+					pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+					pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+				}
 			}
 			else
 			{
@@ -2874,9 +2955,10 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	pglDisable(GL_NORMALIZE);
 
 #ifdef GL_LIGHT_MODEL_AMBIENT
-	if (model_lighting && (!gl_shadersenabled))
+	if (model_lighting)
 	{
-		pglDisable(GL_LIGHTING);
+		if (!gl_shadersenabled)
+			pglDisable(GL_LIGHTING);
 		pglShadeModel(GL_FLAT);
 	}
 #endif
@@ -2961,7 +3043,7 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 
 EXPORT INT32  HWRAPI(GetTextureUsed) (void)
 {
-	FTextureInfo *tmp = gr_cachehead;
+	FTextureInfo *tmp = TexCacheHead;
 	INT32 res = 0;
 
 	while (tmp)
@@ -2970,19 +3052,15 @@ EXPORT INT32  HWRAPI(GetTextureUsed) (void)
 		// I don't know which one the game actually _uses_ but this
 		// follows format2bpp in hw_cache.c
 		int bpp = 1;
-		int format = tmp->grInfo.format;
-		if (format == GR_RGBA)
+		int format = tmp->format;
+		if (format == GL_TEXFMT_RGBA)
 			bpp = 4;
-		else if (format == GR_TEXFMT_RGB_565
-			|| format == GR_TEXFMT_ARGB_1555
-			|| format == GR_TEXFMT_ARGB_4444
-			|| format == GR_TEXFMT_ALPHA_INTENSITY_88
-			|| format == GR_TEXFMT_AP_88)
+		else if (format == GL_TEXFMT_ALPHA_INTENSITY_88 || format == GL_TEXFMT_AP_88)
 			bpp = 2;
 
 		// Add it up!
 		res += tmp->height*tmp->width*bpp;
-		tmp = tmp->nextmipmap;
+		tmp = tmp->next;
 	}
 
 	return res;
@@ -3251,7 +3329,7 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 
 	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
-	SetBlend(PF_Modulated|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	SetBlend(PF_Modulated|PF_NoDepthTest);
 	pglEnable(GL_TEXTURE_2D);
 
 	// Draw the original screen
@@ -3261,7 +3339,7 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
 
 	// Draw the end screen that fades in
 	pglActiveTexture(GL_TEXTURE0);
diff --git a/src/http-mserv.c b/src/http-mserv.c
new file mode 100644
index 0000000000000000000000000000000000000000..7c7d04495cd8f5641bd7c6f236c47bd0eb344c64
--- /dev/null
+++ b/src/http-mserv.c
@@ -0,0 +1,691 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+// \brief HTTP based master server
+
+/*
+Documentation available here.
+
+                     <http://mb.srb2.org/MS/tools/api/v1/>
+*/
+
+#ifdef HAVE_CURL
+#include <curl/curl.h>
+#endif
+
+#include "doomdef.h"
+#include "d_clisrv.h"
+#include "command.h"
+#include "m_argv.h"
+#include "m_menu.h"
+#include "mserv.h"
+#include "i_tcp.h"/* for current_port */
+#include "i_threads.h"
+
+/* reasonable default I guess?? */
+#define DEFAULT_BUFFER_SIZE (4096)
+
+/* I just stop myself from making macros anymore. */
+#define Blame( ... ) \
+	CONS_Printf("\x85" __VA_ARGS__)
+
+static void MasterServer_Debug_OnChange (void);
+
+consvar_t cv_masterserver_timeout = CVAR_INIT
+(
+		"masterserver_timeout", "5", CV_SAVE, CV_Unsigned,
+		NULL
+);
+
+consvar_t cv_masterserver_debug = CVAR_INIT
+(
+	"masterserver_debug", "Off", CV_SAVE|CV_CALL, CV_OnOff,
+	MasterServer_Debug_OnChange
+);
+
+consvar_t cv_masterserver_token = CVAR_INIT
+(
+		"masterserver_token", "", CV_SAVE, NULL,
+		NULL
+);
+
+#ifdef MASTERSERVER
+
+static int hms_started;
+
+static char *hms_api;
+#ifdef HAVE_THREADS
+static I_mutex hms_api_mutex;
+#endif
+
+static char *hms_server_token;
+
+struct HMS_buffer
+{
+	CURL *curl;
+	char *buffer;
+	int   needle;
+	int    end;
+};
+
+static void
+Contact_error (void)
+{
+	CONS_Alert(CONS_ERROR,
+			"There was a problem contacting the master server...\n"
+	);
+}
+
+static size_t
+HMS_on_read (char *s, size_t _1, size_t n, void *userdata)
+{
+	struct HMS_buffer *buffer;
+	size_t blocks;
+
+	(void)_1;
+
+	buffer = userdata;
+
+	if (n >= (size_t)( buffer->end - buffer->needle ))
+	{
+		/* resize to next multiple of buffer size */
+		blocks = ( n / DEFAULT_BUFFER_SIZE + 1 );
+		buffer->end += ( blocks * DEFAULT_BUFFER_SIZE );
+
+		buffer->buffer = realloc(buffer->buffer, buffer->end);
+	}
+
+	memcpy(&buffer->buffer[buffer->needle], s, n);
+	buffer->needle += n;
+
+	return n;
+}
+
+static struct HMS_buffer *
+HMS_connect (const char *format, ...)
+{
+	va_list ap;
+	CURL *curl;
+	char *url;
+	char *quack_token;
+	size_t seek;
+	size_t token_length;
+	struct HMS_buffer *buffer;
+
+	if (! hms_started)
+	{
+		if (curl_global_init(CURL_GLOBAL_ALL) != 0)
+		{
+			Contact_error();
+			Blame("From curl_global_init.\n");
+			return NULL;
+		}
+		else
+		{
+			atexit(curl_global_cleanup);
+			hms_started = 1;
+		}
+	}
+
+	curl = curl_easy_init();
+
+	if (! curl)
+	{
+		Contact_error();
+		Blame("From curl_easy_init.\n");
+		return NULL;
+	}
+
+	if (cv_masterserver_token.string[0])
+	{
+		quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0);
+		token_length = ( sizeof "?token="-1 )+ strlen(quack_token);
+	}
+	else
+	{
+		quack_token = NULL;
+		token_length = 0;
+	}
+
+#ifdef HAVE_THREADS
+	I_lock_mutex(&hms_api_mutex);
+#endif
+
+	seek = strlen(hms_api) + 1;/* + '/' */
+
+	va_start (ap, format);
+	url = malloc(seek + vsnprintf(0, 0, format, ap) + token_length + 1);
+	va_end (ap);
+
+	sprintf(url, "%s/", hms_api);
+
+#ifdef HAVE_THREADS
+	I_unlock_mutex(hms_api_mutex);
+#endif
+
+	va_start (ap, format);
+	seek += vsprintf(&url[seek], format, ap);
+	va_end (ap);
+
+	if (quack_token)
+		sprintf(&url[seek], "?token=%s", quack_token);
+
+	CONS_Printf("HMS: connecting '%s'...\n", url);
+
+	buffer = malloc(sizeof *buffer);
+	buffer->curl = curl;
+	buffer->end = DEFAULT_BUFFER_SIZE;
+	buffer->buffer = malloc(buffer->end);
+	buffer->needle = 0;
+
+	if (cv_masterserver_debug.value)
+	{
+		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+		curl_easy_setopt(curl, CURLOPT_STDERR, logstream);
+	}
+
+	if (M_CheckParm("-bindaddr") && M_IsNextParm())
+	{
+		curl_easy_setopt(curl, CURLOPT_INTERFACE, M_GetNextParm());
+	}
+
+	curl_easy_setopt(curl, CURLOPT_URL, url);
+	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+	curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+
+	curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read);
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
+
+	curl_free(quack_token);
+	free(url);
+
+	return buffer;
+}
+
+static int
+HMS_do (struct HMS_buffer *buffer)
+{
+	CURLcode cc;
+	long status;
+
+	char *p;
+
+	cc = curl_easy_perform(buffer->curl);
+
+	if (cc != CURLE_OK)
+	{
+		Contact_error();
+		Blame(
+				"From curl_easy_perform: %s\n",
+				curl_easy_strerror(cc)
+		);
+		return 0;
+	}
+
+	buffer->buffer[buffer->needle] = '\0';
+
+	curl_easy_getinfo(buffer->curl, CURLINFO_RESPONSE_CODE, &status);
+
+	if (status != 200)
+	{
+		p = strchr(buffer->buffer, '\n');
+
+		if (p)
+			*p = '\0';
+
+		Contact_error();
+		Blame(
+				"Master server error %ld: %s%s\n",
+				status,
+				buffer->buffer,
+				( (p) ? "" : " (malformed)" )
+		);
+
+		return 0;
+	}
+	else
+		return 1;
+}
+
+static void
+HMS_end (struct HMS_buffer *buffer)
+{
+	curl_easy_cleanup(buffer->curl);
+	free(buffer->buffer);
+	free(buffer);
+}
+
+int
+HMS_fetch_rooms (int joining, int query_id)
+{
+	struct HMS_buffer *hms;
+	int ok;
+
+	int doing_shit;
+
+	char *id;
+	char *title;
+	char *room_motd;
+
+	int id_no;
+
+	char *p;
+	char *end;
+
+	int i;
+
+	(void)query_id;
+
+	hms = HMS_connect("rooms");
+
+	if (! hms)
+		return 0;
+
+	if (HMS_do(hms))
+	{
+		doing_shit = 1;
+
+		p = hms->buffer;
+
+		for (i = 0; i < NUM_LIST_ROOMS && ( end = strstr(p, "\n\n\n") );)
+		{
+			*end = '\0';
+
+			id    = strtok(p, "\n");
+			title = strtok(0, "\n");
+			room_motd = strtok(0, "");
+
+			if (id && title && room_motd)
+			{
+				id_no = atoi(id);
+
+				/*
+				Don't show the 'All' room if hosting. And it's a hack like this
+				because I'm way too lazy to add another feature to the MS.
+				*/
+				if (joining || id_no != 0)
+				{
+#ifdef HAVE_THREADS
+					I_lock_mutex(&ms_QueryId_mutex);
+					{
+						if (query_id != ms_QueryId)
+							doing_shit = 0;
+					}
+					I_unlock_mutex(ms_QueryId_mutex);
+
+					if (! doing_shit)
+						break;
+#endif
+
+					room_list[i].header.buffer[0] = 1;
+
+					room_list[i].id = id_no;
+					strlcpy(room_list[i].name, title, sizeof room_list[i].name);
+					strlcpy(room_list[i].motd, room_motd, sizeof room_list[i].motd);
+
+					i++;
+				}
+
+				p = ( end + 3 );/* skip the three linefeeds */
+			}
+			else
+				break;
+		}
+
+		if (doing_shit)
+			room_list[i].header.buffer[0] = 0;
+
+		ok = 1;
+
+		if (doing_shit)
+		{
+#ifdef HAVE_THREADS
+			I_lock_mutex(&m_menu_mutex);
+#endif
+			{
+				for (i = 0; room_list[i].header.buffer[0]; i++)
+				{
+					if(*room_list[i].name != '\0')
+					{
+						MP_RoomMenu[i+1].text = room_list[i].name;
+						roomIds[i] = room_list[i].id;
+						MP_RoomMenu[i+1].status = IT_STRING|IT_CALL;
+					}
+				}
+			}
+#ifdef HAVE_THREADS
+			I_unlock_mutex(m_menu_mutex);
+#endif
+		}
+	}
+	else
+		ok = 0;
+
+	HMS_end(hms);
+
+	return ok;
+}
+
+int
+HMS_register (void)
+{
+	struct HMS_buffer *hms;
+	int ok;
+
+	char post[256];
+
+	char *title;
+
+	hms = HMS_connect("rooms/%d/register", ms_RoomId);
+
+	if (! hms)
+		return 0;
+
+	title = curl_easy_escape(hms->curl, cv_servername.string, 0);
+
+	snprintf(post, sizeof post,
+			"port=%d&"
+			"title=%s&"
+			"version=%s",
+
+			current_port,
+
+			title,
+
+			SRB2VERSION
+	);
+
+	curl_free(title);
+
+	curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
+
+	ok = HMS_do(hms);
+
+	if (ok)
+	{
+		hms_server_token = strdup(strtok(hms->buffer, "\n"));
+	}
+
+	HMS_end(hms);
+
+	return ok;
+}
+
+int
+HMS_unlist (void)
+{
+	struct HMS_buffer *hms;
+	int ok;
+
+	hms = HMS_connect("servers/%s/unlist", hms_server_token);
+
+	if (! hms)
+		return 0;
+
+	curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST");
+
+	ok = HMS_do(hms);
+	HMS_end(hms);
+
+	free(hms_server_token);
+
+	return ok;
+}
+
+int
+HMS_update (void)
+{
+	struct HMS_buffer *hms;
+	int ok;
+
+	char post[256];
+
+	char *title;
+
+	hms = HMS_connect("servers/%s/update", hms_server_token);
+
+	if (! hms)
+		return 0;
+
+	title = curl_easy_escape(hms->curl, cv_servername.string, 0);
+
+	snprintf(post, sizeof post,
+			"title=%s",
+			title
+	);
+
+	curl_free(title);
+
+	curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
+
+	ok = HMS_do(hms);
+	HMS_end(hms);
+
+	return ok;
+}
+
+void
+HMS_list_servers (void)
+{
+	struct HMS_buffer *hms;
+
+	char *list;
+	char *p;
+
+	hms = HMS_connect("servers");
+
+	if (! hms)
+		return;
+
+	if (HMS_do(hms))
+	{
+		list = curl_easy_unescape(hms->curl, hms->buffer, 0, NULL);
+
+		p = strtok(list, "\n");
+
+		while (p != NULL)
+		{
+			CONS_Printf("\x80%s\n", p);
+			p = strtok(NULL, "\n");
+		}
+
+		curl_free(list);
+	}
+
+	HMS_end(hms);
+}
+
+msg_server_t *
+HMS_fetch_servers (msg_server_t *list, int room_number, int query_id)
+{
+	struct HMS_buffer *hms;
+
+	int doing_shit;
+
+	char local_version[9];
+
+	char *room;
+
+	char *address;
+	char *port;
+	char *title;
+	char *version;
+
+	char *end;
+	char *section_end;
+	char *p;
+
+	int i;
+
+	(void)query_id;
+
+	if (room_number > 0)
+	{
+		hms = HMS_connect("rooms/%d/servers", room_number);
+	}
+	else
+		hms = HMS_connect("servers");
+
+	if (! hms)
+		return NULL;
+
+	if (HMS_do(hms))
+	{
+		doing_shit = 1;
+
+		snprintf(local_version, sizeof local_version,
+				"%s",
+				SRB2VERSION
+		);
+
+		p = hms->buffer;
+		i = 0;
+
+		do
+		{
+			section_end = strstr(p, "\n\n");
+
+			room = strtok(p, "\n");
+
+			p = strtok(0, "");
+
+			if (! p)
+				break;
+
+			while (i < MAXSERVERLIST && ( end = strchr(p, '\n') ))
+			{
+				*end = '\0';
+
+				address = strtok(p, " ");
+				port    = strtok(0, " ");
+				title   = strtok(0, " ");
+				version = strtok(0, "");
+
+				if (address && port && title && version)
+				{
+#ifdef HAVE_THREADS
+					I_lock_mutex(&ms_QueryId_mutex);
+					{
+						if (query_id != ms_QueryId)
+							doing_shit = 0;
+					}
+					I_unlock_mutex(ms_QueryId_mutex);
+
+					if (! doing_shit)
+						break;
+#endif
+
+					if (strcmp(version, local_version) == 0)
+					{
+						strlcpy(list[i].ip,      address, sizeof list[i].ip);
+						strlcpy(list[i].port,    port,    sizeof list[i].port);
+						strlcpy(list[i].name,    title,   sizeof list[i].name);
+						strlcpy(list[i].version, version, sizeof list[i].version);
+
+						list[i].room = atoi(room);
+
+						list[i].header.buffer[0] = 1;
+
+						i++;
+					}
+
+					if (end == section_end)/* end of list for this room */
+						break;
+					else
+						p = ( end + 1 );/* skip server delimiter */
+				}
+				else
+				{
+					section_end = 0;/* malformed so quit the parsing */
+					break;
+				}
+			}
+
+			if (! doing_shit)
+				break;
+
+			p = ( section_end + 2 );
+		}
+		while (section_end) ;
+
+		if (doing_shit)
+			list[i].header.buffer[0] = 0;
+	}
+	else
+		list = NULL;
+
+	HMS_end(hms);
+
+	return list;
+}
+
+int
+HMS_compare_mod_version (char *buffer, size_t buffer_size)
+{
+	struct HMS_buffer *hms;
+	int ok;
+
+	char *version;
+	char *version_name;
+
+	hms = HMS_connect("versions/%d", MODID);
+
+	if (! hms)
+		return 0;
+
+	ok = 0;
+
+	if (HMS_do(hms))
+	{
+		version      = strtok(hms->buffer, " ");
+		version_name = strtok(0, "\n");
+
+		if (version && version_name)
+		{
+			if (atoi(version) != MODVERSION)
+			{
+				strlcpy(buffer, version_name, buffer_size);
+				ok = 1;
+			}
+			else
+				ok = -1;
+		}
+	}
+
+	HMS_end(hms);
+
+	return ok;
+}
+
+void
+HMS_set_api (char *api)
+{
+#ifdef HAVE_THREADS
+	I_lock_mutex(&hms_api_mutex);
+#endif
+	{
+		free(hms_api);
+		hms_api = api;
+	}
+#ifdef HAVE_THREADS
+	I_unlock_mutex(hms_api_mutex);
+#endif
+}
+
+#endif/*MASTERSERVER*/
+
+static void
+MasterServer_Debug_OnChange (void)
+{
+#ifdef MASTERSERVER
+	/* TODO: change to 'latest-log.txt' for log files revision. */
+	if (cv_masterserver_debug.value)
+		CONS_Printf("Master server debug messages will appear in log.txt\n");
+#endif
+}
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 7b84784e58102c690f37c21e6a79d07d54adfc86..7c4f1acf1124b1087dcd7a90cf61b2f883434f18 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -98,6 +98,7 @@ patch_t *emeraldpics[3][8]; // 0 = normal, 1 = tiny, 2 = coinbox
 static patch_t *emblemicon;
 patch_t *tokenicon;
 static patch_t *exiticon;
+static patch_t *nopingicon;
 
 //-------------------------------------------
 //              misc vars
@@ -286,6 +287,7 @@ void HU_LoadGraphics(void)
 	emblemicon = W_CachePatchName("EMBLICON", PU_HUDGFX);
 	tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX);
 	exiticon = W_CachePatchName("EXITICON", PU_HUDGFX);
+	nopingicon = W_CachePatchName("NOPINGICON", PU_HUDGFX);
 
 	emeraldpics[0][0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
 	emeraldpics[0][1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
@@ -330,8 +332,6 @@ void HU_Init(void)
 
 	// set shift translation table
 	shiftxform = english_shiftxform;
-
-	HU_LoadGraphics();
 }
 
 static inline void HU_Stop(void)
@@ -1466,7 +1466,7 @@ static void HU_drawMiniChat(void)
 				if (cv_chatbacktint.value) // on request of wolfy
 					V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
 
-				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap);
+				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, true, colormap);
 			}
 
 			dx += charwidth;
@@ -1559,7 +1559,7 @@ static void HU_drawChatLog(INT32 offset)
 			else
 			{
 				if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy)))
-					V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, !cv_allcaps.value, colormap);
+					V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, true, colormap);
 				else
 					j++; // don't forget to increment this or we'll get stuck in the limbo.
 			}
@@ -1659,7 +1659,7 @@ static void HU_DrawChat(void)
 			++i;
 		else
 		{
-			V_DrawChatCharacter(chatx + c + 2, y, talk[i] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|cflag, !cv_allcaps.value, V_GetStringColormap(talk[i]|cflag));
+			V_DrawChatCharacter(chatx + c + 2, y, talk[i] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|cflag, true, V_GetStringColormap(talk[i]|cflag));
 			i++;
 		}
 
@@ -1677,7 +1677,7 @@ static void HU_DrawChat(void)
 	typelines = 1;
 
 	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
-		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
+		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, true, NULL);
 
 	while (w_chat[i])
 	{
@@ -1687,7 +1687,7 @@ static void HU_DrawChat(void)
 			INT32 cursorx = (c+charwidth < boxw-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1); // we may have to go down.
 			INT32 cursory = (cursorx != chatx+1) ? (y) : (y+charheight);
 			if (hu_tick < 4)
-				V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL);
+				V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, true, NULL);
 
 			if (cursorx == chatx+1 && saylen == i) // a weirdo hack
 			{
@@ -1700,7 +1700,7 @@ static void HU_DrawChat(void)
 		if (w_chat[i] < HU_FONTSTART)
 			++i;
 		else
-			V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_allcaps.value, NULL);
+			V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, true, NULL);
 
 		c += charwidth;
 		if (c > boxw-(charwidth*2) && !skippedline)
@@ -1796,8 +1796,8 @@ static void HU_DrawChat_Old(void)
 	size_t i = 0;
 	const char *ntalk = "Say: ", *ttalk = "Say-Team: ";
 	const char *talk = ntalk;
-	INT32 charwidth = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
-	INT32 charheight = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor;
+	INT32 charwidth = 8 * con_scalefactor; //(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
+	INT32 charheight = 8 * con_scalefactor; //(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor;
 	if (teamtalk)
 	{
 		talk = ttalk;
@@ -1818,14 +1818,14 @@ static void HU_DrawChat_Old(void)
 		}
 		else
 		{
-			//charwidth = SHORT(hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor;
+			//charwidth = (hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor;
 			V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, true);
 		}
 		c += charwidth;
 	}
 
 	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
-		V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
+		V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, true);
 
 	i = 0;
 	while (w_chat[i])
@@ -1835,7 +1835,7 @@ static void HU_DrawChat_Old(void)
 		{
 			INT32 cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX); // we may have to go down.
 			INT32 cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight);
-			V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value);
+			V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, true);
 		}
 
 		//Hurdler: isn't it better like that?
@@ -1846,7 +1846,7 @@ static void HU_DrawChat_Old(void)
 		}
 		else
 		{
-			//charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor;
+			//charwidth = (hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor;
 			V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, true);
 		}
 
@@ -1880,7 +1880,7 @@ static inline void HU_DrawCrosshair(void)
 
 #ifdef HWRENDER
 	if (rendermode != render_soft)
-		y = (INT32)gr_basewindowcentery;
+		y = (INT32)gl_basewindowcentery;
 	else
 #endif
 		y = viewwindowy + (viewheight>>1);
@@ -1901,7 +1901,7 @@ static inline void HU_DrawCrosshair2(void)
 
 #ifdef HWRENDER
 	if (rendermode != render_soft)
-		y = (INT32)gr_basewindowcentery;
+		y = (INT32)gl_basewindowcentery;
 	else
 #endif
 		y = viewwindowy + (viewheight>>1);
@@ -1910,7 +1910,7 @@ static inline void HU_DrawCrosshair2(void)
 	{
 #ifdef HWRENDER
 		if (rendermode != render_soft)
-			y += (INT32)gr_viewheight;
+			y += (INT32)gl_viewheight;
 		else
 #endif
 			y += viewheight;
@@ -2038,9 +2038,6 @@ static void HU_DrawDemoInfo(void)
 //
 void HU_Drawer(void)
 {
-	if (needpatchrecache)
-		R_ReloadHUDGraphics();
-
 #ifndef NONET
 	// draw chat string plus cursor
 	if (chat_on)
@@ -2114,18 +2111,21 @@ void HU_Drawer(void)
 		return;
 
 	// draw the crosshair, not when viewing demos nor with chasecam
-	if (!automapactive && cv_crosshair.value && !demoplayback &&
-		(!camera.chase || ticcmd_ztargetfocus[0])
-	&& !players[displayplayer].spectator)
-		HU_DrawCrosshair();
+	if (LUA_HudEnabled(hud_crosshair))
+	{
+		if (!automapactive && cv_crosshair.value && !demoplayback &&
+			(!camera.chase || ticcmd_ztargetfocus[0])
+		&& !players[displayplayer].spectator)
+			HU_DrawCrosshair();
 
-	if (!automapactive && cv_crosshair2.value && !demoplayback &&
-		(!camera2.chase || ticcmd_ztargetfocus[1])
-	&& !players[secondarydisplayplayer].spectator)
-		HU_DrawCrosshair2();
+		if (!automapactive && cv_crosshair2.value && !demoplayback &&
+			(!camera2.chase || ticcmd_ztargetfocus[1])
+		&& !players[secondarydisplayplayer].spectator)
+			HU_DrawCrosshair2();
+	}
 
 	// draw desynch text
-	if (hu_resynching)
+	if (hu_redownloadinggamestate)
 	{
 		static UINT32 resynch_ticker = 0;
 		char resynch_text[14];
@@ -2248,8 +2248,8 @@ void HU_Erase(void)
 //
 void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags)
 {
-	UINT8 numbars = 1; // how many ping bars do we draw?
-	UINT8 barcolor = 35; // color we use for the bars (green, yellow or red)
+	UINT8 numbars = 0; // how many ping bars do we draw?
+	UINT8 barcolor = 31; // color we use for the bars (green, yellow, red or black)
 	SINT8 i = 0;
 	SINT8 yoffset = 6;
 	INT32 dx = x+1 - (V_SmallStringWidth(va("%dms", ping),
@@ -2262,11 +2262,16 @@ void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags)
 	}
 	else if (ping < 256)
 	{
-		numbars = 2; // Apparently ternaries w/ multiple statements don't look good in C so I decided against it.
+		numbars = 2;
 		barcolor = 73;
 	}
+	else if (ping < UINT32_MAX)
+	{
+		numbars = 1;
+		barcolor = 35;
+	}
 
-	if (!notext || vid.width >= 640) // how sad, we're using a shit resolution.
+	if (ping < UINT32_MAX && (!notext || vid.width >= 640)) // how sad, we're using a shit resolution.
 		V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE|flags, va("%dms", ping));
 
 	for (i=0; (i<3); i++) // Draw the ping bar
@@ -2277,6 +2282,9 @@ void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags)
 
 		yoffset -= 2;
 	}
+
+	if (ping == UINT32_MAX)
+		V_DrawSmallScaledPatch(x + 4 - nopingicon->width/2, y + 9 - nopingicon->height/2, 0, nopingicon);
 }
 
 //
@@ -2303,16 +2311,17 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 
 		if (!splitscreen) // don't draw it on splitscreen,
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 253, y, playerpingtable[tab[i].num], false, 0);
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x + 253, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
 			//else
 			//	V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER");
 		}
 
-		V_DrawString(x + 20, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? V_60TRANS : 0)
-		             | V_ALLOWLOWERCASE, tab[i].name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 20, y,
+		                 ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+		                 | (greycheck ? V_60TRANS : 0)
+		                 | V_ALLOWLOWERCASE, tab[i].name);
 
 		// Draw emeralds
 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
@@ -2369,7 +2378,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		}
 
 		if (players[tab[i].num].exiting || (players[tab[i].num].pflags & PF_FINISHED))
-			V_DrawSmallScaledPatch(x - SHORT(exiticon->width)/2 - 1, y-3, 0, exiticon);
+			V_DrawSmallScaledPatch(x - exiticon->width/2 - 1, y-3, 0, exiticon);
 
 		if (gametyperankings[gametype] == GT_RACE)
 		{
@@ -2460,10 +2469,11 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		supercheck = supercheckdef;
 
 		strlcpy(name, tab[i].name, 8);
-		V_DrawString(x + 10, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? 0 : V_TRANSLUCENT)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 10, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? 0 : V_TRANSLUCENT)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (gametyperules & GTR_TEAMFLAGS)
 		{
@@ -2502,10 +2512,10 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
 		if (!splitscreen)
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
-		//else
-			//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x + 135, y+1, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], true, 0);
+			//else
+				//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
 		}
 	}
 }
@@ -2588,10 +2598,11 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		supercheck = supercheckdef;
 
 		strlcpy(name, tab[i].name, 7);
-		V_DrawString(x + 20, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? V_TRANSLUCENT : 0)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 20, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? V_TRANSLUCENT : 0)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (gametyperules & GTR_TEAMFLAGS)
 		{
@@ -2626,10 +2637,10 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 		if (!splitscreen)
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
-		//else
-		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x+ 113, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
+			//else
+			//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
 		}
 	}
 }
@@ -2657,15 +2668,16 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		supercheck = supercheckdef;
 
 		strlcpy(name, tab[i].name, 7);
-		if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-			HU_drawPing(x+ 113, y, playerpingtable[tab[i].num], false, 0);
+		if (tab[i].num != serverplayer)
+			HU_drawPing(x+ 113, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
 		//else
 		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
 
-		V_DrawString(x + 20, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? V_TRANSLUCENT : 0)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 20, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? V_TRANSLUCENT : 0)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (G_GametypeUsesLives() && !(G_GametypeUsesCoopLives() && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
@@ -2673,7 +2685,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 			V_DrawSmallScaledPatch(x-28, y-4, 0, tagico);
 
 		if (players[tab[i].num].exiting || (players[tab[i].num].pflags & PF_FINISHED))
-			V_DrawSmallScaledPatch(x - SHORT(exiticon->width)/2 - 1, y-3, 0, exiticon);
+			V_DrawSmallScaledPatch(x - exiticon->width/2 - 1, y-3, 0, exiticon);
 
 		// Draw emeralds
 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
@@ -2765,16 +2777,17 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 		strlcpy(name, tab[i].name, 7);
 		if (!splitscreen) // don't draw it on splitscreen,
 		{
-			if (!(tab[i].num == serverplayer || players[tab[i].num].quittime))
-				HU_drawPing(x+ 135, y+1, playerpingtable[tab[i].num], true, 0);
-		//else
-		//	V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
+			if (tab[i].num != serverplayer)
+				HU_drawPing(x+ 135, y+1, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], true, 0);
+			//else
+			//	V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
 		}
 
-		V_DrawString(x + 10, y,
-		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | (greycheck ? 0 : V_TRANSLUCENT)
-		             | V_ALLOWLOWERCASE, name);
+		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
+			V_DrawString(x + 10, y,
+			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
+			             | (greycheck ? 0 : V_TRANSLUCENT)
+			             | V_ALLOWLOWERCASE, name);
 
 		if (G_GametypeUsesLives()) //show lives
 			V_DrawRightAlignedThinString(x-1, y, V_ALLOWLOWERCASE, va("%d", players[tab[i].num].lives));
@@ -3099,7 +3112,7 @@ static void HU_DrawCoopOverlay(void)
 	if (LUA_HudEnabled(hud_tabemblems) && (!modifiedgame || savemoddata))
 	{
 		V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems));
-		V_DrawScaledPatch(128, 144 - SHORT(emblemicon->height)/4, 0, emblemicon);
+		V_DrawScaledPatch(128, 144 - emblemicon->height/4, 0, emblemicon);
 	}
 
 	if (!LUA_HudEnabled(hud_coopemeralds))
diff --git a/src/i_sound.h b/src/i_sound.h
index 4bd05d23442322d3a4ee2500f4c2c62e942b33d0..d45c0b323ef4ca34ea936e49b8e598471eb9d290 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -9,7 +9,7 @@
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
 /// \file  i_sound.h
-/// \brief System interface, sound, music and CD
+/// \brief System interface, sound, music
 
 #ifndef __I_SOUND__
 #define __I_SOUND__
@@ -21,15 +21,12 @@
 // copied from SDL mixer, plus GME
 typedef enum {
 	MU_NONE,
-	MU_CMD,
 	MU_WAV,
 	MU_MOD,
 	MU_MID,
 	MU_OGG,
 	MU_MP3,
-	MU_MP3_MAD_UNUSED, // use MU_MP3 instead
 	MU_FLAC,
-	MU_MODPLUG_UNUSED, // use MU_MOD instead
 	MU_GME,
 	MU_MOD_EX, // libopenmpt
 	MU_MID_EX // Non-native MIDI
@@ -241,53 +238,4 @@ boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
 boolean I_FadeOutStopSong(UINT32 ms);
 boolean I_FadeInPlaySong(UINT32 ms, boolean looping);
 
-/// ------------------------
-//  CD MUSIC I/O
-/// ------------------------
-
-/**	\brief  cd music interface
-*/
-extern UINT8 cdaudio_started;
-
-/**	\brief Startup the CD system
-*/
-void I_InitCD(void);
-
-/**	\brief Stop the CD playback
-*/
-void I_StopCD(void);
-
-/**	\brief Pause the CD playback
-*/
-void I_PauseCD(void);
-
-/**	\brief Resume the CD playback
-*/
-void I_ResumeCD(void);
-
-/**	\brief Shutdown the CD system
-*/
-void I_ShutdownCD(void);
-
-/**	\brief Update the CD info
-*/
-void I_UpdateCD(void);
-
-/**	\brief	The I_PlayCD function
-
-	\param	track	CD track number
-	\param	looping	if true, loop the track
-
-	\return	void
-*/
-void I_PlayCD(UINT8 track, UINT8 looping);
-
-/**	\brief	The I_SetVolumeCD function
-
-	\param	volume	volume level to set at
-
-	\return	return 0 on failure
-*/
-boolean I_SetVolumeCD(INT32 volume);
-
 #endif
diff --git a/src/i_system.h b/src/i_system.h
index dd0b65f6df542d228019f5c3c91d59c5bcd6728c..12f0d751d14eec081175d3a01fdb8a1d03647bb4 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -46,7 +46,13 @@ UINT32 I_GetFreeMem(UINT32 *total);
 */
 tic_t I_GetTime(void);
 
-int I_GetTimeMicros(void);// provides microsecond counter for render stats
+/**	\brief	Returns precise time value for performance measurement.
+  */
+precise_t I_GetPreciseTime(void);
+
+/**	\brief	Returns the difference between precise times as microseconds.
+  */
+int I_PreciseToMicros(precise_t);
 
 /**	\brief	The I_Sleep function
 
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 5180869a53b60772ded94d18495bd17906cef137..ab8a69a9fad3c300c92fa540fa60ae68345aff82 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -20,127 +20,121 @@
 #endif
 
 #ifndef NO_IPV6
-#define HAVE_IPV6
+	#define HAVE_IPV6
 #endif
 
 #ifdef _WIN32
-#define USE_WINSOCK
-#if defined (_WIN64) || defined (HAVE_IPV6)
-#define USE_WINSOCK2
-#else //_WIN64/HAVE_IPV6
-#define USE_WINSOCK1
-#endif
+	#define USE_WINSOCK
+	#if defined (_WIN64) || defined (HAVE_IPV6)
+		#define USE_WINSOCK2
+	#else //_WIN64/HAVE_IPV6
+		#define USE_WINSOCK1
+	#endif
 #endif //WIN32 OS
 
 #ifdef USE_WINSOCK2
-#include <ws2tcpip.h>
+	#include <ws2tcpip.h>
 #endif
 
 #include "doomdef.h"
 
 #if defined (NOMD5) && !defined (NONET)
-//#define NONET
+	//#define NONET
 #endif
 
 #ifdef NONET
-#undef HAVE_MINIUPNPC
+	#undef HAVE_MINIUPNPC
 #else
-#ifdef USE_WINSOCK1
-#include <winsock.h>
-#elif !defined (SCOUW2) && !defined (SCOUW7)
-#ifndef USE_WINSOCK
-#include <arpa/inet.h>
-#endif //normal BSD API
-
-#ifndef USE_WINSOCK
-#ifdef __APPLE_CC__
-#ifndef _BSD_SOCKLEN_T_
-#define _BSD_SOCKLEN_T_
-#endif //_BSD_SOCKLEN_T_
-#endif //__APPLE_CC__
-#include <sys/socket.h>
-#include <netinet/in.h>
-#endif //normal BSD API
-
-#ifndef USE_WINSOCK
-#include <netdb.h>
-#include <sys/ioctl.h>
-#endif //normal BSD API
-
-#include <errno.h>
-#include <time.h>
-
-#if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
-	#include <sys/time.h>
-#endif // UNIXCOMMON
-#endif // !NONET
-
-#ifdef USE_WINSOCK
-	// some undefined under win32
-	#undef errno
-	//#define errno WSAGetLastError() //Alam_GBC: this is the correct way, right?
-	#define errno h_errno // some very strange things happen when not using h_error?!?
-	#ifdef EWOULDBLOCK
-	#undef EWOULDBLOCK
-	#endif
-	#define EWOULDBLOCK WSAEWOULDBLOCK
-	#ifdef EMSGSIZE
-	#undef EMSGSIZE
-	#endif
-	#define EMSGSIZE WSAEMSGSIZE
-	#ifdef ECONNREFUSED
-	#undef ECONNREFUSED
-	#endif
-	#define ECONNREFUSED WSAECONNREFUSED
-	#ifdef ETIMEDOUT
-	#undef ETIMEDOUT
-	#endif
-	#define ETIMEDOUT WSAETIMEDOUT
-	#ifndef IOC_VENDOR
-	#define IOC_VENDOR 0x18000000
+	#ifdef USE_WINSOCK1
+		#include <winsock.h>
+	#else
+		#ifndef USE_WINSOCK
+			#include <arpa/inet.h>
+			#ifdef __APPLE_CC__
+				#ifndef _BSD_SOCKLEN_T_
+					#define _BSD_SOCKLEN_T_
+				#endif //_BSD_SOCKLEN_T_
+			#endif //__APPLE_CC__
+			#include <sys/socket.h>
+			#include <netinet/in.h>
+			#include <netdb.h>
+			#include <sys/ioctl.h>
+		#endif //normal BSD API
+
+		#include <errno.h>
+		#include <time.h>
+
+		#if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
+			#include <sys/time.h>
+		#endif // UNIXCOMMON
 	#endif
-	#ifndef _WSAIOW
-	#define _WSAIOW(x,y) (IOC_IN|(x)|(y))
-	#endif
-	#ifndef SIO_UDP_CONNRESET
-	#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
-	#endif
-	#ifndef AI_ADDRCONFIG
-	#define AI_ADDRCONFIG 0x00000400
-	#endif
-	#ifndef STATUS_INVALID_PARAMETER
-	#define STATUS_INVALID_PARAMETER 0xC000000D
-	#endif
-#endif
 
-#ifdef __DJGPP__
-#ifdef WATTCP // Alam_GBC: Wattcp may need this
-#include <tcp.h>
-#define strerror strerror_s
-#else // wattcp
-#include <lsck/lsck.h>
-#endif // libsocket
-#endif // djgpp
-
-typedef union
-{
-	struct sockaddr     any;
-	struct sockaddr_in  ip4;
-#ifdef HAVE_IPV6
-	struct sockaddr_in6 ip6;
-#endif
-} mysockaddr_t;
-
-#ifdef HAVE_MINIUPNPC
-#ifdef STATIC_MINIUPNPC
-#define STATICLIB
-#endif
-#include "miniupnpc/miniwget.h"
-#include "miniupnpc/miniupnpc.h"
-#include "miniupnpc/upnpcommands.h"
-#undef STATICLIB
-static UINT8 UPNP_support = TRUE;
-#endif
+	#ifdef USE_WINSOCK
+		// some undefined under win32
+		#undef errno
+		//#define errno WSAGetLastError() //Alam_GBC: this is the correct way, right?
+		#define errno h_errno // some very strange things happen when not using h_error?!?
+		#ifdef EWOULDBLOCK
+		#undef EWOULDBLOCK
+		#endif
+		#define EWOULDBLOCK WSAEWOULDBLOCK
+		#ifdef EMSGSIZE
+		#undef EMSGSIZE
+		#endif
+		#define EMSGSIZE WSAEMSGSIZE
+		#ifdef ECONNREFUSED
+		#undef ECONNREFUSED
+		#endif
+		#define ECONNREFUSED WSAECONNREFUSED
+		#ifdef ETIMEDOUT
+		#undef ETIMEDOUT
+		#endif
+		#define ETIMEDOUT WSAETIMEDOUT
+		#ifndef IOC_VENDOR
+		#define IOC_VENDOR 0x18000000
+		#endif
+		#ifndef _WSAIOW
+		#define _WSAIOW(x,y) (IOC_IN|(x)|(y))
+		#endif
+		#ifndef SIO_UDP_CONNRESET
+		#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
+		#endif
+		#ifndef AI_ADDRCONFIG
+		#define AI_ADDRCONFIG 0x00000400
+		#endif
+		#ifndef STATUS_INVALID_PARAMETER
+		#define STATUS_INVALID_PARAMETER 0xC000000D
+		#endif
+	#endif // USE_WINSOCK
+
+	#ifdef __DJGPP__
+		#ifdef WATTCP // Alam_GBC: Wattcp may need this
+			#include <tcp.h>
+			#define strerror strerror_s
+		#else // wattcp
+			#include <lsck/lsck.h>
+		#endif // libsocket
+	#endif // djgpp
+
+	typedef union
+	{
+		struct sockaddr     any;
+		struct sockaddr_in  ip4;
+	#ifdef HAVE_IPV6
+		struct sockaddr_in6 ip6;
+	#endif
+	} mysockaddr_t;
+
+	#ifdef HAVE_MINIUPNPC
+		#ifdef STATIC_MINIUPNPC
+			#define STATICLIB
+		#endif
+		#include "miniupnpc/miniwget.h"
+		#include "miniupnpc/miniupnpc.h"
+		#include "miniupnpc/upnpcommands.h"
+		#undef STATICLIB
+		static UINT8 UPNP_support = TRUE;
+	#endif // HAVE_MINIUPNC
 
 #endif // !NONET
 
@@ -177,32 +171,32 @@ static UINT8 UPNP_support = TRUE;
 #define DEFAULTPORT "5029"
 
 #if defined (USE_WINSOCK) && !defined (NONET)
-typedef SOCKET SOCKET_TYPE;
-#define ERRSOCKET (SOCKET_ERROR)
+	typedef SOCKET SOCKET_TYPE;
+	#define ERRSOCKET (SOCKET_ERROR)
 #else
-#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__)
-typedef int SOCKET_TYPE;
-#else
-typedef unsigned long SOCKET_TYPE;
-#endif
-#define ERRSOCKET (-1)
-#endif
-
-#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (USE_WINSOCK1)
-typedef int socklen_t;
+	#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__)
+		typedef int SOCKET_TYPE;
+	#else
+		typedef unsigned long SOCKET_TYPE;
+	#endif
+	#define ERRSOCKET (-1)
 #endif
 
 #ifndef NONET
-static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET};
-static size_t mysocketses = 0;
-static int myfamily[MAXNETNODES+1] = {0};
-static SOCKET_TYPE nodesocket[MAXNETNODES+1] = {ERRSOCKET};
-static mysockaddr_t clientaddress[MAXNETNODES+1];
-static mysockaddr_t broadcastaddress[MAXNETNODES+1];
-static size_t broadcastaddresses = 0;
-static boolean nodeconnected[MAXNETNODES+1];
-static mysockaddr_t banned[MAXBANS];
-static UINT8 bannedmask[MAXBANS];
+	// define socklen_t in DOS/Windows if it is not already defined
+	#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (USE_WINSOCK1)
+		typedef int socklen_t;
+	#endif
+	static SOCKET_TYPE mysockets[MAXNETNODES+1] = {ERRSOCKET};
+	static size_t mysocketses = 0;
+	static int myfamily[MAXNETNODES+1] = {0};
+	static SOCKET_TYPE nodesocket[MAXNETNODES+1] = {ERRSOCKET};
+	static mysockaddr_t clientaddress[MAXNETNODES+1];
+	static mysockaddr_t broadcastaddress[MAXNETNODES+1];
+	static size_t broadcastaddresses = 0;
+	static boolean nodeconnected[MAXNETNODES+1];
+	static mysockaddr_t banned[MAXBANS];
+	static UINT8 bannedmask[MAXBANS];
 #endif
 
 static size_t numbans = 0;
diff --git a/src/i_threads.h b/src/i_threads.h
new file mode 100644
index 0000000000000000000000000000000000000000..ecb9fce6715f3b8c40cc2bd37a0b3caa0d07101b
--- /dev/null
+++ b/src/i_threads.h
@@ -0,0 +1,39 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+//
+// 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  i_threads.h
+/// \brief Multithreading abstraction
+
+#ifdef HAVE_THREADS
+
+#ifndef I_THREADS_H
+#define I_THREADS_H
+
+typedef void (*I_thread_fn)(void *userdata);
+
+typedef void * I_mutex;
+typedef void * I_cond;
+
+void      I_start_threads (void);
+void      I_stop_threads  (void);
+
+void      I_spawn_thread (const char *name, I_thread_fn, void *userdata);
+
+/* check in your thread whether to return early */
+int       I_thread_is_stopped (void);
+
+void      I_lock_mutex      (I_mutex *);
+void      I_unlock_mutex    (I_mutex);
+
+void      I_hold_cond       (I_cond *, I_mutex);
+
+void      I_wake_one_cond   (I_cond *);
+void      I_wake_all_cond   (I_cond *);
+
+#endif/*I_THREADS_H*/
+#endif/*HAVE_THREADS*/
diff --git a/src/i_video.h b/src/i_video.h
index 98ed7f38a18822d8038ec0973ef3c680572c2d89..ab48881d4405036b515ff65988a81bab89e7236a 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -36,10 +36,9 @@ typedef enum
 */
 extern rendermode_t rendermode;
 
-/**	\brief OpenGL state
-	0 = never loaded, 1 = loaded successfully, -1 = failed loading
+/**	\brief render mode set by command line arguments
 */
-extern INT32 vid_opengl_state;
+extern rendermode_t chosenrendermode;
 
 /**	\brief use highcolor modes if true
 */
@@ -90,8 +89,9 @@ INT32 VID_GetModeForSize(INT32 w, INT32 h);
 INT32 VID_SetMode(INT32 modenum);
 
 /**	\brief Checks the render state
+	\return	true if the renderer changed
 */
-void VID_CheckRenderer(void);
+boolean VID_CheckRenderer(void);
 
 /**	\brief Load OpenGL mode
 */
diff --git a/src/info.c b/src/info.c
index 778f1f7418c97e9cd3af6c05787c79c8e15aa4e5..ee836a3728e4bb0ccea8352b790b7dfdb053136e 100644
--- a/src/info.c
+++ b/src/info.c
@@ -150,6 +150,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"SIGN", // Level end sign
 	"SPIK", // Spike Ball
 	"SFLM", // Spin fire
+	"TFLM", // Spin fire (team)
 	"USPK", // Floor spike
 	"WSPK", // Wall spike
 	"WSPB", // Wall spike base
@@ -584,6 +585,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"TAL9",
 	"TALA",
 	"TALB",
+	"TALC",
 
 	"CNT1",
 	"CNT2",
@@ -661,6 +663,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_TAL0, // SPR2_TAL9,
 	SPR2_TAL9, // SPR2_TALA,
 	SPR2_TAL0, // SPR2_TALB,
+	SPR2_TAL6, // SPR2_TALC,
 
 	SPR2_WAIT, // SPR2_CNT1,
 	SPR2_FALL, // SPR2_CNT2,
@@ -801,6 +804,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_TAL9|FF_SPR2MIDSTART, 35, {NULL}, 0, 0, S_TAILSOVERLAY_PAIN}, // S_TAILSOVERLAY_PAIN
 	{SPR_PLAY, SPR2_TALA|FF_SPR2MIDSTART, 35, {NULL}, 0, 0, S_TAILSOVERLAY_GASP}, // S_TAILSOVERLAY_GASP
 	{SPR_PLAY, SPR2_TALB                , 35, {NULL}, 0, 0, S_TAILSOVERLAY_EDGE}, // S_TAILSOVERLAY_EDGE
+	{SPR_PLAY, SPR2_TALC|FF_SPR2MIDSTART, 35, {NULL}, 0, 0, S_TAILSOVERLAY_DASH}, // S_TAILSOVERLAY_DASH
 
 	// [:
 	{SPR_JETF, 3|FF_ANIMATE|FF_FULLBRIGHT, 2, {NULL}, 1, 1, S_JETFUME1}, // S_JETFUMEFLASH
@@ -1891,6 +1895,13 @@ state_t states[NUMSTATES] =
 	{SPR_SFLM, FF_FULLBRIGHT|4, 2, {NULL}, 0, 0, S_SPINFIRE6}, // S_SPINFIRE5
 	{SPR_SFLM, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_SPINFIRE1}, // S_SPINFIRE6
 
+	{SPR_TFLM, FF_FULLBRIGHT,   2, {NULL}, 0, 0, S_TEAM_SPINFIRE2}, // S_TEAM_SPINFIRE1
+	{SPR_TFLM, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE3}, // S_TEAM_SPINFIRE2
+	{SPR_TFLM, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE4}, // S_TEAM_SPINFIRE3
+	{SPR_TFLM, FF_FULLBRIGHT|3, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE5}, // S_TEAM_SPINFIRE4
+	{SPR_TFLM, FF_FULLBRIGHT|4, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE6}, // S_TEAM_SPINFIRE5
+	{SPR_TFLM, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_TEAM_SPINFIRE1}, // S_TEAM_SPINFIRE6
+
 	// Floor Spike
 	{SPR_USPK, 0,-1, {A_SpikeRetract}, 1, 0, S_SPIKE2}, // S_SPIKE1 -- Fully extended
 	{SPR_USPK, 1, 2, {A_Pain},         0, 0, S_SPIKE3}, // S_SPIKE2
@@ -3288,18 +3299,18 @@ state_t states[NUMSTATES] =
 	{SPR_WZAP, FF_TRANS10|FF_ANIMATE|FF_RANDOMANIM, 4, {NULL}, 3, 2, S_NULL},  // S_WATERZAP
 
 	// Spindash dust
-	{SPR_DUST,            0, 7, {NULL}, 0, 0, S_SPINDUST2}, // S_SPINDUST1
-	{SPR_DUST,            1, 6, {NULL}, 0, 0, S_SPINDUST3}, // S_SPINDUST2
-	{SPR_DUST, FF_TRANS30|2, 4, {NULL}, 0, 0, S_SPINDUST4}, // S_SPINDUST3
-	{SPR_DUST, FF_TRANS60|3, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST4
-	{SPR_BUBL,            0, 7, {NULL}, 0, 0, S_SPINDUST_BUBBLE2}, // S_SPINDUST_BUBBLE1
-	{SPR_BUBL,            0, 6, {NULL}, 0, 0, S_SPINDUST_BUBBLE3}, // S_SPINDUST_BUBBLE2
-	{SPR_BUBL, FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_BUBBLE4}, // S_SPINDUST_BUBBLE3
-	{SPR_BUBL, FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_BUBBLE4
-	{SPR_FPRT,            0, 7, {NULL}, 0, 0, S_SPINDUST_FIRE2}, // S_SPINDUST_FIRE1
-	{SPR_FPRT,            0, 6, {NULL}, 0, 0, S_SPINDUST_FIRE3}, // S_SPINDUST_FIRE2
-	{SPR_FPRT, FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_FIRE4}, // S_SPINDUST_FIRE3
-	{SPR_FPRT, FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_FIRE4
+	{SPR_DUST,                          0, 7, {NULL}, 0, 0, S_SPINDUST2}, // S_SPINDUST1
+	{SPR_DUST,                          1, 6, {NULL}, 0, 0, S_SPINDUST3}, // S_SPINDUST2
+	{SPR_DUST,               FF_TRANS30|2, 4, {NULL}, 0, 0, S_SPINDUST4}, // S_SPINDUST3
+	{SPR_DUST,               FF_TRANS60|3, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST4
+	{SPR_BUBL,                          0, 7, {NULL}, 0, 0, S_SPINDUST_BUBBLE2}, // S_SPINDUST_BUBBLE1
+	{SPR_BUBL,                          0, 6, {NULL}, 0, 0, S_SPINDUST_BUBBLE3}, // S_SPINDUST_BUBBLE2
+	{SPR_BUBL,               FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_BUBBLE4}, // S_SPINDUST_BUBBLE3
+	{SPR_BUBL,               FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_BUBBLE4
+	{SPR_FPRT,            FF_FULLBRIGHT|0, 7, {NULL}, 0, 0, S_SPINDUST_FIRE2}, // S_SPINDUST_FIRE1
+	{SPR_FPRT,            FF_FULLBRIGHT|0, 6, {NULL}, 0, 0, S_SPINDUST_FIRE3}, // S_SPINDUST_FIRE2
+	{SPR_FPRT, FF_FULLBRIGHT|FF_TRANS30|0, 4, {NULL}, 0, 0, S_SPINDUST_FIRE4}, // S_SPINDUST_FIRE3
+	{SPR_FPRT, FF_FULLBRIGHT|FF_TRANS60|0, 3, {NULL}, 0, 0, S_NULL}, // S_SPINDUST_FIRE4
 
 
 	{SPR_TFOG, FF_FULLBRIGHT|FF_TRANS50,    2, {NULL}, 0, 0, S_FOG2},  // S_FOG1
@@ -3921,9 +3932,7 @@ state_t states[NUMSTATES] =
 	{SPR_BRIB, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 31, 1, S_NULL}, // S_BLUEBRICKDEBRIS
 	{SPR_BRIY, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 31, 1, S_NULL}, // S_YELLOWBRICKDEBRIS
 
-#ifdef SEENAMES
 	{SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK
-#endif
 };
 
 mobjinfo_t mobjinfo[NUMMOBJTYPES] =
@@ -5139,7 +5148,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_SPECIAL, // flags
+		MF_SPECIAL,     // flags
 		S_NULL          // raisestate
 	},
 
@@ -6455,8 +6464,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_fizzle,     // deathsound
 		10*FRACUNIT,    // speed
-		48*FRACUNIT,    // radius
-		160*FRACUNIT,   // height
+		24*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
 		0,              // display offset
 		DMG_ELECTRIC,   // mass
 		1,              // damage
@@ -20867,33 +20876,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_POLYSPAWNCRUSH
-		762,            // doomednum
-		S_INVISIBLE,    // spawnstate
-		1,              // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
-		0,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		3,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		1*FRACUNIT,     // radius
-		1*FRACUNIT,     // height
-		0,              // display offset
-		1000,           // mass
-		8,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_NOCLIP, // flags
-		S_NULL          // raisestate
-	},
-
 	{           // MT_SKYBOX
 		780,            // doomednum
 		S_INVISIBLE,    // spawnstate
@@ -21677,7 +21659,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-#ifdef SEENAMES
 	{           // MT_NAMECHECK
 		-1,             // doomednum
 		S_NAMECHECK,    // spawnstate
@@ -21704,7 +21685,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MF_NOBLOCKMAP|MF_MISSILE|MF_NOGRAVITY|MF_NOSECTOR, // flags
 		S_NULL          // raisestate
 	},
-#endif
 };
 
 skincolor_t skincolors[MAXSKINCOLORS] = {
diff --git a/src/info.h b/src/info.h
index 4d8e18d63f370989e21fc5f5597f47f77c1d0cd7..60e9702463fb50db9934d6aea9e6eab28a562983 100644
--- a/src/info.h
+++ b/src/info.h
@@ -19,11 +19,279 @@
 #include "sounds.h"
 #include "m_fixed.h"
 
-// dehacked.c now has lists for the more named enums! PLEASE keep them up to date!
+// deh_tables.c now has lists for the more named enums! PLEASE keep them up to date!
 // For great modding!!
 
+// IMPORTANT!
+// DO NOT FORGET TO SYNC THIS LIST WITH THE ACTIONPOINTERS ARRAY IN DEH_TABLES.C
+enum actionnum
+{
+	A_EXPLODE = 0,
+	A_PAIN,
+	A_FALL,
+	A_MONITORPOP,
+	A_GOLDMONITORPOP,
+	A_GOLDMONITORRESTORE,
+	A_GOLDMONITORSPARKLE,
+	A_LOOK,
+	A_CHASE,
+	A_FACESTABCHASE,
+	A_FACESTABREV,
+	A_FACESTABHURL,
+	A_FACESTABMISS,
+	A_STATUEBURST,
+	A_FACETARGET,
+	A_FACETRACER,
+	A_SCREAM,
+	A_BOSSDEATH,
+	A_CUSTOMPOWER,
+	A_GIVEWEAPON,
+	A_RINGBOX,
+	A_INVINCIBILITY,
+	A_SUPERSNEAKERS,
+	A_BUNNYHOP,
+	A_BUBBLESPAWN,
+	A_FANBUBBLESPAWN,
+	A_BUBBLERISE,
+	A_BUBBLECHECK,
+	A_AWARDSCORE,
+	A_EXTRALIFE,
+	A_GIVESHIELD,
+	A_GRAVITYBOX,
+	A_SCORERISE,
+	A_ATTRACTCHASE,
+	A_DROPMINE,
+	A_FISHJUMP,
+	A_THROWNRING,
+	A_SETSOLIDSTEAM,
+	A_UNSETSOLIDSTEAM,
+	A_SIGNSPIN,
+	A_SIGNPLAYER,
+	A_OVERLAYTHINK,
+	A_JETCHASE,
+	A_JETBTHINK,
+	A_JETGTHINK,
+	A_JETGSHOOT,
+	A_SHOOTBULLET,
+	A_MINUSDIGGING,
+	A_MINUSPOPUP,
+	A_MINUSCHECK,
+	A_CHICKENCHECK,
+	A_MOUSETHINK,
+	A_DETONCHASE,
+	A_CAPECHASE,
+	A_ROTATESPIKEBALL,
+	A_SLINGAPPEAR,
+	A_UNIDUSBALL,
+	A_ROCKSPAWN,
+	A_SETFUSE,
+	A_CRAWLACOMMANDERTHINK,
+	A_SMOKETRAILER,
+	A_RINGEXPLODE,
+	A_OLDRINGEXPLODE,
+	A_MIXUP,
+	A_RECYCLEPOWERS,
+	A_BOSS1CHASE,
+	A_FOCUSTARGET,
+	A_BOSS2CHASE,
+	A_BOSS2POGO,
+	A_BOSSZOOM,
+	A_BOSSSCREAM,
+	A_BOSS2TAKEDAMAGE,
+	A_BOSS7CHASE,
+	A_GOOPSPLAT,
+	A_BOSS2POGOSFX,
+	A_BOSS2POGOTARGET,
+	A_BOSSJETFUME,
+	A_EGGMANBOX,
+	A_TURRETFIRE,
+	A_SUPERTURRETFIRE,
+	A_TURRETSTOP,
+	A_JETJAWROAM,
+	A_JETJAWCHOMP,
+	A_POINTYTHINK,
+	A_CHECKBUDDY,
+	A_HOODFIRE,
+	A_HOODTHINK,
+	A_HOODFALL,
+	A_ARROWBONKS,
+	A_SNAILERTHINK,
+	A_SHARPCHASE,
+	A_SHARPSPIN,
+	A_SHARPDECEL,
+	A_CRUSHSTACEANWALK,
+	A_CRUSHSTACEANPUNCH,
+	A_CRUSHCLAWAIM,
+	A_CRUSHCLAWLAUNCH,
+	A_VULTUREVTOL,
+	A_VULTURECHECK,
+	A_VULTUREHOVER,
+	A_VULTUREBLAST,
+	A_VULTUREFLY,
+	A_SKIMCHASE,
+	A_1UPTHINKER,
+	A_SKULLATTACK,
+	A_LOBSHOT,
+	A_FIRESHOT,
+	A_SUPERFIRESHOT,
+	A_BOSSFIRESHOT,
+	A_BOSS7FIREMISSILES,
+	A_BOSS1LASER,
+	A_BOSS4REVERSE,
+	A_BOSS4SPEEDUP,
+	A_BOSS4RAISE,
+	A_SPARKFOLLOW,
+	A_BUZZFLY,
+	A_GUARDCHASE,
+	A_EGGSHIELD,
+	A_SETREACTIONTIME,
+	A_BOSS1SPIKEBALLS,
+	A_BOSS3TAKEDAMAGE,
+	A_BOSS3PATH,
+	A_BOSS3SHOCKTHINK,
+	A_LINEDEFEXECUTE,
+	A_PLAYSEESOUND,
+	A_PLAYATTACKSOUND,
+	A_PLAYACTIVESOUND,
+	A_SPAWNOBJECTABSOLUTE,
+	A_SPAWNOBJECTRELATIVE,
+	A_CHANGEANGLERELATIVE,
+	A_CHANGEANGLEABSOLUTE,
+	A_ROLLANGLE,
+	A_CHANGEROLLANGLERELATIVE,
+	A_CHANGEROLLANGLEABSOLUTE,
+	A_PLAYSOUND,
+	A_FINDTARGET,
+	A_FINDTRACER,
+	A_SETTICS,
+	A_SETRANDOMTICS,
+	A_CHANGECOLORRELATIVE,
+	A_CHANGECOLORABSOLUTE,
+	A_DYE,
+	A_MOVERELATIVE,
+	A_MOVEABSOLUTE,
+	A_THRUST,
+	A_ZTHRUST,
+	A_SETTARGETSTARGET,
+	A_SETOBJECTFLAGS,
+	A_SETOBJECTFLAGS2,
+	A_RANDOMSTATE,
+	A_RANDOMSTATERANGE,
+	A_DUALACTION,
+	A_REMOTEACTION,
+	A_TOGGLEFLAMEJET,
+	A_ORBITNIGHTS,
+	A_GHOSTME,
+	A_SETOBJECTSTATE,
+	A_SETOBJECTTYPESTATE,
+	A_KNOCKBACK,
+	A_PUSHAWAY,
+	A_RINGDRAIN,
+	A_SPLITSHOT,
+	A_MISSILESPLIT,
+	A_MULTISHOT,
+	A_INSTALOOP,
+	A_CUSTOM3DROTATE,
+	A_SEARCHFORPLAYERS,
+	A_CHECKRANDOM,
+	A_CHECKTARGETRINGS,
+	A_CHECKRINGS,
+	A_CHECKTOTALRINGS,
+	A_CHECKHEALTH,
+	A_CHECKRANGE,
+	A_CHECKHEIGHT,
+	A_CHECKTRUERANGE,
+	A_CHECKTHINGCOUNT,
+	A_CHECKAMBUSH,
+	A_CHECKCUSTOMVALUE,
+	A_CHECKCUSVALMEMO,
+	A_SETCUSTOMVALUE,
+	A_USECUSVALMEMO,
+	A_RELAYCUSTOMVALUE,
+	A_CUSVALACTION,
+	A_FORCESTOP,
+	A_FORCEWIN,
+	A_SPIKERETRACT,
+	A_INFOSTATE,
+	A_REPEAT,
+	A_SETSCALE,
+	A_REMOTEDAMAGE,
+	A_HOMINGCHASE,
+	A_TRAPSHOT,
+	A_VILETARGET,
+	A_VILEATTACK,
+	A_VILEFIRE,
+	A_BRAKCHASE,
+	A_BRAKFIRESHOT,
+	A_BRAKLOBSHOT,
+	A_NAPALMSCATTER,
+	A_SPAWNFRESHCOPY,
+	A_FLICKYSPAWN,
+	A_FLICKYCENTER,
+	A_FLICKYAIM,
+	A_FLICKYFLY,
+	A_FLICKYSOAR,
+	A_FLICKYCOAST,
+	A_FLICKYHOP,
+	A_FLICKYFLOUNDER,
+	A_FLICKYCHECK,
+	A_FLICKYHEIGHTCHECK,
+	A_FLICKYFLUTTER,
+	A_FLAMEPARTICLE,
+	A_FADEOVERLAY,
+	A_BOSS5JUMP,
+	A_LIGHTBEAMRESET,
+	A_MINEEXPLODE,
+	A_MINERANGE,
+	A_CONNECTTOGROUND,
+	A_SPAWNPARTICLERELATIVE,
+	A_MULTISHOTDIST,
+	A_WHOCARESIFYOURSONISABEE,
+	A_PARENTTRIESTOSLEEP,
+	A_CRYINGTOMOMMA,
+	A_CHECKFLAGS2,
+	A_BOSS5FINDWAYPOINT,
+	A_DONPCSKID,
+	A_DONPCPAIN,
+	A_PREPAREREPEAT,
+	A_BOSS5EXTRAREPEAT,
+	A_BOSS5CALM,
+	A_BOSS5CHECKONGROUND,
+	A_BOSS5CHECKFALLING,
+	A_BOSS5PINCHSHOT,
+	A_BOSS5MAKEITRAIN,
+	A_BOSS5MAKEJUNK,
+	A_LOOKFORBETTER,
+	A_BOSS5BOMBEXPLODE,
+	A_DUSTDEVILTHINK,
+	A_TNTEXPLODE,
+	A_DEBRISRANDOM,
+	A_TRAINCAMEO,
+	A_TRAINCAMEO2,
+	A_CANARIVOREGAS,
+	A_KILLSEGMENTS,
+	A_SNAPPERSPAWN,
+	A_SNAPPERTHINKER,
+	A_SALOONDOORSPAWN,
+	A_MINECARTSPARKTHINK,
+	A_MODULOTOSTATE,
+	A_LAVAFALLROCKS,
+	A_LAVAFALLLAVA,
+	A_FALLINGLAVACHECK,
+	A_FIRESHRINK,
+	A_SPAWNPTERABYTES,
+	A_PTERABYTEHOVER,
+	A_ROLLOUTSPAWN,
+	A_ROLLOUTROCK,
+	A_DRAGONBOMBERSPAWN,
+	A_DRAGONWING,
+	A_DRAGONSEGMENT,
+	A_CHANGEHEIGHT,
+	NUMACTIONS
+};
+
 // IMPORTANT NOTE: If you add/remove from this list of action
-// functions, don't forget to update them in dehacked.c!
+// functions, don't forget to update them in deh_tables.c!
 void A_Explode();
 void A_Pain();
 void A_Fall();
@@ -286,6 +554,8 @@ void A_DragonWing();
 void A_DragonSegment();
 void A_ChangeHeight();
 
+extern boolean actionsoverridden[NUMACTIONS];
+
 // ratio of states to sprites to mobj types is roughly 6 : 1 : 1
 #define NUMMOBJFREESLOTS 512
 #define NUMSPRITEFREESLOTS NUMMOBJFREESLOTS
@@ -414,6 +684,7 @@ typedef enum sprite
 	SPR_SIGN, // Level end sign
 	SPR_SPIK, // Spike Ball
 	SPR_SFLM, // Spin fire
+	SPR_TFLM, // Spin fire (team)
 	SPR_USPK, // Floor spike
 	SPR_WSPK, // Wall spike
 	SPR_WSPB, // Wall spike base
@@ -856,6 +1127,7 @@ typedef enum playersprite
 	SPR2_TAL9,
 	SPR2_TALA,
 	SPR2_TALB,
+	SPR2_TALC,
 
 	SPR2_CNT1, // continue disappointment
 	SPR2_CNT2, // continue lift
@@ -997,6 +1269,7 @@ typedef enum state
 	S_TAILSOVERLAY_PAIN,
 	S_TAILSOVERLAY_GASP,
 	S_TAILSOVERLAY_EDGE,
+	S_TAILSOVERLAY_DASH,
 
 	// [:
 	S_JETFUMEFLASH,
@@ -2052,6 +2325,13 @@ typedef enum state
 	S_SPINFIRE5,
 	S_SPINFIRE6,
 
+	S_TEAM_SPINFIRE1,
+	S_TEAM_SPINFIRE2,
+	S_TEAM_SPINFIRE3,
+	S_TEAM_SPINFIRE4,
+	S_TEAM_SPINFIRE5,
+	S_TEAM_SPINFIRE6,
+
 	// Spikes
 	S_SPIKE1,
 	S_SPIKE2,
@@ -4008,9 +4288,7 @@ typedef enum state
 	S_BLUEBRICKDEBRIS, // for CEZ3
 	S_YELLOWBRICKDEBRIS, // for CEZ3
 
-#ifdef SEENAMES
 	S_NAMECHECK,
-#endif
 
 	S_FIRSTFREESLOT,
 	S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1,
@@ -4774,7 +5052,6 @@ typedef enum mobj_type
 	MT_ANGLEMAN,
 	MT_POLYANCHOR,
 	MT_POLYSPAWN,
-	MT_POLYSPAWNCRUSH,
 
 	// Skybox objects
 	MT_SKYBOX,
@@ -4811,9 +5088,7 @@ typedef enum mobj_type
 	MT_BLUEBRICKDEBRIS, // for CEZ3
 	MT_YELLOWBRICKDEBRIS, // for CEZ3
 
-#ifdef SEENAMES
 	MT_NAMECHECK,
-#endif
 
 	MT_FIRSTFREESLOT,
 	MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index b537d47da4eecdc1f62e5469be7882445dfc10ae..b6dee247796a71ab72feee9fcdc5ad0e5088d58a 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -32,9 +32,13 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "taglist.h" // P_FindSpecialLineFromTag
+#include "lua_hook.h" // hook_cmd_running errors
 
 #define NOHUD if (hud_running)\
-return luaL_error(L, "HUD rendering code should not call this function!");
+return luaL_error(L, "HUD rendering code should not call this function!");\
+else if (hook_cmd_running)\
+return luaL_error(L, "CMD building code should not call this function!");
 
 boolean luaL_checkboolean(lua_State *L, int narg) {
 	luaL_checktype(L, narg, LUA_TBOOLEAN);
@@ -151,6 +155,8 @@ static const struct {
 	{META_PIVOTLIST,    "spriteframepivot_t[]"},
 	{META_FRAMEPIVOT,   "spriteframepivot_t"},
 
+	{META_TAGLIST,      "taglist"},
+
 	{META_MOBJ,         "mobj_t"},
 	{META_MAPTHING,     "mapthing_t"},
 
@@ -159,6 +165,8 @@ static const struct {
 	{META_SKIN,         "skin_t"},
 	{META_POWERS,       "player_t.powers"},
 	{META_SOUNDSID,     "skin_t.soundsid"},
+	{META_SKINSPRITES,  "skin_t.sprites"},
+	{META_SKINSPRITESLIST,  "skin_t.sprites[]"},
 
 	{META_VERTEX,       "vertex_t"},
 	{META_LINE,         "line_t"},
@@ -170,12 +178,25 @@ static const struct {
 	{META_SEG,          "seg_t"},
 	{META_NODE,         "node_t"},
 #endif
+	{META_SLOPE,        "slope_t"},
+	{META_VECTOR2,      "vector2_t"},
+	{META_VECTOR3,      "vector3_t"},
 	{META_MAPHEADER,    "mapheader_t"},
 
+	{META_POLYOBJ,      "polyobj_t"},
+
 	{META_CVAR,         "consvar_t"},
 
 	{META_SECTORLINES,  "sector_t.lines"},
+#ifdef MUTABLE_TAGS
+	{META_SECTORTAGLIST, "sector_t.taglist"},
+#endif
 	{META_SIDENUM,      "line_t.sidenum"},
+	{META_LINEARGS,     "line_t.args"},
+	{META_LINESTRINGARGS, "line_t.stringargs"},
+
+	{META_THINGARGS,     "mapthing.args"},
+	{META_THINGSTRINGARGS, "mapthing.stringargs"},
 #ifdef HAVE_LUA_SEGS
 	{META_NODEBBOX,     "node_t.bbox"},
 	{META_NODECHILDREN, "node_t.children"},
@@ -233,6 +254,56 @@ static int lib_userdataType(lua_State *L)
 		return luaL_typerror(L, 1, "userdata");
 }
 
+// Takes a metatable as first and only argument
+// Only callable during script loading
+static int lib_registerMetatable(lua_State *L)
+{
+	static UINT16 nextid = 1;
+
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+	luaL_checktype(L, 1, LUA_TTABLE);
+
+	if (nextid == 0)
+		return luaL_error(L, "Too many metatables registered?! Please consider rewriting your script once you are sober again.\n");
+
+	lua_getfield(L, LUA_REGISTRYINDEX, LREG_METATABLES); // 2
+		// registry.metatables[metatable] = nextid
+		lua_pushvalue(L, 1); // 3
+			lua_pushnumber(L, nextid); // 4
+		lua_settable(L, 2);
+
+		// registry.metatables[nextid] = metatable
+		lua_pushnumber(L, nextid); // 3
+			lua_pushvalue(L, 1); // 4
+		lua_settable(L, 2);
+	lua_pop(L, 1);
+
+	nextid++;
+
+	return 0;
+}
+
+// Takes a string as only argument and returns the metatable
+// associated to the userdata type this string refers to
+// Returns nil if the string does not refer to a valid userdata type
+static int lib_userdataMetatable(lua_State *L)
+{
+	UINT32 i;
+	const char *udname = luaL_checkstring(L, 1);
+
+	// Find internal metatable name
+	for (i = 0; meta2utype[i].meta; i++)
+		if (!strcmp(udname, meta2utype[i].utype))
+		{
+			luaL_getmetatable(L, meta2utype[i].meta);
+			return 1;
+		}
+
+	lua_pushnil(L);
+	return 1;
+}
+
 static int lib_isPlayerAdmin(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@@ -361,7 +432,7 @@ static int lib_pAproxDistance(lua_State *L)
 	fixed_t dx = luaL_checkfixed(L, 1);
 	fixed_t dy = luaL_checkfixed(L, 2);
 	//HUDSAFE
-	lua_pushfixed(L, P_AproxDistance(dx, dy));
+	lua_pushfixed(L, R_PointToDist2(0, 0, dx, dy));
 	return 1;
 }
 
@@ -902,6 +973,128 @@ static int lib_pMaceRotate(lua_State *L)
 	return 0;
 }
 
+static int lib_pCreateFloorSpriteSlope(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!mobj)
+		return LUA_ErrInvalid(L, "mobj_t");
+	LUA_PushUserdata(L, (pslope_t *)P_CreateFloorSpriteSlope(mobj), META_SLOPE);
+	return 1;
+}
+
+static int lib_pRemoveFloorSpriteSlope(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!mobj)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_RemoveFloorSpriteSlope(mobj);
+	return 1;
+}
+
+static int lib_pRailThinker(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	mobj_t *ptmthing = tmthing;
+	NOHUD
+	INLEVEL
+	if (!mobj)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_RailThinker(mobj));
+	P_SetTarget(&tmthing, ptmthing);
+	return 1;
+}
+
+static int lib_pXYMovement(lua_State *L)
+{
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	mobj_t *ptmthing = tmthing;
+	NOHUD
+	INLEVEL
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_XYMovement(actor);
+	P_SetTarget(&tmthing, ptmthing);
+	return 0;
+}
+
+static int lib_pRingXYMovement(lua_State *L)
+{
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	mobj_t *ptmthing = tmthing;
+	NOHUD
+	INLEVEL
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_RingXYMovement(actor);
+	P_SetTarget(&tmthing, ptmthing);
+	return 0;
+}
+
+static int lib_pSceneryXYMovement(lua_State *L)
+{
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	mobj_t *ptmthing = tmthing;
+	NOHUD
+	INLEVEL
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_SceneryXYMovement(actor);
+	P_SetTarget(&tmthing, ptmthing);
+	return 0;
+}
+
+static int lib_pZMovement(lua_State *L)
+{
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_ZMovement(actor));
+	P_CheckPosition(actor, actor->x, actor->y);
+	return 1;
+}
+
+static int lib_pRingZMovement(lua_State *L)
+{
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_RingZMovement(actor);
+	P_CheckPosition(actor, actor->x, actor->y);
+	return 0;
+}
+
+static int lib_pSceneryZMovement(lua_State *L)
+{
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_SceneryZMovement(actor));
+	P_CheckPosition(actor, actor->x, actor->y);
+	return 1;
+}
+
+static int lib_pPlayerZMovement(lua_State *L)
+{
+	mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!actor)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_PlayerZMovement(actor);
+	P_CheckPosition(actor, actor->x, actor->y);
+	return 0;
+}
+
 // P_USER
 ////////////
 
@@ -1025,6 +1218,16 @@ static int lib_pPlayerCanDamage(lua_State *L)
 	return 1;
 }
 
+static int lib_pPlayerFullbright(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	lua_pushboolean(L, P_PlayerFullbright(player));
+	return 1;
+}
+
 
 static int lib_pIsObjectInGoop(lua_State *L)
 {
@@ -1272,6 +1475,17 @@ static int lib_pSpawnSkidDust(lua_State *L)
 	return 0;
 }
 
+static int lib_pMovePlayer(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	NOHUD
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	P_MovePlayer(player);
+	return 0;
+}
+
 static int lib_pDoPlayerFinish(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@@ -1601,6 +1815,18 @@ static int lib_pFloorzAtPos(lua_State *L)
 	return 1;
 }
 
+static int lib_pCeilingzAtPos(lua_State *L)
+{
+	fixed_t x = luaL_checkfixed(L, 1);
+	fixed_t y = luaL_checkfixed(L, 2);
+	fixed_t z = luaL_checkfixed(L, 3);
+	fixed_t height = luaL_checkfixed(L, 4);
+	//HUDSAFE
+	INLEVEL
+	lua_pushfixed(L, P_CeilingzAtPos(x, y, z, height));
+	return 1;
+}
+
 static int lib_pDoSpring(lua_State *L)
 {
 	mobj_t *spring = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@@ -2451,6 +2677,14 @@ static int lib_rGetColorByName(lua_State *L)
 	return 1;
 }
 
+static int lib_rGetSuperColorByName(lua_State *L)
+{
+	const char* colorname = luaL_checkstring(L, 1);
+	//HUDSAFE
+	lua_pushinteger(L, R_GetSuperColorByName(colorname));
+	return 1;
+}
+
 // Lua exclusive function, returns the name of a color from the SKINCOLOR_ constant.
 // SKINCOLOR_GREEN > "Green" for example
 static int lib_rGetNameByColor(lua_State *L)
@@ -2464,30 +2698,56 @@ static int lib_rGetNameByColor(lua_State *L)
 
 // S_SOUND
 ////////////
+static int GetValidSoundOrigin(lua_State *L, void **origin)
+{
+	const char *type;
+
+	lua_settop(L, 1);
+	type = GetUserdataUType(L);
+
+	if (fasticmp(type, "mobj_t"))
+	{
+		*origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+		if (!(*origin))
+			return LUA_ErrInvalid(L, "mobj_t");
+		return 1;
+	}
+	else if (fasticmp(type, "sector_t"))
+	{
+		*origin = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
+		if (!(*origin))
+			return LUA_ErrInvalid(L, "sector_t");
+
+		*origin = &((sector_t *)(*origin))->soundorg;
+		return 1;
+	}
+
+	return LUA_ErrInvalid(L, "mobj_t/sector_t");
+}
+
 static int lib_sStartSound(lua_State *L)
 {
-	const void *origin = NULL;
+	void *origin = NULL;
 	sfxenum_t sound_id = luaL_checkinteger(L, 2);
 	player_t *player = NULL;
 	//NOHUD
+
 	if (sound_id >= NUMSFX)
 		return luaL_error(L, "sfx %d out of range (0 - %d)", sound_id, NUMSFX-1);
-	if (!lua_isnil(L, 1))
-	{
-		origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
-		if (!origin)
-			return LUA_ErrInvalid(L, "mobj_t");
-	}
+
 	if (!lua_isnone(L, 3) && lua_isuserdata(L, 3))
 	{
 		player = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
 		if (!player)
 			return LUA_ErrInvalid(L, "player_t");
 	}
+	if (!lua_isnil(L, 1))
+		if (!GetValidSoundOrigin(L, &origin))
+			return 0;
 	if (!player || P_IsLocalPlayer(player))
 	{
-		if (hud_running)
-			origin = NULL;	// HUD rendering startsound shouldn't have an origin, just remove it instead of having a retarded error.
+		if (hud_running || hook_cmd_running)
+			origin = NULL;	// HUD rendering and CMD building startsound shouldn't have an origin, just remove it instead of having a retarded error.
 
 		S_StartSound(origin, sound_id);
 	}
@@ -2496,18 +2756,12 @@ static int lib_sStartSound(lua_State *L)
 
 static int lib_sStartSoundAtVolume(lua_State *L)
 {
-	const void *origin = NULL;
+	void *origin = NULL;
 	sfxenum_t sound_id = luaL_checkinteger(L, 2);
 	INT32 volume = (INT32)luaL_checkinteger(L, 3);
 	player_t *player = NULL;
 	//NOHUD
 
-	if (!lua_isnil(L, 1))
-	{
-		origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
-		if (!origin)
-			return LUA_ErrInvalid(L, "mobj_t");
-	}
 	if (sound_id >= NUMSFX)
 		return luaL_error(L, "sfx %d out of range (0 - %d)", sound_id, NUMSFX-1);
 	if (!lua_isnone(L, 4) && lua_isuserdata(L, 4))
@@ -2516,30 +2770,37 @@ static int lib_sStartSoundAtVolume(lua_State *L)
 		if (!player)
 			return LUA_ErrInvalid(L, "player_t");
 	}
+	if (!lua_isnil(L, 1))
+		if (!GetValidSoundOrigin(L, &origin))
+			return LUA_ErrInvalid(L, "mobj_t/sector_t");
+
 	if (!player || P_IsLocalPlayer(player))
-	S_StartSoundAtVolume(origin, sound_id, volume);
+		S_StartSoundAtVolume(origin, sound_id, volume);
 	return 0;
 }
 
 static int lib_sStopSound(lua_State *L)
 {
-	void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	void *origin = NULL;
 	//NOHUD
-	if (!origin)
-		return LUA_ErrInvalid(L, "mobj_t");
+	if (!GetValidSoundOrigin(L, &origin))
+		return LUA_ErrInvalid(L, "mobj_t/sector_t");
+
 	S_StopSound(origin);
 	return 0;
 }
 
 static int lib_sStopSoundByID(lua_State *L)
 {
-	void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	void *origin = NULL;
 	sfxenum_t sound_id = luaL_checkinteger(L, 2);
 	//NOHUD
-	if (!origin)
-		return LUA_ErrInvalid(L, "mobj_t");
+
 	if (sound_id >= NUMSFX)
 		return luaL_error(L, "sfx %d out of range (0 - %d)", sound_id, NUMSFX-1);
+	if (!lua_isnil(L, 1))
+		if (!GetValidSoundOrigin(L, &origin))
+			return LUA_ErrInvalid(L, "mobj_t/sector_t");
 
 	S_StopSoundByID(origin, sound_id);
 	return 0;
@@ -2765,11 +3026,12 @@ static int lib_sSetMusicPosition(lua_State *L)
 
 static int lib_sOriginPlaying(lua_State *L)
 {
-	void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	void *origin = NULL;
 	//NOHUD
 	INLEVEL
-	if (!origin)
-		return LUA_ErrInvalid(L, "mobj_t");
+	if (!GetValidSoundOrigin(L, &origin))
+		return LUA_ErrInvalid(L, "mobj_t/sector_t");
+
 	lua_pushboolean(L, S_OriginPlaying(origin));
 	return 1;
 }
@@ -2786,14 +3048,15 @@ static int lib_sIdPlaying(lua_State *L)
 
 static int lib_sSoundPlaying(lua_State *L)
 {
-	void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	void *origin = NULL;
 	sfxenum_t id = luaL_checkinteger(L, 2);
 	//NOHUD
 	INLEVEL
-	if (!origin)
-		return LUA_ErrInvalid(L, "mobj_t");
 	if (id >= NUMSFX)
 		return luaL_error(L, "sfx %d out of range (0 - %d)", id, NUMSFX-1);
+	if (!GetValidSoundOrigin(L, &origin))
+		return LUA_ErrInvalid(L, "mobj_t/sector_t");
+
 	lua_pushboolean(L, S_SoundPlaying(origin, id));
 	return 1;
 }
@@ -2823,6 +3086,185 @@ static int lib_sStartMusicCaption(lua_State *L)
 	return 0;
 }
 
+static int lib_sMusicType(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushinteger(L, S_MusicType());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicPlaying(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushboolean(L, S_MusicPlaying());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicPaused(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushboolean(L, S_MusicPaused());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicName(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushstring(L, S_MusicName());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicExists(lua_State *L)
+{
+	boolean checkMIDI = lua_opttrueboolean(L, 2);
+	boolean checkDigi = lua_opttrueboolean(L, 3);
+#ifdef MUSICSLOT_COMPATIBILITY
+	const char *music_name;
+	UINT32 music_num;
+	char music_compat_name[7];
+	UINT16 music_flags = 0;
+	NOHUD
+	if (lua_isnumber(L, 1))
+	{
+		music_num = (UINT32)luaL_checkinteger(L, 1);
+		music_flags = (UINT16)(music_num & 0x0000FFFF);
+		if (music_flags && music_flags <= 1035)
+			snprintf(music_compat_name, 7, "%sM", G_BuildMapName((INT32)music_flags));
+		else if (music_flags && music_flags <= 1050)
+			strncpy(music_compat_name, compat_special_music_slots[music_flags - 1036], 7);
+		else
+			music_compat_name[0] = 0; // becomes empty string
+		music_compat_name[6] = 0;
+		music_name = (const char *)&music_compat_name;
+	}
+	else
+	{
+		music_num = 0;
+		music_name = luaL_checkstring(L, 1);
+	}
+#else
+	const char *music_name = luaL_checkstring(L, 1);
+#endif
+	NOHUD
+	lua_pushboolean(L, S_MusicExists(music_name, checkMIDI, checkDigi));
+	return 1;
+}
+
+static int lib_sSetMusicLoopPoint(lua_State *L)
+{
+	UINT32 looppoint = (UINT32)luaL_checkinteger(L, 1);
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
+	{
+		player = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushboolean(L, S_SetMusicLoopPoint(looppoint));
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sGetMusicLoopPoint(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushinteger(L, (int)S_GetMusicLoopPoint());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sPauseMusic(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+	{
+		S_PauseAudio();
+		lua_pushboolean(L, true);
+	}
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sResumeMusic(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+	{
+		S_ResumeAudio();
+		lua_pushboolean(L, true);
+	}
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
 // G_GAME
 ////////////
 
@@ -3009,6 +3451,117 @@ static int lib_gBuildMapTitle(lua_State *L)
 	return 1;
 }
 
+static void
+Lpushdim (lua_State *L, int c, struct searchdim *v)
+{
+	int i;
+	lua_createtable(L, c, 0);/* I guess narr is numeric indices??? */
+	for (i = 0; i < c; ++i)
+	{
+		lua_createtable(L, 0, 2);/* and hashed indices (field)... */
+			lua_pushnumber(L, v[i].pos);
+			lua_setfield(L, -2, "pos");
+
+			lua_pushnumber(L, v[i].siz);
+			lua_setfield(L, -2, "siz");
+		lua_rawseti(L, -2, 1 + i);
+	}
+}
+
+/*
+I decided to make this return a table because userdata
+is scary and tables let the user set their own fields.
+*/
+/*
+Returns:
+
+[1] => map number
+[2] => map title
+[3] => search frequency table
+
+The frequency table is unsorted. It has the following format:
+
+{
+	['mapnum'],
+
+	['matchd'] => matches in map title string
+	['keywhd'] => matches in map keywords
+
+	The above two tables have the following format:
+
+	{
+		['pos'] => offset from start of string
+		['siz'] => length of match
+	}...
+
+	['total'] => the total matches
+}...
+*/
+static int lib_gFindMap(lua_State *L)
+{
+	const char *query = luaL_checkstring(L, 1);
+
+	INT32 map;
+	char *realname;
+	INT32 frc;
+	mapsearchfreq_t *frv;
+
+	INT32 i;
+
+	map = G_FindMap(query, &realname, &frv, &frc);
+
+	lua_settop(L, 0);
+
+	lua_pushnumber(L, map);
+	lua_pushstring(L, realname);
+
+	lua_createtable(L, frc, 0);
+	for (i = 0; i < frc; ++i)
+	{
+		lua_createtable(L, 0, 4);
+			lua_pushnumber(L, frv[i].mapnum);
+			lua_setfield(L, -2, "mapnum");
+
+			Lpushdim(L, frv[i].matchc, frv[i].matchd);
+			lua_setfield(L, -2, "matchd");
+
+			Lpushdim(L, frv[i].keywhc, frv[i].keywhd);
+			lua_setfield(L, -2, "keywhd");
+
+			lua_pushnumber(L, frv[i].total);
+			lua_setfield(L, -2, "total");
+		lua_rawseti(L, -2, 1 + i);
+	}
+
+	G_FreeMapSearch(frv, frc);
+	Z_Free(realname);
+
+	return 3;
+}
+
+/*
+Returns:
+
+[1] => map number
+[2] => map title
+*/
+static int lib_gFindMapByNameOrCode(lua_State *L)
+{
+	const char *query = luaL_checkstring(L, 1);
+	INT32 map;
+	char *realname;
+	map = G_FindMapByNameOrCode(query, &realname);
+	lua_pushnumber(L, map);
+	if (map)
+	{
+		lua_pushstring(L, realname);
+		Z_Free(realname);
+		return 2;
+	}
+	else
+		return 1;
+}
+
 static int lib_gDoReborn(lua_State *L)
 {
 	INT32 playernum = luaL_checkinteger(L, 1);
@@ -3200,6 +3753,8 @@ static luaL_Reg lib[] = {
 	{"chatprint", lib_chatprint},
 	{"chatprintf", lib_chatprintf},
 	{"userdataType", lib_userdataType},
+	{"registerMetatable", lib_registerMetatable},
+	{"userdataMetatable", lib_userdataMetatable},
 	{"IsPlayerAdmin", lib_isPlayerAdmin},
 	{"reserveLuabanks", lib_reserveLuabanks},
 
@@ -3259,6 +3814,16 @@ static luaL_Reg lib[] = {
 	{"P_CheckSolidLava",lib_pCheckSolidLava},
 	{"P_CanRunOnWater",lib_pCanRunOnWater},
 	{"P_MaceRotate",lib_pMaceRotate},
+	{"P_CreateFloorSpriteSlope",lib_pCreateFloorSpriteSlope},
+	{"P_RemoveFloorSpriteSlope",lib_pRemoveFloorSpriteSlope},
+	{"P_RailThinker",lib_pRailThinker},
+	{"P_XYMovement",lib_pXYMovement},
+	{"P_RingXYMovement",lib_pRingXYMovement},
+	{"P_SceneryXYMovement",lib_pSceneryXYMovement},
+	{"P_ZMovement",lib_pZMovement},
+	{"P_RingZMovement",lib_pRingZMovement},
+	{"P_SceneryZMovement",lib_pSceneryZMovement},
+	{"P_PlayerZMovement",lib_pPlayerZMovement},
 
 	// p_user
 	{"P_GetPlayerHeight",lib_pGetPlayerHeight},
@@ -3271,6 +3836,7 @@ static luaL_Reg lib[] = {
 	{"P_DoPlayerPain",lib_pDoPlayerPain},
 	{"P_ResetPlayer",lib_pResetPlayer},
 	{"P_PlayerCanDamage",lib_pPlayerCanDamage},
+	{"P_PlayerFullbright",lib_pPlayerFullbright},
 	{"P_IsObjectInGoop",lib_pIsObjectInGoop},
 	{"P_IsObjectOnGround",lib_pIsObjectOnGround},
 	{"P_InSpaceSector",lib_pInSpaceSector},
@@ -3290,6 +3856,7 @@ static luaL_Reg lib[] = {
 	{"P_BlackOw",lib_pBlackOw},
 	{"P_ElementalFire",lib_pElementalFire},
 	{"P_SpawnSkidDust", lib_pSpawnSkidDust},
+	{"P_MovePlayer",lib_pMovePlayer},
 	{"P_DoPlayerFinish",lib_pDoPlayerFinish},
 	{"P_DoPlayerExit",lib_pDoPlayerExit},
 	{"P_InstaThrust",lib_pInstaThrust},
@@ -3317,6 +3884,7 @@ static luaL_Reg lib[] = {
 	{"P_CheckHoopPosition",lib_pCheckHoopPosition},
 	{"P_RadiusAttack",lib_pRadiusAttack},
 	{"P_FloorzAtPos",lib_pFloorzAtPos},
+	{"P_CeilingzAtPos",lib_pCeilingzAtPos},
 	{"P_DoSpring",lib_pDoSpring},
 
 	// p_inter
@@ -3385,6 +3953,7 @@ static luaL_Reg lib[] = {
 
 	// r_draw
 	{"R_GetColorByName", lib_rGetColorByName},
+	{"R_GetSuperColorByName", lib_rGetSuperColorByName},
 	{"R_GetNameByColor", lib_rGetNameByColor},
 
 	// s_sound
@@ -3406,11 +3975,22 @@ static luaL_Reg lib[] = {
 	{"S_IdPlaying",lib_sIdPlaying},
 	{"S_SoundPlaying",lib_sSoundPlaying},
 	{"S_StartMusicCaption", lib_sStartMusicCaption},
+	{"S_MusicType",lib_sMusicType},
+	{"S_MusicPlaying",lib_sMusicPlaying},
+	{"S_MusicPaused",lib_sMusicPaused},
+	{"S_MusicName",lib_sMusicName},
+	{"S_MusicExists",lib_sMusicExists},
+	{"S_SetMusicLoopPoint",lib_sSetMusicLoopPoint},
+	{"S_GetMusicLoopPoint",lib_sGetMusicLoopPoint},
+	{"S_PauseMusic",lib_sPauseMusic},
+	{"S_ResumeMusic", lib_sResumeMusic},
 
 	// g_game
 	{"G_AddGametype", lib_gAddGametype},
 	{"G_BuildMapName",lib_gBuildMapName},
 	{"G_BuildMapTitle",lib_gBuildMapTitle},
+	{"G_FindMap",lib_gFindMap},
+	{"G_FindMapByNameOrCode",lib_gFindMapByNameOrCode},
 	{"G_DoReborn",lib_gDoReborn},
 	{"G_SetCustomExitVars",lib_gSetCustomExitVars},
 	{"G_EnoughPlayersFinished",lib_gEnoughPlayersFinished},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index 5aae73284d985f34c16d11498aa35105fdba92ba..1949d56bb56fdca88c46005ae3118affc4e9c1df 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2016 by Iestyn "Monster Iestyn" Jealous.
+// Copyright (C) 2016-2020 by Iestyn "Monster Iestyn" Jealous.
 // Copyright (C) 2016-2020 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -13,6 +13,7 @@
 #include "doomdef.h"
 #include "p_local.h"
 #include "r_main.h" // validcount
+#include "p_polyobj.h"
 #include "lua_script.h"
 #include "lua_libs.h"
 //#include "lua_hud.h" // hud_running errors
@@ -20,6 +21,7 @@
 static const char *const search_opt[] = {
 	"objects",
 	"lines",
+	"polyobjs",
 	NULL};
 
 // a quickly-made function pointer typedef used by lib_searchBlockmap...
@@ -167,6 +169,55 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th
 	return 0; // Everything was checked.
 }
 
+// Helper function for "polyobjs" search
+static UINT8 lib_searchBlockmap_PolyObjs(lua_State *L, INT32 x, INT32 y, mobj_t *thing)
+{
+	INT32 offset;
+	polymaplink_t *plink; // haleyjd 02/22/06
+
+	if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+		return 0;
+
+	offset = y*bmapwidth + x;
+
+	// haleyjd 02/22/06: consider polyobject lines
+	plink = polyblocklinks[offset];
+
+	while (plink)
+	{
+		polyobj_t *po = plink->po;
+
+		if (po->validcount != validcount) // if polyobj hasn't been checked
+		{
+			po->validcount = validcount;
+
+			lua_pushvalue(L, 1);
+			LUA_PushUserdata(L, thing, META_MOBJ);
+			LUA_PushUserdata(L, po, META_POLYOBJ);
+			if (lua_pcall(gL, 2, 1, 0)) {
+				if (!blockfuncerror || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				blockfuncerror = true;
+				return 0; // *shrugs*
+			}
+			if (!lua_isnil(gL, -1))
+			{ // if nil, continue
+				if (lua_toboolean(gL, -1))
+					return 2; // stop whole search
+				else
+					return 1; // stop block search
+			}
+			lua_pop(gL, 1);
+			if (P_MobjWasRemoved(thing))
+				return 2;
+		}
+		plink = (polymaplink_t *)(plink->link.next);
+	}
+
+	return 0; // Everything was checked.
+}
+
 // The searchBlockmap function
 // arguments: searchBlockmap(searchtype, function, mobj, [x1, x2, y1, y2])
 // return value:
@@ -195,6 +246,9 @@ static int lib_searchBlockmap(lua_State *L)
 		case 1: // "lines"
 			searchFunc = lib_searchBlockmap_Lines;
 			break;
+		case 2: // "polyobjs"
+			searchFunc = lib_searchBlockmap_PolyObjs;
+			break;
 	}
 
 	// the mobj we are searching around, the "calling" mobj we could say
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 4fe234deeecc88f6643d04bd0663a070b3fa85be..5344fee7617aacf789e577eed98de26b3296818a 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -40,6 +40,10 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum)
 	// like sending random junk lua commands to crash the server
 
 	if (!gL) goto deny;
+
+	lua_settop(gL, 0); // Just in case...
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
 	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
 	if (!lua_istable(gL, -1)) goto deny;
 
@@ -76,7 +80,7 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum)
 		READSTRINGN(*cp, buf, 255);
 		lua_pushstring(gL, buf);
 	}
-	LUA_Call(gL, (int)argc); // argc is 1-based, so this will cover the player we passed too.
+	LUA_Call(gL, (int)argc, 0, 1); // argc is 1-based, so this will cover the player we passed too.
 	return;
 
 deny:
@@ -98,6 +102,10 @@ void COM_Lua_f(void)
 	INT32 playernum = consoleplayer;
 
 	I_Assert(gL != NULL);
+
+	lua_settop(gL, 0); // Just in case...
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
 	lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command
 	I_Assert(lua_istable(gL, -1));
 
@@ -167,7 +175,7 @@ void COM_Lua_f(void)
 	LUA_PushUserdata(gL, &players[playernum], META_PLAYER);
 	for (i = 1; i < COM_Argc(); i++)
 		lua_pushstring(gL, COM_Argv(i));
-	LUA_Call(gL, (int)COM_Argc()); // COM_Argc is 1-based, so this will cover the player we passed too.
+	LUA_Call(gL, (int)COM_Argc(), 0, 1); // COM_Argc is 1-based, so this will cover the player we passed too.
 }
 
 // Wrapper for COM_AddCommand
@@ -236,15 +244,14 @@ static int lib_comAddCommand(lua_State *L)
 static int lib_comBufAddText(lua_State *L)
 {
 	int n = lua_gettop(L);  /* number of arguments */
-	player_t *plr;
+	player_t *plr = NULL;
 	if (n < 2)
 		return luaL_error(L, "COM_BufAddText requires two arguments: player and text.");
 	NOHUD
 	lua_settop(L, 2);
-	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
-	if (!plr)
-		return LUA_ErrInvalid(L, "player_t");
-	if (plr != &players[consoleplayer])
+	if (!lua_isnoneornil(L, 1))
+		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	if (plr && plr != &players[consoleplayer])
 		return 0;
 	COM_BufAddTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
 	return 0;
@@ -253,15 +260,14 @@ static int lib_comBufAddText(lua_State *L)
 static int lib_comBufInsertText(lua_State *L)
 {
 	int n = lua_gettop(L);  /* number of arguments */
-	player_t *plr;
+	player_t *plr = NULL;
 	if (n < 2)
 		return luaL_error(L, "COM_BufInsertText requires two arguments: player and text.");
 	NOHUD
 	lua_settop(L, 2);
-	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
-	if (!plr)
-		return LUA_ErrInvalid(L, "player_t");
-	if (plr != &players[consoleplayer])
+	if (!lua_isnoneornil(L, 1))
+		plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	if (plr && plr != &players[consoleplayer])
 		return 0;
 	COM_BufInsertTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
 	return 0;
@@ -279,6 +285,9 @@ static void Lua_OnChange(void)
 
 	/// \todo Network this! XD_LUAVAR
 
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	lua_insert(gL, 1); // Because LUA_Call wants it at index 1.
+
 	// From CV_OnChange registry field, get the function for this cvar by name.
 	lua_getfield(gL, LUA_REGISTRYINDEX, "CV_OnChange");
 	I_Assert(lua_istable(gL, -1));
@@ -290,8 +299,9 @@ static void Lua_OnChange(void)
 	lua_getfield(gL, -1, cvname); // get consvar_t* userdata.
 	lua_remove(gL, -2); // pop the CV_Vars table.
 
-	LUA_Call(gL, 1); // call function(cvar)
+	LUA_Call(gL, 1, 0, 1); // call function(cvar)
 	lua_pop(gL, 1); // pop CV_OnChange table
+	lua_remove(gL, 1); // remove LUA_GetErrorMessage
 }
 
 static int lib_cvRegisterVar(lua_State *L)
@@ -434,6 +444,54 @@ static int lib_cvFindVar(lua_State *L)
 	return 1;
 }
 
+static int CVarSetFunction
+(
+		lua_State *L,
+		void (*Set)(consvar_t *, const char *),
+		void (*SetValue)(consvar_t *, INT32)
+){
+	consvar_t *cvar = (consvar_t *)luaL_checkudata(L, 1, META_CVAR);
+
+	if (cvar->flags & CV_NOLUA)
+		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);
+
+	switch (lua_type(L, 2))
+	{
+		case LUA_TSTRING:
+			(*Set)(cvar, lua_tostring(L, 2));
+			break;
+		case LUA_TNUMBER:
+			(*SetValue)(cvar, (INT32)lua_tonumber(L, 2));
+			break;
+		default:
+			return luaL_typerror(L, 1, "string or number");
+	}
+
+	return 0;
+}
+
+static int lib_cvSet(lua_State *L)
+{
+	return CVarSetFunction(L, CV_Set, CV_SetValue);
+}
+
+static int lib_cvStealthSet(lua_State *L)
+{
+	return CVarSetFunction(L, CV_StealthSet, CV_StealthSetValue);
+}
+
+static int lib_cvAddValue(lua_State *L)
+{
+	consvar_t *cvar = (consvar_t *)luaL_checkudata(L, 1, META_CVAR);
+
+	if (cvar->flags & CV_NOLUA)
+		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);
+
+	CV_AddValue(cvar, (INT32)luaL_checknumber(L, 2));
+
+	return 0;
+}
+
 // CONS_Printf for a single player
 // Use 'print' in baselib for a global message.
 static int lib_consPrintf(lua_State *L)
@@ -444,7 +502,7 @@ static int lib_consPrintf(lua_State *L)
 	if (n < 2)
 		return luaL_error(L, "CONS_Printf requires at least two arguments: player and text.");
 	//HUDSAFE
-	INLEVEL
+
 	plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
 	if (!plr)
 		return LUA_ErrInvalid(L, "player_t");
@@ -474,6 +532,9 @@ static luaL_Reg lib[] = {
 	{"COM_BufInsertText", lib_comBufInsertText},
 	{"CV_RegisterVar", lib_cvRegisterVar},
 	{"CV_FindVar", lib_cvFindVar},
+	{"CV_Set", lib_cvSet},
+	{"CV_StealthSet", lib_cvStealthSet},
+	{"CV_AddValue", lib_cvAddValue},
 	{"CONS_Printf", lib_consPrintf},
 	{NULL, NULL}
 };
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 48f6cab32c51ceb695d325b47196341033d0a6a7..5cfcb8360149093bd7340c0301b679b7d2b03698 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -59,11 +59,15 @@ enum hook {
 	hook_PlayerThink,
 	hook_ShouldJingleContinue,
 	hook_GameQuit,
+	hook_PlayerCmd,
+	hook_MusicChange,
 
 	hook_MAX // last hook
 };
 extern const char *const hookNames[];
 
+extern boolean hook_cmd_running;
+
 void LUAh_MapChange(INT16 mapnumber); // Hook for map change (before load)
 void LUAh_MapLoad(void); // Hook for map load
 void LUAh_PlayerJoin(int playernum); // Hook for Got_AddPlayer
@@ -108,9 +112,9 @@ void LUAh_PlayerQuit(player_t *plr, kickreason_t reason); // Hook for player qui
 void LUAh_IntermissionThinker(void); // Hook for Y_Ticker
 boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh....
 UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode
-#ifdef SEENAMES
 boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend); // Hook for MT_NAMECHECK
-#endif
 #define LUAh_PlayerThink(player) LUAh_PlayerHook(player, hook_PlayerThink) // Hook for P_PlayerThink
 boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname); // Hook for whether a jingle of the given music should continue playing
-void LUAh_GameQuit(void); // Hook for game quitting
\ No newline at end of file
+void LUAh_GameQuit(boolean quitting); // Hook for game quitting
+boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd); // Hook for building player's ticcmd struct (Ported from SRB2Kart)
+boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping, UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms); // Hook for music changes
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 5cfd1bd3d461f49e774593dd87f0daac77ee08e1..29c15a4de9887430b90323e505a2d4235bcf0a76 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -23,6 +23,10 @@
 #include "lua_hook.h"
 #include "lua_hud.h" // hud_running errors
 
+#include "m_perfstats.h"
+#include "d_netcmd.h" // for cv_perfstats
+#include "i_system.h" // I_GetPreciseTime
+
 static UINT8 hooksAvailable[(hook_MAX/8)+1];
 
 const char *const hookNames[hook_MAX+1] = {
@@ -71,6 +75,8 @@ const char *const hookNames[hook_MAX+1] = {
 	"PlayerThink",
 	"ShouldJingleContinue",
 	"GameQuit",
+	"PlayerCmd",
+	"MusicChange",
 	NULL
 };
 
@@ -259,6 +265,9 @@ boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 
 	I_Assert(mo->type < NUMMOBJTYPES);
 
+	if (!(mobjhooks[MT_NULL] || mobjhooks[mo->type]))
+		return false;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -268,6 +277,7 @@ boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 		if (hookp->type != which)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 			LUA_PushUserdata(gL, mo, META_MOBJ);
 		PushHook(gL, hookp);
@@ -289,6 +299,7 @@ boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
 		if (hookp->type != which)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 			LUA_PushUserdata(gL, mo, META_MOBJ);
 		PushHook(gL, hookp);
@@ -324,6 +335,7 @@ boolean LUAh_PlayerHook(player_t *plr, enum hook which)
 		if (hookp->type != which)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 			LUA_PushUserdata(gL, plr, META_PLAYER);
 		PushHook(gL, hookp);
@@ -455,6 +467,9 @@ void LUAh_PreThinkFrame(void)
 void LUAh_ThinkFrame(void)
 {
 	hook_p hookp;
+	// variables used by perf stats
+	int hook_index = 0;
+	int time_taken = 0;
 	if (!gL || !(hooksAvailable[hook_ThinkFrame/8] & (1<<(hook_ThinkFrame%8))))
 		return;
 
@@ -465,6 +480,8 @@ void LUAh_ThinkFrame(void)
 		if (hookp->type != hook_ThinkFrame)
 			continue;
 
+		if (cv_perfstats.value == 3)
+			time_taken = I_GetPreciseTime();
 		PushHook(gL, hookp);
 		if (lua_pcall(gL, 0, 0, 1)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
@@ -472,6 +489,16 @@ void LUAh_ThinkFrame(void)
 			lua_pop(gL, 1);
 			hookp->error = true;
 		}
+		if (cv_perfstats.value == 3)
+		{
+			lua_Debug ar;
+			time_taken = I_GetPreciseTime() - time_taken;
+			// we need the function, let's just retrieve it again
+			PushHook(gL, hookp);
+			lua_getinfo(gL, ">S", &ar);
+			PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src);
+			hook_index++;
+		}
 	}
 
 	lua_pop(gL, 1); // Pop error handler
@@ -513,6 +540,9 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
 
 	I_Assert(thing1->type < NUMMOBJTYPES);
 
+	if (!(mobjcollidehooks[MT_NULL] || mobjcollidehooks[thing1->type]))
+		return 0;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -522,6 +552,7 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
 		if (hookp->type != which)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, thing1, META_MOBJ);
@@ -552,6 +583,7 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
 		if (hookp->type != which)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, thing1, META_MOBJ);
@@ -590,6 +622,9 @@ UINT8 LUAh_MobjLineCollideHook(mobj_t *thing, line_t *line, enum hook which)
 
 	I_Assert(thing->type < NUMMOBJTYPES);
 
+	if (!(mobjcollidehooks[MT_NULL] || mobjcollidehooks[thing->type]))
+		return 0;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -599,6 +634,7 @@ UINT8 LUAh_MobjLineCollideHook(mobj_t *thing, line_t *line, enum hook which)
 		if (hookp->type != which)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, thing, META_MOBJ);
@@ -629,6 +665,7 @@ UINT8 LUAh_MobjLineCollideHook(mobj_t *thing, line_t *line, enum hook which)
 		if (hookp->type != which)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, thing, META_MOBJ);
@@ -668,12 +705,16 @@ boolean LUAh_MobjThinker(mobj_t *mo)
 
 	I_Assert(mo->type < NUMMOBJTYPES);
 
+	if (!(mobjthinkerhooks[MT_NULL] || mobjthinkerhooks[mo->type]))
+		return false;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
 	// Look for all generic mobj thinker hooks
 	for (hookp = mobjthinkerhooks[MT_NULL]; hookp; hookp = hookp->next)
 	{
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 			LUA_PushUserdata(gL, mo, META_MOBJ);
 		PushHook(gL, hookp);
@@ -692,6 +733,7 @@ boolean LUAh_MobjThinker(mobj_t *mo)
 
 	for (hookp = mobjthinkerhooks[mo->type]; hookp; hookp = hookp->next)
 	{
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 			LUA_PushUserdata(gL, mo, META_MOBJ);
 		PushHook(gL, hookp);
@@ -718,10 +760,13 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 	hook_p hookp;
 	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_TouchSpecial/8] & (1<<(hook_TouchSpecial%8))))
-		return 0;
+		return false;
 
 	I_Assert(special->type < NUMMOBJTYPES);
 
+	if (!(mobjhooks[MT_NULL] || mobjhooks[special->type]))
+		return false;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -731,6 +776,7 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 		if (hookp->type != hook_TouchSpecial)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, special, META_MOBJ);
@@ -756,6 +802,7 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
 		if (hookp->type != hook_TouchSpecial)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, special, META_MOBJ);
@@ -790,6 +837,9 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 
 	I_Assert(target->type < NUMMOBJTYPES);
 
+	if (!(mobjhooks[MT_NULL] || mobjhooks[target->type]))
+		return 0;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -799,6 +849,7 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 		if (hookp->type != hook_ShouldDamage)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, target, META_MOBJ);
@@ -834,6 +885,7 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 	{
 		if (hookp->type != hook_ShouldDamage)
 			continue;
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, target, META_MOBJ);
@@ -875,10 +927,13 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 	hook_p hookp;
 	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_MobjDamage/8] & (1<<(hook_MobjDamage%8))))
-		return 0;
+		return false;
 
 	I_Assert(target->type < NUMMOBJTYPES);
 
+	if (!(mobjhooks[MT_NULL] || mobjhooks[target->type]))
+		return false;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -888,6 +943,7 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 		if (hookp->type != hook_MobjDamage)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, target, META_MOBJ);
@@ -919,6 +975,7 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 		if (hookp->type != hook_MobjDamage)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, target, META_MOBJ);
@@ -955,10 +1012,13 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8
 	hook_p hookp;
 	boolean hooked = false;
 	if (!gL || !(hooksAvailable[hook_MobjDeath/8] & (1<<(hook_MobjDeath%8))))
-		return 0;
+		return false;
 
 	I_Assert(target->type < NUMMOBJTYPES);
 
+	if (!(mobjhooks[MT_NULL] || mobjhooks[target->type]))
+		return false;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -968,6 +1028,7 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8
 		if (hookp->type != hook_MobjDeath)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, target, META_MOBJ);
@@ -997,6 +1058,7 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8
 		if (hookp->type != hook_MobjDeath)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, target, META_MOBJ);
@@ -1186,9 +1248,10 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
 
 	for (hookp = linedefexecutorhooks; hookp; hookp = hookp->next)
 	{
-		if (strcmp(hookp->s.str, line->text))
+		if (strcmp(hookp->s.str, line->stringargs[0]))
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, line, META_LINE);
@@ -1354,6 +1417,9 @@ boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
 	if (!gL || !(hooksAvailable[hook_MapThingSpawn/8] & (1<<(hook_MapThingSpawn%8))))
 		return false;
 
+	if (!(mobjhooks[MT_NULL] || mobjhooks[mo->type]))
+		return false;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -1363,6 +1429,7 @@ boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
 		if (hookp->type != hook_MapThingSpawn)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, mo, META_MOBJ);
@@ -1388,6 +1455,7 @@ boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
 		if (hookp->type != hook_MapThingSpawn)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, mo, META_MOBJ);
@@ -1420,6 +1488,9 @@ boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj)
 	if (!gL || !(hooksAvailable[hook_FollowMobj/8] & (1<<(hook_FollowMobj%8))))
 		return 0;
 
+	if (!(mobjhooks[MT_NULL] || mobjhooks[mobj->type]))
+		return 0;
+
 	lua_settop(gL, 0);
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
@@ -1429,6 +1500,7 @@ boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj)
 		if (hookp->type != hook_FollowMobj)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, player, META_PLAYER);
@@ -1454,6 +1526,7 @@ boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj)
 		if (hookp->type != hook_FollowMobj)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, player, META_PLAYER);
@@ -1494,6 +1567,7 @@ UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj)
 		if (hookp->type != hook_PlayerCanDamage)
 			continue;
 
+		ps_lua_mobjhooks++;
 		if (lua_gettop(gL) == 1)
 		{
 			LUA_PushUserdata(gL, player, META_PLAYER);
@@ -1680,7 +1754,6 @@ UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean
 }
 
 // Hook for MT_NAMECHECK
-#ifdef SEENAMES
 boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend)
 {
 	hook_p hookp;
@@ -1724,7 +1797,6 @@ boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend)
 
 	return hasSeenPlayer;
 }
-#endif // SEENAMES
 
 boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname)
 {
@@ -1772,7 +1844,7 @@ boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname)
 }
 
 // Hook for game quitting
-void LUAh_GameQuit(void)
+void LUAh_GameQuit(boolean quitting)
 {
 	hook_p hookp;
 	if (!gL || !(hooksAvailable[hook_GameQuit/8] & (1<<(hook_GameQuit%8))))
@@ -1786,13 +1858,116 @@ void LUAh_GameQuit(void)
 			continue;
 
 		PushHook(gL, hookp);
-		if (lua_pcall(gL, 0, 0, 1)) {
+		lua_pushboolean(gL, quitting);
+		if (lua_pcall(gL, 1, 0, 1)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
 				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
 			hookp->error = true;
 		}
 	}
-	
+
 	lua_pop(gL, 1); // Pop error handler
 }
+
+// Hook for building player's ticcmd struct (Ported from SRB2Kart)
+boolean hook_cmd_running = false;
+boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd)
+{
+	hook_p hookp;
+	boolean hooked = false;
+	if (!gL || !(hooksAvailable[hook_PlayerCmd/8] & (1<<(hook_PlayerCmd%8))))
+		return false;
+
+	lua_settop(gL, 0);
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
+	hook_cmd_running = true;
+	for (hookp = roothook; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_PlayerCmd)
+			continue;
+
+		if (lua_gettop(gL) == 1)
+		{
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, cmd, META_TICCMD);
+		}
+		PushHook(gL, hookp);
+		lua_pushvalue(gL, -3);
+		lua_pushvalue(gL, -3);
+		if (lua_pcall(gL, 2, 1, 1)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+			continue;
+		}
+		if (lua_toboolean(gL, -1))
+			hooked = true;
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	hook_cmd_running = false;
+	return hooked;
+}
+
+// Hook for music changes
+boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping,
+	UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms)
+{
+	hook_p hookp;
+	boolean hooked = false;
+
+	if (!gL || !(hooksAvailable[hook_MusicChange/8] & (1<<(hook_MusicChange%8))))
+		return false;
+
+	lua_settop(gL, 0);
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MusicChange)
+		{
+			PushHook(gL, hookp);
+			lua_pushstring(gL, oldname);
+			lua_pushstring(gL, newname);
+			lua_pushinteger(gL, *mflags);
+			lua_pushboolean(gL, *looping);
+			lua_pushinteger(gL, *position);
+			lua_pushinteger(gL, *prefadems);
+			lua_pushinteger(gL, *fadeinms);
+			if (lua_pcall(gL, 7, 6, 1)) {
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+				lua_pop(gL, 1);
+				continue;
+			}
+
+			// output 1: true, false, or string musicname override
+			if (lua_isboolean(gL, -6) && lua_toboolean(gL, -6))
+				hooked = true;
+			else if (lua_isstring(gL, -6))
+				strncpy(newname, lua_tostring(gL, -6), 7);
+			// output 2: mflags override
+			if (lua_isnumber(gL, -5))
+				*mflags = lua_tonumber(gL, -5);
+			// output 3: looping override
+			if (lua_isboolean(gL, -4))
+				*looping = lua_toboolean(gL, -4);
+			// output 4: position override
+			if (lua_isboolean(gL, -3))
+				*position = lua_tonumber(gL, -3);
+			// output 5: prefadems override
+			if (lua_isboolean(gL, -2))
+				*prefadems = lua_tonumber(gL, -2);
+			// output 6: fadeinms override
+			if (lua_isboolean(gL, -1))
+				*fadeinms = lua_tonumber(gL, -1);
+
+			lua_pop(gL, 7);  // Pop returned values and error handler
+		}
+
+	lua_settop(gL, 0);
+	newname[6] = 0;
+	return hooked;
+}
diff --git a/src/lua_hud.h b/src/lua_hud.h
index 4a7c596c8a95e7d343fd9da73b8aa50bcaa344b0..1e9dca00b921abe7e3c0baa2413fc015668e5fa8 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -13,6 +13,7 @@
 enum hud {
 	hud_stagetitle = 0,
 	hud_textspectator,
+	hud_crosshair,
 	// Singleplayer / Co-op
 	hud_score,
 	hud_time,
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 4aa70574b3802b62b22cbc4772fb11aa216cdd61..8d451e99c5100bc2370974ea977ea055fed04595 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -35,15 +35,11 @@ static UINT8 hud_enabled[(hud_MAX/8)+1];
 
 static UINT8 hudAvailable; // hud hooks field
 
-#ifdef LUA_PATCH_SAFETY
-static patchinfo_t *patchinfo, *patchinfohead;
-static int numluapatches;
-#endif
-
 // must match enum hud in lua_hud.h
 static const char *const hud_disable_options[] = {
 	"stagetitle",
 	"textspectator",
+	"crosshair",
 
 	"score",
 	"time",
@@ -292,11 +288,7 @@ static int colormap_get(lua_State *L)
 
 static int patch_get(lua_State *L)
 {
-#ifdef LUA_PATCH_SAFETY
 	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
-#else
-	patchinfo_t *patch = *((patchinfo_t **)luaL_checkudata(L, 1, META_PATCH));
-#endif
 	enum patch field = luaL_checkoption(L, 2, NULL, patch_opt);
 
 	// patches are invalidated when switching renderers
@@ -314,16 +306,16 @@ static int patch_get(lua_State *L)
 		lua_pushboolean(L, patch != NULL);
 		break;
 	case patch_width:
-		lua_pushinteger(L, SHORT(patch->width));
+		lua_pushinteger(L, patch->width);
 		break;
 	case patch_height:
-		lua_pushinteger(L, SHORT(patch->height));
+		lua_pushinteger(L, patch->height);
 		break;
 	case patch_leftoffset:
-		lua_pushinteger(L, SHORT(patch->leftoffset));
+		lua_pushinteger(L, patch->leftoffset);
 		break;
 	case patch_topoffset:
-		lua_pushinteger(L, SHORT(patch->topoffset));
+		lua_pushinteger(L, patch->topoffset);
 		break;
 	}
 	return 1;
@@ -403,59 +395,8 @@ static int libd_patchExists(lua_State *L)
 
 static int libd_cachePatch(lua_State *L)
 {
-#ifdef LUA_PATCH_SAFETY
-	int i;
-	lumpnum_t lumpnum;
-	patchinfo_t *luapat;
-	patch_t *realpatch;
-
-	HUDONLY
-
-	luapat = patchinfohead;
-	lumpnum = W_CheckNumForLongName(luaL_checkstring(L, 1));
-	if (lumpnum == LUMPERROR)
-		lumpnum = W_GetNumForLongName("MISSING");
-
-	for (i = 0; i < numluapatches; i++)
-	{
-		// check if already cached
-		if (luapat->wadnum == WADFILENUM(lumpnum) && luapat->lumpnum == LUMPNUM(lumpnum))
-		{
-			LUA_PushUserdata(L, luapat, META_PATCH);
-			return 1;
-		}
-		luapat = luapat->next;
-		if (!luapat)
-			break;
-	}
-
-	if (numluapatches > 0)
-	{
-		patchinfo->next = Z_Malloc(sizeof(patchinfo_t), PU_STATIC, NULL);
-		patchinfo = patchinfo->next;
-	}
-	else
-	{
-		patchinfo = Z_Malloc(sizeof(patchinfo_t), PU_STATIC, NULL);
-		patchinfohead = patchinfo;
-	}
-
-	realpatch = W_CachePatchNum(lumpnum, PU_PATCH);
-
-	patchinfo->width = realpatch->width;
-	patchinfo->height = realpatch->height;
-	patchinfo->leftoffset = realpatch->leftoffset;
-	patchinfo->topoffset = realpatch->topoffset;
-
-	patchinfo->wadnum = WADFILENUM(lumpnum);
-	patchinfo->lumpnum = LUMPNUM(lumpnum);
-
-	LUA_PushUserdata(L, patchinfo, META_PATCH);
-	numluapatches++;
-#else
 	HUDONLY
 	LUA_PushUserdata(L, W_CachePatchLongName(luaL_checkstring(L, 1), PU_PATCH), META_PATCH);
-#endif
 	return 1;
 }
 
@@ -518,9 +459,8 @@ static int libd_getSpritePatch(lua_State *L)
 		INT32 rot = R_GetRollAngle(rollangle);
 
 		if (rot) {
-			if (!(sprframe->rotsprite.cached & (1<<angle)))
-				R_CacheRotSprite(i, frame, NULL, sprframe, angle, sprframe->flip & (1<<angle));
-			LUA_PushUserdata(L, sprframe->rotsprite.patch[angle][rot], META_PATCH);
+			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), true, &spriteinfo[i], rot);
+			LUA_PushUserdata(L, rotsprite, META_PATCH);
 			lua_pushboolean(L, false);
 			lua_pushboolean(L, true);
 			return 3;
@@ -529,7 +469,7 @@ static int libd_getSpritePatch(lua_State *L)
 #endif
 
 	// push both the patch and it's "flip" value
-	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_SPRITE), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
 	return 2;
 }
@@ -631,9 +571,8 @@ static int libd_getSprite2Patch(lua_State *L)
 		INT32 rot = R_GetRollAngle(rollangle);
 
 		if (rot) {
-			if (!(sprframe->rotsprite.cached & (1<<angle)))
-				R_CacheRotSprite(SPR_PLAY, frame, &skins[i].sprinfo[j], sprframe, angle, sprframe->flip & (1<<angle));
-			LUA_PushUserdata(L, sprframe->rotsprite.patch[angle][rot], META_PATCH);
+			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), true, &skins[i].sprinfo[j], rot);
+			LUA_PushUserdata(L, rotsprite, META_PATCH);
 			lua_pushboolean(L, false);
 			lua_pushboolean(L, true);
 			return 3;
@@ -642,7 +581,7 @@ static int libd_getSprite2Patch(lua_State *L)
 #endif
 
 	// push both the patch and it's "flip" value
-	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_SPRITE), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
 	return 2;
 }
@@ -651,22 +590,14 @@ static int libd_draw(lua_State *L)
 {
 	INT32 x, y, flags;
 	patch_t *patch;
-#ifdef LUA_PATCH_SAFETY
-	patchinfo_t *luapat;
-#endif
 	const UINT8 *colormap = NULL;
 
 	HUDONLY
 	x = luaL_checkinteger(L, 1);
 	y = luaL_checkinteger(L, 2);
-#ifdef LUA_PATCH_SAFETY
-	luapat = *((patchinfo_t **)luaL_checkudata(L, 3, META_PATCH));
-	patch = W_CachePatchNum((luapat->wadnum<<16)+luapat->lumpnum, PU_PATCH);
-#else
 	patch = *((patch_t **)luaL_checkudata(L, 3, META_PATCH));
 	if (!patch)
 		return LUA_ErrInvalid(L, "patch_t");
-#endif
 	flags = luaL_optinteger(L, 4, 0);
 	if (!lua_isnoneornil(L, 5))
 		colormap = *((UINT8 **)luaL_checkudata(L, 5, META_COLORMAP));
@@ -682,9 +613,6 @@ static int libd_drawScaled(lua_State *L)
 	fixed_t x, y, scale;
 	INT32 flags;
 	patch_t *patch;
-#ifdef LUA_PATCH_SAFETY
-	patchinfo_t *luapat;
-#endif
 	const UINT8 *colormap = NULL;
 
 	HUDONLY
@@ -693,14 +621,9 @@ static int libd_drawScaled(lua_State *L)
 	scale = luaL_checkinteger(L, 3);
 	if (scale < 0)
 		return luaL_error(L, "negative scale");
-#ifdef LUA_PATCH_SAFETY
-	luapat = *((patchinfo_t **)luaL_checkudata(L, 4, META_PATCH));
-	patch = W_CachePatchNum((luapat->wadnum<<16)+luapat->lumpnum, PU_PATCH);
-#else
 	patch = *((patch_t **)luaL_checkudata(L, 4, META_PATCH));
 	if (!patch)
 		return LUA_ErrInvalid(L, "patch_t");
-#endif
 	flags = luaL_optinteger(L, 5, 0);
 	if (!lua_isnoneornil(L, 6))
 		colormap = *((UINT8 **)luaL_checkudata(L, 6, META_COLORMAP));
@@ -974,8 +897,10 @@ static int libd_getColormap(lua_State *L)
 	else if (lua_type(L, 1) == LUA_TNUMBER) // skin number
 	{
 		skinnum = (INT32)luaL_checkinteger(L, 1);
-		if (skinnum < TC_BLINK || skinnum >= MAXSKINS)
-			return luaL_error(L, "skin number %d is out of range (%d - %d)", skinnum, TC_BLINK, MAXSKINS-1);
+		if (skinnum >= MAXSKINS)
+			return luaL_error(L, "skin number %d is out of range (>%d)", skinnum, MAXSKINS-1);
+		else if (skinnum < 0 && skinnum > TC_DEFAULT)
+			return luaL_error(L, "translation colormap index is out of range");
 	}
 	else // skin name
 	{
@@ -992,6 +917,19 @@ static int libd_getColormap(lua_State *L)
 	return 1;
 }
 
+static int libd_getStringColormap(lua_State *L)
+{
+	INT32 flags = luaL_checkinteger(L, 1);
+	UINT8* colormap = NULL;
+	HUDONLY
+	colormap = V_GetStringColormap(flags & V_CHARCOLORMASK);
+	if (colormap) {
+		LUA_PushUserdata(L, colormap, META_COLORMAP); // push as META_COLORMAP userdata, specifically for patches to use!
+		return 1;
+	}
+	return 0;
+}
+
 static int libd_fadeScreen(lua_State *L)
 {
 	UINT16 color = luaL_checkinteger(L, 1);
@@ -1142,6 +1080,7 @@ static luaL_Reg lib_draw[] = {
 	{"getSpritePatch", libd_getSpritePatch},
 	{"getSprite2Patch", libd_getSprite2Patch},
 	{"getColormap", libd_getColormap},
+	{"getStringColormap", libd_getStringColormap},
 	// drawing
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
@@ -1247,10 +1186,6 @@ int LUA_HudLib(lua_State *L)
 {
 	memset(hud_enabled, 0xff, (hud_MAX/8)+1);
 
-#ifdef LUA_PATCH_SAFETY
-	numluapatches = 0;
-#endif
-
 	lua_newtable(L); // HUD registry table
 		lua_newtable(L);
 		luaL_register(L, NULL, lib_draw);
@@ -1329,7 +1264,9 @@ void LUAh_GameHUD(player_t *stplayr)
 		return;
 
 	hud_running = true;
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
+
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
@@ -1351,9 +1288,9 @@ void LUAh_GameHUD(player_t *stplayr)
 		lua_pushvalue(gL, -5); // graphics library (HUD[1])
 		lua_pushvalue(gL, -5); // stplayr
 		lua_pushvalue(gL, -5); // camera
-		LUA_Call(gL, 3);
+		LUA_Call(gL, 3, 0, 1);
 	}
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
 	hud_running = false;
 }
 
@@ -1363,7 +1300,9 @@ void LUAh_ScoresHUD(void)
 		return;
 
 	hud_running = true;
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
+
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
@@ -1376,9 +1315,9 @@ void LUAh_ScoresHUD(void)
 	lua_pushnil(gL);
 	while (lua_next(gL, -3) != 0) {
 		lua_pushvalue(gL, -3); // graphics library (HUD[1])
-		LUA_Call(gL, 1);
+		LUA_Call(gL, 1, 0, 1);
 	}
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
 	hud_running = false;
 }
 
@@ -1388,7 +1327,9 @@ void LUAh_TitleHUD(void)
 		return;
 
 	hud_running = true;
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
+
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
@@ -1401,9 +1342,9 @@ void LUAh_TitleHUD(void)
 	lua_pushnil(gL);
 	while (lua_next(gL, -3) != 0) {
 		lua_pushvalue(gL, -3); // graphics library (HUD[1])
-		LUA_Call(gL, 1);
+		LUA_Call(gL, 1, 0, 1);
 	}
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
 	hud_running = false;
 }
 
@@ -1413,7 +1354,9 @@ void LUAh_TitleCardHUD(player_t *stplayr)
 		return;
 
 	hud_running = true;
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
+
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
@@ -1434,10 +1377,10 @@ void LUAh_TitleCardHUD(player_t *stplayr)
 		lua_pushvalue(gL, -6); // stplayr
 		lua_pushvalue(gL, -6); // lt_ticker
 		lua_pushvalue(gL, -6); // lt_endtime
-		LUA_Call(gL, 4);
+		LUA_Call(gL, 4, 0, 1);
 	}
 
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
 	hud_running = false;
 }
 
@@ -1447,7 +1390,9 @@ void LUAh_IntermissionHUD(void)
 		return;
 
 	hud_running = true;
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
+
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
 
 	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(gL, -1));
@@ -1460,8 +1405,8 @@ void LUAh_IntermissionHUD(void)
 	lua_pushnil(gL);
 	while (lua_next(gL, -3) != 0) {
 		lua_pushvalue(gL, -3); // graphics library (HUD[1])
-		LUA_Call(gL, 1);
+		LUA_Call(gL, 1, 0, 1);
 	}
-	lua_pop(gL, -1);
+	lua_settop(gL, 0);
 	hud_running = false;
 }
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 81a215c5363c96655b41782f24bb67d30aad671f..6e86f47b7272701efb42497a8fde70692681911a 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -14,21 +14,26 @@
 #include "fastcmp.h"
 #include "info.h"
 #include "dehacked.h"
+#include "deh_tables.h"
+#include "deh_lua.h"
 #include "p_mobj.h"
 #include "p_local.h"
 #include "z_zone.h"
 #include "r_patch.h"
+#include "r_picformats.h"
 #include "r_things.h"
+#include "r_draw.h" // R_GetColorByName
 #include "doomstat.h" // luabanks[]
 
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h" // hook_cmd_running errors
 
-extern CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
-extern void R_FlushTranslationColormapCache(void);
+extern CV_PossibleValue_t Color_cons_t[];
+extern UINT8 skincolor_modified[];
 
-boolean LUA_CallAction(const char *action, mobj_t *actor);
+boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor);
 state_t *astate;
 
 enum sfxinfo_read {
@@ -61,6 +66,8 @@ const char *const sfxinfo_wopt[] = {
 	"caption",
 	NULL};
 
+boolean actionsoverridden[NUMACTIONS] = {false};
+
 //
 // Sprite Names
 //
@@ -164,6 +171,8 @@ static int lib_setSpr2default(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter spr2defaults[] in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter spr2defaults[] in CMD building code!");
 
 // todo: maybe allow setting below first freeslot..? step 1 is toggling this, step 2 is testing to see whether it's net-safe
 #ifdef SETALLSPR2DEFAULTS
@@ -370,16 +379,14 @@ static int lib_setSpriteInfo(lua_State *L)
 		return luaL_error(L, "Do not alter spriteinfo_t from within a hook or coroutine!");
 	if (hud_running)
 		return luaL_error(L, "Do not alter spriteinfo_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter spriteinfo_t in CMD building code!");
 
 	lua_remove(L, 1);
 	{
 		UINT32 i = luaL_checkinteger(L, 1);
 		if (i == 0 || i >= NUMSPRITES)
 			return luaL_error(L, "spriteinfo[] index %d out of range (1 - %d)", i, NUMSPRITES-1);
-#ifdef ROTSPRITE
-		if (sprites != NULL)
-			R_FreeSingleRotSprite(&sprites[i]);
-#endif
 		info = &spriteinfo[i]; // get the spriteinfo to assign to.
 	}
 	luaL_checktype(L, 2, LUA_TTABLE); // check that we've been passed a table.
@@ -454,6 +461,8 @@ static int spriteinfo_set(lua_State *L)
 		return luaL_error(L, "Do not alter spriteinfo_t from within a hook or coroutine!");
 	if (hud_running)
 		return luaL_error(L, "Do not alter spriteinfo_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter spriteinfo_t in CMD building code!");
 
 	I_Assert(sprinfo != NULL);
 
@@ -461,11 +470,6 @@ static int spriteinfo_set(lua_State *L)
 	lua_remove(L, 1); // remove field
 	lua_settop(L, 1); // leave only one value
 
-#ifdef ROTSPRITE
-	if (sprites != NULL)
-		R_FreeSingleRotSprite(&sprites[sprinfo-spriteinfo]);
-#endif
-
 	if (fastcmp(field, "pivot"))
 	{
 		// pivot[] is a table
@@ -532,6 +536,8 @@ static int pivotlist_set(lua_State *L)
 		return luaL_error(L, "Do not alter spriteframepivot_t from within a hook or coroutine!");
 	if (hud_running)
 		return luaL_error(L, "Do not alter spriteframepivot_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter spriteframepivot_t in CMD building code!");
 
 	I_Assert(pivotlist != NULL);
 
@@ -586,6 +592,8 @@ static int framepivot_set(lua_State *L)
 		return luaL_error(L, "Do not alter spriteframepivot_t from within a hook or coroutine!");
 	if (hud_running)
 		return luaL_error(L, "Do not alter spriteframepivot_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter spriteframepivot_t in CMD building code!");
 
 	I_Assert(framepivot != NULL);
 
@@ -618,6 +626,9 @@ static void A_Lua(mobj_t *actor)
 	boolean found = false;
 	I_Assert(actor != NULL);
 
+	lua_settop(gL, 0); // Just in case...
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
 	// get the action for this state
 	lua_getfield(gL, LUA_REGISTRYINDEX, LREG_STATEACTION);
 	I_Assert(lua_istable(gL, -1));
@@ -646,7 +657,7 @@ static void A_Lua(mobj_t *actor)
 	LUA_PushUserdata(gL, actor, META_MOBJ);
 	lua_pushinteger(gL, var1);
 	lua_pushinteger(gL, var2);
-	LUA_Call(gL, 3);
+	LUA_Call(gL, 3, 0, 1);
 
 	if (found)
 	{
@@ -685,6 +696,8 @@ static int lib_setState(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter states in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter states in CMD building code!");
 
 	// clear the state to start with, in case of missing table elements
 	memset(state,0,sizeof(state_t));
@@ -799,36 +812,33 @@ boolean LUA_SetLuaAction(void *stv, const char *action)
 	return true; // action successfully set.
 }
 
-boolean LUA_CallAction(const char *csaction, mobj_t *actor)
+boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor)
 {
-	I_Assert(csaction != NULL);
 	I_Assert(actor != NULL);
 
-	if (!gL) // Lua isn't loaded,
+	if (!actionsoverridden[actionnum]) // The action is not overriden,
 		return false; // action not called.
 
-	if (superstack && fasticmp(csaction, superactions[superstack-1])) // the action is calling itself,
+	if (superstack && fasticmp(actionpointers[actionnum].name, superactions[superstack-1])) // the action is calling itself,
 		return false; // let it call the hardcoded function instead.
 
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
 	// grab function by uppercase name.
 	lua_getfield(gL, LUA_REGISTRYINDEX, LREG_ACTIONS);
-	{
-		char *action = Z_StrDup(csaction);
-		strupr(action);
-		lua_getfield(gL, -1, action);
-		Z_Free(action);
-	}
+	lua_getfield(gL, -1, actionpointers[actionnum].name);
 	lua_remove(gL, -2); // pop LREG_ACTIONS
 
 	if (lua_isnil(gL, -1)) // no match
 	{
-		lua_pop(gL, 1); // pop nil
+		lua_pop(gL, 2); // pop nil and error handler
 		return false; // action not called.
 	}
 
 	if (superstack == MAXRECURSION)
 	{
 		CONS_Alert(CONS_WARNING, "Max Lua Action recursion reached! Cool it on the calling A_Action functions from inside A_Action functions!\n");
+		lua_pop(gL, 2); // pop function and error handler
 		return true;
 	}
 
@@ -839,10 +849,11 @@ boolean LUA_CallAction(const char *csaction, mobj_t *actor)
 	lua_pushinteger(gL, var1);
 	lua_pushinteger(gL, var2);
 
-	superactions[superstack] = csaction;
+	superactions[superstack] = actionpointers[actionnum].name;
 	++superstack;
 
-	LUA_Call(gL, 3);
+	LUA_Call(gL, 3, 0, -(2 + 3));
+	lua_pop(gL, -1); // Error handler
 
 	--superstack;
 	superactions[superstack] = NULL;
@@ -905,6 +916,8 @@ static int state_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter states in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter states in CMD building code!");
 
 	if (fastcmp(field,"sprite")) {
 		value = luaL_checknumber(L, 3);
@@ -1005,6 +1018,8 @@ static int lib_setMobjInfo(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobjinfo in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mobjinfo in CMD building code!");
 
 	// clear the mobjinfo to start with, in case of missing table elements
 	memset(info,0,sizeof(mobjinfo_t));
@@ -1172,6 +1187,8 @@ static int mobjinfo_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobjinfo in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mobjinfo in CMD building code!");
 
 	I_Assert(info != NULL);
 	I_Assert(info >= mobjinfo);
@@ -1294,6 +1311,8 @@ static int lib_setSfxInfo(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter sfxinfo in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter sfxinfo in CMD building code!");
 
 	lua_pushnil(L);
 	while (lua_next(L, 1)) {
@@ -1375,6 +1394,8 @@ static int sfxinfo_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter S_sfx in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter S_sfx in CMD building code!");
 
 	I_Assert(sfx != NULL);
 
@@ -1442,6 +1463,8 @@ static int lib_setluabanks(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter luabanks[] in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter luabanks[] in CMD building code!");
 
 	lua_remove(L, 1); // don't care about luabanks[] dummy userdata.
 
@@ -1490,7 +1513,7 @@ static void setRamp(lua_State *L, skincolor_t* c) {
 	UINT32 i;
 	lua_pushnil(L);
 	for (i=0; i<COLORRAMPSIZE; i++) {
-		if (lua_objlen(L,-2)<COLORRAMPSIZE) {
+		if (lua_objlen(L,-2)!=COLORRAMPSIZE) {
 			luaL_error(L, LUA_QL("skincolor_t") " field 'ramp' must be %d entries long; got %d.", COLORRAMPSIZE, lua_objlen(L,-2));
 			break;
 		}
@@ -1512,8 +1535,8 @@ static int lib_setSkinColor(lua_State *L)
 	lua_remove(L, 1); // don't care about skincolors[] userdata.
 	{
 		cnum = (UINT16)luaL_checkinteger(L, 1);
-		if (cnum < SKINCOLOR_FIRSTFREESLOT || cnum >= numskincolors)
-			return luaL_error(L, "skincolors[] index %d out of range (%d - %d)", cnum, SKINCOLOR_FIRSTFREESLOT, numskincolors-1);
+		if (!cnum || cnum >= numskincolors)
+			return luaL_error(L, "skincolors[] index %d out of range (1 - %d)", cnum, numskincolors-1);
 		info = &skincolors[cnum]; // get the skincolor to assign to.
 	}
 	luaL_checktype(L, 2, LUA_TTABLE); // check that we've been passed a table.
@@ -1522,6 +1545,8 @@ static int lib_setSkinColor(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter skincolors in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter skincolors in CMD building code!");
 
 	// clear the skincolor to start with, in case of missing table elements
 	memset(info,0,sizeof(skincolor_t));
@@ -1540,7 +1565,18 @@ static int lib_setSkinColor(lua_State *L)
 			const char* n = luaL_checkstring(L, 3);
 			strlcpy(info->name, n, MAXCOLORNAME+1);
 			if (strlen(n) > MAXCOLORNAME)
-				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') longer than %d chars; shortened to %s.\n", n, MAXCOLORNAME, info->name);
+				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') longer than %d chars; clipped to %s.\n", n, MAXCOLORNAME, info->name);
+#if 0
+			if (strchr(info->name, ' ') != NULL)
+				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", info->name);
+#endif
+
+			if (info->name[0] != '\0') // don't check empty string for dupe
+			{
+				UINT16 dupecheck = R_GetColorByName(info->name);
+				if (!stricmp(info->name, skincolors[SKINCOLOR_NONE].name) || (dupecheck && (dupecheck != info-skincolors)))
+					CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') is a duplicate of another skincolor's name.\n", info->name);
+			}
 		} else if (i == 2 || (str && fastcmp(str,"ramp"))) {
 			if (!lua_istable(L, 3) && luaL_checkudata(L, 3, META_COLORRAMP) == NULL)
 				return luaL_error(L, LUA_QL("skincolor_t") " field 'ramp' must be a table or array.");
@@ -1549,17 +1585,20 @@ static int lib_setSkinColor(lua_State *L)
 			else
 				for (j=0; j<COLORRAMPSIZE; j++)
 					info->ramp[j] = (*((UINT8 **)luaL_checkudata(L, 3, META_COLORRAMP)))[j];
-			R_FlushTranslationColormapCache();
-		} else if (i == 3 || (str && fastcmp(str,"invcolor")))
-			info->invcolor = (UINT16)luaL_checkinteger(L, 3);
-		else if (i == 4 || (str && fastcmp(str,"invshade")))
+			skincolor_modified[cnum] = true;
+		} else if (i == 3 || (str && fastcmp(str,"invcolor"))) {
+			UINT16 v = (UINT16)luaL_checkinteger(L, 3);
+			if (v >= numskincolors)
+				return luaL_error(L, "skincolor_t field 'invcolor' out of range (1 - %d)", numskincolors-1);
+			info->invcolor = v;
+		} else if (i == 4 || (str && fastcmp(str,"invshade")))
 			info->invshade = (UINT8)luaL_checkinteger(L, 3)%COLORRAMPSIZE;
 		else if (i == 5 || (str && fastcmp(str,"chatcolor")))
 			info->chatcolor = (UINT16)luaL_checkinteger(L, 3);
 		else if (i == 6 || (str && fastcmp(str,"accessible"))) {
-			boolean v = lua_isboolean(L,3) ? lua_toboolean(L, 3) : true;
+			boolean v = lua_toboolean(L, 3);
 			if (cnum < FIRSTSUPERCOLOR && v != skincolors[cnum].accessible)
-				return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", i);
+				return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
 			else
 				info->accessible = v;
 		}
@@ -1596,8 +1635,10 @@ static int skincolor_get(lua_State *L)
 		lua_pushinteger(L, info->chatcolor);
 	else if (fastcmp(field,"accessible"))
 		lua_pushboolean(L, info->accessible);
-	else
+	else {
 		CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "skincolor_t", field);
+		return 0;
+	}
 	return 1;
 }
 
@@ -1607,20 +1648,30 @@ static int skincolor_set(lua_State *L)
 	UINT32 i;
 	skincolor_t *info = *((skincolor_t **)luaL_checkudata(L, 1, META_SKINCOLOR));
 	const char *field = luaL_checkstring(L, 2);
+	UINT16 cnum = (UINT16)(info-skincolors);
 
 	I_Assert(info != NULL);
 	I_Assert(info >= skincolors);
 
-	if (info-skincolors < SKINCOLOR_FIRSTFREESLOT || info-skincolors >= numskincolors)
-		return luaL_error(L, "skincolors[] index %d out of range (%d - %d)", info-skincolors, SKINCOLOR_FIRSTFREESLOT, numskincolors-1);
+	if (!cnum || cnum >= numskincolors)
+		return luaL_error(L, "skincolors[] index %d out of range (1 - %d)", cnum, numskincolors-1);
 
 	if (fastcmp(field,"name")) {
 		const char* n = luaL_checkstring(L, 3);
-		if (strchr(n, ' ') != NULL)
-			CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", n);
 		strlcpy(info->name, n, MAXCOLORNAME+1);
 		if (strlen(n) > MAXCOLORNAME)
 			CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') longer than %d chars; clipped to %s.\n", n, MAXCOLORNAME, info->name);
+#if 0
+		if (strchr(info->name, ' ') != NULL)
+			CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", info->name);
+#endif
+
+		if (info->name[0] != '\0') // don't check empty string for dupe
+		{
+			UINT16 dupecheck = R_GetColorByName(info->name);
+			if (!stricmp(info->name, skincolors[SKINCOLOR_NONE].name) || (dupecheck && (dupecheck != cnum)))
+				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') is a duplicate of another skincolor's name.\n", info->name);
+		}
 	} else if (fastcmp(field,"ramp")) {
 		if (!lua_istable(L, 3) && luaL_checkudata(L, 3, META_COLORRAMP) == NULL)
 			return luaL_error(L, LUA_QL("skincolor_t") " field 'ramp' must be a table or array.");
@@ -1629,16 +1680,23 @@ static int skincolor_set(lua_State *L)
 		else
 			for (i=0; i<COLORRAMPSIZE; i++)
 				info->ramp[i] = (*((UINT8 **)luaL_checkudata(L, 3, META_COLORRAMP)))[i];
-		R_FlushTranslationColormapCache();
-	} else if (fastcmp(field,"invcolor"))
-		info->invcolor = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"invshade"))
+		skincolor_modified[cnum] = true;
+	} else if (fastcmp(field,"invcolor")) {
+		UINT16 v = (UINT16)luaL_checkinteger(L, 3);
+		if (v >= numskincolors)
+			return luaL_error(L, "skincolor_t field 'invcolor' out of range (1 - %d)", numskincolors-1);
+		info->invcolor = v;
+	} else if (fastcmp(field,"invshade"))
 		info->invshade = (UINT8)luaL_checkinteger(L, 3)%COLORRAMPSIZE;
 	else if (fastcmp(field,"chatcolor"))
 		info->chatcolor = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"accessible"))
-		info->accessible = lua_isboolean(L,3);
-	else
+	else if (fastcmp(field,"accessible")) {
+		boolean v = lua_toboolean(L, 3);
+		if (cnum < FIRSTSUPERCOLOR && v != skincolors[cnum].accessible)
+			return luaL_error(L, "skincolors[] index %d is a standard color; accessibility changes are prohibited.", cnum);
+		else
+			info->accessible = v;
+	} else
 		CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "skincolor_t", field);
 	return 1;
 }
@@ -1670,17 +1728,19 @@ static int colorramp_get(lua_State *L)
 static int colorramp_set(lua_State *L)
 {
 	UINT8 *colorramp = *((UINT8 **)luaL_checkudata(L, 1, META_COLORRAMP));
-	UINT16 cnum = (UINT16)(((uint8_t*)colorramp - (uint8_t*)(skincolors[0].ramp))/sizeof(skincolor_t));
+	UINT16 cnum = (UINT16)(((UINT8*)colorramp - (UINT8*)(skincolors[0].ramp))/sizeof(skincolor_t));
 	UINT32 n = luaL_checkinteger(L, 2);
 	UINT8 i = (UINT8)luaL_checkinteger(L, 3);
-	if (cnum < SKINCOLOR_FIRSTFREESLOT || cnum >= numskincolors)
-		return luaL_error(L, "skincolors[] index %d out of range (%d - %d)", cnum, SKINCOLOR_FIRSTFREESLOT, numskincolors-1);
+	if (!cnum || cnum >= numskincolors)
+		return luaL_error(L, "skincolors[] index %d out of range (1 - %d)", cnum, numskincolors-1);
 	if (n >= COLORRAMPSIZE)
 		return luaL_error(L, LUA_QL("skincolor_t") " field 'ramp' index %d out of range (0 - %d)", n, COLORRAMPSIZE-1);
 	if (hud_running)
 		return luaL_error(L, "Do not alter skincolor_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter skincolor_t in CMD building code!");
 	colorramp[n] = i;
-	R_FlushTranslationColormapCache();
+	skincolor_modified[cnum] = true;
 	return 0;
 }
 
diff --git a/src/lua_libs.h b/src/lua_libs.h
index a78128e9f88bed6d1b2c99677fabc6f665ab753c..fbe8d48780f35009dfb0ca9b8f7209664297017c 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -12,10 +12,13 @@
 
 extern lua_State *gL;
 
+#define MUTABLE_TAGS
+
 #define LREG_VALID "VALID_USERDATA"
 #define LREG_EXTVARS "LUA_VARS"
 #define LREG_STATEACTION "STATE_ACTION"
 #define LREG_ACTIONS "MOBJ_ACTION"
+#define LREG_METATABLES "METATABLES"
 
 #define META_STATE "STATE_T*"
 #define META_MOBJINFO "MOBJINFO_T*"
@@ -26,6 +29,8 @@ extern lua_State *gL;
 #define META_PIVOTLIST "SPRITEFRAMEPIVOT_T[]"
 #define META_FRAMEPIVOT "SPRITEFRAMEPIVOT_T*"
 
+#define META_TAGLIST "TAGLIST"
+
 #define META_MOBJ "MOBJ_T*"
 #define META_MAPTHING "MAPTHING_T*"
 
@@ -34,6 +39,8 @@ extern lua_State *gL;
 #define META_SKIN "SKIN_T*"
 #define META_POWERS "PLAYER_T*POWERS"
 #define META_SOUNDSID "SKIN_T*SOUNDSID"
+#define META_SKINSPRITES "SKIN_T*SPRITES"
+#define META_SKINSPRITESLIST "SKIN_T*SPRITES[]"
 
 #define META_VERTEX "VERTEX_T*"
 #define META_LINE "LINE_T*"
@@ -50,10 +57,21 @@ extern lua_State *gL;
 #define META_VECTOR3 "VECTOR3_T"
 #define META_MAPHEADER "MAPHEADER_T*"
 
+#define META_POLYOBJ "POLYOBJ_T*"
+
 #define META_CVAR "CONSVAR_T*"
 
 #define META_SECTORLINES "SECTOR_T*LINES"
+#ifdef MUTABLE_TAGS
+#define META_SECTORTAGLIST "sector_t.taglist"
+#endif
 #define META_SIDENUM "LINE_T*SIDENUM"
+#define META_LINEARGS "LINE_T*ARGS"
+#define META_LINESTRINGARGS "LINE_T*STRINGARGS"
+#define META_THINGARGS "MAPTHING_T*ARGS"
+#define META_THINGSTRINGARGS "MAPTHING_T*STRINGARGS"
+#define META_POLYOBJVERTICES "POLYOBJ_T*VERTICES"
+#define META_POLYOBJLINES "POLYOBJ_T*LINES"
 #ifdef HAVE_LUA_SEGS
 #define META_NODEBBOX "NODE_T*BBOX"
 #define META_NODECHILDREN "NODE_T*CHILDREN"
@@ -84,5 +102,7 @@ int LUA_PlayerLib(lua_State *L);
 int LUA_SkinLib(lua_State *L);
 int LUA_ThinkerLib(lua_State *L);
 int LUA_MapLib(lua_State *L);
+int LUA_TagLib(lua_State *L);
+int LUA_PolyObjLib(lua_State *L);
 int LUA_BlockmapLib(lua_State *L);
 int LUA_HudLib(lua_State *L);
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 8e74f2157dfc13e74bcc07a76ae62a217117e3dd..5702788066984bb8660b3d5639261ebf81c4e867 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -16,11 +16,13 @@
 #include "p_setup.h"
 #include "z_zone.h"
 #include "p_slopes.h"
+#include "p_polyobj.h"
 #include "r_main.h"
 
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h" // hook_cmd_running errors
 
 #include "dehacked.h"
 #include "fastcmp.h"
@@ -35,6 +37,7 @@ enum sector_e {
 	sector_lightlevel,
 	sector_special,
 	sector_tag,
+	sector_taglist,
 	sector_thinglist,
 	sector_heightsec,
 	sector_camsec,
@@ -53,6 +56,7 @@ static const char *const sector_opt[] = {
 	"lightlevel",
 	"special",
 	"tag",
+	"taglist",
 	"thinglist",
 	"heightsec",
 	"camsec",
@@ -67,6 +71,7 @@ enum subsector_e {
 	subsector_sector,
 	subsector_numlines,
 	subsector_firstline,
+	subsector_polyList
 };
 
 static const char *const subsector_opt[] = {
@@ -74,6 +79,7 @@ static const char *const subsector_opt[] = {
 	"sector",
 	"numlines",
 	"firstline",
+	"polyList",
 	NULL};
 
 enum line_e {
@@ -85,14 +91,18 @@ enum line_e {
 	line_flags,
 	line_special,
 	line_tag,
+	line_taglist,
+	line_args,
+	line_stringargs,
 	line_sidenum,
 	line_frontside,
 	line_backside,
+	line_alpha,
+	line_executordelay,
 	line_slopetype,
 	line_frontsector,
 	line_backsector,
-	line_firsttag,
-	line_nexttag,
+	line_polyobj,
 	line_text,
 	line_callcount
 };
@@ -106,14 +116,18 @@ static const char *const line_opt[] = {
 	"flags",
 	"special",
 	"tag",
+	"taglist",
+	"args",
+	"stringargs",
 	"sidenum",
 	"frontside",
 	"backside",
+	"alpha",
+	"executordelay",
 	"slopetype",
 	"frontsector",
 	"backsector",
-	"firsttag",
-	"nexttag",
+	"polyobj",
 	"text",
 	"callcount",
 	NULL};
@@ -214,6 +228,7 @@ enum seg_e {
 	seg_linedef,
 	seg_frontsector,
 	seg_backsector,
+	seg_polyseg
 };
 
 static const char *const seg_opt[] = {
@@ -227,6 +242,7 @@ static const char *const seg_opt[] = {
 	"linedef",
 	"frontsector",
 	"backsector",
+	"polyseg",
 	NULL};
 
 enum node_e {
@@ -316,9 +332,9 @@ static const char *const vector_opt[] = {
 static const char *const array_opt[] ={"iterate",NULL};
 static const char *const valid_opt[] ={"valid",NULL};
 
-///////////////////////////////////
-// sector list iterate functions //
-///////////////////////////////////
+/////////////////////////////////////////////
+// sector/subsector list iterate functions //
+/////////////////////////////////////////////
 
 // iterates through a sector's thinglist!
 static int lib_iterateSectorThinglist(lua_State *L)
@@ -390,6 +406,41 @@ static int lib_iterateSectorFFloors(lua_State *L)
 	return 0;
 }
 
+// iterates through a subsector's polyList! (for polyobj_t)
+static int lib_iterateSubSectorPolylist(lua_State *L)
+{
+	polyobj_t *state = NULL;
+	polyobj_t *po = NULL;
+
+	INLEVEL
+
+	if (lua_gettop(L) < 2)
+		return luaL_error(L, "Don't call subsector.polyList() directly, use it as 'for polyobj in subsector.polyList do <block> end'.");
+
+	if (!lua_isnil(L, 1))
+		state = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	else
+		return 0; // no polylist to iterate through sorry!
+
+	lua_settop(L, 2);
+	lua_remove(L, 1); // remove state now.
+
+	if (!lua_isnil(L, 1))
+	{
+		po = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+		po = (polyobj_t *)(po->link.next);
+	}
+	else
+		po = state; // state is used as the "start" of the polylist
+
+	if (po)
+	{
+		LUA_PushUserdata(L, po, META_POLYOBJ);
+		return 1;
+	}
+	return 0;
+}
+
 static int sector_iterate(lua_State *L)
 {
 	lua_pushvalue(L, lua_upvalueindex(1)); // iterator function, or the "generator"
@@ -438,7 +489,7 @@ static int sectorlines_get(lua_State *L)
 	// get the "linecount" by shifting our retrieved memory address of "lines" to where "linecount" is in the sector_t, then dereferencing the result
 	// we need this to determine the array's actual size, and therefore also the maximum value allowed as an index
 	// this only works if seclines is actually a pointer to a sector's lines member in memory, oh boy
-	numoflines = (size_t)(*(size_t *)(((size_t)seclines) - (offsetof(sector_t, lines) - offsetof(sector_t, linecount))));
+	numoflines = *(size_t *)FIELDFROM (sector_t, seclines, lines,/* -> */linecount);
 
 /* OLD HACK
 	// check first linedef to figure which of its sectors owns this sector->lines pointer
@@ -472,7 +523,7 @@ static int sectorlines_num(lua_State *L)
 		return luaL_error(L, "accessed sector_t.lines doesn't exist anymore.");
 
 	// see comments in the _get function above
-	numoflines = (size_t)(*(size_t *)(((size_t)seclines) - (offsetof(sector_t, lines) - offsetof(sector_t, linecount))));
+	numoflines = *(size_t *)FIELDFROM (sector_t, seclines, lines,/* -> */linecount);
 	lua_pushinteger(L, numoflines);
 	return 1;
 }
@@ -532,7 +583,10 @@ static int sector_get(lua_State *L)
 		lua_pushinteger(L, sector->special);
 		return 1;
 	case sector_tag:
-		lua_pushinteger(L, sector->tag);
+		lua_pushinteger(L, Tag_FGet(&sector->tags));
+		return 1;
+	case sector_taglist:
+		LUA_PushUserdata(L, &sector->tags, META_SECTORTAGLIST);
 		return 1;
 	case sector_thinglist: // thinglist
 		lua_pushcfunction(L, lib_iterateSectorThinglist);
@@ -577,6 +631,8 @@ static int sector_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter sector_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter sector_t in CMD building code!");
 
 	switch(field)
 	{
@@ -631,8 +687,10 @@ static int sector_set(lua_State *L)
 		sector->special = (INT16)luaL_checkinteger(L, 3);
 		break;
 	case sector_tag:
-		P_ChangeSectorTag((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
+		Tag_SectorFSet((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
 		break;
+	case sector_taglist:
+		return LUA_ErrSetDirectly(L, "sector_t", "taglist");
 	}
 	return 0;
 }
@@ -676,6 +734,11 @@ static int subsector_get(lua_State *L)
 	case subsector_firstline:
 		lua_pushinteger(L, subsector->firstline);
 		return 1;
+	case subsector_polyList: // polyList
+		lua_pushcfunction(L, lib_iterateSubSectorPolylist);
+		LUA_PushUserdata(L, subsector->polyList, META_POLYOBJ);
+		lua_pushcclosure(L, sector_iterate, 2); // push lib_iterateSubSectorPolylist and subsector->polyList as upvalues for the function
+		return 1;
 	}
 	return 0;
 }
@@ -691,6 +754,42 @@ static int subsector_num(lua_State *L)
 // line_t //
 ////////////
 
+// args, i -> args[i]
+static int lineargs_get(lua_State *L)
+{
+	INT32 *args = *((INT32**)luaL_checkudata(L, 1, META_LINEARGS));
+	int i = luaL_checkinteger(L, 2);
+	if (i < 0 || i >= NUMLINEARGS)
+		return luaL_error(L, LUA_QL("line_t.args") " index cannot be %d", i);
+	lua_pushinteger(L, args[i]);
+	return 1;
+}
+
+// #args -> NUMLINEARGS
+static int lineargs_len(lua_State* L)
+{
+	lua_pushinteger(L, NUMLINEARGS);
+	return 1;
+}
+
+// stringargs, i -> stringargs[i]
+static int linestringargs_get(lua_State *L)
+{
+	char **stringargs = *((char***)luaL_checkudata(L, 1, META_LINESTRINGARGS));
+	int i = luaL_checkinteger(L, 2);
+	if (i < 0 || i >= NUMLINESTRINGARGS)
+		return luaL_error(L, LUA_QL("line_t.stringargs") " index cannot be %d", i);
+	lua_pushstring(L, stringargs[i]);
+	return 1;
+}
+
+// #stringargs -> NUMLINESTRINGARGS
+static int linestringargs_len(lua_State *L)
+{
+	lua_pushinteger(L, NUMLINESTRINGARGS);
+	return 1;
+}
+
 static int line_get(lua_State *L)
 {
 	line_t *line = *((line_t **)luaL_checkudata(L, 1, META_LINE));
@@ -729,7 +828,16 @@ static int line_get(lua_State *L)
 		lua_pushinteger(L, line->special);
 		return 1;
 	case line_tag:
-		lua_pushinteger(L, line->tag);
+		lua_pushinteger(L, Tag_FGet(&line->tags));
+		return 1;
+	case line_taglist:
+		LUA_PushUserdata(L, &line->tags, META_TAGLIST);
+		return 1;
+	case line_args:
+		LUA_PushUserdata(L, line->args, META_LINEARGS);
+		return 1;
+	case line_stringargs:
+		LUA_PushUserdata(L, line->stringargs, META_LINESTRINGARGS);
 		return 1;
 	case line_sidenum:
 		LUA_PushUserdata(L, line->sidenum, META_SIDENUM);
@@ -742,6 +850,12 @@ static int line_get(lua_State *L)
 			return 0;
 		LUA_PushUserdata(L, &sides[line->sidenum[1]], META_SIDE);
 		return 1;
+	case line_alpha:
+		lua_pushfixed(L, line->alpha);
+		return 1;
+	case line_executordelay:
+		lua_pushinteger(L, line->executordelay);
+		return 1;
 	case line_slopetype:
 		switch(line->slopetype)
 		{
@@ -765,11 +879,8 @@ static int line_get(lua_State *L)
 	case line_backsector:
 		LUA_PushUserdata(L, line->backsector, META_SECTOR);
 		return 1;
-	case line_firsttag:
-		lua_pushinteger(L, line->firsttag);
-		return 1;
-	case line_nexttag:
-		lua_pushinteger(L, line->nexttag);
+	case line_polyobj:
+		LUA_PushUserdata(L, line->polyobj, META_POLYOBJ);
 		return 1;
 	case line_text:
 		lua_pushstring(L, line->text);
@@ -1033,6 +1144,9 @@ static int seg_get(lua_State *L)
 	case seg_backsector:
 		LUA_PushUserdata(L, seg->backsector, META_SECTOR);
 		return 1;
+	case seg_polyseg:
+		LUA_PushUserdata(L, seg->polyseg, META_POLYOBJ);
+		return 1;
 	}
 	return 0;
 }
@@ -1283,25 +1397,15 @@ static int lib_iterateSectors(lua_State *L)
 
 static int lib_getSector(lua_State *L)
 {
-	int field;
 	INLEVEL
-	lua_settop(L, 2);
-	lua_remove(L, 1); // dummy userdata table is unused.
-	if (lua_isnumber(L, 1))
+	if (lua_isnumber(L, 2))
 	{
-		size_t i = lua_tointeger(L, 1);
+		size_t i = lua_tointeger(L, 2);
 		if (i >= numsectors)
 			return 0;
 		LUA_PushUserdata(L, &sectors[i], META_SECTOR);
 		return 1;
 	}
-	field = luaL_checkoption(L, 1, NULL, array_opt);
-	switch(field)
-	{
-	case 0: // iterate
-		lua_pushcfunction(L, lib_iterateSectors);
-		return 1;
-	}
 	return 0;
 }
 
@@ -1387,25 +1491,15 @@ static int lib_iterateLines(lua_State *L)
 
 static int lib_getLine(lua_State *L)
 {
-	int field;
 	INLEVEL
-	lua_settop(L, 2);
-	lua_remove(L, 1); // dummy userdata table is unused.
-	if (lua_isnumber(L, 1))
+	if (lua_isnumber(L, 2))
 	{
-		size_t i = lua_tointeger(L, 1);
+		size_t i = lua_tointeger(L, 2);
 		if (i >= numlines)
 			return 0;
 		LUA_PushUserdata(L, &lines[i], META_LINE);
 		return 1;
 	}
-	field = luaL_checkoption(L, 1, NULL, array_opt);
-	switch(field)
-	{
-	case 0: // iterate
-		lua_pushcfunction(L, lib_iterateLines);
-		return 1;
-	}
 	return 0;
 }
 
@@ -1716,6 +1810,8 @@ static int ffloor_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter ffloor_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter ffloor_t in CMD building code!");
 
 	switch(field)
 	{
@@ -1840,6 +1936,8 @@ static int slope_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter pslope_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter pslope_t in CMD building code!");
 
 	switch(field) // todo: reorganize this shit
 	{
@@ -2083,6 +2181,8 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->levelflags);
 	else if (fastcmp(field,"menuflags"))
 		lua_pushinteger(L, header->menuflags);
+	else if (fastcmp(field,"selectheading"))
+		lua_pushstring(L, header->selectheading);
 	else if (fastcmp(field,"startrings"))
 		lua_pushinteger(L, header->startrings);
 	else if (fastcmp(field, "sstimer"))
@@ -2143,6 +2243,22 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	luaL_newmetatable(L, META_LINEARGS);
+		lua_pushcfunction(L, lineargs_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, lineargs_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_LINESTRINGARGS);
+		lua_pushcfunction(L, linestringargs_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, linestringargs_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
 	luaL_newmetatable(L, META_SIDENUM);
 		lua_pushcfunction(L, sidenum_get);
 		lua_setfield(L, -2, "__index");
@@ -2236,15 +2352,13 @@ int LUA_MapLib(lua_State *L)
 		//lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
-	lua_newuserdata(L, 0);
-		lua_createtable(L, 0, 2);
-			lua_pushcfunction(L, lib_getSector);
-			lua_setfield(L, -2, "__index");
-
-			lua_pushcfunction(L, lib_numsectors);
-			lua_setfield(L, -2, "__len");
-		lua_setmetatable(L, -2);
-	lua_setglobal(L, "sectors");
+	LUA_PushTaggableObjectArray(L, "sectors",
+			lib_iterateSectors,
+			lib_getSector,
+			lib_numsectors,
+			&tags_sectors,
+			&numsectors, &sectors,
+			sizeof (sector_t), META_SECTOR);
 
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
@@ -2256,15 +2370,13 @@ int LUA_MapLib(lua_State *L)
 		lua_setmetatable(L, -2);
 	lua_setglobal(L, "subsectors");
 
-	lua_newuserdata(L, 0);
-		lua_createtable(L, 0, 2);
-			lua_pushcfunction(L, lib_getLine);
-			lua_setfield(L, -2, "__index");
-
-			lua_pushcfunction(L, lib_numlines);
-			lua_setfield(L, -2, "__len");
-		lua_setmetatable(L, -2);
-	lua_setglobal(L, "lines");
+	LUA_PushTaggableObjectArray(L, "lines",
+			lib_iterateLines,
+			lib_getLine,
+			lib_numlines,
+			&tags_lines,
+			&numlines, &lines,
+			sizeof (line_t), META_LINE);
 
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
diff --git a/src/lua_mathlib.c b/src/lua_mathlib.c
index 7cbe7a6cc9bbc579d47ff6f6a03ab3f2ad6b5ef4..b6046ab53b2d37948bc6fdb40c540a49d148be57 100644
--- a/src/lua_mathlib.c
+++ b/src/lua_mathlib.c
@@ -15,6 +15,7 @@
 #include "tables.h"
 #include "p_local.h"
 #include "doomstat.h" // for ALL7EMERALDS
+#include "r_main.h" // for R_PointToDist2
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -129,7 +130,7 @@ static int lib_fixedsqrt(lua_State *L)
 
 static int lib_fixedhypot(lua_State *L)
 {
-	lua_pushfixed(L, FixedHypot(luaL_checkfixed(L, 1), luaL_checkfixed(L, 2)));
+	lua_pushfixed(L, R_PointToDist2(0, 0, luaL_checkfixed(L, 1), luaL_checkfixed(L, 2)));
 	return 1;
 }
 
@@ -192,18 +193,30 @@ static luaL_Reg lib[] = {
 	{"cos", lib_finecosine},
 	{"tan", lib_finetangent},
 	{"FixedAngle", lib_fixedangle},
+	{"fixangle"  , lib_fixedangle},
 	{"AngleFixed", lib_anglefixed},
+	{"anglefix"  , lib_anglefixed},
 	{"InvAngle", lib_invangle},
 	{"FixedMul", lib_fixedmul},
+	{"fixmul"  , lib_fixedmul},
 	{"FixedInt", lib_fixedint},
+	{"fixint"  , lib_fixedint},
 	{"FixedDiv", lib_fixeddiv},
+	{"fixdiv"  , lib_fixeddiv},
 	{"FixedRem", lib_fixedrem},
+	{"fixrem"  , lib_fixedrem},
 	{"FixedSqrt", lib_fixedsqrt},
+	{"fixsqrt"  , lib_fixedsqrt},
 	{"FixedHypot", lib_fixedhypot},
+	{"fixhypot"  , lib_fixedhypot},
 	{"FixedFloor", lib_fixedfloor},
+	{"fixfloor"  , lib_fixedfloor},
 	{"FixedTrunc", lib_fixedtrunc},
+	{"fixtrunc"  , lib_fixedtrunc},
 	{"FixedCeil", lib_fixedceil},
+	{"fixceil"  , lib_fixedceil},
 	{"FixedRound", lib_fixedround},
+	{"fixround"  , lib_fixedround},
 	{"GetSecSpecial", lib_getsecspecial},
 	{"All7Emeralds", lib_all7emeralds},
 	{"ColorOpposite", lib_coloropposite},
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 129339b96fde68c6eab8b3878c91aacadfa529f5..a202c57ee39c599cba532dcaba28e9e92fcbf198 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -20,8 +20,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
-
-static const char *const array_opt[] ={"iterate",NULL};
+#include "lua_hook.h" // hook_cmd_running errors
 
 enum mobj_e {
 	mobj_valid = 0,
@@ -31,11 +30,18 @@ enum mobj_e {
 	mobj_snext,
 	mobj_sprev,
 	mobj_angle,
+	mobj_pitch,
+	mobj_roll,
 	mobj_rollangle,
 	mobj_sprite,
 	mobj_frame,
 	mobj_sprite2,
 	mobj_anim_duration,
+	mobj_spritexscale,
+	mobj_spriteyscale,
+	mobj_spritexoffset,
+	mobj_spriteyoffset,
+	mobj_floorspriteslope,
 	mobj_touching_sectorlist,
 	mobj_subsector,
 	mobj_floorz,
@@ -53,8 +59,10 @@ enum mobj_e {
 	mobj_flags,
 	mobj_flags2,
 	mobj_eflags,
+	mobj_renderflags,
 	mobj_skin,
 	mobj_color,
+	mobj_blendmode,
 	mobj_bnext,
 	mobj_bprev,
 	mobj_hnext,
@@ -98,11 +106,18 @@ static const char *const mobj_opt[] = {
 	"snext",
 	"sprev",
 	"angle",
+	"pitch",
+	"roll",
 	"rollangle",
 	"sprite",
 	"frame",
 	"sprite2",
 	"anim_duration",
+	"spritexscale",
+	"spriteyscale",
+	"spritexoffset",
+	"spriteyoffset",
+	"floorspriteslope",
 	"touching_sectorlist",
 	"subsector",
 	"floorz",
@@ -120,8 +135,10 @@ static const char *const mobj_opt[] = {
 	"flags",
 	"flags2",
 	"eflags",
+	"renderflags",
 	"skin",
 	"color",
+	"blendmode",
 	"bnext",
 	"bprev",
 	"hnext",
@@ -165,14 +182,15 @@ static int mobj_get(lua_State *L)
 	enum mobj_e field = Lua_optoption(L, 2, NULL, mobj_opt);
 	lua_settop(L, 2);
 
-	INLEVEL
-
-	if (!mo) {
+	if (!mo || !ISINLEVEL) {
 		if (field == mobj_valid) {
 			lua_pushboolean(L, 0);
 			return 1;
 		}
-		return LUA_ErrInvalid(L, "mobj_t");
+		if (!mo) {
+			return LUA_ErrInvalid(L, "mobj_t");
+		} else
+			return luaL_error(L, "Do not access an mobj_t field outside a level!");
 	}
 
 	switch(field)
@@ -200,6 +218,12 @@ static int mobj_get(lua_State *L)
 	case mobj_angle:
 		lua_pushangle(L, mo->angle);
 		break;
+	case mobj_pitch:
+		lua_pushangle(L, mo->pitch);
+		break;
+	case mobj_roll:
+		lua_pushangle(L, mo->roll);
+		break;
 	case mobj_rollangle:
 		lua_pushangle(L, mo->rollangle);
 		break;
@@ -215,6 +239,21 @@ static int mobj_get(lua_State *L)
 	case mobj_anim_duration:
 		lua_pushinteger(L, mo->anim_duration);
 		break;
+	case mobj_spritexscale:
+		lua_pushfixed(L, mo->spritexscale);
+		break;
+	case mobj_spriteyscale:
+		lua_pushfixed(L, mo->spriteyscale);
+		break;
+	case mobj_spritexoffset:
+		lua_pushfixed(L, mo->spritexoffset);
+		break;
+	case mobj_spriteyoffset:
+		lua_pushfixed(L, mo->spriteyoffset);
+		break;
+	case mobj_floorspriteslope:
+		LUA_PushUserdata(L, mo->floorspriteslope, META_SLOPE);
+		break;
 	case mobj_touching_sectorlist:
 		return UNIMPLEMENTED;
 	case mobj_subsector:
@@ -265,6 +304,9 @@ static int mobj_get(lua_State *L)
 	case mobj_eflags:
 		lua_pushinteger(L, mo->eflags);
 		break;
+	case mobj_renderflags:
+		lua_pushinteger(L, mo->renderflags);
+		break;
 	case mobj_skin: // skin name or nil, not struct
 		if (!mo->skin)
 			return 0;
@@ -273,6 +315,9 @@ static int mobj_get(lua_State *L)
 	case mobj_color:
 		lua_pushinteger(L, mo->color);
 		break;
+	case mobj_blendmode:
+		lua_pushinteger(L, mo->blendmode);
+		break;
 	case mobj_bnext:
 		LUA_PushUserdata(L, mo->bnext, META_MOBJ);
 		break;
@@ -426,6 +471,8 @@ static int mobj_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobj_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mobj_t in CMD building code!");
 
 	switch(field)
 	{
@@ -457,6 +504,12 @@ static int mobj_set(lua_State *L)
 		if (mo->player)
 			P_SetPlayerAngle(mo->player, mo->angle);
 		break;
+	case mobj_pitch:
+		mo->pitch = luaL_checkangle(L, 3);
+		break;
+	case mobj_roll:
+		mo->roll = luaL_checkangle(L, 3);
+		break;
 	case mobj_rollangle:
 		mo->rollangle = luaL_checkangle(L, 3);
 		break;
@@ -472,6 +525,20 @@ static int mobj_set(lua_State *L)
 	case mobj_anim_duration:
 		mo->anim_duration = (UINT16)luaL_checkinteger(L, 3);
 		break;
+	case mobj_spritexscale:
+		mo->spritexscale = luaL_checkfixed(L, 3);
+		break;
+	case mobj_spriteyscale:
+		mo->spriteyscale = luaL_checkfixed(L, 3);
+		break;
+	case mobj_spritexoffset:
+		mo->spritexoffset = luaL_checkfixed(L, 3);
+		break;
+	case mobj_spriteyoffset:
+		mo->spriteyoffset = luaL_checkfixed(L, 3);
+		break;
+	case mobj_floorspriteslope:
+		return NOSET;
 	case mobj_touching_sectorlist:
 		return UNIMPLEMENTED;
 	case mobj_subsector:
@@ -560,6 +627,9 @@ static int mobj_set(lua_State *L)
 	case mobj_eflags:
 		mo->eflags = (UINT32)luaL_checkinteger(L, 3);
 		break;
+	case mobj_renderflags:
+		mo->renderflags = (UINT32)luaL_checkinteger(L, 3);
+		break;
 	case mobj_skin: // set skin by name
 	{
 		INT32 i;
@@ -583,6 +653,9 @@ static int mobj_set(lua_State *L)
 		mo->color = newcolor;
 		break;
 	}
+	case mobj_blendmode:
+		mo->blendmode = (INT32)luaL_checkinteger(L, 3);
+		break;
 	case mobj_bnext:
 		return NOSETPOS;
 	case mobj_bprev:
@@ -752,6 +825,42 @@ static int mobj_set(lua_State *L)
 #undef NOSETPOS
 #undef NOFIELD
 
+// args, i -> args[i]
+static int thingargs_get(lua_State *L)
+{
+	INT32 *args = *((INT32**)luaL_checkudata(L, 1, META_THINGARGS));
+	int i = luaL_checkinteger(L, 2);
+	if (i < 0 || i >= NUMMAPTHINGARGS)
+		return luaL_error(L, LUA_QL("mapthing_t.args") " index cannot be %d", i);
+	lua_pushinteger(L, args[i]);
+	return 1;
+}
+
+// #args -> NUMMAPTHINGARGS
+static int thingargs_len(lua_State* L)
+{
+	lua_pushinteger(L, NUMMAPTHINGARGS);
+	return 1;
+}
+
+// stringargs, i -> stringargs[i]
+static int thingstringargs_get(lua_State *L)
+{
+	char **stringargs = *((char***)luaL_checkudata(L, 1, META_THINGSTRINGARGS));
+	int i = luaL_checkinteger(L, 2);
+	if (i < 0 || i >= NUMMAPTHINGSTRINGARGS)
+		return luaL_error(L, LUA_QL("mapthing_t.stringargs") " index cannot be %d", i);
+	lua_pushstring(L, stringargs[i]);
+	return 1;
+}
+
+// #stringargs -> NUMMAPTHINGSTRINGARGS
+static int thingstringargs_len(lua_State *L)
+{
+	lua_pushinteger(L, NUMMAPTHINGSTRINGARGS);
+	return 1;
+}
+
 static int mapthing_get(lua_State *L)
 {
 	mapthing_t *mt = *((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING));
@@ -777,14 +886,37 @@ static int mapthing_get(lua_State *L)
 		number = mt->y;
 	else if(fastcmp(field,"angle"))
 		number = mt->angle;
+	else if(fastcmp(field,"pitch"))
+		number = mt->pitch;
+	else if(fastcmp(field,"roll"))
+		number = mt->roll;
 	else if(fastcmp(field,"type"))
 		number = mt->type;
 	else if(fastcmp(field,"options"))
 		number = mt->options;
+	else if(fastcmp(field,"scale"))
+		number = mt->scale;
 	else if(fastcmp(field,"z"))
 		number = mt->z;
 	else if(fastcmp(field,"extrainfo"))
 		number = mt->extrainfo;
+	else if(fastcmp(field,"tag"))
+		number = Tag_FGet(&mt->tags);
+	else if(fastcmp(field,"taglist"))
+	{
+		LUA_PushUserdata(L, &mt->tags, META_TAGLIST);
+		return 1;
+	}
+	else if(fastcmp(field,"args"))
+	{
+		LUA_PushUserdata(L, mt->args, META_THINGARGS);
+		return 1;
+	}
+	else if(fastcmp(field,"stringargs"))
+	{
+		LUA_PushUserdata(L, mt->stringargs, META_THINGSTRINGARGS);
+		return 1;
+	}
 	else if(fastcmp(field,"mobj")) {
 		LUA_PushUserdata(L, mt->mobj, META_MOBJ);
 		return 1;
@@ -807,6 +939,8 @@ static int mapthing_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mapthing_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter mapthing_t in CMD building code!");
 
 	if(fastcmp(field,"x"))
 		mt->x = (INT16)luaL_checkinteger(L, 3);
@@ -814,10 +948,16 @@ static int mapthing_set(lua_State *L)
 		mt->y = (INT16)luaL_checkinteger(L, 3);
 	else if(fastcmp(field,"angle"))
 		mt->angle = (INT16)luaL_checkinteger(L, 3);
+	else if(fastcmp(field,"pitch"))
+		mt->pitch = (INT16)luaL_checkinteger(L, 3);
+	else if(fastcmp(field,"roll"))
+		mt->roll = (INT16)luaL_checkinteger(L, 3);
 	else if(fastcmp(field,"type"))
 		mt->type = (UINT16)luaL_checkinteger(L, 3);
 	else if(fastcmp(field,"options"))
 		mt->options = (UINT16)luaL_checkinteger(L, 3);
+	else if(fastcmp(field,"scale"))
+		mt->scale = luaL_checkfixed(L, 3);
 	else if(fastcmp(field,"z"))
 		mt->z = (INT16)luaL_checkinteger(L, 3);
 	else if(fastcmp(field,"extrainfo"))
@@ -827,6 +967,10 @@ static int mapthing_set(lua_State *L)
 			return luaL_error(L, "mapthing_t extrainfo set %d out of range (%d - %d)", extrainfo, 0, 15);
 		mt->extrainfo = (UINT8)extrainfo;
 	}
+	else if (fastcmp(field,"tag"))
+		Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
+	else if (fastcmp(field,"taglist"))
+		return LUA_ErrSetDirectly(L, "mapthing_t", "taglist");
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
@@ -864,25 +1008,15 @@ static int lib_iterateMapthings(lua_State *L)
 
 static int lib_getMapthing(lua_State *L)
 {
-	int field;
 	INLEVEL
-	lua_settop(L, 2);
-	lua_remove(L, 1); // dummy userdata table is unused.
-	if (lua_isnumber(L, 1))
+	if (lua_isnumber(L, 2))
 	{
-		size_t i = lua_tointeger(L, 1);
+		size_t i = lua_tointeger(L, 2);
 		if (i >= nummapthings)
 			return 0;
 		LUA_PushUserdata(L, &mapthings[i], META_MAPTHING);
 		return 1;
 	}
-	field = luaL_checkoption(L, 1, NULL, array_opt);
-	switch(field)
-	{
-	case 0: // iterate
-		lua_pushcfunction(L, lib_iterateMapthings);
-		return 1;
-	}
 	return 0;
 }
 
@@ -902,6 +1036,22 @@ int LUA_MobjLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	luaL_newmetatable(L, META_THINGARGS);
+		lua_pushcfunction(L, thingargs_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, thingargs_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_THINGSTRINGARGS);
+		lua_pushcfunction(L, thingstringargs_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, thingstringargs_len);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
 	luaL_newmetatable(L, META_MAPTHING);
 		lua_pushcfunction(L, mapthing_get);
 		lua_setfield(L, -2, "__index");
@@ -913,14 +1063,13 @@ int LUA_MobjLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
-	lua_newuserdata(L, 0);
-		lua_createtable(L, 0, 2);
-			lua_pushcfunction(L, lib_getMapthing);
-			lua_setfield(L, -2, "__index");
+	LUA_PushTaggableObjectArray(L, "mapthings",
+			lib_iterateMapthings,
+			lib_getMapthing,
+			lib_nummapthings,
+			&tags_mapthings,
+			&nummapthings, &mapthings,
+			sizeof (mapthing_t), META_MAPTHING);
 
-			lua_pushcfunction(L, lib_nummapthings);
-			lua_setfield(L, -2, "__len");
-		lua_setmetatable(L, -2);
-	lua_setglobal(L, "mapthings");
 	return 0;
 }
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 1ce9be525f50146735f3973bb5b36de246c5b383..0eb54808fb21a90de8ee5b6aaa3d046eff8cfdd1 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -20,6 +20,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "lua_hook.h" // hook_cmd_running errors
 
 static int lib_iteratePlayers(lua_State *L)
 {
@@ -157,6 +158,10 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->flashpal);
 	else if (fastcmp(field,"skincolor"))
 		lua_pushinteger(L, plr->skincolor);
+	else if (fastcmp(field,"skin"))
+		lua_pushinteger(L, plr->skin);
+	else if (fastcmp(field,"availabilities"))
+		lua_pushinteger(L, plr->availabilities);
 	else if (fastcmp(field,"score"))
 		lua_pushinteger(L, plr->score);
 	else if (fastcmp(field,"dashspeed"))
@@ -400,6 +405,8 @@ static int player_set(lua_State *L)
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter player_t in CMD building code!");
 
 	if (fastcmp(field,"mo") || fastcmp(field,"realmo")) {
 		mobj_t *newmo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
@@ -466,6 +473,10 @@ static int player_set(lua_State *L)
 			return luaL_error(L, "player.skincolor %d out of range (0 - %d).", newcolor, numskincolors-1);
 		plr->skincolor = newcolor;
 	}
+	else if (fastcmp(field,"skin"))
+		return NOSET;
+	else if (fastcmp(field,"availabilities"))
+		return NOSET;
 	else if (fastcmp(field,"score"))
 		plr->score = (UINT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"dashspeed"))
@@ -770,6 +781,8 @@ static int power_set(lua_State *L)
 		return luaL_error(L, LUA_QL("powertype_t") " cannot be %d", (INT16)p);
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
+	if (hook_cmd_running)
+		return luaL_error(L, "Do not alter player_t in CMD building code!");
 	powers[p] = i;
 	return 0;
 }
diff --git a/src/lua_polyobjlib.c b/src/lua_polyobjlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..b025d6810aa1033dcc767ea17233ca3ca753ebab
--- /dev/null
+++ b/src/lua_polyobjlib.c
@@ -0,0 +1,486 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Iestyn "Monster Iestyn" Jealous.
+// Copyright (C) 2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  lua_polyobjlib.c
+/// \brief polyobject library for Lua scripting
+
+#include "doomdef.h"
+#include "fastcmp.h"
+#include "p_local.h"
+#include "p_polyobj.h"
+#include "lua_script.h"
+#include "lua_libs.h"
+#include "lua_hud.h" // hud_running errors
+
+#define NOHUD if (hud_running)\
+return luaL_error(L, "HUD rendering code should not call this function!");
+
+enum polyobj_e {
+	// properties
+	polyobj_valid = 0,
+	polyobj_id,
+	polyobj_parent,
+	polyobj_vertices,
+	polyobj_lines,
+	polyobj_sector,
+	polyobj_angle,
+	polyobj_damage,
+	polyobj_thrust,
+	polyobj_flags,
+	polyobj_translucency,
+	polyobj_triggertag,
+	// special functions - utility
+	polyobj_pointInside,
+	polyobj_mobjTouching,
+	polyobj_mobjInside,
+	// special functions - manipulation
+	polyobj_moveXY,
+	polyobj_rotate
+};
+static const char *const polyobj_opt[] = {
+	// properties
+	"valid",
+	"id",
+	"parent",
+	"vertices",
+	"lines",
+	"sector",
+	"angle",
+	"damage",
+	"thrust",
+	"flags",
+	"translucency",
+	"triggertag",
+	// special functions - utility
+	"pointInside",
+	"mobjTouching",
+	"mobjInside",
+	// special functions - manipulation
+	"moveXY",
+	"rotate",
+	NULL};
+
+static const char *const valid_opt[] ={"valid",NULL};
+
+////////////////////////
+// polyobj.vertices[] //
+////////////////////////
+
+// polyobj.vertices, i -> polyobj.vertices[i]
+// polyobj.vertices.valid, for validity checking
+//
+// see sectorlines_get in lua_maplib.c
+//
+static int polyobjvertices_get(lua_State *L)
+{
+	vertex_t ***polyverts = *((vertex_t ****)luaL_checkudata(L, 1, META_POLYOBJVERTICES));
+	size_t i;
+	size_t numofverts = 0;
+	lua_settop(L, 2);
+	if (!lua_isnumber(L, 2))
+	{
+		int field = luaL_checkoption(L, 2, NULL, valid_opt);
+		if (!polyverts || !(*polyverts))
+		{
+			if (field == 0) {
+				lua_pushboolean(L, 0);
+				return 1;
+			}
+			return luaL_error(L, "accessed polyobj_t.vertices doesn't exist anymore.");
+		} else if (field == 0) {
+			lua_pushboolean(L, 1);
+			return 1;
+		}
+	}
+
+	numofverts = *(size_t *)FIELDFROM (polyobj_t, polyverts, vertices,/* -> */numVertices);
+
+	if (!numofverts)
+		return luaL_error(L, "no vertices found!");
+
+	i = (size_t)lua_tointeger(L, 2);
+	if (i >= numofverts)
+		return 0;
+	LUA_PushUserdata(L, (*polyverts)[i], META_VERTEX);
+	return 1;
+}
+
+// #(polyobj.vertices) -> polyobj.numVertices
+static int polyobjvertices_num(lua_State *L)
+{
+	vertex_t ***polyverts = *((vertex_t ****)luaL_checkudata(L, 1, META_POLYOBJVERTICES));
+	size_t numofverts = 0;
+
+	if (!polyverts || !(*polyverts))
+		return luaL_error(L, "accessed polyobj_t.vertices doesn't exist anymore.");
+
+	numofverts = *(size_t *)FIELDFROM (polyobj_t, polyverts, vertices,/* -> */numVertices);
+	lua_pushinteger(L, numofverts);
+	return 1;
+}
+
+/////////////////////
+// polyobj.lines[] //
+/////////////////////
+
+// polyobj.lines, i -> polyobj.lines[i]
+// polyobj.lines.valid, for validity checking
+//
+// see sectorlines_get in lua_maplib.c
+//
+static int polyobjlines_get(lua_State *L)
+{
+	line_t ***polylines = *((line_t ****)luaL_checkudata(L, 1, META_POLYOBJLINES));
+	size_t i;
+	size_t numoflines = 0;
+	lua_settop(L, 2);
+	if (!lua_isnumber(L, 2))
+	{
+		int field = luaL_checkoption(L, 2, NULL, valid_opt);
+		if (!polylines || !(*polylines))
+		{
+			if (field == 0) {
+				lua_pushboolean(L, 0);
+				return 1;
+			}
+			return luaL_error(L, "accessed polyobj_t.lines doesn't exist anymore.");
+		} else if (field == 0) {
+			lua_pushboolean(L, 1);
+			return 1;
+		}
+	}
+
+	numoflines = *(size_t *)FIELDFROM (polyobj_t, polylines, lines,/* -> */numLines);
+
+	if (!numoflines)
+		return luaL_error(L, "no lines found!");
+
+	i = (size_t)lua_tointeger(L, 2);
+	if (i >= numoflines)
+		return 0;
+	LUA_PushUserdata(L, (*polylines)[i], META_LINE);
+	return 1;
+}
+
+// #(polyobj.lines) -> polyobj.numLines
+static int polyobjlines_num(lua_State *L)
+{
+	line_t ***polylines = *((line_t ****)luaL_checkudata(L, 1, META_POLYOBJLINES));
+	size_t numoflines = 0;
+
+	if (!polylines || !(*polylines))
+		return luaL_error(L, "accessed polyobj_t.lines doesn't exist anymore.");
+
+	numoflines = *(size_t *)FIELDFROM (polyobj_t, polylines, lines,/* -> */numLines);
+	lua_pushinteger(L, numoflines);
+	return 1;
+}
+
+/////////////////////////////////
+// polyobj_t function wrappers //
+/////////////////////////////////
+
+// special functions - utility
+static int lib_polyobj_PointInside(lua_State *L)
+{
+	polyobj_t *po = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	fixed_t x = luaL_checkfixed(L, 2);
+	fixed_t y = luaL_checkfixed(L, 3);
+	INLEVEL
+	if (!po)
+		return LUA_ErrInvalid(L, "polyobj_t");
+	lua_pushboolean(L, P_PointInsidePolyobj(po, x, y));
+	return 1;
+}
+
+static int lib_polyobj_MobjTouching(lua_State *L)
+{
+	polyobj_t *po = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+	INLEVEL
+	if (!po)
+		return LUA_ErrInvalid(L, "polyobj_t");
+	if (!mo)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_MobjTouchingPolyobj(po, mo));
+	return 1;
+}
+
+static int lib_polyobj_MobjInside(lua_State *L)
+{
+	polyobj_t *po = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+	INLEVEL
+	if (!po)
+		return LUA_ErrInvalid(L, "polyobj_t");
+	if (!mo)
+		return LUA_ErrInvalid(L, "mobj_t");
+	lua_pushboolean(L, P_MobjInsidePolyobj(po, mo));
+	return 1;
+}
+
+// special functions - manipulation
+static int lib_polyobj_moveXY(lua_State *L)
+{
+	polyobj_t *po = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	fixed_t x = luaL_checkfixed(L, 2);
+	fixed_t y = luaL_checkfixed(L, 3);
+	boolean checkmobjs = lua_opttrueboolean(L, 4);
+	NOHUD
+	INLEVEL
+	if (!po)
+		return LUA_ErrInvalid(L, "polyobj_t");
+	lua_pushboolean(L, Polyobj_moveXY(po, x, y, checkmobjs));
+	return 1;
+}
+
+static int lib_polyobj_rotate(lua_State *L)
+{
+	polyobj_t *po = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	angle_t delta = luaL_checkangle(L, 2);
+	UINT8 turnthings = (UINT8)luaL_optinteger(L, 3, 0); // don't turn anything by default? (could change this if not desired)
+	boolean checkmobjs = lua_opttrueboolean(L, 4);
+	NOHUD
+	INLEVEL
+	if (!po)
+		return LUA_ErrInvalid(L, "polyobj_t");
+	lua_pushboolean(L, Polyobj_rotate(po, delta, turnthings, checkmobjs));
+	return 1;
+}
+
+///////////////
+// polyobj_t //
+///////////////
+
+static int polyobj_get(lua_State *L)
+{
+	polyobj_t *polyobj = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	enum polyobj_e field = luaL_checkoption(L, 2, NULL, polyobj_opt);
+
+	if (!polyobj) {
+		if (field == polyobj_valid) {
+			lua_pushboolean(L, false);
+			return 1;
+		}
+		return LUA_ErrInvalid(L, "polyobj_t");
+	}
+
+	switch (field)
+	{
+	// properties
+	case polyobj_valid:
+		lua_pushboolean(L, true);
+		break;
+	case polyobj_id:
+		lua_pushinteger(L, polyobj->id);
+		break;
+	case polyobj_parent:
+		lua_pushinteger(L, polyobj->parent);
+		break;
+	case polyobj_vertices: // vertices
+		LUA_PushUserdata(L, &polyobj->vertices, META_POLYOBJVERTICES); // push the address of the "vertices" member in the struct, to allow our hacks to work
+		break;
+	case polyobj_lines: // lines
+		LUA_PushUserdata(L, &polyobj->lines, META_POLYOBJLINES); // push the address of the "lines" member in the struct, to allow our hacks to work
+		break;
+	case polyobj_sector: // shortcut that exists only in Lua!
+		LUA_PushUserdata(L, polyobj->lines[0]->backsector, META_SECTOR);
+		break;
+	case polyobj_angle:
+		lua_pushangle(L, polyobj->angle);
+		break;
+	case polyobj_damage:
+		lua_pushinteger(L, polyobj->damage);
+		break;
+	case polyobj_thrust:
+		lua_pushfixed(L, polyobj->thrust);
+		break;
+	case polyobj_flags:
+		lua_pushinteger(L, polyobj->flags);
+		break;
+	case polyobj_translucency:
+		lua_pushinteger(L, polyobj->translucency);
+		break;
+	case polyobj_triggertag:
+		lua_pushinteger(L, polyobj->triggertag);
+		break;
+	// special functions - utility
+	case polyobj_pointInside:
+		lua_pushcfunction(L, lib_polyobj_PointInside);
+		break;
+	case polyobj_mobjTouching:
+		lua_pushcfunction(L, lib_polyobj_MobjTouching);
+		break;
+	case polyobj_mobjInside:
+		lua_pushcfunction(L, lib_polyobj_MobjInside);
+		break;
+	// special functions - manipulation
+	case polyobj_moveXY:
+		lua_pushcfunction(L, lib_polyobj_moveXY);
+		break;
+	case polyobj_rotate:
+		lua_pushcfunction(L, lib_polyobj_rotate);
+		break;
+	}
+	return 1;
+}
+
+static int polyobj_set(lua_State *L)
+{
+	polyobj_t *polyobj = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	enum polyobj_e field = luaL_checkoption(L, 2, NULL, polyobj_opt);
+
+	if (!polyobj)
+		return LUA_ErrInvalid(L, "polyobj_t");
+
+	if (hud_running)
+		return luaL_error(L, "Do not alter polyobj_t in HUD rendering code!");
+
+	switch (field)
+	{
+	default:
+		return luaL_error(L, LUA_QL("polyobj_t") " field " LUA_QS " cannot be modified.", polyobj_opt[field]);
+	case polyobj_angle:
+		return luaL_error(L, LUA_QL("polyobj_t") " field " LUA_QS " should not be set directly. Use the function " LUA_QL("polyobj:rotate(angle)") " instead.", polyobj_opt[field]);
+	case polyobj_parent:
+		polyobj->parent = luaL_checkinteger(L, 3);
+		break;
+	case polyobj_flags:
+		polyobj->flags = luaL_checkinteger(L, 3);
+		break;
+	case polyobj_translucency:
+		polyobj->translucency = luaL_checkinteger(L, 3);
+		break;
+	}
+
+	return 0;
+}
+
+static int polyobj_num(lua_State *L)
+{
+	polyobj_t *polyobj = *((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ));
+	if (!polyobj)
+		return luaL_error(L, "accessed polyobj_t doesn't exist anymore.");
+	lua_pushinteger(L, polyobj-world->PolyObjects);
+	return 1;
+}
+
+///////////////////
+// PolyObjects[] //
+///////////////////
+
+static int lib_iteratePolyObjects(lua_State *L)
+{
+	INT32 i = -1;
+	if (lua_gettop(L) < 2)
+	{
+		//return luaL_error(L, "Don't call PolyObjects.iterate() directly, use it as 'for polyobj in PolyObjects.iterate do <block> end'.");
+		lua_pushcfunction(L, lib_iteratePolyObjects);
+		return 1;
+	}
+	lua_settop(L, 2);
+	lua_remove(L, 1); // state is unused.
+	if (!lua_isnil(L, 1))
+		i = (INT32)(*((polyobj_t **)luaL_checkudata(L, 1, META_POLYOBJ)) - world->PolyObjects);
+	for (i++; i < world->numPolyObjects; i++)
+	{
+		LUA_PushUserdata(L, &world->PolyObjects[i], META_POLYOBJ);
+		return 1;
+	}
+	return 0;
+}
+
+static int lib_PolyObject_getfornum(lua_State *L)
+{
+	INT32 id = (INT32)luaL_checkinteger(L, 1);
+
+	if (!world->numPolyObjects)
+		return 0; // if there's no PolyObjects then bail out here
+
+	LUA_PushUserdata(L, Polyobj_GetForNum(id), META_POLYOBJ);
+	return 1;
+}
+
+static int lib_getPolyObject(lua_State *L)
+{
+	const char *field;
+	INT32 i;
+
+	// find PolyObject by number
+	if (lua_type(L, 2) == LUA_TNUMBER)
+	{
+		i = luaL_checkinteger(L, 2);
+		if (i < 0 || i >= world->numPolyObjects)
+			return luaL_error(L, "polyobjects[] index %d out of range (0 - %d)", i, world->numPolyObjects-1);
+		LUA_PushUserdata(L, &world->PolyObjects[i], META_POLYOBJ);
+		return 1;
+	}
+
+	field = luaL_checkstring(L, 2);
+	// special function iterate
+	if (fastcmp(field,"iterate"))
+	{
+		lua_pushcfunction(L, lib_iteratePolyObjects);
+		return 1;
+	}
+	// find PolyObject by ID
+	else if (fastcmp(field,"GetForNum")) // name could probably be better
+	{
+		lua_pushcfunction(L, lib_PolyObject_getfornum);
+		return 1;
+	}
+	return 0;
+}
+
+static int lib_numPolyObjects(lua_State *L)
+{
+	lua_pushinteger(L, world->numPolyObjects);
+	return 1;
+}
+
+int LUA_PolyObjLib(lua_State *L)
+{
+	luaL_newmetatable(L, META_POLYOBJVERTICES);
+		lua_pushcfunction(L, polyobjvertices_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, polyobjvertices_num);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_POLYOBJLINES);
+		lua_pushcfunction(L, polyobjlines_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, polyobjlines_num);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L, 1);
+
+	luaL_newmetatable(L, META_POLYOBJ);
+		lua_pushcfunction(L, polyobj_get);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, polyobj_set);
+		lua_setfield(L, -2, "__newindex");
+
+		lua_pushcfunction(L, polyobj_num);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L,1);
+
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_pushcfunction(L, lib_getPolyObject);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_numPolyObjects);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "polyobjects");
+	return 0;
+}
diff --git a/src/lua_script.c b/src/lua_script.c
index 0fe26b1bee192ed4116083499a039d7b065d68fe..fbd46e20b6f8130e54a35ce7d38362af0e9bf89a 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -13,6 +13,7 @@
 #include "doomdef.h"
 #include "fastcmp.h"
 #include "dehacked.h"
+#include "deh_lua.h"
 #include "z_zone.h"
 #include "w_wad.h"
 #include "p_setup.h"
@@ -24,6 +25,7 @@
 #include "p_saveg.h"
 #include "p_local.h"
 #include "p_slopes.h" // for P_SlopeById
+#include "p_polyobj.h" // polyobj_t, PolyObjects
 #ifdef LUA_ALLOW_BYTECODE
 #include "d_netfil.h" // for LUA_DumpFile
 #endif
@@ -33,6 +35,7 @@
 #include "lua_hook.h"
 
 #include "doomstat.h"
+#include "g_state.h"
 
 lua_State *gL = NULL;
 
@@ -50,6 +53,8 @@ static lua_CFunction liblist[] = {
 	LUA_SkinLib, // skin_t, skins[]
 	LUA_ThinkerLib, // thinker_t
 	LUA_MapLib, // line_t, side_t, sector_t, subsector_t
+	LUA_TagLib, // tags
+	LUA_PolyObjLib, // polyobj_t
 	LUA_BlockmapLib, // blockmap stuff
 	LUA_HudLib, // HUD stuff
 	NULL
@@ -130,12 +135,28 @@ int LUA_GetErrorMessage(lua_State *L)
 	return 1;
 }
 
+int LUA_Call(lua_State *L, int nargs, int nresults, int errorhandlerindex)
+{
+	int err = lua_pcall(L, nargs, nresults, errorhandlerindex);
+
+	if (err)
+	{
+		CONS_Alert(CONS_WARNING, "%s\n", lua_tostring(L, -1));
+		lua_pop(L, 1);
+	}
+
+	return err;
+}
+
 // Moved here from lib_getenum.
 int LUA_PushGlobals(lua_State *L, const char *word)
 {
 	if (fastcmp(word,"gamemap")) {
 		lua_pushinteger(L, gamemap);
 		return 1;
+	} else if (fastcmp(word,"udmf")) {
+		lua_pushboolean(L, udmf);
+		return 1;
 	} else if (fastcmp(word,"maptol")) {
 		lua_pushinteger(L, maptol);
 		return 1;
@@ -312,7 +333,7 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		return 1;
 	// local player variables, by popular request
 	} else if (fastcmp(word,"consoleplayer")) { // player controlling console (aka local player 1)
-		if (consoleplayer < 0 || !playeringame[consoleplayer])
+		if (!addedtogame || consoleplayer < 0 || !playeringame[consoleplayer])
 			return 0;
 		LUA_PushUserdata(L, &players[consoleplayer], META_PLAYER);
 		return 1;
@@ -356,6 +377,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 	} else if (fastcmp(word, "token")) {
 		lua_pushinteger(L, token);
 		return 1;
+	} else if (fastcmp(word, "gamestate")) {
+		lua_pushinteger(L, gamestate);
+		return 1;
 	}
 	return 0;
 }
@@ -367,6 +391,44 @@ int LUA_CheckGlobals(lua_State *L, const char *word)
 		redscore = (UINT32)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "bluescore"))
 		bluescore = (UINT32)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "skincolor_redteam"))
+		skincolor_redteam = (UINT16)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "skincolor_blueteam"))
+		skincolor_blueteam = (UINT16)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "skincolor_redring"))
+		skincolor_redring = (UINT16)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "skincolor_bluering"))
+		skincolor_bluering = (UINT16)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "emeralds"))
+		emeralds = (UINT16)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "token"))
+		token = (UINT32)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "gravity"))
+		gravity = (fixed_t)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "stoppedclock"))
+		stoppedclock = luaL_checkboolean(L, 2);
+	else if (fastcmp(word, "displayplayer"))
+	{
+		player_t *player = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
+
+		if (player)
+			displayplayer = player - players;
+	}
+	else if (fastcmp(word, "mapmusname"))
+	{
+		size_t strlength;
+		const char *str = luaL_checklstring(L, 2, &strlength);
+
+		if (strlength > 6)
+			return luaL_error(L, "string length out of range (maximum 6 characters)");
+
+		if (strlen(str) < strlength)
+			return luaL_error(L, "string must not contain embedded zeros!");
+
+		strncpy(mapmusname, str, strlength);
+	}
+	else if (fastcmp(word, "mapmusflags"))
+		mapmusflags = (UINT16)luaL_checkinteger(L, 2);
 	else
 		return 0;
 
@@ -379,6 +441,7 @@ static int setglobals(lua_State *L)
 {
 	const char *csname;
 	char *name;
+	enum actionnum actionnum;
 
 	lua_remove(L, 1); // we're not gonna be using _G
 	csname = lua_tostring(L, 1);
@@ -397,6 +460,10 @@ static int setglobals(lua_State *L)
 		lua_rawset(L, -3); // rawset doesn't trigger this metatable again.
 		// otherwise we would've used setfield, obviously.
 
+		actionnum = LUA_GetActionNumByName(name);
+		if (actionnum < NUMACTIONS)
+			actionsoverridden[actionnum] = true;
+
 		Z_Free(name);
 		return 0;
 	}
@@ -428,12 +495,16 @@ static void LUA_ClearState(void)
 
 	// open base libraries
 	luaL_openlibs(L);
-	lua_pop(L, -1);
+	lua_settop(L, 0);
 
 	// make LREG_VALID table for all pushed userdata cache.
 	lua_newtable(L);
 	lua_setfield(L, LUA_REGISTRYINDEX, LREG_VALID);
 
+	// make LREG_METATABLES table for all registered metatables
+	lua_newtable(L);
+	lua_setfield(L, LUA_REGISTRYINDEX, LREG_METATABLES);
+
 	// open srb2 libraries
 	for(i = 0; liblist[i]; i++) {
 		lua_pushcfunction(L, liblist[i]);
@@ -627,7 +698,7 @@ fixed_t LUA_EvalMath(const char *word)
 	*b = '\0';
 
 	// eval string.
-	lua_pop(L, -1);
+	lua_settop(L, 0);
 	if (luaL_dostring(L, buf))
 	{
 		p = lua_tostring(L, -1);
@@ -669,25 +740,37 @@ void LUA_PushLightUserdata (lua_State *L, void *data, const char *meta)
 // Pushes it to the stack and stores it in the registry.
 void LUA_PushUserdata(lua_State *L, void *data, const char *meta)
 {
+	if (LUA_RawPushUserdata(L, data) == LPUSHED_NEW)
+	{
+		luaL_getmetatable(L, meta);
+		lua_setmetatable(L, -2);
+	}
+}
+
+// Same as LUA_PushUserdata but don't set a metatable yet.
+lpushed_t LUA_RawPushUserdata(lua_State *L, void *data)
+{
+	lpushed_t status = LPUSHED_NIL;
+
 	void **userdata;
 
 	if (!data) { // push a NULL
 		lua_pushnil(L);
-		return;
+		return status;
 	}
 
 	lua_getfield(L, LUA_REGISTRYINDEX, LREG_VALID);
 	I_Assert(lua_istable(L, -1));
+
 	lua_pushlightuserdata(L, data);
 	lua_rawget(L, -2);
+
 	if (lua_isnil(L, -1)) { // no userdata? deary me, we'll have to make one.
 		lua_pop(L, 1); // pop the nil
 
 		// create the userdata
 		userdata = lua_newuserdata(L, sizeof(void *));
 		*userdata = data;
-		luaL_getmetatable(L, meta);
-		lua_setmetatable(L, -2);
 
 		// Set it in the registry so we can find it again
 		lua_pushlightuserdata(L, data); // k (store the userdata via the data's pointer)
@@ -695,8 +778,15 @@ void LUA_PushUserdata(lua_State *L, void *data, const char *meta)
 		lua_rawset(L, -4);
 
 		// stack is left with the userdata on top, as if getting it had originally succeeded.
+
+		status = LPUSHED_NEW;
 	}
+	else
+		status = LPUSHED_EXISTING;
+
 	lua_remove(L, -2); // remove LREG_VALID
+
+	return status;
 }
 
 // When userdata is freed, use this function to remove it from Lua.
@@ -756,6 +846,7 @@ void LUA_InvalidateLevel(world_t *w)
 	{
 		LUA_InvalidateUserdata(&w->sectors[i]);
 		LUA_InvalidateUserdata(&w->sectors[i].lines);
+		LUA_InvalidateUserdata(&w->sectors[i].tags);
 		if (w->sectors[i].ffloors)
 		{
 			for (rover = w->sectors[i].ffloors; rover; rover = rover->next)
@@ -765,12 +856,19 @@ void LUA_InvalidateLevel(world_t *w)
 	for (i = 0; i < w->numlines; i++)
 	{
 		LUA_InvalidateUserdata(&w->lines[i]);
+		LUA_InvalidateUserdata(&w->lines[i].tags);
 		LUA_InvalidateUserdata(w->lines[i].sidenum);
 	}
 	for (i = 0; i < w->numsides; i++)
 		LUA_InvalidateUserdata(&w->sides[i]);
 	for (i = 0; i < w->numvertexes; i++)
 		LUA_InvalidateUserdata(&w->vertexes[i]);
+	for (i = 0; i < (size_t)w->numPolyObjects; i++)
+	{
+		LUA_InvalidateUserdata(&w->PolyObjects[i]);
+		LUA_InvalidateUserdata(&w->PolyObjects[i].vertices);
+		LUA_InvalidateUserdata(&w->PolyObjects[i].lines);
+	}
 #ifdef HAVE_LUA_SEGS
 	for (i = 0; i < w->numsegs; i++)
 		LUA_InvalidateUserdata(&w->segs[i]);
@@ -790,7 +888,10 @@ void LUA_InvalidateMapthings(world_t *w)
 		return;
 
 	for (i = 0; i < w->nummapthings; i++)
+	{
 		LUA_InvalidateUserdata(&w->mapthings[i]);
+		LUA_InvalidateUserdata(&w->mapthings[i].tags);
+	}
 }
 
 void LUA_InvalidatePlayer(player_t *player)
@@ -829,6 +930,7 @@ enum
 	ARCH_NODE,
 #endif
 	ARCH_FFLOOR,
+	ARCH_POLYOBJ,
 	ARCH_SLOPE,
 	ARCH_MAPHEADER,
 	ARCH_SKINCOLOR,
@@ -855,6 +957,7 @@ static const struct {
 	{META_NODE,     ARCH_NODE},
 #endif
 	{META_FFLOOR,	ARCH_FFLOOR},
+	{META_POLYOBJ,  ARCH_POLYOBJ},
 	{META_SLOPE,    ARCH_SLOPE},
 	{META_MAPHEADER,   ARCH_MAPHEADER},
 	{META_SKINCOLOR,   ARCH_SKINCOLOR},
@@ -963,8 +1066,17 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			lua_pop(gL, 1);
 		}
 		if (!found)
+		{
 			t++;
 
+			if (t == 0)
+			{
+				CONS_Alert(CONS_ERROR, "Too many tables to archive!\n");
+				WRITEUINT8(save_p, ARCH_NULL);
+				return 0;
+			}
+		}
+
 		WRITEUINT8(save_p, ARCH_TABLE);
 		WRITEUINT16(save_p, t);
 
@@ -1123,6 +1235,17 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			}
 			break;
 		}
+		case ARCH_POLYOBJ:
+		{
+			polyobj_t *polyobj = *((polyobj_t **)lua_touserdata(gL, myindex));
+			if (!polyobj)
+				WRITEUINT8(save_p, ARCH_NULL);
+			else {
+				WRITEUINT8(save_p, ARCH_POLYOBJ);
+				WRITEUINT16(save_p, polyobj-archiveworld->PolyObjects);
+			}
+			break;
+		}
 		case ARCH_SLOPE:
 		{
 			pslope_t *slope = *((pslope_t **)lua_touserdata(gL, myindex));
@@ -1266,8 +1389,22 @@ static void ArchiveTables(void)
 
 			lua_pop(gL, 1);
 		}
-		lua_pop(gL, 1);
 		WRITEUINT8(save_p, ARCH_TEND);
+
+		// Write metatable ID
+		if (lua_getmetatable(gL, -1))
+		{
+			// registry.metatables[metatable]
+			lua_getfield(gL, LUA_REGISTRYINDEX, LREG_METATABLES);
+			lua_pushvalue(gL, -2);
+			lua_gettable(gL, -2);
+			WRITEUINT16(save_p, lua_isnil(gL, -1) ? 0 : lua_tointeger(gL, -1));
+			lua_pop(gL, 3);
+		}
+		else
+			WRITEUINT16(save_p, 0);
+
+		lua_pop(gL, 1);
 	}
 }
 
@@ -1378,8 +1515,11 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 			LUA_PushUserdata(gL, rover, META_FFLOOR);
 		break;
 	}
+	case ARCH_POLYOBJ:
+		LUA_PushUserdata(gL, &archiveworld->PolyObjects[READUINT16(save_p)], META_POLYOBJ);
+		break;
 	case ARCH_SLOPE:
-		LUA_PushUserdata(gL, P_SlopeById(READUINT16(save_p)), META_SLOPE);
+		LUA_PushUserdata(gL, P_SlopeById(archiveworld->slopelist, READUINT16(save_p)), META_SLOPE);
 		break;
 	case ARCH_MAPHEADER:
 		LUA_PushUserdata(gL, mapheaderinfo[READUINT16(save_p)], META_MAPHEADER);
@@ -1435,6 +1575,7 @@ static void UnArchiveTables(void)
 {
 	int TABLESINDEX;
 	UINT16 i, n;
+	UINT16 metatableid;
 
 	if (!gL)
 		return;
@@ -1459,6 +1600,19 @@ static void UnArchiveTables(void)
 			else
 				lua_rawset(gL, -3);
 		}
+
+		metatableid = READUINT16(save_p);
+		if (metatableid)
+		{
+			// setmetatable(table, registry.metatables[metatableid])
+			lua_getfield(gL, LUA_REGISTRYINDEX, LREG_METATABLES);
+				lua_rawgeti(gL, -1, metatableid);
+				if (lua_isnil(gL, -1))
+					I_Error("Unknown metatable ID %d\n", metatableid);
+				lua_setmetatable(gL, -3);
+			lua_pop(gL, 1);
+		}
+
 		lua_pop(gL, 1);
 	}
 }
@@ -1552,3 +1706,36 @@ int Lua_optoption(lua_State *L, int narg,
 			return i;
 	return -1;
 }
+
+void LUA_PushTaggableObjectArray
+(		lua_State *L,
+		const char *field,
+		lua_CFunction iterator,
+		lua_CFunction indexer,
+		lua_CFunction counter,
+		taggroup_t **garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char *meta)
+{
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_createtable(L, 0, 2);
+				lua_pushcfunction(L, iterator);
+				lua_setfield(L, -2, "iterate");
+
+				LUA_InsertTaggroupIterator(L, garray,
+						max_elements, element_array, sizeof_element, meta);
+
+				lua_createtable(L, 0, 1);
+					lua_pushcfunction(L, indexer);
+					lua_setfield(L, -2, "__index");
+				lua_setmetatable(L, -2);
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, counter);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, field);
+}
diff --git a/src/lua_script.h b/src/lua_script.h
index 00a2af03bd6f834f1934c4a20e2b63ba5f37fd2d..c703055446558232d90a1ae79f881d6a28a2e146 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -10,11 +10,15 @@
 /// \file  lua_script.h
 /// \brief Lua scripting basics
 
+#ifndef LUA_SCRIPT_H
+#define LUA_SCRIPT_H
+
 #include "m_fixed.h"
 #include "doomtype.h"
 #include "d_player.h"
 #include "g_state.h"
 #include "p_world.h"
+#include "taglist.h"
 
 #include "blua/lua.h"
 #include "blua/lualib.h"
@@ -41,17 +45,12 @@ void LUA_ClearExtVars(void);
 extern INT32 lua_lumploading; // is LUA_LoadLump being called?
 
 int LUA_GetErrorMessage(lua_State *L);
+int LUA_Call(lua_State *L, int nargs, int nresults, int errorhandlerindex);
 void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults);
 #ifdef LUA_ALLOW_BYTECODE
 void LUA_DumpFile(const char *filename);
 #endif
 fixed_t LUA_EvalMath(const char *word);
-void LUA_PushLightUserdata(lua_State *L, void *data, const char *meta);
-void LUA_PushUserdata(lua_State *L, void *data, const char *meta);
-void LUA_InvalidateUserdata(void *data);
-void LUA_InvalidateLevel(world_t *w);
-void LUA_InvalidateMapthings(world_t *w);
-void LUA_InvalidatePlayer(player_t *player);
 void LUA_Step(void);
 void LUA_Archive(void);
 void LUA_UnArchive(void);
@@ -63,19 +62,49 @@ int Lua_optoption(lua_State *L, int narg,
 	const char *def, const char *const lst[]);
 void LUAh_NetArchiveHook(lua_CFunction archFunc);
 
+void LUA_PushTaggableObjectArray
+(		lua_State *L,
+		const char *field,
+		lua_CFunction iterator,
+		lua_CFunction indexer,
+		lua_CFunction counter,
+		taggroup_t **garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char *meta);
+
+void LUA_InsertTaggroupIterator
+(		lua_State *L,
+		taggroup_t **garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char * meta);
+
+typedef enum {
+	LPUSHED_NIL,
+	LPUSHED_NEW,
+	LPUSHED_EXISTING,
+} lpushed_t;
+
+void LUA_PushLightUserdata(lua_State *L, void *data, const char *meta);
+void LUA_PushUserdata(lua_State *L, void *data, const char *meta);
+lpushed_t LUA_RawPushUserdata(lua_State *L, void *data);
+
+void LUA_InvalidateUserdata(void *data);
+
+void LUA_InvalidateLevel(world_t *w);
+void LUA_InvalidateMapthings(world_t *w);
+void LUA_InvalidatePlayer(player_t *player);
+
 // Console wrapper
 void COM_Lua_f(void);
 
-#define LUA_Call(L,a)\
-{\
-	if (lua_pcall(L, a, 0, 0)) {\
-		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(L,-1));\
-		lua_pop(L, 1);\
-	}\
-}
-
 #define LUA_ErrInvalid(L, type) luaL_error(L, "accessed " type " doesn't exist anymore, please check 'valid' before using " type ".");
 
+#define LUA_ErrSetDirectly(L, type, field) luaL_error(L, type " field " LUA_QL(field) " cannot be set directly.")
+
 // Deprecation warnings
 // Shows once upon use. Then doesn't show again.
 #define LUA_Deprecated(L,this_func,use_instead)\
@@ -106,3 +135,5 @@ void COM_Lua_f(void);
 
 #define INLEVEL if (! ISINLEVEL)\
 return luaL_error(L, "This can only be used in a level!");
+
+#endif/*LUA_SCRIPT_H*/
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index 3e4ddb9f0806d4debc27867ddbec50eaae66ec1d..56be6bf4f40504420e7e67cb0c6b60c739a6dfc7 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -21,7 +21,6 @@
 enum skin {
 	skin_valid = 0,
 	skin_name,
-	skin_spritedef,
 	skin_wadnum,
 	skin_flags,
 	skin_realname,
@@ -54,12 +53,12 @@ enum skin {
 	skin_contspeed,
 	skin_contangle,
 	skin_soundsid,
-	skin_availability
+	skin_availability,
+	skin_sprites
 };
 static const char *const skin_opt[] = {
 	"valid",
 	"name",
-	"spritedef",
 	"wadnum",
 	"flags",
 	"realname",
@@ -93,6 +92,7 @@ static const char *const skin_opt[] = {
 	"contangle",
 	"soundsid",
 	"availability",
+	"sprites",
 	NULL};
 
 #define UNIMPLEMENTED luaL_error(L, LUA_QL("skin_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", skin_opt[field])
@@ -113,8 +113,6 @@ static int skin_get(lua_State *L)
 	case skin_name:
 		lua_pushstring(L, skin->name);
 		break;
-	case skin_spritedef:
-		return UNIMPLEMENTED;
 	case skin_wadnum:
 		// !!WARNING!! May differ between clients due to music wads, therefore NOT NETWORK SAFE
 		return UNIMPLEMENTED;
@@ -214,6 +212,9 @@ static int skin_get(lua_State *L)
 	case skin_availability:
 		lua_pushinteger(L, skin->availability);
 		break;
+	case skin_sprites:
+		LUA_PushLightUserdata(L, skin->sprites, META_SKINSPRITES);
+		break;
 	}
 	return 1;
 }
@@ -324,6 +325,49 @@ static int soundsid_num(lua_State *L)
 	return 1;
 }
 
+enum spritesopt {
+	numframes = 0
+};
+
+static const char *const sprites_opt[] = {
+	"numframes",
+	NULL};
+
+// skin.sprites[i] -> sprites[i]
+static int lib_getSkinSprite(lua_State *L)
+{
+	spritedef_t *sprites = (spritedef_t *)luaL_checkudata(L, 1, META_SKINSPRITES);
+	playersprite_t i = luaL_checkinteger(L, 2);
+
+	if (i < 0 || i >= NUMPLAYERSPRITES*2)
+		return luaL_error(L, LUA_QL("skin_t") " field 'sprites' index %d out of range (0 - %d)", i, (NUMPLAYERSPRITES*2)-1);
+
+	LUA_PushLightUserdata(L, &sprites[i], META_SKINSPRITESLIST);
+	return 1;
+}
+
+// #skin.sprites -> NUMPLAYERSPRITES*2
+static int lib_numSkinsSprites(lua_State *L)
+{
+	lua_pushinteger(L, NUMPLAYERSPRITES*2);
+	return 1;
+}
+
+static int sprite_get(lua_State *L)
+{
+	spritedef_t *sprite = (spritedef_t *)luaL_checkudata(L, 1, META_SKINSPRITESLIST);
+	enum spritesopt field = luaL_checkoption(L, 2, NULL, sprites_opt);
+
+	switch (field)
+	{
+	case numframes:
+		lua_pushinteger(L, sprite->numframes);
+		break;
+	}
+	return 1;
+}
+
+
 int LUA_SkinLib(lua_State *L)
 {
 	luaL_newmetatable(L, META_SKIN);
@@ -345,6 +389,19 @@ int LUA_SkinLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	luaL_newmetatable(L, META_SKINSPRITES);
+		lua_pushcfunction(L, lib_getSkinSprite);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, lib_numSkinsSprites);
+		lua_setfield(L, -2, "__len");
+	lua_pop(L,1);
+
+	luaL_newmetatable(L, META_SKINSPRITESLIST);
+		lua_pushcfunction(L, sprite_get);
+		lua_setfield(L, -2, "__index");
+	lua_pop(L,1);
+
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
 			lua_pushcfunction(L, lib_getSkin);
diff --git a/src/lua_taglib.c b/src/lua_taglib.c
new file mode 100644
index 0000000000000000000000000000000000000000..f2c064b0b86f576a4450a96739ac37d34ae67b3e
--- /dev/null
+++ b/src/lua_taglib.c
@@ -0,0 +1,451 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+// Copyright (C) 2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  lua_taglib.c
+/// \brief tag list iterator for Lua scripting
+
+#include "doomdef.h"
+#include "taglist.h"
+#include "r_state.h"
+
+#include "lua_script.h"
+#include "lua_libs.h"
+
+#ifdef MUTABLE_TAGS
+#include "z_zone.h"
+#endif
+
+static int tag_iterator(lua_State *L)
+{
+	INT32 tag = lua_isnil(L, 2) ? -1 : lua_tonumber(L, 2);
+	do
+	{
+		if (++tag >= MAXTAGS)
+			return 0;
+	}
+	while (! in_bit_array(world->tags_available, tag)) ;
+	lua_pushnumber(L, tag);
+	return 1;
+}
+
+enum {
+#define UPVALUE lua_upvalueindex
+	up_garray         = UPVALUE(1),
+	up_max_elements   = UPVALUE(2),
+	up_element_array  = UPVALUE(3),
+	up_sizeof_element = UPVALUE(4),
+	up_meta           = UPVALUE(5),
+#undef UPVALUE
+};
+
+static INT32 next_element(lua_State *L, const mtag_t tag, const size_t p)
+{
+	taggroup_t *** garray = lua_touserdata(L, up_garray);
+	const size_t * max_elements = lua_touserdata(L, up_max_elements);
+	return Taggroup_Iterate(*garray, *max_elements, tag, p);
+}
+
+static void push_element(lua_State *L, void *element)
+{
+	if (LUA_RawPushUserdata(L, element) == LPUSHED_NEW)
+	{
+		lua_pushvalue(L, up_meta);
+		lua_setmetatable(L, -2);
+	}
+}
+
+static void push_next_element(lua_State *L, const INT32 element)
+{
+	char * element_array = *(char **)lua_touserdata(L, up_element_array);
+	const size_t sizeof_element = lua_tonumber(L, up_sizeof_element);
+	push_element(L, &element_array[element * sizeof_element]);
+}
+
+struct element_iterator_state {
+	mtag_t tag;
+	size_t p;
+};
+
+static int element_iterator(lua_State *L)
+{
+	struct element_iterator_state * state = lua_touserdata(L, 1);
+	if (lua_isnoneornil(L, 3))
+		state->p = 0;
+	lua_pushnumber(L, ++state->p);
+	lua_gettable(L, 1);
+	return 1;
+}
+
+static int lib_iterateTags(lua_State *L)
+{
+	if (lua_gettop(L) < 2)
+	{
+		lua_pushcfunction(L, tag_iterator);
+		return 1;
+	}
+	else
+		return tag_iterator(L);
+}
+
+static int lib_numTags(lua_State *L)
+{
+	lua_pushnumber(L, world->num_tags);
+	return 1;
+}
+
+static int lib_getTaggroup(lua_State *L)
+{
+	struct element_iterator_state *state;
+
+	mtag_t tag;
+
+	if (lua_gettop(L) > 1)
+		return luaL_error(L, "too many arguments");
+
+	if (lua_isnoneornil(L, 1))
+	{
+		tag = MTAG_GLOBAL;
+	}
+	else
+	{
+		tag = lua_tonumber(L, 1);
+		luaL_argcheck(L, tag >= -1, 1, "tag out of range");
+	}
+
+	state = lua_newuserdata(L, sizeof *state);
+	state->tag = tag;
+	state->p = 0;
+
+	lua_pushvalue(L, lua_upvalueindex(1));
+	lua_setmetatable(L, -2);
+
+	return 1;
+}
+
+static int lib_getTaggroupElement(lua_State *L)
+{
+	const size_t p = luaL_checknumber(L, 2) - 1;
+	const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1);
+	const INT32 element = next_element(L, tag, p);
+
+	if (element == -1)
+		return 0;
+	else
+	{
+		push_next_element(L, element);
+		return 1;
+	}
+}
+
+static int lib_numTaggroupElements(lua_State *L)
+{
+	const mtag_t tag = *(mtag_t *)lua_touserdata(L, 1);
+	if (tag == MTAG_GLOBAL)
+		lua_pushnumber(L, *(size_t *)lua_touserdata(L, up_max_elements));
+	else
+	{
+		const taggroup_t *** garray = lua_touserdata(L, up_garray);
+		lua_pushnumber(L, Taggroup_Count((*garray)[tag]));
+	}
+	return 1;
+}
+
+#ifdef MUTABLE_TAGS
+static int meta_ref[2];
+#endif
+
+static int has_valid_field(lua_State *L)
+{
+	int equal;
+	lua_rawgeti(L, LUA_ENVIRONINDEX, 1);
+	equal = lua_rawequal(L, 2, -1);
+	lua_pop(L, 1);
+	return equal;
+}
+
+static taglist_t * valid_taglist(lua_State *L, int idx, boolean getting)
+{
+	taglist_t *list = *(taglist_t **)lua_touserdata(L, idx);
+
+	if (list == NULL)
+	{
+		if (getting && has_valid_field(L))
+			lua_pushboolean(L, 0);
+		else
+			LUA_ErrInvalid(L, "taglist");/* doesn't actually return */
+		return NULL;
+	}
+	else
+		return list;
+}
+
+static taglist_t * check_taglist(lua_State *L, int idx)
+{
+	if (lua_isuserdata(L, idx) && lua_getmetatable(L, idx))
+	{
+		lua_getref(L, meta_ref[0]);
+		lua_getref(L, meta_ref[1]);
+
+		if (lua_rawequal(L, -3, -2) || lua_rawequal(L, -3, -1))
+		{
+			lua_pop(L, 3);
+			return valid_taglist(L, idx, false);
+		}
+	}
+
+	return luaL_argerror(L, idx, "must be a tag list"), NULL;
+}
+
+static int taglist_get(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, true);
+
+	if (list == NULL)/* valid check */
+		return 1;
+
+	if (lua_isnumber(L, 2))
+	{
+		const size_t i = lua_tonumber(L, 2);
+
+		if (list && i <= list->count)
+		{
+			lua_pushnumber(L, list->tags[i - 1]);
+			return 1;
+		}
+		else
+			return 0;
+	}
+	else if (has_valid_field(L))
+	{
+		lua_pushboolean(L, 1);
+		return 1;
+	}
+	else
+	{
+		lua_getmetatable(L, 1);
+		lua_replace(L, 1);
+		lua_rawget(L, 1);
+		return 1;
+	}
+}
+
+static int taglist_len(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, false);
+	lua_pushnumber(L, list->count);
+	return 1;
+}
+
+static int taglist_equal(lua_State *L)
+{
+	const taglist_t *lhs = check_taglist(L, 1);
+	const taglist_t *rhs = check_taglist(L, 2);
+	lua_pushboolean(L, Tag_Compare(lhs, rhs));
+	return 1;
+}
+
+static int taglist_iterator(lua_State *L)
+{
+	const taglist_t *list = valid_taglist(L, 1, false);
+	const size_t i = 1 + lua_tonumber(L, lua_upvalueindex(1));
+	if (i <= list->count)
+	{
+		lua_pushnumber(L, list->tags[i - 1]);
+		/* watch me exploit an upvalue as a control because
+			I want to use the control as the value */
+		lua_pushnumber(L, i);
+		lua_replace(L, lua_upvalueindex(1));
+		return 1;
+	}
+	else
+		return 0;
+}
+
+static int taglist_iterate(lua_State *L)
+{
+	check_taglist(L, 1);
+	lua_pushnumber(L, 0);
+	lua_pushcclosure(L, taglist_iterator, 1);
+	lua_pushvalue(L, 1);
+	return 2;
+}
+
+static int taglist_find(lua_State *L)
+{
+	const taglist_t *list = check_taglist(L, 1);
+	const mtag_t tag = luaL_checknumber(L, 2);
+	lua_pushboolean(L, Tag_Find(list, tag));
+	return 1;
+}
+
+static int taglist_shares(lua_State *L)
+{
+	const taglist_t *lhs = check_taglist(L, 1);
+	const taglist_t *rhs = check_taglist(L, 2);
+	lua_pushboolean(L, Tag_Share(lhs, rhs));
+	return 1;
+}
+
+/* only sector tags are mutable... */
+
+#ifdef MUTABLE_TAGS
+static size_t sector_of_taglist(taglist_t *list)
+{
+	return (sector_t *)((char *)list - offsetof (sector_t, tags)) - sectors;
+}
+
+static int this_taglist(lua_State *L)
+{
+	lua_settop(L, 1);
+	return 1;
+}
+
+static int taglist_add(lua_State *L)
+{
+	taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST);
+	const mtag_t tag = luaL_checknumber(L, 2);
+
+	if (! Tag_Find(list, tag))
+	{
+		Taggroup_Add(world->tags_sectors, tag, sector_of_taglist(list));
+		Tag_Add(list, tag);
+	}
+
+	return this_taglist(L);
+}
+
+static int taglist_remove(lua_State *L)
+{
+	taglist_t *list = *(taglist_t **)luaL_checkudata(L, 1, META_SECTORTAGLIST);
+	const mtag_t tag = luaL_checknumber(L, 2);
+
+	size_t i;
+
+	for (i = 0; i < list->count; ++i)
+	{
+		if (list->tags[i] == tag)
+		{
+			if (list->count > 1)
+			{
+				memmove(&list->tags[i], &list->tags[i + 1],
+						(list->count - 1 - i) * sizeof (mtag_t));
+				list->tags = Z_Realloc(list->tags,
+						(--list->count) * sizeof (mtag_t), PU_LEVEL, NULL);
+				Taggroup_Remove(world->tags_sectors, tag, sector_of_taglist(list));
+			}
+			else/* reset to default tag */
+				Tag_SectorFSet(sector_of_taglist(list), 0);
+			break;
+		}
+	}
+
+	return this_taglist(L);
+}
+#endif/*MUTABLE_TAGS*/
+
+void LUA_InsertTaggroupIterator
+(		lua_State *L,
+		taggroup_t **garray[],
+		size_t * max_elements,
+		void * element_array,
+		size_t sizeof_element,
+		const char * meta)
+{
+	lua_createtable(L, 0, 3);
+		lua_pushlightuserdata(L, garray);
+		lua_pushlightuserdata(L, max_elements);
+
+		lua_pushvalue(L, -2);
+		lua_pushvalue(L, -2);
+		lua_pushlightuserdata(L, element_array);
+		lua_pushnumber(L, sizeof_element);
+		luaL_getmetatable(L, meta);
+		lua_pushcclosure(L, lib_getTaggroupElement, 5);
+		lua_setfield(L, -4, "__index");
+
+		lua_pushcclosure(L, lib_numTaggroupElements, 2);
+		lua_setfield(L, -2, "__len");
+
+		lua_pushcfunction(L, element_iterator);
+		lua_setfield(L, -2, "__call");
+	lua_pushcclosure(L, lib_getTaggroup, 1);
+	lua_setfield(L, -2, "tagged");
+}
+
+static luaL_Reg taglist_lib[] = {
+	{"iterate", taglist_iterate},
+	{"find", taglist_find},
+	{"shares", taglist_shares},
+#ifdef MUTABLE_TAGS
+	{"add", taglist_add},
+	{"remove", taglist_remove},
+#endif
+	{0}
+};
+
+static void open_taglist(lua_State *L)
+{
+	luaL_register(L, "taglist", taglist_lib);
+
+	lua_getfield(L, -1, "find");
+	lua_setfield(L, -2, "has");
+}
+
+#define new_literal(L, s) \
+	(lua_pushliteral(L, s), luaL_ref(L, -2))
+
+#ifdef MUTABLE_TAGS
+static int
+#else
+static void
+#endif
+set_taglist_metatable(lua_State *L, const char *meta)
+{
+	luaL_newmetatable(L, meta);
+		lua_pushcfunction(L, taglist_get);
+		lua_createtable(L, 0, 1);
+			new_literal(L, "valid");
+		lua_setfenv(L, -2);
+		lua_setfield(L, -2, "__index");
+
+		lua_pushcfunction(L, taglist_len);
+		lua_setfield(L, -2, "__len");
+
+		lua_pushcfunction(L, taglist_equal);
+		lua_setfield(L, -2, "__eq");
+#ifdef MUTABLE_TAGS
+	return luaL_ref(L, LUA_REGISTRYINDEX);
+#endif
+}
+
+int LUA_TagLib(lua_State *L)
+{
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_createtable(L, 0, 1);
+				lua_pushcfunction(L, lib_iterateTags);
+				lua_setfield(L, -2, "iterate");
+			lua_setfield(L, -2, "__index");
+
+			lua_pushcfunction(L, lib_numTags);
+			lua_setfield(L, -2, "__len");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "tags");
+
+	open_taglist(L);
+
+#ifdef MUTABLE_TAGS
+	meta_ref[0] = set_taglist_metatable(L, META_TAGLIST);
+	meta_ref[1] = set_taglist_metatable(L, META_SECTORTAGLIST);
+#else
+	set_taglist_metatable(L, META_TAGLIST);
+#endif
+
+	return 0;
+}
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 83bc3dddc0d46a682fa3df8863caf0a841de5c04..41f99254eff59fea3d80f7592168723d9de7a975 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -18,7 +18,7 @@
 #include "z_zone.h"
 #include "v_video.h"
 #include "i_video.h"
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 #include "m_misc.h"
 #include "st_stuff.h" // st_palette
 
@@ -29,15 +29,21 @@
 // GIFs are always little-endian
 #include "byteptr.h"
 
-consvar_t cv_gif_optimize = {"gif_optimize", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_gif_downscale =  {"gif_downscale", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_gif_dynamicdelay = {"gif_dynamicdelay", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_gif_localcolortable =  {"gif_localcolortable", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+CV_PossibleValue_t gif_dynamicdelay_cons_t[] = {
+	{0, "Off"},
+	{1, "On"},
+	{2, "Accurate, experimental"},
+{0, NULL}};
+
+consvar_t cv_gif_optimize = CVAR_INIT ("gif_optimize", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_gif_downscale =  CVAR_INIT ("gif_downscale", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_gif_dynamicdelay = CVAR_INIT ("gif_dynamicdelay", "On", CV_SAVE, gif_dynamicdelay_cons_t, NULL);
+consvar_t cv_gif_localcolortable =  CVAR_INIT ("gif_localcolortable", "On", CV_SAVE, CV_OnOff, NULL);
 
 #ifdef HAVE_ANIGIF
 static boolean gif_optimize = false; // So nobody can do something dumb
 static boolean gif_downscale = false; // like changing cvars mid output
-static boolean gif_dynamicdelay = false; // and messing something up
+static UINT8 gif_dynamicdelay = (UINT8)0; // and messing something up
 
 // Palette handling
 static boolean gif_localcolortable = false;
@@ -47,7 +53,8 @@ static RGBA_t *gif_framepalette = NULL;
 
 static FILE *gif_out = NULL;
 static INT32 gif_frames = 0;
-static UINT32 gif_prevframems = 0;
+static precise_t gif_prevframetime = 0;
+static UINT32 gif_delayus = 0; // "us" is microseconds
 static UINT8 gif_writeover = 0;
 
 
@@ -499,20 +506,22 @@ static size_t gifframe_size = 8192;
 // converts an RGB frame to a frame with a palette.
 //
 #ifdef HWRENDER
+static colorlookup_t gif_colorlookup;
+
 static void GIF_rgbconvert(UINT8 *linear, UINT8 *scr)
 {
 	UINT8 r, g, b;
 	size_t src = 0, dest = 0;
 	size_t size = (vid.width * vid.height * 3);
 
-	InitColorLUT(gif_framepalette);
+	InitColorLUT(&gif_colorlookup, (gif_localcolortable) ? gif_framepalette : gif_headerpalette, true);
 
 	while (src < size)
 	{
 		r = (UINT8)linear[src];
 		g = (UINT8)linear[src + 1];
 		b = (UINT8)linear[src + 2];
-		scr[dest] = colorlookup[r >> SHIFTCOLORBITS][g >> SHIFTCOLORBITS][b >> SHIFTCOLORBITS];
+		scr[dest] = GetColorLUTDirect(&gif_colorlookup, r, g, b);
 		src += (3 * scrbuf_downscaleamt);
 		dest += scrbuf_downscaleamt;
 	}
@@ -592,16 +601,30 @@ static void GIF_framewrite(void)
 
 	// screen regions are handled in GIF_lzw
 	{
-		UINT16 delay;
+		UINT16 delay = 0;
 		INT32 startline;
 
-		if (gif_dynamicdelay) {
+		if (gif_dynamicdelay ==(UINT8) 2)
+		{
 			// golden's attempt at creating a "dynamic delay"
+			UINT16 mingifdelay = 10; // minimum gif delay in milliseconds (keep at 10 because gifs can't get more precise).
+			gif_delayus += I_PreciseToMicros(I_GetPreciseTime() - gif_prevframetime); // increase delay by how much time was spent between last measurement
+
+			if (gif_delayus/1000 >= mingifdelay) // delay is big enough to be able to effect gif frame delay?
+			{
+				int frames = (gif_delayus/1000) / mingifdelay; // get amount of frames to delay.
+				delay = frames; // set the delay to delay that amount of frames.
+				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)
+		{
 			float delayf = ceil(100.0f/NEWTICRATE);
 
-			delay = (UINT16)((I_GetTimeMicros() - gif_prevframems)/10/1000);
-			if (delay < (int)(delayf))
-				delay = (int)(delayf);
+			delay = (UINT16)I_PreciseToMicros((I_GetPreciseTime() - gif_prevframetime))/10/1000;
+
+			if (delay < (UINT16)(delayf))
+				delay = (UINT16)(delayf);
 		}
 		else
 		{
@@ -688,7 +711,7 @@ static void GIF_framewrite(void)
 	}
 	fwrite(gifframe_data, 1, (p - gifframe_data), gif_out);
 	++gif_frames;
-	gif_prevframems = I_GetTimeMicros();
+	gif_prevframetime = I_GetPreciseTime();
 }
 
 
@@ -709,14 +732,15 @@ INT32 GIF_open(const char *filename)
 
 	gif_optimize = (!!cv_gif_optimize.value);
 	gif_downscale = (!!cv_gif_downscale.value);
-	gif_dynamicdelay = (!!cv_gif_dynamicdelay.value);
+	gif_dynamicdelay = (UINT8)cv_gif_dynamicdelay.value;
 	gif_localcolortable = (!!cv_gif_localcolortable.value);
 	gif_colorprofile = (!!cv_screenshot_colorprofile.value);
 	gif_headerpalette = GIF_getpalette(0);
 
 	GIF_headwrite();
 	gif_frames = 0;
-	gif_prevframems = I_GetTimeMicros();
+	gif_prevframetime = I_GetPreciseTime();
+	gif_delayus = 0;
 	return 1;
 }
 
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 04d27199a898f4d43e83118a6dc2604e7e9fbe22..9e36a325ead0ef7079ff472ba77aa81fa4d00137 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -967,10 +967,10 @@ static CV_PossibleValue_t op_speed_t[] = {{1, "MIN"}, {128, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t op_flags_t[] = {{0, "MIN"}, {15, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t op_hoopflags_t[] = {{0, "MIN"}, {15, "MAX"}, {0, NULL}};
 
-consvar_t cv_mapthingnum = {"op_mapthingnum", "0", CV_NOTINNET, op_mapthing_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_speed = {"op_speed", "16", CV_NOTINNET, op_speed_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_opflags = {"op_flags", "0", CV_NOTINNET, op_flags_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ophoopflags = {"op_hoopflags", "4", CV_NOTINNET, op_hoopflags_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mapthingnum = CVAR_INIT ("op_mapthingnum", "0", CV_NOTINNET, op_mapthing_t, NULL);
+consvar_t cv_speed = CVAR_INIT ("op_speed", "16", CV_NOTINNET, op_speed_t, NULL);
+consvar_t cv_opflags = CVAR_INIT ("op_flags", "0", CV_NOTINNET, op_flags_t, NULL);
+consvar_t cv_ophoopflags = CVAR_INIT ("op_hoopflags", "4", CV_NOTINNET, op_hoopflags_t, NULL);
 
 boolean objectplacing = false;
 mobjtype_t op_currentthing = 0; // For the object placement mode
@@ -1023,8 +1023,8 @@ static void OP_CycleThings(INT32 amt)
 		states[S_OBJPLACE_DUMMY].frame = states[mobjinfo[op_currentthing].spawnstate].frame;
 	}
 	if (players[0].mo->eflags & MFE_VERTICALFLIP) // correct z when flipped
-		players[0].mo->z += players[0].mo->height - mobjinfo[op_currentthing].height;
-	players[0].mo->height = mobjinfo[op_currentthing].height;
+		players[0].mo->z += players[0].mo->height - FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale);
+	players[0].mo->height = FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale);
 	P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY);
 
 	op_currentdoomednum = mobjinfo[op_currentthing].doomednum;
@@ -1110,6 +1110,10 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 	mt->angle = (INT16)(FixedInt(AngleFixed(player->mo->angle)));
 
 	mt->options = (mt->z << ZSHIFT) | (UINT16)cv_opflags.value;
+	mt->scale = player->mo->scale;
+	memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
+	memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
+	mt->pitch = mt->roll = 0;
 	return mt;
 }
 
@@ -1147,7 +1151,7 @@ void OP_NightsObjectplace(player_t *player)
 	if (player->pflags & PF_ATTACKDOWN)
 	{
 		// Are ANY objectplace buttons pressed?  If no, remove flag.
-		if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_USE|BT_WEAPONNEXT|BT_WEAPONPREV)))
+		if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_SPIN|BT_WEAPONNEXT|BT_WEAPONPREV)))
 			player->pflags &= ~PF_ATTACKDOWN;
 
 		// Do nothing.
@@ -1254,7 +1258,7 @@ void OP_NightsObjectplace(player_t *player)
 	}
 
 	// This places a custom object as defined in the console cv_mapthingnum.
-	if (cmd->buttons & BT_USE)
+	if (cmd->buttons & BT_SPIN)
 	{
 		UINT16 angle;
 
@@ -1300,27 +1304,26 @@ void OP_ObjectplaceMovement(player_t *player)
 {
 	ticcmd_t *cmd = &player->cmd;
 
-	if (!player->climbing && (netgame || !cv_analog[0].value || (player->pflags & PF_SPINNING)))
-		player->drawangle = player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
+	player->drawangle = player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
 
 	ticruned++;
 	if (!(cmd->angleturn & TICCMD_RECEIVED))
 		ticmiss++;
 
 	if (cmd->buttons & BT_JUMP)
-		player->mo->z += FRACUNIT*cv_speed.value;
-	else if (cmd->buttons & BT_USE)
-		player->mo->z -= FRACUNIT*cv_speed.value;
+		player->mo->z += player->mo->scale*cv_speed.value;
+	else if (cmd->buttons & BT_SPIN)
+		player->mo->z -= player->mo->scale*cv_speed.value;
 
 	if (cmd->forwardmove != 0)
 	{
-		P_Thrust(player->mo, player->mo->angle, (cmd->forwardmove*FRACUNIT/MAXPLMOVE)*cv_speed.value);
+		P_Thrust(player->mo, player->mo->angle, (cmd->forwardmove*player->mo->scale/MAXPLMOVE)*cv_speed.value);
 		P_TeleportMove(player->mo, player->mo->x+player->mo->momx, player->mo->y+player->mo->momy, player->mo->z);
 		player->mo->momx = player->mo->momy = 0;
 	}
 	if (cmd->sidemove != 0)
 	{
-		P_Thrust(player->mo, player->mo->angle-ANGLE_90, (cmd->sidemove*FRACUNIT/MAXPLMOVE)*cv_speed.value);
+		P_Thrust(player->mo, player->mo->angle-ANGLE_90, (cmd->sidemove*player->mo->scale/MAXPLMOVE)*cv_speed.value);
 		P_TeleportMove(player->mo, player->mo->x+player->mo->momx, player->mo->y+player->mo->momy, player->mo->z);
 		player->mo->momx = player->mo->momy = 0;
 	}
@@ -1434,74 +1437,94 @@ void Command_Writethings_f(void)
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_OBJECTPLACE;
 
-	P_WriteThings(W_GetNumForName(G_BuildMapName(gamemap)) + ML_THINGS);
+	P_WriteThings();
 }
 
 void Command_ObjectPlace_f(void)
 {
+	size_t thingarg;
+	size_t silent;
+
 	REQUIRE_INLEVEL;
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
 
 	G_SetGameModified(multiplayer);
 
-	// Entering objectplace?
-	if (!objectplacing)
-	{
-		objectplacing = true;
+	silent = COM_CheckParm("-silent");
 
-		if (players[0].powers[pw_carry] == CR_NIGHTSMODE)
-			return;
+	thingarg = 2 - ( silent != 1 );
 
-		if (!COM_CheckParm("-silent"))
+	// Entering objectplace?
+	if (!objectplacing || thingarg < COM_Argc())
+	{
+		if (!objectplacing)
 		{
-			HU_SetCEchoFlags(V_RETURN8|V_MONOSPACE|V_AUTOFADEOUT);
-			HU_SetCEchoDuration(10);
-			HU_DoCEcho(va(M_GetText(
-				"\\\\\\\\\\\\\\\\\\\\\\\\\x82"
-				"   Objectplace Controls:   \x80\\\\"
-				"Weapon Next/Prev: Cycle mapthings\\"
-				"            Jump: Float up       \\"
-				"            Spin: Float down     \\"
-				"       Fire Ring: Place object   \\")));
-		}
+			objectplacing = true;
 
-		// Save all the player's data.
-		op_oldflags1 = players[0].mo->flags;
-		op_oldflags2 = players[0].mo->flags2;
-		op_oldeflags = players[0].mo->eflags;
-		op_oldpflags = players[0].pflags;
-		op_oldmomx = players[0].mo->momx;
-		op_oldmomy = players[0].mo->momy;
-		op_oldmomz = players[0].mo->momz;
-		op_oldheight = players[0].mo->height;
-		op_oldstate = S_PLAY_STND;
-		op_oldcolor = players[0].mo->color; // save color too in case of super/fireflower
-
-		// Remove ALL flags and motion.
-		P_UnsetThingPosition(players[0].mo);
-		players[0].pflags = 0;
-		players[0].mo->flags2 = 0;
-		players[0].mo->eflags = 0;
-		players[0].mo->flags = (MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP);
-		players[0].mo->momx = players[0].mo->momy = players[0].mo->momz = 0;
-		P_SetThingPosition(players[0].mo);
+			if (players[0].powers[pw_carry] == CR_NIGHTSMODE)
+				return;
 
-		// Take away color so things display properly
-		players[0].mo->color = 0;
+			if (! silent)
+			{
+				HU_SetCEchoFlags(V_RETURN8|V_MONOSPACE|V_AUTOFADEOUT);
+				HU_SetCEchoDuration(10);
+				HU_DoCEcho(va(M_GetText(
+					"\\\\\\\\\\\\\\\\\\\\\\\\\x82"
+					"   Objectplace Controls:   \x80\\\\"
+					"Weapon Next/Prev: Cycle mapthings\\"
+					"            Jump: Float up       \\"
+					"            Spin: Float down     \\"
+					"       Fire Ring: Place object   \\")));
+			}
+
+			// Save all the player's data.
+			op_oldflags1 = players[0].mo->flags;
+			op_oldflags2 = players[0].mo->flags2;
+			op_oldeflags = players[0].mo->eflags;
+			op_oldpflags = players[0].pflags;
+			op_oldmomx = players[0].mo->momx;
+			op_oldmomy = players[0].mo->momy;
+			op_oldmomz = players[0].mo->momz;
+			op_oldheight = players[0].mo->height;
+			op_oldstate = S_PLAY_STND;
+			op_oldcolor = players[0].mo->color; // save color too in case of super/fireflower
+
+			// Remove ALL flags and motion.
+			P_UnsetThingPosition(players[0].mo);
+			players[0].pflags = 0;
+			players[0].mo->flags2 = 0;
+			players[0].mo->eflags = 0;
+			players[0].mo->flags = (MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP);
+			players[0].mo->momx = players[0].mo->momy = players[0].mo->momz = 0;
+			P_SetThingPosition(players[0].mo);
+
+			// Take away color so things display properly
+			players[0].mo->color = 0;
+
+			// Like the classics, recover from death by entering objectplace
+			if (players[0].mo->health <= 0)
+			{
+				players[0].mo->health = 1;
+				players[0].deadtimer = 0;
+				op_oldflags1 = mobjinfo[MT_PLAYER].flags;
+				++players[0].lives;
+				players[0].playerstate = PST_LIVE;
+				P_RestoreMusic(&players[0]);
+			}
+			else
+				op_oldstate = (statenum_t)(players[0].mo->state-states);
+		}
 
-		// Like the classics, recover from death by entering objectplace
-		if (players[0].mo->health <= 0)
+		if (thingarg < COM_Argc())
 		{
-			players[0].mo->health = 1;
-			players[0].deadtimer = 0;
-			op_oldflags1 = mobjinfo[MT_PLAYER].flags;
-			++players[0].lives;
-			players[0].playerstate = PST_LIVE;
-			P_RestoreMusic(&players[0]);
+			UINT16 mapthingnum = atoi(COM_Argv(thingarg));
+			mobjtype_t type = P_GetMobjtype(mapthingnum);
+			if (type == MT_UNKNOWN)
+				CONS_Printf(M_GetText("No mobj type delegated to thing type %d.\n"), mapthingnum);
+			else
+				op_currentthing = type;
 		}
-		else
-			op_oldstate = (statenum_t)(players[0].mo->state-states);
 
 		// If no thing set, then cycle a little
 		if (!op_currentthing)
@@ -1509,8 +1532,8 @@ void Command_ObjectPlace_f(void)
 			op_currentthing = 1;
 			OP_CycleThings(1);
 		}
-		else // Cycle things sets this for the former.
-			players[0].mo->height = mobjinfo[op_currentthing].height;
+		else
+			OP_CycleThings(0); // sets all necessary height values without cycling op_currentthing
 
 		P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY);
 	}
diff --git a/src/m_menu.c b/src/m_menu.c
index bfe403487c31cbc84c7ba6293b0ed14a1bfeafce..547be04351217853134510f67396137f1af5a811 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -32,6 +32,7 @@
 #include "sounds.h"
 #include "s_sound.h"
 #include "i_system.h"
+#include "i_threads.h"
 
 // Addfile
 #include "filesrch.h"
@@ -74,12 +75,6 @@
 #endif
 #endif
 
-#ifdef PC_DOS
-#include <stdio.h> // for snprintf
-int	snprintf(char *str, size_t n, const char *fmt, ...);
-//int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
-#endif
-
 #if defined (__GNUC__) && (__GNUC__ >= 4)
 #define FIXUPO0
 #endif
@@ -122,6 +117,12 @@ typedef enum
 	NUM_QUITMESSAGES
 } text_enum;
 
+#ifdef HAVE_THREADS
+I_mutex m_menu_mutex;
+#endif
+
+M_waiting_mode_t m_waiting_mode = M_NOT_WAITING;
+
 const char *quitmsg[NUM_QUITMESSAGES];
 
 // Stuff for customizing the player select screen Tails 09-22-2003
@@ -405,37 +406,29 @@ static void M_ResetCvars(void);
 
 // Consvar onchange functions
 static void Newgametype_OnChange(void);
-#ifdef HWRENDER
-static void Newrenderer_OnChange(void);
-#endif
 static void Dummymares_OnChange(void);
 
 // ==========================================================================
 // CONSOLE VARIABLES AND THEIR POSSIBLE VALUES GO HERE.
 // ==========================================================================
 
-consvar_t cv_showfocuslost = {"showfocuslost", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
+consvar_t cv_showfocuslost = CVAR_INIT ("showfocuslost", "Yes", CV_SAVE, CV_YesNo, NULL);
 
 static CV_PossibleValue_t map_cons_t[] = {
 	{1,"MIN"},
 	{NUMMAPS, "MAX"},
 	{0,NULL}
 };
-consvar_t cv_nextmap = {"nextmap", "1", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_nextmap = CVAR_INIT ("nextmap", "1", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange);
 
 static CV_PossibleValue_t skins_cons_t[MAXSKINS+1] = {{1, DEFAULTSKIN}};
-consvar_t cv_chooseskin = {"chooseskin", DEFAULTSKIN, CV_HIDEN|CV_CALL, skins_cons_t, Nextmap_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chooseskin = CVAR_INIT ("chooseskin", DEFAULTSKIN, CV_HIDEN|CV_CALL, skins_cons_t, Nextmap_OnChange);
 
 // This gametype list is integral for many different reasons.
-// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h!
+// When you add gametypes here, don't forget to update them in deh_tables.c and doomstat.h!
 CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1];
 
-consvar_t cv_newgametype = {"newgametype", "Co-op", CV_HIDEN|CV_CALL, gametype_cons_t, Newgametype_OnChange, 0, NULL, NULL, 0, 0, NULL};
-
-#ifdef HWRENDER
-consvar_t cv_newrenderer = {"newrenderer", "Software", CV_HIDEN|CV_CALL, cv_renderer_t, Newrenderer_OnChange, 0, NULL, NULL, 0, 0, NULL};
-static int newrenderer_set = 1;/* Software doesn't need confirmation! */
-#endif
+consvar_t cv_newgametype = CVAR_INIT ("newgametype", "Co-op", CV_HIDEN|CV_CALL, gametype_cons_t, Newgametype_OnChange);
 
 static CV_PossibleValue_t serversort_cons_t[] = {
 	{0,"Ping"},
@@ -446,22 +439,22 @@ static CV_PossibleValue_t serversort_cons_t[] = {
 	{5,"Gametype"},
 	{0,NULL}
 };
-consvar_t cv_serversort = {"serversort", "Ping", CV_HIDEN | CV_CALL, serversort_cons_t, M_SortServerList, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_serversort = CVAR_INIT ("serversort", "Ping", CV_HIDEN | CV_CALL, serversort_cons_t, M_SortServerList);
 
 // first time memory
-consvar_t cv_tutorialprompt = {"tutorialprompt", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_tutorialprompt = CVAR_INIT ("tutorialprompt", "On", CV_SAVE, CV_OnOff, NULL);
 
 // autorecord demos for time attack
-static consvar_t cv_autorecord = {"autorecord", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_autorecord = CVAR_INIT ("autorecord", "Yes", 0, CV_YesNo, NULL);
 
 CV_PossibleValue_t ghost_cons_t[] = {{0, "Hide"}, {1, "Show"}, {2, "Show All"}, {0, NULL}};
 CV_PossibleValue_t ghost2_cons_t[] = {{0, "Hide"}, {1, "Show"}, {0, NULL}};
 
-consvar_t cv_ghost_bestscore = {"ghost_bestscore", "Show", CV_SAVE, ghost_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ghost_besttime  = {"ghost_besttime",  "Show", CV_SAVE, ghost_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ghost_bestrings = {"ghost_bestrings", "Show", CV_SAVE, ghost_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ghost_last      = {"ghost_last",      "Show", CV_SAVE, ghost_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ghost_guest     = {"ghost_guest",     "Show", CV_SAVE, ghost2_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_ghost_bestscore = CVAR_INIT ("ghost_bestscore", "Show", CV_SAVE, ghost_cons_t, NULL);
+consvar_t cv_ghost_besttime  = CVAR_INIT ("ghost_besttime",  "Show", CV_SAVE, ghost_cons_t, NULL);
+consvar_t cv_ghost_bestrings = CVAR_INIT ("ghost_bestrings", "Show", CV_SAVE, ghost_cons_t, NULL);
+consvar_t cv_ghost_last      = CVAR_INIT ("ghost_last",      "Show", CV_SAVE, ghost_cons_t, NULL);
+consvar_t cv_ghost_guest     = CVAR_INIT ("ghost_guest",     "Show", CV_SAVE, ghost2_cons_t, NULL);
 
 //Console variables used solely in the menu system.
 //todo: add a way to use non-console variables in the menu
@@ -475,19 +468,19 @@ static CV_PossibleValue_t dummymares_cons_t[] = {
 	{-1, "END"}, {0,"Overall"}, {1,"Mare 1"}, {2,"Mare 2"}, {3,"Mare 3"}, {4,"Mare 4"}, {5,"Mare 5"}, {6,"Mare 6"}, {7,"Mare 7"}, {8,"Mare 8"}, {0,NULL}
 };
 
-static consvar_t cv_dummyteam = {"dummyteam", "Spectator", CV_HIDEN, dummyteam_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_dummyscramble = {"dummyscramble", "Random", CV_HIDEN, dummyscramble_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_dummyrings = {"dummyrings", "0", CV_HIDEN, ringlimit_cons_t,	NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_dummylives = {"dummylives", "0", CV_HIDEN, liveslimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_dummycontinues = {"dummycontinues", "0", CV_HIDEN, contlimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDEN, dummyteam_cons_t, NULL);
+static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDEN, dummyscramble_cons_t, NULL);
+static consvar_t cv_dummyrings = CVAR_INIT ("dummyrings", "0", CV_HIDEN, ringlimit_cons_t,	NULL);
+static consvar_t cv_dummylives = CVAR_INIT ("dummylives", "0", CV_HIDEN, liveslimit_cons_t, NULL);
+static consvar_t cv_dummycontinues = CVAR_INIT ("dummycontinues", "0", CV_HIDEN, contlimit_cons_t, NULL);
+static consvar_t cv_dummymares = CVAR_INIT ("dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange);
 
 CV_PossibleValue_t marathon_cons_t[] = {{0, "Standard"}, {1, "Live Event Backup"}, {2, "Ultimate"}, {0, NULL}};
 CV_PossibleValue_t loadless_cons_t[] = {{0, "Realtime"}, {1, "In-game"}, {0, NULL}};
 
-consvar_t cv_dummymarathon = {"dummymarathon", "Standard", CV_HIDEN, marathon_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_dummycutscenes = {"dummycutscenes", "Off", CV_HIDEN, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_dummyloadless = {"dummyloadless", "In-game", CV_HIDEN, loadless_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_dummymarathon = CVAR_INIT ("dummymarathon", "Standard", CV_HIDEN, marathon_cons_t, NULL);
+consvar_t cv_dummycutscenes = CVAR_INIT ("dummycutscenes", "Off", CV_HIDEN, CV_OnOff, NULL);
+consvar_t cv_dummyloadless = CVAR_INIT ("dummyloadless", "In-game", CV_HIDEN, loadless_cons_t, NULL);
 
 // ==========================================================================
 // ORGANIZATION START.
@@ -1033,7 +1026,7 @@ enum
 	FIRSTSERVERLINE
 };
 
-static menuitem_t MP_RoomMenu[] =
+menuitem_t MP_RoomMenu[] =
 {
 	{IT_STRING | IT_CALL, NULL, "<Unlisted Mode>", M_ChooseRoom,   9},
 	{IT_DISABLED,         NULL, "",               M_ChooseRoom,  18},
@@ -1116,7 +1109,7 @@ static menuitem_t OP_ChangeControlsMenu[] =
 	{IT_CALL | IT_STRING2, NULL, "Move Left",        M_ChangeControl, gc_strafeleft  },
 	{IT_CALL | IT_STRING2, NULL, "Move Right",       M_ChangeControl, gc_straferight },
 	{IT_CALL | IT_STRING2, NULL, "Jump",             M_ChangeControl, gc_jump      },
-	{IT_CALL | IT_STRING2, NULL, "Spin",             M_ChangeControl, gc_use     },
+	{IT_CALL | IT_STRING2, NULL, "Spin",             M_ChangeControl, gc_spin     },
 	{IT_HEADER, NULL, "Camera", NULL, 0},
 	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
 	{IT_CALL | IT_STRING2, NULL, "Look Up",        M_ChangeControl, gc_lookup      },
@@ -1346,7 +1339,7 @@ static menuitem_t OP_VideoOptionsMenu[] =
 #endif
 	{IT_STRING | IT_CVAR, NULL, "Vertical Sync",                &cv_vidwait,         16},
 #ifdef HWRENDER
-	{IT_STRING | IT_CVAR, NULL, "Renderer",                     &cv_newrenderer,        21},
+	{IT_STRING | IT_CVAR, NULL, "Renderer",                     &cv_renderer,        21},
 #else
 	{IT_TRANSTEXT | IT_PAIR, "Renderer", "Software",            &cv_renderer,           21},
 #endif
@@ -1363,9 +1356,7 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Score/Time/Rings",          &cv_timetic,          71},
 	{IT_STRING | IT_CVAR, NULL, "Show Powerups",             &cv_powerupdisplay,   76},
 	{IT_STRING | IT_CVAR, NULL, "Local ping display",		&cv_showping,			81}, // shows ping next to framerate if we want to.
-#ifdef SEENAMES
 	{IT_STRING | IT_CVAR, NULL, "Show player names",         &cv_seenames,         86},
-#endif
 
 	{IT_HEADER, NULL, "Console", NULL, 95},
 	{IT_STRING | IT_CVAR, NULL, "Background color",          &cons_backcolor,      101},
@@ -1446,19 +1437,19 @@ static menuitem_t OP_ColorOptionsMenu[] =
 static menuitem_t OP_OpenGLOptionsMenu[] =
 {
 	{IT_HEADER, NULL, "3D Models", NULL, 0},
-	{IT_STRING|IT_CVAR,         NULL, "Models",              &cv_grmodels,             12},
-	{IT_STRING|IT_CVAR,         NULL, "Frame interpolation", &cv_grmodelinterpolation, 22},
-	{IT_STRING|IT_CVAR,         NULL, "Ambient lighting",    &cv_grmodellighting,      32},
+	{IT_STRING|IT_CVAR,         NULL, "Models",              &cv_glmodels,             12},
+	{IT_STRING|IT_CVAR,         NULL, "Frame interpolation", &cv_glmodelinterpolation, 22},
+	{IT_STRING|IT_CVAR,         NULL, "Ambient lighting",    &cv_glmodellighting,      32},
 
 	{IT_HEADER, NULL, "General", NULL, 51},
-	{IT_STRING|IT_CVAR,         NULL, "Shaders",             &cv_grshaders,            63},
-	{IT_STRING|IT_CVAR,         NULL, "Lack of perspective", &cv_grshearing,           73},
+	{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_grfiltermode,        124},
-	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",         &cv_granisotropicmode,   134},
+	{IT_STRING|IT_CVAR,         NULL, "Texture filter",      &cv_glfiltermode,        124},
+	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",         &cv_glanisotropicmode,   134},
 #ifdef ALAM_LIGHTING
 	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",         &OP_OpenGLLightingDef,   144},
 #endif
@@ -1470,10 +1461,10 @@ static menuitem_t OP_OpenGLOptionsMenu[] =
 #ifdef ALAM_LIGHTING
 static menuitem_t OP_OpenGLLightingMenu[] =
 {
-	{IT_STRING|IT_CVAR, NULL, "Coronas",          &cv_grcoronas,          0},
-	{IT_STRING|IT_CVAR, NULL, "Coronas size",     &cv_grcoronasize,      10},
-	{IT_STRING|IT_CVAR, NULL, "Dynamic lighting", &cv_grdynamiclighting, 20},
-	{IT_STRING|IT_CVAR, NULL, "Static lighting",  &cv_grstaticlighting,  30},
+	{IT_STRING|IT_CVAR, NULL, "Coronas",          &cv_glcoronas,          0},
+	{IT_STRING|IT_CVAR, NULL, "Coronas size",     &cv_glcoronasize,      10},
+	{IT_STRING|IT_CVAR, NULL, "Dynamic lighting", &cv_gldynamiclighting, 20},
+	{IT_STRING|IT_CVAR, NULL, "Static lighting",  &cv_glstaticlighting,  30},
 };
 #endif // ALAM_LIGHTING
 
@@ -1482,21 +1473,23 @@ static menuitem_t OP_OpenGLLightingMenu[] =
 static menuitem_t OP_SoundOptionsMenu[] =
 {
 	{IT_HEADER, NULL, "Game Audio", NULL, 0},
-	{IT_STRING | IT_CVAR,  NULL,  "Sound Effects", &cv_gamesounds, 12},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 22},
+	{IT_STRING | IT_CVAR,  NULL,  "Sound Effects", &cv_gamesounds, 6},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 11},
 
-	{IT_STRING | IT_CVAR,  NULL,  "Digital Music", &cv_gamedigimusic, 42},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  52},
+	{IT_STRING | IT_CVAR,  NULL,  "Digital Music", &cv_gamedigimusic, 21},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  26},
 
-	{IT_STRING | IT_CVAR,  NULL,  "MIDI Music", &cv_gamemidimusic, 72},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 82},
+	{IT_STRING | IT_CVAR,  NULL,  "MIDI Music", &cv_gamemidimusic, 36},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 41},
 
-	{IT_HEADER, NULL, "Miscellaneous", NULL, 102},
-	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 114},
-	{IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 124},
-	{IT_STRING | IT_CVAR, NULL, "Default 1-Up sound", &cv_1upsound, 134},
+	{IT_STRING | IT_CVAR,  NULL,  "Music Preference", &cv_musicpref, 51},
+
+	{IT_HEADER, NULL, "Miscellaneous", NULL, 61},
+	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 67},
+	{IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 72},
+	{IT_STRING | IT_CVAR, NULL, "Default 1-Up sound", &cv_1upsound, 77},
 
-	{IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 154},
+	{IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 87},
 };
 
 #ifdef HAVE_OPENMPT
@@ -1556,18 +1549,19 @@ static menuitem_t OP_ScreenshotOptionsMenu[] =
 	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bits,           57},
 
 	{IT_HEADER, NULL, "Movie Mode (F9)", NULL, 64},
-	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_movie_option,              70},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_movie_folder, 	  75},
-	{IT_STRING|IT_CVAR, NULL, "Capture Mode",      &cv_moviemode,                 90},
+	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_movie_option,               70},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_movie_folder, 	   75},
+	{IT_STRING|IT_CVAR, NULL, "Capture Mode",      &cv_moviemode,                  90},
 
-	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,              95},
-	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,             100},
+	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,              95},
+	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,              100},
 	{IT_STRING|IT_CVAR, NULL, "Local Color Table", &cv_gif_localcolortable,       105},
 
-	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,              95},
-	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,               100},
-	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,            105},
-	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa,         110},
+	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_apng_downscale,             95},
+	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,              100},
+	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,               105},
+	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,            110},
+	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa,         115},
 };
 
 enum
@@ -1580,7 +1574,7 @@ enum
 	op_screenshot_gif_start = 13,
 	op_screenshot_gif_end = 15,
 	op_screenshot_apng_start = 16,
-	op_screenshot_apng_end = 19,
+	op_screenshot_apng_end = 20,
 };
 
 static menuitem_t OP_EraseDataMenu[] =
@@ -1618,50 +1612,54 @@ static menuitem_t OP_ServerOptionsMenu[] =
 	{IT_STRING | IT_CVAR,    NULL, "Max Players",                      &cv_maxplayers,          21},
 	{IT_STRING | IT_CVAR,    NULL, "Allow Add-on Downloading",         &cv_downloading,         26},
 	{IT_STRING | IT_CVAR,    NULL, "Allow players to join",            &cv_allownewplayer,      31},
+	{IT_STRING | IT_CVAR,    NULL, "Minutes for reconnecting",         &cv_rejointimeout,       36},
 #endif
-	{IT_STRING | IT_CVAR,    NULL, "Map progression",                  &cv_advancemap,          36},
-	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",               &cv_inttime,             41},
+	{IT_STRING | IT_CVAR,    NULL, "Map progression",                  &cv_advancemap,          41},
+	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",               &cv_inttime,             46},
 
-	{IT_HEADER, NULL, "Characters", NULL, 50},
-	{IT_STRING | IT_CVAR,    NULL, "Force a character",                &cv_forceskin,           56},
-	{IT_STRING | IT_CVAR,    NULL, "Restrict character changes",       &cv_restrictskinchange,  61},
+	{IT_HEADER, NULL, "Characters", NULL, 55},
+	{IT_STRING | IT_CVAR,    NULL, "Force a character",                &cv_forceskin,           61},
+	{IT_STRING | IT_CVAR,    NULL, "Restrict character changes",       &cv_restrictskinchange,  66},
 
-	{IT_HEADER, NULL, "Items", NULL, 70},
-	{IT_STRING | IT_CVAR,    NULL, "Item respawn delay",               &cv_itemrespawntime,     76},
-	{IT_STRING | IT_SUBMENU, NULL, "Mystery Item Monitor Toggles...",  &OP_MonitorToggleDef,    81},
+	{IT_HEADER, NULL, "Items", NULL, 75},
+	{IT_STRING | IT_CVAR,    NULL, "Item respawn delay",               &cv_itemrespawntime,     81},
+	{IT_STRING | IT_SUBMENU, NULL, "Mystery Item Monitor Toggles...",  &OP_MonitorToggleDef,    86},
 
-	{IT_HEADER, NULL, "Cooperative", NULL, 90},
-	{IT_STRING | IT_CVAR,    NULL, "Players required for exit",        &cv_playersforexit,      96},
-	{IT_STRING | IT_CVAR,    NULL, "Starposts",                        &cv_coopstarposts,      101},
-	{IT_STRING | IT_CVAR,    NULL, "Life sharing",                     &cv_cooplives,          106},
-	{IT_STRING | IT_CVAR,    NULL, "Post-goal free roaming",           &cv_exitmove,           111},
+	{IT_HEADER, NULL, "Cooperative", NULL, 95},
+	{IT_STRING | IT_CVAR,    NULL, "Players required for exit",        &cv_playersforexit,     101},
+	{IT_STRING | IT_CVAR,    NULL, "Starposts",                        &cv_coopstarposts,      106},
+	{IT_STRING | IT_CVAR,    NULL, "Life sharing",                     &cv_cooplives,          111},
+	{IT_STRING | IT_CVAR,    NULL, "Post-goal free roaming",           &cv_exitmove,           116},
 
-	{IT_HEADER, NULL, "Race, Competition", NULL, 120},
-	{IT_STRING | IT_CVAR,    NULL, "Level completion countdown",       &cv_countdowntime,      126},
-	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_competitionboxes,   131},
+	{IT_HEADER, NULL, "Race, Competition", NULL, 125},
+	{IT_STRING | IT_CVAR,    NULL, "Level completion countdown",       &cv_countdowntime,      131},
+	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_competitionboxes,   136},
 
-	{IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 140},
-	{IT_STRING | IT_CVAR,    NULL, "Time Limit",                       &cv_timelimit,          146},
-	{IT_STRING | IT_CVAR,    NULL, "Score Limit",                      &cv_pointlimit,         151},
-	{IT_STRING | IT_CVAR,    NULL, "Overtime on Tie",                  &cv_overtime,           156},
-	{IT_STRING | IT_CVAR,    NULL, "Player respawn delay",             &cv_respawntime,        161},
+	{IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 145},
+	{IT_STRING | IT_CVAR,    NULL, "Time Limit",                       &cv_timelimit,          151},
+	{IT_STRING | IT_CVAR,    NULL, "Score Limit",                      &cv_pointlimit,         156},
+	{IT_STRING | IT_CVAR,    NULL, "Overtime on Tie",                  &cv_overtime,           161},
+	{IT_STRING | IT_CVAR,    NULL, "Player respawn delay",             &cv_respawntime,        166},
 
-	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_matchboxes,         171},
-	{IT_STRING | IT_CVAR,    NULL, "Weapon Rings",                     &cv_specialrings,       176},
-	{IT_STRING | IT_CVAR,    NULL, "Power Stones",                     &cv_powerstones,        181},
+	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_matchboxes,         176},
+	{IT_STRING | IT_CVAR,    NULL, "Weapon Rings",                     &cv_specialrings,       181},
+	{IT_STRING | IT_CVAR,    NULL, "Power Stones",                     &cv_powerstones,        186},
 
-	{IT_STRING | IT_CVAR,    NULL, "Flag respawn delay",               &cv_flagtime,           191},
-	{IT_STRING | IT_CVAR,    NULL, "Hiding time",                      &cv_hidetime,           196},
+	{IT_STRING | IT_CVAR,    NULL, "Flag respawn delay",               &cv_flagtime,           196},
+	{IT_STRING | IT_CVAR,    NULL, "Hiding time",                      &cv_hidetime,           201},
 
-	{IT_HEADER, NULL, "Teams", NULL, 205},
-	{IT_STRING | IT_CVAR,    NULL, "Autobalance sizes",                &cv_autobalance,        211},
-	{IT_STRING | IT_CVAR,    NULL, "Scramble on Map Change",           &cv_scrambleonchange,   216},
+	{IT_HEADER, NULL, "Teams", NULL, 210},
+	{IT_STRING | IT_CVAR,    NULL, "Autobalance sizes",                &cv_autobalance,        216},
+	{IT_STRING | IT_CVAR,    NULL, "Scramble on Map Change",           &cv_scrambleonchange,   221},
 
 #ifndef NONET
-	{IT_HEADER, NULL, "Advanced", NULL, 225},
-	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,       231},
-	{IT_STRING | IT_CVAR,    NULL, "Join delay",                       &cv_joindelay,          246},
-	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,    251},
+	{IT_HEADER, NULL, "Advanced", NULL, 230},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,       236},
+
+	{IT_STRING | IT_CVAR,    NULL, "Join delay",                       &cv_joindelay,          251},
+	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,    256},
+
+	{IT_STRING | IT_CVAR,    NULL, "Show IP Address of Joiners",       &cv_showjoinaddress,    261},
 #endif
 };
 
@@ -2147,15 +2145,20 @@ menu_t OP_PlaystyleDef = {
 static void M_VideoOptions(INT32 choice)
 {
 	(void)choice;
+
+	OP_VideoOptionsMenu[op_video_renderer].status = (IT_TRANSTEXT | IT_PAIR);
+	OP_VideoOptionsMenu[op_video_renderer].patch = "Renderer";
+	OP_VideoOptionsMenu[op_video_renderer].text = "Software";
+
 #ifdef HWRENDER
-	if (vid_opengl_state == -1)
+	if (vid.glstate != VID_GL_LIBRARY_ERROR)
 	{
-		OP_VideoOptionsMenu[op_video_renderer].status = (IT_TRANSTEXT | IT_PAIR);
-		OP_VideoOptionsMenu[op_video_renderer].patch = "Renderer";
-		OP_VideoOptionsMenu[op_video_renderer].text = "Software";
+		OP_VideoOptionsMenu[op_video_renderer].status = (IT_STRING | IT_CVAR);
+		OP_VideoOptionsMenu[op_video_renderer].patch = NULL;
+		OP_VideoOptionsMenu[op_video_renderer].text = "Renderer";
 	}
-
 #endif
+
 	M_SetupNextMenu(&OP_VideoOptionsDef);
 }
 
@@ -2195,7 +2198,7 @@ menu_t OP_ColorOptionsDef =
 	0,
 	NULL
 };
-menu_t OP_SoundOptionsDef = DEFAULTMENUSTYLE(
+menu_t OP_SoundOptionsDef = DEFAULTSCROLLMENUSTYLE(
 	MTREE2(MN_OP_MAIN, MN_OP_SOUND),
 	"M_SOUND", OP_SoundOptionsMenu, &OP_MainDef, 30, 30);
 menu_t OP_SoundAdvancedDef = DEFAULTMENUSTYLE(
@@ -2452,46 +2455,6 @@ static void Newgametype_OnChange(void)
 	}
 }
 
-#ifdef HWRENDER
-static void Newrenderer_AREYOUSURE(INT32 c)
-{
-	int n;
-	switch (c)
-	{
-		case 'y':
-		case KEY_ENTER:
-			n = cv_newrenderer.value;
-			newrenderer_set |= n;
-			CV_SetValue(&cv_renderer, n);
-			break;
-		default:
-			CV_StealthSetValue(&cv_newrenderer, cv_renderer.value);
-	}
-}
-
-static void Newrenderer_OnChange(void)
-{
-	/* Well this works for now because there's only two options. */
-	int n;
-	n = cv_newrenderer.value;
-	newrenderer_set |= cv_renderer.value;
-	if (( newrenderer_set & n ))
-		CV_SetValue(&cv_renderer, n);
-	else
-	{
-		M_StartMessage(
-				"The OpenGL renderer is incomplete.\n"
-				"Some visuals may fail to appear, or\n"
-				"appear incorrectly.\n"
-				"Do you still want to switch to it?\n"
-				"\n"
-				"(Press 'y' or 'n')",
-				Newrenderer_AREYOUSURE, MM_YESNO
-		);
-	}
-}
-#endif/*HWRENDER*/
-
 void Screenshot_option_Onchange(void)
 {
 	OP_ScreenshotOptionsMenu[op_screenshot_folder].status =
@@ -3078,7 +3041,6 @@ static void M_GoBack(INT32 choice)
 		//make sure the game doesn't still think we're in a netgame.
 		if (!Playing() && netgame && multiplayer)
 		{
-			MSCloseUDPSocket();		// Clean up so we can re-open the connection later.
 			netgame = multiplayer = false;
 		}
 
@@ -3837,6 +3799,30 @@ void M_SetupNextMenu(menu_t *menudef)
 {
 	INT16 i;
 
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+	if (currentMenu == &MP_RoomDef || currentMenu == &MP_ConnectDef)
+	{
+		I_lock_mutex(&ms_QueryId_mutex);
+		{
+			ms_QueryId++;
+		}
+		I_unlock_mutex(ms_QueryId_mutex);
+	}
+
+	if (currentMenu == &MP_ConnectDef)
+	{
+		I_lock_mutex(&ms_ServerList_mutex);
+		{
+			if (ms_ServerList)
+			{
+				free(ms_ServerList);
+				ms_ServerList = NULL;
+			}
+		}
+		I_unlock_mutex(ms_ServerList_mutex);
+	}
+#endif/*HAVE_THREADS*/
+
 	if (currentMenu->quitroutine)
 	{
 		// If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH
@@ -3900,6 +3886,19 @@ void M_Ticker(void)
 
 	if (currentMenu == &OP_ScreenshotOptionsDef)
 		M_SetupScreenshotMenu();
+
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+	I_lock_mutex(&ms_ServerList_mutex);
+	{
+		if (ms_ServerList)
+		{
+			CL_QueryServerList(ms_ServerList);
+			free(ms_ServerList);
+			ms_ServerList = NULL;
+		}
+	}
+	I_unlock_mutex(ms_ServerList_mutex);
+#endif
 }
 
 //
@@ -4032,7 +4031,7 @@ static void M_DrawThermo(INT32 x, INT32 y, consvar_t *cv)
 	cursorlump = W_GetNumForName("M_THERMO");
 
 	V_DrawScaledPatch(xx, y, 0, p = W_CachePatchNum(leftlump,PU_PATCH));
-	xx += SHORT(p->width) - SHORT(p->leftoffset);
+	xx += p->width - p->leftoffset;
 	for (i = 0; i < 16; i++)
 	{
 		V_DrawScaledPatch(xx, y, V_WRAPX, W_CachePatchNum(centerlump[i & 1], PU_PATCH));
@@ -4171,7 +4170,7 @@ static void M_DrawStaticBox(fixed_t x, fixed_t y, INT32 flags, fixed_t w, fixed_
 	fixed_t sw, pw;
 
 	patch = W_CachePatchName("LSSTATIC", PU_PATCH);
-	pw = SHORT(patch->width) - (sw = w*2); //FixedDiv(w, scale); -- for scale FRACUNIT/2
+	pw = patch->width - (sw = w*2); //FixedDiv(w, scale); -- for scale FRACUNIT/2
 
 	/*if (pw > 0) -- model code for modders providing weird LSSTATIC
 	{
@@ -4268,8 +4267,8 @@ static void M_DrawMenuTitle(void)
 
 		if (p->height > 24) // title is larger than normal
 		{
-			INT32 xtitle = (BASEVIDWIDTH - (SHORT(p->width)/2))/2;
-			INT32 ytitle = (30 - (SHORT(p->height)/2))/2;
+			INT32 xtitle = (BASEVIDWIDTH - (p->width/2))/2;
+			INT32 ytitle = (30 - (p->height/2))/2;
 
 			if (xtitle < 0)
 				xtitle = 0;
@@ -4280,8 +4279,8 @@ static void M_DrawMenuTitle(void)
 		}
 		else
 		{
-			INT32 xtitle = (BASEVIDWIDTH - SHORT(p->width))/2;
-			INT32 ytitle = (30 - SHORT(p->height))/2;
+			INT32 xtitle = (BASEVIDWIDTH - p->width)/2;
+			INT32 ytitle = (30 - p->height)/2;
 
 			if (xtitle < 0)
 				xtitle = 0;
@@ -4317,7 +4316,7 @@ static void M_DrawGenericMenu(void)
 					{
 						patch_t *p;
 						p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH);
-						V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p);
+						V_DrawScaledPatch((BASEVIDWIDTH - p->width)/2, y, 0, p);
 					}
 					else
 					{
@@ -4848,7 +4847,7 @@ static void M_DrawCenteredMenu(void)
 					{
 						patch_t *p;
 						p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH);
-						V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p);
+						V_DrawScaledPatch((BASEVIDWIDTH - p->width)/2, y, 0, p);
 					}
 					else
 					{
@@ -5591,9 +5590,6 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 	if (map <= 0)
 		return;
 
-	if (needpatchrecache)
-		M_CacheLevelPlatter();
-
 	//  A 564x100 image of the level as entry MAPxxW
 	if (!(levelselect.rows[row].mapavailable[col]))
 	{
@@ -5625,9 +5621,6 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 	if (map <= 0)
 		return;
 
-	if (needpatchrecache)
-		M_CacheLevelPlatter();
-
 	//  A 160x100 image of the level as entry MAPxxP
 	if (!(levelselect.rows[row].mapavailable[col]))
 	{
@@ -5702,7 +5695,7 @@ static void M_DrawRecordAttackForeground(void)
 	angle_t fa;
 
 	INT32 i;
-	INT32 height = (SHORT(fg->height)/2);
+	INT32 height = (fg->height / 2);
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
 
 	for (i = -12; i < (BASEVIDHEIGHT/height) + 12; i++)
@@ -5737,9 +5730,9 @@ static void M_DrawNightsAttackMountains(void)
 	static INT32 bgscrollx;
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
 	patch_t *background = W_CachePatchName(curbgname, PU_PATCH);
-	INT16 w = SHORT(background->width);
+	INT16 w = background->width;
 	INT32 x = FixedInt(-bgscrollx) % w;
-	INT32 y = BASEVIDHEIGHT - SHORT(background->height)*2;
+	INT32 y = BASEVIDHEIGHT - (background->height * 2);
 
 	if (vid.height != BASEVIDHEIGHT * dupz)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 158);
@@ -5764,18 +5757,18 @@ static void M_DrawNightsAttackBackground(void)
 	// top
 	patch_t *backtopfg = W_CachePatchName("NTSATKT1", PU_PATCH);
 	patch_t *fronttopfg = W_CachePatchName("NTSATKT2", PU_PATCH);
-	INT32 backtopwidth = SHORT(backtopfg->width);
-	//INT32 backtopheight = SHORT(backtopfg->height);
-	INT32 fronttopwidth = SHORT(fronttopfg->width);
-	//INT32 fronttopheight = SHORT(fronttopfg->height);
+	INT32 backtopwidth = backtopfg->width;
+	//INT32 backtopheight = backtopfg->height;
+	INT32 fronttopwidth = fronttopfg->width;
+	//INT32 fronttopheight = fronttopfg->height;
 
 	// bottom
 	patch_t *backbottomfg = W_CachePatchName("NTSATKB1", PU_PATCH);
 	patch_t *frontbottomfg = W_CachePatchName("NTSATKB2", PU_PATCH);
-	INT32 backbottomwidth = SHORT(backbottomfg->width);
-	INT32 backbottomheight = SHORT(backbottomfg->height);
-	INT32 frontbottomwidth = SHORT(frontbottomfg->width);
-	INT32 frontbottomheight = SHORT(frontbottomfg->height);
+	INT32 backbottomwidth = backbottomfg->width;
+	INT32 backbottomheight = backbottomfg->height;
+	INT32 frontbottomwidth = frontbottomfg->width;
+	INT32 frontbottomheight = frontbottomfg->height;
 
 	// background
 	M_DrawNightsAttackMountains();
@@ -5837,8 +5830,6 @@ static void M_DrawNightsAttackSuperSonic(void)
 	const UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE);
 	INT32 timer = (ntsatkdrawtimer/4) % 2;
 	angle_t fa = (FixedAngle(((ntsatkdrawtimer * 4) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK;
-	ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH);
-	ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH);
 	V_DrawFixedPatch(235<<FRACBITS, (120<<FRACBITS) - (8*FINESINE(fa)), FRACUNIT, 0, ntssupersonic[timer], colormap);
 }
 
@@ -6160,7 +6151,7 @@ static void M_DrawMessageMenu(void)
 		}
 
 		V_DrawString((BASEVIDWIDTH - V_StringWidth(string, 0))/2,y,V_ALLOWLOWERCASE,string);
-		y += 8; //SHORT(hu_font[0]->height);
+		y += 8; //hu_font[0]->height;
 	}
 }
 
@@ -6182,7 +6173,7 @@ static void M_StopMessage(INT32 choice)
 static void M_DrawImageDef(void)
 {
 	// Grr.  Need to autodetect for pic_ts.
-	pic_t *pictest = (pic_t *)W_CachePatchName(currentMenu->menuitems[itemOn].text,PU_CACHE);
+	pic_t *pictest = (pic_t *)W_CacheLumpName(currentMenu->menuitems[itemOn].text,PU_CACHE);
 	if (!pictest->zero)
 		V_DrawScaledPic(0,0,0,W_GetNumForName(currentMenu->menuitems[itemOn].text));
 	else
@@ -6450,10 +6441,6 @@ static void M_DrawAddons(void)
 		return;
 	}
 
-	// Lactozilla: Load addons menu patches.
-	if (needpatchrecache)
-		M_LoadAddonsPatches();
-
 	if (Playing())
 		V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems.");
 	else
@@ -6950,8 +6937,7 @@ static void M_SelectableClearMenus(INT32 choice)
 static void M_UltimateCheat(INT32 choice)
 {
 	(void)choice;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(true);
 	I_Quit();
 }
 
@@ -7608,9 +7594,6 @@ static void M_DrawSoundTest(void)
 	fixed_t hscale = FRACUNIT/2, vscale = FRACUNIT/2, bounce = 0;
 	UINT8 frame[4] = {0, 0, -1, SKINCOLOR_RUBY};
 
-	if (needpatchrecache)
-		M_CacheSoundTest();
-
 	// let's handle the ticker first. ideally we'd tick this somewhere else, BUT...
 	if (curplaying)
 	{
@@ -7978,7 +7961,7 @@ static void M_SecretsMenu(INT32 choice)
 
 		skyRoomMenuTranslations[i-1] = (UINT8)ul;
 		SR_MainMenu[i].text = unlockables[ul].name;
-		SR_MainMenu[i].alphaKey = (UINT8)unlockables[ul].height;
+		SR_MainMenu[i].alphaKey = (UINT16)unlockables[ul].height;
 
 		if (unlockables[ul].type == SECRET_HEADER)
 		{
@@ -8252,17 +8235,15 @@ static void M_CacheLoadGameData(void)
 
 static void M_DrawLoadGameData(void)
 {
-	INT32 i, savetodraw, x, y, hsep = 90;
+	INT32 i, prev_i = 1, savetodraw, x, y, hsep = 90;
 	skin_t *charskin = NULL;
 
 	if (vid.width != BASEVIDWIDTH*vid.dupx)
 		hsep = (hsep*vid.width)/(BASEVIDWIDTH*vid.dupx);
 
-	if (needpatchrecache)
-		M_CacheLoadGameData();
-
-	for (i = -2; i <= 2; i++)
+	for (i = 2; prev_i; i = -(i + ((UINT32)i >> 31))) // draws from outwards in; 2, -2, 1, -1, 0
 	{
+		prev_i = i;
 		savetodraw = (saveSlotSelected + i + numsaves)%numsaves;
 		x = (BASEVIDWIDTH/2 - 42 + loadgamescroll) + (i*hsep);
 		y = 33 + 9;
@@ -8441,7 +8422,7 @@ static void M_DrawLoadGameData(void)
 				sprdef = &charbotskin->sprites[SPR2_SIGN];
 				if (!sprdef->numframes)
 					goto skipbot;
-				colormap = R_GetTranslationColormap(savegameinfo[savetodraw].botskin, charbotskin->prefcolor, 0);
+				colormap = R_GetTranslationColormap(savegameinfo[savetodraw].botskin-1, charbotskin->prefcolor, GTC_CACHE);
 				sprframe = &sprdef->spriteframes[0];
 				patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 
@@ -8451,8 +8432,6 @@ static void M_DrawLoadGameData(void)
 					charbotskin->highresscale,
 					0, patch, colormap);
 
-				Z_Free(colormap);
-
 				tempx -= (20<<FRACBITS);
 				//flip = V_FLIP;
 			}
@@ -8461,7 +8440,7 @@ skipbot:
 			if (!charskin) // shut up compiler
 				goto skipsign;
 			sprdef = &charskin->sprites[SPR2_SIGN];
-			colormap = R_GetTranslationColormap(savegameinfo[savetodraw].skinnum, charskin->prefcolor, 0);
+			colormap = R_GetTranslationColormap(savegameinfo[savetodraw].skinnum, charskin->prefcolor, GTC_CACHE);
 			if (!sprdef->numframes)
 				goto skipsign;
 			sprframe = &sprdef->spriteframes[0];
@@ -8501,8 +8480,6 @@ skipsign:
 				charskin->highresscale/2,
 				0, patch, colormap);
 skiplife:
-			if (colormap)
-				Z_Free(colormap);
 
 			patch = W_CachePatchName("STLIVEX", PU_PATCH);
 
@@ -8964,7 +8941,6 @@ void M_ForceSaveSlotSelected(INT32 sslot)
 // CHARACTER SELECT
 // ================
 
-// lactozilla: sometimes the renderer changes and these patches don't exist anymore
 static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
 {
 	if (!(description[i].picname[0]))
@@ -8985,22 +8961,6 @@ static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
 		description[i].namepic = W_CachePatchName(description[i].nametag, PU_PATCH);
 }
 
-static void M_CacheCharacterSelect(void)
-{
-	INT32 i, skinnum;
-
-	for (i = 0; i < MAXSKINS; i++)
-	{
-		if (!description[i].used)
-			continue;
-
-		// Already set in M_SetupChoosePlayer
-		skinnum = description[i].skinnum[0];
-		if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
-			M_CacheCharacterSelectEntry(i, skinnum);
-	}
-}
-
 static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
 {
 	INT32 skinnum;
@@ -9200,17 +9160,13 @@ static void M_DrawSetupChoosePlayerMenu(void)
 
 	patch_t *charbg = W_CachePatchName("CHARBG", PU_PATCH);
 	patch_t *charfg = W_CachePatchName("CHARFG", PU_PATCH);
-	INT16 bgheight = SHORT(charbg->height);
-	INT16 fgheight = SHORT(charfg->height);
-	INT16 bgwidth = SHORT(charbg->width);
-	INT16 fgwidth = SHORT(charfg->width);
+	INT16 bgheight = charbg->height;
+	INT16 fgheight = charfg->height;
+	INT16 bgwidth = charbg->width;
+	INT16 fgwidth = charfg->width;
 	INT32 x, y;
 	INT32 w = (vid.width/vid.dupx);
 
-	// lactozilla: the renderer changed so recache patches
-	if (needpatchrecache)
-		M_CacheCharacterSelect();
-
 	if (abs(char_scroll) > FRACUNIT)
 		char_scroll -= (char_scroll>>2);
 	else // close enough.
@@ -9299,14 +9255,14 @@ static void M_DrawSetupChoosePlayerMenu(void)
 			curoutlinecolor = col = skincolors[charskin->prefcolor].invcolor;
 
 		txsh = oxsh;
-		ox = 8 + SHORT((description[char_on].charpic)->width)/2;
+		ox = 8 + ((description[char_on].charpic)->width)/2;
 		y = my + 144;
 
 		// cur
 		{
 			x = ox - txsh;
 			if (curpatch)
-				x -= (SHORT(curpatch->width)/2);
+				x -= curpatch->width / 2;
 
 			if (curtext[0] != '\0')
 			{
@@ -9339,7 +9295,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 
 				x = (ox - txsh) - w;
 				if (prevpatch)
-					x -= (SHORT(prevpatch->width)/2);
+					x -= prevpatch->width / 2;
 
 				if (prevtext[0] != '\0')
 				{
@@ -9369,7 +9325,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 
 				x = (ox - txsh) + w;
 				if (nextpatch)
-					x -= (SHORT(nextpatch->width)/2);
+					x -= nextpatch->width / 2;
 
 				if (nexttext[0] != '\0')
 				{
@@ -9391,7 +9347,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	{
 		patch_t *header = W_CachePatchName("M_PICKP", PU_PATCH);
 		INT32 xtitle = 146;
-		INT32 ytitle = (35 - SHORT(header->height))/2;
+		INT32 ytitle = (35 - header->height) / 2;
 		V_DrawFixedPatch(xtitle<<FRACBITS, ytitle<<FRACBITS, FRACUNIT/2, 0, header, NULL);
 	}
 #endif // CHOOSEPLAYER_DRAWHEADER
@@ -9857,8 +9813,8 @@ void M_DrawTimeAttackMenu(void)
 
 			empatch = W_CachePatchName(M_GetEmblemPatch(em, true), PU_PATCH);
 
-			empatx = SHORT(empatch->leftoffset)/2;
-			empaty = SHORT(empatch->topoffset)/2;
+			empatx = empatch->leftoffset / 2;
+			empaty = empatch->topoffset / 2;
 
 			if (em->collected)
 				V_DrawSmallMappedPatch(104+76+empatx, yHeight+lsheadingheight/2+empaty, 0, empatch,
@@ -10094,7 +10050,7 @@ void M_DrawNightsAttackMenu(void)
 		// Super Sonic
 		M_DrawNightsAttackSuperSonic();
 		//if (P_HasGrades(cv_nextmap.value, 0))
-		//	V_DrawScaledPatch(235 - (SHORT((ngradeletters[bestoverall])->width)*3)/2, 135, 0, ngradeletters[bestoverall]);
+		//	V_DrawScaledPatch(235 - (((ngradeletters[bestoverall])->width)*3)/2, 135, 0, ngradeletters[bestoverall]);
 
 		if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
 			{//make bigger again
@@ -10181,6 +10137,9 @@ static void M_NightsAttack(INT32 choice)
 	// This is really just to make sure Sonic is the played character, just in case
 	M_PatchSkinNameTable();
 
+	ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH);
+	ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH);
+
 	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
 	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
 	M_SetupNextMenu(&SP_NightsAttackDef);
@@ -10582,10 +10541,6 @@ void M_DrawMarathon(void)
 	angle_t fa;
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy), xspan = (vid.width/dupz), yspan = (vid.height/dupz), diffx = (xspan - BASEVIDWIDTH)/2, diffy = (yspan - BASEVIDHEIGHT)/2, maxy = BASEVIDHEIGHT + diffy;
 
-	// lactozilla: the renderer changed so recache patches
-	if (needpatchrecache)
-		M_CacheCharacterSelect();
-
 	curbgxspeed = 0;
 	curbgyspeed = 18;
 
@@ -10652,7 +10607,7 @@ void M_DrawMarathon(void)
 	{
 		patch_t *fg = W_CachePatchName("RECATKFG", PU_PATCH);
 		INT32 trans = V_60TRANS+((cnt&~3)<<(V_ALPHASHIFT-2));
-		INT32 height = (SHORT(fg->height)/2);
+		INT32 height = fg->height / 2;
 		char patchname[7] = "CEMGx0";
 
 		dupz = (w*7)/6; //(w*42*120)/(360*6); -- I don't know why this works but I'm not going to complain.
@@ -10917,22 +10872,65 @@ static INT32 menuRoomIndex = 0;
 
 static void M_DrawRoomMenu(void)
 {
+	static int frame = -12;
+	int dot_frame;
+	char text[4];
+
 	const char *rmotd;
+	const char *waiting_message;
+
+	int dots;
+
+	if (m_waiting_mode)
+	{
+		dot_frame = frame / 4;
+		dots = dot_frame + 3;
+
+		strcpy(text, "   ");
+
+		if (dots > 0)
+		{
+			if (dot_frame < 0)
+				dot_frame = 0;
+
+			strncpy(&text[dot_frame], "...", min(dots, 3 - dot_frame));
+		}
+
+		if (++frame == 12)
+			frame = -12;
+
+		currentMenu->menuitems[0].text = text;
+	}
 
 	// use generic drawer for cursor, items and title
 	M_DrawGenericMenu();
 
 	V_DrawString(currentMenu->x - 16, currentMenu->y, V_YELLOWMAP, M_GetText("Select a room"));
 
-	M_DrawTextBox(144, 24, 20, 20);
+	if (m_waiting_mode == M_NOT_WAITING)
+	{
+		M_DrawTextBox(144, 24, 20, 20);
+
+		if (itemOn == 0)
+			rmotd = M_GetText("Don't connect to the Master Server.");
+		else
+			rmotd = room_list[itemOn-1].motd;
 
-	if (itemOn == 0)
-		rmotd = M_GetText("Don't connect to the Master Server.");
-	else
-		rmotd = room_list[itemOn-1].motd;
+		rmotd = V_WordWrap(0, 20*8, 0, rmotd);
+		V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd);
+	}
 
-	rmotd = V_WordWrap(0, 20*8, 0, rmotd);
-	V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd);
+	if (m_waiting_mode)
+	{
+		// Display a little "please wait" message.
+		M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
+		if (m_waiting_mode == M_WAITING_VERSION)
+			waiting_message = "Checking for updates...";
+		else
+			waiting_message = "Fetching room info...";
+		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, waiting_message);
+		V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
+	}
 }
 
 static void M_DrawConnectMenu(void)
@@ -11000,6 +10998,14 @@ static void M_DrawConnectMenu(void)
 	localservercount = serverlistcount;
 
 	M_DrawGenericMenu();
+
+	if (m_waiting_mode)
+	{
+		// Display a little "please wait" message.
+		M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
+		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers...");
+		V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
+	}
 }
 
 static boolean M_CancelConnect(void)
@@ -11089,10 +11095,10 @@ void M_SortServerList(void)
 
 #ifndef NONET
 #ifdef UPDATE_ALERT
-static boolean M_CheckMODVersion(void)
+static boolean M_CheckMODVersion(int id)
 {
 	char updatestring[500];
-	const char *updatecheck = GetMODVersion();
+	const char *updatecheck = GetMODVersion(id);
 	if(updatecheck)
 	{
 		sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck);
@@ -11101,8 +11107,67 @@ static boolean M_CheckMODVersion(void)
 	} else
 		return true;
 }
+#endif/*UPDATE_ALERT*/
+
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+static void
+Check_new_version_thread (int *id)
+{
+	int hosting;
+	int okay;
+
+	okay = 0;
+
+#ifdef UPDATE_ALERT
+	if (M_CheckMODVersion(*id))
+#endif
+	{
+		I_lock_mutex(&ms_QueryId_mutex);
+		{
+			okay = ( *id == ms_QueryId );
+		}
+		I_unlock_mutex(ms_QueryId_mutex);
+
+		if (okay)
+		{
+			I_lock_mutex(&m_menu_mutex);
+			{
+				m_waiting_mode = M_WAITING_ROOMS;
+				hosting = ( currentMenu->prevMenu == &MP_ServerDef );
+			}
+			I_unlock_mutex(m_menu_mutex);
+
+			GetRoomsList(hosting, *id);
+		}
+	}
+#ifdef UPDATE_ALERT
+	else
+	{
+		I_lock_mutex(&ms_QueryId_mutex);
+		{
+			okay = ( *id == ms_QueryId );
+		}
+		I_unlock_mutex(ms_QueryId_mutex);
+	}
 #endif
 
+	if (okay)
+	{
+		I_lock_mutex(&m_menu_mutex);
+		{
+			if (m_waiting_mode)
+			{
+				m_waiting_mode = M_NOT_WAITING;
+				MP_RoomMenu[0].text = "<Offline Mode>";
+			}
+		}
+		I_unlock_mutex(m_menu_mutex);
+	}
+
+	free(id);
+}
+#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
+
 static void M_ConnectMenu(INT32 choice)
 {
 	(void)choice;
@@ -11130,18 +11195,21 @@ static void M_ConnectMenuModChecks(INT32 choice)
 
 	if (modifiedgame)
 	{
-		M_StartMessage(M_GetText("Add-ons are currently loaded.\n\nYou will only be able to join a server if\nit has the same ones loaded in the same order, which may be unlikely.\n\nIf you wish to play on other servers,\nrestart the game to clear existing add-ons.\n\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER);
+		M_StartMessage(M_GetText("You have add-ons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\nSRB2 will automatically add\neverything you need when you join.\n\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER);
 		return;
 	}
 
 	M_ConnectMenu(-1);
 }
 
-static UINT32 roomIds[NUM_LIST_ROOMS];
+UINT32 roomIds[NUM_LIST_ROOMS];
 
 static void M_RoomMenu(INT32 choice)
 {
 	INT32 i;
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+	int *id;
+#endif
 
 	(void)choice;
 
@@ -11154,34 +11222,54 @@ static void M_RoomMenu(INT32 choice)
 	if (rendermode == render_soft)
 		I_FinishUpdate(); // page flip or blit buffer
 
-	if (GetRoomsList(currentMenu == &MP_ServerDef) < 0)
-		return;
-
-#ifdef UPDATE_ALERT
-	if (!M_CheckMODVersion())
-		return;
-#endif
-
 	for (i = 1; i < NUM_LIST_ROOMS+1; ++i)
 		MP_RoomMenu[i].status = IT_DISABLED;
 	memset(roomIds, 0, sizeof(roomIds));
 
-	for (i = 0; room_list[i].header.buffer[0]; i++)
+	MP_RoomDef.prevMenu = currentMenu;
+	M_SetupNextMenu(&MP_RoomDef);
+
+#ifdef MASTERSERVER
+#ifdef HAVE_THREADS
+#ifdef UPDATE_ALERT
+	m_waiting_mode = M_WAITING_VERSION;
+#else/*UPDATE_ALERT*/
+	m_waiting_mode = M_WAITING_ROOMS;
+#endif/*UPDATE_ALERT*/
+
+	MP_RoomMenu[0].text = "";
+
+	id = malloc(sizeof *id);
+
+	I_lock_mutex(&ms_QueryId_mutex);
 	{
-		if(*room_list[i].name != '\0')
-		{
-			MP_RoomMenu[i+1].text = room_list[i].name;
-			roomIds[i] = room_list[i].id;
-			MP_RoomMenu[i+1].status = IT_STRING|IT_CALL;
-		}
+		*id = ms_QueryId;
 	}
+	I_unlock_mutex(ms_QueryId_mutex);
 
-	MP_RoomDef.prevMenu = currentMenu;
-	M_SetupNextMenu(&MP_RoomDef);
+	I_spawn_thread("check-new-version",
+			(I_thread_fn)Check_new_version_thread, id);
+#else/*HAVE_THREADS*/
+#ifdef UPDATE_ALERT
+	if (M_CheckMODVersion(0))
+#endif/*UPDATE_ALERT*/
+	{
+		GetRoomsList(currentMenu->prevMenu == &MP_ServerDef, 0);
+	}
+#endif/*HAVE_THREADS*/
+#endif/*MASTERSERVER*/
 }
 
 static void M_ChooseRoom(INT32 choice)
 {
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+	I_lock_mutex(&ms_QueryId_mutex);
+	{
+		ms_QueryId++;
+	}
+	I_unlock_mutex(ms_QueryId_mutex);
+#endif
+
 	if (choice == 0)
 		ms_RoomId = -1;
 	else
@@ -11289,7 +11377,7 @@ static void M_DrawServerMenu(void)
 		else
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
 
-		V_DrawSmallScaledPatch(319 - (currentMenu->x + (SHORT(PictureOfLevel->width)/2)), currentMenu->y + imgheight, 0, PictureOfLevel);
+		V_DrawSmallScaledPatch(319 - (currentMenu->x + (PictureOfLevel->width / 2)), currentMenu->y + imgheight, 0, PictureOfLevel);
 	}
 }
 
@@ -11662,7 +11750,7 @@ static void M_DrawSetupMultiPlayerMenu(void)
 		goto faildraw;
 
 	// ok, draw player sprite for sure now
-	colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor->color, 0);
+	colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor->color, GTC_CACHE);
 
 	if (multi_frame >= sprdef->numframes)
 		multi_frame = 0;
@@ -11680,7 +11768,6 @@ static void M_DrawSetupMultiPlayerMenu(void)
 		FixedDiv(skins[setupm_fakeskin].highresscale, skins[setupm_fakeskin].shieldscale),
 		flags, patch, colormap);
 
-	Z_Free(colormap);
 	goto colordraw;
 
 faildraw:
@@ -13279,8 +13366,7 @@ void M_QuitResponse(INT32 ch)
 
 	if (ch != 'y' && ch != KEY_ENTER)
 		return;
-	if (Playing())
-		LUAh_GameQuit();
+	LUAh_GameQuit(true);
 	if (!(netgame || cv_debug))
 	{
 		S_ResetCaptions();
diff --git a/src/m_menu.h b/src/m_menu.h
index 52bdb0dead9eafe30b12e1c8b127940f7cd1dc3b..0465128ef75063b57cd3849bb6faab251e45e139 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -18,8 +18,10 @@
 #include "doomstat.h" // for NUMGAMETYPES
 #include "d_event.h"
 #include "command.h"
-#include "r_skins.h" // for SKINNAMESIZE
 #include "f_finale.h" // for ttmode_enum
+#include "i_threads.h"
+#include "mserv.h"
+#include "r_things.h" // for SKINNAMESIZE
 
 // Compatibility with old-style named NiGHTS replay files.
 #define OLDNREPLAYNAME
@@ -226,6 +228,18 @@ typedef enum
 } menumessagetype_t;
 void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype);
 
+typedef enum
+{
+	M_NOT_WAITING,
+
+	M_WAITING_VERSION,
+	M_WAITING_ROOMS,
+	M_WAITING_SERVERS,
+}
+M_waiting_mode_t;
+
+extern M_waiting_mode_t m_waiting_mode;
+
 // Called by linux_x/i_video_xshm.c
 void M_QuitResponse(INT32 ch);
 
@@ -313,9 +327,12 @@ typedef struct menuitem_s
 	void *itemaction;
 
 	// hotkey in menu or y of the item
-	UINT8 alphaKey;
+	UINT16 alphaKey;
 } menuitem_t;
 
+extern menuitem_t MP_RoomMenu[];
+extern UINT32     roomIds[NUM_LIST_ROOMS];
+
 typedef struct menu_s
 {
 	UINT32         menuid;             // ID to encode menu type and hierarchy
@@ -335,6 +352,10 @@ void M_ClearMenus(boolean callexitmenufunc);
 // Maybe this goes here????? Who knows.
 boolean M_MouseNeeded(void);
 
+#ifdef HAVE_THREADS
+extern I_mutex m_menu_mutex;
+#endif
+
 extern menu_t *currentMenu;
 
 extern menu_t MainDef;
diff --git a/src/m_misc.c b/src/m_misc.c
index 216fde056c001690899debd7ad1ff2b2f982604b..ad2d133ab1dd8e6f743c4c2bab8adecd35d699e7 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -102,16 +102,16 @@ typedef off_t off64_t;
 #endif
 
 static CV_PossibleValue_t screenshot_cons_t[] = {{0, "Default"}, {1, "HOME"}, {2, "SRB2"}, {3, "CUSTOM"}, {0, NULL}};
-consvar_t cv_screenshot_option = {"screenshot_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Screenshot_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_screenshot_folder = {"screenshot_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_screenshot_option = CVAR_INIT ("screenshot_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Screenshot_option_Onchange);
+consvar_t cv_screenshot_folder = CVAR_INIT ("screenshot_folder", "", CV_SAVE, NULL, NULL);
 
-consvar_t cv_screenshot_colorprofile = {"screenshot_colorprofile", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_screenshot_colorprofile = CVAR_INIT ("screenshot_colorprofile", "Yes", CV_SAVE, CV_YesNo, NULL);
 
 static CV_PossibleValue_t moviemode_cons_t[] = {{MM_GIF, "GIF"}, {MM_APNG, "aPNG"}, {MM_SCREENSHOT, "Screenshots"}, {0, NULL}};
-consvar_t cv_moviemode = {"moviemode_mode", "GIF", CV_SAVE|CV_CALL, moviemode_cons_t, Moviemode_mode_Onchange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_moviemode = CVAR_INIT ("moviemode_mode", "GIF", CV_SAVE|CV_CALL, moviemode_cons_t, Moviemode_mode_Onchange);
 
-consvar_t cv_movie_option = {"movie_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Moviemode_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_movie_folder = {"movie_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_movie_option = CVAR_INIT ("movie_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Moviemode_option_Onchange);
+consvar_t cv_movie_folder = CVAR_INIT ("movie_folder", "", CV_SAVE, NULL, NULL);
 
 static CV_PossibleValue_t zlib_mem_level_t[] = {
 	{1, "(Min Memory) 1"},
@@ -153,16 +153,19 @@ static CV_PossibleValue_t apng_delay_t[] = {
 
 // zlib memory usage is as follows:
 // (1 << (zlib_window_bits+2)) +  (1 << (zlib_level+9))
-consvar_t cv_zlib_memory = {"png_memory_level", "7", CV_SAVE, zlib_mem_level_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_zlib_level = {"png_compress_level", "(Optimal) 6", CV_SAVE, zlib_level_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_zlib_strategy = {"png_strategy", "Normal", CV_SAVE, zlib_strategy_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_zlib_window_bits = {"png_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_zlib_memory = CVAR_INIT ("png_memory_level", "7", CV_SAVE, zlib_mem_level_t, NULL);
+consvar_t cv_zlib_level = CVAR_INIT ("png_compress_level", "(Optimal) 6", CV_SAVE, zlib_level_t, NULL);
+consvar_t cv_zlib_strategy = CVAR_INIT ("png_strategy", "Normal", CV_SAVE, zlib_strategy_t, NULL);
+consvar_t cv_zlib_window_bits = CVAR_INIT ("png_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL);
 
-consvar_t cv_zlib_memorya = {"apng_memory_level", "(Max Memory) 9", CV_SAVE, zlib_mem_level_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_zlib_levela = {"apng_compress_level", "4", CV_SAVE, zlib_level_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_zlib_strategya = {"apng_strategy", "RLE", CV_SAVE, zlib_strategy_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_zlib_window_bitsa = {"apng_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_apng_delay = {"apng_speed", "1x", CV_SAVE, apng_delay_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_zlib_memorya = CVAR_INIT ("apng_memory_level", "(Max Memory) 9", CV_SAVE, zlib_mem_level_t, NULL);
+consvar_t cv_zlib_levela = CVAR_INIT ("apng_compress_level", "4", CV_SAVE, zlib_level_t, NULL);
+consvar_t cv_zlib_strategya = CVAR_INIT ("apng_strategy", "RLE", CV_SAVE, zlib_strategy_t, NULL);
+consvar_t cv_zlib_window_bitsa = CVAR_INIT ("apng_window_size", "32k", CV_SAVE, zlib_window_bits_t, NULL);
+consvar_t cv_apng_delay = CVAR_INIT ("apng_speed", "1x", CV_SAVE, apng_delay_t, NULL);
+consvar_t cv_apng_downscale = CVAR_INIT ("apng_downscale", "On", CV_SAVE, CV_OnOff, NULL);
+
+static boolean apng_downscale = false; // So nobody can do something dumb like changing cvars mid output
 
 boolean takescreenshot = false; // Take a screenshot this tic
 
@@ -793,8 +796,6 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png
 	 "SDL";
 #elif defined (_WINDOWS)
 	 "DirectX";
-#elif defined (PC_DOS)
-	 "Allegro";
 #else
 	 "Unknown";
 #endif
@@ -983,25 +984,38 @@ static inline boolean M_PNGLib(void)
 
 static void M_PNGFrame(png_structp png_ptr, png_infop png_info_ptr, png_bytep png_buf)
 {
+	png_uint_16 downscale = apng_downscale ? vid.dupx : 1;
+
 	png_uint_32 pitch = png_get_rowbytes(png_ptr, png_info_ptr);
-	PNG_CONST png_uint_32 height = vid.height;
-	png_bytepp row_pointers = png_malloc(png_ptr, height* sizeof (png_bytep));
-	png_uint_32 y;
+	PNG_CONST png_uint_32 width = vid.width / downscale;
+	PNG_CONST png_uint_32 height = vid.height / downscale;
+	png_bytepp row_pointers = png_malloc(png_ptr, height * sizeof (png_bytep));
+	png_uint_32 x, y;
 	png_uint_16 framedelay = (png_uint_16)cv_apng_delay.value;
 
 	apng_frames++;
 
 	for (y = 0; y < height; y++)
 	{
-		row_pointers[y] = png_buf;
-		png_buf += pitch;
+		row_pointers[y] = malloc(pitch * sizeof(png_byte));
+		for (x = 0; x < width; x++)
+			row_pointers[y][x] = png_buf[x * downscale];
+		png_buf += pitch * (downscale * downscale);
 	}
+		//for (x = 0; x < width; x++)
+		//{
+		//	printf("%d", x);
+		//	row_pointers[y][x] = 0;
+		//}
+	/*	row_pointers[y] = calloc(1, sizeof(png_bytep));
+		png_buf += pitch * 2;
+	}*/
 
 #ifndef PNG_STATIC
 	if (aPNG_write_frame_head)
 #endif
 		aPNG_write_frame_head(apng_ptr, apng_info_ptr, row_pointers,
-			vid.width, /* width */
+			width,     /* width */
 			height,    /* height */
 			0,         /* x offset */
 			0,         /* y offset */
@@ -1032,6 +1046,12 @@ static void M_PNGfix_acTL(png_structp png_ptr, png_infop png_info_ptr,
 
 static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
 {
+	png_uint_16 downscale;
+
+	apng_downscale = (!!cv_apng_downscale.value);
+
+	downscale = apng_downscale ? vid.dupx : 1;
+
 	apng_FILE = fopen(filename,"wb+"); // + mode for reading
 	if (!apng_FILE)
 	{
@@ -1082,7 +1102,7 @@ static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
 	png_set_compression_strategy(apng_ptr, cv_zlib_strategya.value);
 	png_set_compression_window_bits(apng_ptr, cv_zlib_window_bitsa.value);
 
-	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width, vid.height, pal);
+	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width / downscale, vid.height / downscale, pal);
 
 	M_PNGText(apng_ptr, apng_info_ptr, true);
 
diff --git a/src/m_misc.h b/src/m_misc.h
index dbded37d0a47fdc81c0488098e5bf7ac8e677143..c5ef9f9f2548e339ef76e96ae02f835b82dec23c 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -33,7 +33,7 @@ extern consvar_t cv_screenshot_option, cv_screenshot_folder, cv_screenshot_color
 extern consvar_t cv_moviemode, cv_movie_folder, cv_movie_option;
 extern consvar_t cv_zlib_memory, cv_zlib_level, cv_zlib_strategy, cv_zlib_window_bits;
 extern consvar_t cv_zlib_memorya, cv_zlib_levela, cv_zlib_strategya, cv_zlib_window_bitsa;
-extern consvar_t cv_apng_delay;
+extern consvar_t cv_apng_delay, cv_apng_downscale;
 
 void M_StartMovie(void);
 void M_SaveFrame(void);
diff --git a/src/m_perfstats.c b/src/m_perfstats.c
new file mode 100644
index 0000000000000000000000000000000000000000..1596a87e5a841d019513c706b13608259334d7e7
--- /dev/null
+++ b/src/m_perfstats.c
@@ -0,0 +1,581 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file m_perfstats.c
+/// \brief Performance measurement tools.
+
+#include "m_perfstats.h"
+#include "v_video.h"
+#include "i_video.h"
+#include "d_netcmd.h"
+#include "r_main.h"
+#include "i_system.h"
+#include "z_zone.h"
+#include "p_local.h"
+
+#ifdef HWRENDER
+#include "hardware/hw_main.h"
+#endif
+
+struct perfstatcol;
+struct perfstatrow;
+
+typedef struct perfstatcol perfstatcol_t;
+typedef struct perfstatrow perfstatrow_t;
+
+struct perfstatcol {
+	INT32 lores_x;
+	INT32 hires_x;
+	INT32 color;
+	perfstatrow_t * rows;
+};
+
+struct perfstatrow {
+	const char * lores_label;
+	const char * hires_label;
+	void       * value;
+};
+
+static precise_t ps_frametime = 0;
+
+precise_t ps_tictime = 0;
+
+precise_t ps_playerthink_time = 0;
+precise_t ps_thinkertime = 0;
+
+precise_t ps_thlist_times[NUM_THINKERLISTS];
+
+int ps_checkposition_calls = 0;
+
+precise_t ps_lua_thinkframe_time = 0;
+int ps_lua_mobjhooks = 0;
+
+// dynamically allocated resizeable array for thinkframe hook stats
+ps_hookinfo_t *thinkframe_hooks = NULL;
+int thinkframe_hooks_length = 0;
+int thinkframe_hooks_capacity = 16;
+
+static INT32 draw_row;
+
+void PS_SetThinkFrameHookInfo(int index, UINT32 time_taken, char* short_src)
+{
+	if (!thinkframe_hooks)
+	{
+		// array needs to be initialized
+		thinkframe_hooks = Z_Malloc(sizeof(ps_hookinfo_t) * thinkframe_hooks_capacity, PU_STATIC, NULL);
+	}
+	if (index >= thinkframe_hooks_capacity)
+	{
+		// array needs more space, realloc with double size
+		thinkframe_hooks_capacity *= 2;
+		thinkframe_hooks = Z_Realloc(thinkframe_hooks,
+			sizeof(ps_hookinfo_t) * thinkframe_hooks_capacity, PU_STATIC, NULL);
+	}
+	thinkframe_hooks[index].time_taken = time_taken;
+	memcpy(thinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char));
+	// since the values are set sequentially from begin to end, the last call should leave
+	// the correct value to this variable
+	thinkframe_hooks_length = index + 1;
+}
+
+static void PS_SetFrameTime(void)
+{
+	precise_t currenttime = I_GetPreciseTime();
+	ps_frametime = currenttime - ps_prevframetime;
+	ps_prevframetime = currenttime;
+}
+
+static boolean M_HighResolution(void)
+{
+	return (vid.width >= 640 && vid.height >= 400);
+}
+
+enum {
+	PERF_TIME,
+	PERF_COUNT,
+};
+
+static void M_DrawPerfString(perfstatcol_t *col, int type)
+{
+	const boolean hires = M_HighResolution();
+
+	INT32 draw_flags = V_MONOSPACE | col->color;
+
+	perfstatrow_t * row;
+
+	int value;
+
+	if (hires)
+		draw_flags |= V_ALLOWLOWERCASE;
+
+	for (row = col->rows; row->lores_label; ++row)
+	{
+		if (type == PERF_TIME)
+			value = I_PreciseToMicros(*(precise_t *)row->value);
+		else
+			value = *(int *)row->value;
+
+		if (hires)
+		{
+			V_DrawSmallString(col->hires_x, draw_row, draw_flags,
+					va("%s %d", row->hires_label, value));
+
+			draw_row += 5;
+		}
+		else
+		{
+			V_DrawThinString(col->lores_x, draw_row, draw_flags,
+					va("%s %d", row->lores_label, value));
+
+			draw_row += 8;
+		}
+	}
+}
+
+static void M_DrawPerfTiming(perfstatcol_t *col)
+{
+	M_DrawPerfString(col, PERF_TIME);
+}
+
+static void M_DrawPerfCount(perfstatcol_t *col)
+{
+	M_DrawPerfString(col, PERF_COUNT);
+}
+
+static void M_DrawRenderStats(void)
+{
+	const boolean hires = M_HighResolution();
+
+	const int half_row = hires ? 5 : 4;
+
+	precise_t extrarendertime;
+
+	perfstatrow_t frametime_row[] = {
+		{"frmtime", "Frame time:    ", &ps_frametime},
+		{0}
+	};
+
+	perfstatrow_t rendercalltime_row[] = {
+		{"drwtime", "3d rendering:  ", &ps_rendercalltime},
+		{0}
+	};
+
+	perfstatrow_t opengltime_row[] = {
+		{"skybox ", "Skybox render: ", &ps_hw_skyboxtime},
+		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
+		{"nodesrt", "Drwnode sort:  ", &ps_hw_nodesorttime},
+		{"nodedrw", "Drwnode render:", &ps_hw_nodedrawtime},
+		{"sprsort", "Sprite sort:   ", &ps_hw_spritesorttime},
+		{"sprdraw", "Sprite render: ", &ps_hw_spritedrawtime},
+		{"other  ", "Other:         ", &extrarendertime},
+		{0}
+	};
+
+	perfstatrow_t softwaretime_row[] = {
+		{"bsptime", "RenderBSPNode: ", &ps_bsptime},
+		{"sprclip", "R_ClipSprites: ", &ps_sw_spritecliptime},
+		{"portals", "Portals+Skybox:", &ps_sw_portaltime},
+		{"planes ", "R_DrawPlanes:  ", &ps_sw_planetime},
+		{"masked ", "R_DrawMasked:  ", &ps_sw_maskedtime},
+		{"other  ", "Other:         ", &extrarendertime},
+		{0}
+	};
+
+	perfstatrow_t uiswaptime_row[] = {
+		{"ui     ", "UI render:     ", &ps_uitime},
+		{"finupdt", "I_FinishUpdate:", &ps_swaptime},
+		{0}
+	};
+
+	perfstatrow_t tictime_row[] = {
+		{"logic  ", "Game logic:    ", &ps_tictime},
+		{0}
+	};
+
+	perfstatrow_t rendercalls_row[] = {
+		{"bspcall", "BSP calls:   ", &ps_numbspcalls},
+		{"sprites", "Sprites:     ", &ps_numsprites},
+		{"drwnode", "Drawnodes:   ", &ps_numdrawnodes},
+		{"plyobjs", "Polyobjects: ", &ps_numpolyobjects},
+		{0}
+	};
+
+	perfstatrow_t batchtime_row[] = {
+		{"batsort", "Batch sort:  ", &ps_hw_batchsorttime},
+		{"batdraw", "Batch render:", &ps_hw_batchdrawtime},
+		{0}
+	};
+
+	perfstatrow_t batchcount_row[] = {
+		{"polygon", "Polygons:  ", &ps_hw_numpolys},
+		{"vertex ", "Vertices:  ", &ps_hw_numverts},
+		{0}
+	};
+
+	perfstatrow_t batchcalls_row[] = {
+		{"drwcall", "Draw calls:", &ps_hw_numcalls},
+		{"shaders", "Shaders:   ", &ps_hw_numshaders},
+		{"texture", "Textures:  ", &ps_hw_numtextures},
+		{"polyflg", "Polyflags: ", &ps_hw_numpolyflags},
+		{"colors ", "Colors:    ", &ps_hw_numcolors},
+		{0}
+	};
+
+	perfstatcol_t      frametime_col =  {20,  20, V_YELLOWMAP,      frametime_row};
+	perfstatcol_t rendercalltime_col =  {20,  20, V_YELLOWMAP, rendercalltime_row};
+
+	perfstatcol_t     opengltime_col =  {24,  24, V_YELLOWMAP,     opengltime_row};
+	perfstatcol_t   softwaretime_col =  {24,  24, V_YELLOWMAP,   softwaretime_row};
+
+	perfstatcol_t     uiswaptime_col =  {20,  20, V_YELLOWMAP,     uiswaptime_row};
+	perfstatcol_t        tictime_col =  {20,  20, V_GRAYMAP,          tictime_row};
+
+	perfstatcol_t    rendercalls_col =  {90, 115, V_BLUEMAP,      rendercalls_row};
+
+	perfstatcol_t      batchtime_col =  {90, 115, V_REDMAP,         batchtime_row};
+
+	perfstatcol_t     batchcount_col = {155, 200, V_PURPLEMAP,     batchcount_row};
+	perfstatcol_t     batchcalls_col = {220, 200, V_PURPLEMAP,     batchcalls_row};
+
+
+	boolean rendering = (
+			gamestate == GS_LEVEL ||
+			(gamestate == GS_TITLESCREEN && titlemapinaction)
+	);
+
+	draw_row = 10;
+	M_DrawPerfTiming(&frametime_col);
+
+	if (rendering)
+	{
+		M_DrawPerfTiming(&rendercalltime_col);
+
+		// Remember to update this calculation when adding more 3d rendering stats!
+		extrarendertime = ps_rendercalltime - ps_bsptime;
+
+#ifdef HWRENDER
+		if (rendermode == render_opengl)
+		{
+			extrarendertime -=
+				ps_hw_skyboxtime +
+				ps_hw_nodesorttime +
+				ps_hw_nodedrawtime +
+				ps_hw_spritesorttime +
+				ps_hw_spritedrawtime;
+
+			if (cv_glbatching.value)
+			{
+				extrarendertime -=
+					ps_hw_batchsorttime +
+					ps_hw_batchdrawtime;
+			}
+
+			M_DrawPerfTiming(&opengltime_col);
+		}
+		else
+#endif
+		{
+			extrarendertime -=
+				ps_sw_spritecliptime +
+				ps_sw_portaltime +
+				ps_sw_planetime +
+				ps_sw_maskedtime;
+
+			M_DrawPerfTiming(&softwaretime_col);
+		}
+	}
+
+	M_DrawPerfTiming(&uiswaptime_col);
+
+	draw_row += half_row;
+	M_DrawPerfTiming(&tictime_col);
+
+	if (rendering)
+	{
+		draw_row = 10;
+		M_DrawPerfCount(&rendercalls_col);
+
+#ifdef HWRENDER
+		if (rendermode == render_opengl && cv_glbatching.value)
+		{
+			draw_row += half_row;
+			M_DrawPerfTiming(&batchtime_col);
+
+			draw_row = 10;
+			M_DrawPerfCount(&batchcount_col);
+
+			if (hires)
+				draw_row += half_row;
+			else
+				draw_row  = 10;
+
+			M_DrawPerfCount(&batchcalls_col);
+		}
+#endif
+	}
+}
+
+static void M_DrawTickStats(void)
+{
+	int i = 0;
+	thinker_t *thinker;
+	int thinkercount = 0;
+	int polythcount = 0;
+	int mainthcount = 0;
+	int mobjcount = 0;
+	int nothinkcount = 0;
+	int scenerycount = 0;
+	int regularcount = 0;
+	int dynslopethcount = 0;
+	int precipcount = 0;
+	int removecount = 0;
+
+	precise_t extratime =
+		ps_tictime -
+		ps_playerthink_time -
+		ps_thinkertime -
+		ps_lua_thinkframe_time;
+
+	perfstatrow_t tictime_row[] = {
+		{"logic  ", "Game logic:     ", &ps_tictime},
+		{0}
+	};
+
+	perfstatrow_t thinker_time_row[] = {
+		{"plrthnk", "P_PlayerThink:  ", &ps_playerthink_time},
+		{"thnkers", "P_RunThinkers:  ", &ps_thinkertime},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinker_time_row[] = {
+		{"plyobjs", "Polyobjects:    ", &ps_thlist_times[THINK_POLYOBJ]},
+		{"main   ", "Main:           ", &ps_thlist_times[THINK_MAIN]},
+		{"mobjs  ", "Mobjs:          ", &ps_thlist_times[THINK_MOBJ]},
+		{"dynslop", "Dynamic slopes: ", &ps_thlist_times[THINK_DYNSLOPE]},
+		{"precip ", "Precipitation:  ", &ps_thlist_times[THINK_PRECIP]},
+		{0}
+	};
+
+	perfstatrow_t extra_thinker_time_row[] = {
+		{"lthinkf", "LUAh_ThinkFrame:", &ps_lua_thinkframe_time},
+		{"other  ", "Other:          ", &extratime},
+		{0}
+	};
+
+	perfstatrow_t thinkercount_row[] = {
+		{"thnkers", "Thinkers:       ", &thinkercount},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinkercount_row[] = {
+		{"plyobjs", "Polyobjects:    ", &polythcount},
+		{"main   ", "Main:           ", &mainthcount},
+		{"mobjs  ", "Mobjs:          ", &mobjcount},
+		{0}
+	};
+
+	perfstatrow_t mobjthinkercount_row[] = {
+		{"regular", "Regular:        ", &regularcount},
+		{"scenery", "Scenery:        ", &scenerycount},
+		{0}
+	};
+
+	perfstatrow_t nothinkcount_row[] = {
+		{"nothink", "Nothink:        ", &nothinkcount},
+		{0}
+	};
+
+	perfstatrow_t detailed_thinkercount_row2[] = {
+		{"dynslop", "Dynamic slopes: ", &dynslopethcount},
+		{"precip ", "Precipitation:  ", &precipcount},
+		{"remove ", "Pending removal:", &removecount},
+		{0}
+	};
+
+	perfstatrow_t misc_calls_row[] = {
+		{"lmhook", "Lua mobj hooks: ", &ps_lua_mobjhooks},
+		{"chkpos", "P_CheckPosition:", &ps_checkposition_calls},
+		{0}
+	};
+
+	perfstatcol_t               tictime_col  =  {20,  20, V_YELLOWMAP,               tictime_row};
+	perfstatcol_t          thinker_time_col  =  {24,  24, V_YELLOWMAP,          thinker_time_row};
+	perfstatcol_t detailed_thinker_time_col  =  {28,  28, V_YELLOWMAP, detailed_thinker_time_row};
+	perfstatcol_t    extra_thinker_time_col  =  {24,  24, V_YELLOWMAP,    extra_thinker_time_row};
+
+	perfstatcol_t          thinkercount_col  =  {90, 115, V_BLUEMAP,            thinkercount_row};
+	perfstatcol_t detailed_thinkercount_col  =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row};
+	perfstatcol_t      mobjthinkercount_col  =  {98, 123, V_BLUEMAP,        mobjthinkercount_row};
+	perfstatcol_t          nothinkcount_col  =  {98, 123, V_BLUEMAP,            nothinkcount_row};
+	perfstatcol_t detailed_thinkercount_col2 =  {94, 119, V_BLUEMAP,   detailed_thinkercount_row2};
+	perfstatcol_t            misc_calls_col  = {170, 216, V_PURPLEMAP,            misc_calls_row};
+
+	for (i = 0; i < NUM_THINKERLISTS; i++)
+	{
+		for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
+		{
+			thinkercount++;
+			if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				removecount++;
+			else if (i == THINK_POLYOBJ)
+				polythcount++;
+			else if (i == THINK_MAIN)
+				mainthcount++;
+			else if (i == THINK_MOBJ)
+			{
+				if (thinker->function.acp1 == (actionf_p1)P_MobjThinker)
+				{
+					mobj_t *mobj = (mobj_t*)thinker;
+					mobjcount++;
+					if (mobj->flags & MF_NOTHINK)
+						nothinkcount++;
+					else if (mobj->flags & MF_SCENERY)
+						scenerycount++;
+					else
+						regularcount++;
+				}
+			}
+			else if (i == THINK_DYNSLOPE)
+				dynslopethcount++;
+			else if (i == THINK_PRECIP)
+				precipcount++;
+		}
+	}
+
+	draw_row = 10;
+	M_DrawPerfTiming(&tictime_col);
+	M_DrawPerfTiming(&thinker_time_col);
+	M_DrawPerfTiming(&detailed_thinker_time_col);
+	M_DrawPerfTiming(&extra_thinker_time_col);
+
+	draw_row = 10;
+	M_DrawPerfCount(&thinkercount_col);
+	M_DrawPerfCount(&detailed_thinkercount_col);
+	M_DrawPerfCount(&mobjthinkercount_col);
+
+	if (nothinkcount)
+		M_DrawPerfCount(&nothinkcount_col);
+
+	M_DrawPerfCount(&detailed_thinkercount_col2);
+
+	if (M_HighResolution())
+	{
+		V_DrawSmallString(212, 10, V_MONOSPACE | V_ALLOWLOWERCASE | V_PURPLEMAP, "Calls:");
+
+		draw_row = 15;
+	}
+	else
+	{
+		draw_row = 10;
+	}
+
+	M_DrawPerfCount(&misc_calls_col);
+}
+
+void M_DrawPerfStats(void)
+{
+	char s[100];
+
+	PS_SetFrameTime();
+
+	if (cv_perfstats.value == 1) // rendering
+	{
+		M_DrawRenderStats();
+	}
+	else if (cv_perfstats.value == 2) // logic
+	{
+		M_DrawTickStats();
+	}
+	else if (cv_perfstats.value == 3) // lua thinkframe
+	{
+		if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
+			return;
+		if (vid.width < 640 || vid.height < 400) // low resolution
+		{
+			// it's not gonna fit very well..
+			V_DrawThinString(30, 30, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "Not available for resolutions below 640x400");
+		}
+		else // high resolution
+		{
+			int i;
+			// text writing position
+			int x = 2;
+			int y = 4;
+			UINT32 text_color;
+			char tempbuffer[LUA_IDSIZE];
+			char last_mod_name[LUA_IDSIZE];
+			last_mod_name[0] = '\0';
+			for (i = 0; i < thinkframe_hooks_length; i++)
+			{
+				char* str = thinkframe_hooks[i].short_src;
+				char* tempstr = tempbuffer;
+				int len = (int)strlen(str);
+				char* str_ptr;
+				if (strcmp(".lua", str + len - 4) == 0)
+				{
+					str[len-4] = '\0'; // remove .lua at end
+					len -= 4;
+				}
+				// we locate the wad/pk3 name in the string and compare it to
+				// what we found on the previous iteration.
+				// if the name has changed, print it out on the screen
+				strcpy(tempstr, str);
+				str_ptr = strrchr(tempstr, '|');
+				if (str_ptr)
+				{
+					*str_ptr = '\0';
+					str = str_ptr + 1; // this is the name of the hook without the mod file name
+					str_ptr = strrchr(tempstr, PATHSEP[0]);
+					if (str_ptr)
+						tempstr = str_ptr + 1;
+					// tempstr should now point to the mod name, (wad/pk3) possibly truncated
+					if (strcmp(tempstr, last_mod_name) != 0)
+					{
+						strcpy(last_mod_name, tempstr);
+						len = (int)strlen(tempstr);
+						if (len > 25)
+							tempstr += len - 25;
+						snprintf(s, sizeof s - 1, "%s", tempstr);
+						V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | V_GRAYMAP, s);
+						y += 4; // repeated code!
+						if (y > 192)
+						{
+							y = 4;
+							x += 106;
+							if (x > 214)
+								break;
+						}
+					}
+					text_color = V_YELLOWMAP;
+				}
+				else
+				{
+					// probably a standalone lua file
+					// cut off the folder if it's there
+					str_ptr = strrchr(tempstr, PATHSEP[0]);
+					if (str_ptr)
+						str = str_ptr + 1;
+					text_color = 0; // white
+				}
+				len = (int)strlen(str);
+				if (len > 20)
+					str += len - 20;
+				snprintf(s, sizeof s - 1, "%20s: %u", str, thinkframe_hooks[i].time_taken);
+				V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | text_color, s);
+				y += 4; // repeated code!
+				if (y > 192)
+				{
+					y = 4;
+					x += 106;
+					if (x > 214)
+						break;
+				}
+			}
+		}
+	}
+}
diff --git a/src/m_perfstats.h b/src/m_perfstats.h
new file mode 100644
index 0000000000000000000000000000000000000000..132bea38c696acaf9a7093cc81f795918452356d
--- /dev/null
+++ b/src/m_perfstats.h
@@ -0,0 +1,41 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file m_perfstats.h
+/// \brief Performance measurement tools.
+
+#ifndef __M_PERFSTATS_H__
+#define __M_PERFSTATS_H__
+
+#include "doomdef.h"
+#include "lua_script.h"
+#include "p_local.h"
+
+extern precise_t ps_tictime;
+
+extern precise_t ps_playerthink_time;
+extern precise_t ps_thinkertime;
+
+extern precise_t ps_thlist_times[];
+
+extern int       ps_checkposition_calls;
+
+extern precise_t ps_lua_thinkframe_time;
+extern int       ps_lua_mobjhooks;
+
+typedef struct
+{
+	UINT32 time_taken;
+	char short_src[LUA_IDSIZE];
+} ps_hookinfo_t;
+
+void PS_SetThinkFrameHookInfo(int index, UINT32 time_taken, char* short_src);
+
+void M_DrawPerfStats(void);
+
+#endif
diff --git a/src/mserv.c b/src/mserv.c
index af56907885cc7575069cd4c649ec456ff690b04e..dfb4174156978b35f6d29bebee1738eac0f2d446 100644
--- a/src/mserv.c
+++ b/src/mserv.c
@@ -2,212 +2,81 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by James R.
 //
 // 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  mserv.c
-/// \brief Commands used for communicate with the master server
-
-#ifdef __GNUC__
-#include <unistd.h>
-#include <stdlib.h>
-#include <errno.h>
-#endif
+/// \brief Commands used to communicate with the master server
 
+#if !defined (UNDER_CE)
 #include <time.h>
-
-#if (defined (NOMSERV)) && !defined (NONET)
-#define NONET
-#endif
-
-#ifndef NONET
-
-#ifndef NO_IPV6
-#define HAVE_IPV6
-#endif
-
-#ifdef _WIN32
-#define RPC_NO_WINDOWS_H
-#ifdef HAVE_IPV6
-#include <ws2tcpip.h>
-#else
-#include <winsock.h>     // socket(),...
-#endif //!HAVE_IPV6
-#else
-#include <arpa/inet.h>
-#ifdef __APPLE_CC__
-#ifndef _BSD_SOCKLEN_T_
-#define _BSD_SOCKLEN_T_
-#endif
 #endif
-#include <sys/socket.h> // socket(),...
-#include <netinet/in.h> // sockaddr_in
-#include <netdb.h> // getaddrinfo(),...
-#include <sys/ioctl.h>
-
-#include <sys/time.h> // timeval,... (TIMEOUT)
-#include <errno.h>
-#endif // _WIN32
-#endif // !NONET
 
 #include "doomstat.h"
 #include "doomdef.h"
 #include "command.h"
-#include "i_net.h"
-#include "console.h"
+#include "i_threads.h"
 #include "mserv.h"
-#include "d_net.h"
-#include "i_tcp.h"
-#include "i_system.h"
-#include "byteptr.h"
 #include "m_menu.h"
-#include "m_argv.h" // Alam is going to kill me <3
-#include "m_misc.h" //  GetRevisionString()
-
-#include "i_addrinfo.h"
-
-// ================================ DEFINITIONS ===============================
-
-#define PACKET_SIZE 1024
-
-
-#define  MS_NO_ERROR            	   0
-#define  MS_SOCKET_ERROR        	-201
-#define  MS_CONNECT_ERROR       	-203
-#define  MS_WRITE_ERROR         	-210
-#define  MS_READ_ERROR          	-211
-#define  MS_CLOSE_ERROR         	-212
-#define  MS_GETHOSTBYNAME_ERROR 	-220
-#define  MS_GETHOSTNAME_ERROR   	-221
-#define  MS_TIMEOUT_ERROR       	-231
-
-// see master server code for the values
-#define ADD_SERVER_MSG          	101
-#define REMOVE_SERVER_MSG       	103
-#define ADD_SERVERv2_MSG        	104
-#define GET_SERVER_MSG          	200
-#define GET_SHORT_SERVER_MSG    	205
-#define ASK_SERVER_MSG          	206
-#define ANSWER_ASK_SERVER_MSG   	207
-#define ASK_SERVER_MSG          	206
-#define ANSWER_ASK_SERVER_MSG   	207
-#define GET_MOTD_MSG            	208
-#define SEND_MOTD_MSG           	209
-#define GET_ROOMS_MSG           	210
-#define SEND_ROOMS_MSG          	211
-#define GET_ROOMS_HOST_MSG      	212
-#define GET_VERSION_MSG         	213
-#define SEND_VERSION_MSG        	214
-#define GET_BANNED_MSG          	215 // Someone's been baaaaaad!
-#define PING_SERVER_MSG         	216
-
-#define HEADER_SIZE (sizeof (INT32)*4)
-
-#define HEADER_MSG_POS    0
-#define IP_MSG_POS       16
-#define PORT_MSG_POS     32
-#define HOSTNAME_MSG_POS 40
-
-
-#if defined(_MSC_VER)
-#pragma pack(1)
-#endif
+#include "z_zone.h"
 
-/** A message to be exchanged with the master server.
-  */
-typedef struct
-{
-	INT32 id;                  ///< Unused?
-	INT32 type;                ///< Type of message.
-	INT32 room;                ///< Because everyone needs a roomie.
-	UINT32 length;             ///< Length of the message.
-	char buffer[PACKET_SIZE]; ///< Actual contents of the message.
-} ATTRPACK msg_t;
-
-#if defined(_MSC_VER)
-#pragma pack()
-#endif
+#ifdef MASTERSERVER
 
-typedef struct Copy_CVarMS_t
-{
-	char ip[64];
-	char port[8];
-	char name[64];
-} Copy_CVarMS_s;
-static Copy_CVarMS_s registered_server;
-static time_t MSLastPing;
-
-#if defined(_MSC_VER)
-#pragma pack(1)
-#endif
-typedef struct
-{
-	char ip[16];         // Big enough to hold a full address.
-	UINT16 port;
-	UINT8 padding1[2];
-	tic_t time;
-} ATTRPACK ms_holepunch_packet_t;
-#if defined(_MSC_VER)
-#pragma pack()
-#endif
+static int     MSId;
+static int     MSRegisteredId = -1;
 
-// win32 or djgpp
-#if defined (_WIN32) || defined (__DJGPP__)
-#define ioctl ioctlsocket
-#define close closesocket
-#ifdef WATTCP
-#define strerror strerror_s
-#endif
-#ifdef _WIN32
-#undef errno
-#define errno h_errno // some very strange things happen when not using h_error
-#endif
-#ifndef AI_ADDRCONFIG
-#define AI_ADDRCONFIG 0x00000400
-#endif
-#endif
+static boolean MSRegistered;
+static boolean MSInProgress;
+static boolean MSUpdateAgain;
+
+static time_t  MSLastPing;
+
+#ifdef HAVE_THREADS
+static I_mutex MSMutex;
+static I_cond  MSCond;
+
+#  define Lock_state()   I_lock_mutex  (&MSMutex)
+#  define Unlock_state() I_unlock_mutex (MSMutex)
+#else/*HAVE_THREADS*/
+#  define Lock_state()
+#  define Unlock_state()
+#endif/*HAVE_THREADS*/
 
 #ifndef NONET
 static void Command_Listserv_f(void);
 #endif
+
+#endif/*MASTERSERVER*/
+
+static void Update_parameters (void);
+
 static void MasterServer_OnChange(void);
-static void ServerName_OnChange(void);
 
-#define DEF_PORT "28900"
-consvar_t cv_masterserver = {"masterserver", "ms.srb2.org:"DEF_PORT, CV_SAVE, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_servername = {"servername", "SRB2 server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, ServerName_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t masterserver_update_rate_cons_t[] = {
+	{2,  "MIN"},
+	{60, "MAX"},
+	{0,NULL}
+};
 
-INT16 ms_RoomId = -1;
+consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange);
+consvar_t cv_servername = CVAR_INIT ("servername", "SRB2 server", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, NULL, Update_parameters);
 
-static enum { MSCS_NONE, MSCS_WAITING, MSCS_REGISTERED, MSCS_FAILED } con_state = MSCS_NONE;
+consvar_t cv_masterserver_update_rate = CVAR_INIT ("masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters);
 
-static INT32 msnode = -1;
-UINT16 current_port = 0;
+INT16 ms_RoomId = -1;
 
-#if defined (_WIN32) && !defined (NONET)
-typedef SOCKET SOCKET_TYPE;
-#define ERRSOCKET (SOCKET_ERROR)
-#else
-#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__)
-typedef int SOCKET_TYPE;
-#else
-typedef unsigned long SOCKET_TYPE;
-#endif
-#define ERRSOCKET (-1)
-#endif
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+int           ms_QueryId;
+I_mutex       ms_QueryId_mutex;
 
-#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (_WIN32)
-typedef int socklen_t;
+msg_server_t *ms_ServerList;
+I_mutex       ms_ServerList_mutex;
 #endif
 
-#ifndef NONET
-static SOCKET_TYPE socket_fd = ERRSOCKET; // WINSOCK socket
-static struct timeval select_timeout;
-static fd_set wset;
-static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags);
-#endif
+UINT16 current_port = 0;
 
 // Room list is an external variable now.
 // Avoiding having to get info ten thousand times...
@@ -222,417 +91,100 @@ void AddMServCommands(void)
 {
 #ifndef NONET
 	CV_RegisterVar(&cv_masterserver);
+	CV_RegisterVar(&cv_masterserver_update_rate);
+	CV_RegisterVar(&cv_masterserver_timeout);
+	CV_RegisterVar(&cv_masterserver_debug);
+	CV_RegisterVar(&cv_masterserver_token);
 	CV_RegisterVar(&cv_servername);
+#ifdef MASTERSERVER
 	COM_AddCommand("listserv", Command_Listserv_f);
 #endif
-}
-
-/** Closes the connection to the master server.
-  *
-  * \todo Fix for Windows?
-  */
-static void CloseConnection(void)
-{
-#ifndef NONET
-	if (socket_fd != (SOCKET_TYPE)ERRSOCKET)
-		close(socket_fd);
-	socket_fd = ERRSOCKET;
 #endif
 }
 
-//
-// MS_Write():
-//
-static INT32 MS_Write(msg_t *msg)
-{
-#ifdef NONET
-	(void)msg;
-	return MS_WRITE_ERROR;
-#else
-	size_t len;
-
-	if (msg->length == 0)
-		msg->length = (INT32)strlen(msg->buffer);
-	len = msg->length + HEADER_SIZE;
-
-	msg->type = htonl(msg->type);
-	msg->length = htonl(msg->length);
-	msg->room = htonl(msg->room);
-
-	if ((size_t)send(socket_fd, (char *)msg, (int)len, 0) != len)
-		return MS_WRITE_ERROR;
-	return 0;
-#endif
-}
-
-//
-// MS_Read():
-//
-static INT32 MS_Read(msg_t *msg)
-{
-#ifdef NONET
-	(void)msg;
-	return MS_READ_ERROR;
-#else
-	if (recvfull(socket_fd, (char *)msg, HEADER_SIZE, 0) != HEADER_SIZE)
-		return MS_READ_ERROR;
-
-	msg->type = ntohl(msg->type);
-	msg->length = ntohl(msg->length);
-	msg->room = ntohl(msg->room);
-
-	if (!msg->length) // fix a bug in Windows 2000
-		return 0;
-
-	if (recvfull(socket_fd, (char *)msg->buffer, msg->length, 0) != msg->length)
-		return MS_READ_ERROR;
-	return 0;
-#endif
-}
-
-#ifndef NONET
-/** Gets a list of game servers from the master server.
-  */
-static INT32 GetServersList(void)
-{
-	msg_t msg;
-	INT32 count = 0;
-
-	msg.type = GET_SERVER_MSG;
-	msg.length = 0;
-	msg.room = 0;
-	if (MS_Write(&msg) < 0)
-		return MS_WRITE_ERROR;
-
-	while (MS_Read(&msg) >= 0)
-	{
-		if (!msg.length)
-		{
-			if (!count)
-				CONS_Alert(CONS_NOTICE, M_GetText("No servers currently running.\n"));
-			return MS_NO_ERROR;
-		}
-		count++;
-		CONS_Printf("%s",msg.buffer);
-	}
-
-	return MS_READ_ERROR;
-}
-#endif
+#ifdef MASTERSERVER
 
-//
-// MS_Connect()
-//
-#ifndef NONET
-static INT32 MS_SubConnect(const char *ip_addr, const char *str_port, INT32 async, struct sockaddr *bindaddr, socklen_t bindaddrlen)
+static void WarnGUI (void)
 {
-	struct my_addrinfo *ai, *runp, hints;
-	int gaie;
-
-	memset (&hints, 0x00, sizeof(hints));
-#ifdef AI_ADDRCONFIG
-	hints.ai_flags = AI_ADDRCONFIG;
+#ifdef HAVE_THREADS
+	I_lock_mutex(&m_menu_mutex);
 #endif
-	hints.ai_family = AF_INET;
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_protocol = IPPROTO_TCP;
-
-	//I_InitTcpNetwork(); this is already done on startup in D_SRB2Main()
-	if (!I_InitTcpDriver()) // this is done only if not already done
-		return MS_SOCKET_ERROR;
-
-	gaie = I_getaddrinfo(ip_addr, str_port, &hints, &ai);
-	if (gaie != 0)
-		return MS_GETHOSTBYNAME_ERROR;
-	else
-		runp = ai;
-
-	while (runp != NULL)
-	{
-		socket_fd = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
-		if (socket_fd != (SOCKET_TYPE)ERRSOCKET)
-		{
-			if (!bindaddr || bind(socket_fd, bindaddr, bindaddrlen) == 0)
-			{
-				if (async) // do asynchronous connection
-				{
-#ifdef FIONBIO
-#ifdef WATTCP
-					char res = 1;
-#else
-					unsigned long res = 1;
-#endif
-
-					ioctl(socket_fd, FIONBIO, &res);
-#endif
-
-					if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) == ERRSOCKET)
-					{
-#ifdef _WIN32 // humm, on win32/win64 it doesn't work with EINPROGRESS (stupid windows)
-						if (WSAGetLastError() != WSAEWOULDBLOCK)
-#else
-							if (errno != EINPROGRESS)
-#endif
-							{
-								con_state = MSCS_FAILED;
-								CloseConnection();
-								I_freeaddrinfo(ai);
-								return MS_CONNECT_ERROR;
-							}
-					}
-					con_state = MSCS_WAITING;
-					FD_ZERO(&wset);
-					FD_SET(socket_fd, &wset);
-					select_timeout.tv_sec = 0, select_timeout.tv_usec = 0;
-					I_freeaddrinfo(ai);
-					return 0;
-				}
-				else if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) != ERRSOCKET)
-				{
-					I_freeaddrinfo(ai);
-					return 0;
-				}
-			}
-			close(socket_fd);
-		}
-		runp = runp->ai_next;
-	}
-	I_freeaddrinfo(ai);
-	return MS_CONNECT_ERROR;
-}
-#endif/*NONET xd*/
-
-static INT32 MS_Connect(const char *ip_addr, const char *str_port, INT32 async)
-{
-#ifdef NONET
-	(void)ip_addr;
-	(void)str_port;
-	(void)async;
-	return MS_CONNECT_ERROR;
-#else
-	const char *lhost;
-	struct my_addrinfo hints;
-	struct my_addrinfo *ai, *aip;
-	int c;
-	if (M_CheckParm("-bindaddr") && ( lhost = M_GetNextParm() ))
-	{
-		memset (&hints, 0x00, sizeof(hints));
-#ifdef AI_ADDRCONFIG
-		hints.ai_flags = AI_ADDRCONFIG;
+	M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n\nCheck the console for details.\n"), NULL, MM_NOTHING);
+#ifdef HAVE_THREADS
+	I_unlock_mutex(m_menu_mutex);
 #endif
-		hints.ai_family = AF_INET;
-		hints.ai_socktype = SOCK_STREAM;
-		hints.ai_protocol = IPPROTO_TCP;
-		if (( c = I_getaddrinfo(lhost, 0, &hints, &ai) ) != 0)
-		{
-			CONS_Printf(
-					"mserv.c: bind to %s: %s\n",
-					lhost,
-					gai_strerror(c));
-			return MS_GETHOSTBYNAME_ERROR;
-		}
-		for (aip = ai; aip; aip = aip->ai_next)
-		{
-			c = MS_SubConnect(ip_addr, str_port, async, aip->ai_addr, aip->ai_addrlen);
-			if (c == 0)
-			{
-				I_freeaddrinfo(ai);
-				return 0;
-			}
-		}
-		I_freeaddrinfo(ai);
-		return c;
-	}
-	else
-		return MS_SubConnect(ip_addr, str_port, async, 0, 0);
-#endif/*NONET xd*/
 }
 
 #define NUM_LIST_SERVER MAXSERVERLIST
-const msg_server_t *GetShortServersList(INT32 room)
+msg_server_t *GetShortServersList(INT32 room, int id)
 {
-	static msg_server_t server_list[NUM_LIST_SERVER+1]; // +1 for easy test
-	msg_t msg;
-	INT32 i;
+	msg_server_t *server_list;
 
-	// we must be connected to the master server before writing to it
-	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
-		M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING);
-		return NULL;
-	}
-
-	msg.type = GET_SHORT_SERVER_MSG;
-	msg.length = 0;
-	msg.room = room;
-	if (MS_Write(&msg) < 0)
-		return NULL;
+	// +1 for easy test
+	server_list = malloc(( NUM_LIST_SERVER + 1 ) * sizeof *server_list);
 
-	for (i = 0; i < NUM_LIST_SERVER && MS_Read(&msg) >= 0; i++)
-	{
-		if (!msg.length)
-		{
-			server_list[i].header.buffer[0] = 0;
-			CloseConnection();
-			return server_list;
-		}
-		M_Memcpy(&server_list[i], msg.buffer, sizeof (msg_server_t));
-		server_list[i].header.buffer[0] = 1;
-	}
-	CloseConnection();
-	if (i == NUM_LIST_SERVER)
-	{
-		server_list[i].header.buffer[0] = 0;
+	if (HMS_fetch_servers(server_list, room, id))
 		return server_list;
-	}
 	else
+	{
+		free(server_list);
+		WarnGUI();
 		return NULL;
+	}
 }
 
-INT32 GetRoomsList(boolean hosting)
+INT32 GetRoomsList(boolean hosting, int id)
 {
-	static msg_ban_t banned_info[1];
-	msg_t msg;
-	INT32 i;
-
-	// we must be connected to the master server before writing to it
-	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
-		M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING);
-		return -1;
-	}
-
-	if (hosting)
-		msg.type = GET_ROOMS_HOST_MSG;
-	else
-		msg.type = GET_ROOMS_MSG;
-	msg.length = 0;
-	msg.room = 0;
-	if (MS_Write(&msg) < 0)
-	{
-		room_list[0].id = 1;
-		strcpy(room_list[0].motd,"Master Server Offline.");
-		strcpy(room_list[0].name,"Offline");
-		return -1;
-	}
-
-	for (i = 0; i < NUM_LIST_ROOMS && MS_Read(&msg) >= 0; i++)
-	{
-		if(msg.type == GET_BANNED_MSG)
-		{
-			char banmsg[1000];
-			M_Memcpy(&banned_info[0], msg.buffer, sizeof (msg_ban_t));
-			if (hosting)
-				sprintf(banmsg, M_GetText("You have been banned from\nhosting netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp);
-			else
-				sprintf(banmsg, M_GetText("You have been banned from\njoining netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp);
-			M_StartMessage(banmsg, NULL, MM_NOTHING);
-			ms_RoomId = -1;
-			return -2;
-		}
-		if (!msg.length)
-		{
-			room_list[i].header.buffer[0] = 0;
-			CloseConnection();
-			return 1;
-		}
-		M_Memcpy(&room_list[i], msg.buffer, sizeof (msg_rooms_t));
-		room_list[i].header.buffer[0] = 1;
-	}
-	CloseConnection();
-	if (i == NUM_LIST_ROOMS)
-	{
-		room_list[i].header.buffer[0] = 0;
+	if (HMS_fetch_rooms( ! hosting, id))
 		return 1;
-	}
 	else
 	{
-		room_list[0].id = 1;
-		strcpy(room_list[0].motd,M_GetText("Master Server Offline."));
-		strcpy(room_list[0].name,M_GetText("Offline"));
+		WarnGUI();
 		return -1;
 	}
 }
 
 #ifdef UPDATE_ALERT
-const char *GetMODVersion(void)
+char *GetMODVersion(int id)
 {
-	static msg_t msg;
+	char *buffer;
+	int c;
 
-	// we must be connected to the master server before writing to it
-	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
-		M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING);
-		return NULL;
-	}
+	(void)id;
+
+	buffer = malloc(16);
+
+	c = HMS_compare_mod_version(buffer, 16);
 
-	msg.type = GET_VERSION_MSG;
-	msg.length = sizeof MODVERSION;
-	msg.room = MODID; // Might as well use it for something.
-	sprintf(msg.buffer,"%d",MODVERSION);
-	if (MS_Write(&msg) < 0)
+#ifdef HAVE_THREADS
+	I_lock_mutex(&ms_QueryId_mutex);
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n"));
-		M_StartMessage(M_GetText("Could not send to the Master Server\n"), NULL, MM_NOTHING);
-		CloseConnection();
-		return NULL;
+		if (id != ms_QueryId)
+			c = -1;
 	}
+	I_unlock_mutex(ms_QueryId_mutex);
+#endif
 
-	if (MS_Read(&msg) < 0)
+	if (c > 0)
+		return buffer;
+	else
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n"));
-		M_StartMessage(M_GetText("No reply from the Master Server\n"), NULL, MM_NOTHING);
-		CloseConnection();
-		return NULL;
-	}
+		free(buffer);
 
-	CloseConnection();
+		if (! c)
+			WarnGUI();
 
-	if(strcmp(msg.buffer,"NULL") != 0)
-	{
-		return msg.buffer;
-	}
-	else
 		return NULL;
+	}
 }
 
 // Console only version of the above (used before game init)
 void GetMODVersion_Console(void)
 {
-	static msg_t msg;
-
-	// we must be connected to the master server before writing to it
-	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
-		return;
-	}
-
-	msg.type = GET_VERSION_MSG;
-	msg.length = sizeof MODVERSION;
-	msg.room = MODID; // Might as well use it for something.
-	sprintf(msg.buffer,"%d",MODVERSION);
-	if (MS_Write(&msg) < 0)
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n"));
-		CloseConnection();
-		return;
-	}
+	char buffer[16];
 
-	if (MS_Read(&msg) < 0)
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n"));
-		CloseConnection();
-		return;
-	}
-
-	CloseConnection();
-
-	if(strcmp(msg.buffer,"NULL") != 0)
-		I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, msg.buffer);
+	if (HMS_compare_mod_version(buffer, sizeof buffer) > 0)
+		I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, buffer);
 }
 #endif
 
@@ -641,388 +193,359 @@ void GetMODVersion_Console(void)
   */
 static void Command_Listserv_f(void)
 {
-	if (con_state == MSCS_WAITING)
-	{
-		CONS_Alert(CONS_NOTICE, M_GetText("Not yet connected to the Master Server.\n"));
-		return;
-	}
-
 	CONS_Printf(M_GetText("Retrieving server list...\n"));
 
-	if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
-		return;
+		HMS_list_servers();
 	}
-
-	if (GetServersList())
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot get server list\n"));
-
-	CloseConnection();
 }
 #endif
 
-FUNCMATH static const char *int2str(INT32 n)
+static void
+Finish_registration (void)
 {
-	INT32 i;
-	static char res[16];
+	int registered;
 
-	res[15] = '\0';
-	res[14] = (char)((char)(n%10)+'0');
-	for (i = 13; (n /= 10); i--)
-		res[i] = (char)((char)(n%10)+'0');
+	CONS_Printf("Registering this server on the master server...\n");
 
-	return &res[i+1];
-}
+	registered = HMS_register();
 
-#ifndef NONET
-static INT32 ConnectionFailed(void)
-{
-	con_state = MSCS_FAILED;
-	CONS_Alert(CONS_ERROR, M_GetText("Connection to Master Server failed\n"));
-	CloseConnection();
-	return MS_CONNECT_ERROR;
+	Lock_state();
+	{
+		MSRegistered = registered;
+		MSRegisteredId = MSId;
+
+		time(&MSLastPing);
+	}
+	Unlock_state();
+
+	if (registered)
+		CONS_Printf("Master server registration successful.\n");
 }
-#endif
 
-/** Tries to register the local game server on the master server.
-  */
-static INT32 AddToMasterServer(boolean firstadd)
+static void
+Finish_update (void)
 {
-#ifdef NONET
-	(void)firstadd;
-#else
-	static INT32 retry = 0;
-	int i, res;
-	socklen_t j;
-	msg_t msg;
-	msg_server_t *info = (msg_server_t *)msg.buffer;
-	INT32 room = -1;
-	fd_set tset;
-	time_t timestamp = time(NULL);
-	UINT32 signature, tmp;
-	const char *insname;
-
-	M_Memcpy(&tset, &wset, sizeof (tset));
-	res = select(255, NULL, &tset, NULL, &select_timeout);
-	if (res != ERRSOCKET && !res)
+	int registered;
+	int done;
+
+	Lock_state();
 	{
-		if (retry++ > 30) // an about 30 second timeout
-		{
-			retry = 0;
-			CONS_Alert(CONS_ERROR, M_GetText("Master Server timed out\n"));
-			MSLastPing = timestamp;
-			return ConnectionFailed();
-		}
-		return MS_CONNECT_ERROR;
+		registered = MSRegistered;
+		MSUpdateAgain = false;/* this will happen anyway */
 	}
-	retry = 0;
-	if (res == ERRSOCKET)
+	Unlock_state();
+
+	if (registered)
 	{
-		if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0))
+		if (HMS_update())
 		{
-			CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno));
-			MSLastPing = timestamp;
-			return ConnectionFailed();
+			Lock_state();
+			{
+				time(&MSLastPing);
+				MSRegistered = true;
+			}
+			Unlock_state();
+
+			CONS_Printf("Updated master server listing.\n");
 		}
+		else
+			Finish_registration();
 	}
+	else
+		Finish_registration();
 
-	// so, the socket is writable, but what does that mean, that the connection is
-	// ok, or bad... let see that!
-	j = (socklen_t)sizeof (i);
-	getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&i, &j);
-	if (i) // it was bad
+	Lock_state();
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno));
-		MSLastPing = timestamp;
-		return ConnectionFailed();
+		done = ! MSUpdateAgain;
+
+		if (done)
+			MSInProgress = false;
 	}
+	Unlock_state();
 
-#ifdef PARANOIA
-	if (ms_RoomId <= 0)
-		I_Error("Attmepted to host in room \"All\"!\n");
-#endif
-	room = ms_RoomId;
-
-	for(signature = 0, insname = cv_servername.string; *insname; signature += *insname++);
-	tmp = (UINT32)(signature * (size_t)&MSLastPing);
-	signature *= tmp;
-	signature &= 0xAAAAAAAA;
-	M_Memcpy(&info->header.signature, &signature, sizeof (UINT32));
-
-	strcpy(info->ip, "");
-	strcpy(info->port, int2str(current_port));
-	strcpy(info->name, cv_servername.string);
-	M_Memcpy(&info->room, & room, sizeof (INT32));
-#ifndef DEVELOP
-	strcpy(info->version, SRB2VERSION);
-#else // Trunk build, send revision info
-	strcpy(info->version, GetRevisionString());
-#endif
-	strcpy(registered_server.name, cv_servername.string);
+	if (! done)
+		Finish_update();
+}
 
-	if(firstadd)
-		msg.type = ADD_SERVER_MSG;
-	else
-		msg.type = PING_SERVER_MSG;
+static void
+Finish_unlist (void)
+{
+	int registered;
 
-	msg.length = (UINT32)sizeof (msg_server_t);
-	msg.room = 0;
-	if (MS_Write(&msg) < 0)
+	Lock_state();
 	{
-		MSLastPing = timestamp;
-		return ConnectionFailed();
+		registered = MSRegistered;
 	}
+	Unlock_state();
 
-	if(con_state != MSCS_REGISTERED)
-		CONS_Printf(M_GetText("Master Server update successful.\n"));
+	if (registered)
+	{
+		CONS_Printf("Removing this server from the master server...\n");
 
-	MSLastPing = timestamp;
-	con_state = MSCS_REGISTERED;
-	CloseConnection();
+		if (HMS_unlist())
+			CONS_Printf("Server deregistration request successfully sent.\n");
+
+		Lock_state();
+		{
+			MSRegistered = false;
+		}
+		Unlock_state();
+
+#ifdef HAVE_THREADS
+		I_wake_all_cond(&MSCond);
 #endif
-	return MS_NO_ERROR;
+	}
+
+	Lock_state();
+	{
+		if (MSId == MSRegisteredId)
+			MSId++;
+	}
+	Unlock_state();
 }
 
-static INT32 RemoveFromMasterSever(void)
+#ifdef HAVE_THREADS
+static int *
+Server_id (void)
 {
-	msg_t msg;
-	msg_server_t *info = (msg_server_t *)msg.buffer;
-
-	strcpy(info->header.buffer, "");
-	strcpy(info->ip, "");
-	strcpy(info->port, int2str(current_port));
-	strcpy(info->name, registered_server.name);
-	sprintf(info->version, "%d.%d.%d", VERSION/100, VERSION%100, SUBVERSION);
-
-	msg.type = REMOVE_SERVER_MSG;
-	msg.length = (UINT32)sizeof (msg_server_t);
-	msg.room = 0;
-	if (MS_Write(&msg) < 0)
-		return MS_WRITE_ERROR;
-
-	return MS_NO_ERROR;
+	int *id;
+	id = malloc(sizeof *id);
+	Lock_state();
+	{
+		*id = MSId;
+	}
+	Unlock_state();
+	return id;
 }
 
-const char *GetMasterServerPort(void)
+static int *
+New_server_id (void)
 {
-	const char *t = cv_masterserver.string;
-
-	while ((*t != ':') && (*t != '\0'))
-		t++;
-
-	if (*t)
-		return ++t;
-	else
-		return DEF_PORT;
+	int *id;
+	id = malloc(sizeof *id);
+	Lock_state();
+	{
+		*id = ++MSId;
+		I_wake_all_cond(&MSCond);
+	}
+	Unlock_state();
+	return id;
 }
 
-/** Gets the IP address of the master server. Actually, it seems to just
-  * return the hostname, instead; the lookup is done elsewhere.
-  *
-  * \return Hostname of the master server, without port number on the end.
-  * \todo Rename function?
-  */
-const char *GetMasterServerIP(void)
+static void
+Register_server_thread (int *id)
 {
-	static char str_ip[64];
-	char *t = str_ip;
+	int same;
 
-	if (strstr(cv_masterserver.string, "srb2.ssntails.org:28910")
-	 || strstr(cv_masterserver.string, "srb2.servegame.org:28910")
-	 || strstr(cv_masterserver.string, "srb2.servegame.org:28900")
-	   )
+	Lock_state();
 	{
-		// replace it with the current default one
-		CV_Set(&cv_masterserver, cv_masterserver.defaultvalue);
-	}
+		/* wait for previous unlist to finish */
+		while (*id == MSId && MSRegistered)
+			I_hold_cond(&MSCond, MSMutex);
 
-	strcpy(t, cv_masterserver.string);
+		same = ( *id == MSId );/* it could have been a while */
+	}
+	Unlock_state();
 
-	while ((*t != ':') && (*t != '\0'))
-		t++;
-	*t = '\0';
+	if (same)/* it could have been a while */
+		Finish_registration();
 
-	return str_ip;
+	free(id);
 }
 
-void MSOpenUDPSocket(void)
+static void
+Update_server_thread (int *id)
 {
-#ifndef NONET
-	if (I_NetMakeNodewPort)
+	int same;
+
+	Lock_state();
 	{
-		// If it's already open, there's nothing to do.
-		if (msnode < 0)
-			msnode = I_NetMakeNodewPort(GetMasterServerIP(), GetMasterServerPort());
+		same = ( *id == MSRegisteredId );
 	}
-	else
-#endif
-		msnode = -1;
-}
+	Unlock_state();
 
-void MSCloseUDPSocket(void)
-{
-	if (msnode != INT16_MAX) I_NetFreeNodenum(msnode);
-	msnode = -1;
+	if (same)
+		Finish_update();
+
+	free(id);
 }
 
-void RegisterServer(void)
+static void
+Unlist_server_thread (int *id)
 {
-	if (con_state == MSCS_REGISTERED || con_state == MSCS_WAITING)
-			return;
-
-	CONS_Printf(M_GetText("Registering this server on the Master Server...\n"));
-
-	strcpy(registered_server.ip, GetMasterServerIP());
-	strcpy(registered_server.port, GetMasterServerPort());
+	int same;
 
-	if (MS_Connect(registered_server.ip, registered_server.port, 1))
+	Lock_state();
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
-		return;
+		same = ( *id == MSRegisteredId );
 	}
-	MSOpenUDPSocket();
+	Unlock_state();
 
-	// keep the TCP connection open until AddToMasterServer() is completed;
+	if (same)
+		Finish_unlist();
+
+	free(id);
 }
 
-static inline void SendPingToMasterServer(void)
+static void
+Change_masterserver_thread (char *api)
 {
-/*	static tic_t next_time = 0;
-	tic_t cur_time;
-	char *inbuffer = (char*)netbuffer;
-
-	cur_time = I_GetTime();
-	if (!netgame)
-		UnregisterServer();
-	else if (cur_time > next_time) // ping every 2 second if possible
+	Lock_state();
 	{
-		next_time = cur_time+2*TICRATE;
-
-		if (con_state == MSCS_WAITING)
-			AddToMasterServer();
-
-		if (con_state != MSCS_REGISTERED)
-			return;
-
-		// cur_time is just a dummy data to send
-		WRITEUINT32(inbuffer, cur_time);
-		doomcom->datalength = sizeof (cur_time);
-		doomcom->remotenode = (INT16)msnode;
-		I_NetSend();
+		while (MSRegistered)
+			I_hold_cond(&MSCond, MSMutex);
 	}
-*/
+	Unlock_state();
 
-// Here, have a simpler MS Ping... - Cue
-	if(time(NULL) > (MSLastPing+(60*2)) && con_state != MSCS_NONE)
-	{
-		//CONS_Debug(DBG_NETPLAY, "%ld (current time) is greater than %d (Last Ping Time)\n", time(NULL), MSLastPing);
-		if(MSLastPing < 1)
-			AddToMasterServer(true);
-		else
-			AddToMasterServer(false);
-	}
+	HMS_set_api(api);
+}
+#endif/*HAVE_THREADS*/
+
+void RegisterServer(void)
+{
+#ifdef MASTERSERVER
+#ifdef HAVE_THREADS
+	I_spawn_thread(
+			"register-server",
+			(I_thread_fn)Register_server_thread,
+			New_server_id()
+	);
+#else
+	Finish_registration();
+#endif
+#endif/*MASTERSERVER*/
 }
 
-void SendAskInfoViaMS(INT32 node, tic_t asktime)
+static void UpdateServer(void)
 {
-	const char *address;
-	UINT16 port;
-	char *inip;
-	ms_holepunch_packet_t mshpp;
-
-	MSOpenUDPSocket();
-
-	// This must be called after calling MSOpenUDPSocket, due to the
-	// static buffer.
-	address = I_GetNodeAddress(node);
-
-	// no address?
-	if (!address)
-		return;
-
-	// Copy the IP address into the buffer.
-	inip = mshpp.ip;
-	while(*address && *address != ':') *inip++ = *address++;
-	*inip = '\0';
-
-	// Get the port.
-	port = (UINT16)(*address++ ? atoi(address) : 0);
-	mshpp.port = SHORT(port);
-
-	// Set the time for ping calculation.
-	mshpp.time = LONG(asktime);
-
-	// Send to the MS.
-	M_Memcpy(netbuffer, &mshpp, sizeof(mshpp));
-	doomcom->datalength = sizeof(ms_holepunch_packet_t);
-	doomcom->remotenode = (INT16)msnode;
-	I_NetSend();
+#ifdef HAVE_THREADS
+	I_spawn_thread(
+			"update-server",
+			(I_thread_fn)Update_server_thread,
+			Server_id()
+	);
+#else
+	Finish_update();
+#endif
 }
 
 void UnregisterServer(void)
 {
-	if (con_state != MSCS_REGISTERED)
-	{
-		con_state = MSCS_NONE;
-		CloseConnection();
-		return;
-	}
+#ifdef MASTERSERVER
+#ifdef HAVE_THREADS
+	I_spawn_thread(
+			"unlist-server",
+			(I_thread_fn)Unlist_server_thread,
+			Server_id()
+	);
+#else
+	Finish_unlist();
+#endif
+#endif/*MASTERSERVER*/
+}
 
-	con_state = MSCS_NONE;
+static boolean
+Online (void)
+{
+	return ( serverrunning && ms_RoomId > 0 );
+}
 
-	CONS_Printf(M_GetText("Removing this server from the Master Server...\n"));
+static inline void SendPingToMasterServer(void)
+{
+	int ready;
+	time_t now;
 
-	if (MS_Connect(registered_server.ip, registered_server.port, 0))
+	if (Online())
 	{
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n"));
-		return;
-	}
+		time(&now);
 
-	if (RemoveFromMasterSever() < 0)
-		CONS_Alert(CONS_ERROR, M_GetText("Cannot remove this server from the Master Server\n"));
+		Lock_state();
+		{
+			ready = (
+					MSRegisteredId == MSId &&
+					! MSInProgress &&
+					now >= ( MSLastPing + 60 * cv_masterserver_update_rate.value )
+			);
+
+			if (ready)
+				MSInProgress = true;
+		}
+		Unlock_state();
 
-	CloseConnection();
-	MSCloseUDPSocket();
-	MSLastPing = 0;
+		if (ready)
+			UpdateServer();
+	}
 }
 
 void MasterClient_Ticker(void)
 {
-	if (server && ms_RoomId > 0)
-		SendPingToMasterServer();
+#ifdef MASTERSERVER
+	SendPingToMasterServer();
+#endif
 }
 
-static void ServerName_OnChange(void)
+static void
+Set_api (const char *api)
 {
-	if (con_state == MSCS_REGISTERED)
-		AddToMasterServer(false);
+#ifdef HAVE_THREADS
+	I_spawn_thread(
+			"change-masterserver",
+			(I_thread_fn)Change_masterserver_thread,
+			strdup(api)
+	);
+#else
+	HMS_set_api(strdup(api));
+#endif
 }
 
-static void MasterServer_OnChange(void)
-{
-	UnregisterServer();
-	RegisterServer();
-}
+#endif/*MASTERSERVER*/
 
-#ifndef NONET
-// Like recv, but waits until we've got enough data to fill the buffer.
-static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags)
+static void
+Update_parameters (void)
 {
-	/* Total received. */
-	size_t totallen = 0;
+#ifdef MASTERSERVER
+	int registered;
+	int delayed;
 
-	while(totallen < len)
+	if (Online())
 	{
-		ssize_t ret = (ssize_t)recv(s, buf + totallen, (int)(len - totallen), flags);
+		Lock_state();
+		{
+			delayed = MSInProgress;
 
-		/* Error. */
-		if(ret == -1)
-			return (size_t)-1;
+			if (delayed)/* do another update after the current one */
+				MSUpdateAgain = true;
+			else
+				registered = MSRegistered;
+		}
+		Unlock_state();
 
-		totallen += ret;
+		if (! delayed && registered)
+			UpdateServer();
 	}
+#endif/*MASTERSERVER*/
+}
 
-	return totallen;
+static void MasterServer_OnChange(void)
+{
+#ifdef MASTERSERVER
+	UnregisterServer();
+
+	/*
+	TODO: remove this for v2, it's just a hack
+	for those coming in with an old config.
+	*/
+	if (
+			! cv_masterserver.changed &&
+			strcmp(cv_masterserver.string, "ms.srb2.org:28900") == 0
+	){
+		CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue);
+	}
+
+	Set_api(cv_masterserver.string);
+
+	if (Online())
+		RegisterServer();
+#endif/*MASTERSERVER*/
 }
-#endif
diff --git a/src/mserv.h b/src/mserv.h
index 5f9b8da5f430f5cbc384ba3497de3845fcb5c8b6..d0d5e49dfe9519fa5d0f1c2904fd0f2849e50100 100644
--- a/src/mserv.h
+++ b/src/mserv.h
@@ -2,6 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by James R.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -13,7 +14,7 @@
 #ifndef _MSERV_H_
 #define _MSERV_H_
 
-#define MASTERSERVERS21 // MasterServer v2.1
+#include "i_threads.h"
 
 // lowered from 32 due to menu changes
 #define NUM_LIST_ROOMS 16
@@ -64,33 +65,47 @@ typedef struct
 // ================================ GLOBALS ===============================
 
 extern consvar_t cv_masterserver, cv_servername;
+extern consvar_t cv_masterserver_update_rate;
+extern consvar_t cv_masterserver_timeout;
+extern consvar_t cv_masterserver_debug;
+extern consvar_t cv_masterserver_token;
 
 // < 0 to not connect (usually -1) (offline mode)
 // == 0 to show all rooms, not a valid hosting room
 // anything else is whatever room the MS assigns to that number (online mode)
 extern INT16 ms_RoomId;
 
-const char *GetMasterServerPort(void);
-const char *GetMasterServerIP(void);
+#ifdef HAVE_THREADS
+extern int           ms_QueryId;
+extern I_mutex       ms_QueryId_mutex;
 
-void MSOpenUDPSocket(void);
-void MSCloseUDPSocket(void);
-
-void SendAskInfoViaMS(INT32 node, tic_t asktime);
+extern msg_server_t *ms_ServerList;
+extern I_mutex       ms_ServerList_mutex;
+#endif
 
 void RegisterServer(void);
 void UnregisterServer(void);
 
 void MasterClient_Ticker(void);
 
-const msg_server_t *GetShortServersList(INT32 room);
-INT32 GetRoomsList(boolean hosting);
+msg_server_t *GetShortServersList(INT32 room, int id);
+INT32 GetRoomsList(boolean hosting, int id);
 #ifdef UPDATE_ALERT
-const char *GetMODVersion(void);
+char *GetMODVersion(int id);
 void GetMODVersion_Console(void);
 #endif
 extern msg_rooms_t room_list[NUM_LIST_ROOMS+1];
 
 void AddMServCommands(void);
 
+/* HTTP */
+void HMS_set_api (char *api);
+int  HMS_fetch_rooms (int joining, int id);
+int  HMS_register (void);
+int  HMS_unlist (void);
+int  HMS_update (void);
+void HMS_list_servers (void);
+msg_server_t * HMS_fetch_servers (msg_server_t *list, int room, int id);
+int  HMS_compare_mod_version (char *buffer, size_t size_of_buffer);
+
 #endif
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index 3c3c507cd168e46a50ccdce3a503c8570c028797..f12499d5ce6315e1a8baf70b43f454443ae0bef8 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -394,8 +394,10 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -593,7 +595,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 
 		}
 
-		ceiling->tag = sec->tag;
+		ceiling->tag = tag;
 		ceiling->type = type;
 		firstone = 0;
 	}
@@ -614,8 +616,10 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -670,7 +674,7 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 				break;
 		}
 
-		ceiling->tag = sec->tag;
+		ceiling->tag = tag;
 		ceiling->type = type;
 	}
 	return rtn;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 6ec021ef08a45e29d31f59c555452650c26b3d65..1be8868f836f3ecb9b4959a0604ba2069b272ccb 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -30,7 +30,7 @@
 #include "hardware/hw3sound.h"
 #endif
 
-boolean LUA_CallAction(const char *action, mobj_t *actor);
+boolean LUA_CallAction(enum actionnum actionnum, mobj_t *actor);
 
 player_t *stplyr;
 INT32 var1;
@@ -984,7 +984,7 @@ void A_Look(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Look", actor))
+	if (LUA_CallAction(A_LOOK, actor))
 		return;
 
 	if (!P_LookForPlayers(actor, locvar1 & 65535, false , FixedMul((locvar1 >> 16)*FRACUNIT, actor->scale)))
@@ -1017,7 +1017,7 @@ void A_Chase(mobj_t *actor)
 	INT32 delta;
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_Chase", actor))
+	if (LUA_CallAction(A_CHASE, actor))
 		return;
 
 	I_Assert(actor != NULL);
@@ -1108,7 +1108,7 @@ void A_FaceStabChase(mobj_t *actor)
 {
 	INT32 delta;
 
-	if (LUA_CallAction("A_FaceStabChase", actor))
+	if (LUA_CallAction(A_FACESTABCHASE, actor))
 		return;
 
 	if (actor->reactiontime)
@@ -1230,7 +1230,7 @@ void A_FaceStabRev(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FaceStabRev", actor))
+	if (LUA_CallAction(A_FACESTABREV, actor))
 		return;
 
 	if (!actor->target)
@@ -1273,7 +1273,7 @@ void A_FaceStabHurl(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FaceStabHurl", actor))
+	if (LUA_CallAction(A_FACESTABHURL, actor))
 		return;
 
 	if (actor->target)
@@ -1363,7 +1363,7 @@ void A_FaceStabMiss(mobj_t *actor)
 {
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FaceStabMiss", actor))
+	if (LUA_CallAction(A_FACESTABMISS, actor))
 		return;
 
 	if (++actor->extravalue1 >= 3)
@@ -1398,7 +1398,7 @@ void A_StatueBurst(mobj_t *actor)
 	mobjtype_t chunktype = (mobjtype_t)actor->info->raisestate;
 	mobj_t *new;
 
-	if (LUA_CallAction("A_StatueBurst", actor))
+	if (LUA_CallAction(A_STATUEBURST, actor))
 		return;
 
 	if (!locvar1 || !(new = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1)))
@@ -1448,7 +1448,7 @@ void A_StatueBurst(mobj_t *actor)
 //
 void A_JetJawRoam(mobj_t *actor)
 {
-	if (LUA_CallAction("A_JetJawRoam", actor))
+	if (LUA_CallAction(A_JETJAWROAM, actor))
 		return;
 
 	if (actor->reactiontime)
@@ -1477,7 +1477,7 @@ void A_JetJawChomp(mobj_t *actor)
 {
 	INT32 delta;
 
-	if (LUA_CallAction("A_JetJawChomp", actor))
+	if (LUA_CallAction(A_JETJAWCHOMP, actor))
 		return;
 
 	// turn towards movement direction if not there yet
@@ -1524,7 +1524,7 @@ void A_PointyThink(mobj_t *actor)
 	boolean firsttime = true;
 	INT32 sign;
 
-	if (LUA_CallAction("A_PointyThink", actor))
+	if (LUA_CallAction(A_POINTYTHINK, actor))
 		return;
 
 	actor->momx = actor->momy = actor->momz = 0;
@@ -1622,7 +1622,7 @@ void A_CheckBuddy(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_CheckBuddy", actor))
+	if (LUA_CallAction(A_CHECKBUDDY, actor))
 		return;
 
 	if (locvar1 && (!actor->tracer || actor->tracer->health <= 0))
@@ -1665,7 +1665,7 @@ void A_HoodFire(mobj_t *actor)
 	mobj_t *arrow;
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_HoodFire", actor))
+	if (LUA_CallAction(A_HOODFIRE, actor))
 		return;
 
 	// Check target first.
@@ -1697,7 +1697,7 @@ void A_HoodThink(mobj_t *actor)
 	fixed_t dx, dy, dz, dm;
 	boolean checksight;
 
-	if (LUA_CallAction("A_HoodThink", actor))
+	if (LUA_CallAction(A_HOODTHINK, actor))
 		return;
 
 	// Check target first.
@@ -1764,7 +1764,7 @@ void A_HoodThink(mobj_t *actor)
 //
 void A_HoodFall(mobj_t *actor)
 {
-	if (LUA_CallAction("A_HoodFall", actor))
+	if (LUA_CallAction(A_HOODFALL, actor))
 		return;
 
 	if (!P_IsObjectOnGround(actor))
@@ -1784,7 +1784,7 @@ void A_HoodFall(mobj_t *actor)
 //
 void A_ArrowBonks(mobj_t *actor)
 {
-	if (LUA_CallAction("A_ArrowBonks", actor))
+	if (LUA_CallAction(A_ARROWBONKS, actor))
 		return;
 
 	if (((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height >= actor->ceilingz)
@@ -1807,7 +1807,7 @@ void A_ArrowBonks(mobj_t *actor)
 //
 void A_SnailerThink(mobj_t *actor)
 {
-	if (LUA_CallAction("A_SnailerThink", actor))
+	if (LUA_CallAction(A_SNAILERTHINK, actor))
 		return;
 
 	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
@@ -1880,7 +1880,7 @@ void A_SnailerThink(mobj_t *actor)
 //
 void A_SharpChase(mobj_t *actor)
 {
-	if (LUA_CallAction("A_SharpChase", actor))
+	if (LUA_CallAction(A_SHARPCHASE, actor))
 		return;
 
 	if (actor->reactiontime)
@@ -1936,7 +1936,7 @@ void A_SharpSpin(mobj_t *actor)
 	INT32 locvar2 = var2;
 	angle_t oldang = actor->angle;
 
-	if (LUA_CallAction("A_SharpSpin", actor))
+	if (LUA_CallAction(A_SHARPSPIN, actor))
 		return;
 
 	if (actor->threshold && actor->target)
@@ -1969,7 +1969,7 @@ void A_SharpSpin(mobj_t *actor)
 //
 void A_SharpDecel(mobj_t *actor)
 {
-	if (LUA_CallAction("A_SharpDecel", actor))
+	if (LUA_CallAction(A_SHARPDECEL, actor))
 		return;
 
 	if (actor->momx > 2 || actor->momy > 2)
@@ -1994,7 +1994,7 @@ void A_CrushstaceanWalk(mobj_t *actor)
 	INT32 locvar2 = (var2 ? var2 : (INT32)actor->info->spawnstate);
 	angle_t ang = actor->angle + ((actor->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
 
-	if (LUA_CallAction("A_CrushstaceanWalk", actor))
+	if (LUA_CallAction(A_CRUSHSTACEANWALK, actor))
 		return;
 
 	actor->reactiontime--;
@@ -2023,7 +2023,7 @@ void A_CrushstaceanPunch(mobj_t *actor)
 {
 	INT32 locvar2 = (var2 ? var2 : (INT32)actor->info->spawnstate);
 
-	if (LUA_CallAction("A_CrushstaceanPunch", actor))
+	if (LUA_CallAction(A_CRUSHSTACEANPUNCH, actor))
 		return;
 
 	if (!actor->tracer)
@@ -2055,7 +2055,7 @@ void A_CrushclawAim(mobj_t *actor)
 	mobj_t *crab = actor->tracer;
 	angle_t ang;
 
-	if (LUA_CallAction("A_CrushclawAim", actor))
+	if (LUA_CallAction(A_CRUSHCLAWAIM, actor))
 		return;
 
 	if (!crab)
@@ -2116,7 +2116,7 @@ void A_CrushclawLaunch(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *crab = actor->tracer;
 
-	if (LUA_CallAction("A_CrushclawLaunch", actor))
+	if (LUA_CallAction(A_CRUSHCLAWLAUNCH, actor))
 		return;
 
 	if (!crab)
@@ -2247,7 +2247,7 @@ void A_CrushclawLaunch(mobj_t *actor)
 //
 void A_VultureVtol(mobj_t *actor)
 {
-	if (LUA_CallAction("A_VultureVtol", actor))
+	if (LUA_CallAction(A_VULTUREVTOL, actor))
 		return;
 
 	if (!actor->target)
@@ -2282,7 +2282,7 @@ void A_VultureVtol(mobj_t *actor)
 //
 void A_VultureCheck(mobj_t *actor)
 {
-	if (LUA_CallAction("A_VultureCheck", actor))
+	if (LUA_CallAction(A_VULTURECHECK, actor))
 		return;
 
 	if (actor->momx || actor->momy)
@@ -2338,7 +2338,7 @@ void A_VultureHover(mobj_t *actor)
 	fixed_t memz = actor->z;
 	SINT8 i;
 
-	if (LUA_CallAction("A_VultureHover", actor))
+	if (LUA_CallAction(A_VULTUREHOVER, actor))
 		return;
 
 	if (!actor->target || P_MobjWasRemoved(actor->target))
@@ -2400,7 +2400,7 @@ void A_VultureBlast(mobj_t *actor)
 	angle_t faa;
 	fixed_t faacos, faasin;
 
-	if (LUA_CallAction("A_VultureBlast", actor))
+	if (LUA_CallAction(A_VULTUREBLAST, actor))
 		return;
 
 	S_StartSound(actor, actor->info->attacksound);
@@ -2439,7 +2439,7 @@ void A_VultureFly(mobj_t *actor)
 	mobj_t *dust;
 	fixed_t momm;
 
-	if (LUA_CallAction("A_VultureFly", actor))
+	if (LUA_CallAction(A_VULTUREFLY, actor))
 		return;
 
 	if (!actor->target || P_MobjWasRemoved(actor->target))
@@ -2531,7 +2531,7 @@ void A_SkimChase(mobj_t *actor)
 {
 	INT32 delta;
 
-	if (LUA_CallAction("A_SkimChase", actor))
+	if (LUA_CallAction(A_SKIMCHASE, actor))
 		return;
 
 	if (actor->reactiontime)
@@ -2617,7 +2617,7 @@ nomissile:
 //
 void A_FaceTarget(mobj_t *actor)
 {
-	if (LUA_CallAction("A_FaceTarget", actor))
+	if (LUA_CallAction(A_FACETARGET, actor))
 		return;
 
 	if (!actor->target)
@@ -2635,7 +2635,7 @@ void A_FaceTarget(mobj_t *actor)
 //
 void A_FaceTracer(mobj_t *actor)
 {
-	if (LUA_CallAction("A_FaceTracer", actor))
+	if (LUA_CallAction(A_FACETRACER, actor))
 		return;
 
 	if (!actor->tracer)
@@ -2664,7 +2664,7 @@ void A_LobShot(mobj_t *actor)
 	fixed_t vertical, horizontal;
 	fixed_t airtime = var2 & 65535;
 
-	if (LUA_CallAction("A_LobShot", actor))
+	if (LUA_CallAction(A_LOBSHOT, actor))
 		return;
 
 	if (!actor->target)
@@ -2764,7 +2764,7 @@ void A_FireShot(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FireShot", actor))
+	if (LUA_CallAction(A_FIRESHOT, actor))
 		return;
 
 	if (!actor->target)
@@ -2802,7 +2802,7 @@ void A_SuperFireShot(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SuperFireShot", actor))
+	if (LUA_CallAction(A_SUPERFIRESHOT, actor))
 		return;
 
 	if (!actor->target)
@@ -2849,7 +2849,7 @@ void A_BossFireShot(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *missile;
 
-	if (LUA_CallAction("A_BossFireShot", actor))
+	if (LUA_CallAction(A_BOSSFIRESHOT, actor))
 		return;
 
 	if (!actor->target)
@@ -2933,7 +2933,7 @@ void A_Boss7FireMissiles(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Boss7FireMissiles", actor))
+	if (LUA_CallAction(A_BOSS7FIREMISSILES, actor))
 		return;
 
 	if (!actor->target)
@@ -3008,7 +3008,7 @@ void A_Boss1Laser(mobj_t *actor)
 		SKINCOLOR_SUPERRED3,
 	};
 
-	if (LUA_CallAction("A_Boss1Laser", actor))
+	if (LUA_CallAction(A_BOSS1LASER, actor))
 		return;
 
 	if (!actor->target)
@@ -3043,11 +3043,11 @@ void A_Boss1Laser(mobj_t *actor)
 				z = actor->z + FixedMul(56*FRACUNIT, actor->scale);
 			break;
 		case 2:
-			var2 = 3; // Fire middle laser
+			var1 = locvar1; var2 = 3; // Fire middle laser
 			A_Boss1Laser(actor);
-			var2 = 0; // Fire left laser
+			var1 = locvar1; var2 = 0; // Fire left laser
 			A_Boss1Laser(actor);
-			var2 = 1; // Fire right laser
+			var1 = locvar1; var2 = 1; // Fire right laser
 			A_Boss1Laser(actor);
 			return;
 			break;
@@ -3179,7 +3179,7 @@ void A_FocusTarget(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FocusTarget", actor))
+	if (LUA_CallAction(A_FOCUSTARGET, actor))
 		return;
 
 	if (actor->target)
@@ -3230,7 +3230,7 @@ void A_Boss4Reverse(mobj_t *actor)
 	sfxenum_t locvar1 = (sfxenum_t)var1;
 	sfxenum_t locvar2 = (sfxenum_t)var2;
 
-	if (LUA_CallAction("A_Boss4Reverse", actor))
+	if (LUA_CallAction(A_BOSS4REVERSE, actor))
 		return;
 
 	actor->reactiontime = 0;
@@ -3265,7 +3265,7 @@ void A_Boss4SpeedUp(mobj_t *actor)
 {
 	sfxenum_t locvar1 = (sfxenum_t)var1;
 
-	if (LUA_CallAction("A_Boss4SpeedUp", actor))
+	if (LUA_CallAction(A_BOSS4SPEEDUP, actor))
 		return;
 
 	S_StartSound(NULL, locvar1);
@@ -3283,7 +3283,7 @@ void A_Boss4Raise(mobj_t *actor)
 {
 	sfxenum_t locvar1 = (sfxenum_t)var1;
 
-	if (LUA_CallAction("A_Boss4Raise", actor))
+	if (LUA_CallAction(A_BOSS4RAISE, actor))
 		return;
 
 	S_StartSound(NULL, locvar1);
@@ -3314,7 +3314,7 @@ void A_SkullAttack(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SkullAttack", actor))
+	if (LUA_CallAction(A_SKULLATTACK, actor))
 		return;
 
 	if (!actor->target)
@@ -3432,7 +3432,7 @@ void A_BossZoom(mobj_t *actor)
 	angle_t an;
 	INT32 dist;
 
-	if (LUA_CallAction("A_BossZoom", actor))
+	if (LUA_CallAction(A_BOSSZOOM, actor))
 		return;
 
 	if (!actor->target)
@@ -3472,7 +3472,7 @@ void A_BossScream(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobjtype_t explodetype;
 
-	if (LUA_CallAction("A_BossScream", actor))
+	if (LUA_CallAction(A_BOSSSCREAM, actor))
 		return;
 
 	if (locvar1 & 1)
@@ -3517,7 +3517,7 @@ void A_BossScream(mobj_t *actor)
 //
 void A_Scream(mobj_t *actor)
 {
-	if (LUA_CallAction("A_Scream", actor))
+	if (LUA_CallAction(A_SCREAM, actor))
 		return;
 
 	if (actor->tracer && (actor->tracer->type == MT_SHELL || actor->tracer->type == MT_FIREBALL))
@@ -3535,7 +3535,7 @@ void A_Scream(mobj_t *actor)
 //
 void A_Pain(mobj_t *actor)
 {
-	if (LUA_CallAction("A_Pain", actor))
+	if (LUA_CallAction(A_PAIN, actor))
 		return;
 
 	if (actor->info->painsound)
@@ -3556,7 +3556,7 @@ void A_Fall(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_Fall", actor))
+	if (LUA_CallAction(A_FALL, actor))
 		return;
 
 	// actor is on ground, it can be walked over
@@ -3588,7 +3588,7 @@ void A_1upThinker(mobj_t *actor)
 	fixed_t temp;
 	INT32 closestplayer = -1;
 
-	if (LUA_CallAction("A_1upThinker", actor))
+	if (LUA_CallAction(A_1UPTHINKER, actor))
 		return;
 
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -3654,7 +3654,7 @@ void A_MonitorPop(mobj_t *actor)
 	mobjtype_t item = 0;
 	mobj_t *newmobj;
 
-	if (LUA_CallAction("A_MonitorPop", actor))
+	if (LUA_CallAction(A_MONITORPOP, actor))
 		return;
 
 	// Spawn the "pop" explosion.
@@ -3735,7 +3735,7 @@ void A_GoldMonitorPop(mobj_t *actor)
 	mobjtype_t item = 0;
 	mobj_t *newmobj;
 
-	if (LUA_CallAction("A_GoldMonitorPop", actor))
+	if (LUA_CallAction(A_GOLDMONITORPOP, actor))
 		return;
 
 	// Don't spawn the "pop" explosion, because the monitor isn't broken.
@@ -3818,7 +3818,7 @@ void A_GoldMonitorPop(mobj_t *actor)
 //
 void A_GoldMonitorRestore(mobj_t *actor)
 {
-	if (LUA_CallAction("A_GoldMonitorRestore", actor))
+	if (LUA_CallAction(A_GOLDMONITORRESTORE, actor))
 		return;
 
 	actor->flags |= MF_MONITOR|MF_SHOOTABLE;
@@ -3836,7 +3836,7 @@ void A_GoldMonitorSparkle(mobj_t *actor)
 {
 	fixed_t i, ngangle, xofs, yofs;
 
-	if (LUA_CallAction("A_GoldMonitorSparkle", actor))
+	if (LUA_CallAction(A_GOLDMONITORSPARKLE, actor))
 		return;
 
 	ngangle = FixedAngle(((leveltime * 21) % 360) << FRACBITS);
@@ -3858,7 +3858,7 @@ void A_Explode(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_Explode", actor))
+	if (LUA_CallAction(A_EXPLODE, actor))
 		return;
 
 	P_RadiusAttack(actor, actor->target, actor->info->damage, locvar1, true);
@@ -3878,7 +3878,7 @@ void A_BossDeath(mobj_t *mo)
 	line_t junk;
 	INT32 i;
 
-	if (LUA_CallAction("A_BossDeath", mo))
+	if (LUA_CallAction(A_BOSSDEATH, mo))
 		return;
 
 	if (mo->spawnpoint && mo->spawnpoint->extrainfo)
@@ -3927,12 +3927,17 @@ void A_BossDeath(mobj_t *mo)
 	}
 	else
 	{
+		// Initialize my junk
+		junk.tags.tags = NULL;
+		junk.tags.count = 0;
+
 		// Bring the egg trap up to the surface
-		junk.tag = LE_CAPSULE0;
+		// Incredibly shitty code ahead
+		Tag_FSet(&junk.tags, LE_CAPSULE0);
 		EV_DoElevator(&junk, elevateHighest, false);
-		junk.tag = LE_CAPSULE1;
+		Tag_FSet(&junk.tags, LE_CAPSULE1);
 		EV_DoElevator(&junk, elevateUp, false);
-		junk.tag = LE_CAPSULE2;
+		Tag_FSet(&junk.tags, LE_CAPSULE2);
 		EV_DoElevator(&junk, elevateHighest, false);
 
 		if (mapheaderinfo[gamemap-1]->muspostbossname[0] &&
@@ -4055,7 +4060,11 @@ bossjustdie:
 		}
 		case MT_KOOPA:
 		{
-			junk.tag = LE_KOOPA;
+			// Initialize my junk
+			junk.tags.tags = NULL;
+			junk.tags.count = 0;
+
+			Tag_FSet(&junk.tags, LE_KOOPA);
 			EV_DoCeiling(&junk, raiseToHighest);
 			return;
 		}
@@ -4186,7 +4195,7 @@ void A_CustomPower(mobj_t *actor)
 	INT32 locvar2 = var2;
 	boolean spawnshield = false;
 
-	if (LUA_CallAction("A_CustomPower", actor))
+	if (LUA_CallAction(A_CUSTOMPOWER, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4226,7 +4235,7 @@ void A_GiveWeapon(mobj_t *actor)
 	player_t *player;
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_GiveWeapon", actor))
+	if (LUA_CallAction(A_GIVEWEAPON, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4259,7 +4268,7 @@ void A_RingBox(mobj_t *actor)
 {
 	player_t *player;
 
-	if (LUA_CallAction("A_RingBox", actor))
+	if (LUA_CallAction(A_RINGBOX, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4286,7 +4295,7 @@ void A_Invincibility(mobj_t *actor)
 {
 	player_t *player;
 
-	if (LUA_CallAction("A_Invincibility", actor))
+	if (LUA_CallAction(A_INVINCIBILITY, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4319,7 +4328,7 @@ void A_SuperSneakers(mobj_t *actor)
 {
 	player_t *player;
 
-	if (LUA_CallAction("A_SuperSneakers", actor))
+	if (LUA_CallAction(A_SUPERSNEAKERS, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4354,7 +4363,7 @@ void A_AwardScore(mobj_t *actor)
 {
 	player_t *player;
 
-	if (LUA_CallAction("A_AwardScore", actor))
+	if (LUA_CallAction(A_AWARDSCORE, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4381,7 +4390,7 @@ void A_ExtraLife(mobj_t *actor)
 {
 	player_t *player;
 
-	if (LUA_CallAction("A_ExtraLife", actor))
+	if (LUA_CallAction(A_EXTRALIFE, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4419,7 +4428,7 @@ void A_GiveShield(mobj_t *actor)
 	player_t *player;
 	UINT16 locvar1 = var1;
 
-	if (LUA_CallAction("A_GiveShield", actor))
+	if (LUA_CallAction(A_GIVESHIELD, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4445,7 +4454,7 @@ void A_GravityBox(mobj_t *actor)
 {
 	player_t *player;
 
-	if (LUA_CallAction("A_GravityBox", actor))
+	if (LUA_CallAction(A_GRAVITYBOX, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -4470,7 +4479,7 @@ void A_GravityBox(mobj_t *actor)
 //
 void A_ScoreRise(mobj_t *actor)
 {
-	if (LUA_CallAction("A_ScoreRise", actor))
+	if (LUA_CallAction(A_SCORERISE, actor))
 		return;
 
 	// make logo rise!
@@ -4489,7 +4498,7 @@ void A_BunnyHop(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_BunnyHop", actor))
+	if (LUA_CallAction(A_BUNNYHOP, actor))
 		return;
 
 	if (((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height >= actor->ceilingz)
@@ -4513,7 +4522,7 @@ void A_BubbleSpawn(mobj_t *actor)
 	UINT8 prandom;
 	mobj_t *bubble = NULL;
 
-	if (LUA_CallAction("A_BubbleSpawn", actor))
+	if (LUA_CallAction(A_BUBBLESPAWN, actor))
 		return;
 
 	if (!(actor->eflags & MFE_UNDERWATER))
@@ -4566,7 +4575,7 @@ void A_FanBubbleSpawn(mobj_t *actor)
 	mobj_t *bubble = NULL;
 	fixed_t hz = actor->z + (4*actor->height)/5;
 
-	if (LUA_CallAction("A_FanBubbleSpawn", actor))
+	if (LUA_CallAction(A_FANBUBBLESPAWN, actor))
 		return;
 
 	if (!(actor->eflags & MFE_UNDERWATER))
@@ -4612,7 +4621,7 @@ void A_BubbleRise(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_BubbleRise", actor))
+	if (LUA_CallAction(A_BUBBLERISE, actor))
 		return;
 
 	if (actor->type == MT_EXTRALARGEBUBBLE)
@@ -4648,7 +4657,7 @@ void A_BubbleRise(mobj_t *actor)
 //
 void A_BubbleCheck(mobj_t *actor)
 {
-	if (LUA_CallAction("A_BubbleCheck", actor))
+	if (LUA_CallAction(A_BUBBLECHECK, actor))
 		return;
 
 	if (actor->eflags & MFE_UNDERWATER)
@@ -4666,7 +4675,7 @@ void A_BubbleCheck(mobj_t *actor)
 //
 void A_AttractChase(mobj_t *actor)
 {
-	if (LUA_CallAction("A_AttractChase", actor))
+	if (LUA_CallAction(A_ATTRACTCHASE, actor))
 		return;
 
 	if (actor->flags2 & MF2_NIGHTSPULL || !actor->health)
@@ -4737,7 +4746,7 @@ void A_DropMine(mobj_t *actor)
 	fixed_t z;
 	mobj_t *mine;
 
-	if (LUA_CallAction("A_DropMine", actor))
+	if (LUA_CallAction(A_DROPMINE, actor))
 		return;
 
 	if (locvar2 & 65535)
@@ -4785,7 +4794,7 @@ void A_FishJump(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FishJump", actor))
+	if (LUA_CallAction(A_FISHJUMP, actor))
 		return;
 
 	if (locvar2)
@@ -4836,7 +4845,7 @@ void A_ThrownRing(mobj_t *actor)
 	player_t *player;
 	fixed_t dist;
 
-	if (LUA_CallAction("A_ThrownRing", actor))
+	if (LUA_CallAction(A_THROWNRING, actor))
 		return;
 
 	if (leveltime % (TICRATE/7) == 0)
@@ -4906,7 +4915,7 @@ void A_ThrownRing(mobj_t *actor)
 		}
 
 		if (actor->tracer && (actor->tracer->health)
-			&& (actor->tracer->player->powers[pw_shield] & SH_PROTECTELECTRIC))// Already found someone to follow.
+			&& (actor->tracer->player && actor->tracer->player->powers[pw_shield] & SH_PROTECTELECTRIC))// Already found someone to follow.
 		{
 			const INT32 temp = actor->threshold;
 			actor->threshold = 32000;
@@ -4992,7 +5001,7 @@ void A_ThrownRing(mobj_t *actor)
 //
 void A_SetSolidSteam(mobj_t *actor)
 {
-	if (LUA_CallAction("A_SetSolidSteam", actor))
+	if (LUA_CallAction(A_SETSOLIDSTEAM, actor))
 		return;
 
 	actor->flags &= ~MF_NOCLIP;
@@ -5023,7 +5032,7 @@ void A_SetSolidSteam(mobj_t *actor)
 //
 void A_UnsetSolidSteam(mobj_t *actor)
 {
-	if (LUA_CallAction("A_UnsetSolidSteam", actor))
+	if (LUA_CallAction(A_UNSETSOLIDSTEAM, actor))
 		return;
 
 	actor->flags &= ~MF_SOLID;
@@ -5043,7 +5052,7 @@ void A_SignSpin(mobj_t *actor)
 	INT16 i;
 	angle_t rotateangle = FixedAngle(locvar1 << FRACBITS);
 
-	if (LUA_CallAction("A_SignSpin", actor))
+	if (LUA_CallAction(A_SIGNSPIN, actor))
 		return;
 
 	if (P_IsObjectOnGround(actor) && P_MobjFlip(actor) * actor->momz <= 0)
@@ -5114,7 +5123,7 @@ void A_SignPlayer(mobj_t *actor)
 
 	facecolor = signcolor = (UINT16)locvar2;
 
-	if (LUA_CallAction("A_SignPlayer", actor))
+	if (LUA_CallAction(A_SIGNPLAYER, actor))
 		return;
 
 	if (actor->tracer == NULL || locvar1 < -3 || locvar1 >= numskins || signcolor >= numskincolors)
@@ -5232,7 +5241,7 @@ void A_OverlayThink(mobj_t *actor)
 {
 	fixed_t destx, desty;
 
-	if (LUA_CallAction("A_OverlayThink", actor))
+	if (LUA_CallAction(A_OVERLAYTHINK, actor))
 		return;
 
 	if (!actor->target)
@@ -5284,7 +5293,7 @@ void A_JetChase(mobj_t *actor)
 {
 	fixed_t thefloor;
 
-	if (LUA_CallAction("A_JetChase", actor))
+	if (LUA_CallAction(A_JETCHASE, actor))
 		return;
 
 	if (actor->flags2 & MF2_AMBUSH)
@@ -5380,7 +5389,7 @@ void A_JetbThink(mobj_t *actor)
 	sector_t *nextsector;
 	fixed_t thefloor;
 
-	if (LUA_CallAction("A_JetbThink", actor))
+	if (LUA_CallAction(A_JETBTHINK, actor))
 		return;
 
 	if (actor->z >= actor->waterbottom && actor->watertop > actor->floorz
@@ -5445,7 +5454,7 @@ void A_JetgShoot(mobj_t *actor)
 {
 	fixed_t dist;
 
-	if (LUA_CallAction("A_JetgShoot", actor))
+	if (LUA_CallAction(A_JETGSHOOT, actor))
 		return;
 
 	if (!actor->target)
@@ -5485,7 +5494,7 @@ void A_ShootBullet(mobj_t *actor)
 {
 	fixed_t dist;
 
-	if (LUA_CallAction("A_ShootBullet", actor))
+	if (LUA_CallAction(A_SHOOTBULLET, actor))
 		return;
 
 	if (!actor->target)
@@ -5545,7 +5554,7 @@ void A_MinusDigging(mobj_t *actor)
 	fixed_t mz = (actor->eflags & MFE_VERTICALFLIP) ? actor->ceilingz : actor->floorz;
 	mobj_t *par;
 
-	if (LUA_CallAction("A_MinusDigging", actor))
+	if (LUA_CallAction(A_MINUSDIGGING, actor))
 		return;
 
 	if (!actor->target)
@@ -5626,7 +5635,7 @@ void A_MinusPopup(mobj_t *actor)
 	angle_t ani = FixedAngle(FRACUNIT*360/num);
 	INT32 i;
 
-	if (LUA_CallAction("A_MinusPopup", actor))
+	if (LUA_CallAction(A_MINUSPOPUP, actor))
 		return;
 
 	if (actor->eflags & MFE_VERTICALFLIP)
@@ -5661,7 +5670,7 @@ void A_MinusCheck(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_MinusCheck", actor))
+	if (LUA_CallAction(A_MINUSCHECK, actor))
 		return;
 
 	if (((actor->eflags & MFE_VERTICALFLIP) && actor->z + actor->height >= actor->ceilingz) || (!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz))
@@ -5692,7 +5701,7 @@ void A_MinusCheck(mobj_t *actor)
 //
 void A_ChickenCheck(mobj_t *actor)
 {
-	if (LUA_CallAction("A_ChickenCheck", actor))
+	if (LUA_CallAction(A_CHICKENCHECK, actor))
 		return;
 
 	if ((!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz)
@@ -5723,7 +5732,7 @@ void A_JetgThink(mobj_t *actor)
 
 	fixed_t thefloor;
 
-	if (LUA_CallAction("A_JetgThink", actor))
+	if (LUA_CallAction(A_JETGTHINK, actor))
 		return;
 
 	if (actor->z >= actor->waterbottom && actor->watertop > actor->floorz
@@ -5773,7 +5782,7 @@ void A_JetgThink(mobj_t *actor)
 //
 void A_MouseThink(mobj_t *actor)
 {
-	if (LUA_CallAction("A_MouseThink", actor))
+	if (LUA_CallAction(A_MOUSETHINK, actor))
 		return;
 
 	if (actor->reactiontime)
@@ -5810,7 +5819,7 @@ void A_DetonChase(mobj_t *actor)
 	angle_t exact;
 	fixed_t xydist, dist;
 
-	if (LUA_CallAction("A_DetonChase", actor))
+	if (LUA_CallAction(A_DETONCHASE, actor))
 		return;
 
 	// modify tracer threshold
@@ -5914,13 +5923,18 @@ void A_DetonChase(mobj_t *actor)
 
 	if (actor->reactiontime == -42)
 	{
-		fixed_t xyspeed;
+		fixed_t xyspeed, speed;
+
+		if (actor->target->player)
+			speed = actor->target->player->normalspeed;
+		else
+			speed = actor->target->info->speed;
 
 		actor->reactiontime = -42;
 
 		exact = actor->movedir>>ANGLETOFINESHIFT;
-		xyspeed = FixedMul(FixedMul(actor->tracer->player->normalspeed,3*FRACUNIT/4), FINECOSINE(exact));
-		actor->momz = FixedMul(FixedMul(actor->tracer->player->normalspeed,3*FRACUNIT/4), FINESINE(exact));
+		xyspeed = FixedMul(FixedMul(speed,3*FRACUNIT/4), FINECOSINE(exact));
+		actor->momz = FixedMul(FixedMul(speed,3*FRACUNIT/4), FINESINE(exact));
 
 		exact = actor->angle>>ANGLETOFINESHIFT;
 		actor->momx = FixedMul(xyspeed, FINECOSINE(exact));
@@ -5957,7 +5971,7 @@ void A_CapeChase(mobj_t *actor)
 	INT32 locvar2 = var2;
 	angle_t angle;
 
-	if (LUA_CallAction("A_CapeChase", actor))
+	if (LUA_CallAction(A_CAPECHASE, actor))
 		return;
 
 	CONS_Debug(DBG_GAMELOGIC, "A_CapeChase called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
@@ -6017,7 +6031,7 @@ void A_RotateSpikeBall(mobj_t *actor)
 	INT32 locvar1 = var1;
 	const fixed_t radius = FixedMul(12*actor->info->speed, actor->scale);
 
-	if (LUA_CallAction("A_RotateSpikeBall", actor))
+	if (LUA_CallAction(A_ROTATESPIKEBALL, actor))
 		return;
 
 	if (!((!locvar1 && (actor->target)) || (locvar1 && (actor->tracer))))// This should NEVER happen.
@@ -6068,7 +6082,7 @@ void A_UnidusBall(mobj_t *actor)
 	INT32 locvar1 = var1;
 	boolean canthrow = false;
 
-	if (LUA_CallAction("A_UnidusBall", actor))
+	if (LUA_CallAction(A_UNIDUSBALL, actor))
 		return;
 
 	actor->angle += ANGLE_11hh;
@@ -6160,12 +6174,12 @@ void A_RockSpawn(mobj_t *actor)
 {
 	mobj_t *mo;
 	mobjtype_t type;
-	INT32 i = P_FindSpecialLineFromTag(12, (INT16)actor->threshold, -1);
+	INT32 i = Tag_FindLineSpecial(12, (INT16)actor->threshold);
 	line_t *line;
 	fixed_t dist;
 	fixed_t randomoomph;
 
-	if (LUA_CallAction("A_RockSpawn", actor))
+	if (LUA_CallAction(A_ROCKSPAWN, actor))
 		return;
 
 	if (i == -1)
@@ -6218,7 +6232,7 @@ void A_SlingAppear(mobj_t *actor)
 	UINT8 mlength = 4;
 	mobj_t *spawnee, *hprev;
 
-	if (LUA_CallAction("A_SlingAppear", actor))
+	if (LUA_CallAction(A_SLINGAPPEAR, actor))
 		return;
 
 	P_UnsetThingPosition(actor);
@@ -6268,7 +6282,7 @@ void A_SetFuse(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SetFuse", actor))
+	if (LUA_CallAction(A_SETFUSE, actor))
 		return;
 
 	if ((!actor->fuse || (locvar2 >> 16)) && (locvar2 >> 16) != 2) // set the actor's fuse value
@@ -6297,7 +6311,7 @@ void A_CrawlaCommanderThink(mobj_t *actor)
 	INT32 locvar2 = var2;
 	boolean hovermode = (actor->health > 1 || actor->fuse);
 
-	if (LUA_CallAction("A_CrawlaCommanderThink", actor))
+	if (LUA_CallAction(A_CRAWLACOMMANDERTHINK, actor))
 		return;
 
 	if (actor->z >= actor->waterbottom && actor->watertop > actor->floorz
@@ -6455,7 +6469,7 @@ void A_RingExplode(mobj_t *actor)
 	thinker_t *th;
 	angle_t d;
 
-	if (LUA_CallAction("A_RingExplode", actor))
+	if (LUA_CallAction(A_RINGEXPLODE, actor))
 		return;
 
 	for (d = 0; d < 16; d++)
@@ -6500,7 +6514,7 @@ void A_OldRingExplode(mobj_t *actor) {
 	INT32 locvar1 = var1;
 	boolean changecolor = (actor->target && actor->target->player);
 
-	if (LUA_CallAction("A_OldRingExplode", actor))
+	if (LUA_CallAction(A_OLDRINGEXPLODE, actor))
 		return;
 
 	for (i = 0; i < 32; i++)
@@ -6576,7 +6590,7 @@ void A_MixUp(mobj_t *actor)
 	boolean teleported[MAXPLAYERS];
 	INT32 i, numplayers = 0, prandom = 0;
 
-	if (LUA_CallAction("A_MixUp", actor))
+	if (LUA_CallAction(A_MIXUP, actor))
 		return;
 
 	if (!multiplayer)
@@ -6851,7 +6865,7 @@ void A_RecyclePowers(mobj_t *actor)
 	INT32 weapons[MAXPLAYERS];
 	INT32 weaponheld[MAXPLAYERS];
 
-	if (LUA_CallAction("A_RecyclePowers", actor))
+	if (LUA_CallAction(A_RECYCLEPOWERS, actor))
 		return;
 
 	if (!multiplayer)
@@ -6982,7 +6996,7 @@ void A_Boss1Chase(mobj_t *actor)
 {
 	INT32 delta;
 
-	if (LUA_CallAction("A_Boss1Chase", actor))
+	if (LUA_CallAction(A_BOSS1CHASE, actor))
 		return;
 
 	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
@@ -7105,7 +7119,7 @@ void A_Boss2Chase(mobj_t *actor)
 	boolean reverse = false;
 	INT32 speedvar;
 
-	if (LUA_CallAction("A_Boss2Chase", actor))
+	if (LUA_CallAction(A_BOSS2CHASE, actor))
 		return;
 
 	if (actor->health <= 0)
@@ -7231,7 +7245,7 @@ void A_Boss2Chase(mobj_t *actor)
 //
 void A_Boss2Pogo(mobj_t *actor)
 {
-	if (LUA_CallAction("A_Boss2Pogo", actor))
+	if (LUA_CallAction(A_BOSS2POGO, actor))
 		return;
 
 	if (actor->z <= actor->floorz + FixedMul(8*FRACUNIT, actor->scale) && actor->momz <= 0)
@@ -7279,7 +7293,7 @@ void A_Boss2TakeDamage(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_Boss2TakeDamage", actor))
+	if (LUA_CallAction(A_BOSS2TAKEDAMAGE, actor))
 		return;
 
 	A_Pain(actor);
@@ -7302,7 +7316,7 @@ void A_Boss7Chase(mobj_t *actor)
 	INT32 delta;
 	INT32 i;
 
-	if (LUA_CallAction("A_Boss7Chase", actor))
+	if (LUA_CallAction(A_BOSS7CHASE, actor))
 		return;
 
 	if (actor->z != actor->floorz)
@@ -7432,7 +7446,7 @@ void A_Boss7Chase(mobj_t *actor)
 //
 void A_GoopSplat(mobj_t *actor)
 {
-	if (LUA_CallAction("A_GoopSplat", actor))
+	if (LUA_CallAction(A_GOOPSPLAT, actor))
 		return;
 
 	P_UnsetThingPosition(actor);
@@ -7457,7 +7471,7 @@ void A_Boss2PogoSFX(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Boss2PogoSFX", actor))
+	if (LUA_CallAction(A_BOSS2POGOSFX, actor))
 		return;
 
 	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
@@ -7499,7 +7513,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Boss2PogoTarget", actor))
+	if (LUA_CallAction(A_BOSS2POGOTARGET, actor))
 		return;
 
 	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE) || (actor->target->player && actor->target->player->powers[pw_flashing])
@@ -7515,7 +7529,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	}
 
 	// Target hit, retreat!
-	if (actor->target->player->powers[pw_flashing] > TICRATE || actor->flags2 & MF2_FRET)
+	if ((actor->target->player && actor->target->player->powers[pw_flashing] > TICRATE) || actor->flags2 & MF2_FRET)
 	{
 		UINT8 prandom = P_RandomByte();
 		actor->z++; // unstick from the floor
@@ -7526,7 +7540,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	// Try to land on top of the player.
 	else if (P_AproxDistance(actor->x-actor->target->x, actor->y-actor->target->y) < FixedMul(512*FRACUNIT, actor->scale))
 	{
-		fixed_t airtime, gravityadd, zoffs;
+		fixed_t airtime, gravityadd, zoffs, height;
 
 		// check gravity in the sector (for later math)
 		P_CheckGravity(actor, true);
@@ -7548,7 +7562,13 @@ void A_Boss2PogoTarget(mobj_t *actor)
 		// Remember, kids!
 		// Reduced down Calculus lets you avoid bad 'logic math' loops!
 		//airtime = FixedDiv(-actor->momz<<1, gravityadd)<<1; // going from 0 to 0 is much simpler
-		zoffs = (P_GetPlayerHeight(actor->target->player)>>1) + (actor->target->floorz - actor->floorz); // offset by the difference in floor height plus half the player height,
+
+		if (actor->target->player)
+			height = P_GetPlayerHeight(actor->target->player) >> 1;
+		else
+			height = actor->target->height >> 1;
+
+		zoffs = height + (actor->target->floorz - actor->floorz); // offset by the difference in floor height plus half the player height,
 		airtime = FixedDiv((-actor->momz - FixedSqrt(FixedMul(actor->momz,actor->momz)+zoffs)), gravityadd)<<1; // to try and land on their head rather than on their feet
 
 		actor->angle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
@@ -7584,7 +7604,7 @@ void A_Boss2PogoTarget(mobj_t *actor)
 //
 void A_EggmanBox(mobj_t *actor)
 {
-	if (LUA_CallAction("A_EggmanBox", actor))
+	if (LUA_CallAction(A_EGGMANBOX, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -7610,7 +7630,7 @@ void A_TurretFire(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_TurretFire", actor))
+	if (LUA_CallAction(A_TURRETFIRE, actor))
 		return;
 
 	if (locvar2)
@@ -7648,7 +7668,7 @@ void A_SuperTurretFire(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SuperTurretFire", actor))
+	if (LUA_CallAction(A_SUPERTURRETFIRE, actor))
 		return;
 
 	if (locvar2)
@@ -7684,7 +7704,7 @@ void A_TurretStop(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_TurretStop", actor))
+	if (LUA_CallAction(A_TURRETSTOP, actor))
 		return;
 
 	actor->flags2 &= ~MF2_FIRING;
@@ -7703,7 +7723,7 @@ void A_TurretStop(mobj_t *actor)
 //
 void A_SparkFollow(mobj_t *actor)
 {
-	if (LUA_CallAction("A_SparkFollow", actor))
+	if (LUA_CallAction(A_SPARKFOLLOW, actor))
 		return;
 
 	if ((!actor->target || (actor->target->health <= 0))
@@ -7739,7 +7759,7 @@ void A_BuzzFly(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_BuzzFly", actor))
+	if (LUA_CallAction(A_BUZZFLY, actor))
 		return;
 
 	if (actor->flags2 & MF2_AMBUSH)
@@ -7838,7 +7858,7 @@ void A_GuardChase(mobj_t *actor)
 {
 	INT32 delta;
 
-	if (LUA_CallAction("A_GuardChase", actor))
+	if (LUA_CallAction(A_GUARDCHASE, actor))
 		return;
 
 	if (actor->reactiontime)
@@ -7943,7 +7963,7 @@ void A_EggShield(mobj_t *actor)
 	fixed_t movex, movey;
 	angle_t angle;
 
-	if (LUA_CallAction("A_EggShield", actor))
+	if (LUA_CallAction(A_EGGSHIELD, actor))
 		return;
 
 	if (!actor->target || !actor->target->health)
@@ -8025,7 +8045,7 @@ void A_EggShield(mobj_t *actor)
 //
 void A_SetReactionTime(mobj_t *actor)
 {
-	if (LUA_CallAction("A_SetReactionTime", actor))
+	if (LUA_CallAction(A_SETREACTIONTIME, actor))
 		return;
 
 	if (var1)
@@ -8047,7 +8067,7 @@ void A_Boss1Spikeballs(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *ball;
 
-	if (LUA_CallAction("A_Boss1Spikeballs", actor))
+	if (LUA_CallAction(A_BOSS1SPIKEBALLS, actor))
 		return;
 
 	ball = P_SpawnMobj(actor->x, actor->y, actor->z, MT_EGGMOBILE_BALL);
@@ -8069,7 +8089,7 @@ void A_Boss1Spikeballs(mobj_t *actor)
 //
 void A_Boss3TakeDamage(mobj_t *actor)
 {
-	if (LUA_CallAction("A_Boss3TakeDamage", actor))
+	if (LUA_CallAction(A_BOSS3TAKEDAMAGE, actor))
 		return;
 
 	actor->movecount = var1;
@@ -8089,7 +8109,7 @@ void A_Boss3TakeDamage(mobj_t *actor)
 //
 void A_Boss3Path(mobj_t *actor)
 {
-	if (LUA_CallAction("A_Boss3Path", actor))
+	if (LUA_CallAction(A_BOSS3PATH, actor))
 		return;
 
 	if (actor->tracer && actor->tracer->health && actor->tracer->movecount)
@@ -8216,7 +8236,7 @@ void A_Boss3Path(mobj_t *actor)
 //
 void A_Boss3ShockThink(mobj_t *actor)
 {
-	if (LUA_CallAction("A_Boss3ShockThink", actor))
+	if (LUA_CallAction(A_BOSS3SHOCKTHINK, actor))
 		return;
 
 	if (actor->momx || actor->momy)
@@ -8269,7 +8289,7 @@ void A_LinedefExecute(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_LinedefExecute", actor))
+	if (LUA_CallAction(A_LINEDEFEXECUTE, actor))
 		return;
 
 	tagnum = locvar1;
@@ -8295,7 +8315,7 @@ void A_LinedefExecute(mobj_t *actor)
 //
 void A_PlaySeeSound(mobj_t *actor)
 {
-	if (LUA_CallAction("A_PlaySeeSound", actor))
+	if (LUA_CallAction(A_PLAYSEESOUND, actor))
 		return;
 
 	if (actor->info->seesound)
@@ -8311,7 +8331,7 @@ void A_PlaySeeSound(mobj_t *actor)
 //
 void A_PlayAttackSound(mobj_t *actor)
 {
-	if (LUA_CallAction("A_PlayAttackSound", actor))
+	if (LUA_CallAction(A_PLAYATTACKSOUND, actor))
 		return;
 
 	if (actor->info->attacksound)
@@ -8327,7 +8347,7 @@ void A_PlayAttackSound(mobj_t *actor)
 //
 void A_PlayActiveSound(mobj_t *actor)
 {
-	if (LUA_CallAction("A_PlayActiveSound", actor))
+	if (LUA_CallAction(A_PLAYACTIVESOUND, actor))
 		return;
 
 	if (actor->info->activesound)
@@ -8346,7 +8366,7 @@ void A_SmokeTrailer(mobj_t *actor)
 	mobj_t *th;
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_SmokeTrailer", actor))
+	if (LUA_CallAction(A_SMOKETRAILER, actor))
 		return;
 
 	if (leveltime % 4)
@@ -8387,7 +8407,7 @@ void A_SpawnObjectAbsolute(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SpawnObjectAbsolute", actor))
+	if (LUA_CallAction(A_SPAWNOBJECTABSOLUTE, actor))
 		return;
 
 	x = (INT16)(locvar1>>16);
@@ -8423,7 +8443,7 @@ void A_SpawnObjectRelative(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SpawnObjectRelative", actor))
+	if (LUA_CallAction(A_SPAWNOBJECTRELATIVE, actor))
 		return;
 
 	CONS_Debug(DBG_GAMELOGIC, "A_SpawnObjectRelative called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
@@ -8467,7 +8487,7 @@ void A_ChangeAngleRelative(mobj_t *actor)
 	//const angle_t amin = FixedAngle(locvar1*FRACUNIT);
 	//const angle_t amax = FixedAngle(locvar2*FRACUNIT);
 
-	if (LUA_CallAction("A_ChangeAngleRelative", actor))
+	if (LUA_CallAction(A_CHANGEANGLERELATIVE, actor))
 		return;
 
 #ifdef PARANOIA
@@ -8500,7 +8520,7 @@ void A_ChangeAngleAbsolute(mobj_t *actor)
 	//const angle_t amin = FixedAngle(locvar1*FRACUNIT);
 	//const angle_t amax = FixedAngle(locvar2*FRACUNIT);
 
-	if (LUA_CallAction("A_ChangeAngleAbsolute", actor))
+	if (LUA_CallAction(A_CHANGEANGLEABSOLUTE, actor))
 		return;
 
 #ifdef PARANOIA
@@ -8529,7 +8549,7 @@ void A_RollAngle(mobj_t *actor)
 	INT32 locvar2 = var2;
 	const angle_t angle = FixedAngle(locvar1*FRACUNIT);
 
-	if (LUA_CallAction("A_RollAngle", actor))
+	if (LUA_CallAction(A_ROLLANGLE, actor))
 		return;
 
 	// relative (default)
@@ -8554,7 +8574,7 @@ void A_ChangeRollAngleRelative(mobj_t *actor)
 	const fixed_t amin = locvar1*FRACUNIT;
 	const fixed_t amax = locvar2*FRACUNIT;
 
-	if (LUA_CallAction("A_ChangeRollAngleRelative", actor))
+	if (LUA_CallAction(A_CHANGEROLLANGLERELATIVE, actor))
 		return;
 
 #ifdef PARANOIA
@@ -8579,7 +8599,7 @@ void A_ChangeRollAngleAbsolute(mobj_t *actor)
 	const fixed_t amin = locvar1*FRACUNIT;
 	const fixed_t amax = locvar2*FRACUNIT;
 
-	if (LUA_CallAction("A_ChangeRollAngleAbsolute", actor))
+	if (LUA_CallAction(A_CHANGEROLLANGLEABSOLUTE, actor))
 		return;
 
 #ifdef PARANOIA
@@ -8604,7 +8624,7 @@ void A_PlaySound(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_PlaySound", actor))
+	if (LUA_CallAction(A_PLAYSOUND, actor))
 		return;
 
 	if (leveltime < 2 && (locvar2 >> 16))
@@ -8629,7 +8649,7 @@ void A_FindTarget(mobj_t *actor)
 	mobj_t *mo2;
 	fixed_t dist1 = 0, dist2 = 0;
 
-	if (LUA_CallAction("A_FindTarget", actor))
+	if (LUA_CallAction(A_FINDTARGET, actor))
 		return;
 
 	CONS_Debug(DBG_GAMELOGIC, "A_FindTarget called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
@@ -8693,7 +8713,7 @@ void A_FindTracer(mobj_t *actor)
 	mobj_t *mo2;
 	fixed_t dist1 = 0, dist2 = 0;
 
-	if (LUA_CallAction("A_FindTracer", actor))
+	if (LUA_CallAction(A_FINDTRACER, actor))
 		return;
 
 	CONS_Debug(DBG_GAMELOGIC, "A_FindTracer called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
@@ -8753,7 +8773,7 @@ void A_SetTics(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SetTics", actor))
+	if (LUA_CallAction(A_SETTICS, actor))
 		return;
 
 	if (locvar1)
@@ -8774,7 +8794,7 @@ void A_SetRandomTics(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SetRandomTics", actor))
+	if (LUA_CallAction(A_SETRANDOMTICS, actor))
 		return;
 
 	actor->tics = P_RandomRange(locvar1, locvar2);
@@ -8792,7 +8812,7 @@ void A_ChangeColorRelative(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_ChangeColorRelative", actor))
+	if (LUA_CallAction(A_CHANGECOLORRELATIVE, actor))
 		return;
 
 	if (locvar1)
@@ -8817,7 +8837,7 @@ void A_ChangeColorAbsolute(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_ChangeColorAbsolute", actor))
+	if (LUA_CallAction(A_CHANGECOLORABSOLUTE, actor))
 		return;
 
 	if (locvar1)
@@ -8842,25 +8862,26 @@ void A_Dye(mobj_t *actor)
 	INT32 locvar2 = var2;
 
 	mobj_t *target = ((locvar1 && actor->target) ? actor->target : actor);
-	UINT8 color = (UINT8)locvar2;
-	if (LUA_CallAction("A_Dye", actor))
+	UINT16 color = (UINT16)locvar2;
+	if (LUA_CallAction(A_DYE, actor))
 		return;
 	if (color >= numskincolors)
 		return;
 
-	if (!color)
-		target->colorized = false;
-	else
-		target->colorized = true;
-
 	// What if it's a player?
 	if (target->player)
-	{
 		target->player->powers[pw_dye] = color;
-		return;
-	}
 
-	target->color = color;
+	if (!color)
+	{
+		target->colorized = false;
+		target->color = target->player ? target->player->skincolor : SKINCOLOR_NONE;
+	}
+	else if (!(target->player))
+	{
+		target->colorized = true;
+		target->color = color;
+	}
 }
 
 // Function: A_MoveRelative
@@ -8875,7 +8896,7 @@ void A_MoveRelative(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_MoveRelative", actor))
+	if (LUA_CallAction(A_MOVERELATIVE, actor))
 		return;
 
 	P_Thrust(actor, actor->angle+FixedAngle(locvar1*FRACUNIT), FixedMul(locvar2*FRACUNIT, actor->scale));
@@ -8893,7 +8914,7 @@ void A_MoveAbsolute(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_MoveAbsolute", actor))
+	if (LUA_CallAction(A_MOVEABSOLUTE, actor))
 		return;
 
 	P_InstaThrust(actor, FixedAngle(locvar1*FRACUNIT), FixedMul(locvar2*FRACUNIT, actor->scale));
@@ -8911,7 +8932,7 @@ void A_Thrust(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Thrust", actor))
+	if (LUA_CallAction(A_THRUST, actor))
 		return;
 
 	if (!locvar1)
@@ -8937,7 +8958,7 @@ void A_ZThrust(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_ZThrust", actor))
+	if (LUA_CallAction(A_ZTHRUST, actor))
 		return;
 
 	if (!locvar1)
@@ -8971,7 +8992,7 @@ void A_SetTargetsTarget(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *oldtarg = NULL, *newtarg = NULL;
 
-	if (LUA_CallAction("A_SetTargetsTarget", actor))
+	if (LUA_CallAction(A_SETTARGETSTARGET, actor))
 		return;
 
 	// actor's target
@@ -9015,7 +9036,7 @@ void A_SetObjectFlags(mobj_t *actor)
 	INT32 locvar2 = var2;
 	boolean unlinkthings = false;
 
-	if (LUA_CallAction("A_SetObjectFlags", actor))
+	if (LUA_CallAction(A_SETOBJECTFLAGS, actor))
 		return;
 
 	if (locvar2 == 2)
@@ -9056,7 +9077,7 @@ void A_SetObjectFlags2(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SetObjectFlags2", actor))
+	if (LUA_CallAction(A_SETOBJECTFLAGS2, actor))
 		return;
 
 	if (locvar2 == 2)
@@ -9083,7 +9104,7 @@ void A_BossJetFume(mobj_t *actor)
 	mobj_t *filler;
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_BossJetFume", actor))
+	if (LUA_CallAction(A_BOSSJETFUME, actor))
 		return;
 
 	if (locvar1 == 0) // Boss1 jet fumes
@@ -9218,7 +9239,7 @@ void A_RandomState(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_RandomState", actor))
+	if (LUA_CallAction(A_RANDOMSTATE, actor))
 		return;
 
 	P_SetMobjState(actor, P_RandomChance(FRACUNIT/2) ? locvar1 : locvar2);
@@ -9236,7 +9257,7 @@ void A_RandomStateRange(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_RandomStateRange", actor))
+	if (LUA_CallAction(A_RANDOMSTATERANGE, actor))
 		return;
 
 	P_SetMobjState(actor, P_RandomRange(locvar1, locvar2));
@@ -9254,7 +9275,7 @@ void A_DualAction(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_DualAction", actor))
+	if (LUA_CallAction(A_DUALACTION, actor))
 		return;
 
 	CONS_Debug(DBG_GAMELOGIC, "A_DualAction called from object type %d, var1: %d, var2: %d\n", actor->type, locvar1, locvar2);
@@ -9287,7 +9308,7 @@ void A_RemoteAction(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *originaltarget = actor->target; // Hold on to the target for later.
 
-	if (LUA_CallAction("A_RemoteAction", actor))
+	if (LUA_CallAction(A_REMOTEACTION, actor))
 		return;
 
 	// If >=0, find the closest target.
@@ -9369,7 +9390,7 @@ void A_RemoteAction(mobj_t *actor)
 //
 void A_ToggleFlameJet(mobj_t* actor)
 {
-	if (LUA_CallAction("A_ToggleFlameJet", actor))
+	if (LUA_CallAction(A_TOGGLEFLAMEJET, actor))
 		return;
 
 	// threshold - off delay
@@ -9416,7 +9437,7 @@ void A_OrbitNights(mobj_t* actor)
 	boolean donotrescale = (var2 & 0x40000);
 	INT32 xfactor = 32, yfactor = 32, zfactor = 20;
 
-	if (LUA_CallAction("A_OrbitNights", actor))
+	if (LUA_CallAction(A_ORBITNIGHTS, actor))
 		return;
 
 	if (actor->flags & MF_GRENADEBOUNCE)
@@ -9489,7 +9510,7 @@ void A_GhostMe(mobj_t *actor)
 	INT32 locvar1 = var1;
 	mobj_t *ghost;
 
-	if (LUA_CallAction("A_GhostMe", actor))
+	if (LUA_CallAction(A_GHOSTME, actor))
 		return;
 
 	ghost = P_SpawnGhostMobj(actor);
@@ -9512,7 +9533,7 @@ void A_SetObjectState(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *target;
 
-	if (LUA_CallAction("A_SetObjectState", actor))
+	if (LUA_CallAction(A_SETOBJECTSTATE, actor))
 		return;
 
 	if ((!locvar2 && !actor->target) || (locvar2 && !actor->tracer))
@@ -9556,7 +9577,7 @@ void A_SetObjectTypeState(mobj_t *actor)
 	mobj_t *mo2;
 	fixed_t dist = 0;
 
-	if (LUA_CallAction("A_SetObjectTypeState", actor))
+	if (LUA_CallAction(A_SETOBJECTTYPESTATE, actor))
 		return;
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -9598,7 +9619,7 @@ void A_KnockBack(mobj_t *actor)
 	INT32 locvar1 = var1;
 	mobj_t *target;
 
-	if (LUA_CallAction("A_KnockBack", actor))
+	if (LUA_CallAction(A_KNOCKBACK, actor))
 		return;
 
 	if (!locvar1)
@@ -9633,7 +9654,7 @@ void A_PushAway(mobj_t *actor)
 	mobj_t *target; // target
 	angle_t an; // actor to target angle
 
-	if (LUA_CallAction("A_PushAway", actor))
+	if (LUA_CallAction(A_PUSHAWAY, actor))
 		return;
 
 	if ((!(locvar2 >> 16) && !actor->target) || ((locvar2 >> 16) && !actor->tracer))
@@ -9667,7 +9688,7 @@ void A_RingDrain(mobj_t *actor)
 	INT32 locvar1 = var1;
 	player_t *player;
 
-	if (LUA_CallAction("A_RingDrain", actor))
+	if (LUA_CallAction(A_RINGDRAIN, actor))
 		return;
 
 	if (!actor->target || !actor->target->player)
@@ -9699,7 +9720,7 @@ void A_SplitShot(mobj_t *actor)
 	const fixed_t offs = (fixed_t)(locvar1*FRACUNIT);
 	const fixed_t hoffs = (fixed_t)(loc2up*FRACUNIT);
 
-	if (LUA_CallAction("A_SplitShot", actor))
+	if (LUA_CallAction(A_SPLITSHOT, actor))
 		return;
 
 	if (!actor->target)
@@ -9736,7 +9757,7 @@ void A_MissileSplit(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_MissileSplit", actor))
+	if (LUA_CallAction(A_MISSILESPLIT, actor))
 		return;
 
 	if (actor->eflags & MFE_VERTICALFLIP)
@@ -9764,7 +9785,7 @@ void A_MultiShot(mobj_t *actor)
 	INT32 count = 0;
 	fixed_t ad;
 
-	if (LUA_CallAction("A_MultiShot", actor))
+	if (LUA_CallAction(A_MULTISHOT, actor))
 		return;
 
 	if (actor->target)
@@ -9825,7 +9846,7 @@ void A_InstaLoop(mobj_t *actor)
 	const fixed_t ac = FINECOSINE(fa);
 	const fixed_t as = FINESINE(fa);
 
-	if (LUA_CallAction("A_InstaLoop", actor))
+	if (LUA_CallAction(A_INSTALOOP, actor))
 		return;
 
 	P_InstaThrust(actor, actor->angle, FixedMul(ac, FixedMul(force, actor->scale)));
@@ -9858,7 +9879,7 @@ void A_Custom3DRotate(mobj_t *actor)
 	const fixed_t hspeed = FixedMul(loc2up*FRACUNIT/10, actor->scale);
 	const fixed_t vspeed = FixedMul(loc2lw*FRACUNIT/10, actor->scale);
 
-	if (LUA_CallAction("A_Custom3DRotate", actor))
+	if (LUA_CallAction(A_CUSTOM3DROTATE, actor))
 		return;
 
 	if (actor->target->health == 0)
@@ -9917,7 +9938,7 @@ void A_SearchForPlayers(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SearchForPlayers", actor))
+	if (LUA_CallAction(A_SEARCHFORPLAYERS, actor))
 		return;
 
 	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
@@ -9949,7 +9970,7 @@ void A_CheckRandom(mobj_t *actor)
 	INT32 locvar2 = var2;
 	fixed_t chance = FRACUNIT;
 
-	if (LUA_CallAction("A_CheckRandom", actor))
+	if (LUA_CallAction(A_CHECKRANDOM, actor))
 		return;
 
 	if ((locvar1 & 0xFFFF) == 0)
@@ -9976,7 +9997,7 @@ void A_CheckTargetRings(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_CheckTargetRings", actor))
+	if (LUA_CallAction(A_CHECKTARGETRINGS, actor))
 		return;
 
 	if (!(actor->target) || !(actor->target->player))
@@ -9999,7 +10020,7 @@ void A_CheckRings(mobj_t *actor)
 	INT32 locvar2 = var2;
 	INT32 i, cntr = 0;
 
-	if (LUA_CallAction("A_CheckRings", actor))
+	if (LUA_CallAction(A_CHECKRINGS, actor))
 		return;
 
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -10023,7 +10044,7 @@ void A_CheckTotalRings(mobj_t *actor)
 
 	INT32 i, cntr = 0;
 
-	if (LUA_CallAction("A_CheckTotalRings", actor))
+	if (LUA_CallAction(A_CHECKTOTALRINGS, actor))
 		return;
 
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -10045,7 +10066,7 @@ void A_CheckHealth(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_CheckHealth", actor))
+	if (LUA_CallAction(A_CHECKHEALTH, actor))
 		return;
 
 	if (actor->health <= locvar1)
@@ -10067,7 +10088,7 @@ void A_CheckRange(mobj_t *actor)
 	INT32 locvar2 = var2;
 	fixed_t dist;
 
-	if (LUA_CallAction("A_CheckRange", actor))
+	if (LUA_CallAction(A_CHECKRANGE, actor))
 		return;
 
 	if ((!(locvar1 >> 16) && !actor->target) || ((locvar1 >> 16) && !actor->tracer))
@@ -10097,7 +10118,7 @@ void A_CheckHeight(mobj_t *actor)
 	INT32 locvar2 = var2;
 	fixed_t height;
 
-	if (LUA_CallAction("A_CheckHeight", actor))
+	if (LUA_CallAction(A_CHECKHEIGHT, actor))
 		return;
 
 	if ((!(locvar1 >> 16) && !actor->target) || ((locvar1 >> 16) && !actor->tracer))
@@ -10129,7 +10150,7 @@ void A_CheckTrueRange(mobj_t *actor)
 	fixed_t dist; // horizontal range
 	fixed_t l; // true range
 
-	if (LUA_CallAction("A_CheckTrueRange", actor))
+	if (LUA_CallAction(A_CHECKTRUERANGE, actor))
 		return;
 
 	if ((!(locvar1 >> 16) && !actor->target) || ((locvar1 >> 16) && !actor->tracer))
@@ -10180,7 +10201,7 @@ void A_CheckThingCount(mobj_t *actor)
 	mobj_t *mo2;
 	fixed_t dist = 0;
 
-	if (LUA_CallAction("A_CheckThingCount", actor))
+	if (LUA_CallAction(A_CHECKTHINGCOUNT, actor))
 		return;
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -10225,7 +10246,7 @@ void A_CheckAmbush(mobj_t *actor)
 	angle_t atp; // actor to target angle
 	angle_t an; // angle between at and atp
 
-	if (LUA_CallAction("A_CheckAmbush", actor))
+	if (LUA_CallAction(A_CHECKAMBUSH, actor))
 		return;
 
 	if ((!locvar1 && !actor->target) || (locvar1 && !actor->tracer))
@@ -10263,7 +10284,7 @@ void A_CheckCustomValue(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_CheckCustomValue", actor))
+	if (LUA_CallAction(A_CHECKCUSTOMVALUE, actor))
 		return;
 
 	if (actor->cusval >= locvar1)
@@ -10282,7 +10303,7 @@ void A_CheckCusValMemo(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_CheckCusValMemo", actor))
+	if (LUA_CallAction(A_CHECKCUSVALMEMO, actor))
 		return;
 
 	if (actor->cvmem >= locvar1)
@@ -10307,7 +10328,7 @@ void A_SetCustomValue(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SetCustomValue", actor))
+	if (LUA_CallAction(A_SETCUSTOMVALUE, actor))
 		return;
 
 	if (cv_debug)
@@ -10357,7 +10378,7 @@ void A_UseCusValMemo(mobj_t *actor)
 	INT32 temp = actor->cusval; // value being manipulated
 	INT32 tempM = actor->cvmem; // value used to manipulate temp with
 
-	if (LUA_CallAction("A_UseCusValMemo", actor))
+	if (LUA_CallAction(A_USECUSVALMEMO, actor))
 		return;
 
 	if (locvar1 == 1) // cvmem being changed using cusval
@@ -10420,7 +10441,7 @@ void A_RelayCustomValue(mobj_t *actor)
 	INT32 temp; // reference value - var1 lower 16 bits changes this
 	INT32 tempT; // target's value - changed to tracer if var1 upper 16 bits set, then modified to become final value
 
-	if (LUA_CallAction("A_RelayCustomValue", actor))
+	if (LUA_CallAction(A_RELAYCUSTOMVALUE, actor))
 		return;
 
 	if ((!(locvar1 >> 16) && !actor->target) || ((locvar1 >> 16) && !actor->tracer))
@@ -10479,7 +10500,7 @@ void A_CusValAction(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_CusValAction", actor))
+	if (LUA_CallAction(A_CUSVALACTION, actor))
 		return;
 
 	if (locvar2 == 5)
@@ -10530,7 +10551,7 @@ void A_ForceStop(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_ForceStop", actor))
+	if (LUA_CallAction(A_FORCESTOP, actor))
 		return;
 
 	actor->momx = actor->momy = 0;
@@ -10549,7 +10570,7 @@ void A_ForceWin(mobj_t *actor)
 {
 	INT32 i;
 
-	if (LUA_CallAction("A_ForceWin", actor))
+	if (LUA_CallAction(A_FORCEWIN, actor))
 		return;
 
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -10583,7 +10604,7 @@ void A_SpikeRetract(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_SpikeRetract", actor))
+	if (LUA_CallAction(A_SPIKERETRACT, actor))
 		return;
 
 	if (actor->flags & MF_NOBLOCKMAP)
@@ -10667,7 +10688,7 @@ void A_Repeat(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Repeat", actor))
+	if (LUA_CallAction(A_REPEAT, actor))
 		return;
 
 	if (locvar1 && (!actor->extravalue2 || actor->extravalue2 > locvar1))
@@ -10692,7 +10713,7 @@ void A_SetScale(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *target;
 
-	if (LUA_CallAction("A_SetScale", actor))
+	if (LUA_CallAction(A_SETSCALE, actor))
 		return;
 
 	if (locvar1 <= 0)
@@ -10735,7 +10756,7 @@ void A_RemoteDamage(mobj_t *actor)
 	mobj_t *target; // we MUST have a target
 	mobj_t *source = NULL; // on the other hand we don't necessarily need a source
 
-	if (LUA_CallAction("A_RemoteDamage", actor))
+	if (LUA_CallAction(A_REMOTEDAMAGE, actor))
 		return;
 
 	if (locvar1 == 1)
@@ -10788,7 +10809,7 @@ void A_HomingChase(mobj_t *actor)
 	fixed_t dist;
 	fixed_t speedmul;
 
-	if (LUA_CallAction("A_HomingChase", actor))
+	if (LUA_CallAction(A_HOMINGCHASE, actor))
 		return;
 
 	if (locvar2 == 1)
@@ -10841,7 +10862,7 @@ void A_TrapShot(mobj_t *actor)
 	fixed_t x, y, z;
 	fixed_t speed;
 
-	if (LUA_CallAction("A_TrapShot", actor))
+	if (LUA_CallAction(A_TRAPSHOT, actor))
 		return;
 
 	x = actor->x + P_ReturnThrustX(actor, actor->angle, FixedMul(frontoff*FRACUNIT, actor->scale));
@@ -10906,7 +10927,7 @@ void A_VileTarget(mobj_t *actor)
 	mobjtype_t fogtype;
 	INT32 i;
 
-	if (LUA_CallAction("A_VileTarget", actor))
+	if (LUA_CallAction(A_VILETARGET, actor))
 		return;
 
 	if (!actor->target)
@@ -10993,7 +11014,7 @@ void A_VileAttack(mobj_t *actor)
 	mobj_t *fire;
 	INT32 i;
 
-	if (LUA_CallAction("A_VileAttack", actor))
+	if (LUA_CallAction(A_VILEATTACK, actor))
 		return;
 
 	if (!actor->target)
@@ -11105,7 +11126,7 @@ void A_VileFire(mobj_t *actor)
 	INT32 locvar2 = var2;
 	mobj_t *dest;
 
-	if (LUA_CallAction("A_VileFire", actor))
+	if (LUA_CallAction(A_VILEFIRE, actor))
 		return;
 
 	dest = actor->tracer;
@@ -11191,7 +11212,7 @@ void A_BrakChase(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_BrakChase", actor))
+	if (LUA_CallAction(A_BRAKCHASE, actor))
 		return;
 
 	// Set new tics NOW, in case the state changes while we're doing this and we try applying this to the painstate or something silly
@@ -11311,7 +11332,7 @@ void A_BrakFireShot(mobj_t *actor)
 	fixed_t x, y, z;
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_BrakFireShot", actor))
+	if (LUA_CallAction(A_BRAKFIRESHOT, actor))
 		return;
 
 	if (!actor->target)
@@ -11374,7 +11395,7 @@ void A_BrakLobShot(mobj_t *actor)
 	INT32 locvar2 = var2 & 0x0000FFFF;
 	INT32 aimDirect = var2 & 0xFFFF0000;
 
-	if (LUA_CallAction("A_BrakLobShot", actor))
+	if (LUA_CallAction(A_BRAKLOBSHOT, actor))
 		return;
 
 	if (!actor->target)
@@ -11480,7 +11501,7 @@ void A_NapalmScatter(mobj_t *actor)
 	INT32 i; // for-loop cursor
 	mobj_t *mo; // each and every spawned napalm burst
 
-	if (LUA_CallAction("A_NapalmScatter", actor))
+	if (LUA_CallAction(A_NAPALMSCATTER, actor))
 		return;
 
 	// Some quick sanity-checking
@@ -11532,7 +11553,7 @@ void A_SpawnFreshCopy(mobj_t *actor)
 {
 	mobj_t *newObject;
 
-	if (LUA_CallAction("A_SpawnFreshCopy", actor))
+	if (LUA_CallAction(A_SPAWNFRESHCOPY, actor))
 		return;
 
 	newObject = P_SpawnMobjFromMobj(actor, 0, 0, 0, actor->type);
@@ -11608,7 +11629,7 @@ void A_FlickySpawn(mobj_t *actor)
 	INT32 test = (var1 >> 16);
 	SINT8 moveforward = 0;
 
-	if (LUA_CallAction("A_FlickySpawn", actor))
+	if (LUA_CallAction(A_FLICKYSPAWN, actor))
 		return;
 
 	if (test & 1)
@@ -11676,7 +11697,7 @@ void A_FlickyCenter(mobj_t *actor)
 	UINT8 flickycolor = ((locvar1 >> 16) & 0xFF);
 	UINT8 flickyflags = ((locvar1 >> 20) & 0xF);
 
-	if (LUA_CallAction("A_FlickyCenter", actor))
+	if (LUA_CallAction(A_FLICKYCENTER, actor))
 		return;
 
 	if (!actor->tracer)
@@ -11796,7 +11817,7 @@ void A_FlickyAim(mobj_t *actor)
 	INT32 locvar2 = var2;
 	boolean flickyhitwall = false;
 
-	if (LUA_CallAction("A_FlickyAim", actor))
+	if (LUA_CallAction(A_FLICKYAIM, actor))
 		return;
 
 	if ((actor->momx == actor->momy && actor->momy == 0)
@@ -11896,7 +11917,7 @@ void A_FlickyFly(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FlickyFly", actor))
+	if (LUA_CallAction(A_FLICKYFLY, actor))
 		return;
 
 	P_InternalFlickyFly(actor, locvar1, locvar2,
@@ -11916,7 +11937,7 @@ void A_FlickySoar(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FlickySoar", actor))
+	if (LUA_CallAction(A_FLICKYSOAR, actor))
 		return;
 
 	P_InternalFlickyFly(actor, locvar1, locvar2,
@@ -11940,7 +11961,7 @@ void A_FlickyCoast(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FlickyCoast", actor))
+	if (LUA_CallAction(A_FLICKYCOAST, actor))
 		return;
 
 	if (actor->eflags & MFE_UNDERWATER)
@@ -11987,7 +12008,7 @@ void A_FlickyHop(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FlickyHop", actor))
+	if (LUA_CallAction(A_FLICKYHOP, actor))
 		return;
 
 	P_InternalFlickyHop(actor, locvar1, locvar2, actor->angle);
@@ -12006,7 +12027,7 @@ void A_FlickyFlounder(mobj_t *actor)
 	INT32 locvar2 = var2;
 	angle_t hopangle;
 
-	if (LUA_CallAction("A_FlickyFlounder", actor))
+	if (LUA_CallAction(A_FLICKYFLOUNDER, actor))
 		return;
 
 	locvar1 *= (P_RandomKey(2) + 1);
@@ -12028,7 +12049,7 @@ void A_FlickyCheck(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FlickyCheck", actor))
+	if (LUA_CallAction(A_FLICKYCHECK, actor))
 		return;
 
 	if (actor->target
@@ -12065,7 +12086,7 @@ void A_FlickyHeightCheck(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FlickyHeightCheck", actor))
+	if (LUA_CallAction(A_FLICKYHEIGHTCHECK, actor))
 		return;
 
 	if (actor->target
@@ -12100,7 +12121,7 @@ void A_FlickyFlutter(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FlickyFlutter", actor))
+	if (LUA_CallAction(A_FLICKYFLUTTER, actor))
 		return;
 
 	var1 = locvar1;
@@ -12131,7 +12152,7 @@ void A_FlameParticle(mobj_t *actor)
 	fixed_t rad, hei;
 	mobj_t *particle;
 
-	if (LUA_CallAction("A_FlameParticle", actor))
+	if (LUA_CallAction(A_FLAMEPARTICLE, actor))
 		return;
 
 	if (!type)
@@ -12159,7 +12180,7 @@ void A_FadeOverlay(mobj_t *actor)
 	mobj_t *fade;
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_FadeOverlay", actor))
+	if (LUA_CallAction(A_FADEOVERLAY, actor))
 		return;
 
 	fade = P_SpawnGhostMobj(actor);
@@ -12200,7 +12221,7 @@ void A_Boss5Jump(mobj_t *actor)
 	// INT32 locvar1 = var1;
 	// INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Boss5Jump", actor))
+	if (LUA_CallAction(A_BOSS5JUMP, actor))
 		return;
 
 	if (!actor->tracer)
@@ -12277,7 +12298,7 @@ void A_LightBeamReset(mobj_t *actor)
 	// INT32 locvar1 = var1;
 	// INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_LightBeamReset", actor))
+	if (LUA_CallAction(A_LIGHTBEAMRESET, actor))
 		return;
 
 	actor->destscale = FRACUNIT + P_SignedRandom()*FRACUNIT/256;
@@ -12307,7 +12328,7 @@ void A_MineExplode(mobj_t *actor)
 	// INT32 locvar1 = var1;
 	// INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_MineExplode", actor))
+	if (LUA_CallAction(A_MINEEXPLODE, actor))
 		return;
 
 	A_Scream(actor);
@@ -12360,7 +12381,7 @@ void A_MineRange(mobj_t *actor)
 	INT32 locvar1 = var1;
 	// INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_MineRange", actor))
+	if (LUA_CallAction(A_MINERANGE, actor))
 		return;
 
 	if (!actor->target)
@@ -12386,7 +12407,7 @@ void A_ConnectToGround(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_ConnectToGround", actor))
+	if (LUA_CallAction(A_CONNECTTOGROUND, actor))
 		return;
 
 	if (actor->subsector->sector->ffloors)
@@ -12445,7 +12466,7 @@ void A_SpawnParticleRelative(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_SpawnParticleRelative", actor))
+	if (LUA_CallAction(A_SPAWNPARTICLERELATIVE, actor))
 		return;
 
 
@@ -12483,7 +12504,7 @@ void A_MultiShotDist(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_MultiShotDist", actor))
+	if (LUA_CallAction(A_MULTISHOTDIST, actor))
 		return;
 
 	{
@@ -12522,7 +12543,7 @@ void A_WhoCaresIfYourSonIsABee(mobj_t *actor)
 	fixed_t foffsety;
 	mobj_t *son;
 
-	if (LUA_CallAction("A_WhoCaresIfYourSonIsABee", actor))
+	if (LUA_CallAction(A_WHOCARESIFYOURSONISABEE, actor))
 		return;
 
 	A_FaceTarget(actor);
@@ -12556,7 +12577,7 @@ void A_ParentTriesToSleep(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_ParentTriesToSleep", actor))
+	if (LUA_CallAction(A_PARENTTRIESTOSLEEP, actor))
 		return;
 
 	if (actor->extravalue1)
@@ -12584,7 +12605,7 @@ void A_ParentTriesToSleep(mobj_t *actor)
 //
 void A_CryingToMomma(mobj_t *actor)
 {
-	if (LUA_CallAction("A_CryingToMomma", actor))
+	if (LUA_CallAction(A_CRYINGTOMOMMA, actor))
 		return;
 
 	if (actor->tracer)
@@ -12614,7 +12635,7 @@ void A_CheckFlags2(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_CheckFlags2", actor))
+	if (LUA_CallAction(A_CHECKFLAGS2, actor))
 		return;
 
 	if (actor->flags2 & locvar1)
@@ -12635,7 +12656,7 @@ void A_Boss5FindWaypoint(mobj_t *actor)
 	UINT32 i;
 	UINT8 extrainfo = (actor->spawnpoint ? actor->spawnpoint->extrainfo : 0);
 
-	if (LUA_CallAction("A_Boss5FindWaypoint", actor))
+	if (LUA_CallAction(A_BOSS5FINDWAYPOINT, actor))
 		return;
 
 	avoidcenter = !actor->tracer || (actor->health == actor->info->damage+1);
@@ -12852,7 +12873,7 @@ void A_DoNPCSkid(mobj_t *actor)
 	INT32 locvar2 = var2;
 	fixed_t x, y, z;
 
-	if (LUA_CallAction("A_DoNPCSkid", actor))
+	if (LUA_CallAction(A_DONPCSKID, actor))
 		return;
 
 	x = actor->x;
@@ -12908,7 +12929,7 @@ void A_DoNPCPain(mobj_t *actor)
 	fixed_t vspeed = 0;
 	fixed_t hspeed = FixedMul(4*FRACUNIT, actor->scale);
 
-	if (LUA_CallAction("A_DoNPCPain", actor))
+	if (LUA_CallAction(A_DONPCPAIN, actor))
 		return;
 
 	actor->flags &= ~(MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT);
@@ -12956,7 +12977,7 @@ void A_PrepareRepeat(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_PrepareRepeat", actor))
+	if (LUA_CallAction(A_PREPAREREPEAT, actor))
 		return;
 
 	actor->extravalue2 = locvar1;
@@ -12977,7 +12998,7 @@ void A_Boss5ExtraRepeat(mobj_t *actor)
 	INT32 locspawn;
 	INT32 lochealth;
 
-	if (LUA_CallAction("A_Boss5ExtraRepeat", actor))
+	if (LUA_CallAction(A_BOSS5EXTRAREPEAT, actor))
 		return;
 
 	if (actor->extravalue2 > 0 && !(actor->flags2 & MF2_FRET))
@@ -13009,7 +13030,7 @@ void A_Boss5ExtraRepeat(mobj_t *actor)
 //
 void A_Boss5Calm(mobj_t *actor)
 {
-	if (LUA_CallAction("A_Boss5Calm", actor))
+	if (LUA_CallAction(A_BOSS5CALM, actor))
 		return;
 
 	actor->flags |= MF_SHOOTABLE;
@@ -13028,7 +13049,7 @@ void A_Boss5CheckOnGround(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Boss5CheckOnGround", actor))
+	if (LUA_CallAction(A_BOSS5CHECKONGROUND, actor))
 		return;
 
 	if ((!(actor->eflags & MFE_VERTICALFLIP) && actor->z <= actor->floorz)
@@ -13059,7 +13080,7 @@ void A_Boss5CheckFalling(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_Boss5CheckFalling", actor))
+	if (LUA_CallAction(A_BOSS5CHECKFALLING, actor))
 		return;
 
 	if (actor->health && actor->extravalue2 > 1)
@@ -13088,7 +13109,7 @@ void A_Boss5PinchShot(mobj_t *actor)
 	fixed_t zoffset;
 	mobj_t *missile;
 
-	if (LUA_CallAction("A_Boss5PinchShot", actor))
+	if (LUA_CallAction(A_BOSS5PINCHSHOT, actor))
 		return;
 
 	if (actor->health > actor->info->damage)
@@ -13123,7 +13144,7 @@ void A_Boss5MakeItRain(mobj_t *actor)
 	INT32 offset = (48 + locvar2)<<16; // upper 16 bits, not fixed_t!
 	INT32 i;
 
-	if (LUA_CallAction("A_Boss5MakeItRain", actor))
+	if (LUA_CallAction(A_BOSS5MAKEITRAIN, actor))
 		return;
 
 	actor->flags2 |= MF2_STRONGBOX;
@@ -13159,7 +13180,7 @@ void A_Boss5MakeJunk(mobj_t *actor)
 	angle_t ang;
 	INT32 i = ((locvar2 & 1) ? 8 : 1);
 
-	if (LUA_CallAction("A_Boss5MakeJunk", actor))
+	if (LUA_CallAction(A_BOSS5MAKEJUNK, actor))
 		return;
 
 	if (locvar1 < 0 && (actor->flags2 & MF2_SLIDEPUSH)) // this entire action is a hack, don't judge me
@@ -13249,7 +13270,7 @@ void A_LookForBetter(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_LookForBetter", actor))
+	if (LUA_CallAction(A_LOOKFORBETTER, actor))
 		return;
 
 	P_LookForPlayers(actor, (locvar1 & 65535), false, FixedMul((locvar1 >> 16)*FRACUNIT, actor->scale));
@@ -13309,7 +13330,7 @@ void A_Boss5BombExplode(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_Boss5BombExplode", actor))
+	if (LUA_CallAction(A_BOSS5BOMBEXPLODE, actor))
 		return;
 
 	// The original Lua script did not use |= to add flags but just set these flags exactly apparently?
@@ -13419,7 +13440,7 @@ void A_DustDevilThink(mobj_t *actor)
 	INT32 bx, by, xl, xh, yl, yh;
 	fixed_t radius = actor->radius;
 
-	if (LUA_CallAction("A_DustDevilThink", actor))
+	if (LUA_CallAction(A_DUSTDEVILTHINK, actor))
 		return;
 
 	//Chained thinker for the spiralling dust column.
@@ -13561,7 +13582,7 @@ void A_TNTExplode(mobj_t *actor)
 	INT32 xl, xh, yl, yh;
 	static mappoint_t epicenter = {0,0,0};
 
-	if (LUA_CallAction("A_TNTExplode", actor))
+	if (LUA_CallAction(A_TNTEXPLODE, actor))
 		return;
 
 	if (actor->tracer)
@@ -13627,7 +13648,7 @@ void A_DebrisRandom(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_DebrisRandom", actor))
+	if (LUA_CallAction(A_DEBRISRANDOM, actor))
 		return;
 
 	actor->frame |= P_RandomRange(0, locvar1);
@@ -13668,7 +13689,7 @@ void A_TrainCameo(mobj_t *actor)
 	fixed_t span = locvar1*FRACUNIT;
 	fixed_t len = locvar2*FRACUNIT;
 
-	if (LUA_CallAction("A_TrainCameo", actor))
+	if (LUA_CallAction(A_TRAINCAMEO, actor))
 		return;
 
 	//Spawn sides.
@@ -13706,7 +13727,7 @@ void A_TrainCameo2(mobj_t *actor)
 	fixed_t span = locvar1*FRACUNIT;
 	fixed_t len = locvar2*FRACUNIT;
 
-	if (LUA_CallAction("A_TrainCameo2", actor))
+	if (LUA_CallAction(A_TRAINCAMEO2, actor))
 		return;
 
 	//Spawn sides.
@@ -13732,7 +13753,7 @@ void A_CanarivoreGas(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 
-	if (LUA_CallAction("A_CanarivoreGas", actor))
+	if (LUA_CallAction(A_CANARIVOREGAS, actor))
 		return;
 
 	P_DustRing(locvar1, 4, actor->x, actor->y, actor->z + actor->height / 5, 18, 0, FRACUNIT/10, actor->scale);
@@ -13753,7 +13774,7 @@ void A_KillSegments(mobj_t *actor)
 	mobj_t *seg = actor->tracer;
 	INT32 fuse = locvar1 ? locvar1 : TICRATE/2;
 
-	if (LUA_CallAction("A_KillSegments", actor))
+	if (LUA_CallAction(A_KILLSEGMENTS, actor))
 		return;
 
 	while (seg)
@@ -13836,7 +13857,7 @@ void A_SnapperSpawn(mobj_t *actor)
 	INT32 i;
 	mobj_t *seg;
 
-	if (LUA_CallAction("A_SnapperSpawn", actor))
+	if (LUA_CallAction(A_SNAPPERSPAWN, actor))
 		return;
 
 	// It spawns 1 head.
@@ -13886,7 +13907,7 @@ void A_SnapperThinker(mobj_t *actor)
 	fixed_t dist;
 	boolean chasing;
 
-	if (LUA_CallAction("A_SnapperThinker", actor))
+	if (LUA_CallAction(A_SNAPPERTHINKER, actor))
 		return;
 
 	// We make a check just in case there's no spawnpoint.
@@ -14007,7 +14028,7 @@ void A_SaloonDoorSpawn(mobj_t *actor)
 	mobj_t *door;
 	mobjflag2_t ambush = (actor->flags2 & MF2_AMBUSH);
 
-	if (LUA_CallAction("A_SaloonDoorSpawn", actor))
+	if (LUA_CallAction(A_SALOONDOORSPAWN, actor))
 		return;
 
 	if (!locvar1)
@@ -14044,7 +14065,7 @@ void A_MinecartSparkThink(mobj_t *actor)
 	fixed_t dz, dm;
 	UINT8 i;
 
-	if (LUA_CallAction("A_MinecartSparkThink", actor))
+	if (LUA_CallAction(A_MINECARTSPARKTHINK, actor))
 		return;
 
 	if (actor->momz == 0 && P_IsObjectOnGround(actor))
@@ -14078,7 +14099,7 @@ void A_ModuloToState(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_ModuloToState", actor))
+	if (LUA_CallAction(A_MODULOTOSTATE, actor))
 		return;
 
 	if ((modulothing % locvar1 == 0))
@@ -14097,7 +14118,7 @@ void A_LavafallRocks(mobj_t *actor)
 {
 	UINT8 i;
 
-	if (LUA_CallAction("A_LavafallRocks", actor))
+	if (LUA_CallAction(A_LAVAFALLROCKS, actor))
 		return;
 
 	// Don't spawn rocks unless a player is relatively close by.
@@ -14128,7 +14149,7 @@ void A_LavafallLava(mobj_t *actor)
 	mobj_t *lavafall;
 	UINT8 i;
 
-	if (LUA_CallAction("A_LavafallLava", actor))
+	if (LUA_CallAction(A_LAVAFALLLAVA, actor))
 		return;
 
 	if ((40 - actor->fuse) % (2*(actor->scale >> FRACBITS)))
@@ -14156,7 +14177,7 @@ void A_LavafallLava(mobj_t *actor)
 //
 void A_FallingLavaCheck(mobj_t *actor)
 {
-	if (LUA_CallAction("A_FallingLavaCheck", actor))
+	if (LUA_CallAction(A_FALLINGLAVACHECK, actor))
 		return;
 
 	if (actor->eflags & MFE_TOUCHWATER || P_IsObjectOnGround(actor))
@@ -14181,7 +14202,7 @@ void A_FireShrink(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_FireShrink", actor))
+	if (LUA_CallAction(A_FIRESHRINK, actor))
 		return;
 
 	actor->destscale = locvar1;
@@ -14205,7 +14226,7 @@ void A_SpawnPterabytes(mobj_t *actor)
 	UINT8 amount = 1;
 	UINT8 i;
 
-	if (LUA_CallAction("A_SpawnPterabytes", actor))
+	if (LUA_CallAction(A_SPAWNPTERABYTES, actor))
 		return;
 
 	if (actor->spawnpoint)
@@ -14240,7 +14261,7 @@ void A_PterabyteHover(mobj_t *actor)
 {
 	angle_t ang, fa;
 
-	if (LUA_CallAction("A_PterabyteHover", actor))
+	if (LUA_CallAction(A_PTERABYTEHOVER, actor))
 		return;
 
 	P_InstaThrust(actor, actor->angle, actor->info->speed);
@@ -14262,7 +14283,7 @@ void A_RolloutSpawn(mobj_t *actor)
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
 
-	if (LUA_CallAction("A_RolloutSpawn", actor))
+	if (LUA_CallAction(A_ROLLOUTSPAWN, actor))
 		return;
 
 	if (!(actor->target)
@@ -14298,7 +14319,7 @@ void A_RolloutRock(mobj_t *actor)
 	fixed_t speed = P_AproxDistance(actor->momx, actor->momy), topspeed = FixedMul(actor->info->speed, actor->scale);
 	boolean inwater = actor->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER);
 
-	if (LUA_CallAction("A_RolloutRock", actor))
+	if (LUA_CallAction(A_ROLLOUTROCK, actor))
 		return;
 
 	actor->friction = FRACUNIT; // turns out riding on solids sucks, so let's just make it easier on ourselves
@@ -14381,7 +14402,7 @@ void A_DragonbomberSpawn(mobj_t *actor)
 	UINT8 i;
 	mobj_t *mo = actor;
 
-	if (LUA_CallAction("A_DragonbomberSpawn", actor))
+	if (LUA_CallAction(A_DRAGONBOMBERSPAWN, actor))
 		return;
 
 	for (i = 0; i < var1; i++) // spawn tail segments
@@ -14417,7 +14438,7 @@ void A_DragonWing(mobj_t *actor)
 	mobj_t *target = actor->target;
 	fixed_t x, y;
 
-	if (LUA_CallAction("A_DragonWing", actor))
+	if (LUA_CallAction(A_DRAGONWING, actor))
 		return;
 
 	if (target == NULL || !target->health)
@@ -14450,7 +14471,7 @@ void A_DragonSegment(mobj_t *actor)
 	fixed_t ydist;
 	fixed_t zdist;
 
-	if (LUA_CallAction("A_DragonSegment", actor))
+	if (LUA_CallAction(A_DRAGONSEGMENT, actor))
 		return;
 
 	if (target == NULL || !target->health)
@@ -14488,7 +14509,7 @@ void A_ChangeHeight(mobj_t *actor)
 	fixed_t height = locvar1;
 	boolean reverse;
 
-	if (LUA_CallAction("A_ChangeHeight", actor))
+	if (LUA_CallAction(A_CHANGEHEIGHT, actor))
 		return;
 
 	reverse = (actor->eflags & MFE_VERTICALFLIP) || (actor->flags2 & MF2_OBJECTFLIP);
diff --git a/src/p_floor.c b/src/p_floor.c
index a0f7edd9ca0e68617b1eede0441c5a14a2dbe549..7c26065b59b6e9ef0f002bf20907e3eae75856cc 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -632,8 +632,10 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	fixed_t waterheight;
 	fixed_t floorheight;
 	sector_t *actionsector;
-	INT32 i;
 	boolean remove;
+	INT32 i;
+	mtag_t tag = Tag_FGet(&bouncer->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (bouncer->sector->crumblestate == CRUMBLE_RESTORE || bouncer->sector->crumblestate == CRUMBLE_WAIT
 		|| bouncer->sector->crumblestate == CRUMBLE_ACTIVATED) // Oops! Crumbler says to remove yourself!
@@ -648,7 +650,7 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	}
 
 	// You can use multiple target sectors, but at your own risk!!!
-	for (i = -1; (i = P_FindSectorFromTag(bouncer->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		actionsector = &sectors[i];
 		actionsector->moved = true;
@@ -772,6 +774,8 @@ void T_StartCrumble(crumble_t *crumble)
 	ffloor_t *rover;
 	sector_t *sector;
 	INT32 i;
+	mtag_t tag = Tag_FGet(&crumble->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// Once done, the no-return thinker just sits there,
 	// constantly 'returning'... kind of an oxymoron, isn't it?
@@ -800,7 +804,7 @@ void T_StartCrumble(crumble_t *crumble)
 		}
 		else if (++crumble->timer == 0) // Reposition back to original spot
 		{
-			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, i)
 			{
 				sector = &sectors[i];
 
@@ -836,7 +840,7 @@ void T_StartCrumble(crumble_t *crumble)
 		// Flash to indicate that the platform is about to return.
 		if (crumble->timer > -224 && (leveltime % ((abs(crumble->timer)/8) + 1) == 0))
 		{
-			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, i)
 			{
 				sector = &sectors[i];
 
@@ -928,7 +932,7 @@ void T_StartCrumble(crumble_t *crumble)
 		P_RemoveThinker(&crumble->thinker);
 	}
 
-	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		sector = &sectors[i];
 		sector->moved = true;
@@ -944,6 +948,7 @@ void T_StartCrumble(crumble_t *crumble)
 void T_MarioBlock(mariothink_t *block)
 {
 	INT32 i;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	T_MovePlane
 	(
@@ -978,8 +983,7 @@ void T_MarioBlock(mariothink_t *block)
 		block->sector->ceilspeed = 0;
 		block->direction = 0;
 	}
-
-	for (i = -1; (i = P_FindSectorFromTag(block->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, (INT16)block->tag, i)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -992,8 +996,7 @@ void T_FloatSector(floatthink_t *floater)
 
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag(floater->tag, -1);
-	if (secnum <= 0)
+	if ((secnum = Tag_Iterate_Sectors((INT16)floater->tag, 0)) < 0)
 		return;
 	actionsector = &sectors[secnum];
 
@@ -1061,9 +1064,7 @@ static mobj_t *SearchMarioNode(msecnode_t *node)
 		case MT_HOOP:
 		case MT_HOOPCOLLIDE:
 		case MT_NIGHTSCORE:
-#ifdef SEENAMES
 		case MT_NAMECHECK: // DEFINITELY not this, because it is client-side.
-#endif
 			continue;
 		default:
 			break;
@@ -1131,10 +1132,8 @@ void T_ThwompSector(thwomp_t *thwomp)
 
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag(thwomp->tag, -1);
-
-	if (secnum <= 0)
-		return; // Bad bad bad!
+	if ((secnum = Tag_Iterate_Sectors((INT16)thwomp->tag, 0)) < 0)
+		return;
 
 	actionsector = &sectors[secnum];
 
@@ -1293,8 +1292,10 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 	sector_t *sec = NULL;
 	INT32 secnum = -1;
 	boolean FOFsector = false;
+	mtag_t tag = Tag_FGet(&nobaddies->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(nobaddies->sourceline->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -1304,13 +1305,15 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 		for (i = 0; i < sec->linecount; i++)
 		{
 			INT32 targetsecnum = -1;
+			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
+			TAG_ITER_DECLARECOUNTER(1);
 
 			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
 				continue;
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
+			TAG_ITER_SECTORS(1, tag2, targetsecnum)
 			{
 				if (T_SectorHasEnemies(&sectors[targetsecnum]))
 					return;
@@ -1321,7 +1324,7 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 			return;
 	}
 
-	CONS_Debug(DBG_GAMELOGIC, "Running no-more-enemies exec with tag of %d\n", nobaddies->sourceline->tag);
+	CONS_Debug(DBG_GAMELOGIC, "Running no-more-enemies exec with tag of %d\n", tag);
 
 	// No enemies found, run the linedef exec and terminate this thinker
 	P_RunTriggerLinedef(nobaddies->sourceline, NULL, NULL);
@@ -1396,6 +1399,8 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
 	ffloor_t *rover;
+	mtag_t tag = Tag_FGet(&eachtime->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -1405,7 +1410,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		eachtime->playersOnArea[i] = false;
 	}
 
-	while ((secnum = P_FindSectorFromTag(eachtime->sourceline->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -1422,13 +1427,15 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		for (i = 0; i < sec->linecount; i++)
 		{
 			INT32 targetsecnum = -1;
+			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
+			TAG_ITER_DECLARECOUNTER(1);
 
 			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
 				continue;
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
+			TAG_ITER_SECTORS(1, tag2, targetsecnum)
 			{
 				targetsec = &sectors[targetsecnum];
 
@@ -1530,7 +1537,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		if (!playersArea[i] && (!eachtime->triggerOnExit || !P_IsPlayerValid(i)))
 			continue;
 
-		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", eachtime->sourceline->tag);
+		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", tag);
 
 		// 03/08/14 -Monster Iestyn
 		// No more stupid hacks involving changing eachtime->sourceline's tag or special or whatever!
@@ -1562,11 +1569,13 @@ void T_RaiseSector(raise_t *raise)
 	fixed_t distToNearestEndpoint;
 	INT32 direction;
 	result_e res = 0;
+	mtag_t tag = raise->tag;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (raise->sector->crumblestate >= CRUMBLE_FALL || raise->sector->ceilingdata)
 		return;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		sector = &sectors[i];
 
@@ -1693,7 +1702,7 @@ void T_RaiseSector(raise_t *raise)
 	raise->sector->ceilspeed = 42;
 	raise->sector->floorspeed = speed*direction;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -1810,8 +1819,10 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 	INT32 secnum = -1;
 	sector_t *sec;
 	floormove_t *dofloor;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -2025,9 +2036,11 @@ void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 	INT32 secnum = -1;
 	sector_t *sec;
 	elevator_t *elevator;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// act on all sectors with the same tag as the triggering linedef
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -2148,6 +2161,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	INT16 flags;
 
 	sector_t *controlsec = rover->master->frontsector;
+	mtag_t tag = Tag_FGet(&controlsec->tags);
 
 	if (sec == NULL)
 	{
@@ -2176,9 +2190,9 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	lifetime = 3*TICRATE;
 	flags = 0;
 
-	if (controlsec->tag != 0)
+	if (tag != 0)
 	{
-		INT32 tagline = P_FindSpecialLineFromTag(14, controlsec->tag, -1);
+		INT32 tagline = Tag_FindLineSpecial(14, tag);
 		if (tagline != -1)
 		{
 			if (sides[lines[tagline].sidenum[0]].toptexture)
@@ -2322,6 +2336,8 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	crumble_t *crumble;
 	sector_t *foundsec;
 	INT32 i;
+	mtag_t tag = Tag_FGet(&rover->master->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// If floor is already activated, skip it
 	if (sec->floordata)
@@ -2364,7 +2380,7 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 
 	crumble->sector->crumblestate = CRUMBLE_ACTIVATED;
 
-	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		foundsec = &sectors[i];
 
@@ -2413,7 +2429,7 @@ void EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		block->direction = 1;
 		block->floorstartheight = block->sector->floorheight;
 		block->ceilingstartheight = block->sector->ceilingheight;
-		block->tag = (INT16)sector->tag;
+		block->tag = (INT16)Tag_FGet(&sector->tags);
 
 		if (itsamonitor)
 		{
diff --git a/src/p_inter.c b/src/p_inter.c
index 9caed927d31a6ac5036d9dab2eb7fc6a14ae74d2..e9a16a3dd143128a06fa5658cb8ad2d7fb35f4c2 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -467,10 +467,22 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
 			{
-				if (elementalpierce == 2)
-					P_DoBubbleBounce(player);
-				else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
-					toucher->momz = -toucher->momz;
+				if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+				{
+					fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+
+					if (elementalpierce == 2) // Reset bubblewrap, part 1
+						P_DoBubbleBounce(player);
+					toucher->momz = setmomz;
+					if (elementalpierce == 2) // Reset bubblewrap, part 2
+					{
+						boolean underwater = toucher->eflags & MFE_UNDERWATER;
+
+						if (underwater)
+							toucher->momz /= 2;
+						toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
+					}
+				}
 			}
 			if (player->pflags & PF_BOUNCING)
 				P_DoAbilityBounce(player, false);
@@ -1376,7 +1388,11 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (player->bot)
 					return;
 
-				junk.tag = LE_AXE;
+				// Initialize my junk
+				junk.tags.tags = NULL;
+				junk.tags.count = 0;
+
+				Tag_FSet(&junk.tags, LE_AXE);
 				EV_DoElevator(&junk, bridgeFall, false);
 
 				// scan the remaining thinkers to find koopa
@@ -1601,7 +1617,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 				if (special->tracer && !(special->tracer->flags2 & MF2_STRONGBOX))
 					macespin = true;
-				
+
 				if (macespin ? (player->powers[pw_ignorelatch] & (1<<15)) : (player->powers[pw_ignorelatch]))
 					return;
 
diff --git a/src/p_lights.c b/src/p_lights.c
index 371077a302d39b903b88658baf492d38680dcbb7..d396e92d3dedff6ac56f40901335ec701d6625e9 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -374,8 +374,10 @@ void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean
 void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force)
 {
 	INT32 i;
+	TAG_ITER_DECLARECOUNTER(0);
+
 	// search all sectors for ones with tag
-	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		if (!force && ticbased // always let speed fader execute
 			&& sectors[i].lightingdata
diff --git a/src/p_local.h b/src/p_local.h
index 1f053800f0b98499ea9d5f83e643dc4995c3c58a..b1054ba4beeaf30e2d0682767c7a773a236c8b19 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -145,6 +145,7 @@ void P_SetPlayerAngle(player_t *player, angle_t angle);
 angle_t P_GetLocalAngle(player_t *player);
 void P_SetLocalAngle(player_t *player, angle_t angle);
 void P_ForceLocalAngle(player_t *player, angle_t angle);
+boolean P_PlayerFullbright(player_t *player);
 
 boolean P_IsObjectInGoop(mobj_t *mo);
 boolean P_IsObjectOnGround(mobj_t *mo);
@@ -180,6 +181,7 @@ void P_BlackOw(player_t *player);
 void P_ElementalFire(player_t *player, boolean cropcircle);
 void P_SpawnSkidDust(player_t *player, fixed_t radius, boolean sound);
 
+void P_MovePlayer(player_t *player);
 void P_DoPityCheck(player_t *player);
 void P_PlayerThink(player_t *player);
 void P_PlayerAfterThink(player_t *player);
@@ -268,6 +270,8 @@ void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, b
 
 extern consvar_t cv_gravity, cv_movebob;
 
+mobjtype_t P_GetMobjtype(UINT16 mthingtype);
+
 void P_RespawnSpecials(void);
 
 mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type);
@@ -318,9 +322,7 @@ mobj_t *P_SpawnPointMissile(mobj_t *source, fixed_t xa, fixed_t ya, fixed_t za,
 mobj_t *P_SpawnAlteredDirectionMissile(mobj_t *source, mobjtype_t type, fixed_t x, fixed_t y, fixed_t z, INT32 shiftingAngle);
 mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 aimtype, UINT32 flags2);
 #define P_SpawnPlayerMissile(s,t,f) P_SPMAngle(s,t,s->angle,true,f)
-#ifdef SEENAMES
 #define P_SpawnNameFinder(s,t) P_SPMAngle(s,t,s->angle,true,0)
-#endif
 void P_ColorTeamMissile(mobj_t *missile, player_t *source);
 SINT8 P_MobjFlip(mobj_t *mobj);
 fixed_t P_GetMobjGravity(mobj_t *mo);
@@ -420,6 +422,7 @@ void P_Initsecnode(void);
 void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 damagetype, boolean sightcheck);
 
 fixed_t P_FloorzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height);
+fixed_t P_CeilingzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height);
 boolean PIT_PushableMoved(mobj_t *thing);
 
 boolean P_DoSpring(mobj_t *spring, mobj_t *object);
diff --git a/src/p_map.c b/src/p_map.c
index fdae18bf3ba715560a753325ae527791382162ee..0fa9d9148d8c1eb460d1979176381ceec778923a 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -33,6 +33,8 @@
 
 #include "lua_hook.h"
 
+#include "m_perfstats.h" // ps_checkposition_calls
+
 fixed_t tmbbox[4];
 mobj_t *tmthing;
 static INT32 tmflags;
@@ -380,7 +382,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		if ((spring->info->painchance == 3))
 		{
 			if (!(pflags = (object->player->pflags & PF_SPINNING)) &&
-				(((object->player->charability2 == CA2_SPINDASH) && (object->player->cmd.buttons & BT_USE))
+				(((object->player->charability2 == CA2_SPINDASH) && (object->player->cmd.buttons & BT_SPIN))
 				|| (spring->flags2 & MF2_AMBUSH)))
 			{
 				pflags = PF_SPINNING;
@@ -725,9 +727,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	|| (thing->player && thing->player->spectator))
 		return true;
 
-#ifdef SEENAMES
-  // Do name checks all the way up here
-  // So that NOTHING ELSE can see MT_NAMECHECK because it is client-side.
+	// Do name checks all the way up here
+	// So that NOTHING ELSE can see MT_NAMECHECK because it is client-side.
 	if (tmthing->type == MT_NAMECHECK)
 	{
 		// Ignore things that aren't players, ignore spectators, ignore yourself.
@@ -751,7 +752,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		seenplayer = thing->player;
 		return false;
 	}
-#endif
 
 	// Metal Sonic destroys tiny baby objects.
 	if (tmthing->type == MT_METALSONIC_RACE
@@ -980,7 +980,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	if (thing->type == MT_SALOONDOOR && tmthing->player)
 	{
 		mobj_t *ref = (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)) ? tmthing->tracer : tmthing;
-		if ((thing->flags2 & MF2_AMBUSH) || ref != tmthing)
+		if (((thing->flags2 & MF2_AMBUSH) && (tmthing->z <= thing->z + thing->height) && (tmthing->z + tmthing->height >= thing->z))
+			|| ref != tmthing)
 		{
 			fixed_t dm = min(FixedHypot(ref->momx, ref->momy), 16*FRACUNIT);
 			angle_t ang = R_PointToAngle2(0, 0, ref->momx, ref->momy) - thing->angle;
@@ -993,7 +994,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	if (thing->type == MT_SALOONDOORCENTER && tmthing->player)
 	{
-		if ((thing->flags2 & MF2_AMBUSH) || (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)))
+		if (((thing->flags2 & MF2_AMBUSH) && (tmthing->z <= thing->z + thing->height) && (tmthing->z + tmthing->height >= thing->z))
+			|| (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)))
 			return true;
 	}
 
@@ -1678,13 +1680,23 @@ static boolean PIT_CheckThing(mobj_t *thing)
 				&& (flipval*(*momz) < 0) // monitor is on the floor and you're going down, or on the ceiling and you're going up
 				&& (elementalpierce != 1)) // you're not piercing through the monitor...
 				{
-					if (elementalpierce == 2)
-						P_DoBubbleBounce(player);
-					else if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+					if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
 					{
-						*momz = -*momz; // Therefore, you should be thrust in the opposite direction, vertically.
+						fixed_t setmomz = -*momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
+
+						if (elementalpierce == 2) // Reset bubblewrap, part 1
+							P_DoBubbleBounce(player);
+						*momz = setmomz; // Therefore, you should be thrust in the opposite direction, vertically.
 						if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
 							P_TwinSpinRejuvenate(player, player->thokitem);
+						if (elementalpierce == 2) // Reset bubblewrap, part 2
+						{
+							boolean underwater = tmthing->eflags & MFE_UNDERWATER;
+
+							if (underwater)
+								*momz /= 2;
+							*momz -= (*momz/(underwater ? 8 : 4)); // Cap the height!
+						}
 					}
 				}
 				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
@@ -2009,6 +2021,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 	subsector_t *newsubsec;
 	boolean blockval = true;
 
+	ps_checkposition_calls++;
+
 	I_Assert(thing != NULL);
 #ifdef PARANOIA
 	if (P_MobjWasRemoved(thing))
@@ -2243,6 +2257,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 			{
 				if (!P_BlockThingsIterator(bx, by, PIT_CheckThing))
 					blockval = false;
+				else
+					tmhitthing = tmfloorthing;
 				if (P_MobjWasRemoved(tmthing))
 					return false;
 			}
@@ -2651,7 +2667,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 	fixed_t tryx = thing->x;
 	fixed_t tryy = thing->y;
 	fixed_t radius = thing->radius;
-	fixed_t thingtop = thing->z + thing->height;
+	fixed_t thingtop;
 	fixed_t startingonground = P_IsObjectOnGround(thing);
 	floatok = false;
 
@@ -2692,6 +2708,11 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 				|| GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 13)
 					maxstep <<= 1;
 
+				// If using type Section1:14, no maxstep.
+				if (P_PlayerTouchingSectorSpecial(thing->player, 1, 14)
+				|| GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 14)
+					maxstep = 0;
+
 				// Don't 'step up' while springing,
 				// Only step up "if needed".
 				if (thing->player->panim == PA_SPRING
@@ -2711,47 +2732,47 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 
 			floatok = true;
 
-			if (thing->eflags & MFE_VERTICALFLIP)
+			thingtop = thing->z + thing->height;
+
+			// Step up
+			if (thing->z < tmfloorz)
 			{
-				if (thing->z < tmfloorz)
+				if (maxstep > 0 && tmfloorz - thing->z <= maxstep)
+				{
+					thing->z = thing->floorz = tmfloorz;
+					thing->floorrover = tmfloorrover;
+					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
+				}
+				else
+				{
 					return false; // mobj must raise itself to fit
+				}
 			}
 			else if (tmceilingz < thingtop)
-				return false; // mobj must lower itself to fit
-
-			// Ramp test
-			if (maxstep > 0 && !(
-				thing->player && (
-				P_PlayerTouchingSectorSpecial(thing->player, 1, 14)
-				|| GETSECSPECIAL(R_PointInSubsector(x, y)->sector->special, 1) == 14)
-				)
-			)
+			{
+				if (maxstep > 0 && thingtop - tmceilingz <= maxstep)
+				{
+					thing->z = ( thing->ceilingz = tmceilingz ) - thing->height;
+					thing->ceilingrover = tmceilingrover;
+					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
+				}
+				else
+				{
+					return false; // mobj must lower itself to fit
+				}
+			}
+			else if (maxstep > 0) // Step down
 			{
 				// If the floor difference is MAXSTEPMOVE or less, and the sector isn't Section1:14, ALWAYS
 				// step down! Formerly required a Section1:13 sector for the full MAXSTEPMOVE, but no more.
 
-				if (thing->eflags & MFE_VERTICALFLIP)
-				{
-					if (thingtop == thing->ceilingz && tmceilingz > thingtop && tmceilingz - thingtop <= maxstep)
-					{
-						thing->z = (thing->ceilingz = thingtop = tmceilingz) - thing->height;
-						thing->ceilingrover = tmceilingrover;
-						thing->eflags |= MFE_JUSTSTEPPEDDOWN;
-					}
-					else if (tmceilingz < thingtop && thingtop - tmceilingz <= maxstep)
-					{
-						thing->z = (thing->ceilingz = thingtop = tmceilingz) - thing->height;
-						thing->ceilingrover = tmceilingrover;
-						thing->eflags |= MFE_JUSTSTEPPEDDOWN;
-					}
-				}
-				else if (thing->z == thing->floorz && tmfloorz < thing->z && thing->z - tmfloorz <= maxstep)
+				if (thingtop == thing->ceilingz && tmceilingz > thingtop && tmceilingz - thingtop <= maxstep)
 				{
-					thing->z = thing->floorz = tmfloorz;
-					thing->floorrover = tmfloorrover;
+					thing->z = (thing->ceilingz = tmceilingz) - thing->height;
+					thing->ceilingrover = tmceilingrover;
 					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
 				}
-				else if (tmfloorz > thing->z && tmfloorz - thing->z <= maxstep)
+				else if (thing->z == thing->floorz && tmfloorz < thing->z && thing->z - tmfloorz <= maxstep)
 				{
 					thing->z = thing->floorz = tmfloorz;
 					thing->floorrover = tmfloorrover;
@@ -2759,22 +2780,6 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 				}
 			}
 
-			if (thing->eflags & MFE_VERTICALFLIP)
-			{
-				if (thingtop - tmceilingz > maxstep)
-				{
-					if (tmfloorthing)
-						tmhitthing = tmfloorthing;
-					return false; // too big a step up
-				}
-			}
-			else if (tmfloorz - thing->z > maxstep)
-			{
-				if (tmfloorthing)
-					tmhitthing = tmfloorthing;
-				return false; // too big a step up
-			}
-
 			if (!allowdropoff && !(thing->flags & MF_FLOAT) && thing->type != MT_SKIM && !tmfloorthing)
 			{
 				if (thing->eflags & MFE_VERTICALFLIP)
@@ -3104,7 +3109,7 @@ static void P_HitSlideLine(line_t *ld)
 	lineangle >>= ANGLETOFINESHIFT;
 	deltaangle >>= ANGLETOFINESHIFT;
 
-	movelen = P_AproxDistance(tmxmove, tmymove);
+	movelen = R_PointToDist2(0, 0, tmxmove, tmymove);
 	newlen = FixedMul(movelen, FINECOSINE(deltaangle));
 
 	tmxmove = FixedMul(newlen, FINECOSINE(lineangle));
@@ -3341,7 +3346,7 @@ static void PTR_GlideClimbTraverse(line_t *li)
 	{
 		for (rover = checksector->ffloors; rover; rover = rover->next)
 		{
-			if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || (rover->flags & FF_BUSTUP))
+			if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || ((rover->flags & FF_BUSTUP) && (slidemo->player->charflags & SF_CANBUSTWALLS)))
 				continue;
 
 			topheight    = P_GetFFloorTopZAt   (rover, slidemo->x, slidemo->y);
@@ -3440,9 +3445,17 @@ static boolean PTR_SlideTraverse(intercept_t *in)
 			P_ProcessSpecialSector(slidemo->player, slidemo->subsector->sector, li->polyobj->lines[0]->backsector);
 	}
 
-	if (slidemo->player && slidemo->player->charability == CA_GLIDEANDCLIMB
-		&& (slidemo->player->pflags & PF_GLIDING || slidemo->player->climbing))
-		PTR_GlideClimbTraverse(li);
+	if (slidemo->player)
+	{
+		if (slidemo->player->charability == CA_GLIDEANDCLIMB
+			&& (slidemo->player->pflags & PF_GLIDING || slidemo->player->climbing))
+			PTR_GlideClimbTraverse(li);
+		else
+		{
+			slidemo->player->lastsidehit = li->sidenum[P_PointOnLineSide(slidemo->x, slidemo->y, li)];
+			slidemo->player->lastlinehit = (INT16)(li - lines);
+		}
+	}
 
 	if (in->frac < bestslidefrac && (!slidemo->player || !slidemo->player->climbing))
 	{
@@ -4953,7 +4966,7 @@ void P_MapEnd(void)
 }
 
 // P_FloorzAtPos
-// Returns the floorz of the XYZ position // TODO: Need ceilingpos function too
+// Returns the floorz of the XYZ position
 // Tails 05-26-2003
 fixed_t P_FloorzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height)
 {
@@ -4997,3 +5010,47 @@ fixed_t P_FloorzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height)
 
 	return floorz;
 }
+
+// P_CeilingZAtPos
+// Returns the ceilingz of the XYZ position
+fixed_t P_CeilingzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height)
+{
+	sector_t *sec = R_PointInSubsector(x, y)->sector;
+	fixed_t ceilingz = P_GetSectorCeilingZAt(sec, x, y);
+
+	if (sec->ffloors)
+	{
+		ffloor_t *rover;
+		fixed_t delta1, delta2, thingtop = z + height;
+
+		for (rover = sec->ffloors; rover; rover = rover->next)
+		{
+			fixed_t topheight, bottomheight;
+			if (!(rover->flags & FF_EXISTS))
+				continue;
+
+			if ((!(rover->flags & FF_SOLID || rover->flags & FF_QUICKSAND) || (rover->flags & FF_SWIMMABLE)))
+				continue;
+
+			topheight    = P_GetFFloorTopZAt   (rover, x, y);
+			bottomheight = P_GetFFloorBottomZAt(rover, x, y);
+
+			if (rover->flags & FF_QUICKSAND)
+			{
+				if (thingtop > bottomheight && topheight > z)
+				{
+					if (ceilingz > z)
+						ceilingz = z;
+				}
+				continue;
+			}
+
+			delta1 = z - (bottomheight + ((topheight - bottomheight)/2));
+			delta2 = thingtop - (bottomheight + ((topheight - bottomheight)/2));
+			if (bottomheight < ceilingz && abs(delta1) > abs(delta2))
+				ceilingz = bottomheight;
+		}
+	}
+
+	return ceilingz;
+}
diff --git a/src/p_maputl.c b/src/p_maputl.c
index c6e064d184a722ef2c6fb371ff7e5f9767f2afcd..90718a41cbe700621c191994401925ca5ab360e3 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -18,6 +18,7 @@
 #include "p_local.h"
 #include "r_main.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "p_maputl.h"
 #include "p_polyobj.h"
 #include "p_slopes.h"
diff --git a/src/p_mobj.c b/src/p_mobj.c
index f9467708244b2d3d1388c9c126327c4ed01c5410..b171a029275b4cfddba4c37a82f216e1ec5e4e3b 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -36,11 +36,7 @@
 #include "m_cond.h"
 
 static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
-consvar_t cv_movebob = {"movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-#ifdef WALLSPLATS
-consvar_t cv_splats = {"splats", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-#endif
+consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
 
 actioncache_t actioncachehead;
 
@@ -440,7 +436,7 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 
 			mobj->sprite2 = spr2;
 			mobj->frame = frame|(st->frame&~FF_FRAMEMASK);
-			if (player->powers[pw_super] || (player->powers[pw_carry] == CR_NIGHTSMODE && (player->charflags & (SF_SUPER|SF_NONIGHTSSUPER)) == SF_SUPER)) // Super colours? Super bright!
+			if (P_PlayerFullbright(player))
 				mobj->frame |= FF_FULLBRIGHT;
 		}
 		// Regular sprites
@@ -1959,29 +1955,6 @@ void P_XYMovement(mobj_t *mo)
 				return;
 			}
 
-			// draw damage on wall
-			//SPLAT TEST ----------------------------------------------------------
-#ifdef WALLSPLATS
-			if (blockingline && mo->type != MT_REDRING && mo->type != MT_FIREBALL
-			&& !(mo->flags2 & (MF2_AUTOMATIC|MF2_RAILRING|MF2_BOUNCERING|MF2_EXPLOSION|MF2_SCATTER)))
-				// set by last P_TryMove() that failed
-			{
-				divline_t divl;
-				divline_t misl;
-				fixed_t frac;
-
-				P_MakeDivline(blockingline, &divl);
-				misl.x = mo->x;
-				misl.y = mo->y;
-				misl.dx = mo->momx;
-				misl.dy = mo->momy;
-				frac = P_InterceptVector(&divl, &misl);
-				R_AddWallSplat(blockingline, P_PointOnLineSide(mo->x,mo->y,blockingline),
-					"A_DMG3", mo->z, frac, SPLATDRAWMODE_SHADE);
-			}
-#endif
-			// --------------------------------------------------------- SPLAT TEST
-
 			P_ExplodeMissile(mo);
 			return;
 		}
@@ -2100,7 +2073,7 @@ void P_XYMovement(mobj_t *mo)
 	P_XYFriction(mo, oldx, oldy);
 }
 
-static void P_RingXYMovement(mobj_t *mo)
+void P_RingXYMovement(mobj_t *mo)
 {
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
@@ -2109,7 +2082,7 @@ static void P_RingXYMovement(mobj_t *mo)
 		P_SlideMove(mo);
 }
 
-static void P_SceneryXYMovement(mobj_t *mo)
+void P_SceneryXYMovement(mobj_t *mo)
 {
 	fixed_t oldx, oldy; // reducing bobbing/momentum on ice when up against walls
 
@@ -2268,7 +2241,7 @@ static void P_AdjustMobjFloorZ_PolyObjs(mobj_t *mo, subsector_t *subsec)
 	}
 }
 
-static void P_RingZMovement(mobj_t *mo)
+void P_RingZMovement(mobj_t *mo)
 {
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
@@ -2335,7 +2308,7 @@ boolean P_CheckSolidLava(ffloor_t *rover)
 // P_ZMovement
 // Returns false if the mobj was killed/exploded/removed, true otherwise.
 //
-static boolean P_ZMovement(mobj_t *mo)
+boolean P_ZMovement(mobj_t *mo)
 {
 	fixed_t dist, delta;
 	boolean onground;
@@ -2880,8 +2853,7 @@ static boolean P_PlayerPolyObjectZMovement(mobj_t *mo)
 					continue;
 
 				// We're landing on a PO, so check for a linedef executor.
-				// Trigger tags are 32000 + the PO's ID number.
-				P_LinedefExecute((INT16)(32000 + po->id), mo, NULL);
+				P_LinedefExecute(po->triggertag, mo, NULL);
 			}
 		}
 	}
@@ -2889,7 +2861,7 @@ static boolean P_PlayerPolyObjectZMovement(mobj_t *mo)
 	return stopmovecut;
 }
 
-static void P_PlayerZMovement(mobj_t *mo)
+void P_PlayerZMovement(mobj_t *mo)
 {
 	boolean onground;
 
@@ -3067,7 +3039,7 @@ nightsdone:
 	}
 }
 
-static boolean P_SceneryZMovement(mobj_t *mo)
+boolean P_SceneryZMovement(mobj_t *mo)
 {
 	// Intercept the stupid 'fall through 3dfloors' bug
 	if (mo->subsector->sector->ffloors)
@@ -3214,13 +3186,16 @@ static boolean P_SceneryZMovement(mobj_t *mo)
 //
 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
 {
-	fixed_t topheight = P_GetFFloorTopZAt(rover, player->mo->x, player->mo->y);
+	boolean flip = player->mo->eflags & MFE_VERTICALFLIP;
+	fixed_t surfaceheight = flip ? P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y) : P_GetFFloorTopZAt(rover, player->mo->x, player->mo->y);
+	fixed_t playerbottom = flip ? (player->mo->z + player->mo->height) : player->mo->z;
+	boolean doifit = flip ? (surfaceheight - player->mo->floorz >= player->mo->height) : (player->mo->ceilingz - surfaceheight >= player->mo->height);
 
 	if (!player->powers[pw_carry] && !player->homing
-		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= DASHMODE_THRESHOLD) && player->mo->ceilingz-topheight >= player->mo->height)
+		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= DASHMODE_THRESHOLD) && doifit)
 		&& (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
 		&& !(player->pflags & PF_SLIDING)
-		&& abs(player->mo->z - topheight) < FixedMul(30*FRACUNIT, player->mo->scale))
+		&& abs(playerbottom - surfaceheight) < FixedMul(30*FRACUNIT, player->mo->scale))
 		return true;
 
 	return false;
@@ -3392,7 +3367,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 			}
 
 			// skipping stone!
-			if (p && (p->charability2 == CA2_SPINDASH) && p->speed/2 > abs(mobj->momz)
+			if (p && p->speed/2 > abs(mobj->momz)
 				&& ((p->pflags & (PF_SPINNING|PF_JUMPED)) == PF_SPINNING)
 				&& ((!(mobj->eflags & MFE_VERTICALFLIP) && thingtop - mobj->momz > mobj->watertop)
 				|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z - mobj->momz < mobj->waterbottom)))
@@ -3535,16 +3510,19 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 {
 	sector_t *sector;
 	fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
+	size_t i;
 
 	// see if we are in water
 	sector = thiscam->subsector->sector;
 
-	if (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1)
-		return true;
+	for (i = 0; i < sector->tags.count; i++)
+		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
+			return true;
 
 	if (sector->ffloors)
 	{
 		ffloor_t *rover;
+		size_t j;
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
@@ -3556,7 +3534,8 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 			if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
 				continue;
 
-			if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
+			for (j = 0; j < rover->master->frontsector->tags.count; j++)
+			if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
 				return true;
 		}
 	}
@@ -4194,7 +4173,7 @@ boolean P_SupermanLook4Players(mobj_t *actor)
 
 	for (c = 0; c < MAXPLAYERS; c++)
 	{
-		if (playeringame[c])
+		if (playeringame[c] && !players[c].spectator)
 		{
 			if (players[c].pflags & PF_INVIS)
 				continue; // ignore notarget
@@ -4629,16 +4608,18 @@ static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
 	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum;
 	sector_t *sector;
-	for (snum = sectors[tag%numsectors].firsttag; snum != -1; snum = sector->nexttag)
+	boolean gotcage = false;
+	TAG_ITER_DECLARECOUNTER(0);
+
+	TAG_ITER_SECTORS(0, tag, snum)
 	{
 		sector = &sectors[snum];
-		if (sector->tag != tag)
-			continue;
 		sector->floorheight += delta;
 		sector->ceilingheight += delta;
 		P_CheckSector(sector, true);
+		gotcage = true;
 	}
-	return sectors[tag%numsectors].firsttag != -1;
+	return gotcage;
 }
 
 // Move Boss4's arms to angle
@@ -4710,26 +4691,16 @@ static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 static void P_Boss4DestroyCage(mobj_t *mobj)
 {
 	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
-	INT32 snum, next;
+	INT32 snum;
 	size_t a;
 	sector_t *sector, *rsec;
 	ffloor_t *rover;
+	TAG_ITER_DECLARECOUNTER(0);
 
-	// This will be the final iteration of sector tag.
-	// We'll destroy the tag list as we go.
-	next = sectors[tag%numsectors].firsttag;
-	sectors[tag%numsectors].firsttag = -1;
-
-	for (snum = next; snum != -1; snum = next)
+	TAG_ITER_SECTORS(0, tag, snum)
 	{
 		sector = &sectors[snum];
 
-		next = sector->nexttag;
-		sector->nexttag = -1;
-		if (sector->tag != tag)
-			continue;
-		sector->tag = 0;
-
 		// Destroy the FOFs.
 		for (a = 0; a < sector->numattached; a++)
 		{
@@ -5688,14 +5659,10 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1)
 					break;
 			}
-			if (spawner)
+			if (spawner && dist)
 			{
 				mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER);
-
-				if (dist == 0)
-					missile->fuse = 0;
-				else
-					missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
+				missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
 
 				if (missile->fuse > mobj->fuse)
 					P_RemoveMobj(missile);
@@ -7969,7 +7936,7 @@ static boolean P_MobjPushableThink(mobj_t *mobj)
 	P_PushableThinker(mobj);
 
 	// Extinguish fire objects in water. (Yes, it's extraordinarily rare to have a pushable flame object, but Brak uses such a case.)
-	if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
+	if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
 		&& (mobj->eflags & (MFE_UNDERWATER | MFE_TOUCHWATER)))
 	{
 		P_KillMobj(mobj, NULL, NULL, 0);
@@ -9615,12 +9582,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 			mobj->fuse = 1; // Return to base.
 		break;
 	}
-	case MT_CANNONBALL:
-#ifdef FLOORSPLATS
-		R_AddFloorSplat(mobj->tracer->subsector, mobj->tracer, "TARGET", mobj->tracer->x,
-			mobj->tracer->y, mobj->tracer->floorz, SPLATDRAWMODE_SHADE);
-#endif
-		break;
 	case MT_SPINDUST: // Spindash dust
 		mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
 		mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
@@ -9689,6 +9650,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		break;
 	}
 	case MT_SALOONDOOR:
+		if (!mobj->tracer) // Door center is gone or not spawned?
+		{
+			P_RemoveMobj(mobj); // Die
+			return false;
+		}
+
 		P_SaloonDoorThink(mobj);
 		break;
 	case MT_MINECARTSPAWNER:
@@ -9743,7 +9710,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 		P_MobjCheckWater(mobj);
 
 		// Extinguish fire objects in water
-		if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
+		if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
 			&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
 		{
 			P_KillMobj(mobj, NULL, NULL, 0);
@@ -9873,7 +9840,7 @@ static void P_FlagFuseThink(mobj_t *mobj)
 	if (mobj->type == MT_REDFLAG)
 	{
 		if (!(mobj->flags2 & MF2_JUSTATTACKED))
-			CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x85, M_GetText("Red flag"), 0x80);
+			CONS_Printf(M_GetText("The \205Red flag\200 has returned to base.\n"));
 
 		// Assumedly in splitscreen players will be on opposing teams
 		if (players[consoleplayer].ctfteam == 1 || splitscreen)
@@ -9886,7 +9853,7 @@ static void P_FlagFuseThink(mobj_t *mobj)
 	else // MT_BLUEFLAG
 	{
 		if (!(mobj->flags2 & MF2_JUSTATTACKED))
-			CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x84, M_GetText("Blue flag"), 0x80);
+			CONS_Printf(M_GetText("The \204Blue flag\200 has returned to base.\n"));
 
 		// Assumedly in splitscreen players will be on opposing teams
 		if (players[consoleplayer].ctfteam == 2 || splitscreen)
@@ -10047,11 +10014,12 @@ void P_MobjThinker(mobj_t *mobj)
 	// Sector special (2,8) allows ANY mobj to trigger a linedef exec
 	if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
 	{
-		sector_t *sec2;
-
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
+		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
 		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-			P_LinedefExecute(sec2->tag, mobj, sec2);
+		{
+			mtag_t tag = Tag_FGet(&sec2->tags);
+			P_LinedefExecute(tag, mobj, sec2);
+		}
 	}
 
 	if (mobj->scale != mobj->destscale)
@@ -10275,14 +10243,19 @@ void P_PushableThinker(mobj_t *mobj)
 	sec = mobj->subsector->sector;
 
 	if (GETSECSPECIAL(sec->special, 2) == 1 && mobj->z == sec->floorheight)
-		P_LinedefExecute(sec->tag, mobj, sec);
-//	else if (GETSECSPECIAL(sec->special, 2) == 8)
 	{
-		sector_t *sec2;
+		mtag_t tag = Tag_FGet(&sec->tags);
+		P_LinedefExecute(tag, mobj, sec);
+	}
 
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
+//	else if (GETSECSPECIAL(sec->special, 2) == 8)
+	{
+		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
 		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-			P_LinedefExecute(sec2->tag, mobj, sec2);
+		{
+			mtag_t tag = Tag_FGet(&sec2->tags);
+			P_LinedefExecute(tag, mobj, sec2);
+		}
 	}
 
 	// it has to be pushable RIGHT NOW for this part to happen
@@ -10505,13 +10478,16 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 	if ((maptol & TOL_ERZ3) && !(mobj->type == MT_BLACKEGGMAN))
 		mobj->destscale = FRACUNIT/2;
 
+	// Sprite rendering
+	mobj->blendmode = AST_TRANSLUCENT;
+	mobj->spritexscale = mobj->spriteyscale = mobj->scale;
+	mobj->spritexoffset = mobj->spriteyoffset = 0;
+	mobj->floorspriteslope = NULL;
+
 	// set subsector and/or block links
 	P_SetThingPosition(mobj);
 	I_Assert(mobj->subsector != NULL);
 
-	// Make sure scale matches destscale immediately when spawned
-	P_SetScale(mobj, mobj->destscale);
-
 	mobj->floorz   = P_GetSectorFloorZAt  (mobj->subsector->sector, x, y);
 	mobj->ceilingz = P_GetSectorCeilingZAt(mobj->subsector->sector, x, y);
 
@@ -10906,6 +10882,22 @@ static inline precipmobj_t *P_SpawnSnowMobj(fixed_t x, fixed_t y, fixed_t z, mob
 	return mo;
 }
 
+void *P_CreateFloorSpriteSlope(mobj_t *mobj)
+{
+	if (mobj->floorspriteslope)
+		Z_Free(mobj->floorspriteslope);
+	mobj->floorspriteslope = Z_Calloc(sizeof(pslope_t), PU_LEVEL, NULL);
+	mobj->floorspriteslope->normal.z = FRACUNIT;
+	return (void *)mobj->floorspriteslope;
+}
+
+void P_RemoveFloorSpriteSlope(mobj_t *mobj)
+{
+	if (mobj->floorspriteslope)
+		Z_Free(mobj->floorspriteslope);
+	mobj->floorspriteslope = NULL;
+}
+
 //
 // P_RemoveMobj
 //
@@ -10959,11 +10951,14 @@ void P_RemoveMobj(mobj_t *mobj)
 		P_DelSeclist(sector_list);
 		sector_list = NULL;
 	}
+
 	mobj->flags |= MF_NOSECTOR|MF_NOBLOCKMAP;
 	mobj->subsector = NULL;
 	mobj->state = NULL;
 	mobj->player = NULL;
 
+	P_RemoveFloorSpriteSlope(mobj);
+
 	// stop any playing sound
 	S_StopSound(mobj);
 
@@ -11048,10 +11043,10 @@ void P_RemoveSavegameMobj(mobj_t *mobj)
 }
 
 static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}};
-consvar_t cv_itemrespawntime = {"respawnitemtime", "30", CV_NETVAR|CV_CHEAT, respawnitemtime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_itemrespawn = {"respawnitem", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_itemrespawntime = CVAR_INIT ("respawnitemtime", "30", CV_SAVE|CV_NETVAR|CV_CHEAT, respawnitemtime_cons_t, NULL);
+consvar_t cv_itemrespawn = CVAR_INIT ("respawnitem", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
 static CV_PossibleValue_t flagtime_cons_t[] = {{0, "MIN"}, {300, "MAX"}, {0, NULL}};
-consvar_t cv_flagtime = {"flagtime", "30", CV_NETVAR|CV_CHEAT, flagtime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_flagtime = CVAR_INIT ("flagtime", "30", CV_SAVE|CV_NETVAR|CV_CHEAT, flagtime_cons_t, NULL);
 
 void P_SpawnPrecipitation(void)
 {
@@ -11250,7 +11245,7 @@ void P_PrecipitationEffects(world_t *w)
  * \param mthingtype Mapthing number in question.
  * \return Mobj type; MT_UNKNOWN if nothing found.
  */
-static mobjtype_t P_GetMobjtype(UINT16 mthingtype)
+mobjtype_t P_GetMobjtype(UINT16 mthingtype)
 {
 	mobjtype_t i;
 	for (i = 0; i < NUMMOBJTYPES; i++)
@@ -11405,6 +11400,10 @@ void P_SpawnPlayer(INT32 playernum)
 		p->jumpfactor = skins[p->skin].jumpfactor;
 	}
 
+	// Clear lastlinehit and lastsidehit
+	p->lastsidehit = -1;
+	p->lastlinehit = -1;
+
 	//awayview stuff
 	p->awayviewmobj = NULL;
 	p->awayviewtics = 0;
@@ -11616,7 +11615,7 @@ void P_MovePlayerToStarpost(INT32 playernum)
 mapthing_t *huntemeralds[MAXHUNTEMERALDS];
 INT32 numhuntemeralds;
 
-fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t offset, const boolean flip)
+fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale)
 {
 	const subsector_t *ss = R_PointInSubsector(x, y);
 
@@ -11626,14 +11625,15 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f
 
 	// Establish height.
 	if (flip)
-		return P_GetSectorCeilingZAt(ss->sector, x, y) - offset - mobjinfo[mobjtype].height;
+		return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(scale, offset + mobjinfo[mobjtype].height);
 	else
-		return P_GetSectorFloorZAt(ss->sector, x, y) + offset;
+		return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(scale, offset);
 }
 
 fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y)
 {
-	fixed_t offset = mthing->z << FRACBITS;
+	fixed_t dz = mthing->z << FRACBITS; // Base offset from the floor.
+	fixed_t offset = 0; // Specific scaling object offset.
 	boolean flip = (!!(mobjinfo[mobjtype].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP));
 
 	switch (mobjtype)
@@ -11649,17 +11649,17 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 	case MT_JETTBOMBER:
 	case MT_JETTGUNNER:
 	case MT_EGGMOBILE2:
-		if (!offset)
-			offset = 33*FRACUNIT;
+		if (!dz)
+			dz = 33*FRACUNIT;
 		break;
 	case MT_EGGMOBILE:
-		if (!offset)
-			offset = 128*FRACUNIT;
+		if (!dz)
+			dz = 128*FRACUNIT;
 		break;
 	case MT_GOLDBUZZ:
 	case MT_REDBUZZ:
-		if (!offset)
-			offset = 288*FRACUNIT;
+		if (!dz)
+			dz = 288*FRACUNIT;
 		break;
 
 	// Horizontal springs, may float additional units with MTF_AMBUSH.
@@ -11692,7 +11692,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 			offset += mthing->options & MTF_AMBUSH ? 24*FRACUNIT : 0;
 	}
 
-	if (!offset) // Snap to the surfaces when there's no offset set.
+	if (!(dz + offset)) // Snap to the surfaces when there's no offset set.
 	{
 		if (flip)
 			return ONCEILINGZ;
@@ -11700,7 +11700,7 @@ fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mt
 			return ONFLOORZ;
 	}
 
-	return P_GetMobjSpawnHeight(mobjtype, x, y, offset, flip);
+	return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale);
 }
 
 static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
@@ -11799,7 +11799,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 		if (!(G_CoopGametype() || (mthing->options & MTF_EXTRA)))
 			return false; // she doesn't hang out here
 
-		if (!mariomode && !(netgame || multiplayer) && players[consoleplayer].skin == 3)
+		if (!(netgame || multiplayer) && players[consoleplayer].skin == 3)
 			return false; // no doubles
 
 		break;
@@ -11957,9 +11957,6 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
 			return MT_SCORE1K_BOX; // 1,000
 	}
 
-	if (mariomode && i == MT_ROSY)
-		return MT_TOAD; // don't remove on penalty of death
-
 	return i;
 }
 
@@ -12034,8 +12031,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
 	// Find the corresponding linedef special, using angle as tag
-	// P_FindSpecialLineFromTag works here now =D
-	line = P_FindSpecialLineFromTag(9, mthing->angle, -1);
+	line = Tag_FindLineSpecial(9, mthing->angle);
 
 	if (line == -1)
 	{
@@ -12345,7 +12341,7 @@ static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
 	// Find the corresponding linedef special, using angle as tag
-	line = P_FindSpecialLineFromTag(15, mthing->angle, -1);
+	line = Tag_FindLineSpecial(15, mthing->angle);
 
 	if (line == -1)
 	{
@@ -12584,11 +12580,20 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		break;
 	}
 	case MT_SKYBOX:
+	{
+		mtag_t tag = Tag_FGet(&mthing->tags);
+		if (tag < 0 || tag > 15)
+		{
+			CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings)));
+			break;
+		}
+
 		if (mthing->options & MTF_OBJECTSPECIAL)
-			world->skyboxcenterpnts[mthing->extrainfo] = mobj;
+			world->skyboxcenterpnts[tag] = mobj;
 		else
-			world->skyboxviewpnts[mthing->extrainfo] = mobj;
+			world->skyboxviewpnts[tag] = mobj;
 		break;
+	}
 	case MT_EGGSTATUE:
 		if (mthing->options & MTF_EXTRA)
 		{
@@ -13075,12 +13080,18 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
 	mobj = P_SpawnMobj(x, y, z, i);
 	mobj->spawnpoint = mthing;
 
+	P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale));
+	mobj->destscale = FixedMul(mobj->destscale, mthing->scale);
+
 	if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
 		return mobj;
 
 	if (doangle)
 		mobj->angle = FixedAngle(mthing->angle << FRACBITS);
 
+	mobj->pitch = FixedAngle(mthing->pitch << FRACBITS);
+	mobj->roll = FixedAngle(mthing->roll << FRACBITS);
+
 	mthing->mobj = mobj;
 
 	// ignore MTF_ flags and return early
@@ -13177,7 +13188,7 @@ static void P_SpawnHoopInternal(mapthing_t *mthing, INT32 hoopsize, fixed_t size
 	TVector v, *res;
 	fixed_t x = mthing->x << FRACBITS;
 	fixed_t y = mthing->y << FRACBITS;
-	fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, false);
+	fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale);
 
 	hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER);
 	hoopcenter->spawnpoint = mthing;
@@ -13322,7 +13333,7 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t* itemtypes, UINT8 numi
 			itemtypes[r] = P_GetMobjtypeSubstitute(&dummything, itemtypes[r]);
 		}
 	}
-	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, mthing->options & MTF_OBJECTFLIP);
+	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale);
 
 	for (r = 0; r < numitems; r++)
 	{
@@ -13380,7 +13391,7 @@ static void P_SpawnItemCircle(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 n
 			itemtypes[i] = P_GetMobjtypeSubstitute(&dummything, itemtypes[i]);
 		}
 	}
-	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, false);
+	z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, false, mthing->scale);
 
 	for (i = 0; i < numitems; i++)
 	{
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 9e769ce4cc83bec4b5ba741d347e710ef81bd226..d6e93b1f051f15704eb45ef1688dee7f1249d72a 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -194,6 +194,7 @@ typedef enum
 	MF2_AMBUSH         = 1<<27, // Alternate behaviour typically set by MTF_AMBUSH
 	MF2_LINKDRAW       = 1<<28, // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	MF2_SHIELD         = 1<<29, // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
+	MF2_SPLAT          = 1<<30, // Renders as a splat
 	// free: to and including 1<<31
 } mobjflag2_t;
 
@@ -264,6 +265,7 @@ typedef enum {
 	// Ran the thinker this tic.
 	PCF_THUNK = 32,
 } precipflag_t;
+
 // Map Object definition.
 typedef struct mobj_s
 {
@@ -278,13 +280,19 @@ typedef struct mobj_s
 	struct mobj_s **sprev; // killough 8/11/98: change to ptr-to-ptr
 
 	// More drawing info: to determine current sprite.
-	angle_t angle;  // orientation
+	angle_t angle, pitch, roll; // orientation
 	angle_t rollangle;
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
 	UINT16 anim_duration; // for FF_ANIMATE states
 
+	UINT32 renderflags; // render flags
+	INT32 blendmode; // blend mode
+	fixed_t spritexscale, spriteyscale;
+	fixed_t spritexoffset, spriteyoffset;
+	struct pslope_s *floorspriteslope; // The slope that the floorsprite is rotated by
+
 	struct msecnode_s *touching_sectorlist; // a linked list of sectors where this object appears
 
 	struct subsector_s *subsector; // Subsector the mobj resides in.
@@ -403,13 +411,19 @@ typedef struct precipmobj_s
 	struct precipmobj_s **sprev; // killough 8/11/98: change to ptr-to-ptr
 
 	// More drawing info: to determine current sprite.
-	angle_t angle;  // orientation
+	angle_t angle, pitch, roll; // orientation
 	angle_t rollangle;
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
 	UINT16 anim_duration; // for FF_ANIMATE states
 
+	UINT32 renderflags; // render flags
+	INT32 blendmode; // blend mode
+	fixed_t spritexscale, spriteyscale;
+	fixed_t spritexoffset, spriteyoffset;
+	struct pslope_s *floorspriteslope; // The slope that the floorsprite is rotated by
+
 	struct mprecipsecnode_s *touching_sectorlist; // a linked list of sectors where this object appears
 
 	struct subsector_s *subsector; // Subsector the mobj resides in.
@@ -458,7 +472,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing);
 void P_MovePlayerToStarpost(INT32 playernum);
 void P_AfterPlayerSpawn(INT32 playernum);
 
-fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t offset, const boolean flip);
+fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale);
 fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y);
 
 mobj_t *P_SpawnMapThing(mapthing_t *mthing);
@@ -468,6 +482,8 @@ void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime);
 void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle);
 void P_SpawnPrecipitation(void);
 void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, statenum_t nstate, angle_t rotangle, boolean spawncenter);
+void *P_CreateFloorSpriteSlope(mobj_t *mobj);
+void P_RemoveFloorSpriteSlope(mobj_t *mobj);
 boolean P_BossTargetPlayer(mobj_t *actor, boolean closest);
 boolean P_SupermanLook4Players(mobj_t *actor);
 void P_DestroyRobots(void);
@@ -477,6 +493,12 @@ void P_NullPrecipThinker(precipmobj_t *mobj);
 void P_RemovePrecipMobj(precipmobj_t *mobj);
 void P_SetScale(mobj_t *mobj, fixed_t newscale);
 void P_XYMovement(mobj_t *mo);
+void P_RingXYMovement(mobj_t *mo);
+void P_SceneryXYMovement(mobj_t *mo);
+boolean P_ZMovement(mobj_t *mo);
+void P_RingZMovement(mobj_t *mo);
+boolean P_SceneryZMovement(mobj_t *mo);
+void P_PlayerZMovement(mobj_t *mo);
 void P_EmeraldManager(void);
 
 extern INT32 modulothing;
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 8ce8317a5c81565b56fcb919af3c2953a54c51cc..a78d59280ebd49f976e7fbe4fbac4783b5424e77 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -191,47 +191,44 @@ boolean P_BBoxInsidePolyobj(polyobj_t *po, fixed_t *bbox)
 	return true;
 }
 
-// Finds the 'polyobject settings' linedef for a polyobject
-// the polyobject's id should be set as its tag
-static void Polyobj_GetInfo(polyobj_t *po)
+// Gets the polyobject's settings from its first line
+// args[0] of the first line should be the polyobject's id
+static void Polyobj_GetInfo(polyobj_t *po, line_t *line)
 {
-	INT32 i = P_FindSpecialLineFromTag(POLYINFO_SPECIALNUM, po->id, -1);
-
-	po->flags = POF_SOLID|POF_TESTHEIGHT|POF_RENDERSIDES;
-
-	if (i == -1)
-		return; // no extra settings to apply, let's leave it
-
-	po->parent = lines[i].frontsector->special;
+	po->parent = line->args[1];
 	if (po->parent == po->id) // do not allow a self-reference
 		po->parent = -1;
 
-	po->translucency = (lines[i].flags & ML_DONTPEGTOP)
-						? (sides[lines[i].sidenum[0]].textureoffset>>FRACBITS)
-						: ((lines[i].frontsector->floorheight>>FRACBITS) / 100);
+	po->translucency = max(min(line->args[2], NUMTRANSMAPS), 0);
 
-	po->translucency = max(min(po->translucency, NUMTRANSMAPS), 0);
+	po->flags = POF_SOLID|POF_TESTHEIGHT|POF_RENDERSIDES|POF_RENDERPLANES;
 
-	if (lines[i].flags & ML_EFFECT1)
+	if (line->args[3] & TMPF_NOINSIDES)
 		po->flags |= POF_ONESIDE;
 
-	if (lines[i].flags & ML_EFFECT2)
+	if (line->args[3] & TMPF_INTANGIBLE)
 		po->flags &= ~POF_SOLID;
 
-	if (lines[i].flags & ML_EFFECT3)
+	if (line->args[3] & TMPF_PUSHABLESTOP)
 		po->flags |= POF_PUSHABLESTOP;
 
-	if (lines[i].flags & ML_EFFECT4)
-		po->flags |= POF_RENDERPLANES;
+	if (line->args[3] & TMPF_INVISIBLEPLANES)
+		po->flags &= ~POF_RENDERPLANES;
 
-	/*if (lines[i].flags & ML_EFFECT5)
+	/*if (line->args[3] & TMPF_DONTCLIPPLANES)
 		po->flags &= ~POF_CLIPPLANES;*/
 
-	if (lines[i].flags & ML_EFFECT6)
+	if (line->args[3] & TMPF_SPLAT)
 		po->flags |= POF_SPLAT;
 
-	if (lines[i].flags & ML_NOCLIMB) // Has a linedef executor
+	if (line->args[3] & TMPF_EXECUTOR) // Has a linedef executor
 		po->flags |= POF_LDEXEC;
+
+	// TODO: support customized damage somehow?
+	if (line->args[3] & TMPF_CRUSH)
+		po->damage = 3;
+
+	po->triggertag = line->args[4];
 }
 
 // Reallocating array maintenance
@@ -470,10 +467,6 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 
 	po->id = id;
 
-	// TODO: support customized damage somehow?
-	if (spawnSpot->info->doomednum == POLYOBJ_SPAWNCRUSH_DOOMEDNUM)
-		po->damage = 3;
-
 	// set to default thrust; may be modified by attached thinkers
 	// TODO: support customized thrust?
 	po->thrust = FRACUNIT;
@@ -495,10 +488,10 @@ static void Polyobj_spawnPolyObj(INT32 num, mobj_t *spawnSpot, INT32 id)
 		if (seg->linedef->special != POLYOBJ_START_LINE)
 			continue;
 
-		if (seg->linedef->tag != po->id)
+		if (seg->linedef->args[0] != po->id)
 			continue;
 
-		Polyobj_GetInfo(po); // apply extra settings if they exist!
+		Polyobj_GetInfo(po, seg->linedef); // apply extra settings if they exist!
 
 		// save original flags and translucency to reference later for netgames!
 		po->spawnflags = po->flags;
@@ -550,10 +543,11 @@ static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
 	polyobj_t *po;
 	vertex_t  dist, sspot;
 	size_t i;
+	mtag_t tag = Tag_FGet(&anchor->tags);
 
-	if (!(po = Polyobj_GetForNum(anchor->angle)))
+	if (!(po = Polyobj_GetForNum(tag)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "Bad polyobject %d for anchor point\n", anchor->angle);
+		CONS_Debug(DBG_POLYOBJ, "Bad polyobject %d for anchor point\n", tag);
 		return;
 	}
 
@@ -970,7 +964,7 @@ static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
 
 
 // Moves a polyobject on the x-y plane.
-static boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean checkmobjs)
+boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean checkmobjs)
 {
 	size_t i;
 	vertex_t vec;
@@ -1156,7 +1150,7 @@ static void Polyobj_rotateThings(polyobj_t *po, vector2_t origin, angle_t delta,
 }
 
 // Rotates a polyobject around its start point.
-static boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs)
+boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs)
 {
 	size_t i;
 	angle_t angle;
@@ -1301,8 +1295,7 @@ void Polyobj_InitLevel(void)
 
 		mo = (mobj_t *)th;
 
-		if (mo->info->doomednum == POLYOBJ_SPAWN_DOOMEDNUM ||
-			mo->info->doomednum == POLYOBJ_SPAWNCRUSH_DOOMEDNUM)
+		if (mo->info->doomednum == POLYOBJ_SPAWN_DOOMEDNUM)
 		{
 			++world->numPolyObjects;
 
@@ -1337,7 +1330,7 @@ void Polyobj_InitLevel(void)
 		{
 			qitem = (mobjqitem_t *)M_QueueIterator(&spawnqueue);
 
-			Polyobj_spawnPolyObj(i, qitem->mo, qitem->mo->spawnpoint->angle);
+			Polyobj_spawnPolyObj(i, qitem->mo, Tag_FGet(&qitem->mo->spawnpoint->tags));
 		}
 
 		// move polyobjects to spawn points
@@ -2439,10 +2432,11 @@ boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
 	polymove_t *th;
 	size_t i;
 	INT32 start;
+	mtag_t tag = pfdata->polyObjNum;
 
-	if (!(po = Polyobj_GetForNum(pfdata->polyObjNum)))
+	if (!(po = Polyobj_GetForNum(tag)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", pfdata->polyObjNum);
+		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", tag);
 		return false;
 	}
 
@@ -2465,7 +2459,7 @@ boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
 	po->thinker = &th->thinker;
 
 	// set fields
-	th->polyObjNum = pfdata->polyObjNum;
+	th->polyObjNum = tag;
 	th->distance   = 0;
 	th->speed      = pfdata->speed;
 	th->angle      = pfdata->angle;
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index 506f02d2a3295c77ae596b1ad36b146f992249d6..16ebe0ad1c3bdcba6f5e9a24673dc5f6fb02b5f3 100644
--- a/src/p_polyobj.h
+++ b/src/p_polyobj.h
@@ -26,10 +26,8 @@
 
 #define POLYOBJ_ANCHOR_DOOMEDNUM     760
 #define POLYOBJ_SPAWN_DOOMEDNUM      761
-#define POLYOBJ_SPAWNCRUSH_DOOMEDNUM 762 // todo: REMOVE
 
 #define POLYOBJ_START_LINE    20
-#define POLYINFO_SPECIALNUM   22
 
 typedef enum
 {
@@ -52,6 +50,18 @@ typedef enum
 	POF_SPLAT             = 0x2000,    ///< Use splat flat renderer (treat cyan pixels as invisible).
 } polyobjflags_e;
 
+typedef enum
+{
+	TMPF_NOINSIDES       = 1,
+	TMPF_INTANGIBLE      = 1<<1,
+	TMPF_PUSHABLESTOP    = 1<<2,
+	TMPF_INVISIBLEPLANES = 1<<3,
+	TMPF_EXECUTOR        = 1<<4,
+	TMPF_CRUSH           = 1<<5,
+	TMPF_SPLAT           = 1<<6,
+	//TMPF_DONTCLIPPLANES  = 1<<7,
+} textmappolyobjectflags_t;
+
 //
 // Polyobject Structure
 //
@@ -97,6 +107,7 @@ typedef struct polyobj_s
 
 	UINT8 isBad;         // a bad polyobject: should not be rendered/manipulated
 	INT32 translucency; // index to translucency tables
+	INT16 triggertag;   // Tag of linedef executor to trigger on touch
 
 	struct visplane_s *visplane; // polyobject's visplane, for ease of putting into the list later
 
@@ -325,6 +336,8 @@ typedef struct polyfadedata_s
 // Functions
 //
 
+boolean Polyobj_moveXY(polyobj_t *po, fixed_t x, fixed_t y, boolean checkmobjs);
+boolean Polyobj_rotate(polyobj_t *po, angle_t delta, UINT8 turnthings, boolean checkmobjs);
 polyobj_t *Polyobj_GetForNum(INT32 id);
 void Polyobj_InitLevel(void);
 void Polyobj_MoveOnLoad(polyobj_t *po, angle_t angle, fixed_t x, fixed_t y);
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 9c13a242c65cc7fa4f6806543d1d7ac855bc2804..08743bff35806eaf8486975eaacb912d8a3b4f6c 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -23,6 +23,8 @@
 #include "p_saveg.h"
 #include "p_world.h"
 #include "r_data.h"
+#include "r_textures.h"
+#include "r_things.h"
 #include "r_skins.h"
 #include "r_state.h"
 #include "w_wad.h"
@@ -101,13 +103,16 @@ static void P_NetArchivePlayers(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
+		WRITESINT8(save_p, (SINT8)adminplayers[i]);
+
 		if (!playeringame[i])
 			continue;
 
 		flags = 0;
 
-		// no longer send ticcmds, player name, skin, or color
+		// no longer send ticcmds
 
+		WRITESTRINGN(save_p, player_names[i], MAXPLAYERNAME);
 		WRITEINT32(save_p, players[i].worldnum);
 		WRITEINT16(save_p, players[i].angleturn);
 		WRITEINT16(save_p, players[i].oldrelangleturn);
@@ -138,6 +143,9 @@ static void P_NetArchivePlayers(void)
 		WRITEUINT16(save_p, players[i].flashpal);
 		WRITEUINT16(save_p, players[i].flashcount);
 
+		WRITEUINT8(save_p, players[i].skincolor);
+		WRITEINT32(save_p, players[i].skin);
+		WRITEUINT32(save_p, players[i].availabilities);
 		WRITEUINT32(save_p, players[i].score);
 		WRITEFIXED(save_p, players[i].dashspeed);
 		WRITESINT8(save_p, players[i].lives);
@@ -309,6 +317,8 @@ static void P_NetUnArchivePlayers(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
+		adminplayers[i] = (INT32)READSINT8(save_p);
+
 		// Do NOT memset player struct to 0
 		// other areas may initialize data elsewhere
 		//memset(&players[i], 0, sizeof (player_t));
@@ -316,9 +326,8 @@ static void P_NetUnArchivePlayers(void)
 			continue;
 
 		// NOTE: sending tics should (hopefully) no longer be necessary
-		// sending player names, skin and color should not be necessary at all!
-		// (that data is handled in the server config now)
 
+		READSTRINGN(save_p, player_names[i], MAXPLAYERNAME);
 		players[i].worldnum = READINT32(save_p);
 		players[i].angleturn = READINT16(save_p);
 		players[i].oldrelangleturn = READINT16(save_p);
@@ -349,6 +358,9 @@ static void P_NetUnArchivePlayers(void)
 		players[i].flashpal = READUINT16(save_p);
 		players[i].flashcount = READUINT16(save_p);
 
+		players[i].skincolor = READUINT8(save_p);
+		players[i].skin = READINT32(save_p);
+		players[i].availabilities = READUINT32(save_p);
 		players[i].score = READUINT32(save_p);
 		players[i].dashspeed = READFIXED(save_p); // dashing speed
 		players[i].lives = READSINT8(save_p);
@@ -794,10 +806,38 @@ static void P_NetUnArchiveWaypoints(void)
 #define LD_DIFF2    0x80
 
 // diff2 flags
-#define LD_S2TEXOFF 0x01
-#define LD_S2TOPTEX 0x02
-#define LD_S2BOTTEX 0x04
-#define LD_S2MIDTEX 0x08
+#define LD_S2TEXOFF      0x01
+#define LD_S2TOPTEX      0x02
+#define LD_S2BOTTEX      0x04
+#define LD_S2MIDTEX      0x08
+#define LD_ARGS          0x10
+#define LD_STRINGARGS    0x20
+#define LD_EXECUTORDELAY 0x40
+
+static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli)
+{
+	UINT8 i;
+	for (i = 0; i < NUMLINEARGS; i++)
+		if (li->args[i] != spawnli->args[i])
+			return false;
+
+	return true;
+}
+
+static boolean P_AreStringArgsEqual(const line_t *li, const line_t *spawnli)
+{
+	UINT8 i;
+	for (i = 0; i < NUMLINESTRINGARGS; i++)
+	{
+		if (!li->stringargs[i])
+			return !spawnli->stringargs[i];
+
+		if (strcmp(li->stringargs[i], spawnli->stringargs[i]))
+			return false;
+	}
+
+	return true;
+}
 
 #define FD_FLAGS 0x01
 #define FD_ALPHA 0x02
@@ -890,7 +930,7 @@ static void UnArchiveFFloors(const sector_t *ss)
 
 static void ArchiveSectors(void)
 {
-	size_t i;
+	size_t i, j;
 	const sector_t *ss = archiveworld->sectors;
 	const sector_t *spawnss = archiveworld->spawnsectors;
 	UINT8 diff, diff2, diff3;
@@ -928,10 +968,8 @@ static void ArchiveSectors(void)
 		if (ss->ceilingpic_angle != spawnss->ceilingpic_angle)
 			diff2 |= SD_CEILANG;
 
-		if (ss->tag != spawnss->tag)
+		if (!Tag_Compare(&ss->tags, &spawnss->tags))
 			diff2 |= SD_TAG;
-		if (ss->nexttag != spawnss->nexttag || ss->firsttag != spawnss->firsttag)
-			diff3 |= SD_TAGLIST;
 
 		if (ss->extra_colormap != spawnss->extra_colormap)
 			diff3 |= SD_COLORMAP;
@@ -979,12 +1017,11 @@ static void ArchiveSectors(void)
 				WRITEANGLE(save_p, ss->floorpic_angle);
 			if (diff2 & SD_CEILANG)
 				WRITEANGLE(save_p, ss->ceilingpic_angle);
-			if (diff2 & SD_TAG) // save only the tag
-				WRITEINT16(save_p, ss->tag);
-			if (diff3 & SD_TAGLIST) // save both firsttag and nexttag
-			{ // either of these could be changed even if tag isn't
-				WRITEINT32(save_p, ss->firsttag);
-				WRITEINT32(save_p, ss->nexttag);
+			if (diff2 & SD_TAG)
+			{
+				WRITEUINT32(save_p, ss->tags.count);
+				for (j = 0; j < ss->tags.count; j++)
+					WRITEINT16(save_p, ss->tags.tags[j]);
 			}
 
 			if (diff3 & SD_COLORMAP)
@@ -1002,7 +1039,7 @@ static void ArchiveSectors(void)
 
 static void UnArchiveSectors(void)
 {
-	UINT16 i;
+	UINT16 i, j;
 	UINT8 diff, diff2, diff3;
 	for (;;)
 	{
@@ -1056,13 +1093,29 @@ static void UnArchiveSectors(void)
 		if (diff2 & SD_CEILANG)
 			unarchiveworld->sectors[i].ceilingpic_angle = READANGLE(save_p);
 		if (diff2 & SD_TAG)
-			unarchiveworld->sectors[i].tag = READINT16(save_p); // DON'T use P_ChangeSectorTag
-		if (diff3 & SD_TAGLIST)
 		{
-			unarchiveworld->sectors[i].firsttag = READINT32(save_p);
-			unarchiveworld->sectors[i].nexttag = READINT32(save_p);
+			size_t ncount = READUINT32(save_p);
+
+			// Remove entries from global lists.
+			for (j = 0; j < unarchiveworld->sectors[i].tags.count; j++)
+				Taggroup_Remove(unarchiveworld->tags_sectors, unarchiveworld->sectors[i].tags.tags[j], i);
+
+			// Reallocate if size differs.
+			if (ncount != sectors[i].tags.count)
+			{
+				unarchiveworld->sectors[i].tags.count = ncount;
+				unarchiveworld->sectors[i].tags.tags = Z_Realloc(unarchiveworld->sectors[i].tags.tags, ncount*sizeof(mtag_t), PU_LEVEL, NULL);
+			}
+
+			for (j = 0; j < ncount; j++)
+				unarchiveworld->sectors[i].tags.tags[j] = READINT16(save_p);
+
+			// Add new entries.
+			for (j = 0; j < sectors[i].tags.count; j++)
+				Taggroup_Remove(unarchiveworld->tags_sectors, unarchiveworld->sectors[i].tags.tags[j], i);
 		}
 
+
 		if (diff3 & SD_COLORMAP)
 			unarchiveworld->sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
 		if (diff3 & SD_CRUMBLESTATE)
@@ -1092,6 +1145,15 @@ static void ArchiveLines(void)
 		if (spawnli->special == 321 || spawnli->special == 322) // only reason li->callcount would be non-zero is if either of these are involved
 			diff |= LD_CLLCOUNT;
 
+		if (!P_AreArgsEqual(li, spawnli))
+			diff2 |= LD_ARGS;
+
+		if (!P_AreStringArgsEqual(li, spawnli))
+			diff2 |= LD_STRINGARGS;
+
+		if (li->executordelay != spawnli->executordelay)
+			diff2 |= LD_EXECUTORDELAY;
+
 		if (li->sidenum[0] != 0xffff)
 		{
 			si = &archiveworld->sides[li->sidenum[0]];
@@ -1118,10 +1180,11 @@ static void ArchiveLines(void)
 				diff2 |= LD_S2BOTTEX;
 			if (si->midtexture != spawnsi->midtexture)
 				diff2 |= LD_S2MIDTEX;
-			if (diff2)
-				diff |= LD_DIFF2;
 		}
 
+		if (diff2)
+			diff |= LD_DIFF2;
+
 		if (diff)
 		{
 			WRITEINT16(save_p, i);
@@ -1154,6 +1217,33 @@ static void ArchiveLines(void)
 				WRITEINT32(save_p, si->bottomtexture);
 			if (diff2 & LD_S2MIDTEX)
 				WRITEINT32(save_p, si->midtexture);
+			if (diff2 & LD_ARGS)
+			{
+				UINT8 j;
+				for (j = 0; j < NUMLINEARGS; j++)
+					WRITEINT32(save_p, li->args[j]);
+			}
+			if (diff2 & LD_STRINGARGS)
+			{
+				UINT8 j;
+				for (j = 0; j < NUMLINESTRINGARGS; j++)
+				{
+					size_t len, k;
+
+					if (!li->stringargs[j])
+					{
+						WRITEINT32(save_p, 0);
+						continue;
+					}
+
+					len = strlen(li->stringargs[j]);
+					WRITEINT32(save_p, len);
+					for (k = 0; k < len; k++)
+						WRITECHAR(save_p, li->stringargs[j][k]);
+				}
+			}
+			if (diff2 & LD_EXECUTORDELAY)
+				WRITEINT32(save_p, li->executordelay);
 		}
 	}
 	WRITEUINT16(save_p, 0xffff);
@@ -1209,6 +1299,36 @@ static void UnArchiveLines(void)
 			si->bottomtexture = READINT32(save_p);
 		if (diff2 & LD_S2MIDTEX)
 			si->midtexture = READINT32(save_p);
+		if (diff2 & LD_ARGS)
+		{
+			UINT8 j;
+			for (j = 0; j < NUMLINEARGS; j++)
+				li->args[j] = READINT32(save_p);
+		}
+		if (diff2 & LD_STRINGARGS)
+		{
+			UINT8 j;
+			for (j = 0; j < NUMLINESTRINGARGS; j++)
+			{
+				size_t len = READINT32(save_p);
+				size_t k;
+
+				if (!len)
+				{
+					Z_Free(li->stringargs[j]);
+					li->stringargs[j] = NULL;
+					continue;
+				}
+
+				li->stringargs[j] = Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, NULL);
+				for (k = 0; k < len; k++)
+					li->stringargs[j][k] = READCHAR(save_p);
+				li->stringargs[j][len] = '\0';
+			}
+		}
+		if (diff2 & LD_EXECUTORDELAY)
+			li->executordelay = READINT32(save_p);
+
 	}
 }
 
@@ -1296,6 +1416,13 @@ typedef enum
 	MD2_MIRRORED     = 1<<13,
 	MD2_ROLLANGLE    = 1<<14,
 	MD2_SHADOWSCALE  = 1<<15,
+	MD2_RENDERFLAGS  = 1<<16,
+	MD2_BLENDMODE    = 1<<17,
+	MD2_SPRITEXSCALE = 1<<18,
+	MD2_SPRITEYSCALE = 1<<19,
+	MD2_SPRITEXOFFSET = 1<<20,
+	MD2_SPRITEYOFFSET = 1<<21,
+	MD2_FLOORSPRITESLOPE = 1<<22,
 } mobj_diff2_t;
 
 typedef enum
@@ -1378,7 +1505,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 {
 	const mobj_t *mobj = (const mobj_t *)th;
 	UINT32 diff;
-	UINT16 diff2;
+	UINT32 diff2;
 
 	// Ignore stationary hoops - these will be respawned from mapthings.
 	if (mobj->type == MT_HOOP)
@@ -1399,7 +1526,9 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 
 		if ((mobj->x != mobj->spawnpoint->x << FRACBITS) ||
 			(mobj->y != mobj->spawnpoint->y << FRACBITS) ||
-			(mobj->angle != FixedAngle(mobj->spawnpoint->angle*FRACUNIT)))
+			(mobj->angle != FixedAngle(mobj->spawnpoint->angle*FRACUNIT)) ||
+			(mobj->pitch != FixedAngle(mobj->spawnpoint->pitch*FRACUNIT)) ||
+			(mobj->roll != FixedAngle(mobj->spawnpoint->roll*FRACUNIT)) )
 			diff |= MD_POS;
 
 		if (mobj->info->doomednum != mobj->spawnpoint->type)
@@ -1506,6 +1635,29 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_ROLLANGLE;
 	if (mobj->shadowscale)
 		diff2 |= MD2_SHADOWSCALE;
+	if (mobj->renderflags)
+		diff2 |= MD2_RENDERFLAGS;
+	if (mobj->blendmode != AST_TRANSLUCENT)
+		diff2 |= MD2_BLENDMODE;
+	if (mobj->spritexscale != FRACUNIT)
+		diff2 |= MD2_SPRITEXSCALE;
+	if (mobj->spriteyscale != FRACUNIT)
+		diff2 |= MD2_SPRITEYSCALE;
+	if (mobj->spritexoffset)
+		diff2 |= MD2_SPRITEXOFFSET;
+	if (mobj->spriteyoffset)
+		diff2 |= MD2_SPRITEYOFFSET;
+	if (mobj->floorspriteslope)
+	{
+		pslope_t *slope = mobj->floorspriteslope;
+		if (slope->zangle || slope->zdelta || slope->xydirection
+		|| slope->o.x || slope->o.y || slope->o.z
+		|| slope->d.x || slope->d.y
+		|| slope->normal.x || slope->normal.y
+		|| (slope->normal.z != FRACUNIT))
+			diff2 |= MD2_FLOORSPRITESLOPE;
+	}
+
 	if (diff2 != 0)
 		diff |= MD_MORE;
 
@@ -1516,7 +1668,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, type);
 	WRITEUINT32(save_p, diff);
 	if (diff & MD_MORE)
-		WRITEUINT16(save_p, diff2);
+		WRITEUINT32(save_p, diff2);
 
 	// save pointer, at load time we will search this pointer to reinitilize pointers
 	WRITEUINT32(save_p, (size_t)mobj);
@@ -1555,6 +1707,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEFIXED(save_p, mobj->x);
 		WRITEFIXED(save_p, mobj->y);
 		WRITEANGLE(save_p, mobj->angle);
+		WRITEANGLE(save_p, mobj->pitch);
+		WRITEANGLE(save_p, mobj->roll);
 	}
 	if (diff & MD_MOM)
 	{
@@ -1646,6 +1800,37 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEANGLE(save_p, mobj->rollangle);
 	if (diff2 & MD2_SHADOWSCALE)
 		WRITEFIXED(save_p, mobj->shadowscale);
+	if (diff2 & MD2_RENDERFLAGS)
+		WRITEUINT32(save_p, mobj->renderflags);
+	if (diff2 & MD2_BLENDMODE)
+		WRITEINT32(save_p, mobj->blendmode);
+	if (diff2 & MD2_SPRITEXSCALE)
+		WRITEFIXED(save_p, mobj->spritexscale);
+	if (diff2 & MD2_SPRITEYSCALE)
+		WRITEFIXED(save_p, mobj->spriteyscale);
+	if (diff2 & MD2_SPRITEXOFFSET)
+		WRITEFIXED(save_p, mobj->spritexoffset);
+	if (diff2 & MD2_SPRITEYOFFSET)
+		WRITEFIXED(save_p, mobj->spriteyoffset);
+	if (diff2 & MD2_FLOORSPRITESLOPE)
+	{
+		pslope_t *slope = mobj->floorspriteslope;
+
+		WRITEFIXED(save_p, slope->zdelta);
+		WRITEANGLE(save_p, slope->zangle);
+		WRITEANGLE(save_p, slope->xydirection);
+
+		WRITEFIXED(save_p, slope->o.x);
+		WRITEFIXED(save_p, slope->o.y);
+		WRITEFIXED(save_p, slope->o.z);
+
+		WRITEFIXED(save_p, slope->d.x);
+		WRITEFIXED(save_p, slope->d.y);
+
+		WRITEFIXED(save_p, slope->normal.x);
+		WRITEFIXED(save_p, slope->normal.y);
+		WRITEFIXED(save_p, slope->normal.z);
+	}
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 	WRITEUINT32(save_p, mobj->worldnum);
@@ -2432,14 +2617,14 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 	thinker_t *next;
 	mobj_t *mobj;
 	UINT32 diff;
-	UINT16 diff2;
+	UINT32 diff2;
 	INT32 i;
 	fixed_t z, floorz, ceilingz;
 	ffloor_t *floorrover = NULL, *ceilingrover = NULL;
 
 	diff = READUINT32(save_p);
 	if (diff & MD_MORE)
-		diff2 = READUINT16(save_p);
+		diff2 = READUINT32(save_p);
 	else
 		diff2 = 0;
 
@@ -2514,12 +2699,16 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->x = READFIXED(save_p);
 		mobj->y = READFIXED(save_p);
 		mobj->angle = READANGLE(save_p);
+		mobj->pitch = READANGLE(save_p);
+		mobj->roll = READANGLE(save_p);
 	}
 	else
 	{
 		mobj->x = mobj->spawnpoint->x << FRACBITS;
 		mobj->y = mobj->spawnpoint->y << FRACBITS;
 		mobj->angle = FixedAngle(mobj->spawnpoint->angle*FRACUNIT);
+		mobj->pitch = FixedAngle(mobj->spawnpoint->pitch*FRACUNIT);
+		mobj->roll = FixedAngle(mobj->spawnpoint->roll*FRACUNIT);
 	}
 	if (diff & MD_MOM)
 	{
@@ -2644,7 +2833,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 	if (diff2 & MD2_HPREV)
 		mobj->hprev = (mobj_t *)(size_t)READUINT32(save_p);
 	if (diff2 & MD2_SLOPE)
-		mobj->standingslope = P_SlopeById(READUINT16(save_p));
+		mobj->standingslope = P_SlopeById(unarchiveworld->slopelist, READUINT16(save_p));
 	if (diff2 & MD2_COLORIZED)
 		mobj->colorized = READUINT8(save_p);
 	if (diff2 & MD2_MIRRORED)
@@ -2653,6 +2842,43 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->rollangle = READANGLE(save_p);
 	if (diff2 & MD2_SHADOWSCALE)
 		mobj->shadowscale = READFIXED(save_p);
+	if (diff2 & MD2_RENDERFLAGS)
+		mobj->renderflags = READUINT32(save_p);
+	if (diff2 & MD2_BLENDMODE)
+		mobj->blendmode = READINT32(save_p);
+	else
+		mobj->blendmode = AST_TRANSLUCENT;
+	if (diff2 & MD2_SPRITEXSCALE)
+		mobj->spritexscale = READFIXED(save_p);
+	else
+		mobj->spritexscale = FRACUNIT;
+	if (diff2 & MD2_SPRITEYSCALE)
+		mobj->spriteyscale = READFIXED(save_p);
+	else
+		mobj->spriteyscale = FRACUNIT;
+	if (diff2 & MD2_SPRITEXOFFSET)
+		mobj->spritexoffset = READFIXED(save_p);
+	if (diff2 & MD2_SPRITEYOFFSET)
+		mobj->spriteyoffset = READFIXED(save_p);
+	if (diff2 & MD2_FLOORSPRITESLOPE)
+	{
+		pslope_t *slope = (pslope_t *)P_CreateFloorSpriteSlope(mobj);
+
+		slope->zdelta = READFIXED(save_p);
+		slope->zangle = READANGLE(save_p);
+		slope->xydirection = READANGLE(save_p);
+
+		slope->o.x = READFIXED(save_p);
+		slope->o.y = READFIXED(save_p);
+		slope->o.z = READFIXED(save_p);
+
+		slope->d.x = READFIXED(save_p);
+		slope->d.y = READFIXED(save_p);
+
+		slope->normal.x = READFIXED(save_p);
+		slope->normal.y = READFIXED(save_p);
+		slope->normal.z = READFIXED(save_p);
+	}
 
 	if (diff & MD_REDFLAG)
 	{
@@ -3853,14 +4079,17 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	playeringame[consoleplayer] = true;
 }
 
-static void P_NetArchiveMisc(void)
+static void P_NetArchiveMisc(boolean resending)
 {
 	INT32 i;
 
 	WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
 
+	if (resending)
+		WRITEUINT32(save_p, gametic);
 	WRITEINT16(save_p, baseworld->gamemap);
 	WRITEINT16(save_p, gamestate);
+	WRITEINT16(save_p, gametype);
 
 	{
 		UINT32 pig = 0;
@@ -3892,6 +4121,12 @@ static void P_NetArchiveMisc(void)
 	WRITEINT32(save_p, sstimer);
 	WRITEUINT32(save_p, bluescore);
 	WRITEUINT32(save_p, redscore);
+
+	WRITEUINT16(save_p, skincolor_redteam);
+	WRITEUINT16(save_p, skincolor_blueteam);
+	WRITEUINT16(save_p, skincolor_redring);
+	WRITEUINT16(save_p, skincolor_bluering);
+
 	WRITEINT32(save_p, modulothing);
 
 	WRITEINT16(save_p, autobalance);
@@ -3923,7 +4158,7 @@ static void P_NetArchiveMisc(void)
 		WRITEUINT8(save_p, 0x2e);
 }
 
-static inline boolean P_NetUnArchiveMisc(void)
+static inline boolean P_NetUnArchiveMisc(boolean reloading)
 {
 	INT32 i;
 
@@ -3931,6 +4166,8 @@ static inline boolean P_NetUnArchiveMisc(void)
 		I_Error("Bad $$$.sav at archive block Misc");
 
 	P_UnloadWorldList();
+	if (reloading)
+		gametic = READUINT32(save_p);
 
 	gamemap = READINT16(save_p);
 
@@ -3945,6 +4182,8 @@ static inline boolean P_NetUnArchiveMisc(void)
 
 	G_SetGamestate(READINT16(save_p));
 
+	gametype = READINT16(save_p);
+
 	{
 		UINT32 pig = READUINT32(save_p);
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -3958,7 +4197,7 @@ static inline boolean P_NetUnArchiveMisc(void)
 
 	tokenlist = READUINT32(save_p);
 
-	if (!P_LoadLevel(&players[consoleplayer], false, true))
+	if (!P_LoadLevel(&players[consoleplayer], false, true, reloading))
 		return false;
 
 	// get the time
@@ -3978,6 +4217,12 @@ static inline boolean P_NetUnArchiveMisc(void)
 	sstimer = READINT32(save_p);
 	bluescore = READUINT32(save_p);
 	redscore = READUINT32(save_p);
+
+	skincolor_redteam = READUINT16(save_p);
+	skincolor_blueteam = READUINT16(save_p);
+	skincolor_redring = READUINT16(save_p);
+	skincolor_bluering = READUINT16(save_p);
+
 	modulothing = READINT32(save_p);
 
 	autobalance = READINT16(save_p);
@@ -4085,14 +4330,14 @@ static void P_NetArchiveWorlds(void)
 	P_NetArchiveColormaps();
 }
 
-void P_SaveNetGame(void)
+void P_SaveNetGame(boolean resending)
 {
 	thinker_t *th;
 	mobj_t *mobj;
 	INT32 i;
 
 	CV_SaveNetVars(&save_p);
-	P_NetArchiveMisc();
+	P_NetArchiveMisc(resending);
 
 	// Assign the mobjnumber for pointer tracking
 	for (i = 0; i < numworlds; i++)
@@ -4228,7 +4473,7 @@ static void SetUnArchiveWorld(world_t *w)
 	unarchiveworld = world = baseworld = localworld = w;
 }
 
-static void P_NetUnArchiveWorlds(void)
+static void P_NetUnArchiveWorlds(boolean reloading)
 {
 	player_t *player = &players[consoleplayer];
 	INT32 worldcount, i;
@@ -4249,7 +4494,7 @@ static void P_NetUnArchiveWorlds(void)
 	for (i = 1; i < worldcount; i++)
 	{
 		gamemap = READINT16(save_p);
-		if (!P_LoadLevel(player, true, true))
+		if (!P_LoadLevel(player, true, true, reloading))
 			I_Error("P_NetUnArchiveWorlds: failed loading world");
 		SetUnArchiveWorld(worldlist[i]);
 		UnArchiveWorld();
@@ -4264,14 +4509,14 @@ static void P_NetUnArchiveWorlds(void)
 		SendWorldSwitch(0, true);
 }
 
-boolean P_LoadNetGame(void)
+boolean P_LoadNetGame(boolean reloading)
 {
 	CV_LoadNetVars(&save_p);
-	if (!P_NetUnArchiveMisc())
+	if (!P_NetUnArchiveMisc(reloading))
 		return false;
 	P_NetUnArchivePlayers();
 	if (gamestate == GS_LEVEL)
-		P_NetUnArchiveWorlds();
+		P_NetUnArchiveWorlds(reloading);
 	LUA_UnArchive();
 
 	// This is stupid and hacky, but maybe it'll work!
diff --git a/src/p_saveg.h b/src/p_saveg.h
index bf2f45175365bc1e770b0494bdc991f8403b8dae..90da047b74e865f31b3e7a8d050ac89041424409 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -24,9 +24,9 @@
 // These are the load / save game routines.
 
 void P_SaveGame(INT16 mapnum);
-void P_SaveNetGame(void);
+void P_SaveNetGame(boolean resending);
 boolean P_LoadGame(INT16 mapoverride);
-boolean P_LoadNetGame(void);
+boolean P_LoadNetGame(boolean reloading);
 
 mobj_t *P_FindNewPosition(world_t *w, UINT32 oldposition);
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 6706f796c46807f134f10aed795c1e4daf6eefc4..56f785107f1088ba62692a686fca0e5bdefc5a84 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -29,7 +29,9 @@
 
 #include "r_data.h"
 #include "r_things.h" // for R_AddSpriteDefs
+#include "r_textures.h"
 #include "r_patch.h"
+#include "r_picformats.h"
 #include "r_sky.h"
 #include "r_draw.h"
 
@@ -82,6 +84,8 @@
 
 #include "fastcmp.h" // textmap parsing
 
+#include "taglist.h"
+
 //
 // Map MD5, calculated on level load.
 // Sent to clients in PT_SERVERINFO.
@@ -93,6 +97,7 @@ unsigned char mapmd5[16];
 // Store VERTEXES, LINEDEFS, SIDEDEFS, etc.
 //
 
+boolean udmf;
 size_t numvertexes, numsegs, numsectors, numsubsectors, numnodes, numlines, numsides, nummapthings;
 vertex_t *vertexes;
 seg_t *segs;
@@ -344,8 +349,8 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->mustrack = 0;
 	mapheaderinfo[num]->muspos = 0;
 	mapheaderinfo[num]->musinterfadeout = 0;
-	mapheaderinfo[num]->musintername[0] = '\0';
-	mapheaderinfo[num]->muspostbossname[6] = 0;
+	mapheaderinfo[num]->musintername[0] = 0;
+	mapheaderinfo[num]->muspostbossname[0] = 0;
 	mapheaderinfo[num]->muspostbosstrack = 0;
 	mapheaderinfo[num]->muspostbosspos = 0;
 	mapheaderinfo[num]->muspostbossfadein = 0;
@@ -533,6 +538,8 @@ Ploadflat (world_t *w, levelflat_t *levelflat, const char *flatname, boolean res
 
 	lumpnum_t    flatnum;
 	int       texturenum;
+	UINT8     *flatpatch;
+	size_t    lumplength;
 
 	size_t i;
 
@@ -589,7 +596,9 @@ texturefound:
 	{
 flatfound:
 		/* This could be a flat, patch, or PNG. */
-		if (R_CheckIfPatch(flatnum))
+		flatpatch = W_CacheLumpNum(flatnum, PU_CACHE);
+		lumplength = W_LumpLength(flatnum);
+		if (Picture_CheckIfDoomPatch((softwarepatch_t *)flatpatch, lumplength))
 			levelflat->type = LEVELFLAT_PATCH;
 		else
 		{
@@ -599,12 +608,14 @@ flatfound:
 			FIXME: Put this elsewhere.
 			*/
 			W_ReadLumpHeader(flatnum, buffer, 8, 0);
-			if (R_IsLumpPNG(buffer, W_LumpLength(flatnum)))
+			if (Picture_IsLumpPNG(buffer, lumplength))
 				levelflat->type = LEVELFLAT_PNG;
 			else
 #endif/*NO_PNG_LUMPS*/
 				levelflat->type = LEVELFLAT_FLAT;/* phew */
 		}
+		if (flatpatch)
+			Z_Free(flatpatch);
 
 		levelflat->u.flat.    lumpnum = flatnum;
 		levelflat->u.flat.baselumpnum = LUMPERROR;
@@ -889,16 +900,13 @@ static void P_SpawnMapThings(boolean spawnemblems)
 }
 
 // Experimental groovy write function!
-void P_WriteThings(lumpnum_t lumpnum)
+void P_WriteThings(void)
 {
 	size_t i, length;
 	mapthing_t *mt;
-	UINT8 *data;
 	UINT8 *savebuffer, *savebuf_p;
 	INT16 temp;
 
-	data = W_CacheLumpNum(lumpnum, PU_LEVEL);
-
 	savebuf_p = savebuffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t));
 
 	if (!savebuf_p)
@@ -920,8 +928,6 @@ void P_WriteThings(lumpnum_t lumpnum)
 		WRITEUINT16(savebuf_p, mt->options);
 	}
 
-	Z_Free(data);
-
 	length = savebuf_p - savebuffer;
 
 	FIL_WriteFile(va("newthings%d.lmp", gamemap), savebuffer, length);
@@ -955,8 +961,6 @@ static void P_InitializeSector(sector_t *ss)
 {
 	ss->world = world;
 
-	ss->nexttag = ss->firsttag = -1;
-
 	memset(&ss->soundorg, 0, sizeof(ss->soundorg));
 
 	ss->validcount = 0;
@@ -1027,13 +1031,15 @@ static void P_LoadSectors(UINT8 *data)
 
 		ss->lightlevel = SHORT(ms->lightlevel);
 		ss->special = SHORT(ms->special);
-		ss->tag = SHORT(ms->tag);
+		Tag_FSet(&ss->tags, SHORT(ms->tag));
 
 		ss->floor_xoffs = ss->floor_yoffs = 0;
 		ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
 
 		ss->floorpic_angle = ss->ceilingpic_angle = 0;
 
+		ss->colormap_protected = false;
+
 		P_InitializeSector(ss);
 	}
 }
@@ -1064,10 +1070,6 @@ static void P_InitializeLinedef(line_t *ld)
 	ld->frontsector = ld->backsector = NULL;
 
 	ld->validcount = 0;
-#ifdef WALLSPLATS
-	ld->splats = NULL;
-#endif
-	ld->firsttag = ld->nexttag = -1;
 	ld->polyobj = NULL;
 
 	ld->text = NULL;
@@ -1139,7 +1141,11 @@ static void P_LoadLinedefs(UINT8 *data)
 	{
 		ld->flags = SHORT(mld->flags);
 		ld->special = SHORT(mld->special);
-		ld->tag = SHORT(mld->tag);
+		Tag_FSet(&ld->tags, SHORT(mld->tag));
+		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
+		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
+		ld->alpha = FRACUNIT;
+		ld->executordelay = 0;
 		P_SetLinedefV1(i, SHORT(mld->v1));
 		P_SetLinedefV2(i, SHORT(mld->v2));
 
@@ -1213,9 +1219,11 @@ static void P_LoadSidedefs(UINT8 *data)
 			case 455: // Fade colormaps! mazmazz 9/12/2018 (:flag_us:)
 				// SoM: R_CreateColormap will only create a colormap in software mode...
 				// Perhaps we should just call it instead of doing the calculations here.
-				sd->colormap_data = R_CreateColormap(msd->toptexture, msd->midtexture,
-					msd->bottomtexture);
-				sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
+				if (!udmf)
+				{
+					sd->colormap_data = R_CreateColormapFromLinedef(msd->toptexture, msd->midtexture, msd->bottomtexture);
+					sd->toptexture = sd->midtexture = sd->bottomtexture = 0;
+				}
 				break;
 
 			case 413: // Change music
@@ -1326,7 +1334,7 @@ static void P_LoadSidedefs(UINT8 *data)
 						|| (msd->toptexture[0] >= 'A' && msd->toptexture[0] <= 'F'))
 						sd->toptexture = axtoi(msd->toptexture);
 					else
-						I_Error("Custom FOF (tag %d) needs a value in the linedef's back side upper texture field.", sd->line->tag);
+						I_Error("Custom FOF (line id %s) needs a value in the linedef's back side upper texture field.", sizeu1(sd->line - lines));
 
 					sd->midtexture = R_TextureNumForName(msd->midtexture);
 					sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
@@ -1366,6 +1374,11 @@ static void P_LoadThings(UINT8 *data)
 		mt->type = READUINT16(data);
 		mt->options = READUINT16(data);
 		mt->extrainfo = (UINT8)(mt->type >> 12);
+		Tag_FSet(&mt->tags, 0);
+		mt->scale = FRACUNIT;
+		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
+		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
+		mt->pitch = mt->roll = 0;
 
 		mt->type &= 4095;
 
@@ -1470,6 +1483,19 @@ static void ParseTextmapVertexParameter(UINT32 i, char *param, char *val)
 	}
 }
 
+typedef struct textmap_colormap_s {
+	boolean used;
+	INT32 lightcolor;
+	UINT8 lightalpha;
+	INT32 fadecolor;
+	UINT8 fadealpha;
+	UINT8 fadestart;
+	UINT8 fadeend;
+	UINT8 flags;
+} textmap_colormap_t;
+
+textmap_colormap_t textmap_colormap = { false, 0, 25, 0, 25, 0, 31, 0 };
+
 static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 {
 	if (fastcmp(param, "heightfloor"))
@@ -1485,7 +1511,17 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 	else if (fastcmp(param, "special"))
 		world->sectors[i].special = atol(val);
 	else if (fastcmp(param, "id"))
-		world->sectors[i].tag = atol(val);
+		Tag_FSet(&world->sectors[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&world->sectors[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
 	else if (fastcmp(param, "xpanningfloor"))
 		world->sectors[i].floor_xoffs = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "ypanningfloor"))
@@ -1498,6 +1534,48 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 		world->sectors[i].floorpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
 	else if (fastcmp(param, "rotationceiling"))
 		world->sectors[i].ceilingpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
+	else if (fastcmp(param, "lightcolor"))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.lightcolor = atol(val);
+	}
+	else if (fastcmp(param, "lightalpha"))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.lightalpha = atol(val);
+	}
+	else if (fastcmp(param, "fadecolor"))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.fadecolor = atol(val);
+	}
+	else if (fastcmp(param, "fadealpha"))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.fadealpha = atol(val);
+	}
+	else if (fastcmp(param, "fadestart"))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.fadestart = atol(val);
+	}
+	else if (fastcmp(param, "fadeend"))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.fadeend = atol(val);
+	}
+	else if (fastcmp(param, "colormapfog") && fastcmp("true", val))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.flags |= CMF_FOG;
+	}
+	else if (fastcmp(param, "colormapfadesprites") && fastcmp("true", val))
+	{
+		textmap_colormap.used = true;
+		textmap_colormap.flags |= CMF_FADEFULLBRIGHTSPRITES;
+	}
+	else if (fastcmp(param, "colormapprotected") && fastcmp("true", val))
+		world->sectors[i].colormap_protected = true;
 }
 
 static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
@@ -1521,17 +1599,46 @@ static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
 static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 {
 	if (fastcmp(param, "id"))
-		world->lines[i].tag = atol(val);
+		Tag_FSet(&world->lines[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&world->lines[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
 	else if (fastcmp(param, "special"))
 		world->lines[i].special = atol(val);
 	else if (fastcmp(param, "v1"))
 		P_SetLinedefV1(i, atol(val));
 	else if (fastcmp(param, "v2"))
 		P_SetLinedefV2(i, atol(val));
+	else if (strlen(param) == 7 && fastncmp(param, "arg", 3) && fastncmp(param + 4, "str", 3))
+	{
+		size_t argnum = param[3] - '0';
+		if (argnum >= NUMLINESTRINGARGS)
+			return;
+		world->lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
+		M_Memcpy(world->lines[i].stringargs[argnum], val, strlen(val) + 1);
+	}
+	else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
+	{
+		size_t argnum = atol(param + 3);
+		if (argnum >= NUMLINEARGS)
+			return;
+		world->lines[i].args[argnum] = atol(val);
+	}
 	else if (fastcmp(param, "sidefront"))
 		world->lines[i].sidenum[0] = atol(val);
 	else if (fastcmp(param, "sideback"))
 		world->lines[i].sidenum[1] = atol(val);
+	else if (fastcmp(param, "alpha"))
+		world->lines[i].alpha = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "executordelay"))
+		world->lines[i].executordelay = atol(val);
 
 	// Flags
 	else if (fastcmp(param, "blocking") && fastcmp("true", val))
@@ -1570,7 +1677,19 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 
 static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 {
-	if (fastcmp(param, "x"))
+	if (fastcmp(param, "id"))
+		Tag_FSet(&world->mapthings[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&world->mapthings[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
+	else if (fastcmp(param, "x"))
 		world->mapthings[i].x = atol(val);
 	else if (fastcmp(param, "y"))
 		world->mapthings[i].y = atol(val);
@@ -1578,18 +1697,39 @@ static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 		world->mapthings[i].z = atol(val);
 	else if (fastcmp(param, "angle"))
 		world->mapthings[i].angle = atol(val);
+	else if (fastcmp(param, "pitch"))
+		world->mapthings[i].pitch = atol(val);
+	else if (fastcmp(param, "roll"))
+		world->mapthings[i].roll = atol(val);
 	else if (fastcmp(param, "type"))
 		world->mapthings[i].type = atol(val);
-
+	else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley"))
+		world->mapthings[i].scale = FLOAT_TO_FIXED(atof(val));
 	// Flags
 	else if (fastcmp(param, "extra") && fastcmp("true", val))
 		world->mapthings[i].options |= MTF_EXTRA;
 	else if (fastcmp(param, "flip") && fastcmp("true", val))
 		world->mapthings[i].options |= MTF_OBJECTFLIP;
-	else if (fastcmp(param, "special") && fastcmp("true", val))
+	else if (fastcmp(param, "objectspecial") && fastcmp("true", val))
 		world->mapthings[i].options |= MTF_OBJECTSPECIAL;
 	else if (fastcmp(param, "ambush") && fastcmp("true", val))
 		world->mapthings[i].options |= MTF_AMBUSH;
+
+	else if (strlen(param) == 7 && fastncmp(param, "arg", 3) && fastncmp(param + 4, "str", 3))
+	{
+		size_t argnum = param[3] - '0';
+		if (argnum >= NUMMAPTHINGSTRINGARGS)
+			return;
+		world->mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
+		M_Memcpy(world->mapthings[i].stringargs[argnum], val, strlen(val) + 1);
+	}
+	else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
+	{
+		size_t argnum = atol(param + 3);
+		if (argnum >= NUMMAPTHINGARGS)
+			return;
+		world->mapthings[i].args[argnum] = atol(val);
+	}
 }
 
 /** From a given position table, run a specified parser function through a {}-encapsuled text.
@@ -1652,6 +1792,14 @@ static void TextmapFixFlatOffsets(sector_t *sec)
 	}
 }
 
+static INT32 P_ColorToRGBA(INT32 color, UINT8 alpha)
+{
+	UINT8 r = (color >> 16) & 0xFF;
+	UINT8 g = (color >> 8) & 0xFF;
+	UINT8 b = color & 0xFF;
+	return R_PutRgbaRGBA(r, g, b, alpha);
+}
+
 /** Loads the textmap data, after obtaining the elements count and allocating their respective space.
   */
 static void P_LoadTextmap(void)
@@ -1698,15 +1846,32 @@ static void P_LoadTextmap(void)
 		sc->lightlevel = 255;
 
 		sc->special = 0;
-		sc->tag = 0;
+		Tag_FSet(&sc->tags, 0);
 
 		sc->floor_xoffs = sc->floor_yoffs = 0;
 		sc->ceiling_xoffs = sc->ceiling_yoffs = 0;
 
 		sc->floorpic_angle = sc->ceilingpic_angle = 0;
 
+		sc->colormap_protected = false;
+
+		textmap_colormap.used = false;
+		textmap_colormap.lightcolor = 0;
+		textmap_colormap.lightalpha = 25;
+		textmap_colormap.fadecolor = 0;
+		textmap_colormap.fadealpha = 25;
+		textmap_colormap.fadestart = 0;
+		textmap_colormap.fadeend = 31;
+		textmap_colormap.flags = 0;
 		TextmapParse(sectorsPos[i], i, ParseTextmapSectorParameter);
+
 		P_InitializeSector(sc);
+		if (textmap_colormap.used)
+		{
+			INT32 rgba = P_ColorToRGBA(textmap_colormap.lightcolor, textmap_colormap.lightalpha);
+			INT32 fadergba = P_ColorToRGBA(textmap_colormap.fadecolor, textmap_colormap.fadealpha);
+			sc->extra_colormap = sc->spawn_extra_colormap = R_CreateColormap(rgba, fadergba, textmap_colormap.fadestart, textmap_colormap.fadeend, textmap_colormap.flags);
+		}
 		TextmapFixFlatOffsets(sc);
 	}
 
@@ -1716,7 +1881,12 @@ static void P_LoadTextmap(void)
 		ld->v1 = ld->v2 = NULL;
 		ld->flags = 0;
 		ld->special = 0;
-		ld->tag = 0;
+		Tag_FSet(&ld->tags, 0);
+
+		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
+		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
+		ld->alpha = FRACUNIT;
+		ld->executordelay = 0;
 		ld->sidenum[0] = 0xffff;
 		ld->sidenum[1] = 0xffff;
 
@@ -1755,11 +1925,15 @@ static void P_LoadTextmap(void)
 	{
 		// Defaults.
 		mt->x = mt->y = 0;
-		mt->angle = 0;
+		mt->angle = mt->pitch = mt->roll = 0;
 		mt->type = 0;
 		mt->options = 0;
 		mt->z = 0;
 		mt->extrainfo = 0;
+		Tag_FSet(&mt->tags, 0);
+		mt->scale = FRACUNIT;
+		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
+		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->mobj = NULL;
 
 		TextmapParse(mapthingsPos[i], i, ParseTextmapThingParameter);
@@ -1775,9 +1949,9 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 		ld->frontsector = world->sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here
 		ld->backsector = ld->sidenum[1] != 0xffff ? world->sides[ld->sidenum[1]].sector : 0;
 
-		// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
 		switch (ld->special)
 		{
+		// Compile linedef 'text' from both sidedefs 'text' for appropriate specials.
 		case 331: // Trigger linedef executor: Skin - Continuous
 		case 332: // Trigger linedef executor: Skin - Each time
 		case 333: // Trigger linedef executor: Skin - Once
@@ -1793,6 +1967,41 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 					M_Memcpy(ld->text + strlen(ld->text) + 1, world->sides[ld->sidenum[1]].text, strlen(world->sides[ld->sidenum[1]].text) + 1);
 			}
 			break;
+		case 447: // Change colormap
+		case 455: // Fade colormap
+			if (udmf)
+				break;
+			if (ld->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
+			{
+				extracolormap_t *exc = R_CopyColormap(sides[ld->sidenum[0]].colormap_data, false);
+				INT16 alpha = max(min(sides[ld->sidenum[0]].textureoffset >> FRACBITS, 25), -25);
+				INT16 fadealpha = max(min(sides[ld->sidenum[0]].rowoffset >> FRACBITS, 25), -25);
+
+				// If alpha is negative, set "subtract alpha" flag and store absolute value
+				if (alpha < 0)
+				{
+					alpha *= -1;
+					ld->args[2] |= TMCF_SUBLIGHTA;
+				}
+				if (fadealpha < 0)
+				{
+					fadealpha *= -1;
+					ld->args[2] |= TMCF_SUBFADEA;
+				}
+
+				exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(alpha);
+				exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(fadealpha);
+
+				if (!(sides[ld->sidenum[0]].colormap_data = R_GetColormapFromList(exc)))
+				{
+					exc->colormap = R_CreateLightTable(exc);
+					R_AddColormapToList(exc);
+					sides[ld->sidenum[0]].colormap_data = exc;
+				}
+				else
+					Z_Free(exc);
+			}
+			break;
 		}
 	}
 }
@@ -1800,11 +2009,11 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 static boolean P_LoadMapData(const virtres_t *virt)
 {
 	virtlump_t *virtvertexes = NULL, *virtsectors = NULL, *virtsidedefs = NULL, *virtlinedefs = NULL, *virtthings = NULL;
-	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
 
 	// Count map data.
-	if (textmap) // Count how many entries for each type we got in textmap.
+	if (udmf) // Count how many entries for each type we got in textmap.
 	{
+		virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
 		if (!TextmapCount(textmap->data, textmap->size))
 			return false;
 	}
@@ -1859,7 +2068,7 @@ static boolean P_LoadMapData(const virtres_t *virt)
 	world->numflats = 0;
 
 	// Load map data.
-	if (textmap)
+	if (udmf)
 		P_LoadTextmap();
 	else
 	{
@@ -1890,9 +2099,6 @@ static boolean P_LoadMapData(const virtres_t *virt)
 static void P_InitializeSubsector(subsector_t *ss)
 {
 	ss->sector = NULL;
-#ifdef FLOORSPLATS
-	ss->splats = NULL;
-#endif
 	ss->validcount = 0;
 }
 
@@ -1937,7 +2143,7 @@ static void P_LoadNodes(UINT8 *data)
   * \param seg Seg to compute length for.
   * \return Length in fracunits.
   */
-fixed_t P_SegLength(seg_t *seg)
+static fixed_t P_SegLength(seg_t *seg)
 {
 	INT64 dx = (seg->v2->x - seg->v1->x)>>1;
 	INT64 dy = (seg->v2->y - seg->v1->y)>>1;
@@ -1967,7 +2173,12 @@ static void P_InitializeSeg(seg_t *seg)
 {
 	if (seg->linedef)
 	{
-		seg->sidedef = &world->sides[seg->linedef->sidenum[seg->side]];
+		UINT16 side = seg->linedef->sidenum[seg->side];
+
+		if (side == 0xffff)
+			I_Error("P_InitializeSeg: Seg %s refers to side %d of linedef %s, which doesn't exist!\n", sizeu1((size_t)(seg - segs)), seg->side, sizeu1((size_t)(seg->linedef - lines)));
+
+		seg->sidedef = &world->sides[side];
 
 		seg->frontsector = seg->sidedef->sector;
 		seg->backsector = (seg->linedef->flags & ML_TWOSIDED) ? world->sides[seg->linedef->sidenum[seg->side ^ 1]].sector : NULL;
@@ -2036,7 +2247,7 @@ static nodetype_t P_GetNodetype(const virtres_t *virt, UINT8 **nodedata)
 	nodetype_t nodetype = NT_UNSUPPORTED;
 	char signature[4 + 1];
 
-	if (vres_Find(virt, "TEXTMAP"))
+	if (udmf)
 	{
 		*nodedata = vres_Find(virt, "ZNODES")->data;
 		supported[NT_XGLN] = supported[NT_XGL3] = true;
@@ -2161,10 +2372,8 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 				world->segs[k - 1 + ((m == 0) ? world->subsectors[i].numlines : 0)].v2 = world->segs[k].v1 = &world->vertexes[vertexnum];
 
 				READUINT32((*data)); // partner, can be ignored by software renderer
-				if (nodetype == NT_XGL3)
-					READUINT16((*data)); // Line number is 32-bit in XGL3, but we're limited to 16 bits.
 
-				linenum = READUINT16((*data));
+				linenum = (nodetype == NT_XGL3) ? READUINT32((*data)) : READUINT16((*data));
 				if (linenum != 0xFFFF && linenum >= world->numlines)
 					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
 				world->segs[k].glseg = (linenum == 0xFFFF);
@@ -2207,7 +2416,11 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		P_InitializeSeg(seg);
 		seg->angle = R_PointToAngle2(v1->x, v1->y, v2->x, v2->y);
 		if (seg->linedef)
-			world->segs[i].offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
+			seg->offset = FixedHypot(v1->x - seg->linedef->v1->x, v1->y - seg->linedef->v1->y);
+		seg->length = P_SegLength(seg);
+#ifdef HWRENDER
+		seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0;
+#endif
 	}
 
 	return true;
@@ -2742,6 +2955,267 @@ static void P_LinkMapData(void)
 	}
 }
 
+//For maps in binary format, converts setup of specials to UDMF format.
+static void P_ConvertBinaryMap(void)
+{
+	size_t i;
+
+	for (i = 0; i < numlines; i++)
+	{
+		mtag_t tag = Tag_FGet(&lines[i].tags);
+
+		switch (lines[i].special)
+		{
+		case 20: //PolyObject first line
+		{
+			INT32 check = -1;
+			INT32 paramline = -1;
+
+			TAG_ITER_DECLARECOUNTER(0);
+
+			TAG_ITER_LINES(0, tag, check)
+			{
+				if (lines[check].special == 22)
+				{
+					paramline = check;
+					break;
+				}
+			}
+
+			//PolyObject ID
+			lines[i].args[0] = tag;
+
+			//Default: Invisible planes
+			lines[i].args[3] |= TMPF_INVISIBLEPLANES;
+
+			//Linedef executor tag
+			lines[i].args[4] = 32000 + lines[i].args[0];
+
+			if (paramline == -1)
+				break; // no extra settings to apply, let's leave it
+
+			//Parent ID
+			lines[i].args[1] = lines[paramline].frontsector->special;
+			//Translucency
+			lines[i].args[2] = (lines[paramline].flags & ML_DONTPEGTOP)
+						? (sides[lines[paramline].sidenum[0]].textureoffset >> FRACBITS)
+						: ((lines[paramline].frontsector->floorheight >> FRACBITS) / 100);
+
+			//Flags
+			if (lines[paramline].flags & ML_EFFECT1)
+				lines[i].args[3] |= TMPF_NOINSIDES;
+			if (lines[paramline].flags & ML_EFFECT2)
+				lines[i].args[3] |= TMPF_INTANGIBLE;
+			if (lines[paramline].flags & ML_EFFECT3)
+				lines[i].args[3] |= TMPF_PUSHABLESTOP;
+			if (lines[paramline].flags & ML_EFFECT4)
+				lines[i].args[3] &= ~TMPF_INVISIBLEPLANES;
+			/*if (lines[paramline].flags & ML_EFFECT5)
+				lines[i].args[3] |= TMPF_DONTCLIPPLANES;*/
+			if (lines[paramline].flags & ML_EFFECT6)
+				lines[i].args[3] |= TMPF_SPLAT;
+			if (lines[paramline].flags & ML_NOCLIMB)
+				lines[i].args[3] |= TMPF_EXECUTOR;
+
+			break;
+		}
+		case 443: //Call Lua function
+			if (lines[i].text)
+			{
+				lines[i].stringargs[0] = Z_Malloc(strlen(lines[i].text) + 1, PU_LEVEL, NULL);
+				M_Memcpy(lines[i].stringargs[0], lines[i].text, strlen(lines[i].text) + 1);
+			}
+			else
+				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(i));
+			break;
+		case 447: //Change colormap
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
+			if (lines[i].flags & ML_EFFECT3)
+				lines[i].args[2] |= TMCF_RELATIVE;
+			if (lines[i].flags & ML_EFFECT1)
+				lines[i].args[2] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[2] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
+			if (lines[i].flags & ML_EFFECT2)
+				lines[i].args[2] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
+			break;
+		case 455: //Fade colormap
+		{
+			INT32 speed = (INT32)((((lines[i].flags & ML_DONTPEGBOTTOM) || !sides[lines[i].sidenum[0]].rowoffset) && lines[i].sidenum[1] != 0xFFFF) ?
+				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
+				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS));
+
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
+			if (lines[i].flags & ML_EFFECT4)
+				lines[i].args[2] = speed;
+			else
+				lines[i].args[2] = (256 + speed - 1)/speed;
+			if (lines[i].flags & ML_EFFECT3)
+				lines[i].args[3] |= TMCF_RELATIVE;
+			if (lines[i].flags & ML_EFFECT1)
+				lines[i].args[3] |= TMCF_SUBLIGHTR|TMCF_SUBFADER;
+			if (lines[i].flags & ML_NOCLIMB)
+				lines[i].args[3] |= TMCF_SUBLIGHTG|TMCF_SUBFADEG;
+			if (lines[i].flags & ML_EFFECT2)
+				lines[i].args[3] |= TMCF_SUBLIGHTB|TMCF_SUBFADEB;
+			if (lines[i].flags & ML_BOUNCY)
+				lines[i].args[3] |= TMCF_FROMBLACK;
+			if (lines[i].flags & ML_EFFECT5)
+				lines[i].args[3] |= TMCF_OVERRIDE;
+			break;
+		}
+		case 456: //Stop fading colormap
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
+			break;
+		case 606: //Colormap
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
+			break;
+		case 700: //Slope front sector floor
+		case 701: //Slope front sector ceiling
+		case 702: //Slope front sector floor and ceiling
+		case 703: //Slope front sector floor and back sector ceiling
+		case 710: //Slope back sector floor
+		case 711: //Slope back sector ceiling
+		case 712: //Slope back sector floor and ceiling
+		case 713: //Slope back sector floor and front sector ceiling
+		{
+			boolean frontfloor = (lines[i].special == 700 || lines[i].special == 702 || lines[i].special == 703);
+			boolean backfloor = (lines[i].special == 710 || lines[i].special == 712 || lines[i].special == 713);
+			boolean frontceil = (lines[i].special == 701 || lines[i].special == 702 || lines[i].special == 713);
+			boolean backceil = (lines[i].special == 711 || lines[i].special == 712 || lines[i].special == 703);
+
+			lines[i].args[0] = backfloor ? TMS_BACK : (frontfloor ? TMS_FRONT : TMS_NONE);
+			lines[i].args[1] = backceil ? TMS_BACK : (frontceil ? TMS_FRONT : TMS_NONE);
+
+			if (lines[i].flags & ML_NETONLY)
+				lines[i].args[2] |= TMSL_NOPHYSICS;
+			if (lines[i].flags & ML_NONET)
+				lines[i].args[2] |= TMSL_DYNAMIC;
+
+			lines[i].special = 700;
+			break;
+		}
+		case 704: //Slope front sector floor by 3 tagged vertices
+		case 705: //Slope front sector ceiling by 3 tagged vertices
+		case 714: //Slope back sector floor by 3 tagged vertices
+		case 715: //Slope back sector ceiling  by 3 tagged vertices
+		{
+			if (lines[i].special == 704)
+				lines[i].args[0] = TMSP_FRONTFLOOR;
+			else if (lines[i].special == 705)
+				lines[i].args[0] = TMSP_FRONTCEILING;
+			else if (lines[i].special == 714)
+				lines[i].args[0] = TMSP_BACKFLOOR;
+			else if (lines[i].special == 715)
+				lines[i].args[0] = TMSP_BACKCEILING;
+
+			lines[i].args[1] = tag;
+
+			if (lines[i].flags & ML_EFFECT6)
+			{
+				UINT8 side = lines[i].special >= 714;
+
+				if (side == 1 && lines[i].sidenum[1] == 0xffff)
+					CONS_Debug(DBG_GAMELOGIC, "P_ConvertBinaryMap: Line special %d (line #%s) missing 2nd side!\n", lines[i].special, sizeu1(i));
+				else
+				{
+					lines[i].args[2] = sides[lines[i].sidenum[side]].textureoffset >> FRACBITS;
+					lines[i].args[3] = sides[lines[i].sidenum[side]].rowoffset >> FRACBITS;
+				}
+			}
+			else
+			{
+				lines[i].args[2] = lines[i].args[1];
+				lines[i].args[3] = lines[i].args[1];
+			}
+
+			if (lines[i].flags & ML_NETONLY)
+				lines[i].args[4] |= TMSL_NOPHYSICS;
+			if (lines[i].flags & ML_NONET)
+				lines[i].args[4] |= TMSL_DYNAMIC;
+
+			lines[i].special = 704;
+			break;
+		}
+		case 720: //Copy front side floor slope
+		case 721: //Copy front side ceiling slope
+		case 722: //Copy front side floor and ceiling slope
+			if (lines[i].special != 721)
+				lines[i].args[0] = tag;
+			if (lines[i].special != 720)
+				lines[i].args[1] = tag;
+			lines[i].special = 720;
+			break;
+		case 900: //Translucent wall (10%)
+		case 901: //Translucent wall (20%)
+		case 902: //Translucent wall (30%)
+		case 903: //Translucent wall (40%)
+		case 904: //Translucent wall (50%)
+		case 905: //Translucent wall (60%)
+		case 906: //Translucent wall (70%)
+		case 907: //Translucent wall (80%)
+		case 908: //Translucent wall (90%)
+			lines[i].alpha = ((909 - lines[i].special) << FRACBITS)/10;
+			break;
+		default:
+			break;
+		}
+
+		//Linedef executor delay
+		if (lines[i].special >= 400 && lines[i].special < 500)
+		{
+			//Dummy value to indicate that this executor is delayed.
+			//The real value is taken from the back sector at runtime.
+			if (lines[i].flags & ML_DONTPEGTOP)
+				lines[i].executordelay = 1;
+		}
+	}
+
+	for (i = 0; i < nummapthings; i++)
+	{
+		switch (mapthings[i].type)
+		{
+		case 750:
+			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
+			break;
+		case 760:
+		case 761:
+			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
+			break;
+		case 762:
+		{
+			INT32 check = -1;
+			INT32 firstline = -1;
+			mtag_t tag = mapthings[i].angle;
+
+			TAG_ITER_DECLARECOUNTER(0);
+
+			Tag_FSet(&mapthings[i].tags, tag);
+
+			TAG_ITER_LINES(0, tag, check)
+			{
+				if (lines[check].special == 20)
+				{
+					firstline = check;
+					break;
+				}
+			}
+
+			if (firstline != -1)
+				lines[firstline].args[3] |= TMPF_CRUSH;
+
+			mapthings[i].type = 761;
+			break;
+		}
+		case 780:
+			Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
 /** Compute MD5 message digest for bytes read from memory source
   *
   * The resulting message digest number will be written into the 16 bytes
@@ -2770,11 +3244,13 @@ static INT32 P_MakeBufferMD5(const char *buffer, size_t len, void *resblock)
 
 static void P_MakeMapMD5(virtres_t *virt, void *dest)
 {
-	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
 	unsigned char resmd5[16];
 
-	if (textmap)
+	if (udmf)
+	{
+		virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
 		P_MakeBufferMD5((char*)textmap->data, textmap->size, resmd5);
+	}
 	else
 	{
 		unsigned char linemd5[16];
@@ -2805,6 +3281,9 @@ static void P_MakeMapMD5(virtres_t *virt, void *dest)
 static boolean P_LoadMapFromFile(void)
 {
 	virtres_t *virt = vres_GetMap(lastloadedmaplumpnum);
+	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
+	size_t i;
+	udmf = textmap != NULL;
 
 	if (!P_LoadMapData(virt))
 		return false;
@@ -2813,6 +3292,13 @@ static boolean P_LoadMapFromFile(void)
 
 	P_LinkMapData();
 
+	Taglist_InitGlobalTables();
+
+	P_SetGameWorld(world);
+
+	if (!udmf)
+		P_ConvertBinaryMap();
+
 	// Copy relevant map data for NetArchive purposes.
 	world->spawnsectors = Z_Calloc(world->numsectors * sizeof(*sectors), PU_LEVEL, NULL);
 	world->spawnlines = Z_Calloc(world->numlines * sizeof(*lines), PU_LEVEL, NULL);
@@ -2822,6 +3308,10 @@ static boolean P_LoadMapFromFile(void)
 	memcpy(world->spawnlines, world->lines, world->numlines * sizeof(*lines));
 	memcpy(world->spawnsides, world->sides, world->numsides * sizeof(*sides));
 
+	for (i = 0; i < world->numsectors; i++)
+		if (world->sectors[i].tags.count)
+			world->spawnsectors[i].tags.tags = memcpy(Z_Malloc(world->sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), world->sectors[i].tags.tags, world->sectors[i].tags.count*sizeof(mtag_t));
+
 	P_MakeMapMD5(virt, &mapmd5);
 
 	vres_Free(virt);
@@ -2909,8 +3399,6 @@ static void P_InitWorldSettings(void)
 {
 	leveltime = 0;
 
-	localaiming = 0;
-	localaiming2 = 0;
 	modulothing = 0;
 
 	// special stage tokens, emeralds, and ring total
@@ -3035,6 +3523,9 @@ void P_RespawnThings(void)
 
 	P_InitLevelSettings(NULL, false, false);
 
+	localaiming = 0;
+	localaiming2 = 0;
+
 	P_SpawnMapThings(true);
 
 	// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
@@ -3552,7 +4043,7 @@ static void P_InitGametype(player_t *player, boolean addworld)
   * \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot.
   * \todo Clean up, refactor, split up; get rid of the bloat.
   */
-boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
+boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boolean reloadinggamestate)
 {
 	// use gamemap to get map number.
 	// 99% of the things already did, so.
@@ -3623,27 +4114,30 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	players[consoleplayer].viewz = 1;
 
 	// Cancel all d_main.c fadeouts (keep fade in though).
-	wipegamestate = FORCEWIPEOFF;
+	if (addworld || reloadinggamestate)
+		wipegamestate = gamestate; // Don't fade if reloading the gamestate
+	else
+		wipegamestate = FORCEWIPEOFF;
 	wipestyleflags = 0;
 
 	if (!addworld)
 	{
-		// Special stage fade to white
+		// Special stage & record attack retry fade to white
 		// This is handled BEFORE sounds are stopped.
-		if (modeattacking && !demoplayback && (pausedelay == INT32_MIN))
-			ranspecialwipe = 2;
-		else if (rendermode != render_none && G_IsSpecialStage(gamemap))
-		{
-			P_RunSpecialStageWipe();
-			ranspecialwipe = 1;
-		}
-
 		if (G_GetModeAttackRetryFlag())
 		{
-			if (modeattacking)
+			if (modeattacking && !demoplayback)
+			{
+				ranspecialwipe = 2;
 				wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
+			}
 			G_ClearModeAttackRetryFlag();
 		}
+		else if (rendermode != render_none && G_IsSpecialStage(gamemap))
+		{
+			P_RunSpecialStageWipe();
+			ranspecialwipe = 1;
+		}
 
 		// Make sure all sounds are stopped before Z_FreeTags.
 		S_StopSounds();
@@ -3651,18 +4145,20 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 
 		// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
 		// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
-		if (!titlemapinaction && (RESETMUSIC ||
+		if (!(reloadinggamestate || titlemapinaction) && (RESETMUSIC ||
 			strnicmp(S_MusicName(),
 				(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
+		{
 			S_FadeMusic(0, FixedMul(
 				FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
+		}
 
 		// Let's fade to black here
 		// But only if we didn't do the special stage wipe
-		if (rendermode != render_none && !ranspecialwipe)
+		if (rendermode != render_none && !(ranspecialwipe || reloadinggamestate))
 			P_RunLevelWipe();
 
-		if (!titlemapinaction)
+		if (!(reloadinggamestate || titlemapinaction))
 		{
 			if (ranspecialwipe == 2)
 			{
@@ -3688,15 +4184,13 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 			// We should be fine starting it here.
 			// Don't do this during titlemap, because the menu code handles music by itself.
 			S_Start();
-		}
 
-		levelfadecol = (ranspecialwipe) ? 0 : 31;
+			levelfadecol = (ranspecialwipe) ? 0 : 31;
 
-		// Close text prompt before freeing the old level
-		F_EndTextPrompt(false, true);
+			// Close text prompt before freeing the old level
+			F_EndTextPrompt(false, true);
+		}
 	}
-	else
-		wipegamestate = gamestate;
 
 	if (player && (!titlemapinaction))
 		P_UnloadWorldPlayer(player);
@@ -3729,11 +4223,6 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	if (!addworld || player == &players[consoleplayer])
 		localworld = world;
 
-#if defined (WALLSPLATS) || defined (FLOORSPLATS)
-	// clear the splats from previous level
-	R_ClearLevelSplats();
-#endif
-
 	P_InitThinkers();
 	P_InitCachedActions();
 
@@ -3775,12 +4264,8 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	if (!P_LoadMapFromFile())
 		return false;
 
-	P_SetGameWorld(world);
-
-	// init gravity, tag lists,
-	// anything that P_SpawnSlopes/P_LoadThings needs to know
+	// init anything that P_SpawnSlopes/P_LoadThings needs to know
 	P_InitSpecials();
-	P_InitTagLists();   // Create xref tables for tags
 
 	P_SpawnSlopes(fromnetsave);
 
@@ -3800,14 +4285,14 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 		P_SpawnPrecipitation();
 
 #ifdef HWRENDER // not win32 only 19990829 by Kin
+	gl_maploaded = false;
+
 	// Lactozilla: Free extrasubsectors regardless of renderer.
-	// Maybe we're not in OpenGL anymore.
-	if (extrasubsectors)
-		free(extrasubsectors);
-	extrasubsectors = NULL;
-	// stuff like HWR_CreatePlanePolygons is called there
+	HWR_FreeExtraSubsectors();
+
+	// Create plane polygons.
 	if (rendermode == render_opengl)
-		HWR_SetupLevel();
+		HWR_LoadLevel();
 #endif
 
 	// oh god I hope this helps
@@ -3818,19 +4303,20 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	if (!fromnetsave)
 		P_InitGametype(player, addworld);
 
-	if (runforself)
+	if (runforself && !reloadinggamestate)
+	{
 		P_InitCamera();
+		localaiming = 0;
+		localaiming2 = 0;
+	}
 
 	// clear special respawning que
 	world->iquehead = world->iquetail = 0;
 
-	// Fab : 19-07-98 : start cd music for this level (note: can be remapped)
-	I_PlayCD((UINT8)(gamemap), false);
-
 	P_MapEnd();
 
 	// Remove the loading shit from the screen
-	if (rendermode != render_none && !titlemapinaction && runforself)
+	if (rendermode != render_none && !(titlemapinaction || reloadinggamestate) && runforself)
 		F_WipeColorFill(levelfadecol);
 
 	if (precache || dedicated)
@@ -3839,9 +4325,9 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	nextmapoverride = 0;
 	skipstats = 0;
 
-	if (!(netgame || multiplayer) && (!modifiedgame || savemoddata))
+	if (!(netgame || multiplayer || demoplayback) && (!modifiedgame || savemoddata))
 		mapvisited[gamemap-1] |= MV_VISITED;
-	else
+	else if (netgame || multiplayer)
 		mapvisited[gamemap-1] |= MV_MP; // you want to record that you've been there this session, but not permanently
 
 	levelloading = false;
@@ -3854,7 +4340,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 		if (!lastmaploaded) // Start a new game?
 		{
 			// I'd love to do this in the menu code instead of here, but everything's a mess and I can't guarantee saving proper player struct info before the first act's started. You could probably refactor it, but it'd be a lot of effort. Easier to just work off known good code. ~toast 22/06/2020
-			if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking)
+			if (!(ultimatemode || netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || marathonmode)
 			&& (!modifiedgame || savemoddata) && cursaveslot > 0)
 				G_SaveGame((UINT32)cursaveslot, gamemap);
 			// If you're looking for saving sp file progression (distinct from G_SaveGameOver), check G_DoCompleted.
@@ -3874,8 +4360,8 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 		LUAh_MapLoad();
 	}
 
-	// No render mode, stop here.
-	if (rendermode == render_none)
+	// No render mode or reloading gamestate, stop here.
+	if (rendermode == render_none || reloadinggamestate)
 		return true;
 
 	//if (!runforself || (addworld && splitscreen))
@@ -3897,29 +4383,6 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	return true;
 }
 
-#ifdef HWRENDER
-void HWR_SetupLevel(void)
-{
-	// Lactozilla (December 8, 2019)
-	// Level setup used to free EVERY mipmap from memory.
-	// Even mipmaps that aren't related to level textures.
-	// Presumably, the hardware render code used to store textures as level data.
-	// Meaning, they had memory allocated and marked with the PU_LEVEL tag.
-	// Level textures are only reloaded after R_LoadTextures, which is
-	// when the texture list is loaded.
-
-	// Sal: Unfortunately, NOT freeing them causes the dreaded Color Bug.
-	HWR_FreeMipmapCache();
-
-#ifdef ALAM_LIGHTING
-	// BP: reset light between levels (we draw preview frame lights on current frame)
-	HWR_ResetLights();
-#endif
-
-	HWR_CreatePlanePolygons((INT32)numnodes - 1);
-}
-#endif
-
 //
 // P_RunSOC
 //
@@ -4125,10 +4588,17 @@ boolean P_AddWadFile(const char *wadfilename)
 	if (!devparm && digmreplaces)
 		CONS_Printf(M_GetText("%s digital musics replaced\n"), sizeu1(digmreplaces));
 
+#ifdef HWRENDER
+	// Free GPU textures before freeing patches.
+	if (vid.glstate == VID_GL_LIBRARY_LOADED)
+		HWR_ClearAllTextures();
+#endif
 
 	//
 	// search for sprite replacements
 	//
+	Patch_FreeTag(PU_SPRITE);
+	Patch_FreeTag(PU_PATCH_ROTATED);
 	R_AddSpriteDefs(wadnum);
 
 	// Reload it all anyway, just in case they
diff --git a/src/p_setup.h b/src/p_setup.h
index c3828aa3f63a60d67b1720c08de2b358ca23856c..123b50e68a49b4dfc043dcff0e1a9792118ad16c 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -32,9 +32,7 @@ enum
 	LEVELFLAT_NONE,/* HOM time my friend */
 	LEVELFLAT_FLAT,
 	LEVELFLAT_PATCH,
-#ifndef NO_PNG_LUMPS
 	LEVELFLAT_PNG,
-#endif
 	LEVELFLAT_TEXTURE,
 };
 
@@ -67,15 +65,18 @@ typedef struct
 	u;
 
 	UINT16 width, height;
-	fixed_t topoffset, leftoffset;
 
 	// for flat animation
 	INT32 animseq; // start pos. in the anim sequence
 	INT32 numpics;
 	INT32 speed;
 
-	// for patchflats
-	UINT8 *flatpatch;
+	// for textures
+	UINT8 *picture;
+#ifdef HWRENDER
+	void *mipmap;
+	void *mippic;
+#endif
 } levelflat_t;
 
 INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat);
@@ -91,16 +92,16 @@ void P_SetupSkyTexture(INT32 skynum);
 void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);
 #endif
 void P_RespawnThings(void);
-boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave);
+boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boolean reloadinggamestate);
 #ifdef HWRENDER
-void HWR_SetupLevel(void);
+void HWR_LoadLevel(void);
 #endif
 
 boolean P_AddWadFile(const char *wadfilename);
 boolean P_RunSOC(const char *socfilename);
 void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num);
 void P_LoadMusicsRange(UINT16 wadnum, UINT16 first, UINT16 num);
-void P_WriteThings(lumpnum_t lump);
+void P_WriteThings(void);
 size_t P_PrecacheLevelFlats(void);
 void P_AllocMapHeader(INT16 i);
 
diff --git a/src/p_slopes.c b/src/p_slopes.c
index 4aa2aa4203c22871bfade89859a6f3711e11d8bf..ed16dd224f74c05e2abb964be52c75c822d92a3f 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -136,7 +136,7 @@ void T_DynamicSlopeVert (dynplanethink_t* th)
 	INT32 l;
 
 	for (i = 0; i < 3; i++) {
-		l = P_FindSpecialLineFromTag(799, th->tags[i], -1);
+		l = Tag_FindLineSpecial(799, th->tags[i]);
 		if (l != -1) {
 			th->vex[i].z = lines[l].frontsector->floorheight;
 		}
@@ -242,32 +242,30 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 	// because checking to see if a slope had changed will waste more memory than
 	// if the slope was just updated when called
 	line_t *line = lines + linenum;
-	INT16 special = line->special;
 	pslope_t *fslope = NULL, *cslope = NULL;
 	vector3_t origin, point;
 	vector2_t direction;
 	fixed_t nx, ny, dz, extent;
 
-	boolean frontfloor = (special == 700 || special == 702 || special == 703);
-	boolean backfloor  = (special == 710 || special == 712 || special == 713);
-	boolean frontceil  = (special == 701 || special == 702 || special == 713);
-	boolean backceil   = (special == 711 || special == 712 || special == 703);
-
+	boolean frontfloor = line->args[0] == TMS_FRONT;
+	boolean backfloor = line->args[0] == TMS_BACK;
+	boolean frontceil = line->args[1] == TMS_FRONT;
+	boolean backceil = line->args[1] == TMS_BACK;
 	UINT8 flags = 0; // Slope flags
-	if (line->flags & ML_NETONLY)
+	if (line->args[2] & TMSL_NOPHYSICS)
 		flags |= SL_NOPHYSICS;
-	if (line->flags & ML_NONET)
+	if (line->args[2] & TMSL_DYNAMIC)
 		flags |= SL_DYNAMIC;
 
 	if(!frontfloor && !backfloor && !frontceil && !backceil)
 	{
-		CONS_Printf("P_SpawnSlope_Line called with non-slope line special.\n");
+		CONS_Printf("line_SpawnViaLine: Slope special with nothing to do.\n");
 		return;
 	}
 
 	if(!line->frontsector || !line->backsector)
 	{
-		CONS_Debug(DBG_SETUP, "P_SpawnSlope_Line used on a line without two sides. (line number %i)\n", linenum);
+		CONS_Debug(DBG_SETUP, "line_SpawnViaLine: Slope special used on a line without two sides. (line number %i)\n", linenum);
 		return;
 	}
 
@@ -294,7 +292,7 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 
 		if(extent < 0)
 		{
-			CONS_Printf("P_SpawnSlope_Line failed to get frontsector extent on line number %i\n", linenum);
+			CONS_Printf("line_SpawnViaLine failed to get frontsector extent on line number %i\n", linenum);
 			return;
 		}
 
@@ -360,7 +358,7 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 
 		if(extent < 0)
 		{
-			CONS_Printf("P_SpawnSlope_Line failed to get backsector extent on line number %i\n", linenum);
+			CONS_Printf("line_SpawnViaLine failed to get backsector extent on line number %i\n", linenum);
 			return;
 		}
 
@@ -404,9 +402,6 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 				P_AddDynSlopeThinker(cslope, DP_BACKCEIL, line, extent, NULL, NULL);
 		}
 	}
-
-	if(!line->tag)
-		return;
 }
 
 /// Creates a new slope from three mapthings with the specified IDs
@@ -425,11 +420,11 @@ static pslope_t *MakeViaMapthings(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flag
 		if (mt->type != 750) // Haha, I'm hijacking the old Chaos Spawn thingtype for something!
 			continue;
 
-		if (!vertices[0] && mt->angle == tag1)
+		if (!vertices[0] && Tag_Find(&mt->tags, tag1))
 			vertices[0] = mt;
-		else if (!vertices[1] && mt->angle == tag2)
+		else if (!vertices[1] && Tag_Find(&mt->tags, tag2))
 			vertices[1] = mt;
-		else if (!vertices[2] && mt->angle == tag3)
+		else if (!vertices[2] && Tag_Find(&mt->tags, tag3))
 			vertices[2] = mt;
 	}
 
@@ -459,44 +454,36 @@ static void line_SpawnViaMapthingVertexes(const int linenum, const boolean spawn
 	line_t *line = lines + linenum;
 	side_t *side;
 	pslope_t **slopetoset;
-	UINT16 tag1, tag2, tag3;
-
-	UINT8 flags = 0;
-	if (line->flags & ML_NETONLY)
+	UINT16 tag1 = line->args[1];
+	UINT16 tag2 = line->args[2];
+	UINT16 tag3 = line->args[3];
+	UINT8 flags = 0; // Slope flags
+	if (line->args[4] & TMSL_NOPHYSICS)
 		flags |= SL_NOPHYSICS;
-	if (line->flags & ML_NONET)
+	if (line->args[4] & TMSL_DYNAMIC)
 		flags |= SL_DYNAMIC;
 
-	switch(line->special)
+	switch(line->args[0])
 	{
-	case 704:
+	case TMSP_FRONTFLOOR:
 		slopetoset = &line->frontsector->f_slope;
 		side = &sides[line->sidenum[0]];
 		break;
-	case 705:
+	case TMSP_FRONTCEILING:
 		slopetoset = &line->frontsector->c_slope;
 		side = &sides[line->sidenum[0]];
 		break;
-	case 714:
+	case TMSP_BACKFLOOR:
 		slopetoset = &line->backsector->f_slope;
 		side = &sides[line->sidenum[1]];
 		break;
-	case 715:
+	case TMSP_BACKCEILING:
 		slopetoset = &line->backsector->c_slope;
 		side = &sides[line->sidenum[1]];
 	default:
 		return;
 	}
 
-	if (line->flags & ML_EFFECT6)
-	{
-		tag1 = line->tag;
-		tag2 = side->textureoffset >> FRACBITS;
-		tag3 = side->rowoffset >> FRACBITS;
-	}
-	else
-		tag1 = tag2 = tag3 = line->tag;
-
 	*slopetoset = MakeViaMapthings(tag1, tag2, tag3, flags, spawnthinker);
 
 	side->sector->hasslope = true;
@@ -552,6 +539,47 @@ static void SpawnVertexSlopes(void)
 	}
 }
 
+static boolean P_SetSlopeFromTag(sector_t *sec, INT32 tag, boolean ceiling)
+{
+	INT32 i;
+	pslope_t **secslope = ceiling ? &sec->c_slope : &sec->f_slope;
+	TAG_ITER_DECLARECOUNTER(0);
+
+	if (!tag || *secslope)
+		return false;
+	TAG_ITER_SECTORS(0, tag, i)
+	{
+		pslope_t *srcslope = ceiling ? sectors[i].c_slope : sectors[i].f_slope;
+		if (srcslope)
+		{
+			*secslope = srcslope;
+			return true;
+		}
+	}
+	return false;
+}
+
+static boolean P_CopySlope(pslope_t **toslope, pslope_t *fromslope)
+{
+	if (*toslope || !fromslope)
+		return true;
+
+	*toslope = fromslope;
+	return true;
+}
+
+static void P_UpdateHasSlope(sector_t *sec)
+{
+	size_t i;
+
+	sec->hasslope = true;
+
+	// if this is an FOF control sector, make sure any target sectors also are marked as having slopes
+	if (sec->numattached)
+		for (i = 0; i < sec->numattached; i++)
+			sectors[sec->attached[i]].hasslope = true;
+}
+
 //
 // P_CopySectorSlope
 //
@@ -560,25 +588,31 @@ static void SpawnVertexSlopes(void)
 void P_CopySectorSlope(line_t *line)
 {
 	sector_t *fsec = line->frontsector;
-	int i, special = line->special;
+	sector_t *bsec = line->backsector;
+	boolean setfront = false;
+	boolean setback = false;
 
-	// Check for copy linedefs
-	for (i = -1; (i = P_FindSectorFromTag(line->tag, i)) >= 0;)
+	setfront |= P_SetSlopeFromTag(fsec, line->args[0], false);
+	setfront |= P_SetSlopeFromTag(fsec, line->args[1], true);
+	if (bsec)
 	{
-		sector_t *srcsec = sectors + i;
-
-		if ((special - 719) & 1 && !fsec->f_slope && srcsec->f_slope)
-			fsec->f_slope = srcsec->f_slope; //P_CopySlope(srcsec->f_slope);
-		if ((special - 719) & 2 && !fsec->c_slope && srcsec->c_slope)
-			fsec->c_slope = srcsec->c_slope; //P_CopySlope(srcsec->c_slope);
+		setback |= P_SetSlopeFromTag(bsec, line->args[2], false);
+		setback |= P_SetSlopeFromTag(bsec, line->args[3], true);
+
+		if (line->args[4] & TMSC_FRONTTOBACKFLOOR)
+			setback |= P_CopySlope(&bsec->f_slope, fsec->f_slope);
+		if (line->args[4] & TMSC_BACKTOFRONTFLOOR)
+			setfront |= P_CopySlope(&fsec->f_slope, bsec->f_slope);
+		if (line->args[4] & TMSC_FRONTTOBACKCEILING)
+			setback |= P_CopySlope(&bsec->c_slope, fsec->c_slope);
+		if (line->args[4] & TMSC_BACKTOFRONTCEILING)
+			setfront |= P_CopySlope(&fsec->c_slope, bsec->c_slope);
 	}
 
-	fsec->hasslope = true;
-
-	// if this is an FOF control sector, make sure any target sectors also are marked as having slopes
-	if (fsec->numattached)
-		for (i = 0; i < (int)fsec->numattached; i++)
-			sectors[fsec->attached[i]].hasslope = true;
+	if (setfront)
+		P_UpdateHasSlope(fsec);
+	if (setback)
+		P_UpdateHasSlope(bsec);
 
 	line->special = 0; // Linedef was use to set slopes, it finished its job, so now make it a normal linedef
 }
@@ -588,10 +622,10 @@ void P_CopySectorSlope(line_t *line)
 //
 // Looks in the slope list for a slope with a specified ID. Mostly useful for netgame sync
 //
-pslope_t *P_SlopeById(UINT16 id)
+pslope_t *P_SlopeById(pslope_t *list, UINT16 id)
 {
 	pslope_t *ret;
-	for (ret = world->slopelist; ret && ret->id != id; ret = ret->next);
+	for (ret = list; ret && ret->id != id; ret = ret->next);
 	return ret;
 }
 
@@ -611,20 +645,10 @@ void P_SpawnSlopes(const boolean fromsave) {
 		switch (lines[i].special)
 		{
 			case 700:
-			case 701:
-			case 702:
-			case 703:
-			case 710:
-			case 711:
-			case 712:
-			case 713:
 				line_SpawnViaLine(i, !fromsave);
 				break;
 
 			case 704:
-			case 705:
-			case 714:
-			case 715:
 				line_SpawnViaMapthingVertexes(i, !fromsave);
 				break;
 
@@ -639,8 +663,6 @@ void P_SpawnSlopes(const boolean fromsave) {
 		switch (lines[i].special)
 		{
 			case 720:
-			case 721:
-			case 722:
 				P_CopySectorSlope(&lines[i]);
 			default:
 				break;
diff --git a/src/p_slopes.h b/src/p_slopes.h
index 930a3f944667e7d282e716d7f882359495fc498b..6ec2b291e60e7447056db95de03d49176b3b4424 100644
--- a/src/p_slopes.h
+++ b/src/p_slopes.h
@@ -15,6 +15,35 @@
 
 #include "m_fixed.h" // Vectors
 
+typedef enum
+{
+	TMSP_FRONTFLOOR,
+	TMSP_FRONTCEILING,
+	TMSP_BACKFLOOR,
+	TMSP_BACKCEILING,
+} textmapslopeplane_t;
+
+typedef enum
+{
+	TMSC_FRONTTOBACKFLOOR   = 1,
+	TMSC_BACKTOFRONTFLOOR   = 1<<1,
+	TMSC_FRONTTOBACKCEILING = 1<<2,
+	TMSC_BACKTOFRONTCEILING = 1<<3,
+} textmapslopecopy_t;
+
+typedef enum
+{
+	TMS_NONE,
+	TMS_FRONT,
+	TMS_BACK,
+} textmapside_t;
+
+typedef enum
+{
+	TMSL_NOPHYSICS = 1,
+	TMSL_DYNAMIC = 2,
+} textmapslopeflags_t;
+
 void P_LinkSlopeThinkers (void);
 
 void P_CalculateSlopeNormal(pslope_t *slope);
@@ -27,7 +56,7 @@ void P_SpawnSlopes(const boolean fromsave);
 //
 void P_CopySectorSlope(line_t *line);
 
-pslope_t *P_SlopeById(UINT16 id);
+pslope_t *P_SlopeById(pslope_t *list, UINT16 id);
 
 // Returns the height of the sloped plane at (x, y) as a fixed_t
 fixed_t P_GetSlopeZAt(const pslope_t *slope, fixed_t x, fixed_t y);
diff --git a/src/p_spec.c b/src/p_spec.c
index ee039ceab4c30fc22a6b4bd7b41b4d33207ca3ed..a20dafa6777c248a759573fa5a9125177ac03f6a 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -20,6 +20,7 @@
 #include "p_local.h"
 #include "p_setup.h" // levelflats for flat animation
 #include "r_data.h"
+#include "r_textures.h"
 #include "m_random.h"
 #include "p_mobj.h"
 #include "i_system.h"
@@ -983,99 +984,12 @@ static sector_t *P_FindModelCeilingSector(fixed_t ceildestheight, INT32 secnum)
 }
 #endif
 
-/** Searches the tag lists for the next sector with a given tag.
-  *
-  * \param tag   Tag number to look for.
-  * \param start -1 to start anew, or the result of a previous call to keep
-  *              searching.
-  * \return Number of the next tagged sector found.
-  */
-INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numsectors)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? sectors[start].nexttag :
-			sectors[(unsigned)tag % numsectors].firsttag;
-		while (start >= 0 && sectors[start].tag != tag)
-			start = sectors[start].nexttag;
-		return start;
-	}
-}
-
-/** Searches the tag lists for the next line with a given tag and special.
-  *
-  * \param tag     Tag number.
-  * \param start   -1 to start anew, or the result of a previous call to keep
-  *                searching.
-  * \return Number of next suitable line found.
-  * \author Graue <graue@oceanbase.org>
-  */
-static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)tag % numlines].firsttag;
-		while (start >= 0 && lines[start].tag != tag)
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-
-INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		// This redundant check stops the compiler from complaining about function expansion
-		// elsewhere for some reason and everything is awful
-		if (start >= (INT32)numlines)
-			return -1;
-
-		while (start < (INT32)numlines && lines[start].special != special)
-			start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)tag % numlines].firsttag;
-		while (start >= 0 && (lines[start].tag != tag || lines[start].special != special))
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-
-
 // Parses arguments for parameterized polyobject door types
 static boolean PolyDoor(line_t *line)
 {
 	polydoordata_t pdd;
 
-	pdd.polyObjNum = line->tag; // polyobject id
+	pdd.polyObjNum = Tag_FGet(&line->tags); // polyobject id
 
 	switch(line->special)
 	{
@@ -1112,7 +1026,7 @@ static boolean PolyMove(line_t *line)
 {
 	polymovedata_t pmd;
 
-	pmd.polyObjNum = line->tag;
+	pmd.polyObjNum = Tag_FGet(&line->tags);
 	pmd.speed      = sides[line->sidenum[0]].textureoffset / 8;
 	pmd.angle      = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
 	pmd.distance   = sides[line->sidenum[0]].rowoffset;
@@ -1126,7 +1040,7 @@ static boolean PolyMove(line_t *line)
 // If NOCLIMB is ticked, the polyobject will still be tangible, just not visible.
 static void PolyInvisible(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
@@ -1150,7 +1064,7 @@ static void PolyInvisible(line_t *line)
 // If NOCLIMB is ticked, the polyobject will not be tangible, just visible.
 static void PolyVisible(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
@@ -1175,7 +1089,7 @@ static void PolyVisible(line_t *line)
 // Frontsector floor / 100 = translevel
 static void PolyTranslucency(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 	INT32 value;
 
@@ -1207,7 +1121,7 @@ static void PolyTranslucency(line_t *line)
 // Makes a polyobject translucency fade and applies tangibility
 static boolean PolyFade(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 	polyfadedata_t pfd;
 	INT32 value;
@@ -1269,7 +1183,7 @@ static boolean PolyWaypoint(line_t *line)
 {
 	polywaypointdata_t pwd;
 
-	pwd.polyObjNum = line->tag;
+	pwd.polyObjNum = Tag_FGet(&line->tags);
 	pwd.speed      = sides[line->sidenum[0]].textureoffset / 8;
 	pwd.sequence   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Sequence #
 
@@ -1296,7 +1210,7 @@ static boolean PolyRotate(line_t *line)
 {
 	polyrotdata_t prd;
 
-	prd.polyObjNum = line->tag;
+	prd.polyObjNum = Tag_FGet(&line->tags);
 	prd.speed      = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
 	prd.distance   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
 
@@ -1321,7 +1235,7 @@ static boolean PolyFlag(line_t *line)
 {
 	polyflagdata_t pfd;
 
-	pfd.polyObjNum = line->tag;
+	pfd.polyObjNum = Tag_FGet(&line->tags);
 	pfd.speed = P_AproxDistance(line->dx, line->dy) >> FRACBITS;
 	pfd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y) >> ANGLETOFINESHIFT;
 	pfd.momx = sides[line->sidenum[0]].textureoffset >> FRACBITS;
@@ -1334,7 +1248,7 @@ static boolean PolyDisplace(line_t *line)
 {
 	polydisplacedata_t pdd;
 
-	pdd.polyObjNum = line->tag;
+	pdd.polyObjNum = Tag_FGet(&line->tags);
 
 	pdd.controlSector = line->frontsector;
 	pdd.dx = line->dx>>8;
@@ -1350,7 +1264,7 @@ static boolean PolyRotDisplace(line_t *line)
 	polyrotdisplacedata_t pdd;
 	fixed_t anginter, distinter;
 
-	pdd.polyObjNum = line->tag;
+	pdd.polyObjNum = Tag_FGet(&line->tags);
 	pdd.controlSector = line->frontsector;
 
 	// Rotate 'anginter' interval for each 'distinter' interval from the control sector.
@@ -1370,66 +1284,6 @@ static boolean PolyRotDisplace(line_t *line)
 	return EV_DoPolyObjRotDisplace(&pdd);
 }
 
-/** Changes a sector's tag.
-  * Used by the linedef executor tag changer and by crumblers.
-  *
-  * \param sector Sector whose tag will be changed.
-  * \param newtag New tag number for this sector.
-  * \sa P_InitTagLists, P_FindSectorFromTag
-  * \author Graue <graue@oceanbase.org>
-  */
-void P_ChangeSectorTag(UINT32 sector, INT16 newtag)
-{
-	INT16 oldtag;
-	INT32 i;
-
-	I_Assert(sector < numsectors);
-
-	if ((oldtag = sectors[sector].tag) == newtag)
-		return;
-
-	// first you have to remove it from the old tag's taglist
-	i = sectors[(unsigned)oldtag % numsectors].firsttag;
-
-	if (i == -1) // shouldn't happen
-		I_Error("Corrupt tag list for sector %u\n", sector);
-	else if ((UINT32)i == sector)
-		sectors[(unsigned)oldtag % numsectors].firsttag = sectors[sector].nexttag;
-	else
-	{
-		while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
-			i = sectors[i].nexttag;
-
-		sectors[i].nexttag = sectors[sector].nexttag;
-	}
-
-	sectors[sector].tag = newtag;
-
-	// now add it to the new tag's taglist
-	if ((UINT32)sectors[(unsigned)newtag % numsectors].firsttag > sector)
-	{
-		sectors[sector].nexttag = sectors[(unsigned)newtag % numsectors].firsttag;
-		sectors[(unsigned)newtag % numsectors].firsttag = sector;
-	}
-	else
-	{
-		i = sectors[(unsigned)newtag % numsectors].firsttag;
-
-		if (i == -1)
-		{
-			sectors[(unsigned)newtag % numsectors].firsttag = sector;
-			sectors[sector].nexttag = -1;
-		}
-		else
-		{
-			while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
-				i = sectors[i].nexttag;
-
-			sectors[sector].nexttag = sectors[i].nexttag;
-			sectors[i].nexttag = sector;
-		}
-	}
-}
 
 //
 // P_RunNightserizeExecutors
@@ -1492,30 +1346,6 @@ void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean e
 	}
 }
 
-/** Hashes the sector tags across the sectors and linedefs.
-  *
-  * \sa P_FindSectorFromTag, P_ChangeSectorTag
-  * \author Lee Killough
-  */
-void P_InitTagLists(void)
-{
-	size_t i;
-
-	for (i = world->numsectors - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)world->sectors[i].tag % world->numsectors;
-		world->sectors[i].nexttag = world->sectors[j].firsttag;
-		world->sectors[j].firsttag = (INT32)i;
-	}
-
-	for (i = world->numlines - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)world->lines[i].tag % world->numlines;
-		world->lines[i].nexttag = world->lines[j].firsttag;
-		world->lines[j].firsttag = (INT32)i;
-	}
-}
-
 /** Finds minimum light from an adjacent sector.
   *
   * \param sector Sector to start in.
@@ -1559,16 +1389,24 @@ void T_ExecutorDelay(executor_t *e)
 static void P_AddExecutorDelay(line_t *line, mobj_t *mobj, sector_t *sector)
 {
 	executor_t *e;
+	INT32 delay;
 
-	if (!line->backsector)
-		I_Error("P_AddExecutorDelay: Line has no backsector!\n");
+	if (udmf)
+		delay = line->executordelay;
+	else
+	{
+		if (!line->backsector)
+			I_Error("P_AddExecutorDelay: Line has no backsector!\n");
+
+		delay = (line->backsector->ceilingheight >> FRACBITS) + (line->backsector->floorheight >> FRACBITS);
+	}
 
 	e = Z_Calloc(sizeof (*e), PU_LEVSPEC, NULL);
 
 	e->thinker.function.acp1 = (actionf_p1)T_ExecutorDelay;
 	e->line = line;
 	e->sector = sector;
-	e->timer = (line->backsector->ceilingheight>>FRACBITS)+(line->backsector->floorheight>>FRACBITS);
+	e->timer = delay;
 	P_SetTarget(&e->caller, mobj); // Use P_SetTarget to make sure the mobj doesn't get freed while we're delaying.
 	P_AddThinker(THINK_MAIN, &e->thinker);
 }
@@ -1945,7 +1783,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 		case 336: // object dye - once
 			{
 				INT32 triggercolor = (INT32)sides[triggerline->sidenum[0]].toptexture;
-				UINT8 color = (actor->player ? actor->player->powers[pw_dye] : actor->color);
+				UINT16 color = (actor->player ? actor->player->powers[pw_dye] : actor->color);
 				boolean invert = (triggerline->flags & ML_NOCLIMB ? true : false);
 
 				if (invert ^ (triggercolor != color))
@@ -1969,7 +1807,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			if (ctlsector->lines[i]->special >= 400
 				&& ctlsector->lines[i]->special < 500)
 			{
-				if (ctlsector->lines[i]->flags & ML_DONTPEGTOP)
+				if (ctlsector->lines[i]->executordelay)
 					P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
 				else
 					P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
@@ -2057,7 +1895,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			if (ctlsector->lines[i]->special >= 400
 				&& ctlsector->lines[i]->special < 500)
 			{
-				if (ctlsector->lines[i]->flags & ML_DONTPEGTOP)
+				if (ctlsector->lines[i]->executordelay)
 					P_AddExecutorDelay(ctlsector->lines[i], actor, caller);
 				else
 					P_ProcessLineSpecial(ctlsector->lines[i], actor, caller);
@@ -2114,7 +1952,7 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 
 	for (masterline = 0; masterline < numlines; masterline++)
 	{
-		if (lines[masterline].tag != tag)
+		if (Tag_FGet(&lines[masterline].tags) != tag)
 			continue;
 
 		// "No More Enemies" and "Level Load" take care of themselves.
@@ -2380,6 +2218,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 {
 	INT32 secnum = -1;
 	mobj_t *bot = NULL;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid!
 
@@ -2407,7 +2247,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				newceilinglightsec = line->frontsector->ceilinglightsec;
 
 				// act on all sectors with the same tag as the triggering linedef
-				while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+				TAG_ITER_SECTORS(0, tag, secnum)
 				{
 					if (sectors[secnum].lightingdata)
 					{
@@ -2462,17 +2302,17 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 409: // Change tagged sectors' tag
 		// (formerly "Change calling sectors' tag", but behavior was changed)
 		{
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
-				P_ChangeSectorTag(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+			TAG_ITER_SECTORS(0, tag, secnum)
+				Tag_SectorFSet(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 		}
 
 		case 410: // Change front sector's tag
-			P_ChangeSectorTag((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+			Tag_SectorFSet((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 
 		case 411: // Stop floor/ceiling movement in tagged sector(s)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (sectors[secnum].floordata)
 				{
@@ -2542,7 +2382,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 				else
 				{
-					if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+					if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 						return;
 
 					dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
@@ -2649,7 +2489,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					return;
 				}
 
-				if (line->tag != 0) // Do special stuff only if a non-zero linedef tag is set
+				if (tag != 0) // Do special stuff only if a non-zero linedef tag is set
 				{
 					// Play sounds from tagged sectors' origins.
 					if (line->flags & ML_EFFECT5) // Repeat Midtexture
@@ -2657,7 +2497,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						// Additionally play the sound from tagged sectors' soundorgs
 						sector_t *sec;
 
-						while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+						TAG_ITER_SECTORS(0, tag, secnum)
 						{
 							sec = &sectors[secnum];
 							S_StartSound(&sec->soundorg, sfxnum);
@@ -2677,7 +2517,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							if (!camobj)
 								continue;
 
-							if (foundit || (camobj->subsector->sector->tag == line->tag))
+							if (foundit || Tag_Find(&camobj->subsector->sector->tags, tag))
 							{
 								foundit = true;
 								break;
@@ -2686,7 +2526,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							// Only trigger if mobj is touching the tag
 							for(rover = camobj->subsector->sector->ffloors; rover; rover = rover->next)
 							{
-								if (rover->master->frontsector->tag != line->tag)
+								if (!Tag_Find(&rover->master->frontsector->tags, tag))
 									continue;
 
 								if (camobj->z > P_GetSpecialTopZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
@@ -2772,7 +2612,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 416: // Spawn adjustable fire flicker
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2806,7 +2646,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 417: // Spawn adjustable glowing light
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2840,7 +2680,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 418: // Spawn adjustable strobe flash (unsynchronized)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2874,7 +2714,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 419: // Spawn adjustable strobe flash (synchronized)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2908,7 +2748,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 420: // Fade light levels in tagged sectors to new value
-			P_FadeLight(line->tag,
+			P_FadeLight(tag,
 				(line->flags & ML_DONTPEGBOTTOM) ? max(sides[line->sidenum[0]].textureoffset>>FRACBITS, 0) : line->frontsector->lightlevel,
 				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
 				// to be consistent with other light and fade specials
@@ -2922,7 +2762,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 				if (sectors[secnum].lightingdata)
 				{
 					P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
@@ -2937,7 +2777,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
 					return;
 
-				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 					return;
 
 				altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
@@ -3119,8 +2959,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						continue;
 
 					scroller = (scroll_t *)th;
-
-					if (sectors[scroller->affectee].tag != line->tag)
+					if (!Tag_Find(&sectors[scroller->affectee].tags, tag))
 						continue;
 
 					scroller->dx = FixedMul(line->dx>>SCROLL_SHIFT, CARRYFACTOR);
@@ -3137,7 +2976,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				ffloor_t *rover; // FOF that we are going to crumble
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3149,7 +2988,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3196,12 +3035,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				size_t linenum;
 				side_t *set = &sides[line->sidenum[0]], *this;
 				boolean always = !(line->flags & ML_NOCLIMB); // If noclimb: Only change mid texture if mid texture already exists on tagged lines, etc.
+
 				for (linenum = 0; linenum < numlines; linenum++)
 				{
 					if (lines[linenum].special == 439)
 						continue; // Don't override other set texture lines!
 
-					if (lines[linenum].tag != line->tag)
+					if (!Tag_Find(&lines[linenum].tags, tag))
 						continue; // Find tagged lines
 
 					// Front side
@@ -3261,7 +3101,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			if (line->sidenum[1] != 0xffff)
 				state = (statenum_t)sides[line->sidenum[1]].toptexture;
 
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				boolean tryagain;
 				sec = sectors + secnum;
@@ -3290,10 +3130,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		}
 
 		case 443: // Calls a named Lua function
-			if (line->text)
+			if (line->stringargs[0])
 				LUAh_LinedefExecute(line, mo, callsec);
 			else
-				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(line-lines));
+				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in arg0str)\n", sizeu1(line-lines));
 			break;
 
 		case 444: // Earthquake camera
@@ -3321,7 +3161,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
 				ffloortype_e oldflags; // store FOF's old flags
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3333,7 +3173,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3379,7 +3219,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if (line->flags & ML_NOCLIMB) // don't respawn!
 					respawn = false;
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3391,7 +3231,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3416,46 +3256,52 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			// Except it is activated by linedef executor, not level load
 			// This could even override existing colormaps I believe
 			// -- Monster Iestyn 14/06/18
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0 ;)
+		{
+			extracolormap_t *source;
+			if (!udmf)
+				source = sides[line->sidenum[0]].colormap_data;
+			else
 			{
-				P_ResetColormapFader(&sectors[secnum]);
-
-				if (line->flags & ML_EFFECT3) // relative calc
-				{
-					extracolormap_t *exc = R_AddColormaps(
-						(line->flags & ML_TFERLINE) && line->sidenum[1] != 0xFFFF ?
-							sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap, // use back colormap instead of target sector
-						sides[line->sidenum[0]].colormap_data,
-						line->flags & ML_EFFECT1,  // subtract R
-						line->flags & ML_NOCLIMB,  // subtract G
-						line->flags & ML_EFFECT2,  // subtract B
-						false,                     // subtract A (no flag for this, just pass negative alpha)
-						line->flags & ML_EFFECT1,  // subtract FadeR
-						line->flags & ML_NOCLIMB,  // subtract FadeG
-						line->flags & ML_EFFECT2,  // subtract FadeB
-						false,                     // subtract FadeA (no flag for this, just pass negative alpha)
-						false,                     // subtract FadeStart (we ran out of flags)
-						false,                     // subtract FadeEnd (we ran out of flags)
-						false,                     // ignore Flags (we ran out of flags)
-						line->flags & ML_DONTPEGBOTTOM,
-						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
-						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
-						false);
-
-					if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc)))
+				if (!line->args[1])
+					source = line->frontsector->extra_colormap;
+				else
+				{
+					INT32 sourcesec = Tag_Iterate_Sectors(line->args[1], 0);
+					if (sourcesec == -1)
 					{
-						exc->colormap = R_CreateLightTable(exc);
-						R_AddColormapToList(exc);
-						sectors[secnum].extra_colormap = exc;
+						CONS_Debug(DBG_GAMELOGIC, "Line type 447 Executor: Can't find sector with source colormap (tag %d)!\n", line->args[1]);
+						return;
 					}
-					else
-						Z_Free(exc);
+					source = sectors[sourcesec].extra_colormap;
 				}
-				else if (line->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
+			}
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
+			{
+				if (sectors[secnum].colormap_protected)
+					continue;
+
+				P_ResetColormapFader(&sectors[secnum]);
+
+				if (line->args[2] & TMCF_RELATIVE)
 				{
-					extracolormap_t *exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
-					exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].textureoffset >> FRACBITS, 25), 0));
-					exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].rowoffset >> FRACBITS, 25), 0));
+					extracolormap_t *target = (!udmf && (line->flags & ML_TFERLINE) && line->sidenum[1] != 0xFFFF) ?
+						sides[line->sidenum[1]].colormap_data : sectors[secnum].extra_colormap; // use back colormap instead of target sector
+
+						extracolormap_t *exc = R_AddColormaps(
+							target,
+							source,
+							line->args[2] & TMCF_SUBLIGHTR,
+							line->args[2] & TMCF_SUBLIGHTG,
+							line->args[2] & TMCF_SUBLIGHTB,
+							line->args[2] & TMCF_SUBLIGHTA,
+							line->args[2] & TMCF_SUBFADER,
+							line->args[2] & TMCF_SUBFADEG,
+							line->args[2] & TMCF_SUBFADEB,
+							line->args[2] & TMCF_SUBFADEA,
+							line->args[2] & TMCF_SUBFADESTART,
+							line->args[2] & TMCF_SUBFADEEND,
+							line->args[2] & TMCF_IGNOREFLAGS,
+							false);
 
 					if (!(sectors[secnum].extra_colormap = R_GetColormapFromList(exc)))
 					{
@@ -3467,10 +3313,10 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						Z_Free(exc);
 				}
 				else
-					sectors[secnum].extra_colormap = sides[line->sidenum[0]].colormap_data;
+					sectors[secnum].extra_colormap = source;
 			}
 			break;
-
+		}
 		case 448: // Change skybox viewpoint/centerpoint
 			if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
 			{
@@ -3481,7 +3327,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				{
 					CONS_Alert(CONS_WARNING,
 					M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					line->tag);
+					tag);
 				}
 				else
 				{
@@ -3519,7 +3365,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				CONS_Alert(CONS_WARNING,
 					M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-					line->tag);
+					tag);
 				break;
 			}
 			if (line->flags & ML_NOCLIMB)
@@ -3536,7 +3382,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		}
 
 		case 450: // Execute Linedef Executor - for recursion
-			P_LinedefExecute(line->tag, mo, NULL);
+			P_LinedefExecute(tag, mo, NULL);
 			break;
 
 		case 451: // Execute Random Linedef Executor
@@ -3564,7 +3410,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3576,7 +3422,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3628,7 +3474,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 			size_t j = 0; // sec->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3640,7 +3486,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3713,7 +3559,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3725,7 +3571,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3744,15 +3590,35 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		}
 
 		case 455: // Fade colormap
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0 ;)
+		{
+			extracolormap_t *dest;
+			if (!udmf)
+				dest = sides[line->sidenum[0]].colormap_data;
+			else
+			{
+				if (!line->args[1])
+					dest = line->frontsector->extra_colormap;
+				else
+				{
+					INT32 destsec = Tag_Iterate_Sectors(line->args[1], 0);
+					if (destsec == -1)
+					{
+						CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Can't find sector with destination colormap (tag %d)!\n", line->args[1]);
+						return;
+					}
+					dest = sectors[destsec].extra_colormap;
+				}
+			}
+
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 			{
 				extracolormap_t *source_exc, *dest_exc, *exc;
-				INT32 speed = (INT32)((line->flags & ML_DONTPEGBOTTOM) || !sides[line->sidenum[0]].rowoffset) && line->sidenum[1] != 0xFFFF ?
-					abs(sides[line->sidenum[1]].rowoffset >> FRACBITS)
-					: abs(sides[line->sidenum[0]].rowoffset >> FRACBITS);
 
-				// Prevent continuous execs from interfering on an existing fade
-				if (!(line->flags & ML_EFFECT5)
+				if (sectors[secnum].colormap_protected)
+					continue;
+
+				// Don't interrupt ongoing fade
+				if (!(line->args[3] & TMCF_OVERRIDE)
 					&& sectors[secnum].fadecolormapdata)
 					//&& ((fadecolormap_t*)sectors[secnum].fadecolormapdata)->timer > (ticbased ? 2 : speed*2))
 				{
@@ -3760,19 +3626,19 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					continue;
 				}
 
-				if (line->flags & ML_TFERLINE) // use back colormap instead of target sector
+				if (!udmf && (line->flags & ML_TFERLINE)) // use back colormap instead of target sector
 					sectors[secnum].extra_colormap = (line->sidenum[1] != 0xFFFF) ?
-						sides[line->sidenum[1]].colormap_data : NULL;
+					sides[line->sidenum[1]].colormap_data : NULL;
 
 				exc = sectors[secnum].extra_colormap;
 
-				if (!(line->flags & ML_BOUNCY) // BOUNCY: Do not override fade from default rgba
-					&& !R_CheckDefaultColormap(sides[line->sidenum[0]].colormap_data, true, false, false)
+				if (!(line->args[3] & TMCF_FROMBLACK) // Override fade from default rgba
+					&& !R_CheckDefaultColormap(dest, true, false, false)
 					&& R_CheckDefaultColormap(exc, true, false, false))
 				{
 					exc = R_CopyColormap(exc, false);
-					exc->rgba = R_GetRgbaRGB(sides[line->sidenum[0]].colormap_data->rgba) + R_PutRgbaA(R_GetRgbaA(exc->rgba));
-					//exc->fadergba = R_GetRgbaRGB(sides[line->sidenum[0]].colormap_data->rgba) + R_PutRgbaA(R_GetRgbaA(exc->fadergba));
+					exc->rgba = R_GetRgbaRGB(dest->rgba) + R_PutRgbaA(R_GetRgbaA(exc->rgba));
+					//exc->fadergba = R_GetRgbaRGB(dest->rgba) + R_PutRgbaA(R_GetRgbaA(exc->fadergba));
 
 					if (!(source_exc = R_GetColormapFromList(exc)))
 					{
@@ -3788,35 +3654,26 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				else
 					source_exc = exc ? exc : R_GetDefaultColormap();
 
-				if (line->flags & ML_EFFECT3) // relative calc
+				if (line->args[3] & TMCF_RELATIVE)
 				{
 					exc = R_AddColormaps(
 						source_exc,
-						sides[line->sidenum[0]].colormap_data,
-						line->flags & ML_EFFECT1,  // subtract R
-						line->flags & ML_NOCLIMB,  // subtract G
-						line->flags & ML_EFFECT2,  // subtract B
-						false,                     // subtract A (no flag for this, just pass negative alpha)
-						line->flags & ML_EFFECT1,  // subtract FadeR
-						line->flags & ML_NOCLIMB,  // subtract FadeG
-						line->flags & ML_EFFECT2,  // subtract FadeB
-						false,                     // subtract FadeA (no flag for this, just pass negative alpha)
-						false,                     // subtract FadeStart (we ran out of flags)
-						false,                     // subtract FadeEnd (we ran out of flags)
-						false,                     // ignore Flags (we ran out of flags)
-						line->flags & ML_DONTPEGBOTTOM,
-						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].textureoffset >> FRACBITS) : 0,
-						(line->flags & ML_DONTPEGBOTTOM) ? (sides[line->sidenum[0]].rowoffset >> FRACBITS) : 0,
+						dest,
+						line->args[3] & TMCF_SUBLIGHTR,
+						line->args[3] & TMCF_SUBLIGHTG,
+						line->args[3] & TMCF_SUBLIGHTB,
+						line->args[3] & TMCF_SUBLIGHTA,
+						line->args[3] & TMCF_SUBFADER,
+						line->args[3] & TMCF_SUBFADEG,
+						line->args[3] & TMCF_SUBFADEB,
+						line->args[3] & TMCF_SUBFADEA,
+						line->args[3] & TMCF_SUBFADESTART,
+						line->args[3] & TMCF_SUBFADEEND,
+						line->args[3] & TMCF_IGNOREFLAGS,
 						false);
 				}
-				else if (line->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
-				{
-					exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
-					exc->rgba = R_GetRgbaRGB(exc->rgba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].textureoffset >> FRACBITS, 25), 0));
-					exc->fadergba = R_GetRgbaRGB(exc->fadergba) + R_PutRgbaA(max(min(sides[line->sidenum[0]].rowoffset >> FRACBITS, 25), 0));
-				}
 				else
-					exc = R_CopyColormap(sides[line->sidenum[0]].colormap_data, false);
+					exc = R_CopyColormap(dest, false);
 
 				if (!(dest_exc = R_GetColormapFromList(exc)))
 				{
@@ -3827,13 +3684,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				else
 					Z_Free(exc);
 
-				Add_ColormapFader(&sectors[secnum], source_exc, dest_exc, (line->flags & ML_EFFECT4), // tic-based timing
-					speed);
+				Add_ColormapFader(&sectors[secnum], source_exc, dest_exc, true, // tic-based timing
+					line->args[2]);
 			}
 			break;
-
+		}
 		case 456: // Stop fade colormap
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 				P_ResetColormapFader(&sectors[secnum]);
 			break;
 
@@ -3847,7 +3704,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean persist = (line->flags & ML_EFFECT2);
 				mobj_t *anchormo;
 
-				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 					return;
 
 				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
@@ -3878,7 +3735,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				INT32 promptnum = max(0, (sides[line->sidenum[0]].textureoffset>>FRACBITS)-1);
 				INT32 pagenum = max(0, (sides[line->sidenum[0]].rowoffset>>FRACBITS)-1);
-				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : line->tag);
+				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : tag);
 
 				boolean closetextprompt = (line->flags & ML_BLOCKMONSTERS);
 				//boolean allplayers = (line->flags & ML_NOCLIMB);
@@ -4002,7 +3859,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					if (!mo2->spawnpoint)
 						continue;
 
-					if (mo2->spawnpoint->angle != line->tag)
+					if (mo2->spawnpoint->angle != tag)
 						continue;
 
 					P_KillMobj(mo2, NULL, mo, 0);
@@ -4023,6 +3880,24 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 465: // Set linedef executor delay
+			{
+				INT32 linenum;
+				TAG_ITER_DECLARECOUNTER(1);
+
+				if (!udmf)
+					break;
+
+				TAG_ITER_LINES(1, line->args[0], linenum)
+				{
+					if (line->args[2])
+						lines[linenum].executordelay += line->args[1];
+					else
+						lines[linenum].executordelay = line->args[1];
+				}
+			}
+			break;
+
 		case 480: // Polyobj_DoorSlide
 		case 481: // Polyobj_DoorSwing
 			PolyDoor(line);
@@ -4358,6 +4233,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 	INT32 i = 0;
 	INT32 section1, section2, section3, section4;
 	INT32 special;
+	mtag_t sectag = Tag_FGet(&sector->tags);
 
 	section1 = GETSECSPECIAL(sector->special, 1);
 	section2 = GETSECSPECIAL(sector->special, 2);
@@ -4371,7 +4247,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 	// Ignore dead players.
 	// If this strange phenomenon could be potentially used in levels,
 	// TODO: modify this to accommodate for it.
-	if (player->playerstate == PST_DEAD)
+	if (player->playerstate != PST_LIVE)
 		return;
 
 	// Conveyor stuff
@@ -4424,7 +4300,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			if (leveltime % (TICRATE/2) == 0 && player->rings > 0)
 			{
 				player->rings--;
-				S_StartSound(player->mo, sfx_itemup);
+				S_StartSound(player->mo, sfx_antiri);
 			}
 			break;
 		case 11: // Special Stage Damage
@@ -4512,7 +4388,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 		case 6: // Linedef executor (7 Emeralds)
 		case 7: // Linedef executor (NiGHTS Mare)
 			if (!player->bot)
-				P_LinedefExecute(sector->tag, player->mo, sector);
+				P_LinedefExecute(sectag, player->mo, sector);
 			break;
 		case 8: // Tells pushable things to check FOFs
 			break;
@@ -4540,15 +4416,19 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			// clear the special so you can't push the button twice.
 			sector->special = 0;
 
+			// Initialize my junk
+			junk.tags.tags = NULL;
+			junk.tags.count = 0;
+
 			// Move the button down
-			junk.tag = 680;
+			Tag_FSet(&junk.tags, LE_CAPSULE0);
 			EV_DoElevator(&junk, elevateDown, false);
 
 			// Open the top FOF
-			junk.tag = 681;
+			Tag_FSet(&junk.tags, LE_CAPSULE1);
 			EV_DoFloor(&junk, raiseFloorToNearestFast);
 			// Open the bottom FOF
-			junk.tag = 682;
+			Tag_FSet(&junk.tags, LE_CAPSULE2);
 			EV_DoCeiling(&junk, lowerToLowestFast);
 
 			// Mark all players with the time to exit thingy!
@@ -4583,7 +4463,7 @@ DoneSection2:
 			if (player->powers[pw_flashing] != 0 && player->powers[pw_flashing] < TICRATE/2)
 				break;
 
-			i = P_FindSpecialLineFromTag(4, sector->tag, -1);
+			i = Tag_FindLineSpecial(4, sectag);
 
 			if (i != -1)
 			{
@@ -4596,7 +4476,7 @@ DoneSection2:
 
 				if (linespeed == 0)
 				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sector->tag);
+					CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sectag);
 					break;
 				}
 
@@ -4623,7 +4503,7 @@ DoneSection2:
 
 				P_InstaThrust(player->mo, player->mo->angle, linespeed);
 
-				if ((lines[i].flags & ML_EFFECT5) && (player->charability2 == CA2_SPINDASH)) // Roll!
+				if (lines[i].flags & ML_EFFECT5) // Roll!
 				{
 					if (!(player->pflags & PF_SPINNING))
 						player->pflags |= PF_SPINNING;
@@ -4690,7 +4570,7 @@ DoneSection2:
 				// important: use sector->tag on next line instead of player->mo->subsector->tag
 				// this part is different from in P_PlayerThink, this is what was causing
 				// FOF custom exits not to work.
-				lineindex = P_FindSpecialLineFromTag(2, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(2, sectag);
 
 				if (G_CoopGametype() && lineindex != -1) // Custom exit!
 				{
@@ -4720,7 +4600,7 @@ DoneSection2:
 
 					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sBLUE FLAG%s.\\\\\\\\"), "\x85", player_names[player-players], "\x80", "\x84", "\x80"));
+					HU_DoCEcho(va(M_GetText("\205%s\200\\CAPTURED THE \204BLUE FLAG\200.\\\\\\\\"), player_names[player-players]));
 
 					if (splitscreen || players[consoleplayer].ctfteam == 1)
 						S_StartSound(NULL, sfx_flgcap);
@@ -4753,7 +4633,7 @@ DoneSection2:
 
 					HU_SetCEchoFlags(V_AUTOFADEOUT|V_ALLOWLOWERCASE);
 					HU_SetCEchoDuration(5);
-					HU_DoCEcho(va(M_GetText("%s%s%s\\CAPTURED THE %sRED FLAG%s.\\\\\\\\"), "\x84", player_names[player-players], "\x80", "\x85", "\x80"));
+					HU_DoCEcho(va(M_GetText("\204%s\200\\CAPTURED THE \205RED FLAG\200.\\\\\\\\"), player_names[player-players]));
 
 					if (splitscreen || players[consoleplayer].ctfteam == 2)
 						S_StartSound(NULL, sfx_flgcap);
@@ -4789,7 +4669,7 @@ DoneSection2:
 			break;
 
 		case 7: // Make player spin
-			if (!(player->pflags & PF_SPINNING) && P_IsObjectOnGround(player->mo) && (player->charability2 == CA2_SPINDASH))
+			if (!(player->pflags & PF_SPINNING))
 			{
 				player->pflags |= PF_SPINNING;
 				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
@@ -4816,7 +4696,7 @@ DoneSection2:
 					break;
 
 				// Find line #3 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(3, sectag);
 
 				if (lineindex == -1)
 				{
@@ -4881,7 +4761,7 @@ DoneSection2:
 					break;
 
 				// Find line #3 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(3, sectag);
 
 				if (lineindex == -1)
 				{
@@ -4943,6 +4823,8 @@ DoneSection2:
 
 					if (player->laps >= (UINT8)cv_numlaps.value)
 						CONS_Printf(M_GetText("%s has finished the race.\n"), player_names[player-players]);
+					else if (player->laps == (UINT8)cv_numlaps.value-1)
+						CONS_Printf(M_GetText("%s started the \205final lap\200!\n"), player_names[player-players]);
 					else
 						CONS_Printf(M_GetText("%s started lap %u\n"), player_names[player-players], (UINT32)player->laps+1);
 
@@ -4996,7 +4878,7 @@ DoneSection2:
 				if (player->mo->momz > 0)
 					break;
 
-				if (player->cmd.buttons & BT_USE)
+				if (player->cmd.buttons & BT_SPIN)
 					break;
 
 				if (!(player->pflags & PF_SLIDING) && player->mo->state == &states[player->mo->info->painstate])
@@ -5010,7 +4892,7 @@ DoneSection2:
 				memset(&resulthigh, 0x00, sizeof(resulthigh));
 
 				// Find line #11 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(11, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(11, sectag);
 
 				if (lineindex == -1)
 				{
@@ -5630,7 +5512,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	{
 		fixed_t tempceiling = sec2->ceilingheight;
 		//flip the sector around and print an error instead of crashing 12.1.08 -Inuyasha
-		CONS_Alert(CONS_ERROR, M_GetText("A FOF tagged %d has a top height below its bottom.\n"), master->tag);
+		CONS_Alert(CONS_ERROR, M_GetText("FOF (line %s) has a top height below its bottom.\n"), sizeu1(master - lines));
 		sec2->ceilingheight = sec2->floorheight;
 		sec2->floorheight = tempceiling;
 	}
@@ -5771,7 +5653,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	if ((flags & FF_FLOATBOB))
 	{
-		P_AddFloatThinker(sec2, sec->tag, master);
+		P_AddFloatThinker(sec2, Tag_FGet(&master->tags), master);
 		CheckForFloatBob = true;
 	}
 
@@ -5887,7 +5769,7 @@ static void P_AddRaiseThinker(sector_t *sec, INT16 tag, fixed_t speed, fixed_t c
 	raise->ceilingtop = ceilingtop;
 	raise->ceilingbottom = ceilingbottom;
 
-	raise->basespeed =  speed;
+	raise->basespeed = speed;
 
 	if (lower)
 		raise->flags |= RF_REVERSE;
@@ -5927,7 +5809,7 @@ static void P_AddAirbob(sector_t *sec, INT16 tag, fixed_t dist, boolean raise, b
   * \sa P_SpawnSpecials, T_ThwompSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
+static inline void P_AddThwompThinker(sector_t *sec, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
 {
 	thwomp_t *thwomp;
 
@@ -5951,7 +5833,7 @@ static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceli
 	thwomp->floorstartheight = sec->floorheight;
 	thwomp->ceilingstartheight = sec->ceilingheight;
 	thwomp->delay = 1;
-	thwomp->tag = tag;
+	thwomp->tag = Tag_FGet(&sourceline->tags);
 	thwomp->sound = sound;
 
 	sec->floordata = thwomp;
@@ -6043,8 +5925,9 @@ void T_LaserFlash(laserthink_t *flash)
 	sector_t *sector;
 	sector_t *sourcesec = flash->sourceline->frontsector;
 	fixed_t top, bottom;
+	TAG_ITER_DECLARECOUNTER(0);
 
-	for (s = -1; (s = P_FindSectorFromTag(flash->tag, s)) >= 0 ;)
+	TAG_ITER_SECTORS(0, flash->tag, s)
 	{
 		sector = &sectors[s];
 		for (fflr = sector->ffloors; fflr; fflr = fflr->next)
@@ -6130,7 +6013,7 @@ static void P_RunLevelLoadExecutors(void)
   * by P_SpawnSlopes or P_LoadThings. This was split off from
   * P_SpawnSpecials, in case you couldn't tell.
   *
-  * \sa P_SpawnSpecials, P_InitTagLists
+  * \sa P_SpawnSpecials
   * \author Monster Iestyn
   */
 void P_InitSpecials(void)
@@ -6300,6 +6183,8 @@ void P_SpawnSpecials(boolean fromnetsave)
 	// Init line EFFECTs
 	for (i = 0; i < numlines; i++)
 	{
+		mtag_t tag = Tag_FGet(&lines[i].tags);
+
 		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment in netgames...
 		{
 			// set line specials to 0 here too, same reason as above
@@ -6323,10 +6208,11 @@ void P_SpawnSpecials(boolean fromnetsave)
 			INT32 s;
 			size_t sec;
 			ffloortype_e ffloorflags;
+			TAG_ITER_DECLARECOUNTER(0);
 
 			case 1: // Definable gravity per sector
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					sectors[s].gravity = &sectors[sec].floorheight; // This allows it to change in realtime!
 
@@ -6350,7 +6236,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 5: // Change camera info
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddCameraScanner(&sectors[sec], &sectors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
 				break;
 
@@ -6373,22 +6259,22 @@ void P_SpawnSpecials(boolean fromnetsave)
 					}
 
 					//If no tag is given, apply to front sector
-					if (lines[i].tag == 0)
+					if (tag == 0)
 						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
 					else
 					{
-						for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0;)
+						TAG_ITER_SECTORS(0, tag, s)
 							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
 					}
 				}
 				else // Otherwise, print a helpful warning. Can I do no less?
 					CONS_Alert(CONS_WARNING,
 					M_GetText("Flat alignment linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					lines[i].tag);
+					tag);
 				break;
 
 			case 8: // Sector Parameters
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					if (lines[i].flags & ML_NOCLIMB)
 					{
@@ -6415,8 +6301,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 10: // Vertical culling plane for sprites and FOFs
-				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
 				break;
 
@@ -6477,36 +6362,38 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 63: // support for drawn heights coming from different sector
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].heightsec = (INT32)sec;
 				break;
 
 			case 64: // Appearing/Disappearing FOF option
 				if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
-					for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+					TAG_ITER_SECTORS(0, tag, s)
 						for (j = 0; (unsigned)j < sectors[s].linecount; j++)
 							if (sectors[s].lines[j]->special >= 100 && sectors[s].lines[j]->special < 300)
 								Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), (INT32)(sectors[s].lines[j]-lines), (INT32)i);
 				} else // Find FOFs by effect sector tag
-					for (s = -1; (s = P_FindLineFromTag(lines[i].tag, s)) >= 0 ;)
+				{
+					TAG_ITER_LINES(0, tag, s)
 					{
 						if ((size_t)s == i)
 							continue;
-						if (sides[lines[s].sidenum[0]].sector->tag == sides[lines[i].sidenum[0]].sector->tag)
+						if (Tag_Find(&sides[lines[s].sidenum[0]].sector->tags, Tag_FGet(&sides[lines[i].sidenum[0]].sector->tags)))
 							Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), s, (INT32)i);
 					}
+				}
 				break;
 
 			case 66: // Displace floor by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 67: // Displace ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 68: // Displace both floor AND ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 
@@ -6703,16 +6590,16 @@ void P_SpawnSpecials(boolean fromnetsave)
 			{
 				fixed_t dist = (lines[i].special == 150) ? 16*FRACUNIT : P_AproxDistance(lines[i].dx, lines[i].dy);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			}
 			case 152: // Adjustable air bobbing platform in reverse
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			case 153: // Dynamic Sinking Platform
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
+				P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
 				break;
 
 			case 160: // Float/bob platform
@@ -6762,13 +6649,13 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 177: // Air bobbing platform that will crumble and bob on
 				// the water when it falls and hits, then never return
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 178: // Crumbling platform that will float when it hits water
@@ -6781,7 +6668,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 180: // Air bobbing platform that will crumble
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 190: // Rising Platform FOF (solid, opaque, shadows)
@@ -6808,7 +6695,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 					ffloorflags |= FF_NOSHADE;
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 
-				P_AddRaiseThinker(lines[i].frontsector, lines[i].tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
+				P_AddRaiseThinker(lines[i].frontsector, tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			}
 
@@ -6870,7 +6757,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				fixed_t crushspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dy >> 3 : 10*FRACUNIT;
 				fixed_t retractspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dx >> 3 : 2*FRACUNIT;
 				UINT16 sound = (lines[i].flags & ML_EFFECT4) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : sfx_thwomp;
-				P_AddThwompThinker(lines[i].frontsector, lines[i].tag, &lines[i], crushspeed, retractspeed, sound);
+				P_AddThwompThinker(lines[i].frontsector, &lines[i], crushspeed, retractspeed, sound);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
 				break;
 			}
@@ -6912,7 +6799,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 258: // Laser block
-				P_AddLaserThinker(lines[i].tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
+				P_AddLaserThinker(tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT, secthinkers);
 				break;
 
@@ -6923,7 +6810,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 					P_AddFakeFloorsByLine(i, fofflags, secthinkers);
 				}
 				else
-					I_Error("Custom FOF (tag %d) found without a linedef back side!", lines[i].tag);
+					I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
 				break;
 
 			case 300: // Linedef executor (combines with sector special 974/975) and commands
@@ -7053,7 +6940,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				{
 					CONS_Alert(CONS_WARNING,
 						M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-						lines[i].tag);
+						tag);
 					break;
 				}
 				if (!(lines[i].flags & ML_NOCLIMB))
@@ -7102,47 +6989,71 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 600: // floor lighting independently (e.g. lava)
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].floorlightsec = (INT32)sec;
 				break;
 
 			case 601: // ceiling lighting independently
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].ceilinglightsec = (INT32)sec;
 				break;
 
 			case 602: // Adjustable pulsating light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableGlowingLight(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 603: // Adjustable flickering light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableFireFlicker(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 604: // Adjustable Blinking Light (unsynchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
 				break;
 
 			case 605: // Adjustable Blinking Light (synchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
 				break;
 
 			case 606: // HACK! Copy colormaps. Just plain colormaps.
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
-					sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = sides[lines[i].sidenum[0]].colormap_data;
+				TAG_ITER_SECTORS(0, lines[i].args[0], s)
+				{
+					extracolormap_t *exc;
+
+					if (sectors[s].colormap_protected)
+						continue;
+
+					if (!udmf)
+						exc = sides[lines[i].sidenum[0]].colormap_data;
+					else
+					{
+						if (!lines[i].args[1])
+							exc = lines[i].frontsector->extra_colormap;
+						else
+						{
+							INT32 sourcesec = Tag_Iterate_Sectors(lines[i].args[1], 0);
+							if (sourcesec == -1)
+							{
+								CONS_Debug(DBG_GAMELOGIC, "Line type 606: Can't find sector with source colormap (tag %d)!\n", lines[i].args[1]);
+								return;
+							}
+							exc = sectors[sourcesec].extra_colormap;
+						}
+					}
+					sectors[s].extra_colormap = sectors[s].spawn_extra_colormap = exc;
+				}
 				break;
 
 			default:
@@ -7191,11 +7102,14 @@ void P_SpawnSpecials(boolean fromnetsave)
   */
 static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
 {
+	TAG_ITER_DECLARECOUNTER(0);
 	INT32 s;
+	mtag_t tag = Tag_FGet(&lines[line].tags);
 	size_t sec = sides[*lines[line].sidenum].sector-sectors;
 
-	for (s = -1; (s = P_FindSectorFromTag(lines[line].tag, s)) >= 0 ;)
-		P_AddFakeFloor(&sectors[s], &sectors[sec], lines+line, ffloorflags, secthinkers);
+	line_t* li = lines + line;
+	TAG_ITER_SECTORS(0, tag, s)
+		P_AddFakeFloor(&sectors[s], &sectors[sec], li, ffloorflags, secthinkers);
 }
 
 /*
@@ -7304,6 +7218,7 @@ void T_Scroll(scroll_t *s)
 		size_t i;
 		INT32 sect;
 		ffloor_t *rover;
+		TAG_ITER_DECLARECOUNTER(0);
 
 		case sc_side: // scroll wall texture
 			side = sides + s->affectee;
@@ -7340,7 +7255,7 @@ void T_Scroll(scroll_t *s)
 				if (!is3dblock)
 					continue;
 
-				for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
+				TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7415,8 +7330,7 @@ void T_Scroll(scroll_t *s)
 
 				if (!is3dblock)
 					continue;
-
-				for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
+				TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7514,6 +7428,7 @@ static void P_SpawnScrollers(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 
 	for (i = 0; i < numlines; i++, l++)
 	{
@@ -7522,6 +7437,8 @@ static void P_SpawnScrollers(void)
 		INT32 control = -1, accel = 0; // no control sector or acceleration
 		INT32 special = l->special;
 
+		tag = Tag_FGet(&l->tags);
+
 		// These types are same as the ones they get set to except that the
 		// first side's sector's heights cause scrolling when they change, and
 		// this linedef controls the direction and speed of the scrolling. The
@@ -7553,10 +7470,11 @@ static void P_SpawnScrollers(void)
 		switch (special)
 		{
 			register INT32 s;
+			TAG_ITER_DECLARECOUNTER(0);
 
 			case 513: // scroll effect ceiling
 			case 533: // scroll and carry objects on ceiling
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 533)
 					break;
@@ -7565,13 +7483,13 @@ static void P_SpawnScrollers(void)
 			case 523:	// carry objects on ceiling
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_carry_ceiling, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			case 510: // scroll effect floor
 			case 530: // scroll and carry objects on floor
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 530)
 					break;
@@ -7580,14 +7498,15 @@ static void P_SpawnScrollers(void)
 			case 520:	// carry objects on floor
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			// scroll wall according to linedef
 			// (same direction and speed as scrolling floors)
 			case 502:
-				for (s = -1; (s = P_FindLineFromTag(l->tag, s)) >= 0 ;)
+			{
+				TAG_ITER_LINES(0, tag, s)
 					if (s != (INT32)i)
 					{
 						if (l->flags & ML_EFFECT2) // use texture offsets instead
@@ -7604,6 +7523,7 @@ static void P_SpawnScrollers(void)
 							Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
 					}
 				break;
+			}
 
 			case 505:
 				s = lines[i].sidenum[0];
@@ -7687,8 +7607,10 @@ void T_Disappear(disappear_t *d)
 	{
 		ffloor_t *rover;
 		register INT32 s;
+		mtag_t afftag = Tag_FGet(&lines[d->affectee].tags);
+		TAG_ITER_DECLARECOUNTER(0);
 
-		for (s = -1; (s = P_FindSectorFromTag(lines[d->affectee].tag, s)) >= 0 ;)
+		TAG_ITER_SECTORS(0, afftag, s)
 		{
 			for (rover = sectors[s].ffloors; rover; rover = rover->next)
 			{
@@ -8105,7 +8027,7 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor
 		d->destlightlevel = -1;
 
 	// Set a separate thinker for colormap fading
-	if (docolormap && !(rover->flags & FF_NOSHADE) && sectors[rover->secnum].spawn_extra_colormap)
+	if (docolormap && !(rover->flags & FF_NOSHADE) && sectors[rover->secnum].spawn_extra_colormap && !sectors[rover->secnum].colormap_protected)
 	{
 		extracolormap_t *dest_exc,
 			*source_exc = sectors[rover->secnum].extra_colormap ? sectors[rover->secnum].extra_colormap : R_GetDefaultColormap();
@@ -8414,14 +8336,17 @@ static void P_SpawnFriction(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 	register INT32 s;
 	fixed_t strength; // frontside texture offset controls magnitude
 	fixed_t friction; // friction value to be applied during movement
 	INT32 movefactor; // applied to each player move to simulate inertia
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < numlines; i++, l++)
 		if (l->special == 540)
 		{
+			tag = Tag_FGet(&l->tags);
 			strength = sides[l->sidenum[0]].textureoffset>>FRACBITS;
 			if (strength > 0) // sludge
 				strength = strength*2; // otherwise, the maximum sludginess value is +967...
@@ -8442,7 +8367,7 @@ static void P_SpawnFriction(void)
 			else
 				movefactor = FRACUNIT;
 
-			for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, s)
 				Add_Friction(friction, movefactor, s, -1);
 		}
 }
@@ -8958,22 +8883,26 @@ static void P_SpawnPushers(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 	register INT32 s;
 	mobj_t *thing;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < numlines; i++, l++)
+	{
+		tag = Tag_FGet(&l->tags);
 		switch (l->special)
 		{
 			case 541: // wind
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 544: // current
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 547: // push/pull
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					thing = P_GetPushThing(s);
 					if (thing) // No MT_P* means no effect
@@ -8981,20 +8910,21 @@ static void P_SpawnPushers(void)
 				}
 				break;
 			case 545: // current up
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 546: // current down
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 542: // wind up
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 543: // wind down
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 		}
+	}
 }
diff --git a/src/p_spec.h b/src/p_spec.h
index 40834857db4249d07977e97a7a3d67754444e198..f56a199a962902c4cf18bea491301322be4ca37f 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -34,7 +34,6 @@ void P_SetupLevelFlatAnims(void);
 
 // at map load
 void P_InitSpecials(void);
-void P_InitTagLists(void);
 void P_SpawnSpecials(boolean fromnetsave);
 
 // every tic
@@ -52,9 +51,6 @@ fixed_t P_FindNextLowestFloor(sector_t *sec, fixed_t currentheight);
 fixed_t P_FindLowestCeilingSurrounding(sector_t *sec);
 fixed_t P_FindHighestCeilingSurrounding(sector_t *sec);
 
-INT32 P_FindSectorFromTag(INT16 tag, INT32 start);
-INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
-
 INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max);
 
 void P_SetupSignExit(player_t *player);
@@ -64,7 +60,6 @@ void P_SwitchWeather(INT32 weathernum);
 
 boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller);
 void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller);
-void P_ChangeSectorTag(UINT32 sector, INT16 newtag);
 void P_RunNightserizeExecutors(mobj_t *actor);
 void P_RunDeNightserizeExecutors(mobj_t *actor);
 void P_RunNightsLapExecutors(mobj_t *actor);
diff --git a/src/p_tick.c b/src/p_tick.c
index 0d71dd58f41856ebc388115c08bd4c196af07b09..31e97edeac35c304dc8f6396a43d91489cdde6c9 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -21,6 +21,8 @@
 #include "m_random.h"
 #include "lua_script.h"
 #include "lua_hook.h"
+#include "m_perfstats.h"
+#include "i_system.h" // I_GetPreciseTime
 
 // Object place
 #include "m_cheat.h"
@@ -341,6 +343,7 @@ static inline void P_RunThinkers(void)
 	size_t i;
 	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
+		ps_thlist_times[i] = I_GetPreciseTime();
 		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = currentthinker->next)
 		{
 #ifdef PARANOIA
@@ -348,6 +351,7 @@ static inline void P_RunThinkers(void)
 #endif
 			currentthinker->function.acp1(currentthinker);
 		}
+		ps_thlist_times[i] = I_GetPreciseTime() - ps_thlist_times[i];
 	}
 }
 
@@ -355,9 +359,12 @@ static inline void P_RunWorldThinkers(void)
 {
 	INT32 i;
 
+	ps_thinkertime = I_GetPreciseTime();
+
 	if (!(netgame || multiplayer) || (numworlds < 2))
 	{
 		P_RunThinkers();
+		ps_thinkertime = I_GetPreciseTime() - ps_thinkertime;
 		return;
 	}
 
@@ -371,6 +378,8 @@ static inline void P_RunWorldThinkers(void)
 		P_SetWorld(w);
 		P_RunThinkers();
 	}
+
+	ps_thinkertime = I_GetPreciseTime() - ps_thinkertime;
 }
 
 //
@@ -753,10 +762,22 @@ void P_Ticker(boolean run)
 		if (demorecording)
 			G_WriteDemoTiccmd(&players[consoleplayer].cmd, 0);
 		if (demoplayback)
-			G_ReadDemoTiccmd(&players[consoleplayer].cmd, 0);
+		{
+			player_t* p = &players[consoleplayer];
+			G_ReadDemoTiccmd(&p->cmd, 0);
+			if (!cv_freedemocamera.value)
+			{
+				P_ForceLocalAngle(p, p->cmd.angleturn << 16);
+				localaiming = p->aiming;
+			}
+		}
+
+		ps_lua_mobjhooks = 0;
+		ps_checkposition_calls = 0;
 
 		LUAh_PreThinkFrame();
 
+		ps_playerthink_time = I_GetPreciseTime();
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
 			{
@@ -767,6 +788,7 @@ void P_Ticker(boolean run)
 
 				P_PlayerThink(player);
 			}
+		ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
 	}
 
 	// Keep track of how long they've been playing!
@@ -796,7 +818,9 @@ void P_Ticker(boolean run)
 			}
 	}
 
+	ps_lua_thinkframe_time = I_GetPreciseTime();
 	P_WorldRunVoid(LUAh_ThinkFrame);
+	ps_lua_thinkframe_time = I_GetPreciseTime() - ps_lua_thinkframe_time;
 
 	// Run shield positioning
 	P_RunWorldSpecials();
diff --git a/src/p_user.c b/src/p_user.c
index 8d8dcd3e7c0aa3dd8f90aca1092423eda0d75b07..906ecf982845c2f9ca76b0cad068cf6783682d8b 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -190,7 +190,7 @@ fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move)
 boolean P_AutoPause(void)
 {
 	// Don't pause even on menu-up or focus-lost in netgames or record attack
-	if (netgame || modeattacking || gamestate == GS_TITLESCREEN)
+	if (netgame || modeattacking || gamestate == GS_TITLESCREEN || (marathonmode && gamestate == GS_INTERMISSION))
 		return false;
 
 	return (menuactive || ( window_notinfocus && cv_pauseifunfocused.value ));
@@ -665,7 +665,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->powers[pw_carry] = CR_NIGHTSFALL;
 
 	player->powers[pw_underwater] = 0;
-	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
+	player->pflags &= ~(PF_SPINDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_DRILLING|PF_TRANSFERTOCLOSEST);
 	player->secondjump = 0;
 	player->homing = 0;
 	player->climbing = 0;
@@ -795,7 +795,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		}
 	}
 
-	player->pflags &= ~(PF_USEDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING|PF_DRILLING);
+	player->pflags &= ~(PF_SPINDOWN|PF_JUMPDOWN|PF_ATTACKDOWN|PF_STARTDASH|PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING|PF_DRILLING);
 	player->homing = 0;
 	player->mo->fuse = 0;
 	player->speed = 0;
@@ -1341,7 +1341,7 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 	// Transformation animation
 	P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_TRANS1);
 
-	if (giverings)
+	if (giverings && player->rings < 50)
 		player->rings = 50;
 
 	// Just in case.
@@ -1491,10 +1491,10 @@ void P_PlayLivesJingle(player_t *player)
 	if (player && !P_IsLocalPlayer(player))
 		return;
 
-	if (use1upSound || cv_1upsound.value)
-		S_StartSound(NULL, sfx_oneup);
-	else if (mariomode)
+	if (mariomode)
 		S_StartSound(NULL, sfx_marioa);
+	else if (use1upSound || cv_1upsound.value)
+		S_StartSound(NULL, sfx_oneup);
 	else
 	{
 		P_PlayJingle(player, JT_1UP);
@@ -2029,6 +2029,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	ghost->colorized = mobj->colorized; // alternatively, "true" for sonic advance style colourisation
 
 	ghost->angle = (mobj->player ? mobj->player->drawangle : mobj->angle);
+	ghost->rollangle = mobj->rollangle;
 	ghost->sprite = mobj->sprite;
 	ghost->sprite2 = mobj->sprite2;
 	ghost->frame = mobj->frame;
@@ -2305,7 +2306,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 		if (dorollstuff)
 		{
 			if ((player->charability2 == CA2_SPINDASH) && !((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_THOKKED) && !(player->charability == CA_THOK && player->secondjump)
-			&& (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+			&& (player->cmd.buttons & BT_SPIN) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
 				player->pflags = (player->pflags|PF_SPINNING) & ~PF_THOKKED;
 			else if (!(player->pflags & PF_STARTDASH))
 				player->pflags &= ~PF_SPINNING;
@@ -2330,7 +2331,8 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 			P_MobjCheckWater(player->mo);
 			if (player->pflags & PF_SPINNING)
 			{
-				if (player->mo->state-states != S_PLAY_ROLL && !(player->pflags & PF_STARTDASH))
+				if (!(player->pflags & PF_STARTDASH) && player->panim != PA_ROLL && player->panim != PA_ETC
+				&& player->panim != PA_ABILITY && player->panim != PA_ABILITY2)
 				{
 					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 					S_StartSound(player->mo, sfx_spin);
@@ -2370,7 +2372,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				}
 			}
 			else if (player->charability2 == CA2_MELEE
-				&& ((player->panim == PA_ABILITY2) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY && player->cmd.buttons & (BT_JUMP|BT_USE))))
+				&& ((player->panim == PA_ABILITY2) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY && player->cmd.buttons & (BT_JUMP|BT_SPIN))))
 			{
 				if (player->mo->state-states != S_PLAY_MELEE_LANDING)
 				{
@@ -2566,7 +2568,7 @@ static boolean P_PlayerCanBust(player_t *player, ffloor_t *rover)
 	}
 
 	// Strong abilities can break even FF_STRONGBUST.
-	if (player->charability == CA_GLIDEANDCLIMB)
+	if (player->charflags & SF_CANBUSTWALLS)
 		return true;
 
 	if (player->pflags & PF_BOUNCING)
@@ -3216,7 +3218,7 @@ static void P_DoClimbing(player_t *player)
 
 				for (rover = glidesector->sector->ffloors; rover; rover = rover->next)
 				{
-					if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || (rover->flags & FF_BUSTUP))
+					if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || ((rover->flags & FF_BUSTUP) && (player->charflags & SF_CANBUSTWALLS)))
 						continue;
 
 					floorclimb = true;
@@ -3257,7 +3259,7 @@ static void P_DoClimbing(player_t *player)
 							// Is there a FOF directly below this one that we can move onto?
 							for (roverbelow = glidesector->sector->ffloors; roverbelow; roverbelow = roverbelow->next)
 							{
-								if (!(roverbelow->flags & FF_EXISTS) || !(roverbelow->flags & FF_BLOCKPLAYER) || (roverbelow->flags & FF_BUSTUP))
+								if (!(roverbelow->flags & FF_EXISTS) || !(roverbelow->flags & FF_BLOCKPLAYER) || ((rover->flags & FF_BUSTUP) && (player->charflags & SF_CANBUSTWALLS)))
 									continue;
 
 								if (roverbelow == rover)
@@ -3302,7 +3304,7 @@ static void P_DoClimbing(player_t *player)
 							// Is there a FOF directly below this one that we can move onto?
 							for (roverbelow = glidesector->sector->ffloors; roverbelow; roverbelow = roverbelow->next)
 							{
-								if (!(roverbelow->flags & FF_EXISTS) || !(roverbelow->flags & FF_BLOCKPLAYER) || (roverbelow->flags & FF_BUSTUP))
+								if (!(roverbelow->flags & FF_EXISTS) || !(roverbelow->flags & FF_BLOCKPLAYER) || ((rover->flags & FF_BUSTUP) && (player->charflags & SF_CANBUSTWALLS)))
 									continue;
 
 								if (roverbelow == rover)
@@ -3359,7 +3361,7 @@ static void P_DoClimbing(player_t *player)
 						ffloor_t *rover;
 						for (rover = glidesector->sector->ffloors; rover; rover = rover->next)
 						{
-							if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || (rover->flags & FF_BUSTUP))
+							if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || ((rover->flags & FF_BUSTUP) && (player->charflags & SF_CANBUSTWALLS)))
 								continue;
 
 							bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
@@ -3399,7 +3401,7 @@ static void P_DoClimbing(player_t *player)
 						ffloor_t *rover;
 						for (rover = glidesector->sector->ffloors; rover; rover = rover->next)
 						{
-							if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || (rover->flags & FF_BUSTUP))
+							if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || ((rover->flags & FF_BUSTUP) && (player->charflags & SF_CANBUSTWALLS)))
 								continue;
 
 							topheight = P_GetFFloorTopZAt(rover, player->mo->x, player->mo->y);
@@ -3578,7 +3580,7 @@ static void P_DoClimbing(player_t *player)
 	else if ((!(player->mo->momx || player->mo->momy || player->mo->momz) || !climb) && player->mo->state-states != S_PLAY_CLING)
 		P_SetPlayerMobjState(player->mo, S_PLAY_CLING);
 
-	if (cmd->buttons & BT_USE && !(player->pflags & PF_JUMPSTASIS))
+	if (cmd->buttons & BT_SPIN && !(player->pflags & PF_JUMPSTASIS))
 	{
 		player->climbing = 0;
 		player->pflags |= P_GetJumpFlags(player);
@@ -4499,7 +4501,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 	if (twodlevel || (player->mo->flags2 & MF2_TWOD))
 		factor += player->jumpfactor / 10;
 
-	if (player->charflags & SF_MULTIABILITY && player->charability == CA_DOUBLEJUMP)
+	if (player->charflags & SF_MULTIABILITY && player->charability == CA_DOUBLEJUMP && (player->actionspd >> FRACBITS) != -1)
 		factor -= max(0, player->secondjump * player->jumpfactor / ((player->actionspd >> FRACBITS) + 1)); // Reduce the jump height each time
 
 	//if (maptol & TOL_NIGHTS)
@@ -4526,6 +4528,9 @@ void P_DoJump(player_t *player, boolean soundandstate)
 
 	player->pflags |= P_GetJumpFlags(player);;
 
+	if (player->charflags & SF_NOJUMPDAMAGE)
+		player->pflags &= ~PF_SPINNING;
+
 	if (soundandstate)
 	{
 		if (!player->spectator)
@@ -4576,7 +4581,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 		&& (player->pflags & PF_JUMPSTASIS || player->mo->state-states != S_PLAY_GLIDE_LANDING))
 		return;
 
-	if (cmd->buttons & BT_USE)
+	if (cmd->buttons & BT_SPIN)
 	{
 		if (LUAh_SpinSpecial(player))
 			return;
@@ -4593,20 +4598,20 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 		{
 			case CA2_SPINDASH: // Spinning and Spindashing
 				 // Start revving
-				if ((cmd->buttons & BT_USE) && (player->speed < FixedMul(5<<FRACBITS, player->mo->scale) || player->mo->state - states == S_PLAY_GLIDE_LANDING)
-					&& !player->mo->momz && onground && !(player->pflags & (PF_USEDOWN|PF_SPINNING))
+				if ((cmd->buttons & BT_SPIN) && (player->speed < FixedMul(5<<FRACBITS, player->mo->scale) || player->mo->state - states == S_PLAY_GLIDE_LANDING)
+					&& !player->mo->momz && onground && !(player->pflags & (PF_SPINDOWN|PF_SPINNING))
 						&& canstand)
 				{
 					player->mo->momx = player->cmomx;
 					player->mo->momy = player->cmomy;
-					player->pflags |= (PF_USEDOWN|PF_STARTDASH|PF_SPINNING);
+					player->pflags |= (PF_SPINDOWN|PF_STARTDASH|PF_SPINNING);
 					player->dashspeed = player->mindash;
 					P_SetPlayerMobjState(player->mo, S_PLAY_SPINDASH);
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_spndsh); // Make the rev sound!
 				}
 				 // Revving
-				else if ((cmd->buttons & BT_USE) && (player->pflags & PF_STARTDASH))
+				else if ((cmd->buttons & BT_SPIN) && (player->pflags & PF_STARTDASH))
 				{
 					if (player->speed > 5*player->mo->scale)
 					{
@@ -4639,18 +4644,18 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 				// If not moving up or down, and travelling faster than a speed of five while not holding
 				// down the spin button and not spinning.
 				// AKA Just go into a spin on the ground, you idiot. ;)
-				else if ((cmd->buttons & BT_USE || ((twodlevel || (player->mo->flags2 & MF2_TWOD)) && cmd->forwardmove < -20))
+				else if ((cmd->buttons & BT_SPIN || ((twodlevel || (player->mo->flags2 & MF2_TWOD)) && cmd->forwardmove < -20))
 					&& !player->climbing && !player->mo->momz && onground && (player->speed > FixedMul(5<<FRACBITS, player->mo->scale)
-						|| !canstand) && !(player->pflags & (PF_USEDOWN|PF_SPINNING)))
+						|| !canstand) && !(player->pflags & (PF_SPINDOWN|PF_SPINNING)))
 				{
-					player->pflags |= (PF_USEDOWN|PF_SPINNING);
+					player->pflags |= (PF_SPINDOWN|PF_SPINNING);
 					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_spin);
 				}
 				else
 				// Catapult the player from a spindash rev!
-				if (onground && !(player->pflags & PF_USEDOWN) && (player->pflags & PF_STARTDASH) && (player->pflags & PF_SPINNING))
+				if (onground && !(player->pflags & PF_SPINDOWN) && (player->pflags & PF_STARTDASH) && (player->pflags & PF_SPINNING))
 				{
 					player->pflags &= ~PF_STARTDASH;
 					if (player->powers[pw_carry] == CR_BRAKGOOP)
@@ -4692,7 +4697,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 								P_SetTarget(&visual->target, lockon);
 							}
 						}
-						if ((cmd->buttons & BT_USE) && !(player->pflags & PF_USEDOWN))
+						if ((cmd->buttons & BT_SPIN) && !(player->pflags & PF_SPINDOWN))
 						{
 							mobj_t *bullet;
 
@@ -4721,15 +4726,15 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 
 							player->mo->momx >>= 1;
 							player->mo->momy >>= 1;
-							player->pflags |= PF_USEDOWN;
+							player->pflags |= PF_SPINDOWN;
 							P_SetWeaponDelay(player, TICRATE/2);
 						}
 					}
 				}
 				break;
 			case CA2_MELEE: // Melee attack
-				if (player->panim != PA_ABILITY2 && (cmd->buttons & BT_USE)
-				&& !player->mo->momz && onground && !(player->pflags & PF_USEDOWN)
+				if (player->panim != PA_ABILITY2 && (cmd->buttons & BT_SPIN)
+				&& !player->mo->momz && onground && !(player->pflags & PF_SPINDOWN)
 				&& canstand)
 				{
 					P_ResetPlayer(player);
@@ -4769,7 +4774,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						P_SetPlayerMobjState(player->mo, S_PLAY_MELEE);
 						S_StartSound(player->mo, sfx_s3k42);
 					}
-					player->pflags |= PF_USEDOWN;
+					player->pflags |= PF_SPINDOWN;
 				}
 				break;
 		}
@@ -4837,6 +4842,8 @@ void P_DoJumpShield(player_t *player)
 		}
 #undef limitangle
 #undef numangles
+		player->pflags &= ~PF_NOJUMPDAMAGE;
+		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 		S_StartSound(player->mo, sfx_s3k45);
 	}
 	else
@@ -4865,7 +4872,7 @@ void P_DoBubbleBounce(player_t *player)
 	player->pflags |= PF_THOKKED;
 	player->pflags &= ~PF_STARTJUMP;
 	player->secondjump = UINT8_MAX;
-	player->mo->momz = FixedMul(player->mo->momz, 5*FRACUNIT/4);
+	player->mo->momz = FixedMul(player->mo->momz, 11*FRACUNIT/8);
 }
 
 //
@@ -4875,22 +4882,28 @@ void P_DoBubbleBounce(player_t *player)
 //
 void P_DoAbilityBounce(player_t *player, boolean changemomz)
 {
-	fixed_t prevmomz;
 	if (player->mo->state-states == S_PLAY_BOUNCE_LANDING)
 		return;
+
 	if (changemomz)
 	{
-		fixed_t minmomz;
-		prevmomz = player->mo->momz;
+		fixed_t prevmomz = player->mo->momz, minmomz;
+
 		if (P_MobjFlip(player->mo)*prevmomz < 0)
 			prevmomz = 0;
 		else if (player->mo->eflags & MFE_UNDERWATER)
 			prevmomz /= 2;
+
 		P_DoJump(player, false);
 		player->pflags &= ~(PF_STARTJUMP|PF_JUMPED);
 		minmomz = FixedMul(player->mo->momz, 3*FRACUNIT/2);
-		player->mo->momz = max(minmomz, (minmomz + prevmomz)/2);
+
+		if (player->mo->eflags & MFE_VERTICALFLIP) // Use "min" or "max" depending on if the player is flipped
+			player->mo->momz = min(minmomz, (minmomz + prevmomz)/2);
+		else
+			player->mo->momz = max(minmomz, (minmomz + prevmomz)/2);
 	}
+
 	S_StartSound(player->mo, sfx_boingf);
 	P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE_LANDING);
 	player->pflags |= PF_BOUNCING|PF_THOKKED;
@@ -5008,6 +5021,120 @@ static void P_DoTwinSpin(player_t *player)
 	P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
 }
 
+//
+// returns true if the player used a shield ability, false otherwise
+// passing in the mobjs from P_DoJumpStuff is a bit hackily specific, but I don't care enough to make a more elaborate solution (I think that is more appropriately approached with a more general MT_LOCKON spawning system)
+//
+static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lockonthok, mobj_t *visual)
+{
+	mobj_t *lockonshield = NULL;
+
+	if ((player->powers[pw_shield] & SH_NOSTACK) && !player->powers[pw_super] && !(player->pflags & PF_SPINDOWN)
+		&& ((!(player->pflags & PF_THOKKED) || (((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP || (player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT) && player->secondjump == UINT8_MAX) ))) // thokked is optional if you're bubblewrapped / 3dblasted
+	{
+		if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT && !(player->charflags & SF_NOSHIELDABILITY))
+		{
+			if ((lockonshield = P_LookForEnemies(player, false, false)))
+			{
+				if (P_IsLocalPlayer(player)) // Only display it on your own view.
+				{
+					boolean dovis = true;
+					if (lockonshield == lockonthok)
+					{
+						if (leveltime & 2)
+							dovis = false;
+						else if (visual)
+							P_RemoveMobj(visual);
+					}
+					if (dovis)
+					{
+						visual = P_SpawnMobj(lockonshield->x, lockonshield->y, lockonshield->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+						P_SetTarget(&visual->target, lockonshield);
+						P_SetMobjStateNF(visual, visual->info->spawnstate+1);
+					}
+				}
+			}
+		}
+		if ((!(player->charflags & SF_NOSHIELDABILITY)) && (cmd->buttons & BT_SPIN && !LUAh_ShieldSpecial(player))) // Spin button effects
+		{
+			// Force stop
+			if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
+			{
+				player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+				player->mo->momx = player->mo->momy = player->mo->momz = 0;
+				S_StartSound(player->mo, sfx_ngskid);
+			}
+			else
+			{
+				switch (player->powers[pw_shield] & SH_NOSTACK)
+				{
+					// Whirlwind jump/Thunder jump
+					case SH_WHIRLWIND:
+					case SH_THUNDERCOIN:
+						P_DoJumpShield(player);
+						break;
+					// Armageddon pow
+					case SH_ARMAGEDDON:
+						player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+						P_BlackOw(player);
+						break;
+					// Attraction blast
+					case SH_ATTRACT:
+						player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+						player->homing = 2;
+						P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockonshield));
+						if (lockonshield)
+							{
+								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonshield->x, lockonshield->y);
+									player->pflags &= ~PF_NOJUMPDAMAGE;
+									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+									S_StartSound(player->mo, sfx_s3k40);
+									player->homing = 3*TICRATE;
+							}
+							else
+								S_StartSound(player->mo, sfx_s3ka6);
+							break;
+						// Elemental stomp/Bubble bounce
+						case SH_ELEMENTAL:
+						case SH_BUBBLEWRAP:
+							{
+								boolean elem = ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL);
+								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+								if (elem)
+								{
+									player->mo->momx = player->mo->momy = 0;
+									S_StartSound(player->mo, sfx_s3k43);
+								}
+								else
+								{
+									player->mo->momx -= (player->mo->momx/3);
+									player->mo->momy -= (player->mo->momy/3);
+									player->pflags &= ~PF_NOJUMPDAMAGE;
+									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+									S_StartSound(player->mo, sfx_s3k44);
+								}
+								player->secondjump = 0;
+								P_SetObjectMomZ(player->mo, -24*FRACUNIT, false);
+								break;
+							}
+						// Flame burst
+						case SH_FLAMEAURA:
+							player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
+							P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale));
+							player->drawangle = player->mo->angle;
+							player->pflags &= ~PF_NOJUMPDAMAGE;
+							P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+							S_StartSound(player->mo, sfx_s3k43);
+						default:
+							break;
+				}
+			}
+		}
+		return player->pflags & PF_SHIELDABILITY;
+	}
+	return false;
+}
+
 //
 // P_DoJumpStuff
 //
@@ -5015,7 +5142,7 @@ static void P_DoTwinSpin(player_t *player)
 //
 static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 {
-	mobj_t *lockonthok = NULL, *lockonshield = NULL, *visual = NULL;
+	mobj_t *lockonthok = NULL, *visual = NULL;
 
 	if (player->pflags & PF_JUMPSTASIS)
 		return;
@@ -5042,105 +5169,11 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 			;
 		else if (player->pflags & (PF_GLIDING|PF_SLIDING|PF_SHIELDABILITY)) // If the player has used an ability previously
 			;
-		else if ((player->powers[pw_shield] & SH_NOSTACK) && !player->powers[pw_super] && !(player->pflags & PF_USEDOWN)
-			&& ((!(player->pflags & PF_THOKKED) || ((player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP && player->secondjump == UINT8_MAX)))) // thokked is optional if you're bubblewrapped
-		{
-			if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ATTRACT)
-			{
-				if ((lockonshield = P_LookForEnemies(player, false, false)))
-				{
-					if (P_IsLocalPlayer(player)) // Only display it on your own view.
-					{
-						boolean dovis = true;
-						if (lockonshield == lockonthok)
-						{
-							if (leveltime & 2)
-								dovis = false;
-							else if (visual)
-								P_RemoveMobj(visual);
-						}
-						if (dovis)
-						{
-							visual = P_SpawnMobj(lockonshield->x, lockonshield->y, lockonshield->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-							P_SetTarget(&visual->target, lockonshield);
-							P_SetMobjStateNF(visual, visual->info->spawnstate+1);
-						}
-					}
-				}
-			}
-			if (cmd->buttons & BT_USE && !LUAh_ShieldSpecial(player)) // Spin button effects
-			{
-				// Force stop
-				if ((player->powers[pw_shield] & ~(SH_FORCEHP|SH_STACK)) == SH_FORCE)
-				{
-					player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-					player->mo->momx = player->mo->momy = player->mo->momz = 0;
-					S_StartSound(player->mo, sfx_ngskid);
-				}
-				else
-				{
-					switch (player->powers[pw_shield] & SH_NOSTACK)
-					{
-						// Whirlwind jump/Thunder jump
-						case SH_WHIRLWIND:
-						case SH_THUNDERCOIN:
-							P_DoJumpShield(player);
-							break;
-						// Armageddon pow
-						case SH_ARMAGEDDON:
-							player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-							P_BlackOw(player);
-							break;
-						// Attraction blast
-						case SH_ATTRACT:
-							player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-							player->homing = 2;
-							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockonshield));
-							if (lockonshield)
-								{
-									player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonshield->x, lockonshield->y);
-										player->pflags &= ~PF_NOJUMPDAMAGE;
-										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-										S_StartSound(player->mo, sfx_s3k40);
-										player->homing = 3*TICRATE;
-								}
-								else
-									S_StartSound(player->mo, sfx_s3ka6);
-								break;
-							// Elemental stomp/Bubble bounce
-							case SH_ELEMENTAL:
-							case SH_BUBBLEWRAP:
-								{
-									boolean elem = ((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL);
-									player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-									if (elem)
-										S_StartSound(player->mo, sfx_s3k43);
-									else
-									{
-										player->pflags &= ~PF_NOJUMPDAMAGE;
-										P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-										S_StartSound(player->mo, sfx_s3k44);
-									}
-									player->secondjump = 0;
-									player->mo->momx = player->mo->momy = 0;
-									P_SetObjectMomZ(player->mo, -24*FRACUNIT, false);
-									break;
-								}
-							// Flame burst
-							case SH_FLAMEAURA:
-								player->pflags |= PF_THOKKED|PF_SHIELDABILITY;
-								P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale));
-								player->drawangle = player->mo->angle;
-								S_StartSound(player->mo, sfx_s3k43);
-							default:
-								break;
-					}
-				}
-			}
-		}
-		else if ((cmd->buttons & BT_USE))
+		else if (P_PlayerShieldThink(player, cmd, lockonthok, visual))
+			;
+		else if ((cmd->buttons & BT_SPIN))
 		{
-			if (!(player->pflags & PF_USEDOWN) && P_SuperReady(player))
+			if (!(player->pflags & PF_SPINDOWN) && P_SuperReady(player))
 			{
 				// If you can turn super and aren't already,
 				// and you don't have a shield, do it!
@@ -5170,7 +5203,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						}
 						break;
 					case CA_TELEKINESIS:
-						if (!(player->pflags & (PF_THOKKED|PF_USEDOWN)) || (player->charflags & SF_MULTIABILITY))
+						if (!(player->pflags & (PF_THOKKED|PF_SPINDOWN)) || (player->charflags & SF_MULTIABILITY))
 						{
 							P_Telekinesis(player,
 								-FixedMul(player->actionspd, player->mo->scale), // -ve thrust (pulling towards player)
@@ -5178,7 +5211,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						}
 						break;
 					case CA_TWINSPIN:
-						if ((player->charability2 == CA2_MELEE) && (!(player->pflags & (PF_THOKKED|PF_USEDOWN)) || player->charflags & SF_MULTIABILITY))
+						if ((player->charability2 == CA2_MELEE) && (!(player->pflags & (PF_THOKKED|PF_SPINDOWN)) || player->charflags & SF_MULTIABILITY))
 							P_DoTwinSpin(player);
 						break;
 					default:
@@ -5191,7 +5224,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	{
 		if (player->pflags & PF_JUMPED)
 		{
-			if (cmd->buttons & BT_USE && player->secondjump < 42) // speed up falling down
+			if (cmd->buttons & BT_SPIN && player->secondjump < 42) // speed up falling down
 				player->secondjump++;
 
 			if (player->flyangle > 0 && player->pflags & PF_THOKKED)
@@ -5470,7 +5503,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						break;
 				}
 		}
-		else if ((player->powers[pw_shield] & SH_NOSTACK) == SH_WHIRLWIND && !player->powers[pw_super])
+		else if ((!(player->charflags & SF_NOSHIELDABILITY)) && ((player->powers[pw_shield] & SH_NOSTACK) == SH_WHIRLWIND && !player->powers[pw_super] && !LUAh_ShieldSpecial(player)))
 			P_DoJumpShield(player);
 	}
 
@@ -5482,6 +5515,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		{
 			if (!P_HomingAttack(player->mo, player->mo->tracer))
 			{
+				player->pflags &= ~PF_SHIELDABILITY;
+				player->secondjump = UINT8_MAX;
 				P_SetObjectMomZ(player->mo, 6*FRACUNIT, false);
 				if (player->mo->eflags & MFE_UNDERWATER)
 					player->mo->momz = FixedMul(player->mo->momz, FRACUNIT/3);
@@ -5534,7 +5569,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 		if ((!(gametyperules & GTR_TEAMFLAGS) || !player->gotflag) && !player->exiting)
 		{
-			if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP)
+			if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP && player->charability != CA_THOK)
 			{
 				fixed_t potentialmomz;
 				if (player->charability == CA_SLOWFALL)
@@ -6186,7 +6221,7 @@ static void P_SpectatorMovement(player_t *player)
 
 	if (cmd->buttons & BT_JUMP)
 		player->mo->z += FRACUNIT*16;
-	else if (cmd->buttons & BT_USE)
+	else if (cmd->buttons & BT_SPIN)
 		player->mo->z -= FRACUNIT*16;
 
 	if (player->mo->z > player->mo->ceilingz - player->mo->height)
@@ -7408,7 +7443,7 @@ static void P_NiGHTSMovement(player_t *player)
 	// No more bumper braking
 	if (!player->bumpertime
 	 && ((cmd->buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)
-	  || (cmd->buttons & BT_USE)))
+	  || (cmd->buttons & BT_SPIN)))
 	{
 		if (!(player->pflags & PF_STARTDASH))
 			S_StartSound(player->mo, sfx_ngskid);
@@ -7729,6 +7764,11 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 			flame->eflags = (flame->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP);
 			P_InstaThrust(flame, flame->angle, FixedMul(3*FRACUNIT, flame->scale));
 			P_SetObjectMomZ(flame, 3*FRACUNIT, false);
+			if (!(gametyperules & GTR_FRIENDLY))
+			{
+				P_SetMobjState(flame, S_TEAM_SPINFIRE1);
+				flame->color = player->mo->color;
+			}
 		}
 #undef limitangle
 #undef numangles
@@ -7756,6 +7796,11 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 			flame->destscale = player->mo->scale;
 			P_SetScale(flame, player->mo->scale);
 			flame->eflags = (flame->eflags & ~MFE_VERTICALFLIP)|(player->mo->eflags & MFE_VERTICALFLIP);
+			if (!(gametyperules & GTR_FRIENDLY))
+			{
+				P_SetMobjState(flame, S_TEAM_SPINFIRE1);
+				flame->color = player->mo->color;
+			}
 
 			flame->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
 			P_XYMovement(flame);
@@ -7887,7 +7932,7 @@ static void P_SkidStuff(player_t *player)
 
 //
 // P_MovePlayer
-static void P_MovePlayer(player_t *player)
+void P_MovePlayer(player_t *player)
 {
 	ticcmd_t *cmd;
 	INT32 i;
@@ -7973,20 +8018,13 @@ static void P_MovePlayer(player_t *player)
 	// Locate the capsule for this mare.
 	else if (maptol & TOL_NIGHTS)
 	{
-		if ((player->powers[pw_carry] == CR_NIGHTSMODE)
-		&& (player->exiting
-		|| !(player->mo->state >= &states[S_PLAY_NIGHTS_TRANS1]
-			&& player->mo->state < &states[S_PLAY_NIGHTS_TRANS6]))) // Note the < instead of <=
+		if (P_PlayerFullbright(player))
 		{
-			skin_t *skin = ((skin_t *)(player->mo->skin));
-			if (( skin->flags & (SF_SUPER|SF_NONIGHTSSUPER) ) == SF_SUPER)
-			{
-				player->mo->color = skin->supercolor
-					+ ((player->nightstime == player->startedtime)
-						? 4
-						: abs((((signed)leveltime >> 1) % 9) - 4)); // This is where super flashing is handled.
-				G_GhostAddColor(GHC_SUPER);
-			}
+			player->mo->color = ((skin_t *)player->mo->skin)->supercolor
+				+ ((player->nightstime == player->startedtime)
+					? 4
+					: abs((((signed)leveltime >> 1) % 9) - 4)); // This is where super flashing is handled.
+			G_GhostAddColor(GHC_SUPER);
 		}
 
 		if (!player->capsule && !player->bonustime)
@@ -8462,7 +8500,7 @@ static void P_MovePlayer(player_t *player)
 				S_StartSound(player->mo, sfx_putput);
 
 			// Descend
-			if (cmd->buttons & BT_USE && !(player->pflags & PF_STASIS) && !player->exiting && !(player->mo->eflags & MFE_GOOWATER))
+			if (cmd->buttons & BT_SPIN && !(player->pflags & PF_STASIS) && !player->exiting && !(player->mo->eflags & MFE_GOOWATER))
 				if (P_MobjFlip(player->mo)*player->mo->momz > -FixedMul(5*actionspd, player->mo->scale))
 				{
 					if (player->fly1 > 2)
@@ -8589,12 +8627,6 @@ static void P_MovePlayer(player_t *player)
 		player->climbing--;
 	}
 
-	if (!player->climbing)
-	{
-		player->lastsidehit = -1;
-		player->lastlinehit = -1;
-	}
-
 	// Make sure you're not teetering when you shouldn't be.
 	if (player->panim == PA_EDGE
 	&& (player->mo->momx || player->mo->momy || player->mo->momz))
@@ -8619,6 +8651,7 @@ static void P_MovePlayer(player_t *player)
 		P_DoFiring(player, cmd);
 
 	{
+		boolean atspinheight = false;
 		fixed_t oldheight = player->mo->height;
 
 		// Less height while spinning. Good for spinning under things...?
@@ -8628,32 +8661,35 @@ static void P_MovePlayer(player_t *player)
 		|| player->powers[pw_tailsfly] || player->pflags & PF_GLIDING
 		|| (player->charability == CA_GLIDEANDCLIMB && player->mo->state-states == S_PLAY_GLIDE_LANDING)
 		|| (player->charability == CA_FLY && player->mo->state-states == S_PLAY_FLY_TIRED))
+		{
 			player->mo->height = P_GetPlayerSpinHeight(player);
+			atspinheight = true;
+		}
 		else
 			player->mo->height = P_GetPlayerHeight(player);
 
 		if (player->mo->eflags & MFE_VERTICALFLIP && player->mo->height != oldheight) // adjust z height for reverse gravity, similar to how it's done for scaling
 			player->mo->z -= player->mo->height - oldheight;
-	}
 
-	// Crush test...
-	if ((player->mo->ceilingz - player->mo->floorz < player->mo->height)
-		&& !(player->mo->flags & MF_NOCLIP))
-	{
-		if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_SPINNING))
+		// Crush test...
+		if ((player->mo->ceilingz - player->mo->floorz < player->mo->height)
+			&& !(player->mo->flags & MF_NOCLIP))
 		{
-			player->pflags |= PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
-		}
-		else if (player->mo->ceilingz - player->mo->floorz < player->mo->height)
-		{
-			if ((netgame || multiplayer) && player->spectator)
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); // Respawn crushed spectators
-			else
-				P_DamageMobj(player->mo, NULL, NULL, 1, DMG_CRUSHED);
+			if (!atspinheight)
+			{
+				player->pflags |= PF_SPINNING;
+				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+			}
+			else if (player->mo->ceilingz - player->mo->floorz < player->mo->height)
+			{
+				if ((netgame || multiplayer) && player->spectator)
+					P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); // Respawn crushed spectators
+				else
+					P_DamageMobj(player->mo, NULL, NULL, 1, DMG_CRUSHED);
 
-			if (player->playerstate == PST_DEAD)
-				return;
+				if (player->playerstate == PST_DEAD)
+					return;
+			}
 		}
 	}
 
@@ -8680,12 +8716,6 @@ static void P_MovePlayer(player_t *player)
 		player->fovadd = 0;
 #endif
 
-#ifdef FLOORSPLATS
-	if (cv_shadow.value && rendermode == render_soft)
-		R_AddFloorSplat(player->mo->subsector, player->mo, "SHADOW", player->mo->x,
-			player->mo->y, player->mo->floorz, SPLATDRAWMODE_OPAQUE);
-#endif
-
 	// Look for blocks to bust up
 	// Because of FF_SHATTER, we should look for blocks constantly,
 	// not just when spinning or playing as Knuckles
@@ -8826,9 +8856,9 @@ static void P_DoRopeHang(player_t *player)
 	player->mo->momy = FixedMul(FixedDiv(player->mo->tracer->y - player->mo->y, dist), (speed));
 	player->mo->momz = FixedMul(FixedDiv(player->mo->tracer->z - playerz, dist), (speed));
 
-	if (player->cmd.buttons & BT_USE && !(player->pflags & PF_STASIS)) // Drop off of the rope
+	if (player->cmd.buttons & BT_SPIN && !(player->pflags & PF_STASIS)) // Drop off of the rope
 	{
-		player->pflags |= (P_GetJumpFlags(player)|PF_USEDOWN);
+		player->pflags |= (P_GetJumpFlags(player)|PF_SPINDOWN);
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 
 		P_SetTarget(&player->mo->tracer, NULL);
@@ -9505,7 +9535,7 @@ static void P_DeathThink(player_t *player)
 	// continue logic
 	if (!(netgame || multiplayer) && player->lives <= 0)
 	{
-		if (player->deadtimer > (3*TICRATE) && (cmd->buttons & BT_USE || cmd->buttons & BT_JUMP) && (!continuesInSession || player->continues > 0))
+		if (player->deadtimer > (3*TICRATE) && (cmd->buttons & BT_SPIN || cmd->buttons & BT_JUMP) && (!continuesInSession || player->continues > 0))
 			G_UseContinue();
 		else if (player->deadtimer >= gameovertics)
 			G_UseContinue(); // Even if we don't have one this handles ending the game
@@ -9655,45 +9685,45 @@ static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {25, "MAX"}, {0, NULL
 static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t multiplier_cons_t[] = {{0, "MIN"}, {3*FRACUNIT, "MAX"}, {0, NULL}};
 
-consvar_t cv_cam_dist = {"cam_curdist", "160", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_height = {"cam_curheight", "25", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_still = {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_speed = {"cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_rotate = {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_turnmultiplier = {"cam_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_orbit = {"cam_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_adjust = {"cam_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_dist = {"cam2_curdist", "160", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_height = {"cam2_curheight", "25", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_still = {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_speed = {"cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_rotate = {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_rotspeed = {"cam2_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_turnmultiplier = {"cam2_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_orbit = {"cam2_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_adjust = {"cam2_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_dist = CVAR_INIT ("cam_curdist", "160", CV_FLOAT, NULL, NULL);
+consvar_t cv_cam_height = CVAR_INIT ("cam_curheight", "25", CV_FLOAT, NULL, NULL);
+consvar_t cv_cam_still = CVAR_INIT ("cam_still", "Off", 0, CV_OnOff, NULL);
+consvar_t cv_cam_speed = CVAR_INIT ("cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL);
+consvar_t cv_cam_rotate = CVAR_INIT ("cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange);
+consvar_t cv_cam_rotspeed = CVAR_INIT ("cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL);
+consvar_t cv_cam_turnmultiplier = CVAR_INIT ("cam_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL);
+consvar_t cv_cam_orbit = CVAR_INIT ("cam_orbit", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_cam_adjust = CVAR_INIT ("cam_adjust", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_cam2_dist = CVAR_INIT ("cam2_curdist", "160", CV_FLOAT, NULL, NULL);
+consvar_t cv_cam2_height = CVAR_INIT ("cam2_curheight", "25", CV_FLOAT, NULL, NULL);
+consvar_t cv_cam2_still = CVAR_INIT ("cam2_still", "Off", 0, CV_OnOff, NULL);
+consvar_t cv_cam2_speed = CVAR_INIT ("cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL);
+consvar_t cv_cam2_rotate = CVAR_INIT ("cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange);
+consvar_t cv_cam2_rotspeed = CVAR_INIT ("cam2_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL);
+consvar_t cv_cam2_turnmultiplier = CVAR_INIT ("cam2_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL);
+consvar_t cv_cam2_orbit = CVAR_INIT ("cam2_orbit", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_cam2_adjust = CVAR_INIT ("cam2_adjust", "On", CV_SAVE, CV_OnOff, NULL);
 
 // [standard vs simple][p1 or p2]
 consvar_t cv_cam_savedist[2][2] = {
 	{ // standard
-		{"cam_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
-		{"cam2_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+		CVAR_INIT ("cam_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 	},
 	{ // simple
-		{"cam_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
-		{"cam2_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+		CVAR_INIT ("cam_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 
 	}
 };
 consvar_t cv_cam_saveheight[2][2] = {
 	{ // standard
-		{"cam_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
-		{"cam2_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+		CVAR_INIT ("cam_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 	},
 	{ // simple
-		{"cam_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
-		{"cam2_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+		CVAR_INIT ("cam_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist),
+		CVAR_INIT ("cam2_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist),
 
 	}
 };
@@ -10063,7 +10093,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	if (camorbit) //Sev here, I'm guessing this is where orbital cam lives
 	{
 #ifdef HWRENDER
-		if (rendermode == render_opengl && !cv_grshearing.value)
+		if (rendermode == render_opengl && !cv_glshearing.value)
 			distxy = FixedMul(dist, FINECOSINE((focusaiming>>ANGLETOFINESHIFT) & FINEMASK));
 		else
 #endif
@@ -10586,6 +10616,7 @@ static void P_CalcPostImg(player_t *player)
 	postimg_t *type;
 	INT32 *param;
 	fixed_t pviewheight;
+	size_t i;
 
 	if (player->mo->eflags & MFE_VERTICALFLIP)
 		pviewheight = player->mo->z + player->mo->height - player->viewheight;
@@ -10610,28 +10641,45 @@ static void P_CalcPostImg(player_t *player)
 	}
 
 	// see if we are in heat (no, not THAT kind of heat...)
-
-	if (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1)
-		*type = postimg_heat;
-	else if (sector->ffloors)
+	for (i = 0; i < sector->tags.count; i++)
 	{
-		ffloor_t *rover;
-		fixed_t topheight;
-		fixed_t bottomheight;
-
-		for (rover = sector->ffloors; rover; rover = rover->next)
+		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
 		{
-			if (!(rover->flags & FF_EXISTS))
-				continue;
+			*type = postimg_heat;
+			break;
+		}
+		else if (sector->ffloors)
+		{
+			ffloor_t *rover;
+			fixed_t topheight;
+			fixed_t bottomheight;
+			boolean gotres = false;
 
-			topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
-			bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
+			for (rover = sector->ffloors; rover; rover = rover->next)
+			{
+				size_t j;
 
-			if (pviewheight >= topheight || pviewheight <= bottomheight)
-				continue;
+				if (!(rover->flags & FF_EXISTS))
+					continue;
+
+				topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
+				bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
+
+				if (pviewheight >= topheight || pviewheight <= bottomheight)
+					continue;
 
-			if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
-				*type = postimg_heat;
+				for (j = 0; j < rover->master->frontsector->tags.count; j++)
+				{
+					if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
+					{
+						*type = postimg_heat;
+						gotres = true;
+						break;
+					}
+				}
+			}
+			if (gotres)
+				break;
 		}
 	}
 
@@ -10730,22 +10778,21 @@ static sector_t *P_GetMinecartSector(fixed_t x, fixed_t y, fixed_t z, fixed_t *n
 static INT32 P_GetMinecartSpecialLine(sector_t *sec)
 {
 	INT32 line = -1;
+	size_t i;
 
 	if (!sec)
 		return line;
 
-	if (sec->tag != 0)
-		line = P_FindSpecialLineFromTag(16, sec->tag, -1);
+	for (i = 0; i < sec->tags.count; i++)
+		if (sec->tags.tags[i] != 0)
+			line = Tag_FindLineSpecial(16, sec->tags.tags[i]);
 
 	// Also try for lines facing the sector itself, with tag 0.
+	for (i = 0; i < sec->linecount; i++)
 	{
-		UINT32 i;
-		for (i = 0; i < sec->linecount; i++)
-		{
-			line_t *li = sec->lines[i];
-			if (li->tag == 0 && li->special == 16 && li->frontsector == sec)
-				line = li - lines;
-		}
+		line_t *li = sec->lines[i];
+		if (Tag_Find(&li->tags, 0) && li->special == 16 && li->frontsector == sec)
+			line = li - lines;
 	}
 
 	return line;
@@ -11034,7 +11081,7 @@ static void P_MinecartThink(player_t *player)
 			else if (detright && player->cmd.sidemove > 0)
 				sidelock = detright;
 
-			//if (player->cmd.buttons & BT_USE && currentSpeed > 4*FRACUNIT)
+			//if (player->cmd.buttons & BT_SPIN && currentSpeed > 4*FRACUNIT)
 			//	currentSpeed -= FRACUNIT/8;
 
 			// Jumping
@@ -11214,6 +11261,8 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 		chosenstate = S_TAILSOVERLAY_GASP;
 	else if (player->mo->state-states == S_PLAY_EDGE)
 		chosenstate = S_TAILSOVERLAY_EDGE;
+	else if (player->panim == PA_DASH)
+		chosenstate = S_TAILSOVERLAY_DASH;
 	else if (player->panim == PA_RUN)
 		chosenstate = S_TAILSOVERLAY_RUN;
 	else if (player->panim == PA_WALK)
@@ -11472,7 +11521,6 @@ void P_PlayerThink(player_t *player)
 		}
 	}
 
-#ifdef SEENAMES
 	if (netgame && player == &players[displayplayer] && !(leveltime % (TICRATE/5)))
 	{
 		seenplayer = NULL;
@@ -11497,7 +11545,6 @@ void P_PlayerThink(player_t *player)
 			}
 		}
 	}
-#endif
 
 	if (player->awayviewmobj && P_MobjWasRemoved(player->awayviewmobj))
 	{
@@ -11720,7 +11767,7 @@ void P_PlayerThink(player_t *player)
 
 	if ((gametyperules & GTR_RACE) && leveltime < 4*TICRATE)
 	{
-		cmd->buttons &= BT_USE; // Remove all buttons except BT_USE
+		cmd->buttons &= BT_SPIN; // Remove all buttons except BT_SPIN
 		cmd->forwardmove = 0;
 		cmd->sidemove = 0;
 	}
@@ -12085,10 +12132,10 @@ void P_PlayerThink(player_t *player)
 	// check for use
 	if (player->powers[pw_carry] != CR_NIGHTSMODE)
 	{
-		if (cmd->buttons & BT_USE)
-			player->pflags |= PF_USEDOWN;
+		if (cmd->buttons & BT_SPIN)
+			player->pflags |= PF_SPINDOWN;
 		else
-			player->pflags &= ~PF_USEDOWN;
+			player->pflags &= ~PF_SPINDOWN;
 	}
 
 	// IF PLAYER NOT HERE THEN FLASH END IF
@@ -12306,12 +12353,14 @@ void P_PlayerThink(player_t *player)
 		sector_t *controlsec;
 		for (j=0; j<numsectors; j++)
 		{
+			mtag_t sectag = Tag_FGet(&sectors[j].tags);
 			controlsec = NULL;
 			// Does this sector have a water linedef?
 			for (i=0; i<numlines;i++)
 			{
+				mtag_t linetag = Tag_FGet(&lines[i].tags);
 				if ((lines[i].special == 121 || lines[i].special == 123)
-				&& lines[i].tag == sectors[j].tag)
+				&& linetag == sectag)
 				{
 					controlsec = lines[i].frontsector;
 					break;
@@ -12320,15 +12369,16 @@ void P_PlayerThink(player_t *player)
 
 			if (i < numlines && controlsec)
 			{
+				controlsectag = Tag_FGet(&controlsec->tags);
 				// Does this sector have a colormap?
 				for (i=0; i<numlines;i++)
 				{
-					if (lines[i].special == 606 && lines[i].tag == controlsec->tag)
+					if (lines[i].special == 606 && linetag == controlsectag)
 						break;
 				}
 
 				if (i == numlines)
-					CONS_Debug(DBG_GAMELOGIC, "%d, %d\n", j, sectors[j].tag);
+					CONS_Debug(DBG_GAMELOGIC, "%d, %d\n", j, sectag);
 			}
 		}
 
@@ -12569,13 +12619,16 @@ void P_PlayerAfterThink(player_t *player)
 					player->powers[pw_carry] = CR_NONE;
 				else
 				{
-					P_TryMove(player->mo, tails->x + P_ReturnThrustX(tails, tails->player->drawangle, 4*FRACUNIT), tails->y + P_ReturnThrustY(tails, tails->player->drawangle, 4*FRACUNIT), true);
+					if (tails->player)
+						P_TryMove(player->mo, tails->x + P_ReturnThrustX(tails, tails->player->drawangle, 4*FRACUNIT), tails->y + P_ReturnThrustY(tails, tails->player->drawangle, 4*FRACUNIT), true);
+					else
+						P_TryMove(player->mo, tails->x + P_ReturnThrustX(tails, tails->angle, 4*FRACUNIT), tails->y + P_ReturnThrustY(tails, tails->angle, 4*FRACUNIT), true);
 					player->mo->momx = tails->momx;
 					player->mo->momy = tails->momy;
 					player->mo->momz = tails->momz;
 				}
 
-				if (G_CoopGametype() && (!tails->player || tails->player->bot != 1))
+				if (G_CoopGametype() && tails->player && tails->player->bot != 1)
 				{
 					player->mo->angle = tails->angle;
 
@@ -12590,7 +12643,7 @@ void P_PlayerAfterThink(player_t *player)
 				{
 					if (player->mo->state-states != S_PLAY_RIDE)
 						P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
-					if ((tails->skin && ((skin_t *)(tails->skin))->sprites[SPR2_SWIM].numframes) && (tails->eflags & MFE_UNDERWATER))
+					if (tails->player && (tails->skin && ((skin_t *)(tails->skin))->sprites[SPR2_SWIM].numframes) && (tails->eflags & MFE_UNDERWATER))
 						tails->player->powers[pw_tailsfly] = 0;
 				}
 				else
@@ -12916,3 +12969,12 @@ void P_ForceLocalAngle(player_t *player, angle_t angle)
 	else if (player == &players[secondarydisplayplayer])
 		localangle2 = angle;
 }
+
+boolean P_PlayerFullbright(player_t *player)
+{
+	return (player->powers[pw_super]
+		|| ((player->powers[pw_carry] == CR_NIGHTSMODE && (((skin_t *)player->mo->skin)->flags & (SF_SUPER|SF_NONIGHTSSUPER)) == SF_SUPER) // Super colours? Super bright!
+		&& (player->exiting
+			|| !(player->mo->state >= &states[S_PLAY_NIGHTS_TRANS1]
+			&& player->mo->state < &states[S_PLAY_NIGHTS_TRANS6])))); // Note the < instead of <=
+}
diff --git a/src/p_world.c b/src/p_world.c
index cd58d4124723b038f3b92dcad66cfd3db07d58ca..9aadd7f28af4db0bd9710ff7811ee3cc73100edb 100644
--- a/src/p_world.c
+++ b/src/p_world.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2020 by Sonic Team Junior.
-// Copyright (C) 2020 by Jaime Ita Passos.
+// Copyright (C) 2020-2021 by Jaime Ita Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -29,6 +29,7 @@
 #include "r_data.h"
 #include "r_draw.h"
 #include "r_sky.h"
+#include "r_patch.h"
 
 #include "s_sound.h"
 #include "w_wad.h"
@@ -37,6 +38,10 @@
 #include "lua_script.h"
 #include "lua_hook.h"
 
+#ifdef HWRENDER
+#include "hardware/hw_glob.h"
+#endif
+
 world_t *world = NULL;
 world_t *baseworld = NULL;
 world_t *localworld = NULL;
@@ -113,6 +118,10 @@ void P_SetGameWorld(world_t *w)
 	bmaporgy = world->bmaporgy;
 	blocklinks = world->blocklinks;
 	polyblocklinks = world->polyblocklinks;
+
+	tags_sectors = world->tags_sectors;
+	tags_lines = world->tags_lines;
+	tags_mapthings = world->tags_mapthings;
 }
 
 //
@@ -412,6 +421,14 @@ void P_UnloadWorldList(void)
 	// Clear pointers that would be left dangling by the purge
 	R_FlushTranslationColormapCache();
 
+#ifdef HWRENDER
+	// Free GPU textures before freeing patches.
+	if (vid.glstate == VID_GL_LIBRARY_LOADED)
+		HWR_ClearAllTextures();
+#endif
+
+	Patch_FreeTag(PU_PATCH_LOWPRIORITY);
+	Patch_FreeTag(PU_PATCH_ROTATED);
 	Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
 
 	world = localworld = viewworld = NULL;
diff --git a/src/p_world.h b/src/p_world.h
index a5a8f2787a6e5b7c84dab2fcd9394068991f8fba..ca9716ea128ea06567b92fe0f6d4a9c1c5e9767d 100644
--- a/src/p_world.h
+++ b/src/p_world.h
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 2020 by Sonic Team Junior.
-// Copyright (C) 2020 by Jaime Ita Passos.
+// Copyright (C) 2020-2021 by Jaime Ita Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -17,6 +17,7 @@
 #include "r_state.h"
 #include "p_polyobj.h"
 #include "p_slopes.h"
+#include "taglist.h"
 #include "doomstat.h"
 
 // Player spawn spots for deathmatch.
@@ -106,6 +107,18 @@ typedef struct
 	INT32 numPolyObjects;
 	polymaplink_t **polyblocklinks; // Polyobject Blockmap -- initialized in P_LoadBlockMap
 	polymaplink_t *po_bmap_freelist; // free list of blockmap links
+
+	// Bit array of whether a tag exists for sectors/lines/things.
+	bitarray_t tags_available[BIT_ARRAY_SIZE (MAXTAGS)];
+
+	size_t num_tags;
+
+	// Taggroups are used to list elements of the same tag, for iteration.
+	// Since elements can now have multiple tags, it means an element may appear
+	// in several taggroups at the same time. These are built on level load.
+	taggroup_t* tags_sectors[MAXTAGS + 1];
+	taggroup_t* tags_lines[MAXTAGS + 1];
+	taggroup_t* tags_mapthings[MAXTAGS + 1];
 } world_t;
 
 extern world_t *world;
diff --git a/src/r_bsp.c b/src/r_bsp.c
index a7986ec54a2922091e7e7c3233a7ad846d777578..55335b2a57fdbb855f35bcd242dddf79b938c521 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -21,6 +21,7 @@
 #include "p_local.h" // camera
 #include "p_slopes.h"
 #include "z_zone.h" // Check R_Prep3DFloors
+#include "taglist.h"
 
 seg_t *curline;
 side_t *sidedef;
@@ -374,7 +375,7 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 		// Consider colormaps
 		&& back->extra_colormap == front->extra_colormap
 		&& ((!front->ffloors && !back->ffloors)
-		|| front->tag == back->tag));
+		|| Tag_Compare(&front->tags, &back->tags)));
 }
 
 //
@@ -448,21 +449,25 @@ static void R_AddLine(seg_t *line)
 	// Portal line
 	if (line->linedef->special == 40 && line->side == 0)
 	{
+		// Render portal if recursiveness limit hasn't been reached.
+		// Otherwise, render the wall normally.
 		if (portalrender < cv_maxportals.value)
 		{
-			// Find the other side!
-			INT32 line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, -1);
-			if (line->linedef == &lines[line2])
-				line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, line2);
-			if (line2 >= 0) // found it!
+			size_t p;
+			mtag_t tag = Tag_FGet(&line->linedef->tags);
+			INT32 li1 = line->linedef-lines;
+			INT32 li2;
+
+			for (p = 0; (li2 = Tag_Iterate_Lines(tag, p)) >= 0; p++)
 			{
-				Portal_Add2Lines(line->linedef-lines, line2, x1, x2); // Remember the lines for later rendering
-				//return; // Don't fill in that space now!
+				// Skip invalid lines.
+				if ((tag != Tag_FGet(&lines[li2].tags)) || (lines[li1].special != lines[li2].special) || (li1 == li2))
+					continue;
+
+				Portal_Add2Lines(li1, li2, x1, x2);
 				goto clipsolid;
 			}
 		}
-		// Recursed TOO FAR (viewing a portal within a portal)
-		// So uhhh, render it as a normal wall instead or something ???
 	}
 
 	// Single sided line?
@@ -483,7 +488,7 @@ static void R_AddLine(seg_t *line)
 		if (!line->polyseg &&
 			!line->sidedef->midtexture
 			&& ((!frontsector->ffloors && !backsector->ffloors)
-				|| (frontsector->tag == backsector->tag)))
+				|| Tag_Compare(&frontsector->tags, &backsector->tags)))
 			return; // line is empty, don't even bother
 
 		goto clippass; // treat like wide open window instead
@@ -799,7 +804,7 @@ static void R_AddPolyObjects(subsector_t *sub)
 	}
 
 	// for render stats
-	rs_numpolyobjects += numpolys;
+	ps_numpolyobjects += numpolys;
 
 	// sort polyobjects
 	R_SortPolyObjects(sub);
@@ -1048,11 +1053,6 @@ static void R_Subsector(size_t num)
 		}
 	}
 
-#ifdef FLOORSPLATS
-	if (sub->splats)
-		R_AddVisibleFloorSplats(sub);
-#endif
-
    // killough 9/18/98: Fix underwater slowdown, by passing real sector
    // instead of fake one. Improve sprite lighting by basing sprite
    // lightlevels on floor & ceiling lightlevels in the surrounding area.
@@ -1239,7 +1239,7 @@ void R_RenderBSPNode(INT32 bspnum)
 	node_t *bsp;
 	INT32 side;
 
-	rs_numbspcalls++;
+	ps_numbspcalls++;
 
 	while (!(bspnum & NF_SUBSECTOR))  // Found a subsector?
 	{
diff --git a/src/r_data.c b/src/r_data.c
index b48888a1fe027f2a7ef0334692a295f1bc02659a..afa19bcc93ccc8937b3ab8a7549bf01a9b345290 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -19,7 +19,9 @@
 #include "p_local.h"
 #include "m_misc.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_patch.h"
+#include "r_picformats.h"
 #include "w_wad.h"
 #include "z_zone.h"
 #include "p_world.h" // levelflats
@@ -32,1489 +34,178 @@
 #include <malloc.h> // alloca(sizeof)
 #endif
 
-#ifdef HWRENDER
-#include "hardware/hw_main.h" // HWR_LoadTextures
-#endif
-
-#if defined(_MSC_VER)
-#pragma pack(1)
-#endif
-
-// Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
-#if 0
-#define AVOID_ERRNO
-#else
-#include <errno.h>
-#endif
-
-//
-// Texture definition.
-// Each texture is composed of one or more patches,
-// with patches being lumps stored in the WAD.
-// The lumps are referenced by number, and patched
-// into the rectangular texture space using origin
-// and possibly other attributes.
-//
-typedef struct
-{
-	INT16 originx, originy;
-	INT16 patch, stepdir, colormap;
-} ATTRPACK mappatch_t;
-
-//
-// Texture definition.
-// An SRB2 wall texture is a list of patches
-// which are to be combined in a predefined order.
-//
-typedef struct
-{
-	char name[8];
-	INT32 masked;
-	INT16 width;
-	INT16 height;
-	INT32 columndirectory; // FIXTHIS: OBSOLETE
-	INT16 patchcount;
-	mappatch_t patches[1];
-} ATTRPACK maptexture_t;
-
-#if defined(_MSC_VER)
-#pragma pack()
-#endif
-
-
-// Store lists of lumps for F_START/F_END etc.
-typedef struct
-{
-	UINT16 wadfile;
-	UINT16 firstlump;
-	size_t numlumps;
-} lumplist_t;
-
 //
 // Graphics.
 // SRB2 graphics for walls and sprites
 // is stored in vertical runs of opaque pixels (posts).
 // A column is composed of zero or more posts,
 // a patch or sprite is composed of zero or more columns.
-//
-
-size_t numspritelumps, max_spritelumps;
-
-// textures
-INT32 numtextures = 0; // total number of textures found,
-// size of following tables
-
-texture_t **textures = NULL;
-textureflat_t *texflats = NULL;
-static UINT32 **texturecolumnofs; // column offset lookup table for each texture
-static UINT8 **texturecache; // graphics data for each generated full-size texture
-
-INT32 *texturewidth;
-fixed_t *textureheight; // needed for texture pegging
-
-INT32 *texturetranslation;
-
-// needed for pre rendering
-sprcache_t *spritecachedinfo;
-
-lighttable_t *colormaps;
-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
-
-// Painfully simple texture id cacheing to make maps load faster. :3
-static struct {
-	char name[9];
-	INT32 id;
-} *tidcache = NULL;
-static INT32 tidcachelen = 0;
-
-//
-// MAPTEXTURE_T CACHING
-// When a texture is first needed, it counts the number of composite columns
-//  required in the texture and allocates space for a column directory and
-//  any new columns.
-// The directory will simply point inside other patches if there is only one
-//  patch in a given column, but any columns with multiple patches will have
-//  new column_ts generated.
-//
-
-//
-// R_DrawColumnInCache
-// Clip and draw a column from a patch into a cached post.
-//
-static inline void R_DrawColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	(void)patchheight; // This parameter is unused
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		source = (UINT8 *)patch + 3;
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source -= position; // start further down the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		if (count > 0)
-			M_Memcpy(cache + position, source, count);
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
-//
-// R_DrawFlippedColumnInCache
-// Similar to R_DrawColumnInCache; it draws the column inverted, however.
-//
-static inline void R_DrawFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source, *dest;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		topdelta = patchheight-patch->length-topdelta;
-		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source += position; // start further UP the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		dest = cache + position;
-		if (count > 0)
-		{
-			for (; dest < cache + position + count; --source)
-				*dest++ = *source;
-		}
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
-UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
-{
-	RGBA_t output;
-	INT16 fullalpha = (alpha - (0xFF - foreground.s.alpha));
-	if (style == AST_TRANSLUCENT)
-	{
-		if (fullalpha <= 0)
-			output.rgba = background.rgba;
-		else
-		{
-			// don't go too high
-			if (fullalpha >= 0xFF)
-				fullalpha = 0xFF;
-			alpha = (UINT8)fullalpha;
-
-			// if the background pixel is empty,
-			// match software and don't blend anything
-			if (!background.s.alpha)
-				output.s.alpha = 0;
-			else
-			{
-				UINT8 beta = (0xFF - alpha);
-				output.s.red = ((background.s.red * beta) + (foreground.s.red * alpha)) / 0xFF;
-				output.s.green = ((background.s.green * beta) + (foreground.s.green * alpha)) / 0xFF;
-				output.s.blue = ((background.s.blue * beta) + (foreground.s.blue * alpha)) / 0xFF;
-				output.s.alpha = 0xFF;
-			}
-		}
-		return output.rgba;
-	}
-#define clamp(c) max(min(c, 0xFF), 0x00);
-	else
-	{
-		float falpha = ((float)alpha / 256.0f);
-		float fr = ((float)foreground.s.red * falpha);
-		float fg = ((float)foreground.s.green * falpha);
-		float fb = ((float)foreground.s.blue * falpha);
-		if (style == AST_ADD)
-		{
-			output.s.red = clamp((int)(background.s.red + fr));
-			output.s.green = clamp((int)(background.s.green + fg));
-			output.s.blue = clamp((int)(background.s.blue + fb));
-		}
-		else if (style == AST_SUBTRACT)
-		{
-			output.s.red = clamp((int)(background.s.red - fr));
-			output.s.green = clamp((int)(background.s.green - fg));
-			output.s.blue = clamp((int)(background.s.blue - fb));
-		}
-		else if (style == AST_REVERSESUBTRACT)
-		{
-			output.s.red = clamp((int)((-background.s.red) + fr));
-			output.s.green = clamp((int)((-background.s.green) + fg));
-			output.s.blue = clamp((int)((-background.s.blue) + fb));
-		}
-		else if (style == AST_MODULATE)
-		{
-			fr = ((float)foreground.s.red / 256.0f);
-			fg = ((float)foreground.s.green / 256.0f);
-			fb = ((float)foreground.s.blue / 256.0f);
-			output.s.red = clamp((int)(background.s.red * fr));
-			output.s.green = clamp((int)(background.s.green * fg));
-			output.s.blue = clamp((int)(background.s.blue * fb));
-		}
-		// just copy the pixel
-		else if (style == AST_COPY)
-			output.rgba = foreground.rgba;
-
-		output.s.alpha = 0xFF;
-		return output.rgba;
-	}
-#undef clamp
-	return 0;
-}
-
-UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha)
-{
-	// Alpha style set to translucent?
-	if (style == AST_TRANSLUCENT)
-	{
-		// Is the alpha small enough for translucency?
-		if (alpha <= (10*255/11))
-		{
-			UINT8 *mytransmap;
-			// Is the patch way too translucent? Don't blend then.
-			if (alpha < 255/11)
-				return background;
-			// The equation's not exact but it works as intended. I'll call it a day for now.
-			mytransmap = transtables + ((8*(alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT);
-			if (background != 0xFF)
-				return *(mytransmap + (background<<8) + foreground);
-		}
-		else // just copy the pixel
-			return foreground;
-	}
-	// just copy the pixel
-	else if (style == AST_COPY)
-		return foreground;
-	// use ASTBlendPixel for all other blend modes
-	// and find the nearest colour in the palette
-	else if (style != AST_TRANSLUCENT)
-	{
-		RGBA_t texel;
-		RGBA_t bg = V_GetMasterColor(background);
-		RGBA_t fg = V_GetMasterColor(foreground);
-		texel.rgba = ASTBlendPixel(bg, fg, style, alpha);
-		return NearestColor(texel.s.red, texel.s.green, texel.s.blue);
-	}
-	// fallback if all above fails, somehow
-	// return the background pixel
-	return background;
-}
-
-//
-// R_DrawBlendColumnInCache
-// Draws a translucent column into the cache, applying a half-cooked equation to get a proper translucency value (Needs code in R_GenerateTexture()).
-//
-static inline void R_DrawBlendColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source, *dest;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	(void)patchheight; // This parameter is unused
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		source = (UINT8 *)patch + 3;
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source -= position; // start further down the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		dest = cache + position;
-		if (count > 0)
-		{
-			for (; dest < cache + position + count; source++, dest++)
-				if (*source != 0xFF)
-					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
-		}
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
-//
-// R_DrawBlendFlippedColumnInCache
-// Similar to the one above except that the column is inverted.
-//
-static inline void R_DrawBlendFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source, *dest;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		topdelta = patchheight-patch->length-topdelta;
-		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source += position; // start further UP the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		dest = cache + position;
-		if (count > 0)
-		{
-			for (; dest < cache + position + count; --source, dest++)
-				if (*source != 0xFF)
-					*dest = ASTBlendPixel_8bpp(*dest, *source, originPatch->style, originPatch->alpha);
-		}
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
-//
-// R_GenerateTexture
-//
-// Allocate space for full size texture, either single patch or 'composite'
-// Build the full textures from patches.
-// The texture caching system is a little more hungry of memory, but has
-// been simplified for the sake of highcolor (lol), dynamic ligthing, & speed.
-//
-// This is not optimised, but it's supposed to be executed only once
-// per level, when enough memory is available.
-//
-static UINT8 *R_GenerateTexture(size_t texnum)
-{
-	UINT8 *block;
-	UINT8 *blocktex;
-	texture_t *texture;
-	texpatch_t *patch;
-	patch_t *realpatch;
-	UINT8 *pdata;
-	int x, x1, x2, i, width, height;
-	size_t blocksize;
-	column_t *patchcol;
-	UINT8 *colofs;
-
-	UINT16 wadnum;
-	lumpnum_t lumpnum;
-	size_t lumplength;
-
-	I_Assert(texnum <= (size_t)numtextures);
-	texture = textures[texnum];
-	I_Assert(texture != NULL);
-
-	// allocate texture column offset lookup
-
-	// single-patch textures can have holes in them and may be used on
-	// 2sided lines so they need to be kept in 'packed' format
-	// BUT this is wrong for skies and walls with over 255 pixels,
-	// so check if there's holes and if not strip the posts.
-	if (texture->patchcount == 1)
-	{
-		boolean holey = false;
-		patch = texture->patches;
-
-		wadnum = patch->wad;
-		lumpnum = patch->lump;
-		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-		realpatch = (patch_t *)pdata;
-
-#ifndef NO_PNG_LUMPS
-		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			goto multipatch;
-#endif
-#ifdef WALLFLATS
-		if (texture->type == TEXTURETYPE_FLAT)
-			goto multipatch;
-#endif
-
-		// Check the patch for holes.
-		if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
-			holey = true;
-		colofs = (UINT8 *)realpatch->columnofs;
-		for (x = 0; x < texture->width && !holey; x++)
-		{
-			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(*(UINT32 *)&colofs[x<<2]));
-			INT32 topdelta, prevdelta = -1, y = 0;
-			while (col->topdelta != 0xff)
-			{
-				topdelta = col->topdelta;
-				if (topdelta <= prevdelta)
-					topdelta += prevdelta;
-				prevdelta = topdelta;
-				if (topdelta > y)
-					break;
-				y = topdelta + col->length + 1;
-				col = (column_t *)((UINT8 *)col + col->length + 4);
-			}
-			if (y < texture->height)
-				holey = true; // this texture is HOLEy! D:
-		}
-
-		// If the patch uses transparency, we have to save it this way.
-		if (holey)
-		{
-			texture->holes = true;
-			texture->flip = patch->flip;
-			blocksize = lumplength;
-			block = Z_Calloc(blocksize, PU_STATIC, // will change tag at end of this function
-				&texturecache[texnum]);
-			M_Memcpy(block, realpatch, blocksize);
-			texturememory += blocksize;
-
-			// use the patch's column lookup
-			colofs = (block + 8);
-			texturecolumnofs[texnum] = (UINT32 *)colofs;
-			blocktex = block;
-			if (patch->flip & 1) // flip the patch horizontally
-			{
-				UINT8 *realcolofs = (UINT8 *)realpatch->columnofs;
-				for (x = 0; x < texture->width; x++)
-					*(UINT32 *)&colofs[x<<2] = realcolofs[( texture->width-1-x )<<2]; // swap with the offset of the other side of the texture
-			}
-			// we can't as easily flip the patch vertically sadly though,
-			//  we have wait until the texture itself is drawn to do that
-			for (x = 0; x < texture->width; x++)
-				*(UINT32 *)&colofs[x<<2] = LONG(LONG(*(UINT32 *)&colofs[x<<2]) + 3);
-			goto done;
-		}
-
-		// Otherwise, do multipatch format.
-	}
-
-	// multi-patch textures (or 'composite')
-	multipatch:
-	texture->holes = false;
-	texture->flip = 0;
-	blocksize = (texture->width * 4) + (texture->width * texture->height);
-	texturememory += blocksize;
-	block = Z_Malloc(blocksize+1, PU_STATIC, &texturecache[texnum]);
-
-	memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack
-
-	// columns lookup table
-	colofs = block;
-	texturecolumnofs[texnum] = (UINT32 *)colofs;
-
-	// texture data after the lookup table
-	blocktex = block + (texture->width*4);
-
-	// Composite the columns together.
-	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
-	{
-		boolean dealloc = true;
-		static void (*ColumnDrawerPointer)(column_t *, UINT8 *, texpatch_t *, INT32, INT32); // Column drawing function pointer.
-		if (patch->style != AST_COPY)
-			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawBlendFlippedColumnInCache : R_DrawBlendColumnInCache;
-		else
-			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawFlippedColumnInCache : R_DrawColumnInCache;
-
-		wadnum = patch->wad;
-		lumpnum = patch->lump;
-		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-		realpatch = (patch_t *)pdata;
-		dealloc = true;
-
-#ifndef NO_PNG_LUMPS
-		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL);
-		else
-#endif
-#ifdef WALLFLATS
-		if (texture->type == TEXTURETYPE_FLAT)
-			realpatch = R_FlatToPatch(pdata, texture->width, texture->height, 0, 0, NULL, false);
-		else
-#endif
-		{
-			(void)lumplength;
-			dealloc = false;
-		}
-
-		x1 = patch->originx;
-		width = SHORT(realpatch->width);
-		height = SHORT(realpatch->height);
-		x2 = x1 + width;
-
-		if (x1 > texture->width || x2 < 0)
-			continue; // patch not located within texture's x bounds, ignore
-
-		if (patch->originy > texture->height || (patch->originy + height) < 0)
-			continue; // patch not located within texture's y bounds, ignore
-
-		// patch is actually inside the texture!
-		// now check if texture is partly off-screen and adjust accordingly
-
-		// left edge
-		if (x1 < 0)
-			x = 0;
-		else
-			x = x1;
-
-		// right edge
-		if (x2 > texture->width)
-			x2 = texture->width;
-
-		for (; x < x2; x++)
-		{
-			if (patch->flip & 1)
-				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[(x1+width-1)-x]));
-			else
-				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[x-x1]));
-
-			// generate column ofset lookup
-			*(UINT32 *)&colofs[x<<2] = LONG((x * texture->height) + (texture->width*4));
-			ColumnDrawerPointer(patchcol, block + LONG(*(UINT32 *)&colofs[x<<2]), patch, texture->height, height);
-		}
-
-		if (dealloc)
-			Z_Free(realpatch);
-	}
-
-done:
-	// Now that the texture has been built in column cache, it is purgable from zone memory.
-	Z_ChangeTag(block, PU_CACHE);
-	return blocktex;
-}
-
-//
-// R_GetTextureNum
-//
-// Returns the actual texture id that we should use.
-// This can either be texnum, the current frame for texnum's anim (if animated),
-// or 0 if not valid.
-//
-INT32 R_GetTextureNum(INT32 texnum)
-{
-	if (texnum < 0 || texnum >= numtextures)
-		return 0;
-	return texturetranslation[texnum];
-}
-
-//
-// R_CheckTextureCache
-//
-// Use this if you need to make sure the texture is cached before R_GetColumn calls
-// e.g.: midtextures and FOF walls
-//
-void R_CheckTextureCache(INT32 tex)
-{
-	if (!texturecache[tex])
-		R_GenerateTexture(tex);
-}
-
-//
-// R_GetColumn
-//
-UINT8 *R_GetColumn(fixed_t tex, INT32 col)
-{
-	UINT8 *data;
-	INT32 width = texturewidth[tex];
-
-	if (width & (width - 1))
-		col = (UINT32)col % width;
-	else
-		col &= (width - 1);
-
-	data = texturecache[tex];
-	if (!data)
-		data = R_GenerateTexture(tex);
-
-	return data + LONG(texturecolumnofs[tex][col]);
-}
-
-// convert flats to hicolor as they are requested
-//
-UINT8 *R_GetFlat(lumpnum_t flatlumpnum)
-{
-	return W_CacheLumpNum(flatlumpnum, PU_CACHE);
-}
-
-//
-// Empty the texture cache (used for load wad at runtime)
-//
-void R_FlushTextureCache(void)
-{
-	INT32 i;
-
-	if (numtextures)
-		for (i = 0; i < numtextures; i++)
-			Z_Free(texturecache[i]);
-}
-
-// Need these prototypes for later; defining them here instead of r_data.h so they're "private"
-int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum);
-void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *index);
-
-#ifdef WALLFLATS
-static INT32
-Rloadflats (INT32 i, INT32 w)
-{
-	UINT16 j;
-	UINT16 texstart, texend;
-	texture_t *texture;
-	texpatch_t *patch;
-
-	// Yes
-	if (wadfiles[w]->type == RET_PK3)
-	{
-		texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
-		texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
-	}
-	else
-	{
-		texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
-		texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
-	}
-
-	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
-	{
-		// Work through each lump between the markers in the WAD.
-		for (j = 0; j < (texend - texstart); j++)
-		{
-			UINT8 *flatlump;
-			UINT16 wadnum = (UINT16)w;
-			lumpnum_t lumpnum = texstart + j;
-			size_t lumplength;
-			size_t flatsize = 0;
-
-			if (wadfiles[w]->type == RET_PK3)
-			{
-				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
-					continue; // If it is then SKIP IT
-			}
-
-			flatlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-
-			switch (lumplength)
-			{
-				case 4194304: // 2048x2048 lump
-					flatsize = 2048;
-					break;
-				case 1048576: // 1024x1024 lump
-					flatsize = 1024;
-					break;
-				case 262144:// 512x512 lump
-					flatsize = 512;
-					break;
-				case 65536: // 256x256 lump
-					flatsize = 256;
-					break;
-				case 16384: // 128x128 lump
-					flatsize = 128;
-					break;
-				case 1024: // 32x32 lump
-					flatsize = 32;
-					break;
-				default: // 64x64 lump
-					flatsize = 64;
-					break;
-			}
-
-			//CONS_Printf("\n\"%s\" is a flat, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),flatsize,flatsize);
-			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
-
-			// Set texture properties.
-			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
-
-#ifndef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)flatlump, lumplength))
-			{
-				INT16 width, height;
-				R_PNGDimensions((UINT8 *)flatlump, &width, &height, lumplength);
-				texture->width = width;
-				texture->height = height;
-			}
-			else
-#endif
-				texture->width = texture->height = flatsize;
-
-			texture->type = TEXTURETYPE_FLAT;
-			texture->patchcount = 1;
-			texture->holes = false;
-			texture->flip = 0;
-
-			// Allocate information for the texture's patches.
-			patch = &texture->patches[0];
-
-			patch->originx = patch->originy = 0;
-			patch->wad = (UINT16)w;
-			patch->lump = texstart + j;
-			patch->flip = 0;
-
-			Z_Unlock(flatlump);
-
-			texturewidth[i] = texture->width;
-			textureheight[i] = texture->height << FRACBITS;
-			i++;
-		}
-	}
-
-	return i;
-}
-#endif/*WALLFLATS*/
-
-#define TX_START "TX_START"
-#define TX_END "TX_END"
-
-static INT32
-Rloadtextures (INT32 i, INT32 w)
-{
-	UINT16 j;
-	UINT16 texstart, texend, texturesLumpPos;
-	texture_t *texture;
-	patch_t *patchlump;
-	texpatch_t *patch;
-
-	// Get the lump numbers for the markers in the WAD, if they exist.
-	if (wadfiles[w]->type == RET_PK3)
-	{
-		texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
-		texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		while (texturesLumpPos != INT16_MAX)
-		{
-			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
-			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
-		}
-	}
-	else
-	{
-		texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
-		texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		if (texturesLumpPos != INT16_MAX)
-			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
-	}
-
-	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
-	{
-		// Work through each lump between the markers in the WAD.
-		for (j = 0; j < (texend - texstart); j++)
-		{
-			UINT16 wadnum = (UINT16)w;
-			lumpnum_t lumpnum = texstart + j;
-#ifndef NO_PNG_LUMPS
-			size_t lumplength;
-#endif
-
-			if (wadfiles[w]->type == RET_PK3)
-			{
-				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
-					continue; // If it is then SKIP IT
-			}
-
-			patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-#ifndef NO_PNG_LUMPS
-			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-#endif
-
-			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
-			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
-
-			// Set texture properties.
-			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
-
-#ifndef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)patchlump, lumplength))
-			{
-				INT16 width, height;
-				R_PNGDimensions((UINT8 *)patchlump, &width, &height, lumplength);
-				texture->width = width;
-				texture->height = height;
-			}
-			else
-#endif
-			{
-				texture->width = SHORT(patchlump->width);
-				texture->height = SHORT(patchlump->height);
-			}
-
-			texture->type = TEXTURETYPE_SINGLEPATCH;
-			texture->patchcount = 1;
-			texture->holes = false;
-			texture->flip = 0;
-
-			// Allocate information for the texture's patches.
-			patch = &texture->patches[0];
-
-			patch->originx = patch->originy = 0;
-			patch->wad = (UINT16)w;
-			patch->lump = texstart + j;
-			patch->flip = 0;
-
-			Z_Unlock(patchlump);
-
-			texturewidth[i] = texture->width;
-			textureheight[i] = texture->height << FRACBITS;
-			i++;
-		}
-	}
-
-	return i;
-}
-
-//
-// R_LoadTextures
-// Initializes the texture list with the textures from the world map.
-//
-void R_LoadTextures(void)
-{
-	INT32 i, w;
-	UINT16 j;
-	UINT16 texstart, texend, texturesLumpPos;
-
-	// Free previous memory before numtextures change.
-	if (numtextures)
-	{
-		for (i = 0; i < numtextures; i++)
-		{
-			Z_Free(textures[i]);
-			Z_Free(texturecache[i]);
-		}
-		Z_Free(texturetranslation);
-		Z_Free(textures);
-		Z_Free(texflats);
-	}
-
-	// Load patches and textures.
-
-	// Get the number of textures to check.
-	// NOTE: Make SURE the system does not process
-	// the markers.
-	// This system will allocate memory for all duplicate/patched textures even if it never uses them,
-	// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
-	for (w = 0, numtextures = 0; w < numwadfiles; w++)
-	{
-#ifdef WALLFLATS
-		// Count flats
-		if (wadfiles[w]->type == RET_PK3)
-		{
-			texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
-			texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
-		}
-		else
-		{
-			texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
-			texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
-		}
-
-		if (!( texstart == INT16_MAX || texend == INT16_MAX ))
-		{
-			// PK3s have subfolders, so we can't just make a simple sum
-			if (wadfiles[w]->type == RET_PK3)
-			{
-				for (j = texstart; j < texend; j++)
-				{
-					if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
-						numtextures++;
-				}
-			}
-			else // Add all the textures between F_START and F_END
-			{
-				numtextures += (UINT32)(texend - texstart);
-			}
-		}
-#endif/*WALLFLATS*/
-
-		// Count the textures from TEXTURES lumps
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		while (texturesLumpPos != INT16_MAX)
-		{
-			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
-			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
-		}
-
-		// Count single-patch textures
-		if (wadfiles[w]->type == RET_PK3)
-		{
-			texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
-			texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
-		}
-		else
-		{
-			texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
-			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
-		}
-
-		if (texstart == INT16_MAX || texend == INT16_MAX)
-			continue;
-
-		// PK3s have subfolders, so we can't just make a simple sum
-		if (wadfiles[w]->type == RET_PK3)
-		{
-			for (j = texstart; j < texend; j++)
-			{
-				if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
-					numtextures++;
-			}
-		}
-		else // Add all the textures between TX_START and TX_END
-		{
-			numtextures += (UINT32)(texend - texstart);
-		}
-	}
-
-	// If no textures found by this point, bomb out
-	if (!numtextures)
-		I_Error("No textures detected in any WADs!\n");
-
-	// Allocate memory and initialize to 0 for all the textures we are initialising.
-	// There are actually 5 buffers allocated in one for convenience.
-	textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
-	texflats = Z_Calloc((numtextures * sizeof(*texflats)), PU_STATIC, NULL);
-
-	// Allocate texture column offset table.
-	texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
-	// Allocate texture referencing cache.
-	texturecache     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
-	// Allocate texture width table.
-	texturewidth     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
-	// Allocate texture height table.
-	textureheight    = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
-	// Create translation table for global animation.
-	texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
-
-	for (i = 0; i < numtextures; i++)
-		texturetranslation[i] = i;
-
-	for (i = 0, w = 0; w < numwadfiles; w++)
-	{
-#ifdef WALLFLATS
-		i = Rloadflats(i, w);
-#endif
-		i = Rloadtextures(i, w);
-	}
-
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_LoadTextures(numtextures);
-#endif
-}
-
-static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
-{
-	char *texturesToken;
-	size_t texturesTokenLength;
-	char *endPos;
-	char *patchName = NULL;
-	INT16 patchXPos;
-	INT16 patchYPos;
-	UINT8 flip = 0;
-	UINT8 alpha = 255;
-	enum patchalphastyle style = AST_COPY;
-	texpatch_t *resultPatch = NULL;
-	lumpnum_t patchLumpNum;
-
-	// Patch identifier
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch name should be");
-	}
-	texturesTokenLength = strlen(texturesToken);
-	if (texturesTokenLength>8)
-	{
-		I_Error("Error parsing TEXTURES lump: Patch name \"%s\" exceeds 8 characters",texturesToken);
-	}
-	else
-	{
-		if (patchName != NULL)
-		{
-			Z_Free(patchName);
-		}
-		patchName = (char *)Z_Malloc((texturesTokenLength+1)*sizeof(char),PU_STATIC,NULL);
-		M_Memcpy(patchName,texturesToken,texturesTokenLength*sizeof(char));
-		patchName[texturesTokenLength] = '\0';
-	}
+//
 
-	// Comma 1
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after \"%s\"'s patch name should be",patchName);
-	}
-	if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after %s's patch name, got \"%s\"",patchName,texturesToken);
-	}
+size_t numspritelumps, max_spritelumps;
 
-	// XPos
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s x coordinate should be",patchName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	patchXPos = strtol(texturesToken,&endPos,10);
-	(void)patchXPos; //unused for now
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
-	}
+// needed for pre rendering
+sprcache_t *spritecachedinfo;
 
-	// Comma 2
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after patch \"%s\"'s x coordinate should be",patchName);
-	}
-	if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
-	}
+lighttable_t *colormaps;
+lighttable_t *fadecolormap;
 
-	// YPos
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s y coordinate should be",patchName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	patchYPos = strtol(texturesToken,&endPos,10);
-	(void)patchYPos; //unused for now
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s y coordinate, got \"%s\"",patchName,texturesToken);
-	}
-	Z_Free(texturesToken);
+// for debugging/info purposes
+size_t flatmemory, spritememory, texturememory;
 
-	// Patch parameters block (OPTIONAL)
-	// added by Monster Iestyn (22/10/16)
+// highcolor stuff
+INT16 color8to16[256]; // remap color index to highcolor rgb value
+INT16 *hicolormaps; // test a 32k colormap remaps high -> high
 
-	// Left Curly Brace
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-		; // move on and ignore, R_ParseTextures will deal with this
-	else
+// 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)
+{
+	RGBA_t output;
+	INT16 fullalpha = (alpha - (0xFF - foreground.s.alpha));
+	if (style == AST_TRANSLUCENT)
 	{
-		if (strcmp(texturesToken,"{")==0)
+		if (fullalpha <= 0)
+			output.rgba = background.rgba;
+		else
 		{
-			Z_Free(texturesToken);
-			texturesToken = M_GetToken(NULL);
-			if (texturesToken == NULL)
+			// don't go too high
+			if (fullalpha >= 0xFF)
+				fullalpha = 0xFF;
+			alpha = (UINT8)fullalpha;
+
+			// if the background pixel is empty,
+			// match software and don't blend anything
+			if (!background.s.alpha)
 			{
-				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters should be",patchName);
+				// ...unless the foreground pixel ISN'T actually translucent.
+				if (alpha == 0xFF)
+					output.rgba = foreground.rgba;
+				else
+					output.rgba = 0;
 			}
-			while (strcmp(texturesToken,"}")!=0)
+			else
 			{
-				if (stricmp(texturesToken, "ALPHA")==0)
-				{
-					Z_Free(texturesToken);
-					texturesToken = M_GetToken(NULL);
-					alpha = 255*strtof(texturesToken, NULL);
-				}
-				else if (stricmp(texturesToken, "STYLE")==0)
-				{
-					Z_Free(texturesToken);
-					texturesToken = M_GetToken(NULL);
-					if (stricmp(texturesToken, "TRANSLUCENT")==0)
-						style = AST_TRANSLUCENT;
-					else if (stricmp(texturesToken, "ADD")==0)
-						style = AST_ADD;
-					else if (stricmp(texturesToken, "SUBTRACT")==0)
-						style = AST_SUBTRACT;
-					else if (stricmp(texturesToken, "REVERSESUBTRACT")==0)
-						style = AST_REVERSESUBTRACT;
-					else if (stricmp(texturesToken, "MODULATE")==0)
-						style = AST_MODULATE;
-				}
-				else if (stricmp(texturesToken, "FLIPX")==0)
-					flip |= 1;
-				else if (stricmp(texturesToken, "FLIPY")==0)
-					flip |= 2;
-				Z_Free(texturesToken);
-
-				texturesToken = M_GetToken(NULL);
-				if (texturesToken == NULL)
-				{
-					I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters or right curly brace should be",patchName);
-				}
+				UINT8 beta = (0xFF - alpha);
+				output.s.red = ((background.s.red * beta) + (foreground.s.red * alpha)) / 0xFF;
+				output.s.green = ((background.s.green * beta) + (foreground.s.green * alpha)) / 0xFF;
+				output.s.blue = ((background.s.blue * beta) + (foreground.s.blue * alpha)) / 0xFF;
+				output.s.alpha = 0xFF;
 			}
 		}
-		else
-		{
-			 // this is not what we wanted...
-			 // undo last read so R_ParseTextures can re-get the token for its own purposes
-			M_UnGetToken();
-		}
-		Z_Free(texturesToken);
-	}
-
-	if (actuallyLoadPatch == true)
-	{
-		// Check lump exists
-		patchLumpNum = W_GetNumForName(patchName);
-		// If so, allocate memory for texpatch_t and fill 'er up
-		resultPatch = (texpatch_t *)Z_Malloc(sizeof(texpatch_t),PU_STATIC,NULL);
-		resultPatch->originx = patchXPos;
-		resultPatch->originy = patchYPos;
-		resultPatch->lump = patchLumpNum & 65535;
-		resultPatch->wad = patchLumpNum>>16;
-		resultPatch->flip = flip;
-		resultPatch->alpha = alpha;
-		resultPatch->style = style;
-		// Clean up a little after ourselves
-		Z_Free(patchName);
-		// Then return it
-		return resultPatch;
-	}
-	else
-	{
-		Z_Free(patchName);
-		return NULL;
-	}
-}
-
-static texture_t *R_ParseTexture(boolean actuallyLoadTexture)
-{
-	char *texturesToken;
-	size_t texturesTokenLength;
-	char *endPos;
-	INT32 newTextureWidth;
-	INT32 newTextureHeight;
-	texture_t *resultTexture = NULL;
-	texpatch_t *newPatch;
-	char newTextureName[9]; // no longer dynamically allocated
-
-	// Texture name
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture name should be");
-	}
-	texturesTokenLength = strlen(texturesToken);
-	if (texturesTokenLength>8)
-	{
-		I_Error("Error parsing TEXTURES lump: Texture name \"%s\" exceeds 8 characters",texturesToken);
+		return output.rgba;
 	}
+#define clamp(c) max(min(c, 0xFF), 0x00);
 	else
 	{
-		memset(&newTextureName, 0, 9);
-		M_Memcpy(newTextureName, texturesToken, texturesTokenLength);
-		// ^^ we've confirmed that the token is <= 8 characters so it will never overflow a 9 byte char buffer
-		strupr(newTextureName); // Just do this now so we don't have to worry about it
-	}
-	Z_Free(texturesToken);
-
-	// Comma 1
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s name should be",newTextureName);
-	}
-	else if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s name, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Width
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s width should be",newTextureName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	newTextureWidth = strtol(texturesToken,&endPos,10);
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		|| newTextureWidth < 0) // Number is not positive
-	{
-		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Comma 2
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s width should be",newTextureName);
-	}
-	if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Height
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s height should be",newTextureName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	newTextureHeight = strtol(texturesToken,&endPos,10);
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		|| newTextureHeight < 0) // Number is not positive
-	{
-		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s height, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Left Curly Brace
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where open curly brace for texture \"%s\" should be",newTextureName);
-	}
-	if (strcmp(texturesToken,"{")==0)
-	{
-		if (actuallyLoadTexture)
+		float falpha = ((float)alpha / 256.0f);
+		float fr = ((float)foreground.s.red * falpha);
+		float fg = ((float)foreground.s.green * falpha);
+		float fb = ((float)foreground.s.blue * falpha);
+		if (style == AST_ADD)
 		{
-			// Allocate memory for a zero-patch texture. Obviously, we'll be adding patches momentarily.
-			resultTexture = (texture_t *)Z_Calloc(sizeof(texture_t),PU_STATIC,NULL);
-			M_Memcpy(resultTexture->name, newTextureName, 8);
-			resultTexture->width = newTextureWidth;
-			resultTexture->height = newTextureHeight;
-			resultTexture->type = TEXTURETYPE_COMPOSITE;
+			output.s.red = clamp((int)(background.s.red + fr));
+			output.s.green = clamp((int)(background.s.green + fg));
+			output.s.blue = clamp((int)(background.s.blue + fb));
 		}
-		Z_Free(texturesToken);
-		texturesToken = M_GetToken(NULL);
-		if (texturesToken == NULL)
+		else if (style == AST_SUBTRACT)
 		{
-			I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch definition for texture \"%s\" should be",newTextureName);
+			output.s.red = clamp((int)(background.s.red - fr));
+			output.s.green = clamp((int)(background.s.green - fg));
+			output.s.blue = clamp((int)(background.s.blue - fb));
 		}
-		while (strcmp(texturesToken,"}")!=0)
+		else if (style == AST_REVERSESUBTRACT)
 		{
-			if (stricmp(texturesToken, "PATCH")==0)
-			{
-				Z_Free(texturesToken);
-				if (resultTexture)
-				{
-					// Get that new patch
-					newPatch = R_ParsePatch(true);
-					// Make room for the new patch
-					resultTexture = Z_Realloc(resultTexture, sizeof(texture_t) + (resultTexture->patchcount+1)*sizeof(texpatch_t), PU_STATIC, NULL);
-					// Populate the uninitialized values in the new patch entry of our array
-					M_Memcpy(&resultTexture->patches[resultTexture->patchcount], newPatch, sizeof(texpatch_t));
-					// Account for the new number of patches in the texture
-					resultTexture->patchcount++;
-					// Then free up the memory assigned to R_ParsePatch, as it's unneeded now
-					Z_Free(newPatch);
-				}
-				else
-				{
-					R_ParsePatch(false);
-				}
-			}
-			else
-			{
-				I_Error("Error parsing TEXTURES lump: Expected \"PATCH\" in texture \"%s\", got \"%s\"",newTextureName,texturesToken);
-			}
-
-			texturesToken = M_GetToken(NULL);
-			if (texturesToken == NULL)
-			{
-				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch declaration or right curly brace for texture \"%s\" should be",newTextureName);
-			}
+			output.s.red = clamp((int)((-background.s.red) + fr));
+			output.s.green = clamp((int)((-background.s.green) + fg));
+			output.s.blue = clamp((int)((-background.s.blue) + fb));
 		}
-		if (resultTexture && resultTexture->patchcount == 0)
+		else if (style == AST_MODULATE)
 		{
-			I_Error("Error parsing TEXTURES lump: Texture \"%s\" must have at least one patch",newTextureName);
+			fr = ((float)foreground.s.red / 256.0f);
+			fg = ((float)foreground.s.green / 256.0f);
+			fb = ((float)foreground.s.blue / 256.0f);
+			output.s.red = clamp((int)(background.s.red * fr));
+			output.s.green = clamp((int)(background.s.green * fg));
+			output.s.blue = clamp((int)(background.s.blue * fb));
 		}
-	}
-	else
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \"{\" for texture \"%s\", got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
+		// just copy the pixel
+		else if (style == AST_COPY)
+			output.rgba = foreground.rgba;
 
-	if (actuallyLoadTexture) return resultTexture;
-	else return NULL;
+		output.s.alpha = 0xFF;
+		return output.rgba;
+	}
+#undef clamp
+	return 0;
 }
 
-// Parses the TEXTURES lump... but just to count the number of textures.
-int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum)
+INT32 ASTTextureBlendingThreshold[2] = {255/11, (10*255/11)};
+
+// Blends a pixel for a texture patch.
+UINT32 ASTBlendTexturePixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
 {
-	char *texturesLump;
-	size_t texturesLumpLength;
-	char *texturesText;
-	UINT32 numTexturesInLump = 0;
-	char *texturesToken;
-
-	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
-	// need to make a space of memory where I can ensure that it will terminate
-	// correctly. Start by loading the relevant data from the WAD.
-	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
-	// If that didn't exist, we have nothing to do here.
-	if (texturesLump == NULL) return 0;
-	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
-	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
-	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
-	// Now move the contents of the lump into this new location.
-	memmove(texturesText,texturesLump,texturesLumpLength);
-	// Make damn well sure the last character in our new memory location is \0.
-	texturesText[texturesLumpLength] = '\0';
-	// Finally, free up the memory from the first data load, because we really
-	// don't need it.
-	Z_Free(texturesLump);
-
-	texturesToken = M_GetToken(texturesText);
-	while (texturesToken != NULL)
+	// Alpha style set to translucent?
+	if (style == AST_TRANSLUCENT)
 	{
-		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
-		{
-			numTexturesInLump++;
-			Z_Free(texturesToken);
-			R_ParseTexture(false);
-		}
-		else
+		// Is the alpha small enough for translucency?
+		if (alpha <= ASTTextureBlendingThreshold[1])
 		{
-			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
+			// Is the patch way too translucent? Don't blend then.
+			if (alpha < ASTTextureBlendingThreshold[0])
+				return background.rgba;
+
+			return ASTBlendPixel(background, foreground, style, alpha);
 		}
-		texturesToken = M_GetToken(NULL);
+		else // just copy the pixel
+			return foreground.rgba;
 	}
-	Z_Free(texturesToken);
-	Z_Free((void *)texturesText);
-
-	return numTexturesInLump;
+	else
+		return ASTBlendPixel(background, foreground, style, alpha);
 }
 
-// Parses the TEXTURES lump... for real, this time.
-void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *texindex)
+// Blends two palette indexes for a texture patch, then
+// finds the nearest palette index from the blended output.
+UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT8 alpha)
 {
-	char *texturesLump;
-	size_t texturesLumpLength;
-	char *texturesText;
-	char *texturesToken;
-	texture_t *newTexture;
-
-	I_Assert(texindex != NULL);
-
-	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
-	// need to make a space of memory where I can ensure that it will terminate
-	// correctly. Start by loading the relevant data from the WAD.
-	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
-	// If that didn't exist, we have nothing to do here.
-	if (texturesLump == NULL) return;
-	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
-	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
-	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
-	// Now move the contents of the lump into this new location.
-	memmove(texturesText,texturesLump,texturesLumpLength);
-	// Make damn well sure the last character in our new memory location is \0.
-	texturesText[texturesLumpLength] = '\0';
-	// Finally, free up the memory from the first data load, because we really
-	// don't need it.
-	Z_Free(texturesLump);
-
-	texturesToken = M_GetToken(texturesText);
-	while (texturesToken != NULL)
+	// Alpha style set to translucent?
+	if (style == AST_TRANSLUCENT)
 	{
-		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
-		{
-			Z_Free(texturesToken);
-			// Get the new texture
-			newTexture = R_ParseTexture(true);
-			// Store the new texture
-			textures[*texindex] = newTexture;
-			texturewidth[*texindex] = newTexture->width;
-			textureheight[*texindex] = newTexture->height << FRACBITS;
-			// Increment i back in R_LoadTextures()
-			(*texindex)++;
-		}
-		else
+		// Is the alpha small enough for translucency?
+		if (alpha <= ASTTextureBlendingThreshold[1])
 		{
-			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
+			UINT8 *mytransmap;
+			INT32 trans;
+
+			// Is the patch way too translucent? Don't blend then.
+			if (alpha < ASTTextureBlendingThreshold[0])
+				return background;
+
+			// The equation's not exact but it works as intended. I'll call it a day for now.
+			trans = (8*(alpha) + 255/8)/(255 - 255/11);
+			mytransmap = R_GetTranslucencyTable(trans + 1);
+			if (background != 0xFF)
+				return *(mytransmap + (background<<8) + foreground);
 		}
-		texturesToken = M_GetToken(NULL);
+		else // just copy the pixel
+			return foreground;
+	}
+	// just copy the pixel
+	else if (style == AST_COPY)
+		return foreground;
+	// use ASTBlendPixel for all other blend modes
+	// and find the nearest colour in the palette
+	else if (style != AST_TRANSLUCENT)
+	{
+		RGBA_t texel;
+		RGBA_t bg = V_GetMasterColor(background);
+		RGBA_t fg = V_GetMasterColor(foreground);
+		texel.rgba = ASTBlendPixel(bg, fg, style, alpha);
+		return NearestColor(texel.s.red, texel.s.green, texel.s.blue);
 	}
-	Z_Free(texturesToken);
-	Z_Free((void *)texturesText);
+	// fallback if all above fails, somehow
+	// return the background pixel
+	return background;
 }
 
 #ifdef EXTRACOLORMAPLUMPS
@@ -1571,54 +262,6 @@ static void R_InitExtraColormaps(void)
 }
 #endif
 
-// Search for flat name.
-lumpnum_t R_GetFlatNumForName(const char *name)
-{
-	INT32 i;
-	lumpnum_t lump;
-	lumpnum_t start;
-	lumpnum_t end;
-
-	// Scan wad files backwards so patched flats take preference.
-	for (i = numwadfiles - 1; i >= 0; i--)
-	{
-		switch (wadfiles[i]->type)
-		{
-		case RET_WAD:
-			if ((start = W_CheckNumForMarkerStartPwad("F_START", (UINT16)i, 0)) == INT16_MAX)
-			{
-				if ((start = W_CheckNumForMarkerStartPwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
-					continue;
-				else if ((end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start)) == INT16_MAX)
-					continue;
-			}
-			else
-				if ((end = W_CheckNumForNamePwad("F_END", (UINT16)i, start)) == INT16_MAX)
-					continue;
-			break;
-		case RET_PK3:
-			if ((start = W_CheckNumForFolderStartPK3("Flats/", i, 0)) == INT16_MAX)
-				continue;
-			if ((end = W_CheckNumForFolderEndPK3("Flats/", i, start)) == INT16_MAX)
-				continue;
-			break;
-		default:
-			continue;
-		}
-
-		// Now find lump with specified name in that range.
-		lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
-		if (lump < end)
-		{
-			lump += (i<<16); // found it, in our constraints
-			break;
-		}
-		lump = LUMPERROR;
-	}
-
-	return lump;
-}
-
 //
 // R_InitSpriteLumps
 // Finds the width and hoffset of all sprites in the wad, so the sprite does not need to be
@@ -2041,7 +684,7 @@ extracolormap_t *R_ColormapForName(char *name)
 #endif
 
 //
-// R_CreateColormap
+// R_CreateColormapFromLinedef
 //
 // This is a more GL friendly way of doing colormaps: Specify colormap
 // data in a special linedef's texture areas and use that to generate
@@ -2180,10 +823,8 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 	return lighttable;
 }
 
-extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
+extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
 {
-	extracolormap_t *extra_colormap, *exc;
-
 	// default values
 	UINT8 cr = 0, cg = 0, cb = 0, ca = 0, cfr = 0, cfg = 0, cfb = 0, cfa = 25;
 	UINT32 fadestart = 0, fadeend = 31;
@@ -2306,6 +947,13 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
 	rgba = R_PutRgbaRGBA(cr, cg, cb, ca);
 	fadergba = R_PutRgbaRGBA(cfr, cfg, cfb, cfa);
 
+	return R_CreateColormap(rgba, fadergba, fadestart, fadeend, flags);
+}
+
+extracolormap_t *R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags)
+{
+	extracolormap_t *extra_colormap;
+
 	// Did we just make a default colormap?
 #ifdef EXTRACOLORMAPLUMPS
 	if (R_CheckDefaultColormapByValues(true, true, true, rgba, fadergba, fadestart, fadeend, flags, LUMPERROR))
@@ -2317,17 +965,16 @@ extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3)
 
 	// Look for existing colormaps
 #ifdef EXTRACOLORMAPLUMPS
-	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags, LUMPERROR);
+	extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags, LUMPERROR);
 #else
-	exc = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags);
+	extra_colormap = R_GetColormapFromListByValues(rgba, fadergba, fadestart, fadeend, flags);
 #endif
-	if (exc)
-		return exc;
+	if (extra_colormap)
+		return extra_colormap;
 
-	CONS_Debug(DBG_RENDER, "Creating Colormap: rgba(%d,%d,%d,%d) fadergba(%d,%d,%d,%d)\n",
-		cr, cg, cb, ca, cfr, cfg, cfb, cfa);
+	CONS_Debug(DBG_RENDER, "Creating Colormap: rgba(%x) fadergba(%x)\n", rgba, fadergba);
 
-	extra_colormap = Z_Calloc(sizeof (*extra_colormap), PU_LEVEL, NULL);
+	extra_colormap = Z_Calloc(sizeof(*extra_colormap), PU_LEVEL, NULL);
 
 	extra_colormap->fadestart = (UINT16)fadestart;
 	extra_colormap->fadeend = (UINT16)fadeend;
@@ -2359,7 +1006,6 @@ extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *ex
 	boolean subR, boolean subG, boolean subB, boolean subA,
 	boolean subFadeR, boolean subFadeG, boolean subFadeB, boolean subFadeA,
 	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFlags,
-	boolean useAltAlpha, INT16 altAlpha, INT16 altFadeAlpha,
 	boolean lighttable)
 {
 	INT16 red, green, blue, alpha;
@@ -2395,7 +1041,7 @@ extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *ex
 			* R_GetRgbaB(exc_addend->rgba)
 		, 255), 0);
 
-	alpha = useAltAlpha ? altAlpha : R_GetRgbaA(exc_addend->rgba);
+	alpha = R_GetRgbaA(exc_addend->rgba);
 	alpha = max(min(R_GetRgbaA(exc_augend->rgba) + (subA ? -1 : 1) * alpha, 25), 0);
 
 	exc_augend->rgba = R_PutRgbaRGBA(red, green, blue, alpha);
@@ -2422,8 +1068,8 @@ extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *ex
 			* R_GetRgbaB(exc_addend->fadergba)
 		, 255), 0);
 
-	alpha = useAltAlpha ? altFadeAlpha : R_GetRgbaA(exc_addend->fadergba);
-	if (alpha == 25 && !useAltAlpha && !R_GetRgbaRGB(exc_addend->fadergba))
+	alpha = R_GetRgbaA(exc_addend->fadergba);
+	if (alpha == 25 && !R_GetRgbaRGB(exc_addend->fadergba))
 		alpha = 0; // HACK: fadergba A defaults at 25, so don't add anything in this case
 	alpha = max(min(R_GetRgbaA(exc_augend->fadergba) + (subFadeA ? -1 : 1) * alpha, 25), 0);
 
@@ -2577,74 +1223,6 @@ void R_InitData(void)
 	R_InitColormaps();
 }
 
-void R_ClearTextureNumCache(boolean btell)
-{
-	if (tidcache)
-		Z_Free(tidcache);
-	tidcache = NULL;
-	if (btell)
-		CONS_Debug(DBG_SETUP, "Fun Fact: There are %d textures used in this map.\n", tidcachelen);
-	tidcachelen = 0;
-}
-
-//
-// R_CheckTextureNumForName
-//
-// Check whether texture is available. Filter out NoTexture indicator.
-//
-INT32 R_CheckTextureNumForName(const char *name)
-{
-	INT32 i;
-
-	// "NoTexture" marker.
-	if (name[0] == '-')
-		return 0;
-
-	for (i = 0; i < tidcachelen; i++)
-		if (!strncasecmp(tidcache[i].name, name, 8))
-			return tidcache[i].id;
-
-	// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
-	//for (i = 0; i < numtextures; i++) <- old
-	for (i = (numtextures - 1); i >= 0; i--) // <- new
-		if (!strncasecmp(textures[i]->name, name, 8))
-		{
-			tidcachelen++;
-			Z_Realloc(tidcache, tidcachelen * sizeof(*tidcache), PU_STATIC, &tidcache);
-			strncpy(tidcache[tidcachelen-1].name, name, 8);
-			tidcache[tidcachelen-1].name[8] = '\0';
-#ifndef ZDEBUG
-			CONS_Debug(DBG_SETUP, "texture #%s: %s\n", sizeu1(tidcachelen), tidcache[tidcachelen-1].name);
-#endif
-			tidcache[tidcachelen-1].id = i;
-			return i;
-		}
-
-	return -1;
-}
-
-//
-// R_TextureNumForName
-//
-// Calls R_CheckTextureNumForName, aborts with error message.
-//
-INT32 R_TextureNumForName(const char *name)
-{
-	const INT32 i = R_CheckTextureNumForName(name);
-
-	if (i == -1)
-	{
-		static INT32 redwall = -2;
-		CONS_Debug(DBG_SETUP, "WARNING: R_TextureNumForName: %.8s not found\n", name);
-		if (redwall == -2)
-			redwall = R_CheckTextureNumForName("REDWALL");
-		if (redwall != -1)
-			return redwall;
-		return 1;
-	}
-	return i;
-}
-
 //
 // R_PrecacheLevel
 //
@@ -2729,7 +1307,7 @@ void R_PrecacheLevel(void)
 		lump = sf->lumppat[a];\
 		if (devparm)\
 			spritememory += W_LumpLength(lump);\
-		W_CachePatchNum(lump, PU_PATCH);\
+		W_CachePatchNum(lump, PU_SPRITE);\
 	}
 			// see R_InitSprites for more about lumppat,lumpid
 			switch (sf->rotate)
diff --git a/src/r_data.h b/src/r_data.h
index c7a9348f05c0f9a5b6706951d782090ddd0206d8..b50995dbbc4f009ce5a0c85374bb81a44dd6b72a 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -22,103 +22,34 @@
 #pragma interface
 #endif
 
-// Possible alpha types for a patch.
-enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
-
-UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
-UINT8 ASTBlendPixel_8bpp(UINT8 background, UINT8 foreground, int style, UINT8 alpha);
-
-UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
-
-// moved here for r_sky.c (texpatch_t is used)
-
-// A single patch from a texture definition,
-//  basically a rectangular area within
-//  the texture rectangle.
+// Store lists of lumps for F_START/F_END etc.
 typedef struct
 {
-	// Block origin (always UL), which has already accounted for the internal origin of the patch.
-	INT16 originx, originy;
-	UINT16 wad, lump;
-	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
-	UINT8 alpha; // Translucency value
-	enum patchalphastyle style;
-} texpatch_t;
-
-// texture type
-enum
-{
-	TEXTURETYPE_UNKNOWN,
-	TEXTURETYPE_SINGLEPATCH,
-	TEXTURETYPE_COMPOSITE,
-#ifdef WALLFLATS
-	TEXTURETYPE_FLAT,
-#endif
-};
-
-// A maptexturedef_t describes a rectangular texture,
-//  which is composed of one or more mappatch_t structures
-//  that arrange graphic patches.
-typedef struct
-{
-	// Keep name for switch changing, etc.
-	char name[8];
-	UINT8 type; // TEXTURETYPE_
-	INT16 width, height;
-	boolean holes;
-	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
-
-	// All the patches[patchcount] are drawn back to front into the cached texture.
-	INT16 patchcount;
-	texpatch_t patches[0];
-} texture_t;
+	UINT16 wadfile;
+	UINT16 firstlump;
+	size_t numlumps;
+} lumplist_t;
 
-typedef struct
-{
-	UINT8 *flat;
-	INT16 width, height;
-} textureflat_t;
+// Possible alpha types for a patch.
+enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
 
-// all loaded and prepared textures from the start of the game
-extern texture_t **textures;
-extern textureflat_t *texflats;
+UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
+UINT32 ASTBlendTexturePixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha);
+UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT8 alpha);
 
-extern INT32 *texturewidth;
-extern fixed_t *textureheight; // needed for texture pegging
+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[];
 
-// Load TEXTURES definitions, create lookup tables
-void R_LoadTextures(void);
-void R_FlushTextureCache(void);
-
-INT32 R_GetTextureNum(INT32 texnum);
-void R_CheckTextureCache(INT32 tex);
-
-// Retrieve column data for span blitting.
-UINT8 *R_GetColumn(fixed_t tex, INT32 col);
-UINT8 *R_GetFlat(lumpnum_t flatnum);
-
 // I/O, setting up the stuff.
 void R_InitData(void);
 void R_PrecacheLevel(void);
 
 extern size_t flatmemory, spritememory, texturememory;
 
-// Retrieval.
-// Floor/ceiling opaque texture tiles,
-// lookup by name. For animation?
-lumpnum_t R_GetFlatNumForName(const char *name);
-
-// Called by P_Ticker for switches and animations,
-// returns the texture number for the texture name.
-void R_ClearTextureNumCache(boolean btell);
-INT32 R_TextureNumForName(const char *name);
-INT32 R_CheckTextureNumForName(const char *name);
-
 // Extra Colormap lumps (C_START/C_END) are not used anywhere
 // Uncomment to enable
 //#define EXTRACOLORMAPLUMPS
@@ -146,13 +77,31 @@ boolean R_CheckDefaultColormap(extracolormap_t *extra_colormap, boolean checkrgb
 boolean R_CheckEqualColormaps(extracolormap_t *exc_a, extracolormap_t *exc_b, boolean checkrgba, boolean checkfadergba, boolean checkparams);
 extracolormap_t *R_GetColormapFromList(extracolormap_t *extra_colormap);
 
+typedef enum
+{
+	TMCF_RELATIVE     = 1,
+	TMCF_SUBLIGHTR    = 1<<1,
+	TMCF_SUBLIGHTG    = 1<<2,
+	TMCF_SUBLIGHTB    = 1<<3,
+	TMCF_SUBLIGHTA    = 1<<4,
+	TMCF_SUBFADER     = 1<<5,
+	TMCF_SUBFADEG     = 1<<6,
+	TMCF_SUBFADEB     = 1<<7,
+	TMCF_SUBFADEA     = 1<<8,
+	TMCF_SUBFADESTART = 1<<9,
+	TMCF_SUBFADEEND   = 1<<10,
+	TMCF_IGNOREFLAGS  = 1<<11,
+	TMCF_FROMBLACK    = 1<<12,
+	TMCF_OVERRIDE     = 1<<13,
+} textmapcolormapflags_t;
+
 lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap);
-extracolormap_t *R_CreateColormap(char *p1, char *p2, char *p3);
+extracolormap_t * R_CreateColormapFromLinedef(char *p1, char *p2, char *p3);
+extracolormap_t* R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags);
 extracolormap_t *R_AddColormaps(extracolormap_t *exc_augend, extracolormap_t *exc_addend,
 	boolean subR, boolean subG, boolean subB, boolean subA,
 	boolean subFadeR, boolean subFadeG, boolean subFadeB, boolean subFadeA,
 	boolean subFadeStart, boolean subFadeEnd, boolean ignoreFlags,
-	boolean useAltAlpha, INT16 altAlpha, INT16 altFadeAlpha,
 	boolean lighttable);
 #ifdef EXTRACOLORMAPLUMPS
 extracolormap_t *R_ColormapForName(char *name);
@@ -174,6 +123,4 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap);
 UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette);
 #define NearestColor(r, g, b) NearestPaletteColor(r, g, b, NULL)
 
-extern INT32 numtextures;
-
 #endif
diff --git a/src/r_defs.h b/src/r_defs.h
index 6d7fc3311a81848f45b5946867235b9639d156f8..e8014390b86f0aa003fa4b6a11b255b02cadc1b8 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -28,6 +28,8 @@
 #include "m_aatree.h"
 #endif
 
+#include "taglist.h"
+
 //
 // ClipWallSegment
 // Clips the given range of columns
@@ -283,8 +285,7 @@ typedef struct sector_s
 	INT32 ceilingpic;
 	INT16 lightlevel;
 	INT16 special;
-	UINT16 tag;
-	INT32 nexttag, firsttag; // for fast tag searches
+	taglist_t tags;
 
 	// origin for any sounds played by the sector
 	// also considered the center for e.g. Mario blocks
@@ -335,6 +336,7 @@ typedef struct sector_s
 
 	// per-sector colormaps!
 	extracolormap_t *extra_colormap;
+	boolean colormap_protected;
 
 	// This points to the master's floorheight, so it can be changed in realtime!
 	fixed_t *gravity; // per-sector gravity
@@ -379,6 +381,9 @@ typedef enum
 
 #define HORIZONSPECIAL 41
 
+#define NUMLINEARGS 6
+#define NUMLINESTRINGARGS 2
+
 typedef struct line_s
 {
 	// Vertices, from v1 to v2.
@@ -390,10 +395,14 @@ typedef struct line_s
 	// Animation related.
 	INT16 flags;
 	INT16 special;
-	INT16 tag;
+	taglist_t tags;
+	INT32 args[NUMLINEARGS];
+	char *stringargs[NUMLINESTRINGARGS];
 
 	// Visual appearance: sidedefs.
 	UINT16 sidenum[2]; // sidenum[1] will be 0xffff if one-sided
+	fixed_t alpha; // translucency
+	INT32 executordelay;
 
 	fixed_t bbox[4]; // bounding box for the extent of the linedef
 
@@ -406,10 +415,6 @@ typedef struct line_s
 	sector_t *backsector;
 
 	size_t validcount; // if == validcount, already checked
-#if 1//#ifdef WALLSPLATS
-	void *splats; // wallsplat_t list
-#endif
-	INT32 firsttag, nexttag; // improves searches for tags.
 	polyobj_t *polyobj; // Belongs to a polyobject?
 
 	char *text; // a concatenation of all front and back texture names, for linedef specials that require a string.
@@ -454,9 +459,6 @@ typedef struct subsector_s
 	INT16 numlines;
 	UINT16 firstline;
 	struct polyobj_s *polyList; // haleyjd 02/19/06: list of polyobjects
-#if 1//#ifdef FLOORSPLATS
-	void *splats; // floorsplat_t list
-#endif
 	size_t validcount;
 } subsector_t;
 
@@ -649,8 +651,12 @@ typedef enum
 	RGBA32          = 4,  // 32 bit rgba
 } pic_mode_t;
 
-#if defined(_MSC_VER)
-#pragma pack(1)
+#ifdef ROTSPRITE
+typedef struct
+{
+	INT32 angles;
+	void **patches;
+} rotsprite_t;
 #endif
 
 // Patches.
@@ -658,7 +664,26 @@ typedef enum
 // Patches are used for sprites and all masked pictures, and we compose
 // textures from the TEXTURES list of patches.
 //
-// WARNING: this structure is cloned in GLPatch_t
+typedef struct
+{
+	INT16 width, height;
+	INT16 leftoffset, topoffset;
+
+	INT32 *columnofs; // Column offsets. This is relative to patch->columns
+	UINT8 *columns; // Software column data
+
+	void *hardware; // OpenGL patch, allocated whenever necessary
+	void *flats[4]; // The patch as flats
+
+#ifdef ROTSPRITE
+	rotsprite_t *rotated; // Rotated patches
+#endif
+} patch_t;
+
+#if defined(_MSC_VER)
+#pragma pack(1)
+#endif
+
 typedef struct
 {
 	INT16 width;          // bounding box size
@@ -667,7 +692,7 @@ typedef struct
 	INT16 topoffset;      // pixels below the origin
 	INT32 columnofs[8];     // only [width] used
 	// the [0] is &columnofs[width]
-} ATTRPACK patch_t;
+} ATTRPACK softwarepatch_t;
 
 #ifdef _MSC_VER
 #pragma warning(disable :  4200)
@@ -693,14 +718,32 @@ typedef struct
 #pragma pack()
 #endif
 
-// rotsprite
-#ifdef ROTSPRITE
-typedef struct
+typedef enum
 {
-	patch_t *patch[16][ROTANGLES];
-	UINT16 cached;
-} rotsprite_t;
-#endif/*ROTSPRITE*/
+	RF_HORIZONTALFLIP   = 0x0001,   // Flip sprite horizontally
+	RF_VERTICALFLIP     = 0x0002,   // Flip sprite vertically
+	RF_ABSOLUTEOFFSETS  = 0x0004,   // Sprite uses the object's offsets absolutely, instead of relatively
+	RF_FLIPOFFSETS      = 0x0008,   // Relative object offsets are flipped with the sprite
+
+	RF_SPLATMASK        = 0x00F0,   // --Floor sprite flags
+	RF_SLOPESPLAT       = 0x0010,   // Rotate floor sprites by a slope
+	RF_OBJECTSLOPESPLAT = 0x0020,   // Rotate floor sprites by the object's standing slope
+	RF_NOSPLATBILLBOARD = 0x0040,   // Don't billboard floor sprites (faces forward from the view angle)
+	RF_NOSPLATROLLANGLE = 0x0080,   // Don't rotate floor sprites by the object's rollangle (uses rotated patches instead)
+
+	RF_BLENDMASK        = 0x0F00,   // --Blending modes
+	RF_FULLBRIGHT       = 0x0100,   // Sprite is drawn at full brightness
+	RF_FULLDARK         = 0x0200,   // Sprite is drawn completely dark
+	RF_NOCOLORMAPS      = 0x0400,   // Sprite is not drawn with colormaps
+
+	RF_SPRITETYPEMASK   = 0x7000,   // ---Different sprite types
+	RF_PAPERSPRITE      = 0x1000,   // Paper sprite
+	RF_FLOORSPRITE      = 0x2000,   // Floor sprite
+
+	RF_SHADOWDRAW       = 0x10000,  // Stretches and skews the sprite like a shadow.
+	RF_SHADOWEFFECTS    = 0x20000,  // Scales and becomes transparent like a shadow.
+	RF_DROPSHADOW       = (RF_SHADOWDRAW | RF_SHADOWEFFECTS | RF_FULLDARK),
+} renderflags_t;
 
 typedef enum
 {
@@ -714,24 +757,6 @@ typedef enum
 	SRF_NONE        = 0xff // Initial value
 } spriterotateflags_t;     // SRF's up!
 
-// Same as a patch_t, except just the header
-// and the wadnum/lumpnum combination that points
-// to wherever the patch is in memory.
-struct patchinfo_s
-{
-	INT16 width;          // bounding box size
-	INT16 height;
-	INT16 leftoffset;     // pixels to the left of origin
-	INT16 topoffset;      // pixels below the origin
-
-	UINT16 wadnum;        // the software patch lump num for when the patch
-	UINT16 lumpnum;       // was flushed, and we need to re-create it
-
-	// next patchinfo_t in memory
-	struct patchinfo_s *next;
-};
-typedef struct patchinfo_s patchinfo_t;
-
 //
 // Sprites are patches with a special naming convention so they can be
 //  recognized by R_InitSprites.
@@ -761,7 +786,7 @@ typedef struct
 	UINT16 flip;
 
 #ifdef ROTSPRITE
-	rotsprite_t rotsprite;
+	rotsprite_t *rotated[2][16]; // Rotated patches
 #endif
 } spriteframe_t;
 
diff --git a/src/r_draw.c b/src/r_draw.c
index 5351ef37f079b151a0c5c3effd5fe3d006109783..c3d4efae39a89c296bef158ca488ea1c138c487e 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -75,6 +75,7 @@ UINT8 *dc_source;
 #define NUMTRANSTABLES 9 // how many translucency tables are used
 
 UINT8 *transtables; // translucency tables
+UINT8 *blendtables[NUMBLENDMAPS];
 
 /**	\brief R_DrawTransColumn uses this
 */
@@ -98,15 +99,19 @@ INT32 dc_numlights = 0, dc_maxlights, dc_texheight;
 
 INT32 ds_y, ds_x1, ds_x2;
 lighttable_t *ds_colormap;
+lighttable_t *ds_translation; // Lactozilla: Sprite splat drawer
+
 fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
+INT32 ds_waterofs, ds_bgofs;
+
 UINT16 ds_flatwidth, ds_flatheight;
 boolean ds_powersoftwo;
 
-UINT8 *ds_source; // start of a 64*64 tile image
+UINT8 *ds_source; // points to the start of a flat
 UINT8 *ds_transmap; // one of the translucency tables
 
-pslope_t *ds_slope; // Current slope being used
-floatv3_t ds_su[MAXVIDHEIGHT], ds_sv[MAXVIDHEIGHT], ds_sz[MAXVIDHEIGHT]; // Vectors for... stuff?
+// Vectors for Software's tilted slope drawers
+floatv3_t *ds_su, *ds_sv, *ds_sz;
 floatv3_t *ds_sup, *ds_svp, *ds_szp;
 float focallengthf, zeroheight;
 
@@ -115,10 +120,6 @@ float focallengthf, zeroheight;
 
 UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 
-// ==========================================================================
-//                        OLD DOOM FUZZY EFFECT
-// ==========================================================================
-
 // =========================================================================
 //                   TRANSLATION COLORMAP CODE
 // =========================================================================
@@ -133,15 +134,50 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define DEFAULT_STARTTRANSCOLOR 96
 #define NUM_PALETTE_ENTRIES 256
 
-static UINT8** translationtablecache[MAXSKINS + 7] = {NULL};
+static UINT8 **translationtablecache[MAXSKINS + 7] = {NULL};
+UINT8 skincolor_modified[MAXSKINCOLORS];
+
+static INT32 SkinToCacheIndex(INT32 skinnum)
+{
+	switch (skinnum)
+	{
+		case TC_DEFAULT:    return DEFAULT_TT_CACHE_INDEX;
+		case TC_BOSS:       return BOSS_TT_CACHE_INDEX;
+		case TC_METALSONIC: return METALSONIC_TT_CACHE_INDEX;
+		case TC_ALLWHITE:   return ALLWHITE_TT_CACHE_INDEX;
+		case TC_RAINBOW:    return RAINBOW_TT_CACHE_INDEX;
+		case TC_BLINK:      return BLINK_TT_CACHE_INDEX;
+		case TC_DASHMODE:   return DASHMODE_TT_CACHE_INDEX;
+		     default:       break;
+	}
+
+	return skinnum;
+}
+
+static INT32 CacheIndexToSkin(INT32 ttc)
+{
+	switch (ttc)
+	{
+		case DEFAULT_TT_CACHE_INDEX:    return TC_DEFAULT;
+		case BOSS_TT_CACHE_INDEX:       return TC_BOSS;
+		case METALSONIC_TT_CACHE_INDEX: return TC_METALSONIC;
+		case ALLWHITE_TT_CACHE_INDEX:   return TC_ALLWHITE;
+		case RAINBOW_TT_CACHE_INDEX:    return TC_RAINBOW;
+		case BLINK_TT_CACHE_INDEX:      return TC_BLINK;
+		case DASHMODE_TT_CACHE_INDEX:   return TC_DASHMODE;
+		     default:                   break;
+	}
+
+	return ttc;
+}
 
 CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
 
-/**	\brief The R_InitTranslationTables
+#define TRANSTAB_AMTMUL10 (256.0f / 10.0f)
 
-  load in color translation tables
+/** \brief Initializes the translucency tables used by the Software renderer.
 */
-void R_InitTranslationTables(void)
+void R_InitTranslucencyTables(void)
 {
 	// Load here the transparency lookup tables 'TINTTAB'
 	// NOTE: the TINTTAB resource MUST BE aligned on 64k for the asm
@@ -158,17 +194,94 @@ void R_InitTranslationTables(void)
 	W_ReadLump(W_GetNumForName("TRANS70"), transtables+0x60000);
 	W_ReadLump(W_GetNumForName("TRANS80"), transtables+0x70000);
 	W_ReadLump(W_GetNumForName("TRANS90"), transtables+0x80000);
+
+	R_GenerateBlendTables();
 }
 
+void R_GenerateBlendTables(void)
+{
+	INT32 i;
 
-/**	\brief	Generates a translation colormap.
+	for (i = 0; i < NUMBLENDMAPS; i++)
+	{
+		if (i == blendtab_modulate)
+			continue;
+		blendtables[i] = Z_MallocAlign((NUMTRANSTABLES + 1) * 0x10000, PU_STATIC, NULL, 16);
+	}
 
-	\param	dest_colormap	colormap to populate
-	\param	skinnum		number of skin, TC_DEFAULT or TC_BOSS
-	\param	color		translation color
+	for (i = 0; i <= 9; i++)
+	{
+		const size_t offs = (0x10000 * i);
+		const UINT8 alpha = TRANSTAB_AMTMUL10 * i;
 
-	\return	void
-*/
+		R_GenerateTranslucencyTable(blendtables[blendtab_add] + offs, AST_ADD, alpha);
+		R_GenerateTranslucencyTable(blendtables[blendtab_subtract] + offs, AST_SUBTRACT, alpha);
+		R_GenerateTranslucencyTable(blendtables[blendtab_reversesubtract] + offs, AST_REVERSESUBTRACT, alpha);
+	}
+
+	// Modulation blending only requires a single table
+	blendtables[blendtab_modulate] = Z_MallocAlign(0x10000, PU_STATIC, NULL, 16);
+	R_GenerateTranslucencyTable(blendtables[blendtab_modulate], AST_MODULATE, 0);
+}
+
+static colorlookup_t transtab_lut;
+
+void R_GenerateTranslucencyTable(UINT8 *table, int style, UINT8 blendamt)
+{
+	INT16 bg, fg;
+
+	if (table == NULL)
+		I_Error("R_GenerateTranslucencyTable: input table was NULL!");
+
+	InitColorLUT(&transtab_lut, pMasterPalette, false);
+
+	for (bg = 0; bg < 0xFF; bg++)
+	{
+		for (fg = 0; fg < 0xFF; fg++)
+		{
+			RGBA_t backrgba = V_GetMasterColor(bg);
+			RGBA_t frontrgba = V_GetMasterColor(fg);
+			RGBA_t result;
+
+			result.rgba = ASTBlendPixel(backrgba, frontrgba, style, blendamt);
+			table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
+		}
+	}
+}
+
+#define ClipTransLevel(trans) max(min((trans), NUMTRANSMAPS-2), 0)
+
+UINT8 *R_GetTranslucencyTable(INT32 alphalevel)
+{
+	return transtables + (ClipTransLevel(alphalevel-1) << FF_TRANSSHIFT);
+}
+
+UINT8 *R_GetBlendTable(int style, INT32 alphalevel)
+{
+	size_t offs = (ClipTransLevel(alphalevel) << FF_TRANSSHIFT);
+
+	// Lactozilla: Returns the equivalent to AST_TRANSLUCENT
+	// if no alpha style matches any of the blend tables.
+	switch (style)
+	{
+		case AST_ADD:
+			return blendtables[blendtab_add] + offs;
+		case AST_SUBTRACT:
+			return blendtables[blendtab_subtract] + offs;
+		case AST_REVERSESUBTRACT:
+			return blendtables[blendtab_reversesubtract] + offs;
+		case AST_MODULATE:
+			return blendtables[blendtab_modulate];
+		default:
+			break;
+	}
+
+	// Return a normal translucency table
+	if (--alphalevel >= 0)
+		return transtables + (ClipTransLevel(alphalevel) << FF_TRANSSHIFT);
+	else
+		return NULL;
+}
 
 // Define for getting accurate color brightness readings according to how the human eye sees them.
 // https://en.wikipedia.org/wiki/Relative_luminance
@@ -226,6 +339,14 @@ static void R_RainbowColormap(UINT8 *dest_colormap, UINT16 skincolor)
 
 #undef SETBRIGHTNESS
 
+/**	\brief	Generates a translation colormap.
+
+	\param	dest_colormap	colormap to populate
+	\param	skinnum		skin number, or a translation mode
+	\param	color		translation color
+
+	\return	void
+*/
 static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, UINT16 color)
 {
 	INT32 i, starttranscolor, skinramplength;
@@ -325,6 +446,9 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 	if (color >= numskincolors)
 		I_Error("Invalid skin color #%hu.", (UINT16)color);
 
+	if (skinnum < 0 && skinnum > TC_DEFAULT)
+		I_Error("Invalid translation colormap index %d.", skinnum);
+
 	starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR;
 
 	if (starttranscolor >= NUM_PALETTE_ENTRIES)
@@ -361,30 +485,27 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags)
 {
 	UINT8* ret;
-	INT32 skintableindex;
-
-	// Adjust if we want the default colormap
-	switch (skinnum)
-	{
-		case TC_DEFAULT:    skintableindex = DEFAULT_TT_CACHE_INDEX; break;
-		case TC_BOSS:       skintableindex = BOSS_TT_CACHE_INDEX; break;
-		case TC_METALSONIC: skintableindex = METALSONIC_TT_CACHE_INDEX; break;
-		case TC_ALLWHITE:   skintableindex = ALLWHITE_TT_CACHE_INDEX; break;
-		case TC_RAINBOW:    skintableindex = RAINBOW_TT_CACHE_INDEX; break;
-		case TC_BLINK:      skintableindex = BLINK_TT_CACHE_INDEX; break;
-		case TC_DASHMODE:   skintableindex = DASHMODE_TT_CACHE_INDEX; break;
-		     default:       skintableindex = skinnum; break;
-	}
+	INT32 skintableindex = SkinToCacheIndex(skinnum); // Adjust if we want the default colormap
+	INT32 i;
 
 	if (flags & GTC_CACHE)
 	{
-
 		// Allocate table for skin if necessary
 		if (!translationtablecache[skintableindex])
 			translationtablecache[skintableindex] = Z_Calloc(MAXSKINCOLORS * sizeof(UINT8**), PU_STATIC, NULL);
 
 		// Get colormap
 		ret = translationtablecache[skintableindex][color];
+
+		// Rebuild the cache if necessary
+		if (skincolor_modified[color])
+		{
+			for (i = 0; i < (INT32)(sizeof(translationtablecache) / sizeof(translationtablecache[0])); i++)
+				if (translationtablecache[i] && translationtablecache[i][color])
+					R_GenerateTranslationColormap(translationtablecache[i][color], CacheIndexToSkin(i), color);
+
+			skincolor_modified[color] = false;
+		}
 	}
 	else ret = NULL;
 
@@ -427,12 +548,12 @@ UINT16 R_GetColorByName(const char *name)
 	for (color = 1; color < numskincolors; color++)
 		if (!stricmp(skincolors[color].name, name))
 			return color;
-	return SKINCOLOR_GREEN;
+	return SKINCOLOR_NONE;
 }
 
 UINT16 R_GetSuperColorByName(const char *name)
 {
-	UINT16 i, color = SKINCOLOR_SUPERGOLD1;
+	UINT16 i, color = SKINCOLOR_NONE;
 	char *realname = Z_Malloc(MAXCOLORNAME+1, PU_STATIC, NULL);
 	snprintf(realname, MAXCOLORNAME+1, "Super %s 1", name);
 	for (i = 1; i < numskincolors; i++)
diff --git a/src/r_draw.h b/src/r_draw.h
index 329c4974a6ca722322fb754fd3faf90e6cd92e13..d1eb83033884742adc0c3d75f5325868f6bc29e1 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -37,7 +37,6 @@ extern UINT8 dc_hires;
 extern UINT8 *dc_source; // first pixel in a column
 
 // translucency stuff here
-extern UINT8 *transtables; // translucency tables, should be (*transtables)[5][256][256]
 extern UINT8 *dc_transmap;
 
 // translation stuff here
@@ -56,9 +55,14 @@ extern INT32 dc_texheight;
 
 extern INT32 ds_y, ds_x1, ds_x2;
 extern lighttable_t *ds_colormap;
+extern lighttable_t *ds_translation;
+
 extern fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
+extern INT32 ds_waterofs, ds_bgofs;
+
 extern UINT16 ds_flatwidth, ds_flatheight;
 extern boolean ds_powersoftwo;
+
 extern UINT8 *ds_source;
 extern UINT8 *ds_transmap;
 
@@ -66,8 +70,8 @@ typedef struct {
 	float x, y, z;
 } floatv3_t;
 
-extern pslope_t *ds_slope; // Current slope being used
-extern floatv3_t ds_su[MAXVIDHEIGHT], ds_sv[MAXVIDHEIGHT], ds_sz[MAXVIDHEIGHT]; // Vectors for... stuff?
+// Vectors for Software's tilted slope drawers
+extern floatv3_t *ds_su, *ds_sv, *ds_sz;
 extern floatv3_t *ds_sup, *ds_svp, *ds_szp;
 extern float focallengthf, zeroheight;
 
@@ -102,22 +106,48 @@ extern lumpnum_t viewborderlump[8];
 
 #define GTC_CACHE 1
 
-#define TC_DEFAULT    -1
-#define TC_BOSS       -2
-#define TC_METALSONIC -3 // For Metal Sonic battle
-#define TC_ALLWHITE   -4 // For Cy-Brak-demon
-#define TC_RAINBOW    -5 // For single colour
-#define TC_BLINK      -6 // For item blinking, according to kart
-#define TC_DASHMODE   -7 // For Metal Sonic's dashmode
+enum
+{
+	TC_BOSS       = INT8_MIN,
+	TC_METALSONIC, // For Metal Sonic battle
+	TC_ALLWHITE,   // For Cy-Brak-demon
+	TC_RAINBOW,    // For single colour
+	TC_BLINK,      // For item blinking, according to kart
+	TC_DASHMODE,   // For Metal Sonic's dashmode
+
+	TC_DEFAULT
+};
 
+// Custom player skin translation
 // Initialize color translation tables, for player rendering etc.
-void R_InitTranslationTables(void);
 UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags);
 void R_FlushTranslationColormapCache(void);
 UINT16 R_GetColorByName(const char *name);
 UINT16 R_GetSuperColorByName(const char *name);
 
-// Custom player skin translation
+extern UINT8 *transtables; // translucency tables, should be (*transtables)[5][256][256]
+
+enum
+{
+	blendtab_add,
+	blendtab_subtract,
+	blendtab_reversesubtract,
+	blendtab_modulate,
+	NUMBLENDMAPS
+};
+
+extern UINT8 *blendtables[NUMBLENDMAPS];
+
+void R_InitTranslucencyTables(void);
+void R_GenerateBlendTables(void);
+void R_GenerateTranslucencyTable(UINT8 *table, int style, UINT8 blendamt);
+
+UINT8 *R_GetTranslucencyTable(INT32 alphalevel);
+UINT8 *R_GetBlendTable(int style, INT32 alphalevel);
+
+// Color ramp modification should force a recache
+extern UINT8 skincolor_modified[];
+
 void R_InitViewBuffer(INT32 width, INT32 height);
 void R_InitViewBorder(void);
 void R_VideoErase(size_t ofs, INT32 count);
@@ -146,39 +176,47 @@ void R_Draw2sMultiPatchTranslucentColumn_8(void);
 void R_DrawFogColumn_8(void);
 void R_DrawColumnShadowed_8(void);
 
+#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
+
 void R_DrawSpan_8(void);
-void R_DrawSplat_8(void);
 void R_DrawTranslucentSpan_8(void);
-void R_DrawTranslucentSplat_8(void);
 void R_DrawTiltedSpan_8(void);
 void R_DrawTiltedTranslucentSpan_8(void);
-#ifndef NOWATER
-void R_DrawTiltedTranslucentWaterSpan_8(void);
-#endif
+
+void R_DrawSplat_8(void);
+void R_DrawTranslucentSplat_8(void);
 void R_DrawTiltedSplat_8(void);
+
+void R_DrawFloorSprite_8(void);
+void R_DrawTranslucentFloorSprite_8(void);
+void R_DrawTiltedFloorSprite_8(void);
+void R_DrawTiltedTranslucentFloorSprite_8(void);
+
 void R_CalcTiltedLighting(fixed_t start, fixed_t end);
 extern INT32 tiltlighting[MAXVIDWIDTH];
-#ifndef NOWATER
+
 void R_DrawTranslucentWaterSpan_8(void);
-extern INT32 ds_bgofs;
-extern INT32 ds_waterofs;
-#endif
+void R_DrawTiltedTranslucentWaterSpan_8(void);
+
 void R_DrawFogSpan_8(void);
 
 // Lactozilla: Non-powers-of-two
 void R_DrawSpan_NPO2_8(void);
 void R_DrawTranslucentSpan_NPO2_8(void);
-void R_DrawSplat_NPO2_8(void);
-void R_DrawTranslucentSplat_NPO2_8(void);
 void R_DrawTiltedSpan_NPO2_8(void);
 void R_DrawTiltedTranslucentSpan_NPO2_8(void);
-#ifndef NOWATER
-void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void);
-#endif
+
+void R_DrawSplat_NPO2_8(void);
+void R_DrawTranslucentSplat_NPO2_8(void);
 void R_DrawTiltedSplat_NPO2_8(void);
-#ifndef NOWATER
+
+void R_DrawFloorSprite_NPO2_8(void);
+void R_DrawTranslucentFloorSprite_NPO2_8(void);
+void R_DrawTiltedFloorSprite_NPO2_8(void);
+void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void);
+
 void R_DrawTranslucentWaterSpan_NPO2_8(void);
-#endif
+void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void);
 
 #ifdef USEASM
 void ASMCALL R_DrawColumn_8_ASM(void);
diff --git a/src/r_draw8.c b/src/r_draw8.c
index 940ea724b31ef2411514799d4a9f7df7e284c907..e78ba8a6c49b8f39a9c54fe0af814340c9462641 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -536,6 +536,9 @@ void R_DrawTranslatedColumn_8(void)
 // SPANS
 // ==========================================================================
 
+#define SPANSIZE 16
+#define INVSPAN 0.0625f
+
 /**	\brief The R_DrawSpan_8 function
 	Draws the actual span.
 */
@@ -643,8 +646,6 @@ void R_CalcTiltedLighting(fixed_t start, fixed_t end)
 	}
 }
 
-#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
-
 /**	\brief The R_DrawTiltedSpan_8 function
 	Draw slopes! Holy sheit!
 */
@@ -704,9 +705,6 @@ void R_DrawTiltedSpan_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -839,9 +837,6 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -916,7 +911,6 @@ void R_DrawTiltedTranslucentSpan_8(void)
 #endif
 }
 
-#ifndef NOWATER
 /**	\brief The R_DrawTiltedTranslucentWaterSpan_8 function
 	Like DrawTiltedTranslucentSpan, but for water
 */
@@ -977,9 +971,6 @@ void R_DrawTiltedTranslucentWaterSpan_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -1053,7 +1044,6 @@ void R_DrawTiltedTranslucentWaterSpan_8(void)
 	}
 #endif
 }
-#endif // NOWATER
 
 void R_DrawTiltedSplat_8(void)
 {
@@ -1116,9 +1106,6 @@ void R_DrawTiltedSplat_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -1419,6 +1406,448 @@ void R_DrawTranslucentSplat_8 (void)
 	}
 }
 
+/**	\brief The R_DrawFloorSprite_8 function
+	Just like R_DrawSplat_8, but for floor sprites.
+*/
+void R_DrawFloorSprite_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
+	// can be used for the fraction part. This allows calculation of the memory address in the
+	// texture with two shifts, an OR and one AND. (see below)
+	// for texture sizes > 64 the amount of precision we can allow will decrease, but only by one
+	// bit per power of two (obviously)
+	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
+	// than the original span renderer. Whodathunkit?
+	xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+	xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	while (count >= 8)
+	{
+		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+		// have the uber complicated math to calculate it now, so that was a memory write we didn't
+		// need!
+		//
+		// <Callum> 4194303 = (2048x2048)-1 (2048x2048 is maximum flat size)
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[0] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[1] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[2] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[3] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[4] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[5] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[6] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[7] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		dest += 8;
+		count -= 8;
+	}
+	while (count-- && dest <= deststop)
+	{
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			*dest = colormap[translation[val & 0xFF]];
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTranslucentFloorSplat_8 function
+	Just like R_DrawFloorSprite_8, but is translucent!
+*/
+void R_DrawTranslucentFloorSprite_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
+	// can be used for the fraction part. This allows calculation of the memory address in the
+	// texture with two shifts, an OR and one AND. (see below)
+	// for texture sizes > 64 the amount of precision we can allow will decrease, but only by one
+	// bit per power of two (obviously)
+	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
+	// than the original span renderer. Whodathunkit?
+	xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+	xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	while (count >= 8)
+	{
+		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+		// have the uber complicated math to calculate it now, so that was a memory write we didn't
+		// need!
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[0] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[0]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[1] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[1]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[2] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[2]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[3] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[3]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[4] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[4]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[5] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[5]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[6] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[6]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[7] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[7]);
+		xposition += xstep;
+		yposition += ystep;
+
+		dest += 8;
+		count -= 8;
+	}
+	while (count-- && dest <= deststop)
+	{
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTiltedFloorSprite_8 function
+	Draws a tilted floor sprite.
+*/
+void R_DrawTiltedFloorSprite_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//x1 = 0;
+	width++;
+
+	while (width >= SPANSIZE)
+	{
+		iz += izstep;
+		uz += uzstep;
+		vz += vzstep;
+
+		endz = 1.f/iz;
+		endu = uz*endz;
+		endv = vz*endz;
+		stepu = (INT64)((endu - startu) * INVSPAN);
+		stepv = (INT64)((endv - startv) * INVSPAN);
+		u = (INT64)(startu) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = colormap[translation[val & 0xFF]];
+			dest++;
+
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = colormap[translation[val & 0xFF]];
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->x * left;
+
+			endz = 1.f/iz;
+			endu = uz*endz;
+			endv = vz*endz;
+			left = 1.f/left;
+			stepu = (INT64)((endu - startu) * left);
+			stepv = (INT64)((endv - startv) * left);
+			u = (INT64)(startu) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+				if (val & 0xFF00)
+					*dest = colormap[translation[val & 0xFF]];
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
+/**	\brief The R_DrawTiltedTranslucentFloorSprite_8 function
+	Draws a tilted, translucent, floor sprite.
+*/
+void R_DrawTiltedTranslucentFloorSprite_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//x1 = 0;
+	width++;
+
+	while (width >= SPANSIZE)
+	{
+		iz += izstep;
+		uz += uzstep;
+		vz += vzstep;
+
+		endz = 1.f/iz;
+		endu = uz*endz;
+		endv = vz*endz;
+		stepu = (INT64)((endu - startu) * INVSPAN);
+		stepv = (INT64)((endv - startv) * INVSPAN);
+		u = (INT64)(startu) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+			dest++;
+
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->x * left;
+
+			endz = 1.f/iz;
+			endu = uz*endz;
+			endv = vz*endz;
+			left = 1.f/left;
+			stepu = (INT64)((endu - startu) * left);
+			stepv = (INT64)((endv - startv) * left);
+			u = (INT64)(startu) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+				if (val & 0xFF00)
+					*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
 /**	\brief The R_DrawTranslucentSpan_8 function
 	Draws the actual span with translucency.
 */
@@ -1503,7 +1932,6 @@ void R_DrawTranslucentSpan_8 (void)
 	}
 }
 
-#ifndef NOWATER
 void R_DrawTranslucentWaterSpan_8(void)
 {
 	UINT32 xposition;
@@ -1580,7 +2008,6 @@ void R_DrawTranslucentWaterSpan_8(void)
 		yposition += ystep;
 	}
 }
-#endif
 
 /**	\brief The R_DrawFogSpan_8 function
 	Draws the actual span with fogging.
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 02015569455e94df9e4ec9a8c8be835cbd0da856..a34a20e9a9737241bbc183d71f8bae01a982cb7a 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -15,6 +15,9 @@
 // SPANS
 // ==========================================================================
 
+#define SPANSIZE 16
+#define INVSPAN 0.0625f
+
 /**	\brief The R_DrawSpan_NPO2_8 function
 	Draws the actual span.
 */
@@ -23,6 +26,8 @@ void R_DrawSpan_NPO2_8 (void)
 	fixed_t xposition;
 	fixed_t yposition;
 	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
 
 	UINT8 *source;
 	UINT8 *colormap;
@@ -41,19 +46,39 @@ void R_DrawSpan_NPO2_8 (void)
 	if (dest+8 > deststop)
 		return;
 
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
+
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
+
 	while (count-- && dest <= deststop)
 	{
-		fixed_t x = (xposition >> FRACBITS);
-		fixed_t y = (yposition >> FRACBITS);
-
-		// Carefully align all of my Friends.
-		if (x < 0)
-			x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
-		if (y < 0)
-			y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
 
-		x %= ds_flatwidth;
-		y %= ds_flatheight;
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
 
 		*dest++ = colormap[source[((y * ds_flatwidth) + x)]];
 		xposition += xstep;
@@ -61,8 +86,6 @@ void R_DrawSpan_NPO2_8 (void)
 	}
 }
 
-#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
-
 /**	\brief The R_DrawTiltedSpan_NPO2_8 function
 	Draw slopes! Holy sheit!
 */
@@ -137,9 +160,6 @@ void R_DrawTiltedSpan_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -332,9 +352,6 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -531,9 +548,6 @@ void R_DrawTiltedSplat_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -668,6 +682,8 @@ void R_DrawSplat_NPO2_8 (void)
 	fixed_t xposition;
 	fixed_t yposition;
 	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
 
 	UINT8 *source;
 	UINT8 *colormap;
@@ -684,20 +700,39 @@ void R_DrawSplat_NPO2_8 (void)
 	colormap = ds_colormap;
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 
-	while (count-- && dest <= deststop)
-	{
-		fixed_t x = (xposition >> FRACBITS);
-		fixed_t y = (yposition >> FRACBITS);
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
 
-		// Carefully align all of my Friends.
-		if (x < 0)
-			x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
-		if (y < 0)
-			y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
 
-		x %= ds_flatwidth;
-		y %= ds_flatheight;
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
 
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
 		val = source[((y * ds_flatwidth) + x)];
 		if (val != TRANSPARENTPIXEL)
 			*dest = colormap[val];
@@ -715,6 +750,8 @@ void R_DrawTranslucentSplat_NPO2_8 (void)
 	fixed_t xposition;
 	fixed_t yposition;
 	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
 
 	UINT8 *source;
 	UINT8 *colormap;
@@ -731,20 +768,39 @@ void R_DrawTranslucentSplat_NPO2_8 (void)
 	colormap = ds_colormap;
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 
-	while (count-- && dest <= deststop)
-	{
-		fixed_t x = (xposition >> FRACBITS);
-		fixed_t y = (yposition >> FRACBITS);
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
 
-		// Carefully align all of my Friends.
-		if (x < 0)
-			x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
-		if (y < 0)
-			y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
 
-		x %= ds_flatwidth;
-		y %= ds_flatheight;
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
 
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
 		val = source[((y * ds_flatwidth) + x)];
 		if (val != TRANSPARENTPIXEL)
 			*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
@@ -754,6 +810,446 @@ void R_DrawTranslucentSplat_NPO2_8 (void)
 	}
 }
 
+/**	\brief The R_DrawFloorSprite_NPO2_8 function
+	Just like R_DrawSplat_NPO2_8, but for floor sprites.
+*/
+void R_DrawFloorSprite_NPO2_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
+
+	UINT16 *source;
+	UINT8 *translation;
+	UINT8 *colormap;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
+
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
+
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
+
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
+		val = source[((y * ds_flatwidth) + x)];
+		if (val & 0xFF00)
+			*dest = colormap[translation[val & 0xFF]];
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTranslucentFloorSprite_NPO2_8 function
+	Just like R_DrawFloorSprite_NPO2_8, but is translucent!
+*/
+void R_DrawTranslucentFloorSprite_NPO2_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
+
+	UINT16 *source;
+	UINT8 *translation;
+	UINT8 *colormap;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
+
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
+
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
+
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
+		val = source[((y * ds_flatwidth) + x)];
+		if (val & 0xFF00)
+			*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTiltedFloorSprite_NPO2_8 function
+	Draws a tilted floor sprite.
+*/
+void R_DrawTiltedFloorSprite_NPO2_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//x1 = 0;
+	width++;
+
+	while (width >= SPANSIZE)
+	{
+		iz += izstep;
+		uz += uzstep;
+		vz += vzstep;
+
+		endz = 1.f/iz;
+		endu = uz*endz;
+		endv = vz*endz;
+		stepu = (INT64)((endu - startu) * INVSPAN);
+		stepv = (INT64)((endv - startv) * INVSPAN);
+		u = (INT64)(startu) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			// Lactozilla: Non-powers-of-two
+			fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+			fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			val = source[((y * ds_flatwidth) + x)];
+			if (val & 0xFF00)
+				*dest = colormap[translation[val & 0xFF]];
+			dest++;
+
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			// Lactozilla: Non-powers-of-two
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = colormap[translation[val & 0xFF]];
+			}
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->x * left;
+
+			endz = 1.f/iz;
+			endu = uz*endz;
+			endv = vz*endz;
+			left = 1.f/left;
+			stepu = (INT64)((endu - startu) * left);
+			stepv = (INT64)((endv - startv) * left);
+			u = (INT64)(startu) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				// Lactozilla: Non-powers-of-two
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = colormap[translation[val & 0xFF]];
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
+/**	\brief The R_DrawTiltedTranslucentFloorSprite_NPO2_8 function
+	Draws a tilted, translucent, floor sprite.
+*/
+void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//x1 = 0;
+	width++;
+
+	while (width >= SPANSIZE)
+	{
+		iz += izstep;
+		uz += uzstep;
+		vz += vzstep;
+
+		endz = 1.f/iz;
+		endu = uz*endz;
+		endv = vz*endz;
+		stepu = (INT64)((endu - startu) * INVSPAN);
+		stepv = (INT64)((endv - startv) * INVSPAN);
+		u = (INT64)(startu) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			// Lactozilla: Non-powers-of-two
+			fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+			fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			val = source[((y * ds_flatwidth) + x)];
+			if (val & 0xFF00)
+				*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+			dest++;
+
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			// Lactozilla: Non-powers-of-two
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+			}
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->x * left;
+
+			endz = 1.f/iz;
+			endu = uz*endz;
+			endv = vz*endz;
+			left = 1.f/left;
+			stepu = (INT64)((endu - startu) * left);
+			stepv = (INT64)((endv - startv) * left);
+			u = (INT64)(startu) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				// Lactozilla: Non-powers-of-two
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
 /**	\brief The R_DrawTranslucentSpan_NPO2_8 function
 	Draws the actual span with translucency.
 */
@@ -762,6 +1258,8 @@ void R_DrawTranslucentSpan_NPO2_8 (void)
 	fixed_t xposition;
 	fixed_t yposition;
 	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
 
 	UINT8 *source;
 	UINT8 *colormap;
@@ -778,20 +1276,39 @@ void R_DrawTranslucentSpan_NPO2_8 (void)
 	colormap = ds_colormap;
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 
-	while (count-- && dest <= deststop)
-	{
-		fixed_t x = (xposition >> FRACBITS);
-		fixed_t y = (yposition >> FRACBITS);
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
 
-		// Carefully align all of my Friends.
-		if (x < 0)
-			x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
-		if (y < 0)
-			y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
 
-		x %= ds_flatwidth;
-		y %= ds_flatheight;
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
 
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
 		val = ((y * ds_flatwidth) + x);
 		*dest = *(ds_transmap + (colormap[source[val]] << 8) + *dest);
 		dest++;
@@ -800,12 +1317,13 @@ void R_DrawTranslucentSpan_NPO2_8 (void)
 	}
 }
 
-#ifndef NOWATER
 void R_DrawTranslucentWaterSpan_NPO2_8(void)
 {
 	fixed_t xposition;
 	fixed_t yposition;
 	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
 
 	UINT8 *source;
 	UINT8 *colormap;
@@ -823,20 +1341,39 @@ void R_DrawTranslucentWaterSpan_NPO2_8(void)
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 
-	while (count-- && dest <= deststop)
-	{
-		fixed_t x = (xposition >> FRACBITS);
-		fixed_t y = (yposition >> FRACBITS);
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
 
-		// Carefully align all of my Friends.
-		if (x < 0)
-			x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
-		if (y < 0)
-			y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
 
-		x %= ds_flatwidth;
-		y %= ds_flatheight;
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
 
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
 		*dest++ = colormap[*(ds_transmap + (source[((y * ds_flatwidth) + x)] << 8) + *dsrc++)];
 		xposition += xstep;
 		yposition += ystep;
@@ -918,9 +1455,6 @@ void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -1039,4 +1573,3 @@ void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void)
 	}
 #endif
 }
-#endif // NOWATER
diff --git a/src/r_local.h b/src/r_local.h
index 48044118d386aba9a67d8e46cf541c0484ccb54c..4ccb766cf72c903a952b75c85d2a1f313c42ebd5 100644
--- a/src/r_local.h
+++ b/src/r_local.h
@@ -31,6 +31,7 @@
 #include "r_plane.h"
 #include "r_sky.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_things.h"
 #include "r_draw.h"
 
diff --git a/src/r_main.c b/src/r_main.c
index ed45514ce44fff72e5ae7d725e7be046ed391550..ee6ad2f86d4f650252b7bf4e2993b1df224d88bb 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -35,7 +35,7 @@
 #include "m_random.h" // quake camera shake
 #include "r_portal.h"
 #include "r_main.h"
-#include "i_system.h" // I_GetTimeMicros
+#include "i_system.h" // I_GetPreciseTime
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -102,20 +102,22 @@ lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];
 extracolormap_t *extra_colormaps = NULL;
 
 // Render stats
-int rs_prevframetime = 0;
-int rs_rendercalltime = 0;
-int rs_swaptime = 0;
+precise_t ps_prevframetime = 0;
+precise_t ps_rendercalltime = 0;
+precise_t ps_uitime = 0;
+precise_t ps_swaptime = 0;
 
-int rs_bsptime = 0;
+precise_t ps_bsptime = 0;
 
-int rs_sw_portaltime = 0;
-int rs_sw_planetime = 0;
-int rs_sw_maskedtime = 0;
+precise_t ps_sw_spritecliptime = 0;
+precise_t ps_sw_portaltime = 0;
+precise_t ps_sw_planetime = 0;
+precise_t ps_sw_maskedtime = 0;
 
-int rs_numbspcalls = 0;
-int rs_numsprites = 0;
-int rs_numdrawnodes = 0;
-int rs_numpolyobjects = 0;
+int ps_numbspcalls = 0;
+int ps_numsprites = 0;
+int ps_numdrawnodes = 0;
+int ps_numpolyobjects = 0;
 
 static CV_PossibleValue_t drawdist_cons_t[] = {
 	{256, "256"},	{512, "512"},	{768, "768"},
@@ -143,31 +145,32 @@ static void FlipCam2_OnChange(void);
 void SendWeaponPref(void);
 void SendWeaponPref2(void);
 
-consvar_t cv_tailspickup = {"tailspickup", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_chasecam = {"chasecam", "On", CV_CALL, CV_OnOff, ChaseCam_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_chasecam2 = {"chasecam2", "On", CV_CALL, CV_OnOff, ChaseCam2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_flipcam = {"flipcam", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_flipcam2 = {"flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_shadow = {"shadow", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_skybox = {"skybox", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_allowmlook = {"allowmlook", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_showhud = {"showhud", "Yes", CV_CALL,  CV_YesNo, R_SetViewSize, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_translucenthud = {"translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_translucency = {"translucency", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_drawdist = {"drawdist", "Infinite", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_drawdist_nights = {"drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_drawdist_precip = {"drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-//consvar_t cv_precipdensity = {"precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_fov = {"fov", "90", CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_tailspickup = CVAR_INIT ("tailspickup", "On", CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_chasecam = CVAR_INIT ("chasecam", "On", CV_CALL, CV_OnOff, ChaseCam_OnChange);
+consvar_t cv_chasecam2 = CVAR_INIT ("chasecam2", "On", CV_CALL, CV_OnOff, ChaseCam2_OnChange);
+consvar_t cv_flipcam = CVAR_INIT ("flipcam", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam_OnChange);
+consvar_t cv_flipcam2 = CVAR_INIT ("flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam2_OnChange);
+
+consvar_t cv_shadow = CVAR_INIT ("shadow", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_skybox = CVAR_INIT ("skybox", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_ffloorclip = CVAR_INIT ("ffloorclip", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_allowmlook = CVAR_INIT ("allowmlook", "Yes", CV_NETVAR, CV_YesNo, NULL);
+consvar_t cv_showhud = CVAR_INIT ("showhud", "Yes", CV_CALL,  CV_YesNo, R_SetViewSize);
+consvar_t cv_translucenthud = CVAR_INIT ("translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL);
+
+consvar_t cv_translucency = CVAR_INIT ("translucency", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_drawdist = CVAR_INIT ("drawdist", "Infinite", CV_SAVE, drawdist_cons_t, NULL);
+consvar_t cv_drawdist_nights = CVAR_INIT ("drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL);
+consvar_t cv_drawdist_precip = CVAR_INIT ("drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL);
+//consvar_t cv_precipdensity = CVAR_INIT ("precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL);
+consvar_t cv_fov = CVAR_INIT ("fov", "90", CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange);
 
 // Okay, whoever said homremoval causes a performance hit should be shot.
-consvar_t cv_homremoval = {"homremoval", "No", CV_SAVE, homremoval_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_homremoval = CVAR_INIT ("homremoval", "No", CV_SAVE, homremoval_cons_t, NULL);
 
-consvar_t cv_maxportals = {"maxportals", "2", CV_SAVE, maxportals_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_maxportals = CVAR_INIT ("maxportals", "2", CV_SAVE, maxportals_cons_t, NULL);
 
-consvar_t cv_renderstats = {"renderstats", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_renderstats = CVAR_INIT ("renderstats", "Off", 0, CV_OnOff, NULL);
 
 void SplitScreen_OnChange(void)
 {
@@ -320,6 +323,24 @@ angle_t R_PointToAngle(fixed_t x, fixed_t y)
 		0;
 }
 
+// This version uses 64-bit variables to avoid overflows with large values.
+// Currently used only by OpenGL rendering.
+angle_t R_PointToAngle64(INT64 x, INT64 y)
+{
+	return (y -= viewy, (x -= viewx) || y) ?
+	x >= 0 ?
+	y >= 0 ?
+		(x > y) ? tantoangle[SlopeDivEx(y,x)] :                            // octant 0
+		ANGLE_90-tantoangle[SlopeDivEx(x,y)] :                               // octant 1
+		x > (y = -y) ? 0-tantoangle[SlopeDivEx(y,x)] :                    // octant 8
+		ANGLE_270+tantoangle[SlopeDivEx(x,y)] :                              // octant 7
+		y >= 0 ? (x = -x) > y ? ANGLE_180-tantoangle[SlopeDivEx(y,x)] :  // octant 3
+		ANGLE_90 + tantoangle[SlopeDivEx(x,y)] :                             // octant 2
+		(x = -x) > (y = -y) ? ANGLE_180+tantoangle[SlopeDivEx(y,x)] :    // octant 4
+		ANGLE_270-tantoangle[SlopeDivEx(x,y)] :                              // octant 5
+		0;
+}
+
 angle_t R_PointToAngle2(fixed_t pviewx, fixed_t pviewy, fixed_t x, fixed_t y)
 {
 	return (y -= pviewy, (x -= pviewx) || y) ?
@@ -940,6 +961,16 @@ void R_ExecuteSetViewSize(void)
 			dy = FixedMul(abs(dy), fovtan);
 			yslopetab[i] = FixedDiv(centerx*FRACUNIT, dy);
 		}
+
+		if (ds_su)
+			Z_Free(ds_su);
+		if (ds_sv)
+			Z_Free(ds_sv);
+		if (ds_sz)
+			Z_Free(ds_sz);
+
+		ds_su = ds_sv = ds_sz = NULL;
+		ds_sup = ds_svp = ds_szp = NULL;
 	}
 
 	memset(scalelight, 0xFF, sizeof(scalelight));
@@ -992,8 +1023,8 @@ void R_Init(void)
 	//I_OutputMsg("\nR_InitLightTables");
 	R_InitLightTables();
 
-	//I_OutputMsg("\nR_InitTranslationTables\n");
-	R_InitTranslationTables();
+	//I_OutputMsg("\nR_InitTranslucencyTables\n");
+	R_InitTranslucencyTables();
 
 	R_InitDrawNodes();
 
@@ -1068,15 +1099,22 @@ subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y)
 // recalc necessary stuff for mouseaiming
 // slopes are already calculated for the full possible view (which is 4*viewheight).
 // 18/08/18: (No it's actually 16*viewheight, thanks Jimita for finding this out)
-static void R_SetupFreelook(void)
+static void R_SetupFreelook(player_t *player, boolean skybox)
 {
 	INT32 dy = 0;
 
+#ifndef HWRENDER
+	(void)player;
+	(void)skybox;
+#endif
+
 	// clip it in the case we are looking a hardware 90 degrees full aiming
 	// (lmps, network and use F12...)
 	if (rendermode == render_soft
 #ifdef HWRENDER
-		|| cv_grshearing.value
+		|| (rendermode == render_opengl
+			&& (cv_glshearing.value == 1
+			|| (cv_glshearing.value == 2 && R_IsViewpointThirdPerson(player, skybox))))
 #endif
 		)
 	{
@@ -1211,7 +1249,7 @@ void R_SetupFrame(player_t *player)
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
 
-	R_SetupFreelook();
+	R_SetupFreelook(player, false);
 }
 
 void R_SkyboxFrame(player_t *player)
@@ -1348,7 +1386,7 @@ void R_SkyboxFrame(player_t *player)
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
 
-	R_SetupFreelook();
+	R_SetupFreelook(player, true);
 }
 
 boolean R_ViewpointHasChasecam(player_t *player)
@@ -1486,9 +1524,6 @@ void R_RenderPlayerView(player_t *player)
 	}
 	R_ClearDrawSegs();
 	R_ClearSprites();
-#ifdef FLOORSPLATS
-	R_ClearVisibleFloorSplats();
-#endif
 	Portal_InitList();
 
 	// check for new console commands.
@@ -1503,11 +1538,11 @@ void R_RenderPlayerView(player_t *player)
 	mytotal = 0;
 	ProfZeroTimer();
 #endif
-	rs_numbspcalls = rs_numpolyobjects = rs_numdrawnodes = 0;
-	rs_bsptime = I_GetTimeMicros();
+	ps_numbspcalls = ps_numpolyobjects = ps_numdrawnodes = 0;
+	ps_bsptime = I_GetPreciseTime();
 	R_RenderBSPNode((INT32)viewworld->numnodes - 1);
-	rs_bsptime = I_GetTimeMicros() - rs_bsptime;
-	rs_numsprites = visspritecount;
+	ps_bsptime = I_GetPreciseTime() - ps_bsptime;
+	ps_numsprites = visspritecount;
 #ifdef TIMING
 	RDMSR(0x10, &mycount);
 	mytotal += mycount; // 64bit add
@@ -1517,7 +1552,9 @@ void R_RenderPlayerView(player_t *player)
 //profile stuff ---------------------------------------------------------
 	Mask_Post(&masks[nummasks - 1]);
 
+	ps_sw_spritecliptime = I_GetPreciseTime();
 	R_ClipSprites(drawsegs, NULL);
+	ps_sw_spritecliptime = I_GetPreciseTime() - ps_sw_spritecliptime;
 
 
 	// Add skybox portals caused by sky visplanes.
@@ -1525,7 +1562,7 @@ void R_RenderPlayerView(player_t *player)
 		Portal_AddSkyboxPortals();
 
 	// Portal rendering. Hijacks the BSP traversal.
-	rs_sw_portaltime = I_GetTimeMicros();
+	ps_sw_portaltime = I_GetPreciseTime();
 	if (portal_base)
 	{
 		portal_t *portal;
@@ -1565,44 +1602,21 @@ void R_RenderPlayerView(player_t *player)
 			Portal_Remove(portal);
 		}
 	}
-	rs_sw_portaltime = I_GetTimeMicros() - rs_sw_portaltime;
+	ps_sw_portaltime = I_GetPreciseTime() - ps_sw_portaltime;
 
-	rs_sw_planetime = I_GetTimeMicros();
+	ps_sw_planetime = I_GetPreciseTime();
 	R_DrawPlanes();
-#ifdef FLOORSPLATS
-	R_DrawVisibleFloorSplats();
-#endif
-	rs_sw_planetime = I_GetTimeMicros() - rs_sw_planetime;
+	ps_sw_planetime = I_GetPreciseTime() - ps_sw_planetime;
 
 	// draw mid texture and sprite
 	// And now 3D floors/sides!
-	rs_sw_maskedtime = I_GetTimeMicros();
+	ps_sw_maskedtime = I_GetPreciseTime();
 	R_DrawMasked(masks, nummasks);
-	rs_sw_maskedtime = I_GetTimeMicros() - rs_sw_maskedtime;
+	ps_sw_maskedtime = I_GetPreciseTime() - ps_sw_maskedtime;
 
 	free(masks);
 }
 
-// Lactozilla: Renderer switching
-#ifdef HWRENDER
-void R_InitHardwareMode(void)
-{
-	HWR_AddSessionCommands();
-	HWR_Switch();
-	HWR_LoadTextures(numtextures);
-	if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction))
-		HWR_SetupLevel();
-}
-#endif
-
-void R_ReloadHUDGraphics(void)
-{
-	CONS_Debug(DBG_RENDER, "R_ReloadHUDGraphics()...\n");
-	ST_LoadGraphics();
-	HU_LoadGraphics();
-	ST_ReloadSkinFaceGraphics();
-}
-
 // =========================================================================
 //                    ENGINE COMMANDS & VARS
 // =========================================================================
@@ -1631,6 +1645,7 @@ void R_RegisterEngineStuff(void)
 
 	CV_RegisterVar(&cv_shadow);
 	CV_RegisterVar(&cv_skybox);
+	CV_RegisterVar(&cv_ffloorclip);
 
 	CV_RegisterVar(&cv_cam_dist);
 	CV_RegisterVar(&cv_cam_still);
diff --git a/src/r_main.h b/src/r_main.h
index f2c78237d8acab80acd1247e88b8ec36c393bcd1..2b774712b5fa98da601196c4787dbcf4815274be 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -17,6 +17,7 @@
 #include "d_player.h"
 #include "r_data.h"
 #include "p_world.h"
+#include "r_textures.h"
 
 //
 // POV related.
@@ -64,6 +65,7 @@ extern lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];
 INT32 R_PointOnSide(fixed_t x, fixed_t y, node_t *node);
 INT32 R_PointOnSegSide(fixed_t x, fixed_t y, seg_t *line);
 angle_t R_PointToAngle(fixed_t x, fixed_t y);
+angle_t R_PointToAngle64(INT64 x, INT64 y);
 angle_t R_PointToAngle2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1);
 angle_t R_PointToAngleEx(INT32 x2, INT32 y2, INT32 x1, INT32 y1);
 fixed_t R_PointToDist(fixed_t x, fixed_t y);
@@ -79,22 +81,22 @@ boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixe
 
 // Render stats
 
-extern consvar_t cv_renderstats;
+extern precise_t ps_prevframetime;// time when previous frame was rendered
+extern precise_t ps_rendercalltime;
+extern precise_t ps_uitime;
+extern precise_t ps_swaptime;
 
-extern int rs_prevframetime;// time when previous frame was rendered
-extern int rs_rendercalltime;
-extern int rs_swaptime;
+extern precise_t ps_bsptime;
 
-extern int rs_bsptime;
+extern precise_t ps_sw_spritecliptime;
+extern precise_t ps_sw_portaltime;
+extern precise_t ps_sw_planetime;
+extern precise_t ps_sw_maskedtime;
 
-extern int rs_sw_portaltime;
-extern int rs_sw_planetime;
-extern int rs_sw_maskedtime;
-
-extern int rs_numbspcalls;
-extern int rs_numsprites;
-extern int rs_numdrawnodes;
-extern int rs_numpolyobjects;
+extern int ps_numbspcalls;
+extern int ps_numsprites;
+extern int ps_numdrawnodes;
+extern int ps_numpolyobjects;
 
 //
 // REFRESH - the actual rendering functions.
@@ -106,6 +108,7 @@ extern consvar_t cv_chasecam, cv_chasecam2;
 extern consvar_t cv_flipcam, cv_flipcam2;
 
 extern consvar_t cv_shadow;
+extern consvar_t cv_ffloorclip;
 extern consvar_t cv_translucency;
 extern consvar_t cv_drawdist, cv_drawdist_nights, cv_drawdist_precip;
 extern consvar_t cv_fov;
@@ -114,10 +117,6 @@ extern consvar_t cv_tailspickup;
 
 // Called by startup code.
 void R_Init(void);
-#ifdef HWRENDER
-void R_InitHardwareMode(void);
-#endif
-void R_ReloadHUDGraphics(void);
 
 void R_CheckViewMorph(void);
 void R_ApplyViewMorph(void);
diff --git a/src/r_patch.c b/src/r_patch.c
index ad4b3329a439daf4a5605fb3e8569b2fdc0bd680..1a08d1892d5e13d6b36ebfe59945bce9a58b75f6 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -1,9 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Copyright (C) 2005-2009 by Andrey "entryway" Budko.
-// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
-// Copyright (C) 2019-2020 by Sonic Team Junior.
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -12,1469 +9,152 @@
 /// \file  r_patch.c
 /// \brief Patch generation.
 
-#include "byteptr.h"
-#include "dehacked.h"
-#include "i_video.h"
-#include "r_data.h"
-#include "r_draw.h"
+#include "doomdef.h"
 #include "r_patch.h"
-#include "r_things.h"
+#include "r_picformats.h"
+#include "r_defs.h"
 #include "z_zone.h"
-#include "w_wad.h"
 
 #ifdef HWRENDER
 #include "hardware/hw_glob.h"
 #endif
 
-#ifdef HAVE_PNG
-
-#ifndef _MSC_VER
-#ifndef _LARGEFILE64_SOURCE
-#define _LARGEFILE64_SOURCE
-#endif
-#endif
-
-#ifndef _LFS64_LARGEFILE
-#define _LFS64_LARGEFILE
-#endif
-
-#ifndef _FILE_OFFSET_BITS
-#define _FILE_OFFSET_BITS 0
-#endif
-
-#include "png.h"
-#ifndef PNG_READ_SUPPORTED
-#undef HAVE_PNG
-#endif
-#endif
-
-static unsigned char imgbuf[1<<26];
-
 //
-// R_CheckIfPatch
+// Creates a patch.
+// Assumes a PU_PATCH zone memory tag and no user, but can always be set later
 //
-// Returns true if the lump is a valid patch.
-//
-boolean R_CheckIfPatch(lumpnum_t lump)
-{
-	size_t size;
-	INT16 width, height;
-	patch_t *patch;
-	boolean result;
-
-	size = W_LumpLength(lump);
-
-	// minimum length of a valid Doom patch
-	if (size < 13)
-		return false;
-
-	patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
-
-	width = SHORT(patch->width);
-	height = SHORT(patch->height);
-
-	result = (height > 0 && height <= 16384 && width > 0 && width <= 16384 && width < (INT16)(size / 4));
-
-	if (result)
-	{
-		// The dimensions seem like they might be valid for a patch, so
-		// check the column directory for extra security. All columns
-		// must begin after the column directory, and none of them must
-		// point past the end of the patch.
-		INT16 x;
-
-		for (x = 0; x < width; x++)
-		{
-			UINT32 ofs = LONG(patch->columnofs[x]);
-
-			// Need one byte for an empty column (but there's patches that don't know that!)
-			if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
-			{
-				result = false;
-				break;
-			}
-		}
-	}
-
-	return result;
-}
 
-//
-// R_TextureToFlat
-//
-// Convert a texture to a flat.
-//
-void R_TextureToFlat(size_t tex, UINT8 *flat)
+patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest)
 {
-	texture_t *texture = textures[tex];
-
-	fixed_t col, ofs;
-	column_t *column;
-	UINT8 *desttop, *dest, *deststop;
-	UINT8 *source;
-
-	// yea
-	R_CheckTextureCache(tex);
+	patch_t *patch = (dest == NULL) ? Z_Calloc(sizeof(patch_t), PU_PATCH, NULL) : (patch_t *)(dest);
 
-	desttop = flat;
-	deststop = desttop + (texture->width * texture->height);
-
-	for (col = 0; col < texture->width; col++, desttop++)
+	if (source)
 	{
-		// no post_t info
-		if (!texture->holes)
-		{
-			column = (column_t *)(R_GetColumn(tex, col));
-			source = (UINT8 *)(column);
-			dest = desttop;
-			for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
-			{
-				if (source[ofs] != TRANSPARENTPIXEL)
-					*dest = source[ofs];
-				dest += texture->width;
-			}
-		}
-		else
-		{
-			INT32 topdelta, prevdelta = -1;
-			column = (column_t *)((UINT8 *)R_GetColumn(tex, col) - 3);
-			while (column->topdelta != 0xff)
-			{
-				topdelta = column->topdelta;
-				if (topdelta <= prevdelta)
-					topdelta += prevdelta;
-				prevdelta = topdelta;
-
-				dest = desttop + (topdelta * texture->width);
-				source = (UINT8 *)column + 3;
-				for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
-				{
-					if (source[ofs] != TRANSPARENTPIXEL)
-						*dest = source[ofs];
-					dest += texture->width;
-				}
-				column = (column_t *)((UINT8 *)column + column->length + 4);
-			}
-		}
-	}
-}
-
-//
-// R_PatchToFlat
-//
-// Convert a patch to a flat.
-//
-void R_PatchToFlat(patch_t *patch, UINT8 *flat)
-{
-	fixed_t col, ofs;
-	column_t *column;
-	UINT8 *desttop, *dest, *deststop;
-	UINT8 *source;
-
-	desttop = flat;
-	deststop = desttop + (SHORT(patch->width) * SHORT(patch->height));
+		INT32 col, colsize;
+		size_t size = sizeof(INT32) * SHORT(source->width);
+		size_t offs = (sizeof(INT16) * 4) + size;
 
-	for (col = 0; col < SHORT(patch->width); col++, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[col]));
+		patch->width      = SHORT(source->width);
+		patch->height     = SHORT(source->height);
+		patch->leftoffset = SHORT(source->leftoffset);
+		patch->topoffset  = SHORT(source->topoffset);
+		patch->columnofs  = Z_Calloc(size, PU_PATCH_DATA, NULL);
 
-		while (column->topdelta != 0xff)
+		for (col = 0; col < source->width; col++)
 		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-
-			dest = desttop + (topdelta * SHORT(patch->width));
-			source = (UINT8 *)(column) + 3;
-			for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
-			{
-				*dest = source[ofs];
-				dest += SHORT(patch->width);
-			}
-			column = (column_t *)((UINT8 *)column + column->length + 4);
+			// This makes the column offsets relative to the column data itself,
+			// instead of the entire patch data
+			patch->columnofs[col] = LONG(source->columnofs[col]) - offs;
 		}
-	}
-}
 
-//
-// R_PatchToMaskedFlat
-//
-// Convert a patch to a masked flat.
-// Now, what is a "masked" flat anyway?
-// It means the flat uses two bytes to store image data.
-// The upper byte is used to store the transparent pixel,
-// and the lower byte stores a palette index.
-//
-void R_PatchToMaskedFlat(patch_t *patch, UINT16 *raw, boolean flip)
-{
-	fixed_t col, ofs;
-	column_t *column;
-	UINT16 *desttop, *dest, *deststop;
-	UINT8 *source;
+		if (!srcsize)
+			I_Error("Patch_Create: no source size!");
 
-	desttop = raw;
-	deststop = desttop + (SHORT(patch->width) * SHORT(patch->height));
+		colsize = (INT32)(srcsize) - (INT32)offs;
+		if (colsize <= 0)
+			I_Error("Patch_Create: no column data!");
 
-	for (col = 0; col < SHORT(patch->width); col++, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[flip ? (patch->width-1-col) : col]));
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			dest = desttop + (topdelta * SHORT(patch->width));
-			source = (UINT8 *)(column) + 3;
-			for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
-			{
-				*dest = source[ofs];
-				dest += SHORT(patch->width);
-			}
-			column = (column_t *)((UINT8 *)column + column->length + 4);
-		}
+		patch->columns = Z_Calloc(colsize, PU_PATCH_DATA, NULL);
+		M_Memcpy(patch->columns, ((UINT8 *)source + LONG(source->columnofs[0])), colsize);
 	}
-}
-
-//
-// R_FlatToPatch
-//
-// Convert a flat to a patch.
-//
-patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize, boolean transparency)
-{
-	UINT32 x, y;
-	UINT8 *img;
-	UINT8 *imgptr = imgbuf;
-	UINT8 *colpointers, *startofspan;
-	size_t size = 0;
-
-	if (!raw)
-		return NULL;
-
-	// Write image size and offset
-	WRITEINT16(imgptr, width);
-	WRITEINT16(imgptr, height);
-	WRITEINT16(imgptr, leftoffset);
-	WRITEINT16(imgptr, topoffset);
-
-	// Leave placeholder to column pointers
-	colpointers = imgptr;
-	imgptr += width*4;
-
-	// Write columns
-	for (x = 0; x < width; x++)
-	{
-		int lastStartY = 0;
-		int spanSize = 0;
-		startofspan = NULL;
-
-		// Write column pointer
-		WRITEINT32(colpointers, imgptr - imgbuf);
-
-		// Write pixels
-		for (y = 0; y < height; y++)
-		{
-			UINT8 paletteIndex = raw[((y * width) + x)];
-			boolean opaque = transparency ? (paletteIndex != TRANSPARENTPIXEL) : true;
 
-			// End span if we have a transparent pixel
-			if (!opaque)
-			{
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
-				startofspan = NULL;
-				continue;
-			}
-
-			// Start new column if we need to
-			if (!startofspan || spanSize == 255)
-			{
-				int writeY = y;
-
-				// If we reached the span size limit, finish the previous span
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
-
-				if (y > 254)
-				{
-					// Make sure we're aligned to 254
-					if (lastStartY < 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						lastStartY = 254;
-					}
-
-					// Write stopgap empty spans if needed
-					writeY = y - lastStartY;
-
-					while (writeY > 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						writeY -= 254;
-					}
-				}
-
-				startofspan = imgptr;
-				WRITEUINT8(imgptr, writeY);
-				imgptr += 2;
-				spanSize = 0;
-
-				lastStartY = y;
-			}
-
-			// Write the pixel
-			WRITEUINT8(imgptr, paletteIndex);
-			spanSize++;
-			startofspan[1] = spanSize;
-		}
-
-		if (startofspan)
-			WRITEUINT8(imgptr, 0);
-
-		WRITEUINT8(imgptr, 0xFF);
-	}
-
-	size = imgptr-imgbuf;
-	img = Z_Malloc(size, PU_STATIC, NULL);
-	memcpy(img, imgbuf, size);
-
-	Z_Free(raw);
-
-	if (destsize != NULL)
-		*destsize = size;
-	return (patch_t *)img;
+	return patch;
 }
 
 //
-// R_MaskedFlatToPatch
+// Frees a patch from memory.
 //
-// Convert a masked flat to a patch.
-// Explanation of "masked" flats in R_PatchToMaskedFlat.
-//
-patch_t *R_MaskedFlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize)
-{
-	UINT32 x, y;
-	UINT8 *img;
-	UINT8 *imgptr = imgbuf;
-	UINT8 *colpointers, *startofspan;
-	size_t size = 0;
-
-	if (!raw)
-		return NULL;
-
-	// Write image size and offset
-	WRITEINT16(imgptr, width);
-	WRITEINT16(imgptr, height);
-	WRITEINT16(imgptr, leftoffset);
-	WRITEINT16(imgptr, topoffset);
-
-	// Leave placeholder to column pointers
-	colpointers = imgptr;
-	imgptr += width*4;
-
-	// Write columns
-	for (x = 0; x < width; x++)
-	{
-		int lastStartY = 0;
-		int spanSize = 0;
-		startofspan = NULL;
-
-		// Write column pointer
-		WRITEINT32(colpointers, imgptr - imgbuf);
 
-		// Write pixels
-		for (y = 0; y < height; y++)
-		{
-			UINT16 pixel = raw[((y * width) + x)];
-			UINT8 paletteIndex = (pixel & 0xFF);
-			UINT8 opaque = (pixel != 0xFF00); // If 1, we have a pixel
-
-			// End span if we have a transparent pixel
-			if (!opaque)
-			{
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
-				startofspan = NULL;
-				continue;
-			}
-
-			// Start new column if we need to
-			if (!startofspan || spanSize == 255)
-			{
-				int writeY = y;
-
-				// If we reached the span size limit, finish the previous span
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
-
-				if (y > 254)
-				{
-					// Make sure we're aligned to 254
-					if (lastStartY < 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						lastStartY = 254;
-					}
-
-					// Write stopgap empty spans if needed
-					writeY = y - lastStartY;
-
-					while (writeY > 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						writeY -= 254;
-					}
-				}
-
-				startofspan = imgptr;
-				WRITEUINT8(imgptr, writeY);
-				imgptr += 2;
-				spanSize = 0;
-
-				lastStartY = y;
-			}
-
-			// Write the pixel
-			WRITEUINT8(imgptr, paletteIndex);
-			spanSize++;
-			startofspan[1] = spanSize;
-		}
-
-		if (startofspan)
-			WRITEUINT8(imgptr, 0);
-
-		WRITEUINT8(imgptr, 0xFF);
-	}
-
-	size = imgptr-imgbuf;
-	img = Z_Malloc(size, PU_STATIC, NULL);
-	memcpy(img, imgbuf, size);
-
-	if (destsize != NULL)
-		*destsize = size;
-	return (patch_t *)img;
-}
-
-//
-// R_IsLumpPNG
-//
-// Returns true if the lump is a valid PNG.
-//
-boolean R_IsLumpPNG(const UINT8 *d, size_t s)
+static void Patch_FreeData(patch_t *patch)
 {
-	if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
-		return false;
-	// Check for PNG file signature using memcmp
-	// As it may be faster on CPUs with slow unaligned memory access
-	// Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature
-	return (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0);
-}
-
-#ifndef NO_PNG_LUMPS
-#ifdef HAVE_PNG
-
-/*#if PNG_LIBPNG_VER_DLLNUM < 14
-typedef PNG_CONST png_byte *png_const_bytep;
-#endif*/
-typedef struct
-{
-	const UINT8 *buffer;
-	UINT32 size;
-	UINT32 position;
-} png_io_t;
-
-static void PNG_IOReader(png_structp png_ptr, png_bytep data, png_size_t length)
-{
-	png_io_t *f = png_get_io_ptr(png_ptr);
-	if (length > (f->size - f->position))
-		png_error(png_ptr, "PNG_IOReader: buffer overrun");
-	memcpy(data, f->buffer + f->position, length);
-	f->position += length;
-}
-
-typedef struct
-{
-	char name[4];
-	void *data;
-	size_t size;
-} png_chunk_t;
-
-static png_byte *chunkname = NULL;
-static png_chunk_t chunk;
-
-static int PNG_ChunkReader(png_structp png_ptr, png_unknown_chunkp chonk)
-{
-	(void)png_ptr;
-	if (!memcmp(chonk->name, chunkname, 4))
-	{
-		memcpy(chunk.name, chonk->name, 4);
-		chunk.size = chonk->size;
-		chunk.data = Z_Malloc(chunk.size, PU_STATIC, NULL);
-		memcpy(chunk.data, chonk->data, chunk.size);
-		return 1;
-	}
-	return 0;
-}
-
-static void PNG_error(png_structp PNG, png_const_charp pngtext)
-{
-	CONS_Debug(DBG_RENDER, "libpng error at %p: %s", PNG, pngtext);
-	//I_Error("libpng error at %p: %s", PNG, pngtext);
-}
-
-static void PNG_warn(png_structp PNG, png_const_charp pngtext)
-{
-	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
-}
-
-static png_bytep *PNG_Read(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
-{
-	png_structp png_ptr;
-	png_infop png_info_ptr;
-	png_uint_32 width, height;
-	int bit_depth, color_type;
-	png_uint_32 y;
-#ifdef PNG_SETJMP_SUPPORTED
-#ifdef USE_FAR_KEYWORD
-	jmp_buf jmpbuf;
-#endif
-#endif
-
-	png_io_t png_io;
-	png_bytep *row_pointers;
-
-	png_byte grAb_chunk[5] = {'g', 'r', 'A', 'b', (png_byte)'\0'};
-	png_voidp *user_chunk_ptr;
-
-	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
-	if (!png_ptr)
-	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
-		return NULL;
-	}
-
-	png_info_ptr = png_create_info_struct(png_ptr);
-	if (!png_info_ptr)
-	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
-		png_destroy_read_struct(&png_ptr, NULL, NULL);
-		return NULL;
-	}
-
-#ifdef USE_FAR_KEYWORD
-	if (setjmp(jmpbuf))
-#else
-	if (setjmp(png_jmpbuf(png_ptr)))
-#endif
-	{
-		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
-		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
-		return NULL;
-	}
-#ifdef USE_FAR_KEYWORD
-	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
-#endif
-
-	// set our own read function
-	png_io.buffer = png;
-	png_io.size = size;
-	png_io.position = 0;
-	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
-
-	memset(&chunk, 0x00, sizeof(png_chunk_t));
-	chunkname = grAb_chunk; // I want to read a grAb chunk
-
-	user_chunk_ptr = png_get_user_chunk_ptr(png_ptr);
-	png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr, PNG_ChunkReader);
-	png_set_keep_unknown_chunks(png_ptr, 2, chunkname, 1);
+	INT32 i;
 
-#ifdef PNG_SET_USER_LIMITS_SUPPORTED
-	png_set_user_limits(png_ptr, 2048, 2048);
+#ifdef HWRENDER
+	if (patch->hardware)
+		HWR_FreeTexture(patch);
 #endif
 
-	png_read_info(png_ptr, png_info_ptr);
-	png_get_IHDR(png_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
-
-	if (bit_depth == 16)
-		png_set_strip_16(png_ptr);
-
-	if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
-		png_set_gray_to_rgb(png_ptr);
-	else if (color_type == PNG_COLOR_TYPE_PALETTE)
-		png_set_palette_to_rgb(png_ptr);
-
-	if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_tRNS))
-		png_set_tRNS_to_alpha(png_ptr);
-	else if (color_type != PNG_COLOR_TYPE_RGB_ALPHA && color_type != PNG_COLOR_TYPE_GRAY_ALPHA)
+	for (i = 0; i < 4; i++)
 	{
-#if PNG_LIBPNG_VER < 10207
-		png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
-#else
-		png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
-#endif
+		if (patch->flats[i])
+			Z_Free(patch->flats[i]);
 	}
 
-	png_read_update_info(png_ptr, png_info_ptr);
-
-	// Read the image
-	row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
-	for (y = 0; y < height; y++)
-		row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, png_info_ptr));
-	png_read_image(png_ptr, row_pointers);
-
-	// Read grAB chunk
-	if ((topoffset || leftoffset) && (chunk.data != NULL))
-	{
-		INT32 *offsets = (INT32 *)chunk.data;
-		// read left offset
-		if (leftoffset != NULL)
-			*leftoffset = (INT16)BIGENDIAN_LONG(*offsets);
-		offsets++;
-		// read top offset
-		if (topoffset != NULL)
-			*topoffset = (INT16)BIGENDIAN_LONG(*offsets);
-	}
-
-	// bye
-	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
-	if (chunk.data)
-		Z_Free(chunk.data);
-
-	*w = (INT32)width;
-	*h = (INT32)height;
-	return row_pointers;
-}
-
-// Convert a PNG to a raw image.
-static UINT8 *PNG_RawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
-{
-	UINT8 *flat;
-	png_uint_32 x, y;
-	png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, size);
-	png_uint_32 width = *w, height = *h;
-
-	if (!row_pointers)
-		I_Error("PNG_RawConvert: conversion failed");
-
-	// Convert the image to 8bpp
-	flat = Z_Malloc(width * height, PU_LEVEL, NULL);
-	memset(flat, TRANSPARENTPIXEL, width * height);
-	for (y = 0; y < height; y++)
+#ifdef ROTSPRITE
+	if (patch->rotated)
 	{
-		png_bytep row = row_pointers[y];
-		for (x = 0; x < width; x++)
-		{
-			png_bytep px = &(row[x * 4]);
-			if ((UINT8)px[3])
-				flat[((y * width) + x)] = NearestColor((UINT8)px[0], (UINT8)px[1], (UINT8)px[2]);
-		}
-	}
-	free(row_pointers);
+		rotsprite_t *rotsprite = patch->rotated;
 
-	return flat;
-}
-
-// Convert a PNG with transparency to a raw image.
-static UINT16 *PNG_MaskedRawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
-{
-	UINT16 *flat;
-	png_uint_32 x, y;
-	png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, size);
-	png_uint_32 width = *w, height = *h;
-	size_t flatsize, i;
-
-	if (!row_pointers)
-		I_Error("PNG_MaskedRawConvert: conversion failed");
-
-	// Convert the image to 16bpp
-	flatsize = (width * height);
-	flat = Z_Malloc(flatsize * sizeof(UINT16), PU_LEVEL, NULL);
-
-	// can't memset here
-	for (i = 0; i < flatsize; i++)
-		flat[i] = 0xFF00;
-
-	for (y = 0; y < height; y++)
-	{
-		png_bytep row = row_pointers[y];
-		for (x = 0; x < width; x++)
+		for (i = 0; i < rotsprite->angles; i++)
 		{
-			png_bytep px = &(row[x * 4]);
-			if ((UINT8)px[3])
-				flat[((y * width) + x)] = NearestColor((UINT8)px[0], (UINT8)px[1], (UINT8)px[2]);
+			if (rotsprite->patches[i])
+				Patch_Free(rotsprite->patches[i]);
 		}
-	}
-	free(row_pointers);
-
-	return flat;
-}
 
-//
-// R_PNGToFlat
-//
-// Convert a PNG to a flat.
-//
-UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size)
-{
-	return PNG_RawConvert(png, width, height, NULL, NULL, size);
-}
-
-//
-// R_PNGToPatch
-//
-// Convert a PNG to a patch.
-//
-patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize)
-{
-	UINT16 width, height;
-	INT16 topoffset = 0, leftoffset = 0;
-	UINT16 *raw = PNG_MaskedRawConvert(png, &width, &height, &topoffset, &leftoffset, size);
-
-	if (!raw)
-		I_Error("R_PNGToPatch: conversion failed");
-
-	return R_MaskedFlatToPatch(raw, width, height, leftoffset, topoffset, destsize);
-}
-
-//
-// R_PNGDimensions
-//
-// Get the dimensions of a PNG file.
-//
-boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
-{
-	png_structp png_ptr;
-	png_infop png_info_ptr;
-	png_uint_32 w, h;
-	int bit_depth, color_type;
-#ifdef PNG_SETJMP_SUPPORTED
-#ifdef USE_FAR_KEYWORD
-	jmp_buf jmpbuf;
-#endif
-#endif
-
-	png_io_t png_io;
-
-	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
-		PNG_error, PNG_warn);
-	if (!png_ptr)
-	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
-		return false;
-	}
-
-	png_info_ptr = png_create_info_struct(png_ptr);
-	if (!png_info_ptr)
-	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
-		png_destroy_read_struct(&png_ptr, NULL, NULL);
-		return false;
-	}
-
-#ifdef USE_FAR_KEYWORD
-	if (setjmp(jmpbuf))
-#else
-	if (setjmp(png_jmpbuf(png_ptr)))
-#endif
-	{
-		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
-		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
-		return false;
+		Z_Free(rotsprite->patches);
+		Z_Free(rotsprite);
 	}
-#ifdef USE_FAR_KEYWORD
-	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
 #endif
 
-	// set our own read function
-	png_io.buffer = png;
-	png_io.size = size;
-	png_io.position = 0;
-	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
-
-#ifdef PNG_SET_USER_LIMITS_SUPPORTED
-	png_set_user_limits(png_ptr, 2048, 2048);
-#endif
-
-	png_read_info(png_ptr, png_info_ptr);
-
-	png_get_IHDR(png_ptr, png_info_ptr, &w, &h, &bit_depth, &color_type,
-	 NULL, NULL, NULL);
-
-	// okay done. stop.
-	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
-
-	*width = (INT32)w;
-	*height = (INT32)h;
-	return true;
+	if (patch->columnofs)
+		Z_Free(patch->columnofs);
+	if (patch->columns)
+		Z_Free(patch->columns);
 }
-#endif
-#endif
 
-//
-// R_ParseSpriteInfoFrame
-//
-// Parse a SPRTINFO frame.
-//
-static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
+void Patch_Free(patch_t *patch)
 {
-	char *sprinfoToken;
-	size_t sprinfoTokenLength;
-	char *frameChar = NULL;
-	UINT8 frameFrame = 0xFF;
-	INT16 frameXPivot = 0;
-	INT16 frameYPivot = 0;
-	rotaxis_t frameRotAxis = 0;
-
-	// Sprite identifier
-	sprinfoToken = M_GetToken(NULL);
-	if (sprinfoToken == NULL)
-	{
-		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be");
-	}
-	sprinfoTokenLength = strlen(sprinfoToken);
-	if (sprinfoTokenLength != 1)
-	{
-		I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken);
-	}
-	else
-		frameChar = sprinfoToken;
-
-	frameFrame = R_Char2Frame(frameChar[0]);
-	Z_Free(sprinfoToken);
-
-	// Left Curly Brace
-	sprinfoToken = M_GetToken(NULL);
-	if (sprinfoToken == NULL)
-		I_Error("Error parsing SPRTINFO lump: Missing sprite info");
-	else
-	{
-		if (strcmp(sprinfoToken,"{")==0)
-		{
-			Z_Free(sprinfoToken);
-			sprinfoToken = M_GetToken(NULL);
-			if (sprinfoToken == NULL)
-			{
-				I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be");
-			}
-			while (strcmp(sprinfoToken,"}")!=0)
-			{
-				if (stricmp(sprinfoToken, "XPIVOT")==0)
-				{
-					Z_Free(sprinfoToken);
-					sprinfoToken = M_GetToken(NULL);
-					frameXPivot = atoi(sprinfoToken);
-				}
-				else if (stricmp(sprinfoToken, "YPIVOT")==0)
-				{
-					Z_Free(sprinfoToken);
-					sprinfoToken = M_GetToken(NULL);
-					frameYPivot = atoi(sprinfoToken);
-				}
-				else if (stricmp(sprinfoToken, "ROTAXIS")==0)
-				{
-					Z_Free(sprinfoToken);
-					sprinfoToken = M_GetToken(NULL);
-					if ((stricmp(sprinfoToken, "X")==0) || (stricmp(sprinfoToken, "XAXIS")==0) || (stricmp(sprinfoToken, "ROLL")==0))
-						frameRotAxis = ROTAXIS_X;
-					else if ((stricmp(sprinfoToken, "Y")==0) || (stricmp(sprinfoToken, "YAXIS")==0) || (stricmp(sprinfoToken, "PITCH")==0))
-						frameRotAxis = ROTAXIS_Y;
-					else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0) || (stricmp(sprinfoToken, "YAW")==0))
-						frameRotAxis = ROTAXIS_Z;
-				}
-				Z_Free(sprinfoToken);
-
-				sprinfoToken = M_GetToken(NULL);
-				if (sprinfoToken == NULL)
-				{
-					I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be");
-				}
-			}
-		}
-		Z_Free(sprinfoToken);
-	}
-
-	// set fields
-	info->pivot[frameFrame].x = frameXPivot;
-	info->pivot[frameFrame].y = frameYPivot;
-	info->pivot[frameFrame].rotaxis = frameRotAxis;
+	Patch_FreeData(patch);
+	Z_Free(patch);
 }
 
 //
-// R_ParseSpriteInfo
-//
-// Parse a SPRTINFO lump.
+// Frees patches with a tag range.
 //
-static void R_ParseSpriteInfo(boolean spr2)
-{
-	spriteinfo_t *info;
-	char *sprinfoToken;
-	size_t sprinfoTokenLength;
-	char newSpriteName[5]; // no longer dynamically allocated
-	spritenum_t sprnum = NUMSPRITES;
-	playersprite_t spr2num = NUMPLAYERSPRITES;
-	INT32 i;
-	INT32 skinnumbers[MAXSKINS];
-	INT32 foundskins = 0;
-
-	// Sprite name
-	sprinfoToken = M_GetToken(NULL);
-	if (sprinfoToken == NULL)
-	{
-		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be");
-	}
-	sprinfoTokenLength = strlen(sprinfoToken);
-	if (sprinfoTokenLength != 4)
-	{
-		I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long",sprinfoToken);
-	}
-	else
-	{
-		memset(&newSpriteName, 0, 5);
-		M_Memcpy(newSpriteName, sprinfoToken, sprinfoTokenLength);
-		// ^^ we've confirmed that the token is == 4 characters so it will never overflow a 5 byte char buffer
-		strupr(newSpriteName); // Just do this now so we don't have to worry about it
-	}
-	Z_Free(sprinfoToken);
 
-	if (!spr2)
-	{
-		for (i = 0; i <= NUMSPRITES; i++)
-		{
-			if (i == NUMSPRITES)
-				I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName);
-			if (!memcmp(newSpriteName,sprnames[i],4))
-			{
-				sprnum = i;
-				break;
-			}
-		}
-	}
-	else
-	{
-		for (i = 0; i <= NUMPLAYERSPRITES; i++)
-		{
-			if (i == NUMPLAYERSPRITES)
-				I_Error("Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"", newSpriteName);
-			if (!memcmp(newSpriteName,spr2names[i],4))
-			{
-				spr2num = i;
-				break;
-			}
-		}
-	}
-
-	// allocate a spriteinfo
-	info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL);
-	info->available = true;
-
-#ifdef ROTSPRITE
-	if ((sprites != NULL) && (!spr2))
-		R_FreeSingleRotSprite(&sprites[sprnum]);
-#endif
-
-	// Left Curly Brace
-	sprinfoToken = M_GetToken(NULL);
-	if (sprinfoToken == NULL)
-	{
-		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName);
-	}
-	if (strcmp(sprinfoToken,"{")==0)
-	{
-		Z_Free(sprinfoToken);
-		sprinfoToken = M_GetToken(NULL);
-		if (sprinfoToken == NULL)
-		{
-			I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName);
-		}
-		while (strcmp(sprinfoToken,"}")!=0)
-		{
-			if (stricmp(sprinfoToken, "SKIN")==0)
-			{
-				INT32 skinnum;
-				char *skinName = NULL;
-				if (!spr2)
-					I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition");
-
-				Z_Free(sprinfoToken);
-
-				// Skin name
-				sprinfoToken = M_GetToken(NULL);
-				if (sprinfoToken == NULL)
-				{
-					I_Error("Error parsing SPRTINFO lump: Unexpected end of file where skin frame should be");
-				}
-
-				// copy skin name yada yada
-				sprinfoTokenLength = strlen(sprinfoToken);
-				skinName = (char *)Z_Malloc((sprinfoTokenLength+1)*sizeof(char),PU_STATIC,NULL);
-				M_Memcpy(skinName,sprinfoToken,sprinfoTokenLength*sizeof(char));
-				skinName[sprinfoTokenLength] = '\0';
-				strlwr(skinName);
-				Z_Free(sprinfoToken);
-
-				skinnum = R_SkinAvailable(skinName);
-				if (skinnum == -1)
-					I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName);
-
-				skinnumbers[foundskins] = skinnum;
-				foundskins++;
-			}
-			else if (stricmp(sprinfoToken, "FRAME")==0)
-			{
-				R_ParseSpriteInfoFrame(info);
-				Z_Free(sprinfoToken);
-				if (spr2)
-				{
-					if (!foundskins)
-						I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition");
-					for (i = 0; i < foundskins; i++)
-					{
-						size_t skinnum = skinnumbers[i];
-						skin_t *skin = &skins[skinnum];
-						spriteinfo_t *sprinfo = skin->sprinfo;
-#ifdef ROTSPRITE
-						R_FreeSkinRotSprite(skinnum);
-#endif
-						M_Memcpy(&sprinfo[spr2num], info, sizeof(spriteinfo_t));
-					}
-				}
-				else
-					M_Memcpy(&spriteinfo[sprnum], info, sizeof(spriteinfo_t));
-			}
-			else
-			{
-				I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName);
-			}
-
-			sprinfoToken = M_GetToken(NULL);
-			if (sprinfoToken == NULL)
-			{
-				I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName);
-			}
-		}
-	}
-	else
-	{
-		I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken);
-	}
-	Z_Free(sprinfoToken);
-	Z_Free(info);
-}
-
-//
-// R_ParseSPRTINFOLump
-//
-// Read a SPRTINFO lump.
-//
-void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum)
+static boolean Patch_FreeTagsCallback(void *mem)
 {
-	char *sprinfoLump;
-	size_t sprinfoLumpLength;
-	char *sprinfoText;
-	char *sprinfoToken;
-
-	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
-	// need to make a space of memory where I can ensure that it will terminate
-	// correctly. Start by loading the relevant data from the WAD.
-	sprinfoLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
-	// If that didn't exist, we have nothing to do here.
-	if (sprinfoLump == NULL) return;
-	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
-	sprinfoLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
-	sprinfoText = (char *)Z_Malloc((sprinfoLumpLength+1)*sizeof(char),PU_STATIC,NULL);
-	// Now move the contents of the lump into this new location.
-	memmove(sprinfoText,sprinfoLump,sprinfoLumpLength);
-	// Make damn well sure the last character in our new memory location is \0.
-	sprinfoText[sprinfoLumpLength] = '\0';
-	// Finally, free up the memory from the first data load, because we really
-	// don't need it.
-	Z_Free(sprinfoLump);
-
-	sprinfoToken = M_GetToken(sprinfoText);
-	while (sprinfoToken != NULL)
-	{
-		if (!stricmp(sprinfoToken, "SPRITE"))
-			R_ParseSpriteInfo(false);
-		else if (!stricmp(sprinfoToken, "SPRITE2"))
-			R_ParseSpriteInfo(true);
-		else
-			I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken);
-		Z_Free(sprinfoToken);
-		sprinfoToken = M_GetToken(NULL);
-	}
-	Z_Free((void *)sprinfoText);
+	patch_t *patch = (patch_t *)mem;
+	Patch_FreeData(patch);
+	return true;
 }
 
-//
-// R_LoadSpriteInfoLumps
-//
-// Load and read every SPRTINFO lump from the specified file.
-//
-void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
+void Patch_FreeTags(INT32 lowtag, INT32 hightag)
 {
-	lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
-	UINT16 i;
-	char *name;
-
-	for (i = 0; i < numlumps; i++, lumpinfo++)
-	{
-		name = lumpinfo->name;
-		// Load SPRTINFO and SPR_ lumps as SpriteInfo
-		if (!memcmp(name, "SPRTINFO", 8) || !memcmp(name, "SPR_", 4))
-			R_ParseSPRTINFOLump(wadnum, i);
-	}
+	Z_IterateTags(lowtag, hightag, Patch_FreeTagsCallback);
 }
 
-static UINT16 GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip)
+void Patch_GenerateFlat(patch_t *patch, pictureflags_t flags)
 {
-	fixed_t ofs;
-	column_t *column;
-	UINT8 *source;
-
-	if (x >= 0 && x < SHORT(patch->width))
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[flip ? (patch->width-1-x) : x]));
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (UINT8 *)(column) + 3;
-			for (ofs = 0; ofs < column->length; ofs++)
-			{
-				if ((topdelta + ofs) == y)
-					return source[ofs];
-			}
-			column = (column_t *)((UINT8 *)column + column->length + 4);
-		}
-	}
-
-	return 0xFF00;
+	UINT8 flip = (flags & (PICFLAGS_XFLIP | PICFLAGS_YFLIP));
+	if (patch->flats[flip] == NULL)
+		patch->flats[flip] = Picture_Convert(PICFMT_PATCH, patch, PICFMT_FLAT16, 0, NULL, 0, 0, 0, 0, flags);
 }
 
-#ifdef ROTSPRITE
-//
-// R_GetRollAngle
-//
-// Angles precalculated in R_InitSprites.
-//
-fixed_t rollcosang[ROTANGLES];
-fixed_t rollsinang[ROTANGLES];
-INT32 R_GetRollAngle(angle_t rollangle)
-{
-	INT32 ra = AngleFixed(rollangle)>>FRACBITS;
-#if (ROTANGDIFF > 1)
-	ra += (ROTANGDIFF/2);
-#endif
-	ra /= ROTANGDIFF;
-	ra %= ROTANGLES;
-	return ra;
-}
-
-//
-// R_CacheRotSprite
-//
-// Create a rotated sprite.
-//
-void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip)
-{
-	UINT32 i;
-	INT32 angle;
-	patch_t *patch;
-	patch_t *newpatch;
-	UINT16 *rawdst;
-	size_t size;
-	INT32 bflip = (flip != 0x00);
-
-#define SPRITE_XCENTER (leftoffset)
-#define SPRITE_YCENTER (height / 2)
-#define ROTSPRITE_XCENTER (newwidth / 2)
-#define ROTSPRITE_YCENTER (newheight / 2)
-
-	if (!(sprframe->rotsprite.cached & (1<<rot)))
-	{
-		INT32 dx, dy;
-		INT32 px, py;
-		INT32 width, height, leftoffset;
-		fixed_t ca, sa;
-		lumpnum_t lump = sprframe->lumppat[rot];
-#ifndef NO_PNG_LUMPS
-		size_t lumplength;
-#endif
-
-		if (lump == LUMPERROR)
-			return;
-
-		patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
-#ifndef NO_PNG_LUMPS
-		lumplength = W_LumpLength(lump);
-
-		if (R_IsLumpPNG((UINT8 *)patch, lumplength))
-			patch = R_PNGToPatch((UINT8 *)patch, lumplength, NULL);
-		else
-#endif
-		// Because there's something wrong with SPR_DFLM, I guess
-		if (!R_CheckIfPatch(lump))
-			return;
-
-		width = SHORT(patch->width);
-		height = SHORT(patch->height);
-		leftoffset = SHORT(patch->leftoffset);
-
-		// rotation pivot
-		px = SPRITE_XCENTER;
-		py = SPRITE_YCENTER;
-
-		// get correct sprite info for sprite
-		if (sprinfo == NULL)
-			sprinfo = &spriteinfo[sprnum];
-		if (sprinfo->available)
-		{
-			px = sprinfo->pivot[frame].x;
-			py = sprinfo->pivot[frame].y;
-		}
-		if (bflip)
-		{
-			px = width - px;
-			leftoffset = width - leftoffset;
-		}
-
-		// Don't cache angle = 0
-		for (angle = 1; angle < ROTANGLES; angle++)
-		{
-			INT32 newwidth, newheight;
-
-			ca = rollcosang[angle];
-			sa = rollsinang[angle];
-
-			// Find the dimensions of the rotated patch.
-			{
-				INT32 w1 = abs(FixedMul(width << FRACBITS, ca) - FixedMul(height << FRACBITS, sa));
-				INT32 w2 = abs(FixedMul(-(width << FRACBITS), ca) - FixedMul(height << FRACBITS, sa));
-				INT32 h1 = abs(FixedMul(width << FRACBITS, sa) + FixedMul(height << FRACBITS, ca));
-				INT32 h2 = abs(FixedMul(-(width << FRACBITS), sa) + FixedMul(height << FRACBITS, ca));
-				w1 = FixedInt(FixedCeil(w1 + (FRACUNIT/2)));
-				w2 = FixedInt(FixedCeil(w2 + (FRACUNIT/2)));
-				h1 = FixedInt(FixedCeil(h1 + (FRACUNIT/2)));
-				h2 = FixedInt(FixedCeil(h2 + (FRACUNIT/2)));
-				newwidth = max(width, max(w1, w2));
-				newheight = max(height, max(h1, h2));
-			}
-
-			// check boundaries
-			{
-				fixed_t top[2][2];
-				fixed_t bottom[2][2];
-
-				top[0][0] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				top[0][1] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-				top[1][0] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				top[1][1] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-
-				bottom[0][0] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				bottom[0][1] = -FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-				bottom[1][0] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				bottom[1][1] = -FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-
-				top[0][0] >>= FRACBITS;
-				top[0][1] >>= FRACBITS;
-				top[1][0] >>= FRACBITS;
-				top[1][1] >>= FRACBITS;
-
-				bottom[0][0] >>= FRACBITS;
-				bottom[0][1] >>= FRACBITS;
-				bottom[1][0] >>= FRACBITS;
-				bottom[1][1] >>= FRACBITS;
-
-#define BOUNDARYWCHECK(b) (b[0] < 0 || b[0] >= width)
-#define BOUNDARYHCHECK(b) (b[1] < 0 || b[1] >= height)
-#define BOUNDARYADJUST(x) x *= 2
-				// top left/right
-				if (BOUNDARYWCHECK(top[0]) || BOUNDARYWCHECK(top[1]))
-					BOUNDARYADJUST(newwidth);
-				// bottom left/right
-				else if (BOUNDARYWCHECK(bottom[0]) || BOUNDARYWCHECK(bottom[1]))
-					BOUNDARYADJUST(newwidth);
-				// top left/right
-				if (BOUNDARYHCHECK(top[0]) || BOUNDARYHCHECK(top[1]))
-					BOUNDARYADJUST(newheight);
-				// bottom left/right
-				else if (BOUNDARYHCHECK(bottom[0]) || BOUNDARYHCHECK(bottom[1]))
-					BOUNDARYADJUST(newheight);
-#undef BOUNDARYWCHECK
-#undef BOUNDARYHCHECK
-#undef BOUNDARYADJUST
-			}
-
-			// Draw the rotated sprite to a temporary buffer.
-			size = (newwidth * newheight);
-			if (!size)
-				size = (width * height);
-
-			rawdst = Z_Malloc(size * sizeof(UINT16), PU_STATIC, NULL);
-			for (i = 0; i < size; i++)
-				rawdst[i] = 0xFF00;
-
-			for (dy = 0; dy < newheight; dy++)
-			{
-				for (dx = 0; dx < newwidth; dx++)
-				{
-					INT32 x = (dx-ROTSPRITE_XCENTER) << FRACBITS;
-					INT32 y = (dy-ROTSPRITE_YCENTER) << FRACBITS;
-					INT32 sx = FixedMul(x, ca) + FixedMul(y, sa) + (px << FRACBITS);
-					INT32 sy = -FixedMul(x, sa) + FixedMul(y, ca) + (py << FRACBITS);
-					sx >>= FRACBITS;
-					sy >>= FRACBITS;
-					if (sx >= 0 && sy >= 0 && sx < width && sy < height)
-						rawdst[(dy*newwidth)+dx] = GetPatchPixel(patch, sx, sy, bflip);
-				}
-			}
-
-			// make patch
-			newpatch = R_MaskedFlatToPatch(rawdst, newwidth, newheight, 0, 0, &size);
-			{
-				newpatch->leftoffset = (newpatch->width / 2) + (leftoffset - px);
-				newpatch->topoffset = (newpatch->height / 2) + (SHORT(patch->topoffset) - py);
-			}
-
-			//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
-			if (rendermode != render_none) // not for psprite
-				newpatch->topoffset += FEETADJUST>>FRACBITS;
-
-			// P_PrecacheLevel
-			if (devparm) spritememory += size;
-
-			// convert everything to little-endian, for big-endian support
-			newpatch->width = SHORT(newpatch->width);
-			newpatch->height = SHORT(newpatch->height);
-			newpatch->leftoffset = SHORT(newpatch->leftoffset);
-			newpatch->topoffset = SHORT(newpatch->topoffset);
-
 #ifdef HWRENDER
-			if (rendermode == render_opengl)
-			{
-				GLPatch_t *grPatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
-				grPatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
-				grPatch->rawpatch = newpatch;
-				sprframe->rotsprite.patch[rot][angle] = (patch_t *)grPatch;
-				HWR_MakePatch(newpatch, grPatch, grPatch->mipmap, false);
-			}
-			else
-#endif // HWRENDER
-				sprframe->rotsprite.patch[rot][angle] = newpatch;
-
-			// free rotated image data
-			Z_Free(rawdst);
-		}
-
-		// This rotation is cached now
-		sprframe->rotsprite.cached |= (1<<rot);
-
-		// free image data
-		Z_Free(patch);
-	}
-#undef SPRITE_XCENTER
-#undef SPRITE_YCENTER
-#undef ROTSPRITE_XCENTER
-#undef ROTSPRITE_YCENTER
-}
-
-//
-// R_FreeSingleRotSprite
 //
-// Free sprite rotation data from memory, for a single spritedef.
+// Allocates a hardware patch.
 //
-void R_FreeSingleRotSprite(spritedef_t *spritedef)
-{
-	UINT8 frame;
-	INT32 rot, ang;
 
-	for (frame = 0; frame < spritedef->numframes; frame++)
-	{
-		spriteframe_t *sprframe = &spritedef->spriteframes[frame];
-		for (rot = 0; rot < 16; rot++)
-		{
-			if (sprframe->rotsprite.cached & (1<<rot))
-			{
-				for (ang = 0; ang < ROTANGLES; ang++)
-				{
-					patch_t *rotsprite = sprframe->rotsprite.patch[rot][ang];
-					if (rotsprite)
-					{
-#ifdef HWRENDER
-						if (rendermode == render_opengl)
-						{
-							GLPatch_t *grPatch = (GLPatch_t *)rotsprite;
-							if (grPatch->rawpatch)
-							{
-								Z_Free(grPatch->rawpatch);
-								grPatch->rawpatch = NULL;
-							}
-							if (grPatch->mipmap)
-							{
-								if (grPatch->mipmap->grInfo.data)
-								{
-									Z_Free(grPatch->mipmap->grInfo.data);
-									grPatch->mipmap->grInfo.data = NULL;
-								}
-								Z_Free(grPatch->mipmap);
-								grPatch->mipmap = NULL;
-							}
-						}
-#endif
-						Z_Free(rotsprite);
-					}
-				}
-				sprframe->rotsprite.cached &= ~(1<<rot);
-			}
-		}
-	}
-}
-
-//
-// R_FreeSkinRotSprite
-//
-// Free sprite rotation data from memory, for a skin.
-// Calls R_FreeSingleRotSprite.
-//
-void R_FreeSkinRotSprite(size_t skinnum)
+void *Patch_AllocateHardwarePatch(patch_t *patch)
 {
-	size_t i;
-	skin_t *skin = &skins[skinnum];
-	spritedef_t *skinsprites = skin->sprites;
-	for (i = 0; i < NUMPLAYERSPRITES*2; i++)
+	if (!patch->hardware)
 	{
-		R_FreeSingleRotSprite(skinsprites);
-		skinsprites++;
+		GLPatch_t *grPatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, &patch->hardware);
+		grPatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, &grPatch->mipmap);
 	}
+	return (void *)(patch->hardware);
 }
 
 //
-// R_FreeAllRotSprite
+// Creates a hardware patch.
 //
-// Free ALL sprite rotation data from memory.
-//
-void R_FreeAllRotSprite(void)
+
+void *Patch_CreateGL(patch_t *patch)
 {
-	INT32 i;
-	size_t s;
-	for (s = 0; s < numsprites; s++)
-		R_FreeSingleRotSprite(&sprites[s]);
-	for (i = 0; i < numskins; ++i)
-		R_FreeSkinRotSprite(i);
+	GLPatch_t *grPatch = (GLPatch_t *)Patch_AllocateHardwarePatch(patch);
+	if (!grPatch->mipmap->data) // Run HWR_MakePatch in all cases, to recalculate some things
+		HWR_MakePatch(patch, grPatch, grPatch->mipmap, false);
+	return grPatch;
 }
-#endif
+#endif // HWRENDER
diff --git a/src/r_patch.h b/src/r_patch.h
index a2db6320ca82822f4bfc5648ab8556eaa7ada088..32bcb3909efe057af98d54cd151f56414c71deb1 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -1,8 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
-// Copyright (C) 2019-2020 by Sonic Team Junior.
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -15,60 +13,32 @@
 #define __R_PATCH__
 
 #include "r_defs.h"
+#include "r_picformats.h"
 #include "doomdef.h"
 
-// Structs
-typedef enum
-{
-	ROTAXIS_X, // Roll (the default)
-	ROTAXIS_Y, // Pitch
-	ROTAXIS_Z  // Yaw
-} rotaxis_t;
+// Patch functions
+patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest);
+void Patch_Free(patch_t *patch);
 
-typedef struct
-{
-	INT32 x, y;
-	rotaxis_t rotaxis;
-} spriteframepivot_t;
+#define Patch_FreeTag(tagnum) Patch_FreeTags(tagnum, tagnum)
+void Patch_FreeTags(INT32 lowtag, INT32 hightag);
 
-typedef struct
-{
-	spriteframepivot_t pivot[64];
-	boolean available;
-} spriteinfo_t;
+void Patch_GenerateFlat(patch_t *patch, pictureflags_t flags);
 
-// Conversions between patches / flats / textures...
-boolean R_CheckIfPatch(lumpnum_t lump);
-void R_TextureToFlat(size_t tex, UINT8 *flat);
-void R_PatchToFlat(patch_t *patch, UINT8 *flat);
-void R_PatchToMaskedFlat(patch_t *patch, UINT16 *raw, boolean flip);
-patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize, boolean transparency);
-patch_t *R_MaskedFlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize);
-
-// Portable Network Graphics
-boolean R_IsLumpPNG(const UINT8 *d, size_t s);
-#define W_ThrowPNGError(lumpname, wadfilename) I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .png - please convert to either Doom or Flat (raw) image format.", lumpname, wadfilename); // Fears Of LJ Sonic
-
-#ifndef NO_PNG_LUMPS
-UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size);
-patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize);
-boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size);
+#ifdef HWRENDER
+void *Patch_AllocateHardwarePatch(patch_t *patch);
+void *Patch_CreateGL(patch_t *patch);
 #endif
 
-// SpriteInfo
-extern spriteinfo_t spriteinfo[NUMSPRITES];
-void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
-void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
-
-// Sprite rotation
 #ifdef ROTSPRITE
+void Patch_Rotate(patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip);
+patch_t *Patch_GetRotated(patch_t *patch, INT32 angle, boolean flip);
+patch_t *Patch_GetRotatedSprite(
+	spriteframe_t *sprite,
+	size_t frame, size_t spriteangle,
+	boolean flip, boolean adjustfeet,
+	void *info, INT32 rotationangle);
 INT32 R_GetRollAngle(angle_t rollangle);
-void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip);
-void R_FreeSingleRotSprite(spritedef_t *spritedef);
-void R_FreeSkinRotSprite(size_t skinnum);
-extern fixed_t rollcosang[ROTANGLES];
-extern fixed_t rollsinang[ROTANGLES];
-void R_FreeAllRotSprite(void);
 #endif
 
 #endif // __R_PATCH__
diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c
new file mode 100644
index 0000000000000000000000000000000000000000..123c4eef229a20fa554094bf44a2cc3853e72dc8
--- /dev/null
+++ b/src/r_patchrotation.c
@@ -0,0 +1,273 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+//
+// 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_patchrotation.c
+/// \brief Patch rotation.
+
+#include "r_patchrotation.h"
+#include "r_things.h" // FEETADJUST
+#include "z_zone.h"
+#include "w_wad.h"
+
+#ifdef ROTSPRITE
+fixed_t rollcosang[ROTANGLES];
+fixed_t rollsinang[ROTANGLES];
+
+INT32 R_GetRollAngle(angle_t rollangle)
+{
+	INT32 ra = AngleFixed(rollangle)>>FRACBITS;
+#if (ROTANGDIFF > 1)
+	ra += (ROTANGDIFF/2);
+#endif
+	ra /= ROTANGDIFF;
+	ra %= ROTANGLES;
+	return ra;
+}
+
+patch_t *Patch_GetRotated(patch_t *patch, INT32 angle, boolean flip)
+{
+	rotsprite_t *rotsprite = patch->rotated;
+	if (rotsprite == NULL || angle < 1 || angle >= ROTANGLES)
+		return NULL;
+
+	if (flip)
+		angle += rotsprite->angles;
+
+	return rotsprite->patches[angle];
+}
+
+patch_t *Patch_GetRotatedSprite(
+	spriteframe_t *sprite,
+	size_t frame, size_t spriteangle,
+	boolean flip, boolean adjustfeet,
+	void *info, INT32 rotationangle)
+{
+	rotsprite_t *rotsprite;
+	spriteinfo_t *sprinfo = (spriteinfo_t *)info;
+	INT32 idx = rotationangle;
+	UINT8 type = (adjustfeet ? 1 : 0);
+
+	if (rotationangle < 1 || rotationangle >= ROTANGLES)
+		return NULL;
+
+	rotsprite = sprite->rotated[type][spriteangle];
+
+	if (rotsprite == NULL)
+	{
+		rotsprite = RotatedPatch_Create(ROTANGLES);
+		sprite->rotated[type][spriteangle] = rotsprite;
+	}
+
+	if (flip)
+		idx += rotsprite->angles;
+
+	if (rotsprite->patches[idx] == NULL)
+	{
+		patch_t *patch;
+		INT32 xpivot = 0, ypivot = 0;
+		lumpnum_t lump = sprite->lumppat[spriteangle];
+
+		if (lump == LUMPERROR)
+			return NULL;
+
+		patch = W_CachePatchNum(lump, PU_SPRITE);
+
+		if (sprinfo->available)
+		{
+			xpivot = sprinfo->pivot[frame].x;
+			ypivot = sprinfo->pivot[frame].y;
+		}
+		else
+		{
+			xpivot = patch->leftoffset;
+			ypivot = patch->height / 2;
+		}
+
+		RotatedPatch_DoRotation(rotsprite, patch, rotationangle, xpivot, ypivot, flip);
+
+		//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
+		if (adjustfeet)
+			((patch_t *)rotsprite->patches[idx])->topoffset += FEETADJUST>>FRACBITS;
+	}
+
+	return rotsprite->patches[idx];
+}
+
+void Patch_Rotate(patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip)
+{
+	if (patch->rotated == NULL)
+		patch->rotated = RotatedPatch_Create(ROTANGLES);
+	RotatedPatch_DoRotation(patch->rotated, patch, angle, xpivot, ypivot, flip);
+}
+
+rotsprite_t *RotatedPatch_Create(INT32 numangles)
+{
+	rotsprite_t *rotsprite = Z_Calloc(sizeof(rotsprite_t), PU_STATIC, NULL);
+	rotsprite->angles = numangles;
+	rotsprite->patches = Z_Calloc(rotsprite->angles * 2 * sizeof(void *), PU_STATIC, NULL);
+	return rotsprite;
+}
+
+static void RotatedPatch_CalculateDimensions(
+	INT32 width, INT32 height,
+	fixed_t ca, fixed_t sa,
+	INT32 *newwidth, INT32 *newheight)
+{
+	fixed_t fixedwidth = (width * FRACUNIT);
+	fixed_t fixedheight = (height * FRACUNIT);
+
+	fixed_t w1 = abs(FixedMul(fixedwidth, ca) - FixedMul(fixedheight, sa));
+	fixed_t w2 = abs(FixedMul(-fixedwidth, ca) - FixedMul(fixedheight, sa));
+	fixed_t h1 = abs(FixedMul(fixedwidth, sa) + FixedMul(fixedheight, ca));
+	fixed_t h2 = abs(FixedMul(-fixedwidth, sa) + FixedMul(fixedheight, ca));
+
+	w1 = FixedInt(FixedCeil(w1 + (FRACUNIT/2)));
+	w2 = FixedInt(FixedCeil(w2 + (FRACUNIT/2)));
+	h1 = FixedInt(FixedCeil(h1 + (FRACUNIT/2)));
+	h2 = FixedInt(FixedCeil(h2 + (FRACUNIT/2)));
+
+	*newwidth = max(width, max(w1, w2));
+	*newheight = max(height, max(h1, h2));
+}
+
+void RotatedPatch_DoRotation(rotsprite_t *rotsprite, patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip)
+{
+	patch_t *rotated;
+	UINT16 *rawdst, *rawconv;
+	size_t size;
+	pictureflags_t bflip = (flip) ? PICFLAGS_XFLIP : 0;
+
+	INT32 width = patch->width;
+	INT32 height = patch->height;
+	INT32 leftoffset = patch->leftoffset;
+	INT32 newwidth, newheight;
+
+	fixed_t ca = rollcosang[angle];
+	fixed_t sa = rollsinang[angle];
+	fixed_t xcenter, ycenter;
+	INT32 idx = angle;
+	INT32 x, y;
+	INT32 sx, sy;
+	INT32 dx, dy;
+	INT32 ox, oy;
+	INT32 minx, miny, maxx, maxy;
+
+	// Don't cache angle = 0
+	if (angle < 1 || angle >= ROTANGLES)
+		return;
+
+	if (flip)
+	{
+		idx += rotsprite->angles;
+		xpivot = width - xpivot;
+		leftoffset = width - leftoffset;
+	}
+
+	if (rotsprite->patches[idx])
+		return;
+
+	// Find the dimensions of the rotated patch.
+	RotatedPatch_CalculateDimensions(width, height, ca, sa, &newwidth, &newheight);
+
+	xcenter = (xpivot * FRACUNIT);
+	ycenter = (ypivot * FRACUNIT);
+
+	if (xpivot != width / 2 || ypivot != height / 2)
+	{
+		newwidth *= 2;
+		newheight *= 2;
+	}
+
+	minx = newwidth;
+	miny = newheight;
+	maxx = 0;
+	maxy = 0;
+
+	// Draw the rotated sprite to a temporary buffer.
+	size = (newwidth * newheight);
+	if (!size)
+		size = (width * height);
+	rawdst = Z_Calloc(size * sizeof(UINT16), PU_STATIC, NULL);
+
+	for (dy = 0; dy < newheight; dy++)
+	{
+		for (dx = 0; dx < newwidth; dx++)
+		{
+			x = (dx - (newwidth / 2)) * FRACUNIT;
+			y = (dy - (newheight / 2)) * FRACUNIT;
+			sx = FixedMul(x, ca) + FixedMul(y, sa) + xcenter;
+			sy = -FixedMul(x, sa) + FixedMul(y, ca) + ycenter;
+
+			sx >>= FRACBITS;
+			sy >>= FRACBITS;
+
+			if (sx >= 0 && sy >= 0 && sx < width && sy < height)
+			{
+				void *input = Picture_GetPatchPixel(patch, PICFMT_PATCH, sx, sy, bflip);
+				if (input != NULL)
+				{
+					rawdst[(dy * newwidth) + dx] = (0xFF00 | (*(UINT8 *)input));
+					if (dx < minx)
+						minx = dx;
+					if (dy < miny)
+						miny = dy;
+					if (dx > maxx)
+						maxx = dx;
+					if (dy > maxy)
+						maxy = dy;
+				}
+			}
+		}
+	}
+
+	ox = (newwidth / 2) + (leftoffset - xpivot);
+	oy = (newheight / 2) + (patch->topoffset - ypivot);
+	width = (maxx - minx);
+	height = (maxy - miny);
+
+	if ((unsigned)(width * height) != size)
+	{
+		UINT16 *src, *dest;
+
+		size = (width * height);
+		rawconv = Z_Calloc(size * sizeof(UINT16), PU_STATIC, NULL);
+
+		src = &rawdst[(miny * newwidth) + minx];
+		dest = rawconv;
+		dy = height;
+
+		while (dy--)
+		{
+			M_Memcpy(dest, src, width * sizeof(UINT16));
+			dest += width;
+			src += newwidth;
+		}
+
+		ox -= minx;
+		oy -= miny;
+
+		Z_Free(rawdst);
+	}
+	else
+	{
+		rawconv = rawdst;
+		width = newwidth;
+		height = newheight;
+	}
+
+	// make patch
+	rotated = (patch_t *)Picture_Convert(PICFMT_FLAT16, rawconv, PICFMT_PATCH, 0, NULL, width, height, 0, 0, 0);
+
+	Z_ChangeTag(rotated, PU_PATCH_ROTATED);
+	Z_SetUser(rotated, (void **)(&rotsprite->patches[idx]));
+	Z_Free(rawconv);
+
+	rotated->leftoffset = ox;
+	rotated->topoffset = oy;
+}
+#endif
diff --git a/src/r_patchrotation.h b/src/r_patchrotation.h
new file mode 100644
index 0000000000000000000000000000000000000000..2744f71d25380469b30b1fdcf8b5112578a2abd8
--- /dev/null
+++ b/src/r_patchrotation.h
@@ -0,0 +1,21 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+//
+// 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_patchrotation.h
+/// \brief Patch rotation.
+
+#include "r_patch.h"
+#include "r_picformats.h"
+
+#ifdef ROTSPRITE
+rotsprite_t *RotatedPatch_Create(INT32 numangles);
+void RotatedPatch_DoRotation(rotsprite_t *rotsprite, patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip);
+
+extern fixed_t rollcosang[ROTANGLES];
+extern fixed_t rollsinang[ROTANGLES];
+#endif
diff --git a/src/r_picformats.c b/src/r_picformats.c
new file mode 100644
index 0000000000000000000000000000000000000000..f87362c76ef895097b7e21329367c6b082fa705e
--- /dev/null
+++ b/src/r_picformats.c
@@ -0,0 +1,1699 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 2005-2009 by Andrey "entryway" Budko.
+// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2019-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_picformats.c
+/// \brief Picture generation.
+
+#include "byteptr.h"
+#include "dehacked.h"
+#include "i_video.h"
+#include "r_data.h"
+#include "r_patch.h"
+#include "r_picformats.h"
+#include "r_textures.h"
+#include "r_things.h"
+#include "r_draw.h"
+#include "v_video.h"
+#include "z_zone.h"
+#include "w_wad.h"
+
+#ifdef HWRENDER
+#include "hardware/hw_glob.h"
+#endif
+
+#ifdef HAVE_PNG
+
+#ifndef _MSC_VER
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+#endif
+
+#ifndef _LFS64_LARGEFILE
+#define _LFS64_LARGEFILE
+#endif
+
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 0
+#endif
+
+#include "png.h"
+#ifndef PNG_READ_SUPPORTED
+#undef HAVE_PNG
+#endif
+#endif
+
+static unsigned char imgbuf[1<<26];
+
+#ifdef PICTURE_PNG_USELOOKUP
+static colorlookup_t png_colorlookup;
+#endif
+
+/** Converts a picture between two formats.
+  *
+  * \param informat Input picture format.
+  * \param picture Input picture data.
+  * \param outformat Output picture format.
+  * \param insize Input picture size.
+  * \param outsize Output picture size, as a pointer.
+  * \param inwidth Input picture width.
+  * \param inheight Input picture height.
+  * \param inleftoffset Input picture left offset, for patches.
+  * \param intopoffset Input picture top offset, for patches.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  * \sa Picture_PatchConvert
+  * \sa Picture_FlatConvert
+  */
+void *Picture_Convert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset,
+	pictureflags_t flags)
+{
+	if (informat == PICFMT_NONE)
+		I_Error("Picture_Convert: input format was PICFMT_NONE!");
+	else if (outformat == PICFMT_NONE)
+		I_Error("Picture_Convert: output format was PICFMT_NONE!");
+	else if (informat == outformat)
+		I_Error("Picture_Convert: input and output formats were the same!");
+
+	if (Picture_IsPatchFormat(outformat))
+		return Picture_PatchConvert(informat, picture, outformat, insize, outsize, inwidth, inheight, inleftoffset, intopoffset, flags);
+	else if (Picture_IsFlatFormat(outformat))
+		return Picture_FlatConvert(informat, picture, outformat, insize, outsize, inwidth, inheight, inleftoffset, intopoffset, flags);
+	else
+		I_Error("Picture_Convert: unsupported input format!");
+
+	return NULL;
+}
+
+/** Converts a picture to a patch.
+  *
+  * \param informat Input picture format.
+  * \param picture Input picture data.
+  * \param outformat Output picture format.
+  * \param insize Input picture size.
+  * \param outsize Output picture size, as a pointer.
+  * \param inwidth Input picture width.
+  * \param inheight Input picture height.
+  * \param inleftoffset Input picture left offset, for patches.
+  * \param intopoffset Input picture top offset, for patches.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  */
+void *Picture_PatchConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags)
+{
+	INT16 x, y;
+	UINT8 *img;
+	UINT8 *imgptr = imgbuf;
+	UINT8 *colpointers, *startofspan;
+	size_t size = 0;
+	patch_t *inpatch = NULL;
+	INT32 inbpp = Picture_FormatBPP(informat);
+
+	(void)insize; // ignore
+
+	if (informat == PICFMT_NONE)
+		I_Error("Picture_PatchConvert: input format was PICFMT_NONE!");
+	else if (outformat == PICFMT_NONE)
+		I_Error("Picture_PatchConvert: output format was PICFMT_NONE!");
+	else if (informat == outformat)
+		I_Error("Picture_PatchConvert: input and output formats were the same!");
+
+	if (inbpp == PICDEPTH_NONE)
+		I_Error("Picture_PatchConvert: unknown input bits per pixel?!");
+	if (Picture_FormatBPP(outformat) == PICDEPTH_NONE)
+		I_Error("Picture_PatchConvert: unknown output bits per pixel?!");
+
+	// If it's a patch, you can just figure out
+	// the dimensions from the header.
+	if (Picture_IsPatchFormat(informat))
+	{
+		inpatch = (patch_t *)picture;
+		if (Picture_IsDoomPatchFormat(informat))
+		{
+			softwarepatch_t *doompatch = (softwarepatch_t *)picture;
+			inwidth = SHORT(doompatch->width);
+			inheight = SHORT(doompatch->height);
+			inleftoffset = SHORT(doompatch->leftoffset);
+			intopoffset = SHORT(doompatch->topoffset);
+		}
+		else
+		{
+			inwidth = inpatch->width;
+			inheight = inpatch->height;
+			inleftoffset = inpatch->leftoffset;
+			intopoffset = inpatch->topoffset;
+		}
+	}
+
+	// Write image size and offset
+	WRITEINT16(imgptr, inwidth);
+	WRITEINT16(imgptr, inheight);
+	WRITEINT16(imgptr, inleftoffset);
+	WRITEINT16(imgptr, intopoffset);
+
+	// Leave placeholder to column pointers
+	colpointers = imgptr;
+	imgptr += inwidth*4;
+
+	// Write columns
+	for (x = 0; x < inwidth; x++)
+	{
+		int lastStartY = 0;
+		int spanSize = 0;
+		startofspan = NULL;
+
+		// Write column pointer
+		WRITEINT32(colpointers, imgptr - imgbuf);
+
+		// Write pixels
+		for (y = 0; y < inheight; y++)
+		{
+			void *input = NULL;
+			boolean opaque = false;
+
+			// Read pixel
+			if (Picture_IsPatchFormat(informat))
+				input = Picture_GetPatchPixel(inpatch, informat, x, y, flags);
+			else if (Picture_IsFlatFormat(informat))
+			{
+				size_t offs = ((y * inwidth) + x);
+				switch (informat)
+				{
+					case PICFMT_FLAT32:
+						input = (UINT32 *)picture + offs;
+						break;
+					case PICFMT_FLAT16:
+						input = (UINT16 *)picture + offs;
+						break;
+					case PICFMT_FLAT:
+						input = (UINT8 *)picture + offs;
+						break;
+					default:
+						I_Error("Picture_PatchConvert: unsupported flat input format!");
+						break;
+				}
+			}
+			else
+				I_Error("Picture_PatchConvert: unsupported input format!");
+
+			// Determine opacity
+			if (input != NULL)
+			{
+				UINT8 alpha = 0xFF;
+				if (inbpp == PICDEPTH_32BPP)
+				{
+					RGBA_t px = *(RGBA_t *)input;
+					alpha = px.s.alpha;
+				}
+				else if (inbpp == PICDEPTH_16BPP)
+				{
+					UINT16 px = *(UINT16 *)input;
+					alpha = (px & 0xFF00) >> 8;
+				}
+				else if (inbpp == PICDEPTH_8BPP)
+				{
+					UINT8 px = *(UINT8 *)input;
+					if (px == TRANSPARENTPIXEL)
+						alpha = 0;
+				}
+				opaque = (alpha > 1);
+			}
+
+			// End span if we have a transparent pixel
+			if (!opaque)
+			{
+				if (startofspan)
+					WRITEUINT8(imgptr, 0);
+				startofspan = NULL;
+				continue;
+			}
+
+			// Start new column if we need to
+			if (!startofspan || spanSize == 255)
+			{
+				int writeY = y;
+
+				// If we reached the span size limit, finish the previous span
+				if (startofspan)
+					WRITEUINT8(imgptr, 0);
+
+				if (y > 254)
+				{
+					// Make sure we're aligned to 254
+					if (lastStartY < 254)
+					{
+						WRITEUINT8(imgptr, 254);
+						WRITEUINT8(imgptr, 0);
+						imgptr += 2;
+						lastStartY = 254;
+					}
+
+					// Write stopgap empty spans if needed
+					writeY = y - lastStartY;
+
+					while (writeY > 254)
+					{
+						WRITEUINT8(imgptr, 254);
+						WRITEUINT8(imgptr, 0);
+						imgptr += 2;
+						writeY -= 254;
+					}
+				}
+
+				startofspan = imgptr;
+				WRITEUINT8(imgptr, writeY);
+				imgptr += 2;
+				spanSize = 0;
+
+				lastStartY = y;
+			}
+
+			// Write the pixel
+			switch (outformat)
+			{
+				case PICFMT_PATCH32:
+				case PICFMT_DOOMPATCH32:
+				{
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t out = *(RGBA_t *)input;
+						WRITEUINT32(imgptr, out.rgba);
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF];
+						WRITEUINT32(imgptr, out.rgba);
+					}
+					else // PICFMT_PATCH
+					{
+						RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF];
+						WRITEUINT32(imgptr, out.rgba);
+					}
+					break;
+				}
+				case PICFMT_PATCH16:
+				case PICFMT_DOOMPATCH16:
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						WRITEUINT16(imgptr, (0xFF00 | out));
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+						WRITEUINT16(imgptr, *(UINT16 *)input);
+					else // PICFMT_PATCH
+						WRITEUINT16(imgptr, (0xFF00 | (*(UINT8 *)input)));
+					break;
+				default: // PICFMT_PATCH
+				{
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						WRITEUINT8(imgptr, out);
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						UINT16 out = *(UINT16 *)input;
+						WRITEUINT8(imgptr, (out & 0xFF));
+					}
+					else // PICFMT_PATCH
+						WRITEUINT8(imgptr, *(UINT8 *)input);
+					break;
+				}
+			}
+
+			spanSize++;
+			startofspan[1] = spanSize;
+		}
+
+		if (startofspan)
+			WRITEUINT8(imgptr, 0);
+
+		WRITEUINT8(imgptr, 0xFF);
+	}
+
+	size = imgptr-imgbuf;
+	img = Z_Malloc(size, PU_STATIC, NULL);
+	memcpy(img, imgbuf, size);
+
+	if (Picture_IsInternalPatchFormat(outformat))
+	{
+		patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL);
+
+#ifdef HWRENDER
+		Patch_CreateGL(converted);
+#endif
+
+		Z_Free(img);
+
+		if (outsize != NULL)
+			*outsize = sizeof(patch_t);
+		return converted;
+	}
+	else
+	{
+		if (outsize != NULL)
+			*outsize = size;
+		return img;
+	}
+}
+
+/** Converts a picture to a flat.
+  *
+  * \param informat Input picture format.
+  * \param picture Input picture data.
+  * \param outformat Output picture format.
+  * \param insize Input picture size.
+  * \param outsize Output picture size, as a pointer.
+  * \param inwidth Input picture width.
+  * \param inheight Input picture height.
+  * \param inleftoffset Input picture left offset, for patches.
+  * \param intopoffset Input picture top offset, for patches.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  */
+void *Picture_FlatConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags)
+{
+	void *outflat;
+	patch_t *inpatch = NULL;
+	INT32 inbpp = Picture_FormatBPP(informat);
+	INT32 outbpp = Picture_FormatBPP(outformat);
+	INT32 x, y;
+	size_t size;
+
+	(void)insize; // ignore
+	(void)inleftoffset; // ignore
+	(void)intopoffset; // ignore
+
+	if (informat == PICFMT_NONE)
+		I_Error("Picture_FlatConvert: input format was PICFMT_NONE!");
+	else if (outformat == PICFMT_NONE)
+		I_Error("Picture_FlatConvert: output format was PICFMT_NONE!");
+	else if (informat == outformat)
+		I_Error("Picture_FlatConvert: input and output formats were the same!");
+
+	if (inbpp == PICDEPTH_NONE)
+		I_Error("Picture_FlatConvert: unknown input bits per pixel?!");
+	if (outbpp == PICDEPTH_NONE)
+		I_Error("Picture_FlatConvert: unknown output bits per pixel?!");
+
+	// If it's a patch, you can just figure out
+	// the dimensions from the header.
+	if (Picture_IsPatchFormat(informat))
+	{
+		inpatch = (patch_t *)picture;
+		if (Picture_IsDoomPatchFormat(informat))
+		{
+			softwarepatch_t *doompatch = ((softwarepatch_t *)picture);
+			inwidth = SHORT(doompatch->width);
+			inheight = SHORT(doompatch->height);
+		}
+		else
+		{
+			inwidth = inpatch->width;
+			inheight = inpatch->height;
+		}
+	}
+
+	size = (inwidth * inheight) * (outbpp / 8);
+	outflat = Z_Calloc(size, PU_STATIC, NULL);
+	if (outsize)
+		*outsize = size;
+
+	// Set transparency
+	if (outbpp == PICDEPTH_8BPP)
+		memset(outflat, TRANSPARENTPIXEL, size);
+
+	for (y = 0; y < inheight; y++)
+		for (x = 0; x < inwidth; x++)
+		{
+			void *input;
+			size_t offs = ((y * inwidth) + x);
+
+			// Read pixel
+			if (Picture_IsPatchFormat(informat))
+				input = Picture_GetPatchPixel(inpatch, informat, x, y, flags);
+			else if (Picture_IsFlatFormat(informat))
+				input = (UINT8 *)picture + (offs * (inbpp / 8));
+			else
+				I_Error("Picture_FlatConvert: unsupported input format!");
+
+			if (!input)
+				continue;
+
+			switch (outformat)
+			{
+				case PICFMT_FLAT32:
+				{
+					UINT32 *f32 = (UINT32 *)outflat;
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t out = *(RGBA_t *)input;
+						f32[offs] = out.rgba;
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF];
+						f32[offs] = out.rgba;
+					}
+					else // PICFMT_PATCH
+					{
+						RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF];
+						f32[offs] = out.rgba;
+					}
+					break;
+				}
+				case PICFMT_FLAT16:
+				{
+					UINT16 *f16 = (UINT16 *)outflat;
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						f16[offs] = (0xFF00 | out);
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+						f16[offs] = *(UINT16 *)input;
+					else // PICFMT_PATCH
+						f16[offs] = (0xFF00 | *((UINT8 *)input));
+					break;
+				}
+				case PICFMT_FLAT:
+				{
+					UINT8 *f8 = (UINT8 *)outflat;
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						f8[offs] = out;
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						UINT16 out = *(UINT16 *)input;
+						f8[offs] = (out & 0xFF);
+					}
+					else // PICFMT_PATCH
+						f8[offs] = *(UINT8 *)input;
+					break;
+				}
+				default:
+					I_Error("Picture_FlatConvert: unsupported output format!");
+			}
+		}
+
+	return outflat;
+}
+
+/** Returns a pixel from a patch.
+  *
+  * \param patch Input patch.
+  * \param informat Input picture format.
+  * \param x Pixel X position.
+  * \param y Pixel Y position.
+  * \param flags Input picture flags.
+  * \return A pointer to a pixel in the patch. Returns NULL if not opaque.
+  */
+void *Picture_GetPatchPixel(
+	patch_t *patch, pictureformat_t informat,
+	INT32 x, INT32 y,
+	pictureflags_t flags)
+{
+	fixed_t ofs;
+	column_t *column;
+	UINT8 *s8 = NULL;
+	UINT16 *s16 = NULL;
+	UINT32 *s32 = NULL;
+	softwarepatch_t *doompatch = (softwarepatch_t *)patch;
+	boolean isdoompatch = Picture_IsDoomPatchFormat(informat);
+	INT16 width;
+
+	if (patch == NULL)
+		I_Error("Picture_GetPatchPixel: patch == NULL");
+
+	width = (isdoompatch ? SHORT(doompatch->width) : patch->width);
+
+	if (x >= 0 && x < width)
+	{
+		INT32 colx = (flags & PICFLAGS_XFLIP) ? (width-1)-x : x;
+		INT32 topdelta, prevdelta = -1;
+		INT32 colofs = (isdoompatch ? LONG(doompatch->columnofs[colx]) : patch->columnofs[colx]);
+
+		// Column offsets are pointers, so no casting is required.
+		if (isdoompatch)
+			column = (column_t *)((UINT8 *)doompatch + colofs);
+		else
+			column = (column_t *)((UINT8 *)patch->columns + colofs);
+
+		while (column->topdelta != 0xff)
+		{
+			topdelta = column->topdelta;
+			if (topdelta <= prevdelta)
+				topdelta += prevdelta;
+			prevdelta = topdelta;
+			s8 = (UINT8 *)(column) + 3;
+			if (Picture_FormatBPP(informat) == PICDEPTH_32BPP)
+				s32 = (UINT32 *)s8;
+			else if (Picture_FormatBPP(informat) == PICDEPTH_16BPP)
+				s16 = (UINT16 *)s8;
+			for (ofs = 0; ofs < column->length; ofs++)
+			{
+				if ((topdelta + ofs) == y)
+				{
+					if (Picture_FormatBPP(informat) == PICDEPTH_32BPP)
+						return &s32[ofs];
+					else if (Picture_FormatBPP(informat) == PICDEPTH_16BPP)
+						return &s16[ofs];
+					else // PICDEPTH_8BPP
+						return &s8[ofs];
+				}
+			}
+			if (Picture_FormatBPP(informat) == PICDEPTH_32BPP)
+				column = (column_t *)((UINT32 *)column + column->length);
+			else if (Picture_FormatBPP(informat) == PICDEPTH_16BPP)
+				column = (column_t *)((UINT16 *)column + column->length);
+			else
+				column = (column_t *)((UINT8 *)column + column->length);
+			column = (column_t *)((UINT8 *)column + 4);
+		}
+	}
+
+	return NULL;
+}
+
+/** Returns the amount of bits per pixel in the specified picture format.
+  *
+  * \param format Input picture format.
+  * \return The bits per pixel amount of the picture format.
+  */
+INT32 Picture_FormatBPP(pictureformat_t format)
+{
+	INT32 bpp = PICDEPTH_NONE;
+	switch (format)
+	{
+		case PICFMT_PATCH32:
+		case PICFMT_FLAT32:
+		case PICFMT_DOOMPATCH32:
+		case PICFMT_PNG:
+			bpp = PICDEPTH_32BPP;
+			break;
+		case PICFMT_PATCH16:
+		case PICFMT_FLAT16:
+		case PICFMT_DOOMPATCH16:
+			bpp = PICDEPTH_16BPP;
+			break;
+		case PICFMT_PATCH:
+		case PICFMT_FLAT:
+		case PICFMT_DOOMPATCH:
+			bpp = PICDEPTH_8BPP;
+			break;
+		default:
+			break;
+	}
+	return bpp;
+}
+
+/** Checks if the specified picture format is a patch.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is a patch, false if not.
+  */
+boolean Picture_IsPatchFormat(pictureformat_t format)
+{
+	return (Picture_IsInternalPatchFormat(format) || Picture_IsDoomPatchFormat(format));
+}
+
+/** Checks if the specified picture format is an internal patch.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is an internal patch, false if not.
+  */
+boolean Picture_IsInternalPatchFormat(pictureformat_t format)
+{
+	switch (format)
+	{
+		case PICFMT_PATCH:
+		case PICFMT_PATCH16:
+		case PICFMT_PATCH32:
+			return true;
+		default:
+			return false;
+	}
+}
+
+/** Checks if the specified picture format is a Doom patch.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is a Doom patch, false if not.
+  */
+boolean Picture_IsDoomPatchFormat(pictureformat_t format)
+{
+	switch (format)
+	{
+		case PICFMT_DOOMPATCH:
+		case PICFMT_DOOMPATCH16:
+		case PICFMT_DOOMPATCH32:
+			return true;
+		default:
+			return false;
+	}
+}
+
+/** Checks if the specified picture format is a flat.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is a flat, false if not.
+  */
+boolean Picture_IsFlatFormat(pictureformat_t format)
+{
+	return (format == PICFMT_FLAT || format == PICFMT_FLAT16 || format == PICFMT_FLAT32);
+}
+
+/** Returns true if the lump is a valid Doom patch.
+  * PICFMT_DOOMPATCH only.
+  *
+  * \param patch Input patch.
+  * \param picture Input patch size.
+  * \return True if the input patch is valid.
+  */
+boolean Picture_CheckIfDoomPatch(softwarepatch_t *patch, size_t size)
+{
+	INT16 width, height;
+	boolean result;
+
+	// minimum length of a valid Doom patch
+	if (size < 13)
+		return false;
+
+	width = SHORT(patch->width);
+	height = SHORT(patch->height);
+	result = (height > 0 && height <= 16384 && width > 0 && width <= 16384);
+
+	if (result)
+	{
+		// The dimensions seem like they might be valid for a patch, so
+		// check the column directory for extra security. All columns
+		// must begin after the column directory, and none of them must
+		// point past the end of the patch.
+		INT16 x;
+
+		for (x = 0; x < width; x++)
+		{
+			UINT32 ofs = LONG(patch->columnofs[x]);
+
+			// Need one byte for an empty column (but there's patches that don't know that!)
+			if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
+			{
+				result = false;
+				break;
+			}
+		}
+	}
+
+	return result;
+}
+
+/** Converts a texture to a flat.
+  *
+  * \param trickytex The texture number.
+  * \return The converted flat.
+  */
+void *Picture_TextureToFlat(size_t trickytex)
+{
+	texture_t *texture;
+	size_t tex;
+
+	UINT8 *converted;
+	size_t flatsize;
+	fixed_t col, ofs;
+	column_t *column;
+	UINT8 *desttop, *dest, *deststop;
+	UINT8 *source;
+
+	if (trickytex >= (unsigned)numtextures)
+		I_Error("Picture_TextureToFlat: invalid texture number!");
+
+	// Check the texture cache
+	// If the texture's not there, it'll be generated right now
+	tex = trickytex;
+	texture = textures[tex];
+	R_CheckTextureCache(tex);
+
+	// Allocate the flat
+	flatsize = (texture->width * texture->height);
+	converted = Z_Malloc(flatsize, PU_STATIC, NULL);
+	memset(converted, TRANSPARENTPIXEL, flatsize);
+
+	// Now we're gonna write to it
+	desttop = converted;
+	deststop = desttop + flatsize;
+	for (col = 0; col < texture->width; col++, desttop++)
+	{
+		// no post_t info
+		if (!texture->holes)
+		{
+			column = (column_t *)(R_GetColumn(tex, col));
+			source = (UINT8 *)(column);
+			dest = desttop;
+			for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
+			{
+				if (source[ofs] != TRANSPARENTPIXEL)
+					*dest = source[ofs];
+				dest += texture->width;
+			}
+		}
+		else
+		{
+			INT32 topdelta, prevdelta = -1;
+			column = (column_t *)((UINT8 *)R_GetColumn(tex, col) - 3);
+			while (column->topdelta != 0xff)
+			{
+				topdelta = column->topdelta;
+				if (topdelta <= prevdelta)
+					topdelta += prevdelta;
+				prevdelta = topdelta;
+
+				dest = desttop + (topdelta * texture->width);
+				source = (UINT8 *)column + 3;
+				for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
+				{
+					if (source[ofs] != TRANSPARENTPIXEL)
+						*dest = source[ofs];
+					dest += texture->width;
+				}
+				column = (column_t *)((UINT8 *)column + column->length + 4);
+			}
+		}
+	}
+
+	return converted;
+}
+
+/** Returns true if the lump is a valid PNG.
+  *
+  * \param d The lump to be checked.
+  * \param s The lump size.
+  * \return True if the lump is a PNG image.
+  */
+boolean Picture_IsLumpPNG(const UINT8 *d, size_t s)
+{
+	if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
+		return false;
+	// Check for PNG file signature using memcmp
+	// As it may be faster on CPUs with slow unaligned memory access
+	// Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature
+	return (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0);
+}
+
+#ifndef NO_PNG_LUMPS
+#ifdef HAVE_PNG
+
+/*#if PNG_LIBPNG_VER_DLLNUM < 14
+typedef PNG_CONST png_byte *png_const_bytep;
+#endif*/
+typedef struct
+{
+	const UINT8 *buffer;
+	UINT32 size;
+	UINT32 position;
+} png_io_t;
+
+static void PNG_IOReader(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+	png_io_t *f = png_get_io_ptr(png_ptr);
+	if (length > (f->size - f->position))
+		png_error(png_ptr, "PNG_IOReader: buffer overrun");
+	memcpy(data, f->buffer + f->position, length);
+	f->position += length;
+}
+
+typedef struct
+{
+	char name[4];
+	void *data;
+	size_t size;
+} png_chunk_t;
+
+static png_byte *chunkname = NULL;
+static png_chunk_t chunk;
+
+static int PNG_ChunkReader(png_structp png_ptr, png_unknown_chunkp chonk)
+{
+	(void)png_ptr;
+	if (!memcmp(chonk->name, chunkname, 4))
+	{
+		memcpy(chunk.name, chonk->name, 4);
+		chunk.size = chonk->size;
+		chunk.data = Z_Malloc(chunk.size, PU_STATIC, NULL);
+		memcpy(chunk.data, chonk->data, chunk.size);
+		return 1;
+	}
+	return 0;
+}
+
+static void PNG_error(png_structp PNG, png_const_charp pngtext)
+{
+	CONS_Debug(DBG_RENDER, "libpng error at %p: %s", PNG, pngtext);
+	//I_Error("libpng error at %p: %s", PNG, pngtext);
+}
+
+static void PNG_warn(png_structp PNG, png_const_charp pngtext)
+{
+	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
+}
+
+static png_byte grAb_chunk[5] = {'g', 'r', 'A', 'b', (png_byte)'\0'};
+
+static png_bytep *PNG_Read(
+	const UINT8 *png,
+	INT32 *w, INT32 *h, INT16 *topoffset, INT16 *leftoffset,
+	boolean *use_palette, size_t size)
+{
+	png_structp png_ptr;
+	png_infop png_info_ptr;
+	png_uint_32 width, height;
+	int bit_depth, color_type;
+	png_uint_32 y;
+
+	png_colorp palette;
+	int palette_size;
+
+	png_bytep trans;
+	int trans_num;
+	png_color_16p trans_values;
+
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+	jmp_buf jmpbuf;
+#endif
+#endif
+
+	png_io_t png_io;
+	png_bytep *row_pointers;
+	png_voidp *user_chunk_ptr;
+
+	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
+	if (!png_ptr)
+		I_Error("PNG_Read: Couldn't initialize libpng!");
+
+	png_info_ptr = png_create_info_struct(png_ptr);
+	if (!png_info_ptr)
+	{
+		png_destroy_read_struct(&png_ptr, NULL, NULL);
+		I_Error("PNG_Read: libpng couldn't allocate memory!");
+	}
+
+#ifdef USE_FAR_KEYWORD
+	if (setjmp(jmpbuf))
+#else
+	if (setjmp(png_jmpbuf(png_ptr)))
+#endif
+	{
+		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+		I_Error("PNG_Read: libpng load error!");
+	}
+#ifdef USE_FAR_KEYWORD
+	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
+#endif
+
+	png_io.buffer = png;
+	png_io.size = size;
+	png_io.position = 0;
+	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
+
+	memset(&chunk, 0x00, sizeof(png_chunk_t));
+	chunkname = grAb_chunk; // I want to read a grAb chunk
+
+	user_chunk_ptr = png_get_user_chunk_ptr(png_ptr);
+	png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr, PNG_ChunkReader);
+	png_set_keep_unknown_chunks(png_ptr, 2, chunkname, 1);
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+	png_set_user_limits(png_ptr, 2048, 2048);
+#endif
+
+	png_read_info(png_ptr, png_info_ptr);
+	png_get_IHDR(png_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
+
+	if (bit_depth == 16)
+		png_set_strip_16(png_ptr);
+
+	palette = NULL;
+	*use_palette = false;
+
+	if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+		png_set_gray_to_rgb(png_ptr);
+	else if (color_type == PNG_COLOR_TYPE_PALETTE)
+	{
+		boolean usepal = false;
+
+		// Lactozilla: Check if the PNG has a palette, and if its color count
+		// matches the color count of SRB2's palette: 256 colors.
+		if (png_get_PLTE(png_ptr, png_info_ptr, &palette, &palette_size))
+		{
+			if (palette_size == 256 && pMasterPalette)
+			{
+				png_colorp pal = palette;
+				INT32 i;
+
+				usepal = true;
+
+				for (i = 0; i < 256; i++)
+				{
+					UINT32 rgb = R_PutRgbaRGBA(pal->red, pal->green, pal->blue, 0xFF);
+					if (rgb != pMasterPalette[i].rgba)
+					{
+						usepal = false;
+						break;
+					}
+					pal++;
+				}
+			}
+		}
+
+		// If any of the tRNS colors have an alpha lower than 0xFF, and that
+		// color is present on the image, the palette flag is disabled.
+		if (usepal)
+		{
+			png_get_tRNS(png_ptr, png_info_ptr, &trans, &trans_num, &trans_values);
+
+			if (trans && trans_num == 256)
+			{
+				INT32 i;
+				for (i = 0; i < trans_num; i++)
+				{
+					// libpng will transform this image into RGB even if
+					// the transparent index does not exist in the image,
+					// and there is no way around that.
+					if (trans[i] < 0xFF)
+					{
+						usepal = false;
+						break;
+					}
+				}
+			}
+		}
+
+		if (usepal)
+			*use_palette = true;
+		else
+			png_set_palette_to_rgb(png_ptr);
+	}
+
+	if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_tRNS))
+		png_set_tRNS_to_alpha(png_ptr);
+	else if (color_type != PNG_COLOR_TYPE_RGB_ALPHA && color_type != PNG_COLOR_TYPE_GRAY_ALPHA)
+	{
+#if PNG_LIBPNG_VER < 10207
+		png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
+#else
+		png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+#endif
+	}
+
+	png_read_update_info(png_ptr, png_info_ptr);
+
+	// Read the image
+	row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
+	for (y = 0; y < height; y++)
+		row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, png_info_ptr));
+	png_read_image(png_ptr, row_pointers);
+
+	// Read grAB chunk
+	if ((topoffset || leftoffset) && (chunk.data != NULL))
+	{
+		INT32 *offsets = (INT32 *)chunk.data;
+		// read left offset
+		if (leftoffset != NULL)
+			*leftoffset = (INT16)BIGENDIAN_LONG(*offsets);
+		offsets++;
+		// read top offset
+		if (topoffset != NULL)
+			*topoffset = (INT16)BIGENDIAN_LONG(*offsets);
+	}
+
+	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+	if (chunk.data)
+		Z_Free(chunk.data);
+
+	*w = (INT32)width;
+	*h = (INT32)height;
+
+	return row_pointers;
+}
+
+/** Converts a PNG to a picture.
+  *
+  * \param png The PNG image.
+  * \param outformat The output picture's format.
+  * \param w The output picture's width, as a pointer.
+  * \param h The output picture's height, as a pointer.
+  * \param topoffset The output picture's top offset, for sprites, as a pointer.
+  * \param leftoffset The output picture's left offset, for sprites, as a pointer.
+  * \param insize The input picture's size.
+  * \param outsize A pointer to the output picture's size.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  */
+void *Picture_PNGConvert(
+	const UINT8 *png, pictureformat_t outformat,
+	INT32 *w, INT32 *h,
+	INT16 *topoffset, INT16 *leftoffset,
+	size_t insize, size_t *outsize,
+	pictureflags_t flags)
+{
+	void *flat;
+	INT32 outbpp;
+	size_t flatsize;
+	png_uint_32 x, y;
+	png_bytep row;
+	boolean palette = false;
+	png_bytep *row_pointers = NULL;
+	png_uint_32 width, height;
+
+	INT32 pngwidth, pngheight;
+	INT16 loffs = 0, toffs = 0;
+
+	if (png == NULL)
+		I_Error("Picture_PNGConvert: picture was NULL!");
+
+	if (w == NULL)
+		w = &pngwidth;
+	if (h == NULL)
+		h = &pngheight;
+	if (topoffset == NULL)
+		topoffset = &toffs;
+	if (leftoffset == NULL)
+		leftoffset = &loffs;
+
+	row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, &palette, insize);
+	width = *w;
+	height = *h;
+
+	if (row_pointers == NULL)
+		I_Error("Picture_PNGConvert: row_pointers was NULL!");
+
+	// Find the output format's bits per pixel amount
+	outbpp = Picture_FormatBPP(outformat);
+
+	// Hack for patches because you'll want to preserve transparency.
+	if (Picture_IsPatchFormat(outformat))
+	{
+		// Force a higher bit depth
+		if (outbpp == PICDEPTH_8BPP)
+			outbpp = PICDEPTH_16BPP;
+	}
+
+	// Shouldn't happen.
+	if (outbpp == PICDEPTH_NONE)
+		I_Error("Picture_PNGConvert: unknown output bits per pixel?!");
+
+	// Figure out the size
+	flatsize = (width * height) * (outbpp / 8);
+	if (outsize)
+		*outsize = flatsize;
+
+	// Convert the image
+	flat = Z_Calloc(flatsize, PU_STATIC, NULL);
+
+	// Set transparency
+	if (outbpp == PICDEPTH_8BPP)
+		memset(flat, TRANSPARENTPIXEL, (width * height));
+
+#ifdef PICTURE_PNG_USELOOKUP
+	if (outbpp != PICDEPTH_32BPP)
+		InitColorLUT(&png_colorlookup, pMasterPalette, false);
+#endif
+
+	if (outbpp == PICDEPTH_32BPP)
+	{
+		RGBA_t out;
+		UINT32 *outflat = (UINT32 *)flat;
+
+		if (palette)
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					out = V_GetColor(row[x]);
+					outflat[((y * width) + x)] = out.rgba;
+				}
+			}
+		}
+		else
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					png_bytep px = &(row[x * 4]);
+					if ((UINT8)px[3])
+					{
+						out.s.red = (UINT8)px[0];
+						out.s.green = (UINT8)px[1];
+						out.s.blue = (UINT8)px[2];
+						out.s.alpha = (UINT8)px[3];
+						outflat[((y * width) + x)] = out.rgba;
+					}
+					else
+						outflat[((y * width) + x)] = 0x00000000;
+				}
+			}
+		}
+	}
+	else if (outbpp == PICDEPTH_16BPP)
+	{
+		UINT16 *outflat = (UINT16 *)flat;
+
+		if (palette)
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+					outflat[((y * width) + x)] = (0xFF << 8) | row[x];
+			}
+		}
+		else
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					png_bytep px = &(row[x * 4]);
+					UINT8 red = (UINT8)px[0];
+					UINT8 green = (UINT8)px[1];
+					UINT8 blue = (UINT8)px[2];
+					UINT8 alpha = (UINT8)px[3];
+
+					if (alpha)
+					{
+#ifdef PICTURE_PNG_USELOOKUP
+						UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
+#else
+						UINT8 palidx = NearestColor(red, green, blue);
+#endif
+						outflat[((y * width) + x)] = (0xFF << 8) | palidx;
+					}
+					else
+						outflat[((y * width) + x)] = 0x0000;
+				}
+			}
+		}
+	}
+	else // 8bpp
+	{
+		UINT8 *outflat = (UINT8 *)flat;
+
+		if (palette)
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+					outflat[((y * width) + x)] = row[x];
+			}
+		}
+		else
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					png_bytep px = &(row[x * 4]);
+					UINT8 red = (UINT8)px[0];
+					UINT8 green = (UINT8)px[1];
+					UINT8 blue = (UINT8)px[2];
+					UINT8 alpha = (UINT8)px[3];
+
+					if (alpha)
+					{
+#ifdef PICTURE_PNG_USELOOKUP
+						UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
+#else
+						UINT8 palidx = NearestColor(red, green, blue);
+#endif
+						outflat[((y * width) + x)] = palidx;
+					}
+				}
+			}
+		}
+	}
+
+	// Free the row pointers that we allocated for libpng.
+	for (y = 0; y < height; y++)
+		free(row_pointers[y]);
+	free(row_pointers);
+
+	// But wait, there's more!
+	if (Picture_IsPatchFormat(outformat))
+	{
+		void *converted;
+		pictureformat_t informat = PICFMT_NONE;
+
+		// Figure out the format of the flat, from the bit depth of the output format
+		switch (outbpp)
+		{
+			case 32:
+				informat = PICFMT_FLAT32;
+				break;
+			case 16:
+				informat = PICFMT_FLAT16;
+				break;
+			default:
+				informat = PICFMT_FLAT;
+				break;
+		}
+
+		// Now, convert it!
+		converted = Picture_PatchConvert(informat, flat, outformat, insize, outsize, (INT16)width, (INT16)height, *leftoffset, *topoffset, flags);
+		Z_Free(flat);
+		return converted;
+	}
+
+	// Return the converted flat!
+	return flat;
+}
+
+/** Returns the dimensions of a PNG image, but doesn't perform any conversions.
+  *
+  * \param png The PNG image.
+  * \param width A pointer to the input picture's width.
+  * \param height A pointer to the input picture's height.
+  * \param topoffset A pointer to the input picture's vertical offset.
+  * \param leftoffset A pointer to the input picture's horizontal offset.
+  * \param size The input picture's size.
+  * \return True if reading the file succeeded, false if it failed.
+  */
+boolean Picture_PNGDimensions(UINT8 *png, INT32 *width, INT32 *height, INT16 *topoffset, INT16 *leftoffset, size_t size)
+{
+	png_structp png_ptr;
+	png_infop png_info_ptr;
+	png_uint_32 w, h;
+	int bit_depth, color_type;
+#ifdef PNG_SETJMP_SUPPORTED
+#ifdef USE_FAR_KEYWORD
+	jmp_buf jmpbuf;
+#endif
+#endif
+
+	png_io_t png_io;
+	png_voidp *user_chunk_ptr;
+
+	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
+	if (!png_ptr)
+		I_Error("Picture_PNGDimensions: Couldn't initialize libpng!");
+
+	png_info_ptr = png_create_info_struct(png_ptr);
+	if (!png_info_ptr)
+	{
+		png_destroy_read_struct(&png_ptr, NULL, NULL);
+		I_Error("Picture_PNGDimensions: libpng couldn't allocate memory!");
+	}
+
+#ifdef USE_FAR_KEYWORD
+	if (setjmp(jmpbuf))
+#else
+	if (setjmp(png_jmpbuf(png_ptr)))
+#endif
+	{
+		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+		I_Error("Picture_PNGDimensions: libpng load error!");
+	}
+#ifdef USE_FAR_KEYWORD
+	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
+#endif
+
+	png_io.buffer = png;
+	png_io.size = size;
+	png_io.position = 0;
+	png_set_read_fn(png_ptr, &png_io, PNG_IOReader);
+
+	memset(&chunk, 0x00, sizeof(png_chunk_t));
+	chunkname = grAb_chunk; // I want to read a grAb chunk
+
+	user_chunk_ptr = png_get_user_chunk_ptr(png_ptr);
+	png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr, PNG_ChunkReader);
+	png_set_keep_unknown_chunks(png_ptr, 2, chunkname, 1);
+
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+	png_set_user_limits(png_ptr, 2048, 2048);
+#endif
+
+	png_read_info(png_ptr, png_info_ptr);
+	png_get_IHDR(png_ptr, png_info_ptr, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL);
+
+	// Read grAB chunk
+	if ((topoffset || leftoffset) && (chunk.data != NULL))
+	{
+		INT32 *offsets = (INT32 *)chunk.data;
+		// read left offset
+		if (leftoffset != NULL)
+			*leftoffset = (INT16)BIGENDIAN_LONG(*offsets);
+		offsets++;
+		// read top offset
+		if (topoffset != NULL)
+			*topoffset = (INT16)BIGENDIAN_LONG(*offsets);
+	}
+
+	png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
+	if (chunk.data)
+		Z_Free(chunk.data);
+
+	*width = (INT32)w;
+	*height = (INT32)h;
+	return true;
+}
+#endif
+#endif
+
+//
+// R_ParseSpriteInfoFrame
+//
+// Parse a SPRTINFO frame.
+//
+static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
+{
+	char *sprinfoToken;
+	size_t sprinfoTokenLength;
+	char *frameChar = NULL;
+	UINT8 frameFrame = 0xFF;
+	INT16 frameXPivot = 0;
+	INT16 frameYPivot = 0;
+	rotaxis_t frameRotAxis = 0;
+
+	// Sprite identifier
+	sprinfoToken = M_GetToken(NULL);
+	if (sprinfoToken == NULL)
+	{
+		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be");
+	}
+	sprinfoTokenLength = strlen(sprinfoToken);
+	if (sprinfoTokenLength != 1)
+	{
+		I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken);
+	}
+	else
+		frameChar = sprinfoToken;
+
+	frameFrame = R_Char2Frame(frameChar[0]);
+	Z_Free(sprinfoToken);
+
+	// Left Curly Brace
+	sprinfoToken = M_GetToken(NULL);
+	if (sprinfoToken == NULL)
+		I_Error("Error parsing SPRTINFO lump: Missing sprite info");
+	else
+	{
+		if (strcmp(sprinfoToken,"{")==0)
+		{
+			Z_Free(sprinfoToken);
+			sprinfoToken = M_GetToken(NULL);
+			if (sprinfoToken == NULL)
+			{
+				I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be");
+			}
+			while (strcmp(sprinfoToken,"}")!=0)
+			{
+				if (stricmp(sprinfoToken, "XPIVOT")==0)
+				{
+					Z_Free(sprinfoToken);
+					sprinfoToken = M_GetToken(NULL);
+					frameXPivot = atoi(sprinfoToken);
+				}
+				else if (stricmp(sprinfoToken, "YPIVOT")==0)
+				{
+					Z_Free(sprinfoToken);
+					sprinfoToken = M_GetToken(NULL);
+					frameYPivot = atoi(sprinfoToken);
+				}
+				else if (stricmp(sprinfoToken, "ROTAXIS")==0)
+				{
+					Z_Free(sprinfoToken);
+					sprinfoToken = M_GetToken(NULL);
+					if ((stricmp(sprinfoToken, "X")==0) || (stricmp(sprinfoToken, "XAXIS")==0) || (stricmp(sprinfoToken, "ROLL")==0))
+						frameRotAxis = ROTAXIS_X;
+					else if ((stricmp(sprinfoToken, "Y")==0) || (stricmp(sprinfoToken, "YAXIS")==0) || (stricmp(sprinfoToken, "PITCH")==0))
+						frameRotAxis = ROTAXIS_Y;
+					else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0) || (stricmp(sprinfoToken, "YAW")==0))
+						frameRotAxis = ROTAXIS_Z;
+				}
+				Z_Free(sprinfoToken);
+
+				sprinfoToken = M_GetToken(NULL);
+				if (sprinfoToken == NULL)
+				{
+					I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be");
+				}
+			}
+		}
+		Z_Free(sprinfoToken);
+	}
+
+	// set fields
+	info->pivot[frameFrame].x = frameXPivot;
+	info->pivot[frameFrame].y = frameYPivot;
+	info->pivot[frameFrame].rotaxis = frameRotAxis;
+}
+
+//
+// R_ParseSpriteInfo
+//
+// Parse a SPRTINFO lump.
+//
+static void R_ParseSpriteInfo(boolean spr2)
+{
+	spriteinfo_t *info;
+	char *sprinfoToken;
+	size_t sprinfoTokenLength;
+	char newSpriteName[5]; // no longer dynamically allocated
+	spritenum_t sprnum = NUMSPRITES;
+	playersprite_t spr2num = NUMPLAYERSPRITES;
+	INT32 i;
+	INT32 skinnumbers[MAXSKINS];
+	INT32 foundskins = 0;
+
+	// Sprite name
+	sprinfoToken = M_GetToken(NULL);
+	if (sprinfoToken == NULL)
+	{
+		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be");
+	}
+	sprinfoTokenLength = strlen(sprinfoToken);
+	if (sprinfoTokenLength != 4)
+	{
+		I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long",sprinfoToken);
+	}
+	else
+	{
+		memset(&newSpriteName, 0, 5);
+		M_Memcpy(newSpriteName, sprinfoToken, sprinfoTokenLength);
+		// ^^ we've confirmed that the token is == 4 characters so it will never overflow a 5 byte char buffer
+		strupr(newSpriteName); // Just do this now so we don't have to worry about it
+	}
+	Z_Free(sprinfoToken);
+
+	if (!spr2)
+	{
+		for (i = 0; i <= NUMSPRITES; i++)
+		{
+			if (i == NUMSPRITES)
+				I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName);
+			if (!memcmp(newSpriteName,sprnames[i],4))
+			{
+				sprnum = i;
+				break;
+			}
+		}
+	}
+	else
+	{
+		for (i = 0; i <= NUMPLAYERSPRITES; i++)
+		{
+			if (i == NUMPLAYERSPRITES)
+				I_Error("Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"", newSpriteName);
+			if (!memcmp(newSpriteName,spr2names[i],4))
+			{
+				spr2num = i;
+				break;
+			}
+		}
+	}
+
+	// allocate a spriteinfo
+	info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL);
+	info->available = true;
+
+	// Left Curly Brace
+	sprinfoToken = M_GetToken(NULL);
+	if (sprinfoToken == NULL)
+	{
+		I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName);
+	}
+	if (strcmp(sprinfoToken,"{")==0)
+	{
+		Z_Free(sprinfoToken);
+		sprinfoToken = M_GetToken(NULL);
+		if (sprinfoToken == NULL)
+		{
+			I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName);
+		}
+		while (strcmp(sprinfoToken,"}")!=0)
+		{
+			if (stricmp(sprinfoToken, "SKIN")==0)
+			{
+				INT32 skinnum;
+				char *skinName = NULL;
+				if (!spr2)
+					I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition");
+
+				Z_Free(sprinfoToken);
+
+				// Skin name
+				sprinfoToken = M_GetToken(NULL);
+				if (sprinfoToken == NULL)
+				{
+					I_Error("Error parsing SPRTINFO lump: Unexpected end of file where skin frame should be");
+				}
+
+				// copy skin name yada yada
+				sprinfoTokenLength = strlen(sprinfoToken);
+				skinName = (char *)Z_Malloc((sprinfoTokenLength+1)*sizeof(char),PU_STATIC,NULL);
+				M_Memcpy(skinName,sprinfoToken,sprinfoTokenLength*sizeof(char));
+				skinName[sprinfoTokenLength] = '\0';
+				strlwr(skinName);
+				Z_Free(sprinfoToken);
+
+				skinnum = R_SkinAvailable(skinName);
+				if (skinnum == -1)
+					I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName);
+
+				skinnumbers[foundskins] = skinnum;
+				foundskins++;
+			}
+			else if (stricmp(sprinfoToken, "FRAME")==0)
+			{
+				R_ParseSpriteInfoFrame(info);
+				Z_Free(sprinfoToken);
+				if (spr2)
+				{
+					if (!foundskins)
+						I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition");
+					for (i = 0; i < foundskins; i++)
+					{
+						size_t skinnum = skinnumbers[i];
+						skin_t *skin = &skins[skinnum];
+						spriteinfo_t *sprinfo = skin->sprinfo;
+						M_Memcpy(&sprinfo[spr2num], info, sizeof(spriteinfo_t));
+					}
+				}
+				else
+					M_Memcpy(&spriteinfo[sprnum], info, sizeof(spriteinfo_t));
+			}
+			else
+			{
+				I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName);
+			}
+
+			sprinfoToken = M_GetToken(NULL);
+			if (sprinfoToken == NULL)
+			{
+				I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName);
+			}
+		}
+	}
+	else
+	{
+		I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken);
+	}
+	Z_Free(sprinfoToken);
+	Z_Free(info);
+}
+
+//
+// R_ParseSPRTINFOLump
+//
+// Read a SPRTINFO lump.
+//
+void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum)
+{
+	char *sprinfoLump;
+	size_t sprinfoLumpLength;
+	char *sprinfoText;
+	char *sprinfoToken;
+
+	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
+	// need to make a space of memory where I can ensure that it will terminate
+	// correctly. Start by loading the relevant data from the WAD.
+	sprinfoLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
+	// If that didn't exist, we have nothing to do here.
+	if (sprinfoLump == NULL) return;
+	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
+	sprinfoLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
+	sprinfoText = (char *)Z_Malloc((sprinfoLumpLength+1)*sizeof(char),PU_STATIC,NULL);
+	// Now move the contents of the lump into this new location.
+	memmove(sprinfoText,sprinfoLump,sprinfoLumpLength);
+	// Make damn well sure the last character in our new memory location is \0.
+	sprinfoText[sprinfoLumpLength] = '\0';
+	// Finally, free up the memory from the first data load, because we really
+	// don't need it.
+	Z_Free(sprinfoLump);
+
+	sprinfoToken = M_GetToken(sprinfoText);
+	while (sprinfoToken != NULL)
+	{
+		if (!stricmp(sprinfoToken, "SPRITE"))
+			R_ParseSpriteInfo(false);
+		else if (!stricmp(sprinfoToken, "SPRITE2"))
+			R_ParseSpriteInfo(true);
+		else
+			I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken);
+		Z_Free(sprinfoToken);
+		sprinfoToken = M_GetToken(NULL);
+	}
+	Z_Free((void *)sprinfoText);
+}
+
+//
+// R_LoadSpriteInfoLumps
+//
+// Load and read every SPRTINFO lump from the specified file.
+//
+void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
+{
+	lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
+	UINT16 i;
+	char *name;
+
+	for (i = 0; i < numlumps; i++, lumpinfo++)
+	{
+		name = lumpinfo->name;
+		// Load SPRTINFO and SPR_ lumps as SpriteInfo
+		if (!memcmp(name, "SPRTINFO", 8) || !memcmp(name, "SPR_", 4))
+			R_ParseSPRTINFOLump(wadnum, i);
+	}
+}
diff --git a/src/r_picformats.h b/src/r_picformats.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d3999013475f23b9428e0e252148d91c88c8ea2
--- /dev/null
+++ b/src/r_picformats.h
@@ -0,0 +1,128 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2019-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_picformats.h
+/// \brief Patch generation.
+
+#ifndef __R_PICFORMATS__
+#define __R_PICFORMATS__
+
+#include "r_defs.h"
+#include "doomdef.h"
+
+typedef enum
+{
+	PICFMT_NONE = 0,
+
+	// Doom formats
+	PICFMT_PATCH,
+	PICFMT_FLAT,
+	PICFMT_DOOMPATCH,
+
+	// PNG
+	PICFMT_PNG,
+
+	// 16bpp
+	PICFMT_PATCH16,
+	PICFMT_FLAT16,
+	PICFMT_DOOMPATCH16,
+
+	// 32bpp
+	PICFMT_PATCH32,
+	PICFMT_FLAT32,
+	PICFMT_DOOMPATCH32
+} pictureformat_t;
+
+typedef enum
+{
+	PICFLAGS_XFLIP = 1,
+	PICFLAGS_YFLIP = 1<<1
+} pictureflags_t;
+
+enum
+{
+	PICDEPTH_NONE = 0,
+	PICDEPTH_8BPP = 8,
+	PICDEPTH_16BPP = 16,
+	PICDEPTH_32BPP = 32
+};
+
+void *Picture_Convert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset,
+	pictureflags_t flags);
+
+void *Picture_PatchConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags);
+void *Picture_FlatConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags);
+void *Picture_GetPatchPixel(
+	patch_t *patch, pictureformat_t informat,
+	INT32 x, INT32 y,
+	pictureflags_t flags);
+
+void *Picture_TextureToFlat(size_t trickytex);
+
+INT32 Picture_FormatBPP(pictureformat_t format);
+boolean Picture_IsPatchFormat(pictureformat_t format);
+boolean Picture_IsInternalPatchFormat(pictureformat_t format);
+boolean Picture_IsDoomPatchFormat(pictureformat_t format);
+boolean Picture_IsFlatFormat(pictureformat_t format);
+boolean Picture_CheckIfDoomPatch(softwarepatch_t *patch, size_t size);
+
+// Structs
+typedef enum
+{
+	ROTAXIS_X, // Roll (the default)
+	ROTAXIS_Y, // Pitch
+	ROTAXIS_Z  // Yaw
+} rotaxis_t;
+
+typedef struct
+{
+	INT32 x, y;
+	rotaxis_t rotaxis;
+} spriteframepivot_t;
+
+typedef struct
+{
+	spriteframepivot_t pivot[64];
+	boolean available;
+} spriteinfo_t;
+
+// Portable Network Graphics
+boolean Picture_IsLumpPNG(const UINT8 *d, size_t s);
+#define Picture_ThrowPNGError(lumpname, wadfilename) I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .png - please convert to either Doom or Flat (raw) image format.", lumpname, wadfilename); // Fears Of LJ Sonic
+
+#ifndef NO_PNG_LUMPS
+void *Picture_PNGConvert(
+	const UINT8 *png, pictureformat_t outformat,
+	INT32 *w, INT32 *h,
+	INT16 *topoffset, INT16 *leftoffset,
+	size_t insize, size_t *outsize,
+	pictureflags_t flags);
+boolean Picture_PNGDimensions(UINT8 *png, INT32 *width, INT32 *height, INT16 *topoffset, INT16 *leftoffset, size_t size);
+#endif
+
+#define PICTURE_PNG_USELOOKUP
+
+// SpriteInfo
+extern spriteinfo_t spriteinfo[NUMSPRITES];
+void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
+void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
+
+#endif // __R_PICFORMATS__
diff --git a/src/r_plane.c b/src/r_plane.c
index d7c861aa1fa73b7dd77fe401a02134a07401b74d..9c137866a1af8d6b979b64b7c02257803b23d55a 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -19,6 +19,7 @@
 #include "p_world.h" // levelflats
 #include "p_slopes.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_local.h"
 #include "r_state.h"
 #include "r_splats.h" // faB(21jan):testing
@@ -59,7 +60,7 @@ INT32 numffloors;
 
 //SoM: 3/23/2000: Boom visplane hashing routine.
 #define visplane_hash(picnum,lightlevel,height) \
-  ((unsigned)((picnum)*3+(lightlevel)+(height)*7) & (MAXVISPLANES-1))
+  ((unsigned)((picnum)*3+(lightlevel)+(height)*7) & VISPLANEHASHMASK)
 
 //SoM: 3/23/2000: Use boom opening limit removal
 size_t maxopenings;
@@ -114,33 +115,40 @@ void R_InitPlanes(void)
 }
 
 //
-// Water ripple effect!!
+// Water ripple effect
 // Needs the height of the plane, and the vertical position of the span.
-// Sets ripple_xfrac and ripple_yfrac, added to ds_xfrac and ds_yfrac, if the span is not tilted.
+// Sets planeripple.xfrac and planeripple.yfrac, added to ds_xfrac and ds_yfrac, if the span is not tilted.
 //
 
-#ifndef NOWATER
-INT32 ds_bgofs;
-INT32 ds_waterofs;
-
-static INT32 wtofs=0;
-static boolean itswater;
-static fixed_t ripple_xfrac;
-static fixed_t ripple_yfrac;
+struct
+{
+	INT32 offset;
+	fixed_t xfrac, yfrac;
+	boolean active;
+} planeripple;
 
-static void R_PlaneRipple(visplane_t *plane, INT32 y, fixed_t plheight)
+static void R_CalculatePlaneRipple(visplane_t *plane, INT32 y, fixed_t plheight, boolean calcfrac)
 {
 	fixed_t distance = FixedMul(plheight, yslope[y]);
-	const INT32 yay = (wtofs + (distance>>9) ) & 8191;
+	const INT32 yay = (planeripple.offset + (distance>>9)) & 8191;
+
 	// ripples da water texture
-	angle_t angle = (plane->viewangle + plane->plangle)>>ANGLETOFINESHIFT;
 	ds_bgofs = FixedDiv(FINESINE(yay), (1<<12) + (distance>>11))>>FRACBITS;
 
-	angle = (angle + 2048) & 8191;  // 90 degrees
-	ripple_xfrac = FixedMul(FINECOSINE(angle), (ds_bgofs<<FRACBITS));
-	ripple_yfrac = FixedMul(FINESINE(angle), (ds_bgofs<<FRACBITS));
+	if (calcfrac)
+	{
+		angle_t angle = (plane->viewangle + plane->plangle)>>ANGLETOFINESHIFT;
+		angle = (angle + 2048) & 8191; // 90 degrees
+		planeripple.xfrac = FixedMul(FINECOSINE(angle), (ds_bgofs<<FRACBITS));
+		planeripple.yfrac = FixedMul(FINESINE(angle), (ds_bgofs<<FRACBITS));
+	}
+}
+
+static void R_UpdatePlaneRipple(void)
+{
+	ds_waterofs = (leveltime & 1)*16384;
+	planeripple.offset = (leveltime * 140);
 }
-#endif
 
 //
 // R_MapPlane
@@ -158,7 +166,7 @@ static void R_PlaneRipple(visplane_t *plane, INT32 y, fixed_t plheight)
 void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 {
 	angle_t angle, planecos, planesin;
-	fixed_t distance, span;
+	fixed_t distance = 0, span;
 	size_t pindex;
 
 #ifdef RANGECHECK
@@ -166,41 +174,51 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 		I_Error("R_MapPlane: %d, %d at %d", x1, x2, y);
 #endif
 
-	// from r_splats's R_RenderFloorSplat
-	if (x1 >= vid.width) x1 = vid.width - 1;
-
-	angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
-	planecos = FINECOSINE(angle);
-	planesin = FINESINE(angle);
+	if (x1 >= vid.width)
+		x1 = vid.width - 1;
 
-	if (planeheight != cachedheight[y])
+	if (!currentplane->slope)
 	{
-		cachedheight[y] = planeheight;
-		distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
-		ds_xstep = cachedxstep[y] = FixedMul(distance, basexscale);
-		ds_ystep = cachedystep[y] = FixedMul(distance, baseyscale);
+		angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
+		planecos = FINECOSINE(angle);
+		planesin = FINESINE(angle);
 
-		if ((span = abs(centery-y)))
+		if (planeheight != cachedheight[y])
 		{
-			ds_xstep = cachedxstep[y] = FixedMul(planesin, planeheight) / span;
-			ds_ystep = cachedystep[y] = FixedMul(planecos, planeheight) / span;
+			cachedheight[y] = planeheight;
+			cacheddistance[y] = distance = FixedMul(planeheight, yslope[y]);
+			span = abs(centery - y);
+
+			if (span) // don't divide by zero
+			{
+				ds_xstep = FixedMul(planesin, planeheight) / span;
+				ds_ystep = FixedMul(planecos, planeheight) / span;
+			}
+			else
+			{
+				ds_xstep = FixedMul(distance, basexscale);
+				ds_ystep = FixedMul(distance, baseyscale);
+			}
+
+			cachedxstep[y] = ds_xstep;
+			cachedystep[y] = ds_ystep;
+		}
+		else
+		{
+			distance = cacheddistance[y];
+			ds_xstep = cachedxstep[y];
+			ds_ystep = cachedystep[y];
 		}
-	}
-	else
-	{
-		distance = cacheddistance[y];
-		ds_xstep = cachedxstep[y];
-		ds_ystep = cachedystep[y];
-	}
 
-	ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
-	ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
+		ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
+		ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
+	}
 
-#ifndef NOWATER
-	if (itswater)
+	// Water ripple effect
+	if (planeripple.active)
 	{
 		// Needed for ds_bgofs
-		R_PlaneRipple(currentplane, y, planeheight);
+		R_CalculatePlaneRipple(currentplane, y, planeheight, (!currentplane->slope));
 
 		if (currentplane->slope)
 		{
@@ -210,25 +228,25 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 		}
 		else
 		{
-			ds_xfrac += ripple_xfrac;
-			ds_yfrac += ripple_yfrac;
+			ds_xfrac += planeripple.xfrac;
+			ds_yfrac += planeripple.yfrac;
 		}
 
-		if (y+ds_bgofs>=viewheight)
+		if ((y + ds_bgofs) >= viewheight)
 			ds_bgofs = viewheight-y-1;
-		if (y+ds_bgofs<0)
+		if ((y + ds_bgofs) < 0)
 			ds_bgofs = -y;
 	}
-#endif
-
-	pindex = distance >> LIGHTZSHIFT;
-	if (pindex >= MAXLIGHTZ)
-		pindex = MAXLIGHTZ - 1;
 
 	if (currentplane->slope)
 		ds_colormap = colormaps;
 	else
+	{
+		pindex = distance >> LIGHTZSHIFT;
+		if (pindex >= MAXLIGHTZ)
+			pindex = MAXLIGHTZ - 1;
 		ds_colormap = planezlight[pindex];
+	}
 
 	if (currentplane->extra_colormap)
 		ds_colormap = currentplane->extra_colormap->colormap + (ds_colormap - colormaps);
@@ -379,28 +397,30 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 		lightlevel = 0;
 	}
 
-	// New visplane algorithm uses hash table
-	hash = visplane_hash(picnum, lightlevel, height);
-
-	for (check = visplanes[hash]; check; check = check->next)
+	if (!pfloor)
 	{
-		if (check->polyobj && pfloor)
-			continue;
-		if (polyobj != check->polyobj)
-			continue;
-		if (height == check->height && picnum == check->picnum
-			&& lightlevel == check->lightlevel
-			&& xoff == check->xoffs && yoff == check->yoffs
-			&& planecolormap == check->extra_colormap
-			&& !pfloor && !check->ffloor
-			&& check->viewx == viewx && check->viewy == viewy && check->viewz == viewz
-			&& check->viewangle == viewangle
-			&& check->plangle == plangle
-			&& check->slope == slope)
+		hash = visplane_hash(picnum, lightlevel, height);
+		for (check = visplanes[hash]; check; check = check->next)
 		{
-			return check;
+			if (polyobj != check->polyobj)
+				continue;
+			if (height == check->height && picnum == check->picnum
+				&& lightlevel == check->lightlevel
+				&& xoff == check->xoffs && yoff == check->yoffs
+				&& planecolormap == check->extra_colormap
+				&& check->viewx == viewx && check->viewy == viewy && check->viewz == viewz
+				&& check->viewangle == viewangle
+				&& check->plangle == plangle
+				&& check->slope == slope)
+			{
+				return check;
+			}
 		}
 	}
+	else
+	{
+		hash = MAXVISPLANES - 1;
+	}
 
 	check = new_visplane(hash);
 
@@ -470,9 +490,17 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop)
 	}
 	else /* Cannot use existing plane; create a new one */
 	{
-		unsigned hash =
-			visplane_hash(pl->picnum, pl->lightlevel, pl->height);
-		visplane_t *new_pl = new_visplane(hash);
+		visplane_t *new_pl;
+		if (pl->ffloor)
+		{
+			new_pl = new_visplane(MAXVISPLANES - 1);
+		}
+		else
+		{
+			unsigned hash =
+				visplane_hash(pl->picnum, pl->lightlevel, pl->height);
+			new_pl = new_visplane(hash);
+		}
 
 		new_pl->height = pl->height;
 		new_pl->picnum = pl->picnum;
@@ -579,11 +607,10 @@ void R_MakeSpans(INT32 x, INT32 t1, INT32 b1, INT32 t2, INT32 b2)
 void R_DrawPlanes(void)
 {
 	visplane_t *pl;
+	angle_t va = viewangle;
 	INT32 i;
 
-	// Note: are these two lines really needed?
-	// R_DrawSinglePlane and R_DrawSkyPlane do span/column drawer resets themselves anyway
-	spanfunc = spanfuncs[BASEDRAWFUNC];
+	R_UpdatePlaneRipple();
 
 	for (i = 0; i < MAXVISPLANES; i++, pl++)
 	{
@@ -595,10 +622,8 @@ void R_DrawPlanes(void)
 			R_DrawSinglePlane(pl);
 		}
 	}
-#ifndef NOWATER
-	ds_waterofs = (leveltime & 1)*16384;
-	wtofs = leveltime * 140;
-#endif
+
+	viewangle = va;
 }
 
 // R_DrawSkyPlane
@@ -644,228 +669,48 @@ static void R_DrawSkyPlane(visplane_t *pl)
 	}
 }
 
-//
-// R_CheckPowersOfTwo
-//
-// Self-explanatory?
-//
-boolean R_CheckPowersOfTwo(void)
-{
-	boolean wpow2 = (!(ds_flatwidth & (ds_flatwidth - 1)));
-	boolean hpow2 = (!(ds_flatheight & (ds_flatheight - 1)));
-
-	// Initially, the flat isn't powers-of-two-sized.
-	ds_powersoftwo = false;
-
-	// But if the width and height are powers of two,
-	// and are EQUAL, then it's okay :]
-	if ((ds_flatwidth == ds_flatheight) && (wpow2 && hpow2))
-		ds_powersoftwo = true;
-
-	// Just return ds_powersoftwo.
-	return ds_powersoftwo;
-}
-
-//
-// R_CheckFlatLength
-//
-// Determine the flat's dimensions from the lump length.
-//
-void R_CheckFlatLength(size_t size)
-{
-	switch (size)
-	{
-		case 4194304: // 2048x2048 lump
-			nflatmask = 0x3FF800;
-			nflatxshift = 21;
-			nflatyshift = 10;
-			nflatshiftup = 5;
-			ds_flatwidth = ds_flatheight = 2048;
-			break;
-		case 1048576: // 1024x1024 lump
-			nflatmask = 0xFFC00;
-			nflatxshift = 22;
-			nflatyshift = 12;
-			nflatshiftup = 6;
-			ds_flatwidth = ds_flatheight = 1024;
-			break;
-		case 262144:// 512x512 lump
-			nflatmask = 0x3FE00;
-			nflatxshift = 23;
-			nflatyshift = 14;
-			nflatshiftup = 7;
-			ds_flatwidth = ds_flatheight = 512;
-			break;
-		case 65536: // 256x256 lump
-			nflatmask = 0xFF00;
-			nflatxshift = 24;
-			nflatyshift = 16;
-			nflatshiftup = 8;
-			ds_flatwidth = ds_flatheight = 256;
-			break;
-		case 16384: // 128x128 lump
-			nflatmask = 0x3F80;
-			nflatxshift = 25;
-			nflatyshift = 18;
-			nflatshiftup = 9;
-			ds_flatwidth = ds_flatheight = 128;
-			break;
-		case 1024: // 32x32 lump
-			nflatmask = 0x3E0;
-			nflatxshift = 27;
-			nflatyshift = 22;
-			nflatshiftup = 11;
-			ds_flatwidth = ds_flatheight = 32;
-			break;
-		default: // 64x64 lump
-			nflatmask = 0xFC0;
-			nflatxshift = 26;
-			nflatyshift = 20;
-			nflatshiftup = 10;
-			ds_flatwidth = ds_flatheight = 64;
-			break;
-	}
-}
-
-//
-// R_GenerateFlat
-//
-// Generate a flat from specified width and height.
-//
-static UINT8 *R_GenerateFlat(UINT16 width, UINT16 height)
-{
-	UINT8 *flat = Z_Malloc(width * height, PU_LEVEL, NULL);
-	memset(flat, TRANSPARENTPIXEL, width * height);
-	return flat;
-}
-
-//
-// R_GetTextureFlat
-//
-// Convert a texture or patch to a flat.
-//
-static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boolean ispng)
-{
-	UINT8 *flat;
-	textureflat_t *texflat = &texflats[levelflat->u.texture.num];
-	patch_t *patch = NULL;
-	boolean texturechanged = (leveltexture ? (levelflat->u.texture.num != levelflat->u.texture.lastnum) : false);
-
-	(void)ispng;
-
-	// Check if the texture changed.
-	if (leveltexture && (!texturechanged))
-	{
-		if (texflat != NULL && texflat->flat)
-		{
-			flat = texflat->flat;
-			ds_flatwidth = texflat->width;
-			ds_flatheight = texflat->height;
-			texturechanged = false;
-		}
-		else
-			texturechanged = true;
-	}
-
-	// If the texture changed, or the patch doesn't exist, convert either of them to a flat.
-	if (levelflat->flatpatch == NULL || texturechanged)
-	{
-		// Level texture
-		if (leveltexture)
-		{
-			texture_t *texture = textures[levelflat->u.texture.num];
-			texflat->width = ds_flatwidth = texture->width;
-			texflat->height = ds_flatheight = texture->height;
-
-			texflat->flat = R_GenerateFlat(ds_flatwidth, ds_flatheight);
-			R_TextureToFlat(levelflat->u.texture.num, texflat->flat);
-			flat = texflat->flat;
-
-			levelflat->flatpatch = flat;
-			levelflat->width = ds_flatwidth;
-			levelflat->height = ds_flatheight;
-		}
-		// Patch (never happens yet)
-		else
-		{
-			patch = (patch_t *)ds_source;
-#ifndef NO_PNG_LUMPS
-			if (ispng)
-			{
-				levelflat->flatpatch = R_PNGToFlat(&levelflat->width, &levelflat->height, ds_source, W_LumpLength(levelflat->u.flat.lumpnum));
-				levelflat->topoffset = levelflat->leftoffset = 0;
-				ds_flatwidth = levelflat->width;
-				ds_flatheight = levelflat->height;
-			}
-			else
-#endif
-			{
-				levelflat->width = ds_flatwidth = SHORT(patch->width);
-				levelflat->height = ds_flatheight = SHORT(patch->height);
-
-				levelflat->topoffset = patch->topoffset * FRACUNIT;
-				levelflat->leftoffset = patch->leftoffset * FRACUNIT;
-
-				levelflat->flatpatch = R_GenerateFlat(ds_flatwidth, ds_flatheight);
-				R_PatchToFlat(patch, levelflat->flatpatch);
-			}
-			flat = levelflat->flatpatch;
-		}
-	}
-	else
-	{
-		flat = levelflat->flatpatch;
-		ds_flatwidth = levelflat->width;
-		ds_flatheight = levelflat->height;
-	}
-
-	xoffs += levelflat->leftoffset;
-	yoffs += levelflat->topoffset;
-
-	levelflat->u.texture.lastnum = levelflat->u.texture.num;
-	return flat;
-}
-
-static void R_SlopeVectors(visplane_t *pl, INT32 i, float fudge)
+// Potentially override other stuff for now cus we're mean. :< But draw a slope plane!
+// I copied ZDoom's code and adapted it to SRB2... -Red
+void R_CalculateSlopeVectors(pslope_t *slope, fixed_t planeviewx, fixed_t planeviewy, fixed_t planeviewz, fixed_t planexscale, fixed_t planeyscale, fixed_t planexoffset, fixed_t planeyoffset, angle_t planeviewangle, angle_t planeangle, float fudge)
 {
-	// Potentially override other stuff for now cus we're mean. :< But draw a slope plane!
-	// I copied ZDoom's code and adapted it to SRB2... -Red
 	floatv3_t p, m, n;
 	float ang;
 	float vx, vy, vz;
+	float xscale = FIXED_TO_FLOAT(planexscale);
+	float yscale = FIXED_TO_FLOAT(planeyscale);
 	// compiler complains when P_GetSlopeZAt is used in FLOAT_TO_FIXED directly
 	// use this as a temp var to store P_GetSlopeZAt's return value each time
 	fixed_t temp;
 
-	vx = FIXED_TO_FLOAT(pl->viewx+xoffs);
-	vy = FIXED_TO_FLOAT(pl->viewy-yoffs);
-	vz = FIXED_TO_FLOAT(pl->viewz);
+	vx = FIXED_TO_FLOAT(planeviewx+planexoffset);
+	vy = FIXED_TO_FLOAT(planeviewy-planeyoffset);
+	vz = FIXED_TO_FLOAT(planeviewz);
 
-	temp = P_GetSlopeZAt(pl->slope, pl->viewx, pl->viewy);
+	temp = P_GetSlopeZAt(slope, planeviewx, planeviewy);
 	zeroheight = FIXED_TO_FLOAT(temp);
 
 	// p is the texture origin in view space
 	// Don't add in the offsets at this stage, because doing so can result in
 	// errors if the flat is rotated.
-	ang = ANG2RAD(ANGLE_270 - pl->viewangle);
+	ang = ANG2RAD(ANGLE_270 - planeviewangle);
 	p.x = vx * cos(ang) - vy * sin(ang);
 	p.z = vx * sin(ang) + vy * cos(ang);
-	temp = P_GetSlopeZAt(pl->slope, -xoffs, yoffs);
+	temp = P_GetSlopeZAt(slope, -planexoffset, planeyoffset);
 	p.y = FIXED_TO_FLOAT(temp) - vz;
 
 	// m is the v direction vector in view space
-	ang = ANG2RAD(ANGLE_180 - (pl->viewangle + pl->plangle));
-	m.x = cos(ang);
-	m.z = sin(ang);
+	ang = ANG2RAD(ANGLE_180 - (planeviewangle + planeangle));
+	m.x = yscale * cos(ang);
+	m.z = yscale * sin(ang);
 
 	// n is the u direction vector in view space
-	n.x = sin(ang);
-	n.z = -cos(ang);
+	n.x = xscale * sin(ang);
+	n.z = -xscale * cos(ang);
 
-	ang = ANG2RAD(pl->plangle);
-	temp = P_GetSlopeZAt(pl->slope, pl->viewx + FLOAT_TO_FIXED(sin(ang)), pl->viewy + FLOAT_TO_FIXED(cos(ang)));
+	ang = ANG2RAD(planeangle);
+	temp = P_GetSlopeZAt(slope, planeviewx + FLOAT_TO_FIXED(yscale * sin(ang)), planeviewy + FLOAT_TO_FIXED(yscale * cos(ang)));
 	m.y = FIXED_TO_FLOAT(temp) - zeroheight;
-	temp = P_GetSlopeZAt(pl->slope, pl->viewx + FLOAT_TO_FIXED(cos(ang)), pl->viewy - FLOAT_TO_FIXED(sin(ang)));
+	temp = P_GetSlopeZAt(slope, planeviewx + FLOAT_TO_FIXED(xscale * cos(ang)), planeviewy - FLOAT_TO_FIXED(xscale * sin(ang)));
 	n.y = FIXED_TO_FLOAT(temp) - zeroheight;
 
 	if (ds_powersoftwo)
@@ -881,50 +726,69 @@ static void R_SlopeVectors(visplane_t *pl, INT32 i, float fudge)
 
 	// Eh. I tried making this stuff fixed-point and it exploded on me. Here's a macro for the only floating-point vector function I recall using.
 #define CROSS(d, v1, v2) \
-d.x = (v1.y * v2.z) - (v1.z * v2.y);\
-d.y = (v1.z * v2.x) - (v1.x * v2.z);\
-d.z = (v1.x * v2.y) - (v1.y * v2.x)
-	CROSS(ds_su[i], p, m);
-	CROSS(ds_sv[i], p, n);
-	CROSS(ds_sz[i], m, n);
+d->x = (v1.y * v2.z) - (v1.z * v2.y);\
+d->y = (v1.z * v2.x) - (v1.x * v2.z);\
+d->z = (v1.x * v2.y) - (v1.y * v2.x)
+		CROSS(ds_sup, p, m);
+		CROSS(ds_svp, p, n);
+		CROSS(ds_szp, m, n);
 #undef CROSS
 
-	ds_su[i].z *= focallengthf;
-	ds_sv[i].z *= focallengthf;
-	ds_sz[i].z *= focallengthf;
+	ds_sup->z *= focallengthf;
+	ds_svp->z *= focallengthf;
+	ds_szp->z *= focallengthf;
 
 	// Premultiply the texture vectors with the scale factors
 #define SFMULT 65536.f
 	if (ds_powersoftwo)
 	{
-		ds_su[i].x *= (SFMULT * (1<<nflatshiftup));
-		ds_su[i].y *= (SFMULT * (1<<nflatshiftup));
-		ds_su[i].z *= (SFMULT * (1<<nflatshiftup));
-		ds_sv[i].x *= (SFMULT * (1<<nflatshiftup));
-		ds_sv[i].y *= (SFMULT * (1<<nflatshiftup));
-		ds_sv[i].z *= (SFMULT * (1<<nflatshiftup));
+		ds_sup->x *= (SFMULT * (1<<nflatshiftup));
+		ds_sup->y *= (SFMULT * (1<<nflatshiftup));
+		ds_sup->z *= (SFMULT * (1<<nflatshiftup));
+		ds_svp->x *= (SFMULT * (1<<nflatshiftup));
+		ds_svp->y *= (SFMULT * (1<<nflatshiftup));
+		ds_svp->z *= (SFMULT * (1<<nflatshiftup));
 	}
 	else
 	{
 		// Lactozilla: I'm essentially multiplying the vectors by FRACUNIT...
-		ds_su[i].x *= SFMULT;
-		ds_su[i].y *= SFMULT;
-		ds_su[i].z *= SFMULT;
-		ds_sv[i].x *= SFMULT;
-		ds_sv[i].y *= SFMULT;
-		ds_sv[i].z *= SFMULT;
+		ds_sup->x *= SFMULT;
+		ds_sup->y *= SFMULT;
+		ds_sup->z *= SFMULT;
+		ds_svp->x *= SFMULT;
+		ds_svp->y *= SFMULT;
+		ds_svp->z *= SFMULT;
 	}
 #undef SFMULT
 }
 
+void R_SetTiltedSpan(INT32 span)
+{
+	if (ds_su == NULL)
+		ds_su = Z_Malloc(sizeof(*ds_su) * vid.height, PU_STATIC, NULL);
+	if (ds_sv == NULL)
+		ds_sv = Z_Malloc(sizeof(*ds_sv) * vid.height, PU_STATIC, NULL);
+	if (ds_sz == NULL)
+		ds_sz = Z_Malloc(sizeof(*ds_sz) * vid.height, PU_STATIC, NULL);
+
+	ds_sup = &ds_su[span];
+	ds_svp = &ds_sv[span];
+	ds_szp = &ds_sz[span];
+}
+
+static void R_SetSlopePlaneVectors(visplane_t *pl, INT32 y, fixed_t xoff, fixed_t yoff, float fudge)
+{
+	R_SetTiltedSpan(y);
+	R_CalculateSlopeVectors(pl->slope, pl->viewx, pl->viewy, pl->viewz, FRACUNIT, FRACUNIT, xoff, yoff, pl->viewangle, pl->plangle, fudge);
+}
+
 void R_DrawSinglePlane(visplane_t *pl)
 {
-	UINT8 *flat;
+	levelflat_t *levelflat;
 	INT32 light = 0;
 	INT32 x;
 	INT32 stop, angle;
 	ffloor_t *rover;
-	levelflat_t *levelflat;
 	int type;
 	int spanfunctype = BASEDRAWFUNC;
 
@@ -938,9 +802,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 		return;
 	}
 
-#ifndef NOWATER
-	itswater = false;
-#endif
+	planeripple.active = false;
 	spanfunc = spanfuncs[BASEDRAWFUNC];
 
 	if (pl->polyobj)
@@ -951,7 +813,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 		else if (pl->polyobj->translucency > 0)
 		{
 			spanfunctype = (pl->polyobj->flags & POF_SPLAT) ? SPANDRAWFUNC_TRANSSPLAT : SPANDRAWFUNC_TRANS;
-			ds_transmap = transtables + ((pl->polyobj->translucency-1)<<FF_TRANSSHIFT);
+			ds_transmap = R_GetTranslucencyTable(pl->polyobj->translucency);
 		}
 		else if (pl->polyobj->flags & POF_SPLAT) // Opaque, but allow transparent flat pixels
 			spanfunctype = SPANDRAWFUNC_SPLAT;
@@ -990,23 +852,23 @@ void R_DrawSinglePlane(visplane_t *pl)
 				if (pl->ffloor->alpha < 12)
 					return; // Don't even draw it
 				else if (pl->ffloor->alpha < 38)
-					ds_transmap = transtables + ((tr_trans90-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans90);
 				else if (pl->ffloor->alpha < 64)
-					ds_transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans80);
 				else if (pl->ffloor->alpha < 89)
-					ds_transmap = transtables + ((tr_trans70-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans70);
 				else if (pl->ffloor->alpha < 115)
-					ds_transmap = transtables + ((tr_trans60-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans60);
 				else if (pl->ffloor->alpha < 140)
-					ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans50);
 				else if (pl->ffloor->alpha < 166)
-					ds_transmap = transtables + ((tr_trans40-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans40);
 				else if (pl->ffloor->alpha < 192)
-					ds_transmap = transtables + ((tr_trans30-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans30);
 				else if (pl->ffloor->alpha < 217)
-					ds_transmap = transtables + ((tr_trans20-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans20);
 				else if (pl->ffloor->alpha < 243)
-					ds_transmap = transtables + ((tr_trans10-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans10);
 				else // Opaque, but allow transparent flat pixels
 					spanfunctype = SPANDRAWFUNC_SPLAT;
 
@@ -1022,12 +884,12 @@ void R_DrawSinglePlane(visplane_t *pl)
 			}
 			else light = (pl->lightlevel >> LIGHTSEGSHIFT);
 
-	#ifndef NOWATER
 			if (pl->ffloor->flags & FF_RIPPLE)
 			{
 				INT32 top, bottom;
 
-				itswater = true;
+				planeripple.active = true;
+
 				if (spanfunctype == SPANDRAWFUNC_TRANS)
 				{
 					spanfunctype = SPANDRAWFUNC_WATER;
@@ -1047,26 +909,11 @@ void R_DrawSinglePlane(visplane_t *pl)
 										 vid.width, vid.width);
 				}
 			}
-	#endif
 		}
 		else
 			light = (pl->lightlevel >> LIGHTSEGSHIFT);
 	}
 
-	if (!pl->slope // Don't mess with angle on slopes! We'll handle this ourselves later
-		&& viewangle != pl->viewangle+pl->plangle)
-	{
-		memset(cachedheight, 0, sizeof (cachedheight));
-		angle = (pl->viewangle+pl->plangle-ANGLE_90)>>ANGLETOFINESHIFT;
-		basexscale = FixedDiv(FINECOSINE(angle),centerxfrac);
-		baseyscale = -FixedDiv(FINESINE(angle),centerxfrac);
-		viewangle = pl->viewangle+pl->plangle;
-	}
-
-	xoffs = pl->xoffs;
-	yoffs = pl->yoffs;
-	planeheight = abs(pl->height - pl->viewz);
-
 	currentplane = pl;
 	levelflat = &viewworld->flats[pl->picnum];
 
@@ -1077,35 +924,34 @@ void R_DrawSinglePlane(visplane_t *pl)
 		case LEVELFLAT_NONE:
 			return;
 		case LEVELFLAT_FLAT:
-			ds_source = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE);
+			ds_source = (UINT8 *)R_GetFlat(levelflat->u.flat.lumpnum);
 			R_CheckFlatLength(W_LumpLength(levelflat->u.flat.lumpnum));
 			// Raw flats always have dimensions that are powers-of-two numbers.
 			ds_powersoftwo = true;
 			break;
 		default:
-			switch (type)
-			{
-				case LEVELFLAT_TEXTURE:
-					/* Textures get cached differently and don't need ds_source */
-					ds_source = R_GetTextureFlat(levelflat, true, false);
-					break;
-				default:
-					ds_source = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_STATIC);
-					flat      = R_GetTextureFlat(levelflat, false,
-#ifndef NO_PNG_LUMPS
-							( type == LEVELFLAT_PNG )
-#else
-							false
-#endif
-					);
-					Z_ChangeTag(ds_source, PU_CACHE);
-					ds_source = flat;
-			}
+			ds_source = (UINT8 *)R_GetLevelFlat(levelflat);
+			if (!ds_source)
+				return;
 			// Check if this texture or patch has power-of-two dimensions.
 			if (R_CheckPowersOfTwo())
 				R_CheckFlatLength(ds_flatwidth * ds_flatheight);
 	}
 
+	if (!pl->slope // Don't mess with angle on slopes! We'll handle this ourselves later
+		&& viewangle != pl->viewangle+pl->plangle)
+	{
+		memset(cachedheight, 0, sizeof (cachedheight));
+		angle = (pl->viewangle+pl->plangle-ANGLE_90)>>ANGLETOFINESHIFT;
+		basexscale = FixedDiv(FINECOSINE(angle),centerxfrac);
+		baseyscale = -FixedDiv(FINESINE(angle),centerxfrac);
+		viewangle = pl->viewangle+pl->plangle;
+	}
+
+	xoffs = pl->xoffs;
+	yoffs = pl->yoffs;
+	planeheight = abs(pl->height - pl->viewz);
+
 	if (light >= LIGHTLEVELS)
 		light = LIGHTLEVELS-1;
 
@@ -1165,50 +1011,41 @@ void R_DrawSinglePlane(visplane_t *pl)
 				xoffs -= (pl->slope->o.x + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
 				yoffs += (pl->slope->o.y + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
 			}
+
 			xoffs = (fixed_t)(xoffs*fudgecanyon);
 			yoffs = (fixed_t)(yoffs/fudgecanyon);
 		}
 
-		ds_sup = &ds_su[0];
-		ds_svp = &ds_sv[0];
-		ds_szp = &ds_sz[0];
-
-#ifndef NOWATER
-		if (itswater)
+		if (planeripple.active)
 		{
-			INT32 i;
 			fixed_t plheight = abs(P_GetSlopeZAt(pl->slope, pl->viewx, pl->viewy) - pl->viewz);
-			fixed_t rxoffs = xoffs;
-			fixed_t ryoffs = yoffs;
 
 			R_PlaneBounds(pl);
 
-			for (i = pl->high; i < pl->low; i++)
+			for (x = pl->high; x < pl->low; x++)
 			{
-				R_PlaneRipple(pl, i, plheight);
-				xoffs = rxoffs + ripple_xfrac;
-				yoffs = ryoffs + ripple_yfrac;
-				R_SlopeVectors(pl, i, fudgecanyon);
+				R_CalculatePlaneRipple(pl, x, plheight, true);
+				R_SetSlopePlaneVectors(pl, x, (xoffs + planeripple.xfrac), (yoffs + planeripple.yfrac), fudgecanyon);
 			}
-
-			xoffs = rxoffs;
-			yoffs = ryoffs;
 		}
 		else
-#endif
-			R_SlopeVectors(pl, 0, fudgecanyon);
+			R_SetSlopePlaneVectors(pl, 0, xoffs, yoffs, fudgecanyon);
 
-#ifndef NOWATER
-		if (itswater && (spanfunctype == SPANDRAWFUNC_WATER))
-			spanfunctype = SPANDRAWFUNC_TILTEDWATER;
-		else
-#endif
-		if (spanfunctype == SPANDRAWFUNC_TRANS)
-			spanfunctype = SPANDRAWFUNC_TILTEDTRANS;
-		else if (spanfunctype == SPANDRAWFUNC_SPLAT)
-			spanfunctype = SPANDRAWFUNC_TILTEDSPLAT;
-		else
-			spanfunctype = SPANDRAWFUNC_TILTED;
+		switch (spanfunctype)
+		{
+			case SPANDRAWFUNC_WATER:
+				spanfunctype = SPANDRAWFUNC_TILTEDWATER;
+				break;
+			case SPANDRAWFUNC_TRANS:
+				spanfunctype = SPANDRAWFUNC_TILTEDTRANS;
+				break;
+			case SPANDRAWFUNC_SPLAT:
+				spanfunctype = SPANDRAWFUNC_TILTEDSPLAT;
+				break;
+			default:
+				spanfunctype = SPANDRAWFUNC_TILTED;
+				break;
+		}
 
 		planezlight = scalelight[light];
 	}
@@ -1268,7 +1105,7 @@ using the palette colors.
 	if (spanfunc == spanfuncs[BASEDRAWFUNC])
 	{
 		INT32 i;
-		ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+		ds_transmap = R_GetTranslucencyTable(tr_trans50);
 		spanfunc = spanfuncs[SPANDRAWFUNC_TRANS];
 		for (i=0; i<4; i++)
 		{
diff --git a/src/r_plane.h b/src/r_plane.h
index 67fa19f38bedd3535f6ad1ec6f501139aa8bd426..0d11c5b721c2ffadcaee26f4fbd830a6b2698c0a 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -16,9 +16,13 @@
 
 #include "screen.h" // needs MAXVIDWIDTH/MAXVIDHEIGHT
 #include "r_data.h"
+#include "r_textures.h"
 #include "p_polyobj.h"
 
-#define MAXVISPLANES 512
+#define VISPLANEHASHBITS 9
+#define VISPLANEHASHMASK ((1<<VISPLANEHASHBITS)-1)
+// the last visplane list is outside of the hash table and is used for fof planes
+#define MAXVISPLANES ((1<<VISPLANEHASHBITS)+1)
 
 //
 // Now what is a visplane, anyway?
@@ -83,11 +87,18 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_PlaneBounds(visplane_t *plane);
 
-// Draws a single visplane.
-void R_DrawSinglePlane(visplane_t *pl);
 void R_CheckFlatLength(size_t size);
 boolean R_CheckPowersOfTwo(void);
 
+// Draws a single visplane.
+void R_DrawSinglePlane(visplane_t *pl);
+
+// Calculates the slope vectors needed for tilted span drawing.
+void R_CalculateSlopeVectors(pslope_t *slope, fixed_t planeviewx, fixed_t planeviewy, fixed_t planeviewz, fixed_t planexscale, fixed_t planeyscale, fixed_t planexoffset, fixed_t planeyoffset, angle_t planeviewangle, angle_t planeangle, float fudge);
+
+// Sets the slope vector pointers for the current tilted span.
+void R_SetTiltedSpan(INT32 span);
+
 typedef struct planemgr_s
 {
 	visplane_t *plane;
diff --git a/src/r_portal.h b/src/r_portal.h
index 406b98d10c48a42fccdc348dc701369fc6be415e..e665a26e63d46cf0431c6e46a52cb64bddd0bbd3 100644
--- a/src/r_portal.h
+++ b/src/r_portal.h
@@ -15,6 +15,7 @@
 #define __R_PORTAL__
 
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_plane.h" // visplanes
 
 /** Portal structure for the software renderer.
diff --git a/src/r_segs.c b/src/r_segs.c
index c6d2d3ed13ee8608807b497cdb0d23889933f653..10e336d348a391da00e1af6485a3df466b768326 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -26,6 +26,7 @@
 #include "p_slopes.h"
 #include "p_world.h"
 #include "console.h" // con_clipviewtop
+#include "taglist.h"
 
 // OPTIMIZE: closed two sided lines as single sided
 
@@ -56,6 +57,15 @@ static INT32 worldtop, worldbottom, worldhigh, worldlow;
 static INT32 worldtopslope, worldbottomslope, worldhighslope, worldlowslope; // worldtop/bottom at end of slope
 static fixed_t rw_toptextureslide, rw_midtextureslide, rw_bottomtextureslide; // Defines how to adjust Y offsets along the wall for slopes
 static fixed_t rw_midtextureback, rw_midtexturebackslide; // Values for masked midtexture height calculation
+
+// Lactozilla: 3D floor clipping
+static boolean rw_floormarked = false;
+static boolean rw_ceilingmarked = false;
+
+static INT32 *rw_silhouette = NULL;
+static fixed_t *rw_tsilheight = NULL;
+static fixed_t *rw_bsilheight = NULL;
+
 static fixed_t pixhigh, pixlow, pixhighstep, pixlowstep;
 static fixed_t topfrac, topstep;
 static fixed_t bottomfrac, bottomstep;
@@ -64,170 +74,6 @@ static lighttable_t **walllights;
 static INT16 *maskedtexturecol;
 static fixed_t *maskedtextureheight = NULL;
 
-// ==========================================================================
-// R_Splats Wall Splats Drawer
-// ==========================================================================
-
-#ifdef WALLSPLATS
-static INT16 last_ceilingclip[MAXVIDWIDTH];
-static INT16 last_floorclip[MAXVIDWIDTH];
-
-static void R_DrawSplatColumn(column_t *column)
-{
-	INT32 topscreen, bottomscreen;
-	fixed_t basetexturemid;
-	INT32 topdelta, prevdelta = -1;
-
-	basetexturemid = dc_texturemid;
-
-	for (; column->topdelta != 0xff ;)
-	{
-		// calculate unclipped screen coordinates for post
-		topdelta = column->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		topscreen = sprtopscreen + spryscale*topdelta;
-		bottomscreen = topscreen + spryscale*column->length;
-
-		dc_yl = (topscreen+FRACUNIT-1)>>FRACBITS;
-		dc_yh = (bottomscreen-1)>>FRACBITS;
-
-		if (dc_yh >= last_floorclip[dc_x])
-			dc_yh = last_floorclip[dc_x] - 1;
-		if (dc_yl <= last_ceilingclip[dc_x])
-			dc_yl = last_ceilingclip[dc_x] + 1;
-		if (dc_yl <= dc_yh && dl_yh < vid.height && yh > 0)
-		{
-			dc_source = (UINT8 *)column + 3;
-			dc_texturemid = basetexturemid - (topdelta<<FRACBITS);
-
-			// Drawn by R_DrawColumn.
-			colfunc();
-		}
-		column = (column_t *)((UINT8 *)column + column->length + 4);
-	}
-
-	dc_texturemid = basetexturemid;
-}
-
-static void R_DrawWallSplats(void)
-{
-	wallsplat_t *splat;
-	seg_t *seg;
-	angle_t angle, angle1, angle2;
-	INT32 x1, x2;
-	size_t pindex;
-	column_t *col;
-	patch_t *patch;
-	fixed_t texturecolumn;
-
-	splat = (wallsplat_t *)linedef->splats;
-
-	I_Assert(splat != NULL);
-
-	seg = ds_p->curline;
-
-	// draw all splats from the line that touches the range of the seg
-	for (; splat; splat = splat->next)
-	{
-		angle1 = R_PointToAngle(splat->v1.x, splat->v1.y);
-		angle2 = R_PointToAngle(splat->v2.x, splat->v2.y);
-		angle1 = (angle1 - viewangle + ANGLE_90)>>ANGLETOFINESHIFT;
-		angle2 = (angle2 - viewangle + ANGLE_90)>>ANGLETOFINESHIFT;
-		// out of the viewangletox lut
-		/// \todo clip it to the screen
-		if (angle1 > FINEANGLES/2 || angle2 > FINEANGLES/2)
-			continue;
-		x1 = viewangletox[angle1];
-		x2 = viewangletox[angle2];
-
-		if (x1 >= x2)
-			continue; // does not cross a pixel
-
-		// splat is not in this seg range
-		if (x2 < ds_p->x1 || x1 > ds_p->x2)
-			continue;
-
-		if (x1 < ds_p->x1)
-			x1 = ds_p->x1;
-		if (x2 > ds_p->x2)
-			x2 = ds_p->x2;
-		if (x2 <= x1)
-			continue;
-
-		// calculate incremental stepping values for texture edges
-		rw_scalestep = ds_p->scalestep;
-		spryscale = ds_p->scale1 + (x1 - ds_p->x1)*rw_scalestep;
-		mfloorclip = floorclip;
-		mceilingclip = ceilingclip;
-
-		patch = W_CachePatchNum(splat->patch, PU_PATCH);
-
-		dc_texturemid = splat->top + (SHORT(patch->height)<<(FRACBITS-1)) - viewz;
-		if (splat->yoffset)
-			dc_texturemid += *splat->yoffset;
-
-		sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
-
-		// set drawing mode
-		switch (splat->flags & SPLATDRAWMODE_MASK)
-		{
-			case SPLATDRAWMODE_OPAQUE:
-				colfunc = colfuncs[BASEDRAWFUNC];
-				break;
-			case SPLATDRAWMODE_TRANS:
-				if (!cv_translucency.value)
-					colfunc = colfuncs[BASEDRAWFUNC];
-				else
-				{
-					dc_transmap = transtables + ((tr_trans50 - 1)<<FF_TRANSSHIFT);
-					colfunc = colfuncs[COLDRAWFUNC_FUZZY];
-				}
-
-				break;
-			case SPLATDRAWMODE_SHADE:
-				colfunc = colfuncs[COLDRAWFUNC_SHADE];
-				break;
-		}
-
-		dc_texheight = 0;
-
-		// draw the columns
-		for (dc_x = x1; dc_x <= x2; dc_x++, spryscale += rw_scalestep)
-		{
-			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
-			if (pindex >= MAXLIGHTSCALE)
-				pindex = MAXLIGHTSCALE - 1;
-			dc_colormap = walllights[pindex];
-
-			if (frontsector->extra_colormap)
-				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
-
-			sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
-			dc_iscale = 0xffffffffu / (unsigned)spryscale;
-
-			// find column of patch, from perspective
-			angle = (rw_centerangle + xtoviewangle[dc_x])>>ANGLETOFINESHIFT;
-				texturecolumn = rw_offset2 - splat->offset
-					- FixedMul(FINETANGENT(angle), rw_distance);
-
-			// FIXME!
-			texturecolumn >>= FRACBITS;
-			if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
-				continue;
-
-			// draw the texture
-			col = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
-			R_DrawSplatColumn(col);
-		}
-	} // next splat
-
-	colfunc = colfuncs[BASEDRAWFUNC];
-}
-
-#endif //WALLSPLATS
-
 // ==========================================================================
 // R_RenderMaskedSegRange
 // ==========================================================================
@@ -275,6 +121,11 @@ static void R_Render2sidedMultiPatchColumn(column_t *column)
 	}
 }
 
+transnum_t R_GetLinedefTransTable(fixed_t alpha)
+{
+	return (20*(FRACUNIT - alpha - 1) + FRACUNIT) >> (FRACBITS+1);
+}
+
 void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 {
 	size_t pindex;
@@ -301,38 +152,31 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	texnum = R_GetTextureNum(curline->sidedef->midtexture);
 	windowbottom = windowtop = sprbotscreen = INT32_MAX;
 
-	// hack translucent linedef types (900-909 for transtables 1-9)
 	ldef = curline->linedef;
-	switch (ldef->special)
+	if (!ldef->alpha)
+		return;
+
+	if (ldef->alpha > 0 && ldef->alpha < FRACUNIT)
 	{
-		case 900:
-		case 901:
-		case 902:
-		case 903:
-		case 904:
-		case 905:
-		case 906:
-		case 907:
-		case 908:
-			dc_transmap = transtables + ((ldef->special-900)<<FF_TRANSSHIFT);
-			colfunc = colfuncs[COLDRAWFUNC_FUZZY];
-			break;
-		case 909:
-			colfunc = colfuncs[COLDRAWFUNC_FOG];
-			windowtop = frontsector->ceilingheight;
-			windowbottom = frontsector->floorheight;
-			break;
-		default:
-			colfunc = colfuncs[BASEDRAWFUNC];
-			break;
+		dc_transmap = R_GetTranslucencyTable(R_GetLinedefTransTable(ldef->alpha));
+		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
+
 	}
+	else if (ldef->special == 909)
+	{
+		colfunc = colfuncs[COLDRAWFUNC_FOG];
+		windowtop = frontsector->ceilingheight;
+		windowbottom = frontsector->floorheight;
+	}
+	else
+		colfunc = colfuncs[BASEDRAWFUNC];
 
 	if (curline->polyseg && curline->polyseg->translucency > 0)
 	{
 		if (curline->polyseg->translucency >= NUMTRANSMAPS)
 			return;
 
-		dc_transmap = transtables + ((curline->polyseg->translucency-1)<<FF_TRANSSHIFT);
+		dc_transmap = R_GetTranslucencyTable(curline->polyseg->translucency);
 		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
 	}
 
@@ -689,6 +533,19 @@ static void R_DrawRepeatFlippedMaskedColumn(column_t *col)
 	} while (sprtopscreen < sprbotscreen);
 }
 
+// Returns true if a fake floor is translucent.
+static boolean R_IsFFloorTranslucent(visffloor_t *pfloor)
+{
+	if (pfloor->polyobj)
+		return (pfloor->polyobj->translucency > 0);
+
+	// Polyobjects have no ffloors, and they're handled in the conditional above.
+	if (pfloor->ffloor != NULL)
+		return (pfloor->ffloor->flags & (FF_TRANSLUCENT|FF_FOG));
+
+	return false;
+}
+
 //
 // R_RenderThickSideRange
 // Renders all the thick sides in the given range.
@@ -747,23 +604,23 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		if (pfloor->alpha < 12)
 			return; // Don't even draw it
 		else if (pfloor->alpha < 38)
-			dc_transmap = transtables + ((tr_trans90-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans90);
 		else if (pfloor->alpha < 64)
-			dc_transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans80);
 		else if (pfloor->alpha < 89)
-			dc_transmap = transtables + ((tr_trans70-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans70);
 		else if (pfloor->alpha < 115)
-			dc_transmap = transtables + ((tr_trans60-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans60);
 		else if (pfloor->alpha < 140)
-			dc_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans50);
 		else if (pfloor->alpha < 166)
-			dc_transmap = transtables + ((tr_trans40-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans40);
 		else if (pfloor->alpha < 192)
-			dc_transmap = transtables + ((tr_trans30-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans30);
 		else if (pfloor->alpha < 217)
-			dc_transmap = transtables + ((tr_trans20-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans20);
 		else if (pfloor->alpha < 243)
-			dc_transmap = transtables + ((tr_trans10-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans10);
 		else
 			fuzzy = false; // Opaque
 
@@ -1191,7 +1048,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 
 // R_ExpandPlaneY
 //
-// A simple function to modify a vsplane's top and bottom for a particular column
+// A simple function to modify a visplane's top and bottom for a particular column
 // Sort of like R_ExpandPlane in r_plane.c, except this is vertical expansion
 static inline void R_ExpandPlaneY(visplane_t *pl, INT32 x, INT16 top, INT16 bottom)
 {
@@ -1201,6 +1058,14 @@ static inline void R_ExpandPlaneY(visplane_t *pl, INT32 x, INT16 top, INT16 bott
 	if (pl->bottom[x] < bottom) pl->bottom[x] = bottom;
 }
 
+// R_FFloorCanClip
+//
+// Returns true if a fake floor can clip a column away.
+static boolean R_FFloorCanClip(visffloor_t *pfloor)
+{
+	return (cv_ffloorclip.value && !R_IsFFloorTranslucent(pfloor) && !pfloor->polyobj);
+}
+
 //
 // R_RenderSegLoop
 // Draws zero, one, or two textures (and possibly a masked
@@ -1282,8 +1147,13 @@ static void R_RenderSegLoop (void)
 				R_ExpandPlaneY(floorplane, rw_x, top, bottom);
 		}
 
+		rw_floormarked = false;
+		rw_ceilingmarked = false;
+
 		if (numffloors)
 		{
+			INT16 fftop, ffbottom;
+
 			firstseg->frontscale[rw_x] = frontscale[rw_x];
 			top = ceilingclip[rw_x]+1; // PRBoom
 			bottom = floorclip[rw_x]-1; // PRBoom
@@ -1314,8 +1184,30 @@ static void R_RenderSegLoop (void)
 					{
 						if (top_w <= bottom_w)
 						{
-							ffloor[i].plane->top[rw_x] = (INT16)top_w;
-							ffloor[i].plane->bottom[rw_x] = (INT16)bottom_w;
+							fftop = (INT16)top_w;
+							ffbottom = (INT16)bottom_w;
+
+							ffloor[i].plane->top[rw_x] = fftop;
+							ffloor[i].plane->bottom[rw_x] = ffbottom;
+
+							// Lactozilla: Cull part of the column by the 3D floor if it can't be seen
+							// "bottom" is the top pixel of the floor column
+							if (ffbottom >= bottom-1 && R_FFloorCanClip(&ffloor[i]) && !curline->polyseg)
+							{
+								rw_floormarked = true;
+								floorclip[rw_x] = fftop;
+								if (yh > fftop)
+									yh = fftop;
+
+								if (markfloor && floorplane)
+									floorplane->top[rw_x] = bottom;
+
+								if (rw_silhouette)
+								{
+									(*rw_silhouette) |= SIL_BOTTOM;
+									(*rw_bsilheight) = INT32_MAX;
+								}
+							}
 						}
 					}
 				}
@@ -1340,8 +1232,30 @@ static void R_RenderSegLoop (void)
 					{
 						if (top_w <= bottom_w)
 						{
-							ffloor[i].plane->top[rw_x] = (INT16)top_w;
-							ffloor[i].plane->bottom[rw_x] = (INT16)bottom_w;
+							fftop = (INT16)top_w;
+							ffbottom = (INT16)bottom_w;
+
+							ffloor[i].plane->top[rw_x] = fftop;
+							ffloor[i].plane->bottom[rw_x] = ffbottom;
+
+							// Lactozilla: Cull part of the column by the 3D floor if it can't be seen
+							// "top" is the height of the ceiling column
+							if (fftop <= top+1 && R_FFloorCanClip(&ffloor[i]) && !curline->polyseg)
+							{
+								rw_ceilingmarked = true;
+								ceilingclip[rw_x] = ffbottom;
+								if (yl < ffbottom)
+									yl = ffbottom;
+
+								if (markceiling && ceilingplane)
+									ceilingplane->bottom[rw_x] = top;
+
+								if (rw_silhouette)
+								{
+									(*rw_silhouette) |= SIL_TOP;
+									(*rw_tsilheight) = INT32_MIN;
+								}
+							}
 						}
 					}
 				}
@@ -1447,20 +1361,25 @@ static void R_RenderSegLoop (void)
 
 				// dont draw anything more for this column, since
 				// a midtexture blocks the view
-				ceilingclip[rw_x] = (INT16)viewheight;
-				floorclip[rw_x] = -1;
+				if (!rw_ceilingmarked)
+					ceilingclip[rw_x] = (INT16)viewheight;
+				if (!rw_floormarked)
+					floorclip[rw_x] = -1;
 			}
 			else
 			{
 				// note: don't use min/max macros, since casting from INT32 to INT16 is involved here
-				if (markceiling)
+				if (markceiling && (!rw_ceilingmarked))
 					ceilingclip[rw_x] = (yl >= 0) ? ((yl > viewheight) ? (INT16)viewheight : (INT16)((INT16)yl - 1)) : -1;
-				if (markfloor)
+				if (markfloor && (!rw_floormarked))
 					floorclip[rw_x] = (yh < viewheight) ? ((yh < -1) ? -1 : (INT16)((INT16)yh + 1)) : (INT16)viewheight;
 			}
 		}
 		else
 		{
+			INT16 topclip = (yl >= 0) ? ((yl > viewheight) ? (INT16)viewheight : (INT16)((INT16)yl - 1)) : -1;
+			INT16 bottomclip = (yh < viewheight) ? ((yh < -1) ? -1 : (INT16)((INT16)yh + 1)) : (INT16)viewheight;
+
 			// two sided line
 			if (toptexture)
 			{
@@ -1474,7 +1393,10 @@ static void R_RenderSegLoop (void)
 				if (mid >= yl) // back ceiling lower than front ceiling ?
 				{
 					if (yl >= viewheight) // entirely off bottom of screen
-						ceilingclip[rw_x] = (INT16)viewheight;
+					{
+						if (!rw_ceilingmarked)
+							ceilingclip[rw_x] = (INT16)viewheight;
+					}
 					else if (mid >= 0) // safe to draw top texture
 					{
 						dc_yl = yl;
@@ -1485,14 +1407,14 @@ static void R_RenderSegLoop (void)
 						colfunc();
 						ceilingclip[rw_x] = (INT16)mid;
 					}
-					else // entirely off top of screen
+					else if (!rw_ceilingmarked) // entirely off top of screen
 						ceilingclip[rw_x] = -1;
 				}
-				else
-					ceilingclip[rw_x] = (yl >= 0) ? ((yl > viewheight) ? (INT16)viewheight : (INT16)((INT16)yl - 1)) : -1;
+				else if (!rw_ceilingmarked)
+					ceilingclip[rw_x] = topclip;
 			}
-			else if (markceiling) // no top wall
-				ceilingclip[rw_x] = (yl >= 0) ? ((yl > viewheight) ? (INT16)viewheight : (INT16)((INT16)yl - 1)) : -1;
+			else if (markceiling && (!rw_ceilingmarked)) // no top wall
+				ceilingclip[rw_x] = topclip;
 
 			if (bottomtexture)
 			{
@@ -1507,7 +1429,10 @@ static void R_RenderSegLoop (void)
 				if (mid <= yh) // back floor higher than front floor ?
 				{
 					if (yh < 0) // entirely off top of screen
-						floorclip[rw_x] = -1;
+					{
+						if (!rw_floormarked)
+							floorclip[rw_x] = -1;
+					}
 					else if (mid < viewheight) // safe to draw bottom texture
 					{
 						dc_yl = mid;
@@ -1519,14 +1444,14 @@ static void R_RenderSegLoop (void)
 						colfunc();
 						floorclip[rw_x] = (INT16)mid;
 					}
-					else  // entirely off bottom of screen
+					else if (!rw_floormarked)  // entirely off bottom of screen
 						floorclip[rw_x] = (INT16)viewheight;
 				}
-				else
-					floorclip[rw_x] = (yh < viewheight) ? ((yh < -1) ? -1 : (INT16)((INT16)yh + 1)) : (INT16)viewheight;
+				else if (!rw_floormarked)
+					floorclip[rw_x] = bottomclip;
 			}
-			else if (markfloor) // no bottom wall
-				floorclip[rw_x] = (yh < viewheight) ? ((yh < -1) ? -1 : (INT16)((INT16)yh + 1)) : (INT16)viewheight;
+			else if (markfloor && (!rw_floormarked)) // no bottom wall
+				floorclip[rw_x] = bottomclip;
 		}
 
 		if (maskedtexture || numthicksides)
@@ -1980,7 +1905,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		    || backsector->floorlightsec != frontsector->floorlightsec
 		    //SoM: 4/3/2000: Check for colormaps
 		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && frontsector->tag != backsector->tag))
+		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 			markfloor = true;
 		}
@@ -2011,7 +1936,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		    || backsector->ceilinglightsec != frontsector->ceilinglightsec
 		    //SoM: 4/3/2000: Check for colormaps
 		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && frontsector->tag != backsector->tag))
+		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 				markceiling = true;
 		}
@@ -2101,7 +2026,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		rw_bottomtexturemid += sidedef->rowoffset;
 
 		// allocate space for masked texture tables
-		if (frontsector && backsector && frontsector->tag != backsector->tag && (backsector->ffloors || frontsector->ffloors))
+		if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors))
 		{
 			ffloor_t *rover;
 			ffloor_t *r2;
@@ -2143,6 +2068,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					for (r2 = frontsector->ffloors; r2; r2 = r2->next)
 					{
+						if (r2->master == rover->master) // Skip if same control line.
+							break;
+
 						if (!(r2->flags & FF_EXISTS) || !(r2->flags & FF_RENDERSIDES))
 							continue;
 
@@ -2198,6 +2126,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					for (r2 = backsector->ffloors; r2; r2 = r2->next)
 					{
+						if (r2->master == rover->master) // Skip if same control line.
+							break;
+
 						if (!(r2->flags & FF_EXISTS) || !(r2->flags & FF_RENDERSIDES))
 							continue;
 
@@ -2789,20 +2720,11 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		}
 	}
 
-#ifdef WALLSPLATS
-	if (linedef->splats && cv_splats.value)
-	{
-		// Isn't a bit wasteful to copy the ENTIRE array for every drawseg?
-		M_Memcpy(last_ceilingclip + ds_p->x1, ceilingclip + ds_p->x1,
-			sizeof (INT16) * (ds_p->x2 - ds_p->x1 + 1));
-		M_Memcpy(last_floorclip + ds_p->x1, floorclip + ds_p->x1,
-			sizeof (INT16) * (ds_p->x2 - ds_p->x1 + 1));
-		R_RenderSegLoop();
-		R_DrawWallSplats();
-	}
-	else
-#endif
-		R_RenderSegLoop();
+	rw_silhouette = &(ds_p->silhouette);
+	rw_tsilheight = &(ds_p->tsilheight);
+	rw_bsilheight = &(ds_p->bsilheight);
+
+	R_RenderSegLoop();
 	colfunc = colfuncs[BASEDRAWFUNC];
 
 	if (portalline) // if curline is a portal, set portalrender for drawseg
diff --git a/src/r_segs.h b/src/r_segs.h
index a61f1f9219edc1b8ade89d409116fee808619d00..ace5711d493da30102c791764b78b0f1ba5ff85c 100644
--- a/src/r_segs.h
+++ b/src/r_segs.h
@@ -18,6 +18,7 @@
 #pragma interface
 #endif
 
+transnum_t R_GetLinedefTransTable(fixed_t alpha);
 void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2);
 void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pffloor);
 void R_StoreWallRange(INT32 start, INT32 stop);
diff --git a/src/r_skins.c b/src/r_skins.c
index 57ce382c4183618a66dde1c45a0874b48d5a7535..6f150f234ed9908f9c58bd42ecbe65ac5e5a534a 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -31,12 +31,6 @@
 #include "hardware/hw_md2.h"
 #endif
 
-#ifdef PC_DOS
-#include <stdio.h> // for snprintf
-int	snprintf(char *str, size_t n, const char *fmt, ...);
-//int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
-#endif
-
 INT32 numskins = 0;
 skin_t skins[MAXSKINS];
 
@@ -292,6 +286,11 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 			else if (playernum == secondarydisplayplayer)
 				CV_StealthSetValue(&cv_playercolor2, skin->prefcolor);
 			player->skincolor = newcolor = skin->prefcolor;
+			if (player->bot && botingame)
+			{
+				botskin = (UINT8)(skinnum + 1);
+				botcolor = skin->prefcolor;
+			}
 		}
 
 		if (player->followmobj)
@@ -464,12 +463,19 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETINT(contangle)
 #undef GETINT
 
-#define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) skin->field = R_GetColorByName(value);
+#define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) \
+{ \
+	UINT16 color = R_GetColorByName(value); \
+	skin->field = (color ? color : SKINCOLOR_GREEN); \
+}
 	GETSKINCOLOR(prefcolor)
 	GETSKINCOLOR(prefoppositecolor)
 #undef GETSKINCOLOR
 	else if (!stricmp(stoken, "supercolor"))
-		skin->supercolor = R_GetSuperColorByName(value);
+	{
+		UINT16 color = R_GetSuperColorByName(value);
+		skin->supercolor = (color ? color : SKINCOLOR_SUPERGOLD1);
+	}
 
 #define GETFLOAT(field) else if (!stricmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value));
 	GETFLOAT(jumpfactor)
@@ -505,6 +511,10 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETFLAG(MULTIABILITY)
 	GETFLAG(NONIGHTSROTATION)
 	GETFLAG(NONIGHTSSUPER)
+	GETFLAG(NOSUPERSPRITES)
+	GETFLAG(NOSUPERJUMPBOOST)
+	GETFLAG(CANBUSTWALLS)
+	GETFLAG(NOSHIELDABILITY)
 #undef GETFLAG
 
 	else // let's check if it's a sound, otherwise error out
diff --git a/src/r_skins.h b/src/r_skins.h
index 45c90bdb4b9e5964547c579abcbdd3d80ed91162..fbbb38743d84704d3373aafd9e5cc1a7135a46d2 100644
--- a/src/r_skins.h
+++ b/src/r_skins.h
@@ -17,7 +17,8 @@
 #include "info.h"
 #include "sounds.h"
 #include "d_player.h" // skinflags
-#include "r_patch.h" // spriteinfo_t
+#include "r_patch.h"
+#include "r_picformats.h" // spriteinfo_t
 #include "r_defs.h" // spritedef_t
 
 /// Defaults
diff --git a/src/r_splats.c b/src/r_splats.c
index dfec185a11ef2f4d6fc30ca532bd3de003d61942..a3fad82d81730ff1d0ff967f0a4419c60791e59a 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -8,619 +8,590 @@
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
 /// \file  r_splats.c
-/// \brief floor and wall splats
+/// \brief Floor splats
 
 #include "r_draw.h"
 #include "r_main.h"
-#include "r_plane.h"
 #include "r_splats.h"
+#include "r_bsp.h"
+#include "p_local.h"
+#include "p_slopes.h"
 #include "w_wad.h"
 #include "z_zone.h"
-#include "d_netcmd.h"
 
-#ifdef WALLSPLATS
-static wallsplat_t wallsplats[MAXLEVELSPLATS]; // WALL splats
-static INT32 freewallsplat;
-#endif
-
-#ifdef USEASM
-/// \brief for floorsplats \note accessed by asm code
-struct rastery_s *prastertab;
-#endif
+struct rastery_s *prastertab; // for ASM code
 
-#ifdef FLOORSPLATS
-static floorsplat_t floorsplats[1]; // FLOOR splats
-static INT32 freefloorsplat;
-
-struct rastery_s
-{
-	fixed_t minx, maxx; // for each raster line starting at line 0
-	fixed_t tx1, ty1;
-	fixed_t tx2, ty2; // start/end points in texture at this line
-};
 static struct rastery_s rastertab[MAXVIDHEIGHT];
-
 static void prepare_rastertab(void);
-#endif
 
-// --------------------------------------------------------------------------
-// setup splat cache
-// --------------------------------------------------------------------------
-void R_ClearLevelSplats(void)
-{
-#ifdef WALLSPLATS
-	freewallsplat = 0;
-	memset(wallsplats, 0, sizeof (wallsplats));
-#endif
-#ifdef FLOORSPLATS
-	freefloorsplat = 0;
-	memset(floorsplats, 0, sizeof (floorsplats));
+// ==========================================================================
+//                                                               FLOOR SPLATS
+// ==========================================================================
 
-	// setup to draw floorsplats
-	prastertab = rastertab;
-	prepare_rastertab();
+#ifdef USEASM
+void ASMCALL rasterize_segment_tex_asm(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir);
 #endif
-}
 
-// ==========================================================================
-//                                                                WALL SPLATS
-// ==========================================================================
-#ifdef WALLSPLATS
-// --------------------------------------------------------------------------
-// Return a pointer to a splat free for use, or NULL if no more splats are
-// available
-// --------------------------------------------------------------------------
-static wallsplat_t *R_AllocWallSplat(void)
+// Lactozilla
+static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir)
 {
-	wallsplat_t *splat;
-	wallsplat_t *p_splat;
-	line_t *li;
-
-	// clear the splat from the line if it was in use
-	splat = &wallsplats[freewallsplat];
-	li = splat->line;
-	if (li)
+#ifdef USEASM
+	if (R_ASM)
 	{
-		// remove splat from line splats list
-		if (li->splats == splat)
-			li->splats = splat->next; // remove from head
-		else
-		{
-			I_Assert(li->splats != NULL);
-			for (p_splat = li->splats; p_splat->next; p_splat = p_splat->next)
-				if (p_splat->next == splat)
-				{
-					p_splat->next = splat->next;
-					break;
-				}
-		}
+		rasterize_segment_tex_asm(x1, y1, x2, y2, tv1, tv2, tc, dir);
+		return;
 	}
+	else
+#endif
+	{
+		fixed_t xs, xe, count;
+		fixed_t dx0, dx1;
 
-	memset(splat, 0, sizeof (wallsplat_t));
+		if (y1 == y2)
+			return;
 
-	// for next allocation
-	freewallsplat++;
-	if (freewallsplat >= 20)
-		freewallsplat = 0;
+		if (y2 > y1)
+		{
+			count = (y2-y1)+1;
 
-	return splat;
-}
+			dx0 = FixedDiv((x2-x1)<<FRACBITS, count<<FRACBITS);
+			dx1 = FixedDiv((tv2-tv1)<<FRACBITS, count<<FRACBITS);
 
-// Add a new splat to the linedef:
-// top: top z coord
-// wallfrac: frac along the linedef vector (0 to FRACUNIT)
-// splatpatchname: name of patch to draw
-void R_AddWallSplat(line_t *wallline, INT16 sectorside, const char *patchname, fixed_t top,
-	fixed_t wallfrac, INT32 flags)
-{
-	fixed_t fracsplat, linelength;
-	wallsplat_t *splat = NULL;
-	wallsplat_t *p_splat;
-	patch_t *patch;
-	sector_t *backsector = NULL;
-
-	if (W_CheckNumForName(patchname) != LUMPERROR)
-		splat = R_AllocWallSplat();
-	if (!splat)
-		return;
+			xs = x1 << FRACBITS;
+			xe = tv1 << FRACBITS;
+			tc <<= FRACBITS;
 
-	// set the splat
-	splat->patch = W_GetNumForName(patchname);
-	sectorside ^= 1;
-	if (wallline->sidenum[sectorside] != 0xffff)
-	{
-		backsector = sides[wallline->sidenum[sectorside]].sector;
+			if (dir == 0)
+			{
+				for (;;)
+				{
+					rastertab[y1].maxx = xs;
+					rastertab[y1].tx2 = xe;
+					rastertab[y1].ty2 = tc;
 
-		if (top < backsector->floorheight)
-		{
-			splat->yoffset = &backsector->floorheight;
-			top -= backsector->floorheight;
-		}
-		else if (top > backsector->ceilingheight)
-		{
-			splat->yoffset = &backsector->ceilingheight;
-			top -= backsector->ceilingheight;
-		}
-	}
+					xs += dx0;
+					xe += dx1;
+					y1++;
 
-	splat->top = top;
-	splat->flags = flags;
+					if (count-- < 1) break;
+				}
+			}
+			else
+			{
+				for (;;)
+				{
+					rastertab[y1].maxx = xs;
+					rastertab[y1].tx2 = tc;
+					rastertab[y1].ty2 = xe;
 
-	// bad.. but will be needed for drawing anyway..
-	patch = W_CachePatchNum(splat->patch, PU_PATCH);
+					xs += dx0;
+					xe += dx1;
+					y1++;
 
-	// offset needed by draw code for texture mapping
-	linelength = P_SegLength((seg_t *)wallline);
-	splat->offset = FixedMul(wallfrac, linelength) - (SHORT(patch->width)<<(FRACBITS-1));
-	fracsplat = FixedDiv(((SHORT(patch->width)<<FRACBITS)>>1), linelength);
+					if (count-- < 1) break;
+				}
+			}
+		}
+		else
+		{
+			count = (y1-y2)+1;
 
-	wallfrac -= fracsplat;
-	if (wallfrac > linelength)
-		return;
-	splat->v1.x = wallline->v1->x + FixedMul(wallline->dx, wallfrac);
-	splat->v1.y = wallline->v1->y + FixedMul(wallline->dy, wallfrac);
-	wallfrac += fracsplat + fracsplat;
-	if (wallfrac < 0)
-		return;
-	splat->v2.x = wallline->v1->x + FixedMul(wallline->dx, wallfrac);
-	splat->v2.y = wallline->v1->y + FixedMul(wallline->dy, wallfrac);
+			dx0 = FixedDiv((x1-x2)<<FRACBITS, count<<FRACBITS);
+			dx1 = FixedDiv((tv1-tv2)<<FRACBITS, count<<FRACBITS);
 
-	if (wallline->frontsector && wallline->frontsector == backsector)
-		return;
+			xs = x2 << FRACBITS;
+			xe = tv2 << FRACBITS;
+			tc <<= FRACBITS;
 
-	// insert splat in the linedef splat list
-	// BP: why not insert in head is much more simple?
-	// BP: because for remove it is more simple!
-	splat->line = wallline;
-	splat->next = NULL;
-	if (wallline->splats)
-	{
-		p_splat = wallline->splats;
-		while (p_splat->next)
-			p_splat = p_splat->next;
-		p_splat->next = splat;
-	}
-	else
-		wallline->splats = splat;
-}
-#endif // WALLSPLATS
+			if (dir == 0)
+			{
+				for (;;)
+				{
+					rastertab[y2].minx = xs;
+					rastertab[y2].tx1 = xe;
+					rastertab[y2].ty1 = tc;
 
-// ==========================================================================
-//                                                               FLOOR SPLATS
-// ==========================================================================
-#ifdef FLOORSPLATS
+					xs += dx0;
+					xe += dx1;
+					y2++;
 
-// --------------------------------------------------------------------------
-// Return a pointer to a splat free for use, or NULL if no more splats are
-// available
-// --------------------------------------------------------------------------
-static floorsplat_t *R_AllocFloorSplat(void)
-{
-	floorsplat_t *splat;
-	floorsplat_t *p_splat;
-	subsector_t *sub;
-
-	// find splat to use
-	freefloorsplat++;
-	if (freefloorsplat >= 1)
-		freefloorsplat = 0;
-
-	// clear the splat from the line if it was in use
-	splat = &floorsplats[freefloorsplat];
-	sub = splat->subsector;
-	if (sub)
-	{
-		// remove splat from subsector splats list
-		if (sub->splats == splat)
-			sub->splats = splat->next; // remove from head
-		else
-		{
-			p_splat = sub->splats;
-			while (p_splat->next)
+					if (count-- < 1) break;
+				}
+			}
+			else
 			{
-				if (p_splat->next == splat)
-					p_splat->next = splat->next;
+				for (;;)
+				{
+					rastertab[y2].minx = xs;
+					rastertab[y2].tx1 = tc;
+					rastertab[y2].ty1 = xe;
+
+					xs += dx0;
+					xe += dx1;
+					y2++;
+
+					if (count-- < 1) break;
+				}
 			}
 		}
 	}
-
-	memset(splat, 0, sizeof (floorsplat_t));
-	return splat;
 }
 
-// --------------------------------------------------------------------------
-// Add a floor splat to the subsector
-// --------------------------------------------------------------------------
-void R_AddFloorSplat(subsector_t *subsec, mobj_t *mobj, const char *picname, fixed_t x, fixed_t y, fixed_t z,
-	INT32 flags)
+void R_DrawFloorSprite(vissprite_t *spr)
 {
-	floorsplat_t *splat = NULL;
-	floorsplat_t *p_splat;
-	INT32 size;
+	floorsplat_t splat;
+	mobj_t *mobj = spr->mobj;
+	fixed_t tr_x, tr_y, rot_x, rot_y, rot_z;
+
+	vector3_t *v3d;
+	vector2_t v2d[4];
+	vector2_t rotated[4];
+
+	fixed_t x, y;
+	fixed_t w, h;
+	angle_t angle, splatangle;
+	fixed_t ca, sa;
+	fixed_t xscale, yscale;
+	fixed_t xoffset, yoffset;
+	fixed_t leftoffset, topoffset;
+	pslope_t *slope = NULL;
+	INT32 i;
+
+	boolean hflip = (spr->xiscale < 0);
+	boolean vflip = (spr->cut & SC_VFLIP);
+	UINT8 flipflags = 0;
+
+	renderflags_t renderflags = spr->renderflags;
+
+	if (hflip)
+		flipflags |= PICFLAGS_XFLIP;
+	if (vflip)
+		flipflags |= PICFLAGS_YFLIP;
+
+	if (!mobj || P_MobjWasRemoved(mobj))
+		return;
 
-	if (W_CheckNumForName(picname) != LUMPERROR)
-		splat = R_AllocFloorSplat();
-	if (!splat)
+	Patch_GenerateFlat(spr->patch, flipflags);
+	splat.pic = spr->patch->flats[flipflags];
+	if (splat.pic == NULL)
 		return;
 
-	// set the splat
-	splat->pic = W_GetNumForName(picname);
-	splat->flags = flags;
-	splat->mobj = mobj;
+	splat.mobj = mobj;
+	splat.width = spr->patch->width;
+	splat.height = spr->patch->height;
+	splat.scale = mobj->scale;
 
-	splat->z = z;
+	if (mobj->skin && ((skin_t *)mobj->skin)->flags & SF_HIRES)
+		splat.scale = FixedMul(splat.scale, ((skin_t *)mobj->skin)->highresscale);
 
-	size = W_LumpLength(splat->pic);
+	if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD)
+		splatangle = mobj->angle;
+	else
+		splatangle = viewangle;
 
-	switch (size)
-	{
-		case 4194304: // 2048x2048 lump
-			splat->size = 1024;
-			break;
-		case 1048576: // 1024x1024 lump
-			splat->size = 512;
-			break;
-		case 262144:// 512x512 lump
-			splat->size = 256;
-			break;
-		case 65536: // 256x256 lump
-			splat->size = 128;
-			break;
-		case 16384: // 128x128 lump
-			splat->size = 64;
-			break;
-		case 1024: // 32x32 lump
-			splat->size = 16;
-			break;
-		default: // 64x64 lump
-			splat->size = 32;
-			break;
-	}
+	if (!(spr->cut & SC_ISROTATED))
+		splatangle += mobj->rollangle;
+
+	splat.angle = -splatangle;
+	splat.angle += ANGLE_90;
+
+	topoffset = spr->spriteyoffset;
+	leftoffset = spr->spritexoffset;
+	if (hflip)
+		leftoffset = ((splat.width * FRACUNIT) - leftoffset);
+
+	xscale = spr->spritexscale;
+	yscale = spr->spriteyscale;
+
+	splat.xscale = FixedMul(splat.scale, xscale);
+	splat.yscale = FixedMul(splat.scale, yscale);
+
+	xoffset = FixedMul(leftoffset, splat.xscale);
+	yoffset = FixedMul(topoffset, splat.yscale);
+
+	x = mobj->x;
+	y = mobj->y;
+	w = (splat.width * splat.xscale);
+	h = (splat.height * splat.yscale);
+
+	splat.x = x;
+	splat.y = y;
+	splat.z = mobj->z;
+	splat.tilted = false;
+
+	// Set positions
 
 	// 3--2
 	// |  |
 	// 0--1
-	//
-	splat->verts[0].x = splat->verts[3].x = x - (splat->size<<FRACBITS);
-	splat->verts[2].x = splat->verts[1].x = x + ((splat->size-1)<<FRACBITS);
-	splat->verts[3].y = splat->verts[2].y = y + ((splat->size-1)<<FRACBITS);
-	splat->verts[0].y = splat->verts[1].y = y - (splat->size<<FRACBITS);
-
-	// insert splat in the subsector splat list
-	splat->subsector = subsec;
-	splat->next = NULL;
-	if (subsec->splats)
+
+	splat.verts[0].x = w - xoffset;
+	splat.verts[0].y = yoffset;
+
+	splat.verts[1].x = -xoffset;
+	splat.verts[1].y = yoffset;
+
+	splat.verts[2].x = -xoffset;
+	splat.verts[2].y = -h + yoffset;
+
+	splat.verts[3].x = w - xoffset;
+	splat.verts[3].y = -h + yoffset;
+
+	angle = -splat.angle;
+	ca = FINECOSINE(angle>>ANGLETOFINESHIFT);
+	sa = FINESINE(angle>>ANGLETOFINESHIFT);
+
+	// Rotate
+	for (i = 0; i < 4; i++)
 	{
-		p_splat = subsec->splats;
-		while (p_splat->next)
-			p_splat = p_splat->next;
-		p_splat->next = splat;
+		rotated[i].x = FixedMul(splat.verts[i].x, ca) - FixedMul(splat.verts[i].y, sa);
+		rotated[i].y = FixedMul(splat.verts[i].x, sa) + FixedMul(splat.verts[i].y, ca);
 	}
-	else
-		subsec->splats = splat;
-}
 
-// --------------------------------------------------------------------------
-// Before each frame being rendered, clear the visible floorsplats list
-// --------------------------------------------------------------------------
-static floorsplat_t *visfloorsplats;
+	if (renderflags & (RF_SLOPESPLAT | RF_OBJECTSLOPESPLAT))
+	{
+		pslope_t *standingslope = mobj->standingslope; // The slope that the object is standing on.
 
-void R_ClearVisibleFloorSplats(void)
-{
-	visfloorsplats = NULL;
-}
+		// The slope that was defined for the sprite.
+		if (renderflags & RF_SLOPESPLAT)
+			slope = mobj->floorspriteslope;
 
-// --------------------------------------------------------------------------
-// Add a floorsplat to the visible floorsplats list, for the current frame
-// --------------------------------------------------------------------------
-void R_AddVisibleFloorSplats(subsector_t *subsec)
-{
-	floorsplat_t *pSplat;
-	I_Assert(subsec->splats != NULL);
-
-	pSplat = subsec->splats;
-	// the splat is not visible from below
-	// FIXME: depending on some flag in pSplat->flags, some splats may be visible from 2 sides
-	// (above/below)
-	if (pSplat->z < viewz)
+		if (standingslope && (renderflags & RF_OBJECTSLOPESPLAT))
+			slope = standingslope;
+
+		// Set splat as tilted
+		splat.tilted = (slope != NULL);
+	}
+
+	if (splat.tilted)
 	{
-		pSplat->nextvis = visfloorsplats;
-		visfloorsplats = pSplat;
+		// Lactozilla: Just copy the entire slope LMFAOOOO
+		pslope_t *s = &splat.slope;
+
+		s->o.x = slope->o.x;
+		s->o.y = slope->o.y;
+		s->o.z = slope->o.z;
+
+		s->d.x = slope->d.x;
+		s->d.y = slope->d.y;
+
+		s->normal.x = slope->normal.x;
+		s->normal.y = slope->normal.y;
+		s->normal.z = slope->normal.z;
+
+		s->zdelta = slope->zdelta;
+		s->zangle = slope->zangle;
+		s->xydirection = slope->xydirection;
+
+		s->next = NULL;
+		s->flags = 0;
 	}
 
-	while (pSplat->next)
+	// Translate
+	for (i = 0; i < 4; i++)
 	{
-		pSplat = pSplat->next;
-		if (pSplat->z < viewz)
+		tr_x = rotated[i].x + x;
+		tr_y = rotated[i].y + y;
+
+		if (slope)
 		{
-			pSplat->nextvis = visfloorsplats;
-			visfloorsplats = pSplat;
+			rot_z = P_GetSlopeZAt(slope, tr_x, tr_y);
+			splat.verts[i].z = rot_z;
 		}
+		else
+			splat.verts[i].z = splat.z;
+
+		splat.verts[i].x = tr_x;
+		splat.verts[i].y = tr_y;
 	}
-}
 
-#ifdef USEASM
-// tv1, tv2 = x/y qui varie dans la texture, tc = x/y qui est constant.
-void ASMCALL rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2,
-	INT32 tc, INT32 dir);
-#endif
+	for (i = 0; i < 4; i++)
+	{
+		v3d = &splat.verts[i];
+
+		// transform the origin point
+		tr_x = v3d->x - viewx;
+		tr_y = v3d->y - viewy;
 
-// current test with floor tile
-//#define FLOORSPLATSOLIDCOLOR
+		// rotation around vertical y axis
+		rot_x = FixedMul(tr_x, viewsin) - FixedMul(tr_y, viewcos);
+		rot_y = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+		rot_z = v3d->z - viewz;
+
+		if (rot_y < FRACUNIT)
+			return;
+
+		// note: y from view above of map, is distance far away
+		xscale = FixedDiv(projection, rot_y);
+		yscale = -FixedDiv(projectiony, rot_y);
+
+		// projection
+		v2d[i].x = (centerxfrac + FixedMul(rot_x, xscale))>>FRACBITS;
+		v2d[i].y = (centeryfrac + FixedMul(rot_z, yscale))>>FRACBITS;
+	}
+
+	R_RenderFloorSplat(&splat, v2d, spr);
+}
 
 // --------------------------------------------------------------------------
 // Rasterize the four edges of a floor splat polygon,
 // fill the polygon with linear interpolation, call span drawer for each
 // scan line
 // --------------------------------------------------------------------------
-static void R_RenderFloorSplat(floorsplat_t *pSplat, vertex_t *verts, UINT8 *pTex)
+void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis)
 {
 	// rasterizing
-	INT32 miny = vid.height + 1, maxy = 0, y, x1, ry1, x2, y2;
-	fixed_t offsetx, offsety;
-
-#ifdef FLOORSPLATSOLIDCOLOR
-	UINT8 *pDest;
-	INT32 tdx, tdy, ty, tx, x;
-#else
-	lighttable_t **planezlight;
-	fixed_t planeheight;
-	angle_t angle, planecos, planesin;
-	fixed_t distance, span;
-	size_t indexr;
-	INT32 light;
-#endif
-	(void)pTex;
+	INT32 miny = viewheight + 1, maxy = 0;
+	INT32 y, x1, ry1, x2, y2, i;
+	fixed_t offsetx = 0, offsety = 0;
+	fixed_t planeheight = 0;
+	fixed_t step;
 
-	offsetx = pSplat->verts[0].x & ((pSplat->size << FRACBITS)-1);
-	offsety = pSplat->verts[0].y & ((pSplat->size << FRACBITS)-1);
+	int spanfunctype = SPANDRAWFUNC_SPRITE;
 
-	// do segment a -> top of texture
-	x1 = verts[3].x;
-	ry1 = verts[3].y;
-	x2 = verts[2].x;
-	y2 = verts[2].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, 0, pSplat->size - 1, 0, 0);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
+	prepare_rastertab();
 
-	// do segment b -> right side of texture
-	x1 = x2;
-	ry1 = y2;
-	x2 = verts[1].x;
-	y2 = verts[1].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, 0, pSplat->size - 1, pSplat->size - 1, 1);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
+#define RASTERPARAMS(vnum1, vnum2, tv1, tv2, tc, dir) \
+    x1 = verts[vnum1].x; \
+    ry1 = verts[vnum1].y; \
+    x2 = verts[vnum2].x; \
+    y2 = verts[vnum2].y; \
+    if (y2 > ry1) \
+        step = FixedDiv(x2-x1, y2-ry1+1); \
+    else if (y2 == ry1) \
+        step = 0; \
+    else \
+        step = FixedDiv(x2-x1, ry1-y2+1); \
+    if (ry1 < 0) { \
+        if (step) { \
+            x1 <<= FRACBITS; \
+            x1 += (-ry1)*step; \
+            x1 >>= FRACBITS; \
+        } \
+        ry1 = 0; \
+    } \
+    if (ry1 >= vid.height) { \
+        if (step) { \
+            x1 <<= FRACBITS; \
+            x1 -= (vid.height-1-ry1)*step; \
+            x1 >>= FRACBITS; \
+        } \
+        ry1 = vid.height - 1; \
+    } \
+    if (y2 < 0) { \
+        if (step) { \
+            x2 <<= FRACBITS; \
+            x2 -= (-y2)*step; \
+            x2 >>= FRACBITS; \
+        } \
+        y2 = 0; \
+    } \
+    if (y2 >= vid.height) { \
+        if (step) { \
+            x2 <<= FRACBITS; \
+            x2 += (vid.height-1-y2)*step; \
+            x2 >>= FRACBITS; \
+        } \
+        y2 = vid.height - 1; \
+    } \
+    rasterize_segment_tex(x1, ry1, x2, y2, tv1, tv2, tc, dir); \
+    if (ry1 < miny) \
+        miny = ry1; \
+    if (ry1 > maxy) \
+        maxy = ry1;
 
+	// do segment a -> top of texture
+	RASTERPARAMS(3,2,0,pSplat->width-1,0,0);
+	// do segment b -> right side of texture
+	RASTERPARAMS(2,1,0,pSplat->width-1,pSplat->height-1,0);
 	// do segment c -> bottom of texture
-	x1 = x2;
-	ry1 = y2;
-	x2 = verts[0].x;
-	y2 = verts[0].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, pSplat->size - 1, 0, pSplat->size - 1, 0);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
-
+	RASTERPARAMS(1,0,pSplat->width-1,0,pSplat->height-1,0);
 	// do segment d -> left side of texture
-	x1 = x2;
-	ry1 = y2;
-	x2 = verts[3].x;
-	y2 = verts[3].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, pSplat->size - 1, 0, 0, 1);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
-
-#ifndef FLOORSPLATSOLIDCOLOR
-	// prepare values for all the splat
-	ds_source = W_CacheLumpNum(pSplat->pic, PU_CACHE);
-	planeheight = abs(pSplat->z - viewz);
-	light = (pSplat->subsector->sector->lightlevel >> LIGHTSEGSHIFT);
-	if (light >= LIGHTLEVELS)
-		light = LIGHTLEVELS - 1;
-	if (light < 0)
-		light = 0;
-	planezlight = zlight[light];
+	RASTERPARAMS(0,3,pSplat->width-1,0,0,1);
 
-	for (y = miny; y <= maxy; y++)
-	{
-		x1 = rastertab[y].minx>>FRACBITS;
-		x2 = rastertab[y].maxx>>FRACBITS;
+	ds_source = (UINT8 *)pSplat->pic;
+	ds_flatwidth = pSplat->width;
+	ds_flatheight = pSplat->height;
 
-		if (x1 < 0)
-			x1 = 0;
-		if (x2 >= vid.width)
-			x2 = vid.width - 1;
+	if (R_CheckPowersOfTwo())
+		R_CheckFlatLength(ds_flatwidth * ds_flatheight);
 
-		angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
-		planecos = FINECOSINE(angle);
-		planesin = FINESINE(angle);
+	// Lactozilla: I don't know what I'm doing
+	if (pSplat->tilted)
+	{
+		R_SetTiltedSpan(0);
+		R_CalculateSlopeVectors(&pSplat->slope, viewx, viewy, viewz, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, viewangle, pSplat->angle, 1.0f);
+		spanfunctype = SPANDRAWFUNC_TILTEDSPRITE;
+	}
+	else
+	{
+		planeheight = abs(pSplat->z - viewz);
 
-		if (planeheight != cachedheight[y])
+		if (pSplat->angle)
 		{
-			cachedheight[y] = planeheight;
-			distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
-			ds_xstep = cachedxstep[y] = FixedMul(distance,basexscale);
-			ds_ystep = cachedystep[y] = FixedMul(distance,baseyscale);
-
-			if ((span = abs(centery-y)))
-			{
-				ds_xstep = cachedxstep[y] = FixedMul(planesin, planeheight) / span;
-				ds_ystep = cachedystep[y] = FixedMul(planecos, planeheight) / span;
-			}
+			// Add the view offset, rotated by the plane angle.
+			fixed_t a = -pSplat->verts[0].x + viewx;
+			fixed_t b = -pSplat->verts[0].y + viewy;
+			angle_t angle = (pSplat->angle >> ANGLETOFINESHIFT);
+			offsetx = FixedMul(a, FINECOSINE(angle)) - FixedMul(b,FINESINE(angle));
+			offsety = -FixedMul(a, FINESINE(angle)) - FixedMul(b,FINECOSINE(angle));
+			memset(cachedheight, 0, sizeof(cachedheight));
 		}
 		else
 		{
-			distance = cacheddistance[y];
-			ds_xstep = cachedxstep[y];
-			ds_ystep = cachedystep[y];
+			offsetx = viewx - pSplat->verts[0].x;
+			offsety = pSplat->verts[0].y - viewy;
 		}
+	}
 
-		ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
-		ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
-		ds_xfrac -= offsetx;
-		ds_yfrac += offsety;
+	ds_colormap = vis->colormap;
+	ds_translation = R_GetSpriteTranslation(vis);
+	if (ds_translation == NULL)
+		ds_translation = colormaps;
 
-		indexr = distance >> LIGHTZSHIFT;
-		if (indexr >= MAXLIGHTZ)
-			indexr = MAXLIGHTZ - 1;
-		ds_colormap = planezlight[indexr];
+	if (vis->extra_colormap)
+	{
+		if (!ds_colormap)
+			ds_colormap = vis->extra_colormap->colormap;
+		else
+			ds_colormap = &vis->extra_colormap->colormap[ds_colormap - colormaps];
+	}
 
-		ds_y = y;
-		if (x2 >= x1) // sanity check
-		{
-			ds_x1 = x1;
-			ds_x2 = x2;
-			ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
-			(spanfuncs[SPANDRAWFUNC_SPLAT])();
-		}
+	if (vis->transmap)
+	{
+		ds_transmap = vis->transmap;
 
-		// reset for next calls to edge rasterizer
-		rastertab[y].minx = INT32_MAX;
-		rastertab[y].maxx = INT32_MIN;
+		if (pSplat->tilted)
+			spanfunctype = SPANDRAWFUNC_TILTEDTRANSSPRITE;
+		else
+			spanfunctype = SPANDRAWFUNC_TRANSSPRITE;
 	}
+	else
+		ds_transmap = NULL;
+
+	if (ds_powersoftwo)
+		spanfunc = spanfuncs[spanfunctype];
+	else
+		spanfunc = spanfuncs_npo2[spanfunctype];
+
+	if (maxy >= vid.height)
+		maxy = vid.height-1;
 
-#else
 	for (y = miny; y <= maxy; y++)
 	{
+		boolean cliptab[MAXVIDWIDTH+1];
+
 		x1 = rastertab[y].minx>>FRACBITS;
 		x2 = rastertab[y].maxx>>FRACBITS;
+
+		if (x1 > x2)
+		{
+			INT32 swap = x1;
+			x1 = x2;
+			x2 = swap;
+		}
+
+		if (x1 == INT16_MIN || x2 == INT16_MAX)
+			continue;
+
 		if (x1 < 0)
 			x1 = 0;
-		if (x2 >= vid.width)
-			x2 = vid.width - 1;
+		if (x2 >= viewwidth)
+			x2 = viewwidth - 1;
 
-//		pDest = ylookup[y] + columnofs[x1];
-		pDest = &topleft[y*vid.width + x1];
+		if (x1 >= viewwidth || x2 < 0)
+			continue;
 
-		x = x2 - x1 + 1;
+		for (i = x1; i <= x2; i++)
+			cliptab[i] = (y >= mfloorclip[i]);
 
-		// starting point of the texture
-		tx = rastertab[y].tx1;
-		ty = rastertab[y].ty1;
-
-		// HORRIBLE BUG!!!
-		if (x > 0)
+		// clip left
+		while (cliptab[x1])
 		{
-			tdx = (rastertab[y].tx2 - tx) / x;
-			tdy = (rastertab[y].ty2 - ty) / x;
-
-			while (x-- > 0)
-			{
-				*(pDest++) = (UINT8)(y&1);
-				tx += tdx;
-				ty += tdy;
-			}
+			x1++;
+			if (x1 >= viewwidth)
+				break;
 		}
 
-		// reinitialise the minimum and maximum for the next approach
-		rastertab[y].minx = INT32_MAX;
-		rastertab[y].maxx = INT32_MIN;
-	}
-#endif
-}
+		// clip right
+		i = x2;
 
-// --------------------------------------------------------------------------
-// R_DrawVisibleFloorSplats
-// draw the flat floor/ceiling splats
-// --------------------------------------------------------------------------
-void R_DrawVisibleFloorSplats(void)
-{
-	floorsplat_t *pSplat;
-	INT32 iCount = 0, i;
-	fixed_t tr_x, tr_y, rot_x, rot_y, rot_z, xscale, yscale;
-	vertex_t *v3d;
-	vertex_t v2d[4];
-
-	pSplat = visfloorsplats;
-	while (pSplat)
-	{
-		iCount++;
+		while (i > x1)
+		{
+			if (cliptab[i])
+				x2 = i-1;
+			i--;
+			if (i < 0)
+				break;
+		}
 
-		// Draw a floor splat
-		// 3--2
-		// |  |
-		// 0--1
+		if (x2 < x1)
+			continue;
 
-		rot_z = pSplat->z - viewz;
-		for (i = 0; i < 4; i++)
+		if (!pSplat->tilted)
 		{
-			v3d = &pSplat->verts[i];
+			fixed_t xstep, ystep;
+			fixed_t distance, span;
 
-			// transform the origin point
-			tr_x = v3d->x - viewx;
-			tr_y = v3d->y - viewy;
+			angle_t angle = (viewangle + pSplat->angle)>>ANGLETOFINESHIFT;
+			angle_t planecos = FINECOSINE(angle);
+			angle_t planesin = FINESINE(angle);
 
-			// rotation around vertical y axis
-			rot_x = FixedMul(tr_x, viewsin) - FixedMul(tr_y, viewcos);
-			rot_y = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+			if (planeheight != cachedheight[y])
+			{
+				cachedheight[y] = planeheight;
+				distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
+				span = abs(centery - y);
 
-			if (rot_y < 4*FRACUNIT)
-				goto skipit;
+				if (span) // don't divide by zero
+				{
+					xstep = FixedMul(planesin, planeheight) / span;
+					ystep = FixedMul(planecos, planeheight) / span;
+				}
+				else
+				{
+					// ah
+					xstep = FRACUNIT;
+					ystep = FRACUNIT;
+				}
+
+				cachedxstep[y] = xstep;
+				cachedystep[y] = ystep;
+			}
+			else
+			{
+				distance = cacheddistance[y];
+				xstep = cachedxstep[y];
+				ystep = cachedystep[y];
+			}
 
-			// note: y from view above of map, is distance far away
-			xscale = FixedDiv(projection, rot_y);
-			yscale = -FixedDiv(projectiony, rot_y);
+			ds_xstep = FixedDiv(xstep, pSplat->xscale);
+			ds_ystep = FixedDiv(ystep, pSplat->yscale);
 
-			// projection
-			v2d[i].x = (centerxfrac + FixedMul (rot_x, xscale))>>FRACBITS;
-			v2d[i].y = (centeryfrac + FixedMul (rot_z, yscale))>>FRACBITS;
+			ds_xfrac = FixedDiv(offsetx + FixedMul(planecos, distance) + (x1 - centerx) * xstep, pSplat->xscale);
+			ds_yfrac = FixedDiv(offsety - FixedMul(planesin, distance) + (x1 - centerx) * ystep, pSplat->yscale);
 		}
 
-		R_RenderFloorSplat(pSplat, v2d, NULL);
-skipit:
-		pSplat = pSplat->nextvis;
+		ds_y = y;
+		ds_x1 = x1;
+		ds_x2 = x2;
+		spanfunc();
+
+		rastertab[y].minx = INT32_MAX;
+		rastertab[y].maxx = INT32_MIN;
 	}
+
+	if (pSplat->angle && !pSplat->tilted)
+		memset(cachedheight, 0, sizeof(cachedheight));
 }
 
 static void prepare_rastertab(void)
 {
-	INT32 iLine;
-	for (iLine = 0; iLine < vid.height; iLine++)
+	INT32 i;
+	prastertab = rastertab;
+	for (i = 0; i < vid.height; i++)
 	{
-		rastertab[iLine].minx = INT32_MAX;
-		rastertab[iLine].maxx = INT32_MIN;
+		rastertab[i].minx = INT32_MAX;
+		rastertab[i].maxx = INT32_MIN;
 	}
 }
-
-#endif // FLOORSPLATS
diff --git a/src/r_splats.h b/src/r_splats.h
index 4ad893abbb2db5dcde78116fd94b65cf8f373aa4..e1f836f489bab54513dafd5b867ebfd7dbc79f44 100644
--- a/src/r_splats.h
+++ b/src/r_splats.h
@@ -14,68 +14,35 @@
 #define __R_SPLATS_H__
 
 #include "r_defs.h"
-
-//#define WALLSPLATS      // comment this out to compile without splat effects
-/*#ifdef USEASM
-#define FLOORSPLATS
-#endif*/
-
-#define MAXLEVELSPLATS      1024
-
-// splat flags
-#define SPLATDRAWMODE_MASK 0x03 // mask to get drawmode from flags
-#define SPLATDRAWMODE_OPAQUE 0x00
-#define SPLATDRAWMODE_SHADE 0x01
-#define SPLATDRAWMODE_TRANS 0x02
+#include "r_things.h"
 
 // ==========================================================================
 // DEFINITIONS
 // ==========================================================================
 
-// WALL SPLATS are patches drawn on top of wall segs
-typedef struct wallsplat_s
+struct rastery_s
 {
-	lumpnum_t patch; // lump id.
-	vertex_t v1, v2; // vertices along the linedef
-	fixed_t top;
-	fixed_t offset; // offset in columns<<FRACBITS from start of linedef to start of splat
-	INT32 flags;
-	fixed_t *yoffset;
-	line_t *line; // the parent line of the splat seg
-	struct wallsplat_s *next;
-} wallsplat_t;
+	fixed_t minx, maxx; // for each raster line starting at line 0
+	fixed_t tx1, ty1;   // start points in texture at this line
+	fixed_t tx2, ty2;   // end points in texture at this line
+};
+extern struct rastery_s *prastertab; // for ASM code
 
-// FLOOR SPLATS are pic_t (raw horizontally stored) drawn on top of the floor or ceiling
 typedef struct floorsplat_s
 {
-	lumpnum_t pic; // a pic_t lump id
-	INT32 flags;
-	INT32 size; // 64, 128, 256, etc.
-	vertex_t verts[4]; // (x,y) as viewn from above on map
-	fixed_t z; // z (height) is constant for all the floorsplats
-	subsector_t *subsector; // the parent subsector
+	UINT16 *pic;
+	INT32 width, height;
+	fixed_t scale, xscale, yscale;
+	angle_t angle;
+	boolean tilted; // Uses the tilted drawer
+	pslope_t slope;
+
+	vector3_t verts[4]; // (x,y,z) as viewed from above on map
+	fixed_t x, y, z; // position
 	mobj_t *mobj; // Mobj it is tied to
-	struct floorsplat_s *next;
-	struct floorsplat_s *nextvis;
 } floorsplat_t;
 
-// p_setup.c
-fixed_t P_SegLength(seg_t *seg);
-
-// call at P_SetupLevel()
-void R_ClearLevelSplats(void);
-
-#ifdef WALLSPLATS
-void R_AddWallSplat(line_t *wallline, INT16 sectorside, const char *patchname, fixed_t top,
-	fixed_t wallfrac, INT32 flags);
-#endif
-#ifdef FLOORSPLATS
-void R_AddFloorSplat(subsector_t *subsec, mobj_t *mobj, const char *picname, fixed_t x, fixed_t y, fixed_t z,
-	INT32 flags);
-#endif
-
-void R_ClearVisibleFloorSplats(void);
-void R_AddVisibleFloorSplats(subsector_t *subsec);
-void R_DrawVisibleFloorSplats(void);
+void R_DrawFloorSprite(vissprite_t *spr);
+void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis);
 
 #endif /*__R_SPLATS_H__*/
diff --git a/src/r_state.h b/src/r_state.h
index 7c860a6f20636cf3ac47ccebd2b193b3011c3250..25aa697024c87ceeee88d0b1f1312d7270a502b8 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -52,6 +52,8 @@ extern size_t numspritelumps, max_spritelumps;
 //
 // Lookup tables for map data.
 //
+extern boolean udmf;
+
 extern size_t numsprites;
 extern spritedef_t *sprites;
 
diff --git a/src/r_textures.c b/src/r_textures.c
new file mode 100644
index 0000000000000000000000000000000000000000..a006d739fc75278d6cfbd35cac997ff8e444fe94
--- /dev/null
+++ b/src/r_textures.c
@@ -0,0 +1,1649 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_textures.c
+/// \brief Texture generation.
+
+#include "doomdef.h"
+#include "g_game.h"
+#include "i_video.h"
+#include "r_local.h"
+#include "r_sky.h"
+#include "p_local.h"
+#include "m_misc.h"
+#include "r_data.h"
+#include "r_textures.h"
+#include "r_patch.h"
+#include "r_picformats.h"
+#include "w_wad.h"
+#include "z_zone.h"
+#include "p_setup.h" // levelflats
+#include "byteptr.h"
+#include "dehacked.h"
+
+// I don't know what this is even for, but r_data.c had it.
+#ifdef _WIN32
+#include <malloc.h> // alloca(sizeof)
+#endif
+
+#ifdef HWRENDER
+#include "hardware/hw_glob.h" // HWR_LoadMapTextures
+#endif
+
+#include <errno.h>
+
+//
+// TEXTURE_T CACHING
+// When a texture is first needed, it counts the number of composite columns
+//  required in the texture and allocates space for a column directory and
+//  any new columns.
+// The directory will simply point inside other patches if there is only one
+//  patch in a given column, but any columns with multiple patches will have
+//  new column_ts generated.
+//
+
+INT32 numtextures = 0; // total number of textures found,
+// size of following tables
+
+texture_t **textures = NULL;
+UINT32 **texturecolumnofs; // column offset lookup table for each texture
+UINT8 **texturecache; // graphics data for each generated full-size texture
+
+INT32 *texturewidth;
+fixed_t *textureheight; // needed for texture pegging
+
+INT32 *texturetranslation;
+
+// Painfully simple texture id cacheing to make maps load faster. :3
+static struct {
+	char name[9];
+	INT32 id;
+} *tidcache = NULL;
+static INT32 tidcachelen = 0;
+
+//
+// MAPTEXTURE_T CACHING
+// When a texture is first needed, it counts the number of composite columns
+//  required in the texture and allocates space for a column directory and
+//  any new columns.
+// The directory will simply point inside other patches if there is only one
+//  patch in a given column, but any columns with multiple patches will have
+//  new column_ts generated.
+//
+
+//
+// R_DrawColumnInCache
+// Clip and draw a column from a patch into a cached post.
+//
+static inline void R_DrawColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	(void)patchheight; // This parameter is unused
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		source = (UINT8 *)patch + 3;
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source -= position; // start further down the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		if (count > 0)
+			M_Memcpy(cache + position, source, count);
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_DrawFlippedColumnInCache
+// Similar to R_DrawColumnInCache; it draws the column inverted, however.
+//
+static inline void R_DrawFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source, *dest;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		topdelta = patchheight-patch->length-topdelta;
+		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source += position; // start further UP the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		dest = cache + position;
+		if (count > 0)
+		{
+			for (; dest < cache + position + count; --source)
+				*dest++ = *source;
+		}
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_DrawBlendColumnInCache
+// Draws a translucent column into the cache, applying a half-cooked equation to get a proper translucency value (Needs code in R_GenerateTexture()).
+//
+static inline void R_DrawBlendColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source, *dest;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	(void)patchheight; // This parameter is unused
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		source = (UINT8 *)patch + 3;
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source -= position; // start further down the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		dest = cache + position;
+		if (count > 0)
+		{
+			for (; dest < cache + position + count; source++, dest++)
+				if (*source != 0xFF)
+					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
+		}
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_DrawBlendFlippedColumnInCache
+// Similar to the one above except that the column is inverted.
+//
+static inline void R_DrawBlendFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source, *dest;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		topdelta = patchheight-patch->length-topdelta;
+		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source += position; // start further UP the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		dest = cache + position;
+		if (count > 0)
+		{
+			for (; dest < cache + position + count; --source, dest++)
+				if (*source != 0xFF)
+					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
+		}
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_GenerateTexture
+//
+// Allocate space for full size texture, either single patch or 'composite'
+// Build the full textures from patches.
+// The texture caching system is a little more hungry of memory, but has
+// been simplified for the sake of highcolor (lol), dynamic ligthing, & speed.
+//
+// This is not optimised, but it's supposed to be executed only once
+// per level, when enough memory is available.
+//
+UINT8 *R_GenerateTexture(size_t texnum)
+{
+	UINT8 *block;
+	UINT8 *blocktex;
+	texture_t *texture;
+	texpatch_t *patch;
+	softwarepatch_t *realpatch;
+	UINT8 *pdata;
+	int x, x1, x2, i, width, height;
+	size_t blocksize;
+	column_t *patchcol;
+	UINT8 *colofs;
+
+	UINT16 wadnum;
+	lumpnum_t lumpnum;
+	size_t lumplength;
+
+	I_Assert(texnum <= (size_t)numtextures);
+	texture = textures[texnum];
+	I_Assert(texture != NULL);
+
+	// allocate texture column offset lookup
+
+	// single-patch textures can have holes in them and may be used on
+	// 2sided lines so they need to be kept in 'packed' format
+	// BUT this is wrong for skies and walls with over 255 pixels,
+	// so check if there's holes and if not strip the posts.
+	if (texture->patchcount == 1)
+	{
+		boolean holey = false;
+		patch = texture->patches;
+
+		wadnum = patch->wad;
+		lumpnum = patch->lump;
+		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+		realpatch = (softwarepatch_t *)pdata;
+
+#ifndef NO_PNG_LUMPS
+		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
+			goto multipatch;
+#endif
+#ifdef WALLFLATS
+		if (texture->type == TEXTURETYPE_FLAT)
+			goto multipatch;
+#endif
+
+		// Check the patch for holes.
+		if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
+			holey = true;
+		colofs = (UINT8 *)realpatch->columnofs;
+		for (x = 0; x < texture->width && !holey; x++)
+		{
+			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(*(UINT32 *)&colofs[x<<2]));
+			INT32 topdelta, prevdelta = -1, y = 0;
+			while (col->topdelta != 0xff)
+			{
+				topdelta = col->topdelta;
+				if (topdelta <= prevdelta)
+					topdelta += prevdelta;
+				prevdelta = topdelta;
+				if (topdelta > y)
+					break;
+				y = topdelta + col->length + 1;
+				col = (column_t *)((UINT8 *)col + col->length + 4);
+			}
+			if (y < texture->height)
+				holey = true; // this texture is HOLEy! D:
+		}
+
+		// If the patch uses transparency, we have to save it this way.
+		if (holey)
+		{
+			texture->holes = true;
+			texture->flip = patch->flip;
+			blocksize = lumplength;
+			block = Z_Calloc(blocksize, PU_STATIC, // will change tag at end of this function
+				&texturecache[texnum]);
+			M_Memcpy(block, realpatch, blocksize);
+			texturememory += blocksize;
+
+			// use the patch's column lookup
+			colofs = (block + 8);
+			texturecolumnofs[texnum] = (UINT32 *)colofs;
+			blocktex = block;
+			if (patch->flip & 1) // flip the patch horizontally
+			{
+				UINT8 *realcolofs = (UINT8 *)realpatch->columnofs;
+				for (x = 0; x < texture->width; x++)
+					*(UINT32 *)&colofs[x<<2] = realcolofs[( texture->width-1-x )<<2]; // swap with the offset of the other side of the texture
+			}
+			// we can't as easily flip the patch vertically sadly though,
+			//  we have wait until the texture itself is drawn to do that
+			for (x = 0; x < texture->width; x++)
+				*(UINT32 *)&colofs[x<<2] = LONG(LONG(*(UINT32 *)&colofs[x<<2]) + 3);
+			goto done;
+		}
+
+		// Otherwise, do multipatch format.
+	}
+
+	// multi-patch textures (or 'composite')
+	multipatch:
+	texture->holes = false;
+	texture->flip = 0;
+	blocksize = (texture->width * 4) + (texture->width * texture->height);
+	texturememory += blocksize;
+	block = Z_Malloc(blocksize+1, PU_STATIC, &texturecache[texnum]);
+
+	memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack
+
+	// columns lookup table
+	colofs = block;
+	texturecolumnofs[texnum] = (UINT32 *)colofs;
+
+	// texture data after the lookup table
+	blocktex = block + (texture->width*4);
+
+	// Composite the columns together.
+	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
+	{
+		boolean dealloc = true;
+		static void (*ColumnDrawerPointer)(column_t *, UINT8 *, texpatch_t *, INT32, INT32); // Column drawing function pointer.
+		if (patch->style != AST_COPY)
+			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawBlendFlippedColumnInCache : R_DrawBlendColumnInCache;
+		else
+			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawFlippedColumnInCache : R_DrawColumnInCache;
+
+		wadnum = patch->wad;
+		lumpnum = patch->lump;
+		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+		realpatch = (softwarepatch_t *)pdata;
+		dealloc = true;
+
+#ifndef NO_PNG_LUMPS
+		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
+			realpatch = (softwarepatch_t *)Picture_PNGConvert((UINT8 *)realpatch, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0);
+		else
+#endif
+#ifdef WALLFLATS
+		if (texture->type == TEXTURETYPE_FLAT)
+			realpatch = (softwarepatch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_DOOMPATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
+		else
+#endif
+		{
+			(void)lumplength;
+			dealloc = false;
+		}
+
+		x1 = patch->originx;
+		width = SHORT(realpatch->width);
+		height = SHORT(realpatch->height);
+		x2 = x1 + width;
+
+		if (x1 > texture->width || x2 < 0)
+		{
+			if (dealloc)
+				Z_Free(realpatch);
+			continue; // patch not located within texture's x bounds, ignore
+		}
+
+		if (patch->originy > texture->height || (patch->originy + height) < 0)
+		{
+			if (dealloc)
+				Z_Free(realpatch);
+			continue; // patch not located within texture's y bounds, ignore
+		}
+
+		// patch is actually inside the texture!
+		// now check if texture is partly off-screen and adjust accordingly
+
+		// left edge
+		if (x1 < 0)
+			x = 0;
+		else
+			x = x1;
+
+		// right edge
+		if (x2 > texture->width)
+			x2 = texture->width;
+
+		for (; x < x2; x++)
+		{
+			if (patch->flip & 1)
+				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[(x1+width-1)-x]));
+			else
+				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[x-x1]));
+
+			// generate column ofset lookup
+			*(UINT32 *)&colofs[x<<2] = LONG((x * texture->height) + (texture->width*4));
+			ColumnDrawerPointer(patchcol, block + LONG(*(UINT32 *)&colofs[x<<2]), patch, texture->height, height);
+		}
+
+		if (dealloc)
+			Z_Free(realpatch);
+	}
+
+done:
+	// Now that the texture has been built in column cache, it is purgable from zone memory.
+	Z_ChangeTag(block, PU_CACHE);
+	return blocktex;
+}
+
+//
+// R_GenerateTextureAsFlat
+//
+// Generates a flat picture for a texture.
+//
+UINT8 *R_GenerateTextureAsFlat(size_t texnum)
+{
+	texture_t *texture = textures[texnum];
+	UINT8 *converted = NULL;
+	size_t size = (texture->width * texture->height);
+
+	// The flat picture for this texture was not generated yet.
+	if (!texture->flat)
+	{
+		// Well, let's do it now, then.
+		texture->flat = Z_Malloc(size, PU_STATIC, NULL);
+
+		// Picture_TextureToFlat handles everything for us.
+		converted = (UINT8 *)Picture_TextureToFlat(texnum);
+		M_Memcpy(texture->flat, converted, size);
+		Z_Free(converted);
+	}
+
+	return texture->flat;
+}
+
+//
+// R_GetTextureNum
+//
+// Returns the actual texture id that we should use.
+// This can either be texnum, the current frame for texnum's anim (if animated),
+// or 0 if not valid.
+//
+INT32 R_GetTextureNum(INT32 texnum)
+{
+	if (texnum < 0 || texnum >= numtextures)
+		return 0;
+	return texturetranslation[texnum];
+}
+
+//
+// R_CheckTextureCache
+//
+// Use this if you need to make sure the texture is cached before R_GetColumn calls
+// e.g.: midtextures and FOF walls
+//
+void R_CheckTextureCache(INT32 tex)
+{
+	if (!texturecache[tex])
+		R_GenerateTexture(tex);
+}
+
+//
+// R_GetColumn
+//
+UINT8 *R_GetColumn(fixed_t tex, INT32 col)
+{
+	UINT8 *data;
+	INT32 width = texturewidth[tex];
+
+	if (width & (width - 1))
+		col = (UINT32)col % width;
+	else
+		col &= (width - 1);
+
+	data = texturecache[tex];
+	if (!data)
+		data = R_GenerateTexture(tex);
+
+	return data + LONG(texturecolumnofs[tex][col]);
+}
+
+void *R_GetFlat(lumpnum_t flatlumpnum)
+{
+	return W_CacheLumpNum(flatlumpnum, PU_CACHE);
+}
+
+//
+// R_GetLevelFlat
+//
+// If needed, convert a texture or patch to a flat.
+//
+void *R_GetLevelFlat(levelflat_t *levelflat)
+{
+	boolean isleveltexture = (levelflat->type == LEVELFLAT_TEXTURE);
+	texture_t *texture = (isleveltexture ? textures[levelflat->u.texture.num] : NULL);
+	boolean texturechanged = (isleveltexture ? (levelflat->u.texture.num != levelflat->u.texture.lastnum) : false);
+	UINT8 *flatdata = NULL;
+
+	// Check if the texture changed.
+	if (isleveltexture && (!texturechanged))
+	{
+		if (texture->flat)
+		{
+			flatdata = texture->flat;
+			ds_flatwidth = texture->width;
+			ds_flatheight = texture->height;
+			texturechanged = false;
+		}
+		else
+			texturechanged = true;
+	}
+
+	// If the texture changed, or the flat wasn't generated, convert.
+	if (levelflat->picture == NULL || texturechanged)
+	{
+		// Level texture
+		if (isleveltexture)
+		{
+			levelflat->picture = R_GenerateTextureAsFlat(levelflat->u.texture.num);
+			ds_flatwidth = levelflat->width = texture->width;
+			ds_flatheight = levelflat->height = texture->height;
+		}
+		else
+		{
+#ifndef NO_PNG_LUMPS
+			if (levelflat->type == LEVELFLAT_PNG)
+			{
+				INT32 pngwidth, pngheight;
+
+				levelflat->picture = Picture_PNGConvert(W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, 0);
+				levelflat->width = (UINT16)pngwidth;
+				levelflat->height = (UINT16)pngheight;
+
+				ds_flatwidth = levelflat->width;
+				ds_flatheight = levelflat->height;
+			}
+			else
+#endif
+			if (levelflat->type == LEVELFLAT_PATCH)
+			{
+				UINT8 *converted;
+				size_t size;
+				softwarepatch_t *patch = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE);
+
+				levelflat->width = ds_flatwidth = SHORT(patch->width);
+				levelflat->height = ds_flatheight = SHORT(patch->height);
+
+				levelflat->picture = Z_Malloc(levelflat->width * levelflat->height, PU_LEVEL, NULL);
+				converted = Picture_FlatConvert(PICFMT_DOOMPATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, SHORT(patch->topoffset), SHORT(patch->leftoffset), 0);
+				M_Memcpy(levelflat->picture, converted, size);
+				Z_Free(converted);
+			}
+		}
+	}
+	else
+	{
+		ds_flatwidth = levelflat->width;
+		ds_flatheight = levelflat->height;
+	}
+
+	levelflat->u.texture.lastnum = levelflat->u.texture.num;
+
+	if (flatdata == NULL)
+		flatdata = levelflat->picture;
+	return flatdata;
+}
+
+//
+// R_CheckPowersOfTwo
+//
+// Self-explanatory?
+//
+boolean R_CheckPowersOfTwo(void)
+{
+	boolean wpow2 = (!(ds_flatwidth & (ds_flatwidth - 1)));
+	boolean hpow2 = (!(ds_flatheight & (ds_flatheight - 1)));
+
+	// Initially, the flat isn't powers-of-two-sized.
+	ds_powersoftwo = false;
+
+	// But if the width and height are powers of two,
+	// and are EQUAL, then it's okay :]
+	if ((ds_flatwidth == ds_flatheight) && (wpow2 && hpow2))
+		ds_powersoftwo = true;
+
+	// Just return ds_powersoftwo.
+	return ds_powersoftwo;
+}
+
+//
+// R_CheckFlatLength
+//
+// Determine the flat's dimensions from its lump length.
+//
+void R_CheckFlatLength(size_t size)
+{
+	switch (size)
+	{
+		case 4194304: // 2048x2048 lump
+			nflatmask = 0x3FF800;
+			nflatxshift = 21;
+			nflatyshift = 10;
+			nflatshiftup = 5;
+			ds_flatwidth = ds_flatheight = 2048;
+			break;
+		case 1048576: // 1024x1024 lump
+			nflatmask = 0xFFC00;
+			nflatxshift = 22;
+			nflatyshift = 12;
+			nflatshiftup = 6;
+			ds_flatwidth = ds_flatheight = 1024;
+			break;
+		case 262144:// 512x512 lump
+			nflatmask = 0x3FE00;
+			nflatxshift = 23;
+			nflatyshift = 14;
+			nflatshiftup = 7;
+			ds_flatwidth = ds_flatheight = 512;
+			break;
+		case 65536: // 256x256 lump
+			nflatmask = 0xFF00;
+			nflatxshift = 24;
+			nflatyshift = 16;
+			nflatshiftup = 8;
+			ds_flatwidth = ds_flatheight = 256;
+			break;
+		case 16384: // 128x128 lump
+			nflatmask = 0x3F80;
+			nflatxshift = 25;
+			nflatyshift = 18;
+			nflatshiftup = 9;
+			ds_flatwidth = ds_flatheight = 128;
+			break;
+		case 1024: // 32x32 lump
+			nflatmask = 0x3E0;
+			nflatxshift = 27;
+			nflatyshift = 22;
+			nflatshiftup = 11;
+			ds_flatwidth = ds_flatheight = 32;
+			break;
+		default: // 64x64 lump
+			nflatmask = 0xFC0;
+			nflatxshift = 26;
+			nflatyshift = 20;
+			nflatshiftup = 10;
+			ds_flatwidth = ds_flatheight = 64;
+			break;
+	}
+}
+
+//
+// Empty the texture cache (used for load wad at runtime)
+//
+void R_FlushTextureCache(void)
+{
+	INT32 i;
+
+	if (numtextures)
+		for (i = 0; i < numtextures; i++)
+			Z_Free(texturecache[i]);
+}
+
+// Need these prototypes for later; defining them here instead of r_textures.h so they're "private"
+int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum);
+void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *index);
+
+#ifdef WALLFLATS
+static INT32
+Rloadflats (INT32 i, INT32 w)
+{
+	UINT16 j;
+	UINT16 texstart, texend;
+	texture_t *texture;
+	texpatch_t *patch;
+
+	// Yes
+	if (wadfiles[w]->type == RET_PK3)
+	{
+		texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
+		texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
+	}
+	else
+	{
+		texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
+		texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
+	}
+
+	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
+	{
+		// Work through each lump between the markers in the WAD.
+		for (j = 0; j < (texend - texstart); j++)
+		{
+			UINT8 *flatlump;
+			UINT16 wadnum = (UINT16)w;
+			lumpnum_t lumpnum = texstart + j;
+			size_t lumplength;
+			size_t flatsize = 0;
+
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
+					continue; // If it is then SKIP IT
+			}
+
+			flatlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+
+			switch (lumplength)
+			{
+				case 4194304: // 2048x2048 lump
+					flatsize = 2048;
+					break;
+				case 1048576: // 1024x1024 lump
+					flatsize = 1024;
+					break;
+				case 262144:// 512x512 lump
+					flatsize = 512;
+					break;
+				case 65536: // 256x256 lump
+					flatsize = 256;
+					break;
+				case 16384: // 128x128 lump
+					flatsize = 128;
+					break;
+				case 1024: // 32x32 lump
+					flatsize = 32;
+					break;
+				default: // 64x64 lump
+					flatsize = 64;
+					break;
+			}
+
+			//CONS_Printf("\n\"%s\" is a flat, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),flatsize,flatsize);
+			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
+
+			// Set texture properties.
+			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
+
+#ifndef NO_PNG_LUMPS
+			if (Picture_IsLumpPNG((UINT8 *)flatlump, lumplength))
+			{
+				INT32 width, height;
+				Picture_PNGDimensions((UINT8 *)flatlump, &width, &height, NULL, NULL, lumplength);
+				texture->width = (INT16)width;
+				texture->height = (INT16)height;
+			}
+			else
+#endif
+				texture->width = texture->height = flatsize;
+
+			texture->type = TEXTURETYPE_FLAT;
+			texture->patchcount = 1;
+			texture->holes = false;
+			texture->flip = 0;
+
+			// Allocate information for the texture's patches.
+			patch = &texture->patches[0];
+
+			patch->originx = patch->originy = 0;
+			patch->wad = (UINT16)w;
+			patch->lump = texstart + j;
+			patch->flip = 0;
+
+			Z_Unlock(flatlump);
+
+			texturewidth[i] = texture->width;
+			textureheight[i] = texture->height << FRACBITS;
+			i++;
+		}
+	}
+
+	return i;
+}
+#endif/*WALLFLATS*/
+
+#define TX_START "TX_START"
+#define TX_END "TX_END"
+
+static INT32
+Rloadtextures (INT32 i, INT32 w)
+{
+	UINT16 j;
+	UINT16 texstart, texend, texturesLumpPos;
+	texture_t *texture;
+	softwarepatch_t *patchlump;
+	texpatch_t *patch;
+
+	// Get the lump numbers for the markers in the WAD, if they exist.
+	if (wadfiles[w]->type == RET_PK3)
+	{
+		texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
+		texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		while (texturesLumpPos != INT16_MAX)
+		{
+			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
+			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
+		}
+	}
+	else
+	{
+		texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
+		texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		if (texturesLumpPos != INT16_MAX)
+			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
+	}
+
+	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
+	{
+		// Work through each lump between the markers in the WAD.
+		for (j = 0; j < (texend - texstart); j++)
+		{
+			UINT16 wadnum = (UINT16)w;
+			lumpnum_t lumpnum = texstart + j;
+#ifndef NO_PNG_LUMPS
+			size_t lumplength;
+#endif
+
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
+					continue; // If it is then SKIP IT
+			}
+
+			patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+#ifndef NO_PNG_LUMPS
+			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+#endif
+
+			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
+			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
+
+			// Set texture properties.
+			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
+
+#ifndef NO_PNG_LUMPS
+			if (Picture_IsLumpPNG((UINT8 *)patchlump, lumplength))
+			{
+				INT32 width, height;
+				Picture_PNGDimensions((UINT8 *)patchlump, &width, &height, NULL, NULL, lumplength);
+				texture->width = (INT16)width;
+				texture->height = (INT16)height;
+			}
+			else
+#endif
+			{
+				texture->width = SHORT(patchlump->width);
+				texture->height = SHORT(patchlump->height);
+			}
+
+			texture->type = TEXTURETYPE_SINGLEPATCH;
+			texture->patchcount = 1;
+			texture->holes = false;
+			texture->flip = 0;
+
+			// Allocate information for the texture's patches.
+			patch = &texture->patches[0];
+
+			patch->originx = patch->originy = 0;
+			patch->wad = (UINT16)w;
+			patch->lump = texstart + j;
+			patch->flip = 0;
+
+			Z_Unlock(patchlump);
+
+			texturewidth[i] = texture->width;
+			textureheight[i] = texture->height << FRACBITS;
+			i++;
+		}
+	}
+
+	return i;
+}
+
+//
+// R_LoadTextures
+// Initializes the texture list with the textures from the world map.
+//
+void R_LoadTextures(void)
+{
+	INT32 i, w;
+	UINT16 j;
+	UINT16 texstart, texend, texturesLumpPos;
+
+	// Free previous memory before numtextures change.
+	if (numtextures)
+	{
+		for (i = 0; i < numtextures; i++)
+		{
+			Z_Free(textures[i]);
+			Z_Free(texturecache[i]);
+		}
+		Z_Free(texturetranslation);
+		Z_Free(textures);
+	}
+
+	// Load patches and textures.
+
+	// Get the number of textures to check.
+	// NOTE: Make SURE the system does not process
+	// the markers.
+	// This system will allocate memory for all duplicate/patched textures even if it never uses them,
+	// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
+	for (w = 0, numtextures = 0; w < numwadfiles; w++)
+	{
+#ifdef WALLFLATS
+		// Count flats
+		if (wadfiles[w]->type == RET_PK3)
+		{
+			texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
+			texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
+		}
+		else
+		{
+			texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
+			texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
+		}
+
+		if (!( texstart == INT16_MAX || texend == INT16_MAX ))
+		{
+			// PK3s have subfolders, so we can't just make a simple sum
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				for (j = texstart; j < texend; j++)
+				{
+					if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
+						numtextures++;
+				}
+			}
+			else // Add all the textures between F_START and F_END
+			{
+				numtextures += (UINT32)(texend - texstart);
+			}
+		}
+#endif/*WALLFLATS*/
+
+		// Count the textures from TEXTURES lumps
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		while (texturesLumpPos != INT16_MAX)
+		{
+			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
+			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
+		}
+
+		// Count single-patch textures
+		if (wadfiles[w]->type == RET_PK3)
+		{
+			texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
+			texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
+		}
+		else
+		{
+			texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
+			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
+		}
+
+		if (texstart == INT16_MAX || texend == INT16_MAX)
+			continue;
+
+		// PK3s have subfolders, so we can't just make a simple sum
+		if (wadfiles[w]->type == RET_PK3)
+		{
+			for (j = texstart; j < texend; j++)
+			{
+				if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
+					numtextures++;
+			}
+		}
+		else // Add all the textures between TX_START and TX_END
+		{
+			numtextures += (UINT32)(texend - texstart);
+		}
+	}
+
+	// If no textures found by this point, bomb out
+	if (!numtextures)
+		I_Error("No textures detected in any WADs!\n");
+
+	// Allocate memory and initialize to 0 for all the textures we are initialising.
+	// There are actually 5 buffers allocated in one for convenience.
+	textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
+
+	// Allocate texture column offset table.
+	texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
+	// Allocate texture referencing cache.
+	texturecache     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
+	// Allocate texture width table.
+	texturewidth     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
+	// Allocate texture height table.
+	textureheight    = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
+	// Create translation table for global animation.
+	texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
+
+	for (i = 0; i < numtextures; i++)
+		texturetranslation[i] = i;
+
+	for (i = 0, w = 0; w < numwadfiles; w++)
+	{
+#ifdef WALLFLATS
+		i = Rloadflats(i, w);
+#endif
+		i = Rloadtextures(i, w);
+	}
+
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+		HWR_LoadMapTextures(numtextures);
+#endif
+}
+
+static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
+{
+	char *texturesToken;
+	size_t texturesTokenLength;
+	char *endPos;
+	char *patchName = NULL;
+	INT16 patchXPos;
+	INT16 patchYPos;
+	UINT8 flip = 0;
+	UINT8 alpha = 255;
+	enum patchalphastyle style = AST_COPY;
+	texpatch_t *resultPatch = NULL;
+	lumpnum_t patchLumpNum;
+
+	// Patch identifier
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch name should be");
+	}
+	texturesTokenLength = strlen(texturesToken);
+	if (texturesTokenLength>8)
+	{
+		I_Error("Error parsing TEXTURES lump: Patch name \"%s\" exceeds 8 characters",texturesToken);
+	}
+	else
+	{
+		if (patchName != NULL)
+		{
+			Z_Free(patchName);
+		}
+		patchName = (char *)Z_Malloc((texturesTokenLength+1)*sizeof(char),PU_STATIC,NULL);
+		M_Memcpy(patchName,texturesToken,texturesTokenLength*sizeof(char));
+		patchName[texturesTokenLength] = '\0';
+	}
+
+	// Comma 1
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after \"%s\"'s patch name should be",patchName);
+	}
+	if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after %s's patch name, got \"%s\"",patchName,texturesToken);
+	}
+
+	// XPos
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s x coordinate should be",patchName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	patchXPos = strtol(texturesToken,&endPos,10);
+	(void)patchXPos; //unused for now
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
+	}
+
+	// Comma 2
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after patch \"%s\"'s x coordinate should be",patchName);
+	}
+	if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
+	}
+
+	// YPos
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s y coordinate should be",patchName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	patchYPos = strtol(texturesToken,&endPos,10);
+	(void)patchYPos; //unused for now
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s y coordinate, got \"%s\"",patchName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Patch parameters block (OPTIONAL)
+	// added by Monster Iestyn (22/10/16)
+
+	// Left Curly Brace
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+		; // move on and ignore, R_ParseTextures will deal with this
+	else
+	{
+		if (strcmp(texturesToken,"{")==0)
+		{
+			Z_Free(texturesToken);
+			texturesToken = M_GetToken(NULL);
+			if (texturesToken == NULL)
+			{
+				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters should be",patchName);
+			}
+			while (strcmp(texturesToken,"}")!=0)
+			{
+				if (stricmp(texturesToken, "ALPHA")==0)
+				{
+					Z_Free(texturesToken);
+					texturesToken = M_GetToken(NULL);
+					alpha = 255*strtof(texturesToken, NULL);
+				}
+				else if (stricmp(texturesToken, "STYLE")==0)
+				{
+					Z_Free(texturesToken);
+					texturesToken = M_GetToken(NULL);
+					if (stricmp(texturesToken, "TRANSLUCENT")==0)
+						style = AST_TRANSLUCENT;
+					else if (stricmp(texturesToken, "ADD")==0)
+						style = AST_ADD;
+					else if (stricmp(texturesToken, "SUBTRACT")==0)
+						style = AST_SUBTRACT;
+					else if (stricmp(texturesToken, "REVERSESUBTRACT")==0)
+						style = AST_REVERSESUBTRACT;
+					else if (stricmp(texturesToken, "MODULATE")==0)
+						style = AST_MODULATE;
+				}
+				else if (stricmp(texturesToken, "FLIPX")==0)
+					flip |= 1;
+				else if (stricmp(texturesToken, "FLIPY")==0)
+					flip |= 2;
+				Z_Free(texturesToken);
+
+				texturesToken = M_GetToken(NULL);
+				if (texturesToken == NULL)
+				{
+					I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters or right curly brace should be",patchName);
+				}
+			}
+		}
+		else
+		{
+			 // this is not what we wanted...
+			 // undo last read so R_ParseTextures can re-get the token for its own purposes
+			M_UnGetToken();
+		}
+		Z_Free(texturesToken);
+	}
+
+	if (actuallyLoadPatch == true)
+	{
+		// Check lump exists
+		patchLumpNum = W_GetNumForName(patchName);
+		// If so, allocate memory for texpatch_t and fill 'er up
+		resultPatch = (texpatch_t *)Z_Malloc(sizeof(texpatch_t),PU_STATIC,NULL);
+		resultPatch->originx = patchXPos;
+		resultPatch->originy = patchYPos;
+		resultPatch->lump = patchLumpNum & 65535;
+		resultPatch->wad = patchLumpNum>>16;
+		resultPatch->flip = flip;
+		resultPatch->alpha = alpha;
+		resultPatch->style = style;
+		// Clean up a little after ourselves
+		Z_Free(patchName);
+		// Then return it
+		return resultPatch;
+	}
+	else
+	{
+		Z_Free(patchName);
+		return NULL;
+	}
+}
+
+static texture_t *R_ParseTexture(boolean actuallyLoadTexture)
+{
+	char *texturesToken;
+	size_t texturesTokenLength;
+	char *endPos;
+	INT32 newTextureWidth;
+	INT32 newTextureHeight;
+	texture_t *resultTexture = NULL;
+	texpatch_t *newPatch;
+	char newTextureName[9]; // no longer dynamically allocated
+
+	// Texture name
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture name should be");
+	}
+	texturesTokenLength = strlen(texturesToken);
+	if (texturesTokenLength>8)
+	{
+		I_Error("Error parsing TEXTURES lump: Texture name \"%s\" exceeds 8 characters",texturesToken);
+	}
+	else
+	{
+		memset(&newTextureName, 0, 9);
+		M_Memcpy(newTextureName, texturesToken, texturesTokenLength);
+		// ^^ we've confirmed that the token is <= 8 characters so it will never overflow a 9 byte char buffer
+		strupr(newTextureName); // Just do this now so we don't have to worry about it
+	}
+	Z_Free(texturesToken);
+
+	// Comma 1
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s name should be",newTextureName);
+	}
+	else if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s name, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Width
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s width should be",newTextureName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	newTextureWidth = strtol(texturesToken,&endPos,10);
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		|| newTextureWidth < 0) // Number is not positive
+	{
+		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Comma 2
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s width should be",newTextureName);
+	}
+	if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Height
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s height should be",newTextureName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	newTextureHeight = strtol(texturesToken,&endPos,10);
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		|| newTextureHeight < 0) // Number is not positive
+	{
+		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s height, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Left Curly Brace
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where open curly brace for texture \"%s\" should be",newTextureName);
+	}
+	if (strcmp(texturesToken,"{")==0)
+	{
+		if (actuallyLoadTexture)
+		{
+			// Allocate memory for a zero-patch texture. Obviously, we'll be adding patches momentarily.
+			resultTexture = (texture_t *)Z_Calloc(sizeof(texture_t),PU_STATIC,NULL);
+			M_Memcpy(resultTexture->name, newTextureName, 8);
+			resultTexture->width = newTextureWidth;
+			resultTexture->height = newTextureHeight;
+			resultTexture->type = TEXTURETYPE_COMPOSITE;
+		}
+		Z_Free(texturesToken);
+		texturesToken = M_GetToken(NULL);
+		if (texturesToken == NULL)
+		{
+			I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch definition for texture \"%s\" should be",newTextureName);
+		}
+		while (strcmp(texturesToken,"}")!=0)
+		{
+			if (stricmp(texturesToken, "PATCH")==0)
+			{
+				Z_Free(texturesToken);
+				if (resultTexture)
+				{
+					// Get that new patch
+					newPatch = R_ParsePatch(true);
+					// Make room for the new patch
+					resultTexture = Z_Realloc(resultTexture, sizeof(texture_t) + (resultTexture->patchcount+1)*sizeof(texpatch_t), PU_STATIC, NULL);
+					// Populate the uninitialized values in the new patch entry of our array
+					M_Memcpy(&resultTexture->patches[resultTexture->patchcount], newPatch, sizeof(texpatch_t));
+					// Account for the new number of patches in the texture
+					resultTexture->patchcount++;
+					// Then free up the memory assigned to R_ParsePatch, as it's unneeded now
+					Z_Free(newPatch);
+				}
+				else
+				{
+					R_ParsePatch(false);
+				}
+			}
+			else
+			{
+				I_Error("Error parsing TEXTURES lump: Expected \"PATCH\" in texture \"%s\", got \"%s\"",newTextureName,texturesToken);
+			}
+
+			texturesToken = M_GetToken(NULL);
+			if (texturesToken == NULL)
+			{
+				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch declaration or right curly brace for texture \"%s\" should be",newTextureName);
+			}
+		}
+		if (resultTexture && resultTexture->patchcount == 0)
+		{
+			I_Error("Error parsing TEXTURES lump: Texture \"%s\" must have at least one patch",newTextureName);
+		}
+	}
+	else
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \"{\" for texture \"%s\", got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	if (actuallyLoadTexture) return resultTexture;
+	else return NULL;
+}
+
+// Parses the TEXTURES lump... but just to count the number of textures.
+int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum)
+{
+	char *texturesLump;
+	size_t texturesLumpLength;
+	char *texturesText;
+	UINT32 numTexturesInLump = 0;
+	char *texturesToken;
+
+	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
+	// need to make a space of memory where I can ensure that it will terminate
+	// correctly. Start by loading the relevant data from the WAD.
+	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
+	// If that didn't exist, we have nothing to do here.
+	if (texturesLump == NULL) return 0;
+	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
+	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
+	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
+	// Now move the contents of the lump into this new location.
+	memmove(texturesText,texturesLump,texturesLumpLength);
+	// Make damn well sure the last character in our new memory location is \0.
+	texturesText[texturesLumpLength] = '\0';
+	// Finally, free up the memory from the first data load, because we really
+	// don't need it.
+	Z_Free(texturesLump);
+
+	texturesToken = M_GetToken(texturesText);
+	while (texturesToken != NULL)
+	{
+		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
+		{
+			numTexturesInLump++;
+			Z_Free(texturesToken);
+			R_ParseTexture(false);
+		}
+		else
+		{
+			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
+		}
+		texturesToken = M_GetToken(NULL);
+	}
+	Z_Free(texturesToken);
+	Z_Free((void *)texturesText);
+
+	return numTexturesInLump;
+}
+
+// Parses the TEXTURES lump... for real, this time.
+void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *texindex)
+{
+	char *texturesLump;
+	size_t texturesLumpLength;
+	char *texturesText;
+	char *texturesToken;
+	texture_t *newTexture;
+
+	I_Assert(texindex != NULL);
+
+	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
+	// need to make a space of memory where I can ensure that it will terminate
+	// correctly. Start by loading the relevant data from the WAD.
+	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
+	// If that didn't exist, we have nothing to do here.
+	if (texturesLump == NULL) return;
+	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
+	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
+	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
+	// Now move the contents of the lump into this new location.
+	memmove(texturesText,texturesLump,texturesLumpLength);
+	// Make damn well sure the last character in our new memory location is \0.
+	texturesText[texturesLumpLength] = '\0';
+	// Finally, free up the memory from the first data load, because we really
+	// don't need it.
+	Z_Free(texturesLump);
+
+	texturesToken = M_GetToken(texturesText);
+	while (texturesToken != NULL)
+	{
+		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
+		{
+			Z_Free(texturesToken);
+			// Get the new texture
+			newTexture = R_ParseTexture(true);
+			// Store the new texture
+			textures[*texindex] = newTexture;
+			texturewidth[*texindex] = newTexture->width;
+			textureheight[*texindex] = newTexture->height << FRACBITS;
+			// Increment i back in R_LoadTextures()
+			(*texindex)++;
+		}
+		else
+		{
+			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
+		}
+		texturesToken = M_GetToken(NULL);
+	}
+	Z_Free(texturesToken);
+	Z_Free((void *)texturesText);
+}
+
+// Search for flat name.
+lumpnum_t R_GetFlatNumForName(const char *name)
+{
+	INT32 i;
+	lumpnum_t lump;
+	lumpnum_t start;
+	lumpnum_t end;
+
+	// Scan wad files backwards so patched flats take preference.
+	for (i = numwadfiles - 1; i >= 0; i--)
+	{
+		switch (wadfiles[i]->type)
+		{
+		case RET_WAD:
+			if ((start = W_CheckNumForMarkerStartPwad("F_START", (UINT16)i, 0)) == INT16_MAX)
+			{
+				if ((start = W_CheckNumForMarkerStartPwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
+					continue;
+				else if ((end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start)) == INT16_MAX)
+					continue;
+			}
+			else
+				if ((end = W_CheckNumForNamePwad("F_END", (UINT16)i, start)) == INT16_MAX)
+					continue;
+			break;
+		case RET_PK3:
+			if ((start = W_CheckNumForFolderStartPK3("Flats/", i, 0)) == INT16_MAX)
+				continue;
+			if ((end = W_CheckNumForFolderEndPK3("Flats/", i, start)) == INT16_MAX)
+				continue;
+			break;
+		default:
+			continue;
+		}
+
+		// Now find lump with specified name in that range.
+		lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
+		if (lump < end)
+		{
+			lump += (i<<16); // found it, in our constraints
+			break;
+		}
+		lump = LUMPERROR;
+	}
+
+	return lump;
+}
+
+void R_ClearTextureNumCache(boolean btell)
+{
+	if (tidcache)
+		Z_Free(tidcache);
+	tidcache = NULL;
+	if (btell)
+		CONS_Debug(DBG_SETUP, "Fun Fact: There are %d textures used in this map.\n", tidcachelen);
+	tidcachelen = 0;
+}
+
+//
+// R_CheckTextureNumForName
+//
+// Check whether texture is available. Filter out NoTexture indicator.
+//
+INT32 R_CheckTextureNumForName(const char *name)
+{
+	INT32 i;
+
+	// "NoTexture" marker.
+	if (name[0] == '-')
+		return 0;
+
+	for (i = 0; i < tidcachelen; i++)
+		if (!strncasecmp(tidcache[i].name, name, 8))
+			return tidcache[i].id;
+
+	// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
+	//for (i = 0; i < numtextures; i++) <- old
+	for (i = (numtextures - 1); i >= 0; i--) // <- new
+		if (!strncasecmp(textures[i]->name, name, 8))
+		{
+			tidcachelen++;
+			Z_Realloc(tidcache, tidcachelen * sizeof(*tidcache), PU_STATIC, &tidcache);
+			strncpy(tidcache[tidcachelen-1].name, name, 8);
+			tidcache[tidcachelen-1].name[8] = '\0';
+#ifndef ZDEBUG
+			CONS_Debug(DBG_SETUP, "texture #%s: %s\n", sizeu1(tidcachelen), tidcache[tidcachelen-1].name);
+#endif
+			tidcache[tidcachelen-1].id = i;
+			return i;
+		}
+
+	return -1;
+}
+
+//
+// R_TextureNumForName
+//
+// Calls R_CheckTextureNumForName, aborts with error message.
+//
+INT32 R_TextureNumForName(const char *name)
+{
+	const INT32 i = R_CheckTextureNumForName(name);
+
+	if (i == -1)
+	{
+		static INT32 redwall = -2;
+		CONS_Debug(DBG_SETUP, "WARNING: R_TextureNumForName: %.8s not found\n", name);
+		if (redwall == -2)
+			redwall = R_CheckTextureNumForName("REDWALL");
+		if (redwall != -1)
+			return redwall;
+		return 1;
+	}
+	return i;
+}
diff --git a/src/r_textures.h b/src/r_textures.h
new file mode 100644
index 0000000000000000000000000000000000000000..74a94a9ededc42ca2a7a66413141de9d8f98535d
--- /dev/null
+++ b/src/r_textures.h
@@ -0,0 +1,103 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_textures.h
+/// \brief Texture generation.
+
+#ifndef __R_TEXTURES__
+#define __R_TEXTURES__
+
+#include "r_defs.h"
+#include "r_state.h"
+#include "p_setup.h" // levelflats
+#include "r_data.h"
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+// A single patch from a texture definition,
+//  basically a rectangular area within
+//  the texture rectangle.
+typedef struct
+{
+	// Block origin (always UL), which has already accounted for the internal origin of the patch.
+	INT16 originx, originy;
+	UINT16 wad, lump;
+	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
+	UINT8 alpha; // Translucency value
+	enum patchalphastyle style;
+} texpatch_t;
+
+// texture type
+enum
+{
+	TEXTURETYPE_UNKNOWN,
+	TEXTURETYPE_SINGLEPATCH,
+	TEXTURETYPE_COMPOSITE,
+#ifdef WALLFLATS
+	TEXTURETYPE_FLAT,
+#endif
+};
+
+// A texture_t describes a rectangular texture,
+//  which is composed of one or more texpatch_t structures
+//  that arrange graphic patches.
+typedef struct
+{
+	// Keep name for switch changing, etc.
+	char name[8];
+	UINT8 type; // TEXTURETYPE_
+	INT16 width, height;
+	boolean holes;
+	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
+	void *flat; // The texture, as a flat.
+
+	// All the patches[patchcount] are drawn back to front into the cached texture.
+	INT16 patchcount;
+	texpatch_t patches[0];
+} texture_t;
+
+// all loaded and prepared textures from the start of the game
+extern texture_t **textures;
+
+extern INT32 *texturewidth;
+extern fixed_t *textureheight; // needed for texture pegging
+
+extern UINT32 **texturecolumnofs; // column offset lookup table for each texture
+extern UINT8 **texturecache; // graphics data for each generated full-size texture
+
+// Load TEXTURES definitions, create lookup tables
+void R_LoadTextures(void);
+void R_FlushTextureCache(void);
+
+// Texture generation
+UINT8 *R_GenerateTexture(size_t texnum);
+UINT8 *R_GenerateTextureAsFlat(size_t texnum);
+INT32 R_GetTextureNum(INT32 texnum);
+void R_CheckTextureCache(INT32 tex);
+void R_ClearTextureNumCache(boolean btell);
+
+// Retrieve texture data.
+void *R_GetLevelFlat(levelflat_t *levelflat);
+UINT8 *R_GetColumn(fixed_t tex, INT32 col);
+void *R_GetFlat(lumpnum_t flatnum);
+
+boolean R_CheckPowersOfTwo(void);
+void R_CheckFlatLength(size_t size);
+
+// Returns the texture number for the texture name.
+INT32 R_TextureNumForName(const char *name);
+INT32 R_CheckTextureNumForName(const char *name);
+lumpnum_t R_GetFlatNumForName(const char *name);
+
+extern INT32 numtextures;
+
+#endif
diff --git a/src/r_things.c b/src/r_things.c
index ddb88ef15375acb1769f93b06c2cf79ad3de3f82..47af8c758f7e17e8a42aac3ab7bf387aab709fa5 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -25,8 +25,11 @@
 #include "i_system.h"
 #include "r_things.h"
 #include "r_patch.h"
+#include "r_patchrotation.h"
+#include "r_picformats.h"
 #include "r_plane.h"
 #include "r_portal.h"
+#include "r_splats.h"
 #include "p_tick.h"
 #include "p_local.h"
 #include "p_slopes.h"
@@ -97,7 +100,7 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 {
 	char cn = R_Frame2Char(frame), cr = R_Rotation2Char(rotation); // for debugging
 
-	INT32 r, ang;
+	INT32 r;
 	lumpnum_t lumppat = wad;
 	lumppat <<= 16;
 	lumppat += lump;
@@ -105,15 +108,13 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 	if (maxframe ==(size_t)-1 || frame > maxframe)
 		maxframe = frame;
 
-	// rotsprite
 #ifdef ROTSPRITE
-	sprtemp[frame].rotsprite.cached = 0;
 	for (r = 0; r < 16; r++)
 	{
-		for (ang = 0; ang < ROTANGLES; ang++)
-			sprtemp[frame].rotsprite.patch[r][ang] = NULL;
+		sprtemp[frame].rotated[0][r] = NULL;
+		sprtemp[frame].rotated[1][r] = NULL;
 	}
-#endif/*ROTSPRITE*/
+#endif
 
 	if (rotation == 0)
 	{
@@ -229,7 +230,7 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	UINT8 frame;
 	UINT8 rotation;
 	lumpinfo_t *lumpinfo;
-	patch_t patch;
+	softwarepatch_t patch;
 	UINT8 numadded = 0;
 
 	memset(sprtemp,0xFF, sizeof (sprtemp));
@@ -241,9 +242,6 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	// if so, it might patch only certain frames, not all
 	if (spritedef->numframes) // (then spriteframes is not null)
 	{
-#ifdef ROTSPRITE
-		R_FreeSingleRotSprite(spritedef);
-#endif
 		// copy the already defined sprite frames
 		M_Memcpy(sprtemp, spritedef->spriteframes,
 		 spritedef->numframes * sizeof (spriteframe_t));
@@ -260,6 +258,12 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	{
 		if (memcmp(lumpinfo[l].name,sprname,4)==0)
 		{
+			INT32 width, height;
+			INT16 topoffset, leftoffset;
+#ifndef NO_PNG_LUMPS
+			boolean isPNG = false;
+#endif
+
 			frame = R_Char2Frame(lumpinfo[l].name[4]);
 			rotation = R_Char2Rotation(lumpinfo[l].name[5]);
 
@@ -275,33 +279,38 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 
 			// store sprite info in lookup tables
 			//FIXME : numspritelumps do not duplicate sprite replacements
-			W_ReadLumpHeaderPwad(wadnum, l, &patch, sizeof (patch_t), 0);
+
 #ifndef NO_PNG_LUMPS
 			{
-				patch_t *png = W_CacheLumpNumPwad(wadnum, l, PU_STATIC);
+				softwarepatch_t *png = W_CacheLumpNumPwad(wadnum, l, PU_STATIC);
 				size_t len = W_LumpLengthPwad(wadnum, l);
-				// lump is a png so convert it
-				if (R_IsLumpPNG((UINT8 *)png, len))
+
+				if (Picture_IsLumpPNG((UINT8 *)png, len))
 				{
-					png = R_PNGToPatch((UINT8 *)png, len, NULL);
-					M_Memcpy(&patch, png, sizeof(INT16)*4);
+					Picture_PNGDimensions((UINT8 *)png, &width, &height, &topoffset, &leftoffset, len);
+					isPNG = true;
 				}
+
 				Z_Free(png);
 			}
+
+			if (!isPNG)
 #endif
-			spritecachedinfo[numspritelumps].width = SHORT(patch.width)<<FRACBITS;
-			spritecachedinfo[numspritelumps].offset = SHORT(patch.leftoffset)<<FRACBITS;
-			spritecachedinfo[numspritelumps].topoffset = SHORT(patch.topoffset)<<FRACBITS;
-			spritecachedinfo[numspritelumps].height = SHORT(patch.height)<<FRACBITS;
-
-			//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
-			if (rendermode != render_none) // not for psprite
-				spritecachedinfo[numspritelumps].topoffset += FEETADJUST;
-			// Being selective with this causes bad things. :( Like the special stage tokens breaking apart.
-			/*if (rendermode != render_none // not for psprite
-			 && SHORT(patch.topoffset)>0 && SHORT(patch.topoffset)<SHORT(patch.height))
-				// perfect is patch.height but sometime it is too high
-				spritecachedinfo[numspritelumps].topoffset = min(SHORT(patch.topoffset)+(FEETADJUST>>FRACBITS),SHORT(patch.height))<<FRACBITS;*/
+			{
+				W_ReadLumpHeaderPwad(wadnum, l, &patch, sizeof(INT16) * 4, 0);
+				width = (INT32)(SHORT(patch.width));
+				height = (INT32)(SHORT(patch.height));
+				topoffset = (INT16)(SHORT(patch.topoffset));
+				leftoffset = (INT16)(SHORT(patch.leftoffset));
+			}
+
+			spritecachedinfo[numspritelumps].width = width<<FRACBITS;
+			spritecachedinfo[numspritelumps].offset = leftoffset<<FRACBITS;
+			spritecachedinfo[numspritelumps].topoffset = topoffset<<FRACBITS;
+			spritecachedinfo[numspritelumps].height = height<<FRACBITS;
+
+			// BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
+			spritecachedinfo[numspritelumps].topoffset += FEETADJUST;
 
 			//----------------------------------------------------
 
@@ -398,9 +407,6 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	if (spritedef->numframes &&             // has been allocated
 		spritedef->numframes < maxframe)   // more frames are defined ?
 	{
-#ifdef ROTSPRITE
-		R_FreeSingleRotSprite(spritedef);
-#endif
 		Z_Free(spritedef->spriteframes);
 		spritedef->spriteframes = NULL;
 	}
@@ -730,6 +736,55 @@ void R_DrawFlippedMaskedColumn(column_t *column)
 	dc_texturemid = basetexturemid;
 }
 
+boolean R_SpriteIsFlashing(vissprite_t *vis)
+{
+	return (!(vis->cut & SC_PRECIP)
+	&& (vis->mobj->flags & (MF_ENEMY|MF_BOSS))
+	&& (vis->mobj->flags2 & MF2_FRET)
+	&& !(vis->mobj->flags & MF_GRENADEBOUNCE)
+	&& (leveltime & 1));
+}
+
+UINT8 *R_GetSpriteTranslation(vissprite_t *vis)
+{
+	if (R_SpriteIsFlashing(vis)) // Bosses "flash"
+	{
+		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
+			return R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
+			return R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
+		else
+			return R_GetTranslationColormap(TC_BOSS, 0, GTC_CACHE);
+	}
+	else if (vis->mobj->color)
+	{
+		// New colormap stuff for skins Tails 06-07-2002
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
+			return R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP)
+			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
+			&& (vis->mobj->player->charflags & SF_DASHMODE)
+			&& ((leveltime/2) & 1))
+		{
+			if (vis->mobj->player->charflags & SF_MACHINE)
+				return R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+			else
+				return R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		}
+		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
+		{
+			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
+			return R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
+		}
+		else // Use the defaults
+			return R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color, GTC_CACHE);
+	}
+	else if (vis->mobj->sprite == SPR_PLAY) // Looks like a player, but doesn't have a color? Get rid of green sonic syndrome.
+		return R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLUE, GTC_CACHE);
+
+	return NULL;
+}
+
 //
 // R_DrawVisSprite
 //  mfloorclip and mceilingclip should also be set.
@@ -742,7 +797,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	INT32 pwidth;
 	fixed_t frac;
 	patch_t *patch = vis->patch;
-	fixed_t this_scale = vis->mobj->scale;
+	fixed_t this_scale = vis->thingscale;
 	INT32 x1, x2;
 	INT64 overflow_test;
 
@@ -763,79 +818,26 @@ static void R_DrawVisSprite(vissprite_t *vis)
 
 	colfunc = colfuncs[BASEDRAWFUNC]; // hack: this isn't resetting properly somewhere.
 	dc_colormap = vis->colormap;
-	if (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
-	{
-		// translate certain pixels to white
-		colfunc = colfuncs[COLDRAWFUNC_TRANS];
-		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
-			dc_translation = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
-		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
-			dc_translation = R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
-		else
-			dc_translation = R_GetTranslationColormap(TC_BOSS, 0, GTC_CACHE);
-	}
+	dc_translation = R_GetSpriteTranslation(vis);
+
+	if (R_SpriteIsFlashing(vis)) // Bosses "flash"
+		colfunc = colfuncs[COLDRAWFUNC_TRANS]; // translate certain pixels to white
 	else if (vis->mobj->color && vis->transmap) // Color mapping
 	{
 		colfunc = colfuncs[COLDRAWFUNC_TRANSTRANS];
 		dc_transmap = vis->transmap;
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
-			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		else if (!(vis->cut & SC_PRECIP)
-			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
-			&& (vis->mobj->player->charflags & SF_DASHMODE)
-			&& ((leveltime/2) & 1))
-		{
-			if (vis->mobj->player->charflags & SF_MACHINE)
-				dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
-			else
-				dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		}
-		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
-		{
-			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
-			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
-		}
-		else // Use the defaults
-			dc_translation = R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color, GTC_CACHE);
 	}
 	else if (vis->transmap)
 	{
 		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
 		dc_transmap = vis->transmap;    //Fab : 29-04-98: translucency table
 	}
-	else if (vis->mobj->color)
-	{
-		// translate green skin to another color
+	else if (vis->mobj->color) // translate green skin to another color
 		colfunc = colfuncs[COLDRAWFUNC_TRANS];
-
-		// New colormap stuff for skins Tails 06-07-2002
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
-			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		else if (!(vis->cut & SC_PRECIP)
-			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
-			&& (vis->mobj->player->charflags & SF_DASHMODE)
-			&& ((leveltime/2) & 1))
-		{
-			if (vis->mobj->player->charflags & SF_MACHINE)
-				dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
-			else
-				dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		}
-		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
-		{
-			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
-			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
-		}
-		else // Use the defaults
-			dc_translation = R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color, GTC_CACHE);
-	}
 	else if (vis->mobj->sprite == SPR_PLAY) // Looks like a player, but doesn't have a color? Get rid of green sonic syndrome.
-	{
 		colfunc = colfuncs[COLDRAWFUNC_TRANS];
-		dc_translation = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLUE, GTC_CACHE);
-	}
 
-	if (vis->extra_colormap)
+	if (vis->extra_colormap && !(vis->renderflags & RF_NOCOLORMAPS))
 	{
 		if (!dc_colormap)
 			dc_colormap = vis->extra_colormap->colormap;
@@ -889,18 +891,21 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		vis->x2 = vid.width-1;
 
 	localcolfunc = (vis->cut & SC_VFLIP) ? R_DrawFlippedMaskedColumn : R_DrawMaskedColumn;
-	lengthcol = SHORT(patch->height);
+	lengthcol = patch->height;
 
 	// Split drawing loops for paper and non-paper to reduce conditional checks per sprite
 	if (vis->scalestep)
 	{
-		pwidth = SHORT(patch->width);
+		fixed_t horzscale = FixedMul(vis->spritexscale, this_scale);
+		fixed_t scalestep = FixedMul(vis->scalestep, vis->spriteyscale);
+
+		pwidth = patch->width;
 
 		// Papersprite drawing loop
-		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, spryscale += vis->scalestep)
+		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, spryscale += scalestep)
 		{
 			angle_t angle = ((vis->centerangle + xtoviewangle[dc_x]) >> ANGLETOFINESHIFT) & 0xFFF;
-			texturecolumn = (vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)) / this_scale;
+			texturecolumn = (vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)) / horzscale;
 
 			if (texturecolumn < 0 || texturecolumn >= pwidth)
 				continue;
@@ -911,15 +916,37 @@ static void R_DrawVisSprite(vissprite_t *vis)
 			sprtopscreen = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 			dc_iscale = (0xffffffffu / (unsigned)spryscale);
 
-			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
+
+			localcolfunc (column);
+		}
+	}
+	else if (vis->cut & SC_SHEAR)
+	{
+#ifdef RANGECHECK
+		pwidth = patch->width;
+#endif
+
+		// Vertically sheared sprite
+		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale, dc_texturemid -= vis->shear.tan)
+		{
+#ifdef RANGECHECK
+			texturecolumn = frac>>FRACBITS;
+			if (texturecolumn < 0 || texturecolumn >= pwidth)
+				I_Error("R_DrawSpriteRange: bad texturecolumn at %d from end", vis->x2 - dc_x);
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
+#else
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
+#endif
 
+			sprtopscreen = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 			localcolfunc (column);
 		}
 	}
 	else
 	{
 #ifdef RANGECHECK
-		pwidth = SHORT(patch->width);
+		pwidth = patch->width;
 #endif
 
 		// Non-paper drawing loop
@@ -929,9 +956,9 @@ static void R_DrawVisSprite(vissprite_t *vis)
 			texturecolumn = frac>>FRACBITS;
 			if (texturecolumn < 0 || texturecolumn >= pwidth)
 				I_Error("R_DrawSpriteRange: bad texturecolumn at %d from end", vis->x2 - dc_x);
-			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
 #else
-			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[frac>>FRACBITS]));
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
 #endif
 			localcolfunc (column);
 		}
@@ -993,12 +1020,12 @@ static void R_DrawPrecipitationVisSprite(vissprite_t *vis)
 #ifdef RANGECHECK
 		texturecolumn = frac>>FRACBITS;
 
-		if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
+		if (texturecolumn < 0 || texturecolumn >= patch->width)
 			I_Error("R_DrawPrecipitationSpriteRange: bad texturecolumn");
 
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
+		column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
 #else
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[frac>>FRACBITS]));
+		column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
 #endif
 		R_DrawMaskedColumn(column);
 	}
@@ -1210,6 +1237,29 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope)
 #undef CHECKZ
 }
 
+static void R_SkewShadowSprite(
+			mobj_t *thing, pslope_t *groundslope,
+			fixed_t groundz, INT32 spriteheight, fixed_t scalemul,
+			fixed_t *shadowyscale, fixed_t *shadowskew)
+{
+	// haha let's try some dumb stuff
+	fixed_t xslope, zslope;
+	angle_t sloperelang = (R_PointToAngle(thing->x, thing->y) - groundslope->xydirection) >> ANGLETOFINESHIFT;
+
+	xslope = FixedMul(FINESINE(sloperelang), groundslope->zdelta);
+	zslope = FixedMul(FINECOSINE(sloperelang), groundslope->zdelta);
+
+	//CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope);
+
+	if (viewz < groundz)
+		*shadowyscale += FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope);
+	else
+		*shadowyscale -= FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope);
+
+	*shadowyscale = abs((*shadowyscale));
+	*shadowskew = xslope;
+}
+
 static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, fixed_t tx, fixed_t tz)
 {
 	vissprite_t *shadow;
@@ -1233,45 +1283,27 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
 
-	patch = W_CachePatchName("DSHADOW", PU_CACHE);
+	patch = W_CachePatchName("DSHADOW", PU_SPRITE);
 	xscale = FixedDiv(projection, tz);
 	yscale = FixedDiv(projectiony, tz);
 	shadowxscale = FixedMul(thing->radius*2, scalemul);
 	shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz));
-	shadowyscale = min(shadowyscale, shadowxscale) / SHORT(patch->height);
-	shadowxscale /= SHORT(patch->width);
+	shadowyscale = min(shadowyscale, shadowxscale) / patch->height;
+	shadowxscale /= patch->width;
 	shadowskew = 0;
 
 	if (groundslope)
-	{
-		// haha let's try some dumb stuff
-		fixed_t xslope, zslope;
-		angle_t sloperelang = (R_PointToAngle(thing->x, thing->y) - groundslope->xydirection) >> ANGLETOFINESHIFT;
-
-		xslope = FixedMul(FINESINE(sloperelang), groundslope->zdelta);
-		zslope = FixedMul(FINECOSINE(sloperelang), groundslope->zdelta);
-
-		//CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope);
-
-		if (viewz < groundz)
-			shadowyscale += FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope);
-		else
-			shadowyscale -= FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope);
-
-		shadowyscale = abs(shadowyscale);
+		R_SkewShadowSprite(thing, groundslope, groundz, patch->height, scalemul, &shadowyscale, &shadowskew);
 
-		shadowskew = xslope;
-	}
-
-	tx -= SHORT(patch->width) * shadowxscale/2;
+	tx -= patch->width * shadowxscale/2;
 	x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS;
 	if (x1 >= viewwidth) return;
 
-	tx += SHORT(patch->width) * shadowxscale;
+	tx += patch->width * shadowxscale;
 	x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--;
 	if (x2 < 0 || x2 <= x1) return;
 
-	if (shadowyscale < FRACUNIT/SHORT(patch->height)) return; // fix some crashes?
+	if (shadowyscale < FRACUNIT/patch->height) return; // fix some crashes?
 
 	shadow = R_NewVisSprite();
 	shadow->patch = patch;
@@ -1286,8 +1318,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	shadow->dispoffset = vis->dispoffset - 5;
 	shadow->gx = thing->x;
 	shadow->gy = thing->y;
-	shadow->gzt = (isflipped ? shadow->pzt : shadow->pz) + SHORT(patch->height) * shadowyscale / 2;
-	shadow->gz = shadow->gzt - SHORT(patch->height) * shadowyscale;
+	shadow->gzt = (isflipped ? shadow->pzt : shadow->pz) + patch->height * shadowyscale / 2;
+	shadow->gz = shadow->gzt - patch->height * shadowyscale;
 	shadow->texturemid = FixedMul(thing->scale, FixedDiv(shadow->gzt - viewz, shadowyscale));
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		shadow->texturemid = FixedMul(shadow->texturemid, ((skin_t *)thing->skin)->highresscale);
@@ -1301,6 +1333,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	shadow->xscale = FixedMul(xscale, shadowxscale); //SoM: 4/17/2000
 	shadow->scale = FixedMul(yscale, shadowyscale);
+	shadow->thingscale = thing->scale;
 	shadow->sector = vis->sector;
 	shadow->szt = (INT16)((centeryfrac - FixedMul(shadow->gzt - viewz, yscale))>>FRACBITS);
 	shadow->sz = (INT16)((centeryfrac - FixedMul(shadow->gz - viewz, yscale))>>FRACBITS);
@@ -1308,7 +1341,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	shadow->startfrac = 0;
 	//shadow->xiscale = 0x7ffffff0 / (shadow->xscale/2);
-	shadow->xiscale = (SHORT(patch->width)<<FRACBITS)/(x2-x1+1); // fuck it
+	shadow->xiscale = (patch->width<<FRACBITS)/(x2-x1+1); // fuck it
 
 	if (shadow->x1 > x1)
 		shadow->startfrac += shadow->xiscale*(shadow->x1-x1);
@@ -1317,28 +1350,32 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	x1 += (x2-x1)/2;
 	shadow->shear.offset = shadow->x1-x1;
 
-	if (thing->subsector->sector->numlights)
+	if (thing->renderflags & RF_NOCOLORMAPS)
+		shadow->extra_colormap = NULL;
+	else
 	{
-		INT32 lightnum;
-		light = thing->subsector->sector->numlights - 1;
-
-		// R_GetPlaneLight won't work on sloped lights!
-		for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
-			fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], thing->x, thing->y);
-			if (h <= shadow->gzt) {
-				light = lightnum - 1;
-				break;
+		if (thing->subsector->sector->numlights)
+		{
+			INT32 lightnum;
+			light = thing->subsector->sector->numlights - 1;
+
+			// R_GetPlaneLight won't work on sloped lights!
+			for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
+				fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], thing->x, thing->y);
+				if (h <= shadow->gzt) {
+					light = lightnum - 1;
+					break;
+				}
 			}
 		}
-		//light = R_GetPlaneLight(thing->subsector->sector, shadow->gzt, false);
-	}
 
-	if (thing->subsector->sector->numlights)
-		shadow->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
-	else
-		shadow->extra_colormap = thing->subsector->sector->extra_colormap;
+		if (thing->subsector->sector->numlights)
+			shadow->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
+		else
+			shadow->extra_colormap = thing->subsector->sector->extra_colormap;
+	}
 
-	shadow->transmap = transtables + (trans<<FF_TRANSSHIFT);
+	shadow->transmap = R_GetTranslucencyTable(trans + 1);
 	shadow->colormap = scalelight[0][0]; // full dark!
 
 	objectsdrawn++;
@@ -1354,7 +1391,9 @@ static void R_ProjectSprite(mobj_t *thing)
 	mobj_t *oldthing = thing;
 	fixed_t tr_x, tr_y;
 	fixed_t tx, tz;
-	fixed_t xscale, yscale, sortscale; //added : 02-02-98 : aaargll..if I were a math-guy!!!
+	fixed_t xscale, yscale; //added : 02-02-98 : aaargll..if I were a math-guy!!!
+	fixed_t sortscale, sortsplat = 0;
+	fixed_t sort_x = 0, sort_y = 0, sort_z;
 
 	INT32 x1, x2;
 
@@ -1367,13 +1406,15 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	size_t frame, rot;
 	UINT16 flip;
-	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
+	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !R_ThingVerticallyFlipped(thing));
 	boolean mirrored = thing->mirrored;
-	boolean hflip = (!(thing->frame & FF_HORIZONTALFLIP) != !mirrored);
+	boolean hflip = (!R_ThingHorizontallyFlipped(thing) != !mirrored);
 
 	INT32 lindex;
+	INT32 trans;
 
 	vissprite_t *vis;
+	patch_t *patch;
 
 	spritecut_e cut = SC_NONE;
 
@@ -1382,22 +1423,29 @@ static void R_ProjectSprite(mobj_t *thing)
 	fixed_t scalestep;
 	fixed_t offset, offset2;
 
+	fixed_t sheartan = 0;
+	fixed_t shadowscale = FRACUNIT;
 	fixed_t basetx; // drop shadows
 
-	boolean papersprite = !!(thing->frame & FF_PAPERSPRITE);
-	fixed_t paperoffset = 0, paperdistance = 0; angle_t centerangle = 0;
+	boolean shadowdraw, shadoweffects, shadowskew;
+	boolean splat = R_ThingIsFloorSprite(thing);
+	boolean papersprite = (R_ThingIsPaperSprite(thing) && !splat);
+	fixed_t paperoffset = 0, paperdistance = 0;
+	angle_t centerangle = 0;
 
 	INT32 dispoffset = thing->info->dispoffset;
 
 	//SoM: 3/17/2000
-	fixed_t gz, gzt;
+	fixed_t gz = 0, gzt = 0;
 	INT32 heightsec, phs;
 	INT32 light = 0;
 	fixed_t this_scale = thing->scale;
+	fixed_t spritexscale, spriteyscale;
 
 	// rotsprite
 	fixed_t spr_width, spr_height;
 	fixed_t spr_offset, spr_topoffset;
+
 #ifdef ROTSPRITE
 	patch_t *rotsprite = NULL;
 	INT32 rollangle = 0;
@@ -1444,7 +1492,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			thing->frame = states[S_UNKNOWN].frame;
 			sprdef = &sprites[thing->sprite];
 #ifdef ROTSPRITE
-			sprinfo = NULL;
+			sprinfo = &spriteinfo[thing->sprite];
 #endif
 			frame = thing->frame&FF_FRAMEMASK;
 		}
@@ -1453,7 +1501,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	{
 		sprdef = &sprites[thing->sprite];
 #ifdef ROTSPRITE
-		sprinfo = NULL;
+		sprinfo = &spriteinfo[thing->sprite];
 #endif
 
 		if (frame >= sprdef->numframes)
@@ -1468,6 +1516,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			thing->sprite = states[S_UNKNOWN].sprite;
 			thing->frame = states[S_UNKNOWN].frame;
 			sprdef = &sprites[thing->sprite];
+			sprinfo = &spriteinfo[thing->sprite];
 			frame = thing->frame&FF_FRAMEMASK;
 		}
 	}
@@ -1525,19 +1574,28 @@ static void R_ProjectSprite(mobj_t *thing)
 	spr_offset = spritecachedinfo[lump].offset;
 	spr_topoffset = spritecachedinfo[lump].topoffset;
 
+	//Fab: lumppat is the lump number of the patch to use, this is different
+	//     than lumpid for sprites-in-pwad : the graphics are patched
+	patch = W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE);
+
 #ifdef ROTSPRITE
-	if (thing->rollangle)
+	if (thing->rollangle
+	&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)))
 	{
 		rollangle = R_GetRollAngle(thing->rollangle);
-		if (!(sprframe->rotsprite.cached & (1<<rot)))
-			R_CacheRotSprite(thing->sprite, frame, sprinfo, sprframe, rot, flip);
-		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
+		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
+
 		if (rotsprite != NULL)
 		{
-			spr_width = SHORT(rotsprite->width) << FRACBITS;
-			spr_height = SHORT(rotsprite->height) << FRACBITS;
-			spr_offset = SHORT(rotsprite->leftoffset) << FRACBITS;
-			spr_topoffset = SHORT(rotsprite->topoffset) << FRACBITS;
+			patch = rotsprite;
+			cut |= SC_ISROTATED;
+
+			spr_width = rotsprite->width << FRACBITS;
+			spr_height = rotsprite->height << FRACBITS;
+			spr_offset = rotsprite->leftoffset << FRACBITS;
+			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			spr_topoffset += FEETADJUST;
+
 			// flip -> rotate, not rotate -> flip
 			flip = 0;
 		}
@@ -1547,12 +1605,34 @@ static void R_ProjectSprite(mobj_t *thing)
 	flip = !flip != !hflip;
 
 	// calculate edges of the shape
+	spritexscale = thing->spritexscale;
+	spriteyscale = thing->spriteyscale;
+	if (spritexscale < 1 || spriteyscale < 1)
+		return;
+
+	if (thing->renderflags & RF_ABSOLUTEOFFSETS)
+	{
+		spr_offset = thing->spritexoffset;
+		spr_topoffset = thing->spriteyoffset;
+	}
+	else
+	{
+		SINT8 flipoffset = 1;
+
+		if ((thing->renderflags & RF_FLIPOFFSETS) && flip)
+			flipoffset = -1;
+
+		spr_offset += thing->spritexoffset * flipoffset;
+		spr_topoffset += thing->spriteyoffset * flipoffset;
+	}
+
 	if (flip)
 		offset = spr_offset - spr_width;
 	else
 		offset = -spr_offset;
-	offset = FixedMul(offset, this_scale);
-	offset2 = FixedMul(spr_width, this_scale);
+
+	offset = FixedMul(offset, FixedMul(spritexscale, this_scale));
+	offset2 = FixedMul(spr_width, FixedMul(spritexscale, this_scale));
 
 	if (papersprite)
 	{
@@ -1659,6 +1739,15 @@ static void R_ProjectSprite(mobj_t *thing)
 			return;
 	}
 
+	// Adjust the sort scale if needed
+	if (splat)
+	{
+		sort_z = (patch->height - patch->topoffset) * FRACUNIT;
+		ang = (viewangle >> ANGLETOFINESHIFT);
+		sort_x = FixedMul(FixedMul(FixedMul(spritexscale, this_scale), sort_z), FINECOSINE(ang));
+		sort_y = FixedMul(FixedMul(FixedMul(spriteyscale, this_scale), sort_z), FINESINE(ang));
+	}
+
 	if ((thing->flags2 & MF2_LINKDRAW) && thing->tracer) // toast 16/09/16 (SYMMETRY)
 	{
 		fixed_t linkscale;
@@ -1668,8 +1757,8 @@ static void R_ProjectSprite(mobj_t *thing)
 		if (! R_ThingVisible(thing))
 			return;
 
-		tr_x = thing->x - viewx;
-		tr_y = thing->y - viewy;
+		tr_x = (thing->x + sort_x) - viewx;
+		tr_y = (thing->y + sort_y) - viewy;
 		tz = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
 		linkscale = FixedDiv(projectiony, tz);
 
@@ -1680,7 +1769,23 @@ static void R_ProjectSprite(mobj_t *thing)
 			dispoffset *= -1; // if it's physically behind, make sure it's ordered behind (if dispoffset > 0)
 
 		sortscale = linkscale; // now make sure it's linked
-		cut = SC_LINKDRAW;
+		cut |= SC_LINKDRAW;
+	}
+	else if (splat)
+	{
+		tr_x = (thing->x + sort_x) - viewx;
+		tr_y = (thing->y + sort_y) - viewy;
+		sort_z = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+		sortscale = FixedDiv(projectiony, sort_z);
+	}
+
+	// Calculate the splat's sortscale
+	if (splat)
+	{
+		tr_x = (thing->x - sort_x) - viewx;
+		tr_y = (thing->y - sort_y) - viewy;
+		sort_z = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+		sortsplat = FixedDiv(projectiony, sort_z);
 	}
 
 	// PORTAL SPRITE CLIPPING
@@ -1693,19 +1798,93 @@ static void R_ProjectSprite(mobj_t *thing)
 			return;
 	}
 
-	//SoM: 3/17/2000: Disregard sprites that are out of view..
-	if (vflip)
+	// Determine the translucency value.
+	if (oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility)
+		trans = tr_trans80; // because now the translucency is set through FF_TRANSMASK
+	else if (oldthing->frame & FF_TRANSMASK)
 	{
-		// When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned.
-		// sprite height - sprite topoffset is the proper inverse of the vertical offset, of course.
-		// remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes!
-		gz = oldthing->z + oldthing->height - FixedMul(spr_topoffset, this_scale);
-		gzt = gz + FixedMul(spr_height, this_scale);
+		trans = (oldthing->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
+		if (oldthing->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+			return;
 	}
 	else
+		trans = 0;
+
+	// Check if this sprite needs to be rendered like a shadow
+	shadowdraw = (!!(thing->renderflags & RF_SHADOWDRAW) && !(papersprite || splat));
+	shadoweffects = (thing->renderflags & RF_SHADOWEFFECTS);
+	shadowskew = (shadowdraw && thing->standingslope);
+
+	if (shadowdraw || shadoweffects)
+	{
+		fixed_t groundz = R_GetShadowZ(thing, NULL);
+		boolean isflipped = (thing->eflags & MFE_VERTICALFLIP);
+
+		if (shadoweffects)
+		{
+			mobj_t *caster = thing->target;
+
+			if (caster && !P_MobjWasRemoved(caster))
+			{
+				fixed_t floordiff;
+
+				if (abs(groundz-viewz)/tz > 4)
+					return; // Prevent stretchy shadows and possible crashes
+
+				floordiff = abs((isflipped ? caster->height : 0) + caster->z - groundz);
+				trans += ((floordiff / (100*FRACUNIT)) + 3);
+				shadowscale = FixedMul(FRACUNIT - floordiff/640, caster->scale);
+			}
+			else
+				trans += 3;
+
+			if (trans >= NUMTRANSMAPS)
+				return;
+
+			trans--;
+		}
+
+		if (shadowdraw)
+		{
+			spritexscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spritexscale));
+			spriteyscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spriteyscale));
+			spriteyscale = FixedMul(spriteyscale, FixedDiv(abs(groundz - viewz), tz));
+			spriteyscale = min(spriteyscale, spritexscale) / patch->height;
+			spritexscale /= patch->width;
+		}
+		else
+		{
+			spritexscale = FixedMul(shadowscale, spritexscale);
+			spriteyscale = FixedMul(shadowscale, spriteyscale);
+		}
+
+		if (shadowskew)
+		{
+			R_SkewShadowSprite(thing, thing->standingslope, groundz, patch->height, shadowscale, &spriteyscale, &sheartan);
+
+			gzt = (isflipped ? (thing->z + thing->height) : thing->z) + patch->height * spriteyscale / 2;
+			gz = gzt - patch->height * spriteyscale;
+
+			cut |= SC_SHEAR;
+		}
+	}
+
+	if (!shadowskew)
 	{
-		gzt = oldthing->z + FixedMul(spr_topoffset, this_scale);
-		gz = gzt - FixedMul(spr_height, this_scale);
+		//SoM: 3/17/2000: Disregard sprites that are out of view..
+		if (vflip)
+		{
+			// When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned.
+			// sprite height - sprite topoffset is the proper inverse of the vertical offset, of course.
+			// remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes!
+			gz = oldthing->z + oldthing->height - FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale));
+			gzt = gz + FixedMul(spr_height, FixedMul(spriteyscale, this_scale));
+		}
+		else
+		{
+			gzt = oldthing->z + FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale));
+			gz = gzt - FixedMul(spr_height, FixedMul(spriteyscale, this_scale));
+		}
 	}
 
 	if (thing->subsector->sector->cullheight)
@@ -1717,12 +1896,13 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (thing->subsector->sector->numlights)
 	{
 		INT32 lightnum;
+		fixed_t top = (splat) ? gz : gzt;
 		light = thing->subsector->sector->numlights - 1;
 
 		// R_GetPlaneLight won't work on sloped lights!
 		for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
 			fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], thing->x, thing->y);
-			if (h <= gzt) {
+			if (h <= top) {
 				light = lightnum - 1;
 				break;
 			}
@@ -1758,10 +1938,12 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	// store information in a vissprite
 	vis = R_NewVisSprite();
+	vis->renderflags = thing->renderflags;
+	vis->rotateflags = sprframe->rotate;
 	vis->heightsec = heightsec; //SoM: 3/17/2000
 	vis->mobjflags = thing->flags;
-	vis->scale = yscale; //<<detailshift;
 	vis->sortscale = sortscale;
+	vis->sortsplat = sortsplat;
 	vis->dispoffset = dispoffset; // Monster Iestyn: 23/11/15
 	vis->gx = thing->x;
 	vis->gy = thing->y;
@@ -1770,12 +1952,12 @@ static void R_ProjectSprite(mobj_t *thing)
 	vis->thingheight = thing->height;
 	vis->pz = thing->z;
 	vis->pzt = vis->pz + vis->thingheight;
-	vis->texturemid = vis->gzt - viewz;
+	vis->texturemid = FixedDiv(gzt - viewz, spriteyscale);
 	vis->scalestep = scalestep;
 	vis->paperoffset = paperoffset;
 	vis->paperdistance = paperdistance;
 	vis->centerangle = centerangle;
-	vis->shear.tan = 0;
+	vis->shear.tan = sheartan;
 	vis->shear.offset = 0;
 
 	vis->mobj = thing; // Easy access! Tails 06-07-2002
@@ -1783,17 +1965,35 @@ static void R_ProjectSprite(mobj_t *thing)
 	vis->x1 = x1 < portalclipstart ? portalclipstart : x1;
 	vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2;
 
-	vis->xscale = xscale; //SoM: 4/17/2000
 	vis->sector = thing->subsector->sector;
 	vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, sortscale))>>FRACBITS);
 	vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, sortscale))>>FRACBITS);
 	vis->cut = cut;
+
 	if (thing->subsector->sector->numlights)
 		vis->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
 	else
 		vis->extra_colormap = thing->subsector->sector->extra_colormap;
 
-	iscale = FixedDiv(FRACUNIT, xscale);
+	vis->xscale = FixedMul(spritexscale, xscale); //SoM: 4/17/2000
+	vis->scale = FixedMul(spriteyscale, yscale); //<<detailshift;
+	vis->thingscale = oldthing->scale;
+
+	vis->spritexscale = spritexscale;
+	vis->spriteyscale = spriteyscale;
+	vis->spritexoffset = spr_offset;
+	vis->spriteyoffset = spr_topoffset;
+
+	if (shadowdraw || shadoweffects)
+	{
+		iscale = (patch->width<<FRACBITS)/(x2-x1+1); // fuck it
+		x1 += (x2-x1)/2; // reusing x1 variable
+		vis->shear.offset = vis->x1-x1;
+	}
+	else
+		iscale = FixedDiv(FRACUNIT, vis->xscale);
+
+	vis->shadowscale = shadowscale;
 
 	if (flip)
 	{
@@ -1808,41 +2008,31 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (vis->x1 > x1)
 	{
-		vis->startfrac += FixedDiv(vis->xiscale, this_scale)*(vis->x1-x1);
-		vis->scale += scalestep*(vis->x1 - x1);
+		vis->startfrac += FixedDiv(vis->xiscale, this_scale) * (vis->x1 - x1);
+		vis->scale += FixedMul(scalestep, spriteyscale) * (vis->x1 - x1);
 	}
 
-	//Fab: lumppat is the lump number of the patch to use, this is different
-	//     than lumpid for sprites-in-pwad : the graphics are patched
-#ifdef ROTSPRITE
-	if (rotsprite != NULL)
-		vis->patch = rotsprite;
+	if ((oldthing->blendmode != AST_COPY) && cv_translucency.value)
+		vis->transmap = R_GetBlendTable(oldthing->blendmode, trans);
 	else
-#endif
-		vis->patch = W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
-
-//
-// determine the colormap (lightlevel & special effects)
-//
-	vis->transmap = NULL;
-
-	// specific translucency
-	if (!cv_translucency.value)
-		; // no translucency
-	else if (oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility)
-		vis->transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT); // because now the translucency is set through FF_TRANSMASK
-	else if (oldthing->frame & FF_TRANSMASK)
-		vis->transmap = transtables + (oldthing->frame & FF_TRANSMASK) - 0x10000;
+		vis->transmap = NULL;
 
-	if (oldthing->frame & FF_FULLBRIGHT || oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW)
+	if (R_ThingIsFullBright(oldthing) || oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW)
 		vis->cut |= SC_FULLBRIGHT;
+	else if (R_ThingIsFullDark(oldthing))
+		vis->cut |= SC_FULLDARK;
 
+	//
+	// determine the colormap (lightlevel & special effects)
+	//
 	if (vis->cut & SC_FULLBRIGHT
 		&& (!vis->extra_colormap || !(vis->extra_colormap->flags & CMF_FADEFULLBRIGHTSPRITES)))
 	{
 		// full bright: goggles
 		vis->colormap = colormaps;
 	}
+	else if (vis->cut & SC_FULLDARK)
+		vis->colormap = scalelight[0][0];
 	else
 	{
 		// diminished light
@@ -1856,8 +2046,12 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (vflip)
 		vis->cut |= SC_VFLIP;
+	if (splat)
+		vis->cut |= SC_SPLAT; // I like ya cut g
 
-	if (thing->subsector->sector->numlights)
+	vis->patch = patch;
+
+	if (thing->subsector->sector->numlights && !(shadowdraw || splat))
 		R_SplitSprite(vis);
 
 	if (oldthing->shadowscale && cv_shadow.value)
@@ -2002,7 +2196,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 
 	//Fab: lumppat is the lump number of the patch to use, this is different
 	//     than lumpid for sprites-in-pwad : the graphics are patched
-	vis->patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+	vis->patch = W_CachePatchNum(sprframe->lumppat[0], PU_SPRITE);
 
 	// specific translucency
 	if (thing->frame & FF_TRANSMASK)
@@ -2464,13 +2658,45 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 			}
 			else if (r2->sprite)
 			{
-				if (r2->sprite->x1 > rover->x2 || r2->sprite->x2 < rover->x1)
-					continue;
-				if (r2->sprite->szt > rover->sz || r2->sprite->sz < rover->szt)
-					continue;
+				boolean infront = (r2->sprite->sortscale > rover->sortscale
+								|| (r2->sprite->sortscale == rover->sortscale && r2->sprite->dispoffset > rover->dispoffset));
 
-				if (r2->sprite->sortscale > rover->sortscale
-				 || (r2->sprite->sortscale == rover->sortscale && r2->sprite->dispoffset > rover->dispoffset))
+				if (rover->cut & SC_SPLAT || r2->sprite->cut & SC_SPLAT)
+				{
+					fixed_t scale1 = (rover->cut & SC_SPLAT ? rover->sortsplat : rover->sortscale);
+					fixed_t scale2 = (r2->sprite->cut & SC_SPLAT ? r2->sprite->sortsplat : r2->sprite->sortscale);
+					boolean behind = (scale2 > scale1 || (scale2 == scale1 && r2->sprite->dispoffset > rover->dispoffset));
+
+					if (!behind)
+					{
+						fixed_t z1 = 0, z2 = 0;
+
+						if (rover->mobj->z - viewz > 0)
+						{
+							z1 = rover->pz;
+							z2 = r2->sprite->pz;
+						}
+						else
+						{
+							z1 = r2->sprite->pz;
+							z2 = rover->pz;
+						}
+
+						z1 -= viewz;
+						z2 -= viewz;
+
+						infront = (z1 >= z2);
+					}
+				}
+				else
+				{
+					if (r2->sprite->x1 > rover->x2 || r2->sprite->x2 < rover->x1)
+						continue;
+					if (r2->sprite->szt > rover->sz || r2->sprite->sz < rover->szt)
+						continue;
+				}
+
+				if (infront)
 				{
 					entry = R_CreateDrawNode(NULL);
 					(entry->prev = r2->prev)->next = entry;
@@ -2515,7 +2741,7 @@ static drawnode_t *R_CreateDrawNode(drawnode_t *link)
 	node->ffloor = NULL;
 	node->sprite = NULL;
 
-	rs_numdrawnodes++;
+	ps_numdrawnodes++;
 	return node;
 }
 
@@ -2556,7 +2782,11 @@ static void R_DrawSprite(vissprite_t *spr)
 {
 	mfloorclip = spr->clipbot;
 	mceilingclip = spr->cliptop;
-	R_DrawVisSprite(spr);
+
+	if (spr->cut & SC_SPLAT)
+		R_DrawFloorSprite(spr);
+	else
+		R_DrawVisSprite(spr);
 }
 
 // Special drawer for precipitation sprites Tails 08-18-2002
@@ -2567,214 +2797,219 @@ static void R_DrawPrecipitationSprite(vissprite_t *spr)
 	R_DrawPrecipitationVisSprite(spr);
 }
 
-// R_ClipSprites
+// R_ClipVisSprite
 // Clips vissprites without drawing, so that portals can work. -Red
-void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
+void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal)
 {
-	vissprite_t *spr;
-	for (; clippedvissprites < visspritecount; clippedvissprites++)
-	{
-		drawseg_t *ds;
-		INT32		x;
-		INT32		r1;
-		INT32		r2;
-		fixed_t		scale;
-		fixed_t		lowscale;
-		INT32		silhouette;
-
-		spr = R_GetVisSprite(clippedvissprites);
-
-		for (x = spr->x1; x <= spr->x2; x++)
-			spr->clipbot[x] = spr->cliptop[x] = -2;
-
-		// Scan drawsegs from end to start for obscuring segs.
-		// The first drawseg that has a greater scale
-		//  is the clip seg.
-		//SoM: 4/8/2000:
-		// Pointer check was originally nonportable
-		// and buggy, by going past LEFT end of array:
+	drawseg_t *ds;
+	INT32		x;
+	INT32		r1;
+	INT32		r2;
+	fixed_t		scale;
+	fixed_t		lowscale;
+	INT32		silhouette;
+
+	for (x = x1; x <= x2; x++)
+		spr->clipbot[x] = spr->cliptop[x] = -2;
+
+	// Scan drawsegs from end to start for obscuring segs.
+	// The first drawseg that has a greater scale
+	//  is the clip seg.
+	//SoM: 4/8/2000:
+	// Pointer check was originally nonportable
+	// and buggy, by going past LEFT end of array:
+
+	//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
+	for (ds = ds_p; ds-- > dsstart;)
+	{
+		// determine if the drawseg obscures the sprite
+		if (ds->x1 > x2 ||
+			ds->x2 < x1 ||
+			(!ds->silhouette
+			 && !ds->maskedtexturecol))
+		{
+			// does not cover sprite
+			continue;
+		}
 
-		//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
-		for (ds = ds_p; ds-- > dsstart;)
+		if (ds->portalpass != 66)
 		{
-			// determine if the drawseg obscures the sprite
-			if (ds->x1 > spr->x2 ||
-			    ds->x2 < spr->x1 ||
-			    (!ds->silhouette
-			     && !ds->maskedtexturecol))
+			if (ds->portalpass > 0 && ds->portalpass <= portalrender)
+				continue; // is a portal
+
+			if (ds->scale1 > ds->scale2)
 			{
-				// does not cover sprite
-				continue;
+				lowscale = ds->scale2;
+				scale = ds->scale1;
 			}
-
-			if (ds->portalpass != 66)
+			else
 			{
-				if (ds->portalpass > 0 && ds->portalpass <= portalrender)
-					continue; // is a portal
-
-				if (ds->scale1 > ds->scale2)
-				{
-					lowscale = ds->scale2;
-					scale = ds->scale1;
-				}
-				else
-				{
-					lowscale = ds->scale1;
-					scale = ds->scale2;
-				}
+				lowscale = ds->scale1;
+				scale = ds->scale2;
+			}
 
-				if (scale < spr->sortscale ||
-					(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;
-				}
+			if (scale < spr->sortscale ||
+				(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;
 			}
+		}
 
-			r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
-			r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;
+		r1 = ds->x1 < x1 ? x1 : ds->x1;
+		r2 = ds->x2 > x2 ? x2 : ds->x2;
 
-			// clip this piece of the sprite
-			silhouette = ds->silhouette;
+		// clip this piece of the sprite
+		silhouette = ds->silhouette;
 
-			if (spr->gz >= ds->bsilheight)
-				silhouette &= ~SIL_BOTTOM;
+		if (spr->gz >= ds->bsilheight)
+			silhouette &= ~SIL_BOTTOM;
 
-			if (spr->gzt <= ds->tsilheight)
-				silhouette &= ~SIL_TOP;
+		if (spr->gzt <= ds->tsilheight)
+			silhouette &= ~SIL_TOP;
 
-			if (silhouette == SIL_BOTTOM)
+		if (silhouette == SIL_BOTTOM)
+		{
+			// bottom sil
+			for (x = r1; x <= r2; x++)
+				if (spr->clipbot[x] == -2)
+					spr->clipbot[x] = ds->sprbottomclip[x];
+		}
+		else if (silhouette == SIL_TOP)
+		{
+			// top sil
+			for (x = r1; x <= r2; x++)
+				if (spr->cliptop[x] == -2)
+					spr->cliptop[x] = ds->sprtopclip[x];
+		}
+		else if (silhouette == (SIL_TOP|SIL_BOTTOM))
+		{
+			// both
+			for (x = r1; x <= r2; x++)
 			{
-				// bottom sil
-				for (x = r1; x <= r2; x++)
-					if (spr->clipbot[x] == -2)
-						spr->clipbot[x] = ds->sprbottomclip[x];
+				if (spr->clipbot[x] == -2)
+					spr->clipbot[x] = ds->sprbottomclip[x];
+				if (spr->cliptop[x] == -2)
+					spr->cliptop[x] = ds->sprtopclip[x];
 			}
-			else if (silhouette == SIL_TOP)
-			{
-				// top sil
-				for (x = r1; x <= r2; x++)
-					if (spr->cliptop[x] == -2)
-						spr->cliptop[x] = ds->sprtopclip[x];
+		}
+	}
+	//SoM: 3/17/2000: Clip sprites in water.
+	if (spr->heightsec != -1)  // only things in specially marked sectors
+	{
+		fixed_t mh, h;
+		INT32 phs = viewplayer->mo->subsector->sector->heightsec;
+		if ((mh = viewworld->sectors[spr->heightsec].floorheight) > spr->gz &&
+			(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
+			(h >>= FRACBITS) < viewheight)
+		{
+			if (mh <= 0 || (phs != -1 && viewz > viewworld->sectors[phs].floorheight))
+			{                          // clip bottom
+				for (x = x1; x <= x2; x++)
+					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+						spr->clipbot[x] = (INT16)h;
 			}
-			else if (silhouette == (SIL_TOP|SIL_BOTTOM))
+			else						// clip top
 			{
-				// both
-				for (x = r1; x <= r2; x++)
-				{
-					if (spr->clipbot[x] == -2)
-						spr->clipbot[x] = ds->sprbottomclip[x];
-					if (spr->cliptop[x] == -2)
-						spr->cliptop[x] = ds->sprtopclip[x];
-				}
+				for (x = x1; x <= x2; x++)
+					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+						spr->cliptop[x] = (INT16)h;
 			}
 		}
-		//SoM: 3/17/2000: Clip sprites in water.
-		if (spr->heightsec != -1)  // only things in specially marked sectors
+
+		if ((mh = viewworld->sectors[spr->heightsec].ceilingheight) < spr->gzt &&
+			(h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
+			(h >>= FRACBITS) < viewheight)
 		{
-			fixed_t mh, h;
-			INT32 phs = viewplayer->mo->subsector->sector->heightsec;
-			if ((mh = viewworld->sectors[spr->heightsec].floorheight) > spr->gz &&
-				(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
-				(h >>= FRACBITS) < viewheight)
-			{
-				if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
-				{                          // clip bottom
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-							spr->clipbot[x] = (INT16)h;
-				}
-				else						// clip top
-				{
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-							spr->cliptop[x] = (INT16)h;
-				}
+			if (phs != -1 && viewz >= viewworld->sectors[phs].ceilingheight)
+			{                         // clip bottom
+				for (x = x1; x <= x2; x++)
+					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+						spr->clipbot[x] = (INT16)h;
 			}
-
-			if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
-			    (h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
-			    (h >>= FRACBITS) < viewheight)
+			else                       // clip top
 			{
-				if (phs != -1 && viewz >= sectors[phs].ceilingheight)
-				{                         // clip bottom
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-							spr->clipbot[x] = (INT16)h;
-				}
-				else                       // clip top
-				{
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-							spr->cliptop[x] = (INT16)h;
-				}
+				for (x = x1; x <= x2; x++)
+					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+						spr->cliptop[x] = (INT16)h;
 			}
 		}
-		if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
+	}
+	if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
-					spr->cliptop[x] = spr->szt;
+			if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+				spr->cliptop[x] = spr->szt;
 
-				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
-					spr->clipbot[x] = spr->sz;
-			}
+			if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+				spr->clipbot[x] = spr->sz;
 		}
-		else if (spr->cut & SC_TOP)
+	}
+	else if (spr->cut & SC_TOP)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
-					spr->cliptop[x] = spr->szt;
-			}
+			if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+				spr->cliptop[x] = spr->szt;
 		}
-		else if (spr->cut & SC_BOTTOM)
+	}
+	else if (spr->cut & SC_BOTTOM)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
-					spr->clipbot[x] = spr->sz;
-			}
+			if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+				spr->clipbot[x] = spr->sz;
 		}
+	}
 
-		// all clipping has been performed, so store the values - what, did you think we were drawing them NOW?
+	// all clipping has been performed, so store the values - what, did you think we were drawing them NOW?
 
-		// check for unclipped columns
-		for (x = spr->x1; x <= spr->x2; x++)
-		{
-			if (spr->clipbot[x] == -2)
-				spr->clipbot[x] = (INT16)viewheight;
+	// check for unclipped columns
+	for (x = x1; x <= x2; x++)
+	{
+		if (spr->clipbot[x] == -2)
+			spr->clipbot[x] = (INT16)viewheight;
 
-			if (spr->cliptop[x] == -2)
-				//Fab : 26-04-98: was -1, now clips against console bottom
-				spr->cliptop[x] = (INT16)con_clipviewtop;
-		}
+		if (spr->cliptop[x] == -2)
+			//Fab : 26-04-98: was -1, now clips against console bottom
+			spr->cliptop[x] = (INT16)con_clipviewtop;
+	}
 
-		if (portal)
+	if (portal)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->clipbot[x] > portal->floorclip[x - portal->start])
-					spr->clipbot[x] = portal->floorclip[x - portal->start];
-				if (spr->cliptop[x] < portal->ceilingclip[x - portal->start])
-					spr->cliptop[x] = portal->ceilingclip[x - portal->start];
-			}
+			if (spr->clipbot[x] > portal->floorclip[x - portal->start])
+				spr->clipbot[x] = portal->floorclip[x - portal->start];
+			if (spr->cliptop[x] < portal->ceilingclip[x - portal->start])
+				spr->cliptop[x] = portal->ceilingclip[x - portal->start];
 		}
 	}
 }
 
+void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
+{
+	for (; clippedvissprites < visspritecount; clippedvissprites++)
+	{
+		vissprite_t *spr = R_GetVisSprite(clippedvissprites);
+		INT32 x1 = (spr->cut & SC_SPLAT) ? 0 : spr->x1;
+		INT32 x2 = (spr->cut & SC_SPLAT) ? viewwidth : spr->x2;
+		R_ClipVisSprite(spr, x1, x2, dsstart, portal);
+	}
+}
+
 /* Check if thing may be drawn from our current view. */
 boolean R_ThingVisible (mobj_t *thing)
 {
 	return (!(
 				thing->sprite == SPR_NULL ||
 				( thing->flags2 & (MF2_DONTDRAW) ) ||
-				thing == r_viewmobj
+				(r_viewmobj && (thing == r_viewmobj || (r_viewmobj->player && r_viewmobj->player->followmobj == thing)))
 	));
 }
 
@@ -2817,6 +3052,36 @@ boolean R_PrecipThingVisible (precipmobj_t *precipthing,
 	return ( approx_dist <= limit_dist );
 }
 
+boolean R_ThingHorizontallyFlipped(mobj_t *thing)
+{
+	return (thing->frame & FF_HORIZONTALFLIP || thing->renderflags & RF_HORIZONTALFLIP);
+}
+
+boolean R_ThingVerticallyFlipped(mobj_t *thing)
+{
+	return (thing->frame & FF_VERTICALFLIP || thing->renderflags & RF_VERTICALFLIP);
+}
+
+boolean R_ThingIsPaperSprite(mobj_t *thing)
+{
+	return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE);
+}
+
+boolean R_ThingIsFloorSprite(mobj_t *thing)
+{
+	return (thing->flags2 & MF2_SPLAT || thing->renderflags & RF_FLOORSPRITE);
+}
+
+boolean R_ThingIsFullBright(mobj_t *thing)
+{
+	return (thing->frame & FF_FULLBRIGHT || thing->renderflags & RF_FULLBRIGHT);
+}
+
+boolean R_ThingIsFullDark(mobj_t *thing)
+{
+	return (thing->renderflags & RF_FULLDARK);
+}
+
 //
 // R_DrawMasked
 //
diff --git a/src/r_things.h b/src/r_things.h
index 7a0fe3a60ee557bc358e95e75c49dc5f53d6ef3f..708b6c24cd1f287d64368c91754f77a1703a42c4 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -16,6 +16,7 @@
 
 #include "r_plane.h"
 #include "r_patch.h"
+#include "r_picformats.h"
 #include "r_portal.h"
 #include "r_defs.h"
 #include "r_skins.h"
@@ -64,7 +65,6 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope);
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
 void R_ClearSprites(void);
-void R_ClipSprites(drawseg_t* dsstart, portal_t* portal);
 
 boolean R_ThingVisible (mobj_t *thing);
 
@@ -75,6 +75,15 @@ boolean R_ThingVisibleWithinDist (mobj_t *thing,
 boolean R_PrecipThingVisible (precipmobj_t *precipthing,
 		fixed_t precip_draw_dist);
 
+boolean R_ThingHorizontallyFlipped (mobj_t *thing);
+boolean R_ThingVerticallyFlipped (mobj_t *thing);
+
+boolean R_ThingIsPaperSprite (mobj_t *thing);
+boolean R_ThingIsFloorSprite (mobj_t *thing);
+
+boolean R_ThingIsFullBright (mobj_t *thing);
+boolean R_ThingIsFullDark (mobj_t *thing);
+
 // --------------
 // MASKED DRAWING
 // --------------
@@ -107,19 +116,23 @@ void R_DrawMasked(maskcount_t* masks, UINT8 nummasks);
 typedef enum
 {
 	// actual cuts
-	SC_NONE = 0,
-	SC_TOP = 1,
-	SC_BOTTOM = 1<<1,
+	SC_NONE       = 0,
+	SC_TOP        = 1,
+	SC_BOTTOM     = 1<<1,
 	// other flags
-	SC_PRECIP = 1<<2,
-	SC_LINKDRAW = 1<<3,
+	SC_PRECIP     = 1<<2,
+	SC_LINKDRAW   = 1<<3,
 	SC_FULLBRIGHT = 1<<4,
-	SC_VFLIP = 1<<5,
-	SC_ISSCALED = 1<<6,
-	SC_SHADOW = 1<<7,
+	SC_FULLDARK   = 1<<5,
+	SC_VFLIP      = 1<<6,
+	SC_ISSCALED   = 1<<7,
+	SC_ISROTATED  = 1<<8,
+	SC_SHADOW     = 1<<9,
+	SC_SHEAR      = 1<<10,
+	SC_SPLAT      = 1<<11,
 	// masks
-	SC_CUTMASK = SC_TOP|SC_BOTTOM,
-	SC_FLAGMASK = ~SC_CUTMASK
+	SC_CUTMASK    = SC_TOP|SC_BOTTOM,
+	SC_FLAGMASK   = ~SC_CUTMASK
 } spritecut_e;
 
 // A vissprite_t is a thing that will be drawn during a refresh,
@@ -142,7 +155,10 @@ typedef struct vissprite_s
 	fixed_t pz, pzt; // physical bottom/top for sorting with 3D floors
 
 	fixed_t startfrac; // horizontal position of x1
-	fixed_t scale, sortscale; // sortscale only differs from scale for paper sprites and MF2_LINKDRAW
+	fixed_t xscale, scale; // projected horizontal and vertical scales
+	fixed_t thingscale; // the object's scale
+	fixed_t sortscale; // sortscale only differs from scale for paper sprites, floor sprites, and MF2_LINKDRAW
+	fixed_t sortsplat; // the sortscale from behind the floor sprite
 	fixed_t scalestep; // only for paper sprites, 0 otherwise
 	fixed_t paperoffset, paperdistance; // for paper sprites, offset/dist relative to the angle
 	fixed_t xiscale; // negative if flipped
@@ -168,14 +184,19 @@ typedef struct vissprite_s
 
 	extracolormap_t *extra_colormap; // global colormaps
 
-	fixed_t xscale;
-
 	// Precalculated top and bottom screen coords for the sprite.
 	fixed_t thingheight; // The actual height of the thing (for 3D floors)
 	sector_t *sector; // The sector containing the thing.
 	INT16 sz, szt;
 
 	spritecut_e cut;
+	UINT32 renderflags;
+	UINT8 rotateflags;
+
+	fixed_t spritexscale, spriteyscale;
+	fixed_t spritexoffset, spriteyoffset;
+
+	fixed_t shadowscale;
 
 	INT16 clipbot[MAXVIDWIDTH], cliptop[MAXVIDWIDTH];
 
@@ -184,6 +205,12 @@ typedef struct vissprite_s
 
 extern UINT32 visspritecount;
 
+void R_ClipSprites(drawseg_t* dsstart, portal_t* portal);
+void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal);
+
+boolean R_SpriteIsFlashing(vissprite_t *vis);
+UINT8 *R_GetSpriteTranslation(vissprite_t *vis);
+
 // ----------
 // DRAW NODES
 // ----------
diff --git a/src/s_sound.c b/src/s_sound.c
index cf1af4a6caec83e99737e8d1295f92f5160ea322..1a230c7b3b0aa86e993f551ef0f04d3114c3b8ac 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -11,16 +11,6 @@
 /// \file  s_sound.c
 /// \brief System-independent sound and music routines
 
-#ifdef MUSSERV
-#include <sys/msg.h>
-struct musmsg
-{
-	long msg_type;
-	char msg_text[12];
-};
-extern INT32 msg_id;
-#endif
-
 #include "doomdef.h"
 #include "doomstat.h"
 #include "command.h"
@@ -39,10 +29,7 @@ extern INT32 msg_id;
 #include "fastcmp.h"
 #include "m_misc.h" // for tunes command
 #include "m_cond.h" // for conditionsets
-
-#ifdef HAVE_LUA_MUSICPLUS
 #include "lua_hook.h" // MusicChange hook
-#endif
 
 #ifdef HW3SOUND
 // 3D Sound Interface
@@ -60,6 +47,7 @@ static void Command_RestartAudio_f(void);
 static void GameMIDIMusic_OnChange(void);
 static void GameSounds_OnChange(void);
 static void GameDigiMusic_OnChange(void);
+static void MusicPref_OnChange(void);
 
 #ifdef HAVE_OPENMPT
 static void ModFilter_OnChange(void);
@@ -69,36 +57,26 @@ static lumpnum_t S_GetMusicLumpNum(const char *mname);
 
 static boolean S_CheckQueue(void);
 
-// commands for music and sound servers
-#ifdef MUSSERV
-consvar_t musserver_cmd = {"musserver_cmd", "musserver", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t musserver_arg = {"musserver_arg", "-t 20 -f -u 0 -i music.dta", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-#endif
-#ifdef SNDSERV
-consvar_t sndserver_cmd = {"sndserver_cmd", "llsndserv", CV_SAVE, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t sndserver_arg = {"sndserver_arg", "-quiet", CV_SAVE, NULL, 0, NULL, NULL, 0, 0, NULL};
-#endif
-
 #if defined (_WINDOWS) && !defined (SURROUND) //&& defined (_X86_)
 #define SURROUND
 #endif
 
 #ifdef _WINDOWS
-consvar_t cv_samplerate = {"samplerate", "44100", 0, CV_Unsigned, NULL, 44100, NULL, NULL, 0, 0, NULL}; //Alam: For easy hacking?
+consvar_t cv_samplerate = CVAR_INIT ("samplerate", "44100", 0, CV_Unsigned, NULL); //Alam: For easy hacking?
 #else
-consvar_t cv_samplerate = {"samplerate", "22050", 0, CV_Unsigned, NULL, 22050, NULL, NULL, 0, 0, NULL}; //Alam: For easy hacking?
+consvar_t cv_samplerate = CVAR_INIT ("samplerate", "22050", 0, CV_Unsigned, NULL); //Alam: For easy hacking?
 #endif
 
 // stereo reverse
-consvar_t stereoreverse = {"stereoreverse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t stereoreverse = CVAR_INIT ("stereoreverse", "Off", CV_SAVE, CV_OnOff, NULL);
 
 // if true, all sounds are loaded at game startup
-static consvar_t precachesound = {"precachesound", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t precachesound = CVAR_INIT ("precachesound", "Off", CV_SAVE, CV_OnOff, NULL);
 
 // actual general (maximum) sound & music volume, saved into the config
-consvar_t cv_soundvolume = {"soundvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_digmusicvolume = {"digmusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_midimusicvolume = {"midimusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_soundvolume = CVAR_INIT ("soundvolume", "18", CV_SAVE, soundvolume_cons_t, NULL);
+consvar_t cv_digmusicvolume = CVAR_INIT ("digmusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL);
+consvar_t cv_midimusicvolume = CVAR_INIT ("midimusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL);
 
 static void Captioning_OnChange(void)
 {
@@ -107,36 +85,44 @@ static void Captioning_OnChange(void)
 		S_StartSound(NULL, sfx_menu1);
 }
 
-consvar_t cv_closedcaptioning = {"closedcaptioning", "Off", CV_SAVE|CV_CALL, CV_OnOff, Captioning_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_closedcaptioning = CVAR_INIT ("closedcaptioning", "Off", CV_SAVE|CV_CALL, CV_OnOff, Captioning_OnChange);
 
 // number of channels available
-consvar_t cv_numChannels = {"snd_channels", "32", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_numChannels = CVAR_INIT ("snd_channels", "32", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum);
 
-static consvar_t surround = {"surround", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t surround = CVAR_INIT ("surround", "Off", CV_SAVE, CV_OnOff, NULL);
 
-consvar_t cv_resetmusic = {"resetmusic", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_resetmusicbyheader = {"resetmusicbyheader", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_resetmusic = CVAR_INIT ("resetmusic", "Off", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_resetmusicbyheader = CVAR_INIT ("resetmusicbyheader", "Yes", CV_SAVE, CV_YesNo, NULL);
 
 static CV_PossibleValue_t cons_1upsound_t[] = {
 	{0, "Jingle"},
 	{1, "Sound"},
 	{0, NULL}
 };
-consvar_t cv_1upsound = {"1upsound", "Jingle", CV_SAVE, cons_1upsound_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_1upsound = CVAR_INIT ("1upsound", "Jingle", CV_SAVE, cons_1upsound_t, NULL);
 
 // Sound system toggles, saved into the config
-consvar_t cv_gamedigimusic = {"digimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameDigiMusic_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_gamemidimusic = {"midimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameMIDIMusic_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_gamesounds = {"sounds", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameSounds_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_gamedigimusic = CVAR_INIT ("digimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameDigiMusic_OnChange);
+consvar_t cv_gamemidimusic = CVAR_INIT ("midimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameMIDIMusic_OnChange);
+consvar_t cv_gamesounds = CVAR_INIT ("sounds", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, GameSounds_OnChange);
+
+// Music preference
+static CV_PossibleValue_t cons_musicpref_t[] = {
+	{0, "Digital"},
+	{1, "MIDI"},
+	{0, NULL}
+};
+consvar_t cv_musicpref = CVAR_INIT ("musicpref", "Digital", CV_SAVE|CV_CALL|CV_NOINIT, cons_musicpref_t, MusicPref_OnChange);
 
 // Window focus sound sytem toggles
-consvar_t cv_playmusicifunfocused = {"playmusicifunfocused", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_playsoundsifunfocused = {"playsoundsifunfocused", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_playmusicifunfocused = CVAR_INIT ("playmusicifunfocused", "No", CV_SAVE, CV_YesNo, NULL);
+consvar_t cv_playsoundsifunfocused = CVAR_INIT ("playsoundsifunfocused", "No", CV_SAVE, CV_YesNo, NULL);
 
 #ifdef HAVE_OPENMPT
 openmpt_module *openmpt_mhandle = NULL;
 static CV_PossibleValue_t interpolationfilter_cons_t[] = {{0, "Default"}, {1, "None"}, {2, "Linear"}, {4, "Cubic"}, {8, "Windowed sinc"}, {0, NULL}};
-consvar_t cv_modfilter = {"modfilter", "0", CV_SAVE|CV_CALL, interpolationfilter_cons_t, ModFilter_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_modfilter = CVAR_INIT ("modfilter", "0", CV_SAVE|CV_CALL, interpolationfilter_cons_t, ModFilter_OnChange);
 #endif
 
 #define S_MAX_VOLUME 127
@@ -283,14 +269,6 @@ void S_RegisterSoundStuff(void)
 	CV_RegisterVar(&stereoreverse);
 	CV_RegisterVar(&precachesound);
 
-#ifdef SNDSERV
-	CV_RegisterVar(&sndserver_cmd);
-	CV_RegisterVar(&sndserver_arg);
-#endif
-#ifdef MUSSERV
-	CV_RegisterVar(&musserver_cmd);
-	CV_RegisterVar(&musserver_arg);
-#endif
 	CV_RegisterVar(&surround);
 	CV_RegisterVar(&cv_samplerate);
 	CV_RegisterVar(&cv_resetmusic);
@@ -301,6 +279,7 @@ void S_RegisterSoundStuff(void)
 	CV_RegisterVar(&cv_gamesounds);
 	CV_RegisterVar(&cv_gamedigimusic);
 	CV_RegisterVar(&cv_gamemidimusic);
+	CV_RegisterVar(&cv_musicpref);
 #ifdef HAVE_OPENMPT
 	CV_RegisterVar(&cv_modfilter);
 #endif
@@ -312,27 +291,6 @@ void S_RegisterSoundStuff(void)
 
 	COM_AddCommand("tunes", Command_Tunes_f);
 	COM_AddCommand("restartaudio", Command_RestartAudio_f);
-
-#if defined (macintosh) && !defined (HAVE_SDL) // mp3 playlist stuff
-	{
-		INT32 i;
-		for (i = 0; i < PLAYLIST_LENGTH; i++)
-		{
-			user_songs[i].name = malloc(7);
-			if (!user_songs[i].name)
-				I_Error("No more free memory for mp3 playlist");
-			sprintf(user_songs[i].name, "song%d%d",i/10,i%10);
-			user_songs[i].defaultvalue = malloc(sizeof (char));
-			if (user_songs[i].defaultvalue)
-				I_Error("No more free memory for blank mp3 playerlist");
-			*user_songs[i].defaultvalue = 0;
-			user_songs[i].flags = CV_SAVE;
-			user_songs[i].PossibleValue = NULL;
-			CV_RegisterVar(&user_songs[i]);
-		}
-		CV_RegisterVar(&play_mode);
-	}
-#endif
 }
 
 static void SetChannelsNum(void)
@@ -560,6 +518,7 @@ void S_StartCaption(sfxenum_t sfx_id, INT32 cnum, UINT16 lifespan)
 
 void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 {
+	const INT32 initial_volume = volume;
 	INT32 sep, pitch, priority, cnum;
 	const sfxenum_t actual_id = sfx_id;
 	sfxinfo_t *sfx;
@@ -757,6 +716,7 @@ dontplay:
 
 	// Assigns the handle to one of the channels in the
 	// mix/output buffer.
+	channels[cnum].volume = initial_volume;
 	channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum);
 }
 
@@ -968,7 +928,7 @@ void S_UpdateSounds(void)
 			if (I_SoundIsPlaying(c->handle))
 			{
 				// initialize parameters
-				volume = 255; // 8 bits internal volume precision
+				volume = c->volume; // 8 bits internal volume precision
 				pitch = NORM_PITCH;
 				sep = NORM_SEP;
 
@@ -1082,7 +1042,6 @@ void S_ClearSfx(void)
 
 static void S_StopChannel(INT32 cnum)
 {
-	INT32 i;
 	channel_t *c = &channels[cnum];
 
 	if (c->sfxinfo)
@@ -1091,17 +1050,12 @@ static void S_StopChannel(INT32 cnum)
 		if (I_SoundIsPlaying(c->handle))
 			I_StopSound(c->handle);
 
-		// check to see
-		//  if other channels are playing the sound
-		for (i = 0; i < numofchannels; i++)
-			if (cnum != i && c->sfxinfo == channels[i].sfxinfo)
-				break;
-
 		// degrade usefulness of sound data
 		c->sfxinfo->usefulness--;
-
 		c->sfxinfo = 0;
 	}
+
+	c->origin = NULL;
 }
 
 //
@@ -1243,15 +1197,12 @@ INT32 S_AdjustSoundParams(const mobj_t *listener, const mobj_t *source, INT32 *v
 	}
 
 	// volume calculation
-	if (approx_dist < S_CLOSE_DIST)
-	{
-		// SfxVolume is now hardware volume
-		*vol = 255; // not snd_SfxVolume
-	}
-	else
+	/* not sure if it should be > (no =), but this matches the old behavior */
+	if (approx_dist >= S_CLOSE_DIST)
 	{
 		// distance effect
-		*vol = (15 * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS)) / S_ATTENUATOR;
+		INT32 n = (15 * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS));
+		*vol = FixedMul(*vol * FRACUNIT / 255, n) / S_ATTENUATOR;
 	}
 
 	return (*vol > 0);
@@ -1847,19 +1798,6 @@ const char *S_MusicName(void)
 	return music_name;
 }
 
-boolean S_MusicInfo(char *mname, UINT16 *mflags, boolean *looping)
-{
-	if (!I_SongPlaying())
-		return false;
-
-	strncpy(mname, music_name, 7);
-	mname[6] = 0;
-	*mflags = music_flags;
-	*looping = music_looping;
-
-	return (boolean)mname[0];
-}
-
 boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi)
 {
 	return (
@@ -2100,6 +2038,8 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 	boolean mapmuschanged = false;
 	musicstack_t *result;
 	musicstack_t *entry = Z_Calloc(sizeof (*result), PU_MUSIC, NULL);
+	boolean currentmidi = (I_SongType() == MU_MID || I_SongType() == MU_MID_EX);
+	boolean midipref = cv_musicpref.value;
 
 	if (status)
 		result = S_GetMusicStackEntry(status, fromfirst, -1);
@@ -2154,7 +2094,8 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 		return false;
 	}
 
-	if (strncmp(entry->musname, S_MusicName(), 7)) // don't restart music if we're already playing it
+	if (strncmp(entry->musname, S_MusicName(), 7) || // don't restart music if we're already playing it
+		(midipref != currentmidi && S_PrefAvailable(midipref, entry->musname))) // but do if the user's preference has changed
 	{
 		if (music_stack_fadeout)
 			S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, music_stack_fadeout, 0);
@@ -2201,10 +2142,12 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 
 static lumpnum_t S_GetMusicLumpNum(const char *mname)
 {
-	if (!S_DigMusicDisabled() && S_DigExists(mname))
-		return W_GetNumForName(va("o_%s", mname));
-	else if (!S_MIDIMusicDisabled() && S_MIDIExists(mname))
-		return W_GetNumForName(va("d_%s", mname));
+	boolean midipref = cv_musicpref.value;
+
+	if (S_PrefAvailable(midipref, mname))
+		return W_GetNumForName(va(midipref ? "d_%s":"o_%s", mname));
+	else if (S_PrefAvailable(!midipref, mname))
+		return W_GetNumForName(va(midipref ? "o_%s":"d_%s", mname));
 	else
 		return LUMPERROR;
 }
@@ -2228,17 +2171,6 @@ static boolean S_LoadMusic(const char *mname)
 	// load & register it
 	mdata = W_CacheLumpNum(mlumpnum, PU_MUSIC);
 
-#ifdef MUSSERV
-	if (msg_id != -1)
-	{
-		struct musmsg msg_buffer;
-
-		msg_buffer.msg_type = 6;
-		memset(msg_buffer.msg_text, 0, sizeof (msg_buffer.msg_text));
-		sprintf(msg_buffer.msg_text, "d_%s", mname);
-		msgsnd(msg_id, (struct msgbuf*)&msg_buffer, sizeof (msg_buffer.msg_text), IPC_NOWAIT);
-	}
-#endif
 
 	if (I_LoadSong(mdata, W_LumpLength(mlumpnum)))
 	{
@@ -2330,15 +2262,15 @@ static void S_ChangeMusicToQueue(void)
 void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms)
 {
 	char newmusic[7];
+	boolean currentmidi = (I_SongType() == MU_MID || I_SongType() == MU_MID_EX);
+	boolean midipref = cv_musicpref.value;
 
 	if (S_MusicDisabled())
 		return;
 
 	strncpy(newmusic, mmusic, 7);
-#ifdef HAVE_LUA_MUSICPLUS
-	if(LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms))
+	if (LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms))
 		return;
-#endif
 	newmusic[6] = 0;
 
  	// No Music (empty string)
@@ -2359,7 +2291,8 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 		I_FadeSong(0, prefadems, S_ChangeMusicToQueue);
 		return;
 	}
-	else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET))
+	else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET) ||
+		(midipref != currentmidi && S_PrefAvailable(midipref, newmusic)))
  	{
 		CONS_Debug(DBG_DETAILED, "Now playing song %s\n", newmusic);
 
@@ -2428,13 +2361,6 @@ void S_PauseAudio(void)
 	if (I_SongPlaying() && !I_SongPaused())
 		I_PauseSong();
 
-	// pause cd music
-#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
-	I_PauseCD();
-#else
-	I_StopCD();
-#endif
-
 	S_SetStackAdjustmentStart();
 }
 
@@ -2446,9 +2372,6 @@ void S_ResumeAudio(void)
 	if (I_SongPlaying() && I_SongPaused())
 		I_ResumeSong();
 
-	// resume cd music
-	I_ResumeCD();
-
 	S_AdjustMusicStackTics();
 }
 
@@ -2469,10 +2392,6 @@ void S_SetMusicVolume(INT32 digvolume, INT32 seqvolume)
 	CV_SetValue(&cv_midimusicvolume, seqvolume&31);
 	actualmidimusicvolume = cv_midimusicvolume.value;   //check for change of var
 
-#ifdef DJGPPDOS
-	digvolume = seqvolume = 31;
-#endif
-
 	switch(I_SongType())
 	{
 		case MU_MID:
@@ -2665,32 +2584,24 @@ void GameDigiMusic_OnChange(void)
 		digital_disabled = false;
 		I_StartupSound(); // will return early if initialised
 		I_InitMusic();
-		S_StopMusic();
+
 		if (Playing())
 			P_RestoreMusic(&players[consoleplayer]);
-		else
+		else if ((!cv_musicpref.value || midi_disabled) && S_DigExists("_clear"))
 			S_ChangeMusicInternal("_clear", false);
 	}
 	else
 	{
 		digital_disabled = true;
-		if (S_MusicType() != MU_MID)
+		if (S_MusicType() != MU_MID && S_MusicType() != MU_MID_EX)
 		{
-			if (midi_disabled)
-				S_StopMusic();
-			else
+			S_StopMusic();
+			if (!midi_disabled)
 			{
-				char mmusic[7];
-				UINT16 mflags;
-				boolean looping;
-
-				if (S_MusicInfo(mmusic, &mflags, &looping) && S_MIDIExists(mmusic))
-				{
-					S_StopMusic();
-					S_ChangeMusic(mmusic, mflags, looping);
-				}
+				if (Playing())
+					P_RestoreMusic(&players[consoleplayer]);
 				else
-					S_StopMusic();
+					S_ChangeMusicInternal("_clear", false);
 			}
 		}
 	}
@@ -2708,9 +2619,10 @@ void GameMIDIMusic_OnChange(void)
 		midi_disabled = false;
 		I_StartupSound(); // will return early if initialised
 		I_InitMusic();
+
 		if (Playing())
 			P_RestoreMusic(&players[consoleplayer]);
-		else
+		else if ((cv_musicpref.value || digital_disabled) && S_MIDIExists("_clear"))
 			S_ChangeMusicInternal("_clear", false);
 	}
 	else
@@ -2718,26 +2630,30 @@ void GameMIDIMusic_OnChange(void)
 		midi_disabled = true;
 		if (S_MusicType() == MU_MID || S_MusicType() == MU_MID_EX)
 		{
-			if (digital_disabled)
-				S_StopMusic();
-			else
+			S_StopMusic();
+			if (!digital_disabled)
 			{
-				char mmusic[7];
-				UINT16 mflags;
-				boolean looping;
-
-				if (S_MusicInfo(mmusic, &mflags, &looping) && S_DigExists(mmusic))
-				{
-					S_StopMusic();
-					S_ChangeMusic(mmusic, mflags, looping);
-				}
+				if (Playing())
+					P_RestoreMusic(&players[consoleplayer]);
 				else
-					S_StopMusic();
+					S_ChangeMusicInternal("_clear", false);
 			}
 		}
 	}
 }
 
+void MusicPref_OnChange(void)
+{
+	if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio") ||
+		M_CheckParm("-nomidimusic") || M_CheckParm("-nodigmusic"))
+		return;
+
+	if (Playing())
+		P_RestoreMusic(&players[consoleplayer]);
+	else if (S_PrefAvailable(cv_musicpref.value, "_clear"))
+		S_ChangeMusicInternal("_clear", false);
+}
+
 #ifdef HAVE_OPENMPT
 void ModFilter_OnChange(void)
 {
diff --git a/src/s_sound.h b/src/s_sound.h
index 3334fcb69d7bf18820776130c627ba948b1ecb52..4ac3c70bf0d4a76f759e166ee0db3c682894ee5f 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -46,6 +46,7 @@ extern consvar_t cv_1upsound;
 extern consvar_t cv_gamedigimusic;
 extern consvar_t cv_gamemidimusic;
 extern consvar_t cv_gamesounds;
+extern consvar_t cv_musicpref;
 
 extern consvar_t cv_playmusicifunfocused;
 extern consvar_t cv_playsoundsifunfocused;
@@ -60,27 +61,7 @@ extern consvar_t cv_midisoundfontpath;
 extern consvar_t cv_miditimiditypath;
 #endif
 
-#ifdef SNDSERV
-extern consvar_t sndserver_cmd, sndserver_arg;
-#endif
-#ifdef MUSSERV
-extern consvar_t musserver_cmd, musserver_arg;
-#endif
-
 extern CV_PossibleValue_t soundvolume_cons_t[];
-//part of i_cdmus.c
-extern consvar_t cd_volume, cdUpdate;
-
-#if defined (macintosh) && !defined (HAVE_SDL)
-typedef enum
-{
-	music_normal,
-	playlist_random,
-	playlist_normal
-} playmode_t;
-
-extern consvar_t play_mode;
-#endif
 
 typedef enum
 {
@@ -106,6 +87,9 @@ typedef struct
 	// origin of sound
 	const void *origin;
 
+	// initial volume of sound, which is applied after distance and direction
+	INT32 volume;
+
 	// handle of the sound being played
 	INT32 handle;
 
@@ -178,11 +162,16 @@ boolean S_MusicPaused(void);
 boolean S_MusicNotInFocus(void);
 musictype_t S_MusicType(void);
 const char *S_MusicName(void);
-boolean S_MusicInfo(char *mname, UINT16 *mflags, boolean *looping);
 boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi);
 #define S_DigExists(a) S_MusicExists(a, false, true)
 #define S_MIDIExists(a) S_MusicExists(a, true, false)
 
+// Returns whether the preferred format a (true = MIDI, false = Digital)
+// exists and is enabled for musicname b
+#define S_PrefAvailable(a, b) (a ? \
+	(!S_MIDIMusicDisabled() && S_MIDIExists(b)) : \
+	(!S_DigMusicDisabled() && S_DigExists(b)))
+
 //
 // Music Effects
 //
diff --git a/src/screen.c b/src/screen.c
index e7ff9e73555a35f146b78974f40d25eb4736299b..744523dab0552c3212e74c15f9362f8d9c1fae78 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -28,6 +28,7 @@
 #include "d_main.h"
 #include "d_clisrv.h"
 #include "f_finale.h"
+#include "y_inter.h" // usebuffer
 #include "i_sound.h" // closed captions
 #include "s_sound.h" // ditto
 #include "g_game.h" // ditto
@@ -58,12 +59,11 @@ UINT8 setrenderneeded = 0;
 static CV_PossibleValue_t scr_depth_cons_t[] = {{8, "8 bits"}, {16, "16 bits"}, {24, "24 bits"}, {32, "32 bits"}, {0, NULL}};
 
 //added : 03-02-98: default screen mode, as loaded/saved in config
-consvar_t cv_scr_width = {"scr_width", "1280", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_scr_height = {"scr_height", "800", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_scr_depth = {"scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_renderview = {"renderview", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_scr_width = CVAR_INIT ("scr_width", "1280", CV_SAVE, CV_Unsigned, NULL);
+consvar_t cv_scr_height = CVAR_INIT ("scr_height", "800", CV_SAVE, CV_Unsigned, NULL);
+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);
 
-static void SCR_ActuallyChangeRenderer(void);
 CV_PossibleValue_t cv_renderer_t[] = {
 	{1, "Software"},
 #ifdef HWRENDER
@@ -71,11 +71,12 @@ CV_PossibleValue_t cv_renderer_t[] = {
 #endif
 	{0, NULL}
 };
-consvar_t cv_renderer = {"renderer", "Software", CV_SAVE|CV_NOLUA|CV_CALL, cv_renderer_t, SCR_ChangeRenderer, 0, NULL, NULL, 0, 0, NULL};
+
+consvar_t cv_renderer = CVAR_INIT ("renderer", "Software", CV_SAVE|CV_NOLUA|CV_CALL, cv_renderer_t, SCR_ChangeRenderer);
 
 static void SCR_ChangeFullscreen(void);
 
-consvar_t cv_fullscreen = {"fullscreen", "Yes", CV_SAVE|CV_CALL, CV_YesNo, SCR_ChangeFullscreen, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_fullscreen = CVAR_INIT ("fullscreen", "Yes", CV_SAVE|CV_CALL, CV_YesNo, SCR_ChangeFullscreen);
 
 // =========================================================================
 //                           SCREEN VARIABLES
@@ -121,34 +122,34 @@ void SCR_SetDrawFuncs(void)
 		colfuncs[COLDRAWFUNC_FOG] = R_DrawFogColumn_8;
 
 		spanfuncs[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan_8;
+		spanfuncs[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_8;
+		spanfuncs[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_8;
 		spanfuncs[SPANDRAWFUNC_SPLAT] = R_DrawSplat_8;
 		spanfuncs[SPANDRAWFUNC_TRANSSPLAT] = R_DrawTranslucentSplat_8;
-		spanfuncs[SPANDRAWFUNC_FOG] = R_DrawFogSpan_8;
-#ifndef NOWATER
+		spanfuncs[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_8;
+		spanfuncs[SPANDRAWFUNC_SPRITE] = R_DrawFloorSprite_8;
+		spanfuncs[SPANDRAWFUNC_TRANSSPRITE] = R_DrawTranslucentFloorSprite_8;
+		spanfuncs[SPANDRAWFUNC_TILTEDSPRITE] = R_DrawTiltedFloorSprite_8;
+		spanfuncs[SPANDRAWFUNC_TILTEDTRANSSPRITE] = R_DrawTiltedTranslucentFloorSprite_8;
 		spanfuncs[SPANDRAWFUNC_WATER] = R_DrawTranslucentWaterSpan_8;
-#endif
-		spanfuncs[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_8;
-		spanfuncs[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_8;
-#ifndef NOWATER
 		spanfuncs[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedTranslucentWaterSpan_8;
-#endif
-		spanfuncs[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_8;
+		spanfuncs[SPANDRAWFUNC_FOG] = R_DrawFogSpan_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;
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_SPLAT] = R_DrawSplat_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TRANSSPLAT] = R_DrawTranslucentSplat_NPO2_8;
-		spanfuncs_npo2[SPANDRAWFUNC_FOG] = NULL; // Not needed
-#ifndef NOWATER
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_SPRITE] = R_DrawFloorSprite_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_TRANSSPRITE] = R_DrawTranslucentFloorSprite_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDSPRITE] = R_DrawTiltedFloorSprite_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANSSPRITE] = R_DrawTiltedTranslucentFloorSprite_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_WATER] = R_DrawTranslucentWaterSpan_NPO2_8;
-#endif
-		spanfuncs_npo2[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_NPO2_8;
-		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_NPO2_8;
-#ifndef NOWATER
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedTranslucentWaterSpan_NPO2_8;
-#endif
-		spanfuncs_npo2[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_FOG] = NULL; // Not needed
 
 #ifdef RUSEASM
 		if (R_ASM)
@@ -202,16 +203,21 @@ void SCR_SetMode(void)
 	// Lactozilla: Renderer switching
 	if (setrenderneeded)
 	{
-		Z_PreparePatchFlush();
-		needpatchflush = true;
-		needpatchrecache = true;
-		VID_CheckRenderer();
+		// stop recording movies (APNG only)
+		if (setrenderneeded && (moviemode == MM_APNG))
+			M_StopMovie();
+
+		// VID_SetMode will call VID_CheckRenderer itself,
+		// so no need to do this in here.
 		if (!setmodeneeded)
-			VID_SetMode(vid.modenum);
+			VID_CheckRenderer();
+
+		vid.recalc = 1;
 	}
 
+	// Set the video mode in the video interface.
 	if (setmodeneeded)
-		VID_SetMode(--setmodeneeded);
+		VID_SetMode(setmodeneeded - 1);
 
 	V_SetPalette(0);
 
@@ -279,34 +285,9 @@ void SCR_Startup(void)
 
 	vid.modenum = 0;
 
-	vid.dupx = vid.width / BASEVIDWIDTH;
-	vid.dupy = vid.height / BASEVIDHEIGHT;
-	vid.dupx = vid.dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	vid.fdupx = FixedDiv(vid.width*FRACUNIT, BASEVIDWIDTH*FRACUNIT);
-	vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT);
-
-#ifdef HWRENDER
-	if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
-#endif
-		vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy);
-
-	vid.meddupx = (UINT8)(vid.dupx >> 1) + 1;
-	vid.meddupy = (UINT8)(vid.dupy >> 1) + 1;
-#ifdef HWRENDER
-	vid.fmeddupx = vid.meddupx*FRACUNIT;
-	vid.fmeddupy = vid.meddupy*FRACUNIT;
-#endif
-
-	vid.smalldupx = (UINT8)(vid.dupx / 3) + 1;
-	vid.smalldupy = (UINT8)(vid.dupy / 3) + 1;
-#ifdef HWRENDER
-	vid.fsmalldupx = vid.smalldupx*FRACUNIT;
-	vid.fsmalldupy = vid.smalldupy*FRACUNIT;
-#endif
-
-	vid.baseratio = FRACUNIT;
-
 	V_Init();
+	V_Recalc();
+
 	CV_RegisterVar(&cv_ticrate);
 	CV_RegisterVar(&cv_constextsize);
 
@@ -323,38 +304,7 @@ void SCR_Recalc(void)
 	// bytes per pixel quick access
 	scr_bpp = vid.bpp;
 
-	// scale 1,2,3 times in x and y the patches for the menus and overlays...
-	// calculated once and for all, used by routines in v_video.c
-	vid.dupx = vid.width / BASEVIDWIDTH;
-	vid.dupy = vid.height / BASEVIDHEIGHT;
-	vid.dupx = vid.dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	vid.fdupx = FixedDiv(vid.width*FRACUNIT, BASEVIDWIDTH*FRACUNIT);
-	vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT);
-
-#ifdef HWRENDER
-	//if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
-	// 13/11/18:
-	// The above is no longer necessary, since we want OpenGL to be just like software now
-	// -- Monster Iestyn
-#endif
-		vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy);
-
-	//vid.baseratio = FixedDiv(vid.height << FRACBITS, BASEVIDHEIGHT << FRACBITS);
-	vid.baseratio = FRACUNIT;
-
-	vid.meddupx = (UINT8)(vid.dupx >> 1) + 1;
-	vid.meddupy = (UINT8)(vid.dupy >> 1) + 1;
-#ifdef HWRENDER
-	vid.fmeddupx = vid.meddupx*FRACUNIT;
-	vid.fmeddupy = vid.meddupy*FRACUNIT;
-#endif
-
-	vid.smalldupx = (UINT8)(vid.dupx / 3) + 1;
-	vid.smalldupy = (UINT8)(vid.dupy / 3) + 1;
-#ifdef HWRENDER
-	vid.fsmalldupx = vid.smalldupx*FRACUNIT;
-	vid.fsmalldupy = vid.smalldupy*FRACUNIT;
-#endif
+	V_Recalc();
 
 	// toggle off (then back on) the automap because some screensize-dependent values will
 	// be calculated next time the automap is activated.
@@ -374,6 +324,12 @@ void SCR_Recalc(void)
 	// vid.recalc lasts only for the next refresh...
 	con_recalc = true;
 	am_recalc = true;
+
+#ifdef HWRENDER
+	// Shoot! The screen texture was flushed!
+	if ((rendermode == render_opengl) && (gamestate == GS_INTERMISSION))
+		usebuffer = false;
+#endif
 }
 
 // Check for screen cmd-line parms: to force a resolution.
@@ -411,7 +367,16 @@ void SCR_CheckDefaultMode(void)
 		setmodeneeded = VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value) + 1;
 	}
 
-	SCR_ActuallyChangeRenderer();
+	if (cv_renderer.value != (signed)rendermode)
+	{
+		if (chosenrendermode == render_none) // nothing set at command line
+			SCR_ChangeRenderer();
+		else
+		{
+			// Set cv_renderer to the current render mode
+			CV_StealthSetValue(&cv_renderer, rendermode);
+		}
+	}
 }
 
 // sets the modenum as the new default video mode to be saved in the config file
@@ -441,67 +406,27 @@ void SCR_ChangeFullscreen(void)
 #endif
 }
 
-static int target_renderer = 0;
-
-void SCR_ActuallyChangeRenderer(void)
+void SCR_ChangeRenderer(void)
 {
-	setrenderneeded = target_renderer;
+	if (chosenrendermode != render_none
+	|| (signed)rendermode == cv_renderer.value)
+		return;
 
 #ifdef HWRENDER
-	// Well, it didn't even load anyway.
-	if ((vid_opengl_state == -1) && (setrenderneeded == render_opengl))
+	// Check if OpenGL loaded successfully (or wasn't disabled) before switching to it.
+	if ((vid.glstate == VID_GL_LIBRARY_ERROR)
+	&& (cv_renderer.value == render_opengl))
 	{
 		if (M_CheckParm("-nogl"))
 			CONS_Alert(CONS_ERROR, "OpenGL rendering was disabled!\n");
 		else
 			CONS_Alert(CONS_ERROR, "OpenGL never loaded\n");
-		setrenderneeded = 0;
 		return;
 	}
 #endif
 
-	// setting the same renderer twice WILL crash your game, so let's not, please
-	if (rendermode == setrenderneeded)
-		setrenderneeded = 0;
-}
-
-// Lactozilla: Renderer switching
-void SCR_ChangeRenderer(void)
-{
-	setrenderneeded = 0;
-
-	if (con_startup)
-	{
-		target_renderer = cv_renderer.value;
-#ifdef HWRENDER
-		if (M_CheckParm("-opengl") && (vid_opengl_state == 1))
-			target_renderer = rendermode = render_opengl;
-		else
-#endif
-		if (M_CheckParm("-software"))
-			target_renderer = rendermode = render_soft;
-		// set cv_renderer back
-		SCR_ChangeRendererCVars(rendermode);
-		return;
-	}
-
-	if (cv_renderer.value == 1)
-		target_renderer = render_soft;
-	else if (cv_renderer.value == 2)
-		target_renderer = render_opengl;
-	SCR_ActuallyChangeRenderer();
-}
-
-void SCR_ChangeRendererCVars(INT32 mode)
-{
-	// set cv_renderer back
-	if (mode == render_soft)
-		CV_StealthSetValue(&cv_renderer, 1);
-	else if (mode == render_opengl)
-		CV_StealthSetValue(&cv_renderer, 2);
-#ifdef HWRENDER
-	CV_StealthSetValue(&cv_newrenderer, cv_renderer.value);
-#endif
+	// Set the new render mode
+	setrenderneeded = cv_renderer.value;
 }
 
 boolean SCR_IsAspectCorrect(INT32 width, INT32 height)
diff --git a/src/screen.h b/src/screen.h
index 91ec175f426cb53d723a2a0539c8611ed0cfaa17..e4944775d952249c785c14262960daa8f58bc796 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -72,10 +72,16 @@ typedef struct viddef_s
 #ifdef HWRENDER
 	INT32/*fixed_t*/ fsmalldupx, fsmalldupy;
 	INT32/*fixed_t*/ fmeddupx, fmeddupy;
+	INT32 glstate;
 #endif
 } viddef_t;
-#define VIDWIDTH vid.width
-#define VIDHEIGHT vid.height
+
+enum
+{
+	VID_GL_LIBRARY_NOTLOADED  = 0,
+	VID_GL_LIBRARY_LOADED     = 1,
+	VID_GL_LIBRARY_ERROR      = -1,
+};
 
 // internal additional info for vesa modes only
 typedef struct
@@ -134,18 +140,22 @@ enum
 {
 	SPANDRAWFUNC_BASE = BASEDRAWFUNC,
 	SPANDRAWFUNC_TRANS,
-	SPANDRAWFUNC_SPLAT,
-	SPANDRAWFUNC_TRANSSPLAT,
-	SPANDRAWFUNC_FOG,
-#ifndef NOWATER
-	SPANDRAWFUNC_WATER,
-#endif
 	SPANDRAWFUNC_TILTED,
 	SPANDRAWFUNC_TILTEDTRANS,
+
+	SPANDRAWFUNC_SPLAT,
+	SPANDRAWFUNC_TRANSSPLAT,
 	SPANDRAWFUNC_TILTEDSPLAT,
-#ifndef NOWATER
+
+	SPANDRAWFUNC_SPRITE,
+	SPANDRAWFUNC_TRANSSPRITE,
+	SPANDRAWFUNC_TILTEDSPRITE,
+	SPANDRAWFUNC_TILTEDTRANSSPRITE,
+
+	SPANDRAWFUNC_WATER,
 	SPANDRAWFUNC_TILTEDWATER,
-#endif
+
+	SPANDRAWFUNC_FOG,
 
 	SPANDRAWFUNC_MAX
 };
@@ -168,37 +178,38 @@ extern boolean R_SSE2;
 // ----------------
 // screen variables
 // ----------------
-
 extern viddef_t vid;
 extern INT32 setmodeneeded; // mode number to set if needed, or 0
+extern UINT8 setrenderneeded;
 
 void SCR_ChangeRenderer(void);
-void SCR_ChangeRendererCVars(INT32 mode);
-extern UINT8 setrenderneeded;
+
+extern CV_PossibleValue_t cv_renderer_t[];
 
 extern INT32 scr_bpp;
 extern UINT8 *scr_borderpatch; // patch used to fill the view borders
 
-extern CV_PossibleValue_t cv_renderer_t[];
-
 extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_renderer, cv_fullscreen;
-#ifdef HWRENDER
-extern consvar_t cv_newrenderer;
-#endif
 // wait for page flipping to end or not
 extern consvar_t cv_vidwait;
 
+// Initialize the screen
+void SCR_Startup(void);
+
 // Change video mode, only at the start of a refresh.
 void SCR_SetMode(void);
+
+// Set drawer functions for Software
 void SCR_SetDrawFuncs(void);
+
 // Recalc screen size dependent stuff
 void SCR_Recalc(void);
+
 // Check parms once at startup
 void SCR_CheckDefaultMode(void);
-// Set the mode number which is saved in the config
-void SCR_SetDefaultMode (void);
 
-void SCR_Startup (void);
+// Set the mode number which is saved in the config
+void SCR_SetDefaultMode(void);
 
 FUNCMATH boolean SCR_IsAspectCorrect(INT32 width, INT32 height);
 
diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt
index 744b242fa83d15dd9c51cc5a80fa2758f138a1cd..a7f015c869c783537dccfff26bad85de9c68f5f5 100644
--- a/src/sdl/CMakeLists.txt
+++ b/src/sdl/CMakeLists.txt
@@ -36,7 +36,6 @@ set(SRB2_SDL2_SOURCES
 	dosstr.c
 	endtxt.c
 	hwsym_sdl.c
-	i_cdmus.c
 	i_main.c
 	i_net.c
 	i_system.c
@@ -56,6 +55,10 @@ set(SRB2_SDL2_HEADERS
 	sdlmain.h
 )
 
+if(${SRB2_CONFIG_HAVE_THREADS})
+	set(SRB2_SDL2_SOURCES ${SRB2_SDL2_SOURCES} i_threads.c)
+endif()
+
 source_group("Interface Code" FILES ${SRB2_SDL2_SOURCES} ${SRB2_SDL2_HEADERS})
 
 # Dependency
@@ -166,6 +169,7 @@ if(${SDL2_FOUND})
 			${PNG_LIBRARIES}
 			${ZLIB_LIBRARIES}
 			${OPENGL_LIBRARIES}
+			${CURL_LIBRARIES}
 		)
 		set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
 	else()
@@ -178,6 +182,7 @@ if(${SDL2_FOUND})
 			${PNG_LIBRARIES}
 			${ZLIB_LIBRARIES}
 			${OPENGL_LIBRARIES}
+			${CURL_LIBRARIES}
 		)
 
 		if(${CMAKE_SYSTEM} MATCHES Linux)
@@ -259,6 +264,7 @@ if(${SDL2_FOUND})
 		${PNG_INCLUDE_DIRS}
 		${ZLIB_INCLUDE_DIRS}
 		${OPENGL_INCLUDE_DIRS}
+		${CURL_INCLUDE_DIRS}
 	)
 
 	if((${SRB2_HAVE_MIXER}) OR (${SRB2_HAVE_MIXERX}))
@@ -266,7 +272,7 @@ if(${SDL2_FOUND})
 	endif()
 
 	target_compile_definitions(SRB2SDL2 PRIVATE
-		-DDDIRECTFULLSCREEN -DHAVE_SDL
+		-DDIRECTFULLSCREEN -DHAVE_SDL
 	)
 
 	## strip debug symbols into separate file when using gcc.
diff --git a/src/sdl/Makefile.cfg b/src/sdl/Makefile.cfg
index f1383a04f5a63341aab0bc18977d76e4ef0a7e65..45d0d6ba75a666cba5e4e2c3a3f9704987705cb6 100644
--- a/src/sdl/Makefile.cfg
+++ b/src/sdl/Makefile.cfg
@@ -66,6 +66,11 @@ else
 endif
 endif
 
+ifndef NOTHREADS
+	OPTS+=-DHAVE_THREADS
+	OBJS+=$(OBJDIR)/i_threads.o
+endif
+
 ifdef SDL_TTF
 	OPTS+=-DHAVE_TTF
 	SDL_LDFLAGS+=-lSDL2_ttf -lfreetype -lz
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 38b3d1538733c6aa8f764d671c8225aa13e5bfc9..d46a4af2b0d89ea1dc93b0573e4ef1d6a32665b4 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -196,6 +196,9 @@
     <ClInclude Include="..\comptime.h" />
     <ClInclude Include="..\console.h" />
     <ClInclude Include="..\dehacked.h" />
+    <ClInclude Include="..\deh_soc.h" />
+    <ClInclude Include="..\deh_lua.h" />
+    <ClInclude Include="..\deh_tables.h" />
     <ClInclude Include="..\doomdata.h" />
     <ClInclude Include="..\doomdef.h" />
     <ClInclude Include="..\doomstat.h" />
@@ -226,7 +229,6 @@
     <ClInclude Include="..\hardware\hw_defs.h" />
     <ClInclude Include="..\hardware\hw_dll.h" />
     <ClInclude Include="..\hardware\hw_drv.h" />
-    <ClInclude Include="..\hardware\hw_glide.h" />
     <ClInclude Include="..\hardware\hw_glob.h" />
     <ClInclude Include="..\hardware\hw_light.h" />
     <ClInclude Include="..\hardware\hw_main.h" />
@@ -252,6 +254,7 @@
     <ClInclude Include="..\lzf.h" />
     <ClInclude Include="..\md5.h" />
     <ClInclude Include="..\mserv.h" />
+    <ClInclude Include="..\http-mserv.h" />
     <ClInclude Include="..\m_aatree.h" />
     <ClInclude Include="..\m_anigif.h" />
     <ClInclude Include="..\m_argv.h" />
@@ -262,6 +265,7 @@
     <ClInclude Include="..\m_fixed.h" />
     <ClInclude Include="..\m_menu.h" />
     <ClInclude Include="..\m_misc.h" />
+    <ClInclude Include="..\m_perfstats.h" />
     <ClInclude Include="..\m_queue.h" />
     <ClInclude Include="..\m_random.h" />
     <ClInclude Include="..\m_swap.h" />
@@ -282,14 +286,17 @@
     <ClInclude Include="..\r_draw.h" />
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
-    <ClInclude Include="..\r_plane.h" />
     <ClInclude Include="..\r_patch.h" />
+    <ClInclude Include="..\r_patchrotation.h" />
+    <ClInclude Include="..\r_picformats.h" />
+    <ClInclude Include="..\r_plane.h" />
     <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_skins.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
     <ClInclude Include="..\r_state.h" />
+    <ClInclude Include="..\r_textures.h" />
     <ClInclude Include="..\r_things.h" />
     <ClInclude Include="..\screen.h" />
     <ClInclude Include="..\sounds.h" />
@@ -359,6 +366,9 @@
     <ClCompile Include="..\comptime.c" />
     <ClCompile Include="..\console.c" />
     <ClCompile Include="..\dehacked.c" />
+    <ClCompile Include="..\deh_soc.c" />
+    <ClCompile Include="..\deh_lua.c" />
+    <ClCompile Include="..\deh_tables.c" />
     <ClCompile Include="..\d_clisrv.c" />
     <ClCompile Include="..\d_main.c" />
     <ClCompile Include="..\d_net.c" />
@@ -400,12 +410,14 @@
     <ClCompile Include="..\lua_mathlib.c" />
     <ClCompile Include="..\lua_mobjlib.c" />
     <ClCompile Include="..\lua_playerlib.c" />
+    <ClCompile Include="..\lua_polyobjlib.c" />
     <ClCompile Include="..\lua_script.c" />
     <ClCompile Include="..\lua_skinlib.c" />
     <ClCompile Include="..\lua_thinkerlib.c" />
     <ClCompile Include="..\lzf.c" />
     <ClCompile Include="..\md5.c" />
     <ClCompile Include="..\mserv.c" />
+    <ClCompile Include="..\http-mserv.c" />
     <ClCompile Include="..\m_aatree.c" />
     <ClCompile Include="..\m_anigif.c" />
     <ClCompile Include="..\m_argv.c" />
@@ -415,6 +427,7 @@
     <ClCompile Include="..\m_fixed.c" />
     <ClCompile Include="..\m_menu.c" />
     <ClCompile Include="..\m_misc.c" />
+    <ClCompile Include="..\m_perfstats.c" />
     <ClCompile Include="..\m_queue.c" />
     <ClCompile Include="..\m_random.c" />
     <ClCompile Include="..\p_ceilng.c" />
@@ -447,13 +460,16 @@
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
+    <ClCompile Include="..\r_patch.c" />
+    <ClCompile Include="..\r_patchrotation.c" />
+    <ClCompile Include="..\r_picformats.c" />
     <ClCompile Include="..\r_plane.c" />
-	<ClCompile Include="..\r_patch.c" />
     <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_skins.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
+    <ClCompile Include="..\r_textures.c" />
     <ClCompile Include="..\r_things.c" />
     <ClCompile Include="..\screen.c" />
     <ClCompile Include="..\sounds.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index e49074d3becad90724f0a8f2f3209d69aa28af1d..adae2f446dbde8267e375bb794eacc50bed9c663 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -237,9 +237,6 @@
     <ClInclude Include="..\hardware\hw_drv.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
-    <ClInclude Include="..\hardware\hw_glide.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
     <ClInclude Include="..\hardware\hw_glob.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
@@ -300,6 +297,9 @@
     <ClInclude Include="..\mserv.h">
       <Filter>I_Interface</Filter>
     </ClInclude>
+    <ClInclude Include="..\http-mserv.h">
+      <Filter>I_Interface</Filter>
+    </ClInclude>
     <ClInclude Include="..\lua_hook.h">
       <Filter>LUA</Filter>
     </ClInclude>
@@ -348,6 +348,9 @@
     <ClInclude Include="..\m_misc.h">
       <Filter>M_Misc</Filter>
     </ClInclude>
+    <ClInclude Include="..\m_perfstats.h">
+      <Filter>M_Misc</Filter>
+    </ClInclude>
     <ClInclude Include="..\m_queue.h">
       <Filter>M_Misc</Filter>
     </ClInclude>
@@ -477,6 +480,15 @@
     <ClInclude Include="..\r_patch.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
+    <ClInclude Include="..\r_patchrotation.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_picformats.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_textures.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
     <ClInclude Include="..\r_portal.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
@@ -687,6 +699,9 @@
     <ClCompile Include="..\mserv.c">
       <Filter>I_Interface</Filter>
     </ClCompile>
+    <ClCompile Include="..\http-mserv.c">
+      <Filter>I_Interface</Filter>
+    </ClCompile>
     <ClCompile Include="..\lua_baselib.c">
       <Filter>LUA</Filter>
     </ClCompile>
@@ -717,6 +732,9 @@
     <ClCompile Include="..\lua_playerlib.c">
       <Filter>LUA</Filter>
     </ClCompile>
+    <ClCompile Include="..\lua_polyobjlib.c">
+      <Filter>LUA</Filter>
+    </ClCompile>
     <ClCompile Include="..\lua_script.c">
       <Filter>LUA</Filter>
     </ClCompile>
@@ -756,6 +774,9 @@
     <ClCompile Include="..\m_misc.c">
       <Filter>M_Misc</Filter>
     </ClCompile>
+    <ClCompile Include="..\m_perfstats.c">
+      <Filter>M_Misc</Filter>
+    </ClCompile>
     <ClCompile Include="..\m_queue.c">
       <Filter>M_Misc</Filter>
     </ClCompile>
@@ -949,6 +970,15 @@
     <ClCompile Include="..\r_patch.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
+    <ClCompile Include="..\r_patchrotation.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
+    <ClCompile Include="..\r_picformats.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
+    <ClCompile Include="..\r_textures.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
     <ClCompile Include="..\r_portal.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
diff --git a/src/sdl/Srb2SDL-vc9.vcproj b/src/sdl/Srb2SDL-vc9.vcproj
index 39184f4094882786baeede4c9b0763db684dd026..95f035267026215279b91d1d309b08be34e6e29e 100644
--- a/src/sdl/Srb2SDL-vc9.vcproj
+++ b/src/sdl/Srb2SDL-vc9.vcproj
@@ -1710,6 +1710,138 @@
 				RelativePath="..\dehacked.h"
 				>
 			</File>
+			<File
+				RelativePath="..\deh_soc.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\deh_soc.h"
+				>
+			</File>
+			<File
+				RelativePath="..\deh_lua.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\deh_lua.h"
+				>
+			</File>
+			<File
+				RelativePath="..\deh_tables.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\deh_tables.h"
+				>
+			</File>
 			<File
 				RelativePath="..\doomdata.h"
 				>
@@ -2410,10 +2542,6 @@
 				RelativePath="..\hardware\hw_drv.h"
 				>
 			</File>
-			<File
-				RelativePath="..\hardware\hw_glide.h"
-				>
-			</File>
 			<File
 				RelativePath="..\hardware\hw_glob.h"
 				>
@@ -2746,6 +2874,50 @@
 				RelativePath="..\mserv.h"
 				>
 			</File>
+			<File
+				RelativePath="..\http-mserv.c"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|x64"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\http-mserv.h"
+				>
+			</File>
 		</Filter>
 		<Filter
 			Name="M_Misc"
diff --git a/src/sdl/Srb2SDL.dsp b/src/sdl/Srb2SDL.dsp
index 435820cb050ca1cf0e1778e94234e4950bf35ac5..9f6dd7b330e924a1026a00e48b10415eda233d79 100644
--- a/src/sdl/Srb2SDL.dsp
+++ b/src/sdl/Srb2SDL.dsp
@@ -576,10 +576,6 @@ SOURCE=..\hardware\hw_drv.h
 # End Source File
 # Begin Source File
 
-SOURCE=..\hardware\hw_glide.h
-# End Source File
-# Begin Source File
-
 SOURCE=..\hardware\hw_glob.h
 # End Source File
 # Begin Source File
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 416c8d2f5531f1ec09d15770a711fa756118c129..96e3d7d6926ef23771c8dcf489b4d8d2a16c0a1c 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -86,6 +86,7 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(ClearBuffer);
 	GETFUNC(SetTexture);
 	GETFUNC(UpdateTexture);
+	GETFUNC(DeleteTexture);
 	GETFUNC(ReadRect);
 	GETFUNC(GClipRect);
 	GETFUNC(ClearMipMapCache);
@@ -104,14 +105,13 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(MakeScreenFinalTexture);
 	GETFUNC(DrawScreenFinalTexture);
 
-	GETFUNC(LoadShaders);
-	GETFUNC(KillShaders);
+	GETFUNC(CompileShaders);
+	GETFUNC(CleanShaders);
 	GETFUNC(SetShader);
 	GETFUNC(UnSetShader);
 
 	GETFUNC(SetShaderInfo);
 	GETFUNC(LoadCustomShader);
-	GETFUNC(InitCustomShaders);
 
 #else //HWRENDER
 	if (0 == strcmp("FinishUpdate", funcName))
diff --git a/src/sdl/i_cdmus.c b/src/sdl/i_cdmus.c
deleted file mode 100644
index 5d086e73a05fdc7d28b23818a9141aa9edc650dc..0000000000000000000000000000000000000000
--- a/src/sdl/i_cdmus.c
+++ /dev/null
@@ -1,37 +0,0 @@
-#include "../command.h"
-#include "../s_sound.h"
-#include "../i_sound.h"
-
-//
-// CD MUSIC I/O
-//
-
-UINT8 cdaudio_started = 0;
-
-consvar_t cd_volume = {"cd_volume","31",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-
-
-void I_InitCD(void){}
-
-void I_StopCD(void){}
-
-void I_PauseCD(void){}
-
-void I_ResumeCD(void){}
-
-void I_ShutdownCD(void){}
-
-void I_UpdateCD(void){}
-
-void I_PlayCD(UINT8 track, UINT8 looping)
-{
-	(void)track;
-	(void)looping;
-}
-
-FUNCMATH boolean I_SetVolumeCD(int volume)
-{
-	(void)volume;
-	return false;
-}
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index b24ae2814985d04b3674aaf5afa3c09628dcd78c..d2c819c37093ffbeaa6156f6561d2ffb7db9df50 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -54,12 +54,6 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #include <fcntl.h>
 #endif
 
-#if defined (_WIN32)
-DWORD TimeFunction(int requested_frequency);
-#else
-int TimeFunction(int requested_frequency);
-#endif
-
 #include <stdio.h>
 #ifdef _WIN32
 #include <conio.h>
@@ -173,6 +167,7 @@ static char returnWadPath[256];
 #include "../i_video.h"
 #include "../i_sound.h"
 #include "../i_system.h"
+#include "../i_threads.h"
 #include "../screen.h" //vid.WndParent
 #include "../d_net.h"
 #include "../g_game.h"
@@ -559,14 +554,12 @@ static void Impl_HandleKeyboardConsoleEvent(KEY_EVENT_RECORD evt, HANDLE co)
 			case VK_TAB:
 				event.data1 = KEY_NULL;
 				break;
-			case VK_SHIFT:
-				event.data1 = KEY_LSHIFT;
-				break;
 			case VK_RETURN:
 				entering_con_command = false;
 				/* FALLTHRU */
 			default:
-				event.data1 = MapVirtualKey(evt.wVirtualKeyCode,2); // convert in to char
+				//event.data1 = MapVirtualKey(evt.wVirtualKeyCode,2); // convert in to char
+				event.data1 = evt.uChar.AsciiChar;
 		}
 		if (co != INVALID_HANDLE_VALUE && GetFileType(co) == FILE_TYPE_CHAR && GetConsoleMode(co, &t))
 		{
@@ -585,18 +578,6 @@ static void Impl_HandleKeyboardConsoleEvent(KEY_EVENT_RECORD evt, HANDLE co)
 			}
 		}
 	}
-	else
-	{
-		event.type = ev_keyup;
-		switch (evt.wVirtualKeyCode)
-		{
-			case VK_SHIFT:
-				event.data1 = KEY_LSHIFT;
-				break;
-			default:
-				break;
-		}
-	}
 	if (event.data1) D_PostEvent(&event);
 }
 
@@ -2057,112 +2038,36 @@ ticcmd_t *I_BaseTiccmd2(void)
 	return &emptycmd2;
 }
 
-#if defined (_WIN32)
-static HMODULE winmm = NULL;
-static DWORD starttickcount = 0; // hack for win2k time bug
-static p_timeGetTime pfntimeGetTime = NULL;
-
-// ---------
-// I_GetTime
-// Use the High Resolution Timer if available,
-// else use the multimedia timer which has 1 millisecond precision on Windowz 95,
-// but lower precision on Windows NT
-// ---------
-
-DWORD TimeFunction(int requested_frequency)
-{
-	DWORD newtics = 0;
-	// this var acts as a multiplier if sub-millisecond precision is asked but is not available
-	int excess_frequency = requested_frequency / 1000;
-
-	if (!starttickcount) // high precision timer
-	{
-		LARGE_INTEGER currtime; // use only LowPart if high resolution counter is not available
-		static LARGE_INTEGER basetime = {{0, 0}};
-
-		// use this if High Resolution timer is found
-		static LARGE_INTEGER frequency;
-
-		if (!basetime.LowPart)
-		{
-			if (!QueryPerformanceFrequency(&frequency))
-				frequency.QuadPart = 0;
-			else
-				QueryPerformanceCounter(&basetime);
-		}
-
-		if (frequency.LowPart && QueryPerformanceCounter(&currtime))
-		{
-			newtics = (INT32)((currtime.QuadPart - basetime.QuadPart) * requested_frequency
-				/ frequency.QuadPart);
-		}
-		else if (pfntimeGetTime)
-		{
-			currtime.LowPart = pfntimeGetTime();
-			if (!basetime.LowPart)
-				basetime.LowPart = currtime.LowPart;
-			if (requested_frequency > 1000)
-				newtics = currtime.LowPart - basetime.LowPart * excess_frequency;
-			else
-				newtics = (currtime.LowPart - basetime.LowPart)/(1000/requested_frequency);
-		}
-	}
-	else
-	{
-		if (requested_frequency > 1000)
-			newtics = (GetTickCount() - starttickcount) * excess_frequency;
-		else
-			newtics = (GetTickCount() - starttickcount)/(1000/requested_frequency);
-	}
-
-	return newtics;
-}
-
-static void I_ShutdownTimer(void)
-{
-	pfntimeGetTime = NULL;
-	if (winmm)
-	{
-		p_timeEndPeriod pfntimeEndPeriod = (p_timeEndPeriod)(LPVOID)GetProcAddress(winmm, "timeEndPeriod");
-		if (pfntimeEndPeriod)
-			pfntimeEndPeriod(1);
-		FreeLibrary(winmm);
-		winmm = NULL;
-	}
-}
-#else
 //
 // I_GetTime
 // returns time in 1/TICRATE second tics
 //
 
-// millisecond precision only
-int TimeFunction(int requested_frequency)
-{
-	static Uint64 basetime = 0;
-		   Uint64 ticks = SDL_GetTicks();
+static Uint64 timer_frequency;
 
-	if (!basetime)
-		basetime = ticks;
+static double tic_frequency;
+static Uint64 tic_epoch;
 
-	ticks -= basetime;
+tic_t I_GetTime(void)
+{
+	static double elapsed;
 
-	ticks = (ticks*requested_frequency);
+	const Uint64 now = SDL_GetPerformanceCounter();
 
-	ticks = (ticks/1000);
+	elapsed += (now - tic_epoch) / tic_frequency;
+	tic_epoch = now; // moving epoch
 
-	return ticks;
+	return (tic_t)elapsed;
 }
-#endif
 
-tic_t I_GetTime(void)
+precise_t I_GetPreciseTime(void)
 {
-	return TimeFunction(NEWTICRATE);
+	return SDL_GetPerformanceCounter();
 }
 
-int I_GetTimeMicros(void)
+int I_PreciseToMicros(precise_t d)
 {
-	return TimeFunction(1000000);
+	return (int)(d / (timer_frequency / 1000000.0));
 }
 
 //
@@ -2170,26 +2075,11 @@ int I_GetTimeMicros(void)
 //
 void I_StartupTimer(void)
 {
-#ifdef _WIN32
-	// for win2k time bug
-	if (M_CheckParm("-gettickcount"))
-	{
-		starttickcount = GetTickCount();
-		CONS_Printf("%s", M_GetText("Using GetTickCount()\n"));
-	}
-	winmm = LoadLibraryA("winmm.dll");
-	if (winmm)
-	{
-		p_timeEndPeriod pfntimeBeginPeriod = (p_timeEndPeriod)(LPVOID)GetProcAddress(winmm, "timeBeginPeriod");
-		if (pfntimeBeginPeriod)
-			pfntimeBeginPeriod(1);
-		pfntimeGetTime = (p_timeGetTime)(LPVOID)GetProcAddress(winmm, "timeGetTime");
-	}
-	I_AddExitFunc(I_ShutdownTimer);
-#endif
-}
-
+	timer_frequency = SDL_GetPerformanceFrequency();
+	tic_epoch       = SDL_GetPerformanceCounter();
 
+	tic_frequency   = timer_frequency / (double)NEWTICRATE;
+}
 
 void I_Sleep(void)
 {
@@ -2282,6 +2172,10 @@ INT32 I_StartupSystem(void)
 	SDL_version SDLlinked;
 	SDL_VERSION(&SDLcompiled)
 	SDL_GetVersion(&SDLlinked);
+#ifdef HAVE_THREADS
+	I_start_threads();
+	I_AddExitFunc(I_stop_threads);
+#endif
 	I_StartupConsole();
 #ifdef NEWSIGNALHANDLER
 	I_Fork();
@@ -2328,7 +2222,6 @@ void I_Quit(void)
 	M_FreePlayerSetupColors();
 	I_ShutdownMusic();
 	I_ShutdownSound();
-	I_ShutdownCD();
 	// use this for 1.28 19990220 by Kin
 	I_ShutdownGraphics();
 	I_ShutdownInput();
@@ -2389,16 +2282,14 @@ void I_Error(const char *error, ...)
 		if (errorcount == 3)
 			I_ShutdownSound();
 		if (errorcount == 4)
-			I_ShutdownCD();
-		if (errorcount == 5)
 			I_ShutdownGraphics();
-		if (errorcount == 6)
+		if (errorcount == 5)
 			I_ShutdownInput();
-		if (errorcount == 7)
+		if (errorcount == 6)
 			I_ShutdownSystem();
-		if (errorcount == 8)
+		if (errorcount == 7)
 			SDL_Quit();
-		if (errorcount == 9)
+		if (errorcount == 8)
 		{
 			M_SaveConfig(NULL);
 			G_SaveGameData();
@@ -2446,7 +2337,6 @@ void I_Error(const char *error, ...)
 	M_FreePlayerSetupColors();
 	I_ShutdownMusic();
 	I_ShutdownSound();
-	I_ShutdownCD();
 	// use this for 1.28 19990220 by Kin
 	I_ShutdownGraphics();
 	I_ShutdownInput();
@@ -2601,7 +2491,7 @@ void I_GetDiskFreeSpace(INT64 *freespace)
 	return;
 #else // Both Linux and BSD have this, apparently.
 	struct statfs stfs;
-	if (statfs(".", &stfs) == -1)
+	if (statfs(srb2home, &stfs) == -1)
 	{
 		*freespace = INT32_MAX;
 		return;
@@ -2620,7 +2510,7 @@ void I_GetDiskFreeSpace(INT64 *freespace)
 	}
 	if (pfnGetDiskFreeSpaceEx)
 	{
-		if (pfnGetDiskFreeSpaceEx(NULL, &lfreespace, &usedbytes, NULL))
+		if (pfnGetDiskFreeSpaceEx(srb2home, &lfreespace, &usedbytes, NULL))
 			*freespace = lfreespace.QuadPart;
 		else
 			*freespace = INT32_MAX;
@@ -2726,10 +2616,10 @@ const char *I_ClipboardPaste(void)
 
 	if (!SDL_HasClipboardText())
 		return NULL;
+
 	clipboard_contents = SDL_GetClipboardText();
-	memcpy(clipboard_modified, clipboard_contents, 255);
+	strlcpy(clipboard_modified, clipboard_contents, 256);
 	SDL_free(clipboard_contents);
-	clipboard_modified[255] = 0;
 
 	while (*i)
 	{
diff --git a/src/sdl/i_threads.c b/src/sdl/i_threads.c
new file mode 100644
index 0000000000000000000000000000000000000000..3b1c20b9a3cbb79038253b4bd5b7dbec3df001d7
--- /dev/null
+++ b/src/sdl/i_threads.c
@@ -0,0 +1,356 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+//
+// 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  i_threads.c
+/// \brief Multithreading abstraction
+
+#include "../doomdef.h"
+#include "../i_threads.h"
+
+#include <SDL.h>
+
+typedef void * (*Create_fn)(void);
+
+struct Link;
+struct Thread;
+
+typedef struct Link   * Link;
+typedef struct Thread * Thread;
+
+struct Link
+{
+	void * data;
+	Link   next;
+	Link   prev;
+};
+
+struct Thread
+{
+	I_thread_fn   entry;
+	void        * userdata;
+
+	SDL_Thread  * thread;
+};
+
+static Link    i_thread_pool;
+static Link    i_mutex_pool;
+static Link    i_cond_pool;
+
+static I_mutex        i_thread_pool_mutex;
+static I_mutex        i_mutex_pool_mutex;
+static I_mutex        i_cond_pool_mutex;
+
+static SDL_atomic_t   i_threads_running = {1};
+
+static Link
+Insert_link (
+		Link * head,
+		Link   link
+){
+	link->prev = NULL;
+	link->next = (*head);
+	if ((*head))
+		(*head)->prev = link;
+	(*head)    = link;
+	return link;
+}
+
+static void
+Free_link (
+		Link * head,
+		Link   link
+){
+	if (link->prev)
+		link->prev->next = link->next;
+	else
+		(*head) = link->next;
+
+	if (link->next)
+		link->next->prev = link->prev;
+
+	free(link->data);
+	free(link);
+}
+
+static Link
+New_link (void *data)
+{
+	Link link;
+
+	link = malloc(sizeof *link);
+
+	if (! link)
+		abort();
+
+	link->data = data;
+
+	return link;
+}
+
+static void *
+Identity (
+		Link      *  pool_anchor,
+		I_mutex      pool_mutex,
+
+		void      ** anchor,
+
+		Create_fn    create_fn
+){
+	void * id;
+
+	id = SDL_AtomicGetPtr(anchor);
+
+	if (! id)
+	{
+		I_lock_mutex(&pool_mutex);
+		{
+			id = SDL_AtomicGetPtr(anchor);
+
+			if (! id)
+			{
+				id = (*create_fn)();
+
+				if (! id)
+					abort();
+
+				Insert_link(pool_anchor, New_link(id));
+
+				SDL_AtomicSetPtr(anchor, id);
+			}
+		}
+		I_unlock_mutex(pool_mutex);
+	}
+
+	return id;
+}
+
+static int
+Worker (
+		Link link
+){
+	Thread th;
+
+	th = link->data;
+
+	(*th->entry)(th->userdata);
+
+	if (SDL_AtomicGet(&i_threads_running))
+	{
+		I_lock_mutex(&i_thread_pool_mutex);
+		{
+			if (SDL_AtomicGet(&i_threads_running))
+			{
+				SDL_DetachThread(th->thread);
+				Free_link(&i_thread_pool, link);
+			}
+		}
+		I_unlock_mutex(i_thread_pool_mutex);
+	}
+
+	return 0;
+}
+
+void
+I_spawn_thread (
+		const char  * name,
+		I_thread_fn   entry,
+		void        * userdata
+){
+	Link   link;
+	Thread th;
+
+	th = malloc(sizeof *th);
+
+	if (! th)
+		abort();/* this is pretty GNU of me */
+
+	th->entry    = entry;
+	th->userdata = userdata;
+
+	I_lock_mutex(&i_thread_pool_mutex);
+	{
+		link = Insert_link(&i_thread_pool, New_link(th));
+
+		if (SDL_AtomicGet(&i_threads_running))
+		{
+			th->thread = SDL_CreateThread(
+					(SDL_ThreadFunction)Worker,
+					name,
+					link
+			);
+
+			if (! th->thread)
+				abort();
+		}
+	}
+	I_unlock_mutex(i_thread_pool_mutex);
+}
+
+int
+I_thread_is_stopped (void)
+{
+	return ( ! SDL_AtomicGet(&i_threads_running) );
+}
+
+void
+I_start_threads (void)
+{
+	i_thread_pool_mutex = SDL_CreateMutex();
+	i_mutex_pool_mutex  = SDL_CreateMutex();
+	i_cond_pool_mutex   = SDL_CreateMutex();
+
+	if (!(
+				i_thread_pool_mutex &&
+				i_mutex_pool_mutex  &&
+				i_cond_pool_mutex
+	)){
+		abort();
+	}
+}
+
+void
+I_stop_threads (void)
+{
+	Link        link;
+	Link        next;
+
+	Thread      th;
+	SDL_mutex * mutex;
+	SDL_cond  * cond;
+
+	if (i_threads_running.value)
+	{
+		/* rely on the good will of thread-san */
+		SDL_AtomicSet(&i_threads_running, 0);
+
+		I_lock_mutex(&i_thread_pool_mutex);
+		{
+			for (
+					link = i_thread_pool;
+					link;
+					link = next
+			){
+				next = link->next;
+				th   = link->data;
+
+				SDL_WaitThread(th->thread, NULL);
+
+				free(th);
+				free(link);
+			}
+		}
+		I_unlock_mutex(i_thread_pool_mutex);
+
+		for (
+				link = i_mutex_pool;
+				link;
+				link = next
+		){
+			next  = link->next;
+			mutex = link->data;
+
+			SDL_DestroyMutex(mutex);
+
+			free(link);
+		}
+
+		for (
+				link = i_cond_pool;
+				link;
+				link = next
+		){
+			next = link->next;
+			cond = link->data;
+
+			SDL_DestroyCond(cond);
+
+			free(link);
+		}
+
+		SDL_DestroyMutex(i_thread_pool_mutex);
+		SDL_DestroyMutex(i_mutex_pool_mutex);
+		SDL_DestroyMutex(i_cond_pool_mutex);
+	}
+}
+
+void
+I_lock_mutex (
+		I_mutex * anchor
+){
+	SDL_mutex * mutex;
+
+	mutex = Identity(
+			&i_mutex_pool,
+			i_mutex_pool_mutex,
+			anchor,
+			(Create_fn)SDL_CreateMutex
+	);
+
+	if (SDL_LockMutex(mutex) == -1)
+		abort();
+}
+
+void
+I_unlock_mutex (
+		I_mutex id
+){
+	if (SDL_UnlockMutex(id) == -1)
+		abort();
+}
+
+void
+I_hold_cond (
+		I_cond  * cond_anchor,
+		I_mutex   mutex_id
+){
+	SDL_cond * cond;
+
+	cond = Identity(
+			&i_cond_pool,
+			i_cond_pool_mutex,
+			cond_anchor,
+			(Create_fn)SDL_CreateCond
+	);
+
+	if (SDL_CondWait(cond, mutex_id) == -1)
+		abort();
+}
+
+void
+I_wake_one_cond (
+		I_cond * anchor
+){
+	SDL_cond * cond;
+
+	cond = Identity(
+			&i_cond_pool,
+			i_cond_pool_mutex,
+			anchor,
+			(Create_fn)SDL_CreateCond
+	);
+
+	if (SDL_CondSignal(cond) == -1)
+		abort();
+}
+
+void
+I_wake_all_cond (
+		I_cond * anchor
+){
+	SDL_cond * cond;
+
+	cond = Identity(
+			&i_cond_pool,
+			i_cond_pool_mutex,
+			anchor,
+			(Create_fn)SDL_CreateCond
+	);
+
+	if (SDL_CondBroadcast(cond) == -1)
+		abort();
+}
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 5c5b6119c60824541156a28188d6cdb96d82ff16..0ed10463fc79734e004abdfdb960d56630168abf 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -42,7 +42,7 @@
 
 #ifdef HAVE_IMAGE
 #include "SDL_image.h"
-#elif defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) // Windows doesn't need this, as SDL will do it for us.
+#elif defined (__unix__) || (!defined(__APPLE__) && defined (UNIXCOMMON)) // Windows & Mac don't need this, as SDL will do it for us.
 #define LOAD_XPM //I want XPM!
 #include "IMG_xpm.c" //Alam: I don't want to add SDL_Image.dll/so
 #define HAVE_IMAGE //I have SDL_Image, sortof
@@ -95,17 +95,16 @@ static INT32 numVidModes = -1;
 static char vidModeName[33][32]; // allow 33 different modes
 
 rendermode_t rendermode = render_soft;
-static rendermode_t chosenrendermode = render_soft; // set by command line arguments
+rendermode_t chosenrendermode = render_none; // set by command line arguments
 
 boolean highcolor = false;
 
 // synchronize page flipping with screen refresh
-consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_stretch = {"stretch", "Off", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_alwaysgrabmouse = {"alwaysgrabmouse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, NULL);
+static consvar_t cv_stretch = CVAR_INIT ("stretch", "Off", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL);
+static consvar_t cv_alwaysgrabmouse = CVAR_INIT ("alwaysgrabmouse", "Off", CV_SAVE, CV_OnOff, NULL);
 
 UINT8 graphics_started = 0; // Is used in console.c and screen.c
-INT32 vid_opengl_state = 0;
 
 // To disable fullscreen at startup; is set in VID_PrepareModeList
 boolean allow_fullscreen = false;
@@ -1058,8 +1057,7 @@ void I_GetEvent(void)
 					M_SetupJoystickMenu(0);
 			 	break;
 			case SDL_QUIT:
-				if (Playing())
-					LUAh_GameQuit();
+				LUAh_GameQuit(true);
 				I_Quit();
 				break;
 		}
@@ -1443,7 +1441,8 @@ static SDL_bool Impl_CreateContext(void)
 {
 	// Renderer-specific stuff
 #ifdef HWRENDER
-	if ((rendermode == render_opengl) && (vid_opengl_state != -1))
+	if ((rendermode == render_opengl)
+	&& (vid.glstate != VID_GL_LIBRARY_ERROR))
 	{
 		if (!sdlglcontext)
 			sdlglcontext = SDL_GL_CreateContext(window);
@@ -1480,7 +1479,7 @@ void VID_CheckGLLoaded(rendermode_t oldrender)
 {
 	(void)oldrender;
 #ifdef HWRENDER
-	if (vid_opengl_state == -1) // Well, it didn't work the first time anyway.
+	if (vid.glstate == VID_GL_LIBRARY_ERROR) // Well, it didn't work the first time anyway.
 	{
 		CONS_Alert(CONS_ERROR, "OpenGL never loaded\n");
 		rendermode = oldrender;
@@ -1489,21 +1488,22 @@ void VID_CheckGLLoaded(rendermode_t oldrender)
 		if (setrenderneeded)
 		{
 			CV_StealthSetValue(&cv_renderer, oldrender);
-			CV_StealthSetValue(&cv_newrenderer, oldrender);
 			setrenderneeded = 0;
 		}
 	}
 #endif
 }
 
-void VID_CheckRenderer(void)
+boolean VID_CheckRenderer(void)
 {
 	boolean rendererchanged = false;
 	boolean contextcreated = false;
+#ifdef HWRENDER
 	rendermode_t oldrenderer = rendermode;
+#endif
 
 	if (dedicated)
-		return;
+		return false;
 
 	if (setrenderneeded)
 	{
@@ -1517,11 +1517,12 @@ void VID_CheckRenderer(void)
 
 			// Initialise OpenGL before calling SDLSetMode!!!
 			// This is because SDLSetMode calls OglSdlSurface.
-			if (vid_opengl_state == 0)
+			if (vid.glstate == VID_GL_LIBRARY_NOTLOADED)
 			{
 				VID_StartupOpenGL();
+
 				// Loaded successfully!
-				if (vid_opengl_state == 1)
+				if (vid.glstate == VID_GL_LIBRARY_LOADED)
 				{
 					// Destroy the current window, if it exists.
 					if (window)
@@ -1544,7 +1545,7 @@ void VID_CheckRenderer(void)
 					contextcreated = true;
 				}
 			}
-			else if (vid_opengl_state == -1)
+			else if (vid.glstate == VID_GL_LIBRARY_ERROR)
 				rendererchanged = false;
 		}
 #endif
@@ -1555,7 +1556,7 @@ void VID_CheckRenderer(void)
 		setrenderneeded = 0;
 	}
 
-	SDLSetMode(vid.width, vid.height, USE_FULLSCREEN, (rendererchanged ? SDL_FALSE : SDL_TRUE));
+	SDLSetMode(vid.width, vid.height, USE_FULLSCREEN, (setmodeneeded ? SDL_TRUE : SDL_FALSE));
 	Impl_VideoSetupBuffer();
 
 	if (rendermode == render_soft)
@@ -1566,27 +1567,17 @@ void VID_CheckRenderer(void)
 			bufSurface = NULL;
 		}
 
-		if (rendererchanged)
-		{
-#ifdef HWRENDER
-			if (vid_opengl_state == 1) // Only if OpenGL ever loaded!
-				HWR_FreeTextureCache();
-#endif
-			SCR_SetDrawFuncs();
-		}
+		SCR_SetDrawFuncs();
 	}
 #ifdef HWRENDER
-	else if (rendermode == render_opengl)
+	else if (rendermode == render_opengl && rendererchanged)
 	{
-		if (rendererchanged)
-		{
-			R_InitHardwareMode();
-			V_SetPalette(0);
-		}
+		HWR_Switch();
+		V_SetPalette(0);
 	}
-#else
-	(void)oldrenderer;
 #endif
+
+	return rendererchanged;
 }
 
 INT32 VID_SetMode(INT32 modeNum)
@@ -1627,7 +1618,7 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 		flags |= SDL_WINDOW_BORDERLESS;
 
 #ifdef HWRENDER
-	if (vid_opengl_state == 1)
+	if (vid.glstate == VID_GL_LIBRARY_LOADED)
 		flags |= SDL_WINDOW_OPENGL;
 #endif
 
@@ -1661,7 +1652,7 @@ static void Impl_SetWindowName(const char *title)
 static void Impl_SetWindowIcon(void)
 {
 	if (window && icoSurface)
-		SDL_SetWindowIcon(window, icoSurface);	
+		SDL_SetWindowIcon(window, icoSurface);
 }
 
 static void Impl_VideoSetupSDLBuffer(void)
@@ -1748,12 +1739,44 @@ void I_StartupGraphics(void)
 			framebuffer = SDL_TRUE;
 	}
 
-#ifdef HWRENDER
-	if (M_CheckParm("-opengl"))
-		chosenrendermode = rendermode = render_opengl;
+	// Renderer choices
+	// Takes priority over the config.
+	if (M_CheckParm("-renderer"))
+	{
+		INT32 i = 0;
+		CV_PossibleValue_t *renderer_list = cv_renderer_t;
+		const char *modeparm = M_GetNextParm();
+		while (renderer_list[i].strvalue)
+		{
+			if (!stricmp(modeparm, renderer_list[i].strvalue))
+			{
+				chosenrendermode = renderer_list[i].value;
+				break;
+			}
+			i++;
+		}
+	}
+
+	// Choose Software renderer
 	else if (M_CheckParm("-software"))
+		chosenrendermode = render_soft;
+
+#ifdef HWRENDER
+	// Choose OpenGL renderer
+	else if (M_CheckParm("-opengl"))
+		chosenrendermode = render_opengl;
+
+	// Don't startup OpenGL
+	if (M_CheckParm("-nogl"))
+	{
+		vid.glstate = VID_GL_LIBRARY_ERROR;
+		if (chosenrendermode == render_opengl)
+			chosenrendermode = render_none;
+	}
 #endif
-		chosenrendermode = rendermode = render_soft;
+
+	if (chosenrendermode != render_none)
+		rendermode = chosenrendermode;
 
 	usesdl2soft = M_CheckParm("-softblit");
 	borderlesswindow = M_CheckParm("-borderless");
@@ -1762,16 +1785,14 @@ void I_StartupGraphics(void)
 	VID_Command_ModeList_f();
 
 #ifdef HWRENDER
-	if (M_CheckParm("-nogl"))
-		vid_opengl_state = -1; // Don't startup OpenGL
-	else if (chosenrendermode == render_opengl)
+	if (rendermode == render_opengl)
 		VID_StartupOpenGL();
 #endif
 
 	// Window icon
 #ifdef HAVE_IMAGE
 	icoSurface = IMG_ReadXPMFromArray(SDL_icon_xpm);
-#endif	
+#endif
 
 	// Fury: we do window initialization after GL setup to allow
 	// SDL_GL_LoadLibrary to work well on Windows
@@ -1837,6 +1858,7 @@ void VID_StartupOpenGL(void)
 		HWD.pfnClearBuffer      = hwSym("ClearBuffer",NULL);
 		HWD.pfnSetTexture       = hwSym("SetTexture",NULL);
 		HWD.pfnUpdateTexture    = hwSym("UpdateTexture",NULL);
+		HWD.pfnDeleteTexture    = hwSym("DeleteTexture",NULL);
 		HWD.pfnReadRect         = hwSym("ReadRect",NULL);
 		HWD.pfnGClipRect        = hwSym("GClipRect",NULL);
 		HWD.pfnClearMipMapCache = hwSym("ClearMipMapCache",NULL);
@@ -1856,18 +1878,17 @@ void VID_StartupOpenGL(void)
 		HWD.pfnMakeScreenFinalTexture=hwSym("MakeScreenFinalTexture",NULL);
 		HWD.pfnDrawScreenFinalTexture=hwSym("DrawScreenFinalTexture",NULL);
 
-		HWD.pfnLoadShaders      = hwSym("LoadShaders",NULL);
-		HWD.pfnKillShaders      = hwSym("KillShaders",NULL);
+		HWD.pfnCompileShaders   = hwSym("CompileShaders",NULL);
+		HWD.pfnCleanShaders     = hwSym("CleanShaders",NULL);
 		HWD.pfnSetShader        = hwSym("SetShader",NULL);
 		HWD.pfnUnSetShader      = hwSym("UnSetShader",NULL);
 
 		HWD.pfnSetShaderInfo    = hwSym("SetShaderInfo",NULL);
 		HWD.pfnLoadCustomShader = hwSym("LoadCustomShader",NULL);
-		HWD.pfnInitCustomShaders= hwSym("InitCustomShaders",NULL);
 
-		vid_opengl_state = HWD.pfnInit() ? 1 : -1; // let load the OpenGL library
+		vid.glstate = HWD.pfnInit() ? VID_GL_LIBRARY_LOADED : VID_GL_LIBRARY_ERROR; // let load the OpenGL library
 
-		if (vid_opengl_state == -1)
+		if (vid.glstate == VID_GL_LIBRARY_ERROR)
 		{
 			rendermode = render_soft;
 			setrenderneeded = 0;
diff --git a/src/sdl/macosx/Srb2mac.icns b/src/sdl/macosx/Srb2mac.icns
index a3e37aab3ee846900a873610f7d8d66fd34bfda3..2ac2faf33e1ec211c5648e417befedce9b1eece2 100644
Binary files a/src/sdl/macosx/Srb2mac.icns and b/src/sdl/macosx/Srb2mac.icns differ
diff --git a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
index 5ceb0540811715f8ed89c200bf4d3b8b5210bdb3..909bb2ced72d7f3cc7a157d87fea0eac37dfd178 100644
--- a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
@@ -1064,13 +1064,6 @@
 			path = ../../hardware/hw_drv.h;
 			refType = 2;
 		};
-		8417773B085A106C000C01D8 = {
-			fileEncoding = 30;
-			isa = PBXFileReference;
-			name = hw_glide.h;
-			path = ../../hardware/hw_glide.h;
-			refType = 2;
-		};
 		8417773C085A106C000C01D8 = {
 			fileEncoding = 30;
 			isa = PBXFileReference;
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index eeae1c2de7518e8c2d6678a06adcd09d7b84b9aa..04f8ecc0aaea916da6795d9841caa8451bd3397c 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -180,7 +180,6 @@
 		1E44AE640B67CC2B00BAD059 /* hw_cache.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = hw_cache.c; path = ../../hardware/hw_cache.c; sourceTree = SOURCE_ROOT; };
 		1E44AE650B67CC2B00BAD059 /* hw_dll.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hw_dll.h; path = ../../hardware/hw_dll.h; sourceTree = SOURCE_ROOT; };
 		1E44AE660B67CC2B00BAD059 /* hw_drv.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hw_drv.h; path = ../../hardware/hw_drv.h; sourceTree = SOURCE_ROOT; };
-		1E44AE670B67CC2B00BAD059 /* hw_glide.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hw_glide.h; path = ../../hardware/hw_glide.h; sourceTree = SOURCE_ROOT; };
 		1E44AE680B67CC2B00BAD059 /* hw_light.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = hw_light.c; path = ../../hardware/hw_light.c; sourceTree = SOURCE_ROOT; };
 		1E44AE690B67CC2B00BAD059 /* hw_light.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hw_light.h; path = ../../hardware/hw_light.h; sourceTree = SOURCE_ROOT; };
 		1E44AE6A0B67CC2B00BAD059 /* hw3sound.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = hw3sound.h; path = ../../hardware/hw3sound.h; sourceTree = SOURCE_ROOT; };
@@ -533,7 +532,6 @@
 				1E44AE640B67CC2B00BAD059 /* hw_cache.c */,
 				1E44AE650B67CC2B00BAD059 /* hw_dll.h */,
 				1E44AE660B67CC2B00BAD059 /* hw_drv.h */,
-				1E44AE670B67CC2B00BAD059 /* hw_glide.h */,
 				1E44AE680B67CC2B00BAD059 /* hw_light.c */,
 				1E44AE690B67CC2B00BAD059 /* hw_light.h */,
 				1E44AE6A0B67CC2B00BAD059 /* hw3sound.h */,
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 25e7fde8981c01755b7a77972b1114db09730ef4..412a21ea0098415b46eea3565630f6f9ce99bfe8 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -196,9 +196,9 @@ static void MidiSoundfontPath_Onchange(void)
 // make sure that s_sound.c does not already verify these
 // which happens when: defined(HAVE_MIXERX) && !defined(HAVE_MIXER)
 static CV_PossibleValue_t midiplayer_cons_t[] = {{MIDI_OPNMIDI, "OPNMIDI"}, {MIDI_Fluidsynth, "Fluidsynth"}, {MIDI_Timidity, "Timidity"}, {MIDI_Native, "Native"}, {0, NULL}};
-consvar_t cv_midiplayer = {"midiplayer", "OPNMIDI" /*MIDI_OPNMIDI*/, CV_CALL|CV_NOINIT|CV_SAVE, midiplayer_cons_t, Midiplayer_Onchange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_midisoundfontpath = {"midisoundfont", "sf2/8bitsf.SF2", CV_CALL|CV_NOINIT|CV_SAVE, NULL, MidiSoundfontPath_Onchange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_miditimiditypath = {"midisoundbank", "./timidity", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_midiplayer = CVAR_INIT ("midiplayer", "OPNMIDI" /*MIDI_OPNMIDI*/, CV_CALL|CV_NOINIT|CV_SAVE, midiplayer_cons_t, Midiplayer_Onchange);
+consvar_t cv_midisoundfontpath = CVAR_INIT ("midisoundfont", "sf2/8bitsf.SF2", CV_CALL|CV_NOINIT|CV_SAVE, NULL, MidiSoundfontPath_Onchange);
+consvar_t cv_miditimiditypath = CVAR_INIT ("midisoundbank", "./timidity", CV_SAVE, NULL, NULL);
 #endif
 
 static void var_cleanup(void)
@@ -1298,6 +1298,10 @@ boolean I_PlaySong(boolean looping)
 	if (gme)
 	{
 		gme_equalizer_t eq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
+#if defined (GME_VERSION) && GME_VERSION >= 0x000603
+		if (looping)
+			gme_set_autoload_playback_limit(gme, 0);
+#endif        
 		gme_set_equalizer(gme, &eq);
 		gme_start_track(gme, 0);
 		current_track = 0;
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index edc69b21d5067c264bf16e6df8739fac8b0dced9..52727c05600a5f33221e729d51263b08fcdde30b 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -167,6 +167,18 @@ boolean OglSdlSurface(INT32 w, INT32 h)
 		GL_DBG_Printf("OpenGL %s\n", gl_version);
 		GL_DBG_Printf("GPU: %s\n", gl_renderer);
 		GL_DBG_Printf("Extensions: %s\n", gl_extensions);
+
+		if (strcmp((const char*)gl_renderer, "GDI Generic") == 0 &&
+			strcmp((const char*)gl_version, "1.1.0") == 0)
+		{
+			// Oh no... Windows gave us the GDI Generic rasterizer, so something is wrong...
+			// The game will crash later on when unsupported OpenGL commands are encountered.
+			// Instead of a nondescript crash, show a more informative error message.
+			// Also set the renderer variable back to software so the next launch won't
+			// repeat this error.
+			CV_StealthSet(&cv_renderer, "Software");
+			I_Error("OpenGL Error: Failed to access the GPU. There may be an issue with your graphics drivers.");
+		}
 	}
 	first_init = true;
 
@@ -177,7 +189,7 @@ boolean OglSdlSurface(INT32 w, INT32 h)
 
 	SetupGLFunc4();
 
-	granisotropicmode_cons_t[1].value = maximumAnisotropy;
+	glanisotropicmode_cons_t[1].value = maximumAnisotropy;
 
 	SDL_GL_SetSwapInterval(cv_vidwait.value ? 1 : 0);
 
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 53d98891339d22c41bb4152667e33e671e0e33e0..64964462094e8ce837da9c07f5a74209d85761f9 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -203,9 +203,7 @@ void ST_doPaletteStuff(void)
 {
 	INT32 palette;
 
-	if (paused || P_AutoPause())
-		palette = 0;
-	else if (stplyr && stplyr->flashcount)
+	if (stplyr && stplyr->flashcount)
 		palette = stplyr->flashpal;
 	else
 		palette = 0;
@@ -215,8 +213,6 @@ void ST_doPaletteStuff(void)
 		palette = 0; // No flashpals here in OpenGL
 #endif
 
-	palette = min(max(palette, 0), 13);
-
 	if (palette != st_palette)
 	{
 		st_palette = palette;
@@ -232,7 +228,7 @@ void ST_doPaletteStuff(void)
 
 void ST_UnloadGraphics(void)
 {
-	Z_FreeTag(PU_HUDGFX);
+	Patch_FreeTag(PU_HUDGFX);
 }
 
 void ST_LoadGraphics(void)
@@ -240,8 +236,8 @@ void ST_LoadGraphics(void)
 	int i;
 
 	// SRB2 border patch
-	st_borderpatchnum = W_GetNumForName("GFZFLR01");
-	scr_borderpatch = W_CacheLumpNum(st_borderpatchnum, PU_HUDGFX);
+	// 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
@@ -458,7 +454,7 @@ boolean st_overlay;
 static void ST_DrawNightsOverlayNum(fixed_t x /* right border */, fixed_t y, fixed_t s, INT32 a,
 	UINT32 num, patch_t **numpat, skincolornum_t colornum)
 {
-	fixed_t w = SHORT(numpat[0]->width)*s;
+	fixed_t w = numpat[0]->width * s;
 	const UINT8 *colormap;
 
 	// I want my V_SNAPTOx flags. :< -Red
@@ -676,7 +672,7 @@ static void ST_drawRaceNum(INT32 time)
 		if (!(P_AutoPause() || paused) && !bounce)
 				S_StartSound(0, ((racenum == racego) ? sfx_s3kad : sfx_s3ka7));
 	}
-	V_DrawScaledPatch(((BASEVIDWIDTH - SHORT(racenum->width))/2), height, V_PERPLAYER, racenum);
+	V_DrawScaledPatch(((BASEVIDWIDTH - racenum->width)/2), height, V_PERPLAYER, racenum);
 }
 
 static void ST_drawTime(void)
@@ -1126,7 +1122,7 @@ static void ST_drawInput(void)
 	V_DrawCharacter(x+16+1+(xoffs), y+1+(yoffs)-offs, hudinfo[HUD_LIVES].f|symb, false)
 
 	drawbutt( 4,-3, BT_JUMP, 'J');
-	drawbutt(15,-3, BT_USE,  'S');
+	drawbutt(15,-3, BT_SPIN, 'S');
 
 	V_DrawFill(x+16+4, y+8, 21, 10, hudinfo[HUD_LIVES].f|20); // sundial backing
 	if (stplyr->mo)
@@ -1625,8 +1621,8 @@ static void ST_drawFirstPersonHUD(void)
 	p = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
 
 	// Display the countdown drown numbers!
-	if (p && !F_GetPromptHideHud(60 - SHORT(p->topoffset)))
-		V_DrawScaledPatch((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset), 60 - SHORT(p->topoffset),
+	if (p && !F_GetPromptHideHud(60 - p->topoffset))
+		V_DrawScaledPatch((BASEVIDWIDTH/2) - (p->width / 2) + SHORT(p->leftoffset), 60 - SHORT(p->topoffset),
 			V_PERPLAYER|V_PERPLAYER|V_TRANSLUCENT, p);
 }
 
@@ -2080,21 +2076,21 @@ static void ST_drawNiGHTSHUD(void)
 			if (stplyr->powers[pw_nights_superloop])
 			{
 				pwr = stplyr->powers[pw_nights_superloop];
-				V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_CACHE));
+				V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_SPRITE));
 				V_DrawThinString(106, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 			}
 
 			if (stplyr->powers[pw_nights_helper])
 			{
 				pwr = stplyr->powers[pw_nights_helper];
-				V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_CACHE));
+				V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_SPRITE));
 				V_DrawThinString(146, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 			}
 
 			if (stplyr->powers[pw_nights_linkfreeze])
 			{
 				pwr = stplyr->powers[pw_nights_linkfreeze];
-				V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_CACHE));
+				V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_SPRITE));
 				V_DrawThinString(186, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 			}
 		}
@@ -2195,7 +2191,7 @@ static void ST_drawMatchHUD(void)
 		{
 			sprintf(penaltystr, "-%d", stplyr->ammoremoval);
 			V_DrawString(offset + 8 + stplyr->ammoremovalweapon * 20, y,
-					V_REDMAP, penaltystr);
+				V_REDMAP|V_SNAPTOBOTTOM, penaltystr);
 		}
 
 	}
@@ -2379,7 +2375,7 @@ static void ST_drawTeamHUD(void)
 		p = bmatcico;
 
 	if (LUA_HudEnabled(hud_teamscores))
-		V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
+		V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - (p->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
 
 	if (gametyperules & GTR_TEAMFLAGS)
 		p = rflagico;
@@ -2387,7 +2383,7 @@ static void ST_drawTeamHUD(void)
 		p = rmatcico;
 
 	if (LUA_HudEnabled(hud_teamscores))
-		V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
+		V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - (p->width / 4), 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
 
 	if (!(gametyperules & GTR_TEAMFLAGS))
 		goto num;
@@ -2400,11 +2396,11 @@ static void ST_drawTeamHUD(void)
 		{
 			// Blue flag isn't at base
 			if (players[i].gotflag & GF_BLUEFLAG && LUA_HudEnabled(hud_teamscores))
-				V_DrawScaledPatch(BASEVIDWIDTH/2 - SEP - SHORT(nonicon->width)/2, 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon);
+				V_DrawScaledPatch(BASEVIDWIDTH/2 - SEP - (nonicon->width / 2), 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon);
 
 			// Red flag isn't at base
 			if (players[i].gotflag & GF_REDFLAG && LUA_HudEnabled(hud_teamscores))
-				V_DrawScaledPatch(BASEVIDWIDTH/2 + SEP - SHORT(nonicon2->width)/2, 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon2);
+				V_DrawScaledPatch(BASEVIDWIDTH/2 + SEP - (nonicon2->width / 2), 0, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, nonicon2);
 
 			whichflag |= players[i].gotflag;
 
@@ -2677,7 +2673,7 @@ static void ST_overlayDrawer(void)
 			else
 			{
 				tic_t num = time;
-				INT32 sz = SHORT(tallnum[0]->width)/2, width = 0;
+				INT32 sz = tallnum[0]->width / 2, width = 0;
 				do
 				{
 					width += sz;
@@ -2755,10 +2751,6 @@ static void ST_overlayDrawer(void)
 
 void ST_Drawer(void)
 {
-	if (needpatchrecache)
-		R_ReloadHUDGraphics();
-
-#ifdef SEENAMES
 	if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo)
 	{
 		INT32 c = 0;
@@ -2782,7 +2774,6 @@ void ST_Drawer(void)
 
 		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF|c, player_names[seenplayer-players]);
 	}
-#endif
 
 	// Doom's status bar only updated if necessary.
 	// However, ours updates every frame regardless, so the "refresh" param was removed
diff --git a/src/taglist.c b/src/taglist.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e69db32422fd093f4dfc81b973874a9ace28ba9
--- /dev/null
+++ b/src/taglist.c
@@ -0,0 +1,380 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by Nev3r.
+//
+// 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  taglist.c
+/// \brief Ingame sector/line/mapthing tagging.
+
+#include "taglist.h"
+#include "z_zone.h"
+#include "r_data.h"
+
+taggroup_t** tags_sectors;
+taggroup_t** tags_lines;
+taggroup_t** tags_mapthings;
+
+/// Adds a tag to a given element's taglist.
+/// \warning This does not rebuild the global taggroups, which are used for iteration.
+void Tag_Add (taglist_t* list, const mtag_t tag)
+{
+	list->tags = Z_Realloc(list->tags, (list->count + 1) * sizeof(list->tags), PU_LEVEL, NULL);
+	list->tags[list->count++] = tag;
+}
+
+/// Sets the first tag entry in a taglist.
+/// Replicates the old way of accessing element->tag.
+void Tag_FSet (taglist_t* list, const mtag_t tag)
+{
+	if (!list->count)
+	{
+		Tag_Add(list, tag);
+		return;
+	}
+
+	list->tags[0] = tag;
+}
+
+/// Gets the first tag entry in a taglist.
+/// Replicates the old way of accessing element->tag.
+mtag_t Tag_FGet (const taglist_t* list)
+{
+	if (list->count)
+		return list->tags[0];
+
+	return 0;
+}
+
+/// Returns true if the given tag exist inside the list.
+boolean Tag_Find (const taglist_t* list, const mtag_t tag)
+{
+	size_t i;
+	for (i = 0; i < list->count; i++)
+		if (list->tags[i] == tag)
+			return true;
+
+	return false;
+}
+
+/// Returns true if at least one tag is shared between two given lists.
+boolean Tag_Share (const taglist_t* list1, const taglist_t* list2)
+{
+	size_t i;
+	for (i = 0; i < list1->count; i++)
+		if (Tag_Find(list2, list1->tags[i]))
+			return true;
+
+	return false;
+}
+
+/// Returns true if both lists are identical.
+boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2)
+{
+	size_t i;
+
+	if (list1->count != list2->count)
+		return false;
+
+	for (i = 0; i < list1->count; i++)
+		if (list1->tags[i] != list2->tags[i])
+			return false;
+
+	return true;
+}
+
+/// Search for an element inside a global taggroup.
+size_t Taggroup_Find (const taggroup_t *group, const size_t id)
+{
+	size_t i;
+
+	if (!group)
+		return -1;
+
+	for (i = 0; i < group->count; i++)
+		if (group->elements[i] == id)
+			return i;
+
+	return -1;
+}
+
+/// group->count, but also checks for NULL
+size_t Taggroup_Count (const taggroup_t *group)
+{
+	return group ? group->count : 0;
+}
+
+/// Iterate thru elements in a global taggroup.
+INT32 Taggroup_Iterate
+(		taggroup_t *garray[],
+		const size_t max_elements,
+		const mtag_t tag,
+		const size_t p)
+{
+	const taggroup_t *group;
+
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < max_elements)
+			return p;
+		return -1;
+	}
+
+	group = garray[(UINT16)tag];
+
+	if (group)
+	{
+		if (p < group->count)
+			return group->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
+/// Add an element to a global taggroup.
+void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
+{
+	taggroup_t *group;
+	size_t i; // Insert position.
+
+	if (tag == MTAG_GLOBAL)
+		return;
+
+	group = garray[(UINT16)tag];
+
+	// Don't add duplicate entries.
+	if (Taggroup_Find(group, id) != (size_t)-1)
+		return;
+
+	if (! in_bit_array(world->tags_available, tag))
+	{
+		world->num_tags++;
+		set_bit_array(world->tags_available, tag);
+	}
+
+	// Create group if empty.
+	if (!group)
+	{
+		i = 0;
+		group = garray[(UINT16)tag] = Z_Calloc(sizeof(taggroup_t), PU_LEVEL, NULL);
+	}
+	else
+	{
+		// Keep the group element ids in an ascending order.
+		// Find the location to insert the element to.
+		for (i = 0; i < group->count; i++)
+			if (group->elements[i] > id)
+				break;
+	}
+
+	group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL);
+
+	// Offset existing elements to make room for the new one.
+	if (i < group->count)
+		memmove(&group->elements[i + 1], &group->elements[i], group->count - i);
+
+	group->count++;
+	group->elements[i] = id;
+}
+
+static size_t total_elements_with_tag (const mtag_t tag)
+{
+	return
+		(
+				Taggroup_Count(world->tags_sectors[tag]) +
+				Taggroup_Count(world->tags_lines[tag]) +
+				Taggroup_Count(world->tags_mapthings[tag])
+		);
+}
+
+/// Remove an element from a global taggroup.
+void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
+{
+	taggroup_t *group;
+	size_t rempos;
+	size_t oldcount;
+
+	if (tag == MTAG_GLOBAL)
+		return;
+
+	group = garray[(UINT16)tag];
+
+	if ((rempos = Taggroup_Find(group, id)) == (size_t)-1)
+		return;
+
+	if (group->count == 1 && total_elements_with_tag(tag) == 1)
+	{
+		world->num_tags--;
+		unset_bit_array(world->tags_available, tag);
+	}
+
+	// Strip away taggroup if no elements left.
+	if (!(oldcount = group->count--))
+	{
+		Z_Free(group->elements);
+		Z_Free(group);
+		garray[(UINT16)tag] = NULL;
+	}
+	else
+	{
+		size_t *newelements = Z_Malloc(group->count * sizeof(size_t), PU_LEVEL, NULL);
+		size_t i;
+
+		// Copy the previous entries save for the one to remove.
+		for (i = 0; i < rempos; i++)
+			newelements[i] = group->elements[i];
+
+		for (i = rempos + 1; i < oldcount; i++)
+			newelements[i - 1] = group->elements[i];
+
+		Z_Free(group->elements);
+		group->elements = newelements;
+	}
+}
+
+// Initialization.
+
+static void Taglist_AddToSectors (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(world->tags_sectors, tag, itemid);
+}
+
+static void Taglist_AddToLines (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(world->tags_lines, tag, itemid);
+}
+
+static void Taglist_AddToMapthings (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(world->tags_mapthings, tag, itemid);
+}
+
+/// After all taglists have been built for each element (sectors, lines, things),
+/// the global taggroups, made for iteration, are built here.
+void Taglist_InitGlobalTables(void)
+{
+	size_t i, j;
+
+	memset(world->tags_available, 0, sizeof world->tags_available);
+	world->num_tags = 0;
+
+	for (i = 0; i < MAXTAGS; i++)
+	{
+		world->tags_sectors[i] = NULL;
+		world->tags_lines[i] = NULL;
+		world->tags_mapthings[i] = NULL;
+	}
+	for (i = 0; i < world->numsectors; i++)
+	{
+		for (j = 0; j < world->sectors[i].tags.count; j++)
+			Taglist_AddToSectors(world->sectors[i].tags.tags[j], i);
+	}
+	for (i = 0; i < world->numlines; i++)
+	{
+		for (j = 0; j < world->lines[i].tags.count; j++)
+			Taglist_AddToLines(world->lines[i].tags.tags[j], i);
+	}
+	for (i = 0; i < world->nummapthings; i++)
+	{
+		for (j = 0; j < world->mapthings[i].tags.count; j++)
+			Taglist_AddToMapthings(world->mapthings[i].tags.tags[j], i);
+	}
+}
+
+// Iteration, ingame search.
+
+INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p)
+{
+	return Taggroup_Iterate(world->tags_sectors, world->numsectors, tag, p);
+}
+
+INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p)
+{
+	return Taggroup_Iterate(world->tags_lines, world->numlines, tag, p);
+}
+
+INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p)
+{
+	return Taggroup_Iterate(world->tags_mapthings, world->nummapthings, tag, p);
+}
+
+INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag)
+{
+	size_t i;
+
+	if (tag == MTAG_GLOBAL)
+	{
+		for (i = 0; i < world->numlines; i++)
+			if (world->lines[i].special == special)
+				return i;
+	}
+	else if (world->tags_lines[(UINT16)tag])
+	{
+		taggroup_t *tagged = world->tags_lines[(UINT16)tag];
+		for (i = 0; i < tagged->count; i++)
+			if (world->lines[tagged->elements[i]].special == special)
+				return tagged->elements[i];
+	}
+	return -1;
+}
+
+/// Backwards compatibility iteration function for Lua scripts.
+INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
+{
+	if (tag == -1)
+	{
+		start++;
+
+		if (start >= (INT32)world->numlines)
+			return -1;
+
+		while (start < (INT32)world->numlines && world->lines[start].special != special)
+			start++;
+
+		return start;
+	}
+	else
+	{
+		size_t p = 0;
+		INT32 id;
+
+		// For backwards compatibility's sake, simulate the old linked taglist behavior:
+		// Iterate through the taglist and find the "start" line's position in the list,
+		// And start checking with the next one (if it exists).
+		if (start != -1)
+		{
+			for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++)
+				if (id == start)
+				{
+					p++;
+					break;
+				}
+		}
+
+		for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++)
+			if (world->lines[id].special == special)
+				return id;
+
+		return -1;
+	}
+}
+
+
+// Ingame list manipulation.
+
+/// Changes the first tag for a given sector, and updates the global taggroups.
+void Tag_SectorFSet (const size_t id, const mtag_t tag)
+{
+	sector_t* sec = &world->sectors[id];
+	mtag_t curtag = Tag_FGet(&sec->tags);
+	if (curtag == tag)
+		return;
+
+	Taggroup_Remove(world->tags_sectors, curtag, id);
+	Taggroup_Add(world->tags_sectors, tag, id);
+	Tag_FSet(&sec->tags, tag);
+}
diff --git a/src/taglist.h b/src/taglist.h
new file mode 100644
index 0000000000000000000000000000000000000000..a1398f6f3b0c17d81683c738e2645605441da8e8
--- /dev/null
+++ b/src/taglist.h
@@ -0,0 +1,134 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by Nev3r.
+//
+// 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  taglist.h
+/// \brief Tag iteration and reading functions and macros' declarations.
+
+#ifndef __R_TAGLIST__
+#define __R_TAGLIST__
+
+#include "doomtype.h"
+
+typedef INT16 mtag_t;
+#define MAXTAGS UINT16_MAX
+#define MTAG_GLOBAL -1
+
+/// Multitag list. Each taggable element will have its own taglist.
+typedef struct
+{
+	mtag_t* tags;
+	UINT16 count;
+} taglist_t;
+
+void Tag_Add (taglist_t* list, const mtag_t tag);
+void Tag_FSet (taglist_t* list, const mtag_t tag);
+mtag_t Tag_FGet (const taglist_t* list);
+boolean Tag_Find (const taglist_t* list, const mtag_t tag);
+boolean Tag_Share (const taglist_t* list1, const taglist_t* list2);
+boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2);
+
+void Tag_SectorFSet (const size_t id, const mtag_t tag);
+
+/// Taggroup list. It is essentially just an element id list.
+typedef struct
+{
+	size_t *elements;
+	size_t count;
+} taggroup_t;
+
+extern taggroup_t** tags_sectors;
+extern taggroup_t** tags_lines;
+extern taggroup_t** tags_mapthings;
+
+void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id);
+void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id);
+size_t Taggroup_Find (const taggroup_t *group, const size_t id);
+size_t Taggroup_Count (const taggroup_t *group);
+
+INT32 Taggroup_Iterate
+(		taggroup_t *garray[],
+		const size_t max_elements,
+		const mtag_t tag,
+		const size_t p);
+
+void Taglist_InitGlobalTables(void);
+
+INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p);
+INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p);
+INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p);
+
+INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag);
+INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
+
+// Use this macro to declare an iterator position variable.
+#define TAG_ITER_DECLARECOUNTER(level) size_t ICNT_##level
+
+#define TAG_ITER(level, fn, tag, return_varname) for(ICNT_##level = 0; (return_varname = fn(tag, ICNT_##level)) >= 0; ICNT_##level++)
+
+// Use these macros as wrappers for a taglist iteration.
+#define TAG_ITER_SECTORS(level, tag, return_varname) TAG_ITER(level, Tag_Iterate_Sectors, tag, return_varname)
+#define TAG_ITER_LINES(level, tag, return_varname)   TAG_ITER(level, Tag_Iterate_Lines, tag, return_varname)
+#define TAG_ITER_THINGS(level, tag, return_varname)  TAG_ITER(level, Tag_Iterate_Things, tag, return_varname)
+
+/* ITERATION MACROS
+TAG_ITER_DECLARECOUNTER must be used before using the iterators.
+
+'level':
+For each nested iteration, an additional TAG_ITER_DECLARECOUNTER
+must be used with a different level number to avoid conflict with
+the outer iterations.
+Most cases don't have nested iterations and thus the level is just 0.
+
+'tag':
+Pretty much the elements' tag to iterate through.
+
+'return_varname':
+Target variable's name to return the iteration results to.
+
+
+EXAMPLE:
+{
+	TAG_ITER_DECLARECOUNTER(0);
+	TAG_ITER_DECLARECOUNTER(1); // For the nested iteration.
+
+	size_t li;
+	size_t sec;
+
+	INT32 tag1 = 4;
+
+	...
+
+	TAG_ITER_LINES(0, tag1, li)
+	{
+		line_t *line = lines + li;
+
+		...
+
+		if (something)
+		{
+			mtag_t tag2 = 8;
+
+			// Nested iteration; just make sure the level is higher
+			// and that it has its own counter declared in scope.
+			TAG_ITER_SECTORS(1, tag2, sec)
+			{
+				sector_t *sector = sectors + sec;
+
+				...
+			}
+		}
+	}
+}
+
+Notes:
+If no elements are found for a given tag, the loop inside won't be executed.
+*/
+
+#endif //__R_TAGLIST__
diff --git a/src/tmap.nas b/src/tmap.nas
index 106f38e962b03fef0f0edc9e93dbd71112449ced..69282d0b471dd2c86802df544f4a346e4b96baa9 100644
--- a/src/tmap.nas
+++ b/src/tmap.nas
@@ -763,8 +763,8 @@ TX2             EQU    16
 TY2             EQU    20
 RASTERY_SIZEOF  EQU    24
 
-cglobal rasterize_segment_tex
-rasterize_segment_tex:
+cglobal rasterize_segment_tex_asm
+rasterize_segment_tex_asm:
         push    ebp
         mov     ebp,esp
 
diff --git a/src/v_video.c b/src/v_video.c
index 568997844238f1be5827ce094c65fb4b1cccd48f..8bd4c422e7b9f6411a16c2d3670539921802a243 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -43,47 +43,45 @@ UINT8 *screens[5];
 // screens[4] = fade screen end, postimage tempoarary buffer
 
 static CV_PossibleValue_t ticrate_cons_t[] = {{0, "No"}, {1, "Full"}, {2, "Compact"}, {0, NULL}};
-consvar_t cv_ticrate = {"showfps", "No", CV_SAVE, ticrate_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_ticrate = CVAR_INIT ("showfps", "No", CV_SAVE, ticrate_cons_t, NULL);
 
 static void CV_palette_OnChange(void);
 
 static CV_PossibleValue_t gamma_cons_t[] = {{-15, "MIN"}, {5, "MAX"}, {0, NULL}};
-consvar_t cv_globalgamma = {"gamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_globalgamma = CVAR_INIT ("gamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange);
 
 static CV_PossibleValue_t saturation_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
-consvar_t cv_globalsaturation = {"saturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_globalsaturation = CVAR_INIT ("saturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange);
 
 #define huecoloursteps 4
 
 static CV_PossibleValue_t hue_cons_t[] = {{0, "MIN"}, {(huecoloursteps*6)-1, "MAX"}, {0, NULL}};
-consvar_t cv_rhue = {"rhue",  "0", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_yhue = {"yhue",  "4", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ghue = {"ghue",  "8", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_chue = {"chue", "12", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_bhue = {"bhue", "16", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mhue = {"mhue", "20", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_rgamma = {"rgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ygamma = {"ygamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ggamma = {"ggamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cgamma = {"cgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_bgamma = {"bgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mgamma = {"mgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_rsaturation = {"rsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_ysaturation = {"ysaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_gsaturation = {"gsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_csaturation = {"csaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_bsaturation = {"bsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_msaturation = {"msaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
-
-consvar_t cv_allcaps = {"allcaps", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_rhue = CVAR_INIT ("rhue",  "0", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange);
+consvar_t cv_yhue = CVAR_INIT ("yhue",  "4", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange);
+consvar_t cv_ghue = CVAR_INIT ("ghue",  "8", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange);
+consvar_t cv_chue = CVAR_INIT ("chue", "12", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange);
+consvar_t cv_bhue = CVAR_INIT ("bhue", "16", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange);
+consvar_t cv_mhue = CVAR_INIT ("mhue", "20", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange);
+
+consvar_t cv_rgamma = CVAR_INIT ("rgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange);
+consvar_t cv_ygamma = CVAR_INIT ("ygamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange);
+consvar_t cv_ggamma = CVAR_INIT ("ggamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange);
+consvar_t cv_cgamma = CVAR_INIT ("cgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange);
+consvar_t cv_bgamma = CVAR_INIT ("bgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange);
+consvar_t cv_mgamma = CVAR_INIT ("mgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange);
+
+consvar_t cv_rsaturation = CVAR_INIT ("rsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange);
+consvar_t cv_ysaturation = CVAR_INIT ("ysaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange);
+consvar_t cv_gsaturation = CVAR_INIT ("gsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange);
+consvar_t cv_csaturation = CVAR_INIT ("csaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange);
+consvar_t cv_bsaturation = CVAR_INIT ("bsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange);
+consvar_t cv_msaturation = CVAR_INIT ("msaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange);
 
 static CV_PossibleValue_t constextsize_cons_t[] = {
 	{V_NOSCALEPATCH, "Small"}, {V_SMALLSCALEPATCH, "Medium"}, {V_MEDSCALEPATCH, "Large"}, {0, "Huge"},
 	{0, NULL}};
 static void CV_constextsize_OnChange(void);
-consvar_t cv_constextsize = {"con_textsize", "Medium", CV_SAVE|CV_CALL, constextsize_cons_t, CV_constextsize_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_constextsize = CVAR_INIT ("con_textsize", "Medium", CV_SAVE|CV_CALL, constextsize_cons_t, CV_constextsize_OnChange);
 
 // local copy of the palette for V_GetColor()
 RGBA_t *pLocalPalette = NULL;
@@ -533,7 +531,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 	//if (rendermode != render_soft && !con_startup)		// Why?
 	if (rendermode == render_opengl)
 	{
-		HWR_DrawStretchyFixedPatch((GLPatch_t *)patch, x, y, pscale, vscale, scrn, colormap);
+		HWR_DrawStretchyFixedPatch(patch, x, y, pscale, vscale, scrn, colormap);
 		return;
 	}
 #endif
@@ -555,7 +553,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 
 		if (alphalevel)
 		{
-			v_translevel = transtables + ((alphalevel-1)<<FF_TRANSSHIFT);
+			v_translevel = R_GetTranslucencyTable(alphalevel);
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -603,13 +601,13 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 
 		// left offset
 		if (scrn & V_FLIP)
-			offsetx = FixedMul((SHORT(patch->width) - SHORT(patch->leftoffset))<<FRACBITS, pscale) + 1;
+			offsetx = FixedMul((patch->width - patch->leftoffset)<<FRACBITS, pscale) + 1;
 		else
-			offsetx = FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
+			offsetx = FixedMul(patch->leftoffset<<FRACBITS, pscale);
 
 		// top offset
 		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
-		offsety = FixedMul(SHORT(patch->topoffset)<<FRACBITS, vscale);
+		offsety = FixedMul(patch->topoffset<<FRACBITS, vscale);
 
 		if ((scrn & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
 		{
@@ -716,9 +714,9 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 		if (!(scrn & V_SCALEPATCHMASK))
 		{
 			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
+			if (x == 0 && patch->width == BASEVIDWIDTH && y == 0 && patch->height == BASEVIDHEIGHT)
 			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
+				column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[0]));
 				if (!column->topdelta)
 				{
 					source = (const UINT8 *)(column) + 3;
@@ -758,18 +756,18 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 
 	if (pscale != FRACUNIT) // scale width properly
 	{
-		pwidth = SHORT(patch->width)<<FRACBITS;
+		pwidth = patch->width<<FRACBITS;
 		pwidth = FixedMul(pwidth, pscale);
 		pwidth = FixedMul(pwidth, dupx<<FRACBITS);
 		pwidth >>= FRACBITS;
 	}
 	else
-		pwidth = SHORT(patch->width) * dupx;
+		pwidth = patch->width * dupx;
 
 	deststart = desttop;
 	destend = desttop + pwidth;
 
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, ++offx, desttop++)
+	for (col = 0; (col>>FRACBITS) < patch->width; col += colfrac, ++offx, desttop++)
 	{
 		INT32 topdelta, prevdelta = -1;
 		if (scrn & V_FLIP) // offx is measured from right edge instead of left
@@ -786,7 +784,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 			if (x+offx >= vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 				break;
 		}
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
+		column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS]));
 
 		while (column->topdelta != 0xff)
 		{
@@ -833,7 +831,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	//if (rendermode != render_soft && !con_startup)		// Not this again
 	if (rendermode == render_opengl)
 	{
-		HWR_DrawCroppedPatch((GLPatch_t*)patch,x,y,pscale,scrn,sx,sy,w,h);
+		HWR_DrawCroppedPatch(patch,x,y,pscale,scrn,sx,sy,w,h);
 		return;
 	}
 #endif
@@ -855,7 +853,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 
 		if (alphalevel)
 		{
-			v_translevel = transtables + ((alphalevel-1)<<FF_TRANSSHIFT);
+			v_translevel = R_GetTranslucencyTable(alphalevel);
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -866,8 +864,8 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	colfrac = FixedDiv(FRACUNIT, fdup);
 	rowfrac = FixedDiv(FRACUNIT, fdup);
 
-	y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
-	x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
+	y -= FixedMul(patch->topoffset<<FRACBITS, pscale);
+	x -= FixedMul(patch->leftoffset<<FRACBITS, pscale);
 
 	if (splitscreen && (scrn & V_PERPLAYER))
 	{
@@ -1002,14 +1000,14 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 		desttop += (y*vid.width) + x;
 	}
 
-	for (col = sx<<FRACBITS; (col>>FRACBITS) < SHORT(patch->width) && ((col>>FRACBITS) - sx) < w; col += colfrac, ++x, desttop++)
+	for (col = sx<<FRACBITS; (col>>FRACBITS) < patch->width && ((col>>FRACBITS) - sx) < w; col += colfrac, ++x, desttop++)
 	{
 		INT32 topdelta, prevdelta = -1;
 		if (x < 0) // don't draw off the left of the screen (WRAP PREVENTION)
 			continue;
 		if (x >= vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 			break;
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
+		column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS]));
 
 		while (column->topdelta != 0xff)
 		{
@@ -1504,7 +1502,7 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	// Jimita (12-04-2018)
 	if (alphalevel)
 	{
-		fadetable = ((UINT8 *)transtables + ((alphalevel-1)<<FF_TRANSSHIFT) + (c*256));
+		fadetable = R_GetTranslucencyTable(alphalevel) + (c*256);
 		for (;(--h >= 0) && dest < deststop; dest += vid.width)
 		{
 			u = 0;
@@ -1687,7 +1685,7 @@ void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, U
 
 	fadetable = ((color & 0xFF00) // Color is not palette index?
 		? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
-		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+		: ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade.
 	for (;(--h >= 0) && dest < deststop; dest += vid.width)
 	{
 		u = 0;
@@ -1800,7 +1798,7 @@ void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum)
 void V_DrawPatchFill(patch_t *pat)
 {
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	INT32 x, y, pw = SHORT(pat->width) * dupz, ph = SHORT(pat->height) * dupz;
+	INT32 x, y, pw = pat->width * dupz, ph = pat->height * dupz;
 
 	for (x = 0; x < vid.width; x += pw)
 	{
@@ -1833,7 +1831,7 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength)
 		? ((UINT8 *)(((color & 0x0F00) == 0x0A00) ? fadecolormap // Do fadecolormap fade.
 		: (((color & 0x0F00) == 0x0B00) ? fadecolormap + (256 * FADECOLORMAPROWS) // Do white fadecolormap fade.
 		: colormaps)) + strength*256) // Do COLORMAP fade.
-		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+		: ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade.
 		const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 		UINT8 *buf = screens[0];
 
@@ -1988,7 +1986,7 @@ void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed)
 	if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
 		return;
 
-	w = SHORT(hu_font[c]->width);
+	w = hu_font[c]->width;
 	if (x + w > vid.width)
 		return;
 
@@ -2015,7 +2013,7 @@ void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UI
 	if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
 		return;
 
-	w = (vid.width < 640 ) ? (SHORT(hu_font[c]->width)/2) : (SHORT(hu_font[c]->width));	// use normal sized characters if we're using a terribly low resolution.
+	w = (vid.width < 640 ) ? ((hu_font[c]->width / 2)) : (hu_font[c]->width);	// use normal sized characters if we're using a terribly low resolution.
 	if (x + w > vid.width)
 		return;
 
@@ -2122,6 +2120,9 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	switch (option & V_SPACINGMASK)
 	{
 		case V_MONOSPACE:
@@ -2174,10 +2175,10 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 		if (charwidth)
 		{
 			w = charwidth * dupx;
-			center = w/2 - SHORT(hu_font[c]->width)*dupx/2;
+			center = w/2 - hu_font[c]->width*dupx/2;
 		}
 		else
-			w = SHORT(hu_font[c]->width) * dupx;
+			w = hu_font[c]->width * dupx;
 
 		if (cx > scrwidth)
 			continue;
@@ -2235,6 +2236,9 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	charflags = (option & V_CHARCOLORMASK);
 
 	switch (option & V_SPACINGMASK)
@@ -2288,10 +2292,10 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 		if (charwidth)
 		{
 			w = charwidth * dupx;
-			center = w/2 - SHORT(hu_font[c]->width)*dupx/4;
+			center = w/2 - hu_font[c]->width*dupx/4;
 		}
 		else
-			w = SHORT(hu_font[c]->width) * dupx / 2;
+			w = hu_font[c]->width * dupx / 2;
 
 		if (cx > scrwidth)
 			continue;
@@ -2350,6 +2354,9 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	charflags = (option & V_CHARCOLORMASK);
 
 	switch (option & V_SPACINGMASK)
@@ -2403,7 +2410,7 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 		if (charwidth)
 			w = charwidth * dupx;
 		else
-			w = (SHORT(tny_font[c]->width) * dupx);
+			w = tny_font[c]->width * dupx;
 
 		if (cx > scrwidth)
 			continue;
@@ -2485,6 +2492,9 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	charflags = (option & V_CHARCOLORMASK);
 
 	switch (option & V_SPACINGMASK)
@@ -2539,10 +2549,10 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 		if (charwidth)
 		{
 			w = charwidth * dupx;
-			center = w/2 - SHORT(hu_font[c]->width)*(dupx/2);
+			center = w/2 - hu_font[c]->width*(dupx/2);
 		}
 		else
-			w = SHORT(hu_font[c]->width) * dupx;
+			w = hu_font[c]->width * dupx;
 
 		if ((cx>>FRACBITS) > scrwidth)
 			continue;
@@ -2598,6 +2608,9 @@ void V_DrawSmallStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *st
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	charflags = (option & V_CHARCOLORMASK);
 
 	switch (option & V_SPACINGMASK)
@@ -2652,10 +2665,10 @@ void V_DrawSmallStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *st
 		if (charwidth)
 		{
 			w = charwidth * dupx;
-			center = w/2 - SHORT(hu_font[c]->width)*(dupx/4);
+			center = w/2 - hu_font[c]->width*(dupx/4);
 		}
 		else
-			w = SHORT(hu_font[c]->width) * dupx / 2;
+			w = hu_font[c]->width * dupx / 2;
 
 		if ((cx>>FRACBITS) > scrwidth)
 			break;
@@ -2712,6 +2725,9 @@ void V_DrawThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *str
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	charflags = (option & V_CHARCOLORMASK);
 
 	switch (option & V_SPACINGMASK)
@@ -2766,10 +2782,10 @@ void V_DrawThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *str
 		if (charwidth)
 		{
 			w = charwidth * dupx;
-			center = w/2 - SHORT(tny_font[c]->width)*(dupx/2);
+			center = w/2 - tny_font[c]->width*(dupx/2);
 		}
 		else
-			w = SHORT(tny_font[c]->width) * dupx;
+			w = tny_font[c]->width * dupx;
 
 		if ((cx>>FRACBITS) > scrwidth)
 			break;
@@ -2826,6 +2842,9 @@ void V_DrawSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	charflags = (option & V_CHARCOLORMASK);
 
 	switch (option & V_SPACINGMASK)
@@ -2880,10 +2899,10 @@ void V_DrawSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char
 		if (charwidth)
 		{
 			w = FixedMul(charwidth, dupx);
-			center = w/2 - SHORT(tny_font[c]->width)*(dupx/4);
+			center = w/2 - tny_font[c]->width*(dupx/4);
 		}
 		else
-			w = SHORT(tny_font[c]->width) * dupx / 2;
+			w = tny_font[c]->width * dupx / 2;
 
 		if (cx > scrwidth)
 			break;
@@ -2916,10 +2935,10 @@ void V_DrawRightAlignedSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option
 // Draws a tallnum.  Replaces two functions in y_inter and st_stuff
 void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num)
 {
-	INT32 w = SHORT(tallnum[0]->width);
+	INT32 w = tallnum[0]->width;
 	boolean neg;
 
-	if (flags & V_NOSCALESTART)
+	if (flags & (V_NOSCALESTART|V_NOSCALEPATCH))
 		w *= vid.dupx;
 
 	if ((neg = num < 0))
@@ -2942,9 +2961,9 @@ void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num)
 // Does not handle negative numbers in a special way, don't try to feed it any.
 void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits)
 {
-	INT32 w = SHORT(tallnum[0]->width);
+	INT32 w = tallnum[0]->width;
 
-	if (flags & V_NOSCALESTART)
+	if (flags & (V_NOSCALESTART|V_NOSCALEPATCH))
 		w *= vid.dupx;
 
 	if (num < 0)
@@ -2997,6 +3016,9 @@ void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string)
 	else
 		dupx = dupy = 1;
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	for (;;)
 	{
 		c = *ch++;
@@ -3016,7 +3038,7 @@ void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string)
 			continue;
 		}
 
-		w = SHORT(cred_font[c]->width) * dupx;
+		w = cred_font[c]->width * dupx;
 		if ((cx>>FRACBITS) > scrwidth)
 			continue;
 
@@ -3054,6 +3076,9 @@ static void V_DrawNameTagLine(INT32 x, INT32 y, INT32 option, fixed_t scale, UIN
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	for (;;ch++)
 	{
 		if (!*ch)
@@ -3075,7 +3100,7 @@ static void V_DrawNameTagLine(INT32 x, INT32 y, INT32 option, fixed_t scale, UIN
 			continue;
 		}
 
-		w = FixedMul((SHORT(ntb_font[c]->width)+2 * dupx) * FRACUNIT, scale);
+		w = FixedMul(((ntb_font[c]->width)+2 * dupx) * FRACUNIT, scale);
 
 		if (FixedInt(cx) > scrwidth)
 			continue;
@@ -3216,7 +3241,7 @@ INT32 V_NameTagWidth(const char *string)
 		if (c < 0 || c >= NT_FONTSIZE || !ntb_font[c] || !nto_font[c])
 			w += 4;
 		else
-			w += SHORT(ntb_font[c]->width)+2;
+			w += (ntb_font[c]->width)+2;
 	}
 
 	return w;
@@ -3239,7 +3264,7 @@ INT32 V_CreditStringWidth(const char *string)
 		if (c < 0 || c >= CRED_FONTSIZE)
 			w += 16;
 		else
-			w += SHORT(cred_font[c]->width);
+			w += cred_font[c]->width;
 	}
 
 	return w;
@@ -3269,6 +3294,9 @@ void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string)
 		scrwidth -= left;
 	}
 
+	if (option & V_NOSCALEPATCH)
+		scrwidth *= vid.dupx;
+
 	for (;;ch++)
 	{
 		if (!*ch)
@@ -3294,7 +3322,7 @@ void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string)
 			continue;
 		}
 
-		w = SHORT(lt_font[c]->width) * dupx;
+		w = lt_font[c]->width * dupx;
 
 		if (cx > scrwidth)
 			continue;
@@ -3326,7 +3354,7 @@ INT32 V_LevelNameWidth(const char *string)
 		if (c < 0 || c >= LT_FONTSIZE || !lt_font[c])
 			w += 16;
 		else
-			w += SHORT(lt_font[c]->width);
+			w += lt_font[c]->width;
 	}
 
 	return w;
@@ -3345,8 +3373,8 @@ INT32 V_LevelNameHeight(const char *string)
 		if (c < 0 || c >= LT_FONTSIZE || !lt_font[c])
 			continue;
 
-		if (SHORT(lt_font[c]->height) > w)
-			w = SHORT(lt_font[c]->height);
+		if (lt_font[c]->height > w)
+			w = lt_font[c]->height;
 	}
 
 	return w;
@@ -3359,11 +3387,11 @@ INT16 V_LevelActNumWidth(UINT8 num)
 	INT16 result = 0;
 
 	if (num == 0)
-		result = SHORT(ttlnum[num]->width);
+		result = ttlnum[num]->width;
 
 	while (num > 0 && num <= 99)
 	{
-		result = result + SHORT(ttlnum[num%10]->width);
+		result = result + ttlnum[num%10]->width;
 		num = num/10;
 	}
 
@@ -3401,11 +3429,11 @@ INT32 V_StringWidth(const char *string, INT32 option)
 		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
 			w += spacewidth;
 		else
-			w += (charwidth ? charwidth : SHORT(hu_font[c]->width));
+			w += (charwidth ? charwidth : hu_font[c]->width);
 	}
 
-	if (option & V_NOSCALESTART)
-	w *= vid.dupx;
+	if (option & (V_NOSCALESTART|V_NOSCALEPATCH))
+		w *= vid.dupx;
 
 	return w;
 }
@@ -3441,7 +3469,7 @@ INT32 V_SmallStringWidth(const char *string, INT32 option)
 		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
 			w += spacewidth;
 		else
-			w += (charwidth ? charwidth : SHORT(hu_font[c]->width)/2);
+			w += (charwidth ? charwidth : (hu_font[c]->width / 2));
 	}
 
 	return w;
@@ -3478,7 +3506,7 @@ INT32 V_ThinStringWidth(const char *string, INT32 option)
 		if (c < 0 || c >= HU_FONTSIZE || !tny_font[c])
 			w += spacewidth;
 		else
-			w += (charwidth ? charwidth : SHORT(tny_font[c]->width));
+			w += (charwidth ? charwidth : tny_font[c]->width);
 	}
 
 	return w;
@@ -3539,7 +3567,7 @@ void V_DoPostProcessor(INT32 view, postimg_t type, INT32 param)
 		angle_t disStart = (leveltime * 128) & FINEMASK; // in 0 to FINEANGLE
 		INT32 newpix;
 		INT32 sine;
-		//UINT8 *transme = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+		//UINT8 *transme = R_GetTranslucencyTable(tr_trans50);
 
 		for (y = yoffset; y < yoffset+height; y++)
 		{
@@ -3596,7 +3624,7 @@ Unoptimized version
 		INT32 x, y;
 
 		// TODO: Add a postimg_param so that we can pick the translucency level...
-		UINT8 *transme = transtables + ((param-1)<<FF_TRANSSHIFT);
+		UINT8 *transme = R_GetTranslucencyTable(param);
 
 		for (y = yoffset; y < yoffset+height; y++)
 		{
@@ -3668,28 +3696,51 @@ Unoptimized version
 #endif
 }
 
-// Generates a color look-up table
-// which has up to 64 colors at each channel
-// (see the defines in v_video.h)
-
-UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
-
-void InitColorLUT(RGBA_t *palette)
+// Generates a RGB565 color look-up table
+void InitColorLUT(colorlookup_t *lut, RGBA_t *palette, boolean makecolors)
 {
-	UINT8 r, g, b;
-	static boolean clutinit = false;
-	static RGBA_t *lastpalette = NULL;
-	if ((!clutinit) || (lastpalette != palette))
+	size_t palsize = (sizeof(RGBA_t) * 256);
+
+	if (!lut->init || memcmp(lut->palette, palette, palsize))
 	{
-		for (r = 0; r < CLUTSIZE; r++)
-			for (g = 0; g < CLUTSIZE; g++)
-				for (b = 0; b < CLUTSIZE; b++)
-					colorlookup[r][g][b] = NearestPaletteColor(r << SHIFTCOLORBITS, g << SHIFTCOLORBITS, b << SHIFTCOLORBITS, palette);
-		clutinit = true;
-		lastpalette = palette;
+		INT32 i;
+
+		lut->init = true;
+		memcpy(lut->palette, palette, palsize);
+
+		for (i = 0; i < 0xFFFF; i++)
+			lut->table[i] = 0xFFFF;
+
+		if (makecolors)
+		{
+			UINT8 r, g, b;
+
+			for (r = 0; r < 0xFF; r++)
+			for (g = 0; g < 0xFF; g++)
+			for (b = 0; b < 0xFF; b++)
+			{
+				i = CLUTINDEX(r, g, b);
+				if (lut->table[i] == 0xFFFF)
+					lut->table[i] = NearestPaletteColor(r, g, b, palette);
+			}
+		}
 	}
 }
 
+UINT8 GetColorLUT(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b)
+{
+	INT32 i = CLUTINDEX(r, g, b);
+	if (lut->table[i] == 0xFFFF)
+		lut->table[i] = NearestPaletteColor(r, g, b, lut->palette);
+	return lut->table[i];
+}
+
+UINT8 GetColorLUTDirect(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b)
+{
+	INT32 i = CLUTINDEX(r, g, b);
+	return lut->table[i];
+}
+
 // V_Init
 // old software stuff, buffers are allocated at video mode setup
 // here we set the screens[x] pointers accordingly
@@ -3721,3 +3772,36 @@ void V_Init(void)
 		CONS_Debug(DBG_RENDER, " screens[%d] = %x\n", i, screens[i]);
 #endif
 }
+
+void V_Recalc(void)
+{
+	// scale 1,2,3 times in x and y the patches for the menus and overlays...
+	// calculated once and for all, used by routines in v_video.c and v_draw.c
+	vid.dupx = vid.width / BASEVIDWIDTH;
+	vid.dupy = vid.height / BASEVIDHEIGHT;
+	vid.dupx = vid.dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+	vid.fdupx = FixedDiv(vid.width*FRACUNIT, BASEVIDWIDTH*FRACUNIT);
+	vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT);
+
+#ifdef HWRENDER
+	//if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
+	// 13/11/18:
+	// The above is no longer necessary, since we want OpenGL to be just like software now
+	// -- Monster Iestyn
+#endif
+		vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy);
+
+	vid.meddupx = (UINT8)(vid.dupx >> 1) + 1;
+	vid.meddupy = (UINT8)(vid.dupy >> 1) + 1;
+#ifdef HWRENDER
+	vid.fmeddupx = vid.meddupx*FRACUNIT;
+	vid.fmeddupy = vid.meddupy*FRACUNIT;
+#endif
+
+	vid.smalldupx = (UINT8)(vid.dupx / 3) + 1;
+	vid.smalldupy = (UINT8)(vid.dupy / 3) + 1;
+#ifdef HWRENDER
+	vid.fsmalldupx = vid.smalldupx*FRACUNIT;
+	vid.fsmalldupy = vid.smalldupy*FRACUNIT;
+#endif
+}
diff --git a/src/v_video.h b/src/v_video.h
index 9f7a9a9e9c29dd8fb23ecfbfb4d1d09d1d7cba8d..8a18f82ad7ab834988e672e3f5c21189764876b6 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -27,24 +27,31 @@
 
 extern UINT8 *screens[5];
 
-extern consvar_t cv_ticrate, cv_constextsize,\
-cv_globalgamma, cv_globalsaturation, \
-cv_rhue, cv_yhue, cv_ghue, cv_chue, cv_bhue, cv_mhue,\
-cv_rgamma, cv_ygamma, cv_ggamma, cv_cgamma, cv_bgamma, cv_mgamma, \
-cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation, cv_msaturation,\
-cv_allcaps;
+extern consvar_t cv_ticrate, cv_constextsize,
+cv_globalgamma, cv_globalsaturation,
+cv_rhue, cv_yhue, cv_ghue, cv_chue, cv_bhue, cv_mhue,
+cv_rgamma, cv_ygamma, cv_ggamma, cv_cgamma, cv_bgamma, cv_mgamma,
+cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation, cv_msaturation;
 
 // Allocates buffer screens, call before R_Init.
 void V_Init(void);
 
-// Color look-up table
-#define COLORBITS 6
-#define SHIFTCOLORBITS (8-COLORBITS)
-#define CLUTSIZE (1<<COLORBITS)
-
-extern UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
+// Recalculates the viddef (dupx, dupy, etc.) according to the current screen resolution.
+void V_Recalc(void);
 
-void InitColorLUT(RGBA_t *palette);
+// Color look-up table
+#define CLUTINDEX(r, g, b) (((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3)
+
+typedef struct
+{
+	boolean init;
+	RGBA_t palette[256];
+	UINT16 table[0xFFFF];
+} colorlookup_t;
+
+void InitColorLUT(colorlookup_t *lut, RGBA_t *palette, boolean makecolors);
+UINT8 GetColorLUT(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b);
+UINT8 GetColorLUTDirect(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b);
 
 // Set the current RGB palette lookup to use for palettized graphics
 void V_SetPalette(INT32 palettenum);
diff --git a/src/version.h b/src/version.h
index 31cf85bdcdfae7d462bc1786f752bf3bd673eb82..ece084beb2ddaed925f436d78fcfd290d94c57c5 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,4 +1,4 @@
-#define SRB2VERSION "2.2.6"/* this must be the first line, for cmake !! */
+#define SRB2VERSION "2.2.8"/* this must be the first line, for cmake !! */
 
 // The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/showgroups.php ).
 // DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server.
@@ -9,4 +9,7 @@
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.2.0 is not version "1".
-#define MODVERSION 47
+#define MODVERSION 49
+
+// Define this as a prerelease version suffix
+// #define BETAVERSION "RC1"
diff --git a/src/w_wad.c b/src/w_wad.c
index 9af661b5762c01530ee2c0e4f9cfe22d177768f1..2cbcdecb54435f2c5eab72551c43aa132e065d62 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -56,6 +56,9 @@
 #include "d_clisrv.h"
 #include "r_defs.h"
 #include "r_data.h"
+#include "r_textures.h"
+#include "r_patch.h"
+#include "r_picformats.h"
 #include "i_system.h"
 #include "md5.h"
 #include "lua_script.h"
@@ -63,19 +66,13 @@
 #include "p_setup.h" // P_ScanThings
 #endif
 #include "m_misc.h" // M_MapNumber
+#include "g_game.h" // G_SetGameModified
 
 #ifdef HWRENDER
-#include "r_data.h"
 #include "hardware/hw_main.h"
 #include "hardware/hw_glob.h"
 #endif
 
-#ifdef PC_DOS
-#include <stdio.h> // for snprintf
-int	snprintf(char *str, size_t n, const char *fmt, ...);
-//int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
-#endif
-
 #ifdef _DEBUG
 #include "console.h"
 #endif
@@ -687,9 +684,9 @@ static UINT16 W_InitFileError (const char *filename, boolean exitworthy)
 	if (exitworthy)
 	{
 #ifdef _DEBUG
-		CONS_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
+		CONS_Error(va("%s was not found or not valid.\nCheck the log for more details.\n", filename));
 #else
-		I_Error("A WAD file was not found or not valid.\nCheck the log to see which ones.\n");
+		I_Error("%s was not found or not valid.\nCheck the log for more details.\n", filename);
 #endif
 	}
 	else
@@ -720,7 +717,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 #endif
 	size_t packetsize;
 	UINT8 md5sum[16];
-	boolean important;
+	int important;
 
 	if (!(refreshdirmenu & REFRESHDIR_ADDFILE))
 		refreshdirmenu = REFRESHDIR_NORMAL|REFRESHDIR_ADDFILE; // clean out cons_alerts that happened earlier
@@ -750,10 +747,18 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 	if ((handle = W_OpenWadFile(&filename, true)) == NULL)
 		return W_InitFileError(filename, startup);
 
+	important = W_VerifyNMUSlumps(filename, startup);
+
+	if (important == -1)
+	{
+		fclose(handle);
+		return INT16_MAX;
+	}
+
 	// Check if wad files will overflow fileneededbuffer. Only the filename part
 	// is send in the packet; cf.
 	// see PutFileNeeded in d_netfil.c
-	if ((important = !W_VerifyNMUSlumps(filename)))
+	if ((important = !important))
 	{
 		packetsize = packetsizetally + nameonlylength(filename) + 22;
 
@@ -782,6 +787,8 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 		if (!memcmp(wadfiles[i]->md5sum, md5sum, 16))
 		{
 			CONS_Alert(CONS_ERROR, M_GetText("%s is already loaded\n"), filename);
+			if (important)
+				packetsizetally -= nameonlylength(filename) + 22;
 			if (handle)
 				fclose(handle);
 			return W_InitFileError(filename, false);
@@ -813,6 +820,9 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 		return W_InitFileError(filename, startup);
 	}
 
+	if (important && !mainfile)
+		G_SetGameModified(true);
+
 	//
 	// link wad file to search files
 	//
@@ -836,11 +846,6 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 	Z_Calloc(numlumps * sizeof (*wadfile->lumpcache), PU_STATIC, &wadfile->lumpcache);
 	Z_Calloc(numlumps * sizeof (*wadfile->patchcache), PU_STATIC, &wadfile->patchcache);
 
-#ifdef HWRENDER
-	// allocates GLPatch info structures and store them in a tree
-	wadfile->hwrcache = M_AATreeAlloc(AATREE_ZUSER);
-#endif
-
 	//
 	// add the wadfile
 	//
@@ -850,10 +855,10 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 
 #ifdef HWRENDER
 	// Read shaders from file
-	if (rendermode == render_opengl && (vid_opengl_state == 1))
+	if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
 	{
-		HWR_ReadShaders(numwadfiles - 1, (type == RET_PK3));
-		HWR_LoadShaders();
+		HWR_LoadCustomShadersFromFile(numwadfiles - 1, (type == RET_PK3));
+		HWR_CompileShaders();
 	}
 #endif // HWRENDER
 
@@ -890,16 +895,13 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
   *
   * \param filenames A null-terminated list of files to use.
   */
-void W_InitMultipleFiles(char **filenames, UINT16 mainfiles)
+void W_InitMultipleFiles(char **filenames)
 {
-	// open all the files, load headers, and count lumps
-	numwadfiles = 0;
-
 	// will be realloced as lumps are added
 	for (; *filenames; filenames++)
 	{
 		//CONS_Debug(DBG_SETUP, "Loading %s\n", *filenames);
-		W_InitFile(*filenames, numwadfiles < mainfiles, true);
+		W_InitFile(*filenames, numwadfiles < mainwads, true);
 	}
 }
 
@@ -1389,8 +1391,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 #ifdef NO_PNG_LUMPS
 		{
 			size_t bytesread = fread(dest, 1, size, handle);
-			if (R_IsLumpPNG((UINT8 *)dest, bytesread))
-				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
+			if (Picture_IsLumpPNG((UINT8 *)dest, bytesread))
+				Picture_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 			return bytesread;
 		}
 #else
@@ -1431,8 +1433,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 			Z_Free(rawData);
 			Z_Free(decData);
 #ifdef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)dest, size))
-				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
+			if (Picture_IsLumpPNG((UINT8 *)dest, size))
+				Picture_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 #endif
 			return size;
 #else
@@ -1494,8 +1496,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 			Z_Free(decData);
 
 #ifdef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)dest, size))
-				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
+			if (Picture_IsLumpPNG((UINT8 *)dest, size))
+				Picture_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 #endif
 			return size;
 		}
@@ -1676,30 +1678,21 @@ void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 	if (!lumpcache[lump])
 	{
 		size_t len = W_LumpLengthPwad(wad, lump);
-		void *ptr, *lumpdata;
-#ifndef NO_PNG_LUMPS
-		void *srcdata = NULL;
-#endif
-
-		ptr = Z_Malloc(len, tag, &lumpcache[lump]);
-		lumpdata = Z_Malloc(len, tag, NULL);
+		void *ptr, *dest, *lumpdata = Z_Malloc(len, PU_STATIC, NULL);
 
 		// read the lump in full
 		W_ReadLumpHeaderPwad(wad, lump, lumpdata, 0, 0);
+		ptr = lumpdata;
 
 #ifndef NO_PNG_LUMPS
-		// lump is a png so convert it
-		if (R_IsLumpPNG((UINT8 *)lumpdata, len))
-		{
-			size_t newlen;
-			srcdata = R_PNGToPatch((UINT8 *)lumpdata, len, &newlen);
-			ptr = Z_Realloc(ptr, newlen, tag, &lumpcache[lump]);
-			M_Memcpy(ptr, srcdata, newlen);
-			Z_Free(srcdata);
-		}
-		else // just copy it into the patch cache
+		if (Picture_IsLumpPNG((UINT8 *)lumpdata, len))
+			ptr = Picture_PNGConvert((UINT8 *)lumpdata, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, len, &len, 0);
 #endif
-			M_Memcpy(ptr, lumpdata, len);
+
+		dest = Z_Calloc(sizeof(patch_t), tag, &lumpcache[lump]);
+		Patch_Create(ptr, len, dest);
+
+		Z_Free(ptr);
 	}
 	else
 		Z_ChangeTag(lumpcache[lump], tag);
@@ -1714,45 +1707,22 @@ void *W_CacheSoftwarePatchNum(lumpnum_t lumpnum, INT32 tag)
 
 void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 {
-#ifdef HWRENDER
-	GLPatch_t *grPatch;
-#endif
+	patch_t *patch;
 
 	if (!TestValidLump(wad, lump))
 		return NULL;
 
+	patch = W_CacheSoftwarePatchNumPwad(wad, lump, tag);
+
 #ifdef HWRENDER
 	// Software-only compile cache the data without conversion
 	if (rendermode == render_soft || rendermode == render_none)
 #endif
-	{
-		return W_CacheSoftwarePatchNumPwad(wad, lump, tag);
-	}
-#ifdef HWRENDER
-
-	grPatch = HWR_GetCachedGLPatchPwad(wad, lump);
-
-	if (grPatch->mipmap->grInfo.data)
-	{
-		if (tag == PU_CACHE)
-			tag = PU_HWRCACHE;
-		Z_ChangeTag(grPatch->mipmap->grInfo.data, tag);
-	}
-	else
-	{
-		patch_t *ptr = NULL;
-
-		// Only load the patch if we haven't initialised the grPatch yet
-		if (grPatch->mipmap->width == 0)
-			ptr = W_CacheLumpNumPwad(grPatch->wadnum, grPatch->lumpnum, PU_STATIC);
+		return (void *)patch;
 
-		// Run HWR_MakePatch in all cases, to recalculate some things
-		HWR_MakePatch(ptr, grPatch, grPatch->mipmap, false);
-		Z_Free(ptr);
-	}
-
-	// return GLPatch_t, which can be casted to (patch_t) with valid patch header info
-	return (void *)grPatch;
+#ifdef HWRENDER
+	Patch_CreateGL(patch);
+	return (void *)patch;
 #endif
 }
 
@@ -1763,11 +1733,14 @@ void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag)
 
 void W_UnlockCachedPatch(void *patch)
 {
+	if (!patch)
+		return;
+
 	// The hardware code does its own memory management, as its patches
 	// have different lifetimes from software's.
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
-		HWR_UnlockCachedPatch((GLPatch_t*)patch);
+		HWR_UnlockCachedPatch((GLPatch_t *)((patch_t *)patch)->hardware);
 	else
 #endif
 		Z_Unlock(patch);
@@ -1862,7 +1835,7 @@ void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5)
 #else
 		I_Error
 #endif
-			(M_GetText("File is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);
+			(M_GetText("File is old, is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);
 	}
 #endif
 }
@@ -1947,8 +1920,16 @@ static lumpchecklist_t folderblacklist[] =
 static int
 W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 {
+	int verified = true;
+
     zend_t zend;
     zentry_t zentry;
+    zlentry_t zlentry;
+
+	long file_size;/* size of zip file */
+	long data_size;/* size of data inside zip file */
+
+	long old_position;
 
 	UINT16 numlumps;
 	size_t i;
@@ -1964,6 +1945,8 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 	// Central directory bullshit
 
 	fseek(fp, 0, SEEK_END);
+	file_size = ftell(fp);
+
 	if (!ResFindSignature(fp, pat_end, max(0, ftell(fp) - (22 + 65536))))
 		return true;
 
@@ -1971,6 +1954,8 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 	if (fread(&zend, 1, sizeof zend, fp) < sizeof zend)
 		return true;
 
+	data_size = sizeof zend;
+
 	numlumps = zend.entries;
 
 	fseek(fp, zend.cdiroffset, SEEK_SET);
@@ -1985,40 +1970,79 @@ W_VerifyPK3 (FILE *fp, lumpchecklist_t *checklist, boolean status)
 		if (memcmp(zentry.signature, pat_central, 4))
 			return true;
 
-		fullname = malloc(zentry.namelen + 1);
-		if (fgets(fullname, zentry.namelen + 1, fp) != fullname)
-			return true;
+		if (verified == true)
+		{
+			fullname = malloc(zentry.namelen + 1);
+			if (fgets(fullname, zentry.namelen + 1, fp) != fullname)
+				return true;
 
-		// Strip away file address and extension for the 8char name.
-		if ((trimname = strrchr(fullname, '/')) != 0)
-			trimname++;
-		else
-			trimname = fullname; // Care taken for root files.
+			// Strip away file address and extension for the 8char name.
+			if ((trimname = strrchr(fullname, '/')) != 0)
+				trimname++;
+			else
+				trimname = fullname; // Care taken for root files.
 
-		if (*trimname) // Ignore directories, well kinda
-		{
-			if ((dotpos = strrchr(trimname, '.')) == 0)
-				dotpos = fullname + strlen(fullname); // Watch for files without extension.
+			if (*trimname) // Ignore directories, well kinda
+			{
+				if ((dotpos = strrchr(trimname, '.')) == 0)
+					dotpos = fullname + strlen(fullname); // Watch for files without extension.
 
-			memset(lumpname, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
-			strncpy(lumpname, trimname, min(8, dotpos - trimname));
+				memset(lumpname, '\0', 9); // Making sure they're initialized to 0. Is it necessary?
+				strncpy(lumpname, trimname, min(8, dotpos - trimname));
 
-			if (! W_VerifyName(lumpname, checklist, status))
-				return false;
+				if (! W_VerifyName(lumpname, checklist, status))
+					verified = false;
 
-			// Check for directories next, if it's blacklisted it will return false
-			if (W_VerifyName(fullname, folderblacklist, status))
-				return false;
+				// Check for directories next, if it's blacklisted it will return false
+				else if (W_VerifyName(fullname, folderblacklist, status))
+					verified = false;
+			}
+
+			free(fullname);
+
+			// skip and ignore comments/extra fields
+			if (fseek(fp, zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+				return true;
+		}
+		else
+		{
+			if (fseek(fp, zentry.namelen + zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+				return true;
 		}
 
-		free(fullname);
+		data_size +=
+			sizeof zentry + zentry.namelen + zentry.xtralen + zentry.commlen;
 
-		// skip and ignore comments/extra fields
-		if (fseek(fp, zentry.xtralen + zentry.commlen, SEEK_CUR) != 0)
+		old_position = ftell(fp);
+
+		if (fseek(fp, zentry.offset, SEEK_SET) != 0)
+			return true;
+
+		if (fread(&zlentry, 1, sizeof(zlentry_t), fp) < sizeof (zlentry_t))
 			return true;
+
+		data_size +=
+			sizeof zlentry + zlentry.namelen + zlentry.xtralen + zlentry.compsize;
+
+		fseek(fp, old_position, SEEK_SET);
 	}
 
-	return true;
+	if (data_size < file_size)
+	{
+		const char * error = "ZIP file has holes (%ld extra bytes)\n";
+		CONS_Alert(CONS_ERROR, error, (file_size - data_size));
+		return -1;
+	}
+	else if (data_size > file_size)
+	{
+		const char * error = "Reported size of ZIP file contents exceeds file size (%ld extra bytes)\n";
+		CONS_Alert(CONS_ERROR, error, (data_size - file_size));
+		return -1;
+	}
+	else
+	{
+		return verified;
+	}
 }
 
 // Note: This never opens lumps themselves and therefore doesn't have to
@@ -2057,12 +2081,13 @@ static int W_VerifyFile(const char *filename, lumpchecklist_t *checklist,
   * be sent.
   *
   * \param filename Filename of the wad to check.
+  * \param exit_on_error Whether to exit upon file error.
   * \return 1 if file contains only music/sound lumps, 0 if it contains other
   *         stuff (maps, sprites, dehacked lumps, and so on). -1 if there no
   *         file exists with that filename
   * \author Alam Arias
   */
-int W_VerifyNMUSlumps(const char *filename)
+int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error)
 {
 	// MIDI, MOD/S3M/IT/XM/OGG/MP3/WAV, WAVE SFX
 	// ENDOOM text and palette lumps
@@ -2080,18 +2105,69 @@ int W_VerifyNMUSlumps(const char *filename)
 		{"CLM", 3}, // Colormap changes
 		{"TRANS", 5}, // Translucency map changes
 
+		{"CONSBACK", 8}, // Console Background graphic
+
+		{"SAVE", 4}, // Save Select graphics here and below
+		{"BLACXLVL", 8},
+		{"GAMEDONE", 8},
+		{"CONT", 4}, // Continue icons on saves (probably not used anymore)
+		{"STNONEX", 7}, // "X" graphic
+		{"ULTIMATE", 8}, // Ultimate no-save
+
+		{"CRFNT", 5}, // Sonic 1 font changes
+		{"NTFNT", 5}, // Character Select font changes
+		{"NTFNO", 5}, // Character Select font (outline)
 		{"LTFNT", 5}, // Level title font changes
 		{"TTL", 3}, // Act number changes
 		{"STCFN", 5}, // Console font changes
 		{"TNYFN", 5}, // Tiny console font changes
+
+		{"STLIVE", 6}, // Life graphics, background and the "X" that shows under skin's HUDNAME
+		{"CROSHAI", 7}, // First person crosshairs
+		{"INTERSC", 7}, // Default intermission backgrounds (co-op)
 		{"STT", 3}, // Acceptable HUD changes (Score Time Rings)
 		{"YB_", 3}, // Intermission graphics, goes with the above
-		{"M_", 2}, // As does menu stuff
+		{"RESULT", 6}, // Used in intermission for competitive modes, above too :3
+		{"RACE", 4}, // Race mode graphics, 321go
+		{"M_", 2}, // Menu stuff
+		{"LT", 2}, // Titlecard changes
+
+		{"SLID", 4}, // Continue
+		{"CONT", 4},
+
+		{"MINICAPS", 8}, // NiGHTS graphics here and below
+		{"BLUESTAT", 8}, // Sphere status
+		{"BYELSTAT", 8},
+		{"ORNGSTAT", 8},
+		{"REDSTAT", 7},
+		{"YELSTAT", 7},
+		{"NBRACKET", 8},
+		{"NGHTLINK", 8},
+		{"NGT", 3}, // Link numbers
+		{"NARROW", 6},
+		{"NREDAR", 6},
+		{"NSS", 3},
+		{"NBON", 4},
+		{"NRNG", 4},
+		{"NHUD", 4},
+		{"CAPS", 4},
+		{"DRILL", 5},
+		{"GRADE", 5},
+		{"MINUS5", 6},
+
 		{"MUSICDEF", 8}, // Song definitions (thanks kart)
+		{"SHADERS", 7}, // OpenGL shader definitions
+		{"SH_", 3}, // GLSL shader
 
 		{NULL, 0},
 	};
-	return W_VerifyFile(filename, NMUSlist, false);
+
+	int status = W_VerifyFile(filename, NMUSlist, false);
+
+	if (status == -1)
+		W_InitFileError(filename, exit_on_error);
+
+	return status;
 }
 
 /** \brief Generates a virtual resource used for level data loading.
diff --git a/src/w_wad.h b/src/w_wad.h
index fddc65529de1dbc8a31f3c730eda7fbb556889f1..d0a86bcb44817b678fdda16fd426692cc222c89a 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -102,10 +102,6 @@ virtlump_t* vres_Find(const virtres_t*, const char*);
 
 #define lumpcache_t void *
 
-#ifdef HWRENDER
-#include "m_aatree.h"
-#endif
-
 // Resource type of the WAD. Yeah, I know this sounds dumb, but I'll leave it like this until I clean up the code further.
 typedef enum restype
 {
@@ -123,9 +119,6 @@ typedef struct wadfile_s
 	lumpinfo_t *lumpinfo;
 	lumpcache_t *lumpcache;
 	lumpcache_t *patchcache;
-#ifdef HWRENDER
-	aatree_t *hwrcache; // patches are cached in renderer's native format
-#endif
 	UINT16 numlumps; // this wad's number of resources
 	FILE *handle;
 	UINT32 filesize; // for network
@@ -150,7 +143,7 @@ FILE *W_OpenWadFile(const char **filename, boolean useerrors);
 UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup);
 
 // W_InitMultipleFiles exits if a file was not found, but not if all is okay.
-void W_InitMultipleFiles(char **filenames, UINT16 mainfiles);
+void W_InitMultipleFiles(char **filenames);
 
 const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump);
 const char *W_CheckNameForNum(lumpnum_t lumpnum);
@@ -213,6 +206,6 @@ void W_UnlockCachedPatch(void *patch);
 
 void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5);
 
-int W_VerifyNMUSlumps(const char *filename);
+int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error);
 
 #endif // __W_WAD__
diff --git a/src/win32/Makefile.cfg b/src/win32/Makefile.cfg
index 27926b2085cdbd282fe9113d3a0f3346281295df..702ae3765ce52effaccb48b7d3c3e196a7ac9214 100644
--- a/src/win32/Makefile.cfg
+++ b/src/win32/Makefile.cfg
@@ -56,15 +56,6 @@ ifndef GCC44
 	#OPTS+=-mms-bitfields
 endif
 
-ifndef SDL
-	OPTS+=-D_WINDOWS
-endif
-	OPTS+=-D__USE_MINGW_ANSI_STDIO=0
-
-ifndef SDL
-	LIBS+=-lmingw32 -mwindows -ldinput -ldxguid -lgdi32 -lwinmm
-endif
-
 	LIBS+=-ladvapi32 -lkernel32 -lmsvcrt -luser32
 ifdef MINGW64
 	LIBS+=-lws2_32
@@ -77,11 +68,7 @@ endif
 endif
 
 	# name of the exefile
-ifdef SDL
 	EXENAME?=srb2win.exe
-else
-	EXENAME?=srb2dd.exe
-endif
 
 ifdef SDL
 	i_system_o+=$(OBJDIR)/SRB2.res
@@ -89,22 +76,6 @@ ifdef SDL
 ifndef NOHW
 	OPTS+=-DUSE_WGL_SWAP
 endif
-else
-	D_FILES+=$(D_DIR)/fmodex.dll
-	CFLAGS+=-I../libs/fmodex/inc
-	LDFLAGS+=-L../libs/fmodex/lib
-ifdef MINGW64
-	LIBS+=-lfmodex64_vc
-else
-	LIBS+=-lfmodex_vc
-endif
-	i_cdmus_o=$(OBJDIR)/win_cd.o
-	i_net_o=$(OBJDIR)/win_net.o
-	i_system_o=$(OBJDIR)/win_sys.o $(OBJDIR)/SRB2.res
-	i_sound_o=$(OBJDIR)/win_snd.o
-	i_main_o=$(OBJDIR)/win_main.o
-	#i_main_o+=$(OBJDIR)/win_dbg.o
-	OBJS=$(OBJDIR)/dx_error.o $(OBJDIR)/fabdxlib.o $(OBJDIR)/win_vid.o $(OBJDIR)/win_dll.o
 endif
 
 
@@ -154,3 +125,12 @@ else
 	LDFLAGS+=-L../libs/miniupnpc/mingw32
 endif #MINGW64
 endif
+
+ifndef NOCURL
+	CURL_CFLAGS+=-I../libs/curl/include
+ifdef MINGW64
+	CURL_LDFLAGS+=-L../libs/curl/lib64 -lcurl
+else
+	CURL_LDFLAGS+=-L../libs/curl/lib32 -lcurl
+endif #MINGW64
+endif
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index 5fe723f37b85001b708caf560d1ae2def0a2e682..3e8af3b0ed866b9039879abbc4b4e28b1d623ba4 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -298,12 +298,15 @@
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
-    <ClCompile Include="..\r_plane.c" />
     <ClCompile Include="..\r_patch.c" />
+    <ClCompile Include="..\r_patchrotation.c" />
+    <ClCompile Include="..\r_picformats.c" />
+    <ClCompile Include="..\r_plane.c" />
     <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
+    <ClCompile Include="..\r_textures.c" />
     <ClCompile Include="..\r_things.c" />
     <ClCompile Include="..\screen.c" />
     <ClCompile Include="..\sounds.c" />
@@ -398,7 +401,6 @@
     <ClInclude Include="..\hardware\hw_defs.h" />
     <ClInclude Include="..\hardware\hw_dll.h" />
     <ClInclude Include="..\hardware\hw_drv.h" />
-    <ClInclude Include="..\hardware\hw_glide.h" />
     <ClInclude Include="..\hardware\hw_glob.h" />
     <ClInclude Include="..\hardware\hw_light.h" />
     <ClInclude Include="..\hardware\hw_main.h" />
@@ -454,13 +456,16 @@
     <ClInclude Include="..\r_draw.h" />
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
+    <ClInclude Include="..\r_patch.h" />
+    <ClInclude Include="..\r_patchrotation.h" />
+    <ClInclude Include="..\r_picformats.h" />
     <ClInclude Include="..\r_plane.h" />
-	<ClInclude Include="..\r_patch.h" />
     <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
     <ClInclude Include="..\r_state.h" />
+    <ClInclude Include="..\r_textures.h" />
     <ClInclude Include="..\r_things.h" />
     <ClInclude Include="..\screen.h" />
     <ClInclude Include="..\sounds.h" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index 7b96bf16b11966134e93647a4a15754d537e21ba..7279368f1423f4a02e962395e8d4b451a30e44cd 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -472,6 +472,15 @@
     <ClCompile Include="..\r_patch.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
+    <ClCompile Include="..\r_patchrotation.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
+    <ClCompile Include="..\r_picformats.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
+    <ClCompile Include="..\r_textures.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
     <ClCompile Include="..\r_portal.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
@@ -513,9 +522,6 @@
     <ClInclude Include="..\hardware\hw_drv.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
-    <ClInclude Include="..\hardware\hw_glide.h">
-      <Filter>Hw_Hardware</Filter>
-    </ClInclude>
     <ClInclude Include="..\hardware\hw_glob.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
@@ -892,6 +898,15 @@
     <ClInclude Include="..\r_patch.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
+    <ClInclude Include="..\r_patchrotation.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_picformats.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_textures.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
     <ClInclude Include="..\r_portal.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
diff --git a/src/win32/Srb2win-vc9.vcproj b/src/win32/Srb2win-vc9.vcproj
index b3ba54c10a63e2cbf864c349370cb448b6e8a7fb..c1c6b5bc43e3e99139bef441da8bf3b3c48bdbb2 100644
--- a/src/win32/Srb2win-vc9.vcproj
+++ b/src/win32/Srb2win-vc9.vcproj
@@ -2151,10 +2151,6 @@
 				RelativePath="..\hardware\hw_drv.h"
 				>
 			</File>
-			<File
-				RelativePath="..\hardware\hw_glide.h"
-				>
-			</File>
 			<File
 				RelativePath="..\hardware\hw_glob.h"
 				>
diff --git a/src/win32/Srb2win.dsp b/src/win32/Srb2win.dsp
index d87230a39010f39f427ce4d1b055456918fb50bc..661f3eaf90bcb354898fd9a4a3b1252c4fee014e 100644
--- a/src/win32/Srb2win.dsp
+++ b/src/win32/Srb2win.dsp
@@ -535,10 +535,6 @@ SOURCE=..\hardware\hw_drv.h
 # End Source File
 # Begin Source File
 
-SOURCE=..\hardware\hw_glide.h
-# End Source File
-# Begin Source File
-
 SOURCE=..\hardware\hw_glob.h
 # End Source File
 # Begin Source File
diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc
index b90947a9ece7d48c1166bad85861374687923048..d5d59922c113a29af52c673700d8600b8be7804f 100644
--- a/src/win32/Srb2win.rc
+++ b/src/win32/Srb2win.rc
@@ -22,6 +22,16 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 #pragma code_page(1252)
 #endif //_WIN32*/
 
+#ifndef RT_MANIFEST
+#define RT_MANIFEST 24
+#endif
+
+#ifndef CREATEPROCESS_MANIFEST_RESOURCE_ID
+#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
+#endif
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST srb2win.exe.manifest
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Icon
@@ -66,8 +76,8 @@ END
 #include "../doomdef.h" // Needed for version string
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 2,2,6,0
- PRODUCTVERSION 2,2,6,0
+ FILEVERSION 2,2,8,0
+ PRODUCTVERSION 2,2,8,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -85,14 +95,14 @@ BEGIN
             VALUE "Comments", "Visit our web site at www.srb2.org for news and updates!\0"
             VALUE "CompanyName", "Sonic Team Junior\0"
             VALUE "FileDescription", "Sonic Robo Blast 2\0"
-            VALUE "FileVersion", VERSIONSTRING
+            VALUE "FileVersion", VERSIONSTRING_RC
             VALUE "InternalName", "srb2\0"
             VALUE "LegalCopyright", "Copyright 1998-2020 by Sonic Team Junior\0"
             VALUE "LegalTrademarks", "Sonic the Hedgehog and related characters are trademarks of Sega.\0"
             VALUE "OriginalFilename", "srb2win.exe\0"
             VALUE "PrivateBuild", "\0"
             VALUE "ProductName", "Sonic Robo Blast 2\0"
-            VALUE "ProductVersion", VERSIONSTRING
+            VALUE "ProductVersion", VERSIONSTRING_RC
             VALUE "SpecialBuild", "\0"
         END
     END
diff --git a/src/win32/srb2win.exe.manifest b/src/win32/srb2win.exe.manifest
new file mode 100644
index 0000000000000000000000000000000000000000..d3b8355cbdea8194f1d94b8dd70c13509e8511ef
--- /dev/null
+++ b/src/win32/srb2win.exe.manifest
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel level="asInvoker"/>
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!--The ID below indicates application support for Windows Vista -->
+      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+      <!--The ID below indicates application support for Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+      <!--The ID below indicates application support for Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!--The ID below indicates application support for Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/src/win32/win_cd.c b/src/win32/win_cd.c
index 2586b84405649d431e673d1f7c8e4bb3a4d27a9c..324c2492848a4a78e9f1c22542cc6180533a59ab 100644
--- a/src/win32/win_cd.c
+++ b/src/win32/win_cd.c
@@ -161,13 +161,13 @@ static BOOL wasPlaying;
 //static INT     cdVolume = 0;          // current cd volume (0-31)
 
 // 0-31 like Music & Sfx, though CD hardware volume is 0-255.
-consvar_t cd_volume = {"cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cd_volume = CVAR_INIT ("cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL);
 
 // allow Update for next/loop track
 // some crap cd drivers take up to
 // a second for a simple 'busy' check..
 // (on those Update can be disabled)
-consvar_t cdUpdate  = {"cd_update","1",CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cdUpdate  = CVAR_INIT ("cd_update","1",CV_SAVE, NULL, NULL);
 
 #if (__GNUC__ > 6)
 #pragma GCC diagnostic push
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index 3f6c5e290232a46c9998d9ef9454610d6ba37c13..4743cec34b2e6af738caeec60d7c179e58ec14d1 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -102,11 +102,12 @@ static loadfunc_t hwdFuncTable[] = {
 	{"FinishUpdate@4",      &hwdriver.pfnFinishUpdate},
 	{"Draw2DLine@12",       &hwdriver.pfnDraw2DLine},
 	{"DrawPolygon@16",      &hwdriver.pfnDrawPolygon},
-	{"RenderSkyDome@16",    &hwdriver.pfnRenderSkyDome},
+	{"RenderSkyDome@4",     &hwdriver.pfnRenderSkyDome},
 	{"SetBlend@4",          &hwdriver.pfnSetBlend},
 	{"ClearBuffer@12",      &hwdriver.pfnClearBuffer},
 	{"SetTexture@4",        &hwdriver.pfnSetTexture},
 	{"UpdateTexture@4",     &hwdriver.pfnUpdateTexture},
+	{"DeleteTexture@4",     &hwdriver.pfnDeleteTexture},
 	{"ReadRect@24",         &hwdriver.pfnReadRect},
 	{"GClipRect@20",        &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache@0",  &hwdriver.pfnClearMipMapCache},
@@ -139,6 +140,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"ClearBuffer",         &hwdriver.pfnClearBuffer},
 	{"SetTexture",          &hwdriver.pfnSetTexture},
 	{"UpdateTexture",       &hwdriver.pfnUpdateTexture},
+	{"DeleteTexture",       &hwdriver.pfnDeleteTexture},
 	{"ReadRect",            &hwdriver.pfnReadRect},
 	{"GClipRect",           &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache",    &hwdriver.pfnClearMipMapCache},
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index a374a2587b0b9d9c01786aa9696a4d58f34b2980..da0d5b47ee3c26699b4d538575a22b8dc7420218 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -3658,7 +3658,7 @@ const CPUInfoFlags *I_CPUInfo(void)
 }
 
 static void CPUAffinity_OnChange(void);
-static consvar_t cv_cpuaffinity = {"cpuaffinity", "-1", CV_CALL, NULL, CPUAffinity_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_cpuaffinity = CVAR_INIT ("cpuaffinity", "-1", CV_CALL, NULL, CPUAffinity_OnChange);
 
 typedef HANDLE (WINAPI *p_GetCurrentProcess) (VOID);
 static p_GetCurrentProcess pfnGetCurrentProcess = NULL;
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index 716f380890f965d420d2121a3e84bfbe6ff9e665..7a33e19311f876fe595b72f7552fcf55f0af23fd 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -48,15 +48,15 @@
 
 // this is the CURRENT rendermode!! very important: used by w_wad, and much other code
 rendermode_t rendermode = render_soft;
+rendermode_t chosenrendermode = render_none; // set by command line arguments
 static void OnTop_OnChange(void);
 // synchronize page flipping with screen refresh
 static CV_PossibleValue_t CV_NeverOnOff[] = {{-1, "Never"}, {0, "Off"}, {1, "On"}, {0, NULL}};
-consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, OnTop_OnChange, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_stretch = {"stretch", "On", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_ontop = {"ontop", "Never", 0, CV_NeverOnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", CV_SAVE, CV_OnOff, OnTop_OnChange);
+static consvar_t cv_stretch = CVAR_INIT ("stretch", "On", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL);
+static consvar_t cv_ontop = CVAR_INIT ("ontop", "Never", 0, CV_NeverOnOff, NULL);
 
 boolean highcolor;
-int vid_opengl_state = 0;
 
 static BOOL bDIBMode; // means we are using DIB instead of DirectDraw surfaces
 static LPBITMAPINFO bmiMain = NULL;
@@ -952,7 +952,11 @@ INT32 VID_SetMode(INT32 modenum)
 	return 1;
 }
 
-void VID_CheckRenderer(void) {}
+boolean VID_CheckRenderer(void)
+{
+	return false;
+}
+
 void VID_CheckGLLoaded(rendermode_t oldrender)
 {
 	(void)oldrender;
diff --git a/src/y_inter.c b/src/y_inter.c
index 58e0c4a885702496deacd774a85f8441f9e86015..bd3b557d794928ee2be5c01b33f7d57130fa7e74 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -43,12 +43,6 @@
 #include "hardware/hw_main.h"
 #endif
 
-#ifdef PC_DOS
-#include <stdio.h> // for snprintf
-int	snprintf(char *str, size_t n, const char *fmt, ...);
-//int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
-#endif
-
 typedef struct
 {
 	char patch[9];
@@ -141,7 +135,6 @@ static y_data data;
 
 // graphics
 static patch_t *bgpatch = NULL;     // INTERSCR
-static patch_t *widebgpatch = NULL; // INTERSCW
 static patch_t *bgtile = NULL;      // SPECTILE/SRB2BACK
 static patch_t *interpic = NULL;    // custom picture defined in map header
 static boolean usetile;
@@ -159,7 +152,6 @@ typedef struct
 
 boolean usebuffer = false;
 static boolean useinterpic;
-static boolean safetorender = true;
 static y_buffer_t *y_buffer;
 
 static INT32 intertic;
@@ -176,7 +168,6 @@ static void Y_CalculateCompetitionWinners(void);
 static void Y_CalculateTimeRaceWinners(void);
 static void Y_CalculateMatchWinners(void);
 static void Y_UnloadData(void);
-static void Y_CleanupData(void);
 
 // Stuff copy+pasted from st_stuff.c
 #define ST_DrawNumFromHud(h,n)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n)
@@ -192,7 +183,7 @@ static void Y_IntermissionTokenDrawer(void)
 
 	offs = 0;
 	lowy = BASEVIDHEIGHT - 32 - 8;
-	temp = SHORT(tokenicon->height)/2;
+	temp = tokenicon->height / 2;
 
 	em = 0;
 	while (emeralds & (1 << em))
@@ -221,7 +212,7 @@ static void Y_IntermissionTokenDrawer(void)
 	calc = (lowy - y)*2;
 
 	if (calc > 0)
-		V_DrawCroppedPatch(32<<FRACBITS, y<<FRACBITS, FRACUNIT/2, 0, tokenicon, 0, 0, SHORT(tokenicon->width), calc);
+		V_DrawCroppedPatch(32<<FRACBITS, y<<FRACBITS, FRACUNIT/2, 0, tokenicon, 0, 0, tokenicon->width, calc);
 }
 
 //
@@ -323,19 +314,6 @@ void Y_IntermissionDrawer(void)
 	if (intertype == int_none || rendermode == render_none)
 		return;
 
-	// Lactozilla: Renderer switching
-	if (needpatchrecache)
-	{
-		Y_CleanupData();
-		safetorender = false;
-	}
-
-	if (!usebuffer || !safetorender)
-		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
-
-	if (!safetorender)
-		goto dontdrawbg;
-
 	if (useinterpic)
 		V_DrawScaledPatch(0, 0, 0, interpic);
 	else if (!usetile)
@@ -359,12 +337,11 @@ void Y_IntermissionDrawer(void)
 		else if (rendermode != render_soft && usebuffer)
 			HWR_DrawIntermissionBG();
 #endif
-		else
+		else if (bgpatch)
 		{
-			if (widebgpatch && rendermode == render_soft && vid.width / vid.dupx == 400)
-				V_DrawScaledPatch(0, 0, V_SNAPTOLEFT, widebgpatch);
-			else if (bgpatch)
-				V_DrawScaledPatch(0, 0, 0, bgpatch);
+			fixed_t hs = vid.width  * FRACUNIT / BASEVIDWIDTH;
+			fixed_t vs = vid.height * FRACUNIT / BASEVIDHEIGHT;
+			V_DrawStretchyFixedPatch(0, 0, hs, vs, V_NOSCALEPATCH, bgpatch, NULL);
 		}
 	}
 	else if (bgtile)
@@ -374,7 +351,6 @@ void Y_IntermissionDrawer(void)
 	if (!LUA_HudEnabled(hud_intermissiontally))
 		goto skiptallydrawer;
 
-dontdrawbg:
 	if (intertype == int_coop)
 	{
 		INT32 bonusy;
@@ -424,22 +400,19 @@ dontdrawbg:
 
 		bonusy = 150;
 		// Total
-		if (safetorender)
-		{
-			V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
-			V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
-		}
-		bonusy -= (3*SHORT(tallnum[0]->height)/2) + 1;
+		V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
+		V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
+		bonusy -= (3*(tallnum[0]->height)/2) + 1;
 
 		// Draw bonuses
 		for (i = 3; i >= 0; --i)
 		{
-			if (data.coop.bonuses[i].display && safetorender)
+			if (data.coop.bonuses[i].display)
 			{
 				V_DrawScaledPatch(152, bonusy, 0, data.coop.bonuspatches[i]);
 				V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.bonuses[i].points);
 			}
-			bonusy -= (3*SHORT(tallnum[0]->height)/2) + 1;
+			bonusy -= (3*(tallnum[0]->height)/2) + 1;
 		}
 	}
 	else if (intertype == int_spec)
@@ -663,8 +636,7 @@ dontdrawbg:
 		char strtime[10];
 
 		// draw the header
-		if (safetorender)
-			V_DrawScaledPatch(112, 2, 0, data.match.result);
+		V_DrawScaledPatch(112, 2, 0, data.match.result);
 
 		// draw the level name
 		V_DrawCenteredString(BASEVIDWIDTH/2, 20, 0, data.match.levelstring);
@@ -777,10 +749,10 @@ dontdrawbg:
 		char name[MAXPLAYERNAME+1];
 
 		// Show the team flags and the team score at the top instead of "RESULTS"
-		V_DrawSmallScaledPatch(128 - SHORT(data.match.blueflag->width)/4, 2, 0, data.match.blueflag);
+		V_DrawSmallScaledPatch(128 - (data.match.blueflag->width / 4), 2, 0, data.match.blueflag);
 		V_DrawCenteredString(128, 16, 0, va("%u", bluescore));
 
-		V_DrawSmallScaledPatch(192 - SHORT(data.match.redflag->width)/4, 2, 0, data.match.redflag);
+		V_DrawSmallScaledPatch(192 - (data.match.redflag->width / 4), 2, 0, data.match.redflag);
 		V_DrawCenteredString(192, 16, 0, va("%u", redscore));
 
 		// draw the level name
@@ -1017,7 +989,7 @@ void Y_Ticker(void)
 			return;
 
 		for (i = 0; i < MAXPLAYERS; i++)
-			if (playeringame[i] && (players[i].cmd.buttons & BT_USE))
+			if (playeringame[i] && (players[i].cmd.buttons & BT_SPIN))
 				skip = true;
 
 		// bonuses count down by 222 each tic
@@ -1094,7 +1066,7 @@ void Y_Ticker(void)
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i])
 			{
-				if (players[i].cmd.buttons & BT_USE)
+				if (players[i].cmd.buttons & BT_SPIN)
 					skip = true;
 				if (players[i].charflags & SF_SUPER)
 					super = true;
@@ -1220,8 +1192,6 @@ void Y_StartIntermission(void)
 		I_Error("endtic is dirty");
 #endif
 
-	safetorender = true;
-
 	if (!multiplayer)
 	{
 		timer = 0;
@@ -1259,14 +1229,16 @@ void Y_StartIntermission(void)
 			data.coop.tics = players[consoleplayer].realtime;
 
 			for (i = 0; i < 4; ++i)
-				data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_PATCH);
+			{
+				if (strlen(data.coop.bonuses[i].patch))
+					data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_PATCH);
+			}
 			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_PATCH);
 
 			// get act number
 			data.coop.actnum = mapheaderinfo[gamemap-1]->actnum;
 
 			// get background patches
-			widebgpatch = W_CachePatchName("INTERSCW", PU_PATCH);
 			bgpatch = W_CachePatchName("INTERSCR", PU_PATCH);
 
 			// grab an interscreen if appropriate
@@ -1764,7 +1736,6 @@ static void Y_SetNullBonus(player_t *player, y_bonus_t *bstruct)
 {
 	(void)player;
 	memset(bstruct, 0, sizeof(y_bonus_t));
-	strncpy(bstruct->patch, "MISSING", sizeof(bstruct->patch));
 }
 
 //
@@ -2069,22 +2040,15 @@ void Y_EndIntermission(void)
 	usebuffer = false;
 }
 
-#define UNLOAD(x) if (x) {Z_ChangeTag(x, PU_CACHE);} x = NULL;
-#define CLEANUP(x) x = NULL;
+#define UNLOAD(x) if (x) {Patch_Free(x);} x = NULL;
 
 //
 // Y_UnloadData
 //
 static void Y_UnloadData(void)
 {
-	// In hardware mode, don't Z_ChangeTag a pointer returned by W_CachePatchName().
-	// It doesn't work and is unnecessary.
-	if (rendermode != render_soft)
-		return;
-
 	// unload the background patches
 	UNLOAD(bgpatch);
-	UNLOAD(widebgpatch);
 	UNLOAD(bgtile);
 	UNLOAD(interpic);
 
@@ -2122,46 +2086,3 @@ static void Y_UnloadData(void)
 			break;
 	}
 }
-
-static void Y_CleanupData(void)
-{
-	// unload the background patches
-	CLEANUP(bgpatch);
-	CLEANUP(widebgpatch);
-	CLEANUP(bgtile);
-	CLEANUP(interpic);
-
-	switch (intertype)
-	{
-		case int_coop:
-			// unload the coop and single player patches
-			CLEANUP(data.coop.bonuspatches[3]);
-			CLEANUP(data.coop.bonuspatches[2]);
-			CLEANUP(data.coop.bonuspatches[1]);
-			CLEANUP(data.coop.bonuspatches[0]);
-			CLEANUP(data.coop.ptotal);
-			break;
-		case int_spec:
-			// unload the special stage patches
-			//CLEANUP(data.spec.cemerald);
-			//CLEANUP(data.spec.nowsuper);
-			CLEANUP(data.spec.bonuspatches[1]);
-			CLEANUP(data.spec.bonuspatches[0]);
-			CLEANUP(data.spec.pscore);
-			CLEANUP(data.spec.pcontinues);
-			break;
-		case int_match:
-		case int_race:
-			CLEANUP(data.match.result);
-			break;
-		case int_ctf:
-			CLEANUP(data.match.blueflag);
-			CLEANUP(data.match.redflag);
-			break;
-		default:
-			//without this default,
-			//int_none, int_tag, int_chaos, and int_classicrace
-			//are not handled
-			break;
-	}
-}
diff --git a/src/z_zone.c b/src/z_zone.c
index 2387a11433592e99fb149b85d88863ace01ab016..d7da17e51d1608cf068a0c929bbdbd982d3a935c 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -28,6 +28,7 @@
 #include "doomdef.h"
 #include "doomstat.h"
 #include "r_patch.h"
+#include "r_picformats.h"
 #include "i_system.h" // I_GetFreeMem
 #include "i_video.h" // rendermode
 #include "z_zone.h"
@@ -495,37 +496,37 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag)
 	}
 }
 
-// -----------------
-// Utility functions
-// -----------------
+/** Iterates through all memory for a given set of tags.
+  *
+  * \param lowtag The lowest tag to consider.
+  * \param hightag The highest tag to consider.
+  * \param iterfunc The iterator function.
+  */
+void Z_IterateTags(INT32 lowtag, INT32 hightag, boolean (*iterfunc)(void *))
+{
+	memblock_t *block, *next;
 
-// for renderer switching
-boolean needpatchflush = false;
-boolean needpatchrecache = false;
+	if (!iterfunc)
+		I_Error("Z_IterateTags: no iterator function was given");
 
-// flush all patches from memory
-void Z_FlushCachedPatches(void)
-{
-	CONS_Debug(DBG_RENDER, "Z_FlushCachedPatches()...\n");
-	Z_FreeTag(PU_PATCH);
-	Z_FreeTag(PU_HUDGFX);
-	Z_FreeTag(PU_HWRPATCHINFO);
-	Z_FreeTag(PU_HWRMODELTEXTURE);
-	Z_FreeTag(PU_HWRCACHE);
-	Z_FreeTag(PU_HWRCACHE_UNLOCKED);
-	Z_FreeTag(PU_HWRPATCHINFO_UNLOCKED);
-	Z_FreeTag(PU_HWRMODELTEXTURE_UNLOCKED);
-}
+	for (block = head.next; block != &head; block = next)
+	{
+		next = block->next; // get link before possibly freeing
 
-// happens before a renderer switch
-void Z_PreparePatchFlush(void)
-{
-	CONS_Debug(DBG_RENDER, "Z_PreparePatchFlush()...\n");
-#ifdef ROTSPRITE
-	R_FreeAllRotSprite();
-#endif
+		if (block->tag >= lowtag && block->tag <= hightag)
+		{
+			void *mem = (UINT8 *)block->hdr + sizeof *block->hdr;
+			boolean free = iterfunc(mem);
+			if (free)
+				Z_Free(mem);
+		}
+	}
 }
 
+// -----------------
+// Utility functions
+// -----------------
+
 // starting value of nextcleanup
 #define CLEANUPCOUNT 2000
 
@@ -794,25 +795,30 @@ static void Command_Memfree_f(void)
 
 	Z_CheckHeap(-1);
 	CONS_Printf("\x82%s", M_GetText("Memory Info\n"));
-	CONS_Printf(M_GetText("Total heap used   : %7s KB\n"), sizeu1(Z_TotalUsage()>>10));
-	CONS_Printf(M_GetText("Static            : %7s KB\n"), sizeu1(Z_TagUsage(PU_STATIC)>>10));
-	CONS_Printf(M_GetText("Static (sound)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_SOUND)>>10));
-	CONS_Printf(M_GetText("Static (music)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_MUSIC)>>10));
-	CONS_Printf(M_GetText("Locked cache      : %7s KB\n"), sizeu1(Z_TagUsage(PU_CACHE)>>10));
-	CONS_Printf(M_GetText("Level             : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVEL)>>10));
-	CONS_Printf(M_GetText("Special thinker   : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVSPEC)>>10));
-	CONS_Printf(M_GetText("All purgable      : %7s KB\n"),
+	CONS_Printf(M_GetText("Total heap used        : %7s KB\n"), sizeu1(Z_TotalUsage()>>10));
+	CONS_Printf(M_GetText("Static                 : %7s KB\n"), sizeu1(Z_TagUsage(PU_STATIC)>>10));
+	CONS_Printf(M_GetText("Static (sound)         : %7s KB\n"), sizeu1(Z_TagUsage(PU_SOUND)>>10));
+	CONS_Printf(M_GetText("Static (music)         : %7s KB\n"), sizeu1(Z_TagUsage(PU_MUSIC)>>10));
+	CONS_Printf(M_GetText("Patches                : %7s KB\n"), sizeu1(Z_TagUsage(PU_PATCH)>>10));
+	CONS_Printf(M_GetText("Patches (low priority) : %7s KB\n"), sizeu1(Z_TagUsage(PU_PATCH_LOWPRIORITY)>>10));
+	CONS_Printf(M_GetText("Patches (rotated)      : %7s KB\n"), sizeu1(Z_TagUsage(PU_PATCH_ROTATED)>>10));
+	CONS_Printf(M_GetText("Sprites                : %7s KB\n"), sizeu1(Z_TagUsage(PU_SPRITE)>>10));
+	CONS_Printf(M_GetText("HUD graphics           : %7s KB\n"), sizeu1(Z_TagUsage(PU_HUDGFX)>>10));
+	CONS_Printf(M_GetText("Locked cache           : %7s KB\n"), sizeu1(Z_TagUsage(PU_CACHE)>>10));
+	CONS_Printf(M_GetText("Level                  : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVEL)>>10));
+	CONS_Printf(M_GetText("Special thinker        : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVSPEC)>>10));
+	CONS_Printf(M_GetText("All purgable           : %7s KB\n"),
 		sizeu1(Z_TagsUsage(PU_PURGELEVEL, INT32_MAX)>>10));
 
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
 	{
-		CONS_Printf(M_GetText("Patch info headers: %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHINFO)>>10));
-		CONS_Printf(M_GetText("Mipmap patches    : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHCOLMIPMAP)>>10));
-		CONS_Printf(M_GetText("HW Texture cache  : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRCACHE)>>10));
-		CONS_Printf(M_GetText("Plane polygons    : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPLANE)>>10));
-		CONS_Printf(M_GetText("HW model textures : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRMODELTEXTURE)>>10));
-		CONS_Printf(M_GetText("HW Texture used   : %7d KB\n"), HWR_GetTextureUsed()>>10);
+		CONS_Printf(M_GetText("Patch info headers     : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHINFO)>>10));
+		CONS_Printf(M_GetText("Cached textures        : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRCACHE)>>10));
+		CONS_Printf(M_GetText("Texture colormaps      : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHCOLMIPMAP)>>10));
+		CONS_Printf(M_GetText("Model textures         : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRMODELTEXTURE)>>10));
+		CONS_Printf(M_GetText("Plane polygons         : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPLANE)>>10));
+		CONS_Printf(M_GetText("All GPU textures       : %7d KB\n"), HWR_GetTextureUsed()>>10);
 	}
 #endif
 
diff --git a/src/z_zone.h b/src/z_zone.h
index 5cbcc6655bc24eb2208627f4cf57e31b48c88a1a..7b58be8f3fce52c01642e20bee0de00c1795bece 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -42,8 +42,13 @@ enum
 
 	PU_SOUND                 = 11, // static while playing
 	PU_MUSIC                 = 12, // static while playing
-	PU_HUDGFX                = 13, // static until WAD added
-	PU_PATCH                 = 14, // static until renderer change
+
+	PU_PATCH                 = 14, // static entire execution time
+	PU_PATCH_LOWPRIORITY     = 15, // lower priority patch, static until level exited
+	PU_PATCH_ROTATED         = 16, // rotated patch, static until level exited or WAD added
+	PU_PATCH_DATA            = 17, // patch data, lifetime depends on the patch that owns it
+	PU_SPRITE                = 18, // sprite patch, static until WAD added
+	PU_HUDGFX                = 19, // HUD patch, static until WAD added
 
 	PU_HWRPATCHINFO          = 21, // Hardware GLPatch_t struct for OpenGL texture cache
 	PU_HWRPATCHCOLMIPMAP     = 22, // Hardware GLMipmap_t struct colormap variation of patch
@@ -63,8 +68,7 @@ enum
 	PU_HWRCACHE_UNLOCKED     = 102, // 'unlocked' PU_HWRCACHE memory:
 									// 'second-level' cache for graphics
                                     // stored in hardware format and downloaded as needed
-	PU_HWRPATCHINFO_UNLOCKED = 103, // 'unlocked' PU_HWRPATCHINFO memory
-	PU_HWRMODELTEXTURE_UNLOCKED = 104, // 'unlocked' PU_HWRMODELTEXTURE memory
+	PU_HWRMODELTEXTURE_UNLOCKED = 103, // 'unlocked' PU_HWRMODELTEXTURE memory
 };
 
 //
@@ -107,6 +111,10 @@ void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignb
 #define Z_FreeTag(tagnum) Z_FreeTags(tagnum, tagnum)
 void Z_FreeTags(INT32 lowtag, INT32 hightag);
 
+// Iterate memory by tag
+#define Z_IterateTag(tagnum, func) Z_IterateTags(tagnum, tagnum, func)
+void Z_IterateTags(INT32 lowtag, INT32 hightag, boolean (*iterfunc)(void *));
+
 //
 // Utility functions
 //
@@ -144,10 +152,4 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag);
 char *Z_StrDup(const char *in);
 #define Z_Unlock(p) (void)p // TODO: remove this now that NDS code has been removed
 
-// For renderer switching
-extern boolean needpatchflush;
-extern boolean needpatchrecache;
-void Z_FlushCachedPatches(void);
-void Z_PreparePatchFlush(void);
-
 #endif