Remember statnum when travelling

Also cleans up the functions a little.
This commit is contained in:
Boondorl 2025-07-16 14:26:02 -04:00 committed by Ricardo Luís Vaz Silva
commit 00d2b43ce0
4 changed files with 65 additions and 60 deletions

View file

@ -1666,7 +1666,6 @@ void FLevelLocals::StartTravel()
while ((th = it.Next()) != nullptr)
{
// Already processed this Thinker.
if (th->ObjectFlags & OF_Travelling)
continue;
@ -1679,7 +1678,7 @@ void FLevelLocals::StartTravel()
// No voodoo dolls allowed.
if (mo->player->mo != mo)
{
mo->ChangeStatNum(STAT_PLAYER);
mo->ChangeStatNum(mo->GetStatNum());
continue;
}
@ -1758,32 +1757,17 @@ int FLevelLocals::FinishTravel()
toCallBack.Push(th);
th->ObjectFlags &= ~OF_Travelling;
th->ChangeStatNum(th->GetStatNum());
auto mo = dyn_cast<AActor>(th);
if (mo == nullptr)
{
if (th->IsKindOf(NAME_Bot))
th->ChangeStatNum(STAT_BOT);
else
th->ChangeStatNum(STAT_DEFAULT);
continue;
}
// Check the actual pawn type here since its player field isn't guaranteed
// to be set e.g. unmorphed Actors.
const bool player = mo->IsKindOf(NAME_PlayerPawn);
if (player)
mo->ChangeStatNum(STAT_PLAYER);
else if (mo->IsKindOf(NAME_Inventory) && mo->PointerVar<AActor>(NAME_Owner) != nullptr)
mo->ChangeStatNum(STAT_INVENTORY);
else
mo->ChangeStatNum(STAT_DEFAULT);
mo->flags2 &= ~MF2_BLASTED;
mo->ClearFOVInterpolation();
LinkActorToLevel(*mo);
if (!player || mo->player == nullptr)
if (mo->player == nullptr || !mo->IsKindOf(NAME_PlayerPawn))
{
// Do some basic relinking. Modders will figure out what to do with it
// in the callback.
@ -1806,16 +1790,16 @@ int FLevelLocals::FinishTravel()
const int pNum = PlayerNum(mo->player);
// This will be whatever previous pawn was in the level for this player, be it from a snapshot
// or a map spawn.
auto doll = mo->player->mo;
assert(mo != doll);
auto mapDoll = mo->player->mo;
assert(mo != mapDoll);
auto start = PickPlayerStart(pNum, 0);
if (start == nullptr)
{
if (doll != nullptr)
if (mapDoll != nullptr)
{
Printf(TEXTCOLOR_RED "No player %d start to travel to\n", pNum + 1);
mo->SetOrigin(doll->Pos(), true);
mo->SetOrigin(mapDoll->Pos(), true);
}
else if (failNum <= 0)
{
@ -1824,12 +1808,10 @@ int FLevelLocals::FinishTravel()
}
}
auto mapDoll = doll;
// Find the actual spawn location taking hub entrances into account. This player
// is only meant to be short-lived so don't fire off any events unless there truly
// was no other player spawned beforehand (can happen in co-op).
doll = SpawnPlayer(start, pNum, SPF_TEMPPLAYER);
auto doll = SpawnPlayer(start, pNum, SPF_TEMPPLAYER);
if (doll != nullptr)
{
if (!(changeflags & CHANGELEVEL_KEEPFACING))

View file

@ -783,12 +783,12 @@ int FThinkerList::ProfileThinkers(FThinkerList *dest)
//
//==========================================================================
DThinker::~DThinker ()
DThinker::~DThinker()
{
assert(NextThinker == nullptr && PrevThinker == nullptr);
}
void DThinker::OnDestroy ()
void DThinker::OnDestroy()
{
assert((NextThinker != nullptr && PrevThinker != nullptr) ||
(NextThinker == nullptr && PrevThinker == nullptr));
@ -796,13 +796,32 @@ void DThinker::OnDestroy ()
{
Remove();
}
_statNum = -1;
Super::OnDestroy();
}
void DThinker::Serialize(FSerializer &arc)
{
Super::Serialize(arc);
arc("level", Level);
arc("level", Level)
("statnum", _statNum);
}
//==========================================================================
//
//
//
//==========================================================================
static int GetStatNum(DThinker* self)
{
return self->GetStatNum();
}
DEFINE_ACTION_FUNCTION_NATIVE(DThinker, GetStatNum, GetStatNum)
{
PARAM_SELF_PROLOGUE(DThinker);
ACTION_RETURN_INT(self->GetStatNum());
}
//==========================================================================
@ -838,11 +857,16 @@ void DThinker::Remove()
//
//==========================================================================
void DThinker::PostBeginPlay ()
void DThinker::PostBeginPlay()
{
}
DEFINE_ACTION_FUNCTION(DThinker, PostBeginPlay)
static void NativePostBeginPlay(DThinker* self)
{
self->PostBeginPlay();
}
DEFINE_ACTION_FUNCTION_NATIVE(DThinker, PostBeginPlay, NativePostBeginPlay)
{
PARAM_SELF_PROLOGUE(DThinker);
self->PostBeginPlay();
@ -853,15 +877,9 @@ void DThinker::CallPostBeginPlay()
{
ObjectFlags |= OF_Spawned;
IFVIRTUAL(DThinker, PostBeginPlay)
{
// Without the type cast this picks the 'void *' assignment...
VMValue params[1] = { (DObject*)this };
VMCall(func, params, 1, nullptr, 0);
}
VMCallVoid<DThinker*>(func, this);
else
{
PostBeginPlay();
}
}
//==========================================================================
@ -880,7 +898,7 @@ void DThinker::PostSerialize()
//
//==========================================================================
DThinker *FLevelLocals::FirstThinker (int statnum)
DThinker *FLevelLocals::FirstThinker(int statnum)
{
return Thinkers.FirstThinker(statnum);
}
@ -896,26 +914,27 @@ DThinker* FLevelLocals::FirstClientsideThinker(int statnum)
//
//==========================================================================
void DThinker::ChangeStatNum (int statnum)
void DThinker::ChangeStatNum(int statnum)
{
if ((unsigned)statnum > MAX_STATNUM)
{
statnum = MAX_STATNUM;
}
Remove();
if (IsClientside())
Level->ClientsideThinkers.Link(this, statnum);
else
Level->Thinkers.Link(this, statnum);
// Let us relink it back properly when we're done travelling.
if (statnum != STAT_TRAVELLING)
_statNum = statnum;
}
static void ChangeStatNum(DThinker *thinker, int statnum)
static void ChangeStatNum(DThinker *self, int statnum)
{
// Wait until after these Thinkers are processed to allow modifying them,
// otherwise relinking and callbacks will be completely messed up. This can
// only happen from map objects spawning in intentionally trying to mess with
// the list.
if (thinker->ObjectFlags & OF_Travelling)
if (self->ObjectFlags & OF_Travelling)
{
Printf(TEXTCOLOR_RED "Travelling Thinkers cannot have their statnum changed\n");
return;
@ -923,7 +942,7 @@ static void ChangeStatNum(DThinker *thinker, int statnum)
// This will always break Actors, they should use STAT_TRAVELLING instead to
// transition between levels.
if (statnum == STAT_STATIC && thinker->IsKindOf(NAME_Actor))
if (statnum == STAT_STATIC && self->IsKindOf(NAME_Actor))
{
Printf(TEXTCOLOR_RED "Actors cannot be added to STAT_STATIC\n");
return;
@ -938,17 +957,17 @@ static void ChangeStatNum(DThinker *thinker, int statnum)
return;
}
// These should be handled by the owning Actor, otherwise they'll lose them and become useless anyway.
if (thinker->IsKindOf(NAME_Inventory) && thinker->PointerVar<AActor>(NAME_Owner) != nullptr)
if (self->IsKindOf(NAME_Inventory) && self->PointerVar<AActor>(NAME_Owner) != nullptr)
{
Printf(TEXTCOLOR_RED "Owned Inventory items must travel with their owner on level change\n");
return;
}
if (thinker->IsKindOf(NAME_Bot))
if (self->IsKindOf(NAME_Bot))
{
Printf(TEXTCOLOR_RED "Bot Thinkers must travel with their owner on level change\n");
return;
}
auto mo = dyn_cast<AActor>(thinker);
auto mo = dyn_cast<AActor>(self);
if (mo != nullptr && (mo->flags & MF_UNMORPHED))
{
Printf(TEXTCOLOR_RED "Unmorphed Actors must travel with their owner on level change\n");
@ -956,7 +975,7 @@ static void ChangeStatNum(DThinker *thinker, int statnum)
}
// These need to be locked down since they have native fields that won't be cleared
// properly at the moment.
auto cls = thinker->GetClass()->NativeClass();
auto cls = self->GetClass()->NativeClass();
if (cls->TypeName != NAME_Thinker && cls->TypeName != NAME_Actor)
{
Printf(TEXTCOLOR_RED "Native thinkers cannot travel\n");
@ -969,14 +988,13 @@ static void ChangeStatNum(DThinker *thinker, int statnum)
return;
}
thinker->ChangeStatNum(statnum);
self->ChangeStatNum(statnum);
}
DEFINE_ACTION_FUNCTION_NATIVE(DThinker, ChangeStatNum, ChangeStatNum)
{
PARAM_SELF_PROLOGUE(DThinker);
PARAM_INT(stat);
ChangeStatNum(self, stat);
return 0;
}
@ -1043,11 +1061,16 @@ CCMD(profilethinkers)
//
//==========================================================================
void DThinker::Tick ()
void DThinker::Tick()
{
}
DEFINE_ACTION_FUNCTION(DThinker, Tick)
static void NativeTick(DThinker* self)
{
self->Tick();
}
DEFINE_ACTION_FUNCTION_NATIVE(DThinker, Tick, NativeTick)
{
PARAM_SELF_PROLOGUE(DThinker);
self->Tick();
@ -1057,12 +1080,9 @@ DEFINE_ACTION_FUNCTION(DThinker, Tick)
void DThinker::CallTick()
{
IFVIRTUAL(DThinker, Tick)
{
// Without the type cast this picks the 'void *' assignment...
VMValue params[1] = { (DObject*)this };
VMCall(func, params, 1, nullptr, 0);
}
else Tick();
VMCallVoid<DThinker*>(func, this);
else
Tick();
}
//==========================================================================

View file

@ -113,6 +113,7 @@ public:
size_t PropagateMark();
void ChangeStatNum (int statnum);
inline int GetStatNum() const { return _statNum; }
private:
void Remove();
@ -123,6 +124,7 @@ private:
friend class DObject;
friend class FDoomSerializer;
int8_t _statNum = -1;
DThinker *NextThinker = nullptr, *PrevThinker = nullptr;
public:

View file

@ -242,6 +242,7 @@ class Thinker : Object native play
virtual native void PostBeginPlay();
virtual void OnLoad() {}
native void ChangeStatNum(int stat);
native clearscope int GetStatNum() const;
static clearscope int Tics2Seconds(int tics)
{