diff --git a/Build/Configurations/Includes/Game_Doom.cfg b/Build/Configurations/Includes/Game_Doom.cfg
index a01c3191a905c08b289f460e849716e5b754b661..7b79f73268e9bdc98b735a5f47e5574e9593b869 100644
--- a/Build/Configurations/Includes/Game_Doom.cfg
+++ b/Build/Configurations/Includes/Game_Doom.cfg
@@ -1,6 +1,6 @@
 // Default lump name for new map
 defaultlumpname = "MAP01";
-basegame = 1; //mxd: 0 - UNKNOWN, 1 - DOOM, 2 - HERETIC, 3 - HEXEN, 4 - STRIFE
+basegame = "Doom";
 
 // Decorate actors to include depending on actor game property
 decorategames = "doom";
diff --git a/Build/Configurations/Includes/Game_Heretic.cfg b/Build/Configurations/Includes/Game_Heretic.cfg
index 14f481b402f19bed0ed2d860d1dca54b53d5c2f6..1abd31ce99ec5545ab13796d366fea7143fc52fa 100644
--- a/Build/Configurations/Includes/Game_Heretic.cfg
+++ b/Build/Configurations/Includes/Game_Heretic.cfg
@@ -1,6 +1,6 @@
 // Default lump name for new map
 defaultlumpname = "E1M1";
-basegame = 2;
+basegame = "Heretic";
 
 // Decorate actors to include depending on actor game property
 decorategames = "heretic raven";
diff --git a/Build/Configurations/Includes/Game_Hexen.cfg b/Build/Configurations/Includes/Game_Hexen.cfg
index 86d87cedfa6825859fbc04c38a2e2e4f042949f1..c713cb85dd07518fbfed34db751c1844f11f6610 100644
--- a/Build/Configurations/Includes/Game_Hexen.cfg
+++ b/Build/Configurations/Includes/Game_Hexen.cfg
@@ -1,7 +1,7 @@
 // Default lump name for new map
 defaultlumpname = "MAP01";
 skyflatname = "F_SKY";
-basegame = 3;
+basegame = "Hexen";
 
 // Decorate actors to include depending on actor game property
 decorategames = "hexen raven";
diff --git a/Build/Configurations/Includes/Game_Strife.cfg b/Build/Configurations/Includes/Game_Strife.cfg
index 159078ddc1d7b16b9ef7d30ada8ab7b6dd6161ca..3e7a43322713aa143b01b02c2ec0142b82b6ef68 100644
--- a/Build/Configurations/Includes/Game_Strife.cfg
+++ b/Build/Configurations/Includes/Game_Strife.cfg
@@ -1,7 +1,7 @@
 // Default lump name for new map
 defaultlumpname = "MAP01";
 skyflatname = "F_SKY001";
-basegame = 4;
+basegame = "Strife";
 
 // Decorate actors to include depending on actor game property
 decorategames = "strife";
diff --git a/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg b/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg
index 3652e7968a72f39a3f1ccf4936745b98fc1afd27..2d97ae1828a0e4b1c141aadde207e5c890837065 100644
--- a/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg	
+++ b/Build/Configurations/Other Games/Chex Quest 3/Includes/Game_Chex3.cfg	
@@ -1,5 +1,6 @@
 // Default lump name for new map
 defaultlumpname = "MAP01";
+basegame = "Chex";
 
 // Decorate actors to include depending on actor game property
 decorategames = "chex";
diff --git a/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg b/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg
index b7f240b60d773a221050b62ac557459586eee9fa..6bdc18cf114ad50307e09a01b104c0aff0e7b9ef 100644
--- a/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg	
+++ b/Build/Configurations/Other Games/Chex Quest/Includes/Game_Chex.cfg	
@@ -1,5 +1,6 @@
 // Default lump name for new map
 defaultlumpname = "MAP01";
+basegame = "Chex";
 
 // Decorate actors to include depending on actor game property
 decorategames = "chex";
diff --git a/Help/gc_basicsettings.html b/Help/gc_basicsettings.html
index 2474fcd28ed76170d7c588c6452e834209563f92..509ef63a7274c00d0c8cb3d078519e3715bfde6d 100644
--- a/Help/gc_basicsettings.html
+++ b/Help/gc_basicsettings.html
@@ -31,10 +31,10 @@
 		<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 />
