qzdoom/src/thingdef/thingdef.cpp
Randy Heit 6f1bf257e9 - State code now properly calls action functions and has a RET instruction. As expected,
running with the checked VM can be quite slow, since it has asserts everywhere. Some other
  fixes were needed before the code actually worked:
  - A_CallSpecial needs to have its arguments cast to ints.
  - Some functions that set pnum/paramnum directly did not decrement it by 1. This also applies
    to A_Jump, though it just uses the value of paramnum instead of changing it.
  - Renamed pnum in the PARAM macros to paramnum, since pnum is already used in a few other
    places for something different, so this makes searching for it easier.
  This has not been tested especially thoroughly, but a first glance seems to indicate success.

SVN r2163 (scripting)
2010-02-14 19:59:21 +00:00

410 lines
12 KiB
C++

/*
** 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 "r_draw.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 "p_local.h"
#include "doomerrors.h"
#include "a_hexenglobal.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"
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
void InitThingdef();
void ParseDecorate (FScanner &sc);
// STATIC FUNCTION PROTOTYPES --------------------------------------------
const PClass *QuestItemClasses[31];
PSymbolTable GlobalSymbols;
//==========================================================================
//
// Starts a new actor definition
//
//==========================================================================
FActorInfo *CreateNewActor(const FScriptPosition &sc, FName typeName, FName parentName, bool native)
{
const PClass *replacee = NULL;
PClass *ti = NULL;
FActorInfo *info = NULL;
PClass *parent = RUNTIME_CLASS(AActor);
if (parentName != NAME_None)
{
parent = const_cast<PClass *> (PClass::FindClass (parentName));
const PClass *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 = 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);
}
else if (parent->ActorInfo == NULL)
{
sc.Message(MSG_ERROR, "uninitialized parent type '%s' in %s", parentName.GetChars(), typeName.GetChars());
parent = RUNTIME_CLASS(AActor);
}
}
if (native)
{
ti = (PClass*)PClass::FindClass(typeName);
if (ti == NULL)
{
sc.Message(MSG_ERROR, "Unknown native class '%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->ActorInfo != NULL)
{
sc.Message(MSG_ERROR, "Redefinition of internal class '%s'", typeName.GetChars());
goto create;
}
ti->InitializeActorInfo();
info = ti->ActorInfo;
}
else
{
create:
ti = parent->CreateDerivedClass (typeName, parent->Size);
info = ti->ActorInfo;
}
if (parent->ActorInfo->DamageFactors != NULL)
{
// copy damage factors from parent
info->DamageFactors = new DmgFactors;
*info->DamageFactors = *parent->ActorInfo->DamageFactors;
}
if (parent->ActorInfo->PainChances != NULL)
{
// copy pain chances from parent
info->PainChances = new PainChanceList;
*info->PainChances = *parent->ActorInfo->PainChances;
}
info->Replacee = info->Replacement = NULL;
info->DoomEdNum = -1;
return info;
}
//==========================================================================
//
//
//
//==========================================================================
void SetReplacement(FActorInfo *info, FName replaceName)
{
// Check for "replaces"
if (replaceName != NAME_None)
{
// Get actor name
const PClass *replacee = PClass::FindClass (replaceName);
if (replacee == NULL)
{
I_Error ("Replaced type '%s' not found in %s", replaceName.GetChars(), info->Class->TypeName.GetChars());
}
else if (replacee->ActorInfo == NULL)
{
I_Error ("Replaced type '%s' is not an actor in %s", replaceName.GetChars(), info->Class->TypeName.GetChars());
}
if (replacee != NULL)
{
replacee->ActorInfo->Replacement = info;
info->Replacee = replacee->ActorInfo;
}
}
}
//==========================================================================
//
// Finalizes an actor definition
//
//==========================================================================
void FinishActor(const FScriptPosition &sc, FActorInfo *info, Baggage &bag)
{
PClass *ti = info->Class;
AActor *defaults = (AActor*)ti->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)
{
if (bag.DropItemList == NULL)
{
if (ti->Meta.GetMetaInt (ACMETA_DropItems) != 0)
{
ti->Meta.SetMetaInt (ACMETA_DropItems, 0);
}
}
else
{
ti->Meta.SetMetaInt (ACMETA_DropItems,
StoreDropItemChain(bag.DropItemList));
}
}
if (ti->IsDescendantOf (RUNTIME_CLASS(AInventory)))
{
defaults->flags |= MF_SPECIAL;
}
// Weapons must be checked for all relevant states. They may crash the game otherwise.
if (ti->IsDescendantOf(RUNTIME_CLASS(AWeapon)))
{
FState * ready = ti->ActorInfo->FindState(NAME_Ready);
FState * select = ti->ActorInfo->FindState(NAME_Select);
FState * deselect = ti->ActorInfo->FindState(NAME_Deselect);
FState * fire = ti->ActorInfo->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", ti->TypeName.GetChars());
}
if (!select)
{
sc.Message(MSG_ERROR, "Weapon %s doesn't define a select state.\n", ti->TypeName.GetChars());
}
if (!deselect)
{
sc.Message(MSG_ERROR, "Weapon %s doesn't define a deselect state.\n", ti->TypeName.GetChars());
}
if (!fire)
{
sc.Message(MSG_ERROR, "Weapon %s doesn't define a fire state.\n", ti->TypeName.GetChars());
}
}
}
}
//==========================================================================
//
// Do some postprocessing after everything has been defined
//
//==========================================================================
static void FinishThingdef()
{
int errorcount = StateParams.ResolveAll();
unsigned i, j;
FILE *dump = fopen("disasm.txt", "w");
for (i = 0; i < StateTempCalls.Size(); ++i)
{
FStateTempCall *tcall = StateTempCalls[i];
VMFunction *func;
assert(tcall->Function != NULL);
if (tcall->Parameters.Size() == 0)
{
func = tcall->Function;
}
else
{
FCompileContext ctx(tcall->ActorInfo->Class, true);
for (j = 0; j < tcall->Parameters.Size(); ++j)
{
tcall->Parameters[j]->Resolve(ctx);
}
VMFunctionBuilder buildit;
// Allocate registers used to pass parameters in.
// self, stateowner, state, statecalldata (all are pointers)
buildit.Registers[REGT_POINTER].Get(4);
// Emit code to pass the standard action function parameters.
buildit.Emit(OP_PARAM, 0, REGT_POINTER, 0);
buildit.Emit(OP_PARAM, 0, REGT_POINTER, 1);
buildit.Emit(OP_PARAM, 0, REGT_POINTER, 2);
buildit.Emit(OP_PARAM, 0, REGT_POINTER, 3);
// Emit code for action parameters.
for (j = 0; j < tcall->Parameters.Size(); ++j)
{
FxExpression *p = /*new FxParameter*/(tcall->Parameters[j]);
p->Emit(&buildit);
delete p;
}
buildit.Emit(OP_CALL_K, buildit.GetConstantAddress(tcall->Function, ATAG_OBJECT), NAP + j, 0);
buildit.Emit(OP_RET, 0, REGT_NIL, 0);
VMScriptFunction *sfunc = buildit.MakeFunction();
sfunc->NumArgs = NAP;
func = sfunc;
#if 1
const char *marks = "=======================================================";
char label[64];
int labellen = mysnprintf(label, countof(label), "Function %s.States[%d] (*%d)",
tcall->ActorInfo->Class->TypeName.GetChars(),
tcall->FirstState, tcall->NumStates);
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);
#endif
}
for (int k = 0; k < tcall->NumStates; ++k)
{
tcall->ActorInfo->OwnedStates[tcall->FirstState + k].SetAction(func);
}
}
fclose(dump);
for (i = 0; i < PClass::m_Types.Size(); i++)
{
PClass * ti = PClass::m_Types[i];
// Skip non-actors
if (!ti->IsDescendantOf(RUNTIME_CLASS(AActor))) continue;
if (ti->Size == (unsigned)-1)
{
Printf("Class %s referenced but not defined\n", ti->TypeName.GetChars());
errorcount++;
continue;
}
AActor *def = GetDefaultByType(ti);
if (!def)
{
Printf("No ActorInfo defined for class '%s'\n", ti->TypeName.GetChars());
errorcount++;
continue;
}
}
if (errorcount > 0)
{
I_Error("%d errors during actor postprocessing", errorcount);
}
// 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::FindClass(fmt);
}
}
//==========================================================================
//
// LoadActors
//
// Called from FActor::StaticInit()
//
//==========================================================================
void LoadActors ()
{
int lastlump, lump;
FScriptPosition::ResetErrorCounter();
InitThingdef();
lastlump = 0;
while ((lump = Wads.FindLump ("DECORATE", &lastlump)) != -1)
{
FScanner sc(lump);
ParseDecorate (sc);
}
if (FScriptPosition::ErrorCounter > 0)
{
I_Error("%d errors while parsing DECORATE scripts", FScriptPosition::ErrorCounter);
}
FinishThingdef();
}