gzdoom/src/info.cpp

815 lines
20 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"
#include "cmdlib.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 ();
}
Note: I have not tried compiling these recent changes under Linux. I wouldn't be surprised if it doesn't work. - Reorganized the network startup loops so now they are event driven. There is a single function that gets called to drive it, and it uses callbacks to perform the different stages of the synchronization. This lets me have a nice, responsive abort button instead of the previous unannounced hit-escape-to- abort behavior, and I think the rearranged code is slightly easier to understand too. - Increased the number of bytes for version info during D_ArbitrateNetStart(), in preparation for the day when NETGAMEVERSION requires more than one byte. - I noticed an issue with Vista RC1 and the new fatal error setup. Even after releasing a DirectDraw or Direct3D interface, the DWM can still use the last image drawn using them when it composites the window. It doesn't always do it but it does often enough that it is a real problem. At this point, I don't know if it's a problem with the release version of Vista or not. After messing around, I discovered the problem was caused by ~Win32Video() hiding the window and then having it immediately shown soon after. The DWM kept an image of the window to do the transition effect with, and then when it didn't get a chance to do the transition, it didn't properly forget about its saved image and kept plastering it on top of everything else underneath. - Added a network synchronization panel to the window during netgame startup. - Fixed: PClass::CreateDerivedClass() must initialize StateList to NULL. Otherwise, classic DECORATE definitions generate a big, fat crash. - Resurrected the R_Init progress bar, now as a standard Windows control. - Removed the sound failure dialog. The FMOD setup already defaulted to no sound if initialization failed, so this only applies when snd_output is set to "alternate" which now also falls back to no sound. In addition, it wasn't working right, and I didn't feel like fixing it for the probably 0% of users it affected. - Fixed: The edit control used for logging output added text in reverse order on Win9x. - Went back to the roots and made graphics initialization one of the last things to happen during setup. Now the startup text is visible again. More importantly, the main window is no longer created invisible, which seems to cause trouble with it not always appearing in the taskbar. The fatal error dialog is now also embedded in the main window instead of being a separate modal dialog, so you can play with the log window to see any problems that might be reported there. Rather than completely restoring the original startup order, I tried to keep things as close to the way they were with early graphics startup. In particular, V_Init() now creates a dummy screen so that things that need screen dimensions can get them. It gets replaced by the real screen later in I_InitGraphics(). Will need to check this under Linux to make sure it didn't cause any problems there. - Removed the following stubs that just called functions in Video: - I_StartModeIterator() - I_NextMode() - I_DisplayType() I_FullscreenChanged() was also removed, and a new fullscreen parameter was added to IVideo::StartModeIterator(), since that's all it controlled. - Renamed I_InitHardware() back to I_InitGraphics(), since that's all it's initialized post-1.22. SVN r416 (trunk)
2006-12-19 04:09:10 +00:00
Printf ("LoadDecorations: Load external actors.\n");
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;
}
}
}
//===========================================================================
//
// HasStates
//
// Checks whether the actor has special death states.
//
//===========================================================================
bool AActor::HasSpecialDeathStates () const
{
const FActorInfo *info = GetClass()->ActorInfo;
if (info->StateList != NULL)
{
FStateLabel *slabel = info->StateList->FindLabel (NAME_Death);
if (slabel != NULL && slabel->Children != NULL)
{
for(int i=0;i<slabel->Children->NumLabels;i++)
{
if (slabel->Children->Labels[i].State != NULL) return true;
}
}
}
return false;
}
//===========================================================================
//
// FindState (one name version)
//
// Finds a state with the exact specified name.
//
//===========================================================================
FState *AActor::FindState (FName label) const
{
const FActorInfo *info = GetClass()->ActorInfo;
if (info->StateList != NULL)
{
FStateLabel *slabel = info->StateList->FindLabel (label);
if (slabel != NULL)
{
return slabel->State;
}
}
return NULL;
}
//===========================================================================
//
// FindState (two name version)
//
//===========================================================================
FState *AActor::FindState (FName label, FName sublabel, bool exact) const
{
const FActorInfo *info = GetClass()->ActorInfo;
if (info->StateList != NULL)
{
FStateLabel *slabel = info->StateList->FindLabel (label);
if (slabel != NULL)
{
if (slabel->Children != NULL)
{
FStateLabel *slabel2 = slabel->Children->FindLabel(sublabel);
if (slabel2 != NULL)
{
return slabel2->State;
}
}
if (!exact) return slabel->State;
}
}
return NULL;
}
//===========================================================================
//
// 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.
//
//===========================================================================
FState *FActorInfo::FindState (FName name) const
{
return FindState(1, &name);
}
FState *FActorInfo::FindState (int numnames, FName *names, bool exact) const
{
FStateLabels *labels = StateList;
FState *best = NULL;
if (labels != NULL)
{
int count = 0;
FStateLabel *slabel = NULL;
FName label;
// Find the best-matching label for this class.
while (labels != NULL && count < numnames)
{
label = *names++;
slabel = labels->FindLabel (label);
if (slabel != NULL)
{
count++;
labels = slabel->Children;
best = slabel->State;
}
else
{
break;
}
}
if (count < numnames && exact) return NULL;
}
return best;
}
//===========================================================================
//
// Changes a single state
//
// If the given state does not exist it won't be changed
// This is only used for postprocessing of actors that use different
// spawn states for different games so more complex checks are not needed.
//
//===========================================================================
void FActorInfo::ChangeState (FName label, FState * newstate) const
{
FStateLabel *slabel;
if (StateList != NULL)
{
slabel = StateList->FindLabel (label);
if (slabel != NULL)
{
slabel->State = newstate;
}
}
}
//==========================================================================
//
// Creates a list of names from a string. Dots are used as separator
//
//==========================================================================
void MakeStateNameList(const char * fname, TArray<FName> * out)
{
FName firstpart, secondpart;
char * c;
// Handle the old names for the existing death states
char * name = copystring(fname);
firstpart = strtok(name, ".");
switch (firstpart)
{
case NAME_Burn:
firstpart = NAME_Death;
secondpart = NAME_Fire;
break;
case NAME_Ice:
firstpart = NAME_Death;
secondpart = NAME_Ice;
break;
case NAME_Disintegrate:
firstpart = NAME_Death;
secondpart = NAME_Disintegrate;
break;
case NAME_XDeath:
firstpart = NAME_Death;
secondpart = NAME_Extreme;
break;
}
out->Clear();
out->Push(firstpart);
if (secondpart!=NAME_None) out->Push(secondpart);
while ((c = strtok(NULL, "."))!=NULL)
{
FName cc = c;
out->Push(cc);
}
delete [] name;
}
//==========================================================================
//
//
//==========================================================================
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());
}
}
CCMD (summonfoe)
{
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_SUMMONFOE);
Net_WriteString (type->TypeName.GetChars());
}
}