gzdoom/wadsrc/static/zscript/actors/morph.zs
Boondorl 12dc5c1506 Further morphing clean up
Players will now use their Alternative field to check if they're morphed instead of their MorphTics. This makes the current state of morphing more reliable, otherwise setting this to 0 manually without unmorphing could have very odd results. Both monsters and players consider 0 morph time to mean infinite now (previously this only applied to monsters). Player unmorphs no longer die in the case of a failed unmorph on death. Removed inventory swapping on player pointer substitution as it's too messy to do here.
2024-03-01 08:35:31 -05:00

354 lines
9.9 KiB
Text

//-----------------------------------------------------------------------------
//
// Copyright 1994-1996 Raven Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2018 Christoph Oelckers
// Copyright 2005-2008 Martin Howe
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
extend class Actor
{
enum EPremorphProperty
{
MPROP_SOLID = 1 << 1,
MPROP_SHOOTABLE = 1 << 2,
MPROP_INVIS = 1 << 6,
}
int UnmorphTime;
EMorphFlags MorphFlags;
class<Actor> MorphExitFlash;
EPremorphProperty PremorphProperties;
// 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.
virtual Actor, int, int MorphedDeath()
{
EMorphFlags mStyle = player ? player.MorphStyle : MorphFlags;
if (mStyle & MRF_UNDOBYDEATH)
Unmorph(self, force: mStyle & MRF_UNDOBYDEATHFORCED);
return null, 0, 0;
}
// [MC] Called when an actor morphs, on both the previous form (!current) and present form (current).
virtual void PreMorph(Actor mo, bool current) {}
virtual void PostMorph(Actor mo, bool current) {}
virtual void PreUnmorph(Actor mo, bool current) {}
virtual void PostUnmorph(Actor mo, bool current) {}
//===========================================================================
//
// Main entry point
//
//===========================================================================
virtual bool Morph(Actor activator, class<PlayerPawn> playerClass, class<Actor> monsterClass, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog")
{
if (player)
return player.mo.MorphPlayer(activator ? activator.player : null, playerClass, duration, style, morphFlash, unmorphFlash);
return MorphMonster(monsterClass, duration, style, morphFlash, unmorphFlash);
}
//===========================================================================
//
// Action function variant whose arguments differ from the generic one.
//
//===========================================================================
bool A_Morph(class<Actor> type, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog")
{
return Morph(self, (class<PlayerPawn>)(type), type, duration, style, morphFlash, unmorphFlash);
}
//===========================================================================
//
// Main entry point
//
//===========================================================================
virtual bool Unmorph(Actor activator, EMorphFlags flags = 0, bool force = false)
{
if (player)
return player.mo.UndoPlayerMorph(activator ? activator.player : null, flags, force);
return UndoMonsterMorph(force);
}
virtual bool CheckUnmorph()
{
return UnmorphTime && UnmorphTime <= Level.Time && Unmorph(self, MRF_UNDOBYTIMEOUT);
}
//---------------------------------------------------------------------------
//
// FUNC P_MorphMonster
//
// Returns true if the monster gets turned into a chicken/pig.
//
//---------------------------------------------------------------------------
virtual bool MorphMonster(class<Actor> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
{
if (player || !bIsMonster || !spawnType || bDontMorph || Health <= 0 || spawnType == GetClass())
return false;
Actor morphed = Spawn(spawnType, Pos, ALLOW_REPLACE);
if (!MorphInto(morphed))
{
morphed.ClearCounters();
morphed.Destroy();
return false;
}
// [MC] Notify that we're just about to start the transfer.
PreMorph(morphed, false); // False: No longer the current.
morphed.PreMorph(self, true); // True: Becoming this actor.
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
morphed.Translation = Translation;
morphed.Angle = Angle;
morphed.Alpha = Alpha;
morphed.RenderStyle = RenderStyle;
morphed.bShadow |= bShadow;
morphed.bGhost |= bGhost;
morphed.Score = Score;
morphed.Special = Special;
for (int i; i < 5; ++i)
morphed.Args[i] = Args[i];
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
{
morphed.ChangeTID(TID);
ChangeTID(0);
}
morphed.Tracer = Tracer;
morphed.Master = Master;
morphed.CopyFriendliness(self, true);
// Remove all armor.
for (Inventory item = morphed.Inv; item;)
{
Inventory next = item.Inv;
if (item is "Armor")
item.DepleteOrDestroy();
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);
// 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.FlagsSave = morphMon.PremorphProperties;
}
Special = 0;
bInvisible = true;
bSolid = bShootable = false;
PostMorph(morphed, false);
morphed.PostMorph(self, true);
if (enterFlash)
{
Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE);
if (fog)
fog.Target = morphed;
}
return true;
}
//----------------------------------------------------------------------------
//
// FUNC P_UndoMonsterMorph
//
// Returns true if the monster unmorphs.
//
//----------------------------------------------------------------------------
virtual bool UndoMonsterMorph(bool force = false)
{
if (!Alternative || bStayMorphed || Alternative.bStayMorphed)
return false;
Actor alt = Alternative;
alt.SetOrigin(Pos, false);
if (!force && (PremorphProperties & MPROP_SOLID))
{
bool altSolid = alt.bSolid;
bool isSolid = bSolid;
bool isTouchy = bTouchy;
alt.bSolid = true;
bSolid = bTouchy = false;
bool res = alt.TestMobjLocation();
alt.bSolid = altSolid;
bSolid = isSolid;
bTouchy = isTouchy;
// Didn't fit.
if (!res)
{
UnmorphTime = Level.Time + 5*TICRATE; // Next try in 5 seconds.
return false;
}
}
if (!MorphInto(alt))
return false;
PreUnmorph(alt, false);
alt.PreUnmorph(self, true);
// Check to see if it had a powerup that caused it to morph.
for (Inventory item = alt.Inv; item;)
{
Inventory next = item.Inv;
if (item is "PowerMorph")
item.Destroy();
item = next;
}
alt.Angle = Angle;
alt.bShadow = bShadow;
alt.bGhost = bGhost;
alt.bSolid = (PremorphProperties & MPROP_SOLID);
alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE);
alt.bInvisible = (PremorphProperties & MPROP_INVIS);
alt.Vel = Vel;
alt.Score = Score;
if (TID && (MorphFlags & MRF_NEWTIDBEHAVIOUR))
{
alt.ChangeTID(TID);
ChangeTID(0);
}
alt.Special = Special;
for (int i; i < 5; ++i)
alt.Args[i] = Args[i];
alt.Tracer = Tracer;
alt.Master = Master;
alt.CopyFriendliness(self, true, false);
if (Health > 0 || (MorphFlags & 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.
if (MorphExitFlash)
{
Actor fog = Spawn(MorphExitFlash, alt.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE);
if (fog)
fog.Target = alt;
}
ClearCounters();
Destroy();
return true;
}
}
//===========================================================================
//
//
//
//===========================================================================
class MorphProjectile : Actor
{
class<PlayerPawn> PlayerClass;
class<Actor> MonsterClass, MorphFlash, UnmorphFlash;
int Duration, MorphStyle;
Default
{
Damage 1;
Projectile;
MorphProjectile.MorphFlash "TeleportFog";
MorphProjectile.UnmorphFlash "TeleportFog";
-ACTIVATEIMPACT
-ACTIVATEPCROSS
}
override int DoSpecialDamage(Actor victim, int dmg, Name dmgType)
{
victim.Morph(Target, PlayerClass, MonsterClass, Duration, MorphStyle, MorphFlash, UnmorphFlash);
return -1;
}
}
//===========================================================================
//
// This class is redundant as it's no longer necessary for monsters to
// morph but is kept here for compatibility. Its previous fields either exist
// in the base Actor now or are just a shell for the actual fields
// which either already existed and weren't used for some reason or needed a
// better name.
//
//===========================================================================
class MorphedMonster : Actor
{
Actor UnmorphedMe;
EMorphFlags MorphStyle;
EPremorphProperty FlagsSave;
Default
{
Monster;
+FLOORCLIP
-COUNTKILL
}
// Make sure changes to these are propogated correctly when unmorphing. This is only
// set for monsters since originally players and this class were the only ones
// that were considered valid for morphing.
override bool UndoMonsterMorph(bool force)
{
Alternative = UnmorphedMe;
MorphFlags = MorphStyle;
PremorphProperties = FlagsSave;
return super.UndoMonsterMorph(force);
}
}