From 0f7aa9f8271fae56faae092ff6ab9484e8bfb818 Mon Sep 17 00:00:00 2001
From: MaxED <j.maxed@gmail.com>
Date: Mon, 22 Feb 2016 12:33:19 +0000
Subject: [PATCH] Added, Sector Edit window, UDMF: added UI for sector
 damage-realted properties. Added, DECORATE parser: damage types are now
 parsed. Added: the editor now reports duplicate
 textures/flats/patches/sprites/colormaps/voxels in the loaded wads. Added,
 all text parsers: added #region/#endregion support. Added TERRAIN parser.
 Added, Script Editor: added special handling for DECORATE special comments.
 Added, Sector Edit window, UDMF: Soundsequence value was setup incorrectly
 when showing the window for multiple sectors with mixed Soundsequence value.
 Fixed, Map Options window: "Strictly load patches between P_START and P_END"
 was not applied when applying the changes. Fixed, MAPINFO parser: MapInfo
 should be treated as defined when a map MAPINFO block corresponding to
 current map is encountered even if it doesn't define any properties
 recognized by the editor. Fixed, all text parsers: in some cases error line
 was calculated incorrectly when reporting an error detected by a text parser.
 Cosmetic: changed ' to " in the rest of Error and Warning messages. Internal:
 added text resource tracking. Updated ZDoom_DECORATE.cfg. Updated
 documentation ("Game Configuration - Basic Settings" page).

---
 Build/Configurations/Includes/UDMF_misc.cfg   |   2 +
 .../Configurations/Includes/ZDoom_common.cfg  |   3 +
 Build/Scripting/ZDoom_DECORATE.cfg            |  69 ++-
 Help/gc_basicsettings.html                    | 195 ++++-----
 Source/Core/Builder.csproj                    |   1 +
 Source/Core/Compilers/AccCompiler.cs          |  14 +-
 Source/Core/Config/GameConfiguration.cs       |  78 ++--
 Source/Core/Config/ResourceTextureSet.cs      |   6 +-
 Source/Core/Controls/ScriptDocumentTab.cs     | 149 ++++---
 Source/Core/Controls/ScriptEditorControl.cs   |  42 +-
 Source/Core/Controls/ScriptEditorPanel.cs     |  43 +-
 Source/Core/Controls/ScriptFileDocumentTab.cs |  28 +-
 Source/Core/Controls/ScriptLumpDocumentTab.cs |   5 +-
 Source/Core/Data/DataManager.cs               | 373 ++++++++++------
 Source/Core/Data/DataReader.cs                | 100 ++++-
 Source/Core/Data/DirectoryReader.cs           |  38 +-
 Source/Core/Data/PK3Reader.cs                 |  19 +-
 Source/Core/Data/PK3StructuredReader.cs       | 306 +++++++------
 Source/Core/Data/WADReader.cs                 | 289 ++++++++-----
 .../Core/GZBuilder/Controls/TagsSelector.cs   |   5 +-
 Source/Core/GZBuilder/Data/MapInfo.cs         |   2 +-
 Source/Core/GZBuilder/GZDoom/AcsParserSE.cs   |  65 +--
 .../Core/GZBuilder/GZDoom/DecorateParserSE.cs |  26 +-
 Source/Core/GZBuilder/GZDoom/GldefsParser.cs  |  81 ++--
 Source/Core/GZBuilder/GZDoom/MapinfoParser.cs |  53 ++-
 .../Core/GZBuilder/GZDoom/ModeldefParser.cs   |  35 +-
 .../Core/GZBuilder/GZDoom/ModeldefParserSE.cs |  27 +-
 .../GZBuilder/GZDoom/ModeldefStructure.cs     |  46 +-
 .../GZBuilder/GZDoom/ScriptTypeParserSE.cs    |  36 +-
 Source/Core/General/Launcher.cs               |  21 +-
 Source/Core/General/MapManager.cs             |  62 ++-
 Source/Core/IO/DirectoryFilesList.cs          |  23 +-
 Source/Core/Resources/UDMF_UI.cfg             |   6 +
 Source/Core/Windows/MainForm.cs               |   5 +-
 .../Windows/SectorEditFormUDMF.Designer.cs    | 404 ++++++++++++++----
 Source/Core/Windows/SectorEditFormUDMF.cs     | 113 ++++-
 Source/Core/Windows/SectorEditFormUDMF.resx   |  42 ++
 Source/Core/ZDoom/ActorStructure.cs           |  12 +-
 Source/Core/ZDoom/AnimdefsParser.cs           |  20 +-
 Source/Core/ZDoom/DecorateParser.cs           |  72 +++-
 Source/Core/ZDoom/ReverbsParser.cs            |  36 +-
 Source/Core/ZDoom/SndSeqParser.cs             |  34 +-
 Source/Core/ZDoom/TerrainParser.cs            |  97 +++++
 Source/Core/ZDoom/TexturesParser.cs           |  30 +-
 Source/Core/ZDoom/VoxeldefParser.cs           |  29 +-
 Source/Core/ZDoom/ZDTextParser.cs             | 109 ++++-
 46 files changed, 2250 insertions(+), 1001 deletions(-)
 create mode 100644 Source/Core/ZDoom/TerrainParser.cs

diff --git a/Build/Configurations/Includes/UDMF_misc.cfg b/Build/Configurations/Includes/UDMF_misc.cfg
index cd3f2f391..d03eb418e 100644
--- a/Build/Configurations/Includes/UDMF_misc.cfg
+++ b/Build/Configurations/Includes/UDMF_misc.cfg
@@ -80,6 +80,8 @@ sectorflags
 	waterzone = "Sector is under water and swimmable";
 	norespawn = "Players can't respawn in this sector";
 	dropactors = "Actors drop with instantly moving floors";
+	damageterraineffect = "Spawn terrain splashes on damage";
+	damagehazard = "Strife damage model";
 }
 
 linedefflags
diff --git a/Build/Configurations/Includes/ZDoom_common.cfg b/Build/Configurations/Includes/ZDoom_common.cfg
index a6fe66ba4..7ccbdb7fb 100644
--- a/Build/Configurations/Includes/ZDoom_common.cfg
+++ b/Build/Configurations/Includes/ZDoom_common.cfg
@@ -76,6 +76,9 @@ common
 	{
 		include("ZDoom_generalized.cfg", "gen_sectortypes");
 	}
+	
+	//mxd. Built-in Damage types
+	damagetypes = "BFGSplash Drowning Slime Fire Crush Telefrag Falling Suicide Exit Melee Railgun Ice Disintegrate Poison PoisonCloud Electric Massacre DrainLife Extreme InstantDeath";
 }
 
 // ***********************************************************
diff --git a/Build/Scripting/ZDoom_DECORATE.cfg b/Build/Scripting/ZDoom_DECORATE.cfg
index 162e93d23..64054a701 100644
--- a/Build/Scripting/ZDoom_DECORATE.cfg
+++ b/Build/Scripting/ZDoom_DECORATE.cfg
@@ -17,13 +17,55 @@ arrayopen = "[";
 arrayclose = "]";
 argumentdelimiter = ",";
 terminator = ";";
-extrawordchars = "#."; // Extra characters to be treated as a part of a word by the Script Editor
+extrawordchars = "#.$"; // Extra characters to be treated as a part of a word by the Script Editor
 keywordhelp = "http://www.zdoom.org/wiki/index.php?title=%K";
 scripttype = 3; //0 = unknown script, 1 = acc, 2 = modeldef, 3 = decorate
 
 keywords
 {
+//Editor special comments
+//These are handled in a different fascion: key is replaced with the value and the caret is placed at [EP] position	
+	$Angled = "$Angled";
+	$NotAngled = "$NotAngled";
+	$Category = "//$Category \"[EP]\"";
+	$Sprite = "//$Sprite \"[EP]\"";
+	$Title = "//$Title \"[EP]\"";
+	$Arg0 = "//$Arg0 \"[EP]\"";
+	$Arg1 = "//$Arg1 \"[EP]\"";
+	$Arg2 = "//$Arg2 \"[EP]\"";
+	$Arg3 = "//$Arg3 \"[EP]\"";
+	$Arg4 = "//$Arg4 \"[EP]\"";
+	$Arg0Default = "//$Arg0Default ";
+	$Arg1Default = "//$Arg1Default ";
+	$Arg2Default = "//$Arg2Default ";
+	$Arg3Default = "//$Arg3Default ";
+	$Arg4Default = "//$Arg4Default ";
+	$Arg0Tooltip = "//$Arg0Tooltip \"[EP]\"";
+	$Arg1Tooltip = "//$Arg1Tooltip \"[EP]\"";
+	$Arg2Tooltip = "//$Arg2Tooltip \"[EP]\"";
+	$Arg3Tooltip = "//$Arg3Tooltip \"[EP]\"";
+	$Arg4Tooltip = "//$Arg4Tooltip \"[EP]\"";
+	$Arg0Type = "//$Arg0Type ";
+	$Arg1Type = "//$Arg1Type ";
+	$Arg2Type = "//$Arg2Type ";
+	$Arg3Type = "//$Arg3Type ";
+	$Arg4Type = "//$Arg4Type ";
+	$Arg0Enum = "//$Arg0Enum ";
+	$Arg1Enum = "//$Arg1Enum ";
+	$Arg2Enum = "//$Arg2Enum ";
+	$Arg3Enum = "//$Arg3Enum ";
+	$Arg4Enum = "//$Arg4Enum ";
+	$Color = "//$Color ";
+	$Obsolete = "//$Obsolete \"[EP]\"";
+	$GZDB_SKIP = "//$GZDB_SKIP";
+//Preprocessor directives
 	#Include = "#Include";
+	#region = "#region";
+	#endregion = "#endregion";
+//WFDS
+	A_Bool = "return A_Bool(bool result);";
+	A_Int = "return A_Int(int result);";
+	A_State = "return A_State(int offset OR str state);";
 //Monster AI
 	A_AlertMonsters = "A_AlertMonsters[(float maxrange = 0.0[, int flags = 0])]";
 	A_Burst = "A_Burst(str chunktype)";
@@ -339,25 +381,30 @@ keywords
 	A_ShootGun = "A_ShootGun";
 	A_SPosAttackUseAtkSound = "A_SPosAttackUseAtkSound";
 //Mathematical functions
-	abs = "abs(x)\nReturns the absolute value of x.";
-	sin = "sin(x)\nTrigonometry function, x must be in degrees.";
-	cos = "cos(x)\nTrigonometry function, x must be in degrees.";
-	sqrt = "sqrt(x)\nReturns the square root of x.";
-	random = "random[identifier](min, max)\nReturns a random integer value between min and max.";
-	random2 = "random2[identifier](mask)\nReturns a random integer value between -mask and +mask.";
-	frandom = "frandom[identifier](min, max)\nReturns a random floating point value between min and max.";
-	randompick = "randompick[identifier](int, ...)\nPicks a number from the numbers placed in it.\nThis can take an unlimited amount of parameters.";
-	frandompick = "frandompick[identifier](int, ...)\nSimilar to randompick but for float-point values.";
+	abs = "float abs(x)\nReturns the absolute value of x.";
+	sin = "float sin(x)\nTrigonometry function, x must be in degrees.";
+	cos = "float cos(x)\nTrigonometry function, x must be in degrees.";
+	sqrt = "float sqrt(x)\nReturns the square root of x.";
+	random = "int random[identifier](min, max)\nReturns a random integer value between min and max.";
+	random2 = "int random2[identifier](mask)\nReturns a random integer value between -mask and +mask.";
+	frandom = "float frandom[identifier](min, max)\nReturns a random floating point value between min and max.";
+	randompick = "int randompick[identifier](int, ...)\nPicks a number from the numbers placed in it.\nThis can take an unlimited amount of parameters.";
+	frandompick = "float frandompick[identifier](int, ...)\nSimilar to randompick but for float-point values.";
 //State functions
 	Light = "Light(str lightname)";
 	Offset = "Offset(int x, int y)";
 //Special functions
 	CheckClass = "bool CheckClass(str classname[, int ptr_select = AAPTR_DEFAULT[, bool match_superclass = false]])";
-	IsPointerEqual = "bool IsPointerEqual(int ptr_select1, int ptr_select2)";
+	IsPointerEqual = "bool IsPointerEqual(int ptr1, int ptr2)";
+	CountInv = "int CountInv(str itemclassname[, int ptr_select = AAPTR_DEFAULT])";
+	GetDistance = "float GetDistance(bool checkz[, int ptr_select = AAPTR_TARGET])";
 }
 
 properties
 {
+	action;
+	native;
+	replaces;
 	Actor;
 	enum;
 	const;
diff --git a/Help/gc_basicsettings.html b/Help/gc_basicsettings.html
index a27bcba3c..cfe4a5cde 100644
--- a/Help/gc_basicsettings.html
+++ b/Help/gc_basicsettings.html
@@ -1,46 +1,40 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
 <head>
-	
-	<title>Game Configuration - Basic Settings</title>
-	
-	<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
-	<link rel="stylesheet" type="text/css" href="default.css" media="screen" title="Default" />
+<title>Game Configuration - Basic Settings</title>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+<link rel="stylesheet" type="text/css" href="default.css" media="screen" title="Default" />
 </head>
 <body>
-	
-	<object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e">
+<object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e">
 	<param name="keyword" value="Configurations">
 	<param name="keyword" value="Game Configurations">
-	</object>
-	
-	<div id="title"><h1>Game Configuration - Basic Settings</h1></div>
-	
-	<div id="contents">
-	<p>
-		<b class="fat">type</b> (string)<br />
+</object>
+<div id="title">
+	<h1>Game Configuration - Basic Settings</h1>
+</div>
+<div id="contents">
+	<p> <b class="fat">type</b> (string)<br />
 		This indicates the type of configuration to prevent accedential use of a different configuration. Must always be the string "Doom Builder 2 Game Configuration".<br />
 		<br />
-        <b class="fat">game</b> (string)<br />
+		<b class="fat">game</b> (string)<br />
 		The name that is displayed in Doom Buider for your Game Configuration.<br />
-        Default value is <b>"&lt;unnamed game&gt;"</b>.<br />
+		Default value is <b>"&lt;unnamed game&gt;"</b>.<br />
 		<br />
 		<b class="fat">enabledbydefault</b> (boolean) - <span class="red">GZDB only</span>.<br />
 		This game configuration is available by default. You can enable and disable game configurations using <a href="w_gameconfigurations.html">Game Configurations window</a>.<br />
-        Default value is <b>false</b>.<br />
+		Default value is <b>false</b>.<br />
 		<br />
-        <b class="fat">actionspecialhelp</b> (string) - <span class="red">GZDB only</span>.<br />
+		<b class="fat">actionspecialhelp</b> (string) - <span class="red">GZDB only</span>.<br />
 		The URL used to display action special help. &quot;<strong>%K</strong>&quot; wildcard is replaced by <strong>id</strong> property defined in <a href="gc_linedefsettings.html#actionid">action definition</a>.<br />
 		<br />
-        
-        <b class="fat">thingclasshelp</b> (string) - <span class="red">GZDB only</span>.<br />
-        The URL to  open when thing class name is clicked in the Thing Edit form. &quot;<strong>%K</strong>&quot; wildcard is replaced by <strong>classname</strong> property defined in thing definition or by DECORATE actor name.<br />
+		<b class="fat">thingclasshelp</b> (string) - <span class="red">GZDB only</span>.<br />
+		The URL to  open when thing class name is clicked in the Thing Edit form. &quot;<strong>%K</strong>&quot; wildcard is replaced by <strong>classname</strong> property defined in thing definition or by DECORATE actor name.<br />
 		<br />
-        <b class="fat">basegame</b>  (integer) [0 .. 4] - <span class="red">GZDB only</span>.<br />
-		Indicates which game the current configuration is based on. Used to load game-specific GLDEFS lumps (DOOMDEFS, HTICDEFS, HEXNDEFS or STRFDEFS).
-        <br /><b>Possible values:</b> 1 (DOOM), 2 (HERETIC), 3 (HEXEN) or 4 (STRIFE).<br />
-        Default value is <b>0</b> (don't load game-specific lumps).<br />
-
+		<b class="fat">basegame</b> (integer) [0 .. 4] - <span class="red">GZDB only</span>.<br />
+		Indicates which game the current configuration is based on. Used to load game-specific GLDEFS lumps (DOOMDEFS, HTICDEFS, HEXNDEFS or STRFDEFS). <br />
+		<b>Possible values:</b> 1 (DOOM), 2 (HERETIC), 3 (HEXEN) or 4 (STRIFE).<br />
+		Default value is <b>0</b> (don't load game-specific lumps).<br />
 		<br />
 		<b class="fat">engine</b> (string)<br />
 		Game engine/sourceport name. This is used as the UDMF namespace for UDMF map format interface. It currently has no other function.<br />
@@ -56,19 +50,19 @@
 		Default value is <b>false</b>.<br />
 		<br />
 		<b class="fat">defaultsavecompiler</b> (string)<br />
-	  Name of the <a href="scriptingconfigs.html">Nodebuilder Compiler Configuration structure</a> to use as the default settings for the compiler that is used when saving the map. The user can still change this in the <a href="w_gameconfigurations.html">Game Configurations window</a>.<br />
+		Name of the <a href="scriptingconfigs.html">Nodebuilder Compiler Configuration structure</a> to use as the default settings for the compiler that is used when saving the map. The user can still change this in the <a href="w_gameconfigurations.html">Game Configurations window</a>.<br />
 		<br />
 		<b class="fat">defaulttestcompiler</b> (string)<br />
-	  Name of the <a href="scriptingconfigs.html">Nodebuilder Compiler Configuration structure</a> to use as the default settings for the compiler that is used when testing the map. The user can still change this in the <a href="w_gameconfigurations.html">Game Configurations window</a>.<br />
+		Name of the <a href="scriptingconfigs.html">Nodebuilder Compiler Configuration structure</a> to use as the default settings for the compiler that is used when testing the map. The user can still change this in the <a href="w_gameconfigurations.html">Game Configurations window</a>.<br />
 		<br />
-        <b class="fat">defaultscriptcompiler</b> (string) - <span class="red">GZDB only</span>.<br />
-	  Name of the <a href="scriptingconfigs.html">Script Compiler Configuration file</a> to use as the default settings for the script compiler that is used when compiling map scripts. The user can still change this in the <a href="w_mapoptions.html">Map Options window</a>.<br />
+		<b class="fat">defaultscriptcompiler</b> (string) - <span class="red">GZDB only</span>.<br />
+		Name of the <a href="scriptingconfigs.html">Script Compiler Configuration file</a> to use as the default settings for the script compiler that is used when compiling map scripts. The user can still change this in the <a href="w_mapoptions.html">Map Options window</a>.<br />
 		<br />
 		<b class="fat">skills</b> (structure)<br />
 		This defines the skill options the user has available with this game engine/project. The settings in this structure are expected to be numbers with string values (the descriptive name for the skill level).<br />
 		<br />
 		<strong>Example:</strong>
-  <pre>
+	<pre>
 skills
 {
 	1 = "I'm too young to die";
@@ -78,66 +72,74 @@ skills
 	5 = "Nightmare!";
 }
 </pre>
-		<br />
-		<b class="fat">linetagindicatesectors</b> (boolean)<br />
-		When <b>true</b>, Doom Builder will highlight sectors associated with the same tag number when a line is highlighted. This is only really useful for Doom format maps, because Hexen format and UDMF format has no single tag on linedefs (in those formats, the arguments of the linedef's action can be tags, which also works to highlight sectors).<br />
-Default value is <b>false</b>.<br />
-		<br />
-	  <b class="fat">singlesidedflag</b> (integer or string)<br />
-		This lets Doom Builder know the <a href="gc_linedefflags.html">linedef flag</a> that indicates a line with only one side. Doom Builder will set this flag value on a linedef when it changes a line to become single sided and removes the flag from a linedef when it becomes double sided. Plugins can also use this information to perform operations on linedefs. For map formats that use numeric flags (Doom and Hexen) this must be an integer flag value. For map formats that use named flags (UDMF), this must be a string indicating the name of the flag.<br />
-		<br />
-		<b class="fat">doublesidedflag</b> (integer or string)<br />
-		This lets Doom Builder know the <a href="gc_linedefflags.html">linedef flag</a> that indicates a line with two sides. Doom Builder will set this flag value on a linedef when it changes a line to become double sided and removes the flag from a linedef when it becomes single sided. Plugins can also use this information to perform operations on linedefs. For map formats that use numeric flags (Doom and Hexen) this must be an integer flag value. For map formats that use named flags (UDMF), this must be a string indicating the name of the flag.<br />
-		<br />
-		<b class="fat">impassableflag</b> (integer or string)<br />
-		This lets Doom Builder know the <a href="gc_linedefflags.html">linedef flag</a> that indicates a line which blocks players and monsters. Doom Builder uses this to give the line a special color and plugins can use this information to perform operations related to blocking sound lines. For map formats that use numeric flags (Doom and Hexen) this must be an integer specifying the flag value of the Impassable flag. For map formats that use named flags (UDMF), this must be a string indicating the name of the Impassable flag.<br />
-		<br />
-        <b class="fat">defaultwalltexture</b> (string) - <span class="red">GZDB only</span>.<br />
-		Name of a texture to use on sidedefs when creating a new sector.<br />
-<b>"STARTAN"</b>.<br />
-        <br />
-        <b class="fat">defaultfloortexture</b> (string) - <span class="red">GZDB only</span>.<br />
-		Name of a flat to use on the floor when creating a new sector.<br />
-      Default value is <b>"FLOOR0_1"</b>.<br />
-        <br />
-        <b class="fat">defaultceilingtexture</b> (string) - <span class="red">GZDB only</span>.<br />
-		Name of a flat to use on the ceiling when creating a new sector.<br />
-      Default value is <b>"CEIL1_1"</b>.<br />
-        <br />
-        <b class="fat">makedoortrack</b> (string)<br />
-		Name of a texture to use on the walls when making a door.<br />
-  Default value is <b>"-"</b> (no texture).<br />
-        <br />
-		<b class="fat">makedoordoor</b> (string) - <span class="red">GZDB only</span>.<br />
-		Name of a texture to use as the door texture when making a door.<br />
-  Default value is <b>"-"</b> (no texture).<br />
-		<br />
-        <b class="fat">makedoorceil</b> (string) - <span class="red">GZDB only</span>.<br />
-		Name of a texture to use as the door's ceiling texture when making a door.<br />
-  Default value is <b>"-"</b> (no texture).<br />
-        <br />
-		<b class="fat">makedooraction</b> (integer)<br />
-		Linedef action number to put on the lines when making a door.<br />
-		<br />
-		<b class="fat">makedoorarg#</b> (0 .. 4) (integer)<br />
-		Arguments for the linedef action number to put on the lines when making a door.<br />
-		<br />
-		<b class="fat">doomlightlevels</b> (boolean)<br />
-		Set this to <b>false</b> to use linear lighting in Doom Builder. Normally Doom Builder uses a simulation of Doom's light levels.
-<br />Default value is <b>true</b>.<br />
-		<br />
-		<b class="fat">start3dmode</b> (integer)<br />
-		Thing type number that Doom Builder will use to keep your Visual Mode camera position stored in the map. Doom Builder will place a single thing of this type in your map and move it along as you move in Visual Mode.<br />
-		<br />
-		<b class="fat">skyflatname</b> (string)<br />
-		Name of the flat that is interpreted as sky (meaning there is no ceiling). Doom Builder and plugins can use this information for various purposes.
-  <br />Default value is <b>"F_SKY1"</b>.<br />
-		<br />
-        <b class="fat">defaultskytextures</b> (structure) - <span class="red">GZDB only</span>.<br />
-		Defines the relationship between map names and sky texture names used by vanilla maps.<br />
-		<br />
-        <strong>Example:</strong>
-        <pre>
+	<br />
+	<b class="fat">damagetypes</b> (string) - <span class="red">GZDB only</span>.<br />
+	Space-separated list of built-in damage types. This list is combined with DamageTypes parsed from DECORATE.<br />
+	<br />
+	<strong>Example:</strong>
+	<pre>
+damagetypes = "None BFGSplash Drowning Slime";
+</pre>
+	<br />
+	<b class="fat">linetagindicatesectors</b> (boolean)<br />
+	When <b>true</b>, Doom Builder will highlight sectors associated with the same tag number when a line is highlighted. This is only really useful for Doom format maps, because Hexen format and UDMF format has no single tag on linedefs (in those formats, the arguments of the linedef's action can be tags, which also works to highlight sectors).<br />
+	Default value is <b>false</b>.<br />
+	<br />
+	<b class="fat">singlesidedflag</b> (integer or string)<br />
+	This lets Doom Builder know the <a href="gc_linedefflags.html">linedef flag</a> that indicates a line with only one side. Doom Builder will set this flag value on a linedef when it changes a line to become single sided and removes the flag from a linedef when it becomes double sided. Plugins can also use this information to perform operations on linedefs. For map formats that use numeric flags (Doom and Hexen) this must be an integer flag value. For map formats that use named flags (UDMF), this must be a string indicating the name of the flag.<br />
+	<br />
+	<b class="fat">doublesidedflag</b> (integer or string)<br />
+	This lets Doom Builder know the <a href="gc_linedefflags.html">linedef flag</a> that indicates a line with two sides. Doom Builder will set this flag value on a linedef when it changes a line to become double sided and removes the flag from a linedef when it becomes single sided. Plugins can also use this information to perform operations on linedefs. For map formats that use numeric flags (Doom and Hexen) this must be an integer flag value. For map formats that use named flags (UDMF), this must be a string indicating the name of the flag.<br />
+	<br />
+	<b class="fat">impassableflag</b> (integer or string)<br />
+	This lets Doom Builder know the <a href="gc_linedefflags.html">linedef flag</a> that indicates a line which blocks players and monsters. Doom Builder uses this to give the line a special color and plugins can use this information to perform operations related to blocking sound lines. For map formats that use numeric flags (Doom and Hexen) this must be an integer specifying the flag value of the Impassable flag. For map formats that use named flags (UDMF), this must be a string indicating the name of the Impassable flag.<br />
+	<br />
+	<b class="fat">defaultwalltexture</b> (string) - <span class="red">GZDB only</span>.<br />
+	Name of a texture to use on sidedefs when creating a new sector.<br />
+	Default value is <b>"STARTAN"</b>.<br />
+	<br />
+	<b class="fat">defaultfloortexture</b> (string) - <span class="red">GZDB only</span>.<br />
+	Name of a flat to use on the floor when creating a new sector.<br />
+	Default value is <b>"FLOOR0_1"</b>.<br />
+	<br />
+	<b class="fat">defaultceilingtexture</b> (string) - <span class="red">GZDB only</span>.<br />
+	Name of a flat to use on the ceiling when creating a new sector.<br />
+	Default value is <b>"CEIL1_1"</b>.<br />
+	<br />
+	<b class="fat">makedoortrack</b> (string)<br />
+	Name of a texture to use on the walls when making a door.<br />
+	Default value is <b>"-"</b> (no texture).<br />
+	<br />
+	<b class="fat">makedoordoor</b> (string) - <span class="red">GZDB only</span>.<br />
+	Name of a texture to use as the door texture when making a door.<br />
+	Default value is <b>"-"</b> (no texture).<br />
+	<br />
+	<b class="fat">makedoorceil</b> (string) - <span class="red">GZDB only</span>.<br />
+	Name of a texture to use as the door's ceiling texture when making a door.<br />
+	Default value is <b>"-"</b> (no texture).<br />
+	<br />
+	<b class="fat">makedooraction</b> (integer)<br />
+	Linedef action number to put on the lines when making a door.<br />
+	<br />
+	<b class="fat">makedoorarg#</b> (0 .. 4) (integer)<br />
+	Arguments for the linedef action number to put on the lines when making a door.<br />
+	<br />
+	<b class="fat">doomlightlevels</b> (boolean)<br />
+	Set this to <b>false</b> to use linear lighting in Doom Builder. Normally Doom Builder uses a simulation of Doom's light levels. <br />
+	Default value is <b>true</b>.<br />
+	<br />
+	<b class="fat">start3dmode</b> (integer)<br />
+	Thing type number that Doom Builder will use to keep your Visual Mode camera position stored in the map. Doom Builder will place a single thing of this type in your map and move it along as you move in Visual Mode.<br />
+	<br />
+	<b class="fat">skyflatname</b> (string)<br />
+	Name of the flat that is interpreted as sky (meaning there is no ceiling). Doom Builder and plugins can use this information for various purposes. <br />
+	Default value is <b>"F_SKY1"</b>.<br />
+	<br />
+	<b class="fat">defaultskytextures</b> (structure) - <span class="red">GZDB only</span>.<br />
+	Defines the relationship between map names and sky texture names used by vanilla maps.<br />
+	<br />
+	<strong>Example:</strong>
+	<pre>
 defaultskytextures
 {
 	SKY1 = "MAP01,MAP02,MAP03,MAP04,MAP05";
@@ -145,10 +147,11 @@ defaultskytextures
 	SKY3 = "MAP21,MAP22,MAP23,MAP24,MAP25";
 }
 </pre>
-		<br />
-<b class="fat">longtexturenames</b> (boolean) - <span class="red">GZDB only</span>.<br />
-		Enables support for long (> 8 chars) texture names. This is used by GZDoom Builder to limit the input fields in the user interface and to check the validity of texture names in resources. This setting should only be enabled for UDMF game configurations. Enabling this setting will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3 This does NOT determine the actual limitation on the texture names in the map file format.<br />Default value is <b>false</b>.<br />
-		<br />
+	<br />
+	<b class="fat">longtexturenames</b> (boolean) - <span class="red">GZDB only</span>.<br />
+	Enables support for long (> 8 chars) texture names. This is used by GZDoom Builder to limit the input fields in the user interface and to check the validity of texture names in resources. This setting should only be enabled for UDMF game configurations. Enabling this setting will make maps incompatible with Doom Builder 2 and can lead to problems in Slade 3 This does NOT determine the actual limitation on the texture names in the map file format.<br />
+	Default value is <b>false</b>.<br />
+	<br />
 	</p>
-	</div>
+</div>
 </body>
diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index 1cf75992f..0ba80cbd0 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -1016,6 +1016,7 @@
     <Compile Include="ZDoom\ReverbsParser.cs" />
     <Compile Include="ZDoom\SndSeqParser.cs" />
     <Compile Include="ZDoom\StateGoto.cs" />
+    <Compile Include="ZDoom\TerrainParser.cs" />
     <Compile Include="ZDoom\TexturesParser.cs" />
     <Compile Include="ZDoom\TextureStructure.cs" />
     <Compile Include="ZDoom\VoxeldefParser.cs" />
diff --git a/Source/Core/Compilers/AccCompiler.cs b/Source/Core/Compilers/AccCompiler.cs
index 745120d33..e28b58eb4 100644
--- a/Source/Core/Compilers/AccCompiler.cs
+++ b/Source/Core/Compilers/AccCompiler.cs
@@ -78,9 +78,9 @@ namespace CodeImp.DoomBuilder.Compilers
 				foreach(string include in includes)
 				{
 					// Grab the script text from the resources
-					Stream s = General.Map.Data.LoadFile(include);
+					TextResourceData data = General.Map.Data.LoadFile(include);
 
-					if(s != null)
+					if(data.Stream != null)
 					{
 						// Pull the pk3 or directory sub folder out if applicable
 						FileInfo fi = new FileInfo(Path.Combine(this.tempdir.FullName, include));
@@ -94,8 +94,8 @@ namespace CodeImp.DoomBuilder.Compilers
 							if(!string.IsNullOrEmpty(fi.DirectoryName)) Directory.CreateDirectory(fi.DirectoryName);
 
 							// Dump the script into the target file
-							BinaryReader reader = new BinaryReader(s);
-							File.WriteAllBytes(fi.FullName, reader.ReadBytes((int)s.Length));
+							BinaryReader reader = new BinaryReader(data.Stream);
+							File.WriteAllBytes(fi.FullName, reader.ReadBytes((int)data.Stream.Length));
 						}
 					}
 				}
@@ -174,10 +174,14 @@ namespace CodeImp.DoomBuilder.Compilers
 							
 							// Everything before the match is the filename
 							err.filename = linestr.Substring(0, match.Index).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+
+							//mxd. Get rid of temp directory path
+							if(err.filename.StartsWith(this.tempdir.Name)) err.filename = err.filename.Replace(this.tempdir.Name, string.Empty);
+							
 							if(!Path.IsPathRooted(err.filename))
 							{
 								//mxd. If the error is in an include file, try to find it in loaded resources
-								if(includes.Contains(err.filename.ToLowerInvariant()))
+								if(includes.Contains(err.filename))
 								{
 									foreach(DataReader dr in General.Map.Data.Containers)
 									{
diff --git a/Source/Core/Config/GameConfiguration.cs b/Source/Core/Config/GameConfiguration.cs
index 06ea0f17d..1b40ea2d8 100644
--- a/Source/Core/Config/GameConfiguration.cs
+++ b/Source/Core/Config/GameConfiguration.cs
@@ -54,7 +54,7 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly string defaultsavecompiler;
 		private readonly string defaulttestcompiler;
 		private readonly string formatinterface;
-		private readonly string defaultLinedefActivation; //mxd
+		private readonly string defaultlinedefactivation; //mxd
 		private readonly string singlesidedflag;
 		private readonly string doublesidedflag;
 		private readonly string impassableflag;
@@ -77,7 +77,7 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly bool linetagindicatesectors;
 		private readonly string decorategames;
 		private string skyflatname;
-		private Dictionary<string, string> defaultskytextures; //mxd <map name, sky texture name>
+		private readonly Dictionary<string, string> defaultskytextures; //mxd <map name, sky texture name>
 		private readonly int maxtexturenamelength;
 		private readonly bool longtexturenames; //mxd
 		private readonly int leftboundary;
@@ -147,13 +147,16 @@ namespace CodeImp.DoomBuilder.Config
 		
 		// Enums
 		private readonly Dictionary<string, EnumList> enums;
+
+		//mxd. DamageTypes
+		private HashSet<string> damagetypes; 
 		
 		// Defaults
 		private readonly List<DefinedTextureSet> texturesets;
 		private readonly List<ThingsFilter> thingfilters;
 
 		//mxd. Holds base game type (doom, heretic, hexen or strife)
-		private readonly GameType gameType;
+		private readonly GameType gametype;
 		
 		#endregion
 
@@ -171,7 +174,7 @@ namespace CodeImp.DoomBuilder.Config
 		public string DefaultCeilingTexture { get { return defaultceilingtexture; } } //mxd
 		public bool ScaledTextureOffsets { get { return scaledtextureoffsets; } }
 		public string FormatInterface { get { return formatinterface; } }
-		public string DefaultLinedefActivationFlag { get { return defaultLinedefActivation; } } //mxd
+		public string DefaultLinedefActivationFlag { get { return defaultlinedefactivation; } } //mxd
 		public string SingleSidedFlag { get { return singlesidedflag; } }
 		public string DoubleSidedFlag { get { return doublesidedflag; } }
 		public string ImpassableFlag { get { return impassableflag; } }
@@ -263,12 +266,15 @@ namespace CodeImp.DoomBuilder.Config
 		// Enums
 		public IDictionary<string, EnumList> Enums { get { return enums; } }
 
+		//mxd. DamageTypes
+		internal IEnumerable<string> DamageTypes { get { return damagetypes; } }
+
 		// Defaults
 		internal List<DefinedTextureSet> TextureSets { get { return texturesets; } }
 		public List<ThingsFilter> ThingsFilters { get { return thingfilters; } }
 
 		//mxd
-		public GameType GameType { get { return gameType; } }
+		public GameType GameType { get { return gametype; } }
 		
 		#endregion
 
@@ -316,7 +322,7 @@ namespace CodeImp.DoomBuilder.Config
 
 			//mxd
 			int gt = (cfg.ReadSetting("basegame", (int)GameType.UNKNOWN));
-			gameType = ( (gt > -1 && gt < Gldefs.GLDEFS_LUMPS_PER_GAME.Length) ? (GameType)gt : GameType.UNKNOWN);
+			gametype = ( (gt > -1 && gt < Gldefs.GLDEFS_LUMPS_PER_GAME.Length) ? (GameType)gt : GameType.UNKNOWN);
 
 			enginename = cfg.ReadSetting("engine", "");
 			defaultsavecompiler = cfg.ReadSetting("defaultsavecompiler", "");
@@ -350,7 +356,7 @@ namespace CodeImp.DoomBuilder.Config
 			doomlightlevels = cfg.ReadSetting("doomlightlevels", true);
 			actionspecialhelp = cfg.ReadSetting("actionspecialhelp", string.Empty); //mxd
 			thingclasshelp = cfg.ReadSetting("thingclasshelp", string.Empty); //mxd
-			defaultLinedefActivation = cfg.ReadSetting("defaultlinedefactivation", ""); //mxd
+			defaultlinedefactivation = cfg.ReadSetting("defaultlinedefactivation", ""); //mxd
 			for(int i = 0; i < Linedef.NUM_ARGS; i++) makedoorargs[i] = cfg.ReadSetting("makedoorarg" + i.ToString(CultureInfo.InvariantCulture), 0);
 
 			//mxd. Update map format flags
@@ -391,6 +397,9 @@ namespace CodeImp.DoomBuilder.Config
 
 			// Enums
 			LoadEnums();
+
+			//mxd. Damage types
+			LoadDamageTypes();
 			
 			// Things
 			LoadThingFlags();
@@ -471,6 +480,25 @@ namespace CodeImp.DoomBuilder.Config
 				enums.Add(de.Key.ToString(), list);
 			}
 		}
+
+		//mxd. This loads built-in DamageTypes
+		private void LoadDamageTypes()
+		{
+			string dtypes = cfg.ReadSetting("damagetypes", "None");
+			string[] dtypesarr = dtypes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+			damagetypes = new HashSet<string>();
+
+			foreach(string dtype in dtypesarr)
+			{
+				if(damagetypes.Contains(dtype))
+				{
+					General.ErrorLogger.Add(ErrorType.Warning, "DamageType \"" + dtype + "\" is double defined in the \"" + this.Name + "\" game configuration");
+					continue;
+				}
+
+				damagetypes.Add(dtype);
+			}
+		}
 		
 		// This loads a universal fields list
 		private List<UniversalFieldInfo> LoadUniversalFields(string elementname)
@@ -489,7 +517,7 @@ namespace CodeImp.DoomBuilder.Config
 				}
 				catch(Exception)
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Unable to read universal field definition 'universalfields." + elementname + "." + de.Key + "' from game configuration '" + this.Name + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Unable to read universal field definition \"universalfields." + elementname + "." + de.Key + "\" from game configuration \"" + this.Name + "\"");
 				}
 			}
 
@@ -532,8 +560,8 @@ namespace CodeImp.DoomBuilder.Config
 			{
 				if(!things.ContainsKey(t.Index)) 
 					things.Add(t.Index, t);
-				else 
-					General.ErrorLogger.Add(ErrorType.Warning, "Thing number " + t.Index + " is defined more than once (as '" + things[t.Index].Title + "' and '" + t.Title + "') in the '" + this.Name + "' game configuration");
+				else
+					General.ErrorLogger.Add(ErrorType.Warning, "Thing number " + t.Index + " is defined more than once (as \"" + things[t.Index].Title + "\" and \"" + t.Title + "\") in the \"" + this.Name + "\" game configuration");
 			}
 
 			// Recursively add things from child categories
@@ -624,7 +652,7 @@ namespace CodeImp.DoomBuilder.Config
 							{
 								// Failure
 								if(de.Value != null)
-									General.ErrorLogger.Add(ErrorType.Warning, "Structure 'linedeftypes' contains invalid types in game configuration '" + this.Name + "'. All types must be expanded structures.");
+									General.ErrorLogger.Add(ErrorType.Warning, "Structure \"linedeftypes\" contains invalid types in the \"" + this.Name + "\" game configuration. All types must be expanded structures.");
 							}
 						}
 					}
