diff --git a/extras/conf/udb/Includes/SRB222_common.cfg b/extras/conf/udb/Includes/SRB222_common.cfg
index 0ff044a6d382cc102c5b5dd98681e04ff3c81b31..8f37aabaa41de4072ede5b6910666fb654d7cfd0 100644
--- a/extras/conf/udb/Includes/SRB222_common.cfg
+++ b/extras/conf/udb/Includes/SRB222_common.cfg
@@ -15,7 +15,7 @@ common
 	ignoredextensions = "wad pk3 pk7 bak backup1 backup2 backup3 zip rar 7z";
 
 	// Default testing parameters
-	testparameters = "-file \"%AP\" \"%F\" -warp %L";
+	testparameters = "-folder \"%AF\" -file \"%AA\" \"%F\" -warp %L";
 	testshortpaths = true;
 
 	// Action special help
@@ -26,7 +26,7 @@ common
 	generalizedsectors = true;
 
 	// Maximum safe map size check (0 means skip check)
-	safeboundary = 1;
+	safeboundary = 0;
 
 	// Map boundaries. Map objects can only be placed within these boundaries
 	leftboundary = -32768;
@@ -40,6 +40,8 @@ common
 	defaultflatscale = 1.0f;
 	scaledtextureoffsets = true;
 
+	maxcolormapalpha = 25;
+
 	// Thing number for start position in 3D Mode
 	start3dmode = 3328;
 
@@ -68,137 +70,6 @@ common
 	}
 }
 
-mapformat_doom
-{
-	// The format interface handles the map data format
-	formatinterface = "DoomMapSetIO";
-
-	// Default nodebuilder configurations
-	defaultsavecompiler = "zennode_normal";
-	defaulttestcompiler = "zennode_fast";
-
-	/*
-	GAME DETECT PATTERN
-	Used to guess the game for which a WAD file is made.
-
-	1 = One of these lumps must exist
-	2 = None of these lumps must exist
-	3 = All of these lumps must exist
-	*/
-
-	gamedetect
-	{
-		EXTENDED = 2;
-
-
-		BEHAVIOR = 2;
-
-		E#M# = 2;
-
-		MAP?? = 1;
-	}
-
-	/*
-	MAP LUMP NAMES
-	Map lumps are loaded with the map as long as they are right after each other. When the editor
-	meets a lump which is not defined in this list it will ignore the map if not satisfied.
-	The order of items defines the order in which lumps will be written to WAD file on save.
-	To indicate the map header lump, use ~MAP
-
-	Legenda:
-	required = Lump is required to exist.
-	blindcopy = Lump will be copied along with the map blindly. (usefull for lumps Doom Builder doesn't use)
-	nodebuild = The nodebuilder generates this lump.
-	allowempty = The nodebuilder is allowed to leave this lump empty.
-	script = This lump is a text-based script. Specify the filename of the script configuration to use.
-	*/
-
-	maplumpnames
-	{
-		include("SRB222_misc.cfg", "doommaplumpnames");
-	}
-
-	// When this is set to true, sectors with the same tag will light up when a line is highlighted
-	linetagindicatesectors = true;
-
-	// Special linedefs
-	include("SRB222_misc.cfg", "speciallinedefs");
-
-	// Default flags for first new thing
-	defaultthingflags
-	{
-	}
-
-	// DEFAULT SECTOR BRIGHTNESS LEVELS
-	sectorbrightness
-	{
-		include("SRB222_misc.cfg", "sectorbrightness");
-	}
-
-	// SECTOR TYPES
-	sectortypes
-	{
-		include("SRB222_sectors.cfg", "sectortypes");
-	}
-
-	// GENERALISED SECTOR TYPES
-	gen_sectortypes
-	{
-		include("SRB222_sectors.cfg", "gen_sectortypes");
-	}
-
-	// LINEDEF FLAGS
-	linedefflags
-	{
-		include("SRB222_misc.cfg", "linedefflags");
-	}
-
-	// Linedef flags UDMF translation table
-	// This is needed for copy/paste and prefabs to work properly
-	// When the UDMF field name is prefixed with ! it is inverted
-	linedefflagstranslation
-	{
-		include("SRB222_misc.cfg", "linedefflagstranslation");
-	}
-
-	// LINEDEF ACTIVATIONS
-	linedefactivations
-	{
-	}
-
-	// LINEDEF TYPES
-	linedeftypes
-	{
-		include("SRB222_linedefs.cfg", "doom");
-	}
-
-	// THING FLAGS
-	thingflags
-	{
-		include("SRB222_misc.cfg", "thingflags");
-	}
-
-	// Thing flags UDMF translation table
-	// This is needed for copy/paste and prefabs to work properly
-	// When the UDMF field name is prefixed with ! it is inverted
-	thingflagstranslation
-	{
-		include("SRB222_misc.cfg", "thingflagstranslation");
-	}
-
-	// THING FLAGS ERROR MASK
-	// Mask for the thing flags which indicates the options
-	// that make the same thing appear in the same modes
-	thingflagsmask1 = 7;	// 1 + 2 + 4
-	thingflagsmask2 = 0;
-
-	// THING TYPES
-	thingtypes
-	{
-		include("SRB222_things.cfg", "doom");
-	}
-}
-
 mapformat_udmf
 {
 	// The format interface handles the map data format
@@ -222,9 +93,17 @@ mapformat_udmf
 	{
 		include("SRB222_misc.cfg", "universalfields");
 	}
+	
+	// Disable Doom-related modes that don't make sense for SRB2
+	soundsupport = false;
+	automapsupport = false;
 
 	// When this is set to true, sectors with the same tag will light up when a line is highlighted
 	linetagindicatesectors = false;
+	localsidedeftextureoffsets = true;
+	distinctfloorandceilingbrightness = true;
+	
+	planeequationsupport = true;
 
 	// Special linedefs
 	include("SRB222_misc.cfg", "speciallinedefs_udmf");
@@ -240,6 +119,11 @@ mapformat_udmf
 		include("SRB222_misc.cfg", "sectorflags");
 	}
 
+	sectorflagscategories
+	{
+		include("SRB222_misc.cfg", "sectorflagscategories");
+	}
+
 	// DEFAULT SECTOR BRIGHTNESS LEVELS
 	sectorbrightness
 	{
@@ -247,6 +131,7 @@ mapformat_udmf
 	}
 
 	damagetypes = "Generic Water Fire Lava Electric Spike DeathPitTilt DeathPitNoTilt Instakill SpecialStage";
+	triggerertypes = "Player AllPlayers Mobj";
 
 	// LINEDEF FLAGS
 	linedefflags
@@ -282,7 +167,6 @@ mapformat_udmf
 	// How to compare thing flags (for the stuck things error checker)
 	thingflagscompare
 	{
-		include("UDMF_misc.cfg", "thingflagscompare");
 	}
 
 	// THING TYPES
diff --git a/extras/conf/udb/Includes/SRB222_linedefs.cfg b/extras/conf/udb/Includes/SRB222_linedefs.cfg
index b526e84e7bfff6eb77fa3161bbca16df18b8d393..621b4abd53c4ff087664c9b24b0b424eae5d1e8b 100644
--- a/extras/conf/udb/Includes/SRB222_linedefs.cfg
+++ b/extras/conf/udb/Includes/SRB222_linedefs.cfg
@@ -1,1793 +1,3 @@
-doom
-{
-	misc
-	{
-		title = "Miscellaneous";
-
-		0
-		{
-			title = "None";
-			prefix = "(0)";
-		}
-		1
-		{
-			title = "Per-Sector Gravity";
-			prefix = "(1)";
-		}
-		5
-		{
-			title = "Camera Scanner <deprecated>";
-			prefix = "(5)";
-		}
-		7
-		{
-			title = "Sector Flat Alignment";
-			prefix = "(7)";
-		}
-		10
-		{
-			title = "Culling Plane";
-			prefix = "(10)";
-		}
-		13
-		{
-			title = "Heat Wave Effect";
-			prefix = "(13)";
-		}
-		40
-		{
-			title = "Visual Portal Between Tagged Linedefs";
-			prefix = "(40)";
-		}
-		41
-		{
-			title = "Horizon Effect";
-			prefix = "(41)";
-		}
-		50
-		{
-			title = "Instantly Lower Floor on Level Load";
-			prefix = "(50)";
-		}
-		51
-		{
-			title = "Instantly Raise Ceiling on Level Load";
-			prefix = "(51)";
-		}
-		63
-		{
-			title = "Fake Floor/Ceiling Planes";
-			prefix = "(63)";
-		}
-		540
-		{
-			title = "Floor Friction";
-			prefix = "(540)";
-		}
-	}
-
-	parameters
-	{
-		title = "Parameters";
-
-		2
-		{
-			title = "Custom Exit";
-			prefix = "(2)";
-		}
-		3
-		{
-			title = "Zoom Tube Parameters";
-			prefix = "(3)";
-		}
-		4
-		{
-			title = "Speed Pad Parameters";
-			prefix = "(4)";
-		}
-		8
-		{
-			title = "Special Sector Properties";
-			prefix = "(8)";
-		}
-		9
-		{
-			title = "Chain Parameters";
-			prefix = "(9)";
-		}
-		11
-		{
-			title = "Rope Hang Parameters";
-			prefix = "(11)";
-		}
-		12
-		{
-			title = "Rock Spawner Parameters";
-			prefix = "(12)";
-		}
-		14
-		{
-			title = "Bustable Block Parameters";
-			prefix = "(14)";
-		}
-		15
-		{
-			title = "Fan Particle Spawner Parameters";
-			prefix = "(15)";
-		}
-		16
-		{
-			title = "Minecart Parameters";
-			prefix = "(16)";
-		}
-		64
-		{
-			title = "Continuously Appearing/Disappearing FOF";
-			prefix = "(64)";
-		}
-		76
-		{
-			title = "Make FOF Bouncy";
-			prefix = "(76)";
-		}
-	}
-
-	polyobject
-	{
-		title = "PolyObject";
-
-		20
-		{
-			title = "First Line";
-			prefix = "(20)";
-		}
-		21
-		{
-			title = "Explicitly Include Line <disabled>";
-			prefix = "(21)";
-		}
-		22
-		{
-			title = "Parameters";
-			prefix = "(22)";
-		}
-		30
-		{
-			title = "Waving Flag";
-			prefix = "(30)";
-		}
-		31
-		{
-			title = "Displacement by Front Sector";
-			prefix = "(31)";
-		}
-		32
-		{
-			title = "Angular Displacement by Front Sector";
-			prefix = "(32)";
-		}
-	}
-
-	planemove
-	{
-		title = "Plane Movement";
-
-		52
-		{
-			title = "Continuously Falling Sector";
-			prefix = "(52)";
-		}
-		53
-		{
-			title = "Continuous Floor/Ceiling Mover";
-			prefix = "(53)";
-		}
-		54
-		{
-			title = "Continuous Floor Mover";
-			prefix = "(54)";
-		}
-		55
-		{
-			title = "Continuous Ceiling Mover";
-			prefix = "(55)";
-		}
-		56
-		{
-			title = "Continuous Two-Speed Floor/Ceiling Mover";
-			prefix = "(56)";
-		}
-		57
-		{
-			title = "Continuous Two-Speed Floor Mover";
-			prefix = "(57)";
-		}
-		58
-		{
-			title = "Continuous Two-Speed Ceiling Mover";
-			prefix = "(58)";
-		}
-		59
-		{
-			title = "Activate Moving Platform";
-			prefix = "(59)";
-		}
-		60
-		{
-			title = "Activate Moving Platform (Adjustable Speed)";
-			prefix = "(60)";
-		}
-		61
-		{
-			title = "Crusher (Ceiling to Floor)";
-			prefix = "(61)";
-		}
-		62
-		{
-			title = "Crusher (Floor to Ceiling)";
-			prefix = "(62)";
-		}
-		66
-		{
-			title = "Move Floor by Displacement";
-			prefix = "(66)";
-		}
-		67
-		{
-			title = "Move Ceiling by Displacement";
-			prefix = "(67)";
-		}
-		68
-		{
-			title = "Move Floor and Ceiling by Displacement";
-			prefix = "(68)";
-		}
-	}
-
-	fofsolid
-	{
-		title = "FOF (solid)";
-
-		100
-		{
-			title = "Solid, Opaque";
-			prefix = "(100)";
-		}
-		101
-		{
-			title = "Solid, Opaque, No Shadow";
-			prefix = "(101)";
-		}
-		102
-		{
-			title = "Solid, Translucent";
-			prefix = "(102)";
-		}
-		103
-		{
-			title = "Solid, Sides Only";
-			prefix = "(103)";
-		}
-		104
-		{
-			title = "Solid, No Sides";
-			prefix = "(104)";
-		}
-		105
-		{
-			title = "Solid, Invisible";
-			prefix = "(105)";
-		}
-		140
-		{
-			title = "Intangible from Bottom, Opaque";
-			prefix = "(140)";
-		}
-		141
-		{
-			title = "Intangible from Bottom, Translucent";
-			prefix = "(141)";
-		}
-		142
-		{
-			title = "Intangible from Bottom, Translucent, No Sides";
-			prefix = "(142)";
-		}
-		143
-		{
-			title = "Intangible from Top, Opaque";
-			prefix = "(143)";
-		}
-		144
-		{
-			title = "Intangible from Top, Translucent";
-			prefix = "(144)";
-		}
-		145
-		{
-			title = "Intangible from Top, Translucent, No Sides";
-			prefix = "(145)";
-		}
-		146
-		{
-			title = "Only Tangible from Sides";
-			prefix = "(146)";
-		}
-	}
-
-	fofintangible
-	{
-		title = "FOF (intangible)";
-
-		120
-		{
-			title = "Water, Opaque";
-			prefix = "(120)";
-		}
-		121
-		{
-			title = "Water, Translucent";
-			prefix = "(121)";
-		}
-		122
-		{
-			title = "Water, Opaque, No Sides";
-			prefix = "(122)";
-		}
-		123
-		{
-			title = "Water, Translucent, No Sides";
-			prefix = "(123)";
-		}
-		124
-		{
-			title = "Goo Water, Translucent";
-			prefix = "(124)";
-		}
-		125
-		{
-			title = "Goo Water, Translucent, No Sides";
-			prefix = "(125)";
-		}
-		220
-		{
-			title = "Intangible, Opaque";
-			prefix = "(220)";
-		}
-		221
-		{
-			title = "Intangible, Translucent";
-			prefix = "(221)";
-		}
-		222
-		{
-			title = "Intangible, Sides Only";
-			prefix = "(222)";
-		}
-		223
-		{
-			title = "Intangible, Invisible";
-			prefix = "(223)";
-		}
-	}
-
-	fofmoving
-	{
-		title = "FOF (moving)";
-
-		150
-		{
-			title = "Air Bobbing";
-			prefix = "(150)";
-		}
-		151
-		{
-			title = "Air Bobbing (Adjustable)";
-			prefix = "(151)";
-		}
-		152
-		{
-			title = "Reverse Air Bobbing (Adjustable)";
-			prefix = "(152)";
-		}
-		153
-		{
-			title = "Dynamically Sinking Platform";
-			prefix = "(153)";
-		}
-		160
-		{
-			title = "Water Bobbing";
-			prefix = "(160)";
-		}
-		190
-		{
-			title = "Rising Platform, Solid, Opaque";
-			prefix = "(190)";
-		}
-		191
-		{
-			title = "Rising Platform, Solid, Opaque, No Shadow";
-			prefix = "(191)";
-		}
-		192
-		{
-			title = "Rising Platform, Solid, Translucent";
-			prefix = "(192)";
-		}
-		193
-		{
-			title = "Rising Platform, Solid, Invisible";
-			prefix = "(193)";
-		}
-		194
-		{
-			title = "Rising Platform, Intangible from Bottom, Opaque";
-			prefix = "(194)";
-		}
-		195
-		{
-			title = "Rising Platform, Intangible from Bottom, Translucent";
-			prefix = "(195)";
-		}
-	}
-
-	fofcrumbling
-	{
-		title = "FOF (crumbling)";
-
-		170
-		{
-			title = "Crumbling, Respawn";
-			prefix = "(170)";
-		}
-		171
-		{
-			title = "Crumbling, No Respawn";
-			prefix = "(171)";
-		}
-		172
-		{
-			title = "Crumbling, Respawn, Intangible from Bottom";
-			prefix = "(172)";
-		}
-		173
-		{
-			title = "Crumbling, No Respawn, Intangible from Bottom";
-			prefix = "(173)";
-		}
-		174
-		{
-			title = "Crumbling, Respawn, Int. from Bottom, Translucent";
-			prefix = "(174)";
-		}
-		175
-		{
-			title = "Crumbling, No Respawn, Int. from Bottom, Translucent";
-			prefix = "(175)";
-		}
-		176
-		{
-			title = "Crumbling, Respawn, Floating, Bobbing";
-			prefix = "(176)";
-		}
-		177
-		{
-			title = "Crumbling, No Respawn, Floating, Bobbing";
-			prefix = "(177)";
-		}
-		178
-		{
-			title = "Crumbling, Respawn, Floating";
-			prefix = "(178)";
-		}
-		179
-		{
-			title = "Crumbling, No Respawn, Floating";
-			prefix = "(179)";
-		}
-		180
-		{
-			title = "Crumbling, Respawn, Air Bobbing";
-			prefix = "(180)";
-		}
-	}
-
-	fofspecial
-	{
-		title = "FOF (special)";
-
-		200
-		{
-			title = "Light Block";
-			prefix = "(200)";
-		}
-		201
-		{
-			title = "Half Light Block";
-			prefix = "(201)";
-		}
-		202
-		{
-			title = "Fog Block";
-			prefix = "(202)";
-		}
-		250
-		{
-			title = "Mario Block";
-			prefix = "(250)";
-		}
-		251
-		{
-			title = "Thwomp Block";
-			prefix = "(251)";
-		}
-		252
-		{
-			title = "Shatter Block";
-			prefix = "(252)";
-		}
-		253
-		{
-			title = "Shatter Block, Translucent";
-			prefix = "(253)";
-		}
-		254
-		{
-			title = "Bustable Block";
-			prefix = "(254)";
-		}
-		255
-		{
-			title = "Spin-Bustable Block";
-			prefix = "(255)";
-		}
-		256
-		{
-			title = "Spin-Bustable Block, Translucent";
-			prefix = "(256)";
-		}
-		257
-		{
-			title = "Quicksand";
-			prefix = "(257)";
-		}
-		258
-		{
-			title = "Laser";
-			prefix = "(258)";
-		}
-		259
-		{
-			title = "Custom FOF";
-			prefix = "(259)";
-		}
-	}
-
-	linedeftrigger
-	{
-		title = "Linedef Executor Trigger";
-
-		300
-		{
-			title = "Continuous";
-			prefix = "(300)";
-		}
-		301
-		{
-			title = "Each Time";
-			prefix = "(301)";
-		}
-		302
-		{
-			title = "Once";
-			prefix = "(302)";
-		}
-		303
-		{
-			title = "Ring Count - Continuous";
-			prefix = "(303)";
-		}
-		304
-		{
-			title = "Ring Count - Once";
-			prefix = "(304)";
-		}
-		305
-		{
-			title = "Character Ability - Continuous";
-			prefix = "(305)";
-		}
-		306
-		{
-			title = "Character Ability - Each Time";
-			prefix = "(306)";
-		}
-		307
-		{
-			title = "Character Ability - Once";
-			prefix = "(307)";
-		}
-		308
-		{
-			title = "Race Only - Once";
-			prefix = "(308)";
-		}
-		309
-		{
-			title = "CTF Red Team - Continuous";
-			prefix = "(309)";
-		}
-		310
-		{
-			title = "CTF Red Team - Each Time";
-			prefix = "(310)";
-		}
-		311
-		{
-			title = "CTF Blue Team - Continuous";
-			prefix = "(311)";
-		}
-		312
-		{
-			title = "CTF Blue Team - Each Time";
-			prefix = "(312)";
-		}
-		313
-		{
-			title = "No More Enemies - Once";
-			prefix = "(313)";
-		}
-		314
-		{
-			title = "Number of Pushables - Continuous";
-			prefix = "(314)";
-		}
-		315
-		{
-			title = "Number of Pushables - Once";
-			prefix = "(315)";
-		}
-		317
-		{
-			title = "Condition Set Trigger - Continuous";
-			prefix = "(317)";
-		}
-		318
-		{
-			title = "Condition Set Trigger - Once";
-			prefix = "(318)";
-		}
-		319
-		{
-			title = "Unlockable - Continuous";
-			prefix = "(319)";
-		}
-		320
-		{
-			title = "Unlockable - Once";
-			prefix = "(320)";
-		}
-		321
-		{
-			title = "Trigger After X Calls - Continuous";
-			prefix = "(321)";
-		}
-		322
-		{
-			title = "Trigger After X Calls - Each Time";
-			prefix = "(322)";
-		}
-		323
-		{
-			title = "NiGHTSerize - Each Time";
-			prefix = "(323)";
-		}
-		324
-		{
-			title = "NiGHTSerize - Once";
-			prefix = "(324)";
-		}
-		325
-		{
-			title = "De-NiGHTSerize - Each Time";
-			prefix = "(325)";
-		}
-		326
-		{
-			title = "De-NiGHTSerize - Once";
-			prefix = "(326)";
-		}
-		327
-		{
-			title = "NiGHTS Lap - Each Time";
-			prefix = "(327)";
-		}
-		328
-		{
-			title = "NiGHTS Lap - Once";
-			prefix = "(328)";
-		}
-		329
-		{
-			title = "Ideya Capture Touch - Each Time";
-			prefix = "(329)";
-		}
-		330
-		{
-			title = "Ideya Capture Touch - Once";
-			prefix = "(330)";
-		}
-		331
-		{
-			title = "Player Skin - Continuous";
-			flags64text = "[6] Disable for this skin";
-			prefix = "(331)";
-		}
-		332
-		{
-			title = "Player Skin - Each Time";
-			prefix = "(332)";
-		}
-		333
-		{
-			title = "Player Skin - Once";
-			prefix = "(333)";
-		}
-		334
-		{
-			title = "Object Dye - Continuous";
-			prefix = "(334)";
-		}
-		335
-		{
-			title = "Object Dye - Each Time";
-			prefix = "(335)";
-		}
-		336
-		{
-			title = "Object Dye - Once";
-			prefix = "(336)";
-		}
-		337
-		{
-			title = "Emerald Check - Continuous";
-			prefix = "(337)";
-		}
-		338
-		{
-			title = "Emerald Check - Each Time";
-			prefix = "(338)";
-		}
-		339
-		{
-			title = "Emerald Check - Once";
-			prefix = "(339)";
-		}
-		340
-		{
-			title = "NiGHTS Mare - Continuous";
-			prefix = "(340)";
-		}
-		341
-		{
-			title = "NiGHTS Mare - Each Time";
-			prefix = "(341)";
-		}
-		342
-		{
-			title = "NiGHTS Mare - Once";
-			prefix = "(342)";
-		}
-		343
-		{
-			title = "Gravity Check - Continuous";
-			prefix = "(343)";
-		}
-		344
-		{
-			title = "Gravity Check - Each Time";
-			prefix = "(344)";
-		}
-		345
-		{
-			title = "Gravity Check - Once";
-			prefix = "(345)";
-		}
-		399
-		{
-			title = "Level Load";
-			prefix = "(399)";
-		}
-	}
-
-	linedefexecsector
-	{
-		title = "Linedef Executor (sector)";
-
-		400
-		{
-			title = "Set Tagged Sector's Floor Height/Texture";
-			prefix = "(400)";
-		}
-		401
-		{
-			title = "Set Tagged Sector's Ceiling Height/Texture";
-			prefix = "(401)";
-		}
-		402
-		{
-			title = "Copy Light Level to Tagged Sectors";
-			prefix = "(402)";
-		}
-		408
-		{
-			title = "Set Tagged Sector's Flats";
-			prefix = "(408)";
-		}
-		409
-		{
-			title = "Change Tagged Sector's Tag";
-			prefix = "(409)";
-		}
-		410
-		{
-			title = "Change Front Sector's Tag";
-			prefix = "(410)";
-		}
-		416
-		{
-			title = "Start Adjustable Flickering Light";
-			prefix = "(416)";
-		}
-		417
-		{
-			title = "Start Adjustable Pulsating Light";
-			prefix = "(417)";
-		}
-		418
-		{
-			title = "Start Adjustable Blinking Light (unsynchronized)";
-			prefix = "(418)";
-		}
-		419
-		{
-			title = "Start Adjustable Blinking Light (synchronized)";
-			prefix = "(419)";
-		}
-		420
-		{
-			title = "Fade Light Level";
-			prefix = "(420)";
-		}
-		421
-		{
-			title = "Stop Lighting Effect";
-			prefix = "(421)";
-		}
-		435
-		{
-			title = "Change Plane Scroller Direction";
-			prefix = "(435)";
-		}
-		467
-		{
-			title = "Set Tagged Sector's Light Level";
-			prefix = "(467)";
-		}
-	}
-
-	linedefexecplane
-	{
-		title = "Linedef Executor (plane movement)";
-
-		403
-		{
-			title = "Move Tagged Sector's Floor";
-			prefix = "(403)";
-		}
-		404
-		{
-			title = "Move Tagged Sector's Ceiling";
-			prefix = "(404)";
-		}
-		405
-		{
-			title = "Move Floor According to Front Texture Offsets";
-			prefix = "(405)";
-		}
-		407
-		{
-			title = "Move Ceiling According to Front Texture Offsets";
-			prefix = "(407)";
-		}
-		411
-		{
-			title = "Stop Plane Movement";
-			prefix = "(411)";
-		}
-		428
-		{
-			title = "Start Platform Movement";
-			prefix = "(428)";
-		}
-		429
-		{
-			title = "Crush Ceiling Once";
-			prefix = "(429)";
-		}
-		430
-		{
-			title = "Crush Floor Once";
-			prefix = "(430)";
-		}
-		431
-		{
-			title = "Crush Floor and Ceiling Once";
-			prefix = "(431)";
-		}
-	}
-
-	linedefexecplayer
-	{
-		title = "Linedef Executor (player/object)";
-
-		412
-		{
-			title = "Teleporter";
-			prefix = "(412)";
-		}
-		425
-		{
-			title = "Change Object State";
-			prefix = "(425)";
-		}
-		426
-		{
-			title = "Stop Object";
-			prefix = "(426)";
-		}
-		427
-		{
-			title = "Award Score";
-			prefix = "(427)";
-		}
-		432
-		{
-			title = "Enable/Disable 2D Mode";
-			prefix = "(432)";
-		}
-		433
-		{
-			title = "Enable/Disable Gravity Flip";
-			prefix = "(433)";
-		}
-		434
-		{
-			title = "Award Power-Up";
-			prefix = "(434)";
-		}
-		437
-		{
-			title = "Disable Player Control";
-			prefix = "(437)";
-		}
-		438
-		{
-			title = "Change Object Size";
-			prefix = "(438)";
-		}
-		442
-		{
-			title = "Change Object Type State";
-			prefix = "(442)";
-		}
-		457
-		{
-			title = "Track Object's Angle";
-			prefix = "(457)";
-		}
-		458
-		{
-			title = "Stop Tracking Object's Angle";
-			prefix = "(458)";
-		}
-		460
-		{
-			title = "Award Rings";
-			prefix = "(460)";
-		}
-		461
-		{
-			title = "Spawn Object";
-			prefix = "(461)";
-		}
-		462
-		{
-			title = "Stop Timer/Exit Stage in Record Attack";
-			prefix = "(462)";
-		}
-		463
-		{
-			title = "Dye Object";
-			prefix = "(463)";
-		}
-		464
-		{
-			title = "Trigger Egg Capsule";
-			prefix = "(464)";
-		}
-		466
-		{
-			title = "Set Level Failure State";
-			prefix = "(466)";
-		}
-	}
-
-	linedefexecmisc
-	{
-		title = "Linedef Executor (misc.)";
-
-		413
-		{
-			title = "Change Music";
-			prefix = "(413)";
-		}
-		414
-		{
-			title = "Play Sound Effect";
-			prefix = "(414)";
-		}
-		415
-		{
-			title = "Run Script";
-			prefix = "(415)";
-		}
-		422
-		{
-			title = "Switch to Cut-Away View";
-			prefix = "(422)";
-		}
-		423
-		{
-			title = "Change Sky";
-			prefix = "(423)";
-		}
-		424
-		{
-			title = "Change Weather";
-			prefix = "(424)";
-		}
-		436
-		{
-			title = "Shatter FOF";
-			prefix = "(436)";
-		}
-		439
-		{
-			title = "Change Tagged Linedef's Textures";
-			prefix = "(439)";
-		}
-		440
-		{
-			title = "Start Metal Sonic Race";
-			prefix = "(440)";
-		}
-		441
-		{
-			title = "Condition Set Trigger";
-			prefix = "(441)";
-		}
-		443
-		{
-			title = "Call Lua Function";
-			prefix = "(443)";
-		}
-		444
-		{
-			title = "Earthquake";
-			prefix = "(444)";
-		}
-		445
-		{
-			title = "Make FOF Disappear/Reappear";
-			prefix = "(445)";
-		}
-		446
-		{
-			title = "Make FOF Crumble";
-			prefix = "(446)";
-		}
-		447
-		{
-			title = "Change Tagged Sector's Colormap";
-			prefix = "(447)";
-		}
-		448
-		{
-			title = "Change Skybox";
-			prefix = "(448)";
-		}
-		449
-		{
-			title = "Enable Bosses with Parameter";
-			prefix = "(449)";
-		}
-		450
-		{
-			title = "Execute Linedef Executor (specific tag)";
-			prefix = "(450)";
-		}
-		451
-		{
-			title = "Execute Linedef Executor (random tag in range)";
-			prefix = "(451)";
-		}
-		452
-		{
-			title = "Set FOF Translucency";
-			prefix = "(452)";
-		}
-		453
-		{
-			title = "Fade FOF";
-			prefix = "(453)";
-		}
-		454
-		{
-			title = "Stop Fading FOF";
-			prefix = "(454)";
-		}
-		455
-		{
-			title = "Fade Tagged Sector's Colormap";
-			prefix = "(455)";
-		}
-		456
-		{
-			title = "Stop Fading Tagged Sector's Colormap";
-			prefix = "(456)";
-		}
-		459
-		{
-			title = "Control Text Prompt";
-			prefix = "(459)";
-		}
-	}
-
-	linedefexecpoly
-	{
-		title = "Linedef Executor (polyobject)";
-
-		480
-		{
-			title = "Door Slide";
-			prefix = "(480)";
-		}
-		481
-		{
-			title = "Door Swing";
-			prefix = "(481)";
-		}
-		482
-		{
-			title = "Move";
-			prefix = "(482)";
-		}
-		483
-		{
-			title = "Move, Override";
-			prefix = "(483)";
-		}
-		484
-		{
-			title = "Rotate Right";
-			prefix = "(484)";
-		}
-		485
-		{
-			title = "Rotate Right, Override";
-			prefix = "(485)";
-		}
-		486
-		{
-			title = "Rotate Left";
-			prefix = "(486)";
-		}
-		487
-		{
-			title = "Rotate Left, Override";
-			prefix = "(487)";
-		}
-		488
-		{
-			title = "Move by Waypoints";
-			prefix = "(488)";
-		}
-		489
-		{
-			title = "Turn Invisible, Intangible";
-			prefix = "(489)";
-		}
-		490
-		{
-			title = "Turn Visible, Tangible";
-			prefix = "(490)";
-		}
-		491
-		{
-			title = "Set Translucency";
-			prefix = "(491)";
-		}
-		492
-		{
-			title = "Fade Translucency";
-			prefix = "(492)";
-		}
-	}
-
-	wallscroll
-	{
-		title = "Wall Scrolling";
-
-		500
-		{
-			title = "Scroll Wall Front Side Left";
-			prefix = "(500)";
-		}
-		501
-		{
-			title = "Scroll Wall Front Side Right";
-			prefix = "(501)";
-		}
-		502
-		{
-			title = "Scroll Wall According to Linedef";
-			prefix = "(502)";
-		}
-		503
-		{
-			title = "Scroll Wall According to Linedef (Accelerative)";
-			prefix = "(503)";
-		}
-		504
-		{
-			title = "Scroll Wall According to Linedef (Displacement)";
-			prefix = "(504)";
-		}
-		505
-		{
-			title = "Scroll Texture by Front Side Offsets";
-			prefix = "(505)";
-		}
-		506
-		{
-			title = "Scroll Texture by Back Side Offsets";
-			prefix = "(506)";
-		}
-	}
-
-	planescroll
-	{
-		title = "Plane Scrolling";
-
-		510
-		{
-			title = "Scroll Floor Texture";
-			prefix = "(510)";
-		}
-		511
-		{
-			title = "Scroll Floor Texture (Accelerative)";
-			prefix = "(511)";
-		}
-		512
-		{
-			title = "Scroll Floor Texture (Displacement)";
-			prefix = "(512)";
-		}
-		513
-		{
-			title = "Scroll Ceiling Texture";
-			prefix = "(513)";
-		}
-		514
-		{
-			title = "Scroll Ceiling Texture (Accelerative)";
-			prefix = "(514)";
-		}
-		515
-		{
-			title = "Scroll Ceiling Texture (Displacement)";
-			prefix = "(515)";
-		}
-		516
-		{
-			title = "Scroll Floor and Ceiling Texture";
-			prefix = "(516)";
-		}
-		517
-		{
-			title = "Scroll Floor and Ceiling Texture (Accelerative)";
-			prefix = "(517)";
-		}
-		518
-		{
-			title = "Scroll Floor and Ceiling Texture (Displacement)";
-			prefix = "(518)";
-		}
-		520
-		{
-			title = "Carry Objects on Floor";
-			prefix = "(520)";
-		}
-		521
-		{
-			title = "Carry Objects on Floor (Accelerative)";
-			prefix = "(521)";
-		}
-		522
-		{
-			title = "Carry Objects on Floor (Displacement)";
-			prefix = "(522)";
-		}
-		523
-		{
-			title = "Carry Objects on Ceiling";
-			prefix = "(523)";
-		}
-		524
-		{
-			title = "Carry Objects on Ceiling (Accelerative)";
-			prefix = "(524)";
-		}
-		525
-		{
-			title = "Carry Objects on Ceiling (Displacement)";
-			prefix = "(525)";
-		}
-		526
-		{
-			title = "Carry Objects on Floor and Ceiling";
-			prefix = "(526)";
-		}
-		527
-		{
-			title = "Carry Objects on Floor and Ceiling (Accelerative)";
-			prefix = "(527)";
-		}
-		528
-		{
-			title = "Carry Objects on Floor and Ceiling (Displacement)";
-			prefix = "(528)";
-		}
-		530
-		{
-			title = "Scroll Floor Texture and Carry Objects";
-			prefix = "(530)";
-		}
-		531
-		{
-			title = "Scroll Floor Texture and Carry Objects (Accelerative)";
-			prefix = "(531)";
-		}
-		532
-		{
-			title = "Scroll Floor Texture and Carry Objects (Displacement)";
-			prefix = "(532)";
-		}
-		533
-		{
-			title = "Scroll Ceiling Texture and Carry Objects";
-			prefix = "(533)";
-		}
-		534
-		{
-			title = "Scroll Ceiling Texture and Carry Objects (Accelerative)";
-			prefix = "(534)";
-		}
-		535
-		{
-			title = "Scroll Ceiling Texture and Carry Objects (Displacement)";
-			prefix = "(535)";
-		}
-		536
-		{
-			title = "Scroll Floor and Ceiling Texture and Carry Objects";
-			prefix = "(536)";
-		}
-		537
-		{
-			title = "Scroll Floor and Ceiling Texture and Carry Objects (Accelerative)";
-			prefix = "(537)";
-		}
-		538
-		{
-			title = "Scroll Floor and Ceiling Texture and Carry Objects (Displacement)";
-			prefix = "(538)";
-		}
-	}
-
-	pusher
-	{
-		title = "Pusher";
-
-		541
-		{
-			title = "Wind";
-			prefix = "(541)";
-		}
-		542
-		{
-			title = "Upwards Wind";
-			prefix = "(542)";
-		}
-		543
-		{
-			title = "Downwards Wind";
-			prefix = "(543)";
-		}
-		544
-		{
-			title = "Current";
-			prefix = "(544)";
-		}
-		545
-		{
-			title = "Upwards Current";
-			prefix = "(545)";
-		}
-		546
-		{
-			title = "Downwards Current";
-			prefix = "(546)";
-		}
-		547
-		{
-			title = "Push/Pull";
-			prefix = "(547)";
-		}
-	}
-
-	light
-	{
-		title = "Lighting";
-
-		600
-		{
-			title = "Floor Lighting";
-			prefix = "(600)";
-		}
-		601
-		{
-			title = "Ceiling Lighting";
-			prefix = "(601)";
-		}
-		602
-		{
-			title = "Adjustable Pulsating Light";
-			prefix = "(602)";
-		}
-		603
-		{
-			title = "Adjustable Flickering Light";
-			prefix = "(603)";
-		}
-		604
-		{
-			title = "Adjustable Blinking Light (unsynchronized)";
-			prefix = "(604)";
-		}
-		605
-		{
-			title = "Adjustable Blinking Light (synchronized)";
-			prefix = "(605)";
-		}
-		606
-		{
-			title = "Colormap";
-			prefix = "(606)";
-		}
-	}
-
-	slope
-	{
-		title = "Slope";
-
-		700
-		{
-			title = "Slope Frontside Floor";
-			prefix = "(700)";
-		}
-		701
-		{
-			title = "Slope Frontside Ceiling";
-			prefix = "(701)";
-		}
-		702
-		{
-			title = "Slope Frontside Floor and Ceiling";
-			prefix = "(702)";
-		}
-		703
-		{
-			title = "Slope Frontside Floor and Backside Ceiling";
-			prefix = "(703)";
-ยด		}
-		704
-		{
-			title = "Slope Frontside Floor by 3 Tagged Vertex Things";
-			prefix = "(704)";
-		}
-		705
-		{
-			title = "Slope Frontside Ceiling by 3 Tagged Vertex Things";
-			prefix = "(705)";
-		}
-		710
-		{
-			title = "Slope Backside Floor";
-			prefix = "(710)";
-		}
-		711
-		{
-			title = "Slope Backside Ceiling";
-			prefix = "(711)";
-		}
-		712
-		{
-			title = "Slope Backside Floor and Ceiling";
-			prefix = "(712)";
-		}
-		713
-		{
-			title = "Slope Backside Floor and Frontside Ceiling";
-			prefix = "(713)";
-		}
-		714
-		{
-			title = "Slope Backside Floor by 3 Tagged Vertex Things";
-			prefix = "(714)";
-		}
-		715
-		{
-			title = "Slope Backside Ceiling by 3 Tagged Vertex Things";
-			prefix = "(715)";
-		}
-		720
-		{
-			title = "Copy Frontside Floor Slope from Line Tag";
-			prefix = "(720)";
-		}
-		721
-		{
-			title = "Copy Frontside Ceiling Slope from Line Tag";
-			prefix = "(721)";
-		}
-		722
-		{
-			title = "Copy Frontside Floor and Ceiling Slope from Line Tag";
-			prefix = "(722)";
-		}
-		799
-		{
-			title = "Set Tagged Dynamic Slope Vertex to Front Sector Height";
-			prefix = "(799)";
-		}
-	}
-
-	transwall
-	{
-		title = "Translucent Wall";
-
-		900
-		{
-			title = "90% Opaque";
-			prefix = "(900)";
-		}
-		901
-		{
-			title = "80% Opaque";
-			prefix = "(901)";
-		}
-		902
-		{
-			title = "70% Opaque";
-			prefix = "(902)";
-		}
-		903
-		{
-			title = "60% Opaque";
-			prefix = "(903)";
-		}
-		904
-		{
-			title = "50% Opaque";
-			prefix = "(904)";
-		}
-		905
-		{
-			title = "40% Opaque";
-			prefix = "(905)";
-		}
-		906
-		{
-			title = "30% Opaque";
-			prefix = "(906)";
-		}
-		907
-		{
-			title = "20% Opaque";
-			prefix = "(907)";
-		}
-		908
-		{
-			title = "10% Opaque";
-			prefix = "(908)";
-		}
-		909
-		{
-			title = "Fog Wall";
-			prefix = "(909)";
-		}
-		910
-		{
-			title = "100% Additive";
-			prefix = "(910)";
-		}
-		911
-		{
-			title = "90% Additive";
-			prefix = "(911)";
-		}
-		912
-		{
-			title = "80% Additive";
-			prefix = "(912)";
-		}
-		913
-		{
-			title = "70% Additive";
-			prefix = "(913)";
-		}
-		914
-		{
-			title = "60% Additive";
-			prefix = "(914)";
-		}
-		915
-		{
-			title = "50% Additive";
-			prefix = "(915)";
-		}
-		916
-		{
-			title = "40% Additive";
-			prefix = "(916)";
-		}
-		917
-		{
-			title = "30% Additive";
-			prefix = "(917)";
-		}
-		918
-		{
-			title = "20% Additive";
-			prefix = "(918)";
-		}
-		919
-		{
-			title = "10% Additive";
-			prefix = "(919)";
-		}
-		920
-		{
-			title = "100% Subtractive";
-			prefix = "(920)";
-		}
-		921
-		{
-			title = "90% Subtractive";
-			prefix = "(921)";
-		}
-		922
-		{
-			title = "80% Subtractive";
-			prefix = "(922)";
-		}
-		923
-		{
-			title = "70% Subtractive";
-			prefix = "(923)";
-		}
-		924
-		{
-			title = "60% Subtractive";
-			prefix = "(924)";
-		}
-		925
-		{
-			title = "50% Subtractive";
-			prefix = "(925)";
-		}
-		926
-		{
-			title = "40% Subtractive";
-			prefix = "(926)";
-		}
-		927
-		{
-			title = "30% Subtractive";
-			prefix = "(927)";
-		}
-		928
-		{
-			title = "20% Subtractive";
-			prefix = "(928)";
-		}
-		929
-		{
-			title = "10% Subtractive";
-			prefix = "(929)";
-		}
-		930
-		{
-			title = "100% Reverse Subtractive";
-			prefix = "(930)";
-		}
-		931
-		{
-			title = "90% Reverse Subtractive";
-			prefix = "(931)";
-		}
-		932
-		{
-			title = "80% Reverse Subtractive";
-			prefix = "(932)";
-		}
-		933
-		{
-			title = "70% Reverse Subtractive";
-			prefix = "(933)";
-		}
-		934
-		{
-			title = "60% Reverse Subtractive";
-			prefix = "(934)";
-		}
-		935
-		{
-			title = "50% Reverse Subtractive";
-			prefix = "(935)";
-		}
-		936
-		{
-			title = "40% Reverse Subtractive";
-			prefix = "(936)";
-		}
-		937
-		{
-			title = "30% Reverse Subtractive";
-			prefix = "(937)";
-		}
-		938
-		{
-			title = "20% Reverse Subtractive";
-			prefix = "(938)";
-		}
-		939
-		{
-			title = "10% Reverse Subtractive";
-			prefix = "(939)";
-		}
-		940
-		{
-			title = "Modulate";
-			prefix = "(940)";
-		}
-	}
-}
-
 udmf
 {
 	misc
@@ -2502,6 +712,7 @@ udmf
 		{
 			title = "Solid";
 			prefix = "(100)";
+			id = "srb2_fofsolid";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2544,6 +755,7 @@ udmf
 		{
 			title = "Water";
 			prefix = "(120)";
+			id = "srb2_fofwater";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2580,6 +792,7 @@ udmf
 		{
 			title = "Air Bobbing";
 			prefix = "(150)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2606,6 +819,7 @@ udmf
 		{
 			title = "Water Bobbing";
 			prefix = "(160)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2617,6 +831,7 @@ udmf
 		{
 			title = "Crumbling";
 			prefix = "(170)";
+			id = "srb2_fofcrumbling";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2653,11 +868,12 @@ udmf
 				}
 			}
 		}
-
+		
 		190
 		{
 			title = "Rising";
 			prefix = "(190)";
+			id = "srb2_fofsolid";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2714,6 +930,7 @@ udmf
 		{
 			title = "Light Block";
 			prefix = "(200)";
+			id = "srb2_foflight";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2731,6 +948,7 @@ udmf
 		{
 			title = "Fog Block";
 			prefix = "(202)";
+			id = "srb2_foffog";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2742,6 +960,7 @@ udmf
 		{
 			title = "Intangible";
 			prefix = "(220)";
+			id = "srb2_fofintangible";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2778,6 +997,7 @@ udmf
 		{
 			title = "Intangible, Invisible";
 			prefix = "(223)";
+			id = "srb2_fofintangibleinvisible";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2789,6 +1009,7 @@ udmf
 		{
 			title = "Mario Block";
 			prefix = "(250)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2810,6 +1031,7 @@ udmf
 		{
 			title = "Thwomp Block";
 			prefix = "(251)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2834,6 +1056,7 @@ udmf
 		{
 			title = "Bustable Block";
 			prefix = "(254)";
+			id = "srb2_fofbustable";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2885,6 +1108,7 @@ udmf
 		{
 			title = "Quicksand";
 			prefix = "(257)";
+			id = "srb2_fofsolidopaque";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2910,6 +1134,7 @@ udmf
 		{
 			title = "Laser";
 			prefix = "(258)";
+			id = "srb2_foflaser";
 			arg0
 			{
 				title = "Target sector tag";
@@ -2942,6 +1167,7 @@ udmf
 		{
 			title = "Custom";
 			prefix = "(259)";
+			id = "srb2_fofcustom";
 			arg0
 			{
 				title = "Target sector tag";
@@ -5686,6 +3912,7 @@ udmf
 		{
 			title = "Create Vertex-Based Slope";
 			prefix = "(704)";
+			id = "srb2_vertexslope";
 			arg0
 			{
 				title = "Plane";
@@ -5729,6 +3956,7 @@ udmf
 		{
 			title = "Copy Slope";
 			prefix = "(720)";
+			id = "plane_copy";
 			arg0
 			{
 				title = "Front floor tag";
diff --git a/extras/conf/udb/Includes/SRB222_misc.cfg b/extras/conf/udb/Includes/SRB222_misc.cfg
index ed0488a3ff872aa28e5262ee876ccd2b53ce1397..e274fece69ea37d4be43cd8b7560fde69e181ee5 100644
--- a/extras/conf/udb/Includes/SRB222_misc.cfg
+++ b/extras/conf/udb/Includes/SRB222_misc.cfg
@@ -1,24 +1,3 @@
-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
@@ -42,7 +21,6 @@ linedefflagstranslation
 	32768 = "transfer";
 }
 
-
 linedefflags_udmf
 {
 	blocking = "Impassable";
@@ -74,19 +52,13 @@ linedefrenderstyles
 
 sectorflags
 {
-	colormapfog = "Fog Planes in Colormap";
-	colormapfadesprites = "Fade Fullbright in Colormap";
-	colormapprotected = "Protected Colormap";
-	flipspecial_nofloor = "No Trigger on Floor Touch";
-	flipspecial_ceiling = "Trigger on Ceiling Touch";
-	triggerspecial_touch = "Trigger on Edge Touch";
-	triggerspecial_headbump = "Trigger on Headbump";
-	triggerline_plane = "Linedef Trigger Requires Plane Touch";
-	triggerline_mobj = "Non-Pushables Can Trigger Linedef";
 	invertprecip = "Invert Precipitation";
 	gravityflip = "Flip Objects in Reverse Gravity";
 	heatwave = "Heat Wave";
 	noclipcamera = "Intangible to the Camera";
+	colormapfog = "Fog Planes";
+	colormapfadesprites = "Fade Fullbright";
+	colormapprotected = "Protected from Tagging";
 	outerspace = "Space Countdown";
 	doublestepup = "Ramp Sector (double step-up/down)";
 	nostepdown = "Non-Ramp Sector (No step-down)";
@@ -104,23 +76,59 @@ sectorflags
 	zoomtubeend = "Zoom Tube End";
 	finishline = "Circuit Finish Line";
 	ropehang = "Rope Hang";
+	jumpflip = "Flip Gravity on Jump";
+	gravityoverride = "Make Reverse Gravity Temporary";
+	flipspecial_nofloor = "No Trigger on Floor Touch";
+	flipspecial_ceiling = "Trigger on Ceiling Touch";
+	triggerspecial_touch = "Trigger on Edge Touch";
+	triggerspecial_headbump = "Trigger on Headbump";
+	triggerline_plane = "Linedef Trigger Requires Plane Touch";
+	triggerline_mobj = "Non-Pushables Can Trigger Linedef";
 }
 
-thingflags
+sectorflagscategories
 {
-	1 = "[1] Extra";
-	2 = "[2] Flip";
-	4 = "[4] Special";
-	8 = "[8] Ambush";
+	invertprecip = "regular";
+	gravityflip = "regular";
+	heatwave = "regular";
+	noclipcamera = "regular";
+	colormapfog = "colormap";
+	colormapfadesprites = "colormap";
+	colormapprotected = "colormap";
+	outerspace = "special";
+	doublestepup = "special";
+	nostepdown = "special";
+	speedpad = "special";
+	starpostactivator = "special";
+	exit = "special";
+	specialstagepit = "special";
+	returnflag = "special";
+	redteambase = "special";
+	blueteambase = "special";
+	fan = "special";
+	supertransform = "special";
+	forcespin = "special";
+	zoomtubestart = "special";
+	zoomtubeend = "special";
+	finishline = "special";
+	ropehang = "special";
+	jumpflip = "special";
+	gravityoverride = "special";
+	flipspecial_nofloor = "trigger";
+	flipspecial_ceiling = "trigger";
+	triggerspecial_touch = "trigger";
+	triggerspecial_headbump = "trigger";
+	triggerline_plane = "trigger";
+	triggerline_mobj = "trigger";
 }
 
 // THING FLAGS
 thingflags_udmf
 {
 	flip = "Flip";
+	absolutez = "Absolute Z height";
 }
 
-
 // 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
@@ -130,9 +138,9 @@ thingflagstranslation
 	2 = "flip";
 	4 = "special";
 	8 = "ambush";
+	16 = "absolutez";
 }
 
-
 // DEFAULT SECTOR BRIGHTNESS LEVELS
 sectorbrightness
 {
@@ -171,6 +179,8 @@ sectorbrightness
 	0;
 }
 
+numbrightnesslevels = 32;
+
 /*
 TEXTURES AND FLAT SOURCES
 This tells Doom Builder where to find the information for textures
@@ -221,145 +231,18 @@ universalfields
 {
 	sector
 	{
-		lightalpha
-		{
-			type = 0;
-			default = 25;
-		}
-
-		fadealpha
-		{
-			type = 0;
-			default = 25;
-		}
-
-		fadestart
-		{
-			type = 0;
-			default = 0;
-		}
-
-		fadeend
-		{
-			type = 0;
-			default = 33;
-		}
-
-		foglighting
-		{
-			type = 3;
-			default = false;
-		}
-
-		friction
-		{
-			type = 1;
-			default = 0.90625;
-		}
-
-		triggertag
-		{
-			type = 15;
-			default = 0;
-		}
-
-		triggerer
-		{
-			type = 2;
-			default = "Player";
-		}
 	}
 
 	linedef
 	{
-		arg5
-		{
-			type = 0;
-			default = 0;
-		}
-		arg6
-		{
-			type = 0;
-			default = 0;
-		}
-		arg7
-		{
-			type = 0;
-			default = 0;
-		}
-		arg8
-		{
-			type = 0;
-			default = 0;
-		}
-		arg9
-		{
-			type = 0;
-			default = 0;
-		}
-		stringarg0
-		{
-			type = 2;
-			default = "";
-		}
-		stringarg1
-		{
-			type = 2;
-			default = "";
-		}
-		executordelay
-		{
-			type = 0;
-			default = 0;
-		}
 	}
 
 	sidedef
 	{
-		repeatcnt
-		{
-			type = 0;
-			default = 0;
-		}
 	}
 
 	thing
 	{
-		arg5
-		{
-			type = 0;
-			default = 0;
-		}
-		arg6
-		{
-			type = 0;
-			default = 0;
-		}
-		arg7
-		{
-			type = 0;
-			default = 0;
-		}
-		arg8
-		{
-			type = 0;
-			default = 0;
-		}
-		arg9
-		{
-			type = 0;
-			default = 0;
-		}
-		stringarg0
-		{
-			type = 2;
-			default = "";
-		}
-		stringarg1
-		{
-			type = 2;
-			default = "";
-		}
 	}
 }
 
@@ -378,87 +261,6 @@ 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
@@ -682,48 +484,32 @@ thingsfilters
 
 	}
 
-
-	filter3
-	{
-		name = "Normal Gravity";
-		category = "";
-		type = -1;
-
-		fields
-		{
-			2 = false;
-		}
-
-	}
-
-
-	filter4
-	{
-		name = "Reverse Gravity";
-		category = "";
-		type = -1;
-
-		fields
-		{
-			2 = true;
-		}
-
-	}
+	//filter3
+	//{
+	//	name = "Normal Gravity";
+	//	category = "";
+	//	type = -1;
+	//
+	//	fields
+	//	{
+	//		2 = false;
+	//	}
+	//}
+
+	//filter4
+	//{
+	//	name = "Reverse Gravity";
+	//	category = "";
+	//	type = -1;
+	//
+	//	fields
+	//	{
+	//		2 = true;
+	//	}
+	//}
 }
 
 // Special linedefs
-speciallinedefs
-{
-	soundlinedefflag = 64;	// See linedefflags
-	singlesidedflag = 1;	// See linedefflags
-	doublesidedflag = 4;	// See linedefflags
-	impassableflag = 1;
-	upperunpeggedflag = 8;
-	lowerunpeggedflag = 16;
-	repeatmidtextureflag = 1024;
-	pegmidtextureflag = 256;
-}
-
 speciallinedefs_udmf
 {
 	soundlinedefflag = "noclimb";
@@ -734,6 +520,8 @@ speciallinedefs_udmf
 	lowerunpeggedflag = "dontpegbottom";
 	repeatmidtextureflag = "wrapmidtex";
 	pegmidtextureflag = "midpeg";
+	slopeskewflag = "skewtd";
+	nomidtextureskewflag = "noskew";
 }
 
 scriptlumpnames
diff --git a/extras/conf/udb/Includes/SRB222_sectors.cfg b/extras/conf/udb/Includes/SRB222_sectors.cfg
deleted file mode 100644
index 5b3ad4155c176b229eee741b9a1744121b68d4ae..0000000000000000000000000000000000000000
--- a/extras/conf/udb/Includes/SRB222_sectors.cfg
+++ /dev/null
@@ -1,107 +0,0 @@
-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 <deprecated>";
-	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) <deprecated>";
-	112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
-	128 = "Check for Linedef Executor on FOFs";
-	144 = "Egg Capsule";
-	160 = "Special Stage Time/Spheres Parameters <deprecated>";
-	176 = "Custom Global Gravity <deprecated>";
-	1280 = "Speed Pad";
-	1536 = "Flip Gravity on Jump";
-	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 <deprecated>";
-	}
-
-	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) <deprecated>";
-		112 = "Trigger Line Ex. (NiGHTS Mare) <deprecated>";
-		128 = "Check for Linedef Executor on FOFs";
-		144 = "Egg Capsule";
-		160 = "Special Stage Time/Spheres Parameters <deprecated>";
-		176 = "Custom Global Gravity <deprecated>";
-	}
-
-	third
-	{
-		0 = "Normal";
-		1280 = "Speed Pad";
-		1536 = "Flip Gravity on Jump";
-	}
-
-	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";
-	}
-}
diff --git a/extras/conf/udb/Includes/SRB222_things.cfg b/extras/conf/udb/Includes/SRB222_things.cfg
index b4508c91ea568aecc702ad1bf9927f079e820bd1..df08e3ac50fb47c80b412da2966e10cc2cec5f79 100644
--- a/extras/conf/udb/Includes/SRB222_things.cfg
+++ b/extras/conf/udb/Includes/SRB222_things.cfg
@@ -3,3175 +3,8 @@
 // 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
 
-doom
-{
-	editor
-	{
-		color = 15; // White
-		arrow = 1;
-		title = "<Editor Things>";
-		error = -1;
-		width = 8;
-		height = 16;
-		sort = 1;
-
-		3328 = "3D Mode Start";
-	}
-
-	starts
-	{
-		color = 1; // Blue
-		arrow = 1;
-		title = "Player Starts";
-		width = 16;
-		height = 48;
-		sprite = "PLAYA0";
-
-		1
-		{
-			title = "Player 01 Start";
-			sprite = "PLAYA0";
-		}
-		2
-		{
-			title = "Player 02 Start";
-			sprite = "PLAYA0";
-		}
-		3
-		{
-			title = "Player 03 Start";
-			sprite = "PLAYA0";
-		}
-		4
-		{
-			title = "Player 04 Start";
-			sprite = "PLAYA0";
-		}
-		5
-		{
-			title = "Player 05 Start";
-			sprite = "PLAYA0";
-		}
-		6
-		{
-			title = "Player 06 Start";
-			sprite = "PLAYA0";
-		}
-		7
-		{
-			title = "Player 07 Start";
-			sprite = "PLAYA0";
-		}
-		8
-		{
-			title = "Player 08 Start";
-			sprite = "PLAYA0";
-		}
-		9
-		{
-			title = "Player 09 Start";
-			sprite = "PLAYA0";
-		}
-		10
-		{
-			title = "Player 10 Start";
-			sprite = "PLAYA0";
-		}
-		11
-		{
-			title = "Player 11 Start";
-			sprite = "PLAYA0";
-		}
-		12
-		{
-			title = "Player 12 Start";
-			sprite = "PLAYA0";
-		}
-		13
-		{
-			title = "Player 13 Start";
-			sprite = "PLAYA0";
-		}
-		14
-		{
-			title = "Player 14 Start";
-			sprite = "PLAYA0";
-		}
-		15
-		{
-			title = "Player 15 Start";
-			sprite = "PLAYA0";
-		}
-		16
-		{
-			title = "Player 16 Start";
-			sprite = "PLAYA0";
-		}
-		17
-		{
-			title = "Player 17 Start";
-			sprite = "PLAYA0";
-		}
-		18
-		{
-			title = "Player 18 Start";
-			sprite = "PLAYA0";
-		}
-		19
-		{
-			title = "Player 19 Start";
-			sprite = "PLAYA0";
-		}
-		20
-		{
-			title = "Player 20 Start";
-			sprite = "PLAYA0";
-		}
-		21
-		{
-			title = "Player 21 Start";
-			sprite = "PLAYA0";
-		}
-		22
-		{
-			title = "Player 22 Start";
-			sprite = "PLAYA0";
-		}
-		23
-		{
-			title = "Player 23 Start";
-			sprite = "PLAYA0";
-		}
-		24
-		{
-			title = "Player 24 Start";
-			sprite = "PLAYA0";
-		}
-		25
-		{
-			title = "Player 25 Start";
-			sprite = "PLAYA0";
-		}
-		26
-		{
-			title = "Player 26 Start";
-			sprite = "PLAYA0";
-		}
-		27
-		{
-			title = "Player 27 Start";
-			sprite = "PLAYA0";
-		}
-		28
-		{
-			title = "Player 28 Start";
-			sprite = "PLAYA0";
-		}
-		29
-		{
-			title = "Player 29 Start";
-			sprite = "PLAYA0";
-		}
-		30
-		{
-			title = "Player 30 Start";
-			sprite = "PLAYA0";
-		}
-		31
-		{
-			title = "Player 31 Start";
-			sprite = "PLAYA0";
-		}
-		32
-		{
-			title = "Player 32 Start";
-			sprite = "PLAYA0";
-		}
-		33
-		{
-			title = "Match Start";
-			sprite = "NDRNA2A8";
-		}
-		34
-		{
-			title = "CTF Red Team Start";
-			sprite = "SIGNG0";
-		}
-		35
-		{
-			title = "CTF Blue Team Start";
-			sprite = "SIGNE0";
-		}
-	}
-
-	enemies
-	{
-		color = 9; // Light_Blue
-		arrow = 1;
-		title = "Enemies";
-
-		100
-		{
-			title = "Crawla (Blue)";
-			sprite = "POSSA1";
-			width = 24;
-			height = 32;
-		}
-		101
-		{
-			title = "Crawla (Red)";
-			sprite = "SPOSA1";
-			width = 24;
-			height = 32;
-		}
-		102
-		{
-			title = "Stupid Dumb Unnamed RoboFish";
-			sprite = "FISHA0";
-			width = 8;
-			height = 28;
-		}
-		103
-		{
-			title = "Buzz (Gold)";
-			sprite = "BUZZA1";
-			width = 28;
-			height = 40;
-		}
-		104
-		{
-			title = "Buzz (Red)";
-			sprite = "RBUZA1";
-			width = 28;
-			height = 40;
-		}
-		108
-		{
-			title = "Deton";
-			sprite = "DETNA1";
-			width = 20;
-			height = 32;
-		}
-		110
-		{
-			title = "Turret";
-			sprite = "TRETA1";
-			width = 16;
-			height = 32;
-		}
-		111
-		{
-			title = "Pop-up Turret";
-			sprite = "TURRI1";
-			width = 12;
-			height = 64;
-		}
-		122
-		{
-			title = "Spring Shell (Green)";
-			sprite = "SSHLA1";
-			width = 24;
-			height = 40;
-		}
-		125
-		{
-			title = "Spring Shell (Yellow)";
-			sprite = "SSHLI1";
-			width = 24;
-			height = 40;
-		}
-		109
-		{
-			title = "Skim";
-			sprite = "SKIMA1";
-			width = 16;
-			height = 24;
-		}
-		113
-		{
-			title = "Jet Jaw";
-			sprite = "JJAWA3A7";
-			width = 12;
-			height = 20;
-		}
-		126
-		{
-			title = "Crushstacean";
-			sprite = "CRABA0";
-			width = 24;
-			height = 32;
-		}
-		138
-		{
-			title = "Banpyura";
-			sprite = "CR2BA0";
-			width = 24;
-			height = 32;
-		}
-		117
-		{
-			title = "Robo-Hood";
-			sprite = "ARCHA1";
-			width = 24;
-			height = 32;
-		}
-		118
-		{
-			title = "Lance-a-Bot";
-			sprite = "CBFSA1";
-			width = 32;
-			height = 72;
-		}
-		1113
-		{
-			title = "Suspicious Lance-a-Bot Statue";
-			sprite = "CBBSA1";
-			width = 32;
-			height = 72;
-		}
-		119
-		{
-			title = "Egg Guard";
-			sprite = "ESHIA1";
-			width = 16;
-			height = 48;
-		}
-		115
-		{
-			title = "Bird Aircraft Strike Hazard";
-			sprite = "VLTRF1";
-			width = 12;
-			height = 24;
-		}
-		120
-		{
-			title = "Green Snapper";
-			sprite = "GSNPA1";
-			width = 24;
-			height = 24;
-		}
-		121
-		{
-			title = "Minus";
-			sprite = "MNUSA0";
-			width = 24;
-			height = 32;
-		}
-		134
-		{
-			title = "Canarivore";
-			sprite = "CANAA0";
-			width = 12;
-			height = 80;
-			hangs = 1;
-		}
-		123
-		{
-			title = "Unidus";
-			sprite = "UNIDA1";
-			width = 18;
-			height = 36;
-		}
-		135
-		{
-			title = "Pterabyte Spawner";
-			sprite = "PTERA2A8";
-			width = 16;
-			height = 16;
-		}
-		136
-		{
-			title = "Pyre Fly";
-			sprite = "PYREA0";
-			width = 24;
-			height = 34;
-		}
-		137
-		{
-			title = "Dragonbomber";
-			sprite = "DRABA1";
-			width = 28;
-			height = 48;
-		}
-		105
-		{
-			title = "Jetty-Syn Bomber";
-			sprite = "JETBB1";
-			width = 20;
-			height = 50;
-		}
-		106
-		{
-			title = "Jetty-Syn Gunner";
-			sprite = "JETGB1";
-			width = 20;
-			height = 48;
-		}
-		112
-		{
-			title = "Spincushion";
-			sprite = "SHRPA1";
-			width = 16;
-			height = 24;
-		}
-		114
-		{
-			title = "Snailer";
-			sprite = "SNLRA3A7";
-			width = 24;
-			height = 48;
-		}
-		129
-		{
-			title = "Penguinator";
-			sprite = "PENGA1";
-			width = 24;
-			height = 32;
-		}
-		130
-		{
-			title = "Pophat";
-			sprite = "POPHA1";
-			width = 24;
-			height = 32;
-		}
-		107
-		{
-			title = "Crawla Commander";
-			sprite = "CCOMA1";
-			width = 16;
-			height = 32;
-		}
-		131
-		{
-			title = "Spinbobert";
-			sprite = "SBOBB0";
-			width = 32;
-			height = 32;
-		}
-		132
-		{
-			title = "Cacolantern";
-			sprite = "CACOA0";
-			width = 32;
-			height = 32;
-		}
-		133
-		{
-			title = "Hangster";
-			sprite = "HBATC1";
-			width = 24;
-			height = 24;
-			hangs = 1;
-		}
-		127
-		{
-			title = "Hive Elemental";
-			sprite = "HIVEA0";
-			width = 32;
-			height = 80;
-		}
-		128
-		{
-			title = "Bumblebore";
-			sprite = "BUMBA1";
-			width = 16;
-			height = 32;
-		}
-		124
-		{
-			title = "Buggle";
-			sprite = "BBUZA1";
-			width = 20;
-			height = 24;
-		}
-		116
-		{
-			title = "Pointy";
-			sprite = "PNTYA1";
-			width = 8;
-			height = 16;
-		}
-	}
-
-	bosses
-	{
-		color = 8; // Dark_Gray
-		arrow = 1;
-		title = "Bosses";
-
-		200
-		{
-			title = "Egg Mobile";
-			sprite = "EGGMA1";
-			width = 24;
-			height = 76;
-		}
-		201
-		{
-			title = "Egg Slimer";
-			sprite = "EGGNA1";
-			width = 24;
-			height = 76;
-		}
-		202
-		{
-			title = "Sea Egg";
-			sprite = "EGGOA1";
-			width = 32;
-			height = 116;
-		}
-		203
-		{
-			title = "Egg Colosseum";
-			sprite = "EGGPA1";
-			width = 24;
-			height = 76;
-		}
-		204
-		{
-			title = "Fang";
-			sprite = "FANGA1";
-			width = 24;
-			height = 60;
-		}
-		206
-		{
-			title = "Brak Eggman (Old)";
-			sprite = "BRAKB1";
-			width = 48;
-			height = 160;
-		}
-		207
-		{
-			title = "Metal Sonic (Race)";
-			sprite = "METLI1";
-			width = 16;
-			height = 48;
-		}
-		208
-		{
-			title = "Metal Sonic (Battle)";
-			sprite = "METLC1";
-			width = 16;
-			height = 48;
-		}
-		209
-		{
-			title = "Brak Eggman";
-			sprite = "BRAK01";
-			width = 48;
-			height = 160;
-		}
-		290
-		{
-			arrow = 0;
-			title = "Boss Escape Point";
-			width = 8;
-			height = 16;
-			sprite = "internal:eggmanend";
-		}
-		291
-		{
-			arrow = 0;
-			title = "Egg Capsule Center";
-			width = 8;
-			height = 16;
-			sprite = "internal:capsule";
-		}
-		292
-		{
-			arrow = 0;
-			title = "Boss Waypoint";
-			width = 8;
-			height = 16;
-			sprite = "internal:eggmanway";
-		}
-		293
-		{
-			title = "Metal Sonic Gather Point";
-			sprite = "internal:metal";
-			width = 8;
-			height = 16;
-		}
-		294
-		{
-			title = "Fang Waypoint";
-			sprite = "internal:eggmanway";
-			width = 8;
-			height = 16;
-		}
-	}
-
-	rings
-	{
-		color = 14; // Yellow
-		title = "Rings and Weapon Panels";
-		width = 24;
-		height = 24;
-		sprite = "RINGA0";
-
-		300
-		{
-			title = "Ring";
-			sprite = "RINGA0";
-			width = 16;
-		}
-		301
-		{
-			title = "Bounce Ring";
-			sprite = "internal:RNGBA0";
-		}
-		302
-		{
-			title = "Rail Ring";
-			sprite = "internal:RNGRA0";
-		}
-		303
-		{
-			title = "Infinity Ring";
-			sprite = "internal:RNGIA0";
-		}
-		304
-		{
-			title = "Automatic Ring";
-			sprite = "internal:RNGAA0";
-		}
-		305
-		{
-			title = "Explosion Ring";
-			sprite = "internal:RNGEA0";
-		}
-		306
-		{
-			title = "Scatter Ring";
-			sprite = "internal:RNGSA0";
-		}
-		307
-		{
-			title = "Grenade Ring";
-			sprite = "internal:RNGGA0";
-		}
-		308
-		{
-			title = "CTF Team Ring (Red)";
-			sprite = "internal:RRNGA0";
-			width = 16;
-		}
-		309
-		{
-			title = "CTF Team Ring (Blue)";
-			sprite = "internal:BRNGA0";
-			width = 16;
-		}
-		330
-		{
-			title = "Bounce Ring Panel";
-			sprite = "internal:PIKBA0";
-		}
-		331
-		{
-			title = "Rail Ring Panel";
-			sprite = "internal:PIKRA0";
-		}
-		332
-		{
-			title = "Automatic Ring Panel";
-			sprite = "internal:PIKAA0";
-		}
-		333
-		{
-			title = "Explosion Ring Panel";
-			sprite = "internal:PIKEA0";
-		}
-		334
-		{
-			title = "Scatter Ring Panel";
-			sprite = "internal:PIKSA0";
-		}
-		335
-		{
-			title = "Grenade Ring Panel";
-			sprite = "internal:PIKGA0";
-		}
-	}
-
-	collectibles
-	{
-		color = 10; // Light_Green
-		title = "Other Collectibles";
-		width = 16;
-		height = 32;
-		sort = 1;
-		sprite = "CEMGA0";
-
-		310
-		{
-			title = "CTF Red Flag";
-			sprite = "RFLGA0";
-			width = 24;
-			height = 64;
-		}
-		311
-		{
-			title = "CTF Blue Flag";
-			sprite = "BFLGA0";
-			width = 24;
-			height = 64;
-		}
-		312
-		{
-			title = "Emerald Token";
-			sprite = "TOKEA0";
-			width = 16;
-			height = 32;
-		}
-		313
-		{
-			title = "Chaos Emerald 1 (Green)";
-			sprite = "CEMGA0";
-		}
-		314
-		{
-			title = "Chaos Emerald 2 (Purple)";
-			sprite = "CEMGB0";
-		}
-		315
-		{
-			title = "Chaos Emerald 3 (Blue)";
-			sprite = "CEMGC0";
-		}
-		316
-		{
-			title = "Chaos Emerald 4 (Cyan)";
-			sprite = "CEMGD0";
-		}
-		317
-		{
-			title = "Chaos Emerald 5 (Orange)";
-			sprite = "CEMGE0";
-		}
-		318
-		{
-			title = "Chaos Emerald 6 (Red)";
-			sprite = "CEMGF0";
-		}
-		319
-		{
-			title = "Chaos Emerald 7 (Gray)";
-			sprite = "CEMGG0";
-		}
-		320
-		{
-			title = "Emerald Hunt Location";
-			sprite = "SHRDA0";
-		}
-		321
-		{
-			title = "Match Chaos Emerald Spawn";
-			sprite = "CEMGA0";
-		}
-		322
-		{
-			title = "Emblem";
-			sprite = "EMBMA0";
-			width = 16;
-			height = 30;
-		}
-	}
-
-	boxes
-	{
-		color = 7; // Gray
-		blocking = 2;
-		title = "Monitors";
-		width = 18;
-		height = 40;
-
-		400
-		{
-			title = "Super Ring (10 Rings)";
-			sprite = "TVRIA0";
-		}
-		401
-		{
-			title = "Pity Shield";
-			sprite = "TVPIA0";
-		}
-		402
-		{
-			title = "Attraction Shield";
-			sprite = "TVATA0";
-		}
-		403
-		{
-			title = "Force Shield";
-			sprite = "TVFOA0";
-		}
-		404
-		{
-			title = "Armageddon Shield";
-			sprite = "TVARA0";
-		}
-		405
-		{
-			title = "Whirlwind Shield";
-			sprite = "TVWWA0";
-		}
-		406
-		{
-			title = "Elemental Shield";
-			sprite = "TVELA0";
-		}
-		407
-		{
-			title = "Super Sneakers";
-			sprite = "TVSSA0";
-		}
-		408
-		{
-			title = "Invincibility";
-			sprite = "TVIVA0";
-		}
-		409
-		{
-			title = "Extra Life";
-			sprite = "TV1UA0";
-		}
-		410
-		{
-			title = "Eggman";
-			sprite = "TVEGA0";
-		}
-		411
-		{
-			title = "Teleporter";
-			sprite = "TVMXA0";
-		}
-		413
-		{
-			title = "Gravity Boots";
-			sprite = "TVGVA0";
-		}
-		414
-		{
-			title = "CTF Team Ring Monitor (Red)";
-			sprite = "TRRIA0";
-		}
-		415
-		{
-			title = "CTF Team Ring Monitor (Blue)";
-			sprite = "TBRIA0";
-		}
-		416
-		{
-			title = "Recycler";
-			sprite = "TVRCA0";
-		}
-		418
-		{
-			title = "Score (1,000 Points)";
-			sprite = "TV1KA0";
-		}
-		419
-		{
-			title = "Score (10,000 Points)";
-			sprite = "TVTKA0";
-		}
-		420
-		{
-			title = "Flame Shield";
-			sprite = "TVFLA0";
-		}
-		421
-		{
-			title = "Water Shield";
-			sprite = "TVBBA0";
-		}
-		422
-		{
-			title = "Lightning Shield";
-			sprite = "TVZPA0";
-		}
-	}
-
-	boxes2
-	{
-		color = 18; // Gold
-		blocking = 2;
-		title = "Monitors (Respawning)";
-		width = 20;
-		height = 44;
-
-		431
-		{
-			title = "Pity Shield (Respawn)";
-			sprite = "TVPIB0";
-		}
-		432
-		{
-			title = "Attraction Shield (Respawn)";
-			sprite = "TVATB0";
-		}
-		433
-		{
-			title = "Force Shield (Respawn)";
-			sprite = "TVFOB0";
-		}
-		434
-		{
-			title = "Armageddon Shield (Respawn)";
-			sprite = "TVARB0";
-		}
-		435
-		{
-			title = "Whirlwind Shield (Respawn)";
-			sprite = "TVWWB0";
-		}
-		436
-		{
-			title = "Elemental Shield (Respawn)";
-			sprite = "TVELB0";
-		}
-		437
-		{
-			title = "Super Sneakers (Respawn)";
-			sprite = "TVSSB0";
-		}
-		438
-		{
-			title = "Invincibility (Respawn)";
-			sprite = "TVIVB0";
-		}
-		440
-		{
-			title = "Eggman (Respawn)";
-			sprite = "TVEGB0";
-		}
-		443
-		{
-			title = "Gravity Boots (Respawn)";
-			sprite = "TVGVB0";
-		}
-		450
-		{
-			title = "Flame Shield (Respawn)";
-			sprite = "TVFLB0";
-		}
-		451
-		{
-			title = "Water Shield (Respawn)";
-			sprite = "TVBBB0";
-		}
-		452
-		{
-			title = "Lightning Shield (Respawn)";
-			sprite = "TVZPB0";
-		}
-	}
-
-	generic
-	{
-		color = 11; // Light_Cyan
-		title = "Generic Items & Hazards";
-
-		500
-		{
-			title = "Air Bubble Patch";
-			sprite = "BUBLE0";
-			width = 8;
-			height = 16;
-		}
-		501
-		{
-			title = "Signpost";
-			sprite = "SIGND0";
-			width = 8;
-			height = 32;
-		}
-		502
-		{
-			arrow = 1;
-			title = "Star Post";
-			sprite = "STPTA0M0";
-			width = 64;
-			height = 128;
-		}
-		520
-		{
-			title = "Bomb Sphere";
-			sprite = "SPHRD0";
-			width = 16;
-			height = 24;
-		}
-		521
-		{
-			title = "Spikeball";
-			sprite = "SPIKA0";
-			width = 12;
-			height = 8;
-		}
-		522
-		{
-			title = "Wall Spike";
-			sprite = "WSPKALAR";
-			width = 16;
-			height = 14;
-			arrow = 1;
-		}
-		523
-		{
-			title = "Spike";
-			sprite = "USPKA0";
-			width = 8;
-			height = 32;
-		}
-		1130
-		{
-			title = "Small Mace";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1131
-		{
-			title = "Big Mace";
-			sprite = "BMCEA0";
-			width = 34;
-			height = 68;
-		}
-		1136
-		{
-			title = "Small Fireball";
-			sprite = "SFBRA0";
-			width = 17;
-			height = 34;
-		}
-		1137
-		{
-			title = "Large Fireball";
-			sprite = "BFBRA0";
-			width = 34;
-			height = 68;
-		}
-	}
-
-	springs
-	{
-		color = 12; // Light_Red
-		title = "Springs and Fans";
-		width = 20;
-		height = 16;
-		sprite = "RSPRD2";
-
-		540
-		{
-			title = "Fan";
-			sprite = "FANSA0D0";
-			width = 16;
-			height = 8;
-		}
-		541
-		{
-			title = "Gas Jet";
-			sprite = "STEMD0";
-			width = 32;
-		}
-		542
-		{
-			title = "Bumper";
-			sprite = "BUMPA0";
-			width = 32;
-			height = 64;
-		}
-		543
-		{
-			title = "Balloon";
-			sprite = "BLONA0";
-			width = 32;
-			height = 64;
-		}
-		550
-		{
-			title = "Yellow Spring";
-			sprite = "SPRYA0";
-		}
-		551
-		{
-			title = "Red Spring";
-			sprite = "SPRRA0";
-		}
-		552
-		{
-			title = "Blue Spring";
-			sprite = "SPRBA0";
-		}
-		555
-		{
-			arrow = 1;
-			title = "Diagonal Yellow Spring";
-			sprite = "YSPRD2";
-			width = 16;
-		}
-		556
-		{
-			arrow = 1;
-			title = "Diagonal Red Spring";
-			sprite = "RSPRD2";
-			width = 16;
-		}
-		557
-		{
-			arrow = 1;
-			title = "Diagonal Blue Spring";
-			sprite = "BSPRD2";
-			width = 16;
-		}
-		558
-		{
-			arrow = 1;
-			title = "Horizontal Yellow Spring";
-			sprite = "SSWYD2D8";
-			width = 16;
-			height = 32;
-		}
-		559
-		{
-			arrow = 1;
-			title = "Horizontal Red Spring";
-			sprite = "SSWRD2D8";
-			width = 16;
-			height = 32;
-		}
-		560
-		{
-			arrow = 1;
-			title = "Horizontal Blue Spring";
-			sprite = "SSWBD2D8";
-			width = 16;
-			height = 32;
-		}
-		1134
-		{
-			title = "Yellow Spring Ball";
-			sprite = "YSPBA0";
-			width = 17;
-			height = 34;
-		}
-		1135
-		{
-			title = "Red Spring Ball";
-			sprite = "RSPBA0";
-			width = 17;
-			height = 34;
-		}
-		544
-		{
-			arrow = 1;
-			title = "Yellow Boost Panel";
-			sprite = "BSTYA0";
-			width = 28;
-			height = 2;
-		}
-		545
-		{
-			arrow = 1;
-			title = "Red Boost Panel";
-			sprite = "BSTRA0";
-			width = 28;
-			height = 2;
-		}
-	}
-
-	patterns
-	{
-		color = 5; // Magenta
-		arrow = 1;
-		title = "Special Placement Patterns";
-		width = 16;
-		height = 384;
-		sprite = "RINGA0";
-
-		600
-		{
-			arrow = 0;
-			title = "5 Vertical Rings (Yellow Spring)";
-			sprite = "RINGA0";
-		}
-		601
-		{
-			arrow = 0;
-			title = "5 Vertical Rings (Red Spring)";
-			sprite = "RINGA0";
-			height = 1024;
-		}
-		602
-		{
-			title = "5 Diagonal Rings (Yellow Spring)";
-			sprite = "RINGA0";
-			height = 32;
-		}
-		603
-		{
-			title = "10 Diagonal Rings (Red Spring)";
-			sprite = "RINGA0";
-			height = 32;
-		}
-		604
-		{
-			title = "Circle of Rings";
-			sprite = "RINGA0";
-			width = 96;
-			height = 192;
-		}
-		605
-		{
-			title = "Circle of Rings (Big)";
-			sprite = "RINGA0";
-			width = 192;
-		}
-		606
-		{
-			title = "Circle of Blue Spheres";
-			sprite = "SPHRA0";
-			width = 96;
-			height = 192;
-		}
-		607
-		{
-			title = "Circle of Blue Spheres (Big)";
-			sprite = "SPHRA0";
-			width = 192;
-		}
-		608
-		{
-			title = "Circle of Rings and Spheres";
-			sprite = "SPHRA0";
-			width = 96;
-			height = 192;
-		}
-		609
-		{
-			title = "Circle of Rings and Spheres (Big)";
-			sprite = "SPHRA0";
-			width = 192;
-		}
-	}
-
-	invisible
-	{
-		color = 15; // White
-		title = "Misc. Invisible";
-		width = 0;
-		height = 0;
-		sprite = "UNKNA0";
-		sort = 1;
-		fixedsize = true;
-		blocking = 0;
-
-		700
-		{
-			title = "Water Ambience A (Large)";
-			sprite = "internal:ambiance";
-		}
-
-		701
-		{
-			title = "Water Ambience B (Large)";
-			sprite = "internal:ambiance";
-		}
-
-		702
-		{
-			title = "Water Ambience C (Medium)";
-			sprite = "internal:ambiance";
-		}
-
-		703
-		{
-			title = "Water Ambience D (Medium)";
-			sprite = "internal:ambiance";
-		}
-
-		704
-		{
-			title = "Water Ambience E (Small)";
-			sprite = "internal:ambiance";
-		}
-
-		705
-		{
-			title = "Water Ambience F (Small)";
-			sprite = "internal:ambiance";
-		}
-
-		706
-		{
-			title = "Water Ambience G (Extra Large)";
-			sprite = "internal:ambiance";
-		}
-
-		707
-		{
-			title = "Water Ambience H (Extra Large)";
-			sprite = "internal:ambiance";
-		}
-
-		708
-		{
-			title = "Disco Ambience";
-			sprite = "internal:ambiance";
-		}
-
-		709
-		{
-			title = "Volcano Ambience";
-			sprite = "internal:ambiance";
-		}
-
-		710
-		{
-			title = "Machine Ambience";
-			sprite = "internal:ambiance";
-		}
-
-		750
-		{
-			title = "Slope Vertex";
-			sprite = "internal:vertexslope";
-		}
-
-		751
-		{
-			arrow = 1;
-			title = "Teleport Destination";
-			sprite = "internal:tele";
-		}
-
-		752
-		{
-			arrow = 1;
-			title = "Alternate View Point";
-			sprite = "internal:view";
-		}
-
-		753
-		{
-			title = "Zoom Tube Waypoint";
-			sprite = "internal:zoom";
-		}
-
-		754
-		{
-			title = "Push Point";
-			sprite = "GWLGA0";
-		}
-		755
-		{
-			title = "Pull Point";
-			sprite = "GWLRA0";
-		}
-		756
-		{
-			title = "Blast Linedef Executor";
-			sprite = "TOADA0";
-			width = 32;
-			height = 16;
-		}
-		757
-		{
-			title = "Fan Particle Generator";
-			sprite = "PRTLA0";
-			width = 8;
-			height = 16;
-		}
-		758
-		{
-			title = "Object Angle Anchor";
-			sprite = "internal:view";
-		}
-		760
-		{
-			title = "PolyObject Anchor";
-			sprite = "internal:polyanchor";
-		}
-		761
-		{
-			title = "PolyObject Spawn Point";
-			sprite = "internal:polycenter";
-		}
-		762
-		{
-			title = "PolyObject Spawn Point (Crush)";
-			sprite = "internal:polycentercrush";
-		}
-		780
-		{
-			title = "Skybox View Point";
-			sprite = "internal:skyb";
-		}
-	}
-
-	greenflower
-	{
-		color = 10; // Green
-		title = "Greenflower";
-
-		800
-		{
-			title = "GFZ Flower";
-			sprite = "FWR1A0";
-			width = 16;
-			height = 40;
-		}
-		801
-		{
-			title = "Sunflower";
-			sprite = "FWR2A0";
-			width = 16;
-			height = 96;
-		}
-		802
-		{
-			title = "Budding Flower";
-			sprite = "FWR3A0";
-			width = 8;
-			height = 32;
-		}
-		803
-		{
-			title = "Blueberry Bush";
-			sprite = "BUS3A0";
-			width = 16;
-			height = 32;
-		}
-		804
-		{
-			title = "Berry Bush";
-			sprite = "BUS1A0";
-			width = 16;
-			height = 32;
-		}
-		805
-		{
-			title = "Bush";
-			sprite = "BUS2A0";
-			width = 16;
-			height = 32;
-		}
-		806
-		{
-			title = "GFZ Tree";
-			sprite = "TRE1A0";
-			width = 20;
-			height = 128;
-		}
-		807
-		{
-			title = "GFZ Berry Tree";
-			sprite = "TRE1B0";
-			width = 20;
-			height = 128;
-		}
-		808
-		{
-			title = "GFZ Cherry Tree";
-			sprite = "TRE1C0";
-			width = 20;
-			height = 128;
-		}
-		809
-		{
-			title = "Checkered Tree";
-			sprite = "TRE2A0";
-			width = 20;
-			height = 200;
-		}
-		810
-		{
-			title = "Checkered Tree (Sunset)";
-			sprite = "TRE2B0";
-			width = 20;
-			height = 200;
-		}
-		811
-		{
-			title = "Polygon Tree";
-			sprite = "TRE4A0";
-			width = 20;
-			height = 200;
-		}
-		812
-		{
-			title = "Bush Tree";
-			sprite = "TRE5A0";
-			width = 20;
-			height = 200;
-		}
-		813
-		{
-			title = "Red Bush Tree";
-			sprite = "TRE5B0";
-			width = 20;
-			height = 200;
-		}
-	}
-
-	technohill
-	{
-		color = 10; // Green
-		title = "Techno Hill";
-
-		900
-		{
-			title = "THZ Steam Flower";
-			sprite = "THZPA0";
-			width = 8;
-			height = 32;
-		}
-		901
-		{
-			title = "Alarm";
-			sprite = "ALRMA0";
-			width = 8;
-			height = 16;
-			hangs = 1;
-		}
-		902
-		{
-			title = "THZ Spin Flower (Red)";
-			sprite = "FWR5A0";
-			width = 16;
-			height = 64;
-		}
-		903
-		{
-			title = "THZ Spin Flower (Yellow)";
-			sprite = "FWR6A0";
-			width = 16;
-			height = 64;
-		}
-		904
-		{
-			arrow = 1;
-			title = "Whistlebush";
-			sprite = "THZTA0";
-			width = 16;
-			height = 64;
-		}
-	}
-
-	deepsea
-	{
-		color = 10; // Green
-		title = "Deep Sea";
-
-		1000
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Gargoyle";
-			sprite = "GARGA1";
-			width = 16;
-			height = 40;
-		}
-		1009
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Gargoyle (Big)";
-			sprite = "GARGB1";
-			width = 32;
-			height = 80;
-		}
-		1001
-		{
-			title = "Seaweed";
-			sprite = "SEWEA0";
-			width = 24;
-			height = 56;
-		}
-		1002
-		{
-			title = "Dripping Water";
-			sprite = "DRIPD0";
-			width = 8;
-			height = 16;
-			hangs = 1;
-		}
-		1003
-		{
-			title = "Coral (Green)";
-			sprite = "CORLA0";
-			width = 29;
-			height = 40;
-		}
-		1004
-		{
-			title = "Coral (Red)";
-			sprite = "CORLB0";
-			width = 30;
-			height = 53;
-		}
-		1005
-		{
-			title = "Coral (Orange)";
-			sprite = "CORLC0";
-			width = 28;
-			height = 41;
-		}
-		1006
-		{
-			title = "Blue Crystal";
-			sprite = "BCRYA1";
-			width = 8;
-			height = 16;
-		}
-		1007
-		{
-			title = "Kelp";
-			sprite = "KELPA0";
-			width = 16;
-			height = 292;
-		}
-		1008
-		{
-			title = "Stalagmite (DSZ1)";
-			sprite = "DSTGA0";
-			width = 8;
-			height = 116;
-		}
-		1010
-		{
-			arrow = 1;
-			title = "Light Beam";
-			sprite = "LIBEARAL";
-			width = 16;
-			height = 16;
-		}
-		1011
-		{
-			title = "Stalagmite (DSZ2)";
-			sprite = "DSTGA0";
-			width = 8;
-			height = 116;
-		}
-		1012
-		{
-			arrow = 1;
-			title = "Big Floating Mine";
-			width = 28;
-			height = 56;
-			sprite = "BMNEA1";
-		}
-		1013
-		{
-			title = "Animated Kelp";
-			sprite = "ALGAA0";
-			width = 48;
-			height = 120;
-		}
-		1014
-		{
-			title = "Large Coral (Brown)";
-			sprite = "CORLD0";
-			width = 56;
-			height = 112;
-		}
-		1015
-		{
-			title = "Large Coral (Beige)";
-			sprite = "CORLE0";
-			width = 56;
-			height = 112;
-		}
-	}
-
-	castleeggman
-	{
-		color = 10; // Green
-		title = "Castle Eggman";
-
-		1100
-		{
-			title = "Chain (Decorative)";
-			sprite = "CHANA0";
-			width = 4;
-			height = 128;
-			hangs = 1;
-		}
-		1101
-		{
-			title = "Torch";
-			sprite = "FLAMA0E0";
-			width = 8;
-			height = 32;
-		}
-		1102
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Eggman Statue";
-			sprite = "ESTAA1";
-			width = 32;
-			height = 240;
-		}
-		1103
-		{
-			title = "CEZ Flower";
-			sprite = "FWR4A0";
-			width = 16;
-			height = 40;
-		}
-		1104
-		{
-			title = "Mace Spawnpoint";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1105
-		{
-			title = "Chain with Maces Spawnpoint";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1106
-		{
-			title = "Chained Spring Spawnpoint";
-			sprite = "YSPBA0";
-			width = 17;
-			height = 34;
-		}
-		1107
-		{
-			title = "Chain Spawnpoint";
-			sprite = "BMCHA0";
-			width = 17;
-			height = 34;
-		}
-		1108
-		{
-			arrow = 1;
-			title = "Hidden Chain Spawnpoint";
-			sprite = "internal:chain3";
-			width = 17;
-			height = 34;
-		}
-		1109
-		{
-			title = "Firebar Spawnpoint";
-			sprite = "BFBRA0";
-			width = 17;
-			height = 34;
-		}
-		1110
-		{
-			title = "Custom Mace Spawnpoint";
-			sprite = "SMCEA0";
-			width = 17;
-			height = 34;
-		}
-		1111
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Crawla Statue";
-			sprite = "CSTAA1";
-			width = 16;
-			height = 40;
-		}
-		1112
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Lance-a-Bot Statue";
-			sprite = "CBBSA1";
-			width = 32;
-			height = 72;
-		}
-		1114
-		{
-			title = "Pine Tree";
-			sprite = "PINEA0";
-			width = 16;
-			height = 628;
-		}
-		1115
-		{
-			title = "CEZ Shrub (Small)";
-			sprite = "CEZBA0";
-			width = 16;
-			height = 24;
-		}
-		1116
-		{
-			title = "CEZ Shrub (Large)";
-			sprite = "CEZBB0";
-			width = 32;
-			height = 48;
-		}
-		1117
-		{
-			arrow = 1;
-			title = "Pole Banner (Red)";
-			sprite = "BANRA0";
-			width = 40;
-			height = 224;
-		}
-		1118
-		{
-			arrow = 1;
-			title = "Pole Banner (Blue)";
-			sprite = "BANRA0";
-			width = 40;
-			height = 224;
-		}
-		1119
-		{
-			title = "Candle";
-			sprite = "CNDLA0";
-			width = 8;
-			height = 48;
-		}
-		1120
-		{
-			title = "Candle Pricket";
-			sprite = "CNDLB0";
-			width = 8;
-			height = 176;
-		}
-		1121
-		{
-			title = "Flame Holder";
-			sprite = "FLMHA0";
-			width = 24;
-			height = 80;
-		}
-		1122
-		{
-			title = "Fire Torch";
-			sprite = "CTRCA0";
-			width = 16;
-			height = 80;
-		}
-		1123
-		{
-			title = "Cannonball Launcher";
-			sprite = "internal:cannonball";
-			width = 8;
-			height = 16;
-		}
-		1124
-		{
-			blocking = 2;
-			title = "Cannonball";
-			sprite = "CBLLA0";
-			width = 20;
-			height = 40;
-		}
-		1125
-		{
-			title = "Brambles";
-			sprite = "CABRALAR";
-			width = 48;
-			height = 32;
-		}
-		1126
-		{
-			title = "Invisible Lockon Object";
-			sprite = "LCKNC0";
-			width = 16;
-			height = 32;
-		}
-		1127
-		{
-			title = "Spectator Eggrobo";
-			sprite = "EGR1A1";
-			width = 20;
-			height = 72;
-		}
-		1128
-		{
-			arrow = 1;
-			title = "Waving Flag (Red)";
-			sprite = "CFLGA0";
-			width = 8;
-			height = 208;
-		}
-		1129
-		{
-			arrow = 1;
-			title = "Waving Flag (Blue)";
-			sprite = "CFLGA0";
-			width = 8;
-			height = 208;
-		}
-	}
-
-	aridcanyon
-	{
-		color = 10; // Green
-		title = "Arid Canyon";
-
-		1200
-		{
-			title = "Tumbleweed (Big)";
-			sprite = "BTBLA0";
-			width = 24;
-			height = 48;
-		}
-		1201
-		{
-			title = "Tumbleweed (Small)";
-			sprite = "STBLA0";
-			width = 12;
-			height = 24;
-		}
-		1202
-		{
-			arrow = 1;
-			title = "Rock Spawner";
-			sprite = "ROIAA0";
-			width = 8;
-			height = 16;
-		}
-		1203
-		{
-			title = "Tiny Red Flower Cactus";
-			sprite = "CACTA0";
-			width = 13;
-			height = 24;
-		}
-		1204
-		{
-			title = "Small Red Flower Cactus";
-			sprite = "CACTB0";
-			width = 15;
-			height = 52;
-		}
-		1205
-		{
-			title = "Tiny Blue Flower Cactus";
-			sprite = "CACTC0";
-			width = 13;
-			height = 24;
-		}
-		1206
-		{
-			title = "Small Blue Flower Cactus";
-			sprite = "CACTD0";
-			width = 15;
-			height = 52;
-		}
-		1207
-		{
-			title = "Prickly Pear";
-			sprite = "CACTE0";
-			width = 32;
-			height = 96;
-		}
-		1208
-		{
-			title = "Barrel Cactus";
-			sprite = "CACTF0";
-			width = 20;
-			height = 128;
-		}
-		1209
-		{
-			title = "Tall Barrel Cactus";
-			sprite = "CACTG0";
-			width = 24;
-			height = 224;
-		}
-		1210
-		{
-			title = "Armed Cactus";
-			sprite = "CACTH0";
-			width = 24;
-			height = 256;
-		}
-		1211
-		{
-			title = "Ball Cactus";
-			sprite = "CACTI0";
-			width = 48;
-			height = 96;
-		}
-		1212
-		{
-			title = "Caution Sign";
-			sprite = "WWSGAR";
-			width = 22;
-			height = 64;
-		}
-		1213
-		{
-			title = "Cacti Sign";
-			sprite = "WWS2AR";
-			width = 22;
-			height = 64;
-		}
-		1214
-		{
-			title = "Sharp Turn Sign";
-			sprite = "WWS3ALAR";
-			width = 16;
-			height = 192;
-		}
-		1215
-		{
-			title = "Mine Oil Lamp";
-			sprite = "OILLA0";
-			width = 22;
-			height = 64;
-			hangs = 1;
-		}
-		1216
-		{
-			title = "TNT Barrel";
-			sprite = "BARRA1";
-			width = 24;
-			height = 63;
-		}
-		1217
-		{
-			title = "TNT Proximity Shell";
-			sprite = "REMTA0";
-			width = 64;
-			height = 40;
-		}
-		1218
-		{
-			title = "Dust Devil";
-			sprite = "TAZDCR";
-			width = 80;
-			height = 416;
-		}
-		1219
-		{
-			title = "Minecart Spawner";
-			sprite = "MCRTCLFR";
-			width = 22;
-			height = 32;
-		}
-		1220
-		{
-			title = "Minecart Stopper";
-			sprite = "MCRTIR";
-			width = 32;
-			height = 32;
-		}
-		1221
-		{
-			title = "Minecart Saloon Door";
-			sprite = "SALDARAL";
-			width = 96;
-			height = 160;
-		}
-		1222
-		{
-			title = "Train Cameo Spawner";
-			sprite = "TRAEBRBL";
-			width = 28;
-			height = 32;
-		}
-		1223
-		{
-			title = "Train Dust Spawner";
-			sprite = "ADSTA0";
-			width = 4;
-			height = 4;
-		}
-		1224
-		{
-			title = "Train Steam Spawner";
-			sprite = "STEAA0";
-			width = 4;
-			height = 4;
-		}
-		1229
-		{
-			title = "Minecart Switch Point";
-			sprite = "internal:zoom";
-			width = 8;
-			height = 16;
-		}
-		1230
-		{
-			title = "Tiny Cactus";
-			sprite = "CACTJ0";
-			width = 13;
-			height = 28;
-		}
-		1231
-		{
-			title = "Small Cactus";
-			sprite = "CACTK0";
-			width = 15;
-			height = 60;
-		}
-	}
-
-	redvolcano
-	{
-		color = 10; // Green
-		title = "Red Volcano";
-
-		1300
-		{
-			arrow = 1;
-			title = "Flame Jet (Horizontal)";
-			sprite = "internal:flameh";
-			width = 16;
-			height = 40;
-		}
-		1301
-		{
-			title = "Flame Jet (Vertical)";
-			sprite = "internal:flamev";
-			width = 16;
-			height = 40;
-		}
-		1302
-		{
-			title = "Spinning Flame Jet (Counter-Clockwise)";
-			sprite = "internal:flame2";
-			width = 16;
-			height = 24;
-		}
-		1303
-		{
-			title = "Spinning Flame Jet (Clockwise)";
-			sprite = "internal:flame1";
-			width = 16;
-			height = 24;
-		}
-		1304
-		{
-			title = "Lavafall";
-			sprite = "LFALF0";
-			width = 30;
-			height = 32;
-		}
-		1305
-		{
-			title = "Rollout Rock";
-			sprite = "PUMIA1A5";
-			width = 30;
-			height = 60;
-		}
-		1306
-		{
-			title = "Big Fern";
-			sprite = "JPLAB0";
-			width = 32;
-			height = 48;
-		}
-		1307
-		{
-			title = "Jungle Palm";
-			sprite = "JPLAC0";
-			width = 32;
-			height = 48;
-		}
-		1308
-		{
-			title = "Torch Flower";
-			sprite = "TFLOA0";
-			width = 14;
-			height = 110;
-		}
-		1309
-		{
-			title = "RVZ1 Wall Vine (Long)";
-			sprite = "WVINALAR";
-			width = 1;
-			height = 288;
-		}
-		1310
-		{
-			title = "RVZ1 Wall Vine (Short)";
-			sprite = "WVINBLBR";
-			width = 1;
-			height = 288;
-		}
-	}
-
-	botanicserenity
-	{
-		color = 10; // Green
-		title = "Botanic Serenity";
-		width = 16;
-		height = 32;
-		sprite = "BSZ1A0";
-		1400
-		{
-			title = "Tall Flower (Red)";
-			sprite = "BSZ1A0";
-		}
-		1401
-		{
-			title = "Tall Flower (Purple)";
-			sprite = "BSZ1B0";
-		}
-		1402
-		{
-			title = "Tall Flower (Blue)";
-			sprite = "BSZ1C0";
-		}
-		1403
-		{
-			title = "Tall Flower (Cyan)";
-			sprite = "BSZ1D0";
-		}
-		1404
-		{
-			title = "Tall Flower (Yellow)";
-			sprite = "BSZ1E0";
-		}
-		1405
-		{
-			title = "Tall Flower (Orange)";
-			sprite = "BSZ1F0";
-		}
-		1410
-		{
-			title = "Medium Flower (Red)";
-			sprite = "BSZ2A0";
-		}
-		1411
-		{
-			title = "Medium Flower (Purple)";
-			sprite = "BSZ2B0";
-		}
-		1412
-		{
-			title = "Medium Flower (Blue)";
-			sprite = "BSZ2C0";
-		}
-		1413
-		{
-			title = "Medium Flower (Cyan)";
-			sprite = "BSZ2D0";
-		}
-		1414
-		{
-			title = "Medium Flower (Yellow)";
-			sprite = "BSZ2E0";
-		}
-		1415
-		{
-			title = "Medium Flower (Orange)";
-			sprite = "BSZ2F0";
-		}
-		1420
-		{
-			title = "Short Flower (Red)";
-			sprite = "BSZ3A0";
-		}
-		1421
-		{
-			title = "Short Flower (Purple)";
-			sprite = "BSZ3B0";
-		}
-		1422
-		{
-			title = "Short Flower (Blue)";
-			sprite = "BSZ3C0";
-		}
-		1423
-		{
-			title = "Short Flower (Cyan)";
-			sprite = "BSZ3D0";
-		}
-		1424
-		{
-			title = "Short Flower (Yellow)";
-			sprite = "BSZ3E0";
-		}
-		1425
-		{
-			title = "Short Flower (Orange)";
-			sprite = "BSZ3F0";
-		}
-		1430
-		{
-			title = "Tulip (Red)";
-			sprite = "BST1A0";
-		}
-		1431
-		{
-			title = "Tulip (Purple)";
-			sprite = "BST2A0";
-		}
-		1432
-		{
-			title = "Tulip (Blue)";
-			sprite = "BST3A0";
-		}
-		1433
-		{
-			title = "Tulip (Cyan)";
-			sprite = "BST4A0";
-		}
-		1434
-		{
-			title = "Tulip (Yellow)";
-			sprite = "BST5A0";
-		}
-		1435
-		{
-			title = "Tulip (Orange)";
-			sprite = "BST6A0";
-		}
-		1440
-		{
-			title = "Cluster (Red)";
-			sprite = "BSZ5A0";
-		}
-		1441
-		{
-			title = "Cluster (Purple)";
-			sprite = "BSZ5B0";
-		}
-		1442
-		{
-			title = "Cluster (Blue)";
-			sprite = "BSZ5C0";
-		}
-		1443
-		{
-			title = "Cluster (Cyan)";
-			sprite = "BSZ5D0";
-		}
-		1444
-		{
-			title = "Cluster (Yellow)";
-			sprite = "BSZ5E0";
-		}
-		1445
-		{
-			title = "Cluster (Orange)";
-			sprite = "BSZ5F0";
-		}
-		1450
-		{
-			title = "Bush (Red)";
-			sprite = "BSZ6A0";
-		}
-		1451
-		{
-			title = "Bush (Purple)";
-			sprite = "BSZ6B0";
-		}
-		1452
-		{
-			title = "Bush (Blue)";
-			sprite = "BSZ6C0";
-		}
-		1453
-		{
-			title = "Bush (Cyan)";
-			sprite = "BSZ6D0";
-		}
-		1454
-		{
-			title = "Bush (Yellow)";
-			sprite = "BSZ6E0";
-		}
-		1455
-		{
-			title = "Bush (Orange)";
-			sprite = "BSZ6F0";
-		}
-		1460
-		{
-			title = "Vine (Red)";
-			sprite = "BSZ7A0";
-		}
-		1461
-		{
-			title = "Vine (Purple)";
-			sprite = "BSZ7B0";
-		}
-		1462
-		{
-			title = "Vine (Blue)";
-			sprite = "BSZ7C0";
-		}
-		1463
-		{
-			title = "Vine (Cyan)";
-			sprite = "BSZ7D0";
-		}
-		1464
-		{
-			title = "Vine (Yellow)";
-			sprite = "BSZ7E0";
-		}
-		1465
-		{
-			title = "Vine (Orange)";
-			sprite = "BSZ7F0";
-		}
-		1470
-		{
-			title = "BSZ Shrub";
-			sprite = "BSZ8A0";
-		}
-		1471
-		{
-			title = "BSZ Clover";
-			sprite = "BSZ8B0";
-		}
-		1473
-		{
-			title = "Palm Tree (Big)";
-			width = 16;
-			height = 160;
-			sprite = "BSZ8D0";
-		}
-		1475
-		{
-			title = "Palm Tree (Small)";
-			width = 16;
-			height = 80;
-			sprite = "BSZ8F0";
-		}
-	}
-
-	azuretemple
-	{
-		color = 10; // Green
-		title = "Azure Temple";
-
-		1500
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1501
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Up)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1502
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Down)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1503
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Glaregoyle (Long)";
-			sprite = "BGARA1";
-			width = 16;
-			height = 40;
-		}
-		1504
-		{
-			title = "ATZ Target";
-			sprite = "RCRYB0";
-			width = 24;
-			height = 32;
-		}
-		1505
-		{
-			title = "Green Flame";
-			sprite = "CFLMA0E0";
-			width = 8;
-			height = 32;
-		}
-		1506
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Blue Gargoyle";
-			sprite = "BGARD1";
-			width = 16;
-			height = 40;
-		}
-	}
-
-	dreamhill
-	{
-		color = 10; // Green
-		title = "Dream Hill";
-
-		1600
-		{
-			title = "Spring Tree";
-			sprite = "TRE6A0";
-			width = 16;
-			height = 32;
-		}
-		1601
-		{
-			title = "Shleep";
-			sprite = "SHLPA0";
-			width = 24;
-			height = 32;
-		}
-		1602
-		{
-			title = "Nightopian";
-			sprite = "NTPNA1";
-			width = 16;
-			height = 40;
-		}
-	}
-
-	nightstrk
-	{
-		color = 13; // Pink
-		title = "NiGHTS Track";
-		width = 8;
-		height = 4096;
-		sprite = "UNKNA0";
-
-		1700
-		{
-			title = "Axis";
-			sprite = "internal:axis1";
-			circle = 1;
-		}
-		1701
-		{
-			title = "Axis Transfer";
-			sprite = "internal:axis2";
-		}
-		1702
-		{
-			title = "Axis Transfer Line";
-			sprite = "internal:axis3";
-		}
-		1710
-		{
-			title = "Ideya Capture";
-			sprite = "CAPSA0";
-			width = 72;
-			height = 144;
-		}
-	}
-
-	nights
-	{
-		color = 13; // Pink
-		title = "NiGHTS Items";
-		width = 16;
-		height = 32;
-
-		1703
-		{
-			title = "Ideya Drone";
-			sprite = "NDRNA1";
-			width = 16;
-			height = 56;
-		}
-		1704
-		{
-			arrow = 1;
-			title = "NiGHTS Bumper";
-			sprite = "NBMPG3G7";
-			width = 32;
-			height = 64;
-		}
-		1705
-		{
-			arrow = 1;
-			title = "Hoop (Generic)";
-			sprite = "HOOPA0";
-			width = 80;
-			height = 160;
-		}
-		1706
-		{
-			title = "Blue Sphere";
-			sprite = "SPHRA0";
-			width = 16;
-			height = 24;
-		}
-		1707
-		{
-			title = "Super Paraloop";
-			sprite = "NPRUA0";
-		}
-		1708
-		{
-			title = "Drill Refill";
-			sprite = "NPRUB0";
-		}
-		1709
-		{
-			title = "Nightopian Helper";
-			sprite = "NPRUC0";
-		}
-		1711
-		{
-			title = "Extra Time";
-			sprite = "NPRUD0";
-		}
-		1712
-		{
-			title = "Link Freeze";
-			sprite = "NPRUE0";
-		}
-		1713
-		{
-			arrow = 1;
-			title = "Hoop (Customizable)";
-			sprite = "HOOPA0";
-			width = 80;
-			height = 160;
-		}
-		1714
-		{
-			title = "Ideya Anchor Point";
-			sprite = "internal:axis1";
-			width = 8;
-			height = 16;
-		}
-	}
-
-	mario
-	{
-		color = 6; // Brown
-		title = "Mario";
-
-		1800
-		{
-			title = "Coin";
-			sprite = "COINA0";
-			width = 16;
-			height = 24;
-		}
-		1801
-		{
-			arrow = 1;
-			title = "Goomba";
-			sprite = "GOOMA0";
-			width = 24;
-			height = 32;
-		}
-		1802
-		{
-			arrow = 1;
-			title = "Goomba (Blue)";
-			sprite = "BGOMA0";
-			width = 24;
-			height = 32;
-		}
-		1803
-		{
-			title = "Fire Flower";
-			sprite = "FFWRB0";
-			width = 16;
-			height = 32;
-		}
-		1804
-		{
-			title = "Koopa Shell";
-			sprite = "SHLLA1";
-			width = 16;
-			height = 20;
-		}
-		1805
-		{
-			title = "Puma (Jumping Fireball)";
-			sprite = "PUMAA0";
-			width = 8;
-			height = 16;
-		}
-		1806
-		{
-			title = "King Bowser";
-			sprite = "KOOPA0";
-			width = 16;
-			height = 48;
-		}
-		1807
-		{
-			title = "Axe";
-			sprite = "MAXEA0";
-			width = 8;
-			height = 16;
-		}
-		1808
-		{
-			title = "Bush (Short)";
-			sprite = "MUS1A0";
-			width = 16;
-			height = 32;
-		}
-		1809
-		{
-			title = "Bush (Tall)";
-			sprite = "MUS2A0";
-			width = 16;
-			height = 32;
-		}
-		1810
-		{
-			title = "Toad";
-			sprite = "TOADA0";
-			width = 8;
-			height = 32;
-		}
-	}
-
-	christmasdisco
-	{
-		color = 10; // Green
-		title = "Christmas & Disco";
-
-		1850
-		{
-			title = "Christmas Pole";
-			sprite = "XMS1A0";
-			width = 16;
-			height = 40;
-		}
-		1851
-		{
-			title = "Candy Cane";
-			sprite = "XMS2A0";
-			width = 8;
-			height = 32;
-		}
-		1852
-		{
-			blocking = 2;
-			title = "Snowman";
-			sprite = "XMS3A0";
-			width = 16;
-			height = 64;
-		}
-		1853
-		{
-			blocking = 2;
-			title = "Snowman (With Hat)";
-			sprite = "XMS3B0";
-			width = 16;
-			height = 80;
-		}
-		1854
-		{
-			title = "Lamp Post";
-			sprite = "XMS4A0";
-			width = 8;
-			height = 120;
-		}
-		1855
-		{
-			title = "Lamp Post (Snow)";
-			sprite = "XMS4B0";
-			width = 8;
-			height = 120;
-		}
-		1856
-		{
-			title = "Hanging Star";
-			sprite = "XMS5A0";
-			width = 4;
-			height = 80;
-			hangs = 1;
-		}
-		1857
-		{
-			title = "Berry Bush (Snow)";
-			sprite = "BUS1B0";
-			width = 16;
-			height = 32;
-		}
-		1858
-		{
-			title = "Bush (Snow)";
-			sprite = "BUS2B0";
-			width = 16;
-			height = 32;
-		}
-		1859
-		{
-			title = "Blueberry Bush (Snow)";
-			sprite = "BUS3B0";
-			width = 16;
-			height = 32;
-		}
-		1875
-		{
-			title = "Disco Ball";
-			sprite = "DBALA0";
-			width = 16;
-			height = 54;
-			hangs = 1;
-		}
-		1876
-		{
-			arrow = 1;
-			blocking = 2;
-			title = "Eggman Disco Statue";
-			sprite = "ESTAB1";
-			width = 20;
-			height = 96;
-		}
-	}
-
-	stalagmites
-	{
-		color = 10; // Green
-		title = "Stalagmites";
-		width = 16;
-		height = 40;
-
-		1900
-		{
-			title = "Brown Stalagmite (Tall)";
-			sprite = "STLGA0";
-			width = 16;
-			height = 40;
-		}
-		1901
-		{
-			title = "Brown Stalagmite";
-			sprite = "STLGB0";
-			width = 16;
-			height = 40;
-		}
-		1902
-		{
-			title = "Orange Stalagmite (Tall)";
-			sprite = "STLGC0";
-			width = 16;
-			height = 40;
-		}
-		1903
-		{
-			title = "Orange Stalagmite";
-			sprite = "STLGD0";
-			width = 16;
-			height = 40;
-		}
-		1904
-		{
-			title = "Red Stalagmite (Tall)";
-			sprite = "STLGE0";
-			width = 16;
-			height = 40;
-		}
-		1905
-		{
-			title = "Red Stalagmite";
-			sprite = "STLGF0";
-			width = 16;
-			height = 40;
-		}
-		1906
-		{
-			title = "Gray Stalagmite (Tall)";
-			sprite = "STLGG0";
-			width = 24;
-			height = 96;
-		}
-		1907
-		{
-			title = "Gray Stalagmite";
-			sprite = "STLGH0";
-			width = 16;
-			height = 40;
-		}
-		1908
-		{
-			title = "Blue Stalagmite (Tall)";
-			sprite = "STLGI0";
-			width = 16;
-			height = 40;
-		}
-		1909
-		{
-			title = "Blue Stalagmite";
-			sprite = "STLGJ0";
-			width = 16;
-			height = 40;
-		}
-	}
-
-	hauntedheights
-	{
-		color = 10; // Green
-		title = "Haunted Heights";
-
-		2000
-		{
-			title = "Smashing Spikeball";
-			sprite = "FMCEA0";
-			width = 18;
-			height = 28;
-		}
-		2001
-		{
-			title = "HHZ Grass";
-			sprite = "HHZMA0";
-			width = 16;
-			height = 40;
-		}
-		2002
-		{
-			title = "HHZ Tentacle 1";
-			sprite = "HHZMB0";
-			width = 16;
-			height = 40;
-		}
-		2003
-		{
-			title = "HHZ Tentacle 2";
-			sprite = "HHZMC0";
-			width = 16;
-			height = 40;
-		}
-		2004
-		{
-			title = "HHZ Stalagmite (Tall)";
-			sprite = "HHZME0";
-			width = 16;
-			height = 40;
-		}
-		2005
-		{
-			title = "HHZ Stalagmite (Short)";
-			sprite = "HHZMF0";
-			width = 16;
-			height = 40;
-		}
-		2006
-		{
-			title = "Jack-o'-lantern 1";
-			sprite = "PUMKA0";
-			width = 16;
-			height = 40;
-		}
-		2007
-		{
-			title = "Jack-o'-lantern 2";
-			sprite = "PUMKB0";
-			width = 16;
-			height = 40;
-		}
-		2008
-		{
-			title = "Jack-o'-lantern 3";
-			sprite = "PUMKC0";
-			width = 16;
-			height = 40;
-		}
-		2009
-		{
-			title = "Purple Mushroom";
-			sprite = "SHRMD0";
-			width = 16;
-			height = 48;
-		}
-		2010
-		{
-			title = "HHZ Tree";
-			sprite = "HHPLC0";
-			width = 12;
-			height = 40;
-		}
-	}
-
-	frozenhillside
-	{
-		color = 10; // Green
-		title = "Frozen Hillside";
-
-		2100
-		{
-			title = "Ice Shard (Small)";
-			sprite = "FHZIA0";
-			width = 8;
-			height = 32;
-		}
-		2101
-		{
-			title = "Ice Shard (Large)";
-			sprite = "FHZIB0";
-			width = 8;
-			height = 32;
-		}
-		2102
-		{
-			title = "Crystal Tree (Aqua)";
-			sprite = "TRE3A0";
-			width = 20;
-			height = 200;
-		}
-		2103
-		{
-			title = "Crystal Tree (Pink)";
-			sprite = "TRE3B0";
-			width = 20;
-			height = 200;
-		}
-		2104
-		{
-			title = "Amy Cameo";
-			sprite = "ROSYA1";
-			width = 16;
-			height = 48;
-		}
-		2105
-		{
-			title = "Mistletoe";
-			sprite = "XMS6A0";
-			width = 52;
-			height = 106;
-		}
-	}
-
-	tutorial
-	{
-		color = 10; // Green
-		title = "Tutorial";
-
-		799
-		{
-			title = "Tutorial Plant";
-			sprite = "TUPFH0";
-			width = 40;
-			height = 144;
-		}
-	}
-
-	flickies
-	{
-		color = 10; // Green
-		title = "Flickies";
-		width = 8;
-		height = 20;
-
-		2200
-		{
-			title = "Bluebird";
-			sprite = "FL01A1";
-		}
-		2201
-		{
-			title = "Rabbit";
-			sprite = "FL02A1";
-		}
-		2202
-		{
-			title = "Chicken";
-			sprite = "FL03A1";
-		}
-		2203
-		{
-			title = "Seal";
-			sprite = "FL04A1";
-		}
-		2204
-		{
-			title = "Pig";
-			sprite = "FL05A1";
-		}
-		2205
-		{
-			title = "Chipmunk";
-			sprite = "FL06A1";
-		}
-		2206
-		{
-			title = "Penguin";
-			sprite = "FL07A1";
-		}
-		2207
-		{
-			title = "Fish";
-			sprite = "FL08A1";
-		}
-		2208
-		{
-			title = "Ram";
-			sprite = "FL09A1";
-		}
-		2209
-		{
-			title = "Puffin";
-			sprite = "FL10A1";
-		}
-		2210
-		{
-			title = "Cow";
-			sprite = "FL11A1";
-		}
-		2211
-		{
-			title = "Rat";
-			sprite = "FL12A1";
-		}
-		2212
-		{
-			title = "Bear";
-			sprite = "FL13A1";
-		}
-		2213
-		{
-			title = "Dove";
-			sprite = "FL14A1";
-		}
-		2214
-		{
-			title = "Cat";
-			sprite = "FL15A1";
-		}
-		2215
-		{
-			title = "Canary";
-			sprite = "FL16A1";
-		}
-		2216
-		{
-			title = "Spider";
-			sprite = "FS01A1";
-		}
-		2217
-		{
-			title = "Bat";
-			sprite = "FS02A0";
-		}
-	}
-}
-
 udmf
 {
-	editor
-	{
-		color = 15; // White
-		arrow = 1;
-		title = "<Editor Things>";
-		error = -1;
-		width = 8;
-		height = 16;
-		sort = 1;
-
-		3328 = "3D Mode Start";
-	}
 
 	starts
 	{
@@ -3974,7 +807,7 @@ udmf
 
 	bosses
 	{
-		color = 8; // Dark_Gray
+		color = 4; // Dark Red
 		arrow = 1;
 		title = "Bosses";
 
@@ -4355,95 +1188,185 @@ udmf
 		width = 24;
 		height = 24;
 		sprite = "RINGA0";
-		arg0
-		{
-			title = "Float?";
-			type = 11;
-			enum = "yesno";
-		}
 
 		300
 		{
 			title = "Ring";
 			sprite = "RINGA0";
 			width = 16;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		301
 		{
 			title = "Bounce Ring";
-			sprite = "internal:RNGBA0";
+			sprite = "RNGBA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		302
 		{
 			title = "Rail Ring";
-			sprite = "internal:RNGRA0";
+			sprite = "RNGRA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		303
 		{
 			title = "Infinity Ring";
-			sprite = "internal:RNGIA0";
+			sprite = "RNGIA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		304
 		{
 			title = "Automatic Ring";
-			sprite = "internal:RNGAA0";
+			sprite = "RNGAA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		305
 		{
 			title = "Explosion Ring";
-			sprite = "internal:RNGEA0";
+			sprite = "RNGEA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		306
 		{
 			title = "Scatter Ring";
-			sprite = "internal:RNGSA0";
+			sprite = "RNGSA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		307
 		{
 			title = "Grenade Ring";
-			sprite = "internal:RNGGA0";
+			sprite = "RNGGA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		308
 		{
 			title = "CTF Team Ring (Red)";
-			sprite = "internal:RRNGA0";
+			sprite = "internal:TRNGA0R";
 			width = 16;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		309
 		{
 			title = "CTF Team Ring (Blue)";
-			sprite = "internal:BRNGA0";
+			sprite = "internal:TRNGA0B";
 			width = 16;
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		330
 		{
 			title = "Bounce Ring Panel";
-			sprite = "internal:PIKBA0";
+			sprite = "PIKBA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		331
 		{
 			title = "Rail Ring Panel";
-			sprite = "internal:PIKRA0";
+			sprite = "PIKRA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		332
 		{
 			title = "Automatic Ring Panel";
-			sprite = "internal:PIKAA0";
+			sprite = "PIKAA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		333
 		{
 			title = "Explosion Ring Panel";
-			sprite = "internal:PIKEA0";
+			sprite = "PIKEA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		334
 		{
 			title = "Scatter Ring Panel";
-			sprite = "internal:PIKSA0";
+			sprite = "PIKSA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 		335
 		{
 			title = "Grenade Ring Panel";
-			sprite = "internal:PIKGA0";
+			sprite = "PIKGA0";
+			arg0
+			{
+				title = "Float?";
+				type = 11;
+				enum = "yesno";
+			}
 		}
 	}
 
@@ -4562,16 +1485,16 @@ udmf
 		title = "Monitors";
 		width = 18;
 		height = 40;
-		arg0
-		{
-			title = "Death trigger tag";
-			type = 15;
-		}
 
 		400
 		{
 			title = "Super Ring (10 Rings)";
 			sprite = "TVRIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4583,6 +1506,11 @@ udmf
 		{
 			title = "Pity Shield";
 			sprite = "TVPIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4594,6 +1522,11 @@ udmf
 		{
 			title = "Attraction Shield";
 			sprite = "TVATA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4605,6 +1538,11 @@ udmf
 		{
 			title = "Force Shield";
 			sprite = "TVFOA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4616,6 +1554,11 @@ udmf
 		{
 			title = "Armageddon Shield";
 			sprite = "TVARA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4627,6 +1570,11 @@ udmf
 		{
 			title = "Whirlwind Shield";
 			sprite = "TVWWA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4638,6 +1586,11 @@ udmf
 		{
 			title = "Elemental Shield";
 			sprite = "TVELA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4649,6 +1602,11 @@ udmf
 		{
 			title = "Super Sneakers";
 			sprite = "TVSSA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4660,6 +1618,11 @@ udmf
 		{
 			title = "Invincibility";
 			sprite = "TVIVA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4671,6 +1634,11 @@ udmf
 		{
 			title = "Extra Life";
 			sprite = "TV1UA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4692,11 +1660,21 @@ udmf
 		{
 			title = "Eggman";
 			sprite = "TVEGA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		411
 		{
 			title = "Teleporter";
 			sprite = "TVMXA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4708,21 +1686,41 @@ udmf
 		{
 			title = "Gravity Boots";
 			sprite = "TVGVA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		414
 		{
 			title = "CTF Team Ring Monitor (Red)";
 			sprite = "TRRIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		415
 		{
 			title = "CTF Team Ring Monitor (Blue)";
 			sprite = "TBRIA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		416
 		{
 			title = "Recycler";
 			sprite = "TVRCA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4734,16 +1732,31 @@ udmf
 		{
 			title = "Score (1,000 Points)";
 			sprite = "TV1KA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		419
 		{
 			title = "Score (10,000 Points)";
 			sprite = "TVTKA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		420
 		{
 			title = "Flame Shield";
 			sprite = "TVFLA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4755,6 +1768,11 @@ udmf
 		{
 			title = "Water Shield";
 			sprite = "TVBBA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4766,6 +1784,11 @@ udmf
 		{
 			title = "Lightning Shield";
 			sprite = "TVZPA0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 			arg1
 			{
 				title = "Respawn behavior";
@@ -4782,76 +1805,136 @@ udmf
 		title = "Monitors (Respawning)";
 		width = 20;
 		height = 44;
-		arg0
-		{
-			title = "Death trigger tag";
-			type = 15;
-		}
 
 		431
 		{
 			title = "Pity Shield (Respawn)";
 			sprite = "TVPIB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		432
 		{
 			title = "Attraction Shield (Respawn)";
 			sprite = "TVATB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		433
 		{
 			title = "Force Shield (Respawn)";
 			sprite = "TVFOB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		434
 		{
 			title = "Armageddon Shield (Respawn)";
 			sprite = "TVARB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		435
 		{
 			title = "Whirlwind Shield (Respawn)";
 			sprite = "TVWWB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		436
 		{
 			title = "Elemental Shield (Respawn)";
 			sprite = "TVELB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		437
 		{
 			title = "Super Sneakers (Respawn)";
 			sprite = "TVSSB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		438
 		{
 			title = "Invincibility (Respawn)";
 			sprite = "TVIVB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		440
 		{
 			title = "Eggman (Respawn)";
 			sprite = "TVEGB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		443
 		{
 			title = "Gravity Boots (Respawn)";
 			sprite = "TVGVB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		450
 		{
 			title = "Flame Shield (Respawn)";
 			sprite = "TVFLB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		451
 		{
 			title = "Water Shield (Respawn)";
 			sprite = "TVBBB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 		452
 		{
 			title = "Lightning Shield (Respawn)";
 			sprite = "TVZPB0";
+			arg0
+			{
+				title = "Death trigger tag";
+				type = 15;
+			}
 		}
 	}
 
@@ -5235,13 +2318,13 @@ udmf
 		{
 			arrow = 0;
 			title = "5 Vertical Rings (Yellow Spring)";
-			sprite = "RINGA0";
+			sprite = "internal:ringverticalyellow";
 		}
 		601
 		{
 			arrow = 0;
 			title = "5 Vertical Rings (Red Spring)";
-			sprite = "RINGA0";
+			sprite = "internal:ringverticalred";
 			height = 1024;
 		}
 		602
@@ -5259,41 +2342,47 @@ udmf
 		604
 		{
 			title = "Circle of Rings";
-			sprite = "RINGA0";
+			sprite = "internal:circlering";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 		}
 		605
 		{
 			title = "Circle of Rings (Big)";
-			sprite = "RINGA0";
+			sprite = "internal:circlebigring";
 			width = 192;
+			centerhitbox = true;
 		}
 		606
 		{
 			title = "Circle of Blue Spheres";
-			sprite = "SPHRA0";
+			sprite = "internal:circlesphere";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 		}
 		607
 		{
 			title = "Circle of Blue Spheres (Big)";
-			sprite = "SPHRA0";
+			sprite = "internal:circlebigsphere";
 			width = 192;
+			centerhitbox = true;
 		}
 		608
 		{
 			title = "Circle of Rings and Spheres";
-			sprite = "SPHRA0";
+			sprite = "internal:circleringsphere";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 		}
 		609
 		{
 			title = "Circle of Rings and Spheres (Big)";
-			sprite = "SPHRA0";
+			sprite = "internal:circlebigringsphere";
 			width = 192;
+			centerhitbox = true;
 		}
 		610
 		{
@@ -5322,6 +2411,7 @@ udmf
 			sprite = "RINGA0";
 			width = 96;
 			height = 192;
+			centerhitbox = true;
 			arg0
 			{
 				title = "Number of items";
@@ -5429,7 +2519,7 @@ udmf
 		756
 		{
 			title = "Blast Linedef Executor";
-			sprite = "TOADA0";
+			sprite = "internal:blastexec";
 			width = 32;
 			height = 16;
 			arg0
@@ -5441,7 +2531,7 @@ udmf
 		757
 		{
 			title = "Fan Particle Generator";
-			sprite = "PRTLA0";
+			sprite = "internal:fanparticles";
 			width = 8;
 			height = 16;
 			arg0
@@ -5490,11 +2580,6 @@ udmf
 			title = "PolyObject Spawn Point";
 			sprite = "internal:polycenter";
 		}
-		762
-		{
-			title = "PolyObject Spawn Point (Crush)";
-			sprite = "internal:polycentercrush";
-		}
 		780
 		{
 			title = "Skybox View Point";
@@ -5514,7 +2599,7 @@ udmf
 
 	greenflower
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Greenflower";
 
 		800
@@ -5619,7 +2704,7 @@ udmf
 
 	technohill
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Techno Hill";
 
 		900
@@ -5663,7 +2748,7 @@ udmf
 
 	deepsea
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Deep Sea";
 
 		1000
@@ -5823,7 +2908,7 @@ udmf
 
 	castleeggman
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Castle Eggman";
 
 		1100
@@ -6386,7 +3471,7 @@ udmf
 
 	aridcanyon
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Arid Canyon";
 
 		1200
@@ -6511,6 +3596,7 @@ udmf
 			sprite = "WWSGAR";
 			width = 22;
 			height = 64;
+			wallsprite = true;
 		}
 		1213
 		{
@@ -6518,6 +3604,7 @@ udmf
 			sprite = "WWS2AR";
 			width = 22;
 			height = 64;
+			wallsprite = true;
 		}
 		1214
 		{
@@ -6525,6 +3612,7 @@ udmf
 			sprite = "WWS3ALAR";
 			width = 16;
 			height = 192;
+			wallsprite = true;
 		}
 		1215
 		{
@@ -6644,7 +3732,7 @@ udmf
 
 	redvolcano
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Red Volcano";
 
 		1300
@@ -6789,7 +3877,7 @@ udmf
 
 	botanicserenity
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Botanic Serenity";
 		width = 16;
 		height = 32;
@@ -7032,7 +4120,7 @@ udmf
 
 	azuretemple
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Azure Temple";
 
 		1500
@@ -7144,7 +4232,7 @@ udmf
 
 	dreamhill
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Dream Hill";
 
 		1600
@@ -7178,8 +4266,8 @@ udmf
 
 	nightstrk
 	{
-		color = 13; // Pink
-		title = "NiGHTS Track";
+		color = 16; // Light Pink
+		title = "NiGHTS Track & Misc.";
 		width = 8;
 		height = 4096;
 		sprite = "UNKNA0";
@@ -7207,8 +4295,8 @@ udmf
 				type = 11;
 				enum
 				{
-					0 = "Clockwise";
-					1 = "Counterclockwise";
+					0 = "Counterclockwise";
+					1 = "Clockwise";
 				}
 			}
 		}
@@ -7238,30 +4326,6 @@ udmf
 				title = "Order";
 			}
 		}
-		1710
-		{
-			title = "Ideya Capture";
-			sprite = "CAPSA0";
-			width = 72;
-			height = 144;
-			arg0
-			{
-				title = "Mare";
-			}
-			arg1
-			{
-				title = "Required spheres";
-			}
-		}
-	}
-
-	nights
-	{
-		color = 13; // Pink
-		title = "NiGHTS Items";
-		width = 16;
-		height = 32;
-
 		1703
 		{
 			title = "Ideya Drone";
@@ -7294,11 +4358,46 @@ udmf
 			}
 			arg4
 			{
-				title = "Die upon time up?";
-				type = 11;
-				enum = "noyes";
+				title = "Die upon time up?";
+				type = 11;
+				enum = "noyes";
+			}
+		}
+		1710
+		{
+			title = "Ideya Capture";
+			sprite = "CAPSA0";
+			width = 72;
+			height = 144;
+			arg0
+			{
+				title = "Mare";
+			}
+			arg1
+			{
+				title = "Required spheres";
+			}
+		}
+		1714
+		{
+			title = "Ideya Anchor Point";
+			sprite = "internal:ideya";
+			width = 8;
+			height = 16;
+			arg0
+			{
+				title = "Mare";
 			}
 		}
+	}
+
+	nights
+	{
+		color = 13; // Pink
+		title = "NiGHTS Items";
+		width = 16;
+		height = 32;
+		
 		1704
 		{
 			arrow = 1;
@@ -7399,25 +4498,15 @@ udmf
 		{
 			arrow = 1;
 			title = "Hoop";
-			sprite = "HOOPA0";
+			sprite = "internal:nightshoop";
 			width = 80;
 			height = 160;
+			centerhitbox = true;
 			arg0
 			{
 				title = "Radius";
 			}
 		}
-		1714
-		{
-			title = "Ideya Anchor Point";
-			sprite = "internal:axis1";
-			width = 8;
-			height = 16;
-			arg0
-			{
-				title = "Mare";
-			}
-		}
 	}
 
 	mario
@@ -7528,7 +4617,7 @@ udmf
 
 	christmasdisco
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Christmas & Disco";
 
 		1850
@@ -7643,7 +4732,7 @@ udmf
 
 	stalagmites
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Stalagmites";
 		width = 16;
 		height = 40;
@@ -7722,7 +4811,7 @@ udmf
 
 	hauntedheights
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Haunted Heights";
 
 		2000
@@ -7828,7 +4917,7 @@ udmf
 
 	frozenhillside
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Frozen Hillside";
 
 		2100
@@ -7883,7 +4972,7 @@ udmf
 
 	tutorial
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Tutorial";
 
 		799
@@ -7901,65 +4990,170 @@ udmf
 
 	flickies
 	{
-		color = 10; // Green
+		color = 2; // Green
 		title = "Flickies";
 		width = 8;
 		height = 20;
-		arg0
-		{
-			title = "Radius";
-		}
-		arg1
-		{
-			title = "Flags";
-			type = 12;
-			enum
-			{
-				1 = "Move aimlessly";
-				2 = "No movement";
-				4 = "Hop";
-			}
-		}
 
 		2200
 		{
 			title = "Bluebird";
 			sprite = "FL01A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2201
 		{
 			title = "Rabbit";
 			sprite = "FL02A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2202
 		{
 			title = "Chicken";
 			sprite = "FL03A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2203
 		{
 			title = "Seal";
 			sprite = "FL04A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2204
 		{
 			title = "Pig";
 			sprite = "FL05A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2205
 		{
 			title = "Chipmunk";
 			sprite = "FL06A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2206
 		{
 			title = "Penguin";
 			sprite = "FL07A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2207
 		{
 			title = "Fish";
 			sprite = "FL08A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 			arg2
 			{
 				title = "Color";
@@ -7989,51 +5183,214 @@ udmf
 		{
 			title = "Ram";
 			sprite = "FL09A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2209
 		{
 			title = "Puffin";
 			sprite = "FL10A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2210
 		{
 			title = "Cow";
 			sprite = "FL11A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2211
 		{
 			title = "Rat";
 			sprite = "FL12A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2212
 		{
 			title = "Bear";
 			sprite = "FL13A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2213
 		{
 			title = "Dove";
 			sprite = "FL14A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2214
 		{
 			title = "Cat";
 			sprite = "FL15A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2215
 		{
 			title = "Canary";
 			sprite = "FL16A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2216
 		{
 			title = "Spider";
 			sprite = "FS01A1";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 		2217
 		{
 			title = "Bat";
 			sprite = "FS02A0";
+			arg0
+			{
+				title = "Radius";
+			}
+			arg1
+			{
+				title = "Flags";
+				type = 12;
+				enum
+				{
+					1 = "Move aimlessly";
+					2 = "No movement";
+					4 = "Hop";
+				}
+			}
 		}
 	}
-}
+	
+	editor
+	{
+		color = 15; // White
+		arrow = 1;
+		title = "3D Mode Start";
+		error = -1;
+		width = 8;
+		height = 16;
+		sort = 1;
+
+		3328 = "3D Mode Start";
+	}
+}
\ No newline at end of file
diff --git a/extras/conf/udb/SRB2_22Doom.cfg b/extras/conf/udb/SRB2_22Doom.cfg
deleted file mode 100644
index 9e733aa394581a8d48dab2c592e46008b2142f11..0000000000000000000000000000000000000000
--- a/extras/conf/udb/SRB2_22Doom.cfg
+++ /dev/null
@@ -1,32 +0,0 @@
-/************************************************************************\
-	Ultimate Doom Builder Game Configuration for Sonic Robo Blast 2 Version 2.2
-\************************************************************************/
-
-// This is required to prevent accidental use of a different configuration
-type = "Doom Builder 2 Game Configuration";
-
-// This is the title to show for this game
-game = "Sonic Robo Blast 2 - 2.2 (Doom format)";
-
-// This is the simplified game engine/sourceport name
-engine = "zdoom";
-
-// Settings common to all games and all map formats
-include("Includes\\SRB222_common.cfg", "common");
-
-// Settings common to Doom map format
-include("Includes\\SRB222_common.cfg", "mapformat_doom");
-
-include("Includes\\Game_SRB222.cfg");
-
-// Script lumps detection
-scriptlumpnames
-{
-	include("Includes\\SRB222_misc.cfg", "scriptlumpnames");
-}
-
-//Default things filters
-thingsfilters
-{
-	include("Includes\\SRB222_misc.cfg", "thingsfilters");
-}
\ No newline at end of file
diff --git a/src/console.c b/src/console.c
index 88e7bc2aed4dda92efd15d9cfb62c7933b2cfd69..6d273f62076b0b544b27acf1b22bf313009fc8b6 100644
--- a/src/console.c
+++ b/src/console.c
@@ -395,16 +395,16 @@ static void CON_SetupColormaps(void)
 
 	//                      0x1       0x3                           0x9                           0xF
 	colset(magentamap, 177, 177, 178, 178, 178, 180, 180, 180, 182, 182, 182, 182, 184, 184, 184, 185);
-	colset(yellowmap,   82,  82,  73,  73,  73,  64,  64,  64,  66,  66,  66,  66,  67,  67,  67,  68);
-	colset(lgreenmap,   96,  96,  98,  98,  98, 101, 101, 101, 104, 104, 104, 104, 106, 106, 106, 107);
-	colset(bluemap,    146, 146, 147, 147, 147, 149, 149, 149, 152, 152, 152, 152, 155, 155, 155, 157);
-	colset(redmap,      32,  32,  33,  33,  33,  35,  35,  35,  39,  39,  39,  39,  42,  42,  42,  44);
+	colset(yellowmap,   82,  82,  73,  73,  73,  74,  74,  74,  66,  66,  66,  66,  67,  67,  67,  68);
+	colset(lgreenmap,   96,  96,  98,  98,  98, 100, 100, 100, 103, 103, 103, 103, 105, 105, 105, 107);
+	colset(bluemap,    146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 149, 150, 150, 150, 151);
+	colset(redmap,      32,  32,  33,  33,  33,  34,  34,  34,  35,  35,  35,  35,  37,  37,  37,  39);
 	colset(graymap,      8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23);
 	colset(orangemap,   50,  50,  52,  52,  52,  54,  54,  54,  56,  56,  56,  56,  59,  59,  59,  60);
 	colset(skymap,     129, 129, 130, 130, 130, 131, 131, 131, 133, 133, 133, 133, 135, 135, 135, 136);
 	colset(purplemap,  160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 163, 164, 164, 164, 165);
 	colset(aquamap,    120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 123, 124, 124, 124, 125);
-	colset(peridotmap,  72,  72, 188, 188, 189, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191,  94);
+	colset(peridotmap,  73,  73, 188, 188, 188, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191,  94);
 	colset(azuremap,   144, 144, 145, 145, 145, 146, 146, 146, 170, 170, 170, 170, 171, 171, 171, 172);
 	colset(brownmap,   219, 219, 221, 221, 221, 222, 222, 222, 224, 224, 224, 224, 227, 227, 227, 229);
 	colset(rosymap,    200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 203, 204, 204, 204, 205);
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 64e5aff6b295e464997b98ea7ff9ee1e0e1e2730..ca9f4a24e73094ab2bcbb369e28903917bad86da 100755
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1318,9 +1318,9 @@ static boolean CL_SendJoin(void)
 
 static INT32 FindRejoinerNum(SINT8 node)
 {
-	char strippednodeaddress[64];
+	char addressbuffer[64];
 	const char *nodeaddress;
-	char *port;
+	const char *strippednodeaddress;
 	INT32 i;
 
 	// Make sure there is no dead dress before proceeding to the stripping
@@ -1331,10 +1331,8 @@ static INT32 FindRejoinerNum(SINT8 node)
 		return -1;
 
 	// Strip the address of its port
-	strcpy(strippednodeaddress, nodeaddress);
-	port = strchr(strippednodeaddress, ':');
-	if (port)
-		*port = '\0';
+	strcpy(addressbuffer, nodeaddress);
+	strippednodeaddress = I_NetSplitAddress(addressbuffer, NULL);
 
 	// Check if any player matches the stripped address
 	for (i = 0; i < MAXPLAYERS; i++)
@@ -2487,7 +2485,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
 		{
 			if (!snake)
 			{
-				F_MenuPresTicker(true); // title sky
+				F_MenuPresTicker(); // title sky
 				F_TitleScreenTicker(true);
 				F_TitleScreenDrawer();
 			}
@@ -3645,6 +3643,9 @@ void SV_ResetServer(void)
 
 	CV_RevertNetVars();
 
+	// Ensure synched when creating a new server
+	M_CopyGameData(serverGamedata, clientGamedata);
+
 	DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
 }
 
@@ -3768,14 +3769,13 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 
 		if (server && I_GetNodeAddress)
 		{
+			char addressbuffer[64];
 			const char *address = I_GetNodeAddress(node);
-			char *port = NULL;
 			if (address) // MI: fix msvcrt.dll!_mbscat crash?
 			{
-				strcpy(playeraddress[newplayernum], address);
-				port = strchr(playeraddress[newplayernum], ':');
-				if (port)
-					*port = '\0';
+				strcpy(addressbuffer, address);
+				strcpy(playeraddress[newplayernum],
+						I_NetSplitAddress(addressbuffer, NULL));
 			}
 		}
 	}
diff --git a/src/d_main.c b/src/d_main.c
index 6506c9d4ee40687a557ad3cc031ec08451413d73..5861f988655c62e9b812b13d48832c940f66ebc2 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1350,6 +1350,9 @@ void D_SRB2Main(void)
 	CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n");
 	Z_Init();
 
+	clientGamedata = M_NewGameDataStruct();
+	serverGamedata = M_NewGameDataStruct();
+
 	// Do this up here so that WADs loaded through the command line can use ExecCfg
 	COM_Init();
 
@@ -1479,7 +1482,9 @@ void D_SRB2Main(void)
 		// confusion issues when loading mods.
 		strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename);
 	}
-	G_LoadGameData();
+
+	G_LoadGameData(clientGamedata);
+	M_CopyGameData(serverGamedata, clientGamedata);
 
 #if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL)
 	VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen
@@ -1710,7 +1715,7 @@ void D_SRB2Main(void)
 			// ... unless you're in a dedicated server.  Yes, technically this means you can view any level by
 			// running a dedicated server and joining it yourself, but that's better than making dedicated server's
 			// lives hell.
-			else if (!dedicated && M_MapLocked(pstartmap))
+			else if (!dedicated && M_MapLocked(pstartmap, serverGamedata))
 				I_Error("You need to unlock this level before you can warp to it!\n");
 			else
 			{
diff --git a/src/d_net.c b/src/d_net.c
index 7de3dba56ab9b660a89a09005485fe809810c306..768c9ac7eb898e67967e9836695fa6c109d6748f 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -1207,26 +1207,32 @@ static void Internal_FreeNodenum(INT32 nodenum)
 	(void)nodenum;
 }
 
+char *I_NetSplitAddress(char *host, char **port)
+{
+	boolean v4 = (strchr(host, '.') != NULL);
+
+	host = strtok(host, v4 ? ":" : "[]");
+
+	if (port)
+		*port = strtok(NULL, ":");
+
+	return host;
+}
+
 SINT8 I_NetMakeNode(const char *hostname)
 {
 	SINT8 newnode = -1;
 	if (I_NetMakeNodewPort)
 	{
 		char *localhostname = strdup(hostname);
-		char  *t = localhostname;
-		const char *port;
+		char *port;
 		if (!localhostname)
 			return newnode;
-		// retrieve portnum from address!
-		strtok(localhostname, ":");
-		port = strtok(NULL, ":");
 
-		// remove the port in the hostname as we've it already
-		while ((*t != ':') && (*t != '\0'))
-			t++;
-		*t = '\0';
+		// retrieve portnum from address!
+		hostname = I_NetSplitAddress(localhostname, &port);
 
-		newnode = I_NetMakeNodewPort(localhostname, port);
+		newnode = I_NetMakeNodewPort(hostname, port);
 		free(localhostname);
 	}
 	return newnode;
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 80a084e1677208ac892648dd15c4ad3ed0d5585b..af44e53d63540ddfcde7cdb51e927522533db243 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2036,7 +2036,7 @@ static void Command_Map_f(void)
 	// ... unless you're in a dedicated server.  Yes, technically this means you can view any level by
 	// running a dedicated server and joining it yourself, but that's better than making dedicated server's
 	// lives hell.
-	if (!dedicated && M_MapLocked(newmapnum))
+	if (!dedicated && M_MapLocked(newmapnum, serverGamedata))
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
 		Z_Free(realmapname);
@@ -3945,18 +3945,12 @@ void ItemFinder_OnChange(void)
 	if (!cv_itemfinder.value)
 		return; // it's fine.
 
-	if (!M_SecretUnlocked(SECRET_ITEMFINDER))
+	if (!M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata))
 	{
 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
 		CV_StealthSetValue(&cv_itemfinder, 0);
 		return;
 	}
-	else if (netgame || multiplayer)
-	{
-		CONS_Printf(M_GetText("This only works in single player.\n"));
-		CV_StealthSetValue(&cv_itemfinder, 0);
-		return;
-	}
 }
 
 /** Deals with a pointlimit change by printing the change to the console.
@@ -4305,7 +4299,7 @@ void D_GameTypeChanged(INT32 lastgametype)
 
 static void Ringslinger_OnChange(void)
 {
-	if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && cv_ringslinger.value && !cv_debug)
+	if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !netgame && cv_ringslinger.value && !cv_debug)
 	{
 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
 		CV_StealthSetValue(&cv_ringslinger, 0);
@@ -4318,7 +4312,7 @@ static void Ringslinger_OnChange(void)
 
 static void Gravity_OnChange(void)
 {
-	if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && !cv_debug
+	if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !netgame && !cv_debug
 		&& strcmp(cv_gravity.string, cv_gravity.defaultvalue))
 	{
 		CONS_Printf(M_GetText("You haven't earned this yet.\n"));
diff --git a/src/deh_lua.c b/src/deh_lua.c
index 068fe4f23806b70504318c27c81b9f27e1bbdbe7..6dabb7e2d9f2b9a95dd389f9363ba05d67da6487 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -10,20 +10,7 @@
 /// \file  deh_lua.c
 /// \brief Lua SOC library
 
-#include "g_game.h"
-#include "s_sound.h"
-#include "z_zone.h"
-#include "m_menu.h"
-#include "m_misc.h"
-#include "p_local.h"
-#include "st_stuff.h"
-#include "fastcmp.h"
-#include "lua_script.h"
-#include "lua_libs.h"
-
-#include "dehacked.h"
 #include "deh_lua.h"
-#include "deh_tables.h"
 
 // freeslot takes a name (string only!)
 // and allocates it to the appropriate free slot.
@@ -89,6 +76,8 @@ static inline int lib_freeslot(lua_State *L)
 				strncpy(sprnames[j],word,4);
 				//sprnames[j][4] = 0;
 				used_spr[(j-SPR_FIRSTFREESLOT)/8] |= 1<<(j%8); // Okay, this sprite slot has been named now.
+				// Lua needs to update the value in _G if it exists
+				LUA_UpdateSprName(word, j);
 				lua_pushinteger(L, j);
 				r++;
 				break;
@@ -219,18 +208,27 @@ static int lib_dummysuper(lua_State *L)
 	return luaL_error(L, "Can't call super() outside of hardcode-replacing A_Action functions being called by state changes!"); // convoluted, I know. @_@;;
 }
 
-static inline int lib_getenum(lua_State *L)
+static void CacheAndPushConstant(lua_State *L, const char *name, lua_Integer value)
 {
-	const char *word, *p;
+	// "cache" into _G
+	lua_pushstring(L, name);
+	lua_pushinteger(L, value);
+	lua_rawset(L, LUA_GLOBALSINDEX);
+	// push
+	lua_pushinteger(L, value);
+}
+
+// Search for a matching constant variable.
+// Result is stored into _G for faster subsequent use. (Except for SPR_ in the SOC parser)
+static int ScanConstants(lua_State *L, boolean mathlib, const char *word)
+{
+	const char *p;
 	fixed_t i;
-	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
-	if (lua_type(L,2) != LUA_TSTRING)
-		return 0;
-	word = lua_tostring(L,2);
+
 	if (strlen(word) == 1) { // Assume sprite frame if length 1.
 		if (*word >= 'A' && *word <= '~')
 		{
-			lua_pushinteger(L, *word-'A');
+			CacheAndPushConstant(L, word, *word-'A');
 			return 1;
 		}
 		if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
@@ -240,7 +238,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; MOBJFLAG_LIST[i]; i++)
 			if (fastcmp(p, MOBJFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "mobjflag '%s' could not be found.\n", word);
@@ -250,7 +248,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; MOBJFLAG2_LIST[i]; i++)
 			if (fastcmp(p, MOBJFLAG2_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "mobjflag2 '%s' could not be found.\n", word);
@@ -260,12 +258,12 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; MOBJEFLAG_LIST[i]; i++)
 			if (fastcmp(p, MOBJEFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (fastcmp(p, "REVERSESUPER"))
 		{
-			lua_pushinteger(L, (lua_Integer)MFE_REVERSESUPER);
+			CacheAndPushConstant(L, word, (lua_Integer)MFE_REVERSESUPER);
 			return 1;
 		}
 		if (mathlib) return luaL_error(L, "mobjeflag '%s' could not be found.\n", word);
@@ -275,7 +273,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < 4; i++)
 			if (MAPTHINGFLAG_LIST[i] && fastcmp(p, MAPTHINGFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "mapthingflag '%s' could not be found.\n", word);
@@ -285,17 +283,17 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; PLAYERFLAG_LIST[i]; i++)
 			if (fastcmp(p, PLAYERFLAG_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (fastcmp(p, "FULLSTASIS"))
 		{
-			lua_pushinteger(L, (lua_Integer)PF_FULLSTASIS);
+			CacheAndPushConstant(L, word, (lua_Integer)PF_FULLSTASIS);
 			return 1;
 		}
 		else if (fastcmp(p, "USEDOWN")) // Remove case when 2.3 nears release...
 		{
-			lua_pushinteger(L, (lua_Integer)PF_SPINDOWN);
+			CacheAndPushConstant(L, word, (lua_Integer)PF_SPINDOWN);
 			return 1;
 		}
 		if (mathlib) return luaL_error(L, "playerflag '%s' could not be found.\n", word);
@@ -305,7 +303,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word;
 		for (i = 0; Gametype_ConstantNames[i]; i++)
 			if (fastcmp(p, Gametype_ConstantNames[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word);
@@ -315,7 +313,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; GAMETYPERULE_LIST[i]; i++)
 			if (fastcmp(p, GAMETYPERULE_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "game type rule '%s' could not be found.\n", word);
@@ -325,7 +323,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; TYPEOFLEVEL[i].name; i++)
 			if (fastcmp(p, TYPEOFLEVEL[i].name)) {
-				lua_pushinteger(L, TYPEOFLEVEL[i].flag);
+				CacheAndPushConstant(L, word, TYPEOFLEVEL[i].flag);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "typeoflevel '%s' could not be found.\n", word);
@@ -333,9 +331,10 @@ static inline int lib_getenum(lua_State *L)
 	}
 	else if (fastncmp("ML_", word, 3)) {
 		p = word+3;
+
 		for (i = 0; ML_LIST[i]; i++)
 			if (fastcmp(p, ML_LIST[i])) {
-				lua_pushinteger(L, ((lua_Integer)1<<i));
+				CacheAndPushConstant(L, word, ((lua_Integer)1<<i));
 				return 1;
 			}
 		// Aliases
@@ -418,13 +417,13 @@ static inline int lib_getenum(lua_State *L)
 			if (!FREE_STATES[i])
 				break;
 			if (fastcmp(p, FREE_STATES[i])) {
-				lua_pushinteger(L, S_FIRSTFREESLOT+i);
+				CacheAndPushConstant(L, word, S_FIRSTFREESLOT+i);
 				return 1;
 			}
 		}
 		for (i = 0; i < S_FIRSTFREESLOT; i++)
 			if (fastcmp(p, STATE_LIST[i]+2)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "state '%s' does not exist.\n", word);
@@ -435,13 +434,13 @@ static inline int lib_getenum(lua_State *L)
 			if (!FREE_MOBJS[i])
 				break;
 			if (fastcmp(p, FREE_MOBJS[i])) {
-				lua_pushinteger(L, MT_FIRSTFREESLOT+i);
+				CacheAndPushConstant(L, word, MT_FIRSTFREESLOT+i);
 				return 1;
 			}
 		}
 		for (i = 0; i < MT_FIRSTFREESLOT; i++)
 			if (fastcmp(p, MOBJTYPE_LIST[i]+3)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "mobjtype '%s' does not exist.\n", word);
@@ -450,7 +449,12 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMSPRITES; i++)
 			if (!sprnames[i][4] && fastncmp(p,sprnames[i],4)) {
-				lua_pushinteger(L, i);
+				// updating overridden sprnames is not implemented for soc parser,
+				// so don't use cache
+				if (mathlib)
+					lua_pushinteger(L, i);
+				else
+					CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "sprite '%s' could not be found.\n", word);
@@ -465,12 +469,12 @@ static inline int lib_getenum(lua_State *L)
 				// the spr2names entry will have "_" on the end, as in "RUN_"
 				if (spr2names[i][3] == '_' && !p[3]) {
 					if (fastncmp(p,spr2names[i],3)) {
-						lua_pushinteger(L, i);
+						CacheAndPushConstant(L, word, i);
 						return 1;
 					}
 				}
 				else if (fastncmp(p,spr2names[i],4)) {
-					lua_pushinteger(L, i);
+					CacheAndPushConstant(L, word, i);
 					return 1;
 				}
 			}
@@ -481,7 +485,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMSFX; i++)
 			if (S_sfx[i].name && fastcmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return 0;
@@ -490,7 +494,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMSFX; i++)
 			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "sfx '%s' could not be found.\n", word);
@@ -499,7 +503,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+2;
 		for (i = 0; i < NUMSFX; i++)
 			if (S_sfx[i].name && fasticmp(p, S_sfx[i].name)) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "sfx '%s' could not be found.\n", word);
@@ -509,7 +513,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; i < NUMPOWERS; i++)
 			if (fasticmp(p, POWERS_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return 0;
@@ -518,7 +522,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; i < NUMPOWERS; i++)
 			if (fastcmp(p, POWERS_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "power '%s' could not be found.\n", word);
@@ -527,7 +531,7 @@ static inline int lib_getenum(lua_State *L)
 		p = word+4;
 		for (i = 0; i < NUMHUDITEMS; i++)
 			if (fastcmp(p, HUDITEMS_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "huditem '%s' could not be found.\n", word);
@@ -539,13 +543,13 @@ static inline int lib_getenum(lua_State *L)
 			if (!FREE_SKINCOLORS[i])
 				break;
 			if (fastcmp(p, FREE_SKINCOLORS[i])) {
-				lua_pushinteger(L, SKINCOLOR_FIRSTFREESLOT+i);
+				CacheAndPushConstant(L, word, SKINCOLOR_FIRSTFREESLOT+i);
 				return 1;
 			}
 		}
 		for (i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++)
 			if (fastcmp(p, COLOR_ENUMS[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		return luaL_error(L, "skincolor '%s' could not be found.\n", word);
@@ -556,7 +560,7 @@ static inline int lib_getenum(lua_State *L)
 		for (i = 0; NIGHTSGRADE_LIST[i]; i++)
 			if (*p == NIGHTSGRADE_LIST[i])
 			{
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "NiGHTS grade '%s' could not be found.\n", word);
@@ -566,13 +570,41 @@ static inline int lib_getenum(lua_State *L)
 		p = word+3;
 		for (i = 0; i < NUMMENUTYPES; i++)
 			if (fastcmp(p, MENUTYPES_LIST[i])) {
-				lua_pushinteger(L, i);
+				CacheAndPushConstant(L, word, i);
 				return 1;
 			}
 		if (mathlib) return luaL_error(L, "menutype '%s' could not be found.\n", word);
 		return 0;
 	}
-	else if (!mathlib && fastncmp("A_",word,2)) {
+
+	if (fastcmp(word, "BT_USE")) // Remove case when 2.3 nears release...
+	{
+		CacheAndPushConstant(L, word, (lua_Integer)BT_SPIN);
+		return 1;
+	}
+
+	for (i = 0; INT_CONST[i].n; i++)
+		if (fastcmp(word,INT_CONST[i].n)) {
+			CacheAndPushConstant(L, word, INT_CONST[i].v);
+			return 1;
+		}
+
+	return 0;
+}
+
+static inline int lib_getenum(lua_State *L)
+{
+	const char *word;
+	fixed_t i;
+	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
+	if (lua_type(L,2) != LUA_TSTRING)
+		return 0;
+	word = lua_tostring(L,2);
+
+	// check actions, super and globals first, as they don't have _G caching implemented
+	// so they benefit from being checked first
+
+	if (!mathlib && fastncmp("A_",word,2)) {
 		char *caps;
 		// Try to get a Lua action first.
 		/// \todo Push a closure that sets superactions[] and superstack.
@@ -611,25 +643,34 @@ static inline int lib_getenum(lua_State *L)
 			}
 		return 0;
 	}
-
-	if (fastcmp(word, "BT_USE")) // Remove case when 2.3 nears release...
-	{
-		lua_pushinteger(L, (lua_Integer)BT_SPIN);
+	else if ((!mathlib && LUA_PushGlobals(L, word)) || ScanConstants(L, mathlib, word))
 		return 1;
-	}
-
-	for (i = 0; INT_CONST[i].n; i++)
-		if (fastcmp(word,INT_CONST[i].n)) {
-			lua_pushinteger(L, INT_CONST[i].v);
-			return 1;
-		}
 
 	if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
 
-	// DYNAMIC variables too!!
-	// Try not to add anything that would break netgames or timeattack replays here.
-	// You know, like consoleplayer, displayplayer, secondarydisplayplayer, or gametime.
-	return LUA_PushGlobals(L, word);
+	return 0;
+}
+
+// If a sprname has been "cached" to _G, update it to a new value.
+void LUA_UpdateSprName(const char *name, lua_Integer value)
+{
+	char fullname[9] = "SPR_XXXX";
+
+	if (!gL)
+		return;
+
+	strncpy(&fullname[4], name, 4);
+	lua_pushstring(gL, fullname);
+	lua_rawget(gL, LUA_GLOBALSINDEX);
+
+	if (!lua_isnil(gL, -1))
+	{
+		lua_pushstring(gL, name);
+		lua_pushinteger(gL, value);
+		lua_rawset(gL, LUA_GLOBALSINDEX);
+	}
+
+	lua_pop(gL, 1); // pop the rawget result
 }
 
 int LUA_EnumLib(lua_State *L)
diff --git a/src/deh_lua.h b/src/deh_lua.h
index c400351b8c576969f9c5293b2756a97be446f2d4..1bec371ccb42d5b76549103d9068c5e471825c98 100644
--- a/src/deh_lua.h
+++ b/src/deh_lua.h
@@ -13,6 +13,21 @@
 #ifndef __DEH_LUA_H__
 #define __DEH_LUA_H__
 
+#include "g_game.h"
+#include "s_sound.h"
+#include "z_zone.h"
+#include "m_menu.h"
+#include "m_misc.h"
+#include "p_local.h"
+#include "st_stuff.h"
+#include "fastcmp.h"
+#include "lua_script.h"
+#include "lua_libs.h"
+
+#include "dehacked.h"
+#include "deh_tables.h"
+
+void LUA_UpdateSprName(const char *name, lua_Integer value);
 boolean LUA_SetLuaAction(void *state, const char *actiontocompare);
 const char *LUA_GetActionName(void *action);
 void LUA_SetActionByName(void *state, const char *actiontocompare);
diff --git a/src/deh_soc.c b/src/deh_soc.c
index 3a3942c14f2fb7e6544bd35fd6b0625a8b373896..8dd849daf428ad1dce95dd52d30a105c5fbe2b3b 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -45,6 +45,7 @@
 #include "dehacked.h"
 #include "deh_soc.h"
 #include "deh_lua.h" // included due to some LUA_SetLuaAction hack smh
+// also used for LUA_UpdateSprName
 #include "deh_tables.h"
 
 // Loops through every constant and operation in word and performs its calculations, returning the final value.
@@ -439,6 +440,8 @@ void readfreeslots(MYFILE *f)
 					strncpy(sprnames[i],word,4);
 					//sprnames[i][4] = 0;
 					used_spr[(i-SPR_FIRSTFREESLOT)/8] |= 1<<(i%8); // Okay, this sprite slot has been named now.
+					// Lua needs to update the value in _G if it exists
+					LUA_UpdateSprName(word, i);
 					break;
 				}
 			}
@@ -3839,6 +3842,10 @@ void readmaincfg(MYFILE *f)
 			{
 				useContinues = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
 			}
+			else if (fastcmp(word, "SHAREEMBLEMS"))
+			{
+				shareEmblems = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
+			}
 
 			else if (fastcmp(word, "GAMEDATA"))
 			{
@@ -3849,7 +3856,7 @@ void readmaincfg(MYFILE *f)
 				if (!GoodDataFileName(word2))
 					I_Error("Maincfg: bad data file name '%s'\n", word2);
 
-				G_SaveGameData();
+				G_SaveGameData(clientGamedata);
 				strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
 				strlwr(gamedatafilename);
 				savemoddata = true;
diff --git a/src/dehacked.c b/src/dehacked.c
index 17768eb7f4496107503a50fedbbffc0da0812b0b..fd2a701715e17ce7dab0b6c829147113844972fe 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -575,7 +575,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 	} // end while
 
 	if (gamedataadded)
-		G_LoadGameData();
+		G_LoadGameData(clientGamedata);
 
 	if (gamestate == GS_TITLESCREEN)
 	{
diff --git a/src/doomstat.h b/src/doomstat.h
index 847c10b8c9835f737d47f4fd2b49412a27e03d76..a812cc304f6e0b19cab8f2fecf37868264ed9b16 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -132,8 +132,6 @@ extern INT32 postimgparam2;
 extern INT32 viewwindowx, viewwindowy;
 extern INT32 viewwidth, scaledviewwidth;
 
-extern boolean gamedataloaded;
-
 // Player taking events, and displaying.
 extern INT32 consoleplayer;
 extern INT32 displayplayer;
@@ -495,8 +493,6 @@ typedef struct
 extern tolinfo_t TYPEOFLEVEL[NUMTOLNAMES];
 extern UINT32 lastcustomtol;
 
-extern tic_t totalplaytime;
-
 extern boolean stagefailed;
 
 // Emeralds stored as bits to throw savegame hackers off.
@@ -515,52 +511,6 @@ extern INT32 luabanks[NUM_LUABANKS];
 
 extern INT32 nummaprings; //keep track of spawned rings/coins
 
-/** Time attack information, currently a very small structure.
-  */
-typedef struct
-{
-	tic_t time;   ///< Time in which the level was finished.
-	UINT32 score; ///< Score when the level was finished.
-	UINT16 rings; ///< Rings when the level was finished.
-} recorddata_t;
-
-/** Setup for one NiGHTS map.
-  * These are dynamically allocated because I am insane
-  */
-#define GRADE_F 0
-#define GRADE_E 1
-#define GRADE_D 2
-#define GRADE_C 3
-#define GRADE_B 4
-#define GRADE_A 5
-#define GRADE_S 6
-
-typedef struct
-{
-	// 8 mares, 1 overall (0)
-	UINT8	nummares;
-	UINT32	score[9];
-	UINT8	grade[9];
-	tic_t	time[9];
-} nightsdata_t;
-
-extern nightsdata_t *nightsrecords[NUMMAPS];
-extern recorddata_t *mainrecords[NUMMAPS];
-
-// mapvisited is now a set of flags that says what we've done in the map.
-#define MV_VISITED      1
-#define MV_BEATEN       2
-#define MV_ALLEMERALDS  4
-#define MV_ULTIMATE     8
-#define MV_PERFECT     16
-#define MV_PERFECTRA   32
-#define MV_MAX         63 // used in gamedata check, update whenever MV's are added
-#define MV_MP         128
-extern UINT8 mapvisited[NUMMAPS];
-
-// Temporary holding place for nights data for the current map
-extern nightsdata_t ntemprecords;
-
 extern UINT32 token; ///< Number of tokens collected in a level
 extern UINT32 tokenlist; ///< List of tokens collected
 extern boolean gottoken; ///< Did you get a token? Used for end of act
@@ -593,9 +543,12 @@ extern UINT8 useBlackRock;
 
 extern UINT8 use1upSound;
 extern UINT8 maxXtraLife; // Max extra lives from rings
+
 extern UINT8 useContinues;
 #define continuesInSession (!multiplayer && (ultimatemode || (useContinues && !marathonmode) || (!modeattacking && !(cursaveslot > 0))))
 
+extern UINT8 shareEmblems;
+
 extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations
 
 // For racing
@@ -616,10 +569,6 @@ extern INT32 cheats;
 
 extern tic_t hidetime;
 
-extern UINT32 timesBeaten; // # of times the game has been beaten.
-extern UINT32 timesBeatenWithEmeralds;
-extern UINT32 timesBeatenUltimate;
-
 // ===========================
 // Internal parameters, fixed.
 // ===========================
diff --git a/src/f_finale.c b/src/f_finale.c
index 68b2641a1d0bd248d1eb94e9a19de3945d9f2c58..f529b4564723ea850d5d97b8a57a3b2c05f07177 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -63,7 +63,6 @@ static tic_t stoptimer;
 static boolean keypressed = false;
 
 // (no longer) De-Demo'd Title Screen
-static INT32 menuanimtimer; // Title screen: background animation timing
 mobj_t *titlemapcameraref = NULL;
 
 // menu presentation state
@@ -75,6 +74,8 @@ INT32 curbgyspeed;
 boolean curbghide;
 boolean hidetitlemap;		// WARNING: set to false by M_SetupNextMenu and M_ClearMenus
 
+static fixed_t curbgx = 0;
+static fixed_t curbgy = 0;
 static UINT8  curDemo = 0;
 static UINT32 demoDelayLeft;
 static UINT32 demoIdleLeft;
@@ -1411,7 +1412,7 @@ boolean F_CreditResponder(event_t *event)
 			break;
 	}
 
-	if (!(timesBeaten) && !(netgame || multiplayer) && !cv_debug)
+	if (!(serverGamedata->timesBeaten) && !(netgame || multiplayer) && !cv_debug)
 		return false;
 
 	if (event->type != ev_keydown)
@@ -1573,23 +1574,17 @@ void F_GameEvaluationDrawer(void)
 #if 0 // the following looks like hot garbage the more unlockables we add, and we now have a lot of unlockables
 	if (finalecount >= 5*TICRATE)
 	{
+		INT32 startcoord = 32;
 		V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:");
 
-		if (netgame)
-			V_DrawString(8, 96, V_YELLOWMAP, "Multiplayer games\ncan't unlock\nextras!");
-		else
+		for (i = 0; i < MAXUNLOCKABLES; i++)
 		{
-			INT32 startcoord = 32;
-
-			for (i = 0; i < MAXUNLOCKABLES; i++)
+			if (unlockables[i].conditionset && unlockables[i].conditionset < MAXCONDITIONSETS
+				&& unlockables[i].type && !unlockables[i].nocecho)
 			{
-				if (unlockables[i].conditionset && unlockables[i].conditionset < MAXCONDITIONSETS
-					&& unlockables[i].type && !unlockables[i].nocecho)
-				{
-					if (unlockables[i].unlocked)
-						V_DrawString(8, startcoord, 0, unlockables[i].name);
-					startcoord += 8;
-				}
+				if (clientGamedata->unlocked[i])
+					V_DrawString(8, startcoord, 0, unlockables[i].name);
+				startcoord += 8;
 			}
 		}
 	}
@@ -1648,28 +1643,27 @@ void F_GameEvaluationTicker(void)
 
 	if (finalecount == 5*TICRATE)
 	{
-		if (netgame || multiplayer) // modify this when we finally allow unlocking stuff in 2P
+		serverGamedata->timesBeaten++;
+		clientGamedata->timesBeaten++;
+
+		if (ALL7EMERALDS(emeralds))
 		{
-			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
-			HU_SetCEchoDuration(6);
-			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Multiplayer games can't unlock extras!");
-			S_StartSound(NULL, sfx_s3k68);
+			serverGamedata->timesBeatenWithEmeralds++;
+			clientGamedata->timesBeatenWithEmeralds++;
 		}
-		else
-		{
-			++timesBeaten;
 
-			if (ALL7EMERALDS(emeralds))
-				++timesBeatenWithEmeralds;
+		if (ultimatemode)
+		{
+			serverGamedata->timesBeatenUltimate++;
+			clientGamedata->timesBeatenUltimate++;
+		}
 
-			if (ultimatemode)
-				++timesBeatenUltimate;
+		M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
 
-			if (M_UpdateUnlockablesAndExtraEmblems())
-				S_StartSound(NULL, sfx_s3k68);
+		if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
+			S_StartSound(NULL, sfx_s3k68);
 
-			G_SaveGameData();
-		}
+		G_SaveGameData(clientGamedata);
 	}
 }
 
@@ -2183,7 +2177,7 @@ void F_EndingDrawer(void)
 			//colset(linkmap,  164, 165, 169); -- the ideal purple colour to represent a clicked in-game link, but not worth it just for a soundtest-controlled secret
 			V_DrawCenteredString(BASEVIDWIDTH/2, 8, V_ALLOWLOWERCASE|(trans<<V_ALPHASHIFT), str);
 			V_DrawCharacter(32, BASEVIDHEIGHT-16, '>'|(trans<<V_ALPHASHIFT), false);
-			V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
+			V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((serverGamedata->timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
 		}
 
 		if (finalecount > STOPPINGPOINT-(20+(2*TICRATE)))
@@ -2249,7 +2243,8 @@ void F_GameEndTicker(void)
 
 void F_InitMenuPresValues(void)
 {
-	menuanimtimer = 0;
+	curbgx = 0;
+	curbgy = 0;
 	prevMenuId = 0;
 	activeMenuId = MainDef.menuid;
 
@@ -2282,17 +2277,11 @@ void F_InitMenuPresValues(void)
 //
 // F_SkyScroll
 //
-void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
+void F_SkyScroll(const char *patchname)
 {
-	INT32 xscrolled, x, xneg = (scrollxspeed > 0) - (scrollxspeed < 0), tilex;
-	INT32 yscrolled, y, yneg = (scrollyspeed > 0) - (scrollyspeed < 0), tiley;
-	boolean xispos = (scrollxspeed >= 0), yispos = (scrollyspeed >= 0);
+	INT32 x, basey = 0;
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	INT16 patwidth, patheight;
-	INT32 pw, ph; // scaled by dupz
 	patch_t *pat;
-	INT32 i, j;
-	fixed_t fracmenuanimtimer, xscrolltimer, yscrolltimer;
 
 	if (rendermode == render_none)
 		return;
@@ -2303,43 +2292,34 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 		return;
 	}
 
-	if (!scrollxspeed && !scrollyspeed)
+	pat = W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY);
+
+	if (!curbgxspeed && !curbgyspeed)
 	{
-		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY));
+		V_DrawPatchFill(pat);
+		W_UnlockCachedPatch(pat);
 		return;
 	}
 
-	pat = W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY);
-
-	patwidth = pat->width;
-	patheight = pat->height;
-	pw = patwidth * dupz;
-	ph = patheight * dupz;
-
-	tilex = max(FixedCeil(FixedDiv(vid.width, pw)) >> FRACBITS, 1)+2; // one tile on both sides of center
-	tiley = max(FixedCeil(FixedDiv(vid.height, ph)) >> FRACBITS, 1)+2;
+	// Modulo the background scrolling to prevent jumps from integer overflows
+	// We already load the background patch here, so we can modulo it here
+	// to avoid also having to load the patch in F_MenuPresTicker
+	curbgx %= pat->width  * 16;
+	curbgy %= pat->height * 16;
 
-	fracmenuanimtimer = (menuanimtimer * FRACUNIT) - (FRACUNIT - rendertimefrac);
-	xscrolltimer = ((fracmenuanimtimer*scrollxspeed)/16 + patwidth*xneg*FRACUNIT) % (patwidth * FRACUNIT);
-	yscrolltimer = ((fracmenuanimtimer*scrollyspeed)/16 + patheight*yneg*FRACUNIT) % (patheight * FRACUNIT);
+	// Ooh, fancy frame interpolation
+	x     = ((curbgx*dupz) + FixedInt((rendertimefrac-FRACUNIT) * curbgxspeed*dupz)) / 16;
+	basey = ((curbgy*dupz) + FixedInt((rendertimefrac-FRACUNIT) * curbgyspeed*dupz)) / 16;
 
-	// coordinate offsets
-	xscrolled = FixedInt(xscrolltimer * dupz);
-	yscrolled = FixedInt(yscrolltimer * dupz);
+	if (x     > 0) // Make sure that we don't leave the left or top sides empty
+		x     -= pat->width  * dupz;
+	if (basey > 0)
+		basey -= pat->height * dupz;
 
-	for (x = (xispos) ? -pw*(tilex-1)+pw : 0, i = 0;
-		i < tilex;
-		x += pw, i++)
+	for (; x < vid.width; x += pat->width * dupz)
 	{
-		for (y = (yispos) ? -ph*(tiley-1)+ph : 0, j = 0;
-			j < tiley;
-			y += ph, j++)
-		{
-			V_DrawScaledPatch(
-				(xispos) ? xscrolled - x : x + xscrolled,
-				(yispos) ? yscrolled - y : y + yscrolled,
-				V_NOSCALESTART, pat);
-		}
+		for (INT32 y = basey; y < vid.height; y += pat->height * dupz)
+			V_DrawScaledPatch(x, y, V_NOSCALESTART, pat);
 	}
 
 	W_UnlockCachedPatch(pat);
@@ -2662,7 +2642,7 @@ void F_TitleScreenDrawer(void)
 	if (curbgcolor >= 0)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 	else if (!curbghide || !titlemapinaction || gamestate == GS_WAITINGPLAYERS)
-		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		F_SkyScroll(curbgname);
 
 	// Don't draw outside of the title screen, or if the patch isn't there.
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
@@ -3417,10 +3397,10 @@ luahook:
 
 // separate animation timer for backgrounds, since we also count
 // during GS_TIMEATTACK
-void F_MenuPresTicker(boolean run)
+void F_MenuPresTicker(void)
 {
-	if (run)
-		menuanimtimer++;
+	curbgx += curbgxspeed;
+	curbgy += curbgyspeed;
 }
 
 // (no longer) De-Demo'd Title Screen
diff --git a/src/f_finale.h b/src/f_finale.h
index 6ea1b5537c46f8ab58ad9fb9c74d2d95c241d169..7f53bfbad59b18814693db553eb7d25815a082bd 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -40,7 +40,7 @@ void F_TextPromptTicker(void);
 void F_GameEndDrawer(void);
 void F_IntroDrawer(void);
 void F_TitleScreenDrawer(void);
-void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname);
+void F_SkyScroll(const char *patchname);
 
 void F_GameEvaluationDrawer(void);
 void F_StartGameEvaluation(void);
@@ -131,7 +131,7 @@ extern UINT16 curtttics;
 #define TITLEBACKGROUNDACTIVE (curfadevalue >= 0 || curbgname[0])
 
 void F_InitMenuPresValues(void);
-void F_MenuPresTicker(boolean run);
+void F_MenuPresTicker(void);
 
 //
 // WIPE
diff --git a/src/g_game.c b/src/g_game.c
index 7386b2a84c79450a887c75a56175135a008fc7eb..b239800447972d1bf9e8931ddf6c5796e397824f 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -185,18 +185,6 @@ INT32 tokenbits; // Used for setting token bits
 // Old Special Stage
 INT32 sstimer; // Time allotted in the special stage
 
-tic_t totalplaytime;
-boolean gamedataloaded = false;
-
-// Time attack data for levels
-// These are dynamically allocated for space reasons now
-recorddata_t *mainrecords[NUMMAPS]   = {NULL};
-nightsdata_t *nightsrecords[NUMMAPS] = {NULL};
-UINT8 mapvisited[NUMMAPS];
-
-// Temporary holding place for nights data for the current map
-nightsdata_t ntemprecords;
-
 UINT32 bluescore, redscore; // CTF and Team Match team scores
 
 // ring count... for PERFECT!
@@ -227,6 +215,7 @@ UINT8 ammoremovaltics = 2*TICRATE;
 UINT8 use1upSound = 0;
 UINT8 maxXtraLife = 2; // Max extra lives from rings
 UINT8 useContinues = 0; // Set to 1 to enable continues outside of no-save scenarioes
+UINT8 shareEmblems = 0; // Set to 1 to share all picked up emblems in multiplayer
 
 UINT8 introtoplay;
 UINT8 creditscutscene;
@@ -252,11 +241,6 @@ INT32 cheats; //for multiplayer cheat commands
 
 tic_t hidetime;
 
-// Grading
-UINT32 timesBeaten;
-UINT32 timesBeatenWithEmeralds;
-UINT32 timesBeatenUltimate;
-
 typedef struct joystickvector2_s
 {
 	INT32 xaxis;
@@ -452,86 +436,86 @@ INT16 rw_maximums[NUM_WEAPONS] =
 };
 
 // Allocation for time and nights data
-void G_AllocMainRecordData(INT16 i)
+void G_AllocMainRecordData(INT16 i, gamedata_t *data)
 {
-	if (!mainrecords[i])
-		mainrecords[i] = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL);
-	memset(mainrecords[i], 0, sizeof(recorddata_t));
+	if (!data->mainrecords[i])
+		data->mainrecords[i] = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL);
+	memset(data->mainrecords[i], 0, sizeof(recorddata_t));
 }
 
-void G_AllocNightsRecordData(INT16 i)
+void G_AllocNightsRecordData(INT16 i, gamedata_t *data)
 {
-	if (!nightsrecords[i])
-		nightsrecords[i] = Z_Malloc(sizeof(nightsdata_t), PU_STATIC, NULL);
-	memset(nightsrecords[i], 0, sizeof(nightsdata_t));
+	if (!data->nightsrecords[i])
+		data->nightsrecords[i] = Z_Malloc(sizeof(nightsdata_t), PU_STATIC, NULL);
+	memset(data->nightsrecords[i], 0, sizeof(nightsdata_t));
 }
 
 // MAKE SURE YOU SAVE DATA BEFORE CALLING THIS
-void G_ClearRecords(void)
+void G_ClearRecords(gamedata_t *data)
 {
 	INT16 i;
 	for (i = 0; i < NUMMAPS; ++i)
 	{
-		if (mainrecords[i])
+		if (data->mainrecords[i])
 		{
-			Z_Free(mainrecords[i]);
-			mainrecords[i] = NULL;
+			Z_Free(data->mainrecords[i]);
+			data->mainrecords[i] = NULL;
 		}
-		if (nightsrecords[i])
+		if (data->nightsrecords[i])
 		{
-			Z_Free(nightsrecords[i]);
-			nightsrecords[i] = NULL;
+			Z_Free(data->nightsrecords[i]);
+			data->nightsrecords[i] = NULL;
 		}
 	}
 }
 
 // For easy retrieval of records
-UINT32 G_GetBestScore(INT16 map)
+UINT32 G_GetBestScore(INT16 map, gamedata_t *data)
 {
-	if (!mainrecords[map-1])
+	if (!data->mainrecords[map-1])
 		return 0;
 
-	return mainrecords[map-1]->score;
+	return data->mainrecords[map-1]->score;
 }
 
-tic_t G_GetBestTime(INT16 map)
+tic_t G_GetBestTime(INT16 map, gamedata_t *data)
 {
-	if (!mainrecords[map-1] || mainrecords[map-1]->time <= 0)
+	if (!data->mainrecords[map-1] || data->mainrecords[map-1]->time <= 0)
 		return (tic_t)UINT32_MAX;
 
-	return mainrecords[map-1]->time;
+	return data->mainrecords[map-1]->time;
 }
 
-UINT16 G_GetBestRings(INT16 map)
+UINT16 G_GetBestRings(INT16 map, gamedata_t *data)
 {
-	if (!mainrecords[map-1])
+	if (!data->mainrecords[map-1])
 		return 0;
 
-	return mainrecords[map-1]->rings;
+	return data->mainrecords[map-1]->rings;
 }
 
-UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare)
+UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare, gamedata_t *data)
 {
-	if (!nightsrecords[map-1])
+	if (!data->nightsrecords[map-1])
 		return 0;
 
-	return nightsrecords[map-1]->score[mare];
+	return data->nightsrecords[map-1]->score[mare];
 }
 
-tic_t G_GetBestNightsTime(INT16 map, UINT8 mare)
+tic_t G_GetBestNightsTime(INT16 map, UINT8 mare, gamedata_t *data)
 {
-	if (!nightsrecords[map-1] || nightsrecords[map-1]->time[mare] <= 0)
+	if (!data->nightsrecords[map-1] || data->nightsrecords[map-1]->time[mare] <= 0)
 		return (tic_t)UINT32_MAX;
 
-	return nightsrecords[map-1]->time[mare];
+	return data->nightsrecords[map-1]->time[mare];
 }
 
-UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare)
+UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare, gamedata_t *data)
 {
-	if (!nightsrecords[map-1])
+	if (!data->nightsrecords[map-1])
 		return 0;
 
-	return nightsrecords[map-1]->grade[mare];
+	return data->nightsrecords[map-1]->grade[mare];
 }
 
 // For easy adding of NiGHTS records
@@ -553,7 +537,7 @@ void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare)
 // Update replay files/data, etc. for Record Attack
 // See G_SetNightsRecords for NiGHTS Attack.
 //
-static void G_UpdateRecordReplays(void)
+static void G_UpdateRecordReplays(gamedata_t *data)
 {
 	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
 	char *gpath;
@@ -561,17 +545,17 @@ static void G_UpdateRecordReplays(void)
 	UINT8 earnedEmblems;
 
 	// Record new best time
-	if (!mainrecords[gamemap-1])
-		G_AllocMainRecordData(gamemap-1);
+	if (!data->mainrecords[gamemap-1])
+		G_AllocMainRecordData(gamemap-1, data);
 
-	if (players[consoleplayer].score > mainrecords[gamemap-1]->score)
-		mainrecords[gamemap-1]->score = players[consoleplayer].score;
+	if (players[consoleplayer].score > data->mainrecords[gamemap-1]->score)
+		data->mainrecords[gamemap-1]->score = players[consoleplayer].score;
 
-	if ((mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < mainrecords[gamemap-1]->time))
-		mainrecords[gamemap-1]->time = players[consoleplayer].realtime;
+	if ((data->mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < data->mainrecords[gamemap-1]->time))
+		data->mainrecords[gamemap-1]->time = players[consoleplayer].realtime;
 
-	if ((UINT16)(players[consoleplayer].rings) > mainrecords[gamemap-1]->rings)
-		mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings);
+	if ((UINT16)(players[consoleplayer].rings) > data->mainrecords[gamemap-1]->rings)
+		data->mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings);
 
 	// Save demo!
 	bestdemo[255] = '\0';
@@ -627,14 +611,14 @@ static void G_UpdateRecordReplays(void)
 	free(gpath);
 
 	// Check emblems when level data is updated
-	if ((earnedEmblems = M_CheckLevelEmblems()))
+	if ((earnedEmblems = M_CheckLevelEmblems(data)))
 		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
 
 	// Update timeattack menu's replay availability.
 	Nextmap_OnChange();
 }
 
-void G_SetNightsRecords(void)
+void G_SetNightsRecords(gamedata_t *data)
 {
 	INT32 i;
 	UINT32 totalscore = 0;
@@ -675,9 +659,9 @@ void G_SetNightsRecords(void)
 	{
 		nightsdata_t *maprecords;
 
-		if (!nightsrecords[gamemap-1])
-			G_AllocNightsRecordData(gamemap-1);
-		maprecords = nightsrecords[gamemap-1];
+		if (!data->nightsrecords[gamemap-1])
+			G_AllocNightsRecordData(gamemap-1, data);
+		maprecords = data->nightsrecords[gamemap-1];
 
 		if (maprecords->nummares != ntemprecords.nummares)
 			maprecords->nummares = ntemprecords.nummares;
@@ -739,7 +723,7 @@ void G_SetNightsRecords(void)
 	}
 	free(gpath);
 
-	if ((earnedEmblems = M_CheckLevelEmblems()))
+	if ((earnedEmblems = M_CheckLevelEmblems(data)))
 		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
 
 	// If the mare count changed, this will update the score display
@@ -2407,7 +2391,8 @@ void G_Ticker(boolean run)
 			break;
 
 		case GS_TIMEATTACK:
-			F_MenuPresTicker(run);
+			if (run)
+				F_MenuPresTicker();
 			break;
 
 		case GS_INTRO:
@@ -2455,7 +2440,8 @@ void G_Ticker(boolean run)
 				// then intentionally fall through
 			/* FALLTHRU */
 		case GS_WAITINGPLAYERS:
-			F_MenuPresTicker(run);
+			if (run)
+				F_MenuPresTicker();
 			F_TitleScreenTicker(run);
 			break;
 
@@ -3169,6 +3155,9 @@ void G_DoReborn(INT32 playernum)
 
 	if (resetlevel)
 	{
+		// Don't give completion emblems for reloading the level...
+		stagefailed = true;
+
 		// reload the level from scratch
 		if (countdowntimeup)
 		{
@@ -3838,7 +3827,7 @@ static INT16 RandMap(UINT32 tolflags, INT16 pprevmap)
 	for (ix = 0; ix < NUMMAPS; ix++)
 		if (mapheaderinfo[ix] && (mapheaderinfo[ix]->typeoflevel & tolflags) == tolflags
 		 && ix != pprevmap // Don't pick the same map.
-		 && (dedicated || !M_MapLocked(ix+1)) // Don't pick locked maps.
+		 && (dedicated || !M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps.
 		)
 			okmaps[numokmaps++] = ix;
 
@@ -3855,40 +3844,52 @@ static INT16 RandMap(UINT32 tolflags, INT16 pprevmap)
 //
 // G_UpdateVisited
 //
-static void G_UpdateVisited(void)
+static void G_UpdateVisited(gamedata_t *data, boolean silent)
 {
 	boolean spec = G_IsSpecialStage(gamemap);
 	// Update visitation flags?
-	if (!multiplayer && !demoplayback && (gametype == GT_COOP) // SP/RA/NiGHTS mode
+	if (!demoplayback
+		&& G_CoopGametype() // Campaign mode
 		&& !stagefailed) // Did not fail the stage
 	{
 		UINT8 earnedEmblems;
 
 		// Update visitation flags
-		mapvisited[gamemap-1] |= MV_BEATEN;
+		data->mapvisited[gamemap-1] |= MV_BEATEN;
+
 		// eh, what the hell
 		if (ultimatemode)
-			mapvisited[gamemap-1] |= MV_ULTIMATE;
+			data->mapvisited[gamemap-1] |= MV_ULTIMATE;
+
 		// may seem incorrect but IS possible in what the main game uses as mp special stages, and nummaprings will be -1 in NiGHTS
 		if (nummaprings > 0 && players[consoleplayer].rings >= nummaprings)
 		{
-			mapvisited[gamemap-1] |= MV_PERFECT;
+			data->mapvisited[gamemap-1] |= MV_PERFECT;
 			if (modeattacking)
-				mapvisited[gamemap-1] |= MV_PERFECTRA;
+				data->mapvisited[gamemap-1] |= MV_PERFECTRA;
 		}
+
 		if (!spec)
 		{
 			// not available to special stages because they can only really be done in one order in an unmodified game, so impossible for first six and trivial for seventh
 			if (ALL7EMERALDS(emeralds))
-				mapvisited[gamemap-1] |= MV_ALLEMERALDS;
+				data->mapvisited[gamemap-1] |= MV_ALLEMERALDS;
 		}
 
-		if (modeattacking == ATTACKING_RECORD)
-			G_UpdateRecordReplays();
-		else if (modeattacking == ATTACKING_NIGHTS)
-			G_SetNightsRecords();
+		if (silent)
+		{
+			if (modeattacking)
+				M_CheckLevelEmblems(data);
+		}
+		else
+		{
+			if (modeattacking == ATTACKING_RECORD)
+				G_UpdateRecordReplays(data);
+			else if (modeattacking == ATTACKING_NIGHTS)
+				G_SetNightsRecords(data);
+		}
 
-		if ((earnedEmblems = M_CompletionEmblems()))
+		if ((earnedEmblems = M_CompletionEmblems(data)) && !silent)
 			CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
 	}
 }
@@ -4091,7 +4092,8 @@ static void G_DoCompleted(void)
 
 	if ((skipstats && !modeattacking) || (modeattacking && stagefailed) || (intertype == int_none))
 	{
-		G_UpdateVisited();
+		G_UpdateVisited(serverGamedata, true);
+		G_UpdateVisited(clientGamedata, false);
 		G_HandleSaveLevel();
 		G_AfterIntermission();
 	}
@@ -4100,7 +4102,8 @@ static void G_DoCompleted(void)
 		G_SetGamestate(GS_INTERMISSION);
 		Y_StartIntermission();
 		Y_LoadIntermissionData();
-		G_UpdateVisited();
+		G_UpdateVisited(serverGamedata, true);
+		G_UpdateVisited(clientGamedata, false);
 		G_HandleSaveLevel();
 	}
 }
@@ -4287,7 +4290,7 @@ void G_LoadGameSettings(void)
 
 // G_LoadGameData
 // Loads the main data file, which stores information such as emblems found, etc.
-void G_LoadGameData(void)
+void G_LoadGameData(gamedata_t *data)
 {
 	size_t length;
 	INT32 i, j;
@@ -4304,13 +4307,13 @@ void G_LoadGameData(void)
 	INT32 curmare;
 
 	// Stop saving, until we successfully load it again.
-	gamedataloaded = false;
+	data->loaded = false;
 
 	// Clear things so previously read gamedata doesn't transfer
 	// to new gamedata
-	G_ClearRecords(); // main and nights records
-	M_ClearSecrets(); // emblems, unlocks, maps visited, etc
-	totalplaytime = 0; // total play time (separate from all)
+	G_ClearRecords(data); // main and nights records
+	M_ClearSecrets(data); // emblems, unlocks, maps visited, etc
+	data->totalplaytime = 0; // total play time (separate from all)
 
 	if (M_CheckParm("-nodata"))
 	{
@@ -4321,7 +4324,7 @@ void G_LoadGameData(void)
 	if (M_CheckParm("-resetdata"))
 	{
 		// Don't load, but do save. (essentially, reset)
-		gamedataloaded = true;
+		data->loaded = true;
 		return; 
 	}
 
@@ -4329,7 +4332,7 @@ void G_LoadGameData(void)
 	if (!length)
 	{
 		// No gamedata. We can save a new one.
-		gamedataloaded = true;
+		data->loaded = true;
 		return;
 	}
 
@@ -4352,7 +4355,7 @@ void G_LoadGameData(void)
 		I_Error("Game data is from another version of SRB2.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder);
 	}
 
-	totalplaytime = READUINT32(save_p);
+	data->totalplaytime = READUINT32(save_p);
 
 #ifdef COMPAT_GAMEDATA_ID
 	if (versionID == COMPAT_GAMEDATA_ID)
@@ -4386,7 +4389,7 @@ void G_LoadGameData(void)
 
 	// TODO put another cipher on these things? meh, I don't care...
 	for (i = 0; i < NUMMAPS; i++)
-		if ((mapvisited[i] = READUINT8(save_p)) > MV_MAX)
+		if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX)
 			goto datacorrupt;
 
 	// To save space, use one bit per collected/achieved/unlocked flag
@@ -4394,34 +4397,34 @@ void G_LoadGameData(void)
 	{
 		rtemp = READUINT8(save_p);
 		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
-			emblemlocations[j+i].collected = ((rtemp >> j) & 1);
+			data->collected[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
 	for (i = 0; i < MAXEXTRAEMBLEMS;)
 	{
 		rtemp = READUINT8(save_p);
 		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
-			extraemblems[j+i].collected = ((rtemp >> j) & 1);
+			data->extraCollected[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
 	for (i = 0; i < MAXUNLOCKABLES;)
 	{
 		rtemp = READUINT8(save_p);
 		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
-			unlockables[j+i].unlocked = ((rtemp >> j) & 1);
+			data->unlocked[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
 	for (i = 0; i < MAXCONDITIONSETS;)
 	{
 		rtemp = READUINT8(save_p);
 		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
-			conditionSets[j+i].achieved = ((rtemp >> j) & 1);
+			data->achieved[j+i] = ((rtemp >> j) & 1);
 		i += j;
 	}
 
-	timesBeaten = READUINT32(save_p);
-	timesBeatenWithEmeralds = READUINT32(save_p);
-	timesBeatenUltimate = READUINT32(save_p);
+	data->timesBeaten = READUINT32(save_p);
+	data->timesBeatenWithEmeralds = READUINT32(save_p);
+	data->timesBeatenUltimate = READUINT32(save_p);
 
 	// Main records
 	for (i = 0; i < NUMMAPS; ++i)
@@ -4436,10 +4439,10 @@ void G_LoadGameData(void)
 
 		if (recscore || rectime || recrings)
 		{
-			G_AllocMainRecordData((INT16)i);
-			mainrecords[i]->score = recscore;
-			mainrecords[i]->time = rectime;
-			mainrecords[i]->rings = recrings;
+			G_AllocMainRecordData((INT16)i, data);
+			data->mainrecords[i]->score = recscore;
+			data->mainrecords[i]->time = rectime;
+			data->mainrecords[i]->rings = recrings;
 		}
 	}
 
@@ -4449,19 +4452,21 @@ void G_LoadGameData(void)
 		if ((recmares = READUINT8(save_p)) == 0)
 			continue;
 
-		G_AllocNightsRecordData((INT16)i);
+		G_AllocNightsRecordData((INT16)i, data);
 
 		for (curmare = 0; curmare < (recmares+1); ++curmare)
 		{
-			nightsrecords[i]->score[curmare] = READUINT32(save_p);
-			nightsrecords[i]->grade[curmare] = READUINT8(save_p);
-			nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
+			data->nightsrecords[i]->score[curmare] = READUINT32(save_p);
+			data->nightsrecords[i]->grade[curmare] = READUINT8(save_p);
+			data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
 
-			if (nightsrecords[i]->grade[curmare] > GRADE_S)
+			if (data->nightsrecords[i]->grade[curmare] > GRADE_S)
+			{
 				goto datacorrupt;
+			}
 		}
 
-		nightsrecords[i]->nummares = recmares;
+		data->nightsrecords[i]->nummares = recmares;
 	}
 
 	// done
@@ -4472,10 +4477,11 @@ void G_LoadGameData(void)
 	// It used to do this much earlier, but this would cause the gamedata to
 	// save over itself when it I_Errors from the corruption landing point below,
 	// which can accidentally delete players' legitimate data if the code ever has any tiny mistakes!
-	gamedataloaded = true;
+	data->loaded = true;
 
 	// Silent update unlockables in case they're out of sync with conditions
-	M_SilentUpdateUnlockablesAndEmblems();
+	M_SilentUpdateUnlockablesAndEmblems(data);
+	M_SilentUpdateSkinAvailabilites();
 
 	return;
 
@@ -4495,7 +4501,7 @@ void G_LoadGameData(void)
 
 // G_SaveGameData
 // Saves the main data file, which stores information such as emblems found, etc.
-void G_SaveGameData(void)
+void G_SaveGameData(gamedata_t *data)
 {
 	size_t length;
 	INT32 i, j;
@@ -4503,7 +4509,7 @@ void G_SaveGameData(void)
 
 	INT32 curmare;
 
-	if (!gamedataloaded)
+	if (!data->loaded)
 		return; // If never loaded (-nodata), don't save
 
 	save_p = savebuffer = (UINT8 *)malloc(GAMEDATASIZE);
@@ -4523,20 +4529,20 @@ void G_SaveGameData(void)
 	// Version test
 	WRITEUINT32(save_p, GAMEDATA_ID);
 
-	WRITEUINT32(save_p, totalplaytime);
+	WRITEUINT32(save_p, data->totalplaytime);
 
 	WRITEUINT32(save_p, quickncasehash(timeattackfolder, sizeof timeattackfolder));
 
 	// TODO put another cipher on these things? meh, I don't care...
 	for (i = 0; i < NUMMAPS; i++)
-		WRITEUINT8(save_p, (mapvisited[i] & MV_MAX));
+		WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX));
 
 	// To save space, use one bit per collected/achieved/unlocked flag
 	for (i = 0; i < MAXEMBLEMS;)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
-			btemp |= (emblemlocations[j+i].collected << j);
+			btemp |= (data->collected[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
@@ -4544,7 +4550,7 @@ void G_SaveGameData(void)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
-			btemp |= (extraemblems[j+i].collected << j);
+			btemp |= (data->extraCollected[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
@@ -4552,7 +4558,7 @@ void G_SaveGameData(void)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
-			btemp |= (unlockables[j+i].unlocked << j);
+			btemp |= (data->unlocked[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
@@ -4560,23 +4566,23 @@ void G_SaveGameData(void)
 	{
 		btemp = 0;
 		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
-			btemp |= (conditionSets[j+i].achieved << j);
+			btemp |= (data->achieved[j+i] << j);
 		WRITEUINT8(save_p, btemp);
 		i += j;
 	}
 
-	WRITEUINT32(save_p, timesBeaten);
-	WRITEUINT32(save_p, timesBeatenWithEmeralds);
-	WRITEUINT32(save_p, timesBeatenUltimate);
+	WRITEUINT32(save_p, data->timesBeaten);
+	WRITEUINT32(save_p, data->timesBeatenWithEmeralds);
+	WRITEUINT32(save_p, data->timesBeatenUltimate);
 
 	// Main records
 	for (i = 0; i < NUMMAPS; i++)
 	{
-		if (mainrecords[i])
+		if (data->mainrecords[i])
 		{
-			WRITEUINT32(save_p, mainrecords[i]->score);
-			WRITEUINT32(save_p, mainrecords[i]->time);
-			WRITEUINT16(save_p, mainrecords[i]->rings);
+			WRITEUINT32(save_p, data->mainrecords[i]->score);
+			WRITEUINT32(save_p, data->mainrecords[i]->time);
+			WRITEUINT16(save_p, data->mainrecords[i]->rings);
 		}
 		else
 		{
@@ -4590,19 +4596,19 @@ void G_SaveGameData(void)
 	// NiGHTS records
 	for (i = 0; i < NUMMAPS; i++)
 	{
-		if (!nightsrecords[i] || !nightsrecords[i]->nummares)
+		if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares)
 		{
 			WRITEUINT8(save_p, 0);
 			continue;
 		}
 
-		WRITEUINT8(save_p, nightsrecords[i]->nummares);
+		WRITEUINT8(save_p, data->nightsrecords[i]->nummares);
 
-		for (curmare = 0; curmare < (nightsrecords[i]->nummares + 1); ++curmare)
+		for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare)
 		{
-			WRITEUINT32(save_p, nightsrecords[i]->score[curmare]);
-			WRITEUINT8(save_p, nightsrecords[i]->grade[curmare]);
-			WRITEUINT32(save_p, nightsrecords[i]->time[curmare]);
+			WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]);
+			WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]);
+			WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]);
 		}
 	}
 
diff --git a/src/g_game.h b/src/g_game.h
index 144360db4f73626bfce0a78adfce13895aac7afb..6cda7ca9ccd8e4d7a7cba990fb2b9ffd58983103 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -19,6 +19,7 @@
 #include "d_event.h"
 #include "g_demo.h"
 #include "m_cheat.h" // objectplacing
+#include "m_cond.h"
 
 extern char gamedatafilename[64];
 extern char timeattackfolder[64];
@@ -183,7 +184,7 @@ boolean G_IsTitleCardAvailable(void);
 // Can be called by the startup code or M_Responder, calls P_SetupLevel.
 void G_LoadGame(UINT32 slot, INT16 mapoverride);
 
-void G_SaveGameData(void);
+void G_SaveGameData(gamedata_t *data);
 
 void G_SaveGame(UINT32 slot, INT16 mapnum);
 
@@ -239,7 +240,7 @@ void G_SetModeAttackRetryFlag(void);
 void G_ClearModeAttackRetryFlag(void);
 boolean G_GetModeAttackRetryFlag(void);
 
-void G_LoadGameData(void);
+void G_LoadGameData(gamedata_t *data);
 void G_LoadGameSettings(void);
 
 void G_SetGameModified(boolean silent);
@@ -248,19 +249,19 @@ void G_SetUsedCheats(boolean silent);
 void G_SetGamestate(gamestate_t newstate);
 
 // Gamedata record shit
-void G_AllocMainRecordData(INT16 i);
-void G_AllocNightsRecordData(INT16 i);
-void G_ClearRecords(void);
+void G_AllocMainRecordData(INT16 i, gamedata_t *data);
+void G_AllocNightsRecordData(INT16 i, gamedata_t *data);
+void G_ClearRecords(gamedata_t *data);
 
-UINT32 G_GetBestScore(INT16 map);
-tic_t G_GetBestTime(INT16 map);
-UINT16 G_GetBestRings(INT16 map);
-UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare);
-tic_t G_GetBestNightsTime(INT16 map, UINT8 mare);
-UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare);
+UINT32 G_GetBestScore(INT16 map, gamedata_t *data);
+tic_t G_GetBestTime(INT16 map, gamedata_t *data);
+UINT16 G_GetBestRings(INT16 map, gamedata_t *data);
+UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare, gamedata_t *data);
+tic_t G_GetBestNightsTime(INT16 map, UINT8 mare, gamedata_t *data);
+UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare, gamedata_t *data);
 
 void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare);
-void G_SetNightsRecords(void);
+void G_SetNightsRecords(gamedata_t *data);
 
 FUNCMATH INT32 G_TicsToHours(tic_t tics);
 FUNCMATH INT32 G_TicsToMinutes(tic_t tics, boolean full);
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 0b5d41c4f57015db09b996107fb3e54f1d14778c..c2390519ef78fbb75407dc77fa1010968f5d664f 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1153,7 +1153,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			else
 				texturevpeg = gl_backsector->ceilingheight + textureheight[gl_toptexture] - gl_frontsector->ceilingheight;
 
-			texturevpeg += gl_sidedef->rowoffset;
+			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_top;
 
 			// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
 			texturevpeg %= textureheight[gl_toptexture];
@@ -1162,8 +1162,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_top) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_top) * grTex->scaleX;
 
 			// Adjust t value for sloped walls
 			if (!(gl_linedef->flags & ML_SKEWTD))
@@ -1213,7 +1213,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			else
 				texturevpeg = gl_frontsector->floorheight - gl_backsector->floorheight;
 
-			texturevpeg += gl_sidedef->rowoffset;
+			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bot;
 
 			// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
 			texturevpeg %= textureheight[gl_bottomtexture];
@@ -1222,8 +1222,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_backsector->floorheight - gl_frontsector->floorheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_bot) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_bot) * grTex->scaleX;
 
 			// Adjust t value for sloped walls
 			if (!(gl_linedef->flags & ML_SKEWTD))
@@ -1333,13 +1333,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				// Peg it to the floor
 				if (gl_linedef->flags & ML_MIDPEG)
 				{
-					polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset;
+					polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 					polytop = polybottom + midtexheight;
 				}
 				// Peg it to the ceiling
 				else
 				{
-					polytop = min(front->ceilingheight, back->ceilingheight) + gl_sidedef->rowoffset;
+					polytop = min(front->ceilingheight, back->ceilingheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 					polybottom = polytop - midtexheight;
 				}
 
@@ -1350,9 +1350,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			// Skew the texture, but peg it to the floor
 			else if (gl_linedef->flags & ML_MIDPEG)
 			{
-				polybottom = popenbottom + gl_sidedef->rowoffset;
+				polybottom = popenbottom + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 				polytop = polybottom + midtexheight;
-				polybottomslope = popenbottomslope + gl_sidedef->rowoffset;
+				polybottomslope = popenbottomslope + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 				polytopslope = polybottomslope + midtexheight;
 			}
 			// Skew it according to the ceiling's slope
@@ -1407,12 +1407,12 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			// Left side
 			wallVerts[3].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = (h - l + texturevpeg) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// Right side
 			wallVerts[2].t = texturevpegslope * grTex->scaleY;
 			wallVerts[1].t = (hS - lS + texturevpegslope) * grTex->scaleY;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// set top/bottom coords
 			// Take the texture peg into account, rather than changing the offsets past
@@ -1474,19 +1474,19 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 			// PEGGING
 			if ((gl_linedef->flags & (ML_DONTPEGBOTTOM|ML_NOSKEW)) == (ML_DONTPEGBOTTOM|ML_NOSKEW))
-				texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset;
+				texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 			else if (gl_linedef->flags & ML_DONTPEGBOTTOM)
-				texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset;
+				texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 			else
 				// top of texture at top
-				texturevpeg = gl_sidedef->rowoffset;
+				texturevpeg = gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 
 			grTex = HWR_GetTexture(gl_midtexture);
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// Texture correction for slopes
 			if (gl_linedef->flags & ML_NOSKEW) {
@@ -1634,13 +1634,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					// -- Monster Iestyn 26/06/18
 					if (newline)
 					{
-						texturevpeg = sides[newline->sidenum[0]].rowoffset;
+						texturevpeg = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid;
 						attachtobottom = !!(newline->flags & ML_DONTPEGBOTTOM);
 						slopeskew = !!(newline->flags & ML_SKEWTD);
 					}
 					else
 					{
-						texturevpeg = sides[rover->master->sidenum[0]].rowoffset;
+						texturevpeg = sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid;
 						attachtobottom = !!(gl_linedef->flags & ML_DONTPEGBOTTOM);
 						slopeskew = !!(rover->master->flags & ML_SKEWTD);
 					}
@@ -1672,8 +1672,8 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 						}
 					}
 
-					wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-					wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+					wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
+					wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 				}
 				if (rover->fofflags & FOF_FOG)
 				{
@@ -1785,17 +1785,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 					if (newline)
 					{
-						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) * grTex->scaleY;
-						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset)) * grTex->scaleY;
+						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY;
+						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY;
 					}
 					else
 					{
-						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset) * grTex->scaleY;
-						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset)) * grTex->scaleY;
+						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid) * grTex->scaleY;
+						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid)) * grTex->scaleY;
 					}
 
-					wallVerts[0].s = wallVerts[3].s = cliplow * grTex->scaleX;
-					wallVerts[2].s = wallVerts[1].s = cliphigh * grTex->scaleX;
+					wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
+					wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
 				}
 
 				if (rover->fofflags & FOF_FOG)
@@ -3606,6 +3606,8 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
 	scalemul = FixedMul(scalemul, (thing->radius*2) / gpatch->height);
+	if ((thing->scale != thing->old_scale) && (thing->scale >= FRACUNIT/1024)) // Interpolate shadows when scaling mobjs
+		scalemul = FixedMul(scalemul, FixedDiv(interp.scale, thing->scale));
 
 	fscale = FIXED_TO_FLOAT(scalemul);
 	fx = FIXED_TO_FLOAT(interp.x);
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 6b1d0c6fcbd95376c420739b2e4fb763e07644cc..38e8e8fc4266f6158068906b516363ff8959cdaf 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1660,7 +1660,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 #endif
 
 		// SRB2CBTODO: MD2 scaling support
-		finalscale *= FIXED_TO_FLOAT(spr->mobj->scale);
+		finalscale *= FIXED_TO_FLOAT(interp.scale);
 
 		p.flip = atransform.flip;
 #ifdef USE_FTRANSFORM_MIRROR
diff --git a/src/http-mserv.c b/src/http-mserv.c
index b4dba0db9335da0a07ddbda1b0d280da9ed418b2..b7032e89a3d65ecec15f195d1d46334435a411d9 100644
--- a/src/http-mserv.c
+++ b/src/http-mserv.c
@@ -216,7 +216,11 @@ HMS_connect (const char *format, ...)
 
 	curl_easy_setopt(curl, CURLOPT_URL, url);
 	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
-	curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+
+#ifndef NO_IPV6
+	if (M_CheckParm("-noipv6"))
+#endif
+		curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
 
 	curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value);
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 55ebe0d4d9e3380fcc45708f5c3e8061644de5cb..091e2b2fba50e46638aa6ee52bff2d7e6c81f9f0 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2862,18 +2862,6 @@ static void HU_DrawRankings(void)
 			V_DrawCenteredString(256, 16, 0, va("%d", cv_pointlimit.value));
 		}
 	}
-	else if (gametyperankings[gametype] == GT_COOP)
-	{
-		INT32 totalscore = 0;
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (playeringame[i])
-				totalscore += players[i].score;
-		}
-
-		V_DrawCenteredString(256, 8, 0, "TOTAL SCORE");
-		V_DrawCenteredString(256, 16, 0, va("%u", totalscore));
-	}
 	else
 	{
 		if (circuitmap)
@@ -2996,7 +2984,7 @@ static void HU_DrawCoopOverlay(void)
 
 	if (LUA_HudEnabled(hud_tabemblems))
 	{
-		V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems));
+		V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(clientGamedata), numemblems+numextraemblems));
 		V_DrawScaledPatch(128, 144 - emblemicon->height/4, 0, emblemicon);
 	}
 
@@ -3029,6 +3017,15 @@ static void HU_DrawNetplayCoopOverlay(void)
 		V_DrawSmallScaledPatch(148, 6, 0, tokenicon);
 	}
 
+	if (G_CoopGametype() && LUA_HudEnabled(hud_tabemblems))
+	{
+		V_DrawCenteredString(256, 14, 0, "/");
+		V_DrawString(256 + 4, 14, 0, va("%d", numemblems + numextraemblems));
+		V_DrawRightAlignedString(256 - 4, 14, 0, va("%d", M_CountEmblems(clientGamedata)));
+
+		V_DrawSmallScaledPatch(256 - (emblemicon->width / 4), 6, 0, emblemicon);
+	}
+
 	if (!LUA_HudEnabled(hud_coopemeralds))
 		return;
 
diff --git a/src/i_net.h b/src/i_net.h
index 9f2c38c7b5309c6520705ac5cc35a586f8d7e8e3..12a07f183e515f7ca985afb67ac50cb07cf2c313 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -109,6 +109,17 @@ extern boolean (*I_NetCanSend)(void);
 */
 extern void (*I_NetFreeNodenum)(INT32 nodenum);
 
+/**
+	\brief	split a string into address and port
+
+	\param	address	string to split
+
+	\param	port	double pointer to hold port component (optional)
+
+	\return	address component
+*/
+extern char *I_NetSplitAddress(char *address, char **port);
+
 /**	\brief	open a connection with specified address
 
 	\param	address	address to connect to
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 3820155b83bd4c60b98ece7a8f04638a2bf1f7a1..d95b381f4a65fa86030f9efd44c7164b487df61e 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -340,8 +340,14 @@ static inline void I_UPnP_rem(const char *port, const char * servicetype)
 
 static const char *SOCK_AddrToStr(mysockaddr_t *sk)
 {
-	static char s[64]; // 255.255.255.255:65535 or IPv6:65535
+	static char s[64]; // 255.255.255.255:65535 or
+	// [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535
 #ifdef HAVE_NTOP
+#ifdef HAVE_IPV6
+	int v6 = (sk->any.sa_family == AF_INET6);
+#else
+	int v6 = 0;
+#endif
 	void *addr;
 
 	if(sk->any.sa_family == AF_INET)
@@ -355,14 +361,21 @@ static const char *SOCK_AddrToStr(mysockaddr_t *sk)
 
 	if(addr == NULL)
 		sprintf(s, "No address");
-	else if(inet_ntop(sk->any.sa_family, addr, s, sizeof (s)) == NULL)
+	else if(inet_ntop(sk->any.sa_family, addr, &s[v6], sizeof (s) - v6) == NULL)
 		sprintf(s, "Unknown family type, error #%u", errno);
 #ifdef HAVE_IPV6
-	else if(sk->any.sa_family == AF_INET6 && sk->ip6.sin6_port != 0)
-		strcat(s, va(":%d", ntohs(sk->ip6.sin6_port)));
+	else if(sk->any.sa_family == AF_INET6)
+	{
+		s[0] = '[';
+		strcat(s, "]");
+
+		if (sk->ip6.sin6_port != 0)
+			strcat(s, va(":%d", ntohs(sk->ip6.sin6_port)));
+	}
 #endif
 	else if(sk->any.sa_family == AF_INET  && sk->ip4.sin_port  != 0)
 		strcat(s, va(":%d", ntohs(sk->ip4.sin_port)));
+
 #else
 	if (sk->any.sa_family == AF_INET)
 	{
@@ -427,7 +440,7 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
 			&& (b->ip4.sin_port == 0 || (a->ip4.sin_port == b->ip4.sin_port));
 #ifdef HAVE_IPV6
 	else if (b->any.sa_family == AF_INET6)
-		return memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, sizeof(b->ip6.sin6_addr))
+		return !memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, sizeof(b->ip6.sin6_addr))
 			&& (b->ip6.sin6_port == 0 || (a->ip6.sin6_port == b->ip6.sin6_port));
 #endif
 	else
@@ -735,8 +748,7 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 	unsigned long trueval = true;
 #endif
 	mysockaddr_t straddr;
-	struct sockaddr_in sin;
-	socklen_t len = sizeof(sin);
+	socklen_t len = sizeof(straddr);
 
 	if (s == (SOCKET_TYPE)ERRSOCKET)
 		return (SOCKET_TYPE)ERRSOCKET;
@@ -754,14 +766,12 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 	}
 #endif
 
-	straddr.any = *addr;
+	memcpy(&straddr, addr, addrlen);
 	I_OutputMsg("Binding to %s\n", SOCK_AddrToStr(&straddr));
 
 	if (family == AF_INET)
 	{
-		mysockaddr_t tmpaddr;
-		tmpaddr.any = *addr ;
-		if (tmpaddr.ip4.sin_addr.s_addr == htonl(INADDR_ANY))
+		if (straddr.ip4.sin_addr.s_addr == htonl(INADDR_ANY))
 		{
 			opt = true;
 			opts = (socklen_t)sizeof(opt);
@@ -778,7 +788,7 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 #ifdef HAVE_IPV6
 	else if (family == AF_INET6)
 	{
-		if (memcmp(addr, &in6addr_any, sizeof(in6addr_any)) == 0) //IN6_ARE_ADDR_EQUAL
+		if (memcmp(&straddr.ip6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) //IN6_ARE_ADDR_EQUAL
 		{
 			opt = true;
 			opts = (socklen_t)sizeof(opt);
@@ -788,7 +798,7 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 		// make it IPv6 ony
 		opt = true;
 		opts = (socklen_t)sizeof(opt);
-		if (setsockopt(s, SOL_SOCKET, IPV6_V6ONLY, (char *)&opt, opts))
+		if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, opts))
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Could not limit IPv6 bind\n")); // I do not care anymore
 		}
@@ -830,10 +840,17 @@ static SOCKET_TYPE UDP_Bind(int family, struct sockaddr *addr, socklen_t addrlen
 			CONS_Printf(M_GetText("Network system buffer set to: %dKb\n"), opt>>10);
 	}
 
-	if (getsockname(s, (struct sockaddr *)&sin, &len) == -1)
+	if (getsockname(s, &straddr.any, &len) == -1)
 		CONS_Alert(CONS_WARNING, M_GetText("Failed to get port number\n"));
 	else
-		current_port = (UINT16)ntohs(sin.sin_port);
+	{
+		if (family == AF_INET)
+			current_port = (UINT16)ntohs(straddr.ip4.sin_port);
+#ifdef HAVE_IPV6
+		else if (family == AF_INET6)
+			current_port = (UINT16)ntohs(straddr.ip6.sin6_port);
+#endif
+	}
 
 	return s;
 }
@@ -844,7 +861,7 @@ static boolean UDP_Socket(void)
 	struct my_addrinfo *ai, *runp, hints;
 	int gaie;
 #ifdef HAVE_IPV6
-	const INT32 b_ipv6 = M_CheckParm("-ipv6");
+	const INT32 b_ipv6 = !M_CheckParm("-noipv6");
 #endif
 	const char *serv;
 
@@ -1156,6 +1173,7 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 	SINT8 newnode = -1;
 	struct my_addrinfo *ai = NULL, *runp, hints;
 	int gaie;
+	size_t i;
 
 	 if (!port || !port[0])
 		port = DEFAULTPORT;
@@ -1183,13 +1201,24 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 
 	while (runp != NULL)
 	{
-		// find ip of the server
-		if (sendto(mysockets[0], NULL, 0, 0, runp->ai_addr, runp->ai_addrlen) == 0)
+		// test ip address of server
+		for (i = 0; i < mysocketses; ++i)
 		{
-			memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
-			break;
+			/* sendto tests that there is a network to this
+				address */
+			if (runp->ai_addr->sa_family == myfamily[i] &&
+					sendto(mysockets[i], NULL, 0, 0,
+						runp->ai_addr, runp->ai_addrlen) == 0)
+			{
+				memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
+				break;
+			}
 		}
-		runp = runp->ai_next;
+
+		if (i < mysocketses)
+			runp = runp->ai_next;
+		else
+			break;
 	}
 	I_freeaddrinfo(ai);
 	return newnode;
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index dcaad1416011941230285723037a193ade75102e..1d15b3b145c5e5d0f2bccdd055ff1dcde60056b0 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -566,27 +566,59 @@ static luaL_Reg lib[] = {
 	{NULL, NULL}
 };
 
+enum cvar_e
+{
+	cvar_name,
+	cvar_defaultvalue,
+	cvar_flags,
+	cvar_value,
+	cvar_string,
+	cvar_changed,
+};
+
+static const char *const cvar_opt[] = {
+	"name",
+	"defaultvalue",
+	"flags",
+	"value",
+	"string",
+	"changed",
+	NULL,
+};
+
+static int cvar_fields_ref = LUA_NOREF;
+
 static int cvar_get(lua_State *L)
 {
 	consvar_t *cvar = *(consvar_t **)luaL_checkudata(L, 1, META_CVAR);
-	const char *field = luaL_checkstring(L, 2);
+	enum cvar_e field = Lua_optoption(L, 2, -1, cvar_fields_ref);
 
-	if(fastcmp(field,"name"))
+	switch (field)
+	{
+	case cvar_name:
 		lua_pushstring(L, cvar->name);
-	else if(fastcmp(field,"defaultvalue"))
+		break;
+	case cvar_defaultvalue:
 		lua_pushstring(L, cvar->defaultvalue);
-	else if(fastcmp(field,"flags"))
+		break;
+	case cvar_flags:
 		lua_pushinteger(L, cvar->flags);
-	else if(fastcmp(field,"value"))
+		break;
+	case cvar_value:
 		lua_pushinteger(L, cvar->value);
-	else if(fastcmp(field,"string"))
+		break;
+	case cvar_string:
 		lua_pushstring(L, cvar->string);
-	else if(fastcmp(field,"changed"))
+		break;
+	case cvar_changed:
 		lua_pushboolean(L, cvar->changed);
-	else if (devparm)
-		return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS, field);
-	else
-		return 0;
+		break;
+	default:
+		if (devparm)
+			return luaL_error(L, LUA_QL("consvar_t") " has no field named " LUA_QS, field);
+		else
+			return 0;
+	}
 	return 1;
 }
 
@@ -598,6 +630,8 @@ int LUA_ConsoleLib(lua_State *L)
 		lua_setfield(L, -2, "__index");
 	lua_pop(L,1);
 
+	cvar_fields_ref = Lua_CreateFieldTable(L, cvar_opt);
+
 	// Set empty registry tables
 	lua_newtable(L);
 	lua_setfield(L, LUA_REGISTRYINDEX, "COM_Command");
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 1a05997572f60542ca12d195b697f7aad87da564..c7f67e93ac07d194a25a2ce365726219d52a3ee4 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -95,6 +95,8 @@ static const char *const patch_opt[] = {
 	"topoffset",
 	NULL};
 
+static int patch_fields_ref = LUA_NOREF;
+
 // alignment types for v.drawString
 enum align {
 	align_left = 0,
@@ -196,6 +198,8 @@ static const char *const camera_opt[] = {
 	"momz",
 	NULL};
 
+static int camera_fields_ref = LUA_NOREF;
+
 static int lib_getHudInfo(lua_State *L)
 {
 	UINT32 i;
@@ -276,7 +280,7 @@ static int colormap_get(lua_State *L)
 static int patch_get(lua_State *L)
 {
 	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
-	enum patch field = luaL_checkoption(L, 2, NULL, patch_opt);
+	enum patch field = Lua_optoption(L, 2, -1, patch_fields_ref);
 
 	// patches are invalidated when switching renderers
 	if (!patch) {
@@ -316,7 +320,7 @@ static int patch_set(lua_State *L)
 static int camera_get(lua_State *L)
 {
 	camera_t *cam = *((camera_t **)luaL_checkudata(L, 1, META_CAMERA));
-	enum cameraf field = luaL_checkoption(L, 2, NULL, camera_opt);
+	enum cameraf field = Lua_optoption(L, 2, -1, camera_fields_ref);
 
 	// cameras should always be valid unless I'm a nutter
 	I_Assert(cam != NULL);
@@ -372,7 +376,7 @@ static int camera_get(lua_State *L)
 static int camera_set(lua_State *L)
 {
 	camera_t *cam = *((camera_t **)luaL_checkudata(L, 1, META_CAMERA));
-	enum cameraf field = luaL_checkoption(L, 2, NULL, camera_opt);
+	enum cameraf field = Lua_optoption(L, 2, -1, camera_fields_ref);
 
 	I_Assert(cam != NULL);
 
@@ -1444,6 +1448,8 @@ int LUA_HudLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	patch_fields_ref = Lua_CreateFieldTable(L, patch_opt);
+
 	luaL_newmetatable(L, META_CAMERA);
 		lua_pushcfunction(L, camera_get);
 		lua_setfield(L, -2, "__index");
@@ -1452,6 +1458,8 @@ int LUA_HudLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	camera_fields_ref = Lua_CreateFieldTable(L, camera_opt);
+
 	luaL_register(L, "hud", lib_hud);
 	return 0;
 }
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 7388632d3c6cac8e74619ff4b5ac9bfca8ddb2e8..fb07ccebb53dc00a7c93a2be141d05e54b24baa9 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -1106,75 +1106,161 @@ static int lib_mobjinfolen(lua_State *L)
 	return 1;
 }
 
+enum mobjinfo_e
+{
+	mobjinfo_doomednum,
+	mobjinfo_spawnstate,
+	mobjinfo_spawnhealth,
+	mobjinfo_seestate,
+	mobjinfo_seesound,
+	mobjinfo_reactiontime,
+	mobjinfo_attacksound,
+	mobjinfo_painstate,
+	mobjinfo_painchance,
+	mobjinfo_painsound,
+	mobjinfo_meleestate,
+	mobjinfo_missilestate,
+	mobjinfo_deathstate,
+	mobjinfo_xdeathstate,
+	mobjinfo_deathsound,
+	mobjinfo_speed,
+	mobjinfo_radius,
+	mobjinfo_height,
+	mobjinfo_dispoffset,
+	mobjinfo_mass,
+	mobjinfo_damage,
+	mobjinfo_activesound,
+	mobjinfo_flags,
+	mobjinfo_raisestate,
+};
+
+const char *const mobjinfo_opt[] = {
+	"doomednum",
+	"spawnstate",
+	"spawnhealth",
+	"seestate",
+	"seesound",
+	"reactiontime",
+	"attacksound",
+	"painstate",
+	"painchance",
+	"painsound",
+	"meleestate",
+	"missilestate",
+	"deathstate",
+	"xdeathstate",
+	"deathsound",
+	"speed",
+	"radius",
+	"height",
+	"dispoffset",
+	"mass",
+	"damage",
+	"activesound",
+	"flags",
+	"raisestate",
+	NULL,
+};
+
+static int mobjinfo_fields_ref = LUA_NOREF;
+
 // mobjinfo_t *, field -> number
 static int mobjinfo_get(lua_State *L)
 {
 	mobjinfo_t *info = *((mobjinfo_t **)luaL_checkudata(L, 1, META_MOBJINFO));
-	const char *field = luaL_checkstring(L, 2);
+	enum mobjinfo_e field = luaL_checkoption(L, 2, mobjinfo_opt[0], mobjinfo_opt);
 
 	I_Assert(info != NULL);
 	I_Assert(info >= mobjinfo);
 
-	if (fastcmp(field,"doomednum"))
+	switch (field)
+	{
+	case mobjinfo_doomednum:
 		lua_pushinteger(L, info->doomednum);
-	else if (fastcmp(field,"spawnstate"))
+		break;
+	case mobjinfo_spawnstate:
 		lua_pushinteger(L, info->spawnstate);
-	else if (fastcmp(field,"spawnhealth"))
+		break;
+	case mobjinfo_spawnhealth:
 		lua_pushinteger(L, info->spawnhealth);
-	else if (fastcmp(field,"seestate"))
+		break;
+	case mobjinfo_seestate:
 		lua_pushinteger(L, info->seestate);
-	else if (fastcmp(field,"seesound"))
+		break;
+	case mobjinfo_seesound:
 		lua_pushinteger(L, info->seesound);
-	else if (fastcmp(field,"reactiontime"))
+		break;
+	case mobjinfo_reactiontime:
 		lua_pushinteger(L, info->reactiontime);
-	else if (fastcmp(field,"attacksound"))
+		break;
+	case mobjinfo_attacksound:
 		lua_pushinteger(L, info->attacksound);
-	else if (fastcmp(field,"painstate"))
+		break;
+	case mobjinfo_painstate:
 		lua_pushinteger(L, info->painstate);
-	else if (fastcmp(field,"painchance"))
+		break;
+	case mobjinfo_painchance:
 		lua_pushinteger(L, info->painchance);
-	else if (fastcmp(field,"painsound"))
+		break;
+	case mobjinfo_painsound:
 		lua_pushinteger(L, info->painsound);
-	else if (fastcmp(field,"meleestate"))
+		break;
+	case mobjinfo_meleestate:
 		lua_pushinteger(L, info->meleestate);
-	else if (fastcmp(field,"missilestate"))
+		break;
+	case mobjinfo_missilestate:
 		lua_pushinteger(L, info->missilestate);
-	else if (fastcmp(field,"deathstate"))
+		break;
+	case mobjinfo_deathstate:
 		lua_pushinteger(L, info->deathstate);
-	else if (fastcmp(field,"xdeathstate"))
+		break;
+	case mobjinfo_xdeathstate:
 		lua_pushinteger(L, info->xdeathstate);
-	else if (fastcmp(field,"deathsound"))
+		break;
+	case mobjinfo_deathsound:
 		lua_pushinteger(L, info->deathsound);
-	else if (fastcmp(field,"speed"))
+		break;
+	case mobjinfo_speed:
 		lua_pushinteger(L, info->speed); // sometimes it's fixed_t, sometimes it's not...
-	else if (fastcmp(field,"radius"))
+		break;
+	case mobjinfo_radius:
 		lua_pushfixed(L, info->radius);
-	else if (fastcmp(field,"height"))
+		break;
+	case mobjinfo_height:
 		lua_pushfixed(L, info->height);
-	else if (fastcmp(field,"dispoffset"))
+		break;
+	case mobjinfo_dispoffset:
 		lua_pushinteger(L, info->dispoffset);
-	else if (fastcmp(field,"mass"))
+		break;
+	case mobjinfo_mass:
 		lua_pushinteger(L, info->mass);
-	else if (fastcmp(field,"damage"))
+		break;
+	case mobjinfo_damage:
 		lua_pushinteger(L, info->damage);
-	else if (fastcmp(field,"activesound"))
+		break;
+	case mobjinfo_activesound:
 		lua_pushinteger(L, info->activesound);
-	else if (fastcmp(field,"flags"))
+		break;
+	case mobjinfo_flags:
 		lua_pushinteger(L, info->flags);
-	else if (fastcmp(field,"raisestate"))
+		break;
+	case mobjinfo_raisestate:
 		lua_pushinteger(L, info->raisestate);
-	else {
+		break;
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, info);
 		lua_rawget(L, -2);
 		if (!lua_istable(L, -1)) { // no extra values table
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", lua_tostring(L, 2));
 			return 0;
 		}
-		lua_getfield(L, -1, field);
+		lua_pushvalue(L, 2); // field name
+		lua_gettable(L, -2);
 		if (lua_isnil(L, -1)) // no value for this field
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "mobjinfo_t", lua_tostring(L, 2));
+		break;
 	}
 	return 1;
 }
@@ -1183,7 +1269,7 @@ static int mobjinfo_get(lua_State *L)
 static int mobjinfo_set(lua_State *L)
 {
 	mobjinfo_t *info = *((mobjinfo_t **)luaL_checkudata(L, 1, META_MOBJINFO));
-	const char *field = luaL_checkstring(L, 2);
+	enum mobjinfo_e field = Lua_optoption(L, 2, -1, mobjinfo_fields_ref);
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter mobjinfo in HUD rendering code!");
@@ -1193,55 +1279,81 @@ static int mobjinfo_set(lua_State *L)
 	I_Assert(info != NULL);
 	I_Assert(info >= mobjinfo);
 
-	if (fastcmp(field,"doomednum"))
+	switch (field)
+	{
+	case mobjinfo_doomednum:
 		info->doomednum = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spawnstate"))
+		break;
+	case mobjinfo_spawnstate:
 		info->spawnstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spawnhealth"))
+		break;
+	case mobjinfo_spawnhealth:
 		info->spawnhealth = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"seestate"))
+		break;
+	case mobjinfo_seestate:
 		info->seestate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"seesound"))
+		break;
+	case mobjinfo_seesound:
 		info->seesound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"reactiontime"))
+		break;
+	case mobjinfo_reactiontime:
 		info->reactiontime = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"attacksound"))
+		break;
+	case mobjinfo_attacksound:
 		info->attacksound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"painstate"))
+		break;
+	case mobjinfo_painstate:
 		info->painstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"painchance"))
+		break;
+	case mobjinfo_painchance:
 		info->painchance = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"painsound"))
+		break;
+	case mobjinfo_painsound:
 		info->painsound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"meleestate"))
+		break;
+	case mobjinfo_meleestate:
 		info->meleestate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"missilestate"))
+		break;
+	case mobjinfo_missilestate:
 		info->missilestate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"deathstate"))
+		break;
+	case mobjinfo_deathstate:
 		info->deathstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"xdeathstate"))
+		break;
+	case mobjinfo_xdeathstate:
 		info->xdeathstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"deathsound"))
+		break;
+	case mobjinfo_deathsound:
 		info->deathsound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"speed"))
+		break;
+	case mobjinfo_speed:
 		info->speed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"radius"))
+		break;
+	case mobjinfo_radius:
 		info->radius = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"height"))
+		break;
+	case mobjinfo_height:
 		info->height = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"dispoffset"))
+		break;
+	case mobjinfo_dispoffset:
 		info->dispoffset = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"mass"))
+		break;
+	case mobjinfo_mass:
 		info->mass = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"damage"))
+		break;
+	case mobjinfo_damage:
 		info->damage = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"activesound"))
+		break;
+	case mobjinfo_activesound:
 		info->activesound = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flags"))
+		break;
+	case mobjinfo_flags:
 		info->flags = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"raisestate"))
+		break;
+	case mobjinfo_raisestate:
 		info->raisestate = luaL_checkinteger(L, 3);
-	else {
+		break;
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, info);
@@ -1249,18 +1361,17 @@ static int mobjinfo_set(lua_State *L)
 		if (lua_isnil(L, -1)) {
 			// This index doesn't have a table for extra values yet, let's make one.
 			lua_pop(L, 1);
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "mobjinfo_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "mobjinfo_t", lua_tostring(L, 2));
 			lua_newtable(L);
 			lua_pushlightuserdata(L, info);
 			lua_pushvalue(L, -2); // ext value table
 			lua_rawset(L, -4); // LREG_EXTVARS table
 		}
+		lua_pushvalue(L, 2); // key
 		lua_pushvalue(L, 3); // value to store
-		lua_setfield(L, -2, field);
+		lua_settable(L, -3);
 		lua_pop(L, 2);
 	}
-	//else
-		//return luaL_error(L, LUA_QL("mobjinfo_t") " has no field named " LUA_QS, field);
 	return 0;
 }
 
@@ -1788,6 +1899,8 @@ int LUA_InfoLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	mobjinfo_fields_ref = Lua_CreateFieldTable(L, mobjinfo_opt);
+
 	luaL_newmetatable(L, META_SKINCOLOR);
 		lua_pushcfunction(L, skincolor_get);
 		lua_setfield(L, -2, "__index");
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 898651520d77ead06f05ccbe767fe618708e4c20..f73b3c7d251c1deab5f47fd0293ff08f8e0152e7 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -88,6 +88,8 @@ static const char *const sector_opt[] = {
 	"gravity",
 	NULL};
 
+static int sector_fields_ref = LUA_NOREF;
+
 enum subsector_e {
 	subsector_valid = 0,
 	subsector_sector,
@@ -104,6 +106,8 @@ static const char *const subsector_opt[] = {
 	"polyList",
 	NULL};
 
+static int subsector_fields_ref = LUA_NOREF;
+
 enum line_e {
 	line_valid = 0,
 	line_v1,
@@ -156,10 +160,18 @@ static const char *const line_opt[] = {
 	"callcount",
 	NULL};
 
+static int line_fields_ref = LUA_NOREF;
+
 enum side_e {
 	side_valid = 0,
 	side_textureoffset,
 	side_rowoffset,
+	side_offsetx_top,
+	side_offsety_top,
+	side_offsetx_mid,
+	side_offsety_mid,
+	side_offsetx_bot,
+	side_offsety_bot,
 	side_toptexture,
 	side_bottomtexture,
 	side_midtexture,
@@ -174,6 +186,12 @@ static const char *const side_opt[] = {
 	"valid",
 	"textureoffset",
 	"rowoffset",
+	"offsetx_top",
+	"offsety_top",
+	"offsetx_mid",
+	"offsety_mid",
+	"offsetx_bot",
+	"offsety_bot",
 	"toptexture",
 	"bottomtexture",
 	"midtexture",
@@ -184,6 +202,8 @@ static const char *const side_opt[] = {
 	"text",
 	NULL};
 
+static int side_fields_ref = LUA_NOREF;
+
 enum vertex_e {
 	vertex_valid = 0,
 	vertex_x,
@@ -204,6 +224,8 @@ static const char *const vertex_opt[] = {
 	"ceilingzset",
 	NULL};
 
+static int vertex_fields_ref = LUA_NOREF;
+
 enum ffloor_e {
 	ffloor_valid = 0,
 	ffloor_topheight,
@@ -256,6 +278,8 @@ static const char *const ffloor_opt[] = {
 	"bouncestrength",
 	NULL};
 
+static int ffloor_fields_ref = LUA_NOREF;
+
 #ifdef HAVE_LUA_SEGS
 enum seg_e {
 	seg_valid = 0,
@@ -285,6 +309,8 @@ static const char *const seg_opt[] = {
 	"polyseg",
 	NULL};
 
+static int seg_fields_ref = LUA_NOREF;
+
 enum node_e {
 	node_valid = 0,
 	node_x,
@@ -305,6 +331,8 @@ static const char *const node_opt[] = {
 	"children",
 	NULL};
 
+static int node_fields_ref = LUA_NOREF;
+
 enum nodechild_e {
 	nodechild_valid = 0,
 	nodechild_right,
@@ -356,6 +384,8 @@ static const char *const slope_opt[] = {
 	"flags",
 	NULL};
 
+static int slope_fields_ref = LUA_NOREF;
+
 // shared by both vector2_t and vector3_t
 enum vector_e {
 	vector_x = 0,
@@ -575,7 +605,7 @@ static int sectorlines_num(lua_State *L)
 static int sector_get(lua_State *L)
 {
 	sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
-	enum sector_e field = luaL_checkoption(L, 2, sector_opt[0], sector_opt);
+	enum sector_e field = Lua_optoption(L, 2, sector_valid, sector_fields_ref);
 	INT16 i;
 
 	if (!sector)
@@ -697,7 +727,7 @@ static int sector_get(lua_State *L)
 static int sector_set(lua_State *L)
 {
 	sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
-	enum sector_e field = luaL_checkoption(L, 2, sector_opt[0], sector_opt);
+	enum sector_e field = Lua_optoption(L, 2, sector_valid, sector_fields_ref);
 
 	if (!sector)
 		return luaL_error(L, "accessed sector_t doesn't exist anymore.");
@@ -814,7 +844,7 @@ static int sector_num(lua_State *L)
 static int subsector_get(lua_State *L)
 {
 	subsector_t *subsector = *((subsector_t **)luaL_checkudata(L, 1, META_SUBSECTOR));
-	enum subsector_e field = luaL_checkoption(L, 2, subsector_opt[0], subsector_opt);
+	enum subsector_e field = Lua_optoption(L, 2, subsector_valid, subsector_fields_ref);
 
 	if (!subsector)
 	{
@@ -898,7 +928,7 @@ static int linestringargs_len(lua_State *L)
 static int line_get(lua_State *L)
 {
 	line_t *line = *((line_t **)luaL_checkudata(L, 1, META_LINE));
-	enum line_e field = luaL_checkoption(L, 2, line_opt[0], line_opt);
+	enum line_e field = Lua_optoption(L, 2, line_valid, line_fields_ref);
 
 	if (!line)
 	{
@@ -1057,7 +1087,7 @@ static int sidenum_get(lua_State *L)
 static int side_get(lua_State *L)
 {
 	side_t *side = *((side_t **)luaL_checkudata(L, 1, META_SIDE));
-	enum side_e field = luaL_checkoption(L, 2, side_opt[0], side_opt);
+	enum side_e field = Lua_optoption(L, 2, side_valid, side_fields_ref);
 
 	if (!side)
 	{
@@ -1079,6 +1109,24 @@ static int side_get(lua_State *L)
 	case side_rowoffset:
 		lua_pushfixed(L, side->rowoffset);
 		return 1;
+	case side_offsetx_top:
+		lua_pushfixed(L, side->offsetx_top);
+		return 1;
+	case side_offsety_top:
+		lua_pushfixed(L, side->offsety_top);
+		return 1;
+	case side_offsetx_mid:
+		lua_pushfixed(L, side->offsetx_mid);
+		return 1;
+	case side_offsety_mid:
+		lua_pushfixed(L, side->offsety_mid);
+		return 1;
+	case side_offsetx_bot:
+		lua_pushfixed(L, side->offsetx_bot);
+		return 1;
+	case side_offsety_bot:
+		lua_pushfixed(L, side->offsety_bot);
+		return 1;
 	case side_toptexture:
 		lua_pushinteger(L, side->toptexture);
 		return 1;
@@ -1110,7 +1158,7 @@ static int side_get(lua_State *L)
 static int side_set(lua_State *L)
 {
 	side_t *side = *((side_t **)luaL_checkudata(L, 1, META_SIDE));
-	enum side_e field = luaL_checkoption(L, 2, side_opt[0], side_opt);
+	enum side_e field = Lua_optoption(L, 2, side_valid, side_fields_ref);
 
 	if (!side)
 	{
@@ -1136,6 +1184,24 @@ static int side_set(lua_State *L)
 	case side_rowoffset:
 		side->rowoffset = luaL_checkfixed(L, 3);
 		break;
+	case side_offsetx_top:
+		side->offsetx_top = luaL_checkfixed(L, 3);
+		break;
+	case side_offsety_top:
+		side->offsety_top = luaL_checkfixed(L, 3);
+		break;
+	case side_offsetx_mid:
+		side->offsetx_mid = luaL_checkfixed(L, 3);
+		break;
+	case side_offsety_mid:
+		side->offsety_mid = luaL_checkfixed(L, 3);
+		break;
+	case side_offsetx_bot:
+		side->offsetx_bot = luaL_checkfixed(L, 3);
+		break;
+	case side_offsety_bot:
+		side->offsety_bot = luaL_checkfixed(L, 3);
+		break;
 	case side_toptexture:
 		side->toptexture = luaL_checkinteger(L, 3);
 		break;
@@ -1166,7 +1232,7 @@ static int side_num(lua_State *L)
 static int vertex_get(lua_State *L)
 {
 	vertex_t *vertex = *((vertex_t **)luaL_checkudata(L, 1, META_VERTEX));
-	enum vertex_e field = luaL_checkoption(L, 2, vertex_opt[0], vertex_opt);
+	enum vertex_e field = Lua_optoption(L, 2, vertex_valid, vertex_fields_ref);
 
 	if (!vertex)
 	{
@@ -1220,7 +1286,7 @@ static int vertex_num(lua_State *L)
 static int seg_get(lua_State *L)
 {
 	seg_t *seg = *((seg_t **)luaL_checkudata(L, 1, META_SEG));
-	enum seg_e field = luaL_checkoption(L, 2, seg_opt[0], seg_opt);
+	enum seg_e field = Lua_optoption(L, 2, seg_valid, seg_fields_ref);
 
 	if (!seg)
 	{
@@ -1284,7 +1350,7 @@ static int seg_num(lua_State *L)
 static int node_get(lua_State *L)
 {
 	node_t *node = *((node_t **)luaL_checkudata(L, 1, META_NODE));
-	enum node_e field = luaL_checkoption(L, 2, node_opt[0], node_opt);
+	enum node_e field = Lua_optoption(L, 2, node_valid, node_fields_ref);
 
 	if (!node)
 	{
@@ -1920,7 +1986,7 @@ static INT32 P_GetOldFOFFlags(ffloor_t *fflr)
 static int ffloor_get(lua_State *L)
 {
 	ffloor_t *ffloor = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
-	enum ffloor_e field = luaL_checkoption(L, 2, ffloor_opt[0], ffloor_opt);
+	enum ffloor_e field = Lua_optoption(L, 2, ffloor_valid, ffloor_fields_ref);
 	INT16 i;
 
 	if (!ffloor)
@@ -2102,7 +2168,7 @@ static void P_SetOldFOFFlags(ffloor_t *fflr, oldffloortype_e oldflags)
 static int ffloor_set(lua_State *L)
 {
 	ffloor_t *ffloor = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
-	enum ffloor_e field = luaL_checkoption(L, 2, ffloor_opt[0], ffloor_opt);
+	enum ffloor_e field = Lua_optoption(L, 2, ffloor_valid, ffloor_fields_ref);
 
 	if (!ffloor)
 		return luaL_error(L, "accessed ffloor_t doesn't exist anymore.");
@@ -2197,7 +2263,7 @@ static int ffloor_set(lua_State *L)
 static int slope_get(lua_State *L)
 {
 	pslope_t *slope = *((pslope_t **)luaL_checkudata(L, 1, META_SLOPE));
-	enum slope_e field = luaL_checkoption(L, 2, slope_opt[0], slope_opt);
+	enum slope_e field = Lua_optoption(L, 2, slope_valid, slope_fields_ref);
 
 	if (!slope)
 	{
@@ -2241,7 +2307,7 @@ static int slope_get(lua_State *L)
 static int slope_set(lua_State *L)
 {
 	pslope_t *slope = *((pslope_t **)luaL_checkudata(L, 1, META_SLOPE));
-	enum slope_e field = luaL_checkoption(L, 2, slope_opt[0], slope_opt);
+	enum slope_e field = Lua_optoption(L, 2, slope_valid, slope_fields_ref);
 
 	if (!slope)
 		return luaL_error(L, "accessed pslope_t doesn't exist anymore.");
@@ -2405,110 +2471,258 @@ static int lib_nummapheaders(lua_State *L)
 // mapheader_t //
 /////////////////
 
+enum mapheaderinfo_e
+{
+	mapheaderinfo_lvlttl,
+	mapheaderinfo_subttl,
+	mapheaderinfo_actnum,
+	mapheaderinfo_typeoflevel,
+	mapheaderinfo_nextlevel,
+	mapheaderinfo_marathonnext,
+	mapheaderinfo_keywords,
+	mapheaderinfo_musname,
+	mapheaderinfo_mustrack,
+	mapheaderinfo_muspos,
+	mapheaderinfo_musinterfadeout,
+	mapheaderinfo_musintername,
+	mapheaderinfo_muspostbossname,
+	mapheaderinfo_muspostbosstrack,
+	mapheaderinfo_muspostbosspos,
+	mapheaderinfo_muspostbossfadein,
+	mapheaderinfo_musforcereset,
+	mapheaderinfo_forcecharacter,
+	mapheaderinfo_weather,
+	mapheaderinfo_skynum,
+	mapheaderinfo_skybox_scalex,
+	mapheaderinfo_skybox_scaley,
+	mapheaderinfo_skybox_scalez,
+	mapheaderinfo_interscreen,
+	mapheaderinfo_runsoc,
+	mapheaderinfo_scriptname,
+	mapheaderinfo_precutscenenum,
+	mapheaderinfo_cutscenenum,
+	mapheaderinfo_countdown,
+	mapheaderinfo_palette,
+	mapheaderinfo_numlaps,
+	mapheaderinfo_unlockrequired,
+	mapheaderinfo_levelselect,
+	mapheaderinfo_bonustype,
+	mapheaderinfo_ltzzpatch,
+	mapheaderinfo_ltzztext,
+	mapheaderinfo_ltactdiamond,
+	mapheaderinfo_maxbonuslives,
+	mapheaderinfo_levelflags,
+	mapheaderinfo_menuflags,
+	mapheaderinfo_selectheading,
+	mapheaderinfo_startrings,
+	mapheaderinfo_sstimer,
+	mapheaderinfo_ssspheres,
+	mapheaderinfo_gravity,
+};
+
+static const char *const mapheaderinfo_opt[] = {
+	"lvlttl",
+	"subttl",
+	"actnum",
+	"typeoflevel",
+	"nextlevel",
+	"marathonnext",
+	"keywords",
+	"musname",
+	"mustrack",
+	"muspos",
+	"musinterfadeout",
+	"musintername",
+	"muspostbossname",
+	"muspostbosstrack",
+	"muspostbosspos",
+	"muspostbossfadein",
+	"musforcereset",
+	"forcecharacter",
+	"weather",
+	"skynum",
+	"skybox_scalex",
+	"skybox_scaley",
+	"skybox_scalez",
+	"interscreen",
+	"runsoc",
+	"scriptname",
+	"precutscenenum",
+	"cutscenenum",
+	"countdown",
+	"palette",
+	"numlaps",
+	"unlockrequired",
+	"levelselect",
+	"bonustype",
+	"ltzzpatch",
+	"ltzztext",
+	"ltactdiamond",
+	"maxbonuslives",
+	"levelflags",
+	"menuflags",
+	"selectheading",
+	"startrings",
+	"sstimer",
+	"ssspheres",
+	"gravity",
+	NULL,
+};
+
+static int mapheaderinfo_fields_ref = LUA_NOREF;
+
 static int mapheaderinfo_get(lua_State *L)
 {
 	mapheader_t *header = *((mapheader_t **)luaL_checkudata(L, 1, META_MAPHEADER));
-	const char *field = luaL_checkstring(L, 2);
+	enum mapheaderinfo_e field = Lua_optoption(L, 2, -1, mapheaderinfo_fields_ref);
 	INT16 i;
-	if (fastcmp(field,"lvlttl"))
+
+	switch (field)
+	{
+	case mapheaderinfo_lvlttl:
 		lua_pushstring(L, header->lvlttl);
-	else if (fastcmp(field,"subttl"))
+		break;
+	case mapheaderinfo_subttl:
 		lua_pushstring(L, header->subttl);
-	else if (fastcmp(field,"actnum"))
+		break;
+	case mapheaderinfo_actnum:
 		lua_pushinteger(L, header->actnum);
-	else if (fastcmp(field,"typeoflevel"))
+		break;
+	case mapheaderinfo_typeoflevel:
 		lua_pushinteger(L, header->typeoflevel);
-	else if (fastcmp(field,"nextlevel"))
+		break;
+	case mapheaderinfo_nextlevel:
 		lua_pushinteger(L, header->nextlevel);
-	else if (fastcmp(field,"marathonnext"))
+		break;
+	case mapheaderinfo_marathonnext:
 		lua_pushinteger(L, header->marathonnext);
-	else if (fastcmp(field,"keywords"))
+		break;
+	case mapheaderinfo_keywords:
 		lua_pushstring(L, header->keywords);
-	else if (fastcmp(field,"musname"))
+		break;
+	case mapheaderinfo_musname:
 		lua_pushstring(L, header->musname);
-	else if (fastcmp(field,"mustrack"))
+		break;
+	case mapheaderinfo_mustrack:
 		lua_pushinteger(L, header->mustrack);
-	else if (fastcmp(field,"muspos"))
+		break;
+	case mapheaderinfo_muspos:
 		lua_pushinteger(L, header->muspos);
-	else if (fastcmp(field,"musinterfadeout"))
+		break;
+	case mapheaderinfo_musinterfadeout:
 		lua_pushinteger(L, header->musinterfadeout);
-	else if (fastcmp(field,"musintername"))
+		break;
+	case mapheaderinfo_musintername:
 		lua_pushstring(L, header->musintername);
-	else if (fastcmp(field,"muspostbossname"))
+		break;
+	case mapheaderinfo_muspostbossname:
 		lua_pushstring(L, header->muspostbossname);
-	else if (fastcmp(field,"muspostbosstrack"))
+		break;
+	case mapheaderinfo_muspostbosstrack:
 		lua_pushinteger(L, header->muspostbosstrack);
-	else if (fastcmp(field,"muspostbosspos"))
+		break;
+	case mapheaderinfo_muspostbosspos:
 		lua_pushinteger(L, header->muspostbosspos);
-	else if (fastcmp(field,"muspostbossfadein"))
+		break;
+	case mapheaderinfo_muspostbossfadein:
 		lua_pushinteger(L, header->muspostbossfadein);
-	else if (fastcmp(field,"musforcereset"))
+		break;
+	case mapheaderinfo_musforcereset:
 		lua_pushinteger(L, header->musforcereset);
-	else if (fastcmp(field,"forcecharacter"))
+		break;
+	case mapheaderinfo_forcecharacter:
 		lua_pushstring(L, header->forcecharacter);
-	else if (fastcmp(field,"weather"))
+		break;
+	case mapheaderinfo_weather:
 		lua_pushinteger(L, header->weather);
-	else if (fastcmp(field,"skynum"))
+		break;
+	case mapheaderinfo_skynum:
 		lua_pushinteger(L, header->skynum);
-	else if (fastcmp(field,"skybox_scalex"))
+		break;
+	case mapheaderinfo_skybox_scalex:
 		lua_pushinteger(L, header->skybox_scalex);
-	else if (fastcmp(field,"skybox_scaley"))
+		break;
+	case mapheaderinfo_skybox_scaley:
 		lua_pushinteger(L, header->skybox_scaley);
-	else if (fastcmp(field,"skybox_scalez"))
+		break;
+	case mapheaderinfo_skybox_scalez:
 		lua_pushinteger(L, header->skybox_scalez);
-	else if (fastcmp(field,"interscreen")) {
+		break;
+	case mapheaderinfo_interscreen:
 		for (i = 0; i < 8; i++)
 			if (!header->interscreen[i])
 				break;
 		lua_pushlstring(L, header->interscreen, i);
-	} else if (fastcmp(field,"runsoc"))
+		break;
+	case mapheaderinfo_runsoc:
 		lua_pushstring(L, header->runsoc);
-	else if (fastcmp(field,"scriptname"))
+		break;
+	case mapheaderinfo_scriptname:
 		lua_pushstring(L, header->scriptname);
-	else if (fastcmp(field,"precutscenenum"))
+		break;
+	case mapheaderinfo_precutscenenum:
 		lua_pushinteger(L, header->precutscenenum);
-	else if (fastcmp(field,"cutscenenum"))
+		break;
+	case mapheaderinfo_cutscenenum:
 		lua_pushinteger(L, header->cutscenenum);
-	else if (fastcmp(field,"countdown"))
+		break;
+	case mapheaderinfo_countdown:
 		lua_pushinteger(L, header->countdown);
-	else if (fastcmp(field,"palette"))
+		break;
+	case mapheaderinfo_palette:
 		lua_pushinteger(L, header->palette);
-	else if (fastcmp(field,"numlaps"))
+		break;
+	case mapheaderinfo_numlaps:
 		lua_pushinteger(L, header->numlaps);
-	else if (fastcmp(field,"unlockrequired"))
+		break;
+	case mapheaderinfo_unlockrequired:
 		lua_pushinteger(L, header->unlockrequired);
-	else if (fastcmp(field,"levelselect"))
+		break;
+	case mapheaderinfo_levelselect:
 		lua_pushinteger(L, header->levelselect);
-	else if (fastcmp(field,"bonustype"))
+		break;
+	case mapheaderinfo_bonustype:
 		lua_pushinteger(L, header->bonustype);
-	else if (fastcmp(field,"ltzzpatch"))
+		break;
+	case mapheaderinfo_ltzzpatch:
 		lua_pushstring(L, header->ltzzpatch);
-	else if (fastcmp(field,"ltzztext"))
+		break;
+	case mapheaderinfo_ltzztext:
 		lua_pushstring(L, header->ltzztext);
-	else if (fastcmp(field,"ltactdiamond"))
+		break;
+	case mapheaderinfo_ltactdiamond:
 		lua_pushstring(L, header->ltactdiamond);
-	else if (fastcmp(field,"maxbonuslives"))
+		break;
+	case mapheaderinfo_maxbonuslives:
 		lua_pushinteger(L, header->maxbonuslives);
-	else if (fastcmp(field,"levelflags"))
+		break;
+	case mapheaderinfo_levelflags:
 		lua_pushinteger(L, header->levelflags);
-	else if (fastcmp(field,"menuflags"))
+		break;
+	case mapheaderinfo_menuflags:
 		lua_pushinteger(L, header->menuflags);
-	else if (fastcmp(field,"selectheading"))
+		break;
+	case mapheaderinfo_selectheading:
 		lua_pushstring(L, header->selectheading);
-	else if (fastcmp(field,"startrings"))
+		break;
+	case mapheaderinfo_startrings:
 		lua_pushinteger(L, header->startrings);
-	else if (fastcmp(field, "sstimer"))
+		break;
+	case mapheaderinfo_sstimer:
 		lua_pushinteger(L, header->sstimer);
-	else if (fastcmp(field, "ssspheres"))
+		break;
+	case mapheaderinfo_ssspheres:
 		lua_pushinteger(L, header->ssspheres);
-	else if (fastcmp(field, "gravity"))
+		break;
+	case mapheaderinfo_gravity:
 		lua_pushfixed(L, header->gravity);
+		break;
 	// TODO add support for reading numGradedMares and grades
-	else {
+	default:
 		// Read custom vars now
 		// (note: don't include the "LUA." in your lua scripts!)
 		UINT8 j = 0;
-		for (;j < header->numCustomOptions && !fastcmp(field, header->customopts[j].option); ++j);
+		for (;j < header->numCustomOptions && !fastcmp(lua_tostring(L, 2), header->customopts[j].option); ++j);
 
 		if(j < header->numCustomOptions)
 			lua_pushstring(L, header->customopts[j].value);
@@ -2539,6 +2753,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	sector_fields_ref = Lua_CreateFieldTable(L, sector_opt);
+
 	luaL_newmetatable(L, META_SUBSECTOR);
 		lua_pushcfunction(L, subsector_get);
 		lua_setfield(L, -2, "__index");
@@ -2547,6 +2763,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	subsector_fields_ref = Lua_CreateFieldTable(L, subsector_opt);
+
 	luaL_newmetatable(L, META_LINE);
 		lua_pushcfunction(L, line_get);
 		lua_setfield(L, -2, "__index");
@@ -2555,6 +2773,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	line_fields_ref = Lua_CreateFieldTable(L, line_opt);
+
 	luaL_newmetatable(L, META_LINEARGS);
 		lua_pushcfunction(L, lineargs_get);
 		lua_setfield(L, -2, "__index");
@@ -2587,6 +2807,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	side_fields_ref = Lua_CreateFieldTable(L, side_opt);
+
 	luaL_newmetatable(L, META_VERTEX);
 		lua_pushcfunction(L, vertex_get);
 		lua_setfield(L, -2, "__index");
@@ -2595,6 +2817,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	vertex_fields_ref = Lua_CreateFieldTable(L, vertex_opt);
+
 	luaL_newmetatable(L, META_FFLOOR);
 		lua_pushcfunction(L, ffloor_get);
 		lua_setfield(L, -2, "__index");
@@ -2603,6 +2827,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L, 1);
 
+	ffloor_fields_ref = Lua_CreateFieldTable(L, ffloor_opt);
+
 #ifdef HAVE_LUA_SEGS
 	luaL_newmetatable(L, META_SEG);
 		lua_pushcfunction(L, seg_get);
@@ -2612,6 +2838,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	seg_fields_ref = Lua_CreateFieldTable(L, seg_opt);
+
 	luaL_newmetatable(L, META_NODE);
 		lua_pushcfunction(L, node_get);
 		lua_setfield(L, -2, "__index");
@@ -2620,6 +2848,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	node_fields_ref = Lua_CreateFieldTable(L, node_opt);
+
 	luaL_newmetatable(L, META_NODEBBOX);
 		//lua_pushcfunction(L, nodebbox_get);
 		//lua_setfield(L, -2, "__index");
@@ -2646,6 +2876,8 @@ int LUA_MapLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L, 1);
 
+	slope_fields_ref = Lua_CreateFieldTable(L, slope_opt);
+
 	luaL_newmetatable(L, META_VECTOR2);
 		lua_pushcfunction(L, vector2_get);
 		lua_setfield(L, -2, "__index");
@@ -2664,6 +2896,8 @@ int LUA_MapLib(lua_State *L)
 		//lua_setfield(L, -2, "__len");
 	lua_pop(L, 1);
 
+	mapheaderinfo_fields_ref = Lua_CreateFieldTable(L, mapheaderinfo_opt);
+
 	LUA_PushTaggableObjectArray(L, "sectors",
 			lib_iterateSectors,
 			lib_getSector,
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 5c2b8b4d3cec0be91593807114346c77f454d879..09d244c9101bc7b562e4c082113b197be5abf618 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -179,10 +179,12 @@ static const char *const mobj_opt[] = {
 
 #define UNIMPLEMENTED luaL_error(L, LUA_QL("mobj_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", mobj_opt[field])
 
+static int mobj_fields_ref = LUA_NOREF;
+
 static int mobj_get(lua_State *L)
 {
 	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
-	enum mobj_e field = Lua_optoption(L, 2, NULL, mobj_opt);
+	enum mobj_e field = Lua_optoption(L, 2, -1, mobj_fields_ref);
 	lua_settop(L, 2);
 
 	if (!mo || !ISINLEVEL) {
@@ -467,7 +469,7 @@ static int mobj_get(lua_State *L)
 static int mobj_set(lua_State *L)
 {
 	mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
-	enum mobj_e field = Lua_optoption(L, 2, mobj_opt[0], mobj_opt);
+	enum mobj_e field = Lua_optoption(L, 2, mobj_valid, mobj_fields_ref);
 	lua_settop(L, 3);
 
 	INLEVEL
@@ -876,14 +878,55 @@ static int thingstringargs_len(lua_State *L)
 	return 1;
 }
 
+enum mapthing_e {
+	mapthing_valid = 0,
+	mapthing_x,
+	mapthing_y,
+	mapthing_angle,
+	mapthing_pitch,
+	mapthing_roll,
+	mapthing_type,
+	mapthing_options,
+	mapthing_scale,
+	mapthing_z,
+	mapthing_extrainfo,
+	mapthing_tag,
+	mapthing_taglist,
+	mapthing_args,
+	mapthing_stringargs,
+	mapthing_mobj,
+};
+
+const char *const mapthing_opt[] = {
+	"valid",
+	"x",
+	"y",
+	"angle",
+	"pitch",
+	"roll",
+	"type",
+	"options",
+	"scale",
+	"z",
+	"extrainfo",
+	"tag",
+	"taglist",
+	"args",
+	"stringargs",
+	"mobj",
+	NULL,
+};
+
+static int mapthing_fields_ref = LUA_NOREF;
+
 static int mapthing_get(lua_State *L)
 {
 	mapthing_t *mt = *((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING));
-	const char *field = luaL_checkstring(L, 2);
-	lua_Integer number;
+	enum mapthing_e field = Lua_optoption(L, 2, -1, mapthing_fields_ref);
+	lua_settop(L, 2);
 
 	if (!mt) {
-		if (fastcmp(field,"valid")) {
+		if (field == mapthing_valid) {
 			lua_pushboolean(L, false);
 			return 1;
 		}
@@ -892,62 +935,71 @@ static int mapthing_get(lua_State *L)
 		return 0;
 	}
 
-	if (fastcmp(field,"valid")) {
-		lua_pushboolean(L, true);
-		return 1;
-	} else if(fastcmp(field,"x"))
-		number = mt->x;
-	else if(fastcmp(field,"y"))
-		number = mt->y;
-	else if(fastcmp(field,"angle"))
-		number = mt->angle;
-	else if(fastcmp(field,"pitch"))
-		number = mt->pitch;
-	else if(fastcmp(field,"roll"))
-		number = mt->roll;
-	else if(fastcmp(field,"type"))
-		number = mt->type;
-	else if(fastcmp(field,"options"))
-		number = mt->options;
-	else if(fastcmp(field,"scale"))
-		number = mt->scale;
-	else if(fastcmp(field,"z"))
-		number = mt->z;
-	else if(fastcmp(field,"extrainfo"))
-		number = mt->extrainfo;
-	else if(fastcmp(field,"tag"))
-		number = Tag_FGet(&mt->tags);
-	else if(fastcmp(field,"taglist"))
-	{
-		LUA_PushUserdata(L, &mt->tags, META_TAGLIST);
-		return 1;
-	}
-	else if(fastcmp(field,"args"))
-	{
-		LUA_PushUserdata(L, mt->args, META_THINGARGS);
-		return 1;
-	}
-	else if(fastcmp(field,"stringargs"))
+	switch (field)
 	{
-		LUA_PushUserdata(L, mt->stringargs, META_THINGSTRINGARGS);
-		return 1;
+		case mapthing_valid:
+			lua_pushboolean(L, true);
+			break;
+		case mapthing_x:
+			lua_pushinteger(L, mt->x);
+			break;
+		case mapthing_y:
+			lua_pushinteger(L, mt->y);
+			break;
+		case mapthing_angle:
+			lua_pushinteger(L, mt->angle);
+			break;
+		case mapthing_pitch:
+			lua_pushinteger(L, mt->pitch);
+			break;
+		case mapthing_roll:
+			lua_pushinteger(L, mt->roll);
+			break;
+		case mapthing_type:
+			lua_pushinteger(L, mt->type);
+			break;
+		case mapthing_options:
+			lua_pushinteger(L, mt->options);
+			break;
+		case mapthing_scale:
+			lua_pushinteger(L, mt->scale);
+			break;
+		case mapthing_z:
+			lua_pushinteger(L, mt->z);
+			break;
+		case mapthing_extrainfo:
+			lua_pushinteger(L, mt->extrainfo);
+			break;
+		case mapthing_tag:
+			lua_pushinteger(L, Tag_FGet(&mt->tags));
+			break;
+		case mapthing_taglist:
+			LUA_PushUserdata(L, &mt->tags, META_TAGLIST);
+			break;
+		case mapthing_args:
+			LUA_PushUserdata(L, mt->args, META_THINGARGS);
+			break;
+		case mapthing_stringargs:
+			LUA_PushUserdata(L, mt->stringargs, META_THINGSTRINGARGS);
+			break;
+		case mapthing_mobj:
+			LUA_PushUserdata(L, mt->mobj, META_MOBJ);
+			break;
+		default:
+			if (devparm)
+				return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
+			else
+				return 0;
 	}
-	else if(fastcmp(field,"mobj")) {
-		LUA_PushUserdata(L, mt->mobj, META_MOBJ);
-		return 1;
-	} else if (devparm)
-		return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
-	else
-		return 0;
 
-	lua_pushinteger(L, number);
 	return 1;
 }
 
 static int mapthing_set(lua_State *L)
 {
 	mapthing_t *mt = *((mapthing_t **)luaL_checkudata(L, 1, META_MAPTHING));
-	const char *field = luaL_checkstring(L, 2);
+	enum mapthing_e field = Lua_optoption(L, 2, -1, mapthing_fields_ref);
+	lua_settop(L, 3);
 
 	if (!mt)
 		return luaL_error(L, "accessed mapthing_t doesn't exist anymore.");
@@ -957,39 +1009,52 @@ static int mapthing_set(lua_State *L)
 	if (hook_cmd_running)
 		return luaL_error(L, "Do not alter mapthing_t in CMD building code!");
 
-	if(fastcmp(field,"x"))
-		mt->x = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"y"))
-		mt->y = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"angle"))
-		mt->angle = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"pitch"))
-		mt->pitch = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"roll"))
-		mt->roll = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"type"))
-		mt->type = (UINT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"options"))
-		mt->options = (UINT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"scale"))
-		mt->scale = luaL_checkfixed(L, 3);
-	else if(fastcmp(field,"z"))
-		mt->z = (INT16)luaL_checkinteger(L, 3);
-	else if(fastcmp(field,"extrainfo"))
+	switch (field)
 	{
-		INT32 extrainfo = luaL_checkinteger(L, 3);
-		if (extrainfo & ~15)
-			return luaL_error(L, "mapthing_t extrainfo set %d out of range (%d - %d)", extrainfo, 0, 15);
-		mt->extrainfo = (UINT8)extrainfo;
+		case mapthing_x:
+			mt->x = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_y:
+			mt->y = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_angle:
+			mt->angle = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_pitch:
+			mt->pitch = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_roll:
+			mt->roll = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_type:
+			mt->type = (UINT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_options:
+			mt->options = (UINT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_scale:
+			mt->scale = luaL_checkfixed(L, 3);
+			break;
+		case mapthing_z:
+			mt->z = (INT16)luaL_checkinteger(L, 3);
+			break;
+		case mapthing_extrainfo:
+			INT32 extrainfo = luaL_checkinteger(L, 3);
+			if (extrainfo & ~15)
+				return luaL_error(L, "mapthing_t extrainfo set %d out of range (%d - %d)", extrainfo, 0, 15);
+			mt->extrainfo = (UINT8)extrainfo;
+			break;
+		case mapthing_tag:
+			Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
+			break;
+		case mapthing_taglist:
+			return LUA_ErrSetDirectly(L, "mapthing_t", "taglist");
+		case mapthing_mobj:
+			mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
+			break;
+		default:
+			return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
 	}
-	else if (fastcmp(field,"tag"))
-		Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
-	else if (fastcmp(field,"taglist"))
-		return LUA_ErrSetDirectly(L, "mapthing_t", "taglist");
-	else if(fastcmp(field,"mobj"))
-		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
-	else
-		return luaL_error(L, LUA_QL("mapthing_t") " has no field named " LUA_QS, field);
 
 	return 0;
 }
@@ -1051,6 +1116,8 @@ int LUA_MobjLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	mobj_fields_ref = Lua_CreateFieldTable(L, mobj_opt);
+
 	luaL_newmetatable(L, META_THINGARGS);
 		lua_pushcfunction(L, thingargs_get);
 		lua_setfield(L, -2, "__index");
@@ -1078,6 +1145,8 @@ int LUA_MobjLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	mapthing_fields_ref = Lua_CreateFieldTable(L, mapthing_opt);
+
 	LUA_PushTaggableObjectArray(L, "mapthings",
 			lib_iterateMapthings,
 			lib_getMapthing,
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index f79b1e45e9361d0697852960f9d905f08c3286c9..1c3b4f46c228afd5cce94b19b4757052eef846e0 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -80,322 +80,764 @@ static int lib_lenPlayer(lua_State *L)
 	return 1;
 }
 
+enum player_e
+{
+	player_valid,
+	player_name,
+	player_realmo,
+	player_mo,
+	player_cmd,
+	player_playerstate,
+	player_camerascale,
+	player_shieldscale,
+	player_viewz,
+	player_viewheight,
+	player_deltaviewheight,
+	player_bob,
+	player_viewrollangle,
+	player_aiming,
+	player_drawangle,
+	player_rings,
+	player_spheres,
+	player_pity,
+	player_currentweapon,
+	player_ringweapons,
+	player_ammoremoval,
+	player_ammoremovaltimer,
+	player_ammoremovalweapon,
+	player_powers,
+	player_pflags,
+	player_panim,
+	player_flashcount,
+	player_flashpal,
+	player_skincolor,
+	player_skin,
+	player_availabilities,
+	player_score,
+	player_dashspeed,
+	player_normalspeed,
+	player_runspeed,
+	player_thrustfactor,
+	player_accelstart,
+	player_acceleration,
+	player_charability,
+	player_charability2,
+	player_charflags,
+	player_thokitem,
+	player_spinitem,
+	player_revitem,
+	player_followitem,
+	player_followmobj,
+	player_actionspd,
+	player_mindash,
+	player_maxdash,
+	player_jumpfactor,
+	player_height,
+	player_spinheight,
+	player_lives,
+	player_continues,
+	player_xtralife,
+	player_gotcontinue,
+	player_speed,
+	player_secondjump,
+	player_fly1,
+	player_scoreadd,
+	player_glidetime,
+	player_climbing,
+	player_deadtimer,
+	player_exiting,
+	player_homing,
+	player_dashmode,
+	player_skidtime,
+	player_cmomx,
+	player_cmomy,
+	player_rmomx,
+	player_rmomy,
+	player_numboxes,
+	player_totalring,
+	player_realtime,
+	player_laps,
+	player_ctfteam,
+	player_gotflag,
+	player_weapondelay,
+	player_tossdelay,
+	player_starpostx,
+	player_starposty,
+	player_starpostz,
+	player_starpostnum,
+	player_starposttime,
+	player_starpostangle,
+	player_starpostscale,
+	player_angle_pos,
+	player_old_angle_pos,
+	player_axis1,
+	player_axis2,
+	player_bumpertime,
+	player_flyangle,
+	player_drilltimer,
+	player_linkcount,
+	player_linktimer,
+	player_anotherflyangle,
+	player_nightstime,
+	player_drillmeter,
+	player_drilldelay,
+	player_bonustime,
+	player_capsule,
+	player_drone,
+	player_oldscale,
+	player_mare,
+	player_marelap,
+	player_marebonuslap,
+	player_marebegunat,
+	player_startedtime,
+	player_finishedtime,
+	player_lapbegunat,
+	player_lapstartedtime,
+	player_finishedspheres,
+	player_finishedrings,
+	player_marescore,
+	player_lastmarescore,
+	player_totalmarescore,
+	player_lastmare,
+	player_lastmarelap,
+	player_lastmarebonuslap,
+	player_totalmarelap,
+	player_totalmarebonuslap,
+	player_maxlink,
+	player_texttimer,
+	player_textvar,
+	player_lastsidehit,
+	player_lastlinehit,
+	player_losstime,
+	player_timeshit,
+	player_onconveyor,
+	player_awayviewmobj,
+	player_awayviewtics,
+	player_awayviewaiming,
+	player_spectator,
+	player_outofcoop,
+	player_bot,
+	player_botleader,
+	player_lastbuttons,
+	player_blocked,
+	player_jointime,
+	player_quittime,
+#ifdef HWRENDER
+	player_fovadd,
+#endif
+};
+
+static const char *const player_opt[] = {
+	"valid",
+	"name",
+	"realmo",
+	"mo",
+	"cmd",
+	"playerstate",
+	"camerascale",
+	"shieldscale",
+	"viewz",
+	"viewheight",
+	"deltaviewheight",
+	"bob",
+	"viewrollangle",
+	"aiming",
+	"drawangle",
+	"rings",
+	"spheres",
+	"pity",
+	"currentweapon",
+	"ringweapons",
+	"ammoremoval",
+	"ammoremovaltimer",
+	"ammoremovalweapon",
+	"powers",
+	"pflags",
+	"panim",
+	"flashcount",
+	"flashpal",
+	"skincolor",
+	"skin",
+	"availabilities",
+	"score",
+	"dashspeed",
+	"normalspeed",
+	"runspeed",
+	"thrustfactor",
+	"accelstart",
+	"acceleration",
+	"charability",
+	"charability2",
+	"charflags",
+	"thokitem",
+	"spinitem",
+	"revitem",
+	"followitem",
+	"followmobj",
+	"actionspd",
+	"mindash",
+	"maxdash",
+	"jumpfactor",
+	"height",
+	"spinheight",
+	"lives",
+	"continues",
+	"xtralife",
+	"gotcontinue",
+	"speed",
+	"secondjump",
+	"fly1",
+	"scoreadd",
+	"glidetime",
+	"climbing",
+	"deadtimer",
+	"exiting",
+	"homing",
+	"dashmode",
+	"skidtime",
+	"cmomx",
+	"cmomy",
+	"rmomx",
+	"rmomy",
+	"numboxes",
+	"totalring",
+	"realtime",
+	"laps",
+	"ctfteam",
+	"gotflag",
+	"weapondelay",
+	"tossdelay",
+	"starpostx",
+	"starposty",
+	"starpostz",
+	"starpostnum",
+	"starposttime",
+	"starpostangle",
+	"starpostscale",
+	"angle_pos",
+	"old_angle_pos",
+	"axis1",
+	"axis2",
+	"bumpertime",
+	"flyangle",
+	"drilltimer",
+	"linkcount",
+	"linktimer",
+	"anotherflyangle",
+	"nightstime",
+	"drillmeter",
+	"drilldelay",
+	"bonustime",
+	"capsule",
+	"drone",
+	"oldscale",
+	"mare",
+	"marelap",
+	"marebonuslap",
+	"marebegunat",
+	"startedtime",
+	"finishedtime",
+	"lapbegunat",
+	"lapstartedtime",
+	"finishedspheres",
+	"finishedrings",
+	"marescore",
+	"lastmarescore",
+	"totalmarescore",
+	"lastmare",
+	"lastmarelap",
+	"lastmarebonuslap",
+	"totalmarelap",
+	"totalmarebonuslap",
+	"maxlink",
+	"texttimer",
+	"textvar",
+	"lastsidehit",
+	"lastlinehit",
+	"losstime",
+	"timeshit",
+	"onconveyor",
+	"awayviewmobj",
+	"awayviewtics",
+	"awayviewaiming",
+	"spectator",
+	"outofcoop",
+	"bot",
+	"botleader",
+	"lastbuttons",
+	"blocked",
+	"jointime",
+	"quittime",
+#ifdef HWRENDER
+	"fovadd",
+#endif
+	NULL,
+};
+
+static int player_fields_ref = LUA_NOREF;
+
 static int player_get(lua_State *L)
 {
 	player_t *plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
-	const char *field = luaL_checkstring(L, 2);
+	enum player_e field = Lua_optoption(L, 2, -1, player_fields_ref);
+	lua_settop(L, 2);
 
-	if (!plr) {
-		if (fastcmp(field,"valid")) {
+	if (!plr)
+	{
+		if (field == player_valid)
+		{
 			lua_pushboolean(L, false);
 			return 1;
 		}
 		return LUA_ErrInvalid(L, "player_t");
 	}
 
-	if (fastcmp(field,"valid"))
+	switch (field)
+	{
+	case player_valid:
 		lua_pushboolean(L, true);
-	else if (fastcmp(field,"name"))
+		break;
+	case player_name:
 		lua_pushstring(L, player_names[plr-players]);
-	else if (fastcmp(field,"realmo"))
+		break;
+	case player_realmo:
 		LUA_PushUserdata(L, plr->mo, META_MOBJ);
+		break;
 	// Kept for backward-compatibility
 	// Should be fixed to work like "realmo" later
-	else if (fastcmp(field,"mo"))
-	{
+	case player_mo:
 		if (plr->spectator)
 			lua_pushnil(L);
 		else
 			LUA_PushUserdata(L, plr->mo, META_MOBJ);
-	}
-	else if (fastcmp(field,"cmd"))
+		break;
+	case player_cmd:
 		LUA_PushUserdata(L, &plr->cmd, META_TICCMD);
-	else if (fastcmp(field,"playerstate"))
+		break;
+	case player_playerstate:
 		lua_pushinteger(L, plr->playerstate);
-	else if (fastcmp(field,"camerascale"))
+		break;
+	case player_camerascale:
 		lua_pushfixed(L, plr->camerascale);
-	else if (fastcmp(field,"shieldscale"))
+		break;
+	case player_shieldscale:
 		lua_pushfixed(L, plr->shieldscale);
-	else if (fastcmp(field,"viewz"))
+		break;
+	case player_viewz:
 		lua_pushfixed(L, plr->viewz);
-	else if (fastcmp(field,"viewheight"))
+		break;
+	case player_viewheight:
 		lua_pushfixed(L, plr->viewheight);
-	else if (fastcmp(field,"deltaviewheight"))
+		break;
+	case player_deltaviewheight:
 		lua_pushfixed(L, plr->deltaviewheight);
-	else if (fastcmp(field,"bob"))
+		break;
+	case player_bob:
 		lua_pushfixed(L, plr->bob);
-	else if (fastcmp(field,"viewrollangle"))
+		break;
+	case player_viewrollangle:
 		lua_pushangle(L, plr->viewrollangle);
-	else if (fastcmp(field,"aiming"))
+		break;
+	case player_aiming:
 		lua_pushangle(L, plr->aiming);
-	else if (fastcmp(field,"drawangle"))
+		break;
+	case player_drawangle:
 		lua_pushangle(L, plr->drawangle);
-	else if (fastcmp(field,"rings"))
+		break;
+	case player_rings:
 		lua_pushinteger(L, plr->rings);
-	else if (fastcmp(field,"spheres"))
+		break;
+	case player_spheres:
 		lua_pushinteger(L, plr->spheres);
-	else if (fastcmp(field,"pity"))
+		break;
+	case player_pity:
 		lua_pushinteger(L, plr->pity);
-	else if (fastcmp(field,"currentweapon"))
+		break;
+	case player_currentweapon:
 		lua_pushinteger(L, plr->currentweapon);
-	else if (fastcmp(field,"ringweapons"))
+		break;
+	case player_ringweapons:
 		lua_pushinteger(L, plr->ringweapons);
-	else if (fastcmp(field,"ammoremoval"))
+		break;
+	case player_ammoremoval:
 		lua_pushinteger(L, plr->ammoremoval);
-	else if (fastcmp(field,"ammoremovaltimer"))
+		break;
+	case player_ammoremovaltimer:
 		lua_pushinteger(L, plr->ammoremovaltimer);
-	else if (fastcmp(field,"ammoremovalweapon"))
+		break;
+	case player_ammoremovalweapon:
 		lua_pushinteger(L, plr->ammoremovalweapon);
-	else if (fastcmp(field,"powers"))
+		break;
+	case player_powers:
 		LUA_PushUserdata(L, plr->powers, META_POWERS);
-	else if (fastcmp(field,"pflags"))
+		break;
+	case player_pflags:
 		lua_pushinteger(L, plr->pflags);
-	else if (fastcmp(field,"panim"))
+		break;
+	case player_panim:
 		lua_pushinteger(L, plr->panim);
-	else if (fastcmp(field,"flashcount"))
+		break;
+	case player_flashcount:
 		lua_pushinteger(L, plr->flashcount);
-	else if (fastcmp(field,"flashpal"))
+		break;
+	case player_flashpal:
 		lua_pushinteger(L, plr->flashpal);
-	else if (fastcmp(field,"skincolor"))
+		break;
+	case player_skincolor:
 		lua_pushinteger(L, plr->skincolor);
-	else if (fastcmp(field,"skin"))
+		break;
+	case player_skin:
 		lua_pushinteger(L, plr->skin);
-	else if (fastcmp(field,"availabilities"))
+		break;
+	case player_availabilities:
 		lua_pushinteger(L, plr->availabilities);
-	else if (fastcmp(field,"score"))
+		break;
+	case player_score:
 		lua_pushinteger(L, plr->score);
-	else if (fastcmp(field,"dashspeed"))
+		break;
+	case player_dashspeed:
 		lua_pushfixed(L, plr->dashspeed);
-	else if (fastcmp(field,"normalspeed"))
+		break;
+	case player_normalspeed:
 		lua_pushfixed(L, plr->normalspeed);
-	else if (fastcmp(field,"runspeed"))
+		break;
+	case player_runspeed:
 		lua_pushfixed(L, plr->runspeed);
-	else if (fastcmp(field,"thrustfactor"))
+		break;
+	case player_thrustfactor:
 		lua_pushinteger(L, plr->thrustfactor);
-	else if (fastcmp(field,"accelstart"))
+		break;
+	case player_accelstart:
 		lua_pushinteger(L, plr->accelstart);
-	else if (fastcmp(field,"acceleration"))
+		break;
+	case player_acceleration:
 		lua_pushinteger(L, plr->acceleration);
-	else if (fastcmp(field,"charability"))
+		break;
+	case player_charability:
 		lua_pushinteger(L, plr->charability);
-	else if (fastcmp(field,"charability2"))
+		break;
+	case player_charability2:
 		lua_pushinteger(L, plr->charability2);
-	else if (fastcmp(field,"charflags"))
+		break;
+	case player_charflags:
 		lua_pushinteger(L, plr->charflags);
-	else if (fastcmp(field,"thokitem"))
+		break;
+	case player_thokitem:
 		lua_pushinteger(L, plr->thokitem);
-	else if (fastcmp(field,"spinitem"))
+		break;
+	case player_spinitem:
 		lua_pushinteger(L, plr->spinitem);
-	else if (fastcmp(field,"revitem"))
+		break;
+	case player_revitem:
 		lua_pushinteger(L, plr->revitem);
-	else if (fastcmp(field,"followitem"))
+		break;
+	case player_followitem:
 		lua_pushinteger(L, plr->followitem);
-	else if (fastcmp(field,"followmobj"))
+		break;
+	case player_followmobj:
 		LUA_PushUserdata(L, plr->followmobj, META_MOBJ);
-	else if (fastcmp(field,"actionspd"))
+		break;
+	case player_actionspd:
 		lua_pushfixed(L, plr->actionspd);
-	else if (fastcmp(field,"mindash"))
+		break;
+	case player_mindash:
 		lua_pushfixed(L, plr->mindash);
-	else if (fastcmp(field,"maxdash"))
+		break;
+	case player_maxdash:
 		lua_pushfixed(L, plr->maxdash);
-	else if (fastcmp(field,"jumpfactor"))
+		break;
+	case player_jumpfactor:
 		lua_pushfixed(L, plr->jumpfactor);
-	else if (fastcmp(field,"height"))
+		break;
+	case player_height:
 		lua_pushfixed(L, plr->height);
-	else if (fastcmp(field,"spinheight"))
+		break;
+	case player_spinheight:
 		lua_pushfixed(L, plr->spinheight);
-	else if (fastcmp(field,"lives"))
+		break;
+	case player_lives:
 		lua_pushinteger(L, plr->lives);
-	else if (fastcmp(field,"continues"))
+		break;
+	case player_continues:
 		lua_pushinteger(L, plr->continues);
-	else if (fastcmp(field,"xtralife"))
+		break;
+	case player_xtralife:
 		lua_pushinteger(L, plr->xtralife);
-	else if (fastcmp(field,"gotcontinue"))
+		break;
+	case player_gotcontinue:
 		lua_pushinteger(L, plr->gotcontinue);
-	else if (fastcmp(field,"speed"))
+		break;
+	case player_speed:
 		lua_pushfixed(L, plr->speed);
-	else if (fastcmp(field,"secondjump"))
+		break;
+	case player_secondjump:
 		lua_pushinteger(L, plr->secondjump);
-	else if (fastcmp(field,"fly1"))
+		break;
+	case player_fly1:
 		lua_pushinteger(L, plr->fly1);
-	else if (fastcmp(field,"scoreadd"))
+		break;
+	case player_scoreadd:
 		lua_pushinteger(L, plr->scoreadd);
-	else if (fastcmp(field,"glidetime"))
+		break;
+	case player_glidetime:
 		lua_pushinteger(L, plr->glidetime);
-	else if (fastcmp(field,"climbing"))
+		break;
+	case player_climbing:
 		lua_pushinteger(L, plr->climbing);
-	else if (fastcmp(field,"deadtimer"))
+		break;
+	case player_deadtimer:
 		lua_pushinteger(L, plr->deadtimer);
-	else if (fastcmp(field,"exiting"))
+		break;
+	case player_exiting:
 		lua_pushinteger(L, plr->exiting);
-	else if (fastcmp(field,"homing"))
+		break;
+	case player_homing:
 		lua_pushinteger(L, plr->homing);
-	else if (fastcmp(field,"dashmode"))
+		break;
+	case player_dashmode:
 		lua_pushinteger(L, plr->dashmode);
-	else if (fastcmp(field,"skidtime"))
+		break;
+	case player_skidtime:
 		lua_pushinteger(L, plr->skidtime);
-	else if (fastcmp(field,"cmomx"))
+		break;
+	case player_cmomx:
 		lua_pushfixed(L, plr->cmomx);
-	else if (fastcmp(field,"cmomy"))
+		break;
+	case player_cmomy:
 		lua_pushfixed(L, plr->cmomy);
-	else if (fastcmp(field,"rmomx"))
+		break;
+	case player_rmomx:
 		lua_pushfixed(L, plr->rmomx);
-	else if (fastcmp(field,"rmomy"))
+		break;
+	case player_rmomy:
 		lua_pushfixed(L, plr->rmomy);
-	else if (fastcmp(field,"numboxes"))
+		break;
+	case player_numboxes:
 		lua_pushinteger(L, plr->numboxes);
-	else if (fastcmp(field,"totalring"))
+		break;
+	case player_totalring:
 		lua_pushinteger(L, plr->totalring);
-	else if (fastcmp(field,"realtime"))
+		break;
+	case player_realtime:
 		lua_pushinteger(L, plr->realtime);
-	else if (fastcmp(field,"laps"))
+		break;
+	case player_laps:
 		lua_pushinteger(L, plr->laps);
-	else if (fastcmp(field,"ctfteam"))
+		break;
+	case player_ctfteam:
 		lua_pushinteger(L, plr->ctfteam);
-	else if (fastcmp(field,"gotflag"))
+		break;
+	case player_gotflag:
 		lua_pushinteger(L, plr->gotflag);
-	else if (fastcmp(field,"weapondelay"))
+		break;
+	case player_weapondelay:
 		lua_pushinteger(L, plr->weapondelay);
-	else if (fastcmp(field,"tossdelay"))
+		break;
+	case player_tossdelay:
 		lua_pushinteger(L, plr->tossdelay);
-	else if (fastcmp(field,"starpostx"))
+		break;
+	case player_starpostx:
 		lua_pushinteger(L, plr->starpostx);
-	else if (fastcmp(field,"starposty"))
+		break;
+	case player_starposty:
 		lua_pushinteger(L, plr->starposty);
-	else if (fastcmp(field,"starpostz"))
+		break;
+	case player_starpostz:
 		lua_pushinteger(L, plr->starpostz);
-	else if (fastcmp(field,"starpostnum"))
+		break;
+	case player_starpostnum:
 		lua_pushinteger(L, plr->starpostnum);
-	else if (fastcmp(field,"starposttime"))
+		break;
+	case player_starposttime:
 		lua_pushinteger(L, plr->starposttime);
-	else if (fastcmp(field,"starpostangle"))
+		break;
+	case player_starpostangle:
 		lua_pushangle(L, plr->starpostangle);
-	else if (fastcmp(field,"starpostscale"))
+		break;
+	case player_starpostscale:
 		lua_pushfixed(L, plr->starpostscale);
-	else if (fastcmp(field,"angle_pos"))
+		break;
+	case player_angle_pos:
 		lua_pushangle(L, plr->angle_pos);
-	else if (fastcmp(field,"old_angle_pos"))
+		break;
+	case player_old_angle_pos:
 		lua_pushangle(L, plr->old_angle_pos);
-	else if (fastcmp(field,"axis1"))
+		break;
+	case player_axis1:
 		LUA_PushUserdata(L, plr->axis1, META_MOBJ);
-	else if (fastcmp(field,"axis2"))
+		break;
+	case player_axis2:
 		LUA_PushUserdata(L, plr->axis2, META_MOBJ);
-	else if (fastcmp(field,"bumpertime"))
+		break;
+	case player_bumpertime:
 		lua_pushinteger(L, plr->bumpertime);
-	else if (fastcmp(field,"flyangle"))
+		break;
+	case player_flyangle:
 		lua_pushinteger(L, plr->flyangle);
-	else if (fastcmp(field,"drilltimer"))
+		break;
+	case player_drilltimer:
 		lua_pushinteger(L, plr->drilltimer);
-	else if (fastcmp(field,"linkcount"))
+		break;
+	case player_linkcount:
 		lua_pushinteger(L, plr->linkcount);
-	else if (fastcmp(field,"linktimer"))
+		break;
+	case player_linktimer:
 		lua_pushinteger(L, plr->linktimer);
-	else if (fastcmp(field,"anotherflyangle"))
+		break;
+	case player_anotherflyangle:
 		lua_pushinteger(L, plr->anotherflyangle);
-	else if (fastcmp(field,"nightstime"))
+		break;
+	case player_nightstime:
 		lua_pushinteger(L, plr->nightstime);
-	else if (fastcmp(field,"drillmeter"))
+		break;
+	case player_drillmeter:
 		lua_pushinteger(L, plr->drillmeter);
-	else if (fastcmp(field,"drilldelay"))
+		break;
+	case player_drilldelay:
 		lua_pushinteger(L, plr->drilldelay);
-	else if (fastcmp(field,"bonustime"))
+		break;
+	case player_bonustime:
 		lua_pushboolean(L, plr->bonustime);
-	else if (fastcmp(field,"capsule"))
+		break;
+	case player_capsule:
 		LUA_PushUserdata(L, plr->capsule, META_MOBJ);
-	else if (fastcmp(field,"drone"))
+		break;
+	case player_drone:
 		LUA_PushUserdata(L, plr->drone, META_MOBJ);
-	else if (fastcmp(field,"oldscale"))
+		break;
+	case player_oldscale:
 		lua_pushfixed(L, plr->oldscale);
-	else if (fastcmp(field,"mare"))
+		break;
+	case player_mare:
 		lua_pushinteger(L, plr->mare);
-	else if (fastcmp(field,"marelap"))
+		break;
+	case player_marelap:
 		lua_pushinteger(L, plr->marelap);
-	else if (fastcmp(field,"marebonuslap"))
+		break;
+	case player_marebonuslap:
 		lua_pushinteger(L, plr->marebonuslap);
-	else if (fastcmp(field,"marebegunat"))
+		break;
+	case player_marebegunat:
 		lua_pushinteger(L, plr->marebegunat);
-	else if (fastcmp(field,"startedtime"))
+		break;
+	case player_startedtime:
 		lua_pushinteger(L, plr->startedtime);
-	else if (fastcmp(field,"finishedtime"))
+		break;
+	case player_finishedtime:
 		lua_pushinteger(L, plr->finishedtime);
-	else if (fastcmp(field,"lapbegunat"))
+		break;
+	case player_lapbegunat:
 		lua_pushinteger(L, plr->lapbegunat);
-	else if (fastcmp(field,"lapstartedtime"))
+		break;
+	case player_lapstartedtime:
 		lua_pushinteger(L, plr->lapstartedtime);
-	else if (fastcmp(field,"finishedspheres"))
+		break;
+	case player_finishedspheres:
 		lua_pushinteger(L, plr->finishedspheres);
-	else if (fastcmp(field,"finishedrings"))
+		break;
+	case player_finishedrings:
 		lua_pushinteger(L, plr->finishedrings);
-	else if (fastcmp(field,"marescore"))
+		break;
+	case player_marescore:
 		lua_pushinteger(L, plr->marescore);
-	else if (fastcmp(field,"lastmarescore"))
+		break;
+	case player_lastmarescore:
 		lua_pushinteger(L, plr->lastmarescore);
-	else if (fastcmp(field,"totalmarescore"))
+		break;
+	case player_totalmarescore:
 		lua_pushinteger(L, plr->totalmarescore);
-	else if (fastcmp(field,"lastmare"))
+		break;
+	case player_lastmare:
 		lua_pushinteger(L, plr->lastmare);
-	else if (fastcmp(field,"lastmarelap"))
+		break;
+	case player_lastmarelap:
 		lua_pushinteger(L, plr->lastmarelap);
-	else if (fastcmp(field,"lastmarebonuslap"))
+		break;
+	case player_lastmarebonuslap:
 		lua_pushinteger(L, plr->lastmarebonuslap);
-	else if (fastcmp(field,"totalmarelap"))
+		break;
+	case player_totalmarelap:
 		lua_pushinteger(L, plr->totalmarelap);
-	else if (fastcmp(field,"totalmarebonuslap"))
+		break;
+	case player_totalmarebonuslap:
 		lua_pushinteger(L, plr->totalmarebonuslap);
-	else if (fastcmp(field,"maxlink"))
+		break;
+	case player_maxlink:
 		lua_pushinteger(L, plr->maxlink);
-	else if (fastcmp(field,"texttimer"))
+		break;
+	case player_texttimer:
 		lua_pushinteger(L, plr->texttimer);
-	else if (fastcmp(field,"textvar"))
+		break;
+	case player_textvar:
 		lua_pushinteger(L, plr->textvar);
-	else if (fastcmp(field,"lastsidehit"))
+		break;
+	case player_lastsidehit:
 		lua_pushinteger(L, plr->lastsidehit);
-	else if (fastcmp(field,"lastlinehit"))
+		break;
+	case player_lastlinehit:
 		lua_pushinteger(L, plr->lastlinehit);
-	else if (fastcmp(field,"losstime"))
+		break;
+	case player_losstime:
 		lua_pushinteger(L, plr->losstime);
-	else if (fastcmp(field,"timeshit"))
+		break;
+	case player_timeshit:
 		lua_pushinteger(L, plr->timeshit);
-	else if (fastcmp(field,"onconveyor"))
+		break;
+	case player_onconveyor:
 		lua_pushinteger(L, plr->onconveyor);
-	else if (fastcmp(field,"awayviewmobj"))
+		break;
+	case player_awayviewmobj:
 		LUA_PushUserdata(L, plr->awayviewmobj, META_MOBJ);
-	else if (fastcmp(field,"awayviewtics"))
+		break;
+	case player_awayviewtics:
 		lua_pushinteger(L, plr->awayviewtics);
-	else if (fastcmp(field,"awayviewaiming"))
+		break;
+	case player_awayviewaiming:
 		lua_pushangle(L, plr->awayviewaiming);
-	else if (fastcmp(field,"spectator"))
+		break;
+	case player_spectator:
 		lua_pushboolean(L, plr->spectator);
-	else if (fastcmp(field,"outofcoop"))
+		break;
+	case player_outofcoop:
 		lua_pushboolean(L, plr->outofcoop);
-	else if (fastcmp(field,"bot"))
+		break;
+	case player_bot:
 		lua_pushinteger(L, plr->bot);
-	else if (fastcmp(field,"botleader"))
+		break;
+	case player_botleader:
 		LUA_PushUserdata(L, plr->botleader, META_PLAYER);
-	else if (fastcmp(field,"lastbuttons"))
+		break;
+	case player_lastbuttons:
 		lua_pushinteger(L, plr->lastbuttons);
-	else if (fastcmp(field,"blocked"))
+		break;
+	case player_blocked:
 		lua_pushboolean(L, plr->blocked);
-	else if (fastcmp(field,"jointime"))
+		break;
+	case player_jointime:
 		lua_pushinteger(L, plr->jointime);
-	else if (fastcmp(field,"quittime"))
+		break;
+	case player_quittime:
 		lua_pushinteger(L, plr->quittime);
+		break;
 #ifdef HWRENDER
-	else if (fastcmp(field,"fovadd"))
+	case player_fovadd:
 		lua_pushfixed(L, plr->fovadd);
+		break;
 #endif
-	else {
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, plr);
 		lua_rawget(L, -2);
 		if (!lua_istable(L, -1)) { // no extra values table
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no extvars table or field named '%s'; returning nil.\n"), "player_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no extvars table or field named '%s'; returning nil.\n"), "player_t", lua_tostring(L, 2));
 			return 0;
 		}
-		lua_getfield(L, -1, field);
+		lua_pushvalue(L, 2); // field name
+		lua_gettable(L, -2);
 		if (lua_isnil(L, -1)) // no value for this field
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "player_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; returning nil.\n"), "player_t", lua_tostring(L, 2));
+		break;
 	}
 
 	return 1;
@@ -405,7 +847,7 @@ static int player_get(lua_State *L)
 static int player_set(lua_State *L)
 {
 	player_t *plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
-	const char *field = luaL_checkstring(L, 2);
+	enum player_e field = Lua_optoption(L, 2, player_cmd, player_fields_ref);
 	if (!plr)
 		return LUA_ErrInvalid(L, "player_t");
 
@@ -414,297 +856,424 @@ static int player_set(lua_State *L)
 	if (hook_cmd_running)
 		return luaL_error(L, "Do not alter player_t in CMD building code!");
 
-	if (fastcmp(field,"mo") || fastcmp(field,"realmo")) {
+	switch (field)
+	{
+	case player_mo:
+	case player_realmo:
+	{
 		mobj_t *newmo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		plr->mo->player = NULL; // remove player pointer from old mobj
 		(newmo->player = plr)->mo = newmo; // set player pointer for new mobj, and set new mobj as the player's mobj
+		break;
 	}
-	else if (fastcmp(field,"cmd"))
+	case player_cmd:
 		return NOSET;
-	else if (fastcmp(field,"playerstate"))
+	case player_playerstate:
 		plr->playerstate = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"camerascale"))
+		break;
+	case player_camerascale:
 		plr->camerascale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"shieldscale"))
+		break;
+	case player_shieldscale:
 		plr->shieldscale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"viewz"))
+		break;
+	case player_viewz:
 		plr->viewz = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"viewheight"))
+		break;
+	case player_viewheight:
 		plr->viewheight = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"deltaviewheight"))
+		break;
+	case player_deltaviewheight:
 		plr->deltaviewheight = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"bob"))
+		break;
+	case player_bob:
 		plr->bob = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"viewrollangle"))
+		break;
+	case player_viewrollangle:
 		plr->viewrollangle = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"aiming")) {
+		break;
+	case player_aiming:
+	{
 		plr->aiming = luaL_checkangle(L, 3);
 		if (plr == &players[consoleplayer])
 			localaiming = plr->aiming;
 		else if (plr == &players[secondarydisplayplayer])
 			localaiming2 = plr->aiming;
+		break;
 	}
-	else if (fastcmp(field,"drawangle"))
+	case player_drawangle:
 		plr->drawangle = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"rings"))
+		break;
+	case player_rings:
 		plr->rings = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spheres"))
+		break;
+	case player_spheres:
 		plr->spheres = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"pity"))
+		break;
+	case player_pity:
 		plr->pity = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"currentweapon"))
+		break;
+	case player_currentweapon:
 		plr->currentweapon = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ringweapons"))
+		break;
+	case player_ringweapons:
 		plr->ringweapons = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ammoremoval"))
+		break;
+	case player_ammoremoval:
 		plr->ammoremoval = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ammoremovaltimer"))
+		break;
+	case player_ammoremovaltimer:
 		plr->ammoremovaltimer = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ammoremovalweapon"))
+		break;
+	case player_ammoremovalweapon:
 		plr->ammoremovalweapon = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"powers"))
+		break;
+	case player_powers:
 		return NOSET;
-	else if (fastcmp(field,"pflags"))
+	case player_pflags:
 		plr->pflags = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"panim"))
+		break;
+	case player_panim:
 		plr->panim = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flashcount"))
+		break;
+	case player_flashcount:
 		plr->flashcount = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flashpal"))
+		break;
+	case player_flashpal:
 		plr->flashpal = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"skincolor"))
+		break;
+	case player_skincolor:
 	{
 		UINT16 newcolor = (UINT16)luaL_checkinteger(L,3);
 		if (newcolor >= numskincolors)
 			return luaL_error(L, "player.skincolor %d out of range (0 - %d).", newcolor, numskincolors-1);
 		plr->skincolor = newcolor;
+		break;
 	}
-	else if (fastcmp(field,"skin"))
+	case player_skin:
 		return NOSET;
-	else if (fastcmp(field,"availabilities"))
+	case player_availabilities:
 		return NOSET;
-	else if (fastcmp(field,"score"))
+	case player_score:
 		plr->score = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"dashspeed"))
+		break;
+	case player_dashspeed:
 		plr->dashspeed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"normalspeed"))
+		break;
+	case player_normalspeed:
 		plr->normalspeed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"runspeed"))
+		break;
+	case player_runspeed:
 		plr->runspeed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"thrustfactor"))
+		break;
+	case player_thrustfactor:
 		plr->thrustfactor = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"accelstart"))
+		break;
+	case player_accelstart:
 		plr->accelstart = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"acceleration"))
+		break;
+	case player_acceleration:
 		plr->acceleration = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"charability"))
+		break;
+	case player_charability:
 		plr->charability = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"charability2"))
+		break;
+	case player_charability2:
 		plr->charability2 = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"charflags"))
+		break;
+	case player_charflags:
 		plr->charflags = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"thokitem"))
+		break;
+	case player_thokitem:
 		plr->thokitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"spinitem"))
+		break;
+	case player_spinitem:
 		plr->spinitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"revitem"))
+		break;
+	case player_revitem:
 		plr->revitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"followitem"))
+		break;
+	case player_followitem:
 		plr->followitem = luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"followmobj"))
+		break;
+	case player_followmobj:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->followmobj, mo);
+		break;
 	}
-	else if (fastcmp(field,"actionspd"))
+	case player_actionspd:
 		plr->actionspd = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"mindash"))
+		break;
+	case player_mindash:
 		plr->mindash = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"maxdash"))
+		break;
+	case player_maxdash:
 		plr->maxdash = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"jumpfactor"))
+		break;
+	case player_jumpfactor:
 		plr->jumpfactor = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"height"))
+		break;
+	case player_height:
 		plr->height = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"spinheight"))
+		break;
+	case player_spinheight:
 		plr->spinheight = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"lives"))
+		break;
+	case player_lives:
 		plr->lives = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"continues"))
+		break;
+	case player_continues:
 		plr->continues = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"xtralife"))
+		break;
+	case player_xtralife:
 		plr->xtralife = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"gotcontinue"))
+		break;
+	case player_gotcontinue:
 		plr->gotcontinue = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"speed"))
+		break;
+	case player_speed:
 		plr->speed = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"secondjump"))
+		break;
+	case player_secondjump:
 		plr->secondjump = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"fly1"))
+		break;
+	case player_fly1:
 		plr->fly1 = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"scoreadd"))
+		break;
+	case player_scoreadd:
 		plr->scoreadd = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"glidetime"))
+		break;
+	case player_glidetime:
 		plr->glidetime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"climbing"))
+		break;
+	case player_climbing:
 		plr->climbing = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"deadtimer"))
+		break;
+	case player_deadtimer:
 		plr->deadtimer = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"exiting"))
+		break;
+	case player_exiting:
 		plr->exiting = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"homing"))
+		break;
+	case player_homing:
 		plr->homing = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"dashmode"))
+		break;
+	case player_dashmode:
 		plr->dashmode = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"skidtime"))
+		break;
+	case player_skidtime:
 		plr->skidtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"cmomx"))
+		break;
+	case player_cmomx:
 		plr->cmomx = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"cmomy"))
+		break;
+	case player_cmomy:
 		plr->cmomy = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"rmomx"))
+		break;
+	case player_rmomx:
 		plr->rmomx = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"rmomy"))
+		break;
+	case player_rmomy:
 		plr->rmomy = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"numboxes"))
+		break;
+	case player_numboxes:
 		plr->numboxes = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalring"))
+		break;
+	case player_totalring:
 		plr->totalring = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"realtime"))
+		break;
+	case player_realtime:
 		plr->realtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"laps"))
+		break;
+	case player_laps:
 		plr->laps = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"ctfteam"))
+		break;
+	case player_ctfteam:
 		plr->ctfteam = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"gotflag"))
+		break;
+	case player_gotflag:
 		plr->gotflag = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"weapondelay"))
+		break;
+	case player_weapondelay:
 		plr->weapondelay = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"tossdelay"))
+		break;
+	case player_tossdelay:
 		plr->tossdelay = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostx"))
+		break;
+	case player_starpostx:
 		plr->starpostx = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starposty"))
+		break;
+	case player_starposty:
 		plr->starposty = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostz"))
+		break;
+	case player_starpostz:
 		plr->starpostz = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostnum"))
+		break;
+	case player_starpostnum:
 		plr->starpostnum = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starposttime"))
+		break;
+	case player_starposttime:
 		plr->starposttime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"starpostangle"))
+		break;
+	case player_starpostangle:
 		plr->starpostangle = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"starpostscale"))
+		break;
+	case player_starpostscale:
 		plr->starpostscale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"angle_pos"))
+		break;
+	case player_angle_pos:
 		plr->angle_pos = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"old_angle_pos"))
+		break;
+	case player_old_angle_pos:
 		plr->old_angle_pos = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"axis1"))
+		break;
+	case player_axis1:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->axis1, mo);
+		break;
 	}
-	else if (fastcmp(field,"axis2"))
+	case player_axis2:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->axis2, mo);
+		break;
 	}
-	else if (fastcmp(field,"bumpertime"))
+	case player_bumpertime:
 		plr->bumpertime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"flyangle"))
+		break;
+	case player_flyangle:
 		plr->flyangle = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"drilltimer"))
+		break;
+	case player_drilltimer:
 		plr->drilltimer = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"linkcount"))
+		break;
+	case player_linkcount:
 		plr->linkcount = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"linktimer"))
+		break;
+	case player_linktimer:
 		plr->linktimer = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"anotherflyangle"))
+		break;
+	case player_anotherflyangle:
 		plr->anotherflyangle = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"nightstime"))
+		break;
+	case player_nightstime:
 		plr->nightstime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"drillmeter"))
+		break;
+	case player_drillmeter:
 		plr->drillmeter = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"drilldelay"))
+		break;
+	case player_drilldelay:
 		plr->drilldelay = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"bonustime"))
+		break;
+	case player_bonustime:
 		plr->bonustime = luaL_checkboolean(L, 3);
-	else if (fastcmp(field,"capsule"))
+		break;
+	case player_capsule:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->capsule, mo);
+		break;
 	}
-	else if (fastcmp(field,"drone"))
+	case player_drone:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
 			mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 		P_SetTarget(&plr->drone, mo);
+		break;
 	}
-	else if (fastcmp(field,"oldscale"))
+	case player_oldscale:
 		plr->oldscale = luaL_checkfixed(L, 3);
-	else if (fastcmp(field,"mare"))
+		break;
+	case player_mare:
 		plr->mare = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marelap"))
+		break;
+	case player_marelap:
 		plr->marelap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marebonuslap"))
+		break;
+	case player_marebonuslap:
 		plr->marebonuslap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marebegunat"))
+		break;
+	case player_marebegunat:
 		plr->marebegunat = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"startedtime"))
+		break;
+	case player_startedtime:
 		plr->startedtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"finishedtime"))
+		break;
+	case player_finishedtime:
 		plr->finishedtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lapbegunat"))
+		break;
+	case player_lapbegunat:
 		plr->lapbegunat = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lapstartedtime"))
+		break;
+	case player_lapstartedtime:
 		plr->lapstartedtime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"finishedspheres"))
+		break;
+	case player_finishedspheres:
 		plr->finishedspheres = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"finishedrings"))
+		break;
+	case player_finishedrings:
 		plr->finishedrings = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"marescore"))
+		break;
+	case player_marescore:
 		plr->marescore = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmarescore"))
+		break;
+	case player_lastmarescore:
 		plr->lastmarescore = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalmarescore"))
+		break;
+	case player_totalmarescore:
 		plr->totalmarescore = (UINT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmare"))
+		break;
+	case player_lastmare:
 		plr->lastmare = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmarelap"))
+		break;
+	case player_lastmarelap:
 		plr->lastmarelap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastmarebonuslap"))
+		break;
+	case player_lastmarebonuslap:
 		plr->lastmarebonuslap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalmarelap"))
+		break;
+	case player_totalmarelap:
 		plr->totalmarelap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"totalmarebonuslap"))
+		break;
+	case player_totalmarebonuslap:
 		plr->totalmarebonuslap = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"maxlink"))
+		break;
+	case player_maxlink:
 		plr->maxlink = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"texttimer"))
+		break;
+	case player_texttimer:
 		plr->texttimer = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"textvar"))
+		break;
+	case player_textvar:
 		plr->textvar = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastsidehit"))
+		break;
+	case player_lastsidehit:
 		plr->lastsidehit = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"lastlinehit"))
+		break;
+	case player_lastlinehit:
 		plr->lastlinehit = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"losstime"))
+		break;
+	case player_losstime:
 		plr->losstime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"timeshit"))
+		break;
+	case player_timeshit:
 		plr->timeshit = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"onconveyor"))
+		break;
+	case player_onconveyor:
 		plr->onconveyor = (INT32)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"awayviewmobj"))
+		break;
+	case player_awayviewmobj:
 	{
 		mobj_t *mo = NULL;
 		if (!lua_isnil(L, 3))
@@ -714,41 +1283,50 @@ static int player_set(lua_State *L)
 			P_ResetCamera(plr, &camera); // reset p1 camera on p1 getting an awayviewmobj
 		else if (splitscreen && plr == &players[secondarydisplayplayer])
 			P_ResetCamera(plr, &camera2);  // reset p2 camera on p2 getting an awayviewmobj
+		break;
 	}
-	else if (fastcmp(field,"awayviewtics"))
-	{
+	case player_awayviewtics:
 		plr->awayviewtics = (INT32)luaL_checkinteger(L, 3);
 		if (plr->awayviewtics && !plr->awayviewmobj) // awayviewtics must ALWAYS have an awayviewmobj set!!
 			P_SetTarget(&plr->awayviewmobj, plr->mo); // but since the script might set awayviewmobj immediately AFTER setting awayviewtics, use player mobj as filler for now.
-	}
-	else if (fastcmp(field,"awayviewaiming"))
+		break;
+	case player_awayviewaiming:
 		plr->awayviewaiming = luaL_checkangle(L, 3);
-	else if (fastcmp(field,"spectator"))
+		break;
+	case player_spectator:
 		plr->spectator = lua_toboolean(L, 3);
-	else if (fastcmp(field,"outofcoop"))
+		break;
+	case player_outofcoop:
 		plr->outofcoop = lua_toboolean(L, 3);
-	else if (fastcmp(field,"bot"))
+		break;
+	case player_bot:
 		return NOSET;
-	else if (fastcmp(field,"botleader"))
+	case player_botleader:
 	{
 		player_t *player = NULL;
 		if (!lua_isnil(L, 3))
 			player = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
 		plr->botleader = player;
+		break;
 	}
-	else if (fastcmp(field,"lastbuttons"))
+	case player_lastbuttons:
 		plr->lastbuttons = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"blocked"))
+		break;
+	case player_blocked:
 		plr->blocked = (UINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"jointime"))
+		break;
+	case player_jointime:
 		plr->jointime = (tic_t)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"quittime"))
+		break;
+	case player_quittime:
 		plr->quittime = (tic_t)luaL_checkinteger(L, 3);
+		break;
 #ifdef HWRENDER
-	else if (fastcmp(field,"fovadd"))
+	case player_fovadd:
 		plr->fovadd = luaL_checkfixed(L, 3);
+		break;
 #endif
-	else {
+	default:
 		lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
 		I_Assert(lua_istable(L, -1));
 		lua_pushlightuserdata(L, plr);
@@ -756,15 +1334,17 @@ static int player_set(lua_State *L)
 		if (lua_isnil(L, -1)) {
 			// This index doesn't have a table for extra values yet, let's make one.
 			lua_pop(L, 1);
-			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "player_t", field);
+			CONS_Debug(DBG_LUA, M_GetText("'%s' has no field named '%s'; adding it as Lua data.\n"), "player_t", lua_tostring(L, 2));
 			lua_newtable(L);
 			lua_pushlightuserdata(L, plr);
 			lua_pushvalue(L, -2); // ext value table
 			lua_rawset(L, -4); // LREG_EXTVARS table
 		}
+		lua_pushvalue(L, 2); // key
 		lua_pushvalue(L, 3); // value to store
-		lua_setfield(L, -2, field);
+		lua_settable(L, -3);
 		lua_pop(L, 2);
+		break;
 	}
 
 	return 0;
@@ -818,27 +1398,58 @@ static int power_len(lua_State *L)
 #define NOFIELD luaL_error(L, LUA_QL("ticcmd_t") " has no field named " LUA_QS, field)
 #define NOSET luaL_error(L, LUA_QL("ticcmd_t") " field " LUA_QS " should not be set directly.", field)
 
+enum ticcmd_e
+{
+	ticcmd_forwardmove,
+	ticcmd_sidemove,
+	ticcmd_angleturn,
+	ticcmd_aiming,
+	ticcmd_buttons,
+	ticcmd_latency,
+};
+
+static const char *const ticcmd_opt[] = {
+	"forwardmove",
+	"sidemove",
+	"angleturn",
+	"aiming",
+	"buttons",
+	"latency",
+	NULL,
+};
+
+static int ticcmd_fields_ref = LUA_NOREF;
+
 static int ticcmd_get(lua_State *L)
 {
 	ticcmd_t *cmd = *((ticcmd_t **)luaL_checkudata(L, 1, META_TICCMD));
-	const char *field = luaL_checkstring(L, 2);
+	enum ticcmd_e field = Lua_optoption(L, 2, -1, ticcmd_fields_ref);
 	if (!cmd)
 		return LUA_ErrInvalid(L, "player_t");
 
-	if (fastcmp(field,"forwardmove"))
+	switch (field)
+	{
+	case ticcmd_forwardmove:
 		lua_pushinteger(L, cmd->forwardmove);
-	else if (fastcmp(field,"sidemove"))
+		break;
+	case ticcmd_sidemove:
 		lua_pushinteger(L, cmd->sidemove);
-	else if (fastcmp(field,"angleturn"))
+		break;
+	case ticcmd_angleturn:
 		lua_pushinteger(L, cmd->angleturn);
-	else if (fastcmp(field,"aiming"))
+		break;
+	case ticcmd_aiming:
 		lua_pushinteger(L, cmd->aiming);
-	else if (fastcmp(field,"buttons"))
+		break;
+	case ticcmd_buttons:
 		lua_pushinteger(L, cmd->buttons);
-	else if (fastcmp(field,"latency"))
+		break;
+	case ticcmd_latency:
 		lua_pushinteger(L, cmd->latency);
-	else
+		break;
+	default:
 		return NOFIELD;
+	}
 
 	return 1;
 }
@@ -846,27 +1457,35 @@ static int ticcmd_get(lua_State *L)
 static int ticcmd_set(lua_State *L)
 {
 	ticcmd_t *cmd = *((ticcmd_t **)luaL_checkudata(L, 1, META_TICCMD));
-	const char *field = luaL_checkstring(L, 2);
+	enum ticcmd_e field = Lua_optoption(L, 2, -1, ticcmd_fields_ref);
 	if (!cmd)
 		return LUA_ErrInvalid(L, "ticcmd_t");
 
 	if (hud_running)
 		return luaL_error(L, "Do not alter player_t in HUD rendering code!");
 
-	if (fastcmp(field,"forwardmove"))
+	switch (field)
+	{
+	case ticcmd_forwardmove:
 		cmd->forwardmove = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"sidemove"))
+		break;
+	case ticcmd_sidemove:
 		cmd->sidemove = (SINT8)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"angleturn"))
+		break;
+	case ticcmd_angleturn:
 		cmd->angleturn = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"aiming"))
+		break;
+	case ticcmd_aiming:
 		cmd->aiming = (INT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"buttons"))
+		break;
+	case ticcmd_buttons:
 		cmd->buttons = (UINT16)luaL_checkinteger(L, 3);
-	else if (fastcmp(field,"latency"))
+		break;
+	case ticcmd_latency:
 		return NOSET;
-	else
+	default:
 		return NOFIELD;
+	}
 
 	return 0;
 }
@@ -887,6 +1506,8 @@ int LUA_PlayerLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	player_fields_ref = Lua_CreateFieldTable(L, player_opt);
+
 	luaL_newmetatable(L, META_POWERS);
 		lua_pushcfunction(L, power_get);
 		lua_setfield(L, -2, "__index");
@@ -906,6 +1527,8 @@ int LUA_PlayerLib(lua_State *L)
 		lua_setfield(L, -2, "__newindex");
 	lua_pop(L,1);
 
+	ticcmd_fields_ref = Lua_CreateFieldTable(L, ticcmd_opt);
+
 	lua_newuserdata(L, 0);
 		lua_createtable(L, 0, 2);
 			lua_pushcfunction(L, lib_getPlayer);
diff --git a/src/lua_script.c b/src/lua_script.c
index 9c7636ebe6a99a5e35abc0b7b49dc49a9e57c6fa..6893265d5754995ce2ef75a778b895f69c075480 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -306,6 +306,18 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		lua_pushinteger(L, ammoremovaltics);
 		return 1;
 	// end timers
+	} else if (fastcmp(word,"use1upSound")) {
+		lua_pushinteger(L, use1upSound);
+		return 1;
+	} else if (fastcmp(word,"maxXtraLife")) {
+		lua_pushinteger(L, maxXtraLife);
+		return 1;
+	} else if (fastcmp(word,"useContinues")) {
+		lua_pushinteger(L, useContinues);
+		return 1;
+	} else if (fastcmp(word,"shareEmblems")) {
+		lua_pushinteger(L, shareEmblems);
+		return 1;
 	} else if (fastcmp(word,"gametype")) {
 		lua_pushinteger(L, gametype);
 		return 1;
@@ -698,20 +710,23 @@ void LUA_DumpFile(const char *filename)
 
 fixed_t LUA_EvalMath(const char *word)
 {
-	lua_State *L = NULL;
+	static lua_State *L = NULL;
 	char buf[1024], *b;
 	const char *p;
 	fixed_t res = 0;
 
-	// make a new state so SOC can't interefere with scripts
-	// allocate state
-	L = lua_newstate(LUA_Alloc, NULL);
-	lua_atpanic(L, LUA_Panic);
-
-	// open only enum lib
-	lua_pushcfunction(L, LUA_EnumLib);
-	lua_pushboolean(L, true);
-	lua_call(L, 1, 0);
+	if (!L)
+	{
+		// make a new state so SOC can't interefere with scripts
+		// allocate state
+		L = lua_newstate(LUA_Alloc, NULL);
+		lua_atpanic(L, LUA_Panic);
+
+		// open only enum lib
+		lua_pushcfunction(L, LUA_EnumLib);
+		lua_pushboolean(L, true);
+		lua_call(L, 1, 0);
+	}
 
 	// change ^ into ^^ for Lua.
 	strcpy(buf, "return ");
@@ -736,8 +751,6 @@ fixed_t LUA_EvalMath(const char *word)
 	else
 		res = lua_tointeger(L, -1);
 
-	// clean up and return.
-	lua_close(L);
 	return res;
 }
 
@@ -1716,17 +1729,39 @@ void LUA_UnArchive(void)
 }
 
 // For mobj_t, player_t, etc. to take custom variables.
-int Lua_optoption(lua_State *L, int narg,
-	const char *def, const char *const lst[])
+int Lua_optoption(lua_State *L, int narg, int def, int list_ref)
 {
-	const char *name = (def) ? luaL_optstring(L, narg, def) :  luaL_checkstring(L, narg);
-	int i;
-	for (i=0; lst[i]; i++)
-		if (fastcmp(lst[i], name))
-			return i;
+	if (lua_isnoneornil(L, narg))
+		return def;
+
+	I_Assert(lua_checkstack(L, 2));
+	luaL_checkstring(L, narg);
+
+	lua_rawgeti(L, LUA_REGISTRYINDEX, list_ref);
+	I_Assert(lua_istable(L, -1));
+	lua_pushvalue(L, narg);
+	lua_rawget(L, -2);
+
+	if (lua_isnumber(L, -1))
+		return lua_tointeger(L, -1);
 	return -1;
 }
 
+int Lua_CreateFieldTable(lua_State *L, const char *const lst[])
+{
+	int i;
+
+	lua_newtable(L);
+	for (i = 0; lst[i] != NULL; i++)
+	{
+		lua_pushstring(L, lst[i]);
+		lua_pushinteger(L, i);
+		lua_settable(L, -3);
+	}
+
+	return luaL_ref(L, LUA_REGISTRYINDEX);
+}
+
 void LUA_PushTaggableObjectArray
 (		lua_State *L,
 		const char *field,
diff --git a/src/lua_script.h b/src/lua_script.h
index fe04e5e608fdeca20690799e5df2aa1c3b145db6..d0b06a719e32a254ba56c5f860252aba15484da0 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -57,8 +57,8 @@ 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(void *cvar); // lua_consolelib.c
-int Lua_optoption(lua_State *L, int narg,
-	const char *def, const char *const lst[]);
+int Lua_optoption(lua_State *L, int narg, int def, int list_ref);
+int Lua_CreateFieldTable(lua_State *L, const char *const lst[]);
 void LUA_HookNetArchive(lua_CFunction archFunc);
 
 void LUA_PushTaggableObjectArray
diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c
index 5c21b04c3a31ea4d29d0d73049dbd54d5ab1dc04..b7890a6c71ecdd1356715a7df95d73b4ab573ea5 100644
--- a/src/lua_skinlib.c
+++ b/src/lua_skinlib.c
@@ -55,6 +55,7 @@ enum skin {
 	skin_soundsid,
 	skin_sprites
 };
+
 static const char *const skin_opt[] = {
 	"valid",
 	"name",
@@ -95,10 +96,12 @@ static const char *const skin_opt[] = {
 
 #define UNIMPLEMENTED luaL_error(L, LUA_QL("skin_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", skin_opt[field])
 
+static int skin_fields_ref = LUA_NOREF;
+
 static int skin_get(lua_State *L)
 {
 	skin_t *skin = *((skin_t **)luaL_checkudata(L, 1, META_SKIN));
-	enum skin field = luaL_checkoption(L, 2, NULL, skin_opt);
+	enum skin field = Lua_optoption(L, 2, -1, skin_fields_ref);
 
 	// skins are always valid, only added, never removed
 	I_Assert(skin != NULL);
@@ -376,6 +379,8 @@ int LUA_SkinLib(lua_State *L)
 		lua_setfield(L, -2, "__len");
 	lua_pop(L,1);
 
+	skin_fields_ref = Lua_CreateFieldTable(L, skin_opt);
+
 	luaL_newmetatable(L, META_SOUNDSID);
 		lua_pushcfunction(L, soundsid_get);
 		lua_setfield(L, -2, "__index");
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 9d257b48b6452d57788e2f45edba5a6f28c7252b..e370335f8332d65fd7f5824270f99a81d9ba7a4b 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -79,9 +79,9 @@ static UINT8 cheatf_warp(void)
 
 	// Temporarily unlock stuff.
 	G_SetUsedCheats(false);
-	unlockables[31].unlocked = true; // credits
-	unlockables[30].unlocked = true; // sound test
-	unlockables[28].unlocked = true; // level select
+	clientGamedata->unlocked[31] = true; // credits
+	clientGamedata->unlocked[30] = true; // sound test
+	clientGamedata->unlocked[28] = true; // level select
 
 	// Refresh secrets menu existing.
 	M_ClearMenus(true);
@@ -102,7 +102,7 @@ static UINT8 cheatf_devmode(void)
 	// Just unlock all the things and turn on -debug and console devmode.
 	G_SetUsedCheats(false);
 	for (i = 0; i < MAXUNLOCKABLES; i++)
-		unlockables[i].unlocked = true;
+		clientGamedata->unlocked[i] = true;
 	devparm = true;
 	cv_debug |= 0x8000;
 
@@ -238,7 +238,7 @@ boolean cht_Responder(event_t *ev)
 }
 
 // Console cheat commands rely on these a lot...
-#define REQUIRE_PANDORA if (!M_SecretUnlocked(SECRET_PANDORA) && !cv_debug)\
+#define REQUIRE_PANDORA if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !cv_debug)\
 { CONS_Printf(M_GetText("You haven't earned this yet.\n")); return; }
 
 #define REQUIRE_DEVMODE if (!cv_debug)\
diff --git a/src/m_cond.c b/src/m_cond.c
index bd6faadfd3991aab0a6adcd3d7c1cd4a9fd33708..a54988f67accbb8f790fbfc06e9a9055b2362f7c 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -21,6 +21,9 @@
 #include "r_skins.h" // numskins
 #include "r_draw.h" // R_GetColorByName
 
+gamedata_t *clientGamedata; // Our gamedata
+gamedata_t *serverGamedata; // Server's gamedata
+
 // Map triggers for linedef executors
 // 32 triggers, one bit each
 UINT32 unlocktriggers;
@@ -41,6 +44,70 @@ unlockable_t unlockables[MAXUNLOCKABLES];
 INT32 numemblems = 0;
 INT32 numextraemblems = 0;
 
+// Temporary holding place for nights data for the current map
+nightsdata_t ntemprecords;
+
+// Create a new gamedata_t, for start-up
+gamedata_t *M_NewGameDataStruct(void)
+{
+	gamedata_t *data = Z_Calloc(sizeof (*data), PU_STATIC, NULL);
+	M_ClearSecrets(data);
+	G_ClearRecords(data);
+	return data;
+}
+
+void M_CopyGameData(gamedata_t *dest, gamedata_t *src)
+{
+	INT32 i, j;
+
+	M_ClearSecrets(dest);
+	G_ClearRecords(dest);
+
+	dest->loaded = src->loaded;
+	dest->totalplaytime = src->totalplaytime;
+
+	dest->timesBeaten = src->timesBeaten;
+	dest->timesBeatenWithEmeralds = src->timesBeatenWithEmeralds;
+	dest->timesBeatenUltimate = src->timesBeatenUltimate;
+
+	memcpy(dest->achieved, src->achieved, sizeof(dest->achieved));
+	memcpy(dest->collected, src->collected, sizeof(dest->collected));
+	memcpy(dest->extraCollected, src->extraCollected, sizeof(dest->extraCollected));
+	memcpy(dest->unlocked, src->unlocked, sizeof(dest->unlocked));
+
+	memcpy(dest->mapvisited, src->mapvisited, sizeof(dest->mapvisited));
+
+	// Main records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		if (!src->mainrecords[i])
+			continue;
+
+		G_AllocMainRecordData((INT16)i, dest);
+		dest->mainrecords[i]->score = src->mainrecords[i]->score;
+		dest->mainrecords[i]->time = src->mainrecords[i]->time;
+		dest->mainrecords[i]->rings = src->mainrecords[i]->rings;
+	}
+
+	// Nights records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		if (!src->nightsrecords[i] || !src->nightsrecords[i]->nummares)
+			continue;
+
+		G_AllocNightsRecordData((INT16)i, dest);
+
+		for (j = 0; j < (src->nightsrecords[i]->nummares + 1); j++)
+		{
+			dest->nightsrecords[i]->score[j] = src->nightsrecords[i]->score[j];
+			dest->nightsrecords[i]->grade[j] = src->nightsrecords[i]->grade[j];
+			dest->nightsrecords[i]->time[j] = src->nightsrecords[i]->time[j];
+		}
+
+		dest->nightsrecords[i]->nummares = src->nightsrecords[i]->nummares;
+	}
+}
+
 void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2)
 {
 	condition_t *cond;
@@ -70,89 +137,90 @@ void M_ClearConditionSet(UINT8 set)
 		conditionSets[set - 1].condition = NULL;
 		conditionSets[set - 1].numconditions = 0;
 	}
-	conditionSets[set - 1].achieved = false;
+	clientGamedata->achieved[set - 1] = serverGamedata->achieved[set - 1] = false;
 }
 
 // Clear ALL secrets.
-void M_ClearSecrets(void)
+void M_ClearSecrets(gamedata_t *data)
 {
 	INT32 i;
 
-	memset(mapvisited, 0, sizeof(mapvisited));
+	memset(data->mapvisited, 0, sizeof(data->mapvisited));
 
 	for (i = 0; i < MAXEMBLEMS; ++i)
-		emblemlocations[i].collected = false;
+		data->collected[i] = false;
 	for (i = 0; i < MAXEXTRAEMBLEMS; ++i)
-		extraemblems[i].collected = false;
+		data->extraCollected[i] = false;
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
-		unlockables[i].unlocked = false;
+		data->unlocked[i] = false;
 	for (i = 0; i < MAXCONDITIONSETS; ++i)
-		conditionSets[i].achieved = false;
+		data->achieved[i] = false;
 
-	timesBeaten = timesBeatenWithEmeralds = timesBeatenUltimate = 0;
+	data->timesBeaten = data->timesBeatenWithEmeralds = data->timesBeatenUltimate = 0;
 
 	// Re-unlock any always unlocked things
-	M_SilentUpdateUnlockablesAndEmblems();
+	M_SilentUpdateUnlockablesAndEmblems(data);
+	M_SilentUpdateSkinAvailabilites();
 }
 
 // ----------------------
 // Condition set checking
 // ----------------------
-static UINT8 M_CheckCondition(condition_t *cn)
+static UINT8 M_CheckCondition(condition_t *cn, gamedata_t *data)
 {
 	switch (cn->type)
 	{
 		case UC_PLAYTIME: // Requires total playing time >= x
-			return (totalplaytime >= (unsigned)cn->requirement);
+			return (data->totalplaytime >= (unsigned)cn->requirement);
 		case UC_GAMECLEAR: // Requires game beaten >= x times
-			return (timesBeaten >= (unsigned)cn->requirement);
+			return (data->timesBeaten >= (unsigned)cn->requirement);
 		case UC_ALLEMERALDS: // Requires game beaten with all 7 emeralds >= x times
-			return (timesBeatenWithEmeralds >= (unsigned)cn->requirement);
+			return (data->timesBeatenWithEmeralds >= (unsigned)cn->requirement);
 		case UC_ULTIMATECLEAR: // Requires game beaten on ultimate >= x times (in other words, never)
-			return (timesBeatenUltimate >= (unsigned)cn->requirement);
+			return (data->timesBeatenUltimate >= (unsigned)cn->requirement);
 		case UC_OVERALLSCORE: // Requires overall score >= x
-			return (M_GotHighEnoughScore(cn->requirement));
+			return (M_GotHighEnoughScore(cn->requirement, data));
 		case UC_OVERALLTIME: // Requires overall time <= x
-			return (M_GotLowEnoughTime(cn->requirement));
+			return (M_GotLowEnoughTime(cn->requirement, data));
 		case UC_OVERALLRINGS: // Requires overall rings >= x
-			return (M_GotHighEnoughRings(cn->requirement));
+			return (M_GotHighEnoughRings(cn->requirement, data));
 		case UC_MAPVISITED: // Requires map x to be visited
-			return ((mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED);
+			return ((data->mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED);
 		case UC_MAPBEATEN: // Requires map x to be beaten
-			return ((mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN);
+			return ((data->mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN);
 		case UC_MAPALLEMERALDS: // Requires map x to be beaten with all emeralds in possession
-			return ((mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS);
+			return ((data->mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS);
 		case UC_MAPULTIMATE: // Requires map x to be beaten on ultimate
-			return ((mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE);
+			return ((data->mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE);
 		case UC_MAPPERFECT: // Requires map x to be beaten with a perfect bonus
-			return ((mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT);
+			return ((data->mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT);
 		case UC_MAPSCORE: // Requires score on map >= x
-			return (G_GetBestScore(cn->extrainfo1) >= (unsigned)cn->requirement);
+			return (G_GetBestScore(cn->extrainfo1, data) >= (unsigned)cn->requirement);
 		case UC_MAPTIME: // Requires time on map <= x
-			return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement);
+			return (G_GetBestTime(cn->extrainfo1, data) <= (unsigned)cn->requirement);
 		case UC_MAPRINGS: // Requires rings on map >= x
-			return (G_GetBestRings(cn->extrainfo1) >= cn->requirement);
+			return (G_GetBestRings(cn->extrainfo1, data) >= cn->requirement);
 		case UC_NIGHTSSCORE:
-			return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2) >= (unsigned)cn->requirement);
+			return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2, data) >= (unsigned)cn->requirement);
 		case UC_NIGHTSTIME:
-			return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2) <= (unsigned)cn->requirement);
+			return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2, data) <= (unsigned)cn->requirement);
 		case UC_NIGHTSGRADE:
-			return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2) >= cn->requirement);
+			return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2, data) >= cn->requirement);
 		case UC_TRIGGER: // requires map trigger set
 			return !!(unlocktriggers & (1 << cn->requirement));
 		case UC_TOTALEMBLEMS: // Requires number of emblems >= x
-			return (M_GotEnoughEmblems(cn->requirement));
+			return (M_GotEnoughEmblems(cn->requirement, data));
 		case UC_EMBLEM: // Requires emblem x to be obtained
-			return emblemlocations[cn->requirement-1].collected;
+			return data->collected[cn->requirement-1];
 		case UC_EXTRAEMBLEM: // Requires extra emblem x to be obtained
-			return extraemblems[cn->requirement-1].collected;
+			return data->extraCollected[cn->requirement-1];
 		case UC_CONDITIONSET: // requires condition set x to already be achieved
-			return M_Achieved(cn->requirement-1);
+			return M_Achieved(cn->requirement-1, data);
 	}
 	return false;
 }
 
-static UINT8 M_CheckConditionSet(conditionset_t *c)
+static UINT8 M_CheckConditionSet(conditionset_t *c, gamedata_t *data)
 {
 	UINT32 i;
 	UINT32 lastID = 0;
@@ -173,13 +241,13 @@ static UINT8 M_CheckConditionSet(conditionset_t *c)
 			continue;
 
 		lastID = cn->id;
-		achievedSoFar = M_CheckCondition(cn);
+		achievedSoFar = M_CheckCondition(cn, data);
 	}
 
 	return achievedSoFar;
 }
 
-void M_CheckUnlockConditions(void)
+void M_CheckUnlockConditions(gamedata_t *data)
 {
 	INT32 i;
 	conditionset_t *c;
@@ -187,27 +255,27 @@ void M_CheckUnlockConditions(void)
 	for (i = 0; i < MAXCONDITIONSETS; ++i)
 	{
 		c = &conditionSets[i];
-		if (!c->numconditions || c->achieved)
+		if (!c->numconditions || data->achieved[i])
 			continue;
 
-		c->achieved = (M_CheckConditionSet(c));
+		data->achieved[i] = (M_CheckConditionSet(c, data));
 	}
 }
 
-UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
+UINT8 M_UpdateUnlockablesAndExtraEmblems(gamedata_t *data)
 {
 	INT32 i;
 	char cechoText[992] = "";
 	UINT8 cechoLines = 0;
 
-	M_CheckUnlockConditions();
+	M_CheckUnlockConditions(data);
 
 	// Go through extra emblems
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected || !extraemblems[i].conditionset)
+		if (data->extraCollected[i] || !extraemblems[i].conditionset)
 			continue;
-		if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false)
+		if ((data->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1, data)) != false)
 		{
 			strcat(cechoText, va(M_GetText("Got \"%s\" emblem!\\"), extraemblems[i].name));
 			++cechoLines;
@@ -217,14 +285,14 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
 	// Fun part: if any of those unlocked we need to go through the
 	// unlock conditions AGAIN just in case an emblem reward was reached
 	if (cechoLines)
-		M_CheckUnlockConditions();
+		M_CheckUnlockConditions(data);
 
 	// Go through unlockables
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (unlockables[i].unlocked || !unlockables[i].conditionset)
+		if (data->unlocked[i] || !unlockables[i].conditionset)
 			continue;
-		if ((unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1)) != false)
+		if ((data->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1, data)) != false)
 		{
 			if (unlockables[i].nocecho)
 				continue;
@@ -248,45 +316,50 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void)
 		HU_DoCEcho(slashed);
 		return true;
 	}
+
 	return false;
 }
 
 // Used when loading gamedata to make sure all unlocks are synched with conditions
-void M_SilentUpdateUnlockablesAndEmblems(void)
+void M_SilentUpdateUnlockablesAndEmblems(gamedata_t *data)
 {
 	INT32 i;
 	boolean checkAgain = false;
 
 	// Just in case they aren't to sync
-	M_CheckUnlockConditions();
-	M_CheckLevelEmblems();
+	M_CheckUnlockConditions(data);
+	M_CheckLevelEmblems(data);
+	M_CompletionEmblems(data);
 
 	// Go through extra emblems
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected || !extraemblems[i].conditionset)
+		if (data->extraCollected[i] || !extraemblems[i].conditionset)
 			continue;
-		if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false)
+		if ((data->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1, data)) != false)
 			checkAgain = true;
 	}
 
 	// check again if extra emblems unlocked, blah blah, etc
 	if (checkAgain)
-		M_CheckUnlockConditions();
+		M_CheckUnlockConditions(data);
 
 	// Go through unlockables
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (unlockables[i].unlocked || !unlockables[i].conditionset)
+		if (data->unlocked[i] || !unlockables[i].conditionset)
 			continue;
-		unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1);
+		data->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1, data);
 	}
+}
 
+void M_SilentUpdateSkinAvailabilites(void)
+{
 	players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
 }
 
 // Emblem unlocking shit
-UINT8 M_CheckLevelEmblems(void)
+UINT8 M_CheckLevelEmblems(gamedata_t *data)
 {
 	INT32 i;
 	INT32 valToReach;
@@ -297,7 +370,7 @@ UINT8 M_CheckLevelEmblems(void)
 	// Update Score, Time, Rings emblems
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || emblemlocations[i].collected)
+		if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || data->collected[i])
 			continue;
 
 		levelnum = emblemlocations[i].level;
@@ -306,32 +379,32 @@ UINT8 M_CheckLevelEmblems(void)
 		switch (emblemlocations[i].type)
 		{
 			case ET_SCORE: // Requires score on map >= x
-				res = (G_GetBestScore(levelnum) >= (unsigned)valToReach);
+				res = (G_GetBestScore(levelnum, data) >= (unsigned)valToReach);
 				break;
 			case ET_TIME: // Requires time on map <= x
-				res = (G_GetBestTime(levelnum) <= (unsigned)valToReach);
+				res = (G_GetBestTime(levelnum, data) <= (unsigned)valToReach);
 				break;
 			case ET_RINGS: // Requires rings on map >= x
-				res = (G_GetBestRings(levelnum) >= valToReach);
+				res = (G_GetBestRings(levelnum, data) >= valToReach);
 				break;
 			case ET_NGRADE: // Requires NiGHTS grade on map >= x
-				res = (G_GetBestNightsGrade(levelnum, 0) >= valToReach);
+				res = (G_GetBestNightsGrade(levelnum, 0, data) >= valToReach);
 				break;
 			case ET_NTIME: // Requires NiGHTS time on map <= x
-				res = (G_GetBestNightsTime(levelnum, 0) <= (unsigned)valToReach);
+				res = (G_GetBestNightsTime(levelnum, 0, data) <= (unsigned)valToReach);
 				break;
 			default: // unreachable but shuts the compiler up.
 				continue;
 		}
 
-		emblemlocations[i].collected = res;
+		data->collected[i] = res;
 		if (res)
 			++somethingUnlocked;
 	}
 	return somethingUnlocked;
 }
 
-UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
+UINT8 M_CompletionEmblems(gamedata_t *data) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
 {
 	INT32 i;
 	INT32 embtype;
@@ -342,7 +415,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
 
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].type != ET_MAP || emblemlocations[i].collected)
+		if (emblemlocations[i].type != ET_MAP || data->collected[i])
 			continue;
 
 		levelnum = emblemlocations[i].level;
@@ -358,9 +431,9 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
 		if (embtype & ME_PERFECT)
 			flags |= MV_PERFECT;
 
-		res = ((mapvisited[levelnum - 1] & flags) == flags);
+		res = ((data->mapvisited[levelnum - 1] & flags) == flags);
 
-		emblemlocations[i].collected = res;
+		data->collected[i] = res;
 		if (res)
 			++somethingUnlocked;
 	}
@@ -370,48 +443,54 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
 // -------------------
 // Quick unlock checks
 // -------------------
-UINT8 M_AnySecretUnlocked(void)
+UINT8 M_AnySecretUnlocked(gamedata_t *data)
 {
 	INT32 i;
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (!unlockables[i].nocecho && unlockables[i].unlocked)
+		if (!unlockables[i].nocecho && data->unlocked[i])
 			return true;
 	}
 	return false;
 }
 
-UINT8 M_SecretUnlocked(INT32 type)
+UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data)
 {
 	INT32 i;
 	for (i = 0; i < MAXUNLOCKABLES; ++i)
 	{
-		if (unlockables[i].type == type && unlockables[i].unlocked)
+		if (unlockables[i].type == type && data->unlocked[i])
 			return true;
 	}
 	return false;
 }
 
-UINT8 M_MapLocked(INT32 mapnum)
+UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data)
 {
 	if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0)
+	{
 		return false;
-	if (!unlockables[mapheaderinfo[mapnum-1]->unlockrequired].unlocked)
+	}
+
+	if (!data->unlocked[mapheaderinfo[mapnum-1]->unlockrequired])
+	{
 		return true;
+	}
+
 	return false;
 }
 
-INT32 M_CountEmblems(void)
+INT32 M_CountEmblems(gamedata_t *data)
 {
 	INT32 found = 0, i;
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].collected)
+		if (data->collected[i])
 			found++;
 	}
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected)
+		if (data->extraCollected[i])
 			found++;
 	}
 	return found;
@@ -423,23 +502,23 @@ INT32 M_CountEmblems(void)
 
 // Theoretically faster than using M_CountEmblems()
 // Stops when it reaches the target number of emblems.
-UINT8 M_GotEnoughEmblems(INT32 number)
+UINT8 M_GotEnoughEmblems(INT32 number, gamedata_t *data)
 {
 	INT32 i, gottenemblems = 0;
 	for (i = 0; i < numemblems; ++i)
 	{
-		if (emblemlocations[i].collected)
+		if (data->collected[i])
 			if (++gottenemblems >= number) return true;
 	}
 	for (i = 0; i < numextraemblems; ++i)
 	{
-		if (extraemblems[i].collected)
+		if (data->extraCollected[i])
 			if (++gottenemblems >= number) return true;
 	}
 	return false;
 }
 
-UINT8 M_GotHighEnoughScore(INT32 tscore)
+UINT8 M_GotHighEnoughScore(INT32 tscore, gamedata_t *data)
 {
 	INT32 mscore = 0;
 	INT32 i;
@@ -448,16 +527,16 @@ UINT8 M_GotHighEnoughScore(INT32 tscore)
 	{
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
-		if (!mainrecords[i])
+		if (!data->mainrecords[i])
 			continue;
 
-		if ((mscore += mainrecords[i]->score) > tscore)
+		if ((mscore += data->mainrecords[i]->score) > tscore)
 			return true;
 	}
 	return false;
 }
 
-UINT8 M_GotLowEnoughTime(INT32 tictime)
+UINT8 M_GotLowEnoughTime(INT32 tictime, gamedata_t *data)
 {
 	INT32 curtics = 0;
 	INT32 i;
@@ -467,15 +546,15 @@ UINT8 M_GotLowEnoughTime(INT32 tictime)
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
 
-		if (!mainrecords[i] || !mainrecords[i]->time)
+		if (!data->mainrecords[i] || !data->mainrecords[i]->time)
 			return false;
-		else if ((curtics += mainrecords[i]->time) > tictime)
+		else if ((curtics += data->mainrecords[i]->time) > tictime)
 			return false;
 	}
 	return true;
 }
 
-UINT8 M_GotHighEnoughRings(INT32 trings)
+UINT8 M_GotHighEnoughRings(INT32 trings, gamedata_t *data)
 {
 	INT32 mrings = 0;
 	INT32 i;
@@ -484,10 +563,10 @@ UINT8 M_GotHighEnoughRings(INT32 trings)
 	{
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
-		if (!mainrecords[i])
+		if (!data->mainrecords[i])
 			continue;
 
-		if ((mrings += mainrecords[i]->rings) > trings)
+		if ((mrings += data->mainrecords[i]->rings) > trings)
 			return true;
 	}
 	return false;
diff --git a/src/m_cond.h b/src/m_cond.h
index d49dc920b30433b5d0ac416186ec9ba1cf9b7894..6a3da79ece7091c7ef0f68981355e6d0a691bb79 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -10,7 +10,11 @@
 /// \file  m_cond.h
 /// \brief Unlockable condition system for SRB2 version 2.1
 
+#ifndef __M_COND__
+#define __M_COND__
+
 #include "doomdef.h"
+#include "doomdata.h"
 
 // --------
 // Typedefs
@@ -61,8 +65,6 @@ typedef struct
 {
 	UINT32 numconditions;   /// <- number of conditions.
 	condition_t *condition; /// <- All conditionals to be checked.
-	UINT8 achieved;         /// <- Whether this conditional has been achieved already or not.
-	                        ///    (Conditional checking is skipped if true -- it's assumed you can't relock an unlockable)
 } conditionset_t;
 
 // Emblem information
@@ -94,7 +96,6 @@ typedef struct
 	INT32 var;       ///< If needed, specifies information on the target amount to achieve (or target skin)
 	char *stringVar; ///< String version
 	char hint[110];  ///< Hint for emblem hints menu
-	UINT8 collected; ///< Do you have this emblem?
 } emblem_t;
 typedef struct
 {
@@ -104,7 +105,6 @@ typedef struct
 	UINT8 showconditionset; ///< Condition set that shows this emblem.
 	UINT8 sprite;           ///< emblem sprite to use, 0 - 25
 	UINT16 color;           ///< skincolor to use
-	UINT8 collected;        ///< Do you have this emblem?
 } extraemblem_t;
 
 // Unlockable information
@@ -120,7 +120,6 @@ typedef struct
 	char *stringVar;
 	UINT8 nocecho;
 	UINT8 nochecklist;
-	UINT8 unlocked;
 } unlockable_t;
 
 #define SECRET_NONE         -6 // Does nil.  Use with levels locked by UnlockRequired
@@ -143,6 +142,83 @@ typedef struct
 #define MAXEXTRAEMBLEMS   16
 #define MAXUNLOCKABLES    32
 
+/** Time attack information, currently a very small structure.
+  */
+typedef struct
+{
+	tic_t time;   ///< Time in which the level was finished.
+	UINT32 score; ///< Score when the level was finished.
+	UINT16 rings; ///< Rings when the level was finished.
+} recorddata_t;
+
+/** Setup for one NiGHTS map.
+  * These are dynamically allocated because I am insane
+  */
+#define GRADE_F 0
+#define GRADE_E 1
+#define GRADE_D 2
+#define GRADE_C 3
+#define GRADE_B 4
+#define GRADE_A 5
+#define GRADE_S 6
+
+typedef struct
+{
+	// 8 mares, 1 overall (0)
+	UINT8	nummares;
+	UINT32	score[9];
+	UINT8	grade[9];
+	tic_t	time[9];
+} nightsdata_t;
+
+// mapvisited is now a set of flags that says what we've done in the map.
+#define MV_VISITED      1
+#define MV_BEATEN       2
+#define MV_ALLEMERALDS  4
+#define MV_ULTIMATE     8
+#define MV_PERFECT     16
+#define MV_PERFECTRA   32
+#define MV_MAX         63 // used in gamedata check, update whenever MV's are added
+
+// Temporary holding place for nights data for the current map
+extern nightsdata_t ntemprecords;
+
+// GAMEDATA STRUCTURE
+// Everything that would get saved in gamedata.dat
+typedef struct
+{
+	// WHENEVER OR NOT WE'RE READY TO SAVE
+	boolean loaded;
+
+	// CONDITION SETS ACHIEVED
+	boolean achieved[MAXCONDITIONSETS];
+
+	// EMBLEMS COLLECTED
+	boolean collected[MAXEMBLEMS];
+
+	// EXTRA EMBLEMS COLLECTED
+	boolean extraCollected[MAXEXTRAEMBLEMS];
+
+	// UNLOCKABLES UNLOCKED
+	boolean unlocked[MAXUNLOCKABLES];
+
+	// TIME ATTACK DATA
+	recorddata_t *mainrecords[NUMMAPS];
+	nightsdata_t *nightsrecords[NUMMAPS];
+	UINT8 mapvisited[NUMMAPS];
+
+	// # OF TIMES THE GAME HAS BEEN BEATEN
+	UINT32 timesBeaten;
+	UINT32 timesBeatenWithEmeralds;
+	UINT32 timesBeatenUltimate;
+
+	// PLAY TIME
+	UINT32 totalplaytime;
+} gamedata_t;
+
+extern gamedata_t *clientGamedata;
+extern gamedata_t *serverGamedata;
+
 extern conditionset_t conditionSets[MAXCONDITIONSETS];
 extern emblem_t emblemlocations[MAXEMBLEMS];
 extern extraemblem_t extraemblems[MAXEXTRAEMBLEMS];
@@ -153,25 +229,30 @@ extern INT32 numextraemblems;
 
 extern UINT32 unlocktriggers;
 
+gamedata_t *M_NewGameDataStruct(void);
+void M_CopyGameData(gamedata_t *dest, gamedata_t *src);
+
 // Condition set setup
 void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2);
 
 // Clearing secrets
 void M_ClearConditionSet(UINT8 set);
-void M_ClearSecrets(void);
+void M_ClearSecrets(gamedata_t *data);
 
 // Updating conditions and unlockables
-void M_CheckUnlockConditions(void);
-UINT8 M_UpdateUnlockablesAndExtraEmblems(void);
-void M_SilentUpdateUnlockablesAndEmblems(void);
-UINT8 M_CheckLevelEmblems(void);
-UINT8 M_CompletionEmblems(void);
+void M_CheckUnlockConditions(gamedata_t *data);
+UINT8 M_UpdateUnlockablesAndExtraEmblems(gamedata_t *data);
+void M_SilentUpdateUnlockablesAndEmblems(gamedata_t *data);
+UINT8 M_CheckLevelEmblems(gamedata_t *data);
+UINT8 M_CompletionEmblems(gamedata_t *data);
+
+void M_SilentUpdateSkinAvailabilites(void);
 
 // Checking unlockable status
-UINT8 M_AnySecretUnlocked(void);
-UINT8 M_SecretUnlocked(INT32 type);
-UINT8 M_MapLocked(INT32 mapnum);
-INT32 M_CountEmblems(void);
+UINT8 M_AnySecretUnlocked(gamedata_t *data);
+UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data);
+UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data);
+INT32 M_CountEmblems(gamedata_t *data);
 
 // Emblem shit
 emblem_t *M_GetLevelEmblems(INT32 mapnum);
@@ -183,12 +264,14 @@ const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big);
 // If you're looking to compare stats for unlocks or what not, use these
 // They stop checking upon reaching the target number so they
 // should be (theoretically?) slightly faster.
-UINT8 M_GotEnoughEmblems(INT32 number);
-UINT8 M_GotHighEnoughScore(INT32 tscore);
-UINT8 M_GotLowEnoughTime(INT32 tictime);
-UINT8 M_GotHighEnoughRings(INT32 trings);
+UINT8 M_GotEnoughEmblems(INT32 number, gamedata_t *data);
+UINT8 M_GotHighEnoughScore(INT32 tscore, gamedata_t *data);
+UINT8 M_GotLowEnoughTime(INT32 tictime, gamedata_t *data);
+UINT8 M_GotHighEnoughRings(INT32 trings, gamedata_t *data);
 
 INT32 M_UnlockableSkinNum(unlockable_t *unlock);
 INT32 M_EmblemSkinNum(emblem_t *emblem);
 
-#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved)
+#define M_Achieved(a, data) ((a) >= MAXCONDITIONSETS || data->achieved[a])
+
+#endif
diff --git a/src/m_menu.c b/src/m_menu.c
index 64a1c940486a7034590c6dc2aa66488e61b5b8fd..f9f52335d00dd56f488e2c73f8242546cb33743f 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -241,6 +241,7 @@ static void M_EmblemHints(INT32 choice);
 static void M_HandleEmblemHints(INT32 choice);
 UINT32 hintpage = 1;
 static void M_HandleChecklist(INT32 choice);
+static void M_PauseLevelSelect(INT32 choice);
 menu_t SR_MainDef, SR_UnlockChecklistDef;
 
 static UINT8 check_on;
@@ -554,26 +555,30 @@ static menuitem_t MPauseMenu[] =
 {
 	{IT_STRING | IT_CALL,    NULL, "Add-ons...",                M_Addons,               8},
 	{IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...",         &MISC_ScrambleTeamDef, 16},
-	{IT_STRING | IT_CALL,    NULL, "Switch Gametype/Level...",  M_MapChange,           24},
+	{IT_STRING | IT_CALL,    NULL, "Emblem Hints...",           M_EmblemHints,         24},
+	{IT_STRING | IT_CALL,    NULL, "Switch Gametype/Level...",  M_MapChange,           32},
 
-	{IT_STRING | IT_CALL,    NULL, "Continue",                  M_SelectableClearMenus,40},
-	{IT_STRING | IT_CALL,    NULL, "Player 1 Setup",            M_SetupMultiPlayer,    48}, // splitscreen
-	{IT_STRING | IT_CALL,    NULL, "Player 2 Setup",            M_SetupMultiPlayer2,   56}, // splitscreen
+	{IT_STRING | IT_CALL,    NULL, "Continue",                  M_SelectableClearMenus,48},
 
-	{IT_STRING | IT_CALL,    NULL, "Spectate",                  M_ConfirmSpectate,     48},
-	{IT_STRING | IT_CALL,    NULL, "Enter Game",                M_ConfirmEnterGame,    48},
-	{IT_STRING | IT_SUBMENU, NULL, "Switch Team...",            &MISC_ChangeTeamDef,   48},
-	{IT_STRING | IT_CALL,    NULL, "Player Setup",              M_SetupMultiPlayer,    56}, // alone
-	{IT_STRING | IT_CALL,    NULL, "Options",                   M_Options,             64},
+	{IT_STRING | IT_CALL,    NULL, "Player 1 Setup",            M_SetupMultiPlayer,    56}, // splitscreen
+	{IT_STRING | IT_CALL,    NULL, "Player 2 Setup",            M_SetupMultiPlayer2,   64},
 
-	{IT_STRING | IT_CALL,    NULL, "Return to Title",           M_EndGame,             80},
-	{IT_STRING | IT_CALL,    NULL, "Quit Game",                 M_QuitSRB2,            88},
+	{IT_STRING | IT_CALL,    NULL, "Spectate",                  M_ConfirmSpectate,     56}, // alone
+	{IT_STRING | IT_CALL,    NULL, "Enter Game",                M_ConfirmEnterGame,    56},
+	{IT_STRING | IT_SUBMENU, NULL, "Switch Team...",            &MISC_ChangeTeamDef,   56},
+	{IT_STRING | IT_CALL,    NULL, "Player Setup",              M_SetupMultiPlayer,    64},
+
+	{IT_STRING | IT_CALL,    NULL, "Options",                   M_Options,             72},
+
+	{IT_STRING | IT_CALL,    NULL, "Return to Title",           M_EndGame,             88},
+	{IT_STRING | IT_CALL,    NULL, "Quit Game",                 M_QuitSRB2,            96},
 };
 
 typedef enum
 {
 	mpause_addons = 0,
 	mpause_scramble,
+	mpause_hints,
 	mpause_switchmap,
 
 	mpause_continue,
@@ -597,7 +602,7 @@ static menuitem_t SPauseMenu[] =
 	// Pandora's Box will be shifted up if both options are available
 	{IT_CALL | IT_STRING,    NULL, "Pandora's Box...",     M_PandorasBox,         16},
 	{IT_CALL | IT_STRING,    NULL, "Emblem Hints...",      M_EmblemHints,         24},
-	{IT_CALL | IT_STRING,    NULL, "Level Select...",      M_LoadGameLevelSelect, 32},
+	{IT_CALL | IT_STRING,    NULL, "Level Select...",      M_PauseLevelSelect,    32},
 
 	{IT_CALL | IT_STRING,    NULL, "Continue",             M_SelectableClearMenus,48},
 	{IT_CALL | IT_STRING,    NULL, "Retry",                M_Retry,               56},
@@ -970,7 +975,7 @@ static menuitem_t MP_MainMenu[] =
 {
 	{IT_HEADER, NULL, "Join a game", NULL, 0},
 	{IT_STRING|IT_CALL,       NULL, "Server browser...",     M_ConnectMenuModChecks,          12},
-	{IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:", M_HandleConnectIP,      22},
+	{IT_STRING|IT_KEYHANDLER, NULL, "Specify server address:", M_HandleConnectIP,    22},
 	{IT_HEADER, NULL, "Host a game", NULL, 54},
 	{IT_STRING|IT_CALL,       NULL, "Internet/LAN...",       M_StartServerMenu,      66},
 	{IT_STRING|IT_CALL,       NULL, "Splitscreen...",        M_StartSplitServerMenu, 76},
@@ -1824,6 +1829,10 @@ menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(
 	MTREE4(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER, MN_SP_LEVELSELECT),
 	NULL, SP_LevelSelectMenu);
 
+menu_t SP_PauseLevelSelectDef = MAPPLATTERMENUSTYLE(
+	MTREE4(MN_SP_MAIN, MN_SP_LOAD, MN_SP_PLAYER, MN_SP_LEVELSELECT),
+	NULL, SP_LevelSelectMenu);
+
 menu_t SP_LevelStatsDef =
 {
 	MTREE2(MN_SP_MAIN, MN_SP_LEVELSTATS),
@@ -2284,6 +2293,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt);
 // Nextmap.  Used for Level select.
 void Nextmap_OnChange(void)
 {
+	gamedata_t *data = clientGamedata;
 	char *leveltitle;
 	char tabase[256];
 #ifdef OLDNREPLAYNAME
@@ -2301,7 +2311,7 @@ void Nextmap_OnChange(void)
 	{
 		CV_StealthSetValue(&cv_dummymares, 0);
 		// Hide the record changing CVAR if only one mare is available.
-		if (!nightsrecords[cv_nextmap.value-1] || nightsrecords[cv_nextmap.value-1]->nummares < 2)
+		if (!data->nightsrecords[cv_nextmap.value-1] || data->nightsrecords[cv_nextmap.value-1]->nummares < 2)
 			SP_NightsAttackMenu[narecords].status = IT_DISABLED;
 		else
 			SP_NightsAttackMenu[narecords].status = IT_STRING|IT_CVAR;
@@ -2432,14 +2442,15 @@ void Nextmap_OnChange(void)
 
 static void Dummymares_OnChange(void)
 {
-	if (!nightsrecords[cv_nextmap.value-1])
+	gamedata_t *data = clientGamedata;
+	if (!data->nightsrecords[cv_nextmap.value-1])
 	{
 		CV_StealthSetValue(&cv_dummymares, 0);
 		return;
 	}
 	else
 	{
-		UINT8 mares = nightsrecords[cv_nextmap.value-1]->nummares;
+		UINT8 mares = data->nightsrecords[cv_nextmap.value-1]->nummares;
 
 		if (cv_dummymares.value < 0)
 			CV_StealthSetValue(&cv_dummymares, mares);
@@ -3670,9 +3681,9 @@ void M_StartControlPanel(void)
 	if (!Playing())
 	{
 		// Secret menu!
-		MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked()) ? 76 : 84;
-		MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked()) ? 84 : 92;
-		MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked(clientGamedata)) ? 76 : 84;
+		MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked(clientGamedata)) ? 84 : 92;
+		MainMenu[secrets].status = (M_AnySecretUnlocked(clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		currentMenu = &MainDef;
 		itemOn = singleplr;
@@ -3680,14 +3691,14 @@ void M_StartControlPanel(void)
 	else if (modeattacking)
 	{
 		currentMenu = &MAPauseDef;
-		MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 		itemOn = mapause_continue;
 	}
 	else if (!(netgame || multiplayer)) // Single Player
 	{
 		if (gamestate != GS_LEVEL || ultimatemode) // intermission, so gray out stuff.
 		{
-			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_GRAYEDOUT) : (IT_DISABLED);
+			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata)) ? (IT_GRAYEDOUT) : (IT_DISABLED);
 			SPauseMenu[spause_retry].status = IT_GRAYEDOUT;
 		}
 		else
@@ -3696,7 +3707,7 @@ void M_StartControlPanel(void)
 			if (players[consoleplayer].playerstate != PST_LIVE)
 				++numlives;
 
-			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+			SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 			// The list of things that can disable retrying is (was?) a little too complex
 			// for me to want to use the short if statement syntax
@@ -3707,10 +3718,10 @@ void M_StartControlPanel(void)
 		}
 
 		// We can always use level select though. :33
-		SPauseMenu[spause_levelselect].status = (gamecomplete == 1) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		SPauseMenu[spause_levelselect].status = (maplistoption != 0) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		// And emblem hints.
-		SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		// Shift up Pandora's Box if both pandora and levelselect are active
 		/*if (SPauseMenu[spause_pandora].status != (IT_DISABLED)
@@ -3745,12 +3756,10 @@ void M_StartControlPanel(void)
 		if (splitscreen)
 		{
 			MPauseMenu[mpause_psetupsplit].status = MPauseMenu[mpause_psetupsplit2].status = IT_STRING | IT_CALL;
-			MPauseMenu[mpause_psetup].text = "Player 1 Setup";
 		}
 		else
 		{
 			MPauseMenu[mpause_psetup].status = IT_STRING | IT_CALL;
-			MPauseMenu[mpause_psetup].text = "Player Setup";
 
 			if (G_GametypeHasTeams())
 				MPauseMenu[mpause_switchteam].status = IT_STRING | IT_SUBMENU;
@@ -3760,6 +3769,8 @@ void M_StartControlPanel(void)
 				MPauseMenu[mpause_spectate].status = IT_GRAYEDOUT;
 		}
 
+		MPauseMenu[mpause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && G_CoopGametype()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+
 		currentMenu = &MPauseDef;
 		itemOn = mpause_continue;
 	}
@@ -4259,7 +4270,7 @@ static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y, boolean norecordatt
 			x -= 4;
 		lasttype = curtype;
 
-		if (emblem->collected)
+		if (clientGamedata->collected[emblem - emblemlocations])
 			V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
 			                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 		else
@@ -4692,7 +4703,9 @@ static void M_DrawGenericScrollMenu(void)
 
 static void M_DrawPauseMenu(void)
 {
-	if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
+	gamedata_t *data = clientGamedata;
+
+	if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
 	{
 		emblem_t *emblem_detail[3] = {NULL, NULL, NULL};
 		char emblem_text[3][20];
@@ -4720,7 +4733,7 @@ static void M_DrawPauseMenu(void)
 				{
 					case ET_SCORE:
 						snprintf(targettext, 9, "%d", emblem->var);
-						snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap));
+						snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap, data));
 
 						targettext[8] = 0;
 						currenttext[8] = 0;
@@ -4734,7 +4747,7 @@ static void M_DrawPauseMenu(void)
 							G_TicsToSeconds((tic_t)emblemslot),
 							G_TicsToCentiseconds((tic_t)emblemslot));
 
-						emblemslot = (INT32)G_GetBestTime(gamemap); // dumb hack pt ii
+						emblemslot = (INT32)G_GetBestTime(gamemap, data); // dumb hack pt ii
 						if ((tic_t)emblemslot == UINT32_MAX)
 							snprintf(currenttext, 9, "-:--.--");
 						else
@@ -4750,7 +4763,7 @@ static void M_DrawPauseMenu(void)
 						break;
 					case ET_RINGS:
 						snprintf(targettext, 9, "%d", emblem->var);
-						snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap));
+						snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap, data));
 
 						targettext[8] = 0;
 						currenttext[8] = 0;
@@ -4758,8 +4771,8 @@ static void M_DrawPauseMenu(void)
 						emblemslot = 2;
 						break;
 					case ET_NGRADE:
-						snprintf(targettext, 9, "%u", P_GetScoreForGradeOverall(gamemap, emblem->var));
-						snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0));
+						snprintf(targettext, 9, "%u", P_GetScoreForGrade(gamemap, 0, emblem->var));
+						snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0, data));
 
 						targettext[8] = 0;
 						currenttext[8] = 0;
@@ -4773,7 +4786,7 @@ static void M_DrawPauseMenu(void)
 							G_TicsToSeconds((tic_t)emblemslot),
 							G_TicsToCentiseconds((tic_t)emblemslot));
 
-						emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0); // dumb hack pt iv
+						emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0, data); // dumb hack pt iv
 						if ((tic_t)emblemslot == UINT32_MAX)
 							snprintf(currenttext, 9, "-:--.--");
 						else
@@ -4807,7 +4820,7 @@ static void M_DrawPauseMenu(void)
 			if (!emblem)
 				continue;
 
-			if (emblem->collected)
+			if (data->collected[emblem - emblemlocations])
 				V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 			else
@@ -5019,7 +5032,9 @@ static void M_PatchSkinNameTable(void)
 //
 static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 {
-	if (M_MapLocked(mapnum+1))
+	gamedata_t *data = serverGamedata;
+
+	if (M_MapLocked(mapnum+1, data))
 		return false; // not unlocked
 
 	switch (levellistmode)
@@ -5032,7 +5047,7 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 				return true;
 
 #ifndef DEVELOP
-			if (mapvisited[mapnum]) // MV_MP
+			if (data->mapvisited[mapnum])
 #endif
 				return true;
 
@@ -5040,7 +5055,7 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
 		case LLM_RECORDATTACK:
 		case LLM_NIGHTSATTACK:
 #ifndef DEVELOP
-			if (mapvisited[mapnum] & MV_MAX)
+			if (data->mapvisited[mapnum])
 				return true;
 
 			if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
@@ -5071,7 +5086,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
 	if (!mapheaderinfo[mapnum]->lvlttl[0])
 		return false;
 
-	/*if (M_MapLocked(mapnum+1))
+	/*if (M_MapLocked(mapnum+1, serverGamedata))
 		return false; // not unlocked*/
 
 	switch (levellistmode)
@@ -5966,7 +5981,7 @@ static void M_DrawLevelPlatterMenu(void)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 		else if (!curbghide || !titlemapinaction)
 		{
-			F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+			F_SkyScroll(curbgname);
 			// Draw and animate foreground
 			if (!strncmp("RECATKBG", curbgname, 8))
 				M_DrawRecordAttackForeground();
@@ -6228,7 +6243,7 @@ static void M_DrawMessageMenu(void)
 			}
 			else
 			{
-				F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+				F_SkyScroll(curbgname);
 				if (!strncmp("RECATKBG", curbgname, 8))
 					M_DrawRecordAttackForeground();
 			}
@@ -6904,7 +6919,7 @@ static void M_HandleAddons(INT32 choice)
 		closefilemenu(true);
 
 		// secrets disabled by addfile...
-		MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+		MainMenu[secrets].status = (M_AnySecretUnlocked(clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		if (currentMenu->prevMenu)
 			M_SetupNextMenu(currentMenu->prevMenu);
@@ -7118,6 +7133,7 @@ static void M_DestroyRobots(INT32 choice)
 static void M_LevelSelectWarp(INT32 choice)
 {
 	boolean fromloadgame = (currentMenu == &SP_LevelSelectDef);
+	boolean frompause = (currentMenu == &SP_PauseLevelSelectDef);
 
 	(void)choice;
 
@@ -7128,7 +7144,6 @@ static void M_LevelSelectWarp(INT32 choice)
 	}
 
 	startmap = (INT16)(cv_nextmap.value);
-
 	fromlevelselect = true;
 
 	if (fromloadgame)
@@ -7136,7 +7151,20 @@ static void M_LevelSelectWarp(INT32 choice)
 	else
 	{
 		cursaveslot = 0;
-		M_SetupChoosePlayer(0);
+
+		if (frompause)
+		{
+			M_ClearMenus(true);
+
+			G_DeferedInitNew(false, G_BuildMapName(startmap), cv_skin.value, false, fromlevelselect); // Not sure about using cv_skin here, but it seems fine in testing.
+			COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
+
+			if (levelselect.rows)
+				Z_Free(levelselect.rows);
+			levelselect.rows = NULL;
+		}
+		else
+			M_SetupChoosePlayer(0);
 	}
 }
 
@@ -7150,7 +7178,9 @@ static boolean checklist_cangodown; // uuuueeerggghhhh HACK
 
 static void M_HandleChecklist(INT32 choice)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 j;
+
 	switch (choice)
 	{
 		case KEY_DOWNARROW:
@@ -7167,7 +7197,7 @@ static void M_HandleChecklist(INT32 choice)
 						continue;
 					if (unlockables[j].conditionset > MAXCONDITIONSETS)
 						continue;
-					if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset))
+					if (!data->unlocked[j] && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset, data))
 						continue;
 					if (unlockables[j].conditionset == unlockables[check_on].conditionset)
 						continue;
@@ -7192,7 +7222,7 @@ static void M_HandleChecklist(INT32 choice)
 						continue;
 					if (unlockables[j].conditionset > MAXCONDITIONSETS)
 						continue;
-					if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset))
+					if (!data->unlocked[j] && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset, data))
 						continue;
 					if (j && unlockables[j].conditionset == unlockables[j-1].conditionset)
 						continue;
@@ -7218,6 +7248,9 @@ static void M_HandleChecklist(INT32 choice)
 
 static void M_DrawChecklist(void)
 {
+	gamedata_t *data = clientGamedata;
+	INT32 emblemCount = M_CountEmblems(data);
+
 	INT32 i = check_on, j = 0, y = currentMenu->y, emblems = numemblems+numextraemblems;
 	UINT32 condnum, previd, maxcond;
 	condition_t *cond;
@@ -7228,7 +7261,7 @@ static void M_DrawChecklist(void)
 	// draw emblem counter
 	if (emblems > 0)
 	{
-		V_DrawString(42, 20, (emblems == M_CountEmblems()) ? V_GREENMAP : 0, va("%d/%d", M_CountEmblems(), emblems));
+		V_DrawString(42, 20, (emblems == emblemCount) ? V_GREENMAP : 0, va("%d/%d", emblemCount, emblems));
 		V_DrawSmallScaledPatch(28, 20, 0, W_CachePatchName("EMBLICON", PU_PATCH));
 	}
 
@@ -7239,13 +7272,13 @@ static void M_DrawChecklist(void)
 	{
 		if (unlockables[i].name[0] == 0 //|| unlockables[i].nochecklist
 		|| !unlockables[i].conditionset || unlockables[i].conditionset > MAXCONDITIONSETS
-		|| (!unlockables[i].unlocked && unlockables[i].showconditionset && !M_Achieved(unlockables[i].showconditionset)))
+		|| (!data->unlocked[i] && unlockables[i].showconditionset && !M_Achieved(unlockables[i].showconditionset, data)))
 		{
 			i += 1;
 			continue;
 		}
 
-		V_DrawString(currentMenu->x, y, ((unlockables[i].unlocked) ? V_GREENMAP : V_TRANSLUCENT)|V_ALLOWLOWERCASE, ((unlockables[i].unlocked || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name)));
+		V_DrawString(currentMenu->x, y, ((data->unlocked[i]) ? V_GREENMAP : V_TRANSLUCENT)|V_ALLOWLOWERCASE, ((data->unlocked[i] || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name)));
 
 		for (j = i+1; j < MAXUNLOCKABLES; j++)
 		{
@@ -7323,7 +7356,7 @@ static void M_DrawChecklist(void)
 
 									if (title)
 									{
-										const char *level = ((M_MapLocked(cond[condnum].requirement) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].requirement-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
+										const char *level = ((M_MapLocked(cond[condnum].requirement, data) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].requirement-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
 
 										switch (cond[condnum].type)
 										{
@@ -7356,7 +7389,7 @@ static void M_DrawChecklist(void)
 
 									if (title)
 									{
-										const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
+										const char *level = ((M_MapLocked(cond[condnum].extrainfo1, data) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
 
 										switch (cond[condnum].type)
 										{
@@ -7425,7 +7458,7 @@ static void M_DrawChecklist(void)
 
 									if (title)
 									{
-										const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
+										const char *level = ((M_MapLocked(cond[condnum].extrainfo1, data) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title);
 
 										switch (cond[condnum].type)
 										{
@@ -7488,7 +7521,7 @@ static void M_DrawChecklist(void)
 
 		/*V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective));
 
-		if (unlockables[i].unlocked)
+		if (data->unlocked[i])
 			V_DrawString(308, 8+(24*j), V_YELLOWMAP, "Y");
 		else
 			V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");*/
@@ -7517,7 +7550,7 @@ static void M_EmblemHints(INT32 choice)
 
 	(void)choice;
 	SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_ARROWS) : (IT_DISABLED);
-	SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
+	SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata)) ? (IT_CVAR|IT_STRING) : (IT_SECRET);
 	hintpage = 1;
 	SR_EmblemHintDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&SR_EmblemHintDef);
@@ -7577,7 +7610,7 @@ static void M_DrawEmblemHints(void)
 
 		if (totalemblems >= ((hintpage-1)*(NUMHINTS*2) + 1) && totalemblems < (hintpage*NUMHINTS*2)+1){
 
-			if (emblem->collected)
+			if (clientGamedata->collected[i])
 			{
 				collected = V_GREENMAP;
 				V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
@@ -7647,6 +7680,26 @@ static void M_HandleEmblemHints(INT32 choice)
 
 }
 
+static void M_PauseLevelSelect(INT32 choice)
+{
+	(void)choice;
+
+	SP_PauseLevelSelectDef.prevMenu = currentMenu;
+	levellistmode = LLM_LEVELSELECT;
+
+	// maplistoption is NOT specified, so that this
+	// transfers the level select list from the menu
+	// used to enter the game to the pause menu.
+
+	if (!M_PrepareLevelPlatter(-1, true))
+	{
+		M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
+		return;
+	}
+
+	M_SetupNextMenu(&SP_PauseLevelSelectDef);
+}
+
 /*static void M_DrawSkyRoom(void)
 {
 	INT32 i, y = 0;
@@ -8117,7 +8170,7 @@ static void M_SecretsMenu(INT32 choice)
 
 		SR_MainMenu[i].status = IT_SECRET;
 
-		if (unlockables[ul].unlocked)
+		if (clientGamedata->unlocked[ul])
 		{
 			switch (unlockables[ul].type)
 			{
@@ -8154,6 +8207,7 @@ INT32 ultimate_selectable = false;
 static void M_NewGame(void)
 {
 	fromlevelselect = false;
+	maplistoption = 0;
 
 	startmap = spstage_start;
 	CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
@@ -8165,6 +8219,7 @@ static void M_CustomWarp(INT32 choice)
 {
 	INT32 ul = skyRoomMenuTranslations[choice-1];
 
+	maplistoption = 0;
 	startmap = (INT16)(unlockables[ul].variable);
 
 	M_SetupChoosePlayer(0);
@@ -8216,7 +8271,7 @@ static void M_SinglePlayerMenu(INT32 choice)
 
 	levellistmode = LLM_RECORDATTACK;
 	if (M_GametypeHasLevels(-1))
-		SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET;
+		SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK, clientGamedata)) ? IT_CALL|IT_STRING : IT_SECRET;
 	else // If Record Attack is nonexistent in the current add-on...
 	{
 		SP_MainMenu[sprecordattack].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the Record Attack option...
@@ -8226,7 +8281,7 @@ static void M_SinglePlayerMenu(INT32 choice)
 
 	levellistmode = LLM_NIGHTSATTACK;
 	if (M_GametypeHasLevels(-1))
-		SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE)) ? IT_CALL|IT_STRING : IT_SECRET;
+		SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE, clientGamedata)) ? IT_CALL|IT_STRING : IT_SECRET;
 	else // If NiGHTS Mode is nonexistent in the current add-on...
 	{
 		SP_MainMenu[spnightsmode].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the NiGHTS Mode option...
@@ -8249,7 +8304,7 @@ static void M_SinglePlayerMenu(INT32 choice)
 		SP_MainMenu[spnightsmode]  .alphaKey += 8;
 	}
 	else // Otherwise, if Marathon Run is allowed and Record Attack is unlocked, unlock Marathon Run!
-		SP_MainMenu[spmarathon].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED : IT_SECRET;
+		SP_MainMenu[spmarathon].status = (M_SecretUnlocked(SECRET_RECORDATTACK, clientGamedata)) ? IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED : IT_SECRET;
 
 
 	if (tutorialmap) // If there's a tutorial available in the current add-on...
@@ -8357,6 +8412,7 @@ static void M_StartTutorial(INT32 choice)
 	M_ClearMenus(true);
 	gamecomplete = 0;
 	cursaveslot = 0;
+	maplistoption = 0;
 	G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false);
 }
 
@@ -8719,6 +8775,10 @@ static void M_LoadSelect(INT32 choice)
 {
 	(void)choice;
 
+	// Reset here, if we want a level select
+	// M_LoadGameLevelSelect will set it for us.
+	maplistoption = 0;
+
 	if (saveSlotSelected == NOSAVESLOT) //last slot is play without saving
 	{
 		M_NewGame();
@@ -9626,7 +9686,7 @@ static void M_Statistics(INT32 choice)
 		if (!(mapheaderinfo[i]->typeoflevel & TOL_SP) || (mapheaderinfo[i]->menuflags & LF2_HIDEINSTATS))
 			continue;
 
-		if (!(mapvisited[i] & MV_MAX))
+		if (!(clientGamedata->mapvisited[i] & MV_MAX))
 			continue;
 
 		statsMapList[j++] = i;
@@ -9643,6 +9703,7 @@ static void M_Statistics(INT32 choice)
 
 static void M_DrawStatsMaps(int location)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 y = 80, i = -1;
 	INT16 mnum;
 	extraemblem_t *exemblem;
@@ -9710,14 +9771,14 @@ static void M_DrawStatsMaps(int location)
 		{
 			exemblem = &extraemblems[i];
 
-			if (exemblem->collected)
+			if (data->extraCollected[i])
 				V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_PATCH),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetExtraEmblemColor(exemblem), GTC_CACHE));
 			else
 				V_DrawSmallScaledPatch(292, y, 0, W_CachePatchName("NEEDIT", PU_PATCH));
 
 			V_DrawString(20, y, V_YELLOWMAP|V_ALLOWLOWERCASE,
-				(!exemblem->collected && exemblem->showconditionset && !M_Achieved(exemblem->showconditionset))
+				(!data->extraCollected[i] && exemblem->showconditionset && !M_Achieved(exemblem->showconditionset, data))
 				? M_CreateSecretMenuOption(exemblem->description)
 				: exemblem->description);
 		}
@@ -9734,6 +9795,7 @@ bottomarrow:
 
 static void M_DrawLevelStats(void)
 {
+	gamedata_t *data = clientGamedata;
 	char beststr[40];
 
 	tic_t besttime = 0;
@@ -9748,9 +9810,9 @@ static void M_DrawLevelStats(void)
 
 	V_DrawString(20, 24, V_YELLOWMAP, "Total Play Time:");
 	V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds",
-	                         G_TicsToHours(totalplaytime),
-	                         G_TicsToMinutes(totalplaytime, false),
-	                         G_TicsToSeconds(totalplaytime)));
+	                         G_TicsToHours(data->totalplaytime),
+	                         G_TicsToMinutes(data->totalplaytime, false),
+	                         G_TicsToSeconds(data->totalplaytime)));
 
 	for (i = 0; i < NUMMAPS; i++)
 	{
@@ -9759,25 +9821,25 @@ static void M_DrawLevelStats(void)
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
 
-		if (!mainrecords[i])
+		if (!data->mainrecords[i])
 		{
 			mapsunfinished++;
 			bestunfinished[0] = bestunfinished[1] = bestunfinished[2] = true;
 			continue;
 		}
 
-		if (mainrecords[i]->score > 0)
-			bestscore += mainrecords[i]->score;
+		if (data->mainrecords[i]->score > 0)
+			bestscore += data->mainrecords[i]->score;
 		else
 			mapunfinished = bestunfinished[0] = true;
 
-		if (mainrecords[i]->time > 0)
-			besttime += mainrecords[i]->time;
+		if (data->mainrecords[i]->time > 0)
+			besttime += data->mainrecords[i]->time;
 		else
 			mapunfinished = bestunfinished[1] = true;
 
-		if (mainrecords[i]->rings > 0)
-			bestrings += mainrecords[i]->rings;
+		if (data->mainrecords[i]->rings > 0)
+			bestrings += data->mainrecords[i]->rings;
 		else
 			mapunfinished = bestunfinished[2] = true;
 
@@ -9792,7 +9854,7 @@ static void M_DrawLevelStats(void)
 	else
 		V_DrawString(20, 56, V_GREENMAP, "(complete)");
 
-	V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
+	V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(data), numemblems+numextraemblems));
 	V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_PATCH));
 
 	sprintf(beststr, "%u", bestscore);
@@ -9859,6 +9921,7 @@ static void M_HandleLevelStats(INT32 choice)
 // Drawing function for Time Attack
 void M_DrawTimeAttackMenu(void)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 i, x, y, empatx, empaty, cursory = 0;
 	UINT16 dispstatus;
 	patch_t *PictureOfUrFace;	// my WHAT
@@ -9875,7 +9938,7 @@ void M_DrawTimeAttackMenu(void)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
 	else if (!curbghide || !titlemapinaction)
 	{
-		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
+		F_SkyScroll(curbgname);
 		// Draw and animate foreground
 		if (!strncmp("RECATKBG", curbgname, 8))
 			M_DrawRecordAttackForeground();
@@ -10017,7 +10080,7 @@ void M_DrawTimeAttackMenu(void)
 			empatx = empatch->leftoffset / 2;
 			empaty = empatch->topoffset / 2;
 
-			if (em->collected)
+			if (data->collected[em - emblemlocations])
 				V_DrawSmallMappedPatch(104+76+empatx, yHeight+lsheadingheight/2+empaty, 0, empatch,
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 			else
@@ -10030,34 +10093,34 @@ void M_DrawTimeAttackMenu(void)
 		// Draw in-level emblems.
 		M_DrawMapEmblems(cv_nextmap.value, 288, 28, true);
 
-		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
+		if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->score)
 			sprintf(beststr, "(none)");
 		else
-			sprintf(beststr, "%u", mainrecords[cv_nextmap.value-1]->score);
+			sprintf(beststr, "%u", data->mainrecords[cv_nextmap.value-1]->score);
 
 		V_DrawString(104-72, 33+lsheadingheight/2, V_YELLOWMAP, "SCORE:");
 		V_DrawRightAlignedString(104+64, 33+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
 		V_DrawRightAlignedString(104+72, 43+lsheadingheight/2, V_ALLOWLOWERCASE, reqscore);
 
-		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time)
+		if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->time)
 			sprintf(beststr, "(none)");
 		else
-			sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(mainrecords[cv_nextmap.value-1]->time, true),
-			                                 G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time),
-			                                 G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time));
+			sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(data->mainrecords[cv_nextmap.value-1]->time, true),
+			                                 G_TicsToSeconds(data->mainrecords[cv_nextmap.value-1]->time),
+			                                 G_TicsToCentiseconds(data->mainrecords[cv_nextmap.value-1]->time));
 
 		V_DrawString(104-72, 53+lsheadingheight/2, V_YELLOWMAP, "TIME:");
 		V_DrawRightAlignedString(104+64, 53+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
 		V_DrawRightAlignedString(104+72, 63+lsheadingheight/2, V_ALLOWLOWERCASE, reqtime);
 
-		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->rings)
+		if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->rings)
 			sprintf(beststr, "(none)");
 		else
-			sprintf(beststr, "%hu", mainrecords[cv_nextmap.value-1]->rings);
+			sprintf(beststr, "%hu", data->mainrecords[cv_nextmap.value-1]->rings);
 
 		V_DrawString(104-72, 73+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
 
-		V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((mapvisited[cv_nextmap.value-1] & MV_PERFECTRA) ? V_YELLOWMAP : 0), beststr);
+		V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((data->mapvisited[cv_nextmap.value-1] & MV_PERFECTRA) ? V_YELLOWMAP : 0), beststr);
 
 		V_DrawRightAlignedString(104+72, 83+lsheadingheight/2, V_ALLOWLOWERCASE, reqrings);
 	}
@@ -10151,6 +10214,7 @@ static void M_TimeAttack(INT32 choice)
 // Drawing function for Nights Attack
 void M_DrawNightsAttackMenu(void)
 {
+	gamedata_t *data = clientGamedata;
 	INT32 i, x, y, cursory = 0;
 	UINT16 dispstatus;
 
@@ -10217,10 +10281,10 @@ void M_DrawNightsAttackMenu(void)
 		lumpnum_t lumpnum;
 		char beststr[40];
 
-		//UINT8 bestoverall	= G_GetBestNightsGrade(cv_nextmap.value, 0);
-		UINT8 bestgrade		= G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value);
-		UINT32 bestscore	= G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value);
-		tic_t besttime		= G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value);
+		//UINT8 bestoverall	= G_GetBestNightsGrade(cv_nextmap.value, 0, data);
+		UINT8 bestgrade		= G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value, data);
+		UINT32 bestscore	= G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value, data);
+		tic_t besttime		= G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value, data);
 
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
@@ -10301,7 +10365,7 @@ void M_DrawNightsAttackMenu(void)
 						goto skipThisOne;
 				}
 
-				if (em->collected)
+				if (data->collected[em - emblemlocations])
 					V_DrawSmallMappedPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_PATCH),
 																 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 				else
@@ -10655,6 +10719,7 @@ static void M_Marathon(INT32 choice)
 	}
 
 	fromlevelselect = false;
+	maplistoption = 0;
 
 	startmap = spmarathon_start;
 	CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
@@ -11673,35 +11738,19 @@ static void M_StartServerMenu(INT32 choice)
 
 #define CONNIP_LEN 128
 static char setupm_ip[CONNIP_LEN];
-
 #define DOTS "... "
 
-// Draw the funky Connect IP menu. Tails 11-19-2002
-// So much work for such a little thing!
-static void M_DrawMPMainMenu(void)
+static void M_DrawConnectIP(void)
 {
 	INT32 x = currentMenu->x;
-	INT32 y = currentMenu->y;
+	INT32 y = currentMenu->y + 22;
+
 	const INT32 boxwidth = /*16*8 + 6*/ (BASEVIDWIDTH - 2*(x+5));
 	const INT32 maxstrwidth = boxwidth - 5;
 	char *drawnstr = malloc(sizeof(setupm_ip));
 	char *drawnstr_orig = drawnstr;
 	boolean drawthin, shorten = false;
 
-	// use generic drawer for cursor, items and title
-	M_DrawGenericMenu();
-
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+66,
-		((itemOn == 4) ? V_YELLOWMAP : 0), va("(2-%d players)", MAXPLAYERS));
-
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+76,
-		((itemOn == 5) ? V_YELLOWMAP : 0), "(2 players)");
-
-	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+116,
-		((itemOn == 8) ? V_YELLOWMAP : 0), "(splitscreen)");
-
-	y += 22;
-
 	V_DrawFill(x+5, y+4+5, boxwidth, 8+6, 159);
 
 	strcpy(drawnstr, setupm_ip);
@@ -11749,6 +11798,28 @@ static void M_DrawMPMainMenu(void)
 	free(drawnstr_orig);
 }
 
+// Draw the funky Connect IP menu. Tails 11-19-2002
+// So much work for such a little thing!
+static void M_DrawMPMainMenu(void)
+{
+	INT32 x = currentMenu->x;
+	INT32 y = currentMenu->y;
+
+	// use generic drawer for cursor, items and title
+	M_DrawGenericMenu();
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+66,
+		((itemOn == 4) ? V_YELLOWMAP : 0), va("(2-%d players)", MAXPLAYERS));
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+76,
+		((itemOn == 5) ? V_YELLOWMAP : 0), "(2 players)");
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+116,
+		((itemOn == 8) ? V_YELLOWMAP : 0), "(splitscreen)");
+
+	M_DrawConnectIP();
+}
+
 #undef DOTS
 
 // Tails 11-19-2002
@@ -11886,7 +11957,11 @@ static void M_HandleConnectIP(INT32 choice)
 				break;
 
 			// Rudimentary number and period enforcing - also allows letters so hostnames can be used instead
-			if ((choice >= '-' && choice <= ':') || (choice >= 'A' && choice <= 'Z') || (choice >= 'a' && choice <= 'z'))
+			// and square brackets for RFC 2732 IPv6 addresses
+			if ((choice >= '-' && choice <= ':') ||
+					(choice == '[' || choice == ']') ||
+					(choice >= 'A' && choice <= 'Z') ||
+					(choice >= 'a' && choice <= 'z'))
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_ip[l] = (char)choice;
@@ -12544,12 +12619,12 @@ static void M_EraseDataResponse(INT32 ch)
 
 	// Delete the data
 	if (erasecontext != 1)
-		G_ClearRecords();
+		G_ClearRecords(clientGamedata);
 	if (erasecontext != 0)
-		M_ClearSecrets();
+		M_ClearSecrets(clientGamedata);
 	if (erasecontext == 2)
 	{
-		totalplaytime = 0;
+		clientGamedata->totalplaytime = 0;
 		F_StartIntro();
 	}
 	BwehHehHe();
diff --git a/src/m_random.c b/src/m_random.c
index 3d0774a60b6c73895a31cfcb2f5d69d6fbfda02f..8b5138b9c86f77e6fa4a0d0867e4e62198fb2dca 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -14,12 +14,11 @@
 
 #include "doomdef.h"
 #include "doomtype.h"
-#include "doomstat.h" // totalplaytime
 
 #include "m_random.h"
 #include "m_fixed.h"
 
-
+#include "m_cond.h" // totalplaytime
 
 // ---------------------------
 // RNG functions (not synched)
@@ -252,5 +251,5 @@ void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed)
   */
 UINT32 M_RandomizedSeed(void)
 {
-	return ((totalplaytime & 0xFFFF) << 16)|M_RandomFixed();
+	return ((serverGamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed();
 }
diff --git a/src/mserv.h b/src/mserv.h
index 1c8d742d818915a61cc90389a067ca44a1752f2a..07253da8562906cb51c59e6a27f2ec289d62c9a8 100644
--- a/src/mserv.h
+++ b/src/mserv.h
@@ -33,7 +33,7 @@ typedef union
 typedef struct
 {
 	msg_header_t header;
-	char ip[16];
+	char ip[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"];
 	char port[8];
 	char name[32];
 	INT32 room;
diff --git a/src/p_inter.c b/src/p_inter.c
index 873448dcd614c62a368f7c53200e7403dea7a587..046a0a198ef4df08727b33b90b69d38596c9fc3e 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -164,6 +164,62 @@ boolean P_CanPickupItem(player_t *player, boolean weapon)
 	return true;
 }
 
+boolean P_CanPickupEmblem(player_t *player, INT32 emblemID)
+{
+	emblem_t *emblem = NULL;
+
+	if (emblemID < 0 || emblemID >= numemblems)
+	{
+		// Invalid emblem ID, can't pickup.
+		return false;
+	}
+
+	emblem = &emblemlocations[emblemID];
+
+	if (demoplayback)
+	{
+		// Never collect emblems in replays.
+		return false;
+	}
+
+	if (player->bot && player->bot != BOT_MPAI)
+	{
+		// Your little lap-dog can't grab these for you.
+		return false;
+	}
+
+	if (emblem->type == ET_SKIN)
+	{
+		INT32 skinnum = M_EmblemSkinNum(emblem);
+
+		if (player->skin != skinnum)
+		{
+			// Incorrect skin to pick up this emblem.
+			return false;
+		}
+	}
+
+	return true;
+}
+
+boolean P_EmblemWasCollected(INT32 emblemID)
+{
+	if (emblemID < 0 || emblemID >= numemblems)
+	{
+		// Invalid emblem ID, can't pickup.
+		return true;
+	}
+
+	if (shareEmblems && !serverGamedata->collected[emblemID])
+	{
+		// It can be worth collecting again if we're sharing emblems
+		// and the server doesn't have it.
+		return false;
+	}
+
+	return clientGamedata->collected[emblemID];
+}
+
 //
 // P_DoNightsScore
 //
@@ -563,7 +619,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			special->momx = special->momy = special->momz = 0;
 			P_GivePlayerSpheres(player, 1);
 
-			if (special->type == MT_BLUESPHERE)
+			if (special->type == MT_BLUESPHERE || special->type == MT_FLINGBLUESPHERE)
 			{
 				special->destscale = ((player->powers[pw_carry] == CR_NIGHTSMODE) ? 4 : 2)*special->scale;
 				if (states[special->info->deathstate].tics > 0)
@@ -738,13 +794,70 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		// Secret emblem thingy
 		case MT_EMBLEM:
 			{
-				if (demoplayback || (player->bot && player->bot != BOT_MPAI) || special->health <= 0 || special->health > MAXEMBLEMS)
+				const boolean toucherIsServer = ((player - players) == serverplayer);
+				const boolean consoleIsServer = (consoleplayer == serverplayer);
+				boolean prevCollected = false;
+
+				if ((special->flags2 & MF2_NIGHTSPULL)
+					&& (toucher == special->tracer))
+				{
+					// Since collecting may not remove the object,
+					// we need to manually stop it from chasing.
+					P_SetTarget(&special->tracer, NULL);
+					special->flags2 &= ~MF2_NIGHTSPULL;
+					special->movefactor = 0;
+					special->momx = special->momy = special->momz = 0;
+				}
+
+				if (!P_CanPickupEmblem(player, special->health - 1))
+				{
 					return;
-				emblemlocations[special->health-1].collected = true;
+				}
 
-				M_UpdateUnlockablesAndExtraEmblems();
-				G_SaveGameData();
-				break;
+				prevCollected = P_EmblemWasCollected(special->health - 1);
+
+				if (toucherIsServer || shareEmblems)
+				{
+					serverGamedata->collected[special->health-1] = true;
+					M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+				}
+
+				if (P_IsLocalPlayer(player) || (consoleIsServer && shareEmblems))
+				{
+					clientGamedata->collected[special->health-1] = true;
+					M_UpdateUnlockablesAndExtraEmblems(clientGamedata);
+					G_SaveGameData(clientGamedata);
+				}
+
+				if (netgame)
+				{
+					// This always spawns the object to prevent mobjnum issues,
+					// but makes the effect invisible to whoever it doesn't matter to.
+					mobj_t *spark = P_SpawnMobjFromMobj(special, 0, 0, 0, MT_SPARK);
+
+					if (prevCollected == false && P_EmblemWasCollected(special->health - 1) == true)
+					{
+						// Play the sound if it was collected.
+						S_StartSound((shareEmblems ? NULL : special), special->info->deathsound);
+					}
+					else
+					{
+						// We didn't collect it, make it invisible to us.
+						spark->flags2 |= MF2_DONTDRAW;
+					}
+
+					return;
+				}
+				else
+				{
+					if (prevCollected == false && P_EmblemWasCollected(special->health - 1) == true)
+					{
+						// Disappear when collecting for local games.
+						break;
+					}
+
+					return;
+				}
 			}
 
 		// CTF Flags
diff --git a/src/p_local.h b/src/p_local.h
index cc060e4eee3c9b45f5b0da4e1c9c43a61f5c8ccf..3c84d6fe2f2ddaded0260623b3a402969a8fe11c 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -510,6 +510,8 @@ void P_ClearStarPost(INT32 postnum);
 void P_ResetStarposts(void);
 
 boolean P_CanPickupItem(player_t *player, boolean weapon);
+boolean P_CanPickupEmblem(player_t *player, INT32 emblemID);
+boolean P_EmblemWasCollected(INT32 emblemID);
 void P_DoNightsScore(player_t *player);
 void P_DoMatchSuper(player_t *player);
 
diff --git a/src/p_map.c b/src/p_map.c
index 27d5ea7810e2d630ef1aad9647e8379168f63887..aa2bda90d9b4b7efe899176eadc34099267293f8 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2728,8 +2728,22 @@ increment_move
 	fixed_t thingtop;
 	floatok = false;
 
-	if (radius < MAXRADIUS/2)
-		radius = MAXRADIUS/2;
+	// This makes sure that there are no freezes from computing extremely small movements.
+	// Originally was MAXRADIUS/2, but that can cause some bad inconsistencies for small players.
+	radius = max(radius, thing->scale);
+
+	// And we also have to prevent Big Large (tm) movements, as those can skip too far
+	// across slopes and cause us to fail step up checks on them when we otherwise shouldn't.
+	radius = min(radius, 16 * thing->scale);
+
+	// (This whole "step" system is flawed; it was OK before, but the addition of slopes has
+	// exposed the problems with doing it like this. The right thing to do would be to use
+	// raycasting for physics to fix colliding in weird order, double-checking collisions,
+	// randomly colliding with slopes instead of going up them, etc. I don't feel like porting
+	// that from RR, as its both a huge sweeping change and still incomplete at the time of
+	// writing. Clamping radius to make our steps more precise will work just fine as long
+	// as you keep all of your crazy intentions to poke any of the other deep-rooted movement
+	// code to yourself. -- Sal 6/5/2023)
 
 	do {
 		if (thing->flags & MF_NOCLIP) {
@@ -4242,13 +4256,11 @@ void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 dama
 //  the way it was and call P_CheckSector (? was P_ChangeSector - Graue) again
 //  to undo the changes.
 //
-static boolean crushchange;
-static boolean nofit;
 
 //
 // PIT_ChangeSector
 //
-static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
+static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush, boolean crunch)
 {
 	mobj_t *killer = NULL;
 	//If a thing is both pushable and vulnerable, it doesn't block the crusher because it gets killed.
@@ -4272,11 +4284,7 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 	if (thing->z + thing->height > thing->ceilingz && thing->z <= thing->ceilingz)
 	{
 		if (immunepushable && thing->z + thing->height > thing->subsector->sector->ceilingheight)
-		{
-			//Thing is a pushable and blocks the moving ceiling
-			nofit = true;
-			return false;
-		}
+			return false; //Thing is a pushable and blocks the moving ceiling
 
 		//Check FOFs in the sector
 		if (thing->subsector->sector->ffloors && (realcrush || immunepushable))
@@ -4288,47 +4296,54 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 
 			for (rover = thing->subsector->sector->ffloors; rover; rover = rover->next)
 			{
-				if (!(((rover->fofflags & FOF_BLOCKPLAYER) && thing->player)
-				|| ((rover->fofflags & FOF_BLOCKOTHERS) && !thing->player)) || !(rover->fofflags & FOF_EXISTS))
+				thinker_t *think;
+
+				if (!(rover->fofflags & FOF_EXISTS))
+					continue;
+				if (thing->player && !(rover->fofflags & FOF_BLOCKPLAYER))
+					continue;
+				if (!thing->player && !(rover->fofflags & FOF_BLOCKOTHERS))
 					continue;
 
 				topheight = *rover->topheight;
 				bottomheight = *rover->bottomheight;
-				//topheight    = P_GetFFloorTopZAt   (rover, thing->x, thing->y);
-				//bottomheight = P_GetFFloorBottomZAt(rover, thing->x, thing->y);
+
+				if (bottomheight > thing->ceilingz)
+					continue;
 
 				delta1 = thing->z - (bottomheight + topheight)/2;
 				delta2 = thingtop - (bottomheight + topheight)/2;
-				if (bottomheight <= thing->ceilingz && abs(delta1) >= abs(delta2))
+				if (abs(delta1) < abs(delta2))
+					continue;
+
+				if (immunepushable)
+					return false; //FOF is blocked by pushable
+
+				if (!realcrush)
+					continue;
+
+				//If the thing was crushed by a crumbling FOF, reward the player who made it crumble!
+				for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 				{
-					if (immunepushable)
-					{
-						//FOF is blocked by pushable
-						nofit = true;
-						return false;
-					}
-					else
-					{
-						//If the thing was crushed by a crumbling FOF, reward the player who made it crumble!
-						thinker_t *think;
-						crumble_t *crumbler;
+					crumble_t *crumbler;
 
-						for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
-						{
-							if (think->function.acp1 != (actionf_p1)T_StartCrumble)
-								continue;
-
-							crumbler = (crumble_t *)think;
-
-							if (crumbler->player && crumbler->player->mo
-								&& crumbler->player->mo != thing
-								&& crumbler->actionsector == thing->subsector->sector
-								&& crumbler->sector == rover->master->frontsector)
-							{
-								killer = crumbler->player->mo;
-							}
-						}
-					}
+					if (think->function.acp1 != (actionf_p1)T_StartCrumble)
+						continue;
+
+					crumbler = (crumble_t *)think;
+
+					if (!crumbler->player)
+						continue;
+					if (!crumbler->player->mo)
+						continue;
+					if (crumbler->player->mo == thing)
+						continue;
+					if (crumbler->actionsector != thing->subsector->sector)
+						continue;
+					if (crumbler->sector != rover->master->frontsector)
+						continue;
+
+					killer = crumbler->player->mo;
 				}
 			}
 		}
@@ -4344,121 +4359,68 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 		}
 	}
 
-	if (realcrush && crushchange)
+	if (realcrush && crunch)
 		P_DamageMobj(thing, NULL, NULL, 1, 0);
 
 	// keep checking (crush other things)
 	return true;
 }
 
-//
-// P_CheckSector
-//
-boolean P_CheckSector(sector_t *sector, boolean crunch)
+static boolean P_CheckSectorPolyObjects(sector_t *sector, boolean realcrush, boolean crunch)
 {
-	msecnode_t *n;
 	size_t i;
 
-	nofit = false;
-	crushchange = crunch;
-
-	// killough 4/4/98: scan list front-to-back until empty or exhausted,
-	// restarting from beginning after each thing is processed. Avoids
-	// crashes, and is sure to examine all things in the sector, and only
-	// the things which are in the sector, until a steady-state is reached.
-	// Things can arbitrarily be inserted and removed and it won't mess up.
-	//
-	// killough 4/7/98: simplified to avoid using complicated counter
-
-
-	// First, let's see if anything will keep it from crushing.
-
 	// Sal: This stupid function chain is required to fix polyobjects not being able to crush.
 	// Monster Iestyn: don't use P_CheckSector actually just look for objects in the blockmap instead
 	validcount++;
 
 	for (i = 0; i < sector->linecount; i++)
 	{
-		if (sector->lines[i]->polyobj)
+		INT32 x, y;
+		polyobj_t *po = sector->lines[i]->polyobj;
+
+		if (!po)
+			continue;
+		if (po->validcount == validcount)
+			continue; // skip if already checked
+		if (!(po->flags & POF_SOLID))
+			continue;
+		if (po->lines[0]->backsector != sector) // Make sure you're currently checking the control sector
+			continue;
+
+		po->validcount = validcount;
+
+		for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
 		{
-			polyobj_t *po = sector->lines[i]->polyobj;
-			if (po->validcount == validcount)
-				continue; // skip if already checked
-			if (!(po->flags & POF_SOLID))
-				continue;
-			if (po->lines[0]->backsector == sector) // Make sure you're currently checking the control sector
+			for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
 			{
-				INT32 x, y;
-				po->validcount = validcount;
+				mobj_t *mo;
 
-				for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
-				{
-					for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
-					{
-						mobj_t *mo;
-
-						if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
-							continue;
+				if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+					continue;
 
-						mo = blocklinks[y * bmapwidth + x];
+				mo = blocklinks[y * bmapwidth + x];
 
-						for (; mo; mo = mo->bnext)
-						{
-							// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
+				for (; mo; mo = mo->bnext)
+				{
+					// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
 
-							if (!P_MobjInsidePolyobj(po, mo))
-								continue;
+					if (!P_MobjInsidePolyobj(po, mo))
+						continue;
 
-							if (!PIT_ChangeSector(mo, false))
-							{
-								nofit = true;
-								return nofit;
-							}
-						}
-					}
+					if (!PIT_ChangeSector(mo, realcrush, crunch) && !realcrush)
+						return false;
 				}
 			}
 		}
 	}
 
-	if (sector->numattached)
-	{
-		sector_t *sec;
-		for (i = 0; i < sector->numattached; i++)
-		{
-			sec = &sectors[sector->attached[i]];
-			for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				n->visited = false;
-
-			sec->moved = true;
-
-			P_RecalcPrecipInSector(sec);
-
-			if (!sector->attachedsolid[i])
-				continue;
-
-			do
-			{
-				for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				if (!n->visited)
-				{
-					n->visited = true;
-					if (!(n->m_thing->flags & MF_NOBLOCKMAP))
-					{
-						if (!PIT_ChangeSector(n->m_thing, false))
-						{
-							nofit = true;
-							return nofit;
-						}
-					}
-					break;
-				}
-			} while (n);
-		}
-	}
+	return true;
+}
 
-	// Mark all things invalid
-	sector->moved = true;
+static boolean P_CheckTouchingThinglist(sector_t *sector, boolean realcrush, boolean crunch)
+{
+	msecnode_t *n;
 
 	for (n = sector->touching_thinglist; n; n = n->m_thinglist_next)
 		n->visited = false;
@@ -4466,122 +4428,86 @@ boolean P_CheckSector(sector_t *sector, boolean crunch)
 	do
 	{
 		for (n = sector->touching_thinglist; n; n = n->m_thinglist_next) // go through list
-			if (!n->visited) // unprocessed thing found
-			{
-				n->visited = true; // mark thing as processed
-				if (!(n->m_thing->flags & MF_NOBLOCKMAP)) //jff 4/7/98 don't do these
-				{
-					if (!PIT_ChangeSector(n->m_thing, false)) // process it
-					{
-						nofit = true;
-						return nofit;
-					}
-				}
-				break; // exit and start over
-			}
-	} while (n); // repeat from scratch until all things left are marked valid
+		{
+			if (n->visited)
+				continue;
 
-	// Nothing blocked us, so lets crush for real!
+			n->visited = true; // mark thing as processed
 
-	// Sal: This stupid function chain is required to fix polyobjects not being able to crush.
-	// Monster Iestyn: don't use P_CheckSector actually just look for objects in the blockmap instead
-	validcount++;
-
-	for (i = 0; i < sector->linecount; i++)
-	{
-		if (sector->lines[i]->polyobj)
-		{
-			polyobj_t *po = sector->lines[i]->polyobj;
-			if (po->validcount == validcount)
-				continue; // skip if already checked
-			if (!(po->flags & POF_SOLID))
+			if (n->m_thing->flags & MF_NOBLOCKMAP) //jff 4/7/98 don't do these
 				continue;
-			if (po->lines[0]->backsector == sector) // Make sure you're currently checking the control sector
-			{
-				INT32 x, y;
-				po->validcount = validcount;
 
-				for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
-				{
-					for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
-					{
-						mobj_t *mo;
+			if (!PIT_ChangeSector(n->m_thing, realcrush, crunch) && !realcrush) // process it
+				return false;
 
-						if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
-							continue;
+			break; // exit and start over
+		}
+	} while (n); // repeat from scratch until all things left are marked valid
 
-						mo = blocklinks[y * bmapwidth + x];
+	return true;
+}
 
-						for (; mo; mo = mo->bnext)
-						{
-							// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
+static boolean P_CheckSectorFFloors(sector_t *sector, boolean realcrush, boolean crunch)
+{
+	sector_t *sec;
+	size_t i;
 
-							if (!P_MobjInsidePolyobj(po, mo))
-								continue;
+	if (!sector->numattached)
+		return true;
 
-							PIT_ChangeSector(mo, true);
-							return nofit;
-						}
-					}
-				}
-			}
-		}
-	}
-	if (sector->numattached)
+	for (i = 0; i < sector->numattached; i++)
 	{
-		sector_t *sec;
-		for (i = 0; i < sector->numattached; i++)
-		{
-			sec = &sectors[sector->attached[i]];
-			for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				n->visited = false;
+		sec = &sectors[sector->attached[i]];
 
-			sec->moved = true;
+		sec->moved = true;
 
-			P_RecalcPrecipInSector(sec);
+		P_RecalcPrecipInSector(sec);
 
-			if (!sector->attachedsolid[i])
-				continue;
+		if (!sector->attachedsolid[i])
+			continue;
 
-			do
-			{
-				for (n = sec->touching_thinglist; n; n = n->m_thinglist_next)
-				if (!n->visited)
-				{
-					n->visited = true;
-					if (!(n->m_thing->flags & MF_NOBLOCKMAP))
-					{
-						PIT_ChangeSector(n->m_thing, true);
-						return nofit;
-					}
-					break;
-				}
-			} while (n);
-		}
+		if (!P_CheckTouchingThinglist(sec, realcrush, crunch))
+			return false;
 	}
 
+	return true;
+}
+
+static boolean P_CheckSectorHelper(sector_t *sector, boolean realcrush, boolean crunch)
+{
+	if (!P_CheckSectorPolyObjects(sector, realcrush, crunch))
+		return false;
+
+	if (!P_CheckSectorFFloors(sector, realcrush, crunch))
+		return false;
+
 	// Mark all things invalid
 	sector->moved = true;
 
-	for (n = sector->touching_thinglist; n; n = n->m_thinglist_next)
-		n->visited = false;
+	return P_CheckTouchingThinglist(sector, realcrush, crunch);
+}
 
-	do
-	{
-		for (n = sector->touching_thinglist; n; n = n->m_thinglist_next) // go through list
-			if (!n->visited) // unprocessed thing found
-			{
-				n->visited = true; // mark thing as processed
-				if (!(n->m_thing->flags & MF_NOBLOCKMAP)) //jff 4/7/98 don't do these
-				{
-					PIT_ChangeSector(n->m_thing, true); // process it
-					return nofit;
-				}
-				break; // exit and start over
-			}
-	} while (n); // repeat from scratch until all things left are marked valid
+//
+// P_CheckSector
+//
+boolean P_CheckSector(sector_t *sector, boolean crunch)
+{
+	// killough 4/4/98: scan list front-to-back until empty or exhausted,
+	// restarting from beginning after each thing is processed. Avoids
+	// crashes, and is sure to examine all things in the sector, and only
+	// the things which are in the sector, until a steady-state is reached.
+	// Things can arbitrarily be inserted and removed and it won't mess up.
+	//
+	// killough 4/7/98: simplified to avoid using complicated counter
+
+	// First, let's see if anything will keep it from crushing.
+	if (!P_CheckSectorHelper(sector, false, crunch))
+		return true;
 
-	return nofit;
+	// Nothing blocked us, so lets crush for real!
+	P_CheckSectorHelper(sector, true, crunch);
+
+	return false;
 }
 
 /*
diff --git a/src/p_maputl.c b/src/p_maputl.c
index b6a3207308f3c5c7af8b434e50c2920d09b8f859..e36d5fd724fd152346b34b8ec047277e25735ecb 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -509,26 +509,26 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj)
 				// on non-solid polyobjects should NEVER happen in the future
 				if (linedef->polyobj && (linedef->polyobj->flags & POF_TESTHEIGHT)) {
 					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
-						texbottom = back->floorheight + side->rowoffset;
-						textop = back->ceilingheight + side->rowoffset;
+						texbottom = back->floorheight + side->rowoffset + side->offsety_mid;
+						textop = back->ceilingheight + side->rowoffset + side->offsety_mid;
 					} else if (linedef->flags & ML_MIDTEX) {
-						texbottom = back->floorheight + side->rowoffset;
+						texbottom = back->floorheight + side->rowoffset + side->offsety_mid;
 						textop = texbottom + texheight*(side->repeatcnt+1);
 					} else {
-						textop = back->ceilingheight + side->rowoffset;
+						textop = back->ceilingheight + side->rowoffset + side->offsety_mid;
 						texbottom = textop - texheight*(side->repeatcnt+1);
 					}
 				} else
 #endif
 				{
 					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
-						texbottom = openbottom + side->rowoffset;
-						textop = opentop + side->rowoffset;
+						texbottom = openbottom + side->rowoffset + side->offsety_mid;
+						textop = opentop + side->rowoffset + side->offsety_mid;
 					} else if (linedef->flags & ML_MIDPEG) {
-						texbottom = openbottom + side->rowoffset;
+						texbottom = openbottom + side->rowoffset + side->offsety_mid;
 						textop = texbottom + texheight*(side->repeatcnt+1);
 					} else {
-						textop = opentop + side->rowoffset;
+						textop = opentop + side->rowoffset + side->offsety_mid;
 						texbottom = textop - texheight*(side->repeatcnt+1);
 					}
 				}
diff --git a/src/p_mobj.c b/src/p_mobj.c
index eeaf547769fdbbae34a11cee3afac385addd690c..3eab29c09599f66cab4c58c97facaf2b2d89bb7f 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -3139,7 +3139,8 @@ boolean P_SceneryZMovement(mobj_t *mo)
 
 	if (P_CheckDeathPitCollide(mo))
 	{
-		P_RemoveMobj(mo);
+		if (mo->type != MT_GHOST)  // ghosts play death animations instead, so don't remove them
+			P_RemoveMobj(mo);
 		return false;
 	}
 
@@ -9716,6 +9717,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 			A_AttractChase(mobj);
 		break;
 	case MT_EMBLEM:
+		if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1))
+			mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
+		else
+			mobj->frame &= ~FF_TRANSMASK;
+
 		if (mobj->flags2 & MF2_NIGHTSPULL)
 			P_NightsItemChase(mobj);
 		break;
@@ -12005,8 +12011,8 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
 
 		break;
 	case MT_EMBLEM:
-		if (netgame || multiplayer)
-			return false; // Single player (You're next on my shit list)
+		if (!G_CoopGametype())
+			return false; // Gametype's not right
 		break;
 	default:
 		break;
@@ -12150,7 +12156,6 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
 	INT32 j;
 	emblem_t* emblem = M_GetLevelEmblems(gamemap);
 	skincolornum_t emcolor;
-	boolean validEmblem = true;
 
 	while (emblem)
 	{
@@ -12175,42 +12180,19 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
 	emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
 	mobj->color = (UINT16)emcolor;
 
-	validEmblem = !emblemlocations[j].collected;
+	mobj->frame &= ~FF_TRANSMASK;
 
-	if (emblemlocations[j].type == ET_SKIN)
+	if (emblemlocations[j].type == ET_GLOBAL)
 	{
-		INT32 skinnum = M_EmblemSkinNum(&emblemlocations[j]);
-
-		if (players[0].skin != skinnum)
+		mobj->reactiontime = emblemlocations[j].var;
+		if (emblemlocations[j].var & GE_NIGHTSITEM)
 		{
-			validEmblem = false;
+			mobj->flags |= MF_NIGHTSITEM;
+			mobj->flags &= ~MF_SPECIAL;
+			mobj->flags2 |= MF2_DONTDRAW;
 		}
 	}
 
-	if (validEmblem == false)
-	{
-		P_UnsetThingPosition(mobj);
-		mobj->flags |= MF_NOCLIP;
-		mobj->flags &= ~MF_SPECIAL;
-		mobj->flags |= MF_NOBLOCKMAP;
-		mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
-		P_SetThingPosition(mobj);
-	}
-	else
-	{
-		mobj->frame &= ~FF_TRANSMASK;
-
-		if (emblemlocations[j].type == ET_GLOBAL)
-		{
-			mobj->reactiontime = emblemlocations[j].var;
-			if (emblemlocations[j].var & GE_NIGHTSITEM)
-			{
-				mobj->flags |= MF_NIGHTSITEM;
-				mobj->flags &= ~MF_SPECIAL;
-				mobj->flags2 |= MF2_DONTDRAW;
-			}
-		}
-	}
 	return true;
 }
 
@@ -13706,7 +13688,6 @@ void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime)
 		UINT8 numitemtypes;
 		if (!udmf)
 			return;
-		CONS_Printf("Itemstring: %s\n", mthing->stringargs[0]);
 		P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes);
 		P_SpawnItemCircle(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, bonustime);
 		return;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 8c8a7832252a7531641051812a865c80308d2915..c18319c69816c7e9928795249963188c07929cbc 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -47,6 +47,7 @@ UINT8 *save_p;
 #define ARCHIVEBLOCK_POBJS    0x7F928546
 #define ARCHIVEBLOCK_THINKERS 0x7F37037C
 #define ARCHIVEBLOCK_SPECIALS 0x7F228378
+#define ARCHIVEBLOCK_EMBLEMS  0x7F4A5445
 
 // Note: This cannot be bigger
 // than an UINT16
@@ -4339,6 +4340,8 @@ static void P_NetArchiveMisc(boolean resending)
 
 	WRITEUINT32(save_p, hidetime);
 
+	WRITEUINT32(save_p, unlocktriggers);
+
 	// Is it paused?
 	if (paused)
 		WRITEUINT8(save_p, 0x2f);
@@ -4437,6 +4440,8 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
 
 	hidetime = READUINT32(save_p);
 
+	unlocktriggers = READUINT32(save_p);
+
 	// Is it paused?
 	if (READUINT8(save_p) == 0x2f)
 		paused = true;
@@ -4444,6 +4449,224 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
 	return true;
 }
 
+
+static inline void P_NetArchiveEmblems(void)
+{
+	gamedata_t *data = serverGamedata;
+	INT32 i, j;
+	UINT8 btemp;
+	INT32 curmare;
+
+	WRITEUINT32(save_p, ARCHIVEBLOCK_EMBLEMS);
+
+	// These should be synchronized before savegame loading by the wad files being the same anyway,
+	// but just in case, for now, we'll leave them here for testing. It would be very bad if they mismatch.
+	WRITEUINT8(save_p, (UINT8)savemoddata);
+	WRITEINT32(save_p, numemblems);
+	WRITEINT32(save_p, numextraemblems);
+
+	// The rest of this is lifted straight from G_SaveGameData in g_game.c
+	// TODO: Optimize this to only send information about emblems, unlocks, etc. which actually exist
+	//       There is no need to go all the way up to MAXEMBLEMS when wads are guaranteed to be the same.
+
+	WRITEUINT32(save_p, data->totalplaytime);
+
+	// TODO put another cipher on these things? meh, I don't care...
+	for (i = 0; i < NUMMAPS; i++)
+		WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX));
+
+	// To save space, use one bit per collected/achieved/unlocked flag
+	for (i = 0; i < MAXEMBLEMS;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
+			btemp |= (data->collected[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+	for (i = 0; i < MAXEXTRAEMBLEMS;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
+			btemp |= (data->extraCollected[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+	for (i = 0; i < MAXUNLOCKABLES;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
+			btemp |= (data->unlocked[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+	for (i = 0; i < MAXCONDITIONSETS;)
+	{
+		btemp = 0;
+		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
+			btemp |= (data->achieved[j+i] << j);
+		WRITEUINT8(save_p, btemp);
+		i += j;
+	}
+
+	WRITEUINT32(save_p, data->timesBeaten);
+	WRITEUINT32(save_p, data->timesBeatenWithEmeralds);
+	WRITEUINT32(save_p, data->timesBeatenUltimate);
+
+	// Main records
+	for (i = 0; i < NUMMAPS; i++)
+	{
+		if (data->mainrecords[i])
+		{
+			WRITEUINT32(save_p, data->mainrecords[i]->score);
+			WRITEUINT32(save_p, data->mainrecords[i]->time);
+			WRITEUINT16(save_p, data->mainrecords[i]->rings);
+		}
+		else
+		{
+			WRITEUINT32(save_p, 0);
+			WRITEUINT32(save_p, 0);
+			WRITEUINT16(save_p, 0);
+		}
+	}
+
+	// NiGHTS records
+	for (i = 0; i < NUMMAPS; i++)
+	{
+		if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares)
+		{
+			WRITEUINT8(save_p, 0);
+			continue;
+		}
+
+		WRITEUINT8(save_p, data->nightsrecords[i]->nummares);
+
+		for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare)
+		{
+			WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]);
+			WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]);
+			WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]);
+		}
+	}
+}
+
+static inline void P_NetUnArchiveEmblems(void)
+{
+	gamedata_t *data = serverGamedata;
+	INT32 i, j;
+	UINT8 rtemp;
+	UINT32 recscore;
+	tic_t rectime;
+	UINT16 recrings;
+	UINT8 recmares;
+	INT32 curmare;
+
+	if (READUINT32(save_p) != ARCHIVEBLOCK_EMBLEMS)
+		I_Error("Bad $$$.sav at archive block Emblems");
+
+	savemoddata = (boolean)READUINT8(save_p); // this one is actually necessary because savemoddata stays false otherwise for some reason.
+
+	if (numemblems != READINT32(save_p))
+		I_Error("numemblems mismatch");
+	if (numextraemblems != READINT32(save_p))
+		I_Error("numextraemblems mismatch");
+
+	// This shouldn't happen, but if something really fucked up happens and you transfer
+	// the SERVER player's gamedata over your own CLIENT gamedata,
+	// then this prevents it from being saved over yours.
+	data->loaded = false;
+
+	M_ClearSecrets(data);
+	G_ClearRecords(data);
+
+	// The rest of this is lifted straight from G_LoadGameData in g_game.c
+	// TODO: Optimize this to only read information about emblems, unlocks, etc. which actually exist
+	//       There is no need to go all the way up to MAXEMBLEMS when wads are guaranteed to be the same.
+
+	data->totalplaytime = READUINT32(save_p);
+
+	// TODO put another cipher on these things? meh, I don't care...
+	for (i = 0; i < NUMMAPS; i++)
+		if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX)
+			I_Error("Bad $$$.sav dearchiving Emblems");
+
+	// To save space, use one bit per collected/achieved/unlocked flag
+	for (i = 0; i < MAXEMBLEMS;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
+			data->collected[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+	for (i = 0; i < MAXEXTRAEMBLEMS;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
+			data->extraCollected[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+	for (i = 0; i < MAXUNLOCKABLES;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
+			data->unlocked[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+	for (i = 0; i < MAXCONDITIONSETS;)
+	{
+		rtemp = READUINT8(save_p);
+		for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
+			data->achieved[j+i] = ((rtemp >> j) & 1);
+		i += j;
+	}
+
+	data->timesBeaten = READUINT32(save_p);
+	data->timesBeatenWithEmeralds = READUINT32(save_p);
+	data->timesBeatenUltimate = READUINT32(save_p);
+
+	// Main records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		recscore = READUINT32(save_p);
+		rectime  = (tic_t)READUINT32(save_p);
+		recrings = READUINT16(save_p);
+
+		if (recrings > 10000 || recscore > MAXSCORE)
+			I_Error("Bad $$$.sav dearchiving Emblems");
+
+		if (recscore || rectime || recrings)
+		{
+			G_AllocMainRecordData((INT16)i, data);
+			data->mainrecords[i]->score = recscore;
+			data->mainrecords[i]->time = rectime;
+			data->mainrecords[i]->rings = recrings;
+		}
+	}
+
+	// Nights records
+	for (i = 0; i < NUMMAPS; ++i)
+	{
+		if ((recmares = READUINT8(save_p)) == 0)
+			continue;
+
+		G_AllocNightsRecordData((INT16)i, data);
+
+		for (curmare = 0; curmare < (recmares+1); ++curmare)
+		{
+			data->nightsrecords[i]->score[curmare] = READUINT32(save_p);
+			data->nightsrecords[i]->grade[curmare] = READUINT8(save_p);
+			data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p);
+
+			if (data->nightsrecords[i]->grade[curmare] > GRADE_S)
+			{
+				I_Error("Bad $$$.sav dearchiving Emblems");
+			}
+		}
+
+		data->nightsrecords[i]->nummares = recmares;
+	}
+}
+
 static inline void P_ArchiveLuabanksAndConsistency(void)
 {
 	UINT8 i, banksinuse = NUM_LUABANKS;
@@ -4507,6 +4730,7 @@ void P_SaveNetGame(boolean resending)
 
 	CV_SaveNetVars(&save_p);
 	P_NetArchiveMisc(resending);
+	P_NetArchiveEmblems();
 
 	// Assign the mobjnumber for pointer tracking
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -4559,6 +4783,7 @@ boolean P_LoadNetGame(boolean reloading)
 	CV_LoadNetVars(&save_p);
 	if (!P_NetUnArchiveMisc(reloading))
 		return false;
+	P_NetUnArchiveEmblems();
 	P_NetUnArchivePlayers();
 	if (gamestate == GS_LEVEL)
 	{
diff --git a/src/p_setup.c b/src/p_setup.c
index c8b0936b802928392f08c49ec9107bfde8d11c5c..a10326986e5e08cceba9f161d3ab70b0f4348d36 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -876,7 +876,7 @@ static void P_SpawnMapThings(boolean spawnemblems)
 	size_t i;
 	mapthing_t *mt;
 
-        // Spawn axis points first so they are at the front of the list for fast searching.
+	// Spawn axis points first so they are at the front of the list for fast searching.
 	for (i = 0, mt = mapthings; i < nummapthings; i++, mt++)
 	{
 		switch (mt->type)
@@ -1240,6 +1240,9 @@ static void P_LoadSidedefs(UINT8 *data)
 		}
 		sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
 
+		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0;
+		sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0;
+
 		P_SetSidedefSector(i, SHORT(msd->sector));
 
 		// Special info stored in texture fields!
@@ -1777,6 +1780,18 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char
 		sides[i].textureoffset = atol(val)<<FRACBITS;
 	else if (fastcmp(param, "offsety"))
 		sides[i].rowoffset = atol(val)<<FRACBITS;
+	else if (fastcmp(param, "offsetx_top"))
+		sides[i].offsetx_top = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsetx_mid"))
+		sides[i].offsetx_mid = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsetx_bottom"))
+		sides[i].offsetx_bot = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsety_top"))
+		sides[i].offsety_top = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsety_mid"))
+		sides[i].offsety_mid = atol(val) << FRACBITS;
+	else if (fastcmp(param, "offsety_bottom"))
+		sides[i].offsety_bot = atol(val) << FRACBITS;
 	else if (fastcmp(param, "texturetop"))
 		sides[i].toptexture = R_TextureNumForName(val);
 	else if (fastcmp(param, "texturebottom"))
@@ -2461,6 +2476,18 @@ static void P_WriteTextmap(void)
 			fprintf(f, "offsetx = %d;\n", wsides[i].textureoffset >> FRACBITS);
 		if (wsides[i].rowoffset != 0)
 			fprintf(f, "offsety = %d;\n", wsides[i].rowoffset >> FRACBITS);
+		if (wsides[i].offsetx_top != 0)
+			fprintf(f, "offsetx_top = %d;\n", wsides[i].offsetx_top >> FRACBITS);
+		if (wsides[i].offsety_top != 0)
+			fprintf(f, "offsety_top = %d;\n", wsides[i].offsety_top >> FRACBITS);
+		if (wsides[i].offsetx_mid != 0)
+			fprintf(f, "offsetx_mid = %d;\n", wsides[i].offsetx_mid >> FRACBITS);
+		if (wsides[i].offsety_mid != 0)
+			fprintf(f, "offsety_mid = %d;\n", wsides[i].offsety_mid >> FRACBITS);
+		if (wsides[i].offsetx_bot != 0)
+			fprintf(f, "offsetx_bottom = %d;\n", wsides[i].offsetx_bot >> FRACBITS);
+		if (wsides[i].offsety_bot != 0)
+			fprintf(f, "offsety_bottom = %d;\n", wsides[i].offsety_bot >> FRACBITS);
 		if (wsides[i].toptexture > 0 && wsides[i].toptexture < numtextures)
 			fprintf(f, "texturetop = \"%.*s\";\n", 8, textures[wsides[i].toptexture]->name);
 		if (wsides[i].bottomtexture > 0 && wsides[i].bottomtexture < numtextures)
@@ -2828,6 +2855,8 @@ static void P_LoadTextmap(void)
 		// Defaults.
 		sd->textureoffset = 0;
 		sd->rowoffset = 0;
+		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0;
+		sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0;
 		sd->toptexture = R_TextureNumForName("-");
 		sd->midtexture = R_TextureNumForName("-");
 		sd->bottomtexture = R_TextureNumForName("-");
@@ -7460,7 +7489,7 @@ static void P_WriteLetter(void)
 {
 	char *buf, *b;
 
-	if (!unlockables[28].unlocked) // pandora's box
+	if (!serverGamedata->unlocked[28]) // pandora's box
 		return;
 
 	if (modeattacking)
@@ -7804,10 +7833,11 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	nextmapoverride = 0;
 	skipstats = 0;
 
-	if (!(netgame || multiplayer || demoplayback))
-		mapvisited[gamemap-1] |= MV_VISITED;
-	else if (netgame || multiplayer)
-		mapvisited[gamemap-1] |= MV_MP; // you want to record that you've been there this session, but not permanently
+	if (!demoplayback)
+	{
+		clientGamedata->mapvisited[gamemap-1] |= MV_VISITED;
+		serverGamedata->mapvisited[gamemap-1] |= MV_VISITED;
+	}
 
 	levelloading = false;
 
diff --git a/src/p_spec.c b/src/p_spec.c
index 8489a227f32be33817bf6f9f3de8ea85f103dd87..877b9c7bc3bbf30034ea3ba87ad48169154e9409 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1795,9 +1795,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			{ // Unlockable triggers required
 				INT32 trigid = triggerline->args[1];
 
-				if (netgame || multiplayer)
-					return false;
-				else if (trigid < 0 || trigid > 31) // limited by 32 bit variable
+				if (trigid < 0 || trigid > 31) // limited by 32 bit variable
 				{
 					CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
 					return false;
@@ -1810,14 +1808,12 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 			{ // An unlockable itself must be unlocked!
 				INT32 unlockid = triggerline->args[1];
 
-				if (netgame || multiplayer)
-					return false;
-				else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
+				if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
 				{
 					CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
 					return false;
 				}
-				else if (!(unlockables[unlockid-1].unlocked))
+				else if (!(serverGamedata->unlocked[unlockid-1]))
 					return false;
 			}
 			break;
@@ -2942,7 +2938,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 441: // Trigger unlockable
-			if (!(netgame || multiplayer))
 			{
 				INT32 trigid = line->args[0];
 
@@ -2953,10 +2948,12 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					unlocktriggers |= 1 << trigid;
 
 					// Unlocked something?
-					if (M_UpdateUnlockablesAndExtraEmblems())
+					M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+
+					if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
 					{
 						S_StartSound(NULL, sfx_s3k68);
-						G_SaveGameData(); // only save if unlocked something
+						G_SaveGameData(clientGamedata); // only save if unlocked something
 					}
 				}
 			}
diff --git a/src/p_tick.c b/src/p_tick.c
index 0357258e812a57d05668f8221371098a0fa52373..b1fd367ed94721e5aedab16b3a3e743a6df63425 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -675,7 +675,10 @@ void P_Ticker(boolean run)
 
 	// Keep track of how long they've been playing!
 	if (!demoplayback) // Don't increment if a demo is playing.
-		totalplaytime++;
+	{
+		clientGamedata->totalplaytime++;
+		serverGamedata->totalplaytime++;
+	}
 
 	if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap))
 		P_DoSpecialStageStuff();
diff --git a/src/r_defs.h b/src/r_defs.h
index 6d2b7d3d8ab0f1e2db9932e26c6e1ee188f218bb..963d655b177270a79702c25ea16f5622b4f5d869 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -559,6 +559,10 @@ typedef struct
 	// add this to the calculated texture top
 	fixed_t rowoffset;
 
+	// per-texture offsets for UDMF
+	fixed_t offsetx_top, offsetx_mid, offsetx_bot;
+	fixed_t offsety_top, offsety_mid, offsety_bot;
+
 	// Texture indices.
 	// We do not maintain names here.
 	INT32 toptexture, bottomtexture, midtexture;
diff --git a/src/r_main.c b/src/r_main.c
index ebf7a28bf10b286a2453eb94dbdb52891eef4355..55bb9c4ffefdfee11f38ec50a28cff7de2748ffb 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1092,34 +1092,12 @@ subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y)
 void R_SetupFrame(player_t *player)
 {
 	camera_t *thiscam;
-	boolean chasecam = false;
-
-	if (splitscreen && player == &players[secondarydisplayplayer]
-		&& player != &players[consoleplayer])
-	{
+	boolean chasecam = R_ViewpointHasChasecam(player);
+	
+	if (splitscreen && player == &players[secondarydisplayplayer] && player != &players[consoleplayer])
 		thiscam = &camera2;
-		chasecam = (cv_chasecam2.value != 0);
-		R_SetViewContext(VIEWCONTEXT_PLAYER2);
-	}
 	else
-	{
 		thiscam = &camera;
-		chasecam = (cv_chasecam.value != 0);
-		R_SetViewContext(VIEWCONTEXT_PLAYER1);
-	}
-
-	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode)
-		chasecam = true; // force chasecam on
-	else if (player->spectator) // no spectator chasecam
-		chasecam = false; // force chasecam off
-
-	if (chasecam && !thiscam->chase)
-	{
-		P_ResetCamera(player, thiscam);
-		thiscam->chase = true;
-	}
-	else if (!chasecam)
-		thiscam->chase = false;
 
 	newview->sky = false;
 
@@ -1348,11 +1326,37 @@ boolean R_ViewpointHasChasecam(player_t *player)
 {
 	camera_t *thiscam;
 	boolean chasecam = false;
+	boolean isplayer2 = (splitscreen && player == &players[secondarydisplayplayer] && player != &players[consoleplayer]);
 
-	if (splitscreen && player == &players[secondarydisplayplayer] && player != &players[consoleplayer])
+	if (isplayer2)
 	{
 		thiscam = &camera2;
 		chasecam = (cv_chasecam2.value != 0);
+	}
+	else
+	{
+		thiscam = &camera;
+		chasecam = (cv_chasecam.value != 0);
+	}
+
+	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode)
+		chasecam = true; // force chasecam on
+	else if (player->spectator) // no spectator chasecam
+		chasecam = false; // force chasecam off
+		
+	if (chasecam && !thiscam->chase)
+	{
+		P_ResetCamera(player, thiscam);
+		thiscam->chase = true;
+	}
+	else if (!chasecam && thiscam->chase)
+	{
+		P_ResetCamera(player, thiscam);
+		thiscam->chase = false;
+	}
+	
+	if (isplayer2)
+	{
 		R_SetViewContext(VIEWCONTEXT_PLAYER2);
 		if (thiscam->reset)
 		{
@@ -1362,8 +1366,6 @@ boolean R_ViewpointHasChasecam(player_t *player)
 	}
 	else
 	{
-		thiscam = &camera;
-		chasecam = (cv_chasecam.value != 0);
 		R_SetViewContext(VIEWCONTEXT_PLAYER1);
 		if (thiscam->reset)
 		{
@@ -1372,11 +1374,6 @@ boolean R_ViewpointHasChasecam(player_t *player)
 		}
 	}
 
-	if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode)
-		chasecam = true; // force chasecam on
-	else if (player->spectator) // no spectator chasecam
-		chasecam = false; // force chasecam off
-
 	return chasecam;
 }
 
diff --git a/src/r_segs.c b/src/r_segs.c
index 71fc9f9b2a9801643e646e603ca2f48721e76c28..5acca9b170bd102a9a107cd326a242aa6ed70fcf 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -49,6 +49,7 @@ fixed_t rw_distance;
 static INT32 rw_x, rw_stopx;
 static angle_t rw_centerangle;
 static fixed_t rw_offset;
+static fixed_t rw_offset_top, rw_offset_mid, rw_offset_bot;
 static fixed_t rw_offset2; // for splats
 static fixed_t rw_scale, rw_scalestep;
 static fixed_t rw_midtexturemid, rw_toptexturemid, rw_bottomtexturemid;
@@ -778,7 +779,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 
 	if (newline)
 	{
-		offsetvalue = sides[newline->sidenum[0]].rowoffset;
+		offsetvalue = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid;
 		if (newline->flags & ML_DONTPEGBOTTOM)
 		{
 			skewslope = *pfloor->b_slope; // skew using bottom slope
@@ -790,7 +791,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	}
 	else
 	{
-		offsetvalue = sides[pfloor->master->sidenum[0]].rowoffset;
+		offsetvalue = sides[pfloor->master->sidenum[0]].rowoffset + sides[pfloor->master->sidenum[0]].offsety_mid;
 		if (curline->linedef->flags & ML_DONTPEGBOTTOM)
 		{
 			skewslope = *pfloor->b_slope; // skew using bottom slope
@@ -1335,7 +1336,7 @@ static void R_RenderSegLoop (void)
 				dc_yl = yl;
 				dc_yh = yh;
 				dc_texturemid = rw_midtexturemid;
-				dc_source = R_GetColumn(midtexture,texturecolumn);
+				dc_source = R_GetColumn(midtexture,texturecolumn + (rw_offset_mid>>FRACBITS));
 				dc_texheight = textureheight[midtexture]>>FRACBITS;
 
 				//profile stuff ---------------------------------------------------------
@@ -1396,7 +1397,7 @@ static void R_RenderSegLoop (void)
 						dc_yl = yl;
 						dc_yh = mid;
 						dc_texturemid = rw_toptexturemid;
-						dc_source = R_GetColumn(toptexture,texturecolumn);
+						dc_source = R_GetColumn(toptexture,texturecolumn + (rw_offset_top>>FRACBITS));
 						dc_texheight = textureheight[toptexture]>>FRACBITS;
 						colfunc();
 						ceilingclip[rw_x] = (INT16)mid;
@@ -1433,7 +1434,7 @@ static void R_RenderSegLoop (void)
 						dc_yh = yh;
 						dc_texturemid = rw_bottomtexturemid;
 						dc_source = R_GetColumn(bottomtexture,
-							texturecolumn);
+							texturecolumn + (rw_offset_bot>>FRACBITS));
 						dc_texheight = textureheight[bottomtexture]>>FRACBITS;
 						colfunc();
 						floorclip[rw_x] = (INT16)mid;
@@ -1452,7 +1453,7 @@ static void R_RenderSegLoop (void)
 		{
 			// save texturecol
 			//  for backdrawing of masked mid texture
-			maskedtexturecol[rw_x] = (INT16)texturecolumn;
+			maskedtexturecol[rw_x] = (INT16)(texturecolumn + (rw_offset_mid>>FRACBITS));
 
 			if (maskedtextureheight != NULL) {
 				maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ?
@@ -1783,7 +1784,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			rw_midtexturemid = worldtop;
 			rw_midtextureslide = ceilingfrontslide;
 		}
-		rw_midtexturemid += sidedef->rowoffset;
+		rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid;
 
 		ds_p->silhouette = SIL_BOTH;
 		ds_p->sprtopclip = screenheightarray;
@@ -2022,8 +2023,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			}
 		}
 
-		rw_toptexturemid += sidedef->rowoffset;
-		rw_bottomtexturemid += sidedef->rowoffset;
+		rw_toptexturemid += sidedef->rowoffset + sidedef->offsety_top;
+		rw_bottomtexturemid += sidedef->rowoffset + sidedef->offsety_bot;
 
 		// allocate space for masked texture tables
 		if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors))
@@ -2266,8 +2267,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 					rw_midtexturebackslide = ceilingbackslide;
 				}
 			}
-			rw_midtexturemid += sidedef->rowoffset;
-			rw_midtextureback += sidedef->rowoffset;
+			rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid;
+			rw_midtextureback += sidedef->rowoffset + sidedef->offsety_mid;
 
 			maskedtexture = true;
 		}
@@ -2305,6 +2306,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		/// don't use texture offset for splats
 		rw_offset2 = rw_offset + curline->offset;
 		rw_offset += sidedef->textureoffset + curline->offset;
+		rw_offset_top = sidedef->offsetx_top;
+		rw_offset_mid = sidedef->offsetx_mid;
+		rw_offset_bot = sidedef->offsetx_bot;
 		rw_centerangle = ANGLE_90 + viewangle - rw_normalangle;
 
 		// calculate light table
diff --git a/src/r_skins.c b/src/r_skins.c
index e59e085b8c1c0681a175ec537daf16d42d8a87fb..2c031ee851d129b4d2d2254c517e9c8e90924661 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -194,7 +194,7 @@ UINT32 R_GetSkinAvailabilities(void)
 			return 0;
 		}
 
-		if (unlockables[i].unlocked)
+		if (clientGamedata->unlocked[i])
 		{
 			response |= (1 << unlockShift);
 		}
@@ -242,11 +242,12 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 		// Force 3.
 		return true;
 	}
+
 	if (playernum != -1 && players[playernum].bot)
-    {
-        //Force 4.
-        return true;
-    }
+	{
+		// Force 4.
+		return true;
+	}
 
 	// We will now check if this skin is supposed to be locked or not.
 
@@ -284,7 +285,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 	else
 	{
 		// We want to check our global unlockables.
-		return (unlockables[unlockID].unlocked);
+		return (clientGamedata->unlocked[unlockID]);
 	}
 }
 
diff --git a/src/r_things.c b/src/r_things.c
index 916a7ee4ba85f8cf35fe07237cdba3c526045da4..89b9fe07ef89ddd0307317ca0933608b7cbdf7ff 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1324,6 +1324,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	if (trans >= 9) return;
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
+	if ((thing->scale != thing->old_scale) && (thing->scale >= FRACUNIT/1024)) // Interpolate shadows when scaling mobjs
+		scalemul = FixedMul(scalemul, FixedDiv(interp.scale, thing->scale));
 
 	patch = W_CachePatchName("DSHADOW", PU_SPRITE);
 	xscale = FixedDiv(projection, tz);
diff --git a/src/s_sound.c b/src/s_sound.c
index 111b6ce256661d9f0adac594dd03f4d63573afd7..ada1a0fd2f399f09849a7330cf14ca283c7b1784 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1692,6 +1692,7 @@ UINT8 soundtestpage = 1;
 //
 boolean S_PrepareSoundTest(void)
 {
+	gamedata_t *data = clientGamedata;
 	musicdef_t *def;
 	INT32 pos = numsoundtestdefs = 0;
 
@@ -1717,9 +1718,9 @@ boolean S_PrepareSoundTest(void)
 		if (!(def->soundtestpage & soundtestpage))
 			continue;
 		soundtestdefs[pos++] = def;
-		if (def->soundtestcond > 0 && !(mapvisited[def->soundtestcond-1] & MV_BEATEN))
+		if (def->soundtestcond > 0 && !(data->mapvisited[def->soundtestcond-1] & MV_BEATEN))
 			continue;
-		if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond))
+		if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond, data))
 			continue;
 		def->allowed = true;
 	}
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 67ee8d6684268943c9f2d7e07aab332b5d63189c..66eeffa30b7717597c1732ab9355a8cb76317362 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2352,7 +2352,7 @@ void I_Quit(void)
 #ifndef NONET
 	D_SaveBan(); // save the ban list
 #endif
-	G_SaveGameData(); // Tails 12-08-2002
+	G_SaveGameData(clientGamedata); // Tails 12-08-2002
 	//added:16-02-98: when recording a demo, should exit using 'q' key,
 	//        but sometimes we forget and use 'F10'.. so save here too.
 
@@ -2436,7 +2436,7 @@ void I_Error(const char *error, ...)
 		if (errorcount == 8)
 		{
 			M_SaveConfig(NULL);
-			G_SaveGameData();
+			G_SaveGameData(clientGamedata);
 		}
 		if (errorcount > 20)
 		{
@@ -2469,7 +2469,7 @@ void I_Error(const char *error, ...)
 #ifndef NONET
 	D_SaveBan(); // save the ban list
 #endif
-	G_SaveGameData(); // Tails 12-08-2002
+	G_SaveGameData(clientGamedata); // Tails 12-08-2002
 
 	// Shutdown. Here might be other errors.
 	if (demorecording)
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 206c93273329238b5df39523fccf7cd4d2628752..dcab2bb9c585e48b3be8d74323463ebeb6d44827 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -108,6 +108,9 @@ static patch_t *sneakers;
 static patch_t *gravboots;
 static patch_t *nonicon;
 static patch_t *nonicon2;
+static patch_t *nightopianhelper;
+static patch_t *linkfreeze;
+static patch_t *superparaloop;
 static patch_t *bluestat;
 static patch_t *byelstat;
 static patch_t *orngstat;
@@ -313,6 +316,10 @@ void ST_LoadGraphics(void)
 	nonicon2 = W_CachePatchName("NONICON2", PU_HUDGFX);
 
 	// NiGHTS HUD things
+	nightopianhelper = W_CachePatchName("NHLPICON", PU_HUDGFX);
+	linkfreeze = W_CachePatchName("NLFZICON", PU_HUDGFX);
+	superparaloop = W_CachePatchName("NSPRICON", PU_HUDGFX);
+
 	bluestat = W_CachePatchName("BLUESTAT", PU_HUDGFX);
 	byelstat = W_CachePatchName("BYELSTAT", PU_HUDGFX);
 	orngstat = W_CachePatchName("ORNGSTAT", PU_HUDGFX);
@@ -1448,6 +1455,21 @@ void ST_drawWipeTitleCard(void)
 	}
 }
 
+#define ICONSEP (16+4) // matches weapon rings HUD
+
+static INT32 ST_powerupHUDoffset(UINT16 timer)
+{
+	if (timer > 7)
+		return ICONSEP;
+	else
+	{
+		UINT8 a = ICONSEP, b = 7-timer;
+		while (b--)
+			a = 2*a/3;
+		return a;
+	}
+}
+
 static void ST_drawPowerupHUD(void)
 {
 	patch_t *p = NULL;
@@ -1455,7 +1477,6 @@ static void ST_drawPowerupHUD(void)
 	INT32 offs = hudinfo[HUD_POWERUPS].x;
 	const UINT8 q = ((splitscreen && stplyr == &players[secondarydisplayplayer]) ? 1 : 0);
 	static INT32 flagoffs[2] = {0, 0}, shieldoffs[2] = {0, 0}, finishoffs[2] = {0, 0};
-#define ICONSEP (16+4) // matches weapon rings HUD
 
 	if (F_GetPromptHideHud(hudinfo[HUD_POWERUPS].y))
 		return;
@@ -1567,15 +1588,7 @@ static void ST_drawPowerupHUD(void)
 		DRAWTIMERICON(invincibility, invulntime)
 	}
 
-	if (invulntime > 7)
-		offs -= ICONSEP;
-	else
-	{
-		UINT8 a = ICONSEP, b = 7-invulntime;
-		while (b--)
-			a = 2*a/3;
-		offs -= a;
-	}
+	offs -= ST_powerupHUDoffset(invulntime);
 
 	// Super Sneakers
 	if (stplyr->powers[pw_sneakers] > 3*TICRATE || (stplyr->powers[pw_sneakers] && leveltime & 1))
@@ -1583,15 +1596,7 @@ static void ST_drawPowerupHUD(void)
 		DRAWTIMERICON(sneakers, stplyr->powers[pw_sneakers])
 	}
 
-	if (stplyr->powers[pw_sneakers] > 7)
-		offs -= ICONSEP;
-	else
-	{
-		UINT8 a = ICONSEP, b = 7-stplyr->powers[pw_sneakers];
-		while (b--)
-			a = 2*a/3;
-		offs -= a;
-	}
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_sneakers]);
 
 	// Gravity Boots
 	if (stplyr->powers[pw_gravityboots] > 3*TICRATE || (stplyr->powers[pw_gravityboots] && leveltime & 1))
@@ -1599,6 +1604,36 @@ static void ST_drawPowerupHUD(void)
 		DRAWTIMERICON(gravboots, stplyr->powers[pw_gravityboots])
 	}
 
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_gravityboots]);
+
+// --------------------
+// NiGHTS timer-based powerups
+// --------------------
+
+	// Nightopian Helper
+	if (stplyr->powers[pw_nights_helper] > 3*TICRATE || (stplyr->powers[pw_nights_helper] && leveltime & 1))
+	{
+		DRAWTIMERICON(nightopianhelper, stplyr->powers[pw_nights_helper])
+	}
+
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_nights_helper]);
+
+	// Link Freeze
+	if (stplyr->powers[pw_nights_linkfreeze] > 3*TICRATE || (stplyr->powers[pw_nights_linkfreeze] && leveltime & 1))
+	{
+		DRAWTIMERICON(linkfreeze, stplyr->powers[pw_nights_linkfreeze])
+	}
+
+	offs -= ST_powerupHUDoffset(stplyr->powers[pw_nights_linkfreeze]);
+
+	// Super Paraloop
+	if (stplyr->powers[pw_nights_superloop] > 3*TICRATE || (stplyr->powers[pw_nights_superloop] && leveltime & 1))
+	{
+		DRAWTIMERICON(superparaloop, stplyr->powers[pw_nights_superloop])
+	}
+
+	//offs -= ST_powerupHUDoffset(stplyr->powers[pw_nights_superloop]);
+
 #undef DRAWTIMERICON
 #undef ICONSEP
 }
@@ -1697,7 +1732,7 @@ static void ST_drawNightsRecords(void)
 			ST_DrawNightsOverlayNum((BASEVIDWIDTH/2 + 56)<<FRACBITS, 160<<FRACBITS, FRACUNIT, aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_AZURE);
 
 			// If new record, say so!
-			if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1) <= stplyr->lastmarescore)
+			if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1, clientGamedata) <= stplyr->lastmarescore)
 			{
 				if (stplyr->texttimer & 16)
 					V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_YELLOWMAP|aflag, "* NEW RECORD *");
@@ -2545,7 +2580,7 @@ static void ST_doHuntIconsAndSound(void)
 		S_StartSound(NULL, sfx_emfind);
 }
 
-static void ST_doItemFinderIconsAndSound(void)
+static boolean ST_doItemFinderIconsAndSound(void)
 {
 	INT32 emblems[16];
 	thinker_t *th;
@@ -2556,6 +2591,12 @@ static void ST_doItemFinderIconsAndSound(void)
 	INT32 interval = 0, newinterval = 0;
 	INT32 soffset;
 
+	if (!(cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata)))
+	{
+		// Not unlocked, or not enabled. Use emerald hunt radar.
+		return false;
+	}
+
 	for (i = 0; i < numemblems; ++i)
 	{
 		if (emblemlocations[i].type > ET_SKIN || emblemlocations[i].level != gamemap)
@@ -2563,15 +2604,21 @@ static void ST_doItemFinderIconsAndSound(void)
 
 		emblems[stemblems++] = i;
 
-		if (!emblemlocations[i].collected)
+		if (!P_EmblemWasCollected(i) && P_CanPickupEmblem(stplyr, i))
+		{
 			++stunfound;
+		}
 
 		if (stemblems >= 16)
 			break;
 	}
+
 	// Found all/none exist? Don't waste our time
 	if (!stunfound)
-		return;
+	{
+		// Allow emerald hunt radar to function after they're all collected.
+		return false;
+	}
 
 	// Scan thinkers to find emblem mobj with these ids
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -2591,6 +2638,9 @@ static void ST_doItemFinderIconsAndSound(void)
 		{
 			if (mo2->health == emblems[i] + 1)
 			{
+				if (P_EmblemWasCollected(emblems[i]) || !P_CanPickupEmblem(stplyr, emblems[i]))
+					break;
+
 				soffset = (i * 20) - ((stemblems - 1) * 10);
 
 				newinterval = ST_drawEmeraldHuntIcon(mo2, itemhoming, soffset);
@@ -2605,6 +2655,8 @@ static void ST_doItemFinderIconsAndSound(void)
 
 	if (!(P_AutoPause() || paused) && interval > 0 && leveltime && leveltime % interval == 0 && renderisnewtic)
 		S_StartSound(NULL, sfx_emfind);
+
+	return true;
 }
 
 //
@@ -2723,9 +2775,7 @@ static void ST_overlayDrawer(void)
 			ST_drawRaceHUD();
 
 		// Emerald Hunt Indicators
-		if (cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER))
-			ST_doItemFinderIconsAndSound();
-		else
+		if (!ST_doItemFinderIconsAndSound())
 			ST_doHuntIconsAndSound();
 
 		if(!P_IsLocalPlayer(stplyr))
@@ -2740,18 +2790,16 @@ static void ST_overlayDrawer(void)
 		}
 
 		// This is where we draw all the fun cheese if you have the chasecam off!
-		if (!(maptol & TOL_NIGHTS))
+		if ((stplyr == &players[displayplayer] && !camera.chase)
+		|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
 		{
-			if ((stplyr == &players[displayplayer] && !camera.chase)
-			|| ((splitscreen && stplyr == &players[secondarydisplayplayer]) && !camera2.chase))
-			{
-				ST_drawFirstPersonHUD();
-				if (cv_powerupdisplay.value)
-					ST_drawPowerupHUD();  // same as it ever was...
-			}
-			else if (cv_powerupdisplay.value == 2)
+			ST_drawFirstPersonHUD();
+			if (cv_powerupdisplay.value)
 				ST_drawPowerupHUD();  // same as it ever was...
 		}
+		else if (cv_powerupdisplay.value == 2)
+			ST_drawPowerupHUD();  // same as it ever was...
+		
 	}
 	else if (!(netgame || multiplayer) && cv_powerupdisplay.value == 2)
 		ST_drawPowerupHUD(); // same as it ever was...
diff --git a/src/y_inter.c b/src/y_inter.c
index 02d01233e1136f7e332180de034484ffb73fb5d8..6e7d362a779d9f8bcfb43d7999aacaf433cb2ee1 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -1092,12 +1092,14 @@ void Y_Ticker(void)
 			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
-			if (!(netgame || multiplayer) && !demoplayback)
+			if (!demoplayback)
 			{
-				if (M_UpdateUnlockablesAndExtraEmblems())
+				M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+
+				if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
 					S_StartSound(NULL, sfx_s3k68);
 
-				G_SaveGameData();
+				G_SaveGameData(clientGamedata);
 			}
 		}
 		else if (!(intertic & 1))
@@ -1228,12 +1230,14 @@ void Y_Ticker(void)
 			S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching!
 
 			// Update when done with tally
-			if (!(netgame || multiplayer) && !demoplayback)
+			if (!demoplayback)
 			{
-				if (M_UpdateUnlockablesAndExtraEmblems())
+				M_SilentUpdateUnlockablesAndEmblems(serverGamedata);
+
+				if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata))
 					S_StartSound(NULL, sfx_s3k68);
 
-				G_SaveGameData();
+				G_SaveGameData(clientGamedata);
 			}
 		}
 		else if (!(intertic & 1))