diff --git a/CMakeLists.txt b/CMakeLists.txt
index 25a44f01659a58dcd5b6fcd439b4778970b6b805..5d2d4a7e65982e052c9d8cb222e30fe4fcefe393 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,17 @@
 cmake_minimum_required(VERSION 3.0)
 
+# Enable CCache early
+set(SRB2_USE_CCACHE OFF CACHE BOOL "Use CCache")
+if (${SRB2_USE_CCACHE})
+	find_program(CCACHE_PROGRAM ccache)
+	if(CCACHE_PROGRAM)
+		message(STATUS "Found CCache: ${CCACHE_PROGRAM}")
+		set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
+	else()
+		message(WARNING "You have specified to use CCACHE but it was not found. Object files will not be cached.")
+	endif()
+endif()
+
 file(STRINGS src/version.h SRB2_VERSION)
 string(REGEX MATCH "[0-9]+\\.[0-9.]+" SRB2_VERSION ${SRB2_VERSION})
 
@@ -13,6 +25,10 @@ if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
 	message(FATAL_ERROR "In-source builds will bring you a world of pain. Please make a separate directory to invoke CMake from.")
 endif()
 
+if ((${SRB2_USE_CCACHE}) AND (${CMAKE_C_COMPILER} MATCHES "clang"))
+	message(WARNING "Using clang and CCache: You may want to set environment variable CCACHE_CPP2=yes to prevent include errors during compile.")
+endif()
+
 # Set up CMAKE path
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
 
@@ -117,16 +133,19 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINAR
 
 ##### PACKAGE CONFIGURATION #####
 
-if(${CMAKE_SYSTEM} MATCHES "Windows")
-	set(CPACK_GENERATOR "ZIP")
-endif()
-if(${CMAKE_SYSTEM} MATCHES "Linux")
-	set(CPACK_GENERATOR "TGZ")
-endif()
-if(${CMAKE_SYSTEM} MATCHES "Darwin")
-	set(CPACK_GENERATOR "DragNDrop")
+set(SRB2_CPACK_GENERATOR "" CACHE STRING "Generator to use for making a package. E.g., ZIP, TGZ, DragNDrop (OSX only). Leave blank for default generator.")
+
+if("${SRB2_CPACK_GENERATOR}" STREQUAL "")
+	if(${CMAKE_SYSTEM} MATCHES "Windows")
+		set(SRB2_CPACK_GENERATOR "ZIP")
+	elseif(${CMAKE_SYSTEM} MATCHES "Linux")
+		set(SRB2_CPACK_GENERATOR "TGZ")
+	elseif(${CMAKE_SYSTEM} MATCHES "Darwin")
+		set(SRB2_CPACK_GENERATOR "TGZ")
+	endif()
 endif()
 
+set(CPACK_GENERATOR ${SRB2_CPACK_GENERATOR})
 set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Sonic Robo Blast 2" CACHE STRING "Program name for display purposes")
 set(CPACK_PACKAGE_VENDOR "Sonic Team Jr." CACHE STRING "Vendor name for display purposes")
 #set(CPACK_PACKAGE_DESCRIPTION_FILE )
@@ -135,4 +154,5 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${SRB2_VERSION_MAJOR})
 set(CPACK_PACKAGE_VERSION_MINOR ${SRB2_VERSION_MINOR})
 set(CPACK_PACKAGE_VERSION_PATCH ${SRB2_VERSION_PATCH})
 set(CPACK_PACKAGE_INSTALL_DIRECTORY "CMake ${CMAKE_VERSION_MAJOR}.${CMAKE_VERSION_MINOR}")
+SET(CPACK_OUTPUT_FILE_PREFIX package)
 include(CPack)
diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt
index 095349418ed8901e94ddc229b4cf71d11927e07b..3ea7c28dfb0b950b34b46d2866001ce2d2017380 100644
--- a/assets/CMakeLists.txt
+++ b/assets/CMakeLists.txt
@@ -12,6 +12,9 @@ ENDFUNCTION(PREPEND)
 set(SRB2_ASSET_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/installer"
 	CACHE STRING "Path to directory that contains all asset files for the installer.")
 
+set(SRB2_ASSET_INSTALL ON
+	CACHE BOOL "Insert asset files into the install directory or package.")
+
 ####################
 # POST-V2.2 NOTE: Do not forget to add patch.pk3 to the end of this list!
 ####################
@@ -43,20 +46,27 @@ endforeach()
 
 if(${CMAKE_SYSTEM} MATCHES Darwin)
 	get_target_property(outname SRB2SDL2 OUTPUT_NAME)
-	install(DIRECTORY "${SRB2_ASSET_DIRECTORY}/"
-		DESTINATION "${outname}.app/Contents/Resources"
-	)
+	if(${SRB2_ASSET_INSTALL})
+		install(DIRECTORY "${SRB2_ASSET_DIRECTORY}/"
+			DESTINATION "${outname}.app/Contents/Resources"
+		)
+	endif()
+	# Always install the doc files, even in non-asset packages.
 	install(FILES ${SRB2_ASSET_DOCS}
 		DESTINATION .
 		OPTIONAL
 	)
 else()
-	install(DIRECTORY "${SRB2_ASSET_DIRECTORY}/"
-		DESTINATION .
-	)
-	# Docs are assumed to be located in SRB2_ASSET_DIRECTORY, so don't install again
-	#install(FILES ${SRB2_ASSET_DOCS}
-	#	DESTINATION .
-	#	OPTIONAL
-	#)
+	if(${SRB2_ASSET_INSTALL})
+		install(DIRECTORY "${SRB2_ASSET_DIRECTORY}/"
+			DESTINATION .
+		)
+		# Docs are assumed to be located in SRB2_ASSET_DIRECTORY, so don't install them in their own call.
+	else()
+		# Always install the doc files, even in non-asset packages.
+		install(FILES ${SRB2_ASSET_DOCS}
+			DESTINATION .
+			OPTIONAL
+		)
+	endif()
 endif()
diff --git a/cmake/launch-c.in b/cmake/launch-c.in
new file mode 100644
index 0000000000000000000000000000000000000000..c6055823265594d03c9d16e4d14547c3622150bd
--- /dev/null
+++ b/cmake/launch-c.in
@@ -0,0 +1,3 @@
+#!/bin/sh
+export CCACHE_CPP2=true
+exec "${RULE_LAUNCH_COMPILE}" "${CMAKE_C_COMPILER}" "$@"
diff --git a/cmake/launch-cxx.in b/cmake/launch-cxx.in
new file mode 100644
index 0000000000000000000000000000000000000000..c6055823265594d03c9d16e4d14547c3622150bd
--- /dev/null
+++ b/cmake/launch-cxx.in
@@ -0,0 +1,3 @@
+#!/bin/sh
+export CCACHE_CPP2=true
+exec "${RULE_LAUNCH_COMPILE}" "${CMAKE_C_COMPILER}" "$@"
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..0ea452155181cfd080a65a0713afd8e966531196
--- /dev/null
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -0,0 +1,3141 @@
+// 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/src/CMakeLists.txt b/src/CMakeLists.txt
index 2823eadba161a4ef528b765e366b13f37d4c745d..d3fde6c2e5b7ace84d138dc6a376731ae5b4948c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -228,6 +228,12 @@ set(SRB2_CONFIG_HAVE_OPENMPT ON CACHE BOOL
 	"Enable OpenMPT support.")
 set(SRB2_CONFIG_HAVE_CURL ON CACHE BOOL
 	"Enable curl support, used for downloading files via HTTP.")