@@ -676,7 +704,7 @@ namespace CodeImp.DoomBuilder.Config
 				}
 				else
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Structure 'gen_linedeftypes' contains invalid entries in game configuration '" + this.Name + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Structure \"gen_linedeftypes\" contains invalid entries in the \"" + this.Name + "\" game configuration");
 				}
 			}
 		}
@@ -703,7 +731,7 @@ namespace CodeImp.DoomBuilder.Config
 				}
 				else
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Structure 'sectortypes' contains invalid keys in game configuration '" + this.Name + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Structure \"sectortypes\" contains invalid keys in the \"" + this.Name + "\" game configuration");
 				}
 			}
 
@@ -728,7 +756,7 @@ namespace CodeImp.DoomBuilder.Config
 				}
 				else
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Structure 'sectorbrightness' contains invalid keys in game configuration '" + this.Name + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Structure \"sectorbrightness\" contains invalid keys in the \"" + this.Name + "\" game configuration");
 				}
 			}
 
@@ -751,7 +779,7 @@ namespace CodeImp.DoomBuilder.Config
 				}
 				else
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Structure 'gen_sectortypes' contains invalid entries in game configuration '" + this.Name + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Structure \"gen_sectortypes\" contains invalid entries in the \"" + this.Name + "\" game configuration");
 				}
 			}
 		}
@@ -777,7 +805,7 @@ namespace CodeImp.DoomBuilder.Config
 				foreach(string s in thingflagscompare[group].Flags.Keys)
 				{
 					if(flagscache.Contains(s))
-						General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare flag '" + s + "' is double-defined in '" + group + "' group!");
+						General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare flag \"" + s + "\" is double defined in the \"" + group + "\" group of the \"" + this.Name + "\" game configuration");
 					else
 						flagscache.Add(s);
 				}
@@ -793,7 +821,7 @@ namespace CodeImp.DoomBuilder.Config
 					{
 						if(!thingflagscompare.ContainsKey(s))
 						{
-							General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare group '" + s + "', required by flag '" + flag.Flag + "' does not exist!");
+							General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare group \"" + s + "\" required by flag \"" + flag.Flag + "\" does not exist in the \"" + this.Name + "\" game configuration");
 							flag.RequiredGroups.Remove(s);
 						}
 					}
@@ -803,15 +831,15 @@ namespace CodeImp.DoomBuilder.Config
 					{
 						if(!thingflagscompare.ContainsKey(s))
 						{
-							General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare group '" + s + "', ignored by flag '" + flag.Flag + "' does not exist!");
+							General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare group \"" + s + "\", ignored by flag \"" + flag.Flag + "\" does not exist in the \"" + this.Name + "\" game configuration");
 							flag.IgnoredGroups.Remove(s);
 						}
 					}
 
 					// Required flag is missing?
-					if(!string.IsNullOrEmpty(flag.RequiredFlag) && !flagscache.Contains(flag.RequiredFlag) /*!group.Value.Flags.ContainsKey(flag.RequiredFlag)*/) 
+					if(!string.IsNullOrEmpty(flag.RequiredFlag) && !flagscache.Contains(flag.RequiredFlag)) 
 					{
-						General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare flag '" + flag.RequiredFlag + "', required by flag '" + flag.Flag + "' does not exist!");
+						General.ErrorLogger.Add(ErrorType.Warning, "ThingFlagsCompare flag \"" + flag.RequiredFlag + "\", required by flag \"" + flag.Flag + "\" does not exist in the \"" + this.Name + "\" game configuration");
 						flag.RequiredFlag = string.Empty;
 					}
 				}
@@ -835,7 +863,7 @@ namespace CodeImp.DoomBuilder.Config
 				}
 				else
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Structure 'defaultthingflags' contains unknown thing flags in game configuration '" + this.Name + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Structure \"defaultthingflags\" contains unknown thing flags in the \"" + this.Name + "\" game configuration");
 				}
 			}
 		}
@@ -854,7 +882,7 @@ namespace CodeImp.DoomBuilder.Config
 				}
 				else
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Structure 'skills' contains invalid skill numbers in game configuration '" + this.Name + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Structure \"skills\" contains invalid skill numbers in the \"" + this.Name + "\" game configuration");
 				}
 			}
 		}
@@ -911,14 +939,14 @@ namespace CodeImp.DoomBuilder.Config
 				string skytex = de.Key.ToString();
 				if(defaultskytextures.ContainsKey(skytex))
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Sky texture \"" + skytex + "\" is double-defined in the current game configuration!");
+					General.ErrorLogger.Add(ErrorType.Warning, "Sky texture \"" + skytex + "\" is double defined in the \"" + this.Name + "\" game configuration");
 					continue;
 				}
 
 				string[] maps = de.Value.ToString().Split(separator, StringSplitOptions.RemoveEmptyEntries);
 				if(maps.Length == 0)
 				{
-					General.ErrorLogger.Add(ErrorType.Warning, "Sky texture \"" + skytex + "\" has no map names defined in the current game configuration!");
+					General.ErrorLogger.Add(ErrorType.Warning, "Sky texture \"" + skytex + "\" has no map names defined in the \"" + this.Name + "\" game configuration");
 					continue;
 				}
 
@@ -926,7 +954,7 @@ namespace CodeImp.DoomBuilder.Config
 				{
 					if(defaultskytextures.ContainsKey(map))
 					{
-						General.ErrorLogger.Add(ErrorType.Warning, "Map \"" + map + "\" is double-defined in the \"DefaultSkyTextures\" block of current game configuration!");
+						General.ErrorLogger.Add(ErrorType.Warning, "Map \"" + map + "\" is double defined in the \"DefaultSkyTextures\" block of \"" + this.Name + "\" game configuration");
 						continue;
 					}
 
diff --git a/Source/Core/Config/ResourceTextureSet.cs b/Source/Core/Config/ResourceTextureSet.cs
index edaf0423c..c2ac788bb 100644
--- a/Source/Core/Config/ResourceTextureSet.cs
+++ b/Source/Core/Config/ResourceTextureSet.cs
@@ -65,7 +65,8 @@ namespace CodeImp.DoomBuilder.Config
 		// Add a texture
 		internal void AddTexture(ImageData image)
 		{
-			if(textures.ContainsKey(image.LongName) && !image.HasPatchWithSameName)
+			//mxd. Wad duplicates are checked by WadReader
+			if(location.type != DataLocation.RESOURCE_WAD && textures.ContainsKey(image.LongName) && !image.HasPatchWithSameName)
 				General.ErrorLogger.Add(ErrorType.Warning, "Texture \"" + image.Name + "\" is double defined in resource \"" + this.Location.location + "\".");
 			textures[image.LongName] = image;
 		}
@@ -73,7 +74,8 @@ namespace CodeImp.DoomBuilder.Config
 		// Add a flat
 		internal void AddFlat(ImageData image)
 		{
-			if(flats.ContainsKey(image.LongName) && (!General.Map.Config.MixTexturesFlats || !image.HasPatchWithSameName))
+			//mxd. Wad duplicates are checked by WadReader
+			if(location.type != DataLocation.RESOURCE_WAD && flats.ContainsKey(image.LongName) && (!General.Map.Config.MixTexturesFlats || !image.HasPatchWithSameName))
 				General.ErrorLogger.Add(ErrorType.Warning, "Flat \"" + image.Name + "\" is double defined in resource \"" + this.Location.location + "\".");
 			flats[image.LongName] = image;
 		}
diff --git a/Source/Core/Controls/ScriptDocumentTab.cs b/Source/Core/Controls/ScriptDocumentTab.cs
index 369670f07..61f9afefc 100644
--- a/Source/Core/Controls/ScriptDocumentTab.cs
+++ b/Source/Core/Controls/ScriptDocumentTab.cs
@@ -17,10 +17,12 @@
 #region ================== Namespaces
 
 using System;
+using System.Globalization;
 using System.IO;
 using System.Collections.Generic;
 using System.Drawing;
 using System.Windows.Forms;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.Windows;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Compilers;
@@ -114,7 +116,6 @@ namespace CodeImp.DoomBuilder.Controls
 			//mxd. Bind functionbar events
 			editor.FunctionBar.DropDown += functionbar_DropDown;
 			editor.FunctionBar.SelectedIndexChanged += functionbar_SelectedIndexChanged;
-
 		}
 		
 		// Disposer
@@ -202,7 +203,8 @@ namespace CodeImp.DoomBuilder.Controls
 		// This changes the script configurations
 		public virtual void ChangeScriptConfig(ScriptConfiguration newconfig)
 		{
-			UpdateNavigator(); //mxd
+			List<CompilerError> errors = UpdateNavigator(); //mxd
+			if(panel.ActiveTab == this) panel.ShowErrors(errors); //mxd
 		}
 
 		// Call this to set the tab title
@@ -273,23 +275,25 @@ namespace CodeImp.DoomBuilder.Controls
 		}
 
 		//mxd
-		protected void UpdateNavigator()
+		//TODO: rewrite this using reflection, move UpdateNavigator[Type] to appropriate parsers
+		internal List<CompilerError> UpdateNavigator()
 		{
 			// Store currently selected item name
 			string prevtext = editor.FunctionBar.Text;
+			List<CompilerError> result = new List<CompilerError>();
 			
 			switch(config.ScriptType)
 			{
 				case ScriptType.ACS:
-					UpdateNavigatorAcs(new MemoryStream(editor.GetText()));
+					result = UpdateNavigatorAcs(new MemoryStream(editor.GetText()));
 					break;
 
 				case ScriptType.DECORATE:
-					UpdateNavigatorDecorate(new MemoryStream(editor.GetText()));
+					result = UpdateNavigatorDecorate(new MemoryStream(editor.GetText()));
 					break;
 
 				case ScriptType.MODELDEF:
-					UpdateNavigatorModeldef(new MemoryStream(editor.GetText()));
+					result = UpdateNavigatorModeldef(new MemoryStream(editor.GetText()));
 					break;
 
 				default: // Unsupported script type. Just clear the items
@@ -322,52 +326,59 @@ namespace CodeImp.DoomBuilder.Controls
 
 				preventchanges = false;
 			}
+
+			return result;
 		}
 
 		//mxd
-		private void UpdateNavigatorDecorate(MemoryStream stream) 
+		private List<CompilerError> UpdateNavigatorDecorate(MemoryStream stream) 
 		{
-			if(stream == null) return;
+			List<CompilerError> result = new List<CompilerError>();
+			if(stream == null) return result;
 			editor.FunctionBar.Items.Clear();
 
 			DecorateParserSE parser = new DecorateParserSE();
-			if(parser.Parse(stream, "DECORATE", false))
-			{
+			TextResourceData data = new TextResourceData(stream, new DataLocation(), "DECORATE", false);
+
+			if(parser.Parse(data, false))
 				editor.FunctionBar.Items.AddRange(parser.Actors.ToArray());
-			}
 
 			if(parser.HasError)
-			{
-				panel.ShowErrors(new List<CompilerError> { new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine) });
-			}
+				result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
+
+			return result;
 		}
 
 		//mxd
-		private void UpdateNavigatorModeldef(MemoryStream stream) 
+		private List<CompilerError> UpdateNavigatorModeldef(MemoryStream stream) 
 		{
-			if(stream == null) return;
+			List<CompilerError> result = new List<CompilerError>();
+			if(stream == null) return result;
 			editor.FunctionBar.Items.Clear();
 
 			ModeldefParserSE parser = new ModeldefParserSE();
-			if(parser.Parse(stream, "MODELDEF", false))
-			{
+			TextResourceData data = new TextResourceData(stream, new DataLocation(), "MODELDEF", false);
+
+			if(parser.Parse(data, false))
 				editor.FunctionBar.Items.AddRange(parser.Models.ToArray());
-			}
 
 			if(parser.HasError)
-			{
-				panel.ShowErrors(new List<CompilerError> { new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine) });
-			}
+				result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
+
+			return result;
 		}
 
 		//mxd
-		private void UpdateNavigatorAcs(MemoryStream stream) 
+		private List<CompilerError> UpdateNavigatorAcs(MemoryStream stream) 
 		{
-			if(stream == null) return;
+			List<CompilerError> result = new List<CompilerError>();
+			if(stream == null) return result;
 			editor.FunctionBar.Items.Clear();
 
-			AcsParserSE parser = new AcsParserSE { AddArgumentsToScriptNames = true, IsMapScriptsLump = this is ScriptLumpDocumentTab };
-			if(parser.Parse(stream, "SCRIPTS", false))
+			AcsParserSE parser = new AcsParserSE { AddArgumentsToScriptNames = true, IsMapScriptsLump = this is ScriptLumpDocumentTab, IgnoreErrors = true };
+			TextResourceData data = new TextResourceData(stream, new DataLocation(), (parser.IsMapScriptsLump ? "?SCRIPTS" : Filename), false);
+			
+			if(parser.Parse(data, false))
 			{
 				editor.FunctionBar.Items.AddRange(parser.NamedScripts.ToArray());
 				editor.FunctionBar.Items.AddRange(parser.NumberedScripts.ToArray());
@@ -375,25 +386,25 @@ namespace CodeImp.DoomBuilder.Controls
 			}
 
 			if(parser.HasError)
-			{
-				panel.ShowErrors(new List<CompilerError> { new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine) });
-			}
+				result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
+
+			return result;
 		}
 		
 		//mxd
 		internal ScriptType VerifyScriptType() 
 		{
 			ScriptTypeParserSE parser = new ScriptTypeParserSE();
-			if(parser.Parse(new MemoryStream(editor.GetText()), config.Description, false)) 
+			TextResourceData data = new TextResourceData(new MemoryStream(editor.GetText()), new DataLocation(), config.Description, false);
+			
+			if(parser.Parse(data, false))
 			{
 				if(parser.ScriptType != ScriptType.UNKNOWN && config.ScriptType != parser.ScriptType)
 					return parser.ScriptType;
 			}
 
 			if(parser.HasError)
-			{
 				panel.ShowErrors(new List<CompilerError> { new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine) });
-			}
 
 			return ScriptType.UNKNOWN;
 		}
@@ -416,30 +427,68 @@ namespace CodeImp.DoomBuilder.Controls
 		{
 			// Text must be exactly the same
 			long hash = MurmurHash2.Hash(Text);
-			if(hash != settings.Hash) return;
-			
-			// Restore fold levels
-			if(settings.FoldLevels != null && General.Settings.ScriptShowFolding && (Scintilla.Lexer == Lexer.Cpp || Scintilla.Lexer == Lexer.CppNoCase))
+			bool applyfolding = General.Settings.ScriptShowFolding && (Scintilla.Lexer == Lexer.Cpp || Scintilla.Lexer == Lexer.CppNoCase);
+			if(hash == settings.Hash)
+			{
+				// Restore fold levels
+				if(applyfolding) ApplyFolding(settings.FoldLevels ?? GetFoldLevels());
+
+				// Restore scroll
+				Scintilla.FirstVisibleLine = settings.FirstVisibleLine;
+
+				// Restore caret position
+				Scintilla.SetEmptySelection(settings.CaretPosition);
+			}
+			// Do what Visual Studio does: fold all #regions 
+			else if(applyfolding)
+			{
+				ApplyFolding(GetFoldLevels());
+			}
+		}
+
+		internal void SetDefaultViewSettings()
+		{
+			if(General.Settings.ScriptShowFolding && (Scintilla.Lexer == Lexer.Cpp || Scintilla.Lexer == Lexer.CppNoCase))
+				ApplyFolding(GetFoldLevels());
+		}
+
+		private void ApplyFolding(Dictionary<int, HashSet<int>> foldlevelsarr)
+		{
+			// We'll want to fold deeper levels first...
+			int[] fl = new int[foldlevelsarr.Keys.Count];
+			foldlevelsarr.Keys.CopyTo(fl, 0);
+
+			List<int> foldlevels = new List<int>(fl);
+			foldlevels.Sort((a, b) => -1 * a.CompareTo(b)); // Sort in descending order
+
+			foreach(int level in foldlevels)
 			{
-				// We'll want to fold deeper levels first...
-				int[] fl = new int[settings.FoldLevels.Keys.Count];
-				settings.FoldLevels.Keys.CopyTo(fl, 0);
+				foreach(int line in foldlevelsarr[level])
+					Scintilla.Lines[line].FoldLine(FoldAction.Contract);
+			}
+		}
 
-				List<int> foldlevels = new List<int>(fl);
-				foldlevels.Sort((a, b) => -1 * a.CompareTo(b)); // Sort in descending order
+		private Dictionary<int, HashSet<int>> GetFoldLevels()
+		{
+			Dictionary<int, HashSet<int>> foldlevels = new Dictionary<int, HashSet<int>>();
+			int foldlevel = NativeMethods.SC_FOLDLEVELBASE;
 
-				foreach(int level in foldlevels)
+			for(int i = 0; i < Scintilla.Lines.Count; i++)
+			{
+				string line = Scintilla.Lines[i].Text.TrimStart();
+				if(line.StartsWith("#region", true, CultureInfo.InvariantCulture))
 				{
-					foreach(int line in settings.FoldLevels[level])
-						Scintilla.Lines[line].FoldLine(FoldAction.Contract);
+					foldlevel++;
+					if(!foldlevels.ContainsKey(foldlevel)) foldlevels.Add(foldlevel, new HashSet<int>());
+					foldlevels[foldlevel].Add(i);
+				}
+				else if(line.StartsWith("#endregion", true, CultureInfo.InvariantCulture) && foldlevel > NativeMethods.SC_FOLDLEVELBASE)
+				{
+					foldlevel--;
 				}
 			}
 
-			// Restore scroll
-			Scintilla.FirstVisibleLine = settings.FirstVisibleLine;
-
-			// Restore caret position
-			Scintilla.SetEmptySelection(settings.CaretPosition);
+			return foldlevels;
 		}
 
 		//mxd
@@ -511,7 +560,7 @@ namespace CodeImp.DoomBuilder.Controls
 		//mxd
 		private void functionbar_DropDown(object sender, EventArgs e) 
 		{
-			if(!preventchanges && editor.IsChanged) UpdateNavigator();
+			if(!preventchanges && editor.IsChanged) panel.ShowErrors(UpdateNavigator());
 		}
 
 		//mxd
diff --git a/Source/Core/Controls/ScriptEditorControl.cs b/Source/Core/Controls/ScriptEditorControl.cs
index 4264dfc4e..251a29d9f 100644
--- a/Source/Core/Controls/ScriptEditorControl.cs
+++ b/Source/Core/Controls/ScriptEditorControl.cs
@@ -69,6 +69,7 @@ namespace CodeImp.DoomBuilder.Controls
 		private const string LEXERS_RESOURCE = "Lexers.cfg";
 		private const int MAX_BACKTRACK_LENGTH = 200;
 		private const int HIGHLIGHT_INDICATOR = 8; //mxd. Indicators 0-7 could be in use by a lexer so we'll use indicator 8 to highlight words.
+		private const string ENTRY_POSITION_MARKER = "[EP]"; //mxd
 		
 		#endregion
 
@@ -237,11 +238,14 @@ namespace CodeImp.DoomBuilder.Controls
 		{
 			scriptedit.Lines[linenumber].Goto();
 			EnsureLineVisible(linenumber);
+			scriptedit.SetEmptySelection(scriptedit.Lines[linenumber].Position);
 		}
 
 		// This makes sure a line is visible
 		public void EnsureLineVisible(int linenumber)
 		{
+			int caretpos = scriptedit.CurrentPosition;
+			
 			// Determine target lines range
 			int startline = Math.Max(0, linenumber - 4);
 			int endline = Math.Min(scriptedit.Lines.Count, Math.Max(linenumber, linenumber + scriptedit.LinesOnScreen - 6));
@@ -255,6 +259,9 @@ namespace CodeImp.DoomBuilder.Controls
 				scriptedit.Lines[startline].Goto();
 			else if(scriptedit.FirstVisibleLine + scriptedit.LinesOnScreen <= endline)
 				scriptedit.Lines[endline].Goto();
+			
+			// We don't want to change caret position
+			scriptedit.CurrentPosition = caretpos;
 		}
 
 		//mxd
@@ -513,7 +520,12 @@ namespace CodeImp.DoomBuilder.Controls
 					// Autocomplete doesn't mind '.' or ':'
 					// Skip adding the keyword if we have a snippet with the same name
 					if(!scriptconfig.Snippets.Contains(p))
-						autocompletedict.Add(p, p + "?" + imageindex);
+					{
+						if(autocompletedict.ContainsKey(p))
+							General.ErrorLogger.Add(ErrorType.Warning, "Property \"" + p + "\" is double defined in \"" + scriptconfig.Description + "\" script configuration.");
+						else
+							autocompletedict.Add(p, p + "?" + imageindex);
+					}
 				}
 				string words = propertieslist.ToString();
 				scriptedit.SetKeywords(propertiesindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
@@ -695,7 +707,7 @@ namespace CodeImp.DoomBuilder.Controls
 				// Check if we have the [EP] marker
 				if(entrypos == -1) 
 				{
-					int pos = processedlines[i].IndexOf("[EP]", StringComparison.Ordinal);
+					int pos = processedlines[i].IndexOf(ENTRY_POSITION_MARKER, StringComparison.OrdinalIgnoreCase);
 					if(pos != -1) 
 					{
 						processedlines[i] = processedlines[i].Remove(pos, 4);
@@ -1307,7 +1319,31 @@ namespace CodeImp.DoomBuilder.Controls
 		{
 			// Expand snippet?
 			string[] lines = scriptconfig.GetSnippet(e.Text);
-			if(lines != null) InsertSnippet(lines);
+			if(lines != null)
+			{
+				InsertSnippet(lines);
+			}
+			// Format editor comment?
+			else if(e.Text.StartsWith("$"))
+			{
+				string definition = scriptconfig.GetFunctionDefinition(e.Text);
+				if(!string.IsNullOrEmpty(definition))
+				{
+					int entrypos = definition.IndexOf(ENTRY_POSITION_MARKER, StringComparison.OrdinalIgnoreCase);
+					
+					// Remove the marker
+					if(entrypos != -1) definition = definition.Remove(entrypos, 4);
+					
+					// Replace insterted text with expanded comment
+					int startpos = scriptedit.WordStartPosition(scriptedit.CurrentPosition, true);
+					scriptedit.SelectionStart = startpos;
+					scriptedit.SelectionEnd = scriptedit.WordEndPosition(scriptedit.CurrentPosition, true);
+					scriptedit.ReplaceSelection(definition);
+					
+					// Update caret position
+					if(entrypos != -1) scriptedit.SetEmptySelection(startpos + entrypos);
+				}
+			}
 		}
 		
 		// Key pressed down
diff --git a/Source/Core/Controls/ScriptEditorPanel.cs b/Source/Core/Controls/ScriptEditorPanel.cs
index d3649211b..184242fc7 100644
--- a/Source/Core/Controls/ScriptEditorPanel.cs
+++ b/Source/Core/Controls/ScriptEditorPanel.cs
@@ -129,6 +129,10 @@ namespace CodeImp.DoomBuilder.Controls
 						if(General.Map.Options.ScriptLumpSettings[maplumpinfo.Name].IsActiveTab) 
 							activetab = t;
 					}
+					else
+					{
+						t.SetDefaultViewSettings();
+					}
 					
 					t.OnTextChanged += tabpage_OnLumpTextChanged; //mxd
 					t.Scintilla.UpdateUI += scintilla_OnUpdateUI; //mxd
@@ -146,6 +150,10 @@ namespace CodeImp.DoomBuilder.Controls
 						if(General.Map.Options.ScriptLumpSettings[maplumpinfo.Name].IsActiveTab)
 							activetab = t;
 					}
+					else
+					{
+						t.SetDefaultViewSettings();
+					}
 					
 					t.OnTextChanged += tabpage_OnLumpTextChanged; //mxd
 					t.Scintilla.UpdateUI += scintilla_OnUpdateUI; //mxd
@@ -176,6 +184,7 @@ namespace CodeImp.DoomBuilder.Controls
 			{
 				int scriptsindex = GetTabPageIndex("SCRIPTS");
 				tabs.SelectedIndex = (scriptsindex == -1 ? 0 : scriptsindex);
+				activetab = tabs.TabPages[tabs.SelectedIndex] as ScriptDocumentTab;
 			}
 
 			//mxd. Apply quick search settings
@@ -183,8 +192,12 @@ namespace CodeImp.DoomBuilder.Controls
 			searchwholeword.Checked = matchwholeword;
 			searchbox_TextChanged(this, EventArgs.Empty);
 			
-			// If the map has remembered any compile errors, then show them
-			ShowErrors(General.Map.Errors);
+			//mxd. If the map or script navigator has any compile errors, show them
+			if(activetab != null)
+			{
+				List<CompilerError> errors = activetab.UpdateNavigator();
+				ShowErrors(General.Map.Errors.Count > 0 ? General.Map.Errors : errors);
+			}
 			
 			// Done
 			UpdateToolbar(true);
@@ -839,7 +852,27 @@ namespace CodeImp.DoomBuilder.Controls
 				//mxd. Add new tabs
 				foreach(string name in openfile.FileNames)
 				{
-					if(!openedfiles.Contains(name)) OpenFile(name);
+					if(!openedfiles.Contains(name))
+					{
+						ScriptFileDocumentTab t = OpenFile(name);
+						
+						// Apply document settings
+						bool settingsfound = false;
+						foreach(ScriptDocumentSettings settings in General.Map.Options.ScriptFileSettings.Values)
+						{
+							// Does this file exist?
+							if(settings.Filename == t.Filename)
+							{
+								// Apply stored settings
+								t.SetViewSettings(settings);
+								settingsfound = true;
+								break;
+							}
+						}
+
+						// Apply default settings
+						if(!settingsfound) t.SetDefaultViewSettings();
+					}
 				}
 
 				// Select the last new item
@@ -909,6 +942,10 @@ namespace CodeImp.DoomBuilder.Controls
 		// A tab is selected
 		private void tabs_Selecting(object sender, TabControlCancelEventArgs e)
 		{
+			//mxd. Update script navigator
+			ScriptDocumentTab tab = e.TabPage as ScriptDocumentTab;
+			if(tab != null) ShowErrors(tab.UpdateNavigator());
+
 			UpdateToolbar(true);
 		}
 		
diff --git a/Source/Core/Controls/ScriptFileDocumentTab.cs b/Source/Core/Controls/ScriptFileDocumentTab.cs
index a36f168e3..bf04eced8 100644
--- a/Source/Core/Controls/ScriptFileDocumentTab.cs
+++ b/Source/Core/Controls/ScriptFileDocumentTab.cs
@@ -22,6 +22,7 @@ using System.IO;
 using System.Windows.Forms;
 using CodeImp.DoomBuilder.Compilers;
 using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.GZBuilder.GZDoom;
 
 #endregion
@@ -127,7 +128,7 @@ namespace CodeImp.DoomBuilder.Controls
 			compiler.Dispose();
 
 			//mxd. Update script navigator
-			UpdateNavigator();
+			errors.AddRange(UpdateNavigator());
 			
 			// Feed errors to panel
 			panel.ShowErrors(errors);
@@ -172,10 +173,27 @@ namespace CodeImp.DoomBuilder.Controls
 			}
 
 			// Preprocess the file
-			AcsParserSE parser = new AcsParserSE { OnInclude = (se, path, includetype) => se.Parse(General.Map.Data.LoadFile(path), path, true, includetype, false) };
+			AcsParserSE parser = new AcsParserSE
+			{
+				OnInclude = delegate(AcsParserSE se, string includefile, AcsParserSE.IncludeType includetype)
+				{
+					TextResourceData data = General.Map.Data.LoadFile(includefile);
+					if(data == null)
+					{
+						// Fial
+						errors.Add(new CompilerError("Unable to find include file \"" + includefile + "\""));
+						panel.ShowErrors(errors);
+					}
+					else
+					{
+						se.Parse(data, true, includetype, false);
+					}
+				}
+			};
 			using(FileStream stream = File.OpenRead(filepathname))
 			{
-				if(!parser.Parse(stream, filepathname, scriptconfig.Compiler.Files, true, AcsParserSE.IncludeType.NONE, false))
+				TextResourceData data = new TextResourceData(stream, new DataLocation(), filepathname, false);
+				if(!parser.Parse(data, scriptconfig.Compiler.Files, true, AcsParserSE.IncludeType.NONE, false))
 				{
 					// Check for errors
 					if(parser.HasError)
@@ -258,7 +276,7 @@ namespace CodeImp.DoomBuilder.Controls
 			compiler.Dispose();
 
 			// Update script navigator
-			UpdateNavigator();
+			errors.AddRange(UpdateNavigator());
 
 			// Feed errors to panel
 			panel.ShowErrors(errors);
@@ -333,7 +351,7 @@ namespace CodeImp.DoomBuilder.Controls
 			this.filepathname = filepathname;
 			editor.ClearUndoRedo();
 			SetTitle(Path.GetFileName(filepathname));
-			UpdateNavigator(); //mxd
+			panel.ShowErrors(UpdateNavigator()); //mxd
 
 			return true;
 		}
diff --git a/Source/Core/Controls/ScriptLumpDocumentTab.cs b/Source/Core/Controls/ScriptLumpDocumentTab.cs
index 5b774514f..5f5aba6a4 100644
--- a/Source/Core/Controls/ScriptLumpDocumentTab.cs
+++ b/Source/Core/Controls/ScriptLumpDocumentTab.cs
@@ -74,7 +74,6 @@ namespace CodeImp.DoomBuilder.Controls
 			{
 				editor.SetText(stream.ToArray()); //mxd
 				editor.ClearUndoRedo();
-				UpdateNavigator(); //mxd
 			}
 
 			// Set title
@@ -103,10 +102,10 @@ namespace CodeImp.DoomBuilder.Controls
 			General.Map.CompileLump((ismapheader ? MapManager.CONFIG_MAP_HEADER : lumpname), true);
 			
 			//mxd. Update script navigator
-			UpdateNavigator();
+			errors = UpdateNavigator();
 
 			// Feed errors to panel
-			panel.ShowErrors(General.Map.Errors);
+			panel.ShowErrors(General.Map.Errors.Count > 0 ? General.Map.Errors : errors);
 		}
 		
 		// Implicit save
diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs
index 1e7d8126a..a3521ddd0 100644
--- a/Source/Core/Data/DataManager.cs
+++ b/Source/Core/Data/DataManager.cs
@@ -42,6 +42,19 @@ using Matrix = SlimDX.Matrix;
 
 namespace CodeImp.DoomBuilder.Data
 {
+	public struct TextResource //mxd
+	{
+		public string Filename; // Path to text file inside of Resource
+		public int LumpIndex;   // Text lump index if Resource is wad
+		internal DataReader Resource;
+		public HashSet<string> Entries; // Actors/models/sounds etc.
+
+		public override string ToString()
+		{
+			return Filename + (LumpIndex != -1 ? ":" + LumpIndex : "") + " (" + Entries.Count + " entries)";
+		}
+	}
+	
 	public sealed class DataManager
 	{
 		#region ================== Constants
@@ -74,12 +87,17 @@ namespace CodeImp.DoomBuilder.Data
 
 		//mxd 
 		private Dictionary<int, ModelData> modeldefentries; //Thing.Type, Model entry
-		private readonly Dictionary<int, DynamicLightData> gldefsentries; //Thing.Type, Light entry
+		private Dictionary<int, DynamicLightData> gldefsentries; //Thing.Type, Light entry
 		private MapInfo mapinfo;
 		private Dictionary<string, KeyValuePair<int, int>> reverbs; //<name, <arg1, arg2> 
 		private Dictionary<long, GlowingFlatData> glowingflats; // Texture name hash, Glowing Flat Data
 		private Dictionary<string, SkyboxInfo> skyboxes; 
-		private List<string> soundsequences;
+		private string[] soundsequences;
+		private string[] terrainnames; 
+		private string[] damagetypes;
+
+		//mxd. Text resources
+		private Dictionary<ScriptType, HashSet<TextResource>> textresources; 
 		
 		// Background loading
 		private Queue<ImageData> imageque;
@@ -108,8 +126,8 @@ namespace CodeImp.DoomBuilder.Data
 		private ImageData[] commenttextures;
 		
 		// Used images
-		private Dictionary<long, long> usedtextures; //mxd
-		private Dictionary<long, long> usedflats; //mxd. Used only when MixTextursFlats is disabled
+		private Dictionary<long, bool> usedtextures; //mxd
+		private Dictionary<long, bool> usedflats; //mxd. Used only when MixTextursFlats is disabled
 		
 		// Things combined with things created from Decorate
 		private DecorateParser decorate;
@@ -133,8 +151,13 @@ namespace CodeImp.DoomBuilder.Data
 		public MapInfo MapInfo { get { return mapinfo; } }
 		public Dictionary<string, KeyValuePair<int, int>> Reverbs { get { return reverbs; } }
 		public Dictionary<long, GlowingFlatData> GlowingFlats { get { return glowingflats; } }
-		public List<string> SoundSequences { get { return soundsequences; } }
-		internal List<DataReader> Containers { get { return containers; } } //mxd
+		public string[] SoundSequences { get { return soundsequences; } }
+		public string[] TerrainNames { get { return terrainnames; } }
+		public string[] DamageTypes { get { return damagetypes; } }
+		internal Dictionary<ScriptType, HashSet<TextResource>> TextResources { get { return textresources; } }
+
+		//mxd
+		internal IEnumerable<DataReader> Containers { get { return containers; } }
 
 		public Playpal Palette { get { return palette; } }
 		public PreviewManager Previews { get { return previews; } }
@@ -179,15 +202,6 @@ namespace CodeImp.DoomBuilder.Data
 			// We have no destructor
 			GC.SuppressFinalize(this);
 
-			//mxd.
-			modeldefentries = new Dictionary<int, ModelData>();
-			gldefsentries = new Dictionary<int, DynamicLightData>();
-			reverbs = new Dictionary<string, KeyValuePair<int, int>>(StringComparer.Ordinal);
-			glowingflats = new Dictionary<long, GlowingFlatData>();
-			skyboxes = new Dictionary<string, SkyboxInfo>(StringComparer.Ordinal);
-
-			soundsequences = new List<string>();
-
 			// Load special images (mxd: the rest is loaded in LoadInternalTextures())
 			whitetexture = new ResourceImage("CodeImp.DoomBuilder.Resources.White.png") { UseColorCorrection = false };
 			whitetexture.LoadImage();
@@ -294,11 +308,22 @@ namespace CodeImp.DoomBuilder.Data
 			imageque = new Queue<ImageData>();
 			previews = new PreviewManager();
 			texturesets = new List<MatchingTextureSet>();
-			usedtextures = new Dictionary<long, long>(); //mxd
-			usedflats = new Dictionary<long, long>(); //mxd
+			usedtextures = new Dictionary<long, bool>(); //mxd
+			usedflats = new Dictionary<long, bool>(); //mxd
 			internalsprites = new Dictionary<string, ImageData>(StringComparer.Ordinal);
 			thingcategories = General.Map.Config.GetThingCategories();
 			thingtypes = General.Map.Config.GetThingTypes();
+
+			//mxd. Create even more collections!
+			modeldefentries = new Dictionary<int, ModelData>();
+			gldefsentries = new Dictionary<int, DynamicLightData>();
+			reverbs = new Dictionary<string, KeyValuePair<int, int>>(StringComparer.Ordinal);
+			glowingflats = new Dictionary<long, GlowingFlatData>();
+			skyboxes = new Dictionary<string, SkyboxInfo>(StringComparer.Ordinal);
+			soundsequences = new string[0];
+			terrainnames = new string[0];
+			textresources = new Dictionary<ScriptType, HashSet<TextResource>>();
+			damagetypes = new string[0];
 			
 			// Load texture sets
 			foreach(DefinedTextureSet ts in General.Map.ConfigSettings.TextureSets)
@@ -360,10 +385,12 @@ namespace CodeImp.DoomBuilder.Data
 			
 			// Load stuff
 			LoadPalette();
-			int texcount = LoadTextures(texturesonly, texturenamesshorttofull);
-			int flatcount = LoadFlats(flatsonly, flatnamesshorttofull);
+			Dictionary<string, TexturesParser> cachedparsers = new Dictionary<string, TexturesParser>(); //mxd
+			int texcount = LoadTextures(texturesonly, texturenamesshorttofull, cachedparsers);
+			int flatcount = LoadFlats(flatsonly, flatnamesshorttofull, cachedparsers);
 			int colormapcount = LoadColormaps(colormapsonly);
-			LoadSprites();
+			LoadSprites(cachedparsers);
+			cachedparsers = null; //mxd
 
 			//mxd. Load MAPINFO. Should happen before parisng DECORATE
 			Dictionary<int, string> spawnnums, doomednums;
@@ -465,6 +492,9 @@ namespace CodeImp.DoomBuilder.Data
 
 			//mxd. Create camera textures. Should be done after loading textures.
 			LoadAnimdefs();
+
+			//mxd
+			LoadTerrain();
 			
 			// Sort names
 			texturenames.Sort();
@@ -547,6 +577,14 @@ namespace CodeImp.DoomBuilder.Data
 			flats = null;
 			sprites = null;
 			modeldefentries = null; //mxd
+			gldefsentries = null; //mxd
+			reverbs = null; //mxd
+			glowingflats = null; //mxd
+			skyboxes = null; //mxd
+			soundsequences = null; //mxd
+			terrainnames = null; //mxd
+			textresources = null; //mxd
+			damagetypes = null; //mxd
 			texturenames = null;
 			flatnames = null;
 			imageque = null;
@@ -568,7 +606,7 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DataReader d in containers)
 			{
 				// Suspend
-				General.WriteLogLine("Suspended data resource '" + d.Location.location + "'");
+				General.WriteLogLine("Suspended data resource \"" + d.Location.location + "\"");
 				d.Suspend();
 			}
 		}
