Skip to content

Commit 2c09a44

Browse files
Boondorlmadame-rachelle
authored andcommitted
Reworked Morphing
Removed StaticPointerSubstitution in favor of a much safer function that only changes select pointers. As a result the ability to properly modify morphing has been opened back up to ZScript. Many missing virtual callbacks were amended and MorphedDeath has been reworked to only be called back on an actual morphed death. MorphedMonster is no longer required to morph an Actor. CheckUnmorph virtual added that gets called back on morphed Actors. Fixed numerous bugs related to morph behavior.
1 parent b469770 commit 2c09a44

File tree

15 files changed

+694
-669
lines changed

15 files changed

+694
-669
lines changed

src/g_level.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1711,7 +1711,7 @@ int FLevelLocals::FinishTravel ()
17111711
pawn->flags2 &= ~MF2_BLASTED;
17121712
if (oldpawn != nullptr)
17131713
{
1714-
StaticPointerSubstitution (oldpawn, pawn);
1714+
PlayerPointerSubstitution (oldpawn, pawn);
17151715
oldpawn->Destroy();
17161716
}
17171717
if (pawndup != NULL)

src/namedef_custom.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ xx(Reflection)
145145
xx(CustomInventory)
146146
xx(Inventory)
147147
xx(StateProvider)
148+
xx(ObtainInventory)
148149
xx(CallTryPickup)
149150
xx(QuestItem25)
150151
xx(QuestItem28)
@@ -465,6 +466,7 @@ xx(MonsterClass)
465466
xx(MorphedMonster)
466467
xx(Wi_NoAutostartMap)
467468

469+
xx(MorphFlags)
468470
xx(Duration)
469471
xx(MorphStyle)
470472
xx(MorphFlash)

src/playsim/actor.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,8 +1735,8 @@ struct FTranslatedLineTarget
17351735
bool unlinked; // found by a trace that went through an unlinked portal.
17361736
};
17371737

1738-
1739-
void StaticPointerSubstitution(AActor* old, AActor* notOld);
1738+
void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer);
1739+
int MorphPointerSubstitution(AActor* from, AActor* to);
17401740

17411741
#define S_FREETARGMOBJ 1
17421742

src/playsim/p_interaction.cpp

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -313,36 +313,38 @@ EXTERN_CVAR (Int, fraglimit)
313313

314314
void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath)
315315
{
316-
// Handle possible unmorph on death
317316
bool wasgibbed = (health < GetGibHealth());
318317

318+
// Check to see if unmorph Actors need to be killed as well. Originally this was always
319+
// called but that puts an unnecessary burden on the modder to determine whether it's
320+
// a valid call or not.
321+
if (alternative != nullptr && !(flags & MF_UNMORPHED))
319322
{
320323
IFVIRTUAL(AActor, MorphedDeath)
321324
{
322-
AActor *realthis = NULL;
323-
int realstyle = 0;
324-
int realhealth = 0;
325+
// Return values are no longer used to ensure things stay properly managed.
326+
AActor* const realMo = alternative;
327+
const int morphStyle = player != nullptr ? player->MorphStyle : IntVar(NAME_MorphFlags);
325328

326329
VMValue params[] = { this };
327-
VMReturn returns[3];
328-
returns[0].PointerAt((void**)&realthis);
329-
returns[1].IntAt(&realstyle);
330-
returns[2].IntAt(&realhealth);
331-
VMCall(func, params, 1, returns, 3);
330+
VMCall(func, params, 1, nullptr, 0);
332331

333-
if (realthis && !(realstyle & MORPH_UNDOBYDEATHSAVES))
332+
// Always kill the dummy Actor if it didn't unmorph, otherwise checking the morph flags.
333+
if (realMo != nullptr && (alternative != nullptr || !(morphStyle & MORPH_UNDOBYDEATHSAVES)))
334334
{
335335
if (wasgibbed)
336336
{
337-
int realgibhealth = realthis->GetGibHealth();
338-
if (realthis->health >= realgibhealth)
339-
{
340-
realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l
341-
}
337+
const int realGibHealth = realMo->GetGibHealth();
338+
if (realMo->health >= realGibHealth)
339+
realMo->health = realGibHealth - 1; // If morphed was gibbed, so must original be (where allowed).
340+
}
341+
else if (realMo->health > 0)
342+
{
343+
realMo->health = 0;
342344
}
343-
realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath);
344-
}
345345

346+
realMo->CallDie(source, inflictor, dmgflags, MeansOfDeath);
347+
}
346348
}
347349
}
348350