+if(${CMAKE_SYSTEM} MATCHES Windows)
+	set(SRB2_CONFIG_HAVE_MIXERX ON CACHE BOOL
+		"Enable SDL Mixer X support.")
+else()
+	set(SRB2_CONFIG_HAVE_MIXERX OFF)
+endif()
 set(SRB2_CONFIG_HWRENDER ON CACHE BOOL
 	"Enable hardware rendering through OpenGL.")
 set(SRB2_CONFIG_USEASM OFF CACHE BOOL
@@ -368,6 +374,30 @@ if(${SRB2_CONFIG_HAVE_OPENMPT})
 	endif()
 endif()
 
+if(${SRB2_CONFIG_HAVE_MIXERX})
+	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+		set(MIXERX_FOUND ON)
+		set(MIXERX_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/SDLMixerX/i686-w64-mingw32/include/SDL2)
+        if(${SRB2_SYSTEM_BITS} EQUAL 64)
+			set(MIXERX_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDLMixerX/x86_64-w64-mingw32/lib -lSDL2_mixer_ext")
+		else() # 32-bit
+			set(MIXERX_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/SDLMixerX/i686-w64-mingw32/lib -lSDL2_mixer_ext")
+		endif()
+	else()
+		# No support for non-Windows (yet?)
+		#find_package(MIXERX)
+		message(WARNING "SDL Mixer X is not supported as an external library.")
+		set(MIXERX_FOUND OFF)
+	endif()
+	if(${MIXERX_FOUND})
+		set(SRB2_HAVE_MIXERX ON)
+		set(SRB2_SDL2_SOUNDIMPL mixer_sound.c)
+		add_definitions(-DHAVE_MIXERX)
+	else()
+		message(WARNING "You have specified that SDL Mixer X is available but it was not found.")
+	endif()
+endif()
+
 if(${SRB2_CONFIG_HAVE_ZLIB})
 	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
 		set(ZLIB_FOUND ON)
@@ -515,6 +545,27 @@ endif()
 
 # Targets
 
+# If using CCACHE, then force it.
+# https://github.com/Cockatrice/Cockatrice/pull/3052/files
+if (${CMAKE_SYSTEM} MATCHES "Darwin")
+	get_property(RULE_LAUNCH_COMPILE GLOBAL PROPERTY RULE_LAUNCH_COMPILE)
+	if(RULE_LAUNCH_COMPILE)
+		MESSAGE(STATUS "Force enabling CCache usage under macOS")
+		# Set up wrapper scripts
+		configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/launch-c.in   ${CMAKE_BINARY_DIR}/launch-c)
+		configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/launch-cxx.in ${CMAKE_BINARY_DIR}/launch-cxx)
+		execute_process(COMMAND chmod a+rx
+			"${CMAKE_BINARY_DIR}/launch-c"
+			"${CMAKE_BINARY_DIR}/launch-cxx")
+
+		# Set Xcode project attributes to route compilation through our scripts
+		set(CMAKE_XCODE_ATTRIBUTE_CC         "${CMAKE_BINARY_DIR}/launch-c")
+		set(CMAKE_XCODE_ATTRIBUTE_CXX        "${CMAKE_BINARY_DIR}/launch-cxx")
+		set(CMAKE_XCODE_ATTRIBUTE_LD         "${CMAKE_BINARY_DIR}/launch-c")
+		set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${CMAKE_BINARY_DIR}/launch-cxx")
+	endif()
+endif()
+
 # Compatibility flag with later versions of GCC
 # We should really fix our code to not need this
 if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 4a2c0687be1a4ce7a448ffe47043c0cc25f3e2c9..5c56978e717da251d54494ab68399213b2f92e82 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -12,14 +12,15 @@
 # to avoid a false positive with the version detection...
 
 SUPPORTED_GCC_VERSIONS:=\
-	91\
-	81 82 83\
-	71 72\
+	101 102\
+	91 92 93\
+	81 82 83 84\
+	71 72 73 74 75\
 	61 62 63 64\
-	51 52 53 54\
+	51 52 53 54 55\
 	40 41 42 43 44 45 46 47 48 49
 
-LATEST_GCC_VERSION=9.1
+LATEST_GCC_VERSION=10.2
 
 # gcc or g++
 ifdef PREFIX
@@ -68,7 +69,27 @@ ifeq   (,$(filter GCC%,$(.VARIABLES)))
  endif
 endif
 
+ifdef GCC102
+GCC101=1
+endif
+
+ifdef GCC101
+GCC93=1
+endif
+
+ifdef GCC93
+GCC92=1
+endif
+
+ifdef GCC92
+GCC91=1
+endif
+
 ifdef GCC91
+GCC84=1
+endif
+
+ifdef GCC84
 GCC83=1
 endif
 
@@ -81,6 +102,18 @@ GCC81=1
 endif
 
 ifdef GCC81
+GCC75=1
+endif
+
+ifdef GCC75
+GCC74=1
+endif
+
+ifdef GCC74
+GCC73=1
+endif
+
+ifdef GCC73
 GCC72=1
 endif
 
@@ -105,6 +138,10 @@ GCC61=1
 endif
 
 ifdef GCC61
+GCC55=1
+endif
+
+ifdef GCC55
 GCC54=1
 endif
 
diff --git a/src/command.c b/src/command.c
index 4973812e7c14bf87c951c92ed385b6d1689b04a1..0a46839f343f45c69034cf52918ac6a3cb4b56e2 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.
@@ -818,6 +819,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
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index c4e127e5ea10965e0cd5cee262e241740972fbcd..7ccb4ef92c398a6e0b31545482a96b010740f97b 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -3664,6 +3664,9 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		CL_RemovePlayer(pnum, kickreason);
 }
 
+static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}};
+consvar_t cv_netticbuffer = {"netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 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 CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
@@ -5428,6 +5431,10 @@ void TryRunTics(tic_t realtics)
 				ExtraDataTicker();
 				gametic++;
 				consistancy[gametic%BACKUPTICS] = Consistancy();
+
+				// 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;
 			}
 	}
 }
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 44e0cca11b5055eb261cfe0df6fc5e3fe35b885b..ce8d87adb53079d2eb25e1a29aadd70a65eebac2 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -542,7 +542,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;
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 592734067ffc629b4525251f6d8f207fa40faa60..6aa168aa7680fbe804e382f71ac4142f017e9964 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -701,6 +701,7 @@ void D_RegisterClientCommands(void)
 #endif
 	CV_RegisterVar(&cv_rollingdemos);
 	CV_RegisterVar(&cv_netstat);
+	CV_RegisterVar(&cv_netticbuffer);
 
 #ifdef NETGAME_DEVMODE
 	CV_RegisterVar(&cv_fishcake);
diff --git a/src/dehacked.c b/src/dehacked.c
index d02dc3d24ae797a5fe628bab54570e186d55bc79..9d6729dc22cbc6e899f2d6f3fa9c5257908dface 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -806,8 +806,43 @@ static void readskincolor(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "NAME"))
 			{
-				deh_strlcpy(skincolors[num].name, word2,
-					sizeof (skincolors[num].name), va("Skincolor %d: name", num));
+				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"))
 			{
@@ -818,10 +853,15 @@ static void readskincolor(MYFILE *f, INT32 num)
 					if ((tmp = strtok(NULL,",")) == NULL)
 						break;
 				}
+				skincolor_modified[num] = true;
 			}
 			else if (fastcmp(word, "INVCOLOR"))
 			{
-				skincolors[num].invcolor = (UINT16)get_number(word2);
+				UINT16 v = (UINT16)get_number(word2);
+				if (v < numskincolors)
+					skincolors[num].invcolor = v;
+				else
+					skincolors[num].invcolor = SKINCOLOR_GREEN;
 			}
 			else if (fastcmp(word, "INVSHADE"))
 			{
@@ -4688,11 +4728,11 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 				{
 					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)
+					if (i && i < numskincolors)
 						readskincolor(f, i);
 					else
 					{
-						deh_warning("Skincolor %d out of range (%d - %d)", i, SKINCOLOR_FIRSTFREESLOT, numskincolors-1);
+						deh_warning("Skincolor %d out of range (1 - %d)", i, numskincolors-1);
 						ignorelines(f);
 					}
 				}
@@ -8900,7 +8940,6 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_ANGLEMAN",
 	"MT_POLYANCHOR",
 	"MT_POLYSPAWN",
-	"MT_POLYSPAWNCRUSH",
 
 	// Skybox objects
 	"MT_SKYBOX",
@@ -10628,7 +10667,7 @@ static inline int lib_freeslot(lua_State *L)
 					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);
+					lua_pushinteger(L, S_FIRSTFREESLOT + i);
 					r++;
 					break;
 				}
@@ -10643,7 +10682,7 @@ static inline int lib_freeslot(lua_State *L)
 					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);
+					lua_pushinteger(L, MT_FIRSTFREESLOT + i);
 					r++;
 					break;
 				}
@@ -10659,7 +10698,7 @@ static inline int lib_freeslot(lua_State *L)
 					FREE_SKINCOLORS[i] = Z_Malloc(strlen(word)+1, PU_STATIC, NULL);
 					strcpy(FREE_SKINCOLORS[i],word);
 					M_AddMenuColor(numskincolors++);
-					lua_pushinteger(L, i);
+					lua_pushinteger(L, SKINCOLOR_FIRSTFREESLOT + i);
 					r++;
 					break;
 				}
@@ -10680,11 +10719,12 @@ static inline int lib_freeslot(lua_State *L)
 					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");
 			}
-			r++;
 		}
 		else if (fastcmp(type, "TOL"))
 		{
diff --git a/src/doomdata.h b/src/doomdata.h
index 77ed56bc23e853f825159ccbc60b1c7d3f98e823..f3890c846536dc438573104f98398bace13f7415 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -23,6 +23,8 @@
 // Some global defines, that configure the game.
 #include "doomdef.h"
 
+#include "m_fixed.h" // See the mapthing_t scale.
+
 //
 // Map level types.
 // The following data structures define the persistent format
@@ -193,16 +195,24 @@ 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;
+	fixed_t scale;
+	INT16 tag;
+	INT32 args[NUMMAPTHINGARGS];
+	char *stringargs[NUMMAPTHINGSTRINGARGS];
+
 	struct mobj_s *mobj;
 } mapthing_t;
 
diff --git a/src/g_demo.c b/src/g_demo.c
index 6c58f12fb4b7137c1c8be099cde85d3253093aaa..57a955cb130a3dfa886ab73ad408bfd0b5fce68e 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -2382,9 +2382,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 cce4ac822b65e5ddb1248e52be6663353a9d7ac9..e165d5415a0f6085b069259263a100cf1d22c104 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2013,7 +2013,9 @@ boolean G_Responder(event_t *ev)
 		if (F_CreditResponder(ev))
 		{
 			// Skip credits for everyone
-			if (!netgame || server || IsPlayerAdmin(consoleplayer))
+			if (! serverrunning)/* hahahahahaha */
+				F_StartGameEvaluation();
+			else if (server || IsPlayerAdmin(consoleplayer))
 				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
 			return true;
 		}
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 0d7404c7740760c81793912f58d5b2781c2e01bf..ddc935f0da134c23daf44ed15ec89c5739af908a 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -214,6 +214,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;
@@ -491,7 +494,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);
@@ -525,8 +528,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))));\
 		}\