@@ -582,7 +620,7 @@ namespace CodeImp.DoomBuilder.Data
 				try
 				{
 					// Resume
-					General.WriteLogLine("Resumed data resource '" + d.Location.location + "'");
+					General.WriteLogLine("Resumed data resource \"" + d.Location.location + "\"");
 					d.Resume();
 				}
 				catch(Exception e)
@@ -820,9 +858,6 @@ namespace CodeImp.DoomBuilder.Data
 						i.Value.SetUsedInMap(usedtextures.ContainsKey(i.Key));
 						if(i.Value.IsImageLoaded != i.Value.IsReferenced) ProcessImage(i.Value);
 					}
-
-					// Done
-					updatedusedtextures = false;
 				}
 			}
 			//mxd. Use separate collections
@@ -847,10 +882,10 @@ namespace CodeImp.DoomBuilder.Data
 						if(i.Value.IsImageLoaded != i.Value.IsReferenced) ProcessImage(i.Value);
 					}
 				}
-				
-				// Done
-				updatedusedtextures = false;
 			}
+
+			// Done
+			updatedusedtextures = false;
 		}
 		
 		#endregion
@@ -930,7 +965,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Textures
 
 		// This loads the textures
-		private int LoadTextures(Dictionary<long, ImageData> list, Dictionary<long, long> nametranslation)
+		private int LoadTextures(Dictionary<long, ImageData> list, Dictionary<long, long> nametranslation, Dictionary<string, TexturesParser> cachedparsers)
 		{
 			PatchNames pnames = new PatchNames();
 			int counter = 0;
@@ -946,7 +981,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(newpnames != null) pnames = newpnames;
 
 				// Load textures
-				ICollection<ImageData> images = dr.LoadTextures(pnames);
+				IEnumerable<ImageData> images = dr.LoadTextures(pnames, cachedparsers);
 				if(images != null)
 				{
 					// Go for all textures
@@ -1144,7 +1179,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			else
 			{
-				General.ErrorLogger.Add(ErrorType.Warning, "Unable to load editor texture '" + name + "'. Using built-in one instead.");
+				General.ErrorLogger.Add(ErrorType.Warning, "Unable to load editor texture \"" + name + "\". Using built-in one instead.");
 				result = new ResourceImage("CodeImp.DoomBuilder.Resources." + name);
 			}
 
@@ -1157,7 +1192,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Flats
 
 		// This loads the flats
-		private int LoadFlats(Dictionary<long, ImageData> list, Dictionary<long, long> nametranslation)
+		private int LoadFlats(Dictionary<long, ImageData> list, Dictionary<long, long> nametranslation, Dictionary<string, TexturesParser> cachedparsers)
 		{
 			int counter = 0;
 			
@@ -1165,7 +1200,7 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DataReader dr in containers)
 			{
 				// Load flats
-				ICollection<ImageData> images = dr.LoadFlats();
+				IEnumerable<ImageData> images = dr.LoadFlats(cachedparsers);
 				if(images != null)
 				{
 					// Go for all flats
@@ -1279,7 +1314,7 @@ namespace CodeImp.DoomBuilder.Data
 		#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()
+		private int LoadSprites(Dictionary<string, TexturesParser> cachedparsers)
 		{
 			int counter = 0;
 			
@@ -1288,7 +1323,7 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DataReader dr in containers)
 			{
 				// Load sprites
-				ICollection<ImageData> images = dr.LoadSprites();
+				IEnumerable<ImageData> images = dr.LoadSprites(cachedparsers);
 				if(images != null)
 				{
 					// Add or replace in sprites list
@@ -1330,7 +1365,7 @@ namespace CodeImp.DoomBuilder.Data
 					} 
 					else //mxd
 					{
-						General.ErrorLogger.Add(ErrorType.Error, "Missing sprite lump '" + ti.Sprite + "'. Forgot to include required resources?");
+						General.ErrorLogger.Add(ErrorType.Error, "Missing sprite lump \"" + ti.Sprite + "\". Forgot to include required resources?");
 					}
 				} 
 				else 
@@ -1494,7 +1529,7 @@ namespace CodeImp.DoomBuilder.Data
 		private int LoadDecorateThings(Dictionary<int, string> spawnnumsoverride, Dictionary<int, string> doomednumsoverride)
 		{
 			int counter = 0;
-			char[] catsplitter = new[] {Path.AltDirectorySeparatorChar}; //mxd
+			char[] catsplitter = { Path.AltDirectorySeparatorChar }; //mxd
 			
 			// Create new parser
 			decorate = new DecorateParser { OnInclude = LoadDecorateFromLocation };
@@ -1508,26 +1543,43 @@ 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;
-					Dictionary<string, Stream> decostreams = dr.GetDecorateData("DECORATE");
-					foreach(KeyValuePair<string, Stream> group in decostreams)
+					IEnumerable<TextResourceData> decostreams = dr.GetDecorateData("DECORATE");
+					foreach(TextResourceData data in decostreams)
 					{
 						// Parse the data
-						group.Value.Seek(0, SeekOrigin.Begin);
-						decorate.Parse(group.Value, Path.Combine(currentreader.Location.GetShortName(), group.Key), true);
+						data.Stream.Seek(0, SeekOrigin.Begin);
+						decorate.Parse(data, true);
 						
 						//mxd. DECORATE lumps are interdepandable. Can't carry on...
 						if(decorate.HasError)
 						{
-							decorate.LogError(); //mxd
+							decorate.LogError();
+							currentreader = null;
 							return counter;
 						}
 					}
 				}
-				
+
+				//mxd. Add to text resources collection
+				textresources[decorate.ScriptType] = new HashSet<TextResource>(decorate.TextResources.Values);
 				currentreader = null;
 				
 				if(!decorate.HasError)
 				{
+					//mxd. Create DamageTypes list
+					// Combine damage types from config and decorate
+					HashSet<string> dtset = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+					dtset.UnionWith(General.Map.Config.DamageTypes);
+					dtset.UnionWith(decorate.DamageTypes);
+
+					// Sort values
+					List<string> dtypes = new List<string>(dtset);
+					dtypes.Sort();
+					
+					// Apply to collection
+					damagetypes = new string[dtypes.Count];
+					dtypes.CopyTo(damagetypes);
+					
 					// Step 1. Go for all actors in the decorate to make things or update things
 					foreach(ActorStructure actor in decorate.Actors)
 					{
@@ -1632,7 +1684,7 @@ namespace CodeImp.DoomBuilder.Data
 							// Loudly give up...
 							else
 							{
-								General.ErrorLogger.Add(ErrorType.Warning, "Failed to apply MAPINFO DoomEdNum override '" + group.Key + " = " + group.Value + ": failed to find corresponding actor class...");
+								General.ErrorLogger.Add(ErrorType.Warning, "Failed to apply MAPINFO DoomEdNum override \"" + group.Key + " = " + group.Value + "\": failed to find corresponding actor class...");
 							}
 						}
 
@@ -1658,16 +1710,13 @@ namespace CodeImp.DoomBuilder.Data
 					}
 
 					bool spawnidschanged = false;
-					if(!decorate.HasError)
+					foreach(ActorStructure actor in decorate.Actors)
 					{
-						foreach(ActorStructure actor in decorate.Actors)
+						int spawnid = actor.GetPropertyValueInt("spawnid", 0);
+						if(spawnid != 0)
 						{
-							int spawnid = actor.GetPropertyValueInt("spawnid", 0);
-							if(spawnid != 0)
-							{
-								configspawnnums[spawnid] = new EnumItem(spawnid.ToString(), (actor.HasPropertyWithValue("$title") ? actor.GetPropertyAllValues("$title") : actor.ClassName));
-								spawnidschanged = true;
-							}
+							configspawnnums[spawnid] = new EnumItem(spawnid.ToString(), (actor.HasPropertyWithValue("$title") ? actor.GetPropertyAllValues("$title") : actor.ClassName));
+							spawnidschanged = true;
 						}
 					}
 
@@ -1676,9 +1725,7 @@ namespace CodeImp.DoomBuilder.Data
 					{
 						// Modify by MAPINFO data
 						foreach(KeyValuePair<int, string> group in spawnnumsoverride) 
-						{
 							configspawnnums[group.Key] = new EnumItem(group.Key.ToString(), (thingtypes.ContainsKey(group.Key) ? thingtypes[group.Key].Title : group.Value));
-						}
 
 						spawnidschanged = true;
 					}
@@ -1761,11 +1808,11 @@ namespace CodeImp.DoomBuilder.Data
 		private void LoadDecorateFromLocation(DecorateParser parser, string location)
 		{
 			//General.WriteLogLine("Including DECORATE resource '" + location + "'...");
-			Dictionary<string, Stream> decostreams = currentreader.GetDecorateData(location);
-			foreach(KeyValuePair<string, Stream> group in decostreams)
+			IEnumerable<TextResourceData> decostreams = currentreader.GetDecorateData(location);
+			foreach(TextResourceData data in decostreams)
 			{
 				// Parse this data
-				parser.Parse(group.Value, group.Key, false);
+				parser.Parse(data, false);
 
 				//mxd. DECORATE lumps are interdepandable. Can't carry on...
 				if(parser.HasError)
@@ -1821,13 +1868,13 @@ namespace CodeImp.DoomBuilder.Data
 				if(!string.IsNullOrEmpty(ti.Value.ClassName))
 				{
 					if(actors.ContainsKey(ti.Value.ClassName) && actors[ti.Value.ClassName] != ti.Key)
-						General.ErrorLogger.Add(ErrorType.Warning, "actor '" + ti.Value.ClassName + "' has several editor numbers! Only the last one (" + ti.Key + ") will be used.");
+						General.ErrorLogger.Add(ErrorType.Warning, "Actor \"" + ti.Value.ClassName + "\" has several editor numbers! Only the last one (" + ti.Key + ") will be used.");
 					actors[ti.Value.ClassName] = ti.Key;
 				}
 			}
 
 			if(actors.Count == 0) 
-				General.ErrorLogger.Add(ErrorType.Warning, "unable to find any DECORATE actor definitions!");
+				General.ErrorLogger.Add(ErrorType.Warning, "Unable to find any DECORATE actor definitions!");
 
 			return actors;
 		}
@@ -1836,10 +1883,7 @@ namespace CodeImp.DoomBuilder.Data
 		public void ReloadModeldef() 
 		{
 			if(modeldefentries != null) 
-			{
-				foreach(KeyValuePair<int, ModelData> group in modeldefentries)
-					group.Value.Dispose();
-			}
+				foreach(KeyValuePair<int, ModelData> group in modeldefentries) group.Value.Dispose();
 
 			// Bail out when not supported by current game configuration
 			if(string.IsNullOrEmpty(General.Map.Config.DecorateGames)) return;
@@ -1852,7 +1896,7 @@ namespace CodeImp.DoomBuilder.Data
 
 			foreach(Thing t in General.Map.Map.Things) t.UpdateCache();
 
-			//rebuild geometry if in Visual mode
+			// Rebuild geometry if in Visual mode
 			if(General.Editing.Mode != null && General.Editing.Mode.GetType().Name == "BaseVisualMode") 
 			{
 				General.Editing.Mode.OnReloadResources();
@@ -1905,16 +1949,16 @@ namespace CodeImp.DoomBuilder.Data
 			{
 				currentreader = dr;
 
-				Dictionary<string, Stream> streams = dr.GetModeldefData();
-				foreach(KeyValuePair<string, Stream> group in streams) 
+				IEnumerable<TextResourceData> streams = dr.GetModeldefData();
+				foreach(TextResourceData data in streams) 
 				{
 					// Parse the data
-					if(parser.Parse(group.Value, Path.Combine(currentreader.Location.GetShortName(), group.Key), true)) 
+					if(parser.Parse(data, true)) 
 					{
 						foreach(KeyValuePair<string, ModelData> g in parser.Entries) 
 						{
-							if(modeldefentriesbyname.ContainsKey(g.Key)) 
-								General.ErrorLogger.Add(ErrorType.Warning, "Model definition for actor '" + g.Key + "' is double-defined in '" + group.Key + "'");
+							if(modeldefentriesbyname.ContainsKey(g.Key))
+								General.ErrorLogger.Add(ErrorType.Warning, "Model definition for actor \"" + g.Key + "\" is double defined in \"" + Path.Combine(data.Source.Location.GetShortName(), data.Filename) + "\"");
 							
 							modeldefentriesbyname[g.Key] = g.Value;
 						}
@@ -1925,6 +1969,8 @@ namespace CodeImp.DoomBuilder.Data
 				}
 			}
 
+			//mxd. Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
 			currentreader = null;
 
 			foreach(KeyValuePair<string, ModelData> e in modeldefentriesbyname) 
@@ -1932,7 +1978,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(actorsbyclass.ContainsKey(e.Key))
 					modeldefentries[actorsbyclass[e.Key]] = modeldefentriesbyname[e.Key];
 				else if(!decorate.ActorsByClass.ContainsKey(e.Key))
-					General.ErrorLogger.Add(ErrorType.Warning, "Got MODELDEF override for class '" + e.Key + "', but haven't found such class in Decorate");
+					General.ErrorLogger.Add(ErrorType.Warning, "MODELDEF model \"" + e.Key + "\" doesn't match any Decorate actor class");
 			}
 		}
 
@@ -1942,8 +1988,8 @@ namespace CodeImp.DoomBuilder.Data
 			// Bail out when not supported by current game configuration
 			if(string.IsNullOrEmpty(General.Map.Config.DecorateGames)) return;
 			
-			//Get names of all voxel models, which can be used "as is"
-			Dictionary<string, bool> voxelNames = new Dictionary<string, bool>(StringComparer.Ordinal);
+			// Get names of all voxel models, which can be used "as is"
+			HashSet<string> voxelnames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 			
 			foreach(DataReader dr in containers) 
 			{
@@ -1954,7 +2000,7 @@ namespace CodeImp.DoomBuilder.Data
 
 				foreach(string s in result) 
 				{
-					if(!voxelNames.ContainsKey(s)) voxelNames.Add(s, false);
+					if(!voxelnames.Contains(s)) voxelnames.Add(s);
 				}
 			}
 
@@ -1969,7 +2015,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(ti.Sprite.Length == 0 || ti.Sprite.Length > CLASIC_IMAGE_NAME_LENGTH) 
 				{
 					if(ti.Actor == null) continue;
-					sprite = ti.Actor.FindSuitableVoxel(voxelNames);
+					sprite = ti.Actor.FindSuitableVoxel(voxelnames);
 				} 
 				else 
 				{
@@ -1982,17 +2028,17 @@ namespace CodeImp.DoomBuilder.Data
 			}
 
 			VoxeldefParser parser = new VoxeldefParser();
-			Dictionary<string, bool> processed = new Dictionary<string, bool>(StringComparer.Ordinal);
+			HashSet<string> processed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 
-			//parse VOXLEDEF
+			// Parse VOXLEDEF
 			foreach(DataReader dr in containers) 
 			{
 				currentreader = dr;
 
-				KeyValuePair<string, Stream> group = dr.GetVoxeldefData();
-				if(group.Value != null) 
+				IEnumerable<TextResourceData> streams = dr.GetVoxeldefData();
+				foreach(TextResourceData data in streams)
 				{
-					if(parser.Parse(group.Value, Path.Combine(currentreader.Location.GetShortName(), group.Key), true))
+					if(parser.Parse(data, true))
 					{
 						foreach(KeyValuePair<string, ModelData> entry in parser.Entries)
 						{
@@ -2000,8 +2046,9 @@ namespace CodeImp.DoomBuilder.Data
 							{
 								if(sc.Key.Contains(entry.Key))
 								{
-									foreach(int id in sc.Value) modeldefentries[id] = entry.Value;
-									processed.Add(entry.Key, false);
+									foreach(int id in sc.Value)
+										modeldefentries[id] = entry.Value;
+									processed.Add(entry.Key);
 								}
 							}
 						}
@@ -2012,19 +2059,21 @@ namespace CodeImp.DoomBuilder.Data
 				}
 			}
 
+			//mxd. Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
 			currentreader = null;
 
-			//get voxel models
-			foreach(KeyValuePair<string, bool> group in voxelNames) 
+			// Get voxel models
+			foreach(string voxelname in voxelnames) 
 			{
-				if(processed.ContainsKey(group.Key)) continue;
+				if(processed.Contains(voxelname)) continue;
 				foreach(KeyValuePair<string, List<int>> sc in sprites) 
 				{
-					if(sc.Key.Contains(group.Key)) 
+					if(sc.Key.Contains(voxelname)) 
 					{
-						//it's a model without a definition, and it corresponds to a sprite we can display, so let's add it
+						// It's a model without a definition, and it corresponds to a sprite we can display, so let's add it
 						ModelData data = new ModelData { IsVoxel = true };
-						data.ModelNames.Add(group.Key);
+						data.ModelNames.Add(voxelname);
 
 						foreach(int id in sprites[sc.Key]) modeldefentries[id] = data;
 					}
@@ -2045,21 +2094,26 @@ namespace CodeImp.DoomBuilder.Data
 			{
 				currentreader = dr;
 				parser.ClearIncludesList();
-				Dictionary<string, Stream> streams = dr.GetGldefsData(General.Map.Config.GameType);
+				IEnumerable<TextResourceData> streams = dr.GetGldefsData(General.Map.Config.GameType);
 
-				foreach(KeyValuePair<string, Stream> group in streams)
+				foreach(TextResourceData data in streams)
 				{
-					parser.Parse(group.Value, Path.Combine(currentreader.Location.GetShortName(), group.Key), false);
+					parser.Parse(data, false);
 					
 					// Gldefs can be interdependable. Can't carry on
 					if(parser.HasError)
 					{
 						parser.LogError();
+						currentreader = null;
 						return;
 					}
 				}
 			}
 
+			//mxd. Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
+			currentreader = null;
+
 			// Create Gldefs Entries dictionary
 			foreach(KeyValuePair<string, string> e in parser.Objects) //<ClassName, Light name>
 			{ 
@@ -2067,7 +2121,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(actorsbyclass.ContainsKey(e.Key) && parser.LightsByName.ContainsKey(e.Value)) 
 					gldefsentries[actorsbyclass[e.Key]] = parser.LightsByName[e.Value];
 				else if(!decorate.AllActorsByClass.ContainsKey(e.Key))
-					General.ErrorLogger.Add(ErrorType.Warning, "Got GLDEFS light for class '" + e.Key + "', but haven't found such class in DECORATE");
+					General.ErrorLogger.Add(ErrorType.Warning, "GLDEFS object \"" + e.Key + "\" doesn't match any DECORATE actor class");
 			}
 
 			// Grab them glowy flats!
@@ -2093,12 +2147,12 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DataReader dr in containers)
 			{
 				currentreader = dr;
+				IEnumerable<TextResourceData> streams = dr.GetMapinfoData();
 
-				Dictionary<string, Stream> streams = dr.GetMapinfoData();
-				foreach(KeyValuePair<string, Stream> group in streams)
+				foreach(TextResourceData data in streams)
 				{
 					// Parse the data
-					parser.Parse(group.Value, Path.Combine(dr.Location.location, group.Key), General.Map.Options.LevelName, false);
+					parser.Parse(data, General.Map.Options.LevelName, false);
 
 					//MAPINFO lumps are interdependable. Can't carry on...
 					if(parser.HasError)
@@ -2123,14 +2177,16 @@ namespace CodeImp.DoomBuilder.Data
 				doomednums = new Dictionary<int, string>();
 				mapinfo = new MapInfo();
 			}
-			
+
+			//mxd. Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
 			currentreader = null;
 		}
 
 		private void ParseFromLocation(ZDTextParser parser, string location, bool clearerrors)
 		{
 			if(currentreader.IsSuspended) throw new Exception("Data reader is suspended");
-			parser.Parse(currentreader.LoadFile(location), location, clearerrors);
+			parser.Parse(new TextResourceData(currentreader, currentreader.LoadFile(location), location, true), clearerrors);
 		}
 
 		//mxd. This loads REVERBS
@@ -2145,17 +2201,19 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DataReader dr in containers) 
 			{
 				currentreader = dr;
-				Dictionary<string, Stream> streams = dr.GetReverbsData();
-				foreach(KeyValuePair<string, Stream> group in streams) 
+				IEnumerable<TextResourceData> streams = dr.GetReverbsData();
+				foreach(TextResourceData data in streams) 
 				{
 					// Parse the data
-					parser.Parse(group.Value, Path.Combine(currentreader.Location.GetShortName(), group.Key), true);
+					parser.Parse(data, true);
 
 					// Report errors?
 					if(parser.HasError) parser.LogError();
 				}
 			}
 
+			//mxd. Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
 			currentreader = null;
 			reverbs = parser.GetReverbs();
 		}
@@ -2163,8 +2221,6 @@ namespace CodeImp.DoomBuilder.Data
 		//mxd. This loads SNDSEQ
 		private void LoadSndSeq()
 		{
-			soundsequences.Clear();
-
 			// Bail out when not supported by current game configuration
 			if(string.IsNullOrEmpty(General.Map.Config.DecorateGames)) return;
 
@@ -2172,18 +2228,20 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DataReader dr in containers) 
 			{
 				currentreader = dr;
-				Dictionary<string, Stream> streams = dr.GetSndSeqData();
+				IEnumerable<TextResourceData> streams = dr.GetSndSeqData();
 
 				// Parse the data
-				foreach(KeyValuePair<string, Stream> group in streams)
+				foreach(TextResourceData data in streams)
 				{
-					parser.Parse(group.Value, Path.Combine(currentreader.Location.GetShortName(), group.Key), true);
+					parser.Parse(data, true);
 
 					// Report errors?
 					if(parser.HasError) parser.LogError();
 				}
 			}
 
+			//mxd. Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
 			currentreader = null;
 			soundsequences = parser.GetSoundSequences();
 		}
@@ -2198,12 +2256,12 @@ namespace CodeImp.DoomBuilder.Data
 			foreach(DataReader dr in containers)
 			{
 				currentreader = dr;
-				Dictionary<string, Stream> streams = dr.GetAnimdefsData();
+				IEnumerable<TextResourceData> streams = dr.GetAnimdefsData();
 
 				// Parse the data
-				foreach(KeyValuePair<string, Stream> group in streams)
+				foreach(TextResourceData data in streams)
 				{
-					parser.Parse(group.Value, Path.Combine(currentreader.Location.GetShortName(), group.Key), true);
+					parser.Parse(data, true);
 
 					// Report errors?
 					if(parser.HasError) parser.LogError();
@@ -2212,27 +2270,27 @@ namespace CodeImp.DoomBuilder.Data
 					foreach(var g in parser.CameraTextures)
 					{
 						// Grab a local copy
-						CameraTextureData data = g.Value;
+						CameraTextureData camtexdata = g.Value;
 
 						// Apply texture size override?
-						if(!data.FitTexture)
+						if(!camtexdata.FitTexture)
 						{
-							long longname = Lump.MakeLongName(data.Name);
+							long longname = Lump.MakeLongName(camtexdata.Name);
 
 							if(textures.ContainsKey(longname))
 							{
-								data.ScaleX = (float)textures[longname].Width / data.Width;
-								data.ScaleY = (float)textures[longname].Height / data.Height;
+								camtexdata.ScaleX = (float)textures[longname].Width / camtexdata.Width;
+								camtexdata.ScaleY = (float)textures[longname].Height / camtexdata.Height;
 							}
 							else if(flats.ContainsKey(longname))
 							{
-								data.ScaleX = (float)flats[longname].Width / data.Width;
-								data.ScaleY = (float)flats[longname].Height / data.Height;
+								camtexdata.ScaleX = (float)flats[longname].Width / camtexdata.Width;
+								camtexdata.ScaleY = (float)flats[longname].Height / camtexdata.Height;
 							}
 						}
 
 						// Create texture
-						CameraTextureImage camteximage = new CameraTextureImage(data);
+						CameraTextureImage camteximage = new CameraTextureImage(camtexdata);
 
 						// Add to flats and textures
 						texturenames.Add(camteximage.Name);
@@ -2252,18 +2310,66 @@ namespace CodeImp.DoomBuilder.Data
 				}
 			}
 
+			//mxd. Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
 			currentreader = null;
 		}
 
+		//mxd. This loads TERRAIN
+		private void LoadTerrain()
+		{
+			// Bail out when not supported by current game configuration
+			if(string.IsNullOrEmpty(General.Map.Config.DecorateGames)) return;
+
+			TerrainParser parser = new TerrainParser();
+			foreach(DataReader dr in containers)
+			{
+				currentreader = dr;
+				IEnumerable<TextResourceData> streams = dr.GetTerrainData();
+
+				// Parse the data
+				foreach(TextResourceData data in streams)
+				{
+					parser.Parse(data, true);
+
+					// Report errors?
+					if(parser.HasError) parser.LogError();
+				}
+			}
+
+			// Add to text resources collection
+			textresources[parser.ScriptType] = new HashSet<TextResource>(parser.TextResources.Values);
+			currentreader = null;
+
+			// Sort
+			List<string> names = new List<string>(parser.TerrainNames);
+			names.Sort();
+
+			// Set as collection
+			terrainnames = names.ToArray();
+		}
+
 		//mxd
-		internal Stream LoadFile(string name) 
+		internal TextResourceData LoadFile(string name) 
 		{
 			// Filesystem path?
 			if(Path.IsPathRooted(name))
-				return (File.Exists(name) ? File.OpenRead(name) : null);
+			{
+				if(File.Exists(name))
+				{
+					DataLocation location = new DataLocation{ location = name, type = DataLocation.RESOURCE_DIRECTORY };
+					return new TextResourceData(File.OpenRead(name), location, name, false);
+				}
 
-			foreach(DataReader dr in containers)
-				if(dr.FileExists(name)) return dr.LoadFile(name);
+				return null;
+			}
+
+			// Search in resources
+			for(int i = containers.Count - 1; i >= 0; i--)
+			{
+				if(containers[i].FileExists(name))
+					return new TextResourceData(containers[i], containers[i].LoadFile(name), name, false);
+			}
 
 			return null;
 		}
@@ -2312,21 +2418,18 @@ namespace CodeImp.DoomBuilder.Data
 					foreach(Sidedef sd in General.Map.Map.Sidedefs)
 					{
 						// Add used textures to dictionary
-						if(sd.LongHighTexture != MapSet.EmptyLongName) usedtextures[sd.LongHighTexture] = 0;
-						if(sd.LongMiddleTexture != MapSet.EmptyLongName) usedtextures[sd.LongMiddleTexture] = 0;
-						if(sd.LongLowTexture != MapSet.EmptyLongName) usedtextures[sd.LongLowTexture] = 0;
+						if(sd.LongHighTexture != MapSet.EmptyLongName) usedtextures[sd.LongHighTexture] = true;
+						if(sd.LongMiddleTexture != MapSet.EmptyLongName) usedtextures[sd.LongMiddleTexture] = true;
+						if(sd.LongLowTexture != MapSet.EmptyLongName) usedtextures[sd.LongLowTexture] = true;
 					}
 
 					// Go through the map to find the used flats
 					foreach(Sector s in General.Map.Map.Sectors)
 					{
 						// Add used flats to dictionary
-						usedtextures[s.LongFloorTexture] = 0;
-						usedtextures[s.LongCeilTexture] = 0;
+						usedtextures[s.LongFloorTexture] = false;
+						usedtextures[s.LongCeilTexture] = false;
 					}
-
-					// Notify the background thread that it needs to update the images
-					updatedusedtextures = true;
 				}
 			}
 			//mxd. Use separate collections
@@ -2340,9 +2443,9 @@ namespace CodeImp.DoomBuilder.Data
 					foreach(Sidedef sd in General.Map.Map.Sidedefs)
 					{
 						// Add used textures to dictionary
-						if(sd.LongHighTexture != MapSet.EmptyLongName) usedtextures[sd.LongHighTexture] = 0;
-						if(sd.LongMiddleTexture != MapSet.EmptyLongName) usedtextures[sd.LongMiddleTexture] = 0;
-						if(sd.LongLowTexture != MapSet.EmptyLongName) usedtextures[sd.LongLowTexture] = 0;
+						if(sd.LongHighTexture != MapSet.EmptyLongName) usedtextures[sd.LongHighTexture] = true;
+						if(sd.LongMiddleTexture != MapSet.EmptyLongName) usedtextures[sd.LongMiddleTexture] = true;
+						if(sd.LongLowTexture != MapSet.EmptyLongName) usedtextures[sd.LongLowTexture] = true;
 					}
 				}
 
@@ -2354,14 +2457,14 @@ namespace CodeImp.DoomBuilder.Data
 					foreach(Sector s in General.Map.Map.Sectors)
 					{
 						// Add used flats to dictionary
-						usedflats[s.LongFloorTexture] = 0;
-						usedflats[s.LongCeilTexture] = 0;
+						usedflats[s.LongFloorTexture] = false;
+						usedflats[s.LongCeilTexture] = false;
 					}
 				}
-				
-				// Notify the background thread that it needs to update the images
-				updatedusedtextures = true;
 			}
+
+			// Notify the background thread that it needs to update the images
+			updatedusedtextures = true;
 		}
 
 		#endregion
diff --git a/Source/Core/Data/DataReader.cs b/Source/Core/Data/DataReader.cs
index d8364e38b..13091f339 100644
--- a/Source/Core/Data/DataReader.cs
+++ b/Source/Core/Data/DataReader.cs
@@ -20,11 +20,66 @@ using System.Collections.Generic;
 using System.IO;
 using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.GZBuilder.Data;
+using CodeImp.DoomBuilder.ZDoom;
 
 #endregion
 
 namespace CodeImp.DoomBuilder.Data
 {
+	//mxd
+	public class TextResourceData
+	{
+		private Stream stream;
+		private DataReader source;
+		private DataLocation sourcelocation;
+		private string filename;
+		private int lumpindex;
+		private bool trackable;
+
+		internal Stream Stream { get { return stream; } }
+		internal DataReader Source { get { return source; } }
+		internal DataLocation SourceLocation { get { return sourcelocation; } }
+		internal string Filename { get { return filename; } } // Lump name/Filename
+		internal int LumpIndex { get { return lumpindex; } } // Lump index in a WAD
+		internal bool Trackable { get { return trackable; } set { trackable = value; } } // When false, wont be added to DataManager.TextResources
+
+
+		internal TextResourceData(DataReader Source, Stream Stream, string Filename, bool Trackable)
+		{
+			source = Source;
+			sourcelocation = Source.Location;
+			stream = Stream;
+			filename = Filename;
+			trackable = Trackable;
+
+			WADReader reader = source as WADReader;
+			if(reader != null)
+				lumpindex = reader.WadFile.FindLumpIndex(filename);
+			else
+				lumpindex = -1;
+		}
+
+		internal TextResourceData(DataReader Source, Stream Stream, string Filename, int LumpIndex, bool Trackable)
+		{
+			source = Source;
+			sourcelocation = Source.Location;
+			stream = Stream;
+			filename = Filename;
+			lumpindex = LumpIndex;
+			trackable = Trackable;
+		}
+
+		internal TextResourceData(Stream Stream, DataLocation Location, string Filename, bool Trackable)
+		{
+			source = null;
+			sourcelocation = Location;
+			stream = Stream;
+			filename = Filename;
+			lumpindex = -1;
+			trackable = Trackable;
+		}
+	}
+	
 	internal abstract class DataReader
 	{
 		#region ================== Constants
@@ -114,77 +169,80 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Textures
 
 		// When implemented, this should read the patch names
-		public virtual PatchNames LoadPatchNames() { return null; }
+		public abstract PatchNames LoadPatchNames();
 
 		// When implemented, this returns the patch lump
-		public virtual Stream GetPatchData(string pname, bool longname) { return null; }
+		public abstract Stream GetPatchData(string pname, bool longname);
 
 		// When implemented, this returns the texture lump
-		public virtual Stream GetTextureData(string pname, bool longname) { return null; }
+		public abstract Stream GetTextureData(string pname, bool longname);
 
 		// When implemented, this loads the textures
-		public virtual ICollection<ImageData> LoadTextures(PatchNames pnames) { return null; }
+		public abstract IEnumerable<ImageData> LoadTextures(PatchNames pnames, Dictionary<string, TexturesParser> cachedparsers);
 		
 		#endregion
 
 		#region ================== Flats
 		
 		// When implemented, this loads the flats
-		public virtual ICollection<ImageData> LoadFlats() { return null; }
+		public abstract IEnumerable<ImageData> LoadFlats(Dictionary<string, TexturesParser> cachedparsers);
 
 		// When implemented, this returns the flat lump
-		public virtual Stream GetFlatData(string pname, bool longname) { return null; }
+		public abstract Stream GetFlatData(string pname, bool longname);
 		
 		#endregion
 		
 		#region ================== Sprites
 
 		// When implemented, this loads the sprites
-		public virtual ICollection<ImageData> LoadSprites() { return null; }
+		public abstract IEnumerable<ImageData> LoadSprites(Dictionary<string, TexturesParser> cachedparsers);
 		
 		// When implemented, this returns the sprite lump
-		public virtual Stream GetSpriteData(string pname) { return null; }
+		public abstract Stream GetSpriteData(string pname);
 
 		// When implemented, this checks if the given sprite lump exists
-		public virtual bool GetSpriteExists(string pname) { return false; }
+		public abstract bool GetSpriteExists(string pname);
 		
 		#endregion
 
 		#region ================== Decorate, Modeldef, Mapinfo, Gldefs, etc...
 
 		// When implemented, this returns the DECORATE lump
-		public abstract Dictionary<string, Stream> GetDecorateData(string pname); // { return new Dictionary<string, Stream>(); }
+		public abstract IEnumerable<TextResourceData> GetDecorateData(string pname);
 
 		//mxd. When implemented, this returns the MODELDEF lump
-		public abstract Dictionary<string, Stream> GetModeldefData(); // { return new Dictionary<string, Stream>(); }
+		public abstract IEnumerable<TextResourceData> GetModeldefData();
 
 		//mxd. When implemented, this returns the MAPINFO lump
-		public abstract Dictionary<string, Stream> GetMapinfoData(); // { return new Dictionary<string, Stream>(); }
+		public abstract IEnumerable<TextResourceData> GetMapinfoData();
 
 		//mxd. When implemented, this returns the GLDEFS lump
-		public abstract Dictionary<string, Stream> GetGldefsData(GameType gametype); // { return new Dictionary<string, Stream>(); }
+		public abstract IEnumerable<TextResourceData> GetGldefsData(GameType gametype);
 
 		//mxd. When implemented, this returns the REVERBS lump
-		public abstract Dictionary<string, Stream> GetReverbsData(); // { return new Dictionary<string, Stream>(); }
+		public abstract IEnumerable<TextResourceData> GetReverbsData();
 
 		//mxd. When implemented, this returns the VOXELDEF lump
-		public abstract KeyValuePair<string, Stream> GetVoxeldefData(); // { return new KeyValuePair<string, Stream>(); }
+		public abstract IEnumerable<TextResourceData> GetVoxeldefData();
 
 		//mxd. When implemented, this returns the SNDSEQ lump
-		public abstract Dictionary<string, Stream> GetSndSeqData(); // { return new Dictionary<string, Stream>(); }
+		public abstract IEnumerable<TextResourceData> GetSndSeqData();
 
 		//mxd. When implemented, this returns the ANIMDEFS lump
-		public abstract Dictionary<string, Stream> GetAnimdefsData();
+		public abstract IEnumerable<TextResourceData> GetAnimdefsData();
+
+		//mxd. When implemented, this returns the TERRAIN lump
+		public abstract IEnumerable<TextResourceData> GetTerrainData();
 
 		//mxd. When implemented, this returns the list of voxel model names
-		public abstract IEnumerable<string> GetVoxelNames(); // { return null; }
+		public abstract IEnumerable<string> GetVoxelNames();
 
 		//mxd. When implemented, this returns the voxel lump
-		public abstract Stream GetVoxelData(string name); // { return null; }
+		public abstract Stream GetVoxelData(string name);
 
 		//mxd
-		internal abstract MemoryStream LoadFile(string name);// { return null; }
-		internal abstract bool FileExists(string filename);// { return false; }
+		internal abstract MemoryStream LoadFile(string name);
+		internal abstract bool FileExists(string filename);
 
 		#endregion
 	}
diff --git a/Source/Core/Data/DirectoryReader.cs b/Source/Core/Data/DirectoryReader.cs
index 11baeffd6..d33109d3f 100644
--- a/Source/Core/Data/DirectoryReader.cs
+++ b/Source/Core/Data/DirectoryReader.cs
@@ -17,6 +17,7 @@
 #region ================== Namespaces
 
 using System;
+using System.Collections.Generic;
 using System.IO;
 using CodeImp.DoomBuilder.IO;
 
@@ -37,7 +38,7 @@ namespace CodeImp.DoomBuilder.Data
 		// Constructor
 		public DirectoryReader(DataLocation dl) : base(dl)
 		{
-			General.WriteLogLine("Opening directory resource '" + location.location + "'");
+			General.WriteLogLine("Opening directory resource \"" + location.location + "\"");
 			
 			// Initialize
 			files = new DirectoryFilesList(dl.location, true);
@@ -46,14 +47,27 @@ namespace CodeImp.DoomBuilder.Data
 			// We have no destructor
 			GC.SuppressFinalize(this);
 		}
-		
+
+		//mxd. Don't move directory wads anywhere
+		protected override void Initialize()
+		{
+			// Load all WAD files in the root as WAD resources
+			string[] wadfiles = GetFilesWithExt("", "wad", false);
+			wads = new List<WADReader>(wadfiles.Length);
+			foreach(string wadfile in wadfiles)
+			{
+				DataLocation wdl = new DataLocation(DataLocation.RESOURCE_WAD, Path.Combine(location.location, wadfile), false, false, true);
+				wads.Add(new WADReader(wdl));
+			}
+		}
+
 		// Disposer
 		public override void Dispose()
 		{
 			// Not already disposed?
 			if(!isdisposed)
 			{
-				General.WriteLogLine("Closing directory resource '" + location.location + "'");
+				General.WriteLogLine("Closing directory resource \"" + location.location + "\"");
 				
 				// Done
 				base.Dispose();
@@ -113,7 +127,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading patch '" + pname + "' from directory: " + e.Message);
+				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading patch \"" + pname + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -156,7 +170,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading texture '" + pname + "' from directory: " + e.Message);
+				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading texture \"" + pname + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -189,7 +203,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading colormap '" + pname + "' from directory: " + e.Message);
+				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading colormap \"" + pname + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -225,7 +239,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading sprite '" + pname + "' from directory: " + e.Message);
+				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading sprite \"" + pname + "\" from directory: " + e.Message);
 			}
 			
 			// Nothing found
@@ -256,7 +270,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);
+				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while checking sprite \"" + pname + "\" existance in directory: " + e.Message);
 			}
 			
 			// Nothing found
