From e2e7afa73315cd53605f65914df3c55a2f3d4c1b Mon Sep 17 00:00:00 2001
From: spherallic <spherallic@gmail.com>
Date: Fri, 6 Oct 2023 19:23:48 +0200
Subject: [PATCH] Add SOC parser for custom objects

---
 Source/Core/Builder.csproj                |   2 +
 Source/Core/Config/ScriptConfiguration.cs |   2 +-
 Source/Core/Data/DataManager.cs           |  40 ++++-
 Source/Core/Data/DataReader.cs            |   3 +
 Source/Core/Data/PK3StructuredReader.cs   |  23 ++-
 Source/Core/Data/WADReader.cs             |  14 ++
 Source/Core/SRB2/LuaMobjStructure.cs      |   2 +-
 Source/Core/SRB2/SOCMobjStructure.cs      | 125 +++++++++++++++
 Source/Core/SRB2/SOCParser.cs             | 186 ++++++++++++++++++++++
 Source/Core/ZDoom/ZDTextParser.cs         |  68 ++++----
 10 files changed, 428 insertions(+), 37 deletions(-)
 create mode 100644 Source/Core/SRB2/SOCMobjStructure.cs
 create mode 100644 Source/Core/SRB2/SOCParser.cs

diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index e97ceaaf3..a26ef5c9d 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -312,7 +312,9 @@
     <Compile Include="Rendering\Vector4.cs" />
     <Compile Include="Rendering\VertexBuffer.cs" />
     <Compile Include="Rendering\VisualSlopeHandle.cs" />
+    <Compile Include="SRB2\SOCMobjStructure.cs" />
     <Compile Include="SRB2\LuaMobjStructure.cs" />
+    <Compile Include="SRB2\SOCParser.cs" />
     <Compile Include="SRB2\LuaParser.cs" />
     <Compile Include="VisualModes\VisualSlope.cs" />
     <Compile Include="Windows\ILinedefEditForm.cs" />
diff --git a/Source/Core/Config/ScriptConfiguration.cs b/Source/Core/Config/ScriptConfiguration.cs
index 98f2501e6..b663d93da 100755
--- a/Source/Core/Config/ScriptConfiguration.cs
+++ b/Source/Core/Config/ScriptConfiguration.cs
@@ -59,7 +59,7 @@ namespace CodeImp.DoomBuilder.Config
 		FONTDEFS,
         ZSCRIPT,
 		DECALDEF,
-		//SOC,
+		SOC,
 		LUA
 	}
 	
diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs
index f1bcbb881..215aa0948 100755
--- a/Source/Core/Data/DataManager.cs
+++ b/Source/Core/Data/DataManager.cs
@@ -134,7 +134,8 @@ namespace CodeImp.DoomBuilder.Data
 		private DecorateParser decorate;
         private ZScriptParser zscript;
 		private LuaParser lua;
-        private Dictionary<string, ActorStructure> zdoomclasses;
+		private SOCParser soc;
+		private Dictionary<string, ActorStructure> zdoomclasses;
 		private List<ThingCategory> thingcategories;
 		private Dictionary<int, ThingTypeInfo> thingtypes;
 
@@ -462,6 +463,7 @@ namespace CodeImp.DoomBuilder.Data
             LoadZScriptThings();
             LoadDecorateThings();
 			LoadLuaThings();
+			LoadSOCThings();
 			ApplyDehackedThings();
 			FixRenamedDehackedSprites();
             int thingcount = ApplyZDoomThings(spawnnums, doomednums);
@@ -1880,6 +1882,40 @@ namespace CodeImp.DoomBuilder.Data
 				lua.ClearActors();
 		}
 