@@ -770,6 +771,7 @@ FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf)
 {
 	switch (transtablenum)
 	{
+		case 0          : pSurf->PolyColor.s.alpha = 0x00;return  PF_Masked;
 		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;
@@ -1438,37 +1440,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 (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:
@@ -1489,7 +1464,10 @@ 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;
 			}
 
@@ -2280,8 +2258,8 @@ static void HWR_AddLine(seg_t * line)
 	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!
@@ -2574,8 +2552,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
@@ -2757,8 +2735,11 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 		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;
@@ -2769,13 +2750,13 @@ 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 (gl_frontsector)
@@ -2784,23 +2765,25 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 		{
 			scrollx = FIXED_TO_FLOAT(gl_frontsector->floor_xoffs)/fflatwidth;
 			scrolly = FIXED_TO_FLOAT(gl_frontsector->floor_yoffs)/fflatheight;
-			angle = gl_frontsector->floorpic_angle>>ANGLETOFINESHIFT;
+			angle = gl_frontsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
 			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceiling_xoffs)/fflatwidth;
 			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceiling_yoffs)/fflatheight;
-			angle = gl_frontsector->ceilingpic_angle>>ANGLETOFINESHIFT;
+			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
@@ -2830,10 +2813,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);
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 08d688e1dd9036524f48c10da59b13c412977529..461966224d764b6f9d194b6a0609d892fe3edc7e 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -678,6 +678,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
 //
@@ -775,6 +798,9 @@ static const char *fragment_shaders[] = {
 		"gl_FragColor = texture2D(tex, gl_TexCoord[0].st);\n"
 	"}\0",
 
+	// Model fragment shader + diffuse lighting from above
+	GLSL_SOFTWARE_MODEL_LIGHTING_FRAGMENT_SHADER,
+
 	NULL,
 };
 
@@ -795,6 +821,20 @@ static const char *fragment_shaders[] = {
 		"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"
+
 static const char *vertex_shaders[] = {
 	// Default vertex shader
 	GLSL_DEFAULT_VERTEX_SHADER,
@@ -820,6 +860,9 @@ static const char *vertex_shaders[] = {
 	// Sky vertex shader
 	GLSL_DEFAULT_VERTEX_SHADER,
 
+	// Model vertex shader + diffuse lighting from above
+	GLSL_MODEL_LIGHTING_VERTEX_SHADER,
+
 	NULL,
 };
 
@@ -1063,6 +1106,11 @@ EXPORT void HWRAPI(SetShader) (int shader)
 #ifdef GL_SHADERS
 	if (gl_allowshaders)
 	{
+		// If using model lighting, set the appropriate shader.
+		// However don't override a custom shader.
+		// Should use an enum or something...
+		if (shader == 4 && model_lighting && !gl_shaderprograms[4].custom)
+			shader = 8;
 		if ((GLuint)shader != gl_currentshaderprogram)
 		{
 			gl_currentshaderprogram = shader;
@@ -1535,7 +1583,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 					// 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);
+					pglAlphaFunc(GL_ALWAYS, 0.0f); // Don't discard zero alpha fragments
 					break;
 				default : // must be 0, otherwise it's an error
 					// No blending
@@ -2660,31 +2708,34 @@ 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
@@ -2874,9 +2925,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
diff --git a/src/info.c b/src/info.c
index 778f1f7418c97e9cd3af6c05787c79c8e15aa4e5..fccd1b2693d62926a2e19a4d9524349f79ae2056 100644
--- a/src/info.c
+++ b/src/info.c
@@ -20867,33 +20867,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
diff --git a/src/info.h b/src/info.h
index 4d8e18d63f370989e21fc5f5597f47f77c1d0cd7..721ebf7f269c8d575a5b717a3bc7d76def2b5730 100644
--- a/src/info.h
+++ b/src/info.h
@@ -4774,7 +4774,6 @@ typedef enum mobj_type
 	MT_ANGLEMAN,
 	MT_POLYANCHOR,
 	MT_POLYSPAWN,
-	MT_POLYSPAWNCRUSH,
 
 	// Skybox objects
 	MT_SKYBOX,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 23aa060fe72a2507f0484d5c795ae04192c4fae6..6b25e32ea5e1746536b31111cf9eea749d42c453 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -176,6 +176,11 @@ static const struct {
 
 	{META_SECTORLINES,  "sector_t.lines"},
 	{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"},
@@ -1113,6 +1118,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)
 {
@@ -2550,6 +2565,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)
@@ -3108,6 +3131,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);
@@ -3378,6 +3512,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},
@@ -3493,6 +3628,7 @@ static luaL_Reg lib[] = {
 
 	// r_draw
 	{"R_GetColorByName", lib_rGetColorByName},
+	{"R_GetSuperColorByName", lib_rGetSuperColorByName},
 	{"R_GetNameByColor", lib_rGetNameByColor},
 
 	// s_sound
@@ -3519,6 +3655,8 @@ static luaL_Reg lib[] = {
 	{"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_consolelib.c b/src/lua_consolelib.c
index 4fe234deeecc88f6643d04bd0663a070b3fa85be..fae4f2d14b3ab91eff6eb83fb2a7c6591c96aa63 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -236,15 +236,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 +252,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;
@@ -444,7 +442,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");
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 5cfd1bd3d461f49e774593dd87f0daac77ee08e1..ebf6f9deb2c5f72b87faf3cbb98d6cdfaa4aea7a 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -1186,7 +1186,7 @@ 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;
 
 		if (lua_gettop(gL) == 1)
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 81a215c5363c96655b41782f24bb67d30aad671f..830d97625df628468254c63ad80b00637a544acd 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -19,14 +19,15 @@
 #include "z_zone.h"
 #include "r_patch.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
 
-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);
 state_t *astate;
@@ -1490,7 +1491,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 +1513,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.
@@ -1540,7 +1541,16 @@ 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 (strchr(info->name, ' ') != NULL)
+				CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", info->name);
+
+			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 +1559,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;
 		}
@@ -1607,20 +1620,28 @@ 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 (strchr(info->name, ' ') != NULL)
+			CONS_Alert(CONS_WARNING, "skincolor_t field 'name' ('%s') contains spaces.\n", info->name);
+
+		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 +1650,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 +1698,17 @@ 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!");
 	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..f987c79fdfd25bd52b945b0c7ac052f447d7f36a 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -54,6 +54,10 @@ extern lua_State *gL;
 
 #define META_SECTORLINES "SECTOR_T*LINES"
 #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"
 #ifdef HAVE_LUA_SEGS
 #define META_NODEBBOX "NODE_T*BBOX"
 #define META_NODECHILDREN "NODE_T*CHILDREN"
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 144a6b3f185ad50ff259612345e6610a2234af1c..216818d29b63e85356c698884ea94d93bff79844 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -85,9 +85,13 @@ enum line_e {
 	line_flags,
 	line_special,
 	line_tag,
+	line_args,
+	line_stringargs,
 	line_sidenum,
 	line_frontside,
 	line_backside,
+	line_alpha,
+	line_executordelay,
 	line_slopetype,
 	line_frontsector,
 	line_backsector,
@@ -106,9 +110,13 @@ static const char *const line_opt[] = {
 	"flags",
 	"special",
 	"tag",
+	"args",
+	"stringargs",
 	"sidenum",
 	"frontside",
 	"backside",
+	"alpha",
+	"executordelay",
 	"slopetype",
 	"frontsector",
 	"backsector",
@@ -691,6 +699,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));
@@ -731,6 +775,12 @@ static int line_get(lua_State *L)
 	case line_tag:
 		lua_pushinteger(L, line->tag);
 		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);
 		return 1;
@@ -742,6 +792,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)
 		{
@@ -2143,6 +2199,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");
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 129339b96fde68c6eab8b3878c91aacadfa529f5..4fa938ea54ba0025f87994a0f0bc68a49d467e64 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -31,6 +31,8 @@ enum mobj_e {
 	mobj_snext,
 	mobj_sprev,
 	mobj_angle,
+	mobj_pitch,
+	mobj_roll,
 	mobj_rollangle,
 	mobj_sprite,
 	mobj_frame,
@@ -98,6 +100,8 @@ static const char *const mobj_opt[] = {
 	"snext",
 	"sprev",
 	"angle",
+	"pitch",
+	"roll",
 	"rollangle",
 	"sprite",
 	"frame",
@@ -165,14 +169,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 +205,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;
@@ -457,6 +468,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;
@@ -752,6 +769,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 +830,32 @@ 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 = mt->tag;
+	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;
@@ -814,10 +885,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 +904,8 @@ 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"))
+		mt->tag = (INT16)luaL_checkinteger(L, 3);
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
@@ -902,6 +981,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");
diff --git a/src/lua_script.c b/src/lua_script.c
index 9d7d536b971a45da3f45ee7022e06daf636e4edf..0260f018ab73c15c86487bbfb60082e65089d375 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -136,6 +136,9 @@ 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;
diff --git a/src/m_menu.c b/src/m_menu.c
index 41f663c19a99918834057dcad8badc919748e87b..57d2ed023d1f7734811028e8984b6623e1d030f3 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1489,21 +1489,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_STRING | IT_CVAR,  NULL,  "Music Preference", &cv_musicpref, 51},
 
-	{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_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
@@ -2202,7 +2204,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(
diff --git a/src/p_local.h b/src/p_local.h
index b6c34f357a18e2ca55211cca139eb429c5d871af..4077fecf6b36c2aa880b6d6cfa7c3ab546682377 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -142,6 +142,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);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 6f3f53559103b4bc16143d0cc727e53152744f35..fcba1f690d58e115de9b1c5c5305c6d2a3afc2c7 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -442,7 +442,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
@@ -2882,8 +2882,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);
 			}
 		}
 	}
@@ -11615,7 +11614,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);
 