@@ -292,7 +306,7 @@ namespace CodeImp.DoomBuilder.Data
 			}
 			catch(Exception e)
 			{
-				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading voxel '" + name + "' from directory: " + e.Message);
+				General.ErrorLogger.Add(ErrorType.Error, e.GetType().Name + " while loading voxel \"" + name + "\" from directory: " + e.Message);
 			}
 
 			// Nothing found
@@ -347,9 +361,9 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		//mxd. This returns all files in a given directory which title starts with given title
-		protected override string[] GetAllFilesWhichTitleStartsWith(string path, string title) 
+		protected override string[] GetAllFilesWhichTitleStartsWith(string path, string title, bool subfolders) 
 		{
-			return files.GetAllFilesWhichTitleStartsWith(path, title).ToArray();
+			return files.GetAllFilesWhichTitleStartsWith(path, title, subfolders).ToArray();
 		}
 		
 		// This returns all files in a given directory that match the given extension
@@ -394,7 +408,7 @@ namespace CodeImp.DoomBuilder.Data
 			} 
 			catch(Exception e) 
 			{
-				General.ErrorLogger.Add(ErrorType.Error, "Unable to load file: "+e.Message);
+				General.ErrorLogger.Add(ErrorType.Error, "Unable to load file: " + e.Message);
 			}
 			return s;
 		}
diff --git a/Source/Core/Data/PK3Reader.cs b/Source/Core/Data/PK3Reader.cs
index 9f8d81fdd..e063795f7 100644
--- a/Source/Core/Data/PK3Reader.cs
+++ b/Source/Core/Data/PK3Reader.cs
@@ -51,7 +51,7 @@ namespace CodeImp.DoomBuilder.Data
 		// Constructor
 		public PK3Reader(DataLocation dl) : base(dl)
 		{
-			General.WriteLogLine("Opening " + Path.GetExtension(location.location).ToUpper().Replace(".", "") + " resource '" + location.location + "'");
+			General.WriteLogLine("Opening " + Path.GetExtension(location.location).ToUpper().Replace(".", "") + " resource \"" + location.location + "\"");
 
 			if(!File.Exists(location.location))
 				throw new FileNotFoundException("Could not find the file \"" + location.location + "\"", location.location);
@@ -108,8 +108,15 @@ namespace CodeImp.DoomBuilder.Data
 			// Not already disposed?
 			if(!isdisposed)
 			{
-				General.WriteLogLine("Closing " + Path.GetExtension(location.location).ToUpper().Replace(".", "") + " resource '" + location.location + "'");
-				
+				General.WriteLogLine("Closing " + Path.GetExtension(location.location).ToUpper().Replace(".", "") + " resource \"" + location.location + "\"");
+
+				//mxd. Remove temp files
+				foreach(WADReader wr in wads)
+				{
+					try { File.Delete(wr.Location.location); }
+					catch(Exception) { }
+				}
+
 				//mxd
 				if(archive != null)
 				{
@@ -375,9 +382,9 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		//mxd. This returns all files in a given directory which title starts with given title
-		protected override string[] GetAllFilesWhichTitleStartsWith(string path, string title) 
+		protected override string[] GetAllFilesWhichTitleStartsWith(string path, string title, bool subfolders) 
 		{
-			return files.GetAllFilesWhichTitleStartsWith(path, title).ToArray();
+			return files.GetAllFilesWhichTitleStartsWith(path, title, subfolders).ToArray();
 		}
 		
 		// This returns all files in a given directory that match the given extension
@@ -447,7 +454,7 @@ namespace CodeImp.DoomBuilder.Data
 			if(filedata == null)
 			{
 				//mxd
-				General.ErrorLogger.Add(ErrorType.Error, "Cannot find the file '" + filename + "' in archive '" + location.location + "'.");
+				General.ErrorLogger.Add(ErrorType.Error, "Cannot find the file \"" + filename + "\" in archive \"" + location.location + "\".");
 				return null;
 			}
 
diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs
index 0d10effd0..195bf01c2 100644
--- a/Source/Core/Data/PK3StructuredReader.cs
+++ b/Source/Core/Data/PK3StructuredReader.cs
@@ -21,6 +21,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Text.RegularExpressions;
 using CodeImp.DoomBuilder.GZBuilder.Data;
+using CodeImp.DoomBuilder.ZDoom;
 
 #endregion
 
@@ -44,8 +45,8 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Variables
 		
 		// Source
-		protected bool roottextures;
-		protected bool rootflats;
+		protected readonly bool roottextures;
+		protected readonly bool rootflats;
 		
 		// WAD files that must be loaded as well
 		protected List<WADReader> wads;
@@ -91,13 +92,6 @@ namespace CodeImp.DoomBuilder.Data
 				// Clean up
 				foreach(WADReader wr in wads) wr.Dispose();
 				
-				// Remove temp files
-				foreach(WADReader wr in wads)
-				{
-					try { File.Delete(wr.Location.location); }
-					catch(Exception) { }
-				}
-				
 				// Done
 				base.Dispose();
 			}
@@ -148,7 +142,7 @@ namespace CodeImp.DoomBuilder.Data
 				if(stream.Length > 767) //mxd
 					palette = new Playpal(stream);
 				else
-					General.ErrorLogger.Add(ErrorType.Warning, "Warning: invalid palette '"+foundfile+"'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Warning: invalid palette \"" + foundfile + "\"");
 				stream.Dispose();
 			}
 			
@@ -161,19 +155,19 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Textures
 
 		// This loads the textures
-		public override ICollection<ImageData> LoadTextures(PatchNames pnames)
+		public override IEnumerable<ImageData> LoadTextures(PatchNames pnames, Dictionary<string, TexturesParser> cachedparsers)
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
 			Dictionary<long, ImageData> images = new Dictionary<long, ImageData>();
-			ICollection<ImageData> collection;
+			IEnumerable<ImageData> collection;
 			
 			// Load from wad files (NOTE: backward order, because the last wad's images have priority)
 			for(int i = wads.Count - 1; i >= 0; i--)
 			{
 				PatchNames wadpnames = wads[i].LoadPatchNames(); //mxd
-				collection = wads[i].LoadTextures((wadpnames != null && wadpnames.Length > 0) ? wadpnames : pnames); //mxd
+				collection = wads[i].LoadTextures((wadpnames != null && wadpnames.Length > 0) ? wadpnames : pnames, cachedparsers); //mxd
 				AddImagesToList(images, collection);
 			}
 			
@@ -208,12 +202,24 @@ namespace CodeImp.DoomBuilder.Data
 			
 			// Load TEXTURES lump files
 			imgset.Clear();
-			string[] alltexturefiles = GetAllFilesWhichTitleStartsWith("", "TEXTURES"); //mxd
+			string[] alltexturefiles = GetAllFilesWhichTitleStartsWith("", "TEXTURES", false); //mxd
 			foreach(string texturesfile in alltexturefiles)
 			{
-				MemoryStream filedata = LoadFile(texturesfile);
-				WADReader.LoadHighresTextures(filedata, texturesfile, ref imgset, images, null);
-				filedata.Dispose();
+				//mxd. Added TexturesParser caching
+				string fullpath = Path.Combine(this.location.location, texturesfile);
+				if(cachedparsers.ContainsKey(fullpath))
+				{
+					// Make the textures
+					foreach(TextureStructure t in cachedparsers[fullpath].Textures)
+						imgset.Add(t.MakeImage());
+				}
+				else
+				{
+					MemoryStream filedata = LoadFile(texturesfile);
+					TextResourceData data = new TextResourceData(this, filedata, texturesfile, true); //mxd
+					cachedparsers.Add(fullpath, WADReader.LoadHighresTextures(data, ref imgset)); //mxd
+					filedata.Dispose();
+				}
 			}
 			
 			// Add images from TEXTURES lump file
@@ -224,8 +230,7 @@ namespace CodeImp.DoomBuilder.Data
 			AddImagesToList(images, collection);
 			
 			// Add images to the container-specific texture set
-			foreach(ImageData img in images.Values)
-				textureset.AddTexture(img);
+			foreach(ImageData img in images.Values) textureset.AddTexture(img);
 			
 			return new List<ImageData>(images.Values);
 		}
@@ -265,10 +270,10 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Flats
 		
 		// This loads the textures
-		public override ICollection<ImageData> LoadFlats()
+		public override IEnumerable<ImageData> LoadFlats(Dictionary<string, TexturesParser> cachedparsers)
 		{
 			Dictionary<long, ImageData> images = new Dictionary<long, ImageData>();
-			ICollection<ImageData> collection;
+			IEnumerable<ImageData> collection;
 			List<ImageData> imgset = new List<ImageData>();
 			
 			// Error when suspended
@@ -278,7 +283,7 @@ namespace CodeImp.DoomBuilder.Data
 			// Note the backward order, because the last wad's images have priority
 			for(int i = wads.Count - 1; i >= 0; i--)
 			{
-				collection = wads[i].LoadFlats();
+				collection = wads[i].LoadFlats(cachedparsers);
 				AddImagesToList(images, collection);
 			}
 			
@@ -294,12 +299,24 @@ namespace CodeImp.DoomBuilder.Data
 			AddImagesToList(images, collection);
 
 			// Load TEXTURES lump file
-			string[] alltexturefiles = GetAllFilesWhichTitleStartsWith("", "TEXTURES"); //mxd
+			string[] alltexturefiles = GetAllFilesWhichTitleStartsWith("", "TEXTURES", false); //mxd
 			foreach(string texturesfile in alltexturefiles)
 			{
-				MemoryStream filedata = LoadFile(texturesfile);
-				WADReader.LoadHighresFlats(filedata, texturesfile, ref imgset, null, images);
-				filedata.Dispose();
+				//mxd. Added TexturesParser caching
+				string fullpath = Path.Combine(this.location.location, texturesfile);
+				if(cachedparsers.ContainsKey(fullpath))
+				{
+					// Make the textures
+					foreach(TextureStructure t in cachedparsers[fullpath].Flats)
+						imgset.Add(t.MakeImage());
+				}
+				else
+				{
+					MemoryStream filedata = LoadFile(texturesfile);
+					TextResourceData data = new TextResourceData(this, filedata, texturesfile, true); //mxd
+					cachedparsers.Add(fullpath, WADReader.LoadHighresFlats(data, ref imgset)); //mxd
+					filedata.Dispose();
+				}
 			}
 
 			// Add images from TEXTURES lump file
@@ -308,9 +325,6 @@ namespace CodeImp.DoomBuilder.Data
 			// Add images to the container-specific texture set
 			foreach(ImageData img in images.Values) 
 				textureset.AddFlat(img);
-
-			// Add images from TEXTURES lump file
-			AddImagesToList(images, imgset);
 			
 			return new List<ImageData>(images.Values);
 		}
@@ -341,7 +355,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Sprites
 
 		// This loads the textures
-		public override ICollection<ImageData> LoadSprites()
+		public override IEnumerable<ImageData> LoadSprites(Dictionary<string, TexturesParser> cachedparsers)
 		{
 			Dictionary<long, ImageData> images = new Dictionary<long, ImageData>();
 			List<ImageData> imgset = new List<ImageData>();
@@ -353,18 +367,30 @@ namespace CodeImp.DoomBuilder.Data
 			// Note the backward order, because the last wad's images have priority
 			for(int i = wads.Count - 1; i >= 0; i--)
 			{
-				ICollection<ImageData> collection = wads[i].LoadSprites();
+				IEnumerable<ImageData> collection = wads[i].LoadSprites(cachedparsers);
 				AddImagesToList(images, collection);
 			}
 			
 			// Load TEXTURES lump file
 			imgset.Clear();
-			string[] alltexturefiles = GetAllFilesWhichTitleStartsWith("", "TEXTURES"); //mxd
+			string[] alltexturefiles = GetAllFilesWhichTitleStartsWith("", "TEXTURES", false); //mxd
 			foreach(string texturesfile in alltexturefiles)
 			{
-				MemoryStream filedata = LoadFile(texturesfile);
-				WADReader.LoadHighresSprites(filedata, texturesfile, ref imgset, null, null);
-				filedata.Dispose();
+				//mxd. Added TexturesParser caching
+				string fullpath = Path.Combine(this.location.location, texturesfile);
+				if(cachedparsers.ContainsKey(fullpath))
+				{
+					// Make the textures
+					foreach(TextureStructure t in cachedparsers[fullpath].Sprites)
+						imgset.Add(t.MakeImage());
+				}
+				else
+				{
+					MemoryStream filedata = LoadFile(texturesfile);
+					TextResourceData data = new TextResourceData(this, filedata, texturesfile, true); //mxd
+					cachedparsers.Add(fullpath, WADReader.LoadHighresSprites(data, ref imgset)); //mxd
+					filedata.Dispose();
+				}
 			}
 			
 			// Add images from TEXTURES lump file
@@ -409,14 +435,14 @@ namespace CodeImp.DoomBuilder.Data
 
 		#region ================== DECORATE
 
-		// This finds and returns a sprite stream
-		public override Dictionary<string, Stream> GetDecorateData(string pname)
+		// This finds and returns DECORATE streams
+		public override IEnumerable<TextResourceData> GetDecorateData(string pname)
 		{
-			Dictionary<string, Stream> result = new Dictionary<string, Stream>();
-			string[] allfilenames;
-			
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
+
+			List<TextResourceData> result = new List<TextResourceData>();
+			string[] allfilenames;
 			
 			// Find in root directory
 			string filename = Path.GetFileName(pname);
@@ -424,8 +450,8 @@ namespace CodeImp.DoomBuilder.Data
 			
 			if(filename.IndexOf('.') > -1)
 			{
-				string fullName = Path.Combine(pathname, filename);
-				if(FileExists(fullName)) 
+				string fullname = Path.Combine(pathname, filename);
+				if(FileExists(fullname)) 
 				{
 					allfilenames = new string[1];
 					allfilenames[0] = Path.Combine(pathname, filename);
@@ -433,26 +459,18 @@ namespace CodeImp.DoomBuilder.Data
 				else 
 				{
 					allfilenames = new string[0];
-					General.ErrorLogger.Add(ErrorType.Warning, "Unable to load DECORATE file '" + fullName + "'");
+					General.ErrorLogger.Add(ErrorType.Warning, "Unable to load DECORATE file \"" + fullname + "\"");
 				}
 			}
 			else
 				allfilenames = GetAllFilesWithTitle(pathname, filename, false);
 
 			foreach(string foundfile in allfilenames)
-			{
-				result.Add(foundfile, LoadFile(foundfile));
-			}
+				result.Add(new TextResourceData(this, LoadFile(foundfile), foundfile, true));
 			
 			// Find in any of the wad files
 			for(int i = wads.Count - 1; i >= 0; i--)
-			{
-				Dictionary<string, Stream> wadresult = wads[i].GetDecorateData(pname);
-				foreach(KeyValuePair<string, Stream> group in wadresult)
-				{
-					result.Add(group.Key, group.Value);
-				}
-			}
+				result.AddRange(wads[i].GetDecorateData(pname));
 
 			return result;
 		}
@@ -462,22 +480,25 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== MODELDEF (mxd)
 
 		//mxd
-		public override Dictionary<string, Stream> GetModeldefData() 
+		public override IEnumerable<TextResourceData> GetModeldefData() 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
 			// Modedef should be in root folder
 			string[] files = GetAllFiles("", false);
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>(StringComparer.Ordinal);
+			List<TextResourceData> result = new List<TextResourceData>();
 
 			foreach(string s in files) 
 			{
-				if(Path.GetFileNameWithoutExtension(s).ToUpperInvariant().StartsWith("MODELDEF")) 
-					streams.Add(s, LoadFile(s));
+				if(Path.GetFileNameWithoutExtension(s).ToUpperInvariant().StartsWith("MODELDEF"))
+					result.Add(new TextResourceData(this, LoadFile(s), s, true));
 			}
 
-			return streams;
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetModeldefData());
+
+			return result;
 		}
 
 		#endregion 
@@ -504,21 +525,22 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		//mxd
-		public override KeyValuePair<string, Stream> GetVoxeldefData() 
+		public override IEnumerable<TextResourceData> GetVoxeldefData() 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
-			//voxeldef should be in root folder
-			string[] files = GetAllFiles("", false);
+			// VOXELDEF should be in root folder
+			string[] files = GetAllFilesWithTitle("", "VOXELDEF", false);
+			List<TextResourceData> result = new List<TextResourceData>();
 
 			foreach(string s in files) 
-			{
-				if(Path.GetFileNameWithoutExtension(s).ToUpperInvariant() == "VOXELDEF") 
-					return new KeyValuePair<string,Stream>(s, LoadFile(s));
-			}
+				result.Add(new TextResourceData(this, LoadFile(s), s, true));
+
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetVoxeldefData());
 
-			return new KeyValuePair<string,Stream>();
+			return result;
 		}
 
 		#endregion
@@ -526,24 +548,28 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== (Z)MAPINFO (mxd)
 
 		//mxd
-		public override Dictionary<string, Stream> GetMapinfoData() 
+		public override IEnumerable<TextResourceData> GetMapinfoData() 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
 			// Mapinfo should be in root folder
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>(StringComparer.Ordinal);
+			List<TextResourceData> result = new List<TextResourceData>();
 
 			// Try to find (z)mapinfo
-			string[] files = GetAllFiles("", false);
-			foreach(string s in files)
+			string[] files = GetAllFilesWithTitle("", "ZMAPINFO", false);
+			if(files.Length == 0) files = GetAllFilesWithTitle("", "MAPINFO", false);
+
+			if(files.Length > 0)
 			{
-				string filename = Path.GetFileNameWithoutExtension(s.ToLowerInvariant());
-				if(filename == "zmapinfo" || filename == "mapinfo")
-					streams[s] = LoadFile(s);
+				foreach(string s in files)
+					result.Add(new TextResourceData(this, LoadFile(s), s, true));
 			}
 
-			return streams;
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetMapinfoData());
+
+			return result;
 		}
 
 		#endregion
@@ -551,123 +577,123 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== GLDEFS (mxd)
 
 		//mxd
-		public override Dictionary<string, Stream> GetGldefsData(GameType gametype) 
+		public override IEnumerable<TextResourceData> GetGldefsData(GameType gametype) 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>(StringComparer.Ordinal);
+			List<TextResourceData> result = new List<TextResourceData>();
 
-			//at least one of gldefs should be in root folder
-			string[] files = GetAllFiles("", false);
+			// At least one of gldefs should be in the root folder
+			List<string> files = new List<string>();
 
-			//try to load game specific GLDEFS first
+			// Try to load game specific GLDEFS first
 			if(gametype != GameType.UNKNOWN) 
 			{
 				string lumpname = Gldefs.GLDEFS_LUMPS_PER_GAME[(int)gametype];
-				foreach(string s in files) 
-				{
-					if(Path.GetFileNameWithoutExtension(s).ToUpperInvariant() == lumpname)
-						streams.Add(s, LoadFile(s));
-				}
+				files.AddRange(GetAllFilesWhichTitleStartsWith("", lumpname, false));
 			}
 
 			// Can be several entries
+			files.AddRange(GetAllFilesWhichTitleStartsWith("", "GLDEFS", false));
+
+			// Add to collection
 			foreach(string s in files)
-			{
-				if(Path.GetFileNameWithoutExtension(s).ToUpperInvariant().StartsWith("GLDEFS"))
-					streams.Add(s, LoadFile(s));
-			}
+				result.Add(new TextResourceData(this, LoadFile(s), s, true));
+
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetGldefsData(gametype));
 
-			return streams;
+			return result;
 		}
 
 		#endregion
 
 		#region ================== REVERBS (mxd)
 
-		public override Dictionary<string, Stream> GetReverbsData() 
+		public override IEnumerable<TextResourceData> GetReverbsData() 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>();
+			List<TextResourceData> result = new List<TextResourceData>();
+			string[] files = GetAllFilesWithTitle("", "REVERBS", false);
 
-			// Get from wads first
-			//TODO: is this the correct order?..
-			foreach(WADReader wr in wads) 
-			{
-				Dictionary<string, Stream> wadstreams = wr.GetReverbsData();
-				foreach(KeyValuePair<string, Stream> pair in wadstreams) streams.Add(pair.Key, pair.Value);
-			}
+			// Add to collection
+			foreach(string s in files)
+				result.Add(new TextResourceData(this, LoadFile(s), s, true));
 
-			// Then from our own files
-			string foundfile = FindFirstFile("reverbs", false);
-			if((foundfile != null) && FileExists(foundfile)) 
-			{
-				streams.Add(foundfile, LoadFile(foundfile));
-			}
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetReverbsData());
 
-			return streams;
+			return result;
 		}
 
 		#endregion
 
 		#region ================== SNDSEQ (mxd)
 
-		public override Dictionary<string, Stream> GetSndSeqData() 
+		public override IEnumerable<TextResourceData> GetSndSeqData() 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>();
+			List<TextResourceData> result = new List<TextResourceData>();
+			string[] files = GetAllFilesWithTitle("", "SNDSEQ", false);
 
-			// Get from wads first
-			//TODO: is this the correct order?..
-			foreach(WADReader wr in wads) 
-			{
-				Dictionary<string, Stream> wadstreams = wr.GetSndSeqData();
-				foreach(KeyValuePair<string, Stream> pair in wadstreams) streams.Add(pair.Key, pair.Value);
-			}
+			// Add to collection
+			foreach(string s in files)
+				result.Add(new TextResourceData(this, LoadFile(s), s, true));
 
-			// Then from our own files
-			string foundfile = FindFirstFile("sndseq", false);
-			if(!string.IsNullOrEmpty(foundfile) && FileExists(foundfile))
-			{
-				streams.Add(foundfile, LoadFile(foundfile));
-			}
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetSndSeqData());
 
-			return streams;
+			return result;
 		}
 
 		#endregion
 
 		#region ================== ANIMDEFS (mxd)
 
-		public override Dictionary<string, Stream> GetAnimdefsData()
+		public override IEnumerable<TextResourceData> GetAnimdefsData()
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
 
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>();
+			List<TextResourceData> result = new List<TextResourceData>();
+			string[] files = GetAllFilesWithTitle("", "ANIMDEFS", false);
 
-			// Get from wads first
-			//TODO: is this the correct order?..
-			foreach(WADReader wr in wads)
-			{
-				Dictionary<string, Stream> wadstreams = wr.GetAnimdefsData();
-				foreach(KeyValuePair<string, Stream> pair in wadstreams)
-					streams.Add(pair.Key, pair.Value);
-			}
+			// Add to collection
+			foreach(string s in files)
+				result.Add(new TextResourceData(this, LoadFile(s), s, true));
 
-			// Then from our own files
-			string foundfile = FindFirstFile("animdefs", false);
-			if(!string.IsNullOrEmpty(foundfile) && FileExists(foundfile))
-			{
-				streams.Add(foundfile, LoadFile(foundfile));
-			}
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetAnimdefsData());
+
+			return result;
+		}
+
+		#endregion
 
-			return streams;
+		#region ================== TERRAIN (mxd)
+
+		//mxd
+		public override IEnumerable<TextResourceData> GetTerrainData()
+		{
+			// Error when suspended
+			if(issuspended) throw new Exception("Data reader is suspended");
+
+			List<TextResourceData> result = new List<TextResourceData>();
+			string[] files = GetAllFilesWithTitle("", "TERRAIN", false);
+
+			// Add to collection
+			foreach(string s in files)
+				result.Add(new TextResourceData(this, LoadFile(s), s, true));
+
+			// Find in any of the wad files
+			foreach(WADReader wr in wads) result.AddRange(wr.GetTerrainData());
+
+			return result;
 		}
 
 		#endregion
@@ -700,7 +726,7 @@ namespace CodeImp.DoomBuilder.Data
 		}
 		
 		// This copies images from a collection unless they already exist in the list
-		private static void AddImagesToList(Dictionary<long, ImageData> targetlist, ICollection<ImageData> sourcelist)
+		private static void AddImagesToList(Dictionary<long, ImageData> targetlist, IEnumerable<ImageData> sourcelist)
 		{
 			// Go for all source images
 			foreach(ImageData src in sourcelist)
@@ -721,7 +747,7 @@ namespace CodeImp.DoomBuilder.Data
 		protected abstract string[] GetAllFilesWithTitle(string path, string title, bool subfolders);
 
 		//mxd. This must return all files in a given directory which title starts with given title
-		protected abstract string[] GetAllFilesWhichTitleStartsWith(string path, string title);
+		protected abstract string[] GetAllFilesWhichTitleStartsWith(string path, string title, bool subfolders);
 
 		// This must return all files in a given directory that match the given extension
 		protected abstract string[] GetFilesWithExt(string path, string extension, bool subfolders);
diff --git a/Source/Core/Data/WADReader.cs b/Source/Core/Data/WADReader.cs
index da9cdc125..08c8caa84 100644
--- a/Source/Core/Data/WADReader.cs
+++ b/Source/Core/Data/WADReader.cs
@@ -50,22 +50,23 @@ namespace CodeImp.DoomBuilder.Data
 		// Source
 		private WAD file;
 		private bool is_iwad;
-		private bool strictpatches;
+		private readonly bool strictpatches;
 		
 		// Lump ranges
-		private List<LumpRange> flatranges;
-		private List<LumpRange> invertedflatranges; //mxd
-		private List<LumpRange> patchranges;
-		private List<LumpRange> spriteranges;
-		private List<LumpRange> textureranges;
-		private List<LumpRange> colormapranges;
-		private List<LumpRange> voxelranges; //mxd
+		private readonly List<LumpRange> flatranges;
+		private readonly List<LumpRange> invertedflatranges; //mxd
+		private readonly List<LumpRange> patchranges;
+		private readonly List<LumpRange> spriteranges;
+		private readonly List<LumpRange> textureranges;
+		private readonly List<LumpRange> colormapranges;
+		private readonly List<LumpRange> voxelranges; //mxd
 		
 		#endregion
 
 		#region ================== Properties
 
 		public bool IsIWAD { get { return is_iwad; } }
+		internal WAD WadFile { get { return file; } } //mxd
 
 		#endregion
 
@@ -74,7 +75,7 @@ namespace CodeImp.DoomBuilder.Data
 		// Constructor
 		public WADReader(DataLocation dl) : base(dl)
 		{
-			General.WriteLogLine("Opening WAD resource '" + location.location + "'");
+			General.WriteLogLine("Opening WAD resource \"" + location.location + "\"");
 
 			if(!File.Exists(location.location))
 				throw new FileNotFoundException("Could not find the file \"" + location.location + "\"", location.location);
@@ -91,12 +92,12 @@ namespace CodeImp.DoomBuilder.Data
 			voxelranges = new List<LumpRange>(); //mxd
 			
 			// Find ranges
-			FindRanges(patchranges, General.Map.Config.PatchRanges, "patches");
-			FindRanges(spriteranges, General.Map.Config.SpriteRanges, "sprites");
-			FindRanges(flatranges, General.Map.Config.FlatRanges, "flats");
-			FindRanges(textureranges, General.Map.Config.TextureRanges, "textures");
-			FindRanges(colormapranges, General.Map.Config.ColormapRanges, "colormaps");
-			FindRanges(voxelranges, General.Map.Config.VoxelRanges, "voxels");
+			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(colormapranges, General.Map.Config.ColormapRanges, "colormaps", "Colormap");
+			FindRanges(voxelranges, General.Map.Config.VoxelRanges, "voxels", "Voxel");
 
 			//mxd
 			invertedflatranges = new List<LumpRange>();
@@ -140,7 +141,7 @@ namespace CodeImp.DoomBuilder.Data
 			// Not already disposed?
 			if(!isdisposed)
 			{
-				General.WriteLogLine("Closing WAD resource '" + location.location + "'");
+				General.WriteLogLine("Closing WAD resource \"" + location.location + "\"");
 
 				// Clean up
 				file.Dispose();
@@ -176,7 +177,7 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		// This fills a ranges list
-		private void FindRanges(List<LumpRange> ranges, IDictionary rangeinfos, string rangename)
+		private void FindRanges(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
@@ -193,9 +194,7 @@ namespace CodeImp.DoomBuilder.Data
 					int startindex = file.FindLumpIndex(rangestart);
 					while(startindex > -1)
 					{
-						LumpRange range = new LumpRange();
-						range.start = startindex;
-						range.end = file.FindLumpIndex(rangeend, startindex);
+						LumpRange range = new LumpRange { start = startindex, end = file.FindLumpIndex(rangeend, startindex) };
 						if(range.end > -1)
 						{
 							if(!successfulrangestarts.ContainsKey(startindex)) successfulrangestarts.Add(startindex, false); //mxd
@@ -221,7 +220,20 @@ 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 '" + location.location + "' ('" + group.Value.Value + "' marker is missing)!");
+				General.ErrorLogger.Add(ErrorType.Warning, "\"" + group.Value.Key + "\" range at index " + group.Key.start + " is not closed in resource \"" + location.location + "\" (\"" + group.Value.Value + "\" marker is missing).");
+			}
+
+			//mxd. Check duplicates
+			foreach(LumpRange range in ranges)
+			{
+				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.location + "\".");
+					else
+						names.Add(file.Lumps[i].Name);
+				}
 			}
 		}
 		
@@ -335,7 +347,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Textures
 
 		// This loads the textures
-		public override ICollection<ImageData> LoadTextures(PatchNames pnames)
+		public override IEnumerable<ImageData> LoadTextures(PatchNames pnames, Dictionary<string, TexturesParser> cachedparsers)
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
@@ -371,7 +383,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 + "' because it doesn't contain any data.");
+						General.ErrorLogger.Add(ErrorType.Error, "Can't load texture \"" + file.Lumps[i].Name + "\" because it doesn't contain any data.");
 					}
 				}
 			}
@@ -380,10 +392,21 @@ namespace CodeImp.DoomBuilder.Data
 			int lumpindex = file.FindLumpIndex("TEXTURES");
 			while(lumpindex > -1)
 			{
-				MemoryStream filedata = new MemoryStream(file.Lumps[lumpindex].Stream.ReadAllBytes());
-				WADReader.LoadHighresTextures(filedata, Path.Combine(Path.GetFileName(file.Filename), "TEXTURES#" + lumpindex), ref images, null, null);
-				filedata.Dispose();
-				
+				//mxd. Added TexturesParser caching
+				string fullpath = Path.Combine(this.location.location, "TEXTURES#" + lumpindex);
+				if(cachedparsers.ContainsKey(fullpath))
+				{
+					// Make the textures
+					foreach(TextureStructure t in cachedparsers[fullpath].Textures)
+						images.Add(t.MakeImage());
+				}
+				else
+				{
+					MemoryStream filedata = new MemoryStream(file.Lumps[lumpindex].Stream.ReadAllBytes());
+					cachedparsers.Add(fullpath, LoadHighresTextures(new TextResourceData(this, filedata, "TEXTURES", lumpindex, true), ref images)); //mxd
+					filedata.Dispose();
+				}
+
 				// Find next
 				lumpindex = file.FindLumpIndex("TEXTURES", lumpindex + 1);
 			}
@@ -396,11 +419,11 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		// This loads the texture definitions from a TEXTURES lump
-		public static void LoadHighresTextures(Stream stream, string filename, ref List<ImageData> images, Dictionary<long, ImageData> textures, Dictionary<long, ImageData> flats)
+		public static TexturesParser LoadHighresTextures(TextResourceData data, ref List<ImageData> images)
 		{
 			// Parse the data
 			TexturesParser parser = new TexturesParser();
-			parser.Parse(stream, filename, false);
+			parser.Parse(data, false);
 			if(parser.HasError) parser.LogError(); //mxd
 
 			// Make the textures
@@ -410,6 +433,15 @@ namespace CodeImp.DoomBuilder.Data
 				ImageData img = t.MakeImage();
 				images.Add(img);
 			}
+
+			//mxd. Add to text resources collection
+			if(!General.Map.Data.TextResources.ContainsKey(parser.ScriptType))
+				General.Map.Data.TextResources[parser.ScriptType] = new HashSet<TextResource>();
+
+			foreach(KeyValuePair<string, TextResource> group in parser.TextResources)
+				General.Map.Data.TextResources[parser.ScriptType].Add(group.Value);
+
+			return parser; //mxd
 		}
 		
 		// This loads a set of textures
@@ -582,7 +614,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Flats
 
 		//mxd. This loads the flats
-		public override ICollection<ImageData> LoadFlats() 
+		public override IEnumerable<ImageData> LoadFlats(Dictionary<string, TexturesParser> cachedparsers) 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
@@ -610,9 +642,20 @@ namespace CodeImp.DoomBuilder.Data
 			int lumpindex = file.FindLumpIndex("TEXTURES");
 			while(lumpindex > -1) 
 			{
-				MemoryStream filedata = new MemoryStream(file.Lumps[lumpindex].Stream.ReadAllBytes());
-				WADReader.LoadHighresFlats(filedata, Path.Combine(Path.GetFileName(file.Filename), "TEXTURES#" + lumpindex), ref images, null, null);
-				filedata.Dispose();
+				//mxd. Added TexturesParser caching
+				string fullpath = Path.Combine(this.location.location, "TEXTURES#" + lumpindex);
+				if(cachedparsers.ContainsKey(fullpath))
+				{
+					// Make the textures
+					foreach(TextureStructure t in cachedparsers[fullpath].Flats)
+						images.Add(t.MakeImage());
+				}
+				else
+				{
+					MemoryStream filedata = new MemoryStream(file.Lumps[lumpindex].Stream.ReadAllBytes());
+					cachedparsers.Add(fullpath, LoadHighresFlats(new TextResourceData(this, filedata, "TEXTURES", lumpindex, true), ref images)); //mxd
+					filedata.Dispose();
+				}
 
 				// Find next
 				lumpindex = file.FindLumpIndex("TEXTURES", lumpindex + 1);
@@ -626,11 +669,11 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		// This loads the flat definitions from a TEXTURES lump
-		public static void LoadHighresFlats(Stream stream, string filename, ref List<ImageData> images, Dictionary<long, ImageData> textures, Dictionary<long, ImageData> flats)
+		public static TexturesParser LoadHighresFlats(TextResourceData data, ref List<ImageData> images)
 		{
 			// Parse the data
 			TexturesParser parser = new TexturesParser();
-			parser.Parse(stream, filename, false);
+			parser.Parse(data, false);
 			if(parser.HasError) parser.LogError(); //mxd
 
 			// Make the textures
@@ -640,6 +683,15 @@ namespace CodeImp.DoomBuilder.Data
 				ImageData img = t.MakeImage();
 				images.Add(img);
 			}
+
+			//mxd. Add to text resources collection
+			if(!General.Map.Data.TextResources.ContainsKey(parser.ScriptType))
+				General.Map.Data.TextResources[parser.ScriptType] = new HashSet<TextResource>();
+
+			foreach(KeyValuePair<string, TextResource> group in parser.TextResources)
+				General.Map.Data.TextResources[parser.ScriptType].Add(group.Value);
+
+			return parser; //mxd
 		}
 		
 		// This finds and returns a patch stream