+		// sphere: This loads things from SRB2 Lua files.
+		private void LoadSOCThings()
+		{
+			// Create new parser
+			soc = new SOCParser();
+
+			// Go for all opened containers
+			foreach (DataReader dr in containers)
+			{
+				// Load SOC info cumulatively (the last SOC is added to the previous)
+				// I'm not sure if this is the right thing to do though.
+				currentreader = dr;
+				foreach (TextResourceData data in dr.GetSOCData())
+				{
+					// Parse the data
+					data.Stream.Seek(0, SeekOrigin.Begin);
+					soc.Parse(data, true);
+
+					//mxd. DECORATE lumps are interdepandable. Can't carry on...
+					if (soc.HasError)
+					{
+						soc.LogError();
+						break;
+					}
+				}
+			}
+
+			//mxd. Add to text resources collection
+			currentreader = null;
+
+			if (soc.HasError)
+				soc.ClearActors();
+		}
+
 		// [ZZ] this retrieves ZDoom actor structure by class name.
 		public ActorStructure GetZDoomActor(string classname)
         {
@@ -1924,7 +1960,7 @@ namespace CodeImp.DoomBuilder.Data
             zdoomclasses = mergedAllActorsByClass;
 
 			// Parse SRB2 Lua/SOC objects
-			IEnumerable<ActorStructure> mobjs = lua.Mobjs; // lua.Mobjs.Union(soc.Mobjs);
+			IEnumerable<ActorStructure> mobjs = lua.Mobjs.Union(soc.Mobjs);
 
 			foreach (ActorStructure actor in mobjs)
 			{
diff --git a/Source/Core/Data/DataReader.cs b/Source/Core/Data/DataReader.cs
index 8ea8d866a..2ac0ad367 100755
--- a/Source/Core/Data/DataReader.cs
+++ b/Source/Core/Data/DataReader.cs
@@ -263,6 +263,9 @@ namespace CodeImp.DoomBuilder.Data
 		// When implemented, this returns Lua lumps
 		public abstract IEnumerable<TextResourceData> GetLuaData();
 
+		// When implemented, this returns SOC lumps
+		public abstract IEnumerable<TextResourceData> GetSOCData();
+
 		#endregion
 
 		#region ================== Load/Save (mxd)
diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs
index a7636a9a6..4be5580a8 100755
--- a/Source/Core/Data/PK3StructuredReader.cs
+++ b/Source/Core/Data/PK3StructuredReader.cs
@@ -766,7 +766,7 @@ namespace CodeImp.DoomBuilder.Data
 
 		#endregion
 
-		#region ================== Lua
+		#region ================== Lua/SOC
 
 		// sphere
 		public override IEnumerable<TextResourceData> GetLuaData()
@@ -790,6 +790,27 @@ namespace CodeImp.DoomBuilder.Data
 
 			return result;
 		}
+		public override IEnumerable<TextResourceData> GetSOCData()
+		{
+			// Error when suspended
+			if (issuspended) throw new Exception("Data reader is suspended");
+
+			List<TextResourceData> result = new List<TextResourceData>();
+
+			List<string> files = new List<string>();
+
+			// Can be several entries
+			files.AddRange(GetAllFiles("soc", true));
+
+			// 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.GetSOCData());
+
+			return result;
+		}
 
 		#endregion
 
diff --git a/Source/Core/Data/WADReader.cs b/Source/Core/Data/WADReader.cs
index c7cfa0bc3..cc4b04776 100755
--- a/Source/Core/Data/WADReader.cs
+++ b/Source/Core/Data/WADReader.cs
@@ -20,6 +20,7 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Text.RegularExpressions;
 using CodeImp.DoomBuilder.Compilers;
 using CodeImp.DoomBuilder.Config;
@@ -1143,6 +1144,19 @@ namespace CodeImp.DoomBuilder.Data
 			return GetAllLumpsDataWithPrefix("LUA_");
 		}
 