+		<b class="fat">basegame</b> (string) - <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>Possible values:</b> &quot;Doom&quot;, &quot;Heretic&quot;, &quot;Hexen&quot;, &quot;Strife&quot; or &quot;Chex&quot;.<br />
+		If left unset, game-specific lumps won't be loaded.<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 />
diff --git a/Help/gzdb/text_lumps.html b/Help/gzdb/text_lumps.html
index d11572637c1f3c43bf16447690132b7b4af7522b..0d75e11f51f953a722c88356349565007e4c441e 100644
--- a/Help/gzdb/text_lumps.html
+++ b/Help/gzdb/text_lumps.html
@@ -62,6 +62,8 @@
     <h2>SNDSEQ:</h2>
     Sound Sequence and Sound Sequence Group definitions are loaded and can be selected in the "Sound sequence" drop-down of the Edit Sector window (UDMF only). 
     
+    <h2>LOCKDEFS:</h2>
+    Lock definitions are loaded. The values are used to  populate &quot;<strong>keys</strong>&quot; Game Configuration enum.
     <h2>TERRAIN:</h2>
     Terrain names are loaded and used in the floor and ceiling "Terrain" drop-downs of the Edit Sector window (UDMF only).
     
diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index 6012955cb9f58a1a4ae42a5401be0a4b4bbc1e7e..5fa825f32121ee2da278672c5491afbe71ce154b 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -881,7 +881,7 @@
     </Compile>
     <Compile Include="GZBuilder\Data\BoundingBox.cs" />
     <Compile Include="GZBuilder\Data\EngineInfo.cs" />
-    <Compile Include="GZBuilder\Data\GameType.cs" />
+    <Compile Include="Config\GameType.cs" />
     <Compile Include="GZBuilder\Data\DynamicLight.cs" />
     <Compile Include="GZBuilder\Data\GlowingFlatData.cs" />
     <Compile Include="GZBuilder\Data\LinedefColorPreset.cs" />
@@ -899,6 +899,7 @@
     <Compile Include="Geometry\Line3D.cs" />
     <Compile Include="Data\Scripting\ScriptHandlerAttribute.cs" />
     <Compile Include="ZDoom\DecorateCategoryInfo.cs" />
+    <Compile Include="ZDoom\LockDefsParser.cs" />
     <Compile Include="ZDoom\Scripting\DecorateParserSE.cs" />
     <Compile Include="ZDoom\GldefsParser.cs" />
     <Compile Include="ZDoom\MapinfoParser.cs" />
diff --git a/Source/Core/Config/EnumList.cs b/Source/Core/Config/EnumList.cs
index 2f47347f276a840696edffb132402917cd3737ec..0da0f470e06aeb2b72b4f3d7b247adbfdc515121 100644
--- a/Source/Core/Config/EnumList.cs
+++ b/Source/Core/Config/EnumList.cs
@@ -45,9 +45,10 @@ namespace CodeImp.DoomBuilder.Config
 		#region ================== Constructor
 
 		// Constructor for custom list
-		internal EnumList()
-		{
-		}
+		internal EnumList() { }
+
+		//mxd. Constructor for custom list
+		internal EnumList(int capacity) : base(capacity) { }
 
 		// Constructor to load from dictionary
 		internal EnumList(IDictionary dic)
diff --git a/Source/Core/Config/GameConfiguration.cs b/Source/Core/Config/GameConfiguration.cs
index 4c1c854082ecf346af36d5e283abc639162192db..6e3147f33149c82f110e713456d35ac00400a8d7 100644
--- a/Source/Core/Config/GameConfiguration.cs
+++ b/Source/Core/Config/GameConfiguration.cs
@@ -159,7 +159,7 @@ namespace CodeImp.DoomBuilder.Config
 		private readonly List<ThingsFilter> thingfilters;
 
 		//mxd. Holds base game type (doom, heretic, hexen or strife)
-		private readonly GameType gametype;
+		private readonly string basegame;
 		
 		#endregion
 
@@ -281,7 +281,7 @@ namespace CodeImp.DoomBuilder.Config
 		public List<ThingsFilter> ThingsFilters { get { return thingfilters; } }
 
 		//mxd
-		public GameType GameType { get { return gametype; } }
+		public string BaseGame { get { return basegame; } }
 		
 		#endregion
 
@@ -328,8 +328,13 @@ namespace CodeImp.DoomBuilder.Config
 			configname = cfg.ReadSetting("game", "<unnamed game>");
 
 			//mxd