@@ -664,7 +716,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Sprite
 
 		// This loads the textures
-		public override ICollection<ImageData> LoadSprites()
+		public override IEnumerable<ImageData> LoadSprites(Dictionary<string, TexturesParser> cachedparsers)
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
@@ -675,10 +727,21 @@ namespace CodeImp.DoomBuilder.Data
 			int lumpindex = file.FindLumpIndex("TEXTURES");
 			while(lumpindex > -1)
 			{
-				MemoryStream filedata = new MemoryStream(file.Lumps[lumpindex].Stream.ReadAllBytes());
-				WADReader.LoadHighresSprites(filedata, Path.Combine(Path.GetFileName(file.Filename), "TEXTURES#" + lumpindex), ref images, null, null); 
-				filedata.Dispose();
-				
+				//mxd. Added TexturesParser caching
+				string fullpath = Path.Combine(this.location.location, "TEXTURES#" + lumpindex);
+				if(cachedparsers.ContainsKey(fullpath))
+				{
+					// Make the textures
+					foreach(TextureStructure t in cachedparsers[fullpath].Sprites)
+						images.Add(t.MakeImage());
+				}
+				else
+				{
+					MemoryStream filedata = new MemoryStream(file.Lumps[lumpindex].Stream.ReadAllBytes());
+					cachedparsers.Add(fullpath, LoadHighresSprites(new TextResourceData(this, filedata, "TEXTURES", lumpindex, true), ref images)); //mxd
+					filedata.Dispose();
+				}
+
 				// Find next
 				lumpindex = file.FindLumpIndex("TEXTURES", lumpindex + 1);
 			}
@@ -688,11 +751,11 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		// This loads the sprites definitions from a TEXTURES lump
-		public static void LoadHighresSprites(Stream stream, string filename, ref List<ImageData> images, Dictionary<long, ImageData> textures, Dictionary<long, ImageData> flats)
+		public static TexturesParser LoadHighresSprites(TextResourceData data, ref List<ImageData> images)
 		{
 			// Parse the data
 			TexturesParser parser = new TexturesParser();
-			parser.Parse(stream, filename, false);
+			parser.Parse(data, false);
 			if(parser.HasError) parser.LogError(); //mxd
 			
 			// Make the textures
@@ -702,6 +765,15 @@ namespace CodeImp.DoomBuilder.Data
 				ImageData img = t.MakeImage();
 				images.Add(img);
 			}
+
+			//mxd. Add to text resources collection
+			if(!General.Map.Data.TextResources.ContainsKey(parser.ScriptType))
+				General.Map.Data.TextResources[parser.ScriptType] = new HashSet<TextResource>();
+
+			foreach(KeyValuePair<string, TextResource> group in parser.TextResources)
+				General.Map.Data.TextResources[parser.ScriptType].Add(group.Value);
+
+			return parser; //mxd
 		}
 		
 		// This finds and returns a sprite stream
@@ -763,11 +835,10 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		//mxd
-		public override KeyValuePair<string, Stream> GetVoxeldefData() 
+		public override IEnumerable<TextResourceData> GetVoxeldefData() 
 		{
-			Lump lump = file.FindLump("VOXELDEF");
-			if(lump != null) return new KeyValuePair<string, Stream>("VOXELDEF", lump.Stream);
-			return new KeyValuePair<string, Stream>();
+			if(issuspended) throw new Exception("Data reader is suspended");
+			return GetFirstLump("VOXELDEF");
 		}
 
 		//mxd. This finds and returns a voxel stream or null if no voxel was found
@@ -791,117 +862,107 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== Decorate, Gldefs, Mapinfo, etc...
 
 		// This finds and returns a sprite stream
-		public override Dictionary<string, Stream> GetDecorateData(string pname)
+		public override IEnumerable<TextResourceData> GetDecorateData(string pname)
 		{
-			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
+			return GetAllLumps("DECORATE");
+		}
 
-			Dictionary<string, Stream> result = new Dictionary<string, Stream>();
-
-			// Find all lumps named 'DECORATE'
-			int lumpindex = file.FindLumpIndex(pname);
-			while(lumpindex > -1)
-			{
-				//mxd. Since we are using Dictionary, all names should be unique
-				result.Add(Path.Combine(file.Filename, pname + "#" + lumpindex), file.Lumps[lumpindex].Stream);
-				
-				// Find next
-				lumpindex = file.FindLumpIndex(pname, lumpindex + 1);
-			}
+		//mxd. Should be only one entry per wad
+		public override IEnumerable<TextResourceData> GetMapinfoData() 
+		{
+			if(issuspended) throw new Exception("Data reader is suspended");
 			
+			// First look for ZMAPINFO
+			List<TextResourceData> result = new List<TextResourceData>();
+			result.AddRange(GetFirstLump("ZMAPINFO"));
+
+			// Then for MAPINFO
+			if(result.Count == 0) result.AddRange(GetFirstLump("MAPINFO"));
 			return result;
 		}
 
 		//mxd
-		public override Dictionary<string, Stream> GetMapinfoData() 
+		public override IEnumerable<TextResourceData> GetGldefsData(GameType gametype) 
 		{
 			if(issuspended) throw new Exception("Data reader is suspended");
 
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>(StringComparer.Ordinal);
-			string src = "ZMAPINFO";
-
-			//should be only one entry per wad
-			//first look for ZMAPINFO
-			int lumpindex = file.FindLumpIndex(src);
+			List<TextResourceData> result = new List<TextResourceData>();
 
-			//then for MAPINFO
-			if(lumpindex == -1) 
+			// Try to load game specific GLDEFS first
+			if(gametype != GameType.UNKNOWN)
 			{
-				src = "MAPINFO";
-				lumpindex = file.FindLumpIndex(src);
+				string lumpname = Gldefs.GLDEFS_LUMPS_PER_GAME[(int)gametype];
+				result.AddRange(GetFirstLump(lumpname));
 			}
 
-			if(lumpindex != -1) streams.Add(src, file.Lumps[lumpindex].Stream);
-			return streams;
+			// Should be only one entry per wad
+			result.AddRange(GetFirstLump("GLDEFS"));
+			return result;
 		}
 
 		//mxd
-		public override Dictionary<string, Stream> GetGldefsData(GameType gameType) 
+		public override IEnumerable<TextResourceData> GetModeldefData() 
 		{
 			if(issuspended) throw new Exception("Data reader is suspended");
-
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>(StringComparer.Ordinal);
-			int lumpindex;
-
-			//try to load game specific GLDEFS first
-			if(gameType != GameType.UNKNOWN) 
-			{
-				string lumpName = Gldefs.GLDEFS_LUMPS_PER_GAME[(int)gameType];
-				lumpindex = file.FindLumpIndex(lumpName);
-
-				if(lumpindex != -1)
-					streams.Add(lumpName, file.Lumps[lumpindex].Stream);
-			}
-
-			//should be only one entry per wad
-			lumpindex = file.FindLumpIndex("GLDEFS");
-			
-			if(lumpindex != -1) streams.Add("GLDEFS", file.Lumps[lumpindex].Stream);
-			return streams;
+			return GetFirstLump("MODELDEF");
 		}
 
 		//mxd
-		public override Dictionary<string, Stream> GetModeldefData() 
+		public override IEnumerable<TextResourceData> GetReverbsData() 
 		{
 			if(issuspended) throw new Exception("Data reader is suspended");
-
-			Dictionary<string, Stream> streams = new Dictionary<string, Stream>(StringComparer.Ordinal);
-			int lumpindex = file.FindLumpIndex("MODELDEF");
-
-			if(lumpindex != -1) streams.Add("MODELDEF", file.Lumps[lumpindex].Stream);
-			return streams;
+			return GetFirstLump("REVERBS");
 		}
 
 		//mxd
-		public override Dictionary<string, Stream> GetReverbsData() 
+		public override IEnumerable<TextResourceData> GetSndSeqData() 
 		{
 			if(issuspended) throw new Exception("Data reader is suspended");
+			return GetFirstLump("SNDSEQ");
+		}
 
-			Dictionary<string, Stream> result = new Dictionary<string, Stream>();
-			Lump lump = file.FindLump("REVERBS");
-			if(lump != null) result.Add(Path.Combine(location.location, "REVERBS"), lump.Stream);
-			return result;
+		//mxd
+		public override IEnumerable<TextResourceData> GetAnimdefsData()
+		{
+			if(issuspended) throw new Exception("Data reader is suspended");
+			return GetAllLumps("ANIMDEFS");
 		}
 
 		//mxd
-		public override Dictionary<string, Stream> GetSndSeqData() 
+		public override IEnumerable<TextResourceData> GetTerrainData()
 		{
 			if(issuspended) throw new Exception("Data reader is suspended");
+			return GetAllLumps("TERRAIN");
+		}
+
+		//mxd
+		private IEnumerable<TextResourceData> GetFirstLump(string name)
+		{
+			List<TextResourceData> result = new List<TextResourceData>();
+			int lumpindex = file.FindLumpIndex(name);
+			if(lumpindex != -1)
+				result.Add(new TextResourceData(this, file.Lumps[lumpindex].Stream, name, lumpindex, true));
 
-			Dictionary<string, Stream> result = new Dictionary<string, Stream>();
-			Lump lump = file.FindLump("SNDSEQ");
-			if(lump != null) result.Add(Path.Combine(location.location, "SNDSEQ"), lump.Stream);
 			return result;
 		}
 
 		//mxd
-		public override Dictionary<string, Stream> GetAnimdefsData()
+		private IEnumerable<TextResourceData> GetAllLumps(string name)
 		{
-			if(issuspended) throw new Exception("Data reader is suspended");
+			List<TextResourceData> result = new List<TextResourceData>();
+
+			// Find all lumps with given name
+			int lumpindex = file.FindLumpIndex(name);
+			while(lumpindex > -1)
+			{
+				// Add to collection
+				result.Add(new TextResourceData(this, file.Lumps[lumpindex].Stream, name, lumpindex, true));
+
+				// Find next entry
+				lumpindex = file.FindLumpIndex(name, lumpindex + 1);
+			}
 
-			Dictionary<string, Stream> result = new Dictionary<string, Stream>();
-			Lump lump = file.FindLump("ANIMDEFS");
-			if(lump != null) result.Add(Path.Combine(location.location, "ANIMDEFS"), lump.Stream);
 			return result;
 		}
 
diff --git a/Source/Core/GZBuilder/Controls/TagsSelector.cs b/Source/Core/GZBuilder/Controls/TagsSelector.cs
index 407d5b4c3..70a6219c0 100644
--- a/Source/Core/GZBuilder/Controls/TagsSelector.cs
+++ b/Source/Core/GZBuilder/Controls/TagsSelector.cs
@@ -120,6 +120,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Controls
 			UpdateTagPicker(tags[0]);
 			UpdateTagsList();
 			removetag.Enabled = (tags.Count > 1);
+			clear.Enabled = (tagpicker.Text.Trim() != "0");
 		}
 
 		public void SetValue(List<int> newtags, bool first)
@@ -278,8 +279,9 @@ namespace CodeImp.DoomBuilder.GZBuilder.Controls
 
 		private void clear_Click(object sender, EventArgs e)
 		{
-			tagpicker.SelectedIndex = -1;
+			tagpicker.Focus();
 			tagpicker.Text = "0";
+			tagpicker.SelectAll();
 		}
 
 		private void addtag_Click(object sender, EventArgs e)
@@ -365,6 +367,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Controls
 			}
 
 			string text = tagpicker.Text.Trim();
+			clear.Enabled = (text != "0");
 			if(string.IsNullOrEmpty(text))
 			{
 				tags[curtagindex] = int.MinValue;
diff --git a/Source/Core/GZBuilder/Data/MapInfo.cs b/Source/Core/GZBuilder/Data/MapInfo.cs
index be500bcc5..0b404c10d 100644
--- a/Source/Core/GZBuilder/Data/MapInfo.cs
+++ b/Source/Core/GZBuilder/Data/MapInfo.cs
@@ -34,7 +34,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.Data
 
 		#region ================== Properties
 
-		public bool IsDefined { get { return isdefined; } }
+		public bool IsDefined { get { return isdefined; } internal set { isdefined = value; } }
 
 		public string Title { get { return title; } internal set { title = value; isdefined = true; } }
 		public string Sky1 { get { return sky1; } internal set { sky1 = value; isdefined = true; } }
diff --git a/Source/Core/GZBuilder/GZDoom/AcsParserSE.cs b/Source/Core/GZBuilder/GZDoom/AcsParserSE.cs
index 320ba1dd9..21d0163b6 100644
--- a/Source/Core/GZBuilder/GZDoom/AcsParserSE.cs
+++ b/Source/Core/GZBuilder/GZDoom/AcsParserSE.cs
@@ -2,6 +2,8 @@
 using System.IO;
 using System.Collections.Generic;
 using System.Globalization;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.ZDoom;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 
@@ -10,6 +12,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 {
 	internal sealed class AcsParserSE : ZDTextParser
 	{
+		internal override ScriptType ScriptType { get { return ScriptType.ACS; } }
+		
 		internal delegate void IncludeDelegate(AcsParserSE parser, string includefile, IncludeType includetype);
 		internal IncludeDelegate OnInclude;
 
@@ -31,6 +35,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 		internal bool AddArgumentsToScriptNames;
 		internal bool IsMapScriptsLump;
+		internal bool IgnoreErrors;
 
 		internal enum IncludeType
 		{
@@ -50,37 +55,47 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			specialtokens += "(,)";
 		}
 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors) 
+		public override bool Parse(TextResourceData data, bool clearerrors) 
 		{
-			return Parse(stream, sourcefilename, new HashSet<string>(), false, IncludeType.NONE, clearerrors);
+			return Parse(data, new HashSet<string>(), false, IncludeType.NONE, clearerrors);
 		}
 
-		public bool Parse(Stream stream, string sourcefilename, bool processincludes, IncludeType includetype, bool clearerrors)
+		public bool Parse(TextResourceData data, bool processincludes, IncludeType includetype, bool clearerrors)
 		{
-			return Parse(stream, sourcefilename, includestoskip, processincludes, includetype, clearerrors);
+			return Parse(data, includestoskip, processincludes, includetype, clearerrors);
 		}
 
-		public bool Parse(Stream stream, string sourcefilename, HashSet<string> configincludes, bool processincludes, IncludeType includetype, bool clearerrors) 
+		public bool Parse(TextResourceData data, HashSet<string> configincludes, bool processincludes, IncludeType includetype, bool clearerrors) 
 		{
-			string source = sourcefilename.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+			string source = data.Filename.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
 
 			// Duplicate checks
 			if(parsedlumps.Contains(source))
 			{
-				ReportError("Already parsed '" + source + "'. Check your #include directives");
-				return false;
+				ReportError("Already parsed \"" + source + "\". Check your #include directives");
+				return IgnoreErrors;
 			}
 			
 			parsedlumps.Add(source);
 			includestoskip = configincludes;
 			int bracelevel = 0;
 
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Keep local data
 			Stream localstream = datastream;
 			string localsourcename = sourcename;
+			int localsourcelumpindex = sourcelumpindex;
 			BinaryReader localreader = datareader;
+			DataLocation locallocation = datalocation; //mxd
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -98,7 +113,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 					case "script":
 					{
 						SkipWhitespace(true);
-						int startpos = (int)stream.Position;
+						int startpos = (int)datastream.Position;
 						token = ReadToken();
 
 						//is it named script?
@@ -139,7 +154,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 								if(!string.IsNullOrEmpty(token))
 								{
-									int commentstart = token.IndexOf("//", System.StringComparison.Ordinal);
+									int commentstart = token.IndexOf("//", StringComparison.Ordinal);
 									if(commentstart != -1) //found comment
 									{ 
 										commentstart += 2;
@@ -168,7 +183,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						SkipWhitespace(true);
 						string funcname = ReadToken(); //read return type
 						SkipWhitespace(true);
-						int startpos = (int)stream.Position;
+						int startpos = (int)datastream.Position;
 						funcname += " " + ReadToken(); //read function name
 
 						// Try to parse argument names
@@ -188,7 +203,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(IsMapScriptsLump)
 						{
 							ReportError("SCRIPTS lump can not be compiled as a library");
-							return false;
+							if(!IgnoreErrors) return false;
 						}
 						
 						SkipWhitespace(true);
@@ -197,7 +212,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!libname.StartsWith("\"") || !libname.EndsWith("\""))
 						{
 							ReportError("#library name should be quoted");
-							return false;
+							return IgnoreErrors;
 						}
 
 						libname = StripTokenQuotes(libname);
@@ -205,7 +220,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(string.IsNullOrEmpty(libname))
 						{
 							ReportError("Expected library name");
-							return false;
+							return IgnoreErrors;
 						}
 
 						// Store only when the script compiling was executed for is library
@@ -231,7 +246,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!includelump.StartsWith("\"") || !includelump.EndsWith("\""))
 							{
 								ReportError(token + " filename should be quoted");
-								return false;
+								return IgnoreErrors;
 							}
 
 							includelump = StripTokenQuotes(includelump);
@@ -239,7 +254,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(string.IsNullOrEmpty(includelump))
 							{
 								ReportError("Expected file name to " + token);
-								return false;
+								return IgnoreErrors;
 							}
 
 							includelump = includelump.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
@@ -251,7 +266,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							string includelumppath = GetRootedPath(source, includelump);
 
 							// Rooting succeeded?
-							if(HasError || string.IsNullOrEmpty(includelumppath)) return false;
+							if((HasError && !IgnoreErrors) || string.IsNullOrEmpty(includelumppath))
+								return IgnoreErrors;
 
 							// Already parsed?
 							if(includes.Contains(includelumppath))
@@ -260,8 +276,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 								//and must be included/imported separately
 								if(!islibrary)
 								{
-									ReportError("Already parsed '" + includelump + "'. Check your " + token + " directives");
-									return false;
+									ReportError("Already parsed \"" + includelump + "\". Check your " + token + " directives");
+									return IgnoreErrors;
 								}
 							}
 							else
@@ -272,16 +288,19 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 								// Callback to parse this file
 								if(OnInclude != null)
 								{
+									IsMapScriptsLump = false;
 									OnInclude(this, includelumppath, islibrary ? IncludeType.LIBRARY : IncludeType.INCLUDE);
 								}
 
 								// Bail out on error
-								if(this.HasError) return false;
+								if(this.HasError && !IgnoreErrors) return false;
 
 								// Set our buffers back to continue parsing
 								datastream = localstream;
 								datareader = localreader;
 								sourcename = localsourcename;
+								sourcelumpindex = localsourcelumpindex; //mxd
+								datalocation = locallocation; //mxd
 							}
 						}
 						break;
@@ -340,9 +359,5 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			return "(void)";
 		}
 
-		protected override string GetLanguageType()
-		{
-			return "ACS";
-		}
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/GZBuilder/GZDoom/DecorateParserSE.cs b/Source/Core/GZBuilder/GZDoom/DecorateParserSE.cs
index 65c3d7e21..604abb799 100644
--- a/Source/Core/GZBuilder/GZDoom/DecorateParserSE.cs
+++ b/Source/Core/GZBuilder/GZDoom/DecorateParserSE.cs
@@ -1,5 +1,6 @@
-using System.IO;
-using System.Collections.Generic;
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.ZDoom;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 
@@ -9,6 +10,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 {
 	internal sealed class DecorateParserSE : ZDTextParser
 	{
+		internal override ScriptType ScriptType { get { return ScriptType.DECORATE; } }
+		
 		private readonly List<ScriptItem> actors;
 		public List<ScriptItem> Actors { get { return actors; } }
 
@@ -17,9 +20,17 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			actors = new List<ScriptItem>();
 		}
 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors) 
+		public override bool Parse(TextResourceData data, bool clearerrors) 
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -28,7 +39,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 				if(string.IsNullOrEmpty(token) || token.ToUpperInvariant() != "ACTOR") continue;
 
 				SkipWhitespace(true);
-				int startpos = (int)stream.Position;
+				int startpos = (int)datastream.Position;
 
 				List<string> definition = new List<string>();
 
@@ -47,10 +58,5 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			actors.Sort(ScriptItem.SortByName);
 			return true;
 		}
-
-		protected override string GetLanguageType()
-		{
-			return "DECORATE";
-		}
 	}
 }
diff --git a/Source/Core/GZBuilder/GZDoom/GldefsParser.cs b/Source/Core/GZBuilder/GZDoom/GldefsParser.cs
index 4c762da81..f08a8b712 100644
--- a/Source/Core/GZBuilder/GZDoom/GldefsParser.cs
+++ b/Source/Core/GZBuilder/GZDoom/GldefsParser.cs
@@ -5,6 +5,8 @@ using System.Drawing;
 using System.IO;
 using System.Collections.Generic;
 using System.Globalization;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.ZDoom;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 using CodeImp.DoomBuilder.Rendering;
@@ -56,6 +58,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 		#region ================== Properties
 
+		internal override ScriptType ScriptType { get { return ScriptType.GLDEFS; } }
+		
 		internal Dictionary<string, DynamicLightData> LightsByName { get { return lightsbyname; } }
 		internal Dictionary<string, string> Objects { get { return objects; } }
 		internal Dictionary<long, GlowingFlatData> GlowingFlats { get { return glowingflats; } }
@@ -82,14 +86,25 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 		#region ================== Parsing
 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors) 
+		public override bool Parse(TextResourceData data, bool clearerrors) 
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Keep local data
 			Stream localstream = datastream;
 			string localsourcename = sourcename;
+			int localsourcelumpindex = sourcelumpindex;
 			BinaryReader localreader = datareader;
+			DataLocation locallocation = datalocation;
+			string localtextresourcepath = textresourcepath;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -123,6 +138,9 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						datastream = localstream;
 						datareader = localreader;
 						sourcename = localsourcename;
+						sourcelumpindex = localsourcelumpindex;
+						datalocation = locallocation;
+						textresourcepath = localtextresourcepath;
 						break;
 
 					case "$gzdb_skip": return !this.HasError;
@@ -173,7 +191,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Red))
 						{
 							// Not numeric!
-							ReportError("Expected Red color value, but got '" + token + "'");
+							ReportError("Expected Red color value, but got \"" + token + "\"");
 							return false;
 						}
 
@@ -182,7 +200,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Green))
 						{
 							// Not numeric!
-							ReportError("Expected Green color value, but got '" + token + "'");
+							ReportError("Expected Green color value, but got \"" + token + "\"");
 							return false;
 						}
 
@@ -191,7 +209,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Blue))
 						{
 							// Not numeric!
-							ReportError("Expected Blue color value, but got '" + token + "'");
+							ReportError("Expected Blue color value, but got \"" + token + "\"");
 							return false;
 						}
 					break;
@@ -205,7 +223,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.PrimaryRadius))
 							{
 								// Not numeric!
-								ReportError("Expected Size value, but got '" + token + "'");
+								ReportError("Expected Size value, but got \"" + token + "\"");
 								return false;
 							}
 							light.PrimaryRadius *= 2;
@@ -213,7 +231,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						}
 						else
 						{
-							ReportError("'" + token + "' is not valid property for " + lighttype);
+							ReportError("\"" + token + "\" is not valid property for " + lighttype);
 							return false;
 						}
 					break;
@@ -224,7 +242,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!ReadSignedFloat(token, ref light.Offset.X))
 						{
 							// Not numeric!
-							ReportError("Expected Offset X value, but got '" + token + "'");
+							ReportError("Expected Offset X value, but got \"" + token + "\"");
 							return false;
 						}
 
@@ -233,7 +251,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!ReadSignedFloat(token, ref light.Offset.Z))
 						{
 							// Not numeric!
-							ReportError("Expected Offset Y value, but got '" + token + "'");
+							ReportError("Expected Offset Y value, but got \"" + token + "\"");
 							return false;
 						}
 
@@ -242,7 +260,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!ReadSignedFloat(token, ref light.Offset.Y))
 						{
 							// Not numeric!
-							ReportError("Expected Offset Z value, but got '" + token + "'");
+							ReportError("Expected Offset Z value, but got \"" + token + "\"");
 							return false;
 						}
 					break;
@@ -256,7 +274,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
 						{
 							// Not numeric!
-							ReportError("expected Subtractive value, but got '" + token + "'");
+							ReportError("expected Subtractive value, but got \"" + token + "\"");
 							return false;
 						}
 
@@ -273,7 +291,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
 						{
 							// Not numeric!
-							ReportError("Expected DontLightSelf value, but got '" + token + "'");
+							ReportError("Expected DontLightSelf value, but got \"" + token + "\"");
 							return false;
 						}
 
@@ -291,7 +309,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out interval))
 							{
 								// Not numeric!
-								ReportError("Expected Interval value, but got '" + token + "'");
+								ReportError("Expected Interval value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -309,7 +327,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						}
 						else
 						{
-							ReportError("'" + token + "' is not valid property for " + lighttype);
+							ReportError("\"" + token + "\" is not valid property for " + lighttype);
 							return false;
 						}
 					break;
@@ -323,7 +341,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.SecondaryRadius))
 							{
 								// Not numeric!
-								ReportError("Expected SecondarySize value, but got '" + token + "'");
+								ReportError("Expected SecondarySize value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -331,7 +349,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						}
 						else
 						{
-							ReportError("'" + token + "' is not valid property for " + lighttype);
+							ReportError("\"" + token + "\" is not valid property for " + lighttype);
 							return false;
 						}
 					break;
@@ -346,7 +364,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out chance))
 							{
 								// Not numeric!
-								ReportError("Expected Chance value, but got '" + token + "'");
+								ReportError("Expected Chance value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -355,7 +373,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						}
 						else
 						{
-							ReportError("'" + token + "' is not valid property for " + lighttype);
+							ReportError("\"" + token + "\" is not valid property for " + lighttype);
 							return false;
 						}
 					break;
@@ -370,7 +388,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out scale))
 							{
 								// Not numeric!
-								ReportError("Expected Scale value, but got '" + token + "'");
+								ReportError("Expected Scale value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -386,7 +404,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						}
 						else
 						{
-							ReportError("'" + token + "' is not valid property for " + lighttype);
+							ReportError("\"" + token + "\" is not valid property for " + lighttype);
 							return false;
 						}
 					break;
@@ -398,14 +416,14 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						// General checks
 						if(light.Color.Red == 0.0f && light.Color.Green == 0.0f && light.Color.Blue == 0.0f)
 						{
-							LogWarning("'" + lightname + "' light Color is " + light.Color.Red + "," + light.Color.Green + "," + light.Color.Blue + ". It won't be shown in GZDoom");
+							LogWarning("\"" + lightname + "\" light Color is " + light.Color.Red + "," + light.Color.Green + "," + light.Color.Blue + ". It won't be shown in GZDoom");
 							skip = true;
 						}
 
 						// Light-type specific checks
 						if(light.Type == DynamicLightType.NORMAL && light.PrimaryRadius == 0)
 						{
-							LogWarning("'" + lightname + "' light Size is 0. It won't be shown in GZDoom");
+							LogWarning("\"" + lightname + "\" light Size is 0. It won't be shown in GZDoom");
 							skip = true;
 						}
 
@@ -413,7 +431,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						{
 							if(light.PrimaryRadius == 0 && light.SecondaryRadius == 0)
 							{
-								LogWarning("'" + lightname + "' light Size and SecondarySize are 0. This light won't be shown in GZDoom");
+								LogWarning("\"" + lightname + "\" light Size and SecondarySize are 0. This light won't be shown in GZDoom");
 								skip = true;
 							}
 						}
@@ -484,7 +502,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						}
 						else
 						{
-							LogWarning("Light declaration not found for light '" + token + "'");
+							LogWarning("Light declaration not found for light \"" + token + "\"");
 						}
 					}
 				}
@@ -605,7 +623,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							}
 							else
 							{
-								ReportError("expected glow color value, but got '" + token + "'");
+								ReportError("expected glow color value, but got \"" + token + "\"");
 								return false;
 							}
 						}
@@ -662,8 +680,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 						if(!fullblack && !fullbright)
 						{
-							string expectedflags = (subtractivetexture ? "'fullbright'" : "'fullbright' or 'fullblack'");
-							ReportError("expected " + expectedflags + " flag, but got '" + token + "'");
+							string expectedflags = (subtractivetexture ? "\"fullbright\"" : "\"fullbright\" or \"fullblack\"");
+							ReportError("expected " + expectedflags + " flag, but got \"" + token + "\"");
 							return false;
 						}
 
@@ -697,7 +715,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 				return false;
 			}
 
-			if(skyboxes.ContainsKey(name)) LogWarning("Skybox \"" + name + "\" is double-defined");
+			if(skyboxes.ContainsKey(name)) LogWarning("Skybox \"" + name + "\" is double defined");
 
 			SkyboxInfo info = new SkyboxInfo(name.ToUpperInvariant()); 
 
@@ -782,7 +800,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			// Already parsed?
 			if(parsedlumps.Contains(includelump))
 			{
-				ReportError("Already parsed '" + includelump + "'. Check your #include directives");
+				ReportError("Already parsed \"" + includelump + "\". Check your #include directives");
 				return false;
 			}
 
@@ -805,11 +823,6 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			parsedlumps.Clear();
 		}
 
-		protected override string GetLanguageType()
-		{
-			return "GLDEFS";
-		}
-
 		#endregion
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs b/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs
index 028ddf9a5..504cf4fd5 100644
--- a/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs
+++ b/Source/Core/GZBuilder/GZDoom/MapinfoParser.cs
@@ -5,6 +5,8 @@ using System.Collections.Generic;
 using System.Drawing;
 using System.Globalization;
 using System.IO;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using SlimDX;
 using CodeImp.DoomBuilder.ZDoom;
 using CodeImp.DoomBuilder.GZBuilder.Data;
@@ -34,6 +36,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 		#region ================== Properties
 
+		internal override ScriptType ScriptType { get { return ScriptType.MAPINFO; } }
+		
 		public MapInfo MapInfo { get { return mapinfo; } }
 		public Dictionary<int, string> SpawnNums { get { return spawnnums; } }
 		public Dictionary<int, string> DoomEdNums { get { return doomednums; } }
@@ -59,21 +63,33 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 		#region ================== Parsing
 
 
-		override public bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		override public bool Parse(TextResourceData data, bool clearerrors)
 		{
 			if(string.IsNullOrEmpty(mapname)) throw new NotSupportedException("Map name required!");
-			return Parse(stream, sourcefilename, mapname, clearerrors);
+			return Parse(data, mapname, clearerrors);
 		}
 
-		public bool Parse(Stream stream, string sourcefilename, string mapname, bool clearerrors) 
+		public bool Parse(TextResourceData data, string mapname, bool clearerrors) 
 		{
 			this.mapname = mapname.ToLowerInvariant();
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Keep local data
 			Stream localstream = datastream;
 			string localsourcename = sourcename;
+			int localsourcelumpindex = sourcelumpindex;
 			BinaryReader localreader = datareader;
+			DataLocation locallocation = datalocation;
+			string localtextresourcepath = textresourcepath;
 
 			// Classic format skip stoppers...
 			HashSet<string> breakat = new HashSet<string> { "map", "defaultmap", "adddefaultmap" };
@@ -135,6 +151,9 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 						// Parse properties
 						if(!ParseMapBlock()) return false;
+						
+						// There is a map entry for current map, which makes it defined 
+						mapinfo.IsDefined = true; 
 						break;
 
 					case "include":
@@ -144,6 +163,9 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						datastream = localstream;
 						datareader = localreader;
 						sourcename = localsourcename;
+						sourcelumpindex = localsourcelumpindex;
+						datalocation = locallocation;
+						textresourcepath = localtextresourcepath;
 						break;
 
 					case "gameinfo":
@@ -211,7 +233,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 				// Already parsed?
 				if(parsedlumps.Contains(includelump))
 				{
-					ReportError("Already parsed '" + includelump + "'. Check your include directives");
+					ReportError("Already parsed \"" + includelump + "\". Check your include directives");
 					return false;
 				}
 
@@ -259,7 +281,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 					string skyflatname = StripTokenQuotes(ReadToken());
 					if(string.IsNullOrEmpty(skyflatname))
 					{
-						ReportError("Unable to get SkyFlatName value");
+						ReportError("Expected SkyFlatName value");
 						return false; // Finished with this file
 					}
 
@@ -291,7 +313,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 				if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out id))
 				{
 					// Not numeric!
-					ReportError("Expected DoomEdNums entry number, but got '" + token + "'");
+					ReportError("Expected DoomEdNums entry number, but got \"" + token + "\"");
 					return false; // Finished with this file
 				}
 
@@ -303,7 +325,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 				string classname = StripTokenQuotes(ReadToken());
 				if(string.IsNullOrEmpty(classname))
 				{
-					ReportError("Unable to get DoomEdNums entry class definition");
+					ReportError("Expected DoomEdNums class definition");
 					return false; // Finished with this file
 				}
 
@@ -344,7 +366,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 				if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out id))
 				{
 					// Not numeric!
-					ReportError("Expected SpawnNums number, but got '" + token + "'");
+					ReportError("Expected SpawnNums number, but got \"" + token + "\"");
 					return false; // Finished with this file
 				}
 
@@ -495,7 +517,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 					if(!ReadSignedFloat(token, ref scrollspeed))
 					{
 						// Not numeric!
-						ReportError("Expected " + skytype + " scroll speed value, but got '" + token + "'");
+						ReportError("Expected " + skytype + " scroll speed value, but got \"" + token + "\"");
 						return false;
 					}
 
@@ -579,7 +601,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			}
 			else //...or not
 			{
-				ReportError("Failed to parse " + fadetype + " value from string '" + colorval + "'");
+				ReportError("Failed to parse " + fadetype + " value from string \"" + colorval + "\"");
 				return false;
 			}
 
@@ -603,7 +625,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			if(!ReadSignedInt(token, ref val))
 			{
 				// Not numeric!
-				ReportError("Expected " + shadetype + " value, but got '" + token + "'");
+				ReportError("Expected " + shadetype + " value, but got \"" + token + "\"");
 				return false;
 			}
 
@@ -632,7 +654,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out val))
 			{
 				// Not numeric!
-				ReportError("Expected " + densitytype + " value, but got '" + token + "'");
+				ReportError("Expected " + densitytype + " value, but got \"" + token + "\"");
 				return false;
 			}
 
@@ -670,11 +692,6 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			return false;
 		}
 
-		protected override string GetLanguageType()
-		{
-			return "MAPINFO";
-		}
-
 		#endregion
 	}
 }
diff --git a/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs b/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs
index 8417f4169..d8b3a21ec 100644
--- a/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs
+++ b/Source/Core/GZBuilder/GZDoom/ModeldefParser.cs
@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
-using System.IO;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.ZDoom;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 
@@ -8,6 +9,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 {
 	internal class ModeldefParser : ZDTextParser
 	{
+		internal override ScriptType ScriptType { get { return ScriptType.MODELDEF; } }
+
 		private readonly Dictionary<string, int> actorsbyclass;
 		internal Dictionary<string, int> ActorsByClass { get { return actorsbyclass; } }
 
@@ -21,10 +24,19 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 		}
 
 		//should be called after all decorate actors are parsed 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
 			entries = new Dictionary<string, ModelData>(StringComparer.Ordinal);
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -35,21 +47,15 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 					token = StripTokenQuotes(token).ToLowerInvariant();
 					if(token == "model") //model structure start
 					{ 
-						//find classname
+						// Find classname
 						SkipWhitespace(true);
 						string displayclassname = StripTokenQuotes(ReadToken(ActorStructure.ACTOR_CLASS_SPECIAL_TOKENS));
 						string classname = displayclassname.ToLowerInvariant();
 
 						if(!string.IsNullOrEmpty(classname) && !entries.ContainsKey(classname)) 
 						{
-							//now find opening brace
-							SkipWhitespace(true);
-							token = ReadToken();
-							if(token != "{") 
-							{
-								General.ErrorLogger.Add(ErrorType.Error, "Unexpected token found in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected '{', but got '" + token + "'");
-								continue; //something wrong with modeldef declaration, continue to next one
-							}
+							// Now find opening brace
+							if(!NextTokenIs("{")) return false;
 
 							ModeldefStructure mds = new ModeldefStructure();
 							if(mds.Parse(this, displayclassname) && mds.ModelData != null)
@@ -103,10 +109,5 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			}
 			while(scopelevel > 0);
 		}
-
-		protected override string GetLanguageType()
-		{
-			return "MODELDEF";
-		}
 	}
 }
diff --git a/Source/Core/GZBuilder/GZDoom/ModeldefParserSE.cs b/Source/Core/GZBuilder/GZDoom/ModeldefParserSE.cs
index c0454a939..df01d3033 100644
--- a/Source/Core/GZBuilder/GZDoom/ModeldefParserSE.cs
+++ b/Source/Core/GZBuilder/GZDoom/ModeldefParserSE.cs
@@ -1,7 +1,9 @@
-#region ================== Namespaces
+using CodeImp.DoomBuilder.Data;
+
+#region ================== Namespaces
 
 using System.Collections.Generic;
-using System.IO;
+using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 using CodeImp.DoomBuilder.ZDoom;
 
@@ -13,6 +15,8 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 {
 	internal sealed class ModeldefParserSE : ZDTextParser 
 	{
+		internal override ScriptType ScriptType { get { return ScriptType.MODELDEF; } }
+		
 		private readonly List<ScriptItem> models;
 		internal List<ScriptItem> Models { get { return models; } }
 
@@ -21,9 +25,17 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			models = new List<ScriptItem>();
 		}
 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors) 