@@ -11625,14 +11624,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)
@@ -11648,17 +11648,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.
@@ -11691,7 +11691,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;
@@ -11699,7 +11699,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)
@@ -12583,10 +12583,16 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		break;
 	}
 	case MT_SKYBOX:
+		if (mthing->tag < 0 || mthing->tag > 15)
+		{
+			CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", mthing->tag, sizeu1((size_t)(mthing - mapthings)));
+			break;
+		}
+
 		if (mthing->options & MTF_OBJECTSPECIAL)
-			skyboxcenterpnts[mthing->extrainfo] = mobj;
+			skyboxcenterpnts[mthing->tag] = mobj;
 		else
-			skyboxviewpnts[mthing->extrainfo] = mobj;
+			skyboxviewpnts[mthing->tag] = mobj;
 		break;
 	case MT_EGGSTATUE:
 		if (mthing->options & MTF_EXTRA)
@@ -13074,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, mthing->scale);
+	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
@@ -13176,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;
@@ -13321,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++)
 	{
@@ -13379,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 643e3d04c0b58d440f426f40aafffbb18e078952..27a6ef4f05d3a2f8a5a9cf57b8fe495fe4325ff0 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -278,7 +278,7 @@ 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
@@ -399,7 +399,7 @@ 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
@@ -452,7 +452,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);
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index ccf8519f6c021f3bfaeea7420debf50d2a7cf11c..b0a794ddf1601d0a6818907081f6395e486c7e0d 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -204,47 +204,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
@@ -483,10 +480,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;
@@ -508,10 +501,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;
@@ -564,9 +557,9 @@ static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
 	vertex_t  dist, sspot;
 	size_t i;
 
-	if (!(po = Polyobj_GetForNum(anchor->angle)))
+	if (!(po = Polyobj_GetForNum(anchor->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", anchor->tag);
 		return;
 	}
 
@@ -1314,8 +1307,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)
 		{
 			++numPolyObjects;
 
@@ -1350,7 +1342,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, qitem->mo->spawnpoint->tag);
 		}
 
 		// move polyobjects to spawn points
diff --git a/src/p_polyobj.h b/src/p_polyobj.h
index c6ae716f44bab61e4e45ead48f07a22e4bbb4214..f24caca4e7263102e4b2de2b9d4311435858dc28 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
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index dd4ade115f647e92b52b1e559b91428fa0052370..276b3eb0547341107462a373c17681364b05fcef 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -787,10 +787,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
@@ -1085,6 +1113,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 = &sides[li->sidenum[0]];
@@ -1111,10 +1148,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);
@@ -1147,6 +1185,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);
@@ -1202,6 +1267,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);
+
 	}
 }
 
@@ -1400,7 +1495,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)
@@ -1556,6 +1653,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)
 	{
@@ -2513,12 +2612,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)
 	{
diff --git a/src/p_setup.c b/src/p_setup.c
index 2fd7ba5b095207db4cd1838bbaa1e17f693f85ef..ceb96df40e36836abcb8f49673f0904c665fbdc4 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -92,6 +92,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;
@@ -355,8 +356,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;
@@ -1039,6 +1040,8 @@ static void P_LoadSectors(UINT8 *data)
 
 		ss->floorpic_angle = ss->ceilingpic_angle = 0;
 
+		ss->colormap_protected = false;
+
 		P_InitializeSector(ss);
 	}
 }
@@ -1145,6 +1148,10 @@ static void P_LoadLinedefs(UINT8 *data)
 		ld->flags = SHORT(mld->flags);
 		ld->special = SHORT(mld->special);
 		ld->tag = 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));
 
@@ -1218,9 +1225,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
@@ -1371,6 +1380,11 @@ static void P_LoadThings(UINT8 *data)
 		mt->type = READUINT16(data);
 		mt->options = READUINT16(data);
 		mt->extrainfo = (UINT8)(mt->type >> 12);
+		mt->scale = FRACUNIT;
+		mt->tag = 0;
+		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
+		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
+		mt->pitch = mt->roll = 0;
 
 		mt->type &= 4095;
 
@@ -1475,6 +1489,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"))
@@ -1503,6 +1530,48 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 		sectors[i].floorpic_angle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
 	else if (fastcmp(param, "rotationceiling"))
 		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))
+		sectors[i].colormap_protected = true;
 }
 
 static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
@@ -1533,10 +1602,29 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 		P_SetLinedefV1(i, atol(val));
 	else if (fastcmp(param, "v2"))
 		P_SetLinedefV2(i, atol(val));
+	else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
+	{
+		size_t argnum = atol(param + 3);
+		if (argnum >= NUMLINEARGS)
+			return;
+		lines[i].args[argnum] = atol(val);
+	}
+	else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9)
+	{
+		size_t argnum = param[9] - '0';
+		if (argnum >= NUMLINESTRINGARGS)
+			return;
+		lines[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
+		M_Memcpy(lines[i].stringargs[argnum], val, strlen(val) + 1);
+	}
 	else if (fastcmp(param, "sidefront"))
 		lines[i].sidenum[0] = atol(val);
 	else if (fastcmp(param, "sideback"))
 		lines[i].sidenum[1] = atol(val);
+	else if (fastcmp(param, "alpha"))
+		lines[i].alpha = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "executordelay"))
+		lines[i].executordelay = atol(val);
 
 	// Flags
 	else if (fastcmp(param, "blocking") && fastcmp("true", val))
