gzdoom/wadsrc/static/zscript/actors/morph.zs
Boondorl 2c09a443b4 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.
2024-03-01 08:35:31 -05:00

315 lines
9.1 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()
{
if (MorphFlags & MRF_UNDOBYDEATH)
Unmorph(self, force: MorphFlags & 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 <= Level.Time && Unmorph(self);
}
//---------------------------------------------------------------------------
//
// 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 || !spawnType || bDontMorph || !bIsMonster)
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.ChangeTID(TID);
morphed.Special = Special;
for (int i; i < 5; ++i)
morphed.Args[i] = Args[i];
morphed.CopyFriendliness(self, true);
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.FlagsSave = morphMon.PremorphProperties;
}
ChangeTID(0);
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 (!UnmorphTime || !Alternative || bStayMorphed || Alternative.bStayMorphed)
return false;
Alternative.SetOrigin(Pos, false);
if (!force && (PremorphProperties & MPROP_SOLID))
{
bool altSolid = Alternative.bSolid;
bool isSolid = bSolid;
bool isTouchy = bTouchy;
Alternative.bSolid = true;
bSolid = bTouchy = false;
bool res = Alternative.TestMobjLocation();
Alternative.bSolid = altSolid;
bSolid = isSolid;
bTouchy = isTouchy;
// Didn't fit.
if (!res)
{
UnmorphTime = Level.Time + 5*TICRATE; // Next try in 5 seconds.
return false;
}
}
Actor unmorphed = Alternative;
if (!MorphInto(unmorphed))
return false;
PreUnmorph(unmorphed, false);
unmorphed.PreUnmorph(self, true);
unmorphed.Angle = Angle;
unmorphed.bShadow = bShadow;
unmorphed.bGhost = bGhost;
unmorphed.bSolid = (PremorphProperties & MPROP_SOLID);
unmorphed.bShootable = (PremorphProperties & MPROP_SHOOTABLE);
unmorphed.bInvisible = (PremorphProperties & MPROP_INVIS);
unmorphed.Vel = Vel;
unmorphed.Score = Score;
unmorphed.ChangeTID(TID);
unmorphed.Special = Special;
for (int i; i < 5; ++i)
unmorphed.Args[i] = Args[i];
unmorphed.CopyFriendliness(self, true);
ChangeTID(0);
Special = 0;
bInvisible = true;
bSolid = bShootable = false;
PostUnmorph(unmorphed, false); // From is false here: Leaving the caller's body.
unmorphed.PostUnmorph(self, true); // True here: Entering this body from here.
if (MorphExitFlash)
{
Actor fog = Spawn(MorphExitFlash, unmorphed.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE);
if (fog)
fog.Target = unmorphed;
}
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);
}
}