+		// sphere
+		public override IEnumerable<TextResourceData> GetSOCData()
+		{
+			if (issuspended) throw new Exception("Data reader is suspended");
+
+			List<TextResourceData> soclumps = new List<TextResourceData>();
+			soclumps = soclumps.Concat(GetAllLumpsDataWithPrefix("SOC_")).ToList();
+			soclumps = soclumps.Concat(GetAllLumpsDataWithPrefix("MAINCFG")).ToList();
+			soclumps = soclumps.Concat(GetAllLumpsDataWithPrefix("OBJCTCFG")).ToList();
+
+			return soclumps;
+		}
+
 		//mxd
 		public override IEnumerable<TextResourceData> GetTextLumpData(ScriptType scripttype, bool singular, bool ignored)
 		{
diff --git a/Source/Core/SRB2/LuaMobjStructure.cs b/Source/Core/SRB2/LuaMobjStructure.cs
index c8c671fc6..8c9691011 100644
--- a/Source/Core/SRB2/LuaMobjStructure.cs
+++ b/Source/Core/SRB2/LuaMobjStructure.cs
@@ -48,7 +48,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 					// Property
 					default:
-						General.WriteLogLine(token);
+						//General.WriteLogLine(token);
 						// Property begins with $? Then the whole line is a single value
 						if (token.Contains("$"))
 						{
diff --git a/Source/Core/SRB2/SOCMobjStructure.cs b/Source/Core/SRB2/SOCMobjStructure.cs
new file mode 100644
index 000000000..68627a1ed
--- /dev/null
+++ b/Source/Core/SRB2/SOCMobjStructure.cs
@@ -0,0 +1,125 @@
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
+using CodeImp.DoomBuilder.Types;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+namespace CodeImp.DoomBuilder.ZDoom
+{
+
+    public sealed class SOCMobjStructure : ActorStructure
+	{
+        #region ================== SOC Actor Structure parsing
+
+        internal SOCMobjStructure(ZDTextParser zdparser, string objname)
+        {
+			classname = string.Empty;
+
+            SOCParser parser = (SOCParser)zdparser;
+			General.WriteLogLine(objname);
+
+			if (string.IsNullOrEmpty(objname))
+            {
+                parser.ReportError("Lua object structure has no object name or map thing number");
+                return;
+            }
+
+			// Now parse the contents of actor structure
+
+			while (parser.SkipWhitespace(false, true))
+			{
+				string token = parser.ReadToken();
+				token = token.ToLowerInvariant();
+
+				if (token == "\n")
+				{
+					parser.SkipWhitespace(false, true);
+					token = parser.ReadToken();
+				}
+
+				// Property begins with $? Then the whole line is a single value
+				if (token.Contains("$"))
+				{
+					token = token.ToLowerInvariant().Trim(new char[] { '#' });
+
+					switch (token)
+					{
+						case "$wallsprite":
+						case "$flatsprite":
+						case "$spawnceiling":
+							flags[token.Substring(1)] = true;
+							parser.ReadLine();
+							break;
+						case "$name":
+							token = "$title";
+							goto default;
+						default:
+							props[token] = new List<string> { (parser.SkipWhitespace(false, true) ? parser.ReadLine() : "") };
+							break;
+					}
+				}
+				else
+				{
+					string tokenname = token;
+					parser.SkipWhitespace(false, true);
+					token = parser.ReadToken();
+
+					// SOC definitions are terminated by an empty line. Blegh.
+					if (tokenname == "\n")
+					{
+						General.WriteLogLine("object definition done");
+						break;
+					}
+
+					if (token != "=")
+					{
+						parser.ReportError("Invalid SOC object parameter definition, missing =");
+						return;
+					}
+
+					parser.SkipWhitespace(true, true);
+					token = parser.ReadToken();
+					tokenname = tokenname.ToLowerInvariant();
+
+					List<string> values = new List<string>() { token.TrimEnd(new char[] { ',' }) };
+
+					//mxd. Translate scale to xscale and yscale
+					switch (tokenname)
+					{
+						case "scale":
+							props["xscale"] = values;
+							props["yscale"] = values;
+							break;
+						case "doomednum":
+						case "mapthingnum":
+							doomednum = int.Parse(values[0]);
+							goto default;
+						case "height":
+						case "radius":
+							props[tokenname] = new List<string>() { ReadFracunit(values[0]).ToString() };
+							break;
+						default:
+							props[tokenname] = values;
+							break;
+					}
+				}
+			}
+
+			// parsing done, process thing arguments
+			ParseCustomArguments();
+		}
+
+		private static string ReadFracunit(string input)
+		{
+			if (input.Contains("FRACUNIT") || input.Contains("FRACBITS"))
+				return new string(input.Where(c => char.IsDigit(c)).ToArray());
+			else
+				return (int.Parse(input) >> 16).ToString();
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Core/SRB2/SOCParser.cs b/Source/Core/SRB2/SOCParser.cs
new file mode 100644
index 000000000..b4103f981
--- /dev/null
+++ b/Source/Core/SRB2/SOCParser.cs
@@ -0,0 +1,186 @@
+
+#region ================== Copyright (c) 2007 Pascal vd Heiden
+
+/*
+ * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
+ * This program is released under GNU General Public License
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
+using CodeImp.DoomBuilder.Types;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.ZDoom
+{
+	public sealed class SOCParser : ZDTextParser
+	{
+		#region ================== Delegates
+
+		public delegate void IncludeDelegate(LuaParser parser, string includefile);
+		
+		public IncludeDelegate OnInclude;
+
+		#endregion
+		
+		#region ================== Constants
+		
+		#endregion
+		
+		#region ================== Variables
+
+		//mxd. Script type
+		internal override ScriptType ScriptType { get { return ScriptType.LUA; } }
+
+		// SRB2 mobjs
+		private Dictionary<int, ActorStructure> mobjs;
+
+		//mxd. Includes tracking
+		private HashSet<string> parsedlumps;
+
+		//mxd. Disposing. Is that really needed?..
+		private bool isdisposed;
+
+		//
+		public bool NoWarnings = false;
+
+		#endregion
+
+		#region ================== Properties
+
+		/// <summary>
+		/// All mobjs that are supported by the current game.
+		/// </summary>
+		public ICollection<ActorStructure> Mobjs { get { return mobjs.Values; } }
+
+		#endregion
+
+		#region ================== Constructor / Disposer
+
+		// Constructor
+		public SOCParser()
+		{
+			// Syntax
+			whitespace = "\n \t\r\u00A0"; //mxd. non-breaking space is also space :)
+			specialtokens = "=\n";
+			skipregions = false; //mxd
+
+            ClearActors();
+		}
+		
+		// Disposer
+		public void Dispose()
+		{
+			mobjs = null;
+
+			isdisposed = true;
+		}
+
+		#endregion
+
+		#region ================== Parsing
+
+		protected internal override void LogWarning(string message, int linenumber)
+		{
+			if (NoWarnings)
+				return;
+			base.LogWarning(message, linenumber);
+		}
+
+		// This parses the given SOC stream
+		// Returns false on errors
+		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
+			while (SkipWhitespace(true, true))
+			{
+				// Read a token
+				string token = ReadToken();
+
+				if (!string.IsNullOrEmpty(token))
+				{
+					string objname = null;
+					//General.WriteLogLine("token = " + token);
+
+					token = token.ToLowerInvariant();
+
+					// SOC object
+					if (token.StartsWith("thing") || token.EndsWith("mobj") || token.EndsWith("object"))
+					{
+						SkipWhitespace(true, true);
+						token = ReadToken();
+
+						if (!token.ToUpper().StartsWith("MT_"))
+						{
+							continue;
+						}
+
+						objname = token;
+
+						// Read actor structure
+						ActorStructure mobj = new SOCMobjStructure(this, objname);
+						if (this.HasError) return false;
+
+						mobjs[mobj.DoomEdNum] = mobj;
+					}
+					// Level header
+					else if (token.Equals("level"))
+					{
+						SkipWhitespace(true, true);
+						token = ReadToken();
+
+						if (!(token.Length > 2))
+							General.WriteLogLine("Map token: MAP" + token);
+					}
+				}
+			}
+
+
+			// Return true when no errors occurred
+			return (ErrorDescription == null);
+		}
+		
+		#endregion
+		
+		#region ================== Methods
+
+		public ActorStructure GetMobjByDoomEdNum(int doomednum)
+		{
+			return mobjs.ContainsKey(doomednum) ? mobjs[doomednum] : null;
+		}
+
+        internal void ClearActors()
+        {
+            // Initialize
+			mobjs = new Dictionary<int, ActorStructure>();
+			parsedlumps = new HashSet<string>(StringComparer.OrdinalIgnoreCase); //mxd
+		}
+
+		#endregion
+	}
+}
diff --git a/Source/Core/ZDoom/ZDTextParser.cs b/Source/Core/ZDoom/ZDTextParser.cs
index 1e106dab4..7a7e3edb7 100755
--- a/Source/Core/ZDoom/ZDTextParser.cs
+++ b/Source/Core/ZDoom/ZDTextParser.cs
@@ -39,24 +39,24 @@ namespace CodeImp.DoomBuilder.ZDoom
 		protected static readonly string CURRENT_FOLDER_PATH_MARKER = "." + Path.DirectorySeparatorChar;
 		protected static readonly string ALT_RELATIVE_PATH_MARKER = ".." + Path.AltDirectorySeparatorChar;
 		protected static readonly string ALT_CURRENT_FOLDER_PATH_MARKER = "." + Path.AltDirectorySeparatorChar;
-		
+
 		#endregion
-		
+
 		#region ================== Variables
-		
+
 		// Parsing
 		protected string whitespace = "\n \t\r\u00A0\0"; //mxd. non-breaking space is also space :)
 		protected string specialtokens = ":{}+-\n;";
 		protected bool skipregions; //mxd
 		protected bool skipeditorcomments; //mxd
-		
+
 		// Input data stream
 		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;
@@ -69,9 +69,9 @@ namespace CodeImp.DoomBuilder.ZDoom
 		protected readonly Dictionary<string, ScriptResource> scriptresources;
 		protected readonly HashSet<string> untrackedtextresources;
 		#endregion
-		
+
 		#region ================== Properties
-		
+
 		internal Stream DataStream { get { return datastream; } }
 		internal BinaryReader DataReader { get { return datareader; } }
 		public int ErrorLine { get { return errorline; } }
@@ -82,19 +82,19 @@ namespace CodeImp.DoomBuilder.ZDoom
 		internal Dictionary<string, ScriptResource> ScriptResources { get { return scriptresources; } } //mxd
 
 		#endregion
-		
+
 		#region ================== Constructor / Disposer
-		
+
 		// Constructor
 		protected ZDTextParser()
 		{
-            // Initialize
+			// Initialize
 			errordesc = null;
 			scriptresources = new Dictionary<string, ScriptResource>(StringComparer.OrdinalIgnoreCase); //mxd
 			untrackedtextresources = new HashSet<string>(StringComparer.OrdinalIgnoreCase); //mxd
 			skipregions = true; //mxd
 		}
-		
+
 		#endregion
 
 		#region ================== Parsing
@@ -103,20 +103,20 @@ namespace CodeImp.DoomBuilder.ZDoom
 		public virtual bool Parse(TextResourceData parsedata, bool clearerrors)
 		{
 			// Clear error status?
-			if(clearerrors) ClearError();
-			
+			if (clearerrors) ClearError();
+
 			// Integrity checks
 			// INFO: MapManager.CompileLump() prepends lumpname with "?" to distinguish between temporary files and files compiled in place
 			// We don't want this to show up in error messages
-			if(parsedata.Stream == null)
+			if (parsedata.Stream == null)
 			{
 				ReportError("Unable to load \"" + parsedata.Filename.Replace("?", "") + "\"");
 				return false;
 			}
 
-			if(parsedata.Stream.Length == 0)
+			if (parsedata.Stream.Length == 0)
 			{
-				if(!string.IsNullOrEmpty(sourcename) && sourcename != parsedata.Filename)
+				if (!string.IsNullOrEmpty(sourcename) && sourcename != parsedata.Filename)
 				{
 					LogWarning("Include file \"" + parsedata.Filename.Replace("?", "") + "\" is empty");
 				}
@@ -141,18 +141,18 @@ namespace CodeImp.DoomBuilder.ZDoom
 		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 (string.IsNullOrEmpty(parsedata.SourceLocation.location))
 			{
-				if(parsedata.Trackable) throw new NotSupportedException("Trackable TextResource must have a valid path.");
+				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(scriptresources.ContainsKey(path) || untrackedtextresources.Contains(path))
+			if (scriptresources.ContainsKey(path) || untrackedtextresources.Contains(path))
 				return false;
 
 			//mxd. Create TextResource for this file
-			if(parsedata.Trackable)
+			if (parsedata.Trackable)
 			{
 				textresourcepath = path;
 				ScriptResource res = new ScriptResource(parsedata, this.ScriptType);
@@ -167,7 +167,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			return true;
 		}
-		
+
 		// This returns true if the given character is whitespace
 		protected internal bool IsWhitespace(char c)
 		{
@@ -183,27 +183,27 @@ namespace CodeImp.DoomBuilder.ZDoom
 		// This returns true if the given character is a special token
 		protected internal bool IsSpecialToken(string s)
 		{
-			if(s.Length > 0) return (specialtokens.IndexOf(s[0]) > -1);
+			if (s.Length > 0) return (specialtokens.IndexOf(s[0]) > -1);
 			return false;
 		}
 
 		//mxd. This removes beginning and ending quotes from a token
-		protected internal string StripTokenQuotes(string token) 
+		protected internal string StripTokenQuotes(string token)
 		{
 			return StripQuotes(token);
 		}
-		
+
 		// This removes beginning and ending quotes from a token
 		internal static string StripQuotes(string token)
 		{
 			// Remove first character, if it is a quote
-			if(!string.IsNullOrEmpty(token) && (token[0] == '"'))
+			if (!string.IsNullOrEmpty(token) && (token[0] == '"'))
 				token = token.Substring(1);
-			
+
 			// Remove last character, if it is a quote
-			if(!string.IsNullOrEmpty(token) && (token[token.Length - 1] == '"'))
+			if (!string.IsNullOrEmpty(token) && (token[token.Length - 1] == '"'))
 				token = token.Substring(0, token.Length - 1);
-			
+
 			return token;
 		}
 
@@ -214,7 +214,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 			string token = ReadToken(false);
 			name = StripQuotes(token);
 
-			if(!string.IsNullOrEmpty(name)
+			if (!string.IsNullOrEmpty(name)
 				&& name.Length > DataManager.CLASIC_IMAGE_NAME_LENGTH
 				&& name.Length == token.Length)
 			{
@@ -224,11 +224,15 @@ namespace CodeImp.DoomBuilder.ZDoom
 
 			return true;
 		}
-		
+		protected internal bool SkipWhitespace(bool skipnewline)
+		{ 
+			return SkipWhitespace(skipnewline, false);
+		}
+
 		// This skips whitespace on the stream, placing the read
 		// position right before the first non-whitespace character
 		// Returns false when the end of the stream is reached
-		protected internal bool SkipWhitespace(bool skipnewline)
+		protected internal bool SkipWhitespace(bool skipnewline, bool SOC)
 		{
 			int offset = skipnewline ? 0 : 1;
 			char c;
@@ -240,7 +244,7 @@ namespace CodeImp.DoomBuilder.ZDoom
 				c = (char)datareader.ReadByte();
 
 				// Check if this is comment
-				if (c == '/' || c == '-') // SRB2 Lua allows -- as comment syntax
+				if ((!SOC && (c == '/' || c == '-')) || (SOC && (c == '#'))) // SRB2 Lua allows -- as comment syntax
 				{
 					if(datastream.Position == datastream.Length) return false;
 					char c2 = (char)datareader.ReadByte();
-- 
GitLab