@@ -1575,6 +1663,8 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 
 static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 {
+	if (fastcmp(param, "id"))
+		mapthings[i].tag = atol(val);
 	if (fastcmp(param, "x"))
 		mapthings[i].x = atol(val);
 	else if (fastcmp(param, "y"))
@@ -1583,18 +1673,39 @@ static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 		mapthings[i].z = atol(val);
 	else if (fastcmp(param, "angle"))
 		mapthings[i].angle = atol(val);
+	else if (fastcmp(param, "pitch"))
+		mapthings[i].pitch = atol(val);
+	else if (fastcmp(param, "roll"))
+		mapthings[i].roll = atol(val);
 	else if (fastcmp(param, "type"))
 		mapthings[i].type = atol(val);
-
+	else if (fastcmp(param, "scale") || fastcmp(param, "scalex") || fastcmp(param, "scaley"))
+		mapthings[i].scale = FLOAT_TO_FIXED(atof(val));
 	// Flags
 	else if (fastcmp(param, "extra") && fastcmp("true", val))
 		mapthings[i].options |= MTF_EXTRA;
 	else if (fastcmp(param, "flip") && fastcmp("true", val))
 		mapthings[i].options |= MTF_OBJECTFLIP;
-	else if (fastcmp(param, "special") && fastcmp("true", val))
+	else if (fastcmp(param, "objectspecial") && fastcmp("true", val))
 		mapthings[i].options |= MTF_OBJECTSPECIAL;
 	else if (fastcmp(param, "ambush") && fastcmp("true", val))
 		mapthings[i].options |= MTF_AMBUSH;
+
+	else if (fastncmp(param, "arg", 3) && strlen(param) > 3)
+	{
+		size_t argnum = atol(param + 3);
+		if (argnum >= NUMMAPTHINGARGS)
+			return;
+		mapthings[i].args[argnum] = atol(val);
+	}
+	else if (fastncmp(param, "stringarg", 9) && strlen(param) > 9)
+	{
+		size_t argnum = param[9] - '0';
+		if (argnum >= NUMMAPTHINGSTRINGARGS)
+			return;
+		mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL);
+		M_Memcpy(mapthings[i].stringargs[argnum], val, strlen(val) + 1);
+	}
 }
 
 /** From a given position table, run a specified parser function through a {}-encapsuled text.
@@ -1657,6 +1768,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)
@@ -1710,8 +1829,24 @@ static void P_LoadTextmap(void)
 
 		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);
 	}
 
@@ -1722,6 +1857,10 @@ static void P_LoadTextmap(void)
 		ld->flags = 0;
 		ld->special = 0;
 		ld->tag = 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;
 
@@ -1760,11 +1899,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;
+		mt->scale = FRACUNIT;
+		mt->tag = 0;
+		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
+		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->mobj = NULL;
 
 		TextmapParse(mapthingsPos[i], i, ParseTextmapThingParameter);
@@ -1780,9 +1923,9 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 		ld->frontsector = sides[ld->sidenum[0]].sector; //e6y: Can't be -1 here
 		ld->backsector = ld->sidenum[1] != 0xffff ? 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
@@ -1798,6 +1941,41 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 					M_Memcpy(ld->text + strlen(ld->text) + 1, sides[ld->sidenum[1]].text, strlen(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;
 		}
 	}
 }
@@ -1805,11 +1983,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;
 	}
@@ -1864,7 +2042,7 @@ static boolean P_LoadMapData(const virtres_t *virt)
 	numlevelflats = 0;
 
 	// Load map data.
-	if (textmap)
+	if (udmf)
 		P_LoadTextmap();
 	else
 	{
@@ -1972,7 +2150,12 @@ static void P_InitializeSeg(seg_t *seg)
 {
 	if (seg->linedef)
 	{
-		seg->sidedef = &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 = &sides[side];
 
 		seg->frontsector = seg->sidedef->sector;
 		seg->backsector = (seg->linedef->flags & ML_TWOSIDED) ? sides[seg->linedef->sidenum[seg->side ^ 1]].sector : NULL;
@@ -2041,7 +2224,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;
@@ -2166,10 +2349,8 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 				segs[k - 1 + ((m == 0) ? subsectors[i].numlines : 0)].v2 = segs[k].v1 = &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 >= numlines)
 					I_Error("P_LoadExtendedSubsectorsAndSegs: Seg %s in subsector %d has invalid linedef %d!\n", sizeu1(k), m, linenum);
 				segs[k].glseg = (linenum == 0xFFFF);
@@ -2737,6 +2918,259 @@ static void P_LinkMapData(void)
 	}
 }
 
+/** Hashes the sector tags across the sectors and linedefs.
+  *
+  * \sa P_FindSectorFromTag, P_ChangeSectorTag
+  * \author Lee Killough
+  */
+static inline void P_InitTagLists(void)
+{
+	register size_t i;
+
+	for (i = numsectors - 1; i != (size_t)-1; i--)
+	{
+		size_t j = (unsigned)sectors[i].tag % numsectors;
+		sectors[i].nexttag = sectors[j].firsttag;
+		sectors[j].firsttag = (INT32)i;
+	}
+
+	for (i = numlines - 1; i != (size_t)-1; i--)
+	{
+		size_t j = (unsigned)lines[i].tag % numlines;
+		lines[i].nexttag = lines[j].firsttag;
+		lines[j].firsttag = (INT32)i;
+	}
+}
+
+//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++)
+	{
+		switch (lines[i].special)
+		{
+		case 20: //PolyObject first line
+		{
+			INT32 paramline = P_FindSpecialLineFromTag(22, lines[i].tag, -1);
+
+			//PolyObject ID
+			lines[i].args[0] = lines[i].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] = lines[i].tag;
+			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] = lines[i].tag;
+			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] = lines[i].tag;
+			break;
+		case 606: //Colormap
+			lines[i].args[0] = lines[i].tag;
+			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] = lines[i].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] = lines[i].tag;
+			if (lines[i].special != 720)
+				lines[i].args[1] = lines[i].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:
+		case 760:
+		case 761:
+			mapthings[i].tag = mapthings[i].angle;
+			break;
+		case 762:
+		{
+			INT32 firstline = P_FindSpecialLineFromTag(20, mapthings[i].angle, -1);
+			if (firstline != -1)
+				lines[firstline].args[3] |= TMPF_CRUSH;
+			mapthings[i].tag = mapthings[i].angle;
+			mapthings[i].type = 761;
+			break;
+		}
+		case 780:
+			mapthings[i].tag = 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
@@ -2765,11 +3199,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];
@@ -2800,6 +3236,8 @@ 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");
+	udmf = textmap != NULL;
 
 	if (!P_LoadMapData(virt))
 		return false;
@@ -2808,6 +3246,11 @@ static boolean P_LoadMapFromFile(void)
 
 	P_LinkMapData();
 
+	P_InitTagLists();   // Create xref tables for tags
+
+	if (!udmf)
+		P_ConvertBinaryMap();
+
 	// Copy relevant map data for NetArchive purposes.
 	spawnsectors = Z_Calloc(numsectors * sizeof(*sectors), PU_LEVEL, NULL);
 	spawnlines = Z_Calloc(numlines * sizeof(*lines), PU_LEVEL, NULL);
@@ -3682,8 +4125,7 @@ boolean P_LoadLevel(boolean fromnetsave)
 	if (!P_LoadMapFromFile())
 		return false;
 
-	// 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_SpawnSlopes(fromnetsave);
diff --git a/src/p_slopes.c b/src/p_slopes.c
index 6aeb1b02596633e7c4d6a9db1e0f0853b6c244c3..e93b0f6c993650043a1afe588c21aa1b64bbac37 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -245,32 +245,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;
 	}
 
@@ -297,7 +295,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;
 		}
 
@@ -363,7 +361,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;
 		}
 
@@ -428,11 +426,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] && mt->tag == tag1)
 			vertices[0] = mt;
-		else if (!vertices[1] && mt->angle == tag2)
+		else if (!vertices[1] && mt->tag == tag2)
 			vertices[1] = mt;
-		else if (!vertices[2] && mt->angle == tag3)
+		else if (!vertices[2] && mt->tag == tag3)
 			vertices[2] = mt;
 	}
 
@@ -462,44 +460,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;
@@ -555,6 +545,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;
+
+	if (!tag || *secslope)
+		return false;
+
+	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0;)
+	{
+		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
 //
@@ -563,25 +594,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
 }
@@ -614,20 +651,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;
 
@@ -642,8 +669,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 06d900b666b91c8a901392a4f243b0941a0d05e7..46e8dc1e7e730dec73cfc8ffcd3aff4c4acdfeb4 100644
--- a/src/p_slopes.h
+++ b/src/p_slopes.h
@@ -18,6 +18,35 @@
 extern pslope_t *slopelist;
 extern UINT16 slopecount;
 
+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);
diff --git a/src/p_spec.c b/src/p_spec.c
index b7fdedfd09dd9a03595be38651846fb87205652c..f30e25f7f4d2309faa06d5843efcb5fd5dc5f5b4 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1496,30 +1496,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
-  */
-static inline void P_InitTagLists(void)
-{
-	register size_t i;
-
-	for (i = numsectors - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)sectors[i].tag % numsectors;
-		sectors[i].nexttag = sectors[j].firsttag;
-		sectors[j].firsttag = (INT32)i;
-	}
-
-	for (i = numlines - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)lines[i].tag % numlines;
-		lines[i].nexttag = lines[j].firsttag;
-		lines[j].firsttag = (INT32)i;
-	}
-}
-
 /** Finds minimum light from an adjacent sector.
   *
   * \param sector Sector to start in.
@@ -1563,16 +1539,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);
 }
@@ -1973,7 +1957,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);
@@ -2061,7 +2045,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);
@@ -3294,10 +3278,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
@@ -3420,46 +3404,53 @@ 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 = P_FindSectorFromTag(line->args[1], -1);
+					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)
+			}
+
+			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0;)
+			{
+				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)))
 					{
@@ -3471,10 +3462,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))
 			{
@@ -3748,15 +3739,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 = P_FindSectorFromTag(line->args[1], -1);
+					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;
+				}
+			}
+
+			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0;)
 			{
 				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))
 				{
@@ -3764,19 +3775,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)))
 					{
@@ -3792,35 +3803,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)))
 				{
@@ -3831,13 +3833,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 ;)
+			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0 ;)
 				P_ResetColormapFader(&sectors[secnum]);
 			break;
 
@@ -4027,6 +4029,23 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 465: // Set linedef executor delay
+			{
+				INT32 linenum;
+
+				if (!udmf)
+					break;
+
+				for (linenum = -1; (linenum = P_FindLineFromTag(line->args[0], linenum)) >= 0 ;)
+				{
+					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);
@@ -5890,7 +5909,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;
@@ -6133,7 +6152,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)
@@ -6164,8 +6183,6 @@ void P_InitSpecials(void)
 
 	// Set globalweather
 	globalweather = mapheaderinfo[gamemap-1]->weather;
-
-	P_InitTagLists();   // Create xref tables for tags
 }
 
 static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
@@ -7145,8 +7162,32 @@ void P_SpawnSpecials(boolean fromnetsave)
 				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;
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0], s)) >= 0;)
+				{
+					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 = P_FindSectorFromTag(lines[i].args[1], -1);
+							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:
@@ -8109,7 +8150,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();
diff --git a/src/p_user.c b/src/p_user.c
index 679f4064b4950378769df1c2cbf785e175d530c9..44793b0cc1074684361c9bdccc3bc04ecb45cecf 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -5536,7 +5536,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)
@@ -7975,20 +7975,13 @@ 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)
@@ -12895,3 +12888,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/r_data.c b/src/r_data.c
index d10919d81891fe04a4dff8d458afb0ba1da0ad0d..befb73c20a5e5e75b8307d0ba83c5dd72454fe1d 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -2080,7 +2080,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
@@ -2219,10 +2219,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;
@@ -2345,6 +2343,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))
@@ -2356,17 +2361,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;
@@ -2398,7 +2402,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;
@@ -2434,7 +2437,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);
@@ -2461,8 +2464,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);
 
diff --git a/src/r_data.h b/src/r_data.h
index 8b8d08c52e35a959a981c58388ac07e655f35b9d..fda342083547d46e00ff52549061fb275a94a13d 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -149,13 +149,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);
diff --git a/src/r_defs.h b/src/r_defs.h
index fd868ee97daed196294a650a1ec4836acc9da403..132106bc88c0dd0b28d93fb0cf24e51947bf804d 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -333,6 +333,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
@@ -374,6 +375,9 @@ typedef enum
 
 #define HORIZONSPECIAL 41
 
+#define NUMLINEARGS 6
+#define NUMLINESTRINGARGS 2
+
 typedef struct line_s
 {
 	// Vertices, from v1 to v2.
@@ -386,9 +390,13 @@ typedef struct line_s
 	INT16 flags;
 	INT16 special;
 	INT16 tag;
+	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
 
diff --git a/src/r_draw.c b/src/r_draw.c
index 5351ef37f079b151a0c5c3effd5fe3d006109783..2b798c3bf383c42d22e99674e1ceb4521a85c352 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -134,6 +134,7 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define NUM_PALETTE_ENTRIES 256
 
 static UINT8** translationtablecache[MAXSKINS + 7] = {NULL};
+UINT8 skincolor_modified[MAXSKINCOLORS];
 
 CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
 
@@ -362,6 +363,7 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags
 {
 	UINT8* ret;
 	INT32 skintableindex;
+	INT32 i;
 
 	// Adjust if we want the default colormap
 	switch (skinnum)
@@ -385,6 +387,15 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags
 
 		// 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], i>=MAXSKINS ? MAXSKINS-i-1 : i, color);
+			skincolor_modified[color] = false;
+		}
 	}
 	else ret = NULL;
 
@@ -427,12 +438,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..1ca22f18aa7f80840c9c367dcb2c0199bb62a5c0 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -117,6 +117,9 @@ void R_FlushTranslationColormapCache(void);
 UINT16 R_GetColorByName(const char *name);
 UINT16 R_GetSuperColorByName(const char *name);
 
+// Color ramp modification should force a recache
+extern UINT8 skincolor_modified[];
+
 // Custom player skin translation
 void R_InitViewBuffer(INT32 width, INT32 height);
 void R_InitViewBorder(void);
diff --git a/src/r_main.c b/src/r_main.c
index ead1403becb93140c3265ea652f19784bbab6daa..4f79dd8db4a445b40a5340dc07b9e5812901f444 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -318,6 +318,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) ?
diff --git a/src/r_main.h b/src/r_main.h
index 99a25d86ea4a20f9a4e23f9a8a7ce622b9a1c28e..729ec6973d595ee71ab1b59fbf2389cf62f6eb2d 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -63,6 +63,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);
diff --git a/src/r_segs.c b/src/r_segs.c
index 741a25254037fbc1dd15017603e116d3ee4f8f7d..d9fc758381da988c054953a92e4809f49940d5b8 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -274,6 +274,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;
@@ -300,31 +305,24 @@ 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 = transtables + ((R_GetLinedefTransTable(ldef->alpha) - 1) << FF_TRANSSHIFT);
+		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)
 	{
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..a1484a2b3d754c95731620e9d7dfd2cd5818acd7 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -464,12 +464,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)
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_things.c b/src/r_things.c
index d2647b8111b7c86a8674025bad6730472784475b..c0795acd5a68708884f5908dcbb83ca64fce5e85 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2773,7 +2773,7 @@ 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)))
 	));
 }
 
diff --git a/src/s_sound.c b/src/s_sound.c
index 5ed9fd83a22d8f475c7400878db264d329a42bd1..072a69f6c13de72fcaa2311f6f4e56f27c9fe150 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -60,6 +60,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);
@@ -129,6 +130,14 @@ consvar_t cv_gamedigimusic = {"digimusic", "On", CV_SAVE|CV_CALL|CV_NOINIT, CV_O
 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};
 
+// Music preference
+static CV_PossibleValue_t cons_musicpref_t[] = {
+	{0, "Digital"},
+	{1, "MIDI"},
+	{0, NULL}
+};
+consvar_t cv_musicpref = {"musicpref", "Digital", CV_SAVE|CV_CALL|CV_NOINIT, cons_musicpref_t, MusicPref_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
 // 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};
@@ -301,6 +310,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
@@ -1847,19 +1857,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 +2097,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 +2153,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 +2201,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;
 }
@@ -2330,6 +2332,8 @@ 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;
@@ -2359,7 +2363,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);
 
@@ -2665,32 +2670,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 +2705,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 +2716,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..35d1c3dc5e57276b0cdae86c34f8fe9cb9db362e 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;
@@ -178,11 +179,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/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt
index 70aa68c88b366e94bf1b75200426c59c4da39b95..687e8b388f69f283244ccc45a1e8dffd57660d2a 100644
--- a/src/sdl/CMakeLists.txt
+++ b/src/sdl/CMakeLists.txt
@@ -1,6 +1,10 @@
 # Declare SDL2 interface sources
 
-set(SRB2_CONFIG_SDL2_USEMIXER ON CACHE BOOL "Use SDL2_mixer or regular sdl sound")
+if(NOT ${SRB2_CONFIG_HAVE_MIXERX})
+	set(SRB2_CONFIG_SDL2_USEMIXER ON CACHE BOOL "Use SDL2_mixer or regular sdl sound")
+else()
+	set(SRB2_CONFIG_SDL2_USEMIXER OFF)
+endif()
 
 if(${SRB2_CONFIG_SDL2_USEMIXER})
 	if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
@@ -22,6 +26,8 @@ if(${SRB2_CONFIG_SDL2_USEMIXER})
 		message(WARNING "You specified that SDL2_mixer is available, but it was not found. Falling back to sdl sound.")
 		set(SRB2_SDL2_SOUNDIMPL sdl_sound.c)
 	endif()
+elseif(${MIXERX_FOUND})
+	set(SRB2_SDL2_SOUNDIMPL mixer_sound.c)
 else()
 	set(SRB2_SDL2_SOUNDIMPL sdl_sound.c)
 endif()
@@ -156,6 +162,7 @@ if(${SDL2_FOUND})
 			SDL2_mixer
 			${GME_LIBRARIES}
 			${OPENMPT_LIBRARIES}
+			${MIXERX_LIBRARIES}
 			${PNG_LIBRARIES}
 			${ZLIB_LIBRARIES}
 			${OPENGL_LIBRARIES}
@@ -168,6 +175,7 @@ if(${SDL2_FOUND})
 			${SDL2_MIXER_LIBRARIES}
 			${GME_LIBRARIES}
 			${OPENMPT_LIBRARIES}
+			${MIXERX_LIBRARIES}
 			${PNG_LIBRARIES}
 			${ZLIB_LIBRARIES}
 			${OPENGL_LIBRARIES}
@@ -249,28 +257,33 @@ if(${SDL2_FOUND})
 		${SDL2_MIXER_INCLUDE_DIRS}
 		${GME_INCLUDE_DIRS}
 		${OPENMPT_INCLUDE_DIRS}
+		${MIXERX_INCLUDE_DIRS}
 		${PNG_INCLUDE_DIRS}
 		${ZLIB_INCLUDE_DIRS}
 		${OPENGL_INCLUDE_DIRS}
 		${CURL_INCLUDE_DIRS}
 	)
 
-	if(${SRB2_HAVE_MIXER})
+	if((${SRB2_HAVE_MIXER}) OR (${SRB2_HAVE_MIXERX}))
 		target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_MIXER -DSOUND=SOUND_MIXER)
 	endif()
 
 	target_compile_definitions(SRB2SDL2 PRIVATE
-		-DHAVE_SDL
+		-DDDIRECTFULLSCREEN -DHAVE_SDL
 	)
 
-	## strip debug symbols into separate file when using gcc
-	if(CMAKE_COMPILER_IS_GNUCC)
-		if(${CMAKE_BUILD_TYPE} MATCHES Debug)
+	## strip debug symbols into separate file when using gcc.
+	## to be consistent with Makefile, don't generate for OS X.
+	if((CMAKE_COMPILER_IS_GNUCC) AND NOT (${CMAKE_SYSTEM} MATCHES Darwin))
+		if((${CMAKE_BUILD_TYPE} MATCHES Debug) OR (${CMAKE_BUILD_TYPE} MATCHES RelWithDebInfo))
+			if(${CMAKE_BUILD_TYPE} MATCHES Debug)
+				set(OBJCOPY_ONLY_KEEP_DEBUG "--only-keep-debug")
+			endif()
 			message(STATUS "Will make separate debug symbols in *.debug")
 			add_custom_command(TARGET SRB2SDL2 POST_BUILD
-				COMMAND ${OBJCOPY} --only-keep-debug $<TARGET_FILE:SRB2SDL2> $<TARGET_FILE:SRB2SDL2>.debug
+				COMMAND ${OBJCOPY} ${OBJCOPY_ONLY_KEEP_DEBUG} $<TARGET_FILE:SRB2SDL2> $<TARGET_FILE:SRB2SDL2>.debug
 				COMMAND ${OBJCOPY} --strip-debug $<TARGET_FILE:SRB2SDL2>
-				COMMAND ${OBJCOPY} --add-gnu-debuglink=$<TARGET_FILE_NAME:SRB2SDL2>.debug $<TARGET_FILE:SRB2SDL2>
+				COMMAND ${OBJCOPY} --add-gnu-debuglink=$<TARGET_FILE:SRB2SDL2>.debug $<TARGET_FILE:SRB2SDL2>
 			)
 		endif()
 	endif()
@@ -284,6 +297,15 @@ if(${SDL2_FOUND})
 		install(TARGETS SRB2SDL2 SRB2SDL2
 			RUNTIME DESTINATION .
 		)
+		if ((${CMAKE_BUILD_TYPE} MATCHES Debug) OR (${CMAKE_BUILD_TYPE} MATCHES RelWithDebInfo))
+			set(SRB2_DEBUG_INSTALL OFF CACHE BOOL "Insert *.debug file into the install directory or package.")
+			if (${SRB2_DEBUG_INSTALL})
+				install(FILES $<TARGET_FILE:SRB2SDL2>.debug
+					DESTINATION .
+					OPTIONAL
+				)
+			endif()
+		endif()
 	endif()
 
 	if(${CMAKE_SYSTEM} MATCHES Windows)
@@ -297,6 +319,7 @@ if(${SDL2_FOUND})
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/x86_64-w64-mingw32/bin
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/x86_64-w64-mingw32/bin
 							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86_64/mingw
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDLMixerX/x86_64-w64-mingw32/bin
 						)
 					else()
 						find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}"
@@ -304,6 +327,7 @@ if(${SDL2_FOUND})
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/i686-w64-mingw32/bin
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/i686-w64-mingw32/bin
 							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86/mingw
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDLMixerX/i686-w64-mingw32/bin
 						)
 					endif()
 				else()
@@ -313,6 +337,7 @@ if(${SDL2_FOUND})
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/lib/x64
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/lib/x64
 							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86_64/mingw
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDLMixerX/x86_64-w64-mingw32/bin
 						)
 					else()
 						find_library(SRB2_SDL2_DLL_${dllname} "${defaultname}"
@@ -320,6 +345,7 @@ if(${SDL2_FOUND})
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2/lib/x86
 							HINTS ${CMAKE_SOURCE_DIR}/libs/SDL2_mixer/lib/x86
 							HINTS ${CMAKE_SOURCE_DIR}/libs/libopenmpt/bin/x86/mingw
+							HINTS ${CMAKE_SOURCE_DIR}/libs/SDLMixerX/i686-w64-mingw32/bin
 						)
 					endif()
 				endif()
@@ -343,6 +369,12 @@ if(${SDL2_FOUND})
 		if(${SRB2_CONFIG_HAVE_OPENMPT})
 			getwinlib(libopenmpt "libopenmpt.dll")
 		endif()
+		if(${SRB2_CONFIG_HAVE_MIXERX})
+			getwinlib(SDL2_mixer_ext "SDL2_mixer_ext.dll")
+			getwinlib(libfluidsynth-2 "libfluidsynth-2.dll")
+			getwinlib(libgcc_s_sjlj-1 "libgcc_s_sjlj-1.dll")
+			getwinlib(libstdc++-6 "libstdc++-6.dll")
+		endif()
 
 		install(PROGRAMS
 			${win_extra_dll_list}
diff --git a/src/w_wad.c b/src/w_wad.c
index e6dedba227122c00093a5231fca071c2132db8da..e4a19f30e3084c8b1a4786c64202304185c4284f 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1862,7 +1862,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
 }
diff --git a/src/y_inter.c b/src/y_inter.c
index 58e0c4a885702496deacd774a85f8441f9e86015..d857d60dc0815e6f8eb1991c3f47887107eb6289 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -141,7 +141,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;
@@ -330,7 +329,7 @@ void Y_IntermissionDrawer(void)
 		safetorender = false;
 	}
 
-	if (!usebuffer || !safetorender)
+	if (!safetorender)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	if (!safetorender)
@@ -359,12 +358,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)
@@ -1266,7 +1264,6 @@ void Y_StartIntermission(void)
 			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
@@ -2084,7 +2081,6 @@ static void Y_UnloadData(void)
 
 	// unload the background patches
 	UNLOAD(bgpatch);
-	UNLOAD(widebgpatch);
 	UNLOAD(bgtile);
 	UNLOAD(interpic);
 
@@ -2127,7 +2123,6 @@ static void Y_CleanupData(void)
 {
 	// unload the background patches
 	CLEANUP(bgpatch);
-	CLEANUP(widebgpatch);
 	CLEANUP(bgtile);
 	CLEANUP(interpic);
 
diff --git a/tools/masterserver/.gitignore b/tools/masterserver/.gitignore
index 9f45745a36fda74c3d242e2a1ccd4eb673c15999..8ae879eec144d61b8662f1b319be2d2e97dc373f 100644
--- a/tools/masterserver/.gitignore
+++ b/tools/masterserver/.gitignore
@@ -1,4 +1,4 @@
 /client
 /server
-/server.log
-/*.o
+/*.log
+/*.o
\ No newline at end of file
diff --git a/tools/masterserver/ipcs.h b/tools/masterserver/ipcs.h
index b9dc52fdf55966b448b19b631904a97f66530fa7..4e144f4a57c252d4ab27e1d46f3acbf0e505a7ba 100644
--- a/tools/masterserver/ipcs.h
+++ b/tools/masterserver/ipcs.h
@@ -140,8 +140,8 @@ typedef struct
 	char port[8];
 	char name[32];
 	INT32 room;
-	char key[32]; // Secret key for linking dedicated servers to accounts
 	char version[8]; // format is: x.yy.z (like 1.30.2 or 1.31)
+	char key[32]; // Secret key for linking dedicated servers to accounts
 } ATTRPACK msg_server_t;
 
 typedef struct
diff --git a/tools/masterserver/masterserver.sh b/tools/masterserver/masterserver.sh
index 9b1adb128c06905cfd743fc082fd43e335c095e0..fe4ba00718ad052c54da2208c5f7d96f11947ad0 100755
--- a/tools/masterserver/masterserver.sh
+++ b/tools/masterserver/masterserver.sh
@@ -5,9 +5,10 @@
 
 # Get LSB functions
 . /lib/lsb/init-functions
-. /etc/default/rcS
+#. /etc/default/rcS
 
-SRB2MS=/usr/local/bin/masterserver
+#SRB2MS=/usr/local/bin/masterserver
+SRB2MS=./server
 SRB2MS_PORT=28900
 
 # Check that the package is still installed
@@ -15,11 +16,9 @@ SRB2MS_PORT=28900
 
 case "$1" in
 	start)
-		log_begin_msg "Starting SRB2MS..."
+		log_begin_msg "Starting SRB2MS...\n"
 		umask 002
-		if start-stop-daemon --start \
-		--exec $SRB2MS \
-		-- $SRB2MS_PORT; then
+		if exec $SRB2MS $SRB2MS_PORT & then
 			log_end_msg 0
 		else
 			log_end_msg $?
@@ -27,11 +26,11 @@ case "$1" in
 	;;
 
 	stop)
-		log_begin_msg "Stopping SRB2MS..."
-		if start-stop-daemon --stop --exec $SRB2MS; then
-		log_end_msg 0
+		log_begin_msg "Stopping SRB2MS...\n"
+		if killall $SRB2MS -q & then
+			log_end_msg 0
 		else
-		log_end_msg $?
+			log_end_msg $?
 		fi
 	;;
 
@@ -40,7 +39,7 @@ case "$1" in
 	;;
 
 	*)
-	e	cho "Usage: /etc/init.d/masterserver {start|stop|restart|force-reload}"
+	echo "Usage: $0 {start|stop|restart|force-reload}"
 		exit 1
 	;;
 esac
diff --git a/tools/masterserver/server.cpp b/tools/masterserver/server.cpp
index b7ed0d6b48db8d92f9ba9668e9587f9278ed887a..d0020530179ce45ae429a50d03177a6af752947b 100644
--- a/tools/masterserver/server.cpp
+++ b/tools/masterserver/server.cpp
@@ -86,7 +86,7 @@ typedef struct
 
 //=============================================================================
 
-#define HOSTNAME "loopback"
+#define HOSTNAME "localhost"
 #define USER "srb2_ms"
 #define PASSWORD "gLRDRb7WgLRDRb7W"
 #define DATABASE "srb2_ms"
@@ -291,17 +291,17 @@ void MySQL_AddServer(const char *ip, const char *port, const char *name, const c
         char checkquery[500];
         char updatequery[5000];
         char queryp1[5000] = "INSERT INTO `ms_servers` (`name`,`ip`,`port`,`version`,`timestamp`,`room`,`key`) VALUES ('%s','%s','%s','%s','%ld','%d','%s')";
-        char checkqueryp1[500] = "SELECT room_override FROM `ms_servers` WHERE `ip` = '%s'";
+        char checkqueryp1[500] = "SELECT room_override FROM `ms_servers` WHERE `ip` = '%s' AND `port` = '%s'";
 		char updatequeryp1[5000];
 		if(firstadd)
 		{
 			logPrintf(logfile, "First add.\n");
-			strcpy(updatequeryp1, "UPDATE `ms_servers` SET `name` = '%s', `port` = '%s', `version` = '%s', timestamp = '%ld', upnow = '1', `room` = '%d', `delisted` = '0', `key` = '%s' WHERE `ip` = '%s'");
+			strcpy(updatequeryp1, "UPDATE `ms_servers` SET `name` = '%s', `port` = '%s', `version` = '%s', timestamp = '%ld', upnow = '1', `room` = '%d', `delisted` = '0', `key` = '%s' WHERE `ip` = '%s' AND `port` = '%s'");
 		}
 		else
 		{
 			logPrintf(logfile, "Update ping.\n");
-			strcpy(updatequeryp1, "UPDATE `ms_servers` SET `name` = '%s', `port` = '%s', `version` = '%s', timestamp = '%ld', upnow = '1', `room` = '%d', `key` = '%s' WHERE `ip` = '%s' AND `delisted` = '0'");
+			strcpy(updatequeryp1, "UPDATE `ms_servers` SET `name` = '%s', `port` = '%s', `version` = '%s', timestamp = '%ld', upnow = '1', `room` = '%d', `key` = '%s' WHERE `ip` = '%s' AND `port` = '%s' AND `delisted` = '0'");
 		}
         MySQL_Conn(false);
         mysql_real_escape_string(conn, escapedName, name, (unsigned long)strlen(name));
@@ -314,10 +314,10 @@ void MySQL_AddServer(const char *ip, const char *port, const char *name, const c
 			logPrintf(errorfile, "IP %s tried to use the private room %d! THIS SHOULD NOT HAPPEN\n", ip, room);
 			return;
 		}
-        sprintf(checkquery, checkqueryp1, ip);
+        sprintf(checkquery, checkqueryp1, ip, port);
         time_t timestamp;
         timestamp = time (NULL);
-        logPrintf(logfile, "Checking for existing servers in table with the same IP...\n");
+        logPrintf(logfile, "Checking for existing servers in table with the same IP and port...\n");
         logPrintf(mysqlfile, "Executing MySQL Query: %s\n", checkquery);
         if(mysql_query(conn, checkquery)) {
           logPrintf(errorfile, "MYSQL ERROR: %s\n", mysql_error(conn));
@@ -341,9 +341,9 @@ void MySQL_AddServer(const char *ip, const char *port, const char *name, const c
 			if(atoi(row[0]) != 0)
 				room = atoi(row[0]);
             mysql_free_result(res);
-            logPrintf(logfile, "Server's IP already exists, so let's just update it instead...\n");
+            logPrintf(logfile, "Server's IP and port already exists, so let's just update it instead...\n");
             logPrintf(logfile, "Updating Server Data for %s\n", ip);
-            sprintf(updatequery, updatequeryp1, escapedName, escapedPort, escapedVersion, timestamp, room, escapedKey, ip);
+            sprintf(updatequery, updatequeryp1, escapedName, escapedPort, escapedVersion, timestamp, room, escapedKey, ip, port);
             logPrintf(mysqlfile, "Executing MySQL Query: %s\n", updatequery);
             if(mysql_query(conn, updatequery)) {
                logPrintf(errorfile, "MYSQL ERROR: %s\n", mysql_error(conn));
@@ -619,10 +619,10 @@ void MySQL_ListServServers(UINT32 id, UINT32 type, const char *ip) {
 void MySQL_RemoveServer(char *ip, char *port, char *name, char *version) {
         char escapedName[255];
         char updatequery[5000];
-        char updatequeryp1[5000] = "UPDATE `ms_servers` SET upnow = '0' WHERE `ip` = '%s' AND `permanent` = '0'";
+        char updatequeryp1[5000] = "UPDATE `ms_servers` SET upnow = '0' WHERE `ip` = '%s' AND `port` = '%s' AND `permanent` = '0'";
         MySQL_Conn(false);
         mysql_real_escape_string(conn, escapedName, name, (unsigned long)strlen(name));
-        sprintf(updatequery, updatequeryp1, ip);
+        sprintf(updatequery, updatequeryp1, ip, port);
         logPrintf(mysqlfile, "Executing MySQL Query: %s\n", updatequery);
         if(mysql_query(conn, updatequery)) {
            logPrintf(errorfile, "MYSQL ERROR: %s\n", mysql_error(conn));
@@ -841,6 +841,10 @@ static void addServer(int id, char *buffer, bool firstadd)
 	info->port[sizeof (info->port)-1] = '\0';
 	info->name[sizeof (info->name)-1] = '\0';
 	info->version[sizeof (info->version)-1] = '\0';
+
+	logPrintf(logfile, "addServer(): Version = \"%s\"\n", info->version);
+	logPrintf(logfile, "addServer(): Key = \"%s\"\n", info->key);
+
 	// retrieve the true ip of the server
 	strcpy(info->ip, server_socket.getClientIP(id));
 	//strcpy(info->port, server_socket.getClientPort(id));
@@ -995,7 +999,7 @@ int main(int argc, char *argv[])
 
 	if (server_socket.listen(argv[1]) < 0)
 	{
-		fprintf(stderr, "Error while initializing the server\n");
+		fprintf(stderr, "Error while initializing the server; port being used! Try killing the other Master Server.\n");
 		exit(2);
 	}
 
diff --git a/tools/masterserver/structure.sql b/tools/masterserver/structure.sql
index 3cc2cb15bbcfa24f4bdd37968bc982bbeb5f87ca..013c223839813b45cc51454ac4031b3444f9eb60 100644
--- a/tools/masterserver/structure.sql
+++ b/tools/masterserver/structure.sql
@@ -13,8 +13,8 @@ SET time_zone = "+00:00";
 -- Database: `srb2ms`
 --
 
-CREATE DATABASE IF NOT EXISTS `srb2ms` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
-USE `srb2ms`;
+CREATE DATABASE IF NOT EXISTS `srb2_ms` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+USE `srb2_ms`;
 
 
 -- --------------------------------------------------------
@@ -25,8 +25,8 @@ USE `srb2ms`;
 
 CREATE TABLE `ms_bans` (
   `bid` int(11) DEFAULT NULL,
-  `ipstart` int(11) DEFAULT NULL,
-  `ipend` int(11) DEFAULT NULL,
+  `ipstart` int(10) unsigned DEFAULT NULL,
+  `ipend` int(10) unsigned DEFAULT NULL,
   `full_endtime` int(11) DEFAULT NULL,
   `permanent` tinyint(1) DEFAULT NULL,
   `hostonly` tinyint(1) DEFAULT NULL,
@@ -63,19 +63,19 @@ INSERT INTO `ms_rooms` (`room_id`, `title`, `motd`, `visible`, `order`, `private
 --
 
 CREATE TABLE `ms_servers` (
-  `sid` int(11) NOT NULL,
+  `sid` int(11) primary key AUTO_INCREMENT,
   `name` text COLLATE utf8mb4_unicode_ci NOT NULL,
   `ip` text COLLATE utf8mb4_unicode_ci NOT NULL,
-  `port` int(11) NOT NULL,
+  `port` int(11) NOT NULL DEFAULT 5029,
   `version` text COLLATE utf8mb4_unicode_ci NOT NULL,
-  `timestamp` int(11) NOT NULL,
-  `room` int(11) NOT NULL,
+  `timestamp` int(11) NOT NULL DEFAULT 0,
+  `room` int(11) NOT NULL DEFAULT 0,
   `key` text COLLATE utf8mb4_unicode_ci NOT NULL,
-  `room_override` int(11) NOT NULL,
-  `upnow` tinyint(1) NOT NULL,
-  `permanent` tinyint(1) NOT NULL,
-  `delisted` tinyint(1) NOT NULL,
-  `sticky` int(11) NOT NULL
+  `room_override` int(11) NOT NULL DEFAULT 0,
+  `upnow` tinyint(1) NOT NULL DEFAULT 1,
+  `permanent` tinyint(1) NOT NULL DEFAULT 0,
+  `delisted` tinyint(1) NOT NULL DEFAULT 0,
+  `sticky` int(11) NOT NULL DEFAULT 0
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
 -- --------------------------------------------------------
@@ -85,16 +85,20 @@ CREATE TABLE `ms_servers` (
 --
 
 CREATE TABLE `ms_versions` (
-  `mod_id` int(11) NOT NULL,
-  `mod_version` int(11) NOT NULL
+  `mod_id` int(10) unsigned primary key AUTO_INCREMENT,
+  `mod_version` int(10) unsigned NOT NULL DEFAULT 1,
+  `mod_vstring` varchar(45) NOT NULL DEFAULT 'v1.0',
+  `mod_codebase` int(10) unsigned NOT NULL DEFAULT 205,
+  `mod_name` varchar(255) NOT NULL DEFAULT 'Default MOD Name',
+  `mod_url` text NOT NULL 
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
 
 --
 -- Dumping data for table `ms_versions`
 --
 
-INSERT INTO `ms_versions` (`mod_id`, `mod_version`) VALUES
-(12, 25);
+INSERT INTO `ms_versions` (`mod_id`, `mod_version`, `mod_vstring`, `mod_codebase`, `mod_name`, `mod_url`) VALUES
+(18, 42, 'v2.2.2', 205, 'SRB2 2.2', 'SRB2.org');
 
 -- --------------------------------------------------------
 
@@ -114,4 +118,4 @@ COMMIT;
 
 /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
-/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
\ No newline at end of file