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