-			int gt = (cfg.ReadSetting("basegame", (int)GameType.UNKNOWN));
-			gametype = ( (gt > -1 && gt < Gldefs.GLDEFS_LUMPS_PER_GAME.Length) ? (GameType)gt : GameType.UNKNOWN);
+			basegame = cfg.ReadSetting("basegame", string.Empty).ToLowerInvariant();
+			if(!GameType.GameTypes.Contains(basegame))
+			{
+				if(!string.IsNullOrEmpty(basegame))
+					General.ErrorLogger.Add(ErrorType.Error, "Unknown basegame value specified in current Game Configuration: \"" + basegame + "\"");
+				basegame = GameType.UNKNOWN;
+			}
 
 			enginename = cfg.ReadSetting("engine", "");
 			defaultsavecompiler = cfg.ReadSetting("defaultsavecompiler", "");
diff --git a/Source/Core/Config/GameType.cs b/Source/Core/Config/GameType.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ed879d4de06149599bc685a1e0c333e8298affd1
--- /dev/null
+++ b/Source/Core/Config/GameType.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace CodeImp.DoomBuilder.Config 
+{
+	public struct GameType
+	{
+		public const string UNKNOWN = "UNKNOWN_GAME";
+		public const string DOOM = "doom";
+		public const string HERETIC = "heretic";
+		public const string HEXEN = "hexen";
+		public const string STRIFE = "strife";
+		public const string CHEX = "chex";
+
+		public static readonly HashSet<string> GameTypes = new HashSet<string> { DOOM, HERETIC, HEXEN, STRIFE, CHEX };
+		public static readonly Dictionary<string, string> GldefsLumpsPerGame = new Dictionary<string, string>
+		{
+			{ DOOM, "DOOMDEFS" },
+			{ HERETIC, "HTICDEFS" },
+			{ HEXEN, "HEXNDEFS" },
+			{ STRIFE, "STRFDEFS" },
+			{ CHEX, "DOOMDEFS" }, // Is that so?..
+		};
+	}
+}
diff --git a/Source/Core/Config/ScriptConfiguration.cs b/Source/Core/Config/ScriptConfiguration.cs
index 1bb3b4641c52302ab6b0ca81dcef6bbc31d88063..faf7e59b7b979fb9542bf9722ea9405a020f7ab7 100644
--- a/Source/Core/Config/ScriptConfiguration.cs
+++ b/Source/Core/Config/ScriptConfiguration.cs
@@ -45,6 +45,7 @@ namespace CodeImp.DoomBuilder.Config
 		X11R6RGB,
 		CVARINFO,
 		SNDINFO,
+		LOCKDEFS,
 	}
 	
 	internal class ScriptConfiguration : IComparable<ScriptConfiguration>
diff --git a/Source/Core/Config/ThingTypeInfo.cs b/Source/Core/Config/ThingTypeInfo.cs
index 781d21fa71497699668a42122877e4746b67ef0c..23d8b8133941511f5734366f65c52fb1715b9ef8 100644
--- a/Source/Core/Config/ThingTypeInfo.cs
+++ b/Source/Core/Config/ThingTypeInfo.cs
@@ -514,7 +514,7 @@ namespace CodeImp.DoomBuilder.Config
 			}
 			else if(actor.HasProperty("defaultalpha"))
 			{
-				this.alpha = (General.Map.Config.GameType == GameType.HERETIC ? 0.4f : 0.6f);
+				this.alpha = (General.Map.Config.BaseGame == GameType.HERETIC ? 0.4f : 0.6f);
 				this.alphabyte = (byte)(this.alpha * 255);
 			}
 
diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs
index 7aff2db7dd1e0e6ff04768d81d69206698cfcfa3..93fb5aab458effe5a48591d279dd6b78236e179d 100644
--- a/Source/Core/Data/DataManager.cs
+++ b/Source/Core/Data/DataManager.cs
@@ -423,6 +423,7 @@ namespace CodeImp.DoomBuilder.Data
 			Dictionary<int, string> spawnnums, doomednums;
 			LoadMapInfo(out spawnnums, out doomednums);
 			LoadCvarInfo();
+			LoadLockDefs();
 
 			int thingcount = LoadDecorateThings(spawnnums, doomednums);
 			int spritecount = LoadThingSprites();
