Added support for some niche ZScript and MODELDEF syntax. Resolves #747

This commit is contained in:
Alison Watson 2022-08-23 05:37:58 -06:00 committed by GitHub
parent c1e69eb548
commit 6b0b047c57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 150 additions and 54 deletions

View file

@ -10,7 +10,7 @@ using CodeImp.DoomBuilder.Rendering;
#endregion
namespace CodeImp.DoomBuilder.ZDoom
namespace CodeImp.DoomBuilder.ZDoom
{
internal sealed class ModeldefStructure
{
@ -84,7 +84,7 @@ namespace CodeImp.DoomBuilder.ZDoom
{
// Read modeldef structure contents
bool parsingfinished = false;
while(!parsingfinished && parser.SkipWhitespace(true))
while(!parsingfinished && parser.SkipWhitespace(true))
{
string token = parser.ReadToken().ToLowerInvariant();
if(string.IsNullOrEmpty(token)) continue;
@ -125,24 +125,24 @@ namespace CodeImp.DoomBuilder.ZDoom
// Model path
token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
if(string.IsNullOrEmpty(token))
if(string.IsNullOrEmpty(token))
{
parser.ReportError("Expected model name");
return false;
}
}
// Check invalid path chars
if(!parser.CheckInvalidPathChars(token)) return false;
// Check extension
string modelext = Path.GetExtension(token);
if(string.IsNullOrEmpty(modelext))
if(string.IsNullOrEmpty(modelext))
{
parser.ReportError("Model \"" + token + "\" won't be loaded. Models without extension are not supported by GZDoom");
return false;
}
if(modelext != ".md3" && modelext != ".md2" && modelext != ".3d" && modelext != ".obj")
if(modelext != ".md3" && modelext != ".md2" && modelext != ".3d" && modelext != ".obj")
{
parser.ReportError("Model \"" + token + "\" won't be loaded. Only Unreal 3D, MD2, MD3, and OBJ models are supported");
return false;
@ -176,11 +176,11 @@ namespace CodeImp.DoomBuilder.ZDoom
// Skin path
token = parser.StripTokenQuotes(parser.ReadToken(false)).ToLowerInvariant(); // Don't skip newline
if(string.IsNullOrEmpty(token))
if(string.IsNullOrEmpty(token))
{
parser.ReportError("Expected skin path");
return false;
}
}
// Check invalid path chars
if(!parser.CheckInvalidPathChars(token)) return false;
@ -252,7 +252,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "scale":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref scale.Y))
if(!parser.ReadSignedFloat(token, ref scale.Y))
{
// Not numeric!
parser.ReportError("Expected Scale X value, but got \"" + token + "\"");
@ -261,7 +261,7 @@ namespace CodeImp.DoomBuilder.ZDoom
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref scale.X))
if(!parser.ReadSignedFloat(token, ref scale.X))
{
// Not numeric!
parser.ReportError("Expected Scale Y value, but got \"" + token + "\"");
@ -270,7 +270,7 @@ namespace CodeImp.DoomBuilder.ZDoom
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref scale.Z))
if(!parser.ReadSignedFloat(token, ref scale.Z))
{
// Not numeric!
parser.ReportError("Expected Scale Z value, but got \"" + token + "\"");
@ -281,7 +281,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "offset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref offset.X))
if(!parser.ReadSignedFloat(token, ref offset.X))
{
// Not numeric!
parser.ReportError("Expected Offset X value, but got \"" + token + "\"");
@ -290,7 +290,7 @@ namespace CodeImp.DoomBuilder.ZDoom
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref offset.Y))
if(!parser.ReadSignedFloat(token, ref offset.Y))
{
// Not numeric!
parser.ReportError("Expected Offset Y value, but got \"" + token + "\"");
@ -299,7 +299,7 @@ namespace CodeImp.DoomBuilder.ZDoom
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref offset.Z))
if(!parser.ReadSignedFloat(token, ref offset.Z))
{
// Not numeric!
parser.ReportError("Expected Offset Z value, but got \"" + token + "\"");
@ -310,7 +310,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "zoffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref offset.Z))
if(!parser.ReadSignedFloat(token, ref offset.Z))
{
// Not numeric!
parser.ReportError("Expected ZOffset value, but got \"" + token + "\"");
@ -321,7 +321,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "angleoffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref angleoffset))
if(!parser.ReadSignedFloat(token, ref angleoffset))
{
// Not numeric!
parser.ReportError("Expected AngleOffset value, but got \"" + token + "\"");
@ -332,7 +332,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "pitchoffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref pitchoffset))
if(!parser.ReadSignedFloat(token, ref pitchoffset))
{
// Not numeric!
parser.ReportError("Expected PitchOffset value, but got \"" + token + "\"");
@ -343,7 +343,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "rolloffset":
parser.SkipWhitespace(true);
token = parser.ReadToken();
if(!parser.ReadSignedFloat(token, ref rolloffset))
if(!parser.ReadSignedFloat(token, ref rolloffset))
{
// Not numeric!
parser.ReportError("Expected RollOffset value, but got \"" + token + "\"");
@ -366,7 +366,7 @@ namespace CodeImp.DoomBuilder.ZDoom
parser.LogWarning("INHERITACTORPITCH flag is deprecated. Consider using USEACTORPITCH flag instead");
break;
case "inheritactorroll":
case "inheritactorroll":
useactorroll = true;
parser.LogWarning("INHERITACTORROLL flag is deprecated. Consider using USEACTORROLL flag instead");
break;
@ -375,7 +375,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "frameindex":
// Sprite name
parser.SkipWhitespace(true);
string fispritename = parser.ReadToken();
string fispritename = ZDTextParser.StripQuotes(parser.ReadToken());
if(string.IsNullOrEmpty(fispritename))
{
parser.ReportError("Expected sprite name");
@ -383,13 +383,13 @@ namespace CodeImp.DoomBuilder.ZDoom
}
if(fispritename.Length != 4)
{
parser.ReportError("Sprite name must be 4 characters long");
parser.ReportError("Sprite name must be 4 characters long, got \"" + fispritename + "\"");
return false;
}
// Sprite frame
parser.SkipWhitespace(true);
token = parser.ReadToken();
token = ZDTextParser.StripQuotes(parser.ReadToken());
if(string.IsNullOrEmpty(token))
{
parser.ReportError("Expected sprite frame");

View file

@ -9,17 +9,17 @@ using CodeImp.DoomBuilder.Data;
//mxd. Parser used to determine which script type given text is.
namespace CodeImp.DoomBuilder.ZDoom.Scripting
{
internal sealed class ScriptTypeParserSE : ZDTextParser
internal sealed class ScriptTypeParserSE : ZDTextParser
{
internal override ScriptType ScriptType { get { return scripttype; } }
private ScriptType scripttype;
internal ScriptTypeParserSE()
internal ScriptTypeParserSE()
{
scripttype = ScriptType.UNKNOWN;
}
public override bool Parse(TextResourceData data, bool clearerrors)
public override bool Parse(TextResourceData data, bool clearerrors)
{
//mxd. Already parsed?
if(!base.AddTextResource(data))
@ -32,23 +32,23 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
if(!base.Parse(data, clearerrors)) return false;
// Continue until at the end of the stream
while(SkipWhitespace(true))
while(SkipWhitespace(true))
{
string token = ReadToken();
long cpos = datastream.Position;
if(!string.IsNullOrEmpty(token))
if(!string.IsNullOrEmpty(token))
{
token = token.ToUpperInvariant();
if(token == "MODEL")
if(token == "MODEL")
{
SkipWhitespace(true);
ReadToken(); //should be model name
SkipWhitespace(true);
token = ReadToken();//should be opening brace
if(token == "{")
if(token == "{")
{
scripttype = ScriptType.MODELDEF;
return true;
@ -63,8 +63,8 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
ReadToken(); //should be script parameters/type
SkipWhitespace(true);
token = ReadToken(); //should be opening brace
if(token == "{")
if(token == "{")
{
scripttype = ScriptType.ACS;
return true;
@ -80,7 +80,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
token = ReadToken();
// [ZZ] note: original code compared token to REPLACES without doing ToUpper
if(token == ":" || token == "{" || (token != null && token.ToUpperInvariant() == "REPLACES"))
if(token == ":" || token == "{" || (token != null && token.ToUpperInvariant() == "REPLACES"))
{
scripttype = ScriptType.DECORATE;
return true;
@ -96,7 +96,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
token = ReadToken();
}
if(token == "{")
if(token == "{")
{
scripttype = ScriptType.DECORATE;
return true;
@ -120,7 +120,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
SkipWhitespace(true);
token = ReadToken();
if ((otoken != "ENUM" && token == ":") || token == "{" || (otoken == "CLASS" && (token != null && token.ToUpperInvariant() == "REPLACES")))
if ((otoken != "ENUM" && (token == ":" || token == ";")) || token == "{" || (otoken == "CLASS" && (token != null && token.ToUpperInvariant() == "REPLACES")))
{
scripttype = ScriptType.ZSCRIPT;
return true;
@ -129,7 +129,7 @@ namespace CodeImp.DoomBuilder.ZDoom.Scripting
SkipWhitespace(true);
token = ReadToken(); //should be actor name
if (token == "{")
if (token == "{" || token == ";")
{
scripttype = ScriptType.ZSCRIPT;
return true;

View file

@ -507,6 +507,33 @@ namespace CodeImp.DoomBuilder.ZDoom
return version;
}
private string ParseAction()
{
string[] actioncontexts = new string[] { "actor", "overlay", "weapon", "item" };
tokenizer.SkipWhitespace();
ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen);
if (token == null || !token.IsValid)
{
return "default";
}
tokenizer.SkipWhitespace();
token = tokenizer.ExpectToken(ZScriptTokenType.Identifier);
if (token == null || !token.IsValid || !actioncontexts.Contains(token.Value.ToLowerInvariant()))
{
parser.ReportError("Expected actor, overlay, weapon, or item, got " + ((Object)token ?? "<null>").ToString());
return null;
}
string context = token.Value.Trim();
tokenizer.SkipWhitespace();
token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen);
if (token == null || !token.IsValid)
{
parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString());
return null;
}
return context;
}
internal ZScriptActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo, string _classname, string _replacesname, string _parentname)
{
this.catinfo = catinfo; //mxd
@ -522,10 +549,10 @@ namespace CodeImp.DoomBuilder.ZDoom
mixins = new List<string>();
ZScriptToken cls_open = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly);
ZScriptToken cls_open = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly, ZScriptTokenType.Semicolon);
if (cls_open == null || !cls_open.IsValid)
{
parser.ReportError("Expected {, got " + ((Object)cls_open ?? "<null>").ToString());
parser.ReportError("Expected { or ;, got " + ((Object)cls_open ?? "<null>").ToString());
return;
}
@ -562,8 +589,15 @@ namespace CodeImp.DoomBuilder.ZDoom
ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly);
if (token == null || !token.IsValid)
{
parser.ReportError("Expected identifier, got " + ((Object)cls_open ?? "<null>").ToString());
return;
if(token == null && cls_open.Type == ZScriptTokenType.Semicolon)
{
break;
}
else
{
parser.ReportError("Expected identifier, got " + ((Object)cls_open ?? "<null>").ToString());
return;
}
}
if (token.Type == ZScriptTokenType.CloseCurly) // end of class
break;
@ -663,6 +697,13 @@ namespace CodeImp.DoomBuilder.ZDoom
return;
}
if (b_lower == "action")
{
string context = ParseAction().ToLowerInvariant();
if (context == null)
return;
}
modifiers.Add(b_lower);
}
else
@ -900,7 +941,7 @@ namespace CodeImp.DoomBuilder.ZDoom
else if (arraylen != -1) _args = " [" + arraylen.ToString() + "]";
parser.LogWarning(string.Format("{0} {1} {2}{3}", string.Join(" ", modifiers.ToArray()), string.Join(", ", types.ToArray()), name, _args));
}*/
// update 08.02.17: add user variables from ZScript actors.
if (args == null && types.Count == 1) // it's a field
{

View file

@ -262,10 +262,10 @@ namespace CodeImp.DoomBuilder.ZDoom
string localtextresourcepath = textresourcepath; //mxd
ZScriptTokenizer localtokenizer = tokenizer; // [ZZ]
//INFO: ZDoom DECORATE include paths can't be relative ("../actor.txt")
//or absolute ("d:/project/actor.txt")
//INFO: ZDoom DECORATE include paths can't be relative ("../actor.txt")
//or absolute ("d:/project/actor.txt")
//or have backward slashes ("info\actor.txt")
//include paths are relative to the first parsed entry, not the current one
//include paths are relative to the first parsed entry, not the current one
//also include paths may or may not be quoted
//mxd. Sanity checks
if (string.IsNullOrEmpty(filename))
@ -353,7 +353,7 @@ namespace CodeImp.DoomBuilder.ZDoom
datastream.Position = cpos;
return ol;
}
if (token.Type == ZScriptTokenType.OpenParen)
{
nestingLevel++;
@ -374,10 +374,7 @@ namespace CodeImp.DoomBuilder.ZDoom
internal bool SkipBlock(bool eatSemicolon = true)
{
List<ZScriptToken> ol = new List<ZScriptToken>();
//
int nestingLevel = 0;
//
long cpos = datastream.Position;
ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly);
if (token == null || !token.IsValid)
@ -415,8 +412,6 @@ namespace CodeImp.DoomBuilder.ZDoom
return false;
}
}
ol.Add(token);
}
// there is POTENTIALLY a semicolon after the class definition. it's not supposed to be there, but it's acceptable (GZDoom.pk3 has this)
@ -428,6 +423,66 @@ namespace CodeImp.DoomBuilder.ZDoom
return true;
}
internal bool SkipClassBlock()
{
int nestingLevel = 0;
long cpos = datastream.Position;
ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly, ZScriptTokenType.Semicolon);
bool semico = token.Type == ZScriptTokenType.Semicolon;
if (token == null || !token.IsValid)
{
ReportError("Expected { or ;, got " + ((Object)token ?? "<null>").ToString());
return false;
}
// parse everything between { and } or ; and eof
nestingLevel = 1;
while (nestingLevel > 0)
{
cpos = datastream.Position;
token = tokenizer.ReadToken(true);
//LogWarning(token.ToString());
if (token == null)
{
if (semico)
{
nestingLevel--;
break;
}
else
{
ReportError("Expected a token");
return false;
}
}
if (token.Type != ZScriptTokenType.Invalid)
continue;
if (token.Value == "{")
{
nestingLevel++;
}
else if (token.Value == "}")
{
nestingLevel--;
if (nestingLevel < 0)
{
ReportError("Closing parenthesis without an opening one");
return false;
}
}
}
// there is POTENTIALLY a semicolon after the class definition. it's not supposed to be there, but it's acceptable (GZDoom.pk3 has this)
cpos = datastream.Position;
ZScriptToken tailtoken = tokenizer.ReadToken();
if (tailtoken == null || tailtoken.Type != ZScriptTokenType.Semicolon)
datastream.Position = cpos;
return true;
}
internal List<ZScriptToken> ParseBlock(bool allowsingle)
{
List<ZScriptToken> ol = new List<ZScriptToken>();
@ -748,7 +803,7 @@ namespace CodeImp.DoomBuilder.ZDoom
return false;
}
}
else if (token.Type == ZScriptTokenType.OpenCurly)
else if (token.Type == ZScriptTokenType.Semicolon || token.Type == ZScriptTokenType.OpenCurly)
{
datastream.Position--;
break;
@ -760,7 +815,7 @@ namespace CodeImp.DoomBuilder.ZDoom
long cpos = datastream.Position;
//List<ZScriptToken> classblocktokens = ParseBlock(false);
//if (classblocktokens == null) return false;
if (!SkipBlock()) return false;
if (!SkipClassBlock()) return false;
string log_inherits = ((tok_parentname != null) ? "inherits " + tok_parentname.Value : "");
if (tok_replacename != null) log_inherits += ((log_inherits.Length > 0) ? ", " : "") + "replaces " + tok_replacename.Value;
@ -1113,7 +1168,7 @@ namespace CodeImp.DoomBuilder.ZDoom
}
// [ZZ] inherit arguments from game configuration
//
//
if (!actor.props.ContainsKey("$clearargs"))
{
for (int i = 0; i < 5; i++)