raze/source/core/actorlist.cpp
Christoph Oelckers 64d8fd6072 Blood: scriptified item spawning
This was merely a testbed with a limited amount of code, the biggest gain here is that the spawning no longer depends on item IDs but classes so new ones can be added later.
2023-10-08 09:41:11 +02:00

549 lines
15 KiB
C++

/*
** actorlist.cpp
** Implements the linked stat/sector actor lists
**
**---------------------------------------------------------------------------
** Copyright 2021 Christoph Oelckers
** 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 "build.h"
#include "coreactor.h"
#include "gamefuncs.h"
#include "raze_sound.h"
#include "vm.h"
#include "texturemanager.h"
#include "buildtiles.h"
// Doubly linked ring list of Actors
ActorStatList statList[MAXSTATUS];
//==========================================================================
//
//
//
//==========================================================================
CVAR(Bool, safe_spritelist, false, CVAR_ARCHIVE)
static bool isSafe()
{
return isBlood() || safe_spritelist;
}
//==========================================================================
//
//
//
//==========================================================================
TArray<DCoreActor*> checked;
static bool ValidateStatList(int statnum)
{
#if 0
checked.Clear();
for (auto entry = statList[statnum].firstEntry; entry; entry = entry->nextStat)
{
assert(!checked.Contains(entry));
checked.Push(entry);
assert(entry->prevStat != nullptr || statList[statnum].firstEntry == entry);
assert(entry->nextStat != nullptr || statList[statnum].lastEntry == entry);
assert(entry->prevStat == nullptr || entry->prevStat->nextStat == entry);
assert(entry->nextStat == nullptr || entry->nextStat->prevStat == entry);
}
#endif
return true;
}
//==========================================================================
//
//
//
//==========================================================================
static void AddStatTail(DCoreActor* actor, int statnum)
{
assert(actor->prevStat == nullptr && actor->nextStat == nullptr);
auto tail = statList[statnum].lastEntry;
assert(tail == nullptr || tail->nextStat == nullptr);
assert(ValidateStatList(statnum));
actor->prevStat = tail;
if (tail) tail->nextStat = actor;
else statList[statnum].firstEntry = actor;
statList[statnum].lastEntry = actor;
assert(ValidateStatList(statnum));
actor->spr.statnum = statnum;
actor->link_stat = statnum;
GC::WriteBarrier(actor);
GC::WriteBarrier(tail);
}
//==========================================================================
//
//
//
//==========================================================================
static void AddStatHead(DCoreActor* actor, int statnum)
{
assert(actor->prevStat == nullptr && actor->nextStat == nullptr);
auto head = statList[statnum].firstEntry;
assert(head == nullptr || head->prevStat == nullptr);
assert(ValidateStatList(statnum));
actor->nextStat = head;
if (head) head->prevStat = actor;
else statList[statnum].lastEntry = actor;
assert(ValidateStatList(statnum));
statList[statnum].firstEntry = actor;
actor->spr.statnum = statnum;
actor->link_stat = statnum;
GC::WriteBarrier(actor);
GC::WriteBarrier(head);
}
//==========================================================================
//
//
//
//==========================================================================
static void RemoveActorStat(DCoreActor* actor)
{
DCoreActor* prev = actor->prevStat;
DCoreActor* next = actor->nextStat;
auto& firstEntry = statList[actor->link_stat].firstEntry;
auto& lastEntry = statList[actor->link_stat].lastEntry;
auto prevp = prev ? &prev->nextStat : &firstEntry;
auto nextp = next ? &next->prevStat : &lastEntry;
if (*prevp == nullptr && *nextp == nullptr) return; // can happen during an aborted savegame load.
assert(*prevp == actor);
assert(*nextp == actor);
assert(ValidateStatList(actor->link_stat));
*prevp = next;
*nextp = prev;
assert(ValidateStatList(actor->link_stat));
actor->nextStat = actor->prevStat = nullptr;
actor->spr.statnum = MAXSTATUS;
actor->link_stat = MAXSTATUS;
GC::WriteBarrier(prev);
GC::WriteBarrier(next);
}
//==========================================================================
//
//
//
//==========================================================================
static void InsertActorStat(DCoreActor* actor, int stat, bool tail)
{
assert(actor->prevStat == nullptr && actor->nextStat == nullptr);
assert(stat >= 0 && stat <= MAXSTATUS);
if (isSafe() || tail) AddStatTail(actor, stat);
else AddStatHead(actor, stat);
}
//==========================================================================
//
//
//
//==========================================================================
int ChangeActorStat(DCoreActor* actor, int statnum, bool tail)
{
int oldstat = actor->link_stat;
assert(statnum >= 0 && statnum < MAXSTATUS);
assert(actor->spr.statnum >= 0 && actor->spr.statnum < MAXSTATUS);
RemoveActorStat(actor);
InsertActorStat(actor, statnum, tail);
return 0;
}
//==========================================================================
//
//
//
//==========================================================================
static bool ValidateSectList(sectortype* sect, DCoreActor *checkme = nullptr)
{
#if 0
assert(sect);
checked.Clear();
assert(sect->firstEntry == nullptr || sect->firstEntry->prevSect == nullptr);
assert(sect->lastEntry == nullptr || sect->lastEntry->nextSect == nullptr);
for (auto entry = sect->firstEntry; entry; entry = entry->nextSect)
{
assert(entry != checkme);
assert(!checked.Contains(entry));
checked.Push(entry);
assert(entry->prevSect != nullptr || sect->firstEntry == entry);
assert(entry->nextSect != nullptr || sect->lastEntry == entry);
assert(entry->prevSect == nullptr || entry->prevSect->nextSect == entry);
assert(entry->nextSect == nullptr || entry->nextSect->prevSect == entry);
}
#endif
return true;
}
//==========================================================================
//
//
//
//==========================================================================
static void AddSectTail(DCoreActor *actor, sectortype* sect)
{
assert(actor->prevSect == nullptr && actor->nextSect == nullptr);
auto tail = sect->lastEntry;
assert(tail == nullptr || tail->nextSect == nullptr);
assert(ValidateSectList(sect));
actor->prevSect = tail;
if (tail) tail->nextSect = actor;
else sect->firstEntry = actor;
sect->lastEntry = actor;
assert(ValidateSectList(sect));
actor->setsector(sect);
actor->link_sector = sect;
GC::WriteBarrier(actor);
GC::WriteBarrier(tail);
}
//==========================================================================
//
//
//
//==========================================================================
static void AddSectHead(DCoreActor* actor, sectortype* sect)
{
assert(actor->prevSect == nullptr && actor->nextSect == nullptr);
auto head = sect->firstEntry;
assert(head == nullptr || head->prevSect == nullptr);
assert(ValidateSectList(sect));
actor->nextSect = head;
if (head) head->prevSect = actor;
else sect->lastEntry = actor;
sect->firstEntry = actor;
assert(ValidateSectList(sect));
actor->setsector(sect);
actor->link_sector = sect;
GC::WriteBarrier(actor);
GC::WriteBarrier(head);
}
//==========================================================================
//
//
//
//==========================================================================
static void RemoveActorSect(DCoreActor* actor)
{
if (actor->link_sector == nullptr)
{
assert(actor->prevSect == nullptr && actor->nextSect == nullptr);
return;
}
DCoreActor* prev = actor->prevSect;
DCoreActor* next = actor->nextSect;
auto& firstEntry = actor->link_sector->firstEntry;
auto& lastEntry = actor->link_sector->lastEntry;
auto prevp = prev ? &prev->nextSect : &firstEntry;
auto nextp = next ? &next->prevSect : &lastEntry;
if (*prevp == nullptr && *nextp == nullptr) return; // can happen during an aborted savegame load.
assert(*prevp == actor);
assert(*nextp == actor);
assert(ValidateSectList(actor->link_sector));
*prevp = next;
*nextp = prev;
assert(ValidateSectList(actor->link_sector, actor));
actor->nextSect = actor->prevSect = nullptr;
actor->setsector(nullptr);
actor->link_sector = nullptr;
GC::WriteBarrier(prev);
GC::WriteBarrier(next);
}
//==========================================================================
//
//
//
//==========================================================================
static void InsertActorSect(DCoreActor* actor, sectortype* sector, bool tail)
{
assert(actor->prevSect == nullptr && actor->nextSect == nullptr);
if (!sector)
{
actor->link_sector = nullptr;
actor->setsector(nullptr);
return;
}
if (isSafe() || tail) AddSectTail(actor, sector);
else AddSectHead(actor, sector);
}
//==========================================================================
//
//
//
//==========================================================================
void ChangeActorSect(DCoreActor* actor, sectortype* sect, bool tail)
{
if (sect == nullptr) return;
RemoveActorSect(actor);
InsertActorSect(actor, sect, tail);
}
//==========================================================================
//
//
//
//==========================================================================
DCoreActor* InsertActor(PClass* type, sectortype* sector, int stat, bool tail)
{
if (type == nullptr) return nullptr;
assert(type->IsDescendantOf(RUNTIME_CLASS(DCoreActor)));
if (!type->IsDescendantOf(RUNTIME_CLASS(DCoreActor)))
{
I_Error("Tried to spawn object of non - actor class %s", type->TypeName.GetChars());
}
auto actor = static_cast<DCoreActor*>(type->CreateNew());
auto defaults = GetDefaultByType(type);
auto actorinfo = static_cast<PClassActor*>(actor->GetClass())->ActorInfo();
if (actorinfo && actorinfo->DefaultFlags & DEFF_STATNUM) stat = defaults->spr.statnum;
if (stat == -1) stat = 0;
GC::WriteBarrier(actor);
InsertActorStat(actor, stat, tail);
InsertActorSect(actor, sector, tail);
Numsprites++;
actor->time = leveltimer++;
return actor;
}
void DCoreActor::initFromSprite(spritetype* mspr)
{
auto actorinfo = static_cast<PClassActor*>(GetClass())->ActorInfo();
spr.cstat = (mspr->cstat & ~ESpriteFlags::FromInt(actorinfo->DefaultCstat)) | (spr.cstat & ESpriteFlags::FromInt(actorinfo->DefaultCstat));
spr.pos = mspr->pos;
spr.sectp = mspr->sectp;
spr.clipdist = mspr->clipdist;
// picnum may only be used if the class allows it.
if (!isBlood())
{
if (!(actorinfo->DefaultFlags & DEFF_PICNUM)) spr.picnum = mspr->picnum;
}
else
{
// In Blood, actor type and picnum are distinct entities so the logic must be different.
// Here this only gets set if the map sprite's pic is 0
if (mspr->picnum != 0) spr.picnum = mspr->picnum;
}
if (!mspr->scale.isZero()) spr.scale = mspr->scale; // only use default scale if not set in the map.
#define setter(var) spr.var = mspr->var;
setter(Angles.Yaw);
setter(intangle);
setter(xint);
setter(yint);
setter(inittype);
setter(hitag);
setter(lotag);
setter(extra);
setter(detail);
setter(shade);
setter(pal);
setter(blend);
setter(xoffset);
setter(yoffset);
setter(intowner);
#undef setter
clipdist = spr.clipdist * 0.25;
if (mspr->statnum != 0 && !(actorinfo->DefaultFlags & DEFF_STATNUM))
ChangeActorStat(this, mspr->statnum);
}
//==========================================================================
//
//
//
//==========================================================================
void DCoreActor::OnDestroy()
{
FVector3 pos = GetSoundPos(spr.pos);
soundEngine->RelinkSound(SOURCE_Actor, this, nullptr, &pos);
// also scan all other sounds if they have this actor as source. If so, null the source and stop looped sounds.
soundEngine->EnumerateChannels([=](FSoundChan* chan)
{
if (chan->Source == this)
{
if (chan->ChanFlags & CHANF_LOOP) soundEngine->StopChannel(chan);
else chan->Source = nullptr;
}
return 0;
});
if(link_stat == INT_MAX) return;
int stat = link_stat;
RemoveActorStat(this);
auto sect = link_sector;
if (sect)
{
RemoveActorSect(this);
}
else
{
assert(prevSect == nullptr && nextSect == nullptr);
}
Numsprites--;
if (wallspriteinfo) delete wallspriteinfo;
wallspriteinfo = nullptr;
}
//==========================================================================
//
// code below will go away or be changed once
// we can use real DObject life cycle management.
//
//==========================================================================
void InitSpriteLists()
{
// Do not mass-destroy from the iterator. This may fail if destroying one actor results in further destructions.
TArray<DCoreActor*> allActors;
TSpriteIterator<DCoreActor> it;
while (auto actor = it.Next())
allActors.Push(actor);
// clear all lists manually before doing any mass destruction.
// This may also be called in error situations where the list has become corrupted,
// so we should not depend on its consistency anymore.
for (auto& stat : statList)
{
stat.firstEntry = stat.lastEntry = nullptr;
}
for (auto& sect: sector)
{
sect.firstEntry = sect.lastEntry = nullptr;
}
for (auto& act : allActors)
{
if (!(act->ObjectFlags & OF_EuthanizeMe))
{
act->link_stat = INT_MAX;
act->prevStat = act->nextStat = act->prevSect = act->nextSect = nullptr;
act->Destroy();
}
}
Numsprites = 0;
}
//==========================================================================
//
//
//
//==========================================================================
void SetActor(DCoreActor* actor, const DVector3& newpos)
{
auto tempsector = actor->sector();
actor->spr.pos = newpos;
updatesector(newpos, &tempsector);
if (tempsector && tempsector != actor->sector())
ChangeActorSect(actor, tempsector);
}
void SetActorZ(DCoreActor* actor, const DVector3& newpos)
{
auto tempsector = actor->sector();
actor->spr.pos = newpos;
updatesectorz(newpos, &tempsector);
if (tempsector && tempsector != actor->sector())
ChangeActorSect(actor, tempsector);
}
IMPLEMENT_CLASS(DCoreActor, false, false)
size_t DCoreActor::PropagateMark()
{
GC::Mark(prevStat);
GC::Mark(nextStat);
GC::Mark(prevSect);
GC::Mark(nextSect);
return Super::PropagateMark();
}
double DCoreActor::GetOffsetAndHeight(double& height)
{
auto tex = TexMan.GetGameTexture(spr.spritetexture());
double yscale = spr.scale.Y;
height = tex->GetDisplayHeight() * yscale;
double zofs = (spr.cstat & CSTAT_SPRITE_YCENTER) ? height * 0.5 : 0;
return zofs - tex->GetDisplayTopOffset() * yscale;
}