@@ -2366,7 +2367,7 @@ namespace CodeImp.DoomBuilder.Data
 			{
 				currentreader = dr;
 				parser.ClearIncludesList();
-				IEnumerable<TextResourceData> streams = dr.GetGldefsData(General.Map.Config.GameType);
+				IEnumerable<TextResourceData> streams = dr.GetGldefsData(General.Map.Config.BaseGame);
 
 				foreach(TextResourceData data in streams)
 				{
@@ -2771,6 +2772,52 @@ namespace CodeImp.DoomBuilder.Data
 			cvars = parser.Cvars;
 		}
 
+		//mxd. This loads LOCKDEFS lumps
+		private void LoadLockDefs()
+		{
+			LockDefsParser parser = new LockDefsParser();
+
+			foreach(DataReader dr in containers)
+			{
+				currentreader = dr;
+				IEnumerable<TextResourceData> streams = dr.GetLockDefsData();
+
+				// 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;
+
+			// Apply to the enums list?
+			EnumList keys = parser.GetLockDefs();
+			if(keys.Count > 0)
+			{
+				keys.Insert(0, new EnumItem("0", "None"));
+				General.Map.Config.Enums["keys"] = keys;
+				
+				// Update all ArgumentInfos...
+				foreach(ThingTypeInfo info in thingtypes.Values)
+				{
+					foreach(ArgumentInfo ai in info.Args)
+						if(ai.Enum.Name == "keys") ai.Enum = General.Map.Config.Enums["keys"];
+				}
+
+				foreach(LinedefActionInfo info in General.Map.Config.LinedefActions.Values)
+				{
+					foreach(ArgumentInfo ai in info.Args)
+						if(ai.Enum.Name == "keys") ai.Enum = General.Map.Config.Enums["keys"];
+				}
+			}
+		}
+
 		//mxd
 		internal TextResourceData GetTextResourceData(string name) 
 		{
diff --git a/Source/Core/Data/DataReader.cs b/Source/Core/Data/DataReader.cs
index 8c0094119c65ad8cc9245e417c7df610c5d595e7..db2937e2eb7869dbcc6bbeb743e0a65aa31e74e1 100644
--- a/Source/Core/Data/DataReader.cs
+++ b/Source/Core/Data/DataReader.cs
@@ -229,7 +229,7 @@ namespace CodeImp.DoomBuilder.Data
 		public abstract IEnumerable<TextResourceData> GetMapinfoData();
 
 		//mxd. When implemented, this returns GLDEFS lumps
-		public abstract IEnumerable<TextResourceData> GetGldefsData(GameType gametype);
+		public abstract IEnumerable<TextResourceData> GetGldefsData(string basegame);
 
 		//mxd. When implemented, this returns REVERBS lumps
 		public abstract IEnumerable<TextResourceData> GetReverbsData();
@@ -255,6 +255,9 @@ namespace CodeImp.DoomBuilder.Data
 		//mxd. When implemented, this returns CVARINFO lumps
 		public abstract IEnumerable<TextResourceData> GetCvarInfoData();
 
+		//mxd. When implemented, this returns LOCKDEFS lumps
+		public abstract IEnumerable<TextResourceData> GetLockDefsData();
+
 		//mxd. When implemented, this returns the list of voxel model names
 		public abstract HashSet<string> GetVoxelNames();
 
diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs
index 53be08c96af414d7f5aecaef48d8682cf9bfdb47..43da9b5e104adbfbc87bc2cc218e04203175e918 100644
--- a/Source/Core/Data/PK3StructuredReader.cs
+++ b/Source/Core/Data/PK3StructuredReader.cs
@@ -19,8 +19,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Text.RegularExpressions;
-using CodeImp.DoomBuilder.GZBuilder.Data;
+using CodeImp.DoomBuilder.Config;
 using CodeImp.DoomBuilder.ZDoom;
 
 #endregion
@@ -630,7 +629,7 @@ namespace CodeImp.DoomBuilder.Data
 		#region ================== GLDEFS (mxd)
 
 		//mxd
-		public override IEnumerable<TextResourceData> GetGldefsData(GameType gametype) 
+		public override IEnumerable<TextResourceData> GetGldefsData(string basegame) 
 		{
 			// Error when suspended
 			if(issuspended) throw new Exception("Data reader is suspended");
@@ -641,9 +640,9 @@ namespace CodeImp.DoomBuilder.Data
 			List<string> files = new List<string>();
 
 			// Try to load game specific GLDEFS first
-			if(gametype != GameType.UNKNOWN) 
+			if(basegame != GameType.UNKNOWN)
 			{
-				string lumpname = Gldefs.GLDEFS_LUMPS_PER_GAME[(int)gametype];
+				string lumpname = GameType.GldefsLumpsPerGame[basegame];
 				files.AddRange(GetAllFilesWhichTitleStartsWith("", lumpname, false));
 			}
 
@@ -655,7 +654,7 @@ namespace CodeImp.DoomBuilder.Data
 				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));
+			foreach(WADReader wr in wads) result.AddRange(wr.GetGldefsData(basegame));
 
 			return result;
 		}
@@ -816,6 +815,28 @@ namespace CodeImp.DoomBuilder.Data
 
 		#endregion
 
+		#region ================== Lockdefs (mxd)
+
+		public override IEnumerable<TextResourceData> GetLockDefsData()
+		{
+			// Error when suspended
+			if(issuspended) throw new Exception("Data reader is suspended");
+
+			List<TextResourceData> result = new List<TextResourceData>();
+			string[] files = GetAllFilesWithTitle("", "LOCKDEFS", 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.GetLockDefsData());
+
+			return result;
+		}
+
+		#endregion
+
 		#region ================== Methods
 
 		// This loads the images in this directory
diff --git a/Source/Core/Data/WADReader.cs b/Source/Core/Data/WADReader.cs
index f44cb6affb669740fc07fdf47743aa274078a578..18af40cdf2498b8d824d7e71dc61d6d2fd017304 100644
--- a/Source/Core/Data/WADReader.cs
+++ b/Source/Core/Data/WADReader.cs
@@ -1034,16 +1034,16 @@ namespace CodeImp.DoomBuilder.Data
 		}
 
 		//mxd
