diff --git a/.github/workflows/continuous_integration_other.yml b/.github/workflows/continuous_integration_other.yml
index f703042ec1716c68de9da3280a5451e48b777270..5aa3460e72b3474c5dd32f8ad1bf601c73766bd4 100644
--- a/.github/workflows/continuous_integration_other.yml
+++ b/.github/workflows/continuous_integration_other.yml
@@ -57,11 +57,6 @@ jobs:
           fi
         done
 
-    - name: Prepare Package
-      run: |
-        # Delete unwanted files
-        rm -r Build/Setup
-
     - name: Upload Package
       uses: actions/upload-artifact@v1
       with:
diff --git a/Build/Compilers/BCC/bcc.cfg b/Build/Compilers/BCC/bcc.cfg
index 41f747bc4ee3326f497049e45e53ee35fa839837..ccac468282602e711303ea9904492331f5e0a994 100755
--- a/Build/Compilers/BCC/bcc.cfg
+++ b/Build/Compilers/BCC/bcc.cfg
@@ -9,7 +9,7 @@ compilers
 	{
 		interface = "AccCompiler";
 		program = "bcc.exe";
-		zcommon = "zcommon.acs";
+		zcommon = "zcommon.bcs";
 		std = "std.acs";
 	}
 }
diff --git a/Build/Compilers/BCC/zcommon.acs b/Build/Compilers/BCC/zcommon.bcs
old mode 100755
new mode 100644
similarity index 100%
rename from Build/Compilers/BCC/zcommon.acs
rename to Build/Compilers/BCC/zcommon.bcs
diff --git a/Build/Compilers/ZDoom/acc.exe b/Build/Compilers/ZDoom/acc.exe
index 7534f775fe61e685fee10c497f8b584070dea0e2..9ae2387dcbde7543b14294582bdd8b3ab8ebde95 100755
Binary files a/Build/Compilers/ZDoom/acc.exe and b/Build/Compilers/ZDoom/acc.exe differ
diff --git a/Build/Compilers/ZDoom/zdefs.acs b/Build/Compilers/ZDoom/zdefs.acs
index 3f24b6145c4354bd1b821ce4e80c1bd874515809..87bcf5aa5efd1f417195d77038aa1718f111e743 100755
--- a/Build/Compilers/ZDoom/zdefs.acs
+++ b/Build/Compilers/ZDoom/zdefs.acs
@@ -160,6 +160,7 @@
 #define BT_MOVEUP				262144
 #define BT_MOVEDOWN				524288
 #define BT_SHOWSCORES			1048576
+#define BT_RUN					33554432
 
 // Do whatever you want with these.
 #define BT_USER1				2097152
@@ -298,6 +299,8 @@
 #define APROP_MaxStepHeight 44
 #define APROP_MaxDropOffHeight 45
 #define APROP_DamageType	46
+#define APROP_SoundClass	47
+#define APROP_FriendlySeeBlocks 48
 
 // New to Eternity
 #define APROP_Counter0      100
@@ -746,6 +749,8 @@
 
 #define SDF_ABSANGLE			1
 #define SDF_PERMANENT			2
+#define SDF_FIXED_ZOFF			4
+#define SDF_FIXED_DISTANCE		8
 
 // Actor pointer selectors
 
@@ -997,6 +1002,7 @@
 #define BLOCKF_SIGHT 256
 #define BLOCKF_HITSCAN 512
 #define BLOCKF_SOUND 1024
+#define BLOCKF_LANDMONSTERS 2048
 
 #define FOGP_DENSITY 0
 #define FOGP_OUTSIDEDENSITY 1
@@ -1091,6 +1097,10 @@
 #define QF_MAX			1 << 3
 #define QF_FULLINTENSITY	1 << 4
 #define QF_WAVE			1 << 5
+#define QF_3D			1 << 6
+#define QF_GROUNDONLY		1 << 7
+#define QF_AFFECTACTORS 	1 << 8
+#define QF_SHAKEONLY		1 << 9
 
 #define WARPF_ABSOLUTEOFFSET 0x1
 #define WARPF_ABSOLUTEANGLE 0x2
diff --git a/Build/Compilers/ZDoom/zspecial.acs b/Build/Compilers/ZDoom/zspecial.acs
index a1182afeb3bbd342a77195913b283ba1ec84a0d7..fc0e781465387d2cbb32c53c3e6447b89fdcda1d 100755
--- a/Build/Compilers/ZDoom/zspecial.acs
+++ b/Build/Compilers/ZDoom/zspecial.acs
@@ -277,9 +277,10 @@ special
 	280:Ceiling_MoveToValueAndCrush(4, 5),
 	281:Line_SetAutomapFlags(3),
 	282:Line_SetAutomapStyle(2),
-	
-	// new to Eternity
-//	300:Portal_Define(5),
+	283:Polyobj_StopSound(1),
+	
+	// new to Eternity
+//	300:Portal_Define(5),
 //	301:Line_QuickPortal(1),
 	
 	
@@ -360,7 +361,7 @@ special
 	-73:CheckFont(1),
 	-74:DropItem(2,4),
 	-75:CheckFlag(2),
-	-76:SetLineActivation(2),
+	-76:SetLineActivation(2, 3),
 	-77:GetLineActivation(1),
 	-78:GetActorPowerupTics(2),
 	-79:ChangeActorAngle(2,3),
@@ -428,6 +429,7 @@ special
 	-211:StartSlideshow(1),
 	-212:GetSectorHealth(2),
 	-213:GetLineHealth(1),
+	-214:SetSubtitleNumber(2),
 	
 	
 	// Eternity's
@@ -444,4 +446,3 @@ special
 	-19621:SetTeamScore(2),
 	
 	-100000:__EndOfList__(10);
-	
diff --git a/Build/Configurations/EdgeC_Doom2Doom.cfg b/Build/Configurations/EdgeC_Doom2Doom.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..4a2e71d8796392c7356ac79211a3fc6e3563bdd7
--- /dev/null
+++ b/Build/Configurations/EdgeC_Doom2Doom.cfg
@@ -0,0 +1,74 @@
+/*************************************************************\
+  Doom Builder 2 Game Configuration for EDGE-Classic
+\*************************************************************/
+
+// 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 = "EDGE-Classic: Doom 2 (Doom format)";
+
+// This is the simplified game engine/sourceport name
+engine = "edge";
+
+// *******************************************************
+// *                                                     *
+// *   Note: all the elements that could be factorized   *
+// *   because they were common to ZDoom, GZDoom and     *
+// *   Zandronum have been moved to ZDoom_common.cfg.     *
+// *                                                     *
+// *******************************************************
+
+// STANDARD DOOM SETTINGS
+// Settings common to all games and all map formats
+include("Includes\\Doom_common.cfg", "common");
+
+// Settings common to Doom map format
+include("Includes\\Boom_common.cfg", "mapformat_doom");
+include("Includes\\MBF21_common.cfg", "mapformat_doom");
+
+// Settings common to Doom games
+include("Includes\\Game_Doom.cfg");
+
+include("Includes\\EdgeC_common.cfg", "mapformat_doom");
+
+// Map name format for Doom 2.
+mapnameformat = "MAPxy";
+
+//mxd. No DECORATE support in vanilla
+decorategames = "";
+
+// Default thing filters
+// (these are not required, just useful for new users)
+thingsfilters
+{
+  include("Includes\\Doom_misc.cfg", "thingsfilters");
+}
+
+// THING TYPES
+// Each engine has its own additional thing types
+// Order should always be 1: Game; 2: ZDoom/game; 3: ZDoom/zdoom
+thingtypes
+{
+  // Basic game actors
+  include("Includes\\Doom_things.cfg");
+  include("Includes\\Doom2_things.cfg");
+  include("Includes\\Boom_things.cfg");
+}
+
+// ENUMERATIONS
+// Each engine has its own additional thing types
+// These are enumerated lists for linedef types and UDMF fields.
+enums
+{
+  // Basic game enums
+  include("Includes\\Doom_misc.cfg", "enums");
+}
+
+// Dehacked data
+dehacked
+{
+  include("Includes\\Dehacked_Doom.cfg");
+}
+
+
diff --git a/Build/Configurations/GZDoom_DoomDoom.cfg b/Build/Configurations/GZDoom_DoomDoom.cfg
index a4e03496dbf4136114f831f5c9929526434ae19d..239fd0d0229e81a0e01b541275d4663e800e404e 100755
--- a/Build/Configurations/GZDoom_DoomDoom.cfg
+++ b/Build/Configurations/GZDoom_DoomDoom.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_DoomHexen.cfg b/Build/Configurations/GZDoom_DoomHexen.cfg
index c37e11c1474cca5d211591b03404a952028dfea2..06d90a3276098b79ea30c2ae27b021db145188a7 100755
--- a/Build/Configurations/GZDoom_DoomHexen.cfg
+++ b/Build/Configurations/GZDoom_DoomHexen.cfg
@@ -23,6 +23,9 @@ enabledbydefault = true;
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_DoomUDMF.cfg b/Build/Configurations/GZDoom_DoomUDMF.cfg
index 802c46b80f9c86d295dae3a7bab165decf6644a4..61cbb7e3690886fb151c6662d2e630e2623fbb74 100755
--- a/Build/Configurations/GZDoom_DoomUDMF.cfg
+++ b/Build/Configurations/GZDoom_DoomUDMF.cfg
@@ -23,6 +23,9 @@ enabledbydefault = true;
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_HereticDoom.cfg b/Build/Configurations/GZDoom_HereticDoom.cfg
index 9e76d118ac6b8c6708f543149b67074a50f8edda..8dbff527399af4aa9523de0569fe37ba8c92cd88 100755
--- a/Build/Configurations/GZDoom_HereticDoom.cfg
+++ b/Build/Configurations/GZDoom_HereticDoom.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_HereticHexen.cfg b/Build/Configurations/GZDoom_HereticHexen.cfg
index cbeb76a5a848a079b70062d5ffe44f9a3e05f9ac..0b7ca69bf091da2ca1f3eae437b6545e1716da14 100755
--- a/Build/Configurations/GZDoom_HereticHexen.cfg
+++ b/Build/Configurations/GZDoom_HereticHexen.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_HereticUDMF.cfg b/Build/Configurations/GZDoom_HereticUDMF.cfg
index 75fd34a5a17ee307d91eb4564af721c934150efc..5c0a32637015919655be55a2d561a0b18fb4197e 100755
--- a/Build/Configurations/GZDoom_HereticUDMF.cfg
+++ b/Build/Configurations/GZDoom_HereticUDMF.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_HexenHexen.cfg b/Build/Configurations/GZDoom_HexenHexen.cfg
index df562f5b86d72e12b935ccd2ff1b38c8593c8150..e82fe93ab7306288c8108b7ff5453f450d5dda54 100755
--- a/Build/Configurations/GZDoom_HexenHexen.cfg
+++ b/Build/Configurations/GZDoom_HexenHexen.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_HexenUDMF.cfg b/Build/Configurations/GZDoom_HexenUDMF.cfg
index 1a58d52361558a8c48b326c1ab2df90be217aabf..63332965f1f48f3cf6a173ac7ec116077194d5ca 100755
--- a/Build/Configurations/GZDoom_HexenUDMF.cfg
+++ b/Build/Configurations/GZDoom_HexenUDMF.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_StrifeDoom.cfg b/Build/Configurations/GZDoom_StrifeDoom.cfg
index 9965ae7a2e3920785bbbfa1548b7cef2131beeb1..5c896ba686603a6c8acbb5f68706299026131c45 100755
--- a/Build/Configurations/GZDoom_StrifeDoom.cfg
+++ b/Build/Configurations/GZDoom_StrifeDoom.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_StrifeHexen.cfg b/Build/Configurations/GZDoom_StrifeHexen.cfg
index 544748b07265f48026567120de0ed50004e67eb2..64984196aba47bfa1dfc8bdeb11fe6b893418632 100755
--- a/Build/Configurations/GZDoom_StrifeHexen.cfg
+++ b/Build/Configurations/GZDoom_StrifeHexen.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/GZDoom_StrifeUDMF.cfg b/Build/Configurations/GZDoom_StrifeUDMF.cfg
index 073b6d6da7fa287b8e0248d36b331350bb4e1144..74dafab97f58832e67886f2598d0806d7b4d1df8 100755
--- a/Build/Configurations/GZDoom_StrifeUDMF.cfg
+++ b/Build/Configurations/GZDoom_StrifeUDMF.cfg
@@ -20,6 +20,9 @@ engine = "gzdoom";
 // *******************************************************
 
 // STANDARD ZDOOM SETTINGS
+// GZDoom core
+include("Includes\\GZDoom_common.cfg");
+
 // Settings common to all games and all map formats
 include("Includes\\ZDoom_common.cfg", "common");
 
diff --git a/Build/Configurations/Hexen_HexenHexen.cfg b/Build/Configurations/Hexen_HexenHexen.cfg
index e18232e4b141eb131217ad03bb4817a88805c750..9cef8cce0a9036900aa52d88d7c93f600ca7326f 100755
--- a/Build/Configurations/Hexen_HexenHexen.cfg
+++ b/Build/Configurations/Hexen_HexenHexen.cfg
@@ -35,9 +35,6 @@ mapnameformat = "MAPxy";
 //mxd. No DECORATE support in vanilla
 decorategames = "";
 
-//mxd. Don't do vanilla-style thing rotation angle clamping
-doomthingrotationangles = false;
-
 // Default thing filters
 // (these are not required, just useful for new users)
 thingsfilters
diff --git a/Build/Configurations/Includes/Doom_common.cfg b/Build/Configurations/Includes/Doom_common.cfg
index 8772b7ff7bc517a36d3bfaad7e36c58b19cc42fe..60eb500a96f8976779f382430db4e744f71f4f9f 100755
--- a/Build/Configurations/Includes/Doom_common.cfg
+++ b/Build/Configurations/Includes/Doom_common.cfg
@@ -19,7 +19,7 @@ common
 
   //mxd. Do vanilla-style thing rotation angle clamping
   doomthingrotationangles = true;
-
+  
   // Texture sources
   textures
   {
diff --git a/Build/Configurations/Includes/Doom_misc.cfg b/Build/Configurations/Includes/Doom_misc.cfg
index ddc3ec787fcd4bf0964a9da9cecfcfb31ec1ed3b..02bfd957d70e62302cb710de2e09755d0c23f9f6 100755
--- a/Build/Configurations/Includes/Doom_misc.cfg
+++ b/Build/Configurations/Includes/Doom_misc.cfg
@@ -148,6 +148,13 @@ sprites
 		start = "SS_START";
 		end = "SS_END";
 	}
+	
+	// Some WADs rely on buggy behavior of the sprite markers
+	standard3
+	{
+		start = "SS_START";
+		end = "S_END";
+	}
 }
 
 // Flat sources
diff --git a/Build/Configurations/Includes/Doom_sectors.cfg b/Build/Configurations/Includes/Doom_sectors.cfg
index b0087620e9929022d2f6fab3ea32143fc2c24f47..bf678e478b517793da5f1c35bfae322eb1c64192 100755
--- a/Build/Configurations/Includes/Doom_sectors.cfg
+++ b/Build/Configurations/Includes/Doom_sectors.cfg
@@ -1,8 +1,8 @@
 
 0 = "None";
 1 = "Light Blinks (randomly)";
-2 = "Light Blinks (1 sec.)";
-3 = "Light Blinks (0.5 sec.)";
+2 = "Light Blinks (0.5 sec.)";
+3 = "Light Blinks (1 sec.)";
 4 = "Damage -10 or 20% health and Light Blinks (0.5 sec.)";
 5 = "Damage -5 or 10% health";
 7 = "Damage -2 or 5% health";
@@ -10,8 +10,8 @@
 9 = "Secret";
 10 = "Door Close Stay (after 30 sec.)";
 11 = "Damage -10 or 20% health and End level";
-12 = "Light Blinks (0.5 sec. synchronized)";
-13 = "Light Blinks (1 sec. synchronized)";
+12 = "Light Blinks (1 sec. synchronized)";
+13 = "Light Blinks (0.5 sec. synchronized)";
 14 = "Door Open Close (opens after 5 min.)";
 16 = "Damage -10 or 20% health";
 17 = "Light Flickers (randomly)";
diff --git a/Build/Configurations/Includes/EdgeC_common.cfg b/Build/Configurations/Includes/EdgeC_common.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..26e61d2cc4e1628af263f038e5f4d5761281c13d
--- /dev/null
+++ b/Build/Configurations/Includes/EdgeC_common.cfg
@@ -0,0 +1,390 @@
+// ***********************************************************
+// *                                                         *
+// * These values are mainly for UDMF EDGE-Classic           *
+// *                                                         *
+// ***********************************************************
+
+common
+{
+	// Some common settings
+	include("Common.cfg");
+
+	// Default testing parameters
+	include("Test_params.cfg", "modern");
+
+	// Default nodebuilder configurations
+	defaultsavecompiler = "glbsp_normal";
+	defaulttestcompiler = "glbsp_fast";
+
+	// Generalized actions
+	// generalizedlinedefs is true for Doom format and false for
+	// the other two, so it's not here.
+	generalizedsectors = true;
+	
+	//mxd. Maximum safe map size check (0 means skip check)
+	safeboundary = 0;
+
+	// Texture loading options
+	mixtexturesflats = true;
+	defaulttexturescale = 1.0f;
+	defaultflatscale = 1.0f;
+	scaledtextureoffsets = true;
+	
+	//mxd. Sidedefs compression
+	// ioanch FIXME: what does this do? I made it false
+	sidedefcompressionignoresaction = false;
+
+	// Texture sources
+	textures
+	{
+		include("Doom_misc.cfg", "textures");
+		include("EdgeC_misc.cfg", "textures");
+	}
+	
+	//mxd. HiRes sources
+	hires
+	{
+		include("EdgeC_misc.cfg", "hires");
+	}
+
+
+	// Patch sources
+	patches
+	{
+		include("Doom_misc.cfg", "patches");
+	}
+
+	// Sprite sources
+	sprites
+	{
+		include("Doom_misc.cfg", "sprites");
+	}
+
+	// Flat sources
+	flats
+	{
+		include("Doom_misc.cfg", "flats");
+	}
+
+	// Colormap sources
+	colormaps
+	{
+		include("Boom_misc.cfg", "colormaps");
+	}
+
+	
+	compatibility
+	{
+		fixnegativepatchoffsets = true;
+		fixmaskedpatchoffsets = true;
+	}
+}
+
+mapformat_doom
+{
+    mixtexturesflats = true;
+  // The format interface handles the map data format
+  formatinterface = "DoomMapSetIO";
+
+	maplumpnames
+	{
+		include("Doom_misc.cfg", "doommaplumpnames");
+		include("Boom_misc.cfg", "boommaplumpnames");
+	}
+
+  // When this is set to true, sectors with the same tag will light up when a line is highlighted
+  linetagindicatesectors = true;
+
+
+
+  // Default flags for first new thing
+  defaultthingflags
+  {
+    include("Doom_misc.cfg", "defaultthingflags");
+  }
+
+  // Door making
+  //include("ZDoom_misc.cfg", "doormaking_doom");
+
+  // Generalized actions
+  generalizedlinedefs = true;
+  generalizedsectors = true;
+  
+  // GENERALIZED LINEDEF TYPES
+	gen_linedeftypes
+	{
+		include("Boom_generalized.cfg", "gen_linedeftypes");
+	}
+  
+  // GENERALIZED SECTOR TYPES
+	gen_sectortypes
+	{
+		include("Boom_generalized.cfg", "gen_sectortypes");
+	}
+  
+  // DEFAULT SECTOR BRIGHTNESS LEVELS
+  sectorbrightness
+  {
+    include("Doom_misc.cfg", "sectorbrightness");
+  }
+
+  // SECTOR TYPES
+  sectortypes
+  {
+    include("Doom_sectors.cfg");
+	include("EdgeC_sectors.cfg");
+  }
+
+	// LINEDEF FLAGS
+	linedefflags
+	{
+		include("Doom_misc.cfg", "linedefflags");
+		include("Boom_misc.cfg", "linedefflags");
+	}
+
+	// LINEDEF ACTIVATIONS
+	linedefactivations
+	{
+	}
+
+	// Linedef flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	linedefflagstranslation
+	{
+		include("Doom_misc.cfg", "linedefflagstranslation");
+		include("Boom_misc.cfg", "linedefflagstranslation");
+	}
+
+	// LINEDEF TYPES
+	linedeftypes
+	{
+		include("Doom_linedefs.cfg");
+		include("Boom_linedefs.cfg");
+		include("EdgeC_linedefs.cfg");	
+	}
+
+	thingtypes
+	{
+		include("EdgeC_things.cfg");
+	}
+
+	// THING FLAGS
+	thingflags
+	{
+		include("Doom_misc.cfg", "thingflags");
+		include("Boom_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("Doom_misc.cfg", "thingflagstranslation");
+		include("Boom_misc.cfg", "thingflagstranslation");
+	}
+		// How to compare thing flags (for the stuck things error checker)
+	thingflagscompare
+	{
+		include("Doom_misc.cfg", "thingflagscompare");
+		include("Boom_misc.cfg", "thingflagscompare");
+	}
+
+	// Things flags masks
+	include("Doom_misc.cfg", "thingflagsmasks");
+
+	mixtexturesflats = true;
+	
+	// Texture sources
+	textures
+	{
+		include("Doom_misc.cfg", "textures");
+		include("EdgeC_misc.cfg", "textures");	// works for Eternity too
+	}
+	
+	//mxd. HiRes sources
+	hires
+	{
+		include("EdgeC_misc.cfg", "hires");
+	}
+}
+// ***********************************************************
+// *                                                         *
+// *                       Text map format                   *
+// *                                                         *
+// ***********************************************************
+
+mapformat_udmf
+{
+	// The format interface handles the map data format
+	formatinterface = "UniversalMapSetIO";
+
+	//mxd. The default script compiler to use
+	defaultscriptcompiler = "zdoom_acs.cfg"; 
+
+	// Enables support for long (> 8 chars) texture names
+	// WARNING: this should only be enabled for UDMF game configurations!
+	// WARNING: enabling this will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3!
+	longtexturenames = false;
+	
+	// Enables setting brightness for floor and ceiling independently from each other
+	distinctfloorandceilingbrightness = false;
+	
+	// Default nodebuilder configurations
+	defaultsavecompiler = "zdbsp_udmf_normal";
+	defaulttestcompiler = "zdbsp_udmf_fast";
+	
+	// ioanch: eternity
+	engine = "edge"; // override that so that DB2 uses the correct namespace
+
+	maplumpnames
+	{
+		include("UDMF_misc.cfg", "udmfmaplumpnames_begin");
+		include("EdgeC_misc.cfg", "udmfmaplumpnames");
+		include("UDMF_misc.cfg", "udmfmaplumpnames_end");
+	}
+
+	// eternity
+	universalfields
+	{
+		include("EdgeC_misc.cfg", "universalfields");
+	}
+
+	// When this is set to true, sectors with the same tag will light up when a line is highlighted
+	linetagindicatesectors = true;
+
+	// Special linedefs
+	//include("ZDoom_misc.cfg", "speciallinedefs_udmf");	// same in EE
+
+	// Default flags for first new thing
+	defaultthingflags
+	{
+		include("EdgeC_misc.cfg", "defaultthingflags_udmf");
+	}
+
+	// Door making
+	//include("Eternity_misc.cfg", "doormaking_udmf");
+
+	// Generalized actions
+	generalizedlinedefs = true;
+	generalizedsectors = true;
+	
+	// GENERALIZED SECTOR TYPES
+	//gen_sectortypes
+	//{
+    //    include("Eternity_generalized.cfg", "gen_sectortypes_udmf");
+	//}
+
+	// SECTOR FLAGS
+	//sectorflags
+	//{
+	//	include("Eternity_misc.cfg", "sectorflags_udmf");
+	//}
+	
+	
+	
+	// DEFAULT SECTOR BRIGHTNESS LEVELS
+	//sectorbrightness
+	//{
+	//	include("ZDoom_misc.cfg", "sectorbrightness");
+	//}
+
+	// SECTOR TYPES
+	//sectortypes
+	//{
+	//	include("Eternity_misc.cfg", "sectors_udmf");
+	//}
+
+	// SECTOR RENSERSTYLES
+	//sectorrenderstyles
+	//{
+	//	include("UDMF_misc.cfg", "sectorrenderstyles");
+	//}
+
+	// LINEDEF FLAGS
+	//linedefflags
+	//{
+	//	include("Eternity_misc.cfg", "linedefflags_udmf");
+	//}
+
+	// LINEDEF ACTIVATIONS
+	//linedefactivations
+	//{
+	//	include("Eternity_misc.cfg", "linedefactivations_udmf");
+	//}
+	
+	//mxd. 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("Doom_misc.cfg", "linedefflagstranslation");
+		//include("Hexen_misc.cfg", "linedefflagstranslation");
+		//include("ZDoom_misc.cfg", "linedefflagstranslation");
+	} 
+
+	// LINEDEF RENSERSTYLES
+	//linedefrenderstyles
+	//{
+	//	include("UDMF_misc.cfg", "linedefrenderstyles");
+	//}
+
+	//SIDEDEF FLAGS
+	//sidedefflags
+	//{
+	//	include("Eternity_misc.cfg", "sidedefflags");  
+	//}
+
+	// THING FLAGS
+	//thingflags
+	//{
+	//	include("Eternity_misc.cfg", "thingflags_udmf");
+	//}
+	
+	// THING RENSERSTYLES
+	//thingrenderstyles
+	//{
+	//	include("UDMF_misc.cfg", "thingrenderstyles");
+	//}
+
+	// How to compare thing flags (for the stuck things error checker)
+	//thingflagscompare
+	//{
+	//	include("Eternity_misc.cfg", "thingflagscompare_udmf");
+	//}
+
+	//mxd. Thing flags UDMF translation table
+	// This is needed for copy/paste and prefabs to work properly
+	// When the UDMF field name is prefixed with ! it is inverted
+	thingflagstranslation
+	{
+		include("Doom_misc.cfg", "thingflagstranslation");
+		//include("Hexen_misc.cfg", "thingflagstranslation");
+		//include("ZDoom_misc.cfg", "thingflagstranslation");
+	}
+
+	// Things flags masks
+	//include("Hexen_misc.cfg", "thingflagsmasks");
+
+	// LINEDEF TYPES
+	//linedeftypes
+	//{
+	//	include("Hexen_linedefs.cfg");
+	//	include("Eternity_linedefs.cfg", "udmf");
+	//}
+	
+	// Texture sources
+	textures
+	{
+		include("Doom_misc.cfg", "textures");
+		include("EdgeC_misc.cfg", "textures");	// works for Eternity too
+	}
+	
+	//mxd. HiRes sources
+	hires
+	{
+		include("EdgeC_misc.cfg", "hires");
+	}
+
+}
\ No newline at end of file
diff --git a/Build/Configurations/Includes/EdgeC_linedefs.cfg b/Build/Configurations/Includes/EdgeC_linedefs.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..50cb1ec98cfe08f25ae0b2f515fb3ed6da81219e
--- /dev/null
+++ b/Build/Configurations/Includes/EdgeC_linedefs.cfg
@@ -0,0 +1,1273 @@
+Edge_breakable_wall
+{
+	title = "Edge: Breakable Wall";		
+	835
+	{
+		title = "Breakable Wall: midtex to back lower";
+		prefix = "";
+	}
+	
+	836
+	{
+		title = "Breakable Wall: midtex to back upper";
+		prefix = "";
+	}
+	
+	837
+	{
+		title = "Breakable Wall: midtex to front lower";
+		prefix = "";
+	}
+	
+	838
+	{
+		title = "Breakable Wall: midtex to front upper";
+		prefix = "";
+	}
+	
+}
+
+Edge_breakable_glass
+{
+	title = "Edge: Breakable Glass";		
+	830
+	{
+		title = "Breakable Glass: monster can see through, can break";
+		prefix = "";
+	}
+	
+	831
+	{
+		title = "Breakable Glass: monster cannot see through, can break";
+		prefix = "";
+	}
+	
+	832
+	{
+		title = "Breakable Glass: monster cannot see through, cannot break";
+		prefix = "";
+	}
+	
+}
+
+Edge_extrafloors_thick
+{
+	title = "Edge: Extrafloors(thick)";
+	
+	281
+	{
+		title = "Legacy: Thick Extrafloor (dummy texture)";
+		prefix = "";
+	}
+	
+	289
+	{
+		title = "Legacy: Thick Extrafloor (dummy texture, master light props)";
+		prefix = "";
+	}
+	
+	600
+	{
+		title = "Thick Extrafloor: Trans 40% (dummy texture)";
+		prefix = "";
+	}
+	
+	400
+	{
+		title = "Thick Extrafloor (dummy texture)";
+		prefix = "";
+	}
+	
+	401
+	{
+		title = "Thick Extrafloor (master upper texture)";
+		prefix = "";
+	}
+	
+	402
+	{
+		title = "Thick Extrafloor (master lower texture)";
+		prefix = "";
+	}
+	430
+	{
+		title = "Thick Translucent Liquid Extrafloor Scroll Down (20% translucent)";
+		prefix = "";
+	}
+	
+	431
+	{
+		title = "Thick Translucent Liquid Extrafloor Scroll Down (40% translucent)";
+		prefix = "";
+	}
+	
+	432
+	{
+		title = "Thick Translucent Liquid Extrafloor Scroll Down (60% translucent)";
+		prefix = "";
+	}
+	
+	433
+	{
+		title = "Thick Translucent Liquid Extrafloor Scroll Down (80% translucent)";
+		prefix = "";
+	}
+	
+}
+
+Edge_extrafloors_liquid
+{
+	title = "Edge: Extrafloors(Liquid)";	
+	403
+	{
+		title = "Liquid Extrafloor (solid)";
+		prefix = "";
+	}
+	
+	404
+	{
+		title = "Liquid Extrafloor (20% translucent)";
+		prefix = "";
+	}
+	
+	405
+	{
+		title = "Liquid Extrafloor (40% translucent)";
+		prefix = "";
+	}
+	
+	406
+	{
+		title = "Liquid Extrafloor (60% translucent)";
+		prefix = "";
+	}
+	
+	407
+	{
+		title = "Liquid Extrafloor (80% translucent)";
+		prefix = "";
+	}
+	
+	408
+	{
+		title = "Liquid Extrafloor (invisible)";
+		prefix = "";
+	}
+}
+
+Edge_translucent_linedef
+{
+	title = "Edge: Translucent Linedef";		
+	409
+	{
+		title = "Translucent Linedef (20%) least transparent";
+		prefix = "";
+	}
+	
+	410
+	{
+		title = "Translucent Linedef (40%)";
+		prefix = "";
+	}
+	
+	411
+	{
+		title = "Translucent Linedef (60%)";
+		prefix = "";
+	}
+	
+	412
+	{
+		title = "Translucent Linedef (80%) most transparent";
+		prefix = "";
+	}
+}
+
+Edge_extrafloors_thin
+{
+	title = "Edge: Extrafloors(thin)";	
+	413
+	{
+		title = "Thin Extrafloor (opaque)";
+		prefix = "";
+	}
+	
+	414
+	{
+		title = "Thin Extrafloor (20% translucent)";
+		prefix = "";
+	}
+	
+	415
+	{
+		title = "Thin Extrafloor (40% translucent)";
+		prefix = "";
+	}
+	
+	416
+	{
+		title = "Thin Extrafloor (60% translucent)";
+		prefix = "";
+	}
+	
+	417
+	{
+		title = "Thin Extrafloor (80% translucent)";
+		prefix = "";
+	}
+	
+}
+
+Edge_RTS_Enable
+{
+	title = "Edge: Enable Tagged RTS";		
+	418
+	{
+		title = "Enable Tagged RTS";
+		prefix = "S1";
+	}
+	
+	419
+	{
+		title = "Enable Tagged RTS";
+		prefix = "SR";
+	}
+	
+	420
+	{
+		title = "Enable Tagged RTS";
+		prefix = "W1";
+	}
+	
+	421
+	{
+		title = "Enable Tagged RTS";
+		prefix = "WR";
+	}
+	440
+	{
+		title = "Enable Tagged RTS";
+		prefix = "G1";
+	}
+	
+	441
+	{
+		title = "Enable Tagged RTS";
+		prefix = "GR";
+	}
+	454
+	{
+		title = "Enable Tagged RTS (monster)";
+		prefix = "W1";
+	}
+	
+	455
+	{
+		title = "Enable Tagged RTS (monster)";
+		prefix = "WR";
+	}
+	
+	456
+	{
+		title = "Enable Tagged RTS (Monster)";
+		prefix = "GR";
+	}
+	
+	457
+	{
+		title = "Disable Tagged RTS";
+		prefix = "SR";
+	}
+	
+	458
+	{
+		title = "Disable Tagged RTS";
+		prefix = "WR";
+	}
+	
+	459
+	{
+		title = "Disable Tagged RTS";
+		prefix = "GR";
+	}
+	
+	460
+	{
+		title = "Disable Tagged RTS (monster)";
+		prefix = "WR";
+	}
+	
+	461
+	{
+		title = "Disable Tagged RTS (monster)";
+		prefix = "GR";
+	}
+}
+
+Edge_Scroller_lines
+{
+	title = "Edge: Scroll Lines";			
+	422
+	{
+		title = "Scroll Right";
+		prefix = "";
+	}
+	
+	423
+	{
+		title = "Scroll Up";
+		prefix = "";
+	}
+	
+	424
+	{
+		title = "Scroll Down";
+		prefix = "";
+	}
+	
+	425
+	{
+		title = "Scroll Left And Up";
+		prefix = "";
+	}
+	
+	426
+	{
+		title = "Scroll Left And Down";
+		prefix = "";
+	}
+	
+	427
+	{
+		title = "Scroll Right And Up";
+		prefix = "";
+	}
+	
+	428
+	{
+		title = "Scroll Right And Down";
+		prefix = "";
+	}
+	
+	429
+	{
+		title = "Scroll Fast Lower And Middle Texture (liquid falls)";
+		prefix = "";
+	}
+	
+}
+
+Edge_Floor
+{
+	title = "Edge: Floor";		
+	
+	434
+	{
+		title = "Raise Floor 2 Units (use for RTS Onheight events)";
+		prefix = "S1";
+	}
+	
+	435
+	{
+		title = "Raise Floor 2 Units (use for RTS Onheight events)";
+		prefix = "SR";
+	}
+	
+	436
+	{
+		title = "Raise Floor 2 Units (use for RTS Onheight events)";
+		prefix = "W1";
+	}
+	
+	437
+	{
+		title = "Raise Floor 2 Units (use for RTS Onheight events)";
+		prefix = "WR";
+	}
+	
+	438
+	{
+		title = "Raise Floor 2 Units (use for RTS Onheight events)";
+		prefix = "S1";
+	}
+	
+	439
+	{
+		title = "Raise Floor 2 Units (use for RTS Onheight events)";
+		prefix = "SR";
+	}
+}
+	
+Edge_SlidingDoor
+{
+	title = "Edge: Sliding Door";		
+	
+	442
+	{
+		title = "Sliding Door (left, monsters)";
+		prefix = "SR";
+	}
+	
+	443
+	{
+		title = "Sliding Door (left)";
+		prefix = "SR";
+	}
+	
+	444
+	{
+		title = "Sliding Door (left, fast)";
+		prefix = "SR";
+	}
+	
+	445
+	{
+		title = "Sliding Door (left)";
+		prefix = "S1";
+	}
+	
+	446
+	{
+		title = "Sliding Door (right, monsters)";
+		prefix = "SR";
+	}
+	
+	447
+	{
+		title = "Sliding Door (right)";
+		prefix = "SR";
+	}
+	
+	448
+	{
+		title = "Sliding Door (right, fast)";
+		prefix = "SR";
+	}
+	
+	449
+	{
+		title = "Sliding Door (right)";
+		prefix = "S1";
+	}
+	
+	450
+	{
+		title = "Sliding Door (center, monsters)";
+		prefix = "SR";
+	}
+	
+	451
+	{
+		title = "Sliding Door (center)";
+		prefix = "SR";
+	}
+	
+	452
+	{
+		title = "Sliding Door (center, fast)";
+		prefix = "SR";
+	}
+	
+	453
+	{
+		title = "Sliding Door (center)";
+		prefix = "S1";
+	}
+	542
+	{
+		title = "Remote Slider (LEFT), MONSTERS";
+		prefix = "SR";
+	}
+	
+	543
+	{
+		title = "Remote Slider (LEFT)";
+		prefix = "SR";
+	}
+	
+	544
+	{
+		title = "Remote Slider (LEFT) FAST";
+		prefix = "SR";
+	}
+	
+	545
+	{
+		title = "Remote Slider (LEFT)";
+		prefix = "S1";
+	}
+	
+	546
+	{
+		title = "Remote Slider (RIGHT), MONSTERS";
+		prefix = "SR";
+	}
+	
+	547
+	{
+		title = "Remote Slider (RIGHT)";
+		prefix = "SR";
+	}
+	
+	548
+	{
+		title = "Remote Slider (RIGHT) FAST";
+		prefix = "SR";
+	}
+	
+	549
+	{
+		title = "Remote Slider (RIGHT)";
+		prefix = "S1";
+	}
+	
+	550
+	{
+		title = "Remote Slider (CENTER), MONSTERS";
+		prefix = "SR";
+	}
+	
+	551
+	{
+		title = "Remote Slider (CENTER)";
+		prefix = "SR";
+	}
+	
+	552
+	{
+		title = "Remote Slider (CENTER) FAST";
+		prefix = "SR";
+	}
+	
+	553
+	{
+		title = "Remote Slider (CENTER)";
+		prefix = "S1";
+	}
+	
+	554
+	{
+		title = "Remote Slider (LEFT)";
+		prefix = "WR";
+	}
+	
+	555
+	{
+		title = "Remote Slider (LEFT) FAST";
+		prefix = "WR";
+	}
+	
+	556
+	{
+		title = "Remote Slider (RIGHT)";
+		prefix = "WR";
+	}
+	
+	557
+	{
+		title = "Remote Slider (RIGHT) FAST";
+		prefix = "WR";
+	}
+	
+	558
+	{
+		title = "Remote Slider (CENTER)";
+		prefix = "WR";
+	}
+	
+	559
+	{
+		title = "Remote Slider (CENTER) FAST";
+		prefix = "WR";
+	}
+}
+
+Edge_Mirror
+{
+	title = "Edge: Mirrors";		
+	
+	
+	462
+	{
+		title = "Mirror, Plain";
+		prefix = "";
+	}
+	
+	463
+	{
+		title = "Mirror, White";
+		prefix = "";
+	}
+	
+	464
+	{
+		title = "Mirror, Blue";
+		prefix = "";
+	}
+	
+	465
+	{
+		title = "Mirror, Red";
+		prefix = "";
+	}
+	
+	466
+	{
+		title = "Mirror, Green";
+		prefix = "";
+	}
+}
+
+Edge_ladder
+{
+	title = "Edge: Ladders";	
+	470
+	{
+		title = "Ladder, 48 units high";
+		prefix = "";
+	}
+	
+	471
+	{
+		title = "Ladder, 80 units high";
+		prefix = "";
+	}
+	
+	472
+	{
+		title = "Ladder, 120 units high";
+		prefix = "";
+	}
+	
+	473
+	{
+		title = "Ladder, 160 units high";
+		prefix = "";
+	}
+	
+	474
+	{
+		title = "Ladder, 192 units high";
+		prefix = "";
+	}
+	
+	475
+	{
+		title = "Ladder, 256 units high";
+		prefix = "";
+	}
+	
+	476
+	{
+		title = "Ladder, 384 units high";
+		prefix = "";
+	}
+	
+	477
+	{
+		title = "Ladder, 512 units high";
+		prefix = "";
+	}
+	
+	478
+	{
+		title = "Ladder, 768 units high";
+		prefix = "";
+	}
+	
+	479
+	{
+		title = "Ladder, no limit(9999)";
+		prefix = "";
+	}
+}
+Edge_portals
+{
+	title = "Edge: Portals";		
+	480
+	{
+		title = "Portal, 20% Translucent";
+		prefix = "";
+	}
+	
+	481
+	{
+		title = "Portal, 30% Translucent";
+		prefix = "";
+	}
+	
+	482
+	{
+		title = "Portal, 40% Translucent";
+		prefix = "";
+	}
+	
+	483
+	{
+		title = "Portal, Blue 40%";
+		prefix = "";
+	}
+	
+	484
+	{
+		title = "Portal, Green 30%";
+		prefix = "";
+	}
+	
+	485
+	{
+		title = "Camera Portal, Plain";
+		prefix = "";
+	}
+	
+	486
+	{
+		title = "Camera Portal, White";
+		prefix = "";
+	}
+	
+	487
+	{
+		title = "Camera Portal, Cyan";
+		prefix = "";
+	}
+	
+	488
+	{
+		title = "Camera Portal, Rusty";
+		prefix = "";
+	}
+	
+	489
+	{
+		title = "Camera Portal, Green";
+		prefix = "";
+	}
+}
+Edge_doors
+{
+	title = "Edge: Doors";		
+	490
+	{
+		title = "Green Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	491
+	{
+		title = "Green Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	492
+	{
+		title = "Green Key TAGGED DOOR";
+		prefix = "SR";
+	}
+	
+	493
+	{
+		title = "Green Key TAGGED DOOR";
+		prefix = "S1";
+	}
+	
+	494
+	{
+		title = "Green Key BLAZING DOOR";
+		prefix = "S1";
+	}
+	580
+	{
+		title = "Gold Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	581
+	{
+		title = "Gold Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	582
+	{
+		title = "Gold Key TAGGED DOOR";
+		prefix = "SR";
+	}
+	
+	583
+	{
+		title = "Gold Key TAGGED DOOR";
+		prefix = "S1";
+	}
+	
+	584
+	{
+		title = "Silver Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	585
+	{
+		title = "Silver Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	586
+	{
+		title = "Silver Key TAGGED DOOR";
+		prefix = "SR";
+	}
+	
+	587
+	{
+		title = "Silver Key TAGGED DOOR";
+		prefix = "S1";
+	}
+	
+	588
+	{
+		title = "Brass Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	589
+	{
+		title = "Brass Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	590
+	{
+		title = "Copper Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	591
+	{
+		title = "Copper Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	592
+	{
+		title = "Steel Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	593
+	{
+		title = "Steel Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	594
+	{
+		title = "Wooden Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	595
+	{
+		title = "Wooden Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	596
+	{
+		title = "Fire Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	597
+	{
+		title = "Fire Key MANUAL DOOR";
+		prefix = "S1";
+	}
+	
+	598
+	{
+		title = "Water Key MANUAL DOOR";
+		prefix = "SR";
+	}
+	
+	599
+	{
+		title = "Water Key MANUAL DOOR";
+		prefix = "S1";
+	}
+}
+Edge_HUB
+{
+	title = "Edge: HUB Exits";		
+	501
+	{
+		title = "Hub Exit to MAP01 / E1M1";
+		prefix = "W1";
+	}
+	
+	502
+	{
+		title = "Hub Exit to MAP02 / E1M2";
+		prefix = "W1";
+	}
+	
+	503
+	{
+		title = "Hub Exit to MAP03 / E1M3";
+		prefix = "W1";
+	}
+	
+	504
+	{
+		title = "Hub Exit to MAP04 / E1M4";
+		prefix = "W1";
+	}
+	
+	505
+	{
+		title = "Hub Exit to MAP05 / E1M5";
+		prefix = "W1";
+	}
+	
+	506
+	{
+		title = "Hub Exit to MAP06 / E1M6";
+		prefix = "W1";
+	}
+	
+	507
+	{
+		title = "Hub Exit to MAP07 / E1M7";
+		prefix = "W1";
+	}
+	
+	508
+	{
+		title = "Hub Exit to MAP08 / E1M8";
+		prefix = "W1";
+	}
+	
+	509
+	{
+		title = "Hub Exit to MAP09 / E1M9";
+		prefix = "W1";
+	}
+	
+	510
+	{
+		title = "Hub Exit to MAP10";
+		prefix = "W1";
+	}
+	
+	511
+	{
+		title = "Hub Exit to MAP11 / E2M1";
+		prefix = "W1";
+	}
+	
+	512
+	{
+		title = "Hub Exit to MAP12 / E2M2";
+		prefix = "W1";
+	}
+	
+	513
+	{
+		title = "Hub Exit to MAP13 / E2M3";
+		prefix = "W1";
+	}
+	
+	514
+	{
+		title = "Hub Exit to MAP14 / E2M4";
+		prefix = "W1";
+	}
+	
+	515
+	{
+		title = "Hub Exit to MAP15 / E2M5";
+		prefix = "W1";
+	}
+	
+	516
+	{
+		title = "Hub Exit to MAP16 / E2M6";
+		prefix = "W1";
+	}
+	
+	517
+	{
+		title = "Hub Exit to MAP17 / E2M7";
+		prefix = "W1";
+	}
+	
+	518
+	{
+		title = "Hub Exit to MAP18 / E2M8";
+		prefix = "W1";
+	}
+	
+	519
+	{
+		title = "Hub Exit to MAP19 / E2M9";
+		prefix = "W1";
+	}
+	
+	520
+	{
+		title = "Hub Exit to MAP20";
+		prefix = "W1";
+	}
+	
+	521
+	{
+		title = "Hub Exit to MAP21 / E3M1";
+		prefix = "W1";
+	}
+	
+	522
+	{
+		title = "Hub Exit to MAP22 / E3M2";
+		prefix = "W1";
+	}
+	
+	523
+	{
+		title = "Hub Exit to MAP23 / E3M3";
+		prefix = "W1";
+	}
+	
+	524
+	{
+		title = "Hub Exit to MAP24 / E3M4";
+		prefix = "W1";
+	}
+	
+	525
+	{
+		title = "Hub Exit to MAP25 / E3M5";
+		prefix = "W1";
+	}
+	
+	526
+	{
+		title = "Hub Exit to MAP26 / E3M6";
+		prefix = "W1";
+	}
+	
+	527
+	{
+		title = "Hub Exit to MAP27 / E3M7";
+		prefix = "W1";
+	}
+	
+	528
+	{
+		title = "Hub Exit to MAP28 / E3M8";
+		prefix = "W1";
+	}
+	
+	529
+	{
+		title = "Hub Exit to MAP29 / E3M9";
+		prefix = "W1";
+	}
+	
+	530
+	{
+		title = "Hub Exit to MAP30";
+		prefix = "W1";
+	}
+	
+	531
+	{
+		title = "Hub Exit to MAP31 / E4M1";
+		prefix = "W1";
+	}
+	
+	532
+	{
+		title = "Hub Exit to MAP32 / E4M2";
+		prefix = "W1";
+	}
+	
+	533
+	{
+		title = "Hub Exit to MAP33 / E4M3";
+		prefix = "W1";
+	}
+	
+	534
+	{
+		title = "Hub Exit to MAP34 / E4M4";
+		prefix = "W1";
+	}
+	
+	535
+	{
+		title = "Hub Exit to MAP35 / E4M5";
+		prefix = "W1";
+	}
+	
+	536
+	{
+		title = "Hub Exit to MAP36 / E4M6";
+		prefix = "W1";
+	}
+	
+	537
+	{
+		title = "Hub Exit to MAP37 / E4M7";
+		prefix = "W1";
+	}
+	
+	538
+	{
+		title = "Hub Exit to MAP38 / E4M8";
+		prefix = "W1";
+	}
+	
+	539
+	{
+		title = "Hub Exit to MAP39 / E4M9";
+		prefix = "W1";
+	}
+}
+Edge_slopes
+{
+	title = "Edge: Slopes";	
+	
+	
+	567
+	{
+		title = "Detail Slope : FLOOR";
+		prefix = "";
+	}
+	
+	568
+	{
+		title = "Detail Slope : CEILING";
+		prefix = "";
+	}
+	
+	569
+	{
+		title = "Detail Slope : FLOOR+CEILING";
+		prefix = "";
+	}
+}
+
+Edge_alignflats
+{
+	title = "Edge: Flats";		
+	
+	800
+	{
+		title = "Align and rotate front sector's FLOOR";
+		prefix = "";
+	}
+	
+	801
+	{
+		title = "Align and rotate back sector's FLOOR";
+		prefix = "";
+	}
+	
+	802
+	{
+		title = "Align and rotate front sector's CEILING";
+		prefix = "";
+	}
+	
+	803
+	{
+		title = "Align and rotate back sector's CEILING";
+		prefix = "";
+	}
+	
+	804
+	{
+		title = "Align and rotate front sector's FLOOR+CEILING";
+		prefix = "";
+	}
+	
+	805
+	{
+		title = "Align and rotate back sector's FLOOR+CEILING";
+		prefix = "";
+	}
+	
+	810
+	{
+		title = "Align, scale and rotate front sector's FLOOR";
+		prefix = "";
+	}
+	
+	811
+	{
+		title = "Align, scale and rotate back sector's FLOOR";
+		prefix = "";
+	}
+	
+	812
+	{
+		title = "Align, scale and rotate front sector's CEILING";
+		prefix = "";
+	}
+	
+	813
+	{
+		title = "Align, scale and rotate back sector's CEILING";
+		prefix = "";
+	}
+	
+	814
+	{
+		title = "Align, scale and rotate front sector's FLOOR+CEILING";
+		prefix = "";
+	}
+	
+	815
+	{
+		title = "Align, scale and rotate back sector's FLOOR+CEILING";
+		prefix = "";
+	}
+	
+	820
+	{
+		title = "Scale front sector's FLOOR";
+		prefix = "";
+	}
+	
+	821
+	{
+		title = "Scale back sector's FLOOR";
+		prefix = "";
+	}
+	
+	822
+	{
+		title = "Scale front sector's CEILING";
+		prefix = "";
+	}
+	
+	823
+	{
+		title = "Scale back sector's CEILING";
+		prefix = "";
+	}
+	
+	824
+	{
+		title = "Scale front sector's FLOOR+CEILING";
+		prefix = "";
+	}
+	
+	825
+	{
+		title = "Scale back sector's FLOOR+CEILING";
+		prefix = "";
+	}
+	
+	
+}
diff --git a/Build/Configurations/Includes/EdgeC_misc.cfg b/Build/Configurations/Includes/EdgeC_misc.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..c94fd5f32d987570c4470829e9acc878ee201d2b
--- /dev/null
+++ b/Build/Configurations/Includes/EdgeC_misc.cfg
@@ -0,0 +1,636 @@
+
+// Texture loading options
+mixtexturesflats = true;
+defaulttexturescale = 1.0f;
+defaultflatscale = 1.0f;
+
+linedefflags
+{
+	512 = "PassThru";
+}
+
+
+linedefflagtooltips
+{
+	512 = "Normally, if you try to pull a switch or use a door, then players are only able to activate\nthe closest linedef with a special. When PassThru is set, your use reaches\nthrough and allows players to activate both this line and whatever is behind this\nline.\nThis does nothing for 1-sided lines because they always block use actions.\n\nNote that if someone plays your map on ZDoom-derived ports like GZDoom or Zandronum,\nthen PassThru will be true for walkover specials & scrollers even when this flag\nis not set, unless you set 'compat_useblocking' in MAPINFO.";
+}
+
+
+
+
+// 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
+{
+	512 = "passuse";
+}
+
+
+linedefflags_udmf
+{
+	blocking = "Impassable";
+	blockmonsters = "Block monster";
+	twosided = "Doublesided";
+	dontpegtop = "Upper unpegged";
+	dontpegbottom = "Lower unpegged";
+	secret = "Secret";
+	blocksound = "Block sound";
+	dontdraw = "Hidden";
+	mapped = "Shown";
+	passuse = "PassThru";
+}
+
+linedefflagtooltips_udmf
+{
+	blocking = "If set on a 2-sided linedef, the linedef blocks players and monsters from being able to move through it. \nThis is typically used for mid-textured cage bars.\nBy convention, most doom-related software sets it for 1-sided lines by default because the\nid Software maps did, but it doesn't actually do anything.";
+	blockmonsters = "If set, blocks only monster movement.\n(in addition to whatever existing blocking properties it would have).";
+	twosided = "If set, then it has 2 sidedefs and acts as a connection between two sectors.";
+	dontpegtop = "If set:\nIf the linedef is 2-sided, the upper texture starts at the height of the higher ceiling.\nIf not set: and the linedef is 2-sided, the upper texture starts at the lower ceiling instead.\nIf the linedef is 1-sided, this does nothing.";
+	dontpegbottom = "If set: If the linedef is 2-sided, the lower texture starts at the height of the higher ceiling.\nIf not set: and the linedef is 2-sided, the upper texture starts at the higher floor instead.\nIf the linedef is 1-sided, this makes the mid texture start at floor instead of the ceiling.\nYou probably want to use this for door tracks if you don't want them to move with the door.";
+	secret = "If set, this flag prevents it from being marked as a switch or a door on the automap.";
+	blocksound = "If set, this linedef will partially block monster-waking sound from passing into the sector on the other side of it.\nNote that a sound must try to pass through two of these lines before being blocked.\nThis does not block the actual audio that the player hears at all, it is only for the monster AI.\nThis does nothing for 1-sided linedefs.";
+	dontdraw = "If set, this line is not shown on the automap even if you are looking directly at it";
+	mapped = "If set, this line is always revealed on the automap even if you haven't actually visited the area.";
+	passuse = "Normally, if you try to pull a switch or use a door, then players are only able to activate\nthe closest linedef with a special. When PassThru is set, your use reaches\nthrough and allows players to activate both this line and whatever is behind this\nline.\nThis does nothing for 1-sided lines because they always block use actions.\n\nNote that if someone plays your map on ZDoom-derived ports like GZDoom or Zandronum,\nthen PassThru will be true for walkover specials & scrollers even when this flag\nis not set, unless you set 'compat_useblocking' in MAPINFO.";
+}
+
+
+
+thingflags
+{
+	//2048 = "Translucent (25%)";
+	//4096 = "Invisible";
+	//8192 = "Friendly";
+	//16384 = "Frozen while inactive";
+}
+
+
+thingflags_udmf
+{
+	skill1 = "Skill 1";
+	skill2 = "Skill 2";
+	skill3 = "Skill 3";
+	skill4 = "Skill 4";
+	skill5 = "Skill 5";
+	ambush = "Ambush";
+	single = "Singleplayer";
+	dm = "Deathmatch";
+	coop = "Cooperative";
+	friend = "Friendly";
+}
+
+thingflagtooltips_udmf
+{
+	skill1 = "If set, this actor appears on 'I'm Too Young To Die' difficulty.";
+	skill2 = "If set, this actor appears on 'Hey, Not Too Rough' difficulty.";
+	skill3 = "If set, this actor appears on 'Hurt Me Plenty' difficulty.";
+	skill4 = "If set, this actor appears on 'Ultraviolence' difficulty.";
+	skill5 = "If set, this actor appears on 'Nightmare' difficulty.";
+	ambush = "If set, this actor does not wake up if it hears the player until the player comes within field of view\n(even if the actor is facing the away from the player).";
+	single = "If not set, the actor does not appear in singleplayer modes.";
+	dm = "If not set, the actor does not appear in deathmatch modes.";
+	coop = "If not set, the actor does not appear in co-op modes.";
+	friend = "If set, the actor is friendly to the player.\nIf it is a monster, it will attack other players.";
+}
+
+
+
+// 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
+{
+	//2048 = "translucent";
+	//4096 = "invisible";
+	//8192 = "friend";
+	//16384 = "standing";
+}
+
+
+defaultthingflags
+{
+	1;
+	2;
+	4;
+}
+
+
+defaultthingflags_udmf
+{
+	skill1;
+	skill2;
+	skill3;
+	skill4;
+	skill5;
+	single;
+	coop;
+	dm;
+}
+
+
+// Default sector brightness levels
+sectorbrightness
+{
+	255; 248; 240; 232; 224; 216; 208; 200; 192; 184; 176; 168; 160; 152; 144; 136;
+	128; 120; 112; 104; 96; 88; 80; 72; 64; 56; 48; 40; 32; 24; 16; 8; 0;
+}
+
+
+/*
+TEXTURES AND FLAT SOURCES
+This tells Doom Builder where to find the information for textures
+and flats in the IWAD file, Addition WAD file and Map WAD file.
+
+Start and end lumps must be given in a structure (of which the
+key name doesnt matter) and any textures or flats in between them
+are loaded in either the textures category or flats category.
+
+For textures: PNAMES, TEXTURE1 and TEXTURE2 are loaded by default.
+*/
+
+textures
+{
+	edge1
+	{
+		start = "TX_START";
+		end = "TX_END";
+	}
+}
+
+hires
+{
+	edge1
+	{
+		start = "HI_START";
+		end = "HI_END";
+	}
+}
+
+
+
+/*
+ADDITIONAL UNIVERSAL DOOM MAP FORMAT FIELD DEFINITIONS
+See Documents\fielddatatypes.txt for informtion on the types.
+*/
+universalfields
+{
+	linedef
+	{
+		comment
+		{
+			type = 2;
+			default = "";
+		}
+		
+	}
+	
+	sidedef
+	{
+		comment
+		{
+			type = 2;
+			default = "";
+		}
+		
+	}
+	
+	thing
+	{
+		comment
+		{
+			type = 2;
+			default = "";
+		}
+		
+	}
+	
+	sector
+	{
+		comment
+		{
+			type = 2;
+			default = "";
+		}
+		
+	}
+}
+
+
+/*
+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.
+*/
+
+doommaplumpnames
+{
+	REJECT
+	{
+		allowempty = true;
+	}
+}
+
+
+
+udmfmaplumpnames
+{
+	BEHAVIOR
+	{
+		required = false;
+		nodebuild = false;
+		blindcopy = true;
+	}
+	
+	DIALOGUE
+	{
+		required = false;
+		nodebuild = false;
+		script = "ZDoom_USDF.cfg";
+	}
+	
+	ZNODES
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = false;
+	}
+	
+	BLOCKMAP
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = true;
+	}
+	
+	REJECT
+	{
+		required = false;
+		nodebuild = true;
+		allowempty = true;
+	}
+	
+	SCRIPTS
+	{
+		required = false;
+		nodebuild = false;
+		script = "ZDoom_ACS.cfg";
+	}
+}
+
+
+enums
+{
+	frontback
+	{
+		0 = "Front";
+		1 = "Back";
+	}
+	
+	backfront
+	{
+		1 = "Front";
+		0 = "Back";
+	}
+	
+	floorceiling
+	{
+		0 = "Floor";
+		1 = "Ceiling";
+	}
+	
+	ceilingfloor
+	{
+		0 = "Ceiling";
+		1 = "Floor";
+	}
+	
+	sector_flags
+	{
+		1 = "Silent";
+		2 = "No Falling Damage";
+		4 = "Drop with floor";
+		8 = "No Respawn";
+	}
+	
+	keys
+	{
+		0 = "None";
+		1 = "Red key card";
+		2 = "Blue key card";
+		3 = "Yellow key card";
+		4 = "Red skull key";
+		5 = "Blue skull key";
+		6 = "Yellow skull key";
+		100 = "Any key";
+		101 = "All keys";
+		102 = "Impossible";
+		129 = "Any red key";
+		130 = "Any blue key";
+		131 = "Any yellow key";
+		229 = "One of each color";
+	}
+	
+	spawnthing
+	{
+		0 = "None";
+		1 = "Shotgun Guy";
+		2 = "Chaingun Guy";
+		3 = "Baron Of Hell";
+		4 = "Zombieman";
+		5 = "Imp";
+		6 = "Arachnotron";
+		7 = "Spider Mastermind";
+		8 = "Demon";
+		9 = "Spectre";
+		10 = "Imp Fireball";
+		11 = "Clip";
+		12 = "Shells";
+		19 = "Cacodemon";
+		20 = "Revenant";
+		21 = "Bridge";
+		22 = "Armour Bonus";
+		23 = "Stimpack";
+		24 = "Medkit";
+		25 = "Soul Sphere";
+		27 = "Shotgun";
+		28 = "Chaingun";
+		29 = "Rocket Launcher";
+		30 = "Plasma Gun";
+		31 = "BFG";
+		32 = "Chainsaw";
+		33 = "Super Shotgun";
+		51 = "Plasma Bolt";
+		53 = "Tracer";
+		68 = "Green Armour";
+		69 = "Blue Armour";
+		75 = "Cell";
+		85 = "Blue Keycard";
+		86 = "Red Keycard";
+		87 = "Yellow Keycard";
+		88 = "Yellow Skullkey";
+		89 = "Red Skullkey";
+		90 = "Blue Skullkey";
+		98 = "Temp Large Flame";
+		100 = "Stealth Baron";
+		101 = "Stealth Hell Knight";
+		102 = "Stealth Zombieman";
+		103 = "Stealth Shotgun Guy";
+		110 = "Lost Soul";
+		111 = "Arch-Vile";
+		112 = "Mancubus";
+		113 = "Hell Knight";
+		114 = "Cyberdemon";
+		115 = "Pain Elemental";
+		116 = "Wolf SS Soldier";
+		117 = "Stealth Arachnotron";
+		118 = "Stealth Arch-Vile";
+		119 = "Stealth Cacodemon";
+		120 = "Stealth Chaingun Guy";
+		121 = "Stealth Demon";
+		122 = "Stealth Imp";
+		123 = "Stealth Mancubus";
+		124 = "Stealth Revenant";
+		125 = "Barrel";
+		126 = "Cacodemon Shot";
+		127 = "Rocket (Projectile)";
+		128 = "BFG Shot";
+		129 = "Arachnotron Plasma Bolt";
+		130 = "Blood";
+		131 = "Bullet Puff";
+		132 = "Megasphere";
+		133 = "Invulnerability Sphere";
+		134 = "Berserk Pack";
+		135 = "Invisibility Sphere";
+		136 = "Radiation Suit";
+		137 = "Computer Map";
+		138 = "Light-Amp Goggles";
+		139 = "Box Of Ammo";
+		140 = "Rocket (Ammo)";
+		141 = "Box Of Rockets";
+		142 = "Larce Cell";
+		143 = "Box Of Shells";
+		144 = "Backpack";
+		145 = "Guts";
+		146 = "Blood Pool 1";
+		147 = "Blood Pool 2";
+		148 = "Blood Pool 3";
+		149 = "Flaming Barrel";
+		150 = "Brains";
+		151 = "Scripted Marine";
+		152 = "Health Bonus";
+		153 = "Mancubus Shot";
+		154 = "Baron Fireball";
+	}
+	
+	generic_floor_target
+	{
+		0 = "Relative offset";
+		1 = "Highest neighbor";
+		2 = "Lowest neighbor";
+		3 = "Nearest neighbor";
+		4 = "Lowest neighbor";
+		5 = "Ceiling";
+		6 = "Shortest lower texture";
+	}
+	
+	generic_ceiling_target
+	{
+		0 = "Relative offset";
+		1 = "Highest neighbor";
+		2 = "Lowest neighbor";
+		3 = "Nearest neighbor";
+		4 = "Highest neighbor";
+		5 = "Floor";
+		6 = "Shortest lower texture";
+	}
+	
+	generic_door_types
+	{
+		0 = "Open Close";
+		1 = "Open Stay";
+		2 = "Close Open";
+		3 = "Close Stay";
+	}
+	
+	generic_lift_types
+	{
+		0 = "Up Stay";
+		1 = "Down Up";
+		2 = "Down to nearest";
+		3 = "Down to lowest";
+		4 = "Perpetual raise";
+	}
+
+	death_types
+	{
+		0  = "Unknown";
+		12 = "Water";
+		13 = "Slime";
+		14 = "Lava";
+		15 = "Crush";
+		16 = "Telefrag";
+		17 = "Falling";
+		18 = "Suicide";
+		19 = "Barrel";
+		20 = "Exit";
+		21 = "Splash";
+		22 = "Hit";
+	}
+}
+
+
+// Default thing filters
+// (these are not required, just usefull for new users)
+thingsfilters_udmf
+{
+	
+	filter0
+	{
+		name = "Keys only";
+		category = "keys";
+		type = -1;
+	}
+	
+	
+	filter2
+	{
+		name = "Skill 1";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill1 = true;
+		}
+		
+	}
+	
+	
+	filter3
+	{
+		name = "Skill 2";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill2 = true;
+		}
+		
+	}
+	
+	
+	filter4
+	{
+		name = "Skill 3";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill3 = true;
+		}
+		
+	}
+	
+	
+	filter5
+	{
+		name = "Skill 4";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill4 = true;
+		}
+		
+	}
+	
+	/*
+	filter6
+	{
+		name = "Skill 5";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill5 = true;
+		}
+		
+	}
+	
+	
+	filter7
+	{
+		name = "Skill 6";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill6 = true;
+		}
+		
+	}
+	
+	
+	filter8
+	{
+		name = "Skill 7";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill7 = true;
+		}
+		
+	}
+	
+	
+	filter9
+	{
+		name = "Skill 8";
+		category = "";
+		type = -1;
+		
+		fields
+		{
+			skill8 = true;
+		}
+		
+	}
+	*/
+	
+}
+
+
+// How thing flags should be compared (for the stuck thing error check)
+thingflagscompare_udmf
+{
+	skills {
+		skill6;
+		skill7;
+		skill8;
+	}
+
+	classes {
+		class4;
+		class5;
+		class6;
+		class7;
+		class8;
+	}
+}
\ No newline at end of file
diff --git a/Build/Configurations/Includes/EdgeC_sectors.cfg b/Build/Configurations/Includes/EdgeC_sectors.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..b69478ac525d0b85931fbf64f8e59cf5175ddedd
--- /dev/null
+++ b/Build/Configurations/Includes/EdgeC_sectors.cfg
@@ -0,0 +1,63 @@
+29 = "Hub Entry";
+20 = "Airless";
+25 = "Lo Grav";
+4418 = "Water Liquid Extrafloor SCROLL/PUSH North";
+4419 = "Water Liquid Extrafloor SCROLL/PUSH North East";
+4420 = "Water Liquid Extrafloor SCROLL/PUSH East";
+4421 = "Water Liquid Extrafloor SCROLL/PUSH South East";
+4422 = "Water Liquid Extrafloor SCROLL/PUSH South";
+4423 = "Water Liquid Extrafloor SCROLL/PUSH South West";
+4424 = "Water Liquid Extrafloor SCROLL/PUSH West";
+4425 = "Water Liquid Extrafloor SCROLL/PUSH North West";
+4426 = "Slime Liquid Extrafloor SCROLL/PUSH North";
+4427 = "Slime Liquid Extrafloor SCROLL/PUSH North East";
+4428 = "Slime Liquid Extrafloor SCROLL/PUSH East";
+4429 = "Slime Liquid Extrafloor SCROLL/PUSH South East";
+4430 = "Slime Liquid Extrafloor SCROLL/PUSH South";
+4431 = "Slime Liquid Extrafloor SCROLL/PUSH South West";
+4432 = "Slime Liquid Extrafloor SCROLL/PUSH West";
+4433 = "Slime Liquid Extrafloor SCROLL/PUSH North West";
+4434 = "Lava Liquid Extrafloor SCROLL/PUSH North";
+4435 = "Lava Liquid Extrafloor SCROLL/PUSH North East";
+4436 = "Lava Liquid Extrafloor SCROLL/PUSH East";
+4437 = "Lava Liquid Extrafloor SCROLL/PUSH South East";
+4438 = "Lava Liquid Extrafloor SCROLL/PUSH South";
+4439 = "Lava Liquid Extrafloor SCROLL/PUSH South West";
+4440 = "Lava Liquid Extrafloor SCROLL/PUSH West";
+4441 = "Lava Liquid Extrafloor SCROLL/PUSH North West";
+4442 = "Push North";
+4443 = "Push North East";
+4444 = "Push East";
+4445 = "Push South East";
+4446 = "Push South";
+4447 = "Push South West";
+4448 = "Push West";
+4449 = "Push North West";
+4450 = "Scroll Floor Texture North";
+4451 = "Scroll Floor Texture North East";
+4452 = "Scroll Floor Texture East";
+4453 = "Scroll Floor Texture South East";
+4454 = "Scroll Floor Texture South";
+4455 = "Scroll Floor Texture South West";
+4456 = "Scroll Floor Texture West";
+4457 = "Scroll Floor Texture North West";
+4458 = "Scroll Floor Texture/Push North";
+4459 = "Scroll Floor Texture/Push  North East";
+4460 = "Scroll Floor Texture/Push  East";
+4461 = "Scroll Floor Texture/Push  South East";
+4462 = "Scroll Floor Texture/Push  South";
+4463 = "Scroll Floor Texture/Push  South West";
+4464 = "Scroll Floor Texture/Push  West";
+4465 = "Scroll Floor Texture/Push  North West";
+4466 = "Blue Hue (Swimmable)";
+4467 = "Green Hue (Swimmable)";
+4468 = "Green Hue + Damage (Swimmable)";
+4469 = "Red Hue (Swimmable)";
+4470 = "Red Hue + Damage (Swimmable)";
+4471 = "Blue Hue";
+4472 = "Green Hue";
+4473 = "Red Hue";
+4474 = "Yellow Hue";
+4475 = "Purple Hue";
+4476 = "Grey Hue";
+
diff --git a/Build/Configurations/Includes/EdgeC_things.cfg b/Build/Configurations/Includes/EdgeC_things.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..adb892e26215afd4c7edf7a51e55b20e12336cf6
--- /dev/null
+++ b/Build/Configurations/Includes/EdgeC_things.cfg
@@ -0,0 +1,331 @@
+
+
+EDGE_monsters
+{
+	color = 12;	// Light Red
+	arrow = 1;
+	title = "Edge: Monsters";
+	width = 20;
+	sort = 1;
+	height = 56;
+	hangs = 0;
+	blocking = 1;
+	error = 2;
+	
+	
+	4050
+	{
+		title = "Stealth Arachnotron";
+		height = 32;
+	}
+	
+	4051
+	{
+		title = "Stealth Archvile";
+		height = 32;
+	}
+	
+	4052
+	{
+		title = "Stealth Baron of Hell";
+		height = 32;
+	}
+	
+	4053
+	{
+		title = "Stealth Cacodemon";
+		height = 32;
+	}
+	
+	4054
+	{
+		title = "Stealth Heavy weapon dude";
+		height = 32;
+	}
+	
+	4055
+	{
+		title = "Stealth Demon";
+		height = 32;
+	}
+	
+	4056
+	{
+		title = "Stealth Hell knight";
+		height = 32;
+	}
+	
+	4057
+	{
+		title = "Stealth Imp";
+		height = 32;
+	}		
+	4058
+	{
+		title = "Stealth Mancubus";
+		height = 32;
+	}
+	
+	4059
+	{
+		title = "Stealth Revenant";
+		height = 32;
+	}
+	
+	4060
+	{
+		title = "Stealth Shotgun Guy";
+		height = 32;
+	}
+	
+	4061
+	{
+		title = "Stealth Zombie man";
+		height = 32;
+	}
+	
+	7100
+	{
+		title = "EDGE Revenant MKII";
+		height = 32;
+	}
+	
+	7101
+	{
+		title = "EDGE Teleport spawner";
+		height = 32;
+	}
+	
+	7102
+	{
+		title = "Arachnotron MKII";
+		height = 32;
+	}
+	
+	7103
+	{
+		title = "Mancubus MKII";
+		height = 32;
+	}
+}
+
+
+edge_powerups
+{
+	color = 9;	// Light Blue
+	arrow = 0;
+	title = "EDGE: Powerups";
+	width = 20;
+	height = 20;
+	hangs = 0;
+	blocking = 0;
+	sort = 1;
+	
+	7000
+	{
+		title = "EDGE Nightvision Specs";
+		sprite = "NVSCA0";
+
+	}
+	
+	7020
+	{
+		title = "EDGE Jetpack";
+		sprite = "JPCKA0";
+	}
+	
+	7005
+	{
+		title = "EDGE Stop Time";
+		sprite = "TSTPB0";
+	}
+	
+	
+}
+
+edge_glowlights
+{
+	color = 9;	// Light Blue
+	arrow = 0;
+	title = "EDGE: Sector Light FX";
+	sprite = "internal:light";
+	width = 20;
+	height = 20;
+	hangs = 0;
+	blocking = 0;
+	
+	7041
+	{
+		title = "EDGE Nukage Glow";
+		height = 32;
+	}
+	
+	7042
+	{
+		title = "EDGE Lava Glow";
+		height = 32;
+	}
+	
+	7043
+	{
+		title = "EDGE Water Glow";
+		height = 32;
+	}
+}
+
+edge_lights
+{
+	color = 9;	// Light Blue
+	arrow = 0;
+	title = "EDGE: Lights";
+	sprite = "internal:light";
+	width = 8;
+	height = 20;
+	hangs = 0;
+	blocking = 0;
+	
+	7044
+	{
+		title = "White light: Floor";
+		height = 32;
+		color = 15;
+	}
+
+	7045
+	{
+		title = "Yellow light: Floor";
+		height = 32;
+		color = 15;
+	}
+	
+	7046
+	{
+		title = "Red light: Floor";
+		height = 32;
+		color = 15;
+	}
+	
+	7047
+	{
+		title = "Blue light: Floor";
+		height = 32;
+		color = 15;
+	}
+	
+	7048
+	{
+		title = "Green light: Floor";
+		height = 32;
+		color = 15;
+	}
+	
+	7049
+	{
+		title = "Orange light: Floor";
+		height = 32;
+		color = 15;
+	}
+	
+	7054
+	{
+		title = "White light: Ceiling";
+		height = 16;
+		hangs = 1;
+		color = 15;
+	}
+
+	7055
+	{
+		title = "Yellow light: Ceiling";
+		height = 16;
+		hangs = 1;
+		color = 15;
+	}
+	
+	7056
+	{
+		title = "Red light: Ceiling";
+		height = 16;
+		hangs = 1;
+		color = 15;
+	}
+	
+	7057
+	{
+		title = "Blue light: Ceiling";
+		height = 16;
+		hangs = 1;
+		color = 15;
+	}
+	
+	7058
+	{
+		title = "Green light: Ceiling";
+		height = 16;
+		hangs = 1;
+		color = 15;
+	}
+	
+	7059
+	{
+		title = "Orange light: Ceiling";
+		height = 16;
+		hangs = 1;
+		color = 15;
+	}
+	
+}
+
+
+
+edge_keys
+{
+	color = 13;	// Light Magenta
+	arrow = 0;
+	title = "EDGE: Keys";
+	width = 20;
+	sort = 1;
+	height = 16;
+	hangs = 0;
+	blocking = 0;
+	
+	7015
+	{
+		title = "Green keycard";
+		sprite = "BKEYA0";
+	}
+	7017
+	{
+		title = "Green skullkey";
+		sprite = "BSKUB0";
+	}
+}
+
+edge_armour
+{
+	color = 1;	// Blue
+	arrow = 0;
+	title = "EDGE: Armour";
+	width = 20;
+	sort = 1;
+	height = 16;
+	hangs = 0;
+	blocking = 0;
+	
+	7031
+	{
+		title = "Purple armor";
+		sprite = "ARM1A0";
+	}
+	7032
+	{
+		title = "Yellow armor";
+		sprite = "ARM2A0";
+	}
+	7033
+	{
+		title = "Red armor";
+		sprite = "ARM2A0";
+	}
+}
+
diff --git a/Build/Configurations/Includes/Eternity_misc.cfg b/Build/Configurations/Includes/Eternity_misc.cfg
index b62355e22f143fd3ce87c497b7853097b7c80c2f..c58d744855d2141fd52558d5d7d10d486f3779a0 100755
--- a/Build/Configurations/Includes/Eternity_misc.cfg
+++ b/Build/Configurations/Includes/Eternity_misc.cfg
@@ -511,10 +511,11 @@ linedefactivations_udmf
 	monsteruse = "When monster presses use";
 	monstercross = "When monster walks over";
 	monsterpush = "When monsters bumps";
+	monstershoot = "On monster hitscan impact";
 //	anycross = "Any crossing non-missile activates";
 	missilecross = "When projectile crosses";
     polycross = "When polyobject moves over";
-	impact = "On projectile impact";
+	impact = "On player hitscan impact";
 //	checkswitchrange = "Switch height check";
 	passuse
 	{
diff --git a/Build/Configurations/Includes/GZDoom_common.cfg b/Build/Configurations/Includes/GZDoom_common.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..c746b16d980922db8ac7aa4cdac7fd23e7e9d3a5
--- /dev/null
+++ b/Build/Configurations/Includes/GZDoom_common.cfg
@@ -0,0 +1,21 @@
+// This is used to require GZDoom.pk3 when you are working with a GZDoom game configuration.
+
+requiredarchives
+{
+	gzdoom
+	{
+		filename = "gzdoom.pk3";
+		need_exclude = true;
+		
+		0
+		{
+			// this is currently checked globally for all archives
+			class = "actor";
+		}
+	
+		1
+		{
+			lump = "x11r6rgb.txt";
+		}
+	}
+}
diff --git a/Build/Configurations/Includes/GZDoom_things.cfg b/Build/Configurations/Includes/GZDoom_things.cfg
index 382a921b95cdd29b308ef22523e6f47797dfe1fa..d0416629b59c77424329884be449e4a7d040def8 100755
--- a/Build/Configurations/Includes/GZDoom_things.cfg
+++ b/Build/Configurations/Includes/GZDoom_things.cfg
@@ -1,5 +1,89 @@
 gzdoom_lights
 {
+	staticlights
+	{
+		color = 7;
+		arrow = 0;
+		title = "Static Lights";
+		sort = 1;
+		width = 0;
+		height = 0;
+		hangs = 0;
+		blocking = 0;
+		fixedsize = true;
+		sprite = "internal:light";
+
+		9875
+		{
+			title = "Light Probe";
+		}
+		
+		9876
+		{
+			title = "Static Point Light";
+			class = "StaticPointLight"; // Fake class name
+			arg0
+			{
+				title = "Red";
+				default = 255;
+			}
+			arg1
+			{
+				title = "Green";
+				default = 255;
+			}
+			arg2
+			{
+				title = "Blue";
+				default = 255;
+			}			
+			arg3
+			{
+				title = "Radius";
+				default = 64;
+			}				
+		}
+	
+		9881
+		{
+			title = "Static Spotlight";
+			class = "StaticSpotLight"; // Fake class name
+			arg0
+			{
+				title = "Color";
+				default = 16777215;
+				str = true;
+			}
+			arg1
+			{
+				title = "Inner angle";
+				default = 8;
+			}
+			arg2
+			{
+				title = "Outer angle";
+				default = 32;
+			}
+			arg3
+			{
+				title = "Radius";
+				default = 64;
+			}				
+		}
+		
+		9890
+		{
+			title = "ZDRayInfo";
+			class = "ZDRaySun"; // Fake class name
+			adduniversalfields
+			{
+				lm_suncolor;
+				lm_sampledistance;
+				lm_gridsize;
+			}
+		}
+	}
+	
 	dynlights
 	{
 		color = 7;
diff --git a/Build/Configurations/Includes/Heretic_sectors.cfg b/Build/Configurations/Includes/Heretic_sectors.cfg
index a0d128376f82974ab7ee1c08899e22cdc9c13e83..1f899081076336d136bf1f18b5858f89cb7a7b51 100755
--- a/Build/Configurations/Includes/Heretic_sectors.cfg
+++ b/Build/Configurations/Includes/Heretic_sectors.cfg
@@ -1,16 +1,16 @@
 
 0 = "None";
 1 = "Light Blinks (randomly)";
-2 = "Light Blinks (1 sec.)";
-3 = "Light Blinks (0.5 sec.)";
+2 = "Light Blinks (0.5 sec.)";
+3 = "Light Blinks (1 sec.)";
 4 = "Damage (5 Points, Fast, Fire), Light Blinks (0.5s), Scroll East (Fast)";
 5 = "Damage (5 Points, Fast, Fire)";
 7 = "Damage (4 Points, Slow, Normal)";
 8 = "Light Glows (1.0s)";
 9 = "Secret";
 10 = "Door Close Stay (After 30s)";
-12 = "Light Blinks (0.5s Synchronized)";
-13 = "Light Blinks (1.0s Synchronized)";
+12 = "Light Blinks (1 sec. synchronized)";
+13 = "Light Blinks (0.5 sec. synchronized)";
 14 = "Door Open Stay (After 300s)";
 15 = "Friction";
 16 = "Damage (8 Points, Fast, Fire)";
diff --git a/Build/Configurations/Includes/MBF21_common.cfg b/Build/Configurations/Includes/MBF21_common.cfg
index 89fffb0e62a7ef8c58c2a3de5c2a64cf399e77db..10c4d69839a6ba710b78f9aaefcaff2d859e48b6 100644
--- a/Build/Configurations/Includes/MBF21_common.cfg
+++ b/Build/Configurations/Includes/MBF21_common.cfg
@@ -7,10 +7,14 @@ mapformat_doom
 
   linedefflags
   {
-
     include("MBF21_misc.cfg", "linedefflags");
   }
 
+  thingflags
+  {
+    include("MBF21_misc.cfg", "thingflags");
+  }
+
   linedeftypes
   {
     include("MBF21_linedefs.cfg");
diff --git a/Build/Configurations/Includes/MBF21_misc.cfg b/Build/Configurations/Includes/MBF21_misc.cfg
index 04dfd3ee5ac284ee98b296ea6634e18e9a99e22e..b3166d9e8bbf8767091c638334d1546ecc443699 100644
--- a/Build/Configurations/Includes/MBF21_misc.cfg
+++ b/Build/Configurations/Includes/MBF21_misc.cfg
@@ -4,3 +4,8 @@ linedefflags
   4096 = "Block land monsters";
   8192 = "Block players";
 }
+
+thingflags
+{
+	128 = "Friendly";
+}
diff --git a/Build/Configurations/Includes/Strife_sectors.cfg b/Build/Configurations/Includes/Strife_sectors.cfg
index 3a11186b32301289ecdf7d6ca195d7eef4537e90..b70293bfc482d70230a6f5bb41b482b128767b0d 100755
--- a/Build/Configurations/Includes/Strife_sectors.cfg
+++ b/Build/Configurations/Includes/Strife_sectors.cfg
@@ -1,7 +1,7 @@
 0 = "None";
 1 = "Light blinks (randomly)";
-2 = "Light blinks (1 sec.)";
-3 = "Light blinks (0.5 sec.)";
+2 = "Light blinks (0.5 sec.)";
+3 = "Light blinks (1 sec.)";
 4 = "5% damage every 32 tics + light blink 0.5 sec.";
 5 = "Damage: +2 to nukagecount";
 7 = "Damage 5% every 32 tics";
@@ -9,8 +9,8 @@
 9 = "Secret";
 10 = "Ceiling closes 30 seconds after level start";
 11 = "20% dmg. per sec. When player dies, level ends";
-12 = "Light blink 0.5 second, synchronized";
-13 = "Light blink 1 second, synchronized";
+12 = "Light blink 1 second, synchronized";
+13 = "Light blink 0.5 seconds, synchronized";
 14 = "Ceiling opens 5 minutes after level start";
 15 = "Instant death: 999% damage every tic";
 16 = "Damage: +4 to nukagecount";
diff --git a/Build/Configurations/Includes/ZDoom_common.cfg b/Build/Configurations/Includes/ZDoom_common.cfg
index 878143e32adad92d89cab3f765ab297e65ee4347..e17c01fa2c03358ea80b7060cfd80398d1af0afd 100755
--- a/Build/Configurations/Includes/ZDoom_common.cfg
+++ b/Build/Configurations/Includes/ZDoom_common.cfg
@@ -368,6 +368,12 @@ mapformat_udmf
 	distinctfloorandceilingbrightness = true;
 	distinctwallbrightness = true;
 	
+	// Enabled setting brightness for upper, middle, and lower sidedef independently from each other
+	distinctsidedefpartbrightness = true;
+	
+	// Enables multiple tags on sectors
+	sectormultitag = true;
+	
 	// Default nodebuilder configurations
 	defaultsavecompiler = "zdbsp_udmf_normal";
 	defaulttestcompiler = "zdbsp_udmf_fast";
diff --git a/Build/Configurations/Includes/ZDoom_misc.cfg b/Build/Configurations/Includes/ZDoom_misc.cfg
index 4e131511cdd269fa587356eb07275230fbb60187..b836cac9cae991097f8a5de293558cf5e5da053d 100755
--- a/Build/Configurations/Includes/ZDoom_misc.cfg
+++ b/Build/Configurations/Includes/ZDoom_misc.cfg
@@ -70,7 +70,7 @@ linedefactivations_udmf
 	monsterpush = "When monsters bumps";
 	anycross = "Any crossing non-missile activates";
 	missilecross = "When projectile crosses";
-	impact = "On projectile impact";
+	impact = "On player hitscan/projectile impact";
 	checkswitchrange
 	{
 		name = "Switch height check";
@@ -332,8 +332,25 @@ universalfields
 		
 		automapstyle
 		{
-			type = 0;
+			type = 11;
 			default = 0;
+			enum
+			{
+				0 = "Default";
+				1 = "One-sided wall";
+				2 = "Two-sided wall";
+				3 = "Floor levels of front and back sectors are different";
+				4 = "Ceiling levels of front and back sectors are different";
+				5 = "3D floor border";
+				6 = "Wall with special non-door action";
+				7 = "Secret door";
+				8 = "Wall not seen yet";
+				9 = "Locked door";
+				10 = "Intra-level teleporter";
+				11 = "Inter-level or game-ending teleporter";
+				12 = "Unexplored secret wall";
+				13 = "Portal line";
+			}
 		}
 		
 		arg0str
@@ -358,7 +375,31 @@ universalfields
 		{
 			type = 2;
 			default = "";
-		}		
+		}
+
+		lm_sampledist_line
+		{
+			type = 0;
+			default = 0;
+		}
+		
+		lm_sampledist_top
+		{
+			type = 0;
+			default = 0;
+		}
+		
+		lm_sampledist_mid
+		{
+			type = 0;
+			default = 0;
+		}
+
+		lm_sampledist_bot
+		{
+			type = 0;
+			default = 0;
+		}
 	}
 	
 	sidedef
@@ -451,7 +492,43 @@ universalfields
 		{
 			type = 1;
 			default = 1.0;
-		}		
+		}
+		
+		light_top
+		{
+			type = 0;
+			default = 0;
+		}
+		
+		lightabsolute_top
+		{
+			type = 3;
+			default = false;
+		}
+		
+		light_mid
+		{
+			type = 0;
+			default = 0;
+		}
+		
+		lightabsolute_mid
+		{
+			type = 3;
+			default = false;
+		}
+		
+		light_bottom
+		{
+			type = 0;
+			default = 0;
+		}
+		
+		lightabsolute_bottom
+		{
+			type = 3;
+			default = false;
+		}
 	}
 	
 	thing
@@ -659,6 +736,27 @@ universalfields
 			type = 1;
 			default = 1.0;
 		}
+
+		lm_suncolor
+		{
+			type = 10;
+			default = 16777215;
+			thingtypespecific = true;
+		}
+
+		lm_sampledistance
+		{
+			type = 0;
+			default = 8;
+			thingtypespecific = true;
+		}
+
+		lm_gridsize
+		{
+			type = 1;
+			default = 32.0;
+			thingtypespecific = true;
+		}
 	}
 	
 	sector
@@ -961,7 +1059,19 @@ universalfields
 		{
 			type = 3;
 			default = false;
-		}		
+		}
+		
+		lm_sampledist_floor
+		{
+			type = 0;
+			default = 0;
+		}
+		
+		lm_sampledist_ceiling
+		{
+			type = 0;
+			default = 0;
+		}
 	}
 }
 
@@ -1103,6 +1213,13 @@ udmfmaplumpnames
 		nodebuild = false;
 		scriptbuild = true;
 	}
+	
+	LIGHTMAP
+	{
+		required = false;
+		nodebuild = true;
+		blindcopy = true;		
+	}
 }
 
 enums
diff --git a/Build/Configurations/Includes/ZDoom_things.cfg b/Build/Configurations/Includes/ZDoom_things.cfg
index d80a3b209b9ac140635a0d0d600c3b0490c16570..32780fd18fd1e2cb169394e2f638efbf3efac34e 100755
--- a/Build/Configurations/Includes/ZDoom_things.cfg
+++ b/Build/Configurations/Includes/ZDoom_things.cfg
@@ -971,6 +971,42 @@ zdoom
 		fixedsize = true;
 		sprite = "internal:action";
 		
+		9600
+		{
+			title = "Sector Floor Damaged";
+			class = "SecActDamageFloor";
+		}
+		
+		9601
+		{
+			title = "Sector Ceiling Damaged";
+			class = "SecActDamageCeiling";
+		}
+		
+		9602
+		{
+			title = "Sector Floor Died";
+			class = "SecActDeathFloor";
+		}
+		
+		9603
+		{
+			title = "Sector Ceiling Died";
+			class = "SecActDeathCeiling";
+		}
+		
+		9604
+		{
+			title = "3D Sector Damaged";
+			class = "SecActDamage3D";
+		}
+		
+		9605
+		{
+			title = "3D Sector Died";
+			class = "SecActDeath3D";
+		}
+		
 		9082
 		{
 			title = "Silent Sector";
diff --git a/Build/Configurations/Includes/Zandronum_common.cfg b/Build/Configurations/Includes/Zandronum_common.cfg
index 862cb557daef2a83dbe859d7fb648768db3387e2..0029660c833700efa7c9e4e61a3efc2882e191c1 100755
--- a/Build/Configurations/Includes/Zandronum_common.cfg
+++ b/Build/Configurations/Includes/Zandronum_common.cfg
@@ -1,170 +1,185 @@
-// New things available in all games
-zandronum
+// ***********************************************************
+// *                                                         *
+// *                       Text map format                   *
+// *                                                         *
+// ***********************************************************
+// Contains the differences to the GZDoom UDMF settings
+mapformat_udmf
 {
-	//dynamic lights
-	include("GZDoom_things.cfg", "gzdoom_lights")
+	// Disables support for plane equation slopes
+	planeequationsupport = false;
+}
 
-	// Team player starts
-	players
+// New things available in all games
+things
+{
+	zandronum
 	{
-		color = 10;	// Light Green
-		arrow = 1;
-		title = "Player Starts";
-		width = 16;
-		sort = 1;
-		height = 56;
-		hangs = 0;
-		blocking = 2;
-		error = 2;
-		
-		5080
-		{
-			title = "Player Blue start (team start)";
-			sprite = "PLAYE2E8";
-		}
-		5081
-		{
-			title = "Player Red start (team start)";
-			sprite = "PLAYF2F8";
-		}
-		5082
-		{
-			title = "Player Temporary start (team start)";
-			sprite = "PLAYF1";
-		}
-	}
+		//dynamic lights
+		include("GZDoom_things.cfg", "gzdoom_lights")
 
-	flags
-	{
-		color = 13;	// Light Magenta
-		arrow = 0;
-		title = "Flags";
-		width = 20;
-		sort = 1;
-		height = 20;
-		hangs = 0;
-		blocking = 0;
-		
-		5130
-		{
-			title = "Blue flag";
-			sprite = "internal:ZandFlagBlue";
-		}
-		5131
-		{
-			title = "Red flag";
-			sprite = "internal:ZandFlagRed";
-		}
-		5132
-		{
-			title = "White flag";
-			sprite = "internal:ZandFlagWhite";
-		}
-		5133
-		{
-			title = "Green flag";
-			sprite = "internal:ZandFlagGreen";
+		// Team player starts
+		players
+		{
+			color = 10;	// Light Green
+			arrow = 1;
+			title = "Player Starts";
+			width = 16;
+			sort = 1;
+			height = 56;
+			hangs = 0;
+			blocking = 2;
+			error = 2;
+			
+			5080
+			{
+				title = "Player Blue start (team start)";
+				sprite = "PLAYE2E8";
+			}
+			5081
+			{
+				title = "Player Red start (team start)";
+				sprite = "PLAYF2F8";
+			}
+			5082
+			{
+				title = "Player Temporary start (team start)";
+				sprite = "PLAYF1";
+			}
 		}
-		5134
-		{
-			title = "Gold flag";
-			sprite = "internal:ZandFlagGold";
+
+		flags
+		{
+			color = 13;	// Light Magenta
+			arrow = 0;
+			title = "Flags";
+			width = 20;
+			sort = 1;
+			height = 20;
+			hangs = 0;
+			blocking = 0;
+			
+			5130
+			{
+				title = "Blue flag";
+				sprite = "internal:ZandFlagBlue";
+			}
+			5131
+			{
+				title = "Red flag";
+				sprite = "internal:ZandFlagRed";
+			}
+			5132
+			{
+				title = "White flag";
+				sprite = "internal:ZandFlagWhite";
+			}
+			5133
+			{
+				title = "Green flag";
+				sprite = "internal:ZandFlagGreen";
+			}
+			5134
+			{
+				title = "Gold flag";
+				sprite = "internal:ZandFlagGold";
+			}
 		}
 	}
-}
 
-// New things available in Doom, Heretic, Hexen and Strife but not Chex Quest
-doomheretichexenstrife
-{
-	flags
+	// New things available in Doom, Heretic, Hexen and Strife but not Chex Quest
+	doomheretichexenstrife
 	{
-		color = 13;	// Light Magenta
-		arrow = 0;
-		title = "Flags";
-		width = 20;
-		sort = 1;
-		height = 20;
-		hangs = 0;
-		blocking = 0;
-		
-    5025
-		{
-			title = "Red skull";
-			sprite = "RSKUB0";
-		}
-    5026
-		{
-			title = "Blue skull";
-			sprite = "BSKUB0";
+		flags
+		{
+			color = 13;	// Light Magenta
+			arrow = 0;
+			title = "Flags";
+			width = 20;
+			sort = 1;
+			height = 20;
+			hangs = 0;
+			blocking = 0;
+			
+		5025
+			{
+				title = "Red skull";
+				sprite = "RSKUB0";
+			}
+		5026
+			{
+				title = "Blue skull";
+				sprite = "BSKUB0";
+			}
 		}
 	}
-}
 
 
-// New things available in Doom, Heretic and Hexen; but not in Chex or Strife
-doomheretichexen
-{
-	// Zandronum runes
-	runes
+	// New things available in Doom, Heretic and Hexen; but not in Chex or Strife
+	doomheretichexen
 	{
-		color = 17;  // Light Orange
-		arrow = 0;
-		title = "Runes";
-		width = 20;
-		sort = 1;
-		height = 45;
-		hangs = 0;
-		blocking = 0;
-		
-		5100
-		{
-			title = "Strength";
-			sprite = "internal:ZandRuneStrength";
-		}
-		5101
-		{
-			title = "Rage";
-			sprite = "internal:ZandRuneRage";
-		}
-		5102
-		{
-			title = "Drain";
-			sprite = "internal:ZandRuneDrain";
-		}
-		5103
-		{
-			title = "Spread";
-			sprite = "internal:ZandRuneSpread";
-		}
-		5104
-		{
-			title = "Resistance";
-			sprite = "internal:ZandRuneResistance";
-		}
-		5105
-		{
-			title = "Regeneration";
-			sprite = "internal:ZandRuneRegeneration";
-		}
-		5106
-		{
-			title = "Prosperity";
-			sprite = "internal:ZandRuneProsperity";
-		}
-		5107
-		{
-			title = "Reflection";
-			sprite = "internal:ZandRuneReflection";
-		}
-		5108
-		{
-			title = "High Jumper";
-			sprite = "internal:ZandRuneHighJump";
-		}
-		5109
-		{
-			title = "Haste";
-			sprite = "internal:ZandRuneHaste";
+		// Zandronum runes
+		runes
+		{
+			color = 17;  // Light Orange
+			arrow = 0;
+			title = "Runes";
+			width = 20;
+			sort = 1;
+			height = 45;
+			hangs = 0;
+			blocking = 0;
+			
+			5100
+			{
+				title = "Strength";
+				sprite = "internal:ZandRuneStrength";
+			}
+			5101
+			{
+				title = "Rage";
+				sprite = "internal:ZandRuneRage";
+			}
+			5102
+			{
+				title = "Drain";
+				sprite = "internal:ZandRuneDrain";
+			}
+			5103
+			{
+				title = "Spread";
+				sprite = "internal:ZandRuneSpread";
+			}
+			5104
+			{
+				title = "Resistance";
+				sprite = "internal:ZandRuneResistance";
+			}
+			5105
+			{
+				title = "Regeneration";
+				sprite = "internal:ZandRuneRegeneration";
+			}
+			5106
+			{
+				title = "Prosperity";
+				sprite = "internal:ZandRuneProsperity";
+			}
+			5107
+			{
+				title = "Reflection";
+				sprite = "internal:ZandRuneReflection";
+			}
+			5108
+			{
+				title = "High Jumper";
+				sprite = "internal:ZandRuneHighJump";
+			}
+			5109
+			{
+				title = "Haste";
+				sprite = "internal:ZandRuneHaste";
+			}
 		}
 	}
 }
\ No newline at end of file
diff --git a/Build/Configurations/Includes/Zandronum_things.cfg b/Build/Configurations/Includes/Zandronum_things.cfg
index 4989a3e21a1908ede8ba8d4319243b92a7c2de4b..4b0116256d58bf33d10bc38aaa02f19ca2c38884 100755
--- a/Build/Configurations/Includes/Zandronum_things.cfg
+++ b/Build/Configurations/Includes/Zandronum_things.cfg
@@ -5,18 +5,18 @@
 
 doom
 {
-	include("Zandronum_common.cfg", "zandronum")
-	include("Zandronum_common.cfg", "doomheretichexenstrife")
-	include("Zandronum_common.cfg", "doomheretichexen")
+	include("Zandronum_common.cfg", "things.zandronum")
+	include("Zandronum_common.cfg", "things.doomheretichexenstrife")
+	include("Zandronum_common.cfg", "things.doomheretichexen")
 	include("Skulltag_things.cfg", "doomheretichexen")
 	include("Skulltag_things.cfg", "doom")
 }
 
 heretic
 {
-	include("Zandronum_common.cfg", "zandronum")
-	include("Zandronum_common.cfg", "doomheretichexenstrife")
-	include("Zandronum_common.cfg", "doomheretichexen")
+	include("Zandronum_common.cfg", "things.zandronum")
+	include("Zandronum_common.cfg", "things.doomheretichexenstrife")
+	include("Zandronum_common.cfg", "things.doomheretichexen")
 	include("Skulltag_things.cfg", "doomheretichexen")
 	include("Skulltag_things.cfg", "raven")
 	include("Skulltag_things.cfg", "heretic")
@@ -24,9 +24,9 @@ heretic
 
 hexen
 {
-	include("Zandronum_common.cfg", "zandronum")
-	include("Zandronum_common.cfg", "doomheretichexenstrife")
-	include("Zandronum_common.cfg", "doomheretichexen")
+	include("Zandronum_common.cfg", "things.zandronum")
+	include("Zandronum_common.cfg", "things.doomheretichexenstrife")
+	include("Zandronum_common.cfg", "things.doomheretichexen")
 	include("Skulltag_things.cfg", "doomheretichexen")
 	include("Skulltag_things.cfg", "raven")
 	include("Skulltag_things.cfg", "hexen")
@@ -34,13 +34,13 @@ hexen
 
 strife
 {
-	include("Zandronum_common.cfg", "zandronum")
-	include("Zandronum_common.cfg", "doomheretichexenstrife")
+	include("Zandronum_common.cfg", "things.zandronum")
+	include("Zandronum_common.cfg", "things.doomheretichexenstrife")
 	include("Skulltag_things.cfg", "strife")
 }
 
 default
 {
-	include("Zandronum_common.cfg", "zandronum")
+	include("Zandronum_common.cfg", "things.zandronum")
 	include("Skulltag_things.cfg", "skulltag")
 }
\ No newline at end of file
diff --git a/Build/Configurations/MBF21_Doom2Doom.cfg b/Build/Configurations/MBF21_Doom2Doom.cfg
index ef9fd62c00d5783f0e39738a3e8b05306e398a29..a46518de5b123c1053a2962ae910e877b500182a 100644
--- a/Build/Configurations/MBF21_Doom2Doom.cfg
+++ b/Build/Configurations/MBF21_Doom2Doom.cfg
@@ -12,7 +12,7 @@ game = "MBF21: Doom 2 (Doom format)";
 engine = "mbf21";
 
 // Should this configuration be initially available?
-enabledbydefault = false;
+enabledbydefault = true;
 
 // *******************************************************
 // *                                                     *
diff --git a/Build/Configurations/Zandronum_DoomUDMF.cfg b/Build/Configurations/Zandronum_DoomUDMF.cfg
index 818f2ca5cf73b1ad37fbfe1fec6d275bcf6f59f2..15eaea1a0700f25af66d620b1bb55b92c9e801a6 100755
--- a/Build/Configurations/Zandronum_DoomUDMF.cfg
+++ b/Build/Configurations/Zandronum_DoomUDMF.cfg
@@ -25,8 +25,13 @@ include("Includes\\ZDoom_common.cfg", "common");
 
 // Settings common to text map format
 include("Includes\\ZDoom_common.cfg", "mapformat_udmf");
+include("Includes\\Zandronum_common.cfg", "mapformat_udmf");
 include("Includes\\Zandronum_linedefs.cfg");
 
+// Zandronum doesn't support sectors with multiple tags. Defaults to false, but is set to true
+// by the GZDoom config, so we need to set it to false again
+sectormultitag = false;
+
 // Enables support for long (> 8 chars) texture names
 // WARNING: this should only be enabled for UDMF game configurations!
 // WARNING: enabling this will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3!
diff --git a/Build/Configurations/Zandronum_HereticUDMF.cfg b/Build/Configurations/Zandronum_HereticUDMF.cfg
index 16b043e5589a3e739399011d5e5e8d4517188a97..c52db9e0dfd95a9dd9b0e6ebf983786a3cd043ec 100755
--- a/Build/Configurations/Zandronum_HereticUDMF.cfg
+++ b/Build/Configurations/Zandronum_HereticUDMF.cfg
@@ -25,8 +25,13 @@ include("Includes\\ZDoom_common.cfg", "common");
 
 // Settings common to text map format
 include("Includes\\ZDoom_common.cfg", "mapformat_udmf");
+include("Includes\\Zandronum_common.cfg", "mapformat_udmf");
 include("Includes\\Zandronum_linedefs.cfg");
 
+// Zandronum doesn't support sectors with multiple tags. Defaults to false, but is set to true
+// by the GZDoom config, so we need to set it to false again
+sectormultitag = false;
+
 // Enables support for long (> 8 chars) texture names
 // WARNING: this should only be enabled for UDMF game configurations!
 // WARNING: enabling this will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3!
diff --git a/Build/Configurations/Zandronum_HexenUDMF.cfg b/Build/Configurations/Zandronum_HexenUDMF.cfg
index 49dac386459ae48bd07f5490d1b321a72392bf46..12b61d1238148240be99e872c70c0c678e784ef5 100755
--- a/Build/Configurations/Zandronum_HexenUDMF.cfg
+++ b/Build/Configurations/Zandronum_HexenUDMF.cfg
@@ -25,8 +25,13 @@ include("Includes\\ZDoom_common.cfg", "common");
 
 // Settings common to text map format
 include("Includes\\ZDoom_common.cfg", "mapformat_udmf");
+include("Includes\\Zandronum_common.cfg", "mapformat_udmf");
 include("Includes\\Zandronum_linedefs.cfg");
 
+// Zandronum doesn't support sectors with multiple tags. Defaults to false, but is set to true
+// by the GZDoom config, so we need to set it to false again
+sectormultitag = false;
+
 // Enables support for long (> 8 chars) texture names
 // WARNING: this should only be enabled for UDMF game configurations!
 // WARNING: enabling this will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3!
diff --git a/Build/Configurations/Zandronum_StrifeUDMF.cfg b/Build/Configurations/Zandronum_StrifeUDMF.cfg
index 8c198af838c82abf43641b49a9ffbf46768bfe73..6115fef63eb6e536f2d315368d2d48e525983e85 100755
--- a/Build/Configurations/Zandronum_StrifeUDMF.cfg
+++ b/Build/Configurations/Zandronum_StrifeUDMF.cfg
@@ -25,8 +25,13 @@ include("Includes\\ZDoom_common.cfg", "common");
 
 // Settings common to text map format
 include("Includes\\ZDoom_common.cfg", "mapformat_udmf");
+include("Includes\\Zandronum_common.cfg", "mapformat_udmf");
 include("Includes\\Zandronum_linedefs.cfg");
 
+// Zandronum doesn't support sectors with multiple tags. Defaults to false, but is set to true
+// by the GZDoom config, so we need to set it to false again
+sectormultitag = false;
+
 // Enables support for long (> 8 chars) texture names
 // WARNING: this should only be enabled for UDMF game configurations!
 // WARNING: enabling this will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3!
diff --git a/Build/Plugins/Dependencies/Esprima.dll b/Build/Plugins/Dependencies/Esprima.dll
index 95cff9d6a3d4a138878d40aa8a05829cf6a41178..8baa9ebf755a8adea21a09f289871254d367266c 100644
Binary files a/Build/Plugins/Dependencies/Esprima.dll and b/Build/Plugins/Dependencies/Esprima.dll differ
diff --git a/Build/Plugins/Dependencies/Jint.dll b/Build/Plugins/Dependencies/Jint.dll
index 18a4ba78d9f73d30c65c3f8f8b8cf11f8a040fd3..a39fb36f213c534ceed6fad5379ad366525381af 100644
Binary files a/Build/Plugins/Dependencies/Jint.dll and b/Build/Plugins/Dependencies/Jint.dll differ
diff --git a/Build/Setup/dotnetfx35setup.exe b/Build/Setup/dotnetfx35setup.exe
deleted file mode 100755
index 7fd35dc0938f9ebe293ea355e55cb552a48e5c38..0000000000000000000000000000000000000000
Binary files a/Build/Setup/dotnetfx35setup.exe and /dev/null differ
diff --git a/Build/Setup/vcredist_x86.exe b/Build/Setup/vcredist_x86.exe
deleted file mode 100755
index 378e7f03490d9be02e76f47b34c7af7ff1dc4cfa..0000000000000000000000000000000000000000
Binary files a/Build/Setup/vcredist_x86.exe and /dev/null differ
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/bevel.js b/Build/UDBScript/Scripts/Examples/Geometry/bevel.js
index 1bb22562f4873661f21fdc736d50a5d0f92b1b5e..6acfe1642c07ff61a9dc9809d2ed071756ed74f7 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/bevel.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/bevel.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 // Inspired by ribbiks's DBX Lua script: https://github.com/ribbiks/doom_lua/
 
 `#version 4`;
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/createlineportal.js b/Build/UDBScript/Scripts/Examples/Geometry/createlineportal.js
index 19c58df32ce8ffaa2f04d9d557e041ed329cb9e2..135d5383a2c02ff6f4842c07ebd206a9add947d8 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/createlineportal.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/createlineportal.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Create Line Portal`;
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/createpolyobject.js b/Build/UDBScript/Scripts/Examples/Geometry/createpolyobject.js
index 3c54749faf5d6c52485dccd18d1fd721419c5b2d..2bd499b424af0e3dd60e8e023bdcd39cc89bccb7 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/createpolyobject.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/createpolyobject.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Create PolyObject`;
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/flank.js b/Build/UDBScript/Scripts/Examples/Geometry/flank.js
index f1e4882b30e55e912541804f1bda68d46c806165..fbd3741455196577bb9213796a878793587d6e9a 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/flank.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/flank.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 // Inspired by ribbiks's DBX Lua script: https://github.com/ribbiks/doom_lua/
 
 `#version 4`;
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/jittervertices.js b/Build/UDBScript/Scripts/Examples/Geometry/jittervertices.js
index 3fcbf7970e9a5b1223b6a68474321985b2f4e26f..86b69df18e4d65672f24d1135bf2498db37c30e6 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/jittervertices.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/jittervertices.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Jitter Vertices`;
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/makedoor.js b/Build/UDBScript/Scripts/Examples/Geometry/makedoor.js
index 604a98fc850a12ddf276d9877f6b04655a1b4a42..edaed7e6ce4a92f4d838c207ac1851adb13d9911 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/makedoor.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/makedoor.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Make Door`;
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/triangulatesectors.js b/Build/UDBScript/Scripts/Examples/Geometry/triangulatesectors.js
index 4e08b4aa45da879804921b6eab3776d36fd715a0..a17f7a78b770b85028593270fae1722f995d622a 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/triangulatesectors.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/triangulatesectors.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 `#name Triangulate Sectors`;
 `#description Triangulates the selected or highlighted sectors into new sectors. Note that the triangulation will not "be beautiful", and that the sectors with islands may cause problems.`;
diff --git a/Build/UDBScript/Scripts/Examples/Geometry/voodoodollcloset.js b/Build/UDBScript/Scripts/Examples/Geometry/voodoodollcloset.js
index 41d51a56dc0f45130f1746cf64407dbb30ffee07..6f2765dd77c42deddbaa91a49ab1413ea2fe07e3 100644
--- a/Build/UDBScript/Scripts/Examples/Geometry/voodoodollcloset.js
+++ b/Build/UDBScript/Scripts/Examples/Geometry/voodoodollcloset.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Draw Voodoo Doll Closet`;
@@ -147,7 +149,7 @@ if(UDB.ScriptOptions.inactive)
 if(UDB.ScriptOptions.looping)
 {
 	// Create the teleport destination line
-	p.setAngle(90 * ScriptOptions.direction).moveTo(basepos)
+	p.setAngle(90 * UDB.ScriptOptions.direction).moveTo(basepos)
 	.moveForward(32).turnRight().moveForward(8).drawVertex()
 	.moveForward(closetwidth - 16).drawVertex();
 
diff --git a/Build/UDBScript/Scripts/Examples/Information/assertions.js b/Build/UDBScript/Scripts/Examples/Information/assertions.js
index a4a770963261c0ab6b86c2a9930e48b0b6351433..0078461ff72ea94f2914fc899680036d76ffd919 100644
--- a/Build/UDBScript/Scripts/Examples/Information/assertions.js
+++ b/Build/UDBScript/Scripts/Examples/Information/assertions.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Logic assertions`;
diff --git a/Build/UDBScript/Scripts/Examples/Information/mapsquareness.js b/Build/UDBScript/Scripts/Examples/Information/mapsquareness.js
index b4d3004ced30a67a59f205af29d0330cea57f806..e57be8d905ef0e9f4a1c6a1c64d0043d964a1c70 100644
--- a/Build/UDBScript/Scripts/Examples/Information/mapsquareness.js
+++ b/Build/UDBScript/Scripts/Examples/Information/mapsquareness.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Show Map Squareness`;
diff --git a/Build/UDBScript/Scripts/Examples/Information/sectorarea.js b/Build/UDBScript/Scripts/Examples/Information/sectorarea.js
index 5ba9ec627916785845fdaa682751e1fd51ce8bd9..5aa20d39c4afdcba05a787cf264393c73e7bee85 100644
--- a/Build/UDBScript/Scripts/Examples/Information/sectorarea.js
+++ b/Build/UDBScript/Scripts/Examples/Information/sectorarea.js
@@ -1,3 +1,5 @@
+/// <reference path="../../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Calculate sector area`;
diff --git a/Build/UDBScript/Scripts/Examples/applysectorsidedeftextures.js b/Build/UDBScript/Scripts/Examples/applysectorsidedeftextures.js
index c70b15f0957c030ff8c4129cf5dd702e1b4ee3bb..812b5930e7ce21232598607c43fde98475626cfc 100644
--- a/Build/UDBScript/Scripts/Examples/applysectorsidedeftextures.js
+++ b/Build/UDBScript/Scripts/Examples/applysectorsidedeftextures.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Apply textures to selected surfaces`;
diff --git a/Build/UDBScript/Scripts/Examples/deletesectortag.js b/Build/UDBScript/Scripts/Examples/deletesectortag.js
index ca5163d64a5f09400ea28ad6d9e4588dab310f84..7e7f58c77a865f40393c7c2ca40547e99051b4af 100644
--- a/Build/UDBScript/Scripts/Examples/deletesectortag.js
+++ b/Build/UDBScript/Scripts/Examples/deletesectortag.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Delete Sector Tag`;
diff --git a/Build/UDBScript/Scripts/Examples/fliptriangularsectors.js b/Build/UDBScript/Scripts/Examples/fliptriangularsectors.js
index 2fd180f055b78f3882c607ce6997ee59e0b5069f..3e96297fceef92d0a41feff66fb381ad7d41816b 100644
--- a/Build/UDBScript/Scripts/Examples/fliptriangularsectors.js
+++ b/Build/UDBScript/Scripts/Examples/fliptriangularsectors.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Flip Triangular Sectors`;
diff --git a/Build/UDBScript/Scripts/Examples/imps2archviles.js b/Build/UDBScript/Scripts/Examples/imps2archviles.js
index 3e6b6b895101f4af6dcf77cf7a3c574947b04d54..081a64f3c62f430fd3b2b7aec06bd7410ca9f4e9 100644
--- a/Build/UDBScript/Scripts/Examples/imps2archviles.js
+++ b/Build/UDBScript/Scripts/Examples/imps2archviles.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Imps to Arch-Viles`;
diff --git a/Build/UDBScript/Scripts/Examples/randomizeselectionorder.js b/Build/UDBScript/Scripts/Examples/randomizeselectionorder.js
index ab5e3585c949570f34e259ef6b3b7830d4b367a3..59932aba0996f782031ac11d4512390bbac7858c 100644
--- a/Build/UDBScript/Scripts/Examples/randomizeselectionorder.js
+++ b/Build/UDBScript/Scripts/Examples/randomizeselectionorder.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Randomize Selection Order`;
diff --git a/Build/UDBScript/Scripts/Examples/randomizetextureoffsets.js b/Build/UDBScript/Scripts/Examples/randomizetextureoffsets.js
index 421c7f05d7587e461e5329b5e5fe4c03a8b05422..8bab49035c4c359f6fd9e130be28a13afc437efe 100644
--- a/Build/UDBScript/Scripts/Examples/randomizetextureoffsets.js
+++ b/Build/UDBScript/Scripts/Examples/randomizetextureoffsets.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Randomize Texture Offsets`;
diff --git a/Build/UDBScript/Scripts/Examples/reorderthingsindices.js b/Build/UDBScript/Scripts/Examples/reorderthingsindices.js
index 6efca12eeec3c67ce9cd77dd35cf67aad4574eca..607413b0b59f2499831a86240dfc9b0ff93ae4a0 100644
--- a/Build/UDBScript/Scripts/Examples/reorderthingsindices.js
+++ b/Build/UDBScript/Scripts/Examples/reorderthingsindices.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Reorder Things Indices`;
diff --git a/Build/UDBScript/Scripts/Examples/selectconnectedlines.js b/Build/UDBScript/Scripts/Examples/selectconnectedlines.js
index 7428d1291b7b82242f2ca4a684017c2c1edcf03f..67f3fa7064b64d921598969f5c2748c024f8208d 100644
--- a/Build/UDBScript/Scripts/Examples/selectconnectedlines.js
+++ b/Build/UDBScript/Scripts/Examples/selectconnectedlines.js
@@ -1,3 +1,5 @@
+/// <reference path="../../udbscript.d.ts" />
+
 `#version 4`;
 
 `#name Select Connected Linedefs`;
diff --git a/Build/UDBScript/udbscript.d.ts b/Build/UDBScript/udbscript.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1bf18b506dbc2c5a08acbd4350bd68e0ab1705ae
--- /dev/null
+++ b/Build/UDBScript/udbscript.d.ts
@@ -0,0 +1,1735 @@
+declare namespace UDB {
+	namespace GameConfiguration {
+		/**
+		 * Engine name, like `doom`, `boom`, `zdoom` etc. Used for the namespace in UDMF maps. Read-only.
+		 */
+		let engineName: string;
+		/**
+		 * If the game configuration supports local sidedef texture offsets (distinct offsets for upper, middle, and lower sidedef parts).
+		 */
+		let hasLocalSidedefTextureOffsets: boolean;
+	}
+	namespace Angle2D {
+		/**
+		 * Converts a Doom angle (where 0° is east) to a real world angle (where 0° is north).
+		 * @param doomangle Doom angle in degrees
+		 * @returns Doom angle in degrees
+		 */
+		function doomToReal(doomangle: number): number;
+		/**
+		 * Converts a Doom angle (where 0° is east) to a real world angle (where 0° is north) in radians.
+		 * @param doomangle Doom angle in degrees
+		 * @returns Doom angle in radians
+		 */
+		function doomToRealRad(doomangle: number): number;
+		/**
+		 * Converts a real world angle (where 0° is north) to a Doom angle (where 0° is east).
+		 * @param realangle Real world angle in degrees
+		 * @returns Doom angle in degrees
+		 */
+		function realToDoom(realangle: number): number;
+		/**
+		 * Converts a real world angle (where 0° is north) to a Doom angle (where 0° is east) in radians.
+		 * @param realangle Real world angle in radians
+		 * @returns Doom angle in degrees
+		 */
+		function realToDoomRad(realangle: number): number;
+		/**
+		 * Converts radians to degrees.
+		 * @param rad Angle in radians
+		 * @returns Angle in degrees
+		 */
+		function radToDeg(rad: number): number;
+		/**
+		 * Converts degrees to radians.
+		 * @param deg Angle in degrees
+		 * @returns Angle in radians
+		 */
+		function degToRad(deg: number): number;
+		/**
+		 * Normalizes an angle in degrees so that it is bigger or equal to 0° and smaller than 360°.
+		 * @param angle Angle in degrees
+		 * @returns Normalized angle in degrees
+		 */
+		function normalized(angle: number): number;
+		/**
+		 * Normalizes an angle in radians so that it is bigger or equal to 0 and smaller than 2 Pi.
+		 * @param angle Angle in radians
+		 * @returns Normalized angle in radians
+		 */
+		function normalizedRad(angle: number): number;
+		/**
+		 * Returns the angle between three positions.
+		 * @param p1 First position
+		 * @param p2 Second position
+		 * @param p3 Third position
+		 * @returns Angle in degrees
+		 */
+		function getAngle(p1: any, p2: any, p3: any): number;
+		/**
+		 * Returns the angle between three positions in radians.
+		 * @param p1 First position
+		 * @param p2 Second position
+		 * @param p3 Third position
+		 * @returns Angle in radians
+		 */
+		function getAngleRad(p1: any, p2: any, p3: any): number;
+	}
+	class BlockEntry {
+		/**
+		 * Gets all `Linedef`s in the blockmap entry.
+		 * @returns `Array` of `Linedef`s
+		 */
+		getLinedefs(): Linedef[];
+		/**
+		 * Gets all `Thing`s in the blockmap entry.
+		 * @returns `Array` of `Thing`s
+		 */
+		getThings(): Thing[];
+		/**
+		 * Gets all `Sector`s in the blockmap entry.
+		 * @returns `Array` of `Sector`s
+		 */
+		getSectors(): Sector[];
+		/**
+		 * Gets all `Vertex` in the blockmap entry.
+		 * @returns `Array` of `Vertex`
+		 */
+		getVertices(): Vertex[];
+	}
+	class BlockMapQueryResult {
+		/**
+		 * Gets all `Linedef`s in the blockmap query result.
+		 * @returns `Array` of `Linedef`s
+		 */
+		getLinedefs(): Linedef[];
+		/**
+		 * Gets all `Thing`s in the blockmap query result.
+		 * @returns `Array` of `Thing`s
+		 */
+		getThings(): Thing[];
+		/**
+		 * Gets all `Sector`s in the blockmap query result.
+		 * @returns `Array` of `Sector`s
+		 */
+		getSectors(): Sector[];
+		/**
+		 * Gets all `Vertex` in the blockmap query result.
+		 * @returns `Array` of `Vertex`
+		 */
+		getVertices(): Vertex[];
+	}
+	class BlockMap {
+		/**
+		 * Creates a blockmap that includes linedefs, things, sectors, and vertices.
+		 */
+		constructor();
+		/**
+		 * Creates a blockmap that only includes certain map element types.
+		 * @param lines If linedefs should be added or not
+		 * @param things If thigs should be added or not
+		 * @param sectors If sectors should be added or not
+		 * @param vertices If vertices should be added or not
+		 */
+		constructor(lines: boolean, things: boolean, sectors: boolean, vertices: boolean);
+		/**
+		 * Gets the `BlockEntry` at a point. The given point can be a `Vector2D` or an `Array` of two numbers.
+		 * @param pos The point to get the `BlockEntry` of
+		 * @returns The `BlockEntry` on the given point
+		 */
+		getBlockAt(pos: any): BlockEntry;
+		/**
+		 * Gets a `BlockMapQueryResult` for the blockmap along a line between two points. The given points can be `Vector2D`s or an `Array`s of two numbers.
+		 * @param v1 The first point
+		 * @param v2 The second point
+		 * @returns The `BlockMapQueryResult` for the line between the two points
+		 */
+		getLineBlocks(v1: any, v2: any): BlockMapQueryResult;
+		/**
+		 * Gets a `BlockMapQueryResult` for the blockmap in a rectangle.
+		 * @param x X position of the top-left corner of the rectangle
+		 * @param y Y position of the top-left corner of the rectangle
+		 * @param width Width of the rectangle
+		 * @param height Height of the rectangle
+		 * @returns None
+		 */
+		getRectangleBlocks(x: number, y: number, width: number, height: number): BlockMapQueryResult;
+	}
+	namespace Data {
+		/**
+		 * Returns an `Array` of all texture names.
+		 * @returns `Array` of all texture names
+		 */
+		function getTextureNames(): string[];
+		/**
+		 * Checks if a texture with the given name exists.
+		 * @param name Texture name to check
+		 * @returns `true` if the texture exists, `false` if it doesn't
+		 */
+		function textureExists(name: string): boolean;
+		/**
+		 * Returns an `ImageInfo` object for the given texture name.
+		 * @param name Texture name to get the info for
+		 * @returns `ImageInfo` object containing information about the texture
+		 */
+		function getTextureInfo(name: string): ImageInfo;
+		/**
+		 * Returns an `Array`of all flat names.
+		 * @returns `Array` of all flat names
+		 */
+		function getFlatNames(): string[];
+		/**
+		 * Checks if a flat with the given name exists.
+		 * @param name Flat name to check
+		 * @returns `true` if the flat exists, `false` if it doesn't
+		 */
+		function flatExists(name: string): boolean;
+		/**
+		 * Returns an `ImageInfo` object for the given flat name.
+		 * @param name Flat name to get the info for
+		 * @returns `ImageInfo` object containing information about the flat
+		 */
+		function getFlatInfo(name: string): ImageInfo;
+	}
+	class ImageInfo {
+		/**
+		 * Name of the image.
+		 */
+		name: string;
+		/**
+		 * Width of the image.
+		 */
+		width: number;
+		/**
+		 * Height of the image.
+		 */
+		height: number;
+		/**
+		 * Scale of the image as `Vector2D`.
+		 */
+		scale: Vector2D;
+		/**
+		 * If the image is a flat (`true`) or not (`false`).
+		 */
+		isFlat: boolean;
+	}
+	class Line2D {
+		/**
+		 * Creates a new `Line2D` from two points.
+		 * @param v1 First point
+		 * @param v2 Second point
+		 */
+		constructor(v1: any, v2: any);
+		/**
+		 * Returns the coordinates on the line, where `u` is the position between the first and second point, `u = 0.0` being on the first point, `u = 1.0` being on the second point, and `u = 0.5` being in the middle between the points.
+		 * @param u Position on the line, between 0.0 and 1.0
+		 * @returns Position on the line as `Vector2D`
+		 */
+		getCoordinatesAt(u: number): Vector2D;
+		/**
+		 * Returns the length of the `Line2D`.
+		 * @returns Length of the `Line2D`
+		 */
+		getLength(): number;
+		/**
+		 * Returns the angle of the `Line2D` in radians.
+		 * @returns Angle of `Line2D` in radians
+		 */
+		getAngleRad(): number;
+		/**
+		 * Return the angle of the `Line2D` in degrees.
+		 * @returns Angle of the `Line2D` in degrees
+		 */
+		getAngle(): number;
+		/**
+		 * Returns the perpendicular of this line as `Vector2D`.
+		 * @returns Perpendicular of this line as `Vector2D`
+		 */
+		getPerpendicular(): Vector2D;
+		/**
+		 * Checks if the given `Line2D` intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param ray `Line2D` to check against
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns `true` if lines intersect, `false` if they do not intersect
+		 */
+		isIntersecting(ray: Line2D, bounded: boolean): boolean;
+		/**
+		 * Checks if the given line intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param a1 First point of the line to check against
+		 * @param a2 Second point of the line to check against
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns `true` if the lines intersect, `false` if they do not
+		 */
+		isIntersecting(a1: any, a2: any, bounded: boolean): boolean;
+		/**
+		 * Returns the intersection point of of the given line defined by its start and end points with this line as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param a1 First point of first line
+		 * @param a2 Second point of first line
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns The intersection point as `Vector2D`
+		 */
+		getIntersectionPoint(a1: any, a2: any, bounded: boolean): Vector2D;
+		/**
+		 * Returns the intersection point of of the given line with this line as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param ray Other `Line2D` to get the intersection point from
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns The intersection point as `Vector2D`
+		 */
+		getIntersectionPoint(ray: Line2D, bounded: boolean): Vector2D;
+		/**
+		 * Returns which the of the line defined by its start and end point a given point is on.
+		 * @param p Point to check
+		 * @returns `< 0` if `p` is on the front (right) side, `> 0` if `p` is on the back (left) side, `== 0` if `p` in on the line
+		 */
+		getSideOfLine(p: any): number;
+		/**
+		 * `Vector2D` position of start of the line.
+		 */
+		v1: Vector2D;
+		/**
+		 * `Vector2D` position of end of the line.
+		 */
+		v2: Vector2D;
+	}
+	namespace Line2D {
+		/**
+		 * Checks if two lines intersect. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param line1 First `Line2D`
+		 * @param line2 Second `Line2D`
+		 * @param bounded `true` to use finite length of lines, `false` to use infinite length of lines
+		 * @returns `true` if the lines intersect, `false` if they do not
+		 */
+		function areIntersecting(line1: Line2D, line2: Line2D, bounded: boolean): boolean;
+		/**
+		 * Checks if two lines defined by their start and end points intersect. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param a1 First point of first line
+		 * @param a2 Second point of first line
+		 * @param b1 First point of second line
+		 * @param b2 Second point of second line
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns `true` if the lines intersect, `false` if they do not
+		 */
+		function areIntersecting(a1: any, a2: any, b1: any, b2: any, bounded: boolean): boolean;
+		/**
+		 * Returns the intersection point of two lines as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param a1 First point of first line
+		 * @param a2 Second point of first line
+		 * @param b1 First point of second line
+		 * @param b2 Second point of second line
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns The intersection point as `Vector2D`
+		 */
+		function getIntersectionPoint(a1: any, a2: any, b1: any, b2: any, bounded: boolean): Vector2D;
+		/**
+		 * Returns which the of the line defined by its start and end point a given point is on.
+		 * @param v1 First point of the line
+		 * @param v2 Second point of the line
+		 * @param p Point to check
+		 * @returns `< 0` if `p` is on the front (right) side, `> 0` if `p` is on the back (left) side, `== 0` if `p` in on the line
+		 */
+		function getSideOfLine(v1: any, v2: any, p: any): number;
+		/**
+		 * Returns the shortest distance from point `p` to the line defined by its start and end points. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param v1 First point of the line
+		 * @param v2 Second point of the line
+		 * @param p Point to get the distance to
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns The shortest distance to the line
+		 */
+		function getDistanceToLine(v1: any, v2: any, p: any, bounded: boolean): number;
+		/**
+		 * Returns the shortest square distance from point `p` to the line defined by its start and end points. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+		 * @param v1 First point of the line
+		 * @param v2 Second point of the line
+		 * @param p Point to get the distance to
+		 * @param bounded `true` (default) to use finite length of lines, `false` to use infinite length of lines
+		 * @returns The shortest square distance to the line
+		 */
+		function getDistanceToLineSq(v1: any, v2: any, p: any, bounded: boolean): number;
+		/**
+		 * Returns the offset coordinate on the line nearest to the given point. `0.0` being on the first point, `1.0` being on the second point, and `u = 0.5` being in the middle between the points.
+		 * @param v1 First point of the line
+		 * @param v2 Second point of the line
+		 * @param p Point to get the nearest offset coordinate from
+		 * @returns The offset value relative to the first point of the line.
+		 */
+		function getNearestOnLine(v1: any, v2: any, p: any): number;
+		/**
+		 * Returns the coordinate on a line defined by its start and end points as `Vector2D`.
+		 * @param v1 First point of the line
+		 * @param v2 Second point of the line
+		 * @param u Offset coordinate relative to the first point of the line
+		 * @returns Point on the line as `Vector2D`
+		 */
+		function getCoordinatesAt(v1: any, v2: any, u: number): Vector2D;
+	}
+	class Linedef {
+		/**
+		 * Copies the properties of this `Linedef` to another `Linedef`.
+		 * @param other The `Linedef` to copy the properties to
+		 */
+		copyPropertiesTo(other: Linedef): void;
+		/**
+		 * Clears all flags.
+		 */
+		clearFlags(): void;
+		/**
+		 * Flips the `Linedef`'s vertex attachments.
+		 */
+		flipVertices(): void;
+		/**
+		 * Flips the `Linedef`'s `Sidedef`s.
+		 */
+		flipSidedefs(): void;
+		/**
+		 * Flips the `Linedef`'s vertex attachments and `Sidedef`s. This is a shortcut to using both `flipVertices()` and `flipSidedefs()`.
+		 */
+		flip(): void;
+		/**
+		 * Gets a `Vector2D` for testing on one side. The `Vector2D` is on the front when `true` is passed, otherwise on the back.
+		 * @param front `true` for front, `false` for back
+		 * @returns `Vector2D` that's either on the front of back of the Linedef
+		 */
+		getSidePoint(front: boolean): Vector2D;
+		/**
+		 * Gets a `Vector2D` that's in the center of the `Linedef`.
+		 * @returns `Vector2D` in the center of the `Linedef`
+		 */
+		getCenterPoint(): Vector2D;
+		/**
+		 * Automatically sets the blocking and two-sided flags based on the existing `Sidedef`s.
+		 */
+		applySidedFlags(): void;
+		/**
+		 * Get a `Vector2D` that's *on* the line, closest to `pos`. `pos` can either be a `Vector2D`, or an array of numbers.
+		 * @param pos Point to check against
+		 * @returns `Vector2D` that's on the linedef
+		 */
+		nearestOnLine(pos: any): Vector2D;
+		/**
+		 * Gets the shortest "safe" squared distance from `pos` to the line. If `bounded` is `true` that means that the not the whole line's length will be used, but `lengthInv` less at the start and end.
+		 * @param pos Point to check against
+		 * @param bounded `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used
+		 * @returns Squared distance to the line
+		 */
+		safeDistanceToSq(pos: any, bounded: boolean): number;
+		/**
+		 * Gets the shortest "safe" distance from `pos` to the line. If `bounded` is `true` that means that the not the whole line's length will be used, but `lengthInv` less at the start and end.
+		 * @param pos Point to check against
+		 * @param bounded `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used
+		 * @returns Distance to the line
+		 */
+		safeDistanceTo(pos: any, bounded: boolean): number;
+		/**
+		 * Gets the shortest squared distance from `pos` to the line.
+		 * @param pos Point to check against
+		 * @param bounded `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used
+		 * @returns Squared distance to the line
+		 */
+		distanceToSq(pos: any, bounded: boolean): number;
+		/**
+		 * Gets the shortest distance from `pos` to the line.
+		 * @param pos Point to check against
+		 * @param bounded `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used
+		 * @returns Distance to the line
+		 */
+		distanceTo(pos: any, bounded: boolean): number;
+		/**
+		 * Tests which side of the `Linedef` `pos` is on. Returns < 0 for front (right) side, > for back (left) side, and 0 if `pos` is on the line.
+		 * @param pos Point to check against
+		 * @returns < 0 for front (right) side, > for back (left) side, and 0 if `pos` is on the line
+		 */
+		sideOfLine(pos: any): number;
+		/**
+		 * Splits the `Linedef` at the given position. This can either be a `Vector2D`, an array of numbers, or an existing `Vertex`. The result will be two lines, from the start `Vertex` of the `Linedef` to `pos`, and from `pos` to the end `Vertex` of the `Linedef`.
+		 * @param pos `Vertex` to split by
+		 * @returns The newly created `Linedef`
+		 */
+		split(pos: any): Linedef;
+		/**
+		 * Deletes the `Linedef`. Note that this will result in unclosed `Sector`s unless it has the same `Sector`s on both sides.
+		 */
+		delete(): void;
+		/**
+		 * Returns an `Array` of the `Linedef`'s tags. UDMF only. Supported game configurations only.
+		 * @returns `Array` of tags
+		 */
+		getTags(): number[];
+		/**
+		 * Adds a tag to the `Linedef`. UDMF only. Supported game configurations only.
+		 * @param tag Tag to add
+		 * @returns `true` when the tag was added, `false` when the tag already exists
+		 */
+		addTag(tag: number): boolean;
+		/**
+		 * Removes a tag from the `Linedef`. UDMF only. Supported game configurations only.
+		 * @param tag Tag to remove
+		 * @returns `true` when the tag was removed successfully, `false` when the tag did not exist
+		 */
+		removeTag(tag: number): boolean;
+		/**
+		 * The linedef's index. Read-only.
+		 */
+		index: number;
+		/**
+		 * The linedef's start `Vertex`.
+		 */
+		start: Vertex;
+		/**
+		 * The linedef's end `Vertex`.
+		 */
+		end: Vertex;
+		/**
+		 * The `Linedef`'s front `Sidedef`. Is `null` when there is no front (should not happen).
+		 */
+		front: Sidedef;
+		/**
+		 * The `Linedef`'s back `Sidedef`. Is `null` when there is no back.
+		 */
+		back: Sidedef;
+		/**
+		 * The `Line2D` from the `start` to the `end` `Vertex`.
+		 */
+		line: Line2D;
+		/**
+		 * If the `Linedef` is selected or not.
+		 */
+		selected: boolean;
+		/**
+		 * If the `Linedef` is marked or not. It is used to mark map elements that were created or changed (for example after drawing new geometry).
+		 */
+		marked: boolean;
+		/**
+		 * The activation flag. Hexen format only.
+		 */
+		activate: number;
+		/**
+		 * `Linedef` flags. It's an object with the flags as properties. In Doom format and Hexen format they are identified by numbers, in UDMF by their name.
+		 */
+		flags: any;
+		/**
+		 * `Array` of arguments of the `Linedef`. Number of arguments depends on game config (usually 5). Hexen format and UDMF only.
+		 */
+		args: number[];
+		/**
+		 * `Linedef` action.
+		 */
+		action: number;
+		/**
+		 * `Linedef` tag. UDMF only.
+		 */
+		tag: number;
+		/**
+		 * The `Linedef`'s squared length. Read-only.
+		 */
+		lengthSq: number;
+		/**
+		 * The `Linedef`'s length. Read-only.
+		 */
+		length: number;
+		/**
+		 * 1.0 / length. Read-only.
+		 */
+		lengthInv: number;
+		/**
+		 * The `Linedef`'s angle in degree. Read-only.
+		 */
+		angle: number;
+		/**
+		 * The `Linedef`'s angle in radians. Read-only.
+		 */
+		angleRad: number;
+		/**
+		 * UDMF fields. It's an object with the fields as properties.
+		 */
+		fields: any;
+	}
+	namespace Map {
+		/**
+		 * Returns the given point snapped to the current grid.
+		 * @param pos Point that should be snapped to the grid
+		 * @returns Snapped position as `Vector2D`
+		 */
+		function snappedToGrid(pos: any): Vector2D;
+		/**
+		 * Returns an `Array` of all `Thing`s in the map.
+		 * @returns `Array` of `Thing`s
+		 */
+		function getThings(): Thing[];
+		/**
+		 * Returns an `Array` of all `Sector`s in the map.
+		 * @returns `Array` of `Sector`s
+		 */
+		function getSectors(): Sector[];
+		/**
+		 * Returns an `Array` of all `Sidedef`s in the map.
+		 * @returns `Array` of `Sidedef`s
+		 */
+		function getSidedefs(): Sidedef[];
+		/**
+		 * Returns an `Array` of all `Linedef`s in the map.
+		 * @returns `Array` of `Linedef`s
+		 */
+		function getLinedefs(): Linedef[];
+		/**
+		 * Returns an `Array` of all `Vertex` in the map.
+		 * @returns `Array` of `Vertex`
+		 */
+		function getVertices(): Vertex[];
+		/**
+		 * Stitches marked geometry with non-marked geometry.
+		 * @param mergemode Mode to merge by as `MergeGeometryMode`
+		 * @returns `true` if successful, `false` if failed
+		 */
+		function stitchGeometry(mergemode: MergeGeometryMode): boolean;
+		/**
+		 * Snaps all vertices and things to the map format accuracy. Call this to ensure the vertices and things are at valid coordinates.
+		 * @param usepreciseposition `true` if decimal places defined by the map format should be used, `false` if no decimal places should be used
+		 */
+		function snapAllToAccuracy(usepreciseposition: boolean): void;
+		/**
+		 * Gets a new tag.
+		 * @param usedtags `Array` of tags to skip
+		 * @returns The new tag
+		 */
+		function getNewTag(usedtags: number[]): number;
+		/**
+		 * Gets multiple new tags.
+		 * @param count Number of tags to get
+		 * @returns `Array` of the new tags
+		 */
+		function getMultipleNewTags(count: number): number[];
+		/**
+		 * Gets the `Linedef` that's nearest to the specified position.
+		 * @param pos Position to check against
+		 * @param maxrange Maximum range (optional)
+		 * @returns Nearest `Linedef`
+		 */
+		function nearestLinedef(pos: any, maxrange: number): Linedef;
+		/**
+		 * Gets the `Thing` that's nearest to the specified position.
+		 * @param pos Position to check against
+		 * @param maxrange Maximum range (optional)
+		 * @returns Nearest `Linedef`
+		 */
+		function nearestThing(pos: any, maxrange: number): Thing;
+		/**
+		 * Gets the `Vertex` that's nearest to the specified position.
+		 * @param pos Position to check against
+		 * @param maxrange Maximum range (optional)
+		 * @returns Nearest `Vertex`
+		 */
+		function nearestVertex(pos: any, maxrange: number): Vertex;
+		/**
+		 * Gets the `Sidedef` that's nearest to the specified position.
+		 * @param pos Position to check against
+		 * @returns Nearest `Sidedef`
+		 */
+		function nearestSidedef(pos: any): Sidedef;
+		/**
+		 * Draws lines. Data has to be an `Array` of `Array` of numbers, `Vector2D`s, `Vector3D`s, or objects with x and y properties. Note that the first and last element have to be at the same positions to make a complete drawing.
+		 * @param data `Array` of positions
+		 * @returns `true` if drawing was successful, `false` if it wasn't
+		 */
+		function drawLines(data: any): boolean;
+		/**
+		 * Sets the `marked` property of all map elements. Can be passed `true` to mark all map elements.
+		 * @param mark `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
+		 */
+		function clearAllMarks(mark: boolean): void;
+		/**
+		 * Sets the `marked` property of all vertices. Can be passed `true` to mark all vertices.
+		 * @param mark `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
+		 */
+		function clearMarkedVertices(mark: boolean): void;
+		/**
+		 * Sets the `marked` property of all `Thing`s. Can be passed `true` to mark all `Thing`s.
+		 * @param mark `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
+		 */
+		function clearMarkedThings(mark: boolean): void;
+		/**
+		 * Sets the `marked` property of all `Linedef`s. Can be passed `true` to mark all `Linedef`s.
+		 * @param mark `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
+		 */
+		function clearMarkeLinedefs(mark: boolean): void;
+		/**
+		 * Sets the `marked` property of all `Sidedef`s. Can be passed `true` to mark all `Sidedef`s.
+		 * @param mark `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
+		 */
+		function clearMarkeSidedefs(mark: boolean): void;
+		/**
+		 * Sets the `marked` property of all `Sector`s. Can be passed `true` to mark all `Sector`s.
+		 * @param mark `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
+		 */
+		function clearMarkeSectors(mark: boolean): void;
+		/**
+		 * Inverts all marks of all map elements.
+		 */
+		function invertAllMarks(): void;
+		/**
+		 * Inverts the `marked` property of all vertices.
+		 */
+		function invertMarkedVertices(): void;
+		/**
+		 * Inverts the `marked` property of all `Thing`s.
+		 */
+		function invertMarkedThings(): void;
+		/**
+		 * Inverts the `marked` property of all `Linedef`s.
+		 */
+		function invertMarkedLinedefs(): void;
+		/**
+		 * Inverts the `marked` property of all `Sidedef`s.
+		 */
+		function invertMarkedSidedefs(): void;
+		/**
+		 * Inverts the `marked` property of all `Sector`s.
+		 */
+		function invertMarkedSectors(): void;
+		/**
+		 * Gets all marked (default) or unmarked vertices.
+		 * @param mark `true` to get all marked vertices (default), `false` to get all unmarked vertices
+		 * @returns None
+		 */
+		function getMarkedVertices(mark: boolean): Vertex[];
+		/**
+		 * Gets all marked (default) or unmarked `Thing`s.
+		 * @param mark `true` to get all marked `Thing`s (default), `false` to get all unmarked `Thing`s
+		 * @returns None
+		 */
+		function getMarkedThings(mark: boolean): Thing[];
+		/**
+		 * Gets all marked (default) or unmarked `Linedef`s.
+		 * @param mark `true` to get all marked `Linedef`s (default), `false` to get all unmarked `Linedef`s
+		 * @returns None
+		 */
+		function getMarkedLinedefs(mark: boolean): Linedef[];
+		/**
+		 * Gets all marked (default) or unmarked `Sidedef`s.
+		 * @param mark `true` to get all marked `Sidedef`s (default), `false` to get all unmarked `Sidedef`s
+		 * @returns None
+		 */
+		function getMarkedSidedefs(mark: boolean): Sidedef[];
+		/**
+		 * Gets all marked (default) or unmarked `Sector`s.
+		 * @param mark `true` to get all marked `Sector`s (default), `false` to get all unmarked `Sector`s
+		 * @returns None
+		 */
+		function getMarkedSectors(mark: boolean): Sector[];
+		/**
+		 * Marks (default) or unmarks all selected vertices.
+		 * @param mark `true` to mark all selected vertices (default), `false` to unmark
+		 */
+		function markSelectedVertices(mark: boolean): void;
+		/**
+		 * Marks (default) or unmarks all selected `Linedef`s.
+		 * @param mark `true` to mark all selected `Linedef`s (default), `false` to unmark
+		 */
+		function markSelectedLinedefs(mark: boolean): void;
+		/**
+		 * Marks (default) or unmarks all selected `Sector`s.
+		 * @param mark `true` to mark all selected `Sector`s (default), `false` to unmark
+		 */
+		function markSelectedSectors(mark: boolean): void;
+		/**
+		 * Marks (default) or unmarks all selected `Thing`s.
+		 * @param mark `true` to mark all selected `Thing`s (default), `false` to unmark
+		 */
+		function markSelectedThings(mark: boolean): void;
+		/**
+		 * Gets all selected (default) or unselected vertices.
+		 * @param selected `true` to get all selected vertices, `false` to get all unselected ones
+		 * @returns `Array` of `Vertex`
+		 */
+		function getSelectedVertices(selected: boolean): Vertex[];
+		/**
+		 * Get the currently highlighted `Vertex`.
+		 * @returns The currently highlighted `Vertex` or `null` if no `Vertex` is highlighted
+		 */
+		function getHighlightedVertex(): Vertex;
+		/**
+		 * Gets the currently selected `Vertex`s *or*, if no `Vertex`s are selected, a currently highlighted `Vertex`.
+		 * @returns `Array` of `Vertex`
+		 */
+		function getSelectedOrHighlightedVertices(): Vertex[];
+		/**
+		 * Gets all selected (default) or unselected `Thing`s.
+		 * @param selected `true` to get all selected `Thing`s, `false` to get all unselected ones
+		 * @returns `Array` of `Thing`s
+		 */
+		function getSelectedThings(selected: boolean): Thing[];
+		/**
+		 * Get the currently highlighted `Thing`.
+		 * @returns The currently highlighted `Thing` or `null` if no `Thing` is highlighted
+		 */
+		function getHighlightedThing(): Thing;
+		/**
+		 * Gets the currently selected `Thing`s *or*, if no `Thing`s are selected, a currently highlighted `Thing`.
+		 * @returns `Array` of `Thing`s
+		 */
+		function getSelectedOrHighlightedThings(): Thing[];
+		/**
+		 * Gets all selected (default) or unselected `Sector`s.
+		 * @param selected `true` to get all selected `Sector`s, `false` to get all unselected ones
+		 * @returns `Array` of `Sector`s
+		 */
+		function getSelectedSectors(selected: boolean): Sector[];
+		/**
+		 * Get the currently highlighted `Sector`.
+		 * @returns The currently highlighted `Sector` or `null` if no `Sector` is highlighted
+		 */
+		function getHighlightedSector(): Sector;
+		/**
+		 * Gets the currently selected `Sector`s *or*, if no `Sector`s are selected, a currently highlighted `Sector`.
+		 * @returns `Array` of `Sector`s
+		 */
+		function getSelectedOrHighlightedSectors(): Sector[];
+		/**
+		 * Gets all selected (default) or unselected `Linedef`s.
+		 * @param selected `true` to get all selected `Linedef`s, `false` to get all unselected ones
+		 * @returns `Array` of `Linedef`s
+		 */
+		function getSelectedLinedefs(selected: boolean): Linedef[];
+		/**
+		 * Get the currently highlighted `Linedef`.
+		 * @returns The currently highlighted `Linedef` or `null` if no `Linedef` is highlighted
+		 */
+		function getHighlightedLinedef(): Linedef;
+		/**
+		 * Gets the currently selected `Linedef`s *or*, if no `Linede`f`s are selected, a currently highlighted `Linedef`.
+		 * @returns `Array` of `Linedef`s
+		 */
+		function getSelectedOrHighlightedLinedefs(): Linedef[];
+		/**
+		 * Gets all `Sidedef`s from the selected `Linedef`s.
+		 * @param selected `true` to get all `Sidedef`s of all selected `Linedef`s, `false` to get all `Sidedef`s of all unselected `Linedef`s
+		 * @returns `Array` of `Sidedef`
+		 */
+		function getSidedefsFromSelectedLinedefs(selected: boolean): Sidedef[];
+		/**
+		 * Gets the `Sidedef`s of the currently selected `Linedef`s *or*, if no `Linedef`s are selected, the `Sidedef`s of the currently highlighted `Linedef`.
+		 * @returns `Array` of `Sidedef`s
+		 */
+		function getSidedefsFromSelectedOrHighlightedLinedefs(): Sidedef[];
+		/**
+		 * Clears all selected map elements.
+		 */
+		function clearAllSelected(): void;
+		/**
+		 * Clears all selected vertices.
+		 */
+		function clearSelectedVertices(): void;
+		/**
+		 * Clears all selected `Thing`s.
+		 */
+		function clearSelectedThings(): void;
+		/**
+		 * Clears all selected `Sector`s.
+		 */
+		function clearSelectedSectors(): void;
+		/**
+		 * Creates a new `Vertex` at the given position. The position can be a `Vector2D` or an `Array` of two numbers.
+		 * @param pos Position where the `Vertex` should be created at
+		 * @returns The created `Vertex`
+		 */
+		function createVertex(pos: any): Vertex;
+		/**
+		 * Creates a new `Thing` at the given position. The position can be a `Vector2D`, `Vector3D`, or an `Array` of two numbers or three numbers (note that the z position only works for game configurations that support vertical pos. A thing type can be supplied optionally.
+		 * @param pos Position where the `Thing` should be created at
+		 * @param type Thing type (optional)
+		 * @returns The new `Thing`
+		 */
+		function createThing(pos: any, type: number): Thing;
+		/**
+		 * Joins `Sector`s, keeping lines shared by the `Sector`s. All `Sector`s will be joined with the first `Sector` in the array.
+		 * @param sectors `Array` of `Sector`s
+		 */
+		function joinSectors(sectors: Sector[]): void;
+		/**
+		 * Merges `Sector`s, deleting lines shared by the `Sector`s. All `Sector`s will be merged into the first `Sector` in the array.
+		 * @param sectors `Array` of `Sector`s
+		 */
+		function mergeSectors(sectors: Sector[]): void;
+		/**
+		 * `true` if the map is in Doom format, `false` if it isn't. Read-only.
+		 */
+		let isDoom: boolean;
+		/**
+		 * `true` if the map is in Hexen format, `false` if it isn't. Read-only.
+		 */
+		let isHexen: boolean;
+		/**
+		 * `true` if the map is in UDMF, `false` if it isn't. Read-only.
+		 */
+		let isUDMF: boolean;
+		/**
+		 * The map coordinates of the mouse position as a `Vector2D`. Read-only.
+		 */
+		let mousePosition: Vector2D;
+		/**
+		 * `VisualCamera` object with information about the position of the camera in visual mode. Read-only.
+		 */
+		let camera: VisualCamera;
+	}
+	namespace Map {
+		/**
+		 * How geometry should be merged when geometry is stitched.
+		 */
+		enum MergeGeometryMode {
+			/**
+			 * Merge vertices only
+			 */
+			CLASSIC,
+			/**
+			 * Merge vertices and lines
+			 */
+			MERGE,
+			/**
+			 * Merge vertices and lines, replacing sector geometry
+			 */
+			REPLACE,
+		}
+
+	}
+	class Plane {
+		/**
+		 * Creates a new `Plane` from a normal and an offset. The normal vector has to be `Vector3D`, `Array`s of 3 numbers, or an object with x, y, and z properties.
+		 * @param normal Normal vector of the plane
+		 * @param offset Distance of the plane from the origin
+		 */
+		constructor(normal: any, offset: number);
+		/**
+		 * Creates a new `Plane` from 3 points. The points have to be `Vector3D`, `Array`s of 3 numbers, or an object with x, y, and z properties.
+		 * @param p1 First point
+		 * @param p2 Second point
+		 * @param p3 Thrid point
+		 * @param up `true` if plane is pointing up, `false` if pointing down
+		 */
+		constructor(p1: any, p2: any, p3: any, up: boolean);
+		/**
+		 * Checks if the line between `from` and `to` intersects the plane.
+		 * @param from `Vector3D` of the start of the line
+		 * @param to `Vector3D` of the end of the line
+		 * @returns None
+		 */
+		getIntersection(from: any, to: any): object[];
+		/**
+		 * Computes the distance between the `Plane` and a point. The given point can be a `Vector3D` or an `Array` of three numbers. A result greater than 0 means the point is on the front of the plane, less than 0 means the point is behind the plane.
+		 * @param p Point to compute the distnace to
+		 * @returns Distance between the `Plane` and the point as `number`
+		 */
+		distance(p: any): number;
+		/**
+		 * Returns the point that's closest to the given point on the `Plane`. The given point can be a `Vector3D` or an `Array` of three numbers.
+		 * @param p Point to get the closest position from
+		 * @returns Point as `Vector3D` on the plane closest to the given point
+		 */
+		closestOnPlane(p: any): Vector3D;
+		/**
+		 * Returns the position on the z axis of the plane for the given point. The given point can be a `Vector2D` or an `Array` of two numbers.
+		 * @param p Point to get the z position from
+		 * @returns None
+		 */
+		getZ(p: any): number;
+		/**
+		 * The plane's normal vector.
+		 */
+		normal: Vector3D;
+		/**
+		 * The distance of the plane along the normal vector.
+		 */
+		offset: number;
+		/**
+		 * The `a` value of the plane equation. This is the `x` value of the normal vector.
+		 */
+		a: number;
+		/**
+		 * The `b` value of the plane equation. This is the `y` value of the normal vector.
+		 */
+		b: number;
+		/**
+		 * The `c` value of the plane equation. This is the `z` value of the normal vector.
+		 */
+		c: number;
+		/**
+		 * The `d` value of the plane equation. This is the same as the `offset` value.
+		 */
+		d: number;
+	}
+	class Sector {
+		/**
+		 * Returns an `Array` of all `Sidedef`s of the `Sector`.
+		 * @returns `Array` of the `Sector`'s `Sidedef`s
+		 */
+		getSidedefs(): Sidedef[];
+		/**
+		 * Clears all flags.
+		 */
+		clearFlags(): void;
+		/**
+		 * Copies the properties from this `Sector` to another.
+		 * @param s the `Sector` to copy the properties to
+		 */
+		copyPropertiesTo(s: Sector): void;
+		/**
+		 * Checks if the given point is in this `Sector` or not. The given point can be a `Vector2D` or an `Array` of two numbers.
+		 * @param p Point to test
+		 * @returns `true` if the point is in the `Sector`, `false` if it isn't
+		 */
+		intersect(p: any): boolean;
+		/**
+		 * Joins this `Sector` with another `Sector`. Lines shared between the sectors will not be removed.
+		 * @param other Sector to join with
+		 */
+		join(other: Sector): void;
+		/**
+		 * Deletes the `Sector` and its `Sidedef`s.
+		 */
+		delete(): void;
+		/**
+		 * Gets an array of `Vector2D` arrays, representing the vertices of the triangulated sector. Note that for sectors with islands some triangles may not always have their points on existing vertices.
+		 * @returns Array of `Vector2D` arrays
+		 */
+		getTriangles(): Vector2D[][];
+		/**
+		 * Gets the floor's slope vector.
+		 * @returns The floor's slope normal as a `Vector3D`
+		 */
+		getFloorSlope(): Vector3D;
+		/**
+		 * Sets the floor's slope vector. The vector has to be normalized.
+		 * @param normal The new slope vector as `Vector3D`
+		 */
+		setFloorSlope(normal: any): void;
+		/**
+		 * Gets the ceiling's slope vector.
+		 * @returns The ceiling's slope normal as a `Vector3D`
+		 */
+		getCeilingSlope(): Vector3D;
+		/**
+		 * Sets the ceiling's slope vector. The vector has to be normalized.
+		 * @param normal The new slope vector as `Vector3D`
+		 */
+		setCeilingSlope(normal: any): void;
+		/**
+		 * Returns an `Array` of `Vector2D` of label positions for the `Sector`. This are the positions where for example selection number or tags are shown.
+		 * @returns `Array` of `Vector2D` of all label positions
+		 */
+		getLabelPositions(): Vector2D[];
+		/**
+		 * Returns an `Array` of the `Sector`'s tags. UDMF only. Supported game configurations only.
+		 * @returns `Array` of tags
+		 */
+		getTags(): number[];
+		/**
+		 * Adds a tag to the `Sector`. UDMF only. Supported game configurations only.
+		 * @param tag Tag to add
+		 * @returns `true` when the tag was added, `false` when the tag already exists
+		 */
+		addTag(tag: number): boolean;
+		/**
+		 * Removes a tag from the `Sector`. UDMF only. Supported game configurations only.
+		 * @param tag Tag to remove
+		 * @returns `true` when the tag was removed successfully, `false` when the tag did not exist
+		 */
+		removeTag(tag: number): boolean;
+		/**
+		 * The `Sector`'s index. Read-only.
+		 */
+		index: number;
+		/**
+		 * Floor height of the `Sector`.
+		 */
+		floorHeight: number;
+		/**
+		 * Ceiling height of the `Sector`.
+		 */
+		ceilingHeight: number;
+		/**
+		 * Floor texture of the `Sector`.
+		 */
+		floorTexture: string;
+		/**
+		 * Ceiling texture of the `Sector`.
+		 */
+		ceilingTexture: string;
+		/**
+		 * If the `Sector` is selected or not.
+		 */
+		selected: boolean;
+		/**
+		 * If the `Sector`'s floor is selected or not. Will always return `true` in classic modes if the `Sector` is selected. Read-only.
+		 */
+		floorSelected: boolean;
+		/**
+		 * If the `Sector`'s floor is highlighted or not. Will always return `true` in classic modes if the `Sector` is highlighted. Read-only.
+		 */
+		floorHighlighted: boolean;
+		/**
+		 * If the `Sector`'s ceiling is selected or not. Will always return `true` in classic modes if the `Sector` is selected. Read-only.
+		 */
+		ceilingSelected: boolean;
+		/**
+		 * If the `Sector`'s ceiling is highlighted or not. Will always return `true` in classic modes if the `Sector` is highlighted. Read-only.
+		 */
+		ceilingHighlighted: boolean;
+		/**
+		 * If the `Sector` is marked or not. It is used to mark map elements that were created or changed (for example after drawing new geometry).
+		 */
+		marked: boolean;
+		/**
+		 * `Sector` flags. It's an object with the flags as properties. Only available in UDMF.
+		 */
+		flags: any;
+		/**
+		 * The `Sector`'s special type.
+		 */
+		special: number;
+		/**
+		 * The `Sector`'s tag.
+		 */
+		tag: number;
+		/**
+		 * The `Sector`'s brightness.
+		 */
+		brightness: number;
+		/**
+		 * The floor's slope offset.
+		 */
+		floorSlopeOffset: number;
+		/**
+		 * The ceiling's slope offset.
+		 */
+		ceilingSlopeOffset: number;
+		/**
+		 * UDMF fields. It's an object with the fields as properties.
+		 */
+		fields: any;
+	}
+	class Sidedef {
+		/**
+		 * The `Sidedef`'s index. Read-only.
+		 */
+		index: number;
+		/**
+		 * `true` if this `Sidedef` is the front of its `Linedef`, otherwise `false`. Read-only.
+		 */
+		isFront: boolean;
+		/**
+		 * The `Sector` the `Sidedef` belongs to. Read-only.
+		 */
+		sector: Sector;
+		/**
+		 * The `Linedef` the `Sidedef` belongs to. Read-only.
+		 */
+		line: Linedef;
+		/**
+		 * The `Sidedef` on the other side of this `Sidedef`'s `Linedef`. Returns `null` if there is no other. Read-only.
+		 */
+		other: Sidedef;
+		/**
+		 * The `Sidedef`'s angle in degrees. Read-only.
+		 */
+		angle: number;
+		/**
+		 * The `Sidedef`'s angle in radians. Read-only.
+		 */
+		angleRad: number;
+		/**
+		 * The x offset of the `Sidedef`'s textures.
+		 */
+		offsetX: number;
+		/**
+		 * The y offset of the `Sidedef`'s textures.
+		 */
+		offsetY: number;
+		/**
+		 * `Sidedef` flags. It's an object with the flags as properties. Only available in UDMF.
+		 */
+		flags: any;
+		/**
+		 * The `Sidedef`'s upper texture.
+		 */
+		upperTexture: string;
+		/**
+		 * The `Sidedef`'s middle texture.
+		 */
+		middleTexture: string;
+		/**
+		 * The `Sidedef`'s lower texture.
+		 */
+		lowerTexture: string;
+		/**
+		 * If the `Sidedef`'s upper part is selected or not. Will always return `true` in classic modes if the parent `Linedef` is selected.
+		 */
+		upperSelected: boolean;
+		/**
+		 * If the `Sidedef`'s upper part is highlighted or not. Will always return `true` in classic modes if the parent `Linedef` is selected.
+		 */
+		upperHighlighted: boolean;
+		/**
+		 * If the `Sidedef`'s middle part is selected or not. Will always return `true` in classic modes if the parent `Linedef` is selected.
+		 */
+		middleSelected: boolean;
+		/**
+		 * If the `Sidedef`'s middle part is highlighted or not. Will always return `true` in classic modes if the parent `Linedef` is selected.
+		 */
+		middleHighlighted: boolean;
+		/**
+		 * If the `Sidedef`'s lower part is selected or not. Will always return `true` in classic modes if the parent `Linedef` is selected.
+		 */
+		lowerSelected: boolean;
+		/**
+		 * If the `Sidedef`'s lower part is highlighted or not. Will always return `true` in classic modes if the parent `Linedef` is selected.
+		 */
+		lowerHighlighted: boolean;
+		/**
+		 * UDMF fields. It's an object with the fields as properties.
+		 */
+		fields: any;
+	}
+	class Thing {
+		/**
+		 * Copies the properties from this `Thing` to another.
+		 * @param t The `Thing` to copy the properties to
+		 */
+		copyPropertiesTo(t: Thing): void;
+		/**
+		 * Clears all flags.
+		 */
+		clearFlags(): void;
+		/**
+		 * Snaps the `Thing`'s position to the grid.
+		 */
+		snapToGrid(): void;
+		/**
+		 * Snaps the `Thing`'s position to the map format's accuracy.
+		 */
+		snapToAccuracy(): void;
+		/**
+		 * Gets the squared distance between this `Thing` and the given point.
+		 * @param pos Point to calculate the squared distance to.
+		 * @returns Distance to `pos`
+		 */
+		distanceToSq(pos: any): number;
+		/**
+		 * Gets the distance between this `Thing` and the given point. The point can be either a `Vector2D` or an array of numbers.
+		 * @param pos Point to calculate the distance to.
+		 * @returns Distance to `pos`
+		 */
+		distanceTo(pos: any): number;
+		/**
+		 * Deletes the `Thing`.
+		 */
+		delete(): void;
+		/**
+		 * Determines and returns the `Sector` the `Thing` is in.
+		 * @returns The `Sector` the `Thing` is in
+		 */
+		getSector(): Sector;
+		/**
+		 * Index of the `Thing`. Read-only.
+		 */
+		index: number;
+		/**
+		 * Type of the `Thing`.
+		 */
+		type: number;
+		/**
+		 * Angle of the `Thing` in degrees, see https://doomwiki.org/wiki/Angle.
+		 */
+		angle: number;
+		/**
+		 * Angle of the `Thing` in radians.
+		 */
+		angleRad: number;
+		/**
+		 * `Array` of arguments of the `Thing`. Number of arguments depends on game config (usually 5). Hexen format and UDMF only.
+		 */
+		args: number[];
+		/**
+		 * `Thing` action. Hexen and UDMF only.
+		 */
+		action: number;
+		/**
+		 * `Thing` tag. UDMF only.
+		 */
+		tag: number;
+		/**
+		 * If the `Thing` is selected or not.
+		 */
+		selected: boolean;
+		/**
+		 * If the `Thing` is marked or not. It is used to mark map elements that were created or changed (for example after drawing new geometry).
+		 */
+		marked: boolean;
+		/**
+		 * `Thing` flags. It's an object with the flags as properties. In Doom format and Hexen format they are identified by numbers, in UDMF by their name.
+		 */
+		flags: any;
+		/**
+		 * Position of the `Thing`. It's an object with `x`, `y`, and `z` properties. The latter is only relevant in Hexen format and UDMF.
+		 */
+		position: any;
+		/**
+		 * Pitch of the `Thing` in degrees. Only valid for supporting game configurations.
+		 */
+		pitch: number;
+		/**
+		 * Roll of the `Thing` in degrees. Only valid for supporting game configurations.
+		 */
+		roll: number;
+		/**
+		 * UDMF fields. It's an object with the fields as properties.
+		 */
+		fields: any;
+	}
+	/**
+		 * Set the progress of the script in percent. Value can be between 0 and 100. Also shows the script running dialog.
+		 * @param value Number between 0 and 100
+		 */
+		function setProgress(value: number): void;
+	/**
+		 * Adds a line to the script log. Also shows the script running dialog.
+		 * @param text Line to add to the script log
+		 */
+		function log(text: any): void;
+	/**
+		 * Shows a message box with an "OK" button.
+		 * @param message Message to show
+		 */
+		function showMessage(message: any): void;
+	/**
+		 * Shows a message box with an "Yes" and "No" button.
+		 * @param message Message to show
+		 * @returns true if "Yes" was clicked, false if "No" was clicked
+		 */
+		function showMessageYesNo(message: any): boolean;
+	/**
+		 * Exist the script prematurely without undoing its changes.
+		 * @param s Text to show in the status bar (optional)
+		 */
+		function exit(s: string): void;
+	/**
+		 * Exist the script prematurely with undoing its changes.
+		 * @param s Text to show in the status bar (optional)
+		 */
+		function die(s: string): void;
+	class Vector2D {
+		/**
+		 * Creates a new `Vector2D` from x and y coordinates
+		 * @param x The x coordinate
+		 * @param y The y coordinate
+		 */
+		constructor(x: number, y: number);
+		/**
+		 * Creates a new `Vector2D` from a point.
+		 * @param v The vector to create the `Vector2D` from
+		 */
+		constructor(v: any);
+		/**
+		 * Returns the perpendicular to the `Vector2D`.
+		 * @returns The perpendicular as `Vector2D`
+		 */
+		getPerpendicular(): Vector2D;
+		/**
+		 * Returns a `Vector2D` with the sign of all components.
+		 * @returns A `Vector2D` with the sign of all components
+		 */
+		getSign(): Vector2D;
+		/**
+		 * Returns the angle of the `Vector2D` in radians.
+		 * @returns The angle of the `Vector2D` in radians
+		 */
+		getAngleRad(): number;
+		/**
+		 * Returns the angle of the `Vector2D` in degree.
+		 * @returns The angle of the `Vector2D` in degree
+		 */
+		getAngle(): number;
+		/**
+		 * Returns the length of the `Vector2D`.
+		 * @returns The length of the `Vector2D`
+		 */
+		getLength(): number;
+		/**
+		 * Returns the square length of the `Vector2D`.
+		 * @returns The square length of the `Vector2D`
+		 */
+		getLengthSq(): number;
+		/**
+		 * Returns the normal of the `Vector2D`.
+		 * @returns The normal as `Vector2D`
+		 */
+		getNormal(): Vector2D;
+		/**
+		 * Returns the transformed vector as `Vector2D`.
+		 * @param offsetx X offset
+		 * @param offsety Y offset
+		 * @param scalex X scale
+		 * @param scaley Y scale
+		 * @returns The transformed vector as `Vector2D`
+		 */
+		getTransformed(offsetx: number, offsety: number, scalex: number, scaley: number): Vector2D;
+		/**
+		 * Returns the inverse transformed vector as `Vector2D`.
+		 * @param invoffsetx X offset
+		 * @param invoffsety Y offset
+		 * @param invscalex X scale
+		 * @param invscaley Y scale
+		 * @returns The inverse transformed vector as `Vector2D`
+		 */
+		getInverseTransformed(invoffsetx: number, invoffsety: number, invscalex: number, invscaley: number): Vector2D;
+		/**
+		 * Returns the rotated vector as `Vector2D`.
+		 * @param theta Angle in degree to rotate by
+		 * @returns The rotated `Vector2D`
+		 */
+		getRotated(theta: number): Vector2D;
+		/**
+		 * Returns the rotated vector as `Vector2D`.
+		 * @param theta Angle in radians to rotate by
+		 * @returns The rotated `Vector2D`
+		 */
+		getRotatedRad(theta: number): Vector2D;
+		/**
+		 * Checks if the `Vector2D` is finite or not.
+		 * @returns `true` if `Vector2D` is finite, otherwise `false`
+		 */
+		isFinite(): boolean;
+		/**
+		 * The `x` value of the vector.
+		 */
+		x: number;
+		/**
+		 * The `y` value of the vector.
+		 */
+		y: number;
+	}
+	namespace Vector2D {
+		/**
+		 * Returns the dot product of two `Vector2D`s.
+		 * @param a First `Vector2D`
+		 * @param b Second `Vector2D`
+		 * @returns The dot product of the two vectors
+		 */
+		function dotProduct(a: Vector2D, b: Vector2D): number;
+		/**
+		 * Returns the cross product of two `Vector2D`s.
+		 * @param a First `Vector2D`
+		 * @param b Second `Vector2D`
+		 * @returns Cross product of the two vectors as `Vector2D`
+		 */
+		function crossProduct(a: any, b: any): Vector2D;
+		/**
+		 * Reflects a `Vector2D` over a mirror `Vector2D`.
+		 * @param v `Vector2D` to reflect
+		 * @param m Mirror `Vector2D`
+		 * @returns The reflected vector as `Vector2D`
+		 */
+		function reflect(v: any, m: any): Vector2D;
+		/**
+		 * Returns a reversed `Vector2D`.
+		 * @param v `Vector2D` to reverse
+		 * @returns The reversed vector as `Vector2D`
+		 */
+		function reversed(v: any): Vector2D;
+		/**
+		 * Creates a `Vector2D` from an angle in radians,
+		 * @param angle Angle in radians
+		 * @returns Vector as `Vector2D`
+		 */
+		function fromAngleRad(angle: number): Vector2D;
+		/**
+		 * Creates a `Vector2D` from an angle in degrees,
+		 * @param angle Angle in degrees
+		 * @returns Vector as `Vector2D`
+		 */
+		function fromAngle(angle: number): Vector2D;
+		/**
+		 * Returns the angle between two `Vector2D`s in radians
+		 * @param a First `Vector2D`
+		 * @param b Second `Vector2D`
+		 * @returns Angle in radians
+		 */
+		function getAngleRad(a: any, b: any): number;
+		/**
+		 * Returns the angle between two `Vector2D`s in degrees.
+		 * @param a First `Vector2D`
+		 * @param b Second `Vector2D`
+		 * @returns Angle in degrees
+		 */
+		function getAngle(a: any, b: any): number;
+		/**
+		 * Returns the square distance between two `Vector2D`s.
+		 * @param a First `Vector2D`
+		 * @param b Second `Vector2D`
+		 * @returns The squared distance
+		 */
+		function getDistanceSq(a: any, b: any): number;
+		/**
+		 * Returns the distance between two `Vector2D`s.
+		 * @param a First `Vector2D`
+		 * @param b Second `Vector2D`
+		 * @returns The distance
+		 */
+		function getDistance(a: any, b: any): number;
+	}
+	class Vector3D {
+		/**
+		 * Creates a new `Vector3D` from x and y coordinates
+		 * @param x The x coordinate
+		 * @param y The y coordinate
+		 * @param z The z coordinate
+		 */
+		constructor(x: number, y: number, z: number);
+		/**
+		 * Creates a new `Vector3D` from a point.
+		 * @param v The vector to create the `Vector3D` from
+		 */
+		constructor(v: any);
+		/**
+		 * Returns the x/y angle of the `Vector3D` in radians.
+		 * @returns The x/y angle of the `Vector3D` in radians
+		 */
+		getAngleXYRad(): number;
+		/**
+		 * Returns the angle of the `Vector3D` in degrees.
+		 * @returns The angle of the `Vector3D` in degrees
+		 */
+		getAngleXY(): number;
+		/**
+		 * Returns the z angle of the `Vector3D` in radians.
+		 * @returns The z angle of the `Vector3D` in radians
+		 */
+		getAngleZRad(): number;
+		/**
+		 * Returns the z angle of the `Vector3D` in degrees.
+		 * @returns The z angle of the `Vector3D` in degrees
+		 */
+		getAngleZ(): number;
+		/**
+		 * Returns the length of the `Vector3D`.
+		 * @returns The length of the `Vector3D`
+		 */
+		getLength(): number;
+		/**
+		 * Returns the square length of the `Vector3D`.
+		 * @returns The square length of the `Vector3D`
+		 */
+		getLengthSq(): number;
+		/**
+		 * Returns the normal of the `Vector3D`.
+		 * @returns The normal as `Vector3D`
+		 */
+		getNormal(): Vector3D;
+		/**
+		 * Return the scaled `Vector3D`.
+		 * @param scale Scale, where 1.0 is unscaled
+		 * @returns The scaled `Vector3D`
+		 */
+		getScaled(scale: number): Vector3D;
+		/**
+		 * Checks if the `Vector3D` is normalized or not.
+		 * @returns `true` if `Vector3D` is normalized, otherwise `false`
+		 */
+		isNormalized(): boolean;
+		/**
+		 * Checks if the `Vector3D` is finite or not.
+		 * @returns `true` if `Vector3D` is finite, otherwise `false`
+		 */
+		isFinite(): boolean;
+		/**
+		 * The `x` value of the vector.
+		 */
+		x: number;
+		/**
+		 * The `y` value of the vector.
+		 */
+		y: number;
+		/**
+		 * The `z` value of the vector.
+		 */
+		z: number;
+	}
+	namespace Vector3D {
+		/**
+		 * Returns the dot product of two `Vector3D`s.
+		 * @param a First `Vector3D`
+		 * @param b Second `Vector3D`
+		 * @returns The dot product of the two vectors
+		 */
+		function dotProduct(a: Vector3D, b: Vector3D): number;
+		/**
+		 * Returns the cross product of two `Vector3D`s.
+		 * @param a First `Vector3D`
+		 * @param b Second `Vector3D`
+		 * @returns Cross product of the two vectors as `Vector3D`
+		 */
+		function crossProduct(a: any, b: any): Vector3D;
+		/**
+		 * Reflects a `Vector3D` over a mirror `Vector3D`.
+		 * @param v `Vector3D` to reflect
+		 * @param m Mirror `Vector3D`
+		 * @returns The reflected vector as `Vector3D`
+		 */
+		function reflect(v: any, m: any): Vector3D;
+		/**
+		 * Returns a reversed `Vector3D`.
+		 * @param v `Vector3D` to reverse
+		 * @returns The reversed vector as `Vector3D`
+		 */
+		function reversed(v: any): Vector3D;
+		/**
+		 * Creates a `Vector3D` from an angle in radians
+		 * @param angle Angle on the x/y axes in radians
+		 * @returns Vector as `Vector3D`
+		 */
+		function fromAngleXYRad(angle: number): Vector3D;
+		/**
+		 * Creates a `Vector3D` from an angle in radians,
+		 * @param angle Angle on the x/y axes in degrees
+		 * @returns Vector as `Vector3D`
+		 */
+		function fromAngleXY(angle: number): Vector3D;
+		/**
+		 * Creates a `Vector3D` from two angles in radians
+		 * @param anglexy Angle on the x/y axes in radians
+		 * @param anglez Angle on the z axis in radians
+		 * @returns Vector as `Vector3D`
+		 */
+		function fromAngleXYZRad(anglexy: number, anglez: number): Vector3D;
+		/**
+		 * Creates a `Vector3D` from two angles in degrees
+		 * @param anglexy Angle on the x/y axes in radians
+		 * @param anglez Angle on the z axis in radians
+		 * @returns Vector as `Vector3D`
+		 */
+		function fromAngleXYZ(anglexy: number, anglez: number): Vector3D;
+	}
+	class Vertex {
+		/**
+		 * Gets all `Linedefs` that are connected to this `Vertex`.
+		 * @returns Array of linedefs
+		 */
+		getLinedefs(): Linedef[];
+		/**
+		 * Copies the properties from this `Vertex` to another.
+		 * @param v the vertex to copy the properties to
+		 */
+		copyPropertiesTo(v: Vertex): void;
+		/**
+		 * Gets the squared distance between this `Vertex` and the given point.
+		 * @param pos Point to calculate the squared distance to.
+		 * @returns Squared distance to `pos`
+		 */
+		distanceToSq(pos: any): number;
+		/**
+		 * Gets the distance between this `Vertex` and the given point.
+		 * @param pos Point to calculate the distance to.
+		 * @returns Distance to `pos`
+		 */
+		distanceTo(pos: any): number;
+		/**
+		 * Returns the `Linedef` that is connected to this `Vertex` that is closest to the given point.
+		 * @param pos Point to get the nearest `Linedef` connected to this `Vertex` from
+		 * @returns None
+		 */
+		nearestLinedef(pos: any): Linedef;
+		/**
+		 * Snaps the `Vertex`'s position to the map format's accuracy.
+		 */
+		snapToAccuracy(): void;
+		/**
+		 * Snaps the `Vertex`'s position to the grid.
+		 */
+		snapToGrid(): void;
+		/**
+		 * Joins this `Vertex` with another `Vertex`, deleting this `Vertex` and keeping the other.
+		 * @param other `Vertex` to join with
+		 */
+		join(other: Vertex): void;
+		/**
+		 * Deletes the `Vertex`. Note that this can result in unclosed sectors.
+		 */
+		delete(): void;
+		/**
+		 * The vertex index. Read-only.
+		 */
+		index: number;
+		/**
+		 * Position of the `Vertex`. It's an object with `x` and `y` properties.
+		 */
+		position: any;
+		/**
+		 * If the `Vertex` is selected or not.
+		 */
+		selected: boolean;
+		/**
+		 * If the `Vertex` is marked or not. It is used to mark map elements that were created or changed (for example after drawing new geometry).
+		 */
+		marked: boolean;
+		/**
+		 * The ceiling z position of the `Vertex`. Only available in UDMF. Only available for supported game configurations.
+		 */
+		ceilingZ: number;
+		/**
+		 * The floor z position of the `Vertex`. Only available in UDMF. Only available for supported game configurations.
+		 */
+		floorZ: number;
+		/**
+		 * UDMF fields. It's an object with the fields as properties.
+		 */
+		fields: any;
+	}
+	class VisualCamera {
+		/**
+		 * Position of the camera as `Vector3D`. Read-only.
+		 */
+		position: Vector3D;
+		/**
+		 * Angle of the camera on the X/Y axes. Read-only.
+		 */
+		angleXY: number;
+		/**
+		 * Angle of the camera on the Z axis. Read-only.
+		 */
+		angleZ: number;
+	}
+	class QueryOptions {
+		/**
+		 * Initializes a new `QueryOptions` object.
+		 */
+		constructor();
+		/**
+		 * Adds a parameter to query
+		 * @param name Name of the variable that the queried value is stored in
+		 * @param description Textual description of the parameter
+		 * @param type UniversalType value of the parameter
+		 * @param defaultvalue Default value of the parameter
+		 */
+		addOption(name: string, description: string, type: number, defaultvalue: any): void;
+		/**
+		 * Adds a parameter to query
+		 * @param name Name of the variable that the queried value is stored in
+		 * @param description Textual description of the parameter
+		 * @param type UniversalType value of the parameter
+		 * @param defaultvalue Default value of the parameter
+		 * @param enumvalues *missing*
+		 */
+		addOption(name: string, description: string, type: number, defaultvalue: any, enumvalues: any): void;
+		/**
+		 * Removes all parameters
+		 */
+		clear(): void;
+		/**
+		 * Queries all parameters. Options a window where the user can enter values for the options added through `addOption()`.
+		 * @returns True if OK was pressed, otherwise false
+		 */
+		query(): boolean;
+		/**
+		 * Object containing all the added options as properties.
+		 */
+		options: any;
+	}
+}
diff --git a/Build/Updater.ini b/Build/Updater.ini
index b42b8351725627c40dedad4fdbd135578f7cc120..a93329d204105f5b02f1485b7e2de29d39a030c2 100755
--- a/Build/Updater.ini
+++ b/Build/Updater.ini
@@ -1,4 +1,5 @@
 URL http://devbuilds.drdteam.org/ultimatedoombuilder/
 FileName Builder.exe
 UpdateName UltimateDoomBuilder-r[REVNUM]-x86.7z
+InstallerName UltimateDoomBuilder-Setup-R[REVNUM]-x86.exe
 UpdaterName UDB_Updater-x86.7z
\ No newline at end of file
diff --git a/Builder.sln.DotSettings.user b/Builder.sln.DotSettings.user
index 9af4ab9ee278cda72cdd6dcde86bb300bafd6510..ab84c80c9ab350e834b846f322a235d20bb5bc20 100644
--- a/Builder.sln.DotSettings.user
+++ b/Builder.sln.DotSettings.user
@@ -5,5 +5,6 @@
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UDMF/@EntryIndexedValue">UDMF</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
+	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=BuilderModes_002FProperties_002FResources/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=Builder_002FProperties_002FResources/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/ResxEditorPersonal/Initialized/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/Setup/CodeDependencies.iss b/Setup/CodeDependencies.iss
new file mode 100644
index 0000000000000000000000000000000000000000..f976d719f3acedba0ca05a60d0ab05868d0b9c9d
--- /dev/null
+++ b/Setup/CodeDependencies.iss
@@ -0,0 +1,790 @@
+; -- CodeDependencies.iss --
+;
+; This script shows how to download and install any dependency such as .NET,
+; Visual C++ or SQL Server during your application's installation process.
+;
+; contribute: https://github.com/DomGries/InnoDependencyInstaller
+
+
+; -----------
+; SHARED CODE
+; -----------
+[Code]
+// types and variables
+type
+  TDependency_Entry = record
+    Filename: String;
+    Parameters: String;
+    Title: String;
+    URL: String;
+    Checksum: String;
+    ForceSuccess: Boolean;
+    RestartAfter: Boolean;
+  end;
+
+var
+  Dependency_Memo: String;
+  Dependency_List: array of TDependency_Entry;
+  Dependency_NeedRestart, Dependency_ForceX86: Boolean;
+  Dependency_DownloadPage: TDownloadWizardPage;
+
+procedure Dependency_Add(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, RestartAfter: Boolean);
+var
+  Dependency: TDependency_Entry;
+  DependencyCount: Integer;
+begin
+  Dependency_Memo := Dependency_Memo + #13#10 + '%1' + Title;
+
+  Dependency.Filename := Filename;
+  Dependency.Parameters := Parameters;
+  Dependency.Title := Title;
+
+  if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin
+    Dependency.URL := '';
+  end else begin
+    Dependency.URL := URL;
+  end;
+
+  Dependency.Checksum := Checksum;
+  Dependency.ForceSuccess := ForceSuccess;
+  Dependency.RestartAfter := RestartAfter;
+
+  DependencyCount := GetArrayLength(Dependency_List);
+  SetArrayLength(Dependency_List, DependencyCount + 1);
+  Dependency_List[DependencyCount] := Dependency;
+end;
+
+<event('InitializeWizard')>
+procedure Dependency_Internal1;
+begin
+  Dependency_DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+end;
+
+<event('PrepareToInstall')>
+function Dependency_Internal2(var NeedsRestart: Boolean): String;
+var
+  DependencyCount, DependencyIndex, ResultCode: Integer;
+  Retry: Boolean;
+  TempValue: String;
+begin
+  DependencyCount := GetArrayLength(Dependency_List);
+
+  if DependencyCount > 0 then begin
+    Dependency_DownloadPage.Show;
+
+    for DependencyIndex := 0 to DependencyCount - 1 do begin
+      if Dependency_List[DependencyIndex].URL <> '' then begin
+        Dependency_DownloadPage.Clear;
+        Dependency_DownloadPage.Add(Dependency_List[DependencyIndex].URL, Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Checksum);
+
+        Retry := True;
+        while Retry do begin
+          Retry := False;
+
+          try
+            Dependency_DownloadPage.Download;
+          except
+            if Dependency_DownloadPage.AbortedByUser then begin
+              Result := Dependency_List[DependencyIndex].Title;
+              DependencyIndex := DependencyCount;
+            end else begin
+              case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+                IDABORT: begin
+                  Result := Dependency_List[DependencyIndex].Title;
+                  DependencyIndex := DependencyCount;
+                end;
+                IDRETRY: begin
+                  Retry := True;
+                end;
+              end;
+            end;
+          end;
+        end;
+      end;
+    end;
+
+    if Result = '' then begin
+      for DependencyIndex := 0 to DependencyCount - 1 do begin
+        Dependency_DownloadPage.SetText(Dependency_List[DependencyIndex].Title, '');
+        Dependency_DownloadPage.SetProgress(DependencyIndex + 1, DependencyCount + 1);
+
+        while True do begin
+          ResultCode := 0;
+          if ShellExec('', ExpandConstant('{tmp}{\}') + Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin
+            if Dependency_List[DependencyIndex].RestartAfter then begin
+              if DependencyIndex = DependencyCount - 1 then begin
+                Dependency_NeedRestart := True;
+              end else begin
+                NeedsRestart := True;
+                Result := Dependency_List[DependencyIndex].Title;
+              end;
+              break;
+            end else if (ResultCode = 0) or Dependency_List[DependencyIndex].ForceSuccess then begin // ERROR_SUCCESS (0)
+              break;
+            end else if ResultCode = 1641 then begin // ERROR_SUCCESS_REBOOT_INITIATED (1641)
+              NeedsRestart := True;
+              Result := Dependency_List[DependencyIndex].Title;
+              break;
+            end else if ResultCode = 3010 then begin // ERROR_SUCCESS_REBOOT_REQUIRED (3010)
+              Dependency_NeedRestart := True;
+              break;
+            end;
+          end;
+
+          case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependency_List[DependencyIndex].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+            IDABORT: begin
+              Result := Dependency_List[DependencyIndex].Title;
+              break;
+            end;
+            IDIGNORE: begin
+              break;
+            end;
+          end;
+        end;
+
+        if Result <> '' then begin
+          break;
+        end;
+      end;
+
+      if NeedsRestart then begin
+        TempValue := '"' + ExpandConstant('{srcexe}') + '" /restart=1 /LANG="' + ExpandConstant('{language}') + '" /DIR="' + WizardDirValue + '" /GROUP="' + WizardGroupValue + '" /TYPE="' + WizardSetupType(False) + '" /COMPONENTS="' + WizardSelectedComponents(False) + '" /TASKS="' + WizardSelectedTasks(False) + '"';
+        if WizardNoIcons then begin
+          TempValue := TempValue + ' /NOICONS';
+        end;
+        RegWriteStringValue(HKA, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', '{#SetupSetting("AppName")}', TempValue);
+      end;
+    end;
+
+    Dependency_DownloadPage.Hide;
+  end;
+end;
+
+<event('UpdateReadyMemo')>
+function Dependency_Internal3(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
+begin
+  Result := '';
+  if MemoUserInfoInfo <> '' then begin
+    Result := Result + MemoUserInfoInfo + Newline + NewLine;
+  end;
+  if MemoDirInfo <> '' then begin
+    Result := Result + MemoDirInfo + Newline + NewLine;
+  end;
+  if MemoTypeInfo <> '' then begin
+    Result := Result + MemoTypeInfo + Newline + NewLine;
+  end;
+  if MemoComponentsInfo <> '' then begin
+    Result := Result + MemoComponentsInfo + Newline + NewLine;
+  end;
+  if MemoGroupInfo <> '' then begin
+    Result := Result + MemoGroupInfo + Newline + NewLine;
+  end;
+  if MemoTasksInfo <> '' then begin
+    Result := Result + MemoTasksInfo;
+  end;
+
+  if Dependency_Memo <> '' then begin
+    if MemoTasksInfo = '' then begin
+      Result := Result + SetupMessage(msgReadyMemoTasks);
+    end;
+    Result := Result + FmtMessage(Dependency_Memo, [Space]);
+  end;
+end;
+
+<event('NeedRestart')>
+function Dependency_Internal4: Boolean;
+begin
+  Result := Dependency_NeedRestart;
+end;
+
+function Dependency_IsX64: Boolean;
+begin
+  Result := not Dependency_ForceX86 and Is64BitInstallMode;
+end;
+
+function Dependency_String(const x86, x64: String): String;
+begin
+  if Dependency_IsX64 then begin
+    Result := x64;
+  end else begin
+    Result := x86;
+  end;
+end;
+
+function Dependency_ArchSuffix: String;
+begin
+  Result := Dependency_String('', '_x64');
+end;
+
+function Dependency_ArchTitle: String;
+begin
+  Result := Dependency_String(' (x86)', ' (x64)');
+end;
+
+function Dependency_IsNetCoreInstalled(const Version: String): Boolean;
+var
+  ResultCode: Integer;
+begin
+  // source code: https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck
+  if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + Dependency_ArchSuffix + '.exe') then begin
+    ExtractTemporaryFile('netcorecheck' + Dependency_ArchSuffix + '.exe');
+  end;
+  Result := ShellExec('', ExpandConstant('{tmp}{\}') + 'netcorecheck' + Dependency_ArchSuffix + '.exe', Version, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
+end;
+
+procedure Dependency_AddDotNet35;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-framework/net35-sp1
+  if not IsDotNetInstalled(net35, 1) then begin
+    Dependency_Add('dotnetfx35.exe',
+      '/lang:enu /passive /norestart',
+      '.NET Framework 3.5 Service Pack 1',
+      'https://download.microsoft.com/download/2/0/E/20E90413-712F-438C-988E-FDAA79A8AC3D/dotnetfx35.exe',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet40;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-framework/net40
+  if not IsDotNetInstalled(net4full, 0) then begin
+    Dependency_Add('dotNetFx40_Full_setup.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Framework 4.0',
+      'https://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet45;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-framework/net452
+  if not IsDotNetInstalled(net452, 0) then begin
+    Dependency_Add('dotnetfx45.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Framework 4.5.2',
+      'https://go.microsoft.com/fwlink/?LinkId=397707',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet46;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-framework/net462
+  if not IsDotNetInstalled(net462, 0) then begin
+    Dependency_Add('dotnetfx46.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Framework 4.6.2',
+      'https://go.microsoft.com/fwlink/?linkid=780596',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet47;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-framework/net472
+  if not IsDotNetInstalled(net472, 0) then begin
+    Dependency_Add('dotnetfx47.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Framework 4.7.2',
+      'https://go.microsoft.com/fwlink/?LinkId=863262',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet48;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-framework/net48
+  if not IsDotNetInstalled(net48, 0) then begin
+    Dependency_Add('dotnetfx48.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Framework 4.8',
+      'https://go.microsoft.com/fwlink/?LinkId=2085155',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddNetCore31;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-core/3.1
+  if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 3.1.22') then begin
+    Dependency_Add('netcore31' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Core Runtime 3.1.22' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/c2437aed-8cc4-41d0-a239-d6c7cf7bddae/062c37e8b06df740301c0bca1b0b7b9a/dotnet-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/4e95705e-1bb6-4764-b899-1b97eb70ea1d/dd311e073bd3e25b2efe2dcf02727e81/dotnet-runtime-3.1.22-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddNetCore31Asp;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-core/3.1
+  if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 3.1.22') then begin
+    Dependency_Add('netcore31asp' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      'ASP.NET Core Runtime 3.1.22' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/0a1a2ee5-b8ed-4f0d-a4af-a7bce9a9ac2b/d452039b49d79e8897f272c3ab34b875/aspnetcore-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/80e52143-31e8-450e-aa94-b3f8484aaba9/4b69e5c77d50e7b367960a0079c90a99/aspnetcore-runtime-3.1.22-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddNetCore31Desktop;
+begin
+  // https://dotnet.microsoft.com/download/dotnet-core/3.1
+  if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 3.1.22') then begin
+    Dependency_Add('netcore31desktop' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Desktop Runtime 3.1.22' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/e4fcd574-4487-4b4b-8ca8-c23177c6f59f/c6d67a04956169dc21895cdcb42bf344/windowsdesktop-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1c14e24b-7f31-42dc-ba3c-83295a2d6f7e/41b93591162dfe556cc160ae44fbe75e/windowsdesktop-runtime-3.1.22-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet50;
+begin
+  // https://dotnet.microsoft.com/download/dotnet/5.0
+  if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 5.0.13') then begin
+    Dependency_Add('dotnet50' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Runtime 5.0.13' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/4a79fcd5-d61b-4606-8496-68071c8099c6/2bf770ca40521e8c4563072592eadd06/dotnet-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/fccf43d2-3e62-4ede-b5a5-592a7ccded7b/6339f1fdfe3317df5b09adf65f0261ab/dotnet-runtime-5.0.13-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet50Asp;
+begin
+  // https://dotnet.microsoft.com/download/dotnet/5.0
+  if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 5.0.13') then begin
+    Dependency_Add('dotnet50asp' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      'ASP.NET Core Runtime 5.0.13' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/340f9482-fc43-4ef7-b434-e2ed57f55cb3/c641b805cef3823769409a6dbac5746b/aspnetcore-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/aac560f3-eac8-437e-aebd-9830119deb10/6a3880161cf527e4ec71f67efe4d91ad/aspnetcore-runtime-5.0.13-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet50Desktop;
+begin
+  // https://dotnet.microsoft.com/download/dotnet/5.0
+  if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.13') then begin
+    Dependency_Add('dotnet50desktop' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Desktop Runtime 5.0.13' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/c8125c6b-d399-4be3-b201-8f1394fc3b25/724758f754fc7b67daba74db8d6d91d9/windowsdesktop-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/2bfb80f2-b8f2-44b0-90c1-d3c8c1c8eac8/409dd3d3367feeeda048f4ff34b32e82/windowsdesktop-runtime-5.0.13-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet60;
+begin
+  // https://dotnet.microsoft.com/download/dotnet/6.0
+  if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 6.0.2') then begin
+    Dependency_Add('dotnet60' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Runtime 6.0.2' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/2c266f64-4c86-4209-8113-0146a9c93bef/f771275c8cb0df884dcfc290569fba3a/dotnet-runtime-6.0.2-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/0b3e8ad9-7914-4489-8d02-58b551c2efea/fdc3f3a171bf0b7bb90c01f5faa59fc4/dotnet-runtime-6.0.2-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet60Asp;
+begin
+  // https://dotnet.microsoft.com/download/dotnet/6.0
+  if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 6.0.2') then begin
+    Dependency_Add('dotnet60asp' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      'ASP.NET Core Runtime 6.0.2' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/ad7d20a7-debf-4399-b59b-04419ae7adfe/73918e15d0bde4431546d8f659ed7381/aspnetcore-runtime-6.0.2-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/ef70aabf-e945-4a82-8303-7675e84a183c/a1ef3d32b8572842684974747eee034b/aspnetcore-runtime-6.0.2-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDotNet60Desktop;
+begin
+  // https://dotnet.microsoft.com/download/dotnet/6.0
+  if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 6.0.2') then begin
+    Dependency_Add('dotnet60desktop' + Dependency_ArchSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Desktop Runtime 6.0.2' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/f5f7ed62-0973-400b-9772-4cf0eef96801/87959c77e1fceeafc40c867f9c238bbc/windowsdesktop-runtime-6.0.2-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/7fbe3ce3-4082-4995-93de-674038ac919b/56d3fa94d78dc3f39fc70d73ef174c93/windowsdesktop-runtime-6.0.2-win-x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddVC2005;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=26347
+  if not IsMsiProductInstalled(Dependency_String('{86C9D5AA-F00C-4921-B3F2-C60AF92E2844}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}'), PackVersionComponents(8, 0, 61000, 0)) then begin
+    Dependency_Add('vcredist2005' + Dependency_ArchSuffix + '.exe',
+      '/q',
+      'Visual C++ 2005 Service Pack 1 Redistributable' + Dependency_ArchTitle,
+      Dependency_String('https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddVC2008;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=26368
+  if not IsMsiProductInstalled(Dependency_String('{DE2C306F-A067-38EF-B86C-03DE4B0312F9}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}'), PackVersionComponents(9, 0, 30729, 6161)) then begin
+    Dependency_Add('vcredist2008' + Dependency_ArchSuffix + '.exe',
+      '/q',
+      'Visual C++ 2008 Service Pack 1 Redistributable' + Dependency_ArchTitle,
+      Dependency_String('https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x86.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddVC2010;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=26999
+  if not IsMsiProductInstalled(Dependency_String('{1F4F1D2A-D9DA-32CF-9909-48485DA06DD5}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}'), PackVersionComponents(10, 0, 40219, 0)) then begin
+    Dependency_Add('vcredist2010' + Dependency_ArchSuffix + '.exe',
+      '/passive /norestart',
+      'Visual C++ 2010 Service Pack 1 Redistributable' + Dependency_ArchTitle,
+      Dependency_String('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddVC2012;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=30679
+  if not IsMsiProductInstalled(Dependency_String('{4121ED58-4BD9-3E7B-A8B5-9F8BAAE045B7}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}'), PackVersionComponents(11, 0, 61030, 0)) then begin
+    Dependency_Add('vcredist2012' + Dependency_ArchSuffix + '.exe',
+      '/passive /norestart',
+      'Visual C++ 2012 Update 4 Redistributable' + Dependency_ArchTitle,
+      Dependency_String('https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddVC2013;
+begin
+  // https://support.microsoft.com/en-us/help/4032938
+  if not IsMsiProductInstalled(Dependency_String('{B59F5BF1-67C8-3802-8E59-2CE551A39FC5}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}'), PackVersionComponents(12, 0, 40664, 0)) then begin
+    Dependency_Add('vcredist2013' + Dependency_ArchSuffix + '.exe',
+      '/passive /norestart',
+      'Visual C++ 2013 Update 5 Redistributable' + Dependency_ArchTitle,
+      Dependency_String('https://download.visualstudio.microsoft.com/download/pr/10912113/5da66ddebb0ad32ebd4b922fd82e8e25/vcredist_x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddVC2015To2022;
+begin
+  // https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist
+  if not IsMsiProductInstalled(Dependency_String('{65E5BD06-6392-3027-8C26-853107D3CF1A}', '{36F68A90-239C-34DF-B58C-64B30153CE35}'), PackVersionComponents(14, 30, 30704, 0)) then begin
+    Dependency_Add('vcredist2022' + Dependency_ArchSuffix + '.exe',
+      '/passive /norestart',
+      'Visual C++ 2015-2022 Redistributable' + Dependency_ArchTitle,
+      Dependency_String('https://aka.ms/vs/17/release/vc_redist.x86.exe', 'https://aka.ms/vs/17/release/vc_redist.x64.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddDirectX;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=35
+  Dependency_Add('dxwebsetup.exe',
+    '/q',
+    'DirectX Runtime',
+    'https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe',
+    '', True, False);
+end;
+
+procedure Dependency_AddSql2008Express;
+var
+  Version: String;
+  PackedVersion: Int64;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=30438
+  if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(10, 50, 4000, 0)) < 0) then begin
+    Dependency_Add('sql2008express' + Dependency_ArchSuffix + '.exe',
+      '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+      'SQL Server 2008 R2 Service Pack 2 Express',
+      Dependency_String('https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddSql2012Express;
+var
+  Version: String;
+  PackedVersion: Int64;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=56042
+  if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(11, 0, 7001, 0)) < 0) then begin
+    Dependency_Add('sql2012express' + Dependency_ArchSuffix + '.exe',
+      '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+      'SQL Server 2012 Service Pack 4 Express',
+      Dependency_String('https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddSql2014Express;
+var
+  Version: String;
+  PackedVersion: Int64;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=57473
+  if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(12, 0, 6024, 0)) < 0) then begin
+    Dependency_Add('sql2014express' + Dependency_ArchSuffix + '.exe',
+      '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+      'SQL Server 2014 Service Pack 3 Express',
+      Dependency_String('https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe'),
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddSql2016Express;
+var
+  Version: String;
+  PackedVersion: Int64;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=56840
+  if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(13, 0, 5026, 0)) < 0) then begin
+    Dependency_Add('sql2016express' + Dependency_ArchSuffix + '.exe',
+      '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+      'SQL Server 2016 Service Pack 2 Express',
+      'https://download.microsoft.com/download/3/7/6/3767D272-76A1-4F31-8849-260BD37924E4/SQLServer2016-SSEI-Expr.exe',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddSql2017Express;
+var
+  Version: String;
+  PackedVersion: Int64;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=55994
+  if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(14, 0, 0, 0)) < 0) then begin
+    Dependency_Add('sql2017express' + Dependency_ArchSuffix + '.exe',
+      '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+      'SQL Server 2017 Express',
+      'https://download.microsoft.com/download/5/E/9/5E9B18CC-8FD5-467E-B5BF-BADE39C51F73/SQLServer2017-SSEI-Expr.exe',
+      '', False, False);
+  end;
+end;
+
+procedure Dependency_AddSql2019Express;
+var
+  Version: String;
+  PackedVersion: Int64;
+begin
+  // https://www.microsoft.com/en-us/download/details.aspx?id=101064
+  if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(15, 0, 0, 0)) < 0) then begin
+    Dependency_Add('sql2019express' + Dependency_ArchSuffix + '.exe',
+      '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
+      'SQL Server 2019 Express',
+      'https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe',
+      '', False, False);
+  end;
+end;
+
+
+[Setup]
+; -------------
+; EXAMPLE SETUP
+; -------------
+#ifndef Dependency_NoExampleSetup
+
+; comment out dependency defines to disable installing them
+#define UseDotNet35
+#define UseDotNet40
+#define UseDotNet45
+#define UseDotNet46
+#define UseDotNet47
+#define UseDotNet48
+
+; requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
+#define UseNetCoreCheck
+#ifdef UseNetCoreCheck
+  #define UseNetCore31
+  #define UseNetCore31Asp
+  #define UseNetCore31Desktop
+  #define UseDotNet50
+  #define UseDotNet50Asp
+  #define UseDotNet50Desktop
+  #define UseDotNet60
+  #define UseDotNet60Asp
+  #define UseDotNet60Desktop
+#endif
+
+#define UseVC2005
+#define UseVC2008
+#define UseVC2010
+#define UseVC2012
+#define UseVC2013
+#define UseVC2015To2022
+
+; requires dxwebsetup.exe (see download link below)
+;#define UseDirectX
+
+#define UseSql2008Express
+#define UseSql2012Express
+#define UseSql2014Express
+#define UseSql2016Express
+#define UseSql2017Express
+#define UseSql2019Express
+
+#define MyAppSetupName 'MyProgram'
+#define MyAppVersion '1.0'
+#define MyAppPublisher 'Inno Setup'
+#define MyAppCopyright 'Copyright © Inno Setup'
+#define MyAppURL 'https://jrsoftware.org/isinfo.php'
+
+AppName={#MyAppSetupName}
+AppVersion={#MyAppVersion}
+AppVerName={#MyAppSetupName} {#MyAppVersion}
+AppCopyright={#MyAppCopyright}
+VersionInfoVersion={#MyAppVersion}
+VersionInfoCompany={#MyAppPublisher}
+AppPublisher={#MyAppPublisher}
+AppPublisherURL={#MyAppURL}
+AppSupportURL={#MyAppURL}
+AppUpdatesURL={#MyAppURL}
+OutputBaseFilename={#MyAppSetupName}-{#MyAppVersion}
+DefaultGroupName={#MyAppSetupName}
+DefaultDirName={autopf}\{#MyAppSetupName}
+UninstallDisplayIcon={app}\MyProgram.exe
+SourceDir=src
+OutputDir={#SourcePath}\bin
+AllowNoIcons=yes
+PrivilegesRequired=admin
+
+; remove next line if you only deploy 32-bit binaries and dependencies
+ArchitecturesInstallIn64BitMode=x64
+
+[Languages]
+Name: en; MessagesFile: "compiler:Default.isl"
+Name: nl; MessagesFile: "compiler:Languages\Dutch.isl"
+Name: de; MessagesFile: "compiler:Languages\German.isl"
+
+[Files]
+#ifdef UseNetCoreCheck
+; download netcorecheck.exe: https://go.microsoft.com/fwlink/?linkid=2135256
+; download netcorecheck_x64.exe: https://go.microsoft.com/fwlink/?linkid=2135504
+Source: "netcorecheck.exe"; Flags: dontcopy noencryption
+Source: "netcorecheck_x64.exe"; Flags: dontcopy noencryption
+#endif
+
+#ifdef UseDirectX
+Source: "dxwebsetup.exe"; Flags: dontcopy noencryption
+#endif
+
+Source: "MyProg-x64.exe"; DestDir: "{app}"; DestName: "MyProg.exe"; Check: Dependency_IsX64; Flags: ignoreversion
+Source: "MyProg.exe"; DestDir: "{app}"; Check: not Dependency_IsX64; Flags: ignoreversion
+
+[Icons]
+Name: "{group}\{#MyAppSetupName}"; Filename: "{app}\MyProg.exe"
+Name: "{group}\{cm:UninstallProgram,{#MyAppSetupName}}"; Filename: "{uninstallexe}"
+Name: "{commondesktop}\{#MyAppSetupName}"; Filename: "{app}\MyProg.exe"; Tasks: desktopicon
+
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"
+
+[Run]
+Filename: "{app}\MyProg.exe"; Description: "{cm:LaunchProgram,{#MyAppSetupName}}"; Flags: nowait postinstall skipifsilent
+
+[Code]
+function InitializeSetup: Boolean;
+begin
+#ifdef UseDotNet35
+  Dependency_AddDotNet35;
+#endif
+#ifdef UseDotNet40
+  Dependency_AddDotNet40;
+#endif
+#ifdef UseDotNet45
+  Dependency_AddDotNet45;
+#endif
+#ifdef UseDotNet46
+  Dependency_AddDotNet46;
+#endif
+#ifdef UseDotNet47
+  Dependency_AddDotNet47;
+#endif
+#ifdef UseDotNet48
+  Dependency_AddDotNet48;
+#endif
+
+#ifdef UseNetCore31
+  Dependency_AddNetCore31;
+#endif
+#ifdef UseNetCore31Asp
+  Dependency_AddNetCore31Asp;
+#endif
+#ifdef UseNetCore31Desktop
+  Dependency_AddNetCore31Desktop;
+#endif
+#ifdef UseDotNet50
+  Dependency_AddDotNet50;
+#endif
+#ifdef UseDotNet50Asp
+  Dependency_AddDotNet50Asp;
+#endif
+#ifdef UseDotNet50Desktop
+  Dependency_AddDotNet50Desktop;
+#endif
+#ifdef UseDotNet60
+  Dependency_AddDotNet60;
+#endif
+#ifdef UseDotNet60Asp
+  Dependency_AddDotNet60Asp;
+#endif
+#ifdef UseDotNet60Desktop
+  Dependency_AddDotNet60Desktop;
+#endif
+
+#ifdef UseVC2005
+  Dependency_AddVC2005;
+#endif
+#ifdef UseVC2008
+  Dependency_AddVC2008;
+#endif
+#ifdef UseVC2010
+  Dependency_AddVC2010;
+#endif
+#ifdef UseVC2012
+  Dependency_AddVC2012;
+#endif
+#ifdef UseVC2013
+  //Dependency_ForceX86 := True; // force 32-bit install of next dependencies
+  Dependency_AddVC2013;
+  //Dependency_ForceX86 := False; // disable forced 32-bit install again
+#endif
+#ifdef UseVC2015To2022
+  Dependency_AddVC2015To2022;
+#endif
+
+#ifdef UseDirectX
+  ExtractTemporaryFile('dxwebsetup.exe');
+  Dependency_AddDirectX;
+#endif
+
+#ifdef UseSql2008Express
+  Dependency_AddSql2008Express;
+#endif
+#ifdef UseSql2012Express
+  Dependency_AddSql2012Express;
+#endif
+#ifdef UseSql2014Express
+  Dependency_AddSql2014Express;
+#endif
+#ifdef UseSql2016Express
+  Dependency_AddSql2016Express;
+#endif
+#ifdef UseSql2017Express
+  Dependency_AddSql2017Express;
+#endif
+#ifdef UseSql2019Express
+  Dependency_AddSql2019Express;
+#endif
+
+  Result := True;
+end;
+
+#endif
diff --git a/Setup/UDBuilder_setup.iss b/Setup/UDBuilder_setup.iss
new file mode 100644
index 0000000000000000000000000000000000000000..7dfb1283e94422da06ecbbc6d22f8c892b8fc09a
--- /dev/null
+++ b/Setup/UDBuilder_setup.iss
@@ -0,0 +1,150 @@
+; Script generated by the Inno Setup Script Wizard.
+; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+
+#define public Dependency_NoExampleSetup
+#include "CodeDependencies.iss"
+
+;#define UDB_arch "x64"
+#define UDB_GetVersionString() \
+  Local[0] = GetVersionNumbersString("..\Build\Builder.exe"), \
+  Delete(Local[0], 0, RPos(".", Local[0])+1), \
+  "R" + Local[0]
+
+[Setup]
+AppName=Ultimate Doom Builder
+AppVersion={#UDB_GetVersionString} ({#UDB_arch})
+;AppVerName=Ultimate Doom Builder {#UDB_GetVersionString} ({#UDB_arch})
+AppPublisher=ZZYZX
+AppPublisherURL=https://forum.zdoom.org/memberlist.php?mode=viewprofile&u=7527
+AppSupportURL=https://forum.zdoom.org/viewtopic.php?f=232&t=66745
+AppUpdatesURL=https://devbuilds.drdteam.org/ultimatedoombuilder/
+DefaultDirName={userpf}\Ultimate Doom Builder
+DefaultGroupName=Ultimate Doom Builder
+AllowNoIcons=true
+LicenseFile=..\LICENSE.txt
+OutputDir=..\Release
+OutputBaseFilename=Setup
+Compression=lzma/ultra64
+SolidCompression=true
+SourceDir=..\Build
+SetupLogging=false
+AppMutex=ultimatedoombuilder
+PrivilegesRequired=lowest
+ShowLanguageDialog=no
+LanguageDetectionMethod=none
+MinVersion=0,6.1sp1
+UninstallDisplayIcon={app}\Updater.exe
+WizardImageFile=..\Setup\WizModernImage-IS.bmp
+WizardSmallImageFile=..\Setup\WizModernSmallImage-IS.bmp
+#if UDB_arch != "x86"
+ArchitecturesInstallIn64BitMode=x64
+ArchitecturesAllowed=x64
+#endif
+
+[Languages]
+Name: english; MessagesFile: compiler:Default.isl
+
+[Tasks]
+Name: desktopicon; Description: {cm:CreateDesktopIcon}; GroupDescription: {cm:AdditionalIcons}; Flags: unchecked
+
+[Files]
+Source: Builder.exe; DestDir: {app}; Flags: ignoreversion
+Source: Builder.pdb; DestDir: {app}; Flags: ignoreversion
+Source: Builder.exe.config; DestDir: {app}; Flags: ignoreversion
+Source: UDBuilder.default.cfg; DestDir: {app}; Flags: ignoreversion
+Source: Updater.exe; DestDir: {app}; Flags: ignoreversion
+Source: Updater.ini; DestDir: {app}; Flags: ignoreversion
+Source: Refmanual.chm; DestDir: {app}; Flags: ignoreversion
+Source: BuilderNative.dll; DestDir: {app}; Flags: ignoreversion
+Source: BuilderNative.pdb; DestDir: {app}; Flags: ignoreversion
+Source: SharpCompress.dll; DestDir: {app}; Flags: ignoreversion
+Source: ScintillaNET.dll; DestDir: {app}; Flags: ignoreversion
+Source: TabControlEX.dll; DestDir: {app}; Flags: ignoreversion
+Source: System.Buffers.dll; DestDir: {app}; Flags: ignoreversion
+Source: System.Memory.dll; DestDir: {app}; Flags: ignoreversion
+Source: System.Numerics.Vectors.dll; DestDir: {app}; Flags: ignoreversion
+Source: System.Runtime.CompilerServices.Unsafe.dll; DestDir: {app}; Flags: ignoreversion
+Source: LICENSE.txt; DestDir: {app}; Flags: ignoreversion
+Source: Compilers\*; DestDir: {app}\Compilers; Flags: ignoreversion recursesubdirs
+Source: Configurations\*; DestDir: {app}\Configurations; Flags: ignoreversion recursesubdirs
+Source: Scripting\*; DestDir: {app}\Scripting; Flags: ignoreversion recursesubdirs
+Source: Snippets\*; DestDir: {app}\Snippets; Flags: ignoreversion recursesubdirs
+Source: UDBScript\udbscript.d.ts; DestDir: {app}\UDBScript; Flags: ignoreversion
+Source: UDBScript\Libraries\*; DestDir: {app}\UDBScript\Libraries; Flags: ignoreversion recursesubdirs
+Source: UDBScript\Scripts\Examples\*; DestDir: {app}\UDBScript\Scripts\Examples; Flags: ignoreversion recursesubdirs
+; NOTE: Don't use "Flags: ignoreversion" on any shared system files
+Source: Plugins\AutomapMode.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\AutomapMode.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\BuilderModes.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\BuilderModes.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\BuilderEffects.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\BuilderEffects.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\ColorPicker.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\ColorPicker.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\CommentsPanel.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\CommentsPanel.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\NodesViewer.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\NodesViewer.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\SoundPropagationMode.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\SoundPropagationMode.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\StairSectorBuilder.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\StairSectorBuilder.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\TagExplorer.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\TagExplorer.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\TagRange.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\TagRange.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\ThreeDFloorMode.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\ThreeDFloorMode.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\UDBScript.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\UDBScript.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\VisplaneExplorer.dll; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\VisplaneExplorer.pdb; DestDir: {app}\Plugins; Flags: ignoreversion
+Source: Plugins\Loadorder.cfg; DestDir: {app}\Plugins; Flags: ignoreversion onlyifdoesntexist
+Source: Plugins\Dependencies\*; DestDir: {app}\Plugins\Dependencies; Flags: ignoreversion
+Source: Sprites\*; DestDir: {app}\Sprites; Flags: ignoreversion recursesubdirs
+Source: Textures\*; DestDir: {app}\Textures; Flags: ignoreversion
+
+[Icons]
+Name: {group}\Ultimate Doom Builder; Filename: {app}\Builder.exe
+Name: {group}\{cm:UninstallProgram,Ultimate Doom Builder}; Filename: {uninstallexe}
+Name: {autodesktop}\Ultimate Doom Builder; Filename: {app}\Builder.exe; Tasks: desktopicon
+
+[Run]
+Filename: {app}\Builder.exe; Description: Run {#SetupSetting("AppName")}; Flags: postinstall skipifsilent
+
+[UninstallDelete]
+Name: {app}; Type: filesandordirs
+
+;[InstallDelete]
+;Name: {app}\Builder.pdb; Type: files
+;Name: {app}\Builder.xml; Type: files
+
+[Registry]
+Root: HKCU; Subkey: SOFTWARE\Ultimate Doom Builder\; ValueType: string; ValueName: Location; ValueData: {app}; Flags: uninsdeletevalue
+
+[Messages]
+ReadyLabel2a=Continue to begin with the installation, or click Back if you want to review or change any settings.
+
+[Code]
+// When the wizard initializes
+procedure InitializeWizard();
+begin
+  // .Net and VC Redistributables. Those come from CodeDependencies.iss
+  Dependency_AddDotNet47;
+  //Dependency_AddVC2015To2022;
+end;
+
+//Remove configs?
+procedure DeinitializeUninstall();
+begin
+	if MsgBox('Delete map restore data and program configuration files?', mbConfirmation, MB_YESNO) = IDYES then
+	begin
+		// Remove restore data
+		DelTree(ExpandConstant('{localappdata}\Doom Builder\Restore'), True, True, True);
+
+		// Remove configs
+		DeleteFile(ExpandConstant('{localappdata}\Doom Builder\UDBuilder.cfg'));
+		DeleteFile(ExpandConstant('{localappdata}\Doom Builder\UDBuilder.log'));
+		DeleteFile(ExpandConstant('{localappdata}\Doom Builder\UDBCrash.txt'));
+	end;
+end;
\ No newline at end of file
diff --git a/Setup/gzbuilder_setup.iss b/Setup/gzbuilder_setup.iss
deleted file mode 100755
index c19015308f6f9a1e9ebe6214324a2c89b91e6e22..0000000000000000000000000000000000000000
--- a/Setup/gzbuilder_setup.iss
+++ /dev/null
@@ -1,288 +0,0 @@
-; Script generated by the Inno Setup Script Wizard.
-; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
-
-[Setup]
-AppName=Ultimate Zone Builder
-AppVerName=Ultimate Zone Builder
-AppPublisher=Sonic Team Junior
-AppPublisherURL=https://www.srb2.org/
-AppSupportURL=https://www.srb2.org/
-AppUpdatesURL=https://www.srb2.org/
-DefaultDirName={pf}\Ultimate Zone Builder
-DefaultGroupName=Ultimate Zone Builder
-AllowNoIcons=true
-LicenseFile=..\LICENSE.txt
-OutputDir=..\Release
-OutputBaseFilename=Setup
-Compression=lzma/ultra64
-SolidCompression=true
-SourceDir=..\Build
-SetupLogging=false
-AppMutex=ultimatezonebuilder
-PrivilegesRequired=admin
-ShowLanguageDialog=no
-LanguageDetectionMethod=none
-MinVersion=0,6.0
-UninstallDisplayIcon={app}\Updater.exe
-WizardImageFile=..\Setup\WizModernImage-IS.bmp
-WizardSmallImageFile=..\Setup\WizModernSmallImage-IS.bmp
-
-[Languages]
-Name: english; MessagesFile: compiler:Default.isl
-
-[Tasks]
-Name: desktopicon; Description: {cm:CreateDesktopIcon}; GroupDescription: {cm:AdditionalIcons}; Flags: unchecked
-
-[Files]
-Source: Setup\dotnetfx35setup.exe; DestDir: {tmp}; Flags: dontcopy
-Source: Setup\vcredist_x86.exe; DestDir: {tmp}; Flags: dontcopy
-Source: Builder.exe; DestDir: {app}; Flags: ignoreversion
-Source: Builder.exe.config; DestDir: {app}; Flags: ignoreversion
-Source: UDBuilder.default.cfg; DestDir: {app}; Flags: ignoreversion
-//Source: Updater.exe; DestDir: {app}; Flags: ignoreversion
-Source: Updater.ini; DestDir: {app}; Flags: ignoreversion
-//Source: Refmanual.chm; DestDir: {app}; Flags: ignoreversion
-//Source: DevIL.dll; DestDir: {app}; Flags: ignoreversion
-Source: BuilderNative.dll; DestDir: {app}; Flags: ignoreversion
-Source: SharpCompress.dll; DestDir: {app}; Flags: ignoreversion
-Source: ScintillaNET.dll; DestDir: {app}; Flags: ignoreversion
-Source: TabControlEX.dll; DestDir: {app}; Flags: ignoreversion
-Source: LICENSE.txt; DestDir: {app}; Flags: ignoreversion
-Source: Compilers\*; DestDir: {app}\Compilers; Flags: ignoreversion recursesubdirs
-Source: Configurations\*; DestDir: {app}\Configurations; Flags: ignoreversion recursesubdirs
-Source: Scripting\*; DestDir: {app}\Scripting; Flags: ignoreversion recursesubdirs
-Source: Snippets\*; DestDir: {app}\Snippets; Flags: ignoreversion recursesubdirs
-Source: UDBScript\*; DestDir: {app}\UDBScript; Flags: ignoreversion recursesubdirs
-; NOTE: Don't use "Flags: ignoreversion" on any shared system files
-Source: Plugins\Dependencies\Esprima.dll; DestDir: {app}\Plugins\Dependencies; Flags: ignoreversion
-Source: Plugins\Dependencies\Jint.dll; DestDir: {app}\Plugins\Dependencies; Flags: ignoreversion
-Source: Plugins\AutomapMode.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\BuilderModes.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\BuilderEffects.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\ColorPicker.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\CommentsPanel.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\NodesViewer.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\SoundPropagationMode.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\StairSectorBuilder.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\TagExplorer.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\TagRange.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\ThreeDFloorMode.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\UDBScript.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\VisplaneExplorer.dll; DestDir: {app}\Plugins; Flags: ignoreversion
-Source: Plugins\Loadorder.cfg; DestDir: {app}\Plugins; Flags: ignoreversion onlyifdoesntexist
-Source: Sprites\*; DestDir: {app}\Sprites; Flags: ignoreversion recursesubdirs
-Source: Textures\*; DestDir: {app}\Textures; Flags: ignoreversion
-
-[Icons]
-Name: {group}\Ultimate Zone Builder; Filename: {app}\Builder.exe
-Name: {group}\{cm:UninstallProgram,Ultimate Zone Builder}; Filename: {uninstallexe}
-Name: {commondesktop}\Ultimate Zone Builder; Filename: {app}\Builder.exe; Tasks: desktopicon
-
-[UninstallDelete]
-Name: {app}; Type: filesandordirs
-
-[InstallDelete]
-Name: {app}\Builder.pdb; Type: files
-Name: {app}\Builder.xml; Type: files
-
-[Registry]
-Root: HKLM; Subkey: SOFTWARE\UZB\Ultimate Zone Builder\; ValueType: string; ValueName: Location; ValueData: {app}; Flags: uninsdeletevalue
-
-[Messages]
-ReadyLabel2a=Continue to begin with the installation, or click Back if you want to review or change any settings.
-
-[Code]
-// Global variables
-var
-	page_info_net: TOutputMsgWizardPage;
-	page_info_netfailed: TOutputMsgWizardPage;
-	page_setup_net: TOutputProgressWizardPage;
-	page_info_vc: TOutputMsgWizardPage;
-	page_info_vcfailed: TOutputMsgWizardPage;
-	page_setup_vc: TOutputProgressWizardPage;
-	restartneeded: Boolean;
-	netinstallfailed: Boolean;
-	netisinstalled: Boolean;
-	vcinstallfailed: Boolean;
-	vcisinstalled: Boolean;
-
-// Prerequisites checks
-function CheckNetIsInstalled(): Boolean;
-begin
-	Result := RegKeyExists(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5') or
-					  RegKeyExists(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v3.5');
-end;
-
-function CheckVCIsInstalled(): Boolean;
-begin
-	//mxd. Any VC++ 2008 package will do, I assume...
-	//mxd. Registry values gartered from http://blogs.msdn.com/b/astebner/archive/2009/01/29/9384143.aspx
-	Result := RegKeyExists(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1F1C2DFC-2D24-3E06-BCB8-725134ADF989}') or
-			  RegKeyExists(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{9A25302D-30C0-39D9-BD6F-21E6EC160475}') or
-			  RegKeyExists(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{FF66E9F6-83E7-3A3E-AF14-8DE9A809A6A4}');
-end;
-
-// When the wizard initializes
-procedure InitializeWizard();
-begin
-	restartneeded := false;
-	netinstallfailed := false;
-	netisinstalled := CheckNetIsInstalled();
-	vcinstallfailed := false;
-	vcisinstalled := CheckVCIsInstalled();
-
-	// Create .NET Framework pages
-	page_info_net := CreateOutputMsgPage(wpPreparing,
-		'Installing Microsoft .NET Framework 3.5', '',
-		'Setup has detected that your system is missing the required version of the Microsoft .NET Framework. ' +
-		'Setup will now install or update your Microsoft .NET Framework. This may take several minutes to complete.' + #10 + #10 +
-		'WARNING: The installer will download the Microsoft .NET Framework from the internet, but the progress bar will not ' +
-		'go forward until the download is complete. You may send Microsoft an angry letter about that.' + #10 + #10 +
-		'Click Install to begin.');
-
-	page_info_netfailed := CreateOutputMsgPage(page_info_net.ID,
-		'Installing Microsoft .NET Framework  3.5', '',
-		'Setup could not install the Microsoft .NET Framework  3.5.' + #10 + #10 +
-		'Click Back to try again, or Cancel to exit Setup.');
-
-	page_setup_net := CreateOutputProgressPage('Installing Microsoft .NET Framework 3.5', 'Setup is installing Microsoft .NET Framework 3.5, please wait...');
-
-	// Create VC++ 2008 pages
-	page_info_vc := CreateOutputMsgPage(wpPreparing,
-		'Installing Visual C++ 2008 SP1 ATL Security Update', '',
-		'Setup has detected that your system is missing the required version of the Visual C++ Runtime. ' +
-		'Setup will now install or update your Visual C++ Runtime. This may take several minutes to complete.' + #10 + #10 +
-		'Click Install to begin.');
-
-	page_info_vcfailed := CreateOutputMsgPage(page_info_net.ID,
-		'Installing Visual C++ 2008 SP1 ATL Security Update', '',
-		'Setup could not install Visual C++ 2008 SP1 ATL Security Update.' + #10 + #10 +
-		'Click Back to try again, or Cancel to exit Setup.');
-
-	page_setup_vc := CreateOutputProgressPage('Installing Visual C++ 2008 SP1 ATL Security Update', 'Setup is installing Visual C++ 2008 SP1 ATL Security Update, please wait...');
-end;
-
-// This is called to check if a page must be skipped
-function ShouldSkipPage(PageID: Integer): Boolean;
-begin
-	if(PageID = page_info_net.ID) then // Skip .NET pages?
-		Result := netisinstalled
-	else if(PageID = page_info_netfailed.ID) then
-		Result := (not netinstallfailed) and netisinstalled
-	else if(PageID = page_info_vc.ID) then // Skip VC++ pages?
-		Result := vcisinstalled
-	else if(PageID = page_info_vcfailed.ID) then
-		Result := (not vcinstallfailed) and vcisinstalled
-	else
-		Result := false;
-end;
-
-// This is called to determine if we need to restart
-function NeedRestart(): Boolean;
-begin
-	Result := restartneeded;
-end;
-
-// This is called when the current page changes
-procedure CurPageChanged(CurPageID: Integer);
-begin
-	if(CurPageID = wpReady) then begin
-		if(netisinstalled = false) or (vcisinstalled = false) then
-			WizardForm.NextButton.Caption := 'Next';
-	end
-	else if(CurPageID = page_info_net.ID) or (CurPageID = page_info_vc.ID) then begin
-		WizardForm.NextButton.Caption := 'Install';
-	end
-	else if(CurPageID = page_info_netfailed.ID) or (CurPageID = page_info_vcfailed.ID) then begin
-		WizardForm.NextButton.Visible := true;
-		WizardForm.NextButton.Enabled := false;
-		WizardForm.BackButton.Visible := true;
-		WizardForm.BackButton.Enabled := true;
-		WizardForm.CancelButton.Visible := true;
-		WizardForm.CancelButton.Enabled := true;
-	end;
-end;
-
-// This is called when the Next button is clicked
-function NextButtonClick(CurPage: Integer): Boolean;
-var
-	errorcode: Integer;
-	tempfile: String;
-begin
-
-	// Next pressed on .NET info page?
-	if(CurPage = page_info_net.ID) then begin
-		// Show progress page and run setup
-		page_setup_net.Show;
-		try
-		begin
-			netinstallfailed := false;
-			ExtractTemporaryFile('dotnetfx35setup.exe');
-			// We copy the file to the real temp directory so that it isn't removed when Setup is closed.
-			// Judging from the return codes, this installer may want to run again after a reboot.
-			// See the return codes here: http://msdn.microsoft.com/en-us/library/cc160716.aspx
-			tempfile := RemoveBackslash(GetTempDir()) + '\dotnetfx35setup.exe';
-			FileCopy(ExpandConstant('{tmp}\dotnetfx35setup.exe'), tempfile, false);
-			Exec(tempfile, '/qb /norestart', '', SW_SHOW, ewWaitUntilTerminated, errorcode);
-
-			if((errorcode = 1641) or (errorcode = 3010)) then begin
-				// Success, but restart needed!
-				restartneeded := true;
-			end
-			else if(errorcode <> 0) then begin
-				netinstallfailed := true;
-			end;
-
-			netisinstalled := CheckNetIsInstalled();
-		end
-		finally
-			page_setup_net.Hide;
-		end;
-	end
-	// Next pressed on VC info page?
-	else if(CurPage = page_info_vc.ID) then begin
-		// Show progress page and run setup
-		page_setup_vc.Show;
-		try
-		begin
-			vcinstallfailed := false;
-			ExtractTemporaryFile('vcredist_x86.exe');
-			// We copy the file to the real temp directory so that it isn't removed when Setup is closed.
-			// Judging from the return codes, this installer may want to run again after a reboot.
-			// See the return codes here: http://blogs.msdn.com/b/astebner/archive/2010/10/20/10078468.aspx
-			tempfile := RemoveBackslash(GetTempDir()) + '\vcredist_x86.exe';
-			FileCopy(ExpandConstant('{tmp}\vcredist_x86.exe'), tempfile, false);
-			Exec(tempfile, '/q /norestart', '', SW_SHOW, ewWaitUntilTerminated, errorcode);
-
-			if(errorcode = 3010) then begin
-				// Success, but restart needed!
-				restartneeded := true;
-			end
-			else if(errorcode <> 0) then begin
-				vcinstallfailed := true;
-			end;
-
-			vcisinstalled := CheckVCIsInstalled();
-		end
-		finally
-			page_setup_vc.Hide;
-		end;
-	end;
-
-	Result := True;
-end;
-
-//Remove configs?
-procedure DeinitializeUninstall();
-begin
-	if MsgBox('Delete map restore data and program configuration files?', mbConfirmation, MB_YESNO) = IDYES then
-	begin
-		// Remove restore data
-		DelTree(ExpandConstant('{localappdata}\Doom Builder\Restore'), True, True, True);
-
-		// Remove configs
-		DeleteFile(ExpandConstant('{localappdata}\Doom Builder\GZBuilder.cfg'));
-		DeleteFile(ExpandConstant('{localappdata}\Doom Builder\GZBuilder.log'));
-		DeleteFile(ExpandConstant('{localappdata}\Doom Builder\GZCrash.txt'));
-	end;
-end;
\ No newline at end of file
diff --git a/Source/Core/Actions/Action.cs b/Source/Core/Actions/Action.cs
index 08dedecc220cf059a9137b36ad2e53ffb8e2683e..00a23a54e27c9ba54db502bd7fd42eb1a0e1d036 100755
--- a/Source/Core/Actions/Action.cs
+++ b/Source/Core/Actions/Action.cs
@@ -34,6 +34,7 @@ namespace CodeImp.DoomBuilder.Actions
 		private readonly string title;
 		private readonly string description;
 		private readonly string category;
+		private readonly bool registertoast;
 
 		// Shortcut key
 		private int key;
@@ -62,6 +63,7 @@ namespace CodeImp.DoomBuilder.Actions
 		public string Category { get { return category; } }
 		public string Title { get { return title; } }
 		public string Description { get { return description; } }
+		public bool RegisterToast { get { return registertoast; } }
 		public int ShortcutKey { get { return key; } }
 		public int ShortcutMask { get { return keymask; } }
 		public int DefaultShortcutKey { get { return defaultkey; } }
@@ -88,6 +90,7 @@ namespace CodeImp.DoomBuilder.Actions
 			this.title = cfg.ReadSetting(shortname + ".title", "[" + name + "]");
 			this.category = cfg.ReadSetting(shortname + ".category", "");
 			this.description = cfg.ReadSetting(shortname + ".description", "");
+			this.registertoast = cfg.ReadSetting(shortname + ".registertoast", false);
 			this.allowkeys = cfg.ReadSetting(shortname + ".allowkeys", true);
 			this.allowmouse = cfg.ReadSetting(shortname + ".allowmouse", true);
 			this.allowscroll = cfg.ReadSetting(shortname + ".allowscroll", false);
diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index 675b6ad35503d2d07568d70c7188bf03618654a6..9071d69108ee6fff650881b88be0f92e469a58d6 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -144,6 +144,7 @@
     <Compile Include="Compilers\NodesCompiler.cs" />
     <Compile Include="Config\ArgumentInfo.cs" />
     <Compile Include="Config\ExternalCommandSettings.cs" />
+    <Compile Include="Config\RequiredArchive.cs" />
     <Compile Include="Config\StaticLimits.cs" />
     <Compile Include="Config\MapLumpInfo.cs" />
     <Compile Include="Config\ScriptConfiguration.cs" />
@@ -186,6 +187,12 @@
     <Compile Include="Controls\ArgumentsControlSRB2.Designer.cs">
       <DependentUpon>ArgumentsControlSRB2.cs</DependentUpon>
     </Compile>
+    <Compile Include="Controls\CommandPaletteControl.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Controls\CommandPaletteControl.Designer.cs">
+      <DependentUpon>CommandPaletteControl.cs</DependentUpon>
+    </Compile>
     <Compile Include="Controls\ExternalCommandControl.cs">
       <SubType>UserControl</SubType>
     </Compile>
@@ -197,6 +204,21 @@
       <SubType>Component</SubType>
     </Compile>
     <Compile Include="Controls\Scripting\TextEditorControl.cs" />
+    <Compile Include="Controls\SidedefPartLightControl.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Controls\SidedefPartLightControl.Designer.cs">
+      <DependentUpon>SidedefPartLightControl.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\ToastControl.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Controls\ToastControl.Designer.cs">
+      <DependentUpon>ToastControl.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\ToolStripActionButton.cs">
+      <SubType>Component</SubType>
+    </Compile>
     <Compile Include="Controls\TransparentTrackBar.cs">
       <SubType>Component</SubType>
     </Compile>
@@ -226,6 +248,7 @@
     <Compile Include="Dehacked\DehackedParser.cs" />
     <Compile Include="Dehacked\DehackedThing.cs" />
     <Compile Include="General\SHA256Hash.cs" />
+    <Compile Include="General\ToastManager.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>
       <DesignTime>True</DesignTime>
@@ -676,6 +699,7 @@
       <HintPath>..\..\Build\SharpCompress.dll</HintPath>
     </Reference>
     <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL" />
     <Reference Include="System.Core">
       <RequiredTargetFramework>3.5</RequiredTargetFramework>
     </Reference>
@@ -683,16 +707,23 @@
     <Reference Include="System.Design" />
     <Reference Include="System.Drawing" />
     <Reference Include="System.Management" />
+    <Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml" />
     <Reference Include="TabControlEX, Version=0.0.3271.41578, Culture=neutral, PublicKeyToken=1db242dc828e4b4e">
       <Private>False</Private>
     </Reference>
+    <Reference Include="WindowsBase" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Controls\ArgumentsControlSRB2.resx">
       <DependentUpon>ArgumentsControlSRB2.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Controls\CommandPaletteControl.resx">
+      <DependentUpon>CommandPaletteControl.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Controls\ExternalCommandControl.resx">
       <DependentUpon>ExternalCommandControl.cs</DependentUpon>
     </EmbeddedResource>
@@ -702,6 +733,12 @@
     <EmbeddedResource Include="Windows\LinedefEditFormSRB2.resx">
       <DependentUpon>LinedefEditFormSRB2.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Controls\SidedefPartLightControl.resx">
+      <DependentUpon>SidedefPartLightControl.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Controls\ToastControl.resx">
+      <DependentUpon>ToastControl.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Windows\PreAndPostCommandsForm.resx">
       <DependentUpon>PreAndPostCommandsForm.cs</DependentUpon>
     </EmbeddedResource>
@@ -1389,6 +1426,7 @@
     <None Include="Resources\MergeGeo.png" />
     <None Include="Resources\MergeGeoClassic.png" />
     <None Include="Resources\MergeGeoRemoveLines.png" />
+    <None Include="Resources\Loader.gif" />
     <Content Include="Resources\Model.png" />
     <None Include="Resources\ModelDisabled.png" />
     <None Include="Resources\ModelFiltered.png" />
diff --git a/Source/Core/BuilderMono.csproj b/Source/Core/BuilderMono.csproj
index 2c2dd38d62207bb250d85ccb2466c0242fc11aeb..89a5e292e4b2f2dc708af879f08d369f9dbe34e7 100644
--- a/Source/Core/BuilderMono.csproj
+++ b/Source/Core/BuilderMono.csproj
@@ -160,6 +160,7 @@
     <Compile Include="Config\LinedefActivateInfo.cs" />
     <Compile Include="Config\ProgramConfiguration.cs" />
     <Compile Include="Config\SkillInfo.cs" />
+    <Compile Include="Config\RequiredArchive.cs" />
     <Compile Include="Config\StaticLimits.cs" />
     <Compile Include="Config\TagType.cs" />
     <Compile Include="Config\TextureSet.cs" />
@@ -183,6 +184,12 @@
     <Compile Include="Controls\ArgumentsControlSRB2.Designer.cs">
       <DependentUpon>ArgumentsControlSRB2.cs</DependentUpon>
     </Compile>
+    <Compile Include="Controls\CommandPaletteControl.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Controls\CommandPaletteControl.Designer.cs">
+      <DependentUpon>CommandPaletteControl.cs</DependentUpon>
+    </Compile>
     <Compile Include="Controls\ExternalCommandControl.cs">
       <SubType>UserControl</SubType>
     </Compile>
@@ -194,6 +201,21 @@
       <SubType>Component</SubType>
     </Compile>
     <Compile Include="Controls\Scripting\TextEditorControl.cs" />
+    <Compile Include="Controls\SidedefPartLightControl.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Controls\SidedefPartLightControl.Designer.cs">
+      <DependentUpon>SidedefPartLightControl.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\ToastControl.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
+    <Compile Include="Controls\ToastControl.Designer.cs">
+      <DependentUpon>ToastControl.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Controls\ToolStripActionButton.cs">
+      <SubType>Component</SubType>
+    </Compile>
     <Compile Include="Controls\TransparentTrackBar.cs">
       <SubType>Component</SubType>
     </Compile>
@@ -218,6 +240,7 @@
     <Compile Include="Data\FlatImage.cs" />
     <Compile Include="Data\ImageLoadState.cs" />
     <Compile Include="General\SHA256Hash.cs" />
+    <Compile Include="General\ToastManager.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>
       <DesignTime>True</DesignTime>
@@ -685,6 +708,9 @@
     <EmbeddedResource Include="Controls\ArgumentsControlSRB2.resx">
       <DependentUpon>ArgumentsControlSRB2.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Controls\CommandPaletteControl.resx">
+      <DependentUpon>CommandPaletteControl.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Controls\ExternalCommandControl.resx">
       <DependentUpon>ExternalCommandControl.cs</DependentUpon>
     </EmbeddedResource>
@@ -694,6 +720,12 @@
     <EmbeddedResource Include="Windows\LinedefEditFormSRB2.resx">
       <DependentUpon>LinedefEditFormSRB2.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Controls\SidedefPartLightControl.resx">
+      <DependentUpon>SidedefPartLightControl.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Controls\ToastControl.resx">
+      <DependentUpon>ToastControl.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Windows\PreAndPostCommandsForm.resx">
       <DependentUpon>PreAndPostCommandsForm.cs</DependentUpon>
     </EmbeddedResource>
diff --git a/Source/Core/Compilers/AccCompiler.cs b/Source/Core/Compilers/AccCompiler.cs
index fda95a8a4a324f6a6e4e37ce4f8d113e6b7b84f6..867d1a47150e365b4b1dcb603a50702b0003dcb8 100755
--- a/Source/Core/Compilers/AccCompiler.cs
+++ b/Source/Core/Compilers/AccCompiler.cs
@@ -105,7 +105,7 @@ namespace CodeImp.DoomBuilder.Compilers
 				// Map SCRIPTS lump is empty. Abort the process without generating any warnings or errors. 
 				if(SourceIsMapScriptsLump && stream.Length == 0) return false;
 
-				DataLocation dl = new DataLocation(DataLocation.RESOURCE_DIRECTORY, Path.GetDirectoryName(inputfilepath), false, false, false);
+				DataLocation dl = new DataLocation(DataLocation.RESOURCE_DIRECTORY, Path.GetDirectoryName(inputfilepath), false, false, false, null);
 				//mxd. TextResourceData must point to temp path when compiling WAD lumps for lump to be recognized as map lump when reporting errors...
 				TextResourceData data = new TextResourceData(stream, dl, (SourceIsMapScriptsLump ? inputfile : sourcefile));
 				if(!parser.Parse(data, info.Files, true, AcsParserSE.IncludeType.NONE, false))
diff --git a/Source/Core/Config/AllTexturesSet.cs b/Source/Core/Config/AllTexturesSet.cs
index 0fc50612b2efd7769c59e1d6d0611207361a68ed..0bd584600d9c2230e0fa4e4b6ddb3e29106e846b 100755
--- a/Source/Core/Config/AllTexturesSet.cs
+++ b/Source/Core/Config/AllTexturesSet.cs
@@ -65,7 +65,7 @@ namespace CodeImp.DoomBuilder.Config
 		{
 			//mxd. Use short name when adding a texture with "classic" name to override same-named textures 
 			// with textures loaded from directory/pk3 containters
-			textures[image.DisplayName.Length > 8 ? image.Name : image.ShortName] = image;
+			textures[/*image.DisplayName.Length > 8 ?*/ image.Name /*: image.ShortName*/] = image;
 		}
 
 		internal void AddFlat(ImageData image)
diff --git a/Source/Core/Config/GameConfiguration.cs b/Source/Core/Config/GameConfiguration.cs
old mode 100755
new mode 100644
index 980fa115558d0dc099f08df1e6f13c7f79b9c675..3625ef176e23119329a6cd16a19c34b3cd8ea82c
--- a/Source/Core/Config/GameConfiguration.cs
+++ b/Source/Core/Config/GameConfiguration.cs
@@ -113,6 +113,8 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly bool planeequationsupport;
 		private readonly bool distinctfloorandceilingbrightness;
 		private readonly bool distinctwallbrightness;
+		private readonly bool distinctsidedefpartbrightness;
+		private readonly bool sectormultitag;
 		private readonly int maxcolormapalpha;
 		private readonly int numbrightnesslevels;
 
@@ -200,6 +202,9 @@ namespace CodeImp.DoomBuilder.Config
 		//mxd. Stuff to ignore
 		private HashSet<string> ignoreddirectories;
 		private HashSet<string> ignoredextensions;
+
+		// [ZZ] This implements error message if GZDoom.pk3 is required but not loaded
+		private List<RequiredArchive> requiredarchives;
 		
 		// Defaults
 		private readonly List<DefinedTextureSet> texturesets;
@@ -294,6 +299,8 @@ namespace CodeImp.DoomBuilder.Config
 		public bool PlaneEquationSupport { get { return planeequationsupport; } }
 		public bool DistinctFloorAndCeilingBrightness { get { return distinctfloorandceilingbrightness; } }
 		public bool DistinctWallBrightness { get { return distinctwallbrightness; } }
+		public bool DistinctSidedefPartBrightness { get { return distinctsidedefpartbrightness; } }
+		public bool SectorMultiTag { get { return sectormultitag; } }
 		public int MaxColormapAlpha { get { return maxcolormapalpha; } }
 		public int NumBrightnessLevels { get { return numbrightnesslevels; } }
 
@@ -362,6 +369,9 @@ namespace CodeImp.DoomBuilder.Config
 		internal HashSet<string> IgnoredFileExtensions { get { return ignoredextensions; } }
 		internal HashSet<string> IgnoredDirectoryNames { get { return ignoreddirectories; } }
 
+		// [ZZ] This implements error message if GZDoom.pk3 is required but not loaded
+		internal List<RequiredArchive> RequiredArchives { get { return requiredarchives; } }
+
 		// Defaults
 		internal List<DefinedTextureSet> TextureSets { get { return texturesets; } }
 		public List<ThingsFilter> ThingsFilters { get { return thingfilters; } }
@@ -477,6 +487,9 @@ namespace CodeImp.DoomBuilder.Config
 			planeequationsupport = cfg.ReadSetting("planeequationsupport", false);
 			distinctfloorandceilingbrightness = cfg.ReadSetting("distinctfloorandceilingbrightness", false);
 			distinctwallbrightness = cfg.ReadSetting("distinctwallbrightness", false);
+			distinctsidedefpartbrightness = cfg.ReadSetting("distinctsidedefpartbrightness", false);
+			sectormultitag = cfg.ReadSetting("sectormultitag", false);
+			for (int i = 0; i < Linedef.NUM_ARGS; i++) makedoorargs[i] = cfg.ReadSetting("makedoorarg" + i.ToString(CultureInfo.InvariantCulture), 0);
 			maxcolormapalpha = cfg.ReadSetting("maxcolormapalpha", 25);
 			numbrightnesslevels = cfg.ReadSetting("numbrightnesslevels", 32);
 			for (int i = 0; i < makedoorargs.Length; i++) makedoorargs[i] = cfg.ReadSetting("makedoorarg" + i.ToString(CultureInfo.InvariantCulture), 0);
@@ -547,6 +560,25 @@ namespace CodeImp.DoomBuilder.Config
 			ignoreddirectories = new HashSet<string>(cfg.ReadSetting("ignoreddirectories", string.Empty).Split(splitter, StringSplitOptions.RemoveEmptyEntries), StringComparer.OrdinalIgnoreCase);
 			ignoredextensions = new HashSet<string>(cfg.ReadSetting("ignoredextensions", string.Empty).Split(splitter, StringSplitOptions.RemoveEmptyEntries), StringComparer.OrdinalIgnoreCase);
 
+			// [ZZ]
+			IDictionary requiredArchives = cfg.ReadSetting("requiredarchives", new Hashtable());
+			requiredarchives = new List<RequiredArchive>();
+			foreach (DictionaryEntry cde in requiredArchives)
+            {
+				string filename = cfg.ReadSetting("requiredarchives." + cde.Key + ".filename", "gzdoom.pk3");
+				bool exclude = cfg.ReadSetting("requiredarchives." + cde.Key + ".need_exclude", true);
+				IDictionary entries = cfg.ReadSetting("requiredarchives." + cde.Key, new Hashtable());
+				List<RequiredArchiveEntry> reqEntries = new List<RequiredArchiveEntry>();
+				foreach (DictionaryEntry cde2 in entries)
+                {
+					if ((string)cde2.Key == "filename") continue;
+					string lumpname = cfg.ReadSetting("requiredarchives." + cde.Key + "." + cde2.Key + ".lump", (string)null);
+					string classname = cfg.ReadSetting("requiredarchives." + cde.Key + "." + cde2.Key + ".class", (string)null);
+					reqEntries.Add(new RequiredArchiveEntry(classname, lumpname));
+                }
+				requiredarchives.Add(new RequiredArchive((string)cde.Key, filename, exclude, reqEntries));
+            }
+
 			// Things
 			LoadThingFlags();
 			LoadDefaultThingFlags();
diff --git a/Source/Core/Config/ProgramConfiguration.cs b/Source/Core/Config/ProgramConfiguration.cs
old mode 100755
new mode 100644
index 5cb092ea7ff4b72f6794cf08384ae624d9d8eb2a..2743d835da4442f95da4bd919dfdd018291a9372
--- a/Source/Core/Config/ProgramConfiguration.cs
+++ b/Source/Core/Config/ProgramConfiguration.cs
@@ -98,6 +98,8 @@ namespace CodeImp.DoomBuilder.Config
 		private bool showfps;
 		private int[] colordialogcustomcolors;
 		private bool autolaunchontest;
+		private bool parallelizedlinedefplotting;
+		private bool parallelizedvertexplotting;
 
 		//mxd. Script editor settings
 		private string scriptfontname;
@@ -148,7 +150,9 @@ namespace CodeImp.DoomBuilder.Config
 		
 		//volte
 		private bool classicRendering;
-		
+		private bool flatShadeVertices;
+		private bool alwaysShowVertices;
+
 		// These are not stored in the configuration, only used at runtime
 		private int defaultbrightness;
 		private int defaultfloorheight;
@@ -212,6 +216,8 @@ namespace CodeImp.DoomBuilder.Config
 		public bool ShowFPS { get { return showfps; } internal set { showfps = value; } }
 		public int[] ColorDialogCustomColors { get { return colordialogcustomcolors; } internal set { colordialogcustomcolors = value; } }
 		public bool AutoLaunchOnTest { get { return autolaunchontest; } internal set { autolaunchontest = value; } }
+		public bool ParallelizedLinedefPlotting { get { return parallelizedlinedefplotting; } internal set { parallelizedlinedefplotting = value; } }
+		public bool ParallelizedVertexPlotting { get { return parallelizedvertexplotting; } internal set { parallelizedvertexplotting = value; } }
 
 		//mxd. Highlight mode
 		public bool UseHighlight
@@ -277,6 +283,10 @@ namespace CodeImp.DoomBuilder.Config
 		//volte
 		public bool ClassicRendering { get { return classicRendering; } internal set { classicRendering = value; } }
 
+		public bool FlatShadeVertices {  get { return flatShadeVertices; } internal set { flatShadeVertices = value;  } }
+
+		public bool AlwaysShowVertices {  get { return alwaysShowVertices; } internal set { alwaysShowVertices = value; } }
+
 		//mxd. Left here for compatibility reasons...
 		public string DefaultTexture { get { return General.Map != null ? General.Map.Options.DefaultWallTexture : "-"; } set { if(General.Map != null) General.Map.Options.DefaultWallTexture = value; } }
 		public string DefaultFloorTexture { get { return General.Map != null ? General.Map.Options.DefaultFloorTexture : "-"; } set { if(General.Map != null) General.Map.Options.DefaultFloorTexture = value; } }
@@ -363,6 +373,8 @@ namespace CodeImp.DoomBuilder.Config
 				switchviewmodes = cfg.ReadSetting("switchviewmodes", false); //mxd
 				showfps = cfg.ReadSetting("showfps", false);
 				autolaunchontest = cfg.ReadSetting("autolaunchontest", false);
+				parallelizedlinedefplotting = cfg.ReadSetting("parallelizedlinedefplotting", true);
+				parallelizedvertexplotting = cfg.ReadSetting("parallelizedvertexplotting", false);
 
 				//mxd. Script editor
 				scriptfontname = cfg.ReadSetting("scriptfontname", "Courier New");
@@ -412,7 +424,9 @@ namespace CodeImp.DoomBuilder.Config
 
 				// volte
 				classicRendering = cfg.ReadSetting("classicrendering", false);
-				
+				alwaysShowVertices = cfg.ReadSetting("alwaysshowvertices", true);
+				flatShadeVertices = cfg.ReadSetting("flatshadevertices", false);
+
 				//mxd. Sector defaults
 				defaultceilheight = cfg.ReadSetting("defaultceilheight", 128);
 				defaultfloorheight = cfg.ReadSetting("defaultfloorheight", 0);
@@ -555,6 +569,11 @@ namespace CodeImp.DoomBuilder.Config
 
 			//volte
 			cfg.WriteSetting("classicrendering", classicRendering);
+			cfg.WriteSetting("alwaysshowvertices", alwaysShowVertices);
+			cfg.WriteSetting("flatshadevertices", flatShadeVertices);
+
+			// Toasts
+			General.ToastManager.WriteSettings(cfg);
 			
 			//mxd. Sector defaults
 			cfg.WriteSetting("defaultceilheight", defaultceilheight);
@@ -736,6 +755,21 @@ namespace CodeImp.DoomBuilder.Config
 			{
 				for (int i = 0; i < t.Args.Length; i++)
 					t.Args[i] = (int)tti.Args[i].DefaultValue;
+
+				// Add user vars
+				if (tti.Actor != null)
+				{
+					Dictionary<string, UniversalType> uservars = tti.Actor.GetAllUserVars();
+					Dictionary<string, object> uservardefaults = tti.Actor.GetAllUserVarDefaults();
+
+					t.BeforeFieldsChange();
+
+					foreach (string fname in uservars.Keys)
+					{
+						if (uservardefaults.ContainsKey(fname))
+							t.Fields[fname] = new UniValue(uservars[fname], uservardefaults[fname]);
+					}
+				}
 			}
 		}
 
@@ -763,6 +797,21 @@ namespace CodeImp.DoomBuilder.Config
 			{
 				for (int i = 0; i < t.Args.Length; i++)
 					t.Args[i] = (int)tti.Args[i].DefaultValue;
+
+				// Add user vars
+				if (tti.Actor != null)
+				{
+					Dictionary<string, UniversalType> uservars = tti.Actor.GetAllUserVars();
+					Dictionary<string, object> uservardefaults = tti.Actor.GetAllUserVarDefaults();
+
+					t.BeforeFieldsChange();
+
+					foreach (string fname in uservars.Keys)
+					{
+						if (uservardefaults.ContainsKey(fname))
+							t.Fields[fname] = new UniValue(uservars[fname], uservardefaults[fname]);
+					}
+				}
 			}
 		}
 
diff --git a/Source/Core/Config/RequiredArchive.cs b/Source/Core/Config/RequiredArchive.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f8e5bac83a7655f62dc737c0ab26b2f14c18c914
--- /dev/null
+++ b/Source/Core/Config/RequiredArchive.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CodeImp.DoomBuilder.Config
+{
+    class RequiredArchiveEntry
+    {
+        private string reqClass;
+        private string reqLump;
+
+        public RequiredArchiveEntry(string reqClass, string reqLump)
+        {
+            this.reqClass = reqClass;
+            this.reqLump = reqLump;
+        }
+
+        public string Class { get { return reqClass; } }
+        public string Lump { get { return reqLump; } }
+    }
+
+    class RequiredArchive
+    {
+        private string id;
+        private string filename;
+        private bool excludeFromTesting;
+        private List<RequiredArchiveEntry> entries;
+
+        public RequiredArchive(string id, string filename, bool excludeFromTesting, List<RequiredArchiveEntry> entries)
+        {
+            this.id = id;
+            this.filename = filename;
+            this.excludeFromTesting = excludeFromTesting;
+            this.entries = entries;
+        }
+
+        public string ID { get { return id;  } }
+        public string FileName { get { return filename; } }
+        public bool ExcludeFromTesting { get { return excludeFromTesting; } }
+        public IReadOnlyCollection<RequiredArchiveEntry> Entries {  get { return entries; } }
+    }
+}
diff --git a/Source/Core/Config/ResourceTextureSet.cs b/Source/Core/Config/ResourceTextureSet.cs
index 59ac4c4769aab3ea85ac9307ac6e0e73ce8dee4d..5b305e784dd50b527d4361a73dc757de60768063 100755
--- a/Source/Core/Config/ResourceTextureSet.cs
+++ b/Source/Core/Config/ResourceTextureSet.cs
@@ -111,7 +111,7 @@ namespace CodeImp.DoomBuilder.Config
 			// Add textures to flats
 			foreach(KeyValuePair<long, ImageData> t in textures) 
 			{
-				if(!flats.ContainsKey(t.Key)) flats.Add(t.Key, t.Value);
+				if(!flats.ContainsKey(t.Key) || t.Value.TextureNamespace == TextureNamespace.TEXTURE) flats[t.Key] = t.Value;
 			}
 
 			// Add flats to textures
diff --git a/Source/Core/Config/ThingTypeInfo.cs b/Source/Core/Config/ThingTypeInfo.cs
index d2d140d493846f08c88319660379b91ceaa7ddbc..7fe4091d53fe630ef406b7c6d38c2395f6c8b376 100755
--- a/Source/Core/Config/ThingTypeInfo.cs
+++ b/Source/Core/Config/ThingTypeInfo.cs
@@ -91,6 +91,7 @@ namespace CodeImp.DoomBuilder.Config
 		private string obsoletemessage; //mxd
 		private Dictionary<string, Dictionary<string, string>> flagsrename; //mxd. <MapSetIOName, <flag, title>>
 		private int thinglink;
+		private List<string> adduniversalfields;
 
 		//mxd. GZDoom rendering properties
 		private ThingRenderMode rendermode;
@@ -200,6 +201,7 @@ namespace CodeImp.DoomBuilder.Config
 			this.flagsrename = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); //mxd
 			this.thinglink = 0;
             this.optional = false; // [ZZ]
+			this.adduniversalfields = new List<string>();
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -242,6 +244,16 @@ namespace CodeImp.DoomBuilder.Config
 			this.classname = cfg.ReadSetting("thingtypes." + cat.Name + "." + key + ".class", String.Empty); //mxd
 			this.thinglink = cfg.ReadSetting("thingtypes." + cat.Name + "." + key + ".thinglink", 0);
 
+			// Read universal fields that should be added to this thing type
+			adduniversalfields = new List<string>();
+			IDictionary adduniversalfieldsdic = cfg.ReadSetting("thingtypes." + cat.Name + "." + key + ".adduniversalfields", new Hashtable());
+			foreach(DictionaryEntry de in adduniversalfieldsdic)
+			{
+				string addname = de.Key.ToString().ToLowerInvariant();
+				if (!adduniversalfields.Contains(addname))
+					adduniversalfields.Add(addname);
+			}
+
 			//mxd. Read flagsrename
 			this.flagsrename = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
 			IDictionary maindic = cfg.ReadSetting("thingtypes." + cat.Name + "." + key + ".flagsrename", new Hashtable());
@@ -322,6 +334,7 @@ namespace CodeImp.DoomBuilder.Config
 			this.spritescale = new SizeF(cat.SpriteScale, cat.SpriteScale);
 			this.locksprite = false; //mxd
 			this.flagsrename = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); //mxd
+			this.adduniversalfields = new List<string>();
 
 			// Safety
 			if(this.radius < 4f || this.fixedsize) this.radius = THING_FIXED_SIZE;
@@ -370,6 +383,7 @@ namespace CodeImp.DoomBuilder.Config
 			this.absolutez = cat.AbsoluteZ;
 			this.spritescale = new SizeF(cat.SpriteScale, cat.SpriteScale);
 			this.flagsrename = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); //mxd
+			this.adduniversalfields = new List<string>();
 
 			// Safety
 			if(this.hangs && this.absolutez) this.hangs = false; //mxd
@@ -422,6 +436,7 @@ namespace CodeImp.DoomBuilder.Config
 			this.absolutez = cat.AbsoluteZ;
 			this.spritescale = new SizeF(cat.SpriteScale, cat.SpriteScale);
 			this.flagsrename = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); //mxd
+			this.adduniversalfields = new List<string>();
 
 			// Safety
 			if(this.hangs && this.absolutez) this.hangs = false; //mxd
@@ -478,6 +493,7 @@ namespace CodeImp.DoomBuilder.Config
 			this.xybillboard = other.xybillboard; //mxd
 			this.spritescale = new SizeF(other.spritescale.Width, other.spritescale.Height);
 			this.flagsrename = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); //mxd
+			this.adduniversalfields = new List<string>(other.adduniversalfields);
 
 			//mxd. Copy GZDoom rendering properties
 			this.rendermode = other.rendermode;
@@ -502,6 +518,13 @@ namespace CodeImp.DoomBuilder.Config
 			ModifyByDehackedThing(thing);
 		}
 
+		internal ThingTypeInfo(ThingCategory cat, ActorStructure actor, ThingTypeInfo other) : this(actor.DoomEdNum, other)
+		{
+			category = cat;
+
+			ModifyByDecorateActor(actor);
+		}
+
 		#endregion
 
 		#region ================== Methods
@@ -969,6 +992,11 @@ namespace CodeImp.DoomBuilder.Config
 		{
 			return title + " (" + index + ")";
 		}
+
+		public bool HasAddUniversalField(string fieldname)
+		{
+			return adduniversalfields != null && adduniversalfields.Contains(fieldname);
+		}
 		
 		#endregion
 	}
diff --git a/Source/Core/Config/UniversalFieldInfo.cs b/Source/Core/Config/UniversalFieldInfo.cs
index 2b795f6a0132f114ee1127e35ad62825c00ab989..24f2715ee97f927729e621c4c43996d455a0966a 100755
--- a/Source/Core/Config/UniversalFieldInfo.cs
+++ b/Source/Core/Config/UniversalFieldInfo.cs
@@ -60,6 +60,7 @@ namespace CodeImp.DoomBuilder.Config
 		private string name;
 		private int type;
 		private object defaultvalue;
+		private bool thingtypespecific;
 		private EnumList enumlist;
 		private Dictionary<string, UDMFFieldAssociation> associations;
 
@@ -70,6 +71,7 @@ namespace CodeImp.DoomBuilder.Config
 		public string Name { get { return name; } }
 		public int Type { get { return type; } }
 		public object Default { get { return defaultvalue; } }
+		public bool ThingTypeSpecific { get { return thingtypespecific; } }
 		public EnumList Enum { get { return enumlist; } }
 		public Dictionary<string, UDMFFieldAssociation> Associations { get { return associations; } }
 
@@ -87,16 +89,40 @@ namespace CodeImp.DoomBuilder.Config
 			associations = new Dictionary<string, UDMFFieldAssociation>();
 
 			// Read type
-			this.type = cfg.ReadSetting(setting + ".type", int.MinValue);
-			this.defaultvalue = cfg.ReadSettingObject(setting + ".default", null);
+			type = cfg.ReadSetting(setting + ".type", int.MinValue);
+			defaultvalue = cfg.ReadSettingObject(setting + ".default", null);
+			thingtypespecific = cfg.ReadSetting(setting + ".thingtypespecific", false);
+
+			// Read enum
+			object enumsetting = cfg.ReadSettingObject(setting + ".enum", null);
+			if (enumsetting != null)
+			{
+				// Reference to existing enums list?
+				if (enumsetting is string)
+				{
+					// Link to it
+					enumlist = enums[enumsetting.ToString()];
+				}
+				else if (enumsetting is IDictionary)
+				{
+					// Make list
+					enumlist = new EnumList(enumsetting as IDictionary);
+				}
+			}
 
 			//mxd. Check type
-			if(this.type == int.MinValue)
+			if (this.type == int.MinValue)
 			{
 				General.ErrorLogger.Add(ErrorType.Warning, "No type is defined for universal field \"" + name + "\" defined in \"" + configname + "\". Integer type will be used.");
 				this.type = (int)UniversalType.Integer;
 			}
 
+			if(type == (int)UniversalType.EnumOption && enumsetting == null)
+			{
+				General.ErrorLogger.Add(ErrorType.Warning, "Universal field \"" + name + "\" defined in \"" + configname + "\" is of type enum (" + this.type + "), but has no enum values set. Falling back to integer type");
+				type = (int)UniversalType.Integer;
+			}
+
 			TypeHandler th = General.Types.GetFieldHandler(this);
 			if(th is NullHandler)
 			{
@@ -107,23 +133,6 @@ namespace CodeImp.DoomBuilder.Config
 
 			//mxd. Default value is missing? Get it from typehandler
 			if(this.defaultvalue == null) this.defaultvalue = th.GetDefaultValue();
-			
-			// Read enum
-			object enumsetting = cfg.ReadSettingObject(setting + ".enum", null);
-			if(enumsetting != null)
-			{
-				// Reference to existing enums list?
-				if(enumsetting is string)
-				{
-					// Link to it
-					enumlist = enums[enumsetting.ToString()];
-				}
-				else if(enumsetting is IDictionary)
-				{
-					// Make list
-					enumlist = new EnumList(enumsetting as IDictionary);
-				}
-			}
 
 			// Read associations
 			IDictionary assocdict = cfg.ReadSetting(setting + ".associations", new Hashtable());
@@ -152,6 +161,13 @@ namespace CodeImp.DoomBuilder.Config
 			GC.SuppressFinalize(this);
 		}
 
+		internal UniversalFieldInfo(string name, int type, object defaultvalue)
+		{
+			this.name = name.ToLowerInvariant();
+			this.type = type;
+			this.defaultvalue = defaultvalue;
+		}
+
 		#endregion
 
 		#region ================== Methods
diff --git a/Source/Core/Controls/ArgumentBox.cs b/Source/Core/Controls/ArgumentBox.cs
index e4b37a7e649eb088f7fd4ca5beb0751c90f75c9c..f2befe126b8d294705d989cf2d572b2048e57409 100755
--- a/Source/Core/Controls/ArgumentBox.cs
+++ b/Source/Core/Controls/ArgumentBox.cs
@@ -45,6 +45,11 @@ namespace CodeImp.DoomBuilder.Controls
 
 		public override string Text { get { return combobox.Text; }	} //mxd
 
+		[Browsable(true)]
+		[Category("Action")]
+		[Description("Invoked when user clicks button")]
+		public event EventHandler ValueChanged;
+
 		#endregion
 
 		#region ================== Constructor
@@ -142,6 +147,8 @@ namespace CodeImp.DoomBuilder.Controls
 				forcevalidate = false;
 				combobox_Validating(sender, new CancelEventArgs());
 			}
+
+			ValueChanged?.Invoke(sender, EventArgs.Empty);
 		}
 
 		// Mouse wheel used
diff --git a/Source/Core/Controls/CommandPaletteControl.Designer.cs b/Source/Core/Controls/CommandPaletteControl.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7b7be86dff92f4a20c7f6998d52c737d196068d9
--- /dev/null
+++ b/Source/Core/Controls/CommandPaletteControl.Designer.cs
@@ -0,0 +1,136 @@
+
+namespace CodeImp.DoomBuilder.Controls
+{
+	partial class CommandPaletteControl
+	{
+		/// <summary> 
+		/// Required designer variable.
+		/// </summary>
+		private System.ComponentModel.IContainer components = null;
+
+		/// <summary> 
+		/// Clean up any resources being used.
+		/// </summary>
+		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+		protected override void Dispose(bool disposing)
+		{
+			if (disposing && (components != null))
+			{
+				components.Dispose();
+			}
+			base.Dispose(disposing);
+		}
+
+		#region Component Designer generated code
+
+		/// <summary> 
+		/// Required method for Designer support - do not modify 
+		/// the contents of this method with the code editor.
+		/// </summary>
+		private void InitializeComponent()
+		{
+			System.Windows.Forms.ListViewGroup listViewGroup1 = new System.Windows.Forms.ListViewGroup("Recent", System.Windows.Forms.HorizontalAlignment.Left);
+			System.Windows.Forms.ListViewGroup listViewGroup2 = new System.Windows.Forms.ListViewGroup("Usable actions", System.Windows.Forms.HorizontalAlignment.Left);
+			System.Windows.Forms.ListViewGroup listViewGroup3 = new System.Windows.Forms.ListViewGroup("Not usable in this context", System.Windows.Forms.HorizontalAlignment.Left);
+			this.commandsearch = new System.Windows.Forms.TextBox();
+			this.noresults = new System.Windows.Forms.Label();
+			this.commandlist = new CodeImp.DoomBuilder.Controls.OptimizedListView();
+			this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+			this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+			this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+			this.SuspendLayout();
+			// 
+			// commandsearch
+			// 
+			this.commandsearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+			this.commandsearch.Location = new System.Drawing.Point(3, 2);
+			this.commandsearch.Name = "commandsearch";
+			this.commandsearch.Size = new System.Drawing.Size(864, 20);
+			this.commandsearch.TabIndex = 2;
+			this.commandsearch.TextChanged += new System.EventHandler(this.commandsearch_TextChanged);
+			this.commandsearch.KeyDown += new System.Windows.Forms.KeyEventHandler(this.commandsearch_KeyDown);
+			// 
+			// noresults
+			// 
+			this.noresults.AutoSize = true;
+			this.noresults.Location = new System.Drawing.Point(6, 28);
+			this.noresults.Name = "noresults";
+			this.noresults.Size = new System.Drawing.Size(84, 13);
+			this.noresults.TabIndex = 4;
+			this.noresults.Text = "No results found";
+			this.noresults.Visible = false;
+			// 
+			// commandlist
+			// 
+			this.commandlist.Activation = System.Windows.Forms.ItemActivation.OneClick;
+			this.commandlist.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+			this.commandlist.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.columnHeader1,
+            this.columnHeader3,
+            this.columnHeader2});
+			this.commandlist.FullRowSelect = true;
+			listViewGroup1.Header = "Recent";
+			listViewGroup1.Name = "recent";
+			listViewGroup2.Header = "Usable actions";
+			listViewGroup2.Name = "usableactions";
+			listViewGroup3.Header = "Not usable in this context";
+			listViewGroup3.Name = "notusableactions";
+			this.commandlist.Groups.AddRange(new System.Windows.Forms.ListViewGroup[] {
+            listViewGroup1,
+            listViewGroup2,
+            listViewGroup3});
+			this.commandlist.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
+			this.commandlist.Location = new System.Drawing.Point(3, 25);
+			this.commandlist.MultiSelect = false;
+			this.commandlist.Name = "commandlist";
+			this.commandlist.Size = new System.Drawing.Size(864, 173);
+			this.commandlist.TabIndex = 3;
+			this.commandlist.TabStop = false;
+			this.commandlist.UseCompatibleStateImageBehavior = false;
+			this.commandlist.View = System.Windows.Forms.View.Details;
+			this.commandlist.ItemActivate += new System.EventHandler(this.commandlist_ItemActivate);
+			// 
+			// columnHeader1
+			// 
+			this.columnHeader1.Text = "Action";
+			this.columnHeader1.Width = 275;
+			// 
+			// columnHeader3
+			// 
+			this.columnHeader3.Text = "Section";
+			this.columnHeader3.Width = 196;
+			// 
+			// columnHeader2
+			// 
+			this.columnHeader2.Text = "Key";
+			this.columnHeader2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
+			this.columnHeader2.Width = 117;
+			// 
+			// CommandPaletteControl
+			// 
+			this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+			this.Controls.Add(this.noresults);
+			this.Controls.Add(this.commandlist);
+			this.Controls.Add(this.commandsearch);
+			this.DoubleBuffered = true;
+			this.Name = "CommandPaletteControl";
+			this.Size = new System.Drawing.Size(870, 201);
+			this.ResumeLayout(false);
+			this.PerformLayout();
+
+		}
+
+		#endregion
+
+		private OptimizedListView commandlist;
+		private System.Windows.Forms.ColumnHeader columnHeader1;
+		private System.Windows.Forms.ColumnHeader columnHeader2;
+		private System.Windows.Forms.TextBox commandsearch;
+		private System.Windows.Forms.ColumnHeader columnHeader3;
+		private System.Windows.Forms.Label noresults;
+	}
+}
diff --git a/Source/Core/Controls/CommandPaletteControl.cs b/Source/Core/Controls/CommandPaletteControl.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6f680309df45553d26112e056fb173d5c9f62541
--- /dev/null
+++ b/Source/Core/Controls/CommandPaletteControl.cs
@@ -0,0 +1,461 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Windows.Forms;
+using CodeImp.DoomBuilder.Windows;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.Controls
+{
+	public partial class CommandPaletteControl : UserControl
+	{
+		#region ================== Constants
+
+		private const int MAX_ITEMS = 20;
+		private const int MAX_RECENT_ACTIONS = 5;
+		private const int GROUP_RECENT = 0;
+		private const int GROUP_USABLE = 1;
+		private const int GROUP_UNUSABLE = 2;
+
+
+		#endregion
+
+		#region ================== Variables
+
+		private readonly List<Actions.Action> recentactions;
+
+		#endregion
+
+		#region ================== Constructor
+
+		public CommandPaletteControl()
+		{
+			InitializeComponent();
+
+			recentactions = new List<Actions.Action>();
+
+			Enabled = false;
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Hides the palette. Disabled it and sends it to the background.
+		/// </summary>
+		/// <param name="sender">The sender</param>
+		/// <param name="e">The event args</param>
+		private void HidePalette(object sender, EventArgs e)
+		{
+			commandsearch.LostFocus -= HidePalette;
+			Enabled = false;
+
+			if (Parent is MainForm mf)
+			{
+				mf.Resize -= Reposition;
+
+				mf.Controls.SetChildIndex(this, 0xffff);
+				mf.ActiveControl = null;
+				mf.Focus();
+			}
+		}
+
+		/// <summary>
+		/// Sets the color of the currently selected item.
+		/// </summary>
+		private void HighlightSelectedItem()
+		{
+			if (commandlist.SelectedItems.Count > 0)
+			{
+				commandlist.SelectedItems[0].BackColor = SystemColors.Highlight;
+				commandlist.SelectedItems[0].ForeColor = SystemColors.HighlightText;
+			}
+		}
+
+		/// <summary>
+		/// Shows the palette
+		/// </summary>
+		public void MakeVisible()
+		{
+			if (Parent is MainForm mf)
+			{
+				// Reset everything to a blank slate
+				commandsearch.Text = string.Empty;
+				//				commandsearch_TextChanged(this, EventArgs.Empty);
+				FillCommandList(withrecent: true);
+				HighlightSelectedItem();
+
+				// Set the width of each column to the max width of its fields
+				commandlist.Columns[0].Width = -1;
+				commandlist.Columns[1].Width = -1;
+				commandlist.Columns[2].Width = -1;
+
+				// Compute the new width. It's the width of the columns, the vertical scroll bar and some buffer
+				Width = commandlist.Columns[0].Width + commandlist.Columns[1].Width + commandlist.Columns[2].Width + SystemInformation.VerticalScrollBarWidth + commandlist.Location.X * 4;
+
+				// Center the control at the top middle
+				Location = new Point(mf.Display.Width / 2 - Width / 2, mf.Display.Location.Y + 5);
+
+				Enabled = true;
+
+				commandsearch.Focus();
+
+				// We want to hide the control when the focus is lost
+				commandsearch.LostFocus += HidePalette;
+
+				// Bring it to the foreground
+				mf.Controls.SetChildIndex(this, 0);
+
+				// Always keep the control in the center
+				mf.Resize += Reposition;
+			}
+		}
+
+		/// <summary>
+		/// Keeps the control positioned in the top middle of the window when it is rezied.
+		/// </summary>
+		/// <param name="sender">The sender</param>
+		/// <param name="e">The event args</param>
+		private void Reposition(object sender, EventArgs e)
+		{
+			// Center the control at the top middle
+			if (Parent is MainForm mf)
+				Location = new Point(mf.Display.Width / 2 - Width / 2, mf.Display.Location.Y + 5);
+		}
+
+		/// <summary>
+		/// Selects the item before or after the current item in the command list.
+		/// </summary>
+		/// <param name="changeindexby">By how much the index should be changed. Positive numbers mean that it will scroll up, negative numbers will scroll down.</param>
+		/// <param name="wraparound">If the selection should wrap around to the opposite side if the top or bottom of the list is reached</param>		
+		private void SetSelectedItem(int changeindexby, bool wraparound)
+		{
+			if (commandlist.Items.Count > 1)
+			{
+				int newindex = commandlist.SelectedIndices[0] + changeindexby;
+
+				if (newindex >= commandlist.Items.Count)
+				{
+					if (wraparound)
+						newindex = 0;
+					else
+						newindex = commandlist.Items.Count - 1;
+				}
+				else if (newindex < 0)
+				{
+					if (wraparound)
+						newindex = commandlist.Items.Count - 1;
+					else
+						newindex = 0;
+				}
+
+				// Reset the colors of the currently selected item to the defaults
+				commandlist.SelectedItems[0].BackColor = SystemColors.Window;
+				commandlist.SelectedItems[0].ForeColor = SystemColors.WindowText;
+
+				// Set the new item, scroll the list to it, and set the highlight color
+				commandlist.Items[newindex].Selected = true;
+				commandlist.EnsureVisible(newindex);
+				HighlightSelectedItem();
+			}
+		}
+
+		/// <summary>
+		/// Checks if a search string matches a text. It replicates the behavior of Visual Stuido Code.
+		/// At first it tries to match the whole search string. If that didn't produce a result it'll try to match as much of the search
+		/// string at the *beginning* of a word in the text. If that worked the matching characters are removed from the search text and
+		/// all words in the text up to (including) the found word are removed. This is repeated until all characters in the search string
+		/// are gone. This means:
+		/// "le cl" matches "Toggle classic rendering"
+		///                      ^^^^^
+		/// "tore" matches  "Toggle classic rendering"
+		///                  ^^             ^^
+		/// "tcl" matches   "Toggle classic rendering"
+		///                  ^      ^       ^
+		/// "tof" matches   "Toggle Full Brightness"
+		///                  ^^     ^
+		///                 "Align Floor Textures to Front Side"
+		///                                       ^^ ^
+		///                 "Reset Texture Offsets"
+		///                        ^       ^^
+		///                 (and a couple other)
+		/// </summary>
+		/// <param name="text">The string to search in</param>
+		/// <param name="search">The string to search for</param>
+		/// <returns></returns>
+		private bool MatchText(string text, string search)
+		{
+			text = text.ToLowerInvariant().Trim();
+			text = Regex.Replace(text, @"\s+", " ");
+
+			search = search.ToLowerInvariant().Trim();
+			search = Regex.Replace(search, @"\s+", " ");
+
+			// Check if the search string is empty or the whole search string is in the text to search
+			if (string.IsNullOrWhiteSpace(search) || text.Contains(search))
+				return true;
+
+			// No match yet, so let's check if all search tokens are at the beginning of a text token. This is the same(ish?) behavior as Visual Studio Code.
+			// This means that searching for "op ma" will match "Open Map", but not "Open Command Palette", because the "ma" in "Command" is not in the beginning.
+			List<string> textitems = text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
+			string[] searchitems = search.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+			for(int i=0; i < searchitems.Length; i++)
+			{
+				string si = searchitems[i];
+
+				// If the search item is empty it means we processed all its characters, so go to the next search item
+				if (string.IsNullOrEmpty(si))
+					continue;
+
+				string result = null;
+
+				// Search token not found, so try to match parts of the search token
+				while (si.Length > 0)
+				{
+					// Try to find the first text token that starts with the search token
+					result = textitems.FirstOrDefault(ti => ti.StartsWith(si));
+
+					// We found something, so remove the matching part of the search token and prepare processing this search token again
+					if (result != null)
+					{
+						searchitems[i] = searchitems[i].Remove(0, si.Length);
+						i--;
+						break;
+					}
+
+					// Nothing found, so remove the last character and keep going
+					si = si.Remove(si.Length - 1);
+				}
+
+				// Nothing found, so abort
+				if (result == null)
+					return false;
+
+				// We found a search token (or part of it), so remove all text tokens up to including the found text token
+				int index = textitems.IndexOf(result);
+				textitems.RemoveRange(0, index + 1);
+			}
+
+			// We didn't return yet, so we must have found everything
+			return true;
+		}
+
+		/// <summary>
+		/// Adds an action to the command list, either in the "usable" or "unsuable" group.
+		/// </summary>
+		/// <param name="action">The action to add</param>
+		private void AddActionToList(Actions.Action action, bool isrecent = false)
+		{
+			string actiontitle = action.Title;
+			string catname = string.Empty;
+			bool isbound = action.BeginBound || action.EndBound;
+
+			if (General.Actions.Categories.ContainsKey(action.Category))
+				catname = General.Actions.Categories[action.Category];
+
+			ListViewItem item = commandlist.Items.Add(action.Name, actiontitle, 0);
+
+			// Store the action in the tag, so we can invoke the action later
+			item.Tag = action;
+
+			// Add the item to the appropriate group, either the "usable" (0) or "unusable" (1) one
+			if (isrecent)
+				item.Group = commandlist.Groups[GROUP_RECENT];
+			else
+				item.Group = commandlist.Groups[isbound ? GROUP_USABLE : GROUP_UNUSABLE];
+
+			item.SubItems.Add(catname);
+			item.SubItems.Add(Actions.Action.GetShortcutKeyDesc(action.ShortcutKey));
+		}
+
+		/// <summary>
+		/// Runs an action and adds it to the list of recent actions
+		/// </summary>
+		/// <param name="action"></param>
+		private void RunAction(Actions.Action action)
+		{
+			// Remove the action (if it's in the list) and then insert it at the beginning
+			recentactions.Remove(action);
+			recentactions.Insert(0, action);
+
+			// Remove all actions that exceed the limit of the max number of recent actions
+			if (recentactions.Count > MAX_RECENT_ACTIONS)
+				recentactions.RemoveRange(4, recentactions.Count - MAX_RECENT_ACTIONS);
+
+			General.Actions.InvokeAction(action.Name);
+		}
+
+		/// <summary>
+		/// Fills the control, filtering it so that only the actions that match the search string are shown.
+		/// </summary>
+		/// <param name="searchtext">Text to search for in the action name</param>
+		/// <param name="withrecent">If recently shown actions should be shown or not</param>
+		private void FillCommandList(string searchtext = "", bool withrecent = false)
+		{
+			List<Actions.Action> usableactions = new List<Actions.Action>();
+			List<Actions.Action> unusableactions = new List<Actions.Action>();
+
+			commandlist.BeginUpdate();
+			commandlist.Items.Clear();
+
+			Actions.Action[] actions = General.Actions.GetAllActions();
+
+			// Crawl through all actions and check if they are usable or not in the current context
+			foreach (Actions.Action a in actions)
+			{
+				if (MatchText(a.Title, searchtext))
+				{
+					if (a.BeginBound || a.EndBound)
+						usableactions.Add(a);
+					else
+						unusableactions.Add(a);
+				}
+			}
+
+			// If there are matching actions we have to change the control's height and set the default selection
+			if (usableactions.Count + unusableactions.Count > 0)
+			{
+				noresults.Visible = false;
+				commandlist.Visible = true;
+
+				if (withrecent)
+					foreach (Actions.Action a in recentactions) if (a != null) AddActionToList(a, true);
+
+				// We have to do the sorting on our own, because otherwise the groups will screw with the selection logic when pressing the up/down keys
+				foreach (Actions.Action a in usableactions.OrderBy(o => o.Title)) AddActionToList(a);
+				foreach (Actions.Action a in unusableactions.OrderBy(o => o.Title)) AddActionToList(a);
+				
+				// We want to show at most MAX_ITEMS items before having a scroll bar
+				int numitems = commandlist.Items.Count > MAX_ITEMS ? MAX_ITEMS : commandlist.Items.Count;
+
+				// Get the height of a row
+				int itemheight = commandlist.Items[0].GetBounds(ItemBoundsPortion.Entire).Height;
+
+				// Get the number of shown groups
+				int numgroups = (usableactions.Count == 0 ? 0 : 1) + (unusableactions.Count == 0 ? 0 : 1);
+
+				// Set the new height, which is the number of items times the row height, the groups, the search textbox and some buffer
+				Height = itemheight * numitems + commandsearch.Height + numgroups * (int)(itemheight * 1.4) + commandlist.Location.X * 5;
+
+				// Select the topmost item and highlight it
+				commandlist.Items[0].Selected = true;
+				HighlightSelectedItem();
+
+				noresults.Visible = false;
+			}
+			else // No matching actions, hide line command list and tell the user that there are no matches
+			{
+				commandlist.Visible = false;
+				noresults.Visible = true;
+
+				Height = noresults.Location.Y + noresults.Height + noresults.Margin.Left * 2;
+			}
+
+			commandlist.EndUpdate();
+		}
+
+		#endregion
+
+		#region ================== Events
+
+		private void commandsearch_TextChanged(object sender, EventArgs e)
+		{
+			string searchtext = commandsearch.Text.Trim();
+
+			if (string.IsNullOrWhiteSpace(searchtext))
+				FillCommandList(withrecent: true);
+			else
+				FillCommandList(searchtext);
+		}
+
+		/// <summary>
+		/// Handles certain special keys. Esc will close the palette, the Up and Down keys will change the selection, and Enter will start the command.
+		/// </summary>
+		/// <param name="sender">The sender</param>
+		/// <param name="e">The event args</param>
+		private void commandsearch_KeyDown(object sender, KeyEventArgs e)
+		{
+			switch(e.KeyCode)
+			{
+				case Keys.Escape:
+				case Keys.Down:
+				case Keys.Up:
+				case Keys.PageDown:
+				case Keys.PageUp:
+				//case Keys.End:
+				//case Keys.Home:
+				case Keys.Enter:
+					e.Handled = true;
+					e.SuppressKeyPress = true;
+					break;
+			}
+
+			if (e.KeyCode == Keys.Escape)
+				HidePalette(this, EventArgs.Empty);
+			else if (e.KeyCode == Keys.Down)
+				SetSelectedItem(1, true);
+			else if (e.KeyCode == Keys.Up)
+				SetSelectedItem(-1, true);
+			else if (e.KeyCode == Keys.PageDown)
+				SetSelectedItem(MAX_ITEMS - 1, false);
+			else if (e.KeyCode == Keys.PageUp)
+				SetSelectedItem(-MAX_ITEMS + 1, false);
+			//else if (e.KeyCode == Keys.End)
+			//	SetSelectedItem(commandlist.Items.Count, false);
+			//else if (e.KeyCode == Keys.Home)
+			//	SetSelectedItem(0, false);
+			else if (e.KeyCode == Keys.Enter)
+			{
+				if (commandlist.Items.Count > 0)
+				{
+					HidePalette(this, EventArgs.Empty);
+					RunAction((Actions.Action)commandlist.SelectedItems[0].Tag);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Run the command that was clicked on
+		/// </summary>
+		/// <param name="sender">The sender</param>
+		/// <param name="e">The event args</param>
+		private void commandlist_ItemActivate(object sender, EventArgs e)
+		{
+			HidePalette(this, EventArgs.Empty);
+
+			RunAction((Actions.Action)commandlist.SelectedItems[0].Tag);
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Core/Controls/CommandPaletteControl.resx b/Source/Core/Controls/CommandPaletteControl.resx
new file mode 100644
index 0000000000000000000000000000000000000000..1af7de150c99c12dd67a509fe57c10d63e4eeb04
--- /dev/null
+++ b/Source/Core/Controls/CommandPaletteControl.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/Source/Core/Controls/FieldsEditorControl.cs b/Source/Core/Controls/FieldsEditorControl.cs
index 74aff7120094a9e724ac9d1b66a0964613da95c2..2f2bbdee420d10bb60fcb27b405c4fb9ac6686ab 100755
--- a/Source/Core/Controls/FieldsEditorControl.cs
+++ b/Source/Core/Controls/FieldsEditorControl.cs
@@ -417,7 +417,7 @@ namespace CodeImp.DoomBuilder.Controls
 		}
 
 		//mxd
-		public void ApplyUserVars(Dictionary<string, UniversalType> vars, UniFields tofields)
+		public void ApplyUserVars(Dictionary<string, UniversalType> vars, Dictionary<string, object> vardefaults, UniFields tofields)
 		{
 			// Apply user variables when target map element contains user var definition and the value is not default
 			foreach(DataGridViewRow row in fieldslist.Rows)
@@ -434,8 +434,11 @@ namespace CodeImp.DoomBuilder.Controls
 					// Skip field when mixed values
 					if(newvalue == null) continue;
 
-					// Remove field
-					if(newvalue.Equals(frow.TypeHandler.GetDefaultValue()))
+					object typedefault = frow.TypeHandler.GetDefaultValue();
+					object userdefault = vardefaults.ContainsKey(frow.Name) ? vardefaults[frow.Name] : typedefault;
+
+					// Remove field, but only if the type's default value is the same as the user var's default value
+					if (newvalue.Equals(typedefault) && typedefault.Equals(userdefault))
 					{
 						if(tofields.ContainsKey(frow.Name)) tofields.Remove(frow.Name);
 					}
@@ -981,6 +984,50 @@ namespace CodeImp.DoomBuilder.Controls
 				if(frow != null && frow.RowType == FieldsEditorRowType.FIXED) frow.Visible = showfixedfields;
 			}
 		}
+
+		/// <summary>
+		/// Removes an field by its name.
+		/// </summary>
+		/// <param name="name">Name of the field to remove</param>
+		public void RemoveField(string name)
+		{
+			int index = -1;
+
+			foreach(DataGridViewRow dgvr in fieldslist.Rows)
+			{
+				if(dgvr.Cells[0].Value.ToString().ToLowerInvariant() == name.ToLowerInvariant())
+				{
+					index = dgvr.Index;
+					break;
+				}
+			}
+
+			if (index >= 0)
+				fieldslist.Rows.RemoveAt(index);
+		}
+
+		/// <summary>
+		/// Removes all user vars that have their default values.
+		/// </summary>
+		public void RemoveUserVarsWithDefaultValue()
+		{
+			List<int> removeindices = new List<int>();
+
+			// Go through all rows and find the ones to remove. We can't remove them immediately since that would
+			// change the collection while the loop is going through it.
+			foreach (DataGridViewRow dgvr in fieldslist.Rows)
+			{
+				if(dgvr is FieldsEditorRow frow)
+				{
+					if (frow.RowType == FieldsEditorRowType.USERVAR && frow.Info != null && frow.TypeHandler.GetValue().Equals(frow.Info.Default))
+						removeindices.Add(dgvr.Index);
+				}
+			}
+
+			// Remove rows. Do it from behind since otherwise the indices would not match
+			for (int i = removeindices.Count - 1; i >= 0; i--)
+				fieldslist.Rows.RemoveAt(removeindices[i]);
+		}
 		
 		#endregion
 	}
diff --git a/Source/Core/Controls/FieldsEditorRow.cs b/Source/Core/Controls/FieldsEditorRow.cs
index 3a4d78d74964b491df054a1381e1b67d834341a9..018d7768dac5e8bb7293831d23c9cbf21013f477 100755
--- a/Source/Core/Controls/FieldsEditorRow.cs
+++ b/Source/Core/Controls/FieldsEditorRow.cs
@@ -132,6 +132,9 @@ namespace CodeImp.DoomBuilder.Controls
 				isdefined = false;
 				//fieldtype.ApplyDefaultValue(); // [ZZ] don't do this. this is only done for int, and not a very good place to do it...
 
+				// We need to remember the default value of the user var
+				fieldinfo = new UniversalFieldInfo(name, type, value);
+
 				// Setup property cell
 				this.Cells[0].Value = name;
 				this.Cells[0].ReadOnly = true;
diff --git a/Source/Core/Controls/ImageBrowserControl.cs b/Source/Core/Controls/ImageBrowserControl.cs
index b7848a6d380e8128855f3071236328ffee561e01..a90fa8b89e9f08318451972fe9a873bb08b16790 100755
--- a/Source/Core/Controls/ImageBrowserControl.cs
+++ b/Source/Core/Controls/ImageBrowserControl.cs
@@ -202,6 +202,7 @@ namespace CodeImp.DoomBuilder.Controls
             General.Settings.WriteSetting(settingpath + ".classicview", classicview.Checked);
 			General.Settings.WriteSetting(settingpath + ".verticallycenteritem", list.CenterItem);
 			General.Settings.WriteSetting(settingpath + ".imagesize", list.ImageSize);
+			General.Settings.WriteSetting(settingpath + ".texturetype", texturetype);
 			
 			if (General.Map.Config.UseLongTextureNames) General.Map.Options.UseLongTextureNames = uselongtexturenames;
 
@@ -661,9 +662,9 @@ namespace CodeImp.DoomBuilder.Controls
             //if (!splitter.Panel2Collapsed) 
 			{
                 if (texturetype == 0 && previtem != null && item.TextureName == previtem.TextureName) return false;
-				if (texturetype == 1 && item.Icon.IsFlat) return false;
-				if (texturetype == 2 && !item.Icon.IsFlat) return false;
-				if (texturetype == 3 && (browseflats != item.Icon.IsFlat)) return false;
+				if (texturetype == 1 && item.Icon.TextureNamespace == TextureNamespace.FLAT) return false;
+				if (texturetype == 2 && !(item.Icon.TextureNamespace == TextureNamespace.FLAT)) return false;
+				if (texturetype == 3 && (browseflats != (item.Icon.TextureNamespace == TextureNamespace.FLAT))) return false;
 			}
             //else if (previtem != null && item.TextureName == previtem.TextureName) return false;
 
diff --git a/Source/Core/Controls/ImageSelectorPanel.cs b/Source/Core/Controls/ImageSelectorPanel.cs
index 8b5582b999df0ec9e3047063a9cbec05c54f678e..82f0315da16c47ed78f629882b620c1441d3015a 100755
--- a/Source/Core/Controls/ImageSelectorPanel.cs
+++ b/Source/Core/Controls/ImageSelectorPanel.cs
@@ -7,6 +7,7 @@ using System.Drawing.Drawing2D;
 using System.Drawing.Imaging;
 using System.Linq;
 using System.Windows.Forms;
+using CodeImp.DoomBuilder.Data;
 
 #endregion
 
@@ -732,7 +733,7 @@ namespace CodeImp.DoomBuilder.Controls
 		private static Image GetPreview(ImageBrowserItem item, int imagesize)
 		{
 			if(!item.IsPreviewLoaded) return item.Icon.GetPreview();
-            Dictionary<int, Dictionary<long, Image>> cache = item.Icon.IsFlat ? flatcache : texturecache;
+            Dictionary<int, Dictionary<long, Image>> cache = (item.Icon.TextureNamespace == TextureNamespace.FLAT) ? flatcache : texturecache;
 
 			if(!cache.ContainsKey(imagesize)) cache.Add(imagesize, new Dictionary<long, Image>());
 
diff --git a/Source/Core/Controls/ResourceListEditor.Designer.cs b/Source/Core/Controls/ResourceListEditor.Designer.cs
index 2e9f7dd3a0e4dbe77f5c02635d8aca7da695f7b0..2175c0c006c79d0772a3e1973996761640164bed 100755
--- a/Source/Core/Controls/ResourceListEditor.Designer.cs
+++ b/Source/Core/Controls/ResourceListEditor.Designer.cs
@@ -28,100 +28,68 @@ namespace CodeImp.DoomBuilder.Controls
 		/// </summary>
 		private void InitializeComponent()
 		{
-			this.components = new System.ComponentModel.Container();
-			System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem(new string[] {
+            this.components = new System.ComponentModel.Container();
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ResourceListEditor));
+            System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem(new string[] {
             "C:\\Windows\\Doom\\Doom2.wad"}, 3, System.Drawing.SystemColors.GrayText, System.Drawing.SystemColors.Window, null);
-			System.Windows.Forms.ListViewItem listViewItem2 = new System.Windows.Forms.ListViewItem(new string[] {
+            System.Windows.Forms.ListViewItem listViewItem2 = new System.Windows.Forms.ListViewItem(new string[] {
             "C:\\My\\Little\\Textures\\"}, 2, System.Drawing.SystemColors.GrayText, System.Drawing.SystemColors.Window, null);
-			System.Windows.Forms.ListViewItem listViewItem3 = new System.Windows.Forms.ListViewItem("C:\\My\\Little\\Pony.wad", 1);
-			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ResourceListEditor));
-			this.editresource = new System.Windows.Forms.Button();
-			this.deleteresources = new System.Windows.Forms.Button();
-			this.addresource = new System.Windows.Forms.Button();
-			this.resourceitems = new CodeImp.DoomBuilder.Controls.ResourceListView();
-			this.column = new System.Windows.Forms.ColumnHeader();
-			this.copypastemenu = new System.Windows.Forms.ContextMenuStrip(this.components);
-			this.copyresources = new System.Windows.Forms.ToolStripMenuItem();
-			this.cutresources = new System.Windows.Forms.ToolStripMenuItem();
-			this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
-			this.pasteresources = new System.Windows.Forms.ToolStripMenuItem();
-			this.replaceresources = new System.Windows.Forms.ToolStripMenuItem();
-			this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
-			this.removeresources = new System.Windows.Forms.ToolStripMenuItem();
-			this.images = new System.Windows.Forms.ImageList(this.components);
-			this.copypastemenu.SuspendLayout();
-			this.SuspendLayout();
-			// 
-			// editresource
-			// 
-			this.editresource.Enabled = false;
-			this.editresource.Location = new System.Drawing.Point(122, 140);
-			this.editresource.Name = "editresource";
-			this.editresource.Size = new System.Drawing.Size(136, 24);
-			this.editresource.TabIndex = 0;
-			this.editresource.Text = "Resource options...";
-			this.editresource.UseVisualStyleBackColor = true;
-			this.editresource.Click += new System.EventHandler(this.editresource_Click);
-			// 
-			// deleteresources
-			// 
-			this.deleteresources.Enabled = false;
-			this.deleteresources.Location = new System.Drawing.Point(259, 140);
-			this.deleteresources.Name = "deleteresources";
-			this.deleteresources.Size = new System.Drawing.Size(88, 24);
-			this.deleteresources.TabIndex = 0;
-			this.deleteresources.Text = "Remove";
-			this.deleteresources.UseVisualStyleBackColor = true;
-			this.deleteresources.Click += new System.EventHandler(this.deleteresources_Click);
-			// 
-			// addresource
-			// 
-			this.addresource.Location = new System.Drawing.Point(3, 140);
-			this.addresource.Name = "addresource";
-			this.addresource.Size = new System.Drawing.Size(118, 24);
-			this.addresource.TabIndex = 0;
-			this.addresource.Text = "Add resource...";
-			this.addresource.UseVisualStyleBackColor = true;
-			this.addresource.Click += new System.EventHandler(this.addresource_Click);
-			// 
-			// resourceitems
-			// 
-			this.resourceitems.AllowDrop = true;
-			this.resourceitems.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
-            this.column});
-			this.resourceitems.ContextMenuStrip = this.copypastemenu;
-			this.resourceitems.FullRowSelect = true;
-			this.resourceitems.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
-			this.resourceitems.HideSelection = false;
-			this.resourceitems.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
-            listViewItem1,
-            listViewItem2,
-            listViewItem3});
-			this.resourceitems.Location = new System.Drawing.Point(0, 0);
-			this.resourceitems.Name = "resourceitems";
-			this.resourceitems.ShowGroups = false;
-			this.resourceitems.ShowItemToolTips = true;
-			this.resourceitems.Size = new System.Drawing.Size(350, 138);
-			this.resourceitems.SmallImageList = this.images;
-			this.resourceitems.TabIndex = 0;
-			this.resourceitems.UseCompatibleStateImageBehavior = false;
-			this.resourceitems.View = System.Windows.Forms.View.Details;
-			this.resourceitems.ClientSizeChanged += new System.EventHandler(this.resourceitems_ClientSizeChanged);
-			this.resourceitems.SizeChanged += new System.EventHandler(this.resources_SizeChanged);
-			this.resourceitems.DoubleClick += new System.EventHandler(this.resourceitems_DoubleClick);
-			this.resourceitems.DragDrop += new System.Windows.Forms.DragEventHandler(this.resourceitems_DragDrop);
-			this.resourceitems.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.resourceitems_ItemSelectionChanged);
-			this.resourceitems.KeyUp += new System.Windows.Forms.KeyEventHandler(this.resourceitems_KeyUp);
-			this.resourceitems.DragOver += new System.Windows.Forms.DragEventHandler(this.resourceitems_DragOver);
-			// 
-			// column
-			// 
-			this.column.Text = "Resource location";
-			this.column.Width = 200;
-			// 
-			// copypastemenu
-			// 
-			this.copypastemenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            System.Windows.Forms.ListViewItem listViewItem3 = new System.Windows.Forms.ListViewItem("C:\\My\\Little\\Pony.wad", 1);
+            this.editresource = new System.Windows.Forms.Button();
+            this.deleteresources = new System.Windows.Forms.Button();
+            this.addresource = new System.Windows.Forms.Button();
+            this.copypastemenu = new System.Windows.Forms.ContextMenuStrip(this.components);
+            this.copyresources = new System.Windows.Forms.ToolStripMenuItem();
+            this.cutresources = new System.Windows.Forms.ToolStripMenuItem();
+            this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+            this.pasteresources = new System.Windows.Forms.ToolStripMenuItem();
+            this.replaceresources = new System.Windows.Forms.ToolStripMenuItem();
+            this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
+            this.removeresources = new System.Windows.Forms.ToolStripMenuItem();
+            this.images = new System.Windows.Forms.ImageList(this.components);
+            this.resourceitems = new CodeImp.DoomBuilder.Controls.ResourceListView();
+            this.column = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.copypastemenu.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // editresource
+            // 
+            this.editresource.Anchor = System.Windows.Forms.AnchorStyles.None;
+            this.editresource.Enabled = false;
+            this.editresource.Location = new System.Drawing.Point(122, 140);
+            this.editresource.Name = "editresource";
+            this.editresource.Size = new System.Drawing.Size(136, 24);
+            this.editresource.TabIndex = 0;
+            this.editresource.Text = "Resource options...";
+            this.editresource.UseVisualStyleBackColor = true;
+            this.editresource.Click += new System.EventHandler(this.editresource_Click);
+            // 
+            // deleteresources
+            // 
+            this.deleteresources.Anchor = System.Windows.Forms.AnchorStyles.None;
+            this.deleteresources.Enabled = false;
+            this.deleteresources.Location = new System.Drawing.Point(259, 140);
+            this.deleteresources.Name = "deleteresources";
+            this.deleteresources.Size = new System.Drawing.Size(88, 24);
+            this.deleteresources.TabIndex = 0;
+            this.deleteresources.Text = "Remove";
+            this.deleteresources.UseVisualStyleBackColor = true;
+            this.deleteresources.Click += new System.EventHandler(this.deleteresources_Click);
+            // 
+            // addresource
+            // 
+            this.addresource.Anchor = System.Windows.Forms.AnchorStyles.None;
+            this.addresource.Location = new System.Drawing.Point(3, 140);
+            this.addresource.Name = "addresource";
+            this.addresource.Size = new System.Drawing.Size(118, 24);
+            this.addresource.TabIndex = 0;
+            this.addresource.Text = "Add resource...";
+            this.addresource.UseVisualStyleBackColor = true;
+            this.addresource.Click += new System.EventHandler(this.addresource_Click);
+            // 
+            // copypastemenu
+            // 
+            this.copypastemenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
             this.copyresources,
             this.cutresources,
             this.toolStripSeparator1,
@@ -129,85 +97,121 @@ namespace CodeImp.DoomBuilder.Controls
             this.replaceresources,
             this.toolStripSeparator2,
             this.removeresources});
-			this.copypastemenu.Name = "copypastemenu";
-			this.copypastemenu.Size = new System.Drawing.Size(118, 126);
-			this.copypastemenu.Opening += new System.ComponentModel.CancelEventHandler(this.copypastemenu_Opening);
-			// 
-			// copyresources
-			// 
-			this.copyresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Copy;
-			this.copyresources.Name = "copyresources";
-			this.copyresources.Size = new System.Drawing.Size(117, 22);
-			this.copyresources.Text = "Copy";
-			this.copyresources.Click += new System.EventHandler(this.copyresources_Click);
-			// 
-			// cutresources
-			// 
-			this.cutresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Cut;
-			this.cutresources.Name = "cutresources";
-			this.cutresources.Size = new System.Drawing.Size(117, 22);
-			this.cutresources.Text = "Cut";
-			this.cutresources.Click += new System.EventHandler(this.cutresources_Click);
-			// 
-			// toolStripSeparator1
-			// 
-			this.toolStripSeparator1.Name = "toolStripSeparator1";
-			this.toolStripSeparator1.Size = new System.Drawing.Size(114, 6);
-			// 
-			// pasteresources
-			// 
-			this.pasteresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Paste;
-			this.pasteresources.Name = "pasteresources";
-			this.pasteresources.Size = new System.Drawing.Size(117, 22);
-			this.pasteresources.Text = "Paste";
-			this.pasteresources.Click += new System.EventHandler(this.pasteresources_Click);
-			// 
-			// replaceresources
-			// 
-			this.replaceresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Replace;
-			this.replaceresources.Name = "replaceresources";
-			this.replaceresources.Size = new System.Drawing.Size(117, 22);
-			this.replaceresources.Text = "Replace";
-			this.replaceresources.Click += new System.EventHandler(this.replaceresources_Click);
-			// 
-			// toolStripSeparator2
-			// 
-			this.toolStripSeparator2.Name = "toolStripSeparator2";
-			this.toolStripSeparator2.Size = new System.Drawing.Size(114, 6);
-			// 
-			// removeresources
-			// 
-			this.removeresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.SearchClear;
-			this.removeresources.Name = "removeresources";
-			this.removeresources.Size = new System.Drawing.Size(117, 22);
-			this.removeresources.Text = "Remove";
-			this.removeresources.Click += new System.EventHandler(this.removeresources_Click);
-			// 
-			// images
-			// 
-			this.images.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("images.ImageStream")));
-			this.images.TransparentColor = System.Drawing.Color.Transparent;
-			this.images.Images.SetKeyName(0, "Folder.ico");
-			this.images.Images.SetKeyName(1, "File.ico");
-			this.images.Images.SetKeyName(2, "PK3.ico");
-			this.images.Images.SetKeyName(3, "FolderLocked.ico");
-			this.images.Images.SetKeyName(4, "FileLocked.ico");
-			this.images.Images.SetKeyName(5, "PK3Locked.ico");
-			// 
-			// ResourceListEditor
-			// 
-			this.AllowDrop = true;
-			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
-			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-			this.Controls.Add(this.resourceitems);
-			this.Controls.Add(this.addresource);
-			this.Controls.Add(this.editresource);
-			this.Controls.Add(this.deleteresources);
-			this.Name = "ResourceListEditor";
-			this.Size = new System.Drawing.Size(350, 166);
-			this.Resize += new System.EventHandler(this.ResourceListEditor_Resize);
-			this.copypastemenu.ResumeLayout(false);
-			this.ResumeLayout(false);
+            this.copypastemenu.Name = "copypastemenu";
+            this.copypastemenu.Size = new System.Drawing.Size(118, 126);
+            this.copypastemenu.Opening += new System.ComponentModel.CancelEventHandler(this.copypastemenu_Opening);
+            // 
+            // copyresources
+            // 
+            this.copyresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Copy;
+            this.copyresources.Name = "copyresources";
+            this.copyresources.Size = new System.Drawing.Size(117, 22);
+            this.copyresources.Text = "Copy";
+            this.copyresources.Click += new System.EventHandler(this.copyresources_Click);
+            // 
+            // cutresources
+            // 
+            this.cutresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Cut;
+            this.cutresources.Name = "cutresources";
+            this.cutresources.Size = new System.Drawing.Size(117, 22);
+            this.cutresources.Text = "Cut";
+            this.cutresources.Click += new System.EventHandler(this.cutresources_Click);
+            // 
+            // toolStripSeparator1
+            // 
+            this.toolStripSeparator1.Name = "toolStripSeparator1";
+            this.toolStripSeparator1.Size = new System.Drawing.Size(114, 6);
+            // 
+            // pasteresources
+            // 
+            this.pasteresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Paste;
+            this.pasteresources.Name = "pasteresources";
+            this.pasteresources.Size = new System.Drawing.Size(117, 22);
+            this.pasteresources.Text = "Paste";
+            this.pasteresources.Click += new System.EventHandler(this.pasteresources_Click);
+            // 
+            // replaceresources
+            // 
+            this.replaceresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.Replace;
+            this.replaceresources.Name = "replaceresources";
+            this.replaceresources.Size = new System.Drawing.Size(117, 22);
+            this.replaceresources.Text = "Replace";
+            this.replaceresources.Click += new System.EventHandler(this.replaceresources_Click);
+            // 
+            // toolStripSeparator2
+            // 
+            this.toolStripSeparator2.Name = "toolStripSeparator2";
+            this.toolStripSeparator2.Size = new System.Drawing.Size(114, 6);
+            // 
+            // removeresources
+            // 
+            this.removeresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.SearchClear;
+            this.removeresources.Name = "removeresources";
+            this.removeresources.Size = new System.Drawing.Size(117, 22);
+            this.removeresources.Text = "Remove";
+            this.removeresources.Click += new System.EventHandler(this.removeresources_Click);
+            // 
+            // images
+            // 
+            this.images.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("images.ImageStream")));
+            this.images.TransparentColor = System.Drawing.Color.Transparent;
+            this.images.Images.SetKeyName(0, "Folder.ico");
+            this.images.Images.SetKeyName(1, "File.ico");
+            this.images.Images.SetKeyName(2, "PK3.ico");
+            this.images.Images.SetKeyName(3, "FolderLocked.ico");
+            this.images.Images.SetKeyName(4, "FileLocked.ico");
+            this.images.Images.SetKeyName(5, "PK3Locked.ico");
+            this.images.Images.SetKeyName(6, "Loader.gif");
+            // 
+            // resourceitems
+            // 
+            this.resourceitems.AllowDrop = true;
+            this.resourceitems.Anchor = System.Windows.Forms.AnchorStyles.None;
+            this.resourceitems.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.column});
+            this.resourceitems.ContextMenuStrip = this.copypastemenu;
+            this.resourceitems.FullRowSelect = true;
+            this.resourceitems.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
+            this.resourceitems.HideSelection = false;
+            this.resourceitems.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
+            listViewItem1,
+            listViewItem2,
+            listViewItem3});
+            this.resourceitems.Location = new System.Drawing.Point(0, 0);
+            this.resourceitems.Name = "resourceitems";
+            this.resourceitems.ShowGroups = false;
+            this.resourceitems.ShowItemToolTips = true;
+            this.resourceitems.Size = new System.Drawing.Size(350, 138);
+            this.resourceitems.SmallImageList = this.images;
+            this.resourceitems.TabIndex = 0;
+            this.resourceitems.UseCompatibleStateImageBehavior = false;
+            this.resourceitems.View = System.Windows.Forms.View.Details;
+            this.resourceitems.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.resourceitems_ItemSelectionChanged);
+            this.resourceitems.ClientSizeChanged += new System.EventHandler(this.resourceitems_ClientSizeChanged);
+            this.resourceitems.SizeChanged += new System.EventHandler(this.resources_SizeChanged);
+            this.resourceitems.DragDrop += new System.Windows.Forms.DragEventHandler(this.resourceitems_DragDrop);
+            this.resourceitems.DragOver += new System.Windows.Forms.DragEventHandler(this.resourceitems_DragOver);
+            this.resourceitems.DoubleClick += new System.EventHandler(this.resourceitems_DoubleClick);
+            this.resourceitems.KeyUp += new System.Windows.Forms.KeyEventHandler(this.resourceitems_KeyUp);
+            // 
+            // column
+            // 
+            this.column.Text = "Resource location";
+            this.column.Width = 200;
+            // 
+            // ResourceListEditor
+            // 
+            this.AllowDrop = true;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+            this.Controls.Add(this.resourceitems);
+            this.Controls.Add(this.addresource);
+            this.Controls.Add(this.editresource);
+            this.Controls.Add(this.deleteresources);
+            this.Name = "ResourceListEditor";
+            this.Size = new System.Drawing.Size(350, 166);
+            this.copypastemenu.ResumeLayout(false);
+            this.ResumeLayout(false);
 
 		}
 
diff --git a/Source/Core/Controls/ResourceListEditor.cs b/Source/Core/Controls/ResourceListEditor.cs
index bda7494df24c1bf8da632780a4ffe37214b480e4..3548df439eaf406d60ead7d83cada0061abca717 100755
--- a/Source/Core/Controls/ResourceListEditor.cs
+++ b/Source/Core/Controls/ResourceListEditor.cs
@@ -20,7 +20,10 @@ using System;
 using System.Collections.Generic;
 using System.Drawing;
 using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
 using System.Windows.Forms;
+using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.Windows;
 
@@ -30,11 +33,30 @@ namespace CodeImp.DoomBuilder.Controls
 {
 	internal partial class ResourceListEditor : UserControl
 	{
+		#region ================== Internal warning struct
+		class Warning
+        {
+			public Panel Wrapper;
+			public PictureBox Picture;
+			public Label Text;
+
+			public Warning(Panel wrapper, PictureBox picture, Label text)
+            {
+				Wrapper = wrapper;
+				Picture = picture;
+				Text = text;
+            }
+        }
+		#endregion
+
 		#region ================== Delegates / Events
 
 		public delegate void ContentChanged();
+		public delegate void WarningsChanged(int size);
 		public event ContentChanged OnContentChanged;
+		public event WarningsChanged OnWarningsChanged;
 		public string StartPath; //mxd
+		public bool IsMapControl = false;
 
 		#endregion
 
@@ -47,12 +69,15 @@ namespace CodeImp.DoomBuilder.Controls
 		private readonly int pasteactionkey;
 		private readonly int pastespecialactionkey;
 		private readonly int deleteactionkey;
+		private readonly Dictionary<string, CancellationTokenSource> loadingrequiredarchives;
+		private readonly List<Warning> warnings;
 
 		#endregion
 
 		#region ================== Properties
 
 		public Point DialogOffset { get { return dialogoffset; } set { dialogoffset = value; } }
+		public GameConfiguration GameConfiguration { get; set; }
 
 		#endregion
 
@@ -64,8 +89,11 @@ namespace CodeImp.DoomBuilder.Controls
 			// Initialize
 			InitializeComponent();
 			ResizeColumnHeader();
-			
-			if(General.Actions != null)
+
+			loadingrequiredarchives = new Dictionary<string, CancellationTokenSource>();
+			warnings = new List<Warning>();
+
+			if (General.Actions != null)
 			{
 				// Get key shortcuts (mxd)
 				copyactionkey = General.Actions.GetActionByName("builder_copyselection").ShortcutKey;
@@ -85,6 +113,9 @@ namespace CodeImp.DoomBuilder.Controls
 			// Start with a clear list
 			resourceitems.Items.Clear();
 			copiedresources = new DataLocationList(); //mxd
+
+			Resize += HandleResize;
+			HandleResize(null, null);
 		}
 
 		#endregion
@@ -109,6 +140,257 @@ namespace CodeImp.DoomBuilder.Controls
 				default: return -1;
 			}
 		}
+
+		private List<string> RunCheckRequiredArchives(DataLocation loc, CancellationToken token)
+		{
+			return ResourceOptionsForm.CheckRequiredArchives(GameConfiguration, loc, token);
+		}
+
+		private async void StartRequiredArchivesCheck(string location)
+		{
+			if (GameConfiguration == null) return;
+
+			DataLocation loc = new DataLocation();
+			bool found = false;
+
+			foreach (ListViewItem item in resourceitems.Items)
+            {
+				DataLocation dl = (DataLocation)item.Tag;
+				if (dl.location == location)
+                {
+					loc = dl;
+					found = true;
+					break;
+                }
+            }
+
+			if (!found) return;
+
+			var cancellation = new CancellationTokenSource();
+
+			General.WriteLogLine(string.Format("Resource check started for: {0}", loc.location));
+
+			loadingrequiredarchives.Add(location, cancellation);
+			RefreshLoading();
+
+			try
+			{
+				loc.requiredarchives = await Task.Run(() => RunCheckRequiredArchives(loc, cancellation.Token));
+				
+				// in case of dir, option1/2 should be erased
+				if (loc.type == DataLocation.RESOURCE_DIRECTORY)
+					loc.option1 = loc.option2 = false;
+				// check if it has to be force-excluded from testing
+				foreach (var arc in GameConfiguration.RequiredArchives)
+				{
+					if (loc.requiredarchives.Contains(arc.ID) && arc.ExcludeFromTesting)
+						loc.notfortesting = true;
+				}
+
+				foreach (ListViewItem item in resourceitems.Items)
+				{
+					if (((DataLocation)item.Tag).location == location)
+					{
+						item.Tag = loc;
+						if (OnContentChanged != null) OnContentChanged();
+						break;
+					}
+				}
+			}
+			catch (Exception e)
+			{
+				loc.requiredarchives = new List<string>();
+				General.WriteLogLine(e.ToString());
+			}
+
+			cancellation.Dispose();
+
+			// Loading might have been canceled outside of here, so make sure that the location is still in the list.
+			// See https://github.com/jewalky/UltimateDoomBuilder/issues/813
+			if (loadingrequiredarchives.ContainsKey(location) && loadingrequiredarchives[location] == cancellation)
+			{
+				General.WriteLogLine(string.Format("Resource check completed for: {0} (Match = {1}, RequiredArchives = {2})", location, loadingrequiredarchives[location] == cancellation, string.Join(",", loc.requiredarchives)));
+				loadingrequiredarchives.Remove(location);
+				RefreshLoading();
+			}
+
+			// if nothing is loading, update warnings if any
+			if (loadingrequiredarchives.Count == 0)
+				UpdateWarnings();
+		}
+
+		private void ShowWarning(string text, bool loading)
+        {
+			Panel p = new Panel();
+			Controls.Add(p);
+
+			// find offset
+			int lastTop = 0;
+			foreach (Warning w in warnings)
+				lastTop = Math.Max(lastTop, w.Wrapper.Bottom + 8);
+
+			p.Top = lastTop;
+			p.Left = 0;
+			p.Width = Width;
+			p.Height = 48;
+			p.BackColor = SystemColors.Info;
+			p.BorderStyle = BorderStyle.FixedSingle;
+			p.ForeColor = SystemColors.InfoText;
+
+			PictureBox pb = new PictureBox();
+			pb.Width = 16;
+			pb.Height = 16;
+			pb.Left = 8;
+			pb.Top = 8;
+			pb.Image = loading ? Properties.Resources.Loader: Properties.Resources.Warning;
+			p.Controls.Add(pb);
+
+			Label l = new Label();
+			l.Left = 8 + 16 + 8;
+			l.Top = 10;
+			l.MinimumSize = new Size(Width - 32 - 8, 0);
+			l.MaximumSize = new Size(Width - 32 - 8, 640);
+			l.Width = l.MinimumSize.Width;
+			l.Height = 48;
+			l.Text = text;
+			l.AutoSize = true;
+			p.Controls.Add(l);
+
+			// resize panel
+			p.Height = 22 + l.Height;
+
+			Controls.SetChildIndex(p, 0);
+
+			warnings.Add(new Warning(p, pb, l));
+
+			lastTop = p.Bottom + 8;
+
+			resourceitems.Height = Height - lastTop - 32;
+			resourceitems.Top = lastTop;
+        }
+
+		private int GetWarningsHeight()
+        {
+			int lastTop = 0;
+			foreach (Warning w in warnings)
+				lastTop = Math.Max(lastTop, w.Wrapper.Bottom + 8);
+
+			return lastTop;
+		}
+
+		private void UpdateWarnings()
+        {
+			int lastH = GetWarningsHeight();
+
+			foreach (Warning w in warnings)
+				w.Wrapper.Dispose();
+
+			warnings.Clear();
+
+			HandleResize(this, null);
+
+			List<string> requiredarchives = new List<string>();
+			foreach (ListViewItem item in resourceitems.Items)
+			{
+				DataLocation loc = (DataLocation)item.Tag;
+				if (loc.requiredarchives != null)
+					requiredarchives.AddRange(loc.requiredarchives);
+			}
+
+			General.WriteLogLine(string.Format("Archive check: RequiredArchives = {0}", string.Join(",", requiredarchives)));
+
+			// warning 1: you do not have a required file
+			if (GameConfiguration != null)
+            {
+				foreach (RequiredArchive arc in GameConfiguration.RequiredArchives)
+                {
+					if (!requiredarchives.Contains(arc.ID))
+						ShowWarning(string.Format("Warning: a resource archive is required for this game configuration, but not present:\n  \"{0}\"\nWithout it, UDB will have severely limited capabilities.", arc.FileName), false);
+                }
+            }
+
+			// warning 2: map without any resources. this makes sense only on map open dialog and not game configurations dialog
+			if (IsMapControl)
+            {
+				if (resourceitems.Items.Count == 0)
+					ShowWarning("Warning: you are about to edit a map without any resources.\nTextures, flats and sprites may not be shown correctly or may not show up at all.", false);
+            }
+
+			// warning 3: multiple instances of the same required file
+			if (GameConfiguration != null)
+			{
+				for (int i = 0; i < requiredarchives.Count; i++)
+				{
+					if (requiredarchives.IndexOf(requiredarchives[i]) != i)
+					{
+						foreach (RequiredArchive arc in GameConfiguration.RequiredArchives)
+						{
+							if (arc.ID == requiredarchives[i])
+								ShowWarning(string.Format("Warning: required archive was added more than once:\n  \"{0}\"\nThis will most likely not work.", arc.FileName), false);
+						}
+					}
+				}
+			}
+
+			int h = GetWarningsHeight();
+			if (lastH != h && OnWarningsChanged != null)
+			{
+				OnWarningsChanged(h);
+				// possibly recalculate size
+				HandleResize(this, null);
+			}
+        }
+
+		private void RefreshLoading()
+        {
+			resourceitems.BeginUpdate();
+
+			bool anyLoading = false;
+
+			foreach (ListViewItem item in resourceitems.Items)
+            {
+				DataLocation dl = (DataLocation)item.Tag;
+				if (IsLoading(dl.location))
+				{
+					item.ImageIndex = GetLoaderIndex();
+					anyLoading |= true;
+				}
+				else item.ImageIndex = GetIconIndex(dl.type, item.ForeColor != SystemColors.WindowText);
+            }
+
+			resourceitems.EndUpdate();
+
+			if (anyLoading)
+            {
+				foreach (Warning w in warnings)
+					w.Picture.Image = Properties.Resources.Loader;
+            }
+			else
+            {
+				foreach (Warning w in warnings)
+					w.Picture.Image = Properties.Resources.Warning;
+			}
+		}
+
+		private void CancelLoading(string location)
+        {
+			General.WriteLogLine(string.Format("Resource check cancelled for: {0}", location));
+			if (loadingrequiredarchives.ContainsKey(location))
+			{
+				loadingrequiredarchives[location].Cancel();
+				loadingrequiredarchives.Remove(location);
+			}
+        }
+
+		private bool IsLoading(string location)
+        {
+			return loadingrequiredarchives.ContainsKey(location);
+        }
+
+		private int GetLoaderIndex()
+        {
+			return 6;
+        }
 		
 		// This will show a fixed list
 		public void FixedResourceLocationList(DataLocationList list)
@@ -123,8 +405,11 @@ namespace CodeImp.DoomBuilder.Controls
 			for(int i = resourceitems.Items.Count - 1; i >= 0; i--)
 			{
 				// Remove item if not fixed
-				if(resourceitems.Items[i].ForeColor != SystemColors.WindowText)
+				if (resourceitems.Items[i].ForeColor != SystemColors.WindowText)
+				{
+					CancelLoading(((DataLocation)resourceitems.Items[i].Tag).location);
 					resourceitems.Items.RemoveAt(i);
+				}
 				else
 					currentitems.Add((DataLocation)resourceitems.Items[i].Tag); //mxd
 			}
@@ -138,17 +423,25 @@ namespace CodeImp.DoomBuilder.Controls
 				// Add item as fixed
 				resourceitems.Items.Insert(0, new ListViewItem(list[i].location));
 				resourceitems.Items[0].Tag = list[i];
-				resourceitems.Items[0].ImageIndex = GetIconIndex(list[i].type, true);
+				resourceitems.Items[0].ImageIndex = IsLoading(list[i].location) ? GetLoaderIndex() : GetIconIndex(list[i].type, true);
 
 				// Set disabled
 				resourceitems.Items[0].ForeColor = SystemColors.GrayText;
 
 				// Validate path (mxd)
 				resourceitems.Items[0].BackColor = (list[i].IsValid() ? resourceitems.BackColor : Color.MistyRose);
+
+				// Check if resource has no info about membership in Game configuration's requiredarchives
+				// This normally happens if it was imported from old DBS or drag-dropped
+				if (list[i].requiredarchives == null)
+					StartRequiredArchivesCheck(list[i].location);
 			}
 
 			// Done
 			resourceitems.EndUpdate();
+
+			if (loadingrequiredarchives.Count == 0)
+				UpdateWarnings();
 		}
 
 		// This will edit the given list
@@ -165,8 +458,11 @@ namespace CodeImp.DoomBuilder.Controls
 			for(int i = resourceitems.Items.Count - 1; i >= 0; i--)
 			{
 				// Remove item unless fixed
-				if(resourceitems.Items[i].ForeColor == SystemColors.WindowText)
+				if (resourceitems.Items[i].ForeColor == SystemColors.WindowText)
+				{
+					CancelLoading(((DataLocation)resourceitems.Items[i].Tag).location);
 					resourceitems.Items.RemoveAt(i);
+				}
 			}
 
 			// Go for all items
@@ -176,6 +472,9 @@ namespace CodeImp.DoomBuilder.Controls
 				AddItem(dl);
 			}
 
+			if (loadingrequiredarchives.Count == 0)
+				UpdateWarnings();
+
 			// Done
 			resourceitems.EndUpdate();
 			ResizeColumnHeader();
@@ -208,7 +507,7 @@ namespace CodeImp.DoomBuilder.Controls
 			int index = resourceitems.Items.Count;
 			resourceitems.Items.Add(new ListViewItem(rl.location));
 			resourceitems.Items[index].Tag = rl;
-			resourceitems.Items[index].ImageIndex = GetIconIndex(rl.type, false);
+			resourceitems.Items[index].ImageIndex = IsLoading(rl.location) ? GetLoaderIndex() : GetIconIndex(rl.type, false);
 			
 			// Set normal color
 			resourceitems.Items[index].ForeColor = SystemColors.WindowText;
@@ -218,6 +517,12 @@ namespace CodeImp.DoomBuilder.Controls
 
 			// Done
 			resourceitems.EndUpdate();
+
+			// Check if resource has no info about membership in Game configuration's requiredarchives
+			// This normally happens if it was imported from old DBS or drag-dropped
+			if (rl.requiredarchives == null)
+				StartRequiredArchivesCheck(rl.location);
+
 			return true;
 		}
 		
@@ -240,6 +545,7 @@ namespace CodeImp.DoomBuilder.Controls
 		{
 			// Open resource options dialog
 			ResourceOptionsForm resoptions = new ResourceOptionsForm(new DataLocation(), "Add Resource", StartPath);
+			resoptions.GameConfiguration = GameConfiguration;
 			resoptions.StartPosition = FormStartPosition.Manual;
 			Rectangle startposition = new Rectangle(dialogoffset.X, dialogoffset.Y, 1, 1);
 			startposition = this.RectangleToScreen(startposition);
@@ -257,6 +563,9 @@ namespace CodeImp.DoomBuilder.Controls
 					General.Interface.DisplayStatus(StatusType.Warning, "Resource already added!"); //mxd
 					return; //mxd
 				}
+
+				if (loadingrequiredarchives.Count == 0)
+					UpdateWarnings();
 			}
 
 			// Raise content changed event
@@ -274,6 +583,7 @@ namespace CodeImp.DoomBuilder.Controls
 
 				// Open resource options dialog
 				ResourceOptionsForm resoptions = new ResourceOptionsForm((DataLocation)selecteditem.Tag, "Resource Options", StartPath);
+				resoptions.GameConfiguration = GameConfiguration;
 				resoptions.StartPosition = FormStartPosition.Manual;
 				Rectangle startposition = new Rectangle(dialogoffset.X, dialogoffset.Y, 1, 1);
 				startposition = this.RectangleToScreen(startposition);
@@ -292,13 +602,20 @@ namespace CodeImp.DoomBuilder.Controls
 					DataLocation rl = resoptions.ResourceLocation;
 					selecteditem.Text = rl.location;
 					selecteditem.Tag = rl;
-					selecteditem.ImageIndex = GetIconIndex(rl.type, false);
+					selecteditem.ImageIndex = IsLoading(rl.location) ? GetLoaderIndex() : GetIconIndex(rl.type, false);
 					
 					// Done
 					resourceitems.EndUpdate();
-					
+
+					// Check if resource has no info about membership in Game configuration's requiredarchives
+					// This normally happens if it was imported from old DBS or drag-dropped
+					if (rl.requiredarchives == null)
+						StartRequiredArchivesCheck(rl.location);
+					else if (loadingrequiredarchives.Count == 0)
+						UpdateWarnings();
+
 					// Raise content changed event
-					if(OnContentChanged != null) OnContentChanged();
+					if (OnContentChanged != null) OnContentChanged();
 				}
 			}
 		}
@@ -402,19 +719,19 @@ namespace CodeImp.DoomBuilder.Controls
 						{
 							case ".wad":
                             case ".iwad":
-								if(AddItem(new DataLocation(DataLocation.RESOURCE_WAD, path, false, false, false))) addedfiles++;
+								if(AddItem(new DataLocation(DataLocation.RESOURCE_WAD, path, false, false, false, null))) addedfiles++;
 								break;
 							case ".pk7":
 							case ".pk3":
                             case ".ipk3":
                             case ".ipk7":
-								if(AddItem(new DataLocation(DataLocation.RESOURCE_PK3, path, false, false, false))) addedfiles++;
+								if(AddItem(new DataLocation(DataLocation.RESOURCE_PK3, path, false, false, false, null))) addedfiles++;
 								break;
 						}
 					}
 					else if(Directory.Exists(path))
 					{
-						if(AddItem(new DataLocation(DataLocation.RESOURCE_DIRECTORY, path, false, false, false))) addedfiles++;
+						if(AddItem(new DataLocation(DataLocation.RESOURCE_DIRECTORY, path, false, false, false, null))) addedfiles++;
 					}
 				}
 
@@ -424,9 +741,12 @@ namespace CodeImp.DoomBuilder.Controls
 					return;
 				}
 			}
-			
+
+			if (loadingrequiredarchives.Count == 0)
+				UpdateWarnings();
+
 			// Raise content changed event
-			if(OnContentChanged != null) OnContentChanged();
+			if (OnContentChanged != null) OnContentChanged();
 		}
 
 		// Client size changed
@@ -469,8 +789,11 @@ namespace CodeImp.DoomBuilder.Controls
 				// Display notification
 				General.Interface.DisplayStatus(StatusType.Info, pastedcount + " Resource" + (pastedcount > 1 ? "s" : "") + " Pasted");
 
+				if (loadingrequiredarchives.Count == 0)
+					UpdateWarnings();
+
 				// Raise content changed event
-				if(OnContentChanged != null) OnContentChanged();
+				if (OnContentChanged != null) OnContentChanged();
 			}
 		}
 
@@ -500,8 +823,11 @@ namespace CodeImp.DoomBuilder.Controls
 				// Display notification
 				General.Interface.DisplayStatus(StatusType.Info, pastedcount + " Resource" + (pastedcount > 1 ? "s" : "") + " Replaced");
 
+				if (loadingrequiredarchives.Count == 0)
+					UpdateWarnings();
+
 				// Raise content changed event
-				if(OnContentChanged != null) OnContentChanged();
+				if (OnContentChanged != null) OnContentChanged();
 			}
 		}
 
@@ -519,8 +845,11 @@ namespace CodeImp.DoomBuilder.Controls
 
 			ResizeColumnHeader();
 
+			if (loadingrequiredarchives.Count == 0)
+				UpdateWarnings();
+
 			// Raise content changed event
-			if(OnContentChanged != null) OnContentChanged();
+			if (OnContentChanged != null) OnContentChanged();
 		}
 
 		#endregion
@@ -580,14 +909,19 @@ namespace CodeImp.DoomBuilder.Controls
 
 		#endregion
 
+
 		#region ================== Events (mxd)
 
 		//mxd. Because anchor-based alignment fails when using high-Dpi settings...
-		private void ResourceListEditor_Resize(object sender, EventArgs e)
+		private void HandleResize(object sender, EventArgs e)
 		{
+			int warningsH = GetWarningsHeight();
+
 			resourceitems.Width = this.Width;
-			resourceitems.Height = this.Height - addresource.Height - addresource.Margin.Top - addresource.Margin.Bottom;
-			
+			resourceitems.Top = warningsH;
+			resourceitems.Left = 0;
+			resourceitems.Height = this.Height - addresource.Height - addresource.Margin.Top - addresource.Margin.Bottom - resourceitems.Top;
+
 			addresource.Top = resourceitems.Bottom + addresource.Margin.Top;
 			editresource.Top = addresource.Top;
 			deleteresources.Top = addresource.Top;
diff --git a/Source/Core/Controls/ResourceListEditor.resx b/Source/Core/Controls/ResourceListEditor.resx
index 3d32f7f3694b74090842448f3e2a6ee79b864c50..c721d898e50f5b186c45d4d06dfeabf061e74b34 100755
--- a/Source/Core/Controls/ResourceListEditor.resx
+++ b/Source/Core/Controls/ResourceListEditor.resx
@@ -112,120 +112,141 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
-  <metadata name="copypastemenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="copypastemenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>195, 17</value>
   </metadata>
-  <metadata name="images.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="images.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
   <data name="images.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
     <value>
-        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj0yLjAuMC4w
+        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
         LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
-        ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAAi
-        FgAAAk1TRnQBSQFMAgEBBgEAAZwBAAGcAQABEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA
-        AwABIAMAAQEBAAEgBgABIDcAApYB/wEAApYB/wEAAkAB/wEAAkAB/wEAAkAB/wEAAkAB/y0AApYB/wEA
-        ApYB/wEAAkAB/wEAAkAB/wEAAkAB/wEAAkAB/5QAA4UB/wOFAf8DhQH/A4UB/wEAArkB/wEAApYB/wEA
-        ApYB/wEAAoEB/wEAAoEB/wEAAkAB/xAAA4AB/wOBAf8DgAH/A4AB/wOBAf8DgQH/A4AB/wEAArkB/wEA
-        ApYB/wEAApYB/wEAAoEB/wEAAoEB/wEAAkAB/5AAA4UB/wOFAf8DxgH/A8kB/wPGAf8BAAK5Af8DoAH/
-        A2oB/wNqAf8BAAKBAf8BAAJAAf8MAAOAAf8DVAH/A4AB/wOVAf8DyQH/A9oB/wPaAf8DvAH/AQACuQH/
-        A6gB/wOBAf8DgQH/AQACgQH/AQACQAH/jAADhQH/A8EB/wOFAf8D5QH/A+EB/wPlAf8BAAK5Af8BAAP/
-        AQAD/wEAA/8BAAKBAf8BAAJAAf8IAAOAAf8DgQH/A4EB/wOAAf8DgQH/A4AB/wOBAf8DgAH/A4EB/wEA
-        ArkB/wEAA/8BAAP/AQAD/wEAAoEB/wEAAkAB/4gAA4UB/wPBAf8D0QH/A4UB/wPhAf8D4QH/A+UB/wEA
-        ArkB/wEAApYB/wEAApYB/wEAAoEB/wEAAoEB/wEAAmMB/xAAA48B/wOAAf8DgQH/A4AB/wOBAf8DgAH/
-        A4EB/wEAArkB/wEAApYB/wEAApYB/wEAAoEB/wEAAoEB/wEAAmMB/4gAA4UB/wOFAf8DhQH/A8EB/wPe
-        Af8D3gH/A+EB/wEAArkB/wEAAkAB/wOxAf8DCgH/AQACgQH/AQACQAH/DAADjwH/A+MB/wOPAf8D1QH/
-        A9oB/wPeAf8D4wH/A4AB/wEAArkB/wEAAkAB/wOxAf8DgQH/AQACgQH/AQACQAH/iAADhQH/A+EB/wPR
-        Af8D1QH/A9kB/wPeAf8D4QH/AQACuQH/AQACQAH/AwAB/wMKAf8BAAKWAf8BAAJAAf8MAAOPAf8DjwH/
-        A48B/wPRAf8D2gH/A94B/wPeAf8DgAH/AQACuQH/AQACQAH/AwAB/wEPAQcBCAH/AQAClgH/AQACQAH/
-        iAADhQH/A94B/wPNAf8D0QH/A9UB/wPZAf8D2QH/A94B/wEAArkB/wEAArkB/wEAApYB/wEAAkAB/xAA
-        A48B/wPeAf8D0QH/A9EB/wPVAf8D2gH/A9oB/wOAAf8FAAK5Af8BAAK5Af8BAAKWAf8BAAJAAf+MAAOF
-        Af8D3gH/A80B/wPNAf8D0QH/A9UB/wPZAf8D2QH/A94B/wPZAf8DhQH/FAADjwH/A94B/wPNAf8DzQH/
-        A9EB/wPVAf8D2gH/A4AB/wQAA48B/wOPAf8DgQH/kAADhQH/A94B/wPJAf8DyQH/A80B/wPRAf8D1QH/
-        A9UB/wPZAf8D1QH/A4UB/xQAA48B/wPeAf8DyQH/A8kB/wPNAf8D0QH/A9UB/wOAAf8EAAOBAf8D0QH/
-        A6QB/5AAA4UB/wPeAf8DxgH/A8kB/wPJAf8DzQH/A9EB/wPRAf8D1QH/A9UB/wOFAf8UAAOPAf8D3gH/
-        A8UB/wPFAf8DyQH/A80B/wPNAf8DgQH/BAADjwH/A48B/wOAAf+QAAOFAf8D2QH/A8EB/wPBAf8DxgH/
-        A8kB/wPNAf8DzQH/A9UB/wPRAf8DhQH/FAADjwH/A9oB/wPBAf8DwQH/A8UB/wPJAf8DzQH/A4AB/wQA
-        A4AB/wPRAf8DpAH/kAADhQH/A9kB/wPBAf8DxgH/A8YB/wPJAf8DzQH/A80B/wPRAf8D0QH/A4UB/xQA
-        A48B/wOPAf8DjwH/A48B/wOPAf8DjwH/A48B/wOPAf8EAAOAAf8DoAH/A4EB/5AAA4UB/wP1Af8D4QH/
-        A94B/wPeAf8D2QH/A9kB/wPZAf8D1QH/A9UB/wOFAf8QAAOAAf8DgAH/A4EB/wOAAf8DgQH/A4EB/wOB
-        Af8DgQH/A4EB/wOAAf8DmgH/A58B/wNRAf8DgAH/jAADhQH/A4UB/wOFAf8DhQH/A4UB/wOFAf8DhQH/
-        A4UB/wOFAf8DhQH/A4UB/xQAA4EB/wNaAf8DlQH/A8kB/wPaAf8D2gH/A9oB/wPaAf8D2gH/A7wB/wOA
-        Af8DgAH/A4AB/9AAA4AB/wOAAf8DgAH/A4AB/wOAAf8DgAH/A4AB/wOAAf8DgAH/A4AB/wOAAf//ADQA
-        Af8DAAH/AwAB/wMAAf8tAAKWAf8BAAKWAf8BAAJAAf8BAAJAAf8BAAJAAf8BAAJAAf9YAAMPAf8DDwH/
-        Aw8B/wMPAf8DDwH/Aw8B/wMPAf8DDwH/GAABAgIAAf8BAgIAAf8BAgIAAf8BAgIAAf8BAgIAAf8BAgIA
-        Af8BAgIAAf8BHQETAQ8B/wFkAWABVgH/AVEBTAEfAf8BDgEJAQAB/wECAgAB/ykAArkB/wEAApYB/wEA
-        ApYB/wEAAoEB/wEAAoEB/wEAAkAB/wQAAYEBiAGQAf8BYQGBAZAB/wFhAoEB/wFRAoEB/wFRAWEBgQH/
-        AUEBWQFhAf8BQQFJAVEB/wExATkBQQH/ASECMQH/AiEBMQH/AREBGQEhAf8DEQH/AhEBIQH/HAADDwH/
-        AwEB/wOwAf8DsQH/A7AB/wOtAf8DqwH/A6AB/wMPAf8UAAENAgAB/wExAR8BAAH/AVUBRwEhAf8BnwGC
-        ASgB/wHbAccBkAH/AeYB1wG0Af8B5gHXAbQB/wG0Aa4BjwH/AWcBXQE6Af8BawFdATkB/wFKAT4BFwH/
-        AREBBQEAAf8BAgIAAf8EAAOXAf8DkQH/A4kB/wOEAf8DgQH/A2EB/wNaAf8DTQH/A0AB/wEAArkB/wOg
-        Af8DagH/A2oB/wEAAoEB/wEAAkAB/wQAAYEBiAGQAf8BkAGoAbAB/wGQAagBsAH/AQEBkAHQAf8BAQGQ
-        AdAB/wEBAZAB0AH/AQEBkAHAAf8BEQGIAcAB/wERAYEBsAH/AREBgQGwAf8BIQGBAaAB/wEhAYEBkAH/
-        ASEBSQFhAf8DkgH/FAADDwH/A6oB/wMBAf8D1wH/A9kB/wPcAf8D3wH/A+EB/wPPAf8DDwH/EwAB/wMA
-        Af8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AQ4BBwEDAf8BXgFVAUEB/wFkAVMBJgH/ARwBDQEA
-        Af8BAgIAAf8EAAOXAf8DsQH/A7EB/wOEAf8DhAH/A4QB/wOBAf8DhAH/A4EB/wEAArkB/wEAA/8BAAP/
-        AQAD/wEAAoEB/wEAAkAB/wQAAYEBiAGQAf8BYQHYAv8BkAGoAbAB/wGBAeAC/wFhAdAC/wFRAcgC/wFR
-        AcgC/wFBAcAB8AH/ATEBsAHwAf8BMQGoAfAB/wEhAaAB4AH/AREBkAHQAf8BIQFpAYEB/wFaAWIBaAH/
-        EAADDwH/A6oB/wPBAf8DAQH/A9UB/wPZAf8D3QH/A+EB/wPlAf8D0gH/Aw8B/xgAA1QB/wMAAf8DAAH/
-        AwAB/wMAAf8DAAH/AwAB/wQAASgBIAEaAf8BYAFaAVAB/wELAQEBBAH/CAADngH/A74B/wOxAf8DywH/
-        A74B/wO4Af8DuAH/A6sB/wOkAf8BAAK5Af8BAAKWAf8BAAKWAf8BAAKBAf8BAAKBAf8BAAJjAf8EAAGB
-        AZABoAH/AWEB2AL/AZABqAGwAf8BkAHAAdAB/wGBAdgC/wFhAdAC/wFhAdAC/wFRAcgC/wFRAcAC/wFB
-        AbgB8AH/ATEBsAHwAf8BMQGoAfAB/wERAYgB0AH/ASEBSQFhAf8DkgH/DAADDwH/AwEB/wMBAf8DqgH/
-        A84B/wPTAf8D2AH/A9wB/wPgAf8D0QH/Aw8B/xQAA1QB/wPYAf8DVAH/A8AB/wPOAf8D0wH/A9gB/wMA
-        Af8EAAEiARgBFwH/A8AB/wOBAf8IAAOkAf8DvgH/A7EB/wO+Af8DxAH/A74B/wO+Af8DuAH/A7gB/wEA
-        ArkB/wEAAkAB/wOxAf8DCgH/AQACgQH/AQACQAH/BAABgQGQAaAB/wGBAdgB8AH/AWEB2AL/AZABqAGw
-        Af8BgQHgAv8BgQHQAv8BYQHYAv8BYQHQAv8BYQHQAv8BUQHIAv8BQQHAAfAB/wFBAbgB8AH/ATEBsAHw
-        Af8BIQFpAYEB/wOBAf8MAAMPAf8D1QH/A8EB/wPEAf8DyQH/A88B/wPTAf8D1wH/A9sB/wPNAf8DDwH/
-        FAADVAH/A1QB/wNUAf8DwAH/A8kB/wPPAf8D0wH/AwAB/wQAA1QB/wNUAf8BDgEFAQcB/wgAA6QB/wPE
-        Af8DvgH/A7EB/wPLAf8DxAH/A74B/wO+Af8DvgH/AQACuQH/AQACQAH/AwAB/wMKAf8BAAKWAf8BAAJA
-        Af8EAAGBAZgBoAH/AZAB4AHwAf8BYQHYAv8BkAGoAbAB/wGQAbgBwAH/AYEB2AL/AWEB2AL/AWEB2AL/
-        AWEB2AL/AWEB0AL/AVEB0AL/AVEByAL/AUEBuAHwAf8BMQGgAeAB/wFKAWgBgQH/A5IB/wgAAw8B/wPT
-        Af8DvAH/A8AB/wPFAf8DyQH/A80B/wPRAf8D1QH/A8wB/wMPAf8UAANUAf8D0wH/A7wB/wPAAf8DxQH/
-        A8kB/wPNAf8DAAH/BAABHAESARMB/wPAAf8DgQH/CAADpAH/A8sB/wO+Af8DsQH/A7gB/wPEAf8DvgH/
-        A74B/wO+Af8DvgH/AQACuQH/AQACuQH/AQAClgH/AQACQAH/ATkBVwFnAf8DpAH/AYEBmAGgAf8BkAHg
-        AfAB/wGgAegC/wFhAdgC/wGQAagBsAH/AYEB4AL/AYEB4AL/AYEB4AL/AYEB4AL/AYEB4AL/AYEB4AL/
-        AYEB4AL/AYEB2AL/AYEB2AL/AVEBqAHQAf8DgQH/CAADDwH/A9EB/wO3Af8DuwH/A8AB/wPEAf8DyAH/
-        A8wB/wPRAf8DyQH/Aw8B/xQAA1QB/wPRAf8DtwH/A7sB/wPAAf8DxAH/A8gB/wMAAf8EAANUAf8DVAH/
-        AQcBAAEBAf8IAAOkAf8DywH/A9gB/wO+Af8DsQH/A8sB/wPLAf8DywH/A8sB/wPLAf8DywH/A8sB/wPE
-        Af8DxAH/A6QB/wOJAf8BkAKgAf8BoAHoAfAB/wGgAegC/wGgAegC/wGQAagBsAH/AZABqAGwAf8BkAGo
-        AbAB/wGQAagBsAH/AYEBoAGwAf8BgQGgAbAB/wGBAZgBoAH/AYEBmAGgAf8BgQGQAaAB/wGBAZABoAH/
-        AYEBiAGQAf8BgQGIAZAB/wgAAw8B/wPRAf8DswH/A7UB/wO5Af8DvwH/A8MB/wPHAf8DywH/A8YB/wMP
-        Af8UAANUAf8D0QH/A7MB/wO1Af8DuQH/A78B/wPDAf8DAAH/BAABHAESARMB/wPAAf8DgQH/CAADqwH/
-        A9IB/wPYAf8D2AH/A7EB/wOxAf8DsQH/A7EB/wOrAf8DqwH/A6QB/wOkAf8DpAH/A6QB/wOeAf8DlwH/
-        AZABoAGwAf8BoAHoAfAB/wGgAfAC/wGgAegC/wGgAegC/wGBAdgC/wFhAdgC/wFhAdgC/wFhAdgC/wFh
-        AdgC/wFhAdgC/wFhAdgC/wGBAYgBkAH/FAADDwH/A9EB/wOtAf8DsQH/A7QB/wO5Af8DvAH/A8EB/wPF
-        Af8DxAH/Aw8B/xQAA1QB/wPRAf8DrQH/A7EB/wO0Af8DuQH/A7wB/wMAAf8EAANUAf8DVAH/AQcBAAEB
-        Af8IAAOxAf8D0gH/A9gB/wPYAf8D2AH/A8sB/wO+Af8DvgH/A74B/wO+Af8DvgH/A74B/wOXAf8MAAGQ
-        AaABsAH/AaAC8AH/AbAC8AH/AaAB8AL/AaAB6AL/AaAB6AL/AYEB2AL/AZACoAH/AYEBmAGgAf8BgQGY
-        AaAB/wGBAZABoAH/AYECkAH/AYEBiAGQAf8UAAMPAf8DygH/A6gB/wOsAf8DsAH/A7QB/wO4Af8DvAH/
-        A8AB/wPBAf8DDwH/FAADVAH/A8oB/wOoAf8DrAH/A7AB/wO0Af8DuAH/AwAB/wQAARwBEgETAf8DwAH/
-        A4EB/wgAA7EB/wPSAf8D2AH/A9gB/wPYAf8D2AH/A8QB/wOrAf8DpAH/A6QB/wOkAf8DngH/A5cB/wwA
-        AZABqAGwAf8BoAHQAeAB/wGwAvAB/wGwAvAB/wGgAfAC/wGgAegC/wGQAaABsAH/A5IB/ygAAw8B/wPL
-        Af8DqQH/A6wB/wOwAf8DtAH/A7cB/wO7Af8DvQH/A8AB/wMPAf8UAANUAf8DVAH/A1QB/wNUAf8DVAH/
-        A1QB/wNUAf8DVAH/BAABEQEJAQoB/wGFAYMBYgH/ARMBCgEJAf8IAAOxAf8DywH/A9gB/wPYAf8D2AH/
-        A9gB/wOxAf8DpAH/JAABkAGoAbAB/wGQAagBsAH/AZABqAGwAf8BkAGoAbAB/wGQAagBsAH/A5IB/ywA
-        Aw8B/wPyAf8D1QH/A9IB/wPRAf8DzgH/A8sB/wPKAf8DxwH/A8YB/wMPAf8TAAH/AQICAAH/AQICAAH/
-        AQICAAH/AQICAAH/AQICAAH/AQICAAH/AQICAAH/AQICAAH/AQICAAH/AYEBaAFZAf8BnQGRAUgB/wEt
-        AR0BAAH/AwAB/wgAA7EB/wOxAf8DsQH/A7EB/wOxAf8DpAH/bAADDwH/Aw8B/wMPAf8DDwH/Aw8B/wMP
-        Af8DDwH/Aw8B/wMPAf8DDwH/Aw8B/xQAAQUCAAH/AUUBKQEAAf8BnwGCASgB/wHbAccBkAH/AeYB1wG0
-        Af8B5gHXAbQB/wHmAdcBtAH/AeYB1wG0Af8B4AHVAbEB/wG0Aa4BjwH/AVIBRgEjAf8BFQEIAQAB/wEC
-        AgAB/9MAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BAgIAAf9IAAFCAU0BPgcA
-        AT4DAAEoAwABQAMAASADAAEBAQABAQYAAQEWAAP/AQAB/wGBAf8BwAQAAfgBAQHgBQAB8AEBAcAFAAHg
-        AQEBgAUAAcABAQHgBQABwAEBAcAFAAHAAQEBwAUAAcABAwHAASEEAAHAAQcBwAEjBAABwAEHAcABIwQA
-        AcABBwHAASMEAAHAAQcBwAEjBAABwAEHAcABIwQAAcABBwGAAQEEAAHAAQcBwAEBBAAC/wHgAQMEAAX/
-        AcMB/wGBAv8B+AEHAeABAQH/AYEBAAEHAfABBwHAAQEBAAEBAQABAwHgAQcBgAEBAQABAQEAAQMBwAEH
-        AeABIwEAAQEBAAEBAcABBwHAASMBAAEBAQABAQHAAQcBwAEjAQABAQIAAcABBwHAASMEAAHAAQcBwAEj
-        BAABwAEHAcABIwMAAQcBwAEHAcABIwEAAQcBAAEHAcABBwHAASMBAAEHAQAB/wHAAQcBwAEjAQAB/wGB
-        Af8BwAEHAYABAQGBA/8BwAEHAcABAQb/AeABAwL/Cw==
+        ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAAk
+        GwAAAk1TRnQBSQFMAgEBBwEAAdQBAAHUAQABEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA
+        AwABIAMAAQEBAAEgBgABIDcAApYB/wEAApYB/wEAAjkB/wEAAjkB/wEAAjkB/wEAAjkB/y0AApYB/wEA
+        ApYB/wEAAjkB/wEAAjkB/wEAAjkB/wEAAjkB/xQAAWUBOgEGAf8BoAFaAREB/wG1AWkBFgH/AbUBaQEW
+        Af8BoAFaAREB/wFlAToBBgH/aAADhQH/A4UB/wOFAf8DhQH/AQACuQH/AQAClgH/AQAClgH/AQACgQH/
+        AQACgQH/AQACOQH/EAADgAH/A4EB/wOAAf8DgAH/A4EB/wOBAf8DgAH/AQACuQH/AQAClgH/AQAClgH/
+        AQACgQH/AQACgQH/AQACOQH/DAABXQE0AQQB/wHRAYEBHAH/Ae8BlQEiAf8B8wGXASMB/wHzAZcBIwH/
+        AfMBlwEjAf8B8wGXASMB/wHvAZUBIgH/AdEBgQEcAf8BXQE0AQQB/1wAA4UB/wOFAf8DxgH/A8kB/wPG
+        Af8BAAK5Af8DoAH/A2MB/wNjAf8BAAKBAf8BAAI5Af8MAAOAAf8DTQH/A4AB/wOVAf8DyQH/A9oB/wPa
+        Af8DvAH/AQACuQH/A6gB/wOBAf8DgQH/AQACgQH/AQACOQH/CAABhgFKAQsB/wHqAZIBIgH/AfMBlwEj
+        Af8B8wGXASMB/wHzAZcBIwH/AfMBlwEjAf8B8wGXASMB/wHzAZcBIwH/AfMBlwEjAf8B8wGXASMB/wHq
+        AZIBIgH/AYYBSgELAf9UAAOFAf8DwQH/A4UB/wPlAf8D4QH/A+UB/wEAArkB/wEAA/8BAAP/AQAD/wEA
+        AoEB/wEAAjkB/wgAA4AB/wOBAf8DgQH/A4AB/wOBAf8DgAH/A4EB/wOAAf8DgQH/AQACuQH/AQAD/wEA
+        A/8BAAP/AQACgQH/AQACOQH/BAABYgFLAQIB/wHyAbYBHgH/AfQBmwEjAf8B8wGXASMB/wHyAZYBIwH/
+        AdsBiAEeAf8BvAFtARcB/wG8AW0BFwH/AdsBiAEeAf8B8gGWASMB/wHzAZcBIwH/AfMBlwEjAf8B6gGS
+        ASIB/wFdATQBBAH/TAADhQH/A8EB/wPRAf8DhQH/A+EB/wPhAf8D5QH/AQACuQH/AQAClgH/AQAClgH/
+        AQACgQH/AQACgQH/AQACXAH/EAADjwH/A4AB/wOBAf8DgAH/A4EB/wOAAf8DgQH/AQACuQH/AQAClgH/
+        AQAClgH/AQACgQH/AQACgQH/AQACXAH/BAAB3AGvARcC/wHMAR4B/wH9AcMBHwH/Ae0BmwEhAf8BkAFR
+        AQ4B/xAAAY4BUAENAf8B7AGSASIB/wHzAZcBIwH/AfMBlwEjAf8B0QGBARwB/0wAA4UB/wOFAf8DhQH/
+        A8EB/wPeAf8D3gH/A+EB/wEAArkB/wEAAjkB/wOxAf8DAwH/AQACgQH/AQACOQH/DAADjwH/A+MB/wOP
+        Af8D1QH/A9oB/wPeAf8D4wH/A4AB/wEAArkB/wEAAjkB/wOxAf8DgQH/AQACgQH/AQACOQH/AWsBUwED
+        Af8B+wHJAR0C/wHMAR4B/wH+AcsBHgH/AZYBcAELAf8YAAGQAVEBDgH/AfIBlgEjAf8B8wGXASMB/wHv
+        AZUBIgH/AWQBOQEGAf9IAAOFAf8D4QH/A9EB/wPVAf8D2QH/A94B/wPhAf8BAAK5Af8BAAI5Af8DAAH/
+        AwMB/wEAApYB/wEAAjkB/wwAA48B/wOPAf8DjwH/A9EB/wPaAf8D3gH/A94B/wOAAf8BAAK5Af8BAAI5
+        Af8DAAH/AQgBAAEBAf8BAAKWAf8BAAI5Af8BqAGEAQ0C/wHMAR4C/wHMAR4B/wHmAbcBGQH/IAAB2wGI
+        AR4B/wHzAZcBIwH/AfMBlwEjAf8BnwFbAREB/0gAA4UB/wPeAf8DzQH/A9EB/wPVAf8D2QH/A9kB/wPe
+        Af8BAAK5Af8BAAK5Af8BAAKWAf8BAAI5Af8QAAOPAf8D3gH/A9EB/wPRAf8D1QH/A9oB/wPaAf8DgAH/
+        BQACuQH/AQACuQH/AQAClgH/AQACOQH/BAABvwGWARIC/wHMAR4C/wHMAR4B/wHFAZ0BEwH/IAABvAFt
+        ARcB/wHzAZcBIwH/AfMBlwEjAf8BtgFpARYB/0gAA4UB/wPeAf8DzQH/A80B/wPRAf8D1QH/A9kB/wPZ
+        Af8D3gH/A9kB/wOFAf8UAAOPAf8D3gH/A80B/wPNAf8D0QH/A9UB/wPaAf8DgAH/BAADjwH/A48B/wOB
+        Af8IAAG/AZYBEgL/AcwBHgL/AcwBHgH/AcUBnQETAf8gAAG8AW0BFwH/AfMBlwEjAf8B8wGXASMB/wG2
+        AWkBFgH/SAADhQH/A94B/wPJAf8DyQH/A80B/wPRAf8D1QH/A9UB/wPZAf8D1QH/A4UB/xQAA48B/wPe
+        Af8DyQH/A8kB/wPNAf8D0QH/A9UB/wOAAf8EAAOBAf8D0QH/A6QB/wgAAacBhAEOAv8BzAEeAv8BygEe
+        Af8B5AGrARsB/yAAAdsBhwEeAf8B8wGXASMB/wHzAZcBIwH/AaABWgERAf9IAAOFAf8D3gH/A8YB/wPJ
+        Af8DyQH/A80B/wPRAf8D0QH/A9UB/wPVAf8DhQH/FAADjwH/A94B/wPFAf8DxQH/A8kB/wPNAf8DzQH/
+        A4EB/wQAA48B/wOPAf8DgAH/CAABagFNAQQB/wH0Aa0BIAH/AfQBnQEiAf8B8gGWASMB/wGOAVABDQH/
+        GAABjgFPAQ0B/wHyAZYBIwH/AfMBlwEjAf8B7wGVASIB/wFlAToBBgH/SAADhQH/A9kB/wPBAf8DwQH/
+        A8YB/wPJAf8DzQH/A80B/wPVAf8D0QH/A4UB/xQAA48B/wPaAf8DwQH/A8EB/wPFAf8DyQH/A80B/wOA
+        Af8EAAOAAf8D0QH/A6QB/wwAAdEBgQEcAf8B8wGXASMB/wHzAZcBIwH/AewBkgEiAf8BjgFPAQ0B/xAA
+        AY4BUAENAf8B7AGSASIB/wHzAZcBIwH/AfMBlwEjAf8B0QGBARwB/0wAA4UB/wPZAf8DwQH/A8YB/wPG
+        Af8DyQH/A80B/wPNAf8D0QH/A9EB/wOFAf8UAAOPAf8DjwH/A48B/wOPAf8DjwH/A48B/wOPAf8DjwH/
+        BAADgAH/A6AB/wOBAf8MAAFdATQBBAH/AeoBkgEiAf8B8wGXASMB/wHzAZcBIwH/AfIBlgEjAf8B2wGH
+        AR4B/wG8AW0BFwH/AbwBbQEXAf8B2wGIAR4B/wHyAZYBIwH/AfMBlwEjAf8B8wGXASMB/wHqAZIBIgH/
+        AV0BNAEEAf9MAAOFAf8D9QH/A+EB/wPeAf8D3gH/A9kB/wPZAf8D2QH/A9UB/wPVAf8DhQH/EAADgAH/
+        A4AB/wOBAf8DgAH/A4EB/wOBAf8DgQH/A4EB/wOBAf8DgAH/A5oB/wOfAf8DSgH/A4AB/wwAAYYBSgEL
+        Af8B6gGSASIB/wHzAZcBIwH/AfMBlwEjAf8B8wGXASMB/wHzAZcBIwH/AfMBlwEjAf8B8wGXASMB/wHz
+        AZcBIwH/AfMBlwEjAf8B6gGSASIB/wGGAUoBCwH/UAADhQH/A4UB/wOFAf8DhQH/A4UB/wOFAf8DhQH/
+        A4UB/wOFAf8DhQH/A4UB/xQAA4EB/wNTAf8DlQH/A8kB/wPaAf8D2gH/A9oB/wPaAf8D2gH/A7wB/wOA
+        Af8DgAH/A4AB/xAAAVwBNAEEAf8B0QGBARwB/wHvAZUBIgH/AfMBlwEjAf8B8wGXASMB/wHzAZcBIwH/
+        AfMBlwEjAf8B7wGVASIB/wHRAYEBHAH/AV0BNAEEAf+YAAOAAf8DgAH/A4AB/wOAAf8DgAH/A4AB/wOA
+        Af8DgAH/A4AB/wOAAf8DgAH/HAABZQE6AQYB/wGgAVoBEQH/AbUBaQEWAf8BtQFpARYB/wGgAVoBEQH/
+        AWUBOgEGAf//AAH/AwAB/wMAAf8DAAH/LQAClgH/AQAClgH/AQACOQH/AQACOQH/AQACOQH/AQACOQH/
+        WAADCAH/AwgB/wMIAf8DCAH/AwgB/wMIAf8DCAH/AwgB/xsAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMA
+        Af8BFgEMAQgB/wFdAVkBTwH/AUoBRQEYAf8BBwECAQAB/wMAAf8pAAK5Af8BAAKWAf8BAAKWAf8BAAKB
+        Af8BAAKBAf8BAAI5Af8EAAGBAYgBkAH/AVoBgQGQAf8BWgKBAf8BSgKBAf8BSgFaAYEB/wE6AVIBWgH/
+        AToBQgFKAf8BKgEyAToB/wEaAioB/wIaASoB/wEKARIBGgH/AwoB/wIKARoB/xwAAwgB/wMAAf8DsAH/
+        A7EB/wOwAf8DrQH/A6sB/wOgAf8DCAH/FAABBgIAAf8BKgEYAQAB/wFOAUABGgH/AZ8BggEhAf8B2wHH
+        AZAB/wHmAdcBtAH/AeYB1wG0Af8BtAGuAY8B/wFgAVYBMwH/AWQBVgEyAf8BQwE3ARAB/wEKAgAB/wMA
+        Af8EAAOXAf8DkQH/A4kB/wOEAf8DgQH/A1oB/wNTAf8DRgH/AzkB/wEAArkB/wOgAf8DYwH/A2MB/wEA
+        AoEB/wEAAjkB/wQAAYEBiAGQAf8BkAGoAbAB/wGQAagBsAH/AQABkAHQAf8BAAGQAdAB/wEAAZAB0AH/
+        AQABkAHAAf8BCgGIAcAB/wEKAYEBsAH/AQoBgQGwAf8BGgGBAaAB/wEaAYEBkAH/ARoBQgFaAf8DkgH/
+        FAADCAH/A6oB/wMAAf8D1wH/A9kB/wPcAf8D3wH/A+EB/wPPAf8DCAH/EwAB/wMAAf8DAAH/AwAB/wMA
+        Af8DAAH/AwAB/wMAAf8DAAH/AQcCAAH/AVcBTgE6Af8BXQFMAR8B/wEVAQYBAAH/AwAB/wQAA5cB/wOx
+        Af8DsQH/A4QB/wOEAf8DhAH/A4EB/wOEAf8DgQH/AQACuQH/AQAD/wEAA/8BAAP/AQACgQH/AQACOQH/
+        BAABgQGIAZAB/wFaAdgC/wGQAagBsAH/AYEB4AL/AVoB0AL/AUoByAL/AUoByAL/AToBwAHwAf8BKgGw
+        AfAB/wEqAagB8AH/ARoBoAHgAf8BCgGQAdAB/wEaAWIBgQH/AVMBWwFhAf8QAAMIAf8DqgH/A8EB/wMA
+        Af8D1QH/A9kB/wPdAf8D4QH/A+UB/wPSAf8DCAH/GAADTQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/
+        BAABIQEZARMB/wFZAVMBSQH/AQQCAAH/CAADngH/A74B/wOxAf8DywH/A74B/wO4Af8DuAH/A6sB/wOk
+        Af8BAAK5Af8BAAKWAf8BAAKWAf8BAAKBAf8BAAKBAf8BAAJcAf8EAAGBAZABoAH/AVoB2AL/AZABqAGw
+        Af8BkAHAAdAB/wGBAdgC/wFaAdAC/wFaAdAC/wFKAcgC/wFKAcAC/wE6AbgB8AH/ASoBsAHwAf8BKgGo
+        AfAB/wEKAYgB0AH/ARoBQgFaAf8DkgH/DAADCAH/AwAB/wMAAf8DqgH/A84B/wPTAf8D2AH/A9wB/wPg
+        Af8D0QH/AwgB/xQAA00B/wPYAf8DTQH/A8AB/wPOAf8D0wH/A9gB/wMAAf8EAAEbAREBEAH/A8AB/wOB
+        Af8IAAOkAf8DvgH/A7EB/wO+Af8DxAH/A74B/wO+Af8DuAH/A7gB/wEAArkB/wEAAjkB/wOxAf8DAwH/
+        AQACgQH/AQACOQH/BAABgQGQAaAB/wGBAdgB8AH/AVoB2AL/AZABqAGwAf8BgQHgAv8BgQHQAv8BWgHY
+        Av8BWgHQAv8BWgHQAv8BSgHIAv8BOgHAAfAB/wE6AbgB8AH/ASoBsAHwAf8BGgFiAYEB/wOBAf8MAAMI
+        Af8D1QH/A8EB/wPEAf8DyQH/A88B/wPTAf8D1wH/A9sB/wPNAf8DCAH/FAADTQH/A00B/wNNAf8DwAH/
+        A8kB/wPPAf8D0wH/AwAB/wQAA00B/wNNAf8BBwIAAf8IAAOkAf8DxAH/A74B/wOxAf8DywH/A8QB/wO+
+        Af8DvgH/A74B/wEAArkB/wEAAjkB/wMAAf8DAwH/AQAClgH/AQACOQH/BAABgQGYAaAB/wGQAeAB8AH/
+        AVoB2AL/AZABqAGwAf8BkAG4AcAB/wGBAdgC/wFaAdgC/wFaAdgC/wFaAdgC/wFaAdAC/wFKAdAC/wFK
+        AcgC/wE6AbgB8AH/ASoBoAHgAf8BQwFhAYEB/wOSAf8IAAMIAf8D0wH/A7wB/wPAAf8DxQH/A8kB/wPN
+        Af8D0QH/A9UB/wPMAf8DCAH/FAADTQH/A9MB/wO8Af8DwAH/A8UB/wPJAf8DzQH/AwAB/wQAARUBCwEM
+        Af8DwAH/A4EB/wgAA6QB/wPLAf8DvgH/A7EB/wO4Af8DxAH/A74B/wO+Af8DvgH/A74B/wEAArkB/wEA
+        ArkB/wEAApYB/wEAAjkB/wEyAVABYAH/A6QB/wGBAZgBoAH/AZAB4AHwAf8BoAHoAv8BWgHYAv8BkAGo
+        AbAB/wGBAeAC/wGBAeAC/wGBAeAC/wGBAeAC/wGBAeAC/wGBAeAC/wGBAeAC/wGBAdgC/wGBAdgC/wFK
+        AagB0AH/A4EB/wgAAwgB/wPRAf8DtwH/A7sB/wPAAf8DxAH/A8gB/wPMAf8D0QH/A8kB/wMIAf8UAANN
+        Af8D0QH/A7cB/wO7Af8DwAH/A8QB/wPIAf8DAAH/BAADTQH/A00B/wMAAf8IAAOkAf8DywH/A9gB/wO+
+        Af8DsQH/A8sB/wPLAf8DywH/A8sB/wPLAf8DywH/A8sB/wPEAf8DxAH/A6QB/wOJAf8BkAKgAf8BoAHo
+        AfAB/wGgAegC/wGgAegC/wGQAagBsAH/AZABqAGwAf8BkAGoAbAB/wGQAagBsAH/AYEBoAGwAf8BgQGg
+        AbAB/wGBAZgBoAH/AYEBmAGgAf8BgQGQAaAB/wGBAZABoAH/AYEBiAGQAf8BgQGIAZAB/wgAAwgB/wPR
+        Af8DswH/A7UB/wO5Af8DvwH/A8MB/wPHAf8DywH/A8YB/wMIAf8UAANNAf8D0QH/A7MB/wO1Af8DuQH/
+        A78B/wPDAf8DAAH/BAABFQELAQwB/wPAAf8DgQH/CAADqwH/A9IB/wPYAf8D2AH/A7EB/wOxAf8DsQH/
+        A7EB/wOrAf8DqwH/A6QB/wOkAf8DpAH/A6QB/wOeAf8DlwH/AZABoAGwAf8BoAHoAfAB/wGgAfAC/wGg
+        AegC/wGgAegC/wGBAdgC/wFaAdgC/wFaAdgC/wFaAdgC/wFaAdgC/wFaAdgC/wFaAdgC/wGBAYgBkAH/
+        FAADCAH/A9EB/wOtAf8DsQH/A7QB/wO5Af8DvAH/A8EB/wPFAf8DxAH/AwgB/xQAA00B/wPRAf8DrQH/
+        A7EB/wO0Af8DuQH/A7wB/wMAAf8EAANNAf8DTQH/AwAB/wgAA7EB/wPSAf8D2AH/A9gB/wPYAf8DywH/
+        A74B/wO+Af8DvgH/A74B/wO+Af8DvgH/A5cB/wwAAZABoAGwAf8BoALwAf8BsALwAf8BoAHwAv8BoAHo
+        Av8BoAHoAv8BgQHYAv8BkAKgAf8BgQGYAaAB/wGBAZgBoAH/AYEBkAGgAf8BgQKQAf8BgQGIAZAB/xQA
+        AwgB/wPKAf8DqAH/A6wB/wOwAf8DtAH/A7gB/wO8Af8DwAH/A8EB/wMIAf8UAANNAf8DygH/A6gB/wOs
+        Af8DsAH/A7QB/wO4Af8DAAH/BAABFQELAQwB/wPAAf8DgQH/CAADsQH/A9IB/wPYAf8D2AH/A9gB/wPY
+        Af8DxAH/A6sB/wOkAf8DpAH/A6QB/wOeAf8DlwH/DAABkAGoAbAB/wGgAdAB4AH/AbAC8AH/AbAC8AH/
+        AaAB8AL/AaAB6AL/AZABoAGwAf8DkgH/KAADCAH/A8sB/wOpAf8DrAH/A7AB/wO0Af8DtwH/A7sB/wO9
+        Af8DwAH/AwgB/xQAA00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNNAf8EAAEKAQIBAwH/AYUBgwFb
+        Af8BDAEDAQIB/wgAA7EB/wPLAf8D2AH/A9gB/wPYAf8D2AH/A7EB/wOkAf8kAAGQAagBsAH/AZABqAGw
+        Af8BkAGoAbAB/wGQAagBsAH/AZABqAGwAf8DkgH/LAADCAH/A/IB/wPVAf8D0gH/A9EB/wPOAf8DywH/
+        A8oB/wPHAf8DxgH/AwgB/xMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BgQFh
+        AVIB/wGdAZEBQQH/ASYBFgEAAf8DAAH/CAADsQH/A7EB/wOxAf8DsQH/A7EB/wOkAf9sAAMIAf8DCAH/
+        AwgB/wMIAf8DCAH/AwgB/wMIAf8DCAH/AwgB/wMIAf8DCAH/FwAB/wE+ASIBAAH/AZ8BggEhAf8B2wHH
+        AZAB/wHmAdcBtAH/AeYB1wG0Af8B5gHXAbQB/wHmAdcBtAH/AeAB1QGxAf8BtAGuAY8B/wFLAT8BHAH/
+        AQ4BAQEAAf8DAAH/0wAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf9IAAFC
+        AU0BPgcAAT4DAAEoAwABQAMAASADAAEBAQABAQYAAQEWAAP/AQAB/wGBAf8BwAH4AR8CAAH4AQEB4AEA
+        AeABBwIAAfABAQHAAQABwAEDAgAB4AEBAYABAAGAAQECAAHAAQEB4AEAAYMBwQIAAcABAQHAAQABBwHg
+        AgABwAEBAcABAAEPAfACAAHAAQMBwAEhAQ8B8AIAAcABBwHAASMBDwHwAgABwAEHAcABIwEPAfACAAHA
+        AQcBwAEjAQcB4AIAAcABBwHAASMBgwHBAgABwAEHAcABIwGAAQECAAHAAQcBgAEBAcABAwIAAcABBwHA
+        AQEB4AEHAgAC/wHgAQMB+AEfAgAF/wHDAf8BgQL/AfgBBwHgAQEB/wGBAQABBwHwAQcBwAEBAQABAQEA
+        AQMB4AEHAYABAQEAAQEBAAEDAcABBwHgASMBAAEBAQABAQHAAQcBwAEjAQABAQEAAQEBwAEHAcABIwEA
+        AQECAAHAAQcBwAEjBAABwAEHAcABIwQAAcABBwHAASMDAAEHAcABBwHAASMBAAEHAQABBwHAAQcBwAEj
+        AQABBwEAAf8BwAEHAcABIwEAAf8BgQH/AcABBwGAAQEBgQP/AcABBwHAAQEG/wHgAQMC/ws=
 </value>
   </data>
-  <metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Core/Controls/SidedefPartLightControl.Designer.cs b/Source/Core/Controls/SidedefPartLightControl.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3e86ddfffff134e9597ffa71dcf343788effdea3
--- /dev/null
+++ b/Source/Core/Controls/SidedefPartLightControl.Designer.cs
@@ -0,0 +1,111 @@
+namespace CodeImp.DoomBuilder.Controls
+{
+	partial class SidedefPartLightControl
+	{
+		/// <summary> 
+		/// Required designer variable.
+		/// </summary>
+		private System.ComponentModel.IContainer components = null;
+
+		/// <summary> 
+		/// Clean up any resources being used.
+		/// </summary>
+		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+		protected override void Dispose(bool disposing)
+		{
+			if (disposing && (components != null))
+			{
+				components.Dispose();
+			}
+			base.Dispose(disposing);
+		}
+
+		#region Component Designer generated code
+
+		/// <summary> 
+		/// Required method for Designer support - do not modify 
+		/// the contents of this method with the code editor.
+		/// </summary>
+		private void InitializeComponent()
+		{
+			this.lbLight = new System.Windows.Forms.Label();
+			this.cbAbsolute = new System.Windows.Forms.CheckBox();
+			this.reset = new System.Windows.Forms.Button();
+			this.light = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.SuspendLayout();
+			// 
+			// lbLight
+			// 
+			this.lbLight.Location = new System.Drawing.Point(3, 6);
+			this.lbLight.Name = "lbLight";
+			this.lbLight.Size = new System.Drawing.Size(92, 14);
+			this.lbLight.TabIndex = 29;
+			this.lbLight.Tag = "";
+			this.lbLight.Text = "Brightness:";
+			this.lbLight.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			// 
+			// cbAbsolute
+			// 
+			this.cbAbsolute.AutoSize = true;
+			this.cbAbsolute.Location = new System.Drawing.Point(167, 7);
+			this.cbAbsolute.Name = "cbAbsolute";
+			this.cbAbsolute.Size = new System.Drawing.Size(67, 17);
+			this.cbAbsolute.TabIndex = 31;
+			this.cbAbsolute.Tag = "lightabsolute";
+			this.cbAbsolute.Text = "Absolute";
+			this.cbAbsolute.UseVisualStyleBackColor = true;
+			this.cbAbsolute.CheckedChanged += new System.EventHandler(this.cbAbsolute_CheckedChanged);
+			// 
+			// reset
+			// 
+			this.reset.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
+			this.reset.Location = new System.Drawing.Point(236, 3);
+			this.reset.Name = "reset";
+			this.reset.Size = new System.Drawing.Size(23, 23);
+			this.reset.TabIndex = 32;
+			this.reset.UseVisualStyleBackColor = true;
+			this.reset.Click += new System.EventHandler(this.reset_Click);
+			// 
+			// light
+			// 
+			this.light.AllowDecimal = false;
+			this.light.AllowExpressions = false;
+			this.light.AllowNegative = true;
+			this.light.AllowRelative = true;
+			this.light.ButtonStep = 16;
+			this.light.ButtonStepBig = 32F;
+			this.light.ButtonStepFloat = 1F;
+			this.light.ButtonStepSmall = 1F;
+			this.light.ButtonStepsUseModifierKeys = true;
+			this.light.ButtonStepsWrapAround = false;
+			this.light.Location = new System.Drawing.Point(99, 2);
+			this.light.Name = "light";
+			this.light.Size = new System.Drawing.Size(62, 24);
+			this.light.StepValues = null;
+			this.light.TabIndex = 30;
+			this.light.Tag = "";
+			this.light.WhenTextChanged += new System.EventHandler(this.light_WhenTextChanged);
+			// 
+			// SidedefPartLightControl
+			// 
+			this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+			this.Controls.Add(this.reset);
+			this.Controls.Add(this.light);
+			this.Controls.Add(this.lbLight);
+			this.Controls.Add(this.cbAbsolute);
+			this.Name = "SidedefPartLightControl";
+			this.Size = new System.Drawing.Size(262, 29);
+			this.ResumeLayout(false);
+			this.PerformLayout();
+
+		}
+
+		#endregion
+
+		private System.Windows.Forms.Button reset;
+		private ButtonsNumericTextbox light;
+		private System.Windows.Forms.Label lbLight;
+		private System.Windows.Forms.CheckBox cbAbsolute;
+	}
+}
diff --git a/Source/Core/Controls/SidedefPartLightControl.cs b/Source/Core/Controls/SidedefPartLightControl.cs
new file mode 100644
index 0000000000000000000000000000000000000000..44d40e37b81edfd30a8ab4444656dc2106fca8b0
--- /dev/null
+++ b/Source/Core/Controls/SidedefPartLightControl.cs
@@ -0,0 +1,250 @@
+#region ================== Copyright (c) 2020 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+using CodeImp.DoomBuilder.Types;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.VisualModes;
+using CodeImp.DoomBuilder.Windows;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.Controls
+{
+	public partial class SidedefPartLightControl : UserControl
+	{
+		#region ================== Variables
+
+		private string fieldname;
+		private string fieldabsolutename;
+		private List<Sidedef> sidedefs;
+		private Dictionary<Sidedef, int> originallight;
+		private Dictionary<Sidedef, bool> originalabsolute;
+		private bool preventchanges;
+
+		#endregion
+
+		#region ================== Constructors
+
+		public SidedefPartLightControl()
+		{
+			InitializeComponent();
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Sets up the control for the specified geometry type.
+		/// </summary>
+		/// <param name="geometrytype">Geometry type to set up the control with</param>
+		public void Setup(VisualGeometryType geometrytype)
+		{
+			fieldname = string.Empty;
+			fieldabsolutename = string.Empty;
+			sidedefs = new List<Sidedef>();
+			originallight = new Dictionary<Sidedef, int>();
+			originalabsolute = new Dictionary<Sidedef, bool>();
+
+			// Do not trigger the events that usually fire when textboxes or checkboxes are changed
+			preventchanges = true;
+
+			switch (geometrytype)
+			{
+				case VisualGeometryType.WALL_UPPER:
+					fieldname = "light_top";
+					fieldabsolutename = "lightabsolute_top";
+					lbLight.Text = "Upper brightness:";
+					break;
+				case VisualGeometryType.WALL_MIDDLE:
+				case VisualGeometryType.WALL_MIDDLE_3D:
+					fieldname = "light_mid";
+					fieldabsolutename = "lightabsolute_mid";
+					lbLight.Text = "Middle brightness:";
+					break;
+				case VisualGeometryType.WALL_LOWER:
+					fieldname = "light_bottom";
+					fieldabsolutename = "lightabsolute_bottom";
+					lbLight.Text = "Lower brightness:";
+					break;
+				default:
+					throw new NotImplementedException("Unsupported geometry type: " + Enum.GetName(typeof(VisualGeometryType), geometrytype));
+			}
+		}
+
+		/// <summary>
+		/// Sets the light value and absolute stats of the control.
+		/// </summary>
+		/// <param name="sidedef">Sidedef to use the values of</param>
+		/// <param name="first">If this is the first sidedef</param>
+		public void SetValues(Sidedef sidedef, bool first)
+		{
+			if (!sidedefs.Contains(sidedef))
+				sidedefs.Add(sidedef);
+
+			originallight[sidedef] = sidedef.Fields.GetValue(fieldname, 0);
+			originalabsolute[sidedef] = sidedef.Fields.GetValue(fieldabsolutename, false);
+
+			string lightvalue = originallight[sidedef].ToString();
+			bool isabsolute = sidedef.Fields.GetValue(fieldabsolutename, false);
+
+			if (first)
+			{
+				light.Text = lightvalue;
+				cbAbsolute.Checked = isabsolute;
+			}
+			else
+			{
+				if (light.Text != lightvalue)
+					light.Text = string.Empty;
+
+				if (cbAbsolute.Checked != isabsolute)
+				{
+					cbAbsolute.ThreeState = true;
+					cbAbsolute.CheckState = CheckState.Indeterminate;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Finalize the control's setup, setting visibility of child controls and enable state
+		/// </summary>
+		public void FinalizeSetup()
+		{
+			reset.Visible = (cbAbsolute.CheckState != CheckState.Unchecked || light.GetResult(0) != 0);
+
+			if (!General.Map.Config.DistinctSidedefPartBrightness)
+			{
+				lbLight.Enabled = false;
+				light.Enabled = false;
+				cbAbsolute.Enabled = false;
+				reset.Enabled = false;
+			}
+
+			preventchanges = false;
+		}
+
+		#endregion
+
+		#region ================== Events
+
+		private void reset_Click(object sender, EventArgs e)
+		{
+			light.Text = "0";
+			cbAbsolute.Checked = false;
+			reset.Visible = false;
+		}
+
+		private void light_WhenTextChanged(object sender, EventArgs e)
+		{
+			if (preventchanges)
+				return;
+
+			((LinedefEditFormUDMF)ParentForm).MakeUndo();
+
+			// Reset the increment step for +++/---
+			light.ResetIncrementStep();
+
+			if (string.IsNullOrEmpty(light.Text))
+			{
+				// Text is empty, use each sidedef's original light value
+				foreach (Sidedef sd in sidedefs)
+				{
+					if (sd == null || sd.IsDisposed)
+						continue;
+
+					UniFields.SetInteger(sd.Fields, fieldname, originallight[sd]);
+				}
+			}
+			else
+			{
+				foreach (Sidedef sd in sidedefs)
+				{
+					if (sd == null || sd.IsDisposed)
+						continue;
+
+					bool absolute = false;
+
+					switch (cbAbsolute.CheckState)
+					{
+						case CheckState.Checked:
+							absolute = true;
+							break;
+						case CheckState.Indeterminate:
+							absolute = sd.Fields.GetValue(fieldabsolutename, false);
+							break;
+					}
+
+					int value = General.Clamp(light.GetResult(originallight[sd]), absolute ? 0 : -255, 255);
+					UniFields.SetInteger(sd.Fields, fieldname, value);
+				}
+			}
+
+			reset.Visible = (cbAbsolute.CheckState != CheckState.Unchecked || light.Text != "0");
+			General.Map.IsChanged = true;
+
+			((LinedefEditFormUDMF)ParentForm).ValuesChangedExternal();
+		}
+
+		private void cbAbsolute_CheckedChanged(object sender, EventArgs e)
+		{
+			if (preventchanges)
+				return;
+
+			((LinedefEditFormUDMF)ParentForm).MakeUndo();
+
+			if (cbAbsolute.Checked)
+			{
+				foreach(Sidedef sd in sidedefs)
+				{
+					sd.Fields[fieldabsolutename] = new UniValue(UniversalType.Boolean, true);
+				}
+			}
+			else if(cbAbsolute.CheckState == CheckState.Indeterminate)
+			{
+				foreach(Sidedef sd in sidedefs)
+				{
+					if (originalabsolute[sd])
+						sd.Fields[fieldabsolutename] = new UniValue(UniversalType.Boolean, true);
+					else if (sd.Fields.ContainsKey(fieldabsolutename))
+						sd.Fields.Remove(fieldabsolutename);
+				}
+			}
+			else
+			{
+				foreach (Sidedef sd in sidedefs)
+					if (sd.Fields.ContainsKey(fieldabsolutename))
+						sd.Fields.Remove(fieldabsolutename);
+			}
+
+			((LinedefEditFormUDMF)ParentForm).ValuesChangedExternal();
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Core/Controls/SidedefPartLightControl.resx b/Source/Core/Controls/SidedefPartLightControl.resx
new file mode 100644
index 0000000000000000000000000000000000000000..1af7de150c99c12dd67a509fe57c10d63e4eeb04
--- /dev/null
+++ b/Source/Core/Controls/SidedefPartLightControl.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/Source/Core/Controls/ThingInfoPanel.cs b/Source/Core/Controls/ThingInfoPanel.cs
old mode 100755
new mode 100644
diff --git a/Source/Core/Controls/ToastControl.Designer.cs b/Source/Core/Controls/ToastControl.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7b2a094f4010b4b27efdada6e720043e7218e258
--- /dev/null
+++ b/Source/Core/Controls/ToastControl.Designer.cs
@@ -0,0 +1,104 @@
+
+namespace CodeImp.DoomBuilder.Controls
+{
+	partial class ToastControl
+	{
+		/// <summary> 
+		/// Required designer variable.
+		/// </summary>
+		private System.ComponentModel.IContainer components = null;
+
+		/// <summary> 
+		/// Clean up any resources being used.
+		/// </summary>
+		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+		protected override void Dispose(bool disposing)
+		{
+			if (disposing && (components != null))
+			{
+				components.Dispose();
+			}
+			base.Dispose(disposing);
+		}
+
+		#region Component Designer generated code
+
+		/// <summary> 
+		/// Required method for Designer support - do not modify 
+		/// the contents of this method with the code editor.
+		/// </summary>
+		private void InitializeComponent()
+		{
+			this.lbText = new System.Windows.Forms.Label();
+			this.icon = new System.Windows.Forms.Panel();
+			this.lbTitle = new System.Windows.Forms.Label();
+			this.btnClose = new System.Windows.Forms.Button();
+			this.SuspendLayout();
+			// 
+			// lbText
+			// 
+			this.lbText.AutoSize = true;
+			this.lbText.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.lbText.Location = new System.Drawing.Point(48, 28);
+			this.lbText.MaximumSize = new System.Drawing.Size(200, 0);
+			this.lbText.Name = "lbText";
+			this.lbText.Size = new System.Drawing.Size(46, 18);
+			this.lbText.TabIndex = 0;
+			this.lbText.Text = "label1";
+			// 
+			// icon
+			// 
+			this.icon.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center;
+			this.icon.Location = new System.Drawing.Point(10, 10);
+			this.icon.Name = "icon";
+			this.icon.Size = new System.Drawing.Size(32, 32);
+			this.icon.TabIndex = 1;
+			// 
+			// lbTitle
+			// 
+			this.lbTitle.AutoSize = true;
+			this.lbTitle.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.lbTitle.Location = new System.Drawing.Point(48, 10);
+			this.lbTitle.Name = "lbTitle";
+			this.lbTitle.Size = new System.Drawing.Size(40, 18);
+			this.lbTitle.TabIndex = 2;
+			this.lbTitle.Text = "Title";
+			// 
+			// btnClose
+			// 
+			this.btnClose.BackgroundImage = global::CodeImp.DoomBuilder.Properties.Resources.Close;
+			this.btnClose.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
+			this.btnClose.Location = new System.Drawing.Point(385, 3);
+			this.btnClose.Name = "btnClose";
+			this.btnClose.Size = new System.Drawing.Size(16, 16);
+			this.btnClose.TabIndex = 3;
+			this.btnClose.UseVisualStyleBackColor = true;
+			this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
+			// 
+			// ToastControl
+			// 
+			this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+			this.BackColor = System.Drawing.SystemColors.Control;
+			this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+			this.Controls.Add(this.btnClose);
+			this.Controls.Add(this.lbTitle);
+			this.Controls.Add(this.icon);
+			this.Controls.Add(this.lbText);
+			this.DoubleBuffered = true;
+			this.Margin = new System.Windows.Forms.Padding(6);
+			this.Name = "ToastControl";
+			this.Size = new System.Drawing.Size(404, 49);
+			this.ResumeLayout(false);
+			this.PerformLayout();
+
+		}
+
+		#endregion
+
+		private System.Windows.Forms.Label lbText;
+		private System.Windows.Forms.Panel icon;
+		private System.Windows.Forms.Label lbTitle;
+		private System.Windows.Forms.Button btnClose;
+	}
+}
diff --git a/Source/Core/Controls/ToastControl.cs b/Source/Core/Controls/ToastControl.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f613db32a305717900c2c33e8ea46bb6fc7c0e77
--- /dev/null
+++ b/Source/Core/Controls/ToastControl.cs
@@ -0,0 +1,122 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.Controls
+{
+	internal partial class ToastControl : UserControl
+	{
+		#region ================== Variables
+
+		private long startime;
+		private long lifetime;
+		private bool pausedecay;
+		private bool remove;
+
+		#endregion
+
+		#region ================== Constructors
+
+		public ToastControl(ToastType type, string title, string text, long lifetime = 3000)
+		{
+			InitializeComponent();
+
+			this.lifetime = lifetime;
+			startime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+			// Set icon
+			if(type == ToastType.INFO)
+				icon.BackgroundImage = SystemIcons.Information.ToBitmap();
+			else if(type == ToastType.WARNING)
+				icon.BackgroundImage = SystemIcons.Warning.ToBitmap();
+			else if(type == ToastType.ERROR)
+				icon.BackgroundImage = SystemIcons.Error.ToBitmap();
+
+			lbTitle.Text = title;
+			lbText.Text = text;
+
+			// The text label is auto-size, but we need to programatically set a max width so that longer texts are
+			// automatically broken into multiple lines
+			lbText.MaximumSize = new Size(Width - lbText.Location.X - Margin.Right, lbText.MaximumSize.Height);
+
+			// Resize the height of the control if the text doesn't fit vertically
+			if (lbText.Location.Y + lbText.Height + Margin.Bottom > Height)
+				Height = lbText.Location.Y + lbText.Height + lbTitle.Location.Y + Margin.Bottom;
+
+			pausedecay = false;
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Checks if the toast is decaying, i.e. the cursor is currently not inside the control.
+		/// </summary>
+		public void CheckDecay()
+		{
+			if (ClientRectangle.Contains(PointToClient(Cursor.Position)))
+			{
+				pausedecay = true;
+			}
+			else if(pausedecay)
+			{
+				pausedecay = false;
+
+				// Reset the start time, so that the control will only die "lifetime" ms after the cursor left the control
+				startime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+			}
+		}
+
+		/// <summary>
+		/// Checks if the control is still "alive" (has not reached its lifetime).
+		/// </summary>
+		/// <returns>true if it's alive, false if it isn't</returns>
+		public bool IsAlive()
+		{
+			if (remove || (!pausedecay && DateTimeOffset.Now.ToUnixTimeMilliseconds() - startime > lifetime))
+				return false;
+
+			return true;
+		}
+
+		/// <summary>
+		/// Sets the toast to be removed.
+		/// </summary>
+		/// <param name="sender">The sender</param>
+		/// <param name="e">The event arguments</param>
+		private void btnClose_Click(object sender, EventArgs e)
+		{
+			remove = true;
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Core/Controls/ToastControl.resx b/Source/Core/Controls/ToastControl.resx
new file mode 100644
index 0000000000000000000000000000000000000000..1af7de150c99c12dd67a509fe57c10d63e4eeb04
--- /dev/null
+++ b/Source/Core/Controls/ToastControl.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>
\ No newline at end of file
diff --git a/Source/Core/Controls/ToolStripActionButton.cs b/Source/Core/Controls/ToolStripActionButton.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5f19fa6b87abbf5e5175c163e16fad233bd07191
--- /dev/null
+++ b/Source/Core/Controls/ToolStripActionButton.cs
@@ -0,0 +1,134 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+
+#region ================== Namespaces
+
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Reflection;
+using System.Windows.Forms;
+using System.Windows.Forms.Design;
+using CodeImp.DoomBuilder.Editing;
+using CodeImp.DoomBuilder.Plugins;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.Controls
+{
+	[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip)]
+	public class ToolStripActionButton : ToolStripButton
+	{
+		#region ================== Variables
+
+		private string baseToolTipText;
+		private Assembly parentAssembly;
+
+		#endregion
+
+		#region ================== Constructors
+
+		public ToolStripActionButton() : base()
+		{
+			parentAssembly = Assembly.GetCallingAssembly();
+		}
+
+		public ToolStripActionButton(string text) : base(text)
+		{
+			parentAssembly = Assembly.GetCallingAssembly();
+		}
+
+		public ToolStripActionButton(string text, Image image) : base(text, image)
+		{
+			parentAssembly = Assembly.GetCallingAssembly();
+		}
+
+		public ToolStripActionButton(string text, Image image, EventHandler onClick) : base(text, image, onClick)
+		{
+			parentAssembly = Assembly.GetCallingAssembly();
+		}
+
+		public ToolStripActionButton(string text, Image image, EventHandler onClick, string name) : base(text, image, onClick, name)
+		{
+			parentAssembly = Assembly.GetCallingAssembly();
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Updates the tooltip of the button with its action shortcut key description. This has to be called after the button was added to the MainForm unless the tag contains the full action name (including the assembly).
+		/// </summary>
+		public void UpdateToolTip()
+		{
+			string actionName = string.Empty;
+
+			// The shotcut key can change at runtime, so we need to remember the original tooltip without the shotcut key
+			if (string.IsNullOrWhiteSpace(baseToolTipText))
+				baseToolTipText = ToolTipText;
+
+			// Try to figure out what's the real action name is. The action name can either be directly stored in the Tag, or
+			// (in case it's a button for edit modes) can be extracted from the EditModeInfo, which is also stored in the Tag.
+			if (Tag is string)
+			{
+				actionName = (string)Tag;
+
+				// If it's not a known action we might be missing the plugin name as a prefix, so try to add it. This only works
+				// if this method is called directly from the plugin!
+				if (!General.Actions.Exists(actionName))
+				{
+					Plugin plugin = General.Plugins.FindPluginByAssembly(parentAssembly);
+
+					if (plugin != null)
+						actionName = plugin.Name.ToLowerInvariant() + "_" + actionName;
+				}
+			}
+			else if (Tag is EditModeInfo modeInfo) // It's a mode button
+			{
+				actionName = modeInfo.SwitchAction.ActionName;
+
+				// If it's not a known action we might be missing the plugin name as a prefix, so add it from the mode's plugin
+				if (!General.Actions.Exists(actionName))
+					actionName = General.Plugins.FindPluginByAssembly(modeInfo.Plugin.Assembly).Name.ToLowerInvariant() + "_" + actionName;
+			}
+
+			// Only try to add the shortcut key if the action is valid
+			if (!string.IsNullOrWhiteSpace(actionName) && General.Actions.Exists(actionName))
+			{
+				Actions.Action action = General.Actions.GetActionByName(actionName);
+				string shortcutKey = Actions.Action.GetShortcutKeyDesc(action.ShortcutKey);
+
+				if (string.IsNullOrWhiteSpace(shortcutKey))
+					ToolTipText = baseToolTipText;
+				else
+					ToolTipText = $"{baseToolTipText} ({shortcutKey})";
+			}
+			else
+				ToolTipText = baseToolTipText;
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Core/Data/DataLocation.cs b/Source/Core/Data/DataLocation.cs
index 299259d0d057f96c30a48cdbf7f033b706b96e60..632c5d64732eb2f693f3afc9ce84e5408a3953bb 100755
--- a/Source/Core/Data/DataLocation.cs
+++ b/Source/Core/Data/DataLocation.cs
@@ -17,6 +17,7 @@
 #region ================== Namespaces
 
 using System;
+using System.Collections.Generic;
 using System.IO;
 
 #endregion
@@ -38,9 +39,10 @@ namespace CodeImp.DoomBuilder.Data
 		public bool option1;
 		public bool option2;
 		public bool notfortesting;
+		public List<string> requiredarchives;
 		
 		// Constructor
-		public DataLocation(int type, string location, bool option1, bool option2, bool notfortesting)
+		public DataLocation(int type, string location, bool option1, bool option2, bool notfortesting, List<string> requiredarchives)
 		{
 			// Initialize
 			this.type = type;
@@ -50,10 +52,11 @@ namespace CodeImp.DoomBuilder.Data
 			this.option2 = option2;
 			this.notfortesting = notfortesting;
 			this.name = string.Empty; //mxd
+			this.requiredarchives = requiredarchives;
 		}
 
 		//mxd. Constructor for WADs inside of PK3s
-		internal DataLocation(int type, string location, string initiallocation, bool option1, bool option2, bool notfortesting)
+		internal DataLocation(int type, string location, string initiallocation, bool option1, bool option2, bool notfortesting, List<string> requiredarchives)
 		{
 			// Initialize
 			this.type = type;
@@ -62,7 +65,8 @@ namespace CodeImp.DoomBuilder.Data
 			this.option1 = option1;
 			this.option2 = option2;
 			this.notfortesting = notfortesting;
-			this.name = string.Empty; 
+			this.name = string.Empty;
+			this.requiredarchives = requiredarchives;
 		}
 
 		// This displays the struct as string
diff --git a/Source/Core/Data/DataLocationList.cs b/Source/Core/Data/DataLocationList.cs
index 437f82824fb6ca9a6f1030edeeee0755aec04630..956efe6a17f2928e442c9e927ae5f336f89316de 100755
--- a/Source/Core/Data/DataLocationList.cs
+++ b/Source/Core/Data/DataLocationList.cs
@@ -20,6 +20,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Globalization;
+using System.Linq;
 using CodeImp.DoomBuilder.IO;
 
 #endregion
@@ -61,6 +62,9 @@ namespace CodeImp.DoomBuilder.Data
 					if(rlinfo.Contains("option2") && (rlinfo["option2"] is int)) res.option2 = General.Int2Bool((int)rlinfo["option2"]);
 					if(rlinfo.Contains("notfortesting") && (rlinfo["notfortesting"] is int)) res.notfortesting = General.Int2Bool((int)rlinfo["notfortesting"]);
 
+					if (rlinfo.Contains("requiredarchives") && rlinfo["requiredarchives"] is string) res.requiredarchives = ((string)rlinfo["requiredarchives"]).Split(',').ToList();
+					else res.requiredarchives = null;
+
 					// Add resource
 					Add(res);
 				}
@@ -101,6 +105,8 @@ namespace CodeImp.DoomBuilder.Data
 				rlinfo.Add("option2", General.Bool2Int(this[i].option2));
 				rlinfo.Add("notfortesting", General.Bool2Int(this[i].notfortesting));
 
+				if (this[i].requiredarchives != null) rlinfo.Add("requiredarchives", string.Join(",", this[i].requiredarchives.ToArray()));
+
 				// Add structure
 				resinfo.Add("resource" + i.ToString(CultureInfo.InvariantCulture), rlinfo);
 			}
diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs
index eece18033d75acc6a4d7b7f80f0c96e4bb464c4c..1d04b97ec0721e88a5394958a5a37eb317dbbf6a 100755
--- a/Source/Core/Data/DataManager.cs
+++ b/Source/Core/Data/DataManager.cs
@@ -39,6 +39,7 @@ using CodeImp.DoomBuilder.ZDoom;
 using Matrix = CodeImp.DoomBuilder.Rendering.Matrix;
 using CodeImp.DoomBuilder.Controls;
 using CodeImp.DoomBuilder.Dehacked;
+using System.Diagnostics;
 
 #endregion
 
@@ -80,6 +81,8 @@ namespace CodeImp.DoomBuilder.Data
 		private List<MatchingTextureSet> texturesets;
 		private List<ResourceTextureSet> resourcetextures;
 		private AllTextureSet alltextures;
+		private AllTextureSet walltextureset;
+		private AllTextureSet flattextureset;
 
 		//mxd 
 		private Dictionary<int, ModelData> modeldefentries; //Thing.Type, Model entry
@@ -192,6 +195,8 @@ namespace CodeImp.DoomBuilder.Data
 		internal ICollection<MatchingTextureSet> TextureSets { get { return texturesets; } }
 		internal ICollection<ResourceTextureSet> ResourceTextureSets { get { return resourcetextures; } }
 		internal AllTextureSet AllTextureSet { get { return alltextures; } }
+		internal AllTextureSet WallTextureSet { get { return walltextureset; } }
+		internal AllTextureSet FlatTextureSet { get { return flattextureset; } }
 		
 		public bool IsLoading
 		{
@@ -354,7 +359,7 @@ namespace CodeImp.DoomBuilder.Data
 			knowncolors = new Dictionary<string, PixelColor>(StringComparer.OrdinalIgnoreCase);
 			cvars = new CvarsCollection();
 			ambientsounds = new Dictionary<int, AmbientSoundInfo>();
-			
+
 			// Load texture sets
 			foreach(DefinedTextureSet ts in General.Map.ConfigSettings.TextureSets)
 				texturesets.Add(new MatchingTextureSet(ts));
@@ -364,6 +369,8 @@ namespace CodeImp.DoomBuilder.Data
 			
 			// Special textures sets
 			alltextures = new AllTextureSet();
+			walltextureset = new AllTextureSet();
+			flattextureset = new AllTextureSet();
 			resourcetextures = new List<ResourceTextureSet>();
 			
 			// Go for all locations
@@ -381,7 +388,7 @@ namespace CodeImp.DoomBuilder.Data
 				try
 				{
 					// Choose container type
-					switch(dl.type)
+					switch (dl.type)
 					{
 						//mxd. Load resource in read-only mode if:
 						// 1. UseResourcesInReadonlyMode map option is set.
@@ -391,10 +398,10 @@ namespace CodeImp.DoomBuilder.Data
 
 						// WAD file container
 						case DataLocation.RESOURCE_WAD:
-							c = new WADReader(dl, true);
-							if(((WADReader)c).WadFile.IsOfficialIWAD) //mxd
+							c = new WADReader(dl, General.Map.Config, true);
+							if (((WADReader)c).WadFile.IsOfficialIWAD) //mxd
 							{
-								if(!string.IsNullOrEmpty(prevofficialiwad))
+								if (!string.IsNullOrEmpty(prevofficialiwad))
 									General.ErrorLogger.Add(ErrorType.Warning, "Using more than one official IWAD as a resource is not recommended. Consider removing \"" + prevofficialiwad + "\" or \"" + dl.GetDisplayName() + "\".");
 								prevofficialiwad = dl.GetDisplayName();
 							}
@@ -402,22 +409,22 @@ namespace CodeImp.DoomBuilder.Data
 
 						// Directory container
 						case DataLocation.RESOURCE_DIRECTORY:
-							c = new DirectoryReader(dl, true);
+							c = new DirectoryReader(dl, General.Map.Config, true);
 							break;
 
 						// PK3 file container
 						case DataLocation.RESOURCE_PK3:
-							c = new PK3Reader(dl, true);
+							c = new PK3Reader(dl, General.Map.Config, true);
 							break;
 					}
 				}
-				catch(Exception e)
+				catch (Exception e)
 				{
 					// Unable to load resource
 					General.ErrorLogger.Add(ErrorType.Error, "Unable to load resources from location \"" + dl.location + "\". Please make sure the location is accessible and not in use by another program. The resources will now be loaded with this location excluded. You may reload the resources to try again.\n" + e.GetType().Name + " when creating data reader: " + e.Message);
 					General.WriteLogLine(e.StackTrace);
 					continue;
-				}	
+				}
 
 				// Add container
 				if(c != null)
@@ -426,7 +433,7 @@ namespace CodeImp.DoomBuilder.Data
 					resourcetextures.Add(c.TextureSet);
 				}
 			}
-			
+
 			// Load stuff
 			LoadX11R6RGB(); //mxd
 			LoadPalette();
@@ -511,7 +518,7 @@ namespace CodeImp.DoomBuilder.Data
 						if(t.Value.HasLongName) flatnames.Add(t.Value.ShortName);
 						flatnames.Add(t.Value.Name);
 					}
-					else if(t.Value is TEXTURESImage || t.Value is SimpleTextureImage) //mxd. Textures defined in TEXTURES or placed between TX_START and TX_END markers override "regular" flats in ZDoom
+					else if(t.Value.TextureNamespace == TextureNamespace.TEXTURE)
 					{
 						//TODO: check this!
 						flats[t.Key] = t.Value;
@@ -533,8 +540,8 @@ namespace CodeImp.DoomBuilder.Data
 						textures.Add(f.Key, f.Value);
 
 						//mxd. Add both short and long names?
-						if(f.Value.HasLongName) texturenames.Add(f.Value.ShortName);
-						texturenames.Add(f.Value.Name);
+						if(f.Value.HasLongName && !texturenames.Contains(f.Value.ShortName)) texturenames.Add(f.Value.ShortName);
+						if(!texturenames.Contains(f.Value.Name)) texturenames.Add(f.Value.Name);
 					}
 				}
 
@@ -582,6 +589,7 @@ namespace CodeImp.DoomBuilder.Data
 
 				// Add to all
 				alltextures.AddTexture(img.Value);
+				walltextureset.AddTexture(img.Value);
 			}
 			
 			// Add flat names to texture sets
@@ -593,6 +601,7 @@ namespace CodeImp.DoomBuilder.Data
 				
 				// Add to all
 				alltextures.AddFlat(img.Value);
+				flattextureset.AddFlat(img.Value);
 			}
 
 			//mxd. Create skybox texture(s)
@@ -611,6 +620,20 @@ namespace CodeImp.DoomBuilder.Data
 				gldefsentries.Count + " dynamic light definitions, " + 
 				glowingflats.Count + " glowing flat definitions, " + skyboxes.Count + " skybox definitions, " +
 				reverbs.Count + " sound environment definitions");
+
+			if (General.ErrorLogger.HasChanged)
+			{
+				string key = Actions.Action.GetShortcutKeyDesc(General.Actions.GetActionByName("builder_showerrors").ShortcutKey);
+				string keymessage = "";
+
+				if (!string.IsNullOrEmpty(key))
+					keymessage = $" ({key})";
+
+				if (General.ErrorLogger.IsWarningAdded)
+					General.ToastManager.ShowToast("resourcewarningsanderrors", ToastType.WARNING, ToastManager.TITLE_WARNING, $"There were warnings while loading the resources. Please review the messages in the Warnings and Errors window{keymessage}.", "There were warnings while loading the resources.");
+				else if(General.ErrorLogger.IsErrorAdded)
+					General.ToastManager.ShowToast("resourcewarningsanderrors", ToastType.ERROR, ToastManager.TITLE_ERROR, $"There were errors while loading the resources. Please review the messages in the Warnings and Errors window{keymessage}.", "There were errors while loading the resources.");
+			}
 		}
 		
 		// This unloads all data
@@ -658,9 +681,9 @@ namespace CodeImp.DoomBuilder.Data
 			mapinfo = null; //mxd
 		}
 		
-		#endregion
+#endregion
 		
-		#region ================== Suspend / Resume
+#region ================== Suspend / Resume
 
 		// This suspends data resources
 		internal void Suspend()
@@ -701,9 +724,9 @@ namespace CodeImp.DoomBuilder.Data
 			StartBackgroundLoader();
 		}
 		
-		#endregion
+#endregion
 
-		#region ================== Background Loading
+#region ================== Background Loading
 		
 		// This starts background loading
 		private void StartBackgroundLoader()
@@ -819,9 +842,9 @@ namespace CodeImp.DoomBuilder.Data
 			return false;
 		}
 
-        #endregion
+#endregion
 
-        #region ================== Palette
+#region ================== Palette
 
         // This loads the PLAYPAL palette
         private void LoadPalette()
@@ -860,9 +883,9 @@ namespace CodeImp.DoomBuilder.Data
 	        }
         }
 
-		#endregion
+#endregion
 
-		#region ================== Colormaps
+#region ================== Colormaps
 
 		// This loads the colormaps
 		private int LoadColormaps(Dictionary<long, ImageData> list)
@@ -906,9 +929,9 @@ namespace CodeImp.DoomBuilder.Data
 			return null;
 		}
 
-		#endregion
+#endregion
 
-		#region ================== Textures
+#region ================== Textures
 
 		// This loads the textures
 		private int LoadTextures(Dictionary<long, ImageData> list, Dictionary<long, long> nametranslation, Dictionary<string, TexturesParser> cachedparsers)
@@ -1011,11 +1034,10 @@ namespace CodeImp.DoomBuilder.Data
 		public ImageData GetTextureImage(long longname)
 		{
 			// Does this texture exist?
-			if(textures.ContainsKey(longname) 
-				&& (textures[longname] is TEXTURESImage || textures[longname] is HiResImage))
-				return textures[longname]; //TEXTURES and HiRes textures should still override regular ones...
-			if(texturenamesshorttofull.ContainsKey(longname)) return textures[texturenamesshorttofull[longname]]; //mxd
-			if(textures.ContainsKey(longname)) return textures[longname];
+			if (textures.ContainsKey(longname))
+				return textures[longname];
+			if (texturenamesshorttofull.ContainsKey(longname)) return textures[texturenamesshorttofull[longname]]; //mxd
+			if (textures.ContainsKey(longname)) return textures[longname];
 
 			// Return null image
 			return unknownimage; //mxd
@@ -1129,9 +1151,9 @@ namespace CodeImp.DoomBuilder.Data
 			return result;
 		}
 		
-		#endregion
+#endregion
 
-		#region ================== Flats
+#region ================== Flats
 
 		// This loads the flats
 		private int LoadFlats(Dictionary<long, ImageData> list, Dictionary<long, long> nametranslation, Dictionary<string, TexturesParser> cachedparsers)
@@ -1214,13 +1236,13 @@ namespace CodeImp.DoomBuilder.Data
 		public ImageData GetFlatImage(long longname)
 		{
 			// Does this flat exist?
-			if(flats.ContainsKey(longname) && (flats[longname] is TEXTURESImage || flats[longname] is HiResImage))
+			if (flats.ContainsKey(longname) && (flats[longname] is TEXTURESImage || flats[longname] is HiResImage))
 				return flats[longname]; //TEXTURES and HiRes flats should still override regular ones...
-			if(flatnamesshorttofull.ContainsKey(longname))
-                return flats[flatnamesshorttofull[longname]]; //mxd
-            if (flats.ContainsKey(longname))
-                return flats[longname];
-			
+			if (flatnamesshorttofull.ContainsKey(longname))
+				return flats[flatnamesshorttofull[longname]]; //mxd
+			if (flats.ContainsKey(longname))
+				return flats[longname];
+
 			// Return null image
 			return unknownimage; //mxd
 		}
@@ -1260,9 +1282,9 @@ namespace CodeImp.DoomBuilder.Data
 			return (flatnamesfulltoshort.ContainsKey(hash) ? flatnamesfulltoshort[hash] : hash);
 		}
 		
-		#endregion
+#endregion
 
-		#region ================== mxd. HiRes textures
+#region ================== mxd. HiRes textures
 
 		// This loads the textures
 		private int LoadHiResTextures()
@@ -1356,9 +1378,9 @@ namespace CodeImp.DoomBuilder.Data
 			return null;
 		}
 
-		#endregion
+#endregion
 
-		#region ================== Sprites
+#region ================== Sprites
 
 		// This loads the hard defined sprites (not all the lumps, we do that on a need-to-know basis, see LoadThingSprites)
 		private int LoadSprites(Dictionary<string, TexturesParser> cachedparsers)
@@ -1680,9 +1702,9 @@ namespace CodeImp.DoomBuilder.Data
 			return result;
 		}
 		
-		#endregion
+#endregion
 
-		#region ================== mxd. Voxels
+#region ================== mxd. Voxels
 
 		// This returns a specific voxel stream
 		internal Stream GetVoxelData(string pname, ref string voxellocation)
@@ -1702,9 +1724,9 @@ namespace CodeImp.DoomBuilder.Data
 			return null;
 		}
 
-		#endregion
+#endregion
 
-		#region ================== Things
+#region ================== Things
 		
         private void LoadZScriptThings()
         {
@@ -1717,11 +1739,13 @@ namespace CodeImp.DoomBuilder.Data
                 // Go for all opened containers
                 foreach (DataReader dr in containers)
                 {
-                    // Load Decorate info cumulatively (the last Decorate is added to the previous)
-                    // I'm not sure if this is the right thing to do though.
-                    currentreader = dr;
-                    IEnumerable<TextResourceData> streams = dr.GetDecorateData("ZSCRIPT");
-                    foreach (TextResourceData data in streams)
+					// Load Decorate info cumulatively (the last Decorate is added to the previous)
+					// I'm not sure if this is the right thing to do though.
+					Stopwatch t = new Stopwatch();
+					t.Start();
+
+					currentreader = dr;
+                    foreach (TextResourceData data in dr.GetZScriptData("ZSCRIPT"))
                     {
                         // Parse the data
                         data.Stream.Seek(0, SeekOrigin.Begin);
@@ -1733,11 +1757,16 @@ namespace CodeImp.DoomBuilder.Data
                             zscript.LogError();
                             break;
                         }
-                    }
+					}
+
+					DebugConsole.WriteLine(string.Format("Loading ZScript lumps from {0} took {1}ms", dr.Location.location, t.ElapsedMilliseconds));
                 }
 
+				Stopwatch t2 = new Stopwatch();
+				t2.Start();
                 zscript.Finalize();
-                if (zscript.HasError)
+				DebugConsole.WriteLine(string.Format("Finalizing ZScript lumps took {0}ms", t2.ElapsedMilliseconds));
+				if (zscript.HasError)
                     zscript.LogError();
 
                 //mxd. Add to text resources collection
@@ -1764,15 +1793,14 @@ namespace CodeImp.DoomBuilder.Data
 					// Load Decorate info cumulatively (the last Decorate is added to the previous)
 					// I'm not sure if this is the right thing to do though.
 					currentreader = dr;
-					IEnumerable<TextResourceData> decostreams = dr.GetDecorateData("DECORATE");
-					foreach(TextResourceData data in decostreams)
+					foreach(TextResourceData data in dr.GetDecorateData("DECORATE"))
 					{
 						// Parse the data
 						data.Stream.Seek(0, SeekOrigin.Begin);
 						decorate.Parse(data, true);
-						
+
 						//mxd. DECORATE lumps are interdepandable. Can't carry on...
-						if(decorate.HasError)
+						if (decorate.HasError)
 						{
 							decorate.LogError();
 							break;
@@ -1859,20 +1887,30 @@ namespace CodeImp.DoomBuilder.Data
             Dictionary<string, ActorStructure> mergedAllActorsByClass = decorate.AllActorsByClass.Concat(zscript.AllActorsByClass.Where(x => !decorate.AllActorsByClass.ContainsKey(x.Key))).ToDictionary(k => k.Key, v => v.Value);
             zdoomclasses = mergedAllActorsByClass;
 
+			// Dictionary of replaced actors that have to be recategorized
+			Dictionary<int, ActorStructure> recategorizeactors = new Dictionary<int, ActorStructure>();
+
             // Step 1. Go for all actors in the decorate to make things or update things
             foreach (ActorStructure actor in mergedActors)
             {
                 //mxd. Apply "replaces" DECORATE override...
                 if (!string.IsNullOrEmpty(actor.ReplacesClass) && thingtypesbyclass.ContainsKey(actor.ReplacesClass))
                 {
-                    // Update info
-                    thingtypesbyclass[actor.ReplacesClass].ModifyByDecorateActor(actor);
+					// Update info
+					thingtypesbyclass[actor.ReplacesClass].ModifyByDecorateActor(actor);
 
-                    // Count
-                    counter++;
+					// A replaced actor might have to go to another category. Only store the last one in case the same actor is replaced multiple times
+					if (actor.HasPropertyWithValue("$category"))
+						recategorizeactors[thingtypesbyclass[actor.ReplacesClass].Index] = actor;
+					else
+						recategorizeactors.Remove(thingtypesbyclass[actor.ReplacesClass].Index);
+
+					// Count
+					counter++;
                 }
+
                 // Check if we want to add this actor
-                else if (actor.DoomEdNum > 0)
+                if (actor.DoomEdNum > 0)
                 {
                     // Check if we can find this thing in our existing collection
                     if (thingtypes.ContainsKey(actor.DoomEdNum))
@@ -1886,8 +1924,16 @@ namespace CodeImp.DoomBuilder.Data
                         ThingCategory cat = GetThingCategory(null, thingcategories, GetCategoryInfo(actor)); //mxd
 
                         // Add new thing
-                        ThingTypeInfo t = new ThingTypeInfo(cat, actor);
-                        cat.AddThing(t);
+                        ThingTypeInfo t;
+
+						// If the thing inherits from another actor use the base actor's thing type info, otherwise create a new one
+						// This makes sure that inherited actors get all properties like the icon color
+						if (!string.IsNullOrEmpty(actor.InheritsClass) && thingtypesbyclass.ContainsKey(actor.InheritsClass))
+							t = new ThingTypeInfo(cat, actor, thingtypesbyclass[actor.InheritsClass]);
+						else
+							t = new ThingTypeInfo(cat, actor);
+
+						cat.AddThing(t);
                         thingtypes.Add(t.Index, t);
                     }
 
@@ -1896,6 +1942,26 @@ namespace CodeImp.DoomBuilder.Data
                 }
             }
 
+			// Step 1.1. Recategorize actors that replace other actors
+			foreach(KeyValuePair<int, ActorStructure> kvp in recategorizeactors)
+			{
+				int i = kvp.Key;
+				ActorStructure actor = kvp.Value;
+
+				// Remove the thing from its old thing category
+				thingtypes[i].Category.RemoveThing(thingtypes[i]);
+
+				// Get the new thing category
+				ThingCategory tc = GetThingCategory(null, thingcategories, GetCategoryInfo(actor));
+
+				// Remove the existing ThingTypeInfo and create a new one (with the new DoomEdNum)
+				thingtypes.Remove(thingtypesbyclass[actor.ReplacesClass].Index);
+				thingtypes[i] = new ThingTypeInfo(i, thingtypesbyclass[actor.ReplacesClass]);
+
+				// Re-add the ThingTypeInfo to the ThingCategory
+				tc.AddThing(thingtypes[i]);
+			}
+
             //mxd. Step 2. Apply DoomEdNum MAPINFO overrides, remove actors disabled in MAPINFO
             if (doomednumsoverride.Count > 0)
             {
@@ -2280,9 +2346,9 @@ namespace CodeImp.DoomBuilder.Data
 			return null;
 		}
 		
-		#endregion
+#endregion
 
-		#region ================== mxd. Modeldef, Voxeldef, Gldefs, Mapinfo
+#region ================== mxd. Modeldef, Voxeldef, Gldefs, Mapinfo
 
 		//mxd. This creates <Actor Class, Thing.Type> dictionary. Should be called after all DECORATE actors are parsed
 		private Dictionary<string, int> CreateActorsByClassList() 
@@ -3096,9 +3162,9 @@ namespace CodeImp.DoomBuilder.Data
 			return null;
 		}
 
-		#endregion
+#endregion
 
-		#region ================== Tools
+#region ================== Tools
 
 		// This finds the first IWAD or IPK3 resource
 		// Returns false when not found
@@ -3279,9 +3345,9 @@ namespace CodeImp.DoomBuilder.Data
             General.MainWindow.UpdateStatus();
         }
 
-        #endregion
+#endregion
 
-        #region ================== mxd. Skybox Making
+#region ================== mxd. Skybox Making
 
         internal void SetupSkybox()
 		{
@@ -3813,6 +3879,6 @@ namespace CodeImp.DoomBuilder.Data
             }
 		}
 		
-		#endregion
+#endregion
 	}
 }
diff --git a/Source/Core/Data/DirectoryReader.cs b/Source/Core/Data/DirectoryReader.cs
index fa909ba3d4d11a4ccffe6de99bd2d4512872bb90..90c1534536128697a946779c9b0003dd5245d544 100755
--- a/Source/Core/Data/DirectoryReader.cs
+++ b/Source/Core/Data/DirectoryReader.cs
@@ -38,20 +38,20 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Constructor / Disposer
 
 		// Constructor
-		public DirectoryReader(DataLocation dl, bool asreadonly) : base(dl, asreadonly)
+		public DirectoryReader(DataLocation dl, GameConfiguration config, bool asreadonly) : base(dl, asreadonly)
 		{
 			General.WriteLogLine("Opening directory resource \"" + location.location + "\"");
 			
 			// Initialize
-			files = new DirectoryFilesList(dl.location, true);
-			Initialize();
+			files = new DirectoryFilesList(dl.location, config, true);
+			Initialize(config);
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
 
 		//mxd. Don't move directory wads anywhere
-		protected override void Initialize()
+		protected override void Initialize(GameConfiguration config)
 		{
 			// Load all WAD files in the root as WAD resources
 			string[] wadfiles = GetWadFiles();
@@ -62,8 +62,8 @@ namespace CodeImp.DoomBuilder.Data
 				string wadfilepath = Path.Combine(location.location, wadfile);
 				if(General.Map.FilePathName != wadfilepath)
 				{
-					DataLocation wdl = new DataLocation(DataLocation.RESOURCE_WAD, wadfilepath, false, false, true);
-					wads.Add(new WADReader(wdl, isreadonly) { ParentResource = this } );
+					DataLocation wdl = new DataLocation(DataLocation.RESOURCE_WAD, wadfilepath, false, false, true, null);
+					wads.Add(new WADReader(wdl, config, isreadonly) { ParentResource = this } );
 				}
 			}
 		}
@@ -146,7 +146,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading patch \"" + pname + "\" from directory: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading patch \"" + pname + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -197,7 +197,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading texture \"" + pname + "\" from directory: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading texture \"" + pname + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -230,7 +230,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading HiRes texture \"" + name + "\" from directory: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading HiRes texture \"" + name + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -263,7 +263,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading colormap \"" + pname + "\" from directory: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading colormap \"" + pname + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -300,7 +300,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading sprite \"" + pname + "\" from directory: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading sprite \"" + pname + "\" from directory: " + e.Message);
 			}
 			
 			// Nothing found
@@ -331,7 +331,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while checking sprite \"" + pname + "\" existance in directory: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while checking sprite \"" + pname + "\" existance in directory: " + e.Message);
 			}
 			
 			// Nothing found
@@ -368,7 +368,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading voxel \"" + name + "\" from directory: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading voxel \"" + name + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -471,7 +471,7 @@ namespace CodeImp.DoomBuilder.Data
 
 			if(casecorrectfilename == null)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, "Unable to load file " + filename + ": file doesn't exist");
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Unable to load file " + filename + ": file doesn't exist");
 				return null;
 			}
 
@@ -484,7 +484,7 @@ namespace CodeImp.DoomBuilder.Data
 			} 
 			catch(Exception e) 
 			{
-				General.ErrorLogger.Add(ErrorType.Error, "Unable to load file: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Unable to load file: " + e.Message);
 			}
 			return s;
 		}
@@ -504,7 +504,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, "Unable to save file: " + e.Message);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Unable to save file: " + e.Message);
 				return false;
 			}
 
diff --git a/Source/Core/Data/FileImage.cs b/Source/Core/Data/FileImage.cs
index 6fbef96082c86f9709b8331de8d8a650649f9b7b..a4e3e40cf0b8d7dde9c64a70912474de9eda80d8 100755
--- a/Source/Core/Data/FileImage.cs
+++ b/Source/Core/Data/FileImage.cs
@@ -40,7 +40,7 @@ namespace CodeImp.DoomBuilder.Data
         public FileImage(string name, string filepathname, bool asflat)
         {
             // Initialize
-            this.isFlat = asflat; //mxd
+            this.texturenamespace = TextureNamespace.FLAT;
 
             if (asflat)
             {
@@ -67,7 +67,7 @@ namespace CodeImp.DoomBuilder.Data
             // Initialize
             this.scale.x = scalex;
             this.scale.y = scaley;
-            this.isFlat = asflat; //mxd
+            texturenamespace = TextureNamespace.FLAT;
 
             probableformat = (asflat ? ImageDataFormat.DOOMFLAT : ImageDataFormat.DOOMPICTURE);
 
diff --git a/Source/Core/Data/FlatImage.cs b/Source/Core/Data/FlatImage.cs
index bfb248df13aed2e0cc751e3d05650c15c8a21830..0b7f2ea31d574c41da24d9ecde118423fc4f0165 100755
--- a/Source/Core/Data/FlatImage.cs
+++ b/Source/Core/Data/FlatImage.cs
@@ -35,7 +35,7 @@ namespace CodeImp.DoomBuilder.Data
 			// Initialize
 			SetName(name);
 			virtualname = "[Flats]/" + this.name; //mxd
-			isFlat = true; //mxd
+			texturenamespace = TextureNamespace.FLAT;
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
diff --git a/Source/Core/Data/HiResImage.cs b/Source/Core/Data/HiResImage.cs
index 5863227d76ff2ba19665c57799d9f78d3d7e433b..d046e1b520fc6ae849ee677d9ab458cd399a74cf 100755
--- a/Source/Core/Data/HiResImage.cs
+++ b/Source/Core/Data/HiResImage.cs
@@ -102,7 +102,7 @@ namespace CodeImp.DoomBuilder.Data
 			virtualname = overridden.VirtualName;
 			displayname = overridden.DisplayName;
 
-			isFlat = overridden.IsFlat;
+			texturenamespace = overridden.TextureNamespace;
 			hasLongName = overridden.HasLongName;
 			overridesettingsapplied = true;
 
@@ -137,7 +137,7 @@ namespace CodeImp.DoomBuilder.Data
 				MemoryStream mem = new MemoryStream(membytes);
 				mem.Seek(0, SeekOrigin.Begin);
 
-				bitmap = ImageDataFormat.TryLoadImage(mem, (isFlat ? ImageDataFormat.DOOMFLAT : ImageDataFormat.DOOMPICTURE), General.Map.Data.Palette);
+				bitmap = ImageDataFormat.TryLoadImage(mem, (texturenamespace == TextureNamespace.FLAT) ? ImageDataFormat.DOOMFLAT : ImageDataFormat.DOOMPICTURE, General.Map.Data.Palette);
 
 				// Not loaded?
 				if(bitmap == null)
diff --git a/Source/Core/Data/ImageData.cs b/Source/Core/Data/ImageData.cs
index 2d0a288b50deec734c416b61cb0c54bf7f30b9ff..4c5f24c439f1781729f2b30a46a2448105123295 100755
--- a/Source/Core/Data/ImageData.cs
+++ b/Source/Core/Data/ImageData.cs
@@ -55,7 +55,6 @@ namespace CodeImp.DoomBuilder.Data
 		protected string shortname; //mxd. Name in uppercase and clamped to DataManager.CLASIC_IMAGE_NAME_LENGTH
 		protected string virtualname; //mxd. Path of this name is used in TextureBrowserForm
 		protected string displayname; //mxd. Name to display in TextureBrowserForm
-		protected bool isFlat; //mxd. If false, it's a texture
 		protected bool istranslucent; //mxd. If true, has pixels with alpha > 0 && < 255 
 		protected bool ismasked; //mxd. If true, has pixels with zero alpha
 		protected bool hasLongName; //mxd. Texture name is longer than DataManager.CLASIC_IMAGE_NAME_LENGTH
@@ -63,6 +62,7 @@ namespace CodeImp.DoomBuilder.Data
 		protected int namewidth; // biwa
 		protected int shortnamewidth; // biwa
 		protected bool wantIndexed; // volte
+		protected TextureNamespace texturenamespace;
 
 		//mxd. Hashing
 		private static int hashcounter;
@@ -80,6 +80,7 @@ namespace CodeImp.DoomBuilder.Data
 
         // GDI bitmap
         private Bitmap loadedbitmap;
+        private Bitmap uncorrectedbitmap;
         private Bitmap previewbitmap;
         private Bitmap spritepreviewbitmap;
 
@@ -102,7 +103,7 @@ namespace CodeImp.DoomBuilder.Data
 		public string FilePathName { get { return filepathname; } } //mxd
 		public string VirtualName { get { return virtualname; } } //mxd
 		public string DisplayName { get { return displayname; } } //mxd
-		public bool IsFlat { get { return isFlat; } } //mxd
+		public TextureNamespace TextureNamespace { get { return texturenamespace; } }
 		public bool IsTranslucent { get { return istranslucent; } } //mxd
 		public bool IsMasked { get { return ismasked; } } //mxd
 		public bool HasPatchWithSameName { get { return hasPatchWithSameName; } } //mxd
@@ -178,11 +179,13 @@ namespace CodeImp.DoomBuilder.Data
                 // Clean up
                 loadedbitmap?.Dispose();
                 previewbitmap?.Dispose();
+                uncorrectedbitmap?.Dispose();
                 spritepreviewbitmap?.Dispose();
                 texture?.Dispose();
                 indexedTexture?.Dispose();
                 loadedbitmap = null;
                 previewbitmap = null;
+                uncorrectedbitmap = null;
                 spritepreviewbitmap = null;
 				texture = null;
 					
@@ -305,6 +308,7 @@ namespace CodeImp.DoomBuilder.Data
             // Do the loading
             LocalLoadResult loadResult = LocalLoadImage();
 
+            MakeUncorrectedImage(loadResult);
             ConvertImageFormat(loadResult, usecolorcorrection);
             MakeImagePreview(loadResult);
             MakeAlphaTestImage(loadResult);
@@ -315,6 +319,8 @@ namespace CodeImp.DoomBuilder.Data
             {
                 loadResult.bitmap?.Dispose();
                 loadResult.bitmap = null;
+                loadResult.uncorrected?.Dispose();
+                loadResult.uncorrected = null;
                 onlyPreview = true;
             }
 
@@ -334,10 +340,12 @@ namespace CodeImp.DoomBuilder.Data
                     }
 
                     loadedbitmap?.Dispose();
+                    uncorrectedbitmap?.Dispose();
                     texture?.Dispose();
                     indexedTexture?.Dispose();
                     imagestate = ImageLoadState.Ready;
                     loadedbitmap = loadResult.bitmap;
+                    uncorrectedbitmap = loadResult.uncorrected;
                     alphatest = loadResult.alphatest;
                     alphatestWidth = loadResult.alphatestWidth;
                     alphatestHeight = loadResult.alphatestHeight;
@@ -390,6 +398,7 @@ namespace CodeImp.DoomBuilder.Data
 
             public Bitmap bitmap;
             public Bitmap preview;
+            public Bitmap uncorrected;
             public BitArray alphatest;
             public int alphatestWidth;
             public int alphatestHeight;
@@ -594,6 +603,14 @@ namespace CodeImp.DoomBuilder.Data
         // Dimensions of a single preview image
         const int MAX_PREVIEW_SIZE = 256; //mxd
 
+        // This makes a copy of the image before color correction
+        private void MakeUncorrectedImage(LocalLoadResult loadResult)
+        {
+	        if (loadResult.bitmap == null)
+		        return;
+	        loadResult.uncorrected = new Bitmap(loadResult.bitmap);
+        }
+
         // This makes a preview for the given image and updates the image settings
         private void MakeImagePreview(LocalLoadResult loadResult)
         {
@@ -687,7 +704,7 @@ namespace CodeImp.DoomBuilder.Data
 			if (indexed && indexedTexture != null)
 				return indexedTexture;
 			if (!indexed && texture != null)
-          return texture;
+				return texture;
 
 			if (indexed && !wantIndexed)
 			{
@@ -696,39 +713,46 @@ namespace CodeImp.DoomBuilder.Data
 				imagestate = ImageLoadState.None;
 				wantIndexed = true;
 			}
-			
+
 			if (imagestate == ImageLoadState.Loading)
-          return General.Map.Data.LoadingTexture;
-      if (loadfailed)
-          return General.Map.Data.FailedTexture;
-
-      if (imagestate == ImageLoadState.None)
-      {
-          General.Map.Data.QueueLoadImage(this);
-          return General.Map.Data.LoadingTexture;
-      }
-      
-      if (loadedbitmap == null)
-      {
-	      return General.Map.Data.LoadingTexture;
-      }
-
-      texture = new Texture(General.Map.Graphics, loadedbitmap);
-      if (wantIndexed)
-      {
-	      Bitmap indexedBitmap = CreateIndexedBitmap(loadedbitmap, General.Map.Data.Palette);
-	      indexedTexture = new Texture(General.Map.Graphics, indexedBitmap);
-	      indexedTexture.UserData = TEXTURE_INDEXED;
-      }
-
-      loadedbitmap.Dispose();
-      loadedbitmap = null;
+				return General.Map.Data.LoadingTexture;
+			if (loadfailed)
+				return General.Map.Data.FailedTexture;
+
+			if (imagestate == ImageLoadState.None)
+			{
+				General.Map.Data.QueueLoadImage(this);
+				return General.Map.Data.LoadingTexture;
+			}
+
+			if (loadedbitmap == null)
+			{
+				return General.Map.Data.LoadingTexture;
+			}
+
+			if (wantIndexed)
+			{
+				Bitmap indexedBitmap = CreateIndexedBitmap(uncorrectedbitmap, General.Map.Data.Palette);
+				indexedTexture = new Texture(General.Map.Graphics, indexedBitmap);
+				indexedTexture.UserData = TEXTURE_INDEXED;
+			}
+
+			texture = new Texture(General.Map.Graphics, loadedbitmap);
+
+			loadedbitmap.Dispose();
+			loadedbitmap = null;
+
+			if (uncorrectedbitmap != null)
+			{
+				uncorrectedbitmap.Dispose();
+				uncorrectedbitmap = null;
+			}
 
 #if DEBUG
 			texture.Tag = name; //mxd. Helps with tracking undisposed resources...
 #endif
-			
-      return indexed ? indexedTexture : texture;
+
+			return indexed ? indexedTexture : texture;
 		}
 
 		Bitmap CreateIndexedBitmap(Bitmap original, Playpal palette)
diff --git a/Source/Core/Data/PK3FileImage.cs b/Source/Core/Data/PK3FileImage.cs
index a5d5a44bbba0b3c2a7aab4903c8a7ea08e1a4eb9..2c98fcece0e91511759d9a8ab3400f862eda3747 100755
--- a/Source/Core/Data/PK3FileImage.cs
+++ b/Source/Core/Data/PK3FileImage.cs
@@ -46,7 +46,7 @@ namespace CodeImp.DoomBuilder.Data
 		{
 			// Initialize
 			this.datareader = datareader;
-			this.isFlat = asflat; //mxd
+			texturenamespace = TextureNamespace.FLAT;
 
 			if(asflat)
 			{
diff --git a/Source/Core/Data/PK3Reader.cs b/Source/Core/Data/PK3Reader.cs
index 79d96e940dc9bc01d07e57b7c1b93d7e8ebe8a85..9dd0cd23a51942318373570d750768f0742d6e91 100755
--- a/Source/Core/Data/PK3Reader.cs
+++ b/Source/Core/Data/PK3Reader.cs
@@ -68,17 +68,18 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Constructor / Disposer
 
 		// Constructor
-		public PK3Reader(DataLocation dl, bool asreadonly) : base(dl, asreadonly)
+		public PK3Reader(DataLocation dl, GameConfiguration config, bool asreadonly) : base(dl, asreadonly)
 		{
-            LoadFrom(dl, asreadonly);
+            LoadFrom(dl, config, asreadonly);
 		}
 
-        private void LoadFrom(DataLocation dl, bool asreadonly)
+        private void LoadFrom(DataLocation dl, GameConfiguration config, bool asreadonly)
         {
 			FileAccess access;
 			FileShare share;
 
 			isreadonly = asreadonly;
+			config = config;
 
 			// Determine if opening for read only
 			if (isreadonly)
@@ -143,10 +144,10 @@ namespace CodeImp.DoomBuilder.Data
 			filestream = null;
 
             // Make files list
-            files = new DirectoryFilesList(dl.GetDisplayName(), fileentries);
+            files = new DirectoryFilesList(dl.GetDisplayName(), config, Silent, fileentries);
 
             // Initialize without path (because we use paths relative to the PK3 file)
-            Initialize();
+            Initialize(config);
 
             // We have no destructor
             GC.SuppressFinalize(this);
@@ -589,7 +590,7 @@ namespace CodeImp.DoomBuilder.Data
 							catch(SharpCompress.Compressors.Deflate.ZlibException)
 							{
 								// This happens when the PK3 was modified externally and the resources were not reloaded
-								General.ErrorLogger.Add(ErrorType.Error, "Cannot load the file \"" + filename + "\" from archive \"" + location.GetDisplayName() + "\". Did you modify the archive without reloading the resouces?");
+								if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Cannot load the file \"" + filename + "\" from archive \"" + location.GetDisplayName() + "\". Did you modify the archive without reloading the resouces?");
 
 								filedata.Dispose();
 								filedata = null;
@@ -598,7 +599,7 @@ namespace CodeImp.DoomBuilder.Data
 							}
 							catch(NotSupportedException e)
 							{
-								General.ErrorLogger.Add(ErrorType.Error, "Cannot load the file \"" + filename + "\" from archive \"" + location.GetDisplayName() + "\". " + e.Message);
+								if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Cannot load the file \"" + filename + "\" from archive \"" + location.GetDisplayName() + "\". " + e.Message);
 
 								filedata.Dispose();
 								filedata = null;
@@ -618,7 +619,7 @@ namespace CodeImp.DoomBuilder.Data
 			if(filedata == null)
 			{
 				//mxd
-				General.ErrorLogger.Add(ErrorType.Error, "Cannot find the file \"" + filename + "\" in archive \"" + location.GetDisplayName() + "\".");
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Cannot find the file \"" + filename + "\" in archive \"" + location.GetDisplayName() + "\".");
 				return null;
 			}
 
@@ -656,7 +657,7 @@ namespace CodeImp.DoomBuilder.Data
 						+ ", started at " + checkresult.Processes[0].StartTime + ").";
 				}
 				
-				General.ErrorLogger.Add(ErrorType.Error, errmsg);
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Error, errmsg);
 				return false;
 			}
 
@@ -667,7 +668,7 @@ namespace CodeImp.DoomBuilder.Data
 					if(za == null)
 					{
 						string errmsg = "Unable to save file \"" + filename + "\" into archive \"" + location.GetDisplayName() + "\". Unable to open target file as a zip archive.";
-						General.ErrorLogger.Add(ErrorType.Error, errmsg);
+						if (!Silent) General.ErrorLogger.Add(ErrorType.Error, errmsg);
 						return false;
 					}
 
@@ -720,7 +721,7 @@ namespace CodeImp.DoomBuilder.Data
 					case 60:
 					case 62:
 					case 124:
-						General.ErrorLogger.Add(ErrorType.Error, "Error in \"" + location.GetDisplayName() + "\": unsupported character \"" + c + "\" in path \"" + path + "\". File loading was skipped.");
+						if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Error in \"" + location.GetDisplayName() + "\": unsupported character \"" + c + "\" in path \"" + path + "\". File loading was skipped.");
 						return false;
 
 					default:
diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs
index 99cc69ba36750d8853699ec1c80117f7952e78b5..e3320dc5672e77fb6db28f5f717cdbe7b1793fb9 100755
--- a/Source/Core/Data/PK3StructuredReader.cs
+++ b/Source/Core/Data/PK3StructuredReader.cs
@@ -38,11 +38,14 @@ namespace CodeImp.DoomBuilder.Data
 		protected const string COLORMAPS_DIR = "colormaps";
 		protected const string GRAPHICS_DIR = "graphics"; //mxd
 		protected const string VOXELS_DIR = "voxels"; //mxd
-		
+
 		#endregion
 
 		#region ================== Variables
-		
+
+		// Optional setting
+		public bool Silent = false;
+
 		// Source
 		protected readonly bool roottextures;
 		protected readonly bool rootflats;
@@ -70,7 +73,7 @@ namespace CodeImp.DoomBuilder.Data
 		}
 		
 		// Call this to initialize this class
-		protected virtual void Initialize()
+		protected virtual void Initialize(GameConfiguration config)
 		{
             // [ZZ] we can have wad files already. dispose if any.
             if (wads != null) foreach (WADReader wr in wads) wr.Dispose();
@@ -80,8 +83,8 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(string w in wadfiles)
 			{
 				string tempfile = CreateTempFile(w);
-				DataLocation wdl = new DataLocation(DataLocation.RESOURCE_WAD, tempfile, Path.Combine(location.GetDisplayName(), Path.GetFileName(w)), false, false, true);
-				wads.Add(new WADReader(wdl, location.type != DataLocation.RESOURCE_DIRECTORY) { ParentResource = this } );
+				DataLocation wdl = new DataLocation(DataLocation.RESOURCE_WAD, tempfile, Path.Combine(location.GetDisplayName(), Path.GetFileName(w)), false, false, true, new List<string>());
+				wads.Add(new WADReader(wdl, config, location.type != DataLocation.RESOURCE_DIRECTORY) { ParentResource = this } );
 			}
 		}
 		
@@ -144,7 +147,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(stream.Length > 767) //mxd
 					palette = new Playpal(stream);
 				else
-					General.ErrorLogger.Add(ErrorType.Warning, "Warning: invalid palette \"" + foundfile + "\"");
+					if (!Silent) General.ErrorLogger.Add(ErrorType.Warning, "Warning: invalid palette \"" + foundfile + "\"");
 				stream.Dispose();
 			}
 			
@@ -175,7 +178,7 @@ namespace CodeImp.DoomBuilder.Data
 		        if(stream.Length >= 256) //mxd
 			        colormap = new ColorMap(stream, palette);
 		        else
-			        General.ErrorLogger.Add(ErrorType.Warning, "Warning: invalid colormap \"" + foundfile + "\"");
+					if (!Silent) General.ErrorLogger.Add(ErrorType.Warning, "Warning: invalid colormap \"" + foundfile + "\"");
 		        stream.Dispose();
 	        }
 			
@@ -217,7 +220,7 @@ namespace CodeImp.DoomBuilder.Data
 			if((texture1file != null) && FileExists(texture1file))
 			{
 				MemoryStream filedata = LoadFile(texture1file);
-				WADReader.LoadTextureSet("TEXTURE1", filedata, ref imgset, pnames);
+				WADReader.LoadTextureSet("TEXTURE1", filedata, ref imgset, pnames, Silent);
 				filedata.Dispose();
 			}
 
@@ -226,7 +229,7 @@ namespace CodeImp.DoomBuilder.Data
 			if((texture2file != null) && FileExists(texture2file))
 			{
 				MemoryStream filedata = LoadFile(texture2file);
-				WADReader.LoadTextureSet("TEXTURE2", filedata, ref imgset, pnames);
+				WADReader.LoadTextureSet("TEXTURE2", filedata, ref imgset, pnames, Silent);
 				filedata.Dispose();
 			}
 			
@@ -245,6 +248,9 @@ namespace CodeImp.DoomBuilder.Data
 					// Make the textures
 					foreach(TextureStructure t in cachedparsers[fullpath].Textures)
 						imgset.Add(t.MakeImage());
+
+					foreach (TextureStructure t in cachedparsers[fullpath].WallTextures)
+						imgset.Add(t.MakeImage());
 				}
 				else
 				{
@@ -279,7 +285,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(f)))
 				{
 					// Can't load image without name
-					General.ErrorLogger.Add(ErrorType.Error, "Can't load an unnamed HiRes texture from \"" + Path.Combine(this.location.GetDisplayName(), HIRES_DIR) + "\". Please consider giving names to your resources.");
+					if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Can't load an unnamed HiRes texture from \"" + Path.Combine(this.location.GetDisplayName(), HIRES_DIR) + "\". Please consider giving names to your resources.");
 				}
 				else
 				{
@@ -810,7 +816,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(f))) 
 				{
 					// Can't load image without name
-					General.ErrorLogger.Add(ErrorType.Error, "Can't load an unnamed texture from \"" + path + "\". Please consider giving names to your resources.");
+					if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Can't load an unnamed texture from \"" + path + "\". Please consider giving names to your resources.");
 				} 
 				else 
 				{
@@ -840,7 +846,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			else
 			{
-				General.ErrorLogger.Add(ErrorType.Warning, "Unable to load " + type + " file \"" + fullname + "\"");
+				if (!Silent) General.ErrorLogger.Add(ErrorType.Warning, "Unable to load " + type + " file \"" + fullname + "\"");
 				return new string[0];
 			}
 		}
diff --git a/Source/Core/Data/Scripting/AccScriptHandler.cs b/Source/Core/Data/Scripting/AccScriptHandler.cs
index 024324bcf1466fc0c0d01ef51b991bbc77bdff86..9d3f31b733cc8bfa4077d48d3e481eaabfb00973 100755
--- a/Source/Core/Data/Scripting/AccScriptHandler.cs
+++ b/Source/Core/Data/Scripting/AccScriptHandler.cs
@@ -25,7 +25,7 @@ namespace CodeImp.DoomBuilder.Data.Scripting
 			target.Items.Clear();
 
 			AcsParserSE parser = new AcsParserSE { AddArgumentsToScriptNames = true, IsMapScriptsLump = tab is ScriptLumpDocumentTab, IgnoreErrors = true };
-			DataLocation dl = new DataLocation(DataLocation.RESOURCE_DIRECTORY, Path.GetDirectoryName(string.IsNullOrEmpty(tab.Filename)? tab.Title : tab.Filename), false, false, false);
+			DataLocation dl = new DataLocation(DataLocation.RESOURCE_DIRECTORY, Path.GetDirectoryName(string.IsNullOrEmpty(tab.Filename)? tab.Title : tab.Filename), false, false, false, null);
 			TextResourceData data = new TextResourceData(stream, dl, (parser.IsMapScriptsLump ? "?SCRIPTS" : tab.Filename));
 
 			if(parser.Parse(data, false))
diff --git a/Source/Core/Data/SimpleTextureImage.cs b/Source/Core/Data/SimpleTextureImage.cs
index 5dfe6fe0cf8364660aa248fe6aeaea1352c00023..09d8d660c52c52937560bb5b6268da2d55e9a124 100755
--- a/Source/Core/Data/SimpleTextureImage.cs
+++ b/Source/Core/Data/SimpleTextureImage.cs
@@ -46,6 +46,7 @@ namespace CodeImp.DoomBuilder.Data
 			this.scale.x = scalex;
 			this.scale.y = scaley;
 			this.lumpname = lumpname;
+			texturenamespace = TextureNamespace.TEXTURE;
 			SetName(name);
 			virtualname = "[Textures]/" + this.name; //mxd
 			
diff --git a/Source/Core/Data/SpriteImage.cs b/Source/Core/Data/SpriteImage.cs
index ef00ad43aa0c0752841bad5ad77b6366c4ebafff..221cf0e7ac333576eb7f7a9e20ffe0074db54a6a 100755
--- a/Source/Core/Data/SpriteImage.cs
+++ b/Source/Core/Data/SpriteImage.cs
@@ -57,6 +57,8 @@ namespace CodeImp.DoomBuilder.Data
 			// Initialize
 			SetName(name);
 
+			texturenamespace = TextureNamespace.SPRITE;
+
             AllowUnload = false;
 
 			// We have no destructor
diff --git a/Source/Core/Data/TEXTURESImage.cs b/Source/Core/Data/TEXTURESImage.cs
index 6228bbc7b943613e438a43c3fb0cae319b4e2a99..ac2fc2ea8704e939e432044b5b8699a18fc3d5d3 100755
--- a/Source/Core/Data/TEXTURESImage.cs
+++ b/Source/Core/Data/TEXTURESImage.cs
@@ -30,6 +30,15 @@ using CodeImp.DoomBuilder.Rendering;
 
 namespace CodeImp.DoomBuilder.Data
 {
+	public enum TextureNamespace
+	{
+		TEXTURE,
+		WALLTEXTURE,
+		FLAT,
+		SPRITE,
+		PATCH
+	}
+
 	internal sealed unsafe class TEXTURESImage : ImageData
 	{
 		#region ================== Variables
@@ -37,14 +46,14 @@ namespace CodeImp.DoomBuilder.Data
 		private readonly List<TexturePatch> patches; //mxd
 		private readonly bool optional; //mxd
 		private readonly bool nulltexture; //mxd
-		
+
 		#endregion
 
 		#region ================== Constructor / Disposer
 
 		// Constructor
 		public TEXTURESImage(string name, string virtualpath, int width, int height, float scalex, float scaley, 
-			bool worldpanning, bool isflat, bool optional, bool nulltexture)
+			bool worldpanning, TextureNamespace texturenamespace, bool optional, bool nulltexture)
 		{
 			// Initialize
 			this.width = width;
@@ -55,11 +64,11 @@ namespace CodeImp.DoomBuilder.Data
 			this.optional = optional; //mxd
 			this.nulltexture = nulltexture; //mxd
 			this.patches = new List<TexturePatch>(1);
+			this.texturenamespace = texturenamespace;
 
 			//mxd
 			SetName(name);
 			this.virtualname = (!string.IsNullOrEmpty(virtualpath) ? virtualpath : "[TEXTURES]") + Path.AltDirectorySeparatorChar + this.name;
-			this.isFlat = isflat;
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
diff --git a/Source/Core/Data/TextureImage.cs b/Source/Core/Data/TextureImage.cs
index a3fc35f3980236c48302179575c39f3a64d7d9a8..06ede6069993f6d6cd9f2d0bf66125f3daa1e0bf 100755
--- a/Source/Core/Data/TextureImage.cs
+++ b/Source/Core/Data/TextureImage.cs
@@ -48,6 +48,7 @@ namespace CodeImp.DoomBuilder.Data
 			this.scale.x = scalex;
 			this.scale.y = scaley;
 			this.worldpanning = worldpanning; //mxd
+			this.texturenamespace = TextureNamespace.WALLTEXTURE;
 			this.patches = new List<TexturePatch>();
 			SetName(name);
 			virtualname = "[" + group + "]/" + this.name; //mxd
diff --git a/Source/Core/Data/WADReader.cs b/Source/Core/Data/WADReader.cs
index 2a14be006000be31d7f70ae31650d6101958488e..47d06605cb6e268885b4007e51ce1c24f4122b75 100755
--- a/Source/Core/Data/WADReader.cs
+++ b/Source/Core/Data/WADReader.cs
@@ -61,6 +61,9 @@ namespace CodeImp.DoomBuilder.Data
 
 		#region ================== Variables
 
+		// Optional setting
+		public bool Silent = false;
+
 		// Source
 		private WAD file;
 		private bool is_iwad;
@@ -89,7 +92,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Constructor / Disposer
 
 		// Constructor
-		public WADReader(DataLocation dl, bool asreadonly) : base(dl, asreadonly)
+		public WADReader(DataLocation dl, GameConfiguration config, bool asreadonly) : base(dl, asreadonly)
 		{
 			General.WriteLogLine("Opening WAD resource \"" + location.location + "\"");
 
@@ -99,14 +102,14 @@ namespace CodeImp.DoomBuilder.Data
 			// Initialize
 			file = new WAD(location.location, asreadonly);
 			strictpatches = dl.option1;
-			Initialize(); //mxd
+			Initialize(config); //mxd
 
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
 
 		//mxd. Constructor for temporary WAD files
-		internal WADReader(DataLocation dl, bool asreadonly, bool create) : base(dl, asreadonly)
+		internal WADReader(DataLocation dl, GameConfiguration config, bool asreadonly, bool create) : base(dl, asreadonly)
 		{
 			if(!create)
 			{
@@ -119,14 +122,14 @@ namespace CodeImp.DoomBuilder.Data
 			// Initialize
 			file = new WAD(location.location, asreadonly);
 			strictpatches = dl.option1;
-			Initialize();
+			Initialize(config);
 
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
 
 		//mxd
-		private void Initialize()
+		private void Initialize(GameConfiguration config)
 		{
 			is_iwad = file.IsIWAD;
 			isreadonly |= file.IsReadOnly; // Just in case...
@@ -141,13 +144,13 @@ namespace CodeImp.DoomBuilder.Data
 			voxelranges = new List<LumpRange>(); //mxd
 			
 			// Find ranges
-			FindRanges(patchranges, General.Map.Config.PatchRanges, "patches", "Patch");
-			FindRanges(spriteranges, General.Map.Config.SpriteRanges, "sprites", "Sprite");
-			FindRanges(flatranges, General.Map.Config.FlatRanges, "flats", "Flat");
-			FindRanges(textureranges, General.Map.Config.TextureRanges, "textures", "Texture");
-			FindRanges(hiresranges, General.Map.Config.HiResRanges, "hires", "HiRes texture");
-			FindRanges(colormapranges, General.Map.Config.ColormapRanges, "colormaps", "Colormap");
-			FindRanges(voxelranges, General.Map.Config.VoxelRanges, "voxels", "Voxel");
+			FindRanges(config, patchranges, config.PatchRanges, "patches", "Patch");
+			FindRanges(config, spriteranges, config.SpriteRanges, "sprites", "Sprite");
+			FindRanges(config, flatranges, config.FlatRanges, "flats", "Flat");
+			FindRanges(config, textureranges, config.TextureRanges, "textures", "Texture");
+			FindRanges(config, hiresranges, config.HiResRanges, "hires", "HiRes texture");
+			FindRanges(config, colormapranges, config.ColormapRanges, "colormaps", "Colormap");
+			FindRanges(config, voxelranges, config.VoxelRanges, "voxels", "Voxel");
 
 			//mxd
 			invertedflatranges = new List<LumpRange>();
@@ -225,7 +228,7 @@ namespace CodeImp.DoomBuilder.Data
         }
 
         // This fills a ranges list
-        private void FindRanges(List<LumpRange> ranges, IDictionary rangeinfos, string rangename, string elementname)
+        private void FindRanges(GameConfiguration config, List<LumpRange> ranges, IDictionary rangeinfos, string rangename, string elementname)
 		{
 			Dictionary<LumpRange, KeyValuePair<string, string>> failedranges = new Dictionary<LumpRange, KeyValuePair<string, string>>(); //mxd
 			Dictionary<int, bool> successfulrangestarts = new Dictionary<int, bool>(); //mxd
@@ -234,8 +237,8 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DictionaryEntry r in rangeinfos)
 			{
 				// Read start and end
-				string rangestart = General.Map.Config.ReadSetting(rangename + "." + r.Key + ".start", "");
-				string rangeend = General.Map.Config.ReadSetting(rangename + "." + r.Key + ".end", "");
+				string rangestart = config.ReadSetting(rangename + "." + r.Key + ".start", "");
+				string rangeend = config.ReadSetting(rangename + "." + r.Key + ".end", "");
 				if((rangestart.Length > 0) && (rangeend.Length > 0))
 				{
 					// Find ranges
@@ -271,7 +274,7 @@ namespace CodeImp.DoomBuilder.Data
 				foreach(KeyValuePair<LumpRange, KeyValuePair<string, string>> group in failedranges)
 				{
 					if(successfulrangestarts.ContainsKey(group.Key.start)) continue;
-					General.ErrorLogger.Add(ErrorType.Warning, "\"" + group.Value.Key + "\" range at index " + group.Key.start + " is not closed in resource \"" + location.GetDisplayName() + "\" (\"" + group.Value.Value + "\" marker is missing).");
+					if (!Silent) General.ErrorLogger.Add(ErrorType.Warning, "\"" + group.Value.Key + "\" range at index " + group.Key.start + " is not closed in resource \"" + location.GetDisplayName() + "\" (\"" + group.Value.Value + "\" marker is missing).");
 				}
 
 				//mxd. Check duplicates
@@ -280,8 +283,10 @@ namespace CodeImp.DoomBuilder.Data
 					HashSet<string> names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 					for(int i = range.start + 1; i < range.end; i++)
 					{
-						if(names.Contains(file.Lumps[i].Name))
-							General.ErrorLogger.Add(ErrorType.Warning, elementname + " \"" + file.Lumps[i].Name + "\", index " + i + " is double defined in resource \"" + location.GetDisplayName() + "\".");
+						if (names.Contains(file.Lumps[i].Name))
+						{
+							if (!Silent) General.ErrorLogger.Add(ErrorType.Warning, elementname + " \"" + file.Lumps[i].Name + "\", index " + i + " is double defined in resource \"" + location.GetDisplayName() + "\".");
+						}
 						else
 							names.Add(file.Lumps[i].Name);
 					}
@@ -430,11 +435,11 @@ namespace CodeImp.DoomBuilder.Data
 			Lump lump = file.FindLump("TEXTURE1");
 			if(lump != null) 
 			{
-				LoadTextureSet("TEXTURE1", lump.GetSafeStream(), ref images, pnames);
+				LoadTextureSet("TEXTURE1", lump.GetSafeStream(), ref images, pnames, Silent);
 				if(images.Count > 0) images.RemoveAt(0); //mxd. The first TEXTURE1 texture cannot be used. Let's remove it here
 			}
 			lump = file.FindLump("TEXTURE2");
-			if(lump != null) LoadTextureSet("TEXTURE2", lump.GetSafeStream(), ref images, pnames);
+			if(lump != null) LoadTextureSet("TEXTURE2", lump.GetSafeStream(), ref images, pnames, Silent);
 			
 			// Read ranges from configuration
 			foreach(LumpRange range in textureranges)
@@ -454,7 +459,7 @@ namespace CodeImp.DoomBuilder.Data
 					else 
 					{
 						// Can't load image without size
-						General.ErrorLogger.Add(ErrorType.Error, "Can't load texture \"" + file.Lumps[i].Name + "\" from \"" + location.GetDisplayName() + "\" because it doesn't contain any data.");
+						if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Can't load texture \"" + file.Lumps[i].Name + "\" from \"" + location.GetDisplayName() + "\" because it doesn't contain any data.");
 					}
 				}
 			}
@@ -470,6 +475,9 @@ namespace CodeImp.DoomBuilder.Data
 					// Make the textures
 					foreach(TextureStructure t in cachedparsers[fullpath].Textures)
 						images.Add(t.MakeImage());
+
+					foreach (TextureStructure t in cachedparsers[fullpath].WallTextures)
+						images.Add(t.MakeImage());
 				}
 				else
 				{
@@ -509,7 +517,7 @@ namespace CodeImp.DoomBuilder.Data
 					else
 					{
 						// Can't load image without size
-						General.ErrorLogger.Add(ErrorType.Error, "Can't load HiRes texture \"" + file.Lumps[i].Name + "\" from \"" + location.GetDisplayName() + "\" because it doesn't contain any data.");
+						if (!Silent) General.ErrorLogger.Add(ErrorType.Error, "Can't load HiRes texture \"" + file.Lumps[i].Name + "\" from \"" + location.GetDisplayName() + "\" because it doesn't contain any data.");
 					}
 				}
 			}
@@ -525,12 +533,13 @@ namespace CodeImp.DoomBuilder.Data
 			parser.Parse(data, false);
 			if(parser.HasError) parser.LogError(); //mxd
 
-			// Make the textures
-			foreach(TextureStructure t in parser.Textures)
-			{
-				// Add the texture
+			// Make and add the textures
+			foreach (TextureStructure t in parser.Textures)
+				images.Add(t.MakeImage());
+
+			// Make and add the wall textures
+			foreach (TextureStructure t in parser.WallTextures)
 				images.Add(t.MakeImage());
-			}
 
 			//mxd. Add to text resources collection
 			if(!General.Map.Data.ScriptResources.ContainsKey(parser.ScriptType))
@@ -543,7 +552,7 @@ namespace CodeImp.DoomBuilder.Data
 		}
 		
 		// This loads a set of textures
-		public static void LoadTextureSet(string sourcename, Stream texturedata, ref List<ImageData> images, PatchNames pnames)
+		public static void LoadTextureSet(string sourcename, Stream texturedata, ref List<ImageData> images, PatchNames pnames, bool silent)
 		{
 			if(texturedata.Length == 0) return;
 			BinaryReader reader = new BinaryReader(texturedata);
@@ -604,7 +613,7 @@ namespace CodeImp.DoomBuilder.Data
 					else
 					{
 						// Can't load image without name
-						General.ErrorLogger.Add(ErrorType.Error, "Can't load an unnamed texture from \"" + sourcename + "\". Please consider giving names to your resources.");
+						if (!silent) General.ErrorLogger.Add(ErrorType.Error, "Can't load an unnamed texture from \"" + sourcename + "\". Please consider giving names to your resources.");
 					}
 					
 					// Go for all patches in texture
@@ -627,7 +636,7 @@ namespace CodeImp.DoomBuilder.Data
 							else
 							{
 								// Can't load image without name
-								General.ErrorLogger.Add(ErrorType.Error, "Can't use an unnamed patch referenced in \"" + sourcename + "\". Please consider giving names to your resources.");
+								if (!silent) General.ErrorLogger.Add(ErrorType.Error, "Can't use an unnamed patch referenced in \"" + sourcename + "\". Please consider giving names to your resources.");
 							}
 						}
 					}
diff --git a/Source/Core/Dehacked/DehackedParser.cs b/Source/Core/Dehacked/DehackedParser.cs
index a1d71a79938d9dcc1f1e332f627eebec946b2234..4cb030a1737a3ed7b82362c8e8f5d65226fde82e 100644
--- a/Source/Core/Dehacked/DehackedParser.cs
+++ b/Source/Core/Dehacked/DehackedParser.cs
@@ -196,9 +196,20 @@ namespace CodeImp.DoomBuilder.Dehacked
 			string line = datareader.ReadLine();
 
 			if (line != null)
-				return line.Trim();
-			else
-				return null;
+			{
+				line = line.Trim();
+
+				// Editor key?
+				if (line.StartsWith("#$"))
+					return line;
+
+				// Cut everything from the line after a #, unless it's the "ID #" field, then cut everything after then next #
+				// This is technically against the (nowhere officially defined) DeHackEd specs, but of course people manually
+				// added comments at the end of lines anyway and got away with it
+				return Regex.Replace(line, @"\s*(id\s+#)?([^#]*)(#[^$].+)?", "$1$2", RegexOptions.IgnoreCase).Trim();
+			}
+
+			return null;
 		}
 
 		/// <summary>
@@ -389,7 +400,7 @@ namespace CodeImp.DoomBuilder.Dehacked
 		{
 			// Thing headers have the format "Thing <thingnumber> (<thingname>)". Note that "thingnumber" is not the
 			// DoomEdNum, but the Dehacked thing number
-			Regex re = new Regex(@"thing\s+(\d+)\s+\((.+)\)", RegexOptions.IgnoreCase);
+			Regex re = new Regex(@"thing\s+(\d+)(\s+\((.+)\))?", RegexOptions.IgnoreCase);
 			Match m = re.Match(line);
 
 			if (!m.Success)
@@ -399,7 +410,7 @@ namespace CodeImp.DoomBuilder.Dehacked
 			}
 
 			int dehthingnumber = int.Parse(m.Groups[1].Value);
-			string dehthingname = m.Groups[2].Value;
+			string dehthingname = string.IsNullOrWhiteSpace(m.Groups[3].Value) ? "<DeHackEd thing " + dehthingnumber + ">" : m.Groups[3].Value;
 			string fieldkey = string.Empty;
 			string fieldvalue = string.Empty;
 
diff --git a/Source/Core/Editing/ClassicMode.cs b/Source/Core/Editing/ClassicMode.cs
index d1d16fec485df716433216859b65e4337fb7caba..751d2194114e49102ee6546f0a10ba6aaaa2dc95 100755
--- a/Source/Core/Editing/ClassicMode.cs
+++ b/Source/Core/Editing/ClassicMode.cs
@@ -89,6 +89,9 @@ namespace CodeImp.DoomBuilder.Editing
 
 		#region ================== Properties
 		
+		// If false, then vertices will not be drawn if "hide vertices outside vertex mode" is enabled
+		public virtual bool AlwaysShowVertices { get { return false;  } }
+		
 		// Mouse status
 		public Vector2D MousePos { get { return mousepos; } }
 		public Vector2D MouseLastPos { get { return mouselastpos; } }
@@ -1058,7 +1061,15 @@ namespace CodeImp.DoomBuilder.Editing
 		protected virtual void ToggleHighlight()
 		{
 			General.Settings.UseHighlight = !General.Settings.UseHighlight;
-			General.Interface.DisplayStatus(StatusType.Action, "Highlight is now " + (General.Settings.UseHighlight ? "ON" : "OFF") + ".");
+
+			string shortmessage = "Highlight is now " + (General.Settings.UseHighlight ? "ON" : "OFF") + ".";
+			string message = "Highlight is now " + (General.Settings.UseHighlight ? "ON" : "OFF") + ".";
+			string key = Actions.Action.GetShortcutKeyDesc(General.Actions.Current.ShortcutKey);
+
+			if (!string.IsNullOrEmpty(key))
+				message += $" Press '{key}' to toggle.";
+
+			General.ToastManager.ShowToast("togglehighlight", ToastType.INFO, "Changed highlight", message, new StatusInfo(StatusType.Action, shortmessage));
 
 			// Redraw display to show changes
 			General.Interface.RedrawDisplay();
diff --git a/Source/Core/Editing/EditMode.cs b/Source/Core/Editing/EditMode.cs
index 6c8c35902f330fa1a5853e1ecafc203eace5c4a7..a2c4aa563851867290cde0234599ecfa2a68b3c0 100755
--- a/Source/Core/Editing/EditMode.cs
+++ b/Source/Core/Editing/EditMode.cs
@@ -260,6 +260,10 @@ namespace CodeImp.DoomBuilder.Editing
 		// Script events
 		public virtual bool OnScriptRunBegin() { return true; }
 		public virtual void OnScriptRunEnd() { }
+
+		// This should be called by global actions (i.e. that are not part of an editing mode) when they changed map elements,
+		// so that the mode can react to it (for example by rebuilding a blockmap)
+		public virtual void OnMapElementsChanged() { }
 		
 		#endregion
 	}
diff --git a/Source/Core/Editing/GridSetup.cs b/Source/Core/Editing/GridSetup.cs
index ee221946a2e6c6bad1bfe451d4feb387b41561d5..b6e6e07f85ce1bc7d9b0cc6472eae6ce6f926fab 100755
--- a/Source/Core/Editing/GridSetup.cs
+++ b/Source/Core/Editing/GridSetup.cs
@@ -259,7 +259,7 @@ namespace CodeImp.DoomBuilder.Editing
 		public static Vector2D SnappedToGrid(Vector2D v, double gridsize, double gridsizeinv, double gridrotate = 0.0f, double gridoriginx = 0, double gridoriginy = 0)
 		{
 			Vector2D origin = new Vector2D(gridoriginx, gridoriginy);
-			bool transformed = Math.Abs(gridrotate) > 1e-4 || gridoriginx != 0 || gridoriginx != 0;
+			bool transformed = Math.Abs(gridrotate) > 1e-4 || gridoriginx != 0 || gridoriginy != 0;
 			if (transformed)
 			{
 				// Grid is transformed, so reverse the transformation first
diff --git a/Source/Core/GZBuilder/Data/LinksCollector.cs b/Source/Core/GZBuilder/Data/LinksCollector.cs
index 82a3eae1adedb6592b1300c173ad65ac03c8ded1..4c1586ff3c44d3e7df77664cf5302b3bc5d71b6b 100755
--- a/Source/Core/GZBuilder/Data/LinksCollector.cs
+++ b/Source/Core/GZBuilder/Data/LinksCollector.cs
@@ -579,6 +579,14 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
                         color = new PixelColor((byte)linealpha, (byte)t.Args[1], (byte)t.Args[2], (byte)t.Args[3]);
                         break;
 
+					case GZGeneral.LightDef.POINT_STATIC:
+						// ZDRay static lights have an intensity that's set through the thing's alpha value
+						double intensity = t.Fields.GetValue("alpha", 1.0);
+						byte r = (byte)General.Clamp(t.Args[0] * intensity, 0.0, 255.0);
+						byte g = (byte)General.Clamp(t.Args[1] * intensity, 0.0, 255.0);
+						byte b = (byte)General.Clamp(t.Args[2] * intensity, 0.0, 255.0);
+						color = new PixelColor((byte)linealpha, r, g, b);
+						break;
                     default:
                         color = new PixelColor((byte)linealpha, (byte)t.Args[0], (byte)t.Args[1], (byte)t.Args[2]);
                         break;
@@ -613,6 +621,20 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
             }
             else color = new PixelColor((byte)linealpha, (byte)((t.Args[0] & 0xFF0000) >> 16), (byte)((t.Args[0] & 0x00FF00) >> 8), (byte)((t.Args[0] & 0x0000FF)));
 
+			// ZDRay static lights have an intensity that's set through the thing's alpha value
+			if (t.DynamicLightType.LightDef == GZGeneral.LightDef.SPOT_STATIC)
+			{
+				double intensity = t.Fields.GetValue("alpha", 1.0);
+				if (intensity != 1.0)
+				{
+					byte r = (byte)General.Clamp(color.r * intensity, 0.0, 255.0);
+					byte g = (byte)General.Clamp(color.g * intensity, 0.0, 255.0);
+					byte b = (byte)General.Clamp(color.b * intensity, 0.0, 255.0);
+					color = new PixelColor((byte)linealpha, r, g, b);
+				}
+			}
+
+
             if (highlight)
             {
                 color = General.Colors.Highlight.WithAlpha((byte)linealpha);
@@ -729,7 +751,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
             foreach (Thing t in things)
             {
                 GZGeneral.LightData ld = t.DynamicLightType;
-                if (ld == null) continue;
+                if (ld == null || ld.LightType == GZGeneral.LightType.SUN) continue;
 
                 if (ld.LightType != GZGeneral.LightType.SPOT)
                 {
diff --git a/Source/Core/GZBuilder/Data/ModelData.cs b/Source/Core/GZBuilder/Data/ModelData.cs
index c32998735078072d5a0854382b8d51d88e924126..5424ba5e6d7ec2a840a7bb8365be9f62b9d1e480 100755
--- a/Source/Core/GZBuilder/Data/ModelData.cs
+++ b/Source/Core/GZBuilder/Data/ModelData.cs
@@ -22,6 +22,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 
 		private ModelLoadState loadstate;
 		private Vector3f scale;
+		private Vector3f rotationcenter;
 		private Matrix transform;
         private Matrix transformrotation;
 		private Matrix transformstretched;
@@ -40,6 +41,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 		internal GZModel Model;
 
 		internal Vector3f Scale { get { return scale; } }
+		internal Vector3f RotationCenter { get { return rotationcenter; } set { rotationcenter = value; } }
 		internal Matrix Transform { get { /* return (General.Settings.GZStretchView ? transformstretched : transform); */ return transformstretched; } }
         internal Matrix TransformRotation { get { return transformrotation; } }
 		internal bool OverridePalette; // Used for voxel models only 
@@ -47,6 +49,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 		internal bool InheritActorPitch;
 		internal bool UseActorPitch;
 		internal bool UseActorRoll;
+		internal bool UseRotationCenter;
 
 		internal bool IsVoxel;
 
diff --git a/Source/Core/GZBuilder/GZGeneral.cs b/Source/Core/GZBuilder/GZGeneral.cs
index 2253af9e4cfe34312959aabea07641180f7bd053..759336c80abfddcc5c75abd02fc7c265403727b0 100755
--- a/Source/Core/GZBuilder/GZGeneral.cs
+++ b/Source/Core/GZBuilder/GZGeneral.cs
@@ -120,6 +120,24 @@ namespace CodeImp.DoomBuilder.GZBuilder
             [LightDefClass("vavoomlightcolor")]
             VAVOOM_COLORED,
 
+            [LightDefRenderStyle(LightRenderStyle.STATIC)]
+            [LightDefNum(9876)]
+            [LightDefClass("staticpointlight")]
+            [LightDefModifier(LightModifier.NORMAL)]
+            POINT_STATIC,
+
+            [LightDefRenderStyle(LightRenderStyle.STATIC)]
+            [LightDefNum(9881)]
+            [LightDefClass("staticspotlight")]
+            [LightDefModifier(LightModifier.NORMAL)]
+            SPOT_STATIC,
+
+            [LightDefRenderStyle(LightRenderStyle.NONE)]
+            [LightDefNum(9890)]
+            [LightDefClass("zdraysun")]
+            [LightDefModifier(LightModifier.NORMAL)]
+            SUN,
+
             UNKNOWN
         }
 
@@ -132,6 +150,7 @@ namespace CodeImp.DoomBuilder.GZBuilder
             ATTENUATED = 98,
             VAVOOM = 50,
             ADDITIVE = 25,
+            STATIC = 98, // Same as attenuated
             NONE = 0,
         }
 
@@ -148,7 +167,8 @@ namespace CodeImp.DoomBuilder.GZBuilder
         {
             POINT,
             SPOT,
-            VAVOOM
+            VAVOOM,
+            SUN
         }
 
         public static LightDefNum GetLightDefNum(LightDef d)
@@ -212,18 +232,23 @@ namespace CodeImp.DoomBuilder.GZBuilder
                     case LightDef.POINT_ADDITIVE:
                     case LightDef.POINT_SUBTRACTIVE:
                     case LightDef.POINT_ATTENUATED:
+                    case LightDef.POINT_STATIC:
                         LightType = LightType.POINT;
                         break;
                     case LightDef.SPOT_NORMAL:
                     case LightDef.SPOT_ADDITIVE:
                     case LightDef.SPOT_SUBTRACTIVE:
                     case LightDef.SPOT_ATTENUATED:
+                    case LightDef.SPOT_STATIC:
                         LightType = LightType.SPOT;
                         break;
                     case LightDef.VAVOOM_GENERIC:
                     case LightDef.VAVOOM_COLORED:
                         LightType = LightType.VAVOOM;
                         break;
+                    case LightDef.SUN:
+                        LightType = LightType.SUN;
+                        break;
                 }
             }
 
diff --git a/Source/Core/GZBuilder/md3/ModelReader.cs b/Source/Core/GZBuilder/md3/ModelReader.cs
index d30cd4412822b829854ff50916ad45c1fcc73004..42c716bf9756ce3a321e3f6692b64fd2059d1881 100755
--- a/Source/Core/GZBuilder/md3/ModelReader.cs
+++ b/Source/Core/GZBuilder/md3/ModelReader.cs
@@ -1425,8 +1425,11 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 								worldvertices.Clear();
 								polyindiceslist.Clear();
 							}
+
+							// Add texture name. It might be in quotes, so remove them.
+							// See https://github.com/jewalky/UltimateDoomBuilder/issues/758
 							if (fields.Length >= 2)
-								result.Skins.Add(fields[1]);
+								result.Skins.Add(fields[1].Replace("\"", ""));
 							
 							surfaceskinid++;
 							break;
@@ -1524,7 +1527,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.MD3
 			{
 				t.x = float.Parse(fields[0], CultureInfo.InvariantCulture);
 
-				if (fields.Length == 2)
+				if (fields.Length >= 2)
 					t.y = 1.0f - float.Parse(fields[1], CultureInfo.InvariantCulture);
 				else
 					t.y = 1.0f;
diff --git a/Source/Core/General/General.cs b/Source/Core/General/General.cs
index e82042f55219da96acb4e96813d21ecf56defc80..4f1b77df24392f122fcbec8d65943b50383bdd45 100755
--- a/Source/Core/General/General.cs
+++ b/Source/Core/General/General.cs
@@ -231,10 +231,14 @@ namespace CodeImp.DoomBuilder
 		private static bool delaymainwindow;
 		private static bool nosettings;
 		private static bool portablemode; //mxd
+		private static bool debugrenderdevice;
 
 		//misc
 		private static readonly Random random = new Random(); //mxd
 
+		// Toasts
+		private static ToastManager toastmanager;
+
 		#endregion
 
 		#region ================== Properties
@@ -275,9 +279,11 @@ namespace CodeImp.DoomBuilder
 		public static DataLocationList AutoLoadResources { get { return new DataLocationList(autoloadresources); } }
 		public static bool DelayMainWindow { get { return delaymainwindow; } }
 		public static bool NoSettings { get { return nosettings; } }
+		public static bool DebugRenderDevice { get { return debugrenderdevice; } }
 		public static EditingManager Editing { get { return editing; } }
 		public static ErrorLogger ErrorLogger { get { return errorlogger; } }
 		public static string CommitHash { get { return commithash; } } //mxd
+		public static ToastManager ToastManager { get => toastmanager; }
 
 		#endregion
 
@@ -606,7 +612,12 @@ namespace CodeImp.DoomBuilder
 
 			//mxd. Set CultureInfo
 			Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
-			
+
+			// biwa. If the default culture for threads is not set it'll screw with the culture
+			// in the FileSystemWatcher thread, which can result in incorrect string outputs
+			// See: https://github.com/jewalky/UltimateDoomBuilder/issues/858
+			CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
+
 			// Set current thread name
 			Thread.CurrentThread.Name = "Main Application";
 
@@ -659,7 +670,7 @@ namespace CodeImp.DoomBuilder
 			General.WriteLogLine("Temporary path:          \"" + temppath + "\"");
 			General.WriteLogLine("Local settings path:     \"" + settingspath + "\"");
 			General.WriteLogLine("Command-line arguments:  \"" + string.Join(" ", args) + "\""); //mxd
-			
+
 			// Load configuration
 			General.WriteLogLine("Loading program configuration...");
 			settings = new ProgramConfiguration();
@@ -698,11 +709,20 @@ namespace CodeImp.DoomBuilder
 					mainwindow.Show();
 					mainwindow.Update();
 				}
+
+				// Create the toast manager after the main windows, but before plugins are loaded,
+				// since the plugins can register toasts. Also register toasts for the core
+				toastmanager = new ToastManager(mainwindow.Display);
+				RegisterToasts();
 				
 				// Load plugin manager
 				General.WriteLogLine("Loading plugins...");
 				plugins = new PluginManager();
 				plugins.LoadAllPlugins();
+
+				// Register toasts from actions. This has to be done after all plugins are loaded
+				toastmanager.RegisterActions();
+				toastmanager.LoadSettings(settings.Config);
 				
 				// Load game configurations
 				General.WriteLogLine("Loading game configurations...");
@@ -798,6 +818,11 @@ namespace CodeImp.DoomBuilder
 			}
 		}
 
+		private static void RegisterToasts()
+		{
+			toastmanager.RegisterToast("resourcewarningsanderrors", "Resource warnings and errors", "When there are errors or warning while (re)loading the resources");
+		}
+
 		// This parses the command line arguments
 		private static void ParseCommandLineArgs(string[] args)
 		{
@@ -831,7 +856,7 @@ namespace CodeImp.DoomBuilder
 				else if(string.Compare(curarg, "-MAP", true) == 0)
 				{
 					// Store next arg as map name information
-					autoloadmap = argslist.Dequeue();
+					autoloadmap = argslist.Dequeue()?.ToUpperInvariant();
 				}
 				// Config name info?
 				else if((string.Compare(curarg, "-CFG", true) == 0) ||
@@ -956,6 +981,10 @@ namespace CodeImp.DoomBuilder
 					if(!string.IsNullOrEmpty(dl.location))
 						autoloadresources.Add(dl);
 				}
+				else if (string.Compare(curarg, "-DEBUGRENDERDEVICE", true) == 0)
+				{
+					debugrenderdevice = true;
+				}
 				// Every other arg
 				else
 				{
@@ -1496,7 +1525,12 @@ namespace CodeImp.DoomBuilder
 
 			// Show save as dialog
 			SaveFileDialog savefile = new SaveFileDialog();
+#if NO_WIN32
+			// No easy way to have case-insesitivity for non-Windows platforms
+			savefile.Filter = "Doom WAD Files (*.wad)|*.wad;*.Wad;*.wAd;*.WAd;*.waD;*.WaD;*.wAD;*.WAD";
+#else
 			savefile.Filter = "Doom WAD Files (*.wad)|*.wad";
+#endif
 			savefile.Title = "Save Map As";
 			savefile.AddExtension = true;
 			savefile.CheckPathExists = true;
@@ -1584,7 +1618,12 @@ namespace CodeImp.DoomBuilder
 
 			// Show save as dialog
 			SaveFileDialog savefile = new SaveFileDialog();
+#if NO_WIN32
+			// No easy way to have case-insesitivity for non-Windows platforms
+			savefile.Filter = "Doom WAD Files (*.wad)|*.wad;*.Wad;*.wAd;*.WAd;*.waD;*.WaD;*.wAD;*.WAD";
+#else
 			savefile.Filter = "Doom WAD Files (*.wad)|*.wad";
+#endif
 			savefile.Title = "Save Map Into";
 			savefile.AddExtension = true;
 			savefile.CheckPathExists = true;
@@ -1706,28 +1745,34 @@ namespace CodeImp.DoomBuilder
 		// This outputs log information
 		public static void WriteLogLine(string line)
 		{
+			lock (random)
+			{
 #if DEBUG
-			// Output to consoles
-			Console.WriteLine(line);
-			DebugConsole.WriteLine(DebugMessageType.LOG, line); //mxd
+				// Output to consoles
+				Console.WriteLine(line);
+				DebugConsole.WriteLine(DebugMessageType.LOG, line); //mxd
 #endif
-			// Write to log file
-			try { File.AppendAllText(logfile, line + Environment.NewLine); }
-			catch(Exception) { }
+				// Write to log file
+				try { File.AppendAllText(logfile, line + Environment.NewLine); }
+				catch (Exception) { }
+			}
 		}
 
 		// This outputs log information
 		public static void WriteLog(string text)
 		{
+			lock (random)
+			{
 #if DEBUG
-			// Output to consoles
-			Console.Write(text);
-			DebugConsole.Write(DebugMessageType.LOG, text);
+				// Output to consoles
+				Console.Write(text);
+				DebugConsole.Write(DebugMessageType.LOG, text);
 #endif
 
-			// Write to log file
-			try { File.AppendAllText(logfile, text); }
-			catch(Exception) { }
+				// Write to log file
+				try { File.AppendAllText(logfile, text); }
+				catch (Exception) { }
+			}
 		}
 		
 #endregion
diff --git a/Source/Core/General/Launcher.cs b/Source/Core/General/Launcher.cs
index 75df9c159a95c079e17eedf3e176bb6be6f32d42..ad71842b96426ce0bac7e68e76f5c6d9e04d4ab9 100755
--- a/Source/Core/General/Launcher.cs
+++ b/Source/Core/General/Launcher.cs
@@ -149,7 +149,7 @@ namespace CodeImp.DoomBuilder
 			//mxd. General.Map.FilePathName will be empty when a newly created map was not saved yet.
 			if(!string.IsNullOrEmpty(General.Map.FilePathName))
 			{
-				DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, General.Map.FilePathName, false, false, false);
+				DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, General.Map.FilePathName, false, false, false, null);
 				locations.Remove(maplocation); //If maplocation was already added as a resource, make sure it's singular and is last in the list
 				locations.Add(maplocation); 
 			}
diff --git a/Source/Core/General/MapManager.cs b/Source/Core/General/MapManager.cs
index ace4896608f7f8130015b6390b5aec51b3c7fdd7..47d9069a4196eeb53e14e7d9c2b19461119040af 100755
--- a/Source/Core/General/MapManager.cs
+++ b/Source/Core/General/MapManager.cs
@@ -295,12 +295,12 @@ namespace CodeImp.DoomBuilder
 			map = new MapSet();
 
 			// Create temp wadfile
-			DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false); //mxd
+			DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false, null); //mxd
 			General.WriteLogLine("Creating temporary file: " + templocation.location);
 #if DEBUG
-			tempwadreader = new WADReader(templocation, false, true);
+			tempwadreader = new WADReader(templocation, General.Map.Config, false, true);
 #else
-			try { tempwadreader = new WADReader(templocation, false, true); }
+			try { tempwadreader = new WADReader(templocation, General.Map.Config, false, true); }
 			catch(Exception e)
 			{
 				General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
@@ -389,12 +389,12 @@ namespace CodeImp.DoomBuilder
 			map = new MapSet();
 
 			// Create temp wadfile
-			DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false); //mxd
+			DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false, null); //mxd
 			General.WriteLogLine("Creating temporary file: " + templocation.location);
 #if DEBUG
-			tempwadreader = new WADReader(templocation, false, true);
+			tempwadreader = new WADReader(templocation, General.Map.Config, false, true);
 #else
-			try { tempwadreader = new WADReader(templocation, false, true); } catch(Exception e) 
+			try { tempwadreader = new WADReader(templocation, General.Map.Config, false, true); } catch(Exception e) 
 			{
 				General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
 				return false;
@@ -427,7 +427,7 @@ namespace CodeImp.DoomBuilder
 			// Load data manager
 			General.WriteLogLine("Loading data resources...");
 			data = new DataManager();
-			DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false);
+			DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false, null);
 			data.Load(configinfo.Resources, options.Resources, maplocation);
 
 			// Remove unused sectors
@@ -486,14 +486,14 @@ namespace CodeImp.DoomBuilder
 			WAD mapwad;
 
 			// Create temp wadfile
-			DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false); //mxd
+			DataLocation templocation = new DataLocation(DataLocation.RESOURCE_WAD, General.MakeTempFilename(temppath), false, false, false, null); //mxd
 			General.WriteLogLine("Creating temporary file: " + templocation.location);
 			if(tempwadreader != null) tempwadreader.Dispose();
 
 #if DEBUG
-			tempwadreader = new WADReader(templocation, false, true);
+			tempwadreader = new WADReader(templocation, General.Map.Config, false, true);
 #else
-			try { tempwadreader = new WADReader(templocation, false, true); } catch(Exception e) 
+			try { tempwadreader = new WADReader(templocation, General.Map.Config, false, true); } catch(Exception e) 
 			{
 				General.ShowErrorMessage("Error while creating a temporary wad file:\n" + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK);
 				return false;
@@ -2370,7 +2370,7 @@ namespace CodeImp.DoomBuilder
 			data = new DataManager();
 			if(!string.IsNullOrEmpty(filepathname)) 
 			{
-				DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false);
+				DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false, null);
 				data.Load(configinfo.Resources, options.Resources, maplocation);
 			}
 			else 
@@ -2643,6 +2643,9 @@ namespace CodeImp.DoomBuilder
 				undoredo.WithdrawUndo();
 			}
 
+			// Let the current editing mode know that we changed something
+			General.Editing.Mode.OnMapElementsChanged();
+
 			// Done
 			General.Interface.RedrawDisplay();
 			Cursor.Current = Cursors.Default;
diff --git a/Source/Core/General/ToastManager.cs b/Source/Core/General/ToastManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..945b40f30efbbedcda9fe49ed1a6f8a8c9f20633
--- /dev/null
+++ b/Source/Core/General/ToastManager.cs
@@ -0,0 +1,445 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Windows.Forms;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Controls;
+using CodeImp.DoomBuilder.IO;
+using CodeImp.DoomBuilder.Windows;
+
+#endregion
+
+namespace CodeImp.DoomBuilder
+{
+	public enum ToastType
+	{
+		INFO,
+		WARNING,
+		ERROR
+	}
+
+	public enum ToastAnchor
+	{
+		TOPLEFT = 1,
+		TOPRIGHT,
+		BOTTOMRIGHT,
+		BOTTOMLEFT
+	}
+
+	internal class ToastRegistryEntry
+	{
+		public bool Enabled { get; set; }
+		public string Name { get; set; }
+		public string Title { get; set; }
+		public string Description { get; set; }
+
+		public ToastRegistryEntry(string name, string title, string description, bool enabled)
+		{
+			Enabled = enabled;
+			Name = name;
+			Title = title;
+			Description = description;
+		}
+	}
+
+	public class ToastManager
+	{
+		#region ================== Static variables
+
+		public static readonly string TITLE_INFO = "Information";
+		public static readonly string TITLE_WARNING = "Warning";
+		public static readonly string TITLE_ERROR = "Error";
+
+		#endregion
+
+		#region ================== Variables
+
+		private List<ToastControl> toasts;
+		private Control bindcontrol;
+		private Timer timer;
+		private bool enabled;
+		private ToastAnchor anchor;
+		private long duration;
+		private Dictionary<string, ToastRegistryEntry> registry;
+
+		#endregion
+
+		#region ================== Properties
+
+		internal bool Enabled { get => enabled; set => enabled = value; }
+		internal ToastAnchor Anchor { get => anchor; set => anchor = value; }
+		internal long Duration { get => duration; set => duration = value; }
+		internal Dictionary<string, ToastRegistryEntry> Registry { get => registry; }
+
+		#endregion
+
+		#region ================== Constructors
+
+		public ToastManager(Control bindcontrol)
+		{
+			toasts = new List<ToastControl>();
+
+			this.bindcontrol = bindcontrol;
+
+			// Create the timer that will handle moving the toasts. Do not start it, though
+			timer = new Timer();
+			timer.Interval = 1; // Actually only called every 1/64 second, because Windows
+			timer.Tick += UpdateEvent;
+
+			// Create registry and load toasts from actions
+			registry = new Dictionary<string, ToastRegistryEntry>();
+		}
+
+
+
+		#endregion
+
+		#region ================== Events
+
+		private void UpdateEvent(object sender, EventArgs args)
+		{
+			if (toasts.Count == 0)
+				return;
+
+			// Go through all toasts and check if they should decay or not. Remove toasts that reached their lifetime
+			for (int i = toasts.Count - 1; i >= 0; i--)
+			{
+				toasts[i].CheckDecay();
+
+				if (!toasts[i].IsAlive())
+				{
+					bindcontrol.Controls.Remove(toasts[i]);
+					toasts[i].Dispose(); // Dispose, otherwise it'll leak
+					toasts.RemoveAt(i);
+				}
+			}
+
+			// No toasts left, so we should stop the timer
+			if (toasts.Count == 0)
+			{
+				timer.Stop();
+				return;
+			}
+
+			ToastControl ft = toasts[0];
+
+			// We only need to update the first toasts if it didn't reach it end position yet
+			bool needsupdate =
+				((anchor == ToastAnchor.TOPLEFT || anchor == ToastAnchor.TOPRIGHT) && ft.Location.Y != ft.Margin.Top)
+				||
+				((anchor == ToastAnchor.BOTTOMLEFT || anchor == ToastAnchor.BOTTOMRIGHT) && ft.Location.Y != bindcontrol.Height - ft.Height - ft.Margin.Bottom)
+			;
+
+			if(needsupdate)
+			{
+				int left;
+				int top;
+
+				if (anchor == ToastAnchor.TOPLEFT || anchor == ToastAnchor.BOTTOMLEFT)
+					left = ft.Margin.Right;
+				else
+					left = bindcontrol.Width - ft.Width - ft.Margin.Right;
+
+				// This moves the toast up or down a bit, depending on its anchor position. How fast this happens depends on
+				// the control's height, i.e. no matter the height a toast will always take the same time to slide in
+				// TODO: make it dependent on elapsed time
+				if (anchor == ToastAnchor.TOPLEFT || anchor == ToastAnchor.TOPRIGHT)
+					top = ft.Location.Y + ft.Height / 5;
+				else
+					top = ft.Location.Y - ft.Height / 5;
+
+				Point newLocation = new Point(left, top);
+
+				// If the movement overshot the final position snap it back to the final position
+				if ((anchor == ToastAnchor.BOTTOMLEFT || anchor == ToastAnchor.BOTTOMRIGHT) && newLocation.Y < bindcontrol.Height - ft.Height - ft.Margin.Bottom)
+					newLocation.Y = bindcontrol.Height - ft.Height - ft.Margin.Bottom;
+				else if ((anchor == ToastAnchor.TOPLEFT || anchor == ToastAnchor.TOPRIGHT) && newLocation.Y > ft.Margin.Top)
+					newLocation.Y = ft.Margin.Top;
+
+				ft.Location = newLocation;
+			}
+
+			if (toasts.Count > 1)
+			{
+				// Align all other toasts to their predecessor
+				for (int i = 1; i < toasts.Count; i++)
+				{
+					int top;
+
+					if (anchor == ToastAnchor.TOPLEFT || anchor == ToastAnchor.TOPRIGHT)
+						top = toasts[i - 1].Bottom + toasts[i - 1].Margin.Bottom;
+					else
+						top = toasts[i - 1].Location.Y - toasts[i].Height - toasts[i].Margin.Bottom;
+
+					toasts[i].Location = new Point(
+						ft.Location.X,
+						top
+					);
+				}
+			}
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		public void LoadSettings(Configuration cfg)
+		{
+			enabled = cfg.ReadSetting("toasts.enabled", true);
+			anchor = GetAnchorFromNumber(cfg.ReadSetting("toasts.anchor", 3));
+			duration = cfg.ReadSetting("toasts.duration", 3000);
+
+			// Make sure the duration is set to something sensible
+			if (duration <= 0)
+				duration = 3000;
+ 
+			IDictionary toastactionenableddict = cfg.ReadSetting("toasts.registry", new Hashtable());
+			foreach (string key in toastactionenableddict.Keys)
+			{
+				//string key = de.Key.ToString();
+
+				if (registry.ContainsKey(key))
+					registry[key].Enabled = cfg.ReadSetting($"toasts.registry.{key}", true);
+			}
+		}
+
+		/// <summary>
+		/// Writes the settings to a configuration with a prefix.
+		/// </summary>
+		/// <param name="cfg">The Configuration</param>
+		/// <param name="prefix">The prefix</param>
+		public void WriteSettings(Configuration cfg)
+		{
+			cfg.WriteSetting("toasts.enabled", Enabled);
+			cfg.WriteSetting("toasts.anchor", (int)Anchor);
+			cfg.WriteSetting("toasts.duration", Duration);
+
+			foreach (string key in Registry.Keys)
+			{
+				// true is the default value, so we only need to save it if it's false
+				if (Registry[key].Enabled == false)
+					cfg.WriteSetting($"toasts.registry.{key}", false);
+				else
+					cfg.DeleteSetting($"toasts.registry.{key}");
+			}
+		}
+
+		/// <summary>
+		/// Registers toast from all defined actions.
+		/// </summary>
+		public void RegisterActions()
+		{
+			foreach (Actions.Action action in General.Actions.GetAllActions().Where(a => a.RegisterToast))
+			{
+				if (!registry.ContainsKey(action.Name))
+					registry[action.Name] = new ToastRegistryEntry(action.Name, action.Title, action.Description, true);
+			}
+		}
+
+		/// <summary>
+		/// Registers a toast by name. Automatically prepends the assembly name.
+		/// </summary>
+		/// <param name="name">Name of the toast (without assembly)</param>
+		/// <param name="title">Title to show in the toast preferences dialog</param>
+		/// <param name="description">Description to show in the toast preferences dialog</param>
+		[MethodImpl(MethodImplOptions.NoInlining)]
+		public void RegisterToast(string name, string title, string description)
+		{
+			string fullname = Assembly.GetCallingAssembly().GetName().Name.ToLowerInvariant() + $"_{name}";
+
+			if (registry.ContainsKey(fullname))
+			{
+				General.WriteLogLine($"Tried to register toast \"{fullname}\", but it is already registered");
+				return;
+			}
+
+			registry[fullname] = new ToastRegistryEntry(fullname, title, description, true);
+		}
+
+		/// <summary>
+		/// Gets the ToastAnchor from a number. Makes sure the input is valid, otherwise returns a default.
+		/// </summary>
+		/// <param name="number">The number</param>
+		/// <returns>The appropriate ToastAnchor, or BOTTOMRIGHT if input is not valid</returns>
+		public static ToastAnchor GetAnchorFromNumber(int number)
+		{
+			return Enum.IsDefined(typeof(ToastAnchor), number) ? (ToastAnchor)number : ToastAnchor.BOTTOMRIGHT;
+		}
+
+		/// <summary>
+		/// Shows a new toast.
+		/// </summary>
+		/// <param name="type">Toast type</param>
+		/// <param name="message">The message body of the toast</param>
+		public void ShowToast(ToastType type, string title, string message, string shortmessage = "")
+		{
+			StatusType st = type == ToastType.INFO ? StatusType.Info : StatusType.Warning;
+
+			if (!enabled)
+			{
+				General.Interface.DisplayStatus(new StatusInfo(st, shortmessage));
+				return;
+			}
+
+			if (type == ToastType.WARNING)
+				title = "Warning";
+			else if (type == ToastType.ERROR)
+				title = "Error";
+
+			CreateToast(type, title, message);
+		}
+
+		/// <summary>
+		/// Shows a new toast. Deducts the title from the type.
+		/// </summary>
+		/// <param name="name">Name of the toast</param>
+		/// <param name="type">Type of the toast</param>
+		/// <param name="message">Message to show</param>
+		/// <param name="statusinfo">StatusInfo to use when toasts are disabled</param>
+		[MethodImpl(MethodImplOptions.NoInlining)]
+		public void ShowToast(string name, ToastType type, string message, StatusInfo statusinfo)
+		{
+			string fullname = Assembly.GetCallingAssembly().GetName().Name.ToLowerInvariant() + $"_{name}";
+			string title = "Information";
+
+			if (type == ToastType.WARNING)
+				title = "Warning";
+			else if (type == ToastType.ERROR)
+				title = "Error";
+
+			CreateToast(fullname, type, title, message, statusinfo);
+		}
+
+		/// <summary>
+		/// Shows a new toast.
+		/// </summary>
+		/// <param name="name">Name of the toast</param>
+		/// <param name="type">Type of the toast</param>
+		/// <param name="title">Title to show</param>
+		/// <param name="message">Message to show</param>
+		/// <param name="statusinfo">StatusInfo to use when toasts are disabled</param>
+		[MethodImpl(MethodImplOptions.NoInlining)]
+		public void ShowToast(string name, ToastType type, string title, string message, StatusInfo statusinfo)
+		{
+			string fullname = Assembly.GetCallingAssembly().GetName().Name.ToLowerInvariant() + $"_{name}";
+
+			CreateToast(fullname, type, title, message, statusinfo);
+		}
+
+		/// <summary>
+		/// Shows a new toast.
+		/// </summary>
+		/// <param name="name">Name of the toast</param>
+		/// <param name="type">Type of the toast</param>
+		/// <param name="title">Title to show</param>
+		/// <param name="message">Message to show</param>
+		/// <param name="shortmessage">Message to show in the status bar if toasts are disabled. Should not include line breaks</param>
+		[MethodImpl(MethodImplOptions.NoInlining)]
+		public void ShowToast(string name, ToastType type, string title, string message, string shortmessage = null)
+		{
+			string fullname = Assembly.GetCallingAssembly().GetName().Name.ToLowerInvariant() + $"_{name}";
+			StatusType st = type == ToastType.INFO ? StatusType.Info : StatusType.Warning;
+
+			if (string.IsNullOrWhiteSpace(shortmessage))
+				shortmessage = message;
+
+			CreateToast(fullname, type, title, message, new StatusInfo(st, shortmessage));
+		}
+
+		/// <summary>
+		/// Creates a toast.
+		/// </summary>
+		/// <param name="fullname">Full name (i.e. assembly and toast name) of the toast</param>
+		/// <param name="type">Type of the toast</param>
+		/// <param name="title">Title to show</param>
+		/// <param name="message">Message to show</param>
+		/// <param name="statusinfo">StatusInfo to use when toasts are disabled</param>
+		private void CreateToast(string fullname, ToastType type, string title, string message, StatusInfo statusinfo)
+		{
+			if (!enabled || registry[fullname]?.Enabled == false)
+			{
+				General.Interface.DisplayStatus(statusinfo);
+				return;
+			}
+
+			if (!registry.ContainsKey(fullname))
+			{
+				General.ErrorLogger.Add(ErrorType.Warning, $"Toast setting for \"{fullname}\" is not in the registry. Defaulting to show the toast.");
+			}
+			else if (registry[fullname].Enabled == false)
+			{
+				General.Interface.DisplayStatus(statusinfo);
+				return;
+			}
+
+			CreateToast(type, title, message);
+		}
+
+		/// <summary>
+		/// Creates a toast.
+		/// </summary>
+		/// <param name="type">Type of the toast</param>
+		/// <param name="title">Title to show</param>
+		/// <param name="message">Message to show</param>
+		private void CreateToast(ToastType type, string title, string message)
+		{ 
+			ToastControl tc = new ToastControl(type, title, message, duration);
+
+			// Set the initial y position of the control so that it's outside of the control the toast manager is bound to.
+			// No need to care about the x position, since that will be set in the update event anyway
+			if (anchor == ToastAnchor.TOPLEFT || anchor == ToastAnchor.TOPRIGHT)
+				tc.Location = new Point(0, -tc.Height);
+			else
+				tc.Location = new Point(0, bindcontrol.Height);
+
+			toasts.Insert(0, tc);
+			bindcontrol.Controls.Add(tc);
+
+			// Need to set the toast to be at the front, otherwise the new control would be behind the control the toast manager
+			// is bound to
+			bindcontrol.Controls.SetChildIndex(tc, 0);
+
+			// Start the timer so that the toast is moved into view
+			if (!timer.Enabled)
+				timer.Start();
+
+			// Play a sound for warnings and errors
+			if (type == ToastType.WARNING)
+				General.MessageBeep(MessageBeepType.Warning);
+			else if (type == ToastType.ERROR)
+				General.MessageBeep(MessageBeepType.Error);
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Core/Geometry/Line2D.cs b/Source/Core/Geometry/Line2D.cs
index b8cb9b67bd66c233f28e77d548f39771fd51cd19..416574667ac6a85199c2bbf0a8bf465342a84a8f 100755
--- a/Source/Core/Geometry/Line2D.cs
+++ b/Source/Core/Geometry/Line2D.cs
@@ -151,7 +151,7 @@ namespace CodeImp.DoomBuilder.Geometry
 			double div = (y4 - y3) * (v2.x - v1.x) - (x4 - x3) * (v2.y - v1.y);
 
 			// Can this be tested?
-			if(div != 0.0f)
+			if(div != 0.0)
 			{
 				// Calculate the intersection distance from the line
 				u_line = ((x4 - x3) * (v1.y - y3) - (y4 - y3) * (v1.x - x3)) / div;
@@ -160,7 +160,7 @@ namespace CodeImp.DoomBuilder.Geometry
 				u_ray = ((v2.x - v1.x) * (v1.y - y3) - (v2.y - v1.y) * (v1.x - x3)) / div;
 
 				// Return if intersecting
-				if(bounded && (u_ray < 0.0f || u_ray > 1.0f || u_line < 0.0f || u_line > 1.0f)) return false; //mxd
+				if(bounded && (u_ray < 0.0 || u_ray > 1.0 || u_line < 0.0 || u_line > 1.0)) return false; //mxd
 				return true;
 			}
 
diff --git a/Source/Core/IO/DirectoryFilesList.cs b/Source/Core/IO/DirectoryFilesList.cs
index 426e45e7ce66f0b67f1e360fe25305f2e9d1ac90..cf5e33666b50b91dc43091aa10a076d0ce89db76 100755
--- a/Source/Core/IO/DirectoryFilesList.cs
+++ b/Source/Core/IO/DirectoryFilesList.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
 
 #endregion
@@ -68,7 +69,7 @@ namespace CodeImp.DoomBuilder.IO
 		#region ================== Constructor / Disposer
 
 		// Constructor to fill list from directory and optionally subdirectories
-		public DirectoryFilesList(string path, bool subdirectories)
+		public DirectoryFilesList(string path, GameConfiguration config, bool subdirectories)
 		{
 			path = Path.GetFullPath(path);
 			string[] files = Directory.GetFiles(path, "*", subdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
@@ -85,25 +86,28 @@ namespace CodeImp.DoomBuilder.IO
 					continue;
 				}
 
-				if(General.Map.Config.IgnoredFileExtensions.Contains(e.extension)) continue;
-
-				bool skipfolder = false;
-				foreach(string ef in General.Map.Config.IgnoredDirectoryNames)
+				if (config != null)
 				{
-					if(e.path.StartsWith(ef + Path.DirectorySeparatorChar))
+					if (config.IgnoredFileExtensions.Contains(e.extension)) continue;
+
+					bool skipfolder = false;
+					foreach (string ef in config.IgnoredDirectoryNames)
 					{
-						skipfolder = true;
-						break;
+						if (e.path.StartsWith(ef + Path.DirectorySeparatorChar))
+						{
+							skipfolder = true;
+							break;
+						}
 					}
+					if (skipfolder) continue;
 				}
-				if(skipfolder) continue;
 
 				AddOrReplaceEntry(e);
 			}
 		}
 
 		// Constructor for custom list
-		public DirectoryFilesList(string resourcename, ICollection<DirectoryFileEntry> sourceentries)
+		public DirectoryFilesList(string resourcename, GameConfiguration config, bool silent, ICollection<DirectoryFileEntry> sourceentries)
 		{
 			entries = new Dictionary<string, DirectoryFileEntry>(sourceentries.Count, new PathEqualityComparer());
 			wadentries = new List<string>();
@@ -115,22 +119,25 @@ namespace CodeImp.DoomBuilder.IO
 					continue;
 				}
 
-				if(General.Map.Config.IgnoredFileExtensions.Contains(e.extension)) continue;
-
-				bool skipfolder = false;
-				foreach(string ef in General.Map.Config.IgnoredDirectoryNames)
+				if (config != null)
 				{
-					if(e.path.StartsWith(ef + Path.DirectorySeparatorChar))
+					if (config.IgnoredFileExtensions.Contains(e.extension)) continue;
+
+					bool skipfolder = false;
+					foreach (string ef in config.IgnoredDirectoryNames)
 					{
-						skipfolder = true;
-						break;
+						if (e.path.StartsWith(ef + Path.DirectorySeparatorChar))
+						{
+							skipfolder = true;
+							break;
+						}
 					}
+					if (skipfolder) continue;
 				}
-				if(skipfolder) continue;
 
 				if(entries.ContainsKey(e.filepathname))
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Resource \"" + resourcename + "\" contains multiple files with the same filename. See: \"" + e.filepathname + "\"");
+					if (!silent) General.ErrorLogger.Add(ErrorType.Warning, "Resource \"" + resourcename + "\" contains multiple files with the same filename. See: \"" + e.filepathname + "\"");
 					continue;
 				}
 
diff --git a/Source/Core/IO/WAD.cs b/Source/Core/IO/WAD.cs
index 4c74bf341ed1c2f8e1b602beb88edb955cc5afc7..b5d5997cb593549e12bcb0d099771250e1f183e1 100755
--- a/Source/Core/IO/WAD.cs
+++ b/Source/Core/IO/WAD.cs
@@ -25,7 +25,7 @@ using System.IO;
 
 namespace CodeImp.DoomBuilder.IO
 {
-	internal class WAD : IDisposable
+	public class WAD : IDisposable
 	{
 		#region ================== Constants
 
diff --git a/Source/Core/Map/MapSet.cs b/Source/Core/Map/MapSet.cs
index 83a673fdb4e925b1f762921ee473a86166ee23bf..3194a1c8fb0d06fcbb8d18bdf21f3730b23f020e 100755
--- a/Source/Core/Map/MapSet.cs
+++ b/Source/Core/Map/MapSet.cs
@@ -3710,7 +3710,7 @@ namespace CodeImp.DoomBuilder.Map
 			// Return result
 			return closest;
 		}
-		
+
 		#endregion
 
 		#region ================== Tools
@@ -4172,7 +4172,7 @@ namespace CodeImp.DoomBuilder.Map
 			// Return result
 			return closest;
 		}
-		
+
 		// This performs sidedefs compression
 		// Note: Only use this for saving, because this messes up the expected data structure horribly.
 		internal void CompressSidedefs()
diff --git a/Source/Core/Map/Sector.cs b/Source/Core/Map/Sector.cs
index 62054f8730ee8be61b1434ade57d57736cfcbef1..af750648b45e6dacef1bc8983532ba60cff29132 100755
--- a/Source/Core/Map/Sector.cs
+++ b/Source/Core/Map/Sector.cs
@@ -409,7 +409,8 @@ namespace CodeImp.DoomBuilder.Map
 				General.Plugins.OnSectorCeilingSurfaceUpdate(this, ref updateinfo.ceilvertices);
 				updateinfo.floortexture = longfloortexname;
 				updateinfo.ceiltexture = longceiltexname;
-                updateinfo.desaturation = this.Desaturation;
+				updateinfo.hidden = IsFlagSet("hidden");
+				updateinfo.desaturation = this.Desaturation;
 
                 // Update surfaces
                 General.Map.CRenderer2D.Surfaces.UpdateSurfaces(surfaceentries, updateinfo);
@@ -429,7 +430,9 @@ namespace CodeImp.DoomBuilder.Map
 			flatvertices.CopyTo(updateinfo.floorvertices, 0);
 			General.Plugins.OnSectorFloorSurfaceUpdate(this, ref updateinfo.floorvertices);
 			updateinfo.floortexture = longfloortexname;
-			
+			updateinfo.hidden = IsFlagSet("hidden");
+			updateinfo.desaturation = this.Desaturation;
+
 			// Update entry
 			General.Map.CRenderer2D.Surfaces.UpdateSurfaces(surfaceentries, updateinfo);
 			General.Map.CRenderer2D.Surfaces.UnlockBuffers();
@@ -445,7 +448,8 @@ namespace CodeImp.DoomBuilder.Map
 			flatvertices.CopyTo(updateinfo.ceilvertices, 0);
 			General.Plugins.OnSectorCeilingSurfaceUpdate(this, ref updateinfo.ceilvertices);
 			updateinfo.ceiltexture = longceiltexname;
-            updateinfo.desaturation = this.Desaturation;
+			updateinfo.hidden = IsFlagSet("hidden");
+			updateinfo.desaturation = this.Desaturation;
 			
 			// Update entry
 			General.Map.CRenderer2D.Surfaces.UpdateSurfaces(surfaceentries, updateinfo);
@@ -516,6 +520,10 @@ namespace CodeImp.DoomBuilder.Map
 				BeforePropsChange();
 
 				flags[flagname] = value;
+
+				// [XA] TODO: de-hardcode this special case thing
+				if(flagname == "hidden")
+					updateneeded = true;
 			}
 		}
 
diff --git a/Source/Core/Properties/Resources.Designer.cs b/Source/Core/Properties/Resources.Designer.cs
index 0b487770cc582ba0df5eec07a61be9346d49afec..f4a71b5804fe03b892a802c86e0ed2903b263389 100755
--- a/Source/Core/Properties/Resources.Designer.cs
+++ b/Source/Core/Properties/Resources.Designer.cs
@@ -19,7 +19,7 @@ namespace CodeImp.DoomBuilder.Properties {
     // class via a tool like ResGen or Visual Studio.
     // To add or remove a member, edit your .ResX file then rerun ResGen
     // with the /str option, or rebuild your VS project.
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
     internal class Resources {
@@ -810,6 +810,16 @@ namespace CodeImp.DoomBuilder.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized resource of type System.Drawing.Bitmap.
+        /// </summary>
+        internal static System.Drawing.Bitmap Loader {
+            get {
+                object obj = ResourceManager.GetObject("Loader", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized resource of type System.Drawing.Bitmap.
         /// </summary>
diff --git a/Source/Core/Properties/Resources.resx b/Source/Core/Properties/Resources.resx
index b52d9d290d57189c06fd655fda9b7bddd7806424..a08407a32a00c08c1764620bff67feb673568e18 100755
--- a/Source/Core/Properties/Resources.resx
+++ b/Source/Core/Properties/Resources.resx
@@ -622,4 +622,7 @@
   <data name="UDB2" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\UDB2.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
+  <data name="Loader" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Loader.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
 </root>
\ No newline at end of file
diff --git a/Source/Core/Rendering/IRenderer2D.cs b/Source/Core/Rendering/IRenderer2D.cs
index 0eb9c7af112dc49a93642572de576ab75cde2f9b..0e475a83c53cf7f1cebec1718f47f238ad8f488f 100755
--- a/Source/Core/Rendering/IRenderer2D.cs
+++ b/Source/Core/Rendering/IRenderer2D.cs
@@ -64,9 +64,9 @@ namespace CodeImp.DoomBuilder.Rendering
 		void PlotLinedefSet(ICollection<Linedef> linedefs);
 		void PlotSector(Sector s);
 		void PlotSector(Sector s, PixelColor c);
-		void PlotVertex(Vertex v, int colorindex);
-		void PlotVertexAt(Vector2D v, int colorindex);
-		void PlotVerticesSet(ICollection<Vertex> vertices);
+		void PlotVertex(Vertex v, int colorindex, bool checkMode = true);
+		void PlotVertexAt(Vector2D v, int colorindex, bool checkMode = true);
+		void PlotVerticesSet(ICollection<Vertex> vertices, bool checkMode = true);
 		void RenderThing(Thing t, PixelColor c, float alpha);
 		void RenderThingSet(ICollection<Thing> things, float alpha);
 		void RenderRectangle(RectangleF rect, float bordersize, PixelColor c, bool transformrect);
diff --git a/Source/Core/Rendering/Plotter.cs b/Source/Core/Rendering/Plotter.cs
index 809d2e602ee12d10bfcbbdc988b7eb1c6194a2f1..a36f359115995394526463f655d292b35c337c57 100755
--- a/Source/Core/Rendering/Plotter.cs
+++ b/Source/Core/Rendering/Plotter.cs
@@ -128,23 +128,26 @@ namespace CodeImp.DoomBuilder.Rendering
                     for (int xp = x1; xp <= x2; xp++)
                         pixels[yp * width + xp] = c;
 
-                // Vertical edges
-                for (int yp = y1 + 1; yp <= y2 - 1; yp++)
+                if (!General.Settings.FlatShadeVertices)
                 {
-                    pixels[yp * width + x1] = l;
-                    pixels[yp * width + x2] = d;
-                }
+                    // Vertical edges
+                    for (int yp = y1 + 1; yp <= y2 - 1; yp++)
+                    {
+                        pixels[yp * width + x1] = l;
+                        pixels[yp * width + x2] = d;
+                    }
 
-                // Horizontal edges
-                for (int xp = x1 + 1; xp <= x2 - 1; xp++)
-                {
-                    pixels[y1 * width + xp] = l;
-                    pixels[y2 * width + xp] = d;
-                }
+                    // Horizontal edges
+                    for (int xp = x1 + 1; xp <= x2 - 1; xp++)
+                    {
+                        pixels[y1 * width + xp] = l;
+                        pixels[y2 * width + xp] = d;
+                    }
 
-                // Corners
-                pixels[y2 * width + x2] = d;
-                pixels[y1 * width + x1] = l;
+                    // Corners
+                    pixels[y2 * width + x2] = d;
+                    pixels[y1 * width + x1] = l;
+                }
             }
             /*
 			else
diff --git a/Source/Core/Rendering/Presentation.cs b/Source/Core/Rendering/Presentation.cs
index b9bca7486e9ebc4d5e92b2746ed9e0f131a09d5b..767c0687900ddc32a4692c7e786b7f4b7894eab0 100755
--- a/Source/Core/Rendering/Presentation.cs
+++ b/Source/Core/Rendering/Presentation.cs
@@ -39,6 +39,9 @@ namespace CodeImp.DoomBuilder.Rendering
 		// Static properties
 		public static Presentation Standard { get { return standard; } }
 		public static Presentation Things { get { return things; } }
+
+		// Public properties
+		public bool SkipHiddenSectors { get; set; } // Skip drawing sectors with the "hidden" flag set (i.e. UDMF's "hide on textured automap" property)
 		
 		// Variables
 		protected internal List<PresentLayer> layers;
@@ -48,12 +51,14 @@ namespace CodeImp.DoomBuilder.Rendering
 		{
 			// Initialize
 			layers = new List<PresentLayer>();
+			SkipHiddenSectors = false;
 		}
 
 		// Copy constructor
 		public Presentation(Presentation p)
 		{
 			layers = new List<PresentLayer>(p.layers);
+			SkipHiddenSectors = p.SkipHiddenSectors;
 		}
 
 		// This creates the static instances
diff --git a/Source/Core/Rendering/RenderDevice.cs b/Source/Core/Rendering/RenderDevice.cs
index c7088273c1a04f0ebb4df0115c2d89c5544d1c68..a781ce781c0d4c4f529db72e026c53d0af978a80 100755
--- a/Source/Core/Rendering/RenderDevice.cs
+++ b/Source/Core/Rendering/RenderDevice.cs
@@ -74,7 +74,8 @@ namespace CodeImp.DoomBuilder.Rendering
             // volte: classic rendering
             DeclareUniform(UniformName.drawPaletted, "drawPaletted", UniformType.Int);
             DeclareUniform(UniformName.colormapSize, "colormapSize", UniformType.Vec2i);
-            DeclareUniform(UniformName.lightLevel, "lightLevel", UniformType.Int);
+            DeclareUniform(UniformName.doomlightlevels, "doomlightlevels", UniformType.Int);
+            DeclareUniform(UniformName.sectorLightLevel, "sectorLightLevel", UniformType.Int);
 
             // 2d fsaa
             CompileShader(ShaderName.display2d_fsaa, "display2d.shader", "display2d_fsaa");
@@ -133,7 +134,7 @@ namespace CodeImp.DoomBuilder.Rendering
                 display = (IntPtr)xplatui.GetField("DisplayHandle", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
             }
 
-            Handle = RenderDevice_New(display, RenderTarget.Handle);
+            Handle = RenderDevice_New(display, RenderTarget.Handle, General.DebugRenderDevice);
             if (Handle == IntPtr.Zero)
             {
                 StringBuilder sb = new StringBuilder(4096);
@@ -570,7 +571,7 @@ namespace CodeImp.DoomBuilder.Rendering
         IntPtr Handle;
 
         [DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
-        static extern IntPtr RenderDevice_New(IntPtr display, IntPtr window);
+        static extern IntPtr RenderDevice_New(IntPtr display, IntPtr window, bool debug);
 
         [DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
         static extern void RenderDevice_Delete(IntPtr handle);
@@ -802,7 +803,8 @@ namespace CodeImp.DoomBuilder.Rendering
 		slopeHandleLength,
         drawPaletted,
         colormapSize,
-        lightLevel
+        sectorLightLevel,
+        doomlightlevels
     }
 
     public enum VertexFormat : int { Flat, World }
diff --git a/Source/Core/Rendering/Renderer2D.cs b/Source/Core/Rendering/Renderer2D.cs
index ab00f951a321ede5c9f394060af969263fc545cb..b8757de5f3ce2fc56e1796e01642a7aff70a0531 100755
--- a/Source/Core/Rendering/Renderer2D.cs
+++ b/Source/Core/Rendering/Renderer2D.cs
@@ -19,7 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
-using System.Net;
+using System.Threading.Tasks;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Data;
@@ -125,10 +125,22 @@ namespace CodeImp.DoomBuilder.Rendering
 		public SurfaceManager Surfaces { get { return surfaces; } }
 		public RectangleF Viewport { get { return viewport; } } //mxd
 
+		private bool ShouldRenderVertices
+		{
+			get
+			{
+				if (!(General.Editing.Mode is ClassicMode mode))
+				{
+					return true;
+				}
+				return mode.AlwaysShowVertices || General.Settings.AlwaysShowVertices;
+			}
+		}
+
 		#endregion
 
 		#region ================== Constructor / Disposer
-		
+
 		// Constructor
 		internal Renderer2D(RenderDevice graphics) : base(graphics)
 		{
@@ -544,16 +556,46 @@ namespace CodeImp.DoomBuilder.Rendering
 					return new PixelColor(255, (byte)t.Args[1], (byte)t.Args[2], (byte)t.Args[3]);
 				if (t.DynamicLightType.LightType == GZGeneral.LightType.SPOT)
 				{
+					PixelColor color;
+
 					if (t.Fields.ContainsKey("arg0str"))
 					{
-						PixelColor pc;
-						ZDoom.ZDTextParser.GetColorFromString(t.Fields["arg0str"].Value.ToString(), out pc);
-						pc.a = 255;
-						return pc;
+						
+						ZDoom.ZDTextParser.GetColorFromString(t.Fields["arg0str"].Value.ToString(), out color);
+						color.a = 255;
+						
 					}
-					return new PixelColor(255, (byte)((t.Args[0] & 0xFF0000) >> 16), (byte)((t.Args[0] & 0x00FF00) >> 8), (byte)((t.Args[0] & 0x0000FF)));
+					else 
+						color = new PixelColor(255, (byte)((t.Args[0] & 0xFF0000) >> 16), (byte)((t.Args[0] & 0x00FF00) >> 8), (byte)((t.Args[0] & 0x0000FF)));
+
+					// ZDRay static lights have an intensity that's set through the thing's alpha value
+					if (t.DynamicLightType.LightDef == GZGeneral.LightDef.SPOT_STATIC)
+					{
+						double intensity = t.Fields.GetValue("alpha", 1.0);
+						if (intensity != 1.0)
+						{
+							byte r = (byte)General.Clamp(color.r * intensity, 0.0, 255.0);
+							byte g = (byte)General.Clamp(color.g * intensity, 0.0, 255.0);
+							byte b = (byte)General.Clamp(color.b * intensity, 0.0, 255.0);
+							color = new PixelColor(255, r, g, b);
+						}
+					}
+
+					return color;
+				}
+
+				// Point light
+				if (t.DynamicLightType.LightDef == GZGeneral.LightDef.POINT_STATIC)
+				{
+					// ZDRay static lights have an intensity that's set through the thing's alpha value
+					double intensity = t.Fields.GetValue("alpha", 1.0);
+					byte r = (byte)General.Clamp(t.Args[0] * intensity, 0.0, 255.0);
+					byte g = (byte)General.Clamp(t.Args[1] * intensity, 0.0, 255.0);
+					byte b = (byte)General.Clamp(t.Args[2] * intensity, 0.0, 255.0);
+					return new PixelColor(255, r, g, b);
 				}
-				return new PixelColor(255, (byte)t.Args[0], (byte)t.Args[1], (byte)t.Args[2]);
+				else
+					return new PixelColor(255, (byte)t.Args[0], (byte)t.Args[1], (byte)t.Args[2]);
 			}
 
 			return t.Color;
@@ -1502,7 +1544,12 @@ namespace CodeImp.DoomBuilder.Rendering
 							Matrix modelscale = Matrix.Scaling((float)sx, (float)sx, (float)sy);
 							Matrix rotation = Matrix.RotationY((float)-t.RollRad) * Matrix.RotationX((float)-t.PitchRad) * Matrix.RotationZ((float)t.Angle);
 							Matrix position = Matrix.Translation((float)screenpos.x, (float)screenpos.y, 0.0f);
-							Matrix world = General.Map.Data.ModeldefEntries[t.Type].Transform * modelscale * rotation * viewscale * position;
+							Matrix world;
+
+							if (General.Map.Data.ModeldefEntries[t.Type].UseRotationCenter)
+								world = General.Map.Data.ModeldefEntries[t.Type].Transform * modelscale * Matrix.Translation(-General.Map.Data.ModeldefEntries[t.Type].RotationCenter) * rotation * Matrix.Translation(General.Map.Data.ModeldefEntries[t.Type].RotationCenter) * viewscale * position;
+							else
+								world = General.Map.Data.ModeldefEntries[t.Type].Transform * modelscale * rotation * viewscale * position;
 
 							SetThings2DTransformSettings(world);
 
@@ -1563,24 +1610,25 @@ namespace CodeImp.DoomBuilder.Rendering
 				graphics.SetAlphaBlendEnable(false);
 				graphics.SetAlphaTestEnable(false);
 				graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
-                SetWorldTransformation(true);
+				graphics.SetUniform(UniformName.desaturation, 0.0f);
+				SetWorldTransformation(true);
 				SetDisplay2DSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
 					
 				// Prepare for rendering
 				switch(viewmode)
 				{
 					case ViewMode.Brightness:
-						surfaces.RenderSectorBrightness(yviewport);
+						surfaces.RenderSectorBrightness(yviewport, present.SkipHiddenSectors);
 						surfaces.RenderSectorSurfaces(graphics);
 						break;
 							
 					case ViewMode.FloorTextures:
-						surfaces.RenderSectorFloors(yviewport);
+						surfaces.RenderSectorFloors(yviewport, present.SkipHiddenSectors);
 						surfaces.RenderSectorSurfaces(graphics);
 						break;
 							
 					case ViewMode.CeilingTextures:
-						surfaces.RenderSectorCeilings(yviewport);
+						surfaces.RenderSectorCeilings(yviewport, present.SkipHiddenSectors);
 						surfaces.RenderSectorSurfaces(graphics);
 						break;
 				}
@@ -2081,43 +2129,87 @@ namespace CodeImp.DoomBuilder.Rendering
 		// This renders a set of linedefs
 		public void PlotLinedefSet(ICollection<Linedef> linedefs)
 		{
-			// Go for all linedefs
-			foreach(Linedef l in linedefs)
+			// biwa. Code duplication because the performance hit from the overhead of calling PlotLinedef in a loop causes reduced FPS.
+			// Telling the compiler to agressively inline PlotLinedef seems to mostly alleviate the problem, but I'm not sure how reliable that is
+			if (General.Settings.ParallelizedLinedefPlotting)
 			{
-				// Transform vertex coordinates
-				Vector2D v1 = l.Start.Position.GetTransformed(translatex, translatey, scale, -scale);
-				Vector2D v2 = l.End.Position.GetTransformed(translatex, translatey, scale, -scale);
+				// Go for all linedefs
+				Parallel.ForEach(linedefs, l =>
+				{
+					// Transform vertex coordinates
+					Vector2D v1 = l.Start.Position.GetTransformed(translatex, translatey, scale, -scale);
+					Vector2D v2 = l.End.Position.GetTransformed(translatex, translatey, scale, -scale);
 
-				//mxd. Should we bother?
-				double lengthsq = (v2 - v1).GetLengthSq();
-				if(lengthsq < minlinelength) continue; //mxd
+					//mxd. Should we bother?
+					double lengthsq = (v2 - v1).GetLengthSq();
+					if (lengthsq < minlinelength) return; //mxd
 
-				// Determine color
-				PixelColor c = DetermineLinedefColor(l);
+					// Determine color
+					PixelColor c = DetermineLinedefColor(l);
 
-				// Draw line. mxd: added 3d-floor indication
-				if(l.ExtraFloorFlag && General.Settings.GZMarkExtraFloors)
-					plotter.DrawLine3DFloor((int)v1.x, TransformY((int)v1.y), (int)v2.x, TransformY((int)v2.y), ref c, General.Colors.ThreeDFloor);
-				else
-					plotter.DrawLineSolid((int)v1.x, TransformY((int)v1.y), (int)v2.x, TransformY((int)v2.y), ref c);
+					// Draw line. mxd: added 3d-floor indication
+					if (l.ExtraFloorFlag && General.Settings.GZMarkExtraFloors)
+						plotter.DrawLine3DFloor((int)v1.x, TransformY((int)v1.y), (int)v2.x, TransformY((int)v2.y), ref c, General.Colors.ThreeDFloor);
+					else
+						plotter.DrawLineSolid((int)v1.x, TransformY((int)v1.y), (int)v2.x, TransformY((int)v2.y), ref c);
+
+					//mxd. Should we bother?
+					if (lengthsq < minlinenormallength) return; //mxd
+
+					// Calculate normal indicator
+					double mx = (v2.x - v1.x) * 0.5f;
+					double my = (v2.y - v1.y) * 0.5f;
+
+					// Draw normal indicator
+					plotter.DrawLineSolid((int)(v1.x + mx), TransformY((int)(v1.y + my)),
+										 (int)((v1.x + mx) - (my * l.LengthInv) * linenormalsize),
+										 TransformY((int)((v1.y + my) + (mx * l.LengthInv) * linenormalsize)), ref c);
+				});
+			}
+			else
+			{
+				// Go for all linedefs
+				foreach (Linedef l in linedefs)
+				{
+					// Transform vertex coordinates
+					Vector2D v1 = l.Start.Position.GetTransformed(translatex, translatey, scale, -scale);
+					Vector2D v2 = l.End.Position.GetTransformed(translatex, translatey, scale, -scale);
+
+					//mxd. Should we bother?
+					double lengthsq = (v2 - v1).GetLengthSq();
+					if (lengthsq < minlinelength) continue; //mxd
 
-				//mxd. Should we bother?
-				if(lengthsq < minlinenormallength) continue; //mxd
+					// Determine color
+					PixelColor c = DetermineLinedefColor(l);
 
-				// Calculate normal indicator
-				double mx = (v2.x - v1.x) * 0.5f;
-				double my = (v2.y - v1.y) * 0.5f;
+					// Draw line. mxd: added 3d-floor indication
+					if (l.ExtraFloorFlag && General.Settings.GZMarkExtraFloors)
+						plotter.DrawLine3DFloor((int)v1.x, TransformY((int)v1.y), (int)v2.x, TransformY((int)v2.y), ref c, General.Colors.ThreeDFloor);
+					else
+						plotter.DrawLineSolid((int)v1.x, TransformY((int)v1.y), (int)v2.x, TransformY((int)v2.y), ref c);
+
+					//mxd. Should we bother?
+					if (lengthsq < minlinenormallength) continue; //mxd
 
-				// Draw normal indicator
-				plotter.DrawLineSolid((int)(v1.x + mx), TransformY((int)(v1.y + my)),
-									  (int)((v1.x + mx) - (my * l.LengthInv) * linenormalsize),
-									  TransformY((int)((v1.y + my) + (mx * l.LengthInv) * linenormalsize)), ref c);
+					// Calculate normal indicator
+					double mx = (v2.x - v1.x) * 0.5f;
+					double my = (v2.y - v1.y) * 0.5f;
+
+					// Draw normal indicator
+					plotter.DrawLineSolid((int)(v1.x + mx), TransformY((int)(v1.y + my)),
+										  (int)((v1.x + mx) - (my * l.LengthInv) * linenormalsize),
+										  TransformY((int)((v1.y + my) + (mx * l.LengthInv) * linenormalsize)), ref c);
+				}
 			}
 		}
 
 		// This renders a single vertex
-		public void PlotVertex(Vertex v, int colorindex)
+		public void PlotVertex(Vertex v, int colorindex, bool checkMode = true)
 		{
+			if (checkMode && !ShouldRenderVertices)
+			{
+				return;
+			}
 			// Transform vertex coordinates
 			Vector2D nv = v.Position.GetTransformed(translatex, translatey, scale, -scale);
 
@@ -2126,8 +2218,13 @@ namespace CodeImp.DoomBuilder.Rendering
 		}
 
 		// This renders a single vertex at specified coordinates
-		public void PlotVertexAt(Vector2D v, int colorindex)
+		public void PlotVertexAt(Vector2D v, int colorindex, bool checkMode = true)
 		{
+			if (checkMode && !ShouldRenderVertices)
+			{
+				return;
+			}
+			
 			// Transform vertex coordinates
 			Vector2D nv = v.GetTransformed(translatex, translatey, scale, -scale);
 
@@ -2136,10 +2233,43 @@ namespace CodeImp.DoomBuilder.Rendering
 		}
 		
 		// This renders a set of vertices
-		public void PlotVerticesSet(ICollection<Vertex> vertices)
+		public void PlotVerticesSet(ICollection<Vertex> vertices, bool checkMode = true)
 		{
-			// Go for all vertices
-			foreach(Vertex v in vertices) PlotVertex(v, DetermineVertexColor(v));
+			if (checkMode && !ShouldRenderVertices)
+			{
+				return;
+			}
+
+			// biwa. Code duplication because the performance hit from the overhead of calling PlotLinedef in a loop causes reduced FPS.
+			// Telling the compiler to agressively inline PlotLinedef seems to mostly alleviate the problem, but I'm not sure how reliable that is
+			if (General.Settings.ParallelizedVertexPlotting)
+			{
+				// Go for all vertices
+				Parallel.ForEach(vertices, v =>
+				{
+					// Transform vertex coordinates
+					Vector2D nv = v.Position.GetTransformed(translatex, translatey, scale, -scale);
+
+					int colorindex = DetermineVertexColor(v);
+
+					// Draw pixel here
+					plotter.DrawVertexSolid((int)nv.x, TransformY((int)nv.y), vertexsize, ref General.Colors.Colors[colorindex], ref General.Colors.BrightColors[colorindex], ref General.Colors.DarkColors[colorindex]);
+				});
+			}
+			else
+			{
+				// Go for all vertices
+				foreach (Vertex v in vertices)
+				{
+					// Transform vertex coordinates
+					Vector2D nv = v.Position.GetTransformed(translatex, translatey, scale, -scale);
+
+					int colorindex = DetermineVertexColor(v);
+					
+					// Draw pixel here
+					plotter.DrawVertexSolid((int)nv.x, TransformY((int)nv.y), vertexsize, ref General.Colors.Colors[colorindex], ref General.Colors.BrightColors[colorindex], ref General.Colors.DarkColors[colorindex]);
+				}
+			}
 		}
 
 		#endregion
diff --git a/Source/Core/Rendering/Renderer3D.cs b/Source/Core/Rendering/Renderer3D.cs
index e845921a0caee143b8ff416a651aabd6e87462ae..f1c72145fba8442bb5001d75e1743fe1f954d9f4 100755
--- a/Source/Core/Rendering/Renderer3D.cs
+++ b/Source/Core/Rendering/Renderer3D.cs
@@ -281,6 +281,13 @@ namespace CodeImp.DoomBuilder.Rendering
 			
 			// Make the projection matrix
 			projection = Matrix.PerspectiveFov(fovy, aspect, PROJ_NEAR_PLANE, General.Settings.ViewDistance);
+
+			// We also need to re-create the 2D matrices, otherwise the corsshair will be distorted after the viewport is resized. See
+			// https://github.com/jewalky/UltimateDoomBuilder/issues/321
+			// and
+			// https://github.com/jewalky/UltimateDoomBuilder/issues/777
+			CreateMatrices2D();
+			crosshairverts = null;
 		}
 		
 		// This creates matrices for a camera view
@@ -355,6 +362,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			graphics.SetUniform(UniformName.fogsettings, new Vector4f(-1.0f));
 			graphics.SetUniform(UniformName.fogcolor, General.Colors.Background.ToColorValue());
 			graphics.SetUniform(UniformName.texturefactor, new Color4(1f, 1f, 1f, 1f));
+			graphics.SetUniform(UniformName.doomlightlevels, General.Map.Config.DoomLightLevels);
             graphics.SetUniform(UniformName.highlightcolor, new Color4()); //mxd
             TextureFilter texFilter = (!General.Settings.ClassicRendering && General.Settings.VisualBilinear) ? TextureFilter.Linear : TextureFilter.Nearest;
             MipmapFilter mipFilter = General.Settings.ClassicRendering ? MipmapFilter.None : MipmapFilter.Linear;
@@ -600,6 +608,7 @@ namespace CodeImp.DoomBuilder.Rendering
 					case GZGeneral.LightRenderStyle.VAVOOM: lightOffsets[0]++; break;
 					case GZGeneral.LightRenderStyle.ADDITIVE: lightOffsets[2]++; break;
                     case GZGeneral.LightRenderStyle.SUBTRACTIVE: lightOffsets[3]++; break;
+					case GZGeneral.LightRenderStyle.STATIC: // Static lights look the same as attenuated lights
 					default: lightOffsets[1]++; break; // attenuated
 				}
 			}
@@ -950,7 +959,7 @@ namespace CodeImp.DoomBuilder.Rendering
 						}
 						
 						// volte: set sector light level for classic rendering mode
-						graphics.SetUniform(UniformName.lightLevel, sector.Sector.Brightness);
+						graphics.SetUniform(UniformName.sectorLightLevel, sector.Sector.Brightness);
 
 						//mxd. Set variables for fog rendering?
 						if(wantedshaderpass > ShaderName.world3d_p7)
@@ -1023,7 +1032,7 @@ namespace CodeImp.DoomBuilder.Rendering
 							world = CreateThingPositionMatrix(t);
 
 							//mxd. If current thing is light - set it's color to light color
-							if(t.LightType != null && t.LightType.LightInternal && !fullbrightness && !General.Settings.ClassicRendering) 
+							if(t.LightType != null && t.LightType.LightInternal && t.LightType.LightType != GZGeneral.LightType.SUN && !fullbrightness && !General.Settings.ClassicRendering) 
 							{
 								wantedshaderpass += 4; // Render using one of passes, which uses World3D.VertexColor
 								vertexcolor = t.LightColor;
@@ -1060,7 +1069,7 @@ namespace CodeImp.DoomBuilder.Rendering
 							// Set the colors to use
 							if (t.Thing.Sector != null)
 							{
-								graphics.SetUniform(UniformName.lightLevel, t.Thing.Sector.Brightness);
+								graphics.SetUniform(UniformName.sectorLightLevel, t.Thing.Sector.Brightness);
 								graphics.SetUniform(UniformName.sectorfogcolor, t.Thing.Sector.FogColor);
 							}
                             graphics.SetUniform(UniformName.vertexColor, vertexcolor);
@@ -1594,7 +1603,10 @@ namespace CodeImp.DoomBuilder.Rendering
 				Matrix modelscale = Matrix.Scaling((float)sx, (float)sx, (float)sy);
 				Matrix modelrotation = Matrix.RotationY((float)-t.Thing.RollRad) * Matrix.RotationX((float)-t.Thing.PitchRad) * Matrix.RotationZ((float)t.Thing.Angle);
 
-				world = General.Map.Data.ModeldefEntries[t.Thing.Type].Transform * modelscale * modelrotation * t.Position;
+				if(General.Map.Data.ModeldefEntries[t.Thing.Type].UseRotationCenter)
+					world = General.Map.Data.ModeldefEntries[t.Thing.Type].Transform * modelscale * Matrix.Translation(-General.Map.Data.ModeldefEntries[t.Thing.Type].RotationCenter) * modelrotation * Matrix.Translation(General.Map.Data.ModeldefEntries[t.Thing.Type].RotationCenter) * t.Position;
+				else
+					world = General.Map.Data.ModeldefEntries[t.Thing.Type].Transform * modelscale * modelrotation  * t.Position;
                 graphics.SetUniform(UniformName.world, world);
 
                 // Set variables for fog rendering
diff --git a/Source/Core/Rendering/Shaders/ShaderCompiler.cs b/Source/Core/Rendering/Shaders/ShaderCompiler.cs
index 9dcdfa247f822c8a1005ed236b6af73d5f184aff..427235f125f4ffa323e6ec035fa66539e6f78174 100755
--- a/Source/Core/Rendering/Shaders/ShaderCompiler.cs
+++ b/Source/Core/Rendering/Shaders/ShaderCompiler.cs
@@ -156,8 +156,17 @@ namespace CodeImp.DoomBuilder.Rendering.Shaders
                     output += string.Format("layout(location = {0}) ", location);
                 }
 
-                output += prefix + " ";
-                output += field.TypeName;
+                string typeName = field.TypeName;
+                string flat = "";
+
+                if (typeName.EndsWith("_flat"))
+                {
+                    flat = "flat ";
+                    typeName = typeName.Remove(typeName.Length - 5, 5);
+                }
+
+                output += flat + prefix + " ";
+                output += typeName;
 
                 if (field.ArrayDimensions != null)
                 {
diff --git a/Source/Core/Rendering/SurfaceEntry.cs b/Source/Core/Rendering/SurfaceEntry.cs
index add84fe2869270e4c8037be8b33bc3b4b3c8cbfb..305c820e06c6e3e710c23a387cc014c9984507fd 100755
--- a/Source/Core/Rendering/SurfaceEntry.cs
+++ b/Source/Core/Rendering/SurfaceEntry.cs
@@ -49,6 +49,9 @@ namespace CodeImp.DoomBuilder.Rendering
 		public long floortexture;
 		public long ceiltexture;
 
+		// Sector flags
+		public bool hidden; // sector is hidden on textured automap
+
         //
         public double desaturation;
 		
diff --git a/Source/Core/Rendering/SurfaceManager.cs b/Source/Core/Rendering/SurfaceManager.cs
index 5fbec73b2e8435341fef040ce8c85fb94e94c248..5585371067397901436eebea4c9ab7aafd1f0998 100755
--- a/Source/Core/Rendering/SurfaceManager.cs
+++ b/Source/Core/Rendering/SurfaceManager.cs
@@ -405,6 +405,7 @@ namespace CodeImp.DoomBuilder.Rendering
 					Array.Copy(update.ceilvertices, update.numvertices - vertsremaining, e.ceilvertices, 0, vertsinentry);
 					e.floortexture = update.floortexture;
 					e.ceiltexture = update.ceiltexture;
+					e.hidden = update.hidden;
                     e.desaturation = update.desaturation;
 					
 					entries.Add(e);
@@ -429,6 +430,7 @@ namespace CodeImp.DoomBuilder.Rendering
 						e.ceiltexture = update.ceiltexture;
 					}
 
+					e.hidden = update.hidden;
                     e.desaturation = update.desaturation;
 
                     vertsremaining -= e.numvertices;
@@ -510,7 +512,7 @@ namespace CodeImp.DoomBuilder.Rendering
 		#region ================== Rendering
 		
 		// This renders all sector floors
-		internal void RenderSectorFloors(RectangleF viewport)
+		internal void RenderSectorFloors(RectangleF viewport, bool skipHidden)
 		{
 			surfaces = new Dictionary<ImageData, List<SurfaceEntry>>();
 			surfacevertexoffsetmul = 0;
@@ -521,14 +523,14 @@ namespace CodeImp.DoomBuilder.Rendering
 			{
 				foreach(SurfaceEntry entry in set.Value.entries)
 				{
-					if(entry.bbox.IntersectsWith(viewport))
+					if(SurfaceEntryIsVisible(entry, viewport, skipHidden))
 						AddSurfaceEntryForRendering(entry, entry.floortexture);
 				}
 			}
 		}
 		
 		// This renders all sector ceilings
-		internal void RenderSectorCeilings(RectangleF viewport)
+		internal void RenderSectorCeilings(RectangleF viewport, bool skipHidden)
 		{
 			surfaces = new Dictionary<ImageData, List<SurfaceEntry>>();
 			surfacevertexoffsetmul = 1;
@@ -539,14 +541,14 @@ namespace CodeImp.DoomBuilder.Rendering
 			{
 				foreach(SurfaceEntry entry in set.Value.entries)
 				{
-					if(entry.bbox.IntersectsWith(viewport))
+					if(SurfaceEntryIsVisible(entry, viewport, skipHidden))
 						AddSurfaceEntryForRendering(entry, entry.ceiltexture);
 				}
 			}
 		}
 
 		// This renders all sector brightness levels
-		internal void RenderSectorBrightness(RectangleF viewport)
+		internal void RenderSectorBrightness(RectangleF viewport, bool skipHidden)
 		{
 			surfaces = new Dictionary<ImageData, List<SurfaceEntry>>();
 			surfacevertexoffsetmul = 0;
@@ -557,12 +559,21 @@ namespace CodeImp.DoomBuilder.Rendering
 			{
 				foreach(SurfaceEntry entry in set.Value.entries)
 				{
-					if(entry.bbox.IntersectsWith(viewport))
+					if(SurfaceEntryIsVisible(entry, viewport, skipHidden))
 						AddSurfaceEntryForRendering(entry, 0);
 				}
 			}
 		}
 
+		// Checks to see if a particular surface entry is visible in the viewport
+		private bool SurfaceEntryIsVisible(SurfaceEntry entry, RectangleF viewport, bool skipHidden)
+		{
+			if (skipHidden && entry.hidden)
+				return false;
+
+			return entry.bbox.IntersectsWith(viewport);
+		}
+
 		// This adds a surface entry to the list of surfaces
 		private void AddSurfaceEntryForRendering(SurfaceEntry entry, long longimagename)
 		{
diff --git a/Source/Core/Rendering/SurfaceUpdate.cs b/Source/Core/Rendering/SurfaceUpdate.cs
index 969f8bc89f59ea6bc9bc003d450cc24d138a3537..6964da3c4a25f055838e0380bd585399c90b20fe 100755
--- a/Source/Core/Rendering/SurfaceUpdate.cs
+++ b/Source/Core/Rendering/SurfaceUpdate.cs
@@ -36,8 +36,11 @@ namespace CodeImp.DoomBuilder.Rendering
 		public long floortexture;
 		public long ceiltexture;
 
-        //
-        public double desaturation;
+		// Sector flags
+		public bool hidden;
+
+		//
+		public double desaturation;
 		
 		// Constructor
 		internal SurfaceUpdate(int numvertices, bool updatefloor, bool updateceiling)
@@ -49,6 +52,7 @@ namespace CodeImp.DoomBuilder.Rendering
 			this.floorvertices = (updatefloor ? new FlatVertex[numvertices] : null);
 			this.ceilvertices = (updateceiling ? new FlatVertex[numvertices] : null);
 
+			this.hidden = false;
             this.desaturation = 0f;
 		}
 	}
diff --git a/Source/Core/Rendering/TextLabel.cs b/Source/Core/Rendering/TextLabel.cs
index 125ae7f0b830ff561853f2d0fdfb6a1f99789842..0fd59691c83e541e350e34ce1bf5829af5b762f8 100755
--- a/Source/Core/Rendering/TextLabel.cs
+++ b/Source/Core/Rendering/TextLabel.cs
@@ -467,6 +467,21 @@ namespace CodeImp.DoomBuilder.Rendering
 
 		// This (re)loads the resources
 		public void ReloadResource() { }
+
+		/// <summary>
+		/// Checks if the whole label is in the viewport.
+		/// </summary>
+		/// <returns>true if the label in in the viewport, false if it isn't</returns>
+		public bool IsInViewport()
+		{
+			(double width, double height) = texturesize.IsEmpty ? ( 0, 0 ) : (texturesize.Width, texturesize.Height);
+
+			return
+				location.x >= (General.Map.CRenderer2D.Viewport.X - width) &&
+				location.x < (General.Map.CRenderer2D.Viewport.X + General.Map.CRenderer2D.Viewport.Width + width) &&
+				location.y <= (General.Map.CRenderer2D.Viewport.Y - height) &&
+				location.y > (General.Map.CRenderer2D.Viewport.Y + General.Map.CRenderer2D.Viewport.Height + height);
+		}
 		
 		#endregion
 	}
diff --git a/Source/Core/Resources/Actions.cfg b/Source/Core/Resources/Actions.cfg
index 9690d2340ab6215e29aed559975d2ac9f152da26..4ce2062aecbb40182cd8d46c9ec871a6a38e4a88 100755
--- a/Source/Core/Resources/Actions.cfg
+++ b/Source/Core/Resources/Actions.cfg
@@ -1157,7 +1157,17 @@ togglefixedthingsscale //mxd
 	allowmouse = false;
 	allowscroll = false;
 }
-
+	
+togglealwaysshowvertices
+{
+	title = "Toggle Always Show Vertices";
+	category = "view";
+	description = "When enabled, vertices will always be drawn, regardless of the current mode.";
+	allowkeys = true;
+	allowmouse = false;
+	allowscroll = false;
+}
+	
 togglebrightness //mxd
 {
 	title = "Toggle Full Brightness";
@@ -1167,6 +1177,7 @@ togglebrightness //mxd
 	allowmouse = true;
 	allowscroll = true;
 	default = 66; //B
+	registertoast = true;
 }
 
 togglehighlight
@@ -1178,6 +1189,7 @@ togglehighlight
 	allowmouse = true;
 	allowscroll = true;
 	default = 72;	// H
+	registertoast = true;
 }
 
 visualselect
@@ -1317,6 +1329,7 @@ gztoggleeventlines
 	allowmouse = false;
 	allowscroll = false;
 	default = 73;
+	registertoast = true;
 }
 
 gztogglevisualvertices
@@ -1349,6 +1362,7 @@ gztoggleenhancedrendering
 	allowmouse = true;
 	allowscroll = false;
 	default = 9; //Tab
+	registertoast = true;
 }
 
 //////////////////////////////
@@ -1433,3 +1447,13 @@ placethingatcursor
 	allowscroll = false;
 	default = 131076;
 }
+
+opencommandpalette
+{
+	title = "Open Command Palette";
+	category = "tools";
+	description = "Opens the command palette.";
+	allowkeys = true;
+	allowmouse = true;
+	allowscroll = true;
+}
\ No newline at end of file
diff --git a/Source/Core/Resources/Loader.gif b/Source/Core/Resources/Loader.gif
new file mode 100644
index 0000000000000000000000000000000000000000..0ca31e02ad293c5a84d04dd0774d4a166383dd8a
Binary files /dev/null and b/Source/Core/Resources/Loader.gif differ
diff --git a/Source/Core/Resources/UDMF_UI.cfg b/Source/Core/Resources/UDMF_UI.cfg
old mode 100755
new mode 100644
index 5df1c6985cbbff5688d84cd65b38040f6c2dd540..9329d38829870784cade87b78d28dee181a1d834
--- a/Source/Core/Resources/UDMF_UI.cfg
+++ b/Source/Core/Resources/UDMF_UI.cfg
@@ -33,6 +33,12 @@ uifields
 		offsety_bottom = 1;
 		light = 0;
 		lightabsolute = 3;
+		light_top = 0;
+		lightabsolute_top = 3;
+		light_mid = 0;
+		lightabsolute_mid = 3;
+		light_bottom = 0;
+		lightabsolute_bottom = 3;
 		repeatcnt = 0;
 	}
 	
@@ -96,7 +102,7 @@ uifields
 		arg0str = 2;
 		conversation = 0;
 		gravity = 1;
-		health = 0;
+		health = 1;
 		fillcolor = 0;
 		alpha = 1;
 		score = 0;
diff --git a/Source/Core/Resources/world3d.shader b/Source/Core/Resources/world3d.shader
index 0e54d50bd84f84d0adfa6748cae26844a571366e..1ef9944ad474c618eb719dd7726a55d82ed739f9 100755
--- a/Source/Core/Resources/world3d.shader
+++ b/Source/Core/Resources/world3d.shader
@@ -22,7 +22,8 @@ uniforms
 	// classic lighting related
 	int drawPaletted;
 	ivec2 colormapSize;
-	int lightLevel;
+	int doomlightlevels;
+	int sectorLightLevel;
 
 	// dynamic light related
 	vec4 lightPosAndRadius[64];
@@ -45,8 +46,18 @@ functions
         vec4 colormapColor = texture(texture2, uv);
         return colormapColor;
     }
+    
+    int lightLevelFromVertexColor(vec3 color)
+    {
+        float result = max(max(color.r, color.g), color.b) * 255;
+        if (result < 192 && doomlightlevels > 0) {
+            // correct for darkening in Doom light mode
+            result = -0.666667 * (-96 - result);
+        }
+        return int(result);
+    }
         
-    int classicLightLevelToColorMapOffset(int lightLevel, vec3 position, vec3 normal)
+    int classicLightLevelToColorMapOffset(int lightLevel, vec3 position, vec3 normal, bool hvmod)
     {
         const int LIGHTLEVELS = 16;
         const int LIGHTSEGSHIFT = 4;
@@ -58,13 +69,15 @@ functions
         
         bool isFlat = abs(dot(normal, vec3(0, 0, 1))) > 1e-3; 
         
-        if (abs(dot(normal, vec3(0, 1, 0))) < 1e-3)
-        {
-            scaledLightLevel++;
-        }
-        else if (abs(dot(normal, vec3(1, 0, 0))) < 1e-3)
-        {
-            scaledLightLevel--;
+        if (hvmod) {
+            if (abs(dot(normal, vec3(0, 1, 0))) < 1e-3)
+            {
+                scaledLightLevel++;
+            }
+            else if (abs(dot(normal, vec3(1, 0, 0))) < 1e-3)
+            {
+                scaledLightLevel--;
+            }
         }
         
         int level;
@@ -86,7 +99,7 @@ functions
             level = int(startmap - (1280.0f / dist)) + 1;
         }
         
-        
+       
         if (level < 0) level = 0;
         if (level >= NUMCOLORMAPS) level = NUMCOLORMAPS - 1;
         return level;
@@ -187,6 +200,8 @@ shader world3d_main
 		vec3 PosW;
 		vec3 Normal;
 		vec4 viewpos;
+		vec3_flat flatNormal;
+		vec4_flat flatColor;
 	}
 
 	out
@@ -202,6 +217,8 @@ shader world3d_main
 		v2f.Color = in.Color;
 		v2f.UV = in.TextureCoordinate;
 		v2f.Normal = normalize((modelnormal * vec4(in.Normal, 1.0)).xyz);
+		v2f.flatColor = in.Color;
+		v2f.flatNormal = normalize(in.Normal);
 	}
 	
 	fragment
@@ -444,7 +461,8 @@ shader world3d_classic extends world3d_main
 		    vec4 color = texture(texture1, v2f.UV);
 		    int entry = int(color.r * 255);
 		    float alpha = color.a;
-            int colorMapOffset = classicLightLevelToColorMapOffset(lightLevel, v2f.PosW, v2f.Normal);
+		    int lightLevel = lightLevelFromVertexColor(v2f.flatColor.rgb);
+            int colorMapOffset = classicLightLevelToColorMapOffset(lightLevel, v2f.PosW, v2f.flatNormal, false);
             pcolor = getColorMappedColor(entry, colorMapOffset);
             pcolor.a = alpha;
 		}
@@ -472,8 +490,9 @@ shader world3d_classic_highlight extends world3d_main
             vec4 color = texture(texture1, v2f.UV);
             int entry = int(color.r * 255);
             float alpha = color.a;
-            int modifiedLightLevel = max(lightLevel, 128);	
-            int colorMapOffset = classicLightLevelToColorMapOffset(modifiedLightLevel, v2f.PosW, v2f.Normal);
+            int lightLevel = lightLevelFromVertexColor(v2f.flatColor.rgb);
+            int modifiedLightLevel = max(lightLevel, 128);	 
+            int colorMapOffset = classicLightLevelToColorMapOffset(modifiedLightLevel, v2f.PosW, v2f.flatNormal, false);
             pcolor = getColorMappedColor(entry, colorMapOffset);
             pcolor.a = alpha;
         }
diff --git a/Source/Core/Types/PolyobjectNumberHandler.cs b/Source/Core/Types/PolyobjectNumberHandler.cs
index 5dc67ad407bb4aa0d990144dfdb0d6ca92670613..ce0c00f795391291c20545adc549094ed901a816 100755
--- a/Source/Core/Types/PolyobjectNumberHandler.cs
+++ b/Source/Core/Types/PolyobjectNumberHandler.cs
@@ -31,7 +31,7 @@ namespace CodeImp.DoomBuilder.Types
 			ponumslist.Sort((a, b) => -1 * a.CompareTo(b));
 
 			// Create enum items
-			foreach(int ponum in ponums)
+			foreach(int ponum in ponumslist)
 				polist.Add(new EnumItem(ponum.ToString(), ponum.ToString()));
 
 			return polist;
diff --git a/Source/Core/VisualModes/VisualMode.cs b/Source/Core/VisualModes/VisualMode.cs
index 75955385fa5d1d621b6704d292aa953cfe9fa1aa..b7c7882eeaf40df3cb1e93aa1843e75108151fb8 100755
--- a/Source/Core/VisualModes/VisualMode.cs
+++ b/Source/Core/VisualModes/VisualMode.cs
@@ -1382,7 +1382,16 @@ namespace CodeImp.DoomBuilder.VisualModes
 		public void ToggleHighlight()
 		{
 			General.Settings.UseHighlight = !General.Settings.UseHighlight;
-			General.Interface.DisplayStatus(StatusType.Action, "Highlight is now " + (General.Settings.UseHighlight ? "ON" : "OFF") + ".");
+
+			string shorttext = "Highlight is now " + (General.Settings.UseHighlight ? "ON" : "OFF") + ".";
+			string text = shorttext;
+
+			string key = Actions.Action.GetShortcutKeyDesc(General.Actions.Current.ShortcutKey);
+
+			if (!string.IsNullOrEmpty(key))
+				text += $" Press '{key}' to toggle.";
+
+			General.ToastManager.ShowToast("togglehighlight", ToastType.INFO, "Changed highlight", text, shorttext);
 		}
 
 		#endregion
diff --git a/Source/Core/VisualModes/VisualThing.cs b/Source/Core/VisualModes/VisualThing.cs
index f54e01f51f60ab8e6e02574dda62fc2899c35c87..cec2d0f72d1afa2763f4a7a67a7dd068a803c805 100755
--- a/Source/Core/VisualModes/VisualThing.cs
+++ b/Source/Core/VisualModes/VisualThing.cs
@@ -668,7 +668,7 @@ namespace CodeImp.DoomBuilder.VisualModes
 		public void UpdateLight()
 		{
             lightType = thing.DynamicLightType;
-            if (lightType == null)
+            if (lightType == null || lightType.LightType == GZGeneral.LightType.SUN)
                 return;
             GZGeneral.LightData ld = lightType;
 			if (ld.LightDef != GZGeneral.LightDef.VAVOOM_GENERIC &&
@@ -678,11 +678,14 @@ namespace CodeImp.DoomBuilder.VisualModes
                 {
                     if (ld.LightDef != GZGeneral.LightDef.POINT_SUBTRACTIVE) // normal, additive, attenuated
                     {
+						// ZDRay static lights have an intensity that's set through the thing's alpha value
+						float intensity = ld.LightRenderStyle == GZGeneral.LightRenderStyle.STATIC ? (float)thing.Fields.GetValue("alpha", 1.0) : 1.0f;
+
                         //lightColor.Alpha used in shader to perform some calculations based on light type
                         lightColor = new Color4(
-                            thing.Args[0] / DYNLIGHT_INTENSITY_SCALER,
-                            thing.Args[1] / DYNLIGHT_INTENSITY_SCALER,
-                            thing.Args[2] / DYNLIGHT_INTENSITY_SCALER,
+                            thing.Args[0] / DYNLIGHT_INTENSITY_SCALER * intensity,
+                            thing.Args[1] / DYNLIGHT_INTENSITY_SCALER * intensity,
+                            thing.Args[2] / DYNLIGHT_INTENSITY_SCALER * intensity,
                             (float)ld.LightRenderStyle / 100.0f);
                     }
                     else // negative
@@ -714,10 +717,13 @@ namespace CodeImp.DoomBuilder.VisualModes
 
                     if (ld.LightDef != GZGeneral.LightDef.SPOT_SUBTRACTIVE)
                     {
-                        lightColor = new Color4(
-                            c1 / DYNLIGHT_INTENSITY_SCALER,
-                            c2 / DYNLIGHT_INTENSITY_SCALER,
-                            c3 / DYNLIGHT_INTENSITY_SCALER,
+						// ZDRay static lights have an intensity that's set through the thing's alpha value
+						float intensity = ld.LightRenderStyle == GZGeneral.LightRenderStyle.STATIC ? (float)thing.Fields.GetValue("alpha", 1.0) : 1.0f;
+
+						lightColor = new Color4(
+                            c1 / DYNLIGHT_INTENSITY_SCALER * intensity,
+                            c2 / DYNLIGHT_INTENSITY_SCALER * intensity,
+                            c3 / DYNLIGHT_INTENSITY_SCALER * intensity,
                             (float)ld.LightRenderStyle / 100.0f);
                     }
                     else
diff --git a/Source/Core/Windows/ChangeMapForm.cs b/Source/Core/Windows/ChangeMapForm.cs
index 8b5e276c1c238b6e76e2ff15471904ad180cb16a..2e0ce4192c228ab49680d0fb6135aff43bd0093e 100755
--- a/Source/Core/Windows/ChangeMapForm.cs
+++ b/Source/Core/Windows/ChangeMapForm.cs
@@ -111,7 +111,15 @@ namespace CodeImp.DoomBuilder.Windows
 					{
 						// Count the lump when it is marked as required
 						string lumpname = wadfile.Lumps[scanindex + checkoffset].Name;
-						if(ci.Configuration.ReadSetting("maplumpnames." + lumpname + ".required", false))
+
+						// Lump cannot be present in current map format, fail this check
+						if (ci.Configuration.ReadSetting("maplumpnames." + lumpname + ".forbidden", false))
+						{
+							lumpsfound = -1;
+							break;
+						}
+
+						if (ci.Configuration.ReadSetting("maplumpnames." + lumpname + ".required", false))
 							lumpsfound++;
 
 						// Check the next lump
diff --git a/Source/Core/Windows/ConfigForm.Designer.cs b/Source/Core/Windows/ConfigForm.Designer.cs
index cd27a54d6e063a2009c846033455d7f1ce66f69a..5b4bd93d3debbf11180f719e357dea8d8fbeb8ea 100755
--- a/Source/Core/Windows/ConfigForm.Designer.cs
+++ b/Source/Core/Windows/ConfigForm.Designer.cs
@@ -28,839 +28,886 @@ namespace CodeImp.DoomBuilder.Windows
 		/// </summary>
 		private void InitializeComponent()
 		{
-			this.components = new System.ComponentModel.Container();
-			System.Windows.Forms.Label label5;
-			System.Windows.Forms.Label label6;
-			System.Windows.Forms.Label label3;
-			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigForm));
-			System.Windows.Forms.Label label2;
-			System.Windows.Forms.Label label7;
-			System.Windows.Forms.Label label9;
-			System.Windows.Forms.Label label1;
-			System.Windows.Forms.Label label8;
-			System.Windows.Forms.Label label4;
-			System.Windows.Forms.Label label10;
-			this.linuxpaths = new System.Windows.Forms.CheckBox();
-			this.labelparameters = new System.Windows.Forms.Label();
-			this.cancel = new System.Windows.Forms.Button();
-			this.apply = new System.Windows.Forms.Button();
-			this.tabs = new System.Windows.Forms.TabControl();
-			this.tabresources = new System.Windows.Forms.TabPage();
-			this.configdata = new CodeImp.DoomBuilder.Controls.ResourceListEditor();
-			this.tabnodebuilder = new System.Windows.Forms.TabPage();
-			this.nodebuildertest = new System.Windows.Forms.ComboBox();
-			this.nodebuildersave = new System.Windows.Forms.ComboBox();
-			this.tabtesting = new System.Windows.Forms.TabPage();
-			this.btnRemoveEngine = new System.Windows.Forms.Button();
-			this.btnNewEngine = new System.Windows.Forms.Button();
-			this.cbEngineSelector = new System.Windows.Forms.ComboBox();
-			this.label13 = new System.Windows.Forms.Label();
-			this.shortpaths = new System.Windows.Forms.CheckBox();
-			this.customparameters = new System.Windows.Forms.CheckBox();
-			this.skill = new CodeImp.DoomBuilder.Controls.ActionSelectorControl();
-			this.browsetestprogram = new System.Windows.Forms.Button();
-			this.noresultlabel = new System.Windows.Forms.Label();
-			this.testresult = new System.Windows.Forms.TextBox();
-			this.labelresult = new System.Windows.Forms.Label();
-			this.testparameters = new System.Windows.Forms.TextBox();
-			this.testapplication = new System.Windows.Forms.TextBox();
-			this.tabtextures = new System.Windows.Forms.TabPage();
-			this.listtextures = new System.Windows.Forms.ListView();
-			this.smallimages = new System.Windows.Forms.ImageList(this.components);
-			this.restoretexturesets = new System.Windows.Forms.Button();
-			this.edittextureset = new System.Windows.Forms.Button();
-			this.pastetexturesets = new System.Windows.Forms.Button();
-			this.copytexturesets = new System.Windows.Forms.Button();
-			this.removetextureset = new System.Windows.Forms.Button();
-			this.addtextureset = new System.Windows.Forms.Button();
-			this.tabmodes = new System.Windows.Forms.TabPage();
-			this.startmode = new System.Windows.Forms.ComboBox();
-			this.label11 = new System.Windows.Forms.Label();
-			this.listmodes = new System.Windows.Forms.ListView();
-			this.colmodename = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
-			this.colmodeplugin = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
-			this.listconfigs = new System.Windows.Forms.ListView();
-			this.columnname = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
-			this.copypastemenu = new System.Windows.Forms.ContextMenuStrip(this.components);
-			this.copyall = new System.Windows.Forms.ToolStripMenuItem();
-			this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
-			this.pasteall = new System.Windows.Forms.ToolStripMenuItem();
-			this.pasteresources = new System.Windows.Forms.ToolStripMenuItem();
-			this.pasteengines = new System.Windows.Forms.ToolStripMenuItem();
-			this.pastecolorpresets = new System.Windows.Forms.ToolStripMenuItem();
-			this.testprogramdialog = new System.Windows.Forms.OpenFileDialog();
-			this.hintlabel = new System.Windows.Forms.Label();
-			this.hint = new System.Windows.Forms.PictureBox();
-			this.tooltip = new System.Windows.Forms.ToolTip(this.components);
-			label5 = new System.Windows.Forms.Label();
-			label6 = new System.Windows.Forms.Label();
-			label3 = new System.Windows.Forms.Label();
-			label2 = new System.Windows.Forms.Label();
-			label7 = new System.Windows.Forms.Label();
-			label9 = new System.Windows.Forms.Label();
-			label1 = new System.Windows.Forms.Label();
-			label8 = new System.Windows.Forms.Label();
-			label4 = new System.Windows.Forms.Label();
-			label10 = new System.Windows.Forms.Label();
-			this.tabs.SuspendLayout();
-			this.tabresources.SuspendLayout();
-			this.tabnodebuilder.SuspendLayout();
-			this.tabtesting.SuspendLayout();
-			this.tabtextures.SuspendLayout();
-			this.tabmodes.SuspendLayout();
-			this.copypastemenu.SuspendLayout();
-			((System.ComponentModel.ISupportInitialize)(this.hint)).BeginInit();
-			this.SuspendLayout();
-			// 
-			// label5
-			// 
-			label5.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-			label5.AutoSize = true;
-			label5.Location = new System.Drawing.Point(12, 272);
-			label5.Name = "label5";
-			label5.Size = new System.Drawing.Size(299, 39);
-			label5.TabIndex = 19;
-			label5.Text = "Drag && drop resources to add them.\r\nDrag items to change order (lower items over" +
+            this.components = new System.ComponentModel.Container();
+            System.Windows.Forms.Label label5;
+            System.Windows.Forms.Label label6;
+            System.Windows.Forms.Label label3;
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfigForm));
+            System.Windows.Forms.Label label2;
+            System.Windows.Forms.Label label7;
+            System.Windows.Forms.Label label9;
+            System.Windows.Forms.Label label1;
+            System.Windows.Forms.Label label8;
+            System.Windows.Forms.Label label4;
+            System.Windows.Forms.Label label10;
+            this.linuxpaths = new System.Windows.Forms.CheckBox();
+            this.labelparameters = new System.Windows.Forms.Label();
+            this.cancel = new System.Windows.Forms.Button();
+            this.apply = new System.Windows.Forms.Button();
+            this.tabs = new System.Windows.Forms.TabControl();
+            this.tabresources = new System.Windows.Forms.TabPage();
+            this.configdata = new CodeImp.DoomBuilder.Controls.ResourceListEditor();
+            this.tabnodebuilder = new System.Windows.Forms.TabPage();
+            this.nodebuildertest = new System.Windows.Forms.ComboBox();
+            this.nodebuildersave = new System.Windows.Forms.ComboBox();
+            this.tabtesting = new System.Windows.Forms.TabPage();
+            this.btnRemoveEngine = new System.Windows.Forms.Button();
+            this.btnNewEngine = new System.Windows.Forms.Button();
+            this.cbEngineSelector = new System.Windows.Forms.ComboBox();
+            this.label13 = new System.Windows.Forms.Label();
+            this.shortpaths = new System.Windows.Forms.CheckBox();
+            this.customparameters = new System.Windows.Forms.CheckBox();
+            this.skill = new CodeImp.DoomBuilder.Controls.ActionSelectorControl();
+            this.browsetestprogram = new System.Windows.Forms.Button();
+            this.noresultlabel = new System.Windows.Forms.Label();
+            this.testresult = new System.Windows.Forms.TextBox();
+            this.labelresult = new System.Windows.Forms.Label();
+            this.testparameters = new System.Windows.Forms.TextBox();
+            this.testapplication = new System.Windows.Forms.TextBox();
+            this.tabtextures = new System.Windows.Forms.TabPage();
+            this.exporttexturesets = new System.Windows.Forms.Button();
+            this.importtexturesets = new System.Windows.Forms.Button();
+            this.listtextures = new System.Windows.Forms.ListView();
+            this.smallimages = new System.Windows.Forms.ImageList(this.components);
+            this.restoretexturesets = new System.Windows.Forms.Button();
+            this.edittextureset = new System.Windows.Forms.Button();
+            this.pastetexturesets = new System.Windows.Forms.Button();
+            this.copytexturesets = new System.Windows.Forms.Button();
+            this.removetextureset = new System.Windows.Forms.Button();
+            this.addtextureset = new System.Windows.Forms.Button();
+            this.tabmodes = new System.Windows.Forms.TabPage();
+            this.startmode = new System.Windows.Forms.ComboBox();
+            this.label11 = new System.Windows.Forms.Label();
+            this.listmodes = new System.Windows.Forms.ListView();
+            this.colmodename = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.colmodeplugin = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.listconfigs = new System.Windows.Forms.ListView();
+            this.columnname = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            this.copypastemenu = new System.Windows.Forms.ContextMenuStrip(this.components);
+            this.copyall = new System.Windows.Forms.ToolStripMenuItem();
+            this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+            this.pasteall = new System.Windows.Forms.ToolStripMenuItem();
+            this.pasteresources = new System.Windows.Forms.ToolStripMenuItem();
+            this.pasteengines = new System.Windows.Forms.ToolStripMenuItem();
+            this.pastecolorpresets = new System.Windows.Forms.ToolStripMenuItem();
+            this.testprogramdialog = new System.Windows.Forms.OpenFileDialog();
+            this.hintlabel = new System.Windows.Forms.Label();
+            this.hint = new System.Windows.Forms.PictureBox();
+            this.tooltip = new System.Windows.Forms.ToolTip(this.components);
+            this.importtexturesetdialog = new System.Windows.Forms.OpenFileDialog();
+            this.exporttexturesetdialog = new System.Windows.Forms.SaveFileDialog();
+            label5 = new System.Windows.Forms.Label();
+            label6 = new System.Windows.Forms.Label();
+            label3 = new System.Windows.Forms.Label();
+            label2 = new System.Windows.Forms.Label();
+            label7 = new System.Windows.Forms.Label();
+            label9 = new System.Windows.Forms.Label();
+            label1 = new System.Windows.Forms.Label();
+            label8 = new System.Windows.Forms.Label();
+            label4 = new System.Windows.Forms.Label();
+            label10 = new System.Windows.Forms.Label();
+            this.tabs.SuspendLayout();
+            this.tabresources.SuspendLayout();
+            this.tabnodebuilder.SuspendLayout();
+            this.tabtesting.SuspendLayout();
+            this.tabtextures.SuspendLayout();
+            this.tabmodes.SuspendLayout();
+            this.copypastemenu.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.hint)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // label5
+            // 
+            label5.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            label5.AutoSize = true;
+            label5.Location = new System.Drawing.Point(12, 272);
+            label5.Name = "label5";
+            label5.Size = new System.Drawing.Size(299, 39);
+            label5.TabIndex = 19;
+            label5.Text = "Drag && drop resources to add them.\r\nDrag items to change order (lower items over" +
     "ride higher items).\r\nUse the context menu to cut, copy, paste or remove items.";
-			// 
-			// label6
-			// 
-			label6.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            // 
+            // label6
+            // 
+            label6.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			label6.AutoEllipsis = true;
-			label6.Location = new System.Drawing.Point(12, 15);
-			label6.Name = "label6";
-			label6.Size = new System.Drawing.Size(457, 37);
-			label6.TabIndex = 21;
-			label6.Text = "These are the resources that will be loaded when this configuration is chosen for" +
+            label6.AutoEllipsis = true;
+            label6.Location = new System.Drawing.Point(12, 15);
+            label6.Name = "label6";
+            label6.Size = new System.Drawing.Size(457, 37);
+            label6.TabIndex = 21;
+            label6.Text = "These are the resources that will be loaded when this configuration is chosen for" +
     " editing. Usually you add your IWAD (like DOOM.WAD or DOOM2.WAD) here.";
-			// 
-			// label3
-			// 
-			label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            // 
+            // label3
+            // 
+            label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			label3.AutoEllipsis = true;
-			label3.Location = new System.Drawing.Point(12, 15);
-			label3.Name = "label3";
-			label3.Size = new System.Drawing.Size(468, 54);
-			label3.TabIndex = 22;
-			label3.Text = resources.GetString("label3.Text");
-			// 
-			// label2
-			// 
-			label2.AutoSize = true;
-			label2.Location = new System.Drawing.Point(12, 86);
-			label2.Name = "label2";
-			label2.Size = new System.Drawing.Size(144, 13);
-			label2.TabIndex = 24;
-			label2.Text = "Configuration for saving map:";
-			// 
-			// label7
-			// 
-			label7.AutoSize = true;
-			label7.Location = new System.Drawing.Point(35, 125);
-			label7.Name = "label7";
-			label7.Size = new System.Drawing.Size(121, 13);
-			label7.TabIndex = 26;
-			label7.Text = "Configuration for testing:";
-			// 
-			// label9
-			// 
-			label9.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            label3.AutoEllipsis = true;
+            label3.Location = new System.Drawing.Point(12, 15);
+            label3.Name = "label3";
+            label3.Size = new System.Drawing.Size(468, 54);
+            label3.TabIndex = 22;
+            label3.Text = resources.GetString("label3.Text");
+            // 
+            // label2
+            // 
+            label2.AutoSize = true;
+            label2.Location = new System.Drawing.Point(12, 86);
+            label2.Name = "label2";
+            label2.Size = new System.Drawing.Size(144, 13);
+            label2.TabIndex = 24;
+            label2.Text = "Configuration for saving map:";
+            // 
+            // label7
+            // 
+            label7.AutoSize = true;
+            label7.Location = new System.Drawing.Point(35, 125);
+            label7.Name = "label7";
+            label7.Size = new System.Drawing.Size(121, 13);
+            label7.TabIndex = 26;
+            label7.Text = "Configuration for testing:";
+            // 
+            // label9
+            // 
+            label9.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			label9.AutoEllipsis = true;
-			label9.Location = new System.Drawing.Point(12, 15);
-			label9.Name = "label9";
-			label9.Size = new System.Drawing.Size(477, 54);
-			label9.TabIndex = 23;
-			label9.Text = "Here you can specify the program settings to use for launching a game engine when" +
+            label9.AutoEllipsis = true;
+            label9.Location = new System.Drawing.Point(12, 15);
+            label9.Name = "label9";
+            label9.Size = new System.Drawing.Size(477, 54);
+            label9.TabIndex = 23;
+            label9.Text = "Here you can specify the program settings to use for launching a game engine when" +
     " testing the map. Press F1 for help with custom parameters.";
-			// 
-			// label1
-			// 
-			label1.AutoSize = true;
-			label1.Location = new System.Drawing.Point(15, 89);
-			label1.Name = "label1";
-			label1.Size = new System.Drawing.Size(62, 13);
-			label1.TabIndex = 24;
-			label1.Text = "Application:";
-			// 
-			// label8
-			// 
-			label8.AutoSize = true;
-			label8.Location = new System.Drawing.Point(21, 119);
-			label8.Name = "label8";
-			label8.Size = new System.Drawing.Size(58, 13);
-			label8.TabIndex = 34;
-			label8.Text = "Skill Level:";
-			// 
-			// label4
-			// 
-			label4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            // 
+            // label1
+            // 
+            label1.AutoSize = true;
+            label1.Location = new System.Drawing.Point(15, 89);
+            label1.Name = "label1";
+            label1.Size = new System.Drawing.Size(62, 13);
+            label1.TabIndex = 24;
+            label1.Text = "Application:";
+            // 
+            // label8
+            // 
+            label8.AutoSize = true;
+            label8.Location = new System.Drawing.Point(21, 119);
+            label8.Name = "label8";
+            label8.Size = new System.Drawing.Size(58, 13);
+            label8.TabIndex = 34;
+            label8.Text = "Skill Level:";
+            // 
+            // label4
+            // 
+            label4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			label4.AutoEllipsis = true;
-			label4.Location = new System.Drawing.Point(12, 15);
-			label4.Name = "label4";
-			label4.Size = new System.Drawing.Size(483, 46);
-			label4.TabIndex = 24;
-			label4.Text = "Texture Sets are a way to group textures and flats into categories, so that you c" +
+            label4.AutoEllipsis = true;
+            label4.Location = new System.Drawing.Point(12, 15);
+            label4.Name = "label4";
+            label4.Size = new System.Drawing.Size(483, 46);
+            label4.TabIndex = 24;
+            label4.Text = "Texture Sets are a way to group textures and flats into categories, so that you c" +
     "an easily find a texture for the specific style or purpose you need by selecting" +
     " one of the categories.";
-			// 
-			// label10
-			// 
-			label10.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            // 
+            // label10
+            // 
+            label10.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			label10.AutoEllipsis = true;
-			label10.Location = new System.Drawing.Point(12, 15);
-			label10.Name = "label10";
-			label10.Size = new System.Drawing.Size(470, 58);
-			label10.TabIndex = 25;
-			label10.Text = resources.GetString("label10.Text");
-			// 
-			// linuxpaths
-			// 
-			this.linuxpaths.AutoSize = true;
-			this.linuxpaths.Location = new System.Drawing.Point(375, 217);
-			this.linuxpaths.Name = "linuxpaths";
-			this.linuxpaths.Size = new System.Drawing.Size(102, 17);
-			this.linuxpaths.TabIndex = 41;
-			this.linuxpaths.Text = "Use Linux paths";
-			this.linuxpaths.UseVisualStyleBackColor = true;
-			this.linuxpaths.Visible = false;
-			this.linuxpaths.CheckedChanged += new System.EventHandler(this.Linuxpaths_CheckedChanged);
-			// 
-			// labelparameters
-			// 
-			this.labelparameters.AutoSize = true;
-			this.labelparameters.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-			this.labelparameters.ForeColor = System.Drawing.SystemColors.HotTrack;
-			this.labelparameters.Location = new System.Drawing.Point(16, 169);
-			this.labelparameters.Name = "labelparameters";
-			this.labelparameters.Size = new System.Drawing.Size(63, 13);
-			this.labelparameters.TabIndex = 27;
-			this.labelparameters.Text = "Parameters:";
-			this.tooltip.SetToolTip(this.labelparameters, resources.GetString("labelparameters.ToolTip"));
-			this.labelparameters.Visible = false;
-			// 
-			// cancel
-			// 
-			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-			this.cancel.Location = new System.Drawing.Point(671, 379);
-			this.cancel.Name = "cancel";
-			this.cancel.Size = new System.Drawing.Size(112, 25);
-			this.cancel.TabIndex = 3;
-			this.cancel.Text = "Cancel";
-			this.cancel.UseVisualStyleBackColor = true;
-			this.cancel.Click += new System.EventHandler(this.cancel_Click);
-			// 
-			// apply
-			// 
-			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.apply.Location = new System.Drawing.Point(553, 379);
-			this.apply.Name = "apply";
-			this.apply.Size = new System.Drawing.Size(112, 25);
-			this.apply.TabIndex = 2;
-			this.apply.Text = "OK";
-			this.apply.UseVisualStyleBackColor = true;
-			this.apply.Click += new System.EventHandler(this.apply_Click);
-			// 
-			// tabs
-			// 
-			this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            label10.AutoEllipsis = true;
+            label10.Location = new System.Drawing.Point(12, 15);
+            label10.Name = "label10";
+            label10.Size = new System.Drawing.Size(470, 58);
+            label10.TabIndex = 25;
+            label10.Text = resources.GetString("label10.Text");
+            // 
+            // linuxpaths
+            // 
+            this.linuxpaths.AutoSize = true;
+            this.linuxpaths.Location = new System.Drawing.Point(375, 217);
+            this.linuxpaths.Name = "linuxpaths";
+            this.linuxpaths.Size = new System.Drawing.Size(102, 17);
+            this.linuxpaths.TabIndex = 41;
+            this.linuxpaths.Text = "Use Linux paths";
+            this.linuxpaths.UseVisualStyleBackColor = true;
+            this.linuxpaths.Visible = false;
+            this.linuxpaths.CheckedChanged += new System.EventHandler(this.Linuxpaths_CheckedChanged);
+            // 
+            // labelparameters
+            // 
+            this.labelparameters.AutoSize = true;
+            this.labelparameters.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.labelparameters.ForeColor = System.Drawing.SystemColors.HotTrack;
+            this.labelparameters.Location = new System.Drawing.Point(16, 169);
+            this.labelparameters.Name = "labelparameters";
+            this.labelparameters.Size = new System.Drawing.Size(63, 13);
+            this.labelparameters.TabIndex = 27;
+            this.labelparameters.Text = "Parameters:";
+            this.tooltip.SetToolTip(this.labelparameters, resources.GetString("labelparameters.ToolTip"));
+            this.labelparameters.Visible = false;
+            // 
+            // cancel
+            // 
+            this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.cancel.Location = new System.Drawing.Point(671, 379);
+            this.cancel.Name = "cancel";
+            this.cancel.Size = new System.Drawing.Size(112, 25);
+            this.cancel.TabIndex = 3;
+            this.cancel.Text = "Cancel";
+            this.cancel.UseVisualStyleBackColor = true;
+            this.cancel.Click += new System.EventHandler(this.cancel_Click);
+            // 
+            // apply
+            // 
+            this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.apply.Location = new System.Drawing.Point(553, 379);
+            this.apply.Name = "apply";
+            this.apply.Size = new System.Drawing.Size(112, 25);
+            this.apply.TabIndex = 2;
+            this.apply.Text = "OK";
+            this.apply.UseVisualStyleBackColor = true;
+            this.apply.Click += new System.EventHandler(this.apply_Click);
+            // 
+            // tabs
+            // 
+            this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.tabs.Controls.Add(this.tabresources);
-			this.tabs.Controls.Add(this.tabnodebuilder);
-			this.tabs.Controls.Add(this.tabtesting);
-			this.tabs.Controls.Add(this.tabtextures);
-			this.tabs.Controls.Add(this.tabmodes);
-			this.tabs.Enabled = false;
-			this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-			this.tabs.Location = new System.Drawing.Point(277, 12);
-			this.tabs.Name = "tabs";
-			this.tabs.Padding = new System.Drawing.Point(24, 3);
-			this.tabs.SelectedIndex = 0;
-			this.tabs.Size = new System.Drawing.Size(506, 358);
-			this.tabs.TabIndex = 1;
-			// 
-			// tabresources
-			// 
-			this.tabresources.Controls.Add(label6);
-			this.tabresources.Controls.Add(this.configdata);
-			this.tabresources.Controls.Add(label5);
-			this.tabresources.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-			this.tabresources.Location = new System.Drawing.Point(4, 22);
-			this.tabresources.Name = "tabresources";
-			this.tabresources.Padding = new System.Windows.Forms.Padding(6);
-			this.tabresources.Size = new System.Drawing.Size(498, 332);
-			this.tabresources.TabIndex = 0;
-			this.tabresources.Text = "Resources";
-			this.tabresources.UseVisualStyleBackColor = true;
-			// 
-			// configdata
-			// 
-			this.configdata.AllowDrop = true;
-			this.configdata.DialogOffset = new System.Drawing.Point(-120, 10);
-			this.configdata.Location = new System.Drawing.Point(15, 55);
-			this.configdata.Name = "configdata";
-			this.configdata.Size = new System.Drawing.Size(467, 204);
-			this.configdata.TabIndex = 0;
-			this.configdata.OnContentChanged += new CodeImp.DoomBuilder.Controls.ResourceListEditor.ContentChanged(this.resourcelocations_OnContentChanged);
-			// 
-			// tabnodebuilder
-			// 
-			this.tabnodebuilder.Controls.Add(label7);
-			this.tabnodebuilder.Controls.Add(this.nodebuildertest);
-			this.tabnodebuilder.Controls.Add(label2);
-			this.tabnodebuilder.Controls.Add(this.nodebuildersave);
-			this.tabnodebuilder.Controls.Add(label3);
-			this.tabnodebuilder.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-			this.tabnodebuilder.Location = new System.Drawing.Point(4, 22);
-			this.tabnodebuilder.Name = "tabnodebuilder";
-			this.tabnodebuilder.Padding = new System.Windows.Forms.Padding(6);
-			this.tabnodebuilder.Size = new System.Drawing.Size(498, 332);
-			this.tabnodebuilder.TabIndex = 1;
-			this.tabnodebuilder.Text = "Nodebuilder";
-			this.tabnodebuilder.UseVisualStyleBackColor = true;
-			// 
-			// nodebuildertest
-			// 
-			this.nodebuildertest.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            this.tabs.Controls.Add(this.tabresources);
+            this.tabs.Controls.Add(this.tabnodebuilder);
+            this.tabs.Controls.Add(this.tabtesting);
+            this.tabs.Controls.Add(this.tabtextures);
+            this.tabs.Controls.Add(this.tabmodes);
+            this.tabs.Enabled = false;
+            this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.tabs.Location = new System.Drawing.Point(277, 12);
+            this.tabs.Name = "tabs";
+            this.tabs.Padding = new System.Drawing.Point(24, 3);
+            this.tabs.SelectedIndex = 0;
+            this.tabs.Size = new System.Drawing.Size(506, 358);
+            this.tabs.TabIndex = 1;
+            // 
+            // tabresources
+            // 
+            this.tabresources.Controls.Add(label6);
+            this.tabresources.Controls.Add(this.configdata);
+            this.tabresources.Controls.Add(label5);
+            this.tabresources.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.tabresources.Location = new System.Drawing.Point(4, 22);
+            this.tabresources.Name = "tabresources";
+            this.tabresources.Padding = new System.Windows.Forms.Padding(6);
+            this.tabresources.Size = new System.Drawing.Size(498, 332);
+            this.tabresources.TabIndex = 0;
+            this.tabresources.Text = "Resources";
+            this.tabresources.UseVisualStyleBackColor = true;
+            // 
+            // configdata
+            // 
+            this.configdata.AllowDrop = true;
+            this.configdata.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.configdata.DialogOffset = new System.Drawing.Point(-120, 10);
+            this.configdata.GameConfiguration = null;
+            this.configdata.Location = new System.Drawing.Point(15, 55);
+            this.configdata.Name = "configdata";
+            this.configdata.Size = new System.Drawing.Size(467, 204);
+            this.configdata.TabIndex = 0;
+            this.configdata.OnContentChanged += new CodeImp.DoomBuilder.Controls.ResourceListEditor.ContentChanged(this.resourcelocations_OnContentChanged);
+            this.configdata.OnWarningsChanged += new CodeImp.DoomBuilder.Controls.ResourceListEditor.WarningsChanged(this.configdata_OnWarningsChanged);
+            // 
+            // tabnodebuilder
+            // 
+            this.tabnodebuilder.Controls.Add(label7);
+            this.tabnodebuilder.Controls.Add(this.nodebuildertest);
+            this.tabnodebuilder.Controls.Add(label2);
+            this.tabnodebuilder.Controls.Add(this.nodebuildersave);
+            this.tabnodebuilder.Controls.Add(label3);
+            this.tabnodebuilder.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.tabnodebuilder.Location = new System.Drawing.Point(4, 22);
+            this.tabnodebuilder.Name = "tabnodebuilder";
+            this.tabnodebuilder.Padding = new System.Windows.Forms.Padding(6);
+            this.tabnodebuilder.Size = new System.Drawing.Size(498, 332);
+            this.tabnodebuilder.TabIndex = 1;
+            this.tabnodebuilder.Text = "Nodebuilder";
+            this.tabnodebuilder.UseVisualStyleBackColor = true;
+            // 
+            // nodebuildertest
+            // 
+            this.nodebuildertest.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.nodebuildertest.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.nodebuildertest.FormattingEnabled = true;
-			this.nodebuildertest.Location = new System.Drawing.Point(167, 122);
-			this.nodebuildertest.Name = "nodebuildertest";
-			this.nodebuildertest.Size = new System.Drawing.Size(313, 21);
-			this.nodebuildertest.Sorted = true;
-			this.nodebuildertest.TabIndex = 1;
-			this.nodebuildertest.SelectedIndexChanged += new System.EventHandler(this.nodebuildertest_SelectedIndexChanged);
-			// 
-			// nodebuildersave
-			// 
-			this.nodebuildersave.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            this.nodebuildertest.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.nodebuildertest.FormattingEnabled = true;
+            this.nodebuildertest.Location = new System.Drawing.Point(167, 122);
+            this.nodebuildertest.Name = "nodebuildertest";
+            this.nodebuildertest.Size = new System.Drawing.Size(313, 21);
+            this.nodebuildertest.Sorted = true;
+            this.nodebuildertest.TabIndex = 1;
+            this.nodebuildertest.SelectedIndexChanged += new System.EventHandler(this.nodebuildertest_SelectedIndexChanged);
+            // 
+            // nodebuildersave
+            // 
+            this.nodebuildersave.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.nodebuildersave.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.nodebuildersave.FormattingEnabled = true;
-			this.nodebuildersave.Location = new System.Drawing.Point(167, 83);
-			this.nodebuildersave.Name = "nodebuildersave";
-			this.nodebuildersave.Size = new System.Drawing.Size(313, 21);
-			this.nodebuildersave.Sorted = true;
-			this.nodebuildersave.TabIndex = 0;
-			this.nodebuildersave.SelectedIndexChanged += new System.EventHandler(this.nodebuildersave_SelectedIndexChanged);
-			// 
-			// tabtesting
-			// 
-			this.tabtesting.Controls.Add(this.linuxpaths);
-			this.tabtesting.Controls.Add(this.btnRemoveEngine);
-			this.tabtesting.Controls.Add(this.btnNewEngine);
-			this.tabtesting.Controls.Add(this.cbEngineSelector);
-			this.tabtesting.Controls.Add(this.label13);
-			this.tabtesting.Controls.Add(this.shortpaths);
-			this.tabtesting.Controls.Add(this.customparameters);
-			this.tabtesting.Controls.Add(this.skill);
-			this.tabtesting.Controls.Add(label8);
-			this.tabtesting.Controls.Add(this.browsetestprogram);
-			this.tabtesting.Controls.Add(this.noresultlabel);
-			this.tabtesting.Controls.Add(this.testresult);
-			this.tabtesting.Controls.Add(this.labelresult);
-			this.tabtesting.Controls.Add(this.testparameters);
-			this.tabtesting.Controls.Add(this.labelparameters);
-			this.tabtesting.Controls.Add(this.testapplication);
-			this.tabtesting.Controls.Add(label1);
-			this.tabtesting.Controls.Add(label9);
-			this.tabtesting.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-			this.tabtesting.Location = new System.Drawing.Point(4, 22);
-			this.tabtesting.Name = "tabtesting";
-			this.tabtesting.Padding = new System.Windows.Forms.Padding(6);
-			this.tabtesting.Size = new System.Drawing.Size(498, 332);
-			this.tabtesting.TabIndex = 2;
-			this.tabtesting.Text = "Testing";
-			this.tabtesting.UseVisualStyleBackColor = true;
-			// 
-			// btnRemoveEngine
-			// 
-			this.btnRemoveEngine.Image = global::CodeImp.DoomBuilder.Properties.Resources.SearchClear;
-			this.btnRemoveEngine.Location = new System.Drawing.Point(463, 51);
-			this.btnRemoveEngine.Name = "btnRemoveEngine";
-			this.btnRemoveEngine.Size = new System.Drawing.Size(26, 24);
-			this.btnRemoveEngine.TabIndex = 40;
-			this.tooltip.SetToolTip(this.btnRemoveEngine, "Remove currently selected game engine");
-			this.btnRemoveEngine.UseVisualStyleBackColor = true;
-			this.btnRemoveEngine.Click += new System.EventHandler(this.btnRemoveEngine_Click);
-			// 
-			// btnNewEngine
-			// 
-			this.btnNewEngine.Image = global::CodeImp.DoomBuilder.Properties.Resources.Add;
-			this.btnNewEngine.Location = new System.Drawing.Point(433, 51);
-			this.btnNewEngine.Name = "btnNewEngine";
-			this.btnNewEngine.Size = new System.Drawing.Size(26, 24);
-			this.btnNewEngine.TabIndex = 39;
-			this.tooltip.SetToolTip(this.btnNewEngine, "Add new game engine");
-			this.btnNewEngine.UseVisualStyleBackColor = true;
-			this.btnNewEngine.Click += new System.EventHandler(this.btnNewEngine_Click);
-			// 
-			// cbEngineSelector
-			// 
-			this.cbEngineSelector.FormattingEnabled = true;
-			this.cbEngineSelector.Location = new System.Drawing.Point(87, 53);
-			this.cbEngineSelector.Name = "cbEngineSelector";
-			this.cbEngineSelector.Size = new System.Drawing.Size(340, 21);
-			this.cbEngineSelector.TabIndex = 38;
-			this.cbEngineSelector.DropDown += new System.EventHandler(this.cbEngineSelector_DropDown);
-			this.cbEngineSelector.SelectedIndexChanged += new System.EventHandler(this.cbEngineSelector_SelectedIndexChanged);
-			// 
-			// label13
-			// 
-			this.label13.AutoSize = true;
-			this.label13.Location = new System.Drawing.Point(36, 56);
-			this.label13.Name = "label13";
-			this.label13.Size = new System.Drawing.Size(43, 13);
-			this.label13.TabIndex = 37;
-			this.label13.Text = "Engine:";
-			// 
-			// shortpaths
-			// 
-			this.shortpaths.AutoSize = true;
-			this.shortpaths.Location = new System.Drawing.Point(87, 217);
-			this.shortpaths.Name = "shortpaths";
-			this.shortpaths.Size = new System.Drawing.Size(269, 17);
-			this.shortpaths.TabIndex = 5;
-			this.shortpaths.Text = "Use short paths and file names (MSDOS 8.3 format)";
-			this.shortpaths.UseVisualStyleBackColor = true;
-			this.shortpaths.Visible = false;
-			this.shortpaths.CheckedChanged += new System.EventHandler(this.shortpaths_CheckedChanged);
-			// 
-			// customparameters
-			// 
-			this.customparameters.AutoSize = true;
-			this.customparameters.Location = new System.Drawing.Point(87, 146);
-			this.customparameters.Name = "customparameters";
-			this.customparameters.Size = new System.Drawing.Size(129, 17);
-			this.customparameters.TabIndex = 3;
-			this.customparameters.Text = "Customize parameters";
-			this.customparameters.UseVisualStyleBackColor = true;
-			this.customparameters.CheckedChanged += new System.EventHandler(this.customparameters_CheckedChanged);
-			// 
-			// skill
-			// 
-			this.skill.BackColor = System.Drawing.Color.Transparent;
-			this.skill.Cursor = System.Windows.Forms.Cursors.Default;
-			this.skill.Empty = false;
-			this.skill.GeneralizedCategories = null;
-			this.skill.GeneralizedOptions = null;
-			this.skill.Location = new System.Drawing.Point(87, 116);
-			this.skill.Name = "skill";
-			this.skill.Size = new System.Drawing.Size(402, 21);
-			this.skill.TabIndex = 2;
-			this.skill.Value = 402;
-			this.skill.ValueChanges += new System.EventHandler(this.skill_ValueChanges);
-			// 
-			// browsetestprogram
-			// 
-			this.browsetestprogram.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-			this.browsetestprogram.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
-			this.browsetestprogram.Location = new System.Drawing.Point(459, 84);
-			this.browsetestprogram.Name = "browsetestprogram";
-			this.browsetestprogram.Size = new System.Drawing.Size(30, 24);
-			this.browsetestprogram.TabIndex = 1;
-			this.browsetestprogram.Text = " ";
-			this.tooltip.SetToolTip(this.browsetestprogram, "Browse game engine");
-			this.browsetestprogram.UseVisualStyleBackColor = true;
-			this.browsetestprogram.Click += new System.EventHandler(this.browsetestprogram_Click);
-			// 
-			// noresultlabel
-			// 
-			this.noresultlabel.Location = new System.Drawing.Point(84, 244);
-			this.noresultlabel.Name = "noresultlabel";
-			this.noresultlabel.Size = new System.Drawing.Size(272, 43);
-			this.noresultlabel.TabIndex = 32;
-			this.noresultlabel.Text = "An example result cannot be displayed, because it requires a map to be loaded.";
-			this.noresultlabel.Visible = false;
-			// 
-			// testresult
-			// 
-			this.testresult.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            this.nodebuildersave.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.nodebuildersave.FormattingEnabled = true;
+            this.nodebuildersave.Location = new System.Drawing.Point(167, 83);
+            this.nodebuildersave.Name = "nodebuildersave";
+            this.nodebuildersave.Size = new System.Drawing.Size(313, 21);
+            this.nodebuildersave.Sorted = true;
+            this.nodebuildersave.TabIndex = 0;
+            this.nodebuildersave.SelectedIndexChanged += new System.EventHandler(this.nodebuildersave_SelectedIndexChanged);
+            // 
+            // tabtesting
+            // 
+            this.tabtesting.Controls.Add(this.linuxpaths);
+            this.tabtesting.Controls.Add(this.btnRemoveEngine);
+            this.tabtesting.Controls.Add(this.btnNewEngine);
+            this.tabtesting.Controls.Add(this.cbEngineSelector);
+            this.tabtesting.Controls.Add(this.label13);
+            this.tabtesting.Controls.Add(this.shortpaths);
+            this.tabtesting.Controls.Add(this.customparameters);
+            this.tabtesting.Controls.Add(this.skill);
+            this.tabtesting.Controls.Add(label8);
+            this.tabtesting.Controls.Add(this.browsetestprogram);
+            this.tabtesting.Controls.Add(this.noresultlabel);
+            this.tabtesting.Controls.Add(this.testresult);
+            this.tabtesting.Controls.Add(this.labelresult);
+            this.tabtesting.Controls.Add(this.testparameters);
+            this.tabtesting.Controls.Add(this.labelparameters);
+            this.tabtesting.Controls.Add(this.testapplication);
+            this.tabtesting.Controls.Add(label1);
+            this.tabtesting.Controls.Add(label9);
+            this.tabtesting.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.tabtesting.Location = new System.Drawing.Point(4, 22);
+            this.tabtesting.Name = "tabtesting";
+            this.tabtesting.Padding = new System.Windows.Forms.Padding(6);
+            this.tabtesting.Size = new System.Drawing.Size(498, 332);
+            this.tabtesting.TabIndex = 2;
+            this.tabtesting.Text = "Testing";
+            this.tabtesting.UseVisualStyleBackColor = true;
+            // 
+            // btnRemoveEngine
+            // 
+            this.btnRemoveEngine.Image = global::CodeImp.DoomBuilder.Properties.Resources.SearchClear;
+            this.btnRemoveEngine.Location = new System.Drawing.Point(463, 51);
+            this.btnRemoveEngine.Name = "btnRemoveEngine";
+            this.btnRemoveEngine.Size = new System.Drawing.Size(26, 24);
+            this.btnRemoveEngine.TabIndex = 40;
+            this.tooltip.SetToolTip(this.btnRemoveEngine, "Remove currently selected game engine");
+            this.btnRemoveEngine.UseVisualStyleBackColor = true;
+            this.btnRemoveEngine.Click += new System.EventHandler(this.btnRemoveEngine_Click);
+            // 
+            // btnNewEngine
+            // 
+            this.btnNewEngine.Image = global::CodeImp.DoomBuilder.Properties.Resources.Add;
+            this.btnNewEngine.Location = new System.Drawing.Point(433, 51);
+            this.btnNewEngine.Name = "btnNewEngine";
+            this.btnNewEngine.Size = new System.Drawing.Size(26, 24);
+            this.btnNewEngine.TabIndex = 39;
+            this.tooltip.SetToolTip(this.btnNewEngine, "Add new game engine");
+            this.btnNewEngine.UseVisualStyleBackColor = true;
+            this.btnNewEngine.Click += new System.EventHandler(this.btnNewEngine_Click);
+            // 
+            // cbEngineSelector
+            // 
+            this.cbEngineSelector.FormattingEnabled = true;
+            this.cbEngineSelector.Location = new System.Drawing.Point(87, 53);
+            this.cbEngineSelector.Name = "cbEngineSelector";
+            this.cbEngineSelector.Size = new System.Drawing.Size(340, 21);
+            this.cbEngineSelector.TabIndex = 38;
+            this.cbEngineSelector.DropDown += new System.EventHandler(this.cbEngineSelector_DropDown);
+            this.cbEngineSelector.SelectedIndexChanged += new System.EventHandler(this.cbEngineSelector_SelectedIndexChanged);
+            // 
+            // label13
+            // 
+            this.label13.AutoSize = true;
+            this.label13.Location = new System.Drawing.Point(36, 56);
+            this.label13.Name = "label13";
+            this.label13.Size = new System.Drawing.Size(43, 13);
+            this.label13.TabIndex = 37;
+            this.label13.Text = "Engine:";
+            // 
+            // shortpaths
+            // 
+            this.shortpaths.AutoSize = true;
+            this.shortpaths.Location = new System.Drawing.Point(87, 217);
+            this.shortpaths.Name = "shortpaths";
+            this.shortpaths.Size = new System.Drawing.Size(269, 17);
+            this.shortpaths.TabIndex = 5;
+            this.shortpaths.Text = "Use short paths and file names (MSDOS 8.3 format)";
+            this.shortpaths.UseVisualStyleBackColor = true;
+            this.shortpaths.Visible = false;
+            this.shortpaths.CheckedChanged += new System.EventHandler(this.shortpaths_CheckedChanged);
+            // 
+            // customparameters
+            // 
+            this.customparameters.AutoSize = true;
+            this.customparameters.Location = new System.Drawing.Point(87, 146);
+            this.customparameters.Name = "customparameters";
+            this.customparameters.Size = new System.Drawing.Size(129, 17);
+            this.customparameters.TabIndex = 3;
+            this.customparameters.Text = "Customize parameters";
+            this.customparameters.UseVisualStyleBackColor = true;
+            this.customparameters.CheckedChanged += new System.EventHandler(this.customparameters_CheckedChanged);
+            // 
+            // skill
+            // 
+            this.skill.BackColor = System.Drawing.Color.Transparent;
+            this.skill.Cursor = System.Windows.Forms.Cursors.Default;
+            this.skill.Empty = false;
+            this.skill.GeneralizedCategories = null;
+            this.skill.GeneralizedOptions = null;
+            this.skill.Location = new System.Drawing.Point(87, 116);
+            this.skill.Name = "skill";
+            this.skill.Size = new System.Drawing.Size(402, 21);
+            this.skill.TabIndex = 2;
+            this.skill.Value = 402;
+            this.skill.ValueChanges += new System.EventHandler(this.skill_ValueChanges);
+            // 
+            // browsetestprogram
+            // 
+            this.browsetestprogram.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+            this.browsetestprogram.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
+            this.browsetestprogram.Location = new System.Drawing.Point(459, 84);
+            this.browsetestprogram.Name = "browsetestprogram";
+            this.browsetestprogram.Size = new System.Drawing.Size(30, 24);
+            this.browsetestprogram.TabIndex = 1;
+            this.browsetestprogram.Text = " ";
+            this.tooltip.SetToolTip(this.browsetestprogram, "Browse game engine");
+            this.browsetestprogram.UseVisualStyleBackColor = true;
+            this.browsetestprogram.Click += new System.EventHandler(this.browsetestprogram_Click);
+            // 
+            // noresultlabel
+            // 
+            this.noresultlabel.Location = new System.Drawing.Point(84, 244);
+            this.noresultlabel.Name = "noresultlabel";
+            this.noresultlabel.Size = new System.Drawing.Size(272, 43);
+            this.noresultlabel.TabIndex = 32;
+            this.noresultlabel.Text = "An example result cannot be displayed, because it requires a map to be loaded.";
+            this.noresultlabel.Visible = false;
+            // 
+            // testresult
+            // 
+            this.testresult.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.testresult.BackColor = System.Drawing.SystemColors.Control;
-			this.testresult.Location = new System.Drawing.Point(86, 241);
-			this.testresult.Multiline = true;
-			this.testresult.Name = "testresult";
-			this.testresult.ReadOnly = true;
-			this.testresult.Size = new System.Drawing.Size(403, 79);
-			this.testresult.TabIndex = 6;
-			this.testresult.Visible = false;
-			// 
-			// labelresult
-			// 
-			this.labelresult.AutoSize = true;
-			this.labelresult.Location = new System.Drawing.Point(38, 244);
-			this.labelresult.Name = "labelresult";
-			this.labelresult.Size = new System.Drawing.Size(40, 13);
-			this.labelresult.TabIndex = 30;
-			this.labelresult.Text = "Result:";
-			this.labelresult.Visible = false;
-			// 
-			// testparameters
-			// 
-			this.testparameters.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            this.testresult.BackColor = System.Drawing.SystemColors.Control;
+            this.testresult.Location = new System.Drawing.Point(86, 241);
+            this.testresult.Multiline = true;
+            this.testresult.Name = "testresult";
+            this.testresult.ReadOnly = true;
+            this.testresult.Size = new System.Drawing.Size(403, 79);
+            this.testresult.TabIndex = 6;
+            this.testresult.Visible = false;
+            // 
+            // labelresult
+            // 
+            this.labelresult.AutoSize = true;
+            this.labelresult.Location = new System.Drawing.Point(38, 244);
+            this.labelresult.Name = "labelresult";
+            this.labelresult.Size = new System.Drawing.Size(40, 13);
+            this.labelresult.TabIndex = 30;
+            this.labelresult.Text = "Result:";
+            this.labelresult.Visible = false;
+            // 
+            // testparameters
+            // 
+            this.testparameters.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.testparameters.Location = new System.Drawing.Point(87, 166);
-			this.testparameters.Multiline = true;
-			this.testparameters.Name = "testparameters";
-			this.testparameters.Size = new System.Drawing.Size(402, 41);
-			this.testparameters.TabIndex = 4;
-			this.testparameters.Visible = false;
-			this.testparameters.TextChanged += new System.EventHandler(this.testparameters_TextChanged);
-			// 
-			// testapplication
-			// 
-			this.testapplication.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            this.testparameters.Location = new System.Drawing.Point(87, 166);
+            this.testparameters.Multiline = true;
+            this.testparameters.Name = "testparameters";
+            this.testparameters.Size = new System.Drawing.Size(402, 41);
+            this.testparameters.TabIndex = 4;
+            this.testparameters.Visible = false;
+            this.testparameters.TextChanged += new System.EventHandler(this.testparameters_TextChanged);
+            // 
+            // testapplication
+            // 
+            this.testapplication.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.testapplication.Location = new System.Drawing.Point(87, 86);
-			this.testapplication.Name = "testapplication";
-			this.testapplication.ReadOnly = true;
-			this.testapplication.Size = new System.Drawing.Size(366, 20);
-			this.testapplication.TabIndex = 0;
-			this.testapplication.TextChanged += new System.EventHandler(this.testapplication_TextChanged);
-			// 
-			// tabtextures
-			// 
-			this.tabtextures.Controls.Add(this.listtextures);
-			this.tabtextures.Controls.Add(this.restoretexturesets);
-			this.tabtextures.Controls.Add(this.edittextureset);
-			this.tabtextures.Controls.Add(this.pastetexturesets);
-			this.tabtextures.Controls.Add(this.copytexturesets);
-			this.tabtextures.Controls.Add(this.removetextureset);
-			this.tabtextures.Controls.Add(this.addtextureset);
-			this.tabtextures.Controls.Add(label4);
-			this.tabtextures.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-			this.tabtextures.Location = new System.Drawing.Point(4, 22);
-			this.tabtextures.Name = "tabtextures";
-			this.tabtextures.Size = new System.Drawing.Size(498, 332);
-			this.tabtextures.TabIndex = 3;
-			this.tabtextures.Text = "Textures";
-			this.tabtextures.UseVisualStyleBackColor = true;
-			// 
-			// listtextures
-			// 
-			this.listtextures.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            this.testapplication.Location = new System.Drawing.Point(87, 86);
+            this.testapplication.Name = "testapplication";
+            this.testapplication.ReadOnly = true;
+            this.testapplication.Size = new System.Drawing.Size(366, 20);
+            this.testapplication.TabIndex = 0;
+            this.testapplication.TextChanged += new System.EventHandler(this.testapplication_TextChanged);
+            // 
+            // tabtextures
+            // 
+            this.tabtextures.Controls.Add(this.exporttexturesets);
+            this.tabtextures.Controls.Add(this.importtexturesets);
+            this.tabtextures.Controls.Add(this.listtextures);
+            this.tabtextures.Controls.Add(this.restoretexturesets);
+            this.tabtextures.Controls.Add(this.edittextureset);
+            this.tabtextures.Controls.Add(this.pastetexturesets);
+            this.tabtextures.Controls.Add(this.copytexturesets);
+            this.tabtextures.Controls.Add(this.removetextureset);
+            this.tabtextures.Controls.Add(this.addtextureset);
+            this.tabtextures.Controls.Add(label4);
+            this.tabtextures.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.tabtextures.Location = new System.Drawing.Point(4, 22);
+            this.tabtextures.Name = "tabtextures";
+            this.tabtextures.Size = new System.Drawing.Size(498, 332);
+            this.tabtextures.TabIndex = 3;
+            this.tabtextures.Text = "Textures";
+            this.tabtextures.UseVisualStyleBackColor = true;
+            // 
+            // exporttexturesets
+            // 
+            this.exporttexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.exporttexturesets.Enabled = false;
+            this.exporttexturesets.Location = new System.Drawing.Point(414, 282);
+            this.exporttexturesets.Name = "exporttexturesets";
+            this.exporttexturesets.Size = new System.Drawing.Size(68, 24);
+            this.exporttexturesets.TabIndex = 25;
+            this.exporttexturesets.Text = "Export";
+            this.exporttexturesets.UseVisualStyleBackColor = true;
+            this.exporttexturesets.Click += new System.EventHandler(this.exporttexturesets_Click);
+            // 
+            // importtexturesets
+            // 
+            this.importtexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.importtexturesets.Location = new System.Drawing.Point(340, 282);
+            this.importtexturesets.Name = "importtexturesets";
+            this.importtexturesets.Size = new System.Drawing.Size(68, 24);
+            this.importtexturesets.TabIndex = 25;
+            this.importtexturesets.Text = "Import";
+            this.importtexturesets.UseVisualStyleBackColor = true;
+            this.importtexturesets.Click += new System.EventHandler(this.importtexturesets_Click);
+            // 
+            // listtextures
+            // 
+            this.listtextures.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.listtextures.FullRowSelect = true;
-			this.listtextures.HideSelection = false;
-			this.listtextures.Location = new System.Drawing.Point(15, 64);
-			this.listtextures.Name = "listtextures";
-			this.listtextures.ShowGroups = false;
-			this.listtextures.Size = new System.Drawing.Size(467, 174);
-			this.listtextures.SmallImageList = this.smallimages;
-			this.listtextures.Sorting = System.Windows.Forms.SortOrder.Ascending;
-			this.listtextures.TabIndex = 0;
-			this.listtextures.UseCompatibleStateImageBehavior = false;
-			this.listtextures.View = System.Windows.Forms.View.List;
-			this.listtextures.SelectedIndexChanged += new System.EventHandler(this.listtextures_SelectedIndexChanged);
-			this.listtextures.DoubleClick += new System.EventHandler(this.listtextures_DoubleClick);
-			// 
-			// smallimages
-			// 
-			this.smallimages.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("smallimages.ImageStream")));
-			this.smallimages.TransparentColor = System.Drawing.Color.Transparent;
-			this.smallimages.Images.SetKeyName(0, "KnownTextureSet.ico");
-			// 
-			// restoretexturesets
-			// 
-			this.restoretexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-			this.restoretexturesets.Location = new System.Drawing.Point(15, 282);
-			this.restoretexturesets.Name = "restoretexturesets";
-			this.restoretexturesets.Size = new System.Drawing.Size(140, 24);
-			this.restoretexturesets.TabIndex = 6;
-			this.restoretexturesets.Text = "Add Default Sets";
-			this.restoretexturesets.UseVisualStyleBackColor = true;
-			this.restoretexturesets.Click += new System.EventHandler(this.restoretexturesets_Click);
-			// 
-			// edittextureset
-			// 
-			this.edittextureset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-			this.edittextureset.Enabled = false;
-			this.edittextureset.Location = new System.Drawing.Point(88, 244);
-			this.edittextureset.Name = "edittextureset";
-			this.edittextureset.Size = new System.Drawing.Size(67, 24);
-			this.edittextureset.TabIndex = 2;
-			this.edittextureset.Text = "Edit...";
-			this.edittextureset.UseVisualStyleBackColor = true;
-			this.edittextureset.Click += new System.EventHandler(this.edittextureset_Click);
-			// 
-			// pastetexturesets
-			// 
-			this.pastetexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.pastetexturesets.Enabled = false;
-			this.pastetexturesets.Location = new System.Drawing.Point(424, 244);
-			this.pastetexturesets.Name = "pastetexturesets";
-			this.pastetexturesets.Size = new System.Drawing.Size(58, 24);
-			this.pastetexturesets.TabIndex = 5;
-			this.pastetexturesets.Text = "Paste";
-			this.pastetexturesets.UseVisualStyleBackColor = true;
-			this.pastetexturesets.Click += new System.EventHandler(this.pastetexturesets_Click);
-			// 
-			// copytexturesets
-			// 
-			this.copytexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.copytexturesets.Enabled = false;
-			this.copytexturesets.Location = new System.Drawing.Point(360, 244);
-			this.copytexturesets.Name = "copytexturesets";
-			this.copytexturesets.Size = new System.Drawing.Size(58, 24);
-			this.copytexturesets.TabIndex = 4;
-			this.copytexturesets.Text = "Copy";
-			this.copytexturesets.UseVisualStyleBackColor = true;
-			this.copytexturesets.Click += new System.EventHandler(this.copytexturesets_Click);
-			// 
-			// removetextureset
-			// 
-			this.removetextureset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-			this.removetextureset.Enabled = false;
-			this.removetextureset.Location = new System.Drawing.Point(161, 244);
-			this.removetextureset.Name = "removetextureset";
-			this.removetextureset.Size = new System.Drawing.Size(68, 24);
-			this.removetextureset.TabIndex = 3;
-			this.removetextureset.Text = "Remove";
-			this.removetextureset.UseVisualStyleBackColor = true;
-			this.removetextureset.Click += new System.EventHandler(this.removetextureset_Click);
-			// 
-			// addtextureset
-			// 
-			this.addtextureset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-			this.addtextureset.Location = new System.Drawing.Point(15, 244);
-			this.addtextureset.Name = "addtextureset";
-			this.addtextureset.Size = new System.Drawing.Size(67, 24);
-			this.addtextureset.TabIndex = 1;
-			this.addtextureset.Text = "Add...";
-			this.addtextureset.UseVisualStyleBackColor = true;
-			this.addtextureset.Click += new System.EventHandler(this.addtextureset_Click);
-			// 
-			// tabmodes
-			// 
-			this.tabmodes.Controls.Add(this.startmode);
-			this.tabmodes.Controls.Add(this.label11);
-			this.tabmodes.Controls.Add(this.listmodes);
-			this.tabmodes.Controls.Add(label10);
-			this.tabmodes.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-			this.tabmodes.Location = new System.Drawing.Point(4, 22);
-			this.tabmodes.Name = "tabmodes";
-			this.tabmodes.Size = new System.Drawing.Size(498, 332);
-			this.tabmodes.TabIndex = 4;
-			this.tabmodes.Text = "Modes";
-			this.tabmodes.UseVisualStyleBackColor = true;
-			// 
-			// startmode
-			// 
-			this.startmode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.startmode.FormattingEnabled = true;
-			this.startmode.Location = new System.Drawing.Point(215, 288);
-			this.startmode.Name = "startmode";
-			this.startmode.Size = new System.Drawing.Size(267, 21);
-			this.startmode.TabIndex = 27;
-			this.startmode.SelectedIndexChanged += new System.EventHandler(this.startmode_SelectedIndexChanged);
-			// 
-			// label11
-			// 
-			this.label11.AutoSize = true;
-			this.label11.Location = new System.Drawing.Point(12, 291);
-			this.label11.Name = "label11";
-			this.label11.Size = new System.Drawing.Size(197, 13);
-			this.label11.TabIndex = 26;
-			this.label11.Text = "When opening a map, start in this mode:";
-			// 
-			// listmodes
-			// 
-			this.listmodes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            this.listtextures.FullRowSelect = true;
+            this.listtextures.HideSelection = false;
+            this.listtextures.Location = new System.Drawing.Point(15, 64);
+            this.listtextures.Name = "listtextures";
+            this.listtextures.ShowGroups = false;
+            this.listtextures.Size = new System.Drawing.Size(467, 174);
+            this.listtextures.SmallImageList = this.smallimages;
+            this.listtextures.Sorting = System.Windows.Forms.SortOrder.Ascending;
+            this.listtextures.TabIndex = 0;
+            this.listtextures.UseCompatibleStateImageBehavior = false;
+            this.listtextures.View = System.Windows.Forms.View.List;
+            this.listtextures.SelectedIndexChanged += new System.EventHandler(this.listtextures_SelectedIndexChanged);
+            this.listtextures.DoubleClick += new System.EventHandler(this.listtextures_DoubleClick);
+            // 
+            // smallimages
+            // 
+            this.smallimages.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("smallimages.ImageStream")));
+            this.smallimages.TransparentColor = System.Drawing.Color.Transparent;
+            this.smallimages.Images.SetKeyName(0, "KnownTextureSet.ico");
+            // 
+            // restoretexturesets
+            // 
+            this.restoretexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.restoretexturesets.Location = new System.Drawing.Point(15, 282);
+            this.restoretexturesets.Name = "restoretexturesets";
+            this.restoretexturesets.Size = new System.Drawing.Size(140, 24);
+            this.restoretexturesets.TabIndex = 6;
+            this.restoretexturesets.Text = "Add Default Sets";
+            this.restoretexturesets.UseVisualStyleBackColor = true;
+            this.restoretexturesets.Click += new System.EventHandler(this.restoretexturesets_Click);
+            // 
+            // edittextureset
+            // 
+            this.edittextureset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.edittextureset.Enabled = false;
+            this.edittextureset.Location = new System.Drawing.Point(88, 244);
+            this.edittextureset.Name = "edittextureset";
+            this.edittextureset.Size = new System.Drawing.Size(67, 24);
+            this.edittextureset.TabIndex = 2;
+            this.edittextureset.Text = "Edit...";
+            this.edittextureset.UseVisualStyleBackColor = true;
+            this.edittextureset.Click += new System.EventHandler(this.edittextureset_Click);
+            // 
+            // pastetexturesets
+            // 
+            this.pastetexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.pastetexturesets.Enabled = false;
+            this.pastetexturesets.Location = new System.Drawing.Point(414, 244);
+            this.pastetexturesets.Name = "pastetexturesets";
+            this.pastetexturesets.Size = new System.Drawing.Size(68, 24);
+            this.pastetexturesets.TabIndex = 5;
+            this.pastetexturesets.Text = "Paste";
+            this.pastetexturesets.UseVisualStyleBackColor = true;
+            this.pastetexturesets.Click += new System.EventHandler(this.pastetexturesets_Click);
+            // 
+            // copytexturesets
+            // 
+            this.copytexturesets.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.copytexturesets.Enabled = false;
+            this.copytexturesets.Location = new System.Drawing.Point(340, 244);
+            this.copytexturesets.Name = "copytexturesets";
+            this.copytexturesets.Size = new System.Drawing.Size(68, 24);
+            this.copytexturesets.TabIndex = 4;
+            this.copytexturesets.Text = "Copy";
+            this.copytexturesets.UseVisualStyleBackColor = true;
+            this.copytexturesets.Click += new System.EventHandler(this.copytexturesets_Click);
+            // 
+            // removetextureset
+            // 
+            this.removetextureset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.removetextureset.Enabled = false;
+            this.removetextureset.Location = new System.Drawing.Point(161, 244);
+            this.removetextureset.Name = "removetextureset";
+            this.removetextureset.Size = new System.Drawing.Size(68, 24);
+            this.removetextureset.TabIndex = 3;
+            this.removetextureset.Text = "Remove";
+            this.removetextureset.UseVisualStyleBackColor = true;
+            this.removetextureset.Click += new System.EventHandler(this.removetextureset_Click);
+            // 
+            // addtextureset
+            // 
+            this.addtextureset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.addtextureset.Location = new System.Drawing.Point(15, 244);
+            this.addtextureset.Name = "addtextureset";
+            this.addtextureset.Size = new System.Drawing.Size(67, 24);
+            this.addtextureset.TabIndex = 1;
+            this.addtextureset.Text = "Add...";
+            this.addtextureset.UseVisualStyleBackColor = true;
+            this.addtextureset.Click += new System.EventHandler(this.addtextureset_Click);
+            // 
+            // tabmodes
+            // 
+            this.tabmodes.Controls.Add(this.startmode);
+            this.tabmodes.Controls.Add(this.label11);
+            this.tabmodes.Controls.Add(this.listmodes);
+            this.tabmodes.Controls.Add(label10);
+            this.tabmodes.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+            this.tabmodes.Location = new System.Drawing.Point(4, 22);
+            this.tabmodes.Name = "tabmodes";
+            this.tabmodes.Size = new System.Drawing.Size(498, 332);
+            this.tabmodes.TabIndex = 4;
+            this.tabmodes.Text = "Modes";
+            this.tabmodes.UseVisualStyleBackColor = true;
+            // 
+            // startmode
+            // 
+            this.startmode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.startmode.FormattingEnabled = true;
+            this.startmode.Location = new System.Drawing.Point(215, 288);
+            this.startmode.Name = "startmode";
+            this.startmode.Size = new System.Drawing.Size(267, 21);
+            this.startmode.TabIndex = 27;
+            this.startmode.SelectedIndexChanged += new System.EventHandler(this.startmode_SelectedIndexChanged);
+            // 
+            // label11
+            // 
+            this.label11.AutoSize = true;
+            this.label11.Location = new System.Drawing.Point(12, 291);
+            this.label11.Name = "label11";
+            this.label11.Size = new System.Drawing.Size(197, 13);
+            this.label11.TabIndex = 26;
+            this.label11.Text = "When opening a map, start in this mode:";
+            // 
+            // listmodes
+            // 
+            this.listmodes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.listmodes.CheckBoxes = true;
-			this.listmodes.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.listmodes.CheckBoxes = true;
+            this.listmodes.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
             this.colmodename,
             this.colmodeplugin});
-			this.listmodes.FullRowSelect = true;
-			this.listmodes.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
-			this.listmodes.Location = new System.Drawing.Point(15, 70);
-			this.listmodes.MultiSelect = false;
-			this.listmodes.Name = "listmodes";
-			this.listmodes.ShowGroups = false;
-			this.listmodes.Size = new System.Drawing.Size(467, 201);
-			this.listmodes.Sorting = System.Windows.Forms.SortOrder.Ascending;
-			this.listmodes.TabIndex = 0;
-			this.listmodes.UseCompatibleStateImageBehavior = false;
-			this.listmodes.View = System.Windows.Forms.View.Details;
-			this.listmodes.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.listmodes_ItemChecked);
-			// 
-			// colmodename
-			// 
-			this.colmodename.Text = "Editing Mode";
-			this.colmodename.Width = 179;
-			// 
-			// colmodeplugin
-			// 
-			this.colmodeplugin.Text = "Plugin";
-			this.colmodeplugin.Width = 221;
-			// 
-			// listconfigs
-			// 
-			this.listconfigs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            this.listmodes.FullRowSelect = true;
+            this.listmodes.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
+            this.listmodes.HideSelection = false;
+            this.listmodes.Location = new System.Drawing.Point(15, 70);
+            this.listmodes.MultiSelect = false;
+            this.listmodes.Name = "listmodes";
+            this.listmodes.ShowGroups = false;
+            this.listmodes.Size = new System.Drawing.Size(467, 201);
+            this.listmodes.Sorting = System.Windows.Forms.SortOrder.Ascending;
+            this.listmodes.TabIndex = 0;
+            this.listmodes.UseCompatibleStateImageBehavior = false;
+            this.listmodes.View = System.Windows.Forms.View.Details;
+            this.listmodes.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.listmodes_ItemChecked);
+            // 
+            // colmodename
+            // 
+            this.colmodename.Text = "Editing Mode";
+            this.colmodename.Width = 179;
+            // 
+            // colmodeplugin
+            // 
+            this.colmodeplugin.Text = "Plugin";
+            this.colmodeplugin.Width = 221;
+            // 
+            // listconfigs
+            // 
+            this.listconfigs.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left)));
-			this.listconfigs.CheckBoxes = true;
-			this.listconfigs.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.listconfigs.CheckBoxes = true;
+            this.listconfigs.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
             this.columnname});
-			this.listconfigs.ContextMenuStrip = this.copypastemenu;
-			this.listconfigs.FullRowSelect = true;
-			this.listconfigs.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
-			this.listconfigs.HideSelection = false;
-			this.listconfigs.Location = new System.Drawing.Point(12, 12);
-			this.listconfigs.MultiSelect = false;
-			this.listconfigs.Name = "listconfigs";
-			this.listconfigs.ShowGroups = false;
-			this.listconfigs.Size = new System.Drawing.Size(259, 358);
-			this.listconfigs.TabIndex = 0;
-			this.listconfigs.UseCompatibleStateImageBehavior = false;
-			this.listconfigs.View = System.Windows.Forms.View.Details;
-			this.listconfigs.KeyUp += new System.Windows.Forms.KeyEventHandler(this.listconfigs_KeyUp);
-			this.listconfigs.MouseUp += new System.Windows.Forms.MouseEventHandler(this.listconfigs_MouseUp);
-			// 
-			// columnname
-			// 
-			this.columnname.Text = "Configuration";
-			this.columnname.Width = 200;
-			// 
-			// copypastemenu
-			// 
-			this.copypastemenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.listconfigs.ContextMenuStrip = this.copypastemenu;
+            this.listconfigs.FullRowSelect = true;
+            this.listconfigs.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
+            this.listconfigs.HideSelection = false;
+            this.listconfigs.Location = new System.Drawing.Point(12, 12);
+            this.listconfigs.MultiSelect = false;
+            this.listconfigs.Name = "listconfigs";
+            this.listconfigs.ShowGroups = false;
+            this.listconfigs.Size = new System.Drawing.Size(259, 358);
+            this.listconfigs.TabIndex = 0;
+            this.listconfigs.UseCompatibleStateImageBehavior = false;
+            this.listconfigs.View = System.Windows.Forms.View.Details;
+            this.listconfigs.KeyUp += new System.Windows.Forms.KeyEventHandler(this.listconfigs_KeyUp);
+            this.listconfigs.MouseUp += new System.Windows.Forms.MouseEventHandler(this.listconfigs_MouseUp);
+            // 
+            // columnname
+            // 
+            this.columnname.Text = "Configuration";
+            this.columnname.Width = 200;
+            // 
+            // copypastemenu
+            // 
+            this.copypastemenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
             this.copyall,
             this.toolStripSeparator1,
             this.pasteall,
             this.pasteresources,
             this.pasteengines,
             this.pastecolorpresets});
-			this.copypastemenu.Name = "copypastemenu";
-			this.copypastemenu.Size = new System.Drawing.Size(175, 120);
-			this.copypastemenu.Opening += new System.ComponentModel.CancelEventHandler(this.copypastemenu_Opening);
-			// 
-			// copyall
-			// 
-			this.copyall.Image = global::CodeImp.DoomBuilder.Properties.Resources.Copy;
-			this.copyall.Name = "copyall";
-			this.copyall.Size = new System.Drawing.Size(174, 22);
-			this.copyall.Text = "Copy";
-			this.copyall.Click += new System.EventHandler(this.copyall_Click);
-			// 
-			// toolStripSeparator1
-			// 
-			this.toolStripSeparator1.Name = "toolStripSeparator1";
-			this.toolStripSeparator1.Size = new System.Drawing.Size(171, 6);
-			// 
-			// pasteall
-			// 
-			this.pasteall.Image = global::CodeImp.DoomBuilder.Properties.Resources.Paste;
-			this.pasteall.Name = "pasteall";
-			this.pasteall.Size = new System.Drawing.Size(174, 22);
-			this.pasteall.Text = "Paste";
-			this.pasteall.Click += new System.EventHandler(this.pasteall_Click);
-			// 
-			// pasteresources
-			// 
-			this.pasteresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.PasteSpecial;
-			this.pasteresources.Name = "pasteresources";
-			this.pasteresources.Size = new System.Drawing.Size(174, 22);
-			this.pasteresources.Text = "Paste Resources";
-			this.pasteresources.Click += new System.EventHandler(this.pasteresources_Click);
-			// 
-			// pasteengines
-			// 
-			this.pasteengines.Image = global::CodeImp.DoomBuilder.Properties.Resources.PasteSpecial;
-			this.pasteengines.Name = "pasteengines";
-			this.pasteengines.Size = new System.Drawing.Size(174, 22);
-			this.pasteengines.Text = "Paste Test Engines";
-			this.pasteengines.Click += new System.EventHandler(this.pasteengines_Click);
-			// 
-			// pastecolorpresets
-			// 
-			this.pastecolorpresets.Image = global::CodeImp.DoomBuilder.Properties.Resources.PasteSpecial;
-			this.pastecolorpresets.Name = "pastecolorpresets";
-			this.pastecolorpresets.Size = new System.Drawing.Size(174, 22);
-			this.pastecolorpresets.Text = "Paste Color Presets";
-			this.pastecolorpresets.Click += new System.EventHandler(this.pastecolorpresets_Click);
-			// 
-			// testprogramdialog
-			// 
-			this.testprogramdialog.Filter = "Executable Files (*.exe)|*.exe|Batch Files (*.bat)|*.bat";
-			this.testprogramdialog.Title = "Browse Test Program";
-			// 
-			// hintlabel
-			// 
-			this.hintlabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-			this.hintlabel.AutoSize = true;
-			this.hintlabel.Location = new System.Drawing.Point(30, 384);
-			this.hintlabel.Name = "hintlabel";
-			this.hintlabel.Size = new System.Drawing.Size(276, 13);
-			this.hintlabel.TabIndex = 6;
-			this.hintlabel.Text = "Use the context menu to copy-paste game configurations";
-			// 
-			// hint
-			// 
-			this.hint.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-			this.hint.Image = global::CodeImp.DoomBuilder.Properties.Resources.Lightbulb;
-			this.hint.Location = new System.Drawing.Point(12, 383);
-			this.hint.Name = "hint";
-			this.hint.Size = new System.Drawing.Size(16, 16);
-			this.hint.TabIndex = 5;
-			this.hint.TabStop = false;
-			// 
-			// tooltip
-			// 
-			this.tooltip.AutomaticDelay = 0;
-			this.tooltip.AutoPopDelay = 30000;
-			this.tooltip.InitialDelay = 10;
-			this.tooltip.ReshowDelay = 100;
-			this.tooltip.ToolTipTitle = "Supported Placeholders:";
-			this.tooltip.UseAnimation = false;
-			this.tooltip.UseFading = false;
-			// 
-			// ConfigForm
-			// 
-			this.AcceptButton = this.apply;
-			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
-			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-			this.CancelButton = this.cancel;
-			this.ClientSize = new System.Drawing.Size(794, 416);
-			this.Controls.Add(this.hintlabel);
-			this.Controls.Add(this.hint);
-			this.Controls.Add(this.listconfigs);
-			this.Controls.Add(this.tabs);
-			this.Controls.Add(this.cancel);
-			this.Controls.Add(this.apply);
-			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
-			this.MaximizeBox = false;
-			this.MinimizeBox = false;
-			this.Name = "ConfigForm";
-			this.Opacity = 0;
-			this.ShowIcon = false;
-			this.ShowInTaskbar = false;
-			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
-			this.Text = "Game Configurations";
-			this.Shown += new System.EventHandler(this.ConfigForm_Shown);
-			this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.ConfigForm_HelpRequested);
-			this.tabs.ResumeLayout(false);
-			this.tabresources.ResumeLayout(false);
-			this.tabresources.PerformLayout();
-			this.tabnodebuilder.ResumeLayout(false);
-			this.tabnodebuilder.PerformLayout();
-			this.tabtesting.ResumeLayout(false);
-			this.tabtesting.PerformLayout();
-			this.tabtextures.ResumeLayout(false);
-			this.tabmodes.ResumeLayout(false);
-			this.tabmodes.PerformLayout();
-			this.copypastemenu.ResumeLayout(false);
-			((System.ComponentModel.ISupportInitialize)(this.hint)).EndInit();
-			this.ResumeLayout(false);
-			this.PerformLayout();
+            this.copypastemenu.Name = "copypastemenu";
+            this.copypastemenu.Size = new System.Drawing.Size(175, 120);
+            this.copypastemenu.Opening += new System.ComponentModel.CancelEventHandler(this.copypastemenu_Opening);
+            // 
+            // copyall
+            // 
+            this.copyall.Image = global::CodeImp.DoomBuilder.Properties.Resources.Copy;
+            this.copyall.Name = "copyall";
+            this.copyall.Size = new System.Drawing.Size(174, 22);
+            this.copyall.Text = "Copy";
+            this.copyall.Click += new System.EventHandler(this.copyall_Click);
+            // 
+            // toolStripSeparator1
+            // 
+            this.toolStripSeparator1.Name = "toolStripSeparator1";
+            this.toolStripSeparator1.Size = new System.Drawing.Size(171, 6);
+            // 
+            // pasteall
+            // 
+            this.pasteall.Image = global::CodeImp.DoomBuilder.Properties.Resources.Paste;
+            this.pasteall.Name = "pasteall";
+            this.pasteall.Size = new System.Drawing.Size(174, 22);
+            this.pasteall.Text = "Paste";
+            this.pasteall.Click += new System.EventHandler(this.pasteall_Click);
+            // 
+            // pasteresources
+            // 
+            this.pasteresources.Image = global::CodeImp.DoomBuilder.Properties.Resources.PasteSpecial;
+            this.pasteresources.Name = "pasteresources";
+            this.pasteresources.Size = new System.Drawing.Size(174, 22);
+            this.pasteresources.Text = "Paste Resources";
+            this.pasteresources.Click += new System.EventHandler(this.pasteresources_Click);
+            // 
+            // pasteengines
+            // 
+            this.pasteengines.Image = global::CodeImp.DoomBuilder.Properties.Resources.PasteSpecial;
+            this.pasteengines.Name = "pasteengines";
+            this.pasteengines.Size = new System.Drawing.Size(174, 22);
+            this.pasteengines.Text = "Paste Test Engines";
+            this.pasteengines.Click += new System.EventHandler(this.pasteengines_Click);
+            // 
+            // pastecolorpresets
+            // 
+            this.pastecolorpresets.Image = global::CodeImp.DoomBuilder.Properties.Resources.PasteSpecial;
+            this.pastecolorpresets.Name = "pastecolorpresets";
+            this.pastecolorpresets.Size = new System.Drawing.Size(174, 22);
+            this.pastecolorpresets.Text = "Paste Color Presets";
+            this.pastecolorpresets.Click += new System.EventHandler(this.pastecolorpresets_Click);
+            // 
+            // testprogramdialog
+            // 
+            this.testprogramdialog.Filter = "Executable Files (*.exe)|*.exe|Batch Files (*.bat)|*.bat";
+            this.testprogramdialog.Title = "Browse Test Program";
+            // 
+            // hintlabel
+            // 
+            this.hintlabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.hintlabel.AutoSize = true;
+            this.hintlabel.Location = new System.Drawing.Point(30, 384);
+            this.hintlabel.Name = "hintlabel";
+            this.hintlabel.Size = new System.Drawing.Size(276, 13);
+            this.hintlabel.TabIndex = 6;
+            this.hintlabel.Text = "Use the context menu to copy-paste game configurations";
+            // 
+            // hint
+            // 
+            this.hint.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+            this.hint.Image = global::CodeImp.DoomBuilder.Properties.Resources.Lightbulb;
+            this.hint.Location = new System.Drawing.Point(12, 383);
+            this.hint.Name = "hint";
+            this.hint.Size = new System.Drawing.Size(16, 16);
+            this.hint.TabIndex = 5;
+            this.hint.TabStop = false;
+            // 
+            // tooltip
+            // 
+            this.tooltip.AutomaticDelay = 0;
+            this.tooltip.AutoPopDelay = 30000;
+            this.tooltip.InitialDelay = 10;
+            this.tooltip.ReshowDelay = 100;
+            this.tooltip.ToolTipTitle = "Supported Placeholders:";
+            this.tooltip.UseAnimation = false;
+            this.tooltip.UseFading = false;
+            // 
+            // importtexturesetdialog
+            // 
+            this.importtexturesetdialog.Filter = "Texture Set Configuration File|*.cfg|All files|*.*";
+            this.importtexturesetdialog.RestoreDirectory = true;
+            // 
+            // exporttexturesetdialog
+            // 
+            this.exporttexturesetdialog.DefaultExt = "cfg";
+            this.exporttexturesetdialog.FileName = "ExportedTextureSets.cfg";
+            this.exporttexturesetdialog.Filter = "Texture Set Configuration File|*.cfg|All files|*.*";
+            this.exporttexturesetdialog.RestoreDirectory = true;
+            // 
+            // ConfigForm
+            // 
+            this.AcceptButton = this.apply;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+            this.CancelButton = this.cancel;
+            this.ClientSize = new System.Drawing.Size(794, 416);
+            this.Controls.Add(this.hintlabel);
+            this.Controls.Add(this.hint);
+            this.Controls.Add(this.listconfigs);
+            this.Controls.Add(this.tabs);
+            this.Controls.Add(this.cancel);
+            this.Controls.Add(this.apply);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "ConfigForm";
+            this.Opacity = 0D;
+            this.ShowIcon = false;
+            this.ShowInTaskbar = false;
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Game Configurations";
+            this.Shown += new System.EventHandler(this.ConfigForm_Shown);
+            this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.ConfigForm_HelpRequested);
+            this.tabs.ResumeLayout(false);
+            this.tabresources.ResumeLayout(false);
+            this.tabresources.PerformLayout();
+            this.tabnodebuilder.ResumeLayout(false);
+            this.tabnodebuilder.PerformLayout();
+            this.tabtesting.ResumeLayout(false);
+            this.tabtesting.PerformLayout();
+            this.tabtextures.ResumeLayout(false);
+            this.tabmodes.ResumeLayout(false);
+            this.tabmodes.PerformLayout();
+            this.copypastemenu.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.hint)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
 
 		}
 
@@ -918,5 +965,9 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.PictureBox hint;
 		private System.Windows.Forms.ToolTip tooltip;
         private System.Windows.Forms.CheckBox linuxpaths;
-    }
+		private System.Windows.Forms.Button importtexturesets;
+		private System.Windows.Forms.OpenFileDialog importtexturesetdialog;
+		private System.Windows.Forms.Button exporttexturesets;
+		private System.Windows.Forms.SaveFileDialog exporttexturesetdialog;
+	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/ConfigForm.cs b/Source/Core/Windows/ConfigForm.cs
index 19edbf18f99cda0ff92aff8cd5c4da941773ee04..114ebbed3ccc48cc0af6958e700c5fcf49d52581 100755
--- a/Source/Core/Windows/ConfigForm.cs
+++ b/Source/Core/Windows/ConfigForm.cs
@@ -17,15 +17,17 @@
 #region ================== Namespaces
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Drawing;
 using System.IO;
-using System.Reflection;
+using System.Text.RegularExpressions;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.GZBuilder.Data;
+using CodeImp.DoomBuilder.IO;
 
 #endregion
 
@@ -39,6 +41,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private List<DefinedTextureSet> copiedsets;
 		private bool preventchanges;
 		private bool reloadresources;
+		private readonly int initialformheight;
 
 		//mxd. "Copy/Paste" stuff
 		private ConfigurationInfo configinfocopy;
@@ -53,7 +56,8 @@ namespace CodeImp.DoomBuilder.Windows
 			
 			// Initialize
 			InitializeComponent();
-			CodeImp.DoomBuilder.General.ApplyMonoListViewFix(listtextures);
+			this.initialformheight = Height;
+			General.ApplyMonoListViewFix(listtextures);
 
 			#if NO_WIN32
 			// Linux doesn't require .exe or .bat file extensions
@@ -138,8 +142,9 @@ namespace CodeImp.DoomBuilder.Windows
 
 				// Set defaults
 				configinfo.ApplyDefaults(gameconfig);
-				
+
 				// Fill resources list
+				configdata.GameConfiguration = gameconfig;
 				configdata.EditResourceLocationList(configinfo.Resources);
 				
 				// Go for all nodebuilder save items
@@ -583,9 +588,10 @@ namespace CodeImp.DoomBuilder.Windows
 		// Texture Set selected/deselected
 		private void listtextures_SelectedIndexChanged(object sender, EventArgs e)
 		{
-			edittextureset.Enabled = (listtextures.SelectedItems.Count > 0);
-			removetextureset.Enabled = (listtextures.SelectedItems.Count > 0);
-			copytexturesets.Enabled = (listtextures.SelectedItems.Count > 0);
+			edittextureset.Enabled = listtextures.SelectedItems.Count > 0;
+			removetextureset.Enabled = listtextures.SelectedItems.Count > 0;
+			copytexturesets.Enabled = listtextures.SelectedItems.Count > 0;
+			exporttexturesets.Enabled = listtextures.SelectedItems.Count > 0;
 		}
 		
 		// Doubleclicking a texture set
@@ -844,7 +850,191 @@ namespace CodeImp.DoomBuilder.Windows
 			if(listconfigs.SelectedItems.Count > 0) listconfigs.SelectedItems[0].EnsureVisible();
 		}
 
-#region ============= Copy/Paste context menu (mxd)
+		/// <summary>
+		/// Imports texture sets from a configuration file.
+		/// </summary>
+		/// <param name="sender">The sender</param>
+		/// <param name="e">The event arguments</param>
+		private void importtexturesets_Click(object sender, EventArgs e)
+		{
+			if (importtexturesetdialog.ShowDialog() != DialogResult.OK)
+				return;
+
+			int numnewsets = 0;
+			int numupdatedsets = 0;
+			int numnewfilters = 0;
+			bool foundsets = false;
+			Configuration cfg;
+
+			try
+			{
+				cfg = new Configuration(importtexturesetdialog.FileName, true);
+			}
+			catch (FileNotFoundException ex)
+			{
+				MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+				return;
+			}
+
+			if (cfg.ErrorResult)
+			{
+				string errordesc = "Error configuration file on line " + cfg.ErrorLine + ": " + cfg.ErrorDescription;
+				MessageBox.Show(errordesc, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+				return;
+			}
+
+				// Create a map for texture set names, so that we can easily add new filters to them
+				Dictionary<string, DefinedTextureSet> texturesetmap = new Dictionary<string, DefinedTextureSet>();
+			foreach (DefinedTextureSet dts in configinfo.TextureSets)
+			{
+				// In theory there could be multiple texture sets with the same name, just take the first one
+				if (!texturesetmap.ContainsKey(dts.Name))
+					texturesetmap[dts.Name] = dts;
+			}
+
+			// We're crawling through the configuration ourself instead of using the constructor of the
+			// DefinedTextureSet since we can't know if the configuration contains anything valid. Also
+			// we want to make sure that we don't add the same texture sets (and filters) multiple times
+			// when importing the same config file over and over again
+			foreach (DictionaryEntry rootde in cfg.Root)
+			{
+				string rootkey = rootde.Key.ToString();
+
+				if (!rootkey.ToLowerInvariant().StartsWith("set"))
+					continue;
+
+				if (!(rootde.Value is ICollection))
+					continue;
+
+				string setname = string.Empty;
+				List<string> setfilters = new List<string>();
+
+				foreach (DictionaryEntry de in cfg.ReadSetting(rootkey, new Hashtable()))
+				{
+					string key = de.Key.ToString();
+
+					if (key.ToLowerInvariant() == "name")
+						setname = cfg.ReadSetting($"{rootkey}.name", string.Empty);
+					else
+					{
+						if (Regex.IsMatch(key, @"^(filter\d+)$", RegexOptions.IgnoreCase))
+						{
+							string filter = cfg.ReadSetting($"{rootkey}.{key}", string.Empty);
+
+							if (string.IsNullOrWhiteSpace(filter))
+								continue;
+
+							setfilters.Add(filter);
+						}
+					}
+				}
+
+				if (setfilters.Count == 0)
+					continue;
+
+				foundsets = true;
+
+				// Either update an existing texture set...
+				if (texturesetmap.ContainsKey(setname))
+				{
+					bool updatedfilters = false;
+					foreach (string filter in setfilters)
+					{
+						// Only add the new filter if it does not yet exist in the texture set
+						if (!texturesetmap[setname].Filters.Contains(filter))
+						{
+							texturesetmap[setname].Filters.Add(filter);
+							numnewfilters++;
+							updatedfilters = true;
+						}
+					}
+
+					if (updatedfilters)
+						numupdatedsets++;
+				}
+				else // ... or create a new one
+				{
+					DefinedTextureSet s = new DefinedTextureSet(setname);
+					s.Filters.AddRange(setfilters);
+					configinfo.TextureSets.Add(s);
+					ListViewItem item = listtextures.Items.Add(s.Name);
+					item.Tag = s;
+					item.ImageIndex = 0;
+
+					numnewsets++;
+					numnewfilters += setfilters.Count;
+				}
+
+				configinfo.Changed = true;
+			}
+
+			if (numnewsets > 0 || numupdatedsets > 0)
+			{
+				listtextures.Sort();
+				reloadresources = true;
+
+				List<string> messages = new List<string>();
+
+				if (numnewsets > 0) messages.Add($"Imported {numnewsets} new texture sets.");
+				if (numupdatedsets > 0) messages.Add($"Updated {numupdatedsets} texture sets.");
+				messages.Add($"Added {numnewfilters} filters to new or existing texture sets.");
+
+				MessageBox.Show(string.Join(Environment.NewLine, messages), "Import successful", MessageBoxButtons.OK, MessageBoxIcon.Information);
+			}
+			else if (foundsets) // Nothing was added, but the config file did contain texture sets
+			{
+				MessageBox.Show("No new set, or sets to update found in the configuration file.", "Nothing to import", MessageBoxButtons.OK, MessageBoxIcon.Information);
+			}
+			else // Nothing that could potentially be imported found in the config file
+			{
+				MessageBox.Show($"No texture sets to import found in the configuration file.", "Import unsuccessful", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+			}
+		}
+
+		/// <summary>
+		/// Exports the selected texture sets into a configuration file.
+		/// </summary>
+		/// <param name="sender">The sender</param>
+		/// <param name="e">The event arguments</param>
+		private void exporttexturesets_Click(object sender, EventArgs e)
+		{
+			if (listtextures.SelectedItems.Count == 0)
+			{
+				MessageBox.Show("Please select texture sets to export.", "Nothing selected", MessageBoxButtons.OK, MessageBoxIcon.Information);
+				return;
+			}
+
+			if (exporttexturesetdialog.ShowDialog() != DialogResult.OK)
+				return;
+
+			Configuration cfg = new Configuration(true);
+			int count = 1;
+			int numexportedsets = 0;
+			int numexportedfilters = 0;
+
+			foreach (ListViewItem item in listtextures.SelectedItems)
+			{
+				if (item.Tag is DefinedTextureSet dts)
+				{
+					dts.WriteToConfig(cfg, $"set{count}");
+					count++;
+					numexportedsets++;
+					numexportedfilters += dts.Filters.Count;
+				}
+			}
+
+			if (cfg.SaveConfiguration(exporttexturesetdialog.FileName))
+			{
+				MessageBox.Show($"Exported {numexportedsets} texture set{(numexportedsets != 0 ? "s" : "")} with {numexportedfilters} filter{(numexportedfilters != 0 ? "s" : "")}", "Export successful", MessageBoxButtons.OK, MessageBoxIcon.Information);
+			}
+			else
+			{
+				MessageBox.Show("Failed to write configuration file.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+			}
+		}
+
+
+		#region ============= Copy/Paste context menu (mxd)
 
 		private void copypastemenu_Opening(object sender, System.ComponentModel.CancelEventArgs e) 
 		{
@@ -938,6 +1128,12 @@ namespace CodeImp.DoomBuilder.Windows
 			General.Interface.DisplayStatus(StatusType.Info, "Pasted color presets from \"" + configinfocopy.Name + "\"");
 		}
 
-#endregion
-	}
+        #endregion
+
+        private void configdata_OnWarningsChanged(int size)
+        {
+			Height = initialformheight + size;
+			Refresh();
+        }
+    }
 }
diff --git a/Source/Core/Windows/ConfigForm.resx b/Source/Core/Windows/ConfigForm.resx
index 38db508710fe57dd4e2081405b1317f6792316e0..b70bfa7284e7b3453c5e4527742e20453fbb443b 100755
--- a/Source/Core/Windows/ConfigForm.resx
+++ b/Source/Core/Windows/ConfigForm.resx
@@ -112,69 +112,69 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
-  <metadata name="label5.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label5.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label5.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label5.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="label6.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label6.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label6.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label6.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="label3.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label3.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label3.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label3.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
   <data name="label3.Text" xml:space="preserve">
     <value>The nodebuilder is a compiler which builds geometry structures for your map. You need these structures to be able to play the map in the game. For each purpose you can choose the desired nodebuilder configuration here.</value>
   </data>
-  <metadata name="label2.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label2.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label2.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label2.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="label7.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label7.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label7.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label7.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="label9.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label9.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label1.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label1.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label8.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label8.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label4.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label4.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label4.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label4.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="label10.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label10.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label10.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label10.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
   <data name="label10.Text" xml:space="preserve">
     <value>Here you can select the editing modes that you wish to use in this configuration. This is useful in case there are plugins with additional editing modes that can be used as a replacement for the original editing modes.</value>
   </data>
-  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>416, 17</value>
   </metadata>
   <data name="labelparameters.ToolTip" xml:space="preserve">
@@ -188,28 +188,34 @@
 %S     - Skill number at which to test.
 %NM - Either -nomonsters when testing without monsters, or nothing at all.</value>
   </data>
-  <metadata name="tabresources.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="tabresources.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="tabnodebuilder.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="tabnodebuilder.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="nodebuildertest.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="nodebuildertest.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="nodebuildersave.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="nodebuildersave.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="tabtesting.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="tabtesting.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="tabtextures.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="tabtextures.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="listtextures.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="exporttexturesets.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="smallimages.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="importtexturesets.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <metadata name="listtextures.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <metadata name="smallimages.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>159, 17</value>
   </metadata>
   <data name="smallimages.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
@@ -217,7 +223,7 @@
         AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
         LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
         ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAA4
-        CAAAAk1TRnQBSQFMAwEBAAH0AQEB9AEBARMBAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMA
+        CAAAAk1TRnQBSQFMAwEBAAE8AQIBPAECARMBAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMA
         AUwDAAEQAwABAQEAAQgFAAHAAQQYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA
         AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5
         AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA
@@ -255,34 +261,40 @@
         CQAC/wHgCQAL
 </value>
   </data>
-  <metadata name="restoretexturesets.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="restoretexturesets.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="edittextureset.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="edittextureset.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="pastetexturesets.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="pastetexturesets.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="copytexturesets.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="copytexturesets.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="removetextureset.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="removetextureset.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="addtextureset.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="addtextureset.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="tabmodes.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="tabmodes.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="listmodes.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="listmodes.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="copypastemenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="copypastemenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>278, 17</value>
   </metadata>
-  <metadata name="testprogramdialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="testprogramdialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
+  <metadata name="importtexturesetdialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>504, 17</value>
+  </metadata>
+  <metadata name="exporttexturesetdialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>680, 17</value>
+  </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Core/Windows/FlagsForm.Designer.cs b/Source/Core/Windows/FlagsForm.Designer.cs
index 848dd8499fc1184c595a1ae24db7cac105ce12ed..33960c193fae9f387a523ce10bb2c4c4d50edd5b 100755
--- a/Source/Core/Windows/FlagsForm.Designer.cs
+++ b/Source/Core/Windows/FlagsForm.Designer.cs
@@ -37,14 +37,14 @@
 			// 
 			// flags
 			// 
-			this.flags.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.flags.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.flags.AutoScroll = true;
 			this.flags.Columns = 3;
 			this.flags.Location = new System.Drawing.Point(6, 19);
 			this.flags.Name = "flags";
-			this.flags.Size = new System.Drawing.Size(418, 195);
+			this.flags.Size = new System.Drawing.Size(175, 195);
 			this.flags.TabIndex = 0;
 			this.flags.VerticalSpacing = 1;
 			// 
@@ -52,7 +52,7 @@
 			// 
 			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
 			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-			this.cancel.Location = new System.Drawing.Point(351, 238);
+			this.cancel.Location = new System.Drawing.Point(108, 238);
 			this.cancel.Name = "cancel";
 			this.cancel.Size = new System.Drawing.Size(91, 25);
 			this.cancel.TabIndex = 4;
@@ -63,7 +63,7 @@
 			// apply
 			// 
 			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.apply.Location = new System.Drawing.Point(254, 238);
+			this.apply.Location = new System.Drawing.Point(11, 238);
 			this.apply.Name = "apply";
 			this.apply.Size = new System.Drawing.Size(91, 25);
 			this.apply.TabIndex = 3;
@@ -73,13 +73,13 @@
 			// 
 			// groupBox1
 			// 
-			this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.groupBox1.Controls.Add(this.flags);
 			this.groupBox1.Location = new System.Drawing.Point(12, 7);
 			this.groupBox1.Name = "groupBox1";
-			this.groupBox1.Size = new System.Drawing.Size(430, 225);
+			this.groupBox1.Size = new System.Drawing.Size(187, 225);
 			this.groupBox1.TabIndex = 5;
 			this.groupBox1.TabStop = false;
 			// 
@@ -89,7 +89,7 @@
 			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
 			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
 			this.CancelButton = this.cancel;
-			this.ClientSize = new System.Drawing.Size(454, 267);
+			this.ClientSize = new System.Drawing.Size(211, 267);
 			this.Controls.Add(this.groupBox1);
 			this.Controls.Add(this.cancel);
 			this.Controls.Add(this.apply);
@@ -97,7 +97,7 @@
 			this.MaximizeBox = false;
 			this.MinimizeBox = false;
 			this.Name = "FlagsForm";
-			this.Opacity = 0;
+			this.Opacity = 0D;
 			this.ShowIcon = false;
 			this.ShowInTaskbar = false;
 			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
diff --git a/Source/Core/Windows/FlagsForm.cs b/Source/Core/Windows/FlagsForm.cs
index 4eee992c146b33d84b1dacc4378998d6710064ca..7d2090430a39fdccf82509d4209376c9722274af 100755
--- a/Source/Core/Windows/FlagsForm.cs
+++ b/Source/Core/Windows/FlagsForm.cs
@@ -52,8 +52,8 @@ namespace CodeImp.DoomBuilder.Windows
 			int newflagswidth = flags.GetWidth();
 			int newflagsheight = flags.GetHeight();
 
-			if(flagswidth != newflagswidth) this.Width += (newflagswidth - flagswidth);
-			if(flagsheight != newflagsheight) this.Height += (newflagsheight - flagsheight);
+			if(flagswidth < newflagswidth) this.Width += (newflagswidth - flagswidth);
+			if(flagsheight < newflagsheight) this.Height += (newflagsheight - flagsheight);
 
 			// Parse the value string and check the boxes if necessary
 			if(!string.IsNullOrEmpty(value.Trim()))
diff --git a/Source/Core/Windows/FlagsForm.resx b/Source/Core/Windows/FlagsForm.resx
index ff31a6db56e23b5a334f34387830ba5b4bd33eb8..c7e0d4bdf13aadeae1f2c8b103a0232dd12e22d8 100755
--- a/Source/Core/Windows/FlagsForm.resx
+++ b/Source/Core/Windows/FlagsForm.resx
@@ -112,9 +112,9 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
 </root>
\ No newline at end of file
diff --git a/Source/Core/Windows/LinedefEditFormUDMF.Designer.cs b/Source/Core/Windows/LinedefEditFormUDMF.Designer.cs
index ae982f21ec83040ab972d6b2eae930b74fda893a..01192ae9b53e00f4297aae7862a445359bed391a 100755
--- a/Source/Core/Windows/LinedefEditFormUDMF.Designer.cs
+++ b/Source/Core/Windows/LinedefEditFormUDMF.Designer.cs
@@ -80,6 +80,9 @@ namespace CodeImp.DoomBuilder.Windows
 			this.pfcFrontOffsetMid = new CodeImp.DoomBuilder.Controls.PairedFieldsControl();
 			this.pfcFrontOffsetBottom = new CodeImp.DoomBuilder.Controls.PairedFieldsControl();
 			this.groupBox5 = new System.Windows.Forms.GroupBox();
+			this.lightfrontlower = new CodeImp.DoomBuilder.Controls.SidedefPartLightControl();
+			this.lightfrontmiddle = new CodeImp.DoomBuilder.Controls.SidedefPartLightControl();
+			this.lightfrontupper = new CodeImp.DoomBuilder.Controls.SidedefPartLightControl();
 			this.resetfrontlight = new System.Windows.Forms.Button();
 			this.frontsector = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
 			this.customfrontbutton = new System.Windows.Forms.Button();
@@ -92,6 +95,9 @@ namespace CodeImp.DoomBuilder.Windows
 			this.backside = new System.Windows.Forms.CheckBox();
 			this.backgroup = new System.Windows.Forms.GroupBox();
 			this.groupBox4 = new System.Windows.Forms.GroupBox();
+			this.lightbacklower = new CodeImp.DoomBuilder.Controls.SidedefPartLightControl();
+			this.lightbackmiddle = new CodeImp.DoomBuilder.Controls.SidedefPartLightControl();
+			this.lightbackupper = new CodeImp.DoomBuilder.Controls.SidedefPartLightControl();
 			this.resetbacklight = new System.Windows.Forms.Button();
 			this.backsector = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
 			this.custombackbutton = new System.Windows.Forms.Button();
@@ -164,7 +170,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// label11
 			// 
-			label11.Location = new System.Drawing.Point(12, 23);
+			label11.Location = new System.Drawing.Point(15, 23);
 			label11.Name = "label11";
 			label11.Size = new System.Drawing.Size(80, 14);
 			label11.TabIndex = 13;
@@ -173,7 +179,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// label12
 			// 
-			label12.Location = new System.Drawing.Point(12, 23);
+			label12.Location = new System.Drawing.Point(15, 23);
 			label12.Name = "label12";
 			label12.Size = new System.Drawing.Size(80, 14);
 			label12.TabIndex = 16;
@@ -209,7 +215,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// labelLightFront
 			// 
-			this.labelLightFront.Location = new System.Drawing.Point(12, 53);
+			this.labelLightFront.Location = new System.Drawing.Point(15, 53);
 			this.labelLightFront.Name = "labelLightFront";
 			this.labelLightFront.Size = new System.Drawing.Size(80, 14);
 			this.labelLightFront.TabIndex = 25;
@@ -242,8 +248,8 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// actiongroup
 			// 
-			this.actiongroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.actiongroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.actiongroup.Controls.Add(this.argscontrol);
 			this.actiongroup.Controls.Add(this.actionhelp);
 			this.actiongroup.Controls.Add(label2);
@@ -297,9 +303,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// udmfactivates
 			// 
-			this.udmfactivates.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.udmfactivates.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.udmfactivates.AutoScroll = true;
 			this.udmfactivates.Columns = 2;
 			this.udmfactivates.Location = new System.Drawing.Point(18, 17);
@@ -311,8 +317,8 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// flagsgroup
 			// 
-			this.flagsgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.flagsgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.flagsgroup.Controls.Add(this.flags);
 			this.flagsgroup.Location = new System.Drawing.Point(8, 3);
 			this.flagsgroup.Name = "flagsgroup";
@@ -323,9 +329,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// flags
 			// 
-			this.flags.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.flags.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.flags.AutoScroll = true;
 			this.flags.Columns = 3;
 			this.flags.Location = new System.Drawing.Point(18, 15);
@@ -337,9 +343,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// tabs
 			// 
-			this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.tabs.Controls.Add(this.tabproperties);
 			this.tabs.Controls.Add(this.tabfront);
 			this.tabs.Controls.Add(this.tabback);
@@ -373,8 +379,8 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// groupsettings
 			// 
-			this.groupsettings.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.groupsettings.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.groupsettings.Controls.Add(this.resetalpha);
 			this.groupsettings.Controls.Add(this.lockpick);
 			this.groupsettings.Controls.Add(this.alpha);
@@ -462,8 +468,8 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// idgroup
 			// 
-			this.idgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.idgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.idgroup.Controls.Add(this.tagsselector);
 			this.idgroup.Location = new System.Drawing.Point(8, 533);
 			this.idgroup.Name = "idgroup";
@@ -507,9 +513,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// frontgroup
 			// 
-			this.frontgroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.frontgroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.frontgroup.Controls.Add(this.frontflagsgroup);
 			this.frontgroup.Controls.Add(this.frontscalegroup);
 			this.frontgroup.Controls.Add(this.groupBox6);
@@ -528,23 +534,23 @@ namespace CodeImp.DoomBuilder.Windows
 			// frontflagsgroup
 			// 
 			this.frontflagsgroup.Controls.Add(this.flagsFront);
-			this.frontflagsgroup.Location = new System.Drawing.Point(12, 409);
+			this.frontflagsgroup.Location = new System.Drawing.Point(12, 490);
 			this.frontflagsgroup.Name = "frontflagsgroup";
-			this.frontflagsgroup.Size = new System.Drawing.Size(290, 190);
+			this.frontflagsgroup.Size = new System.Drawing.Size(290, 109);
 			this.frontflagsgroup.TabIndex = 45;
 			this.frontflagsgroup.TabStop = false;
 			this.frontflagsgroup.Text = " Flags ";
 			// 
 			// flagsFront
 			// 
-			this.flagsFront.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.flagsFront.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.flagsFront.AutoScroll = true;
 			this.flagsFront.Columns = 2;
 			this.flagsFront.Location = new System.Drawing.Point(16, 16);
 			this.flagsFront.Name = "flagsFront";
-			this.flagsFront.Size = new System.Drawing.Size(269, 168);
+			this.flagsFront.Size = new System.Drawing.Size(269, 87);
 			this.flagsFront.TabIndex = 0;
 			this.flagsFront.VerticalSpacing = 3;
 			this.flagsFront.OnValueChanged += new System.EventHandler(this.flagsFront_OnValueChanged);
@@ -557,7 +563,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.frontscalegroup.Controls.Add(this.pfcFrontScaleTop);
 			this.frontscalegroup.Controls.Add(this.pfcFrontScaleBottom);
 			this.frontscalegroup.Controls.Add(this.pfcFrontScaleMid);
-			this.frontscalegroup.Location = new System.Drawing.Point(12, 291);
+			this.frontscalegroup.Location = new System.Drawing.Point(12, 377);
 			this.frontscalegroup.Name = "frontscalegroup";
 			this.frontscalegroup.Size = new System.Drawing.Size(290, 112);
 			this.frontscalegroup.TabIndex = 44;
@@ -661,7 +667,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.groupBox6.Controls.Add(this.labelFrontOffsetTop);
 			this.groupBox6.Controls.Add(this.pfcFrontOffsetMid);
 			this.groupBox6.Controls.Add(this.pfcFrontOffsetBottom);
-			this.groupBox6.Location = new System.Drawing.Point(12, 142);
+			this.groupBox6.Location = new System.Drawing.Point(12, 228);
 			this.groupBox6.Name = "groupBox6";
 			this.groupBox6.Size = new System.Drawing.Size(290, 143);
 			this.groupBox6.TabIndex = 43;
@@ -780,6 +786,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// groupBox5
 			// 
+			this.groupBox5.Controls.Add(this.lightfrontlower);
+			this.groupBox5.Controls.Add(this.lightfrontmiddle);
+			this.groupBox5.Controls.Add(this.lightfrontupper);
 			this.groupBox5.Controls.Add(this.resetfrontlight);
 			this.groupBox5.Controls.Add(this.frontsector);
 			this.groupBox5.Controls.Add(label11);
@@ -789,14 +798,35 @@ namespace CodeImp.DoomBuilder.Windows
 			this.groupBox5.Controls.Add(this.cbLightAbsoluteFront);
 			this.groupBox5.Location = new System.Drawing.Point(12, 19);
 			this.groupBox5.Name = "groupBox5";
-			this.groupBox5.Size = new System.Drawing.Size(290, 117);
+			this.groupBox5.Size = new System.Drawing.Size(290, 203);
 			this.groupBox5.TabIndex = 42;
 			this.groupBox5.TabStop = false;
 			// 
+			// lightfrontlower
+			// 
+			this.lightfrontlower.Location = new System.Drawing.Point(0, 137);
+			this.lightfrontlower.Name = "lightfrontlower";
+			this.lightfrontlower.Size = new System.Drawing.Size(262, 29);
+			this.lightfrontlower.TabIndex = 29;
+			// 
+			// lightfrontmiddle
+			// 
+			this.lightfrontmiddle.Location = new System.Drawing.Point(0, 107);
+			this.lightfrontmiddle.Name = "lightfrontmiddle";
+			this.lightfrontmiddle.Size = new System.Drawing.Size(262, 29);
+			this.lightfrontmiddle.TabIndex = 29;
+			// 
+			// lightfrontupper
+			// 
+			this.lightfrontupper.Location = new System.Drawing.Point(0, 77);
+			this.lightfrontupper.Name = "lightfrontupper";
+			this.lightfrontupper.Size = new System.Drawing.Size(262, 29);
+			this.lightfrontupper.TabIndex = 29;
+			// 
 			// resetfrontlight
 			// 
 			this.resetfrontlight.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
-			this.resetfrontlight.Location = new System.Drawing.Point(233, 50);
+			this.resetfrontlight.Location = new System.Drawing.Point(236, 50);
 			this.resetfrontlight.Name = "resetfrontlight";
 			this.resetfrontlight.Size = new System.Drawing.Size(23, 23);
 			this.resetfrontlight.TabIndex = 28;
@@ -816,7 +846,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.frontsector.ButtonStepSmall = 0.1F;
 			this.frontsector.ButtonStepsUseModifierKeys = false;
 			this.frontsector.ButtonStepsWrapAround = false;
-			this.frontsector.Location = new System.Drawing.Point(96, 19);
+			this.frontsector.Location = new System.Drawing.Point(99, 19);
 			this.frontsector.Name = "frontsector";
 			this.frontsector.Size = new System.Drawing.Size(130, 24);
 			this.frontsector.StepValues = null;
@@ -824,7 +854,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// customfrontbutton
 			// 
-			this.customfrontbutton.Location = new System.Drawing.Point(96, 79);
+			this.customfrontbutton.Location = new System.Drawing.Point(98, 170);
 			this.customfrontbutton.Name = "customfrontbutton";
 			this.customfrontbutton.Size = new System.Drawing.Size(130, 25);
 			this.customfrontbutton.TabIndex = 3;
@@ -844,7 +874,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.lightFront.ButtonStepSmall = 1F;
 			this.lightFront.ButtonStepsUseModifierKeys = true;
 			this.lightFront.ButtonStepsWrapAround = false;
-			this.lightFront.Location = new System.Drawing.Point(96, 49);
+			this.lightFront.Location = new System.Drawing.Point(99, 49);
 			this.lightFront.Name = "lightFront";
 			this.lightFront.Size = new System.Drawing.Size(62, 24);
 			this.lightFront.StepValues = null;
@@ -855,7 +885,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// cbLightAbsoluteFront
 			// 
 			this.cbLightAbsoluteFront.AutoSize = true;
-			this.cbLightAbsoluteFront.Location = new System.Drawing.Point(164, 54);
+			this.cbLightAbsoluteFront.Location = new System.Drawing.Point(167, 54);
 			this.cbLightAbsoluteFront.Name = "cbLightAbsoluteFront";
 			this.cbLightAbsoluteFront.Size = new System.Drawing.Size(67, 17);
 			this.cbLightAbsoluteFront.TabIndex = 27;
@@ -924,9 +954,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// backgroup
 			// 
-			this.backgroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.backgroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.backgroup.Controls.Add(this.groupBox4);
 			this.backgroup.Controls.Add(this.backflagsgroup);
 			this.backgroup.Controls.Add(this.backscalegroup);
@@ -944,6 +974,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// groupBox4
 			// 
+			this.groupBox4.Controls.Add(this.lightbacklower);
+			this.groupBox4.Controls.Add(this.lightbackmiddle);
+			this.groupBox4.Controls.Add(this.lightbackupper);
 			this.groupBox4.Controls.Add(this.resetbacklight);
 			this.groupBox4.Controls.Add(this.backsector);
 			this.groupBox4.Controls.Add(label12);
@@ -953,14 +986,35 @@ namespace CodeImp.DoomBuilder.Windows
 			this.groupBox4.Controls.Add(this.cbLightAbsoluteBack);
 			this.groupBox4.Location = new System.Drawing.Point(12, 19);
 			this.groupBox4.Name = "groupBox4";
-			this.groupBox4.Size = new System.Drawing.Size(290, 117);
+			this.groupBox4.Size = new System.Drawing.Size(290, 203);
 			this.groupBox4.TabIndex = 46;
 			this.groupBox4.TabStop = false;
 			// 
+			// lightbacklower
+			// 
+			this.lightbacklower.Location = new System.Drawing.Point(0, 137);
+			this.lightbacklower.Name = "lightbacklower";
+			this.lightbacklower.Size = new System.Drawing.Size(262, 29);
+			this.lightbacklower.TabIndex = 32;
+			// 
+			// lightbackmiddle
+			// 
+			this.lightbackmiddle.Location = new System.Drawing.Point(0, 107);
+			this.lightbackmiddle.Name = "lightbackmiddle";
+			this.lightbackmiddle.Size = new System.Drawing.Size(262, 29);
+			this.lightbackmiddle.TabIndex = 33;
+			// 
+			// lightbackupper
+			// 
+			this.lightbackupper.Location = new System.Drawing.Point(0, 77);
+			this.lightbackupper.Name = "lightbackupper";
+			this.lightbackupper.Size = new System.Drawing.Size(262, 29);
+			this.lightbackupper.TabIndex = 34;
+			// 
 			// resetbacklight
 			// 
 			this.resetbacklight.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
-			this.resetbacklight.Location = new System.Drawing.Point(233, 50);
+			this.resetbacklight.Location = new System.Drawing.Point(236, 50);
 			this.resetbacklight.Name = "resetbacklight";
 			this.resetbacklight.Size = new System.Drawing.Size(23, 23);
 			this.resetbacklight.TabIndex = 31;
@@ -980,7 +1034,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.backsector.ButtonStepSmall = 0.1F;
 			this.backsector.ButtonStepsUseModifierKeys = false;
 			this.backsector.ButtonStepsWrapAround = false;
-			this.backsector.Location = new System.Drawing.Point(96, 19);
+			this.backsector.Location = new System.Drawing.Point(99, 19);
 			this.backsector.Name = "backsector";
 			this.backsector.Size = new System.Drawing.Size(130, 24);
 			this.backsector.StepValues = null;
@@ -988,7 +1042,8 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// custombackbutton
 			// 
-			this.custombackbutton.Location = new System.Drawing.Point(96, 79);
+			this.custombackbutton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+			this.custombackbutton.Location = new System.Drawing.Point(98, 170);
 			this.custombackbutton.Name = "custombackbutton";
 			this.custombackbutton.Size = new System.Drawing.Size(130, 25);
 			this.custombackbutton.TabIndex = 3;
@@ -1008,7 +1063,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.lightBack.ButtonStepSmall = 1F;
 			this.lightBack.ButtonStepsUseModifierKeys = true;
 			this.lightBack.ButtonStepsWrapAround = false;
-			this.lightBack.Location = new System.Drawing.Point(96, 49);
+			this.lightBack.Location = new System.Drawing.Point(99, 49);
 			this.lightBack.Name = "lightBack";
 			this.lightBack.Size = new System.Drawing.Size(62, 24);
 			this.lightBack.StepValues = null;
@@ -1018,7 +1073,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// labelLightBack
 			// 
-			this.labelLightBack.Location = new System.Drawing.Point(12, 53);
+			this.labelLightBack.Location = new System.Drawing.Point(15, 53);
 			this.labelLightBack.Name = "labelLightBack";
 			this.labelLightBack.Size = new System.Drawing.Size(80, 14);
 			this.labelLightBack.TabIndex = 28;
@@ -1029,7 +1084,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// cbLightAbsoluteBack
 			// 
 			this.cbLightAbsoluteBack.AutoSize = true;
-			this.cbLightAbsoluteBack.Location = new System.Drawing.Point(164, 54);
+			this.cbLightAbsoluteBack.Location = new System.Drawing.Point(167, 54);
 			this.cbLightAbsoluteBack.Name = "cbLightAbsoluteBack";
 			this.cbLightAbsoluteBack.Size = new System.Drawing.Size(67, 17);
 			this.cbLightAbsoluteBack.TabIndex = 30;
@@ -1041,23 +1096,23 @@ namespace CodeImp.DoomBuilder.Windows
 			// backflagsgroup
 			// 
 			this.backflagsgroup.Controls.Add(this.flagsBack);
-			this.backflagsgroup.Location = new System.Drawing.Point(12, 409);
+			this.backflagsgroup.Location = new System.Drawing.Point(12, 490);
 			this.backflagsgroup.Name = "backflagsgroup";
-			this.backflagsgroup.Size = new System.Drawing.Size(290, 190);
+			this.backflagsgroup.Size = new System.Drawing.Size(290, 109);
 			this.backflagsgroup.TabIndex = 45;
 			this.backflagsgroup.TabStop = false;
 			this.backflagsgroup.Text = " Flags ";
 			// 
 			// flagsBack
 			// 
-			this.flagsBack.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.flagsBack.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.flagsBack.AutoScroll = true;
 			this.flagsBack.Columns = 2;
 			this.flagsBack.Location = new System.Drawing.Point(16, 16);
 			this.flagsBack.Name = "flagsBack";
-			this.flagsBack.Size = new System.Drawing.Size(269, 168);
+			this.flagsBack.Size = new System.Drawing.Size(269, 87);
 			this.flagsBack.TabIndex = 1;
 			this.flagsBack.VerticalSpacing = 3;
 			this.flagsBack.OnValueChanged += new System.EventHandler(this.flagsBack_OnValueChanged);
@@ -1070,7 +1125,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.backscalegroup.Controls.Add(this.pfcBackScaleTop);
 			this.backscalegroup.Controls.Add(this.pfcBackScaleBottom);
 			this.backscalegroup.Controls.Add(this.pfcBackScaleMid);
-			this.backscalegroup.Location = new System.Drawing.Point(12, 291);
+			this.backscalegroup.Location = new System.Drawing.Point(12, 377);
 			this.backscalegroup.Name = "backscalegroup";
 			this.backscalegroup.Size = new System.Drawing.Size(290, 112);
 			this.backscalegroup.TabIndex = 44;
@@ -1174,7 +1229,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.groupBox1.Controls.Add(this.pfcBackOffsetMid);
 			this.groupBox1.Controls.Add(this.pfcBackOffsetBottom);
 			this.groupBox1.Controls.Add(this.backTextureOffset);
-			this.groupBox1.Location = new System.Drawing.Point(12, 142);
+			this.groupBox1.Location = new System.Drawing.Point(12, 228);
 			this.groupBox1.Name = "groupBox1";
 			this.groupBox1.Size = new System.Drawing.Size(290, 143);
 			this.groupBox1.TabIndex = 43;
@@ -1337,9 +1392,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			// commenteditor
 			// 
-			this.commenteditor.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.commenteditor.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.commenteditor.Location = new System.Drawing.Point(3, 3);
 			this.commenteditor.Name = "commenteditor";
 			this.commenteditor.Size = new System.Drawing.Size(543, 615);
@@ -1361,9 +1416,9 @@ namespace CodeImp.DoomBuilder.Windows
 			// fieldslist
 			// 
 			this.fieldslist.AllowInsert = true;
-			this.fieldslist.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.fieldslist.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.fieldslist.AutoInsertUserPrefix = true;
 			this.fieldslist.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
 			this.fieldslist.Location = new System.Drawing.Point(11, 11);
@@ -1399,7 +1454,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.MaximizeBox = false;
 			this.MinimizeBox = false;
 			this.Name = "LinedefEditFormUDMF";
-			this.Opacity = 0;
+			this.Opacity = 0D;
 			this.ShowIcon = false;
 			this.ShowInTaskbar = false;
 			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
@@ -1531,5 +1586,11 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.Button resetfrontlight;
 		private System.Windows.Forms.Button resetbacklight;
 		private System.Windows.Forms.Button resetalpha;
+		private Controls.SidedefPartLightControl lightfrontupper;
+		private Controls.SidedefPartLightControl lightfrontlower;
+		private Controls.SidedefPartLightControl lightfrontmiddle;
+		private Controls.SidedefPartLightControl lightbacklower;
+		private Controls.SidedefPartLightControl lightbackmiddle;
+		private Controls.SidedefPartLightControl lightbackupper;
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/LinedefEditFormUDMF.cs b/Source/Core/Windows/LinedefEditFormUDMF.cs
index b96615d409f8853efdcd3ce6db25209ce2ebebcc..ecd1a41f2aed01d8e517ca4b8081d19ee78bf547 100755
--- a/Source/Core/Windows/LinedefEditFormUDMF.cs
+++ b/Source/Core/Windows/LinedefEditFormUDMF.cs
@@ -218,8 +218,18 @@ namespace CodeImp.DoomBuilder.Windows
 			pfcBackScaleMid.LinkValues = General.Settings.ReadSetting("windows." + configname + ".linkbackmidscale", false);
 			pfcBackScaleBottom.LinkValues = General.Settings.ReadSetting("windows." + configname + ".linkbackbottomscale", false);
 
+			// Upper/middle/lower brightness of front sidedef
+			lightfrontupper.Setup(VisualModes.VisualGeometryType.WALL_UPPER);
+			lightfrontmiddle.Setup(VisualModes.VisualGeometryType.WALL_MIDDLE);
+			lightfrontlower.Setup(VisualModes.VisualGeometryType.WALL_LOWER);
+
+			// Upper/middle/lower brightness of back sidedef
+			lightbackupper.Setup(VisualModes.VisualGeometryType.WALL_UPPER);
+			lightbackmiddle.Setup(VisualModes.VisualGeometryType.WALL_MIDDLE);
+			lightbacklower.Setup(VisualModes.VisualGeometryType.WALL_LOWER);
+
 			// Disable top/mid/bottom texture offset controls?
-			if(!General.Map.Config.UseLocalSidedefTextureOffsets)
+			if (!General.Map.Config.UseLocalSidedefTextureOffsets)
 			{
 				pfcFrontOffsetTop.Enabled = false;
 				pfcFrontOffsetMid.Enabled = false;
@@ -349,6 +359,11 @@ namespace CodeImp.DoomBuilder.Windows
 				cbLightAbsoluteFront.Checked = fl.Front.Fields.GetValue("lightabsolute", false);
 
 				frontTextureOffset.SetValues(fl.Front.OffsetX, fl.Front.OffsetY, true); //mxd
+
+				// Upper/middle/lower brightness of front sidedef
+				lightfrontupper.SetValues(fl.Front, true);
+				lightfrontmiddle.SetValues(fl.Front, true);
+				lightfrontlower.SetValues(fl.Front, true);
 			}
 
 			// Back settings
@@ -374,6 +389,11 @@ namespace CodeImp.DoomBuilder.Windows
 				cbLightAbsoluteBack.Checked = fl.Back.Fields.GetValue("lightabsolute", false);
  
 				backTextureOffset.SetValues(fl.Back.OffsetX, fl.Back.OffsetY, true); //mxd
+
+				// Upper/middle/lower brightness of back sidedef
+				lightbackupper.SetValues(fl.Back, true);
+				lightbackmiddle.SetValues(fl.Back, true);
+				lightbacklower.SetValues(fl.Back, true);
 			}
 
 			////////////////////////////////////////////////////////////////////////
@@ -381,7 +401,7 @@ namespace CodeImp.DoomBuilder.Windows
 			////////////////////////////////////////////////////////////////////////
 
 			// Go for all lines
-			foreach(Linedef l in lines)
+			foreach (Linedef l in lines)
 			{
 				// Flags
 				foreach(CheckBox c in flags.Checkboxes)
@@ -515,6 +535,11 @@ namespace CodeImp.DoomBuilder.Windows
 					}
 
 					frontTextureOffset.SetValues(l.Front.OffsetX, l.Front.OffsetY, false); //mxd
+
+					// Upper/middle/lower brightness of front sidedef
+					lightfrontupper.SetValues(l.Front, false);
+					lightfrontmiddle.SetValues(l.Front, false);
+					lightfrontlower.SetValues(l.Front, false);
 				}
 
 				// Back settings
@@ -570,6 +595,11 @@ namespace CodeImp.DoomBuilder.Windows
 					}
 
 					backTextureOffset.SetValues(l.Back.OffsetX, l.Back.OffsetY, false); //mxd
+
+					// Upper/middle/lower brightness of back sidedef
+					lightbackupper.SetValues(l.Back, false);
+					lightbackmiddle.SetValues(l.Back, false);
+					lightbacklower.SetValues(l.Back, false);
 				}
 
 				//mxd
@@ -598,10 +628,20 @@ namespace CodeImp.DoomBuilder.Windows
 			resetfrontlight.Visible = (cbLightAbsoluteFront.CheckState != CheckState.Unchecked || lightFront.GetResult(0) != 0);
 			resetbacklight.Visible = (cbLightAbsoluteBack.CheckState != CheckState.Unchecked || lightBack.GetResult(0) != 0);
 			if(alpha.Text == "1") resetalpha.Visible = false;
+
+			// Upper/middle/lower brightness of front sidedef
+			lightfrontupper.FinalizeSetup();
+			lightfrontmiddle.FinalizeSetup();
+			lightfrontlower.FinalizeSetup();
+
+			// Upper/middle/lower brightness of back sidedef
+			lightbackupper.FinalizeSetup();
+			lightbackmiddle.FinalizeSetup();
+			lightbacklower.FinalizeSetup();
 		}
 
 		//mxd
-		private void MakeUndo() 
+		public void MakeUndo() 
 		{
 			if(undocreated) return;
 			undocreated = true;
@@ -656,6 +696,14 @@ namespace CodeImp.DoomBuilder.Windows
 			}
 		}
 
+		/// <summary>
+		/// Runs the OnValuesChanged event. Use after updating properties from other controls.
+		/// </summary>
+		public void ValuesChangedExternal()
+		{
+			OnValuesChanged?.Invoke(this, EventArgs.Empty);
+		}
+
 		#endregion
 
 		#region ================== Events
@@ -1952,9 +2000,8 @@ namespace CodeImp.DoomBuilder.Windows
 			if(OnValuesChanged != null) OnValuesChanged(this, EventArgs.Empty);
 		}
 
-			#endregion
-
 		#endregion
 
+		#endregion
 	}
 }
diff --git a/Source/Core/Windows/LinedefEditFormUDMF.resx b/Source/Core/Windows/LinedefEditFormUDMF.resx
index 47535a86742aaf31a3c55907ff0c160908a1efff..e04828b071eb10435a12666927a6d4e2d5036919 100755
--- a/Source/Core/Windows/LinedefEditFormUDMF.resx
+++ b/Source/Core/Windows/LinedefEditFormUDMF.resx
@@ -112,41 +112,41 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
-  <metadata name="label2.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label2.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label11.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label11.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label12.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label12.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="label6.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="label6.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
-  <metadata name="tabproperties.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="tabproperties.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="tabcustom.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="tabcustom.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="imagelist.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="imagelist.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>105, 17</value>
   </metadata>
   <data name="imagelist.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
     <value>
-        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj0yLjAuMC4w
+        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
         LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
         ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADM
-        CAAAAk1TRnQBSQFMAgEBAgEAAcgBAAHIAQABEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo
+        CAAAAk1TRnQBSQFMAgEBAgEAAQgBAQEIAQEBEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo
         AwABQAMAARADAAEBAQABCAYAAQQYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA
         AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5
         AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA
@@ -186,4 +186,7 @@
         BAAB/wKAAQEEAAH/AcECgQQAAf8B4wLDBAAC/wLnBAAE/wQACw==
 </value>
   </data>
+  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Core/Windows/MainForm.Designer.cs b/Source/Core/Windows/MainForm.Designer.cs
index 90e0deefe67c0a367a0066b752aa7ed5d5a8189a..8da269debf2bc34a4a7133984c6e467ff666aa58 100755
--- a/Source/Core/Windows/MainForm.Designer.cs
+++ b/Source/Core/Windows/MainForm.Designer.cs
@@ -162,21 +162,21 @@ namespace CodeImp.DoomBuilder.Windows
 			this.toggleGeometry = new System.Windows.Forms.ToolStripMenuItem();
 			this.toggleTesting = new System.Windows.Forms.ToolStripMenuItem();
 			this.toggleRendering = new System.Windows.Forms.ToolStripMenuItem();
-			this.buttonnewmap = new System.Windows.Forms.ToolStripButton();
-			this.buttonopenmap = new System.Windows.Forms.ToolStripButton();
-			this.buttonsavemap = new System.Windows.Forms.ToolStripButton();
-			this.buttonscripteditor = new System.Windows.Forms.ToolStripButton();
-			this.buttonundo = new System.Windows.Forms.ToolStripButton();
-			this.buttonredo = new System.Windows.Forms.ToolStripButton();
-			this.buttoncut = new System.Windows.Forms.ToolStripButton();
-			this.buttoncopy = new System.Windows.Forms.ToolStripButton();
-			this.buttonpaste = new System.Windows.Forms.ToolStripButton();
-			this.buttoninsertprefabfile = new System.Windows.Forms.ToolStripButton();
-			this.buttoninsertpreviousprefab = new System.Windows.Forms.ToolStripButton();
-			this.buttonthingsfilter = new System.Windows.Forms.ToolStripButton();
+			this.buttonnewmap = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonopenmap = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonsavemap = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonscripteditor = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonundo = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonredo = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttoncut = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttoncopy = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonpaste = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttoninsertprefabfile = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttoninsertpreviousprefab = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonthingsfilter = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.thingfilters = new System.Windows.Forms.ToolStripDropDownButton();
 			this.separatorlinecolors = new System.Windows.Forms.ToolStripSeparator();
-			this.buttonlinededfcolors = new System.Windows.Forms.ToolStripButton();
+			this.buttonlinededfcolors = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.linedefcolorpresets = new System.Windows.Forms.ToolStripDropDownButton();
 			this.separatorfilters = new System.Windows.Forms.ToolStripSeparator();
 			this.separatorrendering = new System.Windows.Forms.ToolStripSeparator();
@@ -194,25 +194,25 @@ namespace CodeImp.DoomBuilder.Windows
 			this.itemtoggleclassicrendering = new System.Windows.Forms.ToolStripMenuItem();
 			this.itemtoggleeventlines = new System.Windows.Forms.ToolStripMenuItem();
 			this.itemtogglevisualverts = new System.Windows.Forms.ToolStripMenuItem();
-			this.buttonfullbrightness = new System.Windows.Forms.ToolStripButton();
-			this.buttontogglegrid = new System.Windows.Forms.ToolStripButton();
-			this.buttontoggledynamicgrid = new System.Windows.Forms.ToolStripButton();
+			this.buttonfullbrightness = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttontogglegrid = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttontoggledynamicgrid = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.separatorfullbrightness = new System.Windows.Forms.ToolStripSeparator();
-			this.buttonviewnormal = new System.Windows.Forms.ToolStripButton();
-			this.buttonviewbrightness = new System.Windows.Forms.ToolStripButton();
-			this.buttonviewfloors = new System.Windows.Forms.ToolStripButton();
-			this.buttonviewceilings = new System.Windows.Forms.ToolStripButton();
+			this.buttonviewnormal = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonviewbrightness = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonviewfloors = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonviewceilings = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.separatorgeomergemodes = new System.Windows.Forms.ToolStripSeparator();
-			this.buttonmergegeoclassic = new System.Windows.Forms.ToolStripButton();
-			this.buttonmergegeo = new System.Windows.Forms.ToolStripButton();
-			this.buttonplacegeo = new System.Windows.Forms.ToolStripButton();
+			this.buttonmergegeoclassic = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonmergegeo = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonplacegeo = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.seperatorviews = new System.Windows.Forms.ToolStripSeparator();
-			this.buttontogglecomments = new System.Windows.Forms.ToolStripButton();
-			this.buttontogglefixedthingsscale = new System.Windows.Forms.ToolStripButton();
-			this.buttonsnaptogrid = new System.Windows.Forms.ToolStripButton();
-			this.buttonautomerge = new System.Windows.Forms.ToolStripButton();
-			this.buttonsplitjoinedsectors = new System.Windows.Forms.ToolStripButton();
-			this.buttonautoclearsidetextures = new System.Windows.Forms.ToolStripButton();
+			this.buttontogglecomments = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttontogglefixedthingsscale = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonsnaptogrid = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonautomerge = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonsplitjoinedsectors = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonautoclearsidetextures = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.seperatorgeometry = new System.Windows.Forms.ToolStripSeparator();
 			this.dynamiclightmode = new System.Windows.Forms.ToolStripSplitButton();
 			this.sightsdontshow = new System.Windows.Forms.ToolStripMenuItem();
@@ -223,11 +223,11 @@ namespace CodeImp.DoomBuilder.Windows
 			this.modelsshowselection = new System.Windows.Forms.ToolStripMenuItem();
 			this.modelsshowfiltered = new System.Windows.Forms.ToolStripMenuItem();
 			this.modelsshowall = new System.Windows.Forms.ToolStripMenuItem();
-			this.buttontogglefog = new System.Windows.Forms.ToolStripButton();
-			this.buttontogglesky = new System.Windows.Forms.ToolStripButton();
-			this.buttontoggleclassicrendering = new System.Windows.Forms.ToolStripButton();
-			this.buttontoggleeventlines = new System.Windows.Forms.ToolStripButton();
-			this.buttontogglevisualvertices = new System.Windows.Forms.ToolStripButton();
+			this.buttontogglefog = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttontogglesky = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttontoggleclassicrendering = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttontoggleeventlines = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttontogglevisualvertices = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.separatorgzmodes = new System.Windows.Forms.ToolStripSeparator();
 			this.buttontest = new System.Windows.Forms.ToolStripSplitButton();
 			this.seperatortesting = new System.Windows.Forms.ToolStripSeparator();
@@ -288,6 +288,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.modecontrolsloolbar = new System.Windows.Forms.ToolStrip();
 			this.itemtogglecomments = new System.Windows.Forms.ToolStripMenuItem();
 			this.itemtogglefixedthingsscale = new System.Windows.Forms.ToolStripMenuItem();
+			this.itemtogglealwaysshowvertices = new System.Windows.Forms.ToolStripMenuItem();
 			this.itemdynamicgridsize = new System.Windows.Forms.ToolStripMenuItem();
 			this.itemaligngridtolinedef = new System.Windows.Forms.ToolStripMenuItem();
 			this.itemsetgridorigintovertex = new System.Windows.Forms.ToolStripMenuItem();
@@ -795,6 +796,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.itemtogglegrid,
 			this.itemtogglecomments,
 			this.itemtogglefixedthingsscale,
+			this.itemtogglealwaysshowvertices,
 			this.separatorrendering,
 			this.itemdynlightmodes,
 			this.itemmodelmodes,
@@ -1640,7 +1642,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.thingfilters.ImageTransparentColor = System.Drawing.Color.Magenta;
 			this.thingfilters.Margin = new System.Windows.Forms.Padding(1, 1, 0, 2);
 			this.thingfilters.Name = "thingfilters";
-			this.thingfilters.Size = new System.Drawing.Size((int)(120 * MainForm.DPIScaler.Width), (int)(22 * MainForm.DPIScaler.Height));
+			this.thingfilters.Size = new System.Drawing.Size(120, 22);
 			this.thingfilters.Text = "(show all)";
 			this.thingfilters.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
 			this.thingfilters.DropDownClosed += new System.EventHandler(this.LoseFocus);
@@ -1670,7 +1672,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.linedefcolorpresets.ImageTransparentColor = System.Drawing.Color.Magenta;
 			this.linedefcolorpresets.Margin = new System.Windows.Forms.Padding(1, 1, 0, 2);
 			this.linedefcolorpresets.Name = "linedefcolorpresets";
-			this.linedefcolorpresets.Size = new System.Drawing.Size((int)(120 * MainForm.DPIScaler.Width), (int)(22 * MainForm.DPIScaler.Height));
+			this.linedefcolorpresets.Size = new System.Drawing.Size(120, 22);
 			this.linedefcolorpresets.Text = "No presets";
 			this.linedefcolorpresets.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
 			this.linedefcolorpresets.DropDownItemClicked += new System.Windows.Forms.ToolStripItemClickedEventHandler(this.linedefcolorpresets_DropDownItemClicked);
@@ -2282,7 +2284,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.configlabel.AutoSize = false;
 			this.configlabel.Font = new System.Drawing.Font("Verdana", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
 			this.configlabel.Name = "configlabel";
-			this.configlabel.Size = new System.Drawing.Size((int)(280 * MainForm.DPIScaler.Width), (int)(18 * MainForm.DPIScaler.Height));
+			this.configlabel.Size = new System.Drawing.Size(280, 18);
 			this.configlabel.Text = "ZDoom (Doom in Hexen Format)";
 			this.configlabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
 			this.configlabel.ToolTipText = "Current Game Configuration";
@@ -2831,6 +2833,17 @@ namespace CodeImp.DoomBuilder.Windows
 			this.itemtogglefixedthingsscale.Text = "Fixed Things Scale";
 			this.itemtogglefixedthingsscale.Click += new System.EventHandler(this.InvokeTaggedAction);
 			// 
+			// itemtogglealwaysshowvertices
+			// 
+			this.itemtogglealwaysshowvertices.Checked = true;
+			this.itemtogglealwaysshowvertices.CheckOnClick = true;
+			this.itemtogglealwaysshowvertices.CheckState = System.Windows.Forms.CheckState.Checked;
+			this.itemtogglealwaysshowvertices.Name = "itemtogglealwaysshowvertices";
+			this.itemtogglealwaysshowvertices.Size = new System.Drawing.Size(215, 22);
+			this.itemtogglealwaysshowvertices.Tag = "builder_togglealwaysshowvertices";
+			this.itemtogglealwaysshowvertices.Text = "Always Show Vertices";
+			this.itemtogglealwaysshowvertices.Click += new System.EventHandler(this.InvokeTaggedAction);
+			// 
 			// itemdynamicgridsize
 			// 
 			this.itemdynamicgridsize.Checked = true;
@@ -2907,9 +2920,9 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStripMenuItem itemnorecent;
 		private System.Windows.Forms.ToolStripStatusLabel xposlabel;
 		private System.Windows.Forms.ToolStripStatusLabel yposlabel;
-		private System.Windows.Forms.ToolStripButton buttonnewmap;
-		private System.Windows.Forms.ToolStripButton buttonopenmap;
-		private System.Windows.Forms.ToolStripButton buttonsavemap;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonnewmap;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonopenmap;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonsavemap;
 		private System.Windows.Forms.ToolStripStatusLabel zoomlabel;
 		private System.Windows.Forms.ToolStripDropDownButton buttonzoom;
 		private System.Windows.Forms.ToolStripMenuItem itemzoomfittoscreen;
@@ -2929,7 +2942,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private CodeImp.DoomBuilder.Controls.VertexInfoPanel vertexinfo;
 		private CodeImp.DoomBuilder.Controls.SectorInfoPanel sectorinfo;
 		private CodeImp.DoomBuilder.Controls.ThingInfoPanel thinginfo;
-		private System.Windows.Forms.ToolStripButton buttonthingsfilter;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonthingsfilter;
 		private System.Windows.Forms.ToolStripSeparator seperatorviews;
 		private System.Windows.Forms.ToolStripStatusLabel gridlabel;
 		private System.Windows.Forms.ToolStripDropDownButton buttongrid;
@@ -2946,13 +2959,13 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStripStatusLabel poscommalabel;
 		private System.Windows.Forms.ToolStripMenuItem itemundo;
 		private System.Windows.Forms.ToolStripMenuItem itemredo;
-		private System.Windows.Forms.ToolStripButton buttonundo;
-		private System.Windows.Forms.ToolStripButton buttonredo;
-		private System.Windows.Forms.ToolStripButton buttonsnaptogrid;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonundo;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonredo;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonsnaptogrid;
 		private System.Windows.Forms.ToolStripMenuItem itemsnaptogrid;
-		private System.Windows.Forms.ToolStripButton buttonautomerge;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonautomerge;
 		private System.Windows.Forms.ToolStripMenuItem itemautomerge;
-		private System.Windows.Forms.ToolStripButton buttonsplitjoinedsectors;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonsplitjoinedsectors;
 		private System.Windows.Forms.ToolStripMenuItem itemsplitjoinedsectors;
 		private System.Windows.Forms.Timer processor;
 		private System.Windows.Forms.ToolStripSeparator separatorgzmodes;
@@ -2964,25 +2977,25 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStripMenuItem itemgridsetup;
 		private System.Windows.Forms.Timer statusflasher;
 		private System.Windows.Forms.ToolStripSplitButton buttontest;
-		private System.Windows.Forms.ToolStripButton buttoncut;
-		private System.Windows.Forms.ToolStripButton buttoncopy;
-		private System.Windows.Forms.ToolStripButton buttonpaste;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttoncut;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttoncopy;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonpaste;
 		private System.Windows.Forms.ToolStripSeparator seperatoreditundo;
 		private System.Windows.Forms.ToolStripMenuItem itemcut;
 		private System.Windows.Forms.ToolStripMenuItem itemcopy;
 		private System.Windows.Forms.ToolStripMenuItem itempaste;
 		private System.Windows.Forms.ToolStripStatusLabel configlabel;
 		private System.Windows.Forms.ToolStripMenuItem menumode;
-		private System.Windows.Forms.ToolStripButton buttonviewnormal;
-		private System.Windows.Forms.ToolStripButton buttonviewbrightness;
-		private System.Windows.Forms.ToolStripButton buttonviewfloors;
-		private System.Windows.Forms.ToolStripButton buttonviewceilings;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonviewnormal;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonviewbrightness;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonviewfloors;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonviewceilings;
 		private System.Windows.Forms.ToolStripSeparator separatorgeomergemodes;
-		private System.Windows.Forms.ToolStripButton buttonmergegeoclassic;
-		private System.Windows.Forms.ToolStripButton buttonmergegeo;
-		private System.Windows.Forms.ToolStripButton buttonplacegeo;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonmergegeoclassic;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonmergegeo;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonplacegeo;
 		private System.Windows.Forms.ToolStripSeparator seperatortoolsresources;
-		private System.Windows.Forms.ToolStripButton buttonscripteditor;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonscripteditor;
 		private System.Windows.Forms.ToolStripMenuItem menuview;
 		private System.Windows.Forms.ToolStripMenuItem itemthingsfilter;
 		private System.Windows.Forms.ToolStripSeparator seperatorviewthings;
@@ -3003,8 +3016,8 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStripSeparator seperatorprefabsinsert;
 		private System.Windows.Forms.ToolStripMenuItem iteminsertprefabfile;
 		private System.Windows.Forms.ToolStripMenuItem iteminsertpreviousprefab;
-		private System.Windows.Forms.ToolStripButton buttoninsertprefabfile;
-		private System.Windows.Forms.ToolStripButton buttoninsertpreviousprefab;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttoninsertprefabfile;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttoninsertpreviousprefab;
 		private System.Windows.Forms.Button buttontoggleinfo;
 		private System.Windows.Forms.Label labelcollapsedinfo;
 		private System.Windows.Forms.Timer statusresetter;
@@ -3037,15 +3050,15 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStripSeparator seperatoreditgrid;
 		private System.Windows.Forms.ToolStripSeparator seperatoreditcopypaste;
 		private System.Windows.Forms.ToolStripSeparator seperatorgeometry;
-		private System.Windows.Forms.ToolStripButton buttontogglefog;
-		private System.Windows.Forms.ToolStripButton buttontogglesky;
-		private System.Windows.Forms.ToolStripButton buttontoggleclassicrendering;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttontogglefog;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttontogglesky;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttontoggleclassicrendering;
 		private System.Windows.Forms.ToolStripStatusLabel warnsLabel;
 		private System.Windows.Forms.ToolStripMenuItem itemReloadModedef;
 		private System.Windows.Forms.ToolStripMenuItem itemReloadGldefs;
 		private System.Windows.Forms.ToolStripSeparator separatorDrawModes;
-		private System.Windows.Forms.ToolStripButton buttontoggleeventlines;
-		private System.Windows.Forms.ToolStripButton buttontogglevisualvertices;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttontoggleeventlines;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttontogglevisualvertices;
 		private System.Windows.Forms.ToolStripMenuItem itemviewusedtags;
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;
 		private System.Windows.Forms.ToolStripMenuItem addToGroup;
@@ -3075,7 +3088,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStripMenuItem itemzoom400;
 		private System.Windows.Forms.Label modename;
 		private System.Windows.Forms.ToolStripMenuItem itemautoclearsidetextures;
-		private System.Windows.Forms.ToolStripButton buttonautoclearsidetextures;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonautoclearsidetextures;
 		private System.Windows.Forms.ToolStripMenuItem itemgotocoords;
 		private System.Windows.Forms.ToolStripSeparator separatorTransformModes;
 		private System.Windows.Forms.ToolStripMenuItem itemdosnaptogrid;
@@ -3084,7 +3097,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.ToolStrip modecontrolsloolbar;
 		private System.Windows.Forms.ToolStripMenuItem itemfullbrightness;
 		private System.Windows.Forms.ToolStripSeparator separatorhelpers;
-		private System.Windows.Forms.ToolStripButton buttonfullbrightness;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonfullbrightness;
 		private System.Windows.Forms.ToolStripSeparator separatorfullbrightness;
 		private System.Windows.Forms.ToolStripSeparator separatorfilters;
 		private System.Windows.Forms.ToolStripSeparator separatorrendering;
@@ -3136,5 +3149,6 @@ namespace CodeImp.DoomBuilder.Windows
 		private ToolStripMenuItem itemdynamicgridsize;
 		private ToolStripMenuItem itemtogglecomments;
 		private ToolStripMenuItem itemtogglefixedthingsscale;
+		private ToolStripMenuItem itemtogglealwaysshowvertices;
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs
index 71bc4ddeda347213963af426e917a7e20d98dc55..0c9e171e2569ee068349ac728526852367db64a0 100755
--- a/Source/Core/Windows/MainForm.cs
+++ b/Source/Core/Windows/MainForm.cs
@@ -159,6 +159,8 @@ namespace CodeImp.DoomBuilder.Windows
 
 		//mxd. Misc drawing
 		private Graphics graphics;
+
+		private CommandPaletteControl commandpalette;
 		
 		#endregion
 
@@ -181,7 +183,7 @@ namespace CodeImp.DoomBuilder.Windows
 		public static Size ScaledIconSize = new Size(16, 16); //mxd
 		public static SizeF DPIScaler = new SizeF(1.0f, 1.0f); //mxd
 		public int ProcessingCount { get { return processingcount; } }
-		
+
 		#endregion
 
 		#region ================== Constructor / Disposer
@@ -215,6 +217,10 @@ namespace CodeImp.DoomBuilder.Windows
 				xposlabel.Width = (int)Math.Round(xposlabel.Width * DPIScaler.Width);
 				yposlabel.Width = (int)Math.Round(yposlabel.Width * DPIScaler.Width);
 				warnsLabel.Width = (int)Math.Round(warnsLabel.Width * DPIScaler.Width);
+
+				thingfilters.Size = new Size((int)(120 * DPIScaler.Width), (int)(22 * DPIScaler.Height));
+				linedefcolorpresets.Size = new Size((int)(120 * DPIScaler.Width), (int)(22 * DPIScaler.Height));
+				configlabel.Size = new Size((int)(280 * DPIScaler.Width), (int)(18 * DPIScaler.Height));
 			}
 
 			pluginbuttons = new List<PluginToolbarButton>();
@@ -461,11 +467,28 @@ namespace CodeImp.DoomBuilder.Windows
 			
 			this.Update();
 		}
-		
+
+		// We're doing it in EndAction because it'll otherwise screw with the stored keys
+		[EndAction("opencommandpalette")]
+		public void OpenCommandPalette()
+		{
+			if (commandpalette == null)
+			{
+				// We have to add the command palette control manually because trying to use the designer will make the form explode
+				commandpalette = new CommandPaletteControl();
+				Controls.Add(commandpalette);
+
+				// Send it somewhere to the background
+				Controls.SetChildIndex(commandpalette, 0xffff);
+			}
+
+			commandpalette.MakeVisible();
+		}
+
 		#endregion
-		
+
 		#region ================== Window
-		
+
 		// This locks the window for updating
 		internal void LockUpdate()
 		{
@@ -523,10 +546,11 @@ namespace CodeImp.DoomBuilder.Windows
 				if(General.AutoLoadMap != null)
 				{
 					Configuration mapsettings;
-					
+
 					// Try to find existing options in the settings file
-					string dbsfile = General.AutoLoadFile.Substring(0, General.AutoLoadFile.Length - 4) + ".dbs";
-					if(File.Exists(dbsfile))
+					//string dbsfile = General.AutoLoadFile.Substring(0, General.AutoLoadFile.Length - 4) + ".dbs";
+					string dbsfile = Path.ChangeExtension(General.AutoLoadFile, "dbs");
+					if (File.Exists(dbsfile))
 						try { mapsettings = new Configuration(dbsfile, true); }
 						catch(Exception) { mapsettings = new Configuration(true); }
 					else
@@ -534,8 +558,13 @@ namespace CodeImp.DoomBuilder.Windows
 
 					//mxd. Get proper configuration file
 					bool longtexturenamessupported = false;
-					string configfile = General.AutoLoadConfig;
-					if(string.IsNullOrEmpty(configfile)) configfile = mapsettings.ReadSetting("gameconfig", "");
+					string configfile = null;
+
+					// Make sure the config file exists
+					if(General.GetConfigurationInfo(General.AutoLoadConfig) != null)
+						configfile = General.AutoLoadConfig;
+
+					if (string.IsNullOrEmpty(configfile)) configfile = mapsettings.ReadSetting("gameconfig", "");
 					if(configfile.Trim().Length == 0)
 					{
 						showdialog = true;
@@ -721,9 +750,9 @@ namespace CodeImp.DoomBuilder.Windows
 		}
 
 		#endregion
-		
+
 		#region ================== Statusbar
-		
+
 		// This updates the status bar
 		private void UpdateStatusbar()
 		{
@@ -1158,7 +1187,7 @@ namespace CodeImp.DoomBuilder.Windows
 			{
 				General.Plugins.OnEditMouseEnter(e);
 				General.Editing.Mode.OnMouseEnter(e);
-				if(Application.OpenForms.Count == 1 || editformopen) display.Focus(); //mxd
+				if((Application.OpenForms.Count == 1 || editformopen) && (commandpalette == null ? true : !commandpalette.Visible)) display.Focus(); //mxd
 			}
 		}
 
@@ -1420,7 +1449,7 @@ namespace CodeImp.DoomBuilder.Windows
 			if(alt) mod |= (int)Keys.Alt;
 			if(shift) mod |= (int)Keys.Shift;
 			if(ctrl) mod |= (int)Keys.Control;
-			
+
 			// Don't process any keys when they are meant for other input controls
 			if((e.KeyData != Keys.None) && ((ActiveControl == null) || (ActiveControl == display)))
 			{
@@ -1476,7 +1505,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private void MainForm_KeyUp(object sender, KeyEventArgs e)
 		{
 			int mod = 0;
-			
+
 			// Keep key modifiers
 			alt = e.Alt;
 			shift = e.Shift;
@@ -1977,6 +2006,8 @@ namespace CodeImp.DoomBuilder.Windows
 			
 			// Bind visible changed event
 			if(!(button is ToolStripSeparator)) button.VisibleChanged += buttonvisiblechangedhandler;
+
+			if (button is ToolStripActionButton) ((ToolStripActionButton)button).UpdateToolTip();
 			
 			// Insert the button in the right section
 			switch(section)
@@ -2175,6 +2206,12 @@ namespace CodeImp.DoomBuilder.Windows
 			buttontogglevisualvertices.Visible = General.Settings.GZToolbarGZDoom && maploaded && General.Map.UDMF;
 			separatorgzmodes.Visible = General.Settings.GZToolbarGZDoom && maploaded;
 
+			foreach (ToolStripItem item in toolbar.Items)
+			{
+				if (item is ToolStripActionButton)
+					((ToolStripActionButton)item).UpdateToolTip();
+			}
+
 			//mxd. Show/hide additional panels
 			modestoolbar.Visible = maploaded;
 			panelinfo.Visible = maploaded;
@@ -2206,6 +2243,10 @@ namespace CodeImp.DoomBuilder.Windows
 					case ToolbarSection.Geometry: p.button.Visible = General.Settings.ToolbarGeometry; break;
 					case ToolbarSection.Testing: p.button.Visible = General.Settings.ToolbarTesting; break;
 				}
+
+				// Update the tooltips of all buttons added by plugins
+				if (p.button is ToolStripActionButton)
+					((ToolStripActionButton)p.button).UpdateToolTip();
 			}
 
 			preventupdateseperators = false;
@@ -2283,11 +2324,12 @@ namespace CodeImp.DoomBuilder.Windows
 			string controlname = modeinfo.ButtonDesc.Replace("&", "&&");
 			
 			// Create a button
-			ToolStripItem item = new ToolStripButton(modeinfo.ButtonDesc, modeinfo.ButtonImage, EditModeButtonHandler);
+			ToolStripItem item = new ToolStripActionButton(modeinfo.ButtonDesc, modeinfo.ButtonImage, EditModeButtonHandler);
 			item.DisplayStyle = ToolStripItemDisplayStyle.Image;
 			item.Padding = new Padding(0, 2, 0, 2);
 			item.Margin = new Padding();
 			item.Tag = modeinfo;
+			((ToolStripActionButton)item).UpdateToolTip();
 			modestoolbar.Items.Add(item); //mxd
 			editmodeitems.Add(item);
 			
@@ -2951,6 +2993,19 @@ namespace CodeImp.DoomBuilder.Windows
 			// Redraw display to show changes
 			RedrawDisplay();
 		}
+		
+		//mxd. Action to toggle fixed things scale
+		[BeginAction("togglealwaysshowvertices")]
+		internal void ToggleAlwaysShowVertices()
+		{
+			General.Settings.AlwaysShowVertices = !General.Settings.AlwaysShowVertices;
+			itemtogglealwaysshowvertices.Checked = General.Settings.AlwaysShowVertices;
+			
+			DisplayStatus(StatusType.Action, "Always show vertices is " + (General.Settings.AlwaysShowVertices ? "ENABLED" : "DISABLED"));
+
+			// Redraw display to show changes
+			RedrawDisplay();
+		}
 
 		// Action to toggle snap to grid
 		[BeginAction("togglesnap")]
@@ -2987,7 +3042,15 @@ namespace CodeImp.DoomBuilder.Windows
 			Renderer.FullBrightness = !Renderer.FullBrightness;
 			buttonfullbrightness.Checked = Renderer.FullBrightness;
 			itemfullbrightness.Checked = Renderer.FullBrightness;
-			General.Interface.DisplayStatus(StatusType.Action, "Full Brightness is now " + (Renderer.FullBrightness ? "ON" : "OFF"));
+
+			string shorttext = "Full brightness is now " + (Renderer.FullBrightness ? "ON" : "OFF") + ".";
+			string text = shorttext;
+			string key = Actions.Action.GetShortcutKeyDesc(General.Actions.Current.ShortcutKey);
+
+			if (!string.IsNullOrEmpty(key))
+				text += $" Press '{key}' to toggle.";
+
+			General.ToastManager.ShowToast("togglebrightness", ToastType.INFO, "Changed full brightness", text, shorttext);
 
 			// Redraw display to show changes
 			General.Interface.RedrawDisplay();
@@ -2997,6 +3060,9 @@ namespace CodeImp.DoomBuilder.Windows
 		[BeginAction("togglegrid")]
 		protected void ToggleGrid()
 		{
+			if (General.Map == null)
+				return;
+
 			General.Settings.RenderGrid = !General.Settings.RenderGrid;
 			itemtogglegrid.Checked = General.Settings.RenderGrid;
 			buttontogglegrid.Checked = General.Settings.RenderGrid;
@@ -3048,6 +3114,9 @@ namespace CodeImp.DoomBuilder.Windows
 		[BeginAction("resetgrid")]
 		protected void ResetGrid()
 		{
+			if (General.Map == null)
+				return;
+
 			General.Map.Grid.SetGridRotation(0.0f);
 			General.Map.Grid.SetGridOrigin(0, 0);
 			General.Map.CRenderer2D.GridVisibilityChanged();
@@ -3082,6 +3151,9 @@ namespace CodeImp.DoomBuilder.Windows
 		[BeginAction("viewusedtags")]
 		internal void ViewUsedTags() 
 		{
+			if (General.Map == null)
+				return;
+
 			TagStatisticsForm f = new TagStatisticsForm();
 			f.ShowDialog(this);
 		}
@@ -3090,6 +3162,9 @@ namespace CodeImp.DoomBuilder.Windows
 		[BeginAction("viewthingtypes")]
 		internal void ViewThingTypes()
 		{
+			if (General.Map == null)
+				return;
+
 			ThingStatisticsForm f = new ThingStatisticsForm();
 			f.ShowDialog(this);
 		}
@@ -3185,9 +3260,17 @@ namespace CodeImp.DoomBuilder.Windows
 			General.Settings.GZDrawLightsMode = (General.Settings.EnhancedRenderingEffects ? LightRenderMode.ALL : LightRenderMode.NONE);
 			General.Settings.GZDrawModelsMode = (General.Settings.EnhancedRenderingEffects ? ModelRenderMode.ALL : ModelRenderMode.NONE);
 
+			string shorttext = "Enhanced rendering effects are " + (General.Settings.EnhancedRenderingEffects ? "ENABLED" : "DISABLED") + ".";
+			string text = shorttext;
+			string key = Actions.Action.GetShortcutKeyDesc(General.Actions.Current.ShortcutKey);
+
+			if (!string.IsNullOrEmpty(key))
+				text += $" Press '{key}' to toggle.";
+
+			General.ToastManager.ShowToast("gztoggleenhancedrendering", ToastType.INFO, "Changed enhanced rendering", text, shorttext);
+
 			UpdateGZDoomPanel();
 			UpdateViewMenu();
-			DisplayStatus(StatusType.Info, "Enhanced rendering effects are " + (General.Settings.EnhancedRenderingEffects ? "ENABLED" : "DISABLED"));
 		}
 
 		//mxd
@@ -3240,7 +3323,15 @@ namespace CodeImp.DoomBuilder.Windows
 			itemtoggleeventlines.Checked = General.Settings.GZShowEventLines;
 			buttontoggleeventlines.Checked = General.Settings.GZShowEventLines;
 
-			General.MainWindow.DisplayStatus(StatusType.Action, "Event lines are " + (General.Settings.GZShowEventLines ? "ENABLED" : "DISABLED"));
+			string shorttext = "Event lines are now " + (General.Settings.GZShowEventLines ? "ENABLED" : "DISABLED") + ".";
+			string text = shorttext;
+			string key = Actions.Action.GetShortcutKeyDesc(General.Actions.Current.ShortcutKey);
+
+			if (!string.IsNullOrEmpty(key))
+				text += $" Press '{key}' to toggle.";
+
+			General.ToastManager.ShowToast("gztoggleeventlines", ToastType.INFO, "Changed event line visibility", text, shorttext);
+
 			General.MainWindow.RedrawDisplay();
 			General.MainWindow.UpdateGZDoomPanel();
 		}
@@ -3572,7 +3663,18 @@ namespace CodeImp.DoomBuilder.Windows
 
 
 				folder = General.DefaultScreenshotsPath;
-				if(!Directory.Exists(folder)) Directory.CreateDirectory(folder);
+				if (!Directory.Exists(folder))
+				{
+					try
+					{
+						Directory.CreateDirectory(folder);
+					}
+					catch(Exception e)
+					{
+						General.ShowErrorMessage($"Could not create folder \"{folder}\":\n{e}", MessageBoxButtons.OK);
+						return;
+					}
+				}
 			}
 
 			// Create name and bounds
diff --git a/Source/Core/Windows/MapOptionsForm.Designer.cs b/Source/Core/Windows/MapOptionsForm.Designer.cs
index adf58dd86098589330383dfab8d664b286c652c4..4f96a70385f9772c44009c11631ea0edea7dd50a 100755
--- a/Source/Core/Windows/MapOptionsForm.Designer.cs
+++ b/Source/Core/Windows/MapOptionsForm.Designer.cs
@@ -28,255 +28,266 @@ namespace CodeImp.DoomBuilder.Windows
 		/// </summary>
 		private void InitializeComponent()
 		{
-			this.components = new System.ComponentModel.Container();
-			System.Windows.Forms.Label label3;
-			System.Windows.Forms.Label label2;
-			System.Windows.Forms.Label label1;
-			System.Windows.Forms.GroupBox panelsettings;
-			System.Windows.Forms.Label label4;
-			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MapOptionsForm));
-			this.examplelabel = new System.Windows.Forms.Label();
-			this.scriptcompiler = new System.Windows.Forms.ComboBox();
-			this.scriptcompilerlabel = new System.Windows.Forms.Label();
-			this.levelname = new System.Windows.Forms.TextBox();
-			this.config = new System.Windows.Forms.ComboBox();
-			this.apply = new System.Windows.Forms.Button();
-			this.cancel = new System.Windows.Forms.Button();
-			this.panelres = new System.Windows.Forms.GroupBox();
-			this.longtexturenames = new System.Windows.Forms.CheckBox();
-			this.strictpatches = new System.Windows.Forms.CheckBox();
-			this.datalocations = new CodeImp.DoomBuilder.Controls.ResourceListEditor();
-			this.tooltip = new System.Windows.Forms.ToolTip(this.components);
-			this.label5 = new System.Windows.Forms.Label();
-			this.prepostcommands = new System.Windows.Forms.Button();
-			label3 = new System.Windows.Forms.Label();
-			label2 = new System.Windows.Forms.Label();
-			label1 = new System.Windows.Forms.Label();
-			panelsettings = new System.Windows.Forms.GroupBox();
-			label4 = new System.Windows.Forms.Label();
-			panelsettings.SuspendLayout();
-			this.panelres.SuspendLayout();
-			this.SuspendLayout();
-			// 
-			// label3
-			// 
-			label3.AutoSize = true;
-			label3.Location = new System.Drawing.Point(239, 83);
-			label3.Name = "label3";
-			label3.Size = new System.Drawing.Size(52, 13);
-			label3.TabIndex = 9;
-			label3.Text = "example: ";
-			// 
-			// label2
-			// 
-			label2.AutoSize = true;
-			label2.Location = new System.Drawing.Point(58, 83);
-			label2.Name = "label2";
-			label2.Size = new System.Drawing.Size(65, 13);
-			label2.TabIndex = 7;
-			label2.Text = "Level name:";
-			// 
-			// label1
-			// 
-			label1.Location = new System.Drawing.Point(13, 27);
-			label1.Name = "label1";
-			label1.Size = new System.Drawing.Size(110, 14);
-			label1.TabIndex = 5;
-			label1.Text = "Game Configuration:";
-			label1.TextAlign = System.Drawing.ContentAlignment.TopRight;
-			// 
-			// panelsettings
-			// 
-			panelsettings.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            this.components = new System.ComponentModel.Container();
+            System.Windows.Forms.Label label3;
+            System.Windows.Forms.Label label2;
+            System.Windows.Forms.Label label1;
+            System.Windows.Forms.GroupBox panelsettings;
+            System.Windows.Forms.Label label4;
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MapOptionsForm));
+            this.examplelabel = new System.Windows.Forms.Label();
+            this.scriptcompiler = new System.Windows.Forms.ComboBox();
+            this.scriptcompilerlabel = new System.Windows.Forms.Label();
+            this.levelname = new System.Windows.Forms.TextBox();
+            this.config = new System.Windows.Forms.ComboBox();
+            this.apply = new System.Windows.Forms.Button();
+            this.cancel = new System.Windows.Forms.Button();
+            this.panelres = new System.Windows.Forms.GroupBox();
+            this.longtexturenames = new System.Windows.Forms.CheckBox();
+            this.strictpatches = new System.Windows.Forms.CheckBox();
+            this.datalocations = new CodeImp.DoomBuilder.Controls.ResourceListEditor();
+            this.tooltip = new System.Windows.Forms.ToolTip(this.components);
+            this.label5 = new System.Windows.Forms.Label();
+            this.prepostcommands = new System.Windows.Forms.Button();
+            label3 = new System.Windows.Forms.Label();
+            label2 = new System.Windows.Forms.Label();
+            label1 = new System.Windows.Forms.Label();
+            panelsettings = new System.Windows.Forms.GroupBox();
+            label4 = new System.Windows.Forms.Label();
+            panelsettings.SuspendLayout();
+            this.panelres.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // label3
+            // 
+            label3.AutoSize = true;
+            label3.Location = new System.Drawing.Point(239, 83);
+            label3.Name = "label3";
+            label3.Size = new System.Drawing.Size(52, 13);
+            label3.TabIndex = 9;
+            label3.Text = "example: ";
+            // 
+            // label2
+            // 
+            label2.AutoSize = true;
+            label2.Location = new System.Drawing.Point(58, 83);
+            label2.Name = "label2";
+            label2.Size = new System.Drawing.Size(65, 13);
+            label2.TabIndex = 7;
+            label2.Text = "Level name:";
+            // 
+            // label1
+            // 
+            label1.Location = new System.Drawing.Point(13, 27);
+            label1.Name = "label1";
+            label1.Size = new System.Drawing.Size(110, 14);
+            label1.TabIndex = 5;
+            label1.Text = "Game Configuration:";
+            label1.TextAlign = System.Drawing.ContentAlignment.TopRight;
+            // 
+            // panelsettings
+            // 
+            panelsettings.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			panelsettings.Controls.Add(this.examplelabel);
-			panelsettings.Controls.Add(this.scriptcompiler);
-			panelsettings.Controls.Add(this.scriptcompilerlabel);
-			panelsettings.Controls.Add(label3);
-			panelsettings.Controls.Add(this.levelname);
-			panelsettings.Controls.Add(label2);
-			panelsettings.Controls.Add(this.config);
-			panelsettings.Controls.Add(label1);
-			panelsettings.Location = new System.Drawing.Point(12, 12);
-			panelsettings.Name = "panelsettings";
-			panelsettings.Size = new System.Drawing.Size(397, 112);
-			panelsettings.TabIndex = 0;
-			panelsettings.TabStop = false;
-			panelsettings.Text = " Settings ";
-			// 
-			// examplelabel
-			// 
-			this.examplelabel.AutoSize = true;
-			this.examplelabel.Location = new System.Drawing.Point(288, 83);
-			this.examplelabel.Name = "examplelabel";
-			this.examplelabel.Size = new System.Drawing.Size(42, 13);
-			this.examplelabel.TabIndex = 12;
-			this.examplelabel.Text = "MAP01";
-			// 
-			// scriptcompiler
-			// 
-			this.scriptcompiler.DropDownHeight = 206;
-			this.scriptcompiler.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.scriptcompiler.FormattingEnabled = true;
-			this.scriptcompiler.IntegralHeight = false;
-			this.scriptcompiler.Location = new System.Drawing.Point(129, 52);
-			this.scriptcompiler.Name = "scriptcompiler";
-			this.scriptcompiler.Size = new System.Drawing.Size(254, 21);
-			this.scriptcompiler.TabIndex = 10;
-			// 
-			// scriptcompilerlabel
-			// 
-			this.scriptcompilerlabel.Location = new System.Drawing.Point(13, 55);
-			this.scriptcompilerlabel.Name = "scriptcompilerlabel";
-			this.scriptcompilerlabel.Size = new System.Drawing.Size(110, 14);
-			this.scriptcompilerlabel.TabIndex = 11;
-			this.scriptcompilerlabel.Text = "Script Type:";
-			this.scriptcompilerlabel.TextAlign = System.Drawing.ContentAlignment.TopRight;
-			// 
-			// levelname
-			// 
-			this.levelname.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper;
-			this.levelname.Location = new System.Drawing.Point(129, 80);
-			this.levelname.MaxLength = 8;
-			this.levelname.Name = "levelname";
-			this.levelname.Size = new System.Drawing.Size(94, 20);
-			this.levelname.TabIndex = 1;
-			this.levelname.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.levelname_KeyPress);
-			// 
-			// config
-			// 
-			this.config.DropDownHeight = 206;
-			this.config.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.config.FormattingEnabled = true;
-			this.config.IntegralHeight = false;
-			this.config.Location = new System.Drawing.Point(129, 24);
-			this.config.Name = "config";
-			this.config.Size = new System.Drawing.Size(254, 21);
-			this.config.TabIndex = 0;
-			this.config.SelectedIndexChanged += new System.EventHandler(this.config_SelectedIndexChanged);
-			// 
-			// label4
-			// 
-			label4.AutoSize = true;
-			label4.Location = new System.Drawing.Point(13, 198);
-			label4.Name = "label4";
-			label4.Size = new System.Drawing.Size(299, 52);
-			label4.TabIndex = 17;
-			label4.Text = resources.GetString("label4.Text");
-			// 
-			// apply
-			// 
-			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.apply.Location = new System.Drawing.Point(179, 429);
-			this.apply.Name = "apply";
-			this.apply.Size = new System.Drawing.Size(112, 25);
-			this.apply.TabIndex = 2;
-			this.apply.Text = "OK";
-			this.apply.UseVisualStyleBackColor = true;
-			this.apply.Click += new System.EventHandler(this.apply_Click);
-			// 
-			// cancel
-			// 
-			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-			this.cancel.Location = new System.Drawing.Point(297, 429);
-			this.cancel.Name = "cancel";
-			this.cancel.Size = new System.Drawing.Size(112, 25);
-			this.cancel.TabIndex = 3;
-			this.cancel.Text = "Cancel";
-			this.cancel.UseVisualStyleBackColor = true;
-			this.cancel.Click += new System.EventHandler(this.cancel_Click);
-			// 
-			// panelres
-			// 
-			this.panelres.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            panelsettings.Controls.Add(this.examplelabel);
+            panelsettings.Controls.Add(this.scriptcompiler);
+            panelsettings.Controls.Add(this.scriptcompilerlabel);
+            panelsettings.Controls.Add(label3);
+            panelsettings.Controls.Add(this.levelname);
+            panelsettings.Controls.Add(label2);
+            panelsettings.Controls.Add(this.config);
+            panelsettings.Controls.Add(label1);
+            panelsettings.Location = new System.Drawing.Point(12, 12);
+            panelsettings.Name = "panelsettings";
+            panelsettings.Size = new System.Drawing.Size(397, 112);
+            panelsettings.TabIndex = 0;
+            panelsettings.TabStop = false;
+            panelsettings.Text = " Settings ";
+            // 
+            // examplelabel
+            // 
+            this.examplelabel.AutoSize = true;
+            this.examplelabel.Location = new System.Drawing.Point(288, 83);
+            this.examplelabel.Name = "examplelabel";
+            this.examplelabel.Size = new System.Drawing.Size(42, 13);
+            this.examplelabel.TabIndex = 12;
+            this.examplelabel.Text = "MAP01";
+            // 
+            // scriptcompiler
+            // 
+            this.scriptcompiler.DropDownHeight = 206;
+            this.scriptcompiler.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.scriptcompiler.FormattingEnabled = true;
+            this.scriptcompiler.IntegralHeight = false;
+            this.scriptcompiler.Location = new System.Drawing.Point(129, 52);
+            this.scriptcompiler.Name = "scriptcompiler";
+            this.scriptcompiler.Size = new System.Drawing.Size(254, 21);
+            this.scriptcompiler.TabIndex = 10;
+            // 
+            // scriptcompilerlabel
+            // 
+            this.scriptcompilerlabel.Location = new System.Drawing.Point(13, 55);
+            this.scriptcompilerlabel.Name = "scriptcompilerlabel";
+            this.scriptcompilerlabel.Size = new System.Drawing.Size(110, 14);
+            this.scriptcompilerlabel.TabIndex = 11;
+            this.scriptcompilerlabel.Text = "Script Type:";
+            this.scriptcompilerlabel.TextAlign = System.Drawing.ContentAlignment.TopRight;
+            // 
+            // levelname
+            // 
+            this.levelname.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper;
+            this.levelname.Location = new System.Drawing.Point(129, 80);
+            this.levelname.MaxLength = 8;
+            this.levelname.Name = "levelname";
+            this.levelname.Size = new System.Drawing.Size(94, 20);
+            this.levelname.TabIndex = 1;
+            this.levelname.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.levelname_KeyPress);
+            // 
+            // config
+            // 
+            this.config.DropDownHeight = 206;
+            this.config.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.config.FormattingEnabled = true;
+            this.config.IntegralHeight = false;
+            this.config.Location = new System.Drawing.Point(129, 24);
+            this.config.Name = "config";
+            this.config.Size = new System.Drawing.Size(254, 21);
+            this.config.TabIndex = 0;
+            this.config.SelectedIndexChanged += new System.EventHandler(this.config_SelectedIndexChanged);
+            // 
+            // label4
+            // 
+            label4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.panelres.Controls.Add(this.longtexturenames);
-			this.panelres.Controls.Add(this.strictpatches);
-			this.panelres.Controls.Add(this.datalocations);
-			this.panelres.Controls.Add(label4);
-			this.panelres.Location = new System.Drawing.Point(12, 130);
-			this.panelres.Name = "panelres";
-			this.panelres.Size = new System.Drawing.Size(397, 259);
-			this.panelres.TabIndex = 1;
-			this.panelres.TabStop = false;
-			this.panelres.Text = " Resources ";
-			// 
-			// longtexturenames
-			// 
-			this.longtexturenames.AutoSize = true;
-			this.longtexturenames.Location = new System.Drawing.Point(14, 45);
-			this.longtexturenames.Name = "longtexturenames";
-			this.longtexturenames.Size = new System.Drawing.Size(137, 17);
-			this.longtexturenames.TabIndex = 21;
-			this.longtexturenames.Text = "Use long texture names";
-			this.longtexturenames.UseVisualStyleBackColor = true;
-			// 
-			// strictpatches
-			// 
-			this.strictpatches.AutoSize = true;
-			this.strictpatches.Location = new System.Drawing.Point(14, 21);
-			this.strictpatches.Name = "strictpatches";
-			this.strictpatches.Size = new System.Drawing.Size(349, 17);
-			this.strictpatches.TabIndex = 20;
-			this.strictpatches.Text = "Strictly load patches between P_START and P_END only for this file";
-			this.strictpatches.UseVisualStyleBackColor = true;
-			// 
-			// datalocations
-			// 
-			this.datalocations.AllowDrop = true;
-			this.datalocations.DialogOffset = new System.Drawing.Point(40, 20);
-			this.datalocations.Location = new System.Drawing.Point(14, 68);
-			this.datalocations.Name = "datalocations";
-			this.datalocations.Size = new System.Drawing.Size(368, 127);
-			this.datalocations.TabIndex = 0;
-			// 
-			// label5
-			// 
-			this.label5.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
-			this.label5.Location = new System.Drawing.Point(13, 423);
-			this.label5.Name = "label5";
-			this.label5.Size = new System.Drawing.Size(396, 2);
-			this.label5.TabIndex = 21;
-			// 
-			// prepostcommands
-			// 
-			this.prepostcommands.Location = new System.Drawing.Point(247, 395);
-			this.prepostcommands.Name = "prepostcommands";
-			this.prepostcommands.Size = new System.Drawing.Size(162, 25);
-			this.prepostcommands.TabIndex = 20;
-			this.prepostcommands.Text = "Edit pre and post commands";
-			this.prepostcommands.UseVisualStyleBackColor = true;
-			this.prepostcommands.Click += new System.EventHandler(this.prepostcommands_Click);
-			// 
-			// MapOptionsForm
-			// 
-			this.AcceptButton = this.apply;
-			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
-			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-			this.CancelButton = this.cancel;
-			this.ClientSize = new System.Drawing.Size(421, 466);
-			this.Controls.Add(this.label5);
-			this.Controls.Add(this.prepostcommands);
-			this.Controls.Add(this.panelres);
-			this.Controls.Add(this.cancel);
-			this.Controls.Add(this.apply);
-			this.Controls.Add(panelsettings);
-			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
-			this.MaximizeBox = false;
-			this.MinimizeBox = false;
-			this.Name = "MapOptionsForm";
-			this.Opacity = 0D;
-			this.ShowIcon = false;
-			this.ShowInTaskbar = false;
-			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
-			this.Text = "Map Options";
-			this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.MapOptionsForm_HelpRequested);
-			panelsettings.ResumeLayout(false);
-			panelsettings.PerformLayout();
-			this.panelres.ResumeLayout(false);
-			this.panelres.PerformLayout();
-			this.ResumeLayout(false);
+            label4.AutoSize = true;
+            label4.Location = new System.Drawing.Point(13, 198);
+            label4.Name = "label4";
+            label4.Size = new System.Drawing.Size(299, 52);
+            label4.TabIndex = 17;
+            label4.Text = resources.GetString("label4.Text");
+            // 
+            // apply
+            // 
+            this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.apply.Location = new System.Drawing.Point(179, 429);
+            this.apply.Name = "apply";
+            this.apply.Size = new System.Drawing.Size(112, 25);
+            this.apply.TabIndex = 2;
+            this.apply.Text = "OK";
+            this.apply.UseVisualStyleBackColor = true;
+            this.apply.Click += new System.EventHandler(this.apply_Click);
+            // 
+            // cancel
+            // 
+            this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.cancel.Location = new System.Drawing.Point(297, 429);
+            this.cancel.Name = "cancel";
+            this.cancel.Size = new System.Drawing.Size(112, 25);
+            this.cancel.TabIndex = 3;
+            this.cancel.Text = "Cancel";
+            this.cancel.UseVisualStyleBackColor = true;
+            this.cancel.Click += new System.EventHandler(this.cancel_Click);
+            // 
+            // panelres
+            // 
+            this.panelres.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.panelres.Controls.Add(this.longtexturenames);
+            this.panelres.Controls.Add(this.strictpatches);
+            this.panelres.Controls.Add(this.datalocations);
+            this.panelres.Controls.Add(label4);
+            this.panelres.Location = new System.Drawing.Point(12, 130);
+            this.panelres.Name = "panelres";
+            this.panelres.Size = new System.Drawing.Size(397, 259);
+            this.panelres.TabIndex = 1;
+            this.panelres.TabStop = false;
+            this.panelres.Text = " Resources ";
+            // 
+            // longtexturenames
+            // 
+            this.longtexturenames.AutoSize = true;
+            this.longtexturenames.Location = new System.Drawing.Point(14, 45);
+            this.longtexturenames.Name = "longtexturenames";
+            this.longtexturenames.Size = new System.Drawing.Size(137, 17);
+            this.longtexturenames.TabIndex = 21;
+            this.longtexturenames.Text = "Use long texture names";
+            this.longtexturenames.UseVisualStyleBackColor = true;
+            // 
+            // strictpatches
+            // 
+            this.strictpatches.AutoSize = true;
+            this.strictpatches.Location = new System.Drawing.Point(14, 21);
+            this.strictpatches.Name = "strictpatches";
+            this.strictpatches.Size = new System.Drawing.Size(349, 17);
+            this.strictpatches.TabIndex = 20;
+            this.strictpatches.Text = "Strictly load patches between P_START and P_END only for this file";
+            this.strictpatches.UseVisualStyleBackColor = true;
+            // 
+            // datalocations
+            // 
+            this.datalocations.AllowDrop = true;
+            this.datalocations.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.datalocations.DialogOffset = new System.Drawing.Point(40, 20);
+            this.datalocations.GameConfiguration = null;
+            this.datalocations.Location = new System.Drawing.Point(14, 68);
+            this.datalocations.Name = "datalocations";
+            this.datalocations.Size = new System.Drawing.Size(368, 127);
+            this.datalocations.TabIndex = 0;
+            this.datalocations.OnWarningsChanged += new CodeImp.DoomBuilder.Controls.ResourceListEditor.WarningsChanged(this.datalocations_OnWarningsChanged);
+            // 
+            // label5
+            // 
+            this.label5.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.label5.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+            this.label5.Location = new System.Drawing.Point(13, 423);
+            this.label5.Name = "label5";
+            this.label5.Size = new System.Drawing.Size(396, 2);
+            this.label5.TabIndex = 21;
+            // 
+            // prepostcommands
+            // 
+            this.prepostcommands.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.prepostcommands.Location = new System.Drawing.Point(247, 395);
+            this.prepostcommands.Name = "prepostcommands";
+            this.prepostcommands.Size = new System.Drawing.Size(162, 25);
+            this.prepostcommands.TabIndex = 20;
+            this.prepostcommands.Text = "Edit pre and post commands";
+            this.prepostcommands.UseVisualStyleBackColor = true;
+            this.prepostcommands.Click += new System.EventHandler(this.prepostcommands_Click);
+            // 
+            // MapOptionsForm
+            // 
+            this.AcceptButton = this.apply;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+            this.CancelButton = this.cancel;
+            this.ClientSize = new System.Drawing.Size(421, 466);
+            this.Controls.Add(this.label5);
+            this.Controls.Add(this.prepostcommands);
+            this.Controls.Add(this.panelres);
+            this.Controls.Add(this.cancel);
+            this.Controls.Add(this.apply);
+            this.Controls.Add(panelsettings);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "MapOptionsForm";
+            this.Opacity = 0D;
+            this.ShowIcon = false;
+            this.ShowInTaskbar = false;
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Map Options";
+            this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.MapOptionsForm_HelpRequested);
+            panelsettings.ResumeLayout(false);
+            panelsettings.PerformLayout();
+            this.panelres.ResumeLayout(false);
+            this.panelres.PerformLayout();
+            this.ResumeLayout(false);
 
 		}
 
diff --git a/Source/Core/Windows/MapOptionsForm.cs b/Source/Core/Windows/MapOptionsForm.cs
index 825d5edb2930bd596d5293163aedda5efa2d81c6..33354c3d5be15fb9c35ac07db0a280dfa429c51d 100755
--- a/Source/Core/Windows/MapOptionsForm.cs
+++ b/Source/Core/Windows/MapOptionsForm.cs
@@ -39,6 +39,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private ExternalCommandSettings testprecommand;
 		private ExternalCommandSettings testpostcommand;
 		private bool prepostcommandsmodified;
+		private readonly int initialformheight;
 		
 		// Properties
 		public MapOptions Options { get { return options; } }
@@ -50,6 +51,7 @@ namespace CodeImp.DoomBuilder.Windows
 
 			// Initialize
 			InitializeComponent();
+			this.initialformheight = Height;
 
 			// Keep settings
 			this.options = options;
@@ -110,6 +112,8 @@ namespace CodeImp.DoomBuilder.Windows
 			//mxd. Still better than nothing :)
 			if(config.SelectedIndex == -1 && config.Items.Count > 0) config.SelectedIndex = 0;
 
+			// Find whatever was selected after the previous step
+
 			//mxd
 			if(General.Map != null) datalocations.StartPath = General.Map.FilePathName;
 
@@ -120,11 +124,20 @@ namespace CodeImp.DoomBuilder.Windows
 			strictpatches.Checked = options.StrictPatches;
 
 			// Fill the resources list
+			datalocations.IsMapControl = true;
+			datalocations.GameConfiguration = GetGameConfiguration();
 			datalocations.EditResourceLocationList(options.Resources);
 
 			//reloadresourceprecmd.Text = options.ReloadResourcePreCommand;
 		}
 
+		private GameConfiguration GetGameConfiguration()
+        {
+			if (config.Items.Count == 0 || config.SelectedIndex < 0) return null;
+
+			return new GameConfiguration((config.SelectedItem as ConfigurationInfo).Configuration);
+        }
+
 		// OK clicked
 		private void apply_Click(object sender, EventArgs e)
 		{
@@ -175,21 +188,13 @@ namespace CodeImp.DoomBuilder.Windows
 				datalocations.Focus();
 				return;
 			}
-			
-			// When making a new map, check if we should warn the user for missing resources
-			if(newmap)
+
+			// Save the last used config when creating a new map
+			if (newmap)
 			{
-				General.Settings.LastUsedConfigName = configinfo.Name; //mxd
-				
-				if((locations.Count == 0) && (configinfo.Resources.Count == 0) && 
-					MessageBox.Show(this, "You are about to make a map without selecting any resources. Textures, flats and " +
-										 "sprites may not be shown correctly or may not show up at all. Do you want to continue?", Application.ProductName,
-										 MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No)
-				{
-					return;
-				}
+				General.Settings.LastUsedConfigName = configinfo.Name;
 			}
-
+			
 			// Next checks are only for maps that are already opened
 			if(!newmap)
 			{
@@ -242,6 +247,7 @@ namespace CodeImp.DoomBuilder.Windows
 								{
 									// Select this item
 									config.SelectedIndex = i;
+									datalocations.GameConfiguration = GetGameConfiguration();
 								}
 							}
 							return;
@@ -345,6 +351,7 @@ namespace CodeImp.DoomBuilder.Windows
 			}
 
 			// Show resources
+			datalocations.GameConfiguration = GetGameConfiguration();
 			datalocations.FixedResourceLocationList(info.Resources);
 
 			// Update long texture names checkbox (mxd)
@@ -388,5 +395,11 @@ namespace CodeImp.DoomBuilder.Windows
 				prepostcommandsmodified = true;
 			}
 		}
-	}
+
+        private void datalocations_OnWarningsChanged(int size)
+        {
+			Height = initialformheight + size;
+			Refresh();
+		}
+    }
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/OpenMapOptionsForm.Designer.cs b/Source/Core/Windows/OpenMapOptionsForm.Designer.cs
index cb1c44be12a75dfed5ec32fbbe25fe4871e4fc5d..1b966845a1c7d4afacc03e5caff3b7388fe9d3ef 100755
--- a/Source/Core/Windows/OpenMapOptionsForm.Designer.cs
+++ b/Source/Core/Windows/OpenMapOptionsForm.Designer.cs
@@ -28,217 +28,225 @@ namespace CodeImp.DoomBuilder.Windows
 		/// </summary>
 		private void InitializeComponent()
 		{
-			this.components = new System.ComponentModel.Container();
-			System.Windows.Forms.ColumnHeader columnHeader1;
-			System.Windows.Forms.Label label1;
-			System.Windows.Forms.Label label2;
-			System.Windows.Forms.Label label3;
-			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(OpenMapOptionsForm));
-			this.panelres = new System.Windows.Forms.GroupBox();
-			this.longtexturenames = new System.Windows.Forms.CheckBox();
-			this.strictpatches = new System.Windows.Forms.CheckBox();
-			this.datalocations = new CodeImp.DoomBuilder.Controls.ResourceListEditor();
-			this.apply = new System.Windows.Forms.Button();
-			this.cancel = new System.Windows.Forms.Button();
-			this.config = new System.Windows.Forms.ComboBox();
-			this.mapslist = new System.Windows.Forms.ListView();
-			this.scriptcompiler = new System.Windows.Forms.ComboBox();
-			this.scriptcompilerlabel = new System.Windows.Forms.Label();
-			this.tooltip = new System.Windows.Forms.ToolTip(this.components);
-			columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
-			label1 = new System.Windows.Forms.Label();
-			label2 = new System.Windows.Forms.Label();
-			label3 = new System.Windows.Forms.Label();
-			this.panelres.SuspendLayout();
-			this.SuspendLayout();
-			// 
-			// columnHeader1
-			// 
-			columnHeader1.Text = "Map name";
-			columnHeader1.Width = -1;
-			// 
-			// label1
-			// 
-			label1.Location = new System.Drawing.Point(15, 24);
-			label1.Name = "label1";
-			label1.Size = new System.Drawing.Size(120, 14);
-			label1.TabIndex = 14;
-			label1.Text = "Game Configuration:";
-			label1.TextAlign = System.Drawing.ContentAlignment.TopRight;
-			// 
-			// label2
-			// 
-			label2.AutoSize = true;
-			label2.Location = new System.Drawing.Point(12, 89);
-			label2.Name = "label2";
-			label2.Size = new System.Drawing.Size(360, 26);
-			label2.TabIndex = 16;
-			label2.Text = "With the above selected configuration, the maps shown below were found \r\nin the c" +
+            this.components = new System.ComponentModel.Container();
+            System.Windows.Forms.ColumnHeader columnHeader1;
+            System.Windows.Forms.Label label1;
+            System.Windows.Forms.Label label2;
+            System.Windows.Forms.Label label3;
+            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(OpenMapOptionsForm));
+            this.panelres = new System.Windows.Forms.GroupBox();
+            this.longtexturenames = new System.Windows.Forms.CheckBox();
+            this.strictpatches = new System.Windows.Forms.CheckBox();
+            this.datalocations = new CodeImp.DoomBuilder.Controls.ResourceListEditor();
+            this.apply = new System.Windows.Forms.Button();
+            this.cancel = new System.Windows.Forms.Button();
+            this.config = new System.Windows.Forms.ComboBox();
+            this.mapslist = new System.Windows.Forms.ListView();
+            this.scriptcompiler = new System.Windows.Forms.ComboBox();
+            this.scriptcompilerlabel = new System.Windows.Forms.Label();
+            this.tooltip = new System.Windows.Forms.ToolTip(this.components);
+            columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+            label1 = new System.Windows.Forms.Label();
+            label2 = new System.Windows.Forms.Label();
+            label3 = new System.Windows.Forms.Label();
+            this.panelres.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // columnHeader1
+            // 
+            columnHeader1.Text = "Map name";
+            columnHeader1.Width = -1;
+            // 
+            // label1
+            // 
+            label1.Location = new System.Drawing.Point(15, 24);
+            label1.Name = "label1";
+            label1.Size = new System.Drawing.Size(120, 14);
+            label1.TabIndex = 14;
+            label1.Text = "Game Configuration:";
+            label1.TextAlign = System.Drawing.ContentAlignment.TopRight;
+            // 
+            // label2
+            // 
+            label2.AutoSize = true;
+            label2.Location = new System.Drawing.Point(12, 89);
+            label2.Name = "label2";
+            label2.Size = new System.Drawing.Size(360, 26);
+            label2.TabIndex = 16;
+            label2.Text = "With the above selected configuration, the maps shown below were found \r\nin the c" +
     "hosen WAD file. Please select the map to load for editing.";
-			// 
-			// label3
-			// 
-			label3.AutoSize = true;
-			label3.Location = new System.Drawing.Point(11, 198);
-			label3.Name = "label3";
-			label3.Size = new System.Drawing.Size(299, 52);
-			label3.TabIndex = 17;
-			label3.Text = resources.GetString("label3.Text");
-			// 
-			// panelres
-			// 
-			this.panelres.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            // 
+            // label3
+            // 
+            label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.panelres.Controls.Add(this.longtexturenames);
-			this.panelres.Controls.Add(this.strictpatches);
-			this.panelres.Controls.Add(this.datalocations);
-			this.panelres.Controls.Add(label3);
-			this.panelres.Location = new System.Drawing.Point(12, 246);
-			this.panelres.Name = "panelres";
-			this.panelres.Size = new System.Drawing.Size(396, 259);
-			this.panelres.TabIndex = 2;
-			this.panelres.TabStop = false;
-			this.panelres.Text = " Resources ";
-			// 
-			// longtexturenames
-			// 
-			this.longtexturenames.AutoSize = true;
-			this.longtexturenames.Location = new System.Drawing.Point(14, 45);
-			this.longtexturenames.Name = "longtexturenames";
-			this.longtexturenames.Size = new System.Drawing.Size(137, 17);
-			this.longtexturenames.TabIndex = 20;
-			this.longtexturenames.Text = "Use long texture names";
-			this.longtexturenames.UseVisualStyleBackColor = true;
-			// 
-			// strictpatches
-			// 
-			this.strictpatches.AutoSize = true;
-			this.strictpatches.Location = new System.Drawing.Point(14, 21);
-			this.strictpatches.Name = "strictpatches";
-			this.strictpatches.Size = new System.Drawing.Size(349, 17);
-			this.strictpatches.TabIndex = 19;
-			this.strictpatches.Text = "Strictly load patches between P_START and P_END only for this file";
-			this.strictpatches.UseVisualStyleBackColor = true;
-			// 
-			// datalocations
-			// 
-			this.datalocations.AllowDrop = true;
-			this.datalocations.DialogOffset = new System.Drawing.Point(40, 20);
-			this.datalocations.Location = new System.Drawing.Point(14, 68);
-			this.datalocations.Name = "datalocations";
-			this.datalocations.Size = new System.Drawing.Size(368, 127);
-			this.datalocations.TabIndex = 0;
-			// 
-			// apply
-			// 
-			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.apply.Location = new System.Drawing.Point(178, 510);
-			this.apply.Name = "apply";
-			this.apply.Size = new System.Drawing.Size(112, 25);
-			this.apply.TabIndex = 3;
-			this.apply.Text = "OK";
-			this.apply.UseVisualStyleBackColor = true;
-			this.apply.Click += new System.EventHandler(this.apply_Click);
-			// 
-			// cancel
-			// 
-			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-			this.cancel.Location = new System.Drawing.Point(296, 510);
-			this.cancel.Name = "cancel";
-			this.cancel.Size = new System.Drawing.Size(112, 25);
-			this.cancel.TabIndex = 4;
-			this.cancel.Text = "Cancel";
-			this.cancel.UseVisualStyleBackColor = true;
-			this.cancel.Click += new System.EventHandler(this.cancel_Click);
-			// 
-			// config
-			// 
-			this.config.DropDownHeight = 206;
-			this.config.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.config.FormattingEnabled = true;
-			this.config.IntegralHeight = false;
-			this.config.Location = new System.Drawing.Point(141, 21);
-			this.config.Name = "config";
-			this.config.Size = new System.Drawing.Size(267, 21);
-			this.config.TabIndex = 0;
-			this.config.SelectedIndexChanged += new System.EventHandler(this.config_SelectedIndexChanged);
-			// 
-			// mapslist
-			// 
-			this.mapslist.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            label3.AutoSize = true;
+            label3.Location = new System.Drawing.Point(11, 195);
+            label3.Name = "label3";
+            label3.Size = new System.Drawing.Size(299, 52);
+            label3.TabIndex = 17;
+            label3.Text = resources.GetString("label3.Text");
+            // 
+            // panelres
+            // 
+            this.panelres.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-			this.mapslist.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.panelres.Controls.Add(this.longtexturenames);
+            this.panelres.Controls.Add(this.strictpatches);
+            this.panelres.Controls.Add(this.datalocations);
+            this.panelres.Controls.Add(label3);
+            this.panelres.Location = new System.Drawing.Point(12, 246);
+            this.panelres.Name = "panelres";
+            this.panelres.Size = new System.Drawing.Size(396, 259);
+            this.panelres.TabIndex = 2;
+            this.panelres.TabStop = false;
+            this.panelres.Text = " Resources ";
+            // 
+            // longtexturenames
+            // 
+            this.longtexturenames.AutoSize = true;
+            this.longtexturenames.Location = new System.Drawing.Point(14, 45);
+            this.longtexturenames.Name = "longtexturenames";
+            this.longtexturenames.Size = new System.Drawing.Size(137, 17);
+            this.longtexturenames.TabIndex = 20;
+            this.longtexturenames.Text = "Use long texture names";
+            this.longtexturenames.UseVisualStyleBackColor = true;
+            // 
+            // strictpatches
+            // 
+            this.strictpatches.AutoSize = true;
+            this.strictpatches.Location = new System.Drawing.Point(14, 21);
+            this.strictpatches.Name = "strictpatches";
+            this.strictpatches.Size = new System.Drawing.Size(349, 17);
+            this.strictpatches.TabIndex = 19;
+            this.strictpatches.Text = "Strictly load patches between P_START and P_END only for this file";
+            this.strictpatches.UseVisualStyleBackColor = true;
+            // 
+            // datalocations
+            // 
+            this.datalocations.AllowDrop = true;
+            this.datalocations.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.datalocations.DialogOffset = new System.Drawing.Point(40, 20);
+            this.datalocations.GameConfiguration = null;
+            this.datalocations.Location = new System.Drawing.Point(14, 68);
+            this.datalocations.Name = "datalocations";
+            this.datalocations.Size = new System.Drawing.Size(367, 124);
+            this.datalocations.TabIndex = 0;
+            this.datalocations.OnWarningsChanged += new CodeImp.DoomBuilder.Controls.ResourceListEditor.WarningsChanged(this.datalocations_OnWarningsChanged);
+            // 
+            // apply
+            // 
+            this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.apply.Location = new System.Drawing.Point(178, 510);
+            this.apply.Name = "apply";
+            this.apply.Size = new System.Drawing.Size(112, 25);
+            this.apply.TabIndex = 3;
+            this.apply.Text = "OK";
+            this.apply.UseVisualStyleBackColor = true;
+            this.apply.Click += new System.EventHandler(this.apply_Click);
+            // 
+            // cancel
+            // 
+            this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+            this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.cancel.Location = new System.Drawing.Point(296, 510);
+            this.cancel.Name = "cancel";
+            this.cancel.Size = new System.Drawing.Size(112, 25);
+            this.cancel.TabIndex = 4;
+            this.cancel.Text = "Cancel";
+            this.cancel.UseVisualStyleBackColor = true;
+            this.cancel.Click += new System.EventHandler(this.cancel_Click);
+            // 
+            // config
+            // 
+            this.config.DropDownHeight = 206;
+            this.config.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.config.FormattingEnabled = true;
+            this.config.IntegralHeight = false;
+            this.config.Location = new System.Drawing.Point(141, 21);
+            this.config.Name = "config";
+            this.config.Size = new System.Drawing.Size(267, 21);
+            this.config.TabIndex = 0;
+            this.config.SelectedIndexChanged += new System.EventHandler(this.config_SelectedIndexChanged);
+            // 
+            // mapslist
+            // 
+            this.mapslist.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+            this.mapslist.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
             columnHeader1});
-			this.mapslist.FullRowSelect = true;
-			this.mapslist.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
-			this.mapslist.HideSelection = false;
-			this.mapslist.LabelWrap = false;
-			this.mapslist.Location = new System.Drawing.Point(12, 122);
-			this.mapslist.MultiSelect = false;
-			this.mapslist.Name = "mapslist";
-			this.mapslist.ShowGroups = false;
-			this.mapslist.Size = new System.Drawing.Size(396, 118);
-			this.mapslist.Sorting = System.Windows.Forms.SortOrder.Ascending;
-			this.mapslist.TabIndex = 1;
-			this.mapslist.UseCompatibleStateImageBehavior = false;
-			this.mapslist.View = System.Windows.Forms.View.List;
-			this.mapslist.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.mapslist_ItemSelectionChanged);
-			this.mapslist.DoubleClick += new System.EventHandler(this.mapslist_DoubleClick);
-			// 
-			// scriptcompiler
-			// 
-			this.scriptcompiler.DropDownHeight = 206;
-			this.scriptcompiler.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.scriptcompiler.Enabled = false;
-			this.scriptcompiler.FormattingEnabled = true;
-			this.scriptcompiler.IntegralHeight = false;
-			this.scriptcompiler.Location = new System.Drawing.Point(141, 49);
-			this.scriptcompiler.Name = "scriptcompiler";
-			this.scriptcompiler.Size = new System.Drawing.Size(267, 21);
-			this.scriptcompiler.TabIndex = 17;
-			// 
-			// scriptcompilerlabel
-			// 
-			this.scriptcompilerlabel.Enabled = false;
-			this.scriptcompilerlabel.Location = new System.Drawing.Point(15, 52);
-			this.scriptcompilerlabel.Name = "scriptcompilerlabel";
-			this.scriptcompilerlabel.Size = new System.Drawing.Size(120, 14);
-			this.scriptcompilerlabel.TabIndex = 18;
-			this.scriptcompilerlabel.Text = "Script Type:";
-			this.scriptcompilerlabel.TextAlign = System.Drawing.ContentAlignment.TopRight;
-			// 
-			// OpenMapOptionsForm
-			// 
-			this.AcceptButton = this.apply;
-			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
-			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-			this.CancelButton = this.cancel;
-			this.ClientSize = new System.Drawing.Size(420, 541);
-			this.Controls.Add(this.scriptcompiler);
-			this.Controls.Add(this.scriptcompilerlabel);
-			this.Controls.Add(this.mapslist);
-			this.Controls.Add(label2);
-			this.Controls.Add(this.config);
-			this.Controls.Add(label1);
-			this.Controls.Add(this.cancel);
-			this.Controls.Add(this.apply);
-			this.Controls.Add(this.panelres);
-			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
-			this.MaximizeBox = false;
-			this.MinimizeBox = false;
-			this.Name = "OpenMapOptionsForm";
-			this.Opacity = 0D;
-			this.ShowIcon = false;
-			this.ShowInTaskbar = false;
-			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
-			this.Text = "Open Map Options";
-			this.Shown += new System.EventHandler(this.OpenMapOptionsForm_Shown);
-			this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.OpenMapOptionsForm_HelpRequested);
-			this.panelres.ResumeLayout(false);
-			this.panelres.PerformLayout();
-			this.ResumeLayout(false);
-			this.PerformLayout();
+            this.mapslist.FullRowSelect = true;
+            this.mapslist.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
+            this.mapslist.HideSelection = false;
+            this.mapslist.LabelWrap = false;
+            this.mapslist.Location = new System.Drawing.Point(12, 122);
+            this.mapslist.MultiSelect = false;
+            this.mapslist.Name = "mapslist";
+            this.mapslist.ShowGroups = false;
+            this.mapslist.Size = new System.Drawing.Size(396, 118);
+            this.mapslist.Sorting = System.Windows.Forms.SortOrder.Ascending;
+            this.mapslist.TabIndex = 1;
+            this.mapslist.UseCompatibleStateImageBehavior = false;
+            this.mapslist.View = System.Windows.Forms.View.List;
+            this.mapslist.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.mapslist_ItemSelectionChanged);
+            this.mapslist.DoubleClick += new System.EventHandler(this.mapslist_DoubleClick);
+            // 
+            // scriptcompiler
+            // 
+            this.scriptcompiler.DropDownHeight = 206;
+            this.scriptcompiler.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.scriptcompiler.Enabled = false;
+            this.scriptcompiler.FormattingEnabled = true;
+            this.scriptcompiler.IntegralHeight = false;
+            this.scriptcompiler.Location = new System.Drawing.Point(141, 49);
+            this.scriptcompiler.Name = "scriptcompiler";
+            this.scriptcompiler.Size = new System.Drawing.Size(267, 21);
+            this.scriptcompiler.TabIndex = 17;
+            // 
+            // scriptcompilerlabel
+            // 
+            this.scriptcompilerlabel.Enabled = false;
+            this.scriptcompilerlabel.Location = new System.Drawing.Point(15, 52);
+            this.scriptcompilerlabel.Name = "scriptcompilerlabel";
+            this.scriptcompilerlabel.Size = new System.Drawing.Size(120, 14);
+            this.scriptcompilerlabel.TabIndex = 18;
+            this.scriptcompilerlabel.Text = "Script Type:";
+            this.scriptcompilerlabel.TextAlign = System.Drawing.ContentAlignment.TopRight;
+            // 
+            // OpenMapOptionsForm
+            // 
+            this.AcceptButton = this.apply;
+            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+            this.CancelButton = this.cancel;
+            this.ClientSize = new System.Drawing.Size(420, 541);
+            this.Controls.Add(this.scriptcompiler);
+            this.Controls.Add(this.scriptcompilerlabel);
+            this.Controls.Add(this.mapslist);
+            this.Controls.Add(label2);
+            this.Controls.Add(this.config);
+            this.Controls.Add(label1);
+            this.Controls.Add(this.cancel);
+            this.Controls.Add(this.apply);
+            this.Controls.Add(this.panelres);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "OpenMapOptionsForm";
+            this.Opacity = 0D;
+            this.ShowIcon = false;
+            this.ShowInTaskbar = false;
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Open Map Options";
+            this.Shown += new System.EventHandler(this.OpenMapOptionsForm_Shown);
+            this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.OpenMapOptionsForm_HelpRequested);
+            this.panelres.ResumeLayout(false);
+            this.panelres.PerformLayout();
+            this.ResumeLayout(false);
+            this.PerformLayout();
 
 		}
 
diff --git a/Source/Core/Windows/OpenMapOptionsForm.cs b/Source/Core/Windows/OpenMapOptionsForm.cs
index 5d1fad7aac9a42e4d81f9742596fc3906bad491b..e4b9a46290cce4587f5092d3e84748d143766598 100755
--- a/Source/Core/Windows/OpenMapOptionsForm.cs
+++ b/Source/Core/Windows/OpenMapOptionsForm.cs
@@ -41,6 +41,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private string selectedmapname;
 		private static readonly Regex episodemapregex = new Regex("^E[1-9]M[1-9]$");
 		private static readonly Regex noepisodemapregex = new Regex("^MAP[0-9][0-9]$");
+		private readonly int initialformheight;
 
 		// Properties
 		//public string FilePathName { get { return filepathname; } }
@@ -51,10 +52,12 @@ namespace CodeImp.DoomBuilder.Windows
 		{
 			// Initialize
 			InitializeComponent();
-			CodeImp.DoomBuilder.General.ApplyMonoListViewFix(mapslist);
+			this.initialformheight = Height;
+			General.ApplyMonoListViewFix(mapslist);
 			this.Text = "Open Map from " + Path.GetFileName(filepathname);
 			this.filepathname = filepathname;
 			datalocations.StartPath = filepathname; //mxd
+			datalocations.IsMapControl = true;
 			this.options = null;
 		}
 
@@ -63,12 +66,20 @@ namespace CodeImp.DoomBuilder.Windows
 		{
 			// Initialize
 			InitializeComponent();
-			CodeImp.DoomBuilder.General.ApplyMonoListViewFix(mapslist);
+			this.initialformheight = Height;
+			General.ApplyMonoListViewFix(mapslist);
 			this.Text = "Open Map from " + Path.GetFileName(filepathname);
 			this.filepathname = filepathname;
 			this.options = options;
 			datalocations.StartPath = filepathname; //mxd
-			datalocations.EditResourceLocationList(options.Resources);
+			datalocations.IsMapControl = true;
+		}
+
+		private GameConfiguration GetGameConfiguration()
+		{
+			if (config.Items.Count == 0 || config.SelectedIndex < 0) return null;
+
+			return new GameConfiguration((config.SelectedItem as ConfigurationInfo).Configuration);
 		}
 
 		// This loads the settings and attempt to find a suitable config
@@ -230,6 +241,7 @@ namespace CodeImp.DoomBuilder.Windows
 			{
 				// Show the window
 				this.Opacity = 1;
+				datalocations.GameConfiguration = GetGameConfiguration();
 			}
 
 			// Done
@@ -311,6 +323,9 @@ namespace CodeImp.DoomBuilder.Windows
 		// Configuration is selected
 		private void config_SelectedIndexChanged(object sender, EventArgs e)
 		{
+			//
+			datalocations.GameConfiguration = GetGameConfiguration();
+
 			// Anything selected?
 			if(config.SelectedIndex < 0) return;
 
@@ -528,6 +543,10 @@ namespace CodeImp.DoomBuilder.Windows
 			
 			// Load settings
 			LoadSettings();
+
+			// Show
+			if (options != null)
+				datalocations.EditResourceLocationList(options.Resources);
 		}
 
 		// Map name doubleclicked
@@ -540,7 +559,10 @@ namespace CodeImp.DoomBuilder.Windows
 		// Map name selected
 		private void mapslist_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
 		{
-			if(!e.IsSelected) return; //mxd. Don't want to trigger this twice
+			//
+			datalocations.GameConfiguration = GetGameConfiguration();
+
+			if (!e.IsSelected) return; //mxd. Don't want to trigger this twice
 			
 			DataLocationList locations;
 			DataLocationList listedlocations;
@@ -618,5 +640,11 @@ namespace CodeImp.DoomBuilder.Windows
 			General.ShowHelp("w_openmapoptions.html");
 			hlpevent.Handled = true;
 		}
-	}
+
+        private void datalocations_OnWarningsChanged(int size)
+        {
+			Height = initialformheight + size;
+			Refresh();
+		}
+    }
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/PreferencesForm.Designer.cs b/Source/Core/Windows/PreferencesForm.Designer.cs
index b9e4e71b56d5a6a4c34a41d70c5aea0ed033bd30..787bf2df26dbd8bf39773c9784cacc7d3af35c98 100755
--- a/Source/Core/Windows/PreferencesForm.Designer.cs
+++ b/Source/Core/Windows/PreferencesForm.Designer.cs
@@ -38,6 +38,7 @@ namespace CodeImp.DoomBuilder.Windows
 			System.Windows.Forms.Label label18;
 			System.Windows.Forms.Label label1;
 			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PreferencesForm));
+			this.autolaunchontest = new System.Windows.Forms.CheckBox();
 			this.texturesizesbelow = new System.Windows.Forms.CheckBox();
 			this.blackbrowsers = new System.Windows.Forms.CheckBox();
 			this.checkforupdates = new System.Windows.Forms.CheckBox();
@@ -66,12 +67,15 @@ namespace CodeImp.DoomBuilder.Windows
 			this.label26 = new System.Windows.Forms.Label();
 			this.label31 = new System.Windows.Forms.Label();
 			this.cbMarkExtraFloors = new System.Windows.Forms.CheckBox();
+			this.cbFlatShadeVertices = new System.Windows.Forms.CheckBox();
 			this.label32 = new System.Windows.Forms.Label();
 			this.label30 = new System.Windows.Forms.Label();
 			this.cbOldHighlightMode = new System.Windows.Forms.CheckBox();
 			this.cbStretchView = new System.Windows.Forms.CheckBox();
 			this.scriptautoclosebrackets = new System.Windows.Forms.CheckBox();
 			this.scriptallmanstyle = new System.Windows.Forms.CheckBox();
+			this.cbParallelizedLinedefPlotting = new System.Windows.Forms.CheckBox();
+			this.cbParallelizedVertexPlotting = new System.Windows.Forms.CheckBox();
 			this.browseScreenshotsFolderDialog = new System.Windows.Forms.FolderBrowserDialog();
 			this.apply = new System.Windows.Forms.Button();
 			this.cancel = new System.Windows.Forms.Button();
@@ -211,7 +215,20 @@ namespace CodeImp.DoomBuilder.Windows
 			this.tabpasting = new System.Windows.Forms.TabPage();
 			this.label16 = new System.Windows.Forms.Label();
 			this.pasteoptions = new CodeImp.DoomBuilder.Controls.PasteOptionsControl();
-			this.autolaunchontest = new System.Windows.Forms.CheckBox();
+			this.tabtoasts = new System.Windows.Forms.TabPage();
+			this.groupBox10 = new System.Windows.Forms.GroupBox();
+			this.lvToastActions = new System.Windows.Forms.ListView();
+			this.title = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+			this.description = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+			this.tbToastDuration = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.label12 = new System.Windows.Forms.Label();
+			this.cbToastsEnabled = new System.Windows.Forms.CheckBox();
+			this.gbToastPosition = new System.Windows.Forms.GroupBox();
+			this.label6 = new System.Windows.Forms.Label();
+			this.rbToastPosBR = new System.Windows.Forms.RadioButton();
+			this.rbToastPosBL = new System.Windows.Forms.RadioButton();
+			this.rbToastPosTR = new System.Windows.Forms.RadioButton();
+			this.rbToastPosTL = new System.Windows.Forms.RadioButton();
 			groupBox1 = new System.Windows.Forms.GroupBox();
 			label7 = new System.Windows.Forms.Label();
 			label5 = new System.Windows.Forms.Label();
@@ -256,6 +273,9 @@ namespace CodeImp.DoomBuilder.Windows
 			this.groupBox6.SuspendLayout();
 			this.previewgroup.SuspendLayout();
 			this.tabpasting.SuspendLayout();
+			this.tabtoasts.SuspendLayout();
+			this.groupBox10.SuspendLayout();
+			this.gbToastPosition.SuspendLayout();
 			this.SuspendLayout();
 			// 
 			// groupBox1
@@ -285,11 +305,21 @@ namespace CodeImp.DoomBuilder.Windows
 			groupBox1.Controls.Add(this.defaultviewmode);
 			groupBox1.Location = new System.Drawing.Point(8, 8);
 			groupBox1.Name = "groupBox1";
-			groupBox1.Size = new System.Drawing.Size(331, 438);
+			groupBox1.Size = new System.Drawing.Size(331, 462);
 			groupBox1.TabIndex = 0;
 			groupBox1.TabStop = false;
 			groupBox1.Text = " Options ";
 			// 
+			// autolaunchontest
+			// 
+			this.autolaunchontest.AutoSize = true;
+			this.autolaunchontest.Location = new System.Drawing.Point(16, 392);
+			this.autolaunchontest.Name = "autolaunchontest";
+			this.autolaunchontest.Size = new System.Drawing.Size(301, 17);
+			this.autolaunchontest.TabIndex = 50;
+			this.autolaunchontest.Text = "Automatically launch engine when test parameters change";
+			this.autolaunchontest.UseVisualStyleBackColor = true;
+			// 
 			// texturesizesbelow
 			// 
 			this.texturesizesbelow.AutoSize = true;
@@ -399,10 +429,12 @@ namespace CodeImp.DoomBuilder.Windows
 			this.vertexScale.BackColor = System.Drawing.Color.Transparent;
 			this.vertexScale.LargeChange = 1;
 			this.vertexScale.Location = new System.Drawing.Point(127, 119);
+			this.vertexScale.Maximum = 40;
 			this.vertexScale.Minimum = 1;
 			this.vertexScale.Name = "vertexScale";
 			this.vertexScale.Size = new System.Drawing.Size(116, 45);
 			this.vertexScale.TabIndex = 4;
+			this.vertexScale.TickFrequency = 4;
 			this.vertexScale.TickStyle = System.Windows.Forms.TickStyle.TopLeft;
 			this.vertexScale.Value = 1;
 			this.vertexScale.ValueChanged += new System.EventHandler(this.vertexScale_ValueChanged);
@@ -627,7 +659,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// cbMarkExtraFloors
 			// 
 			this.cbMarkExtraFloors.AutoSize = true;
-			this.cbMarkExtraFloors.Location = new System.Drawing.Point(18, 466);
+			this.cbMarkExtraFloors.Location = new System.Drawing.Point(18, 442);
 			this.cbMarkExtraFloors.Name = "cbMarkExtraFloors";
 			this.cbMarkExtraFloors.Size = new System.Drawing.Size(175, 17);
 			this.cbMarkExtraFloors.TabIndex = 1;
@@ -636,6 +668,18 @@ namespace CodeImp.DoomBuilder.Windows
         " color.");
 			this.cbMarkExtraFloors.UseVisualStyleBackColor = true;
 			// 
+			// cbFlatShadeVertices
+			// 
+			this.cbFlatShadeVertices.AutoSize = true;
+			this.cbFlatShadeVertices.Location = new System.Drawing.Point(18, 465);
+			this.cbFlatShadeVertices.Name = "cbFlatShadeVertices";
+			this.cbFlatShadeVertices.Size = new System.Drawing.Size(115, 17);
+			this.cbFlatShadeVertices.TabIndex = 1;
+			this.cbFlatShadeVertices.Text = "Flat shade vertices";
+			this.toolTip1.SetToolTip(this.cbFlatShadeVertices, "When enabled, vertices in classic mode will be drawn with flat shading instead of" +
+        " a raised border.");
+			this.cbFlatShadeVertices.UseVisualStyleBackColor = true;
+			// 
 			// label32
 			// 
 			this.label32.AutoSize = true;
@@ -661,7 +705,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// cbOldHighlightMode
 			// 
 			this.cbOldHighlightMode.AutoSize = true;
-			this.cbOldHighlightMode.Location = new System.Drawing.Point(229, 443);
+			this.cbOldHighlightMode.Location = new System.Drawing.Point(229, 419);
 			this.cbOldHighlightMode.Name = "cbOldHighlightMode";
 			this.cbOldHighlightMode.Size = new System.Drawing.Size(207, 17);
 			this.cbOldHighlightMode.TabIndex = 15;
@@ -673,7 +717,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// cbStretchView
 			// 
 			this.cbStretchView.AutoSize = true;
-			this.cbStretchView.Location = new System.Drawing.Point(229, 397);
+			this.cbStretchView.Location = new System.Drawing.Point(229, 373);
 			this.cbStretchView.Name = "cbStretchView";
 			this.cbStretchView.Size = new System.Drawing.Size(172, 17);
 			this.cbStretchView.TabIndex = 13;
@@ -705,6 +749,29 @@ namespace CodeImp.DoomBuilder.Windows
 			this.toolTip1.SetToolTip(this.scriptallmanstyle, resources.GetString("scriptallmanstyle.ToolTip"));
 			this.scriptallmanstyle.UseVisualStyleBackColor = true;
 			// 
+			// cbParallelizedLinedefPlotting
+			// 
+			this.cbParallelizedLinedefPlotting.AutoSize = true;
+			this.cbParallelizedLinedefPlotting.Location = new System.Drawing.Point(229, 465);
+			this.cbParallelizedLinedefPlotting.Name = "cbParallelizedLinedefPlotting";
+			this.cbParallelizedLinedefPlotting.Size = new System.Drawing.Size(150, 17);
+			this.cbParallelizedLinedefPlotting.TabIndex = 49;
+			this.cbParallelizedLinedefPlotting.Text = "Parallelized linedef plotting";
+			this.toolTip1.SetToolTip(this.cbParallelizedLinedefPlotting, "Parallelizes linedef plotting over all logical CPU cores.");
+			this.cbParallelizedLinedefPlotting.UseVisualStyleBackColor = true;
+			// 
+			// cbParallelizedVertexPlotting
+			// 
+			this.cbParallelizedVertexPlotting.AutoSize = true;
+			this.cbParallelizedVertexPlotting.Location = new System.Drawing.Point(18, 488);
+			this.cbParallelizedVertexPlotting.Name = "cbParallelizedVertexPlotting";
+			this.cbParallelizedVertexPlotting.Size = new System.Drawing.Size(148, 17);
+			this.cbParallelizedVertexPlotting.TabIndex = 50;
+			this.cbParallelizedVertexPlotting.Text = "Parallelized vertex plotting";
+			this.toolTip1.SetToolTip(this.cbParallelizedVertexPlotting, "Parallelizes vertex plotting over all logical CPU cores. Can result in vertices c" +
+        "lose to each other flickering when panning the view");
+			this.cbParallelizedVertexPlotting.UseVisualStyleBackColor = true;
+			// 
 			// browseScreenshotsFolderDialog
 			// 
 			this.browseScreenshotsFolderDialog.Description = "Select a Folder to Save Screenshots Into";
@@ -712,7 +779,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// apply
 			// 
 			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-			this.apply.Location = new System.Drawing.Point(467, 548);
+			this.apply.Location = new System.Drawing.Point(467, 573);
 			this.apply.Name = "apply";
 			this.apply.Size = new System.Drawing.Size(112, 25);
 			this.apply.TabIndex = 0;
@@ -724,7 +791,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// 
 			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
 			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-			this.cancel.Location = new System.Drawing.Point(585, 548);
+			this.cancel.Location = new System.Drawing.Point(585, 573);
 			this.cancel.Name = "cancel";
 			this.cancel.Size = new System.Drawing.Size(112, 25);
 			this.cancel.TabIndex = 1;
@@ -742,12 +809,13 @@ namespace CodeImp.DoomBuilder.Windows
 			this.tabs.Controls.Add(this.tabcolors);
 			this.tabs.Controls.Add(this.tabscripteditor);
 			this.tabs.Controls.Add(this.tabpasting);
+			this.tabs.Controls.Add(this.tabtoasts);
 			this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
 			this.tabs.Location = new System.Drawing.Point(11, 13);
 			this.tabs.Name = "tabs";
 			this.tabs.Padding = new System.Drawing.Point(24, 3);
 			this.tabs.SelectedIndex = 0;
-			this.tabs.Size = new System.Drawing.Size(688, 527);
+			this.tabs.Size = new System.Drawing.Size(688, 552);
 			this.tabs.TabIndex = 0;
 			this.tabs.SelectedIndexChanged += new System.EventHandler(this.tabs_SelectedIndexChanged);
 			// 
@@ -763,7 +831,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.tabinterface.Location = new System.Drawing.Point(4, 22);
 			this.tabinterface.Name = "tabinterface";
 			this.tabinterface.Padding = new System.Windows.Forms.Padding(5);
-			this.tabinterface.Size = new System.Drawing.Size(680, 501);
+			this.tabinterface.Size = new System.Drawing.Size(680, 526);
 			this.tabinterface.TabIndex = 0;
 			this.tabinterface.Text = "Interface";
 			this.tabinterface.UseVisualStyleBackColor = true;
@@ -1203,7 +1271,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.fieldofviewlabel.Name = "fieldofviewlabel";
 			this.fieldofviewlabel.Size = new System.Drawing.Size(23, 13);
 			this.fieldofviewlabel.TabIndex = 19;
-			this.fieldofviewlabel.Text = "50°";
+			this.fieldofviewlabel.Text = "50°";
 			// 
 			// label4
 			// 
@@ -1225,7 +1293,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.tabkeys.Location = new System.Drawing.Point(4, 22);
 			this.tabkeys.Name = "tabkeys";
 			this.tabkeys.Padding = new System.Windows.Forms.Padding(3);
-			this.tabkeys.Size = new System.Drawing.Size(680, 501);
+			this.tabkeys.Size = new System.Drawing.Size(680, 526);
 			this.tabkeys.TabIndex = 1;
 			this.tabkeys.Text = "Controls";
 			this.tabkeys.UseVisualStyleBackColor = true;
@@ -1415,7 +1483,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.tabcolors.Location = new System.Drawing.Point(4, 22);
 			this.tabcolors.Name = "tabcolors";
 			this.tabcolors.Padding = new System.Windows.Forms.Padding(5);
-			this.tabcolors.Size = new System.Drawing.Size(680, 501);
+			this.tabcolors.Size = new System.Drawing.Size(680, 526);
 			this.tabcolors.TabIndex = 2;
 			this.tabcolors.Text = "Appearance";
 			this.tabcolors.UseVisualStyleBackColor = true;
@@ -1425,9 +1493,12 @@ namespace CodeImp.DoomBuilder.Windows
 			this.appearancegroup1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
+			this.appearancegroup1.Controls.Add(this.cbParallelizedVertexPlotting);
+			this.appearancegroup1.Controls.Add(this.cbParallelizedLinedefPlotting);
 			this.appearancegroup1.Controls.Add(this.activethingsalphalabel);
 			this.appearancegroup1.Controls.Add(this.label31);
 			this.appearancegroup1.Controls.Add(this.cbMarkExtraFloors);
+			this.appearancegroup1.Controls.Add(this.cbFlatShadeVertices);
 			this.appearancegroup1.Controls.Add(this.activethingsalpha);
 			this.appearancegroup1.Controls.Add(this.hiddenthingsalphalabel);
 			this.appearancegroup1.Controls.Add(this.label32);
@@ -1549,7 +1620,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// cbShowFPS
 			// 
 			this.cbShowFPS.AutoSize = true;
-			this.cbShowFPS.Location = new System.Drawing.Point(229, 466);
+			this.cbShowFPS.Location = new System.Drawing.Point(229, 442);
 			this.cbShowFPS.Name = "cbShowFPS";
 			this.cbShowFPS.Size = new System.Drawing.Size(148, 17);
 			this.cbShowFPS.TabIndex = 15;
@@ -1568,7 +1639,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// qualitydisplay
 			// 
 			this.qualitydisplay.AutoSize = true;
-			this.qualitydisplay.Location = new System.Drawing.Point(18, 397);
+			this.qualitydisplay.Location = new System.Drawing.Point(18, 373);
 			this.qualitydisplay.Name = "qualitydisplay";
 			this.qualitydisplay.Size = new System.Drawing.Size(128, 17);
 			this.qualitydisplay.TabIndex = 10;
@@ -1622,7 +1693,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// animatevisualselection
 			// 
 			this.animatevisualselection.AutoSize = true;
-			this.animatevisualselection.Location = new System.Drawing.Point(229, 420);
+			this.animatevisualselection.Location = new System.Drawing.Point(229, 396);
 			this.animatevisualselection.Name = "animatevisualselection";
 			this.animatevisualselection.Size = new System.Drawing.Size(190, 17);
 			this.animatevisualselection.TabIndex = 14;
@@ -1665,7 +1736,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// visualbilinear
 			// 
 			this.visualbilinear.AutoSize = true;
-			this.visualbilinear.Location = new System.Drawing.Point(18, 443);
+			this.visualbilinear.Location = new System.Drawing.Point(18, 419);
 			this.visualbilinear.Name = "visualbilinear";
 			this.visualbilinear.Size = new System.Drawing.Size(171, 17);
 			this.visualbilinear.TabIndex = 12;
@@ -1675,7 +1746,7 @@ namespace CodeImp.DoomBuilder.Windows
 			// classicbilinear
 			// 
 			this.classicbilinear.AutoSize = true;
-			this.classicbilinear.Location = new System.Drawing.Point(18, 420);
+			this.classicbilinear.Location = new System.Drawing.Point(18, 396);
 			this.classicbilinear.Name = "classicbilinear";
 			this.classicbilinear.Size = new System.Drawing.Size(176, 17);
 			this.classicbilinear.TabIndex = 11;
@@ -1857,7 +1928,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.tabscripteditor.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
 			this.tabscripteditor.Location = new System.Drawing.Point(4, 22);
 			this.tabscripteditor.Name = "tabscripteditor";
-			this.tabscripteditor.Size = new System.Drawing.Size(680, 501);
+			this.tabscripteditor.Size = new System.Drawing.Size(680, 526);
 			this.tabscripteditor.TabIndex = 4;
 			this.tabscripteditor.Text = "Script Editor";
 			this.tabscripteditor.UseVisualStyleBackColor = true;
@@ -2350,7 +2421,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.tabpasting.Location = new System.Drawing.Point(4, 22);
 			this.tabpasting.Name = "tabpasting";
 			this.tabpasting.Padding = new System.Windows.Forms.Padding(5);
-			this.tabpasting.Size = new System.Drawing.Size(680, 501);
+			this.tabpasting.Size = new System.Drawing.Size(680, 526);
 			this.tabpasting.TabIndex = 3;
 			this.tabpasting.Text = "Pasting ";
 			this.tabpasting.UseVisualStyleBackColor = true;
@@ -2377,15 +2448,161 @@ namespace CodeImp.DoomBuilder.Windows
 			this.pasteoptions.Size = new System.Drawing.Size(666, 427);
 			this.pasteoptions.TabIndex = 0;
 			// 
-			// autolaunchontest
-			// 
-			this.autolaunchontest.AutoSize = true;
-			this.autolaunchontest.Location = new System.Drawing.Point(16, 392);
-			this.autolaunchontest.Name = "autolaunchontest";
-			this.autolaunchontest.Size = new System.Drawing.Size(301, 17);
-			this.autolaunchontest.TabIndex = 50;
-			this.autolaunchontest.Text = "Automatically launch engine when test parameters change";
-			this.autolaunchontest.UseVisualStyleBackColor = true;
+			// tabtoasts
+			// 
+			this.tabtoasts.Controls.Add(this.groupBox10);
+			this.tabtoasts.Controls.Add(this.tbToastDuration);
+			this.tabtoasts.Controls.Add(this.label12);
+			this.tabtoasts.Controls.Add(this.cbToastsEnabled);
+			this.tabtoasts.Controls.Add(this.gbToastPosition);
+			this.tabtoasts.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.tabtoasts.Location = new System.Drawing.Point(4, 22);
+			this.tabtoasts.Name = "tabtoasts";
+			this.tabtoasts.Size = new System.Drawing.Size(680, 526);
+			this.tabtoasts.TabIndex = 5;
+			this.tabtoasts.Text = "Toasts";
+			this.tabtoasts.UseVisualStyleBackColor = true;
+			// 
+			// groupBox10
+			// 
+			this.groupBox10.Controls.Add(this.lvToastActions);
+			this.groupBox10.Location = new System.Drawing.Point(11, 164);
+			this.groupBox10.Name = "groupBox10";
+			this.groupBox10.Size = new System.Drawing.Size(653, 346);
+			this.groupBox10.TabIndex = 5;
+			this.groupBox10.TabStop = false;
+			this.groupBox10.Text = "Toasts";
+			// 
+			// lvToastActions
+			// 
+			this.lvToastActions.CheckBoxes = true;
+			this.lvToastActions.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.title,
+            this.description});
+			this.lvToastActions.FullRowSelect = true;
+			this.lvToastActions.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
+			this.lvToastActions.HideSelection = false;
+			this.lvToastActions.Location = new System.Drawing.Point(6, 19);
+			this.lvToastActions.MultiSelect = false;
+			this.lvToastActions.Name = "lvToastActions";
+			this.lvToastActions.ShowGroups = false;
+			this.lvToastActions.Size = new System.Drawing.Size(641, 321);
+			this.lvToastActions.TabIndex = 5;
+			this.lvToastActions.UseCompatibleStateImageBehavior = false;
+			this.lvToastActions.View = System.Windows.Forms.View.Details;
+			// 
+			// title
+			// 
+			this.title.Text = "Title";
+			// 
+			// description
+			// 
+			this.description.Text = "Description";
+			// 
+			// tbToastDuration
+			// 
+			this.tbToastDuration.AllowDecimal = false;
+			this.tbToastDuration.AllowExpressions = false;
+			this.tbToastDuration.AllowNegative = false;
+			this.tbToastDuration.AllowRelative = false;
+			this.tbToastDuration.ButtonStep = 1;
+			this.tbToastDuration.ButtonStepBig = 10F;
+			this.tbToastDuration.ButtonStepFloat = 1F;
+			this.tbToastDuration.ButtonStepSmall = 0.1F;
+			this.tbToastDuration.ButtonStepsUseModifierKeys = false;
+			this.tbToastDuration.ButtonStepsWrapAround = false;
+			this.tbToastDuration.Location = new System.Drawing.Point(350, 45);
+			this.tbToastDuration.Name = "tbToastDuration";
+			this.tbToastDuration.Size = new System.Drawing.Size(70, 24);
+			this.tbToastDuration.StepValues = null;
+			this.tbToastDuration.TabIndex = 3;
+			this.tbToastDuration.WhenTextChanged += new System.EventHandler(this.tbToastDuration_WhenTextChanged);
+			// 
+			// label12
+			// 
+			this.label12.AutoSize = true;
+			this.label12.Location = new System.Drawing.Point(217, 50);
+			this.label12.Name = "label12";
+			this.label12.Size = new System.Drawing.Size(99, 13);
+			this.label12.TabIndex = 2;
+			this.label12.Text = "Duration (seconds):";
+			// 
+			// cbToastsEnabled
+			// 
+			this.cbToastsEnabled.AutoSize = true;
+			this.cbToastsEnabled.Location = new System.Drawing.Point(11, 15);
+			this.cbToastsEnabled.Name = "cbToastsEnabled";
+			this.cbToastsEnabled.Size = new System.Drawing.Size(90, 17);
+			this.cbToastsEnabled.TabIndex = 1;
+			this.cbToastsEnabled.Text = "Enable toasts";
+			this.cbToastsEnabled.UseVisualStyleBackColor = true;
+			this.cbToastsEnabled.CheckedChanged += new System.EventHandler(this.cbToastsEnabled_CheckedChanged);
+			// 
+			// gbToastPosition
+			// 
+			this.gbToastPosition.Controls.Add(this.label6);
+			this.gbToastPosition.Controls.Add(this.rbToastPosBR);
+			this.gbToastPosition.Controls.Add(this.rbToastPosBL);
+			this.gbToastPosition.Controls.Add(this.rbToastPosTR);
+			this.gbToastPosition.Controls.Add(this.rbToastPosTL);
+			this.gbToastPosition.Location = new System.Drawing.Point(11, 38);
+			this.gbToastPosition.Name = "gbToastPosition";
+			this.gbToastPosition.Size = new System.Drawing.Size(200, 120);
+			this.gbToastPosition.TabIndex = 0;
+			this.gbToastPosition.TabStop = false;
+			// 
+			// label6
+			// 
+			this.label6.AutoSize = true;
+			this.label6.Location = new System.Drawing.Point(62, 56);
+			this.label6.Name = "label6";
+			this.label6.Size = new System.Drawing.Size(73, 13);
+			this.label6.TabIndex = 4;
+			this.label6.Text = "Toast position";
+			// 
+			// rbToastPosBR
+			// 
+			this.rbToastPosBR.AutoSize = true;
+			this.rbToastPosBR.Location = new System.Drawing.Point(180, 101);
+			this.rbToastPosBR.Name = "rbToastPosBR";
+			this.rbToastPosBR.Size = new System.Drawing.Size(14, 13);
+			this.rbToastPosBR.TabIndex = 3;
+			this.rbToastPosBR.TabStop = true;
+			this.rbToastPosBR.Tag = "3";
+			this.rbToastPosBR.UseVisualStyleBackColor = true;
+			// 
+			// rbToastPosBL
+			// 
+			this.rbToastPosBL.AutoSize = true;
+			this.rbToastPosBL.Location = new System.Drawing.Point(6, 101);
+			this.rbToastPosBL.Name = "rbToastPosBL";
+			this.rbToastPosBL.Size = new System.Drawing.Size(14, 13);
+			this.rbToastPosBL.TabIndex = 2;
+			this.rbToastPosBL.TabStop = true;
+			this.rbToastPosBL.Tag = "4";
+			this.rbToastPosBL.UseVisualStyleBackColor = true;
+			// 
+			// rbToastPosTR
+			// 
+			this.rbToastPosTR.AutoSize = true;
+			this.rbToastPosTR.Location = new System.Drawing.Point(180, 12);
+			this.rbToastPosTR.Name = "rbToastPosTR";
+			this.rbToastPosTR.Size = new System.Drawing.Size(14, 13);
+			this.rbToastPosTR.TabIndex = 1;
+			this.rbToastPosTR.TabStop = true;
+			this.rbToastPosTR.Tag = "2";
+			this.rbToastPosTR.UseVisualStyleBackColor = true;
+			// 
+			// rbToastPosTL
+			// 
+			this.rbToastPosTL.AutoSize = true;
+			this.rbToastPosTL.Location = new System.Drawing.Point(6, 12);
+			this.rbToastPosTL.Name = "rbToastPosTL";
+			this.rbToastPosTL.Size = new System.Drawing.Size(14, 13);
+			this.rbToastPosTL.TabIndex = 0;
+			this.rbToastPosTL.TabStop = true;
+			this.rbToastPosTL.Tag = "1";
+			this.rbToastPosTL.UseVisualStyleBackColor = true;
 			// 
 			// PreferencesForm
 			// 
@@ -2393,7 +2610,7 @@ namespace CodeImp.DoomBuilder.Windows
 			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
 			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
 			this.CancelButton = this.cancel;
-			this.ClientSize = new System.Drawing.Size(709, 585);
+			this.ClientSize = new System.Drawing.Size(709, 610);
 			this.Controls.Add(this.cancel);
 			this.Controls.Add(this.apply);
 			this.Controls.Add(this.tabs);
@@ -2458,6 +2675,11 @@ namespace CodeImp.DoomBuilder.Windows
 			this.groupBox6.PerformLayout();
 			this.previewgroup.ResumeLayout(false);
 			this.tabpasting.ResumeLayout(false);
+			this.tabtoasts.ResumeLayout(false);
+			this.tabtoasts.PerformLayout();
+			this.groupBox10.ResumeLayout(false);
+			this.gbToastPosition.ResumeLayout(false);
+			this.gbToastPosition.PerformLayout();
 			this.ResumeLayout(false);
 
 		}
@@ -2559,6 +2781,7 @@ namespace CodeImp.DoomBuilder.Windows
 		private CodeImp.DoomBuilder.Controls.ColorControl color3dFloors;
 		private System.Windows.Forms.TextBox actiondescription;
 		private System.Windows.Forms.CheckBox cbMarkExtraFloors;
+		private System.Windows.Forms.CheckBox cbFlatShadeVertices;
 		private CodeImp.DoomBuilder.Controls.TransparentTrackBar recentFiles;
 		private System.Windows.Forms.Label labelRecentFiles;
 		private System.Windows.Forms.Label label25;
@@ -2638,5 +2861,21 @@ namespace CodeImp.DoomBuilder.Windows
         private System.Windows.Forms.CheckBox texturesizesbelow;
 		private System.Windows.Forms.CheckBox cbShowFPS;
 		private System.Windows.Forms.CheckBox autolaunchontest;
+		private System.Windows.Forms.TabPage tabtoasts;
+		private Controls.ButtonsNumericTextbox tbToastDuration;
+		private System.Windows.Forms.Label label12;
+		private System.Windows.Forms.CheckBox cbToastsEnabled;
+		private System.Windows.Forms.GroupBox gbToastPosition;
+		private System.Windows.Forms.Label label6;
+		private System.Windows.Forms.RadioButton rbToastPosBR;
+		private System.Windows.Forms.RadioButton rbToastPosBL;
+		private System.Windows.Forms.RadioButton rbToastPosTR;
+		private System.Windows.Forms.RadioButton rbToastPosTL;
+		private System.Windows.Forms.GroupBox groupBox10;
+		private System.Windows.Forms.ListView lvToastActions;
+		private System.Windows.Forms.ColumnHeader title;
+		private System.Windows.Forms.ColumnHeader description;
+		private System.Windows.Forms.CheckBox cbParallelizedVertexPlotting;
+		private System.Windows.Forms.CheckBox cbParallelizedLinedefPlotting;
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/PreferencesForm.cs b/Source/Core/Windows/PreferencesForm.cs
index fa76ae38340f2f9e023456d15abd760398b5970e..88cc3b72cd41db30bbb7ad907cb5be8cdac13202 100755
--- a/Source/Core/Windows/PreferencesForm.cs
+++ b/Source/Core/Windows/PreferencesForm.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Actions;
 using System.Globalization;
@@ -97,6 +98,9 @@ namespace CodeImp.DoomBuilder.Windows
             texturesizesbelow.Checked = General.Settings.TextureSizesBelow;
 			cbShowFPS.Checked = General.Settings.ShowFPS;
 			autolaunchontest.Checked = General.Settings.AutoLaunchOnTest;
+			cbFlatShadeVertices.Checked = General.Settings.FlatShadeVertices;
+			cbParallelizedLinedefPlotting.Checked = General.Settings.ParallelizedLinedefPlotting;
+			cbParallelizedVertexPlotting.Checked = General.Settings.ParallelizedVertexPlotting;
 
 			//mxd
 			locatetexturegroup.Checked = General.Settings.LocateTextureGroup;
@@ -108,8 +112,8 @@ namespace CodeImp.DoomBuilder.Windows
 			labelDynLightCount.Text = General.Settings.GZMaxDynamicLights.ToString();
 			cbStretchView.Checked = General.Settings.GZStretchView;
 			cbOldHighlightMode.Checked = General.Settings.GZOldHighlightMode;
-			vertexScale.Value = General.Clamp((int)(General.Settings.GZVertexScale2D), vertexScale.Minimum, vertexScale.Maximum);
-			vertexScaleLabel.Text = vertexScale.Value * 100 + "%" + (vertexScale.Value == 1 ? " (default)" : "");
+			vertexScale.Value = General.Clamp((int)(General.Settings.GZVertexScale2D * 4.0), vertexScale.Minimum, vertexScale.Maximum);
+			vertexScaleLabel.Text = vertexScale.Value * 25 + "%" + (vertexScale.Value == 4 ? " (default)" : "");
 			cbMarkExtraFloors.Checked = General.Settings.GZMarkExtraFloors;
 			recentFiles.Value = General.Settings.MaxRecentFiles;
 			screenshotsfolderpath.Text = General.Settings.ScreenshotsPath;
@@ -267,6 +271,28 @@ namespace CodeImp.DoomBuilder.Windows
 			// Paste options
 			pasteoptions.Setup(General.Settings.PasteOptions.Copy());
 
+			// Toasts
+			cbToastsEnabled.Checked = General.ToastManager.Enabled;
+			tbToastDuration.Text = (General.ToastManager.Duration / 1000).ToString();
+			RadioButton rb = gbToastPosition.Controls.OfType<RadioButton>().FirstOrDefault(r => (string)r.Tag == ((int)General.ToastManager.Anchor).ToString());
+			if (rb != null)
+				rb.Checked = true;
+
+			SetToastSettingEnabled(cbToastsEnabled.Checked);
+
+			// Add checkboxes for all registered toasts
+			foreach(ToastRegistryEntry tre in General.ToastManager.Registry.Values.OrderBy(e => e.Title))
+			{
+				ListViewItem lvi = lvToastActions.Items.Add(tre.Title);
+				lvi.SubItems.Add(tre.Description);
+				lvi.Checked = tre.Enabled;
+				lvi.Tag = tre;
+			}
+
+			// Make the columns fit the contents
+			title.Width = -1;
+			description.Width = -1;
+
 			// Allow plugins to add tabs
 			this.SuspendLayout();
 			controller = new PreferencesController(this) { AllowAddTab = true };
@@ -335,6 +361,9 @@ namespace CodeImp.DoomBuilder.Windows
 			General.Settings.ScreenshotsPath = screenshotsfolderpath.Text.Trim(); //mxd
 			General.Settings.ShowFPS = cbShowFPS.Checked;
 			General.Settings.AutoLaunchOnTest = autolaunchontest.Checked;
+			General.Settings.FlatShadeVertices = cbFlatShadeVertices.Checked;
+			General.Settings.ParallelizedLinedefPlotting = cbParallelizedLinedefPlotting.Checked;
+			General.Settings.ParallelizedVertexPlotting = cbParallelizedVertexPlotting.Checked;
 
 			// Script settings
 			General.Settings.ScriptFontBold = scriptfontbold.Checked;
@@ -416,15 +445,27 @@ namespace CodeImp.DoomBuilder.Windows
 			General.Settings.FilterAnisotropy = RenderDevice.AF_STEPS[anisotropicfiltering.Value];
 			General.Settings.AntiAliasingSamples = RenderDevice.AA_STEPS[antialiasing.Value];
 			General.Settings.GZStretchView = cbStretchView.Checked;
-			General.Settings.GZVertexScale2D = vertexScale.Value;
+			General.Settings.GZVertexScale2D = (float)vertexScale.Value / 4.0f;
 			General.Settings.GZOldHighlightMode = cbOldHighlightMode.Checked;
 			General.Settings.GZMarkExtraFloors = cbMarkExtraFloors.Checked;
 
 			// Paste options
 			General.Settings.PasteOptions = pasteoptions.GetOptions();
+
+			// Toasts
+			General.ToastManager.Enabled = cbToastsEnabled.Checked;
+			General.ToastManager.Anchor = (ToastAnchor)int.Parse((string)gbToastPosition.Controls.OfType<RadioButton>().FirstOrDefault(rb => rb.Checked).Tag);
+			General.ToastManager.Duration = tbToastDuration.GetResult(1) * 1000;
+
+			foreach(ListViewItem lvi in lvToastActions.Items)
+			{
+				if(lvi.Tag is ToastRegistryEntry tre)
+					General.ToastManager.Registry[tre.Name].Enabled = lvi.Checked;
+			}
 			
 			// Let the plugins know we're closing
 			General.Plugins.OnClosePreferences(controller);
+
 			
 			// Close
 			this.DialogResult = DialogResult.OK;
@@ -530,7 +571,7 @@ namespace CodeImp.DoomBuilder.Windows
 		//mxd
 		private void vertexScale_ValueChanged(object sender, EventArgs e) 
 		{
-			vertexScaleLabel.Text = (vertexScale.Value * 100) + "%";
+			vertexScaleLabel.Text = (vertexScale.Value * 25) + "%";
 		}
 
 		//mxd
@@ -1258,6 +1299,39 @@ namespace CodeImp.DoomBuilder.Windows
 
 		#endregion
 
+		#region ================== Toasts
+
+		private void tbToastDuration_WhenTextChanged(object sender, EventArgs e)
+		{
+			if (!allowapplycontrol)
+				return;
+
+			if (tbToastDuration.GetResult(1) <= 0)
+			{
+				allowapplycontrol = false;
+				tbToastDuration.Text = "1";
+				allowapplycontrol = true;
+			}
+		}
+
+		private void cbToastsEnabled_CheckedChanged(object sender, EventArgs e)
+		{
+			SetToastSettingEnabled(cbToastsEnabled.Checked);
+		}
+
+		private void SetToastSettingEnabled(bool enabled)
+		{
+			foreach (Control c in tabtoasts.Controls)
+			{
+				if (c == cbToastsEnabled)
+					continue;
+
+				c.Enabled = cbToastsEnabled.Checked;
+			}
+		}
+
+		#endregion
+
 		#region ================== Screenshots Stuff (mxd)
 
 		private void resetscreenshotsdir_Click(object sender, EventArgs e) 
@@ -1284,7 +1358,7 @@ namespace CodeImp.DoomBuilder.Windows
 			}
 		}
 
-        /*
+		/*
 		// This writes all action help files using a template and some basic info from the actions.
 		// Also writes actioncontents.txt with all files to be inserted into Contents.hhc.
 		// Only used during development. Actual button to call this has been removed.
@@ -1316,5 +1390,5 @@ namespace CodeImp.DoomBuilder.Windows
 			File.WriteAllText(filename, contents.ToString());
 		}
 		*/
-    }
+	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/PreferencesForm.resx b/Source/Core/Windows/PreferencesForm.resx
index ffd242b8d03ab08db4ec320b0ae5b5ad5deddf9b..1bb2546d2eaba754212652bb5d9591afa729be6a 100755
--- a/Source/Core/Windows/PreferencesForm.resx
+++ b/Source/Core/Windows/PreferencesForm.resx
@@ -141,9 +141,6 @@
   <metadata name="label1.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
-  <metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
-    <value>17, 17</value>
-  </metadata>
   <data name="scriptallmanstyle.ToolTip" xml:space="preserve">
     <value>When enabled, the opening brace 
 will be placed on a new line when
diff --git a/Source/Core/Windows/ResourceOptionsForm.Designer.cs b/Source/Core/Windows/ResourceOptionsForm.Designer.cs
index 7f0f2494dda85b7c5be871c10e564bdf6fef03ef..d242e294baf1f53775dde845db37c104fc498337 100755
--- a/Source/Core/Windows/ResourceOptionsForm.Designer.cs
+++ b/Source/Core/Windows/ResourceOptionsForm.Designer.cs
@@ -28,328 +28,378 @@ namespace CodeImp.DoomBuilder.Windows
 		/// </summary>
 		private void InitializeComponent()
 		{
-            System.Windows.Forms.Label label1;
-            System.Windows.Forms.Label label2;
-            System.Windows.Forms.Label label3;
-            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ResourceOptionsForm));
-            this.tabs = new System.Windows.Forms.TabControl();
-            this.wadfiletab = new System.Windows.Forms.TabPage();
-            this.label6 = new System.Windows.Forms.Label();
-            this.strictpatches = new System.Windows.Forms.CheckBox();
-            this.browsewad = new System.Windows.Forms.Button();
-            this.wadlocation = new System.Windows.Forms.TextBox();
-            this.directorytab = new System.Windows.Forms.TabPage();
-            this.directorylink = new System.Windows.Forms.LinkLabel();
-            this.dir_flats = new System.Windows.Forms.CheckBox();
-            this.dir_textures = new System.Windows.Forms.CheckBox();
-            this.browsedir = new System.Windows.Forms.Button();
-            this.dirlocation = new System.Windows.Forms.TextBox();
-            this.pk3filetab = new System.Windows.Forms.TabPage();
-            this.pk3link = new System.Windows.Forms.LinkLabel();
-            this.browsepk3 = new System.Windows.Forms.Button();
-            this.pk3location = new System.Windows.Forms.TextBox();
-            this.cancel = new System.Windows.Forms.Button();
-            this.apply = new System.Windows.Forms.Button();
-            this.wadfiledialog = new System.Windows.Forms.OpenFileDialog();
-            this.pk3filedialog = new System.Windows.Forms.OpenFileDialog();
-            this.notfortesting = new System.Windows.Forms.CheckBox();
-            label1 = new System.Windows.Forms.Label();
-            label2 = new System.Windows.Forms.Label();
-            label3 = new System.Windows.Forms.Label();
-            this.tabs.SuspendLayout();
-            this.wadfiletab.SuspendLayout();
-            this.directorytab.SuspendLayout();
-            this.pk3filetab.SuspendLayout();
-            this.SuspendLayout();
-            // 
-            // label1
-            // 
-            label1.AutoSize = true;
-            label1.Location = new System.Drawing.Point(15, 20);
-            label1.Name = "label1";
-            label1.Size = new System.Drawing.Size(104, 13);
-            label1.TabIndex = 0;
-            label1.Text = "WAD File Resource:";
-            // 
-            // label2
-            // 
-            label2.AutoSize = true;
-            label2.Location = new System.Drawing.Point(15, 20);
-            label2.Name = "label2";
-            label2.Size = new System.Drawing.Size(101, 13);
-            label2.TabIndex = 3;
-            label2.Text = "Directory Resource:";
-            // 
-            // label3
-            // 
-            label3.AutoSize = true;
-            label3.Location = new System.Drawing.Point(15, 20);
-            label3.Name = "label3";
-            label3.Size = new System.Drawing.Size(133, 13);
-            label3.TabIndex = 3;
-            label3.Text = "PK3 or PK7 File Resource:";
-            // 
-            // tabs
-            // 
-            this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+			System.Windows.Forms.Label label1;
+			System.Windows.Forms.Label label2;
+			System.Windows.Forms.Label label3;
+			System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ResourceOptionsForm));
+			this.tabs = new System.Windows.Forms.TabControl();
+			this.wadfiletab = new System.Windows.Forms.TabPage();
+			this.label6 = new System.Windows.Forms.Label();
+			this.strictpatches = new System.Windows.Forms.CheckBox();
+			this.browsewad = new System.Windows.Forms.Button();
+			this.wadlocation = new System.Windows.Forms.TextBox();
+			this.directorytab = new System.Windows.Forms.TabPage();
+			this.directorylink = new System.Windows.Forms.LinkLabel();
+			this.dir_flats = new System.Windows.Forms.CheckBox();
+			this.dir_textures = new System.Windows.Forms.CheckBox();
+			this.browsedir = new System.Windows.Forms.Button();
+			this.dirlocation = new System.Windows.Forms.TextBox();
+			this.pk3filetab = new System.Windows.Forms.TabPage();
+			this.pk3link = new System.Windows.Forms.LinkLabel();
+			this.browsepk3 = new System.Windows.Forms.Button();
+			this.pk3location = new System.Windows.Forms.TextBox();
+			this.cancel = new System.Windows.Forms.Button();
+			this.apply = new System.Windows.Forms.Button();
+			this.wadfiledialog = new System.Windows.Forms.OpenFileDialog();
+			this.pk3filedialog = new System.Windows.Forms.OpenFileDialog();
+			this.notfortesting = new System.Windows.Forms.CheckBox();
+			this.checkingloader = new System.Windows.Forms.Panel();
+			this.pictureBox1 = new System.Windows.Forms.PictureBox();
+			this.label4 = new System.Windows.Forms.Label();
+			label1 = new System.Windows.Forms.Label();
+			label2 = new System.Windows.Forms.Label();
+			label3 = new System.Windows.Forms.Label();
+			this.tabs.SuspendLayout();
+			this.wadfiletab.SuspendLayout();
+			this.directorytab.SuspendLayout();
+			this.pk3filetab.SuspendLayout();
+			this.checkingloader.SuspendLayout();
+			((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
+			this.SuspendLayout();
+			// 
+			// label1
+			// 
+			label1.AutoSize = true;
+			label1.Location = new System.Drawing.Point(15, 20);
+			label1.Name = "label1";
+			label1.Size = new System.Drawing.Size(104, 13);
+			label1.TabIndex = 0;
+			label1.Text = "WAD File Resource:";
+			// 
+			// label2
+			// 
+			label2.AutoSize = true;
+			label2.Location = new System.Drawing.Point(15, 20);
+			label2.Name = "label2";
+			label2.Size = new System.Drawing.Size(101, 13);
+			label2.TabIndex = 3;
+			label2.Text = "Directory Resource:";
+			// 
+			// label3
+			// 
+			label3.AutoSize = true;
+			label3.Location = new System.Drawing.Point(15, 20);
+			label3.Name = "label3";
+			label3.Size = new System.Drawing.Size(133, 13);
+			label3.TabIndex = 3;
+			label3.Text = "PK3 or PK7 File Resource:";
+			// 
+			// tabs
+			// 
+			this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-            this.tabs.Controls.Add(this.wadfiletab);
-            this.tabs.Controls.Add(this.directorytab);
-            this.tabs.Controls.Add(this.pk3filetab);
-            this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
-            this.tabs.Location = new System.Drawing.Point(9, 11);
-            this.tabs.Name = "tabs";
-            this.tabs.Padding = new System.Drawing.Point(16, 3);
-            this.tabs.SelectedIndex = 0;
-            this.tabs.Size = new System.Drawing.Size(369, 211);
-            this.tabs.TabIndex = 0;
-            // 
-            // wadfiletab
-            // 
-            this.wadfiletab.Controls.Add(this.label6);
-            this.wadfiletab.Controls.Add(this.strictpatches);
-            this.wadfiletab.Controls.Add(this.browsewad);
-            this.wadfiletab.Controls.Add(this.wadlocation);
-            this.wadfiletab.Controls.Add(label1);
-            this.wadfiletab.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.wadfiletab.Location = new System.Drawing.Point(4, 22);
-            this.wadfiletab.Name = "wadfiletab";
-            this.wadfiletab.Padding = new System.Windows.Forms.Padding(3);
-            this.wadfiletab.Size = new System.Drawing.Size(361, 185);
-            this.wadfiletab.TabIndex = 0;
-            this.wadfiletab.Text = "From WAD File";
-            this.wadfiletab.UseVisualStyleBackColor = true;
-            // 
-            // label6
-            // 
-            this.label6.Location = new System.Drawing.Point(14, 102);
-            this.label6.Name = "label6";
-            this.label6.Size = new System.Drawing.Size(329, 58);
-            this.label6.TabIndex = 8;
-            this.label6.Text = resources.GetString("label6.Text");
-            // 
-            // strictpatches
-            // 
-            this.strictpatches.AutoSize = true;
-            this.strictpatches.Location = new System.Drawing.Point(17, 72);
-            this.strictpatches.Name = "strictpatches";
-            this.strictpatches.Size = new System.Drawing.Size(299, 17);
-            this.strictpatches.TabIndex = 2;
-            this.strictpatches.Text = "Strictly load patches between P_START and P_END only";
-            this.strictpatches.UseVisualStyleBackColor = true;
-            // 
-            // browsewad
-            // 
-            this.browsewad.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
-            this.browsewad.Location = new System.Drawing.Point(315, 35);
-            this.browsewad.Name = "browsewad";
-            this.browsewad.Padding = new System.Windows.Forms.Padding(0, 0, 1, 3);
-            this.browsewad.Size = new System.Drawing.Size(28, 24);
-            this.browsewad.TabIndex = 1;
-            this.browsewad.Text = " ";
-            this.browsewad.UseVisualStyleBackColor = true;
-            this.browsewad.Click += new System.EventHandler(this.browsewad_Click);
-            // 
-            // wadlocation
-            // 
-            this.wadlocation.Location = new System.Drawing.Point(17, 37);
-            this.wadlocation.Name = "wadlocation";
-            this.wadlocation.ReadOnly = true;
-            this.wadlocation.Size = new System.Drawing.Size(292, 20);
-            this.wadlocation.TabIndex = 0;
-            // 
-            // directorytab
-            // 
-            this.directorytab.Controls.Add(this.directorylink);
-            this.directorytab.Controls.Add(this.dir_flats);
-            this.directorytab.Controls.Add(this.dir_textures);
-            this.directorytab.Controls.Add(this.browsedir);
-            this.directorytab.Controls.Add(this.dirlocation);
-            this.directorytab.Controls.Add(label2);
-            this.directorytab.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.directorytab.Location = new System.Drawing.Point(4, 22);
-            this.directorytab.Name = "directorytab";
-            this.directorytab.Padding = new System.Windows.Forms.Padding(3);
-            this.directorytab.Size = new System.Drawing.Size(361, 185);
-            this.directorytab.TabIndex = 1;
-            this.directorytab.Text = "From Directory";
-            this.directorytab.UseVisualStyleBackColor = true;
-            // 
-            // directorylink
-            // 
-            this.directorylink.LinkArea = new System.Windows.Forms.LinkArea(26, 29);
-            this.directorylink.LinkColor = System.Drawing.SystemColors.HotTrack;
-            this.directorylink.Location = new System.Drawing.Point(14, 127);
-            this.directorylink.Name = "directorylink";
-            this.directorylink.Size = new System.Drawing.Size(329, 54);
-            this.directorylink.TabIndex = 9;
-            this.directorylink.TabStop = true;
-            this.directorylink.Text = "The directory may use the ZDoom PK3 directory structure, or you can choose to use" +
+			this.tabs.Controls.Add(this.wadfiletab);
+			this.tabs.Controls.Add(this.directorytab);
+			this.tabs.Controls.Add(this.pk3filetab);
+			this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+			this.tabs.Location = new System.Drawing.Point(9, 11);
+			this.tabs.Name = "tabs";
+			this.tabs.Padding = new System.Drawing.Point(16, 3);
+			this.tabs.SelectedIndex = 0;
+			this.tabs.Size = new System.Drawing.Size(369, 211);
+			this.tabs.TabIndex = 0;
+			// 
+			// wadfiletab
+			// 
+			this.wadfiletab.Controls.Add(this.label6);
+			this.wadfiletab.Controls.Add(this.strictpatches);
+			this.wadfiletab.Controls.Add(this.browsewad);
+			this.wadfiletab.Controls.Add(this.wadlocation);
+			this.wadfiletab.Controls.Add(label1);
+			this.wadfiletab.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.wadfiletab.Location = new System.Drawing.Point(4, 22);
+			this.wadfiletab.Name = "wadfiletab";
+			this.wadfiletab.Padding = new System.Windows.Forms.Padding(3);
+			this.wadfiletab.Size = new System.Drawing.Size(361, 185);
+			this.wadfiletab.TabIndex = 0;
+			this.wadfiletab.Text = "From WAD File";
+			this.wadfiletab.UseVisualStyleBackColor = true;
+			// 
+			// label6
+			// 
+			this.label6.Location = new System.Drawing.Point(14, 102);
+			this.label6.Name = "label6";
+			this.label6.Size = new System.Drawing.Size(329, 58);
+			this.label6.TabIndex = 0;
+			this.label6.Text = resources.GetString("label6.Text");
+			// 
+			// strictpatches
+			// 
+			this.strictpatches.AutoSize = true;
+			this.strictpatches.Location = new System.Drawing.Point(17, 72);
+			this.strictpatches.Name = "strictpatches";
+			this.strictpatches.Size = new System.Drawing.Size(299, 17);
+			this.strictpatches.TabIndex = 3;
+			this.strictpatches.Text = "Strictly load patches between P_START and P_END only";
+			this.strictpatches.UseVisualStyleBackColor = true;
+			// 
+			// browsewad
+			// 
+			this.browsewad.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
+			this.browsewad.Location = new System.Drawing.Point(315, 35);
+			this.browsewad.Name = "browsewad";
+			this.browsewad.Padding = new System.Windows.Forms.Padding(0, 0, 1, 3);
+			this.browsewad.Size = new System.Drawing.Size(28, 24);
+			this.browsewad.TabIndex = 2;
+			this.browsewad.Text = " ";
+			this.browsewad.UseVisualStyleBackColor = true;
+			this.browsewad.Click += new System.EventHandler(this.browsewad_Click);
+			// 
+			// wadlocation
+			// 
+			this.wadlocation.Location = new System.Drawing.Point(17, 37);
+			this.wadlocation.Name = "wadlocation";
+			this.wadlocation.ReadOnly = true;
+			this.wadlocation.Size = new System.Drawing.Size(292, 20);
+			this.wadlocation.TabIndex = 1;
+			this.wadlocation.TabStop = false;
+			this.wadlocation.Enter += new System.EventHandler(this.wadlocation_Enter);
+			// 
+			// directorytab
+			// 
+			this.directorytab.Controls.Add(this.directorylink);
+			this.directorytab.Controls.Add(this.dir_flats);
+			this.directorytab.Controls.Add(this.dir_textures);
+			this.directorytab.Controls.Add(this.browsedir);
+			this.directorytab.Controls.Add(this.dirlocation);
+			this.directorytab.Controls.Add(label2);
+			this.directorytab.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.directorytab.Location = new System.Drawing.Point(4, 22);
+			this.directorytab.Name = "directorytab";
+			this.directorytab.Padding = new System.Windows.Forms.Padding(3);
+			this.directorytab.Size = new System.Drawing.Size(361, 185);
+			this.directorytab.TabIndex = 1;
+			this.directorytab.Text = "From Directory";
+			this.directorytab.UseVisualStyleBackColor = true;
+			// 
+			// directorylink
+			// 
+			this.directorylink.LinkArea = new System.Windows.Forms.LinkArea(26, 29);
+			this.directorylink.LinkColor = System.Drawing.SystemColors.HotTrack;
+			this.directorylink.Location = new System.Drawing.Point(14, 127);
+			this.directorylink.Name = "directorylink";
+			this.directorylink.Size = new System.Drawing.Size(329, 54);
+			this.directorylink.TabIndex = 1;
+			this.directorylink.TabStop = true;
+			this.directorylink.Text = "The directory may use the ZDoom PK3 directory structure, or you can choose to use" +
     " the options above to load texture or flat images from the directory root.";
-            this.directorylink.UseCompatibleTextRendering = true;
-            this.directorylink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.link_Click);
-            // 
-            // dir_flats
-            // 
-            this.dir_flats.AutoSize = true;
-            this.dir_flats.Location = new System.Drawing.Point(17, 98);
-            this.dir_flats.Name = "dir_flats";
-            this.dir_flats.Size = new System.Drawing.Size(197, 17);
-            this.dir_flats.TabIndex = 3;
-            this.dir_flats.Text = "Load images in directory root as flats";
-            this.dir_flats.UseVisualStyleBackColor = true;
-            // 
-            // dir_textures
-            // 
-            this.dir_textures.AutoSize = true;
-            this.dir_textures.Location = new System.Drawing.Point(17, 72);
-            this.dir_textures.Name = "dir_textures";
-            this.dir_textures.Size = new System.Drawing.Size(215, 17);
-            this.dir_textures.TabIndex = 2;
-            this.dir_textures.Text = "Load images in directory root as textures";
-            this.dir_textures.UseVisualStyleBackColor = true;
-            // 
-            // browsedir
-            // 
-            this.browsedir.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
-            this.browsedir.Location = new System.Drawing.Point(315, 35);
-            this.browsedir.Name = "browsedir";
-            this.browsedir.Padding = new System.Windows.Forms.Padding(0, 0, 1, 3);
-            this.browsedir.Size = new System.Drawing.Size(28, 24);
-            this.browsedir.TabIndex = 1;
-            this.browsedir.UseVisualStyleBackColor = true;
-            this.browsedir.Click += new System.EventHandler(this.browsedir_Click);
-            // 
-            // dirlocation
-            // 
-            this.dirlocation.BackColor = System.Drawing.SystemColors.Control;
-            this.dirlocation.Location = new System.Drawing.Point(17, 37);
-            this.dirlocation.Name = "dirlocation";
-            this.dirlocation.ReadOnly = true;
-            this.dirlocation.Size = new System.Drawing.Size(292, 20);
-            this.dirlocation.TabIndex = 0;
-            // 
-            // pk3filetab
-            // 
-            this.pk3filetab.Controls.Add(this.pk3link);
-            this.pk3filetab.Controls.Add(this.browsepk3);
-            this.pk3filetab.Controls.Add(this.pk3location);
-            this.pk3filetab.Controls.Add(label3);
-            this.pk3filetab.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.pk3filetab.Location = new System.Drawing.Point(4, 22);
-            this.pk3filetab.Name = "pk3filetab";
-            this.pk3filetab.Size = new System.Drawing.Size(361, 185);
-            this.pk3filetab.TabIndex = 2;
-            this.pk3filetab.Text = "From PK3/PK7";
-            this.pk3filetab.UseVisualStyleBackColor = true;
-            // 
-            // pk3link
-            // 
-            this.pk3link.LinkArea = new System.Windows.Forms.LinkArea(40, 33);
-            this.pk3link.LinkColor = System.Drawing.SystemColors.HotTrack;
-            this.pk3link.Location = new System.Drawing.Point(15, 72);
-            this.pk3link.Name = "pk3link";
-            this.pk3link.Size = new System.Drawing.Size(328, 47);
-            this.pk3link.TabIndex = 7;
-            this.pk3link.TabStop = true;
-            this.pk3link.Text = "The archive file is expected to use the ZDoom archive directory structure.";
-            this.pk3link.UseCompatibleTextRendering = true;
-            this.pk3link.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.link_Click);
-            // 
-            // browsepk3
-            // 
-            this.browsepk3.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
-            this.browsepk3.Location = new System.Drawing.Point(315, 35);
-            this.browsepk3.Name = "browsepk3";
-            this.browsepk3.Padding = new System.Windows.Forms.Padding(0, 0, 1, 3);
-            this.browsepk3.Size = new System.Drawing.Size(28, 24);
-            this.browsepk3.TabIndex = 1;
-            this.browsepk3.UseVisualStyleBackColor = true;
-            this.browsepk3.Click += new System.EventHandler(this.browsepk3_Click);
-            // 
-            // pk3location
-            // 
-            this.pk3location.Location = new System.Drawing.Point(17, 37);
-            this.pk3location.Name = "pk3location";
-            this.pk3location.ReadOnly = true;
-            this.pk3location.Size = new System.Drawing.Size(292, 20);
-            this.pk3location.TabIndex = 0;
-            // 
-            // cancel
-            // 
-            this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-            this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-            this.cancel.Location = new System.Drawing.Point(262, 260);
-            this.cancel.Name = "cancel";
-            this.cancel.Size = new System.Drawing.Size(112, 25);
-            this.cancel.TabIndex = 2;
-            this.cancel.Text = "Cancel";
-            this.cancel.UseVisualStyleBackColor = true;
-            this.cancel.Click += new System.EventHandler(this.cancel_Click);
-            // 
-            // apply
-            // 
-            this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-            this.apply.Location = new System.Drawing.Point(144, 260);
-            this.apply.Name = "apply";
-            this.apply.Size = new System.Drawing.Size(112, 25);
-            this.apply.TabIndex = 1;
-            this.apply.Text = "OK";
-            this.apply.UseVisualStyleBackColor = true;
-            this.apply.Click += new System.EventHandler(this.apply_Click);
-            // 
-            // wadfiledialog
-            // 
-            this.wadfiledialog.Filter = "Doom WAD Files (*.wad;*.iwad)|*.wad;*.iwad";
-            this.wadfiledialog.Title = "Browse WAD File";
-            // 
-            // pk3filedialog
-            // 
-            this.pk3filedialog.Filter = "Doom PK3/PK7 Files (*.pk3;*.pk7;*.ipk3;*.ipk7)|*.pk3;*.pk7;*.ipk3;*.ipk7";
-            this.pk3filedialog.Title = "Browse PK3 or PK7 File";
-            // 
-            // notfortesting
-            // 
-            this.notfortesting.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-            this.notfortesting.AutoSize = true;
-            this.notfortesting.Location = new System.Drawing.Point(12, 232);
-            this.notfortesting.Name = "notfortesting";
-            this.notfortesting.Size = new System.Drawing.Size(239, 17);
-            this.notfortesting.TabIndex = 3;
-            this.notfortesting.Text = "Exclude this resource from testing parameters";
-            this.notfortesting.UseVisualStyleBackColor = true;
-            // 
-            // ResourceOptionsForm
-            // 
-            this.AcceptButton = this.apply;
-            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
-            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-            this.CancelButton = this.cancel;
-            this.ClientSize = new System.Drawing.Size(386, 293);
-            this.Controls.Add(this.notfortesting);
-            this.Controls.Add(this.cancel);
-            this.Controls.Add(this.apply);
-            this.Controls.Add(this.tabs);
-            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
-            this.MaximizeBox = false;
-            this.MinimizeBox = false;
-            this.Name = "ResourceOptionsForm";
-            this.Opacity = 0D;
-            this.ShowIcon = false;
-            this.ShowInTaskbar = false;
-            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
-            this.Text = "Resource Options";
-            this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.ResourceOptionsForm_HelpRequested);
-            this.tabs.ResumeLayout(false);
-            this.wadfiletab.ResumeLayout(false);
-            this.wadfiletab.PerformLayout();
-            this.directorytab.ResumeLayout(false);
-            this.directorytab.PerformLayout();
-            this.pk3filetab.ResumeLayout(false);
-            this.pk3filetab.PerformLayout();
-            this.ResumeLayout(false);
-            this.PerformLayout();
+			this.directorylink.UseCompatibleTextRendering = true;
+			this.directorylink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.link_Click);
+			// 
+			// dir_flats
+			// 
+			this.dir_flats.AutoSize = true;
+			this.dir_flats.Location = new System.Drawing.Point(17, 98);
+			this.dir_flats.Name = "dir_flats";
+			this.dir_flats.Size = new System.Drawing.Size(197, 17);
+			this.dir_flats.TabIndex = 4;
+			this.dir_flats.Text = "Load images in directory root as flats";
+			this.dir_flats.UseVisualStyleBackColor = true;
+			// 
+			// dir_textures
+			// 
+			this.dir_textures.AutoSize = true;
+			this.dir_textures.Location = new System.Drawing.Point(17, 72);
+			this.dir_textures.Name = "dir_textures";
+			this.dir_textures.Size = new System.Drawing.Size(215, 17);
+			this.dir_textures.TabIndex = 3;
+			this.dir_textures.Text = "Load images in directory root as textures";
+			this.dir_textures.UseVisualStyleBackColor = true;
+			// 
+			// browsedir
+			// 
+			this.browsedir.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
+			this.browsedir.Location = new System.Drawing.Point(315, 35);
+			this.browsedir.Name = "browsedir";
+			this.browsedir.Padding = new System.Windows.Forms.Padding(0, 0, 1, 3);
+			this.browsedir.Size = new System.Drawing.Size(28, 24);
+			this.browsedir.TabIndex = 2;
+			this.browsedir.UseVisualStyleBackColor = true;
+			this.browsedir.Click += new System.EventHandler(this.browsedir_Click);
+			// 
+			// dirlocation
+			// 
+			this.dirlocation.BackColor = System.Drawing.SystemColors.Control;
+			this.dirlocation.Location = new System.Drawing.Point(17, 37);
+			this.dirlocation.Name = "dirlocation";
+			this.dirlocation.ReadOnly = true;
+			this.dirlocation.Size = new System.Drawing.Size(292, 20);
+			this.dirlocation.TabIndex = 0;
+			this.dirlocation.TabStop = false;
+			this.dirlocation.Enter += new System.EventHandler(this.dirlocation_Enter);
+			// 
+			// pk3filetab
+			// 
+			this.pk3filetab.Controls.Add(this.pk3link);
+			this.pk3filetab.Controls.Add(this.browsepk3);
+			this.pk3filetab.Controls.Add(this.pk3location);
+			this.pk3filetab.Controls.Add(label3);
+			this.pk3filetab.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.pk3filetab.Location = new System.Drawing.Point(4, 22);
+			this.pk3filetab.Name = "pk3filetab";
+			this.pk3filetab.Size = new System.Drawing.Size(361, 185);
+			this.pk3filetab.TabIndex = 2;
+			this.pk3filetab.Text = "From PK3/PK7";
+			this.pk3filetab.UseVisualStyleBackColor = true;
+			// 
+			// pk3link
+			// 
+			this.pk3link.LinkArea = new System.Windows.Forms.LinkArea(40, 33);
+			this.pk3link.LinkColor = System.Drawing.SystemColors.HotTrack;
+			this.pk3link.Location = new System.Drawing.Point(15, 72);
+			this.pk3link.Name = "pk3link";
+			this.pk3link.Size = new System.Drawing.Size(328, 47);
+			this.pk3link.TabIndex = 7;
+			this.pk3link.TabStop = true;
+			this.pk3link.Text = "The archive file is expected to use the ZDoom archive directory structure.";
+			this.pk3link.UseCompatibleTextRendering = true;
+			this.pk3link.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.link_Click);
+			// 
+			// browsepk3
+			// 
+			this.browsepk3.Image = global::CodeImp.DoomBuilder.Properties.Resources.Folder;
+			this.browsepk3.Location = new System.Drawing.Point(315, 35);
+			this.browsepk3.Name = "browsepk3";
+			this.browsepk3.Padding = new System.Windows.Forms.Padding(0, 0, 1, 3);
+			this.browsepk3.Size = new System.Drawing.Size(28, 24);
+			this.browsepk3.TabIndex = 1;
+			this.browsepk3.UseVisualStyleBackColor = true;
+			this.browsepk3.Click += new System.EventHandler(this.browsepk3_Click);
+			// 
+			// pk3location
+			// 
+			this.pk3location.Location = new System.Drawing.Point(17, 37);
+			this.pk3location.Name = "pk3location";
+			this.pk3location.ReadOnly = true;
+			this.pk3location.Size = new System.Drawing.Size(292, 20);
+			this.pk3location.TabIndex = 0;
+			this.pk3location.TabStop = false;
+			this.pk3location.Enter += new System.EventHandler(this.pk3location_Enter);
+			// 
+			// cancel
+			// 
+			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+			this.cancel.Location = new System.Drawing.Point(262, 304);
+			this.cancel.Name = "cancel";
+			this.cancel.Size = new System.Drawing.Size(112, 25);
+			this.cancel.TabIndex = 2;
+			this.cancel.Text = "Cancel";
+			this.cancel.UseVisualStyleBackColor = true;
+			this.cancel.Click += new System.EventHandler(this.cancel_Click);
+			// 
+			// apply
+			// 
+			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+			this.apply.Location = new System.Drawing.Point(144, 304);
+			this.apply.Name = "apply";
+			this.apply.Size = new System.Drawing.Size(112, 25);
+			this.apply.TabIndex = 1;
+			this.apply.Text = "OK";
+			this.apply.UseVisualStyleBackColor = true;
+			this.apply.Click += new System.EventHandler(this.apply_Click);
+			// 
+			// wadfiledialog
+			// 
+			this.wadfiledialog.Filter = "Doom WAD Files (*.wad;*.iwad)|*.wad;*.iwad";
+			this.wadfiledialog.Title = "Browse WAD File";
+			// 
+			// pk3filedialog
+			// 
+			this.pk3filedialog.Filter = "Doom PK3/PK7 Files (*.pk3;*.pk7;*.ipk3;*.ipk7)|*.pk3;*.pk7;*.ipk3;*.ipk7";
+			this.pk3filedialog.Title = "Browse PK3 or PK7 File";
+			// 
+			// notfortesting
+			// 
+			this.notfortesting.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+			this.notfortesting.AutoSize = true;
+			this.notfortesting.Location = new System.Drawing.Point(12, 233);
+			this.notfortesting.Name = "notfortesting";
+			this.notfortesting.Size = new System.Drawing.Size(239, 17);
+			this.notfortesting.TabIndex = 3;
+			this.notfortesting.Text = "Exclude this resource from testing parameters";
+			this.notfortesting.UseVisualStyleBackColor = true;
+			// 
+			// checkingloader
+			// 
+			this.checkingloader.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+			this.checkingloader.BackColor = System.Drawing.SystemColors.Info;
+			this.checkingloader.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+			this.checkingloader.Controls.Add(this.pictureBox1);
+			this.checkingloader.Controls.Add(this.label4);
+			this.checkingloader.Cursor = System.Windows.Forms.Cursors.AppStarting;
+			this.checkingloader.ForeColor = System.Drawing.SystemColors.InfoText;
+			this.checkingloader.Location = new System.Drawing.Point(12, 263);
+			this.checkingloader.Name = "checkingloader";
+			this.checkingloader.Size = new System.Drawing.Size(362, 32);
+			this.checkingloader.TabIndex = 4;
+			this.checkingloader.Visible = false;
+			// 
+			// pictureBox1
+			// 
+			this.pictureBox1.Image = global::CodeImp.DoomBuilder.Properties.Resources.Loader;
+			this.pictureBox1.InitialImage = global::CodeImp.DoomBuilder.Properties.Resources.Loader;
+			this.pictureBox1.Location = new System.Drawing.Point(8, 7);
+			this.pictureBox1.Name = "pictureBox1";
+			this.pictureBox1.Size = new System.Drawing.Size(16, 16);
+			this.pictureBox1.TabIndex = 2;
+			this.pictureBox1.TabStop = false;
+			// 
+			// label4
+			// 
+			this.label4.AutoSize = true;
+			this.label4.Location = new System.Drawing.Point(29, 8);
+			this.label4.Name = "label4";
+			this.label4.Size = new System.Drawing.Size(176, 13);
+			this.label4.TabIndex = 0;
+			this.label4.Text = "Please wait, checking the archive...";
+			// 
+			// ResourceOptionsForm
+			// 
+			this.AcceptButton = this.apply;
+			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+			this.CancelButton = this.cancel;
+			this.ClientSize = new System.Drawing.Size(386, 337);
+			this.Controls.Add(this.checkingloader);
+			this.Controls.Add(this.notfortesting);
+			this.Controls.Add(this.cancel);
+			this.Controls.Add(this.apply);
+			this.Controls.Add(this.tabs);
+			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+			this.MaximizeBox = false;
+			this.MinimizeBox = false;
+			this.Name = "ResourceOptionsForm";
+			this.Opacity = 0D;
+			this.ShowIcon = false;
+			this.ShowInTaskbar = false;
+			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+			this.Text = "Resource Options";
+			this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.ResourceOptionsForm_HelpRequested);
+			this.tabs.ResumeLayout(false);
+			this.wadfiletab.ResumeLayout(false);
+			this.wadfiletab.PerformLayout();
+			this.directorytab.ResumeLayout(false);
+			this.directorytab.PerformLayout();
+			this.pk3filetab.ResumeLayout(false);
+			this.pk3filetab.PerformLayout();
+			this.checkingloader.ResumeLayout(false);
+			this.checkingloader.PerformLayout();
+			((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
+			this.ResumeLayout(false);
+			this.PerformLayout();
 
 		}
 
@@ -376,5 +426,8 @@ namespace CodeImp.DoomBuilder.Windows
 		private System.Windows.Forms.CheckBox strictpatches;
 		private System.Windows.Forms.Label label6;
 		private System.Windows.Forms.CheckBox notfortesting;
-	}
+        private System.Windows.Forms.Panel checkingloader;
+        private System.Windows.Forms.Label label4;
+        private System.Windows.Forms.PictureBox pictureBox1;
+    }
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/ResourceOptionsForm.cs b/Source/Core/Windows/ResourceOptionsForm.cs
index 033676b7b1b76ca71d47c9fdb896f693da1790ad..89bece3dcb14c7c1fe8e262c3d6d40d549cf0388 100755
--- a/Source/Core/Windows/ResourceOptionsForm.cs
+++ b/Source/Core/Windows/ResourceOptionsForm.cs
@@ -20,6 +20,12 @@ using System;
 using System.Windows.Forms;
 using System.IO;
 using CodeImp.DoomBuilder.Data;
+using CodeImp.DoomBuilder.Config;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using CodeImp.DoomBuilder.ZDoom;
+using System.Threading;
 
 #endregion
 
@@ -31,19 +37,68 @@ namespace CodeImp.DoomBuilder.Windows
 		private DataLocation res;
         private string startPath;
         private Controls.FolderSelectDialog dirdialog;
+		private List<string> requiredarchives;
 
         // Properties
         public DataLocation ResourceLocation { get { return res; } }
-		
+		public GameConfiguration GameConfiguration { get; set; }
+
+		//
+		private bool _ischeckingrequiredarchives = false;
+		private bool IsCheckingRequiredArchives
+        {
+			get
+            {
+				return _ischeckingrequiredarchives;
+            }
+			set
+            {
+				if (value)
+                {
+					apply.Enabled = false;
+					cancel.Enabled = false;
+					notfortesting.Enabled = false;
+					dir_textures.Enabled = false;
+					dir_flats.Enabled = false;
+					ControlBox = false;
+					checkingloader.Visible = true;
+                }
+				else
+                {
+					apply.Enabled = true;
+					cancel.Enabled = true;
+					notfortesting.Enabled = true;
+					dir_textures.Enabled = true;
+					dir_flats.Enabled = true;
+					ControlBox = true;
+					checkingloader.Visible = false;
+                }
+				_ischeckingrequiredarchives = value;
+            }
+        }
+
 		// Constructor
 		public ResourceOptionsForm(DataLocation settings, string caption, string startPath) //mxd. added startPath
 		{
 			// Initialize
 			InitializeComponent();
 
+#if NO_WIN32
+			// No easy way to have case-insesitivity for non-Windows platforms
+			wadfiledialog.Filter = "Doom WAD Files (*.wad)|*.wad;*.Wad;*.wAd;*.WAd;*.waD;*.WaD;*.wAD;*.WAD";
+			pk3filedialog.Filter = "Doom PK3/PK7 Files (*.pk3;*.pk7;*.ipk3;*.ipk7)|" +
+				"*.pk3;*.Pk3;*.pK3;*.PK3;" +
+				"*.pk7;*.Pk7;*.pK7;*.PK7;" +
+				"*.ipk3;*.iPk3;*.ipK3;*.iPK3;*.Ipk3;*.IPk3;*.IpK3;*.IPK3;" +
+				"*.ipk7;*.iPk7;*.ipK7;*.iPK7;*.Ipk7;*.IPk7;*.IpK7;*.IPK7";
+#endif
+
 			// Set caption
 			this.Text = caption;
-			
+
+			//
+			this.requiredarchives = new List<string>();
+
 			// Apply settings from ResourceLocation
 			this.res = settings;
 			switch(res.type)
@@ -75,22 +130,230 @@ namespace CodeImp.DoomBuilder.Windows
 
             this.startPath = startPath;
 		}
-		
-		// OK clicked
-		private void apply_Click(object sender, EventArgs e)
-		{
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            base.OnClosing(e);
+			if (IsCheckingRequiredArchives) e.Cancel = true;
+        }
+
+		public static List<string> CheckRequiredArchives(GameConfiguration config, DataLocation loc, CancellationToken token)
+        {
+#if DEBUG
+			General.WriteLogLine(string.Format("CheckRequiredArchives (Config = {0})", config?.Name));
+#endif
+
+			if (config == null)
+				return new List<string>();
+
+			try
+			{
+				DataReader dr = null;
+				List<string> requiredarchives = new List<string>();
+				HashSet<string> classes = null;
+
+				switch (loc.type)
+				{
+					case DataLocation.RESOURCE_WAD:
+						dr = new WADReader(loc, config, true) { Silent = true };
+						break;
+
+					case DataLocation.RESOURCE_DIRECTORY:
+						dr = new DirectoryReader(loc, config, true) { Silent = true };
+						break;
+
+					case DataLocation.RESOURCE_PK3:
+						dr = new PK3Reader(loc, config, true) { Silent = true };
+						break;
+				}
+
+				token.ThrowIfCancellationRequested();
+
+				foreach (var arc in config.RequiredArchives)
+				{
+					bool found = true;
+
+					token.ThrowIfCancellationRequested();
+
+					foreach (RequiredArchiveEntry e in arc.Entries)
+					{
+						token.ThrowIfCancellationRequested();
+
+						if (e.Class != null)
+						{
+							if (classes == null)
+                            {
+								classes = new HashSet<string>();
+
+								// load ZScript
+								var zscript = new ZScriptParser {
+									NoWarnings = true,
+									OnInclude = (parser, location) => {
+										IEnumerable<TextResourceData> includeStreams = dr.GetZScriptData(location);
+										foreach (TextResourceData data in includeStreams)
+										{
+											// Parse this data
+											parser.Parse(data, false);
+
+											//mxd. DECORATE lumps are interdepandable. Can't carry on...
+											if (parser.HasError)
+												break;
+										}
+									}
+								};
+
+								foreach (TextResourceData data in dr.GetZScriptData("ZSCRIPT"))
+								{
+									// Parse the data
+									data.Stream.Seek(0, SeekOrigin.Begin);
+									zscript.Parse(data, true);
+
+									if (zscript.HasError)
+										break;
+
+									foreach (string cls in zscript.LastClasses)
+										classes.Add(cls.ToLowerInvariant());
+								}
+
+#if DEBUG
+								if (zscript.HasError)
+									General.WriteLogLine(string.Format("CRA({0}): ZScript error: {1}", loc.location, zscript.ErrorDescription));
+#endif
+
+								// load DECORATE
+								var decorate = new DecorateParser(zscript.AllActorsByClass) {
+									NoWarnings = true,
+									OnInclude = (parser, location) => {
+										IEnumerable<TextResourceData> includeStreams = dr.GetDecorateData(location);
+										foreach (TextResourceData data in includeStreams)
+										{
+											// Parse this data
+											parser.Parse(data, false);
+
+											//mxd. DECORATE lumps are interdepandable. Can't carry on...
+											if (parser.HasError)
+												break;
+										}
+									}
+								};
+
+								foreach (TextResourceData data in dr.GetDecorateData("DECORATE"))
+								{
+									// Parse the data
+									data.Stream.Seek(0, SeekOrigin.Begin);
+									decorate.Parse(data, true);
+
+									if (decorate.HasError)
+										break;
+
+									foreach (string cls in decorate.LastClasses)
+										classes.Add(cls.ToLowerInvariant());
+								}
+
+#if DEBUG
+								if (decorate.HasError)
+									General.WriteLogLine(string.Format("CRA({0}): DECORATE error: {1}", loc.location, decorate.ErrorDescription));
+#endif
+							}
+
+							if (!classes.Contains(e.Class.ToLowerInvariant()))
+                            {
+#if DEBUG
+								General.WriteLogLine(string.Format("CRA({2}): Does not contain class: {1} <- {0}", string.Join(",", classes), e.Class, loc.location));
+#endif
+								found = false;
+								break;
+                            }
+						}
+
+						if (e.Lump != null && !dr.FileExists(e.Lump))
+						{
+#if DEBUG
+							General.WriteLogLine(string.Format("CRA({1}): Does not contain lump: {0}", e.Lump, loc.location));
+#endif
+							found = false;
+							break;
+						}
+					}
+
+					if (found)
+                    {
+						requiredarchives.Add(arc.ID);
+                    }
+				}
+
+				dr.Dispose();
+
+				return requiredarchives;
+			}
+			catch (OperationCanceledException)
+            {
+				throw;
+            }
+			catch (Exception e)
+			{
+				General.WriteLogLine(e.ToString());
+				return new List<string>();
+			}
+		}
+
+		private List<string> RunCheckRequiredArchives()
+        {
+			// thanks ms for making this a struct
+			CancellationTokenSource dummySource = new CancellationTokenSource();
+			List<string> output = CheckRequiredArchives(GameConfiguration, ToDataLocation(), dummySource.Token);
+			dummySource.Dispose();
+			return output;
+        }
+
+		private async void StartRequiredArchivesCheck()
+        {
+			IsCheckingRequiredArchives = true;
+
+			try
+			{
+				requiredarchives = await Task.Run(() => RunCheckRequiredArchives());
+			}
+			catch
+            {
+				requiredarchives = new List<string>();
+            }
+
+			ApplyDefaultRequiredArchivesSetting();
+
+			IsCheckingRequiredArchives = false;
+        }
+
+		private void ApplyDefaultRequiredArchivesSetting()
+        {
+			dir_textures.Checked = false;
+			dir_flats.Checked = false;
+			notfortesting.Checked = false;
+			// if any of the detected required archives implies "not for testing" — disable it by default
+			foreach (var arc in GameConfiguration.RequiredArchives)
+            {
+				if (requiredarchives.Contains(arc.ID) && arc.ExcludeFromTesting)
+					notfortesting.Checked = true;
+            }
+        }
+
+		private DataLocation ToDataLocation()
+        {
+			DataLocation res = new DataLocation();
+			res.location = "";
+			res.requiredarchives = requiredarchives;
+
 			// Apply settings to ResourceLocation
-			switch(tabs.SelectedIndex)
+			switch (tabs.SelectedIndex)
 			{
 				// Setup WAD File
 				case DataLocation.RESOURCE_WAD:
 
 					// Check if file is specified
-					if((wadlocation.Text.Length == 0) ||
+					if ((wadlocation.Text.Length == 0) ||
 					   (!File.Exists(wadlocation.Text)))
 					{
-						// No valid wad file specified
-						MessageBox.Show(this, "Please select a valid WAD File resource.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+						break;
 					}
 					else
 					{
@@ -100,10 +363,6 @@ namespace CodeImp.DoomBuilder.Windows
 						res.option1 = strictpatches.Checked;
 						res.option2 = false;
 						res.notfortesting = notfortesting.Checked;
-
-						// Done
-						this.DialogResult = DialogResult.OK;
-						this.Close();
 					}
 					break;
 
@@ -111,11 +370,10 @@ namespace CodeImp.DoomBuilder.Windows
 				case DataLocation.RESOURCE_DIRECTORY:
 
 					// Check if directory is specified
-					if((dirlocation.Text.Length == 0) ||
+					if ((dirlocation.Text.Length == 0) ||
 					   (!Directory.Exists(dirlocation.Text)))
 					{
-						// No valid directory specified
-						MessageBox.Show(this, "Please select a valid directory resource.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+						break;
 					}
 					else
 					{
@@ -125,22 +383,17 @@ namespace CodeImp.DoomBuilder.Windows
 						res.option1 = dir_textures.Checked;
 						res.option2 = dir_flats.Checked;
 						res.notfortesting = notfortesting.Checked;
-
-						// Done
-						this.DialogResult = DialogResult.OK;
-						this.Close();
 					}
 					break;
-					
+
 				// Setup PK3 File
 				case DataLocation.RESOURCE_PK3:
 
 					// Check if file is specified
-					if((pk3location.Text.Length == 0) ||
+					if ((pk3location.Text.Length == 0) ||
 					   (!File.Exists(pk3location.Text)))
 					{
-						// No valid pk3 file specified
-						MessageBox.Show(this, "Please select a valid PK3 or PK7 File resource.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+						break;
 					}
 					else
 					{
@@ -150,13 +403,39 @@ namespace CodeImp.DoomBuilder.Windows
 						res.option1 = false;
 						res.option2 = false;
 						res.notfortesting = notfortesting.Checked;
-
-						// Done
-						this.DialogResult = DialogResult.OK;
-						this.Close();
 					}
 					break;
 			}
+
+			return res;
+		}
+
+        // OK clicked
+        private void apply_Click(object sender, EventArgs e)
+		{
+			res = ToDataLocation();
+			if (res.location == "")
+			{
+				switch (tabs.SelectedIndex)
+				{
+					case DataLocation.RESOURCE_WAD:
+						MessageBox.Show(this, "Please select a valid WAD File resource.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+						break;
+
+					case DataLocation.RESOURCE_PK3:
+						MessageBox.Show(this, "Please select a valid PK3 or PK7 File resource.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+						break;
+
+					case DataLocation.RESOURCE_DIRECTORY:
+						MessageBox.Show(this, "Please select a valid directory resource.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+						break;
+				}
+			}
+			else
+            {
+				this.DialogResult = DialogResult.OK;
+				this.Close();
+			}
 		}
 		
 		// Cancel clicked
@@ -175,6 +454,7 @@ namespace CodeImp.DoomBuilder.Windows
 			{
 				// Use this file
 				wadlocation.Text = wadfiledialog.FileName;
+				StartRequiredArchivesCheck();
 			}
 		}
 
@@ -203,6 +483,7 @@ namespace CodeImp.DoomBuilder.Windows
 			{
                 // Use this directory
                 dirlocation.Text = dirdialog.FileName;
+				StartRequiredArchivesCheck();
                 dirdialog = null;
 			}
 		}
@@ -215,6 +496,7 @@ namespace CodeImp.DoomBuilder.Windows
 			{
 				// Use this file
 				pk3location.Text = pk3filedialog.FileName;
+				StartRequiredArchivesCheck();
 			}
 		}
 
@@ -230,5 +512,23 @@ namespace CodeImp.DoomBuilder.Windows
 			General.ShowHelp("w_resourceoptions.html");
 			hlpevent.Handled = true;
 		}
+
+		private void wadlocation_Enter(object sender, EventArgs e)
+		{
+			// Stop textbox control from getting focus
+			browsewad.Focus();
+		}
+
+		private void dirlocation_Enter(object sender, EventArgs e)
+		{
+			// Stop textbox control from getting focus
+			browsedir.Focus();
+		}
+
+		private void pk3location_Enter(object sender, EventArgs e)
+		{
+			// Stop textbox control from getting focus
+			browsepk3.Focus();
+		}
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/ResourceOptionsForm.resx b/Source/Core/Windows/ResourceOptionsForm.resx
index f3fb7e00fcfd1217af2579ce62688e820d716bd8..c26f50626e0f7fffdb3891c086a360e1760c44a3 100755
--- a/Source/Core/Windows/ResourceOptionsForm.resx
+++ b/Source/Core/Windows/ResourceOptionsForm.resx
@@ -141,21 +141,6 @@
   <metadata name="wadfiletab.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="directorytab.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="pk3filetab.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="wadfiletab.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="strictpatches.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="wadlocation.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
   <data name="label6.Text" xml:space="preserve">
     <value>Use the option above to enforce strictly loading texture patches from between P_START and P_END marker lumps only. This can solve lump name conflicts, but old WAD files do not always adhere to this rule.</value>
   </data>
@@ -177,31 +162,16 @@
   <metadata name="dirlocation.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="dir_flats.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="dir_textures.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="dirlocation.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
   <metadata name="pk3filetab.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
   <metadata name="pk3location.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="pk3location.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
   <metadata name="wadfiledialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
   <metadata name="pk3filedialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>227, 17</value>
   </metadata>
-  <metadata name="notfortesting.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Core/Windows/StatusInfo.cs b/Source/Core/Windows/StatusInfo.cs
index 1b0c95e0d46839fa92f387d9db115efa8c7bccfb..f942200fc4897c16e57328c14270eccc020ada12 100755
--- a/Source/Core/Windows/StatusInfo.cs
+++ b/Source/Core/Windows/StatusInfo.cs
@@ -33,7 +33,7 @@ namespace CodeImp.DoomBuilder.Windows
 		public readonly string selectioninfo; //mxd
 		internal bool displayed;
 		
-		internal StatusInfo(StatusType type, string message)
+		public StatusInfo(StatusType type, string message)
 		{
 			this.type = type;
 
diff --git a/Source/Core/Windows/TextureBrowserForm.cs b/Source/Core/Windows/TextureBrowserForm.cs
index a393f53379b550773c32ae9e601391890714eafd..44f16faf1a746026b5fb63ae8061868cc16110dd 100755
--- a/Source/Core/Windows/TextureBrowserForm.cs
+++ b/Source/Core/Windows/TextureBrowserForm.cs
@@ -116,10 +116,11 @@ namespace CodeImp.DoomBuilder.Windows
 			}
 
 			//mxd. Add "All" texture set
-			count = (browseflats ? General.Map.Data.AllTextureSet.Flats.Count : General.Map.Data.AllTextureSet.Textures.Count);
-			item = tvTextureSets.Nodes.Add(General.Map.Data.AllTextureSet.Name + " [" + count + "]");
-			item.Name = General.Map.Data.AllTextureSet.Name;
-			item.Tag = new TreeNodeData { Set = General.Map.Data.AllTextureSet, FolderName = General.Map.Data.AllTextureSet.Name };
+			AllTextureSet alltextureset = browseflats ? General.Map.Data.FlatTextureSet : General.Map.Data.WallTextureSet;
+			count = (browseflats ? alltextureset.Flats.Count : alltextureset.Textures.Count);
+			item = tvTextureSets.Nodes.Add(alltextureset.Name + " [" + count + "]");
+			item.Name = alltextureset.Name;
+			item.Tag = new TreeNodeData { Set = alltextureset, FolderName = alltextureset.Name };
 			item.ImageIndex = 1;
 			item.SelectedImageIndex = item.ImageIndex;
 
@@ -304,7 +305,7 @@ namespace CodeImp.DoomBuilder.Windows
 					if(i == parts.Length - 2) 
 					{
 						ResourceTextureSet curTs = ((TreeNodeData)curNode.Tag).Set as ResourceTextureSet;
-						if(image.IsFlat)
+						if(image.TextureNamespace == TextureNamespace.FLAT)
 							curTs.AddFlat(image);
 						else
 							curTs.AddTexture(image);
@@ -545,7 +546,7 @@ namespace CodeImp.DoomBuilder.Windows
 			else
 			{
 				// Add all available textures
-				foreach(ImageData img in set.Textures) browser.AddItem(img);
+				foreach (ImageData img in set.Textures)	browser.AddItem(img);
 			}
 
 			browser.MakeTexturesUnique(); // biwa
diff --git a/Source/Core/Windows/ThingEditFormUDMF.Designer.cs b/Source/Core/Windows/ThingEditFormUDMF.Designer.cs
index 404477a4aaf5e37107253a6069f70e8191a01ae0..7f68a2a9ef8cbf6aae495b83e397a8c38cc1d6d8 100755
--- a/Source/Core/Windows/ThingEditFormUDMF.Designer.cs
+++ b/Source/Core/Windows/ThingEditFormUDMF.Designer.cs
@@ -28,1030 +28,1030 @@
 		/// </summary>
 		private void InitializeComponent() 
 		{
-            this.components = new System.ComponentModel.Container();
-            this.groupBox1 = new System.Windows.Forms.GroupBox();
-            this.thingtype = new CodeImp.DoomBuilder.Controls.ThingBrowserControl();
-            this.groupBox2 = new System.Windows.Forms.GroupBox();
-            this.cbrandomroll = new System.Windows.Forms.CheckBox();
-            this.cbrandompitch = new System.Windows.Forms.CheckBox();
-            this.cbrandomangle = new System.Windows.Forms.CheckBox();
-            this.roll = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.labelroll = new System.Windows.Forms.Label();
-            this.pitch = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.labelpitch = new System.Windows.Forms.Label();
-            this.angle = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.labelAngle = new System.Windows.Forms.Label();
-            this.anglecontrol = new CodeImp.DoomBuilder.Controls.AngleControlEx();
-            this.labelGravity = new System.Windows.Forms.Label();
-            this.label7 = new System.Windows.Forms.Label();
-            this.tabs = new System.Windows.Forms.TabControl();
-            this.tabproperties = new System.Windows.Forms.TabPage();
-            this.settingsgroup = new System.Windows.Forms.GroupBox();
-            this.missingflags = new System.Windows.Forms.PictureBox();
-            this.flags = new CodeImp.DoomBuilder.Controls.CheckboxArrayControl();
-            this.grouproll = new System.Windows.Forms.GroupBox();
-            this.rollControl = new CodeImp.DoomBuilder.Controls.AngleControlEx();
-            this.grouppitch = new System.Windows.Forms.GroupBox();
-            this.pitchControl = new CodeImp.DoomBuilder.Controls.AngleControlEx();
-            this.groupangle = new System.Windows.Forms.GroupBox();
-            this.groupBox4 = new System.Windows.Forms.GroupBox();
-            this.cbAbsoluteHeight = new System.Windows.Forms.CheckBox();
-            this.label4 = new System.Windows.Forms.Label();
-            this.label5 = new System.Windows.Forms.Label();
-            this.posX = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.posY = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.posZ = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.zlabel = new System.Windows.Forms.Label();
-            this.tabeffects = new System.Windows.Forms.TabPage();
-            this.groupbehaviour = new System.Windows.Forms.GroupBox();
-            this.floatbobphase = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.label1 = new System.Windows.Forms.Label();
-            this.conversationID = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.labelID = new System.Windows.Forms.Label();
-            this.health = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.label10 = new System.Windows.Forms.Label();
-            this.score = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.label9 = new System.Windows.Forms.Label();
-            this.gravity = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.grouprendering = new System.Windows.Forms.GroupBox();
-            this.resetalpha = new System.Windows.Forms.Button();
-            this.labelScale = new System.Windows.Forms.Label();
-            this.scale = new CodeImp.DoomBuilder.Controls.PairedFloatControl();
-            this.color = new CodeImp.DoomBuilder.Controls.ColorFieldsControl();
-            this.alpha = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-            this.label8 = new System.Windows.Forms.Label();
-            this.renderStyle = new System.Windows.Forms.ComboBox();
-            this.labelrenderstyle = new System.Windows.Forms.Label();
-            this.actiongroup = new System.Windows.Forms.GroupBox();
-            this.argscontrol = new CodeImp.DoomBuilder.Controls.ArgumentsControl();
-            this.actionhelp = new CodeImp.DoomBuilder.Controls.ActionSpecialHelpButton();
-            this.action = new CodeImp.DoomBuilder.Controls.ActionSelectorControl();
-            this.browseaction = new System.Windows.Forms.Button();
-            this.grouptag = new System.Windows.Forms.GroupBox();
-            this.tagSelector = new CodeImp.DoomBuilder.Controls.TagSelector();
-            this.tabcomment = new System.Windows.Forms.TabPage();
-            this.commenteditor = new CodeImp.DoomBuilder.Controls.CommentEditor();
-            this.tabcustom = new System.Windows.Forms.TabPage();
-            this.hidefixedfields = new System.Windows.Forms.CheckBox();
-            this.fieldslist = new CodeImp.DoomBuilder.Controls.FieldsEditorControl();
-            this.cancel = new System.Windows.Forms.Button();
-            this.apply = new System.Windows.Forms.Button();
-            this.hint = new System.Windows.Forms.PictureBox();
-            this.hintlabel = new System.Windows.Forms.Label();
-            this.tooltip = new System.Windows.Forms.ToolTip(this.components);
-            this.groupBox1.SuspendLayout();
-            this.groupBox2.SuspendLayout();
-            this.tabs.SuspendLayout();
-            this.tabproperties.SuspendLayout();
-            this.settingsgroup.SuspendLayout();
-            ((System.ComponentModel.ISupportInitialize)(this.missingflags)).BeginInit();
-            this.grouproll.SuspendLayout();
-            this.grouppitch.SuspendLayout();
-            this.groupangle.SuspendLayout();
-            this.groupBox4.SuspendLayout();
-            this.tabeffects.SuspendLayout();
-            this.groupbehaviour.SuspendLayout();
-            this.grouprendering.SuspendLayout();
-            this.actiongroup.SuspendLayout();
-            this.grouptag.SuspendLayout();
-            this.tabcomment.SuspendLayout();
-            this.tabcustom.SuspendLayout();
-            ((System.ComponentModel.ISupportInitialize)(this.hint)).BeginInit();
-            this.SuspendLayout();
-            // 
-            // groupBox1
-            // 
-            this.groupBox1.Controls.Add(this.thingtype);
-            this.groupBox1.Location = new System.Drawing.Point(6, 6);
-            this.groupBox1.Name = "groupBox1";
-            this.groupBox1.Size = new System.Drawing.Size(230, 390);
-            this.groupBox1.TabIndex = 0;
-            this.groupBox1.TabStop = false;
-            this.groupBox1.Text = " Thing ";
-            // 
-            // thingtype
-            // 
-            this.thingtype.Location = new System.Drawing.Point(9, 13);
-            this.thingtype.Margin = new System.Windows.Forms.Padding(6);
-            this.thingtype.Name = "thingtype";
-            this.thingtype.Size = new System.Drawing.Size(212, 374);
-            this.thingtype.TabIndex = 0;
-            this.thingtype.UseMultiSelection = true;
-            this.thingtype.OnTypeChanged += new CodeImp.DoomBuilder.Controls.ThingBrowserControl.TypeChangedDeletegate(this.thingtype_OnTypeChanged);
-            this.thingtype.OnTypeDoubleClicked += new CodeImp.DoomBuilder.Controls.ThingBrowserControl.TypeDoubleClickDeletegate(this.thingtype_OnTypeDoubleClicked);
-            // 
-            // groupBox2
-            // 
-            this.groupBox2.Controls.Add(this.cbrandomroll);
-            this.groupBox2.Controls.Add(this.cbrandompitch);
-            this.groupBox2.Controls.Add(this.cbrandomangle);
-            this.groupBox2.Controls.Add(this.roll);
-            this.groupBox2.Controls.Add(this.labelroll);
-            this.groupBox2.Controls.Add(this.pitch);
-            this.groupBox2.Controls.Add(this.labelpitch);
-            this.groupBox2.Controls.Add(this.angle);
-            this.groupBox2.Controls.Add(this.labelAngle);
-            this.groupBox2.Location = new System.Drawing.Point(428, 298);
-            this.groupBox2.Name = "groupBox2";
-            this.groupBox2.Size = new System.Drawing.Size(193, 98);
-            this.groupBox2.TabIndex = 6;
-            this.groupBox2.TabStop = false;
-            this.groupBox2.Text = " Rotation ";
-            // 
-            // cbrandomroll
-            // 
-            this.cbrandomroll.AutoSize = true;
-            this.cbrandomroll.Location = new System.Drawing.Point(120, 71);
-            this.cbrandomroll.Name = "cbrandomroll";
-            this.cbrandomroll.Size = new System.Drawing.Size(66, 17);
-            this.cbrandomroll.TabIndex = 5;
-            this.cbrandomroll.Text = "Random";
-            this.cbrandomroll.UseVisualStyleBackColor = true;
-            this.cbrandomroll.CheckedChanged += new System.EventHandler(this.cbrandomroll_CheckedChanged);
-            // 
-            // cbrandompitch
-            // 
-            this.cbrandompitch.AutoSize = true;
-            this.cbrandompitch.Location = new System.Drawing.Point(120, 46);
-            this.cbrandompitch.Name = "cbrandompitch";
-            this.cbrandompitch.Size = new System.Drawing.Size(66, 17);
-            this.cbrandompitch.TabIndex = 3;
-            this.cbrandompitch.Text = "Random";
-            this.cbrandompitch.UseVisualStyleBackColor = true;
-            this.cbrandompitch.CheckedChanged += new System.EventHandler(this.cbrandompitch_CheckedChanged);
-            // 
-            // cbrandomangle
-            // 
-            this.cbrandomangle.AutoSize = true;
-            this.cbrandomangle.Location = new System.Drawing.Point(120, 21);
-            this.cbrandomangle.Name = "cbrandomangle";
-            this.cbrandomangle.Size = new System.Drawing.Size(66, 17);
-            this.cbrandomangle.TabIndex = 1;
-            this.cbrandomangle.Text = "Random";
-            this.cbrandomangle.UseVisualStyleBackColor = true;
-            this.cbrandomangle.CheckedChanged += new System.EventHandler(this.cbrandomangle_CheckedChanged);
-            // 
-            // roll
-            // 
-            this.roll.AllowDecimal = false;
-            this.roll.AllowExpressions = true;
-            this.roll.AllowNegative = true;
-            this.roll.AllowRelative = true;
-            this.roll.ButtonStep = 5;
-            this.roll.ButtonStepBig = 15F;
-            this.roll.ButtonStepFloat = 1F;
-            this.roll.ButtonStepSmall = 1F;
-            this.roll.ButtonStepsUseModifierKeys = true;
-            this.roll.ButtonStepsWrapAround = false;
-            this.roll.Location = new System.Drawing.Point(55, 66);
-            this.roll.Name = "roll";
-            this.roll.Size = new System.Drawing.Size(60, 24);
-            this.roll.StepValues = null;
-            this.roll.TabIndex = 4;
-            this.roll.WhenTextChanged += new System.EventHandler(this.roll_WhenTextChanged);
-            // 
-            // labelroll
-            // 
-            this.labelroll.Location = new System.Drawing.Point(5, 71);
-            this.labelroll.Name = "labelroll";
-            this.labelroll.Size = new System.Drawing.Size(44, 14);
-            this.labelroll.TabIndex = 23;
-            this.labelroll.Text = "Roll:";
-            this.labelroll.TextAlign = System.Drawing.ContentAlignment.TopRight;
-            // 
-            // pitch
-            // 
-            this.pitch.AllowDecimal = false;
-            this.pitch.AllowExpressions = true;
-            this.pitch.AllowNegative = true;
-            this.pitch.AllowRelative = true;
-            this.pitch.ButtonStep = 5;
-            this.pitch.ButtonStepBig = 15F;
-            this.pitch.ButtonStepFloat = 1F;
-            this.pitch.ButtonStepSmall = 1F;
-            this.pitch.ButtonStepsUseModifierKeys = true;
-            this.pitch.ButtonStepsWrapAround = false;
-            this.pitch.Location = new System.Drawing.Point(55, 41);
-            this.pitch.Name = "pitch";
-            this.pitch.Size = new System.Drawing.Size(60, 24);
-            this.pitch.StepValues = null;
-            this.pitch.TabIndex = 2;
-            this.pitch.WhenTextChanged += new System.EventHandler(this.pitch_WhenTextChanged);
-            // 
-            // labelpitch
-            // 
-            this.labelpitch.Location = new System.Drawing.Point(5, 46);
-            this.labelpitch.Name = "labelpitch";
-            this.labelpitch.Size = new System.Drawing.Size(44, 14);
-            this.labelpitch.TabIndex = 21;
-            this.labelpitch.Text = "Pitch:";
-            this.labelpitch.TextAlign = System.Drawing.ContentAlignment.TopRight;
-            // 
-            // angle
-            // 
-            this.angle.AllowDecimal = false;
-            this.angle.AllowExpressions = true;
-            this.angle.AllowNegative = true;
-            this.angle.AllowRelative = true;
-            this.angle.ButtonStep = 5;
-            this.angle.ButtonStepBig = 15F;
-            this.angle.ButtonStepFloat = 1F;
-            this.angle.ButtonStepSmall = 1F;
-            this.angle.ButtonStepsUseModifierKeys = true;
-            this.angle.ButtonStepsWrapAround = false;
-            this.angle.Location = new System.Drawing.Point(55, 16);
-            this.angle.Name = "angle";
-            this.angle.Size = new System.Drawing.Size(60, 24);
-            this.angle.StepValues = null;
-            this.angle.TabIndex = 0;
-            this.angle.WhenTextChanged += new System.EventHandler(this.angle_WhenTextChanged);
-            // 
-            // labelAngle
-            // 
-            this.labelAngle.Location = new System.Drawing.Point(5, 21);
-            this.labelAngle.Name = "labelAngle";
-            this.labelAngle.Size = new System.Drawing.Size(44, 14);
-            this.labelAngle.TabIndex = 8;
-            this.labelAngle.Text = "Angle:";
-            this.labelAngle.TextAlign = System.Drawing.ContentAlignment.TopRight;
-            // 
-            // anglecontrol
-            // 
-            this.anglecontrol.Angle = 0;
-            this.anglecontrol.AngleOffset = 0;
-            this.anglecontrol.DoomAngleClamping = false;
-            this.anglecontrol.Location = new System.Drawing.Point(7, 17);
-            this.anglecontrol.Name = "anglecontrol";
-            this.anglecontrol.Size = new System.Drawing.Size(64, 64);
-            this.anglecontrol.TabIndex = 20;
-            this.anglecontrol.AngleChanged += new System.EventHandler(this.anglecontrol_AngleChanged);
-            // 
-            // labelGravity
-            // 
-            this.labelGravity.AutoSize = true;
-            this.labelGravity.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.labelGravity.ForeColor = System.Drawing.SystemColors.HotTrack;
-            this.labelGravity.Location = new System.Drawing.Point(19, 27);
-            this.labelGravity.Name = "labelGravity";
-            this.labelGravity.Size = new System.Drawing.Size(43, 13);
-            this.labelGravity.TabIndex = 18;
-            this.labelGravity.Text = "Gravity:";
-            this.tooltip.SetToolTip(this.labelGravity, "Positive values are multiplied with the class\'s property.\r\nNegative values are us" +
+			this.components = new System.ComponentModel.Container();
+			this.groupBox1 = new System.Windows.Forms.GroupBox();
+			this.thingtype = new CodeImp.DoomBuilder.Controls.ThingBrowserControl();
+			this.groupBox2 = new System.Windows.Forms.GroupBox();
+			this.cbrandomroll = new System.Windows.Forms.CheckBox();
+			this.cbrandompitch = new System.Windows.Forms.CheckBox();
+			this.cbrandomangle = new System.Windows.Forms.CheckBox();
+			this.roll = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.labelroll = new System.Windows.Forms.Label();
+			this.pitch = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.labelpitch = new System.Windows.Forms.Label();
+			this.angle = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.labelAngle = new System.Windows.Forms.Label();
+			this.anglecontrol = new CodeImp.DoomBuilder.Controls.AngleControlEx();
+			this.labelGravity = new System.Windows.Forms.Label();
+			this.label7 = new System.Windows.Forms.Label();
+			this.tabs = new System.Windows.Forms.TabControl();
+			this.tabproperties = new System.Windows.Forms.TabPage();
+			this.settingsgroup = new System.Windows.Forms.GroupBox();
+			this.missingflags = new System.Windows.Forms.PictureBox();
+			this.flags = new CodeImp.DoomBuilder.Controls.CheckboxArrayControl();
+			this.grouproll = new System.Windows.Forms.GroupBox();
+			this.rollControl = new CodeImp.DoomBuilder.Controls.AngleControlEx();
+			this.grouppitch = new System.Windows.Forms.GroupBox();
+			this.pitchControl = new CodeImp.DoomBuilder.Controls.AngleControlEx();
+			this.groupangle = new System.Windows.Forms.GroupBox();
+			this.groupBox4 = new System.Windows.Forms.GroupBox();
+			this.cbAbsoluteHeight = new System.Windows.Forms.CheckBox();
+			this.label4 = new System.Windows.Forms.Label();
+			this.label5 = new System.Windows.Forms.Label();
+			this.posX = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.posY = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.posZ = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.zlabel = new System.Windows.Forms.Label();
+			this.tabeffects = new System.Windows.Forms.TabPage();
+			this.groupbehaviour = new System.Windows.Forms.GroupBox();
+			this.floatbobphase = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.label1 = new System.Windows.Forms.Label();
+			this.conversationID = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.labelID = new System.Windows.Forms.Label();
+			this.health = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.label10 = new System.Windows.Forms.Label();
+			this.score = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.label9 = new System.Windows.Forms.Label();
+			this.gravity = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.grouprendering = new System.Windows.Forms.GroupBox();
+			this.resetalpha = new System.Windows.Forms.Button();
+			this.labelScale = new System.Windows.Forms.Label();
+			this.scale = new CodeImp.DoomBuilder.Controls.PairedFloatControl();
+			this.color = new CodeImp.DoomBuilder.Controls.ColorFieldsControl();
+			this.alpha = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.label8 = new System.Windows.Forms.Label();
+			this.renderStyle = new System.Windows.Forms.ComboBox();
+			this.labelrenderstyle = new System.Windows.Forms.Label();
+			this.actiongroup = new System.Windows.Forms.GroupBox();
+			this.argscontrol = new CodeImp.DoomBuilder.Controls.ArgumentsControl();
+			this.actionhelp = new CodeImp.DoomBuilder.Controls.ActionSpecialHelpButton();
+			this.action = new CodeImp.DoomBuilder.Controls.ActionSelectorControl();
+			this.browseaction = new System.Windows.Forms.Button();
+			this.grouptag = new System.Windows.Forms.GroupBox();
+			this.tagSelector = new CodeImp.DoomBuilder.Controls.TagSelector();
+			this.tabcomment = new System.Windows.Forms.TabPage();
+			this.commenteditor = new CodeImp.DoomBuilder.Controls.CommentEditor();
+			this.tabcustom = new System.Windows.Forms.TabPage();
+			this.hidefixedfields = new System.Windows.Forms.CheckBox();
+			this.fieldslist = new CodeImp.DoomBuilder.Controls.FieldsEditorControl();
+			this.cancel = new System.Windows.Forms.Button();
+			this.apply = new System.Windows.Forms.Button();
+			this.hint = new System.Windows.Forms.PictureBox();
+			this.hintlabel = new System.Windows.Forms.Label();
+			this.tooltip = new System.Windows.Forms.ToolTip(this.components);
+			this.groupBox1.SuspendLayout();
+			this.groupBox2.SuspendLayout();
+			this.tabs.SuspendLayout();
+			this.tabproperties.SuspendLayout();
+			this.settingsgroup.SuspendLayout();
+			((System.ComponentModel.ISupportInitialize)(this.missingflags)).BeginInit();
+			this.grouproll.SuspendLayout();
+			this.grouppitch.SuspendLayout();
+			this.groupangle.SuspendLayout();
+			this.groupBox4.SuspendLayout();
+			this.tabeffects.SuspendLayout();
+			this.groupbehaviour.SuspendLayout();
+			this.grouprendering.SuspendLayout();
+			this.actiongroup.SuspendLayout();
+			this.grouptag.SuspendLayout();
+			this.tabcomment.SuspendLayout();
+			this.tabcustom.SuspendLayout();
+			((System.ComponentModel.ISupportInitialize)(this.hint)).BeginInit();
+			this.SuspendLayout();
+			// 
+			// groupBox1
+			// 
+			this.groupBox1.Controls.Add(this.thingtype);
+			this.groupBox1.Location = new System.Drawing.Point(6, 6);
+			this.groupBox1.Name = "groupBox1";
+			this.groupBox1.Size = new System.Drawing.Size(230, 390);
+			this.groupBox1.TabIndex = 0;
+			this.groupBox1.TabStop = false;
+			this.groupBox1.Text = " Thing ";
+			// 
+			// thingtype
+			// 
+			this.thingtype.Location = new System.Drawing.Point(9, 13);
+			this.thingtype.Margin = new System.Windows.Forms.Padding(6);
+			this.thingtype.Name = "thingtype";
+			this.thingtype.Size = new System.Drawing.Size(212, 374);
+			this.thingtype.TabIndex = 0;
+			this.thingtype.UseMultiSelection = true;
+			this.thingtype.OnTypeChanged += new CodeImp.DoomBuilder.Controls.ThingBrowserControl.TypeChangedDeletegate(this.thingtype_OnTypeChanged);
+			this.thingtype.OnTypeDoubleClicked += new CodeImp.DoomBuilder.Controls.ThingBrowserControl.TypeDoubleClickDeletegate(this.thingtype_OnTypeDoubleClicked);
+			// 
+			// groupBox2
+			// 
+			this.groupBox2.Controls.Add(this.cbrandomroll);
+			this.groupBox2.Controls.Add(this.cbrandompitch);
+			this.groupBox2.Controls.Add(this.cbrandomangle);
+			this.groupBox2.Controls.Add(this.roll);
+			this.groupBox2.Controls.Add(this.labelroll);
+			this.groupBox2.Controls.Add(this.pitch);
+			this.groupBox2.Controls.Add(this.labelpitch);
+			this.groupBox2.Controls.Add(this.angle);
+			this.groupBox2.Controls.Add(this.labelAngle);
+			this.groupBox2.Location = new System.Drawing.Point(428, 298);
+			this.groupBox2.Name = "groupBox2";
+			this.groupBox2.Size = new System.Drawing.Size(193, 98);
+			this.groupBox2.TabIndex = 6;
+			this.groupBox2.TabStop = false;
+			this.groupBox2.Text = " Rotation ";
+			// 
+			// cbrandomroll
+			// 
+			this.cbrandomroll.AutoSize = true;
+			this.cbrandomroll.Location = new System.Drawing.Point(120, 71);
+			this.cbrandomroll.Name = "cbrandomroll";
+			this.cbrandomroll.Size = new System.Drawing.Size(66, 17);
+			this.cbrandomroll.TabIndex = 5;
+			this.cbrandomroll.Text = "Random";
+			this.cbrandomroll.UseVisualStyleBackColor = true;
+			this.cbrandomroll.CheckedChanged += new System.EventHandler(this.cbrandomroll_CheckedChanged);
+			// 
+			// cbrandompitch
+			// 
+			this.cbrandompitch.AutoSize = true;
+			this.cbrandompitch.Location = new System.Drawing.Point(120, 46);
+			this.cbrandompitch.Name = "cbrandompitch";
+			this.cbrandompitch.Size = new System.Drawing.Size(66, 17);
+			this.cbrandompitch.TabIndex = 3;
+			this.cbrandompitch.Text = "Random";
+			this.cbrandompitch.UseVisualStyleBackColor = true;
+			this.cbrandompitch.CheckedChanged += new System.EventHandler(this.cbrandompitch_CheckedChanged);
+			// 
+			// cbrandomangle
+			// 
+			this.cbrandomangle.AutoSize = true;
+			this.cbrandomangle.Location = new System.Drawing.Point(120, 21);
+			this.cbrandomangle.Name = "cbrandomangle";
+			this.cbrandomangle.Size = new System.Drawing.Size(66, 17);
+			this.cbrandomangle.TabIndex = 1;
+			this.cbrandomangle.Text = "Random";
+			this.cbrandomangle.UseVisualStyleBackColor = true;
+			this.cbrandomangle.CheckedChanged += new System.EventHandler(this.cbrandomangle_CheckedChanged);
+			// 
+			// roll
+			// 
+			this.roll.AllowDecimal = false;
+			this.roll.AllowExpressions = true;
+			this.roll.AllowNegative = true;
+			this.roll.AllowRelative = true;
+			this.roll.ButtonStep = 5;
+			this.roll.ButtonStepBig = 15F;
+			this.roll.ButtonStepFloat = 1F;
+			this.roll.ButtonStepSmall = 1F;
+			this.roll.ButtonStepsUseModifierKeys = true;
+			this.roll.ButtonStepsWrapAround = false;
+			this.roll.Location = new System.Drawing.Point(55, 66);
+			this.roll.Name = "roll";
+			this.roll.Size = new System.Drawing.Size(60, 24);
+			this.roll.StepValues = null;
+			this.roll.TabIndex = 4;
+			this.roll.WhenTextChanged += new System.EventHandler(this.roll_WhenTextChanged);
+			// 
+			// labelroll
+			// 
+			this.labelroll.Location = new System.Drawing.Point(5, 71);
+			this.labelroll.Name = "labelroll";
+			this.labelroll.Size = new System.Drawing.Size(44, 14);
+			this.labelroll.TabIndex = 23;
+			this.labelroll.Text = "Roll:";
+			this.labelroll.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			// 
+			// pitch
+			// 
+			this.pitch.AllowDecimal = false;
+			this.pitch.AllowExpressions = true;
+			this.pitch.AllowNegative = true;
+			this.pitch.AllowRelative = true;
+			this.pitch.ButtonStep = 5;
+			this.pitch.ButtonStepBig = 15F;
+			this.pitch.ButtonStepFloat = 1F;
+			this.pitch.ButtonStepSmall = 1F;
+			this.pitch.ButtonStepsUseModifierKeys = true;
+			this.pitch.ButtonStepsWrapAround = false;
+			this.pitch.Location = new System.Drawing.Point(55, 41);
+			this.pitch.Name = "pitch";
+			this.pitch.Size = new System.Drawing.Size(60, 24);
+			this.pitch.StepValues = null;
+			this.pitch.TabIndex = 2;
+			this.pitch.WhenTextChanged += new System.EventHandler(this.pitch_WhenTextChanged);
+			// 
+			// labelpitch
+			// 
+			this.labelpitch.Location = new System.Drawing.Point(5, 46);
+			this.labelpitch.Name = "labelpitch";
+			this.labelpitch.Size = new System.Drawing.Size(44, 14);
+			this.labelpitch.TabIndex = 21;
+			this.labelpitch.Text = "Pitch:";
+			this.labelpitch.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			// 
+			// angle
+			// 
+			this.angle.AllowDecimal = false;
+			this.angle.AllowExpressions = true;
+			this.angle.AllowNegative = true;
+			this.angle.AllowRelative = true;
+			this.angle.ButtonStep = 5;
+			this.angle.ButtonStepBig = 15F;
+			this.angle.ButtonStepFloat = 1F;
+			this.angle.ButtonStepSmall = 1F;
+			this.angle.ButtonStepsUseModifierKeys = true;
+			this.angle.ButtonStepsWrapAround = false;
+			this.angle.Location = new System.Drawing.Point(55, 16);
+			this.angle.Name = "angle";
+			this.angle.Size = new System.Drawing.Size(60, 24);
+			this.angle.StepValues = null;
+			this.angle.TabIndex = 0;
+			this.angle.WhenTextChanged += new System.EventHandler(this.angle_WhenTextChanged);
+			// 
+			// labelAngle
+			// 
+			this.labelAngle.Location = new System.Drawing.Point(5, 21);
+			this.labelAngle.Name = "labelAngle";
+			this.labelAngle.Size = new System.Drawing.Size(44, 14);
+			this.labelAngle.TabIndex = 8;
+			this.labelAngle.Text = "Angle:";
+			this.labelAngle.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			// 
+			// anglecontrol
+			// 
+			this.anglecontrol.Angle = 0;
+			this.anglecontrol.AngleOffset = 0;
+			this.anglecontrol.DoomAngleClamping = false;
+			this.anglecontrol.Location = new System.Drawing.Point(7, 17);
+			this.anglecontrol.Name = "anglecontrol";
+			this.anglecontrol.Size = new System.Drawing.Size(64, 64);
+			this.anglecontrol.TabIndex = 20;
+			this.anglecontrol.AngleChanged += new System.EventHandler(this.anglecontrol_AngleChanged);
+			// 
+			// labelGravity
+			// 
+			this.labelGravity.AutoSize = true;
+			this.labelGravity.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.labelGravity.ForeColor = System.Drawing.SystemColors.HotTrack;
+			this.labelGravity.Location = new System.Drawing.Point(19, 27);
+			this.labelGravity.Name = "labelGravity";
+			this.labelGravity.Size = new System.Drawing.Size(43, 13);
+			this.labelGravity.TabIndex = 18;
+			this.labelGravity.Text = "Gravity:";
+			this.tooltip.SetToolTip(this.labelGravity, "Positive values are multiplied with the class\'s property.\r\nNegative values are us" +
         "ed as their absolute.\r\nDefault is 1.0.");
-            // 
-            // label7
-            // 
-            this.label7.AutoSize = true;
-            this.label7.Location = new System.Drawing.Point(15, 30);
-            this.label7.Name = "label7";
-            this.label7.Size = new System.Drawing.Size(40, 13);
-            this.label7.TabIndex = 9;
-            this.label7.Text = "Action:";
-            // 
-            // tabs
-            // 
-            this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+			// 
+			// label7
+			// 
+			this.label7.AutoSize = true;
+			this.label7.Location = new System.Drawing.Point(15, 30);
+			this.label7.Name = "label7";
+			this.label7.Size = new System.Drawing.Size(40, 13);
+			this.label7.TabIndex = 9;
+			this.label7.Text = "Action:";
+			// 
+			// tabs
+			// 
+			this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-            this.tabs.Controls.Add(this.tabproperties);
-            this.tabs.Controls.Add(this.tabeffects);
-            this.tabs.Controls.Add(this.tabcomment);
-            this.tabs.Controls.Add(this.tabcustom);
-            this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.tabs.Location = new System.Drawing.Point(10, 10);
-            this.tabs.Margin = new System.Windows.Forms.Padding(1);
-            this.tabs.Name = "tabs";
-            this.tabs.Padding = new System.Drawing.Point(24, 3);
-            this.tabs.SelectedIndex = 0;
-            this.tabs.Size = new System.Drawing.Size(635, 425);
-            this.tabs.TabIndex = 0;
-            // 
-            // tabproperties
-            // 
-            this.tabproperties.Controls.Add(this.settingsgroup);
-            this.tabproperties.Controls.Add(this.grouproll);
-            this.tabproperties.Controls.Add(this.grouppitch);
-            this.tabproperties.Controls.Add(this.groupangle);
-            this.tabproperties.Controls.Add(this.groupBox4);
-            this.tabproperties.Controls.Add(this.groupBox2);
-            this.tabproperties.Controls.Add(this.groupBox1);
-            this.tabproperties.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.tabproperties.Location = new System.Drawing.Point(4, 24);
-            this.tabproperties.Name = "tabproperties";
-            this.tabproperties.Padding = new System.Windows.Forms.Padding(3);
-            this.tabproperties.Size = new System.Drawing.Size(627, 397);
-            this.tabproperties.TabIndex = 0;
-            this.tabproperties.Text = "Properties";
-            this.tabproperties.UseVisualStyleBackColor = true;
-            // 
-            // settingsgroup
-            // 
-            this.settingsgroup.Controls.Add(this.missingflags);
-            this.settingsgroup.Controls.Add(this.flags);
-            this.settingsgroup.Location = new System.Drawing.Point(242, 6);
-            this.settingsgroup.Name = "settingsgroup";
-            this.settingsgroup.Size = new System.Drawing.Size(295, 286);
-            this.settingsgroup.TabIndex = 1;
-            this.settingsgroup.TabStop = false;
-            this.settingsgroup.Text = " Flags ";
-            // 
-            // missingflags
-            // 
-            this.missingflags.BackColor = System.Drawing.SystemColors.Window;
-            this.missingflags.Image = global::CodeImp.DoomBuilder.Properties.Resources.Warning;
-            this.missingflags.Location = new System.Drawing.Point(42, -2);
-            this.missingflags.Name = "missingflags";
-            this.missingflags.Size = new System.Drawing.Size(16, 16);
-            this.missingflags.TabIndex = 5;
-            this.missingflags.TabStop = false;
-            this.missingflags.Visible = false;
-            // 
-            // flags
-            // 
-            this.flags.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+			this.tabs.Controls.Add(this.tabproperties);
+			this.tabs.Controls.Add(this.tabeffects);
+			this.tabs.Controls.Add(this.tabcomment);
+			this.tabs.Controls.Add(this.tabcustom);
+			this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.tabs.Location = new System.Drawing.Point(10, 10);
+			this.tabs.Margin = new System.Windows.Forms.Padding(1);
+			this.tabs.Name = "tabs";
+			this.tabs.Padding = new System.Drawing.Point(24, 3);
+			this.tabs.SelectedIndex = 0;
+			this.tabs.Size = new System.Drawing.Size(635, 425);
+			this.tabs.TabIndex = 0;
+			// 
+			// tabproperties
+			// 
+			this.tabproperties.Controls.Add(this.settingsgroup);
+			this.tabproperties.Controls.Add(this.grouproll);
+			this.tabproperties.Controls.Add(this.grouppitch);
+			this.tabproperties.Controls.Add(this.groupangle);
+			this.tabproperties.Controls.Add(this.groupBox4);
+			this.tabproperties.Controls.Add(this.groupBox2);
+			this.tabproperties.Controls.Add(this.groupBox1);
+			this.tabproperties.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.tabproperties.Location = new System.Drawing.Point(4, 22);
+			this.tabproperties.Name = "tabproperties";
+			this.tabproperties.Padding = new System.Windows.Forms.Padding(3);
+			this.tabproperties.Size = new System.Drawing.Size(627, 399);
+			this.tabproperties.TabIndex = 0;
+			this.tabproperties.Text = "Properties";
+			this.tabproperties.UseVisualStyleBackColor = true;
+			// 
+			// settingsgroup
+			// 
+			this.settingsgroup.Controls.Add(this.missingflags);
+			this.settingsgroup.Controls.Add(this.flags);
+			this.settingsgroup.Location = new System.Drawing.Point(242, 6);
+			this.settingsgroup.Name = "settingsgroup";
+			this.settingsgroup.Size = new System.Drawing.Size(295, 286);
+			this.settingsgroup.TabIndex = 1;
+			this.settingsgroup.TabStop = false;
+			this.settingsgroup.Text = " Flags ";
+			// 
+			// missingflags
+			// 
+			this.missingflags.BackColor = System.Drawing.SystemColors.Window;
+			this.missingflags.Image = global::CodeImp.DoomBuilder.Properties.Resources.Warning;
+			this.missingflags.Location = new System.Drawing.Point(42, -2);
+			this.missingflags.Name = "missingflags";
+			this.missingflags.Size = new System.Drawing.Size(16, 16);
+			this.missingflags.TabIndex = 5;
+			this.missingflags.TabStop = false;
+			this.missingflags.Visible = false;
+			// 
+			// flags
+			// 
+			this.flags.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-            this.flags.AutoScroll = true;
-            this.flags.Columns = 2;
-            this.flags.Location = new System.Drawing.Point(6, 19);
-            this.flags.Name = "flags";
-            this.flags.Size = new System.Drawing.Size(283, 260);
-            this.flags.TabIndex = 0;
-            this.flags.VerticalSpacing = 1;
-            this.flags.OnValueChanged += new System.EventHandler(this.flags_OnValueChanged);
-            // 
-            // grouproll
-            // 
-            this.grouproll.Controls.Add(this.rollControl);
-            this.grouproll.Location = new System.Drawing.Point(543, 16);
-            this.grouproll.Name = "grouproll";
-            this.grouproll.Size = new System.Drawing.Size(78, 88);
-            this.grouproll.TabIndex = 2;
-            this.grouproll.TabStop = false;
-            this.grouproll.Text = " Roll ";
-            // 
-            // rollControl
-            // 
-            this.rollControl.Angle = -90;
-            this.rollControl.AngleOffset = 0;
-            this.rollControl.DoomAngleClamping = false;
-            this.rollControl.Location = new System.Drawing.Point(7, 17);
-            this.rollControl.Name = "rollControl";
-            this.rollControl.Size = new System.Drawing.Size(64, 64);
-            this.rollControl.TabIndex = 20;
-            this.rollControl.AngleChanged += new System.EventHandler(this.rollControl_AngleChanged);
-            // 
-            // grouppitch
-            // 
-            this.grouppitch.Controls.Add(this.pitchControl);
-            this.grouppitch.Location = new System.Drawing.Point(543, 110);
-            this.grouppitch.Name = "grouppitch";
-            this.grouppitch.Size = new System.Drawing.Size(78, 88);
-            this.grouppitch.TabIndex = 3;
-            this.grouppitch.TabStop = false;
-            this.grouppitch.Text = " Pitch ";
-            // 
-            // pitchControl
-            // 
-            this.pitchControl.Angle = -90;
-            this.pitchControl.AngleOffset = 0;
-            this.pitchControl.DoomAngleClamping = false;
-            this.pitchControl.Location = new System.Drawing.Point(7, 17);
-            this.pitchControl.Name = "pitchControl";
-            this.pitchControl.Size = new System.Drawing.Size(64, 64);
-            this.pitchControl.TabIndex = 20;
-            this.pitchControl.AngleChanged += new System.EventHandler(this.pitchControl_AngleChanged);
-            // 
-            // groupangle
-            // 
-            this.groupangle.Controls.Add(this.anglecontrol);
-            this.groupangle.Location = new System.Drawing.Point(543, 204);
-            this.groupangle.Name = "groupangle";
-            this.groupangle.Size = new System.Drawing.Size(78, 88);
-            this.groupangle.TabIndex = 4;
-            this.groupangle.TabStop = false;
-            this.groupangle.Text = " Angle";
-            // 
-            // groupBox4
-            // 
-            this.groupBox4.Controls.Add(this.cbAbsoluteHeight);
-            this.groupBox4.Controls.Add(this.label4);
-            this.groupBox4.Controls.Add(this.label5);
-            this.groupBox4.Controls.Add(this.posX);
-            this.groupBox4.Controls.Add(this.posY);
-            this.groupBox4.Controls.Add(this.posZ);
-            this.groupBox4.Controls.Add(this.zlabel);
-            this.groupBox4.Location = new System.Drawing.Point(242, 298);
-            this.groupBox4.Name = "groupBox4";
-            this.groupBox4.Size = new System.Drawing.Size(180, 98);
-            this.groupBox4.TabIndex = 5;
-            this.groupBox4.TabStop = false;
-            this.groupBox4.Text = " Position";
-            // 
-            // cbAbsoluteHeight
-            // 
-            this.cbAbsoluteHeight.AutoSize = true;
-            this.cbAbsoluteHeight.Location = new System.Drawing.Point(109, 71);
-            this.cbAbsoluteHeight.Name = "cbAbsoluteHeight";
-            this.cbAbsoluteHeight.Size = new System.Drawing.Size(67, 17);
-            this.cbAbsoluteHeight.TabIndex = 3;
-            this.cbAbsoluteHeight.Text = "Absolute";
-            this.cbAbsoluteHeight.UseVisualStyleBackColor = true;
-            this.cbAbsoluteHeight.CheckedChanged += new System.EventHandler(this.cbAbsoluteHeight_CheckedChanged);
-            // 
-            // label4
-            // 
-            this.label4.Location = new System.Drawing.Point(4, 21);
-            this.label4.Name = "label4";
-            this.label4.Size = new System.Drawing.Size(22, 14);
-            this.label4.TabIndex = 15;
-            this.label4.Text = "X:";
-            this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
-            // 
-            // label5
-            // 
-            this.label5.Location = new System.Drawing.Point(4, 46);
-            this.label5.Name = "label5";
-            this.label5.Size = new System.Drawing.Size(22, 14);
-            this.label5.TabIndex = 14;
-            this.label5.Text = "Y:";
-            this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
-            // 
-            // posX
-            // 
-            this.posX.AllowDecimal = true;
-            this.posX.AllowExpressions = true;
-            this.posX.AllowNegative = true;
-            this.posX.AllowRelative = true;
-            this.posX.ButtonStep = 8;
-            this.posX.ButtonStepBig = 8F;
-            this.posX.ButtonStepFloat = 1F;
-            this.posX.ButtonStepSmall = 0.1F;
-            this.posX.ButtonStepsUseModifierKeys = true;
-            this.posX.ButtonStepsWrapAround = false;
-            this.posX.Location = new System.Drawing.Point(32, 16);
-            this.posX.Name = "posX";
-            this.posX.Size = new System.Drawing.Size(72, 24);
-            this.posX.StepValues = null;
-            this.posX.TabIndex = 0;
-            this.posX.WhenTextChanged += new System.EventHandler(this.posX_WhenTextChanged);
-            // 
-            // posY
-            // 
-            this.posY.AllowDecimal = true;
-            this.posY.AllowExpressions = true;
-            this.posY.AllowNegative = true;
-            this.posY.AllowRelative = true;
-            this.posY.ButtonStep = 8;
-            this.posY.ButtonStepBig = 8F;
-            this.posY.ButtonStepFloat = 1F;
-            this.posY.ButtonStepSmall = 0.1F;
-            this.posY.ButtonStepsUseModifierKeys = true;
-            this.posY.ButtonStepsWrapAround = false;
-            this.posY.Location = new System.Drawing.Point(32, 41);
-            this.posY.Name = "posY";
-            this.posY.Size = new System.Drawing.Size(72, 24);
-            this.posY.StepValues = null;
-            this.posY.TabIndex = 1;
-            this.posY.WhenTextChanged += new System.EventHandler(this.posY_WhenTextChanged);
-            // 
-            // posZ
-            // 
-            this.posZ.AllowDecimal = true;
-            this.posZ.AllowExpressions = true;
-            this.posZ.AllowNegative = true;
-            this.posZ.AllowRelative = true;
-            this.posZ.ButtonStep = 8;
-            this.posZ.ButtonStepBig = 8F;
-            this.posZ.ButtonStepFloat = 1F;
-            this.posZ.ButtonStepSmall = 0.1F;
-            this.posZ.ButtonStepsUseModifierKeys = true;
-            this.posZ.ButtonStepsWrapAround = false;
-            this.posZ.Location = new System.Drawing.Point(32, 66);
-            this.posZ.Name = "posZ";
-            this.posZ.Size = new System.Drawing.Size(72, 24);
-            this.posZ.StepValues = null;
-            this.posZ.TabIndex = 2;
-            this.posZ.WhenTextChanged += new System.EventHandler(this.posZ_WhenTextChanged);
-            // 
-            // zlabel
-            // 
-            this.zlabel.Location = new System.Drawing.Point(4, 71);
-            this.zlabel.Name = "zlabel";
-            this.zlabel.Size = new System.Drawing.Size(22, 14);
-            this.zlabel.TabIndex = 9;
-            this.zlabel.Text = "Z:";
-            this.zlabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
-            // 
-            // tabeffects
-            // 
-            this.tabeffects.Controls.Add(this.groupbehaviour);
-            this.tabeffects.Controls.Add(this.grouprendering);
-            this.tabeffects.Controls.Add(this.actiongroup);
-            this.tabeffects.Controls.Add(this.grouptag);
-            this.tabeffects.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.tabeffects.Location = new System.Drawing.Point(4, 24);
-            this.tabeffects.Name = "tabeffects";
-            this.tabeffects.Padding = new System.Windows.Forms.Padding(3);
-            this.tabeffects.Size = new System.Drawing.Size(627, 401);
-            this.tabeffects.TabIndex = 1;
-            this.tabeffects.Text = "Action / Tag / Misc.";
-            this.tabeffects.UseVisualStyleBackColor = true;
-            // 
-            // groupbehaviour
-            // 
-            this.groupbehaviour.Controls.Add(this.floatbobphase);
-            this.groupbehaviour.Controls.Add(this.label1);
-            this.groupbehaviour.Controls.Add(this.conversationID);
-            this.groupbehaviour.Controls.Add(this.labelID);
-            this.groupbehaviour.Controls.Add(this.health);
-            this.groupbehaviour.Controls.Add(this.label10);
-            this.groupbehaviour.Controls.Add(this.score);
-            this.groupbehaviour.Controls.Add(this.label9);
-            this.groupbehaviour.Controls.Add(this.gravity);
-            this.groupbehaviour.Controls.Add(this.labelGravity);
-            this.groupbehaviour.Location = new System.Drawing.Point(285, 6);
-            this.groupbehaviour.Name = "groupbehaviour";
-            this.groupbehaviour.Size = new System.Drawing.Size(333, 158);
-            this.groupbehaviour.TabIndex = 23;
-            this.groupbehaviour.TabStop = false;
-            this.groupbehaviour.Text = " Behaviour ";
-            // 
-            // floatbobphase
-            // 
-            this.floatbobphase.AllowDecimal = false;
-            this.floatbobphase.AllowExpressions = false;
-            this.floatbobphase.AllowNegative = true;
-            this.floatbobphase.AllowRelative = false;
-            this.floatbobphase.ButtonStep = 1;
-            this.floatbobphase.ButtonStepBig = 8F;
-            this.floatbobphase.ButtonStepFloat = 1F;
-            this.floatbobphase.ButtonStepSmall = 1F;
-            this.floatbobphase.ButtonStepsUseModifierKeys = false;
-            this.floatbobphase.ButtonStepsWrapAround = false;
-            this.floatbobphase.Location = new System.Drawing.Point(251, 52);
-            this.floatbobphase.Name = "floatbobphase";
-            this.floatbobphase.Size = new System.Drawing.Size(72, 24);
-            this.floatbobphase.StepValues = null;
-            this.floatbobphase.TabIndex = 4;
-            // 
-            // label1
-            // 
-            this.label1.AutoSize = true;
-            this.label1.Location = new System.Drawing.Point(159, 57);
-            this.label1.Name = "label1";
-            this.label1.Size = new System.Drawing.Size(86, 13);
-            this.label1.TabIndex = 27;
-            this.label1.Text = "Float bob phase:";
-            // 
-            // conversationID
-            // 
-            this.conversationID.AllowDecimal = false;
-            this.conversationID.AllowExpressions = false;
-            this.conversationID.AllowNegative = false;
-            this.conversationID.AllowRelative = false;
-            this.conversationID.ButtonStep = 1;
-            this.conversationID.ButtonStepBig = 8F;
-            this.conversationID.ButtonStepFloat = 1F;
-            this.conversationID.ButtonStepSmall = 1F;
-            this.conversationID.ButtonStepsUseModifierKeys = false;
-            this.conversationID.ButtonStepsWrapAround = false;
-            this.conversationID.Location = new System.Drawing.Point(251, 22);
-            this.conversationID.Name = "conversationID";
-            this.conversationID.Size = new System.Drawing.Size(72, 24);
-            this.conversationID.StepValues = null;
-            this.conversationID.TabIndex = 3;
-            // 
-            // labelID
-            // 
-            this.labelID.AutoSize = true;
-            this.labelID.Location = new System.Drawing.Point(159, 27);
-            this.labelID.Name = "labelID";
-            this.labelID.Size = new System.Drawing.Size(86, 13);
-            this.labelID.TabIndex = 25;
-            this.labelID.Text = "Conversation ID:";
-            // 
-            // health
-            // 
-            this.health.AllowDecimal = false;
-            this.health.AllowExpressions = false;
-            this.health.AllowNegative = true;
-            this.health.AllowRelative = false;
-            this.health.ButtonStep = 8;
-            this.health.ButtonStepBig = 16F;
-            this.health.ButtonStepFloat = 0.1F;
-            this.health.ButtonStepSmall = 1F;
-            this.health.ButtonStepsUseModifierKeys = true;
-            this.health.ButtonStepsWrapAround = false;
-            this.health.Location = new System.Drawing.Point(68, 82);
-            this.health.Name = "health";
-            this.health.Size = new System.Drawing.Size(72, 24);
-            this.health.StepValues = null;
-            this.health.TabIndex = 2;
-            // 
-            // label10
-            // 
-            this.label10.AutoSize = true;
-            this.label10.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.label10.ForeColor = System.Drawing.SystemColors.HotTrack;
-            this.label10.Location = new System.Drawing.Point(21, 87);
-            this.label10.Name = "label10";
-            this.label10.Size = new System.Drawing.Size(41, 13);
-            this.label10.TabIndex = 22;
-            this.label10.Text = "Health:";
-            this.tooltip.SetToolTip(this.label10, "Positive values are multiplied with the class\'s property.\r\nNegative values are us" +
+			this.flags.AutoScroll = true;
+			this.flags.Columns = 2;
+			this.flags.Location = new System.Drawing.Point(6, 19);
+			this.flags.Name = "flags";
+			this.flags.Size = new System.Drawing.Size(283, 260);
+			this.flags.TabIndex = 0;
+			this.flags.VerticalSpacing = 1;
+			this.flags.OnValueChanged += new System.EventHandler(this.flags_OnValueChanged);
+			// 
+			// grouproll
+			// 
+			this.grouproll.Controls.Add(this.rollControl);
+			this.grouproll.Location = new System.Drawing.Point(543, 16);
+			this.grouproll.Name = "grouproll";
+			this.grouproll.Size = new System.Drawing.Size(78, 88);
+			this.grouproll.TabIndex = 2;
+			this.grouproll.TabStop = false;
+			this.grouproll.Text = " Roll ";
+			// 
+			// rollControl
+			// 
+			this.rollControl.Angle = -90;
+			this.rollControl.AngleOffset = 0;
+			this.rollControl.DoomAngleClamping = false;
+			this.rollControl.Location = new System.Drawing.Point(7, 17);
+			this.rollControl.Name = "rollControl";
+			this.rollControl.Size = new System.Drawing.Size(64, 64);
+			this.rollControl.TabIndex = 20;
+			this.rollControl.AngleChanged += new System.EventHandler(this.rollControl_AngleChanged);
+			// 
+			// grouppitch
+			// 
+			this.grouppitch.Controls.Add(this.pitchControl);
+			this.grouppitch.Location = new System.Drawing.Point(543, 110);
+			this.grouppitch.Name = "grouppitch";
+			this.grouppitch.Size = new System.Drawing.Size(78, 88);
+			this.grouppitch.TabIndex = 3;
+			this.grouppitch.TabStop = false;
+			this.grouppitch.Text = " Pitch ";
+			// 
+			// pitchControl
+			// 
+			this.pitchControl.Angle = -90;
+			this.pitchControl.AngleOffset = 0;
+			this.pitchControl.DoomAngleClamping = false;
+			this.pitchControl.Location = new System.Drawing.Point(7, 17);
+			this.pitchControl.Name = "pitchControl";
+			this.pitchControl.Size = new System.Drawing.Size(64, 64);
+			this.pitchControl.TabIndex = 20;
+			this.pitchControl.AngleChanged += new System.EventHandler(this.pitchControl_AngleChanged);
+			// 
+			// groupangle
+			// 
+			this.groupangle.Controls.Add(this.anglecontrol);
+			this.groupangle.Location = new System.Drawing.Point(543, 204);
+			this.groupangle.Name = "groupangle";
+			this.groupangle.Size = new System.Drawing.Size(78, 88);
+			this.groupangle.TabIndex = 4;
+			this.groupangle.TabStop = false;
+			this.groupangle.Text = " Angle";
+			// 
+			// groupBox4
+			// 
+			this.groupBox4.Controls.Add(this.cbAbsoluteHeight);
+			this.groupBox4.Controls.Add(this.label4);
+			this.groupBox4.Controls.Add(this.label5);
+			this.groupBox4.Controls.Add(this.posX);
+			this.groupBox4.Controls.Add(this.posY);
+			this.groupBox4.Controls.Add(this.posZ);
+			this.groupBox4.Controls.Add(this.zlabel);
+			this.groupBox4.Location = new System.Drawing.Point(242, 298);
+			this.groupBox4.Name = "groupBox4";
+			this.groupBox4.Size = new System.Drawing.Size(180, 98);
+			this.groupBox4.TabIndex = 5;
+			this.groupBox4.TabStop = false;
+			this.groupBox4.Text = " Position";
+			// 
+			// cbAbsoluteHeight
+			// 
+			this.cbAbsoluteHeight.AutoSize = true;
+			this.cbAbsoluteHeight.Location = new System.Drawing.Point(109, 71);
+			this.cbAbsoluteHeight.Name = "cbAbsoluteHeight";
+			this.cbAbsoluteHeight.Size = new System.Drawing.Size(67, 17);
+			this.cbAbsoluteHeight.TabIndex = 3;
+			this.cbAbsoluteHeight.Text = "Absolute";
+			this.cbAbsoluteHeight.UseVisualStyleBackColor = true;
+			this.cbAbsoluteHeight.CheckedChanged += new System.EventHandler(this.cbAbsoluteHeight_CheckedChanged);
+			// 
+			// label4
+			// 
+			this.label4.Location = new System.Drawing.Point(4, 21);
+			this.label4.Name = "label4";
+			this.label4.Size = new System.Drawing.Size(22, 14);
+			this.label4.TabIndex = 15;
+			this.label4.Text = "X:";
+			this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+			// 
+			// label5
+			// 
+			this.label5.Location = new System.Drawing.Point(4, 46);
+			this.label5.Name = "label5";
+			this.label5.Size = new System.Drawing.Size(22, 14);
+			this.label5.TabIndex = 14;
+			this.label5.Text = "Y:";
+			this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+			// 
+			// posX
+			// 
+			this.posX.AllowDecimal = true;
+			this.posX.AllowExpressions = true;
+			this.posX.AllowNegative = true;
+			this.posX.AllowRelative = true;
+			this.posX.ButtonStep = 8;
+			this.posX.ButtonStepBig = 8F;
+			this.posX.ButtonStepFloat = 1F;
+			this.posX.ButtonStepSmall = 0.1F;
+			this.posX.ButtonStepsUseModifierKeys = true;
+			this.posX.ButtonStepsWrapAround = false;
+			this.posX.Location = new System.Drawing.Point(32, 16);
+			this.posX.Name = "posX";
+			this.posX.Size = new System.Drawing.Size(72, 24);
+			this.posX.StepValues = null;
+			this.posX.TabIndex = 0;
+			this.posX.WhenTextChanged += new System.EventHandler(this.posX_WhenTextChanged);
+			// 
+			// posY
+			// 
+			this.posY.AllowDecimal = true;
+			this.posY.AllowExpressions = true;
+			this.posY.AllowNegative = true;
+			this.posY.AllowRelative = true;
+			this.posY.ButtonStep = 8;
+			this.posY.ButtonStepBig = 8F;
+			this.posY.ButtonStepFloat = 1F;
+			this.posY.ButtonStepSmall = 0.1F;
+			this.posY.ButtonStepsUseModifierKeys = true;
+			this.posY.ButtonStepsWrapAround = false;
+			this.posY.Location = new System.Drawing.Point(32, 41);
+			this.posY.Name = "posY";
+			this.posY.Size = new System.Drawing.Size(72, 24);
+			this.posY.StepValues = null;
+			this.posY.TabIndex = 1;
+			this.posY.WhenTextChanged += new System.EventHandler(this.posY_WhenTextChanged);
+			// 
+			// posZ
+			// 
+			this.posZ.AllowDecimal = true;
+			this.posZ.AllowExpressions = true;
+			this.posZ.AllowNegative = true;
+			this.posZ.AllowRelative = true;
+			this.posZ.ButtonStep = 8;
+			this.posZ.ButtonStepBig = 8F;
+			this.posZ.ButtonStepFloat = 1F;
+			this.posZ.ButtonStepSmall = 0.1F;
+			this.posZ.ButtonStepsUseModifierKeys = true;
+			this.posZ.ButtonStepsWrapAround = false;
+			this.posZ.Location = new System.Drawing.Point(32, 66);
+			this.posZ.Name = "posZ";
+			this.posZ.Size = new System.Drawing.Size(72, 24);
+			this.posZ.StepValues = null;
+			this.posZ.TabIndex = 2;
+			this.posZ.WhenTextChanged += new System.EventHandler(this.posZ_WhenTextChanged);
+			// 
+			// zlabel
+			// 
+			this.zlabel.Location = new System.Drawing.Point(4, 71);
+			this.zlabel.Name = "zlabel";
+			this.zlabel.Size = new System.Drawing.Size(22, 14);
+			this.zlabel.TabIndex = 9;
+			this.zlabel.Text = "Z:";
+			this.zlabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+			// 
+			// tabeffects
+			// 
+			this.tabeffects.Controls.Add(this.groupbehaviour);
+			this.tabeffects.Controls.Add(this.grouprendering);
+			this.tabeffects.Controls.Add(this.actiongroup);
+			this.tabeffects.Controls.Add(this.grouptag);
+			this.tabeffects.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.tabeffects.Location = new System.Drawing.Point(4, 22);
+			this.tabeffects.Name = "tabeffects";
+			this.tabeffects.Padding = new System.Windows.Forms.Padding(3);
+			this.tabeffects.Size = new System.Drawing.Size(627, 399);
+			this.tabeffects.TabIndex = 1;
+			this.tabeffects.Text = "Action / Tag / Misc.";
+			this.tabeffects.UseVisualStyleBackColor = true;
+			// 
+			// groupbehaviour
+			// 
+			this.groupbehaviour.Controls.Add(this.floatbobphase);
+			this.groupbehaviour.Controls.Add(this.label1);
+			this.groupbehaviour.Controls.Add(this.conversationID);
+			this.groupbehaviour.Controls.Add(this.labelID);
+			this.groupbehaviour.Controls.Add(this.health);
+			this.groupbehaviour.Controls.Add(this.label10);
+			this.groupbehaviour.Controls.Add(this.score);
+			this.groupbehaviour.Controls.Add(this.label9);
+			this.groupbehaviour.Controls.Add(this.gravity);
+			this.groupbehaviour.Controls.Add(this.labelGravity);
+			this.groupbehaviour.Location = new System.Drawing.Point(285, 6);
+			this.groupbehaviour.Name = "groupbehaviour";
+			this.groupbehaviour.Size = new System.Drawing.Size(333, 158);
+			this.groupbehaviour.TabIndex = 23;
+			this.groupbehaviour.TabStop = false;
+			this.groupbehaviour.Text = " Behaviour ";
+			// 
+			// floatbobphase
+			// 
+			this.floatbobphase.AllowDecimal = false;
+			this.floatbobphase.AllowExpressions = false;
+			this.floatbobphase.AllowNegative = true;
+			this.floatbobphase.AllowRelative = false;
+			this.floatbobphase.ButtonStep = 1;
+			this.floatbobphase.ButtonStepBig = 8F;
+			this.floatbobphase.ButtonStepFloat = 1F;
+			this.floatbobphase.ButtonStepSmall = 1F;
+			this.floatbobphase.ButtonStepsUseModifierKeys = false;
+			this.floatbobphase.ButtonStepsWrapAround = false;
+			this.floatbobphase.Location = new System.Drawing.Point(251, 52);
+			this.floatbobphase.Name = "floatbobphase";
+			this.floatbobphase.Size = new System.Drawing.Size(72, 24);
+			this.floatbobphase.StepValues = null;
+			this.floatbobphase.TabIndex = 4;
+			// 
+			// label1
+			// 
+			this.label1.AutoSize = true;
+			this.label1.Location = new System.Drawing.Point(159, 57);
+			this.label1.Name = "label1";
+			this.label1.Size = new System.Drawing.Size(86, 13);
+			this.label1.TabIndex = 27;
+			this.label1.Text = "Float bob phase:";
+			// 
+			// conversationID
+			// 
+			this.conversationID.AllowDecimal = false;
+			this.conversationID.AllowExpressions = false;
+			this.conversationID.AllowNegative = false;
+			this.conversationID.AllowRelative = false;
+			this.conversationID.ButtonStep = 1;
+			this.conversationID.ButtonStepBig = 8F;
+			this.conversationID.ButtonStepFloat = 1F;
+			this.conversationID.ButtonStepSmall = 1F;
+			this.conversationID.ButtonStepsUseModifierKeys = false;
+			this.conversationID.ButtonStepsWrapAround = false;
+			this.conversationID.Location = new System.Drawing.Point(251, 22);
+			this.conversationID.Name = "conversationID";
+			this.conversationID.Size = new System.Drawing.Size(72, 24);
+			this.conversationID.StepValues = null;
+			this.conversationID.TabIndex = 3;
+			// 
+			// labelID
+			// 
+			this.labelID.AutoSize = true;
+			this.labelID.Location = new System.Drawing.Point(159, 27);
+			this.labelID.Name = "labelID";
+			this.labelID.Size = new System.Drawing.Size(86, 13);
+			this.labelID.TabIndex = 25;
+			this.labelID.Text = "Conversation ID:";
+			// 
+			// health
+			// 
+			this.health.AllowDecimal = true;
+			this.health.AllowExpressions = false;
+			this.health.AllowNegative = true;
+			this.health.AllowRelative = false;
+			this.health.ButtonStep = 8;
+			this.health.ButtonStepBig = 16F;
+			this.health.ButtonStepFloat = 0.1F;
+			this.health.ButtonStepSmall = 1F;
+			this.health.ButtonStepsUseModifierKeys = true;
+			this.health.ButtonStepsWrapAround = false;
+			this.health.Location = new System.Drawing.Point(68, 82);
+			this.health.Name = "health";
+			this.health.Size = new System.Drawing.Size(72, 24);
+			this.health.StepValues = null;
+			this.health.TabIndex = 2;
+			// 
+			// label10
+			// 
+			this.label10.AutoSize = true;
+			this.label10.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.label10.ForeColor = System.Drawing.SystemColors.HotTrack;
+			this.label10.Location = new System.Drawing.Point(21, 87);
+			this.label10.Name = "label10";
+			this.label10.Size = new System.Drawing.Size(41, 13);
+			this.label10.TabIndex = 22;
+			this.label10.Text = "Health:";
+			this.tooltip.SetToolTip(this.label10, "Positive values are multiplied with the class\'s property.\r\nNegative values are us" +
         "ed as their absolute.\r\nDefault is 1.");
-            // 
-            // score
-            // 
-            this.score.AllowDecimal = false;
-            this.score.AllowExpressions = false;
-            this.score.AllowNegative = false;
-            this.score.AllowRelative = false;
-            this.score.ButtonStep = 8;
-            this.score.ButtonStepBig = 16F;
-            this.score.ButtonStepFloat = 0.1F;
-            this.score.ButtonStepSmall = 1F;
-            this.score.ButtonStepsUseModifierKeys = true;
-            this.score.ButtonStepsWrapAround = false;
-            this.score.Location = new System.Drawing.Point(68, 52);
-            this.score.Name = "score";
-            this.score.Size = new System.Drawing.Size(72, 24);
-            this.score.StepValues = null;
-            this.score.TabIndex = 1;
-            // 
-            // label9
-            // 
-            this.label9.AutoSize = true;
-            this.label9.Location = new System.Drawing.Point(24, 57);
-            this.label9.Name = "label9";
-            this.label9.Size = new System.Drawing.Size(38, 13);
-            this.label9.TabIndex = 20;
-            this.label9.Text = "Score:";
-            // 
-            // gravity
-            // 
-            this.gravity.AllowDecimal = true;
-            this.gravity.AllowExpressions = false;
-            this.gravity.AllowNegative = true;
-            this.gravity.AllowRelative = false;
-            this.gravity.ButtonStep = 8;
-            this.gravity.ButtonStepBig = 0.25F;
-            this.gravity.ButtonStepFloat = 0.1F;
-            this.gravity.ButtonStepSmall = 0.01F;
-            this.gravity.ButtonStepsUseModifierKeys = true;
-            this.gravity.ButtonStepsWrapAround = false;
-            this.gravity.Location = new System.Drawing.Point(68, 22);
-            this.gravity.Name = "gravity";
-            this.gravity.Size = new System.Drawing.Size(72, 24);
-            this.gravity.StepValues = null;
-            this.gravity.TabIndex = 0;
-            // 
-            // grouprendering
-            // 
-            this.grouprendering.Controls.Add(this.resetalpha);
-            this.grouprendering.Controls.Add(this.labelScale);
-            this.grouprendering.Controls.Add(this.scale);
-            this.grouprendering.Controls.Add(this.color);
-            this.grouprendering.Controls.Add(this.alpha);
-            this.grouprendering.Controls.Add(this.label8);
-            this.grouprendering.Controls.Add(this.renderStyle);
-            this.grouprendering.Controls.Add(this.labelrenderstyle);
-            this.grouprendering.Location = new System.Drawing.Point(3, 6);
-            this.grouprendering.Name = "grouprendering";
-            this.grouprendering.Size = new System.Drawing.Size(276, 158);
-            this.grouprendering.TabIndex = 22;
-            this.grouprendering.TabStop = false;
-            this.grouprendering.Text = " Rendering ";
-            // 
-            // resetalpha
-            // 
-            this.resetalpha.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
-            this.resetalpha.Location = new System.Drawing.Point(166, 86);
-            this.resetalpha.Name = "resetalpha";
-            this.resetalpha.Size = new System.Drawing.Size(23, 23);
-            this.resetalpha.TabIndex = 69;
-            this.tooltip.SetToolTip(this.resetalpha, "Reset");
-            this.resetalpha.UseVisualStyleBackColor = true;
-            this.resetalpha.Click += new System.EventHandler(this.resetalpha_Click);
-            // 
-            // labelScale
-            // 
-            this.labelScale.Location = new System.Drawing.Point(5, 27);
-            this.labelScale.Name = "labelScale";
-            this.labelScale.Size = new System.Drawing.Size(80, 14);
-            this.labelScale.TabIndex = 32;
-            this.labelScale.Text = "Scale:";
-            this.labelScale.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
-            // 
-            // scale
-            // 
-            this.scale.ButtonStep = 0.1F;
-            this.scale.ButtonStepBig = 0.25F;
-            this.scale.ButtonStepSmall = 0.01F;
-            this.scale.ButtonStepsUseModifierKeys = true;
-            this.scale.DefaultValue = 1F;
-            this.scale.LinkValues = false;
-            this.scale.Location = new System.Drawing.Point(89, 22);
-            this.scale.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
-            this.scale.Name = "scale";
-            this.scale.Size = new System.Drawing.Size(186, 26);
-            this.scale.TabIndex = 0;
-            this.scale.OnValuesChanged += new System.EventHandler(this.scale_OnValuesChanged);
-            // 
-            // color
-            // 
-            this.color.DefaultValue = 0;
-            this.color.Field = "fillcolor";
-            this.color.Label = "Color:";
-            this.color.Location = new System.Drawing.Point(22, 115);
-            this.color.Name = "color";
-            this.color.Size = new System.Drawing.Size(207, 31);
-            this.color.TabIndex = 3;
-            // 
-            // alpha
-            // 
-            this.alpha.AllowDecimal = true;
-            this.alpha.AllowExpressions = false;
-            this.alpha.AllowNegative = true;
-            this.alpha.AllowRelative = false;
-            this.alpha.ButtonStep = 8;
-            this.alpha.ButtonStepBig = 0.25F;
-            this.alpha.ButtonStepFloat = 0.1F;
-            this.alpha.ButtonStepSmall = 0.01F;
-            this.alpha.ButtonStepsUseModifierKeys = true;
-            this.alpha.ButtonStepsWrapAround = false;
-            this.alpha.Location = new System.Drawing.Point(91, 85);
-            this.alpha.Name = "alpha";
-            this.alpha.Size = new System.Drawing.Size(72, 24);
-            this.alpha.StepValues = null;
-            this.alpha.TabIndex = 2;
-            this.alpha.WhenTextChanged += new System.EventHandler(this.alpha_WhenTextChanged);
-            // 
-            // label8
-            // 
-            this.label8.AutoSize = true;
-            this.label8.Location = new System.Drawing.Point(48, 89);
-            this.label8.Name = "label8";
-            this.label8.Size = new System.Drawing.Size(37, 13);
-            this.label8.TabIndex = 25;
-            this.label8.Text = "Alpha:";
-            // 
-            // renderStyle
-            // 
-            this.renderStyle.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-            this.renderStyle.FormattingEnabled = true;
-            this.renderStyle.Location = new System.Drawing.Point(91, 57);
-            this.renderStyle.Name = "renderStyle";
-            this.renderStyle.Size = new System.Drawing.Size(156, 21);
-            this.renderStyle.TabIndex = 1;
-            this.renderStyle.SelectedIndexChanged += new System.EventHandler(this.renderStyle_SelectedIndexChanged);
-            // 
-            // labelrenderstyle
-            // 
-            this.labelrenderstyle.AutoSize = true;
-            this.labelrenderstyle.Location = new System.Drawing.Point(16, 60);
-            this.labelrenderstyle.Name = "labelrenderstyle";
-            this.labelrenderstyle.Size = new System.Drawing.Size(69, 13);
-            this.labelrenderstyle.TabIndex = 23;
-            this.labelrenderstyle.Text = "Render style:";
-            // 
-            // actiongroup
-            // 
-            this.actiongroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+			// 
+			// score
+			// 
+			this.score.AllowDecimal = false;
+			this.score.AllowExpressions = false;
+			this.score.AllowNegative = false;
+			this.score.AllowRelative = false;
+			this.score.ButtonStep = 8;
+			this.score.ButtonStepBig = 16F;
+			this.score.ButtonStepFloat = 0.1F;
+			this.score.ButtonStepSmall = 1F;
+			this.score.ButtonStepsUseModifierKeys = true;
+			this.score.ButtonStepsWrapAround = false;
+			this.score.Location = new System.Drawing.Point(68, 52);
+			this.score.Name = "score";
+			this.score.Size = new System.Drawing.Size(72, 24);
+			this.score.StepValues = null;
+			this.score.TabIndex = 1;
+			// 
+			// label9
+			// 
+			this.label9.AutoSize = true;
+			this.label9.Location = new System.Drawing.Point(24, 57);
+			this.label9.Name = "label9";
+			this.label9.Size = new System.Drawing.Size(38, 13);
+			this.label9.TabIndex = 20;
+			this.label9.Text = "Score:";
+			// 
+			// gravity
+			// 
+			this.gravity.AllowDecimal = true;
+			this.gravity.AllowExpressions = false;
+			this.gravity.AllowNegative = true;
+			this.gravity.AllowRelative = false;
+			this.gravity.ButtonStep = 8;
+			this.gravity.ButtonStepBig = 0.25F;
+			this.gravity.ButtonStepFloat = 0.1F;
+			this.gravity.ButtonStepSmall = 0.01F;
+			this.gravity.ButtonStepsUseModifierKeys = true;
+			this.gravity.ButtonStepsWrapAround = false;
+			this.gravity.Location = new System.Drawing.Point(68, 22);
+			this.gravity.Name = "gravity";
+			this.gravity.Size = new System.Drawing.Size(72, 24);
+			this.gravity.StepValues = null;
+			this.gravity.TabIndex = 0;
+			// 
+			// grouprendering
+			// 
+			this.grouprendering.Controls.Add(this.resetalpha);
+			this.grouprendering.Controls.Add(this.labelScale);
+			this.grouprendering.Controls.Add(this.scale);
+			this.grouprendering.Controls.Add(this.color);
+			this.grouprendering.Controls.Add(this.alpha);
+			this.grouprendering.Controls.Add(this.label8);
+			this.grouprendering.Controls.Add(this.renderStyle);
+			this.grouprendering.Controls.Add(this.labelrenderstyle);
+			this.grouprendering.Location = new System.Drawing.Point(3, 6);
+			this.grouprendering.Name = "grouprendering";
+			this.grouprendering.Size = new System.Drawing.Size(276, 158);
+			this.grouprendering.TabIndex = 22;
+			this.grouprendering.TabStop = false;
+			this.grouprendering.Text = " Rendering ";
+			// 
+			// resetalpha
+			// 
+			this.resetalpha.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
+			this.resetalpha.Location = new System.Drawing.Point(166, 86);
+			this.resetalpha.Name = "resetalpha";
+			this.resetalpha.Size = new System.Drawing.Size(23, 23);
+			this.resetalpha.TabIndex = 69;
+			this.tooltip.SetToolTip(this.resetalpha, "Reset");
+			this.resetalpha.UseVisualStyleBackColor = true;
+			this.resetalpha.Click += new System.EventHandler(this.resetalpha_Click);
+			// 
+			// labelScale
+			// 
+			this.labelScale.Location = new System.Drawing.Point(5, 27);
+			this.labelScale.Name = "labelScale";
+			this.labelScale.Size = new System.Drawing.Size(80, 14);
+			this.labelScale.TabIndex = 32;
+			this.labelScale.Text = "Scale:";
+			this.labelScale.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+			// 
+			// scale
+			// 
+			this.scale.ButtonStep = 0.1F;
+			this.scale.ButtonStepBig = 0.25F;
+			this.scale.ButtonStepSmall = 0.01F;
+			this.scale.ButtonStepsUseModifierKeys = true;
+			this.scale.DefaultValue = 1D;
+			this.scale.LinkValues = false;
+			this.scale.Location = new System.Drawing.Point(89, 22);
+			this.scale.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
+			this.scale.Name = "scale";
+			this.scale.Size = new System.Drawing.Size(186, 26);
+			this.scale.TabIndex = 0;
+			this.scale.OnValuesChanged += new System.EventHandler(this.scale_OnValuesChanged);
+			// 
+			// color
+			// 
+			this.color.DefaultValue = 0;
+			this.color.Field = "fillcolor";
+			this.color.Label = "Color:";
+			this.color.Location = new System.Drawing.Point(22, 115);
+			this.color.Name = "color";
+			this.color.Size = new System.Drawing.Size(207, 31);
+			this.color.TabIndex = 3;
+			// 
+			// alpha
+			// 
+			this.alpha.AllowDecimal = true;
+			this.alpha.AllowExpressions = false;
+			this.alpha.AllowNegative = true;
+			this.alpha.AllowRelative = false;
+			this.alpha.ButtonStep = 8;
+			this.alpha.ButtonStepBig = 0.25F;
+			this.alpha.ButtonStepFloat = 0.1F;
+			this.alpha.ButtonStepSmall = 0.01F;
+			this.alpha.ButtonStepsUseModifierKeys = true;
+			this.alpha.ButtonStepsWrapAround = false;
+			this.alpha.Location = new System.Drawing.Point(91, 85);
+			this.alpha.Name = "alpha";
+			this.alpha.Size = new System.Drawing.Size(72, 24);
+			this.alpha.StepValues = null;
+			this.alpha.TabIndex = 2;
+			this.alpha.WhenTextChanged += new System.EventHandler(this.alpha_WhenTextChanged);
+			// 
+			// label8
+			// 
+			this.label8.AutoSize = true;
+			this.label8.Location = new System.Drawing.Point(48, 89);
+			this.label8.Name = "label8";
+			this.label8.Size = new System.Drawing.Size(37, 13);
+			this.label8.TabIndex = 25;
+			this.label8.Text = "Alpha:";
+			// 
+			// renderStyle
+			// 
+			this.renderStyle.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+			this.renderStyle.FormattingEnabled = true;
+			this.renderStyle.Location = new System.Drawing.Point(91, 57);
+			this.renderStyle.Name = "renderStyle";
+			this.renderStyle.Size = new System.Drawing.Size(156, 21);
+			this.renderStyle.TabIndex = 1;
+			this.renderStyle.SelectedIndexChanged += new System.EventHandler(this.renderStyle_SelectedIndexChanged);
+			// 
+			// labelrenderstyle
+			// 
+			this.labelrenderstyle.AutoSize = true;
+			this.labelrenderstyle.Location = new System.Drawing.Point(16, 60);
+			this.labelrenderstyle.Name = "labelrenderstyle";
+			this.labelrenderstyle.Size = new System.Drawing.Size(69, 13);
+			this.labelrenderstyle.TabIndex = 23;
+			this.labelrenderstyle.Text = "Render style:";
+			// 
+			// actiongroup
+			// 
+			this.actiongroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-            this.actiongroup.Controls.Add(this.argscontrol);
-            this.actiongroup.Controls.Add(this.actionhelp);
-            this.actiongroup.Controls.Add(this.label7);
-            this.actiongroup.Controls.Add(this.action);
-            this.actiongroup.Controls.Add(this.browseaction);
-            this.actiongroup.Location = new System.Drawing.Point(3, 170);
-            this.actiongroup.Name = "actiongroup";
-            this.actiongroup.Size = new System.Drawing.Size(615, 154);
-            this.actiongroup.TabIndex = 22;
-            this.actiongroup.TabStop = false;
-            this.actiongroup.Text = " Action ";
-            // 
-            // argscontrol
-            // 
-            this.argscontrol.Location = new System.Drawing.Point(6, 56);
-            this.argscontrol.Name = "argscontrol";
-            this.argscontrol.Size = new System.Drawing.Size(603, 80);
-            this.argscontrol.TabIndex = 15;
-            // 
-            // actionhelp
-            // 
-            this.actionhelp.Location = new System.Drawing.Point(581, 24);
-            this.actionhelp.Name = "actionhelp";
-            this.actionhelp.Size = new System.Drawing.Size(28, 26);
-            this.actionhelp.TabIndex = 14;
-            // 
-            // action
-            // 
-            this.action.BackColor = System.Drawing.SystemColors.Control;
-            this.action.Cursor = System.Windows.Forms.Cursors.Default;
-            this.action.Empty = false;
-            this.action.GeneralizedCategories = null;
-            this.action.GeneralizedOptions = null;
-            this.action.Location = new System.Drawing.Point(62, 27);
-            this.action.Name = "action";
-            this.action.Size = new System.Drawing.Size(485, 21);
-            this.action.TabIndex = 0;
-            this.action.Value = 402;
-            this.action.ValueChanges += new System.EventHandler(this.action_ValueChanges);
-            // 
-            // browseaction
-            // 
-            this.browseaction.Image = global::CodeImp.DoomBuilder.Properties.Resources.List;
-            this.browseaction.Location = new System.Drawing.Point(551, 24);
-            this.browseaction.Name = "browseaction";
-            this.browseaction.Size = new System.Drawing.Size(28, 26);
-            this.browseaction.TabIndex = 1;
-            this.browseaction.Text = " ";
-            this.tooltip.SetToolTip(this.browseaction, "Browse Action");
-            this.browseaction.UseVisualStyleBackColor = true;
-            this.browseaction.Click += new System.EventHandler(this.browseaction_Click);
-            // 
-            // grouptag
-            // 
-            this.grouptag.Controls.Add(this.tagSelector);
-            this.grouptag.Location = new System.Drawing.Point(3, 330);
-            this.grouptag.Name = "grouptag";
-            this.grouptag.Size = new System.Drawing.Size(615, 66);
-            this.grouptag.TabIndex = 0;
-            this.grouptag.TabStop = false;
-            this.grouptag.Text = " Identification ";
-            // 
-            // tagSelector
-            // 
-            this.tagSelector.Location = new System.Drawing.Point(6, 21);
-            this.tagSelector.Name = "tagSelector";
-            this.tagSelector.Size = new System.Drawing.Size(603, 35);
-            this.tagSelector.TabIndex = 8;
-            // 
-            // tabcomment
-            // 
-            this.tabcomment.Controls.Add(this.commenteditor);
-            this.tabcomment.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.tabcomment.Location = new System.Drawing.Point(4, 24);
-            this.tabcomment.Name = "tabcomment";
-            this.tabcomment.Size = new System.Drawing.Size(627, 401);
-            this.tabcomment.TabIndex = 3;
-            this.tabcomment.Text = "Comment";
-            this.tabcomment.UseVisualStyleBackColor = true;
-            // 
-            // commenteditor
-            // 
-            this.commenteditor.Location = new System.Drawing.Point(3, 3);
-            this.commenteditor.Name = "commenteditor";
-            this.commenteditor.Size = new System.Drawing.Size(621, 396);
-            this.commenteditor.TabIndex = 0;
-            // 
-            // tabcustom
-            // 
-            this.tabcustom.Controls.Add(this.hidefixedfields);
-            this.tabcustom.Controls.Add(this.fieldslist);
-            this.tabcustom.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
-            this.tabcustom.Location = new System.Drawing.Point(4, 24);
-            this.tabcustom.Name = "tabcustom";
-            this.tabcustom.Size = new System.Drawing.Size(627, 401);
-            this.tabcustom.TabIndex = 2;
-            this.tabcustom.Text = "Custom";
-            this.tabcustom.UseVisualStyleBackColor = true;
-            this.tabcustom.MouseEnter += new System.EventHandler(this.tabcustom_MouseEnter);
-            // 
-            // hidefixedfields
-            // 
-            this.hidefixedfields.AutoSize = true;
-            this.hidefixedfields.Location = new System.Drawing.Point(10, 381);
-            this.hidefixedfields.Name = "hidefixedfields";
-            this.hidefixedfields.Size = new System.Drawing.Size(195, 17);
-            this.hidefixedfields.TabIndex = 2;
-            this.hidefixedfields.Text = "Show user-added custom fields only";
-            this.hidefixedfields.UseVisualStyleBackColor = true;
-            this.hidefixedfields.CheckedChanged += new System.EventHandler(this.hidefixedfields_CheckedChanged);
-            // 
-            // fieldslist
-            // 
-            this.fieldslist.AllowInsert = true;
-            this.fieldslist.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+			this.actiongroup.Controls.Add(this.argscontrol);
+			this.actiongroup.Controls.Add(this.actionhelp);
+			this.actiongroup.Controls.Add(this.label7);
+			this.actiongroup.Controls.Add(this.action);
+			this.actiongroup.Controls.Add(this.browseaction);
+			this.actiongroup.Location = new System.Drawing.Point(3, 170);
+			this.actiongroup.Name = "actiongroup";
+			this.actiongroup.Size = new System.Drawing.Size(615, 154);
+			this.actiongroup.TabIndex = 22;
+			this.actiongroup.TabStop = false;
+			this.actiongroup.Text = " Action ";
+			// 
+			// argscontrol
+			// 
+			this.argscontrol.Location = new System.Drawing.Point(6, 56);
+			this.argscontrol.Name = "argscontrol";
+			this.argscontrol.Size = new System.Drawing.Size(603, 80);
+			this.argscontrol.TabIndex = 15;
+			// 
+			// actionhelp
+			// 
+			this.actionhelp.Location = new System.Drawing.Point(581, 24);
+			this.actionhelp.Name = "actionhelp";
+			this.actionhelp.Size = new System.Drawing.Size(28, 26);
+			this.actionhelp.TabIndex = 14;
+			// 
+			// action
+			// 
+			this.action.BackColor = System.Drawing.SystemColors.Control;
+			this.action.Cursor = System.Windows.Forms.Cursors.Default;
+			this.action.Empty = false;
+			this.action.GeneralizedCategories = null;
+			this.action.GeneralizedOptions = null;
+			this.action.Location = new System.Drawing.Point(62, 27);
+			this.action.Name = "action";
+			this.action.Size = new System.Drawing.Size(485, 21);
+			this.action.TabIndex = 0;
+			this.action.Value = 402;
+			this.action.ValueChanges += new System.EventHandler(this.action_ValueChanges);
+			// 
+			// browseaction
+			// 
+			this.browseaction.Image = global::CodeImp.DoomBuilder.Properties.Resources.List;
+			this.browseaction.Location = new System.Drawing.Point(551, 24);
+			this.browseaction.Name = "browseaction";
+			this.browseaction.Size = new System.Drawing.Size(28, 26);
+			this.browseaction.TabIndex = 1;
+			this.browseaction.Text = " ";
+			this.tooltip.SetToolTip(this.browseaction, "Browse Action");
+			this.browseaction.UseVisualStyleBackColor = true;
+			this.browseaction.Click += new System.EventHandler(this.browseaction_Click);
+			// 
+			// grouptag
+			// 
+			this.grouptag.Controls.Add(this.tagSelector);
+			this.grouptag.Location = new System.Drawing.Point(3, 330);
+			this.grouptag.Name = "grouptag";
+			this.grouptag.Size = new System.Drawing.Size(615, 66);
+			this.grouptag.TabIndex = 0;
+			this.grouptag.TabStop = false;
+			this.grouptag.Text = " Identification ";
+			// 
+			// tagSelector
+			// 
+			this.tagSelector.Location = new System.Drawing.Point(6, 21);
+			this.tagSelector.Name = "tagSelector";
+			this.tagSelector.Size = new System.Drawing.Size(603, 35);
+			this.tagSelector.TabIndex = 8;
+			// 
+			// tabcomment
+			// 
+			this.tabcomment.Controls.Add(this.commenteditor);
+			this.tabcomment.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.tabcomment.Location = new System.Drawing.Point(4, 22);
+			this.tabcomment.Name = "tabcomment";
+			this.tabcomment.Size = new System.Drawing.Size(627, 399);
+			this.tabcomment.TabIndex = 3;
+			this.tabcomment.Text = "Comment";
+			this.tabcomment.UseVisualStyleBackColor = true;
+			// 
+			// commenteditor
+			// 
+			this.commenteditor.Location = new System.Drawing.Point(3, 3);
+			this.commenteditor.Name = "commenteditor";
+			this.commenteditor.Size = new System.Drawing.Size(621, 396);
+			this.commenteditor.TabIndex = 0;
+			// 
+			// tabcustom
+			// 
+			this.tabcustom.Controls.Add(this.hidefixedfields);
+			this.tabcustom.Controls.Add(this.fieldslist);
+			this.tabcustom.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			this.tabcustom.Location = new System.Drawing.Point(4, 22);
+			this.tabcustom.Name = "tabcustom";
+			this.tabcustom.Size = new System.Drawing.Size(627, 399);
+			this.tabcustom.TabIndex = 2;
+			this.tabcustom.Text = "Custom";
+			this.tabcustom.UseVisualStyleBackColor = true;
+			this.tabcustom.MouseEnter += new System.EventHandler(this.tabcustom_MouseEnter);
+			// 
+			// hidefixedfields
+			// 
+			this.hidefixedfields.AutoSize = true;
+			this.hidefixedfields.Location = new System.Drawing.Point(10, 381);
+			this.hidefixedfields.Name = "hidefixedfields";
+			this.hidefixedfields.Size = new System.Drawing.Size(195, 17);
+			this.hidefixedfields.TabIndex = 2;
+			this.hidefixedfields.Text = "Show user-added custom fields only";
+			this.hidefixedfields.UseVisualStyleBackColor = true;
+			this.hidefixedfields.CheckedChanged += new System.EventHandler(this.hidefixedfields_CheckedChanged);
+			// 
+			// fieldslist
+			// 
+			this.fieldslist.AllowInsert = true;
+			this.fieldslist.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
             | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
-            this.fieldslist.AutoInsertUserPrefix = true;
-            this.fieldslist.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
-            this.fieldslist.Location = new System.Drawing.Point(8, 9);
-            this.fieldslist.Margin = new System.Windows.Forms.Padding(8, 9, 8, 9);
-            this.fieldslist.Name = "fieldslist";
-            this.fieldslist.PropertyColumnVisible = true;
-            this.fieldslist.PropertyColumnWidth = 150;
-            this.fieldslist.ShowFixedFields = true;
-            this.fieldslist.Size = new System.Drawing.Size(611, 368);
-            this.fieldslist.TabIndex = 1;
-            this.fieldslist.TypeColumnVisible = true;
-            this.fieldslist.TypeColumnWidth = 100;
-            this.fieldslist.ValueColumnVisible = true;
-            // 
-            // cancel
-            // 
-            this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-            this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
-            this.cancel.Location = new System.Drawing.Point(533, 442);
-            this.cancel.Name = "cancel";
-            this.cancel.Size = new System.Drawing.Size(112, 25);
-            this.cancel.TabIndex = 2;
-            this.cancel.Text = "Cancel";
-            this.cancel.UseVisualStyleBackColor = true;
-            this.cancel.Click += new System.EventHandler(this.cancel_Click);
-            // 
-            // apply
-            // 
-            this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
-            this.apply.Location = new System.Drawing.Point(415, 442);
-            this.apply.Name = "apply";
-            this.apply.Size = new System.Drawing.Size(112, 25);
-            this.apply.TabIndex = 1;
-            this.apply.Text = "OK";
-            this.apply.UseVisualStyleBackColor = true;
-            this.apply.Click += new System.EventHandler(this.apply_Click);
-            // 
-            // hint
-            // 
-            this.hint.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-            this.hint.Image = global::CodeImp.DoomBuilder.Properties.Resources.Lightbulb;
-            this.hint.Location = new System.Drawing.Point(10, 446);
-            this.hint.Name = "hint";
-            this.hint.Size = new System.Drawing.Size(16, 16);
-            this.hint.TabIndex = 3;
-            this.hint.TabStop = false;
-            // 
-            // hintlabel
-            // 
-            this.hintlabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
-            this.hintlabel.AutoSize = true;
-            this.hintlabel.Location = new System.Drawing.Point(24, 447);
-            this.hintlabel.Name = "hintlabel";
-            this.hintlabel.Size = new System.Drawing.Size(365, 13);
-            this.hintlabel.TabIndex = 4;
-            this.hintlabel.Text = "Select categories or several thing types to randomly assign them to selection";
-            // 
-            // tooltip
-            // 
-            this.tooltip.AutomaticDelay = 10;
-            this.tooltip.AutoPopDelay = 10000;
-            this.tooltip.InitialDelay = 10;
-            this.tooltip.ReshowDelay = 100;
-            // 
-            // ThingEditFormUDMF
-            // 
-            this.AcceptButton = this.apply;
-            this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
-            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-            this.CancelButton = this.cancel;
-            this.ClientSize = new System.Drawing.Size(655, 470);
-            this.Controls.Add(this.hint);
-            this.Controls.Add(this.cancel);
-            this.Controls.Add(this.apply);
-            this.Controls.Add(this.hintlabel);
-            this.Controls.Add(this.tabs);
-            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
-            this.MaximizeBox = false;
-            this.MinimizeBox = false;
-            this.Name = "ThingEditFormUDMF";
-            this.Opacity = 0D;
-            this.ShowIcon = false;
-            this.ShowInTaskbar = false;
-            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
-            this.Text = "Edit Thing";
-            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ThingEditForm_FormClosing);
-            this.Shown += new System.EventHandler(this.ThingEditFormUDMF_Shown);
-            this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.ThingEditForm_HelpRequested);
-            this.groupBox1.ResumeLayout(false);
-            this.groupBox2.ResumeLayout(false);
-            this.groupBox2.PerformLayout();
-            this.tabs.ResumeLayout(false);
-            this.tabproperties.ResumeLayout(false);
-            this.settingsgroup.ResumeLayout(false);
-            ((System.ComponentModel.ISupportInitialize)(this.missingflags)).EndInit();
-            this.grouproll.ResumeLayout(false);
-            this.grouppitch.ResumeLayout(false);
-            this.groupangle.ResumeLayout(false);
-            this.groupBox4.ResumeLayout(false);
-            this.groupBox4.PerformLayout();
-            this.tabeffects.ResumeLayout(false);
-            this.groupbehaviour.ResumeLayout(false);
-            this.groupbehaviour.PerformLayout();
-            this.grouprendering.ResumeLayout(false);
-            this.grouprendering.PerformLayout();
-            this.actiongroup.ResumeLayout(false);
-            this.actiongroup.PerformLayout();
-            this.grouptag.ResumeLayout(false);
-            this.tabcomment.ResumeLayout(false);
-            this.tabcustom.ResumeLayout(false);
-            this.tabcustom.PerformLayout();
-            ((System.ComponentModel.ISupportInitialize)(this.hint)).EndInit();
-            this.ResumeLayout(false);
-            this.PerformLayout();
+			this.fieldslist.AutoInsertUserPrefix = true;
+			this.fieldslist.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+			this.fieldslist.Location = new System.Drawing.Point(8, 9);
+			this.fieldslist.Margin = new System.Windows.Forms.Padding(8, 9, 8, 9);
+			this.fieldslist.Name = "fieldslist";
+			this.fieldslist.PropertyColumnVisible = true;
+			this.fieldslist.PropertyColumnWidth = 150;
+			this.fieldslist.ShowFixedFields = true;
+			this.fieldslist.Size = new System.Drawing.Size(611, 368);
+			this.fieldslist.TabIndex = 1;
+			this.fieldslist.TypeColumnVisible = true;
+			this.fieldslist.TypeColumnWidth = 100;
+			this.fieldslist.ValueColumnVisible = true;
+			// 
+			// cancel
+			// 
+			this.cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+			this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+			this.cancel.Location = new System.Drawing.Point(533, 442);
+			this.cancel.Name = "cancel";
+			this.cancel.Size = new System.Drawing.Size(112, 25);
+			this.cancel.TabIndex = 2;
+			this.cancel.Text = "Cancel";
+			this.cancel.UseVisualStyleBackColor = true;
+			this.cancel.Click += new System.EventHandler(this.cancel_Click);
+			// 
+			// apply
+			// 
+			this.apply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+			this.apply.Location = new System.Drawing.Point(415, 442);
+			this.apply.Name = "apply";
+			this.apply.Size = new System.Drawing.Size(112, 25);
+			this.apply.TabIndex = 1;
+			this.apply.Text = "OK";
+			this.apply.UseVisualStyleBackColor = true;
+			this.apply.Click += new System.EventHandler(this.apply_Click);
+			// 
+			// hint
+			// 
+			this.hint.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+			this.hint.Image = global::CodeImp.DoomBuilder.Properties.Resources.Lightbulb;
+			this.hint.Location = new System.Drawing.Point(10, 446);
+			this.hint.Name = "hint";
+			this.hint.Size = new System.Drawing.Size(16, 16);
+			this.hint.TabIndex = 3;
+			this.hint.TabStop = false;
+			// 
+			// hintlabel
+			// 
+			this.hintlabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+			this.hintlabel.AutoSize = true;
+			this.hintlabel.Location = new System.Drawing.Point(24, 447);
+			this.hintlabel.Name = "hintlabel";
+			this.hintlabel.Size = new System.Drawing.Size(365, 13);
+			this.hintlabel.TabIndex = 4;
+			this.hintlabel.Text = "Select categories or several thing types to randomly assign them to selection";
+			// 
+			// tooltip
+			// 
+			this.tooltip.AutomaticDelay = 10;
+			this.tooltip.AutoPopDelay = 10000;
+			this.tooltip.InitialDelay = 10;
+			this.tooltip.ReshowDelay = 100;
+			// 
+			// ThingEditFormUDMF
+			// 
+			this.AcceptButton = this.apply;
+			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
+			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+			this.CancelButton = this.cancel;
+			this.ClientSize = new System.Drawing.Size(655, 470);
+			this.Controls.Add(this.hint);
+			this.Controls.Add(this.cancel);
+			this.Controls.Add(this.apply);
+			this.Controls.Add(this.hintlabel);
+			this.Controls.Add(this.tabs);
+			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+			this.MaximizeBox = false;
+			this.MinimizeBox = false;
+			this.Name = "ThingEditFormUDMF";
+			this.Opacity = 0D;
+			this.ShowIcon = false;
+			this.ShowInTaskbar = false;
+			this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+			this.Text = "Edit Thing";
+			this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ThingEditForm_FormClosing);
+			this.Shown += new System.EventHandler(this.ThingEditFormUDMF_Shown);
+			this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.ThingEditForm_HelpRequested);
+			this.groupBox1.ResumeLayout(false);
+			this.groupBox2.ResumeLayout(false);
+			this.groupBox2.PerformLayout();
+			this.tabs.ResumeLayout(false);
+			this.tabproperties.ResumeLayout(false);
+			this.settingsgroup.ResumeLayout(false);
+			((System.ComponentModel.ISupportInitialize)(this.missingflags)).EndInit();
+			this.grouproll.ResumeLayout(false);
+			this.grouppitch.ResumeLayout(false);
+			this.groupangle.ResumeLayout(false);
+			this.groupBox4.ResumeLayout(false);
+			this.groupBox4.PerformLayout();
+			this.tabeffects.ResumeLayout(false);
+			this.groupbehaviour.ResumeLayout(false);
+			this.groupbehaviour.PerformLayout();
+			this.grouprendering.ResumeLayout(false);
+			this.grouprendering.PerformLayout();
+			this.actiongroup.ResumeLayout(false);
+			this.actiongroup.PerformLayout();
+			this.grouptag.ResumeLayout(false);
+			this.tabcomment.ResumeLayout(false);
+			this.tabcustom.ResumeLayout(false);
+			this.tabcustom.PerformLayout();
+			((System.ComponentModel.ISupportInitialize)(this.hint)).EndInit();
+			this.ResumeLayout(false);
+			this.PerformLayout();
 
 		}
 
diff --git a/Source/Core/Windows/ThingEditFormUDMF.cs b/Source/Core/Windows/ThingEditFormUDMF.cs
index 5dfaf4ac2af7fcfb2e2934313b8fbb84149888de..7667584f10e52920a600514c3fa08774e5504573 100755
--- a/Source/Core/Windows/ThingEditFormUDMF.cs
+++ b/Source/Core/Windows/ThingEditFormUDMF.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Controls;
@@ -224,7 +225,7 @@ namespace CodeImp.DoomBuilder.Windows
 			floatbobphase.Text = ft.Fields.GetValue("floatbobphase", -1).ToString();
 			gravity.Text = ft.Fields.GetValue("gravity", 1.0).ToString();
 			score.Text = ft.Fields.GetValue("score", 0).ToString();
-			health.Text = ft.Fields.GetValue("health", 1).ToString();
+			health.Text = ft.Fields.GetValue("health", 1.0).ToString();
 			alpha.Text = ft.Fields.GetValue("alpha", 1.0).ToString();
 			color.SetValueFrom(ft.Fields, true);
 			scale.SetValues(ft.ScaleX, ft.ScaleY, true);
@@ -312,7 +313,7 @@ namespace CodeImp.DoomBuilder.Windows
 				if(t.Fields.GetValue("floatbobphase", -1).ToString() != floatbobphase.Text) floatbobphase.Text = "";
 				if(t.Fields.GetValue("gravity", 1.0).ToString() != gravity.Text) gravity.Text = "";
 				if(t.Fields.GetValue("score", 0).ToString() != score.Text) score.Text = "";
-				if(t.Fields.GetValue("health", 1).ToString() != health.Text) health.Text = "";
+				if(t.Fields.GetValue("health", 1.0).ToString() != health.Text) health.Text = "";
 				if(t.Fields.GetValue("alpha", 1.0).ToString() != alpha.Text) alpha.Text = "";
 
 				scale.SetValues(t.ScaleX, t.ScaleY, false);
@@ -329,6 +330,18 @@ namespace CodeImp.DoomBuilder.Windows
 				thingprops.Add(new ThingProperties(t));
 			}
 
+			// Remove unused thing type specific fields
+			foreach(UniversalFieldInfo ufi in  General.Map.Config.ThingFields)
+			{
+				if (!ufi.ThingTypeSpecific)
+					continue;
+
+				if(!things.Any(t => { ThingTypeInfo tti = General.Map.Data.GetThingInfoEx(t.Type); return (tti != null && tti.HasAddUniversalField(ufi.Name)); }))
+				{
+					fieldslist.RemoveField(ufi.Name);
+				}
+			}
+
 			preventchanges = false;
 
 			//mxd. Update "Reset" button
@@ -562,7 +575,7 @@ namespace CodeImp.DoomBuilder.Windows
 				if(!string.IsNullOrEmpty(gravity.Text))
 					UniFields.SetFloat(t.Fields, "gravity", gravity.GetResultFloat(t.Fields.GetValue("gravity", 1.0)), 1.0);
 				if(!string.IsNullOrEmpty(health.Text))
-					UniFields.SetInteger(t.Fields, "health", health.GetResult(t.Fields.GetValue("health", 1)), 1);
+					UniFields.SetFloat(t.Fields, "health", health.GetResultFloat(t.Fields.GetValue("health", 1.0)), 1.0);
 				if(!string.IsNullOrEmpty(score.Text))
 					UniFields.SetInteger(t.Fields, "score", score.GetResult(t.Fields.GetValue("score", 0)), 0);
 
@@ -571,9 +584,10 @@ namespace CodeImp.DoomBuilder.Windows
 				if (ti != null && ti.Actor != null)
 				{
 					Dictionary<string, UniversalType> uservars = ti.Actor.GetAllUserVars();
+					Dictionary<string, object> uservardefaults = ti.Actor.GetAllUserVarDefaults();
 
 					if(uservars.Count > 0)
-						fieldslist.ApplyUserVars(uservars, t.Fields);
+						fieldslist.ApplyUserVars(uservars, uservardefaults, t.Fields);
 				}
 
 				color.ApplyTo(t.Fields, t.Fields.GetValue("fillcolor", 0));
@@ -801,6 +815,21 @@ namespace CodeImp.DoomBuilder.Windows
 				t.UpdateConfiguration();
 			}
 
+			// Remove user vars (that have their default value) that do not belong to any selected thing
+			fieldslist.RemoveUserVarsWithDefaultValue();
+
+			// Set the user vars for the new thing
+			Thing ft = things.First();
+			ThingTypeInfo fti = General.Map.Data.GetThingInfoEx(ft.Type);
+			if (fti != null && fti.Actor != null)
+			{
+				Dictionary<string, UniversalType> uservars = fti.Actor.GetAllUserVars();
+				Dictionary<string, object> uservardefaults = fti.Actor.GetAllUserVarDefaults();
+
+				if (uservars.Count > 0)
+					fieldslist.SetUserVars(uservars, uservardefaults, ft.Fields, true);
+			}
+
 			UpdateFlagNames(); //mxd
 
 			General.Map.IsChanged = true;
@@ -982,7 +1011,10 @@ namespace CodeImp.DoomBuilder.Windows
 			{
 				foreach(Thing t in things)
 				{
-					double value = General.Clamp(alpha.GetResultFloat(t.Fields.GetValue("alpha", 1.0)), 0.0, 1.0);
+					// ZDRay static lights uses the alpha value for intensity, which can go higher than 1.0, so don't clamp the upper value.
+					// It doesn't look like UDB or GZDoom have problems with "normal" things having an alpha > 1.0
+					// TODO: clamp based on thing type info?
+					double value = General.Clamp(alpha.GetResultFloat(t.Fields.GetValue("alpha", 1.0)), 0.0, double.MaxValue);
 					UniFields.SetFloat(t.Fields, "alpha", value, 1.0);
 				}
 			}
diff --git a/Source/Core/ZDoom/ActorStructure.cs b/Source/Core/ZDoom/ActorStructure.cs
index b270703b635f17a8c6e86f00b5f381d59b26c091..16e88aa5a9f63a4da24fe161099c8170b51d599f 100755
--- a/Source/Core/ZDoom/ActorStructure.cs
+++ b/Source/Core/ZDoom/ActorStructure.cs
@@ -319,13 +319,21 @@ namespace CodeImp.DoomBuilder.ZDoom
 		/// </summary>
 		public bool CheckActorSupported()
 		{
-			// Check if we want to include this actor
-			string includegames = General.Map.Config.DecorateGames.ToLowerInvariant();
-			bool includeactor = (props["game"].Count == 0);
-			foreach(string g in props["game"])
-				includeactor |= includegames.Contains(g);
-			
-			return includeactor;
+			// Only check if a map is opened. Otherwise we run into problems with the resource checker
+			if (General.Map != null)
+			{
+				// Check if we want to include this actor
+				string includegames = General.Map.Config.DecorateGames.ToLowerInvariant();
+				bool includeactor = (props["game"].Count == 0);
+				foreach (string g in props["game"])
+					includeactor |= includegames.Contains(g);
+
+				return includeactor;
+			}
+			else
+			{
+				return true;
+			}
 		}
 		
 		/// <summary>
diff --git a/Source/Core/ZDoom/CvarInfoParser.cs b/Source/Core/ZDoom/CvarInfoParser.cs
index cdec9bbeee18ba696a624e1184106f30f7f5553a..d55f5137123b962ac8b9ad6b871df8f307e824db 100755
--- a/Source/Core/ZDoom/CvarInfoParser.cs
+++ b/Source/Core/ZDoom/CvarInfoParser.cs
@@ -52,86 +52,88 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			// Continue until at the end of the stream
 			HashSet<string> knowntypes = new HashSet<string> { "int", "float", "color", "bool", "string" };
-			HashSet<string> flags = new HashSet<string> { "noarchive", "cheat", "latch" };
+			HashSet<string> flags = new HashSet<string> { "user", "server", "nosave", "noarchive", "cheat", "latch" };
 			while(SkipWhitespace(true))
 			{
 				string token = ReadToken().ToLowerInvariant();
 				if(string.IsNullOrEmpty(token)) continue;
 
-				//<scope> [noarchive] [cheat] [latch] <type> <name> [= <defaultvalue>];
-				switch(token)
-				{
-					case "user":
-					case "server":
-					case "nosave":
-						// read (skip) flags
-						while(true)
-						{
-							string flagtoken;
+				// According to the ZDoom wikie (https://zdoom.org/wiki/CVARINFO) the format has to be
+				//   <scope> [noarchive] [cheat] [latch] <type> <name> [= <defaultvalue>];
+				// where <scope> is one of "user", "server", or "nosave". This it just the intended format, GZDoom actually
+				// accepts and combination of the scope variables (apparently for backwards compatibility), even when it
+				// doesn't make sense.
+				// See https://github.com/jewalky/UltimateDoomBuilder/issues/748
 
-							SkipWhitespace(true);
-							flagtoken = ReadToken().ToLowerInvariant();
-
-							if(!flags.Contains(flagtoken))
-							{
-								DataStream.Seek(-flagtoken.Length - 1, SeekOrigin.Current);
-								break;
-							}
-						}
+				if (flags.Contains(token))
+				{
+					// read (skip) flags
+					while (true)
+					{
+						string flagtoken;
 
-						// Type
 						SkipWhitespace(true);
-						string type = ReadToken().ToLowerInvariant();
+						flagtoken = ReadToken().ToLowerInvariant();
 
-						if(!knowntypes.Contains(type))
+						if (!flags.Contains(flagtoken))
 						{
-							ReportError("Unknown cvar type");
-							return false;
+							DataStream.Seek(-flagtoken.Length - 1, SeekOrigin.Current);
+							break;
 						}
+					}
 
-						// Name
-						SkipWhitespace(true);
-						string name = ReadToken();
+					// Next should be the type
+					SkipWhitespace(true);
+					string type = ReadToken().ToLowerInvariant();
 
-						if(string.IsNullOrEmpty(name))
-						{
-							ReportError("Expected cvar name");
-							return false;
-						}
+					if (!knowntypes.Contains(type))
+					{
+						ReportError($"Unknown token '{type}'. Expected type of " + string.Join(", ", knowntypes));
+						return false;
+					}
 
-						// Either "=" or ";"
-						SkipWhitespace(true);
-						token = ReadToken();
+					// Name
+					SkipWhitespace(true);
+					string name = ReadToken();
 
-						switch(token)
-						{
-							case "=":
-								SkipWhitespace(true);
-								string value = ReadToken();
-
-								if(string.IsNullOrEmpty(value))
-								{
-									ReportError("Expected \"" + name + "\" cvar value");
-									return false;
-								}
-
-								// Add to collection
-								if(!AddValue(name, type, value)) return false;
-
-								// Next should be ";"
-								if(!NextTokenIs(";")) return false;
-								break;
-
-							case ";":
-								if(!AddValue(name, type, string.Empty)) return false;
-								break;
-						}
+					if (string.IsNullOrEmpty(name))
+					{
+						ReportError("Expected cvar name");
+						return false;
+					}
 
-						break;
+					// Either "=" or ";"
+					SkipWhitespace(true);
+					token = ReadToken();
 
-					default:
-						ReportError("Unknown keyword");
-						return false;
+					switch (token)
+					{
+						case "=":
+							SkipWhitespace(true);
+							string value = ReadToken();
+
+							if (string.IsNullOrEmpty(value))
+							{
+								ReportError("Expected \"" + name + "\" cvar value");
+								return false;
+							}
+
+							// Add to collection
+							if (!AddValue(name, type, value)) return false;
+
+							// Next should be ";"
+							if (!NextTokenIs(";")) return false;
+							break;
+
+						case ";":
+							if (!AddValue(name, type, string.Empty)) return false;
+							break;
+					}
+				}
+				else
+				{
+					ReportError("Unknown keyword");
+					return false;
 				}
 			}
 
diff --git a/Source/Core/ZDoom/DecorateActorStructure.cs b/Source/Core/ZDoom/DecorateActorStructure.cs
index 10c72d441376392f710f6000feac7f89c39cd07a..e73b07d84a2b543058032f4af8ec3dbcaa457bee 100755
--- a/Source/Core/ZDoom/DecorateActorStructure.cs
+++ b/Source/Core/ZDoom/DecorateActorStructure.cs
@@ -106,7 +106,8 @@ namespace CodeImp.DoomBuilder.ZDoom
                             }
 
                             //mxd. Range check
-                            if ((doomednum < General.Map.FormatInterface.MinThingType) || (doomednum > General.Map.FormatInterface.MaxThingType))
+                            // Only check In a map is opened, otherwise we run into problems with the resource checker
+                            if (General.Map != null && ((doomednum < General.Map.FormatInterface.MinThingType) || (doomednum > General.Map.FormatInterface.MaxThingType)))
                             {
                                 // Out of bounds!
                                 parser.ReportError("Actor \"" + classname + "\" has invalid editor number. Editor number must be between "
@@ -394,60 +395,64 @@ namespace CodeImp.DoomBuilder.ZDoom
             // parsing done, process thing arguments
             ParseCustomArguments();
 
-            //mxd. Check if baseclass is valid
-            if (inheritclass.ToLowerInvariant() != "actor" && doomednum > -1)
+            // Only check if a map is opened. Otherwise we run into problems with the resource checker
+            if (General.Map != null)
             {
-                //check if this class inherits from a class defined in game configuration
-                Dictionary<int, ThingTypeInfo> things = General.Map.Config.GetThingTypes();
-                string inheritclasscheck = inheritclass.ToLowerInvariant();
-
-                foreach (KeyValuePair<int, ThingTypeInfo> ti in things)
+                //mxd. Check if baseclass is valid
+                if (inheritclass.ToLowerInvariant() != "actor" && doomednum > -1)
                 {
-                    if (!string.IsNullOrEmpty(ti.Value.ClassName) && ti.Value.ClassName.ToLowerInvariant() == inheritclasscheck)
-                    {
-                        //states
-                        // [ZZ] allow internal prefix here. it can inherit MapSpot, light, or other internal stuff.
-                        if (states.Count == 0 && !string.IsNullOrEmpty(ti.Value.Sprite))
-                            states.Add("spawn", new StateStructure(ti.Value.Sprite.StartsWith(DataManager.INTERNAL_PREFIX) ? ti.Value.Sprite : ti.Value.Sprite.Substring(0, 5)));
+                    //check if this class inherits from a class defined in game configuration
+                    Dictionary<int, ThingTypeInfo> things = General.Map.Config.GetThingTypes();
+                    string inheritclasscheck = inheritclass.ToLowerInvariant();
 
-                        if (baseclass == null)
+                    foreach (KeyValuePair<int, ThingTypeInfo> ti in things)
+                    {
+                        if (!string.IsNullOrEmpty(ti.Value.ClassName) && ti.Value.ClassName.ToLowerInvariant() == inheritclasscheck)
                         {
-                            //flags
-                            if (ti.Value.Hangs && !flags.ContainsKey("spawnceiling"))
-                                flags["spawnceiling"] = true;
+                            //states
+                            // [ZZ] allow internal prefix here. it can inherit MapSpot, light, or other internal stuff.
+                            if (states.Count == 0 && !string.IsNullOrEmpty(ti.Value.Sprite))
+                                states.Add("spawn", new StateStructure(ti.Value.Sprite.StartsWith(DataManager.INTERNAL_PREFIX) ? ti.Value.Sprite : ti.Value.Sprite.Substring(0, 5)));
+
+                            if (baseclass == null)
+                            {
+                                //flags
+                                if (ti.Value.Hangs && !flags.ContainsKey("spawnceiling"))
+                                    flags["spawnceiling"] = true;
 
-                            if (ti.Value.Blocking > 0 && !flags.ContainsKey("solid"))
-                                flags["solid"] = true;
+                                if (ti.Value.Blocking > 0 && !flags.ContainsKey("solid"))
+                                    flags["solid"] = true;
 
-                            //properties
-                            if (!props.ContainsKey("height"))
-                                props["height"] = new List<string> { ti.Value.Height.ToString() };
+                                //properties
+                                if (!props.ContainsKey("height"))
+                                    props["height"] = new List<string> { ti.Value.Height.ToString() };
 
-                            if (!props.ContainsKey("radius"))
-                                props["radius"] = new List<string> { ti.Value.Radius.ToString() };
-                        }
+                                if (!props.ContainsKey("radius"))
+                                    props["radius"] = new List<string> { ti.Value.Radius.ToString() };
+                            }
 
-                        // [ZZ] inherit arguments from game configuration
-                        //      
-                        if (!props.ContainsKey("$clearargs"))
-                        {
-                            for (int i = 0; i < 5; i++)
+                            // [ZZ] inherit arguments from game configuration
+                            //      
+                            if (!props.ContainsKey("$clearargs"))
                             {
-                                if (args[i] != null)
-                                    continue; // don't touch it if we already have overrides
+                                for (int i = 0; i < 5; i++)
+                                {
+                                    if (args[i] != null)
+                                        continue; // don't touch it if we already have overrides
 
-                                ArgumentInfo arg = ti.Value.Args[i];
-                                if (arg != null && arg.Used)
-                                    args[i] = arg;
+                                    ArgumentInfo arg = ti.Value.Args[i];
+                                    if (arg != null && arg.Used)
+                                        args[i] = arg;
+                                }
                             }
-                        }
 
-                        return;
+                            return;
+                        }
                     }
-                }
 
-                if (baseclass == null)
-                    parser.LogWarning("Unable to find \"" + inheritclass + "\" class to inherit from, while parsing \"" + classname + ":" + doomednum + "\"");
+                    if (baseclass == null)
+                        parser.LogWarning("Unable to find \"" + inheritclass + "\" class to inherit from, while parsing \"" + classname + ":" + doomednum + "\"");
+                }
             }
         }
 
diff --git a/Source/Core/ZDoom/DecorateParser.cs b/Source/Core/ZDoom/DecorateParser.cs
index b8cfb303cdd3e9976c8258f4046228c32bfb1e48..7ce74b5ff55d0eb1476b8c0fff4c93078a1489c1 100755
--- a/Source/Core/ZDoom/DecorateParser.cs
+++ b/Source/Core/ZDoom/DecorateParser.cs
@@ -35,7 +35,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		public delegate void IncludeDelegate(DecorateParser parser, string includefile);
 		
 		public IncludeDelegate OnInclude;
-		
+
 		#endregion
 		
 		#region ================== Constants
@@ -64,11 +64,14 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		//mxd. Disposing. Is that really needed?..
 		private bool isdisposed;
-		
+
+		//
+		public bool NoWarnings = false;
+
 		#endregion
-		
+
 		#region ================== Properties
-		
+
 		/// <summary>
 		/// All actors that are supported by the current game.
 		/// </summary>
@@ -93,10 +96,15 @@ namespace CodeImp.DoomBuilder.ZDoom
 		/// mxd. Custom DamageTypes (http://zdoom.org/wiki/Damage_types).
 		/// </summary>
 		public IEnumerable<string> DamageTypes { get { return damagetypes; } }
+
+		/// <summary>
+		/// This is used to find out what classes were parsed from specific archive
+		/// </summary>
+		public HashSet<string> LastClasses { get; internal set; }
 		#endregion
-		
+
 		#region ================== Constructor / Disposer
-		
+
 		// Constructor
 		public DecorateParser(Dictionary<string, ActorStructure> _zscriptactors)
 		{
@@ -124,15 +132,24 @@ namespace CodeImp.DoomBuilder.ZDoom
 				isdisposed = true;
 			}
 		}
-		
+
 		#endregion
 
 		#region ================== Parsing
 
+		protected internal override void LogWarning(string message, int linenumber)
+		{
+			if (NoWarnings)
+				return;
+			base.LogWarning(message, linenumber);
+		}
+
 		// This parses the given decorate stream
 		// Returns false on errors
 		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
+			if (clearerrors) LastClasses = new HashSet<string>();
+
 			//mxd. Already parsed?
 			if(!base.AddTextResource(data))
 			{
@@ -172,6 +189,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 							// Read actor structure
 							ActorStructure actor = new DecorateActorStructure(this, (regions.Count > 0 ? regions[regions.Count - 1] : null));
 							if(this.HasError) return false;
+
+							LastClasses.Add(actor.ClassName.ToLowerInvariant());
 						
 							// Add the actor
 							archivedactors[actor.ClassName.ToLowerInvariant()] = actor;
diff --git a/Source/Core/ZDoom/IWadInfoParser.cs b/Source/Core/ZDoom/IWadInfoParser.cs
index e5a6c7d88db741a0aa5ae15a31e5595995e757b7..93fc9bf675edf5cd4303f6c681eb607e43d01695 100644
--- a/Source/Core/ZDoom/IWadInfoParser.cs
+++ b/Source/Core/ZDoom/IWadInfoParser.cs
@@ -100,6 +100,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		/// Gets a pair of a key and multiple values.
 		/// The key value pair looks like this:
 		/// key = value1 [, value2 [, value3 [...] ] ]
+		/// The value(s) can also be omitted.
 		/// </summary>
 		/// <param name="key">The key</param>
 		/// <param name="values">The list of values</param>
@@ -115,10 +116,15 @@ namespace CodeImp.DoomBuilder.ZDoom
 			
 			key = ReadToken().ToLowerInvariant();
 
-			SkipWhitespace(true);
+			SkipWhitespace(false);
 
 			token = ReadToken().ToLowerInvariant();
 
+			if(token.Length > 0 && IsWhitespace(token[0]))
+			{
+				// Keys actually don't need a value
+				return true;
+			}
 			if(token != "=")
 			{
 				ReportError("Expected \"=\", but got \"" + token + "\"");
@@ -168,7 +174,10 @@ namespace CodeImp.DoomBuilder.ZDoom
 				switch(key)
 				{
 					case "autoname":
-						iwad.AutoName = values[0];
+						if (values.Count == 0)
+							ReportError("'autoname' property has no value");
+						else
+							iwad.AutoName = values[0];
 						break;
 				}
 			}
diff --git a/Source/Core/ZDoom/ModeldefParser.cs b/Source/Core/ZDoom/ModeldefParser.cs
index 8ce1fe2cc0cc8221a16cc58671fce26fcd7cb9bd..a06420d3acf363d8c5eb1df6de2c26f990713ade 100755
--- a/Source/Core/ZDoom/ModeldefParser.cs
+++ b/Source/Core/ZDoom/ModeldefParser.cs
@@ -45,6 +45,9 @@ namespace CodeImp.DoomBuilder.ZDoom
 			this.actorsbyclass = actorsbyclass;
 			this.entries = new Dictionary<string, ModelData>(StringComparer.OrdinalIgnoreCase);
             this.parsedlumps = new HashSet<string>();
+
+			// We don't want the '-' as a special token because commands can contain them (like "rotation-center")
+			specialtokens = ":{}+\n;";
 		}
 
 		#endregion
@@ -187,7 +190,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 								if(mds.Frames.ContainsKey(targetsprite))
 								{
 									// Create model data
-									ModelData md = new ModelData { InheritActorPitch = mds.InheritActorPitch, UseActorPitch = mds.UseActorPitch, UseActorRoll = mds.UseActorRoll, Path = mds.DataPath };
+									ModelData md = new ModelData { InheritActorPitch = mds.InheritActorPitch, UseActorPitch = mds.UseActorPitch, UseActorRoll = mds.UseActorRoll, UseRotationCenter = mds.UseRotationCenter, RotationCenter = mds.RotationCenter, Path = mds.DataPath };
 
 									// Things are complicated in GZDoom...
 									Matrix moffset = Matrix.Translation(mds.Offset.Y, -mds.Offset.X, mds.Offset.Z);
@@ -200,7 +203,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 									foreach(var fs in mds.Frames[targetsprite])
 									{
 										// Sanity checks
-										if(fs.ModelIndex >= mds.ModelNames.Count || string.IsNullOrEmpty(mds.ModelNames[fs.ModelIndex]))
+										if (!mds.ModelNames.ContainsKey(fs.ModelIndex) || string.IsNullOrEmpty(mds.ModelNames[fs.ModelIndex]))
 										{
 											LogWarning("Model definition \"" + classname + "\", frame \"" + fs.SpriteName + " " + fs.FrameName + "\" references undefined model index " + fs.ModelIndex);
 											continue;
diff --git a/Source/Core/ZDoom/ModeldefStructure.cs b/Source/Core/ZDoom/ModeldefStructure.cs
index 7d925cfbd8378be96923acf74e7463f15ffd0591..dee2d052d76146728849e1175b611bb927993d81 100755
--- a/Source/Core/ZDoom/ModeldefStructure.cs
+++ b/Source/Core/ZDoom/ModeldefStructure.cs
@@ -10,7 +10,7 @@ using CodeImp.DoomBuilder.Rendering;
 
 #endregion
 
-namespace CodeImp.DoomBuilder.ZDoom 
+namespace CodeImp.DoomBuilder.ZDoom
 {
 	internal sealed class ModeldefStructure
 	{
@@ -34,12 +34,14 @@ namespace CodeImp.DoomBuilder.ZDoom
 		private string path;
 		private Vector3f scale;
 		private Vector3f offset;
+		private Vector3f rotationcenter;
 		private float angleoffset;
 		private float pitchoffset;
 		private float rolloffset;
 		private bool inheritactorpitch;
 		private bool useactorpitch;
 		private bool useactorroll;
+		private bool userotationcenter;
 
 		private Dictionary<string, HashSet<FrameStructure>> frames;
 
@@ -52,12 +54,14 @@ namespace CodeImp.DoomBuilder.ZDoom
 		public Dictionary<int, string> ModelNames { get { return modelnames; } }
 		public Vector3f Scale { get { return scale; } }
 		public Vector3f Offset { get { return offset; } }
+		public Vector3f RotationCenter { get { return rotationcenter; } }
 		public float AngleOffset { get { return angleoffset; } }
 		public float PitchOffset { get { return pitchoffset; } }
 		public float RollOffset { get { return rolloffset; } }
 		public bool InheritActorPitch { get { return inheritactorpitch; } }
 		public bool UseActorPitch { get { return useactorpitch; } }
 		public bool UseActorRoll { get { return useactorroll; } }
+		public bool UseRotationCenter { get { return userotationcenter; } }
 		public string DataPath { get { return path; } } // biwa
 
 		public Dictionary<string, HashSet<FrameStructure>> Frames { get { return frames; } }
@@ -84,7 +88,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		{
 			// Read modeldef structure contents
 			bool parsingfinished = false;
-			while(!parsingfinished && parser.SkipWhitespace(true)) 
+			while(!parsingfinished && parser.SkipWhitespace(true))
 			{
 				string token = parser.ReadToken().ToLowerInvariant();
 				if(string.IsNullOrEmpty(token)) continue;
@@ -125,24 +129,24 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 						// Model path
 						token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
-						if(string.IsNullOrEmpty(token)) 
+						if(string.IsNullOrEmpty(token))
 						{
 							parser.ReportError("Expected model name");
 							return false;
-						} 
+						}
 
 						// Check invalid path chars
 						if(!parser.CheckInvalidPathChars(token)) return false;
 
 						// Check extension
 						string modelext = Path.GetExtension(token);
-						if(string.IsNullOrEmpty(modelext)) 
+						if(string.IsNullOrEmpty(modelext))
 						{
 							parser.ReportError("Model \"" + token + "\" won't be loaded. Models without extension are not supported by GZDoom");
 							return false;
 						}
 
-						if(modelext != ".md3" && modelext != ".md2" && modelext != ".3d" && modelext != ".obj") 
+						if(modelext != ".md3" && modelext != ".md2" && modelext != ".3d" && modelext != ".obj")
 						{
 							parser.ReportError("Model \"" + token + "\" won't be loaded. Only Unreal 3D, MD2, MD3, and OBJ models are supported");
 							return false;
@@ -176,11 +180,11 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 						// Skin path
 						token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
-						if(string.IsNullOrEmpty(token)) 
+						if(string.IsNullOrEmpty(token))
 						{
 							parser.ReportError("Expected skin path");
 							return false;
-						} 
+						}
 
 						// Check invalid path chars
 						if(!parser.CheckInvalidPathChars(token)) return false;
@@ -252,7 +256,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					case "scale":
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref scale.Y)) 
+						if(!parser.ReadSignedFloat(token, ref scale.Y))
 						{
 							// Not numeric!
 							parser.ReportError("Expected Scale X value, but got \"" + token + "\"");
@@ -261,7 +265,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref scale.X)) 
+						if(!parser.ReadSignedFloat(token, ref scale.X))
 						{
 							// Not numeric!
 							parser.ReportError("Expected Scale Y value, but got \"" + token + "\"");
@@ -270,7 +274,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref scale.Z)) 
+						if(!parser.ReadSignedFloat(token, ref scale.Z))
 						{
 							// Not numeric!
 							parser.ReportError("Expected Scale Z value, but got \"" + token + "\"");
@@ -281,7 +285,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					case "offset":
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref offset.X)) 
+						if(!parser.ReadSignedFloat(token, ref offset.X))
 						{
 							// Not numeric!
 							parser.ReportError("Expected Offset X value, but got \"" + token + "\"");
@@ -290,7 +294,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref offset.Y)) 
+						if(!parser.ReadSignedFloat(token, ref offset.Y))
 						{
 							// Not numeric!
 							parser.ReportError("Expected Offset Y value, but got \"" + token + "\"");
@@ -299,7 +303,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref offset.Z)) 
+						if(!parser.ReadSignedFloat(token, ref offset.Z))
 						{
 							// Not numeric!
 							parser.ReportError("Expected Offset Z value, but got \"" + token + "\"");
@@ -310,7 +314,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					case "zoffset":
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref offset.Z)) 
+						if(!parser.ReadSignedFloat(token, ref offset.Z))
 						{
 							// Not numeric!
 							parser.ReportError("Expected ZOffset value, but got \"" + token + "\"");
@@ -321,7 +325,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					case "angleoffset":
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref angleoffset)) 
+						if(!parser.ReadSignedFloat(token, ref angleoffset))
 						{
 							// Not numeric!
 							parser.ReportError("Expected AngleOffset value, but got \"" + token + "\"");
@@ -332,7 +336,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					case "pitchoffset":
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref pitchoffset)) 
+						if(!parser.ReadSignedFloat(token, ref pitchoffset))
 						{
 							// Not numeric!
 							parser.ReportError("Expected PitchOffset value, but got \"" + token + "\"");
@@ -343,7 +347,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					case "rolloffset":
 						parser.SkipWhitespace(true);
 						token = parser.ReadToken();
-						if(!parser.ReadSignedFloat(token, ref rolloffset)) 
+						if(!parser.ReadSignedFloat(token, ref rolloffset))
 						{
 							// Not numeric!
 							parser.ReportError("Expected RollOffset value, but got \"" + token + "\"");
@@ -351,6 +355,35 @@ namespace CodeImp.DoomBuilder.ZDoom
 						}
 						break;
 
+					case "rotation-center":
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if (!parser.ReadSignedFloat(token, ref rotationcenter.X))
+						{
+							// Not numeric!
+							parser.ReportError("Expected rotation center X value, but got \"" + token + "\"");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if (!parser.ReadSignedFloat(token, ref rotationcenter.Y))
+						{
+							// Not numeric!
+							parser.ReportError("Expected rotation center Y value, but got \"" + token + "\"");
+							return false;
+						}
+
+						parser.SkipWhitespace(true);
+						token = parser.ReadToken();
+						if (!parser.ReadSignedFloat(token, ref rotationcenter.Z))
+						{
+							// Not numeric!
+							parser.ReportError("Expected rotation center Z value, but got \"" + token + "\"");
+							return false;
+						}
+						break;
+
 					case "useactorpitch":
 						inheritactorpitch = false;
 						useactorpitch = true;
@@ -360,13 +393,18 @@ namespace CodeImp.DoomBuilder.ZDoom
 						useactorroll = true;
 						break;
 
+					case "rotating":
+					case "userotationcenter":
+						userotationcenter = true;
+						break;
+
 					case "inheritactorpitch":
 						inheritactorpitch = true;
 						useactorpitch = false;
 						parser.LogWarning("INHERITACTORPITCH flag is deprecated. Consider using USEACTORPITCH flag instead");
 						break;
 
-					case "inheritactorroll": 
+					case "inheritactorroll":
 						useactorroll = true;
 						parser.LogWarning("INHERITACTORROLL flag is deprecated. Consider using USEACTORROLL flag instead");
 						break;
@@ -375,7 +413,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					case "frameindex":
 						// Sprite name
 						parser.SkipWhitespace(true);
-						string fispritename = parser.ReadToken();
+						string fispritename = ZDTextParser.StripQuotes(parser.ReadToken());
 						if(string.IsNullOrEmpty(fispritename))
 						{
 							parser.ReportError("Expected sprite name");
@@ -383,13 +421,13 @@ namespace CodeImp.DoomBuilder.ZDoom
 						}
 						if(fispritename.Length != 4)
 						{
-							parser.ReportError("Sprite name must be 4 characters long");
+							parser.ReportError("Sprite name must be 4 characters long, got \"" + fispritename + "\"");
 							return false;
 						}
 
 						// Sprite frame
 						parser.SkipWhitespace(true);
-						token = parser.ReadToken();
+						token = ZDTextParser.StripQuotes(parser.ReadToken());
 						if(string.IsNullOrEmpty(token))
 						{
 							parser.ReportError("Expected sprite frame");
diff --git a/Source/Core/ZDoom/PatchStructure.cs b/Source/Core/ZDoom/PatchStructure.cs
index fbac843acd00c4c272d0ff8ef54d476756ff5712..101fc368e18b8f0e5df84611138873c36a0b2ef9 100755
--- a/Source/Core/ZDoom/PatchStructure.cs
+++ b/Source/Core/ZDoom/PatchStructure.cs
@@ -84,7 +84,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			// First token is the class name
 			parser.SkipWhitespace(true);
-			if(!parser.ReadTextureName(out name, "patch")) return; //mxd
+			if(!parser.ReadTextureName(out name, TextureNamespace.PATCH)) return; //mxd
 			if(string.IsNullOrEmpty(name))
 			{
 				parser.ReportError("Expected patch name");
diff --git a/Source/Core/ZDoom/Scripting/ScriptTypeParserSE.cs b/Source/Core/ZDoom/Scripting/ScriptTypeParserSE.cs
index a84cf35724ff79ed9d9f2fd1c4a166bf172e6ff0..47d823a56bb1da225624d1878f2415a19e72bb88 100755
--- a/Source/Core/ZDoom/Scripting/ScriptTypeParserSE.cs
+++ b/Source/Core/ZDoom/Scripting/ScriptTypeParserSE.cs
@@ -9,17 +9,17 @@ using CodeImp.DoomBuilder.Data;
 //mxd. Parser used to determine which script type given text is.
 namespace CodeImp.DoomBuilder.ZDoom.Scripting
 {
-	internal sealed class ScriptTypeParserSE : ZDTextParser 
+	internal sealed class ScriptTypeParserSE : ZDTextParser
 	{
 		internal override ScriptType ScriptType { get { return scripttype; } }
 		private ScriptType scripttype;
 
-		internal ScriptTypeParserSE() 
+		internal ScriptTypeParserSE()
 		{
 			scripttype = ScriptType.UNKNOWN;
 		}
-		
-		public override bool Parse(TextResourceData data, bool clearerrors) 
+
+		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
 			//mxd. Already parsed?
 			if(!base.AddTextResource(data))
@@ -32,23 +32,23 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
 			if(!base.Parse(data, clearerrors)) return false;
 
 			// Continue until at the end of the stream
-			while(SkipWhitespace(true)) 
+			while(SkipWhitespace(true))
 			{
 				string token = ReadToken();
                 long cpos = datastream.Position;
 
-				if(!string.IsNullOrEmpty(token)) 
+				if(!string.IsNullOrEmpty(token))
 				{
 					token = token.ToUpperInvariant();
 
-					if(token == "MODEL") 
+					if(token == "MODEL")
 					{
 						SkipWhitespace(true);
 						ReadToken(); //should be model name
 						SkipWhitespace(true);
 						token = ReadToken();//should be opening brace
-						
-						if(token == "{") 
+
+						if(token == "{")
 						{
 							scripttype = ScriptType.MODELDEF;
 							return true;
@@ -63,8 +63,8 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
 						ReadToken(); //should be script parameters/type
 						SkipWhitespace(true);
 						token = ReadToken(); //should be opening brace
-						
-						if(token == "{") 
+
+						if(token == "{")
 						{
 							scripttype = ScriptType.ACS;
 							return true;
@@ -80,7 +80,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
 						token = ReadToken();
 
                         // [ZZ] note: original code compared token to REPLACES without doing ToUpper
-						if(token == ":" || token == "{" || (token != null && token.ToUpperInvariant() == "REPLACES")) 
+						if(token == ":" || token == "{" || (token != null && token.ToUpperInvariant() == "REPLACES"))
 						{
 							scripttype = ScriptType.DECORATE;
 							return true;
@@ -96,7 +96,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
                             token = ReadToken();
                         }
 
-						if(token == "{") 
+						if(token == "{")
 						{
 							scripttype = ScriptType.DECORATE;
 							return true;
@@ -120,7 +120,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
                         SkipWhitespace(true);
                         token = ReadToken();
 
-                        if ((otoken != "ENUM" && token == ":") || token == "{" || (otoken == "CLASS" && (token != null && token.ToUpperInvariant() == "REPLACES")))
+                        if ((otoken != "ENUM" && (token == ":" || token == ";")) || token == "{" || (otoken == "CLASS" && (token != null && token.ToUpperInvariant() == "REPLACES")))
                         {
                             scripttype = ScriptType.ZSCRIPT;
                             return true;
@@ -129,7 +129,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
                         SkipWhitespace(true);
                         token = ReadToken(); //should be actor name
 
-                        if (token == "{")
+                        if (token == "{" || token == ";")
                         {
                             scripttype = ScriptType.ZSCRIPT;
                             return true;
diff --git a/Source/Core/ZDoom/TextureStructure.cs b/Source/Core/ZDoom/TextureStructure.cs
index 41addc653ce154c367766d8c27062cace78d1276..5c13de14332eacf642ef797ac1ea92b79bb1bf9a 100755
--- a/Source/Core/ZDoom/TextureStructure.cs
+++ b/Source/Core/ZDoom/TextureStructure.cs
@@ -33,7 +33,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		#region ================== Variables
 
 		// Declaration
-		private readonly string typename;
+		private readonly TextureNamespace texturenamespace;
 		private readonly string name;
 		private readonly string virtualpath; //mxd
 		private readonly int width;
@@ -55,7 +55,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		#region ================== Properties
 
-		public string TypeName { get { return typename; } }
+		public TextureNamespace TextureNamespace { get { return texturenamespace; } }
 		public string Name { get { return name; } }
 		public int Width { get { return width; } }
 		public int Height { get { return height; } }
@@ -72,11 +72,11 @@ namespace CodeImp.DoomBuilder.ZDoom
 		#region ================== Constructor / Disposer
 
 		// Constructor
-		internal TextureStructure(TexturesParser parser, string typename, string virtualpath)
+		internal TextureStructure(TexturesParser parser, TextureNamespace texturenamespace, string virtualpath)
 		{
 			// Initialize
-			this.typename = typename;
 			this.virtualpath = virtualpath;
+			this.texturenamespace = texturenamespace;
 			patches = new List<PatchStructure>(4);
 			xscale = 0.0f;
 			yscale = 0.0f;
@@ -86,19 +86,19 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			// First token is the texture name
 			parser.SkipWhitespace(true);
-			if(!parser.ReadTextureName(out name, typename)) return; //mxd
+			if(!parser.ReadTextureName(out name, texturenamespace)) return; //mxd
 
 			//mxd. It can also be "optional" keyword.
 			if(name.ToLowerInvariant() == "optional")
 			{
 				optional = true;
 				parser.SkipWhitespace(true);
-				if(!parser.ReadTextureName(out name, typename)) return; //mxd
+				if(!parser.ReadTextureName(out name, texturenamespace)) return; //mxd
 			}
 
 			if(string.IsNullOrEmpty(name))
 			{
-				parser.ReportError("Expected " + typename + " name");
+				parser.ReportError("Expected " + texturenamespace + " name");
 				return;
 			}
 
@@ -249,7 +249,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 			float scaley = ((yscale == 0.0f) ? General.Map.Config.DefaultTextureScale : 1f / yscale);
 
 			// Make texture
-			TEXTURESImage tex = new TEXTURESImage(name, virtualpath, width, height, scalex, scaley, worldpanning, typename == "flat", optional, nulltexture);
+			TEXTURESImage tex = new TEXTURESImage(name, virtualpath, width, height, scalex, scaley, worldpanning, texturenamespace, optional, nulltexture);
 
 			// Add patches
 			foreach(PatchStructure p in patches) tex.AddPatch(new TexturePatch(p));//mxd
diff --git a/Source/Core/ZDoom/TexturesParser.cs b/Source/Core/ZDoom/TexturesParser.cs
index a8ce9a88753ad1be500c4d6ff2d3f40164289867..937cb80314e053291732d943d1b1cc313c0b276c 100755
--- a/Source/Core/ZDoom/TexturesParser.cs
+++ b/Source/Core/ZDoom/TexturesParser.cs
@@ -39,6 +39,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		#region ================== Variables
 
 		private readonly Dictionary<string, TextureStructure> textures;
+		private readonly Dictionary<string, TextureStructure> walltextures;
 		private readonly Dictionary<string, TextureStructure> flats;
 		private readonly Dictionary<string, TextureStructure> sprites;
 		private readonly char[] pathtrimchars = {'_', '.', ' ', '-'}; //mxd
@@ -50,6 +51,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		internal override ScriptType ScriptType { get { return ScriptType.TEXTURES; } } //mxd
 		
 		public IEnumerable<TextureStructure> Textures { get { return textures.Values; } }
+		public IEnumerable<TextureStructure> WallTextures { get { return walltextures.Values; } }
 		public IEnumerable<TextureStructure> Flats { get { return flats.Values; } }
 		public IEnumerable<TextureStructure> Sprites { get { return sprites.Values; } }
 		
@@ -66,6 +68,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			// Initialize
 			textures = new Dictionary<string, TextureStructure>(StringComparer.Ordinal);
+			walltextures = new Dictionary<string, TextureStructure>(StringComparer.Ordinal);
 			flats = new Dictionary<string, TextureStructure>(StringComparer.Ordinal);
 			sprites = new Dictionary<string, TextureStructure>(StringComparer.Ordinal);
 		}
@@ -115,7 +118,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 						case "texture":
 						{
 							// Read texture structure
-							TextureStructure tx = new TextureStructure(this, "texture", virtualpath);
+							TextureStructure tx = new TextureStructure(this, TextureNamespace.TEXTURE, virtualpath);
 							if(this.HasError) return false;
 
 							// if a limit for the texture name length is set make sure that it's not exceeded
@@ -141,7 +144,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 						case "sprite":
 						{
 							// Read sprite structure
-							TextureStructure tx = new TextureStructure(this, "sprite", virtualpath);
+							TextureStructure tx = new TextureStructure(this, TextureNamespace.SPRITE, virtualpath);
 							if(this.HasError) return false;
 
 							//mxd. Sprite name length must be either 6 or 8 chars
@@ -166,7 +169,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 						case "walltexture":
 						{
 							// Read walltexture structure
-							TextureStructure tx = new TextureStructure(this, "walltexture", virtualpath);
+							TextureStructure tx = new TextureStructure(this, TextureNamespace.WALLTEXTURE, virtualpath);
 							if(this.HasError) return false;
 
 							// if a limit for the walltexture name length is set make sure that it's not exceeded
@@ -184,15 +187,15 @@ namespace CodeImp.DoomBuilder.ZDoom
 							}
 
 							// Add the walltexture
-							if(!textures.ContainsKey(tx.Name) || (textures[tx.Name].TypeName != "texture"))
-								textures[tx.Name] = tx;
+							if(!walltextures.ContainsKey(tx.Name) /*|| (walltextures[tx.Name].TypeName != "texture") */)
+									walltextures[tx.Name] = tx;
 						}
 						break;
 
 						case "flat":
 						{
 							// Read flat structure
-							TextureStructure tx = new TextureStructure(this, "flat", virtualpath);
+							TextureStructure tx = new TextureStructure(this, TextureNamespace.FLAT, virtualpath);
 							if(this.HasError) return false;
 
 							// if a limit for the flat name length is set make sure that it's not exceeded
@@ -210,7 +213,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 							}
 
 							// Add the flat
-							if(!flats.ContainsKey(tx.Name) || (flats[tx.Name].TypeName != "texture"))
+							if(!flats.ContainsKey(tx.Name) /*|| (flats[tx.Name].TypeName != "texture") */)
 								flats[tx.Name] = tx;
 						}
 						break;
@@ -250,6 +253,16 @@ namespace CodeImp.DoomBuilder.ZDoom
 			// Return true when no errors occurred
 			return (ErrorDescription == null);
 		}
+
+		public List<TextureStructure> GetMixedWallTextures()
+		{
+			Dictionary<string, TextureStructure> images = new Dictionary<string, TextureStructure>(walltextures);
+
+			foreach(string s in textures.Keys)
+				images[s] = textures[s];
+
+			return new List<TextureStructure>(images.Values);
+		}
 		
 		#endregion
 	}
diff --git a/Source/Core/ZDoom/ZDTextParser.cs b/Source/Core/ZDoom/ZDTextParser.cs
index de761c5f0d1b27278dd69ac1289f6d31dc72368a..cbd920eb421ce453e757bbbba8829ba9c358e865 100755
--- a/Source/Core/ZDoom/ZDTextParser.cs
+++ b/Source/Core/ZDoom/ZDTextParser.cs
@@ -169,7 +169,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		}
 		
 		// This returns true if the given character is whitespace
-		private bool IsWhitespace(char c)
+		protected internal bool IsWhitespace(char c)
 		{
 			return (whitespace.IndexOf(c) > -1);
 		}
@@ -208,8 +208,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 		}
 
 		//mxd
-		internal bool ReadTextureName(out string name) { return ReadTextureName(out name, "texture"); }
-		internal bool ReadTextureName(out string name, string elementname)
+		internal bool ReadTextureName(out string name) { return ReadTextureName(out name, TextureNamespace.WALLTEXTURE); }
+		internal bool ReadTextureName(out string name, TextureNamespace texturenamespace)
 		{
 			string token = ReadToken(false);
 			name = StripQuotes(token);
@@ -218,7 +218,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 				&& name.Length > DataManager.CLASIC_IMAGE_NAME_LENGTH
 				&& name.Length == token.Length)
 			{
-				ReportError("Long " + elementname + " names must be quoted. See \"" + token + "\"");
+				ReportError("Long " + texturenamespace.ToString() + " names must be quoted. See \"" + token + "\"");
 				return false;
 			}
 
@@ -708,7 +708,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		//mxd. This adds a warning to the ErrorLogger
 		protected internal void LogWarning(string message) { LogWarning(message, CompilerError.NO_LINE_NUMBER); }
-		protected internal void LogWarning(string message, int linenumber)
+		protected internal virtual void LogWarning(string message, int linenumber)
 		{
 			// Add a warning
 			int errline = (linenumber != CompilerError.NO_LINE_NUMBER 
diff --git a/Source/Core/ZDoom/ZScriptActorStructure.cs b/Source/Core/ZDoom/ZScriptActorStructure.cs
index 3172b4f8050986e8817f6b2f21dd6c89427430d1..68abd58744050fce0308d34a19196b45b0a1f578 100755
--- a/Source/Core/ZDoom/ZScriptActorStructure.cs
+++ b/Source/Core/ZDoom/ZScriptActorStructure.cs
@@ -249,13 +249,33 @@ namespace CodeImp.DoomBuilder.ZDoom
                 if (internal_type == null)
                     return null;
                 tokenizer.SkipWhitespace();
-                token = tokenizer.ExpectToken(ZScriptTokenType.OpGreaterThan);
-                if (token == null || !token.IsValid)
+                token = tokenizer.ReadToken();
+                if (token == null || (token.Type != ZScriptTokenType.OpGreaterThan && token.Type != ZScriptTokenType.Comma))
                 {
-                    parser.ReportError("Expected >, got " + ((Object)token ?? "<null>").ToString());
+                    parser.ReportError("Expected > or ,, got " + ((Object)token ?? "<null>").ToString());
                     return null;
                 }
-                return outs + "<" + internal_type + ">";
+                else if (token.Type == ZScriptTokenType.OpGreaterThan)
+                {
+                    return outs + "<" + internal_type + ">";
+                }
+                else
+                {
+                    tokenizer.SkipWhitespace();
+                    string second_internal_type = ParseTypeName();
+                    if (second_internal_type == null)
+                        return null;
+
+                    tokenizer.SkipWhitespace();
+                    token = tokenizer.ExpectToken(ZScriptTokenType.OpGreaterThan);
+                    if (token == null || !token.IsValid)
+                    {
+                        parser.ReportError("Expected >, got " + ((Object)token ?? "<null>").ToString());
+                        return null;
+                    }
+
+                    return outs + "<" + internal_type + "," + second_internal_type + ">";
+                }
             }
             else
             {
@@ -507,6 +527,33 @@ namespace CodeImp.DoomBuilder.ZDoom
 			return version;
 		}
 
+		private string ParseAction()
+		{
+			string[] actioncontexts = new string[] { "actor", "overlay", "weapon", "item" };
+			tokenizer.SkipWhitespace();
+			ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen);
+			if (token == null || !token.IsValid)
+			{
+				return "default";
+			}
+			tokenizer.SkipWhitespace();
+			token = tokenizer.ExpectToken(ZScriptTokenType.Identifier);
+			if (token == null || !token.IsValid || !actioncontexts.Contains(token.Value.ToLowerInvariant()))
+			{
+				parser.ReportError("Expected actor, overlay, weapon, or item, got " + ((Object)token ?? "<null>").ToString());
+				return null;
+			}
+			string context = token.Value.Trim();
+			tokenizer.SkipWhitespace();
+			token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen);
+			if (token == null || !token.IsValid)
+			{
+				parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString());
+				return null;
+			}
+			return context;
+		}
+
 		internal ZScriptActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo, string _classname, string _replacesname, string _parentname)
         {
             this.catinfo = catinfo; //mxd
@@ -522,10 +569,10 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			mixins = new List<string>();
 
-            ZScriptToken cls_open = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly);
+            ZScriptToken cls_open = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly, ZScriptTokenType.Semicolon);
             if (cls_open == null || !cls_open.IsValid)
             {
-                parser.ReportError("Expected {, got " + ((Object)cls_open ?? "<null>").ToString());
+                parser.ReportError("Expected { or ;, got " + ((Object)cls_open ?? "<null>").ToString());
                 return;
             }
 
@@ -562,8 +609,15 @@ namespace CodeImp.DoomBuilder.ZDoom
                 ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly);
                 if (token == null || !token.IsValid)
                 {
-                    parser.ReportError("Expected identifier, got " + ((Object)cls_open ?? "<null>").ToString());
-                    return;
+                    if(token == null && cls_open.Type == ZScriptTokenType.Semicolon)
+                    {
+                        break;
+                    }
+                    else
+                    {
+                        parser.ReportError("Expected identifier, got " + ((Object)cls_open ?? "<null>").ToString());
+                        return;
+                    }
                 }
                 if (token.Type == ZScriptTokenType.CloseCurly) // end of class
                     break;
@@ -663,6 +717,13 @@ namespace CodeImp.DoomBuilder.ZDoom
                                 return;
                         }
 
+                        if (b_lower == "action")
+                        {
+                            string context = ParseAction().ToLowerInvariant();
+                            if (context == null)
+                                return;
+                        }
+
                         modifiers.Add(b_lower);
                     }
                     else
@@ -900,7 +961,7 @@ namespace CodeImp.DoomBuilder.ZDoom
                     else if (arraylen != -1) _args = " [" + arraylen.ToString() + "]";
                     parser.LogWarning(string.Format("{0} {1} {2}{3}", string.Join(" ", modifiers.ToArray()), string.Join(", ", types.ToArray()), name, _args));
                 }*/
-                
+
                 // update 08.02.17: add user variables from ZScript actors.
                 if (args == null && types.Count == 1) // it's a field
                 {
diff --git a/Source/Core/ZDoom/ZScriptParser.cs b/Source/Core/ZDoom/ZScriptParser.cs
index 01c3d5a2e650b92be6105fd054c9df871db34879..2ffb113a9736d02ec1e79ce5e34b7ea461617e6a 100755
--- a/Source/Core/ZDoom/ZScriptParser.cs
+++ b/Source/Core/ZDoom/ZScriptParser.cs
@@ -88,11 +88,18 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 						if(_pname == _pstruct.ClassName.ToLowerInvariant())
 						{
-							Parser.ReportError("Class \"" + _pstruct.ClassName + "\" is trying to inherit from itself. Class is being skipped.");
+							Parser.ReportError("Fatal: Class \"" + _pstruct.ClassName + "\" is trying to inherit from itself.");
 							return false;
 						}
 
+                        string _cname = _pstruct.ClassName;
+
                         Parser.allclasses.TryGetValue(_pname, out _pstruct);
+                        if (_pstruct == null)
+                        {
+                            Parser.ReportError("Fatal: Class \"" + _cname + "\" is trying to inherit from \"" + _pname + "\" which does not exist.");
+                            return false;
+                        }
                     }
                     else _pstruct = null;
                 }
@@ -186,6 +193,9 @@ namespace CodeImp.DoomBuilder.ZDoom
         // [ZZ] custom tokenizer class
         internal ZScriptTokenizer tokenizer;
 
+        //
+        public bool NoWarnings = false;
+
         #endregion
 
         #region ================== Properties
@@ -210,6 +220,12 @@ namespace CodeImp.DoomBuilder.ZDoom
         /// </summary>
         internal Dictionary<string, ActorStructure> AllActorsByClass { get { return archivedactors; } }
 
+
+        /// <summary>
+        /// This is used to find out what classes were parsed from specific archive
+        /// </summary>
+        public HashSet<string> LastClasses { get; internal set; }
+
         #endregion
 
         #region ================== Constructor / Disposer
@@ -249,10 +265,10 @@ namespace CodeImp.DoomBuilder.ZDoom
             string localtextresourcepath = textresourcepath; //mxd
             ZScriptTokenizer localtokenizer = tokenizer; // [ZZ]
 
-            //INFO: ZDoom DECORATE include paths can't be relative ("../actor.txt") 
-            //or absolute ("d:/project/actor.txt") 
+            //INFO: ZDoom DECORATE include paths can't be relative ("../actor.txt")
+            //or absolute ("d:/project/actor.txt")
             //or have backward slashes ("info\actor.txt")
-            //include paths are relative to the first parsed entry, not the current one 
+            //include paths are relative to the first parsed entry, not the current one
             //also include paths may or may not be quoted
             //mxd. Sanity checks
             if (string.IsNullOrEmpty(filename))
@@ -271,13 +287,33 @@ namespace CodeImp.DoomBuilder.ZDoom
                 return false;
             }
 
-            //mxd. Relative paths are not supported
-            if (filename.StartsWith(RELATIVE_PATH_MARKER) || filename.StartsWith(CURRENT_FOLDER_PATH_MARKER) ||
-                filename.StartsWith(ALT_RELATIVE_PATH_MARKER) || filename.StartsWith(ALT_CURRENT_FOLDER_PATH_MARKER))
-            {
-                ReportError("Relative include paths are not supported by ZDoom");
-                return false;
-            }
+			// [JD] Relative paths are supported by GZDoom since 4.8.0
+			if (filename.StartsWith(RELATIVE_PATH_MARKER) || filename.StartsWith(CURRENT_FOLDER_PATH_MARKER) ||
+				filename.StartsWith(ALT_RELATIVE_PATH_MARKER) || filename.StartsWith(ALT_CURRENT_FOLDER_PATH_MARKER))
+			{
+				List<string> pathTokens = localsourcename.Split('\\', '/').ToList();	// take full path of current source file, split to individual folders
+				pathTokens.RemoveAt(pathTokens.Count - 1);			// remove filename itself
+				pathTokens.AddRange(filename.Split('\\', '/'));			// add relative path
+				pathTokens.RemoveAll(token => token.Equals(".", StringComparison.InvariantCulture));	// remove all "." folders from the path
+
+				for (int i = 0; i < pathTokens.Count; i++)
+				{
+					if (pathTokens[i].Equals("..", StringComparison.InvariantCulture))	// for each "..": remove them and previous folder from the path
+					{
+						if (i == 0)	// cannot have ".." at start of full path
+						{
+							ReportError("Relative path escaping archive");
+							return false;
+						}
+
+						pathTokens.RemoveAt(i);
+						pathTokens.RemoveAt(i - 1);
+						i -= 2;
+					}
+				}
+
+				filename = string.Join("/", pathTokens);	// combine the included file path
+			}
 
             //mxd. Backward slashes are not supported
             if (filename.Contains("\\"))
@@ -313,6 +349,13 @@ namespace CodeImp.DoomBuilder.ZDoom
             return true;
         }
 
+        protected internal override void LogWarning(string message, int linenumber)
+        {
+            if (NoWarnings)
+                return;
+            base.LogWarning(message, linenumber);
+        }
+
         // read in an expression as a token list.
         internal List<ZScriptToken> ParseExpression(bool betweenparen = false)
         {
@@ -340,7 +383,7 @@ namespace CodeImp.DoomBuilder.ZDoom
                     datastream.Position = cpos;
                     return ol;
                 }
-                
+
                 if (token.Type == ZScriptTokenType.OpenParen)
                 {
                     nestingLevel++;
@@ -361,10 +404,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
         internal bool SkipBlock(bool eatSemicolon = true)
         {
-            List<ZScriptToken> ol = new List<ZScriptToken>();
-            //
             int nestingLevel = 0;
-            //
             long cpos = datastream.Position;
             ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly);
             if (token == null || !token.IsValid)
@@ -402,8 +442,6 @@ namespace CodeImp.DoomBuilder.ZDoom
                         return false;
                     }
                 }
-
-                ol.Add(token);
             }
 
             // there is POTENTIALLY a semicolon after the class definition. it's not supposed to be there, but it's acceptable (GZDoom.pk3 has this)
@@ -415,14 +453,74 @@ namespace CodeImp.DoomBuilder.ZDoom
             return true;
         }
 
-        internal List<ZScriptToken> ParseBlock(bool allowsingle)
+        internal bool SkipClassBlock()
+        {
+            int nestingLevel = 0;
+            long cpos = datastream.Position;
+            ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly, ZScriptTokenType.Semicolon);
+            bool semico = token.Type == ZScriptTokenType.Semicolon;
+            if (token == null || !token.IsValid)
+            {
+                ReportError("Expected { or ;, got " + ((Object)token ?? "<null>").ToString());
+                return false;
+            }
+
+            // parse everything between { and } or ; and eof
+            nestingLevel = 1;
+            while (nestingLevel > 0)
+            {
+                cpos = datastream.Position;
+                token = tokenizer.ReadToken(true);
+                //LogWarning(token.ToString());
+                if (token == null)
+                {
+                    if (semico)
+                    {
+                        nestingLevel--;
+                        break;
+                    }
+                    else
+                    {
+                        ReportError("Expected a token");
+                        return false;
+                    }
+                }
+
+                if (token.Type != ZScriptTokenType.Invalid)
+                    continue;
+
+                if (token.Value == "{")
+                {
+                    nestingLevel++;
+                }
+                else if (token.Value == "}")
+                {
+                    nestingLevel--;
+                    if (nestingLevel < 0)
+                    {
+                        ReportError("Closing parenthesis without an opening one");
+                        return false;
+                    }
+                }
+            }
+
+            // there is POTENTIALLY a semicolon after the class definition. it's not supposed to be there, but it's acceptable (GZDoom.pk3 has this)
+            cpos = datastream.Position;
+            ZScriptToken tailtoken = tokenizer.ReadToken();
+            if (tailtoken == null || tailtoken.Type != ZScriptTokenType.Semicolon)
+                datastream.Position = cpos;
+
+            return true;
+        }
+
+        internal List<ZScriptToken> ParseBlock(bool allowsingle, ZScriptToken skipRead = null)
         {
             List<ZScriptToken> ol = new List<ZScriptToken>();
             //
             int nestingLevel = 0;
             //
             long cpos = datastream.Position;
-            ZScriptToken token = tokenizer.ReadToken();
+            ZScriptToken token = skipRead ?? tokenizer.ReadToken();
             if (token == null)
             {
                 ReportError("Expected a code block, got <null>");
@@ -532,7 +630,26 @@ namespace CodeImp.DoomBuilder.ZDoom
                 return false;
             }
             tokenizer.SkipWhitespace();
-            if (ParseBlock(false) == null) return false; // anything between { and }
+            token = tokenizer.ReadToken();
+            if (token == null)
+            {
+                ReportError("Expected a code block or integer type for enum, got <null>");
+                return false;
+            }
+            else if (token.Type == ZScriptTokenType.Colon)
+            {
+                tokenizer.SkipWhitespace();
+                token = tokenizer.ExpectToken(ZScriptTokenType.Identifier);
+                if (token == null || !token.IsValid)
+                {
+                    ReportError("Expected an integer type, got " + ((Object)token ?? "<null>").ToString());
+                    return false;
+                }
+
+                tokenizer.SkipWhitespace();
+                token = tokenizer.ReadToken();
+            }
+            if (ParseBlock(false, token) == null) return false; // anything between { and }
             //LogWarning(string.Format("Parsed enum {0}", token.Value));
             return true;
         }
@@ -735,7 +852,7 @@ namespace CodeImp.DoomBuilder.ZDoom
                         return false;
                     }
                 }
-                else if (token.Type == ZScriptTokenType.OpenCurly)
+                else if (token.Type == ZScriptTokenType.Semicolon || token.Type == ZScriptTokenType.OpenCurly)
                 {
                     datastream.Position--;
                     break;
@@ -747,7 +864,7 @@ namespace CodeImp.DoomBuilder.ZDoom
             long cpos = datastream.Position;
             //List<ZScriptToken> classblocktokens = ParseBlock(false);
             //if (classblocktokens == null) return false;
-            if (!SkipBlock()) return false;
+            if (!SkipClassBlock()) return false;
 
             string log_inherits = ((tok_parentname != null) ? "inherits " + tok_parentname.Value : "");
             if (tok_replacename != null) log_inherits += ((log_inherits.Length > 0) ? ", " : "") + "replaces " + tok_replacename.Value;
@@ -768,7 +885,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 				allclasses.Add(cls.ClassName.ToLowerInvariant(), cls);
 				allclasseslist.Add(cls);
-			}
+                LastClasses.Add(cls.ClassName.ToLowerInvariant());
+            }
 			else if(!isstruct && extend)
 			{
 				string clskey = tok_classname.Value.ToLowerInvariant();
@@ -816,6 +934,8 @@ namespace CodeImp.DoomBuilder.ZDoom
         // Returns false on errors
         public override bool Parse(TextResourceData data, bool clearerrors)
         {
+            if (clearerrors) LastClasses = new HashSet<string>();
+
             //mxd. Already parsed?
             if (!base.AddTextResource(data))
             {
@@ -846,16 +966,7 @@ namespace CodeImp.DoomBuilder.ZDoom
                                                            ZScriptTokenType.Preprocessor);
 
 				if (token == null) // EOF reached
-				{
-					// Now parse all included files
-					foreach(string include in includes)
-					{
-						if (!ParseInclude(include))
-							return false;
-					}
-
 					break;
-				}
 
                 if (!token.IsValid)
                 {
@@ -863,6 +974,10 @@ namespace CodeImp.DoomBuilder.ZDoom
                     return false;
                 }
 
+                // If $GZDB_SKIP is encountered we stop parsing. Not that we can't "return" here yet, because the includes still have to be parsed
+                if (token.Type == ZScriptTokenType.LineComment && token.Value.Trim().ToLowerInvariant() == "$gzdb_skip")
+                    break;
+
                 // toplevel tokens allowed are only Preprocessor and Identifier.
                 switch (token.Type)
                 {
@@ -876,9 +991,6 @@ namespace CodeImp.DoomBuilder.ZDoom
                             string cmtval = token.Value.TrimStart();
                             if (cmtval.Length <= 0 || cmtval[0] != '$')
                                 break;
-                            // check for $GZDB_SKIP
-                            if (cmtval.Trim().ToLowerInvariant() == "$gzdb_skip")
-                                return true;
                             // if we are in a region, read property using function from ZScriptActorStructure
                             if (regions.Count > 0)
                                 ZScriptActorStructure.ParseGZDBComment(regions.Last().Properties, cmtval);
@@ -1014,6 +1126,13 @@ namespace CodeImp.DoomBuilder.ZDoom
                 }
             }
 
+            // Now parse all included files
+            foreach (string include in includes)
+            {
+                if (!ParseInclude(include))
+                    return false;
+            }
+
             return true;
         }
 
@@ -1098,7 +1217,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 								}
 
 								// [ZZ] inherit arguments from game configuration
-								//      
+								//
 								if (!actor.props.ContainsKey("$clearargs"))
 								{
 									for (int i = 0; i < 5; i++)
@@ -1122,41 +1241,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 					}
 
 					// Mixins. https://zdoom.org/wiki/ZScript_mixins
-					if (((ZScriptActorStructure)actor).Mixins.Count > 0)
-					{
-						foreach(string mixinclassname in ((ZScriptActorStructure)actor).Mixins)
-						{
-							if(!mixinclasses.ContainsKey(mixinclassname))
-							{
-								LogWarning("Unable to find \"" + mixinclassname + "\" mixin class while parsing \"" + actor.ClassName + "\"");
-								continue;
-							}
-
-							ZScriptClassStructure mixincls = mixinclasses[mixinclassname];
-
-							// States
-							if(actor.states.Count == 0 && mixincls.Actor.states.Count != 0)
-							{
-								// Can't use HasState and GetState here, because it does some magic that will not work for mixins
-								if (!actor.states.ContainsKey("spawn") && mixincls.Actor.states.ContainsKey("spawn"))
-									actor.states.Add("spawn", mixincls.Actor.GetState("spawn"));
-							}
-
-							// Properties
-							if (!actor.props.ContainsKey("height") && mixincls.Actor.props.ContainsKey("height"))
-								actor.props["height"] = new List<string>(mixincls.Actor.props["height"]);
-
-							if (!actor.props.ContainsKey("radius") && mixincls.Actor.props.ContainsKey("radius"))
-								actor.props["radius"] = new List<string>(mixincls.Actor.props["radius"]);
-
-							// Flags
-							if (!actor.flags.ContainsKey("spawnceiling") && mixincls.Actor.flags.ContainsKey("spawnceiling"))
-								actor.flags["spawnceiling"] = true;
-
-							if (!actor.flags.ContainsKey("solid") && mixincls.Actor.flags.ContainsKey("solid"))
-								actor.flags["solid"] = true;
-						}
-					}
+					foreach(string mixinclassname in ((ZScriptActorStructure)actor).Mixins)
+                        ApplyMixin(actor, mixinclassname);
 
 					// Extensions. https://zdoom.org/wiki/ZScript_classes#Extending_Classes
 					if (cls.Extensions.Count > 0)
@@ -1168,9 +1254,13 @@ namespace CodeImp.DoomBuilder.ZDoom
 							if (extenseionactor == null)
 								continue;
 
-							// States
-							if (extenseionactor.states.ContainsKey("spawn"))
-								actor.states["spawn"] = extenseionactor.GetState("spawn");
+                            // Apply mixins to the extension class
+                            foreach (string mixinclassname in ((ZScriptActorStructure)extenseionactor).Mixins)
+                                ApplyMixin(extenseionactor, mixinclassname);
+
+                            // States
+                            if (extenseionactor.states.ContainsKey("spawn"))
+							    actor.states["spawn"] = extenseionactor.GetState("spawn");
 
 							// Properties
 							if (extenseionactor.props.ContainsKey("height"))
@@ -1188,7 +1278,12 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 							// user_ variables
 							foreach (string uservarname in extenseionactor.uservars.Keys)
+							{
 								actor.uservars[uservarname] = extenseionactor.uservars[uservarname];
+
+								if (extenseionactor.uservar_defaults.ContainsKey(uservarname))
+									actor.uservar_defaults[uservarname] = extenseionactor.uservar_defaults[uservarname];
+							}
 						}
 					}
 				}
@@ -1221,6 +1316,43 @@ namespace CodeImp.DoomBuilder.ZDoom
             return true;
         }
 
+        private void ApplyMixin(ActorStructure actor, string mixinclassname)
+		{
+            if (!mixinclasses.ContainsKey(mixinclassname))
+            {
+                LogWarning("Unable to find \"" + mixinclassname + "\" mixin class while parsing \"" + actor.ClassName + "\"");
+                return;
+            }
+
+            ZScriptClassStructure mixincls = mixinclasses[mixinclassname];
+
+            // States
+            if (actor.states.Count == 0 && mixincls.Actor.states.Count != 0)
+            {
+                // Can't use HasState and GetState here, because it does some magic that will not work for mixins
+                if (!actor.states.ContainsKey("spawn") && mixincls.Actor.states.ContainsKey("spawn"))
+                    actor.states.Add("spawn", mixincls.Actor.GetState("spawn"));
+            }
+
+            // Properties
+            if (!actor.props.ContainsKey("height") && mixincls.Actor.props.ContainsKey("height"))
+                actor.props["height"] = new List<string>(mixincls.Actor.props["height"]);
+
+            if (!actor.props.ContainsKey("radius") && mixincls.Actor.props.ContainsKey("radius"))
+                actor.props["radius"] = new List<string>(mixincls.Actor.props["radius"]);
+
+            // Flags
+            if (!actor.flags.ContainsKey("spawnceiling") && mixincls.Actor.flags.ContainsKey("spawnceiling"))
+                actor.flags["spawnceiling"] = true;
+
+            if (!actor.flags.ContainsKey("solid") && mixincls.Actor.flags.ContainsKey("solid"))
+                actor.flags["solid"] = true;
+
+            // user_ variables
+            foreach (string uservarname in mixincls.Actor.uservars.Keys)
+                actor.uservars[uservarname] = mixincls.Actor.uservars[uservarname];
+        }
+
         #endregion
 
         #region ================== Methods
diff --git a/Source/Core/ZDoom/ZScriptStateStructure.cs b/Source/Core/ZDoom/ZScriptStateStructure.cs
index 3ad9456fa78ffdcd49512561daa45f24858ef35b..5a7a141d1bf5bc96f8a57b095e9fc41a27d1fb29 100755
--- a/Source/Core/ZDoom/ZScriptStateStructure.cs
+++ b/Source/Core/ZDoom/ZScriptStateStructure.cs
@@ -39,6 +39,7 @@ namespace CodeImp.DoomBuilder.ZDoom
             if (outs.Length > 0)
             {
                 ZScriptToken tok = new ZScriptToken();
+                tok.Position = cpos;
                 tok.Type = ZScriptTokenType.String;
                 tok.Value = outs;
                 return tok;
diff --git a/Source/Core/ZDoom/ZScriptTokenizer.cs b/Source/Core/ZDoom/ZScriptTokenizer.cs
index 33fd32313e1bee8cdc70f5ca0dee44ec98d8c404..8a6db87c4947b6a45a371d34a45f29130bc6fe16 100755
--- a/Source/Core/ZDoom/ZScriptTokenizer.cs
+++ b/Source/Core/ZDoom/ZScriptTokenizer.cs
@@ -135,7 +135,7 @@ namespace CodeImp.DoomBuilder.ZDoom
         private static Dictionary<string, ZScriptTokenType> namedtokentypes; // these are tokens that have precise equivalent in the enum (like operators)
         private static Dictionary<ZScriptTokenType, string> namedtokentypesreverse; // these are tokens that have precise equivalent in the enum (like operators)
         private static List<string> namedtokentypesorder; // this is the list of said tokens ordered by length.
-        private static StringBuilder SB;
+        private StringBuilder SB = new StringBuilder();
 
         public BinaryReader Reader { get { return reader; } }
         public long LastPosition { get; private set; }
@@ -157,9 +157,6 @@ namespace CodeImp.DoomBuilder.ZDoom
             }
             br.BaseStream.Position = cpos;
 
-            if (SB == null)
-                SB = new StringBuilder();
-
             if (namedtokentypes == null || namedtokentypesreverse == null || namedtokentypesorder == null)
             {
                 namedtokentypes = new Dictionary<string, ZScriptTokenType>();
@@ -210,7 +207,15 @@ namespace CodeImp.DoomBuilder.ZDoom
         private ZScriptToken TryReadWhitespace()
         {
             long cpos = LastPosition = reader.BaseStream.Position;
-            char c = reader.ReadChar();
+            char c;
+            try
+            {
+                c = reader.ReadChar();
+            }
+            catch (EndOfStreamException)
+            {
+                return null;
+            }
 
             // 
             string whitespace = " \r\t\u00A0";
@@ -223,7 +228,15 @@ namespace CodeImp.DoomBuilder.ZDoom
                 SB.Append(c);
                 while (true)
                 {
-                    char cnext = reader.ReadChar();
+                    char cnext;
+                    try
+                    {
+                        cnext = reader.ReadChar();
+                    }
+                    catch (EndOfStreamException)
+                    {
+                        break;
+                    }
                     if (whitespace.Contains(cnext))
                     {
                         SB.Append(cnext);
@@ -248,7 +261,15 @@ namespace CodeImp.DoomBuilder.ZDoom
         private ZScriptToken TryReadIdentifier()
         {
             long cpos = LastPosition = reader.BaseStream.Position;
-            char c = reader.ReadChar();
+            char c;
+            try
+            {
+                c = reader.ReadChar();
+            }
+            catch (EndOfStreamException)
+            {
+                return null;
+            }
 
             // check identifier
             if ((c >= 'a' && c <= 'z') ||
@@ -259,7 +280,15 @@ namespace CodeImp.DoomBuilder.ZDoom
                 SB.Append(c);
                 while (true)
                 {
-                    char cnext = reader.ReadChar();
+                    char cnext;
+                    try
+                    {
+                        cnext = reader.ReadChar();
+                    }
+                    catch (EndOfStreamException)
+                    {
+                        break;
+                    }
                     if ((cnext >= 'a' && cnext <= 'z') ||
                         (cnext >= 'A' && cnext <= 'Z') ||
                         (cnext == '_') ||
@@ -287,7 +316,15 @@ namespace CodeImp.DoomBuilder.ZDoom
         private ZScriptToken TryReadNumber()
         {
             long cpos = LastPosition = reader.BaseStream.Position;
-            char c = reader.ReadChar();
+            char c;
+            try
+            {
+                c = reader.ReadChar();
+            }
+            catch (EndOfStreamException)
+            {
+                return null;
+            }
 
             // check integer
             if ((c >= '0' && c <= '9') || c == '.')
@@ -313,7 +350,15 @@ namespace CodeImp.DoomBuilder.ZDoom
                     SB.Append(c);
                     while (true)
                     {
-                        char cnext = reader.ReadChar();
+                        char cnext;
+                        try
+                        {
+                            cnext = reader.ReadChar();
+                        }
+                        catch (EndOfStreamException)
+                        {
+                            break;
+                        }
                         if (!isdouble && (cnext == 'x') && SB.Length == 1)
                         {
                             isoctal = false;
@@ -336,7 +381,15 @@ namespace CodeImp.DoomBuilder.ZDoom
                             isexponent = true;
                             isdouble = true;
                             SB.Append('e');
-                            cnext = reader.ReadChar();
+                            try
+                            {
+                                cnext = reader.ReadChar();
+                            }
+                            catch (EndOfStreamException)
+                            {
+                                reader.BaseStream.Position = cpos;
+                                return null; // bad exponent notation
+                            }
                             if (cnext == '-') SB.Append('-');
                             else reader.BaseStream.Position--;
                         }
@@ -395,7 +448,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 					}
                     catch (Exception)
                     {
-                        // throw new Exception(tok.ToString());
+                        reader.BaseStream.Position = cpos;
                         return null;
                     }
 
@@ -410,14 +463,30 @@ namespace CodeImp.DoomBuilder.ZDoom
         private ZScriptToken TryReadStringOrComment(bool allowstring, bool allowname, bool allowblock, bool allowline)
         {
             long cpos = LastPosition = reader.BaseStream.Position;
-            char c = reader.ReadChar();
+            char c;
+            try
+            {
+                c = reader.ReadChar();
+            }
+            catch (EndOfStreamException)
+            {
+                return null;
+            }
 
             switch (c)
             {
                 case '/': // comment
                     {
                         if (!allowblock && !allowline) break;
-                        char cnext = reader.ReadChar();
+                        char cnext;
+                        try
+                        {
+                            cnext = reader.ReadChar();
+                        }
+                        catch (EndOfStreamException)
+                        {
+                            break; // invalid
+                        }
                         if (cnext == '/')
                         {
                             if (!allowline) break;
@@ -425,7 +494,14 @@ namespace CodeImp.DoomBuilder.ZDoom
                             SB.Length = 0;
                             while (true)
                             {
-                                cnext = reader.ReadChar();
+                                try
+                                {
+                                    cnext = reader.ReadChar();
+                                }
+                                catch (EndOfStreamException)
+                                {
+                                    break;
+                                }
                                 if (cnext == '\n')
                                 {
                                     reader.BaseStream.Position--;
@@ -448,7 +524,14 @@ namespace CodeImp.DoomBuilder.ZDoom
                             SB.Length = 0;
                             while (true)
                             {
-                                cnext = reader.ReadChar();
+                                try
+                                {
+                                    cnext = reader.ReadChar();
+                                }
+                                catch (EndOfStreamException)
+                                {
+                                    break;
+                                }
                                 if (cnext == '*')
                                 {
                                     char cnext2 = reader.ReadChar();
@@ -478,22 +561,29 @@ namespace CodeImp.DoomBuilder.ZDoom
                         SB.Length = 0;
                         while (true)
                         {
-                            // todo: parse escape sequences properly
-                            char cnext = reader.ReadChar();
-                            if (cnext == '\\') // escape sequence. right now, do nothing
+                            try
                             {
-                                cnext = reader.ReadChar();
-                                SB.Append(cnext);
-                            }
-                            else if (cnext == c)
+                                // todo: parse escape sequences properly
+                                char cnext = reader.ReadChar();
+                                if (cnext == '\\') // escape sequence. right now, do nothing
+                                {
+                                    cnext = reader.ReadChar();
+                                    SB.Append(cnext);
+                                }
+                                else if (cnext == c)
+                                {
+                                    ZScriptToken tok = new ZScriptToken();
+                                    tok.Position = cpos;
+                                    tok.Type = type;
+                                    tok.Value = SB.ToString();
+                                    return tok;
+                                }
+                                else SB.Append(cnext);
+                            } catch (EndOfStreamException)
                             {
-                                ZScriptToken tok = new ZScriptToken();
-                                tok.Position = cpos;
-                                tok.Type = type;
-                                tok.Value = SB.ToString();
-                                return tok;
+                                reader.BaseStream.Position = cpos;
+                                return null; // bad string, unterminated and ends with EOF
                             }
-                            else SB.Append(cnext);
                         }
                     }
 
@@ -642,6 +732,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
                 // token not found.
                 tok = new ZScriptToken();
+                tok.Position = reader.BaseStream.Position;
                 tok.Type = ZScriptTokenType.Invalid;
                 tok.Value = "" + reader.ReadChar();
                 tok.IsValid = false;
diff --git a/Source/Native/Backend.cpp b/Source/Native/Backend.cpp
index b361b149f10cbbdd82b71b92819a90215b772b1f..c1c3e872d3acd9c02dc17c31023a5f7d37047b3a 100644
--- a/Source/Native/Backend.cpp
+++ b/Source/Native/Backend.cpp
@@ -66,9 +66,9 @@ Backend* Backend::Get()
 
 extern "C"
 {
-	RenderDevice* RenderDevice_New(void* disp, void* window)
+	RenderDevice* RenderDevice_New(void* disp, void* window, bool debug)
 	{
-		return Backend::Get()->NewRenderDevice(disp, window);
+		return Backend::Get()->NewRenderDevice(disp, window, debug);
 	}
 
 	void RenderDevice_Delete(RenderDevice* device)
diff --git a/Source/Native/Backend.h b/Source/Native/Backend.h
index 2d6ebbca6335e43cfb4edbb0ce6ddf2e0469e4a6..97258f1874e22f5696cb0707fd87b2f7aacb15d9 100644
--- a/Source/Native/Backend.h
+++ b/Source/Native/Backend.h
@@ -133,7 +133,7 @@ public:
 
 	static Backend* Get();
 
-	virtual RenderDevice* NewRenderDevice(void* disp, void* window) = 0;
+	virtual RenderDevice* NewRenderDevice(void* disp, void* window, bool debug) = 0;
 	virtual void DeleteRenderDevice(RenderDevice* device) = 0;
 
 	virtual VertexBuffer* NewVertexBuffer() = 0;
diff --git a/Source/Native/OpenGL/GLBackend.cpp b/Source/Native/OpenGL/GLBackend.cpp
index 39effc25c49a147aec13bba5a89437df61910f45..b45f8089821ff92281bd06f5bf8c29e89620eaba 100644
--- a/Source/Native/OpenGL/GLBackend.cpp
+++ b/Source/Native/OpenGL/GLBackend.cpp
@@ -26,9 +26,9 @@
 #include "GLIndexBuffer.h"
 #include "GLTexture.h"
 
-RenderDevice* GLBackend::NewRenderDevice(void* disp, void* window)
+RenderDevice* GLBackend::NewRenderDevice(void* disp, void* window, bool debug)
 {
-	GLRenderDevice* device = new GLRenderDevice(disp, window);
+	GLRenderDevice* device = new GLRenderDevice(disp, window, debug);
 	if (!device->Context)
 	{
 		delete device;
diff --git a/Source/Native/OpenGL/GLBackend.h b/Source/Native/OpenGL/GLBackend.h
index 89e5fcb4e99a80e199c3a65656cb736724269452..2a78cf582d2b40b9cd40b921f71d269205582fa2 100644
--- a/Source/Native/OpenGL/GLBackend.h
+++ b/Source/Native/OpenGL/GLBackend.h
@@ -26,7 +26,7 @@
 class GLBackend : public Backend
 {
 public:
-	RenderDevice* NewRenderDevice(void* disp, void* window) override;
+	RenderDevice* NewRenderDevice(void* disp, void* window, bool debug) override;
 	void DeleteRenderDevice(RenderDevice* device) override;
 
 	VertexBuffer* NewVertexBuffer() override;
diff --git a/Source/Native/OpenGL/GLRenderDevice.cpp b/Source/Native/OpenGL/GLRenderDevice.cpp
index f2f8f65632304cec18d50e017c17277b42446bd2..77e8382fe7ec59093962e3f3f37304a7661d1aae 100644
--- a/Source/Native/OpenGL/GLRenderDevice.cpp
+++ b/Source/Native/OpenGL/GLRenderDevice.cpp
@@ -44,27 +44,30 @@ static const char* GLLogCheckNull(const GLubyte* str)
 	return str ? (const char*)str : "null";
 }
 
-GLRenderDevice::GLRenderDevice(void* disp, void* window)
+GLRenderDevice::GLRenderDevice(void* disp, void* window, bool debug)
 {
 	Context = IOpenGLContext::Create(disp, window);
 	if (Context)
 	{
 		Context->MakeCurrent();
 
-#ifdef _DEBUG
-		FILE* f = fopen("OpenGLDebug.log", "wb");
-		if (f)
+//#ifdef _DEBUG
+		if (debug)
 		{
-			fprintf(f, "GL_VENDOR = %s\r\n", GLLogCheckNull(glGetString(GL_VENDOR)));
-			fprintf(f, "GL_RENDERER = %s\r\n", GLLogCheckNull(glGetString(GL_RENDERER)));
-			fprintf(f, "GL_VERSION = %s\r\n", GLLogCheckNull(glGetString(GL_VERSION)));
-			fprintf(f, "GL_SHADING_LANGUAGE_VERSION = %s\r\n", GLLogCheckNull(glGetString(GL_SHADING_LANGUAGE_VERSION)));
-			fclose(f);
-
-			glEnable(GL_DEBUG_OUTPUT);
-			glDebugMessageCallback(&GLLogCallback, nullptr);
+			FILE* f = fopen("OpenGLDebug.log", "wb");
+			if (f)
+			{
+				fprintf(f, "GL_VENDOR = %s\r\n", GLLogCheckNull(glGetString(GL_VENDOR)));
+				fprintf(f, "GL_RENDERER = %s\r\n", GLLogCheckNull(glGetString(GL_RENDERER)));
+				fprintf(f, "GL_VERSION = %s\r\n", GLLogCheckNull(glGetString(GL_VERSION)));
+				fprintf(f, "GL_SHADING_LANGUAGE_VERSION = %s\r\n", GLLogCheckNull(glGetString(GL_SHADING_LANGUAGE_VERSION)));
+				fclose(f);
+
+				glEnable(GL_DEBUG_OUTPUT);
+				glDebugMessageCallback(&GLLogCallback, nullptr);
+			}
 		}
-#endif
+//#endif
 
 		glGenVertexArrays(1, &mStreamVAO);
 		glGenBuffers(1, &mStreamVertexBuffer);
@@ -604,7 +607,11 @@ bool GLRenderDevice::SetVertexBufferData(VertexBuffer* ibuffer, void* data, int6
 	buffer->BufferStartIndex = buffer->BufferOffset / (format == VertexFormat::Flat ? VertexBuffer::FlatStride : VertexBuffer::WorldStride);
 	sharedbuf->NextPos += size;
 
-	glBufferSubData(GL_ARRAY_BUFFER, buffer->BufferOffset, size, data);
+	if (data)
+	{
+		glBufferSubData(GL_ARRAY_BUFFER, buffer->BufferOffset, size, data);
+	}
+
 	glBindBuffer(GL_ARRAY_BUFFER, oldbinding);
 	bool result = CheckGLError();
 	return result;
diff --git a/Source/Native/OpenGL/GLRenderDevice.h b/Source/Native/OpenGL/GLRenderDevice.h
index c943bffeaeb7bb8efa3462dc8df3d7578399a1d9..1b39cf3c41cf4e8bd9ed03916f171b000848a6a2 100644
--- a/Source/Native/OpenGL/GLRenderDevice.h
+++ b/Source/Native/OpenGL/GLRenderDevice.h
@@ -35,7 +35,7 @@ class GLTexture;
 class GLRenderDevice : public RenderDevice
 {
 public:
-	GLRenderDevice(void* disp, void* window);
+	GLRenderDevice(void* disp, void* window, bool debug);
 	~GLRenderDevice();
 
 	void DeclareUniform(UniformName name, const char* glslname, UniformType type) override;
diff --git a/Source/Plugins/3DFloorMode/BuilderPlug.cs b/Source/Plugins/3DFloorMode/BuilderPlug.cs
index deccc59f17ecc71f413d8853f5f3fb5c6700eb2d..833e1a64e6e1e970b107c156a7d6d8c44df1168c 100644
--- a/Source/Plugins/3DFloorMode/BuilderPlug.cs
+++ b/Source/Plugins/3DFloorMode/BuilderPlug.cs
@@ -181,6 +181,7 @@ namespace CodeImp.DoomBuilder.ThreeDFloorMode
 			// TMP
 			drawlines = new List<Line3D>();
 			drawpoints = new List<Vector3D>();
+
 		}
 
 		// This is called when the plugin is terminated
@@ -935,7 +936,7 @@ namespace CodeImp.DoomBuilder.ThreeDFloorMode
 			// Bind the 3D floors to the selected sectors
 			foreach (List<Sector> sectors in sectorGroups)
 			{
-				if (General.Map.UDMF == true)
+				if (General.Map.UDMF == true && General.Map.Config.SectorMultiTag)
 				{
 					foreach (Sector s in sectors)
 					{
@@ -962,7 +963,7 @@ namespace CodeImp.DoomBuilder.ThreeDFloorMode
 
 					}
 				}
-				else
+				else // No sector multi tagging support
 				{
 					int newtag;
 
diff --git a/Source/Plugins/3DFloorMode/ThreeDFloor.cs b/Source/Plugins/3DFloorMode/ThreeDFloor.cs
index a48487fdfe8aaf4b1f3dae58409f2f383d50fc1b..e29911fdd728dc959e7ebbb3b08f8fed43ecb2da 100644
--- a/Source/Plugins/3DFloorMode/ThreeDFloor.cs
+++ b/Source/Plugins/3DFloorMode/ThreeDFloor.cs
@@ -295,7 +295,7 @@ namespace CodeImp.DoomBuilder.ThreeDFloorMode
 			sector.Fields["comment"] = new UniValue(UniversalType.String, "[!]DO NOT DELETE! This sector is managed by the 3D floor plugin.");
 
 			// With multiple tag support in UDMF only one tag is needed, so bind it right away
-			if (General.Map.UDMF == true)
+			if (General.Map.UDMF == true && General.Map.Config.SectorMultiTag)
 			{
 				if (isnew || forcenewtag)
 				{
diff --git a/Source/Plugins/3DFloorMode/ThreeDFloorModeMono.csproj b/Source/Plugins/3DFloorMode/ThreeDFloorModeMono.csproj
index cba09efe107329371eef0300f249054c4682a927..761054306414542097166dff97d94418b84d71cf 100644
--- a/Source/Plugins/3DFloorMode/ThreeDFloorModeMono.csproj
+++ b/Source/Plugins/3DFloorMode/ThreeDFloorModeMono.csproj
@@ -74,14 +74,14 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
     <DebugSymbols>true</DebugSymbols>
     <OutputPath>..\..\..\Build\Plugins\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DefineConstants>TRACE;DEBUG;NO_SCINTILLA;NO_FORMS_DESIGN;NO_WIN32;NO_UPDATER;MONO_WINFORMS</DefineConstants>
     <DebugType>full</DebugType>
     <PlatformTarget>x64</PlatformTarget>
     <ErrorReport>prompt</ErrorReport>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
     <OutputPath>..\..\..\Build\Plugins\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
+    <DefineConstants>TRACE;NO_SCINTILLA;NO_FORMS_DESIGN;NO_WIN32;NO_UPDATER;MONO_WINFORMS</DefineConstants>
     <Optimize>true</Optimize>
     <DebugType>pdbonly</DebugType>
     <PlatformTarget>x64</PlatformTarget>
diff --git a/Source/Plugins/3DFloorMode/Windows/MenusForm.Designer.cs b/Source/Plugins/3DFloorMode/Windows/MenusForm.Designer.cs
index aec62a1d2a9413036b0b985894ee62e2410726af..e49b0d94bb84979e08311c047312595ec7d6e2bc 100644
--- a/Source/Plugins/3DFloorMode/Windows/MenusForm.Designer.cs
+++ b/Source/Plugins/3DFloorMode/Windows/MenusForm.Designer.cs
@@ -35,13 +35,13 @@
 			this.ceilingslope = new System.Windows.Forms.ToolStripButton();
 			this.floorandceilingslope = new System.Windows.Forms.ToolStripButton();
 			this.updateslopes = new System.Windows.Forms.ToolStripButton();
+			this.relocatecontrolsectors = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.addsectorscontextmenu = new System.Windows.Forms.ContextMenuStrip(this.components);
 			this.addslopeceiling = new System.Windows.Forms.ToolStripMenuItem();
 			this.addslopefloor = new System.Windows.Forms.ToolStripMenuItem();
 			this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
 			this.removeslopeceiling = new System.Windows.Forms.ToolStripMenuItem();
 			this.removeslopefloor = new System.Windows.Forms.ToolStripMenuItem();
-			this.relocatecontrolsectors = new System.Windows.Forms.ToolStripButton();
 			this.toolStrip1.SuspendLayout();
 			this.addsectorscontextmenu.SuspendLayout();
 			this.SuspendLayout();
@@ -103,6 +103,17 @@
 			this.updateslopes.Text = "Update slopes";
 			this.updateslopes.Click += new System.EventHandler(this.toolStripButton1_Click);
 			// 
+			// relocatecontrolsectors
+			// 
+			this.relocatecontrolsectors.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
+			this.relocatecontrolsectors.Image = ((System.Drawing.Image)(resources.GetObject("relocatecontrolsectors.Image")));
+			this.relocatecontrolsectors.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.relocatecontrolsectors.Name = "relocatecontrolsectors";
+			this.relocatecontrolsectors.Size = new System.Drawing.Size(137, 22);
+			this.relocatecontrolsectors.Tag = "relocate3dfloorcontrolsectors";
+			this.relocatecontrolsectors.Text = "Relocate control sectors";
+			this.relocatecontrolsectors.Click += new System.EventHandler(this.relocatecontrolsectors_Click);
+			// 
 			// addsectorscontextmenu
 			// 
 			this.addsectorscontextmenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
@@ -113,8 +124,8 @@
             this.removeslopefloor});
 			this.addsectorscontextmenu.Name = "addsectorscontextmenu";
 			this.addsectorscontextmenu.Size = new System.Drawing.Size(216, 98);
-			this.addsectorscontextmenu.Opening += new System.ComponentModel.CancelEventHandler(this.addsectorscontextmenu_Opening);
 			this.addsectorscontextmenu.Closing += new System.Windows.Forms.ToolStripDropDownClosingEventHandler(this.addsectorscontextmenu_Closing);
+			this.addsectorscontextmenu.Opening += new System.ComponentModel.CancelEventHandler(this.addsectorscontextmenu_Opening);
 			// 
 			// addslopeceiling
 			// 
@@ -149,17 +160,6 @@
 			this.removeslopefloor.Text = "Remove slope from floor";
 			this.removeslopefloor.Click += new System.EventHandler(this.removeSlopeFromFloorToolStripMenuItem_Click);
 			// 
-			// relocatecontrolsectors
-			// 
-			this.relocatecontrolsectors.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
-			this.relocatecontrolsectors.Image = ((System.Drawing.Image)(resources.GetObject("relocatecontrolsectors.Image")));
-			this.relocatecontrolsectors.ImageTransparentColor = System.Drawing.Color.Magenta;
-			this.relocatecontrolsectors.Name = "relocatecontrolsectors";
-			this.relocatecontrolsectors.Size = new System.Drawing.Size(137, 22);
-			this.relocatecontrolsectors.Tag = "relocate3dfloorcontrolsectors";
-			this.relocatecontrolsectors.Text = "Relocate control sectors";
-			this.relocatecontrolsectors.Click += new System.EventHandler(this.relocatecontrolsectors_Click);
-			// 
 			// MenusForm
 			// 
 			this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -189,6 +189,6 @@
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
 		private System.Windows.Forms.ToolStripMenuItem removeslopeceiling;
 		private System.Windows.Forms.ToolStripMenuItem removeslopefloor;
-		private System.Windows.Forms.ToolStripButton relocatecontrolsectors;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton relocatecontrolsectors;
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/3DFloorMode/Windows/MenusForm.cs b/Source/Plugins/3DFloorMode/Windows/MenusForm.cs
index e7dc6822c8a71f0bcb5288dccd08fa80219e862d..77ffefa5d14c5fcef1cc3ed385f4577358a4c414 100644
--- a/Source/Plugins/3DFloorMode/Windows/MenusForm.cs
+++ b/Source/Plugins/3DFloorMode/Windows/MenusForm.cs
@@ -8,6 +8,7 @@ using System.Text;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Windows;
 using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Controls;
 
 namespace CodeImp.DoomBuilder.ThreeDFloorMode
 {
@@ -17,7 +18,7 @@ namespace CodeImp.DoomBuilder.ThreeDFloorMode
 		public ToolStripButton CeilingSlope { get { return ceilingslope; } }
 		public ToolStripButton FloorAndCeilingSlope { get { return floorandceilingslope; } }
 		public ToolStripButton UpdateSlopes { get { return updateslopes; } }
-		public ToolStripButton RelocateControlSectors { get { return relocatecontrolsectors; } }
+		public ToolStripActionButton RelocateControlSectors { get { return relocatecontrolsectors; } }
 		public ContextMenuStrip AddSectorsContextMenu { get { return addsectorscontextmenu; } }
 
 		public MenusForm()
@@ -25,6 +26,11 @@ namespace CodeImp.DoomBuilder.ThreeDFloorMode
 			InitializeComponent();
 		}
 
+		public void UpdateToolTips()
+		{
+			relocatecontrolsectors.UpdateToolTip();
+		}
+
 		private void InvokeTaggedAction(object sender, EventArgs e)
 		{
 			General.Interface.InvokeTaggedAction(sender, e);
diff --git a/Source/Plugins/3DFloorMode/Windows/MenusForm.resx b/Source/Plugins/3DFloorMode/Windows/MenusForm.resx
index 23345ded5dfed0d5884d90c6c7c5863a1d038360..2c645baabf695daed4caa97cc9bbb9a8e3b1a59a 100644
--- a/Source/Plugins/3DFloorMode/Windows/MenusForm.resx
+++ b/Source/Plugins/3DFloorMode/Windows/MenusForm.resx
@@ -112,15 +112,15 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
-  <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
-  <assembly alias="System.Drawing" name="System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
   <data name="updateslopes.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
         iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
@@ -151,7 +151,7 @@
         TgDQASA1MVpwzwAAAABJRU5ErkJggg==
 </value>
   </data>
-  <metadata name="addsectorscontextmenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="addsectorscontextmenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>122, 17</value>
   </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Plugins/3DFloorMode/Windows/ThreeDFloorEditorWindow.cs b/Source/Plugins/3DFloorMode/Windows/ThreeDFloorEditorWindow.cs
index 4b4e3ca78f2187e0fc431a5f35840de1e8a549ab..2d3d5a41a8eddb5fe0b155633953cf956a1fb13a 100644
--- a/Source/Plugins/3DFloorMode/Windows/ThreeDFloorEditorWindow.cs
+++ b/Source/Plugins/3DFloorMode/Windows/ThreeDFloorEditorWindow.cs
@@ -37,6 +37,10 @@ namespace CodeImp.DoomBuilder.ThreeDFloorMode
 			this.Location = new Point(Screen.PrimaryScreen.WorkingArea.Width/2, Screen.PrimaryScreen.WorkingArea.Height/2);
 			controlpool = new List<ThreeDFloorHelperControl>();
 			InitializeComponent();
+
+			#if NO_WIN32
+			MaximumSize = new Size(10000, MaximumSize.Height);
+			#endif
 		}
 
 		private void ThreeDFloorEditorWindow_Load(object sender, EventArgs e)
diff --git a/Source/Plugins/AutomapMode/AutomapMode.cs b/Source/Plugins/AutomapMode/AutomapMode.cs
index 1077b856a5080ddb0aacd12e55a038ea7634c8c5..f49c57432e4b80af448e4e6af39af599e3d4f10b 100755
--- a/Source/Plugins/AutomapMode/AutomapMode.cs
+++ b/Source/Plugins/AutomapMode/AutomapMode.cs
@@ -61,8 +61,9 @@ namespace CodeImp.DoomBuilder.AutomapMode
 		private List<Linedef> validlinedefs;
 		private HashSet<Sector> secretsectors; //mxd
 
-		// Highlighted item
-		private Linedef highlighted;
+		// Highlighted items
+		private Linedef highlightedLine;
+		private Sector highlightedSector;
 
 		//mxd. UI
 		private MenusForm menusform;
@@ -76,12 +77,25 @@ namespace CodeImp.DoomBuilder.AutomapMode
 		private PixelColor ColorHiddenFlag;
 		private PixelColor ColorInvisible;
 		private PixelColor ColorBackground;
-		
+
+		// Options
+		private bool invertLineVisibility; // CTRL to toggle
+		private bool editSectors; // SHIFT to toggle
+
 		#endregion
 
 		#region ================== Properties
 
-		public override object HighlightedObject { get { return highlighted; } }
+		public override object HighlightedObject
+		{
+			get
+			{
+				if(highlightedLine != null)
+					return highlightedLine;
+				else
+					return highlightedSector;
+			}
+		}
 		
 		#endregion
 
@@ -95,7 +109,8 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			menusform.ShowHiddenLines = General.Settings.ReadPluginSetting("automapmode.showhiddenlines", false);
 			menusform.ShowSecretSectors = General.Settings.ReadPluginSetting("automapmode.showsecretsectors", false);
 			menusform.ShowLocks = General.Settings.ReadPluginSetting("automapmode.showlocks", true);
-			menusform.ColorPreset = (ColorPreset)General.Settings.ReadPluginSetting("automapmode.colorpreset", (int)ColorPreset.DOOM);
+            menusform.ShowTextures = General.Settings.ReadPluginSetting("automapmode.showtextures", true);
+            menusform.ColorPreset = (ColorPreset)General.Settings.ReadPluginSetting("automapmode.colorpreset", (int)ColorPreset.DOOM);
 
 			// Handle events
 			menusform.OnShowHiddenLinesChanged += delegate
@@ -106,8 +121,9 @@ namespace CodeImp.DoomBuilder.AutomapMode
 
 			menusform.OnShowSecretSectorsChanged += delegate { General.Interface.RedrawDisplay(); };
 			menusform.OnShowLocksChanged += delegate { General.Interface.RedrawDisplay(); };
+            menusform.OnShowTexturesChanged += delegate { General.Interface.RedrawDisplay(); };
 
-			menusform.OnColorPresetChanged += delegate
+            menusform.OnColorPresetChanged += delegate
 			{
 				ApplyColorPreset(menusform.ColorPreset);
 				General.Interface.RedrawDisplay();
@@ -121,26 +137,48 @@ namespace CodeImp.DoomBuilder.AutomapMode
 
 		#region ================== Methods
 
-		// This highlights a new item
-		private void Highlight(Linedef l)
+		// Update the current highlight
+		private void UpdateHighlight()
+		{
+			if (EditSectors())
+			{
+				// Get the nearest sector to the cursor; don't factor in the
+				// highlight range since we really just want to capture
+				// whichever sector is under the cursor.
+				Sector s = General.Map.Map.GetSectorByCoordinates(mousemappos);
+
+				if (s != highlightedSector) HighlightSector(s);
+			}
+			else
+			{
+				// Find the nearest linedef within highlight range
+				Linedef l = MapSet.NearestLinedefRange(validlinedefs, mousemappos, BuilderPlug.Me.HighlightRange / renderer.Scale);
+
+				// Highlight if not the same
+				if (l != highlightedLine) HighlightLine(l);
+			}
+		}
+
+		// This highlights a new line
+		private void HighlightLine(Linedef l)
 		{
 			// Update display
 			if(renderer.StartPlotter(false))
 			{
 				// Undraw previous highlight
-				if((highlighted != null) && !highlighted.IsDisposed)
+				if((highlightedLine != null) && !highlightedLine.IsDisposed)
 				{
-					PixelColor c = LinedefIsValid(highlighted) ? DetermineLinedefColor(highlighted) : PixelColor.Transparent;
-					renderer.PlotLine(highlighted.Start.Position, highlighted.End.Position, c, LINE_LENGTH_SCALER);
+					PixelColor c = LinedefIsValid(highlightedLine) ? DetermineLinedefColor(highlightedLine) : PixelColor.Transparent;
+					renderer.PlotLine(highlightedLine.Start.Position, highlightedLine.End.Position, c, LINE_LENGTH_SCALER);
 				}
 
 				// Set new highlight
-				highlighted = l;
+				highlightedLine = l;
 
 				// Render highlighted item
-				if((highlighted != null) && !highlighted.IsDisposed && LinedefIsValid(highlighted))
+				if((highlightedLine != null) && !highlightedLine.IsDisposed && LinedefIsValid(highlightedLine))
 				{
-					renderer.PlotLine(highlighted.Start.Position, highlighted.End.Position, General.Colors.InfoLine, LINE_LENGTH_SCALER);
+					renderer.PlotLine(highlightedLine.Start.Position, highlightedLine.End.Position, General.Colors.InfoLine, LINE_LENGTH_SCALER);
 				}
 
 				// Done
@@ -149,8 +187,48 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			}
 
 			// Show highlight info
-			if((highlighted != null) && !highlighted.IsDisposed)
-				General.Interface.ShowLinedefInfo(highlighted);
+			if((highlightedLine != null) && !highlightedLine.IsDisposed)
+				General.Interface.ShowLinedefInfo(highlightedLine);
+			else
+				General.Interface.HideInfo();
+		}
+
+		// This highlights a new sector
+		private void HighlightSector(Sector sector)
+		{
+			// Update display
+			if (renderer.StartPlotter(false))
+			{
+				// Undraw previous highlight
+				if ((highlightedSector != null) && !highlightedSector.IsDisposed)
+				{
+					foreach(Sidedef sd in highlightedSector.Sidedefs)
+					{
+						if ((sd.Line != null) && !sd.Line.IsDisposed)
+						{
+							PixelColor c = LinedefIsValid(sd.Line) ? DetermineLinedefColor(sd.Line) : PixelColor.Transparent;
+							renderer.PlotLine(sd.Line.Start.Position, sd.Line.End.Position, c, LINE_LENGTH_SCALER);
+						}
+					}
+				}
+
+				// Set new highlight
+				highlightedSector = sector;
+
+				// Render highlighted sector's lines
+				if ((highlightedSector != null) && !highlightedSector.IsDisposed)
+					foreach (Sidedef sd in highlightedSector.Sidedefs)
+						if ((sd.Line != null) && !sd.Line.IsDisposed)
+							renderer.PlotLine(sd.Line.Start.Position, sd.Line.End.Position, General.Colors.Highlight, LINE_LENGTH_SCALER);
+
+				// Done
+				renderer.Finish();
+				renderer.Present();
+			}
+
+			// Show highlight info
+			if ((highlightedSector != null) && !highlightedSector.IsDisposed)
+				General.Interface.ShowSectorInfo(highlightedSector);
 			else
 				General.Interface.HideInfo();
 		}
@@ -193,14 +271,14 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			if(ld.Front.Sector.CeilHeight == ld.Back.Sector.CeilHeight && ld.Front.Sector.FloorHeight == ld.Back.Sector.FloorHeight)
 				return ColorMatchingHeight;
 
-			if(menusform.ShowHiddenLines ^ General.Interface.CtrlState) return ColorInvisible; 
+			if(menusform.ShowHiddenLines ^ invertLineVisibility) return ColorInvisible; 
 
 			return new PixelColor(255, 255, 255, 255);
 		}
 
 		private bool LinedefIsValid(Linedef ld)
 		{
-			if(menusform.ShowHiddenLines ^ General.Interface.CtrlState) return true;
+			if(menusform.ShowHiddenLines ^ invertLineVisibility) return true;
 			if(ld.IsFlagSet(BuilderPlug.Me.HiddenFlag)) return false;
 			if(ld.Back == null || ld.Front == null || ld.IsFlagSet(BuilderPlug.Me.SecretFlag)) return true;
 			if(ld.Back != null && ld.Front != null && (ld.Front.Sector.FloorHeight != ld.Back.Sector.FloorHeight || ld.Front.Sector.CeilHeight != ld.Back.Sector.CeilHeight)) return true;
@@ -208,6 +286,21 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			return false;
 		}
 
+		private bool SectorIsVisible(Sector s)
+		{
+			return(s != null && !s.IsFlagSet("hidden"));
+		}
+
+		private bool ShowTextures()
+		{
+			return menusform.ShowTextures || EditSectors();
+		}
+
+		private bool EditSectors()
+		{
+			return editSectors && General.Map.UDMF;
+		}
+
 		//mxd
 		private static bool SectorIsSecret(Sector s)
 		{
@@ -318,11 +411,14 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			base.OnEngage();
 			renderer.DrawMapCenter = false; //mxd
 
-			// Automap presentation without the surfaces
+			// Automap presentation; now draws surfaces for textured mode support,
+			// but the surfaces are covered up with a background layer.
 			automappresentation = new CustomPresentation();
+			automappresentation.AddLayer(new PresentLayer(RendererLayer.Surface, BlendingMode.Mask));
 			automappresentation.AddLayer(new PresentLayer(RendererLayer.Overlay, BlendingMode.Mask));
 			automappresentation.AddLayer(new PresentLayer(RendererLayer.Grid, BlendingMode.Mask));
 			automappresentation.AddLayer(new PresentLayer(RendererLayer.Geometry, BlendingMode.Alpha, 1f, true));
+			automappresentation.SkipHiddenSectors = true;
 			renderer.SetPresentation(automappresentation);
 
 			UpdateValidLinedefs();
@@ -341,7 +437,8 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			General.Settings.WritePluginSetting("automapmode.showhiddenlines", menusform.ShowHiddenLines);
 			General.Settings.WritePluginSetting("automapmode.showsecretsectors", menusform.ShowSecretSectors);
 			General.Settings.WritePluginSetting("automapmode.showlocks", menusform.ShowLocks);
-			General.Settings.WritePluginSetting("automapmode.colorpreset", (int)menusform.ColorPreset);
+            General.Settings.WritePluginSetting("automapmode.showtextures", menusform.ShowTextures);
+            General.Settings.WritePluginSetting("automapmode.colorpreset", (int)menusform.ColorPreset);
 
 			//mxd. Hide UI
 			menusform.Unregister();
@@ -382,9 +479,9 @@ namespace CodeImp.DoomBuilder.AutomapMode
 						renderer.PlotLine(ld.Start.Position, ld.End.Position, DetermineLinedefColor(ld), LINE_LENGTH_SCALER);
 				}
 
-				if((highlighted != null) && !highlighted.IsDisposed && LinedefIsValid(highlighted))
+				if((highlightedLine != null) && !highlightedLine.IsDisposed && LinedefIsValid(highlightedLine))
 				{
-					renderer.PlotLine(highlighted.Start.Position, highlighted.End.Position, General.Colors.InfoLine, LINE_LENGTH_SCALER);
+					renderer.PlotLine(highlightedLine.Start.Position, highlightedLine.End.Position, General.Colors.InfoLine, LINE_LENGTH_SCALER);
 				}
 
 				renderer.Finish();
@@ -393,8 +490,10 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			//mxd. Render background
 			if(renderer.StartOverlay(true))
 			{
-				RectangleF screenrect = new RectangleF(0, 0, General.Interface.Display.Width, General.Interface.Display.Height);
-				renderer.RenderRectangleFilled(screenrect, ColorBackground, false);
+				if(!ShowTextures()) {
+					RectangleF screenrect = new RectangleF(0, 0, General.Interface.Display.Width, General.Interface.Display.Height);
+					renderer.RenderRectangleFilled(screenrect, ColorBackground, false);
+				}
 				renderer.Finish();
 			}
 
@@ -403,28 +502,44 @@ namespace CodeImp.DoomBuilder.AutomapMode
 
 		protected override void OnSelectEnd()
 		{
-			// Item highlighted?
-			if((highlighted != null) && !highlighted.IsDisposed)
+			// Line highlighted?
+			if((highlightedLine != null) && !highlightedLine.IsDisposed)
 			{
 				General.Map.UndoRedo.CreateUndo("Toggle \"Shown as 1-sided on automap\" linedef flag");
 
 				// Toggle flag
-				highlighted.SetFlag(BuilderPlug.Me.SecretFlag, !highlighted.IsFlagSet(BuilderPlug.Me.SecretFlag));
+				highlightedLine.SetFlag(BuilderPlug.Me.SecretFlag, !highlightedLine.IsFlagSet(BuilderPlug.Me.SecretFlag));
 				UpdateValidLinedefs();
 			}
 
+			// Sector highlighted?
+			if((highlightedSector != null) && !highlightedSector.IsDisposed)
+			{
+				General.Map.UndoRedo.CreateUndo("Toggle \"Not shown on textured automap\" sector flag");
+
+				// Toggle flag
+				highlightedSector.SetFlag("hidden", !highlightedSector.IsFlagSet("hidden"));
+
+				// Redraw the universe
+				General.Map.Map.Update();
+				General.Interface.RedrawDisplay();
+
+				// Re-highlight the sector since it gets lost after RedrawDisplay
+				HighlightSector(highlightedSector);
+			}
+
 			base.OnSelectEnd();
 		}
 		
 		protected override void OnEditEnd()
 		{
-			// Item highlighted?
-			if((highlighted != null) && !highlighted.IsDisposed)
+			// Line highlighted?
+			if ((highlightedLine != null) && !highlightedLine.IsDisposed)
 			{
 				General.Map.UndoRedo.CreateUndo("Toggle \"Not shown on automap\" linedef flag");
 
 				// Toggle flag
-				highlighted.SetFlag(BuilderPlug.Me.HiddenFlag, !highlighted.IsFlagSet(BuilderPlug.Me.HiddenFlag));
+				highlightedLine.SetFlag(BuilderPlug.Me.HiddenFlag, !highlightedLine.IsFlagSet(BuilderPlug.Me.HiddenFlag));
 				UpdateValidLinedefs();
 				General.Interface.RedrawDisplay();
 			}
@@ -440,11 +555,7 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			// Not holding any buttons?
 			if(e.Button == MouseButtons.None)
 			{
-				// Find the nearest linedef within highlight range
-				Linedef l = MapSet.NearestLinedefRange(validlinedefs, mousemappos, BuilderPlug.Me.HighlightRange / renderer.Scale);
-
-				// Highlight if not the same
-				if(l != highlighted) Highlight(l);
+				UpdateHighlight();
 			}
 		}
 
@@ -454,29 +565,42 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			base.OnMouseLeave(e);
 
 			// Highlight nothing
-			Highlight(null);
+			HighlightLine(null);
+			HighlightSector(null);
 		}
 
+		// Keyboard input handling; toggles a couple of options
+
 		public override void OnKeyDown(KeyEventArgs e)
 		{
 			base.OnKeyDown(e);
 
-			if(e.Control)
-			{
-				UpdateValidLinedefs();
-				General.Interface.RedrawDisplay();
-			}
+			UpdateOptions();
 		}
 
 		public override void OnKeyUp(KeyEventArgs e)
 		{
 			base.OnKeyUp(e);
 
-			if(!e.Control)
+			UpdateOptions();
+		}
+
+		private void UpdateOptions()
+		{
+			if(invertLineVisibility != General.Interface.CtrlState)
 			{
+				invertLineVisibility = General.Interface.CtrlState;
 				UpdateValidLinedefs();
 				General.Interface.RedrawDisplay();
 			}
+			if(editSectors != General.Interface.ShiftState)
+			{
+				editSectors = General.Interface.ShiftState;
+				HighlightLine(null);
+				HighlightSector(null);
+				General.Interface.RedrawDisplay();
+				UpdateHighlight();
+			}
 		}
 
 		#endregion
diff --git a/Source/Plugins/AutomapMode/AutomapMode.csproj b/Source/Plugins/AutomapMode/AutomapMode.csproj
index b934f67b71ad05542fe9442ae5edfc321d8bba91..b8adf955d3bb57cd81114d6afa1e85fc7b1d743c 100755
--- a/Source/Plugins/AutomapMode/AutomapMode.csproj
+++ b/Source/Plugins/AutomapMode/AutomapMode.csproj
@@ -122,4 +122,7 @@
   <ItemGroup>
     <None Include="Resources\ShowLocks.png" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\ShowTextures.png" />
+  </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/Source/Plugins/AutomapMode/BuilderPlug.cs b/Source/Plugins/AutomapMode/BuilderPlug.cs
index 844232c9c7322f9dc47ab2614990a3f88995bfbc..b5ab192dc2bc98f69ba8303caf3482c4ca3f84be 100755
--- a/Source/Plugins/AutomapMode/BuilderPlug.cs
+++ b/Source/Plugins/AutomapMode/BuilderPlug.cs
@@ -1,15 +1,15 @@
-
+
 #region ================== Copyright (c) 2016 Boris Iwanski
 
-/*
- * Copyright (c) 2016 Boris Iwanski https://github.com/biwa/automapmode
- * This program is released under GNU General Public License
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
+/*
+ * Copyright (c) 2016 Boris Iwanski https://github.com/biwa/automapmode
+ * This program is released under GNU General Public License
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
  */
 
 #endregion
@@ -17,70 +17,70 @@
 #region ================== Namespaces
 
 using CodeImp.DoomBuilder.Plugins;
-
-#endregion
-
-namespace CodeImp.DoomBuilder.AutomapMode
-{
-	//
-	// MANDATORY: The plug!
-	// This is an important class to the Doom Builder core. Every plugin must
-	// have exactly 1 class that inherits from Plug. When the plugin is loaded,
-	// this class is instantiated and used to receive events from the core.
-	// Make sure the class is public, because only public classes can be seen
-	// by the core.
-	//
-
-	public class BuilderPlug : Plug
-	{
-		#region ================== Variables
-
+
+#endregion
+
+namespace CodeImp.DoomBuilder.AutomapMode
+{
+	//
+	// MANDATORY: The plug!
+	// This is an important class to the Doom Builder core. Every plugin must
+	// have exactly 1 class that inherits from Plug. When the plugin is loaded,
+	// this class is instantiated and used to receive events from the core.
+	// Make sure the class is public, because only public classes can be seen
+	// by the core.
+	//
+
+	public class BuilderPlug : Plug
+	{
+		#region ================== Variables
+
 		private float highlightrange;
 
 		// Static instance. We can't use a real static class, because BuilderPlug must
 		// be instantiated by the core, so we keep a static reference. (this technique
 		// should be familiar to object-oriented programmers)
 		private static BuilderPlug me;
-
-		#endregion
-
-		#region ================== Properties
-
-		public float HighlightRange { get { return highlightrange; } }
-		public string SecretFlag { get { return General.Map.UDMF ? "secret" : "32"; } }
+
+		#endregion
+
+		#region ================== Properties
+
+		public float HighlightRange { get { return highlightrange; } }
+		public string SecretFlag { get { return General.Map.UDMF ? "secret" : "32"; } }
 		public string HiddenFlag { get { return General.Map.UDMF ? "dontdraw" : "128"; } }
 
 		// This plugin relies on some functionality that wasn't there in older versions
 		public override int MinimumRevision { get { return 2651; } }
 
 		// Static property to access the BuilderPlug
-		public static BuilderPlug Me { get { return me; } }
-
+		public static BuilderPlug Me { get { return me; } }
+
 		#endregion
 
 		#region ================== Methods
 
-		// This event is called when the plugin is initialized
-		public override void OnInitialize()
-		{
-			base.OnInitialize();
-
-			// This binds the methods in this class that have the BeginAction
-			// and EndAction attributes with their actions. Without this, the
-			// attributes are useless. Note that in classes derived from EditMode
-			// this is not needed, because they are bound automatically when the
-			// editing mode is engaged.
-			General.Actions.BindMethods(this);
-
-			// TODO: Add DB2 version check so that old DB2 versions won't crash
-			// General.ErrorLogger.Add(ErrorType.Error, "zomg!");
-
-			// Keep a static reference
-			me = this;
-
-			LoadSettings();
-		}
-
+		// This event is called when the plugin is initialized
+		public override void OnInitialize()
+		{
+			base.OnInitialize();
+
+			// This binds the methods in this class that have the BeginAction
+			// and EndAction attributes with their actions. Without this, the
+			// attributes are useless. Note that in classes derived from EditMode
+			// this is not needed, because they are bound automatically when the
+			// editing mode is engaged.
+			General.Actions.BindMethods(this);
+
+			// TODO: Add DB2 version check so that old DB2 versions won't crash
+			// General.ErrorLogger.Add(ErrorType.Error, "zomg!");
+
+			// Keep a static reference
+			me = this;
+
+			LoadSettings();
+		}
+
 		//mxd
 		public override void OnMapOpenEnd()
 		{
@@ -92,8 +92,8 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			}
 			
 			base.OnMapOpenEnd();
-		}
-
+		}
+
 		//mxd
 		public override void OnMapNewEnd()
 		{
@@ -105,22 +105,22 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			}
 			
 			base.OnMapNewEnd();
-		}
-
-		// This is called when the plugin is terminated
-		public override void Dispose()
-		{
-			base.Dispose();
-
-			// This must be called to remove bound methods for actions.
-			General.Actions.UnbindMethods(this);
-		}
-
-		private void LoadSettings()
-		{
-			highlightrange = General.Settings.ReadPluginSetting("buildermodes", "highlightrange", 20);
-		}
-
-		#endregion
-	}
-}
+		}
+
+		// This is called when the plugin is terminated
+		public override void Dispose()
+		{
+			base.Dispose();
+
+			// This must be called to remove bound methods for actions.
+			General.Actions.UnbindMethods(this);
+		}
+
+		private void LoadSettings()
+		{
+			highlightrange = General.Settings.ReadPluginSetting("buildermodes", "highlightrange", 20);
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Plugins/AutomapMode/Interface/MenusForm.Designer.cs b/Source/Plugins/AutomapMode/Interface/MenusForm.Designer.cs
index 28bcf0947eb54adb1427f5ead51b17f607d66caa..7e8f7f8ffd0986d3fd46413de808965c6845f857 100755
--- a/Source/Plugins/AutomapMode/Interface/MenusForm.Designer.cs
+++ b/Source/Plugins/AutomapMode/Interface/MenusForm.Designer.cs
@@ -28,96 +28,108 @@
 		/// </summary>
 		private void InitializeComponent()
 		{
-			this.toolStrip1 = new System.Windows.Forms.ToolStrip();
-			this.colorpresetseparator = new System.Windows.Forms.ToolStripSeparator();
-			this.colorpresetlabel = new System.Windows.Forms.ToolStripLabel();
-			this.colorpreset = new System.Windows.Forms.ToolStripComboBox();
-			this.showhiddenlines = new System.Windows.Forms.ToolStripButton();
-			this.showsecretsectors = new System.Windows.Forms.ToolStripButton();
-			this.showlocks = new System.Windows.Forms.ToolStripButton();
-			this.toolStrip1.SuspendLayout();
-			this.SuspendLayout();
-			// 
-			// toolStrip1
-			// 
-			this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.toolStrip1 = new System.Windows.Forms.ToolStrip();
+            this.showhiddenlines = new System.Windows.Forms.ToolStripButton();
+            this.showsecretsectors = new System.Windows.Forms.ToolStripButton();
+            this.showlocks = new System.Windows.Forms.ToolStripButton();
+            this.colorpresetseparator = new System.Windows.Forms.ToolStripSeparator();
+            this.colorpresetlabel = new System.Windows.Forms.ToolStripLabel();
+            this.colorpreset = new System.Windows.Forms.ToolStripComboBox();
+            this.showtextures = new System.Windows.Forms.ToolStripButton();
+            this.toolStrip1.SuspendLayout();
+            this.SuspendLayout();
+            // 
+            // toolStrip1
+            // 
+            this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
             this.showhiddenlines,
             this.showsecretsectors,
             this.showlocks,
+            this.showtextures,
             this.colorpresetseparator,
             this.colorpresetlabel,
             this.colorpreset});
-			this.toolStrip1.Location = new System.Drawing.Point(0, 0);
-			this.toolStrip1.Name = "toolStrip1";
-			this.toolStrip1.Size = new System.Drawing.Size(731, 25);
-			this.toolStrip1.TabIndex = 0;
-			this.toolStrip1.Text = "toolStrip1";
-			// 
-			// colorpresetseparator
-			// 
-			this.colorpresetseparator.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
-			this.colorpresetseparator.Name = "colorpresetseparator";
-			this.colorpresetseparator.Size = new System.Drawing.Size(6, 25);
-			// 
-			// colorpresetlabel
-			// 
-			this.colorpresetlabel.Name = "colorpresetlabel";
-			this.colorpresetlabel.Size = new System.Drawing.Size(74, 22);
-			this.colorpresetlabel.Text = "Color preset:";
-			// 
-			// colorpreset
-			// 
-			this.colorpreset.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
-			this.colorpreset.Items.AddRange(new object[] {
+            this.toolStrip1.Location = new System.Drawing.Point(0, 0);
+            this.toolStrip1.Name = "toolStrip1";
+            this.toolStrip1.Size = new System.Drawing.Size(731, 25);
+            this.toolStrip1.TabIndex = 0;
+            this.toolStrip1.Text = "toolStrip1";
+            // 
+            // showhiddenlines
+            // 
+            this.showhiddenlines.CheckOnClick = true;
+            this.showhiddenlines.Image = global::CodeImp.DoomBuilder.AutomapMode.Properties.Resources.ShowHiddenLines;
+            this.showhiddenlines.ImageTransparentColor = System.Drawing.Color.Magenta;
+            this.showhiddenlines.Margin = new System.Windows.Forms.Padding(0, 1, 2, 2);
+            this.showhiddenlines.Name = "showhiddenlines";
+            this.showhiddenlines.Size = new System.Drawing.Size(123, 22);
+            this.showhiddenlines.Text = "Show hidden lines";
+            this.showhiddenlines.CheckedChanged += new System.EventHandler(this.showhiddenlines_CheckedChanged);
+            // 
+            // showsecretsectors
+            // 
+            this.showsecretsectors.CheckOnClick = true;
+            this.showsecretsectors.Image = global::CodeImp.DoomBuilder.AutomapMode.Properties.Resources.ShowSecrets;
+            this.showsecretsectors.Margin = new System.Windows.Forms.Padding(0, 1, 2, 2);
+            this.showsecretsectors.Name = "showsecretsectors";
+            this.showsecretsectors.Size = new System.Drawing.Size(95, 22);
+            this.showsecretsectors.Text = "Show secrets";
+            this.showsecretsectors.CheckedChanged += new System.EventHandler(this.showsecretsectors_CheckedChanged);
+            // 
+            // showlocks
+            // 
+            this.showlocks.CheckOnClick = true;
+            this.showlocks.Image = global::CodeImp.DoomBuilder.AutomapMode.Properties.Resources.ShowLocks;
+            this.showlocks.ImageTransparentColor = System.Drawing.Color.Magenta;
+            this.showlocks.Name = "showlocks";
+            this.showlocks.Size = new System.Drawing.Size(86, 22);
+            this.showlocks.Text = "Show locks";
+            this.showlocks.CheckedChanged += new System.EventHandler(this.showlocks_CheckedChanged);
+            // 
+            // colorpresetseparator
+            // 
+            this.colorpresetseparator.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
+            this.colorpresetseparator.Name = "colorpresetseparator";
+            this.colorpresetseparator.Size = new System.Drawing.Size(6, 25);
+            // 
+            // colorpresetlabel
+            // 
+            this.colorpresetlabel.Name = "colorpresetlabel";
+            this.colorpresetlabel.Size = new System.Drawing.Size(74, 22);
+            this.colorpresetlabel.Text = "Color preset:";
+            // 
+            // colorpreset
+            // 
+            this.colorpreset.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.colorpreset.Items.AddRange(new object[] {
             "Doom",
             "Hexen",
             "Strife"});
-			this.colorpreset.Name = "colorpreset";
-			this.colorpreset.Size = new System.Drawing.Size(75, 25);
-			this.colorpreset.SelectedIndexChanged += new System.EventHandler(this.colorpreset_SelectedIndexChanged);
-			// 
-			// showhiddenlines
-			// 
-			this.showhiddenlines.CheckOnClick = true;
-			this.showhiddenlines.Image = global::CodeImp.DoomBuilder.AutomapMode.Properties.Resources.ShowHiddenLines;
-			this.showhiddenlines.ImageTransparentColor = System.Drawing.Color.Magenta;
-			this.showhiddenlines.Margin = new System.Windows.Forms.Padding(0, 1, 2, 2);
-			this.showhiddenlines.Name = "showhiddenlines";
-			this.showhiddenlines.Size = new System.Drawing.Size(123, 22);
-			this.showhiddenlines.Text = "Show hidden lines";
-			this.showhiddenlines.CheckedChanged += new System.EventHandler(this.showhiddenlines_CheckedChanged);
-			// 
-			// showsecretsectors
-			// 
-			this.showsecretsectors.CheckOnClick = true;
-			this.showsecretsectors.Image = global::CodeImp.DoomBuilder.AutomapMode.Properties.Resources.ShowSecrets;
-			this.showsecretsectors.Margin = new System.Windows.Forms.Padding(0, 1, 2, 2);
-			this.showsecretsectors.Name = "showsecretsectors";
-			this.showsecretsectors.Size = new System.Drawing.Size(95, 22);
-			this.showsecretsectors.Text = "Show secrets";
-			this.showsecretsectors.CheckedChanged += new System.EventHandler(this.showsecretsectors_CheckedChanged);
-			// 
-			// showlocks
-			// 
-			this.showlocks.CheckOnClick = true;
-			this.showlocks.Image = global::CodeImp.DoomBuilder.AutomapMode.Properties.Resources.ShowLocks;
-			this.showlocks.ImageTransparentColor = System.Drawing.Color.Magenta;
-			this.showlocks.Name = "showlocks";
-			this.showlocks.Size = new System.Drawing.Size(86, 22);
-			this.showlocks.Text = "Show locks";
-			this.showlocks.CheckedChanged += new System.EventHandler(this.showlocks_CheckedChanged);
-			// 
-			// MenusForm
-			// 
-			this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
-			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
-			this.Controls.Add(this.toolStrip1);
-			this.Name = "MenusForm";
-			this.Size = new System.Drawing.Size(731, 65);
-			this.toolStrip1.ResumeLayout(false);
-			this.toolStrip1.PerformLayout();
-			this.ResumeLayout(false);
-			this.PerformLayout();
+            this.colorpreset.Name = "colorpreset";
+            this.colorpreset.Size = new System.Drawing.Size(75, 25);
+            this.colorpreset.SelectedIndexChanged += new System.EventHandler(this.colorpreset_SelectedIndexChanged);
+            // 
+            // showtextures
+            // 
+            this.showtextures.CheckOnClick = true;
+            this.showtextures.Image = global::CodeImp.DoomBuilder.AutomapMode.Properties.Resources.ShowTextures;
+            this.showtextures.ImageTransparentColor = System.Drawing.Color.Magenta;
+            this.showtextures.Name = "showtextures";
+            this.showtextures.Size = new System.Drawing.Size(101, 22);
+            this.showtextures.Text = "Show textures";
+            this.showtextures.CheckedChanged += new System.EventHandler(this.showtextures_CheckedChanged);
+            // 
+            // MenusForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add(this.toolStrip1);
+            this.Name = "MenusForm";
+            this.Size = new System.Drawing.Size(731, 65);
+            this.toolStrip1.ResumeLayout(false);
+            this.toolStrip1.PerformLayout();
+            this.ResumeLayout(false);
+            this.PerformLayout();
 
 		}
 
@@ -130,5 +142,6 @@
 		private System.Windows.Forms.ToolStripLabel colorpresetlabel;
 		private System.Windows.Forms.ToolStripComboBox colorpreset;
 		private System.Windows.Forms.ToolStripButton showlocks;
-	}
+        private System.Windows.Forms.ToolStripButton showtextures;
+    }
 }
diff --git a/Source/Plugins/AutomapMode/Interface/MenusForm.cs b/Source/Plugins/AutomapMode/Interface/MenusForm.cs
index 7fef864476f1d25da6d2d11380baabaa6518308b..93781797a2d4ce5bcdb607d3ef4175f61f4e9e1a 100755
--- a/Source/Plugins/AutomapMode/Interface/MenusForm.cs
+++ b/Source/Plugins/AutomapMode/Interface/MenusForm.cs
@@ -8,11 +8,13 @@ namespace CodeImp.DoomBuilder.AutomapMode
 		public event EventHandler OnShowHiddenLinesChanged;
 		public event EventHandler OnShowSecretSectorsChanged;
 		public event EventHandler OnShowLocksChanged;
-		internal event EventHandler OnColorPresetChanged;
+        public event EventHandler OnShowTexturesChanged;
+        internal event EventHandler OnColorPresetChanged;
 
 		public bool ShowHiddenLines { get { return showhiddenlines.Checked; } set { showhiddenlines.Checked = value; } }
 		public bool ShowSecretSectors { get { return showsecretsectors.Checked; } set { showsecretsectors.Checked = value; } }
 		public bool ShowLocks { get { return showlocks.Checked; } set { showlocks.Checked = value; } }
+		public bool ShowTextures { get { return showtextures.Checked; } set { showtextures.Checked = value; } }
 		internal AutomapMode.ColorPreset ColorPreset { get { return (AutomapMode.ColorPreset)colorpreset.SelectedIndex; } set { colorpreset.SelectedIndex = (int)value; } }
 		
 		public MenusForm()
@@ -26,6 +28,7 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			General.Interface.AddButton(showhiddenlines);
 			General.Interface.AddButton(showsecretsectors);
 			if(!General.Map.DOOM) General.Interface.AddButton(showlocks);
+			General.Interface.AddButton(showtextures);
 			General.Interface.AddButton(colorpresetseparator);
 			General.Interface.AddButton(colorpresetlabel);
 			General.Interface.AddButton(colorpreset);
@@ -39,7 +42,8 @@ namespace CodeImp.DoomBuilder.AutomapMode
 			General.Interface.RemoveButton(colorpresetlabel);
 			General.Interface.RemoveButton(colorpresetseparator);
 			General.Interface.RemoveButton(showlocks);
-			General.Interface.RemoveButton(showsecretsectors);
+            General.Interface.RemoveButton(showtextures);
+            General.Interface.RemoveButton(showsecretsectors);
 			General.Interface.RemoveButton(showhiddenlines);
 			General.Interface.EndToolbarUpdate(); //mxd
 		}
@@ -57,9 +61,14 @@ namespace CodeImp.DoomBuilder.AutomapMode
 		private void showlocks_CheckedChanged(object sender, EventArgs e)
 		{
 			if(OnShowLocksChanged != null) OnShowLocksChanged(showlocks.Checked, EventArgs.Empty);
-		}
+        }
+
+        private void showtextures_CheckedChanged(object sender, EventArgs e)
+        {
+            if (OnShowTexturesChanged != null) OnShowTexturesChanged(showtextures.Checked, EventArgs.Empty);
+        }
 
-		private void colorpreset_SelectedIndexChanged(object sender, EventArgs e)
+        private void colorpreset_SelectedIndexChanged(object sender, EventArgs e)
 		{
 			if(OnColorPresetChanged != null) OnColorPresetChanged(colorpreset.SelectedIndex, EventArgs.Empty);
 		}
diff --git a/Source/Plugins/AutomapMode/Properties/Resources.Designer.cs b/Source/Plugins/AutomapMode/Properties/Resources.Designer.cs
index b2dedc6fc339963fa04ba9d5e39c7fa41b0ef705..14565e5170cc790051dc84877fc5c99b917a8962 100755
--- a/Source/Plugins/AutomapMode/Properties/Resources.Designer.cs
+++ b/Source/Plugins/AutomapMode/Properties/Resources.Designer.cs
@@ -89,5 +89,15 @@ namespace CodeImp.DoomBuilder.AutomapMode.Properties {
                 return ((System.Drawing.Bitmap)(obj));
             }
         }
+        
+        /// <summary>
+        ///   Looks up a localized resource of type System.Drawing.Bitmap.
+        /// </summary>
+        internal static System.Drawing.Bitmap ShowTextures {
+            get {
+                object obj = ResourceManager.GetObject("ShowTextures", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
     }
 }
diff --git a/Source/Plugins/AutomapMode/Properties/Resources.resx b/Source/Plugins/AutomapMode/Properties/Resources.resx
index 80e0c05ebbf54dfaa2b133f59cc192ed4cf7850a..3feae91bcabe86948e5aa12a17d1c20138d56492 100755
--- a/Source/Plugins/AutomapMode/Properties/Resources.resx
+++ b/Source/Plugins/AutomapMode/Properties/Resources.resx
@@ -127,4 +127,7 @@
   <data name="ShowLocks" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\ShowLocks.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
+  <data name="ShowTextures" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\ShowTextures.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
 </root>
\ No newline at end of file
diff --git a/Source/Plugins/AutomapMode/Resources/Hints.cfg b/Source/Plugins/AutomapMode/Resources/Hints.cfg
index f004af3ebacae77f2619400b84aea9577636b6ef..b5810457d6d9b2248b712eb8cc13de44922a53c9 100755
--- a/Source/Plugins/AutomapMode/Resources/Hints.cfg
+++ b/Source/Plugins/AutomapMode/Resources/Hints.cfg
@@ -8,4 +8,5 @@ class AutomapMode
 group general
 "<b>Left click</b> on a line to toggle the <b>'Shown as 1-sided on automap'</b> flag."
 "<b>Right click</b> on a line to toggle the <b>'Not shown on automap'</b> flag."
-"Hold <b>Ctrl</b> to toggle the display of the lines that are not drawn, either because there was no height change (gray) or the <b>'Not shown on automap'</b> flag is set (light gray)."
\ No newline at end of file
+"Hold <b>Ctrl</b> to toggle the display of the lines that are not drawn, either because there was no height change (gray) or the <b>'Not shown on automap'</b> flag is set (light gray)."
+"Hold <b>Shift</b> to highlight and edit sectors, rather than lines. Clicking on sectors will toggle <b>'Not shown on textured automap'</b> flag. This action is only available for UDMF maps."
\ No newline at end of file
diff --git a/Source/Plugins/AutomapMode/Resources/ShowTextures.png b/Source/Plugins/AutomapMode/Resources/ShowTextures.png
new file mode 100644
index 0000000000000000000000000000000000000000..d3ae3a9a4742b968c0e9db9f9d6f816314c83f85
Binary files /dev/null and b/Source/Plugins/AutomapMode/Resources/ShowTextures.png differ
diff --git a/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs b/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs
index 56a336511e387a54caec40b939d366445f3610b1..fdea61e97986609c9162dd555257687e272cf692 100755
--- a/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs
+++ b/Source/Plugins/BuilderEffects/Interface/MenusForm.Designer.cs
@@ -32,8 +32,8 @@
 			this.menujitter = new System.Windows.Forms.ToolStripMenuItem();
 			this.menusectorflatshading = new System.Windows.Forms.ToolStripMenuItem();
 			this.toolStrip = new System.Windows.Forms.ToolStrip();
-			this.buttonjitter = new System.Windows.Forms.ToolStripButton();
-			this.buttonsectorflatshading = new System.Windows.Forms.ToolStripButton();
+			this.buttonjitter = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonsectorflatshading = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.menuStrip.SuspendLayout();
 			this.toolStrip.SuspendLayout();
 			this.SuspendLayout();
@@ -151,10 +151,10 @@
 		private System.Windows.Forms.ToolStripMenuItem stripimport;
 		private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1;
 		private System.Windows.Forms.ToolStrip toolStrip;
-		private System.Windows.Forms.ToolStripButton buttonjitter;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonjitter;
 		private System.Windows.Forms.ToolStripMenuItem stripmodes;
 		private System.Windows.Forms.ToolStripMenuItem menujitter;
 		private System.Windows.Forms.ToolStripMenuItem menusectorflatshading;
-		private System.Windows.Forms.ToolStripButton buttonsectorflatshading;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonsectorflatshading;
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/BuilderModes/BuilderModes.csproj b/Source/Plugins/BuilderModes/BuilderModes.csproj
index 5afa34fd31ccc9ccb43e4441ab839eea08df525a..20ee51293bc61914a8ea45fc371bd9f23fffe175 100755
--- a/Source/Plugins/BuilderModes/BuilderModes.csproj
+++ b/Source/Plugins/BuilderModes/BuilderModes.csproj
@@ -756,6 +756,9 @@
   <ItemGroup>
     <None Include="Resources\Warning.png" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="Resources\Radial.png" />
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs
index b8bc0a389a3d04581e3c018e9e778c9250af285f..56d4ee24e53f022f81c7a3ce04d45372cee5fe62 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragLinedefsMode.cs
@@ -45,8 +45,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		#region ================== Variables
 
-		private ICollection<Linedef> selectedlines;
-		private ICollection<Linedef> unselectedlines;
+		private ICollection<Linedef> draglines;
+		private ICollection<Linedef> unmovinglines;
 
 		#endregion
 
@@ -57,21 +57,27 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		#region ================== Constructor / Disposer
 
 		// Constructor to start dragging immediately
-		public DragLinedefsMode(Vector2D dragstartmappos)
+		public DragLinedefsMode(Vector2D dragstartmappos, ICollection<Linedef> lines)
 		{
 			// Mark what we are dragging
 			General.Map.Map.ClearAllMarks(false);
-			General.Map.Map.MarkSelectedLinedefs(true, true);
+
+			draglines = new List<Linedef>(lines.Count);
+			foreach(Linedef ld in lines)
+			{
+				ld.Marked = true;
+				draglines.Add(ld);
+			}
+
 			ICollection<Vertex> verts = General.Map.Map.GetVerticesFromLinesMarks(true);
-			foreach(Vertex v in verts) v.Marked = true;
-			
+			foreach (Vertex v in verts) v.Marked = true;
+
 			// Get line collections
-			selectedlines = General.Map.Map.GetSelectedLinedefs(true);
-			unselectedlines = General.Map.Map.GetSelectedLinedefs(false);
+			unmovinglines = General.Map.Map.GetSelectedLinedefs(false);
 
 			// Initialize
 			base.StartDrag(dragstartmappos);
-			undodescription = (selectedlines.Count == 1 ? "Drag linedef" : "Drag " + selectedlines.Count + " linedefs"); //mxd
+			undodescription = (draglines.Count == 1 ? "Drag linedef" : "Drag " + draglines.Count + " linedefs"); //mxd
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -94,28 +100,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		#region ================== Methods
 		
-		// Disenagaging
-		public override void OnDisengage()
-		{
-			// Select vertices from lines selection
-			General.Map.Map.ClearSelectedVertices();
-			ICollection<Vertex> verts = General.Map.Map.GetVerticesFromLinesMarks(true);
-			foreach(Vertex v in verts) v.Selected = true;
-
-			// Perform normal disengage
-			base.OnDisengage();
-
-			// Clear vertex selection
-			General.Map.Map.ClearSelectedVertices();
-			
-			// When not cancelled
-			if(!cancelled)
-			{
-				// If only a single linedef was selected, deselect it now
-				if(selectedlines.Count == 1) General.Map.Map.ClearSelectedLinedefs();
-			}
-		}
-
 		// This redraws the display
 		public override void OnRedrawDisplay()
 		{
@@ -143,8 +127,16 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			if(renderer.StartPlotter(true))
 			{
 				// Render lines and vertices
-				renderer.PlotLinedefSet(unselectedlines);
-				renderer.PlotLinedefSet(selectedlines);
+				renderer.PlotLinedefSet(unmovinglines);
+
+				foreach (Linedef ld in draglines)
+				{
+					if (ld.Selected)
+						renderer.PlotLinedef(ld, General.Colors.Selection);
+					else
+						renderer.PlotLinedef(ld, General.Colors.Highlight);
+				}
+
 				renderer.PlotVerticesSet(General.Map.Map.Vertices);
 
 				// Draw the dragged item highlighted
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs
index 2268aa6e64b7d0df6507fc9b0874d6027f9100ac..67e3126b2166a2d43208f02cc95a3e3449284e01 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragSectorsMode.cs
@@ -45,8 +45,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		#region ================== Variables
 
-		private ICollection<Linedef> selectedlines;
-		private ICollection<Sector> selectedsectors;
+		private ICollection<Linedef> draglines;
+		private ICollection<Sector> dragsectors;
 
 		#endregion
 
@@ -57,21 +57,27 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		#region ================== Constructor / Disposer
 
 		// Constructor to start dragging immediately
-		public DragSectorsMode(Vector2D dragstartmappos)
+		public DragSectorsMode(Vector2D dragstartmappos, ICollection<Sector> sectors)
 		{
 			// Mark what we are dragging
 			General.Map.Map.ClearAllMarks(false);
-			General.Map.Map.MarkSelectedLinedefs(true, true);
-			ICollection<Vertex> verts = General.Map.Map.GetVerticesFromLinesMarks(true);
-			foreach(Vertex v in verts) v.Marked = true;
-			
-			// Get selected lines
-			selectedlines = General.Map.Map.GetSelectedLinedefs(true);
-			selectedsectors = General.Map.Map.GetSelectedSectors(true);
+
+			// Get geometry to drag
+			dragsectors = new List<Sector>(sectors);
+			draglines = new HashSet<Linedef>();
+			foreach (Sector s in sectors)
+			{
+				foreach (Sidedef sd in s.Sidedefs)
+				{
+					draglines.Add(sd.Line);
+					sd.Line.Start.Marked = true;
+					sd.Line.End.Marked = true;
+				}
+			}
 			
 			// Initialize
 			base.StartDrag(dragstartmappos);
-			undodescription = (selectedsectors.Count == 1 ? "Drag sector" : "Drag " + selectedsectors.Count + " sectors"); //mxd
+			undodescription = (dragsectors.Count == 1 ? "Drag sector" : "Drag " + dragsectors.Count + " sectors"); //mxd
 			
 			// We have no destructor
 			GC.SuppressFinalize(this);
@@ -101,40 +107,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			renderer.SetPresentation(Presentation.Standard);
 		}
 		
-		// Disenagaging
-		public override void OnDisengage()
-		{
-			// Select vertices from lines selection
-			General.Map.Map.ClearSelectedVertices();
-			ICollection<Vertex> verts = General.Map.Map.GetVerticesFromLinesMarks(true);
-			foreach(Vertex v in verts) v.Selected = true;
-
-			// Perform normal disengage
-			base.OnDisengage();
-
-			// Clear vertex selection
-			General.Map.Map.ClearSelectedVertices();
-			
-			// When not cancelled
-			if(!cancelled)
-			{
-				// If only a single sector was selected, deselect it now
-				if(selectedsectors.Count == 1)
-				{
-					General.Map.Map.ClearSelectedSectors();
-					General.Map.Map.ClearSelectedLinedefs();
-
-					//mxd. Also (de)select things?
-					if(BuilderPlug.Me.SyncronizeThingEdit)
-					{
-						Sector s = General.GetByIndex(selectedsectors, 0);
-						foreach(Thing t in General.Map.Map.Things)
-							if(t.Sector == s && t.Selected) t.Selected = false;
-					}
-				}
-			}
-		}
-
 		// This redraws the display
 		public override void OnRedrawDisplay()
 		{
@@ -164,13 +136,13 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Render lines and vertices
 				renderer.PlotLinedefSet(snaptolines);
 				renderer.PlotLinedefSet(unstablelines);
-				renderer.PlotLinedefSet(selectedlines);
+				renderer.PlotLinedefSet(draglines);
 				renderer.PlotVerticesSet(General.Map.Map.Vertices);
 
 				// Draw the dragged item highlighted
 				// This is important to know, because this item is used
 				// for snapping to the grid and snapping to nearest items
-				renderer.PlotVertex(dragitem, ColorCollection.HIGHLIGHT);
+				renderer.PlotVertex(dragitem, ColorCollection.HIGHLIGHT, false);
 				
 				// Done
 				renderer.Finish();
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs
index ee3e0721ca319ded07176651a6cf8a1d9c10da44..6d160141172fb99bcb6f2e1a8fb6122c0555e54d 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragThingsMode.cs
@@ -86,10 +86,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		private AlignData aligndata;
 
 		// List of selected items
-		private readonly ICollection<Thing> selectedthings;
+		private readonly ICollection<Thing> dragthings;
 
 		// List of non-selected items
-		private readonly ICollection<Thing> unselectedthings;
+		private readonly ICollection<Thing> unmovingthings;
 		
 		// Keep track of view changes
 		private double lastoffsetx;
@@ -114,7 +114,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		#region ================== Constructor / Disposer
 
 		// Constructor to start dragging immediately
-		public DragThingsMode(EditMode basemode, Vector2D dragstartmappos, bool makeundo)
+		public DragThingsMode(EditMode basemode, Vector2D dragstartmappos, ICollection<Thing> things, bool makeundo)
 		{
 			// Initialize
 			this.dragstartmappos = dragstartmappos;
@@ -125,20 +125,24 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			// Mark what we are dragging
 			General.Map.Map.ClearAllMarks(false);
-			General.Map.Map.MarkSelectedThings(true, true);
+			dragthings = new List<Thing>();
+			foreach (Thing t in things)
+			{
+				t.Marked = true;
+				dragthings.Add(t);
+			}
 			
-			// Get selected things
-			selectedthings = General.Map.Map.GetMarkedThings(true);
-			unselectedthings = new List<Thing>();
-			foreach(Thing t in General.Map.ThingsFilter.VisibleThings) if(!t.Marked) unselectedthings.Add(t);
+			// Get things we're not dragging
+			unmovingthings = new List<Thing>();
+			foreach(Thing t in General.Map.ThingsFilter.VisibleThings) if(!t.Marked) unmovingthings.Add(t);
 			
 			// Get the nearest thing for snapping
-			dragitem = MapSet.NearestThing(selectedthings, dragstartmappos);
+			dragitem = MapSet.NearestThing(dragthings, dragstartmappos);
 
 			// Make old positions list
 			// We will use this as reference to move the vertices, or to move them back on cancel
-			oldpositions = new List<Vector2D>(selectedthings.Count);
-			foreach(Thing t in selectedthings) oldpositions.Add(t.Position);
+			oldpositions = new List<Vector2D>(dragthings.Count);
+			foreach(Thing t in dragthings) oldpositions.Add(t.Position);
 
 			// Also keep old position of the dragged item
 			dragitemposition = dragitem.Position;
@@ -206,7 +210,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			if(snapnearest)
 			{
 				// Find nearest unselected item within selection range
-				Thing nearest = MapSet.NearestThingSquareRange(unselectedthings, mousemappos, BuilderPlug.Me.StitchRange / renderer.Scale);
+				Thing nearest = MapSet.NearestThingSquareRange(unmovingthings, mousemappos, BuilderPlug.Me.StitchRange / renderer.Scale);
 				if(nearest != null)
 				{
 					// Move the dragged item
@@ -253,7 +257,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				int i = 0;
 
 				// Move selected geometry
-				foreach(Thing t in selectedthings)
+				foreach(Thing t in dragthings)
 				{
 					// Move vertex from old position relative to the
 					// mouse position change since drag start
@@ -314,8 +318,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			{
 				// Render things
 				renderer.RenderThingSet(General.Map.ThingsFilter.HiddenThings, General.Settings.HiddenThingsAlpha);
-				renderer.RenderThingSet(unselectedthings, General.Settings.ActiveThingsAlpha);
-				renderer.RenderThingSet(selectedthings, General.Settings.ActiveThingsAlpha);
+				renderer.RenderThingSet(unmovingthings, General.Settings.ActiveThingsAlpha);
+				renderer.RenderThingSet(dragthings, General.Settings.ActiveThingsAlpha);
 
 				// Draw the dragged item highlighted
 				// This is important to know, because this item is used
@@ -334,7 +338,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			MoveThingsRelative(new Vector2D(0f, 0f), false, false, false, false);
 
 			// If only a single vertex was selected, deselect it now
-			if(selectedthings.Count == 1) General.Map.Map.ClearSelectedThings();
+			//if(dragthings.Count == 1) General.Map.Map.ClearSelectedThings();
 			
 			// Update cached values
 			General.Map.Map.Update();
@@ -376,7 +380,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 				// Make undo for the dragging
 				if(makeundo) //mxd
-					General.Map.UndoRedo.CreateUndo((selectedthings.Count == 1 ? "Drag thing" : "Drag " + selectedthings.Count + " things"));
+					General.Map.UndoRedo.CreateUndo((dragthings.Count == 1 ? "Drag thing" : "Drag " + dragthings.Count + " things"));
 
 				// Move selected geometry to final position
 				if(aligndata != null && aligndata.Active) //mxd. Apply aligning
@@ -393,7 +397,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				}
 
 				//mxd. Snap selected things to map format accuracy
-				foreach(Thing thing in selectedthings) thing.SnapToAccuracy(false);
+				foreach(Thing thing in dragthings) thing.SnapToAccuracy(false);
 
 				// Map is changed
 				General.Map.IsChanged = true;
@@ -430,7 +434,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			snaptogridincrement = (!snaptocardinaldirection && General.Interface.AltState); //mxd
 
 			//mxd. Snap to nearest linedef
-			if(selectedthings.Count == 1 && snaptonearest && !snaptocardinaldirection 
+			if(dragthings.Count == 1 && snaptonearest && !snaptocardinaldirection 
 				&& Thing.AlignableRenderModes.Contains(dragitem.RenderMode)
 				&& MoveThingsRelative(mousemappos - dragstartmappos, snaptogrid, snaptogridincrement, false, false)) 
 			{
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs
index c3912f81ff323bd2b9420d3381a31d304fe2fe04..a550f1aca1aaca7c3898ae5637cece257bdcb79c 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DragVerticesMode.cs
@@ -48,17 +48,23 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		#endregion
 
 		#region ================== Properties
-		
+
+		public override bool AlwaysShowVertices
+		{
+			get { return true; }
+		}
+
 		#endregion
 
 		#region ================== Constructor / Disposer
 
 		// Constructor to start dragging immediately
-		public DragVerticesMode(Vector2D dragstartmappos)
+		public DragVerticesMode(Vector2D dragstartmappos, ICollection<Vertex> vertices)
 		{
 			// Mark what we are dragging
 			General.Map.Map.ClearAllMarks(false);
-			General.Map.Map.MarkSelectedVertices(true, true);
+			foreach (Vertex v in vertices)
+				v.Marked = true;
 
 			// Initialize
 			base.StartDrag(dragstartmappos);
@@ -85,24 +91,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		#region ================== Methods
 		
-		// Disenagaging
-		public override void OnDisengage()
-		{
-			// Select vertices from marks
-			General.Map.Map.ClearSelectedVertices();
-			General.Map.Map.SelectMarkedVertices(true, true);
-
-			// Perform normal disengage
-			base.OnDisengage();
-			
-			// When not cancelled
-			if(!cancelled)
-			{
-				// If only a single vertex was selected, deselect it now
-				if(selectedverts.Count == 1) General.Map.Map.ClearSelectedVertices();
-			}
-		}
-
 		// This redraws the display
 		public override void OnRedrawDisplay()
 		{
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DrawEllipseMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DrawEllipseMode.cs
index 9deaf565b54085d7a28caba6f148e1e7cc0ab279..f462069a11bb1393faa80d88ee6e1da629b39708 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/DrawEllipseMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DrawEllipseMode.cs
@@ -68,10 +68,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			panel.OnValueChanged += OptionsPanelOnValueChanged;
 			panel.OnContinuousDrawingChanged += OnContinuousDrawingChanged;
 			panel.OnShowGuidelinesChanged += OnShowGuidelinesChanged;
+			panel.OnRadialDrawingChanged += OnRadialDrawingChanged;
 
 			// Needs to be set after adding the OnContinuousDrawingChanged event...
 			panel.ContinuousDrawing = General.Settings.ReadPluginSetting("drawellipsemode.continuousdrawing", false);
 			panel.ShowGuidelines = General.Settings.ReadPluginSetting("drawellipsemode.showguidelines", false);
+			panel.RadialDrawing = General.Settings.ReadPluginSetting("drawellipsemode.radialdrawing", false);
 		}
 
 		override protected void AddInterface() 
@@ -87,6 +89,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Settings.WritePluginSetting("drawellipsemode.angle", panel.Angle);
 			General.Settings.WritePluginSetting("drawellipsemode.continuousdrawing", panel.ContinuousDrawing);
 			General.Settings.WritePluginSetting("drawellipsemode.showguidelines", panel.ShowGuidelines);
+			General.Settings.WritePluginSetting("drawellipsemode.radialdrawing", panel.RadialDrawing);
+			
 
 			// Remove the buttons
 			panel.Unregister();
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DrawGeometryMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DrawGeometryMode.cs
index 950c717166c9f55aeb5e07ea6fdd91532c5e2bff..a3ab3a128ebf53982343b089ed30f3328e013058 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/DrawGeometryMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DrawGeometryMode.cs
@@ -331,9 +331,15 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			Vector2D scaledPerpendicular = delta.GetPerpendicular().GetNormal().GetScaled(18f / renderer.Scale);
 			renderer.RenderLine(middlePoint, new Vector2D(middlePoint.x - scaledPerpendicular.x, middlePoint.y - scaledPerpendicular.y), LINE_THICKNESS, color, true);
 		}
-		
+
 		// This returns the aligned and snapped draw position
 		public static DrawnVertex GetCurrentPosition(Vector2D mousemappos, bool snaptonearest, bool snaptogrid, bool snaptocardinal, bool usefourcardinaldirections, IRenderer2D renderer, List<DrawnVertex> points)
+		{
+			return GetCurrentPosition(mousemappos, snaptonearest, snaptogrid, snaptocardinal, usefourcardinaldirections, renderer, points, null);
+		}
+
+		// This returns the aligned and snapped draw position
+		public static DrawnVertex GetCurrentPosition(Vector2D mousemappos, bool snaptonearest, bool snaptogrid, bool snaptocardinal, bool usefourcardinaldirections, IRenderer2D renderer, List<DrawnVertex> points, BlockMap<BlockEntry> blockmap)
 		{
 			DrawnVertex p = new DrawnVertex();
 			p.stitch = true; //mxd. Setting these to false seems to be a good way to create invalid geometry...
@@ -380,8 +386,22 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					}
 				}
 
+				List<Vertex> vertices = new List<Vertex>();
+				Vertex nv = null;
+
+				// If we got a blockmap get the veritces that are in range only
+				if (blockmap != null)
+				{
+					HashSet<BlockEntry> blocks = blockmap.GetSquareRange(vm.x - vrange, vm.y - vrange, vrange * 2, vrange * 2);
+					foreach (BlockEntry be in blocks)
+						vertices.AddRange(be.Vertices);
+
+					nv = MapSet.NearestVertexSquareRange(vertices, vm, vrange);
+				}
+				else
+					nv = General.Map.Map.NearestVertexSquareRange(vm, vrange);
+
 				// Try the nearest vertex
-				Vertex nv = General.Map.Map.NearestVertexSquareRange(vm, vrange);
 				if(nv != null)
 				{
 					//mxd. Line angle must stay the same
@@ -402,7 +422,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				}
 
 				// Try the nearest linedef. mxd. We'll need much bigger stitch distance when snapping to cardinal directions
-				Linedef nl = General.Map.Map.NearestLinedefRange(vm, BuilderPlug.Me.StitchRange / renderer.Scale);
+				Linedef nl = blockmap != null ? MapSet.NearestLinedefRange(blockmap, vm, BuilderPlug.Me.StitchRange / renderer.Scale) : General.Map.Map.NearestLinedefRange(vm, BuilderPlug.Me.StitchRange / renderer.Scale);
 				if(nl != null)
 				{
 					//mxd. Line angle must stay the same
diff --git a/Source/Plugins/BuilderModes/ClassicModes/DrawRectangleMode.cs b/Source/Plugins/BuilderModes/ClassicModes/DrawRectangleMode.cs
index adc805685dcac5a0c8281e5a20b9697204ecb7c9..9b3da24f34d658a33292ee14e94a5510dc16a466 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/DrawRectangleMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/DrawRectangleMode.cs
@@ -46,6 +46,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		protected int height;
 		protected int minpointscount;
 		protected bool alwaysrendershapehints;
+		protected bool radialdrawing;
 
 		private bool blockupdate;
 
@@ -102,10 +103,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			panel.OnValueChanged += OptionsPanelOnValueChanged;
 			panel.OnContinuousDrawingChanged += OnContinuousDrawingChanged;
 			panel.OnShowGuidelinesChanged += OnShowGuidelinesChanged;
+			panel.OnRadialDrawingChanged += OnRadialDrawingChanged;
 
 			// Needs to be set after adding the OnContinuousDrawingChanged event...
 			panel.ContinuousDrawing = General.Settings.ReadPluginSetting("drawrectanglemode.continuousdrawing", false);
 			panel.ShowGuidelines = General.Settings.ReadPluginSetting("drawrectanglemode.showguidelines", false);
+			panel.RadialDrawing = General.Settings.ReadPluginSetting("drawrectanglemode.radialdrawing", false);
 		}
 
 		protected override void AddInterface() 
@@ -120,6 +123,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Settings.WritePluginSetting("drawrectanglemode.bevelwidth", bevelwidth);
 			General.Settings.WritePluginSetting("drawrectanglemode.continuousdrawing", panel.ContinuousDrawing);
 			General.Settings.WritePluginSetting("drawrectanglemode.showguidelines", panel.ShowGuidelines);
+			General.Settings.WritePluginSetting("drawrectanglemode.radialdrawing", panel.RadialDrawing);
 
 			// Remove the buttons
 			panel.Unregister();
@@ -317,6 +321,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			if(!p1.pos.IsFinite() || !p2.pos.IsFinite()) return;
 
+			if (radialdrawing)
+			{
+				Vector2D delta = p2.pos - p1.pos;
+				p1.pos -= delta;
+			}
+
 			// Make sure start always stays at left and up from the end
 			if (p1.pos.x < p2.pos.x) 
 			{
@@ -504,6 +514,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			subdivisions = panel.Subdivisions;
 			Update();
 		}
+		
+		protected void OnRadialDrawingChanged(object value, EventArgs e)
+		{
+			radialdrawing = (bool)value;
+		}
 
 		#endregion
 
diff --git a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs
index 9b747a8f16c1f4579883d9499c58e5af1b9bdda4..0cc55cf44b22e6c3c4e793b8775c3620f79be791 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/EditSelectionMode.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Linq;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Actions;
 using CodeImp.DoomBuilder.Config;
@@ -182,14 +183,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		private bool usepreciseposition; //mxd
 
 		//mxd. Texture modification
-		private static bool transformflooroffsets;
-		private static bool transformceiloffsets;
-		private static bool rotateflooroffsets;
-		private static bool rotateceiloffsets;
-		private static bool scaleflooroffsets;
-		private static bool scaleceiloffsets;
+		private static bool pinfloortextures;
+		private static bool pinceilingtextures;
 		private Vector2D selectioncenter;
 		private Vector2D selectionbasecenter;
+		private Vector2D referencepoint;
 		
 		// Modifying Modes
 		private ModifyMode mode;
@@ -230,13 +228,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		//mxd. Modification
 		internal bool UsePrecisePosition { get { return usepreciseposition; } set { usepreciseposition = value; } }
 
-		//mxd. Texture offset properties
-		internal bool TransformFloorOffsets { get { return transformflooroffsets; } set { transformflooroffsets = value; UpdateAllChanges(); } }
-		internal bool TransformCeilingOffsets { get { return transformceiloffsets; } set { transformceiloffsets = value; UpdateAllChanges(); } }
-		internal bool RotateFloorOffsets { get { return rotateflooroffsets; } set { rotateflooroffsets = value; UpdateAllChanges(); } }
-		internal bool RotateCeilingOffsets { get { return rotateceiloffsets; } set { rotateceiloffsets = value; UpdateAllChanges(); } }
-		internal bool ScaleFloorOffsets { get { return scaleflooroffsets; } set { scaleflooroffsets = value; UpdateAllChanges(); } }
-		internal bool ScaleCeilingOffsets { get { return scaleceiloffsets; } set { scaleceiloffsets = value; UpdateAllChanges(); } }
+		// Texture offset properties
+		internal bool PinFloorTextures { get { return pinfloortextures; } set { pinfloortextures = value; UpdateAllChanges(); } }
+		internal bool PinCeilingTextures { get { return pinceilingtextures; } set { pinceilingtextures = value; UpdateAllChanges(); } }
 
 		//mxd. Height offset mode
 		internal HeightAdjustMode SectorHeightAdjustMode { get { return heightadjustmode; } set { heightadjustmode = value; } }
@@ -888,8 +882,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				group.Key.Fields.BeforeFieldsChange();
 
 				// Apply transforms
-				UpdateTextureTransform(group.Key.Fields, group.Value.Ceiling, transformceiloffsets, rotateceiloffsets, scaleceiloffsets);
-				UpdateTextureTransform(group.Key.Fields, group.Value.Floor, transformflooroffsets, rotateflooroffsets, scaleflooroffsets);
+				UpdateTextureTransform(group.Key.Fields, group.Value.Ceiling /*, transformceiloffsets, rotateceiloffsets, scaleceiloffsets */);
+				UpdateTextureTransform(group.Key.Fields, group.Value.Floor /*, transformflooroffsets, rotateflooroffsets, scaleflooroffsets */);
 
 				// Update cache
 				group.Key.UpdateNeeded = true;
@@ -901,46 +895,27 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		}
 
 		//mxd. This updates texture transforms in given UniFields
-		private void UpdateTextureTransform(UniFields fields, SurfaceTextureInfo si, bool transformoffsets, bool rotateoffsets, bool scaleoffsets)
+		private void UpdateTextureTransform(UniFields fields, SurfaceTextureInfo si /*, bool transformoffsets, bool rotateoffsets, bool scaleoffsets */)
 		{
-			// Get offset-ready values
-			double texrotation = Angle2D.PI2 - rotation;
-
-			// Update texture offsets
-			if (transformoffsets)
+			if ((si.Part == "floor" && pinfloortextures) || (si.Part == "ceiling" && pinceilingtextures))
 			{
-				double trotation = rotateoffsets ? (si.Rotation + texrotation) : (si.Rotation);
-				Vector2D offset = selectioncenter.GetRotated(trotation);
+				double texrotation = Angle2D.PI2 - rotation;
 
-				fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, Math.Round(-offset.x, General.Map.FormatInterface.VertexDecimals));
-				fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, Math.Round(offset.y, General.Map.FormatInterface.VertexDecimals));
+				double trotation = texrotation + si.Rotation;
+				Vector2D o = ((referencepoint - selectionbasecenter).GetRotated(-trotation) + selectionbasecenter + this.offset - this.baseoffset).GetRotated(trotation);
 
+				fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, Math.Round(-o.x + si.Offset.x, General.Map.FormatInterface.VertexDecimals));
+				fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, Math.Round(o.y + si.Offset.y, General.Map.FormatInterface.VertexDecimals));
+				fields["rotation" + si.Part] = new UniValue(UniversalType.Float, General.ClampAngle(Math.Round(Angle2D.RadToDeg(trotation), General.Map.FormatInterface.VertexDecimals)));
 			}
-			// Restore texture offsets
-			else 
+			else
 			{
+				// Reset values
 				fields["xpanning" + si.Part] = new UniValue(UniversalType.Float, si.Offset.x);
 				fields["ypanning" + si.Part] = new UniValue(UniversalType.Float, si.Offset.y);
-			}
-
-			// Update rotation
-			if(rotateoffsets)
-				fields["rotation" + si.Part] = new UniValue(UniversalType.AngleDegreesFloat, General.ClampAngle(Math.Round(Angle2D.RadToDeg(si.Rotation + texrotation), General.Map.FormatInterface.VertexDecimals)));
-			// Restore rotation
-			else 
 				fields["rotation" + si.Part] = new UniValue(UniversalType.AngleDegreesFloat, Angle2D.RadToDeg(si.Rotation));
-
-			// Update scale
-			if(scaleoffsets)
-			{
-				fields["xscale" + si.Part] = new UniValue(UniversalType.Float, Math.Round(si.Scale.x * scale.x, General.Map.FormatInterface.VertexDecimals));
-				fields["yscale" + si.Part] = new UniValue(UniversalType.Float, Math.Round(-si.Scale.y * scale.y, General.Map.FormatInterface.VertexDecimals));
-			}
-			// Restore scale
-			else 
-			{
-				fields["xscale" + si.Part] = new UniValue(UniversalType.Float, si.Scale.x);
-				fields["yscale" + si.Part] = new UniValue(UniversalType.Float, -si.Scale.y);
+				//fields["xscale" + si.Part] = new UniValue(UniversalType.Float, Math.Round(si.Scale.x * scale.x, General.Map.FormatInterface.VertexDecimals));
+				//fields["yscale" + si.Part] = new UniValue(UniversalType.Float, Math.Round(-si.Scale.y * scale.y, General.Map.FormatInterface.VertexDecimals));
 			}
 		}
 
@@ -1344,8 +1319,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				Vector2D right;
 				right.x = float.MinValue;
 				right.y = float.MinValue;
-				
-				foreach(Vertex v in selectedvertices)
+
+				if(selectedvertices.Count > 0)
+					referencepoint = selectedvertices.First().Position;
+
+				foreach (Vertex v in selectedvertices)
 				{
 					// Find left-top and right-bottom
 					if(v.Position.x < offset.x) offset.x = v.Position.x;
diff --git a/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs
index c7e2ee8bd90eee4f35ac540d79ad0bfe15a3d950..744f9af986a0adf74802462f4150a3ead4b7a32f 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs
@@ -23,13 +23,11 @@ using System.Linq;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Actions;
 using CodeImp.DoomBuilder.BuilderModes.Interface;
-using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Rendering;
-using CodeImp.DoomBuilder.Types;
 using CodeImp.DoomBuilder.Windows;
 
 #endregion
@@ -66,7 +64,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		
 		// Interface
 		new private bool editpressed;
-		private bool selectionfromhighlight; //mxd
 
 		// The blockmap makes is used to make finding lines faster
 		BlockMap<BlockEntry> blockmap;
@@ -74,12 +71,17 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// Stores sizes of the text for text labels so that they only have to be computed once
 		private Dictionary<string, float> textlabelsizecache;
 
+		// Linedefs that will be edited
+		ICollection<Linedef> editlines;
+
 		#endregion
 
 		#region ================== Properties
 
 		public override object HighlightedObject { get { return highlighted; } }
-		
+
+		public override bool AlwaysShowVertices { get { return true; } }
+
 		#endregion
 
 		#region ================== Constructor / Disposer
@@ -366,8 +368,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		//mxd. This sets up new labels
 		private void SetupSectorLabels()
 		{
+			// Dummy label we need for the font
+			TextLabel dummylabel = new TextLabel();
+
+			// The "+" is always shown if the space for the label isn't big enough to show the full text
+			textlabelsizecache["+"] = General.Interface.MeasureString("+", dummylabel.Font).Width;
+
 			// Dispose old labels
-			if(sectorlabels != null)
+			if (sectorlabels != null)
 			{
 				foreach(TextLabel[] larr in sectorlabels.Values)
 					foreach(TextLabel l in larr) l.Dispose();
@@ -396,6 +404,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					tagdescarr[1] = "T" + s.Tag;
 				}
 
+				// Add string lengths to the label size cache
+				if (!textlabelsizecache.ContainsKey(tagdescarr[0]))
+					textlabelsizecache[tagdescarr[0]] = General.Interface.MeasureString(tagdescarr[0], dummylabel.Font).Width;
+				if (!textlabelsizecache.ContainsKey(tagdescarr[1]))
+					textlabelsizecache[tagdescarr[1]] = General.Interface.MeasureString(tagdescarr[1], dummylabel.Font).Width;
+
 				// Add to collection
 				sectortexts.Add(s, tagdescarr);
 
@@ -426,13 +440,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			{
 				// Dispose old labels
 				foreach(SelectionLabel l in labels.Values) l.Dispose();
-				
-				// Don't show lables for selected-from-highlight item
-				if(selectionfromhighlight)
-				{
-					labels.Clear();
-					return;
-				}
 			}
 
 			// Make text labels for selected linedefs
@@ -466,6 +473,103 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			blockmap.AddLinedefsSet(General.Map.Map.Linedefs);
 		}
 
+		/// <summary>
+		/// Renders the overlay with the (selection) labels and insert vertex preview.
+		/// </summary>
+		private void RenderOverlay()
+		{
+			if (General.Map.Map.IsSafeToAccess && renderer.StartOverlay(true))
+			{
+				if (!selecting) //mxd
+				{
+					if ((highlighted != null) && !highlighted.IsDisposed) highlightasso.Render(); //mxd
+				}
+				else
+				{
+					RenderMultiSelection();
+				}
+
+				//mxd. Render vertex insert preview
+				if (insertpreview.IsFinite())
+				{
+					double dist = Math.Min(Vector2D.Distance(mousemappos, insertpreview), BuilderPlug.Me.HighlightRange);
+					byte alpha = (byte)(255 - (dist / BuilderPlug.Me.HighlightRange) * 128);
+					float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;
+					renderer.RenderRectangleFilled(new RectangleF((float)(insertpreview.x - vsize), (float)(insertpreview.y - vsize), vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine.WithAlpha(alpha), true);
+				}
+
+				//mxd. Render sector tag labels
+				if (BuilderPlug.Me.ViewSelectionEffects)
+				{
+					List<ITextLabel> torender = new List<ITextLabel>(sectorlabels.Count);
+					foreach (KeyValuePair<Sector, string[]> group in sectortexts)
+					{
+						// Pick which text variant to use
+						TextLabel[] labelarray = sectorlabels[group.Key];
+						for (int i = 0; i < group.Key.Labels.Count; i++)
+						{
+							// Only process this label if it's actually in view
+							if (!labelarray[i].IsInViewport())
+								continue;
+
+							TextLabel l = labelarray[i];
+
+							// Render only when enough space for the label to see
+							float requiredsize = textlabelsizecache[group.Value[0]] / 2 / renderer.Scale;
+
+							if (requiredsize > group.Key.Labels[i].radius)
+							{
+								requiredsize = textlabelsizecache[group.Value[1]] / 2 / renderer.Scale;
+
+								string newtext;
+
+								if (requiredsize > group.Key.Labels[i].radius)
+									newtext = (requiredsize > group.Key.Labels[i].radius * 4 ? string.Empty : "+");
+								else
+									newtext = group.Value[1];
+
+								if (l.Text != newtext)
+									l.Text = newtext;
+							}
+							else
+							{
+								if (group.Value[0] != l.Text)
+									l.Text = group.Value[0];
+							}
+
+							if (!string.IsNullOrEmpty(l.Text)) torender.Add(l);
+						}
+					}
+
+					// Render labels
+					renderer.RenderText(torender);
+				}
+
+				//mxd. Render selection labels
+				if (BuilderPlug.Me.ViewSelectionNumbers)
+				{
+					List<ITextLabel> torender = new List<ITextLabel>(labels.Count);
+					foreach (KeyValuePair<Linedef, SelectionLabel> group in labels)
+					{
+						// Render only when enough space for the label to see
+						group.Value.Move(group.Key.Start.Position, group.Key.End.Position);
+						float requiredsize = (group.Value.TextSize.Width) / renderer.Scale;
+						if (group.Key.Length > requiredsize)
+						{
+							torender.Add(group.Value.TextLabel);
+						}
+					}
+
+					renderer.RenderText(torender);
+				}
+
+				//mxd. Render comments
+				if (General.Map.UDMF && General.Settings.RenderComments) foreach (Linedef l in General.Map.Map.Linedefs) RenderComment(l);
+
+				renderer.Finish();
+			}
+		}
+
 		#endregion
 
 		#region ================== Events
@@ -600,99 +704,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				renderer.Finish();
 			}
 
-			// Render selection
-			if(renderer.StartOverlay(true))
-			{
-				if(!selecting) //mxd
-				{ 
-					if ((highlighted != null) && !highlighted.IsDisposed) highlightasso.Render(); //mxd
-				}
-				else
-				{
-					RenderMultiSelection();
-				}
-
-				//mxd. Render vertex insert preview
-				if(insertpreview.IsFinite())
-				{
-					double dist = Math.Min(Vector2D.Distance(mousemappos, insertpreview), BuilderPlug.Me.HighlightRange);
-					byte alpha = (byte)(255 - (dist / BuilderPlug.Me.HighlightRange) * 128);
-					float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;
-					renderer.RenderRectangleFilled(new RectangleF((float)(insertpreview.x - vsize), (float)(insertpreview.y - vsize), vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine.WithAlpha(alpha), true);
-				}
-
-				//mxd. Render sector tag labels
-				if(BuilderPlug.Me.ViewSelectionEffects)
-				{
-					List<ITextLabel> torender = new List<ITextLabel>(sectorlabels.Count);
-					foreach(KeyValuePair<Sector, string[]> group in sectortexts)
-					{
-						// Pick which text variant to use
-						TextLabel[] labelarray = sectorlabels[group.Key];
-						for(int i = 0; i < group.Key.Labels.Count; i++)
-						{
-							TextLabel l = labelarray[i];
-
-							// Render only when enough space for the label to see
-							if (!textlabelsizecache.ContainsKey(group.Value[0]))
-								textlabelsizecache[group.Value[0]] = General.Interface.MeasureString(group.Value[0], l.Font).Width;
-
-							float requiredsize = textlabelsizecache[group.Value[0]] / 2 / renderer.Scale;
-
-							if (requiredsize > group.Key.Labels[i].radius)
-							{
-								if (!textlabelsizecache.ContainsKey(group.Value[1]))
-									textlabelsizecache[group.Value[1]] = General.Interface.MeasureString(group.Value[1], l.Font).Width;
-
-								requiredsize = textlabelsizecache[group.Value[1]] / 2 / renderer.Scale;
-
-								string newtext;
-
-								if (requiredsize > group.Key.Labels[i].radius)
-									newtext = (requiredsize > group.Key.Labels[i].radius * 4 ? string.Empty : "+");
-								else
-									newtext = group.Value[1];
-
-								if (l.Text != newtext)
-									l.Text = newtext;
-							}
-							else
-							{
-								if (group.Value[0] != l.Text)
-									l.Text = group.Value[0];
-							}
-
-							if(!string.IsNullOrEmpty(l.Text)) torender.Add(l);
-						}
-					}
-
-					// Render labels
-					renderer.RenderText(torender);
-				}
-
-				//mxd. Render selection labels
-				if(BuilderPlug.Me.ViewSelectionNumbers)
-				{
-					List<ITextLabel> torender = new List<ITextLabel>(labels.Count);
-					foreach(KeyValuePair<Linedef, SelectionLabel> group in labels)
-					{
-						// Render only when enough space for the label to see
-						group.Value.Move(group.Key.Start.Position, group.Key.End.Position);
-						float requiredsize = (group.Value.TextSize.Width) / renderer.Scale;
-						if(group.Key.Length > requiredsize)
-						{
-							torender.Add(group.Value.TextLabel);
-						}
-					}
-
-					renderer.RenderText(torender);
-				}
-
-				//mxd. Render comments
-				if(General.Map.UDMF && General.Settings.RenderComments) foreach(Linedef l in General.Map.Map.Linedefs) RenderComment(l);
-
-				renderer.Finish();
-			}
+			// Render the overlay with the text labels and ve
+			RenderOverlay();
 
 			renderer.Present();
 		}
@@ -768,21 +781,26 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				editpressed = true;
 
 				// Highlighted item not selected?
-				if(!highlighted.Selected && (BuilderPlug.Me.AutoClearSelection || (General.Map.Map.SelectedLinedefsCount == 0)))
+				if(!highlighted.Selected)
 				{
 					// Make this the only selection
-					selectionfromhighlight = true; //mxd
 					General.Map.Map.ClearSelectedLinedefs();
-					highlighted.Selected = true;
+
+					editlines = new List<Linedef> { highlighted };
+
 					UpdateSelectionInfo(); //mxd
 					General.Interface.RedrawDisplay();
 				}
+				else
+				{
+					editlines = General.Map.Map.GetSelectedLinedefs(true);
+				}
 
 				// Update display
 				if(renderer.StartPlotter(false))
 				{
 					// Redraw highlight to show selection
-					renderer.PlotLinedef(highlighted, renderer.DetermineLinedefColor(highlighted));
+					renderer.PlotLinedef(highlighted, General.Colors.Highlight);
 					renderer.PlotVertex(highlighted.Start, renderer.DetermineVertexColor(highlighted.Start));
 					renderer.PlotVertex(highlighted.End, renderer.DetermineVertexColor(highlighted.End));
 					renderer.Finish();
@@ -812,29 +830,17 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Edit pressed in this mode?
 			if(editpressed)
 			{
-				// Anything selected?
-				ICollection<Linedef> selected = General.Map.Map.GetSelectedLinedefs(true);
-				if(selected.Count > 0)
+				if(editlines?.Count > 0)
 				{
 					if(General.Interface.IsActiveWindow)
 					{
 						// Show line edit dialog
 						General.Interface.OnEditFormValuesChanged += linedefEditForm_OnValuesChanged;
-						DialogResult result = General.Interface.ShowEditLinedefs(selected);
+						DialogResult result = General.Interface.ShowEditLinedefs(editlines);
 						General.Interface.OnEditFormValuesChanged -= linedefEditForm_OnValuesChanged;
 
 						General.Map.Map.Update();
 						
-						// When a single line was selected, deselect it now
-						if(selected.Count == 1 && selectionfromhighlight) 
-						{
-							General.Map.Map.ClearSelectedLinedefs();
-						} 
-						else if(result == DialogResult.Cancel) //mxd. Restore selection...
-						{ 
-							foreach(Linedef l in selected) l.Selected = true;
-						}
-
 						// Update entire display
 						SetupSectorLabels();
 						General.Map.Renderer2D.UpdateExtraFloorFlag(); //mxd
@@ -845,7 +851,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			}
 
 			editpressed = false;
-			selectionfromhighlight = false; //mxd
 			base.OnEditEnd();
 		}
 
@@ -921,6 +926,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public override void OnMouseMove(MouseEventArgs e)
 		{
 			base.OnMouseMove(e);
+
 			if(panning) return; //mxd. Skip all this jazz while panning
 
 			//mxd
@@ -978,22 +984,31 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				{
 					bool snaptogrid = General.Interface.ShiftState ^ General.Interface.SnapToGrid;
 					bool snaptonearest = General.Interface.CtrlState ^ General.Interface.AutoMerge;
-					Vector2D v = DrawGeometryMode.GetCurrentPosition(mousemappos, snaptonearest, snaptogrid, false, false, renderer, new List<DrawnVertex>()).pos;
 
-					if(v != insertpreview)
+					Vector2D v = DrawGeometryMode.GetCurrentPosition(mousemappos, snaptonearest, snaptogrid, false, false, renderer, new List<DrawnVertex>(), blockmap).pos;
+
+					if (v != insertpreview)
 					{
 						insertpreview = v;
-						General.Interface.RedrawDisplay();
+
+						// Render overlay to show the new insert preview. Do not redraw the whole display for performance reasons.
+						// We need to present ourselves because RenderOverlay does not do it
+						RenderOverlay();
+						renderer.Present();
 					}
 				} 
 				else if(insertpreview.IsFinite()) 
 				{
 					insertpreview.x = float.NaN;
-					General.Interface.RedrawDisplay();
+
+					// Render overlay to show the new insert preview. Do not redraw the whole display for performance reasons
+					// We need to present ourselves because RenderOverlay does not do it
+					RenderOverlay();
+					renderer.Present();
 				}
 
 				// Highlight if not the same
-				if(l != highlighted) Highlight(l);
+				if (l != highlighted) Highlight(l);
 
 				//mxd. Show tooltip?
 				if(General.Map.UDMF && General.Settings.RenderComments && mouselastpos != mousepos && highlighted != null && !highlighted.IsDisposed && highlighted.Fields.ContainsKey("comment"))
@@ -1023,7 +1038,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		protected override void BeginViewPan() 
 		{
 			// We don't want vertex preview while panning
-			insertpreview.x = float.NaN;
+			insertpreview.x = double.NaN;
 			base.BeginViewPan();
 		}
 
@@ -1047,17 +1062,24 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Anything highlighted?
 				if((highlighted != null) && !highlighted.IsDisposed)
 				{
+					ICollection<Linedef> draglines;
+
 					// Highlighted item not selected?
 					if(!highlighted.Selected)
 					{
 						// Select only this linedef for dragging
 						General.Map.Map.ClearSelectedLinedefs();
-						highlighted.Selected = true;
+						draglines = new List<Linedef> { highlighted };
+					}
+					else
+					{
+						// Add all selected linedefs to the linedefs we want to drag
+						draglines = General.Map.Map.GetSelectedLinedefs(true);
 					}
 
 					// Start dragging the selection
 					if(!BuilderPlug.Me.DontMoveGeometryOutsideMapBoundary || CanDrag()) //mxd
-						General.Editing.ChangeMode(new DragLinedefsMode(mousedownmappos));
+						General.Editing.ChangeMode(new DragLinedefsMode(mousedownmappos, draglines));
 				}
 			}
 		}
@@ -1217,6 +1239,16 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			return base.OnCopyBegin();
 		}
 
+		/// <summary>
+		/// If map elements have changed the blockmap needs to be recreated.
+		/// </summary>
+		public override void OnMapElementsChanged()
+		{
+			base.OnMapElementsChanged();
+
+			CreateBlockmap();
+		}
+
 		//mxd
 		private void RenderComment(Linedef l)
 		{
@@ -1357,7 +1389,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				else
 					counter++;
 			}
-			
+
+			UpdateSelectionInfo();
+
 			General.Interface.DisplayStatus(StatusType.Action, "Selected only single-sided linedefs (" + counter + ")");
 			General.Interface.RedrawDisplay();
 		}
@@ -1376,6 +1410,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 					counter++;
 			}
 
+			UpdateSelectionInfo();
+
 			General.Interface.DisplayStatus(StatusType.Action, "Selected only double-sided linedefs (" + counter + ")");
 			General.Interface.RedrawDisplay();
 		}
diff --git a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
index bcf81fee3fe949aff4086bf0c52d572c1ad1e6c6..69b67251ed37c080b2badcf28bfb19f84e95da9d 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs
@@ -61,7 +61,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		// Interface
 		new private bool editpressed;
-		private bool selectionfromhighlight; //mxd
 
 		// Labels
 		private Dictionary<Sector, TextLabel[]> labels;
@@ -84,6 +83,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		private ConcurrentDictionary<Thing, bool> determinedsectorthings;
 
+		// Sectors that will be edited
+		private ICollection<Sector> editsectors;
+
 		#endregion
 
 		#region ================== Properties
@@ -185,7 +187,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// This updates the overlay
 		private void UpdateOverlay()
 		{
-			if(renderer.StartOverlay(true))
+			if(General.Map.Map.IsSafeToAccess && renderer.StartOverlay(true))
 			{
 				// Go for all selected sectors
 				ICollection<Sector> orderedselection = General.Map.Map.GetSelectedSectors(true);
@@ -631,9 +633,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// This updates labels from the selected sectors
 		private void UpdateSelectedLabels()
 		{
-			// Don't show lables for selected-from-highlight item
-			if(selectionfromhighlight) return;
-			
 			// Go for all labels in all selected sectors
 			ICollection<Sector> orderedselection = General.Map.Map.GetSelectedSectors(true);
 			PixelColor c = (General.Settings.UseHighlight ? General.Colors.Highlight : General.Colors.Selection); //mxd
@@ -1061,24 +1060,29 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				editpressed = true;
 
 				// Highlighted item not selected?
-				if(!highlighted.Selected && (BuilderPlug.Me.AutoClearSelection || (General.Map.Map.SelectedSectorsCount == 0)))
+				if (!highlighted.Selected)
 				{
 					// Make this the only selection
-					selectionfromhighlight = true; //mxd
 					General.Map.Map.ClearSelectedSectors();
 					General.Map.Map.ClearSelectedLinedefs();
-					SelectSector(highlighted, true, false);
+
+					editsectors = new List<Sector> { highlighted };
+
 					UpdateSelectedLabels(); //mxd
 					UpdateOverlaySurfaces(); //mxd
 					UpdateSelectionInfo(); //mxd
 					General.Interface.RedrawDisplay();
 				}
+				else // Highlight is selected, so take all selected sectors
+				{
+					editsectors = General.Map.Map.GetSelectedSectors(true);
+				}
 
 				// Update display
 				if(renderer.StartPlotter(false))
 				{
 					// Redraw highlight to show selection
-					renderer.PlotSector(highlighted);
+					renderer.PlotSector(highlighted, General.Colors.Highlight);
 					renderer.Finish();
 					renderer.Present();
 				}
@@ -1106,40 +1110,18 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Edit pressed in this mode?
 			if(editpressed)
 			{
-				// Anything selected?
-				ICollection<Sector> selected = General.Map.Map.GetSelectedSectors(true);
-				if(selected.Count > 0)
+				if(editsectors?.Count > 0)
 				{
 					if(General.Interface.IsActiveWindow)
 					{
 						//mxd. Show realtime vertex edit dialog
 						General.Interface.OnEditFormValuesChanged += sectorEditForm_OnValuesChanged;
-						DialogResult result = General.Interface.ShowEditSectors(selected);
+						DialogResult result = General.Interface.ShowEditSectors(editsectors);
 						General.Interface.OnEditFormValuesChanged -= sectorEditForm_OnValuesChanged;
 
 						General.Map.Renderer2D.UpdateExtraFloorFlag(); //mxd
 
-						// When a single sector was selected, deselect it now
-						if(selected.Count == 1 && selectionfromhighlight) 
-						{
-							General.Map.Map.ClearSelectedSectors();
-							General.Map.Map.ClearSelectedLinedefs();
-
-							//mxd. Also deselect things?
-							if(BuilderPlug.Me.SyncronizeThingEdit)
-							{
-								Sector s = General.GetByIndex(selected, 0);
-								foreach(Thing t in General.Map.Map.Things)
-									if(t.Sector == s && t.Selected) t.Selected = false;
-							}
-
-							UpdateEffectLabels(); //mxd
-						} 
-						else if(result == DialogResult.Cancel) //mxd. Restore selection...
-						{ 
-							foreach(Sector s in selected) SelectSector(s, true, false);
-							UpdateSelectedLabels(); //mxd
-						}
+						UpdateEffectLabels();
 
 						UpdateOverlaySurfaces(); //mxd
 						General.Interface.RedrawDisplay();
@@ -1150,7 +1132,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			}
 
 			editpressed = false;
-			selectionfromhighlight = false; //mxd
 			base.OnEditEnd();
 		}
 
@@ -1335,18 +1316,24 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Anything highlighted?
 				if((highlighted != null) && !highlighted.IsDisposed)
 				{
+					ICollection<Sector> dragsectors;
+
 					// Highlighted item not selected?
-					if(!highlighted.Selected)
+					if (!highlighted.Selected)
 					{
 						// Select only this sector for dragging
 						General.Map.Map.ClearSelectedSectors();
-						SelectSector(highlighted, true, true);
+						dragsectors = new List<Sector> { highlighted };
 						UpdateOverlaySurfaces(); //mxd
 					}
+					else
+					{
+						dragsectors = General.Map.Map.GetSelectedSectors(true);
+					}
 
 					// Start dragging the selection
 					if(!BuilderPlug.Me.DontMoveGeometryOutsideMapBoundary || CanDrag()) //mxd
-						General.Editing.ChangeMode(new DragSectorsMode(mousedownmappos));
+						General.Editing.ChangeMode(new DragSectorsMode(mousedownmappos, dragsectors));
 				}
 			}
 		}
@@ -1596,10 +1583,20 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Interface.RedrawDisplay();
 		}
 
+		/// <summary>
+		/// If map elements have changed the blockmap needs to be recreated.
+		/// </summary>
+		public override void OnMapElementsChanged()
+		{
+			base.OnMapElementsChanged();
+
+			CreateBlockmap();
+		}
+
 		//mxd
 		public override void OnViewSelectionNumbersChanged(bool enabled)
 		{
-			if(enabled) UpdateSelectedLabels();
+			UpdateSelectedLabels();
 		}
 
 		//mxd
@@ -1935,7 +1932,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			}
 			else //mxd
 			{
-				General.Interface.DisplayStatus(StatusType.Warning, "This action requires a highlight or selection!");
+				General.ToastManager.ShowToast("makedoor", ToastType.WARNING, "Couldn't create door", "You need to highlight or select at least one sector to create a door.");
 			}
 		}
 		
diff --git a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
index 30685b5531e3fd333d884a9a98a57f4ddb69dd11..cbd92183c600ee9f532c1d8a41357993b011fe4a 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs
@@ -62,7 +62,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		new private bool editpressed;
 		private bool thinginserted;
 		private bool awaitingMouseClick; //mxd
-		private bool selectionfromhighlight; //mxd
 
 		//mxd. Helper shapes
 		private List<Line3D> persistenteventlines;
@@ -77,6 +76,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// Stores sizes of the text for text labels so that they only have to be computed once
 		private Dictionary<string, float> textlabelsizecache;
 
+		// Things that will be edited
+		private ICollection<Thing> editthings;
+
 		#endregion
 
 		#region ================== Properties
@@ -486,21 +488,26 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				editpressed = true;
 				
 				// Highlighted item not selected?
-				if(!highlighted.Selected && (BuilderPlug.Me.AutoClearSelection || (General.Map.Map.SelectedThingsCount == 0)))
+				if(!highlighted.Selected)
 				{
 					// Make this the only selection
-					selectionfromhighlight = true; //mxd
 					General.Map.Map.ClearSelectedThings();
-					highlighted.Selected = true;
+
+					editthings = new List<Thing> { highlighted };
+
 					UpdateSelectionInfo(); //mxd
 					General.Interface.RedrawDisplay();
 				}
+				else
+				{
+					editthings = General.Map.Map.GetSelectedThings(true);
+				}
 
 				// Update display
 				if(renderer.StartThings(false))
 				{
 					// Redraw highlight to show selection
-					renderer.RenderThing(highlighted, renderer.DetermineThingColor(highlighted), General.Settings.FixedThingsScale ? Presentation.THINGS_ALPHA : General.Settings.ActiveThingsAlpha);
+					renderer.RenderThing(highlighted, General.Colors.Highlight, General.Settings.FixedThingsScale ? Presentation.THINGS_ALPHA : General.Settings.ActiveThingsAlpha);
 					renderer.Finish();
 					renderer.Present();
 				}
@@ -522,7 +529,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				else 
 				{
 					General.Map.Map.ClearSelectedThings();
-					t.Selected = true;
+					General.Map.Map.ClearMarkedThings(false);
+					editthings = new List<Thing> { t };
 					Highlight(t);
 					General.Interface.RedrawDisplay();
 				}
@@ -537,9 +545,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Edit pressed in this mode?
 			if(editpressed)
 			{
-				// Anything selected?
-				ICollection<Thing> selected = General.Map.Map.GetSelectedThings(true);
-				if(selected.Count > 0)
+				if(editthings?.Count > 0)
 				{
 					if(General.Interface.IsActiveWindow)
 					{
@@ -548,19 +554,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 						{
 							//mxd. Show realtime thing edit dialog
 							General.Interface.OnEditFormValuesChanged += thingEditForm_OnValuesChanged;
-							DialogResult result = General.Interface.ShowEditThings(selected);
+							DialogResult result = General.Interface.ShowEditThings(editthings);
 							General.Interface.OnEditFormValuesChanged -= thingEditForm_OnValuesChanged;
 
-							// When a single thing was selected, deselect it now
-							if(selected.Count == 1 && selectionfromhighlight) 
-							{
-								General.Map.Map.ClearSelectedThings();
-							} 
-							else if(result == DialogResult.Cancel) //mxd. Restore selection...
-							{ 
-								foreach(Thing t in selected) t.Selected = true;
-							}
-
 							//mxd. Update helper lines
 							UpdateHelperObjects();
 
@@ -575,7 +571,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			}
 
 			editpressed = false;
-			selectionfromhighlight = false; //mxd
 			base.OnEditEnd();
 		}
 
@@ -747,12 +742,19 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Anything highlighted?
 				if((highlighted != null) && !highlighted.IsDisposed)
 				{
+					ICollection<Thing> dragthings;
+
 					// Highlighted item not selected?
 					if(!highlighted.Selected)
 					{
 						// Select only this thing for dragging
 						General.Map.Map.ClearSelectedThings();
-						highlighted.Selected = true;
+						dragthings = new List<Thing> { highlighted };
+					}
+					else
+					{
+						// Add all selected things to the things we want to drag
+						dragthings = General.Map.Map.GetSelectedThings(true);
 					}
 
 					// Start dragging the selection
@@ -762,14 +764,14 @@ namespace CodeImp.DoomBuilder.BuilderModes
 						bool thingscloned = false;
 						if(General.Interface.ShiftState) 
 						{
-							ICollection<Thing> selection = General.Map.Map.GetSelectedThings(true);
-							if(selection.Count > 0)
+							ICollection<Thing> clonedthings = new List<Thing>(dragthings.Count);
+							if(dragthings.Count > 0)
 							{
 								// Make undo
-								General.Map.UndoRedo.CreateUndo((selection.Count == 1 ? "Clone-drag thing" : "Clone-drag " + selection.Count + " things"));
+								General.Map.UndoRedo.CreateUndo((dragthings.Count == 1 ? "Clone-drag thing" : "Clone-drag " + dragthings.Count + " things"));
 
 								// Clone things
-								foreach(Thing t in selection)
+								foreach(Thing t in dragthings)
 								{
 									Thing clone = InsertThing(t.Position);
 									t.CopyPropertiesTo(clone);
@@ -798,15 +800,19 @@ namespace CodeImp.DoomBuilder.BuilderModes
 									}
 
 									t.Selected = false;
-									clone.Selected = true;
+
+									clonedthings.Add(clone);
 								}
 
 								// We'll want to skip creating additional Undo in DragThingsMode
 								thingscloned = true;
+
+								// All the cloned things are now the things we want to drag
+								dragthings = clonedthings;
 							}
 						}
 
-						General.Editing.ChangeMode(new DragThingsMode(new ThingsMode(), mousedownmappos, !thingscloned));
+						General.Editing.ChangeMode(new DragThingsMode(new ThingsMode(), mousedownmappos, dragthings, !thingscloned));
 					}
 				}
 			}
@@ -1048,13 +1054,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			{
 				// Dispose old labels
 				foreach(TextLabel l in labels.Values) l.Dispose();
-
-				// Don't show lables for selected-from-highlight item
-				if(selectionfromhighlight)
-				{
-					labels.Clear();
-					return;
-				}
 			}
 
 			// Make text labels for selected linedefs
diff --git a/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs b/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs
index ab07bd24ec6d9fa3fef486fbda7472bf31f4246a..a17bdd20321904a2200bf49b4c1ed27cbab24e8a 100755
--- a/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs
+++ b/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs
@@ -55,17 +55,21 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 		// Interface
 		new private bool editpressed;
-		private bool selectionfromhighlight; //mxd
 
 		// The blockmap makes is used to make finding lines faster
 		BlockMap<BlockEntry> blockmap;
 
+		// Vertices that will be edited
+		ICollection<Vertex> editvertices;
+
 		#endregion
 
 		#region ================== Properties
 
 		public override object HighlightedObject { get { return highlighted; } }
 
+		public override bool AlwaysShowVertices { get { return true;  } }
+
 		#endregion
 
 		#region ================== Constructor / Disposer
@@ -285,22 +289,30 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Edit pressed in this mode
 				editpressed = true;
 
+				// We use the marks to determine what to edit/drag, so clear it first
+				General.Map.Map.ClearMarkedVertices(false);
+
 				// Highlighted item not selected?
-				if(!highlighted.Selected && (BuilderPlug.Me.AutoClearSelection || (General.Map.Map.SelectedVerticessCount == 0)))
+				if(!highlighted.Selected)
 				{
 					// Make this the only selection
-					selectionfromhighlight = true; //mxd
 					General.Map.Map.ClearSelectedVertices();
-					highlighted.Selected = true;
+
+					editvertices = new List<Vertex> { highlighted };
+
 					UpdateSelectionInfo(); //mxd
 					General.Interface.RedrawDisplay();
 				}
+				else
+				{
+					editvertices = General.Map.Map.GetSelectedVertices(true);
+				}
 
 				// Update display
 				if(renderer.StartPlotter(false))
 				{
 					// Redraw highlight to show selection
-					renderer.PlotVertex(highlighted, renderer.DetermineVertexColor(highlighted));
+					renderer.PlotVertex(highlighted, ColorCollection.HIGHLIGHT);
 					renderer.Finish();
 					renderer.Present();
 				}
@@ -393,27 +405,15 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Edit pressed in this mode?
 			if(editpressed)
 			{
-				// Anything selected?
-				ICollection<Vertex> selected = General.Map.Map.GetSelectedVertices(true);
-				if(selected.Count > 0)
+				if(editvertices?.Count > 0)
 				{
 					if(General.Interface.IsActiveWindow)
 					{
 						//mxd. Show realtime vertex edit dialog
 						General.Interface.OnEditFormValuesChanged += vertexEditForm_OnValuesChanged;
-						DialogResult result = General.Interface.ShowEditVertices(selected);
+						DialogResult result = General.Interface.ShowEditVertices(editvertices);
 						General.Interface.OnEditFormValuesChanged -= vertexEditForm_OnValuesChanged;
 
-						// When a single vertex was selected, deselect it now
-						if(selected.Count == 1 && selectionfromhighlight) 
-						{
-							General.Map.Map.ClearSelectedVertices();
-						} 
-						else if(result == DialogResult.Cancel) //mxd. Restore selection...
-						{ 
-							foreach(Vertex v in selected) v.Selected = true;
-						}
-
 						// Update entire display
 						UpdateSelectionInfo(); //mxd
 						General.Interface.RedrawDisplay();
@@ -422,7 +422,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			}
 
 			editpressed = false;
-			selectionfromhighlight = false; //mxd
 			base.OnEditEnd();
 		}
 
@@ -638,17 +637,24 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				// Anything highlighted?
 				if((highlighted != null) && !highlighted.IsDisposed)
 				{
+					ICollection<Vertex> dragvertices;
+
 					// Highlighted item not selected?
 					if(!highlighted.Selected)
 					{
 						// Select only this vertex for dragging
 						General.Map.Map.ClearSelectedVertices();
-						highlighted.Selected = true;
+						dragvertices = new List<Vertex> { highlighted };
+					}
+					else
+					{
+						// Add all selected vertices to the vertices we want to drag
+						dragvertices = General.Map.Map.GetSelectedVertices(true);
 					}
 
 					// Start dragging the selection
 					if(!BuilderPlug.Me.DontMoveGeometryOutsideMapBoundary || CanDrag()) //mxd
-						General.Editing.ChangeMode(new DragVerticesMode(mousedownmappos));
+						General.Editing.ChangeMode(new DragVerticesMode(mousedownmappos, dragvertices));
 				}
 			}
 		}
@@ -759,7 +765,17 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			
 			return base.OnCopyBegin();
 		}
-		
+
+		/// <summary>
+		/// If map elements have changed the blockmap needs to be recreated.
+		/// </summary>
+		public override void OnMapElementsChanged()
+		{
+			base.OnMapElementsChanged();
+
+			CreateBlockmap();
+		}
+
 		#endregion
 
 		#region ================== Actions
diff --git a/Source/Plugins/BuilderModes/ErrorChecks/CheckOverlappingLines.cs b/Source/Plugins/BuilderModes/ErrorChecks/CheckOverlappingLines.cs
index f05e9f51b7f49fce3fde90ca19598f4f080a1cc9..6b2a666b3d2f404c3fa43d8d963484b41346f510 100755
--- a/Source/Plugins/BuilderModes/ErrorChecks/CheckOverlappingLines.cs
+++ b/Source/Plugins/BuilderModes/ErrorChecks/CheckOverlappingLines.cs
@@ -18,8 +18,10 @@
 
 using System;
 using System.Collections.Generic;
-using CodeImp.DoomBuilder.Map;
+using System.Linq;
 using System.Threading;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Geometry;
 
 #endregion
 
@@ -72,6 +74,37 @@ namespace CodeImp.DoomBuilder.BuilderModes
 							if(!object.ReferenceEquals(l, d) && !doneblocklines.ContainsKey(d))
 							{
 								double lu, du;
+
+								// Temporary lines
+								Line2D tl = l.Line;
+								Line2D td = d.Line;
+
+								// If vertices are off-grid and far from the map's origin the calculation of the intersection can go wrong because of rounding errors.
+								// So if any vertex is off-grid we'll to the calculations with lines that are closer to the origin. This is pretty ugly :(
+								// See https://github.com/jewalky/UltimateDoomBuilder/issues/713
+								if (General.Map.FormatInterface.VertexDecimals > 0 &&
+									l.Line.v1.x % 1 != 0.0 && l.Line.v1.y % 1 != 0.0 && l.Line.v2.x % 1 != 0.0 && l.Line.v2.y % 1 != 0.0 &&
+									d.Line.v1.x % 1 != 0.0 && d.Line.v1.y % 1 != 0.0 && d.Line.v2.x % 1 != 0.0 && d.Line.v2.y % 1 != 0.0)
+								{
+									HashSet<Vertex> vertices = new HashSet<Vertex>() { l.Start, l.End, d.Start, d.End };
+
+									// Create the offset we want to move the lines by. It is getting the most extreme values of the vertices
+									Vector2D offset = new Vector2D(
+										(int)vertices.OrderBy(v => Math.Abs(v.Position.x)).First().Position.x,
+										(int)vertices.OrderBy(v => Math.Abs(v.Position.y)).First().Position.y
+									);
+
+									// Create the two lines to check. this takes the original values, applies the offset, then rounds them to the map format's precision
+									tl = new Line2D(
+										new Vector2D(Math.Round(l.Line.v1.x - offset.x, General.Map.FormatInterface.VertexDecimals), Math.Round(l.Line.v1.y - offset.y, General.Map.FormatInterface.VertexDecimals)),
+										new Vector2D(Math.Round(l.Line.v2.x - offset.x, General.Map.FormatInterface.VertexDecimals), Math.Round(l.Line.v2.y - offset.y, General.Map.FormatInterface.VertexDecimals))
+									);
+
+									td = new Line2D(
+										new Vector2D(Math.Round(d.Line.v1.x - offset.x, General.Map.FormatInterface.VertexDecimals), Math.Round(d.Line.v1.y - offset.y, General.Map.FormatInterface.VertexDecimals)),
+										new Vector2D(Math.Round(d.Line.v2.x - offset.x, General.Map.FormatInterface.VertexDecimals), Math.Round(d.Line.v2.y - offset.y, General.Map.FormatInterface.VertexDecimals))
+									);
+								}
 								
 								//mxd. This can also happen. I suppose. Some people manage to do this. I dunno how, but they do...
 								if((l.Start.Position == d.Start.Position && l.End.Position == d.End.Position)
@@ -80,7 +113,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 									SubmitResult(new ResultLineOverlapping(l, d));
 									donelines[d] = d;
 								} 
-								else if(l.Line.GetIntersection(d.Line, out du, out lu)) 
+								else if(tl.GetIntersection(td, out du, out lu)) 
 								{
 									// Check if the lines touch. Note that I don't include 0.0 and 1.0 here because
 									// the lines may be touching at the ends when sharing the same vertex.
@@ -89,22 +122,22 @@ namespace CodeImp.DoomBuilder.BuilderModes
 										lu = Math.Round(lu, General.Map.FormatInterface.VertexDecimals);
 										du = Math.Round(du, General.Map.FormatInterface.VertexDecimals);
 									}
-									
-									if((lu > 0.0) && (lu < 1.0) && (du > 0.0) && (du < 1.0))
+
+									if ((lu > 0.0) && (lu < 1.0) && (du > 0.0) && (du < 1.0))
 									{
 										// Check if not the same sector on all sides
 										Sector samesector = null;
-										if(l.Front != null) samesector = l.Front.Sector;
-										else if(l.Back != null) samesector = l.Back.Sector;
-										else if(d.Front != null) samesector = d.Front.Sector;
-										else if(d.Back != null) samesector = d.Back.Sector;
-										
-										if((l.Front == null) || (l.Front.Sector != samesector)) samesector = null;
-										else if((l.Back == null) || (l.Back.Sector != samesector)) samesector = null;
-										else if((d.Front == null) || (d.Front.Sector != samesector)) samesector = null;
-										else if((d.Back == null) || (d.Back.Sector != samesector)) samesector = null;
-
-										if(samesector == null)
+										if (l.Front != null) samesector = l.Front.Sector;
+										else if (l.Back != null) samesector = l.Back.Sector;
+										else if (d.Front != null) samesector = d.Front.Sector;
+										else if (d.Back != null) samesector = d.Back.Sector;
+
+										if ((l.Front == null) || (l.Front.Sector != samesector)) samesector = null;
+										else if ((l.Back == null) || (l.Back.Sector != samesector)) samesector = null;
+										else if ((d.Front == null) || (d.Front.Sector != samesector)) samesector = null;
+										else if ((d.Back == null) || (d.Back.Sector != samesector)) samesector = null;
+
+										if (samesector == null)
 										{
 											SubmitResult(new ResultLineOverlapping(l, d));
 											donelines[d] = d;
diff --git a/Source/Plugins/BuilderModes/General/Association.cs b/Source/Plugins/BuilderModes/General/Association.cs
index 49b91bfffb8f3a7dbdeec429876c9a9f538fc01f..73bb20b299529409a33058bdf92ada52ea33c2bd 100755
--- a/Source/Plugins/BuilderModes/General/Association.cs
+++ b/Source/Plugins/BuilderModes/General/Association.cs
@@ -550,7 +550,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				{
 					for (int i = 0; i < ti.Args.Length; i++)
 					{
-						if ((ti.Args[i].Type == (int)UniversalType.SectorTag ||
+						if (actionargs[i] != 0 && // Ignore all args that reference tag 0, since that are likely the majority of all elements
+							(ti.Args[i].Type == (int)UniversalType.SectorTag ||
 							ti.Args[i].Type == (int)UniversalType.LinedefTag ||
 							ti.Args[i].Type == (int)UniversalType.ThingTag))
 						{
diff --git a/Source/Plugins/BuilderModes/General/BuilderPlug.cs b/Source/Plugins/BuilderModes/General/BuilderPlug.cs
index 1f90bb6b5f56268b8b86363b8446ecf7992bec44..9b35433f3251336a0e6d5779c01b4ca22bc0e074 100755
--- a/Source/Plugins/BuilderModes/General/BuilderPlug.cs
+++ b/Source/Plugins/BuilderModes/General/BuilderPlug.cs
@@ -42,6 +42,11 @@ using System.Runtime.CompilerServices;
 
 namespace CodeImp.DoomBuilder.BuilderModes
 {
+	internal class ToastMessages
+	{
+		public static readonly string VISUALSLOPING = "visualsloping";
+	}
+
 	public class BuilderPlug : Plug
 	{
 		#region ================== API Declarations
@@ -249,8 +254,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			//mxd
 			General.Actions.BindMethods(this);
+
+			// Register toasts
+			General.ToastManager.RegisterToast(ToastMessages.VISUALSLOPING, "Visual sloping", "Toasts related to visual sloping");
 		}
-		
+
 		// Disposer
 		public override void Dispose()
 		{
diff --git a/Source/Plugins/BuilderModes/General/CopyStructures.cs b/Source/Plugins/BuilderModes/General/CopyStructures.cs
index 5cf8816e90c8e791f24db5421d87d08fcf269adb..5e9c7a8a8f37fe0e4ceab0851d2615ebd003ab90 100755
--- a/Source/Plugins/BuilderModes/General/CopyStructures.cs
+++ b/Source/Plugins/BuilderModes/General/CopyStructures.cs
@@ -322,6 +322,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		[FieldDescription(Description = "Ceiling glow", Field1 = "ceilingglowcolor", Field2 = "ceilingglowheight")]
 		public bool CeilingGlow = true;
 
+		[FieldDescription(Description = "Fog density", Field1 = "fogdensity")]
+		public bool FogDensity = true;
+
 		[FieldDescription(Description = "Desaturation", Field1 = "desaturation")]
 		public bool Desaturation = true;
 
diff --git a/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.Designer.cs b/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.Designer.cs
index 8fd1fa200761b16ec6692142cdb050c32305cc89..282914177dcf939eb69f5b3075852c8ba2bf5dc4 100755
--- a/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.Designer.cs
+++ b/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.Designer.cs
@@ -30,6 +30,7 @@
 		{
 			this.toolStrip1 = new System.Windows.Forms.ToolStrip();
 			this.continuousdrawing = new System.Windows.Forms.ToolStripButton();
+			this.radialdrawing = new System.Windows.Forms.ToolStripButton();
 			this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
 			this.subdivslabel = new System.Windows.Forms.ToolStripLabel();
 			this.subdivs = new CodeImp.DoomBuilder.Controls.ToolStripNumericUpDown();
@@ -47,6 +48,7 @@
 			this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
             this.continuousdrawing,
             this.showguidelines,
+            this.radialdrawing,
             this.toolStripSeparator1,
             this.subdivslabel,
             this.subdivs,
@@ -184,6 +186,17 @@
 			this.showguidelines.Text = "Guidelines";
 			this.showguidelines.CheckedChanged += new System.EventHandler(this.showguidelines_CheckedChanged);
 			// 
+			// radialdrawing
+			// 
+			this.radialdrawing.CheckOnClick = true;
+			this.radialdrawing.Image = global::CodeImp.DoomBuilder.BuilderModes.Properties.Resources.Radial;
+			this.radialdrawing.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.radialdrawing.Margin = new System.Windows.Forms.Padding(2, 1, 0, 2);
+			this.radialdrawing.Name = "radialdrawing";
+			this.radialdrawing.Size = new System.Drawing.Size(82, 22);
+			this.radialdrawing.Text = "Radial drawing";
+			this.radialdrawing.CheckedChanged += new System.EventHandler(this.radialdrawing_CheckedChanged);
+			// 
 			// DrawEllipseOptionsPanel
 			// 
 			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
@@ -207,6 +220,7 @@
 		private CodeImp.DoomBuilder.Controls.ToolStripNumericUpDown spikiness;
 		private System.Windows.Forms.ToolStripButton reset;
 		private System.Windows.Forms.ToolStripButton continuousdrawing;
+		private System.Windows.Forms.ToolStripButton radialdrawing;
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
 		private System.Windows.Forms.ToolStripLabel anglelabel;
 		private CodeImp.DoomBuilder.Controls.ToolStripNumericUpDown angle;
diff --git a/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.cs b/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.cs
index 042b80acaeb29151a8a774bfb391b072f08db327..af681df73f63b7c2fa066859d0a74fef5d0cecb0 100755
--- a/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.cs
+++ b/Source/Plugins/BuilderModes/Interface/DrawEllipseOptionsPanel.cs
@@ -8,6 +8,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public event EventHandler OnValueChanged;
 		public event EventHandler OnContinuousDrawingChanged;
 		public event EventHandler OnShowGuidelinesChanged;
+		public event EventHandler OnRadialDrawingChanged;
 
 		private bool blockevents;
 
@@ -20,6 +21,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public int MinSpikiness { get { return (int)spikiness.Minimum; } set { spikiness.Minimum = value; } }
 		public bool ContinuousDrawing { get { return continuousdrawing.Checked; } set { continuousdrawing.Checked = value; } }
 		public bool ShowGuidelines { get { return showguidelines.Checked; } set { showguidelines.Checked = value; } }
+		public bool RadialDrawing { get { return radialdrawing.Checked; } set { radialdrawing.Checked = value; } }
 		
 		public DrawEllipseOptionsPanel() 
 		{
@@ -35,6 +37,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Interface.BeginToolbarUpdate();
 			General.Interface.AddButton(continuousdrawing);
 			General.Interface.AddButton(showguidelines);
+			General.Interface.AddButton(radialdrawing);
 			General.Interface.AddButton(toolStripSeparator1);
 			General.Interface.AddButton(subdivslabel);
 			General.Interface.AddButton(subdivs);
@@ -59,6 +62,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Interface.RemoveButton(toolStripSeparator1);
 			General.Interface.RemoveButton(showguidelines);
 			General.Interface.RemoveButton(continuousdrawing);
+			General.Interface.RemoveButton(radialdrawing);
 			General.Interface.EndToolbarUpdate();
 		}
 
@@ -89,5 +93,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			if(OnShowGuidelinesChanged != null) OnShowGuidelinesChanged(showguidelines.Checked, EventArgs.Empty);
 		}
+		
+		private void radialdrawing_CheckedChanged(object sender, EventArgs e)
+		{
+			if(OnRadialDrawingChanged != null) OnRadialDrawingChanged(radialdrawing.Checked, EventArgs.Empty);
+		}
 	}
 }
diff --git a/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.Designer.cs b/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.Designer.cs
index 1ac5f0257d7cbde514516f9350986aed8a553c8d..002bdc53754df1a728a2e8580345ba8e6c0bf1a5 100755
--- a/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.Designer.cs
+++ b/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.Designer.cs
@@ -31,6 +31,7 @@
 			this.toolStrip1 = new System.Windows.Forms.ToolStrip();
 			this.continuousdrawing = new System.Windows.Forms.ToolStripButton();
 			this.showguidelines = new System.Windows.Forms.ToolStripButton();
+			this.radialdrawing = new System.Windows.Forms.ToolStripButton();
 			this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
 			this.radiuslabel = new System.Windows.Forms.ToolStripLabel();
 			this.radius = new CodeImp.DoomBuilder.Controls.ToolStripNumericUpDown();
@@ -45,6 +46,7 @@
 			this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
             this.continuousdrawing,
             this.showguidelines,
+            this.radialdrawing,
             this.toolStripSeparator1,
             this.radiuslabel,
             this.radius,
@@ -78,6 +80,17 @@
 			this.showguidelines.Text = "Guidelines";
 			this.showguidelines.CheckedChanged += new System.EventHandler(this.showguidelines_CheckedChanged);
 			// 
+			// radialdrawing
+			// 
+			this.radialdrawing.CheckOnClick = true;
+			this.radialdrawing.Image = global::CodeImp.DoomBuilder.BuilderModes.Properties.Resources.Radial;
+			this.radialdrawing.ImageTransparentColor = System.Drawing.Color.Magenta;
+			this.radialdrawing.Margin = new System.Windows.Forms.Padding(2, 1, 0, 2);
+			this.radialdrawing.Name = "radialdrawing";
+			this.radialdrawing.Size = new System.Drawing.Size(82, 22);
+			this.radialdrawing.Text = "Radial drawing";
+			this.radialdrawing.CheckedChanged += new System.EventHandler(this.radialdrawing_CheckedChanged);
+			// 
 			// toolStripSeparator1
 			// 
 			this.toolStripSeparator1.Name = "toolStripSeparator1";
@@ -176,5 +189,6 @@
 		private System.Windows.Forms.ToolStripButton continuousdrawing;
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
 		private System.Windows.Forms.ToolStripButton showguidelines;
+		private System.Windows.Forms.ToolStripButton radialdrawing;
 	}
 }
diff --git a/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.cs b/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.cs
index cd880049a16602554e56ab2b16eeb97fc2722bdb..f60b60d4865a7d0d177977181a28354544a00f92 100755
--- a/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.cs
+++ b/Source/Plugins/BuilderModes/Interface/DrawRectangleOptionsPanel.cs
@@ -8,6 +8,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public event EventHandler OnValueChanged;
 		public event EventHandler OnContinuousDrawingChanged;
 		public event EventHandler OnShowGuidelinesChanged;
+		public event EventHandler OnRadialDrawingChanged;
 
 		private bool blockevents;
 
@@ -19,6 +20,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public int MinSubdivisions { get { return (int)subdivs.Minimum; } set { subdivs.Minimum = value; } }
 		public bool ContinuousDrawing { get { return continuousdrawing.Checked; } set { continuousdrawing.Checked = value; } }
 		public bool ShowGuidelines { get { return showguidelines.Checked; } set { showguidelines.Checked = value; } }
+		public bool RadialDrawing { get { return radialdrawing.Checked; } set { radialdrawing.Checked = value; } }
 
 		public DrawRectangleOptionsPanel() 
 		{
@@ -33,6 +35,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Interface.BeginToolbarUpdate();
 			General.Interface.AddButton(continuousdrawing);
 			General.Interface.AddButton(showguidelines);
+			General.Interface.AddButton(radialdrawing);
 			General.Interface.AddButton(toolStripSeparator1);
 			General.Interface.AddButton(radiuslabel);
 			General.Interface.AddButton(radius);
@@ -53,6 +56,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			General.Interface.RemoveButton(toolStripSeparator1);
 			General.Interface.RemoveButton(showguidelines);
 			General.Interface.RemoveButton(continuousdrawing);
+			General.Interface.RemoveButton(radialdrawing);
 			General.Interface.EndToolbarUpdate();
 		}
 
@@ -82,5 +86,10 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			if(OnShowGuidelinesChanged != null) OnShowGuidelinesChanged(showguidelines.Checked, EventArgs.Empty);
 		}
+		
+		private void radialdrawing_CheckedChanged(object sender, EventArgs e)
+		{
+			if(OnRadialDrawingChanged != null) OnRadialDrawingChanged(radialdrawing.Checked, EventArgs.Empty);
+		}
 	}
 }
diff --git a/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.Designer.cs b/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.Designer.cs
index df519de7c5fbcd890815bfb8f75cd2a89da462b6..398297abbd074cbf489f00d050dea8285099d366 100755
--- a/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.Designer.cs
+++ b/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.Designer.cs
@@ -39,10 +39,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.label19 = new System.Windows.Forms.Label();
 			this.label6 = new System.Windows.Forms.Label();
 			this.label5 = new System.Windows.Forms.Label();
-			this.relposy = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-			this.relposx = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-			this.absposy = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-			this.absposx = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
 			this.label2 = new System.Windows.Forms.Label();
 			this.label1 = new System.Windows.Forms.Label();
 			this.groupBox2 = new System.Windows.Forms.GroupBox();
@@ -52,11 +48,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.label9 = new System.Windows.Forms.Label();
 			this.label8 = new System.Windows.Forms.Label();
 			this.label7 = new System.Windows.Forms.Label();
-			this.relsizey = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-			this.relsizex = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
 			this.label4 = new System.Windows.Forms.Label();
-			this.abssizey = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-			this.abssizex = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
 			this.label3 = new System.Windows.Forms.Label();
 			this.groupBox3 = new System.Windows.Forms.GroupBox();
 			this.heightmode = new System.Windows.Forms.ComboBox();
@@ -66,29 +58,29 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.fliph = new System.Windows.Forms.Button();
 			this.label13 = new System.Windows.Forms.Label();
 			this.label11 = new System.Windows.Forms.Label();
-			this.absrot = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
-			this.ceiltexgroup = new System.Windows.Forms.GroupBox();
-			this.ceiltexoffset = new System.Windows.Forms.CheckBox();
-			this.ceiltexrotation = new System.Windows.Forms.CheckBox();
-			this.ceiltexscale = new System.Windows.Forms.CheckBox();
-			this.ceiltexall = new System.Windows.Forms.CheckBox();
-			this.floortexrotation = new System.Windows.Forms.CheckBox();
-			this.floortexoffset = new System.Windows.Forms.CheckBox();
-			this.floortexscale = new System.Windows.Forms.CheckBox();
-			this.floortexall = new System.Windows.Forms.CheckBox();
-			this.floortexgroup = new System.Windows.Forms.GroupBox();
 			this.tooltip = new System.Windows.Forms.ToolTip(this.components);
+			this.pinfloortextures = new System.Windows.Forms.CheckBox();
+			this.gbTextures = new System.Windows.Forms.GroupBox();
+			this.pinceilingtextures = new System.Windows.Forms.CheckBox();
+			this.absrot = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.relsizey = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.relsizex = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.abssizey = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.abssizex = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.relposy = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.relposx = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.absposy = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.absposx = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
 			this.groupBox1.SuspendLayout();
 			this.groupBox2.SuspendLayout();
 			this.groupBox3.SuspendLayout();
-			this.ceiltexgroup.SuspendLayout();
-			this.floortexgroup.SuspendLayout();
+			this.gbTextures.SuspendLayout();
 			this.SuspendLayout();
 			// 
 			// groupBox1
 			// 
-			this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.groupBox1.Controls.Add(this.preciseposition);
 			this.groupBox1.Controls.Add(this.orgposy);
 			this.groupBox1.Controls.Add(this.orgposx);
@@ -118,7 +110,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.preciseposition.TabIndex = 6;
 			this.preciseposition.Text = "High precision positioning";
 			this.tooltip.SetToolTip(this.preciseposition, "When checked, thing and vertex positions will be set using floating point precisi" +
-					"on.\r\nOtherwise, they will be rounded to the nearest integer.");
+        "on.\r\nOtherwise, they will be rounded to the nearest integer.");
 			this.preciseposition.UseVisualStyleBackColor = true;
 			this.preciseposition.CheckedChanged += new System.EventHandler(this.preciseposition_CheckedChanged);
 			// 
@@ -180,90 +172,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.label5.TabIndex = 12;
 			this.label5.Text = "mp";
 			// 
-			// relposy
-			// 
-			this.relposy.AllowDecimal = true;
-			this.relposy.AllowNegative = true;
-			this.relposy.AllowRelative = true;
-			this.relposy.ButtonStep = 1;
-			this.relposy.ButtonStepBig = 10F;
-			this.relposy.ButtonStepFloat = 1F;
-			this.relposy.ButtonStepSmall = 0.1F;
-			this.relposy.ButtonStepsUseModifierKeys = false;
-			this.relposy.ButtonStepsWrapAround = false;
-			this.relposy.Location = new System.Drawing.Point(136, 83);
-			this.relposy.Name = "relposy";
-			this.relposy.Size = new System.Drawing.Size(72, 24);
-			this.relposy.StepValues = null;
-			this.relposy.TabIndex = 5;
-			this.relposy.WhenEnterPressed += new System.EventHandler(this.relposy_Validated);
-			this.relposy.Validated += new System.EventHandler(this.relposy_Validated);
-			this.relposy.WhenButtonsClicked += new System.EventHandler(this.relposy_Validated);
-			this.relposy.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
-			// relposx
-			// 
-			this.relposx.AllowDecimal = true;
-			this.relposx.AllowNegative = true;
-			this.relposx.AllowRelative = true;
-			this.relposx.ButtonStep = 1;
-			this.relposx.ButtonStepBig = 10F;
-			this.relposx.ButtonStepFloat = 1F;
-			this.relposx.ButtonStepSmall = 0.1F;
-			this.relposx.ButtonStepsUseModifierKeys = false;
-			this.relposx.ButtonStepsWrapAround = false;
-			this.relposx.Location = new System.Drawing.Point(58, 83);
-			this.relposx.Name = "relposx";
-			this.relposx.Size = new System.Drawing.Size(72, 24);
-			this.relposx.StepValues = null;
-			this.relposx.TabIndex = 4;
-			this.relposx.WhenEnterPressed += new System.EventHandler(this.relposx_Validated);
-			this.relposx.Validated += new System.EventHandler(this.relposx_Validated);
-			this.relposx.WhenButtonsClicked += new System.EventHandler(this.relposx_Validated);
-			this.relposx.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
-			// absposy
-			// 
-			this.absposy.AllowDecimal = true;
-			this.absposy.AllowNegative = true;
-			this.absposy.AllowRelative = true;
-			this.absposy.ButtonStep = 1;
-			this.absposy.ButtonStepBig = 10F;
-			this.absposy.ButtonStepFloat = 1F;
-			this.absposy.ButtonStepSmall = 0.1F;
-			this.absposy.ButtonStepsUseModifierKeys = false;
-			this.absposy.ButtonStepsWrapAround = false;
-			this.absposy.Location = new System.Drawing.Point(136, 53);
-			this.absposy.Name = "absposy";
-			this.absposy.Size = new System.Drawing.Size(72, 24);
-			this.absposy.StepValues = null;
-			this.absposy.TabIndex = 3;
-			this.absposy.WhenEnterPressed += new System.EventHandler(this.absposy_Validated);
-			this.absposy.Validated += new System.EventHandler(this.absposy_Validated);
-			this.absposy.WhenButtonsClicked += new System.EventHandler(this.absposy_Validated);
-			this.absposy.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
-			// absposx
-			// 
-			this.absposx.AllowDecimal = true;
-			this.absposx.AllowNegative = true;
-			this.absposx.AllowRelative = true;
-			this.absposx.ButtonStep = 1;
-			this.absposx.ButtonStepBig = 10F;
-			this.absposx.ButtonStepFloat = 1F;
-			this.absposx.ButtonStepSmall = 0.1F;
-			this.absposx.ButtonStepsUseModifierKeys = false;
-			this.absposx.ButtonStepsWrapAround = false;
-			this.absposx.Location = new System.Drawing.Point(58, 53);
-			this.absposx.Name = "absposx";
-			this.absposx.Size = new System.Drawing.Size(72, 24);
-			this.absposx.StepValues = null;
-			this.absposx.TabIndex = 2;
-			this.absposx.WhenEnterPressed += new System.EventHandler(this.absposx_Validated);
-			this.absposx.Validated += new System.EventHandler(this.absposx_Validated);
-			this.absposx.WhenButtonsClicked += new System.EventHandler(this.absposx_Validated);
-			this.absposx.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
 			// label2
 			// 
 			this.label2.AutoSize = true;
@@ -284,8 +192,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// 
 			// groupBox2
 			// 
-			this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.groupBox2.Controls.Add(this.orgsizey);
 			this.groupBox2.Controls.Add(this.orgsizex);
 			this.groupBox2.Controls.Add(this.label12);
@@ -363,48 +271,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.label7.TabIndex = 16;
 			this.label7.Text = "mp";
 			// 
-			// relsizey
-			// 
-			this.relsizey.AllowDecimal = true;
-			this.relsizey.AllowNegative = true;
-			this.relsizey.AllowRelative = true;
-			this.relsizey.ButtonStep = 1;
-			this.relsizey.ButtonStepBig = 10F;
-			this.relsizey.ButtonStepFloat = 1F;
-			this.relsizey.ButtonStepSmall = 0.1F;
-			this.relsizey.ButtonStepsUseModifierKeys = false;
-			this.relsizey.ButtonStepsWrapAround = false;
-			this.relsizey.Location = new System.Drawing.Point(136, 83);
-			this.relsizey.Name = "relsizey";
-			this.relsizey.Size = new System.Drawing.Size(72, 24);
-			this.relsizey.StepValues = null;
-			this.relsizey.TabIndex = 5;
-			this.relsizey.WhenEnterPressed += new System.EventHandler(this.relsizey_Validated);
-			this.relsizey.Validated += new System.EventHandler(this.relsizey_Validated);
-			this.relsizey.WhenButtonsClicked += new System.EventHandler(this.relsizey_Validated);
-			this.relsizey.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
-			// relsizex
-			// 
-			this.relsizex.AllowDecimal = true;
-			this.relsizex.AllowNegative = true;
-			this.relsizex.AllowRelative = true;
-			this.relsizex.ButtonStep = 1;
-			this.relsizex.ButtonStepBig = 10F;
-			this.relsizex.ButtonStepFloat = 1F;
-			this.relsizex.ButtonStepSmall = 0.1F;
-			this.relsizex.ButtonStepsUseModifierKeys = false;
-			this.relsizex.ButtonStepsWrapAround = false;
-			this.relsizex.Location = new System.Drawing.Point(58, 83);
-			this.relsizex.Name = "relsizex";
-			this.relsizex.Size = new System.Drawing.Size(72, 24);
-			this.relsizex.StepValues = null;
-			this.relsizex.TabIndex = 4;
-			this.relsizex.WhenEnterPressed += new System.EventHandler(this.relsizex_Validated);
-			this.relsizex.Validated += new System.EventHandler(this.relsizex_Validated);
-			this.relsizex.WhenButtonsClicked += new System.EventHandler(this.relsizex_Validated);
-			this.relsizex.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
 			// label4
 			// 
 			this.label4.AutoSize = true;
@@ -414,48 +280,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.label4.TabIndex = 13;
 			this.label4.Text = "Scale:";
 			// 
-			// abssizey
-			// 
-			this.abssizey.AllowDecimal = true;
-			this.abssizey.AllowNegative = true;
-			this.abssizey.AllowRelative = true;
-			this.abssizey.ButtonStep = 1;
-			this.abssizey.ButtonStepBig = 10F;
-			this.abssizey.ButtonStepFloat = 1F;
-			this.abssizey.ButtonStepSmall = 0.1F;
-			this.abssizey.ButtonStepsUseModifierKeys = false;
-			this.abssizey.ButtonStepsWrapAround = false;
-			this.abssizey.Location = new System.Drawing.Point(136, 53);
-			this.abssizey.Name = "abssizey";
-			this.abssizey.Size = new System.Drawing.Size(72, 24);
-			this.abssizey.StepValues = null;
-			this.abssizey.TabIndex = 3;
-			this.abssizey.WhenEnterPressed += new System.EventHandler(this.abssizey_Validated);
-			this.abssizey.Validated += new System.EventHandler(this.abssizey_Validated);
-			this.abssizey.WhenButtonsClicked += new System.EventHandler(this.abssizey_Validated);
-			this.abssizey.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
-			// abssizex
-			// 
-			this.abssizex.AllowDecimal = true;
-			this.abssizex.AllowNegative = true;
-			this.abssizex.AllowRelative = true;
-			this.abssizex.ButtonStep = 1;
-			this.abssizex.ButtonStepBig = 10F;
-			this.abssizex.ButtonStepFloat = 1F;
-			this.abssizex.ButtonStepSmall = 0.1F;
-			this.abssizex.ButtonStepsUseModifierKeys = false;
-			this.abssizex.ButtonStepsWrapAround = false;
-			this.abssizex.Location = new System.Drawing.Point(58, 53);
-			this.abssizex.Name = "abssizex";
-			this.abssizex.Size = new System.Drawing.Size(72, 24);
-			this.abssizex.StepValues = null;
-			this.abssizex.TabIndex = 2;
-			this.abssizex.WhenEnterPressed += new System.EventHandler(this.abssizex_Validated);
-			this.abssizex.Validated += new System.EventHandler(this.abssizex_Validated);
-			this.abssizex.WhenButtonsClicked += new System.EventHandler(this.abssizex_Validated);
-			this.abssizex.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
-			// 
 			// label3
 			// 
 			this.label3.AutoSize = true;
@@ -467,8 +291,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// 
 			// groupBox3
 			// 
-			this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.groupBox3.Controls.Add(this.heightmode);
 			this.groupBox3.Controls.Add(this.label10);
 			this.groupBox3.Controls.Add(this.label14);
@@ -562,9 +386,45 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.label11.TabIndex = 22;
 			this.label11.Text = "deg.";
 			// 
+			// pinfloortextures
+			// 
+			this.pinfloortextures.AutoSize = true;
+			this.pinfloortextures.Location = new System.Drawing.Point(9, 42);
+			this.pinfloortextures.Name = "pinfloortextures";
+			this.pinfloortextures.Size = new System.Drawing.Size(104, 17);
+			this.pinfloortextures.TabIndex = 39;
+			this.pinfloortextures.Text = "Pin floor textures";
+			this.pinfloortextures.UseVisualStyleBackColor = true;
+			this.pinfloortextures.CheckedChanged += new System.EventHandler(this.cbPinFloorTextures_CheckedChanged);
+			// 
+			// gbTextures
+			// 
+			this.gbTextures.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+			this.gbTextures.Controls.Add(this.pinceilingtextures);
+			this.gbTextures.Controls.Add(this.pinfloortextures);
+			this.gbTextures.Location = new System.Drawing.Point(3, 400);
+			this.gbTextures.Name = "gbTextures";
+			this.gbTextures.Size = new System.Drawing.Size(243, 68);
+			this.gbTextures.TabIndex = 40;
+			this.gbTextures.TabStop = false;
+			this.gbTextures.Text = "Textures";
+			// 
+			// pinceilingtextures
+			// 
+			this.pinceilingtextures.AutoSize = true;
+			this.pinceilingtextures.Location = new System.Drawing.Point(9, 19);
+			this.pinceilingtextures.Name = "pinceilingtextures";
+			this.pinceilingtextures.Size = new System.Drawing.Size(114, 17);
+			this.pinceilingtextures.TabIndex = 40;
+			this.pinceilingtextures.Text = "Pin ceiling textures";
+			this.pinceilingtextures.UseVisualStyleBackColor = true;
+			this.pinceilingtextures.CheckedChanged += new System.EventHandler(this.pinceilingtextures_CheckedChanged);
+			// 
 			// absrot
 			// 
 			this.absrot.AllowDecimal = true;
+			this.absrot.AllowExpressions = false;
 			this.absrot.AllowNegative = true;
 			this.absrot.AllowRelative = true;
 			this.absrot.ButtonStep = 1;
@@ -578,135 +438,192 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.absrot.Size = new System.Drawing.Size(82, 24);
 			this.absrot.StepValues = null;
 			this.absrot.TabIndex = 0;
+			this.absrot.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.absrot.WhenButtonsClicked += new System.EventHandler(this.absrot_Validated);
 			this.absrot.WhenEnterPressed += new System.EventHandler(this.absrot_Validated);
 			this.absrot.Validated += new System.EventHandler(this.absrot_Validated);
-			this.absrot.WhenButtonsClicked += new System.EventHandler(this.absrot_Validated);
-			this.absrot.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
 			// 
-			// ceiltexgroup
-			// 
-			this.ceiltexgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
-			this.ceiltexgroup.Controls.Add(this.ceiltexoffset);
-			this.ceiltexgroup.Controls.Add(this.ceiltexrotation);
-			this.ceiltexgroup.Controls.Add(this.ceiltexscale);
-			this.ceiltexgroup.Location = new System.Drawing.Point(3, 401);
-			this.ceiltexgroup.Name = "ceiltexgroup";
-			this.ceiltexgroup.Size = new System.Drawing.Size(243, 58);
-			this.ceiltexgroup.TabIndex = 3;
-			this.ceiltexgroup.TabStop = false;
-			this.ceiltexgroup.Text = "  ";
-			// 
-			// ceiltexoffset
-			// 
-			this.ceiltexoffset.AutoSize = true;
-			this.ceiltexoffset.Location = new System.Drawing.Point(27, 28);
-			this.ceiltexoffset.Name = "ceiltexoffset";
-			this.ceiltexoffset.Size = new System.Drawing.Size(54, 17);
-			this.ceiltexoffset.TabIndex = 0;
-			this.ceiltexoffset.Text = "Offset";
-			this.ceiltexoffset.UseVisualStyleBackColor = true;
-			this.ceiltexoffset.CheckedChanged += new System.EventHandler(this.ceiltexoffset_CheckedChanged);
-			// 
-			// ceiltexrotation
-			// 
-			this.ceiltexrotation.AutoSize = true;
-			this.ceiltexrotation.Location = new System.Drawing.Point(89, 28);
-			this.ceiltexrotation.Name = "ceiltexrotation";
-			this.ceiltexrotation.Size = new System.Drawing.Size(66, 17);
-			this.ceiltexrotation.TabIndex = 1;
-			this.ceiltexrotation.Text = "Rotation";
-			this.ceiltexrotation.UseVisualStyleBackColor = true;
-			this.ceiltexrotation.CheckedChanged += new System.EventHandler(this.ceiltexrotation_CheckedChanged);
-			// 
-			// ceiltexscale
-			// 
-			this.ceiltexscale.AutoSize = true;
-			this.ceiltexscale.Location = new System.Drawing.Point(163, 28);
-			this.ceiltexscale.Name = "ceiltexscale";
-			this.ceiltexscale.Size = new System.Drawing.Size(53, 17);
-			this.ceiltexscale.TabIndex = 2;
-			this.ceiltexscale.Text = "Scale";
-			this.ceiltexscale.UseVisualStyleBackColor = true;
-			this.ceiltexscale.CheckedChanged += new System.EventHandler(this.ceiltexscale_CheckedChanged);
-			// 
-			// ceiltexall
-			// 
-			this.ceiltexall.AutoSize = true;
-			this.ceiltexall.Location = new System.Drawing.Point(14, 400);
-			this.ceiltexall.Name = "ceiltexall";
-			this.ceiltexall.Size = new System.Drawing.Size(154, 17);
-			this.ceiltexall.TabIndex = 0;
-			this.ceiltexall.Text = "Ceiling Textures Transform:";
-			this.ceiltexall.UseVisualStyleBackColor = true;
-			this.ceiltexall.CheckedChanged += new System.EventHandler(this.ceiltexall_CheckedChanged);
-			// 
-			// floortexrotation
-			// 
-			this.floortexrotation.AutoSize = true;
-			this.floortexrotation.Location = new System.Drawing.Point(89, 28);
-			this.floortexrotation.Name = "floortexrotation";
-			this.floortexrotation.Size = new System.Drawing.Size(66, 17);
-			this.floortexrotation.TabIndex = 1;
-			this.floortexrotation.Text = "Rotation";
-			this.floortexrotation.UseVisualStyleBackColor = true;
-			this.floortexrotation.CheckedChanged += new System.EventHandler(this.floortexrotation_CheckedChanged);
-			// 
-			// floortexoffset
-			// 
-			this.floortexoffset.AutoSize = true;
-			this.floortexoffset.Location = new System.Drawing.Point(27, 28);
-			this.floortexoffset.Name = "floortexoffset";
-			this.floortexoffset.Size = new System.Drawing.Size(54, 17);
-			this.floortexoffset.TabIndex = 0;
-			this.floortexoffset.Text = "Offset";
-			this.floortexoffset.UseVisualStyleBackColor = true;
-			this.floortexoffset.CheckedChanged += new System.EventHandler(this.floortexoffset_CheckedChanged);
-			// 
-			// floortexscale
-			// 
-			this.floortexscale.AutoSize = true;
-			this.floortexscale.Location = new System.Drawing.Point(163, 28);
-			this.floortexscale.Name = "floortexscale";
-			this.floortexscale.Size = new System.Drawing.Size(53, 17);
-			this.floortexscale.TabIndex = 2;
-			this.floortexscale.Text = "Scale";
-			this.floortexscale.UseVisualStyleBackColor = true;
-			this.floortexscale.CheckedChanged += new System.EventHandler(this.floortexscale_CheckedChanged);
-			// 
-			// floortexall
-			// 
-			this.floortexall.AutoSize = true;
-			this.floortexall.Location = new System.Drawing.Point(14, 464);
-			this.floortexall.Name = "floortexall";
-			this.floortexall.Size = new System.Drawing.Size(146, 17);
-			this.floortexall.TabIndex = 1;
-			this.floortexall.Text = "Floor Textures Transform:";
-			this.floortexall.UseVisualStyleBackColor = true;
-			this.floortexall.CheckedChanged += new System.EventHandler(this.floortexall_CheckedChanged);
-			// 
-			// floortexgroup
-			// 
-			this.floortexgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
-			this.floortexgroup.Controls.Add(this.floortexoffset);
-			this.floortexgroup.Controls.Add(this.floortexrotation);
-			this.floortexgroup.Controls.Add(this.floortexscale);
-			this.floortexgroup.Location = new System.Drawing.Point(3, 465);
-			this.floortexgroup.Name = "floortexgroup";
-			this.floortexgroup.Size = new System.Drawing.Size(243, 58);
-			this.floortexgroup.TabIndex = 38;
-			this.floortexgroup.TabStop = false;
-			this.floortexgroup.Text = "  ";
+			// relsizey
+			// 
+			this.relsizey.AllowDecimal = true;
+			this.relsizey.AllowExpressions = false;
+			this.relsizey.AllowNegative = true;
+			this.relsizey.AllowRelative = true;
+			this.relsizey.ButtonStep = 1;
+			this.relsizey.ButtonStepBig = 10F;
+			this.relsizey.ButtonStepFloat = 1F;
+			this.relsizey.ButtonStepSmall = 0.1F;
+			this.relsizey.ButtonStepsUseModifierKeys = false;
+			this.relsizey.ButtonStepsWrapAround = false;
+			this.relsizey.Location = new System.Drawing.Point(136, 83);
+			this.relsizey.Name = "relsizey";
+			this.relsizey.Size = new System.Drawing.Size(72, 24);
+			this.relsizey.StepValues = null;
+			this.relsizey.TabIndex = 5;
+			this.relsizey.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.relsizey.WhenButtonsClicked += new System.EventHandler(this.relsizey_Validated);
+			this.relsizey.WhenEnterPressed += new System.EventHandler(this.relsizey_Validated);
+			this.relsizey.Validated += new System.EventHandler(this.relsizey_Validated);
+			// 
+			// relsizex
+			// 
+			this.relsizex.AllowDecimal = true;
+			this.relsizex.AllowExpressions = false;
+			this.relsizex.AllowNegative = true;
+			this.relsizex.AllowRelative = true;
+			this.relsizex.ButtonStep = 1;
+			this.relsizex.ButtonStepBig = 10F;
+			this.relsizex.ButtonStepFloat = 1F;
+			this.relsizex.ButtonStepSmall = 0.1F;
+			this.relsizex.ButtonStepsUseModifierKeys = false;
+			this.relsizex.ButtonStepsWrapAround = false;
+			this.relsizex.Location = new System.Drawing.Point(58, 83);
+			this.relsizex.Name = "relsizex";
+			this.relsizex.Size = new System.Drawing.Size(72, 24);
+			this.relsizex.StepValues = null;
+			this.relsizex.TabIndex = 4;
+			this.relsizex.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.relsizex.WhenButtonsClicked += new System.EventHandler(this.relsizex_Validated);
+			this.relsizex.WhenEnterPressed += new System.EventHandler(this.relsizex_Validated);
+			this.relsizex.Validated += new System.EventHandler(this.relsizex_Validated);
+			// 
+			// abssizey
+			// 
+			this.abssizey.AllowDecimal = true;
+			this.abssizey.AllowExpressions = false;
+			this.abssizey.AllowNegative = true;
+			this.abssizey.AllowRelative = true;
+			this.abssizey.ButtonStep = 1;
+			this.abssizey.ButtonStepBig = 10F;
+			this.abssizey.ButtonStepFloat = 1F;
+			this.abssizey.ButtonStepSmall = 0.1F;
+			this.abssizey.ButtonStepsUseModifierKeys = false;
+			this.abssizey.ButtonStepsWrapAround = false;
+			this.abssizey.Location = new System.Drawing.Point(136, 53);
+			this.abssizey.Name = "abssizey";
+			this.abssizey.Size = new System.Drawing.Size(72, 24);
+			this.abssizey.StepValues = null;
+			this.abssizey.TabIndex = 3;
+			this.abssizey.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.abssizey.WhenButtonsClicked += new System.EventHandler(this.abssizey_Validated);
+			this.abssizey.WhenEnterPressed += new System.EventHandler(this.abssizey_Validated);
+			this.abssizey.Validated += new System.EventHandler(this.abssizey_Validated);
+			// 
+			// abssizex
+			// 
+			this.abssizex.AllowDecimal = true;
+			this.abssizex.AllowExpressions = false;
+			this.abssizex.AllowNegative = true;
+			this.abssizex.AllowRelative = true;
+			this.abssizex.ButtonStep = 1;
+			this.abssizex.ButtonStepBig = 10F;
+			this.abssizex.ButtonStepFloat = 1F;
+			this.abssizex.ButtonStepSmall = 0.1F;
+			this.abssizex.ButtonStepsUseModifierKeys = false;
+			this.abssizex.ButtonStepsWrapAround = false;
+			this.abssizex.Location = new System.Drawing.Point(58, 53);
+			this.abssizex.Name = "abssizex";
+			this.abssizex.Size = new System.Drawing.Size(72, 24);
+			this.abssizex.StepValues = null;
+			this.abssizex.TabIndex = 2;
+			this.abssizex.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.abssizex.WhenButtonsClicked += new System.EventHandler(this.abssizex_Validated);
+			this.abssizex.WhenEnterPressed += new System.EventHandler(this.abssizex_Validated);
+			this.abssizex.Validated += new System.EventHandler(this.abssizex_Validated);
+			// 
+			// relposy
+			// 
+			this.relposy.AllowDecimal = true;
+			this.relposy.AllowExpressions = false;
+			this.relposy.AllowNegative = true;
+			this.relposy.AllowRelative = true;
+			this.relposy.ButtonStep = 1;
+			this.relposy.ButtonStepBig = 10F;
+			this.relposy.ButtonStepFloat = 1F;
+			this.relposy.ButtonStepSmall = 0.1F;
+			this.relposy.ButtonStepsUseModifierKeys = false;
+			this.relposy.ButtonStepsWrapAround = false;
+			this.relposy.Location = new System.Drawing.Point(136, 83);
+			this.relposy.Name = "relposy";
+			this.relposy.Size = new System.Drawing.Size(72, 24);
+			this.relposy.StepValues = null;
+			this.relposy.TabIndex = 5;
+			this.relposy.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.relposy.WhenButtonsClicked += new System.EventHandler(this.relposy_Validated);
+			this.relposy.WhenEnterPressed += new System.EventHandler(this.relposy_Validated);
+			this.relposy.Validated += new System.EventHandler(this.relposy_Validated);
+			// 
+			// relposx
+			// 
+			this.relposx.AllowDecimal = true;
+			this.relposx.AllowExpressions = false;
+			this.relposx.AllowNegative = true;
+			this.relposx.AllowRelative = true;
+			this.relposx.ButtonStep = 1;
+			this.relposx.ButtonStepBig = 10F;
+			this.relposx.ButtonStepFloat = 1F;
+			this.relposx.ButtonStepSmall = 0.1F;
+			this.relposx.ButtonStepsUseModifierKeys = false;
+			this.relposx.ButtonStepsWrapAround = false;
+			this.relposx.Location = new System.Drawing.Point(58, 83);
+			this.relposx.Name = "relposx";
+			this.relposx.Size = new System.Drawing.Size(72, 24);
+			this.relposx.StepValues = null;
+			this.relposx.TabIndex = 4;
+			this.relposx.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.relposx.WhenButtonsClicked += new System.EventHandler(this.relposx_Validated);
+			this.relposx.WhenEnterPressed += new System.EventHandler(this.relposx_Validated);
+			this.relposx.Validated += new System.EventHandler(this.relposx_Validated);
+			// 
+			// absposy
+			// 
+			this.absposy.AllowDecimal = true;
+			this.absposy.AllowExpressions = false;
+			this.absposy.AllowNegative = true;
+			this.absposy.AllowRelative = true;
+			this.absposy.ButtonStep = 1;
+			this.absposy.ButtonStepBig = 10F;
+			this.absposy.ButtonStepFloat = 1F;
+			this.absposy.ButtonStepSmall = 0.1F;
+			this.absposy.ButtonStepsUseModifierKeys = false;
+			this.absposy.ButtonStepsWrapAround = false;
+			this.absposy.Location = new System.Drawing.Point(136, 53);
+			this.absposy.Name = "absposy";
+			this.absposy.Size = new System.Drawing.Size(72, 24);
+			this.absposy.StepValues = null;
+			this.absposy.TabIndex = 3;
+			this.absposy.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.absposy.WhenButtonsClicked += new System.EventHandler(this.absposy_Validated);
+			this.absposy.WhenEnterPressed += new System.EventHandler(this.absposy_Validated);
+			this.absposy.Validated += new System.EventHandler(this.absposy_Validated);
+			// 
+			// absposx
+			// 
+			this.absposx.AllowDecimal = true;
+			this.absposx.AllowExpressions = false;
+			this.absposx.AllowNegative = true;
+			this.absposx.AllowRelative = true;
+			this.absposx.ButtonStep = 1;
+			this.absposx.ButtonStepBig = 10F;
+			this.absposx.ButtonStepFloat = 1F;
+			this.absposx.ButtonStepSmall = 0.1F;
+			this.absposx.ButtonStepsUseModifierKeys = false;
+			this.absposx.ButtonStepsWrapAround = false;
+			this.absposx.Location = new System.Drawing.Point(58, 53);
+			this.absposx.Name = "absposx";
+			this.absposx.Size = new System.Drawing.Size(72, 24);
+			this.absposx.StepValues = null;
+			this.absposx.TabIndex = 2;
+			this.absposx.WhenTextChanged += new System.EventHandler(this.WhenTextChanged);
+			this.absposx.WhenButtonsClicked += new System.EventHandler(this.absposx_Validated);
+			this.absposx.WhenEnterPressed += new System.EventHandler(this.absposx_Validated);
+			this.absposx.Validated += new System.EventHandler(this.absposx_Validated);
 			// 
 			// EditSelectionPanel
 			// 
 			this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
 			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
-			this.Controls.Add(this.ceiltexall);
-			this.Controls.Add(this.floortexall);
-			this.Controls.Add(this.floortexgroup);
-			this.Controls.Add(this.ceiltexgroup);
+			this.Controls.Add(this.gbTextures);
 			this.Controls.Add(this.groupBox3);
 			this.Controls.Add(this.groupBox2);
 			this.Controls.Add(this.groupBox1);
@@ -718,12 +635,9 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.groupBox2.PerformLayout();
 			this.groupBox3.ResumeLayout(false);
 			this.groupBox3.PerformLayout();
-			this.ceiltexgroup.ResumeLayout(false);
-			this.ceiltexgroup.PerformLayout();
-			this.floortexgroup.ResumeLayout(false);
-			this.floortexgroup.PerformLayout();
+			this.gbTextures.ResumeLayout(false);
+			this.gbTextures.PerformLayout();
 			this.ResumeLayout(false);
-			this.PerformLayout();
 
 		}
 
@@ -762,19 +676,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		private System.Windows.Forms.Button orgposy;
 		private System.Windows.Forms.Button orgsizey;
 		private System.Windows.Forms.Button orgsizex;
-		private System.Windows.Forms.GroupBox ceiltexgroup;
-		private System.Windows.Forms.CheckBox ceiltexrotation;
-		private System.Windows.Forms.CheckBox floortexrotation;
-		private System.Windows.Forms.CheckBox ceiltexoffset;
-		private System.Windows.Forms.CheckBox floortexoffset;
-		private System.Windows.Forms.CheckBox ceiltexscale;
-		private System.Windows.Forms.CheckBox floortexscale;
-		private System.Windows.Forms.CheckBox ceiltexall;
-		private System.Windows.Forms.CheckBox floortexall;
-		private System.Windows.Forms.GroupBox floortexgroup;
 		private System.Windows.Forms.CheckBox preciseposition;
 		private System.Windows.Forms.ToolTip tooltip;
 		private System.Windows.Forms.ComboBox heightmode;
 		private System.Windows.Forms.Label label10;
+		private System.Windows.Forms.CheckBox pinfloortextures;
+		private System.Windows.Forms.GroupBox gbTextures;
+		private System.Windows.Forms.CheckBox pinceilingtextures;
 	}
 }
diff --git a/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.cs b/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.cs
index e1da91b726460b544773d40ccc2817d120b4236a..d26886a6a1e7cfa9fe81e15b1f3f455355f33470 100755
--- a/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.cs
+++ b/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.cs
@@ -125,24 +125,16 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			// Disable groups?
 			if(!enable)
 			{
-				ceiltexgroup.Enabled = false;
-				floortexgroup.Enabled = false;
-				ceiltexall.Enabled = false;
-				floortexall.Enabled = false;
+				pinfloortextures.Enabled = false;
+				pinceilingtextures.Enabled = false;
 				return;
 			}
 
 			// Update checkboxes
 			preventchanges = true;
 
-			floortexoffset.Checked = mode.TransformFloorOffsets;
-			ceiltexoffset.Checked = mode.TransformCeilingOffsets;
-			floortexrotation.Checked = mode.RotateFloorOffsets;
-			ceiltexrotation.Checked = mode.RotateCeilingOffsets;
-			floortexscale.Checked = mode.ScaleFloorOffsets;
-			ceiltexscale.Checked = mode.ScaleCeilingOffsets;
-			floortexall.Checked = (mode.TransformFloorOffsets && mode.RotateFloorOffsets && mode.ScaleFloorOffsets);
-			ceiltexall.Checked = (mode.TransformCeilingOffsets && mode.RotateCeilingOffsets && mode.ScaleCeilingOffsets);
+			pinfloortextures.Checked = mode.PinFloorTextures;
+			pinceilingtextures.Checked = mode.PinCeilingTextures;
 
 			preventchanges = false;
 		}
@@ -156,34 +148,6 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			preventchanges = false;
 		}
 
-		//mxd
-		private void UpdateAllFloorTransformsCheckbox()
-		{
-			preventchanges = true;
-
-			int i = 0;
-			if(floortexoffset.Checked) i++;
-			if(floortexrotation.Checked) i++;
-			if(floortexscale.Checked) i++;
-			floortexall.Checked = (i == 3);
-
-			preventchanges = false;
-		}
-
-		//mxd
-		private void UpdateAllCeilingTransformsCheckbox() 
-		{
-			preventchanges = true;
-
-			int i = 0;
-			if(ceiltexoffset.Checked) i++;
-			if(ceiltexrotation.Checked) i++;
-			if(ceiltexscale.Checked) i++;
-			ceiltexall.Checked = (i == 3);
-
-			preventchanges = false;
-		}
-		
 		#endregion
 		
 		#region ================== Events
@@ -288,108 +252,40 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		}
 
 		//mxd
-		private void floortexoffset_CheckedChanged(object sender, EventArgs e)
-		{
-			if(preventchanges) return;
-			mode.TransformFloorOffsets = floortexoffset.Checked;
-			UpdateAllFloorTransformsCheckbox();
-			General.Interface.FocusDisplay();
-		}
-
-		//mxd
-		private void ceiltexoffset_CheckedChanged(object sender, EventArgs e) 
-		{
-			if(preventchanges) return;
-			mode.TransformCeilingOffsets = ceiltexoffset.Checked;
-			UpdateAllCeilingTransformsCheckbox();
-			General.Interface.FocusDisplay();
-		}
-
-		//mxd
-		private void floortexrotation_CheckedChanged(object sender, EventArgs e) 
-		{
-			if(preventchanges) return;
-			mode.RotateFloorOffsets = floortexrotation.Checked;
-			UpdateAllFloorTransformsCheckbox();
-			General.Interface.FocusDisplay();
-		}
-
-		//mxd
-		private void ceiltexrotation_CheckedChanged(object sender, EventArgs e) 
+		private void preciseposition_CheckedChanged(object sender, EventArgs e) 
 		{
 			if(preventchanges) return;
-			mode.RotateCeilingOffsets = ceiltexrotation.Checked;
-			UpdateAllCeilingTransformsCheckbox();
+			mode.UsePrecisePosition = preciseposition.Checked;
 			General.Interface.FocusDisplay();
 		}
 
 		//mxd
-		private void floortexscale_CheckedChanged(object sender, EventArgs e)
+		private void heightmode_SelectedIndexChanged(object sender, EventArgs e)
 		{
-			if(preventchanges) return;
-			mode.ScaleFloorOffsets = floortexscale.Checked;
-			UpdateAllFloorTransformsCheckbox();
-			General.Interface.FocusDisplay();
+			if(preventchanges || heightmode.SelectedIndex == -1) return;
+			mode.SectorHeightAdjustMode = (EditSelectionMode.HeightAdjustMode)heightmode.SelectedIndex;
 		}
 
-		//mxd
-		private void ceiltexscale_CheckedChanged(object sender, EventArgs e)
-		{
-			if(preventchanges) return;
-			mode.ScaleCeilingOffsets = ceiltexscale.Checked;
-			UpdateAllCeilingTransformsCheckbox();
-			General.Interface.FocusDisplay();
-		}
+		#endregion
 
-		//mxd
-		private void floortexall_CheckedChanged(object sender, EventArgs e) 
+		private void cbPinFloorTextures_CheckedChanged(object sender, EventArgs e)
 		{
-			if(preventchanges) return;
+			if (preventchanges) return;
 			preventchanges = true;
 
-			floortexoffset.Checked = floortexall.Checked;
-			floortexrotation.Checked = floortexall.Checked;
-			floortexscale.Checked = floortexall.Checked;
-
-			mode.TransformFloorOffsets = floortexoffset.Checked;
-			mode.RotateFloorOffsets = floortexrotation.Checked;
-			mode.ScaleFloorOffsets = floortexscale.Checked;
+			mode.PinFloorTextures = pinfloortextures.Checked;
 
 			preventchanges = false;
 		}
 
-		//mxd
-		private void ceiltexall_CheckedChanged(object sender, EventArgs e) 
+		private void pinceilingtextures_CheckedChanged(object sender, EventArgs e)
 		{
-			if(preventchanges) return;
+			if (preventchanges) return;
 			preventchanges = true;
 
-			ceiltexoffset.Checked = ceiltexall.Checked;
-			ceiltexrotation.Checked = ceiltexall.Checked;
-			ceiltexscale.Checked = ceiltexall.Checked;
-
-			mode.TransformCeilingOffsets = ceiltexoffset.Checked;
-			mode.RotateCeilingOffsets = ceiltexrotation.Checked;
-			mode.ScaleCeilingOffsets = ceiltexscale.Checked;
+			mode.PinCeilingTextures = pinceilingtextures.Checked;
 
 			preventchanges = false;
 		}
-
-		//mxd
-		private void preciseposition_CheckedChanged(object sender, EventArgs e) 
-		{
-			if(preventchanges) return;
-			mode.UsePrecisePosition = preciseposition.Checked;
-			General.Interface.FocusDisplay();
-		}
-
-		//mxd
-		private void heightmode_SelectedIndexChanged(object sender, EventArgs e)
-		{
-			if(preventchanges || heightmode.SelectedIndex == -1) return;
-			mode.SectorHeightAdjustMode = (EditSelectionMode.HeightAdjustMode)heightmode.SelectedIndex;
-		}
-		
-		#endregion
 	}
 }
diff --git a/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.resx b/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.resx
index 74d268a3b9c0fb5fb5fd1de118fb85c132ac84fc..f27852897ef8a0ae7e8b41af26cfbdd51e4b64bc 100755
--- a/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.resx
+++ b/Source/Plugins/BuilderModes/Interface/EditSelectionPanel.resx
@@ -112,12 +112,12 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
-  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
   <data name="label10.ToolTip" xml:space="preserve">
@@ -130,7 +130,7 @@ by sectors with the same target height when the mode
 was enabled, and are surrounded by sectors with the 
 same target height when the mode is applied.</value>
   </data>
-  <metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Plugins/BuilderModes/Interface/MenusForm.Designer.cs b/Source/Plugins/BuilderModes/Interface/MenusForm.Designer.cs
index 9cd313bfee8cdc8b5d6356012dff36a429e68c60..f83343238f9749dc03beaf2fd42c0d2026f7af85 100755
--- a/Source/Plugins/BuilderModes/Interface/MenusForm.Designer.cs
+++ b/Source/Plugins/BuilderModes/Interface/MenusForm.Designer.cs
@@ -79,27 +79,27 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			this.selectSimilarVertsItem = new System.Windows.Forms.ToolStripMenuItem();
 			this.globalstrip = new System.Windows.Forms.ToolStrip();
 			this.manualstrip = new System.Windows.Forms.ToolStrip();
-			this.buttoncopyproperties = new System.Windows.Forms.ToolStripButton();
-			this.buttonpasteproperties = new System.Windows.Forms.ToolStripButton();
-			this.buttonpastepropertiesoptions = new System.Windows.Forms.ToolStripButton();
+			this.buttoncopyproperties = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonpasteproperties = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonpastepropertiesoptions = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.seperatorcopypaste = new System.Windows.Forms.ToolStripSeparator();
 			this.buttonselectionnumbers = new System.Windows.Forms.ToolStripButton();
 			this.buttonselectioneffects = new System.Windows.Forms.ToolStripButton();
 			this.separatorsectors1 = new System.Windows.Forms.ToolStripSeparator();
-			this.buttonMakeDoor = new System.Windows.Forms.ToolStripButton();
+			this.buttonMakeDoor = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.separatorsectors2 = new System.Windows.Forms.ToolStripSeparator();
-			this.buttonbrightnessgradient = new System.Windows.Forms.ToolStripButton();
-			this.buttonfloorgradient = new System.Windows.Forms.ToolStripButton();
-			this.buttonceilinggradient = new System.Windows.Forms.ToolStripButton();
-			this.buttonflipselectionh = new System.Windows.Forms.ToolStripButton();
-			this.buttonflipselectionv = new System.Windows.Forms.ToolStripButton();
-			this.buttoncurvelinedefs = new System.Windows.Forms.ToolStripButton();
+			this.buttonbrightnessgradient = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonfloorgradient = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonceilinggradient = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonflipselectionh = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonflipselectionv = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttoncurvelinedefs = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.gradientModeMenu = new System.Windows.Forms.ToolStripComboBox();
 			this.gradientInterpolationMenu = new System.Windows.Forms.ToolStripComboBox();
 			this.separatorsectors3 = new System.Windows.Forms.ToolStripSeparator();
 			this.buttonMarqueSelectTouching = new System.Windows.Forms.ToolStripButton();
-			this.syncthingteditbutton = new System.Windows.Forms.ToolStripButton();
-			this.buttonAlignThingsToWall = new System.Windows.Forms.ToolStripButton();
+			this.syncthingteditbutton = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
+			this.buttonAlignThingsToWall = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.buttonTextureOffsetLock = new System.Windows.Forms.ToolStripButton();
 			this.buttonTextureOffset3DFloorLock = new System.Windows.Forms.ToolStripButton();
 			this.buttonlightradii = new System.Windows.Forms.ToolStripButton();
@@ -993,25 +993,25 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		private System.Windows.Forms.ToolStripMenuItem splitlinedefsitem;
 		private System.Windows.Forms.ToolStrip globalstrip;
 		private System.Windows.Forms.ToolStrip manualstrip;
-		private System.Windows.Forms.ToolStripButton buttonbrightnessgradient;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonbrightnessgradient;
 		private System.Windows.Forms.ToolStripMenuItem selectsinglesideditem;
 		private System.Windows.Forms.ToolStripMenuItem selectdoublesideditem;
 		private System.Windows.Forms.ToolStripSeparator toolStripMenuItem4;
-		private System.Windows.Forms.ToolStripButton buttonflipselectionh;
-		private System.Windows.Forms.ToolStripButton buttonflipselectionv;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonflipselectionh;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonflipselectionv;
 		private System.Windows.Forms.ToolStripButton buttonselectionnumbers;
 		private System.Windows.Forms.ToolStripSeparator separatorsectors1;
-		private System.Windows.Forms.ToolStripButton buttonfloorgradient;
-		private System.Windows.Forms.ToolStripButton buttonceilinggradient;
-		private System.Windows.Forms.ToolStripButton buttoncurvelinedefs;
-		private System.Windows.Forms.ToolStripButton buttoncopyproperties;
-		private System.Windows.Forms.ToolStripButton buttonpasteproperties;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonfloorgradient;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonceilinggradient;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttoncurvelinedefs;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttoncopyproperties;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonpasteproperties;
 		private System.Windows.Forms.ToolStripSeparator seperatorcopypaste;
 		private System.Windows.Forms.ToolStripComboBox gradientModeMenu;
 		private System.Windows.Forms.ToolStripButton buttonMarqueSelectTouching;
 		private System.Windows.Forms.ToolStripMenuItem thingsmenu;
 		private System.Windows.Forms.ToolStripMenuItem alignToWallItem;
-		private System.Windows.Forms.ToolStripButton buttonAlignThingsToWall;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonAlignThingsToWall;
 		private System.Windows.Forms.ToolStripMenuItem pointAtCursorItem;
 		private System.Windows.Forms.ToolStripButton buttonTextureOffsetLock;
 		private System.Windows.Forms.ToolStripMenuItem selectInSectorsItem;
@@ -1030,13 +1030,13 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		private System.Windows.Forms.ToolStripMenuItem syncthingeditlinedefsitem;
 		private System.Windows.Forms.ToolStripMenuItem syncthingeditsectorsitem;
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator3;
-		private System.Windows.Forms.ToolStripButton buttonMakeDoor;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonMakeDoor;
 		private System.Windows.Forms.ToolStripMenuItem makedooritem;
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator4;
 		private System.Windows.Forms.MenuStrip fileMenuStrip;
 		private System.Windows.Forms.ToolStripMenuItem exportStripMenuItem;
 		private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem5;
-		private System.Windows.Forms.ToolStripButton buttonpastepropertiesoptions;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton buttonpastepropertiesoptions;
 		private System.Windows.Forms.ToolStripMenuItem filterSelectionItem;
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;
 		private System.Windows.Forms.ToolStripMenuItem selectSimilarLinesItem;
@@ -1047,7 +1047,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		private System.Windows.Forms.ToolStripMenuItem selectSimilarVertsItem;
 		private System.Windows.Forms.ToolStripMenuItem flipsectorlinedefsitem;
 		private System.Windows.Forms.ToolStripSeparator toolStripSeparator8;
-		private System.Windows.Forms.ToolStripButton syncthingteditbutton;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton syncthingteditbutton;
 		private System.Windows.Forms.ToolStripComboBox gradientInterpolationMenu;
 		private System.Windows.Forms.ToolStripSeparator separatorsectors2;
 		private System.Windows.Forms.ToolStripSeparator separatorsectors3;
diff --git a/Source/Plugins/BuilderModes/Interface/MenusForm.cs b/Source/Plugins/BuilderModes/Interface/MenusForm.cs
index dc5407f186f0cf704c14ae5fe3a97513dd2618da..c286fc364dac3f03916746c96986c75e638e5bd8 100755
--- a/Source/Plugins/BuilderModes/Interface/MenusForm.cs
+++ b/Source/Plugins/BuilderModes/Interface/MenusForm.cs
@@ -18,6 +18,7 @@
 
 using System;
 using System.Windows.Forms;
+using CodeImp.DoomBuilder.Controls;
 using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Windows;
 
@@ -70,26 +71,26 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public ToolStripSeparator SeparatorSectors1 { get { return separatorsectors1; } }
 		public ToolStripSeparator SeparatorSectors2 { get { return separatorsectors2; } } //mxd
 		public ToolStripSeparator SeparatorSectors3 { get { return separatorsectors3; } } //mxd
-		public ToolStripButton MakeGradientBrightness { get { return buttonbrightnessgradient; } }
-		public ToolStripButton MakeGradientFloors { get { return buttonfloorgradient; } }
-		public ToolStripButton MakeGradientCeilings { get { return buttonceilinggradient; } }
-		public ToolStripButton FlipSelectionV { get { return buttonflipselectionv; } }
-		public ToolStripButton FlipSelectionH { get { return buttonflipselectionh; } }
-		public ToolStripButton CurveLinedefs { get { return buttoncurvelinedefs; } }
-		public ToolStripButton CopyProperties { get { return buttoncopyproperties; } }
-		public ToolStripButton PasteProperties { get { return buttonpasteproperties; } }
-		public ToolStripButton PastePropertiesOptions { get { return buttonpastepropertiesoptions; } } //mxd
+		public ToolStripActionButton MakeGradientBrightness { get { return buttonbrightnessgradient; } }
+		public ToolStripActionButton MakeGradientFloors { get { return buttonfloorgradient; } }
+		public ToolStripActionButton MakeGradientCeilings { get { return buttonceilinggradient; } }
+		public ToolStripActionButton FlipSelectionV { get { return buttonflipselectionv; } }
+		public ToolStripActionButton FlipSelectionH { get { return buttonflipselectionh; } }
+		public ToolStripActionButton CurveLinedefs { get { return buttoncurvelinedefs; } }
+		public ToolStripActionButton CopyProperties { get { return buttoncopyproperties; } }
+		public ToolStripActionButton PasteProperties { get { return buttonpasteproperties; } }
+		public ToolStripActionButton PastePropertiesOptions { get { return buttonpastepropertiesoptions; } } //mxd
 		public ToolStripSeparator SeparatorCopyPaste { get { return seperatorcopypaste; } }
 		public ToolStripComboBox GradientModeMenu { get { return gradientModeMenu; } } //mxd
 		public ToolStripComboBox GradientInterpolationMenu { get { return gradientInterpolationMenu; } } //mxd
 		public ToolStripButton MarqueSelectTouching { get { return buttonMarqueSelectTouching; } } //mxd
-		public ToolStripButton AlignThingsToWall { get { return buttonAlignThingsToWall; } } //mxd
+		public ToolStripActionButton AlignThingsToWall { get { return buttonAlignThingsToWall; } } //mxd
 		public ToolStripButton TextureOffsetLock { get { return buttonTextureOffsetLock; } } //mxd
 		public ToolStripButton TextureOffset3DFloorLock { get { return buttonTextureOffset3DFloorLock; } } 
-		public ToolStripButton SyncronizeThingEditButton { get { return syncthingteditbutton; } } //mxd
+		public ToolStripActionButton SyncronizeThingEditButton { get { return syncthingteditbutton; } } //mxd
 		public ToolStripMenuItem SyncronizeThingEditSectorsItem { get { return syncthingeditsectorsitem; } } //mxd
 		public ToolStripMenuItem SyncronizeThingEditLinedefsItem { get { return syncthingeditlinedefsitem; } } //mxd
-		public ToolStripButton MakeDoor { get { return buttonMakeDoor; } } //mxd
+		public ToolStripActionButton MakeDoor { get { return buttonMakeDoor; } } //mxd
 
 		//mxd. Thing mode radii buttons
 		public ToolStripMenuItem ItemLightRadii { get { return itemlightradii; } }
diff --git a/Source/Plugins/BuilderModes/Interface/SectorDrawingOptionsPanel.cs b/Source/Plugins/BuilderModes/Interface/SectorDrawingOptionsPanel.cs
index 3988066c2af29e1a13940d31355a1bf2912c035a..88b927e91cf232796f66bd36465c95bf45892ebf 100755
--- a/Source/Plugins/BuilderModes/Interface/SectorDrawingOptionsPanel.cs
+++ b/Source/Plugins/BuilderModes/Interface/SectorDrawingOptionsPanel.cs
@@ -72,6 +72,10 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 			ceiling.Enabled = cbOverrideCeilingTexture.Checked;
 			General.Map.Options.OverrideCeilingTexture = cbOverrideCeilingTexture.Checked;
 
+			// If we don't set the default texture here it'll not be set until a different texture is selected
+			if (ceiling.Enabled)
+				General.Map.Options.DefaultCeilingTexture = ceiling.TextureName;
+
 			getsectortexturesfromselection.Enabled = (cbOverrideCeilingTexture.Checked || cbOverrideFloorTexture.Checked);
 		}
 
@@ -79,6 +83,12 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 		{
 			floor.Enabled = cbOverrideFloorTexture.Checked;
 			General.Map.Options.OverrideFloorTexture = cbOverrideFloorTexture.Checked;
+
+			// If we don't set the default texture here it'll not be set until a different texture is selected
+			if (floor.Enabled)
+				General.Map.Options.DefaultFloorTexture = floor.TextureName;
+
+			getsectortexturesfromselection.Enabled = (cbOverrideCeilingTexture.Checked || cbOverrideFloorTexture.Checked);
 		}
 
 		private void cbOverrideTopTexture_CheckedChanged(object sender, EventArgs e) 
@@ -86,6 +96,10 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 			top.Enabled = cbOverrideTopTexture.Checked;
 			General.Map.Options.OverrideTopTexture = cbOverrideTopTexture.Checked;
 
+			// If we don't set the default texture here it'll not be set until a different texture is selected
+			if (top.Enabled)
+				General.Map.Options.DefaultTopTexture = top.TextureName;
+
 			getsidetexturesfromselection.Enabled = (cbOverrideTopTexture.Checked || cbOverrideMiddleTexture.Checked || cbOverrideBottomTexture.Checked);
 		}
 
@@ -94,6 +108,10 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 			middle.Enabled = cbOverrideMiddleTexture.Checked;
 			General.Map.Options.OverrideMiddleTexture = cbOverrideMiddleTexture.Checked;
 
+			// If we don't set the default texture here it'll not be set until a different texture is selected
+			if (middle.Enabled)
+				General.Map.Options.DefaultWallTexture = middle.TextureName;
+
 			getsidetexturesfromselection.Enabled = (cbOverrideTopTexture.Checked || cbOverrideMiddleTexture.Checked || cbOverrideBottomTexture.Checked);
 		}
 
@@ -102,6 +120,10 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 			bottom.Enabled = cbOverrideBottomTexture.Checked;
 			General.Map.Options.OverrideBottomTexture = cbOverrideBottomTexture.Checked;
 
+			// If we don't set the default texture here it'll not be set until a different texture is selected
+			if (bottom.Enabled)
+				General.Map.Options.DefaultBottomTexture = bottom.TextureName;
+
 			getsidetexturesfromselection.Enabled = (cbOverrideTopTexture.Checked || cbOverrideMiddleTexture.Checked || cbOverrideBottomTexture.Checked);
 		}
 
diff --git a/Source/Plugins/BuilderModes/Interface/WavefrontSettingsForm.cs b/Source/Plugins/BuilderModes/Interface/WavefrontSettingsForm.cs
index 53e208edbcffe5248cbdc486ac9da728f78d23be..0b6335aed86be87cdc204374e89a01659290631b 100755
--- a/Source/Plugins/BuilderModes/Interface/WavefrontSettingsForm.cs
+++ b/Source/Plugins/BuilderModes/Interface/WavefrontSettingsForm.cs
@@ -104,7 +104,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 			{
 				// gbActorFormat.Enabled = gbActorSettings.Enabled = cbGenerateCode.Checked;
 				gbActorFormat.Enabled = gbActorSettings.Enabled = tbActorPath.Enabled = bBrowseActorPath.Enabled = cbGenerateCode.Checked;
-				tbModelPath.Enabled = bBrowseModelPath.Enabled = cbGenerateModeldef.Checked && cbExportForGZDoom.Checked;
+				tbModelPath.Enabled = bBrowseModelPath.Enabled = cbExportForGZDoom.Checked;
 			}
 
 
@@ -235,7 +235,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 			}
 
 			gbActorSettings.Enabled = gbActorFormat.Enabled =  tbActorPath.Enabled = bBrowseActorPath.Enabled = cbGenerateCode.Checked && cbExportForGZDoom.Checked;
-			tbModelPath.Enabled = bBrowseModelPath.Enabled = cbGenerateModeldef.Checked && cbExportForGZDoom.Checked;
+			tbModelPath.Enabled = bBrowseModelPath.Enabled = cbExportForGZDoom.Checked;
 
 			tbExportPath.Enabled = browse.Enabled = cbExportTextures.Enabled = nudScale.Enabled = !cbExportForGZDoom.Checked;
 		}
@@ -378,7 +378,7 @@ namespace CodeImp.DoomBuilder.BuilderModes.Interface
 
 		private void cbGenerateModeldef_CheckedChanged(object sender, EventArgs e)
 		{
-			tbModelPath.Enabled = bBrowseModelPath.Enabled = cbGenerateModeldef.Checked && cbExportForGZDoom.Checked;
+			tbModelPath.Enabled = bBrowseModelPath.Enabled = cbExportForGZDoom.Checked;
 		}
 	}
 }
diff --git a/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs b/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs
index b600770f957b0b5281d1849d305350830a6a0e40..9505d1834204539ecfd5549b46d099604d630b48 100755
--- a/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs
+++ b/Source/Plugins/BuilderModes/Properties/Resources.Designer.cs
@@ -450,6 +450,16 @@ namespace CodeImp.DoomBuilder.BuilderModes.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized resource of type System.Drawing.Bitmap.
+        /// </summary>
+        internal static System.Drawing.Bitmap Radial {
+            get {
+                object obj = ResourceManager.GetObject("Radial", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized resource of type System.Drawing.Bitmap.
         /// </summary>
diff --git a/Source/Plugins/BuilderModes/Properties/Resources.resx b/Source/Plugins/BuilderModes/Properties/Resources.resx
index 6697ef8e4cbd9a682995a3c130b49c6db7cc9ec0..97dbda6356e7963585a7b61c37d5d6ce11ccefe4 100755
--- a/Source/Plugins/BuilderModes/Properties/Resources.resx
+++ b/Source/Plugins/BuilderModes/Properties/Resources.resx
@@ -199,6 +199,9 @@
   <data name="Guidelines" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\Guidelines.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
+  <data name="Radial" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Radial.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
   <data name="DrawGridMode" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\Resources\DrawGridMode.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
   </data>
diff --git a/Source/Plugins/BuilderModes/Resources/Actions.cfg b/Source/Plugins/BuilderModes/Resources/Actions.cfg
index 07e8eab712670a22e8be5afddfff1e975b22e389..050a66ea02c8f7f4aa37e21c431fa8da62f90562 100755
--- a/Source/Plugins/BuilderModes/Resources/Actions.cfg
+++ b/Source/Plugins/BuilderModes/Resources/Actions.cfg
@@ -528,6 +528,7 @@ makedoor
 	allowkeys = true;
 	allowmouse = true;
 	allowscroll = true;
+	registertoast = true;
 }
 
 lowerfloor8
@@ -654,6 +655,27 @@ raisesector128
 	repeat = true;
 }
 
+lowermapelementbygridsize
+{
+	title = "Lower Floor/Ceiling/Thing by grid size";
+	category = "visual";
+	description = "Lowers the targeted or selected floors/ceilings by the current grid size. This also lowers selected or targeted things.";
+	allowkeys = true;
+	allowmouse = true;
+	allowscroll = true;
+	repeat = true;
+}
+
+raisemapelementbygridsize
+{
+	title = "Raise Floor/Ceiling/Thing by grid size";
+	category = "visual";
+	description = "Raises the targeted or selected floors/ceilings by the current grid size. This also raises selected or targeted things.";
+	allowkeys = true;
+	allowmouse = true;
+	allowscroll = true;
+	repeat = true;
+}
 
 //mxd
 lowersectortonearest
@@ -1070,6 +1092,7 @@ togglegravity
 	allowkeys = true;
 	allowmouse = true;
 	allowscroll = true;
+	registertoast = true;
 }
 
 resettexture
diff --git a/Source/Plugins/BuilderModes/Resources/Radial.png b/Source/Plugins/BuilderModes/Resources/Radial.png
new file mode 100644
index 0000000000000000000000000000000000000000..0702a267a424909a635b95d0a79db63b72d97209
Binary files /dev/null and b/Source/Plugins/BuilderModes/Resources/Radial.png differ
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
index 1d8b61cee51fcafcd3188d68061f1be260aeee8c..988d4af5f1da8b4d8cbfffc48b35bbd112318b27 100755
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualGeometrySidedef.cs
@@ -522,27 +522,65 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		//mxd
 		protected void GetLightValue(out int lightvalue, out bool lightabsolute)
 		{
-			lightabsolute = Sidedef.Fields.GetValue("lightabsolute", false);
+			string partstr = string.Empty, partstrabs = string.Empty;
+
+			if (General.Map.Config.DistinctSidedefPartBrightness)
+			{
+				switch (geometrytype)
+				{
+					case VisualGeometryType.WALL_UPPER:
+						partstr = "light_top";
+						partstrabs = "lightabsolute_top";
+						break;
+					case VisualGeometryType.WALL_LOWER:
+						partstr = "light_bottom";
+						partstrabs = "lightabsolute_bottom";
+						break;
+					case VisualGeometryType.WALL_MIDDLE:
+					case VisualGeometryType.WALL_MIDDLE_3D:
+						partstr = "light_mid";
+						partstrabs = "lightabsolute_mid";
+						break;
+				}
+			}
+
+			bool lightglobalabsolute = Sidedef.Fields.GetValue("lightabsolute", false);
+			bool lightpartabsolute = General.Map.Config.DistinctSidedefPartBrightness ? Sidedef.Fields.GetValue(partstrabs, false) : false;
+			lightabsolute = lightglobalabsolute || lightpartabsolute;
 			bool affectedbyfog = General.Map.Data.MapInfo.HasFadeColor || (Sector.Sector.HasSkyCeiling && General.Map.Data.MapInfo.HasOutsideFogColor) || Sector.Sector.Fields.ContainsKey("fadecolor");
 			bool ignorelight = affectedbyfog && !Sidedef.IsFlagSet("lightfog") && !lightabsolute;
 			lightvalue = ignorelight ? 0 : Sidedef.Fields.GetValue("light", 0); //mxd
+
+			if(ignorelight)
+			{
+				lightvalue = 0;
+			}
+			else
+			{
+				// Absolute value of upper/middle/lower always has precedence
+				if(lightpartabsolute)
+					lightvalue = Sidedef.Fields.GetValue(partstr, 0);
+				else
+					lightvalue = Sidedef.Fields.GetValue("light", 0) + (General.Map.Config.DistinctSidedefPartBrightness ? Sidedef.Fields.GetValue(partstr, 0) : 0);
+			}
+
 			if(ignorelight) lightabsolute = false;
 		}
 
 		// biwa
 		protected static double GetNewTexutreOffset(double oldValue, double offset, double textureSize)
 		{
-			return GetRoundedTextureOffset(oldValue, offset, 1.0f, textureSize);
+			return GetRoundedTextureOffset(oldValue, offset, 1.0, textureSize);
 		}
 
 		//mxd
 		protected static double GetRoundedTextureOffset(double oldValue, double offset, double scale, double textureSize) 
 		{
-			if(offset == 0f) return oldValue;
+			if(offset == 0 || offset % textureSize == 0) return oldValue;
 			double scaledOffset = offset * Math.Abs(scale);
 			double result = Math.Round(oldValue + scaledOffset);
 			if(textureSize > 0) result %= textureSize;
-			if(result == oldValue) result += (scaledOffset < 0 ? -1 : 1);
+			if(result == oldValue) result += (scaledOffset < 0 ? -1 : 1); // biwa. Why?
 			return result;
 		}
 
@@ -1550,10 +1588,19 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public virtual void OnChangeTargetBrightness(bool up)
 		{
 			//mxd. Change UDMF wall light?
-			if(General.Map.UDMF && General.Map.Config.DistinctWallBrightness)
+			if(General.Map.UDMF && (General.Map.Config.DistinctWallBrightness || General.Map.Config.DistinctSidedefPartBrightness))
 			{
-				int light = Sidedef.Fields.GetValue("light", 0);
-				bool absolute = Sidedef.Fields.GetValue("lightabsolute", false);
+				string fieldname = "light";
+				string fieldabsolutename = "lightabsolute";
+
+				if(General.Map.Config.DistinctSidedefPartBrightness)
+				{
+					fieldname += "_" + partname;
+					fieldabsolutename += "_" + partname;
+				}
+
+				int light = Sidedef.Fields.GetValue(fieldname, 0);
+				bool absolute = Sidedef.Fields.GetValue(fieldabsolutename, false);
 				int newlight;
 
 				if(up)
@@ -1568,7 +1615,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				Sidedef.Fields.BeforeFieldsChange();
 
 				// Apply changes
-				UniFields.SetInteger(Sidedef.Fields, "light", newlight, (absolute ? int.MinValue : 0));
+				UniFields.SetInteger(Sidedef.Fields, fieldname, newlight, (absolute ? int.MinValue : 0));
 				Tools.UpdateLightFogFlag(Sidedef);
 				mode.SetActionResult("Changed wall brightness to " + newlight + ".");
 
diff --git a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
index 04b462b5307182fe56e0a3edf2ff0bf20e583d48..743f3846564d62b05b6da8d67370a0f3875ca69b 100755
--- a/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/BaseVisualMode.cs
@@ -2770,9 +2770,33 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			PostAction();
 	    }
 
+		[BeginAction("raisemapelementbygridsize")]
+		public void RaiseMapElementByGridSize()
+		{
+			PreAction(UndoGroup.SectorHeightChange);
+			List<IVisualEventReceiver> objs = GetSelectedObjects(true, true, true, true, true);
+			bool hasvisualslopehandles = objs.Any(o => o is VisualSlope);
+			foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them
+				if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope))
+					i.OnChangeTargetHeight(General.Map.Grid.GridSize);
+			PostAction();
+		}
+
+		[BeginAction("lowermapelementbygridsize")]
+		public void LowerMapElementByGridSize()
+		{
+			PreAction(UndoGroup.SectorHeightChange);
+			List<IVisualEventReceiver> objs = GetSelectedObjects(true, true, true, true, true);
+			bool hasvisualslopehandles = objs.Any(o => o is VisualSlope);
+			foreach (IVisualEventReceiver i in objs) // If slope handles are selected only apply the action to them
+				if (!hasvisualslopehandles || (hasvisualslopehandles && i is VisualSlope))
+					i.OnChangeTargetHeight(-General.Map.Grid.GridSize);
+			PostAction();
+		}
 
-        //mxd
-        [BeginAction("raisesectortonearest")]
+
+		//mxd
+		[BeginAction("raisesectortonearest")]
 		public void RaiseSectorToNearest() 
 		{
 			List<VisualSidedefSlope> selectedhandles = GetSelectedSlopeHandles();
@@ -3666,8 +3690,15 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		public void ToggleGravity()
 		{
 			BuilderPlug.Me.UseGravity = !BuilderPlug.Me.UseGravity;
-			string onoff = BuilderPlug.Me.UseGravity ? "ON" : "OFF";
-			General.Interface.DisplayStatus(StatusType.Action, "Gravity is now " + onoff + ".");
+
+			string shortmessage = "Gravity is now " + (BuilderPlug.Me.UseGravity ? "ON" : "OFF") + ".";
+			string message = shortmessage;
+			string key = Actions.Action.GetShortcutKeyDesc(General.Actions.Current.ShortcutKey);
+
+			if (!string.IsNullOrEmpty(key))
+				message += $" Press '{key}' to toggle.";
+
+			General.ToastManager.ShowToast("togglegravity", ToastType.INFO, "Changed gravity", message, new StatusInfo(StatusType.Action, shortmessage));
 		}
 
 		[BeginAction("resettexture")]
@@ -3876,8 +3907,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			List<IVisualEventReceiver> objs = GetSelectedObjects(true, true, true, true, false);
             foreach (IVisualEventReceiver i in objs)
             {
-                if (i is BaseVisualThing)
-                    visiblethings.Remove((BaseVisualThing)i); // [ZZ] if any
+				if (i is BaseVisualThing)
+				{
+					visiblethings.Remove((BaseVisualThing)i); // [ZZ] if any
+					allthings.Remove(((BaseVisualThing)i).Thing);
+				}
                 i.OnDelete();
             }
             PostAction();
@@ -4473,7 +4507,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			if (!General.Map.UDMF)
 			{
-				General.Interface.DisplayStatus(StatusType.Warning, "Visual sloping is supported in UDMF only!");
+				General.ToastManager.ShowToast(ToastMessages.VISUALSLOPING, ToastType.WARNING, "Visual sloping", "Visual sloping is supported in UDMF only.");
+				return;
+			}
+			else if(!General.Map.Config.PlaneEquationSupport)
+			{
+				General.ToastManager.ShowToast(ToastMessages.VISUALSLOPING, ToastType.WARNING, "Visual sloping", "Visual sloping is not supported in this game configuration.");
 				return;
 			}
 
@@ -4499,7 +4538,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			if (!General.Map.UDMF)
 			{
-				General.Interface.DisplayStatus(StatusType.Warning, "Visual sloping is supported in UDMF only!");
+				General.ToastManager.ShowToast(ToastMessages.VISUALSLOPING, ToastType.WARNING, "Visual sloping", "Visual sloping is supported in UDMF only.");
+				return;
+			}
+			else if (!General.Map.Config.PlaneEquationSupport)
+			{
+				General.ToastManager.ShowToast(ToastMessages.VISUALSLOPING, ToastType.WARNING, "Visual sloping", "Visual sloping is not supported in this game configuration.");
 				return;
 			}
 
@@ -4525,7 +4569,12 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		{
 			if (!General.Map.UDMF)
 			{
-				General.Interface.DisplayStatus(StatusType.Warning, "Visual sloping is supported in UDMF only!");
+				General.ToastManager.ShowToast(ToastMessages.VISUALSLOPING, ToastType.WARNING, "Visual sloping", "Visual sloping is not supported in this game configuration.");
+				return;
+			}
+			else if (!General.Map.Config.PlaneEquationSupport)
+			{
+				General.ToastManager.ShowToast(ToastMessages.VISUALSLOPING, ToastType.WARNING, "Visual sloping", "Visual sloping is not supported in this game configuration.");
 				return;
 			}
 
@@ -4735,6 +4784,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			{
 				t.Rotate(General.Map.VisualCamera.AngleXY - Angle2D.PI);
 				t.SetPitch((int)Angle2D.RadToDeg(General.Map.VisualCamera.AngleZ - Angle2D.PI));
+				((BaseVisualThing)allthings[t]).Rebuild();
 			}
 		}
 
diff --git a/Source/Plugins/BuilderModes/VisualModes/SectorData.cs b/Source/Plugins/BuilderModes/VisualModes/SectorData.cs
index a19f032092513d633df1707868b2eac249e19399..288809ea26c1d5c7d122fbcc2713b69f8135846d 100755
--- a/Source/Plugins/BuilderModes/VisualModes/SectorData.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/SectorData.cs
@@ -333,9 +333,11 @@ namespace CodeImp.DoomBuilder.BuilderModes
             floor.color = floorcolor.WithAlpha(255).ToInt();
             floor.brightnessbelow = sector.Brightness;
             floor.colorbelow = lightcolor.WithAlpha(255);
+			floor.d64color = ColorFloor;
             ceiling.color = ceilingcolor.WithAlpha(255).ToInt();
             ceiling.brightnessbelow = sector.Brightness;
             ceiling.colorbelow = lightcolor.WithAlpha(255);
+			ceiling.d64color = ColorCeiling;
 
             //mxd. Store a copy of initial settings
             floor.CopyProperties(floorbase);
@@ -630,8 +632,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
 				brightness = PixelColor.FromInt(mode.CalculateBrightness(lightceiling));
 			else
 				brightness = PixelColor.FromInt(mode.CalculateBrightness(src.brightnessbelow));
-			
-			PixelColor color = PixelColor.Modulate(src.colorbelow, brightness);
+
+			PixelColor color = PixelColor.Modulate(target.d64color, PixelColor.Modulate(src.colorbelow, brightness));
 			return color.WithAlpha(255).ToInt();
 		}
 		
diff --git a/Source/Plugins/BuilderModes/VisualModes/SectorLevel.cs b/Source/Plugins/BuilderModes/VisualModes/SectorLevel.cs
index b282116c09de912a14c33e7ca5b70491882d0b7f..ac32abacc0c2bceb085496da3c6f481a97eb5447 100755
--- a/Source/Plugins/BuilderModes/VisualModes/SectorLevel.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/SectorLevel.cs
@@ -33,6 +33,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 		// When this is 0, it takes the color from the sector above
 		public int brightnessbelow;
 		public PixelColor colorbelow;
+		public PixelColor d64color; // own color of the plane
 		public bool disablelighting; //mxd
 		public bool restrictlighting; //mxd
 		public bool resetlighting; //mxd
@@ -66,6 +67,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 			target.color = this.color;
 			target.brightnessbelow = this.brightnessbelow;
 			target.colorbelow = this.colorbelow;
+			target.d64color = this.d64color;
 			target.affectedbyglow = this.affectedbyglow; //mxd
 			target.disablelighting = this.disablelighting; //mxd
 			target.restrictlighting = this.restrictlighting; //mxd
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
index e958a2d8c9d6e45d7b4011b7857ea4eb6d1f0c60..ad6dd48aaff1ac7942e7669626549af687c31e39 100755
--- a/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualMiddleDouble.cs
@@ -381,7 +381,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
 
 			//mxd. Don't clamp offsetY of clipped mid textures
 			bool dontClamp = (!textureloaded || (!Sidedef.IsFlagSet("wrapmidtex") && !Sidedef.Line.IsFlagSet("wrapmidtex")));
-			Sidedef.Fields["offsety_mid"] = new UniValue(UniversalType.Float, GetNewTexutreOffset(oldy, offsety, dontClamp ? -1 : Texture.Height)); // biwa
+			Sidedef.Fields["offsety_mid"] = new UniValue(UniversalType.Float, GetNewTexutreOffset(oldy, offsety, dontClamp ? double.MaxValue : Texture.Height)); // biwa
 		}
 
 		protected override Point GetTextureOffset()
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualSidedefSlope.cs b/Source/Plugins/BuilderModes/VisualModes/VisualSidedefSlope.cs
index 01557d492a61c2871a24d023086821229411a9cf..86a9c68c50b381bded4a1ca005f72117ffe88563 100644
--- a/Source/Plugins/BuilderModes/VisualModes/VisualSidedefSlope.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualSidedefSlope.cs
@@ -348,6 +348,13 @@ namespace CodeImp.DoomBuilder.VisualModes
 				}
 			}
 
+			// Can't pivot around itself
+			if(pivothandle == this)
+			{
+				General.Interface.DisplayStatus(Windows.StatusType.Warning, "Slope handle to modify can't be the same as the pivot handle");
+				return;
+			}
+
 			// User didn't set a pivot handle, try to find the smart pivot handle
 			if(pivothandle == null)
 				pivothandle = GetSmartPivotHandle();
@@ -356,18 +363,22 @@ namespace CodeImp.DoomBuilder.VisualModes
 			if (pivothandle == null)
 				return;
 
-			mode.CreateUndo("Change slope");
-
-			Plane originalplane = level.plane;
-			Plane pivotplane = ((BaseVisualSlope)pivothandle).Level.plane;
-
 			// Build a new plane. p1 and p2 are the points of the slope handle that is modified, with the changed amound added; p3 is on the line of the pivot handle
-			Vector3D p1 = new Vector3D(sidedef.Line.Start.Position, originalplane.GetZ(sidedef.Line.Start.Position) + amount);
-			Vector3D p2 = new Vector3D(sidedef.Line.End.Position, originalplane.GetZ(sidedef.Line.End.Position) + amount);
+			Vector3D p1 = new Vector3D(sidedef.Line.Start.Position, level.plane.GetZ(sidedef.Line.Start.Position) + amount);
+			Vector3D p2 = new Vector3D(sidedef.Line.End.Position, level.plane.GetZ(sidedef.Line.End.Position) + amount);
 			Vector3D p3 = pivothandle.GetPivotPoint();
 
 			Plane plane = new Plane(p1, p2, p3, true);
 
+			// Completely vertical planes are not possible. This can for example happen when trying to pivot around the slope handle on the opposite side of a line
+			if (Math.Abs(plane.a) == 1.0 || Math.Abs(plane.b) == 1.0)
+			{
+				General.Interface.DisplayStatus(Windows.StatusType.Warning, "Resulting plane is completely vertical, which is impossible. Aborting");
+				return;
+			}
+
+			mode.CreateUndo("Change slope");
+
 			// Apply slope to surfaces
 			foreach (SectorLevel l in levels)
 				ApplySlope(l, plane, mode);
diff --git a/Source/Plugins/BuilderModes/VisualModes/VisualVertexSlope.cs b/Source/Plugins/BuilderModes/VisualModes/VisualVertexSlope.cs
index b8afef37ce4360d102409c51d2ec3c89ff7bb455..ae20f593b52fc83d837de00f8153752290257ea7 100644
--- a/Source/Plugins/BuilderModes/VisualModes/VisualVertexSlope.cs
+++ b/Source/Plugins/BuilderModes/VisualModes/VisualVertexSlope.cs
@@ -314,6 +314,13 @@ namespace CodeImp.DoomBuilder.VisualModes
 				}
 			}
 
+			// Can't pivot around itself
+			if (pivothandle == this)
+			{
+				General.Interface.DisplayStatus(Windows.StatusType.Warning, "Slope handle to modify can't be the same as the pivot handle");
+				return;
+			}
+
 			// User didn't set a pivot handle, try to find the smart pivot handle
 			if (pivothandle == null)
 				pivothandle = GetSmartPivotHandle();
@@ -322,10 +329,6 @@ namespace CodeImp.DoomBuilder.VisualModes
 			if (pivothandle == null)
 				return;
 
-			mode.CreateUndo("Change slope");
-
-			Plane originalplane = level.plane;
-
 			Vector3D p1, p2, p3;
 
 			if (pivothandle is VisualVertexSlope)
@@ -336,19 +339,28 @@ namespace CodeImp.DoomBuilder.VisualModes
 				p3 = pivothandle.GetPivotPoint();
 				Vector2D perp = new Line2D(vertex.Position, p3).GetPerpendicular();
 
-				p1 = new Vector3D(vertex.Position, originalplane.GetZ(vertex.Position) + amount);
-				p2 = new Vector3D(vertex.Position + perp, originalplane.GetZ(vertex.Position + perp) + amount);
+				p1 = new Vector3D(vertex.Position, level.plane.GetZ(vertex.Position) + amount);
+				p2 = new Vector3D(vertex.Position + perp, level.plane.GetZ(vertex.Position + perp) + amount);
 			}
 			else // VisualSidedefSlope
 			{
 				List<Vector3D> pivotpoints = ((VisualSidedefSlope)pivothandle).GetPivotPoints();
-				p1 = new Vector3D(vertex.Position, originalplane.GetZ(vertex.Position) + amount);
+				p1 = new Vector3D(vertex.Position, level.plane.GetZ(vertex.Position) + amount);
 				p2 = pivotpoints[0];
 				p3 = pivotpoints[1];
 			}
 
 			Plane plane = new Plane(p1, p2, p3, true);
 
+			// Completely vertical planes are not possible
+			if (Math.Abs(plane.a) == 1.0 || Math.Abs(plane.b) == 1.0)
+			{
+				General.Interface.DisplayStatus(Windows.StatusType.Warning, "Resulting plane is completely vertical, which is impossible. Aborting");
+				return;
+			}
+
+			mode.CreateUndo("Change slope");
+
 			// Apply slope to surfaces
 			foreach (SectorLevel l in levels)
 				VisualSidedefSlope.ApplySlope(l, plane, mode);
diff --git a/Source/Plugins/ColorPicker/BuilderPlug.cs b/Source/Plugins/ColorPicker/BuilderPlug.cs
index fe136be8679b6dd55bf42cfd43515d15d03c3c8c..ac1d97a0216632503e8d500a2a0990a41c28a25c 100755
--- a/Source/Plugins/ColorPicker/BuilderPlug.cs
+++ b/Source/Plugins/ColorPicker/BuilderPlug.cs
@@ -172,11 +172,6 @@ namespace CodeImp.DoomBuilder.ColorPicker
 					deselectelement.Selected = false;						
 				}
 			} 
-			else 
-			{
-				form.Dispose();
-				form = null;
-			}
 
 			General.Interface.RedrawDisplay();
 		}
@@ -184,8 +179,6 @@ namespace CodeImp.DoomBuilder.ColorPicker
 		private void form_FormClosed(object sender, FormClosedEventArgs e) 
 		{
 			formLocation = form.Location;
-			form.Dispose();
-			form = null;
 		}
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/ColorPicker/Windows/LightColorPicker.cs b/Source/Plugins/ColorPicker/Windows/LightColorPicker.cs
index 67660d760c147665688e36bac94fd182543ab369..c7578d030492f071de3506927fbb50962df6c4b5 100755
--- a/Source/Plugins/ColorPicker/Windows/LightColorPicker.cs
+++ b/Source/Plugins/ColorPicker/Windows/LightColorPicker.cs
@@ -218,7 +218,7 @@ namespace CodeImp.DoomBuilder.ColorPicker.Windows
 				//update color 
 				if(colorChanged) //need this check to allow relative mode to work properly
 				{ 
-                    if (t.DynamicLightType.LightType == GZGeneral.LightType.SPOT)
+                    if (t.DynamicLightType.LightType == GZGeneral.LightType.SPOT || t.DynamicLightType.LightType == GZGeneral.LightType.SUN)
                     {
                         int c = ((int)lightProps.Red << 16) | ((int)lightProps.Green << 8) | lightProps.Blue;
                         t.Args[0] = 0;
@@ -323,8 +323,7 @@ namespace CodeImp.DoomBuilder.ColorPicker.Windows
 		{
 			General.Map.UndoRedo.CreateUndo(description);
 
-			//tricky way to actually store undo information...
-			foreach(Thing t in selection) t.Move(t.Position);
+			foreach (Thing t in selection) t.Fields.BeforeFieldsChange();
 		}
 
 		//this is called only once
@@ -332,7 +331,7 @@ namespace CodeImp.DoomBuilder.ColorPicker.Windows
 		{
 			if (thing.DynamicLightType.LightDef == GZGeneral.LightDef.VAVOOM_GENERIC) return Color.White; //vavoom light
 			if (thing.DynamicLightType.LightDef == GZGeneral.LightDef.VAVOOM_COLORED) return Color.FromArgb((byte)thing.Args[1], (byte)thing.Args[2], (byte)thing.Args[3]); //vavoom colored light
-            if (thing.DynamicLightType.LightType == GZGeneral.LightType.SPOT)
+            if (thing.DynamicLightType.LightType == GZGeneral.LightType.SPOT || thing.DynamicLightType.LightType == GZGeneral.LightType.SUN)
             {
                 if (thing.Fields.ContainsKey("arg0str"))
                 {
diff --git a/Source/Plugins/ColorPicker/Windows/SectorColorPicker.cs b/Source/Plugins/ColorPicker/Windows/SectorColorPicker.cs
index c3e719ebda2ea28259cb8c8fcbc04e90e1f09d53..a077f7d326f5c336022065618264286e337165a0 100755
--- a/Source/Plugins/ColorPicker/Windows/SectorColorPicker.cs
+++ b/Source/Plugins/ColorPicker/Windows/SectorColorPicker.cs
@@ -97,6 +97,8 @@ namespace CodeImp.DoomBuilder.ColorPicker.Windows
 			rbSectorColor.CheckedChanged += rbColor_CheckedChanged;
 			rbFadeColor.CheckedChanged += rbColor_CheckedChanged;
 
+			this.AcceptButton = colorPickerControl1.OkButton;
+			this.CancelButton = colorPickerControl1.CancelButton;
 			Text = "Editing " + rest;
 
 			//cannot fail here :)
diff --git a/Source/Plugins/ColorPicker/Windows/ToolForm.Designer.cs b/Source/Plugins/ColorPicker/Windows/ToolForm.Designer.cs
index 249f14ccb04a1ec25ca368863604864bc02f1d0c..6aafb11e9b1f7422f1fc413a355117b89bc470ba 100755
--- a/Source/Plugins/ColorPicker/Windows/ToolForm.Designer.cs
+++ b/Source/Plugins/ColorPicker/Windows/ToolForm.Designer.cs
@@ -26,7 +26,7 @@
 		/// </summary>
 		private void InitializeComponent() {
 			this.toolStrip1 = new System.Windows.Forms.ToolStrip();
-			this.cpButton = new System.Windows.Forms.ToolStripButton();
+			this.cpButton = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.menuStrip1 = new System.Windows.Forms.MenuStrip();
 			this.modesmenu = new System.Windows.Forms.ToolStripMenuItem();
 			this.cpMenu = new System.Windows.Forms.ToolStripMenuItem();
@@ -104,7 +104,7 @@
 		#endregion
 
 		private System.Windows.Forms.ToolStrip toolStrip1;
-		private System.Windows.Forms.ToolStripButton cpButton;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton cpButton;
 		private System.Windows.Forms.MenuStrip menuStrip1;
 		private System.Windows.Forms.ToolStripMenuItem modesmenu;
 		private System.Windows.Forms.ToolStripMenuItem cpMenu;
diff --git a/Source/Plugins/CommentsPanel/BuilderPlug.cs b/Source/Plugins/CommentsPanel/BuilderPlug.cs
index 8eb01094f3700e6931f351be7c7da1e98ea0a3b6..61f0627b428a85b11078e89dfd0a45cfa8d1ea05 100755
--- a/Source/Plugins/CommentsPanel/BuilderPlug.cs
+++ b/Source/Plugins/CommentsPanel/BuilderPlug.cs
@@ -111,7 +111,13 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			if(dockerpanel != null)
 				dockerpanel.UpdateListSoon();
 		}
-		
+
+		// Edit performed
+		public override void OnEditAccept()
+		{
+			dockerpanel?.UpdateListSoon();
+		}
+
 		// Mode changes
 		public override bool OnModeChange(EditMode oldmode, EditMode newmode)
 		{
@@ -120,5 +126,14 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 				
 			return base.OnModeChange(oldmode, newmode);
 		}
+
+		/// <summary>
+		/// Checks if the currently active docker is the Comments docker.
+		/// </summary>
+		/// <returns>true if the Comments docker is active, otherwise false</returns>
+		public bool IsDockerActive()
+		{
+			return General.Interface.ActiveDockerTabName == commentsdocker.Title;
+		}
     }
 }
diff --git a/Source/Plugins/CommentsPanel/CommentsDocker.Designer.cs b/Source/Plugins/CommentsPanel/CommentsDocker.Designer.cs
index 13a698bf0b01ec6386531994b89fcb51c8798dd8..f03bf9f7b0e4675698bd6a8e516f1a2a659c193f 100755
--- a/Source/Plugins/CommentsPanel/CommentsDocker.Designer.cs
+++ b/Source/Plugins/CommentsPanel/CommentsDocker.Designer.cs
@@ -43,8 +43,8 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			// 
 			// optionsgroup
 			// 
-			this.optionsgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.optionsgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.optionsgroup.Controls.Add(this.clickselects);
 			this.optionsgroup.Controls.Add(this.filtermode);
 			this.optionsgroup.Location = new System.Drawing.Point(3, 561);
@@ -81,9 +81,9 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			this.grid.AllowUserToDeleteRows = false;
 			this.grid.AllowUserToResizeColumns = false;
 			this.grid.AllowUserToResizeRows = false;
-			this.grid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.grid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.grid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
 			this.grid.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;
 			this.grid.BackgroundColor = System.Drawing.SystemColors.Window;
@@ -112,8 +112,8 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			this.grid.Size = new System.Drawing.Size(250, 465);
 			this.grid.StandardTab = true;
 			this.grid.TabIndex = 6;
-			this.grid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.grid_MouseDown);
 			this.grid.Leave += new System.EventHandler(this.grid_Leave);
+			this.grid.MouseDown += new System.Windows.Forms.MouseEventHandler(this.grid_MouseDown);
 			this.grid.MouseUp += new System.Windows.Forms.MouseEventHandler(this.grid_MouseUp);
 			// 
 			// iconcolumn
@@ -134,7 +134,7 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			// 
 			// updatetimer
 			// 
-			this.updatetimer.Interval = 2000;
+			this.updatetimer.Interval = 750;
 			this.updatetimer.Tick += new System.EventHandler(this.updatetimer_Tick);
 			// 
 			// contextmenu
@@ -192,8 +192,8 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			// 
 			// addcomment
 			// 
-			this.addcomment.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.addcomment.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.addcomment.Location = new System.Drawing.Point(6, 45);
 			this.addcomment.Name = "addcomment";
 			this.addcomment.Size = new System.Drawing.Size(232, 31);
@@ -204,8 +204,8 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			// 
 			// addcommentgroup
 			// 
-			this.addcommentgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.addcommentgroup.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.addcommentgroup.Controls.Add(this.addcommenttext);
 			this.addcommentgroup.Controls.Add(this.addcomment);
 			this.addcommentgroup.Location = new System.Drawing.Point(3, 471);
@@ -216,8 +216,8 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			// 
 			// addcommenttext
 			// 
-			this.addcommenttext.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.addcommenttext.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.addcommenttext.Location = new System.Drawing.Point(6, 19);
 			this.addcommenttext.Name = "addcommenttext";
 			this.addcommenttext.Size = new System.Drawing.Size(232, 20);
@@ -238,6 +238,7 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 			this.Controls.Add(this.optionsgroup);
 			this.Name = "CommentsDocker";
 			this.Size = new System.Drawing.Size(250, 657);
+			this.VisibleChanged += new System.EventHandler(this.CommentsDocker_VisibleChanged);
 			this.optionsgroup.ResumeLayout(false);
 			this.optionsgroup.PerformLayout();
 			((System.ComponentModel.ISupportInitialize)(this.grid)).EndInit();
diff --git a/Source/Plugins/CommentsPanel/CommentsDocker.cs b/Source/Plugins/CommentsPanel/CommentsDocker.cs
index a91922ce12c20625b675669bbde295899bb6c074..a2fe1ac422101a69baea8f46a2a1160fd209e756 100755
--- a/Source/Plugins/CommentsPanel/CommentsDocker.cs
+++ b/Source/Plugins/CommentsPanel/CommentsDocker.cs
@@ -152,7 +152,6 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 		public void UpdateListSoon()
 		{
 			updatetimer.Stop();
-			updatetimer.Interval = 100;
 			updatetimer.Start();
 		}
 		
@@ -387,16 +386,18 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 		// as it is called every time a dialog window closes.
 		private void ParentForm_Activated(object sender, EventArgs e)
 		{
-			UpdateList();
+			UpdateListSoon();
 		}
 		
 		// Update regulary
 		private void updatetimer_Tick(object sender, EventArgs e)
 		{
 			updatetimer.Stop();
-			updatetimer.Interval = 2000;
+
+			if (!BuilderPlug.Me.IsDockerActive())
+				return;
+
 			UpdateList();
-			updatetimer.Start();
 		}
 		
 		// Mouse pressed
@@ -630,7 +631,13 @@ namespace CodeImp.DoomBuilder.CommentsPanel
 		{
 			preventupdate = false;
 		}
-		
+
+		private void CommentsDocker_VisibleChanged(object sender, EventArgs e)
+		{
+			if (Visible)
+				UpdateList();
+		}
+
 		#endregion
 	}
 }
diff --git a/Source/Plugins/CommentsPanel/CommentsDocker.resx b/Source/Plugins/CommentsPanel/CommentsDocker.resx
index 6f50e49b9ff398f77c460b6804adb8bb3e45b6b2..82458d93ea132df8f020d4e7bb20c3facce2e3a3 100755
--- a/Source/Plugins/CommentsPanel/CommentsDocker.resx
+++ b/Source/Plugins/CommentsPanel/CommentsDocker.resx
@@ -112,48 +112,48 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
-  <metadata name="optionsgroup.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="optionsgroup.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="clickselects.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="clickselects.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="filtermode.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="filtermode.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="grid.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="grid.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="iconcolumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="iconcolumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="textcolumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="textcolumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="updatetimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="updatetimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
-  <metadata name="contextmenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="contextmenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>136, 17</value>
   </metadata>
-  <metadata name="addcomment.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="addcomment.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="addcommentgroup.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="addcommentgroup.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="addcommenttext.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="addcommenttext.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
-  <metadata name="enabledtimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="enabledtimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>252, 17</value>
   </metadata>
-  <metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+  <metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Plugins/NodesViewer/BuilderPlug.cs b/Source/Plugins/NodesViewer/BuilderPlug.cs
index 568722a36dd6f2b2178c9b391e070a48a510db42..dba01ab728c80c560c8c6772946dc0370503a894 100755
--- a/Source/Plugins/NodesViewer/BuilderPlug.cs
+++ b/Source/Plugins/NodesViewer/BuilderPlug.cs
@@ -20,6 +20,11 @@
 
 namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 {
+	internal class ToastMessages
+	{
+		public static readonly string NODESVIEWER = "nodesviewer";
+	}
+
 	public class BuilderPlug : Plug
 	{
 		#region ================== Variables
@@ -49,6 +54,9 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 
 			// Keep a static reference
 			me = this;
+
+			// Register toasts
+			General.ToastManager.RegisterToast(ToastMessages.NODESVIEWER, "Nodes Viewer Mode", "Toasts related to Nodes Viewer Mode");
 		}
 
 		// This is called when the plugin is terminated
diff --git a/Source/Plugins/NodesViewer/NodesForm.cs b/Source/Plugins/NodesViewer/NodesForm.cs
index 08c00abf6f080950f3ddd3264ade7767cc28be01..72799025c8c9bd133759ac36f639108fa1ba49ac 100755
--- a/Source/Plugins/NodesViewer/NodesForm.cs
+++ b/Source/Plugins/NodesViewer/NodesForm.cs
@@ -167,8 +167,14 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 			General.Editing.CancelMode();
 			NodesViewerMode newmode = new NodesViewerMode();
 			General.Editing.ChangeMode(newmode);
-			newmode.Form.showsegsvertices.Checked = this.showsegsvertices.Checked;
-			newmode.Form.Location = this.Location; //mxd
+
+			// If something went wrong while engaging the mode (for example an unsupported node format was detected)
+			// the mode will be disposed, so we need to check for it here
+			if (!newmode.IsDisposed)
+			{
+				newmode.Form.showsegsvertices.Checked = this.showsegsvertices.Checked;
+				newmode.Form.Location = this.Location; //mxd
+			}
 		}
 
 		#endregion
diff --git a/Source/Plugins/NodesViewer/NodesViewerMode.cs b/Source/Plugins/NodesViewer/NodesViewerMode.cs
index e280ed02ca47f436716a7a7cf94ac9f3c62c64fb..b96087bb99b53cb28375c3eceab3844c0513ceda 100755
--- a/Source/Plugins/NodesViewer/NodesViewerMode.cs
+++ b/Source/Plugins/NodesViewer/NodesViewerMode.cs
@@ -4,12 +4,14 @@ using System;
 using System.Collections.Generic;
 using System.Drawing;
 using System.IO;
+using System.Linq;
+using System.Text;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Rendering;
 using CodeImp.DoomBuilder.Windows;
-using CodeImp.DoomBuilder.Map;
 
 #endregion
 
@@ -51,6 +53,8 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 		public Vector2D[] Vertices { get { return verts; } }
 		public Subsector[] Subsectors { get { return ssectors; } }
 		public NodesForm Form { get { return form; } }
+		
+		public override bool AlwaysShowVertices { get { return true;  } }
 
 		#endregion
 
@@ -90,12 +94,37 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 		/// </summary>
 		private bool LoadClassicStructures()
 		{
+			List<byte[]> unsupportedheaders = new List<byte[]>() { Encoding.ASCII.GetBytes("ZNOD"), Encoding.ASCII.GetBytes("XNOD") };
+
 			// Load the nodes structure
 			MemoryStream nodesstream = General.Map.GetLumpData("NODES");
+
+			if(nodesstream.Length < 4)
+			{
+				MessageBox.Show("The NODES lump is too short.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+				General.Editing.CancelMode();
+				return false;
+			}
+
+			BinaryReader nodesreader = new BinaryReader(nodesstream);
+
+			// Compare the byte arrays. We can't do it by comparing strings, since the data read from the NODES
+			// lump might be interpreted as some UTF value. See https://github.com/jewalky/UltimateDoomBuilder/issues/827
+			byte[] header = nodesreader.ReadBytes(4);
+			if(unsupportedheaders.Where(e => Enumerable.SequenceEqual(e, header)).Any())
+			{
+				MessageBox.Show("ZDBSP compressed nodes are currently not supported.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+				General.Editing.CancelMode();
+				return false;
+			}
+
+			// Rewind stream position
+			nodesreader.BaseStream.Position = 0;
+
 			int numnodes = (int)nodesstream.Length / 28;
 
 			//mxd. Boilerplate!
-			if(numnodes < 1)
+			if (numnodes < 1)
 			{
 				// Cancel mode
 				MessageBox.Show("The map has only one subsector. Please add more sectors, then try running this mode again.", "THY NODETH ARETH BROKH!", MessageBoxButtons.OK, MessageBoxIcon.Error);
@@ -103,7 +132,7 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 				return false;
 			}
 
-			BinaryReader nodesreader = new BinaryReader(nodesstream);
+			
 			nodes = new Node[numnodes];
 			for(int i = 0; i < nodes.Length; i++)
 			{
@@ -814,6 +843,13 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 			Cursor.Current = Cursors.WaitCursor;
 			base.OnEngage();
 
+			if(General.Map.Map.Vertices.Count == 0)
+			{
+				General.ToastManager.ShowToast(ToastMessages.NODESVIEWER, ToastType.ERROR, "Failed to engage Nodes Viewer Mode", "The map is empty.", "Failed to engage Nodes Viewer Mode: the map is empty");
+				General.Editing.CancelMode();
+				return;
+			}
+
 			//mxd
 			bool haveNodes = General.Map.LumpExists("NODES");
 			bool haveZnodes = General.Map.LumpExists("ZNODES");
@@ -824,7 +860,12 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 			if(General.Map.IsChanged || !(haveZnodes || (haveNodes || haveSectors || haveSegs || haveVerts)))
 			{
 				// We need to build the nodes!
-				if(!General.Map.RebuildNodes(General.Map.ConfigSettings.NodebuilderSave, true)) return;
+				if (!General.Map.RebuildNodes(General.Map.ConfigSettings.NodebuilderSave, true))
+				{
+					General.ToastManager.ShowToast(ToastMessages.NODESVIEWER, ToastType.ERROR, "Failed to engage Nodes Viewer Mode", "Failed to rebuild the nodes.", "Failed to engage Nodes Viewer Mode: failed to rebuild the nodes");
+					General.Editing.CancelMode();
+					return;
+				}
 
 				//mxd. Update nodes availability
 				haveNodes = General.Map.LumpExists("NODES");
@@ -837,6 +878,14 @@ namespace CodeImp.DoomBuilder.Plugins.NodesViewer
 			//mxd
 			if(haveZnodes) 
 			{
+				// For whatever reason ZDBSP reorders the vertices when building the nodes, so if the map was modified in UDB
+				// and then the Nodes Viewer is engaged the vertices in the ZNODES are not the same, resulting in an incorrect
+				// view or even a crash.
+				// See https://github.com/jewalky/UltimateDoomBuilder/issues/659
+				General.Interface.DisplayStatus(StatusType.Warning, "ZNODES are currently not supported.");
+				General.Editing.CancelMode();
+				return;
+
 				General.Interface.DisplayStatus(StatusType.Busy, "Reading map nodes...");
 				if(!LoadZNodes()) 
 				{
diff --git a/Source/Plugins/SoundPropagationMode/BuilderPlug.cs b/Source/Plugins/SoundPropagationMode/BuilderPlug.cs
index 08ca5f57ba1dddc2e4c8157085ad5724f5fc78e1..2acfb699d1ada0873f8a76e5eaf9f41e91ede58a 100755
--- a/Source/Plugins/SoundPropagationMode/BuilderPlug.cs
+++ b/Source/Plugins/SoundPropagationMode/BuilderPlug.cs
@@ -245,7 +245,7 @@ namespace CodeImp.DoomBuilder.SoundPropagationMode
 			for(int i = 0; i < soundenvironmenthings.Count; i++)
 			{
 				//mxd. Make sure same environments use the same color
-				int seid = (soundenvironmenthings[i].Args[0] << 8) + soundenvironmenthings[i].Args[1];
+				int seid = (Math.Abs(soundenvironmenthings[i].Args[0])%256 << 8) + Math.Abs(soundenvironmenthings[i].Args[1])%256;
 				secolor[soundenvironmenthings[i]] = distinctcolors[seid % distinctcolors.Count];
 				senumber.Add(soundenvironmenthings[i], i + 1);
 			}
diff --git a/Source/Plugins/SoundPropagationMode/SoundEnvironmentMode.cs b/Source/Plugins/SoundPropagationMode/SoundEnvironmentMode.cs
index 2516c3d2ecb407a56b1d3cc5e0b4f6e052b62797..0a353705d4b338d0af3f28c85d39e3b415f80099 100755
--- a/Source/Plugins/SoundPropagationMode/SoundEnvironmentMode.cs
+++ b/Source/Plugins/SoundPropagationMode/SoundEnvironmentMode.cs
@@ -219,9 +219,20 @@ namespace CodeImp.DoomBuilder.SoundPropagationMode
 			}
 		}
 
+		protected override void OnEditBegin()
+		{
+			base.OnEditBegin();
+
+			editpressed = true;
+		}
+
 		//mxd. Show Reverb selector dialog or add a new sound environment thing
 		protected override void OnEditEnd()
 		{
+			// Edit pressed in this mode?
+			if (!editpressed)
+				return;
+
 			if(highlightedthing != null)
 			{
 				ReverbsPickerForm form = new ReverbsPickerForm(highlightedthing);
diff --git a/Source/Plugins/SoundPropagationMode/Windows/MenusForm.designer.cs b/Source/Plugins/SoundPropagationMode/Windows/MenusForm.designer.cs
index 3025104ca86d954043e6a1b92305a75b9a127bd6..004ac261c1f19477ae2f25ed334f0058402c1961 100755
--- a/Source/Plugins/SoundPropagationMode/Windows/MenusForm.designer.cs
+++ b/Source/Plugins/SoundPropagationMode/Windows/MenusForm.designer.cs
@@ -29,7 +29,7 @@
 		private void InitializeComponent()
 		{
 			this.toolStrip1 = new System.Windows.Forms.ToolStrip();
-			this.colorconfiguration = new System.Windows.Forms.ToolStripButton();
+			this.colorconfiguration = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.toolStrip1.SuspendLayout();
 			this.SuspendLayout();
 			// 
@@ -72,6 +72,6 @@
 		#endregion
 
 		private System.Windows.Forms.ToolStrip toolStrip1;
-		private System.Windows.Forms.ToolStripButton colorconfiguration;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton colorconfiguration;
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/StairSectorBuilder/BuilderPlug.cs b/Source/Plugins/StairSectorBuilder/BuilderPlug.cs
index eea077e4c904daf9ecb749241584ed5aa9c8799e..7df700e39d1ac2bdcca68810ee5a206f6f8628c4 100755
--- a/Source/Plugins/StairSectorBuilder/BuilderPlug.cs
+++ b/Source/Plugins/StairSectorBuilder/BuilderPlug.cs
@@ -34,6 +34,11 @@ namespace CodeImp.DoomBuilder.StairSectorBuilderMode
 	// by the core.
 	//
 
+	internal class ToastMessages
+	{
+		public static readonly string ENGAGEFAILED = "engagefailed";
+	}
+
 	public class BuilderPlug : Plug
 	{
         public struct Prefab
@@ -119,6 +124,9 @@ namespace CodeImp.DoomBuilder.StairSectorBuilderMode
 
 			// Keep a static reference
             me = this;
+
+			// Register toasts
+			General.ToastManager.RegisterToast(ToastMessages.ENGAGEFAILED, "Stair Sector Builder Mode starting failed", "When no linedefs or sectors are selected when entering Stair Sector Builder Mode");
 		}
 
 		// This is called when the plugin is terminated
diff --git a/Source/Plugins/StairSectorBuilder/StairSectorBuilderForm.cs b/Source/Plugins/StairSectorBuilder/StairSectorBuilderForm.cs
index f6213fe91321a3d1abd268c6e6f8ac49cf4fac12..4bef0b25b2019dfbd7fab0aedf54cd3dba95d48c 100755
--- a/Source/Plugins/StairSectorBuilder/StairSectorBuilderForm.cs
+++ b/Source/Plugins/StairSectorBuilder/StairSectorBuilderForm.cs
@@ -278,16 +278,6 @@ namespace CodeImp.DoomBuilder.StairSectorBuilderMode
 				}
 			}
 
-			// Check if there's a "[Default]" prefab and load it if so
-			foreach (BuilderPlug.Prefab p in BuilderPlug.Me.Prefabs)
-			{
-				if(p.name == "[Default]")
-				{
-					LoadPrefab(p);
-					break;
-				}
-			}
-
 			// Show window
 			base.Show(owner);
         }
@@ -432,6 +422,16 @@ namespace CodeImp.DoomBuilder.StairSectorBuilderMode
 			autocurveflipping.SelectedIndex = 0;
 			MiddleTextureTexture = "-";
 
+			// Check if there's a "[Default]" prefab and load it if so
+			foreach (BuilderPlug.Prefab p in BuilderPlug.Me.Prefabs)
+			{
+				if (p.name == "[Default]")
+				{
+					LoadPrefab(p);
+					break;
+				}
+			}
+
 			fullyloaded = true;
 
 			ComputeHeights();
diff --git a/Source/Plugins/StairSectorBuilder/StairSectorBuilderMode.cs b/Source/Plugins/StairSectorBuilder/StairSectorBuilderMode.cs
index 1b8d631c5d65f1fee381dc24c9f60b1301864e2b..eba439eb7f8e68d58654c76e73ecf41d84f73b90 100755
--- a/Source/Plugins/StairSectorBuilder/StairSectorBuilderMode.cs
+++ b/Source/Plugins/StairSectorBuilder/StairSectorBuilderMode.cs
@@ -1237,7 +1237,7 @@ namespace CodeImp.DoomBuilder.StairSectorBuilderMode
             // If no lines are selected nothing can be done, so exit this mode immediately
             if(General.Map.Map.SelectedLinedefsCount == 0 && General.Map.Map.SelectedSectorsCount == 0)
             {
-                General.Interface.DisplayStatus(StatusType.Warning, "No lines or sectors selected.");
+				General.ToastManager.ShowToast(ToastMessages.ENGAGEFAILED, ToastType.ERROR, "Failed to start Stair Sector Builder Mode", "You need to select at least one linedef or sector to enter Stair Sector Builder Mode");
                 General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
                 return;
             }
diff --git a/Source/Plugins/TagExplorer/BuilderPlug.cs b/Source/Plugins/TagExplorer/BuilderPlug.cs
index ce2137e03a03ec978e6c05a9f6d4069dd190f530..463adee9c898d8959e40fa21a8e36d49d69929e1 100755
--- a/Source/Plugins/TagExplorer/BuilderPlug.cs
+++ b/Source/Plugins/TagExplorer/BuilderPlug.cs
@@ -85,5 +85,14 @@ namespace CodeImp.DoomBuilder.TagExplorer
 			if(tagExplorer != null && action.Name == "builder_deleteitem")
 				tagExplorer.UpdateTreeSoon();
 		}
+
+		/// <summary>
+		/// Checks if the currently active docker is the Tag Explorer docker.
+		/// </summary>
+		/// <returns>true if the Tag Explorer docker is active, otherwise false</returns>
+		public bool IsDockerActive()
+		{
+			return General.Interface.ActiveDockerTabName == docker.Title;
+		}
 	}
 }
diff --git a/Source/Plugins/TagExplorer/Controls/TagExplorer.Designer.cs b/Source/Plugins/TagExplorer/Controls/TagExplorer.Designer.cs
index 05a25b67340db1af6e72a27ac97c5f3e05453b62..f77a364327dc717bd61ff8f8b8345f68e9dac9c2 100755
--- a/Source/Plugins/TagExplorer/Controls/TagExplorer.Designer.cs
+++ b/Source/Plugins/TagExplorer/Controls/TagExplorer.Designer.cs
@@ -38,9 +38,9 @@
 			// 
 			// treeView
 			// 
-			this.treeView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
-						| System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.treeView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.treeView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
 			this.treeView.HideSelection = false;
 			this.treeView.ImageIndex = 0;
@@ -51,10 +51,10 @@
 			this.treeView.ShowNodeToolTips = true;
 			this.treeView.Size = new System.Drawing.Size(266, 226);
 			this.treeView.TabIndex = 0;
-			this.treeView.NodeMouseDoubleClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.treeView_NodeMouseDoubleClick);
+			this.treeView.BeforeLabelEdit += new System.Windows.Forms.NodeLabelEditEventHandler(this.treeView_BeforeLabelEdit);
 			this.treeView.AfterLabelEdit += new System.Windows.Forms.NodeLabelEditEventHandler(this.treeView_AfterLabelEdit);
 			this.treeView.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.treeView_NodeMouseClick);
-			this.treeView.BeforeLabelEdit += new System.Windows.Forms.NodeLabelEditEventHandler(this.treeView_BeforeLabelEdit);
+			this.treeView.NodeMouseDoubleClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.treeView_NodeMouseDoubleClick);
 			// 
 			// imageList1
 			// 
@@ -69,8 +69,8 @@
 			// 
 			// cbDisplayMode
 			// 
-			this.cbDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.cbDisplayMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.cbDisplayMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 			this.cbDisplayMode.Location = new System.Drawing.Point(57, 13);
 			this.cbDisplayMode.Name = "cbDisplayMode";
@@ -89,8 +89,8 @@
 			// 
 			// cbSortMode
 			// 
-			this.cbSortMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.cbSortMode.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.cbSortMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
 			this.cbSortMode.Location = new System.Drawing.Point(57, 40);
 			this.cbSortMode.Name = "cbSortMode";
@@ -119,8 +119,8 @@
 			// 
 			// groupBox1
 			// 
-			this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.groupBox1.Controls.Add(this.cbCommentsOnly);
 			this.groupBox1.Controls.Add(this.cbSelectOnClick);
 			this.groupBox1.Controls.Add(this.labelSearch);
@@ -184,8 +184,8 @@
 			// 
 			// tbSearch
 			// 
-			this.tbSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.tbSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.tbSearch.Location = new System.Drawing.Point(57, 68);
 			this.tbSearch.Name = "tbSearch";
 			this.tbSearch.Size = new System.Drawing.Size(175, 20);
@@ -204,8 +204,8 @@
 			// 
 			// bExportToFile
 			// 
-			this.bExportToFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
-						| System.Windows.Forms.AnchorStyles.Right)));
+			this.bExportToFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
 			this.bExportToFile.Image = global::CodeImp.DoomBuilder.TagExplorer.Properties.Resources.Save;
 			this.bExportToFile.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
 			this.bExportToFile.Location = new System.Drawing.Point(3, 402);
@@ -235,6 +235,7 @@
 			this.Controls.Add(this.treeView);
 			this.Name = "TagExplorer";
 			this.Size = new System.Drawing.Size(272, 430);
+			this.VisibleChanged += new System.EventHandler(this.TagExplorer_VisibleChanged);
 			this.groupBox1.ResumeLayout(false);
 			this.groupBox1.PerformLayout();
 			this.ResumeLayout(false);
diff --git a/Source/Plugins/TagExplorer/Controls/TagExplorer.cs b/Source/Plugins/TagExplorer/Controls/TagExplorer.cs
index 24ac98cb5667ce24115a2e71733790f775cfbc01..14b78210329b61b65dad9d2c59b970c78fc285c4 100755
--- a/Source/Plugins/TagExplorer/Controls/TagExplorer.cs
+++ b/Source/Plugins/TagExplorer/Controls/TagExplorer.cs
@@ -129,7 +129,9 @@ namespace CodeImp.DoomBuilder.TagExplorer
 		public void Setup() 
 		{
 			if(this.ParentForm != null) this.ParentForm.Activated += ParentForm_Activated;
-			UpdateTree(true);
+			
+			if(Visible)
+				UpdateTree(true);
 		}
 
 		public void Terminate() 
@@ -146,6 +148,9 @@ namespace CodeImp.DoomBuilder.TagExplorer
 
 		private void UpdateTree(bool focusDisplay) 
 		{
+			if (!General.Map.Map.IsSafeToAccess)
+				return;
+
 			bool showTags = (currentDisplayMode == DISPLAY_TAGS || currentDisplayMode == DISPLAY_TAGS_AND_ACTIONS);
 			bool showActions = (currentDisplayMode == DISPLAY_ACTIONS || currentDisplayMode == DISPLAY_TAGS_AND_ACTIONS);
 			bool hasComment;
@@ -167,7 +172,7 @@ namespace CodeImp.DoomBuilder.TagExplorer
 
 			List<TreeNode> nodes = new List<TreeNode>();
 
-//add things
+			//add things
 			if(General.Map.FormatInterface.HasThingAction || General.Map.FormatInterface.HasThingTag) 
 			{
 				ICollection<Thing> things = General.Map.Map.Things;
@@ -331,7 +336,7 @@ namespace CodeImp.DoomBuilder.TagExplorer
 				}
 			}
 
-//add sectors
+			//add sectors
 			nodes = new List<TreeNode>();
 			ICollection<Sector> sectors = General.Map.Map.Sectors;
 
@@ -516,7 +521,7 @@ namespace CodeImp.DoomBuilder.TagExplorer
 				}
 			}
 
-//add linedefs
+			//add linedefs
 			nodes = new List<TreeNode>();
 			ICollection<Linedef> linedefs = General.Map.Map.Linedefs;
 
@@ -1098,6 +1103,10 @@ namespace CodeImp.DoomBuilder.TagExplorer
 		private void updatetimer_Tick(object sender, EventArgs e) 
 		{
 			updatetimer.Stop();
+
+			if(!BuilderPlug.Me.IsDockerActive())
+				return;
+
 			UpdateTree(Form.ActiveForm == General.Interface);
 		}
 
@@ -1151,6 +1160,12 @@ namespace CodeImp.DoomBuilder.TagExplorer
 			}
 		}
 
+		private void TagExplorer_VisibleChanged(object sender, EventArgs e)
+		{
+			if (Visible)
+				UpdateTree(true);
+		}
+
 		#endregion
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/TagExplorer/Controls/TagExplorer.resx b/Source/Plugins/TagExplorer/Controls/TagExplorer.resx
index 9481c263e9a22ee19b1b5d70ae8f484357a05af6..4813dcbf2ca07ff959b5b17110e457a818174b01 100755
--- a/Source/Plugins/TagExplorer/Controls/TagExplorer.resx
+++ b/Source/Plugins/TagExplorer/Controls/TagExplorer.resx
@@ -112,130 +112,127 @@
     <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
-  <metadata name="imageList1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="imageList1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
   <data name="imageList1.ImageStream" mimetype="application/x-microsoft.net.object.binary.base64">
     <value>
-        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj0yLjAuMC4w
+        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
         LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
-        ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAAo
-        FwAAAk1TRnQBSQFMAgEBBgEAAYABAAGAAQABEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA
-        AwABIAMAAQEBAAEgBgABIBYAAxIBGQMSARkDEgEZNAADEgEZAxIBGQMSARmwAAMwAUwBAAE5AecB/wEA
-        ASUBoQH/AzABTAMSARksAAMwAUwBswFnAR0B/wFxAUEBEwH/AzABTAMSARmsAAEWAVYC/wEOAVAC/wEA
-        ATIBzgH/AQABJQGhAf8DEgEZLAAB4AGHASUB/wHZAYYBJAH/AZ4BWwEaAf8BcQFBARMB/wMSARmsAAFq
-        AZsC/wHoAe8C/wEOAVAC/wEAATkB5wH/AxIBGSwAAf4BtAFiAv8B/QHlAf8B2QGGASQB/wGzAWcBHQH/
-        AxIBGawAAzABTAFqAZsC/wEWAVYC/wOJAf8DPgH/AyMBMygAAzABTAH+AbQBYgH/AeABhwElAf8BowGJ
-        AWcB/wM+Af8DIwEzsAADIwEzA7wB/wOBAf8DPgH/AyMBMwgAAyMBMwMjATMDIwEzGAADIwEzA7wB/wOB
-        Af8DPgH/AyMBMwgAAyMBMwMjATMDIwEznAADIwEzA80B/wOUAf8DPgH/AyMBMwMwAUwDPgH/A5QB/wMj
-        ATMcAAMjATMDzQH/A5QB/wM+Af8DIwEzAzABTAM+Af8DlAH/AyMBM6AAAyMBMwPNAf8DlAH/Az4B/wM+
-        Af8DlAH/AyMBMyQAAyMBMwPNAf8DlAH/Az4B/wM+Af8DlAH/AyMBM6gAAyMBMwO4Af8DlAH/A4EB/wMj
-        ATMsAAMjATMDuAH/A5QB/wOBAf8DIwEzsAADIwEzA88B/wOUAf8DPgH/AyMBMywAAyMBMwPPAf8DlAH/
-        Az4B/wMjATOwAAMjATMDwwH/A5QB/wM+Af8DIwEzLAADIwEzA8MB/wOUAf8DPgH/AyMBM7AAAyMBMwO8
-        Af8DlAH/Az4B/wMSARkDEgEZAxIBGSQAAyMBMwO8Af8DlAH/Az4B/wMSARkDEgEZAxIBGagAAyMBMwPZ
-        Af8DtwH/AQABOQHnAf8BAAElAaEB/wMwAUwDEgEZJAADIwEzA9kB/wHRAbcBnQH/AbMBZwEdAf8BcQFB
-        ARMB/wMwAUwDEgEZqAADIwEzARYBVgL/AQ4BUAL/AQABMgHOAf8BAAElAaEB/wMSARkoAAMjATMB4AGH
-        ASUB/wHZAYYBJAH/AZ4BWwEaAf8BcQFBARMB/wMSARmsAAFqAZsC/wHoAe8C/wEOAVAC/wEAATkB5wH/
-        AxIBGSwAAf4BtAFiAv8B/QHlAf8B2QGGASQB/wGzAWcBHQH/AxIBGawAAzABTAFqAZsC/wEWAVYC/wMw
-        AUwwAAMwAUwB/gG0AWIB/wHgAYcBJQH/AzABTJgAAwgBCwMfAS0DJwE7AycBOwMnATsDHgErAxIBGAMS
+        ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAAm
+        FwAAAk1TRnQBSQFMAgEBBgEAAYgBAAGIAQABEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA
+        AwABIAMAAQEBAAEgBgABIBYAAxIBGQMSARkDEgEZNAADEgEZAxIBGQMSARmwAAMwAUwBAAE4AecB/wEA
+        ASQBoQH/AzABTAMSARksAAMwAUwBswFmARwB/wFwAUABEgH/AzABTAMSARmsAAEVAVUC/wENAU8C/wEA
+        ATEBzgH/AQABJAGhAf8DEgEZLAAB4AGHASQB/wHZAYYBIwH/AZ4BWgEZAf8BcAFAARIB/wMSARmsAAFp
+        AZsC/wHoAe8C/wENAU8C/wEAATgB5wH/AxIBGSwAAf4BtAFhAv8B/QHlAf8B2QGGASMB/wGzAWYBHAH/
+        AxIBGawAAzABTAFpAZsC/wEVAVUC/wOJAf8DPQH/AyMBMygAAzABTAH+AbQBYQH/AeABhwEkAf8BowGJ
+        AWYB/wM9Af8DIwEzsAADIwEzA7wB/wOBAf8DPQH/AyMBMwgAAyMBMwMjATMDIwEzGAADIwEzA7wB/wOB
+        Af8DPQH/AyMBMwgAAyMBMwMjATMDIwEznAADIwEzA80B/wOUAf8DPQH/AyMBMwMwAUwDPQH/A5QB/wMj
+        ATMcAAMjATMDzQH/A5QB/wM9Af8DIwEzAzABTAM9Af8DlAH/AyMBM6AAAyMBMwPNAf8DlAH/Az0B/wM9
+        Af8DlAH/AyMBMyQAAyMBMwPNAf8DlAH/Az0B/wM9Af8DlAH/AyMBM6gAAyMBMwO4Af8DlAH/A4EB/wMj
+        ATMsAAMjATMDuAH/A5QB/wOBAf8DIwEzsAADIwEzA88B/wOUAf8DPQH/AyMBMywAAyMBMwPPAf8DlAH/
+        Az0B/wMjATOwAAMjATMDwwH/A5QB/wM9Af8DIwEzLAADIwEzA8MB/wOUAf8DPQH/AyMBM7AAAyMBMwO8
+        Af8DlAH/Az0B/wMSARkDEgEZAxIBGSQAAyMBMwO8Af8DlAH/Az0B/wMSARkDEgEZAxIBGagAAyMBMwPZ
+        Af8DtwH/AQABOAHnAf8BAAEkAaEB/wMwAUwDEgEZJAADIwEzA9kB/wHRAbcBnQH/AbMBZgEcAf8BcAFA
+        ARIB/wMwAUwDEgEZqAADIwEzARUBVQL/AQ0BTwL/AQABMQHOAf8BAAEkAaEB/wMSARkoAAMjATMB4AGH
+        ASQB/wHZAYYBIwH/AZ4BWgEZAf8BcAFAARIB/wMSARmsAAFpAZsC/wHoAe8C/wENAU8C/wEAATgB5wH/
+        AxIBGSwAAf4BtAFhAv8B/QHlAf8B2QGGASMB/wGzAWYBHAH/AxIBGawAAzABTAFpAZsC/wEVAVUC/wMw
+        AUwwAAMwAUwB/gG0AWEB/wHgAYcBJAH/AzABTJgAAwgBCwMfAS0DJwE7AycBOwMnATsDHgErAxIBGAMS
         ARggAAMIAQsDHwEtAycBOwMnATsDJwE7Ax4BKwMSARgDEgEYEAADEgEZAxIBGQMSARkgAAMSARkDEgEZ
-        AxIBGQgAAxIBGQMSARkDEgEZIAADEgEZAxIBGQMSARkUAAM4AVwDXwHoARwBJAGBAf8BFgEdAWoB/wEV
-        ARwBZwH/ARQBGwFgAf8CWgFgAeQDOAFcAycBOwMSARgYAAM4AVwDXwHoAYEBTwEcAf8BagFHARYB/wFn
-        AUUBFQH/AWABQAEUAf8BYQJaAeQDOAFcAycBOwMSARgIAAMwAUwBHgEtAbIB/wEUASEBcAH/AzABTAMS
-        ARkDEgEZAxIBGQMSARkDEgEZAxIBGQMSARkDMAFMAR4BLQGyAf8BFAEhAXAB/wMwAUwDEgEZAzABTAGy
-        AWcBHgH/AXABQQEUAf8DMAFMAxIBGQMSARkDEgEZAxIBGQMSARkDEgEZAxIBGQMwAUwBsgFnAR4B/wFw
-        AUEBFAH/AzABTAMSARkMAANaAcUBLAE4AbsB/wEqATYBvAH/ASkBNQG6Af8BKAE0AbcB/wEmATABtAH/
-        ASQBLwGsAf8BHwEnAZQB/wEVAR0BagH/A1oBvQMnATsDEgEYEAADWgHFAbsBgwEsAf8BvAGCASoB/wG6
-        AYEBKQH/AbcBgQEoAf8BtAGBASYB/wGsAW0BJAH/AZQBXwEfAf8BagFGARUB/wNaAb0DJwE7AxIBGAQA
-        AScBPwHfAf8BJQE5AdgB/wEaASYBnQH/ARQBIQFwAf8DNwH/AzcB/wM3Af8DNwH/AzcB/wM3Af8DNwH/
-        AScBPwHfAf8BJQE5AdgB/wEaASYBnQH/ARQBIQFwAf8DEgEZAd8BhwEnAf8B2AGGASUB/wGdAVsBGgH/
-        AXABQQEUAf8DNwH/AzcB/wM3Af8DNwH/AzcB/wM3Af8DNwH/Ad8BhwEnAf8B2AGGASUB/wGdAVsBGgH/
-        AXABQQEUAf8DEgEZCAADWQG+ATEBPgHTAf8BNgFFAegB/wE6AUgB8wH/ATkBSAHwAf8BOAFIAe8B/wE4
-        AUgB7wH/ATcBRQHtAf8BNAFDAeIB/wEqATYBuwH/ARoBIQGBAf8DWgG9AycBOwMSARgIAANZAb4B0wGT
-        ATEB/wHoAaEBNgH/AfMBqgE6Af8B8AGnATkB/wHvAaUBOAH/Ae8BpQE4Af8B7QGlATcB/wHiAZwBNAH/
-        AbsBggEqAf8BgQFRARoB/wNaAb0DJwE7AxIBGAFjAYEB/QH/AewB5QL/ASUBOQHYAf8BHgEtAbIB/wO4
-        Af8DuAH/A7gB/wO4Af8DuAH/A6YB/wOYAf8BYwGBAf0B/wHsAeUC/wElATkB2AH/AR4BLQGyAf8DEgEZ
-        Af0BtAFjAv8B/QHlAf8B2AGGASUB/wGyAWcBHgH/A7gB/wO4Af8DuAH/A7gB/wO4Af8DpgH/A5gB/wH9
-        AbQBYwL/Af0B5QH/AdgBhgElAf8BsgFnAR4B/wMSARkEAAM4AVwBOgFIAeMB/wE5AUcB8QH/ASkBNAGz
-        Af8BJwEyAasB/wE3AUUB6gH/ATwBTAH6Af8BOwFKAfYB/wE5AUcB8QH/ATkBSAHwAf8BNgFEAeoB/wEp
-        ATQBvgH/ARkBHwGBAf8DOAFcAxIBGAQAAzgBXAHjAaABOgH/AfEBqAE5Af8BswGBASkB/wGrAW4BJwH/
-        AeoBowE3Af8B+gGuATwB/wH2AawBOwH/AfEBqAE5Af8B8AGnATkB/wHqAaMBNgH/Ab4BhAEpAf8BgQFQ
-        ARkB/wM4AVwDEgEYAzABTAFjAYEB/QH/AScBPwHfAf8DMAFMDAADuAH/A04BmQgAAzABTAFjAYEB/QH/
-        AScBPwHfAf8DMAFMBAADMAFMAf0BtAFjAf8B3wGHAScB/wMwAUwMAAO4Af8DTgGZCAADMAFMAf0BtAFj
-        Af8B3wGHAScB/wMwAUwIAANfAdsBSQFXAfYB/wFDAVIC/wEpATQBsQH/AwAB/wECAQQBHgH/ARcBHgFq
-        Af8BLgE5AcIB/wE5AUkB8QH/ATwBTAH6Af8BOgFKAfQB/wEzAUEB5AH/ASQBLwGsAf8CXAFkAecDHgEr
-        BAADXwHbAfYBsQFJAv8BtAFDAf8BsQFyASkB/wMAAf8BHgETAQIB/wFqAUcBFwH/AcIBiAEuAf8B8QGn
-        ATkB/wH6Aa4BPAH/AfQBqQE6Af8B5AGeATMB/wGsAW0BJAH/AWQBYgFcAecDHgErBAADmAH/Az4B/wMS
-        ARkMAAOlAf8DTgGZDAADmAH/AzcB/wMSARkIAAOYAf8DPgH/AxIBGQwAA6UB/wNOAZkMAAOYAf8DNwH/
-        AxIBGQgAAUYBVAL/AV4BbAL/AU0BWgH7Af8BPwFNAegB/wECAQMBFAH/AwAB/wMAAf8DAAH/AQYBCQEt
-        Af8BHAEkAYQB/wEwATwBzgH/ATgBRwHvAf8BLgE6AcwB/wEdASYBigH/AycBOwQAAf8BtgFGAv8BvgFe
-        Af8B+wG3AU0B/wHoAaUBPwH/ARQBDQECAf8DAAH/AwAB/wMAAf8BLQEdAQYB/wGEAVMBHAH/Ac4BkAEw
-        Af8B7wGmATgB/wHMAY4BLgH/AYoBVwEdAf8DJwE7BAADpgH/Az4B/wMSARkgAAOmAf8DNwH/AxIBGQgA
-        A6YB/wM+Af8DEgEZIAADpgH/AzcB/wMSARkEAAMjATMBWQFmAv8BbwGEAv8BVwFjAfkB/wFJAVgB/AH/
-        ARoBIgFvAf8DAAH/AwAB/wMAAf8BAAEBAQcB/wETARoBWQH/ASMBLAGdAf8BNgFDAeQB/wEzAUIB4QH/
-        ASABKQGcAf8DJwE7AyMBMwH/Ab0BWQL/AcYBbwH/AfkBugFXAf8B/AG0AUkB/wFvAUsBGgH/AwAB/wMA
-        Af8DAAH/AQcBBAEAAf8BWQE7ARMB/wGdAWUBIwH/AeQBoAE2Af8B4QGbATMB/wGcAWMBIAH/AycBOwQA
-        A7AB/wM+Af8DTgGZA04BmRQAA04BmQNOAZkDsAH/AzcB/wMSARkIAAOwAf8DPgH/A04BmQNOAZkUAANO
-        AZkDTgGZA7AB/wM3Af8DEgEZBAADIwEzAWMBcAL/AYEBjAL/AVgBZgH4Af8BRwFXAfsB/wEvATsBywH/
-        AwAB/wMAAf8DAAH/AQwBEAE/Af8BPAFLAfkB/wE8AUwB+gH/ATkBSAHwAf8BMwFBAeMB/wEhASsBogH/
-        AycBOwMjATMB/wHBAWMC/wHKAYEB/wH4AbgBWAH/AfsBsgFHAf8BywGOAS8B/wMAAf8DAAH/AwAB/wE/
-        ASoBDAH/AfkBrgE8Af8B+gGuATwB/wHwAacBOQH/AeMBnQEzAf8BogFnASEB/wMnATsEAAO4Af8DuAH/
-        A7gB/wOlAf8UAAOlAf8DuAH/A7gB/wM3Af8DEgEZCAADuAH/A7gB/wO4Af8DpQH/FAADpQH/A7gB/wO4
-        Af8DNwH/AxIBGQgAAWEBcAL/AY4BlwL/AWABawH+Af8BSQFYAfUB/wE/AU0B+AH/AQsBEAE/Af8DAAH/
-        ARgBIAFuAf8DAAH/AQ8BFQFOAf8BOQFIAfAB/wE6AUgB8wH/ATEBPwHaAf8BIwEsAZ8B/wMiATEEAAH/
-        Ab8BYQL/AdABjgH/Af4BwQFgAf8B9QGwAUkB/wH4Aa8BPwH/AT8BKQELAf8DAAH/AW4BSQEYAf8DAAH/
-        AU4BMwEPAf8B8AGnATkB/wHzAaoBOgH/AdoBlwExAf8BnwFnASMB/wMiATEEAAO4Af8DPgH/AxIBGSAA
-        A7gB/wM3Af8DEgEZCAADuAH/Az4B/wMSARkgAAO4Af8DNwH/AxIBGQgAA18B2wGWAaAC/wGGAZAC/wFh
-        AW4B+QH/AUwBWgH+Af8BJAEuAZ0B/wEKAQ8BOwH/AT4BTgL/ASIBKwGYAf8DAAH/AQ8BFQFOAf8BOAFH
-        Ae0B/wExAT4B1QH/A1wB6gMMARAEAANfAdsB/wHSAZYC/wHMAYYB/wH5AbwBYQH/Af4BuAFMAf8BnQFl
-        ASQB/wE7ASYBCgL/AbIBPgH/AZgBYgEiAf8DAAH/AU4BMwEPAf8B7QGlATgB/wHVAZQBMQH/A1wB6gMM
-        ARAEAAO4Af8DPgH/AxIBGSAAA7gB/wM3Af8DEgEZCAADuAH/Az4B/wMSARkgAAO4Af8DNwH/AxIBGQgA
-        AzgBXAGYAaIC/wGeAacC/wFqAYEC/wFWAWMB+QH/AUEBTAHkAf8BMgE9Ab4B/wFCAVIB+AH/AUQBUgH9
-        Af8BKwEzAZwB/wMAAf8BKAEyAbEB/wE0AUEB3gH/AzgBXAgAAzgBXAH/AdMBmAL/AdYBngL/AcUBagH/
-        AfkBuQFWAf8B5AGlAUEB/wG+AYcBMgH/AfgBrgFCAf8B/QG0AUQB/wGcAWgBKwH/AwAB/wGxAYABKAH/
-        Ad4BmwE0Af8DOAFcCAADuAH/Az4B/wMSARkMAAOlAf8DTgGZDAADuAH/AzcB/wMSARkIAAO4Af8DPgH/
-        AxIBGQwAA6UB/wNOAZkMAAO4Af8DNwH/AxIBGQwAA1UBsgGoAbEC/wGJAZMC/wFwAYMC/wFnAYAB/AH/
-        AVwBaAH2Af8BVgFiAfYB/wFVAWEB9QH/AVEBXgH7Af8BNAE+AcIB/wE1AUMB4AH/A1sBxhAAA1UBsgH/
-        AdkBqAL/Ac4BiQL/AcgBcAH/AfwBwQFnAf8B9gG5AVwB/wH2AbcBVgH/AfUBtgFVAf8B+wG4AVEB/wHC
-        AYsBNAH/AeABnAE1Af8DWwHGCAADMAFMAR4BLQGyAf8BFAEhAXAB/wMwAUwDEgEZAxIBGQMSARkDuAH/
-        A04BmQMSARkDEgEZAzABTAEeAS0BsgH/ARQBIQFwAf8DMAFMAxIBGQMwAUwBsgFnAR4B/wFwAUEBFAH/
-        AzABTAMSARkDEgEZAxIBGQO4Af8DTgGZAxIBGQMSARkDMAFMAbIBZwEeAf8BcAFBARQB/wMwAUwDEgEZ
-        DAADVgGxAZ8BqQL/AZcBogL/AYsBlQL/AWsBggL/AVkBaAH9Af8BTwFcAfkB/wFDAVIB9wH/ATwBTAH6
-        Af8DWgG9GAADVgGxAf8B1QGfAv8B0QGXAv8BzwGLAv8BwwFrAf8B/QG6AVkB/wH5AbYBTwH/AfcBrwFD
-        Af8B+gGtATwB/wNaAb0MAAEnAT8B3wH/ASUBOQHYAf8BGgEmAZ0B/wEUASEBcAH/Az4B/wM+Af8DPgH/
-        A7gB/wM+Af8DPgH/Az4B/wEnAT8B3wH/ASUBOQHYAf8BGgEmAZ0B/wEUASEBcAH/AxIBGQHfAYcBJwH/
-        AdgBhgElAf8BnQFbARoB/wFwAUEBFAH/Az4B/wM+Af8DPgH/A7gB/wM+Af8DPgH/Az4B/wHfAYcBJwH/
-        AdgBhgElAf8BnQFbARoB/wFwAUEBFAH/AxIBGRAAAzgBXANeAdgBcgGHAv8BgwGNAv8BawGDAv8BVgFk
-        Av8DXgHYAzgBXCAAAzgBXANeAdgB/wHHAXIC/wHMAYMC/wHCAWsC/wG8AVYB/wNeAdgDOAFcEAABYwGB
-        Af0B/wHsAeUC/wElATkB2AH/AR4BLQGyAf8DuAH/A7gB/wO4Af8DuAH/A7gB/wOmAf8DmAH/AWMBgQH9
-        Af8B7AHlAv8BJQE5AdgB/wEeAS0BsgH/AxIBGQH9AbQBYwL/Af0B5QH/AdgBhgElAf8BsgFnAR4B/wO4
-        Af8DuAH/A7gB/wO4Af8DuAH/A6YB/wOYAf8B/QG0AWMC/wH9AeUB/wHYAYYBJQH/AbIBZwEeAf8DEgEZ
-        HAADIwEzAyMBMzgAAyMBMwMjATMcAAMwAUwBYwGBAf0B/wEnAT8B3wH/AzABTBwAAzABTAFjAYEB/QH/
-        AScBPwHfAf8DMAFMBAADMAFMAf0BtAFjAf8B3wGHAScB/wMwAUwcAAMwAUwB/QG0AWMB/wHfAYcBJwH/
-        AzABTAQAAUIBTQE+BwABPgMAASgDAAFAAwABIAMAAQEBAAEBBgABARYAA/8BAAGPAf8BjwH/BAABBwH/
-        AQcB/wQAAQcB/wEHAf8EAAEHAf8BBwH/BAABAwH/AQMB/wQAAcEBjwHBAY8EAAHgAQ8B4AEPBAAB8AEf
-        AfABHwQAAfgBPwH4AT8EAAH8AR8B/AEfBAAB/gEPAf4BDwQAAf8BAQH/AQEEAAH/AYAB/wGABAAB/wHA
-        Af8BwAQAAf8B4AH/AeAEAAH/AeEB/wHhBAAB+AEHAfgBBwGPAfEBjwHxAfABAwHwAQMEAAHgAQEB4AEB
-        BAABwAEAAcAFAAGAAQABgAEAAQ4BYQEOAWEBgAEAAYABAAGOAXEBjgFxAYABAAGAAQABjwHxAY8B8QQA
-        AYcBwQGHAcEEAAGHAcEBhwHBAYABAAGAAQABjwHxAY8B8QGAAQABgAEAAY8B8QGPAfEBgAEBAYABAQGO
-        AXEBjgFxAcABAwHAAQMEAAHgAQcB4AEHBAAB8AEPAfABDwQAAf4BfwH+AX8BDwHhAQ8B4Qs=
+        AxIBGQgAAxIBGQMSARkDEgEZIAADEgEZAxIBGQMSARkUAAM4AVwDXwHoARsBIwGBAf8BFQEcAWkB/wEU
+        ARsBZgH/ARMBGgFfAf8CWgFgAeQDOAFcAycBOwMSARgYAAM4AVwDXwHoAYEBTgEbAf8BaQFGARUB/wFm
+        AUQBFAH/AV8BPwETAf8BYQJaAeQDOAFcAycBOwMSARgIAAMwAUwBHQEsAbIB/wETASABbwH/AzABTAMS
+        ARkDEgEZAxIBGQMSARkDEgEZAxIBGQMSARkDMAFMAR0BLAGyAf8BEwEgAW8B/wMwAUwDEgEZAzABTAGy
+        AWYBHQH/AW8BQAETAf8DMAFMAxIBGQMSARkDEgEZAxIBGQMSARkDEgEZAxIBGQMwAUwBsgFmAR0B/wFv
+        AUABEwH/AzABTAMSARkMAANaAcUBKwE3AbsB/wEpATUBvAH/ASgBNAG6Af8BJwEzAbcB/wElAS8BtAH/
+        ASMBLgGsAf8BHgEmAZQB/wEUARwBaQH/A1oBvQMnATsDEgEYEAADWgHFAbsBgwErAf8BvAGCASkB/wG6
+        AYEBKAH/AbcBgQEnAf8BtAGBASUB/wGsAWwBIwH/AZQBXgEeAf8BaQFFARQB/wNaAb0DJwE7AxIBGAQA
+        ASYBPgHfAf8BJAE4AdgB/wEZASUBnQH/ARMBIAFvAf8DNgH/AzYB/wM2Af8DNgH/AzYB/wM2Af8DNgH/
+        ASYBPgHfAf8BJAE4AdgB/wEZASUBnQH/ARMBIAFvAf8DEgEZAd8BhwEmAf8B2AGGASQB/wGdAVoBGQH/
+        AW8BQAETAf8DNgH/AzYB/wM2Af8DNgH/AzYB/wM2Af8DNgH/Ad8BhwEmAf8B2AGGASQB/wGdAVoBGQH/
+        AW8BQAETAf8DEgEZCAADWQG+ATABPQHTAf8BNQFEAegB/wE5AUcB8wH/ATgBRwHwAf8BNwFHAe8B/wE3
+        AUcB7wH/ATYBRAHtAf8BMwFCAeIB/wEpATUBuwH/ARkBIAGBAf8DWgG9AycBOwMSARgIAANZAb4B0wGT
+        ATAB/wHoAaEBNQH/AfMBqgE5Af8B8AGnATgB/wHvAaUBNwH/Ae8BpQE3Af8B7QGlATYB/wHiAZwBMwH/
+        AbsBggEpAf8BgQFQARkB/wNaAb0DJwE7AxIBGAFiAYEB/QH/AewB5QL/ASQBOAHYAf8BHQEsAbIB/wO4
+        Af8DuAH/A7gB/wO4Af8DuAH/A6YB/wOYAf8BYgGBAf0B/wHsAeUC/wEkATgB2AH/AR0BLAGyAf8DEgEZ
+        Af0BtAFiAv8B/QHlAf8B2AGGASQB/wGyAWYBHQH/A7gB/wO4Af8DuAH/A7gB/wO4Af8DpgH/A5gB/wH9
+        AbQBYgL/Af0B5QH/AdgBhgEkAf8BsgFmAR0B/wMSARkEAAM4AVwBOQFHAeMB/wE4AUYB8QH/ASgBMwGz
+        Af8BJgExAasB/wE2AUQB6gH/ATsBSwH6Af8BOgFJAfYB/wE4AUYB8QH/ATgBRwHwAf8BNQFDAeoB/wEo
+        ATMBvgH/ARgBHgGBAf8DOAFcAxIBGAQAAzgBXAHjAaABOQH/AfEBqAE4Af8BswGBASgB/wGrAW0BJgH/
+        AeoBowE2Af8B+gGuATsB/wH2AawBOgH/AfEBqAE4Af8B8AGnATgB/wHqAaMBNQH/Ab4BhAEoAf8BgQFP
+        ARgB/wM4AVwDEgEYAzABTAFiAYEB/QH/ASYBPgHfAf8DMAFMDAADuAH/A04BmQgAAzABTAFiAYEB/QH/
+        ASYBPgHfAf8DMAFMBAADMAFMAf0BtAFiAf8B3wGHASYB/wMwAUwMAAO4Af8DTgGZCAADMAFMAf0BtAFi
+        Af8B3wGHASYB/wMwAUwIAANfAdsBSAFWAfYB/wFCAVEC/wEoATMBsQH/AwAB/wEBAQMBHQH/ARYBHQFp
+        Af8BLQE4AcIB/wE4AUgB8QH/ATsBSwH6Af8BOQFJAfQB/wEyAUAB5AH/ASMBLgGsAf8CXAFkAecDHgEr
+        BAADXwHbAfYBsQFIAv8BtAFCAf8BsQFxASgB/wMAAf8BHQESAQEB/wFpAUYBFgH/AcIBiAEtAf8B8QGn
+        ATgB/wH6Aa4BOwH/AfQBqQE5Af8B5AGeATIB/wGsAWwBIwH/AWQBYgFcAecDHgErBAADmAH/Az0B/wMS
+        ARkMAAOlAf8DTgGZDAADmAH/AzYB/wMSARkIAAOYAf8DPQH/AxIBGQwAA6UB/wNOAZkMAAOYAf8DNgH/
+        AxIBGQgAAUUBUwL/AV0BawL/AUwBWQH7Af8BPgFMAegB/wEBAQIBEwH/AwAB/wMAAf8DAAH/AQUBCAEs
+        Af8BGwEjAYQB/wEvATsBzgH/ATcBRgHvAf8BLQE5AcwB/wEcASUBigH/AycBOwQAAf8BtgFFAv8BvgFd
+        Af8B+wG3AUwB/wHoAaUBPgH/ARMBDAEBAf8DAAH/AwAB/wMAAf8BLAEcAQUB/wGEAVIBGwH/Ac4BkAEv
+        Af8B7wGmATcB/wHMAY4BLQH/AYoBVgEcAf8DJwE7BAADpgH/Az0B/wMSARkgAAOmAf8DNgH/AxIBGQgA
+        A6YB/wM9Af8DEgEZIAADpgH/AzYB/wMSARkEAAMjATMBWAFlAv8BbgGEAv8BVgFiAfkB/wFIAVcB/AH/
+        ARkBIQFuAf8DAAH/AwAB/wMAAf8CAAEGAf8BEgEZAVgB/wEiASsBnQH/ATUBQgHkAf8BMgFBAeEB/wEf
+        ASgBnAH/AycBOwMjATMB/wG9AVgC/wHGAW4B/wH5AboBVgH/AfwBtAFIAf8BbgFKARkB/wMAAf8DAAH/
+        AwAB/wEGAQMBAAH/AVgBOgESAf8BnQFkASIB/wHkAaABNQH/AeEBmwEyAf8BnAFiAR8B/wMnATsEAAOw
+        Af8DPQH/A04BmQNOAZkUAANOAZkDTgGZA7AB/wM2Af8DEgEZCAADsAH/Az0B/wNOAZkDTgGZFAADTgGZ
+        A04BmQOwAf8DNgH/AxIBGQQAAyMBMwFiAW8C/wGBAYwC/wFXAWUB+AH/AUYBVgH7Af8BLgE6AcsB/wMA
+        Af8DAAH/AwAB/wELAQ8BPgH/ATsBSgH5Af8BOwFLAfoB/wE4AUcB8AH/ATIBQAHjAf8BIAEqAaIB/wMn
+        ATsDIwEzAf8BwQFiAv8BygGBAf8B+AG4AVcB/wH7AbIBRgH/AcsBjgEuAf8DAAH/AwAB/wMAAf8BPgEp
+        AQsB/wH5Aa4BOwH/AfoBrgE7Af8B8AGnATgB/wHjAZ0BMgH/AaIBZgEgAf8DJwE7BAADuAH/A7gB/wO4
+        Af8DpQH/FAADpQH/A7gB/wO4Af8DNgH/AxIBGQgAA7gB/wO4Af8DuAH/A6UB/xQAA6UB/wO4Af8DuAH/
+        AzYB/wMSARkIAAFgAW8C/wGOAZcC/wFfAWoB/gH/AUgBVwH1Af8BPgFMAfgB/wEKAQ8BPgH/AwAB/wEX
+        AR8BbQH/AwAB/wEOARQBTQH/ATgBRwHwAf8BOQFHAfMB/wEwAT4B2gH/ASIBKwGfAf8DIgExBAAB/wG/
+        AWAC/wHQAY4B/wH+AcEBXwH/AfUBsAFIAf8B+AGvAT4B/wE+ASgBCgH/AwAB/wFtAUgBFwH/AwAB/wFN
+        ATIBDgH/AfABpwE4Af8B8wGqATkB/wHaAZcBMAH/AZ8BZgEiAf8DIgExBAADuAH/Az0B/wMSARkgAAO4
+        Af8DNgH/AxIBGQgAA7gB/wM9Af8DEgEZIAADuAH/AzYB/wMSARkIAANfAdsBlgGgAv8BhgGQAv8BYAFt
+        AfkB/wFLAVkB/gH/ASMBLQGdAf8BCQEOAToB/wE9AU0C/wEhASoBmAH/AwAB/wEOARQBTQH/ATcBRgHt
+        Af8BMAE9AdUB/wNcAeoDDAEQBAADXwHbAf8B0gGWAv8BzAGGAf8B+QG8AWAB/wH+AbgBSwH/AZ0BZAEj
+        Af8BOgElAQkC/wGyAT0B/wGYAWEBIQH/AwAB/wFNATIBDgH/Ae0BpQE3Af8B1QGUATAB/wNcAeoDDAEQ
+        BAADuAH/Az0B/wMSARkgAAO4Af8DNgH/AxIBGQgAA7gB/wM9Af8DEgEZIAADuAH/AzYB/wMSARkIAAM4
+        AVwBmAGiAv8BngGnAv8BaQGBAv8BVQFiAfkB/wFAAUsB5AH/ATEBPAG+Af8BQQFRAfgB/wFDAVEB/QH/
+        ASoBMgGcAf8DAAH/AScBMQGxAf8BMwFAAd4B/wM4AVwIAAM4AVwB/wHTAZgC/wHWAZ4C/wHFAWkB/wH5
+        AbkBVQH/AeQBpQFAAf8BvgGHATEB/wH4Aa4BQQH/Af0BtAFDAf8BnAFnASoB/wMAAf8BsQGAAScB/wHe
+        AZsBMwH/AzgBXAgAA7gB/wM9Af8DEgEZDAADpQH/A04BmQwAA7gB/wM2Af8DEgEZCAADuAH/Az0B/wMS
+        ARkMAAOlAf8DTgGZDAADuAH/AzYB/wMSARkMAANVAbIBqAGxAv8BiQGTAv8BbwGDAv8BZgGAAfwB/wFb
+        AWcB9gH/AVUBYQH2Af8BVAFgAfUB/wFQAV0B+wH/ATMBPQHCAf8BNAFCAeAB/wNbAcYQAANVAbIB/wHZ
+        AagC/wHOAYkC/wHIAW8B/wH8AcEBZgH/AfYBuQFbAf8B9gG3AVUB/wH1AbYBVAH/AfsBuAFQAf8BwgGL
+        ATMB/wHgAZwBNAH/A1sBxggAAzABTAEdASwBsgH/ARMBIAFvAf8DMAFMAxIBGQMSARkDEgEZA7gB/wNO
+        AZkDEgEZAxIBGQMwAUwBHQEsAbIB/wETASABbwH/AzABTAMSARkDMAFMAbIBZgEdAf8BbwFAARMB/wMw
+        AUwDEgEZAxIBGQMSARkDuAH/A04BmQMSARkDEgEZAzABTAGyAWYBHQH/AW8BQAETAf8DMAFMAxIBGQwA
+        A1YBsQGfAakC/wGXAaIC/wGLAZUC/wFqAYIC/wFYAWcB/QH/AU4BWwH5Af8BQgFRAfcB/wE7AUsB+gH/
+        A1oBvRgAA1YBsQH/AdUBnwL/AdEBlwL/Ac8BiwL/AcMBagH/Af0BugFYAf8B+QG2AU4B/wH3Aa8BQgH/
+        AfoBrQE7Af8DWgG9DAABJgE+Ad8B/wEkATgB2AH/ARkBJQGdAf8BEwEgAW8B/wM9Af8DPQH/Az0B/wO4
+        Af8DPQH/Az0B/wM9Af8BJgE+Ad8B/wEkATgB2AH/ARkBJQGdAf8BEwEgAW8B/wMSARkB3wGHASYB/wHY
+        AYYBJAH/AZ0BWgEZAf8BbwFAARMB/wM9Af8DPQH/Az0B/wO4Af8DPQH/Az0B/wM9Af8B3wGHASYB/wHY
+        AYYBJAH/AZ0BWgEZAf8BbwFAARMB/wMSARkQAAM4AVwDXgHYAXEBhwL/AYMBjQL/AWoBgwL/AVUBYwL/
+        A14B2AM4AVwgAAM4AVwDXgHYAf8BxwFxAv8BzAGDAv8BwgFqAv8BvAFVAf8DXgHYAzgBXBAAAWIBgQH9
+        Af8B7AHlAv8BJAE4AdgB/wEdASwBsgH/A7gB/wO4Af8DuAH/A7gB/wO4Af8DpgH/A5gB/wFiAYEB/QH/
+        AewB5QL/ASQBOAHYAf8BHQEsAbIB/wMSARkB/QG0AWIC/wH9AeUB/wHYAYYBJAH/AbIBZgEdAf8DuAH/
+        A7gB/wO4Af8DuAH/A7gB/wOmAf8DmAH/Af0BtAFiAv8B/QHlAf8B2AGGASQB/wGyAWYBHQH/AxIBGRwA
+        AyMBMwMjATM4AAMjATMDIwEzHAADMAFMAWIBgQH9Af8BJgE+Ad8B/wMwAUwcAAMwAUwBYgGBAf0B/wEm
+        AT4B3wH/AzABTAQAAzABTAH9AbQBYgH/Ad8BhwEmAf8DMAFMHAADMAFMAf0BtAFiAf8B3wGHASYB/wMw
+        AUwEAAFCAU0BPgcAAT4DAAEoAwABQAMAASADAAEBAQABAQYAAQEWAAP/AQABjwH/AY8B/wQAAQcB/wEH
+        Af8EAAEHAf8BBwH/BAABBwH/AQcB/wQAAQMB/wEDAf8EAAHBAY8BwQGPBAAB4AEPAeABDwQAAfABHwHw
+        AR8EAAH4AT8B+AE/BAAB/AEfAfwBHwQAAf4BDwH+AQ8EAAH/AQEB/wEBBAAB/wGAAf8BgAQAAf8BwAH/
+        AcAEAAH/AeAB/wHgBAAB/wHhAf8B4QQAAfgBBwH4AQcBjwHxAY8B8QHwAQMB8AEDBAAB4AEBAeABAQQA
+        AcABAAHABQABgAEAAYABAAEOAWEBDgFhAYABAAGAAQABjgFxAY4BcQGAAQABgAEAAY8B8QGPAfEEAAGH
+        AcEBhwHBBAABhwHBAYcBwQGAAQABgAEAAY8B8QGPAfEBgAEAAYABAAGPAfEBjwHxAYABAQGAAQEBjgFx
+        AY4BcQHAAQMBwAEDBAAB4AEHAeABBwQAAfABDwHwAQ8EAAH+AX8B/gF/AQ8B4QEPAeEL
 </value>
   </data>
-  <metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>122, 17</value>
   </metadata>
-  <metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
-    <value>122, 17</value>
-  </metadata>
-  <metadata name="updatetimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="updatetimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>219, 17</value>
   </metadata>
-  <metadata name="saveFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+  <metadata name="saveFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>338, 17</value>
   </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Plugins/TagRange/BuilderPlug.cs b/Source/Plugins/TagRange/BuilderPlug.cs
index 3348a8f5407fd1a908d31c8dda0003f1ca090581..a68bcb299695be26b7988502c9420e7de08babfc 100755
--- a/Source/Plugins/TagRange/BuilderPlug.cs
+++ b/Source/Plugins/TagRange/BuilderPlug.cs
@@ -16,10 +16,12 @@
 
 #region ================== Namespaces
 
+using System.Windows.Forms;
 using CodeImp.DoomBuilder.Windows;
 using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Plugins;
 using CodeImp.DoomBuilder.Actions;
+using CodeImp.DoomBuilder.BuilderModes;
 
 #endregion
 
@@ -77,8 +79,22 @@ namespace CodeImp.DoomBuilder.TagRange
 		{
 			TagRangeForm f = new TagRangeForm();
 			f.Setup();
-			if(f.SelectionCount > 0)
-				f.ShowDialog(General.Interface);
+			if (f.SelectionCount > 0)
+			{
+				if (f.ShowDialog(General.Interface) == DialogResult.OK)
+				{
+					if (General.Editing.Mode is BaseClassicMode mode)
+					{
+						// Bit of a hack to make sectors mode update the sector labels, otherwise the new tags will not be
+						// displayed when selection numbering is disabled.
+						// See https://github.com/jewalky/UltimateDoomBuilder/issues/795
+						mode.OnViewSelectionNumbersChanged(BuilderModes.BuilderPlug.Me.ViewSelectionNumbers);
+
+						// Redraw to make the updated labels to show up
+						General.Interface.RedrawDisplay();
+					}
+				}
+			}
 			else
 				General.Interface.DisplayStatus(StatusType.Warning, "This action requires a selection!"); //mxd
 			f.Dispose();
diff --git a/Source/Plugins/TagRange/TagRange.csproj b/Source/Plugins/TagRange/TagRange.csproj
index 7bfc9844f5868f29c9331d73cc404bee99c63a19..4b78d55a2bc5c3883075308a86bc076052adf13b 100755
--- a/Source/Plugins/TagRange/TagRange.csproj
+++ b/Source/Plugins/TagRange/TagRange.csproj
@@ -154,6 +154,10 @@
       <Name>Builder</Name>
       <Private>False</Private>
     </ProjectReference>
+    <ProjectReference Include="..\BuilderModes\BuilderModes.csproj">
+      <Project>{b42d5aa0-f9a6-4234-9c4b-a05b11a64851}</Project>
+      <Name>BuilderModes</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
diff --git a/Source/Plugins/TagRange/TagRangeForm.cs b/Source/Plugins/TagRange/TagRangeForm.cs
index 614248ed105c228aa320eec0508627383733fa64..9601936af1fca0feb748fa69d1ba7c9fae1caeb4 100755
--- a/Source/Plugins/TagRange/TagRangeForm.cs
+++ b/Source/Plugins/TagRange/TagRangeForm.cs
@@ -244,6 +244,8 @@ namespace CodeImp.DoomBuilder.TagRange
 				storedstep = rangestep.GetResult(1);
 				storedrelative = relativemode.Checked;
 
+				DialogResult = DialogResult.OK;
+
 				//We are done here.
 				this.Close();
 			}
diff --git a/Source/Plugins/TagRange/TagRangeMono.csproj b/Source/Plugins/TagRange/TagRangeMono.csproj
index ee8b2b16ad2ef0f476fd2bdaf572e84c08890896..5c5240a6fd5bf7fb61be0d44d7d0a870d9e79953 100644
--- a/Source/Plugins/TagRange/TagRangeMono.csproj
+++ b/Source/Plugins/TagRange/TagRangeMono.csproj
@@ -152,6 +152,10 @@
       <Name>Builder</Name>
       <Private>False</Private>
     </ProjectReference>
+    <ProjectReference Include="..\BuilderModes\BuilderModesMono.csproj">
+      <Project>{b42d5aa0-f9a6-4234-9c4b-a05b11a64851}</Project>
+      <Name>BuilderModesMono</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
diff --git a/Source/Plugins/TagRange/ToolsForm.Designer.cs b/Source/Plugins/TagRange/ToolsForm.Designer.cs
index be47a2d9eef5e7797161de5c2170cb9e4a860594..65034911d850066fbacdceab1fb92e491bb4b91a 100755
--- a/Source/Plugins/TagRange/ToolsForm.Designer.cs
+++ b/Source/Plugins/TagRange/ToolsForm.Designer.cs
@@ -30,7 +30,7 @@ namespace CodeImp.DoomBuilder.TagRange
 		{
 			this.toolstrip = new System.Windows.Forms.ToolStrip();
 			this.seperator1 = new System.Windows.Forms.ToolStripSeparator();
-			this.tagrangebutton = new System.Windows.Forms.ToolStripButton();
+			this.tagrangebutton = new CodeImp.DoomBuilder.Controls.ToolStripActionButton();
 			this.seperator2 = new System.Windows.Forms.ToolStripSeparator();
 			this.toolstrip.SuspendLayout();
 			this.SuspendLayout();
@@ -91,7 +91,7 @@ namespace CodeImp.DoomBuilder.TagRange
 		#endregion
 
 		private System.Windows.Forms.ToolStrip toolstrip;
-		private System.Windows.Forms.ToolStripButton tagrangebutton;
+		private CodeImp.DoomBuilder.Controls.ToolStripActionButton tagrangebutton;
 		private System.Windows.Forms.ToolStripSeparator seperator2;
 		private System.Windows.Forms.ToolStripSeparator seperator1;
 	}
diff --git a/Source/Plugins/UDBScript/API/Angle2DWrapper.cs b/Source/Plugins/UDBScript/API/Angle2DWrapper.cs
index 4dfd26e4f93644872226556ffd4f259690e63b23..c87f842bb824edcd6699c672f21f05475ffdece3 100644
--- a/Source/Plugins/UDBScript/API/Angle2DWrapper.cs
+++ b/Source/Plugins/UDBScript/API/Angle2DWrapper.cs
@@ -31,6 +31,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 {
 	internal struct Angle2DWrapper
 	{
+		#region ================== Methods
+
 		/// <summary>
 		/// Converts a Doom angle (where 0° is east) to a real world angle (where 0° is north).
 		/// </summary>
@@ -62,7 +64,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		}
 
 		/// <summary>
-		/// Converts a real world  angle (where 0° is north) to a Doom angle (where 0° is east) in radians.
+		/// Converts a real world angle (where 0° is north) to a Doom angle (where 0° is east) in radians.
 		/// </summary>
 		/// <param name="realangle">Real world angle in radians</param>
 		/// <returns>Doom angle in degrees</returns>
@@ -124,9 +126,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p1, false);
-				Vector2D v2 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p2, false);
-				Vector2D v3 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p3, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(p1);
+				Vector2D v2 = BuilderPlug.Me.GetVector3DFromObject(p2);
+				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(p3);
 
 				return Angle2D.RadToDeg(Angle2D.GetAngle(v1, v2, v3));
 			}
@@ -147,9 +149,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p1, false);
-				Vector2D v2 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p2, false);
-				Vector2D v3 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p3, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(p1);
+				Vector2D v2 = BuilderPlug.Me.GetVector3DFromObject(p2);
+				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(p3);
 
 				return Angle2D.GetAngle(v1, v2, v3);
 			}
@@ -158,5 +160,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
 			}
 		}
+
+		#endregion
 	}
 }
diff --git a/Source/Plugins/UDBScript/API/BlockEntryWrapper.cs b/Source/Plugins/UDBScript/API/BlockEntryWrapper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..edc4f9cfe134f71932f2838bc1a81db6a94334ac
--- /dev/null
+++ b/Source/Plugins/UDBScript/API/BlockEntryWrapper.cs
@@ -0,0 +1,96 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.UDBScript.Wrapper
+{
+	/// <summary>
+	/// A `BlockEntry` is a single block in a `BlockMap`. It has methods to retrieve the linedefs, things, sectors, and vertices that are in this block.
+	/// </summary>
+	class BlockEntryWrapper : BlockMapContentBase
+	{
+		#region ================== Variables
+
+		private BlockEntry entry;
+
+		#endregion
+
+		#region ================== Constructors
+
+		internal BlockEntryWrapper(BlockEntry entry)
+		{
+			this.entry = entry;
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Gets all `Linedef`s in the blockmap entry.
+		/// </summary>
+		/// <returns>`Array` of `Linedef`s</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override LinedefWrapper[] getLinedefs()
+		{
+			return GetArray(entry.Lines, ref wrappedlines);
+		}
+
+		/// <summary>
+		/// Gets all `Thing`s in the blockmap entry.
+		/// </summary>
+		/// <returns>`Array` of `Thing`s</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override ThingWrapper[] getThings()
+		{
+			return GetArray(entry.Things, ref wrappedthings);
+		}
+
+		/// <summary>
+		/// Gets all `Sector`s in the blockmap entry.
+		/// </summary>
+		/// <returns>`Array` of `Sector`s</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override SectorWrapper[] getSectors()
+		{
+			return GetArray(entry.Sectors, ref wrappedsectors);
+		}
+
+		/// <summary>
+		/// Gets all `Vertex` in the blockmap entry.
+		/// </summary>
+		/// <returns>`Array` of `Vertex`</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override VertexWrapper[] getVertices()
+		{
+			return GetArray(entry.Vertices, ref wrappedvertices);
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Plugins/UDBScript/API/BlockMapContentBase.cs b/Source/Plugins/UDBScript/API/BlockMapContentBase.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fbde98395594903dd35f99bfeb59a68484f4a360
--- /dev/null
+++ b/Source/Plugins/UDBScript/API/BlockMapContentBase.cs
@@ -0,0 +1,76 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.UDBScript.Wrapper
+{
+	abstract class BlockMapContentBase
+	{
+		#region ================== Variables
+
+		protected LinedefWrapper[] wrappedlines;
+		protected ThingWrapper[] wrappedthings;
+		protected SectorWrapper[] wrappedsectors;
+		protected VertexWrapper[] wrappedvertices;
+
+		#endregion
+
+		#region ================== Methods
+
+		abstract public LinedefWrapper[] getLinedefs();
+		abstract public ThingWrapper[] getThings();
+		abstract public SectorWrapper[] getSectors();
+		abstract public VertexWrapper[] getVertices();
+
+		/// <summary>
+		/// Fills the container array with wrapped instances of the map elements in the given list.
+		/// </summary>
+		/// <typeparam name="W">Wrapped map element type</typeparam>
+		/// <typeparam name="T">Regular map element type</typeparam>
+		/// <param name="list">List of regular map elements</param>
+		/// <param name="container">Array of wrapped map elements</param>
+		/// <returns></returns>
+		protected internal W[] GetArray<W, T>(IEnumerable<T> list, ref W[] container)
+		{
+			if (container == null)
+			{
+				if (list == null)
+					container = new W[0];
+				else
+					container = list.Select(s => (W)Activator.CreateInstance(typeof(W), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { s }, null)).ToArray();
+			}
+
+			return container;
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Plugins/UDBScript/API/BlockMapQueryResult.cs b/Source/Plugins/UDBScript/API/BlockMapQueryResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c088c32cfec20ca01a03c7535aa66d35bb25ad8d
--- /dev/null
+++ b/Source/Plugins/UDBScript/API/BlockMapQueryResult.cs
@@ -0,0 +1,145 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Collections;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.UDBScript.Wrapper
+{
+	/// <summary>
+	/// A `BlockMapQueryResult` is an object returned by the `getLineBlocks` and `getRectangleBlocks` methods of the `BlockMap` class. It has methods It has methods to retrieve the linedefs, things, sectors, and vertices that are in the queried blocks. The object is also iterable, returning each block, in cases where more fine-grained control is needed.
+	/// ```
+	/// const blockmap = new UDB.BlockMap();
+	/// const result = blockmap.getLineBlocks([ 0, 0 ], [ 512, 256 ]);
+	/// 
+	/// // Print all linedefs in the blocks
+	/// result.getLinedefs().forEach(ld => UDB.log(ld));
+	/// ```
+	/// Looping over each block:
+	/// ```
+	/// const blockmap = new UDB.BlockMap();
+	/// const result = blockmap.getLineBlocks([ 0, 0 ], [ 512, 256 ]);
+	/// 
+	/// for(const block of result)
+	/// {
+	///		UDB.log('--- New block ---');
+	///		block.getLinedefs().forEach(ld => UDB.log(ld));
+	/// }
+	/// ```
+	/// !!! note
+	///     The methods to retrieve map elements from `BlockMapQueryResult` return arrays that only contain each map element once, since linedefs and sectors can be in multiple blocks, looping over a `BlockMapQueryResult` using `for...of` can return the same map elements multiple times.
+	/// </summary>
+	class BlockMapQueryResult : BlockMapContentBase, IEnumerable<BlockEntryWrapper>
+	{
+		#region ================== Variables
+
+		private BlockEntryWrapper[] wrappedentries;
+		private IEnumerable<BlockEntry> entries;
+		private HashSet<Linedef> lines;
+		private HashSet<Thing> things;
+		private HashSet<Sector> sectors;
+		private HashSet<Vertex> vertices;
+
+		#endregion
+
+		#region ================== Constructors
+
+		internal BlockMapQueryResult(IEnumerable<BlockEntry> entries)
+		{
+			this.entries = entries;
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Gets all `Linedef`s in the blockmap query result.
+		/// </summary>
+		/// <returns>`Array` of `Linedef`s</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override LinedefWrapper[] getLinedefs()
+		{
+			if (lines == null)
+				lines = new HashSet<Linedef>(entries.SelectMany(be => be.Lines));
+
+			return GetArray(lines, ref wrappedlines);
+		}
+
+		/// <summary>
+		/// Gets all `Thing`s in the blockmap query result.
+		/// </summary>
+		/// <returns>`Array` of `Thing`s</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override ThingWrapper[] getThings()
+		{
+			if (things == null)
+				things = new HashSet<Thing>(entries.SelectMany(be => be.Things));
+
+			return GetArray(things, ref wrappedthings);
+		}
+
+
+		/// <summary>
+		/// Gets all `Sector`s in the blockmap query result.
+		/// </summary>
+		/// <returns>`Array` of `Sector`s</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override SectorWrapper[] getSectors()
+		{
+			if (sectors == null)
+				sectors = new HashSet<Sector>(entries.SelectMany(be => be.Sectors));
+
+			return GetArray(sectors, ref wrappedsectors);
+		}
+
+		/// <summary>
+		/// Gets all `Vertex` in the blockmap query result.
+		/// </summary>
+		/// <returns>`Array` of `Vertex`</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public override VertexWrapper[] getVertices()
+		{
+			if (vertices == null)
+				vertices = new HashSet<Vertex>(entries.SelectMany(be => be.Vertices));
+
+			return GetArray(vertices, ref wrappedvertices);
+		}
+
+		#endregion
+
+		#region ================== Enumeration
+
+		public IEnumerator<BlockEntryWrapper> GetEnumerator() => ((IEnumerable<BlockEntryWrapper>)GetArray(entries, ref wrappedentries)).GetEnumerator();
+
+		IEnumerator IEnumerable.GetEnumerator() => GetArray(entries, ref wrappedentries).GetEnumerator();
+
+		#endregion
+	}
+}
diff --git a/Source/Plugins/UDBScript/API/BlockMapWrapper.cs b/Source/Plugins/UDBScript/API/BlockMapWrapper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0d8d095b61959942677ba168ddff9c3399add102
--- /dev/null
+++ b/Source/Plugins/UDBScript/API/BlockMapWrapper.cs
@@ -0,0 +1,228 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using System.Drawing;
+using System.Dynamic;
+using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.UDBScript.Wrapper
+{
+	/// <summary>
+	/// A blockmap is used to retrieve a collection of localized map elements (things, linedefs, sectors, vertices). It can help to significantly speed up costly computations that would otherwise be applied to a large portion of the map elements. The blockmap divides the map into rectangular blocks and computes which map elements are fully or partially in each block. Then you can query the blockmap about only some of those blocks, and perform any further actions only on the map elements that are in those blocks.
+	/// 
+	/// If you for example wanted to find out which sector is at the (0, 0) position you could write something like this without using a blockmap:
+	/// ```
+	/// UDB.Map.getSectors().findIndex((s, i) => {
+	///		if(s.intersect([ 0, 0 ]))
+	///		{
+	///			UDB.log(`Found ${s} after ${i} tries.`)
+	///			return true;
+	///		}
+	/// });
+	/// ```
+	/// This loops through all sectors of the map and uses the `intersect` method to test if the point is inside the sector. While `intersect` is quite fast on its own, doing it potentially thousands of times adds up quickly, especially if you have to loop through all sectors multiple times.
+	/// A pretty extreme example for this is the map Bastion of Chaos. The map contains nearly 32500 sectors, and the sector at (0, 0) is number 25499. That means that the above script has to run `intersect` on 25499 sectors, even on those that are not remotely near the (0, 0) position.
+	/// 
+	/// Using a blockmap the code could look like this:
+	/// ```
+	/// const blockmap = new UDB.BlockMap();
+	/// 
+	/// blockmap.getBlockAt([ 0, 0 ]).getSectors().findIndex((s, i) => {
+	///		if (s.intersect([0, 0]))
+	///		{
+	///			UDB.log(`Found ${s} after ${i} tries.`)
+	///			return true;
+	///		}
+	/// });
+	/// ```
+	/// As you can see the code is quite similar, the difference being that a blockmap is created, and `UDB.Map` is replaced by `blockmap.getBlockAt([ 0, 0 ])`, the latter only getting a single block from the blockmap, that only contains the map elements that are in this block. Taking Bastion of Chaos as an example again, this code finds the sector after only 20 checks, instead of the 25499 checks in the first code example.
+	/// 
+	/// !!! note
+	///     Creating a blockmap has a small overhead, since it has to compute which map elements are in which blocks. This overhead, however, is quickly compensated by the time saved by not looping through irrelevant map elements. You can decrease this overhead by using a `BlockMap` constructor that only adds certain map element types to the blockmap.
+	/// 
+	/// </summary>
+	class BlockMapWrapper
+	{
+		#region ================== Variables
+
+		private BlockMap<BlockEntry> blockmap;
+		private Dictionary<BlockEntry, BlockEntryWrapper> blockentries;
+
+		#endregion
+
+		#region ================== Constructors
+
+		/// <summary>
+		/// Creates a blockmap that includes linedefs, things, sectors, and vertices.
+		/// ```
+		/// // Create a blockmap that includes all linedefs, things, sectors, and vertices
+		/// const blockmap = new UDB.BlockMap();
+		/// ```
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public BlockMapWrapper()
+		{
+			CreateBlockmap(true, true, true, true);
+		}
+
+		/// <summary>
+		/// Creates a blockmap that only includes certain map element types.
+		/// ```
+		/// // Create a blockmap that only includes sectors
+		/// const blockmap = new UDB.BlockMap(false, false, true, false);
+		/// ```
+		/// </summary>
+		/// <param name="lines">If linedefs should be added or not</param>
+		/// <param name="things">If thigs should be added or not</param>
+		/// <param name="sectors">If sectors should be added or not</param>
+		/// <param name="vertices">If vertices should be added or not</param>
+		/// [UDBScriptSettings(MinVersion = 5)]
+		public BlockMapWrapper(bool lines, bool things, bool sectors, bool vertices)
+		{
+			CreateBlockmap(lines, things, sectors, vertices);
+		}
+
+		internal BlockMapWrapper(ExpandoObject options)
+		{
+			bool lines = IsOptionSet(options, "lines");
+			bool things = IsOptionSet(options, "things");
+			bool sectors = IsOptionSet(options, "sectors");
+			bool vertices = IsOptionSet(options, "vertices");
+
+			CreateBlockmap(lines, things, sectors, vertices);
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Generates the blockmap and adds the wanted map elements.
+		/// </summary>
+		/// <param name="lines">If linedefs should be added or not</param>
+		/// <param name="things">If thigs should be added or not</param>
+		/// <param name="sectors">If sectors should be added or not</param>
+		/// <param name="vertices">If vertices should be added or not</param>
+		[UDBScriptSettings(MinVersion = 5)]
+		private void CreateBlockmap(bool lines, bool things, bool sectors, bool vertices)
+		{
+			RectangleF area = MapSet.CreateArea(General.Map.Map.Vertices);
+
+			if (things)
+				area = MapSet.IncreaseArea(area, General.Map.Map.Things);
+
+			blockmap = new BlockMap<BlockEntry>(area);
+
+			if (lines)
+				blockmap.AddLinedefsSet(General.Map.Map.Linedefs);
+
+			if (things)
+				blockmap.AddThingsSet(General.Map.Map.Things);
+
+			if (sectors)
+				blockmap.AddSectorsSet(General.Map.Map.Sectors);
+
+			if (vertices)
+				blockmap.AddVerticesSet(General.Map.Map.Vertices);
+
+			blockentries = new Dictionary<BlockEntry, BlockEntryWrapper>();
+		}
+
+		/// <summary>
+		/// Checks if a dictionary contains a given key and if it's set to true or false.
+		/// </summary>
+		/// <param name="options">The dictionary to check</param>
+		/// <param name="name">Name of the option to check</param>
+		/// <returns>true if the option exists and is set to true, false if the option doesn't exist or is set to false</returns>
+		private bool IsOptionSet(IDictionary<string, object> options, string name)
+		{
+			return options != null && options.ContainsKey(name) && options[name] is bool value && value == true;
+		}
+
+		/// <summary>
+		/// Gets the `BlockEntry` at a point. The given point can be a `Vector2D` or an `Array` of two numbers.
+		/// ```
+		/// const blockmap = new UDB.BlockMap();
+		/// const blockentry = blockmap.getBlockAt([ 64, 128 ]);
+		/// ```
+		/// </summary>
+		/// <param name="pos">The point to get the `BlockEntry` of</param>
+		/// <returns>The `BlockEntry` on the given point</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public BlockEntryWrapper getBlockAt(object pos)
+		{
+			Vector2D p = BuilderPlug.Me.GetVector3DFromObject(pos);
+			BlockEntry be = blockmap.GetBlockAt(p);
+
+			if (!blockentries.ContainsKey(be))
+				blockentries[be] = new BlockEntryWrapper(be);
+
+			return blockentries[be];
+		}
+
+		/// <summary>
+		/// Gets a `BlockMapQueryResult` for the blockmap along a line between two points. The given points can be `Vector2D`s or an `Array`s of two numbers.
+		/// ```
+		/// const blockmap = new UDB.BlockMap();
+		/// const result = blockmap.getLineBlocks([ 0, 0 ], [ 512, 256 ]);
+		/// ```
+		/// </summary>
+		/// <param name="v1">The first point</param>
+		/// <param name="v2">The second point</param>
+		/// <returns>The `BlockMapQueryResult` for the line between the two points</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public BlockMapQueryResult getLineBlocks(object v1, object v2)
+		{
+			Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(v1);
+			Vector2D p2 = BuilderPlug.Me.GetVector3DFromObject(v2);
+
+			return new BlockMapQueryResult(blockmap.GetLineBlocks(p1, p2));
+		}
+
+		/// <summary>
+		/// Gets a `BlockMapQueryResult` for the blockmap in a rectangle.
+		/// ```
+		/// const blockmap = new UDB.BlockMap();
+		/// const result = blockmap.getRectangleBlocks(0, 0, 512, 256);
+		/// ```
+		/// </summary>
+		/// <param name="x">X position of the top-left corner of the rectangle</param>
+		/// <param name="y">Y position of the top-left corner of the rectangle</param>
+		/// <param name="width">Width of the rectangle</param>
+		/// <param name="height">Height of the rectangle</param>
+		/// <returns></returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public BlockMapQueryResult getRectangleBlocks(int x, int y, int width, int height)
+		{
+			return new BlockMapQueryResult(blockmap.GetSquareRange(new RectangleF(x, y, width, height)));
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Plugins/UDBScript/API/DataWrapper.cs b/Source/Plugins/UDBScript/API/DataWrapper.cs
index 4fd3af981fea65c8943d9e74fbd1459d696503a6..ab0d1fa85a01c9e0dcd0de0118b49bcc5bdff82b 100644
--- a/Source/Plugins/UDBScript/API/DataWrapper.cs
+++ b/Source/Plugins/UDBScript/API/DataWrapper.cs
@@ -38,11 +38,19 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 {
 	class DataWrapper
 	{
+		#region ================== Constructors
+
+		internal DataWrapper() { }
+
+		#endregion
+
+		#region ================== Methods
+
 		/// <summary>
 		/// Returns an `Array` of all texture names.
 		/// </summary>
 		/// <returns>`Array` of all texture names</returns>
-		public static string[] getTextureNames()
+		public string[] getTextureNames()
 		{
 			return General.Map.Data.TextureNames.ToArray();
 		}
@@ -52,7 +60,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// </summary>
 		/// <param name="name">Texture name to check</param>
 		/// <returns>`true` if the texture exists, `false` if it doesn't</returns>
-		public static bool textureExists(string name)
+		public bool textureExists(string name)
 		{
 			return General.Map.Data.GetTextureExists(name);
 		}
@@ -62,7 +70,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// </summary>
 		/// <param name="name">Texture name to get the info for</param>
 		/// <returns>`ImageInfo` object containing information about the texture</returns>
-		public static ImageInfo getTextureInfo(string name)
+		public ImageInfo getTextureInfo(string name)
 		{
 			return new ImageInfo(General.Map.Data.GetTextureImage(name));
 		}
@@ -71,7 +79,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// Returns an `Array`of all flat names.
 		/// </summary>
 		/// <returns>`Array` of all flat names</returns>
-		public static string[] getFlatNames()
+		public string[] getFlatNames()
 		{
 			return General.Map.Data.FlatNames.ToArray();
 		}
@@ -81,7 +89,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// </summary>
 		/// <param name="name">Flat name to check</param>
 		/// <returns>`true` if the flat exists, `false` if it doesn't</returns>
-		public static bool flatExists(string name)
+		public bool flatExists(string name)
 		{
 			return General.Map.Data.GetFlatExists(name);
 		}
@@ -91,9 +99,11 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// </summary>
 		/// <param name="name">Flat name to get the info for</param>
 		/// <returns>`ImageInfo` object containing information about the flat</returns>
-		public static ImageInfo getFlatInfo(string name)
+		public ImageInfo getFlatInfo(string name)
 		{
 			return new ImageInfo(General.Map.Data.GetFlatImage(name));
 		}
+
+		#endregion
 	}
 }
diff --git a/Source/Plugins/UDBScript/API/ImageInfo.cs b/Source/Plugins/UDBScript/API/ImageInfo.cs
index a96660762cd502333aea56097a84b93ff4819548..2d970a419b18dc644775bbad4e03d6a826b6a5a1 100644
--- a/Source/Plugins/UDBScript/API/ImageInfo.cs
+++ b/Source/Plugins/UDBScript/API/ImageInfo.cs
@@ -96,7 +96,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			_width = image.Width;
 			_height = image.Height;
 			_scale = new Vector2DWrapper(image.Scale);
-			_isflat = image.IsFlat;
+			_isflat = image.TextureNamespace == TextureNamespace.FLAT;
 		}
 
 		#endregion
diff --git a/Source/Plugins/UDBScript/API/Line2DWrapper.cs b/Source/Plugins/UDBScript/API/Line2DWrapper.cs
index 98c786afb2151919dfe5a88830b82f0a222d577e..c8fd8eab6d610b7928c362b5e6d1bb500dc76e6a 100644
--- a/Source/Plugins/UDBScript/API/Line2DWrapper.cs
+++ b/Source/Plugins/UDBScript/API/Line2DWrapper.cs
@@ -66,8 +66,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				this.v1 = new Vector2DWrapper((Vector2D)BuilderPlug.Me.GetVectorFromObject(v1, false));
-				this.v2 = new Vector2DWrapper((Vector2D)BuilderPlug.Me.GetVectorFromObject(v2, false));
+				this.v1 = new Vector2DWrapper(BuilderPlug.Me.GetVector3DFromObject(v1));
+				this.v2 = new Vector2DWrapper(BuilderPlug.Me.GetVector3DFromObject(v2));
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -114,10 +114,10 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a1, false);
-				Vector2D v2 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a2, false);
-				Vector2D v3 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b1, false);
-				Vector2D v4 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b2, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(a1);
+				Vector2D v2 = BuilderPlug.Me.GetVector3DFromObject(a2);
+				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(b1);
+				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(b2);
 				double u_ray;
 
 				return Line2D.GetIntersection(v1, v2, v3.x, v3.y, v4.x, v4.y, out u_ray, bounded);
@@ -141,10 +141,10 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a1, false);
-				Vector2D v2 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a2, false);
-				Vector2D v3 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b1, false);
-				Vector2D v4 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b2, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(a1);
+				Vector2D v2 = BuilderPlug.Me.GetVector3DFromObject(a2);
+				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(b1);
+				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(b2);
 
 				return new Vector2DWrapper(Line2D.GetIntersectionPoint(new Line2D(v1, v2), new Line2D(v3, v4), bounded));
 			}
@@ -165,9 +165,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v11 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v1, false);
-				Vector2D v21 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v2, false);
-				Vector2D p1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p, false);
+				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
+				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
+				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);
 
 				return Line2D.GetSideOfLine(v11, v21, p1);
 			}
@@ -189,9 +189,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v11 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v1, false);
-				Vector2D v21 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v2, false);
-				Vector2D p1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p, false);
+				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
+				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
+				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);
 
 				return Line2D.GetDistanceToLine(v11, v21, p1, bounded);
 			}
@@ -213,9 +213,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v11 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v1, false);
-				Vector2D v21 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v2, false);
-				Vector2D p1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p, false);
+				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
+				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
+				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);
 
 				return Line2D.GetDistanceToLineSq(v11, v21, p1, bounded);
 			}
@@ -236,9 +236,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v11 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v1, false);
-				Vector2D v21 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v2, false);
-				Vector2D p1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p, false);
+				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
+				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
+				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);
 
 				return Line2D.GetNearestOnLine(v11, v21, p1);
 			}
@@ -259,8 +259,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v11 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v1, false);
-				Vector2D v21 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v2, false);
+				Vector2D v11 = BuilderPlug.Me.GetVector3DFromObject(v1);
+				Vector2D v21 = BuilderPlug.Me.GetVector3DFromObject(v2);
 
 				return new Vector2DWrapper(Line2D.GetCoordinatesAt(v11, v21, u));
 			}
@@ -343,8 +343,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v3 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a1, false);
-				Vector2D v4 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a2, false);
+				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(a1);
+				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(a2);
 				double u_ray;
 
 				return AsLine2D().GetIntersection(v3.x, v3.y, v4.x, v4.y, out u_ray, bounded);
@@ -366,8 +366,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v3 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a1, false);
-				Vector2D v4 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a2, false);
+				Vector2D v3 = BuilderPlug.Me.GetVector3DFromObject(a1);
+				Vector2D v4 = BuilderPlug.Me.GetVector3DFromObject(a2);
 				Line2D line = AsLine2D();
 				double u_ray;
 				line.GetIntersection(v3.x, v3.y, v4.x, v4.y, out u_ray, bounded);
@@ -405,7 +405,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D p1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p, false);
+				Vector2D p1 = BuilderPlug.Me.GetVector3DFromObject(p);
 
 				return AsLine2D().GetSideOfLine(p1);
 			}
diff --git a/Source/Plugins/UDBScript/API/LinedefWrapper.cs b/Source/Plugins/UDBScript/API/LinedefWrapper.cs
index 11f3cb3bfc7ee5993f11618ccccd423c7805646c..1feed1a713401941926ed312441b09a5f96c5db6 100644
--- a/Source/Plugins/UDBScript/API/LinedefWrapper.cs
+++ b/Source/Plugins/UDBScript/API/LinedefWrapper.cs
@@ -268,6 +268,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// <summary>
 		/// `Array` of arguments of the `Linedef`. Number of arguments depends on game config (usually 5). Hexen format and UDMF only.
 		/// </summary>
+		/// <fakedtstype>number[]</fakedtstype>
 		public MapElementArgumentsWrapper args
 		{
 			get
@@ -527,7 +528,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return new Vector2DWrapper(linedef.NearestOnLine(v));
 			}
 			catch (CantConvertToVectorException e)
@@ -549,7 +550,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return linedef.SafeDistanceToSq(v, bounded);
 			}
 			catch (CantConvertToVectorException e)
@@ -571,7 +572,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return linedef.SafeDistanceTo(v, bounded);
 			}
 			catch (CantConvertToVectorException e)
@@ -593,7 +594,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return linedef.DistanceToSq(v, bounded);
 			}
 			catch (CantConvertToVectorException e)
@@ -615,7 +616,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return linedef.DistanceTo(v, bounded);
 			}
 			catch (CantConvertToVectorException e)
@@ -636,7 +637,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return linedef.SideOfLine(v);
 			}
 			catch (CantConvertToVectorException e)
@@ -660,7 +661,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				Vertex nv = General.Map.Map.CreateVertex(v);
 				nv.SnapToAccuracy();
 				Linedef nld = linedef.Split(nv);
diff --git a/Source/Plugins/UDBScript/API/MapElementArgumentsWrapper.cs b/Source/Plugins/UDBScript/API/MapElementArgumentsWrapper.cs
index 19f5e1cd0fcdbbb29fc84fc962e60d7ac7c96e43..91531c80d7639693d4bad9f7cc5bf83b1ba29d26 100644
--- a/Source/Plugins/UDBScript/API/MapElementArgumentsWrapper.cs
+++ b/Source/Plugins/UDBScript/API/MapElementArgumentsWrapper.cs
@@ -51,8 +51,18 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			}
 			set
 			{
-				if (element is Thing) ((Thing)element).Args[i] = value;
-				else if (element is Linedef) ((Linedef)element).Args[i] = value;
+				if (element is Thing)
+				{
+					// We're not directly changing the fields, but apparently that's the only way to record the changes for the undo system
+					((Thing)element).Fields.BeforeFieldsChange();
+					((Thing)element).Args[i] = value;
+				}
+				else if (element is Linedef)
+				{
+					// We're not directly changing the fields, but apparently that's the only way to record the changes for the undo system
+					((Linedef)element).Fields.BeforeFieldsChange();
+					((Linedef)element).Args[i] = value;
+				}
 			}
 		}
 
diff --git a/Source/Plugins/UDBScript/API/MapElementWrapper.cs b/Source/Plugins/UDBScript/API/MapElementWrapper.cs
index 7abfa5134688c93d4052e54458b0b11a9932d4ca..6a5b3b7168b5c69ce54987ca478b4ffee7819cad 100644
--- a/Source/Plugins/UDBScript/API/MapElementWrapper.cs
+++ b/Source/Plugins/UDBScript/API/MapElementWrapper.cs
@@ -28,6 +28,7 @@ using System.Collections.Generic;
 using System.ComponentModel;
 using System.Dynamic;
 using System.Linq;
+using System.Numerics;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Types;
@@ -62,7 +63,13 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// 
 		/// * it only works for fields that are not in the base UDMF standard, since those are handled directly in the respective class
 		/// * it does not work for flags. While they are technically also UDMF fields, they are handled in the `flags` field of the respective class (where applicable)
-		/// * JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation, using the `UniValue` class:
+		/// * JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation:
+		/// Version 5 and later:
+		/// You can use a `BigInt`. This is done by appending a `n` to the number. Note that this is just a convenient way to define whole numbers, it still only supports 32 bit integers:
+		/// ```
+		/// s.fields.user_myintfield = 25n; // Sets the 'user_myintfield' field to an integer value of 25
+		/// ```
+		/// In version 4 and earlier you have to use the `UniValue` class:
 		/// ```
 		/// s.fields.user_myintfield = new UDB.UniValue(0, 25); // Sets the 'user_myintfield' field to an integer value of 25
 		/// ```
@@ -116,21 +123,45 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 						if (so[pname] != null)
 						{
 							object oldvalue = element.Fields[pname].Value;
+							object proposedvalue;
 
-							if (so[pname] is double && ((oldvalue is int) || (oldvalue is double)))
+							if (so[pname] is UniValue)
+								proposedvalue = BuilderPlug.Me.GetConvertedUniValue((UniValue)so[pname]);
+							else if(so[pname] is BigInteger pv)
+							{
+								// Manually check the range, so that we can show a more useful error
+								if (pv > int.MaxValue)
+									throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException($"Value {pv} for UDMF field \"{pname}\" is too big. Maximum value is {int.MaxValue}");
+
+								if (pv < int.MinValue)
+									throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException($"Value {pv} for UDMF field \"{pname}\" is too small. Minimum value is {int.MinValue}");
+
+								proposedvalue = (int)pv;
+							}
+							else
+								proposedvalue = so[pname];
+
+							if (proposedvalue is double && (oldvalue is int || oldvalue is double))
 							{
 								if (oldvalue is int)
-									newvalue = Convert.ToInt32((double)so[pname]);
-								else if (oldvalue is double)
-									newvalue = (double)so[pname];
+									newvalue = Convert.ToInt32((double)proposedvalue);
+								else
+									newvalue = (double)proposedvalue;
 							}
-							else if (so[pname] is string && oldvalue is string)
+							else if(proposedvalue is int && (oldvalue is int || oldvalue is double))
 							{
-								newvalue = (string)so[pname];
+								if (oldvalue is int)
+									newvalue = (int)proposedvalue;
+								else
+									newvalue = Convert.ToDouble((int)proposedvalue);
 							}
-							else if (so[pname] is bool && oldvalue is bool)
+							else if (proposedvalue is string && oldvalue is string)
 							{
-								newvalue = (bool)so[pname];
+								newvalue = (string)proposedvalue;
+							}
+							else if (proposedvalue is bool && oldvalue is bool)
+							{
+								newvalue = (bool)proposedvalue;
 							}
 							else
 								throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("UDMF field '" + pname + "' is of incompatible type for value " + so[pname]);
@@ -200,9 +231,20 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 						else if (newvalue is int)
 							UniFields.SetInteger(element.Fields, pname, (int)newvalue);
 						else if (newvalue is string)
-							UniFields.SetString(element.Fields, pname, (string)newvalue, string.Empty);
+							UniFields.SetString(element.Fields, pname, (string)newvalue, null);
 						else if (newvalue is bool)
 							element.Fields[pname] = new UniValue(UniversalType.Boolean, (bool)newvalue);
+						else if (newvalue is BigInteger nv)
+						{
+							// Manually check the range, so that we can show a more useful error
+							if(nv > int.MaxValue)
+								throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException($"Value {nv} for UDMF field \"{pname}\" is too big. Maximum value is {int.MaxValue}");
+
+							if (nv < int.MinValue)
+								throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException($"Value {nv} for UDMF field \"{pname}\" is too small. Minimum value is {int.MinValue}");
+
+							UniFields.SetInteger(element.Fields, pname, (int)nv);
+						}
 					}
 
 					AfterFieldsUpdate();
diff --git a/Source/Plugins/UDBScript/API/MapWrapper.cs b/Source/Plugins/UDBScript/API/MapWrapper.cs
index 03193e4eec5fb9f55d41e582c36fc717233bf944..774149b5593af0d22ea8911a6d581506777dc185 100644
--- a/Source/Plugins/UDBScript/API/MapWrapper.cs
+++ b/Source/Plugins/UDBScript/API/MapWrapper.cs
@@ -38,11 +38,33 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 {
 	internal class MapWrapper
 	{
+		#region ================== Enums
+
+		/// <summary>
+		/// How geometry should be merged when geometry is stitched.
+		/// ```
+		/// UDB.Map.stitchGeometry(UDB.Map.MergeometryMode.MERGE);
+		/// ```
+		/// </summary>
+		/// <enum name="CLASSIC">Merge vertices only</enum>
+		/// <enum name="MERGE">Merge vertices and lines</enum>
+		/// <enum name="REPLACE">Merge vertices and lines, replacing sector geometry</enum>
+		[UDBScriptSettings(MinVersion = 5)]
+		public enum MergeGeometryMode
+		{
+			CLASSIC = Map.MergeGeometryMode.CLASSIC,
+			MERGE = Map.MergeGeometryMode.MERGE,
+			REPLACE = Map.MergeGeometryMode.REPLACE
+		}
+
+		#endregion
+
 		#region ================== Variables
 
 		private MapSet map;
 		private VisualCameraWrapper visualcamera;
 		private Vector2D mousemappos;
+		private object highlightedobject;
 
 		#endregion
 
@@ -112,6 +134,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			map = General.Map.Map;
 			visualcamera = new VisualCameraWrapper();
 
+			// If the main window loses focus before the script is running General.Editing.Mode.HighlightedObject will always be null, so cache it here
+			highlightedobject = General.Editing.Mode.HighlightedObject;
+
 			if (General.Editing.Mode is ClassicMode)
 				mousemappos = ((ClassicMode)General.Editing.Mode).MouseMapPos;
 			else
@@ -131,7 +156,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				return new Vector2DWrapper(General.Map.Grid.SnappedToGrid((Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false)));
+				return new Vector2DWrapper(General.Map.Grid.SnappedToGrid(BuilderPlug.Me.GetVector3DFromObject(pos)));
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -217,11 +242,18 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// <summary>
 		/// Stitches marked geometry with non-marked geometry.
 		/// </summary>
-		/// <param name="mergemode">Mode to merge by</param>
+		/// <param name="mergemode">Mode to merge by as `MergeGeometryMode`</param>
 		/// <returns>`true` if successful, `false` if failed</returns>
 		public bool stitchGeometry(MergeGeometryMode mergemode = MergeGeometryMode.CLASSIC)
 		{
-			return General.Map.Map.StitchGeometry(mergemode);
+			if(mergemode == MergeGeometryMode.CLASSIC)
+				return General.Map.Map.StitchGeometry(Map.MergeGeometryMode.CLASSIC);
+			else if(mergemode == MergeGeometryMode.MERGE)
+				return General.Map.Map.StitchGeometry(Map.MergeGeometryMode.MERGE);
+			else if(mergemode == MergeGeometryMode.REPLACE)
+				return General.Map.Map.StitchGeometry(Map.MergeGeometryMode.REPLACE);
+
+			throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Unknown MergeGeometryMode value");
 		}
 
 		/// <summary>
@@ -266,7 +298,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				Linedef nearest = null;
 
 				if (double.IsNaN(maxrange))
@@ -295,7 +327,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				Thing nearest = null;
 
 				if (double.IsNaN(maxrange))
@@ -324,7 +356,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				Vertex nearest = null;
 
 				if (double.IsNaN(maxrange))
@@ -353,7 +385,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				Sidedef nearest = MapSet.NearestSidedef(General.Map.Map.Sidedefs, v);
 
 				if (nearest == null)
@@ -400,7 +432,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			{
 				try
 				{
-					Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(item, false);
+					Vector2D v = BuilderPlug.Me.GetVector3DFromObject(item);
 					DrawnVertex dv = new DrawnVertex();
 					dv.pos = v;
 					dv.stitch = dv.stitchline = true;
@@ -688,7 +720,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// <returns>The currently highlighted `Vertex` or `null` if no `Vertex` is highlighted</returns>
 		public VertexWrapper getHighlightedVertex()
 		{
-			Vertex v = General.Editing.Mode.HighlightedObject as Vertex;
+			Vertex v = highlightedobject as Vertex;
 
 			if (v != null)
 				return new VertexWrapper(v);
@@ -704,13 +736,13 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			if (General.Map.Map.SelectedVerticessCount > 0)
 			{
-				List<VertexWrapper> linedefs = new List<VertexWrapper>();
+				List<VertexWrapper> vertices = new List<VertexWrapper>();
 
 				foreach (Vertex v in General.Map.Map.Vertices)
 					if (v.Selected)
-						linedefs.Add(new VertexWrapper(v));
+						vertices.Add(new VertexWrapper(v));
 
-				return linedefs.ToArray();
+				return vertices.ToArray();
 			}
 			else
 			{
@@ -757,10 +789,20 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// <returns>The currently highlighted `Thing` or `null` if no `Thing` is highlighted</returns>
 		public ThingWrapper getHighlightedThing()
 		{
-			Thing t = General.Editing.Mode.HighlightedObject as Thing;
+			if (General.Editing.Mode is BaseVisualMode)
+			{
+				VisualThing t = highlightedobject as VisualThing;
 
-			if (t != null)
-				return new ThingWrapper(t);
+				if (t != null)
+					return new ThingWrapper(t.Thing);
+			}
+			else
+			{
+				Thing t = highlightedobject as Thing;
+
+				if (t != null)
+					return new ThingWrapper(t);
+			}
 
 			return null;
 		}
@@ -817,14 +859,14 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			if (General.Editing.Mode is BaseVisualMode)
 			{
-				VisualSector s = General.Editing.Mode.HighlightedObject as VisualSector;
+				VisualSector s = highlightedobject as VisualSector;
 
 				if (s != null)
 					return new SectorWrapper(s.Sector);
 			}
 			else
 			{
-				Sector s = General.Editing.Mode.HighlightedObject as Sector;
+				Sector s = highlightedobject as Sector;
 
 				if (s != null)
 					return new SectorWrapper(s);
@@ -839,9 +881,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// <returns>`Array` of `Sector`s</returns>
 		public SectorWrapper[] getSelectedOrHighlightedSectors()
 		{
-			SectorWrapper[] things = getSelectedSectors(true);
-			if (things.Length > 0)
-				return things;
+			SectorWrapper[] sectors = getSelectedSectors(true);
+			if (sectors.Length > 0)
+				return sectors;
 
 			SectorWrapper highlight = getHighlightedSector();
 			if (highlight != null)
@@ -885,14 +927,14 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			if (General.Editing.Mode is BaseVisualMode)
 			{
-				Sidedef sd = General.Editing.Mode.HighlightedObject as Sidedef;
+				Sidedef sd = highlightedobject as Sidedef;
 
 				if (sd != null)
 					return new LinedefWrapper(sd.Line);
 			}
 			else
 			{
-				Linedef ld = General.Editing.Mode.HighlightedObject as Linedef;
+				Linedef ld = highlightedobject as Linedef;
 
 				if (ld != null)
 					return new LinedefWrapper(ld);
@@ -1050,7 +1092,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				Vertex newvertex = General.Map.Map.CreateVertex(v);
 
 				if(newvertex == null)
@@ -1083,21 +1125,15 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 				if(type < 0)
 					throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing type can not be negative.");
 
-				object v = BuilderPlug.Me.GetVectorFromObject(pos, true);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				Thing t = General.Map.Map.CreateThing();
 
 				if(t == null)
 					throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Failed to create new thing.");
 
-				General.Settings.ApplyCleanThingSettings(t);
-
-				if (type > 0)
-					t.Type = type;
+				General.Settings.ApplyCleanThingSettings(t, type);
 
-				if(v is Vector2D)
-					t.Move((Vector2D)v);
-				else if(v is Vector3D)
-					t.Move((Vector3D)v);
+				t.Move(v);
 
 				t.UpdateConfiguration();
 
diff --git a/Source/Plugins/UDBScript/API/PlaneWrapper.cs b/Source/Plugins/UDBScript/API/PlaneWrapper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f44ddc65e29f2e9c56767ec9f3487ab31ad9427c
--- /dev/null
+++ b/Source/Plugins/UDBScript/API/PlaneWrapper.cs
@@ -0,0 +1,285 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using CodeImp.DoomBuilder.Geometry;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.UDBScript.Wrapper
+{
+	class PlaneWrapper
+	{
+		#region ================== Variables
+
+		private Plane plane;
+
+		#endregion
+
+		#region ================== Properties
+
+		/// <summary>
+		/// The plane's normal vector.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public Vector3D normal
+		{
+			get
+			{
+				return plane.Normal;
+			}
+		}
+
+		/// <summary>
+		/// The distance of the plane along the normal vector.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public double offset
+		{
+			get
+			{
+				return plane.Offset;
+			}
+			set
+			{
+				plane.Offset = value;
+			}
+		}
+
+		/// <summary>
+		/// The `a` value of the plane equation. This is the `x` value of the normal vector.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public double a
+		{
+			get
+			{
+				return plane.Normal.x;
+			}
+		}
+
+		/// <summary>
+		/// The `b` value of the plane equation. This is the `y` value of the normal vector.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public double b
+		{
+			get
+			{
+				return plane.Normal.y;
+			}
+		}
+
+		/// <summary>
+		/// The `c` value of the plane equation. This is the `z` value of the normal vector.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public double c
+		{
+			get
+			{
+				return plane.Normal.z;
+			}
+		}
+
+		/// <summary>
+		/// The `d` value of the plane equation. This is the same as the `offset` value.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public double d
+		{
+			get
+			{
+				return plane.Offset;
+			}
+			set
+			{
+				plane.Offset = value;
+			}
+		}
+
+		#endregion
+
+		#region ================== Constructors
+
+		/// <summary>
+		/// Creates a new `Plane` from a normal and an offset. The normal vector has to be `Vector3D`, `Array`s of 3 numbers, or an object with x, y, and z properties.
+		/// ```
+		/// let plane1 = new UDB.Plane(new Vector3D(0.0, -0.707, 0.707), 32);
+		/// let plane2 = new UDB.Plane([ 0.0, -0.707, 0.707 ], 32);
+		/// ```
+		/// </summary>
+		/// <param name="normal">Normal vector of the plane</param>
+		/// <param name="offset">Distance of the plane from the origin</param>
+		[UDBScriptSettings(MinVersion = 5)]
+		public PlaneWrapper(object normal, double offset)
+		{
+			plane = new Plane(BuilderPlug.Me.GetVector3DFromObject(normal), offset);
+		}
+
+		private object bla()
+		{
+			return new Vector2D(1, 2);
+		}
+
+		/// <summary>
+		/// Creates a new `Plane` from 3 points. The points have to be `Vector3D`, `Array`s of 3 numbers, or an object with x, y, and z properties.
+		/// ```
+		/// let plane1 = new UDB.Plane(new Vector3D(0, 0, 0), new Vector3D(64, 0, 0), new Vector3D(64, 64, 32), true);
+		/// let plane2 = new UDB.Plane([ 0, 0, 0 ], [ 64, 0, 0 ], [ 64, 64, 32 ], true);
+		/// ```
+		/// </summary>
+		/// <param name="p1">First point</param>
+		/// <param name="p2">Second point</param>
+		/// <param name="p3">Thrid point</param>
+		/// <param name="up">`true` if plane is pointing up, `false` if pointing down</param>
+		[UDBScriptSettings(MinVersion = 5)]
+		public PlaneWrapper(object p1, object p2, object p3, bool up)
+		{
+			//Vector2D a2 = new Vector2D(1, 2);
+			Vector3D a3 = (Vector2D)bla();
+			try
+			{
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(p1);
+				Vector3D v2 = BuilderPlug.Me.GetVector3DFromObject(p2);
+				Vector3D v3 = BuilderPlug.Me.GetVector3DFromObject(p3);
+
+				plane = new Plane(v1, v2, v3, up);
+			}
+			catch (CantConvertToVectorException e)
+			{
+				throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
+			}
+		}
+
+		#endregion
+
+		#region ================== Methods
+
+		/// <summary>
+		/// Checks if the line between `from` and `to` intersects the plane.
+		/// 
+		/// It returns an `Array`, where the first element is a `bool` vaue indicating if there is an intersector, and the second element is the position of the intersection on the line between the two points.
+		/// 
+		/// ```
+		/// const plane = new UDB.Plane([ 0, 0, 1 ], 0);
+		/// const [intersecting, u] = plane.getIntersection([0, 0, 32], [0, 0, -32]);
+		/// UDB.log(`${intersecting} / ${u}`); // Prints "true / 0.5"
+		/// ```
+		/// </summary>
+		/// <param name="from">`Vector3D` of the start of the line</param>
+		/// <param name="to">`Vector3D` of the end of the line</param>
+		/// <returns></returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public object[] getIntersection(object from, object to)
+		{
+			Vector3D f = BuilderPlug.Me.GetVector3DFromObject(from);
+			Vector3D t = BuilderPlug.Me.GetVector3DFromObject(to);
+
+			double u_ray = double.NaN;
+
+			bool r = plane.GetIntersection(f, t, ref u_ray);
+
+			return new object[] { r, u_ray };
+		}
+
+		/// <summary>
+		/// Computes the distance between the `Plane` and a point. The given point can be a `Vector3D` or an `Array` of three numbers. A result greater than 0 means the point is on the front of the plane, less than 0 means the point is behind the plane.
+		/// ```
+		/// const plane = new UDB.Plane([ 0, 0, 0 ], [ 32, 0, 0 ], [ 32, 32, 16 ], true);
+		/// UDB.log(plane.distance([ 16, 16, 32 ])); // Prints '21.466252583998'
+		/// ```
+		/// </summary>
+		/// <param name="p">Point to compute the distnace to</param>
+		/// <returns>Distance between the `Plane` and the point as `number`</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public double distance(object p)
+		{
+			Vector3D v = BuilderPlug.Me.GetVector3DFromObject(p);
+
+			return plane.Distance(v);
+		}
+
+		/// <summary>
+		/// Returns the point that's closest to the given point on the `Plane`. The given point can be a `Vector3D` or an `Array` of three numbers.
+		/// ```
+		/// const plane = new UDB.Plane([ 0, 0, 0 ], [ 32, 0, 0 ], [ 32, 32, 16 ], true);
+		/// UDB.log(plane.closestOnPlane([ 16, 16, 32 ])); // Prints '16, 25.6, 12.8'
+		/// ```
+		/// </summary>
+		/// <param name="p">Point to get the closest position from</param>
+		/// <returns>Point as `Vector3D` on the plane closest to the given point</returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public Vector3DWrapper closestOnPlane(object p)
+		{
+			Vector3D v = BuilderPlug.Me.GetVector3DFromObject(p);
+
+			return new Vector3DWrapper(plane.ClosestOnPlane(v));
+		}
+
+		/// <summary>
+		/// Returns the position on the z axis of the plane for the given point. The given point can be a `Vector2D` or an `Array` of two numbers.
+		/// ```
+		/// const plane = new UDB.Plane([ 0, 0, 0 ], [ 32, 0, 0 ], [ 32, 32, 16 ], true);
+		/// UDB.log(plane.getZ([ 16, 16 ])); // Prints '8'
+		/// ```
+		/// </summary>
+		/// <param name="p">Point to get the z position from</param>
+		/// <returns></returns>
+		[UDBScriptSettings(MinVersion = 5)]
+		public double getZ(object p)
+		{
+			Vector2D v = BuilderPlug.Me.GetVector3DFromObject(p);
+
+			return plane.GetZ(v);
+		}
+
+		public override bool Equals(object obj)
+		{
+			if (!(obj is PlaneWrapper other)) return false;
+
+			return plane.Equals(other.plane);
+		}
+
+		public override int GetHashCode()
+		{
+			return plane.GetHashCode();
+		}
+
+		#endregion
+
+		#region ================== Statics
+
+		public static bool operator ==(PlaneWrapper a, PlaneWrapper b) => a.plane == b.plane;
+
+		public static bool operator !=(PlaneWrapper a, PlaneWrapper b) => a.plane != b.plane;
+
+		#endregion
+	}
+}
diff --git a/Source/Plugins/UDBScript/API/SectorWrapper.cs b/Source/Plugins/UDBScript/API/SectorWrapper.cs
index ceddaa5558cfe3701ae69984c394334573b3a550..8123b7f9805efd3cf0339c4825bdd742f800e2e5 100644
--- a/Source/Plugins/UDBScript/API/SectorWrapper.cs
+++ b/Source/Plugins/UDBScript/API/SectorWrapper.cs
@@ -27,6 +27,7 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Dynamic;
+using System.Linq;
 using CodeImp.DoomBuilder.BuilderModes;
 using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Geometry;
@@ -568,7 +569,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(p, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(p);
 
 				return sector.Intersect(v);
 			}
@@ -709,7 +710,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				sector.FloorSlope = (Vector3D)BuilderPlug.Me.GetVectorFromObject(normal, true);
+				sector.FloorSlope = BuilderPlug.Me.GetVector3DFromObject(normal);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -740,7 +741,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				sector.CeilSlope = (Vector3D)BuilderPlug.Me.GetVectorFromObject(normal, true);
+				sector.CeilSlope = BuilderPlug.Me.GetVector3DFromObject(normal);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -748,6 +749,26 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			}
 		}
 
+		/// <summary>
+		/// Returns an `Array` of `Vector2D` of label positions for the `Sector`. This are the positions where for example selection number or tags are shown.
+		/// 
+		/// This example adds an imp to the label position of each sector in the map:
+		/// ```
+		/// UDB.Map.getSectors().forEach(s => {
+		///		const positions = s.getLabelPositions();
+		///		if(positions.length > 0)
+        ///			UDB.Map.createThing(positions[0], 3001);
+		///	});
+		///	```
+		/// </summary>
+		/// <returns>`Array` of `Vector2D` of all label positions</returns>
+		/// <version>5</version>
+		[UDBScriptSettings(MinVersion = 5)]
+		public Vector2DWrapper[] getLabelPositions()
+		{
+			return Tools.FindLabelPositions(sector).Select(lpi => new Vector2DWrapper(lpi.position)).ToArray();
+		}
+
 		#endregion
 
 		#region ================== Interface implementations
diff --git a/Source/Plugins/UDBScript/API/ThingWrapper.cs b/Source/Plugins/UDBScript/API/ThingWrapper.cs
index ed3959a6adc44838df45c446bf0245c9bead2a96..ed7f3070cbba07317b05af322631758b6986f427 100644
--- a/Source/Plugins/UDBScript/API/ThingWrapper.cs
+++ b/Source/Plugins/UDBScript/API/ThingWrapper.cs
@@ -146,6 +146,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// <summary>
 		/// `Array` of arguments of the `Thing`. Number of arguments depends on game config (usually 5). Hexen format and UDMF only.
 		/// </summary>
+		/// <fakedtstype>number[]</fakedtstype>
 		public MapElementArgumentsWrapper args
 		{
 			get
@@ -319,12 +320,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 				try
 				{
-					object v = BuilderPlug.Me.GetVectorFromObject(value, true);
-
-					if (v is Vector2D)
-						thing.Move((Vector2D)v);
-					else
-						thing.Move((Vector3D)v);
+					Vector3D v = BuilderPlug.Me.GetVector3DFromObject(value);
+					thing.Move(v);
 				}
 				catch (CantConvertToVectorException e)
 				{
@@ -465,7 +462,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return thing.DistanceToSq(v);
 			}
 			catch (CantConvertToVectorException e)
@@ -490,7 +487,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return thing.DistanceTo(v);
 			}
 			catch (CantConvertToVectorException e)
diff --git a/Source/Plugins/UDBScript/API/UDBWrapper.cs b/Source/Plugins/UDBScript/API/UDBWrapper.cs
index 254cb49658e9881bab0228339073aa797fdd9790..46a78472c77eeab858b709d27a1cec32cc7560e1 100644
--- a/Source/Plugins/UDBScript/API/UDBWrapper.cs
+++ b/Source/Plugins/UDBScript/API/UDBWrapper.cs
@@ -46,7 +46,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		private ExpandoObject scriptoptions;
 
 		private TypeReference angle2d;
-		private TypeReference data;
+		private DataWrapper data;
 		private TypeReference line2d;
 		private MapWrapper map;
 		private TypeReference univalue;
@@ -59,6 +59,10 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		private TypeReference thing;
 		private TypeReference vertex;
 
+		// Version 5
+		private TypeReference plane;
+		private TypeReference blockmap;
+
 		private IProgress<int> progress;
 		private IProgress<string> status;
 		private IProgress<string> logger;
@@ -120,7 +124,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		/// let hasfireblu = UDB.Data.textureExists('FIREBLU1');
 		/// ```
 		/// </summary>
-		public TypeReference Data
+		public DataWrapper Data
 		{
 			get
 			{
@@ -186,10 +190,10 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 		/// <summary>
 		/// Instantiable class that contains methods related to three-dimensional vectors. See [Vector3D](Vector3D.md) for more information.
-		/// </summary>
 		/// ```js
 		/// let v = new UDB.Vector3D(32, 64, 128);
 		/// ```
+		/// </summary>
 		public TypeReference Vector3D
 		{
 			get
@@ -204,6 +208,30 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		public TypeReference Thing { get { return thing; } }
 		public TypeReference Vertex { get { return vertex; } }
 
+		/// <summary>
+		/// Instantiable class that contains methods related to a three-dimensional Plane. See [Plane](Plane.md) for more information.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public TypeReference Plane
+		{
+			get
+			{
+				return plane;
+			}
+		}
+
+		/// <summary>
+		/// Instantiable class that contains methods related to blockmaps. See [BlockMap][BlockMap.md) for more information.
+		/// </summary>
+		[UDBScriptSettings(MinVersion = 5)]
+		public TypeReference BlockMap
+		{
+			get
+			{
+				return blockmap;
+			}
+		}
+
 		#endregion
 
 		#region ================== Constructors
@@ -215,7 +243,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			scriptoptions = scriptinfo.GetScriptOptionsObject();
 
 			angle2d = TypeReference.CreateTypeReference(engine, typeof(Angle2DWrapper));
-			data = TypeReference.CreateTypeReference(engine, typeof(DataWrapper));
+			data = new DataWrapper();
 			line2d = TypeReference.CreateTypeReference(engine, typeof(Line2DWrapper));
 			map = new MapWrapper();
 			univalue = TypeReference.CreateTypeReference(engine, typeof(CodeImp.DoomBuilder.Map.UniValue));
@@ -230,6 +258,10 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			thing = TypeReference.CreateTypeReference(engine, typeof(ThingWrapper));
 			vertex = TypeReference.CreateTypeReference(engine, typeof(VertexWrapper));
 
+			// Version 5
+			plane = TypeReference.CreateTypeReference(engine, typeof(PlaneWrapper));
+			blockmap = TypeReference.CreateTypeReference(engine, typeof(BlockMapWrapper));
+
 			this.progress = progress;
 			this.status = status;
 			this.logger = logger;
diff --git a/Source/Plugins/UDBScript/API/Vector2DWrapper.cs b/Source/Plugins/UDBScript/API/Vector2DWrapper.cs
index 14e5a620cb6311be910e50296e7c611ef78c38f3..6233abd353820ae6a7ab94f487d01487fe9eab2f 100644
--- a/Source/Plugins/UDBScript/API/Vector2DWrapper.cs
+++ b/Source/Plugins/UDBScript/API/Vector2DWrapper.cs
@@ -127,7 +127,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(v);
 
 				_x = v1.x;
 				_y = v1.y;
@@ -169,12 +169,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			{
 				try
 				{
-					object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+					Vector2D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-					if (v is Vector2D)
-						return new Vector2DWrapper(lhs._x + ((Vector2D)v).x, lhs._y + ((Vector2D)v).y);
-					else
-						return new Vector2DWrapper(lhs._x + ((Vector3D)v).x, lhs._y + ((Vector3D)v).y);
+					return new Vector2DWrapper(lhs._x + v.x, lhs._y + v.y);
 				}
 				catch (CantConvertToVectorException e)
 				{
@@ -197,12 +194,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			{
 				try
 				{
-					object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+					Vector2D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-					if (v is Vector2D)
-						return new Vector2DWrapper(((Vector2D)v).x + rhs._x, ((Vector2D)v).y + rhs._y);
-					else
-						return new Vector2DWrapper(((Vector3D)v).x + rhs._x, ((Vector3D)v).y + rhs._y);
+					return new Vector2DWrapper(v.x + rhs._x, v.y + rhs._y);
 				}
 				catch (CantConvertToVectorException e)
 				{
@@ -226,12 +220,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-				if (v is Vector2D)
-					return new Vector2DWrapper(lhs._x - ((Vector2D)v).x, lhs._y - ((Vector2D)v).y);
-				else
-					return new Vector2DWrapper(lhs._x - ((Vector3D)v).x, lhs._y - ((Vector3D)v).y);
+				return new Vector2DWrapper(lhs._x - v.x, lhs._y - v.y);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -246,12 +237,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-				if (v is Vector2D)
-					return new Vector2DWrapper(((Vector2D)v).x - rhs._x, ((Vector2D)v).y - rhs._y);
-				else
-					return new Vector2DWrapper(((Vector3D)v).x - rhs._x, ((Vector3D)v).y - rhs._y);
+				return new Vector2DWrapper(v.x - rhs._x, v.y - rhs._y);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -275,12 +263,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-				if (v is Vector2D)
-					return new Vector2DWrapper(lhs._x * ((Vector2D)v).x, lhs._y * ((Vector2D)v).y);
-				else
-					return new Vector2DWrapper(lhs._x * ((Vector3D)v).x, lhs._y * ((Vector3D)v).y);
+				return new Vector2DWrapper(lhs._x * v.x, lhs._y * v.y);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -295,12 +280,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-				if (v is Vector2D)
-					return new Vector2DWrapper(((Vector2D)v).x * rhs._x, ((Vector2D)v).y * rhs._y);
-				else
-					return new Vector2DWrapper(((Vector3D)v).x * rhs._x, ((Vector3D)v).y * rhs._y);
+				return new Vector2DWrapper(v.x * rhs._x, v.y * rhs._y);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -319,12 +301,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-				if (v is Vector2D)
-					return new Vector2DWrapper(lhs._x / ((Vector2D)v).x, lhs._y / ((Vector2D)v).y);
-				else
-					return new Vector2DWrapper(lhs._x / ((Vector3D)v).x, lhs._y / ((Vector3D)v).y);
+				return new Vector2DWrapper(lhs._x / v.x, lhs._y / v.y);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -339,12 +318,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-				if (v is Vector2D)
-					return new Vector2DWrapper(((Vector2D)v).x / rhs._x, ((Vector2D)v).y / rhs._y);
-				else
-					return new Vector2DWrapper(((Vector3D)v).x / rhs._x, ((Vector3D)v).y / rhs._y);
+				return new Vector2DWrapper(v.x / rhs._x, v.y / rhs._y);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -360,7 +336,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(rhs, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(rhs);
 				return (lhs._x == v1.x) && (lhs._y == v1.y);
 			}
 			catch (CantConvertToVectorException e)
@@ -373,7 +349,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(lhs, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(lhs);
 				return (v1.x == rhs._x) && (v1.y == rhs._y);
 			}
 			catch (CantConvertToVectorException e)
@@ -386,7 +362,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(rhs, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(rhs);
 				return (lhs._x != v1.x) || (lhs._y != v1.y);
 			}
 			catch (CantConvertToVectorException e)
@@ -399,7 +375,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(lhs, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(lhs);
 				return (v1.x != rhs._x) || (v1.y != rhs._y);
 			}
 			catch (CantConvertToVectorException e)
@@ -436,8 +412,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D a1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a, false);
-				Vector2D b1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b, false);
+				Vector2D a1 = BuilderPlug.Me.GetVector3DFromObject(a);
+				Vector2D b1 = BuilderPlug.Me.GetVector3DFromObject(b);
 
 				return new Vector2DWrapper(a1.y * b1.x, a1.x * b1.y);
 			}
@@ -457,8 +433,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v, false);
-				Vector2D m1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(m, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(v);
+				Vector2D m1 = BuilderPlug.Me.GetVector3DFromObject(m);
 
 				Vector2D mv = Vector2D.Reflect(v1, m1);
 
@@ -479,7 +455,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D v1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(v, false);
+				Vector2D v1 = BuilderPlug.Me.GetVector3DFromObject(v);
 
 				return new Vector2DWrapper(Vector2D.Reversed(v1));
 			}
@@ -519,8 +495,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D a1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a, false);
-				Vector2D b1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b, false);
+				Vector2D a1 = BuilderPlug.Me.GetVector3DFromObject(a);
+				Vector2D b1 = BuilderPlug.Me.GetVector3DFromObject(b);
 
 				return Vector2D.GetAngle(a1, b1);
 			}
@@ -540,8 +516,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D a1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a, false);
-				Vector2D b1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b, false);
+				Vector2D a1 = BuilderPlug.Me.GetVector3DFromObject(a);
+				Vector2D b1 = BuilderPlug.Me.GetVector3DFromObject(b);
 
 				return Angle2D.RadToDeg(Vector2D.GetAngle(a1, b1));
 			}
@@ -561,8 +537,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D a1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a, false);
-				Vector2D b1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b, false);
+				Vector2D a1 = BuilderPlug.Me.GetVector3DFromObject(a);
+				Vector2D b1 = BuilderPlug.Me.GetVector3DFromObject(b);
 
 				return Vector2D.DistanceSq(a1, b1);
 			}
@@ -582,8 +558,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector2D a1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(a, false);
-				Vector2D b1 = (Vector2D)BuilderPlug.Me.GetVectorFromObject(b, false);
+				Vector2D a1 = BuilderPlug.Me.GetVector3DFromObject(a);
+				Vector2D b1 = BuilderPlug.Me.GetVector3DFromObject(b);
 
 				return Vector2D.Distance(a1, b1);
 			}
diff --git a/Source/Plugins/UDBScript/API/Vector3DWrapper.cs b/Source/Plugins/UDBScript/API/Vector3DWrapper.cs
index 1fe3686c177a195bd50cd5cb7c5537b70a27ee77..223e16566f0de9fc8ab9768953579fa698098df2 100644
--- a/Source/Plugins/UDBScript/API/Vector3DWrapper.cs
+++ b/Source/Plugins/UDBScript/API/Vector3DWrapper.cs
@@ -144,7 +144,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D v1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(v, true);
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(v);
 
 				_x = v1.x;
 				_y = v1.y;
@@ -187,12 +187,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			{
 				try
 				{
-					object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+					Vector3D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-					if(v is Vector2D)
-						return new Vector3DWrapper(lhs._x + ((Vector2D)v).x, lhs._y + ((Vector2D)v).y, lhs._z);
-					else
-						return new Vector3DWrapper(lhs._x + ((Vector3D)v).x, lhs._y + ((Vector3D)v).y, lhs._z + ((Vector3D)v).z);
+					return new Vector3DWrapper(lhs._x + v.x, lhs._y + v.y, lhs._z + v.z);
 				}
 				catch (CantConvertToVectorException e)
 				{
@@ -215,12 +212,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 			{
 				try
 				{
-					object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+					Vector3D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-					if (v is Vector2D)
-						return new Vector3DWrapper(((Vector2D)v).x + rhs._x, ((Vector2D)v).y + rhs._y, rhs._z);
-					else
-						return new Vector3DWrapper(((Vector3D)v).x + rhs._x, ((Vector3D)v).y + rhs._y, ((Vector3D)v).z + rhs._z);
+					return new Vector3DWrapper(v.x + rhs._x, v.y + rhs._y, v.z + rhs._z);
 				}
 				catch (CantConvertToVectorException e)
 				{
@@ -244,12 +238,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector3D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-				if (v is Vector2D)
-					return new Vector3DWrapper(lhs._x - ((Vector2D)v).x, lhs._y - ((Vector2D)v).y, lhs._z);
-				else
-					return new Vector3DWrapper(lhs._x - ((Vector3D)v).x, lhs._y - ((Vector3D)v).y, lhs._z - ((Vector3D)v).z);
+				return new Vector3DWrapper(lhs._x - v.x, lhs._y - v.y, lhs._z - v.z);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -264,12 +255,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector3D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-				if (v is Vector2D)
-					return new Vector3DWrapper(((Vector2D)v).x - rhs._x, ((Vector2D)v).y - rhs._y, -rhs._z);
-				else
-					return new Vector3DWrapper(((Vector3D)v).x - rhs._x, ((Vector3D)v).y - rhs._y, ((Vector3D)v).z - rhs._z);
+				return new Vector3DWrapper(v.x - rhs._x, v.y - rhs._y, v.z - rhs._z);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -288,12 +276,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector3D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-				if (v is Vector2D)
-					return new Vector3DWrapper(lhs._x * ((Vector2D)v).x, lhs._y * ((Vector2D)v).y, 0);
-				else
-					return new Vector3DWrapper(lhs._x * ((Vector3D)v).x, lhs._y * ((Vector3D)v).y, lhs._z * ((Vector3D)v).z);
+				return new Vector3DWrapper(lhs._x * v.x, lhs._y * v.y, lhs._z * v.z);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -308,12 +293,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector3D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-				if (v is Vector2D)
-					return new Vector3DWrapper(((Vector2D)v).x * rhs._x, ((Vector2D)v).y * rhs._y, 0);
-				else
-					return new Vector3DWrapper(((Vector3D)v).x * rhs._x, ((Vector3D)v).y * rhs._y, ((Vector3D)v).z * rhs._z);
+				return new Vector3DWrapper(v.x * rhs._x, v.y * rhs._y, v.z * rhs._z);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -332,12 +314,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector3D v = BuilderPlug.Me.GetVector3DFromObject(rhs);
 
-				if (v is Vector2D)
-					return new Vector3DWrapper(lhs._x / ((Vector2D)v).x, lhs._y / ((Vector2D)v).y, lhs._z / 0);
-				else
-					return new Vector3DWrapper(lhs._x / ((Vector3D)v).x, lhs._y / ((Vector3D)v).y, lhs._z / ((Vector3D)v).z);
+				return new Vector3DWrapper(lhs._x / v.x, lhs._y / v.y, lhs._z / v.z);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -352,12 +331,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				object v = BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector3D v = BuilderPlug.Me.GetVector3DFromObject(lhs);
 
-				if (v is Vector2D)
-					return new Vector3DWrapper(((Vector2D)v).x / rhs._x, ((Vector2D)v).y / rhs._y, 0 / rhs._z);
-				else
-					return new Vector3DWrapper(((Vector3D)v).x / rhs._x, ((Vector3D)v).y / rhs._y, ((Vector3D)v).z / rhs._z);
+				return new Vector3DWrapper(v.x / rhs._x, v.y / rhs._y, v.z / rhs._z);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -373,7 +349,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D v1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(rhs);
 				return (lhs._x == v1.x) && (lhs._y == v1.y) && (lhs._z == v1.z);
 			}
 			catch (CantConvertToVectorException e)
@@ -386,7 +362,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D v1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(lhs);
 				return (v1.x == rhs._x) && (v1.y == rhs._y) && (v1.z == rhs._z);
 			}
 			catch (CantConvertToVectorException e)
@@ -399,7 +375,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D v1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(rhs, true);
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(rhs);
 				return (lhs._x != v1.x) || (lhs._y != v1.y) || (lhs._z != v1.z);
 			}
 			catch (CantConvertToVectorException e)
@@ -412,7 +388,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D v1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(lhs, true);
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(lhs);
 				return (v1.x != rhs._x) || (v1.y != rhs._y) || (v1.z != rhs._z);
 			}
 			catch (CantConvertToVectorException e)
@@ -449,10 +425,14 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D a1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(a, true);
-				Vector3D b1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(b, true);
+				Vector3D a1 = BuilderPlug.Me.GetVector3DFromObject(a);
+				Vector3D b1 = BuilderPlug.Me.GetVector3DFromObject(b);
 
-				return new Vector3DWrapper(a1.y * b1.x - a1.z * b1.y, a1.z * b1.x - a1.x * b1.z, a1.x * b1.y - a1.y * b1.x);
+				return new Vector3DWrapper(
+					a1.y * b1.z - a1.z * b1.y,
+					a1.z * b1.x - a1.x * b1.z,
+					a1.x * b1.y - a1.y * b1.x
+				);
 			}
 			catch (CantConvertToVectorException e)
 			{
@@ -470,8 +450,8 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D v1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(v, true);
-				Vector3D m1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(m, true);
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(v);
+				Vector3D m1 = BuilderPlug.Me.GetVector3DFromObject(m);
 
 				return new Vector3DWrapper(Vector3D.Reflect(v1, m1));
 			}
@@ -490,7 +470,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 		{
 			try
 			{
-				Vector3D v1 = (Vector3D)BuilderPlug.Me.GetVectorFromObject(v, true);
+				Vector3D v1 = BuilderPlug.Me.GetVector3DFromObject(v);
 
 				return new Vector3DWrapper(Vector3D.Reversed(v1));
 			}
diff --git a/Source/Plugins/UDBScript/API/VertexWrapper.cs b/Source/Plugins/UDBScript/API/VertexWrapper.cs
index bb3f6dfed8c18ad97dcf4f20917ab2552e4ef165..317f88ad778dca52ed451849681987b70b65d1df 100644
--- a/Source/Plugins/UDBScript/API/VertexWrapper.cs
+++ b/Source/Plugins/UDBScript/API/VertexWrapper.cs
@@ -109,12 +109,9 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 				try
 				{
-					object v = BuilderPlug.Me.GetVectorFromObject(value, false);
+					Vector2D v = BuilderPlug.Me.GetVector3DFromObject(value);
 
-					if (v is Vector2D)
-						vertex.Move((Vector2D)v);
-					else
-						vertex.Move((Vector3D)v);
+					vertex.Move(v);
 				}
 				catch (CantConvertToVectorException e)
 				{
@@ -280,7 +277,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return vertex.DistanceToSq(v);
 			}
 			catch(CantConvertToVectorException e)
@@ -306,7 +303,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return vertex.DistanceTo(v);
 			}
 			catch (CantConvertToVectorException e)
@@ -327,7 +324,7 @@ namespace CodeImp.DoomBuilder.UDBScript.Wrapper
 
 			try
 			{
-				Vector2D v = (Vector2D)BuilderPlug.Me.GetVectorFromObject(pos, false);
+				Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
 				return new LinedefWrapper(vertex.NearestLinedef(v));
 			}
 			catch (CantConvertToVectorException e)
diff --git a/Source/Plugins/UDBScript/BuilderPlug.cs b/Source/Plugins/UDBScript/BuilderPlug.cs
index 304ad57df751f38318f615ef1075ef95c8fcc93f..d8625fd7e4a35eff2689ea92854bc0874e7550bd 100644
--- a/Source/Plugins/UDBScript/BuilderPlug.cs
+++ b/Source/Plugins/UDBScript/BuilderPlug.cs
@@ -30,6 +30,7 @@ using System.Dynamic;
 using System.Windows.Forms;
 using System.IO;
 using System.Linq;
+using System.Numerics;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Text.RegularExpressions;
@@ -49,14 +50,16 @@ namespace CodeImp.DoomBuilder.UDBScript
 {
 	internal class ScriptDirectoryStructure
 	{
+		public string Path;
 		public string Name;
 		public bool Expanded;
 		public string Hash;
 		public List<ScriptDirectoryStructure> Directories;
 		public List<ScriptInfo> Scripts;
 
-		public ScriptDirectoryStructure(string name, bool expanded, string hash)
+		public ScriptDirectoryStructure(string path, string name, bool expanded, string hash)
 		{
+			Path = path;
 			Name = name;
 			Expanded = expanded;
 			Hash = hash;
@@ -70,7 +73,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 		#region ================== Constants
 
 		private static readonly string SCRIPT_FOLDER = "udbscript";
-		public static readonly uint UDB_SCRIPT_VERSION = 4;
+		public static readonly uint UDB_SCRIPT_VERSION = 5;
 
 		#endregion
 
@@ -130,13 +133,18 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 			General.Actions.BindMethods(this);
 
-			watcher = new FileSystemWatcher(Path.Combine(General.AppPath, SCRIPT_FOLDER, "scripts"));
-			watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size;
-			watcher.IncludeSubdirectories = true;
-			watcher.Changed += OnWatcherEvent;
-			watcher.Created += OnWatcherEvent;
-			watcher.Deleted += OnWatcherEvent;
-			watcher.Renamed += OnWatcherEvent;
+			string scriptspath = Path.Combine(General.AppPath, SCRIPT_FOLDER, "scripts");
+
+			if (Directory.Exists(scriptspath))
+			{
+				watcher = new FileSystemWatcher(Path.Combine(General.AppPath, SCRIPT_FOLDER, "scripts"));
+				watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size;
+				watcher.IncludeSubdirectories = true;
+				watcher.Changed += OnWatcherEvent;
+				watcher.Created += OnWatcherEvent;
+				watcher.Deleted += OnWatcherEvent;
+				watcher.Renamed += OnWatcherEvent;
+			}
 
 			editorexepath = General.Settings.ReadPluginSetting("externaleditor", string.Empty);
 
@@ -152,7 +160,8 @@ namespace CodeImp.DoomBuilder.UDBScript
 			// Methods called by LoadScripts might sleep for some time, so call LoadScripts asynchronously
 			new Task(LoadScripts).Start();
 
-			watcher.EnableRaisingEvents = true;
+			if (watcher != null)
+				watcher.EnableRaisingEvents = true;
 		}
 
 		public override void OnMapOpenEnd()
@@ -162,12 +171,14 @@ namespace CodeImp.DoomBuilder.UDBScript
 			// Methods called by LoadScripts might sleep for some time, so call LoadScripts asynchronously
 			new Task(LoadScripts).Start();
 
-			watcher.EnableRaisingEvents = true;
+			if (watcher != null)
+				watcher.EnableRaisingEvents = true;
 		}
 
 		public override void OnMapCloseBegin()
 		{
-			watcher.EnableRaisingEvents = false;
+			if (watcher != null)
+				watcher.EnableRaisingEvents = false;
 
 			SaveScriptSlotsAndOptions();
 			SaveScriptDirectoryExpansionStatus(scriptdirectorystructure);
@@ -228,6 +239,9 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 		internal void SaveScriptDirectoryExpansionStatus(ScriptDirectoryStructure root)
 		{
+			if (root == null)
+				return;
+
 			if(root.Expanded)
 			{
 				General.Settings.DeletePluginSetting("directoryexpand." + root.Hash);
@@ -386,7 +400,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 			string hash = SHA256Hash.Get(path);
 			bool expanded = General.Settings.ReadPluginSetting("directoryexpand." + hash, true);
 			string name = path.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Last();
-			ScriptDirectoryStructure sds = new ScriptDirectoryStructure(name, expanded, hash);
+			ScriptDirectoryStructure sds = new ScriptDirectoryStructure(path, name, expanded, hash);
 
 			foreach (string directory in Directory.GetDirectories(path))
 				sds.Directories.Add(LoadScriptDirectoryStructure(directory));
@@ -452,41 +466,44 @@ namespace CodeImp.DoomBuilder.UDBScript
 			panel.EndEdit();
 		}
 
-		internal object GetVectorFromObject(object data, bool allow3d)
+		internal Vector3D GetVector3DFromObject(object data)
 		{
 			if (data is Vector2D)
 				return (Vector2D)data;
 			else if (data is Vector2DWrapper)
 				return new Vector2D(((Vector2DWrapper)data)._x, ((Vector2DWrapper)data)._y);
+			else if (data is Vector3D)
+				return (Vector3D)data;
 			else if (data is Vector3DWrapper)
-			{
-				if(allow3d)
-					return new Vector3D(((Vector3DWrapper)data)._x, ((Vector3DWrapper)data)._y, ((Vector3DWrapper)data)._z);
-				else
-					return new Vector2D(((Vector3DWrapper)data)._x, ((Vector3DWrapper)data)._y);
-			}
+				return new Vector3D(((Vector3DWrapper)data)._x, ((Vector3DWrapper)data)._y, ((Vector3DWrapper)data)._z);
 			else if (data.GetType().IsArray)
-			//else if(data is double[])
 			{
-				object[] vals = (object[])data;
-				//double[] vals = (double[])data;
+				object[] rawvals = (object[])data;
+				List<double> vals = new List<double>(rawvals.Length);
 
-				// Make sure all values in the array are doubles
-				foreach (object v in vals)
-					if (!(v is double))
+				// Make sure all values in the array are doubles or BigIntegers
+				foreach (object rv in rawvals)
+				{
+					if (!(rv is double || rv is BigInteger))
 						throw new CantConvertToVectorException("Values in array must be numbers.");
 
-				if (vals.Length == 2)
-					return new Vector2D((double)vals[0], (double)vals[1]);
-				if (vals.Length == 3)
-					return new Vector3D((double)vals[0], (double)vals[1], (double)vals[2]);
+					if (rv is double d)
+						vals.Add(d);
+					else if(rv is BigInteger bi)
+						vals.Add((double)bi);
+				}
+
+				if (vals.Count == 2)
+					return new Vector2D(vals[0], vals[1]);
+				if (vals.Count == 3)
+					return new Vector3D(vals[0], vals[1], vals[2]);
 			}
 			else if (data is ExpandoObject)
 			{
 				IDictionary<string, object> eo = data as IDictionary<string, object>;
 				double x = double.NaN;
 				double y = double.NaN;
-				double z = double.NaN;
+				double z = 0.0;
 
 				if (eo.ContainsKey("x"))
 				{
@@ -524,24 +541,11 @@ namespace CodeImp.DoomBuilder.UDBScript
 					}
 				}
 
-				if (allow3d)
-				{
-					if (!double.IsNaN(x) && !double.IsNaN(y) && double.IsNaN(z))
-						return new Vector2D(x, y);
-					else if (!double.IsNaN(x) && !double.IsNaN(y) && !double.IsNaN(z))
-						return new Vector3D(x, y, z);
-				}
-				else
-				{
-					if (x != double.NaN && y != double.NaN)
-						return new Vector2D(x, y);
-				}
+				if (!double.IsNaN(x) && !double.IsNaN(y) && !double.IsNaN(z))
+					return new Vector3D(x, y, z);
 			}
 
-			if (allow3d)
-				throw new CantConvertToVectorException("Data must be a Vector2D, Vector3D, or an array of numbers.");
-			else
-				throw new CantConvertToVectorException("Data must be a Vector2D, or an array of numbers.");
+			throw new CantConvertToVectorException("Data must be a Vector2D, Vector3D, an array of numbers, or an object with (x, y, z) members.");
 		}
 
 		internal object GetConvertedUniValue(UniValue uv)
diff --git a/Source/Plugins/UDBScript/Controls/ScriptDockerControl.cs b/Source/Plugins/UDBScript/Controls/ScriptDockerControl.cs
index d5a7c847b64bd166054d23c2b4701dbc27fcc8e8..66fd74240dd19ee06a9ecc40810707fc5fdf035f 100644
--- a/Source/Plugins/UDBScript/Controls/ScriptDockerControl.cs
+++ b/Source/Plugins/UDBScript/Controls/ScriptDockerControl.cs
@@ -24,6 +24,7 @@
 using System;
 using System.Collections.Generic;
 using System.Data;
+using System.Diagnostics;
 using System.Linq;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Controls;
@@ -35,7 +36,8 @@ namespace CodeImp.DoomBuilder.UDBScript
 		#region ================== Variables
 
 		private ImageList images;
-		private ContextMenuStrip contextmenu;
+		private ContextMenuStrip filecontextmenu;
+		private ContextMenuStrip foldercontextmenu;
 		ToolStripItem[] slotitems;
 
 		#endregion
@@ -63,7 +65,10 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 		#region ================== Methods
 
-		private void CreateContextMenu()
+		/// <summary>
+		/// Creates the context menu for file items.
+		/// </summary>
+		private void CreateFileContextMenu()
 		{
 			ToolStripMenuItem edititem = new ToolStripMenuItem("Edit");
 			edititem.Click += EditScriptClicked;
@@ -84,14 +89,29 @@ namespace CodeImp.DoomBuilder.UDBScript
 			setslot.DropDownItems.AddRange(slotitems);
 			setslot.DropDownItemClicked += ItemClicked;
 
-			contextmenu = new ContextMenuStrip();
-			contextmenu.Items.AddRange(new ToolStripItem[]
+			filecontextmenu = new ContextMenuStrip();
+			filecontextmenu.Items.AddRange(new ToolStripItem[]
 			{
 				edititem,
 				setslot
 			});
 		}
 
+		/// <summary>
+		/// Creates the context menu for folder items.
+		/// </summary>
+		private void CreateFolderContextMenu()
+		{
+			ToolStripMenuItem openitem = new ToolStripMenuItem("Open in Explorer");
+			openitem.Click += (s, e) => { try { Process.Start("explorer.exe", ((ScriptDirectoryStructure)filetree.SelectedNodes[0].Tag).Path); } catch { } };
+
+			foldercontextmenu = new ContextMenuStrip();
+			foldercontextmenu.Items.AddRange(new ToolStripItem[]
+			{
+				openitem
+			});
+		}
+
 		/// <summary>
 		/// Returns the hotkey text for a script s lot.
 		/// </summary>
@@ -112,7 +132,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 		/// <summary>
 		/// Updates the context menu of the slots so that the items show the script name and hotkey (if applicable)
 		/// </summary>
-		private void UpdateContextMenu()
+		private void UpdateFileContextMenu()
 		{
 			for (int i = 0; i < BuilderPlug.NUM_SCRIPT_SLOTS; i++)
 			{
@@ -174,7 +194,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 					BuilderPlug.Me.SetScriptSlot((int)e.ClickedItem.Tag, si);
 			}
 
-			UpdateContextMenu();
+			UpdateFileContextMenu();
 
 			foreach (TreeNode node in filetree.Nodes)
 				UpdateTree(node);
@@ -215,7 +235,6 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 			filetree.Nodes.Clear();
 			filetree.Nodes.AddRange(AddToTree(filtertext, BuilderPlug.Me.ScriptDirectoryStructure));
-			//filetree.ExpandAll();
 
 			foreach(TreeNode node in filetree.Nodes)
 			{
@@ -239,6 +258,10 @@ namespace CodeImp.DoomBuilder.UDBScript
 		/// <returns>Found TreeNode or null</returns>
 		private TreeNode FindScriptTreeNode(string name, TreeNode root)
 		{
+			// The "root" node might already be the one we're looking for
+			if (root.Tag is ScriptInfo && ((ScriptInfo)root.Tag).ScriptFile == name)
+				return root;
+
 			foreach (TreeNode node in root.Nodes)
 			{
 				if (node.Tag is ScriptInfo && ((ScriptInfo)node.Tag).ScriptFile == name)
@@ -264,6 +287,9 @@ namespace CodeImp.DoomBuilder.UDBScript
 		{
 			List<TreeNode> newnodes = new List<TreeNode>();
 
+			if (sds == null)
+				return newnodes.ToArray();
+
 			// Go through folders and add files (and other folders) recusrively
 			foreach (ScriptDirectoryStructure subsds in sds.Directories.OrderBy(s => s.Name))
 			{
@@ -272,6 +298,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 				tn.Tag = subsds;
 				tn.SelectedImageKey = tn.ImageKey = "Folder";
+				tn.ContextMenuStrip = foldercontextmenu;
 
 				if (subsds.Expanded)
 					tn.Expand();
@@ -300,7 +327,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 				tn.Tag = si;
 				tn.SelectedImageKey = tn.ImageKey = "Script";
-				tn.ContextMenuStrip = contextmenu;
+				tn.ContextMenuStrip = filecontextmenu;
 
 				newnodes.Add(tn);
 			}
@@ -406,8 +433,10 @@ namespace CodeImp.DoomBuilder.UDBScript
 			if (!Visible || Disposing)
 				return;
 
-			CreateContextMenu();
-			UpdateContextMenu();
+			CreateFileContextMenu();
+			UpdateFileContextMenu();
+			CreateFolderContextMenu();
+
 			FillTree();
 		}
 
diff --git a/Source/Plugins/UDBScript/Esprima.dll b/Source/Plugins/UDBScript/Esprima.dll
index 95cff9d6a3d4a138878d40aa8a05829cf6a41178..8baa9ebf755a8adea21a09f289871254d367266c 100644
Binary files a/Source/Plugins/UDBScript/Esprima.dll and b/Source/Plugins/UDBScript/Esprima.dll differ
diff --git a/Source/Plugins/UDBScript/Exceptions.cs b/Source/Plugins/UDBScript/Exceptions.cs
index 9aa365c218c41c050ac72d702ea1ae9066ab18f9..2ced120fd938d4869efec14e1e13e4959942a352 100644
--- a/Source/Plugins/UDBScript/Exceptions.cs
+++ b/Source/Plugins/UDBScript/Exceptions.cs
@@ -76,4 +76,16 @@ namespace CodeImp.DoomBuilder.UDBScript
 		{
 		}
 	}
+
+	[Serializable]
+	public class ScriptRuntimeException : Exception
+	{
+		public ScriptRuntimeException()
+		{
+		}
+
+		public ScriptRuntimeException(string message) : base(message)
+		{
+		}
+	}
 }
diff --git a/Source/Plugins/UDBScript/Jint.dll b/Source/Plugins/UDBScript/Jint.dll
index 18a4ba78d9f73d30c65c3f8f8b8cf11f8a040fd3..a39fb36f213c534ceed6fad5379ad366525381af 100644
Binary files a/Source/Plugins/UDBScript/Jint.dll and b/Source/Plugins/UDBScript/Jint.dll differ
diff --git a/Source/Plugins/UDBScript/RuntimeConstraint.cs b/Source/Plugins/UDBScript/RuntimeConstraint.cs
index 9704765f6f6aa6f2daf5a304e73f69234b2cc79d..64e85e6e942b92128739bf98d803709da60b9e1d 100644
--- a/Source/Plugins/UDBScript/RuntimeConstraint.cs
+++ b/Source/Plugins/UDBScript/RuntimeConstraint.cs
@@ -37,7 +37,7 @@ using Jint;
 
 namespace CodeImp.DoomBuilder.UDBScript
 {
-	class RuntimeConstraint : IConstraint
+	class RuntimeConstraint : Constraint
 	{
 		#region ================== Constants
 
@@ -62,14 +62,14 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 		#region ================== Methods
 
-		public void Reset()
+		public override void Reset()
 		{
 		}
 
 		/// <summary>
 		/// Checks how long the script has been running and asks the user if it should abort or keep running
 		/// </summary>
-		public void Check()
+		public override void Check()
 		{
 			if(stopwatch.ElapsedMilliseconds > CHECK_MILLISECONDS)
 			{
diff --git a/Source/Plugins/UDBScript/ScriptRunner.cs b/Source/Plugins/UDBScript/ScriptRunner.cs
index 7bae488769937c98614be53b118e1a39098afbb1..e114aad1c82cfeba5abf9dab1be0cb6edeb6fc86 100644
--- a/Source/Plugins/UDBScript/ScriptRunner.cs
+++ b/Source/Plugins/UDBScript/ScriptRunner.cs
@@ -26,6 +26,7 @@
 using System;
 using System.Diagnostics;
 using System.IO;
+using System.Reflection;
 using System.Threading;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Map;
@@ -35,6 +36,7 @@ using Jint;
 using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Esprima;
+using Jint.Native;
 
 #endregion
 
@@ -147,9 +149,9 @@ namespace CodeImp.DoomBuilder.UDBScript
 			throw new DieScriptException(s);
 		}
 
-		public JavaScriptException CreateRuntimeException(string message)
+		public ScriptRuntimeException CreateRuntimeException(string message)
 		{
-			return new JavaScriptException(engine.Realm.Intrinsics.Error, message);
+			return new ScriptRuntimeException(message);
 		}
 
 		/// <summary>
@@ -169,8 +171,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 			{
 				try
 				{
-					ParserOptions po = new ParserOptions(file.Remove(0, General.AppPath.Length));
-					engine.Execute(File.ReadAllText(file), po);
+					engine.Execute(File.ReadAllText(file), file.Remove(0, General.AppPath.Length));
 				}
 				catch (ParserException e)
 				{
@@ -182,7 +183,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 				{
 					if (e.Error.Type != Jint.Runtime.Types.String)
 					{
-						UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, e.StackTrace);
+						UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, e.JavaScriptStackTrace, e.StackTrace);
 						sef.ShowDialog();
 					}
 					else
@@ -213,15 +214,15 @@ namespace CodeImp.DoomBuilder.UDBScript
 				MessageBox.Show("There is an error while parsing the script:\n\n" + e.Message, "Script error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 				abort = true;
 			}
-			else if(e is JavaScriptException)
+			else if(e is JavaScriptException jse)
 			{
-				if (((JavaScriptException)e).Error.Type != Jint.Runtime.Types.String)
+				if (jse.Error.Type != Jint.Runtime.Types.String)
 				{
-					UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, e.StackTrace);
+					UDBScriptErrorForm sef = new UDBScriptErrorForm(jse.Message, jse.JavaScriptStackTrace, jse.StackTrace);
 					sef.ShowDialog();
 				}
 				else
-					General.Interface.DisplayStatus(StatusType.Warning, e.Message); // We get here if "throw" is used in a script
+					General.Interface.DisplayStatus(StatusType.Warning, jse.Message); // We get here if "throw" is used in a script
 
 				abort = true;
 			}
@@ -243,7 +244,7 @@ namespace CodeImp.DoomBuilder.UDBScript
 			}
 			else // Catch anything else we didn't think about
 			{
-				UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, e.StackTrace);
+				UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, string.Empty, e.StackTrace);
 				sef.ShowDialog();
 
 				abort = true;
@@ -253,6 +254,44 @@ namespace CodeImp.DoomBuilder.UDBScript
 				General.Map.UndoRedo.WithdrawUndo();
 		}
 
+		/// <summary>
+		/// Makes sure that only properties for the currect feature version are available to scripts.
+		/// </summary>
+		/// <param name="info">MemberInfo about the property that's being accessed</param>
+		/// <returns>true if property can be accessed, false otherwise</returns>
+		private bool MemberFilter(MemberInfo info)
+		{
+			if (info.Name == nameof(GetType))
+				return false;
+
+			if (info.GetCustomAttribute(typeof(UDBScriptSettingsAttribute)) is UDBScriptSettingsAttribute sa)
+				return sa.MinVersion <= scriptinfo.Version;
+
+			return true;
+		}
+
+		/*
+		private JsValue GetObjectMember(Engine engine, object target, string memberName)
+		{
+			Type t = target.GetType();
+			MethodInfo mi = t.GetMethod(memberName);
+			if (mi != null)
+			{
+				var attr = mi.GetCustomAttribute<UDBScriptSettingsAttribute>(false);
+				if (attr != null && scriptinfo.Version < attr.MinVersion)
+					throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException($"{t.Name} requires UDBScript version {attr.MinVersion} or higher.");
+
+			}
+			if (t.GetCustomAttribute(typeof(UDBScriptSettingsAttribute)) is UDBScriptSettingsAttribute sa)
+			{
+				if (scriptinfo.Version < sa.MinVersion)
+					throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException($"{t.Name} requires UDBScript version {sa.MinVersion} or higher.");
+			}
+
+			return null;
+		}
+		*/
+
 		/// <summary>
 		/// Sets everything up for running the script. This has to be done on the UI thread.
 		/// </summary>
@@ -279,10 +318,27 @@ namespace CodeImp.DoomBuilder.UDBScript
 			Options options = new Options();
 			options.CancellationToken(cancellationtoken);
 			options.AllowOperatorOverloading();
+
 			options.SetTypeResolver(new TypeResolver
 			{
-				MemberFilter = member => member.Name != nameof(GetType)
+				MemberFilter = MemberFilter// member => member.Name != nameof(GetType)
+			});
+
+			//options.SetMemberAccessor(GetObjectMember);
+
+			/*
+			options.SetWrapObjectHandler((eng, obj) =>
+			{
+				var wrapper = new ObjectWrapper(eng, obj);
+				if (wrapper.IsArrayLike || obj is BlockMapQueryResult)
+				{
+					wrapper.SetPrototypeOf(eng.Realm.Intrinsics.Array.PrototypeObject);
+				}
+				return wrapper;
 			});
+			*/
+
+			options.CatchClrExceptions(e => e is ScriptRuntimeException || e is CantConvertToVectorException);
 
 			// Create the script engine
 			engine = new Engine(options);
@@ -352,10 +408,9 @@ namespace CodeImp.DoomBuilder.UDBScript
 			string script = File.ReadAllText(scriptinfo.ScriptFile);
 
 			// Run the script file
-			ParserOptions po = new ParserOptions(scriptinfo.ScriptFile.Remove(0, General.AppPath.Length));
-
+			stopwatch.Reset();
 			stopwatch.Start();
-			engine.Execute(script, po);
+			engine.Execute(script, scriptinfo.ScriptFile.Remove(0, General.AppPath.Length));
 			stopwatch.Stop();
 		}
 
@@ -379,6 +434,11 @@ namespace CodeImp.DoomBuilder.UDBScript
 				General.Interface.EnableProcessing();
 		}
 
+		public string GetRuntimeString()
+		{
+			return string.Format("{0:D2}:{1:D2}:{2:D2}.{3:D}", stopwatch.Elapsed.Hours, stopwatch.Elapsed.Minutes, stopwatch.Elapsed.Seconds, stopwatch.Elapsed.Milliseconds);
+		}
+
 		#endregion
 	}
 }
diff --git a/Source/Plugins/UDBScript/UDBScript.csproj b/Source/Plugins/UDBScript/UDBScript.csproj
index f5a53c64c31b026e478e09c6706b4559b762dbf8..3e2025ecb5da87ff0c3ad0c1d2df2d38aad6c346 100644
--- a/Source/Plugins/UDBScript/UDBScript.csproj
+++ b/Source/Plugins/UDBScript/UDBScript.csproj
@@ -21,6 +21,8 @@
     <PlatformTarget>x64</PlatformTarget>
     <ErrorReport>prompt</ErrorReport>
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <DocumentationFile>
+    </DocumentationFile>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
     <OutputPath>..\..\..\Build\Plugins\</OutputPath>
@@ -65,6 +67,10 @@
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Drawing" />
+    <Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Numerics" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml.Linq" />
     <Reference Include="System.Data.DataSetExtensions" />
@@ -75,12 +81,18 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="API\Angle2DWrapper.cs" />
+    <Compile Include="API\BlockEntryWrapper.cs" />
+    <Compile Include="API\BlockMapContentBase.cs" />
+    <Compile Include="API\BlockMapQueryResult.cs" />
+    <Compile Include="API\BlockMapWrapper.cs" />
     <Compile Include="API\DataWrapper.cs" />
     <Compile Include="API\GameConfigurationWrapper.cs" />
     <Compile Include="API\ImageInfo.cs" />
+    <Compile Include="API\PlaneWrapper.cs" />
     <Compile Include="API\UDBWrapper.cs" />
     <Compile Include="API\Vector3DWrapper.cs" />
     <Compile Include="API\VisualCameraWrapper.cs" />
+    <Compile Include="UDBScriptSettingsAttribute.cs" />
     <Compile Include="BuilderPlug.cs" />
     <Compile Include="Controls\ScriptDockerControl.cs">
       <SubType>UserControl</SubType>
diff --git a/Source/Plugins/UDBScript/UDBScriptSettingsAttribute.cs b/Source/Plugins/UDBScript/UDBScriptSettingsAttribute.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a3e917ee2437ae79f52c219fcccdc6bc950f49fc
--- /dev/null
+++ b/Source/Plugins/UDBScript/UDBScriptSettingsAttribute.cs
@@ -0,0 +1,48 @@
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.UDBScript
+{
+	sealed internal class UDBScriptSettingsAttribute : Attribute
+	{
+		#region ================== Properties
+
+		public int MinVersion { get; set; }
+
+		#endregion
+
+		#region ================== Constructors
+
+		public UDBScriptSettingsAttribute()
+		{
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Plugins/UDBScript/Windows/ScriptRunnerForm.cs b/Source/Plugins/UDBScript/Windows/ScriptRunnerForm.cs
index f2a21fc2fe412c17d3ee5e744995c56f0da461b6..2f849e7f3c9000f722a8dbea2c36d18bd6c827ad 100644
--- a/Source/Plugins/UDBScript/Windows/ScriptRunnerForm.cs
+++ b/Source/Plugins/UDBScript/Windows/ScriptRunnerForm.cs
@@ -205,9 +205,13 @@ namespace CodeImp.DoomBuilder.UDBScript
 
 			running = false;
 
+			Text = "Script finished";
+			lbStatus.Text = "Script finished. Runtime: " + BuilderPlug.Me.ScriptRunner.GetRuntimeString();
 			btnAction.Text = "Close";
 			btnAction.Enabled = true;
 
+			SetProgress(0);
+
 			// Stop the progress bar from animating when the script finished
 			if (progressbar.Style == ProgressBarStyle.Marquee)
 				progressbar.Style = ProgressBarStyle.Continuous;
diff --git a/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.Designer.cs b/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.Designer.cs
index 655a9217cf235bca4012546a6b891d3d5d5dc082..a345bc7e55c3abf2886d01d69191e412d99b7b72 100644
--- a/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.Designer.cs
+++ b/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.Designer.cs
@@ -28,24 +28,18 @@
 		/// </summary>
 		private void InitializeComponent()
 		{
-			this.tbStackTrace = new System.Windows.Forms.TextBox();
 			this.btnOK = new System.Windows.Forms.Button();
 			this.label1 = new System.Windows.Forms.Label();
+			this.tabControl1 = new System.Windows.Forms.TabControl();
+			this.tabPage1 = new System.Windows.Forms.TabPage();
+			this.tabPage2 = new System.Windows.Forms.TabPage();
+			this.tbStackTrace = new System.Windows.Forms.TextBox();
+			this.tbInternalStackTrace = new System.Windows.Forms.TextBox();
+			this.tabControl1.SuspendLayout();
+			this.tabPage1.SuspendLayout();
+			this.tabPage2.SuspendLayout();
 			this.SuspendLayout();
 			// 
-			// tbStackTrace
-			// 
-			this.tbStackTrace.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
-            | System.Windows.Forms.AnchorStyles.Left) 
-            | System.Windows.Forms.AnchorStyles.Right)));
-			this.tbStackTrace.Location = new System.Drawing.Point(6, 37);
-			this.tbStackTrace.Multiline = true;
-			this.tbStackTrace.Name = "tbStackTrace";
-			this.tbStackTrace.ReadOnly = true;
-			this.tbStackTrace.ScrollBars = System.Windows.Forms.ScrollBars.Both;
-			this.tbStackTrace.Size = new System.Drawing.Size(516, 183);
-			this.tbStackTrace.TabIndex = 0;
-			// 
 			// btnOK
 			// 
 			this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
@@ -66,6 +60,63 @@
 			this.label1.TabIndex = 2;
 			this.label1.Text = "There was an error while executing the script:";
 			// 
+			// tabControl1
+			// 
+			this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
+            | System.Windows.Forms.AnchorStyles.Left) 
+            | System.Windows.Forms.AnchorStyles.Right)));
+			this.tabControl1.Controls.Add(this.tabPage1);
+			this.tabControl1.Controls.Add(this.tabPage2);
+			this.tabControl1.Location = new System.Drawing.Point(6, 38);
+			this.tabControl1.Name = "tabControl1";
+			this.tabControl1.SelectedIndex = 0;
+			this.tabControl1.Size = new System.Drawing.Size(516, 182);
+			this.tabControl1.TabIndex = 3;
+			// 
+			// tabPage1
+			// 
+			this.tabPage1.Controls.Add(this.tbStackTrace);
+			this.tabPage1.Location = new System.Drawing.Point(4, 22);
+			this.tabPage1.Name = "tabPage1";
+			this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
+			this.tabPage1.Size = new System.Drawing.Size(508, 156);
+			this.tabPage1.TabIndex = 0;
+			this.tabPage1.Text = "JavaScript stack trace";
+			this.tabPage1.UseVisualStyleBackColor = true;
+			// 
+			// tabPage2
+			// 
+			this.tabPage2.Controls.Add(this.tbInternalStackTrace);
+			this.tabPage2.Location = new System.Drawing.Point(4, 22);
+			this.tabPage2.Name = "tabPage2";
+			this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
+			this.tabPage2.Size = new System.Drawing.Size(508, 169);
+			this.tabPage2.TabIndex = 1;
+			this.tabPage2.Text = "Internal stack trace";
+			this.tabPage2.UseVisualStyleBackColor = true;
+			// 
+			// tbStackTrace
+			// 
+			this.tbStackTrace.Dock = System.Windows.Forms.DockStyle.Fill;
+			this.tbStackTrace.Location = new System.Drawing.Point(3, 3);
+			this.tbStackTrace.Multiline = true;
+			this.tbStackTrace.Name = "tbStackTrace";
+			this.tbStackTrace.ReadOnly = true;
+			this.tbStackTrace.ScrollBars = System.Windows.Forms.ScrollBars.Both;
+			this.tbStackTrace.Size = new System.Drawing.Size(502, 150);
+			this.tbStackTrace.TabIndex = 1;
+			// 
+			// tbInternalStackTrace
+			// 
+			this.tbInternalStackTrace.Dock = System.Windows.Forms.DockStyle.Fill;
+			this.tbInternalStackTrace.Location = new System.Drawing.Point(3, 3);
+			this.tbInternalStackTrace.Multiline = true;
+			this.tbInternalStackTrace.Name = "tbInternalStackTrace";
+			this.tbInternalStackTrace.ReadOnly = true;
+			this.tbInternalStackTrace.ScrollBars = System.Windows.Forms.ScrollBars.Both;
+			this.tbInternalStackTrace.Size = new System.Drawing.Size(502, 163);
+			this.tbInternalStackTrace.TabIndex = 2;
+			// 
 			// UDBScriptErrorForm
 			// 
 			this.AcceptButton = this.btnOK;
@@ -73,24 +124,32 @@
 			this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
 			this.CancelButton = this.btnOK;
 			this.ClientSize = new System.Drawing.Size(534, 261);
+			this.Controls.Add(this.tabControl1);
 			this.Controls.Add(this.label1);
 			this.Controls.Add(this.btnOK);
-			this.Controls.Add(this.tbStackTrace);
 			this.MaximizeBox = false;
 			this.MinimizeBox = false;
 			this.MinimumSize = new System.Drawing.Size(550, 300);
 			this.Name = "UDBScriptErrorForm";
 			this.ShowIcon = false;
-			this.Text = "Scripr Error";
+			this.Text = "Script Error";
+			this.tabControl1.ResumeLayout(false);
+			this.tabPage1.ResumeLayout(false);
+			this.tabPage1.PerformLayout();
+			this.tabPage2.ResumeLayout(false);
+			this.tabPage2.PerformLayout();
 			this.ResumeLayout(false);
 			this.PerformLayout();
 
 		}
 
 		#endregion
-
-		private System.Windows.Forms.TextBox tbStackTrace;
 		private System.Windows.Forms.Button btnOK;
 		private System.Windows.Forms.Label label1;
+		private System.Windows.Forms.TabControl tabControl1;
+		private System.Windows.Forms.TabPage tabPage1;
+		private System.Windows.Forms.TextBox tbStackTrace;
+		private System.Windows.Forms.TabPage tabPage2;
+		private System.Windows.Forms.TextBox tbInternalStackTrace;
 	}
 }
\ No newline at end of file
diff --git a/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.cs b/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.cs
index 29aea90a4eadfed1df5872c7687b86ce606d7ea4..bc4e55e206d61fe85571b6212b5d5bf88dbc75df 100644
--- a/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.cs
+++ b/Source/Plugins/UDBScript/Windows/UDBScriptErrorForm.cs
@@ -1,24 +1,52 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Forms;
+#region ================== Copyright (c) 2022 Boris Iwanski
+
+/*
+ * This program is free software: you can redistribute it and/or modify
+ *
+ * it under the terms of the GNU General Public License as published by
+ * 
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
+ * 
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.If not, see<http://www.gnu.org/licenses/>.
+ */
+
+#endregion
+
+#region ================== Namespaces
+
 using CodeImp.DoomBuilder.Windows;
 
+#endregion
+
 namespace CodeImp.DoomBuilder.UDBScript
 {
 	public partial class UDBScriptErrorForm : DelayedForm
 	{
-		public UDBScriptErrorForm(string message, string stacktrace)
+		#region ================== Constructors
+
+		public UDBScriptErrorForm(string message, string stacktrace, string internalstacktrace)
 		{
 			InitializeComponent();
 
 			tbStackTrace.Text = message + "\r\n" + stacktrace;
 			tbStackTrace.Select(0, 0);
+
+			tbInternalStackTrace.Text = internalstacktrace;
+			tbInternalStackTrace.Select(0, 0);
+
+			if (string.IsNullOrWhiteSpace(stacktrace))
+				tabControl1.SelectedIndex = 1;
 		}
+
+		#endregion
 	}
 }
diff --git a/Source/Plugins/UDBScript/docs/create-docs.py b/Source/Plugins/UDBScript/docs/create-docs.py
index 40be062db4e893cbf91489ccb136fce9971984df..7801d5af2a04b7c01942d1ffde1950df0486436b 100644
--- a/Source/Plugins/UDBScript/docs/create-docs.py
+++ b/Source/Plugins/UDBScript/docs/create-docs.py
@@ -1,24 +1,137 @@
+from multiprocessing.forkserver import connect_to_new_process
 import xmltodict
 import glob
 import pprint
 import re
+from pathlib import Path
 
 pp = pprint.PrettyPrinter(indent=4)
 
+def get_param_text_from_xml(data, name):
+    if isinstance(data['param'], list):
+        for p in data['param']:
+            if p['@name'] == name:
+                return p['#text']
+    else:
+        return data['param']['#text']
+    return '*missing*'
+
+def gen_dts_function(data, isclass):
+    outstr = f'\t\t/**\n'
+    if 'summary' in data['xml']:
+        summary = data['xml']['summary'].split('\n')[0]
+        outstr += f'\t\t * {summary}\n'
+    for p in data['parameters']:
+        outstr += f'\t\t * @param {p["name"]} {get_param_text_from_xml(data["xml"], p["name"])}\n'
+    if 'returns' in data['xml']:
+        outstr += f'\t\t * @returns {data["xml"]["returns"]}\n'
+    outstr += f'\t\t */\n'
+    outstr += f'\t\t'
+    if not isclass:
+        outstr += 'function '
+    outstr += f'{data["name"]}('
+    for p in data['parameters']:
+        outstr += f'{p["name"]}: {convert_type_to_js(p["type"])}'
+        #if p['default'] is not None:
+        #    outstr += f' = {p["default"]}'
+        outstr += ', '
+    if outstr.endswith(', '):
+        outstr = outstr[:-2]
+    if data['returntype'] is None:
+        outstr += ');'
+    else:
+        outstr += f'): {convert_type_to_js(data["returntype"])};'
+    return outstr
+
+def gen_dts_property(data, isclass):
+    outstr = f'\t\t/**\n'
+    if 'summary' in data['xml']:
+        summary = data['xml']['summary'].split('\n')[0]
+        outstr += f'\t\t * {summary}\n'
+    outstr += f'\t\t */\n'
+    outstr += f'\t\t'
+    if 'fakedtstype' in data['xml']:
+        returntype = data['xml']['fakedtstype']
+    else:
+        returntype = data['returntype']
+    if not isclass:
+        outstr += 'let '
+    outstr += f'{data["name"]}: {convert_type_to_js(returntype)};'
+    return outstr
+
+def gen_dts_enum(data):
+    outstr = f'\t\t/**\n'
+    if 'summary' in data['xml']:
+        summary = data['xml']['summary'].split('\n')[0]
+        outstr += f'\t\t * {summary}\n'
+    outstr += f'\t\t */\n'
+    outstr += f'\t\tenum {data["name"]} {{\n'
+    for e in data['xml']['enum']:
+        outstr += f'\t\t\t/**\n'
+        outstr += f'\t\t\t * {e["#text"]}\n'
+        outstr += f'\t\t\t */\n'
+        outstr += f'\t\t\t{e["@name"]},\n'
+    outstr += '\t\t}\n'
+    return outstr
+
+def convert_type_to_js(text):
+    if '[]' in text:
+        arr = '[]'
+    else:
+        arr = ''
+
+    if text == 'double' or text == 'float' or 'int' in text:
+        return 'number' + arr
+    elif text == 'bool':
+        return 'boolean' + arr
+    elif text == 'object' or text == 'ExpandoObject':
+        return 'any' + arr
+    return text
+
 def determine_text_type(text):
-    signature = text.replace('public ', '').replace ('static ', '').replace('Wrapper', '')
+    #print('----------')
+    #print(f'text: {text}')
+    signature = text.replace('public ', '').replace('static ', '').replace('override', '').replace('Wrapper', '')
+    #print(f'signature: {signature}')
+    parameters = []
+    if 'internal' in signature:
+        return 'internal', None, None, None
+    if 'private' in signature:
+        return 'private', None, None, None
     if 'class ' in text or 'struct ' in text:
-        return 'global', None
+        return 'global', None, None, None
+    if signature.strip().startswith('enum'):
+        return 'enums', re.sub(r'[^\s]+\s+', r'', signature), None, None        
     if '(' not in text:
-        return 'properties', re.sub(r'[^\s]+\s+', r'', signature).rstrip(';')
+        returntype = signature.split(' ', 1)[0].strip()
+        return 'properties', re.sub(r'[^\s]+\s+', r'', signature).rstrip(';'), None, returntype
     signaturefields = signature.split('(')
+    if signaturefields[1] != ')':
+        for sf in signaturefields[1].rstrip(')').split(','):
+            #print(f'### {sf}')
+            ptype, pname = sf.strip().split(' ', 1)
+            if '=' in pname:
+                defaultvalue = pname.split('=')[1].strip()
+                pname = pname.split('=')[0].strip()
+            else:
+                defaultvalue = None
+            parameters.append({ 'name': pname, 'type': ptype, 'default': defaultvalue })
+    #print('parametertypes:')
+    #for pt in parametertypes:
+    #    print(f'\t{pt}')
+    returntype = signaturefields[0].strip().split(' ')[0]
+    name = re.sub(r'[^\s]+\s+', r'', signaturefields[0].strip())
+    #print(f'name: {name}')
+    #for p in parameters:
+    #    print(f'pname: {p["name"]}, ptype: {p["type"]}')
     signature = re.sub(r'[^\s]+\s+', r'', signaturefields[0]) + '(' + re.sub(r'([^\s]+) ([^,]+)(,?\s*)', r'\2\3', signaturefields[1])
+    #print(f'signature: {signature}')
     fields = text.split()
     if fields[0] == 'public' and ('Wrapper(' in fields[1] or 'QueryOptions(' in fields[1]):
-        return 'constructors', signature
+        return 'constructors', name, parameters, returntype
     elif fields[1] == 'static':
-        return 'staticmethods', signature
-    return 'methods', signature
+        return 'staticmethods', name, parameters, returntype
+    return 'methods', name, parameters, returntype
 
 def get_sorted_comment_texts(texts):
     text = ''
@@ -26,34 +139,64 @@ def get_sorted_comment_texts(texts):
         text += texts[t]
     return text
 
+def parse_attributes_line(line, attributes):
+    mo = re.match(r'\[(.+?)\((.+?)\)\]', line)
+    if mo is None:
+        return
+    attr_name = mo.group(1)
+    attr_vals = mo.group(2)
+    if attr_name not in attributes:
+        attributes[attr_name] = {}
+    for attr in attr_vals.split(','):
+        mo = re.match(r'(.+)=(.+)', attr)
+        if mo is None:
+            return
+        attributes[attr_name][mo.group(1).strip()] = mo.group(2).strip()
+
+
 topics = {
-    'GameConfiguration': [ '../API/GameConfigurationWrapper.cs' ],
-    'Angle2D': [ '../API/Angle2DWrapper.cs' ],
-    'Data': [ '../API/DataWrapper.cs' ],
-    'ImageInfo': [ '../API/ImageInfo.cs' ],
-    'Line2D': [ '../API/Line2DWrapper.cs' ],
-    'Linedef': [ '../API/LinedefWrapper.cs', '../API/MapElementWrapper.cs' ],
-    'Map': [ '../API/MapWrapper.cs' ],
-    'Sector': [ '../API/SectorWrapper.cs', '../API/MapElementWrapper.cs' ],
-    'Sidedef': [ '../API/SidedefWrapper.cs', '../API/MapElementWrapper.cs' ],
-    'Thing': [ '../API/ThingWrapper.cs', '../API/MapElementWrapper.cs' ],
-    'UDB': [ '../API/UDBWrapper.cs' ],
-    'Vector2D': [ '../API/Vector2DWrapper.cs' ],
-    'Vector3D': [ '../API/Vector3DWrapper.cs' ],
-    'Vertex': [ '../API/VertexWrapper.cs', '../API/MapElementWrapper.cs' ],
-    'VisualCamera': [ '../API/VisualCameraWrapper.cs' ],
-    'QueryOptions': [ '../QueryOptions.cs' ],
+    'GameConfiguration': { 'files': [ '../API/GameConfigurationWrapper.cs' ], 'asnamespace': True },
+    'Angle2D': { 'files': [ '../API/Angle2DWrapper.cs' ], 'asnamespace': True },
+    'BlockEntry' : { 'files': [ '../API/BlockEntryWrapper.cs' ] },
+    'BlockMapQueryResult' : { 'files': [ '../API/BlockMapQueryResult.cs' ] },
+    'BlockMap' : { 'files': [ '../API/BlockMapWrapper.cs' ] },
+    'Data': { 'files': [ '../API/DataWrapper.cs' ], 'asnamespace': True },
+    'ImageInfo': { 'files': [ '../API/ImageInfo.cs' ] },
+    'Line2D': { 'files': [ '../API/Line2DWrapper.cs' ] },
+    'Linedef': { 'files': [ '../API/LinedefWrapper.cs', '../API/MapElementWrapper.cs' ] },
+    'Map': { 'files': [ '../API/MapWrapper.cs' ], 'asnamespace': True },
+    'Plane': { 'files': [ '../API/PlaneWrapper.cs' ]},
+    'Sector': { 'files': [ '../API/SectorWrapper.cs', '../API/MapElementWrapper.cs' ] },
+    'Sidedef': { 'files': [ '../API/SidedefWrapper.cs', '../API/MapElementWrapper.cs' ] },
+    'Thing': { 'files': [ '../API/ThingWrapper.cs', '../API/MapElementWrapper.cs' ] },
+    'UDB': { 'files': [ '../API/UDBWrapper.cs' ] },
+    'Vector2D': { 'files': [ '../API/Vector2DWrapper.cs' ] },
+    'Vector3D': { 'files': [ '../API/Vector3DWrapper.cs' ] },
+    'Vertex': { 'files': [ '../API/VertexWrapper.cs', '../API/MapElementWrapper.cs' ] },
+    'VisualCamera': { 'files': [ '../API/VisualCameraWrapper.cs' ] },
+    'QueryOptions': { 'files': [ '../QueryOptions.cs' ] },
 }
 
+dtsdata = {}
+
 for topic in topics:
+    dtsd = {
+        'properties': [],
+        'constructors': [],
+        'methods': [],
+        'staticmethods': [],
+        'enums': []
+    }
     texts = {
         'global': '',
         'properties': {},
         'constructors': {},
         'methods': {},
-        'staticmethods': {}
-    }    
-    for filename in topics[topic]:
+        'staticmethods': {},
+        'enums': {}
+    }
+    memberattributes = {}
+    for filename in topics[topic]['files']:
         topicname = filename.split('\\')[-1].replace('Wrapper.cs', '')
 
         with open(filename, 'r') as file:
@@ -62,9 +205,11 @@ for topic in topics:
             incodeblock = False
             for line in file:
                 line = line.strip()
-                if line.startswith('///'):
+                if line.startswith('['):
+                    parse_attributes_line(line, memberattributes)
+                elif line.startswith('///'):
                     parsingcomment = True
-                    line = re.sub(r'^\t', r'', line.lstrip('/').lstrip(' '))
+                    line = re.sub(r'^\s', r'', line.lstrip('/'))
                     if line.startswith('```'):
                         if incodeblock:
                             xmltext += '```\n'
@@ -78,15 +223,56 @@ for topic in topics:
                     commenttext = ''
                     d = xmltodict.parse('<d>' + xmltext + '</d>')['d']
                     summary = d['summary']
-                    texttype, signature = determine_text_type(line)
+                    texttype, signature, parameters, returntype = determine_text_type(line)
                     if texttype == 'global':
                         texts['global'] = f'{summary}\n'
-                    else:
+                    elif texttype != 'internal' and texttype != 'private':
+                        if texttype == 'properties':
+                            dtsd['properties'].append({
+                                'xml': d,
+                                'name': signature,
+                                'returntype': returntype
+                            })
+                        elif texttype == 'constructors':
+                            dtsd['constructors'].append({
+                                'xml': d,
+                                'name': 'constructor',
+                                'returntype': None,
+                                'parameters': parameters
+                            })
+                        elif texttype == 'methods':
+                            dtsd['methods'].append({
+                                'xml': d,
+                                'name': signature,
+                                'returntype': returntype,
+                                'parameters': parameters
+                            })
+                        elif texttype == 'staticmethods':
+                            dtsd['staticmethods'].append({
+                                'xml': d,
+                                'name': signature,
+                                'returntype': returntype,
+                                'parameters': parameters
+                            })
+                        elif texttype == 'enums':
+                            dtsd['enums'].append({
+                                'xml': d,
+                                'name': signature
+                            })
                         commenttext += '\n---\n'
                         if 'version' in d:
                             commenttext += f'<span style="float:right;font-weight:normal;font-size:66%">Version: {d["version"]}</span>\n'
-                        commenttext += f'### {signature}\n'
-
+                        if 'UDBScriptSettings' in memberattributes:
+                            commenttext += f'<span style="float:right;font-weight:normal;font-size:66%">Version: {memberattributes["UDBScriptSettings"]["MinVersion"]}</span>\n'
+                        commenttext += f'### {signature}'
+                        if parameters is not None:
+                            commenttext += '('
+                            for param in parameters:
+                                commenttext += f'{param["name"]}: {param["type"]}, '
+                            if commenttext.endswith(', '):
+                                commenttext = commenttext[:-2]
+                            commenttext += ')'
+                        commenttext += '\n'
                         commenttext += f'{summary}\n'
                         if 'param' in d:
                             commenttext += '#### Parameters\n'
@@ -101,6 +287,19 @@ for topic in topics:
                                 if '#text' in d['param']:
                                     text = d['param']['#text'].replace('```', '\n```\n')
                                 commenttext += f'* {d["param"]["@name"]}: {text}\n'
+                        if 'enum' in d:
+                            commenttext += '#### Options\n'
+                            if isinstance(d['enum'], list):
+                                for p in d['enum']:
+                                    text = '*missing*'
+                                    if '#text' in p:
+                                        text = p['#text']
+                                    commenttext += f'* {p["@name"]}: {text}\n'
+                            else:
+                                text ='*missing*'
+                                if '#text' in d['enum']:
+                                    text = d['enum']['#text'].replace('```', '\n```\n')
+                                commenttext += f'* {d["enum"]["@name"]}: {text}\n'                                
                         if 'returns' in d:
                             commenttext += '#### Return value\n'
                             text = '*missing*'
@@ -112,8 +311,12 @@ for topic in topics:
                             texts[texttype][signature] = ''
                         texts[texttype][signature] += commenttext
                     xmltext = ''
+                    memberattributes = {}
                     parsingcomment = False
 
+        dtsdata[topic] = dtsd
+
+
     outfile = open(f'htmldoc/docs/{topic}.md', 'w')
     outfile.write(f'# {topic}\n\n')
     outfile.write(f'{texts["global"]}')
@@ -121,8 +324,61 @@ for topic in topics:
         outfile.write(f'## Constructors\n{get_sorted_comment_texts(texts["constructors"])}')
     if len(texts["staticmethods"]) > 0:
         outfile.write(f'## Static methods\n{get_sorted_comment_texts(texts["staticmethods"])}')
-    if len(texts["properties"]) > 0:        
+    if len(texts["properties"]) > 0:
         outfile.write(f'## Properties\n{get_sorted_comment_texts(texts["properties"])}')
     if len(texts["methods"]) > 0:
         outfile.write(f'## Methods\n{get_sorted_comment_texts(texts["methods"])}')
-    outfile.close()
\ No newline at end of file
+    if len(texts['enums']) > 0:
+        outfile.write(f'## Enums\n{get_sorted_comment_texts(texts["enums"])}')
+    outfile.close()
+
+
+# Create the .d.ts file
+dtsoutstr = 'declare namespace UDB {\n'
+for key in dtsdata:
+    if key == 'UDB':
+        for m in dtsdata[key]['methods']:
+            dtsoutstr += (gen_dts_function(m, False) + '\n')[1:]
+    else:
+        if 'asnamespace' in topics[key] and topics[key]['asnamespace'] is True:
+            blocktype = 'namespace'
+            isclass = False
+        else:
+            blocktype = 'class'
+            isclass = True
+
+        if len(dtsdata[key]['constructors']) > 0 or len(dtsdata[key]['methods']) > 0 or len(dtsdata[key]['properties']) > 0:
+            dtsoutstr += f'\t{blocktype} {key} {{\n'
+            # constructors
+            for c in dtsdata[key]['constructors']:
+                dtsoutstr += gen_dts_function(c, isclass) + '\n'
+            # methods
+            for m in dtsdata[key]['methods']:
+                dtsoutstr += gen_dts_function(m, isclass) + '\n'
+            # properties
+            for p in dtsdata[key]['properties']:
+                if not (p['name'] in topics and p['name'] == p['returntype']):
+                    dtsoutstr += gen_dts_property(p, isclass) + '\n'
+                else:
+                    print(f'ignoring {p["name"]} in {key} - returntype {p["returntype"]}')
+                    
+            dtsoutstr += '\t}\n'
+
+        # static methods and enums
+        if len(dtsdata[key]['staticmethods']) > 0 or len(dtsdata[key]['enums']) > 0:
+            dtsoutstr += f'\tnamespace {key} {{\n'
+            if len(dtsdata[key]['staticmethods']) > 0:
+                for m in dtsdata[key]['staticmethods']:
+                    dtsoutstr += gen_dts_function(m, False) + '\n'
+            if len(dtsdata[key]['enums']) > 0:
+                for e in dtsdata[key]['enums']:
+                    dtsoutstr += gen_dts_enum(e) + '\n'
+            dtsoutstr += '\t}\n'
+dtsoutstr += '}\n'
+
+dtsfile = Path('../../../../Build/UDBScript/udbscript.d.ts')
+
+if not dtsfile.parent.exists():
+    dtsfile.mkdir(parents=True, exist_ok=True)
+
+dtsfile.write_text(dtsoutstr)
\ No newline at end of file
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Angle2D.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Angle2D.md
index 7eb562d631b952bc0cd04e87aa355e862b9042d2..a62e5f5efe6727e560ae299cbdbb1aeff81ae464 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Angle2D.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Angle2D.md
@@ -1,9 +1,9 @@
 # Angle2D
 
-## Static methods
+## Methods
 
 ---
-### degToRad(deg)
+### degToRad(deg: double)
 Converts degrees to radians.
 #### Parameters
 * deg: Angle in degrees
@@ -11,7 +11,7 @@ Converts degrees to radians.
 Angle in radians
 
 ---
-### doomToReal(doomangle)
+### doomToReal(doomangle: int)
 Converts a Doom angle (where 0° is east) to a real world angle (where 0° is north).
 #### Parameters
 * doomangle: Doom angle in degrees
@@ -19,7 +19,7 @@ Converts a Doom angle (where 0° is east) to a real world angle (where 0° is no
 Doom angle in degrees
 
 ---
-### doomToRealRad(doomangle)
+### doomToRealRad(doomangle: int)
 Converts a Doom angle (where 0° is east) to a real world angle (where 0° is north) in radians.
 #### Parameters
 * doomangle: Doom angle in degrees
@@ -27,7 +27,7 @@ Converts a Doom angle (where 0° is east) to a real world angle (where 0° is no
 Doom angle in radians
 
 ---
-### getAngle(p1, p2, p3)
+### getAngle(p1: object, p2: object, p3: object)
 Returns the angle between three positions.
 #### Parameters
 * p1: First position
@@ -37,7 +37,7 @@ Returns the angle between three positions.
 Angle in degrees
 
 ---
-### getAngleRad(p1, p2, p3)
+### getAngleRad(p1: object, p2: object, p3: object)
 Returns the angle between three positions in radians.
 #### Parameters
 * p1: First position
@@ -47,7 +47,7 @@ Returns the angle between three positions in radians.
 Angle in radians
 
 ---
-### normalized(angle)
+### normalized(angle: int)
 Normalizes an angle in degrees so that it is bigger or equal to 0° and smaller than 360°.
 #### Parameters
 * angle: Angle in degrees
@@ -55,7 +55,7 @@ Normalizes an angle in degrees so that it is bigger or equal to 0° and smaller
 Normalized angle in degrees
 
 ---
-### normalizedRad(angle)
+### normalizedRad(angle: double)
 Normalizes an angle in radians so that it is bigger or equal to 0 and smaller than 2 Pi.
 #### Parameters
 * angle: Angle in radians
@@ -63,7 +63,7 @@ Normalizes an angle in radians so that it is bigger or equal to 0 and smaller th
 Normalized angle in radians
 
 ---
-### radToDeg(rad)
+### radToDeg(rad: double)
 Converts radians to degrees.
 #### Parameters
 * rad: Angle in radians
@@ -71,7 +71,7 @@ Converts radians to degrees.
 Angle in degrees
 
 ---
-### realToDoom(realangle)
+### realToDoom(realangle: double)
 Converts a real world angle (where 0° is north) to a Doom angle (where 0° is east).
 #### Parameters
 * realangle: Real world angle in degrees
@@ -79,8 +79,8 @@ Converts a real world angle (where 0° is north) to a Doom angle (where 0° is e
 Doom angle in degrees
 
 ---
-### realToDoomRad(realangle)
-Converts a real world  angle (where 0° is north) to a Doom angle (where 0° is east) in radians.
+### realToDoomRad(realangle: double)
+Converts a real world angle (where 0° is north) to a Doom angle (where 0° is east) in radians.
 #### Parameters
 * realangle: Real world angle in radians
 #### Return value
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockEntry.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockEntry.md
new file mode 100644
index 0000000000000000000000000000000000000000..c8a3ec30d83cf0b83bad88ed9446b55e50e40b0c
--- /dev/null
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockEntry.md
@@ -0,0 +1,32 @@
+# BlockEntry
+
+A `BlockEntry` is a single block in a `BlockMap`. It has methods to retrieve the linedefs, things, sectors, and vertices that are in this block.
+## Methods
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getLinedefs()
+Gets all `Linedef`s in the blockmap entry.
+#### Return value
+`Array` of `Linedef`s
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getSectors()
+Gets all `Sector`s in the blockmap entry.
+#### Return value
+`Array` of `Sector`s
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getThings()
+Gets all `Thing`s in the blockmap entry.
+#### Return value
+`Array` of `Thing`s
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getVertices()
+Gets all `Vertex` in the blockmap entry.
+#### Return value
+`Array` of `Vertex`
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockMap.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockMap.md
new file mode 100644
index 0000000000000000000000000000000000000000..ee5be00a8963b8ea67578798c938a4840d206c24
--- /dev/null
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockMap.md
@@ -0,0 +1,107 @@
+# BlockMap
+
+A blockmap is used to retrieve a collection of localized map elements (things, linedefs, sectors, vertices). It can help to significantly speed up costly computations that would otherwise be applied to a large portion of the map elements. The blockmap divides the map into rectangular blocks and computes which map elements are fully or partially in each block. Then you can query the blockmap about only some of those blocks, and perform any further actions only on the map elements that are in those blocks.
+
+If you for example wanted to find out which sector is at the (0, 0) position you could write something like this without using a blockmap:
+
+```js
+UDB.Map.getSectors().findIndex((s, i) => {
+	if(s.intersect([ 0, 0 ]))
+	{
+		UDB.log(`Found ${s} after ${i} tries.`)
+		return true;
+	}
+});
+```
+This loops through all sectors of the map and uses the `intersect` method to test if the point is inside the sector. While `intersect` is quite fast on its own, doing it potentially thousands of times adds up quickly, especially if you have to loop through all sectors multiple times.
+A pretty extreme example for this is the map Bastion of Chaos. The map contains nearly 32500 sectors, and the sector at (0, 0) is number 25499. That means that the above script has to run `intersect` on 25499 sectors, even on those that are not remotely near the (0, 0) position.
+
+Using a blockmap the code could look like this:
+
+```js
+const blockmap = new UDB.BlockMap();
+
+blockmap.getBlockAt([ 0, 0 ]).getSectors().findIndex((s, i) => {
+	if (s.intersect([0, 0]))
+	{
+		UDB.log(`Found ${s} after ${i} tries.`)
+		return true;
+	}
+});
+```
+As you can see the code is quite similar, the difference being that a blockmap is created, and `UDB.Map` is replaced by `blockmap.getBlockAt([ 0, 0 ])`, the latter only getting a single block from the blockmap, that only contains the map elements that are in this block. Taking Bastion of Chaos as an example again, this code finds the sector after only 20 checks, instead of the 25499 checks in the first code example.
+
+!!! note
+    Creating a blockmap has a small overhead, since it has to compute which map elements are in which blocks. This overhead, however, is quickly compensated by the time saved by not looping through irrelevant map elements. You can decrease this overhead by using a `BlockMap` constructor that only adds certain map element types to the blockmap.
+## Constructors
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### BlockMap()
+Creates a blockmap that includes linedefs, things, sectors, and vertices.
+
+```js
+// Create a blockmap that includes all linedefs, things, sectors, and vertices
+const blockmap = new UDB.BlockMap();
+```
+
+---
+### BlockMap(lines: bool, things: bool, sectors: bool, vertices: bool)
+Creates a blockmap that only includes certain map element types.
+
+```js
+// Create a blockmap that only includes sectors
+const blockmap = new UDB.BlockMap(false, false, true, false);
+```
+#### Parameters
+* lines: If linedefs should be added or not
+* things: If thigs should be added or not
+* sectors: If sectors should be added or not
+* vertices: If vertices should be added or not
+## Methods
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getBlockAt(pos: object)
+Gets the `BlockEntry` at a point. The given point can be a `Vector2D` or an `Array` of two numbers.
+
+```js
+const blockmap = new UDB.BlockMap();
+const blockentry = blockmap.getBlockAt([ 64, 128 ]);
+```
+#### Parameters
+* pos: The point to get the `BlockEntry` of
+#### Return value
+The `BlockEntry` on the given point
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getLineBlocks(v1: object, v2: object)
+Gets a `BlockMapQueryResult` for the blockmap along a line between two points. The given points can be `Vector2D`s or an `Array`s of two numbers.
+
+```js
+const blockmap = new UDB.BlockMap();
+const result = blockmap.getLineBlocks([ 0, 0 ], [ 512, 256 ]);
+```
+#### Parameters
+* v1: The first point
+* v2: The second point
+#### Return value
+The `BlockMapQueryResult` for the line between the two points
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getRectangleBlocks(x: int, y: int, width: int, height: int)
+Gets a `BlockMapQueryResult` for the blockmap in a rectangle.
+
+```js
+const blockmap = new UDB.BlockMap();
+const result = blockmap.getRectangleBlocks(0, 0, 512, 256);
+```
+#### Parameters
+* x: X position of the top-left corner of the rectangle
+* y: Y position of the top-left corner of the rectangle
+* width: Width of the rectangle
+* height: Height of the rectangle
+#### Return value
+*missing*
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockMapQueryResult.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockMapQueryResult.md
new file mode 100644
index 0000000000000000000000000000000000000000..dc0371b04ae808400e346d3168116410282c9c1f
--- /dev/null
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/BlockMapQueryResult.md
@@ -0,0 +1,54 @@
+# BlockMapQueryResult
+
+A `BlockMapQueryResult` is an object returned by the `getLineBlocks` and `getRectangleBlocks` methods of the `BlockMap` class. It has methods It has methods to retrieve the linedefs, things, sectors, and vertices that are in the queried blocks. The object is also iterable, returning each block, in cases where more fine-grained control is needed.
+
+```js
+const blockmap = new UDB.BlockMap();
+const result = blockmap.getLineBlocks([ 0, 0 ], [ 512, 256 ]);
+
+// Print all linedefs in the blocks
+result.getLinedefs().forEach(ld => UDB.log(ld));
+```
+Looping over each block:
+
+```js
+const blockmap = new UDB.BlockMap();
+const result = blockmap.getLineBlocks([ 0, 0 ], [ 512, 256 ]);
+
+for(const block of result)
+{
+	UDB.log('--- New block ---');
+	block.getLinedefs().forEach(ld => UDB.log(ld));
+}
+```
+!!! note
+    The methods to retrieve map elements from `BlockMapQueryResult` return arrays that only contain each map element once, since linedefs and sectors can be in multiple blocks, looping over a `BlockMapQueryResult` using `for...of` can return the same map elements multiple times.
+## Methods
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getLinedefs()
+Gets all `Linedef`s in the blockmap query result.
+#### Return value
+`Array` of `Linedef`s
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getSectors()
+Gets all `Sector`s in the blockmap query result.
+#### Return value
+`Array` of `Sector`s
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getThings()
+Gets all `Thing`s in the blockmap query result.
+#### Return value
+`Array` of `Thing`s
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getVertices()
+Gets all `Vertex` in the blockmap query result.
+#### Return value
+`Array` of `Vertex`
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Data.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Data.md
index 338195da07e64ac7438922b91aa80c2105e5c392..cad4c108bcf13afc8a66668babcd1cfbc5cfbed8 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Data.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Data.md
@@ -1,9 +1,9 @@
 # Data
 
-## Static methods
+## Methods
 
 ---
-### flatExists(name)
+### flatExists(name: string)
 Checks if a flat with the given name exists.
 #### Parameters
 * name: Flat name to check
@@ -11,7 +11,7 @@ Checks if a flat with the given name exists.
 `true` if the flat exists, `false` if it doesn't
 
 ---
-### getFlatInfo(name)
+### getFlatInfo(name: string)
 Returns an `ImageInfo` object for the given flat name.
 #### Parameters
 * name: Flat name to get the info for
@@ -25,7 +25,7 @@ Returns an `Array`of all flat names.
 `Array` of all flat names
 
 ---
-### getTextureInfo(name)
+### getTextureInfo(name: string)
 Returns an `ImageInfo` object for the given texture name.
 #### Parameters
 * name: Texture name to get the info for
@@ -39,7 +39,7 @@ Returns an `Array` of all texture names.
 `Array` of all texture names
 
 ---
-### textureExists(name)
+### textureExists(name: string)
 Checks if a texture with the given name exists.
 #### Parameters
 * name: Texture name to check
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Line2D.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Line2D.md
index 0156ed5d7d35a8356c18ed1d60502a3655f51378..ea258cd0193a7b961568f1ef978d1d624af76fc0 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Line2D.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Line2D.md
@@ -3,7 +3,7 @@
 ## Constructors
 
 ---
-### Line2D(v1, v2)
+### Line2D(v1: object, v2: object)
 Creates a new `Line2D` from two points.
 
 ```js
@@ -16,7 +16,17 @@ let line2 = new UDB.Line2D([ 32, 64 ], [ 96, 128 ]);
 ## Static methods
 
 ---
-### areIntersecting(a1, a2, b1, b2, bounded=true)
+### areIntersecting(line1: Line2D, line2: Line2D, bounded: bool)
+Checks if two lines intersect. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+#### Parameters
+* line1: First `Line2D`
+* line2: Second `Line2D`
+* bounded: `true` to use finite length of lines, `false` to use infinite length of lines
+#### Return value
+`true` if the lines intersect, `false` if they do not
+
+---
+### areIntersecting(a1: object, a2: object, b1: object, b2: object, bounded: bool)
 Checks if two lines defined by their start and end points intersect. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
 * a1: First point of first line
@@ -28,17 +38,7 @@ Checks if two lines defined by their start and end points intersect. If `bounded
 `true` if the lines intersect, `false` if they do not
 
 ---
-### areIntersecting(line1, line2, bounded=true)
-Checks if two lines intersect. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
-#### Parameters
-* line1: First `Line2D`
-* line2: Second `Line2D`
-* bounded: `true` to use finite length of lines, `false` to use infinite length of lines
-#### Return value
-`true` if the lines intersect, `false` if they do not
-
----
-### getCoordinatesAt(v1, v2, u)
+### getCoordinatesAt(v1: object, v2: object, u: double)
 Returns the coordinate on a line defined by its start and end points as `Vector2D`.
 #### Parameters
 * v1: First point of the line
@@ -48,7 +48,7 @@ Returns the coordinate on a line defined by its start and end points as `Vector2
 Point on the line as `Vector2D`
 
 ---
-### getDistanceToLine(v1, v2, p, bounded=true)
+### getDistanceToLine(v1: object, v2: object, p: object, bounded: bool)
 Returns the shortest distance from point `p` to the line defined by its start and end points. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
 * v1: First point of the line
@@ -59,7 +59,7 @@ Returns the shortest distance from point `p` to the line defined by its start an
 The shortest distance to the line
 
 ---
-### getDistanceToLineSq(v1, v2, p, bounded = true)
+### getDistanceToLineSq(v1: object, v2: object, p: object, bounded: bool)
 Returns the shortest square distance from point `p` to the line defined by its start and end points. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
 * v1: First point of the line
@@ -70,7 +70,7 @@ Returns the shortest square distance from point `p` to the line defined by its s
 The shortest square distance to the line
 
 ---
-### getIntersectionPoint(a1, a2, b1, b2, bounded = true)
+### getIntersectionPoint(a1: object, a2: object, b1: object, b2: object, bounded: bool)
 Returns the intersection point of two lines as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
 * a1: First point of first line
@@ -82,7 +82,7 @@ Returns the intersection point of two lines as `Vector2D`. If the lines do not i
 The intersection point as `Vector2D`
 
 ---
-### getNearestOnLine(v1, v2, p)
+### getNearestOnLine(v1: object, v2: object, p: object)
 Returns the offset coordinate on the line nearest to the given point. `0.0` being on the first point, `1.0` being on the second point, and `u = 0.5` being in the middle between the points.
 #### Parameters
 * v1: First point of the line
@@ -92,7 +92,7 @@ Returns the offset coordinate on the line nearest to the given point. `0.0` bein
 The offset value relative to the first point of the line.
 
 ---
-### getSideOfLine(v1, v2, p)
+### getSideOfLine(v1: object, v2: object, p: object)
 Returns which the of the line defined by its start and end point a given point is on.
 #### Parameters
 * v1: First point of the line
@@ -124,7 +124,7 @@ Returns the angle of the `Line2D` in radians.
 Angle of `Line2D` in radians
 
 ---
-### getCoordinatesAt(u)
+### getCoordinatesAt(u: double)
 Returns the coordinates on the line, where `u` is the position between the first and second point, `u = 0.0` being on the first point, `u = 1.0` being on the second point, and `u = 0.5` being in the middle between the points.
 #### Parameters
 * u: Position on the line, between 0.0 and 1.0
@@ -132,7 +132,7 @@ Returns the coordinates on the line, where `u` is the position between the first
 Position on the line as `Vector2D`
 
 ---
-### getIntersectionPoint(a1, a2, bounded = true)
+### getIntersectionPoint(a1: object, a2: object, bounded: bool)
 Returns the intersection point of of the given line defined by its start and end points with this line as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
 * a1: First point of first line
@@ -142,7 +142,7 @@ Returns the intersection point of of the given line defined by its start and end
 The intersection point as `Vector2D`
 
 ---
-### getIntersectionPoint(ray, bounded=true)
+### getIntersectionPoint(ray: Line2D, bounded: bool)
 Returns the intersection point of of the given line with this line as `Vector2D`. If the lines do not intersect the `x` and `y` properties of the `Vector2D` are `NaN`. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
 * ray: Other `Line2D` to get the intersection point from
@@ -163,7 +163,7 @@ Returns the perpendicular of this line as `Vector2D`.
 Perpendicular of this line as `Vector2D`
 
 ---
-### getSideOfLine(p)
+### getSideOfLine(p: object)
 Returns which the of the line defined by its start and end point a given point is on.
 #### Parameters
 * p: Point to check
@@ -171,20 +171,20 @@ Returns which the of the line defined by its start and end point a given point i
 `< 0` if `p` is on the front (right) side, `> 0` if `p` is on the back (left) side, `== 0` if `p` in on the line
 
 ---
-### isIntersecting(a1, a2, bounded = true)
-Checks if the given line intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+### isIntersecting(ray: Line2D, bounded: bool)
+Checks if the given `Line2D` intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
-* a1: First point of the line to check against
-* a2: Second point of the line to check against
+* ray: `Line2D` to check against
 * bounded: `true` (default) to use finite length of lines, `false` to use infinite length of lines
 #### Return value
-`true` if the lines intersect, `false` if they do not
+`true` if lines intersect, `false` if they do not intersect
 
 ---
-### isIntersecting(ray, bounded=true)
-Checks if the given `Line2D` intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
+### isIntersecting(a1: object, a2: object, bounded: bool)
+Checks if the given line intersects this line. If `bounded` is set to `true` (default) the finite length of the lines is used, otherwise the infinite length of the lines is used.
 #### Parameters
-* ray: `Line2D` to check against
+* a1: First point of the line to check against
+* a2: Second point of the line to check against
 * bounded: `true` (default) to use finite length of lines, `false` to use infinite length of lines
 #### Return value
-`true` if lines intersect, `false` if they do not intersect
+`true` if the lines intersect, `false` if they do not
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Linedef.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Linedef.md
index 93b055b33624e78c533bab6c9e720f2171df4a27..8a3d8d3d77ac78885f2fe4be435c967053fb0970 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Linedef.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Linedef.md
@@ -49,7 +49,14 @@ There are some restrictions, though:
 
 * it only works for fields that are not in the base UDMF standard, since those are handled directly in the respective class
 * it does not work for flags. While they are technically also UDMF fields, they are handled in the `flags` field of the respective class (where applicable)
-* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation, using the `UniValue` class:
+* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation:
+Version 5 and later:
+You can use a `BigInt`. This is done by appending a `n` to the number. Note that this is just a convenient way to define whole numbers, it still only supports 32 bit integers:
+
+```js
+s.fields.user_myintfield = 25n; // Sets the 'user_myintfield' field to an integer value of 25
+```
+In version 4 and earlier you have to use the `UniValue` class:
 
 ```js
 s.fields.user_myintfield = new UDB.UniValue(0, 25); // Sets the 'user_myintfield' field to an integer value of 25
@@ -117,7 +124,7 @@ The linedef's start `Vertex`.
 ## Methods
 
 ---
-### addTag(tag)
+### addTag(tag: int)
 Adds a tag to the `Linedef`. UDMF only. Supported game configurations only.
 #### Parameters
 * tag: Tag to add
@@ -133,7 +140,7 @@ Automatically sets the blocking and two-sided flags based on the existing `Sided
 Clears all flags.
 
 ---
-### copyPropertiesTo(other)
+### copyPropertiesTo(other: Linedef)
 Copies the properties of this `Linedef` to another `Linedef`.
 #### Parameters
 * other: The `Linedef` to copy the properties to
@@ -143,7 +150,7 @@ Copies the properties of this `Linedef` to another `Linedef`.
 Deletes the `Linedef`. Note that this will result in unclosed `Sector`s unless it has the same `Sector`s on both sides.
 
 ---
-### distanceTo(pos, bounded)
+### distanceTo(pos: object, bounded: bool)
 Gets the shortest distance from `pos` to the line.
 #### Parameters
 * pos: Point to check against
@@ -152,7 +159,7 @@ Gets the shortest distance from `pos` to the line.
 Distance to the line
 
 ---
-### distanceToSq(pos, bounded)
+### distanceToSq(pos: object, bounded: bool)
 Gets the shortest squared distance from `pos` to the line.
 #### Parameters
 * pos: Point to check against
@@ -179,7 +186,7 @@ Gets a `Vector2D` that's in the center of the `Linedef`.
 `Vector2D` in the center of the `Linedef`
 
 ---
-### getSidePoint(front)
+### getSidePoint(front: bool)
 Gets a `Vector2D` for testing on one side. The `Vector2D` is on the front when `true` is passed, otherwise on the back.
 #### Parameters
 * front: `true` for front, `false` for back
@@ -193,7 +200,7 @@ Returns an `Array` of the `Linedef`'s tags. UDMF only. Supported game configurat
 `Array` of tags
 
 ---
-### nearestOnLine(pos)
+### nearestOnLine(pos: object)
 Get a `Vector2D` that's *on* the line, closest to `pos`. `pos` can either be a `Vector2D`, or an array of numbers.
 
 ```js
@@ -206,7 +213,7 @@ var v2 = ld.nearestOnLine([ 32, 64 ]);
 `Vector2D` that's on the linedef
 
 ---
-### removeTag(tag)
+### removeTag(tag: int)
 Removes a tag from the `Linedef`. UDMF only. Supported game configurations only.
 #### Parameters
 * tag: Tag to remove
@@ -214,7 +221,7 @@ Removes a tag from the `Linedef`. UDMF only. Supported game configurations only.
 `true` when the tag was removed successfully, `false` when the tag did not exist
 
 ---
-### safeDistanceTo(pos, bounded)
+### safeDistanceTo(pos: object, bounded: bool)
 Gets the shortest "safe" distance from `pos` to the line. If `bounded` is `true` that means that the not the whole line's length will be used, but `lengthInv` less at the start and end.
 #### Parameters
 * pos: Point to check against
@@ -223,7 +230,7 @@ Gets the shortest "safe" distance from `pos` to the line. If `bounded` is `true`
 Distance to the line
 
 ---
-### safeDistanceToSq(pos, bounded)
+### safeDistanceToSq(pos: object, bounded: bool)
 Gets the shortest "safe" squared distance from `pos` to the line. If `bounded` is `true` that means that the not the whole line's length will be used, but `lengthInv` less at the start and end.
 #### Parameters
 * pos: Point to check against
@@ -232,7 +239,7 @@ Gets the shortest "safe" squared distance from `pos` to the line. If `bounded` i
 Squared distance to the line
 
 ---
-### sideOfLine(pos)
+### sideOfLine(pos: object)
 Tests which side of the `Linedef` `pos` is on. Returns < 0 for front (right) side, > for back (left) side, and 0 if `pos` is on the line.
 #### Parameters
 * pos: Point to check against
@@ -240,7 +247,7 @@ Tests which side of the `Linedef` `pos` is on. Returns < 0 for front (right) sid
 < 0 for front (right) side, > for back (left) side, and 0 if `pos` is on the line
 
 ---
-### split(pos)
+### split(pos: object)
 Splits the `Linedef` at the given position. This can either be a `Vector2D`, an array of numbers, or an existing `Vertex`. The result will be two lines, from the start `Vertex` of the `Linedef` to `pos`, and from `pos` to the end `Vertex` of the `Linedef`.
 #### Parameters
 * pos: `Vertex` to split by
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Map.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Map.md
index 785294fe62aa255144d8c119347ffdf23867a006..3f41ff67cd8b6d0fe2b1a488f301a7f394e6ba86 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Map.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Map.md
@@ -24,7 +24,7 @@ The map coordinates of the mouse position as a `Vector2D`. Read-only.
 ## Methods
 
 ---
-### clearAllMarks(mark=false)
+### clearAllMarks(mark: bool)
 Sets the `marked` property of all map elements. Can be passed `true` to mark all map elements.
 #### Parameters
 * mark: `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
@@ -34,31 +34,31 @@ Sets the `marked` property of all map elements. Can be passed `true` to mark all
 Clears all selected map elements.
 
 ---
-### clearMarkeLinedefs(mark=false)
+### clearMarkeLinedefs(mark: bool)
 Sets the `marked` property of all `Linedef`s. Can be passed `true` to mark all `Linedef`s.
 #### Parameters
 * mark: `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
 
 ---
-### clearMarkeSectors(mark = false)
+### clearMarkeSectors(mark: bool)
 Sets the `marked` property of all `Sector`s. Can be passed `true` to mark all `Sector`s.
 #### Parameters
 * mark: `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
 
 ---
-### clearMarkeSidedefs(mark = false)
+### clearMarkeSidedefs(mark: bool)
 Sets the `marked` property of all `Sidedef`s. Can be passed `true` to mark all `Sidedef`s.
 #### Parameters
 * mark: `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
 
 ---
-### clearMarkedThings(mark=false)
+### clearMarkedThings(mark: bool)
 Sets the `marked` property of all `Thing`s. Can be passed `true` to mark all `Thing`s.
 #### Parameters
 * mark: `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
 
 ---
-### clearMarkedVertices(mark=false)
+### clearMarkedVertices(mark: bool)
 Sets the `marked` property of all vertices. Can be passed `true` to mark all vertices.
 #### Parameters
 * mark: `false` to set the `marked` property to `false` (default), `true` to set the `marked` property to `true`
@@ -76,7 +76,7 @@ Clears all selected `Thing`s.
 Clears all selected vertices.
 
 ---
-### createThing(pos, type=0)
+### createThing(pos: object, type: int)
 Creates a new `Thing` at the given position. The position can be a `Vector2D`, `Vector3D`, or an `Array` of two numbers or three numbers (note that the z position only works for game configurations that support vertical pos. A thing type can be supplied optionally.
 
 ```js
@@ -92,7 +92,7 @@ var t4 = UDB.Map.createThing([ 32, 64 ], 3001); // Create an Imp
 The new `Thing`
 
 ---
-### createVertex(pos)
+### createVertex(pos: object)
 Creates a new `Vertex` at the given position. The position can be a `Vector2D` or an `Array` of two numbers.
 
 ```js
@@ -105,7 +105,7 @@ var v2 = UDB.Map.createVertex([ 32, 64 ]);
 The created `Vertex`
 
 ---
-### drawLines(data)
+### drawLines(data: object)
 Draws lines. Data has to be an `Array` of `Array` of numbers, `Vector2D`s, `Vector3D`s, or objects with x and y properties. Note that the first and last element have to be at the same positions to make a complete drawing.
 
 ```js
@@ -161,7 +161,7 @@ Returns an `Array` of all `Linedef`s in the map.
 `Array` of `Linedef`s
 
 ---
-### getMarkedLinedefs(mark = true)
+### getMarkedLinedefs(mark: bool)
 Gets all marked (default) or unmarked `Linedef`s.
 #### Parameters
 * mark: `true` to get all marked `Linedef`s (default), `false` to get all unmarked `Linedef`s
@@ -169,7 +169,7 @@ Gets all marked (default) or unmarked `Linedef`s.
 *missing*
 
 ---
-### getMarkedSectors(mark = true)
+### getMarkedSectors(mark: bool)
 Gets all marked (default) or unmarked `Sector`s.
 #### Parameters
 * mark: `true` to get all marked `Sector`s (default), `false` to get all unmarked `Sector`s
@@ -177,7 +177,7 @@ Gets all marked (default) or unmarked `Sector`s.
 *missing*
 
 ---
-### getMarkedSidedefs(mark = true)
+### getMarkedSidedefs(mark: bool)
 Gets all marked (default) or unmarked `Sidedef`s.
 #### Parameters
 * mark: `true` to get all marked `Sidedef`s (default), `false` to get all unmarked `Sidedef`s
@@ -185,7 +185,7 @@ Gets all marked (default) or unmarked `Sidedef`s.
 *missing*
 
 ---
-### getMarkedThings(mark = true)
+### getMarkedThings(mark: bool)
 Gets all marked (default) or unmarked `Thing`s.
 #### Parameters
 * mark: `true` to get all marked `Thing`s (default), `false` to get all unmarked `Thing`s
@@ -193,7 +193,7 @@ Gets all marked (default) or unmarked `Thing`s.
 *missing*
 
 ---
-### getMarkedVertices(mark=true)
+### getMarkedVertices(mark: bool)
 Gets all marked (default) or unmarked vertices.
 #### Parameters
 * mark: `true` to get all marked vertices (default), `false` to get all unmarked vertices
@@ -201,7 +201,7 @@ Gets all marked (default) or unmarked vertices.
 *missing*
 
 ---
-### getMultipleNewTags(count)
+### getMultipleNewTags(count: int)
 Gets multiple new tags.
 #### Parameters
 * count: Number of tags to get
@@ -209,7 +209,7 @@ Gets multiple new tags.
 `Array` of the new tags
 
 ---
-### getNewTag(usedtags = null)
+### getNewTag(usedtags: int[])
 Gets a new tag.
 #### Parameters
 * usedtags: `Array` of tags to skip
@@ -223,7 +223,7 @@ Returns an `Array` of all `Sector`s in the map.
 `Array` of `Sector`s
 
 ---
-### getSelectedLinedefs(selected = true)
+### getSelectedLinedefs(selected: bool)
 Gets all selected (default) or unselected `Linedef`s.
 #### Parameters
 * selected: `true` to get all selected `Linedef`s, `false` to get all unselected ones
@@ -255,7 +255,7 @@ Gets the currently selected `Vertex`s *or*, if no `Vertex`s are selected, a curr
 `Array` of `Vertex`
 
 ---
-### getSelectedSectors(selected = true)
+### getSelectedSectors(selected: bool)
 Gets all selected (default) or unselected `Sector`s.
 #### Parameters
 * selected: `true` to get all selected `Sector`s, `false` to get all unselected ones
@@ -263,7 +263,7 @@ Gets all selected (default) or unselected `Sector`s.
 `Array` of `Sector`s
 
 ---
-### getSelectedThings(selected = true)
+### getSelectedThings(selected: bool)
 Gets all selected (default) or unselected `Thing`s.
 #### Parameters
 * selected: `true` to get all selected `Thing`s, `false` to get all unselected ones
@@ -271,7 +271,7 @@ Gets all selected (default) or unselected `Thing`s.
 `Array` of `Thing`s
 
 ---
-### getSelectedVertices(selected=true)
+### getSelectedVertices(selected: bool)
 Gets all selected (default) or unselected vertices.
 #### Parameters
 * selected: `true` to get all selected vertices, `false` to get all unselected ones
@@ -285,7 +285,7 @@ Returns an `Array` of all `Sidedef`s in the map.
 `Array` of `Sidedef`s
 
 ---
-### getSidedefsFromSelectedLinedefs(selected = true)
+### getSidedefsFromSelectedLinedefs(selected: bool)
 Gets all `Sidedef`s from the selected `Linedef`s.
 In classic modes this will return both sidedefs of 2-sided lines, in visual mode it will only return the actually selected `Sidedef`.
 #### Parameters
@@ -338,43 +338,43 @@ Inverts the `marked` property of all `Thing`s.
 Inverts the `marked` property of all vertices.
 
 ---
-### joinSectors(sectors)
+### joinSectors(sectors: Sector[])
 Joins `Sector`s, keeping lines shared by the `Sector`s. All `Sector`s will be joined with the first `Sector` in the array.
 #### Parameters
 * sectors: `Array` of `Sector`s
 
 ---
-### markSelectedLinedefs(mark = true)
+### markSelectedLinedefs(mark: bool)
 Marks (default) or unmarks all selected `Linedef`s.
 #### Parameters
 * mark: `true` to mark all selected `Linedef`s (default), `false` to unmark
 
 ---
-### markSelectedSectors(mark = true)
+### markSelectedSectors(mark: bool)
 Marks (default) or unmarks all selected `Sector`s.
 #### Parameters
 * mark: `true` to mark all selected `Sector`s (default), `false` to unmark
 
 ---
-### markSelectedThings(mark = true)
+### markSelectedThings(mark: bool)
 Marks (default) or unmarks all selected `Thing`s.
 #### Parameters
 * mark: `true` to mark all selected `Thing`s (default), `false` to unmark
 
 ---
-### markSelectedVertices(mark=true)
+### markSelectedVertices(mark: bool)
 Marks (default) or unmarks all selected vertices.
 #### Parameters
 * mark: `true` to mark all selected vertices (default), `false` to unmark
 
 ---
-### mergeSectors(sectors)
+### mergeSectors(sectors: Sector[])
 Merges `Sector`s, deleting lines shared by the `Sector`s. All `Sector`s will be merged into the first `Sector` in the array.
 #### Parameters
 * sectors: `Array` of `Sector`s
 
 ---
-### nearestLinedef(pos, maxrange = double.NaN)
+### nearestLinedef(pos: object, maxrange: double)
 Gets the `Linedef` that's nearest to the specified position.
 #### Parameters
 * pos: Position to check against
@@ -383,7 +383,7 @@ Gets the `Linedef` that's nearest to the specified position.
 Nearest `Linedef`
 
 ---
-### nearestSidedef(pos)
+### nearestSidedef(pos: object)
 Gets the `Sidedef` that's nearest to the specified position.
 #### Parameters
 * pos: Position to check against
@@ -392,7 +392,7 @@ Gets the `Sidedef` that's nearest to the specified position.
 Nearest `Sidedef`
 
 ---
-### nearestThing(pos, maxrange = double.NaN)
+### nearestThing(pos: object, maxrange: double)
 Gets the `Thing` that's nearest to the specified position.
 #### Parameters
 * pos: Position to check against
@@ -401,7 +401,7 @@ Gets the `Thing` that's nearest to the specified position.
 Nearest `Linedef`
 
 ---
-### nearestVertex(pos, maxrange = double.NaN)
+### nearestVertex(pos: object, maxrange: double)
 Gets the `Vertex` that's nearest to the specified position.
 #### Parameters
 * pos: Position to check against
@@ -410,13 +410,13 @@ Gets the `Vertex` that's nearest to the specified position.
 Nearest `Vertex`
 
 ---
-### snapAllToAccuracy(usepreciseposition = true)
+### snapAllToAccuracy(usepreciseposition: bool)
 Snaps all vertices and things to the map format accuracy. Call this to ensure the vertices and things are at valid coordinates.
 #### Parameters
 * usepreciseposition: `true` if decimal places defined by the map format should be used, `false` if no decimal places should be used
 
 ---
-### snappedToGrid(pos)
+### snappedToGrid(pos: object)
 Returns the given point snapped to the current grid.
 #### Parameters
 * pos: Point that should be snapped to the grid
@@ -424,9 +424,23 @@ Returns the given point snapped to the current grid.
 Snapped position as `Vector2D`
 
 ---
-### stitchGeometry(mergemode = MergeGeometryMode.CLASSIC)
+### stitchGeometry(mergemode: MergeGeometryMode)
 Stitches marked geometry with non-marked geometry.
 #### Parameters
-* mergemode: Mode to merge by
+* mergemode: Mode to merge by as `MergeGeometryMode`
 #### Return value
 `true` if successful, `false` if failed
+## Enums
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### MergeGeometryMode
+How geometry should be merged when geometry is stitched.
+
+```js
+UDB.Map.stitchGeometry(UDB.Map.MergeometryMode.MERGE);
+```
+#### Options
+* CLASSIC: Merge vertices only
+* MERGE: Merge vertices and lines
+* REPLACE: Merge vertices and lines, replacing sector geometry
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Plane.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Plane.md
new file mode 100644
index 0000000000000000000000000000000000000000..ea2921d065abcff24684bbf0d0e822b0a5cfcfd6
--- /dev/null
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Plane.md
@@ -0,0 +1,124 @@
+# Plane
+
+## Constructors
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### Plane(normal: object, offset: double)
+Creates a new `Plane` from a normal and an offset. The normal vector has to be `Vector3D`, `Array`s of 3 numbers, or an object with x, y, and z properties.
+
+```js
+let plane1 = new UDB.Plane(new Vector3D(0.0, -0.707, 0.707), 32);
+let plane2 = new UDB.Plane([ 0.0, -0.707, 0.707 ], 32);
+```
+#### Parameters
+* normal: Normal vector of the plane
+* offset: Distance of the plane from the origin
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### Plane(p1: object, p2: object, p3: object, up: bool)
+Creates a new `Plane` from 3 points. The points have to be `Vector3D`, `Array`s of 3 numbers, or an object with x, y, and z properties.
+
+```js
+let plane1 = new UDB.Plane(new Vector3D(0, 0, 0), new Vector3D(64, 0, 0), new Vector3D(64, 64, 32), true);
+let plane2 = new UDB.Plane([ 0, 0, 0 ], [ 64, 0, 0 ], [ 64, 64, 32 ], true);
+```
+#### Parameters
+* p1: First point
+* p2: Second point
+* p3: Thrid point
+* up: `true` if plane is pointing up, `false` if pointing down
+## Properties
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### a
+The `a` value of the plane equation. This is the `x` value of the normal vector.
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### b
+The `b` value of the plane equation. This is the `y` value of the normal vector.
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### c
+The `c` value of the plane equation. This is the `z` value of the normal vector.
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### d
+The `d` value of the plane equation. This is the same as the `offset` value.
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### normal
+The plane's normal vector.
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### offset
+The distance of the plane along the normal vector.
+## Methods
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### closestOnPlane(p: object)
+Returns the point that's closest to the given point on the `Plane`. The given point can be a `Vector3D` or an `Array` of three numbers.
+
+```js
+const plane = new UDB.Plane([ 0, 0, 0 ], [ 32, 0, 0 ], [ 32, 32, 16 ], true);
+UDB.log(plane.closestOnPlane([ 16, 16, 32 ])); // Prints '16, 25.6, 12.8'
+```
+#### Parameters
+* p: Point to get the closest position from
+#### Return value
+Point as `Vector3D` on the plane closest to the given point
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### distance(p: object)
+Computes the distance between the `Plane` and a point. The given point can be a `Vector3D` or an `Array` of three numbers. A result greater than 0 means the point is on the front of the plane, less than 0 means the point is behind the plane.
+
+```js
+const plane = new UDB.Plane([ 0, 0, 0 ], [ 32, 0, 0 ], [ 32, 32, 16 ], true);
+UDB.log(plane.distance([ 16, 16, 32 ])); // Prints '21.466252583998'
+```
+#### Parameters
+* p: Point to compute the distnace to
+#### Return value
+Distance between the `Plane` and the point as `number`
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getIntersection(from: object, to: object)
+Checks if the line between `from` and `to` intersects the plane.
+
+It returns an `Array`, where the first element is a `bool` vaue indicating if there is an intersector, and the second element is the position of the intersection on the line between the two points.
+
+
+```js
+const plane = new UDB.Plane([ 0, 0, 1 ], 0);
+const [intersecting, u] = plane.getIntersection([0, 0, 32], [0, 0, -32]);
+UDB.log(`${intersecting} / ${u}`); // Prints "true / 0.5"
+```
+#### Parameters
+* from: `Vector3D` of the start of the line
+* to: `Vector3D` of the end of the line
+#### Return value
+*missing*
+
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getZ(p: object)
+Returns the position on the z axis of the plane for the given point. The given point can be a `Vector2D` or an `Array` of two numbers.
+
+```js
+const plane = new UDB.Plane([ 0, 0, 0 ], [ 32, 0, 0 ], [ 32, 32, 16 ], true);
+UDB.log(plane.getZ([ 16, 16 ])); // Prints '8'
+```
+#### Parameters
+* p: Point to get the z position from
+#### Return value
+*missing*
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/QueryOptions.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/QueryOptions.md
index 7e2e6b42b9d2e94782e4597e341fcbf8e7c06784..baa9cc61654adcdb399249b24939f062e0cfddf0 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/QueryOptions.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/QueryOptions.md
@@ -26,7 +26,7 @@ Object containing all the added options as properties.
 ## Methods
 
 ---
-### addOption(name, description, type, defaultvalue)
+### addOption(name: string, description: string, type: int, defaultvalue: object)
 Adds a parameter to query
 #### Parameters
 * name: Name of the variable that the queried value is stored in
@@ -35,7 +35,7 @@ Adds a parameter to query
 * defaultvalue: Default value of the parameter
 
 ---
-### addOption(name, description, type, defaultvalue, enumvalues)
+### addOption(name: string, description: string, type: int, defaultvalue: object, enumvalues: object)
 Adds a parameter to query
 #### Parameters
 * name: Name of the variable that the queried value is stored in
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Sector.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Sector.md
index dd3a8c6c0d6f5f11a3586588a5d0bfbc4086627b..2f30e8d13bdb4dda1a2d4dc0c94880e6327a42c0 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Sector.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Sector.md
@@ -47,7 +47,14 @@ There are some restrictions, though:
 
 * it only works for fields that are not in the base UDMF standard, since those are handled directly in the respective class
 * it does not work for flags. While they are technically also UDMF fields, they are handled in the `flags` field of the respective class (where applicable)
-* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation, using the `UniValue` class:
+* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation:
+Version 5 and later:
+You can use a `BigInt`. This is done by appending a `n` to the number. Note that this is just a convenient way to define whole numbers, it still only supports 32 bit integers:
+
+```js
+s.fields.user_myintfield = 25n; // Sets the 'user_myintfield' field to an integer value of 25
+```
+In version 4 and earlier you have to use the `UniValue` class:
 
 ```js
 s.fields.user_myintfield = new UDB.UniValue(0, 25); // Sets the 'user_myintfield' field to an integer value of 25
@@ -112,7 +119,7 @@ The `Sector`'s tag.
 ## Methods
 
 ---
-### addTag(tag)
+### addTag(tag: int)
 Adds a tag to the `Sector`. UDMF only. Supported game configurations only.
 #### Parameters
 * tag: Tag to add
@@ -124,7 +131,7 @@ Adds a tag to the `Sector`. UDMF only. Supported game configurations only.
 Clears all flags.
 
 ---
-### copyPropertiesTo(s)
+### copyPropertiesTo(s: Sector)
 Copies the properties from this `Sector` to another.
 #### Parameters
 * s: the `Sector` to copy the properties to
@@ -145,6 +152,24 @@ Gets the floor's slope vector.
 #### Return value
 The floor's slope normal as a `Vector3D`
 
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### getLabelPositions()
+Returns an `Array` of `Vector2D` of label positions for the `Sector`. This are the positions where for example selection number or tags are shown.
+
+This example adds an imp to the label position of each sector in the map:
+
+```js
+UDB.Map.getSectors().forEach(s => {
+	const positions = s.getLabelPositions();
+	if(positions.length > 0)
+		UDB.Map.createThing(positions[0], 3001);
+});
+```
+#### Return value
+`Array` of `Vector2D` of all label positions
+
 ---
 ### getSidedefs()
 Returns an `Array` of all `Sidedef`s of the `Sector`.
@@ -164,7 +189,7 @@ Gets an array of `Vector2D` arrays, representing the vertices of the triangulate
 Array of `Vector2D` arrays
 
 ---
-### intersect(p)
+### intersect(p: object)
 Checks if the given point is in this `Sector` or not. The given point can be a `Vector2D` or an `Array` of two numbers.
 
 ```js
@@ -180,13 +205,13 @@ if(s.intersect([ 32, 64 ]))
 `true` if the point is in the `Sector`, `false` if it isn't
 
 ---
-### join(other)
+### join(other: Sector)
 Joins this `Sector` with another `Sector`. Lines shared between the sectors will not be removed.
 #### Parameters
 * other: Sector to join with
 
 ---
-### removeTag(tag)
+### removeTag(tag: int)
 Removes a tag from the `Sector`. UDMF only. Supported game configurations only.
 #### Parameters
 * tag: Tag to remove
@@ -194,13 +219,13 @@ Removes a tag from the `Sector`. UDMF only. Supported game configurations only.
 `true` when the tag was removed successfully, `false` when the tag did not exist
 
 ---
-### setCeilingSlope(normal)
+### setCeilingSlope(normal: object)
 Sets the ceiling's slope vector. The vector has to be normalized.
 #### Parameters
 * normal: The new slope vector as `Vector3D`
 
 ---
-### setFloorSlope(normal)
+### setFloorSlope(normal: object)
 Sets the floor's slope vector. The vector has to be normalized.
 #### Parameters
 * normal: The new slope vector as `Vector3D`
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Sidedef.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Sidedef.md
index 15c26a81491ebf4178b424e523e847a5d81930d6..4b7d4ce07ad052891c79c7a7708d8676cf3b2f8f 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Sidedef.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Sidedef.md
@@ -29,7 +29,14 @@ There are some restrictions, though:
 
 * it only works for fields that are not in the base UDMF standard, since those are handled directly in the respective class
 * it does not work for flags. While they are technically also UDMF fields, they are handled in the `flags` field of the respective class (where applicable)
-* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation, using the `UniValue` class:
+* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation:
+Version 5 and later:
+You can use a `BigInt`. This is done by appending a `n` to the number. Note that this is just a convenient way to define whole numbers, it still only supports 32 bit integers:
+
+```js
+s.fields.user_myintfield = 25n; // Sets the 'user_myintfield' field to an integer value of 25
+```
+In version 4 and earlier you have to use the `UniValue` class:
 
 ```js
 s.fields.user_myintfield = new UDB.UniValue(0, 25); // Sets the 'user_myintfield' field to an integer value of 25
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Thing.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Thing.md
index 4daccd4c1ff2dae11beca2acec750b68ac203ab5..8a2c1661278f68eef0a1315b6cf1ad46fb7745c3 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Thing.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Thing.md
@@ -37,7 +37,14 @@ There are some restrictions, though:
 
 * it only works for fields that are not in the base UDMF standard, since those are handled directly in the respective class
 * it does not work for flags. While they are technically also UDMF fields, they are handled in the `flags` field of the respective class (where applicable)
-* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation, using the `UniValue` class:
+* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation:
+Version 5 and later:
+You can use a `BigInt`. This is done by appending a `n` to the number. Note that this is just a convenient way to define whole numbers, it still only supports 32 bit integers:
+
+```js
+s.fields.user_myintfield = 25n; // Sets the 'user_myintfield' field to an integer value of 25
+```
+In version 4 and earlier you have to use the `UniValue` class:
 
 ```js
 s.fields.user_myintfield = new UDB.UniValue(0, 25); // Sets the 'user_myintfield' field to an integer value of 25
@@ -115,7 +122,7 @@ Type of the `Thing`.
 Clears all flags.
 
 ---
-### copyPropertiesTo(t)
+### copyPropertiesTo(t: Thing)
 Copies the properties from this `Thing` to another.
 #### Parameters
 * t: The `Thing` to copy the properties to
@@ -125,7 +132,7 @@ Copies the properties from this `Thing` to another.
 Deletes the `Thing`.
 
 ---
-### distanceTo(pos)
+### distanceTo(pos: object)
 Gets the distance between this `Thing` and the given point. The point can be either a `Vector2D` or an array of numbers.
 
 ```js
@@ -138,7 +145,7 @@ t.distanceToSq([ 32, 64 ]);
 Distance to `pos`
 
 ---
-### distanceToSq(pos)
+### distanceToSq(pos: object)
 Gets the squared distance between this `Thing` and the given point.
 The point can be either a `Vector2D` or an array of numbers.
 
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/UDB.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/UDB.md
index 6cb44c5a7ef440932f17af54022f9f3555a12741..4c53aeb5334fc200cbdd3b711b6adc8b4326cbcb 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/UDB.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/UDB.md
@@ -10,6 +10,11 @@ Class containing methods related to angles. See [Angle2D](Angle2D.md) for more i
 let rad = UDB.Angle2D.degToRad(46);
 ```
 
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### BlockMap
+Instantiable class that contains methods related to blockmaps. See [BlockMap][BlockMap.md) for more information.
+
 ---
 ### Data
 Class containing methods related to the game data. See [Data](Data.md) for more information.
@@ -38,6 +43,11 @@ Object containing methods related to the map. See [Map](Map.md) for more informa
 let sectors = UDB.Map.getSelectedOrHighlightedSectors();
 ```
 
+---
+<span style="float:right;font-weight:normal;font-size:66%">Version: 5</span>
+### Plane
+Instantiable class that contains methods related to a three-dimensional Plane. See [Plane](Plane.md) for more information.
+
 ---
 ### QueryOptions
 Class containing methods and properties related to querying options from the user at runtime. See [QueryOptions](QueryOptions.md) for more information.
@@ -65,40 +75,44 @@ let v = new UDB.Vector2D(32, 64);
 ---
 ### Vector3D
 Instantiable class that contains methods related to three-dimensional vectors. See [Vector3D](Vector3D.md) for more information.
+
+```js
+let v = new UDB.Vector3D(32, 64, 128);
+```
 ## Methods
 
 ---
-### die(s=null)
+### die(s: string)
 Exist the script prematurely with undoing its changes.
 #### Parameters
 * s: Text to show in the status bar (optional)
 
 ---
-### exit(s=null)
+### exit(s: string)
 Exist the script prematurely without undoing its changes.
 #### Parameters
 * s: Text to show in the status bar (optional)
 
 ---
-### log(text)
+### log(text: object)
 Adds a line to the script log. Also shows the script running dialog.
 #### Parameters
 * text: Line to add to the script log
 
 ---
-### setProgress(value)
+### setProgress(value: int)
 Set the progress of the script in percent. Value can be between 0 and 100. Also shows the script running dialog.
 #### Parameters
 * value: Number between 0 and 100
 
 ---
-### showMessage(message)
+### showMessage(message: object)
 Shows a message box with an "OK" button.
 #### Parameters
 * message: Message to show
 
 ---
-### showMessageYesNo(message)
+### showMessageYesNo(message: object)
 Shows a message box with an "Yes" and "No" button.
 #### Parameters
 * message: Message to show
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector2D.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector2D.md
index 88f5456cd048bf3bd5b7988c19e3bd31c3b92aef..d59951eea956a623a1fdd00f3ba42c7a096bc857 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector2D.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector2D.md
@@ -3,29 +3,29 @@
 ## Constructors
 
 ---
-### Vector2D(v)
-Creates a new `Vector2D` from a point.
+### Vector2D(x: double, y: double)
+Creates a new `Vector2D` from x and y coordinates
 
 ```js
-let v = new UDB.Vector2D([ 32, 64 ]);
+let v = new UDB.Vector2D(32, 64);
 ```
 #### Parameters
-* v: The vector to create the `Vector2D` from
+* x: The x coordinate
+* y: The y coordinate
 
 ---
-### Vector2D(x, y)
-Creates a new `Vector2D` from x and y coordinates
+### Vector2D(v: object)
+Creates a new `Vector2D` from a point.
 
 ```js
-let v = new UDB.Vector2D(32, 64);
+let v = new UDB.Vector2D([ 32, 64 ]);
 ```
 #### Parameters
-* x: The x coordinate
-* y: The y coordinate
+* v: The vector to create the `Vector2D` from
 ## Static methods
 
 ---
-### crossProduct(a, b)
+### crossProduct(a: object, b: object)
 Returns the cross product of two `Vector2D`s.
 #### Parameters
 * a: First `Vector2D`
@@ -34,7 +34,7 @@ Returns the cross product of two `Vector2D`s.
 Cross product of the two vectors as `Vector2D`
 
 ---
-### dotProduct(a, b)
+### dotProduct(a: Vector2D, b: Vector2D)
 Returns the dot product of two `Vector2D`s.
 #### Parameters
 * a: First `Vector2D`
@@ -43,7 +43,7 @@ Returns the dot product of two `Vector2D`s.
 The dot product of the two vectors
 
 ---
-### fromAngle(angle)
+### fromAngle(angle: double)
 Creates a `Vector2D` from an angle in degrees,
 #### Parameters
 * angle: Angle in degrees
@@ -51,7 +51,7 @@ Creates a `Vector2D` from an angle in degrees,
 Vector as `Vector2D`
 
 ---
-### fromAngleRad(angle)
+### fromAngleRad(angle: double)
 Creates a `Vector2D` from an angle in radians,
 #### Parameters
 * angle: Angle in radians
@@ -59,7 +59,7 @@ Creates a `Vector2D` from an angle in radians,
 Vector as `Vector2D`
 
 ---
-### getAngle(a, b)
+### getAngle(a: object, b: object)
 Returns the angle between two `Vector2D`s in degrees.
 #### Parameters
 * a: First `Vector2D`
@@ -68,7 +68,7 @@ Returns the angle between two `Vector2D`s in degrees.
 Angle in degrees
 
 ---
-### getAngleRad(a, b)
+### getAngleRad(a: object, b: object)
 Returns the angle between two `Vector2D`s in radians
 #### Parameters
 * a: First `Vector2D`
@@ -77,7 +77,7 @@ Returns the angle between two `Vector2D`s in radians
 Angle in radians
 
 ---
-### getDistance(a, b)
+### getDistance(a: object, b: object)
 Returns the distance between two `Vector2D`s.
 #### Parameters
 * a: First `Vector2D`
@@ -86,7 +86,7 @@ Returns the distance between two `Vector2D`s.
 The distance
 
 ---
-### getDistanceSq(a, b)
+### getDistanceSq(a: object, b: object)
 Returns the square distance between two `Vector2D`s.
 #### Parameters
 * a: First `Vector2D`
@@ -95,7 +95,7 @@ Returns the square distance between two `Vector2D`s.
 The squared distance
 
 ---
-### reflect(v, m)
+### reflect(v: object, m: object)
 Reflects a `Vector2D` over a mirror `Vector2D`.
 #### Parameters
 * v: `Vector2D` to reflect
@@ -104,7 +104,7 @@ Reflects a `Vector2D` over a mirror `Vector2D`.
 The reflected vector as `Vector2D`
 
 ---
-### reversed(v)
+### reversed(v: object)
 Returns a reversed `Vector2D`.
 #### Parameters
 * v: `Vector2D` to reverse
@@ -134,7 +134,7 @@ Returns the angle of the `Vector2D` in radians.
 The angle of the `Vector2D` in radians
 
 ---
-### getInverseTransformed(invoffsetx, invoffsety, invscalex, invscaley)
+### getInverseTransformed(invoffsetx: double, invoffsety: double, invscalex: double, invscaley: double)
 Returns the inverse transformed vector as `Vector2D`.
 #### Parameters
 * invoffsetx: X offset
@@ -169,7 +169,7 @@ Returns the perpendicular to the `Vector2D`.
 The perpendicular as `Vector2D`
 
 ---
-### getRotated(theta)
+### getRotated(theta: double)
 Returns the rotated vector as `Vector2D`.
 #### Parameters
 * theta: Angle in degree to rotate by
@@ -177,7 +177,7 @@ Returns the rotated vector as `Vector2D`.
 The rotated `Vector2D`
 
 ---
-### getRotatedRad(theta)
+### getRotatedRad(theta: double)
 Returns the rotated vector as `Vector2D`.
 #### Parameters
 * theta: Angle in radians to rotate by
@@ -191,7 +191,7 @@ Returns a `Vector2D` with the sign of all components.
 A `Vector2D` with the sign of all components
 
 ---
-### getTransformed(offsetx, offsety, scalex, scaley)
+### getTransformed(offsetx: double, offsety: double, scalex: double, scaley: double)
 Returns the transformed vector as `Vector2D`.
 #### Parameters
 * offsetx: X offset
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector3D.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector3D.md
index 76c32d97fd73925f0ef82b3ad85100d4fa4e9581..2a25180a00a7ea66995a1fc443d8910c6489d175 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector3D.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Vector3D.md
@@ -3,30 +3,30 @@
 ## Constructors
 
 ---
-### Vector3D(v)
-Creates a new `Vector3D` from a point.
+### Vector3D(x: double, y: double, z: double)
+Creates a new `Vector3D` from x and y coordinates
 
 ```js
-let v = new UDB.Vector3D([ 32, 64, 128 ]);
+let v = new UDB.Vector3D(32, 64, 128);
 ```
 #### Parameters
-* v: The vector to create the `Vector3D` from
+* x: The x coordinate
+* y: The y coordinate
+* z: The z coordinate
 
 ---
-### Vector3D(x, y, z)
-Creates a new `Vector3D` from x and y coordinates
+### Vector3D(v: object)
+Creates a new `Vector3D` from a point.
 
 ```js
-let v = new UDB.Vector3D(32, 64, 128);
+let v = new UDB.Vector3D([ 32, 64, 128 ]);
 ```
 #### Parameters
-* x: The x coordinate
-* y: The y coordinate
-* z: The z coordinate
+* v: The vector to create the `Vector3D` from
 ## Static methods
 
 ---
-### crossProduct(a, b)
+### crossProduct(a: object, b: object)
 Returns the cross product of two `Vector3D`s.
 #### Parameters
 * a: First `Vector3D`
@@ -35,7 +35,7 @@ Returns the cross product of two `Vector3D`s.
 Cross product of the two vectors as `Vector3D`
 
 ---
-### dotProduct(a, b)
+### dotProduct(a: Vector3D, b: Vector3D)
 Returns the dot product of two `Vector3D`s.
 #### Parameters
 * a: First `Vector3D`
@@ -44,7 +44,7 @@ Returns the dot product of two `Vector3D`s.
 The dot product of the two vectors
 
 ---
-### fromAngleXY(angle)
+### fromAngleXY(angle: double)
 Creates a `Vector3D` from an angle in radians,
 #### Parameters
 * angle: Angle on the x/y axes in degrees
@@ -52,7 +52,7 @@ Creates a `Vector3D` from an angle in radians,
 Vector as `Vector3D`
 
 ---
-### fromAngleXYRad(angle)
+### fromAngleXYRad(angle: double)
 Creates a `Vector3D` from an angle in radians
 #### Parameters
 * angle: Angle on the x/y axes in radians
@@ -60,7 +60,7 @@ Creates a `Vector3D` from an angle in radians
 Vector as `Vector3D`
 
 ---
-### fromAngleXYZ(anglexy, anglez)
+### fromAngleXYZ(anglexy: double, anglez: double)
 Creates a `Vector3D` from two angles in degrees
 #### Parameters
 * anglexy: Angle on the x/y axes in radians
@@ -69,7 +69,7 @@ Creates a `Vector3D` from two angles in degrees
 Vector as `Vector3D`
 
 ---
-### fromAngleXYZRad(anglexy, anglez)
+### fromAngleXYZRad(anglexy: double, anglez: double)
 Creates a `Vector3D` from two angles in radians
 #### Parameters
 * anglexy: Angle on the x/y axes in radians
@@ -78,7 +78,7 @@ Creates a `Vector3D` from two angles in radians
 Vector as `Vector3D`
 
 ---
-### reflect(v, m)
+### reflect(v: object, m: object)
 Reflects a `Vector3D` over a mirror `Vector3D`.
 #### Parameters
 * v: `Vector3D` to reflect
@@ -87,7 +87,7 @@ Reflects a `Vector3D` over a mirror `Vector3D`.
 The reflected vector as `Vector3D`
 
 ---
-### reversed(v)
+### reversed(v: object)
 Returns a reversed `Vector3D`.
 #### Parameters
 * v: `Vector3D` to reverse
@@ -151,7 +151,7 @@ Returns the normal of the `Vector3D`.
 The normal as `Vector3D`
 
 ---
-### getScaled(scale)
+### getScaled(scale: double)
 Return the scaled `Vector3D`.
 #### Parameters
 * scale: Scale, where 1.0 is unscaled
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/Vertex.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/Vertex.md
index e3f64818d5f417efe51c7926649c213da59f8b6d..959354fee6711a7b31e2b45c56b3164a977df32e 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/Vertex.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/Vertex.md
@@ -25,7 +25,14 @@ There are some restrictions, though:
 
 * it only works for fields that are not in the base UDMF standard, since those are handled directly in the respective class
 * it does not work for flags. While they are technically also UDMF fields, they are handled in the `flags` field of the respective class (where applicable)
-* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation, using the `UniValue` class:
+* JavaScript does not distinguish between integer and floating point numbers, it only has floating point numbers (of double precision). For fields where UDB knows that they are integers this it not a problem, since it'll automatically convert the floating point numbers to integers (dropping the fractional part). However, if you need to specify an integer value for an unknown or custom field you have to work around this limitation:
+Version 5 and later:
+You can use a `BigInt`. This is done by appending a `n` to the number. Note that this is just a convenient way to define whole numbers, it still only supports 32 bit integers:
+
+```js
+s.fields.user_myintfield = 25n; // Sets the 'user_myintfield' field to an integer value of 25
+```
+In version 4 and earlier you have to use the `UniValue` class:
 
 ```js
 s.fields.user_myintfield = new UDB.UniValue(0, 25); // Sets the 'user_myintfield' field to an integer value of 25
@@ -70,7 +77,7 @@ If the `Vertex` is selected or not.
 ## Methods
 
 ---
-### copyPropertiesTo(v)
+### copyPropertiesTo(v: Vertex)
 Copies the properties from this `Vertex` to another.
 #### Parameters
 * v: the vertex to copy the properties to
@@ -80,7 +87,7 @@ Copies the properties from this `Vertex` to another.
 Deletes the `Vertex`. Note that this can result in unclosed sectors.
 
 ---
-### distanceTo(pos)
+### distanceTo(pos: object)
 Gets the distance between this `Vertex` and the given point.
 The point can be either a `Vector2D` or an array of numbers.
 
@@ -94,7 +101,7 @@ v.distanceTo([ 32, 64 ]);
 Distance to `pos`
 
 ---
-### distanceToSq(pos)
+### distanceToSq(pos: object)
 Gets the squared distance between this `Vertex` and the given point.
 The point can be either a `Vector2D` or an array of numbers.
 
@@ -114,13 +121,13 @@ Gets all `Linedefs` that are connected to this `Vertex`.
 Array of linedefs
 
 ---
-### join(other)
+### join(other: Vertex)
 Joins this `Vertex` with another `Vertex`, deleting this `Vertex` and keeping the other.
 #### Parameters
 * other: `Vertex` to join with
 
 ---
-### nearestLinedef(pos)
+### nearestLinedef(pos: object)
 Returns the `Linedef` that is connected to this `Vertex` that is closest to the given point.
 #### Parameters
 * pos: Point to get the nearest `Linedef` connected to this `Vertex` from
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/changes.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/changes.md
index 86834633e94a1db1e58b4684036249c8a75ee715..22f0040fcd15509d4966dc2a18d1579a9faf73da 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/changes.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/changes.md
@@ -1,23 +1,32 @@
 # Changes
 This site lists all changes between different API version of UDBScript
 
-## Verstion 4
+## Version 5
+
+- Added `Plane` class
+- Added `BlockMap`, `BlockEntry`, and `BlackMapQueryResult` classes
+- `Sector` class
+    - Added `getLabelPositions` method to get the position of sector labels (where tags, effects etc. are displayed)
+- Added support for JavaScript BigInt for UDMF fields. This means it's not necessary anymore to use `UniValue` to assign integers to new UDMF fields. Instead it can be done like this: `sector.fields.my_int_field = 1n;`
+- Added type information file (udbscript.d.ts)
+
+## Version 4
 
 - Moved all classes, object, and methods into the `UDB` namespace (everything has to be prefixed wiht `UDB.`)
-- Added methods to report progress for long running scripts and script log output. See [Communicating with the user](gettingstartet.md#communicating-with-the-user) for more information
+- Added methods to report progress for long running scripts and script log output. See [Communicating with the user](gettingstarted.md#communicating-with-the-user) for more information
 
 ## Version 3
 
 - Exported the classes `Linedef`, `Sector`, `Sidedef`, `Thing`, and `Vertex`, so that they can be used with `instanceof`
 - `Map` class
-    - the `getSidedefsFromSelectedLinedefs()` method now correctly only returns the `Sidedef`s of selected `Linedef`s in visual mode (and not also the highlighted one)
-    - added a new `getSidedefsFromSelectedOrHighlightedLinedefs()` method as the equivalent to the other `getSelectedOrHighlighted*()` methods
+    - The `getSidedefsFromSelectedLinedefs()` method now correctly only returns the `Sidedef`s of selected `Linedef`s in visual mode (and not also the highlighted one)
+    - Added a new `getSidedefsFromSelectedOrHighlightedLinedefs()` method as the equivalent to the other `getSelectedOrHighlighted*()` methods
 - `Sector` class
-    - added new `floorSelected`, `ceilingSelected`, `floorHighlighted`, and `ceilingHighlighted` properties. Those are mostly useful in visual mode, since they always return true when the `Sector` is selected or highlighted in the classic modes. The properties are read-only
+    - Added new `floorSelected`, `ceilingSelected`, `floorHighlighted`, and `ceilingHighlighted` properties. Those are mostly useful in visual mode, since they always return true when the `Sector` is selected or highlighted in the classic modes. The properties are read-only
 - `Sidedef` class
-    - added new `upperSelected`, `middleSelected`, `lowerSelected`, `upperHighlighted`, `middleHighlighted`, and `lowerHighlighted` properties. Those are mostly useful in visual mode, since they always return true when the parent `Linedef` is selected or highlighted in the classic modes. The properties are read-only
+    - Added new `upperSelected`, `middleSelected`, `lowerSelected`, `upperHighlighted`, `middleHighlighted`, and `lowerHighlighted` properties. Those are mostly useful in visual mode, since they always return true when the parent `Linedef` is selected or highlighted in the classic modes. The properties are read-only
 
 ## Version 2
 
 - `Pen` built-in library
-    - the methods of the `Pen` class now return the instance of the Pen class to allow method chaining
\ No newline at end of file
+    - The methods of the `Pen` class now return the instance of the Pen class to allow method chaining
\ No newline at end of file
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/gettingstarted.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/gettingstarted.md
index b7b2843e204fbe80dff590696f1c7362f9601e6d..ed1dee3a66d43de1d450ca78381b14947f95640a 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/gettingstarted.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/gettingstarted.md
@@ -58,6 +58,19 @@ You can open a context menu for each script by right-clicking on it. In the cont
 
 ## Writing scripts
 
+### Type information file (udbscript.d.ts)
+
+UDBScript comes with a type information called `udbscript.d.ts` located in the `UDBScript` directory in the UDB installation directory. This file contains information about the available classes, methods, and their parameters. This makes writing scripts more comfortable if you are using an text editor that can interpret this file, such as Visual Studio Code.
+
+To use the file it has to be referenced at the very top of the script file:
+```js
+/// <reference path="../udbscript.d.ts" />
+```
+Depending on where your script is located you have to change the path to the file.
+
+!!! attention
+    `.d.ts` files are originally for the TypeScript programming language, which is a superset of JavaScript. UDBScript does *not* support TypeScript, it merely provides a definition file to make writing scripts easier.
+
 ### Script metadata
 
 Scripts can contain metadata at the top of the file to provide information about the script as well as available script options. The metadata is specified as JavaScript template strings, i.e. strings enclosed by backticks (`` ` ``). The template string has to start with a `#`, followed by a command, followed by the payload, followed by a `;`.
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/docs/index.md b/Source/Plugins/UDBScript/docs/htmldoc/docs/index.md
index 571a7595203da16f6661704142868dc0527e89e8..73ece097963a753a5dacdc05bbc1203c02e0d130 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/docs/index.md
+++ b/Source/Plugins/UDBScript/docs/htmldoc/docs/index.md
@@ -8,12 +8,16 @@
 ## API
 
 - [Angle2D](Angle2D.md)
+- [BlockEntry](BlockEntry.md)
+- [BlockMap](BlockMap.md)
+- [BlockMapQueryResult](BlockMapQueryResult.md)
 - [Data](Data.md)
 - [GameConfiguration](GameConfiguration.md)
 - [ImageInfo](ImageInfo.md)
 - [Line2D](Line2D.md)
 - [Linedef](Linedef.md)
 - [Map](Map.md)
+- [Plane](Plane.md)
 - [QueryOptions](QueryOptions.md)
 - [Sector](Sector.md)
 - [Sidedef](Sidedef.md)
diff --git a/Source/Plugins/UDBScript/docs/htmldoc/mkdocs.yml b/Source/Plugins/UDBScript/docs/htmldoc/mkdocs.yml
index 8df09b5966a77d59b6e7f457622450708b404fab..7dce001c3e54e31cc803b92614bea3272ad02bef 100644
--- a/Source/Plugins/UDBScript/docs/htmldoc/mkdocs.yml
+++ b/Source/Plugins/UDBScript/docs/htmldoc/mkdocs.yml
@@ -10,12 +10,16 @@ nav:
     - 'Changes': 'changes.md'
   - 'API':
     - Angle2D: 'Angle2D.md'
+    - BlockEntry: 'BlockEntry.md'
+    - BlockMap: 'BlockMap.md'
+    - BlockMapQueryResult: 'BlockMapQueryResult.md'
     - Data: 'Data.md'
     - GameConfiguration: 'GameConfiguration.md'
     - ImageInfo: 'ImageInfo.md'
     - Line2D: 'Line2D.md'
     - Linedef: 'Linedef.md'
     - Map: 'Map.md'
+    - Plane: 'Plane.md'
     - QueryOptions: 'QueryOptions.md'
     - Sector: 'Sector.md'
     - Sidedef: 'Sidedef.md'
diff --git a/Source/Plugins/UDBScript/docs/readme.md b/Source/Plugins/UDBScript/docs/readme.md
deleted file mode 100644
index f589a09ee052db84f4bc0b9dbbca1b889c83ddf5..0000000000000000000000000000000000000000
--- a/Source/Plugins/UDBScript/docs/readme.md
+++ /dev/null
@@ -1,258 +0,0 @@
-# UDB Scripting
-## Introduction
-Scripting is one of the big features UDB is missing. This branch is trying to rectify this.
-
-It uses the [Jint](https://github.com/sebastienros/jint) interpreter to let the users execute their own JavaScript (ECMAScript 5.1, with some 6.0 features) scripts in the editor.
-Since it exposes the whole UDB API (including those of the loaded plugins) to the scripting engine, much of what required a dedicated plugin can now be done with scripts.
-
-## How it works
-### Directory structure
-Scripts are automatically found if they are placed in the correct directory structure. The directory structure is in the UDB installation folder and looks like this:
-
-```
-.\UDBScript\
-.\UDBScript\Libraries\
-.\UDBScript\Scripts\
-```
-
-All files ending on .js in the Libraries directory are loaded (parsed and executed) every time a script is run. It is used to provide common functionality to multiple script. Currently there's the Pen.js library file that simplifies drawing geometry (it's inspired by the DBX Lua Pen class).
-
-All files ending on .js in the Scripts directory (and its subdirectories) are added to the Scripts docker. They are only run on the user's command. Scripts can optionally have a configuration file with the same name, just ending on .cfg.
-
-### Script configuration files
-
-The optional script configuration files can be used to let the user set script options before running the script. The configuration files look like this:
-
-```
-name = "Draw Voodoo Doll Closet";
-
-options
-{
-	length
-	{
-		description = "Length of closet";
-		default = 256;
-		type = 0; // Integer
-	}
-	
-	direction
-	{
-		description = "Direction of closet";
-		default = "North";
-		type = 11; // Enum
-		enumvalues {
-			0 = "North";
-			1 = "East";
-			2 = "South";
-			3 = "West";
-		}
-	}
-	
-	inactive
-	{
-		description = "Start inactive";
-		default = "True";
-		type = 3; // Boolean
-	}
-	
-	looping
-	{
-		description = "Looping";
-		default = "False";
-		type = 3; // Boolean
-	}
-}
-```
-
-### The docker
-
-![The docker](docker.png)
-
-
-## Known issues and quirks
-
-### Exposing Assemblies also exposes the System namespace
-Scripts have access to the whole CLR, including Syste.IO and System.Net. [This is intentional behavior in Jint](https://github.com/sebastienros/jint/issues/222). This isn't a problem per se, since plugins could do the same. It'd be nice to blacklist certain namespaces, though, which isn't possible.
-
-It *is* possible to remove the System namespace retroactively on the C# side:
-
-```cs
-engine.Global.RemoveOwnProperty("System");
-```
-
-This, however, it's really feasible, since parts of the namespace (for example System.Collection.Generic) are required for certain things to work, for example Tool.DrawLines takes an IList as an argument, passing an array from a script to it does not work.
-
-### Setting array values doesn't work
-This is a [known issue in Jint that won't be changed](https://github.com/sebastienros/jint/issues/776). This means that it's not possible to set a thing's or linedef's arguments by accessing the Args property:
-
-```js
-var t = UDB.General.Map.Map.CreateThing();
-t.Args[0] = 123; // Won't work, it'll stay at 0
-```
-A workaround would be to implement a setter method the the args on the C# side:
-
-```cs
-public void SetArg(int num, int val)
-{
-    if(num >= 0 && num < args.Length)
-        args[num] = val;
-}
-```
-
-The downside is that this setter is likely to stay, even if the issue gets fixed in Jint (which seems unlikely), since simply removing it will break scripts that use it. It could be tried to later deprecate it, with spilling out error messages telling the user to update their scripts.
-
-Another options is to replace the `args` array with an class that acts like an array, like this:
-
-```cs
-public sealed class MapElementArguments : IEnumerable<int>
-{
-	private int[] data;
-
-	public MapElementArguments(int numargs)
-	{
-		data = new int[numargs];
-	}
-
-	public MapElementArguments(int numargs, IEnumerable<int> newdata) : this(numargs)
-	{
-		int i = 0;
-
-		foreach(int d in newdata)
-		{
-			if (i < numargs)
-				data[i] = d;
-
-			i++;
-		}
-	}
-
-	public int this[int i]
-	{
-		get { return data[i]; }
-		set { data[i] = value; }
-	}
-
-	public int Length { get { return data.Length; } }
-
-	public IEnumerator<int> GetEnumerator()
-	{
-		foreach(int i in data)
-			yield return i;
-	}
-
-	IEnumerator IEnumerable.GetEnumerator()
-	{
-		return GetEnumerator();
-	}
-
-	public MapElementArguments Clone()
-	{
-		return new MapElementArguments(data.Length, data);
-	}
-}
-```
-This works mostly transparent, with few changes to the rest of the code.
-
-### ECMAScript Array can't be passed to methods that take List<> as a parameter
-The good news is that you can create instances of List<> objects. It's rather unwieldy, though. To get the equivalent to C#
-
-```cs
-List<Linedef> linedefs = new List<Linedef>();
-```
-
-in the script you have to write
-
-```js
-var linedefs = new (System.Collections.Generic.List(UDB.Map.Linedef))();
-```
-
-A helper function could be created, for example
-
-```js
-function MakeList(cls)
-{
-    return new (System.Collections.Generic.List(cls))();
-}
-
-var linedefs = MakeList(UDB.Map.Linedef);
-```
-
-Another options would be to add a library that extends the Array prototype like this:
-
-```js
-Array.prototype.ToList = function(cls)
-{
-	var list = new (System.Collections.Generic.List(cls))();
-	
-	this.forEach(e => {
-		list.Add(e);
-	});
-	
-	return list;
-}
-```
-
-which could then be called liks this:
-
-```js
-var myarray = [];
-
-UDB.General.Map.Map.Things.forEach(t => { myarray.push(t); });
-
-var mylist = myarray.ToList(UDB.Map.Thing);
-```
-
-
-### Iterating over LinkedList
-It's cumbersome. It's not possible to use neat methods like `forEach` or `filter` on them. You have to iterate over them using their `First` and `Next` properties. I don't think they are used widely, it mostly seems to affects the  `Sidedefs` property of the `Sector` class, and the `Linedefs` property of the `Vertex` class.
-
-Ways to iterate over `LinkedList`:
-
-```js
-function LinkedListToArray(linkedlist)
-{
-	var array = [];
-	
-	for(var it=linkedlist.First; it != null; it = it.Next)
-		array.push(it.Value);
-
-	return array;	
-}
-```
-or
-```js
-function LinkedListToArray(linkedlist)
-{
-	var array = [];
-	
-	var enumerator = linkedlist.GetEnumerator();
-	
-	while(enumerator.MoveNext())
-		array.push(enumerator.Current);
-	
-	return array;	
-}
-```
-
-### ECMAScript doesn't have operator overloading
-This is only a minor nuisance. An example where this comes into play is when working with Vector2D or Vector3D. Doing basic math operations like addition or multiplication on them doesn't work, instead a new instance has to be created with the desired values:
-
-```js
-var Vector2D = UDB.Geometry.Vector2D;
-
-var v1 = new Vector2D(1, 1);
-var v2 = new Vector2D(2, 2);
-
-var v3 = v1 + v2; // Doesn't work, results in v3 being the string "1, 12, 2"
-
-var v4 = new Vector2D(v1.x + v2.x, v1.y + v2.y); // You have to do this
-```
-
-### Lack of documentation
-There's no API documentation. Since the scripts directly access the UDB API doing an documentation seems futile. It's probably a better idea to go through the source files that are most relevant for scripting, add triple-slash-comments where needed and automatically generate a documentation with something like [DocFX](https://dotnet.github.io/docfx/). Having a FAQ and well-commented example scripts would also help.
-
-## Possible future improvements
-- Update the script file tree when files are added to the directory structure at runtime
-- Add option to open the script file and configuration file in an external editor through the context menu
-- Add option to set favorite scripts
-- Add multiple actions to run user-defined scripts
\ No newline at end of file
diff --git a/Source/Plugins/UDBScript/whatsmissing.md b/Source/Plugins/UDBScript/whatsmissing.md
deleted file mode 100644
index 82f1618d17d2e53c54be838219ade478c0f4b0b8..0000000000000000000000000000000000000000
--- a/Source/Plugins/UDBScript/whatsmissing.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# What's still missing
-## Wrappers
-- `Line3D` wrapper
-
-## Interop
-- Make (parts of) game configuration (for example ThingTypeInfo) available?
-
-## Map elements
-- is the current implementation for thing and linedef action arguments OK?
-
-## UI
-- add favorite script functionality (in the UI and with hotkeys)
-- add "quick script" function to write and run one-shot scripts in UDB without having to create files
-
-## Other
-- remove the `Wrapper` part from the wrapper class names?
-- some way to get information about the current editing mode (name, ...)
-- radians vs degrees?
\ No newline at end of file
diff --git a/Source/Plugins/VisplaneExplorer/VisplaneExplorerMode.cs b/Source/Plugins/VisplaneExplorer/VisplaneExplorerMode.cs
index a04260ef3767c1de9aa58968cd4840eda046a268..707165d9d504d21ccfef83afc9a91cc87543fcac 100755
--- a/Source/Plugins/VisplaneExplorer/VisplaneExplorerMode.cs
+++ b/Source/Plugins/VisplaneExplorer/VisplaneExplorerMode.cs
@@ -5,11 +5,13 @@ using System.Collections.Generic;
 using System.Drawing;
 using System.Drawing.Imaging;
 using System.IO;
-using System.Runtime.InteropServices;
+using System.Linq;
+using System.Text;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.Editing;
 using CodeImp.DoomBuilder.Geometry;
+using CodeImp.DoomBuilder.IO;
 using CodeImp.DoomBuilder.Map;
 using CodeImp.DoomBuilder.Rendering;
 using CodeImp.DoomBuilder.Windows;
@@ -227,6 +229,50 @@ namespace CodeImp.DoomBuilder.Plugins.VisplaneExplorer
 			}
 		}
 
+		/// <summary>
+		/// Checks if the given map is valid for the Visplane Explorer Mode. Specifically it may not have
+		/// ZDBSP nodes. See https://github.com/jewalky/UltimateDoomBuilder/issues/736
+		/// </summary>
+		/// <param name="file">Full path and file name of the WAD to check. Only the first map is checked</param>
+		/// <returns>true if the check was successful, false if there was a problem. Also returns a message in case something is wrong</returns>
+		private (bool, string) CheckMapValidity(string file)
+		{
+			WAD wad = new WAD(file, true);
+
+			try
+			{
+				Lump nodes = wad.FindLump("NODES");
+				if (nodes == null)
+					throw new Exception("NODES lump not found");
+
+				Stream stream = nodes.GetSafeStream();
+				if (stream.Length == 0)
+					throw new Exception("NODES lump is empty");
+
+				using (BinaryReader reader = new BinaryReader(stream))
+				{
+					List<byte[]> formats = new List<byte[]> { Encoding.ASCII.GetBytes("ZNOD"), Encoding.ASCII.GetBytes("XNOD") };
+
+					byte[] format = reader.ReadBytes(4);
+
+					if(formats.Any(f => f.SequenceEqual(format)))
+						throw new Exception("ZDBSP nodes detected. This format is not supporeted by the Visplane Explorer Mode");
+				}
+			}
+			catch(Exception e)
+			{
+				return (false, e.Message);
+			}
+			finally
+			{
+				// Get rid of the WAD ASAP. If something failed and we don't do this the WAD can't be deleted by the
+				// cleanup method
+				wad.Dispose();
+			}
+
+			return (true, string.Empty);
+		}
+
 		#endregion
 
 		#region ================== Events
@@ -257,6 +303,14 @@ namespace CodeImp.DoomBuilder.Plugins.VisplaneExplorer
 				return;
 			}
 
+			(bool mapvalid, string message) = CheckMapValidity(tempfile);
+			if(!mapvalid)
+			{
+				MessageBox.Show($"Error: {message}.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+				General.Editing.CancelMode();
+				return;
+			}
+
 			// Load the map in VPO_DLL
 			BuilderPlug.VPO.Start(tempfile, General.Map.Options.LevelName);
 
diff --git a/Source/Tools/Updater/MainForm.cs b/Source/Tools/Updater/MainForm.cs
index 3d30c8fbc508c461bfdafcb5cc810999de2da03e..5e0573a1d9b6c9d36a89e967661277ff94e91b4e 100755
--- a/Source/Tools/Updater/MainForm.cs
+++ b/Source/Tools/Updater/MainForm.cs
@@ -13,6 +13,7 @@ using System.Threading;
 using System.Windows.Forms;
 using SharpCompress.Archives;
 using SharpCompress.Readers;
+using Microsoft.Win32;
 
 #endregion
 
@@ -34,6 +35,7 @@ namespace mxd.GZDBUpdater
 	    private static bool appclosing;
 	    private static MainForm me;
 		private const string MESSAGEBOX_TITLE = "Ultimate Doom Builder Updater";
+		private bool useInstaller = false;
 
 		#endregion
 
@@ -57,7 +59,17 @@ namespace mxd.GZDBUpdater
 
 		public MainForm()
         {
-            if(!CheckPremissions(Application.StartupPath))
+			// Check if we should use the installer or just unpack the update
+			string installLocation = (string)Registry.GetValue(@"HKEY_CURRENT_USER\SOFTWARE\Ultimate Doom Builder", "Location", null);
+
+			if (installLocation != null)
+			{
+				string ourpath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
+				if (ourpath == installLocation)
+					useInstaller = true;
+			}
+
+			if (!CheckPremissions(Application.StartupPath))
             {
                 ErrorDescription = "Update failed: your user account does not have write access to the destination folder \"" + Application.StartupPath + "\"\n\nMove the editor to a folder with write access,\nor run the updater as Administrator.";
                 InvokeClose();
@@ -109,23 +121,102 @@ namespace mxd.GZDBUpdater
 				return;
 			}
 
-			UpdateLabel(label1, "4/6: Decompressing package...");
-            Thread.Sleep(500);
-			if(!Unpack(updateFolder + downloadFile, Application.StartupPath))
+			// Rename the updater so that a new version can be written
+			UpdateLabel(label1, "4/6: Renaming updater...");
+			Thread.Sleep(500);
+			if(!RenameUpdater())
 			{
 				e.Cancel = true;
 				return;
 			}
 
+			// Install the update
+			if (useInstaller)
+			{
+				UpdateLabel(label1, "5/6: Running installer...");
+				Thread.Sleep(500);
+				if(!InstallUpdateWithInstaller())
+				{
+					e.Cancel = true;
+					return;
+				}
+			}
+			else
+			{
+				UpdateLabel(label1, "5/6: Decompressing package...");
+				Thread.Sleep(500);
+				if (!Unpack(updateFolder + downloadFile, Application.StartupPath))
+				{
+					e.Cancel = true;
+					return;
+				}
+			}
+
+			// This currently doesn't do anything, since the directory it tries to move the files
+			// from is empty (bar the update file)
+			/*
 			UpdateLabel(label1, "5/6: Moving files...");
             Thread.Sleep(500);
             MoveFiles();
+			*/
             
 			UpdateLabel(label1, "6/6: Wrapping up...");
             Thread.Sleep(500);
 			PostDownload();
 		}
 
+		private bool InstallUpdateWithInstaller()
+		{
+			Process process;
+
+			try
+			{
+				// Run the installer in silent mode. This will skip any user interaction (if possible)
+				process = Process.Start(updateFolder + downloadFile, "/silent");
+			}
+			catch (Exception ex)
+			{
+				ErrorDescription = $"Failed to run installer: {ex.Message}";
+				return false;
+			}
+
+			process.WaitForExit();
+
+			// Check if the installer ran successfully. See https://jrsoftware.org/ishelp/index.php?topic=setupexitcodes
+			if (process.ExitCode == 2)
+			{
+				ErrorDescription = "Installer aborted by the user.";
+				return false;
+			}
+			else if (process.ExitCode > 0)
+			{
+				ErrorDescription = $"Installer failed with exit code {process.ExitCode}.";
+				return false;
+			}
+
+			return true;
+		}
+
+		private bool RenameUpdater()
+		{
+			string ourname = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);
+			string movename = ourname + ".old";
+
+			try
+			{
+				if (File.Exists(movename))
+					File.Delete(movename);
+				File.Move(ourname, movename);
+			}
+			catch(Exception e)
+			{
+				ErrorDescription = $"Failed to rename updater from {ourname} to {movename}: {e.Message}";
+				return false;
+			}
+
+			return true;
+		}
+
 		private bool EditorClosed()
 		{
 			try
@@ -287,6 +378,7 @@ namespace mxd.GZDBUpdater
 		private bool LoadConfig(string filename)
 		{
 			string[] lines = File.ReadAllLines(filename);
+
 			foreach (string line in lines)
 			{
 				if(line.StartsWith("URL"))
@@ -298,10 +390,14 @@ namespace mxd.GZDBUpdater
 					appFileName = line.Substring(8).Trim();
 					processToEnd = Path.GetFileNameWithoutExtension(appFileName);
 				}
-				else if(line.StartsWith("UpdateName"))
+				else if(!useInstaller && line.StartsWith("UpdateName"))
 				{
 					downloadFile = line.Substring(10).Trim();
 				}
+				else if(useInstaller && line.StartsWith("InstallerName"))
+				{
+					downloadFile = line.Substring(13).Trim();
+				}
                 else if(line.StartsWith("Platform"))
                 {
                     platform = line.Substring(9).Trim();
@@ -355,7 +451,7 @@ namespace mxd.GZDBUpdater
 					while(reader.MoveToNextEntry())
 					{
 						if(appclosing) break;
-						if(reader.Entry.IsDirectory || Path.GetFileName(reader.Entry.Key) == ourname) continue; // Don't try to overrite ourselves...
+						// if(reader.Entry.IsDirectory || Path.GetFileName(reader.Entry.Key) == ourname) continue; // Don't try to overrite ourselves...
 						reader.WriteEntryToDirectory(unZipTo, options);
 						UpdateProgressBar(new ByteArgs { Downloaded = curentry++, Total = totalentries }, 1, 2);
 					}
diff --git a/Source/Tools/Updater/Properties/AssemblyInfo.cs b/Source/Tools/Updater/Properties/AssemblyInfo.cs
index 2221ad4a0cc0a316eb52bba55aa6858a62063dec..6f6172605b7038127807a718f3e0d1d3ff1b2f16 100755
--- a/Source/Tools/Updater/Properties/AssemblyInfo.cs
+++ b/Source/Tools/Updater/Properties/AssemblyInfo.cs
@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
 // General Information about an assembly is controlled through the following 
 // set of attributes. Change these attribute values to modify the information
 // associated with an assembly.
-[assembly: AssemblyTitle("GZDoom Builder Updater")]
+[assembly: AssemblyTitle("Ultimate Doom Builder Updater")]
 [assembly: AssemblyDescription("")]
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("CodeImp, MaxED, ZZYZX, boris, dpJudas and others")]
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
 //      Build Number
 //      Revision
 //
-[assembly: AssemblyVersion("1.0.0.5")]
-[assembly: AssemblyFileVersion("1.0.0.5")]
+[assembly: AssemblyVersion("1.0.0.6")]
+[assembly: AssemblyFileVersion("1.0.0.6")]
diff --git a/Source/Tools/Updater/Updater.ini b/Source/Tools/Updater/Updater.ini
index d9b74203d982063f0e7a29c870567a8546a3478c..3bb3a23ce9628df0124a95c5a5011ed159f4ae17 100755
--- a/Source/Tools/Updater/Updater.ini
+++ b/Source/Tools/Updater/Updater.ini
@@ -1,4 +1,5 @@
-URL http://devbuilds.drdteam.org/gzdbbf/
+URL http://devbuilds.drdteam.org/ultimatedoombuilder/
 FileName Builder.exe
 UpdateName GZDoom_Builder_Bugfix-r[REVNUM].7z
+InstallerName UltimateDoomBuilder-Setup-R[REVNUM]-[PLATFORM].exe
 UpdaterName GZDB_Updater-[PLATFORM].7z
\ No newline at end of file
diff --git a/UpdaterConfig/Updater-x64.ini b/UpdaterConfig/Updater-x64.ini
index 01c2edb39477c9f45dcb8f6d69414891cdcfb513..1862b51ad7e920bc22166bb50a3137a487f5a5af 100755
--- a/UpdaterConfig/Updater-x64.ini
+++ b/UpdaterConfig/Updater-x64.ini
@@ -1,4 +1,5 @@
 URL http://devbuilds.drdteam.org/ultimatedoombuilder/
 FileName Builder.exe
 UpdateName UltimateDoomBuilder-r[REVNUM]-x64.7z
+InstallerName UltimateDoomBuilder-Setup-R[REVNUM]-x64.exe
 UpdaterName UDB_Updater-x64.7z
\ No newline at end of file
diff --git a/UpdaterConfig/Updater-x86.ini b/UpdaterConfig/Updater-x86.ini
index b42b8351725627c40dedad4fdbd135578f7cc120..a93329d204105f5b02f1485b7e2de29d39a030c2 100755
--- a/UpdaterConfig/Updater-x86.ini
+++ b/UpdaterConfig/Updater-x86.ini
@@ -1,4 +1,5 @@
 URL http://devbuilds.drdteam.org/ultimatedoombuilder/
 FileName Builder.exe
 UpdateName UltimateDoomBuilder-r[REVNUM]-x86.7z
+InstallerName UltimateDoomBuilder-Setup-R[REVNUM]-x86.exe
 UpdaterName UDB_Updater-x86.7z
\ No newline at end of file
diff --git a/build_git_generic.cmd b/build_git_generic.cmd
index aef1e7586f0965f466dfa14e9b0d50d8ba1f09cf..5fcd9b7721e8fe6d8d6ff742c3e7683820442644 100755
--- a/build_git_generic.cmd
+++ b/build_git_generic.cmd
@@ -3,7 +3,7 @@
 ECHO.
 ECHO.     This build script requires the following software to be installed:
 ECHO.       - Git command-line client
-ECHO.       - Microsoft Visual Studio 2008 or newer
+ECHO.       - Microsoft Visual Studio 2019 or newer
 ECHO.       - Microsoft HTML Help compiler
 ECHO.       - 7zip
 ECHO.
@@ -13,7 +13,7 @@ ECHO.     Files in the 'GIT_Build' directory may be overwritten.
 ECHO.
 ECHO.
 
-SET STUDIODIR=c:\Program Files (x86)\Microsoft Visual Studio 14.0
+SET STUDIODIR=c:\Program Files (x86)\Microsoft Visual Studio\2019\Community
 SET HHWDIR=c:\Program Files (x86)\HTML Help Workshop
 SET SEVENZIPDIR=c:\Program Files\7-Zip
 SET ISSDIR=c:\Program Files (x86)\Inno Setup 6
@@ -132,19 +132,19 @@ IF NOT DEFINED BUILD_RELEASE GOTO PACKGIT
 
 set DEL_PATHSPEC="%DB_OUTDIR%\UltimateDoomBuilder-Setup*-%PLATFORM%.exe"
 IF EXIST %DEL_PATHSPEC% DEL /F /Q %DEL_PATHSPEC% > NUL
-"%ISSDIR%\iscc.exe" "Setup\gzbuilder_setup.iss"
+"%ISSDIR%\iscc.exe" /DUDB_arch=%PLATFORM% "Setup\UDBuilder_setup.iss"
 IF %ERRORLEVEL% NEQ 0 GOTO ERRORFAIL
 IF NOT EXIST "%DB_OUTDIR%\Setup.exe" GOTO FILEFAIL
 
 REN "%DB_OUTDIR%\Setup.exe" "UltimateDoomBuilder-Setup-R%REVISIONNUMBER%-%PLATFORM%.exe"
 
-GOTO BUILDDONE
+REM GOTO BUILDDONE
 
 :PACKGIT
 SET DEL_PATHSPEC="%DB_OUTDIR%\UltimateDoomBuilder*-%PLATFORM%.7z"
 IF EXIST %DEL_PATHSPEC% DEL /F /Q %DEL_PATHSPEC% > NUL
 IF EXIST "%DB_OUTDIR%\UDB_Updater-%PLATFORM%.7z" DEL /F /Q "%DB_OUTDIR%\UDB_Updater-%PLATFORM%.7z" > NUL
-"%SEVENZIPDIR%\7z" a %DB_OUTDIR%\udb.7z .\Build\* -xr!*.xml -xr!JetBrains.Profiler.Core.Api.dll -xr!ScintillaNET.3.5.pdb -x!Setup
+"%SEVENZIPDIR%\7z" a %DB_OUTDIR%\udb.7z .\Build\* -xr!*.xml -xr!JetBrains.Profiler.Core.Api.dll -xr!ScintillaNET.3.5.pdb -x!Setup -x!OpenGLDebug.log -x!Builder.vshost.* -x!.gitignore -x!UDBScript\Scripts\*.js
 "%SEVENZIPDIR%\7z" a %DB_OUTDIR%\UDB_Updater-%PLATFORM%.7z .\Build\Updater.exe .\Build\Updater.ini
 IF %ERRORLEVEL% NEQ 0 GOTO PACKFAIL
 IF NOT EXIST %DB_OUTDIR%\udb.7z GOTO FILEFAIL
diff --git a/builder.sh b/builder.sh
index 4f7c7aad1e564f8b2eb95cf7042b3821ceea8c2b..3bad9623dcee8e613225eaaa27994cd9215b47a7 100644
--- a/builder.sh
+++ b/builder.sh
@@ -1,2 +1,2 @@
-#!/bin/bash
+#!/usr/bin/env bash
 mono Builder.exe