//----------------------------------------------------------------------------- // // 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 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 playerClass, class monsterClass, int duration = 0, EMorphFlags style = 0, class morphFlash = "TeleportFog", class 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 type, int duration = 0, EMorphFlags style = 0, class morphFlash = "TeleportFog", class unmorphFlash = "TeleportFog") { return Morph(self, (class)(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 spawnType, int duration, EMorphFlags style, class enterFlash = "TeleportFog", class 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 PlayerClass; class 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); } }