public string GetPropertyValueString(string propname, int valueindex, string defaultvalue, bool stripquotes) { if (Properties.ContainsKey(propname) && (Properties[propname].Count > valueindex)) { return(stripquotes ? ZDTextParser.StripQuotes(Properties[propname][valueindex]) : Properties[propname][valueindex]); } return(defaultvalue); }
} //mxd. Added "stripquotes" parameter public string GetPropertyValueString(string propname, int valueindex, bool stripquotes) { if (props.ContainsKey(propname) && (props[propname].Count > valueindex)) { return(stripquotes ? ZDTextParser.StripQuotes(props[propname][valueindex]) : props[propname][valueindex]); } if (!skipsuper && (baseclass != null)) { return(baseclass.GetPropertyValueString(propname, valueindex, stripquotes)); } return(""); }
internal ZScriptActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo, string _classname, string _replacesname, string _parentname) { this.catinfo = catinfo; //mxd parser = (ZScriptParser)zdparser; stream = parser.DataStream; tokenizer = new ZScriptTokenizer(parser.DataReader); parser.tokenizer = tokenizer; classname = _classname; replaceclass = _replacesname; //baseclass = parser.GetArchivedActorByName(_parentname); // this is not guaranteed to work here mixins = new List <string>(); ZScriptToken cls_open = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); if (cls_open == null || !cls_open.IsValid) { parser.ReportError("Expected {, got " + ((Object)cls_open ?? "<null>").ToString()); return; } // this dict holds temporary user settings per field (function, etc) Dictionary <string, List <string> > var_props = new Dictionary <string, List <string> >(); // in the class definition, we can have the following: // - Defaults block // - States block // - method signature: [native] [action] <type [, type [...]]> <name> (<arguments>); // - method: <method signature (except native)> <block> // - field declaration: [native] <type> <name>; // - enum definition: enum <name> <block>; // we are skipping everything, except Defaults and States. while (true) { var_props.Clear(); while (true) { ZScriptToken tt = tokenizer.ExpectToken(ZScriptTokenType.Whitespace, ZScriptTokenType.BlockComment, ZScriptTokenType.LineComment, ZScriptTokenType.Newline); if (tt == null || !tt.IsValid) { break; } if (tt.Type == ZScriptTokenType.LineComment) { ParseGZDBComment(var_props, tt.Value); } } //tokenizer.SkipWhitespace(); long ocpos = stream.Position; 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.Type == ZScriptTokenType.CloseCurly) // end of class { break; } string b_lower = token.Value.ToLowerInvariant(); switch (b_lower) { case "default": if (!ParseDefaultBlock()) { return; } continue; case "states": if (!ParseStatesBlock()) { return; } continue; case "enum": if (!parser.ParseEnum()) { return; } continue; case "const": if (!parser.ParseConst()) { return; } continue; // apparently we can have a struct inside a class, but not another class. case "struct": if (!parser.ParseClassOrStruct(true, false, false, null)) { return; } continue; // new properties syntax case "property": if (!ParseProperty()) { return; } continue; // new flags syntax case "flagdef": if (!ParseFlagdef()) { return; } continue; // mixins case "mixin": if (!ParseMixin()) { return; } continue; default: stream.Position = ocpos; break; } // try to read in a variable/method. bool bmethod = false; string[] availablemodifiers = new string[] { "static", "native", "action", "readonly", "protected", "private", "virtual", "override", "meta", "transient", "deprecated", "final", "play", "ui", "clearscope", "virtualscope", "version", "const" }; string[] versionedmodifiers = new string[] { "version", "deprecated" }; string[] methodmodifiers = new string[] { "action", "virtual", "override", "final" }; HashSet <string> modifiers = new HashSet <string>(); List <string> types = new List <string>(); List <List <int> > typearraylens = new List <List <int> >(); List <string> names = new List <string>(); List <List <int> > arraylens = new List <List <int> >(); List <ZScriptToken> args = null; // this is for the future //List<ZScriptToken> body = null; while (true) { tokenizer.SkipWhitespace(); long cpos = stream.Position; token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); if (token == null || !token.IsValid) { parser.ReportError("Expected modifier or name, got " + ((Object)cls_open ?? "<null>").ToString()); return; } b_lower = token.Value.ToLowerInvariant(); if (availablemodifiers.Contains(b_lower)) { if (modifiers.Contains(b_lower)) { parser.ReportError("Field/method modifier '" + b_lower + "' was specified twice"); return; } if (methodmodifiers.Contains(b_lower)) { bmethod = true; } if (versionedmodifiers.Contains(b_lower)) { string version = ParseVersion(b_lower == "version"); // deprecated doesn't require version string for historical reasons. (compatibility with old gzdoom.pk3) if (version == null && b_lower == "version") { return; } } modifiers.Add(b_lower); } else { stream.Position = cpos; break; } } // read in the type name(s) // type name can be: // - identifier // - identifier<identifier> while (true) { tokenizer.SkipWhitespace(); string typename = ParseTypeName(); if (typename == null) { return; } types.Add(typename.ToLowerInvariant()); typearraylens.Add(null); long cpos = stream.Position; tokenizer.SkipWhitespace(); token = tokenizer.ExpectToken(ZScriptTokenType.Comma, ZScriptTokenType.Identifier, ZScriptTokenType.OpenSquare); if (token != null && !token.IsValid) { parser.ReportError("Expected comma, identifier or array dimensions, got " + ((Object)token ?? "<null>").ToString()); return; } if (token == null || token.Type != ZScriptTokenType.Comma) { stream.Position = cpos; if (token.Type == ZScriptTokenType.OpenSquare) { List <int> typelens = ParseArrayDimensions(); if (typelens == null) // error { return; } typearraylens[typearraylens.Count - 1] = typelens; } break; } } while (true) { string name = null; List <int> lens = null; // read in the method/field name tokenizer.SkipWhitespace(); token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); if (token == null || !token.IsValid) { parser.ReportError("Expected field/method name, got " + ((Object)token ?? "<null>").ToString()); return; } name = token.Value.ToLowerInvariant(); // check the token. if it's a (, then it's a method. if it's a ;, then it's a field, if it's a [ it's an array field. // if it's a field and bmethod=true, report error. tokenizer.SkipWhitespace(); long cpos = stream.Position; token = tokenizer.ExpectToken(ZScriptTokenType.Comma, ZScriptTokenType.OpenParen, ZScriptTokenType.OpenSquare, ZScriptTokenType.Semicolon); if (token == null || !token.IsValid) { parser.ReportError("Expected comma, ;, [, or argument list, got " + ((Object)token ?? "<null>").ToString()); return; } if (token.Type == ZScriptTokenType.OpenParen) { // if we have multiple names if (names.Count > 0) { parser.ReportError("Cannot have multiple names in a method"); return; } bmethod = true; // now, I could parse this properly, but it won't be used anyway, so I'll do it as a fake expression. args = parser.ParseExpression(true); token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen); if (token == null || !token.IsValid) { parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString()); return; } // also get the body block, if any. tokenizer.SkipWhitespace(); cpos = stream.Position; token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon, ZScriptTokenType.OpenCurly, ZScriptTokenType.Identifier); if (token == null || !token.IsValid) { parser.ReportError("Expected 'const', ; or {, got " + ((Object)token ?? "<null>").ToString()); return; } // if (token.Type == ZScriptTokenType.Identifier) { if (token.Value.ToLowerInvariant() != "const") { parser.ReportError("Expected 'const', got " + ((Object)token ?? "<null>").ToString()); return; } tokenizer.SkipWhitespace(); cpos = stream.Position; token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon, ZScriptTokenType.OpenCurly); if (token == null || !token.IsValid) { parser.ReportError("Expected ; or {, got " + ((Object)token ?? "<null>").ToString()); return; } } if (token.Type == ZScriptTokenType.OpenCurly) { stream.Position = cpos; parser.SkipBlock(); //body = parser.ParseBlock(false); } break; // end method parsing } else { if (bmethod) { parser.ReportError("Cannot have virtual, override or action fields"); return; } // array if (token.Type == ZScriptTokenType.OpenSquare) { stream.Position = cpos; lens = ParseArrayDimensions(); if (lens == null) // error { return; } tokenizer.SkipWhitespace(); ZScriptTokenType[] expectTokens; if (modifiers.Contains("static")) { expectTokens = new ZScriptTokenType[] { ZScriptTokenType.Semicolon, ZScriptTokenType.Comma, ZScriptTokenType.OpAssign } } ; else { expectTokens = new ZScriptTokenType[] { ZScriptTokenType.Semicolon, ZScriptTokenType.Comma } }; token = tokenizer.ExpectToken(expectTokens); if (token == null || !token.IsValid) { parser.ReportError("Expected ;, =, or comma, got " + ((Object)token ?? "<null>").ToString()); return; } // "static int A[] = {1, 2, 3};" if (token.Type == ZScriptTokenType.OpAssign) { // read in array data tokenizer.SkipWhitespace(); parser.SkipBlock(false); tokenizer.SkipWhitespace(); token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon, ZScriptTokenType.Comma); if (token == null || !token.IsValid) { parser.ReportError("Expected ; or comma, got " + ((Object)token ?? "<null>").ToString()); return; } } } } names.Add(name); arraylens.Add(lens); if (token.Type != ZScriptTokenType.Comma) // next name { break; } } // validate modifiers here. // protected and private cannot be combined. if (bmethod) { if (modifiers.Contains("protected") && modifiers.Contains("private")) { parser.ReportError("Cannot have protected and private on the same method"); return; } // virtual and override cannot be combined. int cvirtual = modifiers.Contains("virtual") ? 1 : 0; cvirtual += modifiers.Contains("override") ? 1 : 0; cvirtual += modifiers.Contains("final") ? 1 : 0; if (cvirtual > 1) { parser.ReportError("Cannot have virtual, override and final on the same method"); return; } // meta (what the f**k is that?) probably cant be on a method if (modifiers.Contains("meta")) { parser.ReportError("Cannot have meta on a method"); return; } } // finished method or field parsing. /*for (int i = 0; i < names.Count; i++) * { * string name = names[i]; * int arraylen = arraylens[i]; * * string _args = ""; * if (args != null) _args = " (" + ZScriptTokenizer.TokensToString(args) + ")"; * 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 { // we support: // - float // - int // - double // - bool string type = types[0]; UniversalType utype; object udefault = null; switch (type) { case "int": case "int8": case "int16": case "uint": case "uint8": case "uint16": utype = UniversalType.Integer; break; case "float": case "double": utype = UniversalType.Float; break; case "bool": utype = UniversalType.Boolean; break; case "string": utype = UniversalType.String; break; // todo test if class names and colors will work default: continue; // go read next field } UniversalType utype_reinterpret = utype; if (var_props.ContainsKey("$userreinterpret")) { string sp = var_props["$userreinterpret"][0].Trim().ToLowerInvariant(); switch (sp) { case "color": if (utype != UniversalType.Integer) { parser.LogWarning("Cannot use $UserReinterpret Color with non-integers"); break; } utype_reinterpret = UniversalType.Color; break; } } if (var_props.ContainsKey("$userdefaultvalue")) { string sp = var_props["$userdefaultvalue"][0]; switch (utype) { case UniversalType.String: if (sp[0] == '"' && sp[sp.Length - 1] == '"') { sp = sp.Substring(1, sp.Length - 2); } udefault = sp; break; case UniversalType.Float: double d; if (!double.TryParse(sp, out d)) { parser.LogWarning("Incorrect float default from string \"" + sp + "\""); break; } udefault = d; break; case UniversalType.Integer: int i; if (!int.TryParse(sp, out i)) { if (utype_reinterpret == UniversalType.Color) { sp = sp.ToLowerInvariant(); Rendering.PixelColor pc; if (!ZDTextParser.GetColorFromString(sp, out pc)) { parser.LogWarning("Incorrect color default from string \"" + sp + "\""); break; } udefault = pc.ToInt() & 0xFFFFFF; break; } } udefault = i; break; case UniversalType.Boolean: sp = sp.ToLowerInvariant(); if (sp == "true") { udefault = true; } else if (sp == "false") { udefault = false; } else { parser.LogWarning("Incorrect boolean default from string \"" + sp + "\""); } break; } } for (int i = 0; i < names.Count; i++) { string name = names[i]; if (arraylens[i] != null || typearraylens[0] != null) { continue; // we don't process arrays } if (!name.StartsWith("user_")) { continue; // we don't process non-user_ fields (because ZScript won't pick them up anyway) } // parent class is not guaranteed to be loaded already, so handle collisions later uservars.Add(name, utype_reinterpret); if (udefault != null) { uservar_defaults.Add(name, udefault); } } } } // parsing done, process thing arguments ParseCustomArguments(); } }
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; } } // 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(); }
} //mxd #endregion #region ================== Constructor / Disposer // Constructor internal PatchStructure(TexturesParser parser) { // Initialize alpha = 1.0f; renderstyle = TexturePathRenderStyle.COPY; //mxd blendstyle = TexturePathBlendStyle.NONE; //mxd // There should be 3 tokens separated by 2 commas now: // Name, Width, Height // First token is the class name parser.SkipWhitespace(true); name = parser.StripTokenQuotes(parser.ReadToken(false)); //mxd. Don't skip newline if (string.IsNullOrEmpty(name)) { parser.ReportError("Expected patch name"); return; } //mxd. Skip what must be skipped skip = (name.ToUpperInvariant() == IGNORE_SPRITE); //mxd name = name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); // Now we should find a comma if (!parser.NextTokenIs(",")) { return; //mxd } // Next is the patch width parser.SkipWhitespace(true); string tokenstr = parser.ReadToken(); if (string.IsNullOrEmpty(tokenstr) || !int.TryParse(tokenstr, NumberStyles.Integer, CultureInfo.InvariantCulture, out offsetx)) { parser.ReportError("Expected offset in pixels"); return; } // Now we should find a comma again if (!parser.NextTokenIs(",")) { return; //mxd } // Next is the patch height parser.SkipWhitespace(true); tokenstr = parser.ReadToken(); if (string.IsNullOrEmpty(tokenstr) || !int.TryParse(tokenstr, NumberStyles.Integer, CultureInfo.InvariantCulture, out offsety)) { parser.ReportError("Expected offset in pixels"); return; } // Next token is the beginning of the texture scope. If not, then the patch info ends here. if (!parser.NextTokenIs("{", false)) { return; //mxd } // Now parse the contents of texture structure bool done = false; //mxd while (!done && parser.SkipWhitespace(true)) { string token = parser.ReadToken(); token = token.ToLowerInvariant(); switch (token) { case "flipx": flipx = true; break; case "flipy": flipy = true; break; case "alpha": if (!ReadTokenFloat(parser, token, out alpha)) { return; } alpha = General.Clamp(alpha, 0.0f, 1.0f); break; case "rotate": if (!ReadTokenInt(parser, token, out rotation)) { return; } rotation = rotation % 360; //Coalesce multiples if (rotation < 0) { rotation += 360; //Force positive } if (rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270) { parser.LogWarning("Unsupported rotation (" + rotation + ") in patch \"" + name + "\""); rotation = 0; } break; case "style": //mxd string s; if (!ReadTokenString(parser, token, out s)) { return; } int index = Array.IndexOf(renderStyles, s.ToLowerInvariant()); renderstyle = index == -1 ? TexturePathRenderStyle.COPY : (TexturePathRenderStyle)index; break; case "blend": //mxd parser.SkipWhitespace(false); PixelColor color = new PixelColor(); // Blend <string color>[,<float alpha>] block? token = parser.ReadToken(false); if (!parser.ReadByte(token, ref color.r)) { if (!ZDTextParser.GetColorFromString(token, ref color)) { parser.ReportError("Unsupported patch blend definition"); return; } } // That's Blend <int r>,<int g>,<int b>[,<float alpha>] block else { if (!parser.SkipWhitespace(false) || !parser.NextTokenIs(",", false) || !parser.SkipWhitespace(false) || !parser.ReadByte(ref color.g) || !parser.NextTokenIs(",", false) || !parser.SkipWhitespace(false) || !parser.ReadByte(ref color.b)) { parser.ReportError("Unsupported patch blend definition"); return; } } // Alpha block? float blendalpha = -1f; parser.SkipWhitespace(false); if (parser.NextTokenIs(",", false)) { parser.SkipWhitespace(false); if (!ReadTokenFloat(parser, token, out blendalpha)) { parser.ReportError("Unsupported patch blend alpha value"); return; } } // Blend may never be 0 when using the Tint effect if (blendalpha > 0.0f) { color.a = (byte)General.Clamp((int)(blendalpha * 255), 1, 254); blendstyle = TexturePathBlendStyle.TINT; } else if (blendalpha < 0.0f) { color.a = 255; blendstyle = TexturePathBlendStyle.BLEND; } else { // Ignore Blend when alpha == 0 parser.LogWarning("Blend with zero alpha will be ignored by ZDoom"); break; } // Store the color blendcolor = color; break; case "}": // Patch scope ends here, // break out of this parse loop done = true; break; } } }
internal DecorateActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo) { this.catinfo = catinfo; //mxd DecorateParser parser = (DecorateParser)zdparser; bool done = false; //mxd // First next token is the class name parser.SkipWhitespace(true); classname = parser.StripTokenQuotes(parser.ReadToken(ACTOR_CLASS_SPECIAL_TOKENS)); if (string.IsNullOrEmpty(classname)) { parser.ReportError("Expected actor class name"); return; } //mxd. Fail on duplicates // [ZZ] archived +zscript if (parser.GetArchivedActorByName(classname) != null) { parser.ReportError("Actor \"" + classname + "\" is double-defined"); return; } // Parse tokens before entering the actor scope while (parser.SkipWhitespace(true)) { string token = parser.ReadToken(); if (!string.IsNullOrEmpty(token)) { token = token.ToLowerInvariant(); switch (token) { case ":": // The next token must be the class to inherit from parser.SkipWhitespace(true); inheritclass = parser.StripTokenQuotes(parser.ReadToken()); if (string.IsNullOrEmpty(inheritclass)) { parser.ReportError("Expected class name to inherit from"); return; } // Find the actor to inherit from baseclass = parser.GetArchivedActorByName(inheritclass); break; case "replaces": // The next token must be the class to replace parser.SkipWhitespace(true); replaceclass = parser.StripTokenQuotes(parser.ReadToken()); if (string.IsNullOrEmpty(replaceclass)) { parser.ReportError("Expected class name to replace"); return; } break; case "native": // Igore this token break; case "{": // Actor scope begins here, // break out of this parse loop done = true; break; case "-": // This could be a negative doomednum (but our parser sees the - as separate token) // So read whatever is after this token and ignore it (negative doomednum indicates no doomednum) parser.ReadToken(); break; default: //mxd. Property begins with $? Then the whole line is a single value if (token.StartsWith("$")) { // This is for editor-only properties such as $sprite and $category props[token] = new List <string> { (parser.SkipWhitespace(false) ? parser.ReadLine() : "") }; continue; } if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out doomednum)) // Check if numeric { // Not numeric! parser.ReportError("Expected editor number or start of actor scope while parsing \"" + classname + "\""); return; } //mxd. Range check if ((doomednum < General.Map.FormatInterface.MinThingType) || (doomednum > General.Map.FormatInterface.MaxThingType)) { // Out of bounds! parser.ReportError("Actor \"" + classname + "\" has invalid editor number. Editor number must be between " + General.Map.FormatInterface.MinThingType + " and " + General.Map.FormatInterface.MaxThingType); return; } break; } if (done) { break; //mxd } } else { parser.ReportError("Unexpected end of structure"); return; } } // Now parse the contents of actor structure string previoustoken = ""; done = false; //mxd while (parser.SkipWhitespace(true)) { string token = parser.ReadToken(); token = token.ToLowerInvariant(); switch (token) { case "+": case "-": // Next token is a flag (option) to set or remove bool flagvalue = (token == "+"); parser.SkipWhitespace(true); string flagname = parser.ReadToken(); if (!string.IsNullOrEmpty(flagname)) { // Add the flag with its value flagname = flagname.ToLowerInvariant(); flags[flagname] = flagvalue; } else { parser.ReportError("Expected flag name"); return; } break; case "action": case "native": // We don't need this, ignore up to the first next ; while (parser.SkipWhitespace(true)) { string t = parser.ReadToken(); if (string.IsNullOrEmpty(t) || t == ";") { break; } } break; case "skip_super": skipsuper = true; break; case "states": if (!parser.NextTokenIs("{", true)) { return; } // Now parse actor states until we reach the end of the states structure while (parser.SkipWhitespace(true)) { string statetoken = parser.ReadToken(); if (!string.IsNullOrEmpty(statetoken)) { // End of scope? if (statetoken == "}") { // Done with the states, // break out of this parse loop break; } // State label? else if (statetoken == ":") { if (!string.IsNullOrEmpty(previoustoken)) { // Parse actor state StateStructure st = new DecorateStateStructure(this, parser); if (parser.HasError) { return; } states[previoustoken.ToLowerInvariant()] = st; } else { parser.ReportError("Expected actor state name"); return; } } else { // Keep token previoustoken = statetoken; } } else { parser.ReportError("Unexpected end of structure"); return; } } break; case "var": //mxd // Type parser.SkipWhitespace(true); string typestr = parser.ReadToken().ToUpperInvariant(); UniversalType type = UniversalType.EnumOption; // There is no Unknown type, so let's use something impossiburu... switch (typestr) { case "INT": type = UniversalType.Integer; break; case "FLOAT": type = UniversalType.Float; break; default: parser.LogWarning("Unknown user variable type"); break; } // Name parser.SkipWhitespace(true); string name = parser.ReadToken(); if (string.IsNullOrEmpty(name)) { parser.ReportError("Expected User Variable name"); return; } if (!name.StartsWith("user_", StringComparison.OrdinalIgnoreCase)) { parser.ReportError("User Variable name must start with \"user_\" prefix"); return; } if (uservars.ContainsKey(name)) { parser.ReportError("User Variable \"" + name + "\" is double defined"); return; } if (!skipsuper && baseclass != null && baseclass.uservars.ContainsKey(name)) { parser.ReportError("User variable \"" + name + "\" is already defined in one of the parent classes"); return; } // Rest parser.SkipWhitespace(true); string next = parser.ReadToken(); if (next == "[") // that's User Array. Let's skip it... { int arrlen = -1; if (!parser.ReadSignedInt(ref arrlen)) { parser.ReportError("Expected User Array length"); return; } if (arrlen < 1) { parser.ReportError("User Array length must be a positive value"); return; } if (!parser.NextTokenIs("]") || !parser.NextTokenIs(";")) { return; } } else if (next != ";") { parser.ReportError("Expected \";\", but got \"" + next + "\""); return; } else { // Add to collection uservars.Add(name, type); } break; case "}": //mxd. Get user vars from the BaseClass, if we have one if (!skipsuper && baseclass != null && baseclass.uservars.Count > 0) { foreach (var group in baseclass.uservars) { uservars.Add(group.Key, group.Value); } } // Actor scope ends here, break out of this parse loop done = true; break; // Monster property? case "monster": // This sets certain flags we are interested in flags["shootable"] = true; flags["countkill"] = true; flags["solid"] = true; flags["canpushwalls"] = true; flags["canusewalls"] = true; flags["activatemcross"] = true; flags["canpass"] = true; flags["ismonster"] = true; break; // Projectile property? case "projectile": // This sets certain flags we are interested in flags["noblockmap"] = true; flags["nogravity"] = true; flags["dropoff"] = true; flags["missile"] = true; flags["activateimpact"] = true; flags["activatepcross"] = true; flags["noteleport"] = true; break; // Clearflags property? case "clearflags": // Clear all flags flags.Clear(); break; // Game property? case "game": // Include all tokens on the same line List <string> games = new List <string>(); while (parser.SkipWhitespace(false)) { string v = parser.ReadToken(); if (string.IsNullOrEmpty(v)) { parser.ReportError("Expected \"Game\" property value"); return; } if (v == "\n") { break; } if (v == "}") { return; //mxd } if (v != ",") { games.Add(v.ToLowerInvariant()); } } props[token] = games; break; // Property default: // Property begins with $? Then the whole line is a single value if (token.StartsWith("$")) { // This is for editor-only properties such as $sprite and $category props[token] = new List <string> { (parser.SkipWhitespace(false) ? parser.ReadLine() : "") }; } else { // Next tokens up until the next newline are values List <string> values = new List <string>(); while (parser.SkipWhitespace(false)) { string v = parser.ReadToken(); if (string.IsNullOrEmpty(v)) { parser.ReportError("Unexpected end of structure"); return; } if (v == "\n") { break; } if (v == "}") { return; //mxd } if (v != ",") { values.Add(v); } } //mxd. Translate scale to xscale and yscale if (token == "scale") { props["xscale"] = values; props["yscale"] = values; } else { props[token] = values; } } break; } if (done) { break; //mxd } // Keep token previoustoken = token; } // parsing done, process thing arguments ParseCustomArguments(); //mxd. Check if baseclass is valid if (inheritclass.ToLowerInvariant() != "actor" && doomednum > -1) { //check if this class inherits from a class defined in game configuration Dictionary <int, ThingTypeInfo> things = General.Map.Config.GetThingTypes(); string inheritclasscheck = inheritclass.ToLowerInvariant(); foreach (KeyValuePair <int, ThingTypeInfo> ti in things) { if (!string.IsNullOrEmpty(ti.Value.ClassName) && ti.Value.ClassName.ToLowerInvariant() == inheritclasscheck) { //states // [ZZ] allow internal prefix here. it can inherit MapSpot, light, or other internal stuff. if (states.Count == 0 && !string.IsNullOrEmpty(ti.Value.Sprite)) { states.Add("spawn", new StateStructure(ti.Value.Sprite.StartsWith(DataManager.INTERNAL_PREFIX) ? ti.Value.Sprite : ti.Value.Sprite.Substring(0, 5))); } if (baseclass == null) { //flags if (ti.Value.Hangs && !flags.ContainsKey("spawnceiling")) { flags["spawnceiling"] = true; } if (ti.Value.Blocking > 0 && !flags.ContainsKey("solid")) { flags["solid"] = true; } //properties if (!props.ContainsKey("height")) { props["height"] = new List <string> { ti.Value.Height.ToString() } } ; if (!props.ContainsKey("radius")) { props["radius"] = new List <string> { ti.Value.Radius.ToString() } } ; } // [ZZ] inherit arguments from game configuration // if (!props.ContainsKey("$clearargs")) { for (int i = 0; i < 5; i++) { if (args[i] != null) { continue; // don't touch it if we already have overrides } ArgumentInfo arg = ti.Value.Args[i]; if (arg != null && arg.Used) { args[i] = arg; } } } return; } } if (baseclass == null) { parser.LogWarning("Unable to find \"" + inheritclass + "\" class to inherit from, while parsing \"" + classname + ":" + doomednum + "\""); } } } #endregion } }
internal DecorateStateGoto(ActorStructure actor, ZDTextParser parser) { string firsttarget = ""; string secondtarget = ""; bool commentreached = false; bool offsetreached = false; string offsetstr = ""; int cindex = 0; // This is a bitch to parse because for some bizarre reason someone thought it // was funny to allow quotes here. Read the whole line and start parsing this manually. string line = parser.ReadLine(); // Skip whitespace while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) { cindex++; } // Parse first target while ((cindex < line.Length) && (line[cindex] != ':')) { // When a comment is reached, we're done here if (line[cindex] == '/') { if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) { commentreached = true; break; } } // Whitespace ends the string if ((line[cindex] == ' ') || (line[cindex] == '\t')) { break; } // + sign indicates offset start if (line[cindex] == '+') { cindex++; offsetreached = true; break; } // Ignore quotes if (line[cindex] != '"') { firsttarget += line[cindex]; } cindex++; } if (!commentreached && !offsetreached) { // Skip whitespace while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) { cindex++; } // Parse second target while (cindex < line.Length) { // When a comment is reached, we're done here if (line[cindex] == '/') { if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) { commentreached = true; break; } } // Whitespace ends the string if ((line[cindex] == ' ') || (line[cindex] == '\t')) { break; } // + sign indicates offset start if (line[cindex] == '+') { cindex++; offsetreached = true; break; } // Ignore quotes and semicolons if ((line[cindex] != '"') && (line[cindex] != ':')) { secondtarget += line[cindex]; } cindex++; } } // Try to find the offset if we still haven't found it yet if (!offsetreached) { // Skip whitespace while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) { cindex++; } if ((cindex < line.Length) && (line[cindex] == '+')) { cindex++; offsetreached = true; } } if (offsetreached) { // Parse offset while (cindex < line.Length) { // When a comment is reached, we're done here if (line[cindex] == '/') { if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) { commentreached = true; break; } } // Whitespace ends the string if ((line[cindex] == ' ') || (line[cindex] == '\t')) { break; } // Ignore quotes and semicolons if ((line[cindex] != '"') && (line[cindex] != ':')) { offsetstr += line[cindex]; } cindex++; } } // We should now have a first target, optionally a second target and optionally a sprite offset // Check if we don't have the class specified if (string.IsNullOrEmpty(secondtarget)) { // First target is the state to go to classname = actor.ClassName; statename = firsttarget.ToLowerInvariant().Trim(); } else { // First target is the base class to use // Second target is the state to go to classname = firsttarget.ToLowerInvariant().Trim(); statename = secondtarget.ToLowerInvariant().Trim(); } if (offsetstr.Length > 0) { int.TryParse(offsetstr, out spriteoffset); } if ((classname == "super") && (actor.BaseClass != null)) { classname = actor.BaseClass.ClassName; } }
internal ZScriptStateGoto(ActorStructure actor, ZDTextParser zdparser) { // goto syntax that is accepted by GZDB is [classname::]statename[+offset] ZScriptParser parser = (ZScriptParser)zdparser; Stream stream = parser.DataStream; ZScriptTokenizer tokenizer = new ZScriptTokenizer(parser.DataReader); parser.tokenizer = tokenizer; tokenizer.SkipWhitespace(); string firsttarget = parser.ParseDottedIdentifier(); if (firsttarget == null) { return; } ZScriptToken token; string secondtarget = null; int offset = 0; tokenizer.SkipWhitespace(); token = tokenizer.ExpectToken(ZScriptTokenType.DoubleColon); if (token != null && token.IsValid) { secondtarget = parser.ParseDottedIdentifier(); if (secondtarget == null) { return; } } tokenizer.SkipWhitespace(); token = tokenizer.ExpectToken(ZScriptTokenType.OpAdd); if (token != null && token.IsValid) { tokenizer.SkipWhitespace(); token = tokenizer.ExpectToken(ZScriptTokenType.Integer); if (token == null || !token.IsValid) { parser.ReportError("Expected state offset, got " + ((Object)token ?? "<null>").ToString()); return; } offset = token.ValueInt; } // Check if we don't have the class specified if (string.IsNullOrEmpty(secondtarget)) { // First target is the state to go to classname = actor.ClassName; statename = firsttarget.ToLowerInvariant().Trim(); } else { // First target is the base class to use // Second target is the state to go to classname = firsttarget.ToLowerInvariant().Trim(); statename = secondtarget.ToLowerInvariant().Trim(); } spriteoffset = offset; if ((classname == "super") && (actor.BaseClass != null)) { classname = actor.BaseClass.ClassName; } }
internal DecorateStateStructure(ActorStructure actor, ZDTextParser zdparser) { DecorateParser parser = (DecorateParser)zdparser; string lasttoken = ""; // Skip whitespace while (parser.SkipWhitespace(true)) { // Read first token string token = parser.ReadToken().ToLowerInvariant(); // One of the flow control statements? if ((token == "loop") || (token == "stop") || (token == "wait") || (token == "fail")) { // Ignore flow control // [ZZ] sometimes "fail" is a sprite name... (Skulltag, Zandronum) // probably the same can happen to other single-word flow control keywords. // check if next token is newline. long cpos = parser.DataStream.Position; parser.SkipWhitespace(false); string newline = parser.ReadToken(); parser.DataStream.Position = cpos; if (newline == "\n") // this is actually a loop/stop/wait/fail directive and not a sprite name or something { lasttoken = token; continue; } } // Goto? if (token == "goto") { gotostate = new DecorateStateGoto(actor, parser); if (parser.HasError) { return; } } // Label? else if (token == ":") { // Rewind so that this label can be read again if (!string.IsNullOrEmpty(lasttoken)) { parser.DataStream.Seek(-(lasttoken.Length + 1), SeekOrigin.Current); } // Done here goto endofallthings; } //mxd. Start of inner scope? else if (token == "{") { int bracelevel = 1; while (!string.IsNullOrEmpty(token) && bracelevel > 0) { parser.SkipWhitespace(false); token = parser.ReadToken(); switch (token) { case "{": bracelevel++; break; case "}": bracelevel--; break; } } } // End of scope? else if (token == "}") { // Rewind so that this scope end can be read again parser.DataStream.Seek(-1, SeekOrigin.Current); // Done here goto endofallthings; } else { // First part of the sprite name token = parser.StripTokenQuotes(token); //mxd. First part of the sprite name can be quoted if (string.IsNullOrEmpty(token)) { parser.ReportError("Expected sprite name"); return; } // Frames of the sprite name parser.SkipWhitespace(true); string spriteframes = parser.StripTokenQuotes(parser.ReadToken()); //mxd. Frames can be quoted if (string.IsNullOrEmpty(spriteframes)) { parser.ReportError("Expected sprite frame"); return; } // Label? if (spriteframes == ":") { // Rewind so that this label can be read again parser.DataStream.Seek(-(token.Length + 1), SeekOrigin.Current); // Done here goto endofallthings; } // No first sprite yet? FrameInfo info = new FrameInfo(); //mxd if (spriteframes.Length > 0) { //mxd. I'm not even 50% sure the parser handles all bizzare cases without shifting sprite name / frame blocks, // so let's log it as a warning, not an error... if (token.Length != 4) { parser.LogWarning("Invalid sprite name \"" + token.ToUpperInvariant() + "\". Sprite names must be exactly 4 characters long"); } else { // Make the sprite name string spritename = (token + spriteframes[0]).ToUpperInvariant(); // Ignore some odd ZDoom things if (/*!realspritename.StartsWith("TNT1") && */ !spritename.StartsWith("----") && !spritename.Contains("#")) // [ZZ] some actors have only TNT1 state and receive a random image because of this { info.Sprite = spritename; //mxd int duration = -1; parser.SkipWhitespace(false); string durationstr = parser.ReadToken(); if (durationstr == "-") { durationstr += parser.ReadToken(); } if (string.IsNullOrEmpty(durationstr) || durationstr == "\n") { parser.ReportError("Expected frame duration"); return; } if (!int.TryParse(durationstr.Trim(), out duration)) { parser.DataStream.Seek(-(durationstr.Length), SeekOrigin.Current); } info.Duration = duration; sprites.Add(info); } } } // Continue until the end of the line parser.SkipWhitespace(false); string t = parser.ReadToken(); while (!string.IsNullOrEmpty(t) && t != "\n") { //mxd. Bright keyword support... if (t == "bright") { info.Bright = true; } //mxd. Light() expression support... else if (t == "light") { if (!parser.NextTokenIs("(")) { return; } if (!parser.SkipWhitespace(true)) { parser.ReportError("Unexpected end of the structure"); return; } info.LightName = parser.StripTokenQuotes(parser.ReadToken()); if (string.IsNullOrEmpty(info.LightName)) { parser.ReportError("Expected dynamic light name"); return; } if (!parser.SkipWhitespace(true)) { parser.ReportError("Unexpected end of the structure"); return; } if (!parser.NextTokenIs(")")) { parser.ReportError("Expected closing parenthesis in Light()"); return; } } //mxd. Inner scope start. Step back and reparse using parent loop else if (t == "{") { // Rewind so that this scope end can be read again parser.DataStream.Seek(-1, SeekOrigin.Current); // Break out of this loop break; } //mxd. Function params start (those can span multiple lines) else if (t == "(") { int bracelevel = 1; while (!string.IsNullOrEmpty(token) && bracelevel > 0) { parser.SkipWhitespace(true); token = parser.ReadToken(); switch (token) { case "(": bracelevel++; break; case ")": bracelevel--; break; } } } //mxd. Because stuff like this is also valid: "Actor Oneliner { States { Spawn: WOOT A 1 A_FadeOut(0.1) Loop }}" else if (t == "}") { // Rewind so that this scope end can be read again parser.DataStream.Seek(-1, SeekOrigin.Current); // Done here goto endofallthings; } // Read next token parser.SkipWhitespace(false); t = parser.ReadToken().ToLowerInvariant(); } } lasttoken = token; } // return endofallthings: TrimLeft(); }