Improvements to death and cheat handling

Extra safety to ensure dummy Actor deaths properly emulate a real death and aren't duplicate called. Fixed a crash when using the kill command while set to unmorph on death. Super morphing is now possible while using the morphme cheat if passing the morph class directly. Added a flag to ignore player invulnerability completely when morphing.
This commit is contained in:
Boondorl 2024-02-07 17:56:38 -05:00 committed by Rachael Alexanderson
parent 6c64a4403c
commit 3033fafaa7
8 changed files with 44 additions and 23 deletions

View file

@ -626,10 +626,13 @@ public:
double plyrdmgfact = Pawn->DamageFactor;
Pawn->DamageFactor = 1.;
P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide);
Pawn->DamageFactor = plyrdmgfact;
if (Pawn->health <= 0)
if (Pawn != nullptr)
{
Pawn->flags &= ~MF_SHOOTABLE;
Pawn->DamageFactor = plyrdmgfact;
if (Pawn->health <= 0)
{
Pawn->flags &= ~MF_SHOOTABLE;
}
}
Destroy();
}

View file

@ -465,7 +465,6 @@ xx(MonsterClass)
xx(MorphedMonster)
xx(Wi_NoAutostartMap)
xx(MorphFlags)
xx(Duration)
xx(MorphStyle)
xx(MorphFlash)

View file

@ -26,6 +26,8 @@ enum
MORPH_UNDOBYTIMEOUT = 0x00001000, // Player unmorphs once countdown expires
MORPH_UNDOALWAYS = 0x00002000, // Powerups must always unmorph, no matter what.
MORPH_TRANSFERTRANSLATION = 0x00004000, // Transfer translation from the original actor to the morphed one
MORPH_KEEPARMOR = 0x00008000, // Don't lose current armor value when morphing.
MORPH_IGNOREINVULN = 0x00010000, // Completely ignore invulnerability status on players.
MORPH_STANDARDUNDOING = MORPH_UNDOBYTOMEOFPOWER | MORPH_UNDOBYCHAOSDEVICE | MORPH_UNDOBYTIMEOUT,
};

View file

@ -324,14 +324,23 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf
{
// Return values are no longer used to ensure things stay properly managed.
AActor* const realMo = alternative;
const int morphStyle = player != nullptr ? player->MorphStyle : IntVar(NAME_MorphFlags);
int morphStyle = 0;
VMValue params[] = { this };
{
IFVM(Actor, GetMorphStyle)
{
VMReturn ret[] = { &morphStyle };
VMCall(func, params, 1, ret, 1);
}
}
VMCall(func, params, 1, nullptr, 0);
// Kill the dummy Actor if it didn't unmorph, otherwise checking the morph flags. Player pawns need
// to stay, otherwise they won't respawn correctly.
if (realMo != nullptr
if (realMo != nullptr && !(realMo->flags6 & MF6_KILLED)
&& ((alternative != nullptr && player == nullptr) || (alternative == nullptr && !(morphStyle & MORPH_UNDOBYDEATHSAVES))))
{
if (wasgibbed)
@ -345,6 +354,11 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf
realMo->health = 0;
}
// Pass appropriate damage information along when it's confirmed to die.
realMo->DamageTypeReceived = DamageTypeReceived;
realMo->DamageType = DamageType;
realMo->special1 = special1;
realMo->CallDie(source, inflictor, dmgflags, MeansOfDeath);
}
}

View file

@ -91,7 +91,7 @@ extend class Actor
// when a morphed Actor dies.
virtual Actor, int, int MorphedDeath()
{
EMorphFlags mStyle = player ? player.MorphStyle : MorphFlags;
EMorphFlags mStyle = GetMorphStyle();
if (mStyle & MRF_UNDOBYDEATH)
Unmorph(self, force: mStyle & MRF_UNDOBYDEATHFORCED);

View file

@ -426,29 +426,34 @@ extend class PlayerPawn
}
virtual String CheatMorph(class<PlayerPawn> morphClass, bool quickundo)
virtual String CheatMorph(class<PlayerPawn> morphClass, bool undo)
{
let oldclass = GetClass();
// Set the standard morph style for the current game
int style = MRF_UNDOBYTOMEOFPOWER;
if (gameinfo.gametype == GAME_Hexen) style |= MRF_UNDOBYCHAOSDEVICE;
EMorphFlags style = MRF_UNDOBYTOMEOFPOWER;
if (GameInfo.GameType == GAME_Hexen)
style |= MRF_UNDOBYCHAOSDEVICE;
if (Alternative)
{
if (Unmorph (self))
class<PlayerPawn> cls = GetClass();
Actor mo = Alternative;
if (!undo || Unmorph(self))
{
if (!quickundo && oldclass != morphclass && Morph(self, morphclass, null, 0, style))
if ((!undo && Morph(self, morphClass, null, 0, style))
|| (undo && morphClass != cls && mo.Morph(mo, morphClass, null, 0, style)))
{
return StringTable.Localize("$TXT_STRANGER");
}
return StringTable.Localize("$TXT_NOTSTRANGE");
if (undo)
return StringTable.Localize("$TXT_NOTSTRANGE");
}
}
else if (Morph (self, morphclass, null, 0, style))
else if (Morph(self, morphClass, null, 0, style))
{
return StringTable.Localize("$TXT_STRANGE");
}
return "";
}

View file

@ -98,7 +98,7 @@ extend class PlayerPawn
virtual bool MorphPlayer(PlayerInfo activator, class<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
{
if (!player || !spawnType || bDontMorph || player.Health <= 0
|| (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))))
|| (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))))
{
return false;
}
@ -106,10 +106,10 @@ extend class PlayerPawn
if (!duration)
duration = DEFMORPHTICS;
if (Alternative)
if (spawnType == GetClass())
{
// Player is already a beast.
if (bCanSuperMorph && spawnType == GetClass()
if (Alternative && bCanSuperMorph
&& GetMorphTics() < duration - TICRATE
&& !FindInventory("PowerWeaponLevel2", true))
{
@ -120,9 +120,6 @@ extend class PlayerPawn
return false;
}
if (spawnType == GetClass())
return false;
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE));
if (!MorphInto(morphed))
{
@ -228,7 +225,7 @@ extend class PlayerPawn
if (!Alternative || bStayMorphed || Alternative.bStayMorphed)
return false;
if (bInvulnerable
if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable
&& (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING))))
{
return false;

View file

@ -233,6 +233,7 @@ enum EMorphFlags
MRF_UNDOALWAYS = 0x00002000,
MRF_TRANSFERTRANSLATION = 0x00004000,
MRF_KEEPARMOR = 0x00008000,
MRF_IGNOREINVULN = 0x00010000,
MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT,
};