mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-12 15:44:10 +00:00
406 lines
9 KiB
C++
406 lines
9 KiB
C++
|
/*
|
||
|
** dthinker.cpp
|
||
|
** Implements the base class for almost anything in a level that might think
|
||
|
**
|
||
|
**---------------------------------------------------------------------------
|
||
|
** Copyright 1998-2005 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.
|
||
|
**---------------------------------------------------------------------------
|
||
|
**
|
||
|
*/
|
||
|
|
||
|
#include "dthinker.h"
|
||
|
#include "stats.h"
|
||
|
#include "p_local.h"
|
||
|
#include "statnums.h"
|
||
|
#include "i_system.h"
|
||
|
#include "doomerrors.h"
|
||
|
|
||
|
|
||
|
static cycle_t ThinkCycles;
|
||
|
extern cycle_t BotSupportCycles;
|
||
|
extern cycle_t BotWTG;
|
||
|
|
||
|
IMPLEMENT_CLASS (DThinker)
|
||
|
|
||
|
static Node *NextToThink;
|
||
|
|
||
|
List DThinker::Thinkers[MAX_STATNUM+1];
|
||
|
List DThinker::FreshThinkers[MAX_STATNUM+1];
|
||
|
bool DThinker::bSerialOverride = false;
|
||
|
|
||
|
void DThinker::SerializeAll (FArchive &arc, bool hubLoad)
|
||
|
{
|
||
|
Node *node;
|
||
|
DThinker *thinker;
|
||
|
BYTE stat;
|
||
|
int statcount;
|
||
|
int i;
|
||
|
|
||
|
// Save lists of thinkers, but not by storing the first one and letting
|
||
|
// the archiver catch the rest. (Which leads to buttloads of recursion
|
||
|
// and makes the file larger.) Instead, we explicitly save each thinker
|
||
|
// in sequence. When restoring an archive, we also have to maintain
|
||
|
// the thinker lists here instead of relying on the archiver to do it
|
||
|
// for us.
|
||
|
|
||
|
if (arc.IsStoring ())
|
||
|
{
|
||
|
for (statcount = i = 0; i <= MAX_STATNUM; i++)
|
||
|
{
|
||
|
if (!Thinkers[i].IsEmpty ())
|
||
|
{
|
||
|
statcount++;
|
||
|
}
|
||
|
}
|
||
|
arc << statcount;
|
||
|
for (i = 0; i <= MAX_STATNUM; i++)
|
||
|
{
|
||
|
node = Thinkers[i].Head;
|
||
|
if (node->Succ != NULL)
|
||
|
{
|
||
|
stat = i;
|
||
|
arc << stat;
|
||
|
do
|
||
|
{
|
||
|
thinker = static_cast<DThinker *> (node);
|
||
|
arc << thinker;
|
||
|
node = node->Succ;
|
||
|
} while (node->Succ != NULL);
|
||
|
thinker = NULL;
|
||
|
arc << thinker; // Save a final NULL for this list
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (hubLoad)
|
||
|
DestroyMostThinkers ();
|
||
|
else
|
||
|
DestroyAllThinkers ();
|
||
|
|
||
|
// Prevent the constructor from inserting thinkers into a list.
|
||
|
bSerialOverride = true;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
arc << statcount;
|
||
|
while (statcount > 0)
|
||
|
{
|
||
|
arc << stat << thinker;
|
||
|
while (thinker != NULL)
|
||
|
{
|
||
|
Thinkers[stat].AddTail (thinker);
|
||
|
arc << thinker;
|
||
|
}
|
||
|
statcount--;
|
||
|
}
|
||
|
}
|
||
|
catch (class CDoomError &)
|
||
|
{
|
||
|
bSerialOverride = false;
|
||
|
DestroyAllThinkers ();
|
||
|
throw;
|
||
|
}
|
||
|
bSerialOverride = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DThinker::DThinker (int statnum) throw()
|
||
|
{
|
||
|
if (bSerialOverride)
|
||
|
{ // The serializer will insert us into the right list
|
||
|
Succ = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ObjectFlags |= OF_JustSpawned;
|
||
|
if ((unsigned)statnum > MAX_STATNUM)
|
||
|
{
|
||
|
statnum = MAX_STATNUM;
|
||
|
}
|
||
|
FreshThinkers[statnum].AddTail (this);
|
||
|
}
|
||
|
|
||
|
DThinker::~DThinker ()
|
||
|
{
|
||
|
if (Succ != NULL)
|
||
|
{
|
||
|
Remove ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DThinker::Destroy ()
|
||
|
{
|
||
|
if (this == NextToThink)
|
||
|
NextToThink = Succ;
|
||
|
|
||
|
if (Succ != NULL)
|
||
|
{
|
||
|
Remove ();
|
||
|
Succ = NULL;
|
||
|
}
|
||
|
Super::Destroy ();
|
||
|
}
|
||
|
|
||
|
void DThinker::PostBeginPlay ()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void DThinker::ChangeStatNum (int statnum)
|
||
|
{
|
||
|
if ((unsigned)statnum > MAX_STATNUM)
|
||
|
{
|
||
|
statnum = MAX_STATNUM;
|
||
|
}
|
||
|
Remove ();
|
||
|
if ((ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING)
|
||
|
{
|
||
|
FreshThinkers[statnum].AddTail (this);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Thinkers[statnum].AddTail (this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Destroy every thinker
|
||
|
void DThinker::DestroyAllThinkers ()
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
DObject::BeginFrame ();
|
||
|
for (i = 0; i <= MAX_STATNUM; i++)
|
||
|
{
|
||
|
if (i != STAT_TRAVELLING)
|
||
|
{
|
||
|
DestroyThinkersInList (Thinkers[i].Head);
|
||
|
DestroyThinkersInList (FreshThinkers[i].Head);
|
||
|
}
|
||
|
}
|
||
|
DObject::EndFrame ();
|
||
|
}
|
||
|
|
||
|
// Destroy all thinkers except for player-controlled actors
|
||
|
// Players are simply removed from the list of thinkers and
|
||
|
// will be added back after serialization is complete.
|
||
|
void DThinker::DestroyMostThinkers ()
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
DObject::BeginFrame ();
|
||
|
for (i = 0; i <= MAX_STATNUM; i++)
|
||
|
{
|
||
|
if (i != STAT_TRAVELLING)
|
||
|
{
|
||
|
DestroyMostThinkersInList (Thinkers[i], i);
|
||
|
DestroyMostThinkersInList (FreshThinkers[i], i);
|
||
|
}
|
||
|
}
|
||
|
DObject::EndFrame ();
|
||
|
}
|
||
|
|
||
|
void DThinker::DestroyThinkersInList (Node *node)
|
||
|
{
|
||
|
while (node->Succ != NULL)
|
||
|
{
|
||
|
Node *next = node->Succ;
|
||
|
static_cast<DThinker *> (node)->Destroy ();
|
||
|
node = next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DThinker::DestroyMostThinkersInList (List &list, int stat)
|
||
|
{
|
||
|
if (stat != STAT_PLAYER)
|
||
|
{
|
||
|
DestroyThinkersInList (list.Head);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Node *node = list.Head;
|
||
|
while (node->Succ != NULL)
|
||
|
{
|
||
|
Node *next = node->Succ;
|
||
|
node->Remove ();
|
||
|
node = next;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DThinker::RunThinkers ()
|
||
|
{
|
||
|
int i, count;
|
||
|
|
||
|
ThinkCycles = BotSupportCycles = BotWTG = 0;
|
||
|
|
||
|
clock (ThinkCycles);
|
||
|
|
||
|
// Tick every thinker left from last time
|
||
|
for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i)
|
||
|
{
|
||
|
TickThinkers (&Thinkers[i], NULL);
|
||
|
}
|
||
|
|
||
|
// Keep ticking the fresh thinkers until there are no new ones.
|
||
|
do
|
||
|
{
|
||
|
count = 0;
|
||
|
for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i)
|
||
|
{
|
||
|
count += TickThinkers (&FreshThinkers[i], &Thinkers[i]);
|
||
|
}
|
||
|
} while (count != 0);
|
||
|
|
||
|
unclock (ThinkCycles);
|
||
|
}
|
||
|
|
||
|
int DThinker::TickThinkers (List *list, List *dest)
|
||
|
{
|
||
|
int count = 0;
|
||
|
Node *node = list->Head;
|
||
|
|
||
|
while (node->Succ != NULL)
|
||
|
{
|
||
|
++count;
|
||
|
NextToThink = node->Succ;
|
||
|
DThinker *thinker = static_cast<DThinker *> (node);
|
||
|
if (thinker->ObjectFlags & OF_JustSpawned)
|
||
|
{
|
||
|
thinker->ObjectFlags &= ~OF_JustSpawned;
|
||
|
if (dest != NULL)
|
||
|
{ // Move thinker from this list to the destination list
|
||
|
node->Remove ();
|
||
|
dest->AddTail (node);
|
||
|
}
|
||
|
thinker->PostBeginPlay ();
|
||
|
}
|
||
|
else if (dest != NULL)
|
||
|
{ // Move thinker from this list to the destination list
|
||
|
I_Error ("There is a thinker in the fresh list that has already ticked.\n");
|
||
|
}
|
||
|
|
||
|
if (!(thinker->ObjectFlags & OF_MassDestruction))
|
||
|
{ // Only tick thinkers not scheduled for destruction
|
||
|
thinker->Tick ();
|
||
|
}
|
||
|
node = NextToThink;
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
void DThinker::Tick ()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
FThinkerIterator::FThinkerIterator (const TypeInfo *type, int statnum)
|
||
|
{
|
||
|
if ((unsigned)statnum > MAX_STATNUM)
|
||
|
{
|
||
|
m_Stat = STAT_FIRST_THINKING;
|
||
|
m_SearchStats = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Stat = statnum;
|
||
|
m_SearchStats = false;
|
||
|
}
|
||
|
m_ParentType = type;
|
||
|
m_CurrThinker = DThinker::Thinkers[m_Stat].Head;
|
||
|
m_SearchingFresh = false;
|
||
|
}
|
||
|
|
||
|
FThinkerIterator::FThinkerIterator (const TypeInfo *type, int statnum, DThinker *prev)
|
||
|
{
|
||
|
if ((unsigned)statnum > MAX_STATNUM)
|
||
|
{
|
||
|
m_Stat = STAT_FIRST_THINKING;
|
||
|
m_SearchStats = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Stat = statnum;
|
||
|
m_SearchStats = false;
|
||
|
}
|
||
|
m_ParentType = type;
|
||
|
if (prev == NULL || prev->Succ == NULL)
|
||
|
{
|
||
|
Reinit ();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_CurrThinker = prev->Succ;
|
||
|
m_SearchingFresh = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FThinkerIterator::Reinit ()
|
||
|
{
|
||
|
m_CurrThinker = DThinker::Thinkers[m_Stat].Head;
|
||
|
m_SearchingFresh = false;
|
||
|
}
|
||
|
|
||
|
DThinker *FThinkerIterator::Next ()
|
||
|
{
|
||
|
do
|
||
|
{
|
||
|
do
|
||
|
{
|
||
|
while (m_CurrThinker->Succ)
|
||
|
{
|
||
|
DThinker *thinker = static_cast<DThinker *> (m_CurrThinker);
|
||
|
if (thinker->IsKindOf (m_ParentType))
|
||
|
{
|
||
|
m_CurrThinker = m_CurrThinker->Succ;
|
||
|
return thinker;
|
||
|
}
|
||
|
m_CurrThinker = m_CurrThinker->Succ;
|
||
|
}
|
||
|
if ((m_SearchingFresh = !m_SearchingFresh))
|
||
|
{
|
||
|
m_CurrThinker = DThinker::FreshThinkers[m_Stat].Head;
|
||
|
}
|
||
|
} while (m_SearchingFresh);
|
||
|
if (m_SearchStats)
|
||
|
{
|
||
|
m_Stat++;
|
||
|
if (m_Stat > MAX_STATNUM)
|
||
|
{
|
||
|
m_Stat = STAT_FIRST_THINKING;
|
||
|
}
|
||
|
}
|
||
|
m_CurrThinker = DThinker::Thinkers[m_Stat].Head;
|
||
|
m_SearchingFresh = false;
|
||
|
} while (m_SearchStats && m_Stat != STAT_FIRST_THINKING);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
ADD_STAT (think, out)
|
||
|
{
|
||
|
sprintf (out, "Think time = %04.1f ms",
|
||
|
SecondsPerCycle * (double)ThinkCycles * 1000);
|
||
|
}
|