/* ** thingdef.cpp ** ** Actor definitions ** **--------------------------------------------------------------------------- ** Copyright 2002-2008 Christoph Oelckers ** Copyright 2004-2008 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 "gi.h" #include "actor.h" #include "info.h" #include "sc_man.h" #include "tarray.h" #include "w_wad.h" #include "templates.h" #include "r_defs.h" #include "a_pickups.h" #include "s_sound.h" #include "cmdlib.h" #include "p_lnspec.h" #include "a_action.h" #include "decallib.h" #include "m_random.h" #include "i_system.h" #include "m_argv.h" #include "p_local.h" #include "doomerrors.h" #include "a_weaponpiece.h" #include "p_conversation.h" #include "v_text.h" #include "thingdef.h" #include "thingdef_exp.h" #include "a_sharedglobal.h" #include "vmbuilder.h" #include "stats.h" TDeletingArray ActorDamageFuncs; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- void InitThingdef(); void ParseDecorate(FScanner &ctx); // STATIC FUNCTION PROTOTYPES -------------------------------------------- PClassActor *QuestItemClasses[31]; EXTERN_CVAR(Bool, strictdecorate); 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; } //========================================================================== // // 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; } //========================================================================== // // // //========================================================================== void SetReplacement(FScanner &sc, PClassActor *info, FName replaceName) { // Check for "replaces" if (replaceName != NAME_None) { // Get actor name PClassActor *replacee = PClass::FindActor(replaceName); if (replacee == NULL) { sc.ScriptMessage("Replaced type '%s' not found for %s", replaceName.GetChars(), info->TypeName.GetChars()); return; } if (replacee != NULL) { replacee->Replacement = info; info->Replacee = replacee; } } } //========================================================================== // // Finalizes an actor definition // //========================================================================== void FinishActor(const FScriptPosition &sc, PClassActor *info, Baggage &bag) { AActor *defaults = (AActor*)info->Defaults; try { bag.statedef.FinishStates (info, defaults); } catch (CRecoverableError &err) { sc.Message(MSG_ERROR, "%s", err.GetMessage()); bag.statedef.MakeStateDefines(NULL); return; } bag.statedef.InstallStates (info, defaults); bag.statedef.MakeStateDefines(NULL); if (bag.DropItemSet) { info->DropItems = bag.DropItemList; GC::WriteBarrier(info, info->DropItems); } if (info->IsDescendantOf (RUNTIME_CLASS(AInventory))) { defaults->flags |= MF_SPECIAL; } // Weapons must be checked for all relevant states. They may crash the game otherwise. if (info->IsDescendantOf(RUNTIME_CLASS(AWeapon))) { FState *ready = info->FindState(NAME_Ready); FState *select = info->FindState(NAME_Select); FState *deselect = info->FindState(NAME_Deselect); FState *fire = info->FindState(NAME_Fire); // Consider any weapon without any valid state abstract and don't output a warning // This is for creating base classes for weapon groups that only set up some properties. if (ready || select || deselect || fire) { if (!ready) { sc.Message(MSG_ERROR, "Weapon %s doesn't define a ready state.\n", info->TypeName.GetChars()); } if (!select) { sc.Message(MSG_ERROR, "Weapon %s doesn't define a select state.\n", info->TypeName.GetChars()); } if (!deselect) { sc.Message(MSG_ERROR, "Weapon %s doesn't define a deselect state.\n", info->TypeName.GetChars()); } if (!fire) { sc.Message(MSG_ERROR, "Weapon %s doesn't define a fire state.\n", info->TypeName.GetChars()); } } } } //========================================================================== // // Do some postprocessing after everything has been defined // //========================================================================== static void DumpFunction(FILE *dump, VMScriptFunction *sfunc, const char *label, int labellen) { const char *marks = "======================================================="; fprintf(dump, "\n%.*s %s %.*s", MAX(3, 38 - labellen / 2), marks, label, MAX(3, 38 - labellen / 2), marks); fprintf(dump, "\nInteger regs: %-3d Float regs: %-3d Address regs: %-3d String regs: %-3d\nStack size: %d\n", sfunc->NumRegD, sfunc->NumRegF, sfunc->NumRegA, sfunc->NumRegS, sfunc->MaxParam); VMDumpConstants(dump, sfunc); fprintf(dump, "\nDisassembly @ %p:\n", sfunc->Code); VMDisasm(dump, sfunc->Code, sfunc->CodeSize, sfunc); } static void FinishThingdef() { int errorcount = 0; unsigned i; int codesize = 0; FILE *dump = NULL; if (Args->CheckParm("-dumpdisasm")) dump = fopen("disasm.txt", "w"); for (i = 0; i < StateTempCalls.Size(); ++i) { FStateTempCall *tcall = StateTempCalls[i]; VMFunction *func = nullptr; assert(tcall->Code != NULL); // We don't know the return type in advance for anonymous functions. FCompileContext ctx(tcall->ActorClass, nullptr); tcall->Code = tcall->Code->Resolve(ctx); tcall->Proto = ctx.ReturnProto; // Make sure resolving it didn't obliterate it. if (tcall->Code != nullptr) { // Can we call this function directly without wrapping it in an // anonymous function? e.g. Are we passing any parameters to it? func = tcall->Code->GetDirectFunction(); if (func == nullptr) { VMFunctionBuilder buildit(true); assert(tcall->Proto != nullptr); // Allocate registers used to pass parameters in. // self, stateowner, state (all are pointers) buildit.Registers[REGT_POINTER].Get(3); // Emit code tcall->Code->Emit(&buildit); VMScriptFunction *sfunc = buildit.MakeFunction(); sfunc->NumArgs = NAP; // Generate prototype for this anonymous function TArray args(3); SetImplicitArgs(&args, NULL, tcall->ActorClass, VARF_Method | VARF_Action); sfunc->Proto = NewPrototype(tcall->Proto->ReturnTypes, args); func = sfunc; if (dump != NULL) { char label[64]; int labellen = mysnprintf(label, countof(label), "Function %s.States[%d] (*%d)", tcall->ActorClass->TypeName.GetChars(), tcall->FirstState, tcall->NumStates); DumpFunction(dump, sfunc, label, labellen); codesize += sfunc->CodeSize; } } delete tcall->Code; tcall->Code = nullptr; for (int k = 0; k < tcall->NumStates; ++k) { tcall->ActorClass->OwnedStates[tcall->FirstState + k].SetAction(func); } } } for (i = 0; i < PClassActor::AllActorClasses.Size(); i++) { PClassActor *ti = PClassActor::AllActorClasses[i]; if (ti->Size == TentativeClass) { Printf(TEXTCOLOR_RED "Class %s referenced but not defined\n", ti->TypeName.GetChars()); errorcount++; continue; } AActor *def = GetDefaultByType(ti); if (!def) { Printf(TEXTCOLOR_RED "No ActorInfo defined for class '%s'\n", ti->TypeName.GetChars()); errorcount++; continue; } if (def->DamageFunc != nullptr) { FxDamageValue *dmg = (FxDamageValue *)ActorDamageFuncs[(uintptr_t)def->DamageFunc - 1]; VMScriptFunction *sfunc; sfunc = dmg->GetFunction(); if (sfunc == nullptr) { FCompileContext ctx(ti); dmg = static_cast(dmg->Resolve(ctx)); if (dmg != nullptr) { VMFunctionBuilder buildit; buildit.Registers[REGT_POINTER].Get(1); // The self pointer dmg->Emit(&buildit); sfunc = buildit.MakeFunction(); sfunc->NumArgs = 1; sfunc->Proto = nullptr; ///FIXME: Need a proper prototype here // Save this function in case this damage value was reused // (which happens quite easily with inheritance). dmg->SetFunction(sfunc); } } def->DamageFunc = sfunc; if (dump != nullptr && sfunc != nullptr) { char label[64]; int labellen = mysnprintf(label, countof(label), "Function %s.Damage", ti->TypeName.GetChars()); DumpFunction(dump, sfunc, label, labellen); codesize += sfunc->CodeSize; } } } if (dump != NULL) { fprintf(dump, "\n*************************************************************************\n%i code bytes\n", codesize * 4); fclose(dump); } if (errorcount > 0) { I_Error("%d errors during actor postprocessing", errorcount); } ActorDamageFuncs.DeleteAndClear(); StateTempCalls.DeleteAndClear(); // Since these are defined in DECORATE now the table has to be initialized here. for(int i = 0; i < 31; i++) { char fmt[20]; mysnprintf(fmt, countof(fmt), "QuestItem%d", i+1); QuestItemClasses[i] = PClass::FindActor(fmt); } } //========================================================================== // // LoadActors // // Called from FActor::StaticInit() // //========================================================================== void LoadActors () { int lastlump, lump; cycle_t timer; timer.Reset(); timer.Clock(); ActorDamageFuncs.Clear(); FScriptPosition::ResetErrorCounter(); InitThingdef(); lastlump = 0; while ((lump = Wads.FindLump ("DECORATE", &lastlump)) != -1) { FScanner sc(lump); if (Wads.GetLumpFile(lump) == 0) { // define namespace 'zdoom' } else { // use namespace 'zdoom' } ParseDecorate (sc); } FinishThingdef(); if (FScriptPosition::ErrorCounter > 0) { I_Error("%d errors while parsing DECORATE scripts", FScriptPosition::ErrorCounter); } timer.Unclock(); if (!batchrun) Printf("DECORATE parsing took %.2f ms\n", timer.TimeMS()); // Base time: ~52 ms }