Improved ZScript interface for morphing

Added getter and setter functions for handling whether or not the player fields should be gotten/set. Added MRF_KEEPARMOR flag to prevent stripping armor on morph. Optimized unmorphed Actor by setting it to NoInteraction and removing it from the blockmap and sector lists.
This commit is contained in:
Boondorl 2024-01-14 19:41:58 -05:00 committed by Rachael Alexanderson
parent 12dc5c1506
commit 6c64a4403c
6 changed files with 124 additions and 47 deletions

View file

@ -68,7 +68,7 @@ Class ArtiTomeOfPower : PowerupGiver
override bool Use(bool pickup)
{
EMorphFlags mStyle = Owner.player ? Owner.player.MorphStyle : Owner.MorphFlags;
EMorphFlags mStyle = Owner.GetMorphStyle();
if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER))
{
// Attempt to undo chicken.

View file

@ -1928,12 +1928,10 @@ class PowerMorph : Powerup
// Abort if owner is dead; their Die() method will
// take care of any required unmorphing on death.
if (MorphedPlayer ? MorphedPlayer.Health <= 0 : Owner.Health <= 0)
if (Owner.player ? Owner.player.Health <= 0 : Owner.Health <= 0)
return;
EMorphFlags mStyle = MorphedPlayer ? MorphedPlayer.MorphStyle : MorphStyle;
Owner.Unmorph(Owner, force: mStyle & MRF_UNDOALWAYS);
Owner.Unmorph(Owner, force: Owner.GetMorphStyle() & MRF_UNDOALWAYS);
MorphedPlayer = null;
}
}

View file

