diff --git a/extras/conf/Includes/Game_SRB222.cfg b/extras/conf/Includes/Game_SRB222.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..3c4b11e9f301f90b68a5ee90c366416048946b73
--- /dev/null
+++ b/extras/conf/Includes/Game_SRB222.cfg
@@ -0,0 +1,77 @@
+// Default lump name for new map
+defaultlumpname = "MAP01";
+//GZDB specific. Don't try to load lumps that don't exist.
+basegame = 0;
+
+//Sky textures for vanilla maps
+defaultskytextures
+{
+	SKY1 = "MAP01,MAP02,MAP03,MAP33,MAP50,MAP60,MAPF0,MAPM0";
+	SKY2 = "MAPM7,MAPMB";
+	SKY4 = "MAP04,MAP06,MAP61,MAPF6,MAPM1";
+	SKY6 = "MAP05,MAP51,MAPMA";
+	SKY7 = "MAPM2,MAPM5";
+	SKY8 = "MAP07,MAP08,MAP09,MAP52,MAP62,MAPF1";
+	SKY10 = "MAP10,MAP12,MAP53,MAP63,MAPM3";
+	SKY11 = "MAP11,MAPF7";
+	SKY13 = "MAP13,MAP64";
+	SKY14 = "MAP14";
+	SKY15 = "MAP15,MAP54";
+	SKY17 = "MAP70";
+	SKY20 = "MAP32,MAP55,MAP65,MAPF2,MAPF5";
+	SKY21 = "MAPM4";
+	SKY22 = "MAP22,MAP23,MAP25,MAP26,MAP27,MAP56,MAP66,MAPF4,MAPM6";
+	SKY30 = "MAP30";
+	SKY31 = "MAP31";
+	SKY35 = "MAP42";
+	SKY40 = "MAP41,MAP71,MAPM9";
+	SKY55 = "MAPF3,MAPM8";
+	SKY68 = "MAPF8";
+	SKY99 = "MAP57,MAPZ0";
+	SKY159 = "MAP16";
+	SKY172 = "MAP40";
+	SKY300 = "MAP72";
+	SKY301 = "MAP73";
+}
+
+// Skill levels
+skills
+{
+	1 = "Normal";
+}
+
+// Skins
+skins
+{
+	Sonic;
+	Tails;
+	Knuckles;
+	Amy;
+	Fang;
+	Metalsonic;
+}
+
+// Gametypes
+gametypes
+{
+	-1 = "Single Player";
+	0 = "Co-op";
+	1 = "Competition";
+	2 = "Race";
+	3 = "Match";
+	4 = "Team Match";
+	5 = "Tag";
+	6 = "Hide and Seek";
+	7 = "CTF";
+}
+
+// Texture loading options
+defaultwalltexture = "GFZROCK";
+defaultfloortexture = "GFZFLR01";
+defaultceilingtexture = "F_SKY1";
+
+// Default texture sets
+// (these are not required, but useful for new users)
+texturesets
+{
+}
\ No newline at end of file
diff --git a/extras/conf/Includes/SRB222_common.cfg b/extras/conf/Includes/SRB222_common.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..a832e6cefe6c2ba1580cbd48083bcf1c2702da81
--- /dev/null
+++ b/extras/conf/Includes/SRB222_common.cfg
@@ -0,0 +1,309 @@
+common
+{
+	// Some common settings
+
+
+
+	// Default testing parameters
+	testparameters = "-file \"%AP\" \"%F\" -warp %L";
+	testshortpaths = true;
+
+	// Action special help (mxd)
+	actionspecialhelp = "https://wiki.srb2.org/wiki/Linedef_type_%K";
+
+	// Default nodebuilder configurations
+	defaultsavecompiler = "zennode_normal";
+	defaulttestcompiler = "zennode_fast";
+
+	// Generalized actions
+	generalizedlinedefs = false;
+	generalizedsectors = true;
+
+	mixtexturesflats = true;
+	defaulttexturescale = 1.0f;
+	defaultflatscale = 1.0f;
+	scaledtextureoffsets = true;
+
+	// Thing number for start position in 3D Mode
+	start3dmode = 3328;
+
+	// Texture sources
+	textures
+	{
+		include("SRB222_misc.cfg", "textures");
+	}
+
+	// Patch sources
+	patches
+	{
+		include("SRB222_misc.cfg", "patches");
+	}
+
+	// Sprite sources
+	sprites
+	{
+		include("SRB222_misc.cfg", "sprites");
+	}
+
+	// Flat sources
+	flats
+	{
+		include("SRB222_misc.cfg", "flats");
+	}
+}
+
+mapformat_doom
+{
+	// The format interface handles the map data format - DoomMapSetIO for SRB2DB2, SRB2MapSetIO for Zone Builder
+	formatinterface = "SRB2MapSetIO";
+
+	/*
+	GAME DETECT PATTERN
+	Used to guess the game for which a WAD file is made.
+
+	1 = One of these lumps must exist
+	2 = None of these lumps must exist
+	3 = All of these lumps must exist
+	*/
+
+	gamedetect
+	{
+		EXTENDED = 2;
+
+
+		BEHAVIOR = 2;
+
+		E#M# = 2;
+
+		MAP?? = 1;
+	}
+
+	/*
+	MAP LUMP NAMES
+	Map lumps are loaded with the map as long as they are right after each other. When the editor
+	meets a lump which is not defined in this list it will ignore the map if not satisfied.
+	The order of items defines the order in which lumps will be written to WAD file on save.
+	To indicate the map header lump, use ~MAP
+
+	Legenda:
+	required = Lump is required to exist.
+	blindcopy = Lump will be copied along with the map blindly. (usefull for lumps Doom Builder doesn't use)
+	nodebuild = The nodebuilder generates this lump.
+	allowempty = The nodebuilder is allowed to leave this lump empty.
+	script = This lump is a text-based script. Specify the filename of the script configuration to use.
+	*/
+
+	maplumpnames
+	{
+		include("SRB222_misc.cfg", "doommaplumpnames");
+	}
+
+	// When this is set to true, sectors with the same tag will light up when a line is highlighted
+	linetagindicatesectors = true;
+
+	// Special linedefs
+	include("SRB222_misc.cfg", "speciallinedefs");
+
+	// Default flags for first new thing (As far as 2.2 goes, they're empty just like in 2.1)
+	defaultthingflags
+	{
+	}
+
+	// DEFAULT SECTOR BRIGHTNESS LEVELS
+	sectorbrightness
+	{
+		include("SRB222_misc.cfg", "sectorbrightness");
+	}
+
+	// SECTOR TYPES-----------------------------------------------------------------
+	sectortypes
+	{
+		include("SRB222_sectors.cfg", "sectortypes");
+	}
+
+	// GENERALISED SECTOR TYPES-----------------------------------------------------------------
+	gen_sectortypes
+	{
+		include("SRB222_sectors.cfg", "gen_sectortypes");
+	}
+
+	// LINEDEF FLAGS
+	linedefflags
+	{
+		include("SRB222_misc.cfg", "linedefflags");
+	}
+
+	// Linedef flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	linedefflagstranslation
+	{
+		include("SRB222_misc.cfg", "linedefflagstranslation");
+	}
+
+	// LINEDEF ACTIVATIONS
+	linedefactivations
+	{
+	}
+
+	// LINEDEF TYPES
+	linedeftypes
+	{
+		include("SRB222_linedefs.cfg", "doom");
+	}
+
+	// THING FLAGS
+	thingflags
+	{
+		include("SRB222_misc.cfg", "thingflags");
+	}
+
+	// Thing flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	thingflagstranslation
+	{
+		include("SRB222_misc.cfg", "thingflagstranslation");
+	}
+
+	// THING FLAGS ERROR MASK
+	// Mask for the thing flags which indicates the options
+	// that make the same thing appear in the same modes
+	thingflagsmask1 = 7;	// 1 + 2 + 4
+	thingflagsmask2 = 0;
+}
+
+mapformat_udmf
+{
+	// The format interface handles the map data format
+	formatinterface = "UniversalMapSetIO";
+
+	// Enables support for long (> 8 chars) texture names
+	// WARNING: this should only be enabled for UDMF game configurations!
+	// WARNING: enabling this will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3!
+	longtexturenames = false;
+
+	// Default nodebuilder configurations
+	defaultsavecompiler = "zdbsp_udmf_normal";
+	defaulttestcompiler = "zdbsp_udmf_fast";
+
+	engine = "srb2"; // override that so that DB2 uses the correct namespace
+
+	maplumpnames
+	{
+		include("UDMF_misc.cfg", "udmfmaplumpnames_begin");
+		include("SRB222_misc.cfg", "udmfmaplumpnames");
+		include("UDMF_misc.cfg", "udmfmaplumpnames_end");
+	}
+
+	universalfields
+	{
+//		include("SRB222_misc.cfg", "universalfields");
+	}
+
+	// When this is set to true, sectors with the same tag will light up when a line is highlighted
+	linetagindicatesectors = false;
+
+	// Special linedefs
+	include("SRB222_misc.cfg", "speciallinedefs_udmf");
+
+	// Default flags for first new thing (As far as 2.2 goes, they're empty just like in 2.1)
+	defaultthingflags
+	{
+	}
+
+	// Generalized actions
+	generalizedlinedefs = false;
+
+	// SECTOR FLAGS
+	sectorflags
+	{
+//		include("SRB222_misc.cfg", "sectorflags");
+	}
+
+	// DEFAULT SECTOR BRIGHTNESS LEVELS
+	sectorbrightness
+	{
+		include("SRB222_misc.cfg", "sectorbrightness");
+	}
+
+	// SECTOR TYPES
+	sectortypes
+	{
+		include("SRB222_sectors.cfg", "sectortypes");
+	}
+
+	// SECTOR RENSERSTYLES
+/*	sectorrenderstyles
+	{
+		include("SRB222_misc.cfg", "sectorrenderstyles");
+	}*/
+
+	// LINEDEF FLAGS
+	linedefflags
+	{
+		include("SRB222_misc.cfg", "linedefflags_udmf");
+	}
+
+	// LINEDEF ACTIVATIONS
+	linedefactivations
+	{
+		include("SRB222_misc.cfg", "linedefactivations_udmf");
+	}
+
+	linedefflagstranslation
+	{
+	}
+
+
+	// LINEDEF RENSERSTYLES
+	linedefrenderstyles
+	{
+		include("SRB222_misc.cfg", "linedefrenderstyles");
+	}
+
+	//SIDEDEF FLAGS
+/*	sidedefflags
+	{
+		include("UDMF_misc.cfg", "sidedefflags");
+	}*/
+
+	// THING FLAGS
+	thingflags
+	{
+		include("SRB222_misc.cfg", "thingflags_udmf");
+	}
+
+	// Thing flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	thingflagstranslation
+	{
+		include("SRB222_misc.cfg", "thingflagstranslation");
+	}
+
+	// THING RENSERSTYLES
+/*	thingrenderstyles
+	{
+		include("SRB222_misc.cfg", "thingrenderstyles");
+	}*/
+
+	// How to compare thing flags (for the stuck things error checker)
+/*	thingflagscompare
+	{
+		include("UDMF_misc.cfg", "thingflagscompare");
+	}*/
+
+	//mxd. 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
+	{
+	}
+
+	// LINEDEF TYPES
+	linedeftypes
+	{
+		include("SRB222_linedefs.cfg", "udmf");
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/Includes/SRB222_linedefs.cfg b/extras/conf/Includes/SRB222_linedefs.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..fdf1918502a582abebe398e2ecb7bc8c98db442e
--- /dev/null
+++ b/extras/conf/Includes/SRB222_linedefs.cfg
@@ -0,0 +1,2486 @@
+doom
+{
+	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 end item";
+			flags256text = "[8] Spawn link 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";
+		}
+
+		462
+		{
+			title = "Stop Timer/Exit Stage in Record Attack";
+			prefix = "(462)";
+		}
+	}
+
+	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)";
+		}
+	}
+}
+
+udmf
+{
+	misc
+	{
+		title = "Miscellaneous";
+
+		0
+		{
+			title = "None";
+			prefix = "(0)";
+		}
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/Includes/SRB222_misc.cfg b/extras/conf/Includes/SRB222_misc.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..ce23388b2362094d097ff3893a106e6664a0b255
--- /dev/null
+++ b/extras/conf/Includes/SRB222_misc.cfg
@@ -0,0 +1,726 @@
+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 ACTIVATIONS
+// Make sure these are in order from lowest value to highest value
+linedefactivations
+{
+}
+
+
+// Linedef flags UDMF translation table
+// This is needed for copy/paste and prefabs to work properly
+// When the UDMF field name is prefixed with ! it is inverted
+linedefflagstranslation
+{
+	1 = "blocking";
+	2 = "blockmonsters";
+	4 = "twosided";
+	8 = "dontpegtop";
+	16 = "dontpegbottom";
+	32 = "skewtd";
+	64 = "noclimb";
+	128 = "noskew";
+	256 = "midpeg";
+	512 = "midsolid";
+	1024 = "wrapmidtex";
+	2048 = "netonly";
+	4096 = "nonet";
+	8192 = "effect6";
+	16384 = "bouncy";
+	32768 = "transfer";
+}
+
+
+linedefflags_udmf
+{
+	blocking = "Impassable";
+	blockmonsters = "Block Enemies";
+	twosided = "Double-Sided";
+	dontpegtop = "Upper Unpegged";
+	dontpegbottom = "Lower Unpegged";
+	skewtd = "Slope Skew";
+	noclimb = "Not Climbable";
+	noskew = "No Midtexture Skew";
+	midpeg = "Peg Midtexture";
+	midsolid = "Solid Midtexture";
+	wrapmidtex = "Repeat Midtexture";
+//	netonly = "Netgame-Only special";
+//	nonet = "No netgame special";
+//	effect6 = "Effect 6";
+	bouncy = "Bouncy Wall";
+//	transfer = "Transfer Line";
+}
+
+
+linedefactivations_udmf
+{
+	notriggerorder = "Out of Order";
+	netonly = "Netgame-Only";
+	nonet = "No netgame";
+}
+
+sidedefflags
+{
+	clipmidtex = "Clip middle texture";
+	wrapmidtex = "Wrap middle texture";
+	smoothlighting = "Smooth lighting";
+	nofakecontrast = "Even lighting";
+	nodecals = "No decals";
+	lightfog = "Use sidedef brightness on fogged walls";
+}
+
+//RENDER STYLES
+thingrenderstyles
+{
+}
+
+linedefrenderstyles
+{
+	translucent = "Translucent";
+	fog = "Fog";
+}
+
+sectorrenderstyles
+{
+}
+
+thingflags
+{
+	1 = "[1] Extra";
+	2 = "[2] Flip";
+	4 = "[4] Special";
+	8 = "[8] Ambush";
+}
+
+// THING FLAGS
+thingflags_udmf
+{
+	extra = "Extra";
+	flip = "Flip";
+	special = "Special";
+	ambush = "Ambush";
+}
+
+
+// Thing flags UDMF translation table
+// This is needed for copy/paste and prefabs to work properly
+// When the UDMF field name is prefixed with ! it is inverted
+thingflagstranslation
+{
+	1 = "extra";
+	2 = "flip";
+	4 = "special";
+	8 = "ambush";
+}
+
+
+// DEFAULT SECTOR BRIGHTNESS LEVELS
+sectorbrightness
+{
+	255;
+	248;
+	240;
+	232;
+	224;
+	216;
+	208;
+	200;
+	192;
+	184;
+	176;
+	168;
+	160;
+	152;
+	144;
+	136;
+	128;
+	120;
+	112;
+	104;
+	96;
+	88;
+	80;
+	72;
+	64;
+	56;
+	48;
+	40;
+	32;
+	24;
+	16;
+	8;
+	0;
+}
+
+/*
+TEXTURES AND FLAT SOURCES
+This tells Doom Builder where to find the information for textures
+and flats in the IWAD file, Addition WAD file and Map WAD file.
+
+Start and end lumps must be given in a structure (of which the
+key name doesnt matter) and any textures or flats in between them
+are loaded in either the textures category or flats category.
+
+For textures: PNAMES, TEXTURE1 and TEXTURE2 are loaded by default.
+*/
+textures
+{
+	zdoom1
+	{
+		start = "TX_START";
+		end = "TX_END";
+	}
+}
+
+/*
+ADDITIONAL UNIVERSAL DOOM MAP FORMAT FIELD DEFINITIONS
+Only add fields here that Doom Builder does not edit with its own user-interface!
+The "default" field must match the UDMF specifications!
+
+Field data types:
+0 = integer *
+1 = float
+2 = string
+3 = bool
+4 = linedef action (integer) *
+5 = sector effect (integer) *
+6 = texture (string)
+7 = flat (string)
+8 = angle in degrees (integer)
+9 = angle in radians (float)
+10 = XXRRGGBB color (integer)
+11 = enum option (integer) *
+12 = enum bits (integer) *
+13 = sector tag (integer) *
+14 = thing tag (integer) *
+15 = linedef tag (integer) *
+16 = enum option (string)
+17 = angle in degrees (float)
+22 = byte angle (integer)
+*/
+universalfields
+{
+	sector
+	{
+		friction
+		{
+			name = "Friction";
+			type = 1;
+			default = 1;
+		}
+
+		specialeffectplanes
+		{
+			type = 11;
+			enum = "floorceiling";
+			default = 0;
+		}
+
+		colormapbegin
+		{
+			type = 0;
+			default = 0;
+		}
+
+		colormapend
+		{
+			type = 0;
+			default = 33;
+		}
+
+		foglighting
+		{
+			type = 3;
+			default = false;
+		}
+
+		teambase
+		{
+			type = 11;
+			enum = "ctfteam";
+			default = 0;
+		}
+
+		triggersector
+		{
+			type = 3;
+			default = false;
+		}
+
+		triggerobject
+		{
+			type = 11;
+			enum = "triggerobjects";
+			default = 0;
+		}
+
+		triggersurface
+		{
+			type = 11;
+			enum = "triggersurfaces";
+			default = 0;
+		}
+
+		ringdrain
+		{
+			type = 1;
+			default = 0;
+		}
+	}
+
+	linedef
+	{
+		executordelay
+		{
+			type = 0;
+			default = 0;
+		}
+		midtexrepetitions
+		{
+			type = 0;
+			default = 0;
+		}
+		arg5
+		{
+			type = 0;
+			default = 0;
+		}
+		arg1str
+		{
+			type = 2;
+			default = "";
+		}
+	}
+
+	thing
+	{
+	}
+}
+
+/*
+MAP LUMP NAMES
+Map lumps are loaded with the map as long as they are right after each other. When the editor
+meets a lump which is not defined in this list it will ignore the map if not satisfied.
+The order of items defines the order in which lumps will be written to WAD file on save.
+To indicate the map header lump, use ~MAP
+
+Legenda:
+required = Lump is required to exist.
+blindcopy = Lump will be copied along with the map blindly. (useful for lumps Doom Builder doesn't use)
+nodebuild = The nodebuilder generates this lump.
+allowempty = The nodebuilder is allowed to leave this lump empty.
+scriptbuild = This lump is a text-based script, which should be compiled using current script compiler;
+script = This lump is a text-based script. Specify the filename of the script configuration to use.
+*/
+
+doommaplumpnames
+{
+	~MAP
+	{
+		required = true;
+		blindcopy = true;
+		nodebuild = false;
+	}
+
+	THINGS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = true;
+	}
+
+	LINEDEFS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SIDEDEFS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	VERTEXES
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SEGS
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SSECTORS
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	NODES
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	SECTORS
+	{
+		required = true;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	REJECT
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	BLOCKMAP
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = true;
+	}
+}
+
+udmfmaplumpnames
+{
+	ZNODES
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	REJECT
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+
+	BLOCKMAP
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = true;
+	}
+}
+
+// ENUMERATIONS
+// These are enumerated lists for linedef types and UDMF fields.
+// Reserved names are: angledeg, anglerad, color, texture, flat
+enums
+{
+	falsetrue
+	{
+		0 = "False";
+		1 = "True";
+	}
+
+	yesno
+	{
+		0 = "Yes";
+		1 = "No";
+	}
+
+	noyes
+	{
+		0 = "No";
+		1 = "Yes";
+	}
+
+	onoff
+	{
+		0 = "On";
+		1 = "Off";
+	}
+
+	offon
+	{
+		0 = "Off";
+		1 = "On";
+	}
+
+	updown
+	{
+		0 = "Up";
+		1 = "Down";
+	}
+
+	downup
+	{
+		0 = "Down";
+		1 = "Up";
+	}
+
+	addset
+	{
+		0 = "Add";
+		1 = "Set";
+	}
+
+	floorceiling
+	{
+		0 = "Floor";
+		1 = "Ceiling";
+		2 = "Floor and ceiling";
+	}
+
+	triggertype
+	{
+		0 = "Continuous";
+		1 = "Each Time (Enter)";
+		2 = "Each Time (Enter and leave)";
+		3 = "Once";
+	}
+
+	frontback
+	{
+		0 = "None";
+		1 = "Front";
+		2 = "Back";
+	}
+
+	ctfteam
+	{
+		0 = "None";
+		1 = "Red";
+		2 = "Blue";
+	}
+
+	triggerobjects
+	{
+		0 = "Any player";
+		1 = "All players";
+		2 = "Pushable object";
+		3 = "Any object with thinker";
+	}
+
+	triggersurfaces
+	{
+		0 = "Floor touch";
+		1 = "Ceiling touch";
+		2 = "Floor or ceiling touch";
+		3 = "Anywhere in sector";
+	}
+
+	tangibility
+	{
+		1 = "Intangible from top";
+		2 = "Intangible from bottom";
+		4 = "Don't block players";
+		8 = "Don't block non-players";
+	}
+}
+
+//Default things filters
+thingsfilters
+{
+
+	filter0
+	{
+		name = "Player starts";
+		category = "starts";
+		type = -1;
+	}
+
+
+	filter1
+	{
+		name = "Enemies";
+		category = "enemies";
+		type = -1;
+
+	}
+
+
+	filter2
+	{
+		name = "NiGHTS Track";
+		category = "nightstrk";
+		type = -1;
+
+	}
+
+
+	filter3
+	{
+		name = "Normal Gravity";
+		category = "";
+		type = -1;
+
+		fields
+		{
+			2 = false;
+		}
+
+	}
+
+
+	filter4
+	{
+		name = "Reverse Gravity";
+		category = "";
+		type = -1;
+
+		fields
+		{
+			2 = true;
+		}
+
+	}
+}
+
+thingsfilters_udmf
+{
+}
+
+// Special linedefs
+speciallinedefs
+{
+	soundlinedefflag = 64;	// See linedefflags
+	singlesidedflag = 1;	// See linedefflags
+	doublesidedflag = 4;	// See linedefflags
+	impassableflag = 1;
+	upperunpeggedflag = 8;
+	lowerunpeggedflag = 16;
+	repeatmidtextureflag = 1024;
+	pegmidtextureflag = 256;
+}
+
+speciallinedefs_udmf
+{
+	soundlinedefflag = "noclimb";
+	singlesidedflag = "blocking";
+	doublesidedflag = "twosided";
+	impassableflag = "blocking";
+	upperunpeggedflag = "dontpegtop";
+	lowerunpeggedflag = "dontpegbottom";
+	repeatmidtextureflag = "wrapmidtex";
+	pegmidtextureflag = "midpeg";
+}
+
+scriptlumpnames
+{
+	MAINCFG
+	{
+		script = "SOC.cfg";
+	}
+
+	OBJCTCFG
+	{
+		script = "SOC.cfg";
+	}
+
+	SOC_
+	{
+		script = "SOC.cfg";
+		isprefix = true;
+	}
+
+	LUA_
+	{
+		script = "Lua.cfg";
+		isprefix = true;
+	}
+}
+
+// Texture sources
+textures
+{
+	zdoom1
+	{
+		start = "TX_START";
+		end = "TX_END";
+	}
+}
+
+// Patch sources
+patches
+{
+	standard1
+	{
+		start = "P_START";
+		end = "P_END";
+	}
+
+	standard2
+	{
+		start = "PP_START";
+		end = "PP_END";
+	}
+}
+
+// Sprite sources
+sprites
+{
+	standard1
+	{
+		start = "S_START";
+		end = "S_END";
+	}
+
+	standard2
+	{
+		start = "SS_START";
+		end = "SS_END";
+	}
+}
+
+// Flat sources
+flats
+{
+	standard1
+	{
+		start = "F_START";
+		end = "F_END";
+	}
+
+	standard2
+	{
+		start = "FF_START";
+		end = "FF_END";
+	}
+
+	standard3
+	{
+		start = "FF_START";
+		end = "F_END";
+	}
+
+	standard4
+	{
+		start = "F_START";
+		end = "FF_END";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/Includes/SRB222_sectors.cfg b/extras/conf/Includes/SRB222_sectors.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..3bcbeb1b14d8ac6466f79e888a5be7ef3aa9bc24
--- /dev/null
+++ b/extras/conf/Includes/SRB222_sectors.cfg
@@ -0,0 +1,109 @@
+sectortypes
+{
+	0 = "Normal";
+	1 = "Damage";
+	2 = "Damage (Water)";
+	3 = "Damage (Fire)";
+	4 = "Damage (Electrical)";
+	5 = "Spikes";
+	6 = "Death Pit (Camera Tilt)";
+	7 = "Death Pit (No Camera Tilt)";
+	8 = "Instant Kill";
+	9 = "Ring Drainer (Floor Touch)";
+	10 = "Ring Drainer (Anywhere in Sector)";
+	11 = "Special Stage Damage";
+	12 = "Space Countdown";
+	13 = "Ramp Sector (double step-up/down)";
+	14 = "Non-Ramp Sector (no step-down)";
+	15 = "Bouncy FOF";
+	16 = "Trigger Line Ex. (Pushable Objects)";
+	32 = "Trigger Line Ex. (Anywhere, All Players)";
+	48 = "Trigger Line Ex. (Floor Touch, All Players)";
+	64 = "Trigger Line Ex. (Anywhere in Sector)";
+	80 = "Trigger Line Ex. (Floor Touch)";
+	96 = "Trigger Line Ex. (Emerald Check)";
+	112 = "Trigger Line Ex. (NiGHTS Mare)";
+	128 = "Check for Linedef Executor on FOFs";
+	144 = "Egg Capsule";
+	160 = "Special Stage Time/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";
+}
+
+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";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/Includes/SRB222_things.cfg b/extras/conf/Includes/SRB222_things.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..194e43630b70f5f98b6a3963b4b27371c85d2e02
--- /dev/null
+++ b/extras/conf/Includes/SRB222_things.cfg
@@ -0,0 +1,3398 @@
+// THING TYPES------------------------------------------------------------------
+// Color values: 1-Dark_Blue 2-Dark_Green 3-Turqoise 4-Dark_Red 5-Purple 6-Brown 7-Gray
+// 8-Dark_Gray 9-Blue 10-Green 11-Cyan 12-Red 13-Magenta
+// 14-Yellow 15-White 16-Pink 17-Orange 18-Gold 19-Cream
+
+editor
+{
+	color = 15; // White
+	arrow = 1;
+	title = "<Editor Things>";
+	error = -1;
+	width = 8;
+	height = 16;
+	sort = 1;
+
+	3328 = "3D Mode Start";
+}
+
+starts
+{
+	color = 1; // Blue
+	arrow = 1;
+	title = "Player Starts";
+	width = 16;
+	height = 48;
+	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 = "Lance-a-Bot";
+		sprite = "CBFSA1";
+		width = 32;
+		height = 72;
+	}
+	1113
+	{
+		title = "Suspicious Lance-a-Bot Statue";
+		sprite = "CBBSA1";
+		width = 32;
+		height = 72;
+	}
+	119
+	{
+		title = "Egg Guard";
+		sprite = "ESHIA1";
+		width = 16;
+		height = 48;
+		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 = "MNUSA0";
+		width = 24;
+		height = 32;
+	}
+	134
+	{
+		title = "Canarivore";
+		sprite = "CANAA0";
+		width = 12;
+		height = 80;
+		hangs = 1;
+	}
+	123
+	{
+		title = "Unidus";
+		sprite = "UNIDA1";
+		width = 18;
+		height = 36;
+	}
+	135
+	{
+		title = "Pterabyte Spawner";
+		sprite = "PTERA2A8";
+		width = 16;
+		height = 16;
+		parametertext = "No. Pterabytes";
+	}
+	136
+	{
+		title = "Pyre Fly";
+		sprite = "PYREA0";
+		width = 24;
+		height = 34;
+		flags8text = "[8] Start on fire";
+	}
+	137
+	{
+		title = "Dragonbomber";
+		sprite = "DRABA1";
+		width = 28;
+		height = 48;
+	}
+	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 = "Buggle";
+		sprite = "BBUZA1";
+		width = 20;
+		height = 24;
+	}
+	116
+	{
+		title = "Pointy";
+		sprite = "PNTYA1";
+		width = 8;
+		height = 16;
+	}
+}
+
+bosses
+{
+	color = 8; // Dark_Gray
+	arrow = 1;
+	title = "Bosses";
+
+	200
+	{
+		title = "Egg Mobile";
+		sprite = "EGGMA1";
+		width = 24;
+		height = 76;
+		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;
+		flags1text = "[1] No origin-fling death";
+		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 = 0;
+	height = 0;
+	sprite = "UNKNA0";
+	sort = 1;
+	fixedsize = true;
+	blocking = 0;
+
+	700
+	{
+		title = "Water Ambience A (Large)";
+		sprite = "internal:ambiance";
+	}
+
+	701
+	{
+		title = "Water Ambience B (Large)";
+		sprite = "internal:ambiance";
+	}
+
+	702
+	{
+		title = "Water Ambience C (Medium)";
+		sprite = "internal:ambiance";
+	}
+
+	703
+	{
+		title = "Water Ambience D (Medium)";
+		sprite = "internal:ambiance";
+	}
+
+	704
+	{
+		title = "Water Ambience E (Small)";
+		sprite = "internal:ambiance";
+	}
+
+	705
+	{
+		title = "Water Ambience F (Small)";
+		sprite = "internal:ambiance";
+	}
+
+	706
+	{
+		title = "Water Ambience G (Extra Large)";
+		sprite = "internal:ambiance";
+	}
+
+	707
+	{
+		title = "Water Ambience H (Extra Large)";
+		sprite = "internal:ambiance";
+	}
+
+	708
+	{
+		title = "Disco Ambience";
+		sprite = "internal:ambiance";
+	}
+
+	709
+	{
+		title = "Volcano Ambience";
+		sprite = "internal:ambiance";
+	}
+
+	710
+	{
+		title = "Machine Ambience";
+		sprite = "internal:ambiance";
+	}
+
+	750
+	{
+		title = "Slope Vertex";
+		sprite = "internal:vertexslope";
+		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;
+		flags1text = "[1] Solid gold";
+		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 = "Lance-a-Bot 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 = "Glaregoyle";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+		flags4text = "[4] Slides when pushed";
+		flags8text = "[8] Not pushable";
+	}
+	1501
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Glaregoyle (Up)";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+		flags4text = "[4] Slides when pushed";
+		flags8text = "[8] Not pushable";
+	}
+	1502
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Glaregoyle (Down)";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+		flags4text = "[4] Slides when pushed";
+		flags8text = "[8] Not pushable";
+	}
+	1503
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Glaregoyle (Long)";
+		sprite = "BGARA1";
+		width = 16;
+		height = 40;
+		flags4text = "[4] Slides when pushed";
+		flags8text = "[8] Not pushable";
+	}
+	1504
+	{
+		title = "ATZ Target";
+		sprite = "RCRYB0";
+		width = 24;
+		height = 32;
+	}
+	1505
+	{
+		title = "Green Flame";
+		sprite = "CFLMA0E0";
+		width = 8;
+		height = 32;
+	}
+	1506
+	{
+		arrow = 1;
+		blocking = 2;
+		title = "Blue Gargoyle";
+		sprite = "BGARD1";
+		width = 16;
+		height = 40;
+		flags4text = "[4] Slides when pushed";
+		flags8text = "[8] Not pushable";
+	}
+}
+
+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";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/SRB2-22.cfg b/extras/conf/SRB2-22.cfg
deleted file mode 100644
index 5bc48211c60e8acfacbded90dd0b750d1745a984..0000000000000000000000000000000000000000
--- a/extras/conf/SRB2-22.cfg
+++ /dev/null
@@ -1,6521 +0,0 @@
-/*********************************************************\
-	Zone Builder Game Configuration
-	For Sonic Robo Blast 2 Version 2.2
-	Contributors (alphabetical):
-	* Foxboy
-	* FuriousFox
-	* 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,MAP33,MAP50,MAP60,MAPF0,MAPM0";
-	SKY2 = "MAPM7,MAPMB";
-	SKY4 = "MAP04,MAP06,MAP61,MAPF6,MAPM1";
-	SKY6 = "MAP05,MAP51,MAPMA";
-	SKY7 = "MAPM2,MAPM5";
-	SKY8 = "MAP07,MAP08,MAP09,MAP52,MAP62,MAPF1";
-	SKY10 = "MAP10,MAP12,MAP53,MAP63,MAPM3";
-	SKY11 = "MAP11,MAPF7";
-	SKY13 = "MAP13,MAP64";
-	SKY14 = "MAP14";
-	SKY15 = "MAP15,MAP54";
-	SKY17 = "MAP70";
-	SKY20 = "MAP32,MAP55,MAP65,MAPF2,MAPF5";
-	SKY21 = "MAPM4";
-	SKY22 = "MAP22,MAP23,MAP25,MAP26,MAP27,MAP56,MAP66,MAPF4,MAPM6";
-	SKY30 = "MAP30";
-	SKY31 = "MAP31";
-	SKY35 = "MAP42";
-	SKY40 = "MAP41,MAP71,MAPM9";
-	SKY55 = "MAPF3,MAPM8";
-	SKY68 = "MAPF8";
-	SKY99 = "MAP57,MAPZ0";
-	SKY159 = "MAP16";
-	SKY172 = "MAP40";
-	SKY300 = "MAP72";
-	SKY301 = "MAP73";
-}
-
-// 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;
-	Amy;
-	Fang;
-	Metalsonic;
-}
-
-// Gametypes
-gametypes
-{
-	-1 = "Single Player";
-	0 = "Co-op";
-	1 = "Competition";
-	2 = "Race";
-	3 = "Match";
-	4 = "Team Match";
-	5 = "Tag";
-	6 = "Hide and Seek";
-	7 = "CTF";
-}
-
-// 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";
-		}
-
-		462
-		{
-			title = "Stop Timer/Exit Stage in Record Attack";
-			prefix = "(462)";
-		}
-	}
-
-	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 = "Lance-a-Bot";
-			sprite = "CBFSA1";
-			width = 32;
-			height = 72;
-		}
-		1113
-		{
-			title = "Suspicious Lance-a-Bot Statue";
-			sprite = "CBBSA1";
-			width = 32;
-			height = 72;
-		}
-		119
-		{
-			title = "Egg Guard";
-			sprite = "ESHIA1";
-			width = 16;
-			height = 48;
-			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 = "MNUSA0";
-			width = 24;
-			height = 32;
-		}
-		134
-		{
-			title = "Canarivore";
-			sprite = "CANAA0";
-			width = 12;
-			height = 80;
-			hangs = 1;
-		}
-		123
-		{
-			title = "Unidus";
-			sprite = "UNIDA1";
-			width = 18;
-			height = 36;
-		}
-		135
-		{
-			title = "Pterabyte Spawner";
-			sprite = "PTERA2A8";
-			width = 16;
-			height = 16;
-			parametertext = "No. Pterabytes";
-		}
-		136
-		{
-			title = "Pyre Fly";
-			sprite = "PYREA0";
-			width = 24;
-			height = 34;
-			flags8text = "[8] Start on fire";
-		}
-		137
-		{
-			title = "Dragonbomber";
-			sprite = "DRABA1";
-			width = 28;
-			height = 48;
-		}
-		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 = "Buggle";
-			sprite = "BBUZA1";
-			width = 20;
-			height = 24;
-		}
-		116
-		{
-			title = "Pointy";
-			sprite = "PNTYA1";
-			width = 8;
-			height = 16;
-		}
-	}
-
-	bosses
-	{
-		color = 8; // Dark_Gray
-		arrow = 1;
-		title = "Bosses";
-
-		200
-		{
-			title = "Egg Mobile";
-			sprite = "EGGMA1";
-			width = 24;
-			height = 76;
-			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;
-			flags1text = "[1] No origin-fling death";
-			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;
-			flags1text = "[1] Solid gold";
-			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 = "Lance-a-Bot 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 = "Glaregoyle";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-			flags4text = "[4] Slides when pushed";
-			flags8text = "[8] Not pushable";
-		}
-		1501
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Up)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-			flags4text = "[4] Slides when pushed";
-			flags8text = "[8] Not pushable";
-		}
-		1502
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Down)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-			flags4text = "[4] Slides when pushed";
-			flags8text = "[8] Not pushable";
-		}
-		1503
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Long)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-			flags4text = "[4] Slides when pushed";
-			flags8text = "[8] Not pushable";
-		}
-		1504
-		{
-			title = "ATZ Target";
-			sprite = "RCRYB0";
-			width = 24;
-			height = 32;
-		}
-		1505
-		{
-			title = "Green Flame";
-			sprite = "CFLMA0E0";
-			width = 8;
-			height = 32;
-		}
-		1506
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Blue Gargoyle";
-			sprite = "BGARD1";
-			width = 16;
-			height = 40;
-			flags4text = "[4] Slides when pushed";
-			flags8text = "[8] Not pushable";
-		}
-	}
-
-	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/extras/conf/SRB2_22Doom.cfg b/extras/conf/SRB2_22Doom.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..65e49d3871be87df0c3eb20831543a3304191d1d
--- /dev/null
+++ b/extras/conf/SRB2_22Doom.cfg
@@ -0,0 +1,38 @@
+/************************************************************************\
+	Zone Builder Game Configuration for Sonic Robo Blast 2 Version 2.2
+\************************************************************************/
+
+// This is required to prevent accidental use of a different configuration
+type = "Doom Builder 2 Game Configuration";
+
+// This is the title to show for this game
+game = "Sonic Robo Blast 2 - 2.2 (Doom format)";
+
+// This is the simplified game engine/sourceport name
+engine = "zdoom";
+
+// Settings common to all games and all map formats
+include("Includes\\SRB222_common.cfg", "common");
+
+// Settings common to Doom map format
+include("Includes\\SRB222_common.cfg", "mapformat_doom");
+
+include("Includes\\Game_SRB222.cfg");
+
+// Script lumps detection
+scriptlumpnames
+{
+	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
+}
+
+// THING TYPES
+thingtypes
+{
+	include("Includes\\SRB222_things.cfg");
+}
+
+//Default things filters
+thingsfilters
+{
+	include("Includes\\SRB222_misc.cfg", "thingsfilters");
+}
\ No newline at end of file
diff --git a/extras/conf/SRB2_22UDMF.cfg b/extras/conf/SRB2_22UDMF.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..52104ed090b766914ee69d350c16799b1272d0d2
--- /dev/null
+++ b/extras/conf/SRB2_22UDMF.cfg
@@ -0,0 +1,47 @@
+/************************************************************************\
+	Zone Builder Game Configuration for Sonic Robo Blast 2 Version 2.2
+\************************************************************************/
+
+// This is required to prevent accidental use of a different configuration
+type = "Doom Builder 2 Game Configuration";
+
+// This is the title to show for this game
+game = "Sonic Robo Blast 2 - 2.2 (UDMF)";
+
+// This is the simplified game engine/sourceport name
+engine = "zdoom";
+
+// Settings common to all games and all map formats
+include("Includes\\SRB222_common.cfg", "common");
+
+// Settings common to Doom map format
+include("Includes\\SRB222_common.cfg", "mapformat_udmf");
+
+include("Includes\\Game_SRB222.cfg");
+
+// Script lumps detection
+scriptlumpnames
+{
+	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
+}
+
+// THING TYPES
+thingtypes
+{
+	include("Includes\\SRB222_things.cfg");
+}
+
+//Default things filters
+thingsfilters
+{
+	include("Includes\\SRB222_misc.cfg", "thingsfilters");
+}
+
+// ENUMERATIONS
+// Each engine has its own additional thing types
+// These are enumerated lists for linedef types and UDMF fields.
+enums
+{
+	// Basic game enums
+	include("Includes\\SRB222_misc.cfg", "enums");
+}
\ No newline at end of file
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 6520a1aa1bf2da2bc8d5842108d3cc342cf37316..4448aaae042732fb6a52cf99236486b2e5656019 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -611,11 +611,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
 
 	rsp->health = LONG(players[i].mo->health);
 	rsp->angle = (angle_t)LONG(players[i].mo->angle);
-#ifdef ROTSPRITE
 	rsp->rollangle = (angle_t)LONG(players[i].mo->rollangle);
-#else
-	rsp->rollangle = 0;
-#endif
 	rsp->x = LONG(players[i].mo->x);
 	rsp->y = LONG(players[i].mo->y);
 	rsp->z = LONG(players[i].mo->z);
@@ -766,9 +762,7 @@ static void resynch_read_player(resynch_pak *rsp)
 	//At this point, the player should have a body, whether they were respawned or not.
 	P_UnsetThingPosition(players[i].mo);
 	players[i].mo->angle = (angle_t)LONG(rsp->angle);
-#ifdef ROTSPRITE
 	players[i].mo->rollangle = (angle_t)LONG(rsp->rollangle);
-#endif
 	players[i].mo->eflags = (UINT16)SHORT(rsp->eflags);
 	players[i].mo->flags = LONG(rsp->flags);
 	players[i].mo->flags2 = LONG(rsp->flags2);
@@ -1025,7 +1019,7 @@ static void SV_SendResynch(INT32 node)
 		netbuffer->packettype = PT_RESYNCHEND;
 
 		netbuffer->u.resynchend.randomseed = P_GetRandSeed();
-		if (gametype == GT_CTF)
+		if (gametyperules & GTR_TEAMFLAGS)
 			resynch_write_ctf(&netbuffer->u.resynchend);
 		resynch_write_others(&netbuffer->u.resynchend);
 
@@ -2111,10 +2105,10 @@ static void CL_ConnectToServer(boolean viams)
 
 	if (i != -1)
 	{
-		UINT8 num = serverlist[i].info.gametype;
+		UINT16 num = serverlist[i].info.gametype;
 		const char *gametypestr = NULL;
 		CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername);
-		if (num < NUMGAMETYPES)
+		if (num < gametypecount)
 			gametypestr = Gametype_Names[num];
 		if (gametypestr)
 			CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr);
@@ -2430,7 +2424,7 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
 		}
 	}
 
-	if (gametype == GT_CTF)
+	if (gametyperules & GTR_TEAMFLAGS)
 		P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you!
 
 	// If in a special stage, redistribute the player's spheres across
@@ -2486,6 +2480,17 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
 	(void)reason;
 #endif
 
+	// don't look through someone's view who isn't there
+	if (playernum == displayplayer)
+	{
+#ifdef HAVE_BLUA
+		// Call ViewpointSwitch hooks here.
+		// The viewpoint was forcibly changed.
+		LUAh_ViewpointSwitch(&players[consoleplayer], &players[displayplayer], true);
+#endif
+		displayplayer = consoleplayer;
+	}
+
 	// Reset player data
 	CL_ClearPlayer(playernum);
 
@@ -2503,16 +2508,13 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
 		RemoveAdminPlayer(playernum); // don't stay admin after you're gone
 	}
 
-	if (playernum == displayplayer)
-		displayplayer = consoleplayer; // don't look through someone's view who isn't there
-
 #ifdef HAVE_BLUA
 	LUA_InvalidatePlayer(&players[playernum]);
 #endif
 
 	if (G_TagGametype()) //Check if you still have a game. Location flexible. =P
 		P_CheckSurvivors();
-	else if (gametype == GT_RACE || gametype == GT_COMPETITION)
+	else if (gametyperules & GTR_RACE)
 		P_CheckRacers();
 }
 
@@ -3461,7 +3463,7 @@ void SV_StartSinglePlayerServer(void)
 	server = true;
 	netgame = false;
 	multiplayer = false;
-	gametype = GT_COOP;
+	G_SetGametype(GT_COOP);
 
 	// no more tic the game with this settings!
 	SV_StopServer();
@@ -3740,7 +3742,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			if (client)
 			{
 				maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
-				gametype = netbuffer->u.servercfg.gametype;
+				G_SetGametype(netbuffer->u.servercfg.gametype);
 				modifiedgame = netbuffer->u.servercfg.modifiedgame;
 				for (j = 0; j < MAXPLAYERS; j++)
 					adminplayers[j] = netbuffer->u.servercfg.adminplayers[j];
@@ -4124,7 +4126,7 @@ static void HandlePacketFromPlayer(SINT8 node)
 
 			P_SetRandSeed(netbuffer->u.resynchend.randomseed);
 
-			if (gametype == GT_CTF)
+			if (gametyperules & GTR_TEAMFLAGS)
 				resynch_read_ctf(&netbuffer->u.resynchend);
 			resynch_read_others(&netbuffer->u.resynchend);
 
diff --git a/src/d_main.c b/src/d_main.c
index c4b0d7117e77873349541489cc22f75af80658f7..e25ef998e040bc51dda58ff80fc84a1c9be4a9f2 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -762,7 +762,7 @@ void D_StartTitle(void)
 
 	gameaction = ga_nothing;
 	displayplayer = consoleplayer = 0;
-	gametype = GT_COOP;
+	G_SetGametype(GT_COOP);
 	paused = false;
 	advancedemo = false;
 	F_InitMenuPresValues();
@@ -1419,14 +1419,14 @@ void D_SRB2Main(void)
 			if (newgametype == -1) // reached end of the list with no match
 			{
 				j = atoi(sgametype); // assume they gave us a gametype number, which is okay too
-				if (j >= 0 && j < NUMGAMETYPES)
+				if (j >= 0 && j < gametypecount)
 					newgametype = (INT16)j;
 			}
 
 			if (newgametype != -1)
 			{
 				j = gametype;
-				gametype = newgametype;
+				G_SetGametype(newgametype);
 				D_GameTypeChanged(j);
 			}
 		}
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 262d0d1bc4d592a2acdde70f7a1985fed3b1433f..4e14ca25fea0b0d5318af8bbff3fb4bb5105b9a2 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -327,6 +327,10 @@ consvar_t cv_numlaps = {"numlaps", "4", CV_NETVAR|CV_CALL|CV_NOINIT, numlaps_con
 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};
 
+// Point and time limits for every gametype
+INT32 pointlimits[NUMGAMETYPES];
+INT32 timelimits[NUMGAMETYPES];
+
 // log elemental hazards -- not a netvar, is local to current player
 consvar_t cv_hazardlog = {"hazardlog", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -381,6 +385,9 @@ char timedemo_csv_id[256];
 boolean timedemo_quit;
 
 INT16 gametype = GT_COOP;
+UINT32 gametyperules = 0;
+INT16 gametypecount = (GT_CTF + 1);
+
 boolean splitscreen = false;
 boolean circuitmap = false;
 INT32 adminplayers[MAXPLAYERS];
@@ -1126,7 +1133,7 @@ UINT8 CanChangeSkin(INT32 playernum)
 			return true;
 
 		// Can change skin during initial countdown.
-		if ((gametype == GT_RACE || gametype == GT_COMPETITION) && leveltime < 4*TICRATE)
+		if ((gametyperules & GTR_RACE) && leveltime < 4*TICRATE)
 			return true;
 
 		if (G_TagGametype())
@@ -1942,7 +1949,7 @@ static void Command_Map_f(void)
 			if (isdigit(gametypename[0]))
 			{
 				d = atoi(gametypename);
-				if (d >= 0 && d < NUMGAMETYPES)
+				if (d >= 0 && d < gametypecount)
 					newgametype = d;
 				else
 				{
@@ -1950,7 +1957,7 @@ static void Command_Map_f(void)
 							"Gametype number %d is out of range. Use a number between"
 							" 0 and %d inclusive. ...Or just use the name. :v\n",
 							d,
-							NUMGAMETYPES-1);
+							gametypecount-1);
 					Z_Free(realmapname);
 					Z_Free(mapname);
 					return;
@@ -2069,8 +2076,9 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 
 	lastgametype = gametype;
 	gametype = READUINT8(*cp);
+	G_SetGametype(gametype); // I fear putting that macro as an argument
 
-	if (gametype < 0 || gametype >= NUMGAMETYPES)
+	if (gametype < 0 || gametype >= gametypecount)
 		gametype = lastgametype;
 	else if (gametype != lastgametype)
 		D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype
@@ -2413,7 +2421,7 @@ static void Command_Teamchange_f(void)
 	}
 
 	//additional check for hide and seek. Don't allow change of status after hidetime ends.
-	if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
+	if ((gametyperules & GTR_HIDEFROZEN) && leveltime >= (hidetime * TICRATE))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
 		return;
@@ -2510,7 +2518,7 @@ static void Command_Teamchange2_f(void)
 	}
 
 	//additional check for hide and seek. Don't allow change of status after hidetime ends.
-	if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
+	if ((gametyperules & GTR_HIDEFROZEN) && leveltime >= (hidetime * TICRATE))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
 		return;
@@ -2639,7 +2647,7 @@ static void Command_ServerTeamChange_f(void)
 	}
 
 	//additional check for hide and seek. Don't allow change of status after hidetime ends.
-	if (gametype == GT_HIDEANDSEEK && leveltime >= (hidetime * TICRATE))
+	if ((gametyperules & GTR_HIDEFROZEN) && leveltime >= (hidetime * TICRATE))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("Hiding time expired; no Hide and Seek status changes allowed!\n"));
 		return;
@@ -2728,6 +2736,16 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
+#ifdef HAVE_BLUA
+	// Don't switch team, just go away, please, go awaayyyy, aaauuauugghhhghgh
+	if (!LUAh_TeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled))
+		return;
+#endif
+
+	//no status changes after hidetime
+	if ((gametyperules & GTR_HIDEFROZEN) && (leveltime >= (hidetime * TICRATE)))
+		error = true;
+
 	//Make sure that the right team number is sent. Keep in mind that normal clients cannot change to certain teams in certain gametypes.
 	switch (gametype)
 	{
@@ -2882,7 +2900,15 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 
 	//reset view if you are changed, or viewing someone who was changed.
 	if (playernum == consoleplayer || displayplayer == playernum)
+	{
+#ifdef HAVE_BLUA
+		// Call ViewpointSwitch hooks here.
+		// The viewpoint was forcibly changed.
+		if (displayplayer != consoleplayer) // You're already viewing yourself. No big deal.
+			LUAh_ViewpointSwitch(&players[playernum], &players[displayplayer], true);
+#endif
 		displayplayer = consoleplayer;
+	}
 
 	if (G_GametypeHasTeams())
 	{
@@ -3618,7 +3644,7 @@ static void Command_ShowGametype_f(void)
 	}
 
 	// get name string for current gametype
-	if (gametype >= 0 && gametype < NUMGAMETYPES)
+	if (gametype >= 0 && gametype < gametypecount)
 		gametypestr = Gametype_Names[gametype];
 
 	if (gametypestr)
@@ -3680,7 +3706,7 @@ void ItemFinder_OnChange(void)
 static void PointLimit_OnChange(void)
 {
 	// Don't allow pointlimit in Single Player/Co-Op/Race!
-	if (server && Playing() && G_PlatformGametype())
+	if (server && Playing() && !(gametyperules & GTR_POINTLIMIT))
 	{
 		if (cv_pointlimit.value)
 			CV_StealthSetValue(&cv_pointlimit, 0);
@@ -3843,7 +3869,7 @@ UINT32 timelimitintics = 0;
 static void TimeLimit_OnChange(void)
 {
 	// Don't allow timelimit in Single Player/Co-Op/Race!
-	if (server && Playing() && cv_timelimit.value != 0 && G_PlatformGametype())
+	if (server && Playing() && cv_timelimit.value != 0 && !(gametyperules & GTR_TIMELIMIT))
 	{
 		CV_SetValue(&cv_timelimit, 0);
 		return;
@@ -3879,9 +3905,9 @@ void D_GameTypeChanged(INT32 lastgametype)
 	{
 		const char *oldgt = NULL, *newgt = NULL;
 
-		if (lastgametype >= 0 && lastgametype < NUMGAMETYPES)
+		if (lastgametype >= 0 && lastgametype < gametypecount)
 			oldgt = Gametype_Names[lastgametype];
-		if (gametype >= 0 && lastgametype < NUMGAMETYPES)
+		if (gametype >= 0 && lastgametype < gametypecount)
 			newgt = Gametype_Names[gametype];
 
 		if (oldgt && newgt)
@@ -3935,11 +3961,20 @@ void D_GameTypeChanged(INT32 lastgametype)
 				if (!cv_itemrespawntime.changed)
 					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
 				break;
+			default:
+				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
+				{
+					CV_SetValue(&cv_timelimit, timelimits[gametype]);
+					CV_SetValue(&cv_pointlimit, pointlimits[gametype]);
+				}
+				if (!cv_itemrespawntime.changed)
+					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
+				break;
 		}
 	}
 	else if (!multiplayer && !netgame)
 	{
-		gametype = GT_COOP;
+		G_SetGametype(GT_COOP);
 		// These shouldn't matter anymore
 		//CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue);
 		//CV_SetValue(&cv_itemrespawn, 0);
@@ -3948,7 +3983,7 @@ void D_GameTypeChanged(INT32 lastgametype)
 	// reset timelimit and pointlimit in race/coop, prevent stupid cheats
 	if (server)
 	{
-		if (G_PlatformGametype())
+		if (!(gametyperules & GTR_POINTLIMIT))
 		{
 			if (cv_timelimit.value)
 				CV_SetValue(&cv_timelimit, 0);
@@ -3966,6 +4001,7 @@ void D_GameTypeChanged(INT32 lastgametype)
 
 	// When swapping to a gametype that supports spectators,
 	// make everyone a spectator initially.
+	// Averted with GTR_NOSPECTATORSPAWN.
 	if (!splitscreen && (G_GametypeHasSpectators()))
 	{
 		INT32 i;
@@ -3973,7 +4009,7 @@ void D_GameTypeChanged(INT32 lastgametype)
 			if (playeringame[i])
 			{
 				players[i].ctfteam = 0;
-				players[i].spectator = true;
+				players[i].spectator = (gametyperules & GTR_NOSPECTATORSPAWN) ? false : true;
 			}
 	}
 
diff --git a/src/dehacked.c b/src/dehacked.c
index 0d1881c25c6a957173750209135b4a1eab2b0819..b77939c2a678daf50696969910e2a95dc0e9312b 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -22,6 +22,7 @@
 #include "m_menu.h"
 #include "m_misc.h"
 #include "f_finale.h"
+#include "y_inter.h"
 #include "dehacked.h"
 #include "st_stuff.h"
 #include "i_system.h"
@@ -76,6 +77,7 @@ static UINT16 get_mus(const char *word, UINT8 dehacked_mode);
 static hudnum_t get_huditem(const char *word);
 static menutype_t get_menutype(const char *word);
 #ifndef HAVE_BLUA
+static INT16 get_gametype(const char *word);
 static powertype_t get_power(const char *word);
 #endif
 
@@ -590,6 +592,16 @@ static void readfreeslots(MYFILE *f)
 				} else
 					CONS_Alert(CONS_WARNING, "Ran out of free SPR2 slots!\n");
 			}
+			else if (fastcmp(type, "TOL"))
+			{
+				if (lastcustomtol > 31)
+					CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n");
+				else
+				{
+					G_AddTOL((1<<lastcustomtol), word);
+					lastcustomtol++;
+				}
+			}
 			else
 				deh_warning("Freeslots: unknown enum class '%s' for '%s_%s'", type, type, word);
 		}
@@ -863,14 +875,12 @@ static void readspriteframe(MYFILE *f, spriteinfo_t *sprinfo, UINT8 frame)
 			strupr(word);
 			value = atoi(word2); // used for numerical settings
 
-#ifdef ROTSPRITE
 			if (fastcmp(word, "XPIVOT"))
 				sprinfo->pivot[frame].x = value;
 			else if (fastcmp(word, "YPIVOT"))
 				sprinfo->pivot[frame].y = value;
 			else if (fastcmp(word, "ROTAXIS"))
 				sprinfo->pivot[frame].rotaxis = value;
-#endif
 			else
 			{
 				f->curpos = lastline;
@@ -1095,10 +1105,10 @@ static void readsprite2(MYFILE *f, INT32 num)
 	Z_Free(s);
 }
 
-static const struct {
-	const char *name;
-	const UINT16 flag;
-} TYPEOFLEVEL[] = {
+INT32 numtolinfo = NUMBASETOL;
+UINT32 lastcustomtol = 13;
+
+tolinfo_t TYPEOFLEVEL[NUMMAXTOL] = {
 	{"SOLO",TOL_SP},
 	{"SP",TOL_SP},
 	{"SINGLEPLAYER",TOL_SP},
@@ -1114,8 +1124,6 @@ static const struct {
 	{"TAG",TOL_TAG},
 	{"CTF",TOL_CTF},
 
-	{"CUSTOM",TOL_CUSTOM},
-
 	{"2D",TOL_2D},
 	{"MARIO",TOL_MARIO},
 	{"NIGHTS",TOL_NIGHTS},
@@ -1128,6 +1136,216 @@ static const struct {
 	{NULL, 0}
 };
 
+// copypasted from readPlayer :sleep:
+static const char *const GAMETYPERULE_LIST[];
+static void readgametype(MYFILE *f, char *gtname)
+{
+	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char *word;
+	char *word2, *word2lwr = NULL;
+	char *tmp;
+	INT32 i, j;
+
+	INT16 newgtidx = 0;
+	UINT32 newgtrules = 0;
+	UINT32 newgttol = 0;
+	INT32 newgtpointlimit = 0;
+	INT32 newgttimelimit = 0;
+	UINT8 newgtleftcolor = 0;
+	UINT8 newgtrightcolor = 0;
+	INT16 newgtrankingstype = -1;
+	int newgtinttype = 0;
+	char gtdescription[441];
+	char gtconst[MAXLINELEN];
+
+	// Empty strings.
+	gtdescription[0] = '\0';
+	gtconst[0] = '\0';
+
+	do
+	{
+		if (myfgets(s, MAXLINELEN, f))
+		{
+			if (s[0] == '\n')
+				break;
+
+			word = strtok(s, " ");
+			if (word)
+				strupr(word);
+			else
+				break;
+
+			if (fastcmp(word, "DESCRIPTION"))
+			{
+				char *descr = NULL;
+
+				for (i = 0; i < MAXLINELEN-3; i++)
+				{
+					if (s[i] == '=')
+					{
+						descr = &s[i+2];
+						break;
+					}
+				}
+				if (descr)
+				{
+					strcpy(gtdescription, descr);
+					strcat(gtdescription, myhashfgets(descr, sizeof (gtdescription), f));
+				}
+				else
+					strcpy(gtdescription, "");
+
+				// For some reason, cutting the string did not work above. Most likely due to strcpy or strcat...
+				// It works down here, though.
+				{
+					INT32 numline = 0;
+					for (i = 0; i < MAXLINELEN-1; i++)
+					{
+						if (numline < 20 && gtdescription[i] == '\n')
+							numline++;
+
+						if (numline >= 20 || gtdescription[i] == '\0' || gtdescription[i] == '#')
+							break;
+					}
+				}
+				gtdescription[strlen(gtdescription)-1] = '\0';
+				gtdescription[i] = '\0';
+				continue;
+			}
+
+			word2 = strtok(NULL, " = ");
+			if (word2)
+			{
+				if (!word2lwr)
+					word2lwr = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+				strcpy(word2lwr, word2);
+				strupr(word2);
+			}
+			else
+				break;
+
+			if (word2[strlen(word2)-1] == '\n')
+				word2[strlen(word2)-1] = '\0';
+			i = atoi(word2);
+
+			// Game type rules
+			if (fastcmp(word, "RULES"))
+			{
+				// GTR_
+				newgtrules = (UINT32)get_number(word2);
+			}
+			// Identifier
+			else if (fastcmp(word, "IDENTIFIER"))
+			{
+				// GT_
+				strncpy(gtconst, word2, MAXLINELEN);
+			}
+			// Point and time limits
+			else if (fastcmp(word, "DEFAULTPOINTLIMIT"))
+				newgtpointlimit = (INT32)i;
+			else if (fastcmp(word, "DEFAULTTIMELIMIT"))
+				newgttimelimit = (INT32)i;
+			// Level platter
+			else if (fastcmp(word, "HEADERCOLOR") || fastcmp(word, "HEADERCOLOUR"))
+				newgtleftcolor = newgtrightcolor = (UINT8)get_number(word2);
+			else if (fastcmp(word, "HEADERLEFTCOLOR") || fastcmp(word, "HEADERLEFTCOLOUR"))
+				newgtleftcolor = (UINT8)get_number(word2);
+			else if (fastcmp(word, "HEADERRIGHTCOLOR") || fastcmp(word, "HEADERRIGHTCOLOUR"))
+				newgtrightcolor = (UINT8)get_number(word2);
+			// Rankings type
+			else if (fastcmp(word, "RANKINGTYPE"))
+			{
+				// Case insensitive
+				newgtrankingstype = (int)get_number(word2);
+			}
+			// Intermission type
+			else if (fastcmp(word, "INTERMISSIONTYPE"))
+			{
+				// Case sensitive
+				newgtinttype = (int)get_number(word2lwr);
+			}
+			// Type of level
+			else if (fastcmp(word, "TYPEOFLEVEL"))
+			{
+				if (i) // it's just a number
+					newgttol = (UINT32)i;
+				else
+				{
+					UINT16 tol = 0;
+					tmp = strtok(word2,",");
+					do {
+						for (i = 0; TYPEOFLEVEL[i].name; i++)
+							if (fasticmp(tmp, TYPEOFLEVEL[i].name))
+								break;
+						if (!TYPEOFLEVEL[i].name)
+							deh_warning("readgametype %s: unknown typeoflevel flag %s\n", gtname, tmp);
+						tol |= TYPEOFLEVEL[i].flag;
+					} while((tmp = strtok(NULL,",")) != NULL);
+					newgttol = tol;
+				}
+			}
+			// The SOC probably provided gametype rules as words,
+			// instead of using the RULES keyword.
+			// Like for example "NOSPECTATORSPAWN = TRUE".
+			// This is completely valid, and looks better anyway.
+			else
+			{
+				UINT32 wordgt = 0;
+				for (j = 0; GAMETYPERULE_LIST[j]; j++)
+					if (fastcmp(word, GAMETYPERULE_LIST[j])) {
+						if (!j) // GTR_CAMPAIGN
+							wordgt |= 1;
+						else
+							wordgt |= (1<<j);
+						if (i || word2[0] == 'T' || word2[0] == 'Y')
+							newgtrules |= wordgt;
+						break;
+					}
+				if (!wordgt)
+					deh_warning("readgametype %s: unknown word '%s'", gtname, word);
+			}
+		}
+	} while (!myfeof(f)); // finish when the line is empty
+
+	// Free strings.
+	Z_Free(s);
+	if (word2lwr)
+		Z_Free(word2lwr);
+
+	// Ran out of gametype slots
+	if (gametypecount == NUMGAMETYPEFREESLOTS)
+	{
+		CONS_Alert(CONS_WARNING, "Ran out of free gametype slots!\n");
+		return;
+	}
+
+	// Add the new gametype
+	newgtidx = G_AddGametype(newgtrules);
+	G_AddGametypeTOL(newgtidx, newgttol);
+	G_SetGametypeDescription(newgtidx, gtdescription, newgtleftcolor, newgtrightcolor);
+
+	// Not covered by G_AddGametype alone.
+	if (newgtrankingstype == -1)
+		newgtrankingstype = newgtidx;
+	gametyperankings[newgtidx] = newgtrankingstype;
+	intermissiontypes[newgtidx] = newgtinttype;
+	pointlimits[newgtidx] = newgtpointlimit;
+	timelimits[newgtidx] = newgttimelimit;
+
+	// Write the new gametype name.
+	Gametype_Names[newgtidx] = Z_StrDup((const char *)gtname);
+
+	// Write the constant name.
+	if (gtconst[0] == '\0')
+		strncpy(gtconst, gtname, MAXLINELEN);
+	G_AddGametypeConstant(newgtidx, (const char *)gtconst);
+
+	// Update gametype_cons_t accordingly.
+	G_UpdateGametypeSelections();
+
+	CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]);
+}
+
 static const struct {
 	const char *name;
 	const mobjtype_t type;
@@ -1395,7 +1613,7 @@ static void readlevelheader(MYFILE *f, INT32 num)
 			else if (fastcmp(word, "TYPEOFLEVEL"))
 			{
 				if (i) // it's just a number
-					mapheaderinfo[num-1]->typeoflevel = (UINT16)i;
+					mapheaderinfo[num-1]->typeoflevel = (UINT32)i;
 				else
 				{
 					UINT16 tol = 0;
@@ -2690,11 +2908,9 @@ static actionpointer_t actionpointers[] =
 	{{A_SpawnObjectRelative},    "A_SPAWNOBJECTRELATIVE"},
 	{{A_ChangeAngleRelative},    "A_CHANGEANGLERELATIVE"},
 	{{A_ChangeAngleAbsolute},    "A_CHANGEANGLEABSOLUTE"},
-#ifdef ROTSPRITE
 	{{A_RollAngle},              "A_ROLLANGLE"},
 	{{A_ChangeRollAngleRelative},"A_CHANGEROLLANGLERELATIVE"},
 	{{A_ChangeRollAngleAbsolute},"A_CHANGEROLLANGLEABSOLUTE"},
-#endif
 	{{A_PlaySound},              "A_PLAYSOUND"},
 	{{A_FindTarget},             "A_FINDTARGET"},
 	{{A_FindTracer},             "A_FINDTRACER"},
@@ -4172,6 +4388,7 @@ static void ignorelines(MYFILE *f)
 static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
+	char textline[MAXLINELEN];
 	char *word;
 	char *word2;
 	INT32 i;
@@ -4192,6 +4409,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 		char *traverse;
 
 		myfgets(s, MAXLINELEN, f);
+		memcpy(textline, s, MAXLINELEN);
 		if (s[0] == '\n' || s[0] == '#')
 			continue;
 
@@ -4380,6 +4598,36 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 						ignorelines(f);
 					}
 				}
+				else if (fastcmp(word, "GAMETYPE"))
+				{
+					// Get the gametype name from textline
+					// instead of word2, so that gametype names
+					// aren't allcaps
+					INT32 c;
+					for (c = 0; c < MAXLINELEN; c++)
+					{
+						if (textline[c] == '\0')
+							break;
+						if (textline[c] == ' ')
+						{
+							char *gtname = (textline+c+1);
+							if (gtname)
+							{
+								// remove funny characters
+								INT32 j;
+								for (j = 0; j < (MAXLINELEN - c); j++)
+								{
+									if (gtname[j] == '\0')
+										break;
+									if (gtname[j] < 32)
+										gtname[j] = '\0';
+								}
+								readgametype(f, gtname);
+							}
+							break;
+						}
+					}
+				}
 				else if (fastcmp(word, "CUTSCENE"))
 				{
 					if (i > 0 && i < 129)
@@ -8650,6 +8898,39 @@ static const char *const PLAYERFLAG_LIST[] = {
 	NULL // stop loop here.
 };
 
+static const char *const GAMETYPERULE_LIST[] = {
+	"CAMPAIGN",
+	"RINGSLINGER",
+	"SPECTATORS",
+	"FRIENDLYFIRE",
+	"LIVES",
+	"TEAMS",
+	"RACE",
+	"TAG",
+	"POINTLIMIT",
+	"TIMELIMIT",
+	"HIDETIME",
+	"HIDEFROZEN",
+	"BLINDFOLDED",
+	"FIRSTPERSON",
+	"MATCHEMERALDS",
+	"TEAMFLAGS",
+	"PITYSHIELD",
+	"DEATHPENALTY",
+	"NOSPECTATORSPAWN",
+	"DEATHMATCHSTARTS",
+	"SPECIALSTAGES",
+	"EMERALDTOKENS",
+	"EMERALDHUNT",
+	"SPAWNENEMIES",
+	"ALLOWEXIT",
+	"NOTITLECARD",
+	"OVERTIME",
+	"HURTMESSAGES",
+	"SPAWNINVUL",
+	NULL
+};
+
 #ifdef HAVE_BLUA
 // Linedef flags
 static const char *const ML_LIST[16] = {
@@ -9065,21 +9346,6 @@ struct {
 	{"tr_trans90",tr_trans90},
 	{"NUMTRANSMAPS",NUMTRANSMAPS},
 
-	// Type of levels
-	{"TOL_SP",TOL_SP},
-	{"TOL_COOP",TOL_COOP},
-	{"TOL_COMPETITION",TOL_COMPETITION},
-	{"TOL_RACE",TOL_RACE},
-	{"TOL_MATCH",TOL_MATCH},
-	{"TOL_TAG",TOL_TAG},
-	{"TOL_CTF",TOL_CTF},
-	{"TOL_CUSTOM",TOL_CUSTOM},
-	{"TOL_2D",TOL_2D},
-	{"TOL_MARIO",TOL_MARIO},
-	{"TOL_NIGHTS",TOL_NIGHTS},
-	{"TOL_ERZ3",TOL_ERZ3},
-	{"TOL_XMAS",TOL_XMAS},
-
 	// Level flags
 	{"LF_SCRIPTISFILE",LF_SCRIPTISFILE},
 	{"LF_SPEEDMUSIC",LF_SPEEDMUSIC},
@@ -9262,15 +9528,16 @@ struct {
 	{"DMG_CANHURTSELF",DMG_CANHURTSELF},
 	{"DMG_DEATHMASK",DMG_DEATHMASK},
 
-	// Gametypes, for use with global var "gametype"
-	{"GT_COOP",GT_COOP},
-	{"GT_COMPETITION",GT_COMPETITION},
-	{"GT_RACE",GT_RACE},
-	{"GT_MATCH",GT_MATCH},
-	{"GT_TEAMMATCH",GT_TEAMMATCH},
-	{"GT_TAG",GT_TAG},
-	{"GT_HIDEANDSEEK",GT_HIDEANDSEEK},
-	{"GT_CTF",GT_CTF},
+	// Intermission types
+	{"int_none",int_none},
+	{"int_coop",int_coop},
+	{"int_match",int_match},
+	{"int_teammatch",int_teammatch},
+	//{"int_tag",int_tag},
+	{"int_ctf",int_ctf},
+	{"int_spec",int_spec},
+	{"int_race",int_race},
+	{"int_comp",int_comp},
 
 	// Jingles (jingletype_t)
 	{"JT_NONE",JT_NONE},
@@ -9443,12 +9710,10 @@ struct {
 	{"DI_SOUTHEAST",DI_SOUTHEAST},
 	{"NUMDIRS",NUMDIRS},
 
-#ifdef ROTSPRITE
 	// Sprite rotation axis (rotaxis_t)
 	{"ROTAXIS_X",ROTAXIS_X},
 	{"ROTAXIS_Y",ROTAXIS_Y},
 	{"ROTAXIS_Z",ROTAXIS_Z},
-#endif
 
 	// Buttons (ticcmd_t)
 	{"BT_WEAPONMASK",BT_WEAPONMASK}, //our first four bits.
@@ -9563,7 +9828,7 @@ struct {
 };
 
 static mobjtype_t get_mobjtype(const char *word)
-{ // Returns the vlaue of MT_ enumerations
+{ // Returns the value of MT_ enumerations
 	mobjtype_t i;
 	if (*word >= '0' && *word <= '9')
 		return atoi(word);
@@ -9715,8 +9980,22 @@ static menutype_t get_menutype(const char *word)
 }
 
 #ifndef HAVE_BLUA
+static INT16 get_gametype(const char *word)
+{ // Returns the value of GT_ enumerations
+	INT16 i;
+	if (*word >= '0' && *word <= '9')
+		return atoi(word);
+	if (fastncmp("GT_",word,3))
+		word += 3; // take off the GT_
+	for (i = 0; i < NUMGAMETYPES; i++)
+		if (fastcmp(word, Gametype_ConstantNames[i]+3))
+			return i;
+	deh_warning("Couldn't find gametype named 'GT_%s'",word);
+	return GT_COOP;
+}
+
 static powertype_t get_power(const char *word)
-{ // Returns the vlaue of pw_ enumerations
+{ // Returns the value of pw_ enumerations
 	powertype_t i;
 	if (*word >= '0' && *word <= '9')
 		return atoi(word);
@@ -9910,11 +10189,42 @@ static fixed_t find_const(const char **rword)
 		free(word);
 		return r;
 	}
-	else if (fastncmp("MN_",word,4)) {
+	else if (fastncmp("MN_",word,3)) {
 		r = get_menutype(word);
 		free(word);
 		return r;
 	}
+	else if (fastncmp("GT_",word,4)) {
+		r = get_gametype(word);
+		free(word);
+		return r;
+	}
+	else if (fastncmp("GTR_", word, 4)) {
+		char *p = word+4;
+		for (i = 0; GAMETYPERULE_LIST[i]; i++)
+			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
+				free(word);
+				return (1<<i);
+			}
+
+		// Not found error
+		const_warning("game type rule",word);
+		free(word);
+		return 0;
+	}
+	else if (fastncmp("TOL_", word, 4)) {
+		char *p = word+4;
+		for (i = 0; TYPEOFLEVEL[i].name; i++)
+			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
+				free(word);
+				return TYPEOFLEVEL[i].flag;
+			}
+
+		// Not found error
+		const_warning("typeoflevel",word);
+		free(word);
+		return 0;
+	}
 	else if (fastncmp("HUD_",word,4)) {
 		r = get_huditem(word);
 		free(word);
@@ -10124,6 +10434,20 @@ static inline int lib_freeslot(lua_State *L)
 			}
 			r++;
 		}
+		else if (fastcmp(type, "TOL"))
+		{
+			if (lastcustomtol > 31)
+				CONS_Alert(CONS_WARNING, "Ran out of free typeoflevel slots!\n");
+			else
+			{
+				UINT32 newtol = (1<<lastcustomtol);
+				CONS_Printf("TypeOfLevel TOL_%s allocated.\n",word);
+				G_AddTOL(newtol, word);
+				lua_pushinteger(L, newtol);
+				lastcustomtol++;
+				r++;
+			}
+		}
 		Z_Free(s);
 		lua_remove(L, 1);
 		continue;
@@ -10228,6 +10552,36 @@ static inline int lib_getenum(lua_State *L)
 		if (mathlib) return luaL_error(L, "playerflag '%s' could not be found.\n", word);
 		return 0;
 	}
+	else if (fastncmp("GT_", word, 3)) {
+		p = word;
+		for (i = 0; Gametype_ConstantNames[i]; i++)
+			if (fastcmp(p, Gametype_ConstantNames[i])) {
+				lua_pushinteger(L, i);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("GTR_", word, 4)) {
+		p = word+4;
+		for (i = 0; GAMETYPERULE_LIST[i]; i++)
+			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
+				lua_pushinteger(L, ((lua_Integer)1<<i));
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "game type rule '%s' could not be found.\n", word);
+		return 0;
+	}
+	else if (fastncmp("TOL_", word, 4)) {
+		p = word+4;
+		for (i = 0; TYPEOFLEVEL[i].name; i++)
+			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
+				lua_pushinteger(L, TYPEOFLEVEL[i].flag);
+				return 1;
+			}
+		if (mathlib) return luaL_error(L, "typeoflevel '%s' could not be found.\n", word);
+		return 0;
+	}
 	else if (fastncmp("ML_", word, 3)) {
 		p = word+3;
 		for (i = 0; i < 16; i++)
@@ -10465,198 +10819,7 @@ static inline int lib_getenum(lua_State *L)
 	// DYNAMIC variables too!!
 	// Try not to add anything that would break netgames or timeattack replays here.
 	// You know, like consoleplayer, displayplayer, secondarydisplayplayer, or gametime.
-	if (fastcmp(word,"gamemap")) {
-		lua_pushinteger(L, gamemap);
-		return 1;
-	} else if (fastcmp(word,"maptol")) {
-		lua_pushinteger(L, maptol);
-		return 1;
-	} else if (fastcmp(word,"ultimatemode")) {
-		lua_pushboolean(L, ultimatemode != 0);
-		return 1;
-	} else if (fastcmp(word,"mariomode")) {
-		lua_pushboolean(L, mariomode != 0);
-		return 1;
-	} else if (fastcmp(word,"twodlevel")) {
-		lua_pushboolean(L, twodlevel != 0);
-		return 1;
-	} else if (fastcmp(word,"circuitmap")) {
-		lua_pushboolean(L, circuitmap);
-		return 1;
-	} else if (fastcmp(word,"netgame")) {
-		lua_pushboolean(L, netgame);
-		return 1;
-	} else if (fastcmp(word,"multiplayer")) {
-		lua_pushboolean(L, multiplayer);
-		return 1;
-	} else if (fastcmp(word,"modeattacking")) {
-		lua_pushboolean(L, modeattacking);
-		return 1;
-	} else if (fastcmp(word,"splitscreen")) {
-		lua_pushboolean(L, splitscreen);
-		return 1;
-	} else if (fastcmp(word,"gamecomplete")) {
-		lua_pushboolean(L, gamecomplete);
-		return 1;
-	} else if (fastcmp(word,"devparm")) {
-		lua_pushboolean(L, devparm);
-		return 1;
-	} else if (fastcmp(word,"modifiedgame")) {
-		lua_pushboolean(L, modifiedgame && !savemoddata);
-		return 1;
-	} else if (fastcmp(word,"menuactive")) {
-		lua_pushboolean(L, menuactive);
-		return 1;
-	} else if (fastcmp(word,"paused")) {
-		lua_pushboolean(L, paused);
-		return 1;
-	// begin map vars
-	} else if (fastcmp(word,"spstage_start")) {
-		lua_pushinteger(L, spstage_start);
-		return 1;
-	} else if (fastcmp(word,"sstage_start")) {
-		lua_pushinteger(L, sstage_start);
-		return 1;
-	} else if (fastcmp(word,"sstage_end")) {
-		lua_pushinteger(L, sstage_end);
-		return 1;
-	} else if (fastcmp(word,"smpstage_start")) {
-		lua_pushinteger(L, smpstage_start);
-		return 1;
-	} else if (fastcmp(word,"smpstage_end")) {
-		lua_pushinteger(L, smpstage_end);
-		return 1;
-	} else if (fastcmp(word,"titlemap")) {
-		lua_pushinteger(L, titlemap);
-		return 1;
-	} else if (fastcmp(word,"titlemapinaction")) {
-		lua_pushboolean(L, (titlemapinaction != TITLEMAP_OFF));
-		return 1;
-	} else if (fastcmp(word,"bootmap")) {
-		lua_pushinteger(L, bootmap);
-		return 1;
-	} else if (fastcmp(word,"tutorialmap")) {
-		lua_pushinteger(L, tutorialmap);
-		return 1;
-	} else if (fastcmp(word,"tutorialmode")) {
-		lua_pushboolean(L, tutorialmode);
-		return 1;
-	// end map vars
-	// begin CTF colors
-	} else if (fastcmp(word,"skincolor_redteam")) {
-		lua_pushinteger(L, skincolor_redteam);
-		return 1;
-	} else if (fastcmp(word,"skincolor_blueteam")) {
-		lua_pushinteger(L, skincolor_blueteam);
-		return 1;
-	} else if (fastcmp(word,"skincolor_redring")) {
-		lua_pushinteger(L, skincolor_redring);
-		return 1;
-	} else if (fastcmp(word,"skincolor_bluering")) {
-		lua_pushinteger(L, skincolor_bluering);
-		return 1;
-	// end CTF colors
-	// begin timers
-	} else if (fastcmp(word,"invulntics")) {
-		lua_pushinteger(L, invulntics);
-		return 1;
-	} else if (fastcmp(word,"sneakertics")) {
-		lua_pushinteger(L, sneakertics);
-		return 1;
-	} else if (fastcmp(word,"flashingtics")) {
-		lua_pushinteger(L, flashingtics);
-		return 1;
-	} else if (fastcmp(word,"tailsflytics")) {
-		lua_pushinteger(L, tailsflytics);
-		return 1;
-	} else if (fastcmp(word,"underwatertics")) {
-		lua_pushinteger(L, underwatertics);
-		return 1;
-	} else if (fastcmp(word,"spacetimetics")) {
-		lua_pushinteger(L, spacetimetics);
-		return 1;
-	} else if (fastcmp(word,"extralifetics")) {
-		lua_pushinteger(L, extralifetics);
-		return 1;
-	} else if (fastcmp(word,"nightslinktics")) {
-		lua_pushinteger(L, nightslinktics);
-		return 1;
-	} else if (fastcmp(word,"gameovertics")) {
-		lua_pushinteger(L, gameovertics);
-		return 1;
-	} else if (fastcmp(word,"ammoremovaltics")) {
-		lua_pushinteger(L, ammoremovaltics);
-		return 1;
-	// end timers
-	} else if (fastcmp(word,"gametype")) {
-		lua_pushinteger(L, gametype);
-		return 1;
-	} else if (fastcmp(word,"leveltime")) {
-		lua_pushinteger(L, leveltime);
-		return 1;
-	} else if (fastcmp(word,"curWeather")) {
-		lua_pushinteger(L, curWeather);
-		return 1;
-	} else if (fastcmp(word,"globalweather")) {
-		lua_pushinteger(L, globalweather);
-		return 1;
-	} else if (fastcmp(word,"levelskynum")) {
-		lua_pushinteger(L, levelskynum);
-		return 1;
-	} else if (fastcmp(word,"globallevelskynum")) {
-		lua_pushinteger(L, globallevelskynum);
-		return 1;
-	} else if (fastcmp(word,"mapmusname")) {
-		lua_pushstring(L, mapmusname);
-		return 1;
-	} else if (fastcmp(word,"mapmusflags")) {
-		lua_pushinteger(L, mapmusflags);
-		return 1;
-	} 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;
-		LUA_PushUserdata(L, &players[serverplayer], META_PLAYER);
-		return 1;
-	} else if (fastcmp(word,"admin")) { // BACKWARDS COMPATIBILITY HACK: This was replaced with IsPlayerAdmin(), but some 2.1 Lua scripts still use the admin variable. It now points to the first admin player in the array.
-		LUA_Deprecated(L, "admin", "IsPlayerAdmin(player)");
-		if (!playeringame[adminplayers[0]] || IsPlayerAdmin(serverplayer))
-			return 0;
-		LUA_PushUserdata(L, &players[adminplayers[0]], META_PLAYER);
-		return 1;
-	} else if (fastcmp(word,"emeralds")) {
-		lua_pushinteger(L, emeralds);
-		return 1;
-	} else if (fastcmp(word,"gravity")) {
-		lua_pushinteger(L, gravity);
-		return 1;
-	} else if (fastcmp(word,"VERSIONSTRING")) {
-		lua_pushstring(L, VERSIONSTRING);
-		return 1;
-	} else if (fastcmp(word, "token")) {
-		lua_pushinteger(L, token);
-		return 1;
-	}
-	return 0;
+	return LUA_PushGlobals(L, word);
 }
 
 int LUA_EnumLib(lua_State *L)
@@ -10723,6 +10886,8 @@ static int lib_getActionName(lua_State *L)
 	return luaL_typerror(L, 1, "action userdata or Lua function");
 }
 
+
+
 int LUA_SOCLib(lua_State *L)
 {
 	lua_register(L,"freeslot",lib_freeslot);
diff --git a/src/doomdef.h b/src/doomdef.h
index 7ca1feec70d8b65977edebb75133f8183872692c..e6d3f1ceeb347e0984cc841048cc064369015144 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -127,7 +127,7 @@
 
 #ifdef LOGMESSAGES
 extern FILE *logstream;
-extern char  logfilename[1024];
+extern char logfilename[1024];
 #endif
 
 //#define DEVELOP // Disable this for release builds to remove excessive cheat commands and enable MD5 checking and stuff, all in one go. :3
@@ -625,7 +625,7 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 
 /// Sprite rotation
 #define ROTSPRITE
-#define ROTANGLES 24	// Needs to be a divisor of 360 (45, 60, 90, 120...)
+#define ROTANGLES 72 // Needs to be a divisor of 360 (45, 60, 90, 120...)
 #define ROTANGDIFF (360 / ROTANGLES)
 
 /// PNG support
diff --git a/src/doomstat.h b/src/doomstat.h
index a42591f476d8e30d5fab7ad352235b1e1a32b9ed..b7bb7a36228b5e732db282b0948b2ea4841d2a03 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -39,7 +39,7 @@ extern UINT32 mapmusposition;
 #define MUSIC_FORCERESET  0x4000 // -*--------------
 // Use other bits if necessary.
 
-extern INT16 maptol;
+extern UINT32 maptol;
 extern UINT8 globalweather;
 extern INT32 curWeather;
 extern INT32 cursaveslot;
@@ -84,6 +84,9 @@ extern boolean addedtogame; // true after the server has added you
 extern boolean multiplayer;
 
 extern INT16 gametype;
+extern UINT32 gametyperules;
+extern INT16 gametypecount;
+
 extern boolean splitscreen;
 extern boolean circuitmap; // Does this level have 'circuit mode'?
 extern boolean fromlevelselect;
@@ -284,7 +287,7 @@ typedef struct
 	char lvlttl[22];       ///< Level name without "Zone". (21 character limit instead of 32, 21 characters can display on screen max anyway)
 	char subttl[33];       ///< Subtitle for level
 	UINT8 actnum;          ///< Act number or 0 for none.
-	UINT16 typeoflevel;    ///< Combination of typeoflevel flags.
+	UINT32 typeoflevel;    ///< Combination of typeoflevel flags.
 	INT16 nextlevel;       ///< Map number of next level, or 1100-1102 to end.
 	char musname[7];       ///< Music track to play. "" for no music.
 	UINT16 mustrack;       ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore.
@@ -366,6 +369,70 @@ typedef struct
 
 extern mapheader_t* mapheaderinfo[NUMMAPS];
 
+// Gametypes
+#define NUMGAMETYPEFREESLOTS 128
+enum GameType
+{
+	GT_COOP = 0, // also used in single player
+	GT_COMPETITION, // Classic "Race"
+	GT_RACE,
+
+	GT_MATCH,
+	GT_TEAMMATCH,
+
+	GT_TAG,
+	GT_HIDEANDSEEK,
+
+	GT_CTF, // capture the flag
+
+	GT_FIRSTFREESLOT,
+	GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1,
+	NUMGAMETYPES
+};
+// If you alter this list, update dehacked.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c
+
+// Gametype rules
+enum GameTypeRules
+{
+	GTR_CAMPAIGN         = 1,     // Linear Co-op map progression, don't allow random maps
+	GTR_RINGSLINGER      = 1<<1,  // Outside of Co-op, Competition, and Race (overriden by cv_ringslinger)
+	GTR_SPECTATORS       = 1<<2,  // Outside of Co-op, Competition, and Race
+	GTR_FRIENDLYFIRE     = 1<<3,  // Always allow friendly fire
+	GTR_LIVES            = 1<<4,  // Co-op and Competition
+	GTR_TEAMS            = 1<<5,  // Team Match, CTF
+	GTR_RACE             = 1<<6,  // Race and Competition
+	GTR_TAG              = 1<<7,  // Tag and Hide and Seek
+	GTR_POINTLIMIT       = 1<<8,  // Ringslinger point limit
+	GTR_TIMELIMIT        = 1<<9,  // Ringslinger time limit
+	GTR_HIDETIME         = 1<<10, // Hide time (Tag and Hide and Seek)
+	GTR_HIDEFROZEN       = 1<<11, // Frozen after hide time (Hide and Seek, but not Tag)
+	GTR_BLINDFOLDED      = 1<<12, // Blindfolded view (Tag and Hide and Seek)
+	GTR_FIRSTPERSON      = 1<<13, // First person camera
+	GTR_MATCHEMERALDS    = 1<<14, // Ringslinger emeralds (Match and CTF)
+	GTR_TEAMFLAGS        = 1<<15, // Gametype has team flags (CTF)
+	GTR_PITYSHIELD       = 1<<16, // Award pity shield
+	GTR_DEATHPENALTY     = 1<<17, // Death score penalty
+	GTR_NOSPECTATORSPAWN = 1<<18, // Use with GTR_SPECTATORS, spawn in the map instead of with the spectators
+	GTR_DEATHMATCHSTARTS = 1<<19, // Use deathmatch starts
+	GTR_SPECIALSTAGES    = 1<<20, // Allow special stages
+	GTR_EMERALDTOKENS    = 1<<21, // Spawn emerald tokens
+	GTR_EMERALDHUNT      = 1<<22, // Emerald Hunt
+	GTR_SPAWNENEMIES     = 1<<23, // Spawn enemies
+	GTR_ALLOWEXIT        = 1<<24, // Allow exit sectors
+	GTR_NOTITLECARD      = 1<<25, // Don't show the title card
+	GTR_OVERTIME         = 1<<26, // Allow overtime
+	GTR_HURTMESSAGES     = 1<<27, // Hit and death messages
+	GTR_SPAWNINVUL       = 1<<28, // Babysitting deterrent
+};
+
+// String names for gametypes
+extern const char *Gametype_Names[NUMGAMETYPES];
+extern const char *Gametype_ConstantNames[NUMGAMETYPES];
+
+// Point and time limits for every gametype
+extern INT32 pointlimits[NUMGAMETYPES];
+extern INT32 timelimits[NUMGAMETYPES];
+
 enum TypeOfLevel
 {
 	TOL_SP          = 0x01, ///< Single Player
@@ -381,36 +448,26 @@ enum TypeOfLevel
 	TOL_CTF         = 0x40, ///< Capture the Flag
 // CTF default = 64
 
-	TOL_CUSTOM      = 0x80, ///< Custom (Lua-scripted, etc.)
+	// 0x80 was here
 
 	TOL_2D     = 0x0100, ///< 2D
 	TOL_MARIO  = 0x0200, ///< Mario
 	TOL_NIGHTS = 0x0400, ///< NiGHTS
 	TOL_ERZ3   = 0x0800, ///< ERZ3
-	TOL_XMAS   = 0x1000  ///< Christmas NiGHTS
+	TOL_XMAS   = 0x1000, ///< Christmas NiGHTS
 };
 
-// Gametypes
-enum GameType
-{
-	GT_COOP = 0, // also used in single player
-	GT_COMPETITION, // Classic "Race"
-	GT_RACE,
-
-	GT_MATCH,
-	GT_TEAMMATCH,
-
-	GT_TAG,
-	GT_HIDEANDSEEK,
-
-	GT_CTF, // capture the flag
-
-	NUMGAMETYPES
-};
-// If you alter this list, update dehacked.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c
+#define NUMBASETOL 18
+#define NUMMAXTOL (18 + NUMGAMETYPEFREESLOTS)
 
-// String names for gametypes
-extern const char *Gametype_Names[NUMGAMETYPES];
+typedef struct
+{
+	const char *name;
+	UINT32 flag;
+} tolinfo_t;
+extern tolinfo_t TYPEOFLEVEL[NUMMAXTOL];
+extern INT32 numtolinfo;
+extern UINT32 lastcustomtol;
 
 extern tic_t totalplaytime;
 
diff --git a/src/g_game.c b/src/g_game.c
index cf9b4367da240917a2008641fdad8dfab1969b76..19b18ef8c2f760de8756baaaed0c516f9182425f 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -79,7 +79,7 @@ UINT16 mapmusflags; // Track and reset bit
 UINT32 mapmusposition; // Position to jump to
 
 INT16 gamemap = 1;
-INT16 maptol;
+UINT32 maptol;
 UINT8 globalweather = 0;
 INT32 curWeather = PRECIP_NONE;
 INT32 cursaveslot = 0; // Auto-save 1p savegame slot
@@ -1066,7 +1066,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	// why build a ticcmd if we're paused?
 	// Or, for that matter, if we're being reborn.
 	// ...OR if we're blindfolded. No looking into the floor.
-	if (paused || P_AutoPause() || (gamestate == GS_LEVEL && (player->playerstate == PST_REBORN || ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	if (paused || P_AutoPause() || (gamestate == GS_LEVEL && (player->playerstate == PST_REBORN || ((gametyperules & GTR_TAG)
 	&& (leveltime < hidetime * TICRATE) && (player->pflags & PF_TAGIT)))))
 	{
 		cmd->angleturn = (INT16)(localangle >> 16);
@@ -1159,14 +1159,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	else
 	{
 		if (turnright)
-			cmd->angleturn = (INT16)(cmd->angleturn - angleturn[tspeed]);
+			cmd->angleturn = (INT16)(cmd->angleturn - ((angleturn[tspeed] * cv_cam_turnmultiplier.value)>>FRACBITS));
 		else if (turnleft)
-			cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
+			cmd->angleturn = (INT16)(cmd->angleturn + ((angleturn[tspeed] * cv_cam_turnmultiplier.value)>>FRACBITS));
 
 		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{
 			// JOYAXISRANGE should be 1023 (divide by 1024)
-			cmd->angleturn = (INT16)(cmd->angleturn - ((lookjoystickvector.xaxis * angleturn[1]) >> 10)); // ANALOG!
+			cmd->angleturn = (INT16)(cmd->angleturn - ((((lookjoystickvector.xaxis * angleturn[1]) >> 10) * cv_cam_turnmultiplier.value)>>FRACBITS)); // ANALOG!
 		}
 	}
 
@@ -1379,7 +1379,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	//Reset away view if a command is given.
 	if ((cmd->forwardmove || cmd->sidemove || cmd->buttons)
 		&& displayplayer != consoleplayer)
+	{
+#ifdef HAVE_BLUA
+		// Call ViewpointSwitch hooks here.
+		// The viewpoint was forcibly changed.
+		LUAh_ViewpointSwitch(player, &players[displayplayer], true);
+#endif
 		displayplayer = consoleplayer;
+	}
 }
 
 // like the g_buildticcmd 1 but using mouse2, gamcontrolbis, ...
@@ -1497,14 +1504,14 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	else
 	{
 		if (turnright)
-			cmd->angleturn = (INT16)(cmd->angleturn - angleturn[tspeed]);
+			cmd->angleturn = (INT16)(cmd->angleturn - (((angleturn[tspeed]<<FRACBITS) * cv_cam2_turnmultiplier.value)>>FRACBITS));
 		else if (turnleft)
-			cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
+			cmd->angleturn = (INT16)(cmd->angleturn + (((angleturn[tspeed]<<FRACBITS) * cv_cam2_turnmultiplier.value)>>FRACBITS));
 
 		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
 		{
 			// JOYAXISRANGE should be 1023 (divide by 1024)
-			cmd->angleturn = (INT16)(cmd->angleturn - ((lookjoystickvector.xaxis * angleturn[1]) >> 10)); // ANALOG!
+			cmd->angleturn = (INT16)(cmd->angleturn - ((((lookjoystickvector.xaxis * angleturn[1]) >> 10) * cv_cam2_turnmultiplier.value)>>FRACBITS)); // ANALOG!
 		}
 	}
 
@@ -1885,7 +1892,7 @@ void G_StartTitleCard(void)
 {
 	// The title card has been disabled for this map.
 	// Oh well.
-	if (mapheaderinfo[gamemap-1]->levelflags & LF_NOTITLECARD)
+	if (!G_IsTitleCardAvailable())
 	{
 		WipeStageTitle = false;
 		return;
@@ -1930,6 +1937,23 @@ void G_PreLevelTitleCard(void)
 		wipestyleflags = WSF_CROSSFADE;
 }
 
+//
+// Returns true if the current level has a title card.
+//
+boolean G_IsTitleCardAvailable(void)
+{
+	// The current level header explicitly disabled the title card.
+	if (mapheaderinfo[gamemap-1]->levelflags & LF_NOTITLECARD)
+		return false;
+
+	// The current gametype doesn't have a title card.
+	if (gametyperules & GTR_NOTITLECARD)
+		return false;
+
+	// The title card is available.
+	return true;
+}
+
 INT32 pausedelay = 0;
 boolean pausebreakkey = false;
 static INT32 camtoggledelay, camtoggledelay2 = 0;
@@ -2020,6 +2044,11 @@ boolean G_Responder(event_t *ev)
 	if (gamestate == GS_LEVEL && ev->type == ev_keydown
 		&& (ev->data1 == KEY_F12 || ev->data1 == gamecontrol[gc_viewpoint][0] || ev->data1 == gamecontrol[gc_viewpoint][1]))
 	{
+		// ViewpointSwitch Lua hook.
+#ifdef HAVE_BLUA
+		UINT8 canSwitchView = 0;
+#endif
+
 		if (splitscreen || !netgame)
 			displayplayer = consoleplayer;
 		else
@@ -2034,6 +2063,15 @@ boolean G_Responder(event_t *ev)
 				if (!playeringame[displayplayer])
 					continue;
 
+#ifdef HAVE_BLUA
+				// Call ViewpointSwitch hooks here.
+				canSwitchView = LUAh_ViewpointSwitch(&players[consoleplayer], &players[displayplayer], false);
+				if (canSwitchView == 1) // Set viewpoint to this player
+					break;
+				else if (canSwitchView == 2) // Skip this player
+					continue;
+#endif
+
 				if (players[displayplayer].spectator)
 					continue;
 
@@ -2642,7 +2680,7 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
 
 	// -- CTF --
 	// Order: CTF->DM->Coop
-	if (gametype == GT_CTF && players[playernum].ctfteam)
+	if ((gametyperules & (GTR_TEAMFLAGS|GTR_TEAMS)) && players[playernum].ctfteam)
 	{
 		if (!(spawnpoint = G_FindCTFStart(playernum)) // find a CTF start
 		&& !(spawnpoint = G_FindMatchStart(playernum))) // find a DM start
@@ -2651,7 +2689,7 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
 
 	// -- DM/Tag/CTF-spectator/etc --
 	// Order: DM->CTF->Coop
-	else if (gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF
+	else if ((gametyperules & GTR_DEATHMATCHSTARTS) || gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF
 	 || ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && !(players[playernum].pflags & PF_TAGIT)))
 	{
 		if (!(spawnpoint = G_FindMatchStart(playernum)) // find a DM start
@@ -2698,7 +2736,7 @@ mapthing_t *G_FindCTFStart(INT32 playernum)
 
 	if (!numredctfstarts && !numbluectfstarts) //why even bother, eh?
 	{
-		if (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer))
+		if ((gametyperules & GTR_TEAMFLAGS) && (playernum == consoleplayer || (splitscreen && playernum == secondarydisplayplayer)))
 			CONS_Alert(CONS_WARNING, M_GetText("No CTF starts in this map!\n"));
 		return NULL;
 	}
@@ -3104,7 +3142,7 @@ void G_ExitLevel(void)
 				CV_SetValue(&cv_teamscramble, cv_scrambleonchange.value);
 		}
 
-		if (gametype != GT_COOP)
+		if (!(gametyperules & GTR_CAMPAIGN))
 			CONS_Printf(M_GetText("The round has ended.\n"));
 
 		// Remove CEcho text on round end.
@@ -3136,6 +3174,221 @@ const char *Gametype_Names[NUMGAMETYPES] =
 	"CTF" // GT_CTF
 };
 
+// For dehacked
+const char *Gametype_ConstantNames[NUMGAMETYPES] =
+{
+	"GT_COOP", // GT_COOP
+	"GT_COMPETITION", // GT_COMPETITION
+	"GT_RACE", // GT_RACE
+
+	"GT_MATCH", // GT_MATCH
+	"GT_TEAMMATCH", // GT_TEAMMATCH
+
+	"GT_TAG", // GT_TAG
+	"GT_HIDEANDSEEK", // GT_HIDEANDSEEK
+
+	"GT_CTF" // GT_CTF
+};
+
+// Gametype rules
+UINT32 gametypedefaultrules[NUMGAMETYPES] =
+{
+	// Co-op
+	GTR_CAMPAIGN|GTR_LIVES|GTR_SPAWNENEMIES|GTR_ALLOWEXIT|GTR_EMERALDHUNT|GTR_EMERALDTOKENS|GTR_SPECIALSTAGES,
+	// Competition
+	GTR_RACE|GTR_LIVES|GTR_SPAWNENEMIES|GTR_EMERALDTOKENS|GTR_SPAWNINVUL|GTR_ALLOWEXIT,
+	// Race
+	GTR_RACE|GTR_SPAWNENEMIES|GTR_SPAWNINVUL|GTR_ALLOWEXIT,
+
+	// Match
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_MATCHEMERALDS|GTR_SPAWNINVUL|GTR_PITYSHIELD|GTR_DEATHPENALTY,
+	// Team Match
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_SPAWNINVUL|GTR_PITYSHIELD,
+
+	// Tag
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_HIDETIME|GTR_BLINDFOLDED|GTR_SPAWNINVUL,
+	// Hide and Seek
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_TAG|GTR_SPECTATORS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_HIDETIME|GTR_BLINDFOLDED|GTR_SPAWNINVUL,
+
+	// CTF
+	GTR_RINGSLINGER|GTR_FIRSTPERSON|GTR_SPECTATORS|GTR_TEAMS|GTR_TEAMFLAGS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_MATCHEMERALDS|GTR_SPAWNINVUL|GTR_PITYSHIELD,
+};
+
+//
+// G_SetGametype
+//
+// Set a new gametype, also setting gametype rules accordingly. Yay!
+//
+void G_SetGametype(INT16 gtype)
+{
+	gametype = gtype;
+	gametyperules = gametypedefaultrules[gametype];
+}
+
+//
+// G_AddGametype
+//
+// Add a gametype. Returns the new gametype number.
+//
+INT16 G_AddGametype(UINT32 rules)
+{
+	INT16 newgtype = gametypecount;
+	gametypecount++;
+
+	// Set gametype rules.
+	gametypedefaultrules[newgtype] = rules;
+	Gametype_Names[newgtype] = "???";
+
+	// Update gametype_cons_t accordingly.
+	G_UpdateGametypeSelections();
+
+	return newgtype;
+}
+
+//
+// G_AddGametypeConstant
+//
+// Self-explanatory. Filters out "bad" characters.
+//
+void G_AddGametypeConstant(INT16 gtype, const char *newgtconst)
+{
+	char *gtconst = Z_Malloc(strlen(newgtconst) + 3, PU_STATIC, NULL);
+	// Copy GT_ and the gametype name.
+	strcpy(gtconst, "GT_");
+	strcat(gtconst, newgtconst);
+	// Make uppercase.
+	strupr(gtconst);
+	// Remove characters.
+#define REMOVECHAR(chr) \
+	{ \
+		char *chrfind = strchr(gtconst, chr); \
+		while (chrfind) \
+		{ \
+			*chrfind = '_'; \
+			chrfind = strchr(chrfind, chr); \
+		} \
+	}
+
+	// Space
+	REMOVECHAR(' ')
+	// Used for operations
+	REMOVECHAR('+')
+	REMOVECHAR('-')
+	REMOVECHAR('*')
+	REMOVECHAR('/')
+	REMOVECHAR('%')
+	REMOVECHAR('^')
+	// Part of Lua's syntax
+	REMOVECHAR('#')
+	REMOVECHAR('=')
+	REMOVECHAR('~')
+	REMOVECHAR('<')
+	REMOVECHAR('>')
+	REMOVECHAR('(')
+	REMOVECHAR(')')
+	REMOVECHAR('{')
+	REMOVECHAR('}')
+	REMOVECHAR('[')
+	REMOVECHAR(']')
+	REMOVECHAR(':')
+	REMOVECHAR(';')
+	REMOVECHAR(',')
+	REMOVECHAR('.')
+
+#undef REMOVECHAR
+
+	// Finally, set the constant string.
+	Gametype_ConstantNames[gtype] = gtconst;
+}
+
+//
+// G_UpdateGametypeSelections
+//
+// Updates gametype_cons_t.
+//
+void G_UpdateGametypeSelections(void)
+{
+	INT32 i;
+	for (i = 0; i < gametypecount; i++)
+	{
+		gametype_cons_t[i].value = i;
+		gametype_cons_t[i].strvalue = Gametype_Names[i];
+	}
+	gametype_cons_t[NUMGAMETYPES].value = 0;
+	gametype_cons_t[NUMGAMETYPES].strvalue = NULL;
+}
+
+//
+// G_SetGametypeDescription
+//
+// Set a description for the specified gametype.
+// (Level platter)
+//
+void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolor, UINT8 rightcolor)
+{
+	if (descriptiontext != NULL)
+		strncpy(gametypedesc[gtype].notes, descriptiontext, 441);
+	gametypedesc[gtype].col[0] = leftcolor;
+	gametypedesc[gtype].col[1] = rightcolor;
+}
+
+// Gametype rankings
+INT16 gametyperankings[NUMGAMETYPES] =
+{
+	GT_COOP,
+	GT_COMPETITION,
+	GT_RACE,
+
+	GT_MATCH,
+	GT_TEAMMATCH,
+
+	GT_TAG,
+	GT_HIDEANDSEEK,
+
+	GT_CTF,
+};
+
+// Gametype to TOL (Type Of Level)
+UINT32 gametypetol[NUMGAMETYPES] =
+{
+	TOL_COOP, // Co-op
+	TOL_COMPETITION, // Competition
+	TOL_RACE, // Race
+
+	TOL_MATCH, // Match
+	TOL_MATCH, // Team Match
+
+	TOL_TAG, // Tag
+	TOL_TAG, // Hide and Seek
+
+	TOL_CTF, // CTF
+};
+
+//
+// G_AddTOL
+//
+// Adds a type of level.
+//
+void G_AddTOL(UINT32 newtol, const char *tolname)
+{
+	TYPEOFLEVEL[numtolinfo].name = Z_StrDup(tolname);
+	TYPEOFLEVEL[numtolinfo].flag = newtol;
+	numtolinfo++;
+
+	TYPEOFLEVEL[numtolinfo].name = NULL;
+	TYPEOFLEVEL[numtolinfo].flag = 0;
+}
+
+//
+// G_AddTOL
+//
+// Assigns a type of level to a gametype.
+//
+void G_AddGametypeTOL(INT16 gtype, UINT32 newtol)
+{
+	gametypetol[gtype] = newtol;
+}
+
 //
 // G_GetGametypeByName
 //
@@ -3178,8 +3431,8 @@ boolean G_IsSpecialStage(INT32 mapnum)
 //
 boolean G_GametypeUsesLives(void)
 {
-	 // Coop, Competitive
-	if ((gametype == GT_COOP || gametype == GT_COMPETITION)
+	// Coop, Competitive
+	if ((gametyperules & GTR_LIVES)
 	 && !(modeattacking || metalrecording) // No lives in Time Attack
 	 && !G_IsSpecialStage(gamemap)
 	 && !(maptol & TOL_NIGHTS)) // No lives in NiGHTS
@@ -3195,7 +3448,7 @@ boolean G_GametypeUsesLives(void)
 //
 boolean G_GametypeHasTeams(void)
 {
-	return (gametype == GT_TEAMMATCH || gametype == GT_CTF);
+	return (gametyperules & GTR_TEAMS);
 }
 
 //
@@ -3206,7 +3459,7 @@ boolean G_GametypeHasTeams(void)
 //
 boolean G_GametypeHasSpectators(void)
 {
-	return (gametype != GT_COOP && gametype != GT_COMPETITION && gametype != GT_RACE);
+	return (gametyperules & GTR_SPECTATORS);
 }
 
 //
@@ -3217,7 +3470,7 @@ boolean G_GametypeHasSpectators(void)
 //
 boolean G_RingSlingerGametype(void)
 {
-	return ((gametype != GT_COOP && gametype != GT_COMPETITION && gametype != GT_RACE) || (cv_ringslinger.value));
+	return ((gametyperules & GTR_RINGSLINGER) || (cv_ringslinger.value));
 }
 
 //
@@ -3227,7 +3480,7 @@ boolean G_RingSlingerGametype(void)
 //
 boolean G_PlatformGametype(void)
 {
-	return (gametype == GT_COOP || gametype == GT_RACE || gametype == GT_COMPETITION);
+	return (!(gametyperules & GTR_RINGSLINGER));
 }
 
 //
@@ -3237,7 +3490,7 @@ boolean G_PlatformGametype(void)
 //
 boolean G_TagGametype(void)
 {
-	return (gametype == GT_TAG || gametype == GT_HIDEANDSEEK);
+	return (gametyperules & GTR_TAG);
 }
 
 /** Get the typeoflevel flag needed to indicate support of a gametype.
@@ -3248,18 +3501,9 @@ boolean G_TagGametype(void)
   */
 INT16 G_TOLFlag(INT32 pgametype)
 {
-	if (!multiplayer)                 return TOL_SP;
-	if (pgametype == GT_COOP)         return TOL_COOP;
-	if (pgametype == GT_COMPETITION)  return TOL_COMPETITION;
-	if (pgametype == GT_RACE)         return TOL_RACE;
-	if (pgametype == GT_MATCH)        return TOL_MATCH;
-	if (pgametype == GT_TEAMMATCH)    return TOL_MATCH;
-	if (pgametype == GT_TAG)          return TOL_TAG;
-	if (pgametype == GT_HIDEANDSEEK)  return TOL_TAG;
-	if (pgametype == GT_CTF)          return TOL_CTF;
-
-	CONS_Alert(CONS_ERROR, M_GetText("Unknown gametype! %d\n"), pgametype);
-	return INT16_MAX;
+	if (!multiplayer)
+		return TOL_SP;
+	return gametypetol[pgametype];
 }
 
 /** Select a random map with the given typeoflevel flags.
@@ -3270,7 +3514,7 @@ INT16 G_TOLFlag(INT32 pgametype)
   *         has those flags.
   * \author Graue <graue@oceanbase.org>
   */
-static INT16 RandMap(INT16 tolflags, INT16 pprevmap)
+static INT16 RandMap(UINT32 tolflags, INT16 pprevmap)
 {
 	INT16 *okmaps = Z_Malloc(NUMMAPS * sizeof(INT16), PU_STATIC, NULL);
 	INT32 numokmaps = 0;
@@ -3428,10 +3672,10 @@ static void G_DoCompleted(void)
 		I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1);
 
 	// wrap around in race
-	if (nextmap >= 1100-1 && nextmap <= 1102-1 && (gametype == GT_RACE || gametype == GT_COMPETITION))
+	if (nextmap >= 1100-1 && nextmap <= 1102-1 && !(gametyperules & GTR_CAMPAIGN))
 		nextmap = (INT16)(spstage_start-1);
 
-	if ((gottoken = (gametype == GT_COOP && token)))
+	if ((gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token)))
 	{
 		token--;
 
@@ -3451,7 +3695,7 @@ static void G_DoCompleted(void)
 
 	automapactive = false;
 
-	if (gametype != GT_COOP)
+	if (!(gametyperules & GTR_CAMPAIGN))
 	{
 		if (cv_advancemap.value == 0) // Stay on same map.
 			nextmap = prevmap;
diff --git a/src/g_game.h b/src/g_game.h
index c19faebe4721bd4ea2ad25177e350c0f61af300b..238dd196420d860517630ca857fde566b60a8b31 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -143,6 +143,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar,
 void G_DoLoadLevel(boolean resetplayer);
 void G_StartTitleCard(void);
 void G_PreLevelTitleCard(void);
+boolean G_IsTitleCardAvailable(void);
 void G_DeferedPlayDemo(const char *demo);
 
 // Can be called by the startup code or M_Responder, calls P_SetupLevel.
@@ -202,6 +203,18 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill);
 void G_StopDemo(void);
 boolean G_CheckDemoStatus(void);
 
+extern UINT32 gametypedefaultrules[NUMGAMETYPES];
+extern UINT32 gametypetol[NUMGAMETYPES];
+extern INT16 gametyperankings[NUMGAMETYPES];
+
+void G_SetGametype(INT16 gametype);
+INT16 G_AddGametype(UINT32 rules);
+void G_AddGametypeConstant(INT16 gtype, const char *newgtconst);
+void G_UpdateGametypeSelections(void);
+void G_AddTOL(UINT32 newtol, const char *tolname);
+void G_AddGametypeTOL(INT16 gtype, UINT32 newtol);
+void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolor, UINT8 rightcolor);
+
 INT32 G_GetGametypeByName(const char *gametypestr);
 boolean G_IsSpecialStage(INT32 mapnum);
 boolean G_GametypeUsesLives(void);
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 313c62eec19e6c77320183f13d7bfbf9fa544e3a..dfecbea85cb91cdaa773cfd35d72f6c8b9a33237 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -112,13 +112,11 @@ typedef struct
 	FLOAT       fovxangle, fovyangle;
 	UINT8       splitscreen;
 	boolean     flip;            // screenflip
-#ifdef ROTSPRITE
 	boolean     roll;
 	SINT8       rollflip;
 	FLOAT       rollangle; // done to not override USE_FTRANSFORM_ANGLEZ
 	UINT8       rotaxis;
 	FLOAT       centerx, centery;
-#endif
 #ifdef USE_FTRANSFORM_MIRROR
 	boolean     mirror;          // SRB2Kart: Encore Mode
 #endif
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 7fd214a299b1a82e6368cfe139dcd950b604fdf6..1cfa1a2e8f23cfdae9c5d904cf882899f63711fa 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5118,17 +5118,12 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	fixed_t spr_offset, spr_topoffset;
 #ifdef ROTSPRITE
 	patch_t *rotsprite = NULL;
-	angle_t arollangle;
-	UINT32 rollangle;
+	INT32 rollangle = 0;
 #endif
 
 	if (!thing)
 		return;
 
-#ifdef ROTSPRITE
-	arollangle = thing->rollangle;
-	rollangle = AngleFixed(arollangle)>>FRACBITS;
-#endif
 	this_scale = FIXED_TO_FLOAT(thing->scale);
 
 	// transform the origin point
@@ -5235,11 +5230,11 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	spr_topoffset = spritecachedinfo[lumpoff].topoffset;
 
 #ifdef ROTSPRITE
-	if (rollangle > 0)
+	if (thing->rollangle)
 	{
+		rollangle = R_GetRollAngle(thing->rollangle);
 		if (!sprframe->rotsprite.cached[rot])
 			R_CacheRotSprite(thing->sprite, (thing->frame & FF_FRAMEMASK), sprinfo, sprframe, rot, flip);
-		rollangle /= ROTANGDIFF;
 		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
 		if (rotsprite != NULL)
 		{
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 0591f9b8f0c6fe64c857156c0f2e211cf6c13211..02e8df9a3519546d7307aaf857f711a0464fa848 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1197,10 +1197,8 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
-#ifdef ROTSPRITE
 		spriteinfo_t *sprinfo;
 		angle_t ang;
-#endif
 		INT32 mod;
 		float finalscale;
 
@@ -1224,16 +1222,12 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		{
 			md2 = &md2_playermodels[(skin_t*)spr->mobj->skin-skins];
 			md2->skin = (skin_t*)spr->mobj->skin-skins;
-#ifdef ROTSPRITE
 			sprinfo = &((skin_t *)spr->mobj->skin)->sprinfo[spr->mobj->sprite2];
-#endif
 		}
 		else
 		{
 			md2 = &md2_models[spr->mobj->sprite];
-#ifdef ROTSPRITE
 			sprinfo = &spriteinfo[spr->mobj->sprite];
-#endif
 		}
 
 		if (md2->error)
@@ -1433,9 +1427,8 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 
-#ifdef ROTSPRITE
 		p.rollangle = 0.0f;
-		p.rollflip = 0;
+		p.rollflip = 1;
 		p.rotaxis = 0;
 		if (spr->mobj->rollangle)
 		{
@@ -1458,7 +1451,6 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 			else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
 				p.rollflip = -1;
 		}
-#endif
 
 		p.anglex = 0.0f;
 
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 6997ebcfe926c2bc662d65a007dbcf6a6367e6c5..5689d7945fbc1b15c7f3f552e75aa906b290d2e8 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2628,7 +2628,6 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
 	pglRotatef(pos->anglex, 1.0f, 0.0f, 0.0f);
 
-#ifdef ROTSPRITE
 	if (pos->roll)
 	{
 		float roll = (1.0f * pos->rollflip);
@@ -2641,7 +2640,6 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 			pglRotatef(pos->rollangle, roll, 0.0f, 0.0f);
 		pglTranslatef(-pos->centerx, -pos->centery, 0);
 	}
-#endif
 
 	pglScalef(scalex, scaley, scalez);
 
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 91a167a60517bfa6043a70328a291eec08a605d9..d9801793b85e0885bc9c52889099c7e70ff36be5 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2066,7 +2066,7 @@ static void HU_drawGametype(void)
 {
 	const char *strvalue = NULL;
 
-	if (gametype < 0 || gametype >= NUMGAMETYPES)
+	if (gametype < 0 || gametype >= gametypecount)
 		return; // not a valid gametype???
 
 	strvalue = Gametype_Names[gametype];
@@ -2371,7 +2371,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator && gametype != GT_COOP)
+		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
 			continue; //ignore them.
 
 		greycheck = greycheckdef;
@@ -2434,7 +2434,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 			}
 		}
 
-		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
+		if (G_GametypeUsesLives() && !(gametyperankings[gametype] == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|(greycheck ? V_60TRANS : 0), va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 		{
@@ -2447,7 +2447,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		if (players[tab[i].num].exiting || (players[tab[i].num].pflags & PF_FINISHED))
 			V_DrawSmallScaledPatch(x - SHORT(exiticon->width)/2 - 1, y-3, 0, exiticon);
 
-		if (gametype == GT_RACE)
+		if (gametyperankings[gametype] == GT_RACE)
 		{
 			if (circuitmap)
 			{
@@ -2541,7 +2541,7 @@ static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		             | (greycheck ? 0 : V_TRANSLUCENT)
 		             | V_ALLOWLOWERCASE, name);
 
-		if (gametype == GT_CTF)
+		if (gametyperules & GTR_TEAMFLAGS)
 		{
 			if (players[tab[i].num].gotflag & GF_REDFLAG) // Red
 				V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, rflagico, 0);
@@ -2669,7 +2669,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		             | (greycheck ? V_TRANSLUCENT : 0)
 		             | V_ALLOWLOWERCASE, name);
 
-		if (gametype == GT_CTF)
+		if (gametyperules & GTR_TEAMFLAGS)
 		{
 			if (players[tab[i].num].gotflag & GF_REDFLAG) // Red
 				V_DrawSmallScaledPatch(x-28, y-4, 0, rflagico);
@@ -2726,7 +2726,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator && gametype != GT_COOP)
+		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
 			continue; //ignore them.
 
 		greycheck = greycheckdef;
@@ -2743,7 +2743,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		             | (greycheck ? V_TRANSLUCENT : 0)
 		             | V_ALLOWLOWERCASE, name);
 
-		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
+		if (G_GametypeUsesLives() && !(gametyperankings[gametype] == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 			V_DrawSmallScaledPatch(x-28, y-4, 0, tagico);
@@ -2792,7 +2792,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		}
 
 		// All data drawn with thin string for space.
-		if (gametype == GT_RACE)
+		if (gametyperankings[gametype] == GT_RACE)
 		{
 			if (circuitmap)
 			{
@@ -2832,7 +2832,7 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator && gametype != GT_COOP)
+		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
 			continue; //ignore them.
 
 		greycheck = greycheckdef;
@@ -2902,7 +2902,7 @@ static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scor
 		}
 
 		// All data drawn with thin string for space.
-		if (gametype == GT_RACE)
+		if (gametyperankings[gametype] == GT_RACE)
 		{
 			if (circuitmap)
 			{
@@ -3026,21 +3026,21 @@ static void HU_DrawRankings(void)
 	// draw the current gametype in the lower right
 	HU_drawGametype();
 
-	if (gametype != GT_RACE && gametype != GT_COMPETITION && gametype != GT_COOP)
+	if (gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT))
 	{
-		if (cv_timelimit.value && timelimitintics > 0)
+		if ((gametyperules & GTR_TIMELIMIT) && cv_timelimit.value && timelimitintics > 0)
 		{
 			V_DrawCenteredString(64, 8, 0, "TIME");
 			V_DrawCenteredString(64, 16, 0, va("%i:%02i", G_TicsToMinutes(stplyr->realtime, true), G_TicsToSeconds(stplyr->realtime)));
 		}
 
-		if (cv_pointlimit.value > 0)
+		if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0)
 		{
 			V_DrawCenteredString(256, 8, 0, "POINT LIMIT");
 			V_DrawCenteredString(256, 16, 0, va("%d", cv_pointlimit.value));
 		}
 	}
-	else if (gametype == GT_COOP)
+	else if (gametyperankings[gametype] == GT_COOP)
 	{
 		INT32 totalscore = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -3074,7 +3074,7 @@ static void HU_DrawRankings(void)
 		tab[i].num = -1;
 		tab[i].name = 0;
 
-		if (gametype == GT_RACE && !circuitmap)
+		if (gametyperankings[gametype] == GT_RACE && !circuitmap)
 			tab[i].count = INT32_MAX;
 	}
 
@@ -3083,7 +3083,7 @@ static void HU_DrawRankings(void)
 		if (!playeringame[j])
 			continue;
 
-		if (gametype != GT_COOP && players[j].spectator)
+		if (!G_PlatformGametype() && players[j].spectator)
 			continue;
 
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -3091,10 +3091,10 @@ static void HU_DrawRankings(void)
 			if (!playeringame[i])
 				continue;
 
-			if (gametype != GT_COOP && players[i].spectator)
+			if (!G_PlatformGametype() && players[i].spectator)
 				continue;
 
-			if (gametype == GT_RACE)
+			if (gametyperankings[gametype] == GT_RACE)
 			{
 				if (circuitmap)
 				{
@@ -3117,7 +3117,7 @@ static void HU_DrawRankings(void)
 					}
 				}
 			}
-			else if (gametype == GT_COMPETITION)
+			else if (gametyperankings[gametype] == GT_COMPETITION)
 			{
 				// todo put something more fitting for the gametype here, such as current
 				// number of categories led
diff --git a/src/info.h b/src/info.h
index 26fb1738ded63abfe1ba0edc08e696f05c192849..261abbac5f9da5812becad7f7a3ddcea35670635 100644
--- a/src/info.h
+++ b/src/info.h
@@ -155,11 +155,9 @@ void A_SpawnObjectAbsolute();
 void A_SpawnObjectRelative();
 void A_ChangeAngleRelative();
 void A_ChangeAngleAbsolute();
-#ifdef ROTSPRITE
 void A_RollAngle();
 void A_ChangeRollAngleRelative();
 void A_ChangeRollAngleAbsolute();
-#endif
 void A_PlaySound();
 void A_FindTarget();
 void A_FindTracer();
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 018529722f53a8d51dd28dd359d010a595790e49..695e9367e57d2fb972e48f833bae160d94f71744 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -12,6 +12,7 @@
 
 #include "doomdef.h"
 #ifdef HAVE_BLUA
+#include "fastcmp.h"
 #include "p_local.h"
 #include "p_setup.h" // So we can have P_SetupLevelSky
 #ifdef ESLOPE
@@ -23,6 +24,8 @@
 #include "m_random.h"
 #include "s_sound.h"
 #include "g_game.h"
+#include "m_menu.h"
+#include "y_inter.h"
 #include "hu_stuff.h"	// HU_AddChatText
 #include "console.h"
 #include "d_netcmd.h" // IsPlayerAdmin
@@ -144,10 +147,8 @@ static const struct {
 	{META_MOBJINFO,     "mobjinfo_t"},
 	{META_SFXINFO,      "sfxinfo_t"},
 	{META_SPRITEINFO,   "spriteinfo_t"},
-#ifdef ROTSPRITE
 	{META_PIVOTLIST,    "spriteframepivot_t[]"},
 	{META_FRAMEPIVOT,   "spriteframepivot_t"},
-#endif
 
 	{META_MOBJ,         "mobj_t"},
 	{META_MAPTHING,     "mapthing_t"},
@@ -2632,6 +2633,145 @@ static int lib_sStartMusicCaption(lua_State *L)
 // G_GAME
 ////////////
 
+// Copypasted from lib_cvRegisterVar :]
+static int lib_gAddGametype(lua_State *L)
+{
+	const char *k;
+	lua_Integer i;
+
+	const char *gtname = NULL;
+	const char *gtconst = NULL;
+	const char *gtdescription = NULL;
+	INT16 newgtidx = 0;
+	UINT32 newgtrules = 0;
+	UINT32 newgttol = 0;
+	INT32 newgtpointlimit = 0;
+	INT32 newgttimelimit = 0;
+	UINT8 newgtleftcolor = 0;
+	UINT8 newgtrightcolor = 0;
+	INT16 newgtrankingstype = -1;
+	int newgtinttype = 0;
+
+	luaL_checktype(L, 1, LUA_TTABLE);
+	lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one.
+
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+
+	// Ran out of gametype slots
+	if (gametypecount == NUMGAMETYPEFREESLOTS)
+		return luaL_error(L, "Ran out of free gametype slots!");
+
+#define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to " LUA_QL("G_AddGametype") " (%s)", e);
+#define TYPEERROR(f, t) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t), luaL_typename(L, -1)))
+
+	lua_pushnil(L);
+	while (lua_next(L, 1)) {
+		// stack: gametype table, key/index, value
+		//               1            2        3
+		i = 0;
+		k = NULL;
+		if (lua_isnumber(L, 2))
+			i = lua_tointeger(L, 2);
+		else if (lua_isstring(L, 2))
+			k = lua_tostring(L, 2);
+
+		// Sorry, no gametype rules as key names.
+		if (i == 1 || (k && fasticmp(k, "name"))) {
+			if (!lua_isstring(L, 3))
+				TYPEERROR("name", LUA_TSTRING)
+			gtname = Z_StrDup(lua_tostring(L, 3));
+		} else if (i == 2 || (k && fasticmp(k, "identifier"))) {
+			if (!lua_isstring(L, 3))
+				TYPEERROR("identifier", LUA_TSTRING)
+			gtconst = Z_StrDup(lua_tostring(L, 3));
+		} else if (i == 3 || (k && fasticmp(k, "rules"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("rules", LUA_TNUMBER)
+			newgtrules = (UINT32)lua_tointeger(L, 3);
+		} else if (i == 4 || (k && fasticmp(k, "typeoflevel"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("typeoflevel", LUA_TNUMBER)
+			newgttol = (UINT32)lua_tointeger(L, 3);
+		} else if (i == 5 || (k && fasticmp(k, "rankingtype"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("rankingtype", LUA_TNUMBER)
+			newgtrankingstype = (INT16)lua_tointeger(L, 3);
+		} else if (i == 6 || (k && fasticmp(k, "intermissiontype"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("intermissiontype", LUA_TNUMBER)
+			newgtinttype = (int)lua_tointeger(L, 3);
+		} else if (i == 7 || (k && fasticmp(k, "defaultpointlimit"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("defaultpointlimit", LUA_TNUMBER)
+			newgtpointlimit = (INT32)lua_tointeger(L, 3);
+		} else if (i == 8 || (k && fasticmp(k, "defaulttimelimit"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("defaulttimelimit", LUA_TNUMBER)
+			newgttimelimit = (INT32)lua_tointeger(L, 3);
+		} else if (i == 9 || (k && fasticmp(k, "description"))) {
+			if (!lua_isstring(L, 3))
+				TYPEERROR("description", LUA_TSTRING)
+			gtdescription = Z_StrDup(lua_tostring(L, 3));
+		} else if (i == 10 || (k && fasticmp(k, "headerleftcolor"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("headerleftcolor", LUA_TNUMBER)
+			newgtleftcolor = (UINT8)lua_tointeger(L, 3);
+		} else if (i == 11 || (k && fasticmp(k, "headerrightcolor"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("headerrightcolor", LUA_TNUMBER)
+			newgtrightcolor = (UINT8)lua_tointeger(L, 3);
+		// Key name specified
+		} else if ((!i) && (k && fasticmp(k, "headercolor"))) {
+			if (!lua_isnumber(L, 3))
+				TYPEERROR("headercolor", LUA_TNUMBER)
+			newgtleftcolor = newgtrightcolor = (UINT8)lua_tointeger(L, 3);
+		}
+		lua_pop(L, 1);
+	}
+
+#undef FIELDERROR
+#undef TYPEERROR
+
+	// pop gametype table
+	lua_pop(L, 1);
+
+	// Set defaults
+	if (gtname == NULL)
+		gtname = Z_StrDup("Unnamed gametype");
+	if (gtdescription == NULL)
+		gtdescription = Z_StrDup("???");
+
+	// Add the new gametype
+	newgtidx = G_AddGametype(newgtrules);
+	G_AddGametypeTOL(newgtidx, newgttol);
+	G_SetGametypeDescription(newgtidx, NULL, newgtleftcolor, newgtrightcolor);
+	strncpy(gametypedesc[newgtidx].notes, gtdescription, 441);
+
+	// Not covered by G_AddGametype alone.
+	if (newgtrankingstype == -1)
+		newgtrankingstype = newgtidx;
+	gametyperankings[newgtidx] = newgtrankingstype;
+	intermissiontypes[newgtidx] = newgtinttype;
+	pointlimits[newgtidx] = newgtpointlimit;
+	timelimits[newgtidx] = newgttimelimit;
+
+	// Write the new gametype name.
+	Gametype_Names[newgtidx] = gtname;
+
+	// Write the constant name.
+	if (gtconst == NULL)
+		gtconst = gtname;
+	G_AddGametypeConstant(newgtidx, gtconst);
+
+	// Update gametype_cons_t accordingly.
+	G_UpdateGametypeSelections();
+
+	// done
+	CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]);
+	return 0;
+}
+
 static int lib_gBuildMapName(lua_State *L)
 {
 	INT32 map = luaL_optinteger(L, 1, gamemap);
@@ -2991,6 +3131,7 @@ static luaL_Reg lib[] = {
 	{"S_StartMusicCaption", lib_sStartMusicCaption},
 
 	// g_game
+	{"G_AddGametype", lib_gAddGametype},
 	{"G_BuildMapName",lib_gBuildMapName},
 	{"G_DoReborn",lib_gDoReborn},
 	{"G_SetCustomExitVars",lib_gSetCustomExitVars},
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 6617bca93a34c2db6742f40814d74eb079279f32..68efbce93d89a0e81cd0d7cd0ede54e2a5e09b05 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -51,6 +51,8 @@ enum hook {
 	hook_PlayerCanDamage,
 	hook_PlayerQuit,
 	hook_IntermissionThinker,
+	hook_TeamSwitch,
+	hook_ViewpointSwitch,
 
 	hook_MAX // last hook
 };
@@ -93,5 +95,7 @@ boolean LUAh_FollowMobj(player_t *player, mobj_t *mobj); // Hook for P_PlayerAft
 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
+boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); // Hook for team switching in... uh....
+UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); // Hook for spy mode
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index ef87d0b6f7a9cc592609be76cbeb59f770710ee1..91b4c699249a77a5103c1b61911aa1de51a223db 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -62,6 +62,8 @@ const char *const hookNames[hook_MAX+1] = {
 	"PlayerCanDamage",
 	"PlayerQuit",
 	"IntermissionThinker",
+	"TeamSwitch",
+	"ViewpointSwitch",
 	NULL
 };
 
@@ -203,6 +205,8 @@ static int lib_addHook(lua_State *L)
 	case hook_PlayerSpawn:
 	case hook_FollowMobj:
 	case hook_PlayerCanDamage:
+	case hook_TeamSwitch:
+	case hook_ViewpointSwitch:
 	case hook_ShieldSpawn:
 	case hook_ShieldSpecial:
 		lastp = &playerhooks;
@@ -657,14 +661,16 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 			LUA_PushUserdata(gL, inflictor, META_MOBJ);
 			LUA_PushUserdata(gL, source, META_MOBJ);
 			lua_pushinteger(gL, damage);
+			lua_pushinteger(gL, damagetype);
 		}
 		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
 		lua_gettable(gL, LUA_REGISTRYINDEX);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		if (lua_pcall(gL, 4, 1, 0)) {
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		if (lua_pcall(gL, 5, 1, 0)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
 				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
@@ -745,14 +751,16 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
 			LUA_PushUserdata(gL, inflictor, META_MOBJ);
 			LUA_PushUserdata(gL, source, META_MOBJ);
 			lua_pushinteger(gL, damage);
+			lua_pushinteger(gL, damagetype);
 		}
 		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
 		lua_gettable(gL, LUA_REGISTRYINDEX);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		lua_pushvalue(gL, -5);
-		if (lua_pcall(gL, 4, 1, 0)) {
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		if (lua_pcall(gL, 5, 1, 0)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
 				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
@@ -823,13 +831,15 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8
 			LUA_PushUserdata(gL, target, META_MOBJ);
 			LUA_PushUserdata(gL, inflictor, META_MOBJ);
 			LUA_PushUserdata(gL, source, META_MOBJ);
+			lua_pushinteger(gL, damagetype);
 		}
 		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
 		lua_gettable(gL, LUA_REGISTRYINDEX);
-		lua_pushvalue(gL, -4);
-		lua_pushvalue(gL, -4);
-		lua_pushvalue(gL, -4);
-		if (lua_pcall(gL, 3, 1, 0)) {
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		lua_pushvalue(gL, -5);
+		if (lua_pcall(gL, 4, 1, 0)) {
 			if (!hookp->error || cv_debug & DBG_LUA)
 				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
 			lua_pop(gL, 1);
@@ -1346,4 +1356,101 @@ void LUAh_IntermissionThinker(void)
 	}
 }
 
+// Hook for team switching
+// It's just an edit of LUAh_ViewpointSwitch.
+boolean LUAh_TeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble)
+{
+	hook_p hookp;
+	boolean canSwitchTeam = true;
+	if (!gL || !(hooksAvailable[hook_TeamSwitch/8] & (1<<(hook_TeamSwitch%8))))
+		return true;
+
+	lua_settop(gL, 0);
+
+	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_TeamSwitch)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+		{
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			lua_pushinteger(gL, newteam);
+			lua_pushboolean(gL, fromspectators);
+			lua_pushboolean(gL, tryingautobalance);
+			lua_pushboolean(gL, tryingscramble);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		lua_pushvalue(gL, -6);
+		if (lua_pcall(gL, 5, 1, 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;
+			continue;
+		}
+		if (!lua_isnil(gL, -1) && !lua_toboolean(gL, -1))
+			canSwitchTeam = false; // Can't switch team
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	return canSwitchTeam;
+}
+
+// Hook for spy mode
+UINT8 LUAh_ViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced)
+{
+	hook_p hookp;
+	UINT8 canSwitchView = 0; // 0 = default, 1 = force yes, 2 = force no.
+	if (!gL || !(hooksAvailable[hook_ViewpointSwitch/8] & (1<<(hook_ViewpointSwitch%8))))
+		return 0;
+
+	lua_settop(gL, 0);
+	hud_running = true;
+
+	for (hookp = playerhooks; hookp; hookp = hookp->next)
+	{
+		if (hookp->type != hook_ViewpointSwitch)
+			continue;
+
+		if (lua_gettop(gL) == 0)
+		{
+			LUA_PushUserdata(gL, player, META_PLAYER);
+			LUA_PushUserdata(gL, newdisplayplayer, META_PLAYER);
+			lua_pushboolean(gL, forced);
+		}
+		lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+		lua_gettable(gL, LUA_REGISTRYINDEX);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		lua_pushvalue(gL, -4);
+		if (lua_pcall(gL, 3, 1, 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;
+			continue;
+		}
+		if (!lua_isnil(gL, -1))
+		{ // if nil, leave canSwitchView = 0.
+			if (lua_toboolean(gL, -1))
+				canSwitchView = 1; // Force viewpoint switch
+			else
+				canSwitchView = 2; // Skip viewpoint switch
+		}
+		lua_pop(gL, 1);
+	}
+
+	lua_settop(gL, 0);
+	hud_running = false;
+
+	return canSwitchView;
+}
+
 #endif
diff --git a/src/lua_hud.h b/src/lua_hud.h
index 23e3ef8348c5e5d8e746f6a8d0f3fd5b0b1df8fc..a00a5cb028bca8f63b1e9349757742e79405b703 100644
--- a/src/lua_hud.h
+++ b/src/lua_hud.h
@@ -34,6 +34,9 @@ enum hud {
 	hud_coopemeralds,
 	hud_tokens,
 	hud_tabemblems,
+	// Intermission
+	hud_intermissiontally,
+	hud_intermissionmessages,
 	hud_MAX
 };
 
@@ -44,4 +47,5 @@ boolean LUA_HudEnabled(enum hud option);
 void LUAh_GameHUD(player_t *stplyr);
 void LUAh_ScoresHUD(void);
 void LUAh_TitleHUD(void);
-void LUAh_TitleCardHUD(player_t *stplyr);
+void LUAh_TitleCardHUD(player_t *stplayr);
+void LUAh_IntermissionHUD(void);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 5b5aa3b4ba277ed00f81cb0113a66a905f4627f5..12ae1b5fc06e81b722c229325e3117fee6d8703b 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -61,6 +61,9 @@ static const char *const hud_disable_options[] = {
 	"coopemeralds",
 	"tokens",
 	"tabemblems",
+
+	"intermissiontally",
+	"intermissionmessages",
 	NULL};
 
 enum hudinfo {
@@ -93,12 +96,14 @@ static const char *const patch_opt[] = {
 enum hudhook {
 	hudhook_game = 0,
 	hudhook_scores,
+	hudhook_intermission,
 	hudhook_title,
 	hudhook_titlecard
 };
 static const char *const hudhook_opt[] = {
 	"game",
 	"scores",
+	"intermission",
 	"title",
 	"titlecard",
 	NULL};
@@ -1051,13 +1056,16 @@ int LUA_HudLib(lua_State *L)
 		lua_rawseti(L, -2, 2); // HUD[2] = game rendering functions array
 
 		lua_newtable(L);
-		lua_rawseti(L, -2, 3); // HUD[2] = scores rendering functions array
+		lua_rawseti(L, -2, 3); // HUD[3] = scores rendering functions array
+
+		lua_newtable(L);
+		lua_rawseti(L, -2, 4); // HUD[4] = intermission rendering functions array
 
 		lua_newtable(L);
-		lua_rawseti(L, -2, 4); // HUD[3] = title rendering functions array
+		lua_rawseti(L, -2, 5); // HUD[5] = title rendering functions array
 
 		lua_newtable(L);
-		lua_rawseti(L, -2, 5); // HUD[4] = title card rendering functions array
+		lua_rawseti(L, -2, 6); // HUD[6] = title card rendering functions array
 	lua_setfield(L, LUA_REGISTRYINDEX, "HUD");
 
 	luaL_newmetatable(L, META_HUDINFO);
@@ -1229,4 +1237,29 @@ void LUAh_TitleCardHUD(player_t *stplayr)
 	hud_running = false;
 }
 
+void LUAh_IntermissionHUD(void)
+{
+	if (!gL || !(hudAvailable & (1<<hudhook_intermission)))
+		return;
+
+	hud_running = true;
+	lua_pop(gL, -1);
+
+	lua_getfield(gL, LUA_REGISTRYINDEX, "HUD");
+	I_Assert(lua_istable(gL, -1));
+	lua_rawgeti(gL, -1, 4); // HUD[4] = rendering funcs
+	I_Assert(lua_istable(gL, -1));
+
+	lua_rawgeti(gL, -2, 1); // HUD[1] = lib_draw
+	I_Assert(lua_istable(gL, -1));
+	lua_remove(gL, -3); // pop HUD
+	lua_pushnil(gL);
+	while (lua_next(gL, -3) != 0) {
+		lua_pushvalue(gL, -3); // graphics library (HUD[1])
+		LUA_Call(gL, 1);
+	}
+	lua_pop(gL, -1);
+	hud_running = false;
+}
+
 #endif
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index b617a7a14d5885c45ddcac5a22c854733991680d..7a2465ef770ad2c36fde2805cb585360bf20d007 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -266,7 +266,6 @@ static int lib_getSpriteInfo(lua_State *L)
 #define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to spriteinfo[] (%s)", e);
 #define TYPEERROR(f, t1, t2) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t1), lua_typename(L, t2)))
 
-#ifdef ROTSPRITE
 static int PopPivotSubTable(spriteframepivot_t *pivot, lua_State *L, int stk, int idx)
 {
 	int okcool = 0;
@@ -360,7 +359,6 @@ static int PopPivotTable(spriteinfo_t *info, lua_State *L, int stk)
 
 	return 0;
 }
-#endif
 
 static int lib_setSpriteInfo(lua_State *L)
 {
@@ -393,14 +391,11 @@ static int lib_setSpriteInfo(lua_State *L)
 		if (lua_isnumber(L, 2))
 		{
 			i = lua_tointeger(L, 2);
-#ifndef ROTSPRITE
 			i++; // shift index in case of missing rotsprite support
-#endif
 		}
 		else
 			str = luaL_checkstring(L, 2);
 
-#ifdef ROTSPRITE
 		if (i == 1 || (str && fastcmp(str, "pivot")))
 		{
 			// pivot[] is a table
@@ -409,7 +404,6 @@ static int lib_setSpriteInfo(lua_State *L)
 			else
 				FIELDERROR("pivot", va("%s expected, got %s", lua_typename(L, LUA_TTABLE), luaL_typename(L, -1)))
 		}
-#endif
 
 		lua_pop(L, 1);
 	}
@@ -434,7 +428,6 @@ static int spriteinfo_get(lua_State *L)
 
 	I_Assert(sprinfo != NULL);
 
-#ifdef ROTSPRITE
 	// push spriteframepivot_t userdata
 	if (fastcmp(field, "pivot"))
 	{
@@ -448,7 +441,6 @@ static int spriteinfo_get(lua_State *L)
 		return 1;
 	}
 	else
-#endif
 		return luaL_error(L, LUA_QL("spriteinfo_t") " has no field named " LUA_QS, field);
 
 	return 0;
@@ -473,6 +465,7 @@ static int spriteinfo_set(lua_State *L)
 #ifdef ROTSPRITE
 	if (sprites != NULL)
 		R_FreeSingleRotSprite(&sprites[sprinfo-spriteinfo]);
+#endif
 
 	if (fastcmp(field, "pivot"))
 	{
@@ -488,7 +481,6 @@ static int spriteinfo_set(lua_State *L)
 		}
 	}
 	else
-#endif
 		return luaL_error(L, va("Field %s does not exist in spriteinfo_t", field));
 
 	return 0;
@@ -506,7 +498,6 @@ static int spriteinfo_num(lua_State *L)
 }
 
 // framepivot_t
-#ifdef ROTSPRITE
 static int pivotlist_get(lua_State *L)
 {
 	void **userdata;
@@ -616,7 +607,6 @@ static int framepivot_num(lua_State *L)
 	lua_pushinteger(L, 2);
 	return 1;
 }
-#endif
 
 ////////////////
 // STATE INFO //
@@ -1538,7 +1528,6 @@ int LUA_InfoLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
-#ifdef ROTSPRITE
 	luaL_newmetatable(L, META_PIVOTLIST);
 		lua_pushcfunction(L, pivotlist_get);
 		lua_setfield(L, -2, "__index");
@@ -1560,7 +1549,6 @@ int LUA_InfoLib(lua_State *L)
 		lua_pushcfunction(L, framepivot_num);
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
-#endif
 
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 104a1e51c0c4338c3ec64e9196180dd5a4f5876a..6a908d03dbc7069990198f3fa3d940e36bf77177 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -23,10 +23,8 @@ extern lua_State *gL;
 #define META_MOBJINFO "MOBJINFO_T*"
 #define META_SFXINFO "SFXINFO_T*"
 #define META_SPRITEINFO "SPRITEINFO_T*"
-#ifdef ROTSPRITE
 #define META_PIVOTLIST "SPRITEFRAMEPIVOT_T[]"
 #define META_FRAMEPIVOT "SPRITEFRAMEPIVOT_T*"
-#endif
 
 #define META_MOBJ "MOBJ_T*"
 #define META_MAPTHING "MAPTHING_T*"
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 49f93d29cd4f5180eccf784b5ce01af56795db8e..22248775153b5afd4ce21c61656fc3f3b73a8a50 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -32,9 +32,7 @@ enum mobj_e {
 	mobj_snext,
 	mobj_sprev,
 	mobj_angle,
-#ifdef ROTSPRITE
 	mobj_rollangle,
-#endif
 	mobj_sprite,
 	mobj_frame,
 	mobj_sprite2,
@@ -101,9 +99,7 @@ static const char *const mobj_opt[] = {
 	"snext",
 	"sprev",
 	"angle",
-#ifdef ROTSPRITE
 	"rollangle",
-#endif
 	"sprite",
 	"frame",
 	"sprite2",
@@ -205,11 +201,9 @@ static int mobj_get(lua_State *L)
 	case mobj_angle:
 		lua_pushangle(L, mo->angle);
 		break;
-#ifdef ROTSPRITE
 	case mobj_rollangle:
 		lua_pushangle(L, mo->rollangle);
 		break;
-#endif
 	case mobj_sprite:
 		lua_pushinteger(L, mo->sprite);
 		break;
@@ -462,11 +456,9 @@ static int mobj_set(lua_State *L)
 		else if (mo->player == &players[secondarydisplayplayer])
 			localangle2 = mo->angle;
 		break;
-#ifdef ROTSPRITE
 	case mobj_rollangle:
 		mo->rollangle = luaL_checkangle(L, 3);
 		break;
-#endif
 	case mobj_sprite:
 		mo->sprite = luaL_checkinteger(L, 3);
 		break;
diff --git a/src/lua_script.c b/src/lua_script.c
index fe3c2f10d11f5f5f513fbb0be8003150e1cdf15e..18d9a87c22e70de737351160ca018545c92aa12f 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -18,7 +18,9 @@
 #include "w_wad.h"
 #include "p_setup.h"
 #include "r_state.h"
+#include "r_sky.h"
 #include "g_game.h"
+#include "f_finale.h"
 #include "byteptr.h"
 #include "p_saveg.h"
 #include "p_local.h"
@@ -79,8 +81,239 @@ FUNCNORETURN static int LUA_Panic(lua_State *L)
 #endif
 }
 
+// Moved here from lib_getenum.
+int LUA_PushGlobals(lua_State *L, const char *word)
+{
+	if (fastcmp(word,"gamemap")) {
+		lua_pushinteger(L, gamemap);
+		return 1;
+	} else if (fastcmp(word,"maptol")) {
+		lua_pushinteger(L, maptol);
+		return 1;
+	} else if (fastcmp(word,"ultimatemode")) {
+		lua_pushboolean(L, ultimatemode != 0);
+		return 1;
+	} else if (fastcmp(word,"mariomode")) {
+		lua_pushboolean(L, mariomode != 0);
+		return 1;
+	} else if (fastcmp(word,"twodlevel")) {
+		lua_pushboolean(L, twodlevel != 0);
+		return 1;
+	} else if (fastcmp(word,"circuitmap")) {
+		lua_pushboolean(L, circuitmap);
+		return 1;
+	} else if (fastcmp(word,"netgame")) {
+		lua_pushboolean(L, netgame);
+		return 1;
+	} else if (fastcmp(word,"multiplayer")) {
+		lua_pushboolean(L, multiplayer);
+		return 1;
+	} else if (fastcmp(word,"modeattacking")) {
+		lua_pushboolean(L, modeattacking);
+		return 1;
+	} else if (fastcmp(word,"splitscreen")) {
+		lua_pushboolean(L, splitscreen);
+		return 1;
+	} else if (fastcmp(word,"gamecomplete")) {
+		lua_pushboolean(L, gamecomplete);
+		return 1;
+	} else if (fastcmp(word,"devparm")) {
+		lua_pushboolean(L, devparm);
+		return 1;
+	} else if (fastcmp(word,"modifiedgame")) {
+		lua_pushboolean(L, modifiedgame && !savemoddata);
+		return 1;
+	} else if (fastcmp(word,"menuactive")) {
+		lua_pushboolean(L, menuactive);
+		return 1;
+	} else if (fastcmp(word,"paused")) {
+		lua_pushboolean(L, paused);
+		return 1;
+	} else if (fastcmp(word,"bluescore")) {
+		lua_pushinteger(L, bluescore);
+		return 1;
+	} else if (fastcmp(word,"redscore")) {
+		lua_pushinteger(L, redscore);
+		return 1;
+	} else if (fastcmp(word,"timelimit")) {
+		lua_pushinteger(L, cv_timelimit.value);
+		return 1;
+	} else if (fastcmp(word,"pointlimit")) {
+		lua_pushinteger(L, cv_pointlimit.value);
+		return 1;
+	// begin map vars
+	} else if (fastcmp(word,"spstage_start")) {
+		lua_pushinteger(L, spstage_start);
+		return 1;
+	} else if (fastcmp(word,"sstage_start")) {
+		lua_pushinteger(L, sstage_start);
+		return 1;
+	} else if (fastcmp(word,"sstage_end")) {
+		lua_pushinteger(L, sstage_end);
+		return 1;
+	} else if (fastcmp(word,"smpstage_start")) {
+		lua_pushinteger(L, smpstage_start);
+		return 1;
+	} else if (fastcmp(word,"smpstage_end")) {
+		lua_pushinteger(L, smpstage_end);
+		return 1;
+	} else if (fastcmp(word,"titlemap")) {
+		lua_pushinteger(L, titlemap);
+		return 1;
+	} else if (fastcmp(word,"titlemapinaction")) {
+		lua_pushboolean(L, (titlemapinaction != TITLEMAP_OFF));
+		return 1;
+	} else if (fastcmp(word,"bootmap")) {
+		lua_pushinteger(L, bootmap);
+		return 1;
+	} else if (fastcmp(word,"tutorialmap")) {
+		lua_pushinteger(L, tutorialmap);
+		return 1;
+	} else if (fastcmp(word,"tutorialmode")) {
+		lua_pushboolean(L, tutorialmode);
+		return 1;
+	// end map vars
+	// begin CTF colors
+	} else if (fastcmp(word,"skincolor_redteam")) {
+		lua_pushinteger(L, skincolor_redteam);
+		return 1;
+	} else if (fastcmp(word,"skincolor_blueteam")) {
+		lua_pushinteger(L, skincolor_blueteam);
+		return 1;
+	} else if (fastcmp(word,"skincolor_redring")) {
+		lua_pushinteger(L, skincolor_redring);
+		return 1;
+	} else if (fastcmp(word,"skincolor_bluering")) {
+		lua_pushinteger(L, skincolor_bluering);
+		return 1;
+	// end CTF colors
+	// begin timers
+	} else if (fastcmp(word,"invulntics")) {
+		lua_pushinteger(L, invulntics);
+		return 1;
+	} else if (fastcmp(word,"sneakertics")) {
+		lua_pushinteger(L, sneakertics);
+		return 1;
+	} else if (fastcmp(word,"flashingtics")) {
+		lua_pushinteger(L, flashingtics);
+		return 1;
+	} else if (fastcmp(word,"tailsflytics")) {
+		lua_pushinteger(L, tailsflytics);
+		return 1;
+	} else if (fastcmp(word,"underwatertics")) {
+		lua_pushinteger(L, underwatertics);
+		return 1;
+	} else if (fastcmp(word,"spacetimetics")) {
+		lua_pushinteger(L, spacetimetics);
+		return 1;
+	} else if (fastcmp(word,"extralifetics")) {
+		lua_pushinteger(L, extralifetics);
+		return 1;
+	} else if (fastcmp(word,"nightslinktics")) {
+		lua_pushinteger(L, nightslinktics);
+		return 1;
+	} else if (fastcmp(word,"gameovertics")) {
+		lua_pushinteger(L, gameovertics);
+		return 1;
+	} else if (fastcmp(word,"ammoremovaltics")) {
+		lua_pushinteger(L, ammoremovaltics);
+		return 1;
+	// end timers
+	} else if (fastcmp(word,"gametype")) {
+		lua_pushinteger(L, gametype);
+		return 1;
+	} else if (fastcmp(word,"gametyperules")) {
+		lua_pushinteger(L, gametyperules);
+		return 1;
+	} else if (fastcmp(word,"leveltime")) {
+		lua_pushinteger(L, leveltime);
+		return 1;
+	} else if (fastcmp(word,"sstimer")) {
+		lua_pushinteger(L, sstimer);
+		return 1;
+	} else if (fastcmp(word,"curWeather")) {
+		lua_pushinteger(L, curWeather);
+		return 1;
+	} else if (fastcmp(word,"globalweather")) {
+		lua_pushinteger(L, globalweather);
+		return 1;
+	} else if (fastcmp(word,"levelskynum")) {
+		lua_pushinteger(L, levelskynum);
+		return 1;
+	} else if (fastcmp(word,"globallevelskynum")) {
+		lua_pushinteger(L, globallevelskynum);
+		return 1;
+	} else if (fastcmp(word,"mapmusname")) {
+		lua_pushstring(L, mapmusname);
+		return 1;
+	} else if (fastcmp(word,"mapmusflags")) {
+		lua_pushinteger(L, mapmusflags);
+		return 1;
+	} 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;
+		LUA_PushUserdata(L, &players[serverplayer], META_PLAYER);
+		return 1;
+	} else if (fastcmp(word,"admin")) { // BACKWARDS COMPATIBILITY HACK: This was replaced with IsPlayerAdmin(), but some 2.1 Lua scripts still use the admin variable. It now points to the first admin player in the array.
+		LUA_Deprecated(L, "admin", "IsPlayerAdmin(player)");
+		if (!playeringame[adminplayers[0]] || IsPlayerAdmin(serverplayer))
+			return 0;
+		LUA_PushUserdata(L, &players[adminplayers[0]], META_PLAYER);
+		return 1;
+	} else if (fastcmp(word,"emeralds")) {
+		lua_pushinteger(L, emeralds);
+		return 1;
+	} else if (fastcmp(word,"gravity")) {
+		lua_pushinteger(L, gravity);
+		return 1;
+	} else if (fastcmp(word,"VERSIONSTRING")) {
+		lua_pushstring(L, VERSIONSTRING);
+		return 1;
+	} else if (fastcmp(word, "token")) {
+		lua_pushinteger(L, token);
+		return 1;
+	}
+	return 0;
+}
+
+// See the above.
+int LUA_CheckGlobals(lua_State *L, const char *word)
+{
+	if (fastcmp(word, "gametyperules"))
+		gametyperules = (UINT32)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "redscore"))
+		redscore = (UINT32)luaL_checkinteger(L, 2);
+	else if (fastcmp(word, "bluescore"))
+		bluescore = (UINT32)luaL_checkinteger(L, 2);
+	else
+		return 0;
+
+	// Global variable set, so return and don't error.
+	return 1;
+}
+
 // This function decides which global variables you are allowed to set.
-static int noglobals(lua_State *L)
+static int setglobals(lua_State *L)
 {
 	const char *csname;
 	char *name;
@@ -106,6 +339,9 @@ static int noglobals(lua_State *L)
 		return 0;
 	}
 
+	if (LUA_CheckGlobals(L, csname))
+		return 0;
+
 	Z_Free(name);
 	return luaL_error(L, "Implicit global " LUA_QS " prevented. Create a local variable instead.", csname);
 }
@@ -144,7 +380,7 @@ static void LUA_ClearState(void)
 
 	// lock the global namespace
 	lua_getmetatable(L, LUA_GLOBALSINDEX);
-		lua_pushcfunction(L, noglobals);
+		lua_pushcfunction(L, setglobals);
 		lua_setfield(L, -2, "__newindex");
 		lua_newtable(L);
 		lua_setfield(L, -2, "__metatable");
diff --git a/src/lua_script.h b/src/lua_script.h
index 4f66d3f8a43ad1d8c94e1ccfa405bde8413dc8ad..8f27dcb4c9b5343095705b9901b7201fc8b854dd 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -54,6 +54,8 @@ void LUA_InvalidatePlayer(player_t *player);
 void LUA_Step(void);
 void LUA_Archive(void);
 void LUA_UnArchive(void);
+int LUA_PushGlobals(lua_State *L, const char *word);
+int LUA_CheckGlobals(lua_State *L, const char *word);
 void Got_Luacmd(UINT8 **cp, INT32 playernum); // lua_consolelib.c
 void LUA_CVarChanged(const char *name); // lua_consolelib.c
 int Lua_optoption(lua_State *L, int narg,
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 3d1fe5b7e6e909e58aaa3511dcfb6d0173e2c9d2..bd6eca73ac907d73b77e66489b092ea1e0070309 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -778,7 +778,7 @@ void Command_CauseCfail_f(void)
 	P_SetThingPosition(players[consoleplayer].mo);
 
 	// CTF consistency test
-	if (gametype == GT_CTF)
+	if (gametyperules & GTR_TEAMFLAGS)
 	{
 		if (blueflag) {
 			P_RemoveMobj(blueflag);
diff --git a/src/m_menu.c b/src/m_menu.c
index b62813db69d485aaac72d51b4bcb11c1805291a7..b3ff8ce717acb2c0e510570db40dc978a434f86b 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -605,7 +605,7 @@ static menuitem_t MISC_ChangeTeamMenu[] =
 	{IT_WHITESTRING|IT_CALL,         NULL, "Confirm",           M_ConfirmTeamChange,    90},
 };
 
-static const gtdesc_t gametypedesc[] =
+gtdesc_t gametypedesc[NUMGAMETYPES] =
 {
 	{{ 54,  54}, "Play through the single-player campaign with your friends, teaming up to beat Dr Eggman's nefarious challenges!"},
 	{{103, 103}, "Speed your way through the main acts, competing in several different categories to see who's the best."},
@@ -1181,7 +1181,8 @@ static menuitem_t OP_CameraOptionsMenu[] =
 
 	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_dist, 60},
 	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_height, 70},
-	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Speed", &cv_cam_speed, 80},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam_speed, 80},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Turning Speed", &cv_cam_turnmultiplier, 90},
 
 	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 100},
 };
@@ -1195,7 +1196,8 @@ static menuitem_t OP_Camera2OptionsMenu[] =
 
 	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam2_dist, 60},
 	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam2_height, 70},
-	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Speed", &cv_cam2_speed, 80},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam2_speed, 80},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Turning Speed", &cv_cam2_turnmultiplier, 90},
 
 	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 100},
 };
@@ -4688,6 +4690,9 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 			if (gt == GT_RACE && (mapheaderinfo[mapnum]->typeoflevel & TOL_RACE))
 				return true;
 
+			if (gt > 0 && gt < gametypecount && (mapheaderinfo[mapnum]->typeoflevel & gametypetol[gt]))
+				return true;
+
 			return false;
 
 		case LLM_LEVELSELECT:
@@ -9936,7 +9941,7 @@ static void M_DrawConnectMenu(void)
 		                     va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time)));
 
 		gt = "Unknown";
-		if (serverlist[slindex].info.gametype < NUMGAMETYPES)
+		if (serverlist[slindex].info.gametype < gametypecount)
 			gt = Gametype_Names[serverlist[slindex].info.gametype];
 
 		V_DrawSmallString(currentMenu->x+46,S_LINEY(i)+8, globalflags,
diff --git a/src/m_menu.h b/src/m_menu.h
index ce9b422dc4f5e3f889209970f47f9ba1af38c13e..19858e2fc810165a8c21d162dca28900fb63c58e 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -371,6 +371,7 @@ typedef struct
 	UINT8 col[2];
 	char notes[441];
 } gtdesc_t;
+extern gtdesc_t gametypedesc[NUMGAMETYPES];
 
 // mode descriptions for video mode menu
 typedef struct
diff --git a/src/m_misc.c b/src/m_misc.c
index 138a25ab149e9f57cff300d9ed62abf31e2c0dea..15d0a27e149ad537040dda7a9879d4633d89830f 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2466,3 +2466,92 @@ const char *M_FileError(FILE *fp)
 	else
 		return "end-of-file";
 }
+
+/** Return the number of parts of this path.
+*/
+int M_PathParts(const char *path)
+{
+	int n;
+	const char *p;
+	const char *t;
+	for (n = 0, p = path ;; ++n)
+	{
+		t = p;
+		if (( p = strchr(p, PATHSEP[0]) ))
+			p += strspn(p, PATHSEP);
+		else
+		{
+			if (*t)/* there is something after the final delimiter */
+				n++;
+			break;
+		}
+	}
+	return n;
+}
+
+/** Check whether a path is an absolute path.
+*/
+boolean M_IsPathAbsolute(const char *path)
+{
+#ifdef _WIN32
+	return ( strncmp(&path[1], ":\\", 2) == 0 );
+#else
+	return ( path[0] == '/' );
+#endif
+}
+
+/** I_mkdir for each part of the path.
+*/
+void M_MkdirEachUntil(const char *cpath, int start, int end, int mode)
+{
+	char path[MAX_WADPATH];
+	char *p;
+	char *t;
+
+	if (end > 0 && end <= start)
+		return;
+
+	strlcpy(path, cpath, sizeof path);
+#ifdef _WIN32
+	if (strncmp(&path[1], ":\\", 2) == 0)
+		p = &path[3];
+	else
+#endif
+		p = path;
+
+	if (end > 0)
+		end -= start;
+
+	for (; start > 0; --start)
+	{
+		p += strspn(p, PATHSEP);
+		if (!( p = strchr(p, PATHSEP[0]) ))
+			return;
+	}
+	p += strspn(p, PATHSEP);
+	for (;;)
+	{
+		if (end > 0 && !--end)
+			break;
+
+		t = p;
+		if (( p = strchr(p, PATHSEP[0]) ))
+		{
+			*p = '\0';
+			I_mkdir(path, mode);
+			*p = PATHSEP[0];
+			p += strspn(p, PATHSEP);
+		}
+		else
+		{
+			if (*t)
+				I_mkdir(path, mode);
+			break;
+		}
+	}
+}
+
+void M_MkdirEach(const char *path, int start, int mode)
+{
+	M_MkdirEachUntil(path, start, -1, mode);
+}
diff --git a/src/m_misc.h b/src/m_misc.h
index 99ca8d0c931c02d7bb9c16934b874649d37f273f..e0a73e0b7f9d30789931b671c2fe487bdf1910e1 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -96,6 +96,11 @@ void M_SetupMemcpy(void);
 
 const char *M_FileError(FILE *handle);
 
+int     M_PathParts      (const char *path);
+boolean M_IsPathAbsolute (const char *path);
+void    M_MkdirEach      (const char *path, int start, int mode);
+void    M_MkdirEachUntil (const char *path, int start, int end, int mode);
+
 // counting bits, for weapon ammo code, usually
 FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size);
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 6b6ce62c73f82bc4d5cc212179f875ebe3bf4779..d3c1318cd9cd9e3aa61f929e969532d600e3d4d0 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -181,11 +181,9 @@ void A_SpawnObjectAbsolute(mobj_t *actor);
 void A_SpawnObjectRelative(mobj_t *actor);
 void A_ChangeAngleRelative(mobj_t *actor);
 void A_ChangeAngleAbsolute(mobj_t *actor);
-#ifdef ROTSPRITE
 void A_RollAngle(mobj_t *actor);
 void A_ChangeRollAngleRelative(mobj_t *actor);
 void A_ChangeRollAngleAbsolute(mobj_t *actor);
-#endif // ROTSPRITE
 void A_PlaySound(mobj_t *actor);
 void A_FindTarget(mobj_t *actor);
 void A_FindTracer(mobj_t *actor);
@@ -4985,7 +4983,7 @@ void A_ThrownRing(mobj_t *actor)
 				continue;
 
 			// Don't home in on teammates.
-			if (gametype == GT_CTF
+			if ((gametyperules & GTR_TEAMFLAGS)
 				&& actor->target->player->ctfteam == player->ctfteam)
 				continue;
 		}
@@ -6591,7 +6589,7 @@ void A_OldRingExplode(mobj_t *actor) {
 
 		if (changecolor)
 		{
-			if (gametype != GT_CTF)
+			if (!(gametyperules & GTR_TEAMFLAGS))
 				mo->color = actor->target->color; //copy color
 			else if (actor->target->player->ctfteam == 2)
 				mo->color = skincolor_bluering;
@@ -6607,7 +6605,7 @@ void A_OldRingExplode(mobj_t *actor) {
 
 	if (changecolor)
 	{
-		if (gametype != GT_CTF)
+		if (!(gametyperules & GTR_TEAMFLAGS))
 			mo->color = actor->target->color; //copy color
 		else if (actor->target->player->ctfteam == 2)
 			mo->color = skincolor_bluering;
@@ -6622,7 +6620,7 @@ void A_OldRingExplode(mobj_t *actor) {
 
 	if (changecolor)
 	{
-		if (gametype != GT_CTF)
+		if (!(gametyperules & GTR_TEAMFLAGS))
 			mo->color = actor->target->color; //copy color
 		else if (actor->target->player->ctfteam == 2)
 			mo->color = skincolor_bluering;
@@ -8627,7 +8625,6 @@ void A_ChangeAngleAbsolute(mobj_t *actor)
 	actor->angle = FixedAngle(P_RandomRange(amin, amax));
 }
 
-#ifdef ROTSPRITE
 // Function: A_RollAngle
 //
 // Description: Changes the roll angle.
@@ -8663,16 +8660,10 @@ void A_RollAngle(mobj_t *actor)
 //
 void A_ChangeRollAngleRelative(mobj_t *actor)
 {
-	// Oh god, the old code /sucked/. Changed this and the absolute version to get a random range using amin and amax instead of
-	//  getting a random angle from the _entire_ spectrum and then clipping. While we're at it, do the angle conversion to the result
-	//  rather than the ranges, so <0 and >360 work as possible values. -Red
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
-	//angle_t angle = (P_RandomByte()+1)<<24;
 	const fixed_t amin = locvar1*FRACUNIT;
 	const fixed_t amax = locvar2*FRACUNIT;
-	//const angle_t amin = FixedAngle(locvar1*FRACUNIT);
-	//const angle_t amax = FixedAngle(locvar2*FRACUNIT);
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_ChangeRollAngleRelative", actor))
 		return;
@@ -8682,11 +8673,6 @@ void A_ChangeRollAngleRelative(mobj_t *actor)
 	if (amin > amax)
 		I_Error("A_ChangeRollAngleRelative: var1 is greater than var2");
 #endif
-/*
-	if (angle < amin)
-		angle = amin;
-	if (angle > amax)
-		angle = amax;*/
 
 	actor->rollangle += FixedAngle(P_RandomRange(amin, amax));
 }
@@ -8702,11 +8688,8 @@ void A_ChangeRollAngleAbsolute(mobj_t *actor)
 {
 	INT32 locvar1 = var1;
 	INT32 locvar2 = var2;
-	//angle_t angle = (P_RandomByte()+1)<<24;
 	const fixed_t amin = locvar1*FRACUNIT;
 	const fixed_t amax = locvar2*FRACUNIT;
-	//const angle_t amin = FixedAngle(locvar1*FRACUNIT);
-	//const angle_t amax = FixedAngle(locvar2*FRACUNIT);
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_ChangeRollAngleAbsolute", actor))
 		return;
@@ -8716,15 +8699,9 @@ void A_ChangeRollAngleAbsolute(mobj_t *actor)
 	if (amin > amax)
 		I_Error("A_ChangeRollAngleAbsolute: var1 is greater than var2");
 #endif
-/*
-	if (angle < amin)
-		angle = amin;
-	if (angle > amax)
-		angle = amax;*/
 
 	actor->rollangle = FixedAngle(P_RandomRange(amin, amax));
 }
-#endif // ROTSPRITE
 
 // Function: A_PlaySound
 //
diff --git a/src/p_inter.c b/src/p_inter.c
index 2645c5e62fcfba2769fc7dc238ae94d133b99404..e8095ebfc84c2d547ae07cba94f844657ee2f75a 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -625,7 +625,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			P_AddPlayerScore(player, 1000);
 
-			if (gametype != GT_COOP || modeattacking) // score only?
+			if (!(gametyperules & GTR_SPECIALSTAGES) || modeattacking) // score only?
 			{
 				S_StartSound(toucher, sfx_chchng);
 				break;
@@ -1888,7 +1888,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 	char targetname[MAXPLAYERNAME+4];
 	char sourcename[MAXPLAYERNAME+4];
 
-	if (G_PlatformGametype())
+	if (!(gametyperules & (GTR_RINGSLINGER|GTR_HURTMESSAGES)))
 		return; // Not in coop, etc.
 
 	if (!player)
@@ -2093,7 +2093,7 @@ void P_CheckTimeLimit(void)
 	if (!(multiplayer || netgame))
 		return;
 
-	if (G_PlatformGametype())
+	if (!(gametyperules & GTR_TIMELIMIT))
 		return;
 
 	if (leveltime < timelimitintics)
@@ -2124,7 +2124,7 @@ void P_CheckTimeLimit(void)
 	}
 
 	//Optional tie-breaker for Match/CTF
-	else if (cv_overtime.value)
+	else if ((cv_overtime.value) && (gametyperules & GTR_OVERTIME))
 	{
 		INT32 playerarray[MAXPLAYERS];
 		INT32 tempplayer = 0;
@@ -2206,7 +2206,7 @@ void P_CheckPointLimit(void)
 	if (!(multiplayer || netgame))
 		return;
 
-	if (G_PlatformGametype())
+	if (!(gametyperules & GTR_POINTLIMIT))
 		return;
 
 	// pointlimit is nonzero, check if it's been reached by this player
@@ -2389,7 +2389,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	{
 		if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording!
 			G_StopMetalRecording(true);
-		if (gametype == GT_MATCH // note, no team match suicide penalty
+		if ((gametyperules & GTR_DEATHPENALTY) // note, no team match suicide penalty
 			&& ((target == source) || (source == NULL && inflictor == NULL) || (source && !source->player)))
 		{ // Suicide penalty
 			if (target->player->score >= 50)
@@ -2962,7 +2962,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	   Graue 12-22-2003 */
 }
 
-static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
+static void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 {
 	player_t *player = target->player;
 	tic_t oldnightstime = player->nightstime;
@@ -2978,7 +2978,7 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 		player->flyangle += 180; // Shuffle's BETTERNIGHTSMOVEMENT?
 		player->flyangle %= 360;
 
-		if (gametype == GT_RACE || gametype == GT_COMPETITION)
+		if (gametyperules & GTR_RACE)
 			player->drillmeter -= 5*20;
 		else
 		{
@@ -3005,9 +3005,7 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 		P_SetPlayerMobjState(target, S_PLAY_NIGHTS_STUN);
 		S_StartSound(target, sfx_nghurt);
 
-#ifdef ROTSPRITE
 		player->mo->rollangle = 0;
-#endif
 
 		if (oldnightstime > 10*TICRATE
 			&& player->nightstime < 10*TICRATE)
@@ -3028,7 +3026,7 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 	}
 }
 
-static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
+static boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
 {
 	player_t *player = target->player;
 	(void)damage; //unused parm
@@ -3042,7 +3040,7 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 		return false;
 
 	// Ignore IT players shooting each other, unless friendlyfire is on.
-	if ((player->pflags & PF_TAGIT && !((cv_friendlyfire.value || (damagetype & DMG_CANHURTSELF)) &&
+	if ((player->pflags & PF_TAGIT && !((cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE) || (damagetype & DMG_CANHURTSELF)) &&
 		source && source->player && source->player->pflags & PF_TAGIT)))
 	{
 		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
@@ -3058,7 +3056,7 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 
 	// Don't allow players on the same team to hurt one another,
 	// unless cv_friendlyfire is on.
-	if (!(cv_friendlyfire.value || (damagetype & DMG_CANHURTSELF)) && (player->pflags & PF_TAGIT) == (source->player->pflags & PF_TAGIT))
+	if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE) || (damagetype & DMG_CANHURTSELF)) && (player->pflags & PF_TAGIT) == (source->player->pflags & PF_TAGIT))
 	{
 		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
 		{
@@ -3132,7 +3130,7 @@ static inline boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *sou
 	return true;
 }
 
-static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
+static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
 {
 	player_t *player = target->player;
 
@@ -3143,7 +3141,7 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 			return false;
 
 		// In COOP/RACE, you can't hurt other players unless cv_friendlyfire is on
-		if (!cv_friendlyfire.value && (G_PlatformGametype()))
+		if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)) && (G_PlatformGametype()))
 		{
 			if (gametype == GT_COOP && inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK)) // co-op only
 			{
@@ -3166,7 +3164,7 @@ static inline boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj
 	{
 		// Don't allow players on the same team to hurt one another,
 		// unless cv_friendlyfire is on.
-		if (!cv_friendlyfire.value && target->player->ctfteam == source->player->ctfteam)
+		if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)) && target->player->ctfteam == source->player->ctfteam)
 		{
 			if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
 			{
@@ -3224,7 +3222,7 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
 		player->mo->flags2 &= ~MF2_DONTDRAW;
 
 	P_SetPlayerMobjState(player->mo, player->mo->info->deathstate);
-	if (gametype == GT_CTF && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
 	{
 		P_PlayerFlagBurst(player, false);
 		if (source && source->player)
@@ -3349,7 +3347,7 @@ static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source,
 	else
 		S_StartSound (player->mo, sfx_shldls); // Ba-Dum! Shield loss.
 
-	if (gametype == GT_CTF && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
 	{
 		P_PlayerFlagBurst(player, false);
 		if (source && source->player)
@@ -3383,7 +3381,7 @@ static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, IN
 			P_AddPlayerScore(source->player, 50);
 	}
 
-	if (gametype == GT_CTF && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
+	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
 	{
 		P_PlayerFlagBurst(player, false);
 		if (source && source->player)
@@ -3426,6 +3424,24 @@ void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 	if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super])
 		return;
 
+	if (!cv_friendlyfire.value)
+	{
+		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
+		{
+			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
+			{
+				P_SwitchShield(player, SH_PINK);
+				S_StartSound(player->mo, mobjinfo[MT_PITY_ICON].seesound);
+			}
+		}
+
+		if (source->player->ctfteam == player->ctfteam)
+			return;
+	}
+
+	if (inflictor->type == MT_LHRT)
+		return;
+
 	if (player->powers[pw_shield] || player->bot)  //If One-Hit Shield
 	{
 		P_RemoveShield(player);
@@ -3442,7 +3458,7 @@ void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 
 	P_DoPlayerPain(player, inflictor, source);
 
-	if (gametype == GT_CTF && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
+	if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
 		P_PlayerFlagBurst(player, false);
 
 	if (oldnightstime > 10*TICRATE
@@ -3593,7 +3609,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 			{
 				if (source == target)
 					return false; // Don't hit yourself with your own paraloop, baka
-				if (source && source->player && !cv_friendlyfire.value
+				if (source && source->player && !(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE))
 				&& (gametype == GT_COOP
 				|| (G_GametypeHasTeams() && player->ctfteam == source->player->ctfteam)))
 					return false; // Don't run eachother over in special stages and team games and such
@@ -3688,7 +3704,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 		// by friendly fire. Spilling their rings and other items is enough.
 		else if (!force && G_GametypeHasTeams()
 			&& source && source->player && (source->player->ctfteam == player->ctfteam)
-			&& cv_friendlyfire.value)
+			&& (cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)))
 		{
 			damage = 0;
 			P_ShieldDamage(player, inflictor, source, damage, damagetype);
diff --git a/src/p_local.h b/src/p_local.h
index a5fd0eaa87e5f914a507060a5611aaf9ad83e328..a5f95af02940b0ec37759861af990e2a3089f7c9 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -115,10 +115,10 @@ typedef struct camera_s
 
 extern camera_t camera, camera2;
 extern consvar_t cv_cam_dist, cv_cam_still, cv_cam_height;
-extern consvar_t cv_cam_speed, cv_cam_rotate, cv_cam_rotspeed, cv_cam_orbit, cv_cam_adjust;
+extern consvar_t cv_cam_speed, cv_cam_rotate, cv_cam_rotspeed, cv_cam_turnmultiplier, cv_cam_orbit, cv_cam_adjust;
 
 extern consvar_t cv_cam2_dist, cv_cam2_still, cv_cam2_height;
-extern consvar_t cv_cam2_speed, cv_cam2_rotate, cv_cam2_rotspeed, cv_cam2_orbit, cv_cam2_adjust;
+extern consvar_t cv_cam2_speed, cv_cam2_rotate, cv_cam2_rotspeed, cv_cam2_turnmultiplier, cv_cam2_orbit, cv_cam2_adjust;
 
 extern fixed_t t_cam_dist, t_cam_height, t_cam_rotate;
 extern fixed_t t_cam2_dist, t_cam2_height, t_cam2_rotate;
diff --git a/src/p_map.c b/src/p_map.c
index 2d36f747cc21bd8e96a2dcfbbd0b94d6c0bfb8a6..1b6f23cdefad839512185180d57b3f161b7c8b94 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -615,7 +615,7 @@ static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 	// Why block opposing teams from tailsflying each other?
 	// Sneaking into the hands of a flying tails player in Race might be a viable strategy, who knows.
 	/*
-	if (gametype == GT_RACE || gametype == GT_COMPETITION
+	if ((gametyperules & GTR_RACE)
 		|| (netgame && (tails->spectator || sonic->spectator))
 		|| (G_TagGametype() && (!(tails->pflags & PF_TAGIT) != !(sonic->pflags & PF_TAGIT)))
 		|| (gametype == GT_MATCH)
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 9b88758c6e48114df15511025ae0053806aa4a60..cd400f400506200566e89051d7c343035c85469c 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -7143,2904 +7143,3054 @@ static void P_PyreFlyBurn(mobj_t *mobj, fixed_t hoffs, INT16 vrange, mobjtype_t
 	particle->momz = momz;
 }
 
-//
-// P_MobjThinker
-//
-void P_MobjThinker(mobj_t *mobj)
+static void P_MobjScaleThink(mobj_t *mobj)
 {
-	I_Assert(mobj != NULL);
-	I_Assert(!P_MobjWasRemoved(mobj));
-
-	if (mobj->flags & MF_NOTHINK)
-		return;
-
-	if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->extrainfo)))
-		return;
+	fixed_t oldheight = mobj->height;
+	UINT8 correctionType = 0; // Don't correct Z position, just gain height
 
-	// Remove dead target/tracer.
-	if (mobj->target && P_MobjWasRemoved(mobj->target))
-		P_SetTarget(&mobj->target, NULL);
-	if (mobj->tracer && P_MobjWasRemoved(mobj->tracer))
-		P_SetTarget(&mobj->tracer, NULL);
-	if (mobj->hnext && P_MobjWasRemoved(mobj->hnext))
-		P_SetTarget(&mobj->hnext, NULL);
-	if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
-		P_SetTarget(&mobj->hprev, NULL);
+	if ((mobj->flags & MF_NOCLIPHEIGHT || (mobj->z > mobj->floorz && mobj->z + mobj->height < mobj->ceilingz))
+		&& mobj->type != MT_EGGMOBILE_FIRE)
+		correctionType = 1; // Correct Z position by centering
+	else if (mobj->eflags & MFE_VERTICALFLIP)
+		correctionType = 2; // Correct Z position by moving down
+
+	if (abs(mobj->scale - mobj->destscale) < mobj->scalespeed)
+		P_SetScale(mobj, mobj->destscale);
+	else if (mobj->scale < mobj->destscale)
+		P_SetScale(mobj, mobj->scale + mobj->scalespeed);
+	else if (mobj->scale > mobj->destscale)
+		P_SetScale(mobj, mobj->scale - mobj->scalespeed);
+
+	if (correctionType == 1)
+		mobj->z -= (mobj->height - oldheight)/2;
+	else if (correctionType == 2)
+		mobj->z -= mobj->height - oldheight;
+
+	if (mobj->scale == mobj->destscale)
+		/// \todo Lua hook for "reached destscale"?
+		switch (mobj->type)
+		{
+		case MT_EGGMOBILE_FIRE:
+			mobj->destscale = FRACUNIT;
+			mobj->scalespeed = FRACUNIT>>4;
+			break;
+		default:
+			break;
+		}
+}
 
-	mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG);
+static void P_MaceSceneryThink(mobj_t *mobj)
+{
+	angle_t oldmovedir = mobj->movedir;
 
-	tmfloorthing = tmhitthing = NULL;
+	// Always update movedir to prevent desyncing (in the traditional sense, not the netplay sense).
+	mobj->movedir = (mobj->movedir + mobj->lastlook) & FINEMASK;
 
-	// 970 allows ANY mobj to trigger a linedef exec
-	if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
+	// If too far away and not deliberately spitting in the face of optimisation, don't think!
+	if (!(mobj->flags2 & MF2_BOSSNOTRAP))
 	{
-		sector_t *sec2;
-
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
-		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-			P_LinedefExecute(sec2->tag, mobj, sec2);
-	}
+		UINT8 i;
+		// Quick! Look through players! Don't move unless a player is relatively close by.
+		// The below is selected based on CEZ2's first room. I promise you it is a coincidence that it looks like the weed number.
+		for (i = 0; i < MAXPLAYERS; ++i)
+			if (playeringame[i] && players[i].mo
+				&& P_AproxDistance(P_AproxDistance(mobj->x - players[i].mo->x, mobj->y - players[i].mo->y), mobj->z - players[i].mo->z) < (4200 << FRACBITS))
+				break; // Stop looking.
+		if (i == MAXPLAYERS)
+		{
+			if (!(mobj->flags2 & MF2_BEYONDTHEGRAVE))
+			{
+				mobj_t *ref = mobj;
 
-	// Slowly scale up/down to reach your destscale.
-	if (mobj->scale != mobj->destscale)
-	{
-		fixed_t oldheight = mobj->height;
-		UINT8 correctionType = 0; // Don't correct Z position, just gain height
+				// stop/hide all your babies
+				while ((ref = ref->hnext))
+				{
+					ref->eflags = (((ref->flags & MF_NOTHINK) ? 0 : 1)
+						| ((ref->flags & MF_NOCLIPTHING) ? 0 : 2)
+						| ((ref->flags2 & MF2_DONTDRAW) ? 0 : 4)); // oh my god this is nasty.
+					ref->flags |= MF_NOTHINK|MF_NOCLIPTHING;
+					ref->flags2 |= MF2_DONTDRAW;
+					ref->momx = ref->momy = ref->momz = 0;
+				}
 
-		if ((mobj->flags & MF_NOCLIPHEIGHT || (mobj->z > mobj->floorz && mobj->z + mobj->height < mobj->ceilingz))
-		&& mobj->type != MT_EGGMOBILE_FIRE)
-			correctionType = 1; // Correct Z position by centering
-		else if (mobj->eflags & MFE_VERTICALFLIP)
-			correctionType = 2; // Correct Z position by moving down
-
-		if (abs(mobj->scale - mobj->destscale) < mobj->scalespeed)
-			P_SetScale(mobj, mobj->destscale);
-		else if (mobj->scale < mobj->destscale)
-			P_SetScale(mobj, mobj->scale + mobj->scalespeed);
-		else if (mobj->scale > mobj->destscale)
-			P_SetScale(mobj, mobj->scale - mobj->scalespeed);
-
-		if (correctionType == 1)
-			mobj->z -= (mobj->height - oldheight)/2;
-		else if (correctionType == 2)
-			mobj->z -= mobj->height - oldheight;
+				mobj->flags2 |= MF2_BEYONDTHEGRAVE;
+			}
+			return; // don't make bubble!
+		}
+		else if (mobj->flags2 & MF2_BEYONDTHEGRAVE)
+		{
+			mobj_t *ref = mobj;
 
-		if (mobj->scale == mobj->destscale)
-			/// \todo Lua hook for "reached destscale"?
-			switch(mobj->type)
+			// start/show all your babies
+			while ((ref = ref->hnext))
 			{
-			case MT_EGGMOBILE_FIRE:
-				mobj->destscale = FRACUNIT;
-				mobj->scalespeed = FRACUNIT>>4;
-				break;
-			default:
-				break;
+				if (ref->eflags & 1)
+					ref->flags &= ~MF_NOTHINK;
+				if (ref->eflags & 2)
+					ref->flags &= ~MF_NOCLIPTHING;
+				if (ref->eflags & 4)
+					ref->flags2 &= ~MF2_DONTDRAW;
+				ref->eflags = 0; // le sign
 			}
-	}
 
-	if ((mobj->type == MT_GHOST || mobj->type == MT_THOK) && mobj->fuse > 0) // Not guaranteed to be MF_SCENERY or not MF_SCENERY!
-	{
-		if (mobj->flags2 & MF2_BOSSNOTRAP) // "fast" flag
-		{
-			if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - (2*mobj->fuse)/3)
-				// fade out when nearing the end of fuse...
-				mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - (2*mobj->fuse)/3) << FF_TRANSSHIFT);
-		}
-		else
-		{
-			if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - mobj->fuse / 2)
-				// fade out when nearing the end of fuse...
-				mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - mobj->fuse / 2) << FF_TRANSSHIFT);
+			mobj->flags2 &= ~MF2_BEYONDTHEGRAVE;
 		}
 	}
 
-	// Special thinker for scenery objects
-	if (mobj->flags & MF_SCENERY)
-	{
-#ifdef HAVE_BLUA
-		if (LUAh_MobjThinker(mobj))
-			return;
-		if (P_MobjWasRemoved(mobj))
-			return;
-#endif
+	// Okay, time to MOVE
+	P_MaceRotate(mobj, mobj->movedir, oldmovedir);
+}
 
-		if (mobj->flags2 & MF2_SHIELD)
-			if (!P_AddShield(mobj))
-				return;
+static boolean P_DrownNumbersSceneryThink(mobj_t *mobj)
+{
+	if (!mobj->target)
+	{
+		P_RemoveMobj(mobj);
+		return false;
+	}
+	if (!mobj->target->player || !(mobj->target->player->powers[pw_underwater] || mobj->target->player->powers[pw_spacetime]))
+	{
+		P_RemoveMobj(mobj);
+		return false;
+	}
+	mobj->x = mobj->target->x;
+	mobj->y = mobj->target->y;
 
-		switch (mobj->type)
-		{
-			case MT_BOSSJUNK:
-				mobj->flags2 ^= MF2_DONTDRAW;
-				break;
-			case MT_MACEPOINT:
-			case MT_CHAINMACEPOINT:
-			case MT_SPRINGBALLPOINT:
-			case MT_CHAINPOINT:
-			case MT_FIREBARPOINT:
-			case MT_CUSTOMMACEPOINT:
-			case MT_HIDDEN_SLING:
-				{
-					angle_t oldmovedir = mobj->movedir;
+	mobj->destscale = mobj->target->destscale;
+	P_SetScale(mobj, mobj->target->scale);
 
-					// Always update movedir to prevent desyncing (in the traditional sense, not the netplay sense).
-					mobj->movedir = (mobj->movedir + mobj->lastlook) & FINEMASK;
+	if (mobj->target->eflags & MFE_VERTICALFLIP)
+	{
+		mobj->z = mobj->target->z - FixedMul(16*FRACUNIT, mobj->target->scale) - mobj->height;
+		if (mobj->target->player->pflags & PF_FLIPCAM)
+			mobj->eflags |= MFE_VERTICALFLIP;
+	}
+	else
+		mobj->z = mobj->target->z + (mobj->target->height) + FixedMul(8*FRACUNIT, mobj->target->scale); // Adjust height for height changes
 
-					// If too far away and not deliberately spitting in the face of optimisation, don't think!
-					if (!(mobj->flags2 & MF2_BOSSNOTRAP))
-					{
-						UINT8 i;
-						// Quick! Look through players! Don't move unless a player is relatively close by.
-						// The below is selected based on CEZ2's first room. I promise you it is a coincidence that it looks like the weed number.
-						for (i = 0; i < MAXPLAYERS; ++i)
-							if (playeringame[i] && players[i].mo
-							 && P_AproxDistance(P_AproxDistance(mobj->x - players[i].mo->x, mobj->y - players[i].mo->y), mobj->z - players[i].mo->z) < (4200<<FRACBITS))
-								break; // Stop looking.
-						if (i == MAXPLAYERS)
-						{
-							if (!(mobj->flags2 & MF2_BEYONDTHEGRAVE))
-							{
-								mobj_t *ref = mobj;
+	if (mobj->threshold <= 35)
+		mobj->flags2 |= MF2_DONTDRAW;
+	else
+		mobj->flags2 &= ~MF2_DONTDRAW;
+	if (mobj->threshold <= 30)
+		mobj->threshold = 40;
+	mobj->threshold--;
+	return true;
+}
 
-								// stop/hide all your babies
-								while ((ref = ref->hnext))
-								{
-									ref->eflags = (((ref->flags & MF_NOTHINK) ? 0 : 1)
-										| ((ref->flags & MF_NOCLIPTHING) ? 0 : 2)
-										| ((ref->flags2 & MF2_DONTDRAW) ? 0 : 4)); // oh my god this is nasty.
-									ref->flags |= MF_NOTHINK|MF_NOCLIPTHING;
-									ref->flags2 |= MF2_DONTDRAW;
-									ref->momx = ref->momy = ref->momz = 0;
-								}
+static void P_FlameJetSceneryThink(mobj_t *mobj)
+{
+	mobj_t *flame;
+	fixed_t strength;
 
-								mobj->flags2 |= MF2_BEYONDTHEGRAVE;
-							}
+	if (!(mobj->flags2 & MF2_FIRING))
+		return;
 
-							break; // don't make bubble!
-						}
-						else if (mobj->flags2 & MF2_BEYONDTHEGRAVE)
-						{
-							mobj_t *ref = mobj;
+	if ((leveltime & 3) == 0)
+		return;
 
-							// start/show all your babies
-							while ((ref = ref->hnext))
-							{
-								if (ref->eflags & 1)
-									ref->flags &= ~MF_NOTHINK;
-								if (ref->eflags & 2)
-									ref->flags &= ~MF_NOCLIPTHING;
-								if (ref->eflags & 4)
-									ref->flags2 &= ~MF2_DONTDRAW;
-								ref->eflags = 0; // le sign
-							}
+	// Wave the flames back and forth. Reactiontime determines which direction it's going.
+	if (mobj->fuse <= -16)
+		mobj->reactiontime = 1;
+	else if (mobj->fuse >= 16)
+		mobj->reactiontime = 0;
 
-							mobj->flags2 &= ~MF2_BEYONDTHEGRAVE;
-						}
-					}
+	if (mobj->reactiontime)
+		mobj->fuse += 2;
+	else
+		mobj->fuse -= 2;
 
-					// Okay, time to MOVE
-					P_MaceRotate(mobj, mobj->movedir, oldmovedir);
-				}
-				break;
-			case MT_HOOP:
-				if (mobj->fuse > 1)
-					P_MoveHoop(mobj);
-				else if (mobj->fuse == 1)
-					mobj->movecount = 1;
+	flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
+	P_SetMobjState(flame, S_FLAMEJETFLAME4);
 
-				if (mobj->movecount)
-				{
-					mobj->fuse++;
+	flame->angle = mobj->angle;
 
-					if (mobj->fuse > 32)
-					{
-						// Don't kill the hoop center. For the sake of respawning.
-						//if (mobj->target)
-						//	P_RemoveMobj(mobj->target);
+	if (mobj->flags2 & MF2_AMBUSH) // Wave up and down instead of side-to-side
+		flame->momz = mobj->fuse << (FRACBITS - 2);
+	else
+		flame->angle += FixedAngle(mobj->fuse<<FRACBITS);
 
-						P_RemoveMobj(mobj);
-					}
-				}
-				else
-					mobj->fuse--;
-				return;
-			case MT_NIGHTSPARKLE:
-				if (mobj->tics != -1)
-				{
-					mobj->tics--;
+	strength = 20*FRACUNIT;
+	strength -= ((20*FRACUNIT)/16)*mobj->movedir;
 
-					// you can cycle through multiple states in a tic
-					if (!mobj->tics)
-						if (!P_SetMobjState(mobj, mobj->state->nextstate))
-							return; // freed itself
-				}
+	P_InstaThrust(flame, flame->angle, strength);
+	S_StartSound(flame, sfx_fire);
+}
 
-				P_UnsetThingPosition(mobj);
-				mobj->x += mobj->momx;
-				mobj->y += mobj->momy;
-				mobj->z += mobj->momz;
-				P_SetThingPosition(mobj);
-				return;
-			case MT_NIGHTSLOOPHELPER:
-				if (--mobj->tics <= 0)
-					P_RemoveMobj(mobj);
+static void P_VerticalFlameJetSceneryThink(mobj_t *mobj)
+{
+	mobj_t *flame;
+	fixed_t strength;
 
-				// Don't touch my fuse!
-				return;
-			case MT_OVERLAY:
-				if (!mobj->target)
-				{
-					P_RemoveMobj(mobj);
-					return;
-				}
-				else
-					P_AddOverlay(mobj);
-				break;
-			case MT_PITY_ORB:
-			case MT_WHIRLWIND_ORB:
-			case MT_ARMAGEDDON_ORB:
-				if (!(mobj->flags2 & MF2_SHIELD))
-					return;
-				break;
-			case MT_ATTRACT_ORB:
-				if (!(mobj->flags2 & MF2_SHIELD))
-					return;
-				if (/*(mobj->target) -- the following is implicit by P_AddShield
-				&& (mobj->target->player)
-				&& */ (mobj->target->player->homing) && (mobj->target->player->pflags & PF_SHIELDABILITY))
-				{
-					P_SetMobjState(mobj, mobj->info->painstate);
-					mobj->tics++;
-				}
-				break;
-			case MT_ELEMENTAL_ORB:
-				if (!(mobj->flags2 & MF2_SHIELD))
-					return;
-				if (mobj->tracer
-				/* && mobj->target -- the following is implicit by P_AddShield
-				&& mobj->target->player
-				&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL */
-				&& mobj->target->player->pflags & PF_SHIELDABILITY
-				&& ((statenum_t)(mobj->tracer->state-states) < mobj->info->raisestate
-					|| (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
-				{
-					P_SetMobjState(mobj, mobj->info->painstate);
-					mobj->tics++;
-					P_SetMobjState(mobj->tracer, mobj->info->raisestate);
-					mobj->tracer->tics++;
-				}
-				break;
-			case MT_FORCE_ORB:
-				if (!(mobj->flags2 & MF2_SHIELD))
-					return;
-				if (/*
-				&& mobj->target -- the following is implicit by P_AddShield
-				&& mobj->target->player
-				&& (mobj->target->player->powers[pw_shield] & SH_FORCE)
-				&& */ (mobj->target->player->pflags & PF_SHIELDABILITY))
-				{
-					mobj_t *whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
-					P_SetMobjState(whoosh, mobj->info->raisestate);
-					whoosh->destscale = whoosh->scale<<1;
-					whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
-					whoosh->height = 38*whoosh->scale;
-					whoosh->fuse = 10;
-					whoosh->flags |= MF_NOCLIPHEIGHT;
-					whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames
-					mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal whoosh
-				}
-				/* FALLTHRU */
-			case MT_FLAMEAURA_ORB:
-				if (!(mobj->flags2 & MF2_SHIELD))
-					return;
-				if ((statenum_t)(mobj->state-states) < mobj->info->painstate)
-					mobj->angle = mobj->target->angle; // implicitly okay because of P_AddShield
-				if (mobj->tracer
-				/* && mobj->target -- the following is implicit by P_AddShield
-				&& mobj->target->player
-				&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_FLAMEAURA */
-				&& mobj->target->player->pflags & PF_SHIELDABILITY
-				&& ((statenum_t)(mobj->tracer->state-states) < mobj->info->raisestate
-					|| (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
-				{
-					P_SetMobjState(mobj, mobj->info->painstate);
-					mobj->tics++;
-					P_SetMobjState(mobj->tracer, mobj->info->raisestate);
-					mobj->tracer->tics++;
-				}
-				break;
-			case MT_BUBBLEWRAP_ORB:
-				if (!(mobj->flags2 & MF2_SHIELD))
-					return;
-				if (mobj->tracer
-				/* && mobj->target -- the following is implicit by P_AddShield
-				&& mobj->target->player
-				&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP */
-				)
-				{
-					if (mobj->target->player->pflags & PF_SHIELDABILITY
-					&& ((statenum_t)(mobj->state-states) < mobj->info->painstate
-						|| (mobj->state->nextstate < mobj->info->painstate && mobj->tics == 1)))
-					{
-						P_SetMobjState(mobj, mobj->info->painstate);
-						mobj->tics++;
-						P_SetMobjState(mobj->tracer, mobj->info->raisestate);
-						mobj->tracer->tics++;
-					}
-					else if (mobj->target->eflags & MFE_JUSTHITFLOOR
-					&& (statenum_t)(mobj->state-states) == mobj->info->painstate)
-					{
-						P_SetMobjState(mobj, mobj->info->painstate+1);
-						mobj->tics++;
-						P_SetMobjState(mobj->tracer, mobj->info->raisestate+1);
-						mobj->tracer->tics++;
-					}
-				}
-				break;
-			case MT_THUNDERCOIN_ORB:
-				if (!(mobj->flags2 & MF2_SHIELD))
-					return;
-				if (mobj->tracer
-				/* && mobj->target -- the following is implicit by P_AddShield
-				&& mobj->target->player
-				&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_THUNDERCOIN */
-				&& (mobj->target->player->pflags & PF_SHIELDABILITY))
-				{
-					P_SetMobjState(mobj, mobj->info->painstate);
-					mobj->tics++;
-					P_SetMobjState(mobj->tracer, mobj->info->raisestate);
-					mobj->tracer->tics++;
-					mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal spark
-				}
-				break;
-			case MT_WATERDROP:
-				P_SceneryCheckWater(mobj);
-				if ((mobj->z <= mobj->floorz || mobj->z <= mobj->watertop)
-					&& mobj->health > 0)
-				{
-					mobj->health = 0;
-					P_SetMobjState(mobj, mobj->info->deathstate);
-					S_StartSound(mobj, mobj->info->deathsound+P_RandomKey(mobj->info->mass));
-					return;
-				}
-				break;
-			case MT_BUBBLES:
-				P_SceneryCheckWater(mobj);
-				break;
-			case MT_SMALLBUBBLE:
-			case MT_MEDIUMBUBBLE:
-			case MT_EXTRALARGEBUBBLE:	// start bubble dissipate
-				P_SceneryCheckWater(mobj);
-				if (P_MobjWasRemoved(mobj)) // bubble was removed by not being in water
-					return;
-				if (!(mobj->eflags & MFE_UNDERWATER)
-					|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz)
-					|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z <= mobj->floorz)
-					|| (P_CheckDeathPitCollide(mobj))
-					|| --mobj->fuse <= 0) // Bubbles eventually dissipate if they can't reach the surface.
-				{
-					// no playing sound: no point; the object is being removed
-					P_RemoveMobj(mobj);
-					return;
-				}
-				break;
-			case MT_LOCKON:
-				if (!mobj->target)
-				{
-					P_RemoveMobj(mobj);
-					return;
-				}
+	if (!(mobj->flags2 & MF2_FIRING))
+		return;
 
-				mobj->flags2 &= ~MF2_DONTDRAW;
+	if ((leveltime & 3) == 0)
+		return;
 
-				mobj->x = mobj->target->x;
-				mobj->y = mobj->target->y;
+	// Wave the flames back and forth. Reactiontime determines which direction it's going.
+	if (mobj->fuse <= -16)
+		mobj->reactiontime = 1;
+	else if (mobj->fuse >= 16)
+		mobj->reactiontime = 0;
 
-				mobj->eflags |= (mobj->target->eflags & MFE_VERTICALFLIP);
+	if (mobj->reactiontime)
+		mobj->fuse++;
+	else
+		mobj->fuse--;
 
-				mobj->destscale = mobj->target->destscale;
-				P_SetScale(mobj, mobj->target->scale);
+	flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
 
-				if (!(mobj->eflags & MFE_VERTICALFLIP))
-					mobj->z = mobj->target->z + mobj->target->height + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale);
-				else
-					mobj->z = mobj->target->z - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale) - mobj->height;
-				break;
-			case MT_LOCKONINF:
-				if (!(mobj->flags2 & MF2_STRONGBOX))
-				{
-					mobj->threshold = mobj->z;
-					mobj->flags2 |= MF2_STRONGBOX;
-				}
-				if (!(mobj->eflags & MFE_VERTICALFLIP))
-					mobj->z = mobj->threshold + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
-				else
-					mobj->z = mobj->threshold - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
-				break;
-			case MT_DROWNNUMBERS:
-				if (!mobj->target)
-				{
-					P_RemoveMobj(mobj);
-					return;
-				}
-				if (!mobj->target->player || !(mobj->target->player->powers[pw_underwater] || mobj->target->player->powers[pw_spacetime]))
-				{
-					P_RemoveMobj(mobj);
-					return;
-				}
-				mobj->x = mobj->target->x;
-				mobj->y = mobj->target->y;
+	strength = 20*FRACUNIT;
+	strength -= ((20*FRACUNIT)/16)*mobj->movedir;
 
-				mobj->destscale = mobj->target->destscale;
-				P_SetScale(mobj, mobj->target->scale);
+	// If deaf'd, the object spawns on the ceiling.
+	if (mobj->flags2 & MF2_AMBUSH)
+	{
+		mobj->z = mobj->ceilingz - mobj->height;
+		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);
+}
 
-				if (mobj->target->eflags & MFE_VERTICALFLIP)
-				{
-					mobj->z = mobj->target->z - FixedMul(16*FRACUNIT, mobj->target->scale) - mobj->height;
-					if (mobj->target->player->pflags & PF_FLIPCAM)
-						mobj->eflags |= MFE_VERTICALFLIP;
-				}
-				else
-					mobj->z = mobj->target->z + (mobj->target->height) + FixedMul(8*FRACUNIT, mobj->target->scale); // Adjust height for height changes
+static boolean P_ParticleGenSceneryThink(mobj_t *mobj)
+{
+	if (!mobj->lastlook)
+		return false;
 
-				if (mobj->threshold <= 35)
-					mobj->flags2 |= MF2_DONTDRAW;
-				else
-					mobj->flags2 &= ~MF2_DONTDRAW;
-				if (mobj->threshold <= 30)
-					mobj->threshold = 40;
-				mobj->threshold--;
-				break;
-			case MT_FLAMEJET:
-				if ((mobj->flags2 & MF2_FIRING) && (leveltime & 3) == 0)
-				{
-					mobj_t *flame;
-					fixed_t strength;
+	if (!mobj->threshold)
+		return false;
 
-					// Wave the flames back and forth. Reactiontime determines which direction it's going.
-					if (mobj->fuse <= -16)
-						mobj->reactiontime = 1;
-					else if (mobj->fuse >= 16)
-						mobj->reactiontime = 0;
+	if (--mobj->fuse <= 0)
+	{
+		INT32 i = 0;
+		mobj_t *spawn;
+		fixed_t bottomheight, topheight;
+		INT32 type = mobj->threshold, line = mobj->cvmem;
 
-					if (mobj->reactiontime)
-						mobj->fuse += 2;
-					else
-						mobj->fuse -= 2;
+		mobj->fuse = (tic_t)mobj->reactiontime;
 
-					flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
-					P_SetMobjState(flame, S_FLAMEJETFLAME4);
+		bottomheight = lines[line].frontsector->floorheight;
+		topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
 
-					flame->angle = mobj->angle;
+		if (mobj->waterbottom != bottomheight || mobj->watertop != topheight)
+		{
+			if (mobj->movefactor && (topheight > bottomheight))
+				mobj->health = (tic_t)(FixedDiv((topheight - bottomheight), abs(mobj->movefactor)) >> FRACBITS);
+			else
+				mobj->health = 0;
 
-					if (mobj->flags2 & MF2_AMBUSH) // Wave up and down instead of side-to-side
-						flame->momz = mobj->fuse << (FRACBITS-2);
-					else
-						flame->angle += FixedAngle(mobj->fuse*FRACUNIT);
+			mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
+		}
 
-					strength = 20*FRACUNIT;
-					strength -= ((20*FRACUNIT)/16)*mobj->movedir;
+		if (!mobj->health)
+			return false;
 
-					P_InstaThrust(flame, flame->angle, strength);
-					S_StartSound(flame, sfx_fire);
-				}
-				break;
-			case MT_VERTICALFLAMEJET:
-				if ((mobj->flags2 & MF2_FIRING) && (leveltime & 3) == 0)
-				{
-					mobj_t *flame;
-					fixed_t strength;
+		for (i = 0; i < mobj->lastlook; i++)
+		{
+			spawn = P_SpawnMobj(
+				mobj->x + FixedMul(FixedMul(mobj->friction, mobj->scale), FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)),
+				mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle >> ANGLETOFINESHIFT)),
+				mobj->z,
+				(mobjtype_t)mobj->threshold);
+			P_SetScale(spawn, mobj->scale);
+			spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
+			spawn->destscale = spawn->scale/100;
+			spawn->scalespeed = spawn->scale/mobj->health;
+			spawn->tics = (tic_t)mobj->health;
+			spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
+			spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
 
-					// Wave the flames back and forth. Reactiontime determines which direction it's going.
-					if (mobj->fuse <= -16)
-						mobj->reactiontime = 1;
-					else if (mobj->fuse >= 16)
-						mobj->reactiontime = 0;
+			mobj->angle += mobj->movedir;
+		}
 
-					if (mobj->reactiontime)
-						mobj->fuse++;
-					else
-						mobj->fuse--;
+		mobj->angle += (angle_t)mobj->movecount;
+	}
 
-					flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
+	return true;
+}
 
-					strength = 20*FRACUNIT;
-					strength -= ((20*FRACUNIT)/16)*mobj->movedir;
+static void P_RosySceneryThink(mobj_t *mobj)
+{
+	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 deaf'd, the object spawns on the ceiling.
-					if (mobj->flags2 & MF2_AMBUSH)
-					{
-						mobj->z = mobj->ceilingz-mobj->height;
-						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);
-				}
-				break;
-			case MT_FLICKY_01_CENTER:
-			case MT_FLICKY_02_CENTER:
-			case MT_FLICKY_03_CENTER:
-			case MT_FLICKY_04_CENTER:
-			case MT_FLICKY_05_CENTER:
-			case MT_FLICKY_06_CENTER:
-			case MT_FLICKY_07_CENTER:
-			case MT_FLICKY_08_CENTER:
-			case MT_FLICKY_09_CENTER:
-			case MT_FLICKY_10_CENTER:
-			case MT_FLICKY_11_CENTER:
-			case MT_FLICKY_12_CENTER:
-			case MT_FLICKY_13_CENTER:
-			case MT_FLICKY_14_CENTER:
-			case MT_FLICKY_15_CENTER:
-			case MT_FLICKY_16_CENTER:
-			case MT_SECRETFLICKY_01_CENTER:
-			case MT_SECRETFLICKY_02_CENTER:
-				if (mobj->tracer && (mobj->flags & MF_NOCLIPTHING)
-					&& (mobj->flags & MF_GRENADEBOUNCE))
-					// for now: only do this bounce routine if flicky is in-place. \todo allow in all movements
-				{
-					if (!(mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z <= mobj->tracer->floorz)
-						mobj->tracer->momz = 7*FRACUNIT;
-					else if ((mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z >= mobj->tracer->ceilingz - mobj->tracer->height)
-						mobj->tracer->momz = -7*FRACUNIT;
-				}
-				break;
-			case MT_SEED:
-				if (P_MobjFlip(mobj)*mobj->momz < mobj->info->speed)
-					mobj->momz = P_MobjFlip(mobj)*mobj->info->speed;
-				break;
-			case MT_ROCKCRUMBLE1:
-			case MT_ROCKCRUMBLE2:
-			case MT_ROCKCRUMBLE3:
-			case MT_ROCKCRUMBLE4:
-			case MT_ROCKCRUMBLE5:
-			case MT_ROCKCRUMBLE6:
-			case MT_ROCKCRUMBLE7:
-			case MT_ROCKCRUMBLE8:
-			case MT_ROCKCRUMBLE9:
-			case MT_ROCKCRUMBLE10:
-			case MT_ROCKCRUMBLE11:
-			case MT_ROCKCRUMBLE12:
-			case MT_ROCKCRUMBLE13:
-			case MT_ROCKCRUMBLE14:
-			case MT_ROCKCRUMBLE15:
-			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])
-				{
-					P_SetMobjState(mobj, mobj->info->deathstate);
-					return;
-				}
-				break;
-			case MT_PARTICLEGEN:
-				if (!mobj->lastlook)
-					return;
-
-				if (!mobj->threshold)
-					return;
-
-				if (--mobj->fuse <= 0)
-				{
-					INT32 i = 0;
-					mobj_t *spawn;
-					fixed_t bottomheight, topheight;
-					INT32 type = mobj->threshold, line = mobj->cvmem;
-
-					mobj->fuse = (tic_t)mobj->reactiontime;
-
-					bottomheight = lines[line].frontsector->floorheight;
-					topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
-
-					if (mobj->waterbottom != bottomheight || mobj->watertop != topheight)
-					{
-						if (mobj->movefactor && (topheight > bottomheight))
-							mobj->health = (tic_t)(FixedDiv((topheight - bottomheight), abs(mobj->movefactor)) >> FRACBITS);
-						else
-							mobj->health = 0;
-
-						mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
-					}
-
-					if (!mobj->health)
-						return;
-
-					for (i = 0; i < mobj->lastlook; i++)
-					{
-						spawn = P_SpawnMobj(
-							mobj->x + FixedMul(FixedMul(mobj->friction, mobj->scale), FINECOSINE(mobj->angle>>ANGLETOFINESHIFT)),
-							mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle>>ANGLETOFINESHIFT)),
-							mobj->z,
-							(mobjtype_t)mobj->threshold);
-						P_SetScale(spawn, mobj->scale);
-						spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
-						spawn->destscale = spawn->scale/100;
-						spawn->scalespeed = spawn->scale/mobj->health;
-						spawn->tics = (tic_t)mobj->health;
-						spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
-						spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
-
-						mobj->angle += mobj->movedir;
-					}
-
-					mobj->angle += (angle_t)mobj->movecount;
-				}
-				break;
-			case MT_FSGNA:
-				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);
-						// Tatsuru: Don't try to hug them if they're above or below you!
-						targonground = (P_IsObjectOnGround(mobj->target) && (player->panim == PA_IDLE || player->panim == PA_WALK || player->panim == PA_RUN) && player->mo->z == mobj->z);
-						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, (INT32)mobj->movedir)*3;
-					if (strength < 10)
-						mobj->frame |= ((10-strength)<<(FF_TRANSSHIFT));
-				}
-				/* FALLTHRU */
-			default:
-				if (mobj->fuse)
-				{ // Scenery object fuse! Very basic!
-					mobj->fuse--;
-					if (!mobj->fuse)
-					{
-#ifdef HAVE_BLUA
-						if (!LUAh_MobjFuse(mobj))
-#endif
-						P_RemoveMobj(mobj);
-						return;
-					}
-				}
-				break;
+	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);
 		}
-
-		P_SceneryThinker(mobj);
-		return;
+		else if (P_MobjFlip(mobj)*mobj->momz < 0)
+			mobj->frame = mobj->state->frame + mobj->state->var1;
 	}
 
-#ifdef HAVE_BLUA
-	// Check for a Lua thinker first
-	if (!mobj->player)
-	{
-		if (LUAh_MobjThinker(mobj) || P_MobjWasRemoved(mobj))
-			return;
-	}
-	else if (!mobj->player->spectator)
-	{
-		// You cannot short-circuit the player thinker like you can other thinkers.
-		LUAh_MobjThinker(mobj);
-		if (P_MobjWasRemoved(mobj))
-			return;
-	}
-#endif
-	// if it's pushable, or if it would be pushable other than temporary disablement, use the
-	// separate thinker
-	if (mobj->flags & MF_PUSHABLE || (mobj->info->flags & MF_PUSHABLE && mobj->fuse))
+	if (!player)
 	{
-		P_MobjCheckWater(mobj);
-		P_PushableThinker(mobj);
-
-		// Extinguish fire objects in water. (Yes, it's extraordinarily rare to have a pushable flame object, but Brak uses such a case.)
-		if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
-			&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
+		if ((stat < S_ROSY_IDLE1 || stat > S_ROSY_IDLE4) && stat != S_ROSY_JUMP)
 		{
-			P_KillMobj(mobj, NULL, NULL, 0);
-			return;
+			mobj->momx = mobj->momy = 0;
+			P_SetMobjState(mobj, S_ROSY_IDLE1);
 		}
 	}
-	else if (mobj->flags & MF_BOSS)
+	else
 	{
-#ifdef HAVE_BLUA
-		if (LUAh_BossThinker(mobj))
+		boolean dojump = false, targonground, love, makeheart = false;
+		if (mobj->target != player->mo)
+			P_SetTarget(&mobj->target, player->mo);
+		// Tatsuru: Don't try to hug them if they're above or below you!
+		targonground = (P_IsObjectOnGround(mobj->target) && (player->panim == PA_IDLE || player->panim == PA_WALK || player->panim == PA_RUN) && player->mo->z == mobj->z);
+		love = (player->skin == 0 || player->skin == 5);
+
+		switch (stat)
 		{
-			if (P_MobjWasRemoved(mobj))
-				return;
-		}
-		else if (P_MobjWasRemoved(mobj))
-			return;
-		else
-#endif
-		switch (mobj->type)
+		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:
 		{
-			case MT_EGGMOBILE:
-				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
-				{
-					fixed_t rad = mobj->radius>>FRACBITS;
-					fixed_t hei = mobj->height>>FRACBITS;
-					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(hei/2, hei)<<FRACBITS,
-						MT_SMOKE);
-					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
-					particle->momz += mobj->momz;
-				}
-				if (mobj->flags2 & MF2_SKULLFLY)
-#if 1
-					P_SpawnGhostMobj(mobj);
-#else // all the way back from final demo... MT_THOK isn't even the same size anymore!
-				{
-					mobj_t *spawnmobj;
-					spawnmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->painchance);
-					P_SetTarget(&spawnmobj->target, mobj);
-					spawnmobj->color = SKINCOLOR_GREY;
-				}
-#endif
-				P_Boss1Thinker(mobj);
-				break;
-			case MT_EGGMOBILE2:
-				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
-				{
-					fixed_t rad = mobj->radius>>FRACBITS;
-					fixed_t hei = mobj->height>>FRACBITS;
-					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(hei/2, hei)<<FRACBITS,
-						MT_SMOKE);
-					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
-					particle->momz += mobj->momz;
-				}
-				P_Boss2Thinker(mobj);
-				break;
-			case MT_EGGMOBILE3:
-				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
+			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)
 				{
-					fixed_t rad = mobj->radius>>FRACBITS;
-					fixed_t hei = mobj->height>>FRACBITS;
-					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(hei/2, hei)<<FRACBITS,
-						MT_SMOKE);
-					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
-					particle->momz += mobj->momz;
+					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;
 				}
-				P_Boss3Thinker(mobj);
-				break;
-			case MT_EGGMOBILE4:
-				if (mobj->health < mobj->info->damage+1 && leveltime & 2)
+				if (max < (mobj->radius + mobj->target->radius))
 				{
-					fixed_t rad = mobj->radius>>FRACBITS;
-					fixed_t hei = mobj->height>>FRACBITS;
-					mobj_t *particle = P_SpawnMobjFromMobj(mobj,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(rad, -rad)<<FRACBITS,
-						P_RandomRange(hei/2, hei)<<FRACBITS,
-						MT_SMOKE);
-					P_SetObjectMomZ(particle, 2<<FRACBITS, false);
-					particle->momz += mobj->momz;
+					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;
+					}
 				}
-				P_Boss4Thinker(mobj);
-				break;
-			case MT_FANG:
-				P_Boss5Thinker(mobj);
-				break;
-			case MT_BLACKEGGMAN:
-				P_Boss7Thinker(mobj);
-				break;
-			case MT_METALSONIC_BATTLE:
-				P_Boss9Thinker(mobj);
-				break;
-			default: // Generic SOC-made boss
-				if (mobj->flags2 & MF2_SKULLFLY)
-					P_SpawnGhostMobj(mobj);
-				P_GenericBossThinker(mobj);
-				break;
-		}
-		if (mobj->flags2 & MF2_BOSSFLEE)
-		{
-			if (mobj->extravalue1)
-			{
-				if (!(--mobj->extravalue1))
+				else
 				{
-					if (mobj->target)
+					max /= 3;
+					if (max > 30*mobj->scale)
+						max = 30*mobj->scale;
+					if (mom > max && max > mobj->scale)
 					{
-						mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, P_AproxDistance(mobj->x-mobj->target->x,mobj->y-mobj->target->y)), mobj->scale<<1);
-						mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+						max = FixedDiv(max, mom);
+						mobj->momx = FixedMul(mobj->momx, max);
+						mobj->momy = FixedMul(mobj->momy, max);
 					}
-					else
-						mobj->momz = 8*mobj->scale;
+					if (abs(mobj->momx) > mobj->scale || abs(mobj->momy) > mobj->scale)
+						mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
 				}
-				else
-					mobj->angle += mobj->movedir;
-			}
-			else if (mobj->target)
-				P_InstaThrust(mobj, mobj->angle, FixedMul(12*FRACUNIT, mobj->scale));
-		}
-		if (mobj->type == MT_CYBRAKDEMON && !mobj->health)
-		{
-			if (!(mobj->tics & 1))
-			{
-				var1 = 2;
-				var2 = 0;
-				A_BossScream(mobj);
-			}
-			if (P_CheckDeathPitCollide(mobj))
-			{
-				P_RemoveMobj(mobj);
-				return;
-			}
-			if (mobj->momz && mobj->z+mobj->momz <= mobj->floorz)
-			{
-				S_StartSound(mobj, sfx_befall);
-				if (mobj->state != states+S_CYBRAKDEMON_DIE8)
-					P_SetMobjState(mobj, S_CYBRAKDEMON_DIE8);
 			}
+			else
+				dojump = true;
 		}
-	}
-	else if (mobj->health <= 0) // Dead things think differently than the living.
-		switch (mobj->type)
-		{
-		case MT_BLUESPHERE:
-			if ((mobj->tics>>2)+1 > 0 && (mobj->tics>>2)+1 <= tr_trans60) // tr_trans50 through tr_trans90, shifting once every second frame
-				mobj->frame = (NUMTRANSMAPS-((mobj->tics>>2)+1))<<FF_TRANSSHIFT;
-			else // tr_trans60 otherwise
-				mobj->frame = tr_trans60<<FF_TRANSSHIFT;
-			break;
-		case MT_EGGCAPSULE:
-			if (mobj->z <= mobj->floorz)
+		break;
+		case S_ROSY_HUG:
+			if (targonground)
 			{
-				P_RemoveMobj(mobj);
-				return;
+				player->pflags |= PF_STASIS;
+				if (mobj->cvmem < 5*TICRATE)
+					mobj->cvmem++;
+				if (love && !(leveltime & 7))
+					makeheart = true;
 			}
-			break;
-		case MT_FAKEMOBILE:
-			if (mobj->scale == mobj->destscale)
+			else
 			{
-				if (!mobj->fuse)
+				if (mobj->cvmem < (love ? 5*TICRATE : 0))
 				{
-					S_StartSound(mobj, sfx_s3k77);
-					mobj->flags2 |= MF2_DONTDRAW;
-					mobj->fuse = TICRATE;
+					P_SetMobjState(mobj, (stat = S_ROSY_PAIN));
+					S_StartSound(mobj, sfx_cdpcm7);
 				}
-				return;
-			}
-			if (!mobj->reactiontime)
-			{
-				if (P_RandomChance(FRACUNIT/2))
-					mobj->movefactor = FRACUNIT;
-				else
-					mobj->movefactor = -FRACUNIT;
-				if (P_RandomChance(FRACUNIT/2))
-					mobj->movedir = ANG20;
 				else
-					mobj->movedir = -ANG20;
-				mobj->reactiontime = 5;
-			}
-			mobj->momz += mobj->movefactor;
-			mobj->angle += mobj->movedir;
-			P_InstaThrust(mobj, mobj->angle, -mobj->info->speed);
-			mobj->reactiontime--;
-			break;
-		case MT_EGGSHIELD:
-			mobj->flags2 ^= MF2_DONTDRAW;
-			break;
-		case MT_EGGTRAP: // Egg Capsule animal release
-			if (mobj->fuse > 0)// && mobj->fuse < TICRATE-(TICRATE/7))
-			{
-				INT32 i;
-				fixed_t x,y,z;
-				fixed_t ns;
-				mobj_t *mo2;
-				mobj_t *flicky;
-
-				z = mobj->subsector->sector->floorheight + FRACUNIT + (P_RandomKey(64)<<FRACBITS);
-				for (i = 0; i < 3; i++)
-				{
-					const angle_t fa = P_RandomKey(FINEANGLES) & FINEMASK;
-					ns = 64 * FRACUNIT;
-					x = mobj->x + FixedMul(FINESINE(fa),ns);
-					y = mobj->y + FixedMul(FINECOSINE(fa),ns);
-
-					mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
-					P_SetMobjStateNF(mo2, S_XPLD_EGGTRAP); // so the flickies don't lose their target if they spawn
-					ns = 4 * FRACUNIT;
-					mo2->momx = FixedMul(FINESINE(fa),ns);
-					mo2->momy = FixedMul(FINECOSINE(fa),ns);
-					mo2->angle = fa << ANGLETOFINESHIFT;
-
-					if (!i && !(mobj->fuse & 2))
-						S_StartSound(mo2, mobj->info->deathsound);
-
-					flicky = P_InternalFlickySpawn(mo2, 0, 8*FRACUNIT, false, -1);
-					if (!flicky)
-						break;
-
-					P_SetTarget(&flicky->target, mo2);
-					flicky->momx = mo2->momx;
-					flicky->momy = mo2->momy;
-				}
-
-				mobj->fuse--;
+					P_SetMobjState(mobj, (stat = S_ROSY_JUMP));
+				var1 = var2 = 0;
+				A_DoNPCPain(mobj);
+				mobj->cvmem -= TICRATE;
 			}
 			break;
-		case MT_PLAYER:
-			/// \todo Have the player's dead body completely finish its animation even if they've already respawned.
-			if (!mobj->fuse)
-			{ // Go away.
-			  /// \todo Actually go ahead and remove mobj completely, and fix any bugs and crashes doing this creates. Chasecam should stop moving, and F12 should never return to it.
-				mobj->momz = 0;
-				if (mobj->player)
-					mobj->flags2 |= MF2_DONTDRAW;
-				else // safe to remove, nobody's going to complain!
-				{
-					P_RemoveMobj(mobj);
-					return;
-				}
-			}
-			else // Apply gravity to fall downwards.
+		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 (mobj->player && !(mobj->fuse % 8) && (mobj->player->charflags & SF_MACHINE))
+				if (love && !(leveltime & 15))
+					makeheart = true;
+				if (player->exiting || --mobj->cvmem < TICRATE)
 				{
-					fixed_t r = mobj->radius >> FRACBITS;
-					mobj_t *explosion = P_SpawnMobj(
-						mobj->x + (P_RandomRange(r, -r) << FRACBITS),
-						mobj->y + (P_RandomRange(r, -r) << FRACBITS),
-						mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
-						MT_SONIC3KBOSSEXPLODE);
-					S_StartSound(explosion, sfx_s3kb4);
+					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;
 				}
-				if (mobj->movedir == DMG_DROWNED)
-					P_SetObjectMomZ(mobj, -FRACUNIT / 2, true); // slower fall from drowning
-				else
-					P_SetObjectMomZ(mobj, -2 * FRACUNIT / 3, true);
 			}
 			break;
-		case MT_METALSONIC_RACE:
-			{
-				if (!(mobj->fuse % 8))
-				{
-					fixed_t r = mobj->radius >> FRACBITS;
-					mobj_t *explosion = P_SpawnMobj(
-						mobj->x + (P_RandomRange(r, -r) << FRACBITS),
-						mobj->y + (P_RandomRange(r, -r) << FRACBITS),
-						mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
-						MT_SONIC3KBOSSEXPLODE);
-					S_StartSound(explosion, sfx_s3kb4);
-				}
-				P_SetObjectMomZ(mobj, -2 * FRACUNIT / 3, true);
-			}
+		case S_ROSY_UNHAPPY:
 		default:
 			break;
 		}
-	else
-	{
-		if ((mobj->flags & MF_ENEMY) && (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1))
-			mobj->flags2 &= ~MF2_FRET;
 
-		// Angle-to-tracer to trigger a linedef exec
-		// See Linedef Exec 457 (Track mobj angle to point)
-		if ((mobj->eflags & MFE_TRACERANGLE) && mobj->tracer && mobj->extravalue2)
+		if (stat == S_ROSY_HUG)
 		{
-			// mobj->lastlook - Don't disable behavior after first failure
-			// mobj->extravalue1 - Angle tolerance
-			// mobj->extravalue2 - Exec tag upon failure
-			// mobj->cvval - Allowable failure delay
-			// mobj->cvmem - Failure timer
-
-			angle_t ang = mobj->angle - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
-
-			// \todo account for distance between mobj and tracer
-			// Because closer mobjs can be facing beyond the angle tolerance
-			// yet tracer is still in the camera view
-
-			// failure state: mobj is not facing tracer
-			// Reasaonable defaults: ANGLE_67h, ANGLE_292h
-			if (ang >= (angle_t)mobj->extravalue1 && ang <= ANGLE_MAX - (angle_t)mobj->extravalue1)
-			{
-				if (mobj->cvmem)
-					mobj->cvmem--;
-				else
-				{
-					INT32 exectag = mobj->extravalue2; // remember this before we erase the values
-
-					if (mobj->lastlook)
-						mobj->cvmem = mobj->cusval; // reset timer for next failure
-					else
-					{
-						// disable after first failure
-						mobj->eflags &= ~MFE_TRACERANGLE;
-						mobj->lastlook = mobj->extravalue1 = mobj->extravalue2 = mobj->cvmem = mobj->cusval = 0;
-					}
+			if (player->panim != PA_IDLE)
+				P_SetPlayerMobjState(mobj->target, S_PLAY_STND);
+			player->pflags |= PF_STASIS;
+		}
 
-					P_LinedefExecute(exectag, mobj, NULL);
-				}
-			}
-			else
-				mobj->cvmem = mobj->cusval; // reset failure timer
+		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);
 		}
 
-		switch (mobj->type)
+		if (makeheart)
 		{
-			case MT_WALLSPIKEBASE:
-				if (!mobj->target) {
+			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;
+		}
+	}
+}
+
+static void P_MobjSceneryThink(mobj_t *mobj)
+{
+#ifdef HAVE_BLUA
+	if (LUAh_MobjThinker(mobj))
+		return;
+	if (P_MobjWasRemoved(mobj))
+		return;
+#endif
+
+	if ((mobj->flags2 & MF2_SHIELD) && !P_AddShield(mobj))
+		return;
+
+	switch (mobj->type)
+	{
+	case MT_BOSSJUNK:
+		mobj->flags2 ^= MF2_DONTDRAW;
+		break;
+	case MT_MACEPOINT:
+	case MT_CHAINMACEPOINT:
+	case MT_SPRINGBALLPOINT:
+	case MT_CHAINPOINT:
+	case MT_FIREBARPOINT:
+	case MT_CUSTOMMACEPOINT:
+	case MT_HIDDEN_SLING:
+		P_MaceSceneryThink(mobj);
+		break;
+	case MT_HOOP:
+		if (mobj->fuse > 1)
+			P_MoveHoop(mobj);
+		else if (mobj->fuse == 1)
+			mobj->movecount = 1;
+
+		if (mobj->movecount)
+		{
+			mobj->fuse++;
+
+			if (mobj->fuse > 32)
+			{
+				// Don't kill the hoop center. For the sake of respawning.
+				//if (mobj->target)
+				//	P_RemoveMobj(mobj->target);
+
+				P_RemoveMobj(mobj);
+			}
+		}
+		else
+			mobj->fuse--;
+		return;
+	case MT_NIGHTSPARKLE:
+		if (mobj->tics != -1)
+		{
+			mobj->tics--;
+
+			// you can cycle through multiple states in a tic
+			if (!mobj->tics)
+				if (!P_SetMobjState(mobj, mobj->state->nextstate))
+					return; // freed itself
+		}
+
+		P_UnsetThingPosition(mobj);
+		mobj->x += mobj->momx;
+		mobj->y += mobj->momy;
+		mobj->z += mobj->momz;
+		P_SetThingPosition(mobj);
+		return;
+	case MT_NIGHTSLOOPHELPER:
+		if (--mobj->tics <= 0)
+			P_RemoveMobj(mobj);
+
+		// Don't touch my fuse!
+		return;
+	case MT_OVERLAY:
+		if (!mobj->target)
+		{
+			P_RemoveMobj(mobj);
+			return;
+		}
+		else
+			P_AddOverlay(mobj);
+		break;
+	case MT_PITY_ORB:
+	case MT_WHIRLWIND_ORB:
+	case MT_ARMAGEDDON_ORB:
+		if (!(mobj->flags2 & MF2_SHIELD))
+			return;
+		break;
+	case MT_ATTRACT_ORB:
+		if (!(mobj->flags2 & MF2_SHIELD))
+			return;
+		if (/*(mobj->target) -- the following is implicit by P_AddShield
+		&& (mobj->target->player)
+		&& */ (mobj->target->player->homing) && (mobj->target->player->pflags & PF_SHIELDABILITY))
+		{
+			P_SetMobjState(mobj, mobj->info->painstate);
+			mobj->tics++;
+		}
+		break;
+	case MT_ELEMENTAL_ORB:
+		if (!(mobj->flags2 & MF2_SHIELD))
+			return;
+		if (mobj->tracer
+			/* && mobj->target -- the following is implicit by P_AddShield
+			&& mobj->target->player
+			&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL */
+			&& mobj->target->player->pflags & PF_SHIELDABILITY
+			&& ((statenum_t)(mobj->tracer->state - states) < mobj->info->raisestate
+				|| (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
+		{
+			P_SetMobjState(mobj, mobj->info->painstate);
+			mobj->tics++;
+			P_SetMobjState(mobj->tracer, mobj->info->raisestate);
+			mobj->tracer->tics++;
+		}
+		break;
+	case MT_FORCE_ORB:
+		if (!(mobj->flags2 & MF2_SHIELD))
+			return;
+		if (/*
+		&& mobj->target -- the following is implicit by P_AddShield
+		&& mobj->target->player
+		&& (mobj->target->player->powers[pw_shield] & SH_FORCE)
+		&& */ (mobj->target->player->pflags & PF_SHIELDABILITY))
+		{
+			mobj_t *whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
+			P_SetMobjState(whoosh, mobj->info->raisestate);
+			whoosh->destscale = whoosh->scale << 1;
+			whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
+			whoosh->height = 38*whoosh->scale;
+			whoosh->fuse = 10;
+			whoosh->flags |= MF_NOCLIPHEIGHT;
+			whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames
+			mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal whoosh
+		}
+		/* FALLTHRU */
+	case MT_FLAMEAURA_ORB:
+		if (!(mobj->flags2 & MF2_SHIELD))
+			return;
+		if ((statenum_t)(mobj->state - states) < mobj->info->painstate)
+			mobj->angle = mobj->target->angle; // implicitly okay because of P_AddShield
+		if (mobj->tracer
+			/* && mobj->target -- the following is implicit by P_AddShield
+			&& mobj->target->player
+			&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_FLAMEAURA */
+			&& mobj->target->player->pflags & PF_SHIELDABILITY
+			&& ((statenum_t)(mobj->tracer->state - states) < mobj->info->raisestate
+				|| (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
+		{
+			P_SetMobjState(mobj, mobj->info->painstate);
+			mobj->tics++;
+			P_SetMobjState(mobj->tracer, mobj->info->raisestate);
+			mobj->tracer->tics++;
+		}
+		break;
+	case MT_BUBBLEWRAP_ORB:
+		if (!(mobj->flags2 & MF2_SHIELD))
+			return;
+		if (mobj->tracer
+			/* && mobj->target -- the following is implicit by P_AddShield
+			&& mobj->target->player
+			&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP */
+			)
+		{
+			if (mobj->target->player->pflags & PF_SHIELDABILITY
+				&& ((statenum_t)(mobj->state - states) < mobj->info->painstate
+					|| (mobj->state->nextstate < mobj->info->painstate && mobj->tics == 1)))
+			{
+				P_SetMobjState(mobj, mobj->info->painstate);
+				mobj->tics++;
+				P_SetMobjState(mobj->tracer, mobj->info->raisestate);
+				mobj->tracer->tics++;
+			}
+			else if (mobj->target->eflags & MFE_JUSTHITFLOOR
+				&& (statenum_t)(mobj->state - states) == mobj->info->painstate)
+			{
+				P_SetMobjState(mobj, mobj->info->painstate + 1);
+				mobj->tics++;
+				P_SetMobjState(mobj->tracer, mobj->info->raisestate + 1);
+				mobj->tracer->tics++;
+			}
+		}
+		break;
+	case MT_THUNDERCOIN_ORB:
+		if (!(mobj->flags2 & MF2_SHIELD))
+			return;
+		if (mobj->tracer
+			/* && mobj->target -- the following is implicit by P_AddShield
+			&& mobj->target->player
+			&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_THUNDERCOIN */
+			&& (mobj->target->player->pflags & PF_SHIELDABILITY))
+		{
+			P_SetMobjState(mobj, mobj->info->painstate);
+			mobj->tics++;
+			P_SetMobjState(mobj->tracer, mobj->info->raisestate);
+			mobj->tracer->tics++;
+			mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal spark
+		}
+		break;
+	case MT_WATERDROP:
+		P_SceneryCheckWater(mobj);
+		if ((mobj->z <= mobj->floorz || mobj->z <= mobj->watertop)
+			&& mobj->health > 0)
+		{
+			mobj->health = 0;
+			P_SetMobjState(mobj, mobj->info->deathstate);
+			S_StartSound(mobj, mobj->info->deathsound + P_RandomKey(mobj->info->mass));
+			return;
+		}
+		break;
+	case MT_BUBBLES:
+		P_SceneryCheckWater(mobj);
+		break;
+	case MT_SMALLBUBBLE:
+	case MT_MEDIUMBUBBLE:
+	case MT_EXTRALARGEBUBBLE:	// start bubble dissipate
+		P_SceneryCheckWater(mobj);
+		if (P_MobjWasRemoved(mobj)) // bubble was removed by not being in water
+			return;
+		if (!(mobj->eflags & MFE_UNDERWATER)
+			|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz)
+			|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z <= mobj->floorz)
+			|| (P_CheckDeathPitCollide(mobj))
+			|| --mobj->fuse <= 0) // Bubbles eventually dissipate if they can't reach the surface.
+		{
+			// no playing sound: no point; the object is being removed
+			P_RemoveMobj(mobj);
+			return;
+		}
+		break;
+	case MT_LOCKON:
+		if (!mobj->target)
+		{
+			P_RemoveMobj(mobj);
+			return;
+		}
+
+		mobj->flags2 &= ~MF2_DONTDRAW;
+
+		mobj->x = mobj->target->x;
+		mobj->y = mobj->target->y;
+
+		mobj->eflags |= (mobj->target->eflags & MFE_VERTICALFLIP);
+
+		mobj->destscale = mobj->target->destscale;
+		P_SetScale(mobj, mobj->target->scale);
+
+		if (!(mobj->eflags & MFE_VERTICALFLIP))
+			mobj->z = mobj->target->z + mobj->target->height + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale);
+		else
+			mobj->z = mobj->target->z - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale) - mobj->height;
+		break;
+	case MT_LOCKONINF:
+		if (!(mobj->flags2 & MF2_STRONGBOX))
+		{
+			mobj->threshold = mobj->z;
+			mobj->flags2 |= MF2_STRONGBOX;
+		}
+		if (!(mobj->eflags & MFE_VERTICALFLIP))
+			mobj->z = mobj->threshold + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
+		else
+			mobj->z = mobj->threshold - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
+		break;
+	case MT_DROWNNUMBERS:
+		if (!P_DrownNumbersSceneryThink(mobj))
+			return;
+		break;
+	case MT_FLAMEJET:
+		P_FlameJetSceneryThink(mobj);
+		break;
+	case MT_VERTICALFLAMEJET:
+		P_VerticalFlameJetSceneryThink(mobj);
+		break;
+	case MT_FLICKY_01_CENTER:
+	case MT_FLICKY_02_CENTER:
+	case MT_FLICKY_03_CENTER:
+	case MT_FLICKY_04_CENTER:
+	case MT_FLICKY_05_CENTER:
+	case MT_FLICKY_06_CENTER:
+	case MT_FLICKY_07_CENTER:
+	case MT_FLICKY_08_CENTER:
+	case MT_FLICKY_09_CENTER:
+	case MT_FLICKY_10_CENTER:
+	case MT_FLICKY_11_CENTER:
+	case MT_FLICKY_12_CENTER:
+	case MT_FLICKY_13_CENTER:
+	case MT_FLICKY_14_CENTER:
+	case MT_FLICKY_15_CENTER:
+	case MT_FLICKY_16_CENTER:
+	case MT_SECRETFLICKY_01_CENTER:
+	case MT_SECRETFLICKY_02_CENTER:
+		if (mobj->tracer && (mobj->flags & MF_NOCLIPTHING)
+			&& (mobj->flags & MF_GRENADEBOUNCE))
+			// for now: only do this bounce routine if flicky is in-place. \todo allow in all movements
+		{
+			if (!(mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z <= mobj->tracer->floorz)
+				mobj->tracer->momz = 7*FRACUNIT;
+			else if ((mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z >= mobj->tracer->ceilingz - mobj->tracer->height)
+				mobj->tracer->momz = -7*FRACUNIT;
+		}
+		break;
+	case MT_SEED:
+		if (P_MobjFlip(mobj)*mobj->momz < mobj->info->speed)
+			mobj->momz = P_MobjFlip(mobj)*mobj->info->speed;
+		break;
+	case MT_ROCKCRUMBLE1:
+	case MT_ROCKCRUMBLE2:
+	case MT_ROCKCRUMBLE3:
+	case MT_ROCKCRUMBLE4:
+	case MT_ROCKCRUMBLE5:
+	case MT_ROCKCRUMBLE6:
+	case MT_ROCKCRUMBLE7:
+	case MT_ROCKCRUMBLE8:
+	case MT_ROCKCRUMBLE9:
+	case MT_ROCKCRUMBLE10:
+	case MT_ROCKCRUMBLE11:
+	case MT_ROCKCRUMBLE12:
+	case MT_ROCKCRUMBLE13:
+	case MT_ROCKCRUMBLE14:
+	case MT_ROCKCRUMBLE15:
+	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])
+		{
+			P_SetMobjState(mobj, mobj->info->deathstate);
+			return;
+		}
+		break;
+	case MT_PARTICLEGEN:
+		if (!P_ParticleGenSceneryThink(mobj))
+			return;
+		break;
+	case MT_FSGNA:
+		if (mobj->movedir)
+			mobj->angle += mobj->movedir;
+		break;
+	case MT_ROSY:
+		P_RosySceneryThink(mobj);
+		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, (INT32)mobj->movedir)*3;
+		if (strength < 10)
+			mobj->frame |= ((10 - strength) << (FF_TRANSSHIFT));
+	}
+	/* FALLTHRU */
+	default:
+		if (mobj->fuse)
+		{ // Scenery object fuse! Very basic!
+			mobj->fuse--;
+			if (!mobj->fuse)
+			{
+#ifdef HAVE_BLUA
+				if (!LUAh_MobjFuse(mobj))
+#endif
+					P_RemoveMobj(mobj);
+				return;
+			}
+		}
+		break;
+	}
+
+	P_SceneryThinker(mobj);
+}
+
+static boolean P_MobjPushableThink(mobj_t *mobj)
+{
+	P_MobjCheckWater(mobj);
+	P_PushableThinker(mobj);
+
+	// Extinguish fire objects in water. (Yes, it's extraordinarily rare to have a pushable flame object, but Brak uses such a case.)
+	if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
+		&& (mobj->eflags & (MFE_UNDERWATER | MFE_TOUCHWATER)))
+	{
+		P_KillMobj(mobj, NULL, NULL, 0);
+		return false;
+	}
+
+	return true;
+}
+
+static boolean P_MobjBossThink(mobj_t *mobj)
+{
+#ifdef HAVE_BLUA
+	if (LUAh_BossThinker(mobj))
+	{
+		if (P_MobjWasRemoved(mobj))
+			return false;
+	}
+	else if (P_MobjWasRemoved(mobj))
+		return false;
+	else
+#endif
+		switch (mobj->type)
+		{
+		case MT_EGGMOBILE:
+			if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
+			{
+				fixed_t rad = mobj->radius >> FRACBITS;
+				fixed_t hei = mobj->height >> FRACBITS;
+				mobj_t *particle = P_SpawnMobjFromMobj(mobj,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(hei / 2, hei) << FRACBITS,
+					MT_SMOKE);
+				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+				particle->momz += mobj->momz;
+			}
+			if (mobj->flags2 & MF2_SKULLFLY)
+#if 1
+				P_SpawnGhostMobj(mobj);
+#else // all the way back from final demo... MT_THOK isn't even the same size anymore!
+			{
+				mobj_t *spawnmobj;
+				spawnmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->painchance);
+				P_SetTarget(&spawnmobj->target, mobj);
+				spawnmobj->color = SKINCOLOR_GREY;
+			}
+#endif
+			P_Boss1Thinker(mobj);
+			break;
+		case MT_EGGMOBILE2:
+			if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
+			{
+				fixed_t rad = mobj->radius >> FRACBITS;
+				fixed_t hei = mobj->height >> FRACBITS;
+				mobj_t *particle = P_SpawnMobjFromMobj(mobj,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(hei/2, hei) << FRACBITS,
+					MT_SMOKE);
+				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+				particle->momz += mobj->momz;
+			}
+			P_Boss2Thinker(mobj);
+			break;
+		case MT_EGGMOBILE3:
+			if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
+			{
+				fixed_t rad = mobj->radius >> FRACBITS;
+				fixed_t hei = mobj->height >> FRACBITS;
+				mobj_t *particle = P_SpawnMobjFromMobj(mobj,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(hei/2, hei) << FRACBITS,
+					MT_SMOKE);
+				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+				particle->momz += mobj->momz;
+			}
+			P_Boss3Thinker(mobj);
+			break;
+		case MT_EGGMOBILE4:
+			if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
+			{
+				fixed_t rad = mobj->radius >> FRACBITS;
+				fixed_t hei = mobj->height >> FRACBITS;
+				mobj_t* particle = P_SpawnMobjFromMobj(mobj,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(rad, -rad) << FRACBITS,
+					P_RandomRange(hei/2, hei) << FRACBITS,
+					MT_SMOKE);
+				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+				particle->momz += mobj->momz;
+			}
+			P_Boss4Thinker(mobj);
+			break;
+		case MT_FANG:
+			P_Boss5Thinker(mobj);
+			break;
+		case MT_BLACKEGGMAN:
+			P_Boss7Thinker(mobj);
+			break;
+		case MT_METALSONIC_BATTLE:
+			P_Boss9Thinker(mobj);
+			break;
+		default: // Generic SOC-made boss
+			if (mobj->flags2 & MF2_SKULLFLY)
+				P_SpawnGhostMobj(mobj);
+			P_GenericBossThinker(mobj);
+			break;
+		}
+	if (mobj->flags2 & MF2_BOSSFLEE)
+	{
+		if (mobj->extravalue1)
+		{
+			if (!(--mobj->extravalue1))
+			{
+				if (mobj->target)
+				{
+					mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y)), mobj->scale << 1);
+					mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+				}
+				else
+					mobj->momz = 8*mobj->scale;
+			}
+			else
+				mobj->angle += mobj->movedir;
+		}
+		else if (mobj->target)
+			P_InstaThrust(mobj, mobj->angle, FixedMul(12*FRACUNIT, mobj->scale));
+	}
+	if (mobj->type == MT_CYBRAKDEMON && !mobj->health)
+	{
+		if (!(mobj->tics & 1))
+		{
+			var1 = 2;
+			var2 = 0;
+			A_BossScream(mobj);
+		}
+		if (P_CheckDeathPitCollide(mobj))
+		{
+			P_RemoveMobj(mobj);
+			return false;
+		}
+		if (mobj->momz && mobj->z + mobj->momz <= mobj->floorz)
+		{
+			S_StartSound(mobj, sfx_befall);
+			if (mobj->state != states + S_CYBRAKDEMON_DIE8)
+				P_SetMobjState(mobj, S_CYBRAKDEMON_DIE8);
+		}
+	}
+	return true;
+}
+
+static boolean P_MobjDeadThink(mobj_t *mobj)
+{
+	switch (mobj->type)
+	{
+	case MT_BLUESPHERE:
+		if ((mobj->tics >> 2) + 1 > 0 && (mobj->tics >> 2) + 1 <= tr_trans60) // tr_trans50 through tr_trans90, shifting once every second frame
+			mobj->frame = (NUMTRANSMAPS - ((mobj->tics >> 2) + 1)) << FF_TRANSSHIFT;
+		else // tr_trans60 otherwise
+			mobj->frame = tr_trans60 << FF_TRANSSHIFT;
+		break;
+	case MT_EGGCAPSULE:
+		if (mobj->z <= mobj->floorz)
+		{
+			P_RemoveMobj(mobj);
+			return false;
+		}
+		break;
+	case MT_FAKEMOBILE:
+		if (mobj->scale == mobj->destscale)
+		{
+			if (!mobj->fuse)
+			{
+				S_StartSound(mobj, sfx_s3k77);
+				mobj->flags2 |= MF2_DONTDRAW;
+				mobj->fuse = TICRATE;
+			}
+			return false;
+		}
+		if (!mobj->reactiontime)
+		{
+			if (P_RandomChance(FRACUNIT/2))
+				mobj->movefactor = FRACUNIT;
+			else
+				mobj->movefactor = -FRACUNIT;
+			if (P_RandomChance(FRACUNIT/2))
+				mobj->movedir = ANG20;
+			else
+				mobj->movedir = -ANG20;
+			mobj->reactiontime = 5;
+		}
+		mobj->momz += mobj->movefactor;
+		mobj->angle += mobj->movedir;
+		P_InstaThrust(mobj, mobj->angle, -mobj->info->speed);
+		mobj->reactiontime--;
+		break;
+	case MT_EGGSHIELD:
+		mobj->flags2 ^= MF2_DONTDRAW;
+		break;
+	case MT_EGGTRAP: // Egg Capsule animal release
+		if (mobj->fuse > 0)// && mobj->fuse < TICRATE-(TICRATE/7))
+		{
+			INT32 i;
+			fixed_t x, y, z;
+			fixed_t ns;
+			mobj_t* mo2;
+			mobj_t* flicky;
+
+			z = mobj->subsector->sector->floorheight + FRACUNIT + (P_RandomKey(64) << FRACBITS);
+			for (i = 0; i < 3; i++)
+			{
+				const angle_t fa = P_RandomKey(FINEANGLES) & FINEMASK;
+				ns = 64*FRACUNIT;
+				x = mobj->x + FixedMul(FINESINE(fa), ns);
+				y = mobj->y + FixedMul(FINECOSINE(fa), ns);
+
+				mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
+				P_SetMobjStateNF(mo2, S_XPLD_EGGTRAP); // so the flickies don't lose their target if they spawn
+				ns = 4*FRACUNIT;
+				mo2->momx = FixedMul(FINESINE(fa), ns);
+				mo2->momy = FixedMul(FINECOSINE(fa), ns);
+				mo2->angle = fa << ANGLETOFINESHIFT;
+
+				if (!i && !(mobj->fuse & 2))
+					S_StartSound(mo2, mobj->info->deathsound);
+
+				flicky = P_InternalFlickySpawn(mo2, 0, 8*FRACUNIT, false, -1);
+				if (!flicky)
+					break;
+
+				P_SetTarget(&flicky->target, mo2);
+				flicky->momx = mo2->momx;
+				flicky->momy = mo2->momy;
+			}
+
+			mobj->fuse--;
+		}
+		break;
+	case MT_PLAYER:
+		/// \todo Have the player's dead body completely finish its animation even if they've already respawned.
+		if (!mobj->fuse)
+		{ // Go away.
+		  /// \todo Actually go ahead and remove mobj completely, and fix any bugs and crashes doing this creates. Chasecam should stop moving, and F12 should never return to it.
+			mobj->momz = 0;
+			if (mobj->player)
+				mobj->flags2 |= MF2_DONTDRAW;
+			else // safe to remove, nobody's going to complain!
+			{
+				P_RemoveMobj(mobj);
+				return false;
+			}
+		}
+		else // Apply gravity to fall downwards.
+		{
+			if (mobj->player && !(mobj->fuse % 8) && (mobj->player->charflags & SF_MACHINE))
+			{
+				fixed_t r = mobj->radius >> FRACBITS;
+				mobj_t *explosion = P_SpawnMobj(
+					mobj->x + (P_RandomRange(r, -r) << FRACBITS),
+					mobj->y + (P_RandomRange(r, -r) << FRACBITS),
+					mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
+					MT_SONIC3KBOSSEXPLODE);
+				S_StartSound(explosion, sfx_s3kb4);
+			}
+			if (mobj->movedir == DMG_DROWNED)
+				P_SetObjectMomZ(mobj, -FRACUNIT/2, true); // slower fall from drowning
+			else
+				P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
+		}
+		break;
+	case MT_METALSONIC_RACE:
+	{
+		if (!(mobj->fuse % 8))
+		{
+			fixed_t r = mobj->radius >> FRACBITS;
+			mobj_t *explosion = P_SpawnMobj(
+				mobj->x + (P_RandomRange(r, -r) << FRACBITS),
+				mobj->y + (P_RandomRange(r, -r) << FRACBITS),
+				mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
+				MT_SONIC3KBOSSEXPLODE);
+			S_StartSound(explosion, sfx_s3kb4);
+		}
+		P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
+	}
+	break;
+	default:
+		break;
+	}
+	return true;
+}
+
+// Angle-to-tracer to trigger a linedef exec
+// See Linedef Exec 457 (Track mobj angle to point)
+static void P_TracerAngleThink(mobj_t *mobj)
+{
+	angle_t ang;
+
+	if (!mobj->tracer)
+		return;
+
+	if (!mobj->extravalue2)
+		return;
+
+	// mobj->lastlook - Don't disable behavior after first failure
+	// mobj->extravalue1 - Angle tolerance
+	// mobj->extravalue2 - Exec tag upon failure
+	// mobj->cvval - Allowable failure delay
+	// mobj->cvmem - Failure timer
+
+	ang = mobj->angle - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
+
+	// \todo account for distance between mobj and tracer
+	// Because closer mobjs can be facing beyond the angle tolerance
+	// yet tracer is still in the camera view
+
+	// failure state: mobj is not facing tracer
+	// Reasaonable defaults: ANGLE_67h, ANGLE_292h
+	if (ang >= (angle_t)mobj->extravalue1 && ang <= ANGLE_MAX - (angle_t)mobj->extravalue1)
+	{
+		if (mobj->cvmem)
+			mobj->cvmem--;
+		else
+		{
+			INT32 exectag = mobj->extravalue2; // remember this before we erase the values
+
+			if (mobj->lastlook)
+				mobj->cvmem = mobj->cusval; // reset timer for next failure
+			else
+			{
+				// disable after first failure
+				mobj->eflags &= ~MFE_TRACERANGLE;
+				mobj->lastlook = mobj->extravalue1 = mobj->extravalue2 = mobj->cvmem = mobj->cusval = 0;
+			}
+
+			P_LinedefExecute(exectag, mobj, NULL);
+		}
+	}
+	else
+		mobj->cvmem = mobj->cusval; // reset failure timer
+}
+
+static void P_ArrowThink(mobj_t *mobj)
+{
+	if (mobj->flags & MF_MISSILE)
+	{
+		// Calculate the angle of movement.
+		/*
+			   momz
+			 / |
+		   /   |
+		 /     |
+		0------dist(momx,momy)
+		*/
+
+		fixed_t dist = P_AproxDistance(mobj->momx, mobj->momy);
+		angle_t angle = R_PointToAngle2(0, 0, dist, mobj->momz);
+
+		if (angle > ANG20 && angle <= ANGLE_180)
+			mobj->frame = 2;
+		else if (angle < ANG340 && angle > ANGLE_180)
+			mobj->frame = 0;
+		else
+			mobj->frame = 1;
+
+		if (!(mobj->extravalue1) && (mobj->momz < 0))
+		{
+			mobj->extravalue1 = 1;
+			S_StartSound(mobj, mobj->info->activesound);
+		}
+		if (leveltime & 1)
+		{
+			mobj_t *dust = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_PARTICLE);
+			dust->tics = 18;
+			dust->scalespeed = 4096;
+			dust->destscale = FRACUNIT/32;
+		}
+	}
+	else
+		mobj->flags2 ^= MF2_DONTDRAW;
+}
+
+static void P_BumbleboreThink(mobj_t *mobj)
+{
+	statenum_t st = mobj->state - states;
+	if (st == S_BUMBLEBORE_FLY1 || st == S_BUMBLEBORE_FLY2)
+	{
+		if (!mobj->target)
+			P_SetMobjState(mobj, mobj->info->spawnstate);
+		else if (P_MobjFlip(mobj)*((mobj->z + (mobj->height >> 1)) - (mobj->target->z + (mobj->target->height >> 1))) > 0
+			&& R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) <= 32*FRACUNIT)
+		{
+			mobj->momx >>= 1;
+			mobj->momy >>= 1;
+			if (++mobj->movefactor == 4)
+			{
+				S_StartSound(mobj, mobj->info->seesound);
+				mobj->momx = mobj->momy = mobj->momz = 0;
+				mobj->flags = (mobj->flags|MF_PAIN) & ~MF_NOGRAVITY;
+				P_SetMobjState(mobj, mobj->info->meleestate);
+			}
+		}
+		else
+			mobj->movefactor = 0;
+	}
+	else if (st == S_BUMBLEBORE_RAISE || st == S_BUMBLEBORE_FALL2) // no _FALL1 because it's an 0-tic
+	{
+		if (P_IsObjectOnGround(mobj))
+		{
+			S_StopSound(mobj);
+			S_StartSound(mobj, mobj->info->attacksound);
+			mobj->flags = (mobj->flags | MF_NOGRAVITY) & ~MF_PAIN;
+			mobj->momx = mobj->momy = mobj->momz = 0;
+			P_SetMobjState(mobj, mobj->info->painstate);
+		}
+		else
+		{
+			mobj->angle += ANGLE_22h;
+			mobj->frame = mobj->state->frame + ((mobj->tics & 2) >> 1);
+		}
+	}
+	else if (st == S_BUMBLEBORE_STUCK2 && mobj->tics < TICRATE)
+		mobj->frame = mobj->state->frame + ((mobj->tics & 2) >> 1);
+}
+
+static boolean P_HangsterThink(mobj_t *mobj)
+{
+	statenum_t st = mobj->state - states;
+	//ghost image trail when flying down
+	if (st == S_HANGSTER_SWOOP1 || st == S_HANGSTER_SWOOP2)
+	{
+		P_SpawnGhostMobj(mobj);
+		//curve when in line with target, otherwise curve to avoid crashing into floor
+		if ((mobj->z - mobj->floorz <= 80*FRACUNIT) || (mobj->target && (mobj->z - mobj->target->z <= 80*FRACUNIT)))
+			P_SetMobjState(mobj, (st = S_HANGSTER_ARC1));
+	}
+
+	//swoop arc movement stuff
+	if (st == S_HANGSTER_ARC1)
+	{
+		A_FaceTarget(mobj);
+		P_Thrust(mobj, mobj->angle, 1*FRACUNIT);
+	}
+	else if (st == S_HANGSTER_ARC2)
+		P_Thrust(mobj, mobj->angle, 2*FRACUNIT);
+	else if (st == S_HANGSTER_ARC3)
+		P_Thrust(mobj, mobj->angle, 4*FRACUNIT);
+	//if movement has stopped while flying (like hitting a wall), fly up immediately
+	else if (st == S_HANGSTER_FLY1 && !mobj->momx && !mobj->momy)
+	{
+		mobj->extravalue1 = 0;
+		P_SetMobjState(mobj, S_HANGSTER_ARCUP1);
+	}
+	//after swooping back up, check for ceiling
+	else if ((st == S_HANGSTER_RETURN1 || st == S_HANGSTER_RETURN2) && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height))
+		P_SetMobjState(mobj, (st = S_HANGSTER_RETURN3));
+
+	//should you roost on a ceiling with F_SKY1 as its flat, disappear forever
+	if (st == S_HANGSTER_RETURN3 && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height)
+		&& mobj->subsector->sector->ceilingpic == skyflatnum
+		&& mobj->subsector->sector->ceilingheight == mobj->ceilingz)
+	{
+		P_RemoveMobj(mobj);
+		return false;
+	}
+
+	return true;
+}
+
+static boolean P_JetFume1Think(mobj_t *mobj)
+{
+	fixed_t jetx, jety;
+
+	if (!mobj->target // if you have no target
+		|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
+	{ // then remove yourself as well!
+		P_RemoveMobj(mobj);
+		return false;
+	}
+
+	jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
+	jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
+
+	if (mobj->fuse == 56) // First one
+	{
+		P_UnsetThingPosition(mobj);
+		mobj->x = jetx;
+		mobj->y = jety;
+		if (mobj->target->eflags & MFE_VERTICALFLIP)
+			mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale);
+		else
+			mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale);
+		mobj->floorz = mobj->z;
+		mobj->ceilingz = mobj->z + mobj->height;
+		P_SetThingPosition(mobj);
+	}
+	else if (mobj->fuse == 57)
+	{
+		P_UnsetThingPosition(mobj);
+		mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle - ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+		mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle - ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+		if (mobj->target->eflags & MFE_VERTICALFLIP)
+			mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
+		else
+			mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
+		mobj->floorz = mobj->z;
+		mobj->ceilingz = mobj->z + mobj->height;
+		P_SetThingPosition(mobj);
+	}
+	else if (mobj->fuse == 58)
+	{
+		P_UnsetThingPosition(mobj);
+		mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle + ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+		mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle + ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
+		if (mobj->target->eflags & MFE_VERTICALFLIP)
+			mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
+		else
+			mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
+		mobj->floorz = mobj->z;
+		mobj->ceilingz = mobj->z + mobj->height;
+		P_SetThingPosition(mobj);
+	}
+	else if (mobj->fuse == 59)
+	{
+		boolean dashmod = ((mobj->target->flags & MF_PAIN) && (mobj->target->health <= mobj->target->info->damage));
+		jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius);
+		jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius);
+		P_UnsetThingPosition(mobj);
+		mobj->x = jetx;
+		mobj->y = jety;
+		mobj->destscale = mobj->target->scale;
+		if (!(dashmod && mobj->target->state == states + S_METALSONIC_BOUNCE))
+		{
+			mobj->destscale = (mobj->destscale + FixedDiv(R_PointToDist2(0, 0, mobj->target->momx, mobj->target->momy), 36*mobj->target->scale))/3;
+		}
+		if (mobj->target->eflags & MFE_VERTICALFLIP)
+			mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2;
+		else
+			mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2;
+		mobj->floorz = mobj->z;
+		mobj->ceilingz = mobj->z + mobj->height;
+		P_SetThingPosition(mobj);
+		if (dashmod)
+		{
+			mobj->color = SKINCOLOR_SUNSET;
+			if (mobj->target->movecount == 3 && !mobj->target->reactiontime && (mobj->target->movedir == 0 || mobj->target->movedir == 2))
+				P_SpawnGhostMobj(mobj);
+		}
+		else
+			mobj->color = SKINCOLOR_ICY;
+	}
+	mobj->fuse++;
+	return true;
+}
+
+static boolean P_EggRobo1Think(mobj_t *mobj)
+{
+#define SPECTATORRADIUS (96*mobj->scale)
+	if (!(mobj->flags2 & MF2_STRONGBOX))
+	{
+		mobj->cusval = mobj->x; // eat my SOCs, p_mobj.h warning, we have lua now
+		mobj->cvmem = mobj->y; // ditto
+		mobj->movedir = mobj->angle;
+		mobj->threshold = P_MobjFlip(mobj)*10*mobj->scale;
+		if (mobj->threshold < 0)
+			mobj->threshold += (mobj->ceilingz - mobj->height);
+		else
+			mobj->threshold += mobj->floorz;
+		var1 = 4;
+		A_BossJetFume(mobj);
+		mobj->flags2 |= MF2_STRONGBOX;
+	}
+
+	if (mobj->state == &states[mobj->info->deathstate]) // todo: make map actually set health to 0 for these
+	{
+		if (mobj->movecount)
+		{
+			if (!(--mobj->movecount))
+				S_StartSound(mobj, mobj->info->deathsound);
+		}
+		else
+		{
+			mobj->momz += P_MobjFlip(mobj)*mobj->scale;
+			if (mobj->momz > 0)
+			{
+				if (mobj->z + mobj->momz > mobj->ceilingz + (1000 << FRACBITS))
+				{
 					P_RemoveMobj(mobj);
-					return;
+					return false;
 				}
-				mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
-#if 0
-				if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
+			}
+			else if (mobj->z + mobj->height + mobj->momz < mobj->floorz - (1000 << FRACBITS))
+			{
+				P_RemoveMobj(mobj);
+				return false;
+			}
+		}
+	}
+	else
+	{
+		fixed_t basex = mobj->cusval, basey = mobj->cvmem;
+
+		if (mobj->spawnpoint && mobj->spawnpoint->options & (MTF_AMBUSH|MTF_OBJECTSPECIAL))
+		{
+			angle_t sideang = mobj->movedir + ((mobj->spawnpoint->options & MTF_AMBUSH) ? ANGLE_90 : -ANGLE_90);
+			fixed_t oscillate = FixedMul(FINESINE(((leveltime * ANG1) >> (ANGLETOFINESHIFT + 2)) & FINEMASK), 250*mobj->scale);
+			basex += P_ReturnThrustX(mobj, sideang, oscillate);
+			basey += P_ReturnThrustY(mobj, sideang, oscillate);
+		}
+
+		mobj->z = mobj->threshold + FixedMul(FINESINE(((leveltime + mobj->movecount)*ANG2 >> (ANGLETOFINESHIFT - 2)) & FINEMASK), 8*mobj->scale);
+		if (mobj->state != &states[mobj->info->meleestate])
+		{
+			boolean didmove = false;
+
+			if (mobj->state == &states[mobj->info->spawnstate])
+			{
+				UINT8 i;
+				fixed_t dist = INT32_MAX;
+
+				for (i = 0; i < MAXPLAYERS; i++)
 				{
-					mobj_t *target = mobj->target; // shortcut
-					const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
-					P_UnsetThingPosition(mobj);
-					mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
-					mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
-					P_SetThingPosition(mobj);
-					mobj->angle = target->angle + ANGLE_90;
+					fixed_t compdist;
+					if (!playeringame[i])
+						continue;
+					if (players[i].spectator)
+						continue;
+					if (!players[i].mo)
+						continue;
+					if (!players[i].mo->health)
+						continue;
+					if (P_PlayerInPain(&players[i]))
+						continue;
+					if (players[i].mo->z > mobj->z + mobj->height + 8*mobj->scale)
+						continue;
+					if (players[i].mo->z + players[i].mo->height < mobj->z - 8*mobj->scale)
+						continue;
+					compdist = P_AproxDistance(
+						players[i].mo->x + players[i].mo->momx - basex,
+						players[i].mo->y + players[i].mo->momy - basey);
+					if (compdist >= dist)
+						continue;
+					dist = compdist;
+					P_SetTarget(&mobj->target, players[i].mo);
 				}
-#endif
-				break;
-			case MT_FALLINGROCK:
-				// Despawn rocks here in case zmovement code can't do so (blame slopes)
-				if (!mobj->momx && !mobj->momy && !mobj->momz
-				&& ((mobj->eflags & MFE_VERTICALFLIP) ?
-					  mobj->z + mobj->height >= mobj->ceilingz
-					: mobj->z <= mobj->floorz))
+
+				if (dist < (SPECTATORRADIUS << 1))
 				{
-					P_RemoveMobj(mobj);
-					return;
+					didmove = true;
+					mobj->frame = 3 + ((leveltime & 2) >> 1);
+					mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+
+					if (P_AproxDistance(
+						mobj->x - basex,
+						mobj->y - basey)
+						< mobj->scale)
+						S_StartSound(mobj, mobj->info->seesound);
+
+					P_TeleportMove(mobj,
+						(15*(mobj->x >> 4)) + (basex >> 4) + P_ReturnThrustX(mobj, mobj->angle, SPECTATORRADIUS >> 4),
+						(15*(mobj->y >> 4)) + (basey >> 4) + P_ReturnThrustY(mobj, mobj->angle, SPECTATORRADIUS >> 4),
+						mobj->z);
 				}
-				P_MobjCheckWater(mobj);
-				break;
-			case MT_ARROW:
-				if (mobj->flags & MF_MISSILE)
+				else
 				{
-					// Calculate the angle of movement.
-					/*
-						   momz
-						 / |
-					   /   |
-					 /     |
-					0------dist(momx,momy)
-					*/
-
-					fixed_t dist = P_AproxDistance(mobj->momx, mobj->momy);
-					angle_t angle = R_PointToAngle2(0, 0, dist, mobj->momz);
-
-					if (angle > ANG20 && angle <= ANGLE_180)
-						mobj->frame = 2;
-					else if (angle < ANG340 && angle > ANGLE_180)
-						mobj->frame = 0;
+					angle_t diff = (mobj->movedir - mobj->angle);
+					if (diff > ANGLE_180)
+						diff = InvAngle(InvAngle(diff)/8);
 					else
-						mobj->frame = 1;
+						diff /= 8;
+					mobj->angle += diff;
 
-					if (!(mobj->extravalue1) && (mobj->momz < 0))
-					{
-						mobj->extravalue1 = 1;
-						S_StartSound(mobj, mobj->info->activesound);
-					}
-					if (leveltime & 1)
-					{
-						mobj_t *dust = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_PARTICLE);
-						dust->tics = 18;
-						dust->scalespeed = 4096;
-						dust->destscale = FRACUNIT/32;
-					}
+					dist = FINECOSINE(((leveltime + mobj->movecount)*ANG2 >> (ANGLETOFINESHIFT - 2)) & FINEMASK);
+
+					if (abs(dist) < FRACUNIT/2)
+						mobj->frame = 0;
+					else
+						mobj->frame = (dist > 0) ? 1 : 2;
 				}
+			}
+
+			if (!didmove)
+			{
+				if (P_AproxDistance(mobj->x - basex, mobj->y - basey) < mobj->scale)
+					P_TeleportMove(mobj, basex, basey, mobj->z);
 				else
-					mobj->flags2 ^= MF2_DONTDRAW;
-				break;
-			case MT_EMERALDSPAWN:
-				if (mobj->threshold)
-				{
-					mobj->threshold--;
+					P_TeleportMove(mobj,
+					(15*(mobj->x >> 4)) + (basex >> 4),
+						(15*(mobj->y >> 4)) + (basey >> 4),
+						mobj->z);
+			}
+		}
+	}
+	return true;
+#undef SPECTATORRADIUS
+}
 
-					if (!mobj->threshold && !mobj->target && mobj->reactiontime)
-					{
-						mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime);
-						emerald->threshold = 42;
-						P_SetTarget(&mobj->target, emerald);
-						P_SetTarget(&emerald->target, mobj);
-					}
-				}
-				break;
-			case MT_BUGGLE:
-				mobj->eflags |= MFE_UNDERWATER; //P_MobjCheckWater(mobj); // solely for MFE_UNDERWATER for A_FlickySpawn
-				{
-					if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
-						&& P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius * 16)
-					{
-						// Home in on the target.
-						P_HomingAttack(mobj, mobj->tracer);
+static void P_NiGHTSDroneThink(mobj_t *mobj)
+{
+	{
+		// variable setup
+		mobj_t *goalpost = NULL;
+		mobj_t *sparkle = NULL;
+		mobj_t *droneman = NULL;
 
-						if (mobj->z < mobj->floorz)
-							mobj->z = mobj->floorz;
+		boolean flip = mobj->flags2 & MF2_OBJECTFLIP;
+		boolean topaligned = (mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
+		boolean middlealigned = (mobj->flags & MF_GRENADEBOUNCE) && !(mobj->flags & MF_SLIDEME);
+		boolean bottomoffsetted = !(mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
+		boolean flipchanged = false;
 
-						if (leveltime % mobj->info->painchance == 0)
-							S_StartSound(mobj, mobj->info->activesound);
+		fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
 
-						if ((statenum_t)(mobj->state-states) != mobj->info->seestate)
-							P_SetMobjState(mobj, mobj->info->seestate);
-					}
-					else
-					{
-						// Try to find a player
-						P_LookForPlayers(mobj, true, true, mobj->radius * 16);
-						mobj->momx >>= 1;
-						mobj->momy >>= 1;
-						mobj->momz >>= 1;
-						if ((statenum_t)(mobj->state-states) != mobj->info->spawnstate)
-							P_SetMobjState(mobj, mobj->info->spawnstate);
-					}
-				}
-				break;
-			case MT_BUMBLEBORE:
-				{
-					statenum_t st = mobj->state-states;
-					if (st == S_BUMBLEBORE_FLY1 || st == S_BUMBLEBORE_FLY2)
-					{
-						if (!mobj->target)
-							P_SetMobjState(mobj, mobj->info->spawnstate);
-						else if (P_MobjFlip(mobj)*((mobj->z + (mobj->height>>1)) - (mobj->target->z + (mobj->target->height>>1))) > 0
-							&& R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) <= 32*FRACUNIT)
-						{
-							mobj->momx >>= 1;
-							mobj->momy >>= 1;
-							if (++mobj->movefactor == 4)
-							{
-								S_StartSound(mobj, mobj->info->seesound);
-								mobj->momx = mobj->momy = mobj->momz = 0;
-								mobj->flags = (mobj->flags|MF_PAIN) & ~MF_NOGRAVITY;
-								P_SetMobjState(mobj, mobj->info->meleestate);
-							}
-						}
-						else
-							mobj->movefactor = 0;
-					}
-					else if (st == S_BUMBLEBORE_RAISE || st == S_BUMBLEBORE_FALL2) // no _FALL1 because it's an 0-tic
-					{
-						if (P_IsObjectOnGround(mobj))
-						{
-							S_StopSound(mobj);
-							S_StartSound(mobj, mobj->info->attacksound);
-							mobj->flags = (mobj->flags|MF_NOGRAVITY) & ~MF_PAIN;
-							mobj->momx = mobj->momy = mobj->momz = 0;
-							P_SetMobjState(mobj, mobj->info->painstate);
-						}
-						else
-						{
-							mobj->angle += ANGLE_22h;
-							mobj->frame = mobj->state->frame + ((mobj->tics & 2)>>1);
-						}
-					}
-					else if (st == S_BUMBLEBORE_STUCK2 && mobj->tics < TICRATE)
-						mobj->frame = mobj->state->frame + ((mobj->tics & 2)>>1);
-				}
-				break;
-			case MT_BIGMINE:
-				mobj->extravalue1 += 3;
-				mobj->extravalue1 %= 360;
-				P_UnsetThingPosition(mobj);
-				mobj->z += FINESINE(mobj->extravalue1*(FINEMASK+1)/360);
-				P_SetThingPosition(mobj);
-				break;
-			case MT_FLAME:
-				if (mobj->flags2 & MF2_BOSSNOTRAP)
-				{
-					if (!mobj->target || P_MobjWasRemoved(mobj->target))
-					{
-						if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
-							P_RemoveMobj(mobj->tracer);
-						P_RemoveMobj(mobj);
-						return;
-					}
-					mobj->z = mobj->target->z + mobj->target->momz;
-					if (!(mobj->eflags & MFE_VERTICALFLIP))
-						mobj->z += mobj->target->height;
-				}
-				if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
-				{
-					mobj->tracer->z = mobj->z + P_MobjFlip(mobj)*20*mobj->scale;
-					if (mobj->eflags & MFE_VERTICALFLIP)
-						mobj->tracer->z += mobj->height;
-				}
-				break;
-			case MT_WAVINGFLAG1:
-			case MT_WAVINGFLAG2:
+		if (mobj->target && mobj->target->type == MT_NIGHTSDRONE_GOAL)
+		{
+			goalpost = mobj->target;
+			if (goalpost->target && goalpost->target->type == MT_NIGHTSDRONE_SPARKLING)
+				sparkle = goalpost->target;
+			if (goalpost->tracer && goalpost->tracer->type == MT_NIGHTSDRONE_MAN)
+				droneman = goalpost->tracer;
+		}
+
+		if (!goalpost || !sparkle || !droneman)
+			return;
+
+		// did NIGHTSDRONE position, scale, flip, or flags change? all elements need to be synced
+		droneboxmandiff = max(mobj->height - droneman->height, 0);
+		dronemangoaldiff = max(droneman->height - goalpost->height, 0);
+
+		if (!(goalpost->flags2 & MF2_OBJECTFLIP) && (mobj->flags2 & MF2_OBJECTFLIP))
+		{
+			goalpost->eflags |= MFE_VERTICALFLIP;
+			goalpost->flags2 |= MF2_OBJECTFLIP;
+			sparkle->eflags |= MFE_VERTICALFLIP;
+			sparkle->flags2 |= MF2_OBJECTFLIP;
+			droneman->eflags |= MFE_VERTICALFLIP;
+			droneman->flags2 |= MF2_OBJECTFLIP;
+			flipchanged = true;
+		}
+		else if ((goalpost->flags2 & MF2_OBJECTFLIP) && !(mobj->flags2 & MF2_OBJECTFLIP))
+		{
+			goalpost->eflags &= ~MFE_VERTICALFLIP;
+			goalpost->flags2 &= ~MF2_OBJECTFLIP;
+			sparkle->eflags &= ~MFE_VERTICALFLIP;
+			sparkle->flags2 &= ~MF2_OBJECTFLIP;
+			droneman->eflags &= ~MFE_VERTICALFLIP;
+			droneman->flags2 &= ~MF2_OBJECTFLIP;
+			flipchanged = true;
+		}
+
+		if (goalpost->destscale != mobj->destscale
+			|| goalpost->movefactor != mobj->z
+			|| goalpost->friction != mobj->height
+			|| flipchanged
+			|| goalpost->threshold != (INT32)(mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE)))
+		{
+			goalpost->destscale = sparkle->destscale = droneman->destscale = mobj->destscale;
+
+			// straight copy-pasta from P_SpawnMapThing, case MT_NIGHTSDRONE
+			if (!flip)
+			{
+				if (topaligned) // Align droneman to top of hitbox
 				{
-					fixed_t base = (leveltime<<(FRACBITS+1));
-					mobj_t *seg = mobj->tracer, *prev = mobj;
-					mobj->movedir = mobj->angle
-						+ ((((FINESINE((FixedAngle(base<<1)>>ANGLETOFINESHIFT) & FINEMASK)
-							+ FINESINE((FixedAngle(base<<4)>>ANGLETOFINESHIFT) & FINEMASK))>>1)
-						+ FINESINE((FixedAngle(base*9)>>ANGLETOFINESHIFT) & FINEMASK)
-						+ FINECOSINE(((FixedAngle(base*9))>>ANGLETOFINESHIFT) & FINEMASK))<<12); //*2^12
-					while (seg)
-					{
-						seg->movedir = seg->angle;
-						seg->angle = prev->movedir;
-						P_UnsetThingPosition(seg);
-						seg->x = prev->x + P_ReturnThrustX(prev, prev->angle, prev->radius);
-						seg->y = prev->y + P_ReturnThrustY(prev, prev->angle, prev->radius);
-						seg->z = prev->z + prev->height - (seg->scale>>1);
-						P_SetThingPosition(seg);
-						prev = seg;
-						seg = seg->tracer;
-					}
+					dronemanoffset = droneboxmandiff;
+					goaloffset = dronemangoaldiff/2 + dronemanoffset;
 				}
-				break;
-			case MT_SPINCUSHION:
-				if (mobj->target && mobj->state-states >= S_SPINCUSHION_AIM1 && mobj->state-states <= S_SPINCUSHION_AIM5)
-					mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
-				break;
-			case MT_CRUSHCLAW:
-				if (mobj->state-states == S_CRUSHCLAW_STAY && mobj->target)
+				else if (middlealigned) // Align droneman to center of hitbox
 				{
-					mobj_t *chain = mobj->target->target;
-					SINT8 sign = ((mobj->tics & 1) ? mobj->tics : -(SINT8)(mobj->tics));
-					while (chain)
-					{
-						chain->z = chain->watertop + sign*mobj->scale;
-						sign = -sign;
-						chain = chain->target;
-					}
+					dronemanoffset = droneboxmandiff/2;
+					goaloffset = dronemangoaldiff/2 + dronemanoffset;
 				}
-				break;
-			case MT_SMASHINGSPIKEBALL:
-				mobj->momx = mobj->momy = 0;
-				if (mobj->state-states == S_SMASHSPIKE_FALL && P_IsObjectOnGround(mobj))
+				else if (bottomoffsetted)
 				{
-					P_SetMobjState(mobj, S_SMASHSPIKE_STOMP1);
-					S_StartSound(mobj, sfx_spsmsh);
+					dronemanoffset = 24*FRACUNIT;
+					goaloffset = dronemangoaldiff + dronemanoffset;
 				}
-				else if (mobj->state-states == S_SMASHSPIKE_RISE2 && P_MobjFlip(mobj)*(mobj->z - mobj->movecount) >= 0)
+				else
 				{
-					mobj->momz = 0;
-					P_SetMobjState(mobj, S_SMASHSPIKE_FLOAT);
+					dronemanoffset = 0;
+					goaloffset = dronemangoaldiff/2 + dronemanoffset;
 				}
-				break;
-			case MT_HANGSTER:
-				{
-					statenum_t st =  mobj->state-states;
-					//ghost image trail when flying down
-					if (st == S_HANGSTER_SWOOP1 || st == S_HANGSTER_SWOOP2)
-					{
-						P_SpawnGhostMobj(mobj);
-						//curve when in line with target, otherwise curve to avoid crashing into floor
-						if ((mobj->z - mobj->floorz <= 80*FRACUNIT) || (mobj->target && (mobj->z - mobj->target->z <= 80*FRACUNIT)))
-							P_SetMobjState(mobj, (st = S_HANGSTER_ARC1));
-					}
 
-					//swoop arc movement stuff
-					if (st == S_HANGSTER_ARC1)
-					{
-						A_FaceTarget(mobj);
-						P_Thrust(mobj, mobj->angle, 1*FRACUNIT);
-					}
-					else if (st == S_HANGSTER_ARC2)
-						P_Thrust(mobj, mobj->angle, 2*FRACUNIT);
-					else if (st == S_HANGSTER_ARC3)
-						P_Thrust(mobj, mobj->angle, 4*FRACUNIT);
-					//if movement has stopped while flying (like hitting a wall), fly up immediately
-					else if (st == S_HANGSTER_FLY1 && !mobj->momx && !mobj->momy)
-					{
-						mobj->extravalue1 = 0;
-						P_SetMobjState(mobj, S_HANGSTER_ARCUP1);
-					}
-					//after swooping back up, check for ceiling
-					else if ((st == S_HANGSTER_RETURN1 || st == S_HANGSTER_RETURN2) && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height))
-						P_SetMobjState(mobj, (st = S_HANGSTER_RETURN3));
-
-					//should you roost on a ceiling with F_SKY1 as its flat, disappear forever
-					if (st == S_HANGSTER_RETURN3 && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height)
-					&& mobj->subsector->sector->ceilingpic == skyflatnum
-					&& mobj->subsector->sector->ceilingheight == mobj->ceilingz)
-					{
-						P_RemoveMobj(mobj);
-						return;
-					}
-				}
-				break;
-			case MT_LHRT:
-				mobj->momx = FixedMul(mobj->momx, mobj->extravalue2);
-				mobj->momy = FixedMul(mobj->momy, mobj->extravalue2);
-				break;
-			case MT_EGGCAPSULE:
-				if (!mobj->reactiontime)
+				sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
+			}
+			else
+			{
+				if (topaligned) // Align droneman to top of hitbox
 				{
-					// Target nearest player on your mare.
-					// (You can make it float up/down by adding MF_FLOAT,
-					//  but beware level design pitfalls.)
-					fixed_t shortest = 1024*FRACUNIT;
-					INT32 i;
-					P_SetTarget(&mobj->target, NULL);
-					for (i = 0; i < MAXPLAYERS; i++)
-						if (playeringame[i] && players[i].mo
-						&& players[i].mare == mobj->threshold && players[i].spheres > 0)
-						{
-							fixed_t dist = P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y);
-							if (dist < shortest)
-							{
-								P_SetTarget(&mobj->target, players[i].mo);
-								shortest = dist;
-							}
-						}
+					dronemanoffset = 0;
+					goaloffset = dronemangoaldiff/2 + dronemanoffset;
 				}
-				break;
-			case MT_EGGMOBILE2_POGO:
-				if (!mobj->target
-				|| !mobj->target->health
-				|| mobj->target->state == &states[mobj->target->info->spawnstate]
-				|| mobj->target->state == &states[mobj->target->info->raisestate])
+				else if (middlealigned) // Align droneman to center of hitbox
 				{
-					P_RemoveMobj(mobj);
-					return;
+					dronemanoffset = droneboxmandiff/2;
+					goaloffset = dronemangoaldiff/2 + dronemanoffset;
 				}
-				P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z - mobj->height);
-				break;
-			case MT_HAMMER:
-				if (mobj->z <= mobj->floorz)
+				else if (bottomoffsetted)
 				{
-					P_RemoveMobj(mobj);
-					return;
+					dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+					goaloffset = dronemangoaldiff + dronemanoffset;
 				}
-				break;
-			case MT_KOOPA:
-				P_KoopaThinker(mobj);
-				break;
-			case MT_FIREBALL:
-				if (P_AproxDistance(mobj->momx, mobj->momy) <= 16*FRACUNIT) // Once fireballs lose enough speed, kill them
+				else
 				{
-					P_KillMobj(mobj, NULL, NULL, 0);
-					return;
+					dronemanoffset = droneboxmandiff;
+					goaloffset = dronemangoaldiff/2 + dronemanoffset;
 				}
-				break;
-			case MT_REDRING:
-				if (((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
-					&& mobj->flags & MF_MISSILE)
+
+				sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
+			}
+
+			P_TeleportMove(goalpost, mobj->x, mobj->y, mobj->z + goaloffset);
+			P_TeleportMove(sparkle, mobj->x, mobj->y, mobj->z + sparkleoffset);
+			if (goalpost->movefactor != mobj->z || goalpost->friction != mobj->height)
+			{
+				P_TeleportMove(droneman, mobj->x, mobj->y, mobj->z + dronemanoffset);
+				goalpost->movefactor = mobj->z;
+				goalpost->friction = mobj->height;
+			}
+			goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
+		}
+		else
+		{
+			if (goalpost->x != mobj->x || goalpost->y != mobj->y)
+			{
+				P_TeleportMove(goalpost, mobj->x, mobj->y, goalpost->z);
+				P_TeleportMove(sparkle, mobj->x, mobj->y, sparkle->z);
+			}
+
+			if (droneman->x != mobj->x || droneman->y != mobj->y)
+				P_TeleportMove(droneman, mobj->x, mobj->y,
+					droneman->z >= mobj->floorz && droneman->z <= mobj->ceilingz ? droneman->z : mobj->z);
+		}
+
+		// now toggle states!
+		// GOAL mode?
+		if (sparkle->state >= &states[S_NIGHTSDRONE_SPARKLING1] && sparkle->state <= &states[S_NIGHTSDRONE_SPARKLING16])
+		{
+			INT32 i;
+			boolean bonustime = false;
+
+			for (i = 0; i < MAXPLAYERS; i++)
+				if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				{
-					P_ExplodeMissile(mobj);
-					return;
+					bonustime = true;
+					break;
 				}
-				break;
-			case MT_BOSSFLYPOINT:
-				return;
-			case MT_NIGHTSCORE:
-				mobj->color = (UINT8)(leveltime % SKINCOLOR_WHITE);
-				break;
-			case MT_JETFUME1:
-				{
-					fixed_t jetx, jety;
 
-					if (!mobj->target // if you have no target
-					|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
-					{ // then remove yourself as well!
-						P_RemoveMobj(mobj);
-						return;
-					}
+			if (!bonustime)
+			{
+				CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
+				if (goalpost && goalpost->state != &states[S_INVISIBLE])
+					P_SetMobjState(goalpost, S_INVISIBLE);
+				if (sparkle && sparkle->state != &states[S_INVISIBLE])
+					P_SetMobjState(sparkle, S_INVISIBLE);
+			}
+		}
+		// Invisible/bouncing mode.
+		else
+		{
+			INT32 i;
+			boolean bonustime = false;
+			fixed_t zcomp;
+
+			// Bouncy bouncy!
+			if (!flip)
+			{
+				if (topaligned)
+					zcomp = droneboxmandiff + mobj->z;
+				else if (middlealigned)
+					zcomp = (droneboxmandiff/2) + mobj->z;
+				else if (bottomoffsetted)
+					zcomp = mobj->z + FixedMul(24*FRACUNIT, mobj->scale);
+				else
+					zcomp = mobj->z;
+			}
+			else
+			{
+				if (topaligned)
+					zcomp = mobj->z;
+				else if (middlealigned)
+					zcomp = (droneboxmandiff/2) + mobj->z;
+				else if (bottomoffsetted)
+					zcomp = mobj->z + droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+				else
+					zcomp = mobj->z + droneboxmandiff;
+			}
 
-					jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
-					jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
+			droneman->angle += ANG10;
+			if (!flip && droneman->z <= zcomp)
+				droneman->momz = FixedMul(5*FRACUNIT, droneman->scale);
+			else if (flip && droneman->z >= zcomp)
+				droneman->momz = FixedMul(-5*FRACUNIT, droneman->scale);
 
-					if (mobj->fuse == 56) // First one
-					{
-						P_UnsetThingPosition(mobj);
-						mobj->x = jetx;
-						mobj->y = jety;
-						if (mobj->target->eflags & MFE_VERTICALFLIP)
-							mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale);
-						else
-							mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale);
-						mobj->floorz = mobj->z;
-						mobj->ceilingz = mobj->z+mobj->height;
-						P_SetThingPosition(mobj);
-					}
-					else if (mobj->fuse == 57)
-					{
-						P_UnsetThingPosition(mobj);
-						mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-						mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-						if (mobj->target->eflags & MFE_VERTICALFLIP)
-							mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
-						else
-							mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
-						mobj->floorz = mobj->z;
-						mobj->ceilingz = mobj->z+mobj->height;
-						P_SetThingPosition(mobj);
-					}
-					else if (mobj->fuse == 58)
-					{
-						P_UnsetThingPosition(mobj);
-						mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-						mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
-						if (mobj->target->eflags & MFE_VERTICALFLIP)
-							mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
-						else
-							mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
-						mobj->floorz = mobj->z;
-						mobj->ceilingz = mobj->z+mobj->height;
-						P_SetThingPosition(mobj);
-					}
-					else if (mobj->fuse == 59)
-					{
-						boolean dashmod = ((mobj->target->flags & MF_PAIN) && (mobj->target->health <= mobj->target->info->damage));
-						jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius);
-						jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius);
-						P_UnsetThingPosition(mobj);
-						mobj->x = jetx;
-						mobj->y = jety;
-						mobj->destscale = mobj->target->scale;
-						if (!(dashmod && mobj->target->state == states+S_METALSONIC_BOUNCE))
-						{
-							mobj->destscale = (mobj->destscale + FixedDiv(R_PointToDist2(0, 0, mobj->target->momx, mobj->target->momy), 36*mobj->target->scale))/3;
-						}
-						if (mobj->target->eflags & MFE_VERTICALFLIP)
-							mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2;
-						else
-							mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2;
-						mobj->floorz = mobj->z;
-						mobj->ceilingz = mobj->z+mobj->height;
-						P_SetThingPosition(mobj);
-						if (dashmod)
-						{
-							mobj->color = SKINCOLOR_SUNSET;
-							if (mobj->target->movecount == 3 && !mobj->target->reactiontime && (mobj->target->movedir == 0 || mobj->target->movedir == 2))
-								P_SpawnGhostMobj(mobj);
-						}
-						else
-							mobj->color = SKINCOLOR_ICY;
-					}
-					mobj->fuse++;
-				}
-				break;
-			case MT_JETFLAME:
+			// state switching logic
+			for (i = 0; i < MAXPLAYERS; i++)
+				if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
 				{
-					if (!mobj->target // if you have no target
-					|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
-					{ // then remove yourself as well!
-						P_RemoveMobj(mobj);
-						return;
+					bonustime = true;
+					break;
+				}
+
+			if (bonustime)
+			{
+				CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
+				if (!(droneman->flags2 & MF2_DONTDRAW))
+					droneman->flags2 |= MF2_DONTDRAW;
+				if (goalpost->state == &states[S_INVISIBLE])
+					P_SetMobjState(goalpost, mobjinfo[goalpost->type].meleestate);
+				if (sparkle->state == &states[S_INVISIBLE])
+					P_SetMobjState(sparkle, mobjinfo[sparkle->type].meleestate);
+			}
+			else if (!G_IsSpecialStage(gamemap))
+			{
+				for (i = 0; i < MAXPLAYERS; i++)
+					if (playeringame[i] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
+					{
+						bonustime = true; // variable reuse
+						break;
 					}
 
-					P_UnsetThingPosition(mobj);
-					mobj->x = mobj->target->x;
-					mobj->y = mobj->target->y;
-					mobj->z = mobj->target->z - 50*mobj->target->scale;
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_SetThingPosition(mobj);
+				if (bonustime)
+				{
+					// show droneman if at least one player is non-nights
+					if (goalpost->state != &states[S_INVISIBLE])
+						P_SetMobjState(goalpost, S_INVISIBLE);
+					if (sparkle->state != &states[S_INVISIBLE])
+						P_SetMobjState(sparkle, S_INVISIBLE);
+					if (droneman->state != &states[mobjinfo[droneman->type].meleestate])
+						P_SetMobjState(droneman, mobjinfo[droneman->type].meleestate);
+					if (droneman->flags2 & MF2_DONTDRAW)
+						droneman->flags2 &= ~MF2_DONTDRAW;
 				}
-				break;
-			case MT_EGGROBO1:
-#define SPECTATORRADIUS (96*mobj->scale)
+				else
 				{
-					if (!(mobj->flags2 & MF2_STRONGBOX))
-					{
-						mobj->cusval = mobj->x; // eat my SOCs, p_mobj.h warning, we have lua now
-						mobj->cvmem = mobj->y; // ditto
-						mobj->movedir = mobj->angle;
-						mobj->threshold = P_MobjFlip(mobj)*10*mobj->scale;
-						if (mobj->threshold < 0)
-							mobj->threshold += (mobj->ceilingz - mobj->height);
-						else
-							mobj->threshold += mobj->floorz;
-						var1 = 4;
-						A_BossJetFume(mobj);
-						mobj->flags2 |= MF2_STRONGBOX;
-					}
+					// else, hide it
+					if (!(droneman->flags2 & MF2_DONTDRAW))
+						droneman->flags2 |= MF2_DONTDRAW;
+				}
+			}
+		}
+	}
+}
 
-					if (mobj->state == &states[mobj->info->deathstate]) // todo: make map actually set health to 0 for these
-					{
-						if (mobj->movecount)
-						{
-							if (!(--mobj->movecount))
-								S_StartSound(mobj, mobj->info->deathsound);
-						}
-						else
-						{
-							mobj->momz += P_MobjFlip(mobj)*mobj->scale;
-							if (mobj->momz > 0)
-							{
-								if (mobj->z + mobj->momz > mobj->ceilingz + (1000<<FRACBITS))
-								{
-									P_RemoveMobj(mobj);
-									return;
-								}
-							}
-							else if (mobj->z + mobj->height + mobj->momz < mobj->floorz - (1000<<FRACBITS))
-							{
-								P_RemoveMobj(mobj);
-								return;
-							}
-						}
-					}
-					else
-					{
+static boolean P_TurretThink(mobj_t *mobj)
+{
+	P_MobjCheckWater(mobj);
+	P_CheckPosition(mobj, mobj->x, mobj->y);
+	if (P_MobjWasRemoved(mobj))
+		return false;
+	mobj->floorz = tmfloorz;
+	mobj->ceilingz = tmceilingz;
+	mobj->floorrover = tmfloorrover;
+	mobj->ceilingrover = tmceilingrover;
 
-						fixed_t basex = mobj->cusval, basey = mobj->cvmem;
+	if ((mobj->eflags & MFE_UNDERWATER) && mobj->health > 0)
+	{
+		P_SetMobjState(mobj, mobj->info->deathstate);
+		mobj->health = 0;
+		mobj->flags2 &= ~MF2_FIRING;
+	}
+	else if (mobj->health > 0 && mobj->z + mobj->height > mobj->ceilingz) // Crushed
+	{
+		INT32 i, j;
+		fixed_t ns;
+		fixed_t x, y, z;
+		mobj_t* mo2;
 
-						if (mobj->spawnpoint && mobj->spawnpoint->options & (MTF_AMBUSH|MTF_OBJECTSPECIAL))
-						{
-							angle_t sideang = mobj->movedir + ((mobj->spawnpoint->options & MTF_AMBUSH) ? ANGLE_90 : -ANGLE_90);
-							fixed_t oscillate = FixedMul(FINESINE(((leveltime*ANG1)>>(ANGLETOFINESHIFT+2)) & FINEMASK), 250*mobj->scale);
-							basex += P_ReturnThrustX(mobj, sideang, oscillate);
-							basey += P_ReturnThrustY(mobj, sideang, oscillate);
-						}
+		z = mobj->subsector->sector->floorheight + FixedMul(64*FRACUNIT, mobj->scale);
+		for (j = 0; j < 2; j++)
+		{
+			for (i = 0; i < 32; i++)
+			{
+				const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
+				ns = FixedMul(64*FRACUNIT, mobj->scale);
+				x = mobj->x + FixedMul(FINESINE(fa), ns);
+				y = mobj->y + FixedMul(FINECOSINE(fa), ns);
 
-						mobj->z = mobj->threshold + FixedMul(FINESINE(((leveltime + mobj->movecount)*ANG2>>(ANGLETOFINESHIFT-2)) & FINEMASK), 8*mobj->scale);
-						if (mobj->state != &states[mobj->info->meleestate])
-						{
-							boolean didmove = false;
+				mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
+				ns = FixedMul(16*FRACUNIT, mobj->scale);
+				mo2->momx = FixedMul(FINESINE(fa), ns);
+				mo2->momy = FixedMul(FINECOSINE(fa), ns);
+			}
+			z -= FixedMul(32*FRACUNIT, mobj->scale);
+		}
+		P_SetMobjState(mobj, mobj->info->deathstate);
+		mobj->health = 0;
+		mobj->flags2 &= ~MF2_FIRING;
+	}
+	return true;
+}
 
-							if (mobj->state == &states[mobj->info->spawnstate])
-							{
-								UINT8 i;
-								fixed_t dist = INT32_MAX;
+static void P_SaloonDoorThink(mobj_t *mobj)
+{
+	fixed_t x = mobj->tracer->x;
+	fixed_t y = mobj->tracer->y;
+	fixed_t z = mobj->tracer->z;
+	angle_t oang = FixedAngle(mobj->extravalue1);
+	angle_t fa = (oang >> ANGLETOFINESHIFT) & FINEMASK;
+	fixed_t c0 = -96*FINECOSINE(fa);
+	fixed_t s0 = -96*FINESINE(fa);
+	angle_t fma;
+	fixed_t c, s;
+	angle_t angdiff;
 
-								for (i = 0; i < MAXPLAYERS; i++)
-								{
-									fixed_t compdist;
-									if (!playeringame[i])
-										continue;
-									if (players[i].spectator)
-										continue;
-									if (!players[i].mo)
-										continue;
-									if (!players[i].mo->health)
-										continue;
-									if (P_PlayerInPain(&players[i]))
-										continue;
-									if (players[i].mo->z > mobj->z + mobj->height + 8*mobj->scale)
-										continue;
-									if (players[i].mo->z + players[i].mo->height < mobj->z - 8*mobj->scale)
-										continue;
-									compdist = P_AproxDistance(
-										players[i].mo->x + players[i].mo->momx - basex,
-										players[i].mo->y + players[i].mo->momy - basey);
-									if (compdist >= dist)
-										continue;
-									dist = compdist;
-									P_SetTarget(&mobj->target, players[i].mo);
-								}
+	// Adjust angular speed
+	fixed_t da = AngleFixed(mobj->angle - oang);
+	if (da > 180*FRACUNIT)
+		da -= 360*FRACUNIT;
+	mobj->extravalue2 = 8*(mobj->extravalue2 - da/32)/9;
 
-								if (dist < (SPECTATORRADIUS<<1))
-								{
-									didmove = true;
-									mobj->frame = 3 + ((leveltime & 2)>>1);
-									mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
-
-									if (P_AproxDistance(
-										mobj->x - basex,
-										mobj->y - basey)
-										< mobj->scale)
-										S_StartSound(mobj, mobj->info->seesound);
-
-									P_TeleportMove(mobj,
-										(15*(mobj->x>>4)) + (basex>>4) + P_ReturnThrustX(mobj, mobj->angle, SPECTATORRADIUS>>4),
-										(15*(mobj->y>>4)) + (basey>>4) + P_ReturnThrustY(mobj, mobj->angle, SPECTATORRADIUS>>4),
-										mobj->z);
-								}
-								else
-								{
-									angle_t diff = (mobj->movedir - mobj->angle);
-									if (diff > ANGLE_180)
-										diff = InvAngle(InvAngle(diff)/8);
-									else
-										diff /= 8;
-									mobj->angle += diff;
-
-									dist = FINECOSINE(((leveltime + mobj->movecount)*ANG2>>(ANGLETOFINESHIFT-2)) & FINEMASK);
-
-									if (abs(dist) < FRACUNIT/2)
-										mobj->frame = 0;
-									else
-										mobj->frame = (dist > 0) ? 1 : 2;
-								}
-							}
+	// Update angle
+	mobj->angle += FixedAngle(mobj->extravalue2);
 
-							if (!didmove)
-							{
-								if (P_AproxDistance(mobj->x - basex, mobj->y - basey) < mobj->scale)
-									P_TeleportMove(mobj, basex, basey, mobj->z);
-								else
-									P_TeleportMove(mobj,
-										(15*(mobj->x>>4)) + (basex>>4),
-										(15*(mobj->y>>4)) + (basey>>4),
-										mobj->z);
-							}
-						}
-					}
-				}
-				break;
-#undef SPECTATORRADIUS
-			case MT_EGGROBO1JET:
-				{
-					if (!mobj->target || P_MobjWasRemoved(mobj->target) // if you have no target
-					|| (mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
-					{ // then remove yourself as well!
-						P_RemoveMobj(mobj);
-						return;
-					}
+	angdiff = mobj->angle - FixedAngle(mobj->extravalue1);
+	if (angdiff > (ANGLE_90 - ANG2) && angdiff < ANGLE_180)
+	{
+		mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_90 - ANG2);
+		mobj->extravalue2 /= 2;
+	}
+	else if (angdiff < (ANGLE_270 + ANG2) && angdiff >= ANGLE_180)
+	{
+		mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_270 + ANG2);
+		mobj->extravalue2 /= 2;
+	}
 
-					mobj->flags2 ^= MF2_DONTDRAW;
-
-					P_UnsetThingPosition(mobj);
-					mobj->x = mobj->target->x + P_ReturnThrustX(mobj, mobj->target->angle+ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustX(mobj, mobj->target->angle, 19*mobj->target->scale);
-					mobj->y = mobj->target->y + P_ReturnThrustY(mobj, mobj->target->angle+ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustY(mobj, mobj->target->angle, 19*mobj->target->scale);
-					mobj->z = mobj->target->z;
-					if (mobj->target->eflags & MFE_VERTICALFLIP)
-						mobj->z += (mobj->target->height - mobj->height);
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_SetThingPosition(mobj);
-				}
-				break;
-			case MT_NIGHTSDRONE:
-				{
-					// variable setup
-					mobj_t *goalpost = NULL;
-					mobj_t *sparkle = NULL;
-					mobj_t *droneman = NULL;
+	// Update position
+	fma = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
+	c = 48*FINECOSINE(fma);
+	s = 48*FINESINE(fma);
+	P_TeleportMove(mobj, x + c0 + c, y + s0 + s, z);
+}
 
-					boolean flip = mobj->flags2 & MF2_OBJECTFLIP;
-					boolean topaligned = (mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
-					boolean middlealigned = (mobj->flags & MF_GRENADEBOUNCE) && !(mobj->flags & MF_SLIDEME);
-					boolean bottomoffsetted = !(mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
-					boolean flipchanged = false;
+static void P_PyreFlyThink(mobj_t *mobj)
+{
+	fixed_t hdist;
 
-					fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
+	mobj->extravalue1 = (mobj->extravalue1 + 3) % 360;
+	mobj->z += FINESINE(((mobj->extravalue1 * ANG1) >> ANGLETOFINESHIFT) & FINEMASK);
 
-					if (mobj->target && mobj->target->type == MT_NIGHTSDRONE_GOAL)
-					{
-						goalpost = mobj->target;
-						if (goalpost->target && goalpost->target->type == MT_NIGHTSDRONE_SPARKLING)
-							sparkle = goalpost->target;
-						if (goalpost->tracer && goalpost->tracer->type == MT_NIGHTSDRONE_MAN)
-							droneman = goalpost->tracer;
-					}
+	if (!(mobj->flags2 & MF2_BOSSNOTRAP))
+		P_LookForPlayers(mobj, true, false, 1500*FRACUNIT);
 
-					if (!goalpost || !sparkle || !droneman)
-						break;
+	if (!mobj->target)
+		return;
+
+	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) << FRACBITS, 20, MT_FLAMEPARTICLE, 4*FRACUNIT);
+		P_PyreFlyBurn(mobj, fireradius*FRACUNIT, 40, MT_PYREFLY_FIRE, 0);
+	}
 
-					// did NIGHTSDRONE position, scale, flip, or flags change? all elements need to be synced
-					droneboxmandiff = max(mobj->height - droneman->height, 0);
-					dronemangoaldiff = max(droneman->height - goalpost->height, 0);
+	hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
 
-					if (!(goalpost->flags2 & MF2_OBJECTFLIP) && (mobj->flags2 & MF2_OBJECTFLIP))
-					{
-						goalpost->eflags |= MFE_VERTICALFLIP;
-						goalpost->flags2 |= MF2_OBJECTFLIP;
-						sparkle->eflags |= MFE_VERTICALFLIP;
-						sparkle->flags2 |= MF2_OBJECTFLIP;
-						droneman->eflags |= MFE_VERTICALFLIP;
-						droneman->flags2 |= MF2_OBJECTFLIP;
-						flipchanged = true;
-					}
-					else if ((goalpost->flags2 & MF2_OBJECTFLIP) && !(mobj->flags2 & MF2_OBJECTFLIP))
-					{
-						goalpost->eflags &= ~MFE_VERTICALFLIP;
-						goalpost->flags2 &= ~MF2_OBJECTFLIP;
-						sparkle->eflags &= ~MFE_VERTICALFLIP;
-						sparkle->flags2 &= ~MF2_OBJECTFLIP;
-						droneman->eflags &= ~MFE_VERTICALFLIP;
-						droneman->flags2 &= ~MF2_OBJECTFLIP;
-						flipchanged = true;
-					}
+	if (hdist > 1500*FRACUNIT)
+	{
+		mobj->flags2 &= ~MF2_BOSSNOTRAP;
+		P_SetTarget(&mobj->target, NULL);
+		return;
+	}
 
-					if (goalpost->destscale != mobj->destscale
-					    || goalpost->movefactor != mobj->z
-						|| goalpost->friction != mobj->height
-						|| flipchanged
-						|| goalpost->threshold != (INT32)(mobj->flags & (MF_SLIDEME | MF_GRENADEBOUNCE)))
-					{
-						goalpost->destscale = sparkle->destscale = droneman->destscale = mobj->destscale;
+	if (!(mobj->flags2 & MF2_BOSSNOTRAP) && hdist <= 450*FRACUNIT)
+		mobj->flags2 |= MF2_BOSSNOTRAP;
 
-						// straight copy-pasta from P_SpawnMapThing, case MT_NIGHTSDRONE
-						if (!flip)
-						{
-							if (topaligned) // Align droneman to top of hitbox
-							{
-								dronemanoffset = droneboxmandiff;
-								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-							}
-							else if (middlealigned) // Align droneman to center of hitbox
-							{
-								dronemanoffset = droneboxmandiff / 2;
-								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-							}
-							else if (bottomoffsetted)
-							{
-								dronemanoffset = 24*FRACUNIT;
-								goaloffset = dronemangoaldiff + dronemanoffset;
-							}
-							else
-							{
-								dronemanoffset = 0;
-								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-							}
+	if (!(mobj->flags2 & MF2_BOSSNOTRAP))
+		return;
 
-							sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
-						}
-						else
-						{
-							if (topaligned) // Align droneman to top of hitbox
-							{
-								dronemanoffset = 0;
-								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-							}
-							else if (middlealigned) // Align droneman to center of hitbox
-							{
-								dronemanoffset = droneboxmandiff / 2;
-								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-							}
-							else if (bottomoffsetted)
-							{
-								dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
-								goaloffset = dronemangoaldiff + dronemanoffset;
-							}
-							else
-							{
-								dronemanoffset = droneboxmandiff;
-								goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-							}
+	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;
+	}
+}
 
-							sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
-						}
+static void P_PterabyteThink(mobj_t *mobj)
+{
+	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)
+			return; // Still carrying a player or in cooldown
+
+		P_LookForPlayers(mobj, true, false, 256*FRACUNIT);
+
+		if (!mobj->target)
+			return;
+
+		if (mobj->target->player->powers[pw_flashing])
+		{
+			P_SetTarget(&mobj->target, NULL);
+			return;
+		}
+
+		vdist = mobj->z - mobj->target->z - mobj->target->height;
+		if (P_MobjFlip(mobj)*vdist <= 0)
+		{
+			P_SetTarget(&mobj->target, NULL);
+			return;
+		}
 
-						P_TeleportMove(goalpost, mobj->x, mobj->y, mobj->z + goaloffset);
-						P_TeleportMove(sparkle, mobj->x, mobj->y, mobj->z + sparkleoffset);
-						if (goalpost->movefactor != mobj->z || goalpost->friction != mobj->height)
-						{
-							P_TeleportMove(droneman, mobj->x, mobj->y, mobj->z + dronemanoffset);
-							goalpost->movefactor = mobj->z;
-							goalpost->friction = mobj->height;
-						}
-						goalpost->threshold = mobj->flags & (MF_SLIDEME | MF_GRENADEBOUNCE);
-					}
-					else
-					{
-						if (goalpost->x != mobj->x || goalpost->y != mobj->y)
-						{
-							P_TeleportMove(goalpost, mobj->x, mobj->y, goalpost->z);
-							P_TeleportMove(sparkle, mobj->x, mobj->y, sparkle->z);
-						}
+		hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+		if (hdist > 450*FRACUNIT)
+		{
+			P_SetTarget(&mobj->target, NULL);
+			return;
+		}
 
-						if (droneman->x != mobj->x || droneman->y != mobj->y)
-							P_TeleportMove(droneman, mobj->x, mobj->y,
-							               droneman->z >= mobj->floorz && droneman->z <= mobj->ceilingz ? droneman->z : mobj->z);
-					}
+		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)
+			return;
 
-					// now toggle states!
-					// GOAL mode?
-					if (sparkle->state >= &states[S_NIGHTSDRONE_SPARKLING1] && sparkle->state <= &states[S_NIGHTSDRONE_SPARKLING16])
-					{
-						INT32 i;
-						boolean bonustime = false;
+		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;
+		}
+	}
+}
 
-						for (i = 0; i < MAXPLAYERS; i++)
-							if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
-							{
-								bonustime = true;
-								break;
-							}
+static void P_DragonbomberThink(mobj_t *mobj)
+{
+#define DRAGONTURNSPEED ANG2
+	mobj->movecount = (mobj->movecount + 9) % 360;
+	P_SetObjectMomZ(mobj, 4*FINESINE(((mobj->movecount*ANG1) >> ANGLETOFINESHIFT) & FINEMASK), false);
+	if (mobj->threshold > 0) // are we dropping mines?
+	{
+		mobj->threshold--;
+		if (mobj->threshold == 0) // if the timer hits 0, look for a mine to drop!
+		{
+			mobj_t *segment = mobj;
+			while (segment->tracer != NULL && !P_MobjWasRemoved(segment->tracer) && segment->tracer->state == &states[segment->tracer->info->spawnstate])
+				segment = segment->tracer;
+			if (segment != mobj) // found an unactivated segment?
+			{
+				mobj_t *mine = P_SpawnMobjFromMobj(segment, 0, 0, 0, segment->info->painchance);
+				mine->angle = segment->angle;
+				P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1);
+				P_SetObjectMomZ(mine, -2*FRACUNIT, true);
+				S_StartSound(mine, mine->info->seesound);
+				P_SetMobjState(segment, segment->info->raisestate);
+				mobj->threshold = mobj->info->painchance;
+			}
+		}
+	}
+	if (mobj->target) // Are we chasing a player?
+	{
+		fixed_t dist = P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y);
+		if (dist > 2000*mobj->scale) // Not anymore!
+			P_SetTarget(&mobj->target, NULL);
+		else
+		{
+			fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
+			fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->flags & MFE_VERTICALFLIP ? -128*mobj->scale : 128*mobj->scale + mobj->target->height);
+			angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
+			if (diff > ANGLE_180)
+				mobj->angle -= DRAGONTURNSPEED;
+			else
+				mobj->angle += DRAGONTURNSPEED;
+			if (!mobj->threshold && dist < 512*mobj->scale) // Close enough to drop bombs
+			{
+				mobj->threshold = mobj->info->painchance;
+			}
+			mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
+		}
+	}
+	else // Can we find a player to chase?
+	{
+		if (mobj->tracer == NULL || mobj->tracer->state != &states[mobj->tracer->info->spawnstate]
+			|| !P_LookForPlayers(mobj, true, false, 2000*mobj->scale)) // if not, circle around the spawnpoint
+		{
+			if (!mobj->spawnpoint) // unless we don't have one, in which case uhhh just circle around wherever we currently are I guess??
+				mobj->angle += DRAGONTURNSPEED;
+			else
+			{
+				fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
+				fixed_t x = mobj->spawnpoint->x << FRACBITS;
+				fixed_t y = mobj->spawnpoint->y << FRACBITS;
+				fixed_t z = mobj->spawnpoint->z << FRACBITS;
+				angle_t diff = R_PointToAngle2(mobj->x, mobj->y, x, y) - mobj->angle;
+				if (diff > ANGLE_180)
+					mobj->angle -= DRAGONTURNSPEED;
+				else
+					mobj->angle += DRAGONTURNSPEED;
+				mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
+			}
+		}
+	}
+	P_InstaThrust(mobj, mobj->angle, FixedMul(mobj->info->speed, mobj->scale));
+#undef DRAGONTURNSPEED
+}
 
-						if (!bonustime)
-						{
-							CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
-							if (goalpost && goalpost->state != &states[S_INVISIBLE])
-								P_SetMobjState(goalpost, S_INVISIBLE);
-							if (sparkle && sparkle->state != &states[S_INVISIBLE])
-								P_SetMobjState(sparkle, S_INVISIBLE);
-						}
-					}
-					// Invisible/bouncing mode.
-					else
-					{
-						INT32 i;
-						boolean bonustime = false;
-						fixed_t zcomp;
+static boolean P_MobjRegularThink(mobj_t *mobj)
+{
+	if ((mobj->flags & MF_ENEMY) && (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1))
+		mobj->flags2 &= ~MF2_FRET;
 
-						// Bouncy bouncy!
-						if (!flip)
-						{
-							if (topaligned)
-								zcomp = droneboxmandiff + mobj->z;
-							else if (middlealigned)
-								zcomp = (droneboxmandiff / 2) + mobj->z;
-							else if (bottomoffsetted)
-								zcomp = mobj->z + FixedMul(24*FRACUNIT, mobj->scale);
-							else
-								zcomp = mobj->z;
-						}
-						else
-						{
-							if (topaligned)
-								zcomp = mobj->z;
-							else if (middlealigned)
-								zcomp = (droneboxmandiff / 2) + mobj->z;
-							else if (bottomoffsetted)
-								zcomp = mobj->z + droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
-							else
-								zcomp = mobj->z + droneboxmandiff;
-						}
+	if (mobj->eflags & MFE_TRACERANGLE)
+		P_TracerAngleThink(mobj);
 
-						droneman->angle += ANG10;
-						if (!flip && droneman->z <= zcomp)
-							droneman->momz = FixedMul(5*FRACUNIT, droneman->scale);
-						else if (flip && droneman->z >= zcomp)
-							droneman->momz = FixedMul(-5*FRACUNIT, droneman->scale);
+	switch (mobj->type)
+	{
+	case MT_WALLSPIKEBASE:
+		if (!mobj->target) {
+			P_RemoveMobj(mobj);
+			return false;
+		}
+		mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
+#if 0
+		if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
+		{
+			mobj_t* target = mobj->target; // shortcut
+			const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
+			P_UnsetThingPosition(mobj);
+			mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
+			mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
+			P_SetThingPosition(mobj);
+			mobj->angle = target->angle + ANGLE_90;
+		}
+#endif
+		break;
+	case MT_FALLINGROCK:
+		// Despawn rocks here in case zmovement code can't do so (blame slopes)
+		if (!mobj->momx && !mobj->momy && !mobj->momz
+			&& ((mobj->eflags & MFE_VERTICALFLIP) ?
+				mobj->z + mobj->height >= mobj->ceilingz
+				: mobj->z <= mobj->floorz))
+		{
+			P_RemoveMobj(mobj);
+			return false;
+		}
+		P_MobjCheckWater(mobj);
+		break;
+	case MT_ARROW:
+		P_ArrowThink(mobj);
+		break;
+	case MT_EMERALDSPAWN:
+		if (mobj->threshold)
+		{
+			mobj->threshold--;
 
-						// state switching logic
-						for (i = 0; i < MAXPLAYERS; i++)
-							if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
-							{
-								bonustime = true;
-								break;
-							}
+			if (!mobj->threshold && !mobj->target && mobj->reactiontime)
+			{
+				mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime);
+				emerald->threshold = 42;
+				P_SetTarget(&mobj->target, emerald);
+				P_SetTarget(&emerald->target, mobj);
+			}
+		}
+		break;
+	case MT_BUGGLE:
+		mobj->eflags |= MFE_UNDERWATER; //P_MobjCheckWater(mobj); // solely for MFE_UNDERWATER for A_FlickySpawn
+		{
+			if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
+				&& P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius*16)
+			{
+				// Home in on the target.
+				P_HomingAttack(mobj, mobj->tracer);
 
-						if (bonustime)
-						{
-							CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
-							if (!(droneman->flags2 & MF2_DONTDRAW))
-								droneman->flags2 |= MF2_DONTDRAW;
-							if (goalpost->state == &states[S_INVISIBLE])
-								P_SetMobjState(goalpost, mobjinfo[goalpost->type].meleestate);
-							if (sparkle->state == &states[S_INVISIBLE])
-								P_SetMobjState(sparkle, mobjinfo[sparkle->type].meleestate);
-						}
-						else if (!G_IsSpecialStage(gamemap))
-						{
-							for (i = 0; i < MAXPLAYERS; i++)
-								if (playeringame[i] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
-								{
-									bonustime = true; // variable reuse
-									break;
-								}
+				if (mobj->z < mobj->floorz)
+					mobj->z = mobj->floorz;
 
-							if (bonustime)
-							{
-								// show droneman if at least one player is non-nights
-								if (goalpost->state != &states[S_INVISIBLE])
-									P_SetMobjState(goalpost, S_INVISIBLE);
-								if (sparkle->state != &states[S_INVISIBLE])
-									P_SetMobjState(sparkle, S_INVISIBLE);
-								if (droneman->state != &states[mobjinfo[droneman->type].meleestate])
-									P_SetMobjState(droneman, mobjinfo[droneman->type].meleestate);
-								if (droneman->flags2 & MF2_DONTDRAW)
-									droneman->flags2 &= ~MF2_DONTDRAW;
-							}
-							else
-							{
-								// else, hide it
-								if (!(droneman->flags2 & MF2_DONTDRAW))
-									droneman->flags2 |= MF2_DONTDRAW;
-							}
-						}
-					}
-				}
-				break;
-			case MT_PLAYER:
-				if (mobj->player)
-					P_PlayerMobjThinker(mobj);
-				return;
-			case MT_SKIM:
-				// check mobj against possible water content, before movement code
-				P_MobjCheckWater(mobj);
+				if (leveltime % mobj->info->painchance == 0)
+					S_StartSound(mobj, mobj->info->activesound);
 
-				// Keep Skim at water surface
-				if (mobj->z <= mobj->watertop)
+				if ((statenum_t)(mobj->state - states) != mobj->info->seestate)
+					P_SetMobjState(mobj, mobj->info->seestate);
+			}
+			else
+			{
+				// Try to find a player
+				P_LookForPlayers(mobj, true, true, mobj->radius*16);
+				mobj->momx >>= 1;
+				mobj->momy >>= 1;
+				mobj->momz >>= 1;
+				if ((statenum_t)(mobj->state - states) != mobj->info->spawnstate)
+					P_SetMobjState(mobj, mobj->info->spawnstate);
+			}
+		}
+		break;
+	case MT_BUMBLEBORE:
+		P_BumbleboreThink(mobj);
+		break;
+	case MT_BIGMINE:
+		mobj->extravalue1 += 3;
+		mobj->extravalue1 %= 360;
+		P_UnsetThingPosition(mobj);
+		mobj->z += FINESINE(mobj->extravalue1*(FINEMASK + 1)/360);
+		P_SetThingPosition(mobj);
+		break;
+	case MT_FLAME:
+		if (mobj->flags2 & MF2_BOSSNOTRAP)
+		{
+			if (!mobj->target || P_MobjWasRemoved(mobj->target))
+			{
+				if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
+					P_RemoveMobj(mobj->tracer);
+				P_RemoveMobj(mobj);
+				return false;
+			}
+			mobj->z = mobj->target->z + mobj->target->momz;
+			if (!(mobj->eflags & MFE_VERTICALFLIP))
+				mobj->z += mobj->target->height;
+		}
+		if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
+		{
+			mobj->tracer->z = mobj->z + P_MobjFlip(mobj)*20*mobj->scale;
+			if (mobj->eflags & MFE_VERTICALFLIP)
+				mobj->tracer->z += mobj->height;
+		}
+		break;
+	case MT_WAVINGFLAG1:
+	case MT_WAVINGFLAG2:
+	{
+		fixed_t base = (leveltime << (FRACBITS + 1));
+		mobj_t *seg = mobj->tracer, *prev = mobj;
+		mobj->movedir = mobj->angle
+			+ ((((FINESINE((FixedAngle(base << 1) >> ANGLETOFINESHIFT) & FINEMASK)
+				+ FINESINE((FixedAngle(base << 4) >> ANGLETOFINESHIFT) & FINEMASK)) >> 1)
+				+ FINESINE((FixedAngle(base*9) >> ANGLETOFINESHIFT) & FINEMASK)
+				+ FINECOSINE(((FixedAngle(base*9)) >> ANGLETOFINESHIFT) & FINEMASK)) << 12); //*2^12
+		while (seg)
+		{
+			seg->movedir = seg->angle;
+			seg->angle = prev->movedir;
+			P_UnsetThingPosition(seg);
+			seg->x = prev->x + P_ReturnThrustX(prev, prev->angle, prev->radius);
+			seg->y = prev->y + P_ReturnThrustY(prev, prev->angle, prev->radius);
+			seg->z = prev->z + prev->height - (seg->scale >> 1);
+			P_SetThingPosition(seg);
+			prev = seg;
+			seg = seg->tracer;
+		}
+	}
+	break;
+	case MT_SPINCUSHION:
+		if (mobj->target && mobj->state - states >= S_SPINCUSHION_AIM1 && mobj->state - states <= S_SPINCUSHION_AIM5)
+			mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+		break;
+	case MT_CRUSHCLAW:
+		if (mobj->state - states == S_CRUSHCLAW_STAY && mobj->target)
+		{
+			mobj_t *chain = mobj->target->target;
+			SINT8 sign = ((mobj->tics & 1) ? mobj->tics : -(SINT8)(mobj->tics));
+			while (chain)
+			{
+				chain->z = chain->watertop + sign*mobj->scale;
+				sign = -sign;
+				chain = chain->target;
+			}
+		}
+		break;
+	case MT_SMASHINGSPIKEBALL:
+		mobj->momx = mobj->momy = 0;
+		if (mobj->state - states == S_SMASHSPIKE_FALL && P_IsObjectOnGround(mobj))
+		{
+			P_SetMobjState(mobj, S_SMASHSPIKE_STOMP1);
+			S_StartSound(mobj, sfx_spsmsh);
+		}
+		else if (mobj->state - states == S_SMASHSPIKE_RISE2 && P_MobjFlip(mobj)*(mobj->z - mobj->movecount) >= 0)
+		{
+			mobj->momz = 0;
+			P_SetMobjState(mobj, S_SMASHSPIKE_FLOAT);
+		}
+		break;
+	case MT_HANGSTER:
+		if (!P_HangsterThink(mobj))
+			return false;
+		break;
+	case MT_LHRT:
+		mobj->momx = FixedMul(mobj->momx, mobj->extravalue2);
+		mobj->momy = FixedMul(mobj->momy, mobj->extravalue2);
+		break;
+	case MT_EGGCAPSULE:
+		if (!mobj->reactiontime)
+		{
+			// Target nearest player on your mare.
+			// (You can make it float up/down by adding MF_FLOAT,
+			//  but beware level design pitfalls.)
+			fixed_t shortest = 1024*FRACUNIT;
+			INT32 i;
+			P_SetTarget(&mobj->target, NULL);
+			for (i = 0; i < MAXPLAYERS; i++)
+				if (playeringame[i] && players[i].mo
+					&& players[i].mare == mobj->threshold && players[i].spheres > 0)
 				{
-					mobj->flags |= MF_NOGRAVITY;
-					if (mobj->z < mobj->watertop)
+					fixed_t dist = P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y);
+					if (dist < shortest)
 					{
-						if (mobj->watertop - mobj->z <= FixedMul(mobj->info->speed*FRACUNIT, mobj->scale))
-							mobj->z = mobj->watertop;
-						else
-							mobj->momz = FixedMul(mobj->info->speed*FRACUNIT, mobj->scale);
+						P_SetTarget(&mobj->target, players[i].mo);
+						shortest = dist;
 					}
 				}
-				else
-				{
-					mobj->flags &= ~MF_NOGRAVITY;
-					if (mobj->z > mobj->watertop && mobj->z - mobj->watertop < FixedMul(MAXSTEPMOVE, mobj->scale))
-						mobj->z = mobj->watertop;
-				}
-				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:
-				// No need to check water. Who cares?
-				P_RingThinker(mobj);
-				if (mobj->flags2 & MF2_NIGHTSPULL)
-					P_NightsItemChase(mobj);
-				else
-					A_AttractChase(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:
-				if (mobj->flags2 & MF2_NIGHTSPULL)
-					P_NightsItemChase(mobj);
-				else
-					A_AttractChase(mobj);
-				break;
-			case MT_EMBLEM:
-				if (mobj->flags2 & MF2_NIGHTSPULL)
-					P_NightsItemChase(mobj);
-				break;
-			case MT_SHELL:
-				if (mobj->threshold && mobj->threshold != TICRATE)
-					mobj->threshold--;
+		}
+		break;
+	case MT_EGGMOBILE2_POGO:
+		if (!mobj->target
+			|| !mobj->target->health
+			|| mobj->target->state == &states[mobj->target->info->spawnstate]
+			|| mobj->target->state == &states[mobj->target->info->raisestate])
+		{
+			P_RemoveMobj(mobj);
+			return false;
+		}
+		P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z - mobj->height);
+		break;
+	case MT_HAMMER:
+		if (mobj->z <= mobj->floorz)
+		{
+			P_RemoveMobj(mobj);
+			return false;
+		}
+		break;
+	case MT_KOOPA:
+		P_KoopaThinker(mobj);
+		break;
+	case MT_FIREBALL:
+		if (P_AproxDistance(mobj->momx, mobj->momy) <= 16*FRACUNIT) // Once fireballs lose enough speed, kill them
+		{
+			P_KillMobj(mobj, NULL, NULL, 0);
+			return false;
+		}
+		break;
+	case MT_REDRING:
+		if (((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
+			&& mobj->flags & MF_MISSILE)
+		{
+			P_ExplodeMissile(mobj);
+			return false;
+		}
+		break;
+	case MT_BOSSFLYPOINT:
+		return false;
+	case MT_NIGHTSCORE:
+		mobj->color = (UINT8)(leveltime % SKINCOLOR_WHITE);
+		break;
+	case MT_JETFUME1:
+		if (!P_JetFume1Think(mobj))
+			return false;
+		break;
+	case MT_JETFLAME:
+	{
+		if (!mobj->target // if you have no target
+			|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
+		{ // then remove yourself as well!
+			P_RemoveMobj(mobj);
+			return false;
+		}
 
-				if (mobj->threshold >= TICRATE)
-				{
-					mobj->angle += ((mobj->movedir == 1) ? ANGLE_22h : ANGLE_337h);
-					P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), (mobj->info->speed*mobj->scale));
-				}
-				break;
-			case MT_TURRET:
-				P_MobjCheckWater(mobj);
-				P_CheckPosition(mobj, mobj->x, mobj->y);
-				if (P_MobjWasRemoved(mobj))
-					return;
-				mobj->floorz = tmfloorz;
-				mobj->ceilingz = tmceilingz;
-				mobj->floorrover = tmfloorrover;
-				mobj->ceilingrover = tmceilingrover;
+		P_UnsetThingPosition(mobj);
+		mobj->x = mobj->target->x;
+		mobj->y = mobj->target->y;
+		mobj->z = mobj->target->z - 50*mobj->target->scale;
+		mobj->floorz = mobj->z;
+		mobj->ceilingz = mobj->z + mobj->height;
+		P_SetThingPosition(mobj);
+	}
+	break;
+	case MT_EGGROBO1:
+		if (!P_EggRobo1Think(mobj))
+			return false;
+		break;
+	case MT_EGGROBO1JET:
+	{
+		if (!mobj->target || P_MobjWasRemoved(mobj->target) // if you have no target
+			|| (mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
+		{ // then remove yourself as well!
+			P_RemoveMobj(mobj);
+			return false;
+		}
 
-				if ((mobj->eflags & MFE_UNDERWATER) && mobj->health > 0)
-				{
-					P_SetMobjState(mobj, mobj->info->deathstate);
-					mobj->health = 0;
-					mobj->flags2 &= ~MF2_FIRING;
-				}
-				else if (mobj->health > 0 && mobj->z + mobj->height > mobj->ceilingz) // Crushed
-				{
-					INT32 i,j;
-					fixed_t ns;
-					fixed_t x,y,z;
-					mobj_t *mo2;
+		mobj->flags2 ^= MF2_DONTDRAW;
+
+		P_UnsetThingPosition(mobj);
+		mobj->x = mobj->target->x + P_ReturnThrustX(mobj, mobj->target->angle + ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustX(mobj, mobj->target->angle, 19*mobj->target->scale);
+		mobj->y = mobj->target->y + P_ReturnThrustY(mobj, mobj->target->angle + ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustY(mobj, mobj->target->angle, 19*mobj->target->scale);
+		mobj->z = mobj->target->z;
+		if (mobj->target->eflags & MFE_VERTICALFLIP)
+			mobj->z += (mobj->target->height - mobj->height);
+		mobj->floorz = mobj->z;
+		mobj->ceilingz = mobj->z + mobj->height;
+		P_SetThingPosition(mobj);
+	}
+	break;
+	case MT_NIGHTSDRONE:
+		P_NiGHTSDroneThink(mobj);
+		break;
+	case MT_PLAYER:
+		if (mobj->player)
+			P_PlayerMobjThinker(mobj);
+		return false;
+	case MT_SKIM:
+		// check mobj against possible water content, before movement code
+		P_MobjCheckWater(mobj);
+
+		// Keep Skim at water surface
+		if (mobj->z <= mobj->watertop)
+		{
+			mobj->flags |= MF_NOGRAVITY;
+			if (mobj->z < mobj->watertop)
+			{
+				if (mobj->watertop - mobj->z <= FixedMul(mobj->info->speed*FRACUNIT, mobj->scale))
+					mobj->z = mobj->watertop;
+				else
+					mobj->momz = FixedMul(mobj->info->speed*FRACUNIT, mobj->scale);
+			}
+		}
+		else
+		{
+			mobj->flags &= ~MF_NOGRAVITY;
+			if (mobj->z > mobj->watertop && mobj->z - mobj->watertop < FixedMul(MAXSTEPMOVE, mobj->scale))
+				mobj->z = mobj->watertop;
+		}
+		break;
+	case MT_RING:
+	case MT_REDTEAMRING:
+	case MT_BLUETEAMRING:
+		P_KillRingsInLava(mobj);
+		if (P_MobjWasRemoved(mobj))
+			return false;
+		/* FALLTHRU */
+	case MT_COIN:
+	case MT_BLUESPHERE:
+	case MT_BOMBSPHERE:
+	case MT_NIGHTSCHIP:
+	case MT_NIGHTSSTAR:
+		// No need to check water. Who cares?
+		P_RingThinker(mobj);
+		if (mobj->flags2 & MF2_NIGHTSPULL)
+			P_NightsItemChase(mobj);
+		else
+			A_AttractChase(mobj);
+		return false;
+		// Flung items
+	case MT_FLINGRING:
+		P_KillRingsInLava(mobj);
+		if (P_MobjWasRemoved(mobj))
+			return false;
+		/* FALLTHRU */
+	case MT_FLINGCOIN:
+	case MT_FLINGBLUESPHERE:
+	case MT_FLINGNIGHTSCHIP:
+		if (mobj->flags2 & MF2_NIGHTSPULL)
+			P_NightsItemChase(mobj);
+		else
+			A_AttractChase(mobj);
+		break;
+	case MT_EMBLEM:
+		if (mobj->flags2 & MF2_NIGHTSPULL)
+			P_NightsItemChase(mobj);
+		break;
+	case MT_SHELL:
+		if (mobj->threshold && mobj->threshold != TICRATE)
+			mobj->threshold--;
 
-					z = mobj->subsector->sector->floorheight + FixedMul(64*FRACUNIT, mobj->scale);
-					for (j = 0; j < 2; j++)
-					{
-						for (i = 0; i < 32; i++)
-						{
-							const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
-							ns = FixedMul(64 * FRACUNIT, mobj->scale);
-							x = mobj->x + FixedMul(FINESINE(fa),ns);
-							y = mobj->y + FixedMul(FINECOSINE(fa),ns);
-
-							mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
-							ns = FixedMul(16 * FRACUNIT, mobj->scale);
-							mo2->momx = FixedMul(FINESINE(fa),ns);
-							mo2->momy = FixedMul(FINECOSINE(fa),ns);
-						}
-						z -= FixedMul(32*FRACUNIT, mobj->scale);
-					}
-					P_SetMobjState(mobj, mobj->info->deathstate);
-					mobj->health = 0;
-					mobj->flags2 &= ~MF2_FIRING;
-				}
-				break;
-			case MT_BLUEFLAG:
-			case MT_REDFLAG:
-				{
-					sector_t *sec2;
-					sec2 = P_ThingOnSpecial3DFloor(mobj);
-					if ((sec2 && GETSECSPECIAL(sec2->special, 4) == 2) || (GETSECSPECIAL(mobj->subsector->sector->special, 4) == 2))
-						mobj->fuse = 1; // Return to base.
-					break;
-				}
-			case MT_CANNONBALL:
+		if (mobj->threshold >= TICRATE)
+		{
+			mobj->angle += ((mobj->movedir == 1) ? ANGLE_22h : ANGLE_337h);
+			P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), (mobj->info->speed*mobj->scale));
+		}
+		break;
+	case MT_TURRET:
+		if (!P_TurretThink(mobj))
+			return false;
+		break;
+	case MT_BLUEFLAG:
+	case MT_REDFLAG:
+	{
+		sector_t* sec2;
+		sec2 = P_ThingOnSpecial3DFloor(mobj);
+		if ((sec2 && GETSECSPECIAL(sec2->special, 4) == 2) || (GETSECSPECIAL(mobj->subsector->sector->special, 4) == 2))
+			mobj->fuse = 1; // Return to base.
+		break;
+	}
+	case MT_CANNONBALL:
 #ifdef FLOORSPLATS
-				R_AddFloorSplat(mobj->tracer->subsector, mobj->tracer, "TARGET", mobj->tracer->x,
-					mobj->tracer->y, mobj->tracer->floorz, SPLATDRAWMODE_SHADE);
+		R_AddFloorSplat(mobj->tracer->subsector, mobj->tracer, "TARGET", mobj->tracer->x,
+			mobj->tracer->y, mobj->tracer->floorz, SPLATDRAWMODE_SHADE);
 #endif
-				break;
-			case MT_SPINDUST: // Spindash dust
-					mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
-					mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
-					//mobj->momz = mobj->momz+P_MobjFlip(mobj)/3; // no meaningful change in value to be frank
-					if (mobj->state >= &states[S_SPINDUST_BUBBLE1] && mobj->state <= &states[S_SPINDUST_BUBBLE4]) // bubble dust!
-					{
-						P_MobjCheckWater(mobj);
-						if (mobj->watertop != mobj->subsector->sector->floorheight - 1000*FRACUNIT
-							&& mobj->z+mobj->height >= mobj->watertop - 5*FRACUNIT)
-							mobj->flags2 |= MF2_DONTDRAW;
-					}
-				break;
-			case MT_TRAINDUSTSPAWNER:
-				if (leveltime % 5 == 0) {
-					mobj_t *traindust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PARTICLE);
-					traindust->flags = MF_SCENERY;
-					P_SetMobjState(traindust, S_TRAINDUST);
-					traindust->frame = P_RandomRange(0, 8) | FF_TRANS90;
-					traindust->angle = mobj->angle;
-					traindust->tics = TICRATE * 4;
-					traindust->destscale = FRACUNIT * 64;
-					traindust->scalespeed = FRACUNIT / 24;
-					P_SetScale(traindust, FRACUNIT * 6);
-				}
-				break;
-			case MT_TRAINSTEAMSPAWNER:
-				if (leveltime % 5 == 0) {
-					mobj_t *steam = P_SpawnMobj(mobj->x + FRACUNIT*P_SignedRandom() / 2, mobj->y + FRACUNIT*P_SignedRandom() / 2, mobj->z, MT_PARTICLE);
-					P_SetMobjState(steam, S_TRAINSTEAM);
-					steam->frame = P_RandomRange(0, 1) | FF_TRANS90;
-					steam->tics = TICRATE * 8;
-					steam->destscale = FRACUNIT * 64;
-					steam->scalespeed = FRACUNIT / 8;
-					P_SetScale(steam, FRACUNIT * 16);
-					steam->momx = P_SignedRandom() * 32;
-					steam->momy = -64 * FRACUNIT;
-					steam->momz = 2 * FRACUNIT;
-				}
-				break;
-			case MT_CANARIVORE_GAS:
-				{
-					fixed_t momz;
-
-					if (mobj->flags2 & MF2_AMBUSH)
-					{
-						mobj->momx = FixedMul(mobj->momx, 50 * FRACUNIT / 51);
-						mobj->momy = FixedMul(mobj->momy, 50 * FRACUNIT / 51);
-						break;
-					}
-
-					if (mobj->eflags & MFE_VERTICALFLIP)
-					{
-						if ((mobj->z + mobj->height + mobj->momz) <= mobj->ceilingz)
-							break;
-					}
-					else
-					{
-						if ((mobj->z + mobj->momz) >= mobj->floorz)
-							break;
-					}
+		break;
+	case MT_SPINDUST: // Spindash dust
+		mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
+		mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
+		//mobj->momz = mobj->momz+P_MobjFlip(mobj)/3; // no meaningful change in value to be frank
+		if (mobj->state >= &states[S_SPINDUST_BUBBLE1] && mobj->state <= &states[S_SPINDUST_BUBBLE4]) // bubble dust!
+		{
+			P_MobjCheckWater(mobj);
+			if (mobj->watertop != mobj->subsector->sector->floorheight - 1000*FRACUNIT
+				&& mobj->z + mobj->height >= mobj->watertop - 5*FRACUNIT)
+				mobj->flags2 |= MF2_DONTDRAW;
+		}
+		break;
+	case MT_TRAINDUSTSPAWNER:
+		if (leveltime % 5 == 0) {
+			mobj_t* traindust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PARTICLE);
+			traindust->flags = MF_SCENERY;
+			P_SetMobjState(traindust, S_TRAINDUST);
+			traindust->frame = P_RandomRange(0, 8)|FF_TRANS90;
+			traindust->angle = mobj->angle;
+			traindust->tics = TICRATE*4;
+			traindust->destscale = FRACUNIT*64;
+			traindust->scalespeed = FRACUNIT/24;
+			P_SetScale(traindust, FRACUNIT*6);
+		}
+		break;
+	case MT_TRAINSTEAMSPAWNER:
+		if (leveltime % 5 == 0) {
+			mobj_t *steam = P_SpawnMobj(mobj->x + FRACUNIT*P_SignedRandom()/2, mobj->y + FRACUNIT*P_SignedRandom()/2, mobj->z, MT_PARTICLE);
+			P_SetMobjState(steam, S_TRAINSTEAM);
+			steam->frame = P_RandomRange(0, 1)|FF_TRANS90;
+			steam->tics = TICRATE*8;
+			steam->destscale = FRACUNIT*64;
+			steam->scalespeed = FRACUNIT/8;
+			P_SetScale(steam, FRACUNIT*16);
+			steam->momx = P_SignedRandom()*32;
+			steam->momy = -64*FRACUNIT;
+			steam->momz = 2*FRACUNIT;
+		}
+		break;
+	case MT_CANARIVORE_GAS:
+	{
+		fixed_t momz;
 
-					momz = abs(mobj->momz);
-					if (R_PointToDist2(0, 0, mobj->momx, mobj->momy) < momz)
-						P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), momz);
-					mobj->flags2 |= MF2_AMBUSH;
-					break;
-				}
-			case MT_SALOONDOOR:
-				{
-					fixed_t x = mobj->tracer->x;
-					fixed_t y = mobj->tracer->y;
-					fixed_t z = mobj->tracer->z;
-					angle_t oang = FixedAngle(mobj->extravalue1);
-					angle_t fa = (oang >> ANGLETOFINESHIFT) & FINEMASK;
-					fixed_t c0 = -96*FINECOSINE(fa);
-					fixed_t s0 = -96*FINESINE(fa);
-					angle_t fma;
-					fixed_t c, s;
-					angle_t angdiff;
-
-					// Adjust angular speed
-					fixed_t da = AngleFixed(mobj->angle - oang);
-					if (da > 180*FRACUNIT)
-						da -= 360*FRACUNIT;
-					mobj->extravalue2 = 8*(mobj->extravalue2 - da/32)/9;
-
-					// Update angle
-					mobj->angle += FixedAngle(mobj->extravalue2);
-
-					angdiff = mobj->angle - FixedAngle(mobj->extravalue1);
-					if (angdiff > (ANGLE_90 - ANG2) && angdiff < ANGLE_180)
-					{
-						mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_90 - ANG2);
-						mobj->extravalue2 /= 2;
-					}
-					else if (angdiff < (ANGLE_270 + ANG2) && angdiff >= ANGLE_180)
-					{
-						mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_270 + ANG2);
-						mobj->extravalue2 /= 2;
-					}
+		if (mobj->flags2 & MF2_AMBUSH)
+		{
+			mobj->momx = FixedMul(mobj->momx, 50*FRACUNIT/51);
+			mobj->momy = FixedMul(mobj->momy, 50*FRACUNIT/51);
+			break;
+		}
 
-					// Update position
-					fma = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
-					c = 48*FINECOSINE(fma);
-					s = 48*FINESINE(fma);
-					P_TeleportMove(mobj, x + c0 + c, y + s0 + s, z);
-					break;
-				}
-			case MT_MINECARTSPAWNER:
-				P_HandleMinecartSegments(mobj);
-				if (!mobj->fuse || mobj->fuse > TICRATE)
-					break;
-				if (mobj->fuse == 2)
-				{
-					mobj->fuse = 0;
-					break;
-				}
-				mobj->flags2 ^= MF2_DONTDRAW;
+		if (mobj->eflags & MFE_VERTICALFLIP)
+		{
+			if ((mobj->z + mobj->height + mobj->momz) <= mobj->ceilingz)
 				break;
-			case MT_LAVAFALLROCK:
-				if (P_IsObjectOnGround(mobj))
-					P_RemoveMobj(mobj);
+		}
+		else
+		{
+			if ((mobj->z + mobj->momz) >= mobj->floorz)
 				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);
+		momz = abs(mobj->momz);
+		if (R_PointToDist2(0, 0, mobj->momx, mobj->momy) < momz)
+			P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), momz);
+		mobj->flags2 |= MF2_AMBUSH;
+		break;
+	}
+	case MT_SALOONDOOR:
+		P_SaloonDoorThink(mobj);
+		break;
+	case MT_MINECARTSPAWNER:
+		P_HandleMinecartSegments(mobj);
+		if (!mobj->fuse || mobj->fuse > TICRATE)
+			break;
+		if (mobj->fuse == 2)
+		{
+			mobj->fuse = 0;
+			break;
+		}
+		mobj->flags2 ^= MF2_DONTDRAW;
+		break;
+	case MT_LAVAFALLROCK:
+		if (P_IsObjectOnGround(mobj))
+			P_RemoveMobj(mobj);
+		break;
+	case MT_PYREFLY:
+		P_PyreFlyThink(mobj);
+		break;
+	case MT_PTERABYTE:
+		P_PterabyteThink(mobj);
+		break;
+	case MT_DRAGONBOMBER:
+		P_DragonbomberThink(mobj);
+		break;
+	case MT_MINUS:
+		if (P_IsObjectOnGround(mobj))
+			mobj->rollangle = 0;
+		else
+			mobj->rollangle = R_PointToAngle2(0, 0, mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
+		break;
+	case MT_SPINFIRE:
+		if (mobj->flags & MF_NOGRAVITY)
+		{
+			if (mobj->eflags & MFE_VERTICALFLIP)
+				mobj->z = mobj->ceilingz - mobj->height;
+			else
+				mobj->z = mobj->floorz;
+		}
+		else if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= mobj->floorz)
+			|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz))
+		{
+			mobj->flags |= MF_NOGRAVITY;
+			mobj->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
+			mobj->momy = mobj->momz = 0;
+			mobj->z = ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->ceilingz - mobj->height : mobj->floorz);
+		}
+		/* FALLTHRU */
+	default:
+		// check mobj against possible water content, before movement code
+		P_MobjCheckWater(mobj);
 
-					if (!mobj->target)
-						break;
+		// Extinguish fire objects in water
+		if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
+			&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
+		{
+			P_KillMobj(mobj, NULL, NULL, 0);
+			return false;
+		}
+		break;
+	}
+	return true;
+}
 
-					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);
-					}
+static void P_FiringThink(mobj_t *mobj)
+{
+	if (!mobj->target)
+		return;
 
-					hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+	if (mobj->health <= 0)
+		return;
 
-					if (hdist > 1500*FRACUNIT)
-					{
-						mobj->flags2 &= ~MF2_BOSSNOTRAP;
-						P_SetTarget(&mobj->target, NULL);
-						break;
-					}
+	if (mobj->state->action.acp1 == (actionf_p1)A_Boss1Laser)
+	{
+		if (mobj->state->tics > 1)
+		{
+			var1 = mobj->state->var1;
+			var2 = mobj->state->var2 & 65535;
+			mobj->state->action.acp1(mobj);
+		}
+	}
+	else if (leveltime & 1) // Fire mode
+	{
+		mobj_t *missile;
 
-					if (!(mobj->flags2 & MF2_BOSSNOTRAP) && hdist <= 450*FRACUNIT)
-						mobj->flags2 |= MF2_BOSSNOTRAP;
+		if (mobj->target->player && mobj->target->player->powers[pw_carry] == CR_NIGHTSMODE)
+		{
+			fixed_t oldval = mobjinfo[mobj->extravalue1].speed;
 
-					if (!(mobj->flags2 & MF2_BOSSNOTRAP))
-						break;
+			mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx, mobj->target->y + mobj->target->momy);
+			mobjinfo[mobj->extravalue1].speed = FixedMul(60*FRACUNIT, mobj->scale);
+			missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
+			mobjinfo[mobj->extravalue1].speed = oldval;
+		}
+		else
+		{
+			mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+			missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
+		}
 
-					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;
-					}
-					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 (missile)
+		{
+			if (mobj->flags2 & MF2_SUPERFIRE)
+				missile->flags2 |= MF2_SUPERFIRE;
 
-					if ((mobj->extravalue1 & 3) == 0) // Hovering
-					{
-						fixed_t vdist, hdist, time;
-						fixed_t hspeed = 3*mobj->info->speed;
-						angle_t fa;
+			if (mobj->info->attacksound)
+				S_StartSound(missile, mobj->info->attacksound);
+		}
+	}
+	else
+		mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
+}
 
-						var1 = 1;
-						var2 = 0;
-						A_CapeChase(mobj);
+static void P_MonitorFuseThink(mobj_t *mobj)
+{
+	mobj_t *newmobj;
 
-						if (mobj->target)
-							break; // Still carrying a player or in cooldown
+	// Special case for ALL monitors.
+	// If a box's speed is nonzero, it's allowed to respawn as a WRM/SRM.
+	if (mobj->info->speed != 0 && (mobj->flags2 & (MF2_AMBUSH|MF2_STRONGBOX)))
+	{
+		mobjtype_t spawnchance[64];
+		INT32 numchoices = 0, i = 0;
 
-						P_LookForPlayers(mobj, true, false, 256*FRACUNIT);
+		// This define should make it a lot easier to organize and change monitor weights
+#define SETMONITORCHANCES(type, strongboxamt, weakboxamt) \
+for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) spawnchance[numchoices++] = type
 
-						if (!mobj->target)
-							break;
+					//                Type             SRM WRM
+		SETMONITORCHANCES(MT_SNEAKERS_BOX, 0, 10); // Super Sneakers
+		SETMONITORCHANCES(MT_INVULN_BOX, 2, 0); // Invincibility
+		SETMONITORCHANCES(MT_WHIRLWIND_BOX, 3, 8); // Whirlwind Shield
+		SETMONITORCHANCES(MT_ELEMENTAL_BOX, 3, 8); // Elemental Shield
+		SETMONITORCHANCES(MT_ATTRACT_BOX, 2, 0); // Attraction Shield
+		SETMONITORCHANCES(MT_FORCE_BOX, 3, 3); // Force Shield
+		SETMONITORCHANCES(MT_ARMAGEDDON_BOX, 2, 0); // Armageddon Shield
+		SETMONITORCHANCES(MT_MIXUP_BOX, 0, 1); // Teleporters
+		SETMONITORCHANCES(MT_RECYCLER_BOX, 0, 1); // Recycler
+		SETMONITORCHANCES(MT_1UP_BOX, 1, 1); // 1-Up
+		// =======================================
+		//                Total             16  32
 
-						if (mobj->target->player->powers[pw_flashing])
-						{
-							P_SetTarget(&mobj->target, NULL);
-							break;
-						}
+#undef SETMONITORCHANCES
 
-						vdist = mobj->z - mobj->target->z - mobj->target->height;
-						if (P_MobjFlip(mobj)*vdist <= 0)
-						{
-							P_SetTarget(&mobj->target, NULL);
-							break;
-						}
+		i = P_RandomKey(numchoices); // Gotta love those random numbers!
+		newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, spawnchance[i]);
+	}
+	else
+		newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
 
-						hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
-						if (hdist > 450*FRACUNIT)
-						{
-							P_SetTarget(&mobj->target, NULL);
-							break;
-						}
+	// Transfer flags2 (ambush, strongbox, objectflip)
+	newmobj->flags2 = mobj->flags2;
+	P_RemoveMobj(mobj); // make sure they disappear
+}
 
-						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;
+static void P_FlagFuseThink(mobj_t *mobj)
+{
+	subsector_t *ss;
+	fixed_t x, y, z;
+	mobj_t* flagmo;
 
-						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_DRAGONBOMBER:
-				{
-#define DRAGONTURNSPEED ANG2
-					mobj->movecount = (mobj->movecount + 9) % 360;
-					P_SetObjectMomZ(mobj, 4*FINESINE(((mobj->movecount*ANG1) >> ANGLETOFINESHIFT) & FINEMASK), false);
-					if (mobj->threshold > 0) // are we dropping mines?
-					{
-						mobj->threshold--;
-						if (mobj->threshold == 0) // if the timer hits 0, look for a mine to drop!
-						{
-							mobj_t *segment = mobj;
-							while (segment->tracer != NULL && !P_MobjWasRemoved(segment->tracer) && segment->tracer->state == &states[segment->tracer->info->spawnstate])
-							{
-								segment = segment->tracer;
-							}
-							if (segment != mobj) // found an unactivated segment?
-							{
-								mobj_t *mine = P_SpawnMobjFromMobj(segment, 0, 0, 0, segment->info->painchance);
-								mine->angle = segment->angle;
-								P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1);
-								P_SetObjectMomZ(mine, -2*FRACUNIT, true);
-								S_StartSound(mine, mine->info->seesound);
-								P_SetMobjState(segment, segment->info->raisestate);
-								mobj->threshold = mobj->info->painchance;
-							}
-						}
-					}
-					if (mobj->target != NULL) // Are we chasing a player?
-					{
-						fixed_t dist = P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y);
-						if (dist > 2000 * mobj->scale) // Not anymore!
-							P_SetTarget(&mobj->target, NULL);
-						else
-						{
-							fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
-							fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->flags & MFE_VERTICALFLIP ? -128*mobj->scale : 128*mobj->scale + mobj->target->height);
-							angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
-							if (diff > ANGLE_180)
-								mobj->angle -= DRAGONTURNSPEED;
-							else
-								mobj->angle += DRAGONTURNSPEED;
-							if (!mobj->threshold && dist < 512 * mobj->scale) // Close enough to drop bombs
-							{
-								mobj->threshold = mobj->info->painchance;
-							}
-							mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
-						}
-					}
-					else // Can we find a player to chase?
-					{
-						if (mobj->tracer == NULL || mobj->tracer->state != &states[mobj->tracer->info->spawnstate]
-							|| !P_LookForPlayers(mobj, true, false, 2000*mobj->scale)) // if not, circle around the spawnpoint
-						{
-							if (!mobj->spawnpoint) // unless we don't have one, in which case uhhh just circle around wherever we currently are I guess??
-								mobj->angle += DRAGONTURNSPEED;
-							else
-							{
-								fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
-								fixed_t x = mobj->spawnpoint->x << FRACBITS;
-								fixed_t y = mobj->spawnpoint->y << FRACBITS;
-								fixed_t z = mobj->spawnpoint->z << FRACBITS;
-								angle_t diff = R_PointToAngle2(mobj->x, mobj->y, x, y) - mobj->angle;
-								if (diff > ANGLE_180)
-									mobj->angle -= DRAGONTURNSPEED;
-								else
-									mobj->angle += DRAGONTURNSPEED;
-								mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
-							}
-						}
-					}
-					P_InstaThrust(mobj, mobj->angle, FixedMul(mobj->info->speed, mobj->scale));
-#undef DRAGONTURNSPEED
-				}
-				break;
-			case MT_MINUS:
-#ifdef ROTSPRITE
-				{
-					if (P_IsObjectOnGround(mobj))
-						mobj->rollangle = 0;
-					else
-						mobj->rollangle = R_PointToAngle2(0, 0, mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
-				}
-#endif
-				break;
-			case MT_SPINFIRE:
-				if (mobj->flags & MF_NOGRAVITY)
-				{
-					if (mobj->eflags & MFE_VERTICALFLIP)
-						mobj->z = mobj->ceilingz - mobj->height;
-					else
-						mobj->z = mobj->floorz;
-				}
-				else if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= mobj->floorz)
-				|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z+mobj->height >= mobj->ceilingz))
-				{
-					mobj->flags |= MF_NOGRAVITY;
-					mobj->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
-					mobj->momy = mobj->momz = 0;
-					mobj->z = ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->ceilingz-mobj->height : mobj->floorz);
-				}
-				/* FALLTHRU */
-			default:
-				// check mobj against possible water content, before movement code
-				P_MobjCheckWater(mobj);
+	if (!mobj->spawnpoint)
+		return;
 
-				// Extinguish fire objects in water
-				if (mobj->flags & MF_FIRE && mobj->type != MT_PUMA && mobj->type != MT_FIREBALL
-					&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
-				{
-					P_KillMobj(mobj, NULL, NULL, 0);
-					return;
-				}
-				break;
-		}
+	x = mobj->spawnpoint->x << FRACBITS;
+	y = mobj->spawnpoint->y << FRACBITS;
+	ss = R_PointInSubsector(x, y);
+	if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
+	{
+		z = ss->sector->ceilingheight - mobjinfo[mobj->type].height;
+		if (mobj->spawnpoint->options >> ZSHIFT)
+			z -= (mobj->spawnpoint->options >> ZSHIFT) << FRACBITS;
 	}
-	if (P_MobjWasRemoved(mobj))
-		return;
+	else
+	{
+		z = ss->sector->floorheight;
+		if (mobj->spawnpoint->options >> ZSHIFT)
+			z += (mobj->spawnpoint->options >> ZSHIFT) << FRACBITS;
+	}
+	flagmo = P_SpawnMobj(x, y, z, mobj->type);
+	flagmo->spawnpoint = mobj->spawnpoint;
+	if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
+	{
+		flagmo->eflags |= MFE_VERTICALFLIP;
+		flagmo->flags2 |= MF2_OBJECTFLIP;
+	}
+
+	if (mobj->type == MT_REDFLAG)
+	{
+		if (!(mobj->flags2 & MF2_JUSTATTACKED))
+			CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x85, M_GetText("Red flag"), 0x80);
+
+		// Assumedly in splitscreen players will be on opposing teams
+		if (players[consoleplayer].ctfteam == 1 || splitscreen)
+			S_StartSound(NULL, sfx_hoop1);
+		else if (players[consoleplayer].ctfteam == 2)
+			S_StartSound(NULL, sfx_hoop3);
 
-	if (mobj->flags2 & MF2_FIRING && mobj->target && mobj->health > 0)
+		redflag = flagmo;
+	}
+	else // MT_BLUEFLAG
 	{
-		if (mobj->state->action.acp1 == (actionf_p1)A_Boss1Laser)
+		if (!(mobj->flags2 & MF2_JUSTATTACKED))
+			CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x84, M_GetText("Blue flag"), 0x80);
+
+		// Assumedly in splitscreen players will be on opposing teams
+		if (players[consoleplayer].ctfteam == 2 || splitscreen)
+			S_StartSound(NULL, sfx_hoop1);
+		else if (players[consoleplayer].ctfteam == 1)
+			S_StartSound(NULL, sfx_hoop3);
+
+		blueflag = flagmo;
+	}
+}
+
+static boolean P_FuseThink(mobj_t *mobj)
+{
+	if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG)
+		mobj->flags2 ^= MF2_DONTDRAW;
+
+	mobj->fuse--;
+
+	if (mobj->fuse)
+		return true;
+
+#ifdef HAVE_BLUA
+	if (LUAh_MobjFuse(mobj) || P_MobjWasRemoved(mobj))
+		;
+	else
+#endif
+		if (mobj->info->flags & MF_MONITOR)
 		{
-			if (mobj->state->tics > 1)
-			{
-				var1 = mobj->state->var1;
-				var2 = mobj->state->var2 & 65535;
-				mobj->state->action.acp1(mobj);
-			}
+			P_MonitorFuseThink(mobj);
+			return false;
 		}
-		else if (leveltime & 1) // Fire mode
+		else switch (mobj->type)
 		{
-			mobj_t *missile;
-
-			if (mobj->target->player && mobj->target->player->powers[pw_carry] == CR_NIGHTSMODE)
+			// gargoyle and snowman handled in P_PushableThinker, not here
+		case MT_THROWNGRENADE:
+		case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
+			P_SetMobjState(mobj, mobj->info->deathstate);
+			break;
+		case MT_LHRT:
+			P_KillMobj(mobj, NULL, NULL, 0);
+			break;
+		case MT_BLUEFLAG:
+		case MT_REDFLAG:
+			P_FlagFuseThink(mobj);
+			P_RemoveMobj(mobj);
+			return false;
+		case MT_FANG:
+			if (mobj->flags2 & MF2_SLIDEPUSH)
 			{
-				fixed_t oldval = mobjinfo[mobj->extravalue1].speed;
-
-				mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x+mobj->target->momx, mobj->target->y+mobj->target->momy);
-				mobjinfo[mobj->extravalue1].speed = FixedMul(60*FRACUNIT, mobj->scale);
-				missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
-				mobjinfo[mobj->extravalue1].speed = oldval;
+				var1 = 0;
+				var2 = 0;
+				A_BossDeath(mobj);
+				return false;
+			}
+			P_SetMobjState(mobj, mobj->state->nextstate);
+			if (P_MobjWasRemoved(mobj))
+				return false;
+			break;
+		case MT_METALSONIC_BATTLE:
+			break; // don't remove
+		case MT_SPIKE:
+			P_SetMobjState(mobj, mobj->state->nextstate);
+			mobj->fuse = mobj->info->speed;
+			if (mobj->spawnpoint)
+				mobj->fuse += mobj->spawnpoint->angle;
+			break;
+		case MT_WALLSPIKE:
+			P_SetMobjState(mobj, mobj->state->nextstate);
+			mobj->fuse = mobj->info->speed;
+			if (mobj->spawnpoint)
+				mobj->fuse += (mobj->spawnpoint->angle / 360);
+			break;
+		case MT_NIGHTSCORE:
+			P_RemoveMobj(mobj);
+			return false;
+		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->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
-				missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
+				mobj->fuse = 30;
+				P_SetMobjState(mobj, S_LAVAFALL_DORMANT);
+				S_StopSound(mobj);
 			}
+			return false;
+		case MT_PYREFLY:
+			if (mobj->health <= 0)
+				break;
 
-			if (missile)
+			mobj->extravalue2 = (mobj->extravalue2 + 1) % 3;
+			if (mobj->extravalue2 == 0)
 			{
-				if (mobj->flags2 & MF2_SUPERFIRE)
-					missile->flags2 |= MF2_SUPERFIRE;
-
-				if (mobj->info->attacksound)
-					S_StartSound(missile, mobj->info->attacksound);
+				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 false;
+		case MT_PLAYER:
+			break; // don't remove
+		default:
+			P_SetMobjState(mobj, mobj->info->xdeathstate); // will remove the mobj if S_NULL.
+			break;
+			// Looking for monitors? They moved to a special condition above.
 		}
-		else
-			mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
-	}
 
-	if (mobj->flags & MF_AMBIENT)
-	{
-		if (!(leveltime % mobj->health) && mobj->info->seesound)
-			S_StartSound(mobj, mobj->info->seesound);
-		return;
-	}
-
-	// Check fuse
-	if (mobj->fuse)
-	{
+	return !P_MobjWasRemoved(mobj);
+}
 
-		if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG)
-			mobj->flags2 ^= MF2_DONTDRAW;
+//
+// P_MobjThinker
+//
+void P_MobjThinker(mobj_t *mobj)
+{
+	I_Assert(mobj != NULL);
+	I_Assert(!P_MobjWasRemoved(mobj));
 
-		mobj->fuse--;
-		if (!mobj->fuse)
-		{
-			subsector_t *ss;
-			fixed_t x, y, z;
-			mobj_t *flagmo, *newmobj;
+	if (mobj->flags & MF_NOTHINK)
+		return;
 
-#ifdef HAVE_BLUA
-			if (LUAh_MobjFuse(mobj) || P_MobjWasRemoved(mobj))
-				;
-			else
-#endif
-			if (mobj->info->flags & MF_MONITOR)
-			{
-				// Special case for ALL monitors.
-				// If a box's speed is nonzero, it's allowed to respawn as a WRM/SRM.
-				if (mobj->info->speed != 0 && (mobj->flags2 & (MF2_AMBUSH|MF2_STRONGBOX)))
-				{
-					mobjtype_t spawnchance[64];
-					INT32 numchoices = 0, i = 0;
+	if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->extrainfo)))
+		return;
 
-// This define should make it a lot easier to organize and change monitor weights
-#define SETMONITORCHANCES(type, strongboxamt, weakboxamt) \
-for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) spawnchance[numchoices++] = type
+	// Remove dead target/tracer.
+	if (mobj->target && P_MobjWasRemoved(mobj->target))
+		P_SetTarget(&mobj->target, NULL);
+	if (mobj->tracer && P_MobjWasRemoved(mobj->tracer))
+		P_SetTarget(&mobj->tracer, NULL);
+	if (mobj->hnext && P_MobjWasRemoved(mobj->hnext))
+		P_SetTarget(&mobj->hnext, NULL);
+	if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
+		P_SetTarget(&mobj->hprev, NULL);
 
-					//                Type             SRM WRM
-					SETMONITORCHANCES(MT_SNEAKERS_BOX,   0, 10); // Super Sneakers
-					SETMONITORCHANCES(MT_INVULN_BOX,     2,  0); // Invincibility
-					SETMONITORCHANCES(MT_WHIRLWIND_BOX,  3,  8); // Whirlwind Shield
-					SETMONITORCHANCES(MT_ELEMENTAL_BOX,  3,  8); // Elemental Shield
-					SETMONITORCHANCES(MT_ATTRACT_BOX,    2,  0); // Attraction Shield
-					SETMONITORCHANCES(MT_FORCE_BOX,      3,  3); // Force Shield
-					SETMONITORCHANCES(MT_ARMAGEDDON_BOX, 2,  0); // Armageddon Shield
-					SETMONITORCHANCES(MT_MIXUP_BOX,      0,  1); // Teleporters
-					SETMONITORCHANCES(MT_RECYCLER_BOX,   0,  1); // Recycler
-					SETMONITORCHANCES(MT_1UP_BOX,        1,  1); // 1-Up
-					// =======================================
-					//                Total             16  32
+	mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG);
 
-#undef SETMONITORCHANCES
+	tmfloorthing = tmhitthing = NULL;
 
-					i = P_RandomKey(numchoices); // Gotta love those random numbers!
-					newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, spawnchance[i]);
-				}
-				else
-					newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
+	// Sector special (2,8) allows ANY mobj to trigger a linedef exec
+	if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
+	{
+		sector_t *sec2;
 
-				// Transfer flags2 (ambush, strongbox, objectflip)
-				newmobj->flags2 = mobj->flags2;
-				P_RemoveMobj(mobj); // make sure they disappear
-				return;
-			}
-			else switch (mobj->type)
-			{
-				// gargoyle and snowman handled in P_PushableThinker, not here
-				case MT_THROWNGRENADE:
-				case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
-					P_SetMobjState(mobj, mobj->info->deathstate);
-					break;
-				case MT_LHRT:
-					P_KillMobj(mobj, NULL, NULL, 0);
-					break;
-				case MT_BLUEFLAG:
-				case MT_REDFLAG:
-					if (mobj->spawnpoint)
-					{
-						x = mobj->spawnpoint->x << FRACBITS;
-						y = mobj->spawnpoint->y << FRACBITS;
-						ss = R_PointInSubsector(x, y);
-						if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
-						{
-							z = ss->sector->ceilingheight - mobjinfo[mobj->type].height;
-							if (mobj->spawnpoint->options >> ZSHIFT)
-								z -= (mobj->spawnpoint->options >> ZSHIFT) << FRACBITS;
-						}
-						else
-						{
-							z = ss->sector->floorheight;
-							if (mobj->spawnpoint->options >> ZSHIFT)
-								z += (mobj->spawnpoint->options >> ZSHIFT) << FRACBITS;
-						}
-						flagmo = P_SpawnMobj(x, y, z, mobj->type);
-						flagmo->spawnpoint = mobj->spawnpoint;
-						if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
-						{
-							flagmo->eflags |= MFE_VERTICALFLIP;
-							flagmo->flags2 |= MF2_OBJECTFLIP;
-						}
+		sec2 = P_ThingOnSpecial3DFloor(mobj);
+		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
+			P_LinedefExecute(sec2->tag, mobj, sec2);
+	}
 
-						if (mobj->type == MT_REDFLAG)
-						{
-							if (!(mobj->flags2 & MF2_JUSTATTACKED))
-								CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x85, M_GetText("Red flag"), 0x80);
+	if (mobj->scale != mobj->destscale)
+		P_MobjScaleThink(mobj); // Slowly scale up/down to reach your destscale.
 
-							// Assumedly in splitscreen players will be on opposing teams
-							if (players[consoleplayer].ctfteam == 1 || splitscreen)
-								S_StartSound(NULL, sfx_hoop1);
-							else if (players[consoleplayer].ctfteam == 2)
-								S_StartSound(NULL, sfx_hoop3);
+	if ((mobj->type == MT_GHOST || mobj->type == MT_THOK) && mobj->fuse > 0) // Not guaranteed to be MF_SCENERY or not MF_SCENERY!
+	{
+		if (mobj->flags2 & MF2_BOSSNOTRAP) // "fast" flag
+		{
+			if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - (2*mobj->fuse)/3)
+				// fade out when nearing the end of fuse...
+				mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - (2*mobj->fuse)/3) << FF_TRANSSHIFT);
+		}
+		else
+		{
+			if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - mobj->fuse / 2)
+				// fade out when nearing the end of fuse...
+				mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - mobj->fuse / 2) << FF_TRANSSHIFT);
+		}
+	}
 
-							redflag = flagmo;
-						}
-						else // MT_BLUEFLAG
-						{
-							if (!(mobj->flags2 & MF2_JUSTATTACKED))
-								CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x84, M_GetText("Blue flag"), 0x80);
+	// Special thinker for scenery objects
+	if (mobj->flags & MF_SCENERY)
+	{
+		P_MobjSceneryThink(mobj);
+		return;
+	}
 
-							// Assumedly in splitscreen players will be on opposing teams
-							if (players[consoleplayer].ctfteam == 2 || splitscreen)
-								S_StartSound(NULL, sfx_hoop1);
-							else if (players[consoleplayer].ctfteam == 1)
-								S_StartSound(NULL, sfx_hoop3);
+#ifdef HAVE_BLUA
+	// Check for a Lua thinker first
+	if (!mobj->player)
+	{
+		if (LUAh_MobjThinker(mobj) || P_MobjWasRemoved(mobj))
+			return;
+	}
+	else if (!mobj->player->spectator)
+	{
+		// You cannot short-circuit the player thinker like you can other thinkers.
+		LUAh_MobjThinker(mobj);
+		if (P_MobjWasRemoved(mobj))
+			return;
+	}
+#endif
+	// if it's pushable, or if it would be pushable other than temporary disablement, use the
+	// separate thinker
+	if (mobj->flags & MF_PUSHABLE || (mobj->info->flags & MF_PUSHABLE && mobj->fuse))
+	{
+		if (!P_MobjPushableThink(mobj))
+			return;
+	}
+	else if (mobj->flags & MF_BOSS)
+	{
+		if (!P_MobjBossThink(mobj))
+			return;
+	}
+	else if (mobj->health <= 0) // Dead things think differently than the living.
+	{
+		if (!P_MobjDeadThink(mobj))
+			return;
+	}
+	else
+	{
+		if (!P_MobjRegularThink(mobj))
+			return;
+	}
+	if (P_MobjWasRemoved(mobj))
+		return;
 
-							blueflag = flagmo;
-						}
-					}
-					P_RemoveMobj(mobj);
-					return;
-				case MT_FANG:
-					if (mobj->flags2 & MF2_SLIDEPUSH)
-					{
-						var1 = 0;
-						var2 = 0;
-						A_BossDeath(mobj);
-						return;
-					}
-					P_SetMobjState(mobj, mobj->state->nextstate);
-					if (P_MobjWasRemoved(mobj))
-						return;
-					break;
-				case MT_METALSONIC_BATTLE:
-					break; // don't remove
-				case MT_SPIKE:
-					P_SetMobjState(mobj, mobj->state->nextstate);
-					mobj->fuse = mobj->info->speed;
-					if (mobj->spawnpoint)
-						mobj->fuse += mobj->spawnpoint->angle;
-					break;
-				case MT_WALLSPIKE:
-					P_SetMobjState(mobj, mobj->state->nextstate);
-					mobj->fuse = mobj->info->speed;
-					if (mobj->spawnpoint)
-						mobj->fuse += (mobj->spawnpoint->angle/360);
-					break;
-				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;
+	if (mobj->flags2 & MF2_FIRING)
+		P_FiringThink(mobj);
 
-					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:
-					P_SetMobjState(mobj, mobj->info->xdeathstate); // will remove the mobj if S_NULL.
-					break;
-				// Looking for monitors? They moved to a special condition above.
-			}
-			if (P_MobjWasRemoved(mobj))
-				return;
-		}
+	if (mobj->flags & MF_AMBIENT)
+	{
+		if (!(leveltime % mobj->health) && mobj->info->seesound)
+			S_StartSound(mobj, mobj->info->seesound);
+		return;
 	}
 
+	// Check fuse
+	if (mobj->fuse && !P_FuseThink(mobj))
+		return;
+
 	I_Assert(mobj != NULL);
 	I_Assert(!P_MobjWasRemoved(mobj));
 
@@ -11188,7 +11338,7 @@ void P_RespawnSpecials(void)
 		}
 
 		//CTF rings should continue to respawn as normal rings outside of CTF.
-		if (gametype != GT_CTF)
+		if (!(gametyperules & GTR_TEAMFLAGS))
 		{
 			if (i == MT_REDTEAMRING || i == MT_BLUETEAMRING)
 				i = MT_RING;
@@ -11259,7 +11409,10 @@ void P_SpawnPlayer(INT32 playernum)
 	{
 		p->outofcoop = false;
 		if (netgame && p->jointime < 1)
-			p->spectator = true;
+		{
+			// Averted by GTR_NOSPECTATORSPAWN.
+			p->spectator = (gametyperules & GTR_NOSPECTATORSPAWN) ? false : true;
+		}
 		else if (multiplayer && !netgame)
 		{
 			// If you're in a team game and you don't have a team assigned yet...
@@ -11302,7 +11455,7 @@ void P_SpawnPlayer(INT32 playernum)
 			p->skincolor = skincolor_blueteam;
 	}
 
-	if ((netgame || multiplayer) && (gametype != GT_COOP || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS))
+	if ((netgame || multiplayer) && ((gametyperules & GTR_SPAWNINVUL) || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS))
 		p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
 
 	mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER);
@@ -11707,7 +11860,7 @@ static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
 	else if (mthing->type == mobjinfo[MT_EMERHUNT].doomednum)
 	{
 		// Emerald Hunt is Coop only. Don't spawn the emerald yet, but save the spawnpoint for later.
-		if (gametype == GT_COOP && numhuntemeralds < MAXHUNTEMERALDS)
+		if ((gametyperules & GTR_EMERALDHUNT) && numhuntemeralds < MAXHUNTEMERALDS)
 			huntemeralds[numhuntemeralds++] = mthing;
 		return true;
 	}
@@ -11740,7 +11893,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 		if (!cv_powerstones.value)
 			return false;
 
-		if (!(gametype == GT_MATCH || gametype == GT_CTF))
+		if (!(gametyperules & GTR_MATCHEMERALDS))
 			return false;
 
 		runemeraldmanager = true;
@@ -11754,7 +11907,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 
 		break;
 	case MT_TOKEN:
-		if (gametype != GT_COOP && gametype != GT_COMPETITION)
+		if (!(gametyperules & GTR_EMERALDTOKENS))
 			return false; // Gametype's not right
 
 		if (tokenbits == 30)
@@ -11784,20 +11937,20 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 			return false;
 	}
 
-	if (!G_PlatformGametype())
-	{
-		if ((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS))
-			return false; // No enemies in ringslinger modes
+	if (((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS)) && !(gametyperules & GTR_SPAWNENEMIES))
+		return false; // No enemies in ringslinger modes
 
-		if (i == MT_SIGN || i == MT_STARPOST)
-			return false; // Don't spawn exit signs or starposts in wrong game modes
-	}
+	if (!(gametyperules & GTR_ALLOWEXIT) && (i == MT_SIGN))
+		return false; // Don't spawn exit signs in wrong game modes
+
+	if (!G_PlatformGametype() && (i == MT_STARPOST))
+		return false; // Don't spawn starposts in wrong game modes
 
 	if (!G_RingSlingerGametype() || !cv_specialrings.value)
 		if (P_WeaponOrPanel(i))
 			return false; // Don't place weapons/panels in non-ringslinger modes
 
-	if (gametype != GT_CTF) // CTF specific things
+	if (!(gametyperules & GTR_TEAMFLAGS)) // CTF specific things
 	{
 		if (i == MT_BLUEFLAG || i == MT_REDFLAG)
 			return false; // No flags in non-CTF modes!
@@ -11845,7 +11998,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
 	// Yeah, this is a dirty hack.
 	if ((mobjinfo[i].flags & (MF_MONITOR|MF_GRENADEBOUNCE)) == MF_MONITOR)
 	{
-		if (gametype == GT_COMPETITION || gametype == GT_RACE)
+		if (gametyperules & GTR_RACE)
 		{
 			// Set powerup boxes to user settings for competition.
 			switch (cv_competitionboxes.value)
@@ -11889,7 +12042,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
 			return MT_NIGHTSCHIP;
 	}
 
-	if (gametype != GT_CTF)
+	if (!(gametyperules & GTR_TEAMS))
 	{
 		if (i == MT_BLUETEAMRING || i == MT_REDTEAMRING)
 			return MT_RING;
@@ -13372,7 +13525,7 @@ void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime)
 	{
 		INT32 numitems = (mthing->type & 1) ? 16 : 8;
 		fixed_t size = (mthing->type & 1) ? 192*FRACUNIT : 96*FRACUNIT;
-		mobjtype_t itemtypes[1] = { (mthing->type & 1) ? MT_RING : MT_BLUESPHERE };
+		mobjtype_t itemtypes[1] = { (mthing->type < 606) ? MT_RING : MT_BLUESPHERE };
 		P_SpawnItemCircle(mthing, itemtypes, 1, numitems, size, bonustime);
 		return;
 	}
diff --git a/src/p_mobj.h b/src/p_mobj.h
index a272003c1d40db48e658d372e66e29ef8a710b64..92160d9e2d70f4634d90d47feed67cfa5940b1a9 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -279,9 +279,7 @@ typedef struct mobj_s
 
 	// More drawing info: to determine current sprite.
 	angle_t angle;  // orientation
-#ifdef ROTSPRITE
 	angle_t rollangle;
-#endif
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
@@ -402,9 +400,7 @@ typedef struct precipmobj_s
 
 	// More drawing info: to determine current sprite.
 	angle_t angle;  // orientation
-#ifdef ROTSPRITE
 	angle_t rollangle;
-#endif
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
diff --git a/src/p_saveg.c b/src/p_saveg.c
index f96368d0e3af3f504b0b316423d0e201bc195546..85efacf8852195302cd1023ceff75733f4cc04bc 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1252,9 +1252,7 @@ typedef enum
 	MD2_SLOPE        = 1<<11,
 #endif
 	MD2_COLORIZED    = 1<<12,
-#ifdef ROTSPRITE
 	MD2_ROLLANGLE    = 1<<13,
-#endif
 } mobj_diff2_t;
 
 typedef enum
@@ -1474,10 +1472,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 #endif
 	if (mobj->colorized)
 		diff2 |= MD2_COLORIZED;
-#ifdef ROTSPRITE
 	if (mobj->rollangle)
 		diff2 |= MD2_ROLLANGLE;
-#endif
 	if (diff2 != 0)
 		diff |= MD_MORE;
 
@@ -1642,10 +1638,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 #endif
 	if (diff2 & MD2_COLORIZED)
 		WRITEUINT8(save_p, mobj->colorized);
-#ifdef ROTSPRITE
 	if (diff2 & MD2_ROLLANGLE)
 		WRITEANGLE(save_p, mobj->rollangle);
-#endif
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -2722,12 +2716,8 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 #endif
 	if (diff2 & MD2_COLORIZED)
 		mobj->colorized = READUINT8(save_p);
-#ifdef ROTSPRITE
 	if (diff2 & MD2_ROLLANGLE)
 		mobj->rollangle = READANGLE(save_p);
-	else
-		mobj->rollangle = 0;
-#endif
 
 	if (diff & MD_REDFLAG)
 	{
diff --git a/src/p_setup.c b/src/p_setup.c
index 288a483f4012ce95c680cd9195cb362b43e78dc4..8009eb36eab7adab9e8591bfd65ebbe716bfd4d2 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -664,6 +664,7 @@ static void P_LoadRawSectors(UINT8 *data)
 		ss->ceilingpic = P_AddLevelFlat(ms->ceilingpic, foundflats);
 
 		ss->lightlevel = SHORT(ms->lightlevel);
+		ss->spawn_lightlevel = ss->lightlevel;
 		ss->special = SHORT(ms->special);
 		ss->tag = SHORT(ms->tag);
 		ss->nexttag = ss->firsttag = -1;
@@ -2539,8 +2540,7 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	// chasecam on in chaos, race, coop
 	// chasecam off in match, tag, capture the flag
-	chase = (gametype == GT_RACE || gametype == GT_COMPETITION || gametype == GT_COOP)
-		|| (maptol & TOL_2D);
+	chase = (!(gametyperules & GTR_FIRSTPERSON)) || (maptol & TOL_2D);
 
 	if (!dedicated)
 	{
diff --git a/src/p_spec.c b/src/p_spec.c
index 614dd4b0a1a7385f7a023a46760dbfcc738cb621..17a43d5ce4b136880c3967ff368ef6c83d6797e1 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4692,7 +4692,7 @@ DoneSection2:
 		}
 
 		case 2: // Special stage GOAL sector / Exit Sector / CTF Flag Return
-			if (player->bot || !G_PlatformGametype())
+			if (player->bot || !(gametyperules & GTR_ALLOWEXIT))
 				break;
 			if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && player->nightstime > 6)
 			{
@@ -4727,7 +4727,7 @@ DoneSection2:
 			break;
 
 		case 3: // Red Team's Base
-			if (gametype == GT_CTF && P_IsObjectOnGround(player->mo))
+			if ((gametyperules & GTR_TEAMFLAGS) && P_IsObjectOnGround(player->mo))
 			{
 				if (player->ctfteam == 1 && (player->gotflag & GF_BLUEFLAG))
 				{
@@ -4760,7 +4760,7 @@ DoneSection2:
 			break;
 
 		case 4: // Blue Team's Base
-			if (gametype == GT_CTF && P_IsObjectOnGround(player->mo))
+			if ((gametyperules & GTR_TEAMFLAGS) && P_IsObjectOnGround(player->mo))
 			{
 				if (player->ctfteam == 2 && (player->gotflag & GF_REDFLAG))
 				{
@@ -7224,14 +7224,14 @@ void P_SpawnSpecials(INT32 fromnetsave)
 				break;
 
 			case 308: // Race-only linedef executor. Triggers once.
-				if (gametype != GT_RACE && gametype != GT_COMPETITION)
+				if (!(gametyperules & GTR_RACE))
 					lines[i].special = 0;
 				break;
 
 			// Linedef executor triggers for CTF teams.
 			case 309:
 			case 311:
-				if (gametype != GT_CTF)
+				if (!(gametyperules & GTR_TEAMFLAGS))
 					lines[i].special = 0;
 				break;
 
diff --git a/src/p_user.c b/src/p_user.c
index f700588acf976d4814bd451f42aef03b49160e04..1717a78b9d5502fe0ea63d354b315f105b880027 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -387,7 +387,7 @@ UINT8 P_FindLowestMare(void)
 	mobj_t *mo2;
 	UINT8 mare = UINT8_MAX;
 
-	if (gametype == GT_RACE || gametype == GT_COMPETITION)
+	if (gametyperules & GTR_RACE)
 		return 0;
 
 	// scan the thinkers
@@ -638,9 +638,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->marebonuslap = 0;
 	player->flyangle = 0;
 	player->anotherflyangle = 0;
-#ifdef ROTSPRITE
 	player->mo->rollangle = 0;
-#endif
 
 	P_SetTarget(&player->mo->target, NULL);
 	P_SetTarget(&player->axis1, P_SetTarget(&player->axis2, NULL));
@@ -768,9 +766,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	player->secondjump = 0;
 	player->flyangle = 0;
 	player->anotherflyangle = 0;
-#ifdef ROTSPRITE
 	player->mo->rollangle = 0;
-#endif
 
 	player->powers[pw_shield] = SH_NONE;
 	player->powers[pw_super] = 0;
@@ -793,7 +789,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 		P_RestoreMusic(player);
 	}
 
-	if (gametype == GT_RACE || gametype == GT_COMPETITION)
+	if (gametyperules & GTR_RACE)
 	{
 		if (player->drillmeter < 48*20)
 			player->drillmeter = 48*20;
@@ -2181,7 +2177,7 @@ void P_DoPlayerExit(player_t *player)
 
 	if (cv_allowexitlevel.value == 0 && !G_PlatformGametype())
 		return;
-	else if (gametype == GT_RACE || gametype == GT_COMPETITION) // If in Race Mode, allow
+	else if (gametyperules & GTR_RACE) // If in Race Mode, allow
 	{
 		if (!countdown) // a 60-second wait ala Sonic 2.
 			countdown = (cv_countdowntime.value - 1)*TICRATE + 1; // Use cv_countdowntime
@@ -3110,7 +3106,7 @@ static void P_DoPlayerHeadSigns(player_t *player)
 			}
 		}
 	}
-	else if (gametype == GT_CTF && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))) // If you have the flag (duh).
+	else if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))) // If you have the flag (duh).
 	{
 		// Spawn a got-flag message over the head of the player that
 		// has it (but not on your own screen if you have the flag).
@@ -4670,7 +4666,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					if (player->powers[pw_carry] == CR_BRAKGOOP)
 						player->dashspeed = 0;
 
-					if (!((gametype == GT_RACE || gametype == GT_COMPETITION) && leveltime < 4*TICRATE))
+					if (!((gametyperules & GTR_RACE) && leveltime < 4*TICRATE))
 					{
 						if (player->dashspeed)
 						{
@@ -5048,7 +5044,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	{
 		if (onground || player->climbing || player->powers[pw_carry])
 			;
-		else if (gametype == GT_CTF && player->gotflag)
+		else if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag)
 			;
 		else if (player->pflags & (PF_GLIDING|PF_SLIDING|PF_SHIELDABILITY)) // If the player has used an ability previously
 			;
@@ -5269,7 +5265,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 			player->secondjump = 0;
 			player->pflags &= ~PF_THOKKED;
 		}
-		else if (player->pflags & PF_SLIDING || (gametype == GT_CTF && player->gotflag) || player->pflags & PF_SHIELDABILITY)
+		else if (player->pflags & PF_SLIDING || ((gametyperules & GTR_TEAMFLAGS) && player->gotflag) || player->pflags & PF_SHIELDABILITY)
 			;
 		/*else if (P_SuperReady(player))
 		{
@@ -5556,7 +5552,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	{
 		player->pflags |= PF_JUMPDOWN;
 
-		if ((gametype != GT_CTF || !player->gotflag) && !player->exiting)
+		if ((!(gametyperules & GTR_TEAMFLAGS) || !player->gotflag) && !player->exiting)
 		{
 			if (player->secondjump == 1 && player->charability != CA_DOUBLEJUMP)
 			{
@@ -6820,7 +6816,6 @@ static void P_DoNiGHTSCapsule(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
 	}
 
-#ifdef ROTSPRITE
 	if (!(player->charflags & SF_NONIGHTSROTATION))
 	{
 		if ((player->mo->state == &states[S_PLAY_NIGHTS_PULL])
@@ -6829,7 +6824,6 @@ static void P_DoNiGHTSCapsule(player_t *player)
 		else
 			player->mo->rollangle = 0;
 	}
-#endif
 
 	if (G_IsSpecialStage(gamemap))
 	{ // In special stages, share rings. Everyone gives up theirs to the capsule player always, because we can't have any individualism here!
@@ -7092,9 +7086,7 @@ static void P_NiGHTSMovement(player_t *player)
 	INT32 i;
 	statenum_t flystate;
 	UINT16 visangle;
-#ifdef ROTSPRITE
 	angle_t rollangle = 0;
-#endif
 
 	player->pflags &= ~PF_DRILLING;
 
@@ -7133,7 +7125,7 @@ static void P_NiGHTSMovement(player_t *player)
 		&& !player->exiting)
 			player->nightstime--;
 	}
-	else if (gametype != GT_RACE && gametype != GT_COMPETITION
+	else if (!(gametyperules & GTR_RACE)
 	&& !(player->mo->state >= &states[S_PLAY_NIGHTS_TRANS1]
 			&& player->mo->state <= &states[S_PLAY_NIGHTS_TRANS6])
 	&& !(player->capsule && player->capsule->reactiontime)
@@ -7279,9 +7271,7 @@ static void P_NiGHTSMovement(player_t *player)
 		&& player->mo->state <= &states[S_PLAY_NIGHTS_TRANS6])
 	{
 		player->mo->momx = player->mo->momy = player->mo->momz = 0;
-#ifdef ROTSPRITE
 		player->mo->rollangle = 0;
-#endif
 		return;
 	}
 
@@ -7289,7 +7279,7 @@ static void P_NiGHTSMovement(player_t *player)
 	{
 		player->mo->momx = player->mo->momy = 0;
 
-		if (gametype != GT_RACE && gametype != GT_COMPETITION)
+		if (!(gametyperules & GTR_RACE))
 			P_SetObjectMomZ(player->mo, FRACUNIT/2, (P_MobjFlip(player->mo)*player->mo->momz >= 0));
 		else
 			player->mo->momz = 0;
@@ -7607,7 +7597,6 @@ static void P_NiGHTSMovement(player_t *player)
 			flystate += (visangle*2); // S_PLAY_NIGHTS_FLY0-C - the *2 is to skip over drill states
 #endif
 		}
-#ifdef ROTSPRITE
 		else
 		{
 			angle_t a = R_PointToAngle(player->mo->x, player->mo->y) - player->mo->angle;
@@ -7625,18 +7614,15 @@ static void P_NiGHTSMovement(player_t *player)
 
 			rollangle = FixedAngle(visangle<<FRACBITS);
 		}
-#endif
 	}
 
 	if (player->mo->state != &states[flystate])
 		P_SetPlayerMobjState(player->mo, flystate);
 
-#ifdef ROTSPRITE
 	if (player->charflags & SF_NONIGHTSROTATION)
 		player->mo->rollangle = 0;
 	else
 		player->mo->rollangle = rollangle;
-#endif
 
 	if (player == &players[consoleplayer])
 		localangle = player->mo->angle;
@@ -9531,12 +9517,12 @@ static void P_DeathThink(player_t *player)
 		player->playerstate = PST_REBORN;
 	}
 
-	if (gametype == GT_RACE || gametype == GT_COMPETITION || (gametype == GT_COOP && (multiplayer || netgame)))
+	if ((gametyperules & GTR_RACE) || (gametype == GT_COOP && (multiplayer || netgame)))
 	{
 		// Keep time rolling in race mode
 		if (!(countdown2 && !countdown) && !player->exiting && !(player->pflags & PF_GAMETYPEOVER) && !stoppedclock)
 		{
-			if (gametype == GT_RACE || gametype == GT_COMPETITION)
+			if (gametyperules & GTR_RACE)
 			{
 				if (leveltime >= 4*TICRATE)
 					player->realtime = leveltime - 4*TICRATE;
@@ -9585,6 +9571,7 @@ static void CV_CamRotate2_OnChange(void)
 static CV_PossibleValue_t CV_CamSpeed[] = {{0, "MIN"}, {1*FRACUNIT, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {45, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t multiplier_cons_t[] = {{0, "MIN"}, {3*FRACUNIT, "MAX"}, {0, NULL}};
 
 consvar_t cv_cam_dist = {"cam_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_height = {"cam_height", "25", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -9592,6 +9579,7 @@ consvar_t cv_cam_still = {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL,
 consvar_t cv_cam_speed = {"cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotate = {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_turnmultiplier = {"cam_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_orbit = {"cam_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_adjust = {"cam_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_dist = {"cam2_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -9600,6 +9588,7 @@ consvar_t cv_cam2_still = {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL
 consvar_t cv_cam2_speed = {"cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotate = {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotspeed = {"cam2_rotspeed", "10", CV_SAVE, rotation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_turnmultiplier = {"cam2_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_orbit = {"cam2_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_adjust = {"cam2_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -10360,6 +10349,11 @@ boolean P_SpectatorJoinGame(player_t *player)
 		else
 			changeto = (P_RandomFixed() & 1) + 1;
 
+#ifdef HAVE_BLUA
+		if (!LUAh_TeamSwitch(player, changeto, true, false, false))
+			return false;
+#endif
+
 		if (player->mo)
 		{
 			P_RemoveMobj(player->mo);
@@ -10371,7 +10365,14 @@ boolean P_SpectatorJoinGame(player_t *player)
 
 		//Reset away view
 		if (P_IsLocalPlayer(player) && displayplayer != consoleplayer)
+		{
+#ifdef HAVE_BLUA
+			// Call ViewpointSwitch hooks here.
+			// The viewpoint was forcibly changed.
+			LUAh_ViewpointSwitch(player, &players[displayplayer], true);
+#endif
 			displayplayer = consoleplayer;
+		}
 
 		if (changeto == 1)
 			CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[player-players], '\x85', M_GetText("Red team"), '\x80');
@@ -10385,8 +10386,12 @@ boolean P_SpectatorJoinGame(player_t *player)
 	{
 		// Exception for hide and seek. Don't join a game when you simply
 		// respawn in place and sit there for the rest of the round.
-		if (!(gametype == GT_HIDEANDSEEK && leveltime > (hidetime * TICRATE)))
+		if (!((gametyperules & GTR_HIDEFROZEN) && leveltime > (hidetime * TICRATE)))
 		{
+#ifdef HAVE_BLUA
+			if (!LUAh_TeamSwitch(player, 3, true, false, false))
+				return false;
+#endif
 			if (player->mo)
 			{
 				P_RemoveMobj(player->mo);
@@ -10409,7 +10414,14 @@ boolean P_SpectatorJoinGame(player_t *player)
 
 			//Reset away view
 			if (P_IsLocalPlayer(player) && displayplayer != consoleplayer)
+			{
+#ifdef HAVE_BLUA
+				// Call ViewpointSwitch hooks here.
+				// The viewpoint was forcibly changed.
+				LUAh_ViewpointSwitch(player, &players[displayplayer], true);
+#endif
 				displayplayer = consoleplayer;
+			}
 
 			if (gametype != GT_COOP)
 				CONS_Printf(M_GetText("%s entered the game.\n"), player_names[player-players]);
@@ -10535,7 +10547,7 @@ void P_DoPityCheck(player_t *player)
 {
 	// No pity outside of match or CTF.
 	if (player->spectator
-		|| !(gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF))
+		|| !(gametyperules & GTR_PITYSHIELD))
 		return;
 
 	// Apply pity shield if available.
@@ -11368,7 +11380,7 @@ void P_PlayerThink(player_t *player)
 		I_Error("player %s is in PST_REBORN\n", sizeu1(playeri));
 #endif
 
-	if (gametype == GT_RACE || gametype == GT_COMPETITION)
+	if (gametyperules & GTR_RACE)
 	{
 		INT32 i;
 
@@ -11431,7 +11443,7 @@ void P_PlayerThink(player_t *player)
 		player->exiting > 0 && player->exiting <= 1*TICRATE &&
 		(!multiplayer || gametype == GT_COOP ? !mapheaderinfo[gamemap-1]->musinterfadeout : true) &&
 			// don't fade if we're fading during intermission. follows Y_StartIntermission intertype = int_coop
-		(gametype == GT_RACE || gametype == GT_COMPETITION ? countdown2 == 0 : true) && // don't fade on timeout
+		((gametyperules & GTR_RACE) ? countdown2 == 0 : true) && // don't fade on timeout
 		player->lives > 0 && // don't fade on game over (competition)
 		P_IsLocalPlayer(player))
 	{
@@ -11546,7 +11558,7 @@ void P_PlayerThink(player_t *player)
 		player->lives = cv_startinglives.value;
 	}
 
-	if ((gametype == GT_RACE || gametype == GT_COMPETITION) && leveltime < 4*TICRATE)
+	if ((gametyperules & GTR_RACE) && leveltime < 4*TICRATE)
 	{
 		cmd->buttons &= BT_USE; // Remove all buttons except BT_USE
 		cmd->forwardmove = 0;
@@ -11556,7 +11568,7 @@ void P_PlayerThink(player_t *player)
 	// Synchronizes the "real" amount of time spent in the level.
 	if (!player->exiting && !stoppedclock)
 	{
-		if (gametype == GT_RACE || gametype == GT_COMPETITION)
+		if (gametyperules & GTR_RACE)
 		{
 			if (leveltime >= 4*TICRATE)
 				player->realtime = leveltime - 4*TICRATE;
diff --git a/src/r_main.c b/src/r_main.c
index fedc217a495aef745252e22c03e4ff0465b46962..09bbf6aa1968bed97b346ee2d49516014f56bb58 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1192,6 +1192,7 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_cam_speed);
 	CV_RegisterVar(&cv_cam_rotate);
 	CV_RegisterVar(&cv_cam_rotspeed);
+	CV_RegisterVar(&cv_cam_turnmultiplier);
 	CV_RegisterVar(&cv_cam_orbit);
 	CV_RegisterVar(&cv_cam_adjust);
 
@@ -1201,6 +1202,7 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_cam2_speed);
 	CV_RegisterVar(&cv_cam2_rotate);
 	CV_RegisterVar(&cv_cam2_rotspeed);
+	CV_RegisterVar(&cv_cam2_turnmultiplier);
 	CV_RegisterVar(&cv_cam2_orbit);
 	CV_RegisterVar(&cv_cam2_adjust);
 
diff --git a/src/r_patch.c b/src/r_patch.c
index d4bfa9cbda29f84a07b234d91d13324a14979875..0519d6dc9754776636cc823e439c73640b0049fb 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -49,8 +49,6 @@
 #endif
 
 static unsigned char imgbuf[1<<26];
-fixed_t cosang2rad[ROTANGLES];
-fixed_t sinang2rad[ROTANGLES];
 
 //
 // R_CheckIfPatch
@@ -789,11 +787,9 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 	size_t sprinfoTokenLength;
 	char *frameChar = NULL;
 	UINT8 frameFrame = 0xFF;
-#ifdef ROTSPRITE
 	INT16 frameXPivot = 0;
 	INT16 frameYPivot = 0;
 	rotaxis_t frameRotAxis = 0;
-#endif
 
 	// Sprite identifier
 	sprinfoToken = M_GetToken(NULL);
@@ -828,7 +824,6 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 			}
 			while (strcmp(sprinfoToken,"}")!=0)
 			{
-#ifdef ROTSPRITE
 				if (stricmp(sprinfoToken, "XPIVOT")==0)
 				{
 					Z_Free(sprinfoToken);
@@ -852,7 +847,6 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 					else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0) || (stricmp(sprinfoToken, "YAW")==0))
 						frameRotAxis = ROTAXIS_Z;
 				}
-#endif
 				Z_Free(sprinfoToken);
 
 				sprinfoToken = M_GetToken(NULL);
@@ -866,11 +860,9 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 	}
 
 	// set fields
-#ifdef ROTSPRITE
 	info->pivot[frameFrame].x = frameXPivot;
 	info->pivot[frameFrame].y = frameYPivot;
 	info->pivot[frameFrame].rotaxis = frameRotAxis;
-#endif
 }
 
 //
@@ -1093,16 +1085,60 @@ void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
 	for (i = 0; i < numlumps; i++, lumpinfo++)
 	{
 		name = lumpinfo->name;
-		// load SPRTINFO lumps
-		if (!stricmp(name, "SPRTINFO"))
+		// Load SPRTINFO and SPR_ lumps as SpriteInfo
+		if (!memcmp(name, "SPRTINFO", 8) || !memcmp(name, "SPR_", 4))
 			R_ParseSPRTINFOLump(wadnum, i);
-		// load SPR_ lumps (as DEHACKED lump)
-		else if (!memcmp(name, "SPR_", 4))
-			DEH_LoadDehackedLumpPwad(wadnum, i, false);
 	}
 }
 
+static UINT16 GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip)
+{
+	fixed_t ofs;
+	column_t *column;
+	UINT8 *source;
+
+	if (x >= 0 && x < SHORT(patch->width))
+	{
+		INT32 topdelta, prevdelta = -1;
+		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[flip ? (patch->width-1-x) : x]));
+		while (column->topdelta != 0xff)
+		{
+			topdelta = column->topdelta;
+			if (topdelta <= prevdelta)
+				topdelta += prevdelta;
+			prevdelta = topdelta;
+			source = (UINT8 *)(column) + 3;
+			for (ofs = 0; ofs < column->length; ofs++)
+			{
+				if ((topdelta + ofs) == y)
+					return source[ofs];
+			}
+			column = (column_t *)((UINT8 *)column + column->length + 4);
+		}
+	}
+
+	return 0xFF00;
+}
+
 #ifdef ROTSPRITE
+//
+// R_GetRollAngle
+//
+// Angles precalculated in R_InitSprites.
+//
+fixed_t rollcosang[ROTANGLES];
+fixed_t rollsinang[ROTANGLES];
+INT32 R_GetRollAngle(angle_t rollangle)
+{
+	INT32 ra = AngleFixed(rollangle)>>FRACBITS;
+#if (ROTANGDIFF > 1)
+	ra += (ROTANGDIFF/2);
+#endif
+	ra /= ROTANGDIFF;
+	ra %= ROTANGLES;
+	return ra;
+}
+
 //
 // R_CacheRotSprite
 //
@@ -1114,8 +1150,8 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 	INT32 angle;
 	patch_t *patch;
 	patch_t *newpatch;
-	UINT16 *rawsrc, *rawdst;
-	size_t size, size2;
+	UINT16 *rawdst;
+	size_t size;
 	INT32 bflip = (flip != 0x00);
 
 #define SPRITE_XCENTER (leftoffset)
@@ -1160,23 +1196,12 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			leftoffset = width - leftoffset;
 		}
 
-		// Draw the sprite to a temporary buffer.
-		size = (width*height);
-		rawsrc = Z_Malloc(size * sizeof(UINT16), PU_STATIC, NULL);
-
-		// can't memset here
-		for (i = 0; i < size; i++)
-			rawsrc[i] = 0xFF00;
-
-		R_PatchToFlat_16bpp(patch, rawsrc, bflip);
-
-		// Don't cache angle = 0
 		for (angle = 1; angle < ROTANGLES; angle++)
 		{
 			INT32 newwidth, newheight;
 
-			ca = cosang2rad[angle];
-			sa = sinang2rad[angle];
+			ca = rollcosang[angle];
+			sa = rollsinang[angle];
 
 			// Find the dimensions of the rotated patch.
 			{
@@ -1237,17 +1262,15 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 #undef BOUNDARYADJUST
 			}
 
-			size2 = (newwidth * newheight);
-			if (!size2)
-				size2 = size;
-
-			rawdst = Z_Malloc(size2 * sizeof(UINT16), PU_STATIC, NULL);
+			// Draw the rotated sprite to a temporary buffer.
+			size = (newwidth * newheight);
+			if (!size)
+				size = (width * height);
 
-			// can't memset here
-			for (i = 0; i < size2; i++)
+			rawdst = Z_Malloc(size * sizeof(UINT16), PU_STATIC, NULL);
+			for (i = 0; i < size; i++)
 				rawdst[i] = 0xFF00;
 
-			// Draw the rotated sprite to a temporary buffer.
 			for (dy = 0; dy < newheight; dy++)
 			{
 				for (dx = 0; dx < newwidth; dx++)
@@ -1259,7 +1282,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 					sx >>= FRACBITS;
 					sy >>= FRACBITS;
 					if (sx >= 0 && sy >= 0 && sx < width && sy < height)
-						rawdst[(dy*newwidth)+dx] = rawsrc[(sy*width)+sx];
+						rawdst[(dy*newwidth)+dx] = GetPatchPixel(patch, sx, sy, bflip);
 				}
 			}
 
@@ -1296,7 +1319,6 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 		sprframe->rotsprite.cached[rot] = true;
 
 		// free image data
-		Z_Free(rawsrc);
 		Z_Free(patch);
 	}
 #undef SPRITE_XCENTER
diff --git a/src/r_patch.h b/src/r_patch.h
index 8a8ab5602bbebaa507a84cb129b82d5a35bac53d..72371f253d0e1f0ecf03148e26e32741e8d5843f 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -18,7 +18,6 @@
 #include "doomdef.h"
 
 // Structs
-#ifdef ROTSPRITE
 typedef enum
 {
 	ROTAXIS_X, // Roll (the default)
@@ -31,13 +30,10 @@ typedef struct
 	INT32 x, y;
 	rotaxis_t rotaxis;
 } spriteframepivot_t;
-#endif
 
 typedef struct
 {
-#ifdef ROTSPRITE
 	spriteframepivot_t pivot[64];
-#endif
 	boolean available;
 } spriteinfo_t;
 
@@ -66,11 +62,12 @@ void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
 
 // Sprite rotation
 #ifdef ROTSPRITE
+INT32 R_GetRollAngle(angle_t rollangle);
 void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip);
 void R_FreeSingleRotSprite(spritedef_t *spritedef);
 void R_FreeSkinRotSprite(size_t skinnum);
-extern fixed_t cosang2rad[ROTANGLES];
-extern fixed_t sinang2rad[ROTANGLES];
+extern fixed_t rollcosang[ROTANGLES];
+extern fixed_t rollsinang[ROTANGLES];
 #endif
 
 #endif // __R_PATCH__
diff --git a/src/r_things.c b/src/r_things.c
index aa2a73515adfd2d75d450e18b6d9d10c9b050f34..cd7e4006433acbe8097cbf9d8b5fa369f5f51659 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -500,7 +500,7 @@ void R_InitSprites(void)
 {
 	size_t i;
 #ifdef ROTSPRITE
-	INT32 angle, realangle = 0;
+	INT32 angle;
 	float fa;
 #endif
 
@@ -508,12 +508,11 @@ void R_InitSprites(void)
 		negonearray[i] = -1;
 
 #ifdef ROTSPRITE
-	for (angle = 0; angle < ROTANGLES; angle++)
+	for (angle = 1; angle < ROTANGLES; angle++)
 	{
-		fa = ANG2RAD(FixedAngle(realangle<<FRACBITS));
-		cosang2rad[angle] = FLOAT_TO_FIXED(cos(-fa));
-		sinang2rad[angle] = FLOAT_TO_FIXED(sin(-fa));
-		realangle += ROTANGDIFF;
+		fa = ANG2RAD(FixedAngle((ROTANGDIFF * angle)<<FRACBITS));
+		rollcosang[angle] = FLOAT_TO_FIXED(cos(-fa));
+		rollsinang[angle] = FLOAT_TO_FIXED(sin(-fa));
 	}
 #endif
 
@@ -1127,8 +1126,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	fixed_t spr_offset, spr_topoffset;
 #ifdef ROTSPRITE
 	patch_t *rotsprite = NULL;
-	angle_t arollangle = thing->rollangle;
-	UINT32 rollangle = AngleFixed(arollangle)>>FRACBITS;
+	INT32 rollangle = 0;
 #endif
 
 #ifndef PROPERPAPER
@@ -1260,11 +1258,11 @@ static void R_ProjectSprite(mobj_t *thing)
 	spr_topoffset = spritecachedinfo[lump].topoffset;
 
 #ifdef ROTSPRITE
-	if (rollangle > 0)
+	if (thing->rollangle)
 	{
+		rollangle = R_GetRollAngle(thing->rollangle);
 		if (!sprframe->rotsprite.cached[rot])
 			R_CacheRotSprite(thing->sprite, (thing->frame & FF_FRAMEMASK), sprinfo, sprframe, rot, flip);
-		rollangle /= ROTANGDIFF;
 		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
 		if (rotsprite != NULL)
 		{
@@ -2839,7 +2837,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 {
 	return ((skinnum == -1) // Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
 		|| (!skins[skinnum].availability)
-		|| ((playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
+		|| (((netgame || multiplayer) && playernum != -1) ? (players[playernum].availabilities & (1 << skinnum)) : (unlockables[skins[skinnum].availability - 1].unlocked))
 		|| (modeattacking) // If you have someone else's run you might as well take a look
 		|| (Playing() && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum)) // Force 1.
 		|| (netgame && (cv_forceskin.value == skinnum)) // Force 2.
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index d0830396fa8741cc99ec3e3f7aa32484bb16f2de..5d0009927f1f64579c538d2cc604ee30fa3aa007 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -20,12 +20,17 @@
 #include "../doomdef.h"
 #include "../m_argv.h"
 #include "../d_main.h"
+#include "../m_misc.h"/* path shit */
 #include "../i_system.h"
 
-#ifdef __GNUC__
+#if defined (__GNUC__) || defined (__unix__)
 #include <unistd.h>
 #endif
 
+#ifdef __unix__
+#include <errno.h>
+#endif
+
 #include "time.h" // For log timestamps
 
 #ifdef HAVE_SDL
@@ -47,7 +52,7 @@ extern int SDL_main(int argc, char *argv[]);
 
 #ifdef LOGMESSAGES
 FILE *logstream = NULL;
-char  logfilename[1024];
+char logfilename[1024];
 #endif
 
 #ifndef DOXYGEN
@@ -133,34 +138,86 @@ int main(int argc, char **argv)
 	{
 		time_t my_time;
 		struct tm * timeinfo;
-		char buf[26];
+		const char *format;
+		const char *reldir;
+		int left;
+		boolean fileabs;
+#ifdef __unix__
+		const char *link;
+#endif
 
 		logdir = D_Home();
 
 		my_time = time(NULL);
 		timeinfo = localtime(&my_time);
 
-		strftime(buf, 26, "%Y-%m-%d %H-%M-%S", timeinfo);
-		strcpy(logfilename, va("log-%s.txt", buf));
+		if (M_CheckParm("-logfile") && M_IsNextParm())
+		{
+			format = M_GetNextParm();
+			fileabs = M_IsPathAbsolute(format);
+		}
+		else
+		{
+			format = "log-%Y-%m-%d_%H-%M-%S.txt";
+			fileabs = false;
+		}
 
-#ifdef DEFAULTDIR
-		if (logdir)
+		if (fileabs)
 		{
-			// Create dirs here because D_SRB2Main() is too late.
-			I_mkdir(va("%s%s"DEFAULTDIR, logdir, PATHSEP), 0755);
-			I_mkdir(va("%s%s"DEFAULTDIR"%slogs",logdir, PATHSEP, PATHSEP), 0755);
-			strcpy(logfilename, va("%s%s"DEFAULTDIR"%slogs%s%s",logdir, PATHSEP, PATHSEP, PATHSEP, logfilename));
+			strftime(logfilename, sizeof logfilename, format, timeinfo);
 		}
 		else
-#endif
 		{
-			I_mkdir("."PATHSEP"logs"PATHSEP, 0755);
-			strcpy(logfilename, va("."PATHSEP"logs"PATHSEP"%s", logfilename));
+			if (M_CheckParm("-logdir") && M_IsNextParm())
+				reldir = M_GetNextParm();
+			else
+				reldir = "logs";
+
+			if (M_IsPathAbsolute(reldir))
+			{
+				left = snprintf(logfilename, sizeof logfilename,
+						"%s"PATHSEP, reldir);
+			}
+			else
+#ifdef DEFAULTDIR
+			if (logdir)
+			{
+				left = snprintf(logfilename, sizeof logfilename,
+						"%s"PATHSEP DEFAULTDIR PATHSEP"%s"PATHSEP, logdir, reldir);
+			}
+			else
+#endif/*DEFAULTDIR*/
+			{
+				left = snprintf(logfilename, sizeof logfilename,
+						"."PATHSEP"%s"PATHSEP, reldir);
+			}
+#endif/*LOGMESSAGES*/
+
+			strftime(&logfilename[left], sizeof logfilename - left,
+					format, timeinfo);
 		}
 
-		logstream = fopen(logfilename, "wt");
+		M_MkdirEachUntil(logfilename,
+				M_PathParts(logdir) - 1,
+				M_PathParts(logfilename) - 1, 0755);
+
+#ifdef __unix__
+		logstream = fopen(logfilename, "w");
+#ifdef DEFAULTDIR
+		if (logdir)
+			link = va("%s/"DEFAULTDIR"/latest-log.txt", logdir);
+		else
+#endif/*DEFAULTDIR*/
+			link = "latest-log.txt";
+		unlink(link);
+		if (symlink(logfilename, link) == -1)
+		{
+			I_OutputMsg("Error symlinking latest-log.txt: %s\n", strerror(errno));
+		}
+#else/*__unix__*/
+		logstream = fopen("latest-log.txt", "wt+");
+#endif/*__unix__*/
 	}
-#endif
 
 	//I_OutputMsg("I_StartupSystem() ...\n");
 	I_StartupSystem();
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 52186baae20dfca6e924aea5d87105f2decfb607..81420e75772d621e1e20163c558ab0c9bd81d3ae 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2484,6 +2484,48 @@ void I_RemoveExitFunc(void (*func)())
 	}
 }
 
+#ifndef __unix__
+static void Shittycopyerror(const char *name)
+{
+	I_OutputMsg(
+			"Error copying log file: %s: %s\n",
+			name,
+			strerror(errno)
+	);
+}
+
+static void Shittylogcopy(void)
+{
+	char buf[8192];
+	FILE *fp;
+	size_t r;
+	if (fseek(logstream, 0, SEEK_SET) == -1)
+	{
+		Shittycopyerror("fseek");
+	}
+	else if (( fp = fopen(logfilename, "wt") ))
+	{
+		while (( r = fread(buf, 1, sizeof buf, logstream) ))
+		{
+			if (fwrite(buf, 1, r, fp) < r)
+			{
+				Shittycopyerror("fwrite");
+				break;
+			}
+		}
+		if (ferror(logstream))
+		{
+			Shittycopyerror("fread");
+		}
+		fclose(fp);
+	}
+	else
+	{
+		Shittycopyerror(logfilename);
+	}
+}
+#endif/*__unix__*/
+
 //
 //  Closes down everything. This includes restoring the initial
 //  palette and video mode, and removing whatever mouse, keyboard, and
@@ -2506,6 +2548,9 @@ void I_ShutdownSystem(void)
 	if (logstream)
 	{
 		I_OutputMsg("I_ShutdownSystem(): end of logstream.\n");
+#ifndef __unix__
+		Shittylogcopy();
+#endif
 		fclose(logstream);
 		logstream = NULL;
 	}
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 963dc24cc3732cede78954cb17179782e9a0b3aa..5e05030c38d305f90d81ffa9a1db24217650738e 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -694,7 +694,7 @@ static void ST_drawTime(void)
 	else
 	{
 		// Counting down the hidetime?
-		if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (stplyr->realtime <= (hidetime*TICRATE)))
+		if ((gametyperules & GTR_HIDETIME) && (stplyr->realtime <= (hidetime*TICRATE)))
 		{
 			tics = (hidetime*TICRATE - stplyr->realtime);
 			if (tics < 3*TICRATE)
@@ -705,11 +705,11 @@ static void ST_drawTime(void)
 		else
 		{
 			// Hidetime finish!
-			if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (stplyr->realtime < ((hidetime+1)*TICRATE)))
+			if ((gametyperules & GTR_HIDETIME) && (stplyr->realtime < ((hidetime+1)*TICRATE)))
 				ST_drawRaceNum(hidetime*TICRATE - stplyr->realtime);
 
 			// Time limit?
-			if (gametype != GT_COOP && gametype != GT_RACE && gametype != GT_COMPETITION && cv_timelimit.value && timelimitintics > 0)
+			if ((gametyperules & GTR_TIMELIMIT) && cv_timelimit.value && timelimitintics > 0)
 			{
 				if (timelimitintics > stplyr->realtime)
 				{
@@ -723,7 +723,7 @@ static void ST_drawTime(void)
 				downwards = true;
 			}
 			// Post-hidetime normal.
-			else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+			else if (gametyperules & GTR_TAG)
 				tics = stplyr->realtime - hidetime*TICRATE;
 			// "Shadow! What are you doing? Hurry and get back here
 			// right now before the island blows up with you on it!"
@@ -912,7 +912,7 @@ static void ST_drawLivesArea(void)
 	else if (stplyr->spectator)
 		v_colmap = V_GRAYMAP;
 	// Tag
-	else if (gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	else if (gametyperules & GTR_TAG)
 	{
 		if (stplyr->pflags & PF_TAGIT)
 		{
@@ -1228,6 +1228,9 @@ void ST_startTitleCard(void)
 //
 void ST_preDrawTitleCard(void)
 {
+	if (!G_IsTitleCardAvailable())
+		return;
+
 	if (lt_ticker >= (lt_endtime + TICRATE))
 		return;
 
@@ -1243,6 +1246,9 @@ void ST_preDrawTitleCard(void)
 //
 void ST_runTitleCard(void)
 {
+	if (!G_IsTitleCardAvailable())
+		return;
+
 	if (lt_ticker >= (lt_endtime + TICRATE))
 		return;
 
@@ -1296,6 +1302,9 @@ void ST_drawTitleCard(void)
 	INT32 zzticker;
 	patch_t *actpat, *zigzag, *zztext;
 
+	if (!G_IsTitleCardAvailable())
+		return;
+
 #ifdef HAVE_BLUA
 	if (!LUA_HudEnabled(hud_stagetitle))
 		goto luahook;
@@ -1774,7 +1783,7 @@ static void ST_drawNiGHTSHUD(void)
 		ST_drawNiGHTSLink();
 	}
 
-	if (gametype == GT_RACE || gametype == GT_COMPETITION)
+	if (gametyperules & GTR_RACE)
 	{
 		ST_drawScore();
 		ST_drawTime();
@@ -2215,34 +2224,37 @@ static void ST_drawTextHUD(void)
 
 		if (G_IsSpecialStage(gamemap))
 			textHUDdraw(M_GetText("\x82""Wait for the stage to end..."))
-		else if (gametype == GT_COOP)
+		else if (G_PlatformGametype())
 		{
-			if (stplyr->lives <= 0
-			&& cv_cooplives.value == 2
-			&& (netgame || multiplayer))
+			if (gametype == GT_COOP)
 			{
-				INT32 i;
-				for (i = 0; i < MAXPLAYERS; i++)
+				if (stplyr->lives <= 0
+				&& cv_cooplives.value == 2
+				&& (netgame || multiplayer))
 				{
-					if (!playeringame[i])
-						continue;
-
-					if (&players[i] == stplyr)
-						continue;
-
-					if (players[i].lives > 1)
-						break;
-					}
-
-				if (i != MAXPLAYERS)
-					textHUDdraw(M_GetText("You'll steal a life on respawn..."))
+					INT32 i;
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (!playeringame[i])
+							continue;
+
+						if (&players[i] == stplyr)
+							continue;
+
+						if (players[i].lives > 1)
+							break;
+						}
+
+					if (i != MAXPLAYERS)
+						textHUDdraw(M_GetText("You'll steal a life on respawn..."))
+					else
+						textHUDdraw(M_GetText("Wait to respawn..."))
+				}
 				else
 					textHUDdraw(M_GetText("Wait to respawn..."))
 			}
-			else
-				textHUDdraw(M_GetText("Wait to respawn..."))
 		}
-		else
+		else if (G_GametypeHasSpectators())
 			textHUDdraw(M_GetText("\x82""FIRE:""\x80 Enter game"))
 	}
 
@@ -2285,13 +2297,14 @@ static void ST_drawTextHUD(void)
 			}
 		}
 	}
-	else if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK) && (!stplyr->spectator))
+	else if ((gametyperules & GTR_TAG) && (!stplyr->spectator))
 	{
 		if (leveltime < hidetime * TICRATE)
 		{
 			if (stplyr->pflags & PF_TAGIT)
 			{
-				textHUDdraw(M_GetText("\x82""You are blindfolded!"))
+				if (gametyperules & GTR_BLINDFOLDED)
+					textHUDdraw(M_GetText("\x82""You are blindfolded!"))
 				textHUDdraw(M_GetText("Waiting for players to hide..."))
 			}
 			else if (gametype == GT_HIDEANDSEEK)
@@ -2306,7 +2319,8 @@ static void ST_drawTextHUD(void)
 				textHUDdraw(M_GetText("\x82""VIEWPOINT:""\x80 Switch view"))
 				donef12 = true;
 			}
-			textHUDdraw(M_GetText("You cannot move while hiding."))
+			if (gametyperules & GTR_HIDEFROZEN)
+				textHUDdraw(M_GetText("You cannot move while hiding."))
 		}
 	}
 
@@ -2328,27 +2342,27 @@ static void ST_drawTeamHUD(void)
 	if (F_GetPromptHideHud(0)) // y base is 0
 		return;
 
-	if (gametype == GT_CTF)
+	if (gametyperules & GTR_TEAMFLAGS)
 		p = bflagico;
 	else
 		p = bmatcico;
-	
+
 #ifdef HAVE_BLUA
 	if (LUA_HudEnabled(hud_teamscores))
 #endif
 	V_DrawSmallScaledPatch(BASEVIDWIDTH/2 - SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
-	
-	if (gametype == GT_CTF)
+
+	if (gametyperules & GTR_TEAMFLAGS)
 		p = rflagico;
 	else
 		p = rmatcico;
-	
+
 #ifdef HAVE_BLUA
 	if (LUA_HudEnabled(hud_teamscores))
 #endif
 	V_DrawSmallScaledPatch(BASEVIDWIDTH/2 + SEP - SHORT(p->width)/4, 4, V_HUDTRANS|V_PERPLAYER|V_SNAPTOTOP, p);
 
-	if (gametype != GT_CTF)
+	if (!(gametyperules & GTR_TEAMFLAGS))
 		goto num;
 	{
 		INT32 i;
@@ -2676,7 +2690,7 @@ static void ST_overlayDrawer(void)
 		}
 
 		// If you are in overtime, put a big honkin' flashin' message on the screen.
-		if (G_RingSlingerGametype() && cv_overtime.value
+		if (((gametyperules & GTR_TIMELIMIT) && (gametyperules & GTR_OVERTIME)) && cv_overtime.value
 		&& (leveltime > (timelimitintics + TICRATE/2)) && cv_timelimit.value && (leveltime/TICRATE % 2 == 0))
 			V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_PERPLAYER, M_GetText("OVERTIME!"));
 
@@ -2691,7 +2705,7 @@ static void ST_overlayDrawer(void)
 			ST_drawMatchHUD();
 
 		// Race HUD Stuff
-		if (gametype == GT_RACE || gametype == GT_COMPETITION)
+		if (gametyperules & GTR_RACE)
 			ST_drawRaceHUD();
 
 		// Emerald Hunt Indicators
@@ -2796,7 +2810,7 @@ void ST_Drawer(void)
 		if (rendermode != render_none) ST_doPaletteStuff();
 
 	// Blindfold!
-	if ((gametype == GT_TAG || gametype == GT_HIDEANDSEEK)
+	if ((gametyperules & GTR_BLINDFOLDED)
 	&& (leveltime < hidetime * TICRATE))
 	{
 		if (players[displayplayer].pflags & PF_TAGIT)
diff --git a/src/y_inter.c b/src/y_inter.c
index 842bd1b287e22d7696185cdec69af5dce953aa29..b26c0797e36906c88185506db48515f8ce44f6c0 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -37,6 +37,10 @@
 #include "m_cond.h" // condition sets
 #include "lua_hook.h" // IntermissionThinker hook
 
+#ifdef HAVE_BLUA
+#include "lua_hud.h"
+#endif
+
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
 #endif
@@ -164,6 +168,7 @@ static INT32 tallydonetic = -1;
 static INT32 endtic = -1;
 
 intertype_t intertype = int_none;
+intertype_t intermissiontypes[NUMGAMETYPES];
 
 static void Y_RescaleScreenBuffer(void);
 static void Y_AwardCoopBonuses(void);
@@ -318,9 +323,17 @@ void Y_IntermissionDrawer(void)
 	// Bonus loops
 	INT32 i;
 
-	if (intertype == int_none || rendermode == render_none)
+	if (rendermode == render_none)
 		return;
 
+	if (intertype == int_none)
+	{
+#ifdef HAVE_BLUA
+		LUAh_IntermissionHUD();
+#endif
+		return;
+	}
+
 	if (!usebuffer)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
@@ -358,6 +371,12 @@ void Y_IntermissionDrawer(void)
 	else
 		V_DrawPatchFill(bgtile);
 
+#ifdef HAVE_BLUA
+	LUAh_IntermissionHUD();
+	if (!LUA_HudEnabled(hud_intermissiontally))
+		goto skiptallydrawer;
+#endif
+
 	if (intertype == int_coop)
 	{
 		INT32 bonusy;
@@ -907,6 +926,12 @@ void Y_IntermissionDrawer(void)
 		}
 	}
 
+#ifdef HAVE_BLUA
+skiptallydrawer:
+	if (!LUA_HudEnabled(hud_intermissionmessages))
+		return;
+#endif
+
 	if (timer)
 		V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP,
 			va("start in %d seconds", timer/TICRATE));
@@ -1187,7 +1212,9 @@ void Y_StartIntermission(void)
 				timer = 1;
 		}
 
-		if (gametype == GT_COOP)
+		if (intermissiontypes[gametype] != int_none)
+			intertype = intermissiontypes[gametype];
+		else if (gametype == GT_COOP)
 			intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
 		else if (gametype == GT_TEAMMATCH)
 			intertype = int_teammatch;
diff --git a/src/y_inter.h b/src/y_inter.h
index 7dffbff32c3ad61643d912ec2ae4deadcbaa0918..5cf2cc1b84ee8c9ac75923cac9fc9e99481e5e6f 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -31,3 +31,4 @@ typedef enum
 	int_comp,     // Competition
 } intertype_t;
 extern intertype_t intertype;
+extern intertype_t intermissiontypes[NUMGAMETYPES];