qzdoom-gpl/src/info.cpp
Randy Heit 955d929d5e - Ensure that only actor with positive SpawnIDs get added to the SpawnableThings map, so there will
be no conflicts with recently-added named arguments for spawnable things on UDMF maps.
- Change the SpawnableThings array into a map, so there is no longer any particular upper limit on an actor's SpawnID. Also fixes a possible exploit, since an actor's SpawnID was never checked to make sure it was within range.

SVN r3959 (trunk)
2012-11-09 23:53:58 +00:00

657 lines
18 KiB
C++

/*
** 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.
**
*/
#include "info.h"
#include "m_fixed.h"
#include "c_dispatch.h"
#include "d_net.h"
#include "v_text.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"
#include "g_level.h"
extern void LoadActors ();
extern void InitBotStuff();
extern void ClearStrifeTypes();
FRandom FState::pr_statetics;
//==========================================================================
//
//
//==========================================================================
int GetSpriteIndex(const char * spritename, bool add)
{
static char lastsprite[5];
static int lastindex;
// Make sure that the string is upper case and 4 characters long
char upper[5]={0,0,0,0,0};
for (int i = 0; spritename[i] != 0 && i < 4; i++)
{
upper[i] = toupper (spritename[i]);
}
// cache the name so if the next one is the same the function doesn't have to perform a search.
if (!strcmp(upper, lastsprite))
{
return lastindex;
}
strcpy(lastsprite, upper);
for (unsigned i = 0; i < sprites.Size (); ++i)
{
if (strcmp (sprites[i].name, upper) == 0)
{
return (lastindex = (int)i);
}
}
if (!add)
{
return (lastindex = -1);
}
spritedef_t temp;
strcpy (temp.name, upper);
temp.numframes = 0;
temp.spriteframes = 0;
return (lastindex = (int)sprites.Push (temp));
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::StaticInit ()
{
sprites.Clear();
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);
// Sprite 2 is always ####
memcpy (temp.name, "####", 5);
sprites.Push (temp);
}
Printf ("LoadActors: Load actor definitions.\n");
ClearStrifeTypes();
LoadActors ();
InitBotStuff();
}
//==========================================================================
//
// Called after Dehacked patches are applied
//
//==========================================================================
void FActorInfo::StaticSetActorNums ()
{
SpawnableThings.Clear();
DoomEdMap.Empty ();
for (unsigned int i = 0; i < PClass::m_RuntimeActors.Size(); ++i)
{
PClass::m_RuntimeActors[i]->ActorInfo->RegisterIDs ();
}
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::RegisterIDs ()
{
const PClass *cls = PClass::FindClass(Class->TypeName);
bool set = false;
if (GameFilter == GAME_Any || (GameFilter & gameinfo.gametype))
{
if (SpawnID > 0)
{
SpawnableThings[SpawnID] = cls;
if (cls != Class)
{
Printf(TEXTCOLOR_RED"Spawn ID %d refers to hidden class type '%s'\n", SpawnID, cls->TypeName.GetChars());
}
}
if (DoomEdNum != -1)
{
DoomEdMap.AddType (DoomEdNum, cls);
if (cls != Class)
{
Printf(TEXTCOLOR_RED"Editor number %d refers to hidden class type '%s'\n", DoomEdNum, cls->TypeName.GetChars());
}
}
}
// Fill out the list for Chex Quest with Doom's actors
if (gameinfo.gametype == GAME_Chex && DoomEdMap.FindType(DoomEdNum) == NULL &&
(GameFilter & GAME_Doom))
{
DoomEdMap.AddType (DoomEdNum, Class, true);
if (cls != Class)
{
Printf(TEXTCOLOR_RED"Editor number %d refers to hidden class type '%s'\n", DoomEdNum, cls->TypeName.GetChars());
}
}
}
//==========================================================================
//
//
//==========================================================================
FActorInfo *FActorInfo::GetReplacement (bool lookskill)
{
FName skillrepname;
if (lookskill && AllSkills.Size() > (unsigned)gameskill)
{
skillrepname = AllSkills[gameskill].GetReplacement(this->Class->TypeName);
if (skillrepname != NAME_None && PClass::FindClass(skillrepname) == NULL)
{
Printf("Warning: incorrect actor name in definition of skill %s: \n"
"class %s is replaced by non-existent class %s\n"
"Skill replacement will be ignored for this actor.\n",
AllSkills[gameskill].Name.GetChars(),
this->Class->TypeName.GetChars(), skillrepname.GetChars());
AllSkills[gameskill].SetReplacement(this->Class->TypeName, NAME_None);
AllSkills[gameskill].SetReplacedBy(skillrepname, NAME_None);
lookskill = false; skillrepname = NAME_None;
}
}
if (Replacement == NULL && (!lookskill || skillrepname == NAME_None))
{
return this;
}
// The Replacement field is temporarily NULLed to prevent
// potential infinite recursion.
FActorInfo *savedrep = Replacement;
Replacement = NULL;
FActorInfo *rep = savedrep;
// Handle skill-based replacement here. It has precedence on DECORATE replacement
// in that the skill replacement is applied first, followed by DECORATE replacement
// on the actor indicated by the skill replacement.
if (lookskill && (skillrepname != NAME_None))
{
rep = PClass::FindClass(skillrepname)->ActorInfo;
}
// Now handle DECORATE replacement chain
// Skill replacements are not recursive, contrarily to DECORATE replacements
rep = rep->GetReplacement(false);
// Reset the temporarily NULLed field
Replacement = savedrep;
return rep;
}
//==========================================================================
//
//
//==========================================================================
FActorInfo *FActorInfo::GetReplacee (bool lookskill)
{
FName skillrepname;
if (lookskill && AllSkills.Size() > (unsigned)gameskill)
{
skillrepname = AllSkills[gameskill].GetReplacedBy(this->Class->TypeName);
if (skillrepname != NAME_None && PClass::FindClass(skillrepname) == NULL)
{
Printf("Warning: incorrect actor name in definition of skill %s: \n"
"non-existent class %s is replaced by class %s\n"
"Skill replacement will be ignored for this actor.\n",
AllSkills[gameskill].Name.GetChars(),
skillrepname.GetChars(), this->Class->TypeName.GetChars());
AllSkills[gameskill].SetReplacedBy(this->Class->TypeName, NAME_None);
AllSkills[gameskill].SetReplacement(skillrepname, NAME_None);
lookskill = false;
}
}
if (Replacee == NULL && (!lookskill || skillrepname == NAME_None))
{
return this;
}
// The Replacee field is temporarily NULLed to prevent
// potential infinite recursion.
FActorInfo *savedrep = Replacee;
Replacee = NULL;
FActorInfo *rep = savedrep;
if (lookskill && (skillrepname != NAME_None) && (PClass::FindClass(skillrepname) != NULL))
{
rep = PClass::FindClass(skillrepname)->ActorInfo;
}
rep = rep->GetReplacee (false); Replacee = savedrep;
return rep;
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::SetDamageFactor(FName type, fixed_t factor)
{
if (DamageFactors == NULL)
{
DamageFactors = new DmgFactors;
}
DamageFactors->Insert(type, factor);
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::SetPainChance(FName type, int chance)
{
if (chance >= 0)
{
if (PainChances == NULL) PainChances=new PainChanceList;
PainChances->Insert(type, MIN(chance, 256));
}
else
{
if (PainChances != NULL)
PainChances->Remove(type);
}
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::SetPainFlash(FName type, PalEntry color)
{
if (PainFlashes == NULL)
PainFlashes = new PainFlashList;
PainFlashes->Insert(type, color);
}
//==========================================================================
//
//
//==========================================================================
bool FActorInfo::GetPainFlash(FName type, PalEntry *color) const
{
const FActorInfo *info = this;
while (info != NULL)
{
if (info->PainFlashes != NULL)
{
PalEntry *flash = info->PainFlashes->CheckKey(type);
if (flash != NULL)
{
*color = *flash;
return true;
}
}
// Try parent class
info = info->Class->ParentClass->ActorInfo;
}
return false;
}
//==========================================================================
//
//
//==========================================================================
void FActorInfo::SetColorSet(int index, const FPlayerColorSet *set)
{
if (set != NULL)
{
if (ColorSets == NULL) ColorSets = new FPlayerColorSetMap;
ColorSets->Insert(index, *set);
}
else
{
if (ColorSets != NULL)
ColorSets->Remove(index);
}
}
//==========================================================================
//
// DmgFactors :: CheckFactor
//
// Checks for the existance of a certain damage type. If that type does not
// exist, the damage factor for type 'None' will be returned, if present.
//
//==========================================================================
fixed_t *DmgFactors::CheckFactor(FName type)
{
fixed_t *pdf = CheckKey(type);
if (pdf == NULL && type != NAME_None)
{
pdf = CheckKey(NAME_None);
}
return pdf;
}
//==========================================================================
//
//
//==========================================================================
FDoomEdMap DoomEdMap;
FDoomEdMap::FDoomEdEntry *FDoomEdMap::DoomEdHash[DOOMED_HASHSIZE];
FDoomEdMap::~FDoomEdMap()
{
Empty();
}
void FDoomEdMap::AddType (int doomednum, const PClass *type, bool temporary)
{
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 if (!entry->temp)
{
Printf (PRINT_BOLD, "Warning: %s and %s both have doomednum %d.\n",
type->TypeName.GetChars(), entry->Type->TypeName.GetChars(), doomednum);
}
entry->temp = temporary;
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 ();
}
static void SummonActor (int command, int command2, FCommandLine argv)
{
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 (argv.argc() > 2 ? command2 : command);
Net_WriteString (type->TypeName.GetChars());
if (argv.argc () > 2) {
Net_WriteWord (atoi (argv[2])); // angle
if (argv.argc () > 3) Net_WriteWord (atoi (argv[3])); // TID
else Net_WriteWord (0);
if (argv.argc () > 4) Net_WriteByte (atoi (argv[4])); // special
else Net_WriteByte (0);
for(int i = 5; i < 10; i++) { // args[5]
if(i < argv.argc()) Net_WriteLong (atoi (argv[i]));
else Net_WriteLong (0);
}
}
}
}
CCMD (summon)
{
SummonActor (DEM_SUMMON, DEM_SUMMON2, argv);
}
CCMD (summonfriend)
{
SummonActor (DEM_SUMMONFRIEND, DEM_SUMMONFRIEND2, argv);
}
CCMD (summonmbf)
{
SummonActor (DEM_SUMMONMBF, DEM_SUMMONFRIEND2, argv);
}
CCMD (summonfoe)
{
SummonActor (DEM_SUMMONFOE, DEM_SUMMONFOE2, argv);
}
// Damage type defaults / global settings
TMap<FName, DamageTypeDefinition> GlobalDamageDefinitions;
void DamageTypeDefinition::Apply(FName const type)
{
GlobalDamageDefinitions[type] = *this;
}
DamageTypeDefinition *DamageTypeDefinition::Get(FName const type)
{
return GlobalDamageDefinitions.CheckKey(type);
}
bool DamageTypeDefinition::IgnoreArmor(FName const type)
{
DamageTypeDefinition *dtd = Get(type);
if (dtd) return dtd->NoArmor;
return false;
}
//==========================================================================
//
// DamageTypeDefinition :: ApplyMobjDamageFactor
//
// Calculates mobj damage based on original damage, defined damage factors
// and damage type.
//
// If the specific damage type is not defined, the damage factor for
// type 'None' will be used (with 1.0 as a default value).
//
// Globally declared damage types may override or multiply the damage
// factor when 'None' is used as a fallback in this function.
//
//==========================================================================
int DamageTypeDefinition::ApplyMobjDamageFactor(int damage, FName const type, DmgFactors const * const factors)
{
if (factors)
{
// If the actor has named damage factors, look for a specific factor
fixed_t const *pdf = factors->CheckKey(type);
if (pdf) return FixedMul(damage, *pdf); // type specific damage type
// If this was nonspecific damage, don't fall back to nonspecific search
if (type == NAME_None) return damage;
}
// If this was nonspecific damage, don't fall back to nonspecific search
else if (type == NAME_None)
{
return damage;
}
else
{
// Normal is unsupplied / 1.0, so there's no difference between modifying and overriding
DamageTypeDefinition *dtd = Get(type);
return dtd ? FixedMul(damage, dtd->DefaultFactor) : damage;
}
{
fixed_t const *pdf = factors->CheckKey(NAME_None);
DamageTypeDefinition *dtd = Get(type);
// Here we are looking for modifications to untyped damage
// If the calling actor defines untyped damage factor, that is contained in "pdf".
if (pdf) // normal damage available
{
if (dtd)
{
if (dtd->ReplaceFactor) return FixedMul(damage, dtd->DefaultFactor); // use default instead of untyped factor
return FixedMul(damage, FixedMul(*pdf, dtd->DefaultFactor)); // use default as modification of untyped factor
}
return FixedMul(damage, *pdf); // there was no default, so actor default is used
}
else if (dtd)
{
return FixedMul(damage, dtd->DefaultFactor); // implicit untyped factor 1.0 does not need to be applied/replaced explicitly
}
}
return damage;
}