+		public override bool Parse(TextResourceData data, bool clearerrors) 
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -32,7 +44,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 				if(string.IsNullOrEmpty(token) || token.ToUpperInvariant() != "MODEL") continue; 
 
 				SkipWhitespace(true);
-				int startpos = (int)stream.Position;
+				int startpos = (int)datastream.Position;
 				string modelname = ReadToken();
 
 				SkipWhitespace(true);
@@ -55,10 +67,5 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			models.Sort(ScriptItem.SortByName);
 			return true;
 		}
-
-		protected override string GetLanguageType()
-		{
-			return "MODELDEF";
-		}
 	}
 }
diff --git a/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs b/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs
index 2381f7488..388880c43 100644
--- a/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs
+++ b/Source/Core/GZBuilder/GZDoom/ModeldefStructure.cs
@@ -60,7 +60,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							path = parser.StripTokenQuotes(parser.ReadToken(false)).Replace("/", "\\"); // Don't skip newline
 							if(string.IsNullOrEmpty(path))
 							{
-								parser.ReportError("Expected model path, but got '" + token + "'");
+								parser.ReportError("Expected model path");
 								return false;
 							}
 							break;
@@ -78,7 +78,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out index)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected model index, but got '" + token + "'");
+								parser.ReportError("Expected model index, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -105,13 +105,13 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							string modelext = Path.GetExtension(token);
 							if(string.IsNullOrEmpty(modelext)) 
 							{
-								parser.ReportError("Model '" + token + "' won't be loaded. Models without extension are not supported by GZDoom");
+								parser.ReportError("Model \"" + token + "\" won't be loaded. Models without extension are not supported by GZDoom");
 								return false;
 							}
 
 							if(modelext != ".md3" && modelext != ".md2") 
 							{
-								parser.ReportError("Model '" + token + "' won't be loaded. Only MD2 and MD3 models are supported");
+								parser.ReportError("Model \"" + token + "\" won't be loaded. Only MD2 and MD3 models are supported");
 								return false;
 							}
 
@@ -132,7 +132,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out skinIndex)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected skin index, but got '" + token + "'");
+								parser.ReportError("Expected skin index, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -148,7 +148,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
 							if(string.IsNullOrEmpty(token)) 
 							{
-								parser.ReportError("Skin path required");
+								parser.ReportError("Expected skin path");
 								return false;
 							} 
 
@@ -159,7 +159,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							string texext = Path.GetExtension(token);
 							if(Array.IndexOf(ModelData.SUPPORTED_TEXTURE_EXTENSIONS, texext) == -1) 
 							{
-								parser.ReportError("Image format '" + texext + "' is not supported");
+								parser.ReportError("Image format \"" + texext + "\" is not supported");
 								return false;
 							} 
 
@@ -177,7 +177,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref scale.Y)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected Scale X value, but got '" + token + "'");
+								parser.ReportError("Expected Scale X value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -186,7 +186,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref scale.X)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected Scale Y value, but got '" + token + "'");
+								parser.ReportError("Expected Scale Y value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -195,7 +195,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref scale.Z)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected Scale Z value, but got '" + token + "'");
+								parser.ReportError("Expected Scale Z value, but got \"" + token + "\"");
 								return false;
 							}
 							break;
@@ -210,7 +210,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref offset.X)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected Offset X value, but got '" + token + "'");
+								parser.ReportError("Expected Offset X value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -219,7 +219,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref offset.Y)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected Offset Y value, but got '" + token + "'");
+								parser.ReportError("Expected Offset Y value, but got \"" + token + "\"");
 								return false;
 							}
 
@@ -228,7 +228,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref offset.Z)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected Offset Z value, but got '" + token + "'");
+								parser.ReportError("Expected Offset Z value, but got \"" + token + "\"");
 								return false;
 							}
 							break;
@@ -243,7 +243,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref offset.Z)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected ZOffset value, but got '" + token + "'");
+								parser.ReportError("Expected ZOffset value, but got \"" + token + "\"");
 								return false;
 							}
 							break;
@@ -258,7 +258,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref angleOffset)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected AngleOffset value, but got '" + token + "'");
+								parser.ReportError("Expected AngleOffset value, but got \"" + token + "\"");
 								return false;
 							}
 							break;
@@ -273,7 +273,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref pitchOffset)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected PitchOffset value, but got '" + token + "'");
+								parser.ReportError("Expected PitchOffset value, but got \"" + token + "\"");
 								return false;
 							}
 							break;
@@ -288,7 +288,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 							if(!parser.ReadSignedFloat(token, ref rollOffset)) 
 							{
 								// Not numeric!
-								parser.ReportError("Expected RollOffset value, but got '" + token + "'");
+								parser.ReportError("Expected RollOffset value, but got \"" + token + "\"");
 								return false;
 							}
 							break;
@@ -382,7 +382,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 										if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out modelIndex)) 
 										{
 											// Not numeric!
-											parser.ReportError("Expected model index, but got '" + token + "'");
+											parser.ReportError("Expected model index, but got \"" + token + "\"");
 											return false;
 										}
 
@@ -410,7 +410,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 											if(!parser.ReadSignedInt(token, ref frame))
 											{
 												// Not numeric!
-												parser.ReportError("Expected model frame index, but got '" + token + "'");
+												parser.ReportError("Expected model frame index, but got \"" + token + "\"");
 												return false;
 											}
 
@@ -457,14 +457,12 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 			// Bail out when got errors or no models are used
 			if(Array.IndexOf(modelsUsed, true) == -1)
 			{
-				parser.ReportError("No models are used by '" + classname + "'");
+				parser.ReportError("No models are used by \"" + classname + "\"");
 				return false;
 			}
 			
 			// Classname is set in ModeldefParser
-			ModelData = new ModelData();
-			ModelData.InheritActorPitch = inheritactorpitch;
-			ModelData.InheritActorRoll = inheritactorroll;
+			ModelData = new ModelData { InheritActorPitch = inheritactorpitch, InheritActorRoll = inheritactorroll };
 			Matrix moffset = Matrix.Translation(offset.Y, -offset.X, offset.Z); // Things are complicated in GZDoom...
 			Matrix mrotation = Matrix.RotationY(-Angle2D.DegToRad(rollOffset)) * Matrix.RotationX(-Angle2D.DegToRad(pitchOffset)) * Matrix.RotationZ(Angle2D.DegToRad(angleOffset));
 			ModelData.SetTransform(mrotation, moffset, scale);
@@ -482,7 +480,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 			if(ModelData.ModelNames.Count == 0)
 			{
-				parser.ReportError("'" + classname + "' has no models");
+				parser.ReportError("\"" + classname + "\" has no models");
 				return false;
 			}
 
diff --git a/Source/Core/GZBuilder/GZDoom/ScriptTypeParserSE.cs b/Source/Core/GZBuilder/GZDoom/ScriptTypeParserSE.cs
index 1928108bc..7e5ab16e0 100644
--- a/Source/Core/GZBuilder/GZDoom/ScriptTypeParserSE.cs
+++ b/Source/Core/GZBuilder/GZDoom/ScriptTypeParserSE.cs
@@ -1,7 +1,8 @@
-#region ================== Namespaces
+
+#region ================== Namespaces
 
-using System.IO;
 using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.ZDoom;
 
 #endregion
@@ -11,17 +12,25 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 {
 	internal sealed class ScriptTypeParserSE :ZDTextParser 
 	{
-		private ScriptType scriptType;
-		internal ScriptType ScriptType { get { return scriptType; } }
+		internal override ScriptType ScriptType { get { return scripttype; } }
+		private ScriptType scripttype;
 
 		internal ScriptTypeParserSE() 
 		{
-			scriptType = ScriptType.UNKNOWN;
+			scripttype = ScriptType.UNKNOWN;
 		}
 		
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors) 
+		public override bool Parse(TextResourceData data, bool clearerrors) 
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -41,7 +50,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						
 						if(token == "{") 
 						{
-							scriptType = ScriptType.MODELDEF;
+							scripttype = ScriptType.MODELDEF;
 							return true;
 						}
 
@@ -57,7 +66,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 						
 						if(token == "{") 
 						{
-							scriptType = ScriptType.ACS;
+							scripttype = ScriptType.ACS;
 							return true;
 						}
 
@@ -72,7 +81,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 						if(token == ":" || token == "{" || token == "REPLACES") 
 						{
-							scriptType = ScriptType.DECORATE;
+							scripttype = ScriptType.DECORATE;
 							return true;
 						}
 
@@ -81,7 +90,7 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 						if(token == "{") 
 						{
-							scriptType = ScriptType.DECORATE;
+							scripttype = ScriptType.DECORATE;
 							return true;
 						}
 					}
@@ -90,10 +99,5 @@ namespace CodeImp.DoomBuilder.GZBuilder.GZDoom
 
 			return false;
 		}
-
-		protected override string GetLanguageType()
-		{
-			return "SCRIPT TYPE CHECKER";
-		}
 	}
 }
diff --git a/Source/Core/General/Launcher.cs b/Source/Core/General/Launcher.cs
index 53724eae2..57a5db403 100644
--- a/Source/Core/General/Launcher.cs
+++ b/Source/Core/General/Launcher.cs
@@ -22,6 +22,7 @@ using CodeImp.DoomBuilder.Data;
 using System.Diagnostics;
 using CodeImp.DoomBuilder.Actions;
 using System.Windows.Forms;
+using CodeImp.DoomBuilder.VisualModes;
 using CodeImp.DoomBuilder.Windows;
 using CodeImp.DoomBuilder.IO;
 using CodeImp.DoomBuilder.Editing;
@@ -346,12 +347,13 @@ namespace CodeImp.DoomBuilder
 					General.MainWindow.DisplayStatus(StatusType.Warning, "Unable to test the map due to script errors.");
 				}
 			}
+			General.Plugins.OnMapSaveEnd(SavePurpose.Testing);
 		}
 
 		//mxd
 		private void TestingFinished() 
 		{
-			//Done
+			// Done
 			TimeSpan deltatime = TimeSpan.FromTicks(process.ExitTime.Ticks - process.StartTime.Ticks);
 			process = null;
 			General.WriteLogLine("Test program has finished.");
@@ -361,9 +363,22 @@ namespace CodeImp.DoomBuilder
 			// Clean up temp file
 			CleanTempFile(General.Map);
 
-			General.Plugins.OnMapSaveEnd(SavePurpose.Testing);
+			if(General.Map != null)
+			{
+				// Device reset may be needed...
+				if(General.Editing.Mode is ClassicMode)
+				{
+					General.Map.Graphics.Reset();
+					General.MainWindow.RedrawDisplay();
+				}
+				else if(General.Editing.Mode is VisualMode)
+				{
+					//General.MainWindow.StopExclusiveMouseInput();
+					//General.MainWindow.StartExclusiveMouseInput();
+				}
+			}
+
 			General.MainWindow.FocusDisplay();
-			if(General.Editing.Mode is ClassicMode) General.MainWindow.RedrawDisplay();
 		}
 
 		//mxd
diff --git a/Source/Core/General/MapManager.cs b/Source/Core/General/MapManager.cs
index 0ef62a232..d8bf95180 100644
--- a/Source/Core/General/MapManager.cs
+++ b/Source/Core/General/MapManager.cs
@@ -130,7 +130,6 @@ namespace CodeImp.DoomBuilder
 		//mxd. Scripts
 		internal Dictionary<string, ScriptItem> NamedScripts { get { return namedscripts; } }
 		internal Dictionary<int, ScriptItem> NumberedScripts { get { return numberedscripts; } }
-		internal HashSet<string> ScriptIncludes { get { return scriptincludes; } }
 
 		public ViewMode ViewMode { get { return renderer2d.ViewMode; } }
 
@@ -159,8 +158,8 @@ namespace CodeImp.DoomBuilder
 
 			//mxd
 			numberedscripts = new Dictionary<int, ScriptItem>();
-			namedscripts = new Dictionary<string, ScriptItem>();
-			scriptincludes = new HashSet<string>();
+			namedscripts = new Dictionary<string, ScriptItem>(StringComparer.OrdinalIgnoreCase);
+			scriptincludes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 		}
 
 		// Disposer
@@ -255,7 +254,7 @@ namespace CodeImp.DoomBuilder
 			this.changed = false;
 			this.options = options;
 
-			General.WriteLogLine("Creating new map '" + options.CurrentName + "' with configuration '" + options.ConfigFile + "'");
+			General.WriteLogLine("Creating new map \"" + options.CurrentName + "\" with configuration \"" + options.ConfigFile + "\"");
 
 			// Initiate graphics
 			General.WriteLogLine("Initializing graphics device...");
@@ -350,7 +349,7 @@ namespace CodeImp.DoomBuilder
 			this.maploading = true; //mxd
 			this.options = options;
 
-			General.WriteLogLine("Opening map '" + options.CurrentName + "' with configuration '" + options.ConfigFile + "'");
+			General.WriteLogLine("Opening map \"" + options.CurrentName + "\" with configuration \"" + options.ConfigFile + "\"");
 
 			// Initiate graphics
 			General.WriteLogLine("Initializing graphics device...");
@@ -572,7 +571,7 @@ namespace CodeImp.DoomBuilder
 					{
 						General.WriteLogLine("Initializing map format interface " + config.FormatInterface + "...");
 						io = MapSetIO.Create(config.FormatInterface, tempwad, this);
-						General.WriteLogLine("Restoring map from '" + backuppath + "'...");
+						General.WriteLogLine("Restoring map from \"" + backuppath + "\"...");
 						
 #if DEBUG
 						// Restore map
@@ -838,7 +837,7 @@ namespace CodeImp.DoomBuilder
 						int mapindex = wad.FindLumpIndex(origmapname);
 						wad.Dispose();
 
-						if(mapindex != -1 && MessageBox.Show(General.MainWindow, "Target file already contains map '" + origmapname + "'\nDo you want to replace it?", "Map already exists!", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) 
+						if(mapindex != -1 && MessageBox.Show(General.MainWindow, "Target file already contains map \"" + origmapname + "\"\nDo you want to replace it?", "Map already exists!", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) 
 						{
 							data.Resume();
 							General.WriteLogLine("Map saving cancelled...");
@@ -934,7 +933,7 @@ namespace CodeImp.DoomBuilder
 			{
 				if(purpose != SavePurpose.IntoFile) 
 				{
-					General.WriteLogLine("Changing map name from '" + options.PreviousName + "' to '" + options.CurrentName + "'");
+					General.WriteLogLine("Changing map name from \"" + options.PreviousName + "\" to \"" + options.CurrentName + "\"");
 
 					// Find the map header in target
 					int index = targetwad.FindLumpIndex(options.PreviousName);
@@ -1048,7 +1047,7 @@ namespace CodeImp.DoomBuilder
 					File.WriteAllBytes(backuppath, SharpCompressHelper.CompressStream(ms).ToArray());
 
 					// Log it
-					General.WriteLogLine("Map backup saved to '" + backuppath + "'");
+					General.WriteLogLine("Map backup saved to \"" + backuppath + "\"");
 				}
 				else
 				{
@@ -1915,7 +1914,7 @@ namespace CodeImp.DoomBuilder
 			//mxd. Boilerplate
 			if(!config.MapLumps.ContainsKey(lumpname))
 			{
-				General.ShowErrorMessage("Unable to compile lump '" + lumpname + "'. This lump is not defined in the current game configuration.", MessageBoxButtons.OK);
+				General.ShowErrorMessage("Unable to compile lump \"" + lumpname + "\". This lump is not defined in the current game configuration.", MessageBoxButtons.OK);
 				return false;
 			}
 			
@@ -1930,7 +1929,7 @@ namespace CodeImp.DoomBuilder
 				//mxd. More boilderplate
 				if(!General.CompiledScriptConfigs.ContainsKey(General.Map.Options.ScriptCompiler))
 				{
-					General.ShowErrorMessage("Unable to compile lump '" + lumpname + "'. Unable to find required script compiler configuration ('" + General.Map.Options.ScriptCompiler + "').", MessageBoxButtons.OK);
+					General.ShowErrorMessage("Unable to compile lump \"" + lumpname + "\". Unable to find required script compiler configuration (\"" + General.Map.Options.ScriptCompiler + "\").", MessageBoxButtons.OK);
 					return false;
 				}
 				
@@ -1945,7 +1944,7 @@ namespace CodeImp.DoomBuilder
 			// Find the lump
 			if(lumpname == CONFIG_MAP_HEADER) reallumpname = TEMP_MAP_HEADER;
 			Lump lump = tempwad.FindLump(reallumpname);
-			if(lump == null) throw new Exception("No such lump in temporary wad file '" + reallumpname + "'.");
+			if(lump == null) throw new Exception("No such lump in temporary wad file \"" + reallumpname + "\".");
 
 			// Determine source file
 			string sourcefile = (filepathname.Length > 0 ? filepathname : tempwad.Filename);
@@ -1995,7 +1994,7 @@ namespace CodeImp.DoomBuilder
 			//mxd
 			if(scriptconfig.ScriptType == ScriptType.ACS)
 			{
-				compiler.Includes = General.Map.ScriptIncludes;
+				compiler.Includes = scriptincludes;
 				compiler.CopyIncludesToWorkingDirectory = true;
 			}
 
@@ -2074,8 +2073,8 @@ namespace CodeImp.DoomBuilder
 				//INFO: also, error.linenumber is zero-based
 				foreach(CompilerError error in compilererrors)
 				{
-					General.ErrorLogger.Add(ErrorType.Error, "ACS error in '" + (error.filename.StartsWith("?") ? error.filename.Replace("?", string.Empty) : error.filename)
-						+ (error.linenumber != CompilerError.NO_LINE_NUMBER ? "', line " + (error.linenumber + 1) : "'") 
+					General.ErrorLogger.Add(ErrorType.Error, "ACS error in \"" + (error.filename.StartsWith("?") ? error.filename.Substring(1) : error.filename)
+						+ (error.linenumber != CompilerError.NO_LINE_NUMBER ? "\", line " + (error.linenumber + 1) : "\"") 
 						+ ". " + error.description + ".");
 				}
 			}
@@ -2090,6 +2089,7 @@ namespace CodeImp.DoomBuilder
 			List<ScriptItem> numberedscriptslist = new List<ScriptItem>();
 			List<string> scripincludeslist = new List<string>();
 			List<CompilerError> compilererrors = new List<CompilerError>();
+			General.Map.Data.TextResources[ScriptType.ACS] = new HashSet<TextResource>();
 
 			// Load the script lumps
 			foreach(MapLumpInfo maplumpinfo in config.MapLumps.Values) 
@@ -2103,7 +2103,7 @@ namespace CodeImp.DoomBuilder
 						//mxd. More boilderplate
 						if(!General.CompiledScriptConfigs.ContainsKey(General.Map.Options.ScriptCompiler))
 						{
-							compilererrors.Add(new CompilerError("Unable to compile lump '" + maplumpinfo.Name + "'. Unable to find required script compiler configuration ('" + General.Map.Options.ScriptCompiler + "')."));
+							compilererrors.Add(new CompilerError("Unable to compile lump \"" + maplumpinfo.Name + "\". Unable to find required script compiler configuration (\"" + General.Map.Options.ScriptCompiler + "\")."));
 							return compilererrors;
 						}
 
@@ -2119,15 +2119,37 @@ namespace CodeImp.DoomBuilder
 					if(stream != null && stream.Length > 0 && scriptconfig != null && scriptconfig.Compiler != null)
 					{
 						// Get script names
-						AcsParserSE parser = new AcsParserSE { OnInclude = (se, path, includetype) => se.Parse(General.Map.Data.LoadFile(path), path, true, includetype, false) };
+						AcsParserSE parser = new AcsParserSE
+						{
+							IsMapScriptsLump = true,
+							IgnoreErrors = true,
+							OnInclude = delegate(AcsParserSE se, string includefile, AcsParserSE.IncludeType includetype)
+							{
+								TextResourceData includedata = General.Map.Data.LoadFile(includefile);
+								if(includedata != null)
+								{
+									includedata.Trackable = true;
+									se.Parse(includedata, true, includetype, false);
+								}
+								else
+								{
+									General.ErrorLogger.Add(ErrorType.Error, "Unable to find include file \"" + includefile + "\"");
+								}
+							}
+						};
 
 						//INFO: CompileLump() prepends lumpname with "?" to distinguish between temporary files and files compiled in place
-						if(parser.Parse(stream, "?SCRIPTS", scriptconfig.Compiler.Files, true, AcsParserSE.IncludeType.NONE, false))
+						DataLocation location = new DataLocation { location = tempwad.Filename, type = DataLocation.RESOURCE_WAD };
+						TextResourceData data = new TextResourceData(stream, location, "?SCRIPTS", false);
+						if(parser.Parse(data, scriptconfig.Compiler.Files, true, AcsParserSE.IncludeType.NONE, false))
 						{
 							// Add them to arrays
 							namedscriptslist.AddRange(parser.NamedScripts);
 							numberedscriptslist.AddRange(parser.NumberedScripts);
 							scripincludeslist.AddRange(parser.Includes);
+
+							// Add to text resource list
+							General.Map.Data.TextResources[parser.ScriptType].UnionWith(parser.TextResources.Values);
 						}
 						
 						// Check for errors
@@ -2297,7 +2319,7 @@ namespace CodeImp.DoomBuilder
 			data = new DataManager();
 			if(!string.IsNullOrEmpty(filepathname)) 
 			{
-				DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, false, false, false);
+				DataLocation maplocation = new DataLocation(DataLocation.RESOURCE_WAD, filepathname, options.StrictPatches, false, false);
 				data.Load(configinfo.Resources, options.Resources, maplocation);
 			}
 			else 
@@ -2346,7 +2368,7 @@ namespace CodeImp.DoomBuilder
 				this.options = optionsform.Options;
 
 				// Load new game configuration
-				General.WriteLogLine("Loading game configuration '" + options.ConfigFile + "'...");
+				General.WriteLogLine("Loading game configuration \"" + options.ConfigFile + "\"...");
 				configinfo = General.GetConfigurationInfo(options.ConfigFile);
 				Type oldiotype = io.GetType(); //mxd
 
diff --git a/Source/Core/IO/DirectoryFilesList.cs b/Source/Core/IO/DirectoryFilesList.cs
index 0c99b628e..4c39ff127 100644
--- a/Source/Core/IO/DirectoryFilesList.cs
+++ b/Source/Core/IO/DirectoryFilesList.cs
@@ -166,19 +166,34 @@ namespace CodeImp.DoomBuilder.IO
 		}
 
 		//mxd. This returns a list of all files that are in the given path and which names starts with title
-		public List<string> GetAllFilesWhichTitleStartsWith(string path, string title) 
+		public List<string> GetAllFilesWhichTitleStartsWith(string path, string title)
 		{
 			path = CorrectPath(path).ToLowerInvariant();
 			title = title.ToLowerInvariant();
 			List<string> files = new List<string>(entries.Length);
 			for(int i = 0; i < entries.Length; i++)
-			{
-				if((entries[i].path == path) && (entries[i].filetitle.StartsWith(title))) 
+				if((entries[i].path.StartsWith(path)) && (entries[i].filetitle.StartsWith(title)))
 					files.Add(entries[i].filepathname);
-			}
 			return files;
 		}
 
+		//mxd. This returns a list of all files that are in the given path and which names starts with title
+		public List<string> GetAllFilesWhichTitleStartsWith(string path, string title, bool subdirectories) 
+		{
+			if(subdirectories)
+				return GetAllFilesWhichTitleStartsWith(path, title);
+			else
+			{
+				path = CorrectPath(path).ToLowerInvariant();
+				title = title.ToLowerInvariant();
+				List<string> files = new List<string>(entries.Length);
+				for(int i = 0; i < entries.Length; i++)
+					if((entries[i].path == path) && (entries[i].filetitle.StartsWith(title)))
+						files.Add(entries[i].filepathname);
+				return files;
+			}
+		}
+
 		// This returns a list of all files that are in the given path and subdirectories and have the given extension
 		public List<string> GetAllFiles(string path, string extension)
 		{
diff --git a/Source/Core/Resources/UDMF_UI.cfg b/Source/Core/Resources/UDMF_UI.cfg
index 1e2a56992..7598f4732 100644
--- a/Source/Core/Resources/UDMF_UI.cfg
+++ b/Source/Core/Resources/UDMF_UI.cfg
@@ -58,6 +58,12 @@ uifields
 		desaturation = 1;
 		soundsequence = 2;
 		comment = 2;
+		damageamount = 0;
+		damagetype = 2;
+		damageinterval = 0;
+		leakiness = 0;
+		floorterrain = 2;
+		ceilingterrain = 2;
 	}
 	
 	thing
diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs
index 6f55aea75..c465a5a1b 100644
--- a/Source/Core/Windows/MainForm.cs
+++ b/Source/Core/Windows/MainForm.cs
@@ -123,7 +123,6 @@ namespace CodeImp.DoomBuilder.Windows
 		private bool shift, ctrl, alt;
 		private MouseButtons mousebuttons;
 		private MouseInput mouseinput;
-		private Rectangle originalclip;
 		private bool mouseexclusive;
 		private int mouseexclusivebreaklevel;
 		
@@ -1246,7 +1245,7 @@ namespace CodeImp.DoomBuilder.Windows
 				mouseinput = new MouseInput(this);
 
 				// Lock and hide the mouse in window
-				originalclip = Cursor.Clip;
+				Cursor.Position = display.PointToScreen(new Point(display.ClientSize.Width / 2, display.ClientSize.Height / 2)); //mxd
 				Cursor.Clip = display.RectangleToScreen(display.ClientRectangle);
 				Cursor.Hide();
 			}
@@ -1263,7 +1262,7 @@ namespace CodeImp.DoomBuilder.Windows
 				mouseinput = null;
 
 				// Release and show the mouse
-				Cursor.Clip = originalclip;
+				Cursor.Clip = Rectangle.Empty;
 				Cursor.Position = display.PointToScreen(new Point(display.ClientSize.Width / 2, display.ClientSize.Height / 2));
 				Cursor.Show();
 			}
diff --git a/Source/Core/Windows/SectorEditFormUDMF.Designer.cs b/Source/Core/Windows/SectorEditFormUDMF.Designer.cs
index f2d7f589b..f12e4c948 100644
--- a/Source/Core/Windows/SectorEditFormUDMF.Designer.cs
+++ b/Source/Core/Windows/SectorEditFormUDMF.Designer.cs
@@ -40,6 +40,10 @@
 			System.Windows.Forms.Label label15;
 			System.Windows.Forms.Label label6;
 			System.Windows.Forms.Label label5;
+			System.Windows.Forms.Label label17;
+			System.Windows.Forms.Label label16;
+			System.Windows.Forms.Label label18;
+			System.Windows.Forms.Label label19;
 			this.tagsselector = new CodeImp.DoomBuilder.GZBuilder.Controls.TagsSelector();
 			this.soundsequence = new System.Windows.Forms.ComboBox();
 			this.resetsoundsequence = new System.Windows.Forms.Button();
@@ -57,10 +61,19 @@
 			this.floorheight = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
 			this.tabs = new System.Windows.Forms.TabControl();
 			this.tabproperties = new System.Windows.Forms.TabPage();
+			this.groupdamage = new System.Windows.Forms.GroupBox();
+			this.leakiness = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.damageinterval = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.resetdamagetype = new System.Windows.Forms.Button();
+			this.damageamount = new CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox();
+			this.damagetype = new System.Windows.Forms.ComboBox();
 			this.groupBox3 = new System.Windows.Forms.GroupBox();
 			this.flags = new CodeImp.DoomBuilder.Controls.CheckboxArrayControl();
 			this.tabSurfaces = new System.Windows.Forms.TabPage();
 			this.groupBox2 = new System.Windows.Forms.GroupBox();
+			this.resetfloorterrain = new System.Windows.Forms.Button();
+			this.label3 = new System.Windows.Forms.Label();
+			this.floorterrain = new System.Windows.Forms.ComboBox();
 			this.resetfloorlight = new System.Windows.Forms.Button();
 			this.labelFloorOffsets = new System.Windows.Forms.Label();
 			this.labelFloorScale = new System.Windows.Forms.Label();
@@ -79,6 +92,9 @@
 			this.floorOffsets = new CodeImp.DoomBuilder.GZBuilder.Controls.PairedFieldsControl();
 			this.floortex = new CodeImp.DoomBuilder.Controls.FlatSelectorControl();
 			this.groupBox1 = new System.Windows.Forms.GroupBox();
+			this.resetceilterrain = new System.Windows.Forms.Button();
+			this.label7 = new System.Windows.Forms.Label();
+			this.ceilterrain = new System.Windows.Forms.ComboBox();
 			this.resetceillight = new System.Windows.Forms.Button();
 			this.labelCeilOffsets = new System.Windows.Forms.Label();
 			this.labelCeilScale = new System.Windows.Forms.Label();
@@ -119,11 +135,16 @@
 			label15 = new System.Windows.Forms.Label();
 			label6 = new System.Windows.Forms.Label();
 			label5 = new System.Windows.Forms.Label();
+			label17 = new System.Windows.Forms.Label();
+			label16 = new System.Windows.Forms.Label();
+			label18 = new System.Windows.Forms.Label();
+			label19 = new System.Windows.Forms.Label();
 			groupaction.SuspendLayout();
 			groupeffect.SuspendLayout();
 			groupfloorceiling.SuspendLayout();
 			this.tabs.SuspendLayout();
 			this.tabproperties.SuspendLayout();
+			this.groupdamage.SuspendLayout();
 			this.groupBox3.SuspendLayout();
 			this.tabSurfaces.SuspendLayout();
 			this.groupBox2.SuspendLayout();
@@ -140,16 +161,16 @@
 			groupaction.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
 						| System.Windows.Forms.AnchorStyles.Right)));
 			groupaction.Controls.Add(this.tagsselector);
-			groupaction.Location = new System.Drawing.Point(7, 327);
+			groupaction.Location = new System.Drawing.Point(7, 394);
 			groupaction.Name = "groupaction";
-			groupaction.Size = new System.Drawing.Size(490, 85);
+			groupaction.Size = new System.Drawing.Size(490, 80);
 			groupaction.TabIndex = 2;
 			groupaction.TabStop = false;
 			groupaction.Text = " Identification ";
 			// 
 			// tagsselector
 			// 
-			this.tagsselector.Location = new System.Drawing.Point(6, 21);
+			this.tagsselector.Location = new System.Drawing.Point(6, 15);
 			this.tagsselector.Name = "tagsselector";
 			this.tagsselector.Size = new System.Drawing.Size(478, 60);
 			this.tagsselector.TabIndex = 0;
@@ -172,9 +193,9 @@
 			groupeffect.Controls.Add(this.browseeffect);
 			groupeffect.Controls.Add(this.effect);
 			groupeffect.Controls.Add(label8);
-			groupeffect.Location = new System.Drawing.Point(7, 160);
+			groupeffect.Location = new System.Drawing.Point(7, 256);
 			groupeffect.Name = "groupeffect";
-			groupeffect.Size = new System.Drawing.Size(490, 161);
+			groupeffect.Size = new System.Drawing.Size(490, 132);
 			groupeffect.TabIndex = 1;
 			groupeffect.TabStop = false;
 			groupeffect.Text = " Effects ";
@@ -182,9 +203,9 @@
 			// soundsequence
 			// 
 			this.soundsequence.FormattingEnabled = true;
-			this.soundsequence.Location = new System.Drawing.Point(125, 54);
+			this.soundsequence.Location = new System.Drawing.Point(283, 48);
 			this.soundsequence.Name = "soundsequence";
-			this.soundsequence.Size = new System.Drawing.Size(325, 21);
+			this.soundsequence.Size = new System.Drawing.Size(167, 21);
 			this.soundsequence.TabIndex = 32;
 			this.soundsequence.MouseDown += new System.Windows.Forms.MouseEventHandler(this.soundsequence_MouseDown);
 			this.soundsequence.TextChanged += new System.EventHandler(this.soundsequence_TextChanged);
@@ -192,7 +213,7 @@
 			// resetsoundsequence
 			// 
 			this.resetsoundsequence.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
-			this.resetsoundsequence.Location = new System.Drawing.Point(456, 52);
+			this.resetsoundsequence.Location = new System.Drawing.Point(456, 46);
 			this.resetsoundsequence.Name = "resetsoundsequence";
 			this.resetsoundsequence.Size = new System.Drawing.Size(28, 25);
 			this.resetsoundsequence.TabIndex = 31;
@@ -205,7 +226,7 @@
 			this.fadeColor.DefaultValue = 0;
 			this.fadeColor.Field = "fadecolor";
 			this.fadeColor.Label = "Fade:";
-			this.fadeColor.Location = new System.Drawing.Point(241, 104);
+			this.fadeColor.Location = new System.Drawing.Point(236, 99);
 			this.fadeColor.Name = "fadeColor";
 			this.fadeColor.Size = new System.Drawing.Size(207, 31);
 			this.fadeColor.TabIndex = 30;
@@ -216,7 +237,7 @@
 			this.lightColor.DefaultValue = 16777215;
 			this.lightColor.Field = "lightcolor";
 			this.lightColor.Label = "Light:";
-			this.lightColor.Location = new System.Drawing.Point(241, 77);
+			this.lightColor.Location = new System.Drawing.Point(236, 71);
 			this.lightColor.Name = "lightColor";
 			this.lightColor.Size = new System.Drawing.Size(207, 29);
 			this.lightColor.TabIndex = 29;
@@ -233,7 +254,7 @@
 			this.brightness.ButtonStepSmall = 1F;
 			this.brightness.ButtonStepsUseModifierKeys = true;
 			this.brightness.ButtonStepsWrapAround = false;
-			this.brightness.Location = new System.Drawing.Point(125, 79);
+			this.brightness.Location = new System.Drawing.Point(89, 46);
 			this.brightness.Name = "brightness";
 			this.brightness.Size = new System.Drawing.Size(81, 24);
 			this.brightness.StepValues = null;
@@ -251,7 +272,7 @@
 			this.desaturation.ButtonStepSmall = 0.01F;
 			this.desaturation.ButtonStepsUseModifierKeys = true;
 			this.desaturation.ButtonStepsWrapAround = false;
-			this.desaturation.Location = new System.Drawing.Point(125, 131);
+			this.desaturation.Location = new System.Drawing.Point(89, 102);
 			this.desaturation.Name = "desaturation";
 			this.desaturation.Size = new System.Drawing.Size(81, 24);
 			this.desaturation.StepValues = null;
@@ -260,7 +281,7 @@
 			// label14
 			// 
 			label14.AutoSize = true;
-			label14.Location = new System.Drawing.Point(27, 58);
+			label14.Location = new System.Drawing.Point(186, 52);
 			label14.Name = "label14";
 			label14.Size = new System.Drawing.Size(91, 13);
 			label14.TabIndex = 3;
@@ -269,7 +290,7 @@
 			// label9
 			// 
 			label9.AutoSize = true;
-			label9.Location = new System.Drawing.Point(57, 84);
+			label9.Location = new System.Drawing.Point(21, 51);
 			label9.Name = "label9";
 			label9.Size = new System.Drawing.Size(59, 13);
 			label9.TabIndex = 2;
@@ -277,7 +298,7 @@
 			// 
 			// label13
 			// 
-			label13.Location = new System.Drawing.Point(45, 136);
+			label13.Location = new System.Drawing.Point(9, 107);
 			label13.Name = "label13";
 			label13.Size = new System.Drawing.Size(74, 14);
 			label13.TabIndex = 27;
@@ -295,7 +316,7 @@
 			this.gravity.ButtonStepSmall = 0.01F;
 			this.gravity.ButtonStepsUseModifierKeys = true;
 			this.gravity.ButtonStepsWrapAround = false;
-			this.gravity.Location = new System.Drawing.Point(125, 105);
+			this.gravity.Location = new System.Drawing.Point(89, 74);
 			this.gravity.Name = "gravity";
 			this.gravity.Size = new System.Drawing.Size(81, 24);
 			this.gravity.StepValues = null;
@@ -303,7 +324,7 @@
 			// 
 			// label2
 			// 
-			label2.Location = new System.Drawing.Point(45, 110);
+			label2.Location = new System.Drawing.Point(9, 79);
 			label2.Name = "label2";
 			label2.Size = new System.Drawing.Size(74, 14);
 			label2.TabIndex = 23;
@@ -313,7 +334,7 @@
 			// browseeffect
 			// 
 			this.browseeffect.Image = global::CodeImp.DoomBuilder.Properties.Resources.List;
-			this.browseeffect.Location = new System.Drawing.Point(456, 26);
+			this.browseeffect.Location = new System.Drawing.Point(456, 16);
 			this.browseeffect.Name = "browseeffect";
 			this.browseeffect.Size = new System.Drawing.Size(28, 25);
 			this.browseeffect.TabIndex = 1;
@@ -328,16 +349,16 @@
 			this.effect.Empty = false;
 			this.effect.GeneralizedCategories = null;
 			this.effect.GeneralizedOptions = null;
-			this.effect.Location = new System.Drawing.Point(68, 28);
+			this.effect.Location = new System.Drawing.Point(89, 18);
 			this.effect.Name = "effect";
-			this.effect.Size = new System.Drawing.Size(382, 21);
+			this.effect.Size = new System.Drawing.Size(361, 21);
 			this.effect.TabIndex = 0;
 			this.effect.Value = 402;
 			// 
 			// label8
 			// 
 			label8.AutoSize = true;
-			label8.Location = new System.Drawing.Point(17, 31);
+			label8.Location = new System.Drawing.Point(35, 22);
 			label8.Name = "label8";
 			label8.Size = new System.Drawing.Size(45, 13);
 			label8.TabIndex = 0;
@@ -355,9 +376,9 @@
 			groupfloorceiling.Controls.Add(this.sectorheightlabel);
 			groupfloorceiling.Controls.Add(this.sectorheight);
 			groupfloorceiling.Controls.Add(this.floorheight);
