qzdoom/src/info.cpp

836 lines
21 KiB
C++
Raw Normal View History

/*
** info.cpp
** Keeps track of available actors and their states
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 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.
**
** 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.
**---------------------------------------------------------------------------
**
** This is completely different from Doom's info.c.
**
** The primary advancement over Doom's system is that actors can be defined
** across multiple files without having to recompile most of the source
** whenever one changes.
*/
#include "info.h"
#include "m_fixed.h"
#include "c_dispatch.h"
#include "autosegs.h"
#include "gi.h"
#include "actor.h"
#include "r_state.h"
#include "i_system.h"
#include "p_local.h"
#include "templates.h"
extern void LoadDecorations (void (*process)(FState *, int));
// Each state is owned by an actor. Actors can own any number of
// states, but a single state cannot be owned by more than one
// actor. States are archived by recording the actor they belong
// to and the index into that actor's list of states.
// For NULL states, which aren't owned by any actor, the owner
// is recorded as AActor with the following state. AActor should
// never actually have this many states of its own, so this
// is (relatively) safe.
#define NULL_STATE_INDEX 127
//==========================================================================
//
//
//==========================================================================
FArchive &operator<< (FArchive &arc, FState *&state)
{
const PClass *info;
2006-05-06 03:25:12 +00:00
if (arc.IsStoring ())
{
if (state == NULL)
{
arc.UserWriteClass (RUNTIME_CLASS(AActor));
arc.WriteCount (NULL_STATE_INDEX);
return arc;
}
2006-05-06 03:25:12 +00:00
info = FState::StaticFindStateOwner (state);
2006-05-06 03:25:12 +00:00
if (info != NULL)
{
2006-05-06 03:25:12 +00:00
arc.UserWriteClass (info);
arc.WriteCount ((DWORD)(state - info->ActorInfo->OwnedStates));
}
2006-05-06 03:25:12 +00:00
else
{
I_Error ("Cannot find owner for state %p:\n"
"%s %c%c %3d [%p] -> %p", state,
sprites[state->sprite.index].name,
state->GetFrame() + 'A',
state->GetFullbright() ? '*' : ' ',
state->GetTics(),
state->GetAction(),
state->GetNextState());
}
}
else
{
const PClass *info;
DWORD ofs;
arc.UserReadClass (info);
ofs = arc.ReadCount ();
if (ofs == NULL_STATE_INDEX && info == RUNTIME_CLASS(AActor))
{
state = NULL;
}
else if (info->ActorInfo != NULL)
{
state = info->ActorInfo->OwnedStates + ofs;
}
else
{
state = NULL;
}
}
return arc;
}
//==========================================================================
//
2006-05-06 03:25:12 +00:00
// Find the actor that a state belongs to.
//
//==========================================================================
const PClass *FState::StaticFindStateOwner (const FState *state)
2006-05-06 03:25:12 +00:00
{
const FActorInfo *info = RUNTIME_CLASS(AActor)->ActorInfo;
if (state >= info->OwnedStates &&
state < info->OwnedStates + info->NumOwnedStates)
{
return RUNTIME_CLASS(AActor);
}
TAutoSegIterator<FActorInfo *, &ARegHead, &ARegTail> reg;
while (++reg != NULL)
{
if (state >= reg->OwnedStates &&
state < reg->OwnedStates + reg->NumOwnedStates)
{
return reg->Class;
}
}
for (unsigned int i = 0; i < PClass::m_RuntimeActors.Size(); ++i)
2006-05-06 03:25:12 +00:00
{
info = PClass::m_RuntimeActors[i]->ActorInfo;
2006-05-06 03:25:12 +00:00
if (state >= info->OwnedStates &&
state < info->OwnedStates + info->NumOwnedStates)
{
return info->Class;
}
}
return NULL;
}
//==========================================================================
//
2006-05-06 03:25:12 +00:00
// Find the actor that a state belongs to, but restrict the search to
// the specified type and its ancestors.
//
//==========================================================================
const PClass *FState::StaticFindStateOwner (const FState *state, const FActorInfo *info)
2006-05-06 03:25:12 +00:00
{
while (info != NULL)
{
if (state >= info->OwnedStates &&
state < info->OwnedStates + info->NumOwnedStates)
{
return info->Class;
}
info = info->Class->ParentClass->ActorInfo;
2006-05-06 03:25:12 +00:00
}
return NULL;
}
//==========================================================================
//
//
//==========================================================================
int GetSpriteIndex(const char * spritename)
{
for (unsigned i = 0; i < sprites.Size (); ++i)
{
if (strncmp (sprites[i].name, spritename, 4) == 0)
{
return (int)i;
}
}
spritedef_t temp;
strncpy (temp.name, spritename, 4);
temp.name[4] = 0;
temp.numframes = 0;
temp.spriteframes = 0;
return (int)sprites.Push (temp);
}
//==========================================================================
//
// Change sprite names to indices
//
//==========================================================================
static void ProcessStates (FState *states, int numstates)
{
int sprite = -1;
if (states == NULL)
return;
while (--numstates >= 0)
{
if (sprite == -1 || strncmp (sprites[sprite].name, states->sprite.name, 4) != 0)
{
sprite = GetSpriteIndex(states->sprite.name);
}
states->sprite.index = sprite;
states++;
}
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::StaticInit ()
{
TAutoSegIterator<FActorInfo *, &ARegHead, &ARegTail> reg;
if (sprites.Size() == 0)
{
spritedef_t temp;
// Sprite 0 is always TNT1
memcpy (temp.name, "TNT1", 5);
temp.numframes = 0;
temp.spriteframes = 0;
sprites.Push (temp);
// Sprite 1 is always ----
memcpy (temp.name, "----", 5);
sprites.Push (temp);
}
// Attach FActorInfo structures to every actor's PClass
while (++reg != NULL)
{
reg->Class->ActorInfo = reg;
if (reg->OwnedStates &&
(unsigned)reg->OwnedStates->sprite.index < sprites.Size ())
{
Printf ("\x1c+%s is stateless. Fix its default list.\n",
reg->Class->TypeName.GetChars());
}
ProcessStates (reg->OwnedStates, reg->NumOwnedStates);
}
// Now build default instances of every actor
reg.Reset ();
while (++reg != NULL)
{
reg->BuildDefaults ();
}
LoadDecorations (ProcessStates);
}
//==========================================================================
//
// Called after the IWAD has been identified
//
//==========================================================================
void FActorInfo::StaticGameSet ()
{
// Run every AT_GAME_SET function
TAutoSegIteratorNoArrow<void (*)(), &GRegHead, &GRegTail> setters;
while (++setters != NULL)
{
((void (*)())setters) ();
}
}
//==========================================================================
//
// Called after Dehacked patches are applied
//
//==========================================================================
void FActorInfo::StaticSetActorNums ()
{
memset (SpawnableThings, 0, sizeof(SpawnableThings));
DoomEdMap.Empty ();
// For every actor valid for this game, add it to the
// SpawnableThings array and DoomEdMap
TAutoSegIterator<FActorInfo *, &ARegHead, &ARegTail> reg;
while (++reg != NULL)
{
reg->RegisterIDs ();
}
for (unsigned int i = 0; i < PClass::m_RuntimeActors.Size(); ++i)
{
PClass::m_RuntimeActors[i]->ActorInfo->RegisterIDs ();
}
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::RegisterIDs ()
{
if (GameFilter == GAME_Any || (GameFilter & gameinfo.gametype))
{
if (SpawnID != 0)
{
SpawnableThings[SpawnID] = Class;
}
if (DoomEdNum != -1)
{
DoomEdMap.AddType (DoomEdNum, Class);
}
}
}
//==========================================================================
//
// Called when a new game is started, but only if the game
// speed has changed.
//
//==========================================================================
void FActorInfo::StaticSpeedSet ()
{
TAutoSegIteratorNoArrow<void (*)(int), &SRegHead, &SRegTail> setters;
while (++setters != NULL)
{
((void (*)(int))setters) (GameSpeed);
}
}
//==========================================================================
//
//
//==========================================================================
FActorInfo *FActorInfo::GetReplacement ()
{
if (Replacement == NULL)
{
return this;
}
// The Replacement field is temporarily NULLed to prevent
// potential infinite recursion.
FActorInfo *savedrep = Replacement;
Replacement = NULL;
FActorInfo *rep = savedrep->GetReplacement ();
Replacement = savedrep;
return rep;
}
//==========================================================================
//
//
//==========================================================================
FActorInfo *FActorInfo::GetReplacee ()
{
if (Replacee == NULL)
{
return this;
}
// The Replacee field is temporarily NULLed to prevent
// potential infinite recursion.
FActorInfo *savedrep = Replacee;
Replacee = NULL;
FActorInfo *rep = savedrep->GetReplacee ();
Replacee = savedrep;
return rep;
}
//==========================================================================
//
//
//==========================================================================
FStateLabel *FStateLabels::FindLabel (FName label)
{
return const_cast<FStateLabel *>(BinarySearch<FStateLabel, FName> (Labels, NumLabels, &FStateLabel::Label, label));
}
void FStateLabels::Destroy ()
{
for(int i=0; i<NumLabels;i++)
{
if (Labels[i].Children != NULL)
{
Labels[i].Children->Destroy();
free (Labels[i].Children); // These are malloc'd, not new'd!
Labels[i].Children=NULL;
}
}
}
//===========================================================================
//
// FindState (one name version)
//
// Finds a state with the exact specified name.
//
//===========================================================================
FState *AActor::FindState (FName label) const
{
const FActorInfo *info = GetClass()->ActorInfo;
FStateLabel *slabel;
while (info != NULL)
{
if (info->StateList != NULL)
{
slabel = info->StateList->FindLabel (label);
if (slabel != NULL && slabel->valid)
{
return slabel->State;
}
}
info = info->Class->ParentClass->ActorInfo;
}
return NULL;
}
//===========================================================================
//
// HasStates
//
// Checks whether the actor has special death states.
//
//===========================================================================
bool AActor::HasSpecialDeathStates () const
{
const FActorInfo *info = GetClass()->ActorInfo;
FStateLabel *slabel;
TArray<FName> checkedTypes;
while (info != NULL)
{
if (info->StateList != NULL)
{
slabel = info->StateList->FindLabel (NAME_Death);
if (slabel != NULL && slabel->Children != NULL)
{
for(int i=0;i<slabel->Children->NumLabels;i++)
{
unsigned int j;
for(j=0;j<checkedTypes.Size();j++)
{
if (slabel->Children->Labels[i].Label == checkedTypes[j]) break;
}
// Only check if this damage type hasn't been checked by another class with higher priority.
if (j==checkedTypes.Size())
{
if (slabel->Children->Labels[i].State != NULL) return true;
else if (slabel->Children->Labels[i].valid) checkedTypes.Push(slabel->Children->Labels[i].Label);
}
}
}
}
info = info->Class->ParentClass->ActorInfo;
}
return false;
}
//===========================================================================
//
// FindState (multiple names version)
//
// Finds a state that matches as many of the supplied names as possible.
// A state with more names than those provided does not match.
// A state with fewer names can match if there are no states with the exact
// same number of names.
//
// The search proceeds like this. For the current class, keeping matching
// names until there are no more. If both the argument list and the state
// are out of names, it's an exact match, so return it. If the state still
// has names, ignore it. If the argument list still has names, remember it.
// Repeat with each successive ancestor class. The state with the longest
// match not exceeding the supplied number of names is returned.
//
//===========================================================================
FState *AActor::FindState (int numnames, ...) const
{
va_list arglist;
va_start (arglist, numnames);
return FindState (numnames, arglist);
}
FState *FActorInfo::FindState (int numnames, ...) const
{
va_list arglist;
va_start (arglist, numnames);
return FindState (numnames, arglist);
}
FState *AActor::FindState (int numnames, va_list arglist) const
{
return GetClass()->ActorInfo->FindState (numnames, arglist);
}
FState *FActorInfo::FindState (int numnames, va_list arglist) const
{
const FActorInfo *info = this;
FState *best = NULL;
int bestcount = 0;
// Search this actor's class, plus all its ancestors for a match.
while (info != NULL && bestcount < numnames)
{
FStateLabels *labels = info->StateList;
if (labels != NULL)
{
va_list names = arglist;
int count = 0;
FStateLabel *slabel = NULL;
FName label;
// Find the best-matching label for this class.
while (labels != NULL && count < numnames)
{
label = ENamedName(va_arg (names, int));
slabel = labels->FindLabel (label);
if (slabel != NULL)
{
count++;
labels = slabel->Children;
// Labels that are more specific than what we want do not match.
// Less specific labels do match.
if (slabel->valid && count > bestcount)
{
if (count == numnames)
{
return slabel->State;
}
bestcount = count;
best = slabel->State;
}
}
else
{
break;
}
}
}
// Walk up the class hierarchy and repeat.
info = info->Class->ParentClass->ActorInfo;
}
return best;
}
//===========================================================================
//
// FindStateExact
//
// This is like FindState, except it will only return states whose labels
// match the requested one exactly.
//
//===========================================================================
FState *FActorInfo::FindStateExact (int numnames, ...) const
{
va_list arglist;
va_start (arglist, numnames);
return FindStateExact (numnames, arglist);
}
FState *FActorInfo::FindStateExact (int numnames, va_list arglist) const
{
const FActorInfo *info = this;
// Search this actor's class, plus all its ancestors for a match.
while (info != NULL)
{
FStateLabels *labels = info->StateList;
if (labels != NULL)
{
va_list names = arglist;
int count = 0;
FStateLabel *slabel = NULL;
FName label;
// Look for a matching label for this class.
while (labels != NULL && count < numnames)
{
label = ENamedName(va_arg (names, int));
slabel = labels->FindLabel (label);
if (slabel != NULL)
{
count++;
labels = slabel->Children;
}
else
{
break;
}
}
// Only exact matches count.
if (slabel != NULL && slabel->valid && count == numnames)
{
return slabel->State;
}
}
// Walk up the class hierarchy and repeat.
info = info->Class->ParentClass->ActorInfo;
}
return NULL;
}
//===========================================================================
//
// Changes a single state
//
// If the given state does not exist it won't be changed
//
//===========================================================================
void FActorInfo::ChangeState (FName label, FState * newstate) const
{
FStateLabel *slabel;
if (StateList != NULL)
{
slabel = StateList->FindLabel (label);
if (slabel != NULL)
{
slabel->State = newstate;
}
}
}
//==========================================================================
//
//
//==========================================================================
FDoomEdMap DoomEdMap;
FDoomEdMap::FDoomEdEntry *FDoomEdMap::DoomEdHash[DOOMED_HASHSIZE];
- Fixed: The names in the Depths array in m_options.cpp were never freed. - Fixed: FDoomEdMap needed a destructor. - Fixed: Decal animators were never freed. - Fixed: Colormaps were never freed. - Fixed: Memory allocated in R_InitTranslationTables() was never freed. - Fixed: R_InitParticles() allocated way more memory than it needed to. (And the particle memory was never freed, either.) - Fixed: FMetaTable::FreeMeta() should use delete[] to free string metadata. - Fixed: FConfigFile::ClearCurrentSection() must cast the entry to a char * before deleting it, because that's the way it was allocated. - Fixed definitions of DeadZombieMan and DeadShotgunGuy in doom/deadthings.txt. Skip_super resets the dropitem list, so having it after "DropItem None" is pointless. - Fixed: Decorate DropItem information was never freed. - Fixed: FinishStates() allocated even 0-entry state arrays. - Fixed: Default actor instances were never freed. - Fixed: FRandomSoundList never freed its sound list. - Fixed: Level and cluster strings read from MAPINFO were never freed. - Fixed: Episode names were never freed. - Fixed: InverseColormap and GoldColormap were never freed. Since they're always allocated, they can just be arrays rather than pointers. - Fixed: FFont destructor never freed any of the character data or the font's name. - Fixed: Fonts were not freed at exit. - Fixed: FStringTable::LoadLanguage() did not call SC_Close(). - Fixed: When using the -iwad parameter, IdentifyVersion() did not release the buffer it created to hold the parameter's path. SVN r88 (trunk)
2006-05-09 03:40:15 +00:00
FDoomEdMap::~FDoomEdMap()
{
Empty();
}
void FDoomEdMap::AddType (int doomednum, const PClass *type)
{
unsigned int hash = (unsigned int)doomednum % DOOMED_HASHSIZE;
FDoomEdEntry *entry = DoomEdHash[hash];
while (entry && entry->DoomEdNum != doomednum)
{
entry = entry->HashNext;
}
if (entry == NULL)
{
entry = new FDoomEdEntry;
entry->HashNext = DoomEdHash[hash];
entry->DoomEdNum = doomednum;
DoomEdHash[hash] = entry;
}
else
{
Printf (PRINT_BOLD, "Warning: %s and %s both have doomednum %d.\n",
type->TypeName.GetChars(), entry->Type->TypeName.GetChars(), doomednum);
}
entry->Type = type;
}
void FDoomEdMap::DelType (int doomednum)
{
unsigned int hash = (unsigned int)doomednum % DOOMED_HASHSIZE;
FDoomEdEntry **prev = &DoomEdHash[hash];
FDoomEdEntry *entry = *prev;
while (entry && entry->DoomEdNum != doomednum)
{
prev = &entry->HashNext;
entry = entry->HashNext;
}
if (entry != NULL)
{
*prev = entry->HashNext;
delete entry;
}
}
void FDoomEdMap::Empty ()
{
int bucket;
for (bucket = 0; bucket < DOOMED_HASHSIZE; ++bucket)
{
FDoomEdEntry *probe = DoomEdHash[bucket];
while (probe != NULL)
{
FDoomEdEntry *next = probe->HashNext;
delete probe;
probe = next;
}
DoomEdHash[bucket] = NULL;
}
}
const PClass *FDoomEdMap::FindType (int doomednum) const
{
unsigned int hash = (unsigned int)doomednum % DOOMED_HASHSIZE;
FDoomEdEntry *entry = DoomEdHash[hash];
while (entry && entry->DoomEdNum != doomednum)
entry = entry->HashNext;
return entry ? entry->Type : NULL;
}
struct EdSorting
{
const PClass *Type;
int DoomEdNum;
};
static int STACK_ARGS sortnums (const void *a, const void *b)
{
return ((const EdSorting *)a)->DoomEdNum -
((const EdSorting *)b)->DoomEdNum;
}
void FDoomEdMap::DumpMapThings ()
{
TArray<EdSorting> infos (PClass::m_Types.Size());
int i;
for (i = 0; i < DOOMED_HASHSIZE; ++i)
{
FDoomEdEntry *probe = DoomEdHash[i];
while (probe != NULL)
{
EdSorting sorting = { probe->Type, probe->DoomEdNum };
infos.Push (sorting);
probe = probe->HashNext;
}
}
if (infos.Size () == 0)
{
Printf ("No map things registered\n");
}
else
{
qsort (&infos[0], infos.Size (), sizeof(EdSorting), sortnums);
for (i = 0; i < (int)infos.Size (); ++i)
{
Printf ("%6d %s\n",
infos[i].DoomEdNum, infos[i].Type->TypeName.GetChars());
}
}
}
CCMD (dumpmapthings)
{
FDoomEdMap::DumpMapThings ();
}
bool CheckCheatmode ();
CCMD (summon)
{
if (CheckCheatmode ())
return;
if (argv.argc() > 1)
{
const PClass *type = PClass::FindClass (argv[1]);
if (type == NULL)
{
Printf ("Unknown class '%s'\n", argv[1]);
return;
}
Net_WriteByte (DEM_SUMMON);
Net_WriteString (type->TypeName.GetChars());
}
}
CCMD (summonfriend)
{
if (CheckCheatmode ())
return;
if (argv.argc() > 1)
{
const PClass *type = PClass::FindClass (argv[1]);
if (type == NULL)
{
Printf ("Unknown class '%s'\n", argv[1]);
return;
}
Net_WriteByte (DEM_SUMMONFRIEND);
Net_WriteString (type->TypeName.GetChars());
}
}