-		public override IEnumerable<TextResourceData> GetGldefsData(GameType gametype) 
+		public override IEnumerable<TextResourceData> GetGldefsData(string basegame) 
 		{
 			if(issuspended) throw new Exception("Data reader is suspended");
 
 			List<TextResourceData> result = new List<TextResourceData>();
 
 			// Try to load game specific GLDEFS first
-			if(gametype != GameType.UNKNOWN)
+			if(basegame != GameType.UNKNOWN)
 			{
-				string lumpname = Gldefs.GLDEFS_LUMPS_PER_GAME[(int)gametype];
+				string lumpname = GameType.GldefsLumpsPerGame[basegame];
 				result.AddRange(GetAllLumps(lumpname));
 			}
 
@@ -1108,6 +1108,13 @@ namespace CodeImp.DoomBuilder.Data
 			return GetAllLumps("CVARINFO");
 		}
 
+		//mxd
+		public override IEnumerable<TextResourceData> GetLockDefsData()
+		{
+			if(issuspended) throw new Exception("Data reader is suspended");
+			return GetAllLumps("LOCKDEFS");
+		}
+
 		//mxd
 		private IEnumerable<TextResourceData> GetFirstLump(string name)
 		{
diff --git a/Source/Core/GZBuilder/Data/GameType.cs b/Source/Core/GZBuilder/Data/GameType.cs
deleted file mode 100644
index 2de62739b6c2233b030bde4157c11fcccbf117d9..0000000000000000000000000000000000000000
--- a/Source/Core/GZBuilder/Data/GameType.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-
-namespace CodeImp.DoomBuilder.GZBuilder.Data 
-{
-	public enum GameType 
-	{
-		UNKNOWN = 0,
-		DOOM = 1,
-		HERETIC = 2,
-		HEXEN = 3,
-		STRIFE = 4,
-	}
-
-	public struct Gldefs 
-	{
-		public static readonly string[] GLDEFS_LUMPS_PER_GAME = { "UNKNOWN_GAME", "DOOMDEFS", "HTICDEFS", "HEXNDEFS", "STRFDEFS" };
-	}
-}
diff --git a/Source/Core/ZDoom/LockDefsParser.cs b/Source/Core/ZDoom/LockDefsParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4ff8cf603ed8648855a434734d8cd764c4b9b758
--- /dev/null
+++ b/Source/Core/ZDoom/LockDefsParser.cs
@@ -0,0 +1,137 @@
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
+
+namespace CodeImp.DoomBuilder.ZDoom
+{
+	internal sealed class LockDefsParser :ZDTextParser
+	{
+		internal override ScriptType ScriptType { get { return ScriptType.LOCKDEFS; } }
+
+		private Dictionary<int, string> locks;
+
+		public LockDefsParser()
+		{
+			locks = new Dictionary<int, string>();
+		}
+
+		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
+			int locknum = -1;
+			string locktitle = string.Empty;
+			string game = string.Empty;
+			int bracelevel = 0;
+
+			while(SkipWhitespace(true))
+			{
+				string token = ReadToken().ToLowerInvariant();
+				if(string.IsNullOrEmpty(token)) continue;
+
+				switch(token)
+				{
+					case "clearlocks":
+						if(bracelevel == 0)
+						{
+							locks.Clear();
+						}
+						else
+						{
+							ReportError("Unexpected \"CLEARLOCKS\" keyword");
+							return false;
+						}
+						break;
+
+					// LOCK locknumber [game]
+					case "lock":
+						SkipWhitespace(false);
+						if(!ReadSignedInt(ref locknum))
+						{
+							ReportError("Expected lock number");
+							return false;
+						}
+
+						//wiki: The locknumber must be in the 1-to-255 range
+						if(locknum < 1 || locknum > 255)
+						{
+							ReportError("The locknumber must be in the 1-to-255 range, but is " + locknum);
+							return false;
+						}
+
+						SkipWhitespace(true);
+						token = ReadToken().ToLowerInvariant();
+						if(!string.IsNullOrEmpty(token))
+						{
+							if(token == "{")
+							{
+								bracelevel++;
+							}
+							//Should be game
+							else if(!GameType.GameTypes.Contains(token))
+							{
+								LogWarning("Lock " + locknum + " is defined for unknown game \"" + token + "\"");
+							}
+							else
+							{
+								game = token;
+							}
+						}
+						break;
+
+					case "$title":
+						SkipWhitespace(false);
+						locktitle = StripQuotes(ReadToken(false));
+						break;
+
+					case "{":
+						bracelevel++;
+						break;
+
+					case "}":
+						if(--bracelevel > 0) continue;
+
+						// Add to collection?
+						if(locknum > 0 && (string.IsNullOrEmpty(game) || General.Map.Config.BaseGame == game))
+						{
+							// No custom title given?
+							if(string.IsNullOrEmpty(locktitle)) locktitle = "Lock " + locknum;
+
+							if(locks.ContainsKey(locknum))
+								LogWarning("Lock " + locknum + " is double-defined as \"" + locks[locknum] + "\" and \"" + locktitle + "\"");
+
+							locks[locknum] = locktitle;
+						}
+
+						// Reset values
+						locknum = -1;
+						locktitle = string.Empty;
+						game = string.Empty;
+						break;
+				}
+			}
+
+
+			return true;
+		}
+
+		public EnumList GetLockDefs()
+		{
+			EnumList result = new EnumList(locks.Count);
+
+			foreach(KeyValuePair<int, string> group in locks)
+				result.Add(new EnumItem(group.Key.ToString(), group.Value));
+
+			return result;
+		}
+	}
+}
diff --git a/Source/Core/ZDoom/TerrainParser.cs b/Source/Core/ZDoom/TerrainParser.cs
index ebb335b82fd4b8e3fcb4f28c7307015da35da116..3a89e10a98385e7a14fd92187b41415061c20b77 100644
--- a/Source/Core/ZDoom/TerrainParser.cs
+++ b/Source/Core/ZDoom/TerrainParser.cs
@@ -52,19 +52,19 @@ namespace CodeImp.DoomBuilder.ZDoom
 				switch(token)
 				{
 					case "ifheretic":
-						skipdefinitions = (General.Map.Config.GameType != GameType.HERETIC);
+						skipdefinitions = (General.Map.Config.BaseGame != GameType.HERETIC);
 						break;
 
 					case "ifhexen":
-						skipdefinitions = (General.Map.Config.GameType != GameType.HEXEN);
+						skipdefinitions = (General.Map.Config.BaseGame != GameType.HEXEN);
 						break;
 
 					case "ifstrife":
-						skipdefinitions = (General.Map.Config.GameType != GameType.STRIFE);
+						skipdefinitions = (General.Map.Config.BaseGame != GameType.STRIFE);
 						break;
 
 					case "ifdoom": // TODO: is it even a thing?..
-						skipdefinitions = (General.Map.Config.GameType != GameType.DOOM);
+						skipdefinitions = (General.Map.Config.BaseGame != GameType.DOOM);
 						break;
 
 					case "terrain":