-			groupfloorceiling.Location = new System.Drawing.Point(7, 6);
+			groupfloorceiling.Location = new System.Drawing.Point(7, 112);
 			groupfloorceiling.Name = "groupfloorceiling";
-			groupfloorceiling.Size = new System.Drawing.Size(188, 148);
+			groupfloorceiling.Size = new System.Drawing.Size(187, 138);
 			groupfloorceiling.TabIndex = 0;
 			groupfloorceiling.TabStop = false;
 			groupfloorceiling.Text = " Heights ";
@@ -366,7 +387,7 @@
 			// 
 			label15.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
 			label15.ForeColor = System.Drawing.SystemColors.HotTrack;
-			label15.Location = new System.Drawing.Point(9, 84);
+			label15.Location = new System.Drawing.Point(9, 83);
 			label15.Name = "label15";
 			label15.Size = new System.Drawing.Size(74, 14);
 			label15.TabIndex = 27;
@@ -377,7 +398,7 @@
 			// 
 			// label6
 			// 
-			label6.Location = new System.Drawing.Point(9, 24);
+			label6.Location = new System.Drawing.Point(9, 23);
 			label6.Name = "label6";
 			label6.Size = new System.Drawing.Size(74, 14);
 			label6.TabIndex = 19;
@@ -386,7 +407,7 @@
 			// 
 			// label5
 			// 
-			label5.Location = new System.Drawing.Point(9, 54);
+			label5.Location = new System.Drawing.Point(9, 53);
 			label5.Name = "label5";
 			label5.Size = new System.Drawing.Size(74, 14);
 			label5.TabIndex = 17;
@@ -404,9 +425,9 @@
 			this.heightoffset.ButtonStepSmall = 1F;
 			this.heightoffset.ButtonStepsUseModifierKeys = true;
 			this.heightoffset.ButtonStepsWrapAround = false;
-			this.heightoffset.Location = new System.Drawing.Point(89, 79);
+			this.heightoffset.Location = new System.Drawing.Point(89, 78);
 			this.heightoffset.Name = "heightoffset";
-			this.heightoffset.Size = new System.Drawing.Size(88, 24);
+			this.heightoffset.Size = new System.Drawing.Size(81, 24);
 			this.heightoffset.StepValues = null;
 			this.heightoffset.TabIndex = 28;
 			this.heightoffset.WhenTextChanged += new System.EventHandler(this.heightoffset_WhenTextChanged);
@@ -422,16 +443,16 @@
 			this.ceilingheight.ButtonStepSmall = 1F;
 			this.ceilingheight.ButtonStepsUseModifierKeys = true;
 			this.ceilingheight.ButtonStepsWrapAround = false;
-			this.ceilingheight.Location = new System.Drawing.Point(89, 19);
+			this.ceilingheight.Location = new System.Drawing.Point(89, 18);
 			this.ceilingheight.Name = "ceilingheight";
-			this.ceilingheight.Size = new System.Drawing.Size(88, 24);
+			this.ceilingheight.Size = new System.Drawing.Size(81, 24);
 			this.ceilingheight.StepValues = null;
 			this.ceilingheight.TabIndex = 22;
 			this.ceilingheight.WhenTextChanged += new System.EventHandler(this.ceilingheight_WhenTextChanged);
 			// 
 			// sectorheightlabel
 			// 
-			this.sectorheightlabel.Location = new System.Drawing.Point(9, 114);
+			this.sectorheightlabel.Location = new System.Drawing.Point(9, 113);
 			this.sectorheightlabel.Name = "sectorheightlabel";
 			this.sectorheightlabel.Size = new System.Drawing.Size(74, 14);
 			this.sectorheightlabel.TabIndex = 20;
@@ -441,7 +462,7 @@
 			// sectorheight
 			// 
 			this.sectorheight.AutoSize = true;
-			this.sectorheight.Location = new System.Drawing.Point(90, 114);
+			this.sectorheight.Location = new System.Drawing.Point(89, 114);
 			this.sectorheight.Name = "sectorheight";
 			this.sectorheight.Size = new System.Drawing.Size(13, 13);
 			this.sectorheight.TabIndex = 21;
@@ -458,13 +479,60 @@
 			this.floorheight.ButtonStepSmall = 1F;
 			this.floorheight.ButtonStepsUseModifierKeys = true;
 			this.floorheight.ButtonStepsWrapAround = false;
-			this.floorheight.Location = new System.Drawing.Point(89, 49);
+			this.floorheight.Location = new System.Drawing.Point(89, 48);
 			this.floorheight.Name = "floorheight";
-			this.floorheight.Size = new System.Drawing.Size(88, 24);
+			this.floorheight.Size = new System.Drawing.Size(81, 24);
 			this.floorheight.StepValues = null;
 			this.floorheight.TabIndex = 23;
 			this.floorheight.WhenTextChanged += new System.EventHandler(this.floorheight_WhenTextChanged);
 			// 
+			// label17
+			// 
+			label17.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			label17.ForeColor = System.Drawing.SystemColors.HotTrack;
+			label17.Location = new System.Drawing.Point(10, 51);
+			label17.Name = "label17";
+			label17.Size = new System.Drawing.Size(74, 14);
+			label17.TabIndex = 29;
+			label17.Text = "Ammount:";
+			label17.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			this.tooltip.SetToolTip(label17, "Amount of damage inflicted by this sector.\r\nIf this is 0, all other damage proper" +
+					"ties will be ignored.\r\nSetting this to a negative value will create a healing se" +
+					"ctor.");
+			// 
+			// label16
+			// 
+			label16.Location = new System.Drawing.Point(10, 23);
+			label16.Name = "label16";
+			label16.Size = new System.Drawing.Size(74, 14);
+			label16.TabIndex = 36;
+			label16.Text = "Type:";
+			label16.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			// 
+			// label18
+			// 
+			label18.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			label18.ForeColor = System.Drawing.SystemColors.HotTrack;
+			label18.Location = new System.Drawing.Point(10, 81);
+			label18.Name = "label18";
+			label18.Size = new System.Drawing.Size(74, 14);
+			label18.TabIndex = 37;
+			label18.Text = "Interval:";
+			label18.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			this.tooltip.SetToolTip(label18, "Interval in tics between damage application.");
+			// 
+			// label19
+			// 
+			label19.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+			label19.ForeColor = System.Drawing.SystemColors.HotTrack;
+			label19.Location = new System.Drawing.Point(10, 111);
+			label19.Name = "label19";
+			label19.Size = new System.Drawing.Size(74, 14);
+			label19.TabIndex = 39;
+			label19.Text = "Leakiness:";
+			label19.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			this.tooltip.SetToolTip(label19, "Probability of leaking through radiation suit\r\n(0 = never, 256 = always)");
+			// 
 			// tabs
 			// 
 			this.tabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
@@ -481,11 +549,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(511, 445);
+			this.tabs.Size = new System.Drawing.Size(511, 504);
 			this.tabs.TabIndex = 1;
 			// 
 			// tabproperties
 			// 
+			this.tabproperties.Controls.Add(this.groupdamage);
 			this.tabproperties.Controls.Add(this.groupBox3);
 			this.tabproperties.Controls.Add(groupaction);
 			this.tabproperties.Controls.Add(groupeffect);
@@ -494,30 +563,123 @@
 			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(503, 419);
+			this.tabproperties.Size = new System.Drawing.Size(503, 478);
 			this.tabproperties.TabIndex = 0;
 			this.tabproperties.Text = "Properties";
 			this.tabproperties.UseVisualStyleBackColor = true;
 			// 
+			// groupdamage
+			// 
+			this.groupdamage.Controls.Add(label19);
+			this.groupdamage.Controls.Add(this.leakiness);
+			this.groupdamage.Controls.Add(label18);
+			this.groupdamage.Controls.Add(this.damageinterval);
+			this.groupdamage.Controls.Add(label16);
+			this.groupdamage.Controls.Add(label17);
+			this.groupdamage.Controls.Add(this.resetdamagetype);
+			this.groupdamage.Controls.Add(this.damageamount);
+			this.groupdamage.Controls.Add(this.damagetype);
+			this.groupdamage.Location = new System.Drawing.Point(200, 112);
+			this.groupdamage.Name = "groupdamage";
+			this.groupdamage.Size = new System.Drawing.Size(297, 136);
+			this.groupdamage.TabIndex = 4;
+			this.groupdamage.TabStop = false;
+			this.groupdamage.Text = " Sector damage ";
+			// 
+			// leakiness
+			// 
+			this.leakiness.AllowDecimal = false;
+			this.leakiness.AllowNegative = false;
+			this.leakiness.AllowRelative = true;
+			this.leakiness.ButtonStep = 8;
+			this.leakiness.ButtonStepBig = 16F;
+			this.leakiness.ButtonStepFloat = 1F;
+			this.leakiness.ButtonStepSmall = 1F;
+			this.leakiness.ButtonStepsUseModifierKeys = true;
+			this.leakiness.ButtonStepsWrapAround = false;
+			this.leakiness.Location = new System.Drawing.Point(90, 106);
+			this.leakiness.Name = "leakiness";
+			this.leakiness.Size = new System.Drawing.Size(81, 24);
+			this.leakiness.StepValues = null;
+			this.leakiness.TabIndex = 40;
+			// 
+			// damageinterval
+			// 
+			this.damageinterval.AllowDecimal = false;
+			this.damageinterval.AllowNegative = false;
+			this.damageinterval.AllowRelative = true;
+			this.damageinterval.ButtonStep = 8;
+			this.damageinterval.ButtonStepBig = 16F;
+			this.damageinterval.ButtonStepFloat = 1F;
+			this.damageinterval.ButtonStepSmall = 1F;
+			this.damageinterval.ButtonStepsUseModifierKeys = true;
+			this.damageinterval.ButtonStepsWrapAround = false;
+			this.damageinterval.Location = new System.Drawing.Point(90, 76);
+			this.damageinterval.Name = "damageinterval";
+			this.damageinterval.Size = new System.Drawing.Size(81, 24);
+			this.damageinterval.StepValues = null;
+			this.damageinterval.TabIndex = 38;
+			// 
+			// resetdamagetype
+			// 
+			this.resetdamagetype.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
+			this.resetdamagetype.Location = new System.Drawing.Point(263, 17);
+			this.resetdamagetype.Name = "resetdamagetype";
+			this.resetdamagetype.Size = new System.Drawing.Size(28, 25);
+			this.resetdamagetype.TabIndex = 35;
+			this.resetdamagetype.Text = " ";
+			this.resetdamagetype.UseVisualStyleBackColor = true;
+			this.resetdamagetype.Click += new System.EventHandler(this.resetdamagetype_Click);
+			// 
+			// damageamount
+			// 
+			this.damageamount.AllowDecimal = false;
+			this.damageamount.AllowNegative = true;
+			this.damageamount.AllowRelative = true;
+			this.damageamount.ButtonStep = 8;
+			this.damageamount.ButtonStepBig = 16F;
+			this.damageamount.ButtonStepFloat = 1F;
+			this.damageamount.ButtonStepSmall = 1F;
+			this.damageamount.ButtonStepsUseModifierKeys = true;
+			this.damageamount.ButtonStepsWrapAround = false;
+			this.damageamount.Location = new System.Drawing.Point(90, 46);
+			this.damageamount.Name = "damageamount";
+			this.damageamount.Size = new System.Drawing.Size(81, 24);
+			this.damageamount.StepValues = null;
+			this.damageamount.TabIndex = 30;
+			// 
+			// damagetype
+			// 
+			this.damagetype.FormattingEnabled = true;
+			this.damagetype.Location = new System.Drawing.Point(90, 19);
+			this.damagetype.Name = "damagetype";
+			this.damagetype.Size = new System.Drawing.Size(167, 21);
+			this.damagetype.TabIndex = 34;
+			this.damagetype.MouseDown += new System.Windows.Forms.MouseEventHandler(this.damagetype_MouseDown);
+			this.damagetype.TextChanged += new System.EventHandler(this.damagetype_TextChanged);
+			// 
 			// groupBox3
 			// 
 			this.groupBox3.Controls.Add(this.flags);
-			this.groupBox3.Location = new System.Drawing.Point(201, 6);
+			this.groupBox3.Location = new System.Drawing.Point(7, 6);
 			this.groupBox3.Name = "groupBox3";
-			this.groupBox3.Size = new System.Drawing.Size(296, 148);
+			this.groupBox3.Size = new System.Drawing.Size(490, 100);
 			this.groupBox3.TabIndex = 3;
 			this.groupBox3.TabStop = false;
 			this.groupBox3.Text = " Flags ";
 			// 
 			// 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 = 1;
+			this.flags.Columns = 2;
 			this.flags.Location = new System.Drawing.Point(15, 21);
 			this.flags.Name = "flags";
-			this.flags.Size = new System.Drawing.Size(275, 121);
+			this.flags.Size = new System.Drawing.Size(469, 73);
 			this.flags.TabIndex = 5;
-			this.flags.VerticalSpacing = 2;
+			this.flags.VerticalSpacing = 1;
 			// 
 			// tabSurfaces
 			// 
@@ -526,7 +688,7 @@
 			this.tabSurfaces.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
 			this.tabSurfaces.Location = new System.Drawing.Point(4, 22);
 			this.tabSurfaces.Name = "tabSurfaces";
-			this.tabSurfaces.Size = new System.Drawing.Size(503, 419);
+			this.tabSurfaces.Size = new System.Drawing.Size(503, 478);
 			this.tabSurfaces.TabIndex = 2;
 			this.tabSurfaces.Text = "Surfaces";
 			this.tabSurfaces.UseVisualStyleBackColor = true;
@@ -535,6 +697,9 @@
 			// 
 			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.resetfloorterrain);
+			this.groupBox2.Controls.Add(this.label3);
+			this.groupBox2.Controls.Add(this.floorterrain);
 			this.groupBox2.Controls.Add(this.resetfloorlight);
 			this.groupBox2.Controls.Add(this.labelFloorOffsets);
 			this.groupBox2.Controls.Add(this.labelFloorScale);
@@ -552,17 +717,48 @@
 			this.groupBox2.Controls.Add(this.floorScale);
 			this.groupBox2.Controls.Add(this.floorOffsets);
 			this.groupBox2.Controls.Add(this.floortex);
-			this.groupBox2.Location = new System.Drawing.Point(3, 212);
+			this.groupBox2.Location = new System.Drawing.Point(3, 239);
 			this.groupBox2.Name = "groupBox2";
-			this.groupBox2.Size = new System.Drawing.Size(497, 203);
+			this.groupBox2.Size = new System.Drawing.Size(497, 230);
 			this.groupBox2.TabIndex = 55;
 			this.groupBox2.TabStop = false;
 			this.groupBox2.Text = " Floor ";
 			// 
+			// resetfloorterrain
+			// 
+			this.resetfloorterrain.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
+			this.resetfloorterrain.Location = new System.Drawing.Point(246, 110);
+			this.resetfloorterrain.Name = "resetfloorterrain";
+			this.resetfloorterrain.Size = new System.Drawing.Size(23, 23);
+			this.resetfloorterrain.TabIndex = 66;
+			this.resetfloorterrain.Text = " ";
+			this.resetfloorterrain.UseVisualStyleBackColor = true;
+			this.resetfloorterrain.Click += new System.EventHandler(this.resetfloorterrain_Click);
+			// 
+			// label3
+			// 
+			this.label3.Location = new System.Drawing.Point(24, 114);
+			this.label3.Name = "label3";
+			this.label3.Size = new System.Drawing.Size(80, 14);
+			this.label3.TabIndex = 62;
+			this.label3.Tag = "";
+			this.label3.Text = "Terrain:";
+			this.label3.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			// 
+			// floorterrain
+			// 
+			this.floorterrain.FormattingEnabled = true;
+			this.floorterrain.Location = new System.Drawing.Point(113, 111);
+			this.floorterrain.Name = "floorterrain";
+			this.floorterrain.Size = new System.Drawing.Size(130, 21);
+			this.floorterrain.TabIndex = 61;
+			this.floorterrain.MouseDown += new System.Windows.Forms.MouseEventHandler(this.floorterrain_MouseDown);
+			this.floorterrain.TextChanged += new System.EventHandler(this.floorterrain_TextChanged);
+			// 
 			// resetfloorlight
 			// 
 			this.resetfloorlight.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
-			this.resetfloorlight.Location = new System.Drawing.Point(246, 114);
+			this.resetfloorlight.Location = new System.Drawing.Point(246, 138);
 			this.resetfloorlight.Name = "resetfloorlight";
 			this.resetfloorlight.Size = new System.Drawing.Size(23, 23);
 			this.resetfloorlight.TabIndex = 60;
@@ -593,7 +789,7 @@
 			// cbUseFloorLineAngles
 			// 
 			this.cbUseFloorLineAngles.AutoSize = true;
-			this.cbUseFloorLineAngles.Location = new System.Drawing.Point(181, 148);
+			this.cbUseFloorLineAngles.Location = new System.Drawing.Point(181, 172);
 			this.cbUseFloorLineAngles.Name = "cbUseFloorLineAngles";
 			this.cbUseFloorLineAngles.Size = new System.Drawing.Size(113, 17);
 			this.cbUseFloorLineAngles.TabIndex = 57;
@@ -604,9 +800,9 @@
 			// 
 			// floorAngleControl
 			// 
-			this.floorAngleControl.Angle = -450;
+			this.floorAngleControl.Angle = -1350;
 			this.floorAngleControl.AngleOffset = 90;
-			this.floorAngleControl.Location = new System.Drawing.Point(6, 132);
+			this.floorAngleControl.Location = new System.Drawing.Point(6, 156);
 			this.floorAngleControl.Name = "floorAngleControl";
 			this.floorAngleControl.Size = new System.Drawing.Size(44, 44);
 			this.floorAngleControl.TabIndex = 56;
@@ -624,7 +820,7 @@
 			// 
 			// label10
 			// 
-			this.label10.Location = new System.Drawing.Point(24, 178);
+			this.label10.Location = new System.Drawing.Point(24, 202);
 			this.label10.Name = "label10";
 			this.label10.Size = new System.Drawing.Size(80, 14);
 			this.label10.TabIndex = 52;
@@ -643,7 +839,7 @@
 			this.floorAlpha.ButtonStepSmall = 0.01F;
 			this.floorAlpha.ButtonStepsUseModifierKeys = true;
 			this.floorAlpha.ButtonStepsWrapAround = false;
-			this.floorAlpha.Location = new System.Drawing.Point(113, 173);
+			this.floorAlpha.Location = new System.Drawing.Point(113, 197);
 			this.floorAlpha.Name = "floorAlpha";
 			this.floorAlpha.Size = new System.Drawing.Size(62, 24);
 			this.floorAlpha.StepValues = null;
@@ -652,7 +848,7 @@
 			// 
 			// label11
 			// 
-			this.label11.Location = new System.Drawing.Point(24, 148);
+			this.label11.Location = new System.Drawing.Point(24, 172);
 			this.label11.Name = "label11";
 			this.label11.Size = new System.Drawing.Size(80, 14);
 			this.label11.TabIndex = 50;
@@ -671,7 +867,7 @@
 			this.floorRotation.ButtonStepSmall = 0.1F;
 			this.floorRotation.ButtonStepsUseModifierKeys = true;
 			this.floorRotation.ButtonStepsWrapAround = false;
-			this.floorRotation.Location = new System.Drawing.Point(113, 143);
+			this.floorRotation.Location = new System.Drawing.Point(113, 167);
 			this.floorRotation.Name = "floorRotation";
 			this.floorRotation.Size = new System.Drawing.Size(62, 24);
 			this.floorRotation.StepValues = null;
@@ -682,7 +878,7 @@
 			// floorLightAbsolute
 			// 
 			this.floorLightAbsolute.AutoSize = true;
-			this.floorLightAbsolute.Location = new System.Drawing.Point(181, 118);
+			this.floorLightAbsolute.Location = new System.Drawing.Point(181, 142);
 			this.floorLightAbsolute.Name = "floorLightAbsolute";
 			this.floorLightAbsolute.Size = new System.Drawing.Size(67, 17);
 			this.floorLightAbsolute.TabIndex = 49;
@@ -692,7 +888,7 @@
 			// 
 			// label12
 			// 
-			this.label12.Location = new System.Drawing.Point(24, 118);
+			this.label12.Location = new System.Drawing.Point(24, 142);
 			this.label12.Name = "label12";
 			this.label12.Size = new System.Drawing.Size(80, 14);
 			this.label12.TabIndex = 47;
@@ -711,7 +907,7 @@
 			this.floorBrightness.ButtonStepSmall = 1F;
 			this.floorBrightness.ButtonStepsUseModifierKeys = true;
 			this.floorBrightness.ButtonStepsWrapAround = false;
-			this.floorBrightness.Location = new System.Drawing.Point(113, 113);
+			this.floorBrightness.Location = new System.Drawing.Point(113, 137);
 			this.floorBrightness.Name = "floorBrightness";
 			this.floorBrightness.Size = new System.Drawing.Size(62, 24);
 			this.floorBrightness.StepValues = null;
@@ -771,7 +967,7 @@
 			this.floortex.Location = new System.Drawing.Point(300, 13);
 			this.floortex.MultipleTextures = false;
 			this.floortex.Name = "floortex";
-			this.floortex.Size = new System.Drawing.Size(190, 184);
+			this.floortex.Size = new System.Drawing.Size(190, 204);
 			this.floortex.TabIndex = 15;
 			this.floortex.TextureName = "";
 			this.floortex.UsePreviews = false;
@@ -781,6 +977,9 @@
 			// 
 			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.resetceilterrain);
+			this.groupBox1.Controls.Add(this.label7);
+			this.groupBox1.Controls.Add(this.ceilterrain);
 			this.groupBox1.Controls.Add(this.resetceillight);
 			this.groupBox1.Controls.Add(this.labelCeilOffsets);
 			this.groupBox1.Controls.Add(this.labelCeilScale);
@@ -800,15 +999,46 @@
 			this.groupBox1.Controls.Add(this.ceilingtex);
 			this.groupBox1.Location = new System.Drawing.Point(3, 3);
 			this.groupBox1.Name = "groupBox1";
-			this.groupBox1.Size = new System.Drawing.Size(497, 203);
+			this.groupBox1.Size = new System.Drawing.Size(497, 230);
 			this.groupBox1.TabIndex = 0;
 			this.groupBox1.TabStop = false;
 			this.groupBox1.Text = " Ceiling ";
 			// 
+			// resetceilterrain
+			// 
+			this.resetceilterrain.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
+			this.resetceilterrain.Location = new System.Drawing.Point(246, 110);
+			this.resetceilterrain.Name = "resetceilterrain";
+			this.resetceilterrain.Size = new System.Drawing.Size(23, 23);
+			this.resetceilterrain.TabIndex = 65;
+			this.resetceilterrain.Text = " ";
+			this.resetceilterrain.UseVisualStyleBackColor = true;
+			this.resetceilterrain.Click += new System.EventHandler(this.resetceilterrain_Click);
+			// 
+			// label7
+			// 
+			this.label7.Location = new System.Drawing.Point(24, 114);
+			this.label7.Name = "label7";
+			this.label7.Size = new System.Drawing.Size(80, 14);
+			this.label7.TabIndex = 64;
+			this.label7.Tag = "";
+			this.label7.Text = "Terrain:";
+			this.label7.TextAlign = System.Drawing.ContentAlignment.TopRight;
+			// 
+			// ceilterrain
+			// 
+			this.ceilterrain.FormattingEnabled = true;
+			this.ceilterrain.Location = new System.Drawing.Point(113, 111);
+			this.ceilterrain.Name = "ceilterrain";
+			this.ceilterrain.Size = new System.Drawing.Size(130, 21);
+			this.ceilterrain.TabIndex = 63;
+			this.ceilterrain.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ceilterrain_MouseDown);
+			this.ceilterrain.TextChanged += new System.EventHandler(this.ceilterrain_TextChanged);
+			// 
 			// resetceillight
 			// 
 			this.resetceillight.Image = global::CodeImp.DoomBuilder.Properties.Resources.Reset;
-			this.resetceillight.Location = new System.Drawing.Point(246, 114);
+			this.resetceillight.Location = new System.Drawing.Point(246, 138);
 			this.resetceillight.Name = "resetceillight";
 			this.resetceillight.Size = new System.Drawing.Size(23, 23);
 			this.resetceillight.TabIndex = 62;
@@ -839,7 +1069,7 @@
 			// cbUseCeilLineAngles
 			// 
 			this.cbUseCeilLineAngles.AutoSize = true;
-			this.cbUseCeilLineAngles.Location = new System.Drawing.Point(181, 148);
+			this.cbUseCeilLineAngles.Location = new System.Drawing.Point(181, 172);
 			this.cbUseCeilLineAngles.Name = "cbUseCeilLineAngles";
 			this.cbUseCeilLineAngles.Size = new System.Drawing.Size(113, 17);
 			this.cbUseCeilLineAngles.TabIndex = 56;
@@ -850,9 +1080,9 @@
 			// 
 			// ceilAngleControl
 			// 
-			this.ceilAngleControl.Angle = -450;
+			this.ceilAngleControl.Angle = -1350;
 			this.ceilAngleControl.AngleOffset = 90;
-			this.ceilAngleControl.Location = new System.Drawing.Point(6, 132);
+			this.ceilAngleControl.Location = new System.Drawing.Point(6, 156);
 			this.ceilAngleControl.Name = "ceilAngleControl";
 			this.ceilAngleControl.Size = new System.Drawing.Size(44, 44);
 			this.ceilAngleControl.TabIndex = 55;
@@ -870,7 +1100,7 @@
 			// 
 			// label4
 			// 
-			this.label4.Location = new System.Drawing.Point(24, 178);
+			this.label4.Location = new System.Drawing.Point(24, 202);
 			this.label4.Name = "label4";
 			this.label4.Size = new System.Drawing.Size(80, 14);
 			this.label4.TabIndex = 52;
@@ -889,7 +1119,7 @@
 			this.ceilAlpha.ButtonStepSmall = 0.01F;
 			this.ceilAlpha.ButtonStepsUseModifierKeys = true;
 			this.ceilAlpha.ButtonStepsWrapAround = false;
-			this.ceilAlpha.Location = new System.Drawing.Point(113, 173);
+			this.ceilAlpha.Location = new System.Drawing.Point(113, 197);
 			this.ceilAlpha.Name = "ceilAlpha";
 			this.ceilAlpha.Size = new System.Drawing.Size(62, 24);
 			this.ceilAlpha.StepValues = null;
@@ -898,7 +1128,7 @@
 			// 
 			// label1
 			// 
-			this.label1.Location = new System.Drawing.Point(24, 148);
+			this.label1.Location = new System.Drawing.Point(24, 172);
 			this.label1.Name = "label1";
 			this.label1.Size = new System.Drawing.Size(80, 14);
 			this.label1.TabIndex = 50;
@@ -917,7 +1147,7 @@
 			this.ceilRotation.ButtonStepSmall = 0.1F;
 			this.ceilRotation.ButtonStepsUseModifierKeys = true;
 			this.ceilRotation.ButtonStepsWrapAround = true;
-			this.ceilRotation.Location = new System.Drawing.Point(113, 143);
+			this.ceilRotation.Location = new System.Drawing.Point(113, 167);
 			this.ceilRotation.Name = "ceilRotation";
 			this.ceilRotation.Size = new System.Drawing.Size(62, 24);
 			this.ceilRotation.StepValues = null;
@@ -928,7 +1158,7 @@
 			// ceilLightAbsolute
 			// 
 			this.ceilLightAbsolute.AutoSize = true;
-			this.ceilLightAbsolute.Location = new System.Drawing.Point(181, 118);
+			this.ceilLightAbsolute.Location = new System.Drawing.Point(181, 142);
 			this.ceilLightAbsolute.Name = "ceilLightAbsolute";
 			this.ceilLightAbsolute.Size = new System.Drawing.Size(67, 17);
 			this.ceilLightAbsolute.TabIndex = 49;
@@ -939,7 +1169,7 @@
 			// 
 			// labelLightFront
 			// 
-			this.labelLightFront.Location = new System.Drawing.Point(24, 118);
+			this.labelLightFront.Location = new System.Drawing.Point(24, 142);
 			this.labelLightFront.Name = "labelLightFront";
 			this.labelLightFront.Size = new System.Drawing.Size(80, 14);
 			this.labelLightFront.TabIndex = 47;
@@ -958,7 +1188,7 @@
 			this.ceilBrightness.ButtonStepSmall = 1F;
 			this.ceilBrightness.ButtonStepsUseModifierKeys = true;
 			this.ceilBrightness.ButtonStepsWrapAround = false;
-			this.ceilBrightness.Location = new System.Drawing.Point(113, 113);
+			this.ceilBrightness.Location = new System.Drawing.Point(113, 137);
 			this.ceilBrightness.Name = "ceilBrightness";
 			this.ceilBrightness.Size = new System.Drawing.Size(62, 24);
 			this.ceilBrightness.StepValues = null;
@@ -1018,7 +1248,7 @@
 			this.ceilingtex.Location = new System.Drawing.Point(300, 13);
 			this.ceilingtex.MultipleTextures = false;
 			this.ceilingtex.Name = "ceilingtex";
-			this.ceilingtex.Size = new System.Drawing.Size(190, 184);
+			this.ceilingtex.Size = new System.Drawing.Size(190, 204);
 			this.ceilingtex.TabIndex = 15;
 			this.ceilingtex.TextureName = "";
 			this.ceilingtex.UsePreviews = false;
@@ -1031,7 +1261,7 @@
 			this.tabslopes.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
 			this.tabslopes.Location = new System.Drawing.Point(4, 22);
 			this.tabslopes.Name = "tabslopes";
-			this.tabslopes.Size = new System.Drawing.Size(503, 419);
+			this.tabslopes.Size = new System.Drawing.Size(503, 478);
 			this.tabslopes.TabIndex = 3;
 			this.tabslopes.Text = "Slopes";
 			this.tabslopes.UseVisualStyleBackColor = true;
@@ -1039,9 +1269,9 @@
 			// groupBox5
 			// 
 			this.groupBox5.Controls.Add(this.floorslopecontrol);
-			this.groupBox5.Location = new System.Drawing.Point(3, 212);
+			this.groupBox5.Location = new System.Drawing.Point(3, 239);
 			this.groupBox5.Name = "groupBox5";
-			this.groupBox5.Size = new System.Drawing.Size(497, 203);
+			this.groupBox5.Size = new System.Drawing.Size(497, 230);
 			this.groupBox5.TabIndex = 1;
 			this.groupBox5.TabStop = false;
 			this.groupBox5.Text = " Floor ";
@@ -1063,7 +1293,7 @@
 			this.groupBox4.Controls.Add(this.ceilingslopecontrol);
 			this.groupBox4.Location = new System.Drawing.Point(3, 3);
 			this.groupBox4.Name = "groupBox4";
-			this.groupBox4.Size = new System.Drawing.Size(497, 203);
+			this.groupBox4.Size = new System.Drawing.Size(497, 230);
 			this.groupBox4.TabIndex = 0;
 			this.groupBox4.TabStop = false;
 			this.groupBox4.Text = " Ceiling ";
@@ -1086,7 +1316,7 @@
 			this.tabcomment.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
 			this.tabcomment.Location = new System.Drawing.Point(4, 22);
 			this.tabcomment.Name = "tabcomment";
-			this.tabcomment.Size = new System.Drawing.Size(503, 419);
+			this.tabcomment.Size = new System.Drawing.Size(503, 478);
 			this.tabcomment.TabIndex = 4;
 			this.tabcomment.Text = "Comment";
 			this.tabcomment.UseVisualStyleBackColor = true;
@@ -1095,7 +1325,7 @@
 			// 
 			this.commenteditor.Location = new System.Drawing.Point(3, 3);
 			this.commenteditor.Name = "commenteditor";
-			this.commenteditor.Size = new System.Drawing.Size(497, 413);
+			this.commenteditor.Size = new System.Drawing.Size(497, 472);
 			this.commenteditor.TabIndex = 0;
 			// 
 			// tabcustom
@@ -1105,7 +1335,7 @@
 			this.tabcustom.Location = new System.Drawing.Point(4, 22);
 			this.tabcustom.Name = "tabcustom";
 			this.tabcustom.Padding = new System.Windows.Forms.Padding(3);
-			this.tabcustom.Size = new System.Drawing.Size(503, 419);
+			this.tabcustom.Size = new System.Drawing.Size(503, 478);
 			this.tabcustom.TabIndex = 1;
 			this.tabcustom.Text = "Custom";
 			this.tabcustom.UseVisualStyleBackColor = true;
@@ -1124,7 +1354,8 @@
 			this.fieldslist.Name = "fieldslist";
 			this.fieldslist.PropertyColumnVisible = true;
 			this.fieldslist.PropertyColumnWidth = 150;
-			this.fieldslist.Size = new System.Drawing.Size(481, 398);
+			this.fieldslist.ShowFixedFields = true;
+			this.fieldslist.Size = new System.Drawing.Size(481, 444);
 			this.fieldslist.TabIndex = 1;
 			this.fieldslist.TypeColumnVisible = true;
 			this.fieldslist.TypeColumnWidth = 100;
@@ -1134,7 +1365,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(407, 459);
+			this.cancel.Location = new System.Drawing.Point(407, 518);
 			this.cancel.Name = "cancel";
 			this.cancel.Size = new System.Drawing.Size(112, 25);
 			this.cancel.TabIndex = 4;
@@ -1145,7 +1376,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(289, 459);
+			this.apply.Location = new System.Drawing.Point(289, 518);
 			this.apply.Name = "apply";
 			this.apply.Size = new System.Drawing.Size(112, 25);
 			this.apply.TabIndex = 3;
@@ -1166,7 +1397,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(531, 489);
+			this.ClientSize = new System.Drawing.Size(531, 548);
 			this.Controls.Add(this.cancel);
 			this.Controls.Add(this.apply);
 			this.Controls.Add(this.tabs);
@@ -1188,6 +1419,7 @@
 			groupfloorceiling.PerformLayout();
 			this.tabs.ResumeLayout(false);
 			this.tabproperties.ResumeLayout(false);
+			this.groupdamage.ResumeLayout(false);
 			this.groupBox3.ResumeLayout(false);
 			this.tabSurfaces.ResumeLayout(false);
 			this.groupBox2.ResumeLayout(false);
@@ -1273,5 +1505,17 @@
 		private CodeImp.DoomBuilder.GZBuilder.Controls.TagsSelector tagsselector;
 		private System.Windows.Forms.Button resetfloorlight;
 		private System.Windows.Forms.Button resetceillight;
+		private System.Windows.Forms.Label label3;
+		private System.Windows.Forms.ComboBox floorterrain;
+		private System.Windows.Forms.Label label7;
+		private System.Windows.Forms.ComboBox ceilterrain;
+		private System.Windows.Forms.GroupBox groupdamage;
+		private CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox leakiness;
+		private CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox damageinterval;
+		private System.Windows.Forms.Button resetdamagetype;
+		private CodeImp.DoomBuilder.Controls.ButtonsNumericTextbox damageamount;
+		private System.Windows.Forms.ComboBox damagetype;
+		private System.Windows.Forms.Button resetfloorterrain;
+		private System.Windows.Forms.Button resetceilterrain;
 	}
 }
\ No newline at end of file
diff --git a/Source/Core/Windows/SectorEditFormUDMF.cs b/Source/Core/Windows/SectorEditFormUDMF.cs
index 014991094..f8ac77250 100644
--- a/Source/Core/Windows/SectorEditFormUDMF.cs
+++ b/Source/Core/Windows/SectorEditFormUDMF.cs
@@ -24,6 +24,8 @@ namespace CodeImp.DoomBuilder.Windows
 		#region ================== Constants
 
 		private const string NO_SOUND_SEQUENCE = "None"; //mxd
+		private const string NO_TERRAIN = "Default"; //mxd
+		private const string NO_DAMAGETYPE = "None"; //mxd
 
 		#endregion
 
@@ -206,7 +208,17 @@ namespace CodeImp.DoomBuilder.Windows
 
 			// Fill sound sequences list
 			soundsequence.Items.Add(NO_SOUND_SEQUENCE);
-			soundsequence.Items.AddRange(General.Map.Data.SoundSequences.ToArray());
+			soundsequence.Items.AddRange(General.Map.Data.SoundSequences);
+
+			// Fill damagetype list
+			damagetype.Items.Add(NO_DAMAGETYPE);
+			damagetype.Items.AddRange(General.Map.Data.DamageTypes);
+
+			// Fill terrain type lists
+			ceilterrain.Items.Add(NO_TERRAIN);
+			ceilterrain.Items.AddRange(General.Map.Data.TerrainNames);
+			floorterrain.Items.Add(NO_TERRAIN);
+			floorterrain.Items.AddRange(General.Map.Data.TerrainNames);
 
 			// Initialize custom fields editor
 			fieldslist.Setup("sector");
@@ -308,6 +320,16 @@ namespace CodeImp.DoomBuilder.Windows
 			ceilRenderStyle.SelectedIndex = Array.IndexOf(renderstyles, sc.Fields.GetValue("renderstyleceiling", "translucent"));
 			floorRenderStyle.SelectedIndex = Array.IndexOf(renderstyles, sc.Fields.GetValue("renderstylefloor", "translucent"));
 
+			//Damage
+			damagetype.Text = sc.Fields.GetValue("damagetype", NO_DAMAGETYPE);
+			damageamount.Text = sc.Fields.GetValue("damageamount", 0).ToString();
+			damageinterval.Text = sc.Fields.GetValue("damageinterval", 32).ToString();
+			leakiness.Text = General.Clamp(sc.Fields.GetValue("leakiness", 0), 0, 256).ToString();
+
+			//Terrain
+			ceilterrain.Text = sc.Fields.GetValue("ceilingterrain", NO_TERRAIN);
+			floorterrain.Text = sc.Fields.GetValue("floorterrain", NO_TERRAIN);
+
 			//Misc
 			soundsequence.Text = sc.Fields.GetValue("soundsequence", NO_SOUND_SEQUENCE);
 			gravity.Text = sc.Fields.GetValue("gravity", 1.0f).ToString();
