mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 14:12:16 +00:00
310 lines
14 KiB
C#
Executable file
310 lines
14 KiB
C#
Executable file
using CodeImp.DoomBuilder.Data;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace CodeImp.DoomBuilder.ZDoom
|
|
{
|
|
public sealed class ZScriptStateStructure : StateStructure
|
|
{
|
|
ZScriptToken TryReadSprite(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer)
|
|
{
|
|
// this is a hack to read #### and ----.
|
|
// also [\]
|
|
string specialinvalid = "-#";
|
|
string outs = "";
|
|
long cpos = stream.Position;
|
|
for (int i = 0; ; i++)
|
|
{
|
|
cpos = stream.Position;
|
|
ZScriptToken token = tokenizer.ReadToken(true);
|
|
|
|
if ((token == null) ||
|
|
(token.Type != ZScriptTokenType.Invalid) ||
|
|
(token.Value == ";") ||
|
|
((outs.Length > 0 && token.Value[0] != outs[0]) && specialinvalid.Contains(token.Value[0])))
|
|
{
|
|
if (outs.Length == 0 && (token.Type == ZScriptTokenType.String || token.Type == ZScriptTokenType.Name))
|
|
return token;
|
|
|
|
stream.Position = cpos;
|
|
break;
|
|
}
|
|
|
|
outs += token.Value[0];
|
|
}
|
|
|
|
if (outs.Length > 0)
|
|
{
|
|
ZScriptToken tok = new ZScriptToken();
|
|
tok.Type = ZScriptTokenType.String;
|
|
tok.Value = outs;
|
|
return tok;
|
|
}
|
|
|
|
stream.Position = cpos;
|
|
return null;
|
|
}
|
|
|
|
internal ZScriptStateStructure(ActorStructure actor, ZDTextParser zdparser)
|
|
{
|
|
ZScriptParser parser = (ZScriptParser)zdparser;
|
|
Stream stream = parser.DataStream;
|
|
ZScriptTokenizer tokenizer = new ZScriptTokenizer(parser.DataReader);
|
|
parser.tokenizer = tokenizer;
|
|
|
|
// todo: parse stuff
|
|
//
|
|
string[] control_keywords = new string[] { "goto", "loop", "wait", "fail", "stop" };
|
|
|
|
while (true)
|
|
{
|
|
// expect identifier or string.
|
|
// if it's an identifier, it can be goto/loop/wait/fail/stop.
|
|
// if it's a string, it's always a sprite name.
|
|
tokenizer.SkipWhitespace();
|
|
long cpos = stream.Position;
|
|
ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.String, ZScriptTokenType.CloseCurly);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
ZScriptToken _token = TryReadSprite(parser, stream, tokenizer);
|
|
if (_token == null)
|
|
{
|
|
parser.ReportError("Expected identifier or string, got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
|
|
token = _token;
|
|
}
|
|
|
|
if (token.Type == ZScriptTokenType.CloseCurly)
|
|
{
|
|
stream.Position--;
|
|
break; // done
|
|
}
|
|
else if (token.Type == ZScriptTokenType.Identifier)
|
|
{
|
|
string s_keyword = token.Value.ToLowerInvariant();
|
|
if (control_keywords.Contains(s_keyword))
|
|
{
|
|
if (s_keyword == "goto") // just use decorate goto here. should work. but check for semicolon!
|
|
{
|
|
gotostate = new ZScriptStateGoto(actor, parser);
|
|
parser.tokenizer = tokenizer;
|
|
}
|
|
|
|
//parser.LogWarning(string.Format("keyword {0}", s_keyword));
|
|
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
parser.ReportError("Expected ;, got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
tokenizer.SkipWhitespace(); // Skip whitepace because there might be whitepsace between the state label and the colon. See https://github.com/jewalky/UltimateDoomBuilder/issues/631
|
|
}
|
|
|
|
// make sure it's not a label of the next state. if it is, break out.
|
|
long cpos2 = stream.Position; // local rewind point
|
|
ZScriptToken token2 = tokenizer.ExpectToken(ZScriptTokenType.Colon, ZScriptTokenType.Dot);
|
|
bool nextstate = (token2 != null && token2.IsValid);
|
|
stream.Position = cpos2; // rewind to before state label read
|
|
if (nextstate)
|
|
{
|
|
stream.Position = cpos;
|
|
break;
|
|
}
|
|
|
|
// it's a frame definition. read it.
|
|
string spritename = token.Value.ToLowerInvariant();
|
|
if (spritename.Length != 4)
|
|
{
|
|
parser.ReportError("Sprite name should be exactly 4 characters long (got " + spritename + ")");
|
|
return;
|
|
}
|
|
|
|
tokenizer.SkipWhitespace();
|
|
|
|
token = TryReadSprite(parser, stream, tokenizer);
|
|
if (token == null)
|
|
{
|
|
parser.ReportError("Expected sprite frame(s)");
|
|
return;
|
|
}
|
|
|
|
string spriteframes = token.Value;
|
|
|
|
//parser.LogWarning(string.Format("sprite {0} {1}", spritename, spriteframes));
|
|
|
|
// duration
|
|
int duration;
|
|
tokenizer.SkipWhitespace();
|
|
// this can be a function call, or a constant.
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.Identifier);
|
|
if (token != null && token.IsValid)
|
|
{
|
|
duration = -1;
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen);
|
|
if (token != null && token.IsValid)
|
|
{
|
|
List<ZScriptToken> tokens = parser.ParseExpression(true);
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!parser.ParseInteger(out duration))
|
|
return;
|
|
}
|
|
|
|
// now, it can also contain BRIGHT, LIGHT(), OFFSET()
|
|
string[] allspecials = new string[] { "bright", "light", "offset", "fast", "slow", "nodelay", "canraise" };
|
|
HashSet<string> specials = new HashSet<string>();
|
|
// maybe something else. I don't know.
|
|
FrameInfo info = new FrameInfo();
|
|
|
|
// Make the sprite name
|
|
string realspritename = (spritename + spriteframes[0]).ToUpperInvariant();
|
|
|
|
// Ignore some odd ZDoom things
|
|
if (/*!realspritename.StartsWith("TNT1") && */!realspritename.StartsWith("----") && !realspritename.Contains("#")) // [ZZ] some actors have only TNT1 state and receive a random image because of this
|
|
{
|
|
info.Sprite = realspritename; //mxd
|
|
info.Duration = duration;
|
|
sprites.Add(info);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
tokenizer.SkipWhitespace();
|
|
cpos2 = stream.Position;
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.Semicolon, ZScriptTokenType.OpenCurly);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
parser.ReportError("Expected identifier, ;, or {, got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
|
|
// if it's opencurly, it means that everything else is an anonymous block.
|
|
// skip/parse that.
|
|
// if it's semicolon, it means end of the frame.
|
|
// if it's BRIGHT, LIGHT() or OFFSET(), it should be processed.
|
|
// if it's something else (but identifier), then it's an action function call, process it.
|
|
if (token.Type == ZScriptTokenType.OpenCurly)
|
|
{
|
|
stream.Position--;
|
|
if (!parser.SkipBlock())
|
|
return;
|
|
break; // this block is done
|
|
}
|
|
else if (token.Type == ZScriptTokenType.Semicolon)
|
|
{
|
|
break; // done
|
|
}
|
|
else // identifier
|
|
{
|
|
string special = token.Value.ToLowerInvariant();
|
|
if (allspecials.Contains(special))
|
|
{
|
|
if (specials.Contains(special))
|
|
{
|
|
parser.ReportError("'" + special + "' cannot be used twice");
|
|
return;
|
|
}
|
|
|
|
specials.Add(special);
|
|
|
|
if (special == "bright")
|
|
{
|
|
info.Bright = true;
|
|
}
|
|
else if (special == "light" || special == "offset")
|
|
{
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
parser.ReportError("Expected (, got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
|
|
List<ZScriptToken> tokens = parser.ParseExpression(true);
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
|
|
// parse the light expression.
|
|
if (special == "light")
|
|
{
|
|
if (tokens.Count != 1 || (tokens[0].Type != ZScriptTokenType.String && tokens[0].Type != ZScriptTokenType.Identifier))
|
|
{
|
|
parser.ReportError("Light() special takes one string argument");
|
|
return;
|
|
}
|
|
|
|
info.LightName = tokens[0].Value;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
stream.Position = cpos2;
|
|
string actionfunction = parser.ParseDottedIdentifier();
|
|
//parser.LogWarning("actionfunction = " + actionfunction);
|
|
if (actionfunction == null)
|
|
return;
|
|
//
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen);
|
|
if (token != null && token.IsValid)
|
|
{
|
|
List<ZScriptToken> tokens = parser.ParseExpression(true);
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
|
|
// possibly do something with the arguments? not now though.
|
|
}
|
|
|
|
// expect semicolon and break.
|
|
tokenizer.SkipWhitespace();
|
|
token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon);
|
|
if (token == null || !token.IsValid)
|
|
{
|
|
parser.ReportError("Expected ;, got " + ((Object)token ?? "<null>").ToString());
|
|
return;
|
|
}
|
|
|
|
break;
|
|
} // if not special
|
|
} // if identifier
|
|
} // frame parsing loop (inner)
|
|
} // state parsing loop (outer)
|
|
|
|
TrimLeft();
|
|
}
|
|
}
|
|
}
|