From f8e7bd37397c5c7f7896458cda2aa273e4df691c Mon Sep 17 00:00:00 2001 From: spherallic <spherallic@gmail.com> Date: Fri, 15 Sep 2023 21:28:20 +0200 Subject: [PATCH] Add Lua parser for custom objects --- Source/Core/Builder.csproj | 2 + Source/Core/Config/ScriptConfiguration.cs | 4 +- Source/Core/Data/DataManager.cs | 71 ++++++++- Source/Core/Data/DataReader.cs | 3 + Source/Core/Data/PK3StructuredReader.cs | 29 ++++ Source/Core/Data/WADReader.cs | 8 + Source/Core/SRB2/LuaMobjStructure.cs | 141 +++++++++++++++++ Source/Core/SRB2/LuaParser.cs | 176 ++++++++++++++++++++++ Source/Core/ZDoom/ActorStructure.cs | 5 +- Source/Core/ZDoom/ZDTextParser.cs | 4 +- 10 files changed, 436 insertions(+), 7 deletions(-) create mode 100644 Source/Core/SRB2/LuaMobjStructure.cs create mode 100644 Source/Core/SRB2/LuaParser.cs diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index 7e1443f44..e97ceaaf3 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -312,6 +312,8 @@ <Compile Include="Rendering\Vector4.cs" /> <Compile Include="Rendering\VertexBuffer.cs" /> <Compile Include="Rendering\VisualSlopeHandle.cs" /> + <Compile Include="SRB2\LuaMobjStructure.cs" /> + <Compile Include="SRB2\LuaParser.cs" /> <Compile Include="VisualModes\VisualSlope.cs" /> <Compile Include="Windows\ILinedefEditForm.cs" /> <Compile Include="Windows\ISectorEditForm.cs" /> diff --git a/Source/Core/Config/ScriptConfiguration.cs b/Source/Core/Config/ScriptConfiguration.cs index 9a753dd9d..98f2501e6 100755 --- a/Source/Core/Config/ScriptConfiguration.cs +++ b/Source/Core/Config/ScriptConfiguration.cs @@ -58,7 +58,9 @@ namespace CodeImp.DoomBuilder.Config KEYCONF, FONTDEFS, ZSCRIPT, - DECALDEF + DECALDEF, + //SOC, + LUA } public class ScriptConfiguration : IComparable<ScriptConfiguration> diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs index a0e148503..5e8281c66 100755 --- a/Source/Core/Data/DataManager.cs +++ b/Source/Core/Data/DataManager.cs @@ -133,6 +133,7 @@ namespace CodeImp.DoomBuilder.Data // Things combined with things created from Decorate private DecorateParser decorate; private ZScriptParser zscript; + private LuaParser lua; private Dictionary<string, ActorStructure> zdoomclasses; private List<ThingCategory> thingcategories; private Dictionary<int, ThingTypeInfo> thingtypes; @@ -460,6 +461,7 @@ namespace CodeImp.DoomBuilder.Data LoadDehackedThings(); LoadZScriptThings(); LoadDecorateThings(); + LoadLuaThings(); ApplyDehackedThings(); FixRenamedDehackedSprites(); int thingcount = ApplyZDoomThings(spawnnums, doomednums); @@ -1844,8 +1846,42 @@ namespace CodeImp.DoomBuilder.Data } } - // [ZZ] this retrieves ZDoom actor structure by class name. - public ActorStructure GetZDoomActor(string classname) + // sphere: This loads things from SRB2 Lua files. + private void LoadLuaThings() + { + // Create new parser + lua = new LuaParser(); + + // Go for all opened containers + foreach (DataReader dr in containers) + { + // Load Lua info cumulatively (the last Lua 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.GetLuaData()) + { + // Parse the data + data.Stream.Seek(0, SeekOrigin.Begin); + lua.Parse(data, true); + + //mxd. DECORATE lumps are interdepandable. Can't carry on... + if (lua.HasError) + { + lua.LogError(); + break; + } + } + } + + //mxd. Add to text resources collection + currentreader = null; + + if (lua.HasError) + lua.ClearActors(); + } + + // [ZZ] this retrieves ZDoom actor structure by class name. + public ActorStructure GetZDoomActor(string classname) { classname = classname.ToLowerInvariant(); ActorStructure outv; @@ -1887,6 +1923,37 @@ namespace CodeImp.DoomBuilder.Data Dictionary<string, ActorStructure> mergedAllActorsByClass = decorate.AllActorsByClass.Concat(zscript.AllActorsByClass.Where(x => !decorate.AllActorsByClass.ContainsKey(x.Key))).ToDictionary(k => k.Key, v => v.Value); zdoomclasses = mergedAllActorsByClass; + // Parse SRB2 Lua/SOC objects + IEnumerable<ActorStructure> mobjs = lua.Mobjs; // lua.Mobjs.Union(soc.Mobjs); + + foreach (ActorStructure actor in mobjs) + { + // Check if we want to add this actor + if (actor.DoomEdNum > 0) + { + // Check if we can find this thing in our existing collection + if (thingtypes.ContainsKey(actor.DoomEdNum)) + { + // Update the thing + thingtypes[actor.DoomEdNum].ModifyByDecorateActor(actor); + } + else + { + // Find the category to put the actor in + ThingCategory cat = GetThingCategory(null, thingcategories, GetCategoryInfo(actor)); //mxd + + // Add new thing + ThingTypeInfo t = new ThingTypeInfo(cat, actor); ; + + cat.AddThing(t); + thingtypes.Add(t.Index, t); + } + + // Count + counter++; + } + } + // Dictionary of replaced actors that have to be recategorized Dictionary<int, ActorStructure> recategorizeactors = new Dictionary<int, ActorStructure>(); diff --git a/Source/Core/Data/DataReader.cs b/Source/Core/Data/DataReader.cs index 932ec8c02..8ea8d866a 100755 --- a/Source/Core/Data/DataReader.cs +++ b/Source/Core/Data/DataReader.cs @@ -260,6 +260,9 @@ namespace CodeImp.DoomBuilder.Data // When implemented, this returns the list of IWAD infos public abstract List<IWadInfo> GetIWadInfos(); + // When implemented, this returns Lua lumps + public abstract IEnumerable<TextResourceData> GetLuaData(); + #endregion #region ================== Load/Save (mxd) diff --git a/Source/Core/Data/PK3StructuredReader.cs b/Source/Core/Data/PK3StructuredReader.cs index e3320dc56..68fce0308 100755 --- a/Source/Core/Data/PK3StructuredReader.cs +++ b/Source/Core/Data/PK3StructuredReader.cs @@ -766,6 +766,35 @@ namespace CodeImp.DoomBuilder.Data #endregion + #region ================== Lua + + // sphere + public override IEnumerable<TextResourceData> GetLuaData() + { + // 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(GetAllFilesWhichTitleStartsWith("", "LUA_", true)); + files.AddRange(GetFilesWithExt("", "lua", 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.GetLuaData()); + + return result; + } + + #endregion + + #region ================== Generic text lumps loading (mxd) public override IEnumerable<TextResourceData> GetTextLumpData(ScriptType scripttype, bool singular, bool partialtitlematch) diff --git a/Source/Core/Data/WADReader.cs b/Source/Core/Data/WADReader.cs index 47d06605c..624fda838 100755 --- a/Source/Core/Data/WADReader.cs +++ b/Source/Core/Data/WADReader.cs @@ -1135,6 +1135,14 @@ namespace CodeImp.DoomBuilder.Data return result; } + // sphere + public override IEnumerable<TextResourceData> GetLuaData() + { + if (issuspended) throw new Exception("Data reader is suspended"); + + return GetAllLumpsData("LUA_"); + } + //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 new file mode 100644 index 000000000..34993e776 --- /dev/null +++ b/Source/Core/SRB2/LuaMobjStructure.cs @@ -0,0 +1,141 @@ +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 LuaMobjStructure : ActorStructure + { + #region ================== DECORATE Actor Structure parsing + + internal LuaMobjStructure(ZDTextParser zdparser, string objname) + { + classname = string.Empty; + + LuaParser parser = (LuaParser)zdparser; + bool done = false; //mxd + General.WriteLogLine(objname); + + if (string.IsNullOrEmpty(objname)) + { + parser.ReportError("Expected actor class name"); + return; + } + + // Now parse the contents of actor structure + + while (parser.SkipWhitespace(true)) + { + string token = parser.ReadToken(); + token = token.ToLowerInvariant(); + + switch (token) + { + case "}": + // Actor scope ends here, break out of this parse loop + done = true; + break; + + // Property + default: + General.WriteLogLine(token); + // Property begins with $? Then the whole line is a single value + if (token.Contains("$")) + { + bool isflag = false; + bool isname = false; + + switch (token) + { + case "$name": + token = "$title"; + isname = true; + break; + case "$wallsprite": + case "$flatsprite": + case "$spawnceiling": + isflag = true; + break; + } + // This is for editor-only properties such as $sprite and $category + string t = token; + if (isflag) + { + parser.SkipWhitespace(false); + string val = parser.ReadLine(); + flags[t.Substring(1)] = (val == "true" || val == "1") ? true : false; + } + else + props[token] = new List<string> { (parser.SkipWhitespace(false) ? parser.ReadLine() : "") }; + + if (isname) + { + General.WriteLogLine("name = " + props["$title"][0]); + } + } + else + { + string tokenname = token; + parser.SkipWhitespace(true); + token = parser.ReadToken(); + + if (token != "=") + { + parser.ReportError("Invalid Lua object parameter definition, missing ="); + return; + } + + parser.SkipWhitespace(true); + token = parser.ReadToken(); + + if (!token.EndsWith(",")) + { + done = true; + } + + 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": + doomednum = int.Parse(values[0]); + General.WriteLogLine("parsed doomednum: " + DoomEdNum); + goto default; + case "height": + case "radius": + props[tokenname] = new List<string>() { GetNumbers(values[0]).ToString() }; + break; + default: + props[tokenname] = values; + break; + } + } + break; + } + + if (done) break; //mxd + } + + // parsing done, process thing arguments + ParseCustomArguments(); + } + + private static string GetNumbers(string input) + { + return new string(input.Where(c => char.IsDigit(c)).ToArray()); + } + + #endregion + } +} diff --git a/Source/Core/SRB2/LuaParser.cs b/Source/Core/SRB2/LuaParser.cs new file mode 100644 index 000000000..c2b842644 --- /dev/null +++ b/Source/Core/SRB2/LuaParser.cs @@ -0,0 +1,176 @@ + +#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 LuaParser : 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 LuaParser() + { + // 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 Lua 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)) + { + // Read a token + string token = ReadToken(); + if (!string.IsNullOrEmpty(token)) + { + if (!token.StartsWith("mobjinfo[") || !token.EndsWith("]")) continue; + string objname = token.Substring(9).TrimEnd(new char[] { ']' }); + + SkipWhitespace(true); + token = ReadToken(); + if (token != "=") + { + continue; + } + + SkipWhitespace(true); + token = ReadToken(); + if (token != "{") + { + ReportError("Invalid object definition, missing {"); + return false; + } + // Read actor structure + ActorStructure mobj = new LuaMobjStructure(this, objname); + if (mobj.props.ContainsKey("doomednum")) + General.WriteLogLine("Mobj doomednum = " + mobj.props["doomednum"][0]); + if (this.HasError) return false; + + mobjs[mobj.DoomEdNum] = mobj; + } + } + + + // 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/ActorStructure.cs b/Source/Core/ZDoom/ActorStructure.cs index 16e88aa5a..abd14ad27 100755 --- a/Source/Core/ZDoom/ActorStructure.cs +++ b/Source/Core/ZDoom/ActorStructure.cs @@ -22,6 +22,7 @@ using System.Globalization; using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Types; +using CodeImp.DoomBuilder.Map; #endregion @@ -60,7 +61,7 @@ namespace CodeImp.DoomBuilder.ZDoom internal DecorateCategoryInfo catinfo; // [ZZ] direct ArgumentInfos (from game configuration), or own ArgumentInfos (from props) - internal ArgumentInfo[] args = new ArgumentInfo[5]; + internal ArgumentInfo[] args = new ArgumentInfo[Thing.NUM_ARGS]; // States internal Dictionary<string, StateStructure> states; @@ -404,7 +405,7 @@ namespace CodeImp.DoomBuilder.ZDoom /// </summary> public void ParseCustomArguments() { - for (int i = 0; i < 5; i++) + for (int i = 0; i < Thing.NUM_ARGS; i++) { if (HasProperty("$arg" + i)) args[i] = new ArgumentInfo(this, i); diff --git a/Source/Core/ZDoom/ZDTextParser.cs b/Source/Core/ZDoom/ZDTextParser.cs index cbd920eb4..1e106dab4 100755 --- a/Source/Core/ZDoom/ZDTextParser.cs +++ b/Source/Core/ZDoom/ZDTextParser.cs @@ -240,11 +240,11 @@ namespace CodeImp.DoomBuilder.ZDoom c = (char)datareader.ReadByte(); // Check if this is comment - if(c == '/') + if (c == '/' || c == '-') // SRB2 Lua allows -- as comment syntax { if(datastream.Position == datastream.Length) return false; char c2 = (char)datareader.ReadByte(); - if(c2 == '/') + if(c2 == c) { // Check if not a special comment with a token if(datastream.Position == datastream.Length) return false; -- GitLab