@@ -415,8 +437,22 @@ namespace CodeImp.DoomBuilder.Windows
 				if(floorRenderStyle.SelectedIndex > -1 && floorRenderStyle.SelectedIndex != Array.IndexOf(renderstyles, s.Fields.GetValue("renderstylefloor", "translucent")))
 					floorRenderStyle.SelectedIndex = -1;
 
+				//Damage
+				if(damagetype.SelectedIndex > -1 && s.Fields.GetValue("damagetype", NO_DAMAGETYPE) != damagetype.Text) 
+					damagetype.SelectedIndex = -1;
+				if(s.Fields.GetValue("damageamount", 0).ToString() != damageamount.Text) damageamount.Text = "";
+				if(s.Fields.GetValue("damageinterval", 32).ToString() != damageinterval.Text) damageinterval.Text = "";
+				if(s.Fields.GetValue("leakiness", 0).ToString() != leakiness.Text) leakiness.Text = "";
+
+				//Terrain
+				if(ceilterrain.SelectedIndex > -1 && s.Fields.GetValue("ceilingterrain", NO_TERRAIN) != ceilterrain.Text)
+					ceilterrain.SelectedIndex = -1;
+				if(floorterrain.SelectedIndex > -1 && s.Fields.GetValue("floorterrain", NO_TERRAIN) != floorterrain.Text)
+					floorterrain.SelectedIndex = -1;
+
 				//Misc
-				if(s.Fields.GetValue("soundsequence", NO_SOUND_SEQUENCE) != soundsequence.Text) soundsequence.Text = "";
+				if(soundsequence.SelectedIndex > -1 && s.Fields.GetValue("soundsequence", NO_SOUND_SEQUENCE) != soundsequence.Text)
+					soundsequence.SelectedIndex = -1;
 				if(s.Fields.GetValue("gravity", 1.0f).ToString() != gravity.Text) gravity.Text = "";
 				if(s.Fields.GetValue("desaturation", 0.0f).ToString() != desaturation.Text) desaturation.Text = "";
 
@@ -706,15 +742,27 @@ namespace CodeImp.DoomBuilder.Windows
 				if(rskeys != null) 
 				{
 					if(ceilRenderStyle.SelectedIndex > -1) 
-					{
 						UniFields.SetString(s.Fields, "renderstyleceiling", rskeys[ceilRenderStyle.SelectedIndex], "translucent");
-					}
 					if(floorRenderStyle.SelectedIndex > -1) 
-					{
 						UniFields.SetString(s.Fields, "renderstylefloor", rskeys[floorRenderStyle.SelectedIndex], "translucent");
-					}
 				}
 
+				//Damage
+				if(!string.IsNullOrEmpty(damagetype.Text))
+					UniFields.SetString(s.Fields, "damagetype", damagetype.Text, NO_DAMAGETYPE);
+				if(!string.IsNullOrEmpty(damageamount.Text))
+					UniFields.SetInteger(s.Fields, "damageamount", damageamount.GetResult(s.Fields.GetValue("damageamount", 0)), 0);
+				if(!string.IsNullOrEmpty(damageinterval.Text))
+					UniFields.SetInteger(s.Fields, "damageinterval", damageinterval.GetResult(s.Fields.GetValue("damageinterval", 32)), 32);
+				if(!string.IsNullOrEmpty(leakiness.Text))
+					UniFields.SetInteger(s.Fields, "leakiness", General.Clamp(leakiness.GetResult(s.Fields.GetValue("leakiness", 0)), 0, 256), 0);
+
+				//Terrain
+				if(!string.IsNullOrEmpty(ceilterrain.Text))
+					UniFields.SetString(s.Fields, "ceilingterrain", ceilterrain.Text, NO_TERRAIN);
+				if(!string.IsNullOrEmpty(floorterrain.Text))
+					UniFields.SetString(s.Fields, "floorterrain", floorterrain.Text, NO_TERRAIN);
+
 				// Misc
 				if(!string.IsNullOrEmpty(soundsequence.Text))
 					UniFields.SetString(s.Fields, "soundsequence", soundsequence.Text, NO_SOUND_SEQUENCE);
@@ -814,14 +862,61 @@ namespace CodeImp.DoomBuilder.Windows
 			floorRotation.StepValues = (cbUseFloorLineAngles.Checked ? anglesteps : null);
 		}
 
+		private void resetfloorterrain_Click(object sender, EventArgs e)
+		{
+			floorterrain.Text = NO_TERRAIN;
+		}
+
+		private void floorterrain_TextChanged(object sender, EventArgs e)
+		{
+			resetfloorterrain.Visible = (floorterrain.Text != NO_TERRAIN);
+		}
+
+		private void floorterrain_MouseDown(object sender, MouseEventArgs e)
+		{
+			if(floorterrain.Text == NO_TERRAIN) floorterrain.SelectAll();
+		}
+
+		private void resetceilterrain_Click(object sender, EventArgs e)
+		{
+			ceilterrain.Text = NO_TERRAIN;
+		}
+
+		private void ceilterrain_TextChanged(object sender, EventArgs e)
+		{
+			resetceilterrain.Visible = (ceilterrain.Text != NO_TERRAIN);
+		}
+
+		private void ceilterrain_MouseDown(object sender, MouseEventArgs e)
+		{
+			if(ceilterrain.Text == NO_TERRAIN) ceilterrain.SelectAll();
+		}
+
+		private void resetdamagetype_Click(object sender, EventArgs e)
+		{
+			damagetype.Focus();
+			damagetype.Text = NO_DAMAGETYPE;
+		}
+
+		private void damagetype_TextChanged(object sender, EventArgs e)
+		{
+			resetdamagetype.Visible = (damagetype.Text != NO_DAMAGETYPE);
+		}
+
+		private void damagetype_MouseDown(object sender, MouseEventArgs e)
+		{
+			if(damagetype.Text == NO_DAMAGETYPE) damagetype.SelectAll();
+		}
+
 		private void resetsoundsequence_Click(object sender, EventArgs e) 
 		{
+			soundsequence.Focus();
 			soundsequence.Text = NO_SOUND_SEQUENCE;
 		}
 
-		private void soundsequence_TextChanged(object sender, EventArgs e) 
+		private void soundsequence_TextChanged(object sender, EventArgs e)
 		{
-			resetsoundsequence.Enabled = (soundsequence.Text != NO_SOUND_SEQUENCE);
+			resetsoundsequence.Visible = (soundsequence.Text != NO_SOUND_SEQUENCE);
 		}
 
 		private void soundsequence_MouseDown(object sender, MouseEventArgs e) 
@@ -1419,7 +1514,7 @@ namespace CodeImp.DoomBuilder.Windows
 					return new Vector3D(0, 0, offset);
 
 				default:
-					throw new NotImplementedException("SectorEditFormUDMF.GetSectorCenter: Got unknown SlopePivotMode (" + (int)mode + ")");
+					throw new NotImplementedException("Unknown SlopePivotMode: " + (int)mode);
 			}
 		}
 
diff --git a/Source/Core/Windows/SectorEditFormUDMF.resx b/Source/Core/Windows/SectorEditFormUDMF.resx
index 2f82d0ae9..c2cb36180 100644
--- a/Source/Core/Windows/SectorEditFormUDMF.resx
+++ b/Source/Core/Windows/SectorEditFormUDMF.resx
@@ -138,12 +138,36 @@
   <metadata name="label8.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
+  <metadata name="label14.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
+  <metadata name="label9.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
+  <metadata name="label13.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
+  <metadata name="label2.GenerateMember" type="System.Boolean, mscorlib, Version=2.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">
+    <value>False</value>
+  </metadata>
   <metadata name="groupfloorceiling.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
   <metadata name="label15.GenerateMember" type="System.Boolean, mscorlib, Version=2.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">
+    <value>False</value>
+  </metadata>
+  <metadata name="label5.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
+  <metadata name="label15.GenerateMember" type="System.Boolean, mscorlib, Version=2.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">
     <value>17, 17</value>
   </metadata>
@@ -153,6 +177,18 @@
   <metadata name="label5.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>False</value>
   </metadata>
+  <metadata name="label17.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
+  <metadata name="label16.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
+  <metadata name="label18.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
+  <metadata name="label19.GenerateMember" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>False</value>
+  </metadata>
   <metadata name="tabproperties.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
@@ -162,4 +198,10 @@
   <metadata name="fieldslist.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
+  <metadata name="fieldslist.Locked" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <metadata name="tooltip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
 </root>
\ No newline at end of file
diff --git a/Source/Core/ZDoom/ActorStructure.cs b/Source/Core/ZDoom/ActorStructure.cs
index 55ad088d2..0ecce738c 100644
--- a/Source/Core/ZDoom/ActorStructure.cs
+++ b/Source/Core/ZDoom/ActorStructure.cs
@@ -117,7 +117,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 							// The next token must be the class to inherit from
 							parser.SkipWhitespace(true);
 							inheritclass = parser.StripTokenQuotes(parser.ReadToken());
-							if(string.IsNullOrEmpty(inheritclass) || parser.IsSpecialToken(inheritclass)) 
+							if(string.IsNullOrEmpty(inheritclass)) 
 							{
 								parser.ReportError("Expected class name to inherit from");
 								return;
@@ -131,7 +131,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 							// The next token must be the class to replace
 							parser.SkipWhitespace(true);
 							replaceclass = parser.StripTokenQuotes(parser.ReadToken());
-							if(string.IsNullOrEmpty(replaceclass) || parser.IsSpecialToken(replaceclass)) 
+							if(string.IsNullOrEmpty(replaceclass)) 
 							{
 								parser.ReportError("Expected class name to replace");
 								return;
@@ -302,7 +302,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 						}
 						if(uservars.ContainsKey(name))
 						{
-							parser.ReportError("User Variable \"" + name + "\" is double-defined");
+							parser.ReportError("User Variable \"" + name + "\" is double defined");
 							return;
 						}
 						if(!skipsuper && baseclass != null && baseclass.uservars.ContainsKey(name))
@@ -737,7 +737,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		//mxd. 
 		///TODO: rewrite this
-		public string FindSuitableVoxel(Dictionary<string, bool> voxels) 
+		public string FindSuitableVoxel(HashSet<string> voxels) 
 		{
 			string result = string.Empty;
 			
@@ -790,11 +790,11 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			if(!string.IsNullOrEmpty(result)) 
 			{
-				if(voxels.ContainsKey(result)) return result;
+				if(voxels.Contains(result)) return result;
 
 				// The sprite name may be incomplete. Find an existing sprite with direction.
 				foreach(string postfix in SPRITE_POSTFIXES)
-					if(voxels.ContainsKey(result + postfix)) return result + postfix;
+					if(voxels.Contains(result + postfix)) return result + postfix;
 			}
 
 
diff --git a/Source/Core/ZDoom/AnimdefsParser.cs b/Source/Core/ZDoom/AnimdefsParser.cs
index f1027051e..40eb9c7a1 100644
--- a/Source/Core/ZDoom/AnimdefsParser.cs
+++ b/Source/Core/ZDoom/AnimdefsParser.cs
@@ -2,6 +2,7 @@
 
 using System.Collections.Generic;
 using System.IO;
+using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
 
 #endregion
@@ -30,6 +31,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		#region ================== Properties
 
+		internal override ScriptType ScriptType { get { return ScriptType.ANIMDEFS; } }
+
 		public Dictionary<string, CameraTextureData> CameraTextures { get { return cameratextures; } }
 
 		#endregion
@@ -45,9 +48,17 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		#region ================== Parsing
 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			// Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true))
@@ -146,11 +157,6 @@ namespace CodeImp.DoomBuilder.ZDoom
 			return true;
 		}
 
-		protected override string GetLanguageType()
-		{
-			return "ANIMDEFS";
-		}
-
 		#endregion
 	}
 }
diff --git a/Source/Core/ZDoom/DecorateParser.cs b/Source/Core/ZDoom/DecorateParser.cs
index 1afdffe4c..d7966b99a 100644
--- a/Source/Core/ZDoom/DecorateParser.cs
+++ b/Source/Core/ZDoom/DecorateParser.cs
@@ -20,6 +20,8 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 
 #endregion
 
@@ -40,7 +42,10 @@ namespace CodeImp.DoomBuilder.ZDoom
 		#endregion
 		
 		#region ================== Variables
-		
+
+		//mxd. Script type
+		internal override ScriptType ScriptType { get { return ScriptType.DECORATE; } }
+
 		// These are actors we want to keep
 		private Dictionary<string, ActorStructure> actors;
 		
@@ -48,7 +53,10 @@ namespace CodeImp.DoomBuilder.ZDoom
 		private Dictionary<string, ActorStructure> archivedactors;
 
 		//mxd. Includes tracking
-		private readonly HashSet<string> parsedlumps; 
+		private readonly HashSet<string> parsedlumps;
+
+		//mxd. Custom damagetypes
+		private readonly HashSet<string> damagetypes;
 
 		//mxd. Disposing. Is that really needed?..
 		private bool isdisposed;
@@ -77,6 +85,11 @@ namespace CodeImp.DoomBuilder.ZDoom
 		/// </summary>
 		internal Dictionary<string, ActorStructure> AllActorsByClass { get { return archivedactors; } }
 
+		/// <summary>
+		/// mxd. Custom DamageTypes (http://zdoom.org/wiki/Damage_types).
+		/// </summary>
+		public IEnumerable<string> DamageTypes { get { return damagetypes; } }
+
 		#endregion
 		
 		#region ================== Constructor / Disposer
@@ -92,6 +105,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 			actors = new Dictionary<string, ActorStructure>(StringComparer.OrdinalIgnoreCase);
 			archivedactors = new Dictionary<string, ActorStructure>(StringComparer.OrdinalIgnoreCase);
 			parsedlumps = new HashSet<string>(StringComparer.OrdinalIgnoreCase); //mxd
+			damagetypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); //mxd
 		}
 		
 		// Disposer
@@ -116,14 +130,24 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		// This parses the given decorate stream
 		// Returns false on errors
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 			
 			// Keep local data
 			Stream localstream = datastream;
 			string localsourcename = sourcename;
 			BinaryReader localreader = datareader;
+			DataLocation locallocation = datalocation; //mxd
+			string localtextresourcepath = textresourcepath; //mxd
 			
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true))
@@ -153,11 +177,14 @@ namespace CodeImp.DoomBuilder.ZDoom
 								if(GetArchivedActorByName(actor.ReplacesClass) != null)
 									archivedactors[actor.ReplacesClass.ToLowerInvariant()] = actor;
 								else
-									LogWarning("Unable to find '" + actor.ReplacesClass + "' class to replace, while parsing '" + actor.ClassName + "'");
+									LogWarning("Unable to find \"" + actor.ReplacesClass + "\" class to replace, while parsing \"" + actor.ClassName + "\"");
 							
 								if(actor.CheckActorSupported() && GetActorByName(actor.ReplacesClass) != null)
 									actors[actor.ReplacesClass.ToLowerInvariant()] = actor;
 							}
+
+							//mxd. Add to current text resource
+							if(!textresources[textresourcepath].Entries.Contains(actor.ClassName)) textresources[textresourcepath].Entries.Add(actor.ClassName);
 						}
 						break;
 
@@ -206,7 +233,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 							//mxd. Already parsed?
 							if(parsedlumps.Contains(filename))
 							{
-								ReportError("Already parsed '" + filename + "'. Check your include directives");
+								ReportError("Already parsed \"" + filename + "\". Check your include directives");
 								return false;
 							}
 
@@ -223,9 +250,36 @@ namespace CodeImp.DoomBuilder.ZDoom
 							datastream = localstream;
 							datareader = localreader;
 							sourcename = localsourcename;
+							datalocation = locallocation; //mxd
+							textresourcepath = localtextresourcepath; //mxd
 						}
 						break;
 
+						case "damagetype": //mxd
+							// Get DamageType name
+							SkipWhitespace(true);
+							string damagetype = StripTokenQuotes(ReadToken(false));
+							if(string.IsNullOrEmpty(damagetype))
+							{
+								ReportError("Expected DamageType name");
+								return false;
+							}
+
+							// Next should be "{"
+							SkipWhitespace(true);
+							if(!NextTokenIs("{")) return false;
+
+							// Skip the structure
+							while(SkipWhitespace(true))
+							{
+								string t = ReadToken();
+								if(string.IsNullOrEmpty(t) || t == "}") break;
+							}
+
+							// Add to collection
+							if(!damagetypes.Contains(damagetype)) damagetypes.Add(damagetype);
+							break;
+
 						case "enum":
 						case "native":
 						case "const":
@@ -300,12 +354,6 @@ namespace CodeImp.DoomBuilder.ZDoom
 			name = name.ToLowerInvariant();
 			return (archivedactors.ContainsKey(name) ? archivedactors[name] : null);
 		}
-
-		//mxd
-		protected override string GetLanguageType()
-		{
-			return "DECORATE";
-		}
 		
 		#endregion
 	}
diff --git a/Source/Core/ZDoom/ReverbsParser.cs b/Source/Core/ZDoom/ReverbsParser.cs
index 4fed5a662..0db10e400 100644
--- a/Source/Core/ZDoom/ReverbsParser.cs
+++ b/Source/Core/ZDoom/ReverbsParser.cs
@@ -1,12 +1,15 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 
 namespace CodeImp.DoomBuilder.ZDoom
 {
 	internal sealed class ReverbsParser : ZDTextParser
 	{
+		internal override ScriptType ScriptType { get { return ScriptType.REVERBS; } }
+		
 		private readonly List<string> names;
 		private readonly List<int> firstargs;
 		private readonly List<int> secondargs;
@@ -20,9 +23,17 @@ namespace CodeImp.DoomBuilder.ZDoom
 			combinedargs = new List<int>();
 		}
 		
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -48,7 +59,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 						if(string.IsNullOrEmpty(name))
 						{
 							ReportError("Expected sound environment name");
-							break;
+							return false;
 						}
 
 						// Read first part of the ID
@@ -57,8 +68,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 						int arg1;
 						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out arg1))
 						{
-							ReportError("Expected first part of '" + name + "' sound environment ID, but got '" + token + "'");
-							break;
+							ReportError("Expected first part of \"" + name + "\" sound environment ID, but got \"" + token + "\"");
+							return false;
 						}
 
 						// Read second part of the ID
@@ -67,15 +78,15 @@ namespace CodeImp.DoomBuilder.ZDoom
 						int arg2;
 						if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out arg2)) 
 						{
-							ReportError("Expected second part of '" + name + "' sound environment ID, but got '" + token + "'");
-							break;
+							ReportError("Expected second part of \"" + name + "\" sound environment ID, but got \"" + token + "\"");
+							return false;
 						}
 
 						int combined = arg1 * 1000000 + arg2 * 1000;
 						int combinedindex = combinedargs.IndexOf(combined);
 						if(combinedindex != -1)
 						{
-							LogWarning("'" + names[combinedindex] + "' and '" + name + "' sound environments share the same ID (" + arg1 + " " + arg2 + ")");
+							LogWarning("\"" + names[combinedindex] + "\" and \"" + name + "\" sound environments share the same ID (" + arg1 + " " + arg2 + ")");
 						}
 						else
 						{
@@ -84,7 +95,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 							// Add to collections
 							if(names.Contains(name)) 
 							{
-								LogWarning("'" + name + "' sound environment is double-defined in '" + sourcefilename + "'");
+								LogWarning("\"" + name + "\" sound environment is double defined");
 								int index = names.IndexOf(name);
 								firstargs[index] = arg1;
 								secondargs[index] = arg2;
@@ -119,10 +130,5 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			return result;
 		}
-
-		protected override string GetLanguageType()
-		{
-			return "REVERBS";
-		}
 	}
 }
diff --git a/Source/Core/ZDoom/SndSeqParser.cs b/Source/Core/ZDoom/SndSeqParser.cs
index 613baed74..f2a2c96f1 100644
--- a/Source/Core/ZDoom/SndSeqParser.cs
+++ b/Source/Core/ZDoom/SndSeqParser.cs
@@ -1,7 +1,8 @@
 #region ================== Namespaces
 
 using System.Collections.Generic;
-using System.IO;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 
 #endregion
 
@@ -17,6 +18,12 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		#endregion
 
+		#region ================== Properties
+
+		internal override ScriptType ScriptType { get { return ScriptType.SNDSEQ; } }
+
+		#endregion
+
 		#region ================== Constructor
 
 		public SndSeqParser()
@@ -31,12 +38,20 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		#region ================== Parsing
 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 			
-			char[] dots = new[] { ':' };
-			char[] brace = new[] { '[' };
+			char[] dots = { ':' };
+			char[] brace = { '[' };
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true))
@@ -71,7 +86,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 			return true;
 		}
 
-		internal List<string> GetSoundSequences() 
+		internal string[] GetSoundSequences() 
 		{
 			List<string> result = new List<string>(sequencegroups.Count + sequences.Count);
 			
@@ -83,12 +98,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 			result.AddRange(sequences);
 
 			// Return the collection
-			return result;
-		}
-
-		protected override string GetLanguageType()
-		{
-			return "SNDSEQ";
+			return result.ToArray();
 		}
 
 		#endregion
diff --git a/Source/Core/ZDoom/TerrainParser.cs b/Source/Core/ZDoom/TerrainParser.cs
new file mode 100644
index 000000000..ebb335b82
--- /dev/null
+++ b/Source/Core/ZDoom/TerrainParser.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
+using CodeImp.DoomBuilder.GZBuilder.Data;
+
+namespace CodeImp.DoomBuilder.ZDoom
+{
+	internal sealed class TerrainParser : ZDTextParser
+	{
+		internal override ScriptType ScriptType { get { return ScriptType.TERRAIN; } }
+		
+		private readonly HashSet<string> terrainnames;
+		public HashSet<string> TerrainNames { get { return terrainnames; } }
+
+		public TerrainParser()
+		{
+			terrainnames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+		}
+
+		public override bool Parse(TextResourceData data, bool clearerrors)
+		{
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
+
+			// Continue until at the end of the stream
+			bool skipdefinitions = false;
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken().ToLowerInvariant();
+				if(string.IsNullOrEmpty(token)) continue;
+
+				if(skipdefinitions)
+				{
+					do
+					{
+						SkipWhitespace(true);
+						token = ReadToken();
+					} while(!string.IsNullOrEmpty(token) && token != "endif");
+
+					skipdefinitions = false;
+					continue;
+				}
+
+				switch(token)
+				{
+					case "ifheretic":
+						skipdefinitions = (General.Map.Config.GameType != GameType.HERETIC);
+						break;
+
+					case "ifhexen":
+						skipdefinitions = (General.Map.Config.GameType != GameType.HEXEN);
+						break;
+
+					case "ifstrife":
+						skipdefinitions = (General.Map.Config.GameType != GameType.STRIFE);
+						break;
+
+					case "ifdoom": // TODO: is it even a thing?..
+						skipdefinitions = (General.Map.Config.GameType != GameType.DOOM);
+						break;
+
+					case "terrain":
+						SkipWhitespace(true);
+						token = ReadToken();
+						if(string.IsNullOrEmpty(token))
+						{
+							ReportError("Expected terrain name");
+							return false;
+						}
+
+						// Add to collection
+						if(!terrainnames.Contains(token)) terrainnames.Add(token);
+						break;
+
+					case "{":
+						// Skip inner properties
+						do
+						{
+							SkipWhitespace(true);
+							token = ReadToken();
+						} while(!string.IsNullOrEmpty(token) && token != "}");
+						break;
+				}
+			}
+
+			return true;
+		}
+	}
+}
diff --git a/Source/Core/ZDoom/TexturesParser.cs b/Source/Core/ZDoom/TexturesParser.cs
index 066073453..d01c295f3 100644
--- a/Source/Core/ZDoom/TexturesParser.cs
+++ b/Source/Core/ZDoom/TexturesParser.cs
@@ -19,6 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
 
 #endregion
@@ -40,12 +41,14 @@ namespace CodeImp.DoomBuilder.ZDoom
 		private readonly Dictionary<string, TextureStructure> textures;
 		private readonly Dictionary<string, TextureStructure> flats;
 		private readonly Dictionary<string, TextureStructure> sprites;
-		private readonly char[] pathtrimchars = new[] {'_', '.', ' ', '-'}; //mxd
+		private readonly char[] pathtrimchars = {'_', '.', ' ', '-'}; //mxd
 
 		#endregion
 		
 		#region ================== Properties
 
+		internal override ScriptType ScriptType { get { return ScriptType.TEXTURES; } } //mxd
+		
 		public IEnumerable<TextureStructure> Textures { get { return textures.Values; } }
 		public IEnumerable<TextureStructure> Flats { get { return flats.Values; } }
 		public IEnumerable<TextureStructure> Sprites { get { return sprites.Values; } }
@@ -73,20 +76,27 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 		// This parses the given stream
 		// Returns false on errors
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		public override bool Parse(TextResourceData data, bool clearerrors)
 		{
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			//mxd. Make vitrual path from filename
 			string virtualpath;
-			if(sourcefilename.Contains("#")) // It's TEXTURES lump
+			if(data.LumpIndex != -1) // It's TEXTURES lump
 			{
-				virtualpath = Path.GetFileName(sourcefilename);
-				if(!string.IsNullOrEmpty(virtualpath)) virtualpath = virtualpath.Substring(0, virtualpath.LastIndexOf("#", StringComparison.Ordinal));
+				virtualpath = data.Filename;
 			}
 			else // If it's actual filename, try to use extension(s) as virtualpath
 			{
-				virtualpath = Path.GetFileName(sourcefilename);
+				virtualpath = Path.GetFileName(data.Filename);
 				if(!string.IsNullOrEmpty(virtualpath)) virtualpath = virtualpath.Substring(8).TrimStart(pathtrimchars);
 				if(!string.IsNullOrEmpty(virtualpath) && virtualpath.ToLowerInvariant() == "txt") virtualpath = string.Empty;
 				if(string.IsNullOrEmpty(virtualpath)) virtualpath = "[TEXTURES]";
@@ -240,12 +250,6 @@ namespace CodeImp.DoomBuilder.ZDoom
 			// Return true when no errors occurred
 			return (ErrorDescription == null);
 		}
-
-		//mxd
-		protected override string GetLanguageType()
-		{
-			return "TEXTURES";
-		}
 		
 		#endregion
 	}
diff --git a/Source/Core/ZDoom/VoxeldefParser.cs b/Source/Core/ZDoom/VoxeldefParser.cs
index cd2eca2aa..4c59b9502 100644
--- a/Source/Core/ZDoom/VoxeldefParser.cs
+++ b/Source/Core/ZDoom/VoxeldefParser.cs
@@ -2,7 +2,8 @@
 
 using System;
 using System.Collections.Generic;
-using System.IO;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
 using CodeImp.DoomBuilder.Geometry;
 using CodeImp.DoomBuilder.GZBuilder.Data;
 using SlimDX;
@@ -13,17 +14,28 @@ namespace CodeImp.DoomBuilder.ZDoom
 {
 	public sealed class VoxeldefParser : ZDTextParser
 	{
+		internal override ScriptType ScriptType { get { return ScriptType.VOXELDEF; } }
+		
 		private Dictionary<string, ModelData> entries; //sprite name, entry
 		internal Dictionary<string, ModelData> Entries { get { return entries; } }
 
-		public override bool Parse(Stream stream, string sourcefilename, bool clearerrors) 
+		public override bool Parse(TextResourceData data, bool clearerrors) 
 		{
 			entries = new Dictionary<string, ModelData>(StringComparer.Ordinal);
-			if(!base.Parse(stream, sourcefilename, clearerrors)) return false;
-			string prevToken = string.Empty;
+			
+			//mxd. Already parsed?
+			if(!base.AddTextResource(data))
+			{
+				if(clearerrors) ClearError();
+				return true;
+			}
+
+			// Cannot process?
+			if(!base.Parse(data, clearerrors)) return false;
 
 			List<string> spriteNames = new List<string>();
 			string modelName = string.Empty;
+			string prevToken = string.Empty;
 
 			// Continue until at the end of the stream
 			while(SkipWhitespace(true)) 
@@ -101,7 +113,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 									if(!ReadSignedFloat(token, ref angleoffset))
 									{
 										// Not numeric!
-										ReportError("Expected AngleOffset value, but got '" + token + "'");
+										ReportError("Expected AngleOffset value, but got \"" + token + "\"");
 										return false;
 									}
 								} 
@@ -113,7 +125,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 									if(!ReadSignedFloat(token, ref scale)) 
 									{
 										// Not numeric!
-										ReportError("Expected Scale value, but got '" + token + "'");
+										ReportError("Expected Scale value, but got \"" + token + "\"");
 										return false;
 									}
 								}
@@ -131,10 +143,5 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			return entries.Count > 0;
 		}
-
-		protected override string GetLanguageType()
-		{
-			return "VOXELDEF";
-		}
 	}
 }
diff --git a/Source/Core/ZDoom/ZDTextParser.cs b/Source/Core/ZDoom/ZDTextParser.cs
index 9897d84a6..f93a7abca 100644
--- a/Source/Core/ZDoom/ZDTextParser.cs
+++ b/Source/Core/ZDoom/ZDTextParser.cs
@@ -22,6 +22,7 @@ using System.Globalization;
 using System.Text;
 using System.IO;
 using CodeImp.DoomBuilder.Compilers;
+using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.Data;
 
 #endregion
@@ -49,12 +50,19 @@ namespace CodeImp.DoomBuilder.ZDoom
 		protected Stream datastream;
 		protected BinaryReader datareader;
 		protected string sourcename;
+		protected int sourcelumpindex; //mxd
+		protected DataLocation datalocation; //mxd
 		
 		// Error report
 		private int errorline;
 		private string errordesc;
 		private string errorsource;
 		private long prevstreamposition; //mxd. Text stream position storted before performing ReadToken.
+
+		//mxd. Text lumps
+		protected string textresourcepath;
+		protected readonly Dictionary<string, TextResource> textresources;
+		protected readonly HashSet<string> untrackedtextresources; 
 		
 		#endregion
 		
@@ -66,7 +74,9 @@ namespace CodeImp.DoomBuilder.ZDoom
 		public string ErrorDescription { get { return errordesc; } }
 		public string ErrorSource { get { return errorsource; } }
 		public bool HasError { get { return (errordesc != null); } }
-		
+		internal abstract ScriptType ScriptType { get; } //mxd
+		internal Dictionary<string, TextResource> TextResources { get { return textresources; } } //mxd
+
 		#endregion
 		
 		#region ================== Constructor / Disposer
@@ -76,6 +86,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 		{
 			// Initialize
 			errordesc = null;
+			textresources = new Dictionary<string, TextResource>(StringComparer.OrdinalIgnoreCase); //mxd
+			untrackedtextresources = new HashSet<string>(StringComparer.OrdinalIgnoreCase); //mxd
 		}
 		
 		#endregion
@@ -83,35 +95,76 @@ namespace CodeImp.DoomBuilder.ZDoom
 		#region ================== Parsing
 
 		//mxd. This parses the given decorate stream. Returns false on errors
-		public virtual bool Parse(Stream stream, string sourcefilename, bool clearerrors)
+		public virtual bool Parse(TextResourceData parsedata, bool clearerrors)
 		{
 			//mxd. Clear error status?
 			if(clearerrors) ClearError();
 
 			//mxd. Integrity checks
-			if(stream == null)
+			if(parsedata.Stream == null)
 			{
-				ReportError("Unable to load \"" + sourcefilename + "\"");
+				ReportError("Unable to load \"" + parsedata.Filename + "\"");
 				return false;
 			}
 
-			if(stream.Length == 0)
+			if(parsedata.Stream.Length == 0)
 			{
-				if(!string.IsNullOrEmpty(sourcename) && sourcename != sourcefilename)
+				if(!string.IsNullOrEmpty(sourcename) && sourcename != parsedata.Filename)
 				{
-					LogWarning("Include file \"" + sourcefilename + "\" is empty");
+					LogWarning("Include file \"" + parsedata.Filename + "\" is empty");
 				}
 				else
 				{
-					sourcename = sourcefilename; // LogWarning() needs "sourcename" property to properly log the warning...
+					sourcename = parsedata.Filename; // LogWarning() needs "sourcename" property to properly log the warning...
 					LogWarning("File is empty");
 				}
 			}
-			
-			datastream = stream;
-			datareader = new BinaryReader(stream, Encoding.ASCII);
-			sourcename = sourcefilename;
+
+			datastream = parsedata.Stream;
+			datareader = new BinaryReader(parsedata.Stream, Encoding.ASCII);
+			sourcename = parsedata.Filename;
+			sourcelumpindex = parsedata.LumpIndex; //mxd
+			datalocation = parsedata.SourceLocation; //mxd
 			datastream.Seek(0, SeekOrigin.Begin);
+
+			return true;
+		}
+
+		//mxd
+		protected bool AddTextResource(TextResourceData parsedata)
+		{
+			// Script Editor resources don't have actual path and should always be parsed
+			if(string.IsNullOrEmpty(parsedata.SourceLocation.location))
+			{
+				if(parsedata.Trackable) throw new NotSupportedException("Trackable TextResource must have a valid path.");
+				return true;
+			}
+
+			string path = Path.Combine(parsedata.SourceLocation.location, parsedata.Filename + (parsedata.LumpIndex != -1 ? "#" + parsedata.LumpIndex : ""));
+			if(textresources.ContainsKey(path) || untrackedtextresources.Contains(path))
+				return false;
+
+			//mxd. Create TextResource for this file
+			if(parsedata.Trackable)
+			{
+				textresourcepath = path;
+				TextResource res = new TextResource
+				{
+					Resource = parsedata.Source,
+					Entries = new HashSet<string>(StringComparer.OrdinalIgnoreCase),
+					Filename = parsedata.Filename,
+					LumpIndex = parsedata.LumpIndex
+				};
+
+				textresources.Add(textresourcepath, res);
+			}
+			// Track the untrackable!
+			else
+			{
+				untrackedtextresources.Add(path);
+				textresourcepath = string.Empty;
+			}
+
 			return true;
 		}
 		
@@ -217,6 +270,24 @@ namespace CodeImp.DoomBuilder.ZDoom
 						datastream.Seek(-1, SeekOrigin.Current);
 					}
 				}
+				//mxd. Region/endregion handling
+				else if(c == '#')
+				{
+					string s = ReadToken(false).ToLowerInvariant();
+					if(s == "region" || s == "endregion")
+					{
+						// Skip entire line
+						char ch = ' ';
+						while((ch != '\n') && (datastream.Position < datastream.Length)) { ch = (char)datareader.ReadByte(); }
+						c = ch;
+					}
+					else
+					{
+						// Rewind so this structure can be read again
+						DataStream.Seek(-s.Length - 2, SeekOrigin.Current);
+						return true;
+					}
+				}
 			}
 			while(whitespace.IndexOf(c, offset) > -1);
 			
@@ -523,7 +594,8 @@ namespace CodeImp.DoomBuilder.ZDoom
 			// Set error information
 			errordesc = message;
 			errorline = (datastream != null ? GetCurrentLineNumber() : CompilerError.NO_LINE_NUMBER); //mxd
-			errorsource = sourcename;
+			errorsource = (ScriptType == ScriptType.ACS && sourcename.StartsWith("?") ? sourcename : Path.Combine(datalocation.GetShortName(), sourcename));
+			if(sourcelumpindex != -1) errorsource += ":" + sourcelumpindex; //mxd
 		}
 
 		//mxd. This adds a warning to the ErrorLogger
@@ -531,7 +603,9 @@ namespace CodeImp.DoomBuilder.ZDoom
 		{
 			// Add a warning
 			int errline = (datastream != null ? GetCurrentLineNumber() : CompilerError.NO_LINE_NUMBER);
-			General.ErrorLogger.Add(ErrorType.Warning, GetLanguageType() + " warning in \"" + sourcename
+			string errsource = (ScriptType == ScriptType.ACS && sourcename.StartsWith("?") ? sourcename.Substring(1) : Path.Combine(datalocation.GetShortName(), sourcename));
+			if(sourcelumpindex != -1) errsource += ":" + sourcelumpindex;
+			General.ErrorLogger.Add(ErrorType.Warning, ScriptType + " warning in \"" + errsource
 								+ (errline != CompilerError.NO_LINE_NUMBER ? "\", line " + (errline + 1) : "\"") + ". " 
 								+ message + ".");
 		}
@@ -539,7 +613,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 		//mxd. This adds an error to the ErrorLogger
 		public void LogError()
 		{
-			General.ErrorLogger.Add(ErrorType.Error, GetLanguageType() + " error in \"" + errorsource
+			General.ErrorLogger.Add(ErrorType.Error, ScriptType + " error in \"" + errorsource
 								+ (errorline != CompilerError.NO_LINE_NUMBER ? "\", line " + (errorline + 1) : "\"") + ". "
 								+ errordesc + ".");
 		}
@@ -563,7 +637,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 			// Find the line on which we found this error
 			datastream.Seek(0, SeekOrigin.Begin);
 			StreamReader textreader = new StreamReader(datastream, Encoding.ASCII);
-			while(readpos < finishpos) 
+			while(readpos < finishpos + 1) 
 			{
 				string line = textreader.ReadLine();
 				if(line == null) break;
@@ -661,9 +735,6 @@ namespace CodeImp.DoomBuilder.ZDoom
 			return true;
 		}
 
-		//mxd. Language type
-		protected abstract string GetLanguageType();
-
 		#endregion
 	}
 }
-- 
GitLab