@ -23,11 +23,16 @@
extend class Actor
{
// Blockmap, sector, and no interaction are the only relevant flags but the old ones are kept around
// for legacy reasons.
enum EPremorphProperty
{
MPROP_SOLID = 1 << 1,
MPROP_SHOOTABLE = 1 << 2,
MPROP_INVIS = 1 << 6,
MPROP_SOLID = 1 << 1,
MPROP_SHOOTABLE = 1 << 2,
MPROP_NO_BLOCKMAP = 1 << 3,
MPROP_NO_SECTOR = 1 << 4,
MPROP_NO_INTERACTION = 1 << 5,
MPROP_INVIS = 1 << 6,
}
int UnmorphTime;
@ -35,6 +40,52 @@ extend class Actor
class<Actor> MorphExitFlash;
EPremorphProperty PremorphProperties;
// Players still track these separately for legacy reasons.
void SetMorphStyle(EMorphFlags flags)
{
if (player)
player.MorphStyle = flags;
else
MorphFlags = flags;
}
clearscope EMorphFlags GetMorphStyle() const
{
return player ? player.MorphStyle : MorphFlags;
}
void SetMorphExitFlash(class<Actor> flash)
{
if (player)
player.MorphExitFlash = flash;
else
MorphExitFlash = flash;
}
clearscope class<Actor> GetMorphExitFlash() const
{
return player ? player.MorphExitFlash : MorphExitFlash;
}
void SetMorphTics(int dur)
{
if (player)
player.MorphTics = dur;
else
UnmorphTime = Level.Time + dur;
}
clearscope int GetMorphTics() const
{
if (player)
return player.MorphTics;
if (UnmorphTime <= 0)
return UnmorphTime;
return UnmorphTime > Level.Time ? UnmorphTime - Level.Time : 0;
}
// This function doesn't return anything anymore since allowing so would be too volatile
// for morphing management. Instead it's now a function that lets special actions occur
// when a morphed Actor dies.
@ -113,8 +164,12 @@ extend class Actor
Actor morphed = Spawn(spawnType, Pos, ALLOW_REPLACE);
if (!MorphInto(morphed))
{
morphed.ClearCounters();
morphed.Destroy();
if (morphed)
{
morphed.ClearCounters();
morphed.Destroy();
}
return false;
}
@ -146,30 +201,39 @@ extend class Actor
morphed.CopyFriendliness(self, true);
// Remove all armor.
for (Inventory item = morphed.Inv; item;)
if (!(style & MRF_KEEPARMOR))
{
Inventory next = item.Inv;
if (item is "Armor")
item.DepleteOrDestroy();
for (Inventory item = morphed.Inv; item;)
{
Inventory next = item.Inv;
if (item is "Armor")
item.DepleteOrDestroy();
item = next;
item = next;
}
}
morphed.UnmorphTime = Level.Time + (duration ? duration : DEFMORPHTICS) + Random[morphmonst]();
morphed.MorphFlags = style;
morphed.MorphExitFlash = exitFlash;
morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE) | (bInvisible * MPROP_INVIS);
morphed.SetMorphTics((duration ? duration : DEFMORPHTICS) + Random[morphmonst]());
morphed.SetMorphStyle(style);
morphed.SetMorphExitFlash(exitFlash);
morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE)
| (bNoBlockmap * MPROP_NO_BLOCKMAP) | (bNoSector * MPROP_NO_SECTOR)
| (bNoInteraction * MPROP_NO_INTERACTION) | (bInvisible * MPROP_INVIS);
// This is just here for backwards compatibility as MorphedMonster used to be required.
let morphMon = MorphedMonster(morphed);
if (morphMon)
{
morphMon.UnmorphedMe = morphMon.Alternative;
morphMon.MorphStyle = morphMon.MorphFlags;
morphMon.MorphStyle = morphMon.GetMorphStyle();
morphMon.FlagsSave = morphMon.PremorphProperties;
}
Special = 0;
bNoInteraction = true;
A_ChangeLinkFlags(true, true);
// Legacy
bInvisible = true;
bSolid = bShootable = false;
@ -219,7 +283,7 @@ extend class Actor
// Didn't fit.
if (!res)
{
UnmorphTime = Level.Time + 5*TICRATE; // Next try in 5 seconds.
SetMorphTics(5 * TICRATE);
return false;
}
}
@ -249,7 +313,11 @@ extend class Actor
alt.Vel = Vel;
alt.Score = Score;
if (TID && (MorphFlags & MRF_NEWTIDBEHAVIOUR))
alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION);
alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR));
EMorphFlags style = GetMorphStyle();
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
{
alt.ChangeTID(TID);
ChangeTID(0);
@ -262,14 +330,12 @@ extend class Actor
alt.Tracer = Tracer;
alt.Master = Master;
alt.CopyFriendliness(self, true, false);
if (Health > 0 || (MorphFlags & MRF_UNDOBYDEATHSAVES))
if (Health > 0 || (style & MRF_UNDOBYDEATHSAVES))
alt.Health = alt.SpawnHealth();
else
alt.Health = Health;
Special = 0;
bInvisible = true;
bSolid = bShootable = false;
PostUnmorph(alt, false); // From is false here: Leaving the caller's body.
alt.PostUnmorph(self, true); // True here: Entering this body from here.
@ -347,7 +413,7 @@ class MorphedMonster : Actor
override bool UndoMonsterMorph(bool force)
{
Alternative = UnmorphedMe;
MorphFlags = MorphStyle;
SetMorphStyle(MorphStyle);
PremorphProperties = FlagsSave;
return super.UndoMonsterMorph(force);
}

View file

@ -110,7 +110,7 @@ extend class PlayerPawn
{
// Player is already a beast.
if (bCanSuperMorph && spawnType == GetClass()
&& player.MorphTics < duration - TICRATE
&& GetMorphTics() < duration - TICRATE
&& !FindInventory("PowerWeaponLevel2", true))
{
// Make a super chicken.
@ -126,7 +126,9 @@ extend class PlayerPawn
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE));
if (!MorphInto(morphed))
{
morphed.Destroy();
if (morphed)
morphed.Destroy();
return false;
}
@ -155,29 +157,35 @@ extend class PlayerPawn
}
// special2 is no longer used here since Actors now have a proper field for it.
morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE) | (bInvisible * MPROP_INVIS);
morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE)
| (bNoBlockmap * MPROP_NO_BLOCKMAP) | (bNoSector * MPROP_NO_SECTOR)
| (bNoInteraction * MPROP_NO_INTERACTION) | (bInvisible * MPROP_INVIS);
morphed.bShadow |= bShadow;
morphed.bNoGravity |= bNoGravity;
morphed.bFly |= bFly;
morphed.bGhost |= bGhost;
// Remove all armor.
for (Inventory item = morphed.Inv; item;)
if (!(style & MRF_KEEPARMOR))
{
Inventory next = item.Inv;
if (item is "Armor")
item.DepleteOrDestroy();
for (Inventory item = morphed.Inv; item;)
{
Inventory next = item.Inv;
if (item is "Armor")
item.DepleteOrDestroy();
item = next;
item = next;
}
}
// Players store their morph behavior into their PlayerInfo unlike regular Actors which use the
// morph properties. This is needed for backwards compatibility and to give the HUD info.
let p = morphed.player;
p.MorphTics = duration;
morphed.SetMorphTics(duration);
morphed.SetMorphStyle(style);
morphed.SetMorphExitFlash(exitFlash);
p.MorphedPlayerClass = spawnType;
p.MorphStyle = style;
p.MorphExitFlash = exitFlash;
p.PremorphWeapon = p.ReadyWeapon;
p.Health = morphed.Health;
p.Vel = (0.0, 0.0);
@ -185,6 +193,10 @@ extend class PlayerPawn
if (morphed.ViewHeight > p.ViewHeight && !p.DeltaViewHeight)
p.DeltaViewHeight = p.GetDeltaViewHeight();
bNoInteraction = true;
A_ChangeLinkFlags(true, true);
// Legacy
bSolid = bShootable = false;
bInvisible = true;
@ -242,7 +254,7 @@ extend class PlayerPawn
if (!res)
{
player.MorphTics = 2 * TICRATE;
SetMorphTics(2 * TICRATE);
return false;
}
}
@ -284,9 +296,12 @@ extend class PlayerPawn
alt.bFly = bFly;
alt.Vel = (0.0, 0.0, Vel.Z);
alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION);
alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR));
let p = alt.player;
class<Actor> exitFlash = p.MorphExitFlash;
EMorphFlags style = p.MorphStyle;
class<Actor> exitFlash = alt.GetMorphExitFlash();
EMorphFlags style = alt.GetMorphStyle();
Weapon premorphWeap = p.PremorphWeapon;
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
@ -295,10 +310,10 @@ extend class PlayerPawn
ChangeTID(0);
}
p.MorphTics = 0;
alt.SetMorphTics(0);
alt.SetMorphStyle(0);
alt.SetMorphExitFlash(null);
p.MorphedPlayerClass = null;
p.MorphStyle = 0;
p.MorphExitFlash = null;
p.PremorphWeapon = null;
p.ViewHeight = alt.ViewHeight;
p.Vel = (0.0, 0.0);
@ -347,9 +362,6 @@ extend class PlayerPawn
alt.ClearFOVInterpolation();
alt.InitAllPowerupEffects();
bInvisible = true;
bSolid = bShootable = false;
PostUnmorph(alt, false); // This body is no longer current.
alt.PostUnmorph(self, true); // altmo body is current.

View file

@ -38,7 +38,7 @@ class ArtiTeleport : Inventory
Owner.Teleport(dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG);
bool canLaugh = Owner.player != null;
EMorphFlags mStyle = Owner.player ? Owner.player.MorphStyle : Owner.MorphFlags;
EMorphFlags mStyle = Owner.GetMorphStyle();
if (Owner.Alternative && (mStyle & MRF_UNDOBYCHAOSDEVICE))
{
// Teleporting away will undo any morph effects (pig).

View file

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