/* ** 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 "i_system.h" #include "codegeneration/codegen.h" #include "w_wad.h" #include "v_video.h" #include "version.h" #include "v_text.h" #include "m_argv.h" void ParseOldDecoration(FScanner &sc, EDefinitionType def); EXTERN_CVAR(Bool, strictdecorate); //========================================================================== // // DecoDerivedClass // // Create a derived class and performs some additional sanity checks // //========================================================================== PClassActor *DecoDerivedClass(const FScriptPosition &sc, PClassActor *parent, FName typeName) { PClassActor *type = static_cast(parent->CreateDerivedClass(typeName, parent->Size)); if (type == nullptr) { FString newname = typeName.GetChars(); FString sourcefile = sc.FileName; sourcefile.Substitute(":", "@"); newname << '@' << sourcefile; if (strictdecorate) { sc.Message(MSG_ERROR, "Tried to define class '%s' more than once.", typeName.GetChars()); } else { // Due to backwards compatibility issues this cannot be an unconditional error. sc.Message(MSG_WARNING, "Tried to define class '%s' more than once. Renaming class to '%s'", typeName.GetChars(), newname.GetChars()); } type = static_cast(parent->CreateDerivedClass(newname, parent->Size)); if (type == nullptr) { // This we cannot handle cleanly anymore. Let's just abort and forget about the odd mod out that was this careless. sc.Message(MSG_FATAL, "Tried to define class '%s' more than twice in the same file.", typeName.GetChars()); } } return type; } //========================================================================== // // ParseParameter // // Parses a parameter - either a default in a function declaration // or an argument in a function call. // //========================================================================== FxExpression *ParseParameter(FScanner &sc, PClassActor *cls, PType *type, bool constant) { FxExpression *x = NULL; int v; if (type == TypeSound) { sc.MustGetString(); x = new FxConstant(FSoundID(sc.String), sc); } else if (type == TypeBool || type == TypeSInt32 || type == TypeFloat64) { x = ParseExpression (sc, cls, constant); if (constant && !x->isConstant()) { sc.ScriptMessage("Default parameter must be constant."); FScriptPosition::ErrorCounter++; } // Do automatic coercion between bools, ints and floats. if (type == TypeBool) { x = new FxBoolCast(x); } else if (type == TypeSInt32) { x = new FxIntCast(x, true); } else { x = new FxFloatCast(x); } } else if (type == TypeName || type == TypeString) { sc.SetEscape(true); sc.MustGetString(); sc.SetEscape(false); if (type == TypeName) { x = new FxConstant(sc.String[0] ? FName(sc.String) : NAME_None, sc); } else { x = new FxConstant(strbin1(sc.String), sc); } } else if (type == TypeColor) { 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 = TypeColor; val.Int = v; x = new FxConstant(val, sc); } else if (type == TypeState) { // This forces quotation marks around the state name. if (sc.CheckToken(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); } } else if (!constant) { x = new FxRuntimeStateIndex(ParseExpression(sc, cls)); } else sc.MustGetToken(TK_StringConst); // This is for the error. } else if (type->GetClass() == RUNTIME_CLASS(PClassPointer)) { // Actor name sc.SetEscape(true); sc.MustGetString(); sc.SetEscape(false); x = new FxClassTypeCast(static_cast(type), new FxConstant(FName(sc.String), sc)); } else { assert(false && "Unknown parameter type"); x = NULL; } return x; } //========================================================================== // // ActorConstDef // // Parses a constant definition. // //========================================================================== static void ParseConstant (FScanner &sc, PSymbolTable *symt, PClassActor *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, true); sc.MustGetToken(';'); if (expr == nullptr) { sc.ScriptMessage("Error while resolving constant definition"); FScriptPosition::ErrorCounter++; } else if (!expr->isConstant()) { sc.ScriptMessage("Constant definition is not a constant"); FScriptPosition::ErrorCounter++; } else { ExpVal val = static_cast(expr)->GetValue(); delete expr; PSymbolConstNumeric *sym; if (type == TK_Int) { sym = new PSymbolConstNumeric(symname, TypeSInt32); sym->Value = val.GetInt(); } else { sym = new PSymbolConstNumeric(symname, TypeFloat64); sym->Float = val.GetFloat(); } if (symt->AddSymbol (sym) == NULL) { delete sym; sc.ScriptMessage ("'%s' is already defined in '%s'.", symname.GetChars(), cls? cls->TypeName.GetChars() : "Global"); FScriptPosition::ErrorCounter++; } } } else { sc.ScriptMessage("Numeric type required for constant"); FScriptPosition::ErrorCounter++; } } //========================================================================== // // ActorEnumDef // // Parses an enum definition. // //========================================================================== static void ParseEnum (FScanner &sc, PSymbolTable *symt, PClassActor *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, true); if (expr != nullptr) { if (!expr->isConstant()) { sc.ScriptMessage("'%s' must be constant", symname.GetChars()); FScriptPosition::ErrorCounter++; } else { currvalue = static_cast(expr)->GetValue().GetInt(); } delete expr; } else { sc.ScriptMessage("Error while resolving expression of '%s'", symname.GetChars()); FScriptPosition::ErrorCounter++; } } PSymbolConstNumeric *sym = new PSymbolConstNumeric(symname, TypeSInt32); sym->Value = currvalue; if (symt->AddSymbol (sym) == NULL) { delete sym; sc.ScriptMessage ("'%s' is already defined in '%s'.", symname.GetChars(), cls? cls->TypeName.GetChars() : "Global"); FScriptPosition::ErrorCounter++; } // This allows a comma after the last value but doesn't enforce it. if (sc.CheckToken('}')) break; sc.MustGetToken(','); currvalue++; } sc.MustGetToken(';'); } //========================================================================== // // ParseUserVariable // // Parses a user variable declaration. // //========================================================================== static void ParseUserVariable (FScanner &sc, PSymbolTable *symt, PClassActor *cls) { PType *type; int maxelems = 1; // Only non-native classes may have user variables. if (!cls->bRuntimeClass) { sc.ScriptError("Native classes may not have user variables"); } // Read the type and make sure it's acceptable. sc.MustGetAnyToken(); if (sc.TokenType != TK_Int && sc.TokenType != TK_Float) { sc.ScriptMessage("User variables must be of type 'int' or 'float'"); FScriptPosition::ErrorCounter++; } type = sc.TokenType == TK_Int ? (PType *)TypeSInt32 : (PType *)TypeFloat64; sc.MustGetToken(TK_Identifier); // For now, restrict user variables to those that begin with "user_" to guarantee // no clashes with internal member variable names. if (sc.StringLen < 6 || strnicmp("user_", sc.String, 5) != 0) { sc.ScriptMessage("User variable names must begin with \"user_\""); FScriptPosition::ErrorCounter++; } FName symname = sc.String; // We must ensure that we do not define duplicates, even when they come from a parent table. if (symt->FindSymbol(symname, true) != NULL) { sc.ScriptMessage ("'%s' is already defined in '%s' or one of its ancestors.", symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global"); FScriptPosition::ErrorCounter++; return; } if (sc.CheckToken('[')) { FxExpression *expr = ParseExpression(sc, cls, true); if (expr == nullptr) { sc.ScriptMessage("Error while resolving array size"); FScriptPosition::ErrorCounter++; maxelems = 1; } else if (!expr->isConstant()) { sc.ScriptMessage("Array size must be a constant"); FScriptPosition::ErrorCounter++; maxelems = 1; } else { maxelems = static_cast(expr)->GetValue().GetInt(); } sc.MustGetToken(']'); if (maxelems <= 0) { sc.ScriptMessage("Array size must be positive"); FScriptPosition::ErrorCounter++; maxelems = 1; } type = NewArray(type, maxelems); } sc.MustGetToken(';'); PField *sym = cls->AddField(symname, type, 0); if (sym == NULL) { sc.ScriptMessage ("'%s' is already defined in '%s'.", symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global"); FScriptPosition::ErrorCounter++; } } //========================================================================== // // 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, part1, part2)) ) { AActor *defaults = (AActor*)bag.Info->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 { ModActorFlag(defaults, fd, mod == '+'); } } else { if (part2 == NULL) { sc.ScriptMessage("\"%s\" is an unknown flag\n", part1); } else { sc.ScriptMessage("\"%s.%s\" is an unknown flag\n", part1, part2); } FScriptPosition::ErrorCounter++; } } //========================================================================== // // [MH] parses a morph style expression // //========================================================================== struct FParseValue { const char *Name; int Flag; }; int ParseFlagExpressionString(FScanner &sc, const FParseValue *vals) { // 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 |= vals[sc.MustMatchString(&vals->Name, sizeof (*vals))].Flag; } while (sc.CheckString("|")); if (gotparen) { sc.MustGetStringName(")"); } return style; } static int ParseMorphStyle (FScanner &sc) { static const FParseValue morphstyles[]={ { "MRF_ADDSTAMINA", MORPH_ADDSTAMINA}, { "MRF_FULLHEALTH", MORPH_FULLHEALTH}, { "MRF_UNDOBYTOMEOFPOWER", MORPH_UNDOBYTOMEOFPOWER}, { "MRF_UNDOBYCHAOSDEVICE", MORPH_UNDOBYCHAOSDEVICE}, { "MRF_FAILNOTELEFRAG", MORPH_FAILNOTELEFRAG}, { "MRF_FAILNOLAUGH", MORPH_FAILNOLAUGH}, { "MRF_WHENINVULNERABLE", MORPH_WHENINVULNERABLE}, { "MRF_LOSEACTUALWEAPON", MORPH_LOSEACTUALWEAPON}, { "MRF_NEWTIDBEHAVIOUR", MORPH_NEWTIDBEHAVIOUR}, { "MRF_UNDOBYDEATH", MORPH_UNDOBYDEATH}, { "MRF_UNDOBYDEATHFORCED", MORPH_UNDOBYDEATHFORCED}, { "MRF_UNDOBYDEATHSAVES", MORPH_UNDOBYDEATHSAVES}, { "MRF_UNDOALWAYS", MORPH_UNDOALWAYS }, { "MRF_TRANSFERTRANSLATION", MORPH_TRANSFERTRANSLATION }, { NULL, 0 } }; return ParseFlagExpressionString(sc, morphstyles); } static int ParseThingActivation (FScanner &sc) { static const FParseValue activationstyles[]={ { "THINGSPEC_Default", THINGSPEC_Default}, { "THINGSPEC_ThingActs", THINGSPEC_ThingActs}, { "THINGSPEC_ThingTargets", THINGSPEC_ThingTargets}, { "THINGSPEC_TriggerTargets", THINGSPEC_TriggerTargets}, { "THINGSPEC_MonsterTrigger", THINGSPEC_MonsterTrigger}, { "THINGSPEC_MissileTrigger", THINGSPEC_MissileTrigger}, { "THINGSPEC_ClearSpecial", THINGSPEC_ClearSpecial}, { "THINGSPEC_NoDeathSpecial", THINGSPEC_NoDeathSpecial}, { "THINGSPEC_TriggerActs", THINGSPEC_TriggerActs}, { "THINGSPEC_Activate", THINGSPEC_Activate}, { "THINGSPEC_Deactivate", THINGSPEC_Deactivate}, { "THINGSPEC_Switch", THINGSPEC_Switch}, { NULL, 0 } }; return ParseFlagExpressionString(sc, activationstyles); } //========================================================================== // // 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(); PClassActor *info = dyn_cast(type->ParentClass); 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.ScriptMessage("Attempt to get invalid state from actor %s\n", type->ParentClass->TypeName.GetChars()); FScriptPosition::ErrorCounter++; return NULL; } state += v; return state; } else { sc.ScriptMessage("Invalid state assignment"); FScriptPosition::ErrorCounter++; } } 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; bag.ScriptPosition = sc; switch ((*p) & 223) { case 'X': // Expression in parentheses or number. { FxExpression *x = NULL; if (sc.CheckString ("(")) { conv.i = -1; params.Push(conv); x = ParseExpression(sc, bag.Info); sc.MustGetStringName(")"); conv.exp = x; params.Push(conv); } else { sc.MustGetNumber(); conv.i = sc.Number; params.Push(conv); conv.exp = nullptr; } } break; case 'I': sc.MustGetNumber(); conv.i = sc.Number; break; case 'F': sc.MustGetFloat(); conv.d = 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': sc.MustGetString(); conv.s = strings[strings.Reserve(1)] = sc.String; break; case 'T': sc.MustGetString(); conv.s = strings[strings.Reserve(1)] = strbin1(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 'N': // special case. An expression-aware parser will not need this. conv.i = ParseThingActivation(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->IsDescendantOf(*prop->cls)) { ParsePropertyParams(sc, prop, (AActor *)bag.Info->Defaults, bag); } else { sc.ScriptMessage("'%s' requires an actor of type '%s'\n", propname.GetChars(), (*prop->cls)->TypeName.GetChars()); FScriptPosition::ErrorCounter++; } } else if (MatchString(propname, statenames) != -1) { bag.statedef.SetStateLabel(propname, CheckState (sc, bag.Info)); } else { sc.ScriptError("'%s' is an unknown actor property\n", propname.GetChars()); } } //========================================================================== // // Starts a new actor definition // //========================================================================== PClassActor *CreateNewActor(const FScriptPosition &sc, FName typeName, FName parentName, bool native) { PClassActor *replacee = NULL; PClassActor *ti = NULL; PClassActor *parent = RUNTIME_CLASS(AActor); if (parentName != NAME_None) { parent = PClass::FindActor(parentName); PClassActor *p = parent; while (p != NULL) { if (p->TypeName == typeName) { sc.Message(MSG_ERROR, "'%s' inherits from a class with the same name", typeName.GetChars()); break; } p = dyn_cast(p->ParentClass); } if (parent == NULL) { sc.Message(MSG_ERROR, "Parent type '%s' not found in %s", parentName.GetChars(), typeName.GetChars()); parent = RUNTIME_CLASS(AActor); } else if (!parent->IsDescendantOf(RUNTIME_CLASS(AActor))) { sc.Message(MSG_ERROR, "Parent type '%s' is not an actor in %s", parentName.GetChars(), typeName.GetChars()); parent = RUNTIME_CLASS(AActor); } } if (native) { ti = PClass::FindActor(typeName); if (ti == NULL) { extern void DumpTypeTable(); DumpTypeTable(); sc.Message(MSG_ERROR, "Unknown native actor '%s'", typeName.GetChars()); goto create; } else if (ti != RUNTIME_CLASS(AActor) && ti->ParentClass->NativeClass() != parent->NativeClass()) { sc.Message(MSG_ERROR, "Native class '%s' does not inherit from '%s'", typeName.GetChars(), parentName.GetChars()); parent = RUNTIME_CLASS(AActor); goto create; } else if (ti->Defaults != NULL) { sc.Message(MSG_ERROR, "Redefinition of internal class '%s'", typeName.GetChars()); goto create; } ti->InitializeNativeDefaults(); ti->ParentClass->DeriveData(ti); } else { create: ti = DecoDerivedClass(sc, parent, typeName); } ti->Replacee = ti->Replacement = NULL; ti->DoomEdNum = -1; return ti; } //========================================================================== // // Starts a new actor definition // //========================================================================== static PClassActor *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; if (replaceName == typeName) { sc.ScriptMessage ("Cannot replace class %s with itself", typeName.GetChars()); FScriptPosition::ErrorCounter++; } } // 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 { PClassActor *info = CreateNewActor(sc, typeName, parentName, native); info->DoomEdNum = DoomEdNum > 0 ? DoomEdNum : -1; info->SourceLumpName = Wads.GetLumpFullPath(sc.LumpNum); if (!info->SetReplacement(replaceName)) { sc.ScriptMessage("Replaced type '%s' not found for %s", replaceName.GetChars(), info->TypeName.GetChars()); } ResetBaggage (bag, info == RUNTIME_CLASS(AActor) ? NULL : static_cast(info->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) { PClassActor *info = NULL; Baggage bag; info = ParseActorHeader(sc, &bag); sc.MustGetToken('{'); while (sc.MustGetAnyToken(), sc.TokenType != '}') { switch (sc.TokenType) { case TK_Const: ParseConstant (sc, &info->Symbols, info); break; case TK_Enum: ParseEnum (sc, &info->Symbols, info); break; case TK_Var: ParseUserVariable (sc, &info->Symbols, info); break; case TK_Identifier: ParseActorProperty(sc, bag); break; case TK_States: if (bag.StateSet) { sc.ScriptMessage("'%s' contains multiple state declarations", bag.Info->TypeName.GetChars()); FScriptPosition::ErrorCounter++; } ParseStates(sc, bag.Info, (AActor *)bag.Info->Defaults, bag); bag.StateSet = true; break; case '+': case '-': ParseActorFlag(sc, bag, sc.TokenType); break; default: sc.ScriptError("Unexpected '%s' in definition of '%s'", sc.String, bag.Info->TypeName.GetChars()); break; } } if (bag.DropItemSet) { bag.Info->SetDropItems(bag.DropItemList); } try { info->Finalize(bag.statedef); } catch (CRecoverableError &err) { sc.ScriptError("%s", err.GetMessage()); } sc.SetCMode (false); } //========================================================================== // // Reads a damage definition // //========================================================================== static void ParseDamageDefinition(FScanner &sc) { sc.SetCMode (true); // This may be 100% irrelevant for such a simple syntax, but I don't know // Get DamageType sc.MustGetString(); FName damageType = sc.String; DamageTypeDefinition dtd; sc.MustGetToken('{'); while (sc.MustGetAnyToken(), sc.TokenType != '}') { if (sc.Compare("FACTOR")) { sc.MustGetFloat(); dtd.DefaultFactor = sc.Float; if (dtd.DefaultFactor == 0) dtd.ReplaceFactor = true; } else if (sc.Compare("REPLACEFACTOR")) { dtd.ReplaceFactor = true; } else if (sc.Compare("NOARMOR")) { dtd.NoArmor = true; } else { sc.ScriptError("Unexpected data (%s) in damagetype definition.", sc.String); } } dtd.Apply(damageType); sc.SetCMode (false); // (set to true earlier in function) } //========================================================================== // // 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(); // This check needs to remain overridable for testing purposes. if (Wads.GetLumpFile(sc.LumpNum) == 0 && !Args->CheckParm("-allowdecoratecrossincludes")) { int includefile = Wads.GetLumpFile(Wads.CheckNumForFullName(sc.String, true)); if (includefile != 0) { I_FatalError("File %s is overriding core lump %s.", Wads.GetWadFullName(includefile), sc.String); } } 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 ';': // ';' 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; } else if (sc.Compare("DAMAGETYPE")) { ParseDamageDefinition(sc); break; } default: sc.RestorePos(pos); ParseOldDecoration(sc, DEF_Decoration); break; } } } void ParseAllDecorate() { int lastlump = 0, lump; while ((lump = Wads.FindLump("DECORATE", &lastlump)) != -1) { FScanner sc(lump); ParseDecorate(sc); } }