src/playsim/p_mobj.cpp

Lines changed: 195 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3767,6 +3767,22 @@ void AActor::Tick ()
37673767
static const uint8_t HereticScrollDirs[4] = { 6, 9, 1, 4 };
37683768
static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 };
37693769

3770+
// Check for Actor unmorphing, but only on the thing that is the morphed Actor.
3771+
// Players do their own special checking for this.
3772+
if (alternative != nullptr && !(flags & MF_UNMORPHED) && player == nullptr)
3773+
{
3774+
int res = false;
3775+
IFVIRTUAL(AActor, CheckUnmorph)
3776+
{
3777+
VMValue params[] = { this };
3778+
VMReturn ret[] = { &res };
3779+
VMCall(func, params, 1, ret, 1);
3780+
}
3781+
3782+
if (res)
3783+
return;
3784+
}
3785+
37703786
if (freezetics > 0)
37713787
{
37723788
freezetics--;
@@ -5062,6 +5078,16 @@ void AActor::CallDeactivate(AActor *activator)
50625078

50635079
void AActor::OnDestroy ()
50645080
{
5081+
// If the Actor is leaving behind a premorph Actor, make sure it gets cleaned up as
5082+
// well so it's not stuck in the map.
5083+
if (alternative != nullptr && !(flags & MF_UNMORPHED))
5084+
{
5085+
alternative->ClearCounters();
5086+
alternative->alternative = nullptr;
5087+
alternative->Destroy();
5088+
alternative = nullptr;
5089+
}
5090+
50655091
// [ZZ] call destroy event hook.
50665092
// note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook.
50675093
// but you can't really do that without utterly breaking the game, so it's ok.
@@ -5183,59 +5209,191 @@ extern bool demonew;
51835209

51845210
//==========================================================================
51855211
//
5186-
// This once was the main method for pointer cleanup, but
5187-
// nowadays its only use is swapping out PlayerPawns.
5188-
// This requires pointer fixing throughout all objects and a few
5189-
// global variables, but it only needs to look at pointers that
5190-
// can point to a player.
5212+
// This function is dangerous and only designed for swapping player pawns
5213+
// over to their new ones upon changing levels or respawning. It SHOULD NOT be
5214+
// used for anything else! Do not export this functionality as it's
5215+
// meant strictly for internal usage. Only swap pointers if the thing being swapped
5216+
// to is a type of the thing being swapped from.
51915217
//
51925218
//==========================================================================
51935219

5194-
void StaticPointerSubstitution(AActor* old, AActor* notOld)
5220+
void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer)
51955221
{
5196-
DObject* probe;
5197-
size_t changed = 0;
5198-
int i;
5222+
if (oldPlayer == nullptr || newPlayer == nullptr || oldPlayer == newPlayer
5223+
|| !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn))
5224+
{
5225+
return;
5226+
}
51995227

5200-
if (old == nullptr) return;
5228+
// Swap over the inventory.
5229+
auto func = dyn_cast<PFunction>(newPlayer->GetClass()->FindSymbol(NAME_ObtainInventory, true));
5230+
if (func)
5231+
{
5232+
VMValue params[] = { newPlayer, oldPlayer };
5233+
VMCall(func->Variants[0].Implementation, params, 2, nullptr, 0);
5234+
}
52015235

5202-
// This is only allowed to replace players or swap out morphed monsters
5203-
if (!old->IsKindOf(NAME_PlayerPawn) || (notOld != nullptr && !notOld->IsKindOf(NAME_PlayerPawn)))
5236+
// Go through player infos.
5237+
for (int i = 0; i < MAXPLAYERS; ++i)
52045238
{
5205-
if (notOld == nullptr) return;
5206-
if (!old->IsKindOf(NAME_MorphedMonster) && !notOld->IsKindOf(NAME_MorphedMonster)) return;
5239+
if (!oldPlayer->Level->PlayerInGame(i))
5240+
continue;
5241+
5242+
auto p = oldPlayer->Level->Players[i];
5243+
5244+
if (p->mo == oldPlayer)
5245+
p->mo = newPlayer;
5246+
if (p->poisoner == oldPlayer)
5247+
p->poisoner = newPlayer;
5248+
if (p->attacker == oldPlayer)
5249+
p->attacker = newPlayer;
5250+
if (p->camera == oldPlayer)
5251+
p->camera = newPlayer;
5252+
if (p->ConversationNPC == oldPlayer)
5253+
p->ConversationNPC = newPlayer;
5254+
if (p->ConversationPC == oldPlayer)
5255+
p->ConversationPC = newPlayer;
52075256
}
5208-
// Go through all objects.
5209-
i = 0; DObject* last = 0;
5210-
for (probe = GC::Root; probe != NULL; probe = probe->ObjNext)
5257+
5258+
// Go through sectors.
5259+
for (auto& sec : oldPlayer->Level->sectors)
52115260
{
5212-
i++;
5213-
changed += probe->PointerSubstitution(old, notOld);
5214-
last = probe;
5261+
if (sec.SoundTarget == oldPlayer)
5262+
sec.SoundTarget = newPlayer;
52155263
}
52165264

5217-
// Go through players.
5218-
for (i = 0; i < MAXPLAYERS; i++)
5265+
// Update all the remaining object pointers. This is dangerous but needed to ensure
5266+
// everything functions correctly when respawning or changing levels.
5267+
for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext)
5268+
probe->PointerSubstitution(oldPlayer, newPlayer);
5269+
}
5270+
5271+
//==========================================================================
5272+
//
5273+
// This function is much safer than PlayerPointerSubstition as it only truly
5274+
// swaps a few safe pointers. This has some extra barriers to it to allow
5275+
// Actors to freely morph into other Actors which is its main usage.
5276+
// Previously this used raw pointer substitutions but that's far too
5277+
// volatile to use with modder-provided information. It also allows morphing
5278+
// to be more extendable from ZScript.
5279+
//
5280+
//==========================================================================
5281+
5282+
int MorphPointerSubstitution(AActor* from, AActor* to)
5283+
{
5284+
// Special care is taken here to make sure things marked as a dummy Actor for a morphed thing aren't
5285+
// allowed to be changed into other things. Anything being morphed into that's considered a player
5286+
// is automatically out of the question to ensure modders aren't swapping clients around.
5287+
if (from == nullptr || to == nullptr || from == to || to->player != nullptr
5288+
|| (from->flags & MF_UNMORPHED) // Another thing's dummy Actor, unmorphing the wrong way, etc.
5289+
|| (from->alternative == nullptr && to->alternative != nullptr) // Morphing into something that's already morphed.
5290+
|| (from->alternative != nullptr && from->alternative != to)) // Only allow something morphed to unmorph.
52195291
{
5220-
if (playeringame[i])
5221-
{
5222-
AActor* replacement = notOld;
5223-
auto& p = players[i];
5292+
return false;
5293+
}
52245294

5225-
if (p.mo == old) p.mo = replacement, changed++;
5226-
if (p.poisoner.ForceGet() == old) p.poisoner = replacement, changed++;
5227-
if (p.attacker.ForceGet() == old) p.attacker = replacement, changed++;
5228-
if (p.camera.ForceGet() == old) p.camera = replacement, changed++;
5229-
if (p.ConversationNPC.ForceGet() == old) p.ConversationNPC = replacement, changed++;
5230-
if (p.ConversationPC.ForceGet() == old) p.ConversationPC = replacement, changed++;
5231-
}
5295+
const bool toIsPlayer = to->IsKindOf(NAME_PlayerPawn);
5296+
if (from->IsKindOf(NAME_PlayerPawn))
5297+
{
5298+
// Players are only allowed to turn into other valid player pawns. For
5299+
// valid pawns, make sure an actual player is changing into an empty one.
5300+
// Voodoo dolls aren't allowed to morph since that should be passed to
5301+
// the main player directly.
5302+
if (!toIsPlayer || from->player == nullptr || from->player->mo != from)
5303+
return false;
5304+
}
5305+
else if (toIsPlayer || from->player != nullptr
5306+
|| (from->IsKindOf(NAME_Inventory) && from->PointerVar<AActor>(NAME_Owner) != nullptr)
5307+
|| (to->IsKindOf(NAME_Inventory) && to->PointerVar<AActor>(NAME_Owner) != nullptr))
5308+
{
5309+
// Only allow items to be swapped around if they aren't currently owned. Also prevent non-players from
5310+
// turning into fake players.
5311+
return false;
5312+
}
5313+
5314+
// Since the check is good, move the inventory items over. This should always be done when
5315+
// morphing to emulate Heretic/Hexen's behavior since those stored the inventory in their
5316+
// player structs.
5317+
auto func = dyn_cast<PFunction>(to->GetClass()->FindSymbol(NAME_ObtainInventory, true));
5318+
if (func)
5319+
{
5320+
VMValue params[] = { to, from };
5321+
VMCall(func->Variants[0].Implementation, params, 2, nullptr, 0);
5322+
}
5323+
5324+
// Only change some gameplay-related pointers that we know we can safely swap to whatever
5325+
// new Actor class is present.
5326+
AActor* mo = nullptr;
5327+
auto it = from->Level->GetThinkerIterator<AActor>();
5328+
while ((mo = it.Next()) != nullptr)
5329+
{
5330+
if (mo->target == from)
5331+
mo->target = to;
5332+
if (mo->tracer == from)
5333+
mo->tracer = to;
5334+
if (mo->master == from)
5335+
mo->master = to;
5336+
if (mo->goal == from)
5337+
mo->goal = to;
5338+
if (mo->lastenemy == from)
5339+
mo->lastenemy = to;
5340+
if (mo->LastHeard == from)
5341+
mo->LastHeard = to;
5342+
if (mo->LastLookActor == from)
5343+
mo->LastLookActor = to;
5344+
if (mo->Poisoner == from)
5345+
mo->Poisoner = to;
5346+
}
5347+
5348+
// Go through player infos.
5349+
for (int i = 0; i < MAXPLAYERS; ++i)
5350+
{
5351+
if (!from->Level->PlayerInGame(i))
5352+
continue;
5353+
5354+
auto p = from->Level->Players[i];
5355+
5356+
if (p->mo == from)
5357+
p->mo = to;
5358+
if (p->poisoner == from)
5359+
p->poisoner = to;
5360+
if (p->attacker == from)
5361+
p->attacker = to;
5362+
if (p->camera == from)
5363+
p->camera = to;
5364+
if (p->ConversationNPC == from)
5365+
p->ConversationNPC = to;
5366+
if (p->ConversationPC == from)
5367+
p->ConversationPC = to;
5368+
}
5369+
5370+
// Go through sectors.
5371+
for (auto& sec : from->Level->sectors)
5372+
{
5373+
if (sec.SoundTarget == from)
5374+
sec.SoundTarget = to;
5375+
}
5376+
5377+
// Remaining maintenance related to morphing.
5378+
if (from->player != nullptr)
5379+
{
5380+
to->player = from->player;
5381+
from->player = nullptr;
52325382
}
52335383

5234-
// Go through sectors. Only the level this actor belongs to is relevant.
5235-
for (auto& sec : old->Level->sectors)
5384+
if (from->alternative != nullptr)
52365385
{
5237-
if (sec.SoundTarget == old) sec.SoundTarget = notOld;
5386+
to->flags &= ~MF_UNMORPHED;
5387+
to->alternative = from->alternative = nullptr;
52385388
}
5389+
else
5390+
{
5391+
from->flags |= MF_UNMORPHED;
5392+
from->alternative = to;
5393+
to->alternative = from;
5394+
}
5395+
5396+
return true;
52395397
}
52405398

52415399
void FLevelLocals::PlayerSpawnPickClass (int playernum)
@@ -5508,7 +5666,7 @@ AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flag
55085666
if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr;
55095667
}
55105668

5511-
StaticPointerSubstitution (oldactor, p->mo);
5669+
PlayerPointerSubstitution (oldactor, p->mo);
55125670

55135671
localEventManager->PlayerRespawned(PlayerNum(p));
55145672
Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true);

src/scripting/thingdef_properties.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,15 @@ DEFINE_CLASS_PROPERTY(playerclass, S, PowerMorph)
18141814
defaults->PointerVar<PClassActor>(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
18151815
}
18161816

1817+
//==========================================================================
1818+
//
1819+
//==========================================================================
1820+
DEFINE_CLASS_PROPERTY(monsterclass, S, PowerMorph)
1821+
{
1822+
PROP_STRING_PARM(str, 0);
1823+
defaults->PointerVar<PClassActor>(NAME_MonsterClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
1824+
}
1825+
18171826
//==========================================================================
18181827
//
18191828
//==========================================================================

0 commit comments

Comments
 (0)