/* ** thingdef-parse.cpp ** ** Actor definitions - all parser related code ** **--------------------------------------------------------------------------- ** Copyright 2002-2007 Christoph Oelckers ** Copyright 2004-2007 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be ** covered by the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or (at ** your option) any later version. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #include "doomtype.h" #include "actor.h" #include "a_pickups.h" #include "sc_man.h" #include "thingdef.h" #include "a_morph.h" #include "cmdlib.h" #include "templates.h" #include "v_palette.h" #include "doomerrors.h" #include "autosegs.h" #include "i_system.h" #include "thingdef_exp.h" #include "w_wad.h" #include "v_video.h" void ParseOldDecoration(FScanner &sc, EDefinitionType def); //========================================================================== // // ParseParameter // // Parses aparameter - either a default in a function declaration // or an argument in a function call. // //========================================================================== FxExpression *ParseParameter(FScanner &sc, PClass *cls, char type, bool constant) { FxExpression *x = NULL; int v; switch(type) { case 'S': case 's': // Sound name sc.MustGetString(); x = new FxConstant(FSoundID(sc.String), sc); break; case 'M': case 'm': // Actor name sc.SetEscape(true); sc.MustGetString(); sc.SetEscape(false); x = new FxClassTypeCast(RUNTIME_CLASS(AActor), new FxConstant(FName(sc.String), sc)); break; case 'T': case 't': // String sc.SetEscape(true); sc.MustGetString(); sc.SetEscape(false); x = new FxConstant(sc.String[0]? FName(sc.String) : NAME_None, sc); break; case 'C': case 'c': // Color sc.MustGetString (); if (sc.Compare("none")) { v = -1; } else if (sc.Compare("")) { v = 0; } else { int c = V_GetColor (NULL, sc.String); // 0 needs to be the default so we have to mark the color. v = MAKEARGB(1, RPART(c), GPART(c), BPART(c)); } { ExpVal val; val.Type = VAL_Color; val.Int = v; x = new FxConstant(val, sc); break; } case 'L': case 'l': { // This forces quotation marks around the state name. sc.MustGetToken(TK_StringConst); if (sc.String[0] == 0 || sc.Compare("None")) { x = new FxConstant((FState*)NULL, sc); } else if (sc.Compare("*")) { if (constant) { x = new FxConstant((FState*)(intptr_t)-1, sc); } else sc.ScriptError("Invalid state name '*'"); } else { x = new FxMultiNameState(sc.String, sc); } break; } case 'X': case 'x': case 'Y': case 'y': x = ParseExpression (sc, cls); if (constant && !x->isConstant()) { sc.ScriptError("Default parameter must be constant."); } break; default: assert(false); return NULL; } return x; } //========================================================================== // // ActorConstDef // // Parses a constant definition. // //========================================================================== static void ParseConstant (FScanner &sc, PSymbolTable * symt, PClass *cls) { // Read the type and make sure it's int or float. if (sc.CheckToken(TK_Int) || sc.CheckToken(TK_Float)) { int type = sc.TokenType; sc.MustGetToken(TK_Identifier); FName symname = sc.String; sc.MustGetToken('='); FxExpression *expr = ParseExpression (sc, cls); sc.MustGetToken(';'); ExpVal val = expr->EvalExpression(NULL); delete expr; PSymbolConst *sym = new PSymbolConst(symname); if (type == TK_Int) { sym->ValueType = VAL_Int; sym->Value = val.GetInt(); } else { sym->ValueType = VAL_Float; sym->Float = val.GetFloat(); } if (symt->AddSymbol (sym) == NULL) { delete sym; sc.ScriptError ("'%s' is already defined in '%s'.", symname.GetChars(), cls? cls->TypeName.GetChars() : "Global"); } } else { sc.ScriptError("Numeric type required for constant"); } } //========================================================================== // // ActorEnumDef // // Parses an enum definition. // //========================================================================== static void ParseEnum (FScanner &sc, PSymbolTable *symt, PClass *cls) { int currvalue = 0; sc.MustGetToken('{'); while (!sc.CheckToken('}')) { sc.MustGetToken(TK_Identifier); FName symname = sc.String; if (sc.CheckToken('=')) { FxExpression *expr = ParseExpression (sc, cls); currvalue = expr->EvalExpression(NULL).GetInt(); delete expr; } PSymbolConst *sym = new PSymbolConst(symname); sym->ValueType = VAL_Int; sym->Value = currvalue; if (symt->AddSymbol (sym) == NULL) { delete sym; sc.ScriptError ("'%s' is already defined in '%s'.", symname.GetChars(), cls? cls->TypeName.GetChars() : "Global"); } // This allows a comma after the last value but doesn't enforce it. if (sc.CheckToken('}')) break; sc.MustGetToken(','); currvalue++; } sc.MustGetToken(';'); } //========================================================================== // // ActorConstDef // // Parses a constant definition. // //========================================================================== static void ParseVariable (FScanner &sc, PSymbolTable * symt, PClass *cls) { FExpressionType valuetype; if (sc.LumpNum == -1 || Wads.GetLumpFile(sc.LumpNum) > 0) { sc.ScriptError ("variables can only be imported by internal class and actor definitions!"); } // Read the type and make sure it's int or float. sc.MustGetAnyToken(); switch (sc.TokenType) { case TK_Int: valuetype = VAL_Int; break; case TK_Float: valuetype = VAL_Float; break; case TK_Angle_t: valuetype = VAL_Angle; break; case TK_Fixed_t: valuetype = VAL_Fixed; break; case TK_Bool: valuetype = VAL_Bool; break; case TK_Identifier: valuetype = VAL_Object; // Todo: Object type sc.ScriptError("Object type variables not implemented yet!"); break; default: sc.ScriptError("Invalid variable type %s", sc.String); return; } sc.MustGetToken(TK_Identifier); FName symname = sc.String; if (sc.CheckToken('[')) { FxExpression *expr = ParseExpression (sc, cls); int maxelems = expr->EvalExpression(NULL).GetInt(); delete expr; sc.MustGetToken(']'); valuetype.MakeArray(maxelems); } sc.MustGetToken(';'); FVariableInfo *vi = FindVariable(symname, cls); if (vi == NULL) { sc.ScriptError("Unknown native variable '%s'", symname.GetChars()); } PSymbolVariable *sym = new PSymbolVariable(symname); sym->offset = vi->address; // todo sym->ValueType = valuetype; if (symt->AddSymbol (sym) == NULL) { delete sym; sc.ScriptError ("'%s' is already defined in '%s'.", symname.GetChars(), cls? cls->TypeName.GetChars() : "Global"); } } //========================================================================== // // Parses a flag name // //========================================================================== static void ParseActorFlag (FScanner &sc, Baggage &bag, int mod) { sc.MustGetString (); FString part1 = sc.String; const char *part2 = NULL; if (sc.CheckString (".")) { sc.MustGetString (); part2 = sc.String; } HandleActorFlag(sc, bag, part1, part2, mod); } //========================================================================== // // Processes a flag. Also used by olddecorations.cpp // //========================================================================== void HandleActorFlag(FScanner &sc, Baggage &bag, const char *part1, const char *part2, int mod) { FFlagDef *fd; if ( (fd = FindFlag (bag.Info->Class, part1, part2)) ) { AActor *defaults = (AActor*)bag.Info->Class->Defaults; if (fd->structoffset == -1) // this is a deprecated flag that has been changed into a real property { HandleDeprecatedFlags(defaults, bag.Info, mod=='+', fd->flagbit); } else { DWORD * flagvar = (DWORD*) ((char*)defaults + fd->structoffset); if (mod == '+') { *flagvar |= fd->flagbit; } else { *flagvar &= ~fd->flagbit; } } } else { if (part2 == NULL) { sc.ScriptError("\"%s\" is an unknown flag\n", part1); } else { sc.ScriptError("\"%s.%s\" is an unknown flag\n", part1, part2); } } } //========================================================================== // // [MH] parses a morph style expression // //========================================================================== static int ParseMorphStyle (FScanner &sc) { static const char * morphstyles[]={ "MRF_ADDSTAMINA", "MRF_FULLHEALTH", "MRF_UNDOBYTOMEOFPOWER", "MRF_UNDOBYCHAOSDEVICE", "MRF_FAILNOTELEFRAG", "MRF_FAILNOLAUGH", "MRF_WHENINVULNERABLE", "MRF_LOSEACTUALWEAPON", "MRF_NEWTIDBEHAVIOUR", "MRF_UNDOBYDEATH", "MRF_UNDOBYDEATHFORCED", "MRF_UNDOBYDEATHSAVES", NULL}; static const int morphstyle_values[]={ MORPH_ADDSTAMINA, MORPH_FULLHEALTH, MORPH_UNDOBYTOMEOFPOWER, MORPH_UNDOBYCHAOSDEVICE, MORPH_FAILNOTELEFRAG, MORPH_FAILNOLAUGH, MORPH_WHENINVULNERABLE, MORPH_LOSEACTUALWEAPON, MORPH_NEWTIDBEHAVIOUR, MORPH_UNDOBYDEATH, MORPH_UNDOBYDEATHFORCED, MORPH_UNDOBYDEATHSAVES}; // May be given flags by number... if (sc.CheckNumber()) { sc.MustGetNumber(); return sc.Number; } // ... else should be flags by name. // NOTE: Later this should be removed and a normal expression used. // The current DECORATE parser can't handle this though. bool gotparen = sc.CheckString("("); int style = 0; do { sc.MustGetString(); style |= morphstyle_values[sc.MustMatchString(morphstyles)]; } while (sc.CheckString("|")); if (gotparen) { sc.MustGetStringName(")"); } return style; } //========================================================================== // // For getting a state address from the parent // No attempts have been made to add new functionality here // This is strictly for keeping compatibility with old WADs! // //========================================================================== static FState *CheckState(FScanner &sc, PClass *type) { int v=0; if (sc.GetString() && !sc.Crossed) { if (sc.Compare("0")) return NULL; else if (sc.Compare("PARENT")) { FState * state = NULL; sc.MustGetString(); FActorInfo * info = type->ParentClass->ActorInfo; if (info != NULL) { state = info->FindState(FName(sc.String)); } if (sc.GetString ()) { if (sc.Compare ("+")) { sc.MustGetNumber (); v = sc.Number; } else { sc.UnGet (); } } if (state == NULL && v==0) return NULL; if (v!=0 && state==NULL) { sc.ScriptError("Attempt to get invalid state from actor %s\n", type->ParentClass->TypeName.GetChars()); return NULL; } state+=v; return state; } else sc.ScriptError("Invalid state assignment"); } return NULL; } //========================================================================== // // Parses an actor property's parameters and calls the handler // //========================================================================== static bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defaults, Baggage &bag) { static TArray params; static TArray strings; params.Clear(); strings.Clear(); params.Reserve(1); params[0].i = 0; if (prop->params[0] != '0') { const char * p = prop->params; bool nocomma; bool optcomma; while (*p) { FPropParam conv; FPropParam pref; nocomma = false; conv.s = NULL; pref.s = NULL; pref.i = -1; switch ((*p) & 223) { case 'X': // Expression in parentheses or number. if (sc.CheckString ("(")) { FxExpression *x = ParseExpression(sc, bag.Info->Class); conv.i = 0x40000000 | StateParams.Add(x, bag.Info->Class, false); params.Push(conv); sc.MustGetStringName(")"); break; } // fall through case 'I': sc.MustGetNumber(); conv.i = sc.Number; break; case 'F': sc.MustGetFloat(); conv.f = sc.Float; break; case 'Z': // an optional string. Does not allow any numerical value. if (sc.CheckFloat()) { nocomma = true; sc.UnGet(); break; } // fall through case 'S': case 'T': sc.MustGetString(); conv.s = strings[strings.Reserve(1)] = sc.String; break; case 'C': if (sc.CheckNumber ()) { int R, G, B; R = clamp (sc.Number, 0, 255); sc.CheckString (","); sc.MustGetNumber (); G = clamp (sc.Number, 0, 255); sc.CheckString (","); sc.MustGetNumber (); B = clamp (sc.Number, 0, 255); conv.i = MAKERGB(R, G, B); pref.i = 0; } else { sc.MustGetString (); conv.s = strings[strings.Reserve(1)] = sc.String; pref.i = 1; } break; case 'M': // special case. An expression-aware parser will not need this. conv.i = ParseMorphStyle(sc); break; case 'L': // Either a number or a list of strings if (sc.CheckNumber()) { pref.i = 0; conv.i = sc.Number; } else { pref.i = 1; params.Push(pref); params[0].i++; do { sc.MustGetString (); conv.s = strings[strings.Reserve(1)] = sc.String; params.Push(conv); params[0].i++; } while (sc.CheckString(",")); goto endofparm; } break; default: assert(false); break; } if (pref.i != -1) { params.Push(pref); params[0].i++; } params.Push(conv); params[0].i++; endofparm: p++; // Hack for some properties that have to allow comma less // parameter lists for compatibility. if ((optcomma = (*p == '_'))) p++; if (nocomma) { continue; } else if (*p == 0) { break; } else if (*p >= 'a') { if (!sc.CheckString(",")) { if (optcomma) { if (!sc.CheckFloat()) break; else sc.UnGet(); } else break; } } else { if (!optcomma) sc.MustGetStringName(","); else sc.CheckString(","); } } } // call the handler try { prop->Handler(defaults, bag.Info, bag, ¶ms[0]); } catch (CRecoverableError &error) { sc.ScriptError("%s", error.GetMessage()); } return true; } //========================================================================== // // Parses an actor property // //========================================================================== static void ParseActorProperty(FScanner &sc, Baggage &bag) { static const char *statenames[] = { "Spawn", "See", "Melee", "Missile", "Pain", "Death", "XDeath", "Burn", "Ice", "Raise", "Crash", "Crush", "Wound", "Disintegrate", "Heal", NULL }; strlwr (sc.String); FString propname = sc.String; if (sc.CheckString (".")) { sc.MustGetString (); propname += '.'; strlwr (sc.String); propname += sc.String; } else { sc.UnGet (); } FPropertyInfo *prop = FindProperty(propname); if (prop != NULL) { if (bag.Info->Class->IsDescendantOf(prop->cls)) { ParsePropertyParams(sc, prop, (AActor *)bag.Info->Class->Defaults, bag); } else { sc.ScriptMessage("\"%s\" requires an actor of type \"%s\"\n", propname.GetChars(), prop->cls->TypeName.GetChars()); FScriptPosition::ErrorCounter++; } } else if (!propname.CompareNoCase("States")) { if (bag.StateSet) { sc.ScriptMessage("'%s' contains multiple state declarations", bag.Info->Class->TypeName.GetChars()); FScriptPosition::ErrorCounter++; } ParseStates(sc, bag.Info, (AActor *)bag.Info->Class->Defaults, bag); bag.StateSet=true; } else if (MatchString(propname, statenames) != -1) { bag.statedef.SetStateLabel(propname, CheckState (sc, bag.Info->Class)); } else { sc.ScriptError("\"%s\" is an unknown actor property\n", propname.GetChars()); } } //========================================================================== // // ActorActionDef // // Parses an action function definition. A lot of this is essentially // documentation in the declaration for when I have a proper language // ready. // //========================================================================== static void ParseActionDef (FScanner &sc, PClass *cls) { enum { OPTIONAL = 1 }; bool error = false; AFuncDesc *afd; FName funcname; FString args; TArray DefaultParams; bool hasdefaults = false; if (sc.LumpNum == -1 || Wads.GetLumpFile(sc.LumpNum) > 0) { sc.ScriptMessage ("action functions can only be imported by internal class and actor definitions!"); error++; } sc.MustGetToken(TK_Native); sc.MustGetToken(TK_Identifier); funcname = sc.String; afd = FindFunction(sc.String); if (afd == NULL) { sc.ScriptMessage ("The function '%s' has not been exported from the executable.", sc.String); error++; } sc.MustGetToken('('); if (!sc.CheckToken(')')) { while (sc.TokenType != ')') { int flags = 0; char type = '@'; // Retrieve flags before type name for (;;) { if (sc.CheckToken(TK_Coerce) || sc.CheckToken(TK_Native)) { } else { break; } } // Read the variable type sc.MustGetAnyToken(); switch (sc.TokenType) { case TK_Bool: case TK_Int: type = 'x'; break; case TK_Float: type = 'y'; break; case TK_Sound: type = 's'; break; case TK_String: type = 't'; break; case TK_Name: type = 't'; break; case TK_State: type = 'l'; break; case TK_Color: type = 'c'; break; case TK_Class: sc.MustGetToken('<'); sc.MustGetToken(TK_Identifier); // Skip class name, since the parser doesn't care sc.MustGetToken('>'); type = 'm'; break; case TK_Ellipsis: type = '+'; sc.MustGetToken(')'); sc.UnGet(); break; default: sc.ScriptMessage ("Unknown variable type %s", sc.TokenName(sc.TokenType, sc.String).GetChars()); type = 'x'; FScriptPosition::ErrorCounter++; break; } // Read the optional variable name if (!sc.CheckToken(',') && !sc.CheckToken(')')) { sc.MustGetToken(TK_Identifier); } else { sc.UnGet(); } FxExpression *def; if (sc.CheckToken('=')) { hasdefaults = true; flags |= OPTIONAL; def = ParseParameter(sc, cls, type, true); } else { def = NULL; } DefaultParams.Push(def); if (!(flags & OPTIONAL) && type != '+') { type -= 'a' - 'A'; } args += type; sc.MustGetAnyToken(); if (sc.TokenType != ',' && sc.TokenType != ')') { sc.ScriptError ("Expected ',' or ')' but got %s instead", sc.TokenName(sc.TokenType, sc.String).GetChars()); } } } sc.MustGetToken(';'); PSymbolActionFunction *sym = new PSymbolActionFunction(funcname); sym->Arguments = args; sym->Function = afd->Function; if (hasdefaults) { sym->defaultparameterindex = StateParams.Size(); for(unsigned int i = 0; i < DefaultParams.Size(); i++) { StateParams.Add(DefaultParams[i], cls, true); } } else { sym->defaultparameterindex = -1; } if (error) { FScriptPosition::ErrorCounter++; } else if (cls->Symbols.AddSymbol (sym) == NULL) { delete sym; sc.ScriptMessage ("'%s' is already defined in class '%s'.", funcname.GetChars(), cls->TypeName.GetChars()); FScriptPosition::ErrorCounter++; } } //========================================================================== // // Starts a new actor definition // //========================================================================== static FActorInfo *ParseActorHeader(FScanner &sc, Baggage *bag) { FName typeName; FName parentName; FName replaceName; bool native = false; int DoomEdNum = -1; // Get actor name sc.MustGetString(); char *colon = strchr(sc.String, ':'); if (colon != NULL) { *colon++ = 0; } typeName = sc.String; // Do some tweaking so that a definition like 'Actor:Parent' which is read as a single token is recognized as well // without having resort to C-mode (which disallows periods in actor names.) if (colon == NULL) { sc.MustGetString (); if (sc.String[0]==':') { colon = sc.String + 1; } } if (colon != NULL) { if (colon[0] == 0) { sc.MustGetString (); colon = sc.String; } } if (colon == NULL) { sc.UnGet(); } parentName = colon; // Check for "replaces" if (sc.CheckString ("replaces")) { // Get actor name sc.MustGetString (); replaceName = sc.String; } // Now, after the actor names have been parsed, it is time to switch to C-mode // for the rest of the actor definition. sc.SetCMode (true); if (sc.CheckNumber()) { if (sc.Number>=-1 && sc.Number<32768) DoomEdNum = sc.Number; else { // does not need to be fatal. sc.ScriptMessage ("DoomEdNum must be in the range [-1,32767]"); FScriptPosition::ErrorCounter++; } } if (sc.CheckString("native")) { native = true; } try { FActorInfo *info = CreateNewActor(sc, typeName, parentName, native); info->DoomEdNum = DoomEdNum > 0? DoomEdNum : -1; SetReplacement(info, replaceName); ResetBaggage (bag, info->Class->ParentClass); bag->Info = info; bag->Lumpnum = sc.LumpNum; #ifdef _DEBUG bag->ClassName = typeName; #endif return info; } catch (CRecoverableError &err) { sc.ScriptError("%s", err.GetMessage()); return NULL; } } //========================================================================== // // Reads an actor definition // //========================================================================== static void ParseActor(FScanner &sc) { FActorInfo * info=NULL; Baggage bag; info = ParseActorHeader(sc, &bag); sc.MustGetToken('{'); while (sc.MustGetAnyToken(), sc.TokenType != '}') { switch (sc.TokenType) { case TK_Action: ParseActionDef (sc, info->Class); break; case TK_Const: ParseConstant (sc, &info->Class->Symbols, info->Class); break; case TK_Enum: ParseEnum (sc, &info->Class->Symbols, info->Class); break; case TK_Native: ParseVariable (sc, &info->Class->Symbols, info->Class); break; case TK_Identifier: ParseActorProperty(sc, bag); break; case '+': case '-': ParseActorFlag(sc, bag, sc.TokenType); break; default: sc.ScriptError("Unexpected '%s' in definition of '%s'", sc.String, bag.Info->Class->TypeName.GetChars()); break; } } FinishActor(sc, info, bag); sc.SetCMode (false); } //========================================================================== // // ParseDecorate // // Parses a single DECORATE lump // //========================================================================== void ParseDecorate (FScanner &sc) { // Get actor class name. for(;;) { FScanner::SavedPos pos = sc.SavePos(); if (!sc.GetToken ()) { return; } switch (sc.TokenType) { case TK_Include: { sc.MustGetString(); FScanner newscanner; newscanner.Open(sc.String); ParseDecorate(newscanner); break; } case TK_Const: ParseConstant (sc, &GlobalSymbols, NULL); break; case TK_Enum: ParseEnum (sc, &GlobalSymbols, NULL); break; case TK_Native: ParseVariable(sc, &GlobalSymbols, NULL); break; case ';': // ';' is the start of a comment in the non-cmode parser which // is used to parse parts of the DECORATE lump. If we don't add // a check here the user will only get weird non-informative // error messages if a semicolon is found. sc.ScriptError("Unexpected ';'"); break; case TK_Identifier: // 'ACTOR' cannot be a keyword because it is also needed as a class identifier // so let's do a special case for this. if (sc.Compare("ACTOR")) { ParseActor (sc); break; } else if (sc.Compare("PICKUP")) { ParseOldDecoration (sc, DEF_Pickup); break; } else if (sc.Compare("BREAKABLE")) { ParseOldDecoration (sc, DEF_BreakableDecoration); break; } else if (sc.Compare("PROJECTILE")) { ParseOldDecoration (sc, DEF_Projectile); break; } default: // without the option of game filters following, anything but an opening brace // here means a syntax error. sc.MustGetStringName("{"); sc.RestorePos(pos); ParseOldDecoration(sc, DEF_Decoration); break; } } }