diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt
index 68ff0fdf9fdd64134989b8a2a16fd7c74ff589a5..8811536826795daf8667d943a5a725ea112e19cf 100644
--- a/assets/CMakeLists.txt
+++ b/assets/CMakeLists.txt
@@ -13,11 +13,10 @@ 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_HASHED
-"srb2.srb;\
+"srb2.pk3;\
 player.dta;\
-rings.dta;\
-zones.dta;\
-patch.dta"
+zones.pk3;\
+patch.pk3"
 	CACHE STRING "Asset filenames to apply MD5 checks. No spaces between entries!"
 )
 
diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..de5b2ea6c0439405f9099eb7373e1b3b4e6d1aa9
--- /dev/null
+++ b/extras/conf/SRB2-22.cfg
@@ -0,0 +1,6482 @@
+/*********************************************************\
+	Zone Builder Game Configuration
+	For Sonic Robo Blast 2 Version 2.2
+	Contributors (alphabetical):
+	* Foxboy
+	* JJames19119
+	* Kalaron
+	* Kristos
+	* MascaraSnake
+	* mazmazz
+	* Morpheus
+	* Neo Chaotikal
+	* Nev3r
+	* Oogaland
+	* Rob
+	* Shadow Hog
+	* Spherallic
+	* SRB2-Playah
+	* SSNTails
+	* SteelT
+	* ST218
+	* toaster
+	* Viola
+\*********************************************************/
+
+// 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";
+
+//GZDB specific. Don't try to load lumps that don't exist.
+basegame = 0;
+
+// This is the simplified game engine/sourceport name
+engine = "zdoom";
+
+// When this is set to true, sectors with the same tag will light up when a line is highlighted
+linetagindicatesectors = true;
+
+// The format interface handles the map data format - DoomMapSetIO for SRB2DB2, SRB2MapSetIO for Zone Builder
+formatinterface = "SRB2MapSetIO";
+
+//Sky textures for vanilla maps
+defaultskytextures
+{
+	SKY1 = "MAP01,MAP02,MAP03,MAP50,MAPA1,MAPA2,MAPA5,MAPA6,MAPA9,MAPAA,MAPAB,MAPAC,MAPAD,MAPAE,MAPAG,MAPAJ,MAPAK,MAPF0,MAPF1,MAPFA,MAPM0,MAPM8,MAPMA,MAPMB,MAPMC";
+	SKY4 = "MAP04,MAP06,MAP51,MAPF8,MAPM1";
+	SKY6 = "MAP05";
+	SKY7 = "MAP07,MAP08,MAP09,MAP52,MAPM2,MAPM5";
+	SKY10 = "MAP12,MAP53,MAPM3";
+	SKY11 = "MAP10,MAP11,MAP16,MAP55,MAPF2,MAPF5,MAPF6,MAPF9,MAPM7";
+	SKY13 = "MAP13,MAP54,MAPAS";
+	SKY21 = "MAPAF,MAPF7,MAPM4";
+	SKY22 = "MAP22,MAP23,MAP24,MAP25,MAP56,MAPAN,MAPAO,MAPF4,MAPM6";
+	SKY29 = "MAP58,MAPAV";
+	SKY30 = "MAP30";
+	SKY35 = "MAP41";
+	SKY40 = "MAP40";
+	SKY55 = "MAPF3,MAPM9";
+	SKY66 = "MAPAT";
+	SKY99 = "MAP57";
+	SKY103 = "MAPA3,MAPA4,MAPAU";
+	SKY107 = "MAPA7,MAPA8";
+	SKY117 = "MAPAH,MAPAI";
+	SKY127 = "MAPAR";
+	SKY132 = "MAPAW";
+}
+
+// Default lump name for new map
+defaultlumpname = "MAP01";
+
+// Default testing parameters
+testparameters = "-file \"%AP\" \"%F\" -warp %L";
+testshortpaths = true;
+
+// Default nodebuilder configurations
+defaultsavecompiler = "zennode_normal";
+defaulttestcompiler = "zennode_fast";
+
+// Skill levels
+skills
+{
+	1 = "Normal";
+}
+
+// Skins
+skins
+{
+	Sonic;
+	Tails;
+	Knuckles;
+	Metalsonic;
+	Fang;
+	Amy;
+}
+
+// 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";
+}
+
+// Special linedefs
+soundlinedefflag = 64;	// See linedefflags
+singlesidedflag = 1;	// See linedefflags
+doublesidedflag = 4;	// See linedefflags
+impassableflag = 1;
+upperunpeggedflag = 8;
+lowerunpeggedflag = 16;
+repeatmidtextureflag = 1024;
+pegmidtextureflag = 256;
+
+// Generalized actions
+generalizedlinedefs = false;
+generalizedsectors = true;
+
+// Texture loading options
+defaultwalltexture = "GFZROCK";
+defaultfloortexture = "GFZFLR01";
+defaultceilingtexture = "F_SKY1";
+mixtexturesflats = true;
+defaulttexturescale = 1.0f;
+defaultflatscale = 1.0f;
+
+// Thing number for start position in 3D Mode
+start3dmode = 3328;
+
+
+
+
+/*
+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.
+Kalaron: and now TX_START
+*/
+
+// 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";
+	}
+}
+
+
+/*
+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
+{
+	~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;
+	}
+}
+
+scriptlumpnames
+{
+	MAINCFG
+	{
+		script = "SOC.cfg";
+	}
+
+	OBJCTCFG
+	{
+		script = "SOC.cfg";
+	}
+
+	SOC_
+	{
+		script = "SOC.cfg";
+		isprefix = true;
+	}
+
+	LUA_
+	{
+		script = "Lua.cfg";
+		isprefix = true;
+	}
+}
+
+// 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;
+}
+
+// SECTOR TYPES-----------------------------------------------------------------
+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/Rings 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";
+}
+
+
+// GENERALISED SECTOR TYPES-----------------------------------------------------------------
+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/Rings 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";
+	}
+}
+
+// LINEDEF FLAGS
+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 = "secret";
+	64 = "blocksound";
+	128 = "dontdraw";
+	256 = "mapped";
+}
+
+// LINEDEF ACTIVATIONS
+linedefactivations
+{
+}
+
+// LINEDEF TYPES
+linedeftypes
+{
+	misc
+	{
+		title = "Miscellaneous";
+
+		0
+		{
+			title = "None";
+			prefix = "(0)";
+		}
+
+		1
+		{
+			title = "Per-Sector Gravity";
+			prefix = "(1)";
+			flags64text = "[6] Flip in reverse gravity";
+		}
+
+		5
+		{
+			title = "Camera Scanner";
+			prefix = "(5)";
+		}
+
+		7
+		{
+			title = "Sector Flat Alignment";
+			prefix = "(7)";
+			flags2048text = "[11] Don't align floor";
+			flags4096text = "[12] Don't align ceiling";
+			flags8192text = "[13] Use texture offsets";
+		}
+
+		10
+		{
+			title = "Culling Plane";
+			prefix = "(10)";
+			flags64text = "[6] Cull only while in sector";
+		}
+
+		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)";
+			flags2text = "[1] Check emeralds";
+			flags64text = "[6] Skip score tally";
+		}
+
+		3
+		{
+			title = "Zoom Tube Parameters";
+			prefix = "(3)";
+			flags512text = "[9] Ignore player direction";
+		}
+
+		4
+		{
+			title = "Speed Pad Parameters";
+			prefix = "(4)";
+			flags512text = "[9] No teleport to center";
+			flags1024text = "[10] Force spinning frames";
+		}
+
+		8
+		{
+			title = "Special Sector Properties";
+			prefix = "(8)";
+			flags32text = "[5] Invert precipitation";
+			flags64text = "[6] Touch only ceiling";
+			flags128text = "[7] Allow opposite gravity";
+			flags256text = "[8] Touch sector edge";
+			flags512text = "[9] Touch floor or ceiling";
+		}
+
+		9
+		{
+			title = "Chain Parameters";
+			prefix = "(9)";
+			flags32text = "[5] Swing instead of spin";
+			flags64text = "[6] Player-turnable chain";
+			flags128text = "[7] Make chain from maces";
+			flags256text = "[8] Spawn mace at origin";
+			flags512text = "[9] Don't clip inside ground";
+			flags1024text = "[10] No distance check";
+		}
+
+		11
+		{
+			title = "Rope Hang Parameters";
+			prefix = "(11)";
+			flags32text = "[5] Don't loop";
+			flags64text = "[6] Static";
+		}
+
+		12
+		{
+			title = "Rock Spawner Parameters";
+			prefix = "(12)";
+			flags64text = "[6] Randomize speed";
+		}
+
+		14
+		{
+			title = "Bustable Block Parameters";
+			prefix = "(14)";
+			flags32text = "[5] Particles launch from center";
+		}
+
+		15
+		{
+			title = "Fan Particle Spawner Parameters";
+			prefix = "(15)";
+		}
+
+		16
+		{
+			title = "Minecart Parameters";
+			prefix = "(16)";
+		}
+
+		64
+		{
+			title = "Continuously Appearing/Disappearing FOF";
+			prefix = "(64)";
+			flags2text = "[1] Use control sector tag";
+			flags64text = "[6] No sound effect";
+		}
+
+		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)";
+			flags64text = "[6] Trigger linedef executor";
+			flags128text = "[7] Intangible";
+			flags256text = "[8] Stopped by pushables";
+			flags512text = "[9] Render flats";
+		}
+
+		30
+		{
+			title = "Waving Flag";
+			prefix = "(30)";
+		}
+
+		31
+		{
+			title = "Displacement by Front Sector";
+			prefix = "(31)";
+		}
+
+		32
+		{
+			title = "Angular Displacement by Front Sector";
+			prefix = "(32)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Don't turn players";
+			flags512text = "[9] Turn all objects";
+		}
+	}
+
+	planemove
+	{
+		title = "Plane Movement";
+
+		52
+		{
+			title = "Continuously Falling Sector";
+			prefix = "(52)";
+			flags64text = "[6] Continuously rising";
+		}
+
+		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)";
+			flags64text = "[6] Move upwards at start";
+		}
+
+		60
+		{
+			title = "Activate Moving Platform (Adjustable Speed)";
+			prefix = "(60)";
+			flags64text = "[6] Move upwards at start";
+		}
+
+		61
+		{
+			title = "Crusher (Ceiling to Floor)";
+			prefix = "(61)";
+			flags512text = "[9] Double, constant speed";
+		}
+
+		62
+		{
+			title = "Crusher (Floor to Ceiling)";
+			prefix = "(62)";
+			flags512text = "[9] Double, constant speed";
+		}
+
+		66
+		{
+			title = "Move Floor by Displacement";
+			prefix = "(66)";
+			flags64text = "[6] Inverse movement";
+		}
+
+		67
+		{
+			title = "Move Ceiling by Displacement";
+			prefix = "(67)";
+			flags64text = "[6] Inverse movement";
+		}
+
+		68
+		{
+			title = "Move Floor and Ceiling by Displacement";
+			prefix = "(68)";
+			flags64text = "[6] Inverse movement";
+		}
+	}
+
+	fofsolid
+	{
+		title = "FOF (solid)";
+
+		100
+		{
+			title = "Solid, Opaque";
+			prefix = "(100)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "19F";
+		}
+
+		101
+		{
+			title = "Solid, Opaque, No Shadow";
+			prefix = "(101)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "1DF";
+		}
+
+		102
+		{
+			title = "Solid, Translucent";
+			prefix = "(102)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Render insides";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "195F";
+			flags643dfloorflagsadd = "7C80";
+		}
+
+		103
+		{
+			title = "Solid, Sides Only";
+			prefix = "(103)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "1CF";
+		}
+
+		104
+		{
+			title = "Solid, No Sides";
+			prefix = "(104)";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Cast shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "1D7";
+			flags643dfloorflagsremove = "40";
+		}
+
+		105
+		{
+			title = "Solid, Invisible";
+			prefix = "(105)";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "47";
+		}
+
+		140
+		{
+			title = "Intangible from Bottom, Opaque";
+			prefix = "(140)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "200841F";
+			flags643dfloorflagsadd = "40";
+		}
+
+		141
+		{
+			title = "Intangible from Bottom, Translucent";
+			prefix = "(141)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Render insides/block non-plr";
+			3dfloor = true;
+			3dfloorflags = "200191F";
+			flags1283dfloorflagsadd = "7C80";
+			flags643dfloorflagsadd = "40";
+		}
+
+		142
+		{
+			title = "Intangible from Bottom, Translucent, No Sides";
+			prefix = "(142)";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Render insides/block non-plr";
+			3dfloor = true;
+			3dfloorflags = "2001917";
+			flags1283dfloorflagsadd = "7C80";
+			flags643dfloorflagsadd = "40";
+		}
+
+		143
+		{
+			title = "Intangible from Top, Opaque";
+			prefix = "(143)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "400841F";
+			flags643dfloorflagsadd = "40";
+		}
+
+		144
+		{
+			title = "Intangible from Top, Translucent";
+			prefix = "(144)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Render insides/block non-plr";
+			3dfloor = true;
+			3dfloorflags = "400191F";
+			flags1283dfloorflagsadd = "7C80";
+			flags643dfloorflagsadd = "40";
+		}
+
+		145
+		{
+			title = "Intangible from Top, Translucent, No Sides";
+			prefix = "(145)";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Render insides/block non-plr";
+			3dfloor = true;
+			3dfloorflags = "4001917";
+			flags1283dfloorflagsadd = "7C80";
+			flags643dfloorflagsadd = "40";
+		}
+
+		146
+		{
+			title = "Only Tangible from Sides";
+			prefix = "(146)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "600800F";
+		}
+	}
+
+	fofintangible
+	{
+		title = "FOF (intangible)";
+
+		120
+		{
+			title = "Water, Opaque";
+			prefix = "(120)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Use two light levels";
+			flags512text = "[9] Use target light level";
+			flags1024text = "[10] Ripple effect";
+			3dfloor = true;
+			3dfloorflags = "8F39";
+			flags643dfloorflagsadd = "20000";
+			flags5123dfloorflagsadd = "80000000";
+			flags10243dfloorflagsadd = "40000000";
+		}
+
+		121
+		{
+			title = "Water, Translucent";
+			prefix = "(121)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Use two light levels";
+			flags512text = "[9] Use target light level";
+			flags1024text = "[10] Ripple effect";
+			3dfloor = true;
+			3dfloorflags = "9F39";
+			flags643dfloorflagsadd = "20000";
+			flags5123dfloorflagsadd = "80000000";
+			flags10243dfloorflagsadd = "40000000";
+		}
+
+		122
+		{
+			title = "Water, Opaque, No Sides";
+			prefix = "(122)";
+			flags64text = "[6] Use two light levels";
+			flags512text = "[9] Use target light level";
+			flags1024text = "[10] Ripple effect";
+			3dfloor = true;
+			3dfloorflags = "F31";
+			flags643dfloorflagsadd = "20000";
+			flags5123dfloorflagsadd = "80000000";
+			flags10243dfloorflagsadd = "40000000";
+		}
+
+		123
+		{
+			title = "Water, Translucent, No Sides";
+			prefix = "(123)";
+			flags64text = "[6] Use two light levels";
+			flags512text = "[9] Use target light level";
+			flags1024text = "[10] Ripple effect";
+			3dfloor = true;
+			3dfloorflags = "1F31";
+			flags643dfloorflagsadd = "20000";
+			flags5123dfloorflagsadd = "80000000";
+			flags10243dfloorflagsadd = "40000000";
+		}
+
+		124
+		{
+			title = "Goo Water, Translucent";
+			prefix = "(124)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Use two light levels";
+			flags512text = "[9] Use target light level";
+			flags1024text = "[10] Ripple effect";
+			3dfloor = true;
+			3dfloorflags = "209F39";
+			flags643dfloorflagsadd = "20000";
+			flags5123dfloorflagsadd = "80000000";
+			flags10243dfloorflagsadd = "40000000";
+		}
+
+		125
+		{
+			title = "Goo Water, Translucent, No Sides";
+			prefix = "(125)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Use two light levels";
+			flags512text = "[9] Use target light level";
+			flags1024text = "[10] Ripple effect";
+			3dfloor = true;
+			3dfloorflags = "201F31";
+			flags643dfloorflagsadd = "20000";
+			flags5123dfloorflagsadd = "80000000";
+			flags10243dfloorflagsadd = "40000000";
+		}
+
+		220
+		{
+			title = "Intangible, Opaque";
+			prefix = "(220)";
+			flags8text = "[3] Slope skew sides";
+			3dfloor = true;
+			3dfloorflags = "8F19";
+		}
+
+		221
+		{
+			title = "Intangible, Translucent";
+			prefix = "(221)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Cast shadow";
+			3dfloor = true;
+			3dfloorflags = "1B59";
+			flags643dfloorflagsremove = "40";
+		}
+
+		222
+		{
+			title = "Intangible, Sides Only";
+			prefix = "(222)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Cast shadow";
+			3dfloor = true;
+			3dfloorflags = "8249";
+			flags643dfloorflagsremove = "240";
+		}
+
+		223
+		{
+			title = "Intangible, Invisible";
+			prefix = "(223)";
+			3dfloor = true;
+			3dfloorflags = "41";
+		}
+	}
+
+	fofmoving
+	{
+		title = "FOF (moving)";
+
+		150
+		{
+			title = "Air Bobbing";
+			prefix = "(150)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "19F";
+		}
+
+		151
+		{
+			title = "Air Bobbing (Adjustable)";
+			prefix = "(151)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "19F";
+		}
+
+		152
+		{
+			title = "Reverse Air Bobbing (Adjustable)";
+			prefix = "(152)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "19F";
+		}
+
+		160
+		{
+			title = "Floating, Bobbing";
+			prefix = "(160)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "4019F";
+		}
+
+		190
+		{
+			title = "Rising Platform, Solid, Opaque";
+			prefix = "(190)";
+			flags2text = "[1] Sink when stepped on";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "19F";
+		}
+
+		191
+		{
+			title = "Rising Platform, Solid, Opaque, No Shadow";
+			prefix = "(191)";
+			flags2text = "[1] Sink when stepped on";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "1DF";
+		}
+
+		192
+		{
+			title = "Rising Platform, Solid, Translucent";
+			prefix = "(192)";
+			flags2text = "[1] Sink when stepped on";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "195F";
+		}
+
+		193
+		{
+			title = "Rising Platform, Solid, Invisible";
+			prefix = "(193)";
+			flags2text = "[1] Sink when stepped on";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "47";
+		}
+
+		194
+		{
+			title = "Rising Platform, Intangible from Bottom, Opaque";
+			prefix = "(194)";
+			flags2text = "[1] Sink when stepped on";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash, no shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "200841F";
+			flags643dfloorflagsadd = "40";
+		}
+
+		195
+		{
+			title = "Rising Platform, Intangible from Bottom, Translucent";
+			prefix = "(195)";
+			flags2text = "[1] Sink when stepped on";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash, no shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "2009D1F";
+			flags643dfloorflagsadd = "40";
+		}
+	}
+
+	fofcrumbling
+	{
+		title = "FOF (crumbling)";
+
+		170
+		{
+			title = "Crumbling, Respawn";
+			prefix = "(170)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "10019F";
+		}
+
+		171
+		{
+			title = "Crumbling, No Respawn";
+			prefix = "(171)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "80019F";
+		}
+
+		172
+		{
+			title = "Crumbling, Respawn, Intangible from Bottom";
+			prefix = "(172)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "210841F";
+			flags643dfloorflagsadd = "40";
+		}
+
+		173
+		{
+			title = "Crumbling, No Respawn, Intangible from Bottom";
+			prefix = "(173)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "218841F";
+			flags643dfloorflagsadd = "40";
+		}
+
+		174
+		{
+			title = "Crumbling, Respawn, Int. from Bottom, Translucent";
+			prefix = "(174)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "210959F";
+			flags643dfloorflagsadd = "40";
+		}
+
+		175
+		{
+			title = "Crumbling, No Respawn, Int. from Bottom, Translucent";
+			prefix = "(175)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Don't cast shadow";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "218959F";
+			flags643dfloorflagsadd = "40";
+		}
+
+		176
+		{
+			title = "Crumbling, Respawn, Floating, Bobbing";
+			prefix = "(176)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "14019F";
+		}
+
+		177
+		{
+			title = "Crumbling, No Respawn, Floating, Bobbing";
+			prefix = "(177)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "1C019F";
+		}
+
+		178
+		{
+			title = "Crumbling, Respawn, Floating";
+			prefix = "(178)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "14019F";
+		}
+
+		179
+		{
+			title = "Crumbling, No Respawn, Floating";
+			prefix = "(179)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "1C019F";
+		}
+
+		180
+		{
+			title = "Crumbling, Respawn, Air Bobbing";
+			prefix = "(180)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags64text = "[6] Spindash to move";
+			flags128text = "[7] Only block non-players";
+			3dfloor = true;
+			3dfloorflags = "10019F";
+		}
+	}
+
+	fofspecial
+	{
+		title = "FOF (special)";
+
+		200
+		{
+			title = "Light Block";
+			prefix = "(200)";
+			3dfloor = true;
+			3dfloorflags = "20201";
+		}
+
+		201
+		{
+			title = "Half Light Block";
+			prefix = "(201)";
+			3dfloor = true;
+			3dfloorflags = "201";
+		}
+
+		202
+		{
+			title = "Fog Block";
+			prefix = "(202)";
+			3dfloor = true;
+			3dfloorflags = "3EF19";
+		}
+
+		250
+		{
+			title = "Mario Block";
+			prefix = "(250)";
+			flags32text = "[5] Invisible block";
+			flags64text = "[6] Brick block";
+			3dfloor = true;
+			3dfloorflags = "40019F";
+		}
+
+		251
+		{
+			title = "Thwomp Block";
+			prefix = "(251)";
+			flags512text = "[9] Custom crushing sound";
+			flags1024text = "[10] Custom speed";
+			3dfloor = true;
+			3dfloorflags = "19F";
+		}
+
+		252
+		{
+			title = "Shatter Block";
+			prefix = "(252)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Shatter only from below";
+			flags512text = "[9] Shattered by pushables";
+			flags1024text = "[10] Trigger linedef executor";
+			3dfloor = true;
+			3dfloorflags = "8800019";
+			flags643dfloorflagsadd = "200006";
+		}
+
+		253
+		{
+			title = "Shatter Block, Translucent";
+			prefix = "(253)";
+			flags8text = "[3] Slope skew sides";
+			flags512text = "[9] Shattered by pushables";
+			flags1024text = "[10] Trigger linedef executor";
+			3dfloor = true;
+			3dfloorflags = "8801019";
+		}
+
+		254
+		{
+			title = "Bustable Block";
+			prefix = "(254)";
+			flags8text = "[3] Slope skew sides";
+			flags64text = "[6] Strong characters only";
+			flags128text = "[7] Only block non-players";
+			flags512text = "[9] Shattered by pushables";
+			flags1024text = "[10] Trigger linedef executor";
+			3dfloor = true;
+			3dfloorflags = "80001F";
+			flags643dfloorflagsadd = "20000000";
+		}
+
+		255
+		{
+			title = "Spin-Bustable Block";
+			prefix = "(255)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			flags512text = "[9] Shattered by pushables";
+			flags1024text = "[10] Trigger linedef executor";
+			3dfloor = true;
+			3dfloorflags = "1080001F";
+		}
+
+		256
+		{
+			title = "Spin-Bustable Block, Translucent";
+			prefix = "(256)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			flags512text = "[9] Shattered by pushables";
+			flags1024text = "[10] Trigger linedef executor";
+			3dfloor = true;
+			3dfloorflags = "1080101F";
+		}
+
+		257
+		{
+			title = "Quicksand";
+			prefix = "(257)";
+			flags8text = "[3] Slope skew sides";
+			flags1024text = "[10] Ripple effect";
+			3dfloor = true;
+			3dfloorflags = "1008219";
+			flags10243dfloorflagsadd = "40000000";
+		}
+
+		258
+		{
+			title = "Laser";
+			prefix = "(258)";
+			flags8text = "[3] Slope skew sides";
+			flags32text = "[5] Don't damage bosses";
+			3dfloor = true;
+			3dfloorflags = "959";
+		}
+
+		259
+		{
+			title = "Custom FOF";
+			prefix = "(259)";
+			flags32text = "[5] Only block player";
+			flags128text = "[7] Only block non-players";
+			flags512text = "[9] Shattered by pushables";
+			flags1024text = "[10] Trigger linedef executor";
+			3dfloor = true;
+			3dfloorcustom = true;
+		}
+	}
+
+	linedeftrigger
+	{
+		title = "Linedef Executor Trigger";
+
+		300
+		{
+			title = "Continuous";
+			prefix = "(300)";
+		}
+
+		301
+		{
+			title = "Each Time";
+			prefix = "(301)";
+			flags16384text = "[14] Also trigger on exit";
+		}
+
+		302
+		{
+			title = "Once";
+			prefix = "(302)";
+		}
+
+		303
+		{
+			title = "Ring Count - Continuous";
+			prefix = "(303)";
+			flags2text = "[1] Rings greater or equal";
+			flags64text = "[6] Rings less or equal";
+			flags512text = "[9] Consider all players";
+		}
+
+		304
+		{
+			title = "Ring Count - Once";
+			prefix = "(304)";
+			flags2text = "[1] Rings greater or equal";
+			flags64text = "[6] Rings less or equal";
+			flags512text = "[9] Consider all players";
+		}
+
+		305
+		{
+			title = "Character Ability - Continuous";
+			prefix = "(305)";
+		}
+
+		306
+		{
+			title = "Character Ability - Each Time";
+			prefix = "(306)";
+			flags16384text = "[14] Also trigger on exit";
+		}
+
+		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)";
+			flags16384text = "[14] Also trigger on exit";
+		}
+
+		311
+		{
+			title = "CTF Blue Team - Continuous";
+			prefix = "(311)";
+		}
+
+		312
+		{
+			title = "CTF Blue Team - Each Time";
+			prefix = "(312)";
+			flags16384text = "[14] Also trigger on exit";
+		}
+
+		313
+		{
+			title = "No More Enemies - Once";
+			prefix = "(313)";
+		}
+
+		314
+		{
+			title = "Number of Pushables - Continuous";
+			prefix = "(314)";
+			flags64text = "[6] Number greater or equal";
+			flags512text = "[9] Number less";
+		}
+
+		315
+		{
+			title = "Number of Pushables - Once";
+			prefix = "(315)";
+			flags64text = "[6] Number greater or equal";
+			flags512text = "[9] Number less";
+		}
+
+		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)";
+			flags64text = "[6] Trigger more than once";
+
+		}
+
+		322
+		{
+			title = "Trigger After X Calls - Each Time";
+			prefix = "(322)";
+			flags64text = "[6] Trigger more than once";
+		}
+
+		323
+		{
+			title = "NiGHTSerize - Each Time";
+			prefix = "(323)";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags8text = "[3] Run only if player is NiGHTS";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			flags16384text = "[14] Run if no more mares";
+			flags32768text = "[15] Run if player is not NiGHTS";
+		}
+
+		324
+		{
+			title = "NiGHTSerize - Once";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags8text = "[3] Run only if player is NiGHTS";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			flags16384text = "[14] Run if no more mares";
+			flags32768text = "[15] Run if player is not NiGHTS";
+			prefix = "(324)";
+		}
+
+		325
+		{
+			title = "De-NiGHTSerize - Each Time";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags8text = "[3] Run if anyone is NiGHTS";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			flags32768text = "[15] Run if no one is NiGHTS";
+			prefix = "(325)";
+		}
+
+		326
+		{
+			title = "De-NiGHTSerize - Once";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags8text = "[3] Run if anyone is NiGHTS";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			flags32768text = "[15] Run if no one is NiGHTS";
+			prefix = "(326)";
+		}
+
+		327
+		{
+			title = "NiGHTS Lap - Each Time";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			prefix = "(327)";
+		}
+
+		328
+		{
+			title = "NiGHTS Lap - Once";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			prefix = "(328)";
+		}
+
+		329
+		{
+			title = "Ideya Capture Touch - Each Time";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags8text = "[3] Run regardless of spheres";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			flags16384text = "[14] Only if not enough spheres";
+			flags32768text = "[15] Run when entering Capture";
+			prefix = "(329)";
+		}
+
+		330
+		{
+			title = "Ideya Capture Touch - Once";
+			flags2text = "[1] Mare >= Front X Offset";
+			flags8text = "[3] Run regardless of spheres";
+			flags16text = "[4] Count from lowest of players";
+			flags32text = "[5] Lap <= Front Y Offset";
+			flags64text = "[6] Mare <= Front X Offset";
+			flags128text = "[7] Lap >= Front Y Offset";
+			flags256text = "[8] Count laps from Bonus Time";
+			flags512text = "[9] Count from triggering player";
+			flags16384text = "[14] Only if not enough spheres";
+			flags32768text = "[15] Run when entering Capture";
+			prefix = "(330)";
+		}
+
+		331
+		{
+			title = "Player Skin - Continuous";
+			flags64text = "[6] Disable for this skin";
+			prefix = "(331)";
+		}
+
+		332
+		{
+			title = "Player Skin - Each Time";
+			flags64text = "[6] Disable for this skin";
+			prefix = "(332)";
+		}
+
+		333
+		{
+			title = "Player Skin - Once";
+			flags64text = "[6] Disable for this skin";
+			prefix = "(333)";
+		}
+
+		399
+		{
+			title = "Level Load";
+			prefix = "(399)";
+		}
+	}
+
+	linedefexecsector
+	{
+		title = "Linedef Executor (sector)";
+
+		400
+		{
+			title = "Set Tagged Sector's Floor Height/Texture";
+			prefix = "(400)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Keep floor flat";
+		}
+
+		401
+		{
+			title = "Set Tagged Sector's Ceiling Height/Texture";
+			prefix = "(401)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		402
+		{
+			title = "Set Tagged Sector's Light Level";
+			prefix = "(402)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		409
+		{
+			title = "Change Tagged Sector's Tag";
+			prefix = "(409)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		410
+		{
+			title = "Change Front Sector's Tag";
+			prefix = "(410)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		416
+		{
+			title = "Start Adjustable Flickering Light";
+			prefix = "(416)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Second level from back";
+		}
+
+		417
+		{
+			title = "Start Adjustable Pulsating Light";
+			prefix = "(417)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Second level from back";
+		}
+
+		418
+		{
+			title = "Start Adjustable Blinking Light (unsynchronized)";
+			prefix = "(418)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Second level from back";
+		}
+
+		419
+		{
+			title = "Start Adjustable Blinking Light (synchronized)";
+			prefix = "(419)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Second level from back";
+		}
+
+		420
+		{
+			title = "Fade Light Level";
+			prefix = "(420)";
+			flags8text = "[3] Set delay by backside sector";
+			flags16text = "[4] Set params by X/Y offsets";
+			flags512text = "[9] Speed = Tic Duration";
+			flags1024text = "[10] Override existing fade";
+		}
+
+		421
+		{
+			title = "Stop Lighting Effect";
+			prefix = "(421)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		435
+		{
+			title = "Change Plane Scroller Direction";
+			prefix = "(435)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+	}
+
+	linedefexecplane
+	{
+		title = "Linedef Executor (plane movement)";
+
+		403
+		{
+			title = "Move Tagged Sector's Floor";
+			prefix = "(403)";
+			flags2text = "[1] Trigger linedef executor";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Change floor flat";
+		}
+
+		404
+		{
+			title = "Move Tagged Sector's Ceiling";
+			prefix = "(404)";
+			flags2text = "[1] Trigger linedef executor";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Change ceiling flat";
+		}
+
+		405
+		{
+			title = "Move Floor According to Front Texture Offsets";
+			prefix = "(405)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Move instantly";
+		}
+
+		407
+		{
+			title = "Move Ceiling According to Front Texture Offsets";
+			prefix = "(407)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Move instantly";
+		}
+
+		411
+		{
+			title = "Stop Plane Movement";
+			prefix = "(411)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		428
+		{
+			title = "Start Platform Movement";
+			prefix = "(428)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Move upwards at start";
+		}
+
+		429
+		{
+			title = "Crush Ceiling Once";
+			prefix = "(429)";
+			flags8text = "[3] Set delay by backside sector";
+			flags512text = "[9] Double, constant speed";
+		}
+
+		430
+		{
+			title = "Crush Floor Once";
+			prefix = "(430)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		431
+		{
+			title = "Crush Floor and Ceiling Once";
+			prefix = "(431)";
+			flags8text = "[3] Set delay by backside sector";
+			flags512text = "[9] Double, constant speed";
+		}
+	}
+
+	linedefexecplayer
+	{
+		title = "Linedef Executor (player/object)";
+
+		412
+		{
+			title = "Teleporter";
+			prefix = "(412)";
+			flags2text = "[1] Silent";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Retain angle";
+			flags256text = "[8] Relative, silent";
+			flags512text = "[9] Retain momentum";
+		}
+
+		425
+		{
+			title = "Change Object State";
+			prefix = "(425)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		426
+		{
+			title = "Stop Object";
+			prefix = "(426)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Teleport to sector center";
+		}
+
+		427
+		{
+			title = "Award Score";
+			prefix = "(427)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		432
+		{
+			title = "Enable/Disable 2D Mode";
+			prefix = "(432)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Return to 3D";
+		}
+
+		433
+		{
+			title = "Enable/Disable Gravity Flip";
+			prefix = "(433)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Return to normal";
+		}
+
+		434
+		{
+			title = "Award Power-Up";
+			prefix = "(434)";
+			flags2text = "[1] Use back upper texture";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] No time limit";
+		}
+
+		437
+		{
+			title = "Disable Player Control";
+			prefix = "(437)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Allow jumping";
+		}
+
+		438
+		{
+			title = "Change Object Size";
+			prefix = "(438)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		442
+		{
+			title = "Change Object Type State";
+			prefix = "(442)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		457
+		{
+			title = "Track Object's Angle";
+			prefix = "(457)";
+			flags8text = "[3] Set delay by backside sector";
+			flags128text = "[7] Don't stop after first fail";
+		}
+
+		458
+		{
+			title = "Stop Tracking Object's Angle";
+			prefix = "(458)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		460
+		{
+			title = "Award Rings";
+			prefix = "(460)";
+		}
+
+		461
+		{
+			title = "Spawn Object";
+			prefix = "(461)";
+			flags64text = "[6] Spawn inside a range";
+		}
+	}
+
+	linedefexecmisc
+	{
+		title = "Linedef Executor (misc.)";
+
+		413
+		{
+			title = "Change Music";
+			prefix = "(413)";
+			flags2text = "[1] Keep after death";
+			flags8text = "[3] Set delay by backside sector";
+			flags32text = "[5] Seek to current song position";
+			flags64text = "[6] For everyone";
+			flags128text = "[7] Fade to custom volume";
+			flags512text = "[9] Don't loop";
+			flags16384text = "[14] Force music reload";
+		}
+
+		414
+		{
+			title = "Play Sound Effect";
+			prefix = "(414)";
+			flags2text = "[1] From calling sector";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] From nowhere for triggerer";
+			flags512text = "[9] For everyone";
+			flags1024text = "[10] From tagged sectors";
+		}
+
+		415
+		{
+			title = "Run Script";
+			prefix = "(415)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		422
+		{
+			title = "Switch to Cut-Away View";
+			prefix = "(422)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Adjust pitch";
+		}
+
+		423
+		{
+			title = "Change Sky";
+			prefix = "(423)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] For everyone";
+		}
+
+		424
+		{
+			title = "Change Weather";
+			prefix = "(424)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] For everyone";
+		}
+
+		436
+		{
+			title = "Shatter FOF";
+			prefix = "(436)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		439
+		{
+			title = "Change Tagged Linedef's Textures";
+			prefix = "(439)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Only existing";
+		}
+
+		440
+		{
+			title = "Start Metal Sonic Race";
+			prefix = "(440)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		441
+		{
+			title = "Condition Set Trigger";
+			prefix = "(441)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		443
+		{
+			title = "Call Lua Function";
+			prefix = "(443)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		444
+		{
+			title = "Earthquake";
+			prefix = "(444)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+
+		445
+		{
+			title = "Make FOF Disappear/Reappear";
+			prefix = "(445)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Reappear";
+		}
+
+		446
+		{
+			title = "Make FOF Crumble";
+			prefix = "(446)";
+			flags2text = "[1] Flags determine respawn";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Don't respawn";
+		}
+
+		447
+		{
+			title = "Change Tagged Sector's Colormap";
+			prefix = "(447)";
+			flags8text = "[3] Set delay by backside sector";
+			flags16text = "[4] Front X/Y = Alpha";
+			flags32text = "[5] Subtract Red value";
+			flags64text = "[6] Subtract Green value";
+			flags128text = "[7] Subtract Blue value";
+			flags256text = "[8] Calc relative values";
+			flags32768text = "[15] Use back side colormap";
+		}
+
+		448
+		{
+			title = "Change Skybox";
+			prefix = "(448)";
+			flags2text = "[1] Change centerpoint";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] For everyone";
+			flags512text = "[9] Don't change viewpoint";
+		}
+
+		450
+		{
+			title = "Execute Linedef Executor (specific tag)";
+			prefix = "(450)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		451
+		{
+			title = "Execute Linedef Executor (random tag in range)";
+			prefix = "(451)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		452
+		{
+			title = "Set FOF Translucency";
+			prefix = "(452)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Do not handle FF_TRANS";
+			flags256text = "[8] Set relative to current val";
+		}
+
+		453
+		{
+			title = "Fade FOF";
+			prefix = "(453)";
+			flags2text = "[1] Do not handle FF_EXISTS";
+			flags8text = "[3] Set delay by backside sector";
+			flags32text = "[5] No collision during fade";
+			flags64text = "[6] Do not handle FF_TRANS";
+			flags128text = "[7] Do not handle lighting";
+			flags256text = "[8] Set relative to current val";
+			flags512text = "[9] Speed = Tic Duration";
+			flags1024text = "[10] Override existing fade";
+			flags16384text = "[14] Do not handle collision";
+			flags32768text = "[15] Use exact alpha in OGL";
+		}
+
+		454
+		{
+			title = "Stop Fading FOF";
+			prefix = "(454)";
+			flags2text = "[1] Do not finalize collision";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		455
+		{
+			title = "Fade Tagged Sector's Colormap";
+			prefix = "(455)";
+			flags8text = "[3] Set delay by backside sector";
+			flags16text = "[4] Front X/Y = Alpha";
+			flags32text = "[5] Subtract Red value";
+			flags64text = "[6] Subtract Green value";
+			flags128text = "[7] Subtract Blue value";
+			flags256text = "[8] Calc relative values";
+			flags512text = "[9] Speed = Tic Duration";
+			flags1024text = "[10] Override existing fade";
+			flags16384text = "[14] Fade from invisible black";
+			flags32768text = "[15] Use back side colormap";
+		}
+
+		456
+		{
+			title = "Stop Fading Tagged Sector's Colormap";
+			prefix = "(456)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		459
+		{
+			title = "Control Text Prompt";
+			prefix = "(459)";
+			flags2text = "[1] Close text prompt";
+			flags8text = "[3] Set delay by backside sector";
+			flags32text = "[5] Run executor tag on close";
+			flags64text = "[6] For everyone";
+			flags128text = "[7] Do not block controls";
+			flags256text = "[8] Do not freeze time";
+			flags32768text = "[15] Find prompt by name";
+		}
+	}
+
+	linedefexecpoly
+	{
+		title = "Linedef Executor (polyobject)";
+
+		480
+		{
+			title = "Door Slide";
+			prefix = "(480)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		481
+		{
+			title = "Door Swing";
+			prefix = "(481)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		482
+		{
+			title = "Move";
+			prefix = "(482)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		483
+		{
+			title = "Move, Override";
+			prefix = "(483)";
+			flags8text = "[3] Set delay by backside sector";
+		}
+
+		484
+		{
+			title = "Rotate Right";
+			prefix = "(484)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Don't turn players";
+			flags512text = "[9] Turn all objects";
+		}
+
+		485
+		{
+			title = "Rotate Right, Override";
+			prefix = "(485)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Don't turn players";
+			flags512text = "[9] Turn all objects";
+		}
+
+		486
+		{
+			title = "Rotate Left";
+			prefix = "(486)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Don't turn players";
+			flags512text = "[9] Turn all objects";
+		}
+
+		487
+		{
+			title = "Rotate Left, Override";
+			prefix = "(487)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Don't turn players";
+			flags512text = "[9] Turn all objects";
+		}
+
+		488
+		{
+			title = "Move by Waypoints";
+			prefix = "(488)";
+			flags8text = "[3] Set delay by backside sector";
+			flags32text = "[5] Reverse order";
+			flags128text = "[7] There and back";
+			flags256text = "[8] Return when done";
+			flags512text = "[9] Loop movement";
+		}
+
+		489
+		{
+			title = "Turn Invisible, Intangible";
+			prefix = "(489)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Only invisible";
+		}
+
+		490
+		{
+			title = "Turn Visible, Tangible";
+			prefix = "(490)";
+			flags8text = "[3] Set delay by backside sector";
+			flags64text = "[6] Only visible";
+		}
+
+		491
+		{
+			title = "Set Translucency";
+			prefix = "(491)";
+			flags8text = "[3] Set delay by backside sector";
+			flags16text = "[4] Set raw alpha by Front X";
+			flags256text = "[8] Calc relative values";
+		}
+
+		492
+		{
+			title = "Fade Translucency";
+			prefix = "(492)";
+			flags8text = "[3] Set delay by backside sector";
+			flags16text = "[4] Set raw alpha by Front X";
+			flags32text = "[5] No collision during fade";
+			flags256text = "[8] Calc relative values";
+			flags512text = "[9] Speed = Tic Duration";
+			flags1024text = "[10] Override existing fade";
+			flags16384text = "[14] Do not handle collision";
+		}
+	}
+
+	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)";
+			flags64text = "[6] Even across edges";
+		}
+
+		522
+		{
+			title = "Carry Objects on Floor (Displacement)";
+			prefix = "(522)";
+		}
+
+		523
+		{
+			title = "Carry Objects on Ceiling";
+			prefix = "(523)";
+			flags64text = "[6] Even across edges";
+		}
+
+		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)";
+			flags64text = "[6] Even across edges";
+		}
+
+		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)";
+			flags64text = "[6] Even across edges";
+		}
+
+		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)";
+			flags512text = "[9] Player slides";
+			flags64text = "[6] Even across edges";
+		}
+
+		542
+		{
+			title = "Upwards Wind";
+			prefix = "(542)";
+			flags512text = "[9] Player slides";
+			flags64text = "[6] Even across edges";
+		}
+
+		543
+		{
+			title = "Downwards Wind";
+			prefix = "(543)";
+			flags512text = "[9] Player slides";
+			flags64text = "[6] Even across edges";
+		}
+
+		544
+		{
+			title = "Current";
+			prefix = "(544)";
+			flags512text = "[9] Player slides";
+			flags64text = "[6] Even across edges";
+		}
+
+		545
+		{
+			title = "Upwards Current";
+			prefix = "(545)";
+			flags512text = "[9] Player slides";
+			flags64text = "[6] Even across edges";
+		}
+
+		546
+		{
+			title = "Downwards Current";
+			prefix = "(546)";
+			flags512text = "[9] Player slides";
+			flags64text = "[6] Even across edges";
+		}
+
+		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)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 1;
+		}
+
+		701
+		{
+			title = "Slope Frontside Ceiling";
+			prefix = "(701)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 2;
+		}
+
+		702
+		{
+			title = "Slope Frontside Floor and Ceiling";
+			prefix = "(702)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 3;
+		}
+
+		703
+		{
+			title = "Slope Frontside Floor and Backside Ceiling";
+			prefix = "(703)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 9;
+		}
+
+		704
+		{
+			title = "Slope Frontside Floor by 3 Tagged Vertex Things";
+			prefix = "(704)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			flags8192text = "[13] Use tag and offsets";
+			slope = "vertex";
+			slopeargs = 0;
+		}
+
+		705
+		{
+			title = "Slope Frontside Ceiling by 3 Tagged Vertex Things";
+			prefix = "(705)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			flags8192text = "[13] Use tag and offsets";
+			slope = "vertex";
+			slopeargs = 1;
+		}
+
+		710
+		{
+			title = "Slope Backside Floor";
+			prefix = "(710)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 4;
+		}
+
+		711
+		{
+			title = "Slope Backside Ceiling";
+			prefix = "(711)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 8;
+		}
+
+		712
+		{
+			title = "Slope Backside Floor and Ceiling";
+			prefix = "(712)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 12;
+		}
+
+		713
+		{
+			title = "Slope Backside Floor and Frontside Ceiling";
+			prefix = "(713)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			slope = "regular";
+			slopeargs = 6;
+		}
+
+		714
+		{
+			title = "Slope Backside Floor by 3 Tagged Vertex Things";
+			prefix = "(714)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			flags8192text = "[13] Use tag and offsets";
+			slope = "vertex";
+			slopeargs = 2;
+		}
+
+		715
+		{
+			title = "Slope Backside Ceiling by 3 Tagged Vertex Things";
+			prefix = "(715)";
+			flags2048text = "[11] No physics";
+			flags4096text = "[12] Dynamic";
+			flags8192text = "[13] Use tag and offsets";
+			slope = "vertex";
+			slopeargs = 3;
+		}
+
+		720
+		{
+			title = "Copy Frontside Floor Slope from Line Tag";
+			prefix = "(720)";
+			slope = "copy";
+			slopeargs = 1;
+		}
+
+		721
+		{
+			title = "Copy Frontside Ceiling Slope from Line Tag";
+			prefix = "(721)";
+			slope = "copy";
+			slopeargs = 2;
+		}
+
+		722
+		{
+			title = "Copy Frontside Floor and Ceiling Slope from Line Tag";
+			prefix = "(722)";
+			slope = "copy";
+			slopeargs = 3;
+		}
+
+		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)";
+		}
+	}
+}
+
+
+// THING FLAGS
+thingflags
+{
+	1 = "[1] Extra";
+	2 = "[2] Flip";
+	4 = "[4] Special";
+	8 = "[8] 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 = "skill1";
+	2 = "skill2";
+	4 = "skill3";
+	8 = "ambush";
+}
+
+// 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;
+
+
+// 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
+thingtypes
+{
+	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;
+		flags8text = "[8] Spawn on ceiling";
+		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;
+			angletext = "Jump strength";
+		}
+		103
+		{
+			title = "Buzz (Gold)";
+			sprite = "BUZZA1";
+			width = 28;
+			height = 40;
+			flags8text = "[8] Cannot move";
+		}
+		104
+		{
+			title = "Buzz (Red)";
+			sprite = "RBUZA1";
+			width = 28;
+			height = 40;
+			flags8text = "[8] Cannot move";
+		}
+		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;
+			angletext = "Firing delay";
+		}
+		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;
+			flags8text = "[8] Move left from spawn";
+		}
+		138
+		{
+			title = "Banpyura";
+			sprite = "CR2BA0";
+			width = 24;
+			height = 32;
+			flags8text = "[8] Move left from spawn";
+		}
+		117
+		{
+			title = "Robo-Hood";
+			sprite = "ARCHA1";
+			width = 24;
+			height = 32;
+		}
+		118
+		{
+			title = "CastleBot FaceStabber";
+			sprite = "CBFSA1";
+			width = 32;
+			height = 72;
+		}
+		1113
+		{
+			title = "Suspicious FaceStabber Statue";
+			sprite = "CBBSA1";
+			width = 32;
+			height = 72;
+		}
+		119
+		{
+			title = "Egg Guard";
+			sprite = "ESHIA1";
+			width = 16;
+			height = 48;
+			flags1text = "[1] 90 degrees counter-clockwise";
+			flags4text = "[4] 90 degrees clockwise";
+			flags8text = "[8] Double speed";
+		}
+		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 = "MNUSA1";
+			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;
+			parametertext = "No. Pterabytes";
+		}
+		136
+		{
+			title = "Pyre Fly";
+			sprite = "PYREA0";
+			width = 24;
+			height = 34;
+			flags8text = "[8] Start on fire";
+		}
+		105
+		{
+			title = "Jetty-Syn Bomber";
+			sprite = "JETBB1";
+			width = 20;
+			height = 50;
+			flags8text = "[8] Cannot move";
+		}
+		106
+		{
+			title = "Jetty-Syn Gunner";
+			sprite = "JETGB1";
+			width = 20;
+			height = 48;
+			flags8text = "[8] Cannot move";
+		}
+		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;
+			flags8text = "[8] Cannot move";
+		}
+		133
+		{
+			title = "Hangster";
+			sprite = "HBATC1";
+			width = 24;
+			height = 24;
+			hangs = 1;
+		}
+		127
+		{
+			title = "Hive Elemental";
+			sprite = "HIVEA0";
+			width = 32;
+			height = 80;
+			parametertext = "No. bees";
+		}
+		128
+		{
+			title = "Bumblebore";
+			sprite = "BUMBA1";
+			width = 16;
+			height = 32;
+		}
+		124
+		{
+			title = "AquaBuzz";
+			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;
+			flags4text = "[4] End level on death";
+			flags8text = "[8] Alternate laser attack";
+		}
+		201
+		{
+			title = "Egg Slimer";
+			sprite = "EGGNA1";
+			width = 24;
+			height = 76;
+			flags4text = "[4] End level on death";
+			flags8text = "[8] Speed up when hit";
+		}
+		202
+		{
+			title = "Sea Egg";
+			sprite = "EGGOA1";
+			width = 32;
+			height = 116;
+			flags4text = "[4] End level on death";
+		}
+		203
+		{
+			title = "Egg Colosseum";
+			sprite = "EGGPA1";
+			width = 24;
+			height = 76;
+			flags4text = "[4] End level on death";
+		}
+		204
+		{
+			title = "Fang";
+			sprite = "FANGA1";
+			width = 24;
+			height = 60;
+			flags1text = "[1] Grayscale mode";
+			flags4text = "[4] End level on death";
+		}
+		206
+		{
+			title = "Brak Eggman (Old)";
+			sprite = "BRAKB1";
+			width = 48;
+			height = 160;
+			flags4text = "[4] End level on death";
+		}
+		207
+		{
+			title = "Metal Sonic (Race)";
+			sprite = "METLI1";
+			width = 16;
+			height = 48;
+			flags1text = "[1] Grayscale mode";
+		}
+		208
+		{
+			title = "Metal Sonic (Battle)";
+			sprite = "METLC1";
+			width = 16;
+			height = 48;
+			flags1text = "[1] Grayscale mode";
+			flags4text = "[4] End level on death";
+		}
+		209
+		{
+			title = "Brak Eggman";
+			sprite = "BRAK01";
+			width = 48;
+			height = 160;
+			flags4text = "[4] End level on death";
+			flags8text = "[8] Electric barrier";
+		}
+		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;
+			flags8text = "[8] Sea Egg shooting point";
+			sprite = "internal:eggmanway";
+			angletext = "No. (Sea Egg)";
+			flagsvaluetext = "No. (Brak)";
+			parametertext = "Next";
+		}
+		293
+		{
+			title = "Metal Sonic Gather Point";
+			sprite = "internal:metal";
+			width = 8;
+			height = 16;
+		}
+		294
+		{
+			title = "Fang Waypoint";
+			flags8text = "[8] Center waypoint";
+			sprite = "internal:eggmanway";
+			width = 8;
+			height = 16;
+		}
+	}
+
+	rings
+	{
+		color = 14; // Yellow
+		title = "Rings and Weapon Panels";
+		width = 24;
+		height = 24;
+		flags8height = 24;
+		flags8text = "[8] Float";
+		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;
+			flags8height = 24;
+			flags8text = "[8] Float";
+		}
+		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";
+			flags8height = 24;
+			flags8text = "[8] Float";
+		}
+		322
+		{
+			title = "Emblem";
+			sprite = "EMBMA0";
+			width = 16;
+			height = 30;
+			flags8height = 24;
+			flags8text = "[8] Float";
+			angletext = "Tag";
+		}
+	}
+
+	boxes
+	{
+		color = 7; // Gray
+		blocking = 2;
+		title = "Monitors";
+		width = 18;
+		height = 40;
+		flags1text = "[1] Run Linedef Executor on pop";
+		flags4text = "[4] Random (Strong)";
+		flags8text = "[8] Random (Weak)";
+
+		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";
+			flags4text = "[4] Random (Strong) / 10k points";
+			flags8text = "[8] Random (Weak) / 10k points";
+		}
+		410
+		{
+			title = "Eggman";
+			sprite = "TVEGA0";
+			flags4text = "[4] Special";
+			flags8text = "[8] Ambush";
+		}
+		411
+		{
+			title = "Teleporter";
+			sprite = "TVMXA0";
+		}
+		413
+		{
+			title = "Gravity Boots";
+			sprite = "TVGVA0";
+			flags4text = "[4] Special";
+			flags8text = "[8] Ambush";
+		}
+		414
+		{
+			title = "CTF Team Ring Monitor (Red)";
+			sprite = "TRRIA0";
+			flags4text = "[4] Special";
+			flags8text = "[8] Ambush";
+		}
+		415
+		{
+			title = "CTF Team Ring Monitor (Blue)";
+			sprite = "TBRIA0";
+			flags4text = "[4] Special";
+			flags8text = "[8] Ambush";
+		}
+		416
+		{
+			title = "Recycler";
+			sprite = "TVRCA0";
+		}
+		418
+		{
+			title = "Score (1,000 Points)";
+			sprite = "TV1KA0";
+			flags4text = "[4] Special";
+			flags8text = "[8] Ambush";
+		}
+		419
+		{
+			title = "Score (10,000 Points)";
+			sprite = "TVTKA0";
+			flags4text = "[4] Special";
+			flags8text = "[8] Ambush";
+		}
+		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;
+		flags1text = "[1] Run Linedef Executor on pop";
+
+		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;
+			flags8text = "[8] No distance check";
+		}
+		501
+		{
+			title = "Signpost";
+			sprite = "SIGND0";
+			width = 8;
+			height = 32;
+		}
+		502
+		{
+			arrow = 1;
+			title = "Star Post";
+			sprite = "STPTA0M0";
+			width = 64;
+			height = 128;
+			angletext = "Angle/Order";
+		}
+		520
+		{
+			title = "Bomb Sphere";
+			sprite = "SPHRD0";
+			width = 16;
+			height = 24;
+			flags8height = 24;
+			flags8text = "[8] Float";
+			unflippable = true;
+		}
+		521
+		{
+			title = "Spikeball";
+			sprite = "SPIKA0";
+			width = 12;
+			height = 8;
+			flags8height = 24;
+			flags8text = "[8] Float";
+		}
+		522
+		{
+			title = "Wall Spike";
+			sprite = "WSPKALAR";
+			width = 16;
+			height = 14;
+			flags1text = "[1] Start retracted";
+			flags4text = "[4] Retractable";
+			flags8text = "[8] Intangible";
+			parametertext = "Initial delay";
+		}
+		523
+		{
+			title = "Spike";
+			sprite = "USPKA0";
+			width = 8;
+			height = 32;
+			flags1text = "[1] Start retracted";
+			flags4text = "[4] Retractable";
+			flags8text = "[8] Intangible";
+			angletext = "Retraction interval";
+			parametertext = "Initial delay";
+		}
+		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;
+			flags4text = "[4] Invisible";
+			flags8text = "[8] No distance check";
+			angletext = "Lift height";
+		}
+		541
+		{
+			title = "Gas Jet";
+			sprite = "STEMD0";
+			flags8text = "[8] No sounds";
+			width = 32;
+		}
+		542
+		{
+			title = "Bumper";
+			sprite = "BUMPA0";
+			width = 32;
+			height = 64;
+			angletext = "Strength";
+		}
+		543
+		{
+			title = "Balloon";
+			sprite = "BLONA0";
+			width = 32;
+			height = 64;
+			flags8text = "[8] Respawn";
+			angletext = "Color";
+		}
+		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;
+			flags4text = "[4] Ignore gravity";
+			flags8text = "[8] Rotate 22.5° CCW";
+		}
+		556
+		{
+			arrow = 1;
+			title = "Diagonal Red Spring";
+			sprite = "RSPRD2";
+			width = 16;
+			flags4text = "[4] Ignore gravity";
+			flags8text = "[8] Rotate 22.5° CCW";
+		}
+		557
+		{
+			arrow = 1;
+			title = "Diagonal Blue Spring";
+			sprite = "BSPRD2";
+			width = 16;
+			flags4text = "[4] Ignore gravity";
+			flags8text = "[8] Rotate 22.5° CCW";
+		}
+		558
+		{
+			arrow = 1;
+			title = "Horizontal Yellow Spring";
+			sprite = "SSWYD2D8";
+			flags8height = 16;
+			flags8text = "[8] Float";
+			width = 16;
+			height = 32;
+		}
+		559
+		{
+			arrow = 1;
+			title = "Horizontal Red Spring";
+			sprite = "SSWRD2D8";
+			flags8height = 16;
+			flags8text = "[8] Float";
+			width = 16;
+			height = 32;
+		}
+		560
+		{
+			arrow = 1;
+			title = "Horizontal Blue Spring";
+			sprite = "SSWBD2D8";
+			flags8height = 16;
+			flags8text = "[8] Float";
+			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";
+			flags8text = "[8] Force spin";
+			width = 28;
+			height = 2;
+		}
+		545
+		{
+			arrow = 1;
+			title = "Red Boost Panel";
+			sprite = "BSTRA0";
+			flags8text = "[8] Force spin";
+			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;
+			unflippable = true;
+			centerHitbox = true;
+		}
+		605
+		{
+			title = "Circle of Rings (Big)";
+			sprite = "RINGA0";
+			width = 192;
+			unflippable = true;
+			centerHitbox = true;
+		}
+		606
+		{
+			title = "Circle of Blue Spheres";
+			sprite = "SPHRA0";
+			width = 96;
+			height = 192;
+			unflippable = true;
+			centerHitbox = true;
+		}
+		607
+		{
+			title = "Circle of Blue Spheres (Big)";
+			sprite = "SPHRA0";
+			width = 192;
+			unflippable = true;
+			centerHitbox = true;
+		}
+		608
+		{
+			title = "Circle of Rings and Spheres";
+			sprite = "SPHRA0";
+			width = 96;
+			height = 192;
+			unflippable = true;
+			centerHitbox = true;
+		}
+		609
+		{
+			title = "Circle of Rings and Spheres (Big)";
+			sprite = "SPHRA0";
+			width = 192;
+			unflippable = true;
+			centerHitbox = true;
+		}
+	}
+
+	invisible
+	{
+		color = 15; // White
+		title = "Misc. Invisible";
+		width = 8;
+		height = 16;
+		sprite = "UNKNA0";
+
+		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";
+			angletext = "Tag";
+		}
+
+		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";
+			angletext = "Order";
+		}
+
+		754
+		{
+			title = "Push Point";
+			flags4text = "[4] Fades using XY";
+			flags8text = "[8] Push using XYZ";
+			sprite = "GWLGA0";
+			angletext = "Radius";
+		}
+		755
+		{
+			title = "Pull Point";
+			flags4text = "[4] Fades using XY";
+			flags8text = "[8] Pull using XYZ";
+			sprite = "GWLRA0";
+			angletext = "Radius";
+		}
+		756
+		{
+			title = "Blast Linedef Executor";
+			sprite = "TOADA0";
+			width = 32;
+			height = 16;
+		}
+		757
+		{
+			title = "Fan Particle Generator";
+			sprite = "PRTLA0";
+			width = 8;
+			height = 16;
+			angletext = "Tag";
+		}
+		758
+		{
+			title = "Object Angle Anchor";
+			sprite = "internal:view";
+		}
+		760
+		{
+			title = "PolyObject Anchor";
+			sprite = "internal:polyanchor";
+			angletext = "ID";
+		}
+
+		761
+		{
+			title = "PolyObject Spawn Point";
+			sprite = "internal:polycenter";
+			angletext = "ID";
+		}
+
+		762
+		{
+			title = "PolyObject Spawn Point (Crush)";
+			sprite = "internal:polycentercrush";
+			angletext = "ID";
+		}
+		780
+		{
+			title = "Skybox View Point";
+			sprite = "internal:skyb";
+			flags4text = "[4] In-map centerpoint";
+			parametertext = "ID";
+		}
+	}
+
+	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;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1009
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Gargoyle (Big)";
+			sprite = "GARGB1";
+			width = 32;
+			height = 80;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1001
+		{
+			title = "Seaweed";
+			sprite = "SEWEA0";
+			width = 24;
+			height = 56;
+		}
+		1002
+		{
+			title = "Dripping Water";
+			sprite = "DRIPD0";
+			width = 8;
+			height = 16;
+			hangs = 1;
+			angletext = "Dripping interval";
+		}
+		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;
+			flags4text = "[4] Double size";
+		}
+		1008
+		{
+			title = "Stalagmite (DSZ1)";
+			sprite = "DSTGA0";
+			width = 8;
+			height = 116;
+			flags4text = "[4] Double size";
+		}
+		1010
+		{
+			arrow = 1;
+			title = "Light Beam";
+			sprite = "LIBEARAL";
+			width = 16;
+			height = 16;
+		}
+		1011
+		{
+			title = "Stalagmite (DSZ2)";
+			sprite = "DSTGA0";
+			width = 8;
+			height = 116;
+			flags4text = "[4] Double size";
+		}
+		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;
+			flags1text = "[1] Add corona";
+		}
+		1102
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Eggman Statue";
+			sprite = "ESTAA1";
+			width = 32;
+			height = 240;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1103
+		{
+			title = "CEZ Flower";
+			sprite = "FWR4A0";
+			width = 16;
+			height = 40;
+		}
+		1104
+		{
+			title = "Mace Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+			flags4text = "[4] No sounds";
+			flags8text = "[8] Double size";
+			angletext = "Tag";
+		}
+		1105
+		{
+			title = "Chain with Maces Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+			flags4text = "[4] No sounds";
+			flags8text = "[8] Double size";
+			angletext = "Tag";
+		}
+		1106
+		{
+			title = "Chained Spring Spawnpoint";
+			sprite = "YSPBA0";
+			width = 17;
+			height = 34;
+			flags4text = "[4] No sounds";
+			flags8text = "[8] Red spring";
+			angletext = "Tag";
+		}
+		1107
+		{
+			title = "Chain Spawnpoint";
+			sprite = "BMCHA0";
+			width = 17;
+			height = 34;
+			flags8text = "[8] Double size";
+			angletext = "Tag";
+		}
+		1108
+		{
+			arrow = 1;
+			title = "Hidden Chain Spawnpoint";
+			sprite = "internal:chain3";
+			width = 17;
+			height = 34;
+			flags8text = "[8] Double size";
+		}
+		1109
+		{
+			title = "Firebar Spawnpoint";
+			sprite = "BFBRA0";
+			width = 17;
+			height = 34;
+			flags4text = "[4] No sounds";
+			flags8text = "[8] Double size";
+			angletext = "Tag";
+		}
+		1110
+		{
+			title = "Custom Mace Spawnpoint";
+			sprite = "SMCEA0";
+			width = 17;
+			height = 34;
+			flags4text = "[4] No sounds";
+			angletext = "Tag";
+		}
+		1111
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Crawla Statue";
+			sprite = "CSTAA1";
+			width = 16;
+			height = 40;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1112
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "FaceStabber Statue";
+			sprite = "CBBSA1";
+			width = 32;
+			height = 72;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		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;
+			flags1text = "[1] Add corona";
+		}
+		1120
+		{
+			title = "Candle Pricket";
+			sprite = "CNDLB0";
+			width = 8;
+			height = 176;
+			flags1text = "[1] Add corona";
+		}
+		1121
+		{
+			title = "Flame Holder";
+			sprite = "FLMHA0";
+			width = 24;
+			height = 80;
+			flags1text = "[1] Add corona";
+			flags4text = "[4] No flame";
+		}
+		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;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		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;
+			flags8text = "[8] Moves perpetually";
+		}
+		1201
+		{
+			title = "Tumbleweed (Small)";
+			sprite = "STBLA0";
+			width = 12;
+			height = 24;
+			flags8text = "[8] Moves perpetually";
+		}
+		1202
+		{
+			arrow = 1;
+			title = "Rock Spawner";
+			sprite = "ROIAA0";
+			width = 8;
+			height = 16;
+			angletext = "Tag";
+		}
+		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;
+			flags8text = "[8] Allow non-minecart players";
+		}
+		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;
+			flags8text = "[8] Enable switching";
+		}
+		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;
+			flags8text = "[8] Waves vertically";
+			angletext = "On/Off time";
+			parametertext = "Strength";
+		}
+		1301
+		{
+			title = "Flame Jet (Vertical)";
+			sprite = "internal:flamev";
+			width = 16;
+			height = 40;
+			flags8text = "[8] Shoot downwards";
+			angletext = "On/Off time";
+			parametertext = "Strength";
+		}
+		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;
+			angletext = "Initial delay";
+			flags8text = "[8] Double size";
+		}
+		1305
+		{
+			title = "Rollout Rock";
+			sprite = "PUMIA1A5";
+			width = 30;
+			height = 60;
+			flags8text = "[8] Non-buoyant";
+		}
+		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 = "Trapgoyle";
+			sprite = "GARGA1";
+			width = 16;
+			height = 40;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1501
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Trapgoyle (Up)";
+			sprite = "GARGA1";
+			width = 16;
+			height = 40;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1502
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Trapgoyle (Down)";
+			sprite = "GARGA1";
+			width = 16;
+			height = 40;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1503
+		{
+			arrow = 1;
+			blocking = 2;
+			title = "Trapgoyle (Long)";
+			sprite = "GARGA1";
+			width = 16;
+			height = 40;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1504
+		{
+			title = "ATZ Target";
+			sprite = "RCRYB0";
+			width = 24;
+			height = 32;
+		}
+	}
+
+	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;
+			unflippable = true;
+			ignoreZ = true;
+			flagsvaluetext = "Order";
+			angletext = "Radius/Direction";
+			parametertext = "Mare";
+		}
+		1701
+		{
+			title = "Axis Transfer";
+			sprite = "internal:axis2";
+			unflippable = true;
+			ignoreZ = true;
+			flagsvaluetext = "Order";
+			parametertext = "Mare";
+		}
+		1702
+		{
+			title = "Axis Transfer Line";
+			sprite = "internal:axis3";
+			unflippable = true;
+			ignoreZ = true;
+			flagsvaluetext = "Order";
+			parametertext = "Mare";
+		}
+		1710
+		{
+			title = "Ideya Capture";
+			sprite = "CAPSA0";
+			width = 72;
+			height = 144;
+			angletext = "Rings";
+			parametertext = "Mare";
+		}
+	}
+
+	nights
+	{
+		color = 13; // Pink
+		title = "NiGHTS Items";
+		width = 16;
+		height = 32;
+
+		1703
+		{
+			title = "Ideya Drone";
+			sprite = "NDRNA1";
+			width = 16;
+			height = 56;
+			flags1text = "[1] Align player to middle";
+			flags4text = "[4] Align player to top";
+			flags8text = "[8] Die upon time up";
+			angletext = "Time limit";
+			parametertext = "Height";
+		}
+		1704
+		{
+			arrow = 1;
+			title = "NiGHTS Bumper";
+			sprite = "NBMPG3G7";
+			width = 32;
+			height = 64;
+			unflippable = true;
+			flagsvaluetext = "Pitch";
+			angletext = "Yaw";
+		}
+		1705
+		{
+			arrow = 1;
+			title = "Hoop (Generic)";
+			sprite = "HOOPA0";
+			width = 80;
+			height = 160;
+			unflippable = true;
+			centerHitbox = true;
+			flagsvaluetext = "Height";
+			angletext = "Pitch/Yaw";
+		}
+		1706
+		{
+			title = "Blue Sphere";
+			sprite = "SPHRA0";
+			width = 16;
+			height = 24;
+			flags8height = 24;
+			flags8text = "[8] Float";
+			unflippable = true;
+		}
+		1707
+		{
+			title = "Super Paraloop";
+			sprite = "NPRUA0";
+			flags4text = "[4] Bonus time only";
+			flags8text = "[8] Spawn immediately";
+		}
+		1708
+		{
+			title = "Drill Refill";
+			sprite = "NPRUB0";
+			flags4text = "[4] Bonus time only";
+			flags8text = "[8] Spawn immediately";
+		}
+		1709
+		{
+			title = "Nightopian Helper";
+			sprite = "NPRUC0";
+			flags4text = "[4] Bonus time only";
+			flags8text = "[8] Spawn immediately";
+		}
+		1711
+		{
+			title = "Extra Time";
+			sprite = "NPRUD0";
+			flags4text = "[4] Bonus time only";
+			flags8text = "[8] Spawn immediately";
+		}
+		1712
+		{
+			title = "Link Freeze";
+			sprite = "NPRUE0";
+			flags4text = "[4] Bonus time only";
+			flags8text = "[8] Spawn immediately";
+		}
+		1713
+		{
+			arrow = 1;
+			title = "Hoop (Customizable)";
+			flags1text = "[1] Radius +16";
+			flags2text = "[2] Radius +32";
+			flags4text = "[4] Radius +64";
+			flags8text = "[8] Radius +128";
+			sprite = "HOOPA0";
+			width = 80;
+			height = 160;
+			unflippable = true;
+			centerHitbox = true;
+		}
+		1714
+		{
+			title = "Ideya Anchor Point";
+			sprite = "internal:axis1";
+			width = 8;
+			height = 16;
+			parametertext = "Ideya";
+		}
+	}
+
+	mario
+	{
+		color = 6; // Brown
+		title = "Mario";
+
+		1800
+		{
+			title = "Coin";
+			sprite = "COINA0";
+			width = 16;
+			height = 24;
+			flags8height = 24;
+			flags8text = "[8] Float";
+		}
+		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;
+			angletext = "Jump strength";
+		}
+		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;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		1853
+		{
+			blocking = 2;
+			title = "Snowman (With Hat)";
+			sprite = "XMS3B0";
+			width = 16;
+			height = 80;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+		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;
+			flags4text = "[4] Slides when pushed";
+			flags8text = "[8] Not pushable";
+		}
+	}
+
+	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;
+			angletext = "Initial delay";
+		}
+		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;
+			flags1text = "Don't flicker";
+		}
+		2007
+		{
+			title = "Jack-o'-lantern 2";
+			sprite = "PUMKB0";
+			width = 16;
+			height = 40;
+			flags1text = "Don't flicker";
+		}
+		2008
+		{
+			title = "Jack-o'-lantern 3";
+			sprite = "PUMKC0";
+			width = 16;
+			height = 40;
+			flags1text = "Don't flicker";
+		}
+		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;
+			flags1text = "[1] Grayscale mode";
+		}
+		2105
+		{
+			title = "Mistletoe";
+			sprite = "XMS6A0";
+			width = 52;
+			height = 106;
+		}
+	}
+
+	flickies
+	{
+		color = 10; // Green
+		title = "Flickies";
+		width = 8;
+		height = 20;
+		flags1text = "[1] Move aimlessly";
+		flags4text = "[4] No movement";
+		flags8text = "[8] Hop";
+		angletext = "Radius";
+
+		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";
+			parametertext = "Color";
+		}
+		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";
+		}
+	}
+}
+
+//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;
+		}
+
+	}
+}
diff --git a/libs/libopenmpt/SRB2NOTE.md b/libs/libopenmpt/SRB2NOTE.md
index d664ddd7e49930e69cdf6c81d7981568fb2e3d78..7eac9183bc1fbac89647e64404c04458ae10255a 100644
--- a/libs/libopenmpt/SRB2NOTE.md
+++ b/libs/libopenmpt/SRB2NOTE.md
@@ -1,6 +1,6 @@
 # libopenmpt mingw-w64 binary info
 
-Current built version as of 2019/05/23 is 0.4.4+r11531.pkg
+Current built version as of 2019/09/27 is 0.4.7+r12088.pkg
 
 * mingw binaries (.dll): `bin/[x86 or x86_64]/mingw`
 * mingw import libraries (.dll.a): `lib/[x86 or x86_64]/mingw`
diff --git a/libs/libopenmpt/bin/x86/libopenmpt.dll b/libs/libopenmpt/bin/x86/libopenmpt.dll
index 0fc9e7656d5681b59df4a605b5f39ffe59fd55a9..8ad64fdfd94a9ca0008c3760dd8f57904beb14a6 100644
Binary files a/libs/libopenmpt/bin/x86/libopenmpt.dll and b/libs/libopenmpt/bin/x86/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86/mingw/libopenmpt.dll b/libs/libopenmpt/bin/x86/mingw/libopenmpt.dll
index 5fa3642566039530a3ddf633d45617ef9d93f9bd..945b475e62878802d824f500b127185a8be6498e 100644
Binary files a/libs/libopenmpt/bin/x86/mingw/libopenmpt.dll and b/libs/libopenmpt/bin/x86/mingw/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-mpg123.dll b/libs/libopenmpt/bin/x86/openmpt-mpg123.dll
index 79148a40ef3cef8e0f745ff14d1ccc1ac4aab025..b068ddd0c8d7d56bcbac4ea727cdeda8b6a5de03 100644
Binary files a/libs/libopenmpt/bin/x86/openmpt-mpg123.dll and b/libs/libopenmpt/bin/x86/openmpt-mpg123.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-ogg.dll b/libs/libopenmpt/bin/x86/openmpt-ogg.dll
index 6b5a42e8f1fd11353588880cd36c649993eebe9e..a5b4600637793a541e61f866734ec3720fcf664a 100644
Binary files a/libs/libopenmpt/bin/x86/openmpt-ogg.dll and b/libs/libopenmpt/bin/x86/openmpt-ogg.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-vorbis.dll b/libs/libopenmpt/bin/x86/openmpt-vorbis.dll
index 486b30731c234696ad1feb3ca9740eebbd390668..1ebed9b0e2401648f7f85c5adb7f8fca854b0832 100644
Binary files a/libs/libopenmpt/bin/x86/openmpt-vorbis.dll and b/libs/libopenmpt/bin/x86/openmpt-vorbis.dll differ
diff --git a/libs/libopenmpt/bin/x86/openmpt-zlib.dll b/libs/libopenmpt/bin/x86/openmpt-zlib.dll
index b6e028ab4f31b40c620983e73b76b747f9f3249e..41bc22d2f554108782fee07ff564812aa705dca7 100644
Binary files a/libs/libopenmpt/bin/x86/openmpt-zlib.dll and b/libs/libopenmpt/bin/x86/openmpt-zlib.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/libopenmpt.dll b/libs/libopenmpt/bin/x86_64/libopenmpt.dll
index 536492798e9167241fc526d010f6ed25e9d0686e..ffbb4b19a0224601264f85de933893aad9aae860 100644
Binary files a/libs/libopenmpt/bin/x86_64/libopenmpt.dll and b/libs/libopenmpt/bin/x86_64/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/mingw/libopenmpt.dll b/libs/libopenmpt/bin/x86_64/mingw/libopenmpt.dll
index 3f6bea95f72b0a8ee68a41128f8bb700918d568d..018ae827657b1c1b550d58129a09d495cbcddfd4 100644
Binary files a/libs/libopenmpt/bin/x86_64/mingw/libopenmpt.dll and b/libs/libopenmpt/bin/x86_64/mingw/libopenmpt.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-mpg123.dll b/libs/libopenmpt/bin/x86_64/openmpt-mpg123.dll
index f96d446f25c61df1368c4219324e3a2bf6138723..9a0fca8745a180ffe92ea8c05bff042665092bd4 100644
Binary files a/libs/libopenmpt/bin/x86_64/openmpt-mpg123.dll and b/libs/libopenmpt/bin/x86_64/openmpt-mpg123.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-ogg.dll b/libs/libopenmpt/bin/x86_64/openmpt-ogg.dll
index 3fd9514147973966faf77c7c2e08fb61c3fa4878..4bc1a336b89cf18fcf45b361bbabc7a13a7120f9 100644
Binary files a/libs/libopenmpt/bin/x86_64/openmpt-ogg.dll and b/libs/libopenmpt/bin/x86_64/openmpt-ogg.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-vorbis.dll b/libs/libopenmpt/bin/x86_64/openmpt-vorbis.dll
index 56047c82fbf4ff4eecc148718ed2b9fc1882a9da..ebe8ef9b3c05737e2d8bf626bd8a420a037ea29b 100644
Binary files a/libs/libopenmpt/bin/x86_64/openmpt-vorbis.dll and b/libs/libopenmpt/bin/x86_64/openmpt-vorbis.dll differ
diff --git a/libs/libopenmpt/bin/x86_64/openmpt-zlib.dll b/libs/libopenmpt/bin/x86_64/openmpt-zlib.dll
index 562d8e6a9e89b933d2ea4ef30ae2d7e500e35c97..319b646ff393aa2bbf3137fde1518da2bb9faea4 100644
Binary files a/libs/libopenmpt/bin/x86_64/openmpt-zlib.dll and b/libs/libopenmpt/bin/x86_64/openmpt-zlib.dll differ
diff --git a/libs/libopenmpt/changelog.md b/libs/libopenmpt/changelog.md
index 9847be1197908bc6962277f54d7a68f0b7d95a79..0b727955878745b2b0fdafc28e6eed682f597e12 100644
--- a/libs/libopenmpt/changelog.md
+++ b/libs/libopenmpt/changelog.md
@@ -5,6 +5,44 @@ Changelog {#changelog}
 For fully detailed change log, please see the source repository directly. This
 is just a high-level summary.
 
+### libopenmpt 0.4.7 (2019-09-23)
+
+ *  [**Bug**] Compilation fix for various platforms that do not provide
+    `std::aligned_alloc` in C++17 mode. The problematic dependency has been
+    removed. This should fix build problems on MinGW, OpenBSD, Haiku, and others
+    for good.
+
+ *  J2B: Ignore notes with non-existing instrument (fixes Ending.j2b).
+
+ *  mpg123: Update to v1.25.13 (2019-08-24).
+ *  ogg: Update to v1.3.4. (2019-08-31).
+ *  flac: Update to v1.3.3. (2019-08-04).
+
+### libopenmpt 0.4.6 (2019-08-10)
+
+ *  [**Bug**] Compilation fix for OpenBSD.
+ *  [**Bug**] Compilation fix for NO_PLUGINS being defined.
+
+ *  in_openmpt: Correct documentation. `openmpt-mpg123.dll` must be placed into
+    the Winamp directory.
+
+ *  Detect IT files unpacked with early UNMO3 versions.
+
+ *  mpg123: Update to v1.25.11 (2019-07-18).
+ *  minimp3: Update to commit 977514a6dfc4960d819a103f43b358e58ac6c28f
+    (2019-07-24).
+ *  miniz: Update to v2.1.0 (2019-05-05).
+ *  stb_vorbis: Update to v1.17 (2019-08-09).
+
+### libopenmpt 0.4.5 (2019-05-27)
+
+ *  [**Sec**] Possible crash during playback due out-of-bounds read in XM and
+    MT2 files (r11608).
+
+ *  Breaking out of a sustain loop through Note-Off sometimes didn't continue in
+    the regular sample loop.
+ *  Seeking did not stop notes playing with XM Key Off (Kxx) effect.
+
 ### libopenmpt 0.4.4 (2019-04-07)
 
  *  [**Bug**] Channel VU meters were swapped.
diff --git a/libs/libopenmpt/inc/libopenmpt/libopenmpt_version.h b/libs/libopenmpt/inc/libopenmpt/libopenmpt_version.h
index 5ca7f21d0ff4a776d8011fc72b67d140aa237b9d..4bb90278c6c903d9d62e4dbf752b79846c950307 100644
--- a/libs/libopenmpt/inc/libopenmpt/libopenmpt_version.h
+++ b/libs/libopenmpt/inc/libopenmpt/libopenmpt_version.h
@@ -19,7 +19,7 @@
 /*! \brief libopenmpt minor version number */
 #define OPENMPT_API_VERSION_MINOR 4
 /*! \brief libopenmpt patch version number */
-#define OPENMPT_API_VERSION_PATCH 4
+#define OPENMPT_API_VERSION_PATCH 7
 /*! \brief libopenmpt pre-release tag */
 #define OPENMPT_API_VERSION_PREREL ""
 /*! \brief libopenmpt pre-release flag */
diff --git a/libs/libopenmpt/lib/x86/libopenmpt.lib b/libs/libopenmpt/lib/x86/libopenmpt.lib
index 3f814528acc851cec1e764547570756c24d65225..c81c4c4edc15454f65e9f36738e17591a4ec8d15 100644
Binary files a/libs/libopenmpt/lib/x86/libopenmpt.lib and b/libs/libopenmpt/lib/x86/libopenmpt.lib differ
diff --git a/libs/libopenmpt/lib/x86/mingw/libopenmpt.dll.a b/libs/libopenmpt/lib/x86/mingw/libopenmpt.dll.a
index e3fa0c58fcd082319ea3b6365cb566dd7a3efa24..1d76372535b4a432a259ab2fc3a539b3af803c2d 100644
Binary files a/libs/libopenmpt/lib/x86/mingw/libopenmpt.dll.a and b/libs/libopenmpt/lib/x86/mingw/libopenmpt.dll.a differ
diff --git a/libs/libopenmpt/lib/x86_64/libopenmpt.lib b/libs/libopenmpt/lib/x86_64/libopenmpt.lib
index 75e3849fac966542b457684ca6f55a41237211d3..216d93b45b32cc968decb30aa240839bef2c13d6 100644
Binary files a/libs/libopenmpt/lib/x86_64/libopenmpt.lib and b/libs/libopenmpt/lib/x86_64/libopenmpt.lib differ
diff --git a/libs/libopenmpt/lib/x86_64/mingw/libopenmpt.dll.a b/libs/libopenmpt/lib/x86_64/mingw/libopenmpt.dll.a
index 72940372211829f26b413075496f7e201d5fb357..c6eda119fecb39b7f19c5c43e2c43fbeb88a0716 100644
Binary files a/libs/libopenmpt/lib/x86_64/mingw/libopenmpt.dll.a and b/libs/libopenmpt/lib/x86_64/mingw/libopenmpt.dll.a differ
diff --git a/src/Makefile b/src/Makefile
index b09801158fccb39b469d3004907289e755a25c5f..c5eb8b4a3ac2e7f69cb33b40e007125f448b88d1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -86,10 +86,7 @@
 D_DIR?=../bin/Resources
 D_FILES=$(D_DIR)/srb2.pk3 \
 	$(D_DIR)/player.dta \
-	$(D_DIR)/rings.wpn \
-	$(D_DIR)/drill.dta \
-	$(D_DIR)/soar.dta \
-	$(D_DIR)/zones.dta \
+	$(D_DIR)/zones.pk3 \
 	$(D_DIR)/music.dta \
 
 PKG_CONFIG?=pkg-config
diff --git a/src/b_bot.c b/src/b_bot.c
index 17211b3532d2ace7448fd3a7f1cafac82a1bef9e..651aeb03d6f1911be09114118ff034b54367d8c2 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -140,6 +140,9 @@ void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
 
 void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward, boolean left, boolean right, boolean strafeleft, boolean straferight, boolean jump, boolean spin)
 {
+	// don't try to do stuff if your sonic is in a minecart or something
+	if (players[consoleplayer].powers[pw_carry])
+		return;
 	// Turn the virtual keypresses into ticcmd_t.
 	if (twodlevel || mo->flags2 & MF2_TWOD) {
 		if (players[consoleplayer].climbing
@@ -218,7 +221,12 @@ boolean B_CheckRespawn(player_t *player)
 		return false;
 
 	// Low ceiling, do not want!
-	if (sonic->ceilingz - sonic->z < 2*sonic->height)
+	if (sonic->eflags & MFE_VERTICALFLIP)
+	{
+		if (sonic->z - sonic->floorz < (sonic->player->exiting ? 5 : 2)*sonic->height)
+			return false;
+	}
+	else if (sonic->ceilingz - sonic->z < (sonic->player->exiting ? 6 : 3)*sonic->height)
 		return false;
 
 	// If you're dead, wait a few seconds to respawn.
@@ -252,11 +260,11 @@ void B_RespawnBot(INT32 playernum)
 	y = sonic->y;
 	if (sonic->eflags & MFE_VERTICALFLIP) {
 		tails->eflags |= MFE_VERTICALFLIP;
-		z = sonic->z - FixedMul(512*FRACUNIT,sonic->scale);
+		z = sonic->z - (512*sonic->scale);
 		if (z < sonic->floorz)
 			z = sonic->floorz;
 	} else {
-		z = sonic->z + sonic->height + FixedMul(512*FRACUNIT,sonic->scale);
+		z = sonic->z + sonic->height + (512*sonic->scale);
 		if (z > sonic->ceilingz - sonic->height)
 			z = sonic->ceilingz - sonic->height;
 	}
diff --git a/src/command.c b/src/command.c
index cfb36f02fbf8e0d56694c856e51b5ed2641dd7e4..d39730f98e74ab976b0a529b373f46f8f869b41e 100644
--- a/src/command.c
+++ b/src/command.c
@@ -49,6 +49,7 @@ static void COM_Exec_f(void);
 static void COM_Wait_f(void);
 static void COM_Help_f(void);
 static void COM_Toggle_f(void);
+static void COM_Add_f(void);
 
 static void CV_EnforceExecVersion(void);
 static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
@@ -291,6 +292,7 @@ void COM_Init(void)
 	COM_AddCommand("wait", COM_Wait_f);
 	COM_AddCommand("help", COM_Help_f);
 	COM_AddCommand("toggle", COM_Toggle_f);
+	COM_AddCommand("add", COM_Add_f);
 	RegisterNetXCmd(XD_NETVAR, Got_NetVar);
 }
 
@@ -709,15 +711,21 @@ static void COM_Help_f(void)
 
 	if (COM_Argc() > 1)
 	{
-		cvar = CV_FindVar(COM_Argv(1));
+		const char *help = COM_Argv(1);
+		cvar = CV_FindVar(help);
 		if (cvar)
 		{
-			CONS_Printf(M_GetText("Variable %s:\n"), cvar->name);
+			boolean floatmode = false;
+			const char *cvalue = NULL;
+			CONS_Printf("\x82""Variable %s:\n", cvar->name);
 			CONS_Printf(M_GetText("  flags :"));
 			if (cvar->flags & CV_SAVE)
 				CONS_Printf("AUTOSAVE ");
 			if (cvar->flags & CV_FLOAT)
+			{
 				CONS_Printf("FLOAT ");
+				floatmode = true;
+			}
 			if (cvar->flags & CV_NETVAR)
 				CONS_Printf("NETVAR ");
 			if (cvar->flags & CV_CALL)
@@ -727,59 +735,113 @@ static void COM_Help_f(void)
 			CONS_Printf("\n");
 			if (cvar->PossibleValue)
 			{
-				if (stricmp(cvar->PossibleValue[0].strvalue, "MIN") == 0)
-				{
-					for (i = 1; cvar->PossibleValue[i].strvalue != NULL; i++)
-						if (!stricmp(cvar->PossibleValue[i].strvalue, "MAX"))
-							break;
-					CONS_Printf(M_GetText("  range from %d to %d\n"), cvar->PossibleValue[0].value,
-						cvar->PossibleValue[i].value);
-					CONS_Printf(M_GetText(" Current value: %d\n"), cvar->value);
-				}
+				CONS_Printf(" Possible values:\n");
+				if (cvar->PossibleValue == CV_YesNo)
+					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
 				{
-					const char *cvalue = NULL;
-					CONS_Printf(M_GetText("  possible value : %s\n"), cvar->name);
+#define MINVAL 0
+#define MAXVAL 1
+					if (!stricmp(cvar->PossibleValue[MINVAL].strvalue, "MIN"))
+					{
+						if (floatmode)
+							CONS_Printf("  range from %f to %f\n", FIXED_TO_FLOAT(cvar->PossibleValue[MINVAL].value),
+								FIXED_TO_FLOAT(cvar->PossibleValue[MAXVAL].value));
+						else
+							CONS_Printf("  range from %d to %d\n", cvar->PossibleValue[MINVAL].value,
+								cvar->PossibleValue[MAXVAL].value);
+						i = MAXVAL+1;
+					}
+#undef MINVAL
+#undef MAXVAL
+
+					//CONS_Printf(M_GetText("  possible value : %s\n"), cvar->name);
 					while (cvar->PossibleValue[i].strvalue)
 					{
-						CONS_Printf("    %-2d : %s\n", cvar->PossibleValue[i].value,
-							cvar->PossibleValue[i].strvalue);
+						if (floatmode)
+							CONS_Printf("  %-2f : %s\n", FIXED_TO_FLOAT(cvar->PossibleValue[i].value),
+								cvar->PossibleValue[i].strvalue);
+						else
+							CONS_Printf("  %-2d : %s\n", cvar->PossibleValue[i].value,
+								cvar->PossibleValue[i].strvalue);
 						if (cvar->PossibleValue[i].value == cvar->value)
 							cvalue = cvar->PossibleValue[i].strvalue;
 						i++;
 					}
-					if (cvalue)
-						CONS_Printf(M_GetText(" Current value: %s\n"), cvalue);
-					else
-						CONS_Printf(M_GetText(" Current value: %d\n"), cvar->value);
 				}
 			}
+
+			if (cvalue)
+				CONS_Printf(" Current value: %s\n", cvalue);
+			else if (cvar->string)
+				CONS_Printf(" Current value: %s\n", cvar->string);
 			else
-				CONS_Printf(M_GetText(" Current value: %d\n"), cvar->value);
+				CONS_Printf(" Current value: %d\n", cvar->value);
 		}
 		else
-			CONS_Printf(M_GetText("No help for this command/variable\n"));
+		{
+			for (cmd = com_commands; cmd; cmd = cmd->next)
+			{
+				if (strcmp(cmd->name, help))
+					continue;
+
+				CONS_Printf("\x82""Command %s:\n", cmd->name);
+				CONS_Printf("  help is not available for commands");
+				CONS_Printf("\x82""\nCheck wiki.srb2.org for more or try typing <name> without arguments\n");
+				return;
+			}
+
+			CONS_Printf("No exact match, searching...\n");
+
+			// variables
+			CONS_Printf("\x82""Variables:\n");
+			for (cvar = consvar_vars; cvar; cvar = cvar->next)
+			{
+				if ((cvar->flags & CV_NOSHOWHELP) || (!strstr(cvar->name, help)))
+					continue;
+				CONS_Printf("%s ", cvar->name);
+				i++;
+			}
+
+			// commands
+			CONS_Printf("\x82""\nCommands:\n");
+			for (cmd = com_commands; cmd; cmd = cmd->next)
+			{
+				if (!strstr(cmd->name, help))
+					continue;
+				CONS_Printf("%s ",cmd->name);
+				i++;
+			}
+
+			CONS_Printf("\x82""\nCheck wiki.srb2.org for more or type help <command or variable>\n");
+
+			CONS_Debug(DBG_GAMELOGIC, "\x87Total : %d\n", i);
+		}
+		return;
 	}
 	else
 	{
-		// commands
-		CONS_Printf("\x82%s", M_GetText("Commands\n"));
-		for (cmd = com_commands; cmd; cmd = cmd->next)
+		// variables
+		CONS_Printf("\x82""Variables:\n");
+		for (cvar = consvar_vars; cvar; cvar = cvar->next)
 		{
-			CONS_Printf("%s ",cmd->name);
+			if (cvar->flags & CV_NOSHOWHELP)
+				continue;
+			CONS_Printf("%s ", cvar->name);
 			i++;
 		}
 
-		// variables
-		CONS_Printf("\n\x82%s", M_GetText("Variables\n"));
-		for (cvar = consvar_vars; cvar; cvar = cvar->next)
+		// commands
+		CONS_Printf("\x82""\nCommands:\n");
+		for (cmd = com_commands; cmd; cmd = cmd->next)
 		{
-			if (!(cvar->flags & CV_NOSHOWHELP))
-				CONS_Printf("%s ", cvar->name);
+			CONS_Printf("%s ",cmd->name);
 			i++;
 		}
 
-		CONS_Printf("\n\x82%s", M_GetText("Read help file for more or type help <command or variable>\n"));
+		CONS_Printf("\x82""\nCheck wiki.srb2.org for more or type help <command or variable>\n");
 
 		CONS_Debug(DBG_GAMELOGIC, "\x82Total : %d\n", i);
 	}
@@ -816,6 +878,30 @@ static void COM_Toggle_f(void)
 	CV_AddValue(cvar, +1);
 }
 
+/** Command variant of CV_AddValue
+  */
+static void COM_Add_f(void)
+{
+	consvar_t *cvar;
+
+	if (COM_Argc() != 3)
+	{
+		CONS_Printf(M_GetText("Add <cvar_name> <value>: Add to the value of a cvar. Negative values work too!\n"));
+		return;
+	}
+	cvar = CV_FindVar(COM_Argv(1));
+	if (!cvar)
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("%s is not a cvar\n"), COM_Argv(1));
+		return;
+	}
+
+	if (( cvar->flags & CV_FLOAT ))
+		CV_Set(cvar, va("%f", FIXED_TO_FLOAT (cvar->value) + atof(COM_Argv(2))));
+	else
+		CV_AddValue(cvar, atoi(COM_Argv(2)));
+}
+
 // =========================================================================
 //                      VARIABLE SIZE BUFFERS
 // =========================================================================
@@ -1123,32 +1209,42 @@ static void Setvalue(consvar_t *var, const char *valstr, boolean stealth)
 
 		if (var->PossibleValue[0].strvalue && !stricmp(var->PossibleValue[0].strvalue, "MIN")) // bounded cvar
 		{
+#define MINVAL 0
+#define MAXVAL 1
 			INT32 i;
-			// search for maximum
-			for (i = 1; var->PossibleValue[i].strvalue; i++)
-				if (!stricmp(var->PossibleValue[i].strvalue, "MAX"))
-					break;
 #ifdef PARANOIA
-			if (!var->PossibleValue[i].strvalue)
+			if (!var->PossibleValue[MAXVAL].strvalue)
 				I_Error("Bounded cvar \"%s\" without maximum!\n", var->name);
 #endif
 
-			if ((v != INT32_MIN && v < var->PossibleValue[0].value) || !stricmp(valstr, "MIN"))
+			// search for other
+			for (i = MAXVAL+1; var->PossibleValue[i].strvalue; i++)
+				if (v == var->PossibleValue[i].value || !stricmp(var->PossibleValue[i].strvalue, valstr))
+				{
+					var->value = var->PossibleValue[i].value;
+					var->string = var->PossibleValue[i].strvalue;
+					goto finish;
+				}
+
+
+			if ((v != INT32_MIN && v < var->PossibleValue[MINVAL].value) || !stricmp(valstr, "MIN"))
 			{
-				v = var->PossibleValue[0].value;
-				valstr = var->PossibleValue[0].strvalue;
+				v = var->PossibleValue[MINVAL].value;
+				valstr = var->PossibleValue[MINVAL].strvalue;
 				override = true;
 				overrideval = v;
 			}
-			else if ((v != INT32_MIN && v > var->PossibleValue[i].value) || !stricmp(valstr, "MAX"))
+			else if ((v != INT32_MIN && v > var->PossibleValue[MAXVAL].value) || !stricmp(valstr, "MAX"))
 			{
-				v = var->PossibleValue[i].value;
-				valstr = var->PossibleValue[i].strvalue;
+				v = var->PossibleValue[MAXVAL].value;
+				valstr = var->PossibleValue[MAXVAL].strvalue;
 				override = true;
 				overrideval = v;
 			}
 			if (v == INT32_MIN)
 				goto badinput;
+#undef MINVAL
+#undef MAXVAL
 		}
 		else
 		{
@@ -1515,6 +1611,9 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 {
 	INT32 newvalue, max;
 
+	if (!increment)
+		return;
+
 	// count pointlimit better
 	if (var == &cv_pointlimit && (gametype == GT_MATCH))
 		increment *= 50;
@@ -1538,13 +1637,11 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 
 	if (var->PossibleValue)
 	{
-#define MINVAL 0
 		if (var == &cv_nextmap)
 		{
 			// Special case for the nextmap variable, used only directly from the menu
 			INT32 oldvalue = var->value - 1, gt;
 			gt = cv_newgametype.value;
-			if (increment != 0) // Going up!
 			{
 				newvalue = var->value - 1;
 				do
@@ -1575,21 +1672,58 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 				return;
 			}
 		}
+#define MINVAL 0
+#define MAXVAL 1
 		else if (var->PossibleValue[MINVAL].strvalue && !strcmp(var->PossibleValue[MINVAL].strvalue, "MIN"))
 		{
-			// search the next to last
-			for (max = 0; var->PossibleValue[max+1].strvalue; max++)
-				;
+#ifdef PARANOIA
+			if (!var->PossibleValue[MAXVAL].strvalue)
+				I_Error("Bounded cvar \"%s\" without maximum!\n", var->name);
+#endif
 
-			if (newvalue < var->PossibleValue[MINVAL].value) // add the max+1
-				newvalue += var->PossibleValue[max].value - var->PossibleValue[MINVAL].value + 1;
+			if (newvalue < var->PossibleValue[MINVAL].value || newvalue > var->PossibleValue[MAXVAL].value)
+			{
+				INT32 currentindice = -1, newindice;
+				for (max = MAXVAL+1; var->PossibleValue[max].strvalue; max++)
+				{
+					if (var->PossibleValue[max].value == newvalue)
+					{
+						increment = 0;
+						currentindice = max;
+					}
+					else if (var->PossibleValue[max].value == var->value)
+						currentindice = max;
+				}
 
-			newvalue = var->PossibleValue[MINVAL].value + (newvalue - var->PossibleValue[MINVAL].value)
-				% (var->PossibleValue[max].value - var->PossibleValue[MINVAL].value + 1);
+				if (increment)
+				{
+					increment = (increment > 0) ? 1 : -1;
+					if (currentindice == -1 && max != MAXVAL+1)
+						newindice = ((increment > 0) ? MAXVAL : max) + increment;
+					else
+						newindice = currentindice + increment;
 
-			CV_SetValue(var, newvalue);
-#undef MINVAL
+					if (newindice >= max || newindice <= MAXVAL)
+					{
+						if (var == &cv_pointlimit && (gametype == GT_MATCH) && increment > 0)
+							CV_SetValue(var, 50);
+						else
+						{
+							newvalue = var->PossibleValue[((increment > 0) ? MINVAL : MAXVAL)].value;
+							CV_SetValue(var, newvalue);
+						}
+					}
+					else
+						CV_Set(var, var->PossibleValue[newindice].strvalue);
+				}
+				else
+					CV_Set(var, var->PossibleValue[currentindice].strvalue);
+			}
+			else
+				CV_SetValue(var, newvalue);
 		}
+#undef MINVAL
+#undef MAXVAL
 		else
 		{
 			INT32 currentindice = -1, newindice;
@@ -1599,8 +1733,6 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 				if (var->PossibleValue[max].value == var->value)
 					currentindice = max;
 
-			max--;
-
 			if (var == &cv_chooseskin)
 			{
 				// Special case for the chooseskin variable, used only directly from the menu
@@ -1632,7 +1764,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
 					var->value);
 #endif
 
-			newindice = (currentindice + increment + max + 1) % (max+1);
+			newindice = (currentindice + increment + max) % max;
 			CV_Set(var, var->PossibleValue[newindice].strvalue);
 		}
 	}
diff --git a/src/config.h.in b/src/config.h.in
index fc32aef823e27f61abf1e22859073112646158ef..58d07e26d2d875d688109b6895a3ceecd534eb53 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -13,7 +13,7 @@
 
 #define ASSET_HASH_SRB2_PK3   "${SRB2_ASSET_srb2.pk3_HASH}"
 #define ASSET_HASH_PLAYER_DTA "${SRB2_ASSET_player.dta_HASH}"
-#define ASSET_HASH_ZONES_DTA  "${SRB2_ASSET_zones.dta_HASH}"
+#define ASSET_HASH_ZONES_PK3  "${SRB2_ASSET_zones.pk3_HASH}"
 #ifdef USE_PATCH_DTA
 #define ASSET_HASH_PATCH_PK3  "${SRB2_ASSET_patch.pk3_HASH}"
 #endif
@@ -30,7 +30,7 @@
  * Last updated 2018 / ?? / ?? - v2.2 - patch.pk3
  */
 #define ASSET_HASH_SRB2_PK3   "c1b9577687f8a795104aef4600720ea7"
-#define ASSET_HASH_ZONES_DTA  "303838c6c534d9540288360fa49cca60"
+#define ASSET_HASH_ZONES_PK3  "303838c6c534d9540288360fa49cca60"
 #define ASSET_HASH_PLAYER_DTA "cfca0f1c73023cbbd8f844f45480f799"
 #ifdef USE_PATCH_DTA
 #define ASSET_HASH_PATCH_PK3  "dbbf8bc6121618ee3be2d5b14650429b"
diff --git a/src/console.c b/src/console.c
index 09a6cab453b288f83d9d5778ab88cc878837642a..11e236696093e3c754c26a861d8862206af9312a 100644
--- a/src/console.c
+++ b/src/console.c
@@ -175,11 +175,11 @@ static void CONS_Clear_f(void)
 
 // Choose english keymap
 //
-static void CONS_English_f(void)
+/*static void CONS_English_f(void)
 {
 	shiftxform = english_shiftxform;
 	CONS_Printf(M_GetText("%s keymap.\n"), M_GetText("English"));
-}
+}*/
 
 static char *bindtable[NUMINPUTS];
 
@@ -394,7 +394,7 @@ void CON_Init(void)
 	// register our commands
 	//
 	COM_AddCommand("cls", CONS_Clear_f);
-	COM_AddCommand("english", CONS_English_f);
+	//COM_AddCommand("english", CONS_English_f);
 	// set console full screen for game startup MAKE SURE VID_Init() done !!!
 	con_destlines = vid.height;
 	con_curlines = vid.height;
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 1e4ee6a0473cfecc00cdc012be43102afc7b39cd..ed66d51d29c1a124d938740734accc77c183538f 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -83,11 +83,9 @@ tic_t jointimeout = (10*TICRATE);
 static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
 static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
 
-#ifdef NEWPING
 UINT16 pingmeasurecount = 1;
 UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
 UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values.
-#endif
 SINT8 nodetoplayer[MAXNETNODES];
 SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen)
 UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen
@@ -621,6 +619,10 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 	rsp->friction = LONG(players[i].mo->friction);
 	rsp->movefactor = LONG(players[i].mo->movefactor);
 
+	rsp->sprite = (spritenum_t)LONG(players[i].mo->sprite);
+	rsp->frame = LONG(players[i].mo->frame);
+	rsp->sprite2 = players[i].mo->sprite2;
+	rsp->anim_duration = SHORT(players[i].mo->anim_duration);
 	rsp->tics = LONG(players[i].mo->tics);
 	rsp->statenum = (statenum_t)LONG(players[i].mo->state-states); // :(
 	rsp->eflags = (UINT16)SHORT(players[i].mo->eflags);
@@ -767,8 +769,17 @@ static void resynch_read_player(resynch_pak *rsp)
 	players[i].mo->momy = LONG(rsp->momy);
 	players[i].mo->momz = LONG(rsp->momz);
 	players[i].mo->movefactor = LONG(rsp->movefactor);
+
+	// Don't use P_SetMobjStateNF to restore state, write/read all the values manually!
+	// This should stop those stupid console errors, hopefully.
+	// -- Monster Iestyn
+	players[i].mo->sprite = (spritenum_t)LONG(rsp->sprite);
+	players[i].mo->frame = LONG(rsp->frame);
+	players[i].mo->sprite2 = rsp->sprite2;
+	players[i].mo->anim_duration = SHORT(rsp->anim_duration);
 	players[i].mo->tics = LONG(rsp->tics);
-	P_SetMobjStateNF(players[i].mo, LONG(rsp->statenum));
+	players[i].mo->state = &states[LONG(rsp->statenum)];
+
 	players[i].mo->x = LONG(rsp->x);
 	players[i].mo->y = LONG(rsp->y);
 	players[i].mo->z = LONG(rsp->z);
@@ -1253,7 +1264,8 @@ static boolean CL_SendJoin(void)
 	netbuffer->u.clientcfg.localplayers = localplayers;
 	netbuffer->u.clientcfg.version = VERSION;
 	netbuffer->u.clientcfg.subversion = SUBVERSION;
-
+	strncpy(netbuffer->u.clientcfg.names[0], cv_playername.zstring, MAXPLAYERNAME);
+	strncpy(netbuffer->u.clientcfg.names[1], cv_playername2.zstring, MAXPLAYERNAME);
 	return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
 }
 
@@ -1312,33 +1324,13 @@ static void SV_SendPlayerInfo(INT32 node)
 			continue;
 		}
 
-		netbuffer->u.playerinfo[i].node = (UINT8)playernode[i];
+		netbuffer->u.playerinfo[i].node = i;
 		strncpy(netbuffer->u.playerinfo[i].name, (const char *)&player_names[i], MAXPLAYERNAME+1);
 		netbuffer->u.playerinfo[i].name[MAXPLAYERNAME] = '\0';
 
 		//fetch IP address
-		{
-			const char *claddress;
-			UINT32 numericaddress[4];
-
-			memset(netbuffer->u.playerinfo[i].address, 0, 4);
-			if (playernode[i] == 0)
-			{
-				//127.0.0.1
-				netbuffer->u.playerinfo[i].address[0] = 127;
-				netbuffer->u.playerinfo[i].address[3] = 1;
-			}
-			else if (playernode[i] > 0 && I_GetNodeAddress && (claddress = I_GetNodeAddress(playernode[i])) != NULL)
-			{
-				if (sscanf(claddress, "%d.%d.%d.%d", &numericaddress[0], &numericaddress[1], &numericaddress[2], &numericaddress[3]) < 4)
-					goto badaddress;
-				netbuffer->u.playerinfo[i].address[0] = (UINT8)numericaddress[0];
-				netbuffer->u.playerinfo[i].address[1] = (UINT8)numericaddress[1];
-				netbuffer->u.playerinfo[i].address[2] = (UINT8)numericaddress[2];
-				netbuffer->u.playerinfo[i].address[3] = (UINT8)numericaddress[3];
-			}
-		}
-		badaddress:
+		//No, don't do that, you fuckface.
+		memset(netbuffer->u.playerinfo[i].address, 0, 4);
 
 		if (G_GametypeHasTeams())
 		{
@@ -2870,12 +2862,10 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false);
 			kickreason = KR_KICK;
 			break;
-#ifdef NEWPING
 		case KICK_MSG_PING_HIGH:
 			HU_AddChatText(va("\x82*%s left the game (Broke ping limit)", player_names[pnum]), false);
 			kickreason = KR_PINGLIMIT;
 			break;
-#endif
 		case KICK_MSG_CON_FAIL:
 			HU_AddChatText(va("\x82*%s left the game (Synch Failure)", player_names[pnum]), false);
 			kickreason = KR_SYNCH;
@@ -2948,10 +2938,8 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		D_StartTitle();
 		if (msg == KICK_MSG_CON_FAIL)
 			M_StartMessage(M_GetText("Server closed connection\n(synch failure)\nPress ESC\n"), NULL, MM_NOTHING);
-#ifdef NEWPING
 		else if (msg == KICK_MSG_PING_HIGH)
 			M_StartMessage(M_GetText("Server closed connection\n(Broke ping limit)\nPress ESC\n"), NULL, MM_NOTHING);
-#endif
 		else if (msg == KICK_MSG_BANNED)
 			M_StartMessage(M_GetText("You have been banned by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
 		else if (msg == KICK_MSG_CUSTOM_KICK)
@@ -2969,7 +2957,7 @@ consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, N
 consvar_t cv_joinnextround = {"joinnextround", "Off", CV_SAVE|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}};
 consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static CV_PossibleValue_t resynchattempts_cons_t[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}};
 consvar_t cv_resynchattempts = {"resynchattempts", "10", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
 consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
 
@@ -3199,6 +3187,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 	if (!splitscreen && !botingame)
 		CL_ClearPlayer(newplayernum);
 	playeringame[newplayernum] = true;
+	READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
 	G_AddPlayer(newplayernum);
 	if (newplayernum+1 > doomcom->numslots)
 		doomcom->numslots = (INT16)(newplayernum+1);
@@ -3231,10 +3220,10 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 		{
 			const char *address;
 			if (I_GetNodeAddress && (address = I_GetNodeAddress(node)) != NULL)
-				HU_AddChatText(va("\x82*Player %d has joined the game (node %d) (%s)", newplayernum+1, node, address), false);	// merge join notification + IP to avoid clogging console/chat.
+				HU_AddChatText(va("\x82*%s has joined the game (node %d) (%s)", player_names[newplayernum], node, address), false);	// merge join notification + IP to avoid clogging console/chat.
 		}
 		else
-			HU_AddChatText(va("\x82*Player %d has joined the game (node %d)", newplayernum+1, node), false);	// if you don't wanna see the join address.
+			HU_AddChatText(va("\x82*%s has joined the game (node %d)", player_names[newplayernum], node), false);	// if you don't wanna see the join address.
 	}
 
 	if (server && multiplayer && motd[0] != '\0')
@@ -3245,10 +3234,11 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 #endif
 }
 
-static boolean SV_AddWaitingPlayers(void)
+static boolean SV_AddWaitingPlayers(const char *name, const char *name2)
 {
 	INT32 node, n, newplayer = false;
-	UINT8 buf[2];
+	UINT8 buf[2 + MAXPLAYERNAME];
+	UINT8 *p;
 	UINT8 newplayernum = 0;
 
 	// What is the reason for this? Why can't newplayernum always be 0?
@@ -3331,18 +3321,23 @@ static boolean SV_AddWaitingPlayers(void)
 
 			playernode[newplayernum] = (UINT8)node;
 
+			p = buf + 2;
 			buf[0] = (UINT8)node;
 			buf[1] = newplayernum;
 			if (playerpernode[node] < 1)
+			{
 				nodetoplayer[node] = newplayernum;
+				WRITESTRINGN(p, name, MAXPLAYERNAME);
+			}
 			else
 			{
 				nodetoplayer2[node] = newplayernum;
 				buf[1] |= 0x80;
+				WRITESTRINGN(p, name2, MAXPLAYERNAME);
 			}
 			playerpernode[node]++;
 
-			SendNetXCmd(XD_ADDPLAYER, &buf, 2);
+			SendNetXCmd(XD_ADDPLAYER, &buf, p - buf);
 
 			DEBFILE(va("Server added player %d node %d\n", newplayernum, node));
 			// use the next free slot (we can't put playeringame[newplayernum] = true here)
@@ -3404,7 +3399,7 @@ boolean SV_SpawnServer(void)
 		else doomcom->numslots = 1;
 	}
 
-	return SV_AddWaitingPlayers();
+	return SV_AddWaitingPlayers(cv_playername.zstring, cv_playername2.zstring);
 }
 
 void SV_StopServer(void)
@@ -3475,6 +3470,9 @@ static size_t TotalTextCmdPerTic(tic_t tic)
   */
 static void HandleConnect(SINT8 node)
 {
+	char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
+	INT32 i;
+
 	if (bannednode && bannednode[node])
 		SV_SendRefuse(node, M_GetText("You have been banned\nfrom the server"));
 	else if (netbuffer->u.clientcfg.version != VERSION
@@ -3494,6 +3492,16 @@ static void HandleConnect(SINT8 node)
 		boolean newnode = false;
 #endif
 
+		for (i = 0; i < netbuffer->u.clientcfg.localplayers - playerpernode[node]; i++)
+		{
+			strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1);
+			if (!EnsurePlayerNameIsGood(names[i], -1))
+			{
+				SV_SendRefuse(node, "Bad player name");
+				return;
+			}
+		}
+
 		// client authorised to join
 		nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]);
 		if (!nodeingame[node])
@@ -3534,7 +3542,7 @@ static void HandleConnect(SINT8 node)
 				SV_SendSaveGame(node); // send a complete game state
 				DEBFILE("send savegame\n");
 			}
-			SV_AddWaitingPlayers();
+			SV_AddWaitingPlayers(names[0], names[1]);
 			player_joining = true;
 		}
 #else
@@ -3841,7 +3849,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 				break;
 
 			// Ignore tics from those not synched
-			if (resynch_inprogress[node])
+			if (resynch_inprogress[node] && nettics[node] == gametic)
 				break;
 
 			// To save bytes, only the low byte of tic numbers are sent
@@ -4175,7 +4183,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 			resynch_local_inprogress = true;
 			CL_AcknowledgeResynch(&netbuffer->u.resynchpak);
 			break;
-#ifdef NEWPING
 		case PT_PING:
 			// Only accept PT_PING from the server.
 			if (node != servernode)
@@ -4203,7 +4210,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 			}
 
 			break;
-#endif
 		case PT_SERVERCFG:
 			break;
 		case PT_FILEFRAGMENT:
@@ -4699,7 +4705,7 @@ void TryRunTics(tic_t realtics)
 	if (player_joining)
 		return;
 
-	if (neededtic > gametic)
+	if (neededtic > gametic && !resynch_local_inprogress)
 	{
 		if (advancedemo)
 		{
@@ -4722,7 +4728,6 @@ void TryRunTics(tic_t realtics)
 	}
 }
 
-#ifdef NEWPING
 static inline void PingUpdate(void)
 {
 	INT32 i;
@@ -4780,7 +4785,6 @@ static inline void PingUpdate(void)
 
 	pingmeasurecount = 1; //Reset count
 }
-#endif
 
 void NetUpdate(void)
 {
@@ -4805,7 +4809,6 @@ void NetUpdate(void)
 
 	gametime = nowtime;
 
-#ifdef NEWPING
 	if (server)
 	{
 		if (netgame && !(gametime % 255))
@@ -4816,7 +4819,6 @@ void NetUpdate(void)
 				realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
 		pingmeasurecount++;
 	}
-#endif
 
 	if (client)
 		maketic = neededtic;
@@ -4858,8 +4860,13 @@ void NetUpdate(void)
 			for (i = 0; i < MAXNETNODES; ++i)
 				if (resynch_inprogress[i])
 				{
-					SV_SendResynch(i);
-					counts = -666;
+					if (!nodeingame[i] || nettics[i] == gametic)
+					{
+						SV_SendResynch(i);
+						counts = -666;
+					}
+					else
+						counts = 0; // Let the client catch up with the server
 				}
 
 			// Do not make tics while resynching
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index d09d2aa48e024a5d7ce0451b26bf347663f83469..d7c210895ab5c2d0a376d384d31d586898925e6e 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -15,6 +15,7 @@
 
 #include "d_ticcmd.h"
 #include "d_netcmd.h"
+#include "d_net.h"
 #include "tables.h"
 #include "d_player.h"
 
@@ -73,9 +74,7 @@ typedef enum
 
 	PT_LOGIN,         // Login attempt from the client.
 
-#ifdef NEWPING
 	PT_PING,          // Packet sent to tell clients the other client's latency to server.
-#endif
 	NUMPACKETTYPE
 } packettype_t;
 
@@ -265,6 +264,10 @@ typedef struct
 	fixed_t friction;
 	fixed_t movefactor;
 
+	spritenum_t sprite;
+	UINT32 frame;
+	UINT8 sprite2;
+	UINT16 anim_duration;
 	INT32 tics;
 	statenum_t statenum;
 	UINT32 flags;
@@ -322,6 +325,7 @@ typedef struct
 	UINT8 subversion; // Contains build version
 	UINT8 localplayers;
 	UINT8 mode;
+	char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME];
 } ATTRPACK clientconfig_pak;
 
 #define MAXSERVERNAME 32
@@ -421,9 +425,7 @@ typedef struct
 		msaskinfo_pak msaskinfo;            //          22 bytes
 		plrinfo playerinfo[MAXPLAYERS];     //        1152 bytes (I'd say 36~38)
 		plrconfig playerconfig[MAXPLAYERS]; // (up to) 896 bytes (welp they ARE)
-#ifdef NEWPING
 		UINT32 pingtable[MAXPLAYERS];       //         128 bytes
-#endif
 	} u; // This is needed to pack diff packet types data together
 } ATTRPACK doomdata_t;
 
@@ -457,9 +459,7 @@ extern consvar_t cv_playbackspeed;
 #define KICK_MSG_PLAYER_QUIT 3
 #define KICK_MSG_TIMEOUT     4
 #define KICK_MSG_BANNED      5
-#ifdef NEWPING
 #define KICK_MSG_PING_HIGH   6
-#endif
 #define KICK_MSG_CUSTOM_KICK 7
 #define KICK_MSG_CUSTOM_BAN  8
 
@@ -484,11 +484,9 @@ extern SINT8 servernode;
 void Command_Ping_f(void);
 extern tic_t connectiontimeout;
 extern tic_t jointimeout;
-#ifdef NEWPING
 extern UINT16 pingmeasurecount;
 extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
-#endif
 
 extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend, cv_noticedownload, cv_downloadspeed;
 
diff --git a/src/d_main.c b/src/d_main.c
index 5e21c7d64e7f64bff848d23b357e0c30803572e8..3308b18db61938e33e10e1048bd1029d45a8d232 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -359,7 +359,7 @@ static void D_Display(void)
 
 		// clean up border stuff
 		// see if the border needs to be initially drawn
-		if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide))
+		if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
 		{
 			// draw the view directly
 
@@ -856,7 +856,7 @@ static void IdentifyVersion(void)
 	// checking in D_SRB2Main
 
 	// Add the maps
-	D_AddFile(va(pandf,srb2waddir,"zones.dta"));
+	D_AddFile(va(pandf,srb2waddir,"zones.pk3"));
 
 	// Add the players
 	D_AddFile(va(pandf,srb2waddir, "player.dta"));
@@ -1149,10 +1149,10 @@ void D_SRB2Main(void)
 
 	// Check MD5s of autoloaded files
 	W_VerifyFileMD5(mainwads++, ASSET_HASH_SRB2_PK3); // srb2.pk3
-	W_VerifyFileMD5(mainwads++, ASSET_HASH_ZONES_DTA); // zones.dta
+	W_VerifyFileMD5(mainwads++, ASSET_HASH_ZONES_PK3); // zones.pk3
 	W_VerifyFileMD5(mainwads++, ASSET_HASH_PLAYER_DTA); // player.dta
 #ifdef USE_PATCH_DTA
-	W_VerifyFileMD5(mainwads++, ASSET_HASH_PATCH_DTA); // patch.dta
+	W_VerifyFileMD5(mainwads++, ASSET_HASH_PATCH_DTA); // patch.pk3
 #endif
 	// don't check music.dta because people like to modify it, and it doesn't matter if they do
 	// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
@@ -1161,7 +1161,7 @@ void D_SRB2Main(void)
 #else
 
 	mainwads++;	// srb2.pk3
-	mainwads++; // zones.dta
+	mainwads++; // zones.pk3
 	mainwads++; // player.dta
 #ifdef USE_PATCH_DTA
 	mainwads++; // patch.dta
@@ -1248,24 +1248,40 @@ void D_SRB2Main(void)
 		sound_disabled = true;
 		midi_disabled = digital_disabled = true;
 	}
-	else
+	if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic
 	{
-		CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
-	}
-	if (M_CheckParm("-nosound"))
 		sound_disabled = true;
-	if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
-		midi_disabled = digital_disabled = true;
+		digital_disabled = true;
+		midi_disabled = true;
+	}
 	else
 	{
-		if (M_CheckParm("-nomidimusic"))
-			midi_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
-		if (M_CheckParm("-nodigmusic"))
-			digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
+		if (M_CheckParm("-nosound"))
+			sound_disabled = true;
+		if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
+		{
+			digital_disabled = true;
+			midi_disabled = true;
+		}
+		else
+		{
+			if (M_CheckParm("-nomidimusic"))
+				midi_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
+			if (M_CheckParm("-nodigmusic"))
+				digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
+		}
+	}
+	if (!( sound_disabled && digital_disabled
+#ifndef NO_MIDI
+				&& midi_disabled
+#endif
+	 ))
+	{
+		CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
+		I_StartupSound();
+		I_InitMusic();
+		S_InitSfxChannels(cv_soundvolume.value);
 	}
-	I_StartupSound();
-	I_InitMusic();
-	S_InitSfxChannels(cv_soundvolume.value);
 
 	CONS_Printf("ST_Init(): Init status bar.\n");
 	ST_Init();
diff --git a/src/d_net.c b/src/d_net.c
index 9f68c187c472e7cf73175c0f1ed55dcaccdba31b..cbfef7726ab8e5e96077c4c09b900193eb07582c 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -185,22 +185,10 @@ typedef struct
 	UINT8 nextacknum;
 
 	UINT8 flags;
-#ifndef NEWPING
-	// jacobson tcp timeout evaluation algorithm (Karn variation)
-	fixed_t ping;
-	fixed_t varping;
-	INT32 timeout; // computed with ping and varping
-#endif
 } node_t;
 
 static node_t nodes[MAXNETNODES];
-#ifndef NEWPING
-#define PINGDEFAULT ((200*TICRATE*FRACUNIT)/1000)
-#define VARPINGDEFAULT ((50*TICRATE*FRACUNIT)/1000)
-#define TIMEOUT(p,v) (p+4*v+FRACUNIT/2)>>FRACBITS;
-#else
-#define NODETIMEOUT 14 //What the above boiled down to...
-#endif
+#define NODETIMEOUT 14
 
 #ifndef NONET
 // return <0 if a < b (mod 256)
@@ -320,19 +308,7 @@ static UINT8 GetAcktosend(INT32 node)
 static void RemoveAck(INT32 i)
 {
 	INT32 node = ackpak[i].destinationnode;
-#ifndef NEWPING
-	fixed_t trueping = (I_GetTime() - ackpak[i].senttime)<<FRACBITS;
-	if (ackpak[i].resentnum)
-	{
-		// +FRACUNIT/2 for round
-		nodes[node].ping = (nodes[node].ping*7 + trueping)/8;
-		nodes[node].varping = (nodes[node].varping*7 + abs(nodes[node].ping-trueping))/8;
-		nodes[node].timeout = TIMEOUT(nodes[node].ping,nodes[node].varping);
-	}
-	DEBFILE(va("Remove ack %d trueping %d ping %f var %f timeout %d\n",ackpak[i].acknum,trueping>>FRACBITS,(double)FIXED_TO_FLOAT(nodes[node].ping),(double)FIXED_TO_FLOAT(nodes[node].varping),nodes[node].timeout));
-#else
 	DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
-#endif
 	ackpak[i].acknum = 0;
 	if (nodes[node].flags & NF_CLOSE)
 		Net_CloseConnection(node);
@@ -519,11 +495,7 @@ void Net_AckTicker(void)
 	{
 		const INT32 nodei = ackpak[i].destinationnode;
 		node_t *node = &nodes[nodei];
-#ifdef NEWPING
 		if (ackpak[i].acknum && ackpak[i].senttime + NODETIMEOUT < I_GetTime())
-#else
-		if (ackpak[i].acknum && ackpak[i].senttime + node->timeout < I_GetTime())
-#endif
 		{
 			if (ackpak[i].resentnum > 10 && (node->flags & NF_CLOSE))
 			{
@@ -534,13 +506,8 @@ void Net_AckTicker(void)
 				ackpak[i].acknum = 0;
 				continue;
 			}
-#ifdef NEWPING
 			DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
 				NODETIMEOUT, I_GetTime()));
-#else
-			DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
-				node->timeout, I_GetTime()));
-#endif
 			M_Memcpy(netbuffer, ackpak[i].pak.raw, ackpak[i].length);
 			ackpak[i].senttime = I_GetTime();
 			ackpak[i].resentnum++;
@@ -658,11 +625,6 @@ void Net_WaitAllAckReceived(UINT32 timeout)
 static void InitNode(node_t *node)
 {
 	node->acktosend_head = node->acktosend_tail = 0;
-#ifndef NEWPING
-	node->ping = PINGDEFAULT;
-	node->varping = VARPINGDEFAULT;
-	node->timeout = TIMEOUT(node->ping, node->varping);
-#endif
 	node->firstacktosend = 0;
 	node->nextacknum = 1;
 	node->remotefirstack = 0;
@@ -843,9 +805,7 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"CLIENTJOIN",
 	"NODETIMEOUT",
 	"RESYNCHING",
-#ifdef NEWPING
 	"PING"
-#endif
 };
 
 static void DebugPrintpacket(const char *header)
@@ -1384,30 +1344,73 @@ boolean D_CheckNetGame(void)
 	return ret;
 }
 
+struct pingcell
+{
+	INT32 num;
+	INT32 ms;
+};
+
+static int pingcellcmp(const void *va, const void *vb)
+{
+	const struct pingcell *a, *b;
+	a = va;
+	b = vb;
+	return ( a->ms - b->ms );
+}
+
+/*
+New ping command formatted nicely to present ping in
+ascending order. And with equally spaced columns.
+The caller's ping is presented at the bottom too, for
+convenience.
+*/
+
 void Command_Ping_f(void)
 {
-#ifndef NEWPING
-	if(server)
+	struct pingcell pingv[MAXPLAYERS];
+	INT32           pingc;
+
+	int name_width = 0;
+	int   ms_width = 0;
+
+	int n;
+	INT32 i;
+
+	pingc = 0;
+	for (i = 1; i < MAXPLAYERS; ++i)
+		if (playeringame[i])
 	{
-#endif
-		INT32 i;
-		for (i = 0; i < MAXPLAYERS;i++)
-		{
-#ifndef NEWPING
-			const INT32 node = playernode[i];
-			if (playeringame[i] && node != 0)
-				CONS_Printf(M_GetText("%.2d : %s\n %d tics, %d ms.\n"), i, player_names[i],
-				GetLag(node), G_TicsToMilliseconds(GetLag(node)));
-#else
-			if (playeringame[i] && i != 0)
-				CONS_Printf(M_GetText("%.2d : %s\n %d ms\n"), i, player_names[i], playerpingtable[i]);
-#endif
-		}
-#ifndef NEWPING
+		n = strlen(player_names[i]);
+		if (n > name_width)
+			name_width = n;
+
+		n = playerpingtable[i];
+		if (n > ms_width)
+			ms_width = n;
+
+		pingv[pingc].num = i;
+		pingv[pingc].ms  = playerpingtable[i];
+		pingc++;
+	}
+
+	     if (ms_width < 10)  ms_width = 1;
+	else if (ms_width < 100) ms_width = 2;
+	else                     ms_width = 3;
+
+	qsort(pingv, pingc, sizeof (struct pingcell), &pingcellcmp);
+
+	for (i = 0; i < pingc; ++i)
+	{
+		CONS_Printf("%02d : %-*s %*d ms\n",
+				pingv[i].num,
+				name_width, player_names[pingv[i].num],
+				ms_width,   pingv[i].ms);
+	}
+
+	if (!server && playeringame[consoleplayer])
+	{
+		CONS_Printf("\nYour ping is %d ms\n", playerpingtable[consoleplayer]);
 	}
-	else
-		CONS_Printf(M_GetText("Only the server can use this.\n"));
-#endif
 }
 
 void D_CloseConnection(void)
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 0c09b34c89ef4c48b1462d40952131e880c1997a..7dda967c18bcf20572c4df74b5faf8168c327fab 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -74,6 +74,7 @@ static void Got_Clearscores(UINT8 **cp, INT32 playernum);
 static void PointLimit_OnChange(void);
 static void TimeLimit_OnChange(void);
 static void NumLaps_OnChange(void);
+static void BaseNumLaps_OnChange(void);
 static void Mute_OnChange(void);
 
 static void Hidetime_OnChange(void);
@@ -210,7 +211,7 @@ consvar_t cv_allowteamchange = {"allowteamchange", "Yes", CV_NETVAR, CV_YesNo, N
 
 consvar_t cv_startinglives = {"startinglives", "3", CV_NETVAR|CV_CHEAT, startingliveslimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t respawntime_cons_t[] = {{0, "MIN"}, {30, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t respawntime_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "Off"}, {0, NULL}};
 consvar_t cv_respawntime = {"respawndelay", "3", CV_NETVAR|CV_CHEAT, respawntime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_competitionboxes = {"competitionboxes", "Mystery", CV_NETVAR|CV_CHEAT, competitionboxes_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -315,16 +316,17 @@ consvar_t cv_timetic = {"timerres", "Classic", CV_SAVE, timetic_cons_t, NULL, 0,
 static CV_PossibleValue_t powerupdisplay_cons_t[] = {{0, "Never"}, {1, "First-person only"}, {2, "Always"}, {0, NULL}};
 consvar_t cv_powerupdisplay = {"powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t pointlimit_cons_t[] = {{0, "MIN"}, {999999990, "MAX"}, {0, NULL}};
-consvar_t cv_pointlimit = {"pointlimit", "0", CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t,
+static CV_PossibleValue_t pointlimit_cons_t[] = {{1, "MIN"}, {MAXSCORE, "MAX"}, {0, "None"}, {0, NULL}};
+consvar_t cv_pointlimit = {"pointlimit", "None", CV_NETVAR|CV_CALL|CV_NOINIT, pointlimit_cons_t,
 	PointLimit_OnChange, 0, NULL, NULL, 0, 0, NULL};
-static CV_PossibleValue_t timelimit_cons_t[] = {{0, "MIN"}, {30, "MAX"}, {0, NULL}};
-consvar_t cv_timelimit = {"timelimit", "0", CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t,
+static CV_PossibleValue_t timelimit_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "None"}, {0, NULL}};
+consvar_t cv_timelimit = {"timelimit", "None", CV_NETVAR|CV_CALL|CV_NOINIT, timelimit_cons_t,
 	TimeLimit_OnChange, 0, NULL, NULL, 0, 0, NULL};
-static CV_PossibleValue_t numlaps_cons_t[] = {{0, "MIN"}, {50, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t numlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, NULL}};
 consvar_t cv_numlaps = {"numlaps", "4", CV_NETVAR|CV_CALL|CV_NOINIT, numlaps_cons_t,
 	NumLaps_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usemapnumlaps = {"usemaplaps", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t basenumlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, "Map default"}, {0, NULL}};
+consvar_t cv_basenumlaps = {"basenumlaps", "Map default", CV_NETVAR|CV_CALL|CV_CHEAT, basenumlaps_cons_t, BaseNumLaps_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 // log elemental hazards -- not a netvar, is local to current player
 consvar_t cv_hazardlog = {"hazardlog", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -340,9 +342,7 @@ static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE
 consvar_t cv_nettimeout = {"nettimeout", "350", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t jointimeout_cons_t[] = {{5*TICRATE, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
 consvar_t cv_jointimeout = {"jointimeout", "350", CV_CALL|CV_SAVE, jointimeout_cons_t, JoinTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
-#ifdef NEWPING
 consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
-#endif
 // Intermission time Tails 04-19-2002
 static CV_PossibleValue_t inttime_cons_t[] = {{0, "MIN"}, {3600, "MAX"}, {0, NULL}};
 consvar_t cv_inttime = {"inttime", "10", CV_NETVAR, inttime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -363,7 +363,7 @@ consvar_t cv_runscripts = {"runscripts", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL
 consvar_t cv_pause = {"pausepermission", "Server", CV_NETVAR, pause_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_mute = {"mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_sleep = {"cpusleep", "-1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
+consvar_t cv_sleep = {"cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
 
 char timedemo_name[256];
 boolean timedemo_csv;
@@ -502,7 +502,7 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_friendlyfire);
 	CV_RegisterVar(&cv_pointlimit);
 	CV_RegisterVar(&cv_numlaps);
-	CV_RegisterVar(&cv_usemapnumlaps);
+	CV_RegisterVar(&cv_basenumlaps);
 
 	CV_RegisterVar(&cv_hazardlog);
 
@@ -578,9 +578,7 @@ void D_RegisterServerCommands(void)
 
 	CV_RegisterVar(&cv_skipmapcheck);
 	CV_RegisterVar(&cv_sleep);
-#ifdef NEWPING
 	CV_RegisterVar(&cv_maxping);
-#endif
 
 #ifdef SEENAMES
 	 CV_RegisterVar(&cv_allowseenames);
@@ -745,6 +743,8 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_chasefreelook);
 	CV_RegisterVar(&cv_chasefreelook2);
 	CV_RegisterVar(&cv_tutorialprompt);
+	CV_RegisterVar(&cv_showfocuslost);
+	CV_RegisterVar(&cv_pauseifunfocused);
 
 	// g_input.c
 	CV_RegisterVar(&cv_sideaxis);
@@ -884,7 +884,7 @@ void D_RegisterClientCommands(void)
   * \sa CleanupPlayerName, SetPlayerName, Got_NameAndColor
   * \author Graue <graue@oceanbase.org>
   */
-static boolean IsNameGood(char *name, INT32 playernum)
+boolean EnsurePlayerNameIsGood(char *name, INT32 playernum)
 {
 	INT32 ix;
 
@@ -925,14 +925,14 @@ static boolean IsNameGood(char *name, INT32 playernum)
 			if (len > 1)
 			{
 				name[len-1] = '\0';
-				if (!IsNameGood (name, playernum))
+				if (!EnsurePlayerNameIsGood (name, playernum))
 					return false;
 			}
 			else if (len == 1) // Agh!
 			{
 				// Last ditch effort...
 				sprintf(name, "%d", M_RandomKey(10));
-				if (!IsNameGood (name, playernum))
+				if (!EnsurePlayerNameIsGood (name, playernum))
 					return false;
 			}
 			else
@@ -1061,12 +1061,12 @@ static void CleanupPlayerName(INT32 playernum, const char *newname)
   * \param newname   New name for that player. Should be good, but won't
   *                  necessarily be if the client is maliciously modified or
   *                  buggy.
-  * \sa CleanupPlayerName, IsNameGood
+  * \sa CleanupPlayerName, EnsurePlayerNameIsGood
   * \author Graue <graue@oceanbase.org>
   */
 static void SetPlayerName(INT32 playernum, char *newname)
 {
-	if (IsNameGood(newname, playernum))
+	if (EnsurePlayerNameIsGood(newname, playernum))
 	{
 		if (strcasecmp(newname, player_names[playernum]) != 0)
 		{
@@ -1191,12 +1191,12 @@ static void SendNameAndColor(void)
 	&& !strcmp(cv_skin.string, skins[players[consoleplayer].skin].name))
 		return;
 
+	players[consoleplayer].availabilities = R_GetSkinAvailabilities();
+
 	// We'll handle it later if we're not playing.
 	if (!Playing())
 		return;
 
-	players[consoleplayer].availabilities = R_GetSkinAvailabilities();
-
 	// If you're not in a netgame, merely update the skin, color, and name.
 	if (!netgame)
 	{
@@ -1309,12 +1309,12 @@ static void SendNameAndColor2(void)
 			CV_StealthSet(&cv_playercolor2, cv_playercolor2.defaultvalue);
 	}
 
+	players[secondplaya].availabilities = R_GetSkinAvailabilities();
+
 	// We'll handle it later if we're not playing.
 	if (!Playing())
 		return;
 
-	players[secondplaya].availabilities = R_GetSkinAvailabilities();
-
 	// If you're not in a netgame, merely update the skin, color, and name.
 	if (botingame)
 	{
@@ -1708,7 +1708,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 		// Kick bot from special stages
 		if (botskin)
 		{
-			if (G_IsSpecialStage(mapnum))
+			if (G_IsSpecialStage(mapnum) || (mapheaderinfo[mapnum-1] && (mapheaderinfo[mapnum-1]->typeoflevel & TOL_NIGHTS)))
 			{
 				if (botingame)
 				{
@@ -2048,8 +2048,6 @@ static void Command_Suicide(void)
 	UINT8 buf[4];
 	UINT8 *cp = buf;
 
-	WRITEINT32(cp, consoleplayer);
-
 	if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
 	{
 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
@@ -2069,6 +2067,7 @@ static void Command_Suicide(void)
 		return;
 	}
 
+	WRITEINT32(cp, consoleplayer);
 	SendNetXCmd(XD_SUICIDE, &buf, 4);
 }
 
@@ -4481,3 +4480,14 @@ static void Command_ShowTime_f(void)
 
 	CONS_Printf(M_GetText("The current time is %f.\nThe timelimit is %f\n"), (double)leveltime/TICRATE, (double)timelimitintics/TICRATE);
 }
+
+static void BaseNumLaps_OnChange(void)
+{
+	if (gametype == GT_RACE)
+	{
+		if (cv_basenumlaps.value)
+			CONS_Printf(M_GetText("Number of laps will be changed to map defaults next round.\n"));
+		else
+			CONS_Printf(M_GetText("Number of laps will be changed to %d next round.\n"), cv_basenumlaps.value);
+	}
+}
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 2bacdfe3f2e9cfc10786ac64611cd91534e2dfc2..0b538890ec569dc8a2062889dc9842c502b89af3 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -66,7 +66,7 @@ extern consvar_t cv_friendlyfire;
 extern consvar_t cv_pointlimit;
 extern consvar_t cv_timelimit;
 extern consvar_t cv_numlaps;
-extern consvar_t cv_usemapnumlaps;
+extern consvar_t cv_basenumlaps;
 extern UINT32 timelimitintics;
 extern consvar_t cv_allowexitlevel;
 
@@ -107,9 +107,7 @@ extern consvar_t cv_ringslinger, cv_soundtest;
 
 extern consvar_t cv_specialrings, cv_powerstones, cv_matchboxes, cv_competitionboxes;
 
-#ifdef NEWPING
 extern consvar_t cv_maxping;
-#endif
 
 extern consvar_t cv_skipmapcheck;
 
@@ -195,6 +193,7 @@ typedef union {
 // add game commands, needs cleanup
 void D_RegisterServerCommands(void);
 void D_RegisterClientCommands(void);
+boolean EnsurePlayerNameIsGood(char *name, INT32 playernum);
 void D_SendPlayerConfig(void);
 void Command_ExitGame_f(void);
 void Command_Retry_f(void);
diff --git a/src/d_netfil.c b/src/d_netfil.c
index ed2d11138ad267f51353c29673f15783b4a97061..5e7f59310c84bca583253276bee4927a995cf377 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -752,11 +752,9 @@ void Got_Filetxpak(void)
 	nameonly(filename);
 
 	if (!(strcmp(filename, "srb2.pk3")
-		&& strcmp(filename, "srb2.srb")
-		&& strcmp(filename, "srb2.wad")
-		&& strcmp(filename, "zones.dta")
+		&& strcmp(filename, "zones.pk3")
 		&& strcmp(filename, "player.dta")
-		&& strcmp(filename, "patch.dta")
+		&& strcmp(filename, "patch.pk3")
 		&& strcmp(filename, "music.dta")
 		))
 		I_Error("Tried to download \"%s\"", filename);
diff --git a/src/d_player.h b/src/d_player.h
index 69080bd9dc230450241fb0ca668a53c32d54889c..f2fdda0500ad09e1efa88736985f087c6428e5a8 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -234,7 +234,9 @@ typedef enum
 	CR_ZOOMTUBE,
 	CR_ROPEHANG,
 	CR_MACESPIN,
-	CR_MINECART
+	CR_MINECART,
+	CR_ROLLOUT,
+	CR_PTERABYTE
 } carrytype_t; // pw_carry
 
 // Player powers. (don't edit this comment)
@@ -250,6 +252,8 @@ typedef enum
 	pw_spacetime, // In space, no one can hear you spin!
 	pw_extralife, // Extra Life timer
 	pw_pushing,
+	pw_justsprung,
+	pw_noautobrake,
 
 	pw_super, // Are you super?
 	pw_gravityboots, // gravity boots
@@ -509,6 +513,10 @@ typedef struct player_s
 #endif
 } player_t;
 
+// Values for dashmode
+#define DASHMODE_THRESHOLD (3*TICRATE)
+#define DASHMODE_MAX (DASHMODE_THRESHOLD + 3)
+
 // Value for infinite lives
 #define INFLIVES 0x7F
 
diff --git a/src/dehacked.c b/src/dehacked.c
index 4cdffd5edf99f45bae30be1d82b76e908982b24c..34ee1f17016e274a385e37c5cead8060bb2a13eb 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -313,7 +313,13 @@ static boolean findFreeSlot(INT32 *num)
 	if (*num >= MAXSKINS)
 		return false;
 
-	description[*num].picname[0] = '\0'; // Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
+	// Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
+	description[*num].picname[0] = '\0';
+	description[*num].nametag[0] = '\0';
+	description[*num].displayname[0] = '\0';
+	description[*num].oppositecolor = SKINCOLOR_NONE;
+	description[*num].tagtextcolor = SKINCOLOR_NONE;
+	description[*num].tagoutlinecolor = SKINCOLOR_NONE;
 
 	// Found one! ^_^
 	return (description[*num].used = true);
@@ -326,9 +332,16 @@ static void readPlayer(MYFILE *f, INT32 num)
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
 	char *word;
 	char *word2;
+	char *displayname = ZZ_Alloc(MAXLINELEN+1);
 	INT32 i;
 	boolean slotfound = false;
 
+	#define SLOTFOUND \
+		if (!slotfound && (slotfound = findFreeSlot(&num)) == false) \
+			goto done;
+
+	displayname[MAXLINELEN] = '\0';
+
 	do
 	{
 		if (myfgets(s, MAXLINELEN, f))
@@ -336,6 +349,17 @@ static void readPlayer(MYFILE *f, INT32 num)
 			if (s[0] == '\n')
 				break;
 
+			for (i = 0; i < MAXLINELEN-3; i++)
+			{
+				char *tmp;
+				if (s[i] == '=')
+				{
+					tmp = &s[i+2];
+					strncpy(displayname, tmp, SKINNAMESIZE);
+					break;
+				}
+			}
+
 			word = strtok(s, " ");
 			if (word)
 				strupr(word);
@@ -346,8 +370,7 @@ static void readPlayer(MYFILE *f, INT32 num)
 			{
 				char *playertext = NULL;
 
-				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
-					goto done;
+				SLOTFOUND
 
 				for (i = 0; i < MAXLINELEN-3; i++)
 				{
@@ -395,11 +418,54 @@ static void readPlayer(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "PICNAME"))
 			{
-				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
-					goto done;
-
+				SLOTFOUND
 				strncpy(description[num].picname, word2, 8);
 			}
+			// new character select
+			else if (fastcmp(word, "DISPLAYNAME"))
+			{
+				SLOTFOUND
+				// replace '#' with line breaks
+				// (also remove any '\n')
+				{
+					char *cur = NULL;
+
+					// remove '\n'
+					cur = strchr(displayname, '\n');
+					if (cur)
+						*cur = '\0';
+
+					// turn '#' into '\n'
+					cur = strchr(displayname, '#');
+					while (cur)
+					{
+						*cur = '\n';
+						cur = strchr(cur, '#');
+					}
+				}
+				// copy final string
+				strncpy(description[num].displayname, displayname, SKINNAMESIZE);
+			}
+			else if (fastcmp(word, "OPPOSITECOLOR") || fastcmp(word, "OPPOSITECOLOUR"))
+			{
+				SLOTFOUND
+				description[num].oppositecolor = (UINT8)get_number(word2);
+			}
+			else if (fastcmp(word, "NAMETAG") || fastcmp(word, "TAGNAME"))
+			{
+				SLOTFOUND
+				strncpy(description[num].nametag, word2, 8);
+			}
+			else if (fastcmp(word, "TAGTEXTCOLOR") || fastcmp(word, "TAGTEXTCOLOUR"))
+			{
+				SLOTFOUND
+				description[num].tagtextcolor = (UINT8)get_number(word2);
+			}
+			else if (fastcmp(word, "TAGOUTLINECOLOR") || fastcmp(word, "TAGOUTLINECOLOUR"))
+			{
+				SLOTFOUND
+				description[num].tagoutlinecolor = (UINT8)get_number(word2);
+			}
 			else if (fastcmp(word, "STATUS"))
 			{
 				/*
@@ -417,9 +483,7 @@ static void readPlayer(MYFILE *f, INT32 num)
 			else if (fastcmp(word, "SKINNAME"))
 			{
 				// Send to free slot.
-				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
-					goto done;
-
+				SLOTFOUND
 				strlcpy(description[num].skinname, word2, sizeof description[num].skinname);
 				strlwr(description[num].skinname);
 			}
@@ -427,8 +491,9 @@ static void readPlayer(MYFILE *f, INT32 num)
 				deh_warning("readPlayer %d: unknown word '%s'", num, word);
 		}
 	} while (!myfeof(f)); // finish when the line is empty
-
+	#undef SLOTFOUND
 done:
+	Z_Free(displayname);
 	Z_Free(s);
 }
 
@@ -2447,6 +2512,14 @@ static actionpointer_t actionpointers[] =
 	{{A_SaloonDoorSpawn},        "A_SALOONDOORSPAWN"},
 	{{A_MinecartSparkThink},     "A_MINECARTSPARKTHINK"},
 	{{A_ModuloToState},          "A_MODULOTOSTATE"},
+	{{A_LavafallRocks},          "A_LAVAFALLROCKS"},
+	{{A_LavafallLava},           "A_LAVAFALLLAVA"},
+	{{A_FallingLavaCheck},       "A_FALLINGLAVACHECK"},
+	{{A_FireShrink},             "A_FIRESHRINK"},
+	{{A_SpawnPterabytes},        "A_SPAWNPTERABYTES"},
+	{{A_PterabyteHover},         "A_PTERABYTEHOVER"},
+	{{A_RolloutSpawn},           "A_ROLLOUTSPAWN"},
+	{{A_RolloutRock},            "A_ROLLOUTROCK"},
 	{{NULL},                     "NONE"},
 
 	// This NULL entry must be the last in the list
@@ -4185,6 +4258,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// CA_GLIDEANDCLIMB
 	"S_PLAY_GLIDE",
+	"S_PLAY_GLIDE_LANDING",
 	"S_PLAY_CLING",
 	"S_PLAY_CLIMB",
 
@@ -4284,6 +4358,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_TAILSOVERLAY_GASP",
 	"S_TAILSOVERLAY_EDGE",
 
+	// [:
+	"S_JETFUMEFLASH",
+
 	// Blue Crawla
 	"S_POSS_STND",
 	"S_POSS_RUN1",
@@ -4432,6 +4509,21 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CRUSHCLAW_WAIT",
 	"S_CRUSHCHAIN",
 
+	// Banpyura
+	"S_BANPYURA_ROAM1",
+	"S_BANPYURA_ROAM2",
+	"S_BANPYURA_ROAM3",
+	"S_BANPYURA_ROAM4",
+	"S_BANPYURA_ROAMPAUSE",
+	"S_CDIAG1",
+	"S_CDIAG2",
+	"S_CDIAG3",
+	"S_CDIAG4",
+	"S_CDIAG5",
+	"S_CDIAG6",
+	"S_CDIAG7",
+	"S_CDIAG8",
+
 	// Jet Jaw
 	"S_JETJAW_ROAM1",
 	"S_JETJAW_ROAM2",
@@ -4618,6 +4710,22 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_CANARIVOREGAS_7",
 	"S_CANARIVOREGAS_8",
 
+	// Pyre Fly
+	"S_PYREFLY_FLY",
+	"S_PYREFLY_BURN",
+	"S_PYREFIRE1",
+	"S_PYREFIRE2",
+
+	// Pterabyte
+	"S_PTERABYTESPAWNER",
+	"S_PTERABYTEWAYPOINT",
+	"S_PTERABYTE_FLY1",
+	"S_PTERABYTE_FLY2",
+	"S_PTERABYTE_FLY3",
+	"S_PTERABYTE_FLY4",
+	"S_PTERABYTE_SWOOPDOWN",
+	"S_PTERABYTE_SWOOPUP",
+
 	// Boss Explosion
 	"S_BOSSEXPLODE",
 
@@ -5861,7 +5969,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Saloon door
 	"S_SALOONDOOR",
-	"S_SALOONDOORTHINKER",
+	"S_SALOONDOORCENTER",
 
 	// Train cameo
 	"S_TRAINCAMEOSPAWNER_1",
@@ -5882,6 +5990,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLAMEJETFLAME1",
 	"S_FLAMEJETFLAME2",
 	"S_FLAMEJETFLAME3",
+	"S_FLAMEJETFLAME4",
+	"S_FLAMEJETFLAME5",
+	"S_FLAMEJETFLAME6",
+	"S_FLAMEJETFLAME7",
+	"S_FLAMEJETFLAME8",
+	"S_FLAMEJETFLAME9",
 
 	// Spinning flame jets
 	"S_FJSPINAXISA1", // Counter-clockwise
@@ -5894,6 +6008,28 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_FLAMEJETFLAMEB2",
 	"S_FLAMEJETFLAMEB3",
 
+	// Lavafall
+	"S_LAVAFALL_DORMANT",
+	"S_LAVAFALL_TELL",
+	"S_LAVAFALL_SHOOT",
+	"S_LAVAFALL_LAVA1",
+	"S_LAVAFALL_LAVA2",
+	"S_LAVAFALL_LAVA3",
+	"S_LAVAFALLROCK",
+
+	// Rollout Rock
+	"S_ROLLOUTSPAWN",
+	"S_ROLLOUTROCK",
+
+	// RVZ scenery
+	"S_BIGFERNLEAF",
+	"S_BIGFERN1",
+	"S_BIGFERN2",
+	"S_JUNGLEPALM",
+	"S_TORCHFLOWER",
+	"S_WALLVINE_LONG",
+	"S_WALLVINE_SHORT",
+
 	// Trapgoyles
 	"S_TRAPGOYLE",
 	"S_TRAPGOYLE_CHECK",
@@ -5925,6 +6061,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_TARGET_RESPAWN",
 	"S_TARGET_ALLDONE",
 
+	// ATZ's green flame
+	"S_GREENFLAME",
+
 	// Stalagmites
 	"S_STG0",
 	"S_STG1",
@@ -5945,6 +6084,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_LAMPPOST1",  // normal
 	"S_LAMPPOST2",  // with snow
 	"S_HANGSTAR",
+	"S_MISTLETOE",
 	// Xmas GFZ bushes
 	"S_XMASBLUEBERRYBUSH",
 	"S_XMASBERRYBUSH",
@@ -5952,6 +6092,16 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	// FHZ
 	"S_FHZICE1",
 	"S_FHZICE2",
+	"S_ROSY_IDLE1",
+	"S_ROSY_IDLE2",
+	"S_ROSY_IDLE3",
+	"S_ROSY_IDLE4",
+	"S_ROSY_JUMP",
+	"S_ROSY_WALK",
+	"S_ROSY_HUG",
+	"S_ROSY_PAIN",
+	"S_ROSY_STND",
+	"S_ROSY_UNHAPPY",
 
 	// Halloween Scenery
 	// Pumpkins
@@ -6603,6 +6753,16 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BHORIZ7",
 	"S_BHORIZ8",
 
+	"S_BOOSTERSOUND",
+	"S_YELLOWBOOSTERROLLER",
+	"S_YELLOWBOOSTERSEG_LEFT",
+	"S_YELLOWBOOSTERSEG_RIGHT",
+	"S_YELLOWBOOSTERSEG_FACE",
+	"S_REDBOOSTERROLLER",
+	"S_REDBOOSTERSEG_LEFT",
+	"S_REDBOOSTERSEG_RIGHT",
+	"S_REDBOOSTERSEG_FACE",
+
 	// Rain
 	"S_RAIN1",
 	"S_RAINRETURN",
@@ -6623,6 +6783,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SPLISH8",
 	"S_SPLISH9",
 
+	// Lava splish
+	"S_LAVASPLISH",
+
 	// added water splash
 	"S_SPLASH1",
 	"S_SPLASH2",
@@ -7234,6 +7397,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_THOK", // Thok! mobj
 	"MT_PLAYER",
 	"MT_TAILSOVERLAY", // c:
+	"MT_METALJETFUME", // [:
 
 	// Enemies
 	"MT_BLUECRAWLA", // Crawla (Blue)
@@ -7252,6 +7416,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_CRUSHSTACEAN", // Crushstacean
 	"MT_CRUSHCLAW", // Big meaty claw
 	"MT_CRUSHCHAIN", // Chain
+	"MT_BANPYURA", // Banpyura
+	"MT_BANPSPRING", // Banpyura spring
 	"MT_JETJAW", // Jet Jaw
 	"MT_SNAILER", // Snailer
 	"MT_VULTURE", // BASH
@@ -7273,6 +7439,11 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_UNIBALL", // Unidus Ball
 	"MT_CANARIVORE", // Canarivore
 	"MT_CANARIVORE_GAS", // Canarivore gas
+	"MT_PYREFLY", // Pyre Fly
+	"MT_PYREFLY_FIRE", // Pyre Fly fire
+	"MT_PTERABYTESPAWNER", // Pterabyte spawner
+	"MT_PTERABYTEWAYPOINT", // Pterabyte waypoint
+	"MT_PTERABYTE", // Pterabyte
 
 	// Generic Boss Items
 	"MT_BOSSEXPLODE",
@@ -7384,6 +7555,11 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_REDHORIZ",
 	"MT_BLUEHORIZ",
 
+	"MT_BOOSTERSEG",
+	"MT_BOOSTERROLLER",
+	"MT_YELLOWBOOSTER",
+	"MT_REDBOOSTER",
+
 	// Interactive Objects
 	"MT_BUBBLES", // Bubble source
 	"MT_SIGN", // Level end sign
@@ -7601,7 +7777,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_MINECARTSIDEMARK",
 	"MT_MINECARTSPARK",
 	"MT_SALOONDOOR",
-	"MT_SALOONDOORTHINKER",
+	"MT_SALOONDOORCENTER",
 	"MT_TRAINCAMEOSPAWNER",
 	"MT_TRAINSEG",
 	"MT_TRAINDUSTSPAWNER",
@@ -7618,6 +7794,20 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 
 	"MT_FLAMEJETFLAMEB", // Blade's flame
 
+	"MT_LAVAFALL",
+	"MT_LAVAFALL_LAVA",
+	"MT_LAVAFALLROCK",
+
+	"MT_ROLLOUTSPAWN",
+	"MT_ROLLOUTROCK",
+
+	"MT_BIGFERNLEAF",
+	"MT_BIGFERN",
+	"MT_JUNGLEPALM",
+	"MT_TORCHFLOWER",
+	"MT_WALLVINE_LONG",
+	"MT_WALLVINE_SHORT",
+
 	// Dark City Scenery
 
 	// Egg Rock Scenery
@@ -7628,6 +7818,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_TRAPGOYLEDOWN",
 	"MT_TRAPGOYLELONG",
 	"MT_TARGET",
+	"MT_GREENFLAME",
 
 	// Stalagmites
 	"MT_STALAGMITE0",
@@ -7649,6 +7840,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_LAMPPOST1",  // normal
 	"MT_LAMPPOST2",  // with snow
 	"MT_HANGSTAR",
+	"MT_MISTLETOE",
 	// Xmas GFZ bushes
 	"MT_XMASBLUEBERRYBUSH",
 	"MT_XMASBERRYBUSH",
@@ -7656,6 +7848,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	// FHZ
 	"MT_FHZICE1",
 	"MT_FHZICE2",
+	"MT_ROSY",
+	"MT_CDLHRT",
 
 	// Halloween Scenery
 	// Pumpkins
@@ -7784,6 +7978,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_RAIN", // Rain
 	"MT_SNOWFLAKE", // Snowflake
 	"MT_SPLISH", // Water splish!
+	"MT_LAVASPLISH", // Lava splish!
 	"MT_SMOKE",
 	"MT_SMALLBUBBLE", // small bubble
 	"MT_MEDIUMBUBBLE", // medium bubble
@@ -8269,6 +8464,8 @@ static const char *const POWERS_LIST[] = {
 	"SPACETIME", // In space, no one can hear you spin!
 	"EXTRALIFE", // Extra Life timer
 	"PUSHING",
+	"JUSTSPRUNG",
+	"NOAUTOBRAKE",
 
 	"SUPER", // Are you super?
 	"GRAVITYBOOTS", // gravity boots
@@ -8609,6 +8806,8 @@ struct {
 	{"CR_ROPEHANG",CR_ROPEHANG},
 	{"CR_MACESPIN",CR_MACESPIN},
 	{"CR_MINECART",CR_MINECART},
+	{"CR_ROLLOUT", CR_ROLLOUT},
+	{"CR_PTERABYTE",CR_PTERABYTE},
 
 	// Ring weapons (ringweapons_t)
 	// Useful for A_GiveWeapon
@@ -8830,9 +9029,9 @@ struct {
 	{"FF_PLATFORM",FF_PLATFORM},               ///< You can jump up through this to the top.
 	{"FF_REVERSEPLATFORM",FF_REVERSEPLATFORM}, ///< A fall-through floor in normal gravity, a platform in reverse gravity.
 	{"FF_INTANGABLEFLATS",FF_INTANGABLEFLATS}, ///< Both flats are intangable, but the sides are still solid.
-	{"FF_SHATTER",FF_SHATTER},                 ///< Used with ::FF_BUSTUP. Thinks everyone's Knuckles.
-	{"FF_SPINBUST",FF_SPINBUST},               ///< Used with ::FF_BUSTUP. Jump or fall onto it while curled in a ball.
-	{"FF_ONLYKNUX",FF_ONLYKNUX},               ///< Used with ::FF_BUSTUP. Only Knuckles can break this rock.
+	{"FF_SHATTER",FF_SHATTER},                 ///< Used with ::FF_BUSTUP. Bustable on mere touch.
+	{"FF_SPINBUST",FF_SPINBUST},               ///< Used with ::FF_BUSTUP. Also bustable if you're in your spinning frames.
+	{"FF_STRONGBUST",FF_STRONGBUST },          ///< Used with ::FF_BUSTUP. Only bustable by "strong" characters (Knuckles) and abilities (bouncing, twinspin, melee).
 	{"FF_RIPPLE",FF_RIPPLE},                   ///< Ripple the flats
 	{"FF_COLORMAPONLY",FF_COLORMAPONLY},       ///< Only copy the colormap, not the lightlevel
 	{"FF_GOOWATER",FF_GOOWATER},               ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
@@ -8963,6 +9162,7 @@ struct {
 	{"V_OFFSET",V_OFFSET},
 	{"V_ALLOWLOWERCASE",V_ALLOWLOWERCASE},
 	{"V_FLIP",V_FLIP},
+	{"V_CENTERNAMETAG",V_CENTERNAMETAG},
 	{"V_SNAPTOTOP",V_SNAPTOTOP},
 	{"V_SNAPTOBOTTOM",V_SNAPTOBOTTOM},
 	{"V_SNAPTOLEFT",V_SNAPTOLEFT},
@@ -8996,6 +9196,7 @@ struct {
 	{"TC_ALLWHITE",TC_ALLWHITE},
 	{"TC_RAINBOW",TC_RAINBOW},
 	{"TC_BLINK",TC_BLINK},
+	{"TC_DASHMODE",TC_DASHMODE},
 #endif
 
 	{NULL,0}
@@ -9982,6 +10183,23 @@ static inline int lib_getenum(lua_State *L)
 	} else if (fastcmp(word,"mapmusposition")) {
 		lua_pushinteger(L, mapmusposition);
 		return 1;
+	// local player variables, by popular request
+	} else if (fastcmp(word,"consoleplayer")) { // player controlling console (aka local player 1)
+		if (consoleplayer < 0 || !playeringame[consoleplayer])
+			return 0;
+		LUA_PushUserdata(L, &players[consoleplayer], META_PLAYER);
+		return 1;
+	} else if (fastcmp(word,"displayplayer")) { // player visible on screen (aka display player 1)
+		if (displayplayer < 0 || !playeringame[displayplayer])
+			return 0;
+		LUA_PushUserdata(L, &players[displayplayer], META_PLAYER);
+		return 1;
+	} else if (fastcmp(word,"secondarydisplayplayer")) { // local/display player 2, for splitscreen
+		if (!splitscreen || secondarydisplayplayer < 0 || !playeringame[secondarydisplayplayer])
+			return 0;
+		LUA_PushUserdata(L, &players[secondarydisplayplayer], META_PLAYER);
+		return 1;
+	// end local player variables
 	} else if (fastcmp(word,"server")) {
 		if ((!multiplayer || !netgame) && !playeringame[serverplayer])
 			return 0;
diff --git a/src/doomdef.h b/src/doomdef.h
index 8d44d0896758316e60eb93afe845fe249d505477..676c86e0d68b8c844c6edf042804f3a68589b048 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -509,13 +509,17 @@ INT32 I_GetKey(void);
 // Max gamepad/joysticks that can be detected/used.
 #define MAX_JOYSTICKS 4
 
+#ifndef M_PIl
+#define M_PIl 3.1415926535897932384626433832795029L
+#endif
+
 // Floating point comparison epsilons from float.h
 #ifndef FLT_EPSILON
 #define FLT_EPSILON 1.1920928955078125e-7f
 #endif
 
 #ifndef DBL_EPSILON
-#define DBL_EPSILON 2.2204460492503131e-16
+#define DBL_EPSILON 2.2204460492503131e-16l
 #endif
 
 // An assert-type mechanism.
@@ -561,9 +565,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 ///	Polyobject fake flat code
 #define POLYOBJECTS_PLANES
 
-///	Improved way of dealing with ping values and a ping limit.
-#define NEWPING
-
 ///	See name of player in your crosshair
 #define SEENAMES
 
diff --git a/src/doomstat.h b/src/doomstat.h
index 57e37f72dc98dfbcbecdb225292e6bcc10c71db7..7d06f03e24445c868d385f6dc83002fbcb542ab2 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -327,7 +327,7 @@ typedef struct
 	// Music stuff.
 	UINT32 musinterfadeout;  ///< Fade out level music on intermission screen in milliseconds
 	char musintername[7];    ///< Intermission screen music.
-	
+
 	char muspostbossname[7];    ///< Post-bossdeath music.
 	UINT16 muspostbosstrack;    ///< Post-bossdeath track.
 	UINT32 muspostbosspos;      ///< Post-bossdeath position
@@ -433,6 +433,7 @@ typedef struct
 	tic_t time;   ///< Time in which the level was finished.
 	UINT32 score; ///< Score when the level was finished.
 	UINT16 rings; ///< Rings when the level was finished.
+	boolean gotperfect; ///< Got perfect bonus?
 } recorddata_t;
 
 /** Setup for one NiGHTS map.
diff --git a/src/f_finale.c b/src/f_finale.c
index ee22ecb2f6f8b3afe399bc0887c6649821ab4b6b..eb1415042bc2a10efa6f0798db80b53a93db101d 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -75,6 +75,7 @@ INT32 curbgcolor;
 INT32 curbgxspeed;
 INT32 curbgyspeed;
 boolean curbghide;
+boolean hidetitlemap;		// WARNING: set to false by M_SetupNextMenu and M_ClearMenus
 
 static UINT8  curDemo = 0;
 static UINT32 demoDelayLeft;
@@ -637,6 +638,7 @@ static void F_IntroDrawScene(void)
 		}
 		else
 		{
+			menuanimtimer = animtimer; // Reusing this variable for the intro to fix the scrolling sky, better than changing the function around.
 			F_SkyScroll(80*4, 0, "TITLESKY");
 			if (timetonext == 6)
 			{
@@ -1584,15 +1586,15 @@ void F_StartEnding(void)
 		UINT8 skinnum = players[consoleplayer].skin;
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
-		if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 7)
+		if (skins[skinnum].sprites[SPR2_XTRA].numframes >= (XTRA_ENDING+2)+1)
 		{
 			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
 			// character head, skin specific
-			sprframe = &sprdef->spriteframes[4];
+			sprframe = &sprdef->spriteframes[XTRA_ENDING];
 			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
-			sprframe = &sprdef->spriteframes[5];
+			sprframe = &sprdef->spriteframes[XTRA_ENDING+1];
 			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
-			sprframe = &sprdef->spriteframes[6];
+			sprframe = &sprdef->spriteframes[XTRA_ENDING+2];
 			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
 		}
 		else // Show a star if your character doesn't have an ending firework display. (Basically the MISSINGs for this)
@@ -2097,12 +2099,12 @@ void F_InitMenuPresValues(void)
 	curfadevalue = 16;
 	curhidepics = hidetitlepics;
 	curbgcolor = -1;
-	curbgxspeed = titlescrollxspeed;
-	curbgyspeed = titlescrollyspeed;
-	curbghide = true;
+	curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
+	curbgyspeed = (gamestate == GS_TIMEATTACK) ? 22 : titlescrollyspeed;
+	curbghide = (gamestate == GS_TIMEATTACK) ? false : true;
 
 	// Find current presentation values
-	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "SRB2BACK" : "TITLESKY");
+	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "RECATTBG" : "TITLESKY");
 	M_SetMenuCurFadeValue(16);
 	M_SetMenuCurHideTitlePics();
 }
diff --git a/src/f_finale.h b/src/f_finale.h
index d640abc8a4896590a406d79a5505050cd20cd29f..58c492c3d9578725a59df4049b78dc027810a2c6 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -94,6 +94,7 @@ extern INT32 curbgcolor;
 extern INT32 curbgxspeed;
 extern INT32 curbgyspeed;
 extern boolean curbghide;
+extern boolean hidetitlemap;
 
 #define TITLEBACKGROUNDACTIVE (curfadevalue >= 0 || curbgname[0])
 
diff --git a/src/g_game.c b/src/g_game.c
index e6fcec7b0118fd20242c473adb77444f448fe399..4fa2431b8e91467c16fe7a216e6e40f5eccd2161 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -361,6 +361,8 @@ consvar_t cv_chatbacktint = {"chatbacktint", "On", CV_SAVE, CV_OnOff, NULL, 0, N
 static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Window"}, {1, "Console"}, {2, "Window (Hidden)"}, {0, NULL}};
 consvar_t cv_consolechat = {"chatmode", "Window", CV_SAVE, consolechat_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
+// Pause game upon window losing focus
+consvar_t cv_pauseifunfocused = {"pauseifunfocused", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_crosshair = {"crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_crosshair2 = {"crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -966,8 +968,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 			forcefullinput = true;
 	if (twodlevel
 		|| (player->mo && (player->mo->flags2 & MF2_TWOD))
-		|| (!demoplayback && (player->climbing
-		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
+		|| (!demoplayback && ((player->powers[pw_carry] == CR_NIGHTSMODE)
 		|| (player->pflags & (PF_SLIDING|PF_FORCESTRAFE))))) // Analog
 			forcestrafe = true;
 	if (forcestrafe)
@@ -1148,8 +1149,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	if (!mouseaiming && cv_mousemove.value)
 		forward += mousey;
 
-	if ((!demoplayback && (player->climbing
-		|| (player->pflags & PF_SLIDING)))) // Analog for mouse
+	if ((!demoplayback && (player->pflags & PF_SLIDING))) // Analog for mouse
 		side += mousex*2;
 	else if (cv_analog.value)
 	{
@@ -1713,65 +1713,6 @@ static INT32 camtoggledelay, camtoggledelay2 = 0;
 //
 boolean G_Responder(event_t *ev)
 {
-	// allow spy mode changes even during the demo
-	if (gamestate == GS_LEVEL && ev->type == ev_keydown
-		&& (ev->data1 == KEY_F12 || ev->data1 == gamecontrol[gc_viewpoint][0] || ev->data1 == gamecontrol[gc_viewpoint][1]))
-	{
-		if (splitscreen || !netgame)
-			displayplayer = consoleplayer;
-		else
-		{
-			// spy mode
-			do
-			{
-				displayplayer++;
-				if (displayplayer == MAXPLAYERS)
-					displayplayer = 0;
-
-				if (!playeringame[displayplayer])
-					continue;
-
-				if (players[displayplayer].spectator)
-					continue;
-
-				if (G_GametypeHasTeams())
-				{
-					if (players[consoleplayer].ctfteam
-					 && players[displayplayer].ctfteam != players[consoleplayer].ctfteam)
-						continue;
-				}
-				else if (gametype == GT_HIDEANDSEEK)
-				{
-					if (players[consoleplayer].pflags & PF_TAGIT)
-						continue;
-				}
-				// Other Tag-based gametypes?
-				else if (G_TagGametype())
-				{
-					if (!players[consoleplayer].spectator
-					 && (players[consoleplayer].pflags & PF_TAGIT) != (players[displayplayer].pflags & PF_TAGIT))
-						continue;
-				}
-				else if (G_GametypeHasSpectators() && G_RingSlingerGametype())
-				{
-					if (!players[consoleplayer].spectator)
-						continue;
-				}
-
-				break;
-			} while (displayplayer != consoleplayer);
-
-			// change statusbar also if playing back demo
-			if (singledemo)
-				ST_changeDemoView();
-
-			// tell who's the view
-			CONS_Printf(M_GetText("Viewpoint: %s\n"), player_names[displayplayer]);
-
-			return true;
-		}
-	}
-
 	// any other key pops up menu if in demos
 	if (gameaction == ga_nothing && !singledemo &&
 		((demoplayback && !modeattacking && !titledemo) || gamestate == GS_TITLESCREEN))
@@ -1848,6 +1789,65 @@ boolean G_Responder(event_t *ev)
 		if (HU_Responder(ev))
 			return true; // chat ate the event
 
+	// allow spy mode changes even during the demo
+	if (gamestate == GS_LEVEL && ev->type == ev_keydown
+		&& (ev->data1 == KEY_F12 || ev->data1 == gamecontrol[gc_viewpoint][0] || ev->data1 == gamecontrol[gc_viewpoint][1]))
+	{
+		if (splitscreen || !netgame)
+			displayplayer = consoleplayer;
+		else
+		{
+			// spy mode
+			do
+			{
+				displayplayer++;
+				if (displayplayer == MAXPLAYERS)
+					displayplayer = 0;
+
+				if (!playeringame[displayplayer])
+					continue;
+
+				if (players[displayplayer].spectator)
+					continue;
+
+				if (G_GametypeHasTeams())
+				{
+					if (players[consoleplayer].ctfteam
+					 && players[displayplayer].ctfteam != players[consoleplayer].ctfteam)
+						continue;
+				}
+				else if (gametype == GT_HIDEANDSEEK)
+				{
+					if (players[consoleplayer].pflags & PF_TAGIT)
+						continue;
+				}
+				// Other Tag-based gametypes?
+				else if (G_TagGametype())
+				{
+					if (!players[consoleplayer].spectator
+					 && (players[consoleplayer].pflags & PF_TAGIT) != (players[displayplayer].pflags & PF_TAGIT))
+						continue;
+				}
+				else if (G_GametypeHasSpectators() && G_RingSlingerGametype())
+				{
+					if (!players[consoleplayer].spectator)
+						continue;
+				}
+
+				break;
+			} while (displayplayer != consoleplayer);
+
+			// change statusbar also if playing back demo
+			if (singledemo)
+				ST_changeDemoView();
+
+			// tell who's the view
+			CONS_Printf(M_GetText("Viewpoint: %s\n"), player_names[displayplayer]);
+
+			return true;
+		}
+	}
+
 	// update keys current state
 	G_MapEventsToControls(ev);
 
@@ -3341,6 +3341,7 @@ void G_LoadGameData(void)
 	UINT32 recscore;
 	tic_t  rectime;
 	UINT16 recrings;
+	boolean gotperf;
 
 	UINT8 recmares;
 	INT32 curmare;
@@ -3433,6 +3434,7 @@ void G_LoadGameData(void)
 		recscore = READUINT32(save_p);
 		rectime  = (tic_t)READUINT32(save_p);
 		recrings = READUINT16(save_p);
+		gotperf = (boolean)READUINT8(save_p);
 
 		if (recrings > 10000 || recscore > MAXSCORE)
 			goto datacorrupt;
@@ -3444,6 +3446,9 @@ void G_LoadGameData(void)
 			mainrecords[i]->time = rectime;
 			mainrecords[i]->rings = recrings;
 		}
+
+		if (gotperf)
+			mainrecords[i]->gotperfect = gotperf;
 	}
 
 	// Nights records
@@ -3575,12 +3580,14 @@ void G_SaveGameData(void)
 			WRITEUINT32(save_p, mainrecords[i]->score);
 			WRITEUINT32(save_p, mainrecords[i]->time);
 			WRITEUINT16(save_p, mainrecords[i]->rings);
+			WRITEUINT8(save_p, mainrecords[i]->gotperfect);
 		}
 		else
 		{
 			WRITEUINT32(save_p, 0);
 			WRITEUINT32(save_p, 0);
 			WRITEUINT16(save_p, 0);
+			WRITEUINT8(save_p, 0);
 		}
 	}
 
diff --git a/src/g_game.h b/src/g_game.h
index 1bd566f59424eea2bb5c0878aee56ca89a4069ab..595714432749a14d9c77af39f6e2981bc4a95f2b 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -60,6 +60,8 @@ extern boolean pausebreakkey;
 
 extern boolean promptactive;
 
+extern consvar_t cv_pauseifunfocused;
+
 // used in game menu
 extern consvar_t cv_tutorialprompt;
 extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_compactscoreboard;
diff --git a/src/g_input.c b/src/g_input.c
index 45c517e1a634740caf8a25accf5facc93df35227..afefbcc3b7629ea03ad57e58522d9a39f41c219b 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -709,8 +709,8 @@ void G_DefineDefaultControls(void)
 
 	for (i = 1; i < num_gamecontrolschemes; i++) // skip gcs_custom (0)
 	{
-		gamecontroldefault[i][gc_weaponnext ][0] = 'e';
-		gamecontroldefault[i][gc_weaponprev ][0] = 'q';
+		gamecontroldefault[i][gc_weaponnext ][0] = KEY_MOUSEWHEELUP+0;
+		gamecontroldefault[i][gc_weaponprev ][0] = KEY_MOUSEWHEELDOWN+0;
 		gamecontroldefault[i][gc_wepslot1   ][0] = '1';
 		gamecontroldefault[i][gc_wepslot2   ][0] = '2';
 		gamecontroldefault[i][gc_wepslot3   ][0] = '3';
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 48393249214ff7dc7dd13051d88f5b50c2478a44..9e454bcd5275dd28150e33f053fc5ee20c79ddb0 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -201,7 +201,7 @@ static polyvertex_t *fracdivline(fdivline_t *bsp, polyvertex_t *v1,
 	// (do not accept hit with the extensions)
 	num = (v2x - v1x)*v2dy + (v1y - v2y)*v2dx;
 	frac = num / den;
-	if (frac < 0.0 || frac > 1.0)
+	if (frac < 0.0l || frac > 1.0l)
 		return NULL;
 
 	// now get the frac along the BSP line
diff --git a/src/hardware/hw_dll.h b/src/hardware/hw_dll.h
index 452e9037c2dab8f14bf1798113a22b87f3f95049..3fa5852d88d844060f6a3601e148d1a540461493 100644
--- a/src/hardware/hw_dll.h
+++ b/src/hardware/hw_dll.h
@@ -61,9 +61,6 @@ typedef void (*I_Error_t) (const char *error, ...) FUNCIERROR;
 // ==========================================================================
 
 // Constants
-#ifndef M_PIl
-#define M_PIl 3.1415926535897932384626433832795029L
-#endif
 #define DEGREE (0.017453292519943295769236907684883l) // 2*PI/360
 
 void DBG_Printf(const char *lpFmt, ...) /*FUNCPRINTF*/;
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 54bd9e78648f467abb1dcccb270d44a15f3613dd..aed1611f111176ba0ab9c907362e154421894825 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -47,6 +47,7 @@ EXPORT void HWRAPI(SetPalette) (RGBA_t *ppal, RGBA_t *pgamma);
 EXPORT void HWRAPI(FinishUpdate) (INT32 waitvbl);
 EXPORT void HWRAPI(Draw2DLine) (F2DCoord *v1, F2DCoord *v2, RGBA_t Color);
 EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPts, FBITFIELD PolyFlags);
+EXPORT void HWRAPI(RenderSkyDome) (INT32 tex, INT32 texture_width, INT32 texture_height, FTransform transform);
 EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags);
 EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask, FBOOLEAN DepthMask, FRGBAFloat *ClearColor);
 EXPORT void HWRAPI(SetTexture) (FTextureInfo *TexInfo);
@@ -87,6 +88,7 @@ struct hwdriver_s
 	FinishUpdate        pfnFinishUpdate;
 	Draw2DLine          pfnDraw2DLine;
 	DrawPolygon         pfnDrawPolygon;
+	RenderSkyDome       pfnRenderSkyDome;
 	SetBlend            pfnSetBlend;
 	ClearBuffer         pfnClearBuffer;
 	SetTexture          pfnSetTexture;
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index b613fdae1d641de30d762d0e0e7f1d8dc38d7ce9..fa66536b6592edd1687ba80e066bb6603f405780 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -162,6 +162,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_TURR
 	&lspr[NOLIGHT],     // SPR_SHRP
 	&lspr[NOLIGHT],     // SPR_CRAB
+	&lspr[NOLIGHT],     // SPR_CR2B
+	&lspr[NOLIGHT],     // SPR_CSPR
 	&lspr[NOLIGHT],     // SPR_JJAW
 	&lspr[NOLIGHT],     // SPR_SNLR
 	&lspr[NOLIGHT],     // SPR_VLTR
@@ -180,6 +182,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_UNID
 	&lspr[NOLIGHT],     // SPR_CANA
 	&lspr[NOLIGHT],     // SPR_CANG
+	&lspr[NOLIGHT],     // SPR_PYRE
+	&lspr[NOLIGHT],     // SPR_PTER
 
 	// Generic Boos Items
 	&lspr[JETLIGHT_L],     // SPR_JETF // Boss jet fumes
@@ -197,7 +201,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_EGGO
 	&lspr[NOLIGHT],     // SPR_SEBH
 	&lspr[NOLIGHT],     // SPR_FAKE
-	&lspr[NOLIGHT],     // SPR_SHCK
+	&lspr[LBLUESHINE_L],// SPR_SHCK
 
 	// Boss 4 (Castle Eggman)
 	&lspr[NOLIGHT],     // SPR_EGGP
@@ -260,6 +264,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_WSPB
 	&lspr[NOLIGHT],     // SPR_STPT
 	&lspr[NOLIGHT],     // SPR_BMNE
+	&lspr[NOLIGHT],     // SPR_PUMI
 
 	// Monitor Boxes
 	&lspr[NOLIGHT],     // SPR_MSTV
@@ -377,6 +382,10 @@ light_t *t_lspr[NUMSPRITES] =
 	// Red Volcano Scenery
 	&lspr[REDBALL_L],   // SPR_FLME
 	&lspr[REDBALL_L],   // SPR_DFLM
+	&lspr[NOLIGHT],     // SPR_LFAL
+	&lspr[NOLIGHT],     // SPR_JPLA
+	&lspr[NOLIGHT],     // SPR_TFLO
+	&lspr[NOLIGHT],     // SPR_WVIN
 
 	// Dark City Scenery
 
@@ -388,13 +397,20 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_XMS3
 	&lspr[NOLIGHT],     // SPR_XMS4
 	&lspr[NOLIGHT],     // SPR_XMS5
+	&lspr[NOLIGHT],     // SPR_XMS6
 	&lspr[NOLIGHT],     // SPR_FHZI
+	&lspr[NOLIGHT],     // SPR_ROSY
 
 	// Halloween Scenery
 	&lspr[RINGLIGHT_L], // SPR_PUMK
 	&lspr[NOLIGHT],     // SPR_HHPL
 	&lspr[NOLIGHT],     // SPR_SHRM
 	&lspr[NOLIGHT],     // SPR_HHZM
+	
+	// Azure Temple Scenery
+	&lspr[NOLIGHT],     // SPR_BGAR
+	&lspr[NOLIGHT],     // SPR_RCRY
+	&lspr[GREENBALL_L], // SPR_CFLM
 
 	// Botanic Serenity Scenery
 	&lspr[NOLIGHT],     // SPR_BSZ1
@@ -415,7 +431,6 @@ light_t *t_lspr[NUMSPRITES] =
 	// Misc Scenery
 	&lspr[NOLIGHT],     // SPR_STLG
 	&lspr[NOLIGHT],     // SPR_DBAL
-	&lspr[NOLIGHT],     // SPR_RCRY
 
 	// Powerup Indicators
 	&lspr[NOLIGHT],     // SPR_ARMA
@@ -469,11 +484,14 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_SSWY
 	&lspr[NOLIGHT],     // SPR_SSWR
 	&lspr[NOLIGHT],     // SPR_SSWB
+	&lspr[NOLIGHT],     // SPR_BSTY
+	&lspr[NOLIGHT],     // SPR_BSTR
 
 	// Environmental Effects
 	&lspr[NOLIGHT],     // SPR_RAIN
 	&lspr[NOLIGHT],     // SPR_SNO1
 	&lspr[NOLIGHT],     // SPR_SPLH
+	&lspr[NOLIGHT],     // SPR_LSPL
 	&lspr[NOLIGHT],     // SPR_SPLA
 	&lspr[NOLIGHT],     // SPR_SMOK
 	&lspr[NOLIGHT],     // SPR_BUBL
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index c08860ffa5e96c47c8f3f391d5e3ee59d489d1b5..7e400b7dd41d3330b9c2d07dad9d68908340b390 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5454,7 +5454,7 @@ static void HWR_AddSprites(sector_t *sec)
 #ifdef HWPRECIP
 	precipmobj_t *precipthing;
 #endif
-	fixed_t approx_dist, limit_dist;
+	fixed_t approx_dist, limit_dist, hoop_limit_dist;
 
 	// BSP is traversed by subsector.
 	// A sector might have been split into several
@@ -5471,7 +5471,9 @@ static void HWR_AddSprites(sector_t *sec)
 
 	// Handle all things in sector.
 	// If a limit exists, handle things a tiny bit different.
-	if ((limit_dist = (fixed_t)((maptol & TOL_NIGHTS) ? cv_drawdist_nights.value : cv_drawdist.value) << FRACBITS))
+	limit_dist = (fixed_t)(cv_drawdist.value) << FRACBITS;
+	hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
+	if (limit_dist || hoop_limit_dist)
 	{
 		for (thing = sec->thinglist; thing; thing = thing->snext)
 		{
@@ -5480,8 +5482,16 @@ static void HWR_AddSprites(sector_t *sec)
 
 			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
 
-			if (approx_dist > limit_dist)
-				continue;
+			if (thing->sprite == SPR_HOOP)
+			{
+				if (hoop_limit_dist && approx_dist > hoop_limit_dist)
+					continue;
+			}
+			else
+			{
+				if (limit_dist && approx_dist > limit_dist)
+					continue;
+			}
 
 			HWR_ProjectSprite(thing);
 		}
@@ -5711,6 +5721,13 @@ static void HWR_ProjectSprite(mobj_t *thing)
 			return;
 	}
 
+	if ((thing->flags2 & MF2_LINKDRAW) && thing->tracer)
+	{
+		// bodge support - not nearly as comprehensive as r_things.c, but better than nothing
+		if (thing->tracer->sprite == SPR_NULL || thing->tracer->flags2 & MF2_DONTDRAW)
+			return;
+	}
+
 	// store information in a vissprite
 	vis = HWR_NewVisSprite();
 	vis->x1 = x1;
@@ -5724,7 +5741,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->z2 = z2;
 
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
-	if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
+	if ((vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
 	{
 		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
 			vis->colormap = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
@@ -5869,86 +5886,122 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 // ==========================================================================
 //
 // ==========================================================================
-static void HWR_DrawSkyBackground(void)
+static void HWR_DrawSkyBackground(player_t *player)
 {
-	FOutVector v[4];
-	angle_t angle;
-	float dimensionmultiply;
-	float aspectratio;
-	float angleturn;
+	if (cv_grskydome.value)
+	{
+		FTransform transform;
+		const float fpov = FIXED_TO_FLOAT(cv_grfov.value+player->fovadd);
+		postimg_t *type;
 
-	HWR_GetTexture(texturetranslation[skytexture]);
-	aspectratio = (float)vid.width/(float)vid.height;
+		if (splitscreen && player == &players[secondarydisplayplayer])
+			type = &postimgtype2;
+		else
+			type = &postimgtype;
 
-	//Hurdler: the sky is the only texture who need 4.0f instead of 1.0
-	//         because it's called just after clearing the screen
-	//         and thus, the near clipping plane is set to 3.99
-	// Sryder: Just use the near clipping plane value then
+		memset(&transform, 0x00, sizeof(FTransform));
 
-	//  3--2
-	//  | /|
-	//  |/ |
-	//  0--1
-	v[0].x = v[3].x = -ZCLIP_PLANE-1;
-	v[1].x = v[2].x =  ZCLIP_PLANE+1;
-	v[0].y = v[1].y = -ZCLIP_PLANE-1;
-	v[2].y = v[3].y =  ZCLIP_PLANE+1;
+		//04/01/2000: Hurdler: added for T&L
+		//                     It should replace all other gr_viewxxx when finished
+		transform.anglex = (float)(aimingangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
+		transform.angley = (float)((viewangle-ANGLE_270)>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 
-	v[0].z = v[1].z = v[2].z = v[3].z = ZCLIP_PLANE+1;
+		if (*type == postimg_flip)
+			transform.flip = true;
+		else
+			transform.flip = false;
 
-	// X
+		transform.scalex = 1;
+		transform.scaley = (float)vid.width/vid.height;
+		transform.scalez = 1;
+		transform.fovxangle = fpov; // Tails
+		transform.fovyangle = fpov; // Tails
+		transform.splitscreen = splitscreen;
 
-	// NOTE: This doesn't work right with texture widths greater than 1024
-	// software doesn't draw any further than 1024 for skies anyway, but this doesn't overlap properly
-	// The only time this will probably be an issue is when a sky wider than 1024 is used as a sky AND a regular wall texture
+		HWR_GetTexture(texturetranslation[skytexture]);
+		HWD.pfnRenderSkyDome(skytexture, textures[skytexture]->width, textures[skytexture]->height, transform);
+	}
+	else
+	{
+		FOutVector v[4];
+		angle_t angle;
+		float dimensionmultiply;
+		float aspectratio;
+		float angleturn;
 
-	angle = (dup_viewangle + gr_xtoviewangle[0]);
+		HWR_GetTexture(texturetranslation[skytexture]);
+		aspectratio = (float)vid.width/(float)vid.height;
 
-	dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->width/256.0f);
+		//Hurdler: the sky is the only texture who need 4.0f instead of 1.0
+		//         because it's called just after clearing the screen
+		//         and thus, the near clipping plane is set to 3.99
+		// Sryder: Just use the near clipping plane value then
 
-	v[0].sow = v[3].sow = (-1.0f * angle) / ((ANGLE_90-1)*dimensionmultiply); // left
-	v[2].sow = v[1].sow = v[0].sow + (1.0f/dimensionmultiply); // right (or left + 1.0f)
-	// use +angle and -1.0f above instead if you wanted old backwards behavior
+		//  3--2
+		//  | /|
+		//  |/ |
+		//  0--1
+		v[0].x = v[3].x = -ZCLIP_PLANE-1;
+		v[1].x = v[2].x =  ZCLIP_PLANE+1;
+		v[0].y = v[1].y = -ZCLIP_PLANE-1;
+		v[2].y = v[3].y =  ZCLIP_PLANE+1;
 
-	// Y
-	angle = aimingangle;
-	dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->height/(128.0f*aspectratio));
+		v[0].z = v[1].z = v[2].z = v[3].z = ZCLIP_PLANE+1;
 
-	if (splitscreen)
-	{
-		dimensionmultiply *= 2;
-		angle *= 2;
-	}
+		// X
 
-	// Middle of the sky should always be at angle 0
-	// need to keep correct aspect ratio with X
-	if (atransform.flip)
-	{
-		// During vertical flip the sky should be flipped and it's y movement should also be flipped obviously
-		v[3].tow = v[2].tow = -(0.5f-(0.5f/dimensionmultiply)); // top
-		v[0].tow = v[1].tow = v[3].tow - (1.0f/dimensionmultiply); // bottom (or top - 1.0f)
-	}
-	else
-	{
-		v[0].tow = v[1].tow = -(0.5f-(0.5f/dimensionmultiply)); // bottom
-		v[3].tow = v[2].tow = v[0].tow - (1.0f/dimensionmultiply); // top (or bottom - 1.0f)
-	}
+		// NOTE: This doesn't work right with texture widths greater than 1024
+		// software doesn't draw any further than 1024 for skies anyway, but this doesn't overlap properly
+		// The only time this will probably be an issue is when a sky wider than 1024 is used as a sky AND a regular wall texture
 
-	angleturn = (((float)ANGLE_45-1.0f)*aspectratio)*dimensionmultiply;
+		angle = (dup_viewangle + gr_xtoviewangle[0]);
 
-	if (angle > ANGLE_180) // Do this because we don't want the sky to suddenly teleport when crossing over 0 to 360 and vice versa
-	{
-		angle = InvAngle(angle);
-		v[3].tow = v[2].tow += ((float) angle / angleturn);
-		v[0].tow = v[1].tow += ((float) angle / angleturn);
-	}
-	else
-	{
-		v[3].tow = v[2].tow -= ((float) angle / angleturn);
-		v[0].tow = v[1].tow -= ((float) angle / angleturn);
-	}
+		dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->width/256.0f);
+
+		v[0].sow = v[3].sow = (-1.0f * angle) / ((ANGLE_90-1)*dimensionmultiply); // left
+		v[2].sow = v[1].sow = v[0].sow + (1.0f/dimensionmultiply); // right (or left + 1.0f)
+		// use +angle and -1.0f above instead if you wanted old backwards behavior
 
-	HWD.pfnDrawPolygon(NULL, v, 4, 0);
+		// Y
+		angle = aimingangle;
+		dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->height/(128.0f*aspectratio));
+
+		if (splitscreen)
+		{
+			dimensionmultiply *= 2;
+			angle *= 2;
+		}
+
+		// Middle of the sky should always be at angle 0
+		// need to keep correct aspect ratio with X
+		if (atransform.flip)
+		{
+			// During vertical flip the sky should be flipped and it's y movement should also be flipped obviously
+			v[3].tow = v[2].tow = -(0.5f-(0.5f/dimensionmultiply)); // top
+			v[0].tow = v[1].tow = v[3].tow - (1.0f/dimensionmultiply); // bottom (or top - 1.0f)
+		}
+		else
+		{
+			v[0].tow = v[1].tow = -(0.5f-(0.5f/dimensionmultiply)); // bottom
+			v[3].tow = v[2].tow = v[0].tow - (1.0f/dimensionmultiply); // top (or bottom - 1.0f)
+		}
+
+		angleturn = (((float)ANGLE_45-1.0f)*aspectratio)*dimensionmultiply;
+
+		if (angle > ANGLE_180) // Do this because we don't want the sky to suddenly teleport when crossing over 0 to 360 and vice versa
+		{
+			angle = InvAngle(angle);
+			v[3].tow = v[2].tow += ((float) angle / angleturn);
+			v[0].tow = v[1].tow += ((float) angle / angleturn);
+		}
+		else
+		{
+			v[3].tow = v[2].tow -= ((float) angle / angleturn);
+			v[0].tow = v[1].tow -= ((float) angle / angleturn);
+		}
+
+		HWD.pfnDrawPolygon(NULL, v, 4, 0);
+	}
 }
 
 
@@ -6100,7 +6153,7 @@ if (0)
 }
 
 	if (drawsky)
-		HWR_DrawSkyBackground();
+		HWR_DrawSkyBackground(player);
 
 	//Hurdler: it doesn't work in splitscreen mode
 	drawsky = splitscreen;
@@ -6318,7 +6371,7 @@ if (0)
 }
 
 	if (!skybox && drawsky) // Don't draw the regular sky if there's a skybox
-		HWR_DrawSkyBackground();
+		HWR_DrawSkyBackground(player);
 
 	//Hurdler: it doesn't work in splitscreen mode
 	drawsky = splitscreen;
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 67d022ec62c1577a5c86632df9faa5968e2affc1..5abf10f42034d8fe9540b49453a3a70f89e57b6e 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -96,6 +96,7 @@ extern consvar_t cv_grcorrecttricks;
 extern consvar_t cv_grfovchange;
 extern consvar_t cv_grsolvetjoin;
 extern consvar_t cv_grspritebillboarding;
+extern consvar_t cv_grskydome;
 
 extern float gr_viewwidth, gr_viewheight, gr_baseviewwindowy;
 
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 675f84e4aa7f4002d259ef82a7c27912d556b5ac..94abef06dda34ee1e58c71030602e6cdc44d37e4 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -925,6 +925,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 
 	// MD2 colormap fix
 	// colormap test
+	if (spr->mobj->subsector)
 	{
 		sector_t *sector = spr->mobj->subsector->sector;
 		UINT8 lightlevel = 255;
@@ -956,6 +957,8 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 		else
 			Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, false);
 	}
+	else
+		Surf.FlatColor.rgba = 0xFFFFFFFF;
 
 	// Look at HWR_ProjectSprite for more
 	{
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 415e6648f081238369fef42fd99b7f0988be9e3d..d4c9ed5d8a0b5362f0dad2dacf9a4d8e81957fd8 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -1390,6 +1390,219 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 		Clamp2D(GL_TEXTURE_WRAP_T);
 }
 
+typedef struct vbo_vertex_s
+{
+	float x, y, z;
+	float u, v;
+	unsigned char r, g, b, a;
+} vbo_vertex_t;
+
+typedef struct
+{
+	int mode;
+	int vertexcount;
+	int vertexindex;
+	int use_texture;
+} GLSkyLoopDef;
+
+typedef struct
+{
+	int id;
+	int rows, columns;
+	int loopcount;
+	GLSkyLoopDef *loops;
+	vbo_vertex_t *data;
+} GLSkyVBO;
+
+// The texture offset to be applied to the texture coordinates in SkyVertex().
+static int rows, columns;
+static boolean yflip;
+static int texw, texh;
+static boolean foglayer;
+static float delta = 0.0f;
+
+static int gl_sky_detail = 16;
+
+static INT32 lasttex = -1;
+
+#define MAP_COEFF 128.0f
+
+static void SkyVertex(vbo_vertex_t *vbo, int r, int c)
+{
+	const float radians = (M_PIl / 180.0f);
+	const float scale = 10000.0f;
+	const float maxSideAngle = 60.0f;
+
+	float topAngle = (c / (float)columns * 360.0f);
+	float sideAngle = (maxSideAngle * (rows - r) / rows);
+	float height = sin(sideAngle * radians);
+	float realRadius = scale * cos(sideAngle * radians);
+	float x = realRadius * cos(topAngle * radians);
+	float y = (!yflip) ? scale * height : -scale * height;
+	float z = realRadius * sin(topAngle * radians);
+	float timesRepeat = (4 * (256.0f / texw));
+	if (fpclassify(timesRepeat) == FP_ZERO)
+		timesRepeat = 1.0f;
+
+	if (!foglayer)
+	{
+		vbo->r = 255;
+		vbo->g = 255;
+		vbo->b = 255;
+		vbo->a = (r == 0 ? 0 : 255);
+
+		// And the texture coordinates.
+		vbo->u = (-timesRepeat * c / (float)columns);
+		if (!yflip)	// Flipped Y is for the lower hemisphere.
+			vbo->v = (r / (float)rows) + 0.5f;
+		else
+			vbo->v = 1.0f + ((rows - r) / (float)rows) + 0.5f;
+	}
+
+	if (r != 4)
+	{
+		y += 300.0f;
+	}
+
+	// And finally the vertex.
+	vbo->x = x;
+	vbo->y = y + delta;
+	vbo->z = z;
+}
+
+static GLSkyVBO sky_vbo;
+
+static void gld_BuildSky(int row_count, int col_count)
+{
+	int c, r;
+	vbo_vertex_t *vertex_p;
+	int vertex_count = 2 * row_count * (col_count * 2 + 2) + col_count * 2;
+
+	GLSkyVBO *vbo = &sky_vbo;
+
+	if ((vbo->columns != col_count) || (vbo->rows != row_count))
+	{
+		free(vbo->loops);
+		free(vbo->data);
+		memset(vbo, 0, sizeof(&vbo));
+	}
+
+	if (!vbo->data)
+	{
+		memset(vbo, 0, sizeof(&vbo));
+		vbo->loops = malloc((row_count * 2 + 2) * sizeof(vbo->loops[0]));
+		// create vertex array
+		vbo->data = malloc(vertex_count * sizeof(vbo->data[0]));
+	}
+
+	vbo->columns = col_count;
+	vbo->rows = row_count;
+
+	vertex_p = &vbo->data[0];
+	vbo->loopcount = 0;
+
+	for (yflip = 0; yflip < 2; yflip++)
+	{
+		vbo->loops[vbo->loopcount].mode = GL_TRIANGLE_FAN;
+		vbo->loops[vbo->loopcount].vertexindex = vertex_p - &vbo->data[0];
+		vbo->loops[vbo->loopcount].vertexcount = col_count;
+		vbo->loops[vbo->loopcount].use_texture = false;
+		vbo->loopcount++;
+
+		delta = 0.0f;
+		foglayer = true;
+		for (c = 0; c < col_count; c++)
+		{
+			SkyVertex(vertex_p, 1, c);
+			vertex_p->r = 255;
+			vertex_p->g = 255;
+			vertex_p->b = 255;
+			vertex_p->a = 255;
+			vertex_p++;
+		}
+		foglayer = false;
+
+		delta = (yflip ? 5.0f : -5.0f) / MAP_COEFF;
+
+		for (r = 0; r < row_count; r++)
+		{
+			vbo->loops[vbo->loopcount].mode = GL_TRIANGLE_STRIP;
+			vbo->loops[vbo->loopcount].vertexindex = vertex_p - &vbo->data[0];
+			vbo->loops[vbo->loopcount].vertexcount = 2 * col_count + 2;
+			vbo->loops[vbo->loopcount].use_texture = true;
+			vbo->loopcount++;
+
+			for (c = 0; c <= col_count; c++)
+			{
+				SkyVertex(vertex_p++, r + (yflip ? 1 : 0), (c ? c : 0));
+				SkyVertex(vertex_p++, r + (yflip ? 0 : 1), (c ? c : 0));
+			}
+		}
+	}
+}
+
+//-----------------------------------------------------------------------------
+//
+//
+//
+//-----------------------------------------------------------------------------
+
+static void RenderDome(INT32 skytexture)
+{
+	int i, j;
+	GLSkyVBO *vbo = &sky_vbo;
+
+	pglRotatef(270.0f, 0.0f, 1.0f, 0.0f);
+
+	rows = 4;
+	columns = 4 * gl_sky_detail;
+
+	if (lasttex != skytexture)
+	{
+		lasttex = skytexture;
+		gld_BuildSky(rows, columns);
+	}
+
+	pglScalef(1.0f, (float)texh / 230.0f, 1.0f);
+
+	for (j = 0; j < 2; j++)
+	{
+		for (i = 0; i < vbo->loopcount; i++)
+		{
+			GLSkyLoopDef *loop = &vbo->loops[i];
+
+			if (j == 0 ? loop->use_texture : !loop->use_texture)
+				continue;
+			else
+			{
+				int k;
+				pglBegin(loop->mode);
+				for (k = loop->vertexindex; k < (loop->vertexindex + loop->vertexcount); k++)
+				{
+					vbo_vertex_t *v = &vbo->data[k];
+					if (loop->use_texture)
+						pglTexCoord2f(v->u, v->v);
+					pglColor4f(v->r, v->g, v->b, v->a);
+					pglVertex3f(v->x, v->y, v->z);
+				}
+				pglEnd();
+			}
+		}
+	}
+
+	pglScalef(1.0f, 1.0f, 1.0f);
+	pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+}
+
+EXPORT void HWRAPI(RenderSkyDome) (INT32 tex, INT32 texture_width, INT32 texture_height, FTransform transform)
+{
+	SetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated);
+	SetTransform(&transform);
+	texw = texture_width;
+	texh = texture_height;
+	RenderDome(tex);
+	SetBlend(0);
+}
 
 // ==========================================================================
 //
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 3bc643c3c2537f691e9545ceccfeec370c9a2539..428656bf2a396b6bc707e6677eecc09bf3e497eb 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -71,6 +71,10 @@ patch_t *lt_font[LT_FONTSIZE];
 patch_t *cred_font[CRED_FONTSIZE];
 patch_t *ttlnum[20]; // act numbers (0-19)
 
+// Name tag fonts
+patch_t *ntb_font[NT_FONTSIZE];
+patch_t *nto_font[NT_FONTSIZE];
+
 static player_t *plr;
 boolean chat_on; // entering a chat message?
 static char w_chat[HU_MAXMSGLEN];
@@ -246,6 +250,32 @@ void HU_LoadGraphics(void)
 		ttlnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
 	}
 
+	// cache the base name tag font for entire game execution
+	j = NT_FONTSTART;
+	for (i = 0; i < NT_FONTSIZE; i++)
+	{
+		sprintf(buffer, "NTFNT%.3d", j);
+		j++;
+
+		if (W_CheckNumForName(buffer) == LUMPERROR)
+			ntb_font[i] = NULL;
+		else
+			ntb_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
+	// cache the outline name tag font for entire game execution
+	j = NT_FONTSTART;
+	for (i = 0; i < NT_FONTSIZE; i++)
+	{
+		sprintf(buffer, "NTFNO%.3d", j);
+		j++;
+
+		if (W_CheckNumForName(buffer) == LUMPERROR)
+			nto_font[i] = NULL;
+		else
+			nto_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
+	}
+
 	// cache the crosshairs, don't bother to know which one is being used,
 	// just cache all 3, they're so small anyway.
 	for (i = 0; i < HU_CROSSHAIRS; i++)
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index ab77e67b6f9a36cd3f390344a0337f9a5e666c4c..55b61d4b7a995d6ffb4f9fd85958ed0d25d2ba66 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -35,6 +35,12 @@
 #define CRED_FONTEND 'Z' // the last font character
 #define CRED_FONTSIZE (CRED_FONTEND - CRED_FONTSTART + 1)
 
+// Name tag font
+// Used by base and outline font set
+#define NT_FONTSTART '!' // the first font character
+#define NT_FONTEND 'Z' // the last font character
+#define NT_FONTSIZE (NT_FONTEND - NT_FONTSTART + 1)
+
 #define HU_CROSSHAIRS 3 // maximum of 9 - see HU_Init();
 
 extern char *shiftxform; // english translation shift table
@@ -77,6 +83,8 @@ extern patch_t *tallnum[10];
 extern patch_t *nightsnum[10];
 extern patch_t *lt_font[LT_FONTSIZE];
 extern patch_t *cred_font[CRED_FONTSIZE];
+extern patch_t *ntb_font[NT_FONTSIZE];
+extern patch_t *nto_font[NT_FONTSIZE];
 extern patch_t *ttlnum[20];
 extern patch_t *emeraldpics[3][8];
 extern patch_t *rflagico;
diff --git a/src/i_tcp.c b/src/i_tcp.c
index f2b4336dc313faba1f0c3bebe84e31e89fb923ab..da92f27673fa4edc872b2056bfdbc64bea9d07cf 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -776,6 +776,8 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 #endif
 #endif
 	mysockaddr_t straddr;
+	struct sockaddr_in sin;
+	socklen_t len = sizeof(sin);
 
 	if (s == (SOCKET_TYPE)ERRSOCKET)
 		return (SOCKET_TYPE)ERRSOCKET;
@@ -869,12 +871,16 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 			CONS_Printf(M_GetText("Network system buffer set to: %dKb\n"), opt>>10);
 	}
 
+	if (getsockname(s, (struct sockaddr *)&sin, &len) == -1)
+		CONS_Alert(CONS_WARNING, M_GetText("Failed to get port number\n"));
+	else
+		current_port = (UINT16)ntohs(sin.sin_port);
+
 	return s;
 }
 
 static boolean UDP_Socket(void)
 {
-	const char *sock_port = NULL;
 	size_t s;
 	struct my_addrinfo *ai, *runp, hints;
 	int gaie;
@@ -896,20 +902,11 @@ static boolean UDP_Socket(void)
 	hints.ai_socktype = SOCK_DGRAM;
 	hints.ai_protocol = IPPROTO_UDP;
 
-	if (M_CheckParm("-clientport"))
-	{
-		if (!M_IsNextParm())
-			I_Error("syntax: -clientport <portnum>");
-		sock_port = M_GetNextParm();
-	}
-	else
-		sock_port = port_name;
-
 	if (M_CheckParm("-bindaddr"))
 	{
 		while (M_IsNextParm())
 		{
-			gaie = I_getaddrinfo(M_GetNextParm(), sock_port, &hints, &ai);
+			gaie = I_getaddrinfo(M_GetNextParm(), port_name, &hints, &ai);
 			if (gaie == 0)
 			{
 				runp = ai;
@@ -930,7 +927,7 @@ static boolean UDP_Socket(void)
 	}
 	else
 	{
-		gaie = I_getaddrinfo("0.0.0.0", sock_port, &hints, &ai);
+		gaie = I_getaddrinfo("0.0.0.0", port_name, &hints, &ai);
 		if (gaie == 0)
 		{
 			runp = ai;
@@ -945,8 +942,8 @@ static boolean UDP_Socket(void)
 #ifdef HAVE_MINIUPNPC
 					if (UPNP_support)
 					{
-						I_UPnP_rem(sock_port, "UDP");
-						I_UPnP_add(NULL, sock_port, "UDP");
+						I_UPnP_rem(port_name, "UDP");
+						I_UPnP_add(NULL, port_name, "UDP");
 					}
 #endif
 				}
@@ -963,7 +960,7 @@ static boolean UDP_Socket(void)
 		{
 			while (M_IsNextParm())
 			{
-				gaie = I_getaddrinfo(M_GetNextParm(), sock_port, &hints, &ai);
+				gaie = I_getaddrinfo(M_GetNextParm(), port_name, &hints, &ai);
 				if (gaie == 0)
 				{
 					runp = ai;
@@ -984,7 +981,7 @@ static boolean UDP_Socket(void)
 		}
 		else
 		{
-			gaie = I_getaddrinfo("::", sock_port, &hints, &ai);
+			gaie = I_getaddrinfo("::", port_name, &hints, &ai);
 			if (gaie == 0)
 			{
 				runp = ai;
@@ -1260,7 +1257,7 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 	int gaie;
 
 	 if (!port || !port[0])
-		port = port_name;
+		port = DEFAULTPORT;
 
 	DEBFILE(va("Creating new node: %s@%s\n", address, port));
 
@@ -1424,14 +1421,15 @@ boolean I_InitTcpNetwork(void)
 	if (!I_InitTcpDriver())
 		return false;
 
-	if (M_CheckParm("-udpport"))
+	if (M_CheckParm("-port"))
+	// Combined -udpport and -clientport into -port
+	// As it was really redundant having two seperate parms that does the same thing
 	{
 		if (M_IsNextParm())
 			strcpy(port_name, M_GetNextParm());
 		else
 			strcpy(port_name, "0");
 	}
-	current_port = (UINT16)atoi(port_name);
 
 	// parse network game options,
 	if (M_CheckParm("-server") || dedicated)
diff --git a/src/info.c b/src/info.c
index 5fb4d3070247e4cce5ead0eb8c90ad4661c0cc4e..cfaad552dcf4213dcab1201fe3d7c7c0880a1a99 100644
--- a/src/info.c
+++ b/src/info.c
@@ -50,6 +50,8 @@ char sprnames[NUMSPRITES + 1][5] =
 	"TURR", // Pop-Up Turret
 	"SHRP", // Sharp
 	"CRAB", // Crushstacean
+	"CR2B", // Banpyura
+	"CSPR", // Banpyura spring
 	"JJAW", // Jet Jaw
 	"SNLR", // Snailer
 	"VLTR", // BASH
@@ -68,6 +70,8 @@ char sprnames[NUMSPRITES + 1][5] =
 	"UNID", // Unidus
 	"CANA", // Canarivore
 	"CANG", // Canarivore gas
+	"PYRE", // Pyre Fly
+	"PTER", // Pterabyte
 
 	// Generic Boss Items
 	"JETF", // Boss jet fumes
@@ -149,6 +153,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"WSPB", // Wall spike base
 	"STPT", // Starpost
 	"BMNE", // Big floating mine
+	"PUMI", // Rollout Rock
 
 	// Monitor Boxes
 	"MSTV", // MiSc TV sprites
@@ -263,7 +268,6 @@ char sprnames[NUMSPRITES + 1][5] =
 	"ADST", // Arid dust
 	"MCRT", // Minecart
 	"MCSP", // Minecart spark
-	"NON2", // Saloon door thinker
 	"SALD", // Saloon door
 	"TRAE", // Train cameo locomotive
 	"TRAI", // Train cameo wagon
@@ -272,6 +276,10 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Red Volcano Scenery
 	"FLME", // Flame jet
 	"DFLM", // Blade's flame
+	"LFAL", // Lavafall
+	"JPLA", // Jungle palm
+	"TFLO", // Torch flower
+	"WVIN", // Wall vines
 
 	// Dark City Scenery
 
@@ -283,7 +291,9 @@ char sprnames[NUMSPRITES + 1][5] =
 	"XMS3", // Snowman
 	"XMS4", // Lamppost
 	"XMS5", // Hanging Star
+	"XMS6", // Mistletoe
 	"FHZI", // FHZ ice
+	"ROSY",
 
 	// Halloween Scenery
 	"PUMK", // Pumpkins
@@ -291,6 +301,11 @@ char sprnames[NUMSPRITES + 1][5] =
 	"SHRM", // Mushroom
 	"HHZM", // Misc
 
+	// Azure Temple Scenery
+	"BGAR", // ATZ Gargoyles
+	"RCRY", // ATZ Red Crystal (Target)
+	"CFLM", // Green torch flame
+
 	// Botanic Serenity Scenery
 	"BSZ1", // Tall flowers
 	"BSZ2", // Medium flowers
@@ -310,7 +325,6 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Misc Scenery
 	"STLG", // Stalagmites
 	"DBAL", // Disco
-	"RCRY", // ATZ Red Crystal (Target)
 
 	// Powerup Indicators
 	"ARMA", // Armageddon Shield Orb
@@ -364,11 +378,14 @@ char sprnames[NUMSPRITES + 1][5] =
 	"SSWY", // Yellow Side Spring
 	"SSWR", // Red Side Spring
 	"SSWB", // Blue Side Spring
+	"BSTY", // Yellow Booster
+	"BSTR", // Red Booster
 
 	// Environmental Effects
 	"RAIN", // Rain
 	"SNO1", // Snowflake
 	"SPLH", // Water Splish
+	"LSPL", // Lava Splish
 	"SPLA", // Water Splash
 	"SMOK",
 	"BUBL", // Bubble
@@ -516,6 +533,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"TIRE",
 
 	"GLID",
+	"LAND",
 	"CLNG",
 	"CLMB",
 
@@ -523,7 +541,6 @@ char spr2names[NUMPLAYERSPRITES][5] =
 	"FRUN",
 
 	"BNCE",
-	"BLND",
 
 	"FIRE",
 
@@ -619,6 +636,7 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	0, // SPR2_TIRE, (conditional, will never be referenced)
 
 	SPR2_FLY , // SPR2_GLID,
+	SPR2_ROLL, // SPR2_LAND,
 	SPR2_CLMB, // SPR2_CLNG,
 	SPR2_ROLL, // SPR2_CLMB,
 
@@ -626,7 +644,6 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = {
 	SPR2_RUN , // SPR2_FRUN,
 
 	SPR2_FALL, // SPR2_BNCE,
-	SPR2_ROLL, // SPR2_BLND,
 
 	0, // SPR2_FIRE,
 
@@ -749,6 +766,7 @@ state_t states[NUMSTATES] =
 
 	// CA_GLIDEANDCLIMB
 	{SPR_PLAY, SPR2_GLID,                 2, {NULL}, 0,  0, S_PLAY_GLIDE}, // S_PLAY_GLIDE
+	{SPR_PLAY, SPR2_LAND,                 9, {NULL}, 0,  0, S_PLAY_STND},  // S_PLAY_GLIDE_LANDING
 	{SPR_PLAY, SPR2_CLNG|FF_ANIMATE,     -1, {NULL}, 0,  4, S_NULL},       // S_PLAY_CLING
 	{SPR_PLAY, SPR2_CLMB,                 5, {NULL}, 0,  0, S_PLAY_CLIMB}, // S_PLAY_CLIMB
 
@@ -758,7 +776,7 @@ state_t states[NUMSTATES] =
 
 	// CA_BOUNCE
 	{SPR_PLAY, SPR2_BNCE|FF_ANIMATE,     -1, {NULL},             0,  0, S_NULL},                // S_PLAY_BOUNCE
-	{SPR_PLAY, SPR2_BLND|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_BOUNCE,  0, S_PLAY_BOUNCE_LANDING}, // S_PLAY_BOUNCE_LANDING
+	{SPR_PLAY, SPR2_LAND|FF_SPR2ENDSTATE, 2, {NULL}, S_PLAY_BOUNCE,  0, S_PLAY_BOUNCE_LANDING}, // S_PLAY_BOUNCE_LANDING
 
 	// CA2_GUNSLINGER
 	{SPR_PLAY, SPR2_FIRE|FF_SPR2ENDSTATE,  2, {NULL}, S_PLAY_FIRE_FINISH, 0, S_PLAY_FIRE},   // S_PLAY_FIRE
@@ -849,6 +867,9 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_TALA|FF_SPR2MIDSTART, 35, {NULL}, 0, 0, S_TAILSOVERLAY_GASP}, // S_TAILSOVERLAY_GASP
 	{SPR_PLAY, SPR2_TALB                , 35, {NULL}, 0, 0, S_TAILSOVERLAY_EDGE}, // S_TAILSOVERLAY_EDGE
 
+	// [:
+	{SPR_JETF, 3|FF_ANIMATE|FF_FULLBRIGHT, 2, {NULL}, 1, 1, S_JETFUME1}, // S_JETFUMEFLASH
+
 	// Blue Crawla
 	{SPR_POSS, 0, 5, {A_Look}, 0, 0, S_POSS_STND},   // S_POSS_STND
 	{SPR_POSS, 0, 3, {A_Chase}, 0, 0, S_POSS_RUN2},   // S_POSS_RUN1
@@ -996,6 +1017,22 @@ state_t states[NUMSTATES] =
 	{SPR_CRAB, 3, 37, {NULL},              0,                0, S_CRUSHCLAW_AIM}, // S_CRUSHCLAW_WAIT
 	{SPR_CRAB, 4, -1, {NULL}, 0, 0, S_NULL}, // S_CRUSHCHAIN
 
+	// Banpyura
+	{SPR_CR2B, 0,  3, {A_CrushstaceanWalk},  0, S_BANPYURA_ROAMPAUSE, S_BANPYURA_ROAM2}, // S_BANPYURA_ROAM1
+	{SPR_CR2B, 1,  3, {A_CrushstaceanWalk},  0, S_BANPYURA_ROAMPAUSE, S_BANPYURA_ROAM3}, // S_BANPYURA_ROAM2
+	{SPR_CR2B, 0,  3, {A_CrushstaceanWalk},  0, S_BANPYURA_ROAMPAUSE, S_BANPYURA_ROAM4}, // S_BANPYURA_ROAM3
+	{SPR_CR2B, 2,  3, {A_CrushstaceanWalk},  0, S_BANPYURA_ROAMPAUSE, S_BANPYURA_ROAM1}, // S_BANPYURA_ROAM4
+	{SPR_CR2B, 0, 40, {NULL},                0,                    0, S_BANPYURA_ROAM1}, // S_BANPYURA_ROAMPAUSE
+
+	{SPR_CSPR, 0, 1, {A_CrushclawAim}, 50, 20, S_CDIAG1}, // S_CDIAG1
+	{SPR_CSPR, 1, 1, {A_Pain},          0,  0, S_CDIAG3}, // S_CDIAG2
+	{SPR_CSPR, 2, 1, {A_CrushclawAim}, 50, 20, S_CDIAG4}, // S_CDIAG3
+	{SPR_CSPR, 3, 1, {A_CrushclawAim}, 50, 20, S_CDIAG5}, // S_CDIAG4
+	{SPR_CSPR, 4, 1, {A_CrushclawAim}, 50, 20, S_CDIAG6}, // S_CDIAG5
+	{SPR_CSPR, 3, 1, {A_CrushclawAim}, 50, 20, S_CDIAG7}, // S_CDIAG6
+	{SPR_CSPR, 2, 1, {A_CrushclawAim}, 50, 20, S_CDIAG8}, // S_CDIAG7
+	{SPR_CSPR, 1, 1, {A_CrushclawAim}, 50, 20, S_CDIAG1}, // S_CDIAG8
+
 	// Jet Jaw
 	{SPR_JJAW, 0, 1, {A_JetJawRoam},  0, 0, S_JETJAW_ROAM2},   // S_JETJAW_ROAM1
 	{SPR_JJAW, 0, 1, {A_JetJawRoam},  0, 0, S_JETJAW_ROAM3},   // S_JETJAW_ROAM2
@@ -1184,6 +1221,22 @@ state_t states[NUMSTATES] =
 	{SPR_CANG, 0|FF_TRANS80, 10,        {NULL},            0, 0,       S_CANARIVOREGAS_8}, // S_CANARIVOREGAS_7
 	{SPR_CANG, 0|FF_TRANS90, 10,        {NULL},            0, 0,       S_NULL},            // S_CANARIVOREGAS_8
 
+	// Pyre Fly
+	{SPR_PYRE, FF_ANIMATE, -1, {NULL}, 3, 2, S_NULL}, // S_PYREFLY_FLY
+	{SPR_PYRE, 4|FF_ANIMATE|FF_FULLBRIGHT, -1, {NULL}, 3, 2, S_NULL}, // S_PYREFLY_BURN
+	{SPR_FLAM, FF_FULLBRIGHT, 10, {NULL}, 0, 0, S_PYREFIRE2}, // S_PYREFIRE1
+	{SPR_FLAM, 1|FF_FULLBRIGHT, 10, {A_FireShrink}, 0, 16, S_NULL}, // S_PYREFIRE2
+
+	// Pterabyte
+	{SPR_NULL, 0, -1, {A_SpawnPterabytes}, 0, 0, S_PTERABYTESPAWNER},    // S_PTERABYTESPAWNER
+	{SPR_NULL, 0,  1, {A_PterabyteHover},  0, 0, S_PTERABYTEWAYPOINT},   // S_PTERABYTEWAYPOINT
+	{SPR_PTER, 0,  6, {NULL},              0, 0, S_PTERABYTE_FLY2},      // S_PTERABYTE_FLY1
+	{SPR_PTER, 1,  2, {NULL},              0, 0, S_PTERABYTE_FLY3},      // S_PTERABYTE_FLY2
+	{SPR_PTER, 2,  6, {NULL},              0, 0, S_PTERABYTE_FLY4},      // S_PTERABYTE_FLY3
+	{SPR_PTER, 3,  2, {NULL},              0, 0, S_PTERABYTE_FLY1},      // S_PTERABYTE_FLY4
+	{SPR_PTER, 4,  1, {NULL},              0, 0, S_PTERABYTE_SWOOPDOWN}, // S_PTERABYTE_SWOOPDOWN
+	{SPR_PTER, 0,  1, {NULL},              0, 0, S_PTERABYTE_SWOOPUP},   // S_PTERABYTE_SWOOPUP
+
 	// Boss Explosion
 	{SPR_BOM2, FF_FULLBRIGHT|FF_ANIMATE, (5*7), {NULL}, 6, 5, S_NULL}, // S_BOSSEXPLODE
 
@@ -2462,8 +2515,8 @@ state_t states[NUMSTATES] =
 	{SPR_MCSP, FF_FULLBRIGHT,                1, {A_MinecartSparkThink}, 0, 0, S_MINECARTSPARK},   // S_MINECARTSPARK
 
 	// Saloon door
-	{SPR_SALD, 0|FF_PAPERSPRITE, -1, {NULL},              0, 0, S_NULL}, // S_SALOONDOOR
-	{SPR_NON2, 0,                -1, {A_SaloonDoorSpawn}, 0, 0, S_NULL}, // S_SALONDOORTHINKER
+	{SPR_SALD, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SALOONDOOR
+	{SPR_NULL, 0, -1, {A_SaloonDoorSpawn}, MT_SALOONDOOR, 48, S_NULL}, // S_SALOONDOORCENTER
 
 	// Train cameo
 	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_TRAINCAMEOSPAWNER_2}, // S_TRAINCAMEOSPAWNER_1
@@ -2483,6 +2536,12 @@ state_t states[NUMSTATES] =
 	{SPR_FLME, FF_FULLBRIGHT  ,  4, {NULL}, 0, 0, S_FLAMEJETFLAME2}, // S_FLAMEJETFLAME1
 	{SPR_FLME, FF_FULLBRIGHT|1,  5, {NULL}, 0, 0, S_FLAMEJETFLAME3}, // S_FLAMEJETFLAME2
 	{SPR_FLME, FF_FULLBRIGHT|2, 11, {NULL}, 0, 0,           S_NULL}, // S_FLAMEJETFLAME3
+	{SPR_FLME, FF_FULLBRIGHT|3,  4, {NULL}, 0, 0, S_FLAMEJETFLAME5}, // S_FLAMEJETFLAME4
+	{SPR_FLME, FF_FULLBRIGHT|4,  5, {NULL}, 0, 0, S_FLAMEJETFLAME6}, // S_FLAMEJETFLAME5
+	{SPR_FLME, FF_FULLBRIGHT|5, 11, {NULL}, 0, 0,           S_NULL}, // S_FLAMEJETFLAME6
+	{SPR_FLME, FF_FULLBRIGHT|6,  4, {NULL}, 0, 0, S_FLAMEJETFLAME8}, // S_FLAMEJETFLAME7
+	{SPR_FLME, FF_FULLBRIGHT|7,  5, {NULL}, 0, 0, S_FLAMEJETFLAME9}, // S_FLAMEJETFLAME8
+	{SPR_FLME, FF_FULLBRIGHT|8, 11, {NULL}, 0, 0,           S_NULL}, // S_FLAMEJETFLAME9
 
 	// Spinning flame jets
 	// A: Counter-clockwise
@@ -2498,32 +2557,54 @@ state_t states[NUMSTATES] =
 	{SPR_DFLM, FF_FULLBRIGHT|FF_TRANS40, 1, {A_MoveRelative}, 0, 7, S_FLAMEJETFLAMEB3}, // S_FLAMEJETFLAMEB2
 	{SPR_DFLM, FF_FULLBRIGHT|FF_TRANS40|FF_ANIMATE, (12*7), {NULL}, 7, 12, S_NULL},  // S_FLAMEJETFLAMEB3
 
+	// Lavafall
+	{SPR_LFAL, 5, 1, {NULL}, 0, 0, S_LAVAFALL_DORMANT}, // S_LAVAFALL_DORMANT
+	{SPR_LFAL, 6|FF_ANIMATE, 4, {A_LavafallRocks}, 1, 2, S_LAVAFALL_TELL}, // S_LAVAFALL_TELL
+	{SPR_LFAL, 9|FF_FULLBRIGHT|FF_ANIMATE, 2, {A_LavafallLava}, 1, 1, S_LAVAFALL_SHOOT}, // S_LAVAFALL_SHOOT
+	{SPR_LFAL, FF_FULLBRIGHT, 1, {A_FallingLavaCheck}, 0, 0, S_LAVAFALL_LAVA2}, // S_LAVAFALL_LAVA1
+	{SPR_LFAL, FF_FULLBRIGHT, 1, {A_FallingLavaCheck}, 0, 0, S_LAVAFALL_LAVA1}, // S_LAVAFALL_LAVA2
+	{SPR_LFAL, 2|FF_FULLBRIGHT|FF_ANIMATE, 9, {NULL}, 2, 3, S_NULL}, // S_LAVAFALL_LAVA3
+	{SPR_LFAL, 11|FF_ANIMATE|FF_RANDOMANIM, 12, {NULL}, 3, 3, S_LAVAFALLROCK}, // S_LAVAFALLROCK
+
+	// Rollout Rock
+	{SPR_NULL, 0, 1, {A_RolloutSpawn}, 256*FRACUNIT, MT_ROLLOUTROCK, S_ROLLOUTSPAWN}, // S_ROLLOUTSPAWN
+	{SPR_PUMI, 0, 1, {A_RolloutRock},    63*FRACUNIT/64,  7*FRACUNIT/10,  S_ROLLOUTROCK}, // S_ROLLOUTROCK
+
+	// RVZ scenery
+	{SPR_JPLA, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BIGFERNLEAF
+	{SPR_JPLA, 1, 1, {NULL}, 0, 0, S_BIGFERN2}, // S_BIGFERN1
+	{SPR_JPLA, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BIGFERN2
+	{SPR_JPLA, 2, -1, {NULL}, 0, 0, S_NULL}, // S_JUNGLEPALM
+	{SPR_TFLO, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_TORCHFLOWER}, // S_TORCHFLOWER
+	{SPR_WVIN, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_WALLVINE_LONG
+	{SPR_WVIN, 1|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_WALLVINE_SHORT
+
 	// Trapgoyles
-	{SPR_GARG, 0, 67, {NULL},       0, 0, S_TRAPGOYLE_CHECK},  // S_TRAPGOYLE
-	{SPR_GARG, 0,  3, {NULL},       0, 0, S_TRAPGOYLE_FIRE1},  // S_TRAPGOYLE_CHECK
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLE_FIRE2},  // S_TRAPGOYLE_FIRE1
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLE_FIRE3},  // S_TRAPGOYLE_FIRE2
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLE},  // S_TRAPGOYLE_FIRE3
-
-	{SPR_GARG, 0, 67, {NULL},       0, 0, S_TRAPGOYLEUP_CHECK},  // S_TRAPGOYLEUP
-	{SPR_GARG, 0,  3, {NULL},       0, 0, S_TRAPGOYLEUP_FIRE1},  // S_TRAPGOYLEUP_CHECK
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+45, S_TRAPGOYLEUP_FIRE2},  // S_TRAPGOYLEUP_FIRE1
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+45, S_TRAPGOYLEUP_FIRE3},  // S_TRAPGOYLEUP_FIRE2
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+45, S_TRAPGOYLEUP},  // S_TRAPGOYLEUP_FIRE3
-
-	{SPR_GARG, 0, 67, {NULL},       0, 0, S_TRAPGOYLEDOWN_CHECK},  // S_TRAPGOYLEDOWN
-	{SPR_GARG, 0,  3, {NULL},       0, 0, S_TRAPGOYLEDOWN_FIRE1},  // S_TRAPGOYLEDOWN_CHECK
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+315, S_TRAPGOYLEDOWN_FIRE2},  // S_TRAPGOYLEDOWN_FIRE1
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+315, S_TRAPGOYLEDOWN_FIRE3},  // S_TRAPGOYLEDOWN_FIRE2
-	{SPR_GARG, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+315, S_TRAPGOYLEDOWN},  // S_TRAPGOYLEDOWN_FIRE3
-
-	{SPR_GARG, 0, 135, {NULL},       0, 0, S_TRAPGOYLELONG_CHECK},  // S_TRAPGOYLELONG
-	{SPR_GARG, 0,   3, {NULL},       0, 0, S_TRAPGOYLELONG_FIRE1},  // S_TRAPGOYLELONG_CHECK
-	{SPR_GARG, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE2},  // S_TRAPGOYLELONG_FIRE1
-	{SPR_GARG, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE3},  // S_TRAPGOYLELONG_FIRE2
-	{SPR_GARG, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE4},  // S_TRAPGOYLELONG_FIRE3
-	{SPR_GARG, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE5},  // S_TRAPGOYLELONG_FIRE4
-	{SPR_GARG, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG},  // S_TRAPGOYLELONG_FIRE5
+	{SPR_BGAR, 0, 67, {NULL},       0, 0, S_TRAPGOYLE_CHECK},  // S_TRAPGOYLE
+	{SPR_BGAR, 0,  3, {NULL},       0, 0, S_TRAPGOYLE_FIRE1},  // S_TRAPGOYLE_CHECK
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLE_FIRE2},  // S_TRAPGOYLE_FIRE1
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLE_FIRE3},  // S_TRAPGOYLE_FIRE2
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLE},  // S_TRAPGOYLE_FIRE3
+
+	{SPR_BGAR, 0, 67, {NULL},       0, 0, S_TRAPGOYLEUP_CHECK},  // S_TRAPGOYLEUP
+	{SPR_BGAR, 0,  3, {NULL},       0, 0, S_TRAPGOYLEUP_FIRE1},  // S_TRAPGOYLEUP_CHECK
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+45, S_TRAPGOYLEUP_FIRE2},  // S_TRAPGOYLEUP_FIRE1
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+45, S_TRAPGOYLEUP_FIRE3},  // S_TRAPGOYLEUP_FIRE2
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+45, S_TRAPGOYLEUP},  // S_TRAPGOYLEUP_FIRE3
+
+	{SPR_BGAR, 0, 67, {NULL},       0, 0, S_TRAPGOYLEDOWN_CHECK},  // S_TRAPGOYLEDOWN
+	{SPR_BGAR, 0,  3, {NULL},       0, 0, S_TRAPGOYLEDOWN_FIRE1},  // S_TRAPGOYLEDOWN_CHECK
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+315, S_TRAPGOYLEDOWN_FIRE2},  // S_TRAPGOYLEDOWN_FIRE1
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+315, S_TRAPGOYLEDOWN_FIRE3},  // S_TRAPGOYLEDOWN_FIRE2
+	{SPR_BGAR, 0,  1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16)+315, S_TRAPGOYLEDOWN},  // S_TRAPGOYLEDOWN_FIRE3
+
+	{SPR_BGAR, 0, 135, {NULL},       0, 0, S_TRAPGOYLELONG_CHECK},  // S_TRAPGOYLELONG
+	{SPR_BGAR, 0,   3, {NULL},       0, 0, S_TRAPGOYLELONG_FIRE1},  // S_TRAPGOYLELONG_CHECK
+	{SPR_BGAR, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE2},  // S_TRAPGOYLELONG_FIRE1
+	{SPR_BGAR, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE3},  // S_TRAPGOYLELONG_FIRE2
+	{SPR_BGAR, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE4},  // S_TRAPGOYLELONG_FIRE3
+	{SPR_BGAR, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG_FIRE5},  // S_TRAPGOYLELONG_FIRE4
+	{SPR_BGAR, 0,   1, {A_TrapShot}, (16<<16)+MT_DEMONFIRE, (30<<16), S_TRAPGOYLELONG},  // S_TRAPGOYLELONG_FIRE5
 
 	// Target/Red Crystal
 	{SPR_RCRY,               0, -1, {NULL},                  0, 0, S_TARGET_IDLE},  // S_TARGET_IDLE
@@ -2532,6 +2613,9 @@ state_t states[NUMSTATES] =
 	{SPR_RCRY,               1,  0, {A_SpawnObjectRelative}, 0, MT_TARGET, S_NULL},  // S_TARGET_RESPAWN
 	{SPR_RCRY, FF_FULLBRIGHT|1, -1, {A_SetObjectFlags},      MF_PUSHABLE, 1, S_TARGET_ALLDONE},  // S_TARGET_ALLDONE
 
+	// Green flame
+	{SPR_CFLM, FF_FULLBRIGHT|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 7, 3, S_GREENFLAME}, // S_GREENFLAME
+
 	// Stalagmites
 	{SPR_STLG, 0, -1, {NULL}, 0, 0, S_NULL}, // S_STG0
 	{SPR_STLG, 1, -1, {NULL}, 0, 0, S_NULL}, // S_STG1
@@ -2552,6 +2636,7 @@ state_t states[NUMSTATES] =
 	{SPR_XMS4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_LAMPPOST1
 	{SPR_XMS4, 1, -1, {NULL}, 0, 0, S_NULL}, // S_LAMPPOST2
 	{SPR_XMS5, 0, -1, {NULL}, 0, 0, S_NULL}, // S_HANGSTAR
+	{SPR_XMS6, 0, -1, {NULL}, 0, 0, S_NULL}, // S_MISTLETOE
 	// Xmas GFZ bushes
 	{SPR_BUS3, 1, -1, {NULL}, 0, 0, S_NULL}, // S_XMASBLUEBERRYBUSH
 	{SPR_BUS1, 1, -1, {NULL}, 0, 0, S_NULL}, // S_XMASBERRYBUSH
@@ -2559,6 +2644,16 @@ state_t states[NUMSTATES] =
 	// FHZ
 	{SPR_FHZI, 0, -1, {NULL}, 0, 0, S_NULL}, // S_FHZICE1
 	{SPR_FHZI, 1, -1, {NULL}, 0, 0, S_NULL}, // S_FHZICE2
+	{SPR_ROSY, 16, 8, {NULL}, 0, 0, S_ROSY_IDLE2}, // S_ROSY_IDLE1
+	{SPR_ROSY, 17, 4, {NULL}, 0, 0, S_ROSY_IDLE3}, // S_ROSY_IDLE2
+	{SPR_ROSY, 18, 8, {NULL}, 0, 0, S_ROSY_IDLE4}, // S_ROSY_IDLE3
+	{SPR_ROSY, 17, 4, {NULL}, 0, 0, S_ROSY_IDLE1}, // S_ROSY_IDLE4
+	{SPR_ROSY, 14, -1, {NULL}, 1, 0, S_NULL}, // S_ROSY_JUMP
+	{SPR_ROSY,  5, -1, {NULL}, 7, 0, S_NULL}, // S_ROSY_WALK
+	{SPR_ROSY, 19, -1, {NULL}, 0, 0, S_NULL}, // S_ROSY_HUG
+	{SPR_ROSY, 13, -1, {NULL}, 0, 0, S_NULL}, // S_ROSY_PAIN
+	{SPR_ROSY,  1|FF_ANIMATE, -1, {NULL}, 3, 16, S_NULL}, // S_ROSY_STND
+	{SPR_ROSY, 20|FF_ANIMATE, TICRATE, {NULL}, 3, 4, S_ROSY_WALK}, // S_ROSY_UNHAPPY
 
 	// Halloween Scenery
 	// Pumpkins
@@ -3214,6 +3309,17 @@ state_t states[NUMSTATES] =
 	{SPR_SSWB, 2, 1, {NULL}, 0, 0, S_BHORIZ8},   // S_BHORIZ7
 	{SPR_SSWB, 1, 1, {NULL}, 0, 0, S_BHORIZ1},   // S_BHORIZ8
 
+	// Boosters
+	{SPR_NULL, 0, 1, {A_Pain}, 0, 0, S_INVISIBLE}, // S_BOOSTERSOUND
+	{SPR_BSTY,                  FF_ANIMATE, -1, {NULL}, 2, 1, S_NULL}, // S_YELLOWBOOSTERROLLER
+	{SPR_BSTY, 3|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 2, 3, S_NULL}, // S_YELLOWBOOSTERSEG_LEFT
+	{SPR_BSTY, 6|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 2, 3, S_NULL}, // S_YELLOWBOOSTERSEG_RIGHT
+	{SPR_BSTY, 9|FF_PAPERSPRITE,            -1, {NULL}, 0, 0, S_NULL}, // S_YELLOWBOOSTERSEG_FACE
+	{SPR_BSTR,                  FF_ANIMATE, -1, {NULL}, 2, 1, S_NULL}, // S_REDBOOSTERROLLER
+	{SPR_BSTR, 3|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 2, 3, S_NULL}, // S_REDBOOSTERSEG_LEFT
+	{SPR_BSTR, 6|FF_PAPERSPRITE|FF_ANIMATE, -1, {NULL}, 2, 3, S_NULL}, // S_REDBOOSTERSEG_RIGHT
+	{SPR_BSTR, 9|FF_PAPERSPRITE,            -1, {NULL}, 0, 0, S_NULL}, // S_REDBOOSTERSEG_FACE
+
 	// Rain
 	{SPR_RAIN, FF_FULLBRIGHT|FF_TRANS50, -1, {NULL}, 0, 0, S_NULL}, // S_RAIN1
 	{SPR_RAIN, FF_FULLBRIGHT|FF_TRANS50, 1, {NULL}, 0, 0, S_RAIN1}, // S_RAINRETURN
@@ -3234,6 +3340,9 @@ state_t states[NUMSTATES] =
 	{SPR_SPLH, FF_TRANS50|7, 2, {NULL}, 0, 0, S_SPLISH9}, // S_SPLISH8
 	{SPR_SPLH, FF_TRANS50|8, 2, {NULL}, 0, 0, S_NULL},    // S_SPLISH9
 
+	// Lava splish
+	{SPR_LSPL, FF_ANIMATE, 16, {NULL}, 7, 2, S_NULL}, // S_LAVASPLISH
+
 	// Water Splash
 	{SPR_SPLA, FF_TRANS50  , 3, {NULL}, 0, 0, S_SPLASH2},    // S_SPLASH1
 	{SPR_SPLA, FF_TRANS70|1, 3, {NULL}, 0, 0, S_SPLASH3},    // S_SPLASH2
@@ -3545,7 +3654,7 @@ state_t states[NUMSTATES] =
 
 	// Puma (Mario fireball)
 	{SPR_PUMA, FF_FULLBRIGHT|2, 1, {A_FishJump}, 0, MT_PUMATRAIL, S_PUMA_START2},   // S_PUMA_START1
-	{SPR_PUMA, FF_FULLBRIGHT|2, 1, {A_PlaySound}, sfx_s3k70, 1, S_PUMA_UP1},   // S_PUMA_START2
+	{SPR_PUMA, FF_FULLBRIGHT|2, 1, {A_PlaySound}, sfx_s3k70, 1 + (1<<16), S_PUMA_UP1},   // S_PUMA_START2
 	{SPR_PUMA, FF_FULLBRIGHT  , 2, {A_FishJump}, 0, MT_PUMATRAIL, S_PUMA_UP2},   // S_PUMA_UP1
 	{SPR_PUMA, FF_FULLBRIGHT|1, 2, {A_FishJump}, 0, MT_PUMATRAIL, S_PUMA_UP3},   // S_PUMA_UP2
 	{SPR_PUMA, FF_FULLBRIGHT|2, 2, {A_FishJump}, 0, MT_PUMATRAIL, S_PUMA_UP1},   // S_PUMA_UP3
@@ -3998,7 +4107,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MT_THOK,        // damage
 		sfx_None,       // activesound
 		MF_SOLID|MF_SHOOTABLE, // flags
-		MT_NULL         // raisestate
+		(statenum_t)MT_NULL// raisestate
 	},
 
 	{           // MT_TAILSOVERLAY
@@ -4028,6 +4137,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_METALJETFUME
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
+		1000,           // spawnhealth
+		S_JETFUMEFLASH, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		2,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_JETFUME1      // raisestate
+	},
+
 	{           // MT_BLUECRAWLA
 		100,            // doomednum
 		S_POSS_STND,    // spawnstate
@@ -4422,7 +4558,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_XPLD1,        // deathstate
 		S_NULL,         // xdeathstate
 		sfx_pop,        // deathsound
-		1,              // speed
+		600,            // speed
 		22*FRACUNIT,    // radius
 		24*FRACUNIT,    // height
 		0,              // display offset
@@ -4430,7 +4566,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // damage
 		sfx_s3kd2l,     // activesound
 		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
-		MT_CRUSHCHAIN   // raisestate
+		(statenum_t)MT_CRUSHCHAIN// raisestate
 	},
 
 	{           // MT_CRUSHCHAIN
@@ -4460,6 +4596,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_BANPYURA
+		138,            // doomednum
+		S_BANPYURA_ROAM1, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		32,             // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		8,              // speed
+		24*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_BANPSPRING
+		-1,             // doomednum
+		S_CDIAG1,       // spawnstate
+		1,              // spawnhealth
+		S_CDIAG2,       // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_cdfm08,     // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_XPLD1,        // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		300,            // speed
+		22*FRACUNIT,    // radius
+		22*FRACUNIT,    // height
+		0,              // display offset
+		11*FRACUNIT,    // mass
+		11*FRACUNIT,    // damage
+		sfx_None,       // activesound
+		MF_SPRING|MF_NOGRAVITY, // flags
+		S_CDIAG2        // raisestate
+	},
+
 	{           // MT_JETJAW
 		113,            // doomednum
 		S_JETJAW_ROAM1, // spawnstate
@@ -4780,7 +4970,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SCENERY|MF_PAIN|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		MF_SCENERY|MF_PAIN|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_SNAPPER_LEGRAISE // raisestate
 	},
 
@@ -4807,7 +4997,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_PAIN|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		MF_PAIN|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -5027,6 +5217,141 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_PYREFLY
+		136,            // doomednum
+		S_PYREFLY_FLY,  // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_PYREFLY_BURN, // meleestate
+		S_NULL,         // missilestate
+		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		1,              // speed
+		24*FRACUNIT,    // radius
+		34*FRACUNIT,    // height
+		0,              // display offset
+		DMG_FIRE,       // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_SLIDEME, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_PYREFLY_FIRE
+		-1,             // doomednum
+		S_PYREFIRE1,    // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		24*FRACUNIT,    // radius
+		34*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_FIRE|MF_PAIN, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_PTERABYTESPAWNER
+		135,            // doomednum
+		S_PTERABYTESPAWNER, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_PTERABYTEWAYPOINT
+		-1,             // doomednum
+		S_PTERABYTEWAYPOINT, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		4*FRACUNIT,     // speed
+		24*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_PTERABYTE
+		-1,             // doomednum
+		S_PTERABYTE_FLY1, // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_pscree,     // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_XPLD_FLICKY,  // deathstate
+		S_NULL,         // xdeathstate
+		sfx_pop,        // deathsound
+		4*FRACUNIT,     // speed
+		24*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_SHOOTABLE|MF_ENEMY|MF_NOGRAVITY|MF_SLIDEME, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_BOSSEXPLODE
 		-1,             // doomednum
 		S_BOSSEXPLODE,  // spawnstate
@@ -7364,19 +7689,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		32*FRACUNIT,    // height
 		0,              // display offset
 		0,              // mass
-		1*FRACUNIT,     // damage
+		11*FRACUNIT,    // damage
 		sfx_None,       // activesound
 		MF_SPRING|MF_NOGRAVITY, // flags
 		S_BHORIZ2       // raisestate
 	},
 
-	{           // MT_BUBBLES
-		500,            // doomednum
-		S_BUBBLES1,     // spawnstate
-		1000,           // spawnhealth
+	{          // MT_BOOSTERSEG
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -7387,23 +7712,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		28*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP,  // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SIGN
-		501,            // doomednum
-		S_SIGN52,       // spawnstate
-		1000,           // spawnhealth
-		S_PLAY_SIGN,    // seestate
-		sfx_lvpass,     // seesound
-		8,              // reactiontime
+	{          // MT_BOOSTERROLLER
+		-1,             // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
@@ -7413,45 +7738,153 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
+		0,              // speed
+		14*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP,    // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SPIKEBALL
-		521,            // doomednum
-		S_SPIKEBALL1,   // spawnstate
-		1000,           // spawnhealth
+	{           // MT_YELLOWBOOSTER
+		544,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
-		8,              // reactiontime
+		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
+		3,              // painchance
+		sfx_cdfm62,     // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		10*FRACUNIT,    // speed
-		12*FRACUNIT,    // radius
-		8*FRACUNIT,     // height
+		0,              // speed
+		28*FRACUNIT,    // radius
+		FRACUNIT,       // height
 		0,              // display offset
-		DMG_SPIKE,      // mass
-		1,              // damage
+		0,              // mass
+		36*FRACUNIT,    // damage
 		sfx_None,       // activesound
-		MF_PAIN|MF_NOGRAVITY, // flags
-		S_NULL          // raisestate
+		MF_SPRING|MF_NOGRAVITY, // flags
+		S_BOOSTERSOUND  // raisestate
 	},
 
-	{           // MT_SPINFIRE
+	{           // MT_REDBOOSTER
+		545,            // doomednum
+		S_INVISIBLE,    // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		3,              // painchance
+		sfx_cdfm62,     // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		28*FRACUNIT,    // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		0,              // mass
+		72*FRACUNIT,    // damage
+		sfx_None,       // activesound
+		MF_SPRING|MF_NOGRAVITY, // flags
+		S_BOOSTERSOUND  // raisestate
+	},
+
+	{           // MT_BUBBLES
+		500,            // doomednum
+		S_BUBBLES1,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY|MF_RUNSPAWNFUNC, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SIGN
+		501,            // doomednum
+		S_SIGN52,       // spawnstate
+		1000,           // spawnhealth
+		S_PLAY_SIGN,    // seestate
+		sfx_lvpass,     // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		8*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SPIKEBALL
+		521,            // doomednum
+		S_SPIKEBALL1,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		10*FRACUNIT,    // speed
+		12*FRACUNIT,    // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		DMG_SPIKE,      // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_PAIN|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SPINFIRE
 		-1,             // doomednum
 		S_SPINFIRE1,    // spawnstate
 		1,              // spawnhealth
@@ -10908,7 +11341,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{            // MT_SMALLMACE
-		-1,             // doomednum
+		1130,           // doomednum
 		S_SMALLMACE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -10935,7 +11368,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{            // MT_BIGMACE
-		-1,             // doomednum
+		1131,           // doomednum
 		S_BIGMACE,      // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -10990,7 +11423,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{            // MT_BIGGRABCHAIN
 		-1,             // doomednum
-		S_BIGGRABCHAIN,	// spawnstate
+		S_BIGGRABCHAIN, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -11016,7 +11449,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{            // MT_YELLOWSPRINGBALL
-		-1,             // doomednum
+		1134,           // doomednum
 		S_YELLOWSPRINGBALL, // spawnstate
 		1000,           // spawnhealth
 		S_YELLOWSPRINGBALL2, // seestate
@@ -11043,7 +11476,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{            // MT_REDSPRINGBALL
-		-1,             // doomednum
+		1135,           // doomednum
 		S_REDSPRINGBALL, // spawnstate
 		1000,           // spawnhealth
 		S_REDSPRINGBALL2, // seestate
@@ -11070,7 +11503,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{            // MT_SMALLFIREBAR
-		-1,             // doomednum
+		1136,           // doomednum
 		S_SMALLFIREBAR1,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -11097,7 +11530,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{            // MT_BIGFIREBAR
-		-1,             // doomednum
+		1137,           // doomednum
 		S_BIGFIREBAR1,  // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
@@ -11633,7 +12066,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // damage
 		sfx_None,       // activesound
 		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
-		MT_ROCKCRUMBLE3 // raisestate
+		(statenum_t)MT_ROCKCRUMBLE3// raisestate
 	},
 
 	{           // MT_BRAMBLES
@@ -12199,7 +12632,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SHOOTABLE|MF_ENEMY|MF_PUSHABLE, // flags
+		MF_SOLID|MF_SHOOTABLE|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
@@ -12335,7 +12768,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		0,              // damage
 		sfx_s3k76,      // activesound
 		MF_PUSHABLE,    // flags
-		MT_MINECARTSIDEMARK // raisestate
+		(statenum_t)MT_MINECARTSIDEMARK// raisestate
 	},
 
 	{          // MT_MINECARTSEG
@@ -12527,9 +12960,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{          // MT_SALOONDOORTHINKER
+	{          // MT_SALOONDOORCENTER
 		1221,           // doomednum
-		S_SALOONDOORTHINKER,   // spawnstate
+		S_SALOONDOORCENTER, // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -12550,7 +12983,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags
+		MF_SOLID|MF_NOGRAVITY|MF_RUNSPAWNFUNC|MF_PAPERCOLLISION|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
@@ -12851,6 +13284,303 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_LAVAFALL
+		1304,           // doomednum
+		S_LAVAFALL_DORMANT, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_lvfal1,     // seesound
+		8,              // reactiontime
+		sfx_s3kd5l,     // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		30*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		1,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_SPAWNCEILING, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_LAVAFALL_LAVA
+		-1,             // doomednum
+		S_LAVAFALL_LAVA1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_LAVAFALL_LAVA3, // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		30*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_PAIN|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_LAVAFALLROCK
+		-1,             // doomednum
+		S_LAVAFALLROCK, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_ROLLOUTSPAWN
+		1305,           // doomednum
+		S_ROLLOUTSPAWN, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SPAWNCEILING,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_ROLLOUTROCK
+		-1,             // doomednum
+		S_ROLLOUTROCK,  // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime (sets number of frames the rock cycles through)
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		12*TICRATE,     // painchance (sets how long an unridden rock should last before disappearing - set to 0 to disable)
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		32*FRACUNIT,    // speed
+		30*FRACUNIT,    // radius
+		60*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_PUSHABLE|MF_SOLID|MF_SLIDEME,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_BIGFERNLEAF
+		-1,             // doomednum
+		S_BIGFERNLEAF,  // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_BIGFERN
+		1306,           // doomednum
+		S_BIGFERN1,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_JUNGLEPALM
+		1307,           // doomednum
+		S_JUNGLEPALM,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_TORCHFLOWER
+		1308,           // doomednum
+		S_TORCHFLOWER,  // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		14*FRACUNIT,    // radius
+		110*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WALLVINE_LONG
+		1309,           // doomednum
+		S_WALLVINE_LONG, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		1*FRACUNIT,    // radius
+		288*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WALLVINE_SHORT
+		1310,           // doomednum
+		S_WALLVINE_SHORT, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		1*FRACUNIT,    // radius
+		288*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_TRAPGOYLE
 		1500,           // doomednum
 		S_TRAPGOYLE,    // spawnstate
@@ -12986,6 +13716,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_GREENFLAME
+		1505,           // doomednum
+		S_GREENFLAME,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_NULL,        // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		0,       // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_PAIN, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_STALAGMITE0
 		1900,           // doomednum
 		S_STG0,         // spawnstate
@@ -13445,6 +14202,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_MISTLETOE
+		2105,           // doomednum
+		S_MISTLETOE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		52*FRACUNIT,    // radius
+		106*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_XMASBLUEBERRYBUSH
 		1859,           // doomednum
 		S_XMASBLUEBERRYBUSH, // spawnstate
@@ -13580,6 +14364,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_ROSY
+		2104,           // doomednum
+		S_ROSY_IDLE1,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		48*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_ENEMY|MF_SLIDEME, // flags -- "enemy" may seem weird but it doesn't have any unintended consequences in context because no MF_SHOOTABLE|MF_SPECIAL
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CDLHRT
+		-1,             // doomednum
+		S_LHRT,         // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		4*FRACUNIT,     // speed
+		4*FRACUNIT,     // radius
+		4*FRACUNIT,     // height
+		1,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_JACKO1
 		2006,           // doomednum
 		S_JACKO1,       // spawnstate
@@ -16635,6 +17473,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_LAVASPLISH
+		-1,             // doomednum
+		S_LAVASPLISH,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		6*FRACUNIT,     // radius
+		1*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_SMOKE
 		-1,             // doomednum
 		S_SMOKE1,       // spawnstate
diff --git a/src/info.h b/src/info.h
index 79a191cccd67bb25f19ee4025b1d9ed9a364313a..e7f41f585cbd959b88a3ab332edd44bc0979b58d 100644
--- a/src/info.h
+++ b/src/info.h
@@ -268,6 +268,14 @@ void A_SnapperThinker();
 void A_SaloonDoorSpawn();
 void A_MinecartSparkThink();
 void A_ModuloToState();
+void A_LavafallRocks();
+void A_LavafallLava();
+void A_FallingLavaCheck();
+void A_FireShrink();
+void A_SpawnPterabytes();
+void A_PterabyteHover();
+void A_RolloutSpawn();
+void A_RolloutRock();
 
 // ratio of states to sprites to mobj types is roughly 6 : 1 : 1
 #define NUMMOBJFREESLOTS 512
@@ -298,6 +306,8 @@ typedef enum sprite
 	SPR_TURR, // Pop-Up Turret
 	SPR_SHRP, // Sharp
 	SPR_CRAB, // Crushstacean
+	SPR_CR2B, // Banpyura
+	SPR_CSPR, // Banpyura spring
 	SPR_JJAW, // Jet Jaw
 	SPR_SNLR, // Snailer
 	SPR_VLTR, // BASH
@@ -316,6 +326,8 @@ typedef enum sprite
 	SPR_UNID, // Unidus
 	SPR_CANA, // Canarivore
 	SPR_CANG, // Canarivore gas
+	SPR_PYRE, // Pyre Fly
+	SPR_PTER, // Pterabyte
 
 	// Generic Boss Items
 	SPR_JETF, // Boss jet fumes
@@ -397,6 +409,7 @@ typedef enum sprite
 	SPR_WSPB, // Wall spike base
 	SPR_STPT, // Starpost
 	SPR_BMNE, // Big floating mine
+	SPR_PUMI, // Rollout Rock
 
 	// Monitor Boxes
 	SPR_MSTV, // MiSc TV sprites
@@ -511,7 +524,6 @@ typedef enum sprite
 	SPR_ADST, // Arid dust
 	SPR_MCRT, // Minecart
 	SPR_MCSP, // Minecart spark
-	SPR_NON2, // Saloon door thinker
 	SPR_SALD, // Saloon door
 	SPR_TRAE, // Train cameo locomotive
 	SPR_TRAI, // Train cameo wagon
@@ -520,6 +532,10 @@ typedef enum sprite
 	// Red Volcano Scenery
 	SPR_FLME, // Flame jet
 	SPR_DFLM, // Blade's flame
+	SPR_LFAL, // Lavafall
+	SPR_JPLA, // Jungle palm
+	SPR_TFLO, // Torch flower
+	SPR_WVIN, // Wall vines
 
 	// Dark City Scenery
 
@@ -531,13 +547,20 @@ typedef enum sprite
 	SPR_XMS3, // Snowman
 	SPR_XMS4, // Lamppost
 	SPR_XMS5, // Hanging Star
+	SPR_XMS6, // Mistletoe
 	SPR_FHZI, // FHZ Ice
+	SPR_ROSY,
 
 	// Halloween Scenery
 	SPR_PUMK, // Pumpkins
 	SPR_HHPL, // Dr Seuss Trees
 	SPR_SHRM, // Mushroom
 	SPR_HHZM, // Misc
+	
+	// Azure Temple Scenery
+	SPR_BGAR, // ATZ Gargoyles
+	SPR_RCRY, // ATZ Red Crystal (Target)
+	SPR_CFLM, // Green torch flame
 
 	// Botanic Serenity Scenery
 	SPR_BSZ1, // Tall flowers
@@ -558,7 +581,6 @@ typedef enum sprite
 	// Misc Scenery
 	SPR_STLG, // Stalagmites
 	SPR_DBAL, // Disco
-	SPR_RCRY, // ATZ Red Crystal (Target)
 
 	// Powerup Indicators
 	SPR_ARMA, // Armageddon Shield Orb
@@ -612,11 +634,14 @@ typedef enum sprite
 	SPR_SSWY, // Yellow Side Spring
 	SPR_SSWR, // Red Side Spring
 	SPR_SSWB, // Blue Side Spring
+	SPR_BSTY, // Yellow Booster
+	SPR_BSTR, // Red Booster
 
 	// Environmental Effects
 	SPR_RAIN, // Rain
 	SPR_SNO1, // Snowflake
 	SPR_SPLH, // Water Splish
+	SPR_LSPL, // Lava Splish
 	SPR_SPLA, // Water Splash
 	SPR_SMOK,
 	SPR_BUBL, // Bubble
@@ -771,6 +796,7 @@ typedef enum playersprite
 	SPR2_TIRE, // tired
 
 	SPR2_GLID, // glide
+	SPR2_LAND, // landing after glide/bounce
 	SPR2_CLNG, // cling
 	SPR2_CLMB, // climb
 
@@ -778,7 +804,6 @@ typedef enum playersprite
 	SPR2_FRUN, // float run
 
 	SPR2_BNCE, // bounce
-	SPR2_BLND, // bounce landing
 
 	SPR2_FIRE, // fire
 
@@ -854,6 +879,12 @@ typedef enum playersprite
 	NUMPLAYERSPRITES
 } playersprite_t;
 
+// SPR2_XTRA
+#define XTRA_LIFEPIC    0                 // Life icon patch
+#define XTRA_CHARSEL    1                 // Character select picture
+#define XTRA_CONTINUE   2                 // Continue icon
+#define XTRA_ENDING     3                 // Ending finale patches
+
 typedef enum state
 {
 	S_NULL,
@@ -900,6 +931,7 @@ typedef enum state
 
 	// CA_GLIDEANDCLIMB
 	S_PLAY_GLIDE,
+	S_PLAY_GLIDE_LANDING,
 	S_PLAY_CLING,
 	S_PLAY_CLIMB,
 
@@ -999,6 +1031,9 @@ typedef enum state
 	S_TAILSOVERLAY_GASP,
 	S_TAILSOVERLAY_EDGE,
 
+	// [:
+	S_JETFUMEFLASH,
+
 	// Blue Crawla
 	S_POSS_STND,
 	S_POSS_RUN1,
@@ -1147,6 +1182,21 @@ typedef enum state
 	S_CRUSHCLAW_WAIT,
 	S_CRUSHCHAIN,
 
+	// Banpyura
+	S_BANPYURA_ROAM1,
+	S_BANPYURA_ROAM2,
+	S_BANPYURA_ROAM3,
+	S_BANPYURA_ROAM4,
+	S_BANPYURA_ROAMPAUSE,
+	S_CDIAG1,
+	S_CDIAG2,
+	S_CDIAG3,
+	S_CDIAG4,
+	S_CDIAG5,
+	S_CDIAG6,
+	S_CDIAG7,
+	S_CDIAG8,
+
 	// Jet Jaw
 	S_JETJAW_ROAM1,
 	S_JETJAW_ROAM2,
@@ -1333,6 +1383,22 @@ typedef enum state
 	S_CANARIVOREGAS_7,
 	S_CANARIVOREGAS_8,
 
+	// Pyre Fly
+	S_PYREFLY_FLY,
+	S_PYREFLY_BURN,
+	S_PYREFIRE1,
+	S_PYREFIRE2,
+
+	// Pterabyte
+	S_PTERABYTESPAWNER,
+	S_PTERABYTEWAYPOINT,
+	S_PTERABYTE_FLY1,
+	S_PTERABYTE_FLY2,
+	S_PTERABYTE_FLY3,
+	S_PTERABYTE_FLY4,
+	S_PTERABYTE_SWOOPDOWN,
+	S_PTERABYTE_SWOOPUP,
+
 	// Boss Explosion
 	S_BOSSEXPLODE,
 
@@ -2576,7 +2642,7 @@ typedef enum state
 
 	// Saloon door
 	S_SALOONDOOR,
-	S_SALOONDOORTHINKER,
+	S_SALOONDOORCENTER,
 
 	// Train cameo
 	S_TRAINCAMEOSPAWNER_1,
@@ -2597,6 +2663,12 @@ typedef enum state
 	S_FLAMEJETFLAME1,
 	S_FLAMEJETFLAME2,
 	S_FLAMEJETFLAME3,
+	S_FLAMEJETFLAME4,
+	S_FLAMEJETFLAME5,
+	S_FLAMEJETFLAME6,
+	S_FLAMEJETFLAME7,
+	S_FLAMEJETFLAME8,
+	S_FLAMEJETFLAME9,
 
 	// Spinning flame jets
 	S_FJSPINAXISA1, // Counter-clockwise
@@ -2609,6 +2681,28 @@ typedef enum state
 	S_FLAMEJETFLAMEB2,
 	S_FLAMEJETFLAMEB3,
 
+	// Lavafall
+	S_LAVAFALL_DORMANT,
+	S_LAVAFALL_TELL,
+	S_LAVAFALL_SHOOT,
+	S_LAVAFALL_LAVA1,
+	S_LAVAFALL_LAVA2,
+	S_LAVAFALL_LAVA3,
+	S_LAVAFALLROCK,
+
+	// Rollout Rock
+	S_ROLLOUTSPAWN,
+	S_ROLLOUTROCK,
+
+	// RVZ scenery
+	S_BIGFERNLEAF,
+	S_BIGFERN1,
+	S_BIGFERN2,
+	S_JUNGLEPALM,
+	S_TORCHFLOWER,
+	S_WALLVINE_LONG,
+	S_WALLVINE_SHORT,
+
 	// Trapgoyles
 	S_TRAPGOYLE,
 	S_TRAPGOYLE_CHECK,
@@ -2640,6 +2734,9 @@ typedef enum state
 	S_TARGET_RESPAWN,
 	S_TARGET_ALLDONE,
 
+	// ATZ's green flame
+	S_GREENFLAME,
+
 	// Stalagmites
 	S_STG0,
 	S_STG1,
@@ -2660,6 +2757,7 @@ typedef enum state
 	S_LAMPPOST1,  // normal
 	S_LAMPPOST2,  // with snow
 	S_HANGSTAR,
+	S_MISTLETOE,
 	// Xmas GFZ bushes
 	S_XMASBLUEBERRYBUSH,
 	S_XMASBERRYBUSH,
@@ -2667,6 +2765,16 @@ typedef enum state
 	// FHZ
 	S_FHZICE1,
 	S_FHZICE2,
+	S_ROSY_IDLE1,
+	S_ROSY_IDLE2,
+	S_ROSY_IDLE3,
+	S_ROSY_IDLE4,
+	S_ROSY_JUMP,
+	S_ROSY_WALK,
+	S_ROSY_HUG,
+	S_ROSY_PAIN,
+	S_ROSY_STND,
+	S_ROSY_UNHAPPY,
 
 	// Halloween Scenery
 	// Pumpkins
@@ -3318,6 +3426,17 @@ typedef enum state
 	S_BHORIZ7,
 	S_BHORIZ8,
 
+	// Booster
+	S_BOOSTERSOUND,
+	S_YELLOWBOOSTERROLLER,
+	S_YELLOWBOOSTERSEG_LEFT,
+	S_YELLOWBOOSTERSEG_RIGHT,
+	S_YELLOWBOOSTERSEG_FACE,
+	S_REDBOOSTERROLLER,
+	S_REDBOOSTERSEG_LEFT,
+	S_REDBOOSTERSEG_RIGHT,
+	S_REDBOOSTERSEG_FACE,
+
 	// Rain
 	S_RAIN1,
 	S_RAINRETURN,
@@ -3338,6 +3457,9 @@ typedef enum state
 	S_SPLISH8,
 	S_SPLISH9,
 
+	// Lava Splish
+	S_LAVASPLISH,
+
 	// added water splash
 	S_SPLASH1,
 	S_SPLASH2,
@@ -3971,6 +4093,7 @@ typedef enum mobj_type
 	MT_THOK, // Thok! mobj
 	MT_PLAYER,
 	MT_TAILSOVERLAY, // c:
+	MT_METALJETFUME,
 
 	// Enemies
 	MT_BLUECRAWLA, // Crawla (Blue)
@@ -3989,6 +4112,8 @@ typedef enum mobj_type
 	MT_CRUSHSTACEAN, // Crushstacean
 	MT_CRUSHCLAW, // Big meaty claw
 	MT_CRUSHCHAIN, // Chain
+	MT_BANPYURA, // Banpyura
+	MT_BANPSPRING, // Banpyura spring
 	MT_JETJAW, // Jet Jaw
 	MT_SNAILER, // Snailer
 	MT_VULTURE, // BASH
@@ -4010,6 +4135,11 @@ typedef enum mobj_type
 	MT_UNIBALL, // Unidus Ball
 	MT_CANARIVORE, // Canarivore
 	MT_CANARIVORE_GAS, // Canarivore gas
+	MT_PYREFLY, // Pyre Fly
+	MT_PYREFLY_FIRE, // Pyre Fly fire
+	MT_PTERABYTESPAWNER, // Pterabyte spawner
+	MT_PTERABYTEWAYPOINT, // Pterabyte waypoint
+	MT_PTERABYTE, // Pterabyte
 
 	// Generic Boss Items
 	MT_BOSSEXPLODE,
@@ -4121,6 +4251,11 @@ typedef enum mobj_type
 	MT_REDHORIZ,
 	MT_BLUEHORIZ,
 
+	MT_BOOSTERSEG,
+	MT_BOOSTERROLLER,
+	MT_YELLOWBOOSTER,
+	MT_REDBOOSTER,
+
 	// Interactive Objects
 	MT_BUBBLES, // Bubble source
 	MT_SIGN, // Level end sign
@@ -4338,7 +4473,7 @@ typedef enum mobj_type
 	MT_MINECARTSIDEMARK,
 	MT_MINECARTSPARK,
 	MT_SALOONDOOR,
-	MT_SALOONDOORTHINKER,
+	MT_SALOONDOORCENTER,
 	MT_TRAINCAMEOSPAWNER,
 	MT_TRAINSEG,
 	MT_TRAINDUSTSPAWNER,
@@ -4355,6 +4490,20 @@ typedef enum mobj_type
 
 	MT_FLAMEJETFLAMEB, // Blade's flame
 
+	MT_LAVAFALL,
+	MT_LAVAFALL_LAVA,
+	MT_LAVAFALLROCK,
+
+	MT_ROLLOUTSPAWN,
+	MT_ROLLOUTROCK,
+
+	MT_BIGFERNLEAF,
+	MT_BIGFERN,
+	MT_JUNGLEPALM,
+	MT_TORCHFLOWER,
+	MT_WALLVINE_LONG,
+	MT_WALLVINE_SHORT,
+
 	// Dark City Scenery
 
 	// Egg Rock Scenery
@@ -4365,6 +4514,7 @@ typedef enum mobj_type
 	MT_TRAPGOYLEDOWN,
 	MT_TRAPGOYLELONG,
 	MT_TARGET, // AKA Red Crystal
+	MT_GREENFLAME,
 
 	// Stalagmites
 	MT_STALAGMITE0,
@@ -4386,6 +4536,7 @@ typedef enum mobj_type
 	MT_LAMPPOST1,  // normal
 	MT_LAMPPOST2,  // with snow
 	MT_HANGSTAR,
+	MT_MISTLETOE,
 	// Xmas GFZ bushes
 	MT_XMASBLUEBERRYBUSH,
 	MT_XMASBERRYBUSH,
@@ -4393,6 +4544,8 @@ typedef enum mobj_type
 	// FHZ
 	MT_FHZICE1,
 	MT_FHZICE2,
+	MT_ROSY,
+	MT_CDLHRT,
 
 	// Halloween Scenery
 	// Pumpkins
@@ -4521,6 +4674,7 @@ typedef enum mobj_type
 	MT_RAIN, // Rain
 	MT_SNOWFLAKE, // Snowflake
 	MT_SPLISH, // Water splish!
+	MT_LAVASPLISH, // Lava splish!
 	MT_SMOKE,
 	MT_SMALLBUBBLE, // small bubble
 	MT_MEDIUMBUBBLE, // medium bubble
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index a69e8a188d0030a2893ce4e4eac8237130edafc9..8f173e32ede8f26cda15a3fb1a91a048690601ac 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -816,15 +816,12 @@ static int lib_pCheckDeathPitCollide(lua_State *L)
 
 static int lib_pCheckSolidLava(lua_State *L)
 {
-	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
 	ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
 	//HUDSAFE
 	INLEVEL
-	if (!mo)
-		return LUA_ErrInvalid(L, "mobj_t");
 	if (!rover)
 		return LUA_ErrInvalid(L, "ffloor_t");
-	lua_pushboolean(L, P_CheckSolidLava(mo, rover));
+	lua_pushboolean(L, P_CheckSolidLava(rover));
 	return 1;
 }
 
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 45e116c344243ec23a78f05eb1d0581160747d52..37b1f3e0663e90882e5a5a3ec78fec4c925c05f7 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -50,6 +50,7 @@ enum hook {
 	hook_FollowMobj,
 	hook_PlayerCanDamage,
 	hook_PlayerQuit,
+	hook_IntermissionThinker,
 
 	hook_MAX // last hook
 };
@@ -91,5 +92,6 @@ boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnM
 boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAfterThink Smiles mobj-following
 UINT8 LUAh_PlayerCanDamage(player_t *player, mobj_t *mobj); // Hook for P_PlayerCanDamage
 void LUAh_PlayerQuit(player_t *plr, int reason); // Hook for player quitting
+void LUAh_IntermissionThinker(void); // Hook for Y_Ticker
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 7f7e8adc672d19dcb6276bcff0075bc51f56bfb8..03c7ce9113094ef6425a560bdac2ed9410cb8c76 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -61,6 +61,7 @@ const char *const hookNames[hook_MAX+1] = {
 	"FollowMobj",
 	"PlayerCanDamage",
 	"PlayerQuit",
+	"IntermissionThinker",
 	NULL
 };
 
@@ -1322,4 +1323,27 @@ void LUAh_PlayerQuit(player_t *plr, int reason)
 	lua_settop(gL, 0);
 }
 
+// Hook for Y_Ticker
+void LUAh_IntermissionThinker(void)
+{
+	hook_p hookp;
+	if (!gL || !(hooksAvailable[hook_IntermissionThinker/8] & (1<<(hook_IntermissionThinker%8))))
+		return;
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_IntermissionThinker)
+			continue;
+
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		if (lua_pcall(gL, 0, 0, 0)) {
+			if (!hookp->error || cv_debug & DBG_LUA)
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+			lua_pop(gL, 1);
+			hookp->error = true;
+		}
+	}
+}
+
 #endif
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 8c1134bca7c942ed4fe18398570e0713ec9fe686..865b61e8fcb6cd1206beb4b8d4485dea99cf59c4 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -637,6 +637,68 @@ static int libd_drawString(lua_State *L)
 	return 0;
 }
 
+static int libd_drawNameTag(lua_State *L)
+{
+	INT32 x;
+	INT32 y;
+	const char *str;
+	INT32 flags;
+	UINT8 basecolor;
+	UINT8 outlinecolor;
+	UINT8 *basecolormap = NULL;
+	UINT8 *outlinecolormap = NULL;
+
+	HUDONLY
+
+	x = luaL_checkinteger(L, 1);
+	y = luaL_checkinteger(L, 2);
+	str = luaL_checkstring(L, 3);
+	flags = luaL_optinteger(L, 4, 0);
+	basecolor = luaL_optinteger(L, 5, SKINCOLOR_BLUE);
+	outlinecolor = luaL_optinteger(L, 6, SKINCOLOR_ORANGE);
+	if (basecolor != SKINCOLOR_NONE)
+		basecolormap = R_GetTranslationColormap(TC_DEFAULT, basecolor, GTC_CACHE);
+	if (outlinecolor != SKINCOLOR_NONE)
+		outlinecolormap = R_GetTranslationColormap(TC_DEFAULT, outlinecolor, GTC_CACHE);
+
+	flags &= ~V_PARAMMASK; // Don't let crashes happen.
+	V_DrawNameTag(x, y, flags, FRACUNIT, basecolormap, outlinecolormap, str);
+	return 0;
+}
+
+static int libd_drawScaledNameTag(lua_State *L)
+{
+	fixed_t x;
+	fixed_t y;
+	const char *str;
+	INT32 flags;
+	fixed_t scale;
+	UINT8 basecolor;
+	UINT8 outlinecolor;
+	UINT8 *basecolormap = NULL;
+	UINT8 *outlinecolormap = NULL;
+
+	HUDONLY
+
+	x = luaL_checkfixed(L, 1);
+	y = luaL_checkfixed(L, 2);
+	str = luaL_checkstring(L, 3);
+	flags = luaL_optinteger(L, 4, 0);
+	scale = luaL_optinteger(L, 5, FRACUNIT);
+	if (scale < 0)
+		return luaL_error(L, "negative scale");
+	basecolor = luaL_optinteger(L, 6, SKINCOLOR_BLUE);
+	outlinecolor = luaL_optinteger(L, 7, SKINCOLOR_ORANGE);
+	if (basecolor != SKINCOLOR_NONE)
+		basecolormap = R_GetTranslationColormap(TC_DEFAULT, basecolor, GTC_CACHE);
+	if (outlinecolor != SKINCOLOR_NONE)
+		outlinecolormap = R_GetTranslationColormap(TC_DEFAULT, outlinecolor, GTC_CACHE);
+
+	flags &= ~V_PARAMMASK; // Don't let crashes happen.
+	V_DrawNameTag(FixedInt(x), FixedInt(y), flags, scale, basecolormap, outlinecolormap, str);
+	return 0;
+}
+
 static int libd_stringWidth(lua_State *L)
 {
 	const char *str = luaL_checkstring(L, 1);
@@ -659,6 +721,13 @@ static int libd_stringWidth(lua_State *L)
 	return 1;
 }
 
+static int libd_nameTagWidth(lua_State *L)
+{
+	HUDONLY
+	lua_pushinteger(L, V_NameTagWidth(luaL_checkstring(L, 1)));
+	return 1;
+}
+
 static int libd_getColormap(lua_State *L)
 {
 	INT32 skinnum = TC_DEFAULT;
@@ -837,9 +906,12 @@ static luaL_Reg lib_draw[] = {
 	{"drawPaddedNum", libd_drawPaddedNum},
 	{"drawFill", libd_drawFill},
 	{"drawString", libd_drawString},
+	{"drawNameTag", libd_drawNameTag},
+	{"drawScaledNameTag", libd_drawScaledNameTag},
 	{"fadeScreen", libd_fadeScreen},
 	// misc
 	{"stringWidth", libd_stringWidth},
+	{"nameTagWidth", libd_nameTagWidth},
 	// m_random
 	{"RandomFixed",libd_RandomFixed},
 	{"RandomByte",libd_RandomByte},
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index dbb69b7e26dd4129600a7915a7a8426a6cd23c54..1da232efa0e07bd9899d45333f05fa78aa564c63 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -411,37 +411,53 @@ static int sector_iterate(lua_State *L)
 
 // sector.lines, i -> sector.lines[i]
 // sector.lines.valid, for validity checking
+//
+// 25/9/19 Monster Iestyn
+// Modified this and _num to use triple pointers, to allow for a new hack of mine involving offsetof
+// this way we don't need to check frontsector or backsector of line #0 in the array
+//
 static int sectorlines_get(lua_State *L)
 {
-	line_t **seclines = *((line_t ***)luaL_checkudata(L, 1, META_SECTORLINES));
+	line_t ***seclines = *((line_t ****)luaL_checkudata(L, 1, META_SECTORLINES));
 	size_t i;
 	size_t numoflines = 0;
 	lua_settop(L, 2);
 	if (!lua_isnumber(L, 2))
 	{
 		int field = luaL_checkoption(L, 2, NULL, valid_opt);
-		if (!seclines)
+		if (!seclines || !(*seclines))
 		{
 			if (field == 0) {
 				lua_pushboolean(L, 0);
 				return 1;
 			}
-			return luaL_error(L, "accessed sector_t doesn't exist anymore.");
+			return luaL_error(L, "accessed sector_t.lines doesn't exist anymore.");
 		} else if (field == 0) {
 			lua_pushboolean(L, 1);
 			return 1;
 		}
 	}
 
+/* a snip from sector_t struct in r_defs.h, for reference
+	size_t linecount;
+	struct line_s **lines; // [linecount] size
+*/
+	// get the "linecount" by shifting our retrieved memory address of "lines" to where "linecount" is in the sector_t, then dereferencing the result
+	// we need this to determine the array's actual size, and therefore also the maximum value allowed as an index
+	// this only works if seclines is actually a pointer to a sector's lines member in memory, oh boy
+	numoflines = (size_t)(*(seclines - (offsetof(sector_t, lines) - offsetof(sector_t, linecount))));
+
+/* OLD HACK
 	// check first linedef to figure which of its sectors owns this sector->lines pointer
 	// then check that sector's linecount to get a maximum index
-	//if (!seclines[0])
+	//if (!(*seclines)[0])
 		//return luaL_error(L, "no lines found!"); // no first linedef?????
-	if (seclines[0]->frontsector->lines == seclines)
-		numoflines = seclines[0]->frontsector->linecount;
-	else if (seclines[0]->backsector && seclines[0]->backsector->lines == seclines) // check backsector exists first
-		numoflines = seclines[0]->backsector->linecount;
+	if ((*seclines)[0]->frontsector->lines == *seclines)
+		numoflines = (*seclines)[0]->frontsector->linecount;
+	else if ((*seclines)[0]->backsector && *seclines[0]->backsector->lines == *seclines) // check backsector exists first
+		numoflines = (*seclines)[0]->backsector->linecount;
 	//if neither sector has it then ???
+*/
 
 	if (!numoflines)
 		return luaL_error(L, "no lines found!");
@@ -449,23 +465,21 @@ static int sectorlines_get(lua_State *L)
 	i = (size_t)lua_tointeger(L, 2);
 	if (i >= numoflines)
 		return 0;
-	LUA_PushUserdata(L, seclines[i], META_LINE);
+	LUA_PushUserdata(L, (*seclines)[i], META_LINE);
 	return 1;
 }
 
+// #(sector.lines) -> sector.linecount
 static int sectorlines_num(lua_State *L)
 {
-	line_t **seclines = *((line_t ***)luaL_checkudata(L, 1, META_SECTORLINES));
+	line_t ***seclines = *((line_t ****)luaL_checkudata(L, 1, META_SECTORLINES));
 	size_t numoflines = 0;
-	// check first linedef to figure which of its sectors owns this sector->lines pointer
-	// then check that sector's linecount to get a maximum index
-	//if (!seclines[0])
-		//return luaL_error(L, "no lines found!"); // no first linedef?????
-	if (seclines[0]->frontsector->lines == seclines)
-		numoflines = seclines[0]->frontsector->linecount;
-	else if (seclines[0]->backsector && seclines[0]->backsector->lines == seclines) // check backsector exists first
-		numoflines = seclines[0]->backsector->linecount;
-	//if neither sector has it then ???
+
+	if (!seclines || !(*seclines))
+		return luaL_error(L, "accessed sector_t.lines doesn't exist anymore.");
+
+	// see comments in the _get function above
+	numoflines = (size_t)(*(seclines - (offsetof(sector_t, lines) - offsetof(sector_t, linecount))));
 	lua_pushinteger(L, numoflines);
 	return 1;
 }
@@ -543,7 +557,7 @@ static int sector_get(lua_State *L)
 		LUA_PushUserdata(L, &sectors[sector->camsec], META_SECTOR);
 		return 1;
 	case sector_lines: // lines
-		LUA_PushUserdata(L, sector->lines, META_SECTORLINES);
+		LUA_PushUserdata(L, &sector->lines, META_SECTORLINES); // push the address of the "lines" member in the struct, to allow our hacks in sectorlines_get/_num to work
 		return 1;
 	case sector_ffloors: // ffloors
 		lua_pushcfunction(L, lib_iterateSectorFFloors);
@@ -579,6 +593,7 @@ static int sector_set(lua_State *L)
 	case sector_thinglist: // thinglist
 	case sector_heightsec: // heightsec
 	case sector_camsec: // camsec
+	case sector_lines: // lines
 	case sector_ffloors: // ffloors
 #ifdef ESLOPE
 	case sector_fslope: // f_slope
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 063158b263b58b4f4a6b7764a6c3075a4e72867c..30026da49bdf2c4d8d4e5ad9b045e00615d10b2d 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -164,6 +164,8 @@ 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 (field == mobj_valid) {
 			lua_pushboolean(L, 0);
@@ -409,6 +411,8 @@ static int mobj_set(lua_State *L)
 	enum mobj_e field = Lua_optoption(L, 2, mobj_opt[0], mobj_opt);
 	lua_settop(L, 3);
 
+	INLEVEL
+
 	if (!mo)
 		return LUA_ErrInvalid(L, "mobj_t");
 
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index dd9959afbdf3300bc150438e2e5cedce2f40d298..b1222ce672fbc64c4487b6c46cb31498a9de7944 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -25,7 +25,6 @@
 static int lib_iteratePlayers(lua_State *L)
 {
 	INT32 i = -1;
-	INLEVEL
 	if (lua_gettop(L) < 2)
 	{
 		//return luaL_error(L, "Don't call players.iterate() directly, use it as 'for player in players.iterate do <block> end'.");
@@ -52,7 +51,6 @@ static int lib_getPlayer(lua_State *L)
 {
 	const char *field;
 	// i -> players[i]
-	INLEVEL
 	if (lua_type(L, 2) == LUA_TNUMBER)
 	{
 		lua_Integer i = luaL_checkinteger(L, 2);
diff --git a/src/lua_script.c b/src/lua_script.c
index deb644dc0351c169c60cb859952627278538241c..ec73d7bf70136762eeec08edda22ccdf9ac762d4 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -431,7 +431,7 @@ void LUA_InvalidateLevel(void)
 	for (i = 0; i < numsectors; i++)
 	{
 		LUA_InvalidateUserdata(&sectors[i]);
-		LUA_InvalidateUserdata(sectors[i].lines);
+		LUA_InvalidateUserdata(&sectors[i].lines);
 		if (sectors[i].ffloors)
 		{
 			for (rover = sectors[i].ffloors; rover; rover = rover->next)
@@ -1121,7 +1121,7 @@ void LUA_Archive(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (!playeringame[i])
+		if (!playeringame[i] && i > 0) // dedicated servers...
 			continue;
 		// all players in game will be archived, even if they just add a 0.
 		ArchiveExtVars(&players[i], "player");
@@ -1157,7 +1157,7 @@ void LUA_UnArchive(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (!playeringame[i])
+		if (!playeringame[i] && i > 0) // dedicated servers...
 			continue;
 		UnArchiveExtVars(&players[i]);
 	}
diff --git a/src/m_cond.c b/src/m_cond.c
index 539c6d1f6d1fc38efacad7d1e56329a323010afb..b7520aba78e1be64ef810e46bfbb58c3d593b155 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -528,12 +528,22 @@ skincolors_t M_GetEmblemColor(emblem_t *em)
 	return em->color;
 }
 
-const char *M_GetEmblemPatch(emblem_t *em)
+const char *M_GetEmblemPatch(emblem_t *em, boolean big)
 {
-	static char pnamebuf[7] = "GOTITn";
+	static char pnamebuf[7];
+
+	if (!big)
+		strcpy(pnamebuf, "GOTITn");
+	else
+		strcpy(pnamebuf, "EMBMn0");
 
 	I_Assert(em->sprite >= 'A' && em->sprite <= 'Z');
-	pnamebuf[5] = em->sprite;
+
+	if (!big)
+		pnamebuf[5] = em->sprite;
+	else
+		pnamebuf[4] = em->sprite;
+
 	return pnamebuf;
 }
 
@@ -544,11 +554,21 @@ skincolors_t M_GetExtraEmblemColor(extraemblem_t *em)
 	return em->color;
 }
 
-const char *M_GetExtraEmblemPatch(extraemblem_t *em)
+const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big)
 {
-	static char pnamebuf[7] = "GOTITn";
+	static char pnamebuf[7];
+
+	if (!big)
+		strcpy(pnamebuf, "GOTITn");
+	else
+		strcpy(pnamebuf, "EMBMn0");
 
 	I_Assert(em->sprite >= 'A' && em->sprite <= 'Z');
-	pnamebuf[5] = em->sprite;
+
+	if (!big)
+		pnamebuf[5] = em->sprite;
+	else
+		pnamebuf[4] = em->sprite;
+
 	return pnamebuf;
 }
diff --git a/src/m_cond.h b/src/m_cond.h
index f82e49372ed0120da76cdd2bd8d8c0a7f2a128c5..e9859cf11552125e40fdfffba8b57bb2f5497386 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -171,9 +171,9 @@ INT32 M_CountEmblems(void);
 // Emblem shit
 emblem_t *M_GetLevelEmblems(INT32 mapnum);
 skincolors_t M_GetEmblemColor(emblem_t *em);
-const char *M_GetEmblemPatch(emblem_t *em);
+const char *M_GetEmblemPatch(emblem_t *em, boolean big);
 skincolors_t M_GetExtraEmblemColor(extraemblem_t *em);
-const char *M_GetExtraEmblemPatch(extraemblem_t *em);
+const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big);
 
 // If you're looking to compare stats for unlocks or what not, use these
 // They stop checking upon reaching the target number so they
diff --git a/src/m_menu.c b/src/m_menu.c
index 1ab361b80a5aa2ba1039f796af93bf2364d78855..8fe782cac20fad747cc878b73f25432928f65b17 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -127,7 +127,6 @@ const char *quitmsg[NUM_QUITMESSAGES];
 description_t description[MAXSKINS];
 INT16 char_on = -1, startchar = 0;
 static char *char_notes = NULL;
-static fixed_t char_scroll = 0;
 
 boolean menuactive = false;
 boolean fromlevelselect = false;
@@ -167,6 +166,16 @@ static INT32 vidm_selected = 0;
 static INT32 vidm_nummodes;
 static INT32 vidm_column_size;
 
+// new menus
+static tic_t recatkdrawtimer = 0;
+static tic_t ntsatkdrawtimer = 0;
+
+static tic_t charseltimer = 0;
+static fixed_t char_scroll = 0;
+#define charscrollamt 128*FRACUNIT
+
+static tic_t keydown = 0;
+
 //
 // PROTOTYPES
 //
@@ -247,6 +256,7 @@ menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
 // Single Player
 static void M_StartTutorial(INT32 choice);
 static void M_LoadGame(INT32 choice);
+static void M_HandleTimeAttackLevelSelect(INT32 choice);
 static void M_TimeAttackLevelSelect(INT32 choice);
 static void M_TimeAttack(INT32 choice);
 static void M_NightsAttackLevelSelect(INT32 choice);
@@ -386,9 +396,12 @@ static void Dummymares_OnChange(void);
 // CONSOLE VARIABLES AND THEIR POSSIBLE VALUES GO HERE.
 // ==========================================================================
 
+consvar_t cv_showfocuslost = {"showfocuslost", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
+
 static CV_PossibleValue_t map_cons_t[] = {
 	{1,"MIN"},
-	{NUMMAPS, "MAX"}
+	{NUMMAPS, "MAX"},
+	{0,NULL}
 };
 consvar_t cv_nextmap = {"nextmap", "1", CV_HIDEN|CV_CALL, map_cons_t, Nextmap_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
@@ -406,7 +419,7 @@ static CV_PossibleValue_t serversort_cons_t[] = {
 	{1,"Modified State"},
 	{2,"Most Players"},
 	{3,"Least Players"},
-	{4,"Max Players"},
+	{4,"Max Player Slots"},
 	{5,"Gametype"},
 	{0,NULL}
 };
@@ -433,7 +446,8 @@ consvar_t cv_ghost_guest     = {"ghost_guest",     "Show", CV_SAVE, ghost2_cons_
 static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2, "Blue"}, {0, NULL}};
 static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}};
 static CV_PossibleValue_t ringlimit_cons_t[] = {{0, "MIN"}, {9999, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t liveslimit_cons_t[] = {{-1, "MIN"}, {99, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t liveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {-1, "Infinite"}, {0, NULL}};
+static CV_PossibleValue_t contlimit_cons_t[] = {{0, "MIN"}, {99, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t dummymares_cons_t[] = {
 	{-1, "END"}, {0,"Overall"}, {1,"Mare 1"}, {2,"Mare 2"}, {3,"Mare 3"}, {4,"Mare 4"}, {5,"Mare 5"}, {6,"Mare 6"}, {7,"Mare 7"}, {8,"Mare 8"}, {0,NULL}
 };
@@ -442,7 +456,7 @@ static consvar_t cv_dummyteam = {"dummyteam", "Spectator", CV_HIDEN, dummyteam_c
 static consvar_t cv_dummyscramble = {"dummyscramble", "Random", CV_HIDEN, dummyscramble_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static consvar_t cv_dummyrings = {"dummyrings", "0", CV_HIDEN, ringlimit_cons_t,	NULL, 0, NULL, NULL, 0, 0, NULL};
 static consvar_t cv_dummylives = {"dummylives", "0", CV_HIDEN, liveslimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static consvar_t cv_dummycontinues = {"dummycontinues", "0", CV_HIDEN, liveslimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static consvar_t cv_dummycontinues = {"dummycontinues", "0", CV_HIDEN, contlimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 // ==========================================================================
@@ -743,8 +757,8 @@ static menuitem_t SP_TimeAttackLevelSelectMenu[] =
 // Single Player Time Attack
 static menuitem_t SP_TimeAttackMenu[] =
 {
-	{IT_STRING|IT_CALL,        NULL, "Level Select...", &M_TimeAttackLevelSelect,   52},
-	{IT_STRING|IT_CVAR,        NULL, "Character",       &cv_chooseskin,             62},
+	{IT_STRING|IT_KEYHANDLER,  NULL, "Level Select...", M_HandleTimeAttackLevelSelect,   62},
+	{IT_STRING|IT_CVAR,        NULL, "Character",       &cv_chooseskin,             72},
 
 	{IT_DISABLED,              NULL, "Guest Option...", &SP_GuestReplayDef, 100},
 	{IT_DISABLED,              NULL, "Replay...",       &SP_ReplayDef,      110},
@@ -1224,11 +1238,12 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_HEADER, NULL, "Level", NULL, 155},
 	{IT_STRING | IT_CVAR, NULL, "Draw Distance",             &cv_drawdist,        161},
 	{IT_STRING | IT_CVAR, NULL, "Weather Draw Dist.",        &cv_drawdist_precip, 166},
-	{IT_STRING | IT_CVAR, NULL, "NiGHTS mode Draw Dist.",    &cv_drawdist_nights, 171},
+	{IT_STRING | IT_CVAR, NULL, "NiGHTS Hoop Draw Dist.",    &cv_drawdist_nights, 171},
 
 	{IT_HEADER, NULL, "Diagnostic", NULL, 180},
 	{IT_STRING | IT_CVAR, NULL, "Show FPS",                  &cv_ticrate,         186},
 	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw",       &cv_homremoval,      191},
+	{IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"",       &cv_showfocuslost,   196},
 };
 
 static menuitem_t OP_VideoModeMenu[] =
@@ -1322,22 +1337,25 @@ static menuitem_t OP_OpenGLColorMenu[] =
 
 static menuitem_t OP_SoundOptionsMenu[] =
 {
-	{IT_HEADER, NULL, "Game Audio", NULL, 0}, // 0 // ScrollMenu offsets
-	{IT_STRING | IT_CVAR,  NULL,  "Sound Effects", &cv_gamesounds, 13}, // 6
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 23}, // 11
+	{IT_HEADER, NULL, "Game Audio", NULL, 0},
+	{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, 21},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  26},
 
-	{IT_STRING | IT_CVAR,  NULL,  "Digital Music", &cv_gamedigimusic, 43}, // 21
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  53}, // 26
+	{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,  "MIDI Music", &cv_gamemidimusic, 73}, // 36
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 83}, // 41
+	{IT_HEADER, NULL, "Accessibility", NULL, 50},
+	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 56},
+	{IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 61},
 
-	{IT_HEADER, NULL, "Accessibility", NULL, 103}, // 50
-	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 115}, // 56
-	{IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 125}, // 62
+	{IT_STRING | IT_CVAR, NULL, "Play Sound Effects if Unfocused", &cv_playsoundsifunfocused, 71},
+	{IT_STRING | IT_CVAR, NULL, "Play Music if Unfocused", &cv_playmusicifunfocused, 76},
 
 #ifdef HAVE_MIXERX
-	{IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 143},
+	{IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 94},
 #endif
 };
 
@@ -1960,18 +1978,9 @@ menu_t OP_ColorOptionsDef =
 	0,
 	NULL
 };
-menu_t OP_SoundOptionsDef =
-{
+menu_t OP_SoundOptionsDef = DEFAULTSCROLLMENUSTYLE(
 	MN_OP_MAIN + (MN_OP_SOUND << 6),
-	"M_SOUND",
-	sizeof (OP_SoundOptionsMenu)/sizeof (menuitem_t),
-	&OP_MainDef,
-	OP_SoundOptionsMenu,
-	M_DrawGenericMenu,
-	30, 30,
-	0,
-	NULL
-};
+	"M_SOUND", OP_SoundOptionsMenu, &OP_MainDef, 30, 30);
 #ifdef HAVE_MIXERX
 menu_t OP_SoundAdvancedDef = DEFAULTMENUSTYLE(MN_OP_MAIN + (MN_OP_SOUND << 6), "M_SOUND", OP_SoundAdvancedMenu, &OP_SoundOptionsDef, 30, 30);
 #endif
@@ -2294,8 +2303,10 @@ void M_InitMenuPresTables(void)
 		{
 			menupres[i].muslooping = true;
 		}
-		if (i == MN_SP_TIMEATTACK || i == MN_SP_NIGHTSATTACK)
-			strncpy(menupres[i].musname, "_inter", 7);
+		if (i == MN_SP_TIMEATTACK)
+			strncpy(menupres[i].musname, "_recat", 7);
+		else if (i == MN_SP_NIGHTSATTACK)
+			strncpy(menupres[i].musname, "_nitat", 7);
 		else if (i == MN_SP_PLAYER)
 			strncpy(menupres[i].musname, "_chsel", 7);
 	}
@@ -2388,7 +2399,7 @@ static boolean MIT_SetCurBackground(UINT32 menutype, INT32 level, INT32 *retval,
 	}
 	else if (menupres[menutype].bgname[0])
 	{
-		strncpy(curbgname, menupres[menutype].bgname, 9);
+		strncpy(curbgname, menupres[menutype].bgname, 8);
 		curbgxspeed = menupres[menutype].titlescrollxspeed != INT32_MAX ? menupres[menutype].titlescrollxspeed : titlescrollxspeed;
 		curbgyspeed = menupres[menutype].titlescrollyspeed != INT32_MAX ? menupres[menutype].titlescrollyspeed : titlescrollyspeed;
 		return true;
@@ -2506,7 +2517,7 @@ void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping)
 
 void M_SetMenuCurBackground(const char *defaultname)
 {
-	char name[8];
+	char name[9];
 	strncpy(name, defaultname, 8);
 	M_IterateMenuTree(MIT_SetCurBackground, &name);
 }
@@ -2572,8 +2583,6 @@ static void M_HandleMenuPresState(menu_t *newMenu)
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)
 		return;
 
-	// Find current presentation values
-	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "SRB2BACK" : "TITLESKY");
 	M_SetMenuCurFadeValue(16);
 	M_SetMenuCurHideTitlePics();
 
@@ -2786,31 +2795,19 @@ static void M_ChangeCvar(INT32 choice)
 
 	choice = (choice<<1) - 1;
 
-	if (((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_SLIDER)
-	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER)
-	    ||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD))
+	if (cv->flags & CV_FLOAT)
 	{
-		if (cv->flags & CV_FLOAT && (currentMenu->menuitems[itemOn].status & IT_CV_FLOATSLIDER) == IT_CV_FLOATSLIDER)
+		if (((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_SLIDER)
+			||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_INVISSLIDER)
+			||((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_NOMOD)
+			|| !(currentMenu->menuitems[itemOn].status & IT_CV_INTEGERSTEP))
 		{
 			char s[20];
 			sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
 			CV_Set(cv,s);
 		}
 		else
-			CV_SetValue(cv,cv->value+(choice));
-	}
-	else if (cv->flags & CV_FLOAT)
-	{
-		if (currentMenu->menuitems[itemOn].status & IT_CV_INTEGERSTEP)
-		{
 			CV_SetValue(cv,FIXED_TO_FLOAT(cv->value)+(choice));
-		}
-		else
-		{
-			char s[20];
-			sprintf(s,"%f",FIXED_TO_FLOAT(cv->value)+(choice)*(1.0f/16.0f));
-			CV_Set(cv,s);
-		}
 	}
 	else
 		CV_AddValue(cv,choice);
@@ -2895,6 +2892,15 @@ static void M_PrevOpt(void)
 // (in other words -- stop bullshit happening by mashing buttons in fades)
 static boolean noFurtherInput = false;
 
+static void Command_Manual_f(void)
+{
+	if (modeattacking)
+		return;
+	M_StartControlPanel();
+	currentMenu = &MISC_HelpDef;
+	itemOn = 0;
+}
+
 //
 // M_Responder
 //
@@ -2923,6 +2929,7 @@ boolean M_Responder(event_t *ev)
 	{
 		if (ev->type == ev_keydown)
 		{
+			keydown++;
 			ch = ev->data1;
 
 			// added 5-2-98 remap virtual keys (mouse & joystick buttons)
@@ -3029,6 +3036,8 @@ boolean M_Responder(event_t *ev)
 				pmousex = lastx += 30;
 			}
 		}
+		else if (ev->type == ev_keyup) // Preserve event for other responders
+			keydown = 0;
 	}
 	else if (ev->type == ev_keydown) // Preserve event for other responders
 		ch = ev->data1;
@@ -3045,11 +3054,7 @@ boolean M_Responder(event_t *ev)
 		switch (ch)
 		{
 			case KEY_F1: // Help key
-				if (modeattacking)
-					return true;
-				M_StartControlPanel();
-				currentMenu = &MISC_HelpDef;
-				itemOn = 0;
+				Command_Manual_f();
 				return true;
 
 			case KEY_F2: // Empty
@@ -3321,7 +3326,7 @@ void M_Drawer(void)
 	}
 
 	// focus lost notification goes on top of everything, even the former everything
-	if (window_notinfocus)
+	if (window_notinfocus && cv_showfocuslost.value)
 	{
 		M_DrawTextBox((BASEVIDWIDTH/2) - (60), (BASEVIDHEIGHT/2) - (16), 13, 2);
 		if (gamestate == GS_LEVEL && (P_AutoPause() || paused))
@@ -3476,6 +3481,7 @@ void M_ClearMenus(boolean callexitmenufunc)
 	if (currentMenu == &MessageDef) // Oh sod off!
 		currentMenu = &MainDef; // Not like it matters
 	menuactive = false;
+	hidetitlemap = false;
 }
 
 //
@@ -3514,6 +3520,8 @@ void M_SetupNextMenu(menu_t *menudef)
 			}
 		}
 	}
+
+	hidetitlemap = false;
 }
 
 //
@@ -3546,6 +3554,8 @@ void M_Init(void)
 {
 	int i;
 
+	COM_AddCommand("manual", Command_Manual_f);
+
 	CV_RegisterVar(&cv_nextmap);
 	CV_RegisterVar(&cv_newgametype);
 	CV_RegisterVar(&cv_chooseskin);
@@ -3621,9 +3631,13 @@ void M_InitCharacterTables(void)
 		description[i].used = false;
 		strcpy(description[i].notes, "???");
 		strcpy(description[i].picname, "");
+		strcpy(description[i].nametag, "");
 		strcpy(description[i].skinname, "");
+		strcpy(description[i].displayname, "");
 		description[i].prev = description[i].next = 0;
-		description[i].pic = NULL;
+		description[i].charpic = NULL;
+		description[i].namepic = NULL;
+		description[i].oppositecolor = description[i].tagtextcolor = description[i].tagoutlinecolor = 0;
 	}
 }
 
@@ -3886,7 +3900,7 @@ static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y)
 		lasttype = curtype;
 
 		if (emblem->collected)
-			V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem), PU_CACHE),
+			V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE),
 			                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 		else
 			V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE));
@@ -4325,7 +4339,7 @@ static void M_DrawPauseMenu(void)
 				continue;
 
 			if (emblem->collected)
-				V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem), PU_CACHE),
+				V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 			else
 				V_DrawSmallScaledPatch(40, 44 + (i*8), 0, W_CachePatchName("NEEDIT", PU_CACHE));
@@ -4545,10 +4559,12 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 			if (!(mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
 				return true;
 
-			if (mapvisited[mapnum]) // MV_MP
+			if (mapnum+1 == spstage_start)
 				return true;
 
-			if (mapnum+1 == spstage_start)
+#ifndef DEVELOP
+			if (mapvisited[mapnum]) // MV_MP
+#endif
 				return true;
 
 			/* FALLTHRU */
@@ -4894,13 +4910,25 @@ static void M_HandleLevelPlatter(INT32 choice)
 {
 	boolean exitmenu = false;  // exit to previous menu
 	INT32 selectval;
+	UINT8 iter;
 
 	switch (choice)
 	{
 		case KEY_DOWNARROW:
+			if (lsrow == levelselect.numrows-1)
+			{
+				if (levelselect.numrows < 3)
+				{
+					if (!lsoffs[0]) // prevent sound spam
+					{
+						lsoffs[0] = -8;
+						S_StartSound(NULL,sfx_s3kb7);
+					}
+					return;
+				}
+				lsrow = UINT8_MAX;
+			}
 			lsrow++;
-			if (lsrow == levelselect.numrows)
-				lsrow = 0;
 
 			lsoffs[0] = lsvseperation(lsrow);
 
@@ -4914,17 +4942,29 @@ static void M_HandleLevelPlatter(INT32 choice)
 			break;
 
 		case KEY_UPARROW:
-			lsoffs[0] = -lsvseperation(lsrow);
-
+			iter = lsrow;
+			if (!lsrow)
+			{
+				if (levelselect.numrows < 3)
+				{
+					if (!lsoffs[0]) // prevent sound spam
+					{
+						lsoffs[0] = 8;
+						S_StartSound(NULL,sfx_s3kb7);
+					}
+					return;
+				}
+				lsrow = levelselect.numrows;
+			}
 			lsrow--;
-			if (lsrow == UINT8_MAX)
-				lsrow = levelselect.numrows-1;
+
+			lsoffs[0] = -lsvseperation(iter);
 
 			if (levelselect.rows[lsrow].header[0])
 				lshli = lsrow;
 			else
 			{
-				UINT8 iter = lsrow;
+				iter = lsrow;
 				do
 					iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
 				while ((iter != lsrow) && !(levelselect.rows[iter].header[0]));
@@ -4957,7 +4997,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 						M_LevelSelectWarp(0);
 					Nextmap_OnChange();
 				}
-				else if (!lsoffs[0]) //  prevent sound spam
+				else if (!lsoffs[0]) // prevent sound spam
 				{
 					lsoffs[0] = -8;
 					S_StartSound(NULL,sfx_s3kb2);
@@ -4987,7 +5027,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 
 				ifselectvalnextmap(lscol) else ifselectvalnextmap(0)
 			}
-			else if (!lsoffs[1]) //  prevent sound spam
+			else if (!lsoffs[1]) // prevent sound spam
 			{
 				lsoffs[1] = 8;
 				S_StartSound(NULL,sfx_s3kb7);
@@ -5016,7 +5056,7 @@ static void M_HandleLevelPlatter(INT32 choice)
 
 				ifselectvalnextmap(lscol) else ifselectvalnextmap(0)
 			}
-			else if (!lsoffs[1]) //  prevent sound spam
+			else if (!lsoffs[1]) // prevent sound spam
 			{
 				lsoffs[1] = -8;
 				S_StartSound(NULL,sfx_s3kb7);
@@ -5168,18 +5208,187 @@ static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 	}
 }
 
+// new menus
+static void M_DrawRecordAttackForeground(void)
+{
+	patch_t *fg = W_CachePatchName("RECATKFG", PU_CACHE);
+	patch_t *clock = W_CachePatchName("RECCLOCK", PU_CACHE);
+	angle_t fa;
+
+	INT32 i;
+	INT32 height = (SHORT(fg->height)/2);
+	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+
+	for (i = -12; i < (BASEVIDHEIGHT/height) + 12; i++)
+	{
+		INT32 y = ((i*height) - (height - ((recatkdrawtimer*2)%height)));
+		// don't draw above the screen
+		{
+			INT32 sy = FixedMul(y, dupz<<FRACBITS) >> FRACBITS;
+			if (vid.height != BASEVIDHEIGHT * dupz)
+				sy += (vid.height - (BASEVIDHEIGHT * dupz)) / 2;
+			if ((sy+height) < 0)
+				continue;
+		}
+		V_DrawFixedPatch(0, y<<FRACBITS, FRACUNIT/2, V_SNAPTOLEFT, fg, NULL);
+		V_DrawFixedPatch(BASEVIDWIDTH<<FRACBITS, y<<FRACBITS, FRACUNIT/2, V_SNAPTORIGHT|V_FLIP, fg, NULL);
+		// don't draw below the screen
+		if (y > vid.height)
+			break;
+	}
+
+	// draw clock
+	fa = (FixedAngle(((recatkdrawtimer * 4) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK;
+	V_DrawSciencePatch(160<<FRACBITS, (80<<FRACBITS) + (4*FINESINE(fa)), 0, clock, FRACUNIT);
+
+	// Increment timer.
+	recatkdrawtimer++;
+}
+
+// NiGHTS Attack background.
+static void M_DrawNightsAttackMountains(void)
+{
+	static INT32 bgscrollx;
+	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+	patch_t *background = W_CachePatchName(curbgname, PU_CACHE);
+	INT32 x = FixedInt(bgscrollx) % SHORT(background->width);
+	INT32 y = BASEVIDHEIGHT - SHORT(background->height)*2;
+
+	if (vid.height != BASEVIDHEIGHT * dupz)
+		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 158);
+	V_DrawFill(0, y+50, vid.width, BASEVIDHEIGHT, V_SNAPTOLEFT|31);
+
+	V_DrawScaledPatch(x, y, V_SNAPTOLEFT, background);
+	x += SHORT(background->width);
+	if (x < BASEVIDWIDTH)
+		V_DrawScaledPatch(x, y, V_SNAPTOLEFT, background);
+
+	bgscrollx -= (FRACUNIT/2);
+}
+
+// NiGHTS Attack foreground.
+static void M_DrawNightsAttackBackground(void)
+{
+	INT32 x, y = 0;
+	INT32 i;
+
+	// top
+	patch_t *backtopfg = W_CachePatchName("NTSATKT1", PU_CACHE);
+	patch_t *fronttopfg = W_CachePatchName("NTSATKT2", PU_CACHE);
+	INT32 backtopwidth = SHORT(backtopfg->width);
+	//INT32 backtopheight = SHORT(backtopfg->height);
+	INT32 fronttopwidth = SHORT(fronttopfg->width);
+	//INT32 fronttopheight = SHORT(fronttopfg->height);
+
+	// bottom
+	patch_t *backbottomfg = W_CachePatchName("NTSATKB1", PU_CACHE);
+	patch_t *frontbottomfg = W_CachePatchName("NTSATKB2", PU_CACHE);
+	INT32 backbottomwidth = SHORT(backbottomfg->width);
+	INT32 backbottomheight = SHORT(backbottomfg->height);
+	INT32 frontbottomwidth = SHORT(frontbottomfg->width);
+	INT32 frontbottomheight = SHORT(frontbottomfg->height);
+
+	// background
+	M_DrawNightsAttackMountains();
+
+	// back top foreground patch
+	x = -(ntsatkdrawtimer%backtopwidth);
+	V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, backtopfg);
+	for (i = 0; i < 3; i++)
+	{
+		x += (backtopwidth);
+		if (x >= vid.width)
+			break;
+		V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, backtopfg);
+	}
+
+	// front top foreground patch
+	x = -((ntsatkdrawtimer*2)%fronttopwidth);
+	V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, fronttopfg);
+	for (i = 0; i < 3; i++)
+	{
+		x += (fronttopwidth);
+		if (x >= vid.width)
+			break;
+		V_DrawScaledPatch(x, y, V_SNAPTOTOP|V_SNAPTOLEFT, fronttopfg);
+	}
+
+	// back bottom foreground patch
+	x = -(ntsatkdrawtimer%backbottomwidth);
+	y = BASEVIDHEIGHT - backbottomheight;
+	V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, backbottomfg);
+	for (i = 0; i < 3; i++)
+	{
+		x += (backbottomwidth);
+		if (x >= vid.width)
+			break;
+		V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, backbottomfg);
+	}
+
+	// front bottom foreground patch
+	x = -((ntsatkdrawtimer*2)%frontbottomwidth);
+	y = BASEVIDHEIGHT - frontbottomheight;
+	V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, frontbottomfg);
+	for (i = 0; i < 3; i++)
+	{
+		x += (frontbottomwidth);
+		if (x >= vid.width)
+			break;
+		V_DrawScaledPatch(x, y, V_SNAPTOBOTTOM|V_SNAPTOLEFT, frontbottomfg);
+	}
+
+	// Increment timer.
+	ntsatkdrawtimer++;
+}
+
+// NiGHTS Attack floating Super Sonic.
+static patch_t *ntssupersonic[2];
+static void M_DrawNightsAttackSuperSonic(void)
+{
+	const UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE);
+	INT32 timer = (ntsatkdrawtimer/4) % 2;
+	angle_t fa = (FixedAngle(((ntsatkdrawtimer * 4) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK;
+	V_DrawFixedPatch(235<<FRACBITS, (120<<FRACBITS) - (8*FINESINE(fa)), FRACUNIT, 0, ntssupersonic[timer], colormap);
+}
+
 static void M_DrawLevelPlatterMenu(void)
 {
 	UINT8 iter = lsrow, sizeselect = (lswide(lsrow) ? 1 : 0);
 	INT32 y = lsbasey + lsoffs[0] - getheadingoffset(lsrow);
 	const INT32 cursorx = (sizeselect ? 0 : (lscol*lshseperation));
 
-	if (gamestate == GS_TIMEATTACK)
+	if (currentMenu->prevMenu == &SP_TimeAttackDef)
 	{
+		M_SetMenuCurBackground("RECATKBG");
+
+		curbgxspeed = 0;
+		curbgyspeed = 18;
+
 		if (curbgcolor >= 0)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 		else if (!curbghide || !titlemapinaction)
+		{
 			F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+			// Draw and animate foreground
+			if (!strncmp("RECATKBG", curbgname, 8))
+				M_DrawRecordAttackForeground();
+		}
+
+		if (curfadevalue)
+			V_DrawFadeScreen(0xFF00, curfadevalue);
+	}
+
+	if (currentMenu->prevMenu == &SP_NightsAttackDef)
+	{
+		M_SetMenuCurBackground("NTSATKBG");
+
+		if (curbgcolor >= 0)
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
+		else if (!curbghide || !titlemapinaction)
+		{
+			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 158);
+			M_DrawNightsAttackMountains();
+		}
 		if (curfadevalue)
 			V_DrawFadeScreen(0xFF00, curfadevalue);
 	}
@@ -5187,7 +5396,13 @@ static void M_DrawLevelPlatterMenu(void)
 	// finds row at top of the screen
 	while (y > -8)
 	{
-		iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
+		if (iter == 0)
+		{
+			if (levelselect.numrows < 3)
+				break;
+			iter = levelselect.numrows;
+		}
+		iter--;
 		y -= lsvseperation(iter);
 	}
 
@@ -5196,7 +5411,13 @@ static void M_DrawLevelPlatterMenu(void)
 	{
 		M_DrawLevelPlatterRow(iter, y);
 		y += lsvseperation(iter);
-		iter = ((iter == levelselect.numrows-1) ? 0 : iter+1);
+		if (iter == levelselect.numrows-1)
+		{
+			if (levelselect.numrows < 3)
+				break;
+			iter = UINT8_MAX;
+		}
+		iter++;
 	}
 
 	// draw cursor box
@@ -5388,7 +5609,19 @@ static void M_DrawMessageMenu(void)
 		if (curbgcolor >= 0)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 		else if (!curbghide || !titlemapinaction)
-			F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		{
+			if (levellistmode == LLM_NIGHTSATTACK)
+			{
+				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 158);
+				M_DrawNightsAttackMountains();
+			}
+			else
+			{
+				F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+				if (!strncmp("RECATKBG", curbgname, 8))
+					M_DrawRecordAttackForeground();
+			}
+		}
 		if (curfadevalue)
 			V_DrawFadeScreen(0xFF00, curfadevalue);
 	}
@@ -6080,9 +6313,9 @@ static void M_PandorasBox(INT32 choice)
 	else
 		CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
 	if (players[consoleplayer].lives == INFLIVES)
-		CV_StealthSetValue(&cv_dummylives, -1);
+		CV_StealthSet(&cv_dummylives, "Infinite");
 	else
-		CV_StealthSetValue(&cv_dummylives, players[consoleplayer].lives);
+		CV_StealthSetValue(&cv_dummylives, max(players[consoleplayer].lives, 1));
 	CV_StealthSetValue(&cv_dummycontinues, players[consoleplayer].continues);
 	SR_PandorasBox[6].status = ((players[consoleplayer].charflags & SF_SUPER)
 #ifndef DEVELOP
@@ -6664,7 +6897,7 @@ static void M_DrawEmblemHints(void)
 		if (emblem->collected)
 		{
 			collected = V_GREENMAP;
-			V_DrawMappedPatch(12, 12+(28*j), 0, W_CachePatchName(M_GetEmblemPatch(emblem), PU_CACHE),
+			V_DrawMappedPatch(12, 12+(28*j), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE),
 				R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 		}
 		else
@@ -7615,6 +7848,8 @@ static void M_HandleLoadSave(INT32 choice)
 	}
 	if (exitmenu)
 	{
+		// Is this a hack?
+		charseltimer = 0;
 		if (currentMenu->prevMenu)
 			M_SetupNextMenu(currentMenu->prevMenu);
 		else
@@ -7691,7 +7926,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 	UINT8 firstvalid = 255;
 	UINT8 lastvalid = 0;
 	boolean allowed = false;
-	char *name;
+	char *and;
 	(void)choice;
 
 	SP_PlayerMenu[0].status &= ~IT_DYBIGSPACE; // Correcting a hack that may be made below.
@@ -7700,8 +7935,21 @@ static void M_SetupChoosePlayer(INT32 choice)
 	{
 		if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
 		{
-			name = strtok(Z_StrDup(description[i].skinname), "&");
-			skinnum = R_SkinAvailable(name);
+			and = strchr(description[i].skinname, '&');
+			if (and)
+			{
+				char firstskin[SKINNAMESIZE+1];
+				strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
+				firstskin[(and - description[i].skinname)] = '\0';
+				description[i].skinnum[0] = R_SkinAvailable(firstskin);
+				description[i].skinnum[1] = R_SkinAvailable(and+1);
+			}
+			else
+			{
+				description[i].skinnum[0] = R_SkinAvailable(description[i].skinname);
+				description[i].skinnum[1] = -1;
+			}
+			skinnum = description[i].skinnum[0];
 			if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
 			{
 				// Handling order.
@@ -7719,20 +7967,27 @@ static void M_SetupChoosePlayer(INT32 choice)
 
 				if (!(description[i].picname[0]))
 				{
-					if (skins[skinnum].sprites[SPR2_XTRA].numframes >= 2)
+					if (skins[skinnum].sprites[SPR2_XTRA].numframes >= XTRA_CHARSEL+1)
 					{
 						spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
-						spriteframe_t *sprframe = &sprdef->spriteframes[1];
-						description[i].pic = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+						spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
+						description[i].charpic = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
 					}
 					else
-						description[i].pic = W_CachePatchName("MISSING", PU_CACHE);
+						description[i].charpic = W_CachePatchName("MISSING", PU_CACHE);
 				}
 				else
-					description[i].pic = W_CachePatchName(description[i].picname, PU_CACHE);
+					description[i].charpic = W_CachePatchName(description[i].picname, PU_CACHE);
+
+				if (description[i].nametag[0])
+				{
+					const char *nametag = description[i].nametag;
+					description[i].namepic = NULL;
+					if (W_LumpExists(nametag))
+						description[i].namepic = W_CachePatchName(nametag, PU_CACHE);
+				}
 			}
 			// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
-			Z_Free(name);
 		}
 	}
 
@@ -7752,8 +8007,22 @@ static void M_SetupChoosePlayer(INT32 choice)
 		return;
 	}
 
-	if (Playing() == false)
-		M_ChangeMenuMusic("_chsel", true);
+	M_ChangeMenuMusic("_chsel", true);
+
+	/* the menus suck -James */
+	if (currentMenu == &SP_LoadDef)/* from save states */
+	{
+		SP_PlayerDef.menuid =
+			MN_SP_MAIN +
+			( MN_SP_LOAD   <<  6 ) +
+			( MN_SP_PLAYER << 12 );
+	}
+	else/* from Secret level select */
+	{
+		SP_PlayerDef.menuid =
+			MN_SR_MAIN +
+			( MN_SR_PLAYER <<  6 );
+	}
 
 	SP_PlayerDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&SP_PlayerDef);
@@ -7767,7 +8036,11 @@ static void M_SetupChoosePlayer(INT32 choice)
 				char_on = description[char_on].next;
 		}
 	}
-	char_scroll = 0; // finish scrolling the menu
+
+	// finish scrolling the menu
+	char_scroll = 0;
+	charseltimer = 0;
+
 	Z_Free(char_notes);
 	char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
 }
@@ -7782,6 +8055,9 @@ static void M_HandleChoosePlayerMenu(INT32 choice)
 	boolean exitmenu = false;  // exit to previous menu
 	INT32 selectval;
 
+	if (keydown > 1)
+		return;
+
 	switch (choice)
 	{
 		case KEY_DOWNARROW:
@@ -7789,7 +8065,7 @@ static void M_HandleChoosePlayerMenu(INT32 choice)
 			{
 				S_StartSound(NULL,sfx_s3kb7);
 				char_on = selectval;
-				char_scroll = -128*FRACUNIT;
+				char_scroll = -charscrollamt;
 				Z_Free(char_notes);
 				char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
 			}
@@ -7805,7 +8081,7 @@ static void M_HandleChoosePlayerMenu(INT32 choice)
 			{
 				S_StartSound(NULL,sfx_s3kb7);
 				char_on = selectval;
-				char_scroll = 128*FRACUNIT;
+				char_scroll = charscrollamt;
 				Z_Free(char_notes);
 				char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
 			}
@@ -7831,6 +8107,8 @@ static void M_HandleChoosePlayerMenu(INT32 choice)
 
 	if (exitmenu)
 	{
+		// Is this a hack?
+		charseltimer = 0;
 		if (currentMenu->prevMenu)
 			M_SetupNextMenu(currentMenu->prevMenu);
 		else
@@ -7839,100 +8117,218 @@ static void M_HandleChoosePlayerMenu(INT32 choice)
 }
 
 // Draw the choose player setup menu, had some fun with player anim
+//define CHOOSEPLAYER_DRAWHEADER
+
 static void M_DrawSetupChoosePlayerMenu(void)
 {
-	const INT32 my = 24;
-	patch_t *patch;
-	INT32 i, o;
-	UINT8 prev, next;
+	const INT32 my = 16;
 
-	// Black BG
-	if (curbgcolor >= 0)
-		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
-	else if (!curbghide || !titlemapinaction)
-		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
-	if (curfadevalue)
-		V_DrawFadeScreen(0xFF00, curfadevalue);
-
-	// Character select profile images!1
-	M_DrawTextBox(0, my, 16, 20);
+	skin_t *charskin = &skins[0];
+	INT32 skinnum = 0;
+	UINT8 col;
+	UINT8 *colormap = NULL;
+	INT32 prev = -1, next = -1;
+
+	patch_t *charbg = W_CachePatchName("CHARBG", PU_CACHE);
+	patch_t *charfg = W_CachePatchName("CHARFG", PU_CACHE);
+	INT16 bgheight = SHORT(charbg->height);
+	INT16 fgheight = SHORT(charfg->height);
+	INT16 bgwidth = SHORT(charbg->width);
+	INT16 fgwidth = SHORT(charfg->width);
+	INT32 x, y;
+	INT32 w = (vid.width/vid.dupx);
 
 	if (abs(char_scroll) > FRACUNIT)
 		char_scroll -= (char_scroll>>2);
 	else // close enough.
 		char_scroll = 0; // just be exact now.
 
-	o = (char_scroll >> FRACBITS) + 16;
+	// Get prev character...
+	prev = description[char_on].prev;
+	// If there's more than one character available...
+	if (prev != char_on)
+		// Let's get the next character now.
+		next = description[char_on].next;
+	else
+		// No there isn't.
+		prev = -1;
+
+	// Find skin number from description[]
+	skinnum = description[char_on].skinnum[0];
+	charskin = &skins[skinnum];
+
+	// Use the opposite of the character's skincolor
+	col = description[char_on].oppositecolor;
+	if (!col)
+		col = Color_Opposite[charskin->prefcolor - 1][0];
+
+	// Make the translation colormap
+	colormap = R_GetTranslationColormap(TC_DEFAULT, col, 0);
 
-	if (o < 0) // A little hacky...
+	// Don't render the title map
+	hidetitlemap = true;
+	charseltimer++;
+
+	// Background and borders
+	V_DrawFill(0, 0, bgwidth, vid.height, V_SNAPTOTOP|colormap[101]);
 	{
-		i = description[char_on].prev;
-		o += 128;
+		INT32 sw = (BASEVIDWIDTH * vid.dupx);
+		INT32 bw = (vid.width - sw) / 2;
+		col = colormap[106];
+		if (bw)
+			V_DrawFill(0, 0, bw, vid.height, V_NOSCALESTART|col);
 	}
-	else
-		i = char_on;
 
-	// Get prev character...
-	prev = description[i].prev;
+	y = (charseltimer%32);
+	V_DrawMappedPatch(0, y-bgheight, V_SNAPTOTOP, charbg, colormap);
+	V_DrawMappedPatch(0, y, V_SNAPTOTOP, charbg, colormap);
+	V_DrawMappedPatch(0, y+bgheight, V_SNAPTOTOP, charbg, colormap);
+	V_DrawMappedPatch(0, -y, V_SNAPTOTOP, charfg, colormap);
+	V_DrawMappedPatch(0, -y+fgheight, V_SNAPTOTOP, charfg, colormap);
+	V_DrawFill(fgwidth, 0, vid.width, vid.height, V_SNAPTOTOP|colormap[106]);
 
-	if (prev != i) // If there's more than one character available...
+	// Character pictures
 	{
-		// Let's get the next character now.
-		next = description[i].next;
+		x = 8;
+		y = (my+16) - FixedInt(char_scroll);
+		V_DrawScaledPatch(x, y, 0, description[char_on].charpic);
+		if (prev != -1)
+			V_DrawScaledPatch(x, y - 144, 0, description[prev].charpic);
+		if (next != -1)
+			V_DrawScaledPatch(x, y + 144, 0, description[next].charpic);
+	}
 
-		// Draw prev character if it's visible and its number isn't greater than the current one or there's more than two
-		if (o < 32)
+	// Character description
+	{
+		INT32 flags = V_ALLOWLOWERCASE|V_RETURN8;
+		x = 146;
+		y = my + 9;
+		V_DrawString(x, y, flags, char_notes);
+	}
+
+	// Name tags
+	{
+		INT32 ox, oxsh = FixedInt(FixedMul(BASEVIDWIDTH*FRACUNIT, FixedDiv(char_scroll, 128*FRACUNIT))), txsh;
+		patch_t *curpatch = NULL, *prevpatch = NULL, *nextpatch = NULL;
+		const char *curtext = NULL, *prevtext = NULL, *nexttext = NULL;
+		UINT8 curtextcolor = 0, prevtextcolor = 0, nexttextcolor = 0;
+		UINT8 curoutlinecolor = 0, prevoutlinecolor = 0, nextoutlinecolor = 0;
+
+		// Name tag
+		curtext = description[char_on].displayname;
+		curtextcolor = description[char_on].tagtextcolor;
+		curoutlinecolor = description[char_on].tagoutlinecolor;
+		if (curtext[0] == '\0')
+			curpatch = description[char_on].namepic;
+		if (!curtextcolor)
+			curtextcolor = charskin->prefcolor;
+		if (!curoutlinecolor)
+			curoutlinecolor = Color_Opposite[charskin->prefcolor - 1][0];
+
+		txsh = oxsh;
+		ox = 8 + SHORT((description[char_on].charpic)->width)/2;
+		y = my + 144;
+
+		// cur
 		{
-			patch = description[prev].pic;
-			if (SHORT(patch->width) >= 256)
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, SHORT(patch->height) + 2*(o-32), SHORT(patch->width), 64 - 2*o);
-			else
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT, 0, patch, 0, SHORT(patch->height) + o - 32, SHORT(patch->width), 32 - o);
-			W_UnlockCachedPatch(patch);
+			x = ox - txsh;
+			if (curpatch)
+				x -= (SHORT(curpatch->width)/2);
+
+			if (curtext[0] != '\0')
+			{
+				V_DrawNameTag(
+					x, y, V_CENTERNAMETAG, FRACUNIT,
+					R_GetTranslationColormap(TC_DEFAULT, curtextcolor, 0),
+					R_GetTranslationColormap(TC_DEFAULT, curoutlinecolor, 0),
+					curtext
+				);
+			}
+			else if (curpatch)
+				V_DrawScaledPatch(x, y, 0, curpatch);
 		}
 
-		// Draw next character if it's visible and its number isn't less than the current one or there's more than two
-		if (o < 128) // (next != i) was previously a part of this, but it's implicitly true if (prev != i) is true.
+		if (char_scroll)
 		{
-			patch = description[next].pic;
-			if (SHORT(patch->width) >= 256)
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT/2, 0, patch, 0, 0, SHORT(patch->width), 2*o);
-			else
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT, 0, patch, 0, 0, SHORT(patch->width), o);
-			W_UnlockCachedPatch(patch);
+			// prev
+			if ((prev != -1) && char_scroll < 0)
+			{
+				prevtext = description[prev].displayname;
+				prevtextcolor = description[prev].tagtextcolor;
+				prevoutlinecolor = description[prev].tagoutlinecolor;
+				if (prevtext[0] == '\0')
+					prevpatch = description[prev].namepic;
+				charskin = &skins[description[prev].skinnum[0]];
+				if (!prevtextcolor)
+					prevtextcolor = charskin->prefcolor;
+				if (!prevoutlinecolor)
+					prevoutlinecolor = Color_Opposite[charskin->prefcolor - 1][0];
+
+				x = (ox - txsh) - w;
+				if (prevpatch)
+					x -= (SHORT(prevpatch->width)/2);
+
+				if (prevtext[0] != '\0')
+				{
+					V_DrawNameTag(
+						x, y, V_CENTERNAMETAG, FRACUNIT,
+						R_GetTranslationColormap(TC_DEFAULT, prevtextcolor, 0),
+						R_GetTranslationColormap(TC_DEFAULT, prevoutlinecolor, 0),
+						prevtext
+					);
+				}
+				else if (prevpatch)
+					V_DrawScaledPatch(x, y, 0, prevpatch);
+			}
+			// next
+			else if ((next != -1) && char_scroll > 0)
+			{
+				nexttext = description[next].displayname;
+				nexttextcolor = description[next].tagtextcolor;
+				nextoutlinecolor = description[next].tagoutlinecolor;
+				if (nexttext[0] == '\0')
+					nextpatch = description[next].namepic;
+				charskin = &skins[description[next].skinnum[0]];
+				if (!nexttextcolor)
+					nexttextcolor = charskin->prefcolor;
+				if (!nextoutlinecolor)
+					nextoutlinecolor = Color_Opposite[charskin->prefcolor - 1][0];
+
+				x = (ox - txsh) + w;
+				if (nextpatch)
+					x -= (SHORT(nextpatch->width)/2);
+
+				if (nexttext[0] != '\0')
+				{
+					V_DrawNameTag(
+						x, y, V_CENTERNAMETAG, FRACUNIT,
+						R_GetTranslationColormap(TC_DEFAULT, nexttextcolor, 0),
+						R_GetTranslationColormap(TC_DEFAULT, nextoutlinecolor, 0),
+						nexttext
+					);
+				}
+				else if (nextpatch)
+					V_DrawScaledPatch(x, y, 0, nextpatch);
+			}
 		}
 	}
 
-	patch = description[i].pic;
-	if (o >= 0 && o <= 32)
-	{
-		if (SHORT(patch->width) >= 256)
-			V_DrawSmallScaledPatch(8, my + 40 - o, 0, patch);
-		else
-			V_DrawScaledPatch(8, my + 40 - o, 0, patch);
-	}
-	else
+	// Alternative menu header
+#ifdef CHOOSEPLAYER_DRAWHEADER
 	{
-		if (SHORT(patch->width) >= 256)
-			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, (o-32)*2, SHORT(patch->width), SHORT(patch->height) - 2*(o-32));
-		else
-			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT, 0, patch, 0, (o-32), SHORT(patch->width), SHORT(patch->height) - (o-32));
+		patch_t *header = W_CachePatchName("M_PICKP", PU_CACHE);
+		INT32 xtitle = 146;
+		INT32 ytitle = (35 - SHORT(header->height))/2;
+		V_DrawFixedPatch(xtitle<<FRACBITS, ytitle<<FRACBITS, FRACUNIT/2, 0, header, NULL);
 	}
-	W_UnlockCachedPatch(patch);
+#endif // CHOOSEPLAYER_DRAWHEADER
 
-	// draw title (or big pic)
 	M_DrawMenuTitle();
-
-	// Character description
-	M_DrawTextBox(136, my, 21, 20);
-	V_DrawString(146, my + 9, V_RETURN8|V_ALLOWLOWERCASE, char_notes);
 }
 
 // Chose the player you want to use Tails 03-02-2002
 static void M_ChoosePlayer(INT32 choice)
 {
-	char *skin1,*skin2;
-	INT32 skinnum;
 	boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
 
 	// skip this if forcecharacter or no characters available
@@ -7941,24 +8337,19 @@ static void M_ChoosePlayer(INT32 choice)
 		// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
 		char_scroll = 0; // finish scrolling the menu
 		M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
+		// Is this a hack?
+		charseltimer = 0;
 	}
 	M_ClearMenus(true);
 
-	skin1 = strtok(description[choice].skinname, "&");
-	skin2 = strtok(NULL, "&");
-
-	if (skin2) {
+	if (description[choice].skinnum[1] != -1) {
 		// this character has a second skin
-		skinnum = R_SkinAvailable(skin1);
-		botskin = (UINT8)(R_SkinAvailable(skin2)+1);
 		botingame = true;
-
-		botcolor = skins[botskin-1].prefcolor;
-
-		// undo the strtok
-		description[choice].skinname[strlen(skin1)] = '&';
-	} else {
-		skinnum = R_SkinAvailable(description[choice].skinname);
+		botskin = (UINT8)(description[choice].skinnum[1]+1);
+		botcolor = skins[description[choice].skinnum[1]].prefcolor;
+	}
+	else
+	{
 		botingame = false;
 		botskin = 0;
 		botcolor = 0;
@@ -7970,7 +8361,7 @@ static void M_ChoosePlayer(INT32 choice)
 	//lastmapsaved = 0;
 	gamecomplete = false;
 
-	G_DeferedInitNew(ultmode, G_BuildMapName(startmap), (UINT8)skinnum, false, fromlevelselect);
+	G_DeferedInitNew(ultmode, G_BuildMapName(startmap), (UINT8)description[choice].skinnum[0], false, fromlevelselect);
 	COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
 
 	if (levelselect.rows)
@@ -8091,7 +8482,7 @@ static void M_DrawStatsMaps(int location)
 			exemblem = &extraemblems[i];
 
 			if (exemblem->collected)
-				V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem), PU_CACHE),
+				V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_CACHE),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetExtraEmblemColor(exemblem), GTC_CACHE));
 			else
 				V_DrawSmallScaledPatch(292, y, 0, W_CachePatchName("NEEDIT", PU_CACHE));
@@ -8236,16 +8627,27 @@ static void M_HandleLevelStats(INT32 choice)
 // Drawing function for Time Attack
 void M_DrawTimeAttackMenu(void)
 {
-	INT32 i, x, y, cursory = 0;
+	INT32 i, x, y, empatx, empaty, cursory = 0;
 	UINT16 dispstatus;
-	patch_t *PictureOfUrFace;
+	patch_t *PictureOfUrFace;	// my WHAT
+	patch_t *empatch;
 
-	M_ChangeMenuMusic("_inter", true); // Eww, but needed for when user hits escape during demo playback
+	M_SetMenuCurBackground("RECATKBG");
+
+	curbgxspeed = 0;
+	curbgyspeed = 18;
+
+	M_ChangeMenuMusic("_recat", true); // Eww, but needed for when user hits escape during demo playback
 
 	if (curbgcolor >= 0)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 	else if (!curbghide || !titlemapinaction)
+	{
 		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		// Draw and animate foreground
+		if (!strncmp("RECATKBG", curbgname, 8))
+			M_DrawRecordAttackForeground();
+	}
 	if (curfadevalue)
 		V_DrawFadeScreen(0xFF00, curfadevalue);
 
@@ -8296,10 +8698,10 @@ void M_DrawTimeAttackMenu(void)
 
 	// Character face!
 	{
-		if (skins[cv_chooseskin.value-1].sprites[SPR2_XTRA].numframes >= 2)
+		if (skins[cv_chooseskin.value-1].sprites[SPR2_XTRA].numframes >= XTRA_CHARSEL+1)
 		{
 			spritedef_t *sprdef = &skins[cv_chooseskin.value-1].sprites[SPR2_XTRA];
-			spriteframe_t *sprframe = &sprdef->spriteframes[1];
+			spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
 			PictureOfUrFace = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
 		}
 		else
@@ -8319,6 +8721,7 @@ void M_DrawTimeAttackMenu(void)
 		patch_t *PictureOfLevel;
 		lumpnum_t lumpnum;
 		char beststr[40];
+		char reqscore[40], reqtime[40], reqrings[40];
 
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
@@ -8330,17 +8733,72 @@ void M_DrawTimeAttackMenu(void)
 		else
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
 
-		V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
+		y = 32+lsheadingheight;
+		V_DrawSmallScaledPatch(216, y, 0, PictureOfLevel);
 
-		V_DrawString(104 - 72, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
+
+		if (currentMenu == &SP_TimeAttackDef)
+		{
+			if (itemOn == talevel)
+			{
+				/* Draw arrows !! */
+				y = y + 25 - 4;
+				V_DrawCharacter(216 - 10 - (skullAnimCounter/5), y,
+						'\x1C' | V_YELLOWMAP, false);
+				V_DrawCharacter(216 + 80 + 2 + (skullAnimCounter/5), y,
+						'\x1D' | V_YELLOWMAP, false);
+			}
+			// Draw press ESC to exit string on main record attack menu
+			V_DrawString(104-72, 180, V_TRANSLUCENT, M_GetText("Press ESC to exit"));
+		}
+
+		em = M_GetLevelEmblems(cv_nextmap.value);
+		// Draw record emblems.
+		while (em)
+		{
+			switch (em->type)
+			{
+				case ET_SCORE:
+					yHeight = 33;
+					sprintf(reqscore, "(%u)", em->var);
+					break;
+				case ET_TIME:
+					yHeight = 53;
+					sprintf(reqtime, "(%i:%02i.%02i)", G_TicsToMinutes((tic_t)em->var, true),
+										G_TicsToSeconds((tic_t)em->var),
+										G_TicsToCentiseconds((tic_t)em->var));
+					break;
+				case ET_RINGS:
+					yHeight = 73;
+					sprintf(reqrings, "(%u)", em->var);
+					break;
+				default:
+					goto skipThisOne;
+			}
+
+			empatch = W_CachePatchName(M_GetEmblemPatch(em, true), PU_CACHE);
+
+			empatx = SHORT(empatch->leftoffset)/2;
+			empaty = SHORT(empatch->topoffset)/2;
+
+			if (em->collected)
+				V_DrawSmallMappedPatch(104+76+empatx, yHeight+lsheadingheight/2+empaty, 0, empatch,
+				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
+			else
+				V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDITL", PU_CACHE));
+
+			skipThisOne:
+			em = M_GetLevelEmblems(-1);
+		}
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
 			sprintf(beststr, "(none)");
 		else
 			sprintf(beststr, "%u", mainrecords[cv_nextmap.value-1]->score);
 
-		V_DrawString(104-72, 48+lsheadingheight/2, V_YELLOWMAP, "SCORE:");
-		V_DrawRightAlignedString(104+72, 48+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+		V_DrawString(104-72, 33+lsheadingheight/2, V_YELLOWMAP, "SCORE:");
+		V_DrawRightAlignedString(104+64, 33+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+		V_DrawRightAlignedString(104+72, 43+lsheadingheight/2, V_ALLOWLOWERCASE, reqscore);
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time)
 			sprintf(beststr, "(none)");
@@ -8349,39 +8807,23 @@ void M_DrawTimeAttackMenu(void)
 			                                 G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time),
 			                                 G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time));
 
-		V_DrawString(104-72, 58+lsheadingheight/2, V_YELLOWMAP, "TIME:");
-		V_DrawRightAlignedString(104+72, 58+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+		V_DrawString(104-72, 53+lsheadingheight/2, V_YELLOWMAP, "TIME:");
+		V_DrawRightAlignedString(104+64, 53+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+		V_DrawRightAlignedString(104+72, 63+lsheadingheight/2, V_ALLOWLOWERCASE, reqtime);
 
 		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->rings)
 			sprintf(beststr, "(none)");
 		else
 			sprintf(beststr, "%hu", mainrecords[cv_nextmap.value-1]->rings);
 
-		V_DrawString(104-72, 68+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
-		V_DrawRightAlignedString(104+72, 68+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
-
-		// Draw record emblems.
-		em = M_GetLevelEmblems(cv_nextmap.value);
-		while (em)
-		{
-			switch (em->type)
-			{
-				case ET_SCORE: yHeight = 48; break;
-				case ET_TIME:  yHeight = 58; break;
-				case ET_RINGS: yHeight = 68; break;
-				default:
-					goto skipThisOne;
-			}
+		V_DrawString(104-72, 73+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
 
-			if (em->collected)
-				V_DrawSmallMappedPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
-				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
-			else
-				V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->gotperfect)
+			V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+		else
+			V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|V_YELLOWMAP, beststr);
 
-			skipThisOne:
-			em = M_GetLevelEmblems(-1);
-		}
+		V_DrawRightAlignedString(104+72, 83+lsheadingheight/2, V_ALLOWLOWERCASE, reqrings);
 	}
 
 	// ALWAYS DRAW level and skin even when not on this menu!
@@ -8400,6 +8842,39 @@ void M_DrawTimeAttackMenu(void)
 	}
 }
 
+static void M_HandleTimeAttackLevelSelect(INT32 choice)
+{
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			M_NextOpt();
+			break;
+		case KEY_UPARROW:
+			M_PrevOpt();
+			break;
+
+		case KEY_LEFTARROW:
+			CV_AddValue(&cv_nextmap, -1);
+			break;
+		case KEY_RIGHTARROW:
+			CV_AddValue(&cv_nextmap, 1);
+			break;
+
+		case KEY_ENTER:
+			M_TimeAttackLevelSelect(0);
+			break;
+
+		case KEY_ESCAPE:
+			noFurtherInput = true;
+			M_GoBack(0);
+			return;
+
+		default:
+			return;
+	}
+	S_StartSound(NULL, sfx_menu1);
+}
+
 static void M_TimeAttackLevelSelect(INT32 choice)
 {
 	(void)choice;
@@ -8440,12 +8915,11 @@ void M_DrawNightsAttackMenu(void)
 	INT32 i, x, y, cursory = 0;
 	UINT16 dispstatus;
 
-	M_ChangeMenuMusic("_inter", true); // Eww, but needed for when user hits escape during demo playback
+	M_SetMenuCurBackground("NTSATKBG");
 
-	if (curbgcolor >= 0)
-		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
-	else if (!curbghide || !titlemapinaction)
-		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+	M_ChangeMenuMusic("_nitat", true); // Eww, but needed for when user hits escape during demo playback
+
+	M_DrawNightsAttackBackground();
 	if (curfadevalue)
 		V_DrawFadeScreen(0xFF00, curfadevalue);
 
@@ -8503,7 +8977,7 @@ void M_DrawNightsAttackMenu(void)
 		lumpnum_t lumpnum;
 		char beststr[40];
 
-		UINT8 bestoverall	= G_GetBestNightsGrade(cv_nextmap.value, 0);
+		//UINT8 bestoverall	= G_GetBestNightsGrade(cv_nextmap.value, 0);
 		UINT8 bestgrade		= G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value);
 		UINT32 bestscore	= G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value);
 		tic_t besttime		= G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value);
@@ -8520,10 +8994,10 @@ void M_DrawNightsAttackMenu(void)
 
 		V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
 
-		V_DrawString(104 - 72, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
-
-		if (P_HasGrades(cv_nextmap.value, 0))
-			V_DrawScaledPatch(235, 135, 0, ngradeletters[bestoverall]);
+		// Super Sonic
+		M_DrawNightsAttackSuperSonic();
+		//if (P_HasGrades(cv_nextmap.value, 0))
+		//	V_DrawScaledPatch(235 - (SHORT((ngradeletters[bestoverall])->width)*3)/2, 135, 0, ngradeletters[bestoverall]);
 
 		if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
 			{//make bigger again
@@ -8565,10 +9039,10 @@ void M_DrawNightsAttackMenu(void)
 				}
 
 				if (em->collected)
-					V_DrawSmallMappedPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
+					V_DrawSmallMappedPatch(104+38, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_CACHE),
 																 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 				else
-					V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+					V_DrawSmallScaledPatch(104+38, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
 
 				skipThisOne:
 				em = M_GetLevelEmblems(-1);
@@ -8576,6 +9050,10 @@ void M_DrawNightsAttackMenu(void)
 		}
 	}
 
+	// Draw press ESC to exit string on main nights attack menu
+	if (currentMenu == &SP_NightsAttackDef)
+		V_DrawString(104-72, 180, V_TRANSLUCENT, M_GetText("Press ESC to exit"));
+
 	// ALWAYS DRAW level even when not on this menu!
 	if (currentMenu != &SP_NightsAttackDef)
 		V_DrawString(SP_NightsAttackDef.x, SP_NightsAttackDef.y + SP_TimeAttackMenu[nalevel].alphaKey, V_TRANSLUCENT, SP_NightsAttackMenu[nalevel].text);
@@ -8604,6 +9082,9 @@ static void M_NightsAttack(INT32 choice)
 	// This is really just to make sure Sonic is the played character, just in case
 	M_PatchSkinNameTable();
 
+	ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_CACHE);
+	ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_CACHE);
+
 	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
 	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
 	M_SetupNextMenu(&SP_NightsAttackDef);
@@ -9386,6 +9867,12 @@ static void M_ServerOptions(INT32 choice)
 	}
 #endif
 
+	/* Disable fading because of different menu head. */
+	if (currentMenu == &OP_MainDef)/* from Options menu */
+		OP_ServerOptionsDef.menuid = MN_OP_MAIN + ( MN_OP_SERVER << 6 );
+	else/* from Multiplayer menu */
+		OP_ServerOptionsDef.menuid = MN_MP_MAIN + ( MN_MP_SERVER_OPTIONS << 6 );
+
 	OP_ServerOptionsDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&OP_ServerOptionsDef);
 }
@@ -9450,6 +9937,8 @@ static void M_ConnectIP(INT32 choice)
 		return;
 	}
 
+	M_ClearMenus(true);
+
 	COM_BufAddText(va("connect \"%s\"\n", setupm_ip));
 
 	// A little "please wait" message.
@@ -9481,7 +9970,6 @@ static void M_HandleConnectIP(INT32 choice)
 
 		case KEY_ENTER:
 			S_StartSound(NULL,sfx_menu1); // Tails
-			M_ClearMenus(true);
 			M_ConnectIP(1);
 			break;
 
@@ -9531,6 +10019,7 @@ static void M_HandleConnectIP(INT32 choice)
 
 	if (exitmenu)
 	{
+		currentMenu->lastOn = itemOn;
 		if (currentMenu->prevMenu)
 			M_SetupNextMenu (currentMenu->prevMenu);
 		else
@@ -10035,7 +10524,7 @@ static void M_DrawJoystick(void)
 			compareval = cv_usejoystick.value;
 #else
 		compareval2 = cv_usejoystick2.value;
-		compareval = cv_usejoystick.value
+		compareval = cv_usejoystick.value;
 #endif
 
 		if ((setupcontrols_secondaryplayer && (i == compareval2))
diff --git a/src/m_menu.h b/src/m_menu.h
index 05962d2b19072eda900642223db6f042f7790c8e..3bfa48597b9d2ecafcb086d571eb6d405581a4c8 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -63,6 +63,7 @@ typedef enum
 	MN_MP_CONNECT,
 	MN_MP_ROOM,
 	MN_MP_PLAYERSETUP, // MP_PlayerSetupDef shared with SPLITSCREEN if #defined NONET
+	MN_MP_SERVER_OPTIONS,
 
 	// Options
 	MN_OP_MAIN,
@@ -103,6 +104,7 @@ typedef enum
 	MN_SR_LEVELSELECT,
 	MN_SR_UNLOCKCHECKLIST,
 	MN_SR_EMBLEMHINT,
+	MN_SR_PLAYER,
 
 	// Addons (Part of MISC, but let's make it our own)
 	MN_AD_MAIN,
@@ -323,9 +325,18 @@ typedef struct
 	char notes[441];
 	char picname[8];
 	char skinname[SKINNAMESIZE*2+2]; // skin&skin\0
-	patch_t *pic;
+	patch_t *charpic;
 	UINT8 prev;
 	UINT8 next;
+
+	// new character select
+	char displayname[SKINNAMESIZE+1];
+	SINT8 skinnum[2];
+	UINT8 oppositecolor;
+	char nametag[8];
+	patch_t *namepic;
+	UINT8 tagtextcolor;
+	UINT8 tagoutlinecolor;
 } description_t;
 
 // level select platter
@@ -374,6 +385,7 @@ typedef struct
 
 extern description_t description[MAXSKINS];
 
+extern consvar_t cv_showfocuslost;
 extern consvar_t cv_newgametype, cv_nextmap, cv_chooseskin, cv_serversort;
 extern CV_PossibleValue_t gametype_cons_t[];
 
diff --git a/src/m_misc.c b/src/m_misc.c
index aaaf30d67ff4263967f693f8126ff6b72b1b6f8c..f7d5cf9613b17ff00e5390f4a50710193470707f 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -197,7 +197,7 @@ INT32 M_MapNumber(char first, char second)
 // ==========================================================================
 
 // some libcs has no access function, make our own
-#if defined (_WIN32_WCE)
+#if 0
 int access(const char *path, int amode)
 {
 	int accesshandle = -1;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 000b5cbfb009b5a9e4beaecc308bb882d1a56f83..cc2d64e8b9b7e6901a76bd3d520756cd9cdb3ea5 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -298,6 +298,14 @@ void A_SnapperThinker(mobj_t *actor);
 void A_SaloonDoorSpawn(mobj_t *actor);
 void A_MinecartSparkThink(mobj_t *actor);
 void A_ModuloToState(mobj_t *actor);
+void A_LavafallRocks(mobj_t *actor);
+void A_LavafallLava(mobj_t *actor);
+void A_FallingLavaCheck(mobj_t *actor);
+void A_FireShrink(mobj_t *actor);
+void A_SpawnPterabytes(mobj_t *actor);
+void A_PterabyteHover(mobj_t *actor);
+void A_RolloutSpawn(mobj_t *actor);
+void A_RolloutRock(mobj_t *actor);
 
 //for p_enemy.c
 
@@ -2019,6 +2027,7 @@ void A_CrushstaceanWalk(mobj_t *actor)
 	|| (actor->reactiontime-- <= 0))
 	{
 		actor->flags2 ^= MF2_AMBUSH;
+		P_SetTarget(&actor->target, NULL);
 		P_SetMobjState(actor, locvar2);
 		actor->reactiontime = actor->info->reactiontime;
 	}
@@ -2079,7 +2088,7 @@ void A_CrushclawAim(mobj_t *actor)
 		return; // there is only one step and it is crab
 	}
 
-	if (crab->target || P_LookForPlayers(crab, true, false, 600*crab->scale))
+	if (crab->target || P_LookForPlayers(crab, true, false, actor->info->speed*crab->scale))
 		ang = R_PointToAngle2(crab->x, crab->y, crab->target->x, crab->target->y);
 	else
 		ang = crab->angle + ((crab->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
@@ -2162,7 +2171,7 @@ void A_CrushclawLaunch(mobj_t *actor)
 		UINT8 i = 0;
 		for (i = 0; (i < CSEGS); i++)
 		{
-			mobj_t *newchain = P_SpawnMobjFromMobj(actor, 0, 0, 0, actor->info->raisestate);
+			mobj_t *newchain = P_SpawnMobjFromMobj(actor, 0, 0, 0, (mobjtype_t)actor->info->raisestate);
 			P_SetTarget(&prevchain->target, newchain);
 			prevchain = newchain;
 		}
@@ -5343,20 +5352,22 @@ static mobj_t *minus;
 
 static boolean PIT_MinusCarry(mobj_t *thing)
 {
+	if (minus->tracer)
+		return true;
+
 	if (minus->type == thing->type)
 		return true;
 
-	if (!(thing->flags & MF_SHOOTABLE) || !(thing->flags & MF_ENEMY))
+	if (!(thing->flags & (MF_PUSHABLE|MF_ENEMY)))
 		return true;
 
-	if (P_AproxDistance(minus->x - thing->x, minus->y - thing->y) >= minus->radius * 3)
+	if (P_AproxDistance(minus->x - thing->x, minus->y - thing->y) >= minus->radius*3)
 		return true;
 
 	if (abs(thing->z - minus->z) > minus->height)
 		return true;
 
 	P_SetTarget(&minus->tracer, thing);
-	minus->tracer->flags &= ~MF_PUSHABLE;
 
 	return true;
 }
@@ -5420,6 +5431,9 @@ void A_MinusDigging(mobj_t *actor)
 	A_Chase(actor);
 
 	// Carry over shit, maybe
+	if (P_MobjWasRemoved(actor->tracer) || !actor->tracer->health)
+		P_SetTarget(&actor->tracer, NULL);
+
 	if (!actor->tracer)
 	{
 		fixed_t radius = 3*actor->radius;
@@ -5469,7 +5483,6 @@ void A_MinusPopup(mobj_t *actor)
 	else
 		actor->momz = 10*FRACUNIT;
 
-	actor->flags |= MF_SPECIAL|MF_SHOOTABLE;
 	S_StartSound(actor, sfx_s3k82);
 	for (i = 1; i <= num; i++)
 	{
@@ -5482,6 +5495,7 @@ void A_MinusPopup(mobj_t *actor)
 	if (actor->tracer)
 		P_DamageMobj(actor->tracer, actor, actor, 1, 0);
 
+	actor->flags = (actor->flags & ~MF_NOCLIPTHING)|MF_SPECIAL|MF_SHOOTABLE;
 }
 
 // Function: A_MinusCheck
@@ -8408,8 +8422,8 @@ void A_ChangeAngleAbsolute(mobj_t *actor)
 //
 // var1 = sound # to play
 // var2:
-//		0 = Play sound without an origin
-//		1 = Play sound using calling object as origin
+//		lower 16 bits = If 1, play sound using calling object as origin. If 0, play sound without an origin
+//		upper 16 bits = If 1, do not play sound during preticker.
 //
 void A_PlaySound(mobj_t *actor)
 {
@@ -8420,7 +8434,10 @@ void A_PlaySound(mobj_t *actor)
 		return;
 #endif
 
-	S_StartSound(locvar2 ? actor : NULL, locvar1);
+	if (leveltime < 2 && (locvar2 >> 16))
+		return;
+
+	S_StartSound((locvar2 & 65535) ? actor : NULL, locvar1);
 }
 
 // Function: A_FindTarget
@@ -12197,7 +12214,7 @@ void A_MineExplode(mobj_t *actor)
 #undef dist
 
 		if (actor->watertop != INT32_MAX)
-			P_SpawnMobj(actor->x, actor->y, actor->watertop, MT_SPLISH);
+			P_SpawnMobj(actor->x, actor->y, actor->watertop, (actor->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH);
 	}
 }
 
@@ -12237,7 +12254,6 @@ void A_ConnectToGround(mobj_t *actor)
 	mobj_t *work;
 	fixed_t workz;
 	fixed_t workh;
-	SINT8 dir;
 	angle_t ang;
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
@@ -12251,23 +12267,17 @@ void A_ConnectToGround(mobj_t *actor)
 		P_AdjustMobjFloorZ_FFloors(actor, actor->subsector->sector, 2);
 
 	if (actor->flags2 & MF2_OBJECTFLIP)
-	{
-		workz = actor->ceilingz - (actor->z + actor->height);
-		dir = -1;
-	}
+		workz = (actor->z + actor->height) - actor->ceilingz;
 	else
-	{
 		workz = actor->floorz - actor->z;
-		dir = 1;
-	}
 
 	if (locvar2)
 	{
 		workh = FixedMul(mobjinfo[locvar2].height, actor->scale);
 		if (actor->flags2 & MF2_OBJECTFLIP)
-			workz -= workh;
+			workz += workh;
 		work = P_SpawnMobjFromMobj(actor, 0, 0, workz, locvar2);
-		workz += dir*workh;
+		workz += workh;
 	}
 
 	if (!locvar1)
@@ -12276,21 +12286,18 @@ void A_ConnectToGround(mobj_t *actor)
 	if (!(workh = FixedMul(mobjinfo[locvar1].height, actor->scale)))
 		return;
 
-	if (actor->flags2 & MF2_OBJECTFLIP)
-		workz -= workh;
-
 	ang = actor->angle + ANGLE_45;
-	while (dir*workz < 0)
+	while (workz < 0)
 	{
 		work = P_SpawnMobjFromMobj(actor, 0, 0, workz, locvar1);
 		if (work)
 			work->angle = ang;
 		ang += ANGLE_90;
-		workz += dir*workh;
+		workz += workh;
 	}
 
 	if (workz != 0)
-		actor->z += workz;
+		actor->z += P_MobjFlip(actor)*workz;
 }
 
 // Function: A_SpawnParticleRelative
@@ -13455,6 +13462,13 @@ void A_TNTExplode(mobj_t *actor)
 	if (LUA_CallAction("A_TNTExplode", actor))
 		return;
 #endif
+
+	if (actor->tracer)
+	{
+		P_SetTarget(&actor->tracer->tracer, NULL);
+		P_SetTarget(&actor->tracer, NULL);
+	}
+
 	P_UnsetThingPosition(actor);
 	if (sector_list)
 	{
@@ -13668,8 +13682,6 @@ void A_KillSegments(mobj_t *actor)
 static void P_SnapperLegPlace(mobj_t *mo)
 {
 	mobj_t *seg = mo->tracer;
-	fixed_t x0 = mo->x;
-	fixed_t y0 = mo->y;
 	angle_t a = mo->angle;
 	angle_t fa = (a >> ANGLETOFINESHIFT) & FINEMASK;
 	fixed_t c = FINECOSINE(fa);
@@ -13684,7 +13696,8 @@ static void P_SnapperLegPlace(mobj_t *mo)
 	fixed_t rad = mo->radius;
 	INT32 necklen = (32*(mo->info->reactiontime - mo->reactiontime))/mo->info->reactiontime; // Not in FU
 
-	P_TeleportMove(seg, mo->x + FixedMul(c, rad) + necklen*c, mo->y + FixedMul(s, rad) + necklen*s, mo->z + mo->height/3);
+	seg->z = mo->z + ((mo->eflags & MFE_VERTICALFLIP) ? (((mo->height<<1)/3) - seg->height) : mo->height/3);
+	P_TryMove(seg, mo->x + FixedMul(c, rad) + necklen*c, mo->y + FixedMul(s, rad) + necklen*s, true);
 	seg->angle = a;
 
 	// Move as many legs as available.
@@ -13704,13 +13717,14 @@ static void P_SnapperLegPlace(mobj_t *mo)
 		{
 			x = c*o2 + s*o1;
 			y = s*o2 - c*o1;
-			P_TryMove(seg, x0 + x, y0 + y, true);
+			seg->z = mo->z + (((mo->eflags & MFE_VERTICALFLIP) ? (mo->height - seg->height) : 0));
+			P_TryMove(seg, mo->x + x, mo->y + y, true);
 			P_SetMobjState(seg, seg->info->raisestate);
 		}
 		else
 			P_SetMobjState(seg, seg->info->spawnstate);
 
-		seg->angle = R_PointToAngle2(x0, y0, seg->x, seg->y);
+		seg->angle = R_PointToAngle2(mo->x, mo->y, seg->x, seg->y);
 
 		seg = seg->tracer;
 	} while (seg);
@@ -13738,14 +13752,14 @@ void A_SnapperSpawn(mobj_t *actor)
 #endif
 
 	// It spawns 1 head.
-	seg = P_SpawnMobj(actor->x, actor->y, actor->z, headtype);
+	seg = P_SpawnMobjFromMobj(actor, 0, 0, 0, headtype);
 	P_SetTarget(&ptr->tracer, seg);
 	ptr = seg;
 
 	// It spawns 4 legs which will be handled in the thinker function.
 	for (i = 1; i <= 4; i++)
 	{
-		seg = P_SpawnMobj(actor->x, actor->y, actor->z, legtype);
+		seg = P_SpawnMobjFromMobj(actor, 0, 0, 0, legtype);
 		P_SetTarget(&ptr->tracer, seg);
 		ptr = seg;
 
@@ -13893,51 +13907,43 @@ void A_SnapperThinker(mobj_t *actor)
 //
 // Description: Spawns a saloon door.
 //
-// var1 = unused
-// var2 = unused
+// var1 = mobjtype for sides
+// var2 = distance sides should be placed apart
 //
 void A_SaloonDoorSpawn(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
 	angle_t ang = actor->angle;
 	angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
-	fixed_t c = FINECOSINE(fa);
-	fixed_t s = FINESINE(fa);
-	INT32 d = 48;
-	fixed_t x = actor->x;
-	fixed_t y = actor->y;
-	fixed_t z = actor->z;
+	fixed_t c = FINECOSINE(fa)*locvar2;
+	fixed_t s = FINESINE(fa)*locvar2;
 	mobj_t *door;
+	mobjflag2_t ambush = (actor->flags & MF2_AMBUSH);
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_SaloonDoorSpawn", actor))
 		return;
 #endif
 
-	//Front
-	door = P_SpawnMobj(x + c*d, y + s*d, z, MT_SALOONDOOR);
-	door->angle = ang + ANGLE_180;
-
-	// Origin angle
-	door->extravalue1 = AngleFixed(door->angle);
-
-	// Angular speed
-	door->extravalue2 = 0;
+	if (!locvar1)
+		return;
 
-	// Origin door
-	P_SetTarget(&door->tracer, actor);
+	// One door...
+	if (!(door = P_SpawnMobjFromMobj(actor, c, s, 0, locvar1))) return;
+	door->angle = ang + ANGLE_180;
+	door->extravalue1 = AngleFixed(door->angle); // Origin angle
+	door->extravalue2 = 0; // Angular speed
+	P_SetTarget(&door->tracer, actor); // Origin door
+	door->flags2 |= ambush; // Can be opened by normal players?
 
-	//Back
-	door = P_SpawnMobj(x - c*d, y - s*d, z, MT_SALOONDOOR);
+	// ...two door!
+	if (!(door = P_SpawnMobjFromMobj(actor, -c, -s, 0, locvar1))) return;
 	door->angle = ang;
-
-	// Origin angle
-	door->extravalue1 = AngleFixed(door->angle);
-
-	// Angular speed
-	door->extravalue2 = 0;
-
-	// Origin door
-	P_SetTarget(&door->tracer, actor);
+	door->extravalue1 = AngleFixed(door->angle); // Origin angle
+	door->extravalue2 = 0; // Angular speed
+	P_SetTarget(&door->tracer, actor); // Origin door
+	door->flags2 |= ambush; // Can be opened by normal players?
 }
 
 // Function: A_MinecartSparkThink
@@ -13998,3 +14004,289 @@ void A_ModuloToState(mobj_t *actor)
 		P_SetMobjState(actor, (locvar2));
 	modulothing++;
 }
+
+// Function: A_LavafallRocks
+//
+// Description: Spawn random rock particles.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_LavafallRocks(mobj_t *actor)
+{
+	UINT8 i;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_LavafallRocks", actor))
+		return;
+#endif
+
+	// Don't spawn rocks unless a player is relatively close by.
+	for (i = 0; i < MAXPLAYERS; ++i)
+		if (playeringame[i] && players[i].mo
+			&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (1600 << FRACBITS))
+			break; // Stop looking.
+
+	if (i < MAXPLAYERS)
+	{
+		angle_t fa = (FixedAngle(P_RandomKey(360) << FRACBITS) >> ANGLETOFINESHIFT) & FINEMASK;
+		fixed_t offset = P_RandomRange(4, 12) << FRACBITS;
+		fixed_t xoffs = FixedMul(FINECOSINE(fa), actor->radius + offset);
+		fixed_t yoffs = FixedMul(FINESINE(fa), actor->radius + offset);
+		P_SpawnMobjFromMobj(actor, xoffs, yoffs, 0, MT_LAVAFALLROCK);
+	}
+}
+
+// Function: A_LavafallLava
+//
+// Description: Spawn lava from lavafall.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_LavafallLava(mobj_t *actor)
+{
+	mobj_t *lavafall;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_LavafallLava", actor))
+		return;
+#endif
+
+	if ((40 - actor->fuse) % (2*(actor->scale >> FRACBITS)))
+		return;
+
+	lavafall = P_SpawnMobjFromMobj(actor, 0, 0, -8*FRACUNIT, MT_LAVAFALL_LAVA);
+	lavafall->momz = -P_MobjFlip(actor)*25*FRACUNIT;
+}
+
+// Function: A_FallingLavaCheck
+//
+// Description: If actor hits the ground or a water surface, enter the death animation.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_FallingLavaCheck(mobj_t *actor)
+{
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_FallingLavaCheck", actor))
+		return;
+#endif
+
+	if (actor->eflags & MFE_TOUCHWATER || P_IsObjectOnGround(actor))
+	{
+		actor->flags = MF_NOGRAVITY|MF_NOCLIPTHING;
+		actor->momz = 0;
+		if (actor->eflags & MFE_TOUCHWATER)
+			actor->z = (actor->eflags & MFE_VERTICALFLIP) ? actor->waterbottom : actor->watertop;
+		P_SetMobjState(actor, actor->info->deathstate);
+	}
+}
+
+// Function: A_FireShrink
+//
+// Description: Shrink the actor down to the specified scale at the specified speed.
+//
+// var1 = Scale to shrink to
+// var2 = Shrinking speed
+//
+void A_FireShrink(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_FireShrink", actor))
+		return;
+#endif
+
+	actor->destscale = locvar1;
+	actor->scalespeed = FRACUNIT/locvar2;
+}
+
+// Function: A_SpawnPterabytes
+//
+// Description: Spawn Pterabytes around the actor in a circle.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_SpawnPterabytes(mobj_t *actor)
+{
+	mobj_t *waypoint, *ptera;
+	fixed_t c, s;
+	fixed_t rad = 280*FRACUNIT;
+	angle_t ang = 0;
+	angle_t interval, fa;
+	UINT8 amount = 1;
+	UINT8 i;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_SpawnPterabytes", actor))
+		return;
+#endif
+
+	if (actor->spawnpoint)
+		amount = actor->spawnpoint->extrainfo + 1;
+
+	interval = FixedAngle(FRACUNIT*360/amount);
+
+	for (i = 0; i < amount; i++)
+	{
+		fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
+		c = FINECOSINE(fa);
+		s = FINESINE(fa);
+		waypoint = P_SpawnMobjFromMobj(actor, FixedMul(c, rad), FixedMul(s, rad), 0, MT_PTERABYTEWAYPOINT);
+		waypoint->angle = ang + ANGLE_90;
+		P_SetTarget(&waypoint->tracer, actor);
+		ptera = P_SpawnMobjFromMobj(waypoint, 0, 0, 0, MT_PTERABYTE);
+		ptera->angle = waypoint->angle;
+		P_SetTarget(&ptera->tracer, waypoint);
+		ptera->extravalue1 = 0;
+		ang += interval;
+	}
+}
+
+// Function: A_PterabyteHover
+//
+// Description: Hover in a circular fashion, bobbing up and down slightly.
+//
+// var1 = unused
+// var2 = unused
+//
+void A_PterabyteHover(mobj_t *actor)
+{
+	angle_t ang, fa;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_PterabyteHover", actor))
+		return;
+#endif
+
+	P_InstaThrust(actor, actor->angle, actor->info->speed);
+	actor->angle += ANG1;
+	actor->extravalue1 = (actor->extravalue1 + 3) % 360;
+	ang = actor->extravalue1*ANG1;
+	fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
+	actor->z += FINESINE(fa);
+}
+// Function: A_RolloutSpawn
+//
+// Description: Spawns a new Rollout Rock when the currently spawned rock is destroyed or moves far enough away.
+//
+// var1 = Distance currently spawned rock should travel before spawning a new one
+// var2 = Object type to spawn
+//
+void A_RolloutSpawn(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_RolloutSpawn", actor))
+		return;
+#endif
+
+	if (!(actor->target)
+		|| P_MobjWasRemoved(actor->target)
+		|| P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) > locvar1)
+	{
+		actor->target = P_SpawnMobj(actor->x, actor->y, actor->z, locvar2);
+		actor->target->flags2 |= (actor->flags2 & (MF2_AMBUSH | MF2_OBJECTFLIP)) | MF2_SLIDEPUSH;
+		actor->target->eflags |= (actor->eflags & MFE_VERTICALFLIP);
+
+		if (actor->target->flags2 & MF2_AMBUSH)
+		{
+			actor->target->color = SKINCOLOR_SUPERRUST3;
+			actor->target->colorized = true;
+		}
+	}
+}
+
+// Function: A_RolloutRock
+//
+// Description: Thinker for Rollout Rock.
+//
+// var1 = Drag
+// var2 = Vertical bobbing speed factor
+//
+void A_RolloutRock(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	UINT8 maxframes = actor->info->reactiontime; // number of frames the mobj cycles through
+	fixed_t pi = (22*FRACUNIT/7);
+	fixed_t circumference = FixedMul(2 * pi, actor->radius); // used to calculate when to change frame
+	fixed_t speed = P_AproxDistance(actor->momx, actor->momy), topspeed = FixedMul(actor->info->speed, actor->scale);
+	boolean inwater = actor->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER);
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_RolloutRock", actor))
+		return;
+#endif
+
+	actor->friction = FRACUNIT; // turns out riding on solids sucks, so let's just make it easier on ourselves
+
+	if (actor->threshold)
+		actor->threshold--;
+
+	if (inwater && !(actor->flags2 & MF2_AMBUSH)) // buoyancy in water (or lava)
+	{
+		UINT8 flip = P_MobjFlip(actor);
+		fixed_t prevmomz = actor->momz;
+		actor->momz = FixedMul(actor->momz, locvar2);
+		actor->momz += flip * FixedMul(locvar2, actor->scale);
+		if (flip*prevmomz < 0 && flip*actor->momz >= 0 && !actor->threshold)
+		{
+			if (actor->eflags & MFE_UNDERWATER)
+				S_StartSound(actor, sfx_splash);
+			else if (!actor->threshold)
+				S_StartSound(actor, sfx_splish);
+			actor->threshold = max((topspeed - speed) >> FRACBITS, 8);
+		}
+	}
+
+	if (speed > topspeed) // cap speed
+	{
+		actor->momx = FixedMul(FixedDiv(actor->momx, speed), topspeed);
+		actor->momy = FixedMul(FixedDiv(actor->momy, speed), topspeed);
+	}
+
+	if (P_IsObjectOnGround(actor) || inwater) // apply drag to speed (compensates for lack of friction but also works in liquids)
+	{
+		actor->momx = FixedMul(actor->momx, locvar1);
+		actor->momy = FixedMul(actor->momy, locvar1);
+	}
+
+	speed = P_AproxDistance(actor->momx, actor->momy); // recalculate speed for visual rolling
+
+	if (speed < actor->scale >> 1) // stop moving if speed is insignificant
+	{
+		actor->momx = 0;
+		actor->momy = 0;
+	}
+	else if (speed > actor->scale)
+	{
+		actor->movecount = 1; // rock has moved; fuse should be set so we don't have a trillion rocks lying around
+		actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy); // set rock's angle to movement direction
+		actor->movefactor += speed;
+		if (actor->movefactor > circumference / maxframes) // if distance moved is enough to change frame, change it!
+		{
+			actor->reactiontime++;
+			actor->reactiontime %= maxframes;
+			actor->movefactor = 0;
+		}
+	}
+
+	actor->frame = actor->reactiontime % maxframes; // set frame
+
+	if (!(actor->flags & MF_PUSHABLE)) // if being ridden, don't disappear
+		actor->fuse = 0;
+	else if (!actor->fuse && actor->movecount == 1) // otherwise if rock has moved, set its fuse
+		actor->fuse = actor->info->painchance;
+
+	if (actor->fuse && actor->fuse < 2*TICRATE)
+		actor->flags2 ^= MF2_DONTDRAW;
+
+}
diff --git a/src/p_floor.c b/src/p_floor.c
index 7887dc530a6291adb50adb1304baee52197fe440..1360375a751c3930ea97bc153b8a07dd44a501f2 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -1778,6 +1778,7 @@ static mobj_t *SearchMarioNode(msecnode_t *node)
 		case MT_RAIN:
 		case MT_SNOWFLAKE:
 		case MT_SPLISH:
+		case MT_LAVASPLISH:
 		case MT_SMOKE:
 		case MT_SMALLBUBBLE:
 		case MT_MEDIUMBUBBLE:
@@ -2424,7 +2425,7 @@ void T_RaiseSector(levelspecthink_t *raise)
 	mobj_t *thing;
 	sector_t *sector;
 	INT32 i;
-	boolean playeronme = false;
+	boolean playeronme = false, active = false;
 	fixed_t ceilingdestination, floordestination;
 	result_e res = 0;
 
@@ -2458,8 +2459,53 @@ void T_RaiseSector(levelspecthink_t *raise)
 			break;
 		}
 	}
+	
+	if (raise->vars[9]) // Dynamically Sinking Platform^tm
+	{
+#define shaketime 10
+		if (raise->vars[11] > shaketime) // State: moving
+		{
+			if (playeronme) // If player is standing on the platform, accelerate
+			{
+				raise->vars[10] += (FRACUNIT >> 5);
+			}
+			else // otherwise, decelerate until inflection
+			{
+				raise->vars[10] -= FRACUNIT >> 3;
+				if (raise->vars[10] <= 0) // inflection!
+				{
+					raise->vars[10] = 0;
+					raise->vars[11] = 0; // allow the shake to occur again (fucks over players attempting to jump-cheese)
+				}
+			}
+			active = raise->vars[10] > 0;
+		}
+		else // State: shaking
+		{
+			if (playeronme || raise->vars[11])
+			{
+				active = true;
+				if (++raise->vars[11] > shaketime)
+				{
+					if (playeronme)
+						raise->vars[10] = FRACUNIT >> 5;
+					else
+						raise->vars[10] = FRACUNIT << 1;
+				}
+				else
+				{
+					raise->vars[10] = ((shaketime/2) - raise->vars[11]) << FRACBITS;
+					if (raise->vars[10] < -raise->vars[2]/2)
+						raise->vars[10] = -raise->vars[2]/2;
+				}
+			}
+		}
+#undef shaketime
+	}
+	else // Air bobbing platform (not a Dynamically Sinking Platform^tm)
+		active = playeronme;
 
-	if (playeronme)
+	if (active)
 	{
 		raise->vars[3] = raise->vars[2];
 
@@ -2553,6 +2599,8 @@ void T_RaiseSector(levelspecthink_t *raise)
 			raise->vars[3] = origspeed;
 	}
 
+	raise->vars[3] += raise->vars[10];
+
 	res = T_MovePlane
 	(
 		raise->sector,         // sector
diff --git a/src/p_inter.c b/src/p_inter.c
index 403f36b53943b411f2bc78782a4378920211db5f..9017f795d38d6066c9512c37298e581560a02aaa 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -428,7 +428,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					  || special->state == &states[S_FANG_BOUNCE4]
 					  || special->state == &states[S_FANG_PINCHBOUNCE3]
 					  || special->state == &states[S_FANG_PINCHBOUNCE4])
-					&& P_MobjFlip(special)*((special->z + special->height/2) - (toucher->z - toucher->height/2)) > -(special->height/4))
+					&& P_MobjFlip(special)*((special->z + special->height/2) - (toucher->z - toucher->height/2)) > (special->height/4))
 					{
 						P_DamageMobj(toucher, special, special, 1, 0);
 						P_SetTarget(&special->tracer, toucher);
@@ -450,12 +450,18 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					}
 				}
 				break;
+			case MT_PYREFLY:
+				if (special->extravalue2 == 2 && P_DamageMobj(player->mo, special, special, 1, DMG_FIRE))
+					return;
 			default:
 				break;
 		}
 
 		if (P_PlayerCanDamage(player, special)) // Do you possess the ability to subdue the object?
 		{
+			if (special->type == MT_PTERABYTE && special->target == player->mo && special->extravalue1 == 1)
+				return; // Can't hurt a Pterabyte if it's trying to pick you up
+
 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
 			{
 				if (elementalpierce == 2)
@@ -471,13 +477,29 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				toucher->momy = -toucher->momy;
 				if (player->charability == CA_FLY && player->panim == PA_ABILITY)
 					toucher->momz = -toucher->momz/2;
+				else if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
+				{
+					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
+					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+					toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
+					toucher->momx = 7*toucher->momx>>3;
+					toucher->momy = 7*toucher->momy>>3;
+				}
+				else if (player->dashmode >= DASHMODE_THRESHOLD && (player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)
+					&& player->panim == PA_DASH)
+					P_DoPlayerPain(player, special, special);
 			}
 			P_DamageMobj(special, toucher, toucher, 1, 0);
 			if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
 				P_TwinSpinRejuvenate(player, player->thokitem);
 		}
 		else
+		{
+			if (special->type == MT_PTERABYTE && special->target == player->mo)
+				return; // Don't hurt the player you're trying to grab
+
 			P_DamageMobj(toucher, special, special, 1, 0);
+		}
 
 		return;
 	}
@@ -1074,7 +1096,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->exiting)
 				return;
 
-			if (player->bumpertime < TICRATE/4)
+			if (player->bumpertime <= (TICRATE/2)-5)
 			{
 				S_StartSound(toucher, special->info->seesound);
 				if (player->powers[pw_carry] == CR_NIGHTSMODE)
@@ -1482,8 +1504,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					P_SetMobjState(mo2, mo2->info->painstate);
 				}
 			}
-
-			S_StartSound(toucher, special->info->painsound);
 			return;
 
 		case MT_FAKEMOBILE:
@@ -1509,10 +1529,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				toucher->momx = P_ReturnThrustX(special, angle, touchspeed);
 				toucher->momy = P_ReturnThrustY(special, angle, touchspeed);
 				toucher->momz = -toucher->momz;
-				if (player->pflags & PF_GLIDING)
+				if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
 				{
 					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+					toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
+					toucher->momx = 7*toucher->momx>>3;
+					toucher->momy = 7*toucher->momy>>3;
 				}
 				player->homing = 0;
 
@@ -1557,10 +1580,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					toucher->momx = P_ReturnThrustX(special, special->angle, touchspeed);
 					toucher->momy = P_ReturnThrustY(special, special->angle, touchspeed);
 					toucher->momz = -toucher->momz;
-					if (player->pflags & PF_GLIDING)
+					if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
 					{
 						player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 						P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+						toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
+						toucher->momx = 7*toucher->momx>>3;
+						toucher->momy = 7*toucher->momy>>3;
 					}
 					player->homing = 0;
 
@@ -1765,7 +1791,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			return;
 
 		case MT_MINECARTSPAWNER:
-			if (!special->fuse || player->powers[pw_carry] != CR_MINECART)
+			if (!player->bot && (special->fuse < TICRATE || player->powers[pw_carry] != CR_MINECART))
 			{
 				mobj_t *mcart = P_SpawnMobj(special->x, special->y, special->z, MT_MINECART);
 				P_SetTarget(&mcart->target, toucher);
@@ -1775,7 +1801,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_ResetPlayer(player);
 				player->pflags |= PF_JUMPDOWN;
 				player->powers[pw_carry] = CR_MINECART;
-				toucher->player->pflags &= ~PF_APPLYAUTOBRAKE;
+				player->pflags &= ~PF_APPLYAUTOBRAKE;
 				P_SetTarget(&toucher->tracer, mcart);
 				toucher->momx = toucher->momy = toucher->momz = 0;
 
@@ -2457,6 +2483,28 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY;
 		P_SetThingPosition(target);
 
+		if (target->player->powers[pw_super])
+		{
+			target->player->powers[pw_super] = 0;
+			if (P_IsLocalPlayer(target->player))
+			{
+				music_stack_noposition = true; // HACK: Do not reposition next music
+				music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
+			}
+			P_RestoreMusic(target->player);
+
+			if (gametype != GT_COOP)
+			{
+				HU_SetCEchoFlags(0);
+				HU_SetCEchoDuration(5);
+				HU_DoCEcho(va("%s\\is no longer super.\\\\\\\\", player_names[target->player-players]));
+			}
+		}
+
+		target->color = target->player->skincolor;
+		target->colorized = false;
+		G_GhostAddColor(GHC_NORMAL);
+
 		if ((target->player->lives <= 1) && (netgame || multiplayer) && (gametype == GT_COOP) && (cv_cooplives.value == 0))
 			;
 		else if (!target->player->bot && !target->player->spectator && !G_IsSpecialStage(gamemap) && (target->player->lives != INFLIVES)
@@ -2602,6 +2650,14 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			}
 			break;
 
+		case MT_BANPYURA:
+			if (target->tracer)
+			{
+				S_StopSound(target->tracer);
+				P_KillMobj(target->tracer, inflictor, source, damagetype);
+			}
+			break;
+
 		case MT_EGGSHIELD:
 			P_SetObjectMomZ(target, 4*target->scale, false);
 			P_InstaThrust(target, target->angle, 3*target->scale);
diff --git a/src/p_local.h b/src/p_local.h
index 4a58650272b7c8ba20c69ef3db283872c958fa57..02f497850d23b02d0331501ab80908e358d64916 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -303,7 +303,7 @@ fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, f
 
 boolean P_InsideANonSolidFFloor(mobj_t *mobj, ffloor_t *rover);
 boolean P_CheckDeathPitCollide(mobj_t *mo);
-boolean P_CheckSolidLava(mobj_t *mo, ffloor_t *rover);
+boolean P_CheckSolidLava(ffloor_t *rover);
 void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype);
 
 mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zofs, mobjtype_t type);
diff --git a/src/p_map.c b/src/p_map.c
index 8035d64a576386c2b22475f4be1958eecf62906a..159489f708c64f5262e7b8c2fa86552608c0944d 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -124,6 +124,7 @@ boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
 //     Positive spring modes are minor variants of vanilla spring behaviour.
 //       1 = launch players in jump
 //       2 = don't modify player at all, just add momentum
+//       3 = speed-booster mode (force onto ground, MF_AMBUSH causes auto-spin)
 //     Negative spring modes are mildly-related gimmicks with customisation.
 //      -1 = pinball bumper
 //     Any other spring mode defaults to standard vanilla spring behaviour,
@@ -151,7 +152,9 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 
 	if (object->player)
 	{
-		if (object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
+		if (spring->info->painchance == 3)
+			;
+		else if (object->player->charability == CA_TWINSPIN && object->player->panim == PA_ABILITY)
 			strong = 1;
 		else if (object->player->charability2 == CA2_MELEE && object->player->panim == PA_ABILITY2)
 			strong = 2;
@@ -208,7 +211,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			{
 				angle_t nightsangle = 0;
 
-				if (object->player->bumpertime >= TICRATE/4)
+				if (object->player->bumpertime > (TICRATE/2)-5)
 					return false;
 
 				if ((object->player->pflags & PF_TRANSFERTOCLOSEST) && object->player->axis1 && object->player->axis2)
@@ -286,7 +289,27 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 	if (spring->info->painchance != 2)
 	{
 		if (object->player)
+		{
 			object->player->pflags &= ~PF_APPLYAUTOBRAKE;
+#ifndef SPRINGSPIN
+			object->player->powers[pw_justsprung] = 5;
+			if (horizspeed)
+				object->player->powers[pw_noautobrake] = ((horizspeed*TICRATE)>>(FRACBITS+3))/9; // TICRATE at 72*FRACUNIT
+			else if (P_MobjFlip(object) == P_MobjFlip(spring))
+				object->player->powers[pw_justsprung] |= (1<<15);
+#else
+			object->player->powers[pw_justsprung] = 15;
+			if (horizspeed)
+				object->player->powers[pw_noautobrake] = ((horizspeed*TICRATE)>>(FRACBITS+3))/9; // TICRATE at 72*FRACUNIT
+			else
+			{
+				if (abs(object->player->rmomx) > object->scale || abs(object->player->rmomy) > object->scale)
+					object->player->drawangle = R_PointToAngle2(0, 0, object->player->rmomx, object->player->rmomy);
+				if (P_MobjFlip(object) == P_MobjFlip(spring))
+					object->player->powers[pw_justsprung] |= (1<<15);
+			}
+#endif
+		}
 
 		if ((horizspeed && vertispeed) || (object->player && object->player->homing)) // Mimic SA
 		{
@@ -321,6 +344,14 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 
 			// Set position!
 			P_TryMove(object, spring->x + offx, spring->y + offy, true);
+
+			if ((spring->info->painchance == 3))
+			{
+				object->z = spring->z;
+				if (spring->eflags & MFE_VERTICALFLIP)
+					object->z -= object->height;
+				object->momz = 0;
+			}
 		}
 	}
 
@@ -344,8 +375,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 
 		if (horizspeed)
 		{
-			object->player->drawangle = spring->angle;
-			object->angle = spring->angle;
+			object->angle = object->player->drawangle = spring->angle;
 
 			if (!demoplayback || P_AnalogMove(object->player))
 			{
@@ -356,11 +386,25 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			}
 		}
 
-		pflags = object->player->pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_BOUNCING); // I still need these.
-		secondjump = object->player->secondjump;
-		washoming = object->player->homing;
 		if (object->player->pflags & PF_GLIDING)
 			P_SetPlayerMobjState(object, S_PLAY_FALL);
+		if ((spring->info->painchance == 3))
+		{
+			if (!(pflags = (object->player->pflags & PF_SPINNING)) &&
+				(((object->player->charability2 == CA2_SPINDASH) && (object->player->cmd.buttons & BT_USE))
+				|| (spring->flags2 & MF2_AMBUSH)))
+			{
+				pflags = PF_SPINNING;
+				P_SetPlayerMobjState(object, S_PLAY_ROLL);
+				S_StartSound(object, sfx_spin);
+			}
+			else
+				P_SetPlayerMobjState(object, S_PLAY_ROLL);
+		}
+		else
+			pflags = object->player->pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_BOUNCING); // I still need these.
+		secondjump = object->player->secondjump;
+		washoming = object->player->homing;
 		P_ResetPlayer(object->player);
 
 		if (spring->info->painchance == 1) // For all those ancient, SOC'd abilities.
@@ -368,7 +412,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			object->player->pflags |= P_GetJumpFlags(object->player);
 			P_SetPlayerMobjState(object, S_PLAY_JUMP);
 		}
-		else if ((spring->info->painchance == 2) || (pflags & PF_BOUNCING)) // Adding momentum only.
+		else if ((spring->info->painchance == 2) || ((spring->info->painchance != 3) && (pflags & PF_BOUNCING))) // Adding momentum only.
 		{
 			object->player->pflags |= (pflags &~ PF_STARTJUMP);
 			object->player->secondjump = secondjump;
@@ -382,6 +426,10 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 				object->player->pflags |= pflags;
 				object->player->secondjump = secondjump;
 			}
+			else if (object->player->dashmode >= 3*TICRATE)
+				P_SetPlayerMobjState(object, S_PLAY_DASH);
+			else if (P_IsObjectOnGround(object) && horizspeed >= FixedMul(object->player->runspeed, object->scale))
+				P_SetPlayerMobjState(object, S_PLAY_RUN);
 			else
 				P_SetPlayerMobjState(object, S_PLAY_WALK);
 		}
@@ -490,6 +538,42 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
 	}
 }
 
+static void P_DoPterabyteCarry(player_t *player, mobj_t *ptera)
+{
+	if (player->powers[pw_carry] && players->powers[pw_carry] != CR_ROLLOUT)
+		return;
+	if (ptera->extravalue1 != 1)
+		return; // Not swooping
+	if (ptera->target != player->mo)
+		return; // Not swooping for you!
+
+	if (player->spectator)
+		return;
+
+	if ((player->mo->eflags & MFE_VERTICALFLIP) != (ptera->eflags & MFE_VERTICALFLIP))
+		return; // Both should be in same gravity
+
+	if (ptera->eflags & MFE_VERTICALFLIP)
+	{
+		if (ptera->ceilingz - (ptera->z + ptera->height) < player->mo->height - FixedMul(2*FRACUNIT, player->mo->scale))
+			return;
+	}
+	else if (ptera->z - ptera->floorz < player->mo->height - FixedMul(2*FRACUNIT, player->mo->scale))
+		return; // No room to pick up this guy!
+
+	P_ResetPlayer(player);
+	P_SetTarget(&player->mo->tracer, ptera);
+	player->pflags &= ~PF_APPLYAUTOBRAKE;
+	player->powers[pw_carry] = CR_PTERABYTE;
+	S_StartSound(player->mo, sfx_s3k4a);
+	P_UnsetThingPosition(player->mo);
+	player->mo->x = ptera->x;
+	player->mo->y = ptera->y;
+	P_SetThingPosition(player->mo);
+	ptera->movefactor = 3*TICRATE;
+	ptera->watertop = ptera->waterbottom = ptera->cusval = 0;
+}
+
 static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 {
 	INT32 p;
@@ -604,6 +688,34 @@ static void P_SlapStick(mobj_t *fang, mobj_t *pole)
 	P_SetTarget(&pole->tracer, NULL);
 }
 
+static void P_PlayerBarrelCollide(mobj_t *toucher, mobj_t *barrel)
+{
+	if (toucher->momz < 0)
+	{
+		if (toucher->z + toucher->momz > barrel->z + barrel->height)
+			return;
+	}
+	else
+	{
+		if (toucher->z > barrel->z + barrel->height)
+			return;
+	}
+
+	if (toucher->momz > 0)
+	{
+		if (toucher->z + toucher->height + toucher->momz < barrel->z)
+			return;
+	}
+	else
+	{
+		if (toucher->z + toucher->height < barrel->z)
+			return;
+	}
+
+	if (P_PlayerCanDamage(toucher->player, barrel))
+		P_DamageMobj(barrel, toucher, toucher, 1, 0);
+}
+
 //
 // PIT_CheckThing
 //
@@ -854,6 +966,15 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	}
 #endif
 
+	if (tmthing->type == MT_LAVAFALL_LAVA && (thing->type == MT_RING || thing->type == MT_REDTEAMRING || thing->type == MT_BLUETEAMRING || thing->type == MT_FLINGRING))
+	{
+		//height check
+		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !(thing->health))
+			return true;
+
+		P_KillMobj(thing, tmthing, tmthing, DMG_FIRE);
+	}
+
 	if (tmthing->type == MT_MINECART)
 	{
 		//height check
@@ -872,12 +993,11 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	if (thing->type == MT_SALOONDOOR && tmthing->player)
 	{
-		if (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer) && tmthing->tracer->health)
+		mobj_t *ref = (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)) ? tmthing->tracer : tmthing;
+		if ((thing->flags & MF2_AMBUSH) || ref != tmthing)
 		{
-			fixed_t dx = tmthing->tracer->momx;
-			fixed_t dy = tmthing->tracer->momy;
-			fixed_t dm = min(FixedHypot(dx, dy), 16*FRACUNIT);
-			angle_t ang = R_PointToAngle2(0, 0, dx, dy) - thing->angle;
+			fixed_t dm = min(FixedHypot(ref->momx, ref->momy), 16*FRACUNIT);
+			angle_t ang = R_PointToAngle2(0, 0, ref->momx, ref->momy) - thing->angle;
 			fixed_t s = FINESINE((ang >> ANGLETOFINESHIFT) & FINEMASK);
 			S_StartSound(tmthing, thing->info->activesound);
 			thing->extravalue2 += 2*FixedMul(s, dm)/3;
@@ -885,41 +1005,73 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 	}
 
-	if (thing->type == MT_TNTBARREL && tmthing->player)
+	if (thing->type == MT_SALOONDOORCENTER && tmthing->player)
+	{
+		if ((thing->flags & MF2_AMBUSH) || (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)))
+			return true;
+	}
+
+	if (thing->type == MT_ROLLOUTROCK && tmthing->player && tmthing->health)
 	{
-		if (tmthing->momz < 0)
+		if (tmthing->player->powers[pw_carry] == CR_ROLLOUT)
 		{
-			if (tmthing->z + tmthing->momz > thing->z + thing->height)
-				return true;
+			return true;
 		}
-		else
+		if ((thing->flags & MF_PUSHABLE) // not carrying a player
+			&& (tmthing->player->powers[pw_carry] == CR_NONE) // player is not already riding something
+			&& ((tmthing->eflags & MFE_VERTICALFLIP) == (thing->eflags & MFE_VERTICALFLIP))
+			&& (P_AproxDistance(thing->x - tmthing->x, thing->y - tmthing->y) < (thing->radius))
+			&& (P_MobjFlip(tmthing)*tmthing->momz <= 0)
+			&& ((!(tmthing->eflags & MFE_VERTICALFLIP) && abs(thing->z + thing->height - tmthing->z) < (thing->height>>2))
+				|| (tmthing->eflags & MFE_VERTICALFLIP && abs(tmthing->z + tmthing->height - thing->z) < (thing->height>>2))))
 		{
-			if (tmthing->z > thing->z + thing->height)
-				return true;
+			thing->flags &= ~MF_PUSHABLE; // prevent riding player from applying pushable movement logic
+			thing->flags2 &= ~MF2_DONTDRAW; // don't leave the rock invisible if it was flashing prior to boarding
+			P_SetTarget(&thing->tracer, tmthing);
+			P_ResetPlayer(tmthing->player);
+			P_SetPlayerMobjState(tmthing, S_PLAY_WALK);
+			tmthing->player->powers[pw_carry] = CR_ROLLOUT;
+			P_SetTarget(&tmthing->tracer, thing);
+			P_SetObjectMomZ(thing, tmthing->momz, true);
+			return true;
 		}
+	}
+	else if (tmthing->type == MT_ROLLOUTROCK)
+	{
+		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !thing->health)
+			return true;
+		
+		if (thing == tmthing->tracer) // don't collide with rider
+			return true;
 
-		if (tmthing->momz > 0)
+		if (thing->flags & MF_SPRING) // bounce on springs
 		{
-			if (tmthing->z + tmthing->height + tmthing->momz < thing->z)
-				return true;
+			P_DoSpring(thing, tmthing);
+			return true;
 		}
-		else
+		else if ((thing->flags & (MF_MONITOR|MF_SHOOTABLE)) == (MF_MONITOR|MF_SHOOTABLE) && !(tmthing->flags & MF_PUSHABLE)) // pop monitors while carrying a player
 		{
-			if (tmthing->z + tmthing->height < thing->z)
-				return true;
+			P_KillMobj(thing, tmthing, tmthing->tracer, 0);
+			return true;
+		}
+		
+		if (thing->type == tmthing->type // bounce against other rollout rocks
+			&& (tmthing->momx || tmthing->momy || thing->momx || thing->momy))
+		{
+			fixed_t tempmomx = thing->momx, tempmomy = thing->momy;
+			thing->momx = tmthing->momx;
+			thing->momy = tmthing->momy;
+			tmthing->momx = tempmomx;
+			tmthing->momy = tempmomy;
 		}
-
-		if ((tmthing->player->pflags & (PF_SPINNING | PF_GLIDING))
-			|| ((tmthing->player->pflags & PF_JUMPED)
-				&& (!(tmthing->player->pflags & PF_NOJUMPDAMAGE)
-					|| (tmthing->player->charability == CA_TWINSPIN && tmthing->player->panim == PA_ABILITY)))
-			|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2)
-			|| ((tmthing->player->charflags & SF_STOMPDAMAGE || tmthing->player->pflags & PF_BOUNCING)
-				&& (P_MobjFlip(tmthing)*(tmthing->z - (thing->z + thing->height / 2)) > 0) && (P_MobjFlip(tmthing)*tmthing->momz < 0))
-			|| (((tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (tmthing->player->pflags & PF_SHIELDABILITY)))
-			P_DamageMobj(thing, tmthing, tmthing, 1, 0);
 	}
 
+	if (thing->type == MT_PTERABYTE && tmthing->player)
+		P_DoPterabyteCarry(tmthing->player, thing);
+
+	if (thing->type == MT_TNTBARREL && tmthing->player)
+		P_PlayerBarrelCollide(tmthing, thing);
+
 	if (thing->type == MT_VULTURE && tmthing->type == MT_VULTURE)
 	{
 		fixed_t dx = thing->x - tmthing->x;
@@ -1560,8 +1712,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 	}
 
-	if ((!tmthing->player) && (thing->player))
-		; // no solid thing should ever be able to step up onto a player
+	if ((tmthing->flags & MF_SPRING || tmthing->type == MT_STEAM) && (thing->player))
+		; // springs and gas jets should never be able to step up onto a player
 	// z checking at last
 	// Treat noclip things as non-solid!
 	else if ((thing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID
@@ -1964,7 +2116,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 				continue;
 			}
 
-			if (thing->player && (P_CheckSolidLava(thing, rover) || P_CanRunOnWater(thing->player, rover)))
+			if (thing->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(thing->player, rover)))
 				;
 			else if (thing->type == MT_SKIM && (rover->flags & FF_SWIMMABLE))
 				;
@@ -2629,8 +2781,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 						thing->eflags |= MFE_JUSTSTEPPEDDOWN;
 					}
 #ifdef ESLOPE
-					// HACK TO FIX DSZ2: apply only if slopes are involved
-					else if (tmceilingslope && tmceilingz < thingtop && thingtop - tmceilingz <= maxstep)
+					else if (tmceilingz < thingtop && thingtop - tmceilingz <= maxstep)
 					{
 						thing->z = (thing->ceilingz = thingtop = tmceilingz) - thing->height;
 						thing->ceilingrover = tmceilingrover;
@@ -2645,8 +2796,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
 				}
 #ifdef ESLOPE
-				// HACK TO FIX DSZ2: apply only if slopes are involved
-				else if (tmfloorslope && tmfloorz > thing->z && tmfloorz - thing->z <= maxstep)
+				else if (tmfloorz > thing->z && tmfloorz - thing->z <= maxstep)
 				{
 					thing->z = thing->floorz = tmfloorz;
 					thing->floorrover = tmfloorrover;
@@ -3334,13 +3484,13 @@ isblocking:
 			&& canclimb)
 			{
 				slidemo->angle = climbangle;
-				if (!demoplayback || P_AnalogMove(slidemo->player))
+				/*if (!demoplayback || P_AnalogMove(slidemo->player))
 				{
 					if (slidemo->player == &players[consoleplayer])
 						localangle = slidemo->angle;
 					else if (slidemo->player == &players[secondarydisplayplayer])
 						localangle2 = slidemo->angle;
-				}
+				}*/
 
 				if (!slidemo->player->climbing)
 				{
@@ -3479,6 +3629,64 @@ stairstep:
 		goto retry;
 }
 
+static void P_CheckLavaWall(mobj_t *mo, sector_t *sec)
+{
+	ffloor_t *rover;
+	fixed_t topheight, bottomheight;
+
+	for (rover = sec->ffloors; rover; rover = rover->next)
+	{
+		if (!(rover->flags & FF_EXISTS))
+			continue;
+
+		if (!(rover->flags & FF_SWIMMABLE))
+			continue;
+
+		if (GETSECSPECIAL(rover->master->frontsector->special, 1) != 3)
+			continue;
+
+		if (rover->master->flags & ML_BLOCKMONSTERS)
+			continue;
+
+		topheight =
+#ifdef ESLOPE
+			*rover->t_slope ? P_GetZAt(*rover->t_slope, mo->x, mo->y) :
+#endif
+			*rover->topheight;
+
+		if (mo->eflags & MFE_VERTICALFLIP)
+		{
+			if (topheight < mo->z - mo->height)
+				continue;
+		}
+		else
+		{
+			if (topheight < mo->z)
+				continue;
+		}
+
+		bottomheight =
+#ifdef ESLOPE
+			*rover->b_slope ? P_GetZAt(*rover->b_slope, mo->x, mo->y) :
+#endif
+			*rover->bottomheight;
+
+		if (mo->eflags & MFE_VERTICALFLIP)
+		{
+			if (bottomheight > mo->z)
+				continue;
+		}
+		else
+		{
+			if (bottomheight > mo->z + mo->height)
+				continue;
+		}
+
+		P_DamageMobj(mo, NULL, NULL, 1, DMG_FIRE);
+		return;
+	}
+}
+
 //
 // P_SlideMove
 // The momx / momy move is bad, so try to slide
@@ -3639,6 +3847,12 @@ retry:
 	P_PathTraverse(leadx, traily, leadx + mo->momx, traily + mo->momy,
 		PT_ADDLINES, PTR_SlideTraverse);
 
+	if (bestslideline && mo->player && bestslideline->sidenum[1] != 0xffff)
+	{
+		sector_t *sec = P_PointOnLineSide(mo->x, mo->y, bestslideline) ? bestslideline->frontsector : bestslideline->backsector;
+		P_CheckLavaWall(mo, sec);
+	}
+
 	// Some walls are bouncy even if you're not
 	if (bestslideline && bestslideline->flags & ML_BOUNCY)
 	{
diff --git a/src/p_maputl.c b/src/p_maputl.c
index 45fc82fbfe2b0b97e9ee27b7491d8de0edebd6fc..111103294c659d38b8286a36597fa9c97d76f951 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -662,7 +662,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				if (!(rover->flags & FF_EXISTS))
 					continue;
 
-				if (mobj->player && (P_CheckSolidLava(mobj, rover) || P_CanRunOnWater(mobj->player, rover)))
+				if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
 					;
 				else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player)
 					|| (rover->flags & FF_BLOCKOTHERS && !mobj->player)))
@@ -674,7 +674,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				delta1 = abs(mobj->z - (bottomheight + ((topheight - bottomheight)/2)));
 				delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
 
-				if (delta1 >= delta2 && !(rover->flags & FF_PLATFORM)) // thing is below FOF
+				if (delta1 >= delta2 && (rover->flags & FF_INTANGABLEFLATS) != FF_PLATFORM) // thing is below FOF
 				{
 					if (bottomheight < opentop) {
 						opentop = bottomheight;
@@ -687,7 +687,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 						highceiling = bottomheight;
 				}
 
-				if (delta1 < delta2 && !(rover->flags & FF_REVERSEPLATFORM)) // thing is above FOF
+				if (delta1 < delta2 && (rover->flags & FF_INTANGABLEFLATS) != FF_REVERSEPLATFORM) // thing is above FOF
 				{
 					if (topheight > openbottom) {
 						openbottom = topheight;
@@ -708,7 +708,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				if (!(rover->flags & FF_EXISTS))
 					continue;
 
-				if (mobj->player && (P_CheckSolidLava(mobj, rover) || P_CanRunOnWater(mobj->player, rover)))
+				if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
 					;
 				else if (!((rover->flags & FF_BLOCKPLAYER && mobj->player)
 					|| (rover->flags & FF_BLOCKOTHERS && !mobj->player)))
@@ -720,7 +720,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				delta1 = abs(mobj->z - (bottomheight + ((topheight - bottomheight)/2)));
 				delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
 
-				if (delta1 >= delta2 && !(rover->flags & FF_PLATFORM)) // thing is below FOF
+				if (delta1 >= delta2 && (rover->flags & FF_INTANGABLEFLATS) != FF_PLATFORM) // thing is below FOF
 				{
 					if (bottomheight < opentop) {
 						opentop = bottomheight;
@@ -733,7 +733,7 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 						highceiling = bottomheight;
 				}
 
-				if (delta1 < delta2 && !(rover->flags & FF_REVERSEPLATFORM)) // thing is above FOF
+				if (delta1 < delta2 && (rover->flags & FF_INTANGABLEFLATS) != FF_REVERSEPLATFORM) // thing is above FOF
 				{
 					if (topheight > openbottom) {
 						openbottom = topheight;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 5c4609f046907792f771e6fc119fc4772e712b71..5735dc27bc8ca95811dac3ee854c7f7bcd27497f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1578,7 +1578,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 
 	// Goop has slower, reversed gravity
 	if (goopgravity)
-		gravityadd = -gravityadd/5;
+		gravityadd = -((gravityadd/5) + (gravityadd/8));
 
 	gravityadd = FixedMul(gravityadd, mo->scale);
 
@@ -1990,6 +1990,8 @@ void P_XYMovement(mobj_t *mo)
 				{
 					mo->momz = transfermomz;
 					mo->standingslope = NULL;
+					if (player->pflags & PF_SPINNING)
+						player->pflags = (player->pflags & ~PF_SPINNING) | (PF_JUMPED | PF_THOKKED);
 				}
 			}
 #endif
@@ -2222,7 +2224,7 @@ void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype)
 		topheight = P_GetFOFTopZ(mo, sector, rover, mo->x, mo->y, NULL);
 		bottomheight = P_GetFOFBottomZ(mo, sector, rover, mo->x, mo->y, NULL);
 
-		if (mo->player && (P_CheckSolidLava(mo, rover) || P_CanRunOnWater(mo->player, rover))) // only the player should stand on lava or run on water
+		if (mo->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mo->player, rover))) // only the player should stand on lava or run on water
 			;
 		else if (motype != 0 && rover->flags & FF_SWIMMABLE) // "scenery" only
 			continue;
@@ -2369,9 +2371,9 @@ boolean P_CheckDeathPitCollide(mobj_t *mo)
 		return false;
 
 	if (((mo->z <= mo->subsector->sector->floorheight
-		&& !(mo->eflags & MFE_VERTICALFLIP) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_FLOOR))
+		&& ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_FLOOR))
 	|| (mo->z + mo->height >= mo->subsector->sector->ceilingheight
-		&& (mo->eflags & MFE_VERTICALFLIP) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_CEILING)))
+		&& ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_CEILING)))
 	&& (GETSECSPECIAL(mo->subsector->sector->special, 1) == 6
 	|| GETSECSPECIAL(mo->subsector->sector->special, 1) == 7))
 		return true;
@@ -2379,43 +2381,11 @@ boolean P_CheckDeathPitCollide(mobj_t *mo)
 	return false;
 }
 
-boolean P_CheckSolidLava(mobj_t *mo, ffloor_t *rover)
+boolean P_CheckSolidLava(ffloor_t *rover)
 {
-	fixed_t topheight;
-
-	I_Assert(mo != NULL);
-	I_Assert(!P_MobjWasRemoved(mo));
-
-	// not a lava block with solid planes
-	if (!(rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
-		&& !(rover->master->flags & ML_BLOCKMONSTERS)))
-		return false;
-
-	// is solid from the sides
-	if (rover->master->flags & ML_EFFECT3)
-		return true;
-
-	if (mo->eflags & MFE_VERTICALFLIP)
-	{
-		topheight =
-	#ifdef ESLOPE
-			*rover->b_slope ? P_GetZAt(*rover->b_slope, mo->x, mo->y) :
-	#endif
-			*rover->bottomheight;
-
-		if (mo->z+mo->height-mo->momz < topheight + FixedMul(16*FRACUNIT, mo->scale))
-				return true;
-		return false;
-	}
-
-	topheight =
-	#ifdef ESLOPE
-		*rover->t_slope ? P_GetZAt(*rover->t_slope, mo->x, mo->y) :
-	#endif
-		*rover->topheight;
-
-	if (mo->z-mo->momz > topheight - FixedMul(16*FRACUNIT, mo->scale))
-		return true;
+	if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
+		&& !(rover->master->flags & ML_BLOCKMONSTERS))
+			return true;
 
 	return false;
 }
@@ -3350,7 +3320,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 	mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
 
 	// Reset water state.
-	mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER|MFE_GOOWATER);
+	mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER|MFE_GOOWATER|MFE_TOUCHLAVA);
 
 	for (rover = sector->ffloors; rover; rover = rover->next)
 	{
@@ -3391,16 +3361,18 @@ void P_MobjCheckWater(mobj_t *mobj)
 		// Just touching the water?
 		if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - height < bottomheight)
 		 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + height > topheight))
-		{
 			mobj->eflags |= MFE_TOUCHWATER;
-			if (rover->flags & FF_GOOWATER && !(mobj->flags & MF_NOGRAVITY))
-				mobj->eflags |= MFE_GOOWATER;
-		}
+
 		// Actually in the water?
 		if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - (height>>1) > bottomheight)
 		 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + (height>>1) < topheight))
-		{
 			mobj->eflags |= MFE_UNDERWATER;
+
+		if (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
+		{
+			if (GETSECSPECIAL(rover->master->frontsector->special, 1) == 3)
+				mobj->eflags |= MFE_TOUCHLAVA;
+
 			if (rover->flags & FF_GOOWATER && !(mobj->flags & MF_NOGRAVITY))
 				mobj->eflags |= MFE_GOOWATER;
 		}
@@ -3464,9 +3436,18 @@ void P_MobjCheckWater(mobj_t *mobj)
 			|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->ceilingz-mobj->waterbottom <= height>>1))
 			return;
 
-		if (!wasgroundpounding && (mobj->eflags & MFE_GOOWATER || wasingoo)) { // Decide what happens to your momentum when you enter/leave goopy water.
-			if (P_MobjFlip(mobj)*mobj->momz < 0) // You are entering the goo?
-				mobj->momz = FixedMul(mobj->momz, FixedDiv(2*FRACUNIT, 5*FRACUNIT)); // kill momentum significantly, to make the goo feel thick.
+		if (mobj->eflags & MFE_GOOWATER || wasingoo) { // Decide what happens to your momentum when you enter/leave goopy water.
+			if (P_MobjFlip(mobj)*mobj->momz > 0)
+			{
+				mobj->momz -= (mobj->momz/8); // cut momentum a little bit to prevent multiple bobs
+				//CONS_Printf("leaving\n");
+			}
+			else
+			{
+				if (!wasgroundpounding)
+					mobj->momz >>= 1; // kill momentum significantly, to make the goo feel thick.
+				//CONS_Printf("entering\n");
+			}
 		}
 		else if (wasinwater && P_MobjFlip(mobj)*mobj->momz > 0)
 			mobj->momz = FixedMul(mobj->momz, FixedDiv(780*FRACUNIT, 457*FRACUNIT)); // Give the mobj a little out-of-water boost.
@@ -3478,14 +3459,15 @@ void P_MobjCheckWater(mobj_t *mobj)
 			{
 				// Spawn a splash
 				mobj_t *splish;
+				mobjtype_t splishtype = (mobj->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
 				if (mobj->eflags & MFE_VERTICALFLIP)
 				{
-					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[MT_SPLISH].height, mobj->scale), MT_SPLISH);
+					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
 					splish->flags2 |= MF2_OBJECTFLIP;
 					splish->eflags |= MFE_VERTICALFLIP;
 				}
 				else
-					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, MT_SPLISH);
+					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
 				splish->destscale = mobj->scale;
 				P_SetScale(splish, mobj->scale);
 			}
@@ -3513,14 +3495,15 @@ void P_MobjCheckWater(mobj_t *mobj)
 			{
 				// Spawn a splash
 				mobj_t *splish;
+				mobjtype_t splishtype = (mobj->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
 				if (mobj->eflags & MFE_VERTICALFLIP)
 				{
-					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[MT_SPLISH].height, mobj->scale), MT_SPLISH);
+					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
 					splish->flags2 |= MF2_OBJECTFLIP;
 					splish->eflags |= MFE_VERTICALFLIP;
 				}
 				else
-					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, MT_SPLISH);
+					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
 				splish->destscale = mobj->scale;
 				P_SetScale(splish, mobj->scale);
 			}
@@ -3536,6 +3519,8 @@ void P_MobjCheckWater(mobj_t *mobj)
 
 			if (mobj->eflags & MFE_GOOWATER || wasingoo)
 				S_StartSound(mobj, sfx_ghit);
+			else if (mobj->eflags & MFE_TOUCHLAVA)
+				S_StartSound(mobj, sfx_splash);
 			else
 				S_StartSound(mobj, sfx_splish); // And make a sound!
 
@@ -4146,6 +4131,45 @@ void P_RainThinker(precipmobj_t *mobj)
 	P_SetPrecipMobjState(mobj, S_SPLASH1);
 }
 
+static void P_KillRingsInLava(mobj_t *mo)
+{
+	msecnode_t *node;
+	I_Assert(mo != NULL);
+	I_Assert(!P_MobjWasRemoved(mo));
+
+	// go through all sectors being touched by the ring
+	for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+	{
+		if (!node->m_sector)
+			break;
+
+		if (node->m_sector->ffloors)
+		{
+			ffloor_t *rover;
+			fixed_t topheight, bottomheight;
+
+			for (rover = node->m_sector->ffloors; rover; rover = rover->next) // go through all fofs in the sector
+			{
+				if (!(rover->flags & FF_EXISTS)) continue; // fof must be real
+
+				if (!(rover->flags & FF_SWIMMABLE // fof must be water
+					&& GETSECSPECIAL(rover->master->frontsector->special, 1) == 3)) // fof must be lava water
+					continue;
+
+				// find heights of FOF
+				topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
+				bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
+
+				if (mo->z <= topheight && mo->z + mo->height >= bottomheight) // if ring touches it, KILL IT
+				{
+					P_KillMobj(mo, NULL, NULL, DMG_FIRE);
+					return;
+				}
+			}
+		}
+	}
+}
+
 static void P_RingThinker(mobj_t *mobj)
 {
 	if (mobj->momx || mobj->momy)
@@ -4694,13 +4718,17 @@ static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
 	}
 }
 
+#define CEZ3TILT
+
 // Pull them closer.
 static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 {
 	INT32 s;
 	mobj_t *base = mobj, *seg;
-	fixed_t originx, originy, workx, worky, dx, dy, bz = mobj->watertop+(8<<FRACBITS);
-
+	fixed_t workx, worky, dx, dy, bz = mobj->watertop+(8<<FRACBITS);
+	fixed_t rad = (9*132)<<FRACBITS;
+#ifdef CEZ3TILT
+	fixed_t originx, originy;
 	if (mobj->spawnpoint)
 	{
 		originx = mobj->spawnpoint->x << FRACBITS;
@@ -4711,13 +4739,25 @@ static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 		originx = mobj->x;
 		originy = mobj->y;
 	}
+#else
+	if (mobj->spawnpoint)
+	{
+		rad -= R_PointToDist2(mobj->x, mobj->y,
+			(mobj->spawnpoint->x<<FRACBITS), (mobj->spawnpoint->y<<FRACBITS));
+	}
+#endif
 
 	dz /= 9;
 
 	while ((base = base->tracer)) // there are 10 per spoke, remember that
 	{
-		dx = (originx + P_ReturnThrustX(mobj, angle, (9*132)<<FRACBITS) - mobj->x)/9;
-		dy = (originy + P_ReturnThrustY(mobj, angle, (9*132)<<FRACBITS) - mobj->y)/9;
+#ifdef CEZ3TILT
+		dx = (originx + P_ReturnThrustX(mobj, angle, rad) - mobj->x)/9;
+		dy = (originy + P_ReturnThrustY(mobj, angle, rad) - mobj->y)/9;
+#else
+		dx = P_ReturnThrustX(mobj, angle, rad)/9;
+		dy = P_ReturnThrustY(mobj, angle, rad)/9;
+#endif
 		workx = mobj->x + P_ReturnThrustX(mobj, angle, (112)<<FRACBITS);
 		worky = mobj->y + P_ReturnThrustY(mobj, angle, (112)<<FRACBITS);
 		for (seg = base, s = 9; seg; seg = seg->hnext, --s)
@@ -4907,6 +4947,7 @@ static void P_Boss4Thinker(mobj_t *mobj)
 			mobj->movecount += mobj->threshold;
 			if (mobj->movecount <= 0)
 			{
+				mobj->flags2 &= ~MF2_INVERTAIMABLE;
 				mobj->movecount = 0;
 				mobj->movedir++; // Initialization complete, next phase!
 			}
@@ -7054,6 +7095,16 @@ void P_HandleMinecartSegments(mobj_t *mobj)
 	P_UpdateMinecartSegments(mobj);
 }
 
+static void P_PyreFlyBurn(mobj_t *mobj, fixed_t hoffs, INT16 vrange, mobjtype_t mobjtype, fixed_t momz)
+{
+	angle_t fa = (FixedAngle(P_RandomKey(360)*FRACUNIT) >> ANGLETOFINESHIFT) & FINEMASK;
+	fixed_t xoffs = FixedMul(FINECOSINE(fa), mobj->radius + hoffs);
+	fixed_t yoffs = FixedMul(FINESINE(fa), mobj->radius + hoffs);
+	fixed_t zoffs = P_RandomRange(-vrange, vrange)*FRACUNIT;
+	mobj_t *particle = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, zoffs, mobjtype);
+	particle->momz = momz;
+}
+
 //
 // P_MobjThinker
 //
@@ -7520,6 +7571,7 @@ void P_MobjThinker(mobj_t *mobj)
 						mobj->fuse -= 2;
 
 					flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
+					P_SetMobjState(flame, S_FLAMEJETFLAME4);
 
 					flame->angle = mobj->angle;
 
@@ -7564,7 +7616,10 @@ void P_MobjThinker(mobj_t *mobj)
 						flame->momz = -strength;
 					}
 					else
+					{
 						flame->momz = strength;
+						P_SetMobjState(flame, S_FLAMEJETFLAME7);
+					}
 					P_InstaThrust(flame, mobj->angle, FixedDiv(mobj->fuse*FRACUNIT,3*FRACUNIT));
 					S_StartSound(flame, sfx_fire);
 				}
@@ -7619,6 +7674,7 @@ void P_MobjThinker(mobj_t *mobj)
 			case MT_ROCKCRUMBLE16:
 			case MT_WOODDEBRIS:
 			case MT_BRICKDEBRIS:
+			case MT_BROKENROBOT:
 				if (mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)
 					&& mobj->state != &states[mobj->info->deathstate])
 				{
@@ -7683,13 +7739,244 @@ void P_MobjThinker(mobj_t *mobj)
 				if (mobj->movedir)
 					mobj->angle += mobj->movedir;
 				break;
+			case MT_ROSY:
+				{
+					UINT8 i;
+					fixed_t pdist = 1700*mobj->scale, work, actualwork;
+					player_t *player = NULL;
+					statenum_t stat = (mobj->state-states);
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (!playeringame[i])
+							continue;
+						if (!players[i].mo)
+							continue;
+						if (players[i].bot)
+							continue;
+						if (!players[i].mo->health)
+							continue;
+						actualwork = work = FixedHypot(mobj->x-players[i].mo->x, mobj->y-players[i].mo->y);
+						if (player)
+						{
+							if (players[i].skin == 0 || players[i].skin == 5)
+								work = (2*work)/3;
+							if (work >= pdist)
+								continue;
+						}
+						pdist = actualwork;
+						player = &players[i];
+					}
+
+					if (stat == S_ROSY_JUMP || stat == S_ROSY_PAIN)
+					{
+						if (P_IsObjectOnGround(mobj))
+						{
+							mobj->momx = mobj->momy = 0;
+							if (player && mobj->cvmem < (-2*TICRATE))
+								stat = S_ROSY_UNHAPPY;
+							else
+								stat = S_ROSY_WALK;
+							P_SetMobjState(mobj, stat);
+						}
+						else if (P_MobjFlip(mobj)*mobj->momz < 0)
+							mobj->frame = mobj->state->frame+mobj->state->var1;
+					}
+
+					if (!player)
+					{
+						if ((stat < S_ROSY_IDLE1 || stat > S_ROSY_IDLE4) && stat != S_ROSY_JUMP)
+						{
+							mobj->momx = mobj->momy = 0;
+							P_SetMobjState(mobj, S_ROSY_IDLE1);
+						}
+					}
+					else
+					{
+						boolean dojump = false, targonground, love, makeheart = false;
+						if (mobj->target != player->mo)
+							P_SetTarget(&mobj->target, player->mo);
+						targonground = (P_IsObjectOnGround(mobj->target) && (player->panim == PA_IDLE || player->panim == PA_WALK || player->panim == PA_RUN));
+						love = (player->skin == 0 || player->skin == 5);
+
+						switch (stat)
+						{
+							case S_ROSY_IDLE1:
+							case S_ROSY_IDLE2:
+							case S_ROSY_IDLE3:
+							case S_ROSY_IDLE4:
+								dojump = true;
+								break;
+							case S_ROSY_JUMP:
+							case S_ROSY_PAIN:
+								// handled above
+								break;
+							case S_ROSY_WALK:
+								{
+									fixed_t x = mobj->x, y = mobj->y, z = mobj->z;
+									angle_t angletoplayer = R_PointToAngle2(x, y, mobj->target->x, mobj->target->y);
+									boolean allowed = P_TryMove(mobj, mobj->target->x, mobj->target->y, false);
+
+									P_UnsetThingPosition(mobj);
+									mobj->x = x;
+									mobj->y = y;
+									mobj->z = z;
+									P_SetThingPosition(mobj);
+
+									if (allowed)
+									{
+										fixed_t mom, max;
+										P_Thrust(mobj, angletoplayer, (3*FRACUNIT)>>1);
+										mom = FixedHypot(mobj->momx, mobj->momy);
+										max = pdist;
+										if ((--mobj->extravalue1) <= 0)
+										{
+											if (++mobj->frame > mobj->state->frame+mobj->state->var1)
+												mobj->frame = mobj->state->frame;
+											if (mom > 12*mobj->scale)
+												mobj->extravalue1 = 2;
+											else if (mom > 6*mobj->scale)
+												mobj->extravalue1 = 3;
+											else
+												mobj->extravalue1 = 4;
+										}
+										if (max < (mobj->radius + mobj->target->radius))
+										{
+											mobj->momx = mobj->target->player->cmomx;
+											mobj->momy = mobj->target->player->cmomy;
+											if ((mobj->cvmem > TICRATE && !player->exiting) || !targonground)
+												P_SetMobjState(mobj, (stat = S_ROSY_STND));
+											else
+											{
+												mobj->target->momx = mobj->momx;
+												mobj->target->momy = mobj->momy;
+												P_SetMobjState(mobj, (stat = S_ROSY_HUG));
+												S_StartSound(mobj, sfx_cdpcm6);
+												mobj->angle = angletoplayer;
+											}
+										}
+										else
+										{
+											max /= 3;
+											if (max > 30*mobj->scale)
+												max = 30*mobj->scale;
+											if (mom > max && max > mobj->scale)
+											{
+												max = FixedDiv(max, mom);
+												mobj->momx = FixedMul(mobj->momx, max);
+												mobj->momy = FixedMul(mobj->momy, max);
+											}
+											if (abs(mobj->momx) > mobj->scale || abs(mobj->momy) > mobj->scale)
+												mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
+										}
+									}
+									else
+										dojump = true;
+								}
+								break;
+							case S_ROSY_HUG:
+								if (targonground)
+								{
+									player->pflags |= PF_STASIS;
+									if (mobj->cvmem < 5*TICRATE)
+										mobj->cvmem++;
+									if (love && !(leveltime & 7))
+										makeheart = true;
+								}
+								else
+								{
+									if (mobj->cvmem < (love ? 5*TICRATE : 0))
+									{
+										P_SetMobjState(mobj, (stat = S_ROSY_PAIN));
+										S_StartSound(mobj, sfx_cdpcm7);
+									}
+									else
+										P_SetMobjState(mobj, (stat = S_ROSY_JUMP));
+									var1 = var2 = 0;
+									A_DoNPCPain(mobj);
+									mobj->cvmem -= TICRATE;
+								}
+								break;
+							case S_ROSY_STND:
+								if ((pdist > (mobj->radius + mobj->target->radius + 3*(mobj->scale + mobj->target->scale))))
+									P_SetMobjState(mobj, (stat = S_ROSY_WALK));
+								else if (!targonground)
+									;
+								else
+								{
+									if (love && !(leveltime & 15))
+										makeheart = true;
+									if (player->exiting || --mobj->cvmem < TICRATE)
+									{
+										P_SetMobjState(mobj, (stat = S_ROSY_HUG));
+										S_StartSound(mobj, sfx_cdpcm6);
+										mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+										mobj->target->momx = mobj->momx;
+										mobj->target->momy = mobj->momy;
+									}
+								}
+								break;
+							case S_ROSY_UNHAPPY:
+							default:
+								break;
+						}
+
+						if (stat == S_ROSY_HUG)
+						{
+							if (player->panim != PA_IDLE)
+								P_SetPlayerMobjState(mobj->target, S_PLAY_STND);
+							player->pflags |= PF_STASIS;
+						}
+
+						if (dojump)
+						{
+							P_SetMobjState(mobj, S_ROSY_JUMP);
+							mobj->z += P_MobjFlip(mobj);
+							mobj->momx = mobj->momy = 0;
+							P_SetObjectMomZ(mobj, 6<<FRACBITS, false);
+							S_StartSound(mobj, sfx_cdfm02);
+						}
+
+						if (makeheart)
+						{
+							mobj_t *cdlhrt = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_CDLHRT);
+							cdlhrt->destscale = (5*mobj->scale)>>4;
+							P_SetScale(cdlhrt, cdlhrt->destscale);
+							cdlhrt->fuse = (5*TICRATE)>>1;
+							cdlhrt->momz = mobj->scale;
+							P_SetTarget(&cdlhrt->target, mobj);
+							cdlhrt->extravalue1 = mobj->x;
+							cdlhrt->extravalue2 = mobj->y;
+						}
+					}
+				}
+				break;
+			case MT_CDLHRT:
+				{
+					if (mobj->cvmem < 24)
+						mobj->cvmem++;
+					mobj->movedir += ANG10;
+					P_UnsetThingPosition(mobj);
+					mobj->x = mobj->extravalue1 + P_ReturnThrustX(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
+					mobj->y = mobj->extravalue2 + P_ReturnThrustY(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
+					P_SetThingPosition(mobj);
+					if ((--mobj->fuse) < 6)
+					{
+						if (!mobj->fuse)
+						{
+							P_RemoveMobj(mobj);
+							return;
+						}
+						mobj->frame = (mobj->frame & ~FF_TRANSMASK)|((10-(mobj->fuse*2))<<(FF_TRANSSHIFT));
+					}
+				}
+				break;
 			case MT_VWREF:
 			case MT_VWREB:
 				{
 					INT32 strength;
 					++mobj->movedir;
 					mobj->frame &= ~FF_TRANSMASK;
-					strength = min(mobj->fuse, mobj->movedir)*3;
+					strength = min(mobj->fuse, (INT32)mobj->movedir)*3;
 					if (strength < 10)
 						mobj->frame |= ((10-strength)<<(FF_TRANSSHIFT));
 				}
@@ -8896,13 +9183,17 @@ void P_MobjThinker(mobj_t *mobj)
 				}
 				break;
 			case MT_RING:
+			case MT_REDTEAMRING:
+			case MT_BLUETEAMRING:
+				P_KillRingsInLava(mobj);
+				if (P_MobjWasRemoved(mobj))
+					return;
+				/* FALLTHRU */
 			case MT_COIN:
 			case MT_BLUESPHERE:
 			case MT_BOMBSPHERE:
 			case MT_NIGHTSCHIP:
 			case MT_NIGHTSSTAR:
-			case MT_REDTEAMRING:
-			case MT_BLUETEAMRING:
 				// No need to check water. Who cares?
 				P_RingThinker(mobj);
 				if (mobj->flags2 & MF2_NIGHTSPULL)
@@ -8912,6 +9203,10 @@ void P_MobjThinker(mobj_t *mobj)
 				return;
 			// Flung items
 			case MT_FLINGRING:
+				P_KillRingsInLava(mobj);
+				if (P_MobjWasRemoved(mobj))
+					return;
+				/* FALLTHRU */
 			case MT_FLINGCOIN:
 			case MT_FLINGBLUESPHERE:
 			case MT_FLINGNIGHTSCHIP:
@@ -9113,6 +9408,159 @@ void P_MobjThinker(mobj_t *mobj)
 				}
 				mobj->flags2 ^= MF2_DONTDRAW;
 				break;
+			case MT_LAVAFALLROCK:
+				if (P_IsObjectOnGround(mobj))
+					P_RemoveMobj(mobj);
+				break;
+			case MT_PYREFLY:
+				{
+					fixed_t hdist;
+
+					mobj->extravalue1 = (mobj->extravalue1 + 3) % 360;
+					mobj->z += FINESINE(((mobj->extravalue1*ANG1) >> ANGLETOFINESHIFT) & FINEMASK);
+
+					if (!(mobj->flags2 & MF2_BOSSNOTRAP))
+						P_LookForPlayers(mobj, true, false, 1500*FRACUNIT);
+
+					if (!mobj->target)
+						break;
+
+					if (mobj->extravalue2 == 1)
+						P_PyreFlyBurn(mobj, 0, 20, MT_SMOKE, 4*FRACUNIT);
+					else if (mobj->extravalue2 == 2)
+					{
+						INT32 fireradius = min(100 - mobj->fuse, 52);
+						P_PyreFlyBurn(mobj, P_RandomRange(0, fireradius)*FRACUNIT, 20, MT_FLAMEPARTICLE, 4*FRACUNIT);
+						P_PyreFlyBurn(mobj, fireradius*FRACUNIT, 40, MT_PYREFLY_FIRE, 0);
+					}
+
+					hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+
+					if (!(mobj->flags2 & MF2_BOSSNOTRAP) && hdist <= 450*FRACUNIT)
+						mobj->flags2 |= MF2_BOSSNOTRAP;
+
+					if (!(mobj->flags2 & MF2_BOSSNOTRAP))
+						break;
+
+					if (hdist < 1000*FRACUNIT)
+					{
+						//Aim for player z position. If too close to floor/ceiling, aim just above/below them.
+						fixed_t destz = min(max(mobj->target->z, mobj->target->floorz + 70*FRACUNIT), mobj->target->ceilingz - 80*FRACUNIT - mobj->height);
+						fixed_t dist = P_AproxDistance(hdist, destz - mobj->z);
+						P_InstaThrust(mobj, R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y), 2*FRACUNIT);
+						mobj->momz = FixedMul(FixedDiv(destz - mobj->z, dist), 2*FRACUNIT);
+					}
+					else
+					{
+						mobj->momx = 0;
+						mobj->momy = 0;
+						mobj->momz = 0;
+						if (hdist >= 1500*FRACUNIT)
+						{
+							mobj->flags2 &= ~MF2_BOSSNOTRAP;
+							P_SetTarget(&mobj->target, NULL);
+						}
+					}
+					break;
+				}
+			case MT_PTERABYTE:
+				{
+					if (mobj->extravalue1 & 4) // Cooldown after grabbing
+					{
+						if (mobj->movefactor)
+							mobj->movefactor--;
+						else
+						{
+							P_SetTarget(&mobj->target, NULL);
+							mobj->extravalue1 &= 3;
+						}
+					}
+
+					if ((mobj->extravalue1 & 3) == 0) // Hovering
+					{
+						fixed_t vdist, hdist, time;
+						fixed_t hspeed = 3*mobj->info->speed;
+						angle_t fa;
+
+						var1 = 1;
+						var2 = 0;
+						A_CapeChase(mobj);
+
+						if (mobj->target)
+							break; // Still carrying a player or in cooldown
+
+						P_LookForPlayers(mobj, true, false, 256*FRACUNIT);
+
+						if (!mobj->target)
+							break;
+
+						if (mobj->target->player->powers[pw_flashing])
+						{
+							P_SetTarget(&mobj->target, NULL);
+							break;
+						}
+
+						vdist = mobj->z - mobj->target->z - mobj->target->height;
+						if (P_MobjFlip(mobj)*vdist <= 0)
+						{
+							P_SetTarget(&mobj->target, NULL);
+							break;
+						}
+
+						hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+						if (hdist > 450*FRACUNIT)
+						{
+							P_SetTarget(&mobj->target, NULL);
+							break;
+						}
+
+						P_SetMobjState(mobj, S_PTERABYTE_SWOOPDOWN);
+						mobj->extravalue1++;
+						S_StartSound(mobj, mobj->info->attacksound);
+						time = FixedDiv(hdist, hspeed);
+						mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+						fa = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
+						mobj->momx = FixedMul(FINECOSINE(fa), hspeed);
+						mobj->momy = FixedMul(FINESINE(fa), hspeed);
+						mobj->momz = -2*FixedDiv(vdist, time);
+						mobj->extravalue2 = -FixedDiv(mobj->momz, time); //Z accel
+						mobj->movecount = time >> FRACBITS;
+						mobj->reactiontime = mobj->movecount;
+					}
+					else if ((mobj->extravalue1 & 3) == 1) // Swooping
+					{
+						mobj->reactiontime--;
+						mobj->momz += mobj->extravalue2;
+						if (mobj->reactiontime)
+							break;
+
+						if (mobj->state - states == S_PTERABYTE_SWOOPDOWN)
+						{
+							P_SetMobjState(mobj, S_PTERABYTE_SWOOPUP);
+							mobj->reactiontime = mobj->movecount;
+						}
+						else if (mobj->state - states == S_PTERABYTE_SWOOPUP)
+						{
+							P_SetMobjState(mobj, S_PTERABYTE_FLY1);
+							mobj->extravalue1++;
+							if (mobj->target && mobj->target->tracer != mobj)
+								P_SetTarget(&mobj->target, NULL); // Failed to grab the target
+							mobj->momx = mobj->momy = mobj->momz = 0;
+						}
+					}
+					else // Returning
+					{
+						var1 = 2*mobj->info->speed;
+						var2 = 1;
+						A_HomingChase(mobj);
+						if (P_AproxDistance(mobj->x - mobj->tracer->x, mobj->y - mobj->tracer->y) <= mobj->info->speed)
+						{
+							mobj->extravalue1 -= 2;
+							mobj->momx = mobj->momy = mobj->momz = 0;
+						}
+					}
+					break;
+				}
 			case MT_SPINFIRE:
 				if (mobj->flags & MF_NOGRAVITY)
 				{
@@ -9343,6 +9791,52 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 				case MT_NIGHTSCORE:
 					P_RemoveMobj(mobj);
 					return;
+				case MT_LAVAFALL:
+					if (mobj->state - states == S_LAVAFALL_DORMANT)
+					{
+						mobj->fuse = 30;
+						P_SetMobjState(mobj, S_LAVAFALL_TELL);
+						S_StartSound(mobj, mobj->info->seesound);
+					}
+					else if (mobj->state - states == S_LAVAFALL_TELL)
+					{
+						mobj->fuse = 40;
+						P_SetMobjState(mobj, S_LAVAFALL_SHOOT);
+						S_StopSound(mobj);
+						S_StartSound(mobj, mobj->info->attacksound);
+					}
+					else
+					{
+						mobj->fuse = 30;
+						P_SetMobjState(mobj, S_LAVAFALL_DORMANT);
+						S_StopSound(mobj);
+					}
+					return;
+				case MT_PYREFLY:
+					if (mobj->health <= 0)
+						break;
+
+					mobj->extravalue2 = (mobj->extravalue2 + 1) % 3;
+					if (mobj->extravalue2 == 0)
+					{
+						P_SetMobjState(mobj, mobj->info->spawnstate);
+						mobj->fuse = 100;
+						S_StopSound(mobj);
+						S_StartSound(mobj, sfx_s3k8c);
+					}
+					else if (mobj->extravalue2 == 1)
+					{
+						mobj->fuse = 50;
+						S_StartSound(mobj, sfx_s3ka3);
+					}
+					else
+					{
+						P_SetMobjState(mobj, mobj->info->meleestate);
+						mobj->fuse = 100;
+						S_StopSound(mobj);
+						S_StartSound(mobj, sfx_s3kc2l);
+					}
+					return;
 				case MT_PLAYER:
 					break; // don't remove
 				default:
@@ -9812,6 +10306,15 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 				mobj->reactiontime >>= 1;
 			}
 			break;
+		case MT_BANPYURA:
+			{
+				mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BANPSPRING);
+				bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);;
+				P_SetTarget(&mobj->tracer, bigmeatyclaw);
+				P_SetTarget(&bigmeatyclaw->tracer, mobj);
+				mobj->reactiontime >>= 1;
+			}
+			break;
 		case MT_BIGMINE:
 			mobj->extravalue1 = FixedHypot(mobj->x, mobj->y)>>FRACBITS;
 			break;
@@ -9837,6 +10340,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			mobj->movefactor = -512*FRACUNIT;
 			mobj->flags2 |= MF2_CLASSICPUSH;
 			break;
+		case MT_EGGMOBILE4:
+			mobj->flags2 |= MF2_INVERTAIMABLE;
+			break;
 		case MT_FLICKY_08:
 			mobj->color = (P_RandomChance(FRACUNIT/2) ? SKINCOLOR_RED : SKINCOLOR_AQUA);
 			break;
@@ -9895,11 +10401,14 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			break;
 		case MT_METALSONIC_BATTLE:
 		case MT_METALSONIC_RACE:
-			sc = 3;
+			sc = 5;
 			break;
 		case MT_FANG:
 			sc = 4;
 			break;
+		case MT_ROSY:
+			sc = 3;
+			break;
 		case MT_CORK:
 			mobj->flags2 |= MF2_SUPERFIRE;
 			break;
@@ -9915,11 +10424,23 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			}
 		case MT_TNTBARREL:
 			mobj->momx = 1; //stack hack
+			mobj->flags2 |= MF2_INVERTAIMABLE;
 			break;
 		case MT_MINECARTEND:
 			P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID));
 			mobj->tracer->angle = mobj->angle + ANGLE_90;
 			break;
+		case MT_TORCHFLOWER:
+			{
+				mobj_t *fire = P_SpawnMobjFromMobj(mobj, 0, 0, 46*FRACUNIT, MT_FLAME);
+				P_SetTarget(&mobj->target, fire);
+				break;
+			}
+		case MT_PYREFLY:
+			mobj->extravalue1 = (FixedHypot(mobj->x, mobj->y)/FRACUNIT) % 360;
+			mobj->extravalue2 = 0;
+			mobj->fuse = 100;
+			break;
 		default:
 			break;
 	}
@@ -10237,7 +10758,7 @@ void P_SpawnPrecipitation(void)
 		if (curWeather == PRECIP_SNOW)
 		{
 			// Not in a sector with visible sky -- exception for NiGHTS.
-			if (!(maptol & TOL_NIGHTS) && precipsector->sector->ceilingpic != skyflatnum)
+			if ((!(maptol & TOL_NIGHTS) && (precipsector->sector->ceilingpic != skyflatnum)) == !(precipsector->sector->flags & SF_INVERTPRECIP))
 				continue;
 
 			rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
@@ -10250,7 +10771,7 @@ void P_SpawnPrecipitation(void)
 		else // everything else.
 		{
 			// Not in a sector with visible sky.
-			if (precipsector->sector->ceilingpic != skyflatnum)
+			if ((precipsector->sector->ceilingpic != skyflatnum) == !(precipsector->sector->flags & SF_INVERTPRECIP))
 				continue;
 
 			rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
@@ -11070,6 +11591,16 @@ You should think about modifying the deathmatch starts to take full advantage of
 		// They're likely facets of the level's design and therefore required to progress.
 	}
 
+	if (i == MT_ROSY)
+	{
+		if (!(gametype == GT_COOP || (mthing->options & MTF_EXTRA)))
+			return; // she doesn't hang out here
+		else if (mariomode)
+			i = MT_TOAD; // don't remove on penalty of death
+		else if (!(netgame || multiplayer) && players[consoleplayer].skin == 3)
+			return; // no doubles
+	}
+
 	if (i == MT_TOKEN && ((gametype != GT_COOP && gametype != GT_COMPETITION) || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
 		return; // you already got this token, or there are too many, or the gametype's not right
 
@@ -11283,9 +11814,10 @@ You should think about modifying the deathmatch starts to take full advantage of
 		else
 			mobj->health = FixedMul(ss->sector->ceilingheight-ss->sector->floorheight, 3*(FRACUNIT/4))>>FRACBITS;
 		break;
-	case MT_FANG:
 	case MT_METALSONIC_RACE:
 	case MT_METALSONIC_BATTLE:
+	case MT_FANG:
+	case MT_ROSY:
 		if (mthing->options & MTF_EXTRA)
 		{
 			mobj->color = SKINCOLOR_SILVER;
@@ -11957,6 +12489,108 @@ ML_EFFECT5 : Don't stop thinking when too far away
 		if (mthing->angle > 0)
 			mobj->tics += mthing->angle;
 		break;
+	case MT_LAVAFALL:
+		mobj->fuse = 30 + mthing->angle;
+		if (mthing->options & MTF_AMBUSH)
+		{
+			P_SetScale(mobj, 2*mobj->scale);
+			mobj->destscale = mobj->scale;
+		}
+		break;
+	case MT_PYREFLY:
+		//start on fire if Ambush flag is set, otherwise behave normally
+		if (mthing->options & MTF_AMBUSH)
+		{
+			P_SetMobjState(mobj, mobj->info->meleestate);
+			mobj->extravalue2 = 2;
+			S_StartSound(mobj, sfx_s3kc2l);
+		}
+		break;
+	case MT_BIGFERN:
+	{
+		angle_t angle = FixedAngle(mthing->angle << FRACBITS);
+		UINT8 j;
+		for (j = 0; j < 8; j++)
+		{
+			angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
+			fixed_t xoffs = FINECOSINE(fa);
+			fixed_t yoffs = FINESINE(fa);
+			mobj_t *leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF);
+			leaf->angle = angle;
+			angle += ANGLE_45;
+		}
+		break;
+	}
+	case MT_REDBOOSTER:
+	{
+		angle_t angle = FixedAngle(mthing->angle << FRACBITS);
+		fixed_t x1 = FINECOSINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
+		fixed_t y1 = FINESINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
+		fixed_t x2 = FINECOSINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
+		fixed_t y2 = FINESINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
+
+		mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
+		seg->angle = angle-ANGLE_90;
+		P_SetMobjState(seg, S_REDBOOSTERSEG_FACE);
+		seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
+		seg->angle = angle+ANGLE_90;
+		P_SetMobjState(seg, S_REDBOOSTERSEG_FACE);
+		seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_REDBOOSTERSEG_LEFT);
+		seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_REDBOOSTERSEG_RIGHT);
+
+		seg = P_SpawnMobjFromMobj(mobj, 13*(x1+x2), 13*(y1+y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_REDBOOSTERROLLER);
+		seg = P_SpawnMobjFromMobj(mobj, 13*(x1-x2), 13*(y1-y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_REDBOOSTERROLLER);
+		seg = P_SpawnMobjFromMobj(mobj, -13*(x1+x2), -13*(y1+y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_REDBOOSTERROLLER);
+		seg = P_SpawnMobjFromMobj(mobj, -13*(x1-x2), -13*(y1-y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_REDBOOSTERROLLER);
+		break;
+	}
+	case MT_YELLOWBOOSTER:
+	{
+		angle_t angle = FixedAngle(mthing->angle << FRACBITS);
+		fixed_t x1 = FINECOSINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
+		fixed_t y1 = FINESINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
+		fixed_t x2 = FINECOSINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
+		fixed_t y2 = FINESINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
+
+		mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
+		seg->angle = angle-ANGLE_90;
+		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_FACE);
+		seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
+		seg->angle = angle+ANGLE_90;
+		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_FACE);
+		seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_LEFT);
+		seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_RIGHT);
+
+		seg = P_SpawnMobjFromMobj(mobj, 13*(x1+x2), 13*(y1+y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
+		seg = P_SpawnMobjFromMobj(mobj, 13*(x1-x2), 13*(y1-y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
+		seg = P_SpawnMobjFromMobj(mobj, -13*(x1+x2), -13*(y1+y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
+		seg = P_SpawnMobjFromMobj(mobj, -13*(x1-x2), -13*(y1-y2), 0, MT_BOOSTERROLLER);
+		seg->angle = angle;
+		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
+		break;
+	}
 	default:
 		break;
 	}
@@ -12155,7 +12789,7 @@ ML_EFFECT5 : Don't stop thinking when too far away
 	{
 		if (mthing->options & MTF_AMBUSH)
 		{
-			if (i == MT_YELLOWDIAG || i == MT_REDDIAG)
+			if (i == MT_YELLOWDIAG || i == MT_REDDIAG || i == MT_BLUEDIAG)
 				mobj->angle += ANGLE_22h;
 
 			if (i == MT_YELLOWHORIZ || i == MT_REDHORIZ || i == MT_BLUEHORIZ)
@@ -12194,7 +12828,7 @@ ML_EFFECT5 : Don't stop thinking when too far away
 
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
-			if (i == MT_YELLOWDIAG || i == MT_REDDIAG)
+			if (i == MT_YELLOWDIAG || i == MT_REDDIAG || i == MT_BLUEDIAG)
 				mobj->flags |= MF_NOGRAVITY;
 
 			if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
diff --git a/src/p_mobj.h b/src/p_mobj.h
index a9d5244b044ad58840238d753e2e70184e2fc585..94fcc29879727f110205e10656bcb97f45a81472 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -233,15 +233,17 @@ typedef enum
 	MFE_VERTICALFLIP      = 1<<5,
 	// Goo water
 	MFE_GOOWATER          = 1<<6,
+	// The mobj is touching a lava block
+	MFE_TOUCHLAVA         = 1<<7,
 	// Mobj was already pushed this tic
-	MFE_PUSHED            = 1<<7,
+	MFE_PUSHED            = 1<<8,
 	// Mobj was already sprung this tic
-	MFE_SPRUNG            = 1<<8,
+	MFE_SPRUNG            = 1<<9,
 	// Platform movement
-	MFE_APPLYPMOMZ        = 1<<9,
+	MFE_APPLYPMOMZ        = 1<<10,
 	// Compute and trigger on mobj angle relative to tracer
 	// See Linedef Exec 457 (Track mobj angle to point)
-	MFE_TRACERANGLE       = 1<<10,
+	MFE_TRACERANGLE       = 1<<11,
 	// free: to and including 1<<15
 } mobjeflag_t;
 
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 69c942236df283f5825f2ce8a1f8b081daa12d61..fb2365bf0983e3de1cdb44b813ab9c333cc4bc48 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -3618,7 +3618,7 @@ static void P_NetUnArchiveThinkers(void)
 	{
 		executor_t *delay = NULL;
 		UINT32 mobjnum;
-		for (currentthinker = thlist[i].next; currentthinker != &thlist[i];
+		for (currentthinker = thlist[THINK_MAIN].next; currentthinker != &thlist[THINK_MAIN];
 		currentthinker = currentthinker->next)
 		{
 			if (currentthinker->function.acp1 != (actionf_p1)T_ExecutorDelay)
diff --git a/src/p_setup.c b/src/p_setup.c
index 4685efb78f7831e7da501053c1ca0885a2443251..96bc467aa3b3d4c1224e9f5a47bea4f78e6b383f 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2607,7 +2607,6 @@ boolean P_SetupLevel(boolean skipprecip)
 	boolean loadedbm = false;
 	sector_t *ss;
 	boolean chase;
-
 	levelloading = true;
 
 	// This is needed. Don't touch.
@@ -3048,8 +3047,11 @@ boolean P_SetupLevel(boolean skipprecip)
 			CONS_Printf(M_GetText("No player currently available to become IT. Awaiting available players.\n"));
 
 	}
-	else if (gametype == GT_RACE && server && cv_usemapnumlaps.value)
-		CV_StealthSetValue(&cv_numlaps, mapheaderinfo[gamemap - 1]->numlaps);
+	else if (gametype == GT_RACE && server)
+		CV_StealthSetValue(&cv_numlaps,
+			(cv_basenumlaps.value)
+			? cv_basenumlaps.value
+			: mapheaderinfo[gamemap - 1]->numlaps);
 
 	// ===========
 	// landing point for netgames.
diff --git a/src/p_spec.c b/src/p_spec.c
index 256ca3453f0017311392d0b7d7d3043f7fe576a6..50939ae5b2fd8beca0ddce735490bb7711487105 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2718,6 +2718,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					CONS_Debug(DBG_GAMELOGIC, "Line type 414 Executor: sfx number %d is invalid!\n", sfxnum);
 					return;
 				}
+
 				if (line->tag != 0) // Do special stuff only if a non-zero linedef tag is set
 				{
 					if (line->flags & ML_EFFECT5) // Repeat Midtexture
@@ -2758,30 +2759,32 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							return;
 					}
 				}
-
-				if (line->flags & ML_NOCLIMB)
+				else
 				{
-					// play the sound from nowhere, but only if display player triggered it
-					if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer]))
+					if (line->flags & ML_NOCLIMB)
+					{
+						// play the sound from nowhere, but only if display player triggered it
+						if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer]))
+							S_StartSound(NULL, sfxnum);
+					}
+					else if (line->flags & ML_EFFECT4)
+					{
+						// play the sound from nowhere
 						S_StartSound(NULL, sfxnum);
-				}
-				else if (line->flags & ML_EFFECT4)
-				{
-					// play the sound from nowhere
-					S_StartSound(NULL, sfxnum);
-				}
-				else if (line->flags & ML_BLOCKMONSTERS)
-				{
-					// play the sound from calling sector's soundorg
-					if (callsec)
-						S_StartSound(&callsec->soundorg, sfxnum);
+					}
+					else if (line->flags & ML_BLOCKMONSTERS)
+					{
+						// play the sound from calling sector's soundorg
+						if (callsec)
+							S_StartSound(&callsec->soundorg, sfxnum);
+						else if (mo)
+							S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
+					}
 					else if (mo)
-						S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
-				}
-				else if (mo)
-				{
-					// play the sound from mobj that triggered it
-					S_StartSound(mo, sfxnum);
+					{
+						// play the sound from mobj that triggered it
+						S_StartSound(mo, sfxnum);
+					}
 				}
 			}
 			break;
@@ -4174,26 +4177,11 @@ sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 n
 		// Check the 3D floor's type...
 		if (rover->flags & FF_BLOCKPLAYER)
 		{
+			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
+			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
 			// Thing must be on top of the floor to be affected...
-			if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
-			{
-				if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != topheight)
-					continue;
-			}
-			else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
-			{
-				if (!(player->mo->eflags & MFE_VERTICALFLIP)
-					|| player->mo->z + player->mo->height != bottomheight)
-					continue;
-			}
-			else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
-			{
-				if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == bottomheight)
-					|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == topheight)))
-					continue;
-			}
+			if (!(floorallowed || ceilingallowed))
+				continue;
 		}
 		else
 		{
@@ -4234,26 +4222,11 @@ sector_t *P_PlayerTouchingSectorSpecial(player_t *player, INT32 section, INT32 n
 			// Check the 3D floor's type...
 			if (rover->flags & FF_BLOCKPLAYER)
 			{
+				boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
+				boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
 				// Thing must be on top of the floor to be affected...
-				if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
-					&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
-				{
-					if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != topheight)
-						continue;
-				}
-				else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
-					&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
-				{
-					if (!(player->mo->eflags & MFE_VERTICALFLIP)
-						|| player->mo->z + player->mo->height != bottomheight)
-						continue;
-				}
-				else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
-				{
-					if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == bottomheight)
-						|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == topheight)))
-						continue;
-				}
+				if (!(floorallowed || ceilingallowed))
+					continue;
 			}
 			else
 			{
@@ -4304,26 +4277,11 @@ static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *tar
 		// Check the 3D floor's type...
 		if (rover->flags & FF_BLOCKPLAYER)
 		{
+			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == top));
+			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == bottom));
 			// Thing must be on top of the floor to be affected...
-			if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
-			{
-				if ((mo->eflags & MFE_VERTICALFLIP) || mo->z != top)
-					return false;
-			}
-			else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
-			{
-				if (!(mo->eflags & MFE_VERTICALFLIP)
-					|| mo->z + mo->height != bottom)
-					return false;
-			}
-			else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
-			{
-				if (!((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height == bottom)
-					|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->z == top)))
-					return false;
-			}
+			if (!(floorallowed || ceilingallowed))
+				continue;
 		}
 		else
 		{
@@ -4345,10 +4303,10 @@ static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *tar
 //
 static boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
 {
-	if (mo->eflags & MFE_VERTICALFLIP)
-		return (mo->z+mo->height == P_GetSpecialTopZ(mo, sec, sec) && sec->flags & SF_FLIPSPECIAL_CEILING);
-	else
-		return (mo->z == P_GetSpecialBottomZ(mo, sec, sec) && sec->flags & SF_FLIPSPECIAL_FLOOR);
+	boolean floorallowed = ((sec->flags & SF_FLIPSPECIAL_FLOOR) && ((sec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == P_GetSpecialBottomZ(mo, sec, sec)));
+	boolean ceilingallowed = ((sec->flags & SF_FLIPSPECIAL_CEILING) && ((sec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == P_GetSpecialTopZ(mo, sec, sec)));
+	// Thing must be on top of the floor to be affected...
+	return (floorallowed || ceilingallowed);
 }
 
 /** Applies a sector special to a player.
@@ -5312,26 +5270,11 @@ sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
 		if (((rover->flags & FF_BLOCKPLAYER) && mo->player)
 			|| ((rover->flags & FF_BLOCKOTHERS) && !mo->player))
 		{
+			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->z == topheight));
+			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->z + mo->height == bottomheight));
 			// Thing must be on top of the floor to be affected...
-			if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
-			{
-				if ((mo->eflags & MFE_VERTICALFLIP) || mo->z != topheight)
-					continue;
-			}
-			else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
-			{
-				if (!(mo->eflags & MFE_VERTICALFLIP)
-					|| mo->z + mo->height != bottomheight)
-					continue;
-			}
-			else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
-			{
-				if (!((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height == bottomheight)
-					|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->z == topheight)))
-					continue;
-			}
+			if (!(floorallowed || ceilingallowed))
+				continue;
 		}
 		else
 		{
@@ -5374,26 +5317,11 @@ static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
 		// Check the 3D floor's type...
 		if (rover->flags & FF_BLOCKPLAYER)
 		{
+			boolean floorallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == topheight));
+			boolean ceilingallowed = ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING) && ((rover->master->frontsector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == bottomheight));
 			// Thing must be on top of the floor to be affected...
-			if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING))
-			{
-				if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != topheight)
-					continue;
-			}
-			else if ((rover->master->frontsector->flags & SF_FLIPSPECIAL_CEILING)
-				&& !(rover->master->frontsector->flags & SF_FLIPSPECIAL_FLOOR))
-			{
-				if (!(player->mo->eflags & MFE_VERTICALFLIP)
-					|| player->mo->z + player->mo->height != bottomheight)
-					continue;
-			}
-			else if (rover->master->frontsector->flags & SF_FLIPSPECIAL_BOTH)
-			{
-				if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == bottomheight)
-					|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == topheight)))
-					continue;
-			}
+			if (!(floorallowed || ceilingallowed))
+				continue;
 		}
 		else
 		{
@@ -5450,38 +5378,16 @@ static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
 			}
 
 			if (!(po->flags & POF_TESTHEIGHT)) // Don't do height checking
-			{
-			}
+				;
 			else if (po->flags & POF_SOLID)
 			{
+				boolean floorallowed = ((polysec->flags & SF_FLIPSPECIAL_FLOOR) && ((polysec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == polysec->ceilingheight));
+				boolean ceilingallowed = ((polysec->flags & SF_FLIPSPECIAL_CEILING) && ((polysec->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == polysec->floorheight));
 				// Thing must be on top of the floor to be affected...
-				if ((polysec->flags & SF_FLIPSPECIAL_FLOOR)
-					&& !(polysec->flags & SF_FLIPSPECIAL_CEILING))
-				{
-					if ((player->mo->eflags & MFE_VERTICALFLIP) || player->mo->z != polysec->ceilingheight)
-					{
-						po = (polyobj_t *)(po->link.next);
-						continue;
-					}
-				}
-				else if ((polysec->flags & SF_FLIPSPECIAL_CEILING)
-					&& !(polysec->flags & SF_FLIPSPECIAL_FLOOR))
-				{
-					if (!(player->mo->eflags & MFE_VERTICALFLIP)
-						|| player->mo->z + player->mo->height != polysec->floorheight)
-					{
-						po = (polyobj_t *)(po->link.next);
-						continue;
-					}
-				}
-				else if (polysec->flags & SF_FLIPSPECIAL_BOTH)
+				if (!(floorallowed || ceilingallowed))
 				{
-					if (!((player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height == polysec->floorheight)
-						|| (!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z == polysec->ceilingheight)))
-					{
-						po = (polyobj_t *)(po->link.next);
-						continue;
-					}
+					po = (polyobj_t *)(po->link.next);
+					continue;
 				}
 			}
 			else
@@ -5580,17 +5486,13 @@ static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
 	f_affectpoint = P_GetSpecialBottomZ(player->mo, sector, sector);
 	c_affectpoint = P_GetSpecialTopZ(player->mo, sector, sector);
 
-	// Only go further if on the ground
-	if ((sector->flags & SF_FLIPSPECIAL_FLOOR) && !(sector->flags & SF_FLIPSPECIAL_CEILING) && player->mo->z != f_affectpoint)
-		return;
-
-	if ((sector->flags & SF_FLIPSPECIAL_CEILING) && !(sector->flags & SF_FLIPSPECIAL_FLOOR) && player->mo->z + player->mo->height != c_affectpoint)
-		return;
-
-	if ((sector->flags & SF_FLIPSPECIAL_BOTH)
-		&& player->mo->z != f_affectpoint
-		&& player->mo->z + player->mo->height != c_affectpoint)
-		return;
+	{
+		boolean floorallowed = ((sector->flags & SF_FLIPSPECIAL_FLOOR) && ((sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z == f_affectpoint));
+		boolean ceilingallowed = ((sector->flags & SF_FLIPSPECIAL_CEILING) && ((sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (player->mo->eflags & MFE_VERTICALFLIP)) && (player->mo->z + player->mo->height == c_affectpoint));
+		// Thing must be on top of the floor to be affected...
+		if (!(floorallowed || ceilingallowed))
+			return;
+	}
 
 	P_ProcessSpecialSector(player, sector, NULL);
 }
@@ -6085,8 +5987,6 @@ static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
   * to the lowest nearby height if not
   * there already.
   *
-  * Replaces the old "AirBob".
-  *
   * \param sec          Control sector.
   * \param actionsector Target sector.
   * \param sourceline   Control linedef.
@@ -6131,8 +6031,7 @@ static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline)
 	raise->sourceline = sourceline;
 }
 
-// Function to maintain backwards compatibility
-static void P_AddOldAirbob(sector_t *sec, line_t *sourceline, boolean noadjust)
+static void P_AddAirbob(sector_t *sec, line_t *sourceline, boolean noadjust, boolean dynamic)
 {
 	levelspecthink_t *airbob;
 
@@ -6169,6 +6068,8 @@ static void P_AddOldAirbob(sector_t *sec, line_t *sourceline, boolean noadjust)
 	airbob->vars[5] = sec->ceilingheight;
 	airbob->vars[4] = airbob->vars[5]
 			- (sec->ceilingheight - sec->floorheight);
+	
+	airbob->vars[9] = dynamic ? 1 : 0;
 
 	airbob->sourceline = sourceline;
 }
@@ -6683,6 +6584,11 @@ void P_SpawnSpecials(INT32 fromnetsave)
 
 					if (lines[i].flags & ML_EFFECT3)
 						sectors[s].flags |= SF_TRIGGERSPECIAL_TOUCH;
+					if (lines[i].flags & ML_EFFECT2)
+						sectors[s].flags |= SF_TRIGGERSPECIAL_HEADBUMP;
+
+					if (lines[i].flags & ML_EFFECT1)
+						sectors[s].flags |= SF_INVERTPRECIP;
 
 					if (lines[i].frontsector && GETSECSPECIAL(lines[i].frontsector->special, 4) == 12)
 						sectors[s].camsec = sides[*lines[i].sidenum].sector-sectors;
@@ -6987,11 +6893,16 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 151: // Adjustable air bobbing platform
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
 				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddOldAirbob(lines[i].frontsector, lines + i, (lines[i].special != 151));
+				P_AddAirbob(lines[i].frontsector, lines + i, (lines[i].special != 151), false);
 				break;
 			case 152: // Adjustable air bobbing platform in reverse
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddOldAirbob(lines[i].frontsector, lines + i, true);
+				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
+				break;
+			case 153: // Dynamic Sinking Platform
+				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
+				lines[i].flags |= ML_BLOCKMONSTERS;
+				P_AddAirbob(lines[i].frontsector, lines + i, false, true);
 				break;
 
 			case 160: // Float/bob platform
@@ -7042,14 +6953,14 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
 				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddOldAirbob(lines[i].frontsector, lines + i, true);
+				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
 				break;
 
 			case 177: // Air bobbing platform that will crumble and bob on
 				// the water when it falls and hits, then never return
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
 				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddOldAirbob(lines[i].frontsector, lines + i, true);
+				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
 				break;
 
 			case 178: // Crumbling platform that will float when it hits water
@@ -7063,7 +6974,7 @@ void P_SpawnSpecials(INT32 fromnetsave)
 			case 180: // Air bobbing platform that will crumble
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
 				lines[i].flags |= ML_BLOCKMONSTERS;
-				P_AddOldAirbob(lines[i].frontsector, lines + i, true);
+				P_AddAirbob(lines[i].frontsector, lines + i, true, false);
 				break;
 
 			case 190: // Rising Platform FOF (solid, opaque, shadows)
@@ -7171,21 +7082,21 @@ void P_SpawnSpecials(INT32 fromnetsave)
 				break;
 
 			case 252: // Shatter block (breaks when touched)
-				ffloorflags = FF_EXISTS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER;
+				ffloorflags = FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER;
 				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_SOLID|FF_SHATTERBOTTOM;
+					ffloorflags |= FF_BLOCKPLAYER|FF_SHATTERBOTTOM;
 
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 				break;
 
 			case 253: // Translucent shatter block (see 76)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER|FF_TRANSLUCENT, secthinkers);
+				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER|FF_TRANSLUCENT, secthinkers);
 				break;
 
 			case 254: // Bustable block
 				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_BUSTUP;
 				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_ONLYKNUX;
+					ffloorflags |= FF_STRONGBUST;
 
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 				break;
diff --git a/src/p_tick.c b/src/p_tick.c
index 7606510fef2d8a302809f0d541616babb003e95a..6b5c7980c86d7307a460e66111b70d0bad37a23e 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -482,7 +482,7 @@ static inline void P_DoSpecialStageStuff(void)
 				countspheres += players[i].spheres;
 
 				// If in water, deplete timer 6x as fast.
-				if (players[i].mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
+				if (players[i].mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER) && !(players[i].powers[pw_shield] & SH_PROTECTWATER))
 					players[i].nightstime -= 5;
 				if (--players[i].nightstime > 6)
 				{
diff --git a/src/p_user.c b/src/p_user.c
index e4792c1071e8ca08fa650de7ced26b569f8aebb2..f81f6d956bc51cd5717fddfbb9bbbfebf3bc2519 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -191,7 +191,7 @@ boolean P_AutoPause(void)
 	if (netgame || modeattacking || gamestate == GS_TITLESCREEN)
 		return false;
 
-	return (menuactive || window_notinfocus);
+	return (menuactive || ( window_notinfocus && cv_pauseifunfocused.value ));
 }
 
 //
@@ -898,7 +898,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	if (player->mo->target)
 	{
 		player->angle_pos = R_PointToAngle2(player->mo->target->x, player->mo->target->y, player->mo->x, player->mo->y);
-		player->drawangle = player->mo->angle = player->angle_pos
+		player->drawangle = player->angle_pos
 			+ ((player->mo->target->flags2 & MF2_AMBUSH) ? // if axis is invert, take the opposite right angle
 				-ANGLE_90 : ANGLE_90); // flyangle is always 0 here, below is kept for posterity
 				/*(player->flyangle > 90 && player->flyangle < 270 ? ANGLE_90 : -ANGLE_90)
@@ -1031,6 +1031,17 @@ void P_ResetPlayer(player_t *player)
 {
 	player->pflags &= ~(PF_SPINNING|PF_STARTDASH|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_THOKKED|PF_CANCARRY|PF_SHIELDABILITY|PF_BOUNCING);
 
+	if (player->powers[pw_carry] == CR_ROLLOUT)
+	{
+		if (player->mo->tracer && !P_MobjWasRemoved(player->mo->tracer))
+		{
+			player->mo->tracer->flags |= MF_PUSHABLE;
+			P_SetTarget(&player->mo->tracer->tracer, NULL);
+		}
+		P_SetTarget(&player->mo->tracer, NULL);
+		player->powers[pw_carry] = CR_NONE;
+	}
+
 	if (!(player->powers[pw_carry] == CR_NIGHTSMODE || player->powers[pw_carry] == CR_NIGHTSFALL || player->powers[pw_carry] == CR_BRAKGOOP || player->powers[pw_carry] == CR_MINECART))
 		player->powers[pw_carry] = CR_NONE;
 
@@ -1085,6 +1096,9 @@ boolean P_PlayerCanDamage(player_t *player, mobj_t *thing)
 	// Spinning.
 	if (player->pflags & PF_SPINNING)
 		return true;
+	
+	if (player->dashmode >= DASHMODE_THRESHOLD && (player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE))
+		return true;
 
 	// From the front.
 	if (((player->pflags & PF_GLIDING) || (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
@@ -1979,7 +1993,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 		mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj);
 		P_SetTarget(&ghost2->tracer, ghost);
 		P_SetTarget(&ghost->tracer, ghost2);
-		ghost2->flags2 |= MF2_LINKDRAW;
+		ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW);
 	}
 
 	return ghost;
@@ -2249,7 +2263,20 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				else if (!player->skidtime)
 					player->pflags &= ~PF_GLIDING;
 			}
-			else if (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
+			else if (player->charability == CA_GLIDEANDCLIMB && player->pflags & PF_THOKKED && (~player->pflags) & PF_SHIELDABILITY)
+			{
+				if (player->mo->state-states != S_PLAY_GLIDE_LANDING)
+				{
+					P_ResetPlayer(player);
+					P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE_LANDING);
+					S_StartSound(player->mo, sfx_s3k4c);
+					player->pflags |= PF_STASIS;
+					player->mo->momx = ((player->mo->momx - player->cmomx)/3) + player->cmomx;
+					player->mo->momy = ((player->mo->momy - player->cmomy)/3) + player->cmomy;
+				}
+			}
+			else if (player->charability2 == CA2_MELEE
+				&& ((player->panim == PA_ABILITY2) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY && player->cmd.buttons & (BT_JUMP|BT_USE))))
 			{
 				if (player->mo->state-states != S_PLAY_MELEE_LANDING)
 				{
@@ -2301,7 +2328,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 			{
 				if (player->cmomx || player->cmomy)
 				{
-					if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+					if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH)
 						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
 					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
 					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
@@ -2314,7 +2341,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				}
 				else
 				{
-					if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim != PA_DASH)
+					if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH)
 						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
 					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
 					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
@@ -2445,39 +2472,42 @@ static void P_CheckBustableBlocks(player_t *player)
 
 				if ((rover->flags & FF_BUSTUP)/* && !rover->master->frontsector->crumblestate*/)
 				{
-					// If it's an FF_SPINBUST, you have to either be jumping, or coming down
-					// onto the top from a spin.
-					if (rover->flags & FF_SPINBUST && ((!(player->pflags & PF_JUMPED) && !(player->pflags & PF_SPINNING) && !(player->pflags & PF_BOUNCING)) || (player->pflags & PF_STARTDASH)))
+					// If it's an FF_SHATTER, you can break it just by touching it.
+					if (rover->flags & FF_SHATTER)
+						goto bust;
+
+					// If it's an FF_SPINBUST, you can break it if you are in your spinning frames
+					// (either from jumping or spindashing).
+					if (rover->flags & FF_SPINBUST
+						&& (((player->pflags & PF_SPINNING) && !(player->pflags & PF_STARTDASH))
+							|| (player->pflags & PF_JUMPED && !(player->pflags & PF_NOJUMPDAMAGE))))
+						goto bust;
+
+					// You can always break it if you have CA_GLIDEANDCLIMB
+					// or if you are bouncing on it
+					// or you are using CA_TWINSPIN/CA2_MELEE.
+					if (player->charability == CA_GLIDEANDCLIMB
+						|| (player->pflags & PF_BOUNCING)
+						|| ((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
+						|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
+						goto bust;
+
+					if (rover->flags & FF_STRONGBUST)
 						continue;
 
-					// if it's not an FF_SHATTER, you must be spinning (and not jumping)
-					// or be super
-					// or have CA_GLIDEANDCLIMB
-					// or be in dashmode with SF_DASHMODE
-					// or be using CA_TWINSPIN
-					// or be using CA2_MELEE
-					// or are drilling in NiGHTS
-					// or are recording for Metal Sonic
-					if (!(rover->flags & FF_SHATTER) && !(rover->flags & FF_SPINBUST)
-						&& !((player->pflags & PF_SPINNING) && !(player->pflags & PF_JUMPED))
+					// If it's not an FF_STRONGBUST, you can break if you are spinning (and not jumping)
+					// or you are super
+					// or you are in dashmode with SF_DASHMODE
+					// or you are drilling in NiGHTS
+					// or you are recording for Metal Sonic
+					if (!((player->pflags & PF_SPINNING) && !(player->pflags & PF_JUMPED))
 						&& !(player->powers[pw_super])
-						&& !(player->charability == CA_GLIDEANDCLIMB)
-						&& !(player->pflags & PF_BOUNCING)
-						&& !((player->charflags & SF_DASHMODE) && (player->dashmode >= 3*TICRATE))
-						&& !((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
-						&& !(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)
+						&& !(((player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)) && (player->dashmode >= DASHMODE_THRESHOLD))
 						&& !(player->pflags & PF_DRILLING)
 						&& !metalrecording)
 						continue;
 
-					// Only players with CA_GLIDEANDCLIMB, or CA_TWINSPIN/CA2_MELEE users can break this rock...
-					if (!(rover->flags & FF_SHATTER) && (rover->flags & FF_ONLYKNUX)
-						&& !(player->charability == CA_GLIDEANDCLIMB
-						|| (player->pflags & PF_BOUNCING)
-						|| ((player->charability == CA_TWINSPIN) && (player->panim == PA_ABILITY))
-						|| (player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2)))
-						continue;
-
+				bust:
 					topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 					bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 
@@ -3062,7 +3092,6 @@ static void P_DoClimbing(player_t *player)
 
 	glidesector = R_IsPointInSubsector(player->mo->x + platx, player->mo->y + platy);
 
-	if (!glidesector || glidesector->sector != player->mo->subsector->sector)
 	{
 		boolean floorclimb = false;
 		boolean thrust = false;
@@ -3446,9 +3475,13 @@ static void P_DoClimbing(player_t *player)
 		if (!floorclimb)
 		{
 			if (boostup)
+			{
 				P_SetObjectMomZ(player->mo, 2*FRACUNIT, true);
+				if (cmd->forwardmove)
+					P_SetObjectMomZ(player->mo, 2*player->mo->momz/3, false);
+			}
 			if (thrust)
-				P_InstaThrust(player->mo, player->mo->angle, FixedMul(4*FRACUNIT, player->mo->scale)); // Lil' boost up.
+				P_Thrust(player->mo, player->mo->angle, FixedMul(4*FRACUNIT, player->mo->scale)); // Lil' boost up.
 
 			player->climbing = 0;
 			player->pflags |= P_GetJumpFlags(player);
@@ -3462,12 +3495,6 @@ static void P_DoClimbing(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		}
 	}
-	else
-	{
-		player->climbing = 0;
-		player->pflags |= P_GetJumpFlags(player);
-		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
-	}
 
 	if (cmd->sidemove != 0 || cmd->forwardmove != 0)
 		climb = true;
@@ -3486,15 +3513,28 @@ static void P_DoClimbing(player_t *player)
 		player->pflags |= P_GetJumpFlags(player);
 		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
 		P_SetObjectMomZ(player->mo, 4*FRACUNIT, false);
-		P_InstaThrust(player->mo, player->mo->angle, FixedMul(-4*FRACUNIT, player->mo->scale));
+		P_Thrust(player->mo, player->mo->angle, FixedMul(-4*FRACUNIT, player->mo->scale));
 	}
 
+#define CLIMBCONEMAX FixedAngle(90*FRACUNIT)
 	if (!demoplayback || P_AnalogMove(player))
 	{
 		if (player == &players[consoleplayer])
-			localangle = player->mo->angle;
+		{
+			angle_t angdiff = localangle - player->mo->angle;
+			if (angdiff < ANGLE_180 && angdiff > CLIMBCONEMAX)
+				localangle = player->mo->angle + CLIMBCONEMAX;
+			else if (angdiff > ANGLE_180 && angdiff < InvAngle(CLIMBCONEMAX))
+				localangle = player->mo->angle - CLIMBCONEMAX;
+		}
 		else if (player == &players[secondarydisplayplayer])
-			localangle2 = player->mo->angle;
+		{
+			angle_t angdiff = localangle2 - player->mo->angle;
+			if (angdiff < ANGLE_180 && angdiff > CLIMBCONEMAX)
+				localangle2 = player->mo->angle + CLIMBCONEMAX;
+			else if (angdiff > ANGLE_180 && angdiff < InvAngle(CLIMBCONEMAX))
+				localangle2 = player->mo->angle - CLIMBCONEMAX;
+		}
 	}
 
 	if (player->climbing == 0)
@@ -3689,7 +3729,7 @@ static void P_DoTeeter(player_t *player)
 				bottomheight = *rover->bottomheight;
 #endif
 
-				if (P_CheckSolidLava(player->mo, rover))
+				if (P_CheckSolidLava(rover))
 					;
 				else if (!(rover->flags & FF_BLOCKPLAYER || rover->flags & FF_QUICKSAND))
 					continue; // intangible 3d floor
@@ -4142,8 +4182,11 @@ static void P_DoSuperStuff(player_t *player)
 		{
 			player->powers[pw_super] = 0;
 			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
-			music_stack_noposition = true; // HACK: Do not reposition next music
-			music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
+			if (P_IsLocalPlayer(player))
+			{
+				music_stack_noposition = true; // HACK: Do not reposition next music
+				music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
+			}
 			P_RestoreMusic(player);
 			P_SpawnShieldOrb(player);
 
@@ -4212,7 +4255,7 @@ static void P_DoSuperStuff(player_t *player)
 			if (gametype != GT_COOP)
 				player->powers[pw_flashing] = flashingtics-1;
 
-			if ((player->mo->health > 0) && (player->mo->sprite2 & FF_SPR2SUPER))
+			if (player->mo->sprite2 & FF_SPR2SUPER)
 				P_SetPlayerMobjState(player->mo, player->mo->state-states);
 
 			// Inform the netgame that the champion has fallen in the heat of battle.
@@ -4225,8 +4268,11 @@ static void P_DoSuperStuff(player_t *player)
 			}
 
 			// Resume normal music if you're the console player
-			music_stack_noposition = true; // HACK: Do not reposition next music
-			music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
+			if (P_IsLocalPlayer(player))
+			{
+				music_stack_noposition = true; // HACK: Do not reposition next music
+				music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
+			}
 			P_RestoreMusic(player);
 
 			// If you had a shield, restore its visual significance.
@@ -4311,6 +4357,16 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		if (player->mo->ceilingz-player->mo->floorz <= player->mo->height-1)
 			return;
 
+		if (player->powers[pw_carry] == CR_PTERABYTE)
+		{
+			S_StartSound(player->mo, sfx_s3kd7s);
+			player->mo->tracer->cusval += 10;
+			player->mo->tracer->watertop = P_RandomRange(-player->mo->tracer->cusval, player->mo->tracer->cusval) << (FRACBITS - 1);
+			player->mo->tracer->waterbottom = P_RandomRange(-player->mo->tracer->cusval, player->mo->tracer->cusval) << (FRACBITS - 1);
+			player->mo->tracer->cvmem = P_RandomRange(-player->mo->tracer->cusval, player->mo->tracer->cusval) << (FRACBITS - 1);
+			return;
+		}
+
 		// Jump this high.
 		if (player->powers[pw_carry] == CR_PLAYER)
 		{
@@ -4324,6 +4380,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		{
 			player->mo->momz = 9*FRACUNIT;
 			player->powers[pw_carry] = CR_NONE;
+			P_SetTarget(&player->mo->tracer->target, NULL);
 			P_SetTarget(&player->mo->tracer, NULL);
 		}
 		else if (player->powers[pw_carry] == CR_ROPEHANG)
@@ -4332,6 +4389,14 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			player->powers[pw_carry] = CR_NONE;
 			P_SetTarget(&player->mo->tracer, NULL);
 		}
+		else if (player->powers[pw_carry] == CR_ROLLOUT)
+		{
+			player->mo->momz = 9*FRACUNIT + player->mo->tracer->momz;
+			player->powers[pw_carry] = CR_NONE;
+			player->mo->tracer->flags |= MF_PUSHABLE;
+			P_SetTarget(&player->mo->tracer->tracer, NULL);
+			P_SetTarget(&player->mo->tracer, NULL);
+		}
 		else if (player->mo->eflags & MFE_GOOWATER)
 		{
 			player->mo->momz = 7*FRACUNIT;
@@ -4457,7 +4522,8 @@ static void P_DoSpinDashDust(player_t *player)
 static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 {
 	boolean canstand = true; // can we stand on the ground? (mostly relevant for slopes)
-	if (player->pflags & PF_STASIS)
+	if (player->pflags & PF_STASIS
+		&& (player->pflags & PF_JUMPSTASIS || player->mo->state-states != S_PLAY_GLIDE_LANDING))
 		return;
 
 #ifdef HAVE_BLUA
@@ -4481,7 +4547,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 		{
 			case CA2_SPINDASH: // Spinning and Spindashing
 				 // Start revving
-				if ((cmd->buttons & BT_USE) && player->speed < FixedMul(5<<FRACBITS, player->mo->scale)
+				if ((cmd->buttons & BT_USE) && (player->speed < FixedMul(5<<FRACBITS, player->mo->scale) || player->mo->state - states == S_PLAY_GLIDE_LANDING)
 					&& !player->mo->momz && onground && !(player->pflags & (PF_USEDOWN|PF_SPINNING))
 						&& canstand)
 				{
@@ -4643,8 +4709,9 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						if (player->speed < FixedMul(player->maxdash, player->mo->scale))
 #endif
 						{
-							player->drawangle = player->mo->angle;
-							P_InstaThrust(player->mo, player->mo->angle, FixedMul(player->maxdash, player->mo->scale));
+							if (player->panim == PA_IDLE)
+								player->drawangle = player->mo->angle;
+							P_InstaThrust(player->mo, player->drawangle, FixedMul(player->maxdash, player->mo->scale));
 						}
 						player->mo->momx += player->cmomx;
 						player->mo->momy += player->cmomy;
@@ -5242,7 +5309,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						player->glidetime = 0;
 
 						P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
-						P_InstaThrust(player->mo, player->mo->angle, FixedMul(glidespeed, player->mo->scale));
+						if (player->speed < glidespeed)
+							P_Thrust(player->mo, player->mo->angle, glidespeed - player->speed);
 						player->pflags &= ~(PF_SPINNING|PF_STARTDASH);
 					}
 					break;
@@ -5259,7 +5327,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				case CA_SLOWFALL: // Slow descent hover
 					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
-						if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE)
+						if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD)
 							P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
 						else if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
 							P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
@@ -5685,7 +5753,7 @@ static void P_2dMovement(player_t *player)
 	if (player->climbing)
 	{
 		if (cmd->forwardmove != 0)
-			P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT,10*FRACUNIT), false);
+			P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT, 15*FRACUNIT>>1), false);
 
 		player->mo->momx = 0;
 	}
@@ -5842,35 +5910,28 @@ static void P_3dMovement(player_t *player)
 		else
 			topspeed = normalspd;
 	}
-	else if (player->powers[pw_super] || player->powers[pw_sneakers])
+	else
 	{
-		thrustfactor = player->thrustfactor*2;
-		acceleration = player->accelstart/2 + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration/2;
-
-		if (player->powers[pw_tailsfly])
-			topspeed = normalspd;
-		else if (player->mo->eflags & (MFE_UNDERWATER|MFE_GOOWATER))
+		if (player->powers[pw_super] || player->powers[pw_sneakers])
 		{
-			topspeed = normalspd;
-			acceleration = 2*acceleration/3;
+			topspeed = 5 * normalspd / 3; // 1.67x
+			thrustfactor = player->thrustfactor*2;
+			acceleration = player->accelstart/2 + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration/2;
 		}
 		else
-			topspeed = normalspd * 2;
-	}
-	else
-	{
-		thrustfactor = player->thrustfactor;
-		acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
+		{
+			topspeed = normalspd;
+			thrustfactor = player->thrustfactor;
+			acceleration = player->accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * player->acceleration;
+		}
 
 		if (player->powers[pw_tailsfly])
-			topspeed = normalspd/2;
+			topspeed >>= 1;
 		else if (player->mo->eflags & (MFE_UNDERWATER|MFE_GOOWATER))
 		{
-			topspeed = normalspd/2;
+			topspeed >>= 1;
 			acceleration = 2*acceleration/3;
 		}
-		else
-			topspeed = normalspd;
 	}
 
 	if (spin) // Prevent gaining speed whilst rolling!
@@ -5909,7 +5970,7 @@ static void P_3dMovement(player_t *player)
 	if (player->climbing)
 	{
 		if (cmd->forwardmove)
-			P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT, 10*FRACUNIT), false);
+			P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT, 15*FRACUNIT>>1), false);
 	}
 	else if (!analogmove
 		&& cmd->forwardmove != 0 && !(player->pflags & PF_GLIDING || player->exiting
@@ -5943,7 +6004,7 @@ static void P_3dMovement(player_t *player)
 	}
 	// Sideways movement
 	if (player->climbing)
-		P_InstaThrust(player->mo, player->mo->angle-ANGLE_90, FixedMul(FixedDiv(cmd->sidemove*FRACUNIT, 10*FRACUNIT), player->mo->scale));
+		P_InstaThrust(player->mo, player->mo->angle-ANGLE_90, FixedDiv(cmd->sidemove*player->mo->scale, 15*FRACUNIT>>1));
 	// Analog movement control
 	else if (analogmove)
 	{
@@ -7350,10 +7411,13 @@ static void P_NiGHTSMovement(player_t *player)
 		&& player->mo->z + player->mo->height - P_GetPlayerHeight(player) <= player->mo->waterbottom && player->mo->z + player->mo->height >= player->mo->waterbottom))
 	&& player->speed > 9000 && leveltime % (TICRATE/7) == 0 && !player->spectator)
 	{
+		mobjtype_t splishtype = (player->mo->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
 		mobj_t *water = P_SpawnMobj(player->mo->x, player->mo->y,
-			((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_SPLISH].height, player->mo->scale) : player->mo->watertop), MT_SPLISH);
+			((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[splishtype].height, player->mo->scale) : player->mo->watertop), splishtype);
 		if (player->mo->eflags & MFE_GOOWATER)
 			S_StartSound(water, sfx_ghit);
+		else if (player->mo->eflags & MFE_TOUCHLAVA)
+			S_StartSound(water, sfx_splash);
 		else
 			S_StartSound(water, sfx_wslap);
 		if (player->mo->eflags & MFE_VERTICALFLIP)
@@ -7639,15 +7703,17 @@ static void P_SkidStuff(player_t *player)
 		{
 			player->skidtime = 0;
 			player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
+			player->pflags |= PF_THOKKED; // nice try, speedrunners (but for real this is just behavior from S3K)
 			P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 		}
 		// Get up and brush yourself off, idiot.
-		else if (player->glidetime > 15)
+		else if (player->glidetime > 15 || !(player->cmd.buttons & BT_JUMP))
 		{
 			P_ResetPlayer(player);
-			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
-			player->mo->momx = player->cmomx;
-			player->mo->momy = player->cmomy;
+			P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE_LANDING);
+			player->pflags |= PF_STASIS;
+			player->mo->momx = ((player->mo->momx - player->cmomx)/3) + player->cmomx;
+			player->mo->momy = ((player->mo->momy - player->cmomy)/3) + player->cmomy;
 		}
 		// Didn't stop yet? Skid FOREVER!
 		else if (player->skidtime == 1)
@@ -7655,7 +7721,8 @@ static void P_SkidStuff(player_t *player)
 		// Spawn a particle every 3 tics.
 		else if (!(player->skidtime % 3))
 		{
-			mobj_t *particle = P_SpawnMobjFromMobj(player->mo, P_RandomRange(-player->mo->radius, player->mo->radius), P_RandomRange(-player->mo->radius, player->mo->radius), 0, MT_SPINDUST);
+			fixed_t radius = player->mo->radius >> FRACBITS;
+			mobj_t *particle = P_SpawnMobjFromMobj(player->mo, P_RandomRange(-radius, radius) << FRACBITS, P_RandomRange(-radius, radius) << FRACBITS, 0, MT_SPINDUST);
 			particle->tics = 10;
 
 			particle->destscale = (2*player->mo->scale)/3;
@@ -7753,6 +7820,12 @@ static void P_MovePlayer(player_t *player)
 		if (!(player->powers[pw_nocontrol] & (1<<15)))
 			player->pflags |= PF_JUMPSTASIS;
 	}
+	
+	if (player->charability == CA_GLIDEANDCLIMB && player->mo->state-states == S_PLAY_GLIDE_LANDING)
+	{
+		player->pflags |= PF_STASIS;
+	}
+
 	// note: don't unset stasis here
 
 	if (!player->spectator && G_TagGametype())
@@ -7908,7 +7981,7 @@ static void P_MovePlayer(player_t *player)
 	if ((cmd->forwardmove != 0 || cmd->sidemove != 0) || (player->powers[pw_super] && !onground))
 	{
 		// If the player is in dashmode, here's their peelout.
-		if (player->charflags & SF_DASHMODE && player->dashmode >= 3*TICRATE && player->panim == PA_RUN && !player->skidtime && (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
+		if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim == PA_RUN && !player->skidtime && (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
 			P_SetPlayerMobjState (player->mo, S_PLAY_DASH);
 		// If the player is moving fast enough,
 		// break into a run!
@@ -7932,7 +8005,7 @@ static void P_MovePlayer(player_t *player)
 
 	// If your peelout animation is playing, and you're
 	// going too slow, switch back to the run.
-	if (player->charflags & SF_DASHMODE && player->panim == PA_DASH && player->dashmode < 3*TICRATE)
+	if (player->charflags & SF_DASHMODE && player->panim == PA_DASH && player->dashmode < DASHMODE_THRESHOLD)
 		P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
 
 	// If your running animation is playing, and you're
@@ -8012,10 +8085,13 @@ static void P_MovePlayer(player_t *player)
 	// AKA my own gravity. =)
 	if (player->pflags & PF_GLIDING)
 	{
+		mobj_t *mo = player->mo; // seriously why isn't this at the top of the function hngngngng
 		fixed_t leeway;
 		fixed_t glidespeed = player->actionspd;
+		fixed_t momx = mo->momx - player->cmomx, momy = mo->momy - player->cmomy;
+		angle_t angle, moveangle = R_PointToAngle2(0, 0, momx, momy);
 
-		if (player->powers[pw_super])
+		if (player->powers[pw_super] || player->powers[pw_sneakers])
 			glidespeed *= 2;
 
 		if (player->mo->eflags & MFE_VERTICALFLIP)
@@ -8030,22 +8106,46 @@ static void P_MovePlayer(player_t *player)
 		}
 
 		// Strafing while gliding.
-		leeway = FixedAngle(cmd->sidemove*(FRACUNIT/2));
+		leeway = FixedAngle(cmd->sidemove*(FRACUNIT));
+		angle = mo->angle - leeway;
 
-		if (player->skidtime) // ground gliding
+		if (!player->skidtime) // TODO: make sure this works in 2D!
 		{
-			fixed_t speed = FixedMul(glidespeed, FRACUNIT - (FRACUNIT>>2));
-			if (player->mo->eflags & MFE_UNDERWATER)
-				speed >>= 1;
-			speed = FixedMul(speed - player->glidetime*FRACUNIT, player->mo->scale);
-			if (speed < 0)
-				speed = 0;
-			P_InstaThrust(player->mo, player->mo->angle-leeway, speed);
+			fixed_t speed, scale = mo->scale;
+			fixed_t newMagnitude, oldMagnitude = R_PointToDist2(momx, momy, 0, 0);
+			fixed_t accelfactor = 4*FRACUNIT - 3*FINECOSINE(((angle-moveangle) >> ANGLETOFINESHIFT) & FINEMASK); // mamgic number BAD but this feels right
+
+			if (mo->eflags & MFE_UNDERWATER)
+				speed = FixedMul((glidespeed>>1) + player->glidetime*750, scale);
+			else
+				speed = FixedMul(glidespeed + player->glidetime*1500, scale);
+
+			P_Thrust(mo, angle, FixedMul(accelfactor, scale));
+
+			newMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
+			if (newMagnitude > speed)
+			{
+				fixed_t tempmomx, tempmomy;
+				if (oldMagnitude > speed)
+				{
+					if (newMagnitude > oldMagnitude)
+					{
+						tempmomx = FixedMul(FixedDiv(player->mo->momx - player->cmomx, newMagnitude), oldMagnitude);
+						tempmomy = FixedMul(FixedDiv(player->mo->momy - player->cmomy, newMagnitude), oldMagnitude);
+						player->mo->momx = tempmomx + player->cmomx;
+						player->mo->momy = tempmomy + player->cmomy;
+					}
+					// else do nothing
+				}
+				else
+				{
+					tempmomx = FixedMul(FixedDiv(player->mo->momx - player->cmomx, newMagnitude), speed);
+					tempmomy = FixedMul(FixedDiv(player->mo->momy - player->cmomy, newMagnitude), speed);
+					player->mo->momx = tempmomx + player->cmomx;
+					player->mo->momy = tempmomy + player->cmomy;
+				}
+			}
 		}
-		else if (player->mo->eflags & MFE_UNDERWATER)
-			P_InstaThrust(player->mo, player->mo->angle-leeway, FixedMul((glidespeed>>1) + player->glidetime*750, player->mo->scale));
-		else
-			P_InstaThrust(player->mo, player->mo->angle-leeway, FixedMul(glidespeed + player->glidetime*1500, player->mo->scale));
 
 		player->glidetime++;
 
@@ -8070,18 +8170,9 @@ static void P_MovePlayer(player_t *player)
 	}
 	else if (player->climbing) // 'Deceleration' for climbing on walls.
 	{
-		if (player->mo->momz > 0)
-		{
-			player->mo->momz -= FixedMul(FRACUNIT/2, player->mo->scale);
-			if (player->mo->momz < 0)
-				player->mo->momz = 0;
-		}
-		else if (player->mo->momz < 0)
-		{
-			player->mo->momz += FixedMul(FRACUNIT/2, player->mo->scale);
-			if (player->mo->momz > 0)
-				player->mo->momz = 0;
-		}
+
+		if (!player->cmd.forwardmove)
+			player->mo->momz = 0;
 	}
 	else if (player->pflags & PF_BOUNCING)
 	{
@@ -8115,10 +8206,13 @@ static void P_MovePlayer(player_t *player)
 	&& (player->speed > runspd || (player->pflags & PF_STARTDASH))
 	&& leveltime % (TICRATE/7) == 0 && player->mo->momz == 0 && !(player->pflags & PF_SLIDING) && !player->spectator)
 	{
+		mobjtype_t splishtype = (player->mo->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
 		mobj_t *water = P_SpawnMobj(player->mo->x - P_ReturnThrustX(NULL, player->mo->angle, player->mo->radius), player->mo->y - P_ReturnThrustY(NULL, player->mo->angle, player->mo->radius),
-			((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_SPLISH].height, player->mo->scale) : player->mo->watertop), MT_SPLISH);
+			((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[splishtype].height, player->mo->scale) : player->mo->watertop), splishtype);
 		if (player->mo->eflags & MFE_GOOWATER)
 			S_StartSound(water, sfx_ghit);
+		else if (player->mo->eflags & MFE_TOUCHLAVA)
+			S_StartSound(water, sfx_splash);
 		else
 			S_StartSound(water, sfx_wslap);
 		if (player->mo->eflags & MFE_VERTICALFLIP)
@@ -8855,7 +8949,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 			continue;
 
 		if (mo->type == MT_MINUS && !(mo->flags & (MF_SPECIAL|MF_SHOOTABLE)))
-			mo->flags |= MF_SPECIAL|MF_SHOOTABLE;
+			mo->flags = (mo->flags & ~MF_NOCLIPTHING)|MF_SPECIAL|MF_SHOOTABLE;
 
 		if (mo->type == MT_EGGGUARD && mo->tracer) //nuke Egg Guard's shield!
 			P_KillMobj(mo->tracer, inflictor, source, DMG_NUKE);
@@ -10254,7 +10348,7 @@ static sector_t *P_GetMinecartSector(fixed_t x, fixed_t y, fixed_t z, fixed_t *n
 		ffloor_t *rover;
 		for (rover = sec->ffloors; rover; rover = rover->next)
 		{
-			if (!(rover->flags & FF_EXISTS))
+			if (!(rover->flags & (FF_EXISTS|FF_BLOCKOTHERS)))
 				continue;
 
 			*nz = *rover->t_slope ? P_GetZAt(*rover->t_slope, x, y) : *rover->topheight;
@@ -10426,7 +10520,7 @@ static mobj_t *P_LookForRails(mobj_t* mobj, fixed_t c, fixed_t s, angle_t target
 			//Axes must be directly parallel or antiparallel, give or take 5 degrees.
 			if (angdiff < ANG10)
 			{
-				mark = P_SpawnMobj(nx, ny, nz, mobj->info->raisestate);
+				mark = P_SpawnMobj(nx, ny, nz, (mobjtype_t)mobj->info->raisestate);
 				return mark;
 			}
 		}
@@ -10641,7 +10735,11 @@ static void P_MinecartThink(player_t *player)
 		}
 	}
 
-	P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+	if (player->mo->state-states != S_PLAY_STND)
+	{
+		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		player->mo->tics = -1;
+	}
 
 	// Move player to minecart.
 	P_TeleportMove(player->mo, minecart->x - minecart->momx, minecart->y - minecart->momy, minecart->z + max(minecart->momz, 0) + 8*FRACUNIT);
@@ -10852,6 +10950,123 @@ static void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 	P_SetThingPosition(tails);
 }
 
+// Metal Sonic's jet fume
+static void P_DoMetalJetFume(player_t *player, mobj_t *fume)
+{
+	static const UINT8 FUME_SKINCOLORS[] =
+	{
+		SKINCOLOR_ICY,
+		SKINCOLOR_SKY,
+		SKINCOLOR_CYAN,
+		SKINCOLOR_WAVE,
+		SKINCOLOR_TEAL,
+		SKINCOLOR_AQUA,
+		SKINCOLOR_SEAFOAM,
+		SKINCOLOR_MINT,
+		SKINCOLOR_PERIDOT,
+		SKINCOLOR_LIME,
+		SKINCOLOR_YELLOW,
+		SKINCOLOR_SANDY,
+		SKINCOLOR_GOLD,
+		SKINCOLOR_APRICOT,
+		SKINCOLOR_SUNSET
+	};
+	mobj_t *mo = player->mo;
+	angle_t angle = player->drawangle;
+	fixed_t dist;
+	panim_t panim = player->panim;
+	tic_t dashmode = player->dashmode;
+	boolean underwater = mo->eflags & MFE_UNDERWATER;
+	statenum_t stat = fume->state-states;
+	
+	if (panim != PA_WALK && panim != PA_RUN && panim != PA_DASH) // turn invisible when not in a coherent movement state
+	{
+		if (stat != fume->info->spawnstate)
+			P_SetMobjState(fume, fume->info->spawnstate);
+		return;
+	}
+	
+	if (underwater) // No fume underwater; spawn bubbles instead!
+	{
+		fume->movedir	+= FixedAngle(FixedDiv(2 * player->speed, 3 * mo->scale));
+		fume->movefactor += player->speed;
+		
+		if (fume->movefactor > FixedDiv(2 * player->normalspeed, 3 * mo->scale))
+		{
+			INT16 i;
+			fixed_t radiusV = 4*FRACUNIT;
+			fixed_t radiusX = P_ReturnThrustX(mo, angle, -mo->radius >> (panim == PA_WALK ? 1 : 0));
+			fixed_t radiusY = P_ReturnThrustY(mo, angle, -mo->radius >> (panim == PA_WALK ? 1 : 0));
+			fixed_t factorX = P_ReturnThrustX(mo, angle + ANGLE_90, mo->scale);
+			fixed_t factorY = P_ReturnThrustY(mo, angle + ANGLE_90, mo->scale);
+			fixed_t offsetH, offsetV, x, y, z;
+			
+			for (i = -1; i < 2; i += 2)
+			{
+				offsetH = i*P_ReturnThrustX(fume, fume->movedir, radiusV);
+				offsetV = i*P_ReturnThrustY(fume, fume->movedir, radiusV);
+				x = mo->x + radiusX + FixedMul(offsetH, factorX);
+				y = mo->y + radiusY + FixedMul(offsetH, factorY);
+				z = mo->z + (mo->height >> 1) + offsetV;
+				P_SpawnMobj(x, y, z, MT_SMALLBUBBLE)->scale = mo->scale >> 1;
+			}
+			
+			fume->movefactor = 0;
+		}
+		
+		if (panim == PA_WALK)
+		{
+			if (stat != fume->info->spawnstate)
+				P_SetMobjState(fume, fume->info->spawnstate);
+			return;
+		}
+	}
+	
+	if (stat == fume->info->spawnstate) // If currently inivisble, activate!
+	{
+		P_SetMobjState(fume, (stat = fume->info->seestate));
+		P_SetScale(fume, mo->scale);
+	}
+	
+	if (dashmode > DASHMODE_THRESHOLD && stat != fume->info->seestate) // If in dashmode, grow really big and flash
+	{
+		fume->destscale = mo->scale;
+		fume->flags2 ^= MF2_DONTDRAW;
+		fume->flags2 |= mo->flags2 & MF2_DONTDRAW;
+	}
+	else // Otherwise, pick a size and color depending on speed and proximity to dashmode
+	{
+		if (dashmode == DASHMODE_THRESHOLD && dashmode > (tic_t)fume->movecount) // If just about to enter dashmode, play the startup animation again
+		{
+			P_SetMobjState(fume, (stat = fume->info->seestate));
+			P_SetScale(fume, mo->scale << 1);
+		}
+		fume->flags2 = (fume->flags2 & ~MF2_DONTDRAW) | (mo->flags2 & MF2_DONTDRAW);
+		fume->destscale = (mo->scale + FixedDiv(player->speed, player->normalspeed)) / (underwater ? 6 : 3);
+		fume->color = FUME_SKINCOLORS[(dashmode * sizeof(FUME_SKINCOLORS)) / (DASHMODE_MAX + 1)];
+		
+		if (underwater)
+		{
+			fume->frame = (fume->frame & FF_FRAMEMASK) | FF_ANIMATE | (P_RandomRange(0, 9) * FF_TRANS10);
+		}
+	}
+	
+	fume->movecount = dashmode; // keeps track of previous dashmode value so we know whether Metal is entering or leaving it
+	fume->eflags = (fume->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP); // Make sure to flip in reverse gravity!
+	
+	// Finally, set its position
+	dist = -mo->radius - FixedMul(fume->info->radius, fume->destscale - mo->scale/3);
+	
+	P_UnsetThingPosition(fume);
+	fume->x = mo->x + P_ReturnThrustX(fume, angle, dist);
+	fume->y = mo->y + P_ReturnThrustY(fume, angle, dist);
+	if (fume->eflags & MFE_VERTICALFLIP)
+		fume->z = mo->z + ((mo->height + fume->height) >> 1);
+	else
+		fume->z = mo->z + ((mo->height - fume->height) >> 1);
+	P_SetThingPosition(fume);
+}
+
 //
 // P_PlayerThink
 //
@@ -11240,14 +11455,15 @@ void P_PlayerThink(player_t *player)
 
 	// deez New User eXperiences.
 	{
+		angle_t diff = 0;
+		UINT8 factor;
 		// Directionchar!
 		// Camera angle stuff.
 		if (player->exiting // no control, no modification
 		|| player->powers[pw_carry] == CR_NIGHTSMODE)
 			;
 		else if (!(player->pflags & PF_DIRECTIONCHAR)
-		|| (player->climbing // stuff where the direction is forced at all times
-		|| (player->pflags & PF_GLIDING))
+		|| (player->climbing) // stuff where the direction is forced at all times
 		|| (P_AnalogMove(player) || twodlevel || player->mo->flags2 & MF2_TWOD) // keep things synchronised up there, since the camera IS seperate from player motion when that happens
 		|| G_RingSlingerGametype()) // no firing rings in directions your player isn't aiming
 			player->drawangle = player->mo->angle;
@@ -11266,8 +11482,16 @@ void P_PlayerThink(player_t *player)
 					/* FALLTHRU */
 				case CR_MINECART:
 				case CR_GENERIC:
+				case CR_PTERABYTE:
 					player->drawangle = player->mo->tracer->angle;
 					break;
+				case CR_ROLLOUT:
+					if (cmd->forwardmove || cmd->sidemove) // only when you're pressing movement keys
+					{ // inverse direction!
+						diff = ((player->mo->angle + R_PointToAngle2(0, 0, -cmd->forwardmove<<FRACBITS, cmd->sidemove<<FRACBITS)) - player->drawangle);
+						factor = 4;
+					}
+					break;
 				/* -- in case we wanted to have the camera freely movable during zoom tubes
 				case CR_ZOOMTUBE:*/
 				case CR_ROPEHANG:
@@ -11282,22 +11506,34 @@ void P_PlayerThink(player_t *player)
 					break;
 			}
 		}
+		else if (player->powers[pw_justsprung])
+		{
+#ifdef SPRINGSPIN
+			if (player->powers[pw_justsprung] & (1<<15))
+				player->drawangle += (player->powers[pw_justsprung] & ~(1<<15))*(ANG2+ANG1);
+#endif
+		}
 		else if ((player->skidtime > (TICRATE/2 - 2) || ((player->pflags & (PF_SPINNING|PF_STARTDASH)) == PF_SPINNING)) && (abs(player->rmomx) > 5*player->mo->scale || abs(player->rmomy) > 5*player->mo->scale)) // spin/skid force
 			player->drawangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy);
 		else if (((player->charability2 == CA2_GUNSLINGER || player->charability2 == CA2_MELEE) && player->panim == PA_ABILITY2) || player->pflags & PF_STASIS || player->skidtime)
 			;
 		else
 		{
-			angle_t diff;
-			UINT8 factor;
-
-			if (player->pflags & PF_SLIDING)
+			if (player->pflags & PF_GLIDING)
+			{
+				if (player->speed < player->mo->scale)
+					diff = player->mo->angle - player->drawangle;
+				else
+					diff = (R_PointToAngle2(0, 0, player->rmomx, player->rmomy) - player->drawangle);
+				factor = 4;
+			}
+			else if (player->pflags & PF_SLIDING)
 			{
 #if 0 // fun hydrocity style horizontal spin
 				if (player->mo->eflags & MFE_TOUCHWATER || player->powers[pw_flashing] > (flashingtics/4)*3)
 				{
 					diff = (player->mo->angle - player->drawangle);
-					factor = 4;
+					factor = 16;
 				}
 				else
 				{
@@ -11306,7 +11542,7 @@ void P_PlayerThink(player_t *player)
 				}
 #else
 				diff = (player->mo->angle - player->drawangle);
-				factor = 4;
+				factor = 16;
 #endif
 			}
 			else if (player->pflags & PF_STARTDASH)
@@ -11326,22 +11562,24 @@ void P_PlayerThink(player_t *player)
 				diff = (player->mo->angle - player->drawangle);
 				factor = 8;
 			}
+		}
 
-			if (diff)
-			{
-				if (diff > ANGLE_180)
-					diff = InvAngle(InvAngle(diff)/factor);
-				else
-					diff /= factor;
-				player->drawangle += diff;
-			}
+		if (diff)
+		{
+			if (diff > ANGLE_180)
+				diff = InvAngle(InvAngle(diff)/factor);
+			else
+				diff /= factor;
+			player->drawangle += diff;
 		}
 
 		// Autobrake! check ST_drawInput if you modify this
 		{
 			boolean currentlyonground = P_IsObjectOnGround(player->mo);
 
-			if (!player->powers[pw_carry] && !player->powers[pw_nocontrol]
+			if (player->powers[pw_noautobrake])
+				;
+			else if (!player->powers[pw_carry] && !player->powers[pw_nocontrol]
 			&& ((player->pflags & (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE|PF_STASIS)) == (PF_AUTOBRAKE|PF_APPLYAUTOBRAKE))
 			&& !(cmd->forwardmove || cmd->sidemove)
 			&& (player->rmomx || player->rmomy)
@@ -11383,9 +11621,6 @@ void P_PlayerThink(player_t *player)
 		}
 	}
 
-	if (player->powers[pw_pushing])
-		player->powers[pw_pushing]--;
-
 	player->mo->movefactor = FRACUNIT; // We're not going to do any more with this, so let's change it back for the next frame.
 
 	// Unset statis flags after moving.
@@ -11465,6 +11700,17 @@ void P_PlayerThink(player_t *player)
 	if (player->powers[pw_tailsfly] && player->powers[pw_tailsfly] < UINT16_MAX && player->charability != CA_SWIM) // tails fly counter
 		player->powers[pw_tailsfly]--;
 
+	if (player->powers[pw_pushing] && player->powers[pw_pushing] < UINT16_MAX)
+		player->powers[pw_pushing]--;
+
+	if (player->powers[pw_justsprung] & ((1<<15)-1) && player->powers[pw_justsprung] < UINT16_MAX)
+		player->powers[pw_justsprung]--;
+	else
+		player->powers[pw_justsprung] = 0;
+
+	if (player->powers[pw_noautobrake] && player->powers[pw_noautobrake] < UINT16_MAX)
+		player->powers[pw_noautobrake]--;
+
 	if (player->powers[pw_underwater] && (player->pflags & PF_GODMODE || (player->powers[pw_shield] & SH_PROTECTWATER)))
 	{
 		if (player->powers[pw_underwater] <= 12*TICRATE+1)
@@ -11573,9 +11819,9 @@ void P_PlayerThink(player_t *player)
 
 		if ((totallyradical && !floating) || (player->pflags & PF_STARTDASH))
 		{
-			if (dashmode < 3*TICRATE + 3)
+			if (dashmode < DASHMODE_MAX)
 				dashmode++; // Counter. Adds 1 to dash mode per tic in top speed.
-			if (dashmode == 3*TICRATE) // This isn't in the ">=" equation because it'd cause the sound to play infinitely.
+			if (dashmode == DASHMODE_THRESHOLD) // This isn't in the ">=" equation because it'd cause the sound to play infinitely.
 				S_StartSound(player->mo, sfx_s3ka2); // If the player enters dashmode, play this sound on the the tic it starts.
 		}
 		else if ((!totallyradical || !floating) && !(player->pflags & PF_SPINNING))
@@ -11586,7 +11832,7 @@ void P_PlayerThink(player_t *player)
 				dashmode = 0;
 		}
 
-		if (dashmode < 3*TICRATE) // Exits Dash Mode if you drop below speed/dash counter tics. Not in the above block so it doesn't keep disabling in midair.
+		if (dashmode < DASHMODE_THRESHOLD) // Exits Dash Mode if you drop below speed/dash counter tics. Not in the above block so it doesn't keep disabling in midair.
 		{
 			player->normalspeed = skins[player->skin].normalspeed; // Reset to default if not capable of entering dash mode.
 			player->jumpfactor = skins[player->skin].jumpfactor;
@@ -11608,7 +11854,7 @@ void P_PlayerThink(player_t *player)
 	}
 	else if (dashmode)
 	{
-		if (dashmode >= 3*TICRATE) // catch getting the flag!
+		if (dashmode >= DASHMODE_THRESHOLD) // catch getting the flag!
 		{
 			player->normalspeed = skins[player->skin].normalspeed;
 			player->jumpfactor = skins[player->skin].jumpfactor;
@@ -11653,6 +11899,36 @@ void P_PlayerThink(player_t *player)
 	}*/
 }
 
+// Checks if the mobj is above lava. Used by Pterabyte.
+static boolean P_MobjAboveLava(mobj_t *mobj)
+{
+	sector_t *sector = mobj->subsector->sector;
+
+	if (sector->ffloors)
+	{
+		ffloor_t *rover;
+
+		for (rover = sector->ffloors; rover; rover = rover->next)
+		{
+			if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || GETSECSPECIAL(rover->master->frontsector->special, 1) != 3)
+				continue;
+
+			if (mobj->eflags & MFE_VERTICALFLIP)
+			{
+				if (*rover->bottomheight <= mobj->ceilingz && *rover->bottomheight >= mobj->z)
+					return true;
+			}
+			else
+			{
+				if (*rover->topheight >= mobj->floorz && *rover->topheight <= mobj->z)
+					return true;
+			}
+		}
+	}
+
+	return false;
+}
+
 //
 // P_PlayerAfterThink
 //
@@ -11968,6 +12244,114 @@ void P_PlayerAfterThink(player_t *player)
 				}
 				break;
 			}
+			case CR_ROLLOUT:
+			{
+				mobj_t *mo = player->mo, *rock = player->mo->tracer;
+				UINT8 walktics = mo->state->tics - P_GetPlayerControlDirection(player);
+
+				if (!rock || P_MobjWasRemoved(rock))
+				{
+					P_SetTarget(&player->mo->tracer, NULL);
+					player->powers[pw_carry] = CR_NONE;
+					break;
+				}
+
+				if (player->cmd.forwardmove || player->cmd.sidemove)
+				{
+					rock->movedir = (player->cmd.angleturn << FRACBITS) + R_PointToAngle2(0, 0, player->cmd.forwardmove << FRACBITS, -player->cmd.sidemove << FRACBITS);
+					P_Thrust(rock, rock->movedir, rock->scale >> 1);
+				}
+
+				mo->momx = rock->momx;
+				mo->momy = rock->momy;
+				mo->momz = 0;
+
+				if (player->panim == PA_IDLE && (mo->momx || mo->momy))
+				{
+					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				}
+
+				if (player->panim == PA_WALK && mo->tics > walktics)
+				{
+					mo->tics = walktics;
+				}
+
+				P_TeleportMove(player->mo, rock->x, rock->y, rock->z + rock->height);
+				break;
+			}
+			case CR_PTERABYTE: // being carried by a Pterabyte
+			{
+				mobj_t *ptera = player->mo->tracer;
+				mobj_t *spawnpoint = ptera->tracer->tracer;
+				player->mo->height = FixedDiv(P_GetPlayerHeight(player), FixedDiv(14 * FRACUNIT, 10 * FRACUNIT));
+
+				if (ptera->health <= 0)
+					goto dropoff;
+
+				if (P_MobjAboveLava(ptera) && ptera->movefactor <= 3*TICRATE - 10)
+					goto dropoff;
+
+				if (player->mo->eflags & MFE_VERTICALFLIP)
+				{
+					if ((ptera->z + ptera->height + player->mo->height + FixedMul(FRACUNIT, player->mo->scale)) <= ptera->ceilingz
+						&& (ptera->eflags & MFE_VERTICALFLIP)) // Reverse gravity check for the carrier - Flame
+						player->mo->z = ptera->z + ptera->height + FixedMul(FRACUNIT, player->mo->scale);
+
+					if (ptera->ceilingz - ptera->z > spawnpoint->ceilingz - spawnpoint->z + 512*FRACUNIT && ptera->movefactor <= 3 * TICRATE - 10)
+						goto dropoff;
+				}
+				else
+				{
+					if ((ptera->z - player->mo->height - FixedMul(FRACUNIT, player->mo->scale)) >= ptera->floorz
+						&& !(ptera->eflags & MFE_VERTICALFLIP)) // Correct gravity check for the carrier - Flame
+						player->mo->z = ptera->z - player->mo->height - FixedMul(FRACUNIT, player->mo->scale);
+
+					if (ptera->z - ptera->floorz > spawnpoint->z - spawnpoint->floorz + 512 * FRACUNIT && ptera->movefactor <= 3 * TICRATE - 10)
+						goto dropoff;
+				}
+
+				ptera->movefactor--;
+				if (!ptera->movefactor)
+					goto dropoff;
+
+				if (ptera->cusval >= 50)
+				{
+					player->powers[pw_carry] = CR_NONE;
+					P_SetTarget(&player->mo->tracer, NULL);
+					P_KillMobj(ptera, player->mo, player->mo, 0);
+					player->mo->momz = 9*FRACUNIT;
+					player->pflags |= PF_APPLYAUTOBRAKE|PF_JUMPED|PF_THOKKED;
+					P_SetMobjState(player->mo, S_PLAY_ROLL);
+					break;
+				}
+
+				if (ptera->cusval)
+					ptera->cusval--;
+
+				P_TryMove(player->mo, ptera->x + ptera->watertop, ptera->y + ptera->waterbottom, true);
+				player->mo->z += ptera->cvmem;
+				player->mo->momx = ptera->momx;
+				player->mo->momy = ptera->momy;
+				player->mo->momz = ptera->momz;
+
+				if (P_AproxDistance(player->mo->x - ptera->x - ptera->watertop, player->mo->y - ptera->y - ptera->waterbottom) > player->mo->radius)
+					goto dropoff;
+
+				ptera->watertop >>= 1;
+				ptera->waterbottom >>= 1;
+				ptera->cvmem >>= 1;
+
+				if (player->mo->state-states != S_PLAY_FALL)
+					P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+				break;
+
+			dropoff:
+				player->powers[pw_carry] = CR_NONE;
+				P_SetTarget(&player->mo->tracer, NULL);
+				ptera->movefactor = TICRATE;
+				ptera->extravalue1 |= 4;
+				break;
+			}
 			default:
 				break;
 		}
@@ -12016,7 +12400,15 @@ void P_PlayerAfterThink(player_t *player)
 		{
 			P_SetTarget(&player->followmobj, P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem));
 			P_SetTarget(&player->followmobj->tracer, player->mo);
-			player->followmobj->flags2 |= MF2_LINKDRAW;
+			switch (player->followmobj->type)
+			{
+				case MT_METALJETFUME:
+					player->followmobj->colorized = true;
+					break;
+				default:
+					player->followmobj->flags2 |= MF2_LINKDRAW;
+					break;
+			}
 		}
 
 		if (player->followmobj)
@@ -12032,6 +12424,9 @@ void P_PlayerAfterThink(player_t *player)
 					case MT_TAILSOVERLAY: // c:
 						P_DoTailsOverlay(player, player->followmobj);
 						break;
+					case MT_METALJETFUME:
+						P_DoMetalJetFume(player, player->followmobj);
+						break;
 					default:
 						var1 = 1;
 						var2 = 0;
diff --git a/src/r_data.c b/src/r_data.c
index b8b3630219c7bbb4adf02ec29f8a5d8ec00049e7..524baad151cf534125790bc125d597f7620d27c5 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -35,7 +35,7 @@
 #endif
 
 // Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
-#ifdef _WIN32_WCE
+#if 0
 #define AVOID_ERRNO
 #else
 #include <errno.h>
@@ -483,7 +483,7 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 		wadnum = patch->wad;
 		lumpnum = patch->lump;
 		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-		realpatch = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+		realpatch = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE); // can't use W_CachePatchNumPwad because OpenGL
 
 #ifndef NO_PNG_LUMPS
 		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
@@ -557,7 +557,7 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	texturememory += blocksize;
 	block = Z_Malloc(blocksize+1, PU_STATIC, &texturecache[texnum]);
 
-	memset(block, 0xFF, blocksize+1); // Transparency hack
+	memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack
 
 	// columns lookup table
 	colofs = (UINT32 *)(void *)block;
@@ -2520,7 +2520,11 @@ void R_PrecacheLevel(void)
 			"spritememory:  %s k\n", sizeu1(flatmemory>>10), sizeu2(texturememory>>10), sizeu3(spritememory>>10));
 }
 
-// https://github.com/coelckers/prboom-plus/blob/master/prboom2/src/r_patch.c#L350
+//
+// R_CheckIfPatch
+//
+// Returns true if the lump is a valid patch.
+//
 boolean R_CheckIfPatch(lumpnum_t lump)
 {
 	size_t size;
@@ -2565,6 +2569,71 @@ boolean R_CheckIfPatch(lumpnum_t lump)
 	return result;
 }
 
+//
+// R_TextureToFlat
+//
+// Convert a texture to a flat.
+//
+void R_TextureToFlat(size_t tex, UINT8 *flat)
+{
+	texture_t *texture = textures[tex];
+
+	fixed_t col, ofs;
+	column_t *column;
+	UINT8 *desttop, *dest, *deststop;
+	UINT8 *source;
+
+	// yea
+	R_CheckTextureCache(tex);
+
+	desttop = flat;
+	deststop = desttop + (texture->width * texture->height);
+
+	for (col = 0; col < texture->width; col++, desttop++)
+	{
+		// no post_t info
+		if (!texture->holes)
+		{
+			column = (column_t *)(R_GetColumn(tex, col));
+			source = (UINT8 *)(column);
+			dest = desttop;
+			for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
+			{
+				if (source[ofs] != TRANSPARENTPIXEL)
+					*dest = source[ofs];
+				dest += texture->width;
+			}
+		}
+		else
+		{
+			INT32 topdelta, prevdelta = -1;
+			column = (column_t *)((UINT8 *)R_GetColumn(tex, col) - 3);
+			while (column->topdelta != 0xff)
+			{
+				topdelta = column->topdelta;
+				if (topdelta <= prevdelta)
+					topdelta += prevdelta;
+				prevdelta = topdelta;
+
+				dest = desttop + (topdelta * texture->width);
+				source = (UINT8 *)column + 3;
+				for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
+				{
+					if (source[ofs] != TRANSPARENTPIXEL)
+						*dest = source[ofs];
+					dest += texture->width;
+				}
+				column = (column_t *)((UINT8 *)column + column->length + 4);
+			}
+		}
+	}
+}
+
+//
+// R_PatchToFlat
+//
+// Convert a patch to a flat.
+//
 void R_PatchToFlat(patch_t *patch, UINT8 *flat)
 {
 	fixed_t col, ofs;
@@ -2599,7 +2668,124 @@ void R_PatchToFlat(patch_t *patch, UINT8 *flat)
 	}
 }
 
+//
+// R_FlatToPatch
+//
+// Convert a flat to a patch.
+//
+static unsigned char imgbuf[1<<26];
+patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize, boolean transparency)
+{
+	UINT32 x, y;
+	UINT8 *img;
+	UINT8 *imgptr = imgbuf;
+	UINT8 *colpointers, *startofspan;
+	size_t size = 0;
+
+	// Write image size and offset
+	WRITEINT16(imgptr, width);
+	WRITEINT16(imgptr, height);
+	WRITEINT16(imgptr, leftoffset);
+	WRITEINT16(imgptr, topoffset);
+
+	// Leave placeholder to column pointers
+	colpointers = imgptr;
+	imgptr += width*4;
+
+	// Write columns
+	for (x = 0; x < width; x++)
+	{
+		int lastStartY = 0;
+		int spanSize = 0;
+		startofspan = NULL;
+
+		// Write column pointer
+		WRITEINT32(colpointers, imgptr - imgbuf);
+
+		// Write pixels
+		for (y = 0; y < height; y++)
+		{
+			UINT8 paletteIndex = raw[((y * width) + x)];
+			boolean opaque = transparency ? (paletteIndex != TRANSPARENTPIXEL) : true;
+
+			// End span if we have a transparent pixel
+			if (!opaque)
+			{
+				if (startofspan)
+					WRITEUINT8(imgptr, 0);
+				startofspan = NULL;
+				continue;
+			}
+
+			// Start new column if we need to
+			if (!startofspan || spanSize == 255)
+			{
+				int writeY = y;
+
+				// If we reached the span size limit, finish the previous span
+				if (startofspan)
+					WRITEUINT8(imgptr, 0);
+
+				if (y > 254)
+				{
+					// Make sure we're aligned to 254
+					if (lastStartY < 254)
+					{
+						WRITEUINT8(imgptr, 254);
+						WRITEUINT8(imgptr, 0);
+						imgptr += 2;
+						lastStartY = 254;
+					}
+
+					// Write stopgap empty spans if needed
+					writeY = y - lastStartY;
+
+					while (writeY > 254)
+					{
+						WRITEUINT8(imgptr, 254);
+						WRITEUINT8(imgptr, 0);
+						imgptr += 2;
+						writeY -= 254;
+					}
+				}
+
+				startofspan = imgptr;
+				WRITEUINT8(imgptr, writeY);
+				imgptr += 2;
+				spanSize = 0;
+
+				lastStartY = y;
+			}
+
+			// Write the pixel
+			WRITEUINT8(imgptr, paletteIndex);
+			spanSize++;
+			startofspan[1] = spanSize;
+		}
+
+		if (startofspan)
+			WRITEUINT8(imgptr, 0);
+
+		WRITEUINT8(imgptr, 0xFF);
+	}
+
+	size = imgptr-imgbuf;
+	img = Z_Malloc(size, PU_STATIC, NULL);
+	memcpy(img, imgbuf, size);
+
+	Z_Free(raw);
+
+	if (destsize != NULL)
+		*destsize = size;
+	return (patch_t *)img;
+}
+
 #ifndef NO_PNG_LUMPS
+//
+// R_IsLumpPNG
+//
+// Returns true if the lump is a valid PNG.
+//
 boolean R_IsLumpPNG(const UINT8 *d, size_t s)
 {
 	if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
@@ -2812,125 +2998,31 @@ static UINT8 *PNG_RawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topo
 	return flat;
 }
 
+//
+// R_PNGToFlat
+//
 // Convert a PNG to a flat.
-UINT8 *R_PNGToFlat(levelflat_t *levelflat, UINT8 *png, size_t size)
+//
+UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size)
 {
-	return PNG_RawConvert(png, &levelflat->width, &levelflat->height, NULL, NULL, size);
+	return PNG_RawConvert(png, width, height, NULL, NULL, size);
 }
 
+//
+// R_PNGToPatch
+//
 // Convert a PNG to a patch.
-static unsigned char imgbuf[1<<26];
+//
 patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize, boolean transparency)
 {
 	UINT16 width, height;
 	INT16 topoffset = 0, leftoffset = 0;
 	UINT8 *raw = PNG_RawConvert(png, &width, &height, &topoffset, &leftoffset, size);
 
-	UINT32 x, y;
-	UINT8 *img;
-	UINT8 *imgptr = imgbuf;
-	UINT8 *colpointers, *startofspan;
-
 	if (!raw)
 		I_Error("R_PNGToPatch: conversion failed");
 
-	// Write image size and offset
-	WRITEINT16(imgptr, width);
-	WRITEINT16(imgptr, height);
-	WRITEINT16(imgptr, leftoffset);
-	WRITEINT16(imgptr, topoffset);
-
-	// Leave placeholder to column pointers
-	colpointers = imgptr;
-	imgptr += width*4;
-
-	// Write columns
-	for (x = 0; x < width; x++)
-	{
-		int lastStartY = 0;
-		int spanSize = 0;
-		startofspan = NULL;
-
-		//printf("%d ", x);
-		// Write column pointer (@TODO may be wrong)
-		WRITEINT32(colpointers, imgptr - imgbuf);
-
-		// Write pixels
-		for (y = 0; y < height; y++)
-		{
-			UINT8 paletteIndex = raw[((y * width) + x)];
-			boolean opaque = transparency ? (paletteIndex != TRANSPARENTPIXEL) : true;
-
-			// End span if we have a transparent pixel
-			if (!opaque)
-			{
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
-				startofspan = NULL;
-				continue;
-			}
-
-			// Start new column if we need to
-			if (!startofspan || spanSize == 255)
-			{
-				int writeY = y;
-
-				// If we reached the span size limit, finish the previous span
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
-
-				if (y > 254)
-				{
-					// Make sure we're aligned to 254
-					if (lastStartY < 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						lastStartY = 254;
-					}
-
-					// Write stopgap empty spans if needed
-					writeY = y - lastStartY;
-
-					while (writeY > 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						writeY -= 254;
-					}
-				}
-
-				startofspan = imgptr;
-				WRITEUINT8(imgptr, writeY);///@TODO calculate starting y pos
-				imgptr += 2;
-				spanSize = 0;
-
-				lastStartY = y;
-			}
-
-			// Write the pixel
-			WRITEUINT8(imgptr, paletteIndex);
-			spanSize++;
-			startofspan[1] = spanSize;
-		}
-
-		if (startofspan)
-			WRITEUINT8(imgptr, 0);
-
-		WRITEUINT8(imgptr, 0xFF);
-	}
-
-	size = imgptr-imgbuf;
-	img = Z_Malloc(size, PU_STATIC, NULL);
-	memcpy(img, imgbuf, size);
-
-	Z_Free(raw);
-
-	if (destsize != NULL)
-		*destsize = size;
-	return (patch_t *)img;
+	return R_FlatToPatch(raw, width, height, leftoffset, topoffset, destsize, transparency);
 }
 
 boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
@@ -3001,53 +3093,3 @@ boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
 }
 #endif
 #endif
-
-void R_TextureToFlat(size_t tex, UINT8 *flat)
-{
-	texture_t *texture = textures[tex];
-
-	fixed_t col, ofs;
-	column_t *column;
-	UINT8 *desttop, *dest, *deststop;
-	UINT8 *source;
-
-	desttop = flat;
-	deststop = desttop + (texture->width * texture->height);
-
-	for (col = 0; col < texture->width; col++, desttop++)
-	{
-		column = (column_t *)R_GetColumn(tex, col);
-		if (!texture->holes)
-		{
-			dest = desttop;
-			source = (UINT8 *)(column);
-			for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
-			{
-				if (source[ofs] != TRANSPARENTPIXEL)
-					*dest = source[ofs];
-				dest += texture->width;
-			}
-		}
-		else
-		{
-			INT32 topdelta, prevdelta = -1;
-			while (column->topdelta != 0xff)
-			{
-				topdelta = column->topdelta;
-				if (topdelta <= prevdelta)
-					topdelta += prevdelta;
-				prevdelta = topdelta;
-
-				dest = desttop + (topdelta * texture->width);
-				source = (UINT8 *)(column) + 3;
-				for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
-				{
-					if (source[ofs] != TRANSPARENTPIXEL)
-						*dest = source[ofs];
-					dest += texture->width;
-				}
-				column = (column_t *)((UINT8 *)column + column->length + 4);
-			}
-		}
-	}
-}
diff --git a/src/r_data.h b/src/r_data.h
index c2fd284ff016810d5062ebe6523f5eedcb9df025..e71d457663e1eabe06a10cb7c4c7911379c87b0c 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -159,15 +159,14 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap);
 #define R_PutRgbaRGBA(r, g, b, a) (R_PutRgbaRGB(r, g, b) + R_PutRgbaA(a))
 
 boolean R_CheckIfPatch(lumpnum_t lump);
-UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
-
-void R_PatchToFlat(patch_t *patch, UINT8 *flat);
 void R_TextureToFlat(size_t tex, UINT8 *flat);
+void R_PatchToFlat(patch_t *patch, UINT8 *flat);
+patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize, boolean transparency);
 
 #ifndef NO_PNG_LUMPS
 boolean R_IsLumpPNG(const UINT8 *d, size_t s);
 
-UINT8 *R_PNGToFlat(levelflat_t *levelflat, UINT8 *png, size_t size);
+UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size);
 patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize, boolean transparency);
 boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size);
 #endif
diff --git a/src/r_defs.h b/src/r_defs.h
index def7b46f3b8aa5ecdd81c9d1d359848b87268822..4a5c38410491b3378badea7925bfa98932d81f4e 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -139,9 +139,9 @@ typedef enum
 	FF_PLATFORM          = 0x2000000,  ///< You can jump up through this to the top.
 	FF_REVERSEPLATFORM   = 0x4000000,  ///< A fall-through floor in normal gravity, a platform in reverse gravity.
 	FF_INTANGABLEFLATS   = 0x6000000,  ///< Both flats are intangable, but the sides are still solid.
-	FF_SHATTER           = 0x8000000,  ///< Used with ::FF_BUSTUP. Thinks everyone's Knuckles.
-	FF_SPINBUST          = 0x10000000, ///< Used with ::FF_BUSTUP. Jump or fall onto it while curled in a ball.
-	FF_ONLYKNUX          = 0x20000000, ///< Used with ::FF_BUSTUP. Only Knuckles can break this rock.
+	FF_SHATTER           = 0x8000000,  ///< Used with ::FF_BUSTUP. Bustable on mere touch.
+	FF_SPINBUST          = 0x10000000, ///< Used with ::FF_BUSTUP. Also bustable if you're in your spinning frames.
+	FF_STRONGBUST        = 0x20000000, ///< Used with ::FF_BUSTUP. Only bustable by "strong" characters (Knuckles) and abilities (bouncing, twinspin, melee).
 	FF_RIPPLE            = 0x40000000, ///< Ripple the flats
 	FF_COLORMAPONLY      = 0x80000000, ///< Only copy the colormap, not the lightlevel
 	FF_GOOWATER          = FF_SHATTERBOTTOM, ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
@@ -263,10 +263,15 @@ typedef struct pslope_s
 
 typedef enum
 {
-	SF_FLIPSPECIAL_FLOOR    =  1,
-	SF_FLIPSPECIAL_CEILING  =  2,
-	SF_FLIPSPECIAL_BOTH     =  3,
-	SF_TRIGGERSPECIAL_TOUCH =  4,
+	// flipspecial - planes with effect
+	SF_FLIPSPECIAL_FLOOR       =  1,
+	SF_FLIPSPECIAL_CEILING     =  1<<1,
+	SF_FLIPSPECIAL_BOTH        =  (SF_FLIPSPECIAL_FLOOR|SF_FLIPSPECIAL_CEILING),
+	// triggerspecial - conditions under which plane touch causes effect
+	SF_TRIGGERSPECIAL_TOUCH    =  1<<2,
+	SF_TRIGGERSPECIAL_HEADBUMP =  1<<3,
+	// invertprecip - inverts presence of precipitation
+	SF_INVERTPRECIP            =  1<<4,
 } sectorflags_t;
 
 //
diff --git a/src/r_draw.c b/src/r_draw.c
index 1754403c44141a2ca599bddcc28d0ca6ed90ca80..bb70a319f0a5137d3f95740e10b77efda06eeed7 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -130,10 +130,11 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define ALLWHITE_TT_CACHE_INDEX (MAXSKINS + 3)
 #define RAINBOW_TT_CACHE_INDEX (MAXSKINS + 4)
 #define BLINK_TT_CACHE_INDEX (MAXSKINS + 5)
+#define DASHMODE_TT_CACHE_INDEX (MAXSKINS + 6)
 #define DEFAULT_STARTTRANSCOLOR 96
 #define NUM_PALETTE_ENTRIES 256
 
-static UINT8** translationtablecache[MAXSKINS + 6] = {NULL};
+static UINT8** translationtablecache[MAXSKINS + 7] = {NULL};
 
 const UINT8 Color_Index[MAXTRANSLATIONS-1][16] = {
 	// {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // SKINCOLOR_NONE
@@ -569,6 +570,40 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 				dest_colormap[Color_Index[SKINCOLOR_BLUE-1][12-i]] = Color_Index[SKINCOLOR_BLUE-1][i];
 			dest_colormap[159] = dest_colormap[253] = dest_colormap[254] = 0;
 		}
+		else if (skinnum == TC_DASHMODE) // This is a long one, because MotorRoach basically hand-picked the indices
+		{
+			// greens -> ketchups
+			dest_colormap[96] = dest_colormap[97] = 48;
+			dest_colormap[98] = 49;
+			dest_colormap[99] = 51;
+			dest_colormap[100] = 52;
+			dest_colormap[101] = dest_colormap[102] = 54;
+			dest_colormap[103] = 34;
+			dest_colormap[104] = 37;
+			dest_colormap[105] = 39;
+			dest_colormap[106] = 41;
+			for (i = 0; i < 5; i++)
+				dest_colormap[107 + i] = 43 + i;
+
+			// reds -> steel blues
+			dest_colormap[32] = 146;
+			dest_colormap[33] = 147;
+			dest_colormap[34] = dest_colormap[35] = 170;
+			dest_colormap[36] = 171;
+			dest_colormap[37] = dest_colormap[38] = 172;
+			dest_colormap[39] = dest_colormap[40] = dest_colormap[41] = 173;
+			dest_colormap[42] = dest_colormap[43] = dest_colormap[44] = 174;
+			dest_colormap[45] = dest_colormap[46] = dest_colormap[47] = 175;
+			dest_colormap[71] = 139;
+
+			// steel blues -> oranges
+			dest_colormap[170] = 52;
+			dest_colormap[171] = 54;
+			dest_colormap[172] = 56;
+			dest_colormap[173] = 42;
+			dest_colormap[174] = 45;
+			dest_colormap[175] = 47;
+		}
 		return;
 	}
 	else if (color == SKINCOLOR_NONE)
@@ -628,6 +663,7 @@ UINT8* R_GetTranslationColormap(INT32 skinnum, skincolors_t color, UINT8 flags)
 		case TC_ALLWHITE:   skintableindex = ALLWHITE_TT_CACHE_INDEX; break;
 		case TC_RAINBOW:    skintableindex = RAINBOW_TT_CACHE_INDEX; break;
 		case TC_BLINK:      skintableindex = BLINK_TT_CACHE_INDEX; break;
+		case TC_DASHMODE:   skintableindex = DASHMODE_TT_CACHE_INDEX; break;
 		     default:       skintableindex = skinnum; break;
 	}
 
diff --git a/src/r_draw.h b/src/r_draw.h
index 3c142972257c446a29e32fd1c36a810e47c63bc0..a0ab748480930d6f06191b6de78adfefdb539ce5 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -109,6 +109,7 @@ extern lumpnum_t viewborderlump[8];
 #define TC_ALLWHITE   -4 // For Cy-Brak-demon
 #define TC_RAINBOW    -5 // For single colour
 #define TC_BLINK      -6 // For item blinking, according to kart
+#define TC_DASHMODE   -7 // For Metal Sonic's dashmode
 
 // Initialize color translation tables, for player rendering etc.
 void R_InitTranslationTables(void);
diff --git a/src/r_main.c b/src/r_main.c
index 0824de0e4fef8ddcf6f7cac30eaaa74dc9b9bbe4..1a5e402b50751a10db0fb9188e306ea8db738dfe 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1218,6 +1218,7 @@ void R_RegisterEngineStuff(void)
 #endif
 	CV_RegisterVar(&cv_grmodels);
 	CV_RegisterVar(&cv_grspritebillboarding);
+	CV_RegisterVar(&cv_grskydome);
 #endif
 
 #ifdef HWRENDER
diff --git a/src/r_plane.c b/src/r_plane.c
index db5fb0f249d690f70dacabaaca4e87767f7576d4..9f417ee6bff85de8aa409b6d6db677d33b2bff2f 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -44,6 +44,9 @@
 // Quincunx antialiasing of flats!
 //#define QUINCUNX
 
+// good night sweet prince
+#define SHITPLANESPARENCY
+
 //SoM: 3/23/2000: Use Boom visplane hashing.
 
 visplane_t *visplanes[MAXVISPLANES];
@@ -650,6 +653,11 @@ static void R_DrawSkyPlane(visplane_t *pl)
 	}
 }
 
+//
+// R_CheckPowersOfTwo
+//
+// Self-explanatory?
+//
 boolean R_CheckPowersOfTwo(void)
 {
 	boolean wpow2 = (!(ds_flatwidth & (ds_flatwidth - 1)));
@@ -667,6 +675,11 @@ boolean R_CheckPowersOfTwo(void)
 	return ds_powersoftwo;
 }
 
+//
+// R_CheckFlatLength
+//
+// Determine the flat's dimensions from the lump length.
+//
 void R_CheckFlatLength(size_t size)
 {
 	switch (size)
@@ -723,7 +736,24 @@ void R_CheckFlatLength(size_t size)
 	}
 }
 
-static UINT8 *R_GetPatchFlat(levelflat_t *levelflat, boolean leveltexture, boolean ispng)
+//
+// R_GenerateFlat
+//
+// Generate a flat from specified width and height.
+//
+static UINT8 *R_GenerateFlat(UINT16 width, UINT16 height)
+{
+	UINT8 *flat = Z_Malloc(width * height, PU_LEVEL, NULL);
+	memset(flat, TRANSPARENTPIXEL, width * height);
+	return flat;
+}
+
+//
+// R_GetTextureFlat
+//
+// Convert a texture or patch to a flat.
+//
+static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boolean ispng)
 {
 	UINT8 *flat;
 	textureflat_t *texflat = &texflats[levelflat->texturenum];
@@ -747,14 +777,14 @@ static UINT8 *R_GetPatchFlat(levelflat_t *levelflat, boolean leveltexture, boole
 	// If the texture changed, or the patch doesn't exist, convert either of them to a flat.
 	if (levelflat->flatpatch == NULL || texturechanged)
 	{
+		// Level texture
 		if (leveltexture)
 		{
 			texture_t *texture = textures[levelflat->texturenum];
 			texflat->width = ds_flatwidth = texture->width;
 			texflat->height = ds_flatheight = texture->height;
 
-			texflat->flat = Z_Malloc(ds_flatwidth * ds_flatheight, PU_LEVEL, NULL);
-			memset(texflat->flat, TRANSPARENTPIXEL, ds_flatwidth * ds_flatheight);
+			texflat->flat = R_GenerateFlat(ds_flatwidth, ds_flatheight);
 			R_TextureToFlat(levelflat->texturenum, texflat->flat);
 			flat = texflat->flat;
 
@@ -762,13 +792,14 @@ static UINT8 *R_GetPatchFlat(levelflat_t *levelflat, boolean leveltexture, boole
 			levelflat->width = ds_flatwidth;
 			levelflat->height = ds_flatheight;
 		}
+		// Patch (never happens yet)
 		else
 		{
 			patch = (patch_t *)ds_source;
 #ifndef NO_PNG_LUMPS
 			if (ispng)
 			{
-				levelflat->flatpatch = R_PNGToFlat(levelflat, ds_source, W_LumpLength(levelflat->lumpnum));
+				levelflat->flatpatch = R_PNGToFlat(&levelflat->width, &levelflat->height, ds_source, W_LumpLength(levelflat->lumpnum));
 				levelflat->topoffset = levelflat->leftoffset = 0;
 				ds_flatwidth = levelflat->width;
 				ds_flatheight = levelflat->height;
@@ -782,8 +813,7 @@ static UINT8 *R_GetPatchFlat(levelflat_t *levelflat, boolean leveltexture, boole
 				levelflat->topoffset = patch->topoffset * FRACUNIT;
 				levelflat->leftoffset = patch->leftoffset * FRACUNIT;
 
-				levelflat->flatpatch = Z_Malloc(ds_flatwidth * ds_flatheight, PU_LEVEL, NULL);
-				memset(levelflat->flatpatch, TRANSPARENTPIXEL, ds_flatwidth * ds_flatheight);
+				levelflat->flatpatch = R_GenerateFlat(ds_flatwidth, ds_flatheight);
 				R_PatchToFlat(patch, levelflat->flatpatch);
 			}
 			flat = levelflat->flatpatch;
@@ -794,11 +824,11 @@ static UINT8 *R_GetPatchFlat(levelflat_t *levelflat, boolean leveltexture, boole
 		flat = levelflat->flatpatch;
 		ds_flatwidth = levelflat->width;
 		ds_flatheight = levelflat->height;
-
-		xoffs += levelflat->leftoffset;
-		yoffs += levelflat->topoffset;
 	}
 
+	xoffs += levelflat->leftoffset;
+	yoffs += levelflat->topoffset;
+
 	levelflat->lasttexturenum = levelflat->texturenum;
 	return flat;
 }
@@ -841,7 +871,11 @@ void R_DrawSinglePlane(visplane_t *pl)
 		else // Opaque, but allow transparent flat pixels
 			spanfunc = splatfunc;
 
+#ifdef SHITPLANESPARENCY
+		if ((spanfunc == splatfunc) != (pl->extra_colormap && (pl->extra_colormap->fog & 4)))
+#else
 		if (!pl->extra_colormap || !(pl->extra_colormap->fog & 2))
+#endif
 			light = (pl->lightlevel >> LIGHTSEGSHIFT);
 		else
 			light = LIGHTLEVELS-1;
@@ -895,7 +929,11 @@ void R_DrawSinglePlane(visplane_t *pl)
 			else // Opaque, but allow transparent flat pixels
 				spanfunc = splatfunc;
 
+#ifdef SHITPLANESPARENCY
+			if ((spanfunc == splatfunc) != (pl->extra_colormap && (pl->extra_colormap->fog & 4)))
+#else
 			if (!pl->extra_colormap || !(pl->extra_colormap->fog & 2))
+#endif
 				light = (pl->lightlevel >> LIGHTSEGSHIFT);
 			else
 				light = LIGHTLEVELS-1;
@@ -963,15 +1001,15 @@ void R_DrawSinglePlane(visplane_t *pl)
 
 	// Check if the flat is actually a wall texture.
 	if (levelflat->texturenum != 0 && levelflat->texturenum != -1)
-		flat = R_GetPatchFlat(levelflat, true, false);
+		flat = R_GetTextureFlat(levelflat, true, false);
 #ifndef NO_PNG_LUMPS
 	// Maybe it's a PNG?!
 	else if (R_IsLumpPNG(ds_source, size))
-		flat = R_GetPatchFlat(levelflat, false, true);
+		flat = R_GetTextureFlat(levelflat, false, true);
 #endif
 	// Maybe it's just a patch, then?
 	else if (R_CheckIfPatch(levelflat->lumpnum))
-		flat = R_GetPatchFlat(levelflat, false, false);
+		flat = R_GetTextureFlat(levelflat, false, false);
 	// It's a raw flat.
 	else
 	{
@@ -1075,7 +1113,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 		temp = P_GetZAt(pl->slope, pl->viewx, pl->viewy);
 		zeroheight = FIXED_TO_FLOAT(temp);
 
-#define ANG2RAD(angle) ((float)((angle)*M_PI)/ANGLE_180)
+#define ANG2RAD(angle) ((float)((angle)*M_PIl)/ANGLE_180)
 
 		// p is the texture origin in view space
 		// Don't add in the offsets at this stage, because doing so can result in
diff --git a/src/r_things.c b/src/r_things.c
index 238a0a2479d86d703b4102277ca4855ac5f92917..1c98ee34781b8087cf04e45a05a3a0ab658a3d23 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -753,6 +753,13 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		dc_transmap = vis->transmap;
 		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
 			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP)
+			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
+			&& (vis->mobj->player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)
+			&& ((leveltime/2) & 1))
+		{
+			dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+		}
 		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
@@ -774,6 +781,13 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		// New colormap stuff for skins Tails 06-07-2002
 		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
 			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP)
+			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
+			&& (vis->mobj->player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)
+			&& ((leveltime/2) & 1))
+		{
+			dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+		}
 		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
@@ -1230,7 +1244,7 @@ static void R_ProjectSprite(mobj_t *thing)
 		else
 			range = 1;
 
-		scalestep = (yscale2 - yscale)/range;
+		scalestep = (yscale2 - yscale)/range ?: 1;
 
 		// The following two are alternate sorting methods which might be more applicable in some circumstances. TODO - maybe enable via MF2?
 		// sortscale = max(yscale, yscale2);
@@ -1637,7 +1651,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	mobj_t *thing;
 	precipmobj_t *precipthing; // Tails 08-25-2002
 	INT32 lightnum;
-	fixed_t approx_dist, limit_dist;
+	fixed_t approx_dist, limit_dist, hoop_limit_dist;
 
 	if (rendermode != render_soft)
 		return;
@@ -1668,7 +1682,9 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 
 	// Handle all things in sector.
 	// If a limit exists, handle things a tiny bit different.
-	if ((limit_dist = (fixed_t)((maptol & TOL_NIGHTS) ? cv_drawdist_nights.value : cv_drawdist.value) << FRACBITS))
+	limit_dist = (fixed_t)(cv_drawdist.value) << FRACBITS;
+	hoop_limit_dist = (fixed_t)(cv_drawdist_nights.value) << FRACBITS;
+	if (limit_dist || hoop_limit_dist)
 	{
 		for (thing = sec->thinglist; thing; thing = thing->snext)
 		{
@@ -1677,8 +1693,16 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 
 			approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y);
 
-			if (approx_dist > limit_dist)
-				continue;
+			if (thing->sprite == SPR_HOOP)
+			{
+				if (hoop_limit_dist && approx_dist > hoop_limit_dist)
+					continue;
+			}
+			else
+			{
+				if (limit_dist && approx_dist > limit_dist)
+					continue;
+			}
 
 			R_ProjectSprite(thing);
 		}
@@ -2786,9 +2810,9 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 	}
 
 	if (P_IsLocalPlayer(player))
-		CONS_Alert(CONS_WARNING, M_GetText("Requested skin not found\n"));
+		CONS_Alert(CONS_WARNING, M_GetText("Requested skin %d not found\n"), skinnum);
 	else if(server || IsPlayerAdmin(consoleplayer))
-		CONS_Alert(CONS_WARNING, "Player %d (%s) skin not found\n", playernum, player_names[playernum]);
+		CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum);
 	SetPlayerSkinByNum(playernum, 0); // not found put the sonic skin
 }
 
diff --git a/src/s_sound.c b/src/s_sound.c
index 299e4b88976935c271852f024d412bc70a6e635a..6134e338c2649f5114531364275c70ff518498df 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -117,6 +117,10 @@ 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};
 
+// 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};
+
 #ifdef HAVE_OPENMPT
 static CV_PossibleValue_t interpolationfilter_cons_t[] = {{0, "Default"}, {1, "None"}, {2, "Linear"}, {4, "Cubic"}, {8, "Windowed sinc"}, {0, NULL}};
 consvar_t cv_modfilter = {"modfilter", "0", CV_SAVE|CV_CALL, interpolationfilter_cons_t, ModFilter_OnChange, 0, NULL, NULL, 0, 0, NULL};
@@ -278,6 +282,8 @@ void S_RegisterSoundStuff(void)
 	CV_RegisterVar(&cv_samplerate);
 	CV_RegisterVar(&cv_resetmusic);
 	CV_RegisterVar(&cv_resetmusicbyheader);
+	CV_RegisterVar(&cv_playsoundsifunfocused);
+	CV_RegisterVar(&cv_playmusicifunfocused);
 	CV_RegisterVar(&cv_gamesounds);
 	CV_RegisterVar(&cv_gamedigimusic);
 	CV_RegisterVar(&cv_gamemidimusic);
@@ -373,6 +379,18 @@ lumpnum_t S_GetSfxLumpNum(sfxinfo_t *sfx)
 	return W_GetNumForName("dsthok");
 }
 
+//
+// Sound Status
+//
+
+boolean S_SoundDisabled(void)
+{
+	return (
+			sound_disabled ||
+			( window_notinfocus && ! cv_playsoundsifunfocused.value )
+	);
+}
+
 // Stop all sounds, load level info, THEN start sounds.
 void S_StopSounds(void)
 {
@@ -540,7 +558,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 	mobj_t *listenmobj = players[displayplayer].mo;
 	mobj_t *listenmobj2 = NULL;
 
-	if (sound_disabled || !sound_started)
+	if (S_SoundDisabled() || !sound_started)
 		return;
 
 	// Don't want a sound? Okay then...
@@ -730,7 +748,7 @@ dontplay:
 
 void S_StartSound(const void *origin, sfxenum_t sfx_id)
 {
-	if (sound_disabled)
+	if (S_SoundDisabled())
 		return;
 
 	if (mariomode) // Sounds change in Mario mode!
@@ -1434,6 +1452,13 @@ boolean S_MusicPaused(void)
 	return I_SongPaused();
 }
 
+boolean S_MusicNotInFocus(void)
+{
+	return (
+			( window_notinfocus && ! cv_playmusicifunfocused.value )
+	);
+}
+
 musictype_t S_MusicType(void)
 {
 	return I_SongType();
@@ -1867,6 +1892,10 @@ static boolean S_PlayMusic(boolean looping, UINT32 fadeinms)
 	}
 
 	S_InitMusicVolume(); // switch between digi and sequence volume
+
+	if (S_MusicNotInFocus())
+		S_PauseAudio();
+
 	return true;
 }
 
@@ -2009,6 +2038,9 @@ void S_PauseAudio(void)
 
 void S_ResumeAudio(void)
 {
+	if (S_MusicNotInFocus())
+		return;
+
 	if (I_SongPlaying() && I_SongPaused())
 		I_ResumeSong();
 
@@ -2202,7 +2234,7 @@ static void Command_RestartAudio_f(void)
 
 void GameSounds_OnChange(void)
 {
-	if (M_CheckParm("-nosound"))
+	if (M_CheckParm("-nosound") || M_CheckParm("-noaudio"))
 		return;
 
 	if (sound_disabled)
@@ -2220,7 +2252,7 @@ void GameSounds_OnChange(void)
 
 void GameDigiMusic_OnChange(void)
 {
-	if (M_CheckParm("-nomusic"))
+	if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio"))
 		return;
 	else if (M_CheckParm("-nodigmusic"))
 		return;
@@ -2262,7 +2294,7 @@ void GameDigiMusic_OnChange(void)
 
 void GameMIDIMusic_OnChange(void)
 {
-	if (M_CheckParm("-nomusic"))
+	if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio"))
 		return;
 	else if (M_CheckParm("-nomidimusic"))
 		return;
@@ -2279,7 +2311,7 @@ void GameMIDIMusic_OnChange(void)
 	else
 	{
 		midi_disabled = true;
-		if (S_MusicType() == MU_MID)
+		if (S_MusicType() == MU_MID || S_MusicType() == MU_MID_EX)
 		{
 			if (digital_disabled)
 				S_StopMusic();
diff --git a/src/s_sound.h b/src/s_sound.h
index 48128527ce62dd816a66608a886595a5df0b65de..20b2489a5de6fdb75f0d8467b0b2058ee27d9324 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -45,6 +45,9 @@ extern consvar_t cv_gamedigimusic;
 extern consvar_t cv_gamemidimusic;
 extern consvar_t cv_gamesounds;
 
+extern consvar_t cv_playmusicifunfocused;
+extern consvar_t cv_playsoundsifunfocused;
+
 #ifdef HAVE_OPENMPT
 extern consvar_t cv_modfilter;
 #endif
@@ -144,6 +147,12 @@ void S_StartEx(boolean reset);
 //
 lumpnum_t S_GetSfxLumpNum(sfxinfo_t *sfx);
 
+//
+// Sound Status
+//
+
+boolean S_SoundDisabled(void);
+
 //
 // Start sound for thing at <origin> using <sound_id> from sounds.h
 //
@@ -164,6 +173,7 @@ boolean S_MIDIMusicDisabled(void);
 boolean S_MusicDisabled(void);
 boolean S_MusicPlaying(void);
 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);
diff --git a/src/screen.c b/src/screen.c
index 547036a60bf7b7624d0f4f58622df2f2d44feadf..9939aff93388dcdb816404be9b4795963fc41960 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -421,9 +421,9 @@ void SCR_DisplayTicRate(void)
 	else if (totaltics == TICRATE) ticcntcolor = V_GREENMAP;
 
 	V_DrawString(vid.width-(72*vid.dupx), h,
-		V_YELLOWMAP|V_NOSCALESTART, "FPS:");
+		V_YELLOWMAP|V_NOSCALESTART|V_HUDTRANS, "FPS:");
 	V_DrawString(vid.width-(40*vid.dupx), h,
-		ticcntcolor|V_NOSCALESTART, va("%02d/%02u", totaltics, TICRATE));
+		ticcntcolor|V_NOSCALESTART|V_HUDTRANS, va("%02d/%02u", totaltics, TICRATE));
 
 	lasttic = ontic;
 }
diff --git a/src/screen.h b/src/screen.h
index 3554b5520a541783e3a8b66f780ed1906dd3b3d7..79f21e8e470bd4832d0c20c5d0b04c586b0635b0 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -39,13 +39,8 @@
 // we try to re-allocate a minimum of buffers for stability of the memory,
 // so all the small-enough tables based on screen size, are allocated once
 // and for all at the maximum size.
-#if defined (_WIN32_WCE)
-#define MAXVIDWIDTH 320
-#define MAXVIDHEIGHT 200
-#else
 #define MAXVIDWIDTH 1920 // don't set this too high because actually
 #define MAXVIDHEIGHT 1200 // lots of tables are allocated with the MAX size.
-#endif
 #define BASEVIDWIDTH 320 // NEVER CHANGE THIS! This is the original
 #define BASEVIDHEIGHT 200 // resolution of the graphics.
 
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 4e083b4c2949c8524fde8a0b396137365515c97d..5f040023a8032e1086ebec70f8a28891a0b741e1 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -79,6 +79,7 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(Init);
 	GETFUNC(Draw2DLine);
 	GETFUNC(DrawPolygon);
+	GETFUNC(RenderSkyDome);
 	GETFUNC(SetBlend);
 	GETFUNC(ClearBuffer);
 	GETFUNC(SetTexture);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index b958b9689861bcef535d2b41c3691d215148d81f..fb0f4b2ba3eaea01e6881a77f2717672860cb21f 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -357,6 +357,14 @@ static INT32 Impl_SDL_Scancode_To_Keycode(SDL_Scancode code)
 	return 0;
 }
 
+static void SDLdoGrabMouse(void)
+{
+	SDL_ShowCursor(SDL_DISABLE);
+	SDL_SetWindowGrab(window, SDL_TRUE);
+	if (SDL_SetRelativeMouseMode(SDL_TRUE) == 0) // already warps mouse if successful
+		wrapmouseok = SDL_TRUE; // TODO: is wrapmouseok or HalfWarpMouse needed anymore?
+}
+
 static void SDLdoUngrabMouse(void)
 {
 	SDL_ShowCursor(SDL_ENABLE);
@@ -579,12 +587,18 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt)
 			if (cv_usemouse.value) I_StartupMouse();
 		}
 		//else firsttimeonmouse = SDL_FALSE;
+
+		if (USE_MOUSEINPUT)
+			SDLdoGrabMouse();
 	}
 	else if (!mousefocus && !kbfocus)
 	{
 		// Tell game we lost focus, pause music
 		window_notinfocus = true;
-		S_PauseAudio();
+		if (! cv_playmusicifunfocused.value)
+			S_PauseAudio();
+		if (! cv_playsoundsifunfocused.value)
+			S_StopSounds();
 
 		if (!disable_mouse)
 		{
@@ -655,9 +669,7 @@ static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 		// -- Monster Iestyn
 		if (SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window)
 		{
-			SDL_SetWindowGrab(window, SDL_TRUE);
-			if (SDL_SetRelativeMouseMode(SDL_TRUE) == 0) // already warps mouse if successful
-				wrapmouseok = SDL_TRUE; // TODO: is wrapmouseok or HalfWarpMouse needed anymore?
+			SDLdoGrabMouse();
 		}
 	}
 }
@@ -1056,7 +1068,7 @@ void I_StartupMouse(void)
 	else
 		firsttimeonmouse = SDL_FALSE;
 	if (cv_usemouse.value)
-		return;
+		SDLdoGrabMouse();
 	else
 		SDLdoUngrabMouse();
 }
@@ -1164,8 +1176,11 @@ void I_FinishUpdate(void)
 	if (cv_closedcaptioning.value)
 		SCR_ClosedCaptions();
 
-	if (cv_ticrate.value)
-		SCR_DisplayTicRate();
+	if (st_overlay)
+	{
+		if (cv_ticrate.value)
+			SCR_DisplayTicRate();
+	}
 
 	if (rendermode == render_soft && screens[0])
 	{
@@ -1629,6 +1644,7 @@ void I_StartupGraphics(void)
 		HWD.pfnFinishUpdate     = NULL;
 		HWD.pfnDraw2DLine       = hwSym("Draw2DLine",NULL);
 		HWD.pfnDrawPolygon      = hwSym("DrawPolygon",NULL);
+		HWD.pfnRenderSkyDome    = hwSym("RenderSkyDome",NULL);
 		HWD.pfnSetBlend         = hwSym("SetBlend",NULL);
 		HWD.pfnClearBuffer      = hwSym("ClearBuffer",NULL);
 		HWD.pfnSetTexture       = hwSym("SetTexture",NULL);
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index d94010d9add02dcc47693b9149f5b7fc177b148d..c4b2f98f4d73dbbe85df4f0ef7933568cc7fcbd2 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -878,7 +878,11 @@ boolean I_SetSongSpeed(float speed)
 #ifdef HAVE_OPENMPT
 	if (openmpt_mhandle)
 	{
-		char modspd[16];
+		char modspd[13];
+
+		if (speed > 4.0f)
+			speed = 4.0f; // Limit this to 4x to prevent crashing, stupid fix but... ~SteelT 27/9/19
+
 		sprintf(modspd, "%g", speed);
 		openmpt_module_ctl_set(openmpt_mhandle, "play.tempo_factor", modspd);
 		return true;
diff --git a/src/sounds.c b/src/sounds.c
index 6940cd9ee69cfc86479bdaea6a1019be2c761d74..8dc97b1e635f302c7b45eb32c00926b3cef68600 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -218,6 +218,8 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"chuchu", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Train horn"},
   {"bsnipe", false, 200,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Home-run smash"},
   {"sprong", false, 112,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power spring"},
+  {"lvfal1",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rumble"},
+  {"pscree", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "SCREE!"},
 
   // Menu, interface
   {"chchng", false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Score"},
@@ -504,7 +506,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3k6d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s3k6e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical damage"},
   {"s3k6f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
-  {"s3k70",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
+  {"s3k70",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
   {"s3k71",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Basic Shield"},
   {"s3k72",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Movement"},
   {"s3k73",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Warp"},
@@ -555,7 +557,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"s3ka0",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Launch"},
   {"s3ka1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s3ka2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Launch"},
-  {"s3ka3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lift"},
+  {"s3ka3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising charge"},
   {"s3ka4",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
   {"s3ka5",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"s3ka6",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction fizzle"},
@@ -713,7 +715,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"cdfm59", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"cdfm60", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"cdfm61", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"cdfm62", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm62", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Speed boost"},
   {"cdfm63", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"cdfm64", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"cdfm65", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
diff --git a/src/sounds.h b/src/sounds.h
index e5568b59a49148382c22bcc9b60ae7c57404ea22..9f6e0bab640128b2e8c4a36182479bc2b28f7cbe 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -267,6 +267,8 @@ typedef enum
 	sfx_chuchu,
 	sfx_bsnipe,
 	sfx_sprong,
+	sfx_lvfal1,
+	sfx_pscree,
 
 	// Menu, interface
 	sfx_chchng,
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 20a132b3a19aecb158ba428e7a66460480e1de71..392cb1c03fe4d600a73f226a9d5eafd431ba4afa 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -351,7 +351,7 @@ void ST_LoadFaceGraphics(INT32 skinnum)
 	if (skins[skinnum].sprites[SPR2_XTRA].numframes)
 	{
 		spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
-		spriteframe_t *sprframe = &sprdef->spriteframes[0];
+		spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_LIFEPIC];
 		faceprefix[skinnum] = W_CachePatchNum(sprframe->lumppat[0], PU_HUDGFX);
 		if (skins[skinnum].sprites[(SPR2_XTRA|FF_SPR2SUPER)].numframes)
 		{
@@ -1474,12 +1474,9 @@ static void ST_drawNightsRecords(void)
 
 			if (P_HasGrades(gamemap, stplyr->lastmare + 1))
 			{
-				if (aflag)
-					V_DrawTranslucentPatch(BASEVIDWIDTH/2 + 60, 160, aflag,
-										   ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
-				else
-					V_DrawScaledPatch(BASEVIDWIDTH/2 + 60, 160, 0,
-									  ngradeletters[P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare)]);
+				UINT8 grade = P_GetGrade(stplyr->lastmarescore, gamemap, stplyr->lastmare);
+				if (modeattacking || grade >= GRADE_A)
+					V_DrawTranslucentPatch(BASEVIDWIDTH/2 + 60, 160, aflag, ngradeletters[grade]);
 			}
 			break;
 		}
@@ -1857,7 +1854,8 @@ static void ST_drawNiGHTSHUD(void)
 			numbersize = 48/2;
 
 		if ((oldspecialstage && leveltime & 2)
-			&& (stplyr->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)))
+			&& (stplyr->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
+			&& !(stplyr->powers[pw_shield] & SH_PROTECTWATER))
 			col = SKINCOLOR_ORANGE;
 
 		ST_DrawNightsOverlayNum((160 + numbersize)<<FRACBITS, 14<<FRACBITS, FRACUNIT, V_PERPLAYER|V_SNAPTOTOP, realnightstime, nightsnum, col);
diff --git a/src/v_video.c b/src/v_video.c
index f865398f1849c44ea845a1b6411d790eae4119f0..8e977f25d2438a7b1b886ceec7374d719d98d8e4 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -111,6 +111,7 @@ static CV_PossibleValue_t CV_MD2[] = {{0, "Off"}, {1, "On"}, {2, "Old"}, {0, NUL
 // console variables in development
 consvar_t cv_grmodels = {"gr_models", "Off", CV_SAVE, CV_MD2, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grspritebillboarding = {"gr_spritebillboarding", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grskydome = {"gr_skydome", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
 // local copy of the palette for V_GetColor()
@@ -1072,7 +1073,7 @@ void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skin
 	if (skinnum >= 0 && skinnum < numskins && skins[skinnum].sprites[SPR2_XTRA].numframes >= 4)
 	{
 		spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
-		spriteframe_t *sprframe = &sprdef->spriteframes[3];
+		spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CONTINUE];
 		patch_t *patch = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
 		const UINT8 *colormap = R_GetTranslationColormap(skinnum, skincolor, GTC_CACHE);
 
@@ -2190,7 +2191,7 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 			w = SHORT(hu_font[c]->width) * dupx;
 
 		if (cx > scrwidth)
-			break;
+			continue;
 		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
@@ -2304,7 +2305,7 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 			w = SHORT(hu_font[c]->width) * dupx / 2;
 
 		if (cx > scrwidth)
-			break;
+			continue;
 		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
@@ -2409,7 +2410,7 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 			w = (SHORT(tny_font[c]->width) * dupx);
 
 		if (cx > scrwidth)
-			break;
+			continue;
 		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
@@ -2507,7 +2508,7 @@ void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string)
 			w = SHORT(hu_font[c]->width) * dupx;
 
 		if ((cx>>FRACBITS) > scrwidth)
-			break;
+			continue;
 		if ((cx>>FRACBITS)+left + w < 0) //left boundary check
 		{
 			cx += w<<FRACBITS;
@@ -2619,13 +2620,210 @@ void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string)
 
 		w = SHORT(cred_font[c]->width) * dupx;
 		if ((cx>>FRACBITS) > scrwidth)
-			break;
+			continue;
 
 		V_DrawSciencePatch(cx, cy, option, cred_font[c], FRACUNIT);
 		cx += w<<FRACBITS;
 	}
 }
 
+// Draw a string using the nt_font
+// Note that the outline is a seperate font set
+static void V_DrawNameTagLine(INT32 x, INT32 y, INT32 option, fixed_t scale, UINT8 *basecolormap, UINT8 *outlinecolormap, const char *string)
+{
+	fixed_t cx, cy, w;
+	INT32 c, dupx, dupy, scrwidth, left = 0;
+	const char *ch = string;
+
+	if (option & V_CENTERNAMETAG)
+		x -= FixedInt(FixedMul((V_NameTagWidth(string)/2)*FRACUNIT, scale));
+	option &= ~V_CENTERNAMETAG; // which is also shared with V_ALLOWLOWERCASE...
+
+	cx = x<<FRACBITS;
+	cy = y<<FRACBITS;
+
+	if (option & V_NOSCALESTART)
+	{
+		dupx = vid.dupx;
+		dupy = vid.dupy;
+		scrwidth = vid.width;
+	}
+	else
+	{
+		dupx = dupy = 1;
+		scrwidth = vid.width/vid.dupx;
+		left = (scrwidth - BASEVIDWIDTH)/2;
+		scrwidth -= left;
+	}
+
+	for (;;ch++)
+	{
+		if (!*ch)
+			break;
+		if (*ch == '\n')
+		{
+			cx = x<<FRACBITS;
+			cy += FixedMul((21*dupy)*FRACUNIT, scale);
+			continue;
+		}
+
+		c = toupper(*ch);
+		c -= NT_FONTSTART;
+
+		// character does not exist or is a space
+		if (c < 0 || c >= NT_FONTSIZE || !ntb_font[c] || !nto_font[c])
+		{
+			cx += FixedMul((4 * dupx)*FRACUNIT, scale);
+			continue;
+		}
+
+		w = FixedMul((SHORT(ntb_font[c]->width)+2 * dupx) * FRACUNIT, scale);
+
+		if (FixedInt(cx) > scrwidth)
+			continue;
+		if (cx+(left*FRACUNIT) + w < 0) // left boundary check
+		{
+			cx += w;
+			continue;
+		}
+
+		V_DrawFixedPatch(cx, cy, scale, option, nto_font[c], outlinecolormap);
+		V_DrawFixedPatch(cx, cy, scale, option, ntb_font[c], basecolormap);
+
+		cx += w;
+	}
+}
+
+// Looks familiar.
+void V_DrawNameTag(INT32 x, INT32 y, INT32 option, fixed_t scale, UINT8 *basecolormap, UINT8 *outlinecolormap, const char *string)
+{
+	const char *text = string;
+	const char *first_token = text;
+	char *last_token = strchr(text, '\n');
+	const INT32 lbreakheight = 21;
+	INT32 ntlines;
+
+	if (option & V_CENTERNAMETAG)
+	{
+		ntlines = V_CountNameTagLines(string);
+		y -= FixedInt(FixedMul(((lbreakheight/2) * (ntlines-1))*FRACUNIT, scale));
+	}
+
+	// No line breaks?
+	// Draw entire string
+	if (!last_token)
+		V_DrawNameTagLine(x, y, option, scale, basecolormap, outlinecolormap, string);
+	// Split string by the line break character
+	else
+	{
+		char *str = NULL;
+		INT32 len;
+		while (true)
+		{
+			// There are still lines left to draw
+			if (last_token)
+			{
+				size_t shift = 0;
+				// Free this line
+				if (str)
+					Z_Free(str);
+				// Find string length, do a malloc...
+				len = (last_token-first_token)+1;
+				str = ZZ_Alloc(len);
+				// Copy the line
+				strncpy(str, first_token, len-1);
+				str[len-1] = '\0';
+				// Don't leave a line break character
+				// at the start of the string!
+				if ((strlen(str) >= 2) && (string[0] == '\n') && (string[1] != '\n'))
+					shift++;
+				// Then draw it
+				V_DrawNameTagLine(x, y, option, scale, basecolormap, outlinecolormap, str+shift);
+			}
+			// No line break character was found
+			else
+			{
+				// Don't leave a line break character
+				// at the start of the string!
+				if ((strlen(first_token) >= 2) && (first_token[0] == '\n') && (first_token[1] != '\n'))
+					first_token++;
+				// Then draw it
+				V_DrawNameTagLine(x, y, option, scale, basecolormap, outlinecolormap, first_token);
+				break;
+			}
+
+			// Next line
+			y += FixedInt(FixedMul(lbreakheight*FRACUNIT, scale));
+			if ((last_token-text)+1 >= (signed)strlen(text))
+				last_token = NULL;
+			else
+			{
+				first_token = last_token;
+				last_token = strchr(first_token+1, '\n');
+			}
+		}
+		// Free this line
+		if (str)
+			Z_Free(str);
+	}
+}
+
+// Count the amount of lines in name tag string
+INT32 V_CountNameTagLines(const char *string)
+{
+	INT32 ntlines = 1;
+	const char *text = string;
+	const char *first_token = text;
+	char *last_token = strchr(text, '\n');
+
+	// No line breaks?
+	if (!last_token)
+		return ntlines;
+	// Split string by the line break character
+	else
+	{
+		while (true)
+		{
+			if (last_token)
+				ntlines++;
+			// No line break character was found
+			else
+				break;
+
+			// Next line
+			if ((last_token-text)+1 >= (signed)strlen(text))
+				last_token = NULL;
+			else
+			{
+				first_token = last_token;
+				last_token = strchr(first_token+1, '\n');
+			}
+		}
+	}
+	return ntlines;
+}
+
+INT32 V_NameTagWidth(const char *string)
+{
+	INT32 c, w = 0;
+	size_t i;
+
+	// It's possible for string to be a null pointer
+	if (!string)
+		return 0;
+
+	for (i = 0; i < strlen(string); i++)
+	{
+		c = toupper(string[i]) - NT_FONTSTART;
+		if (c < 0 || c >= NT_FONTSIZE || !ntb_font[c] || !nto_font[c])
+			w += 4;
+		else
+			w += SHORT(ntb_font[c]->width)+2;
+	}
+
+	return w;
+}
+
 // Find string width from cred_font chars
 //
 INT32 V_CreditStringWidth(const char *string)
@@ -2701,7 +2899,7 @@ void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string)
 		w = SHORT(lt_font[c]->width) * dupx;
 
 		if (cx > scrwidth)
-			break;
+			continue;
 		if (cx+left + w < 0) //left boundary check
 		{
 			cx += w;
diff --git a/src/v_video.h b/src/v_video.h
index 7eb990295de7c23d35eef3ebf29a243b74081eae..01d50cd57957479dfe74700aba40212caa215996 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -112,6 +112,7 @@ extern RGBA_t *pMasterPalette;
 #define V_OFFSET             0x00400000 // account for offsets in patches
 #define V_ALLOWLOWERCASE     0x00800000 // (strings only) allow fonts that have lowercase letters to use them
 #define V_FLIP               0x00800000 // (patches only) Horizontal flip
+#define V_CENTERNAMETAG      0x00800000 // (nametag only) center nametag lines
 
 #define V_SNAPTOTOP          0x01000000 // for centering
 #define V_SNAPTOBOTTOM       0x02000000 // for centering
@@ -205,6 +206,11 @@ INT32 V_LevelActNumWidth(INT32 num); // act number width
 void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string);
 INT32 V_CreditStringWidth(const char *string);
 
+// Draw a string using the nt_font
+void V_DrawNameTag(INT32 x, INT32 y, INT32 option, fixed_t scale, UINT8 *basecolormap, UINT8 *outlinecolormap, const char *string);
+INT32 V_CountNameTagLines(const char *string);
+INT32 V_NameTagWidth(const char *string);
+
 // Find string width from hu_font chars
 INT32 V_StringWidth(const char *string, INT32 option);
 // Find string width from hu_font chars, 0.5x scale
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index f86981015726ba1ea425f871214c7e6cde17af6d..5378bb52f645bc10abc0c58dcf27d38f15d9b85a 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -102,6 +102,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"FinishUpdate@4",      &hwdriver.pfnFinishUpdate},
 	{"Draw2DLine@12",       &hwdriver.pfnDraw2DLine},
 	{"DrawPolygon@16",      &hwdriver.pfnDrawPolygon},
+	{"RenderSkyDome@16",    &hwdriver.pfnRenderSkyDome},
 	{"SetBlend@4",          &hwdriver.pfnSetBlend},
 	{"ClearBuffer@12",      &hwdriver.pfnClearBuffer},
 	{"SetTexture@4",        &hwdriver.pfnSetTexture},
@@ -132,6 +133,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"FinishUpdate",        &hwdriver.pfnFinishUpdate},
 	{"Draw2DLine",          &hwdriver.pfnDraw2DLine},
 	{"DrawPolygon",         &hwdriver.pfnDrawPolygon},
+	{"RenderSkyDome",       &hwdriver.pfnRenderSkyDome},
 	{"SetBlend",            &hwdriver.pfnSetBlend},
 	{"ClearBuffer",         &hwdriver.pfnClearBuffer},
 	{"SetTexture",          &hwdriver.pfnSetTexture},
diff --git a/src/y_inter.c b/src/y_inter.c
index 0d6a3d03cff6b9e7676838d7a7a9d93e18cbdbcc..2fed35de30cbfedbae5c930cf29a9b78a9dae2f9 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -35,6 +35,7 @@
 #include "p_local.h"
 
 #include "m_cond.h" // condition sets
+#include "lua_hook.h" // IntermissionThinker hook
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
@@ -802,6 +803,10 @@ void Y_Ticker(void)
 	if (paused || P_AutoPause())
 		return;
 
+#ifdef HAVE_BLUA
+	LUAh_IntermissionThinker();
+#endif
+
 	intertic++;
 
 	// Team scramble code for team match and CTF.
@@ -1047,6 +1052,9 @@ static void Y_UpdateRecordReplays(void)
 	if ((UINT16)(players[consoleplayer].rings) > mainrecords[gamemap-1]->rings)
 		mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings);
 
+	if (data.coop.gotperfbonus)
+		mainrecords[gamemap-1]->gotperfect = true;
+
 	// Save demo!
 	bestdemo[255] = '\0';
 	lastdemo[255] = '\0';
diff --git a/tools/flatb/Makefile b/tools/flatb/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..2134973e68099497121ed8d79526e83549897163
--- /dev/null
+++ b/tools/flatb/Makefile
@@ -0,0 +1,9 @@
+.PHONY : all clean
+
+all : flatb
+
+flatb.exe : flatb.c
+	i686-w64-mingw32-gcc $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ $<
+
+clean :
+	$(RM) flatb flatb.exe
diff --git a/tools/flatb/flatb.c b/tools/flatb/flatb.c
new file mode 100644
index 0000000000000000000000000000000000000000..edc0892324d3728da88a6aad7d98738547c8c37b
--- /dev/null
+++ b/tools/flatb/flatb.c
@@ -0,0 +1,566 @@
+#define HELP \
+"Usage: flatb WAD-file list-file"                                         "\n"\
+"Replace flats and textures by name in a DOOM WAD."                       "\n"\
+"\n"\
+"list-file may have the following format:"                                "\n"\
+"\n"\
+"GFZFLR01 GFZFLR02"                                                       "\n"\
+"# Comment"                                                               "\n"\
+"GFZROCK GFZBLOCK"                                                        "\n"\
+"\n"\
+"The first name and second name may be delimited by any whitespace."       "\n"\
+"\n"\
+"Copyright 2019 James R."                                                 "\n"\
+"All rights reserved."                                                    "\n"
+
+/*
+Copyright 2019 James R.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+#define cchar const char
+#define cvoid const void
+
+#define LONG int32_t
+
+#define va_inline( __ap,__last, ... )\
+(\
+		va_start (__ap,__last),\
+		__VA_ARGS__,\
+		va_end   (__ap)\
+)
+
+#define DELIM "\t\n\r "
+
+typedef struct
+{
+	FILE  *       fp;
+	cchar * filename;
+}
+File;
+
+int (*le32)(cvoid *);
+
+void
+Pexit (int c, cchar *s, ...)
+{
+	va_list ap;
+	va_inline (ap, s,
+
+			vfprintf(stderr, s, ap)
+
+	);
+	exit(c);
+}
+
+void
+Prexit (cchar *pr, ...)
+{
+	va_list ap;
+	va_inline (ap, pr,
+
+			vfprintf(stderr, pr, ap)
+
+	);
+	perror("");
+	exit(-1);
+}
+
+void
+Fopen (File *f, cchar *filename, const char *mode)
+{
+	FILE *fp;
+	if (!( fp = fopen(filename, mode) ))
+		Prexit("%s", filename);
+	f->filename = filename;
+	f->fp = fp;
+}
+
+void
+Ferr (File *f)
+{
+	if (ferror(f->fp))
+		Prexit("%s", f->filename);
+}
+
+char *
+Fgets (File *f, int b, char *p)
+{
+	if (!( p = fgets(p, b, f->fp) ))
+		Ferr(f);
+	return p;
+}
+
+void
+Fread (File *f, int b, void *p)
+{
+	if (fread(p, 1, b, f->fp) < b)
+		Ferr(f);
+}
+
+void
+Fwrite (File *f, int b, cvoid *s)
+{
+	if (fwrite(s, 1, b, f->fp) < b)
+		Ferr(f);
+}
+
+void
+Fseek (File *f, long o)
+{
+	if (fseek(f->fp, o, SEEK_SET) == -1)
+		Prexit("%s", f->filename);
+}
+
+void *
+Malloc (int b)
+{
+	void *p;
+	if (!( p = malloc(b) ))
+		Prexit("%d", b);
+	return p;
+}
+
+void *
+Calloc (int c, int b)
+{
+	void *p;
+	if (!( p = calloc(c, b) ))
+		Prexit("(%d)%d", c, b);
+	return p;
+}
+
+void
+Reallocp (void *pp, int b)
+{
+	void *p;
+	if (!( p = realloc((*(void **)pp), b) ))
+		Prexit("%d", b);
+	(*(void **)pp) = p;
+}
+
+void
+strucpy (char *p, cchar *s, int n)
+{
+	int c;
+	int i;
+	for (i = 0; i < n && ( c = s[i] ); ++i)
+		p[i] = toupper(c);
+}
+
+int
+e32 (cvoid *s)
+{
+	unsigned int c;
+	c = *(LONG *)s;
+	return (
+			 ( c >> 24 )            |
+			(( c >>  8 )& 0x00FF00 )|
+			(( c <<  8 )& 0xFF0000 )|
+			 ( c << 24 )
+	);
+}
+
+int
+n32 (cvoid *s)
+{
+	return *(LONG *)s;
+}
+
+void
+Ie ()
+{
+	int c;
+	c = 1;
+	if (*(char *)&c == 1)
+		le32 = n32;
+	else
+		le32 = e32;
+}
+
+File         wad_file;
+File        list_file;
+
+int         list_c;
+char ***    list_v;
+
+char   * directory;
+char   *  lump;
+int       lumpsize;
+
+char   * sectors;
+int      sectors_c;
+
+char   *   sides;
+int        sides_c;
+
+int      st_floors;
+int      st_ceilings;
+int      st_sectors;
+
+int      st_sides;
+int      st_uppers;
+int      st_mids;
+int      st_lowers;
+
+/* this is horseshit */
+char   * old;
+char   * new;
+int      did;
+
+void
+Itable ()
+{
+	char a[1024];
+
+	char ***ttt;
+	char ***ppp;
+
+	char  **pp;
+
+	int c;
+
+	while (Fgets(&list_file, sizeof a, a))
+	{
+		c = a[0];
+		if (!(
+					c == '\n' ||
+					c == '#'
+		))
+		{
+			list_c++;
+		}
+	}
+
+	rewind(list_file.fp);
+
+	list_v = Calloc(list_c, sizeof (char **));
+	for (
+			ttt = ( ppp = list_v ) + list_c;
+			ppp < ttt;
+			++ppp
+	)
+	{
+		(*ppp) = pp = Calloc(2, sizeof (char *));
+		pp[0] = Malloc(9);
+		pp[1] = Malloc(9);
+	}
+}
+
+void
+Iwad ()
+{
+	char  buf[12];
+
+	char *  t;
+	char *  p;
+	int   map;
+
+	char *sector_p;
+	char *  side_p;
+
+	int n;
+	int h;
+
+	Fread(&wad_file, 12, buf);
+	if (
+			memcmp(buf, "IWAD", 4) != 0 &&
+			memcmp(buf, "PWAD", 4) != 0
+	)
+	{
+		Pexit(-1,"%s: Not a WAD\n", wad_file.filename);
+	}
+
+	Fseek(&wad_file, (*le32)(&buf[8]));
+
+	n         = (*le32)(&buf[4]) * 8;
+	h         = n / 9;
+	n        *= 2;
+	directory = Malloc(n);
+	/* minimum number of lumps for a map */
+	sectors   = Malloc(h);
+	sides     = Malloc(h);
+
+	Fread(&wad_file, n, directory);
+
+	sector_p = sectors;
+	side_p   = sides;
+	map = 3;
+	for (t = ( p = directory ) + n; p < t; p += 16)
+	{
+		/* looking for SECTORS? Hopefully order doesn't matter in real world. */
+		/* also search for fucking SIDES MY SIDES AAAAAAAAAA */
+		switch (map)
+		{
+			case 0:
+			case 2:
+				if (strncmp(&p[8], "SECTORS", 8) == 0)
+				{
+					/* copy file offset and size */
+					memcpy(sector_p, p, 8);
+					sector_p += 8;
+					sectors_c++;
+					map |= 1;
+				}
+			case 1:
+				if (strncmp(&p[8], "SIDEDEFS", 8) == 0)
+				{
+					memcpy(side_p, p, 8);
+					side_p += 8;
+					sides_c++;
+					map |= 2;
+				}
+		}
+		if (map == 3)
+		{
+			/* MAP marker */
+			if (p[13] == '\0' && strncmp(&p[8], "MAP", 3) == 0)
+				map = 0;
+		}
+	}
+}
+
+void
+Fuckyou (char *p, int f, int *st)
+{
+	if (strncmp(p, old, 8) == 0)
+	{
+		strncpy(p, new, 8);
+		(*st)++;
+		did |= f;
+	}
+}
+
+void
+Epic (char *p, char *t)
+{
+	char *top;
+	char *bot;
+	int i;
+	/* oh hi magic number! */
+	for (; p < t; p += 26)
+	{
+		bot = &p [4];
+		top = &p[12];
+		did = 0;
+		for (i = 0; i < list_c; ++i)
+		{
+			old = list_v[i][0];
+			new = list_v[i][1];
+			switch (did)
+			{
+				case 0:
+				case 2:
+					Fuckyou(bot, 1, &st_floors);
+				case 1:
+					Fuckyou(top, 2, &st_ceilings);
+			}
+			if (did == 3)
+				break;
+		}
+		if (did)
+			st_sectors++;
+	}
+}
+
+void
+Epic2 (char *p, char *t)
+{
+	char *top;
+	char *mid;
+	char *bot;
+	int i;
+	for (; p < t; p += 30)
+	{
+		top = &p [4];
+		bot = &p[12];
+		mid = &p[20];
+		did = 0;
+		for (i = 0; i < list_c; ++i)
+		{
+			old = list_v[i][0];
+			new = list_v[i][1];
+			switch (did)
+			{
+				case 0:
+				case 2:
+				case 4:
+				case 6:
+					Fuckyou(top, 1, &st_uppers);
+				case 1:
+				case 5:
+					Fuckyou(mid, 2, &st_mids);
+				case 3:
+					Fuckyou(bot, 4, &st_lowers);
+			}
+			if (did == 7)
+				break;
+		}
+		if (did)
+			st_sides++;
+	}
+}
+
+void
+Fuck (char *p, int c, void (*fn)(char *,char *))
+{
+	char *t;
+	int offs;
+	int size;
+	for (t = p + c * 8; p < t; p += 8)
+	{
+		offs = (*le32)(p);
+		size = (*le32)(p + 4);
+		if (lumpsize < size)
+		{
+			Reallocp(&lump, size);
+			lumpsize = size;
+		}
+		Fseek(&wad_file, offs);
+		Fread(&wad_file, size, lump);
+		(*fn)(lump, lump + size);
+		Fseek(&wad_file, offs);
+		Fwrite(&wad_file, size, lump);
+	}
+}
+
+void
+Awad ()
+{
+	Fuck (sectors, sectors_c, Epic);
+	Fuck   (sides,   sides_c, Epic2);
+}
+
+void
+Readtable ()
+{
+	char    a[1024];
+
+	int     s;
+	char *old;
+	char *new;
+
+	int c;
+
+	s = 0;
+
+	while (Fgets(&list_file, sizeof a, a))
+	{
+		c = a[0];
+		if (!(
+				c == '\n' ||
+				c == '#'
+		))
+		{
+			if (
+					( old = strtok(a, DELIM) ) &&
+					( new = strtok(0, DELIM) )
+			)
+			{
+				strucpy(list_v[s][0], old, 8);
+				strucpy(list_v[s][1], new, 8);
+				++s;
+			}
+		}
+	}
+}
+
+void
+Cleanup ()
+{
+	char ***ttt;
+	char ***ppp;
+
+	char  **pp;
+
+	free(lump);
+	free(sides);
+	free(sectors);
+	free(directory);
+
+	if (list_v)
+	{
+		for (
+				ttt = ( ppp = list_v ) + list_c;
+				ppp < ttt && ( pp = (*ppp) );
+				++ppp
+		)
+		{
+			free(pp[0]);
+			free(pp[1]);
+			free(pp);
+		}
+		free(list_v);
+	}
+}
+
+int
+main (int ac, char **av)
+{
+	int n;
+
+	if (ac < 3)
+		Pexit(0,HELP);
+
+	Fopen (& wad_file, av[1], "rb+");
+	Fopen (&list_file, av[2], "r");
+
+	if (atexit(Cleanup) != 0)
+		Pexit(-1,"Failed to register cleanup function.\n");
+
+	Itable();
+	Readtable();
+
+	Ie();
+
+	Iwad();
+	Awad();
+
+	printf(
+			"%5d sectors changed.\n"
+			"%5d floors.\n"
+			"%5d ceilings.\n"
+			"\n"
+			"%5d sides.\n"
+			"%5d upper textures.\n"
+			"%5d mid textures.\n"
+			"%5d lower textures.\n",
+
+			st_sectors,
+
+			st_floors,
+			st_ceilings,
+
+			st_sides,
+
+			st_uppers,
+			st_mids,
+			st_lowers);
+
+	return 0;
+}