diff --git a/src/g_level.cpp b/src/g_level.cpp index 386df8d09..ff42656da 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1711,7 +1711,7 @@ int FLevelLocals::FinishTravel () pawn->flags2 &= ~MF2_BLASTED; if (oldpawn != nullptr) { - PlayerPointerSubstitution (oldpawn, pawn, true); + StaticPointerSubstitution (oldpawn, pawn); oldpawn->Destroy(); } if (pawndup != NULL) diff --git a/src/g_statusbar/sbar_mugshot.cpp b/src/g_statusbar/sbar_mugshot.cpp index 493124389..f1d43af28 100644 --- a/src/g_statusbar/sbar_mugshot.cpp +++ b/src/g_statusbar/sbar_mugshot.cpp @@ -479,7 +479,7 @@ FGameTexture *FMugShot::GetFace(player_t *player, const char *default_face, int if (CurrentState != NULL) { int skin = player->userinfo.GetSkin(); - const char *skin_face = (stateflags & FMugShot::CUSTOM) ? nullptr : (player->mo->alternative != nullptr ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars()); + const char *skin_face = (stateflags & FMugShot::CUSTOM) ? nullptr : (player->morphTics ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars()); return CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle); } return NULL; diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 7834f92d4..2ed4ddf62 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -626,13 +626,10 @@ public: double plyrdmgfact = Pawn->DamageFactor; Pawn->DamageFactor = 1.; P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide); - if (Pawn != nullptr) + Pawn->DamageFactor = plyrdmgfact; + if (Pawn->health <= 0) { - Pawn->DamageFactor = plyrdmgfact; - if (Pawn->health <= 0) - { - Pawn->flags &= ~MF_SHOOTABLE; - } + Pawn->flags &= ~MF_SHOOTABLE; } Destroy(); } diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index d5b0abe14..cb67e6e48 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -1138,7 +1138,7 @@ void FLevelLocals::UnSnapshotLevel(bool hubLoad) // If this isn't the unmorphed original copy of a player, destroy it, because it's extra. for (i = 0; i < MAXPLAYERS; ++i) { - if (PlayerInGame(i) && Players[i]->mo->alternative == pawn) + if (PlayerInGame(i) && Players[i]->morphTics && Players[i]->mo->alternative == pawn) { break; } diff --git a/src/playsim/a_morph.cpp b/src/playsim/a_morph.cpp index 5f6c38b91..80e6807af 100644 --- a/src/playsim/a_morph.cpp +++ b/src/playsim/a_morph.cpp @@ -37,7 +37,7 @@ bool P_MorphActor(AActor *activator, AActor *victim, PClassActor *ptype, PClassA bool P_UnmorphActor(AActor *activator, AActor *morphed, int flags, bool force) { - IFVIRTUALPTR(morphed, AActor, Unmorph) + IFVIRTUALPTR(morphed, AActor, UnMorph) { VMValue params[] = { morphed, activator, flags, force }; int retval; diff --git a/src/playsim/a_morph.h b/src/playsim/a_morph.h index 762cd01bb..00303533c 100644 --- a/src/playsim/a_morph.h +++ b/src/playsim/a_morph.h @@ -26,8 +26,6 @@ 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, }; diff --git a/src/playsim/actor.h b/src/playsim/actor.h index eac4b9f43..51e9eede4 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -1747,8 +1747,8 @@ struct FTranslatedLineTarget bool unlinked; // found by a trace that went through an unlinked portal. }; -void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer, bool removeOld); -int MorphPointerSubstitution(AActor* from, AActor* to); + +void StaticPointerSubstitution(AActor* old, AActor* notOld); #define S_FREETARGMOBJ 1 diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index 50ed1a74d..1b1d3f77c 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -3139,7 +3139,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_Pain) PARAM_SELF_PROLOGUE(AActor); // [RH] Vary player pain sounds depending on health (ala Quake2) - if (self->player && self->alternative == nullptr) + if (self->player && self->player->morphTics == 0) { const char *pain_amount; FSoundID sfx_id = NO_SOUND; diff --git a/src/playsim/p_interaction.cpp b/src/playsim/p_interaction.cpp index 86416f0c3..88326acb5 100644 --- a/src/playsim/p_interaction.cpp +++ b/src/playsim/p_interaction.cpp @@ -313,54 +313,36 @@ EXTERN_CVAR (Int, fraglimit) void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath) { + // Handle possible unmorph on death bool wasgibbed = (health < GetGibHealth()); - // Check to see if unmorph Actors need to be killed as well. Originally this was always - // called but that puts an unnecessary burden on the modder to determine whether it's - // a valid call or not. - if (alternative != nullptr && !(flags & MF_UNMORPHED)) { IFVIRTUAL(AActor, MorphedDeath) { - // Return values are no longer used to ensure things stay properly managed. - AActor* const realMo = alternative; - int morphStyle = 0; + AActor *realthis = NULL; + int realstyle = 0; + int realhealth = 0; VMValue params[] = { this }; + VMReturn returns[3]; + returns[0].PointerAt((void**)&realthis); + returns[1].IntAt(&realstyle); + returns[2].IntAt(&realhealth); + VMCall(func, params, 1, returns, 3); - { - 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 && !(realMo->flags6 & MF6_KILLED) - && ((alternative != nullptr && player == nullptr) || (alternative == nullptr && !(morphStyle & MORPH_UNDOBYDEATHSAVES)))) + if (realthis && !(realstyle & MORPH_UNDOBYDEATHSAVES)) { if (wasgibbed) { - const int realGibHealth = realMo->GetGibHealth(); - if (realMo->health >= realGibHealth) - realMo->health = realGibHealth - 1; // If morphed was gibbed, so must original be (where allowed). + int realgibhealth = realthis->GetGibHealth(); + if (realthis->health >= realgibhealth) + { + realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l + } } - else if (realMo->health > 0) - { - 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); + realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath); } + } } @@ -476,7 +458,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf ++source->player->spreecount; } - if (source->alternative != nullptr) + if (source->player->morphTics) { // Make a super chicken source->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2)); } @@ -1347,7 +1329,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (damage >= player->health && !telefragDamage && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) - && target->alternative == nullptr) + && !player->morphTics) { // Try to use some inventory health P_AutoUseHealth (player, damage - player->health + 1); } @@ -1481,7 +1463,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da // check for special fire damage or ice damage deaths if (mod == NAME_Fire) { - if (player && target->alternative == nullptr) + if (player && !player->morphTics) { // Check for flame death if (!inflictor || ((target->health > -50) && (damage > 25)) || @@ -1815,7 +1797,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain } if (damage >= player->health && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) - && target->alternative == nullptr) + && !player->morphTics) { // Try to use some inventory health P_AutoUseHealth(player, damage - player->health+1); } @@ -1845,7 +1827,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain else { target->special1 = damage; - if (player && target->alternative == nullptr) + if (player && !player->morphTics) { // Check for flame death if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25)) { diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 1887d7dc8..94a33868c 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -819,7 +819,7 @@ int P_GetRealMaxHealth(AActor *actor, int max) { max = actor->GetMaxHealth(true); // [MH] First step in predictable generic morph effects - if (actor->alternative != nullptr) + if (player->morphTics) { if (player->MorphStyle & MORPH_FULLHEALTH) { @@ -841,7 +841,7 @@ int P_GetRealMaxHealth(AActor *actor, int max) else { // Bonus health should be added on top of the item's limit. - if (actor->alternative == nullptr || (player->MorphStyle & MORPH_ADDSTAMINA)) + if (player->morphTics == 0 || (player->MorphStyle & MORPH_ADDSTAMINA)) { max += actor->IntVar(NAME_BonusHealth); } @@ -3788,22 +3788,6 @@ void AActor::Tick () static const uint8_t HereticScrollDirs[4] = { 6, 9, 1, 4 }; static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 }; - // Check for Actor unmorphing, but only on the thing that is the morphed Actor. - // Players do their own special checking for this. - if (alternative != nullptr && !(flags & MF_UNMORPHED) && player == nullptr) - { - int res = false; - IFVIRTUAL(AActor, CheckUnmorph) - { - VMValue params[] = { this }; - VMReturn ret[] = { &res }; - VMCall(func, params, 1, ret, 1); - } - - if (res) - return; - } - if (freezetics > 0) { freezetics--; @@ -5098,16 +5082,6 @@ void AActor::CallDeactivate(AActor *activator) void AActor::OnDestroy () { - // If the Actor is leaving behind a premorph Actor, make sure it gets cleaned up as - // well so it's not stuck in the map. - if (alternative != nullptr && !(flags & MF_UNMORPHED)) - { - alternative->ClearCounters(); - alternative->alternative = nullptr; - alternative->Destroy(); - alternative = nullptr; - } - // [ZZ] call destroy event hook. // note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook. // but you can't really do that without utterly breaking the game, so it's ok. @@ -5229,157 +5203,59 @@ extern bool demonew; //========================================================================== // -// This function is only designed for swapping player pawns -// over to their new ones upon changing levels or respawning. It SHOULD NOT be -// used for anything else! Do not export this functionality as it's -// meant strictly for internal usage. +// This once was the main method for pointer cleanup, but +// nowadays its only use is swapping out PlayerPawns. +// This requires pointer fixing throughout all objects and a few +// global variables, but it only needs to look at pointers that +// can point to a player. // //========================================================================== -void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer, bool removeOld) +void StaticPointerSubstitution(AActor* old, AActor* notOld) { - if (oldPlayer == nullptr || newPlayer == nullptr || oldPlayer == newPlayer - || !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn)) + DObject* probe; + size_t changed = 0; + int i; + + if (old == nullptr) return; + + // This is only allowed to replace players or swap out morphed monsters + if (!old->IsKindOf(NAME_PlayerPawn) || (notOld != nullptr && !notOld->IsKindOf(NAME_PlayerPawn))) { - return; + if (notOld == nullptr) return; + if (!old->IsKindOf(NAME_MorphedMonster) && !notOld->IsKindOf(NAME_MorphedMonster)) return; + } + // Go through all objects. + i = 0; DObject* last = 0; + for (probe = GC::Root; probe != NULL; probe = probe->ObjNext) + { + i++; + changed += probe->PointerSubstitution(old, notOld, true); + last = probe; } - // Go through player infos. - for (int i = 0; i < MAXPLAYERS; ++i) + // Go through players. + for (i = 0; i < MAXPLAYERS; i++) { - if (!oldPlayer->Level->PlayerInGame(i)) - continue; + if (playeringame[i]) + { + AActor* replacement = notOld; + auto& p = players[i]; - auto p = oldPlayer->Level->Players[i]; - - if (p->mo == oldPlayer) - p->mo = newPlayer; - if (p->poisoner == oldPlayer) - p->poisoner = newPlayer; - if (p->attacker == oldPlayer) - p->attacker = newPlayer; - if (p->camera == oldPlayer) - p->camera = newPlayer; - if (p->ConversationNPC == oldPlayer) - p->ConversationNPC = newPlayer; - if (p->ConversationPC == oldPlayer) - p->ConversationPC = newPlayer; + if (p.mo == old) p.mo = replacement, changed++; + if (p.poisoner.ForceGet() == old) p.poisoner = replacement, changed++; + if (p.attacker.ForceGet() == old) p.attacker = replacement, changed++; + if (p.camera.ForceGet() == old) p.camera = replacement, changed++; + if (p.ConversationNPC.ForceGet() == old) p.ConversationNPC = replacement, changed++; + if (p.ConversationPC.ForceGet() == old) p.ConversationPC = replacement, changed++; + } } - // Go through sectors. - for (auto& sec : oldPlayer->Level->sectors) + // Go through sectors. Only the level this actor belongs to is relevant. + for (auto& sec : old->Level->sectors) { - if (sec.SoundTarget == oldPlayer) - sec.SoundTarget = newPlayer; + if (sec.SoundTarget == old) sec.SoundTarget = notOld; } - - // Update all the remaining object pointers. - for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext) - probe->PointerSubstitution(oldPlayer, newPlayer, removeOld); -} - -//========================================================================== -// -// This has some extra barriers compared to PlayerPointerSubstitution to allow -// Actors to freely morph into other Actors which is its main usage. -// It also allows morphing to be more extendable from ZScript. -// -//========================================================================== - -int MorphPointerSubstitution(AActor* from, AActor* to) -{ - // Special care is taken here to make sure things marked as a dummy Actor for a morphed thing aren't - // allowed to be changed into other things. Anything being morphed into that's considered a player - // is automatically out of the question to ensure modders aren't swapping clients around. - if (from == nullptr || to == nullptr || from == to || to->player != nullptr - || (from->flags & MF_UNMORPHED) // Another thing's dummy Actor, unmorphing the wrong way, etc. - || (from->alternative == nullptr && to->alternative != nullptr) // Morphing into something that's already morphed. - || (from->alternative != nullptr && from->alternative != to)) // Only allow something morphed to unmorph. - { - return false; - } - - const bool toIsPlayer = to->IsKindOf(NAME_PlayerPawn); - if (from->IsKindOf(NAME_PlayerPawn)) - { - // Players are only allowed to turn into other valid player pawns. For - // valid pawns, make sure an actual player is changing into an empty one. - // Voodoo dolls aren't allowed to morph since that should be passed to - // the main player directly. - if (!toIsPlayer || from->player == nullptr || from->player->mo != from) - return false; - } - else if (toIsPlayer || from->player != nullptr - || (from->IsKindOf(NAME_Inventory) && from->PointerVar(NAME_Owner) != nullptr) - || (to->IsKindOf(NAME_Inventory) && to->PointerVar(NAME_Owner) != nullptr)) - { - // Only allow items to be swapped around if they aren't currently owned. Also prevent non-players from - // turning into fake players. - return false; - } - - // Since the check is good, move the inventory items over. This should always be done when - // morphing to emulate Heretic/Hexen's behavior since those stored the inventory in their - // player structs. - IFVM(Actor, ObtainInventory) - { - VMValue params[] = { to, from }; - VMCall(func, params, 2, nullptr, 0); - } - - // Go through player infos. - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (!from->Level->PlayerInGame(i)) - continue; - - auto p = from->Level->Players[i]; - - if (p->mo == from) - p->mo = to; - if (p->poisoner == from) - p->poisoner = to; - if (p->attacker == from) - p->attacker = to; - if (p->camera == from) - p->camera = to; - if (p->ConversationNPC == from) - p->ConversationNPC = to; - if (p->ConversationPC == from) - p->ConversationPC = to; - } - - // Go through sectors. - for (auto& sec : from->Level->sectors) - { - if (sec.SoundTarget == from) - sec.SoundTarget = to; - } - - // Replace any object pointers that are safe to swap around. - for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext) - probe->PointerSubstitution(from, to, false); - - // Remaining maintenance related to morphing. - if (from->player != nullptr) - { - to->player = from->player; - from->player = nullptr; - } - - if (from->alternative != nullptr) - { - to->flags &= ~MF_UNMORPHED; - to->alternative = from->alternative = nullptr; - } - else - { - from->flags |= MF_UNMORPHED; - from->alternative = to; - to->alternative = from; - } - - return true; } void FLevelLocals::PlayerSpawnPickClass (int playernum) @@ -5653,7 +5529,7 @@ AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flag if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr; } - PlayerPointerSubstitution (oldactor, p->mo, false); + StaticPointerSubstitution (oldactor, p->mo); localEventManager->PlayerRespawned(PlayerNum(p)); Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true); diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index 6839b412a..58f71beb7 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -693,7 +693,7 @@ bool player_t::Resurrect() P_BringUpWeapon(this); } - if (mo->alternative != nullptr) + if (morphTics) { P_UnmorphActor(mo, mo); } @@ -1172,7 +1172,7 @@ void P_CheckEnvironment(player_t *player) P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo)); } if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) && - player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && player->mo->alternative == nullptr && + player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && !player->morphTics && player->mo->waterlevel == 0) { auto id = S_FindSkinnedSound(player->mo, S_FindSound("*falling")); diff --git a/src/scripting/thingdef_properties.cpp b/src/scripting/thingdef_properties.cpp index bedd66cd3..fa7a5b13f 100644 --- a/src/scripting/thingdef_properties.cpp +++ b/src/scripting/thingdef_properties.cpp @@ -1814,15 +1814,6 @@ DEFINE_CLASS_PROPERTY(playerclass, S, PowerMorph) defaults->PointerVar(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate); } -//========================================================================== -// -//========================================================================== -DEFINE_CLASS_PROPERTY(monsterclass, S, PowerMorph) -{ - PROP_STRING_PARM(str, 0); - defaults->PointerVar(NAME_MonsterClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate); -} - //========================================================================== // //========================================================================== diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index 7094212e0..06a5c3b5c 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -1693,11 +1693,28 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_BossDeath, A_BossDeath) return 0; } -DEFINE_ACTION_FUNCTION_NATIVE(AActor, MorphInto, MorphPointerSubstitution) +DEFINE_ACTION_FUNCTION_NATIVE(AActor, Substitute, StaticPointerSubstitution) { PARAM_SELF_PROLOGUE(AActor); - PARAM_OBJECT(to, AActor); - ACTION_RETURN_INT(MorphPointerSubstitution(self, to)); + PARAM_OBJECT(replace, AActor); + StaticPointerSubstitution(self, replace); + return 0; +} + +DEFINE_ACTION_FUNCTION_NATIVE(_PlayerPawn, Substitute, StaticPointerSubstitution) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT(replace, AActor); + StaticPointerSubstitution(self, replace); + return 0; +} + +DEFINE_ACTION_FUNCTION_NATIVE(_MorphedMonster, Substitute, StaticPointerSubstitution) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT(replace, AActor); + StaticPointerSubstitution(self, replace); + return 0; } DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetSpawnableType, P_GetSpawnableType) diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 72f441b10..8994287e8 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -504,7 +504,7 @@ class Actor : Thinker native virtual native bool Slam(Actor victim); virtual void Touch(Actor toucher) {} virtual native void FallAndSink(double grav, double oldfloorz); - native bool MorphInto(Actor morph); + private native void Substitute(Actor replacement); native ui void DisplayNameTag(); native clearscope void DisableLocalRendering(uint playerNum, bool disable); native ui bool ShouldRenderLocally(); // Only clients get to check this, never the playsim. @@ -1410,7 +1410,7 @@ class Actor : Thinker native bool grunted; // [RH] only make noise if alive - if (self.health > 0 && !Alternative) + if (self.health > 0 && self.player.morphTics == 0) { grunted = false; // Why should this number vary by gravity? diff --git a/wadsrc/static/zscript/actors/heretic/chicken.zs b/wadsrc/static/zscript/actors/heretic/chicken.zs index a49418ca3..4bc2e20de 100644 --- a/wadsrc/static/zscript/actors/heretic/chicken.zs +++ b/wadsrc/static/zscript/actors/heretic/chicken.zs @@ -221,7 +221,7 @@ class ChickenPlayer : PlayerPawn pspr.y = WEAPONTOP + player.chickenPeck / 2; } } - if ((player.MorphTics ? player.MorphTics : Random[ChickenPlayerThink]()) & 15) + if (player.morphTics & 15) { return; } diff --git a/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs b/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs index b620b04b4..d12bf2b19 100644 --- a/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs +++ b/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs @@ -66,26 +66,28 @@ Class ArtiTomeOfPower : PowerupGiver Loop; } - override bool Use(bool pickup) + override bool Use (bool pickup) { - EMorphFlags mStyle = Owner.GetMorphStyle(); - if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER)) - { - // Attempt to undo chicken. - if (!Owner.Unmorph(Owner, MRF_UNDOBYTOMEOFPOWER)) - { - if (!(mStyle & MRF_FAILNOTELEFRAG)) - Owner.DamageMobj(null, null, TELEFRAG_DAMAGE, 'Telefrag'); + Playerinfo p = Owner.player; + if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYTOMEOFPOWER)) + { // Attempt to undo chicken + if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER)) + { // Failed + if (!(p.MorphStyle & MRF_FAILNOTELEFRAG)) + { + Owner.DamageMobj (null, null, TELEFRAG_DAMAGE, 'Telefrag'); + } } - else if (Owner.player) - { - Owner.A_StartSound("*evillaugh", CHAN_VOICE); + else + { // Succeeded + Owner.A_StartSound ("*evillaugh", CHAN_VOICE); } - return true; } - - return Super.Use(pickup); + else + { + return Super.Use (pickup); + } } } diff --git a/wadsrc/static/zscript/actors/hexen/pig.zs b/wadsrc/static/zscript/actors/hexen/pig.zs index 95e90722e..45c601c4e 100644 --- a/wadsrc/static/zscript/actors/hexen/pig.zs +++ b/wadsrc/static/zscript/actors/hexen/pig.zs @@ -145,7 +145,7 @@ class PigPlayer : PlayerPawn override void MorphPlayerThink () { - if ((player.MorphTics ? player.MorphTics : Random[PigPlayerThink]()) & 15) + if (player.morphTics & 15) { return; } diff --git a/wadsrc/static/zscript/actors/inventory/powerups.zs b/wadsrc/static/zscript/actors/inventory/powerups.zs index 76b0fea2d..9c4903d01 100644 --- a/wadsrc/static/zscript/actors/inventory/powerups.zs +++ b/wadsrc/static/zscript/actors/inventory/powerups.zs @@ -1870,14 +1870,13 @@ class PowerReflection : Powerup //=========================================================================== // // PowerMorph -// Now works with monsters too! // //=========================================================================== class PowerMorph : Powerup { - class PlayerClass; - class MonsterClass, MorphFlash, UnmorphFlash; + Class PlayerClass; + Class MorphFlash, UnMorphFlash; int MorphStyle; PlayerInfo MorphedPlayer; @@ -1896,19 +1895,19 @@ class PowerMorph : Powerup { Super.InitEffect(); - if (!Owner) - return; - - if (Owner.Morph(Owner, PlayerClass, MonsterClass, int.max, MorphStyle, MorphFlash, UnmorphFlash)) + if (Owner != null && Owner.player != null && PlayerClass != null) { - // Get the real owner; safe because we are not attached to anything yet. - Owner = Owner.Alternative; - bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor). - MorphedPlayer = Owner.player; - } - else - { - bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate). + let realplayer = Owner.player; // Remember the identity of the player + if (realplayer.mo.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash)) + { + Owner = realplayer.mo; // Replace the new owner in our owner; safe because we are not attached to anything yet + bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor) + MorphedPlayer = realplayer; // Store the player identity (morphing clears the unmorphed actor's "player" field) + } + else // morph failed - give the caller an opportunity to fail the pickup completely + { + bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate) + } } } @@ -1922,16 +1921,24 @@ class PowerMorph : Powerup { Super.EndEffect(); - // Abort if owner already destroyed or unmorphed. - if (!Owner || !Owner.Alternative) + // Abort if owner already destroyed or unmorphed + if (Owner == null || MorphedPlayer == null || Owner.alternative == null) + { return; + } // Abort if owner is dead; their Die() method will // take care of any required unmorphing on death. - if (Owner.player ? Owner.player.Health <= 0 : Owner.Health <= 0) + if (MorphedPlayer.health <= 0) + { return; + } - Owner.Unmorph(Owner, force: Owner.GetMorphStyle() & MRF_UNDOALWAYS); + int savedMorphTics = MorphedPlayer.morphTics; + MorphedPlayer.mo.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS)); MorphedPlayer = null; } + + } + diff --git a/wadsrc/static/zscript/actors/inventory/weapons.zs b/wadsrc/static/zscript/actors/inventory/weapons.zs index 66a7f8964..49e32f3a2 100644 --- a/wadsrc/static/zscript/actors/inventory/weapons.zs +++ b/wadsrc/static/zscript/actors/inventory/weapons.zs @@ -262,7 +262,7 @@ class Weapon : StateProvider } let psp = player.GetPSprite(PSP_WEAPON); if (!psp) return; - if (Alternative || player.cheats & CF_INSTANTWEAPSWITCH) + if (player.morphTics || player.cheats & CF_INSTANTWEAPSWITCH) { psp.y = WEAPONBOTTOM; } diff --git a/wadsrc/static/zscript/actors/morph.zs b/wadsrc/static/zscript/actors/morph.zs index b9fdbb101..1dcfb31fa 100644 --- a/wadsrc/static/zscript/actors/morph.zs +++ b/wadsrc/static/zscript/actors/morph.zs @@ -23,78 +23,8 @@ 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_NO_BLOCKMAP = 1 << 3, - MPROP_NO_SECTOR = 1 << 4, - MPROP_NO_INTERACTION = 1 << 5, - MPROP_INVIS = 1 << 6, - } - - int UnmorphTime; - EMorphFlags MorphFlags; - class 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 flash) - { - if (player) - player.MorphExitFlash = flash; - else - MorphExitFlash = flash; - } - - clearscope class 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. virtual Actor, int, int MorphedDeath() { - EMorphFlags mStyle = GetMorphStyle(); - if (mStyle & MRF_UNDOBYDEATH) - Unmorph(self, force: mStyle & MRF_UNDOBYDEATHFORCED); - return null, 0, 0; } @@ -110,12 +40,16 @@ extend class Actor // //=========================================================================== - virtual bool Morph(Actor activator, class playerClass, class monsterClass, int duration = 0, EMorphFlags style = 0, class morphFlash = "TeleportFog", class unmorphFlash = "TeleportFog") + virtual bool Morph(Actor activator, class playerclass, class monsterclass, int duration = 0, int style = 0, class morphflash = null, classunmorphflash = null) { - if (player) - return player.mo.MorphPlayer(activator ? activator.player : null, playerClass, duration, style, morphFlash, unmorphFlash); - - return MorphMonster(monsterClass, duration, style, morphFlash, unmorphFlash); + if (player != null && player.mo != null && playerclass != null) + { + return player.mo.MorphPlayer(activator? activator.player : null, playerclass, duration, style, morphflash, unmorphflash); + } + else + { + return MorphMonster(monsterclass, duration, style, morphflash, unmorphflash); + } } //=========================================================================== @@ -124,9 +58,18 @@ extend class Actor // //=========================================================================== - bool A_Morph(class type, int duration = 0, EMorphFlags style = 0, class morphFlash = "TeleportFog", class unmorphFlash = "TeleportFog") + bool A_Morph(class type, int duration = 0, int style = 0, class morphflash = null, classunmorphflash = null) { - return Morph(self, (class)(type), type, duration, style, morphFlash, unmorphFlash); + if (self.player != null) + { + let playerclass = (class)(type); + if (playerclass && self.player.mo != null) return player.mo.MorphPlayer(self.player, playerclass, duration, style, morphflash, unmorphflash); + } + else + { + return MorphMonster(type, duration, style, morphflash, unmorphflash); + } + return false; } //=========================================================================== @@ -135,18 +78,21 @@ extend class Actor // //=========================================================================== - virtual bool Unmorph(Actor activator, EMorphFlags flags = 0, bool force = false) + virtual bool UnMorph(Actor activator, int flags, bool force) { if (player) - return player.mo.UndoPlayerMorph(activator ? activator.player : null, flags, force); - - return UndoMonsterMorph(force); + { + return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force); + } + else + { + let morphed = MorphedMonster(self); + if (morphed) + return morphed.UndoMonsterMorph(force); + } + return false; } - virtual bool CheckUnmorph() - { - return UnmorphTime && UnmorphTime <= Level.Time && Unmorph(self, MRF_UNDOBYTIMEOUT); - } //--------------------------------------------------------------------------- // @@ -156,265 +102,57 @@ extend class Actor // //--------------------------------------------------------------------------- - virtual bool MorphMonster(class spawnType, int duration, EMorphFlags style, class enterFlash = "TeleportFog", class exitFlash = "TeleportFog") + virtual bool MorphMonster (Class spawntype, int duration, int style, Class enter_flash, Class exit_flash) { - if (player || !bIsMonster || !spawnType || bDontMorph || Health <= 0 || spawnType == GetClass()) - return false; - - Actor morphed = Spawn(spawnType, Pos, ALLOW_REPLACE); - if (!MorphInto(morphed)) + if (player || spawntype == NULL || bDontMorph || !bIsMonster || !(spawntype is 'MorphedMonster')) { - if (morphed) - { - morphed.ClearCounters(); - morphed.Destroy(); - } - return false; } + let morphed = MorphedMonster(Spawn (spawntype, Pos, NO_REPLACE)); + // [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. + Substitute (morphed); if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) + { morphed.Translation = Translation; - + } + morphed.ChangeTid(tid); + ChangeTid(0); morphed.Angle = Angle; + morphed.UnmorphedMe = self; morphed.Alpha = Alpha; morphed.RenderStyle = RenderStyle; + morphed.Score = Score; + + morphed.UnmorphTime = level.time + ((duration) ? duration : DEFMORPHTICS) + random[morphmonst](); + morphed.MorphStyle = style; + morphed.MorphExitFlash = (exit_flash) ? exit_flash : (class)("TeleportFog"); + morphed.FlagsSave = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility + + morphed.special = special; + morphed.args[0] = args[0]; + morphed.args[1] = args[1]; + morphed.args[2] = args[2]; + morphed.args[3] = args[3]; + morphed.args[4] = args[4]; + morphed.CopyFriendliness (self, true); 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. - if (!(style & MRF_KEEPARMOR)) - { - for (Inventory item = morphed.Inv; item;) - { - Inventory next = item.Inv; - if (item is "Armor") - item.DepleteOrDestroy(); - - item = next; - } - } - - 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.GetMorphStyle(); - morphMon.FlagsSave = morphMon.PremorphProperties; - } - - Special = 0; - bNoInteraction = true; - A_ChangeLinkFlags(true, true); - - // Legacy + special = 0; + bSolid = false; + bShootable = false; + bUnmorphed = true; bInvisible = true; - bSolid = bShootable = false; - + let eflash = Spawn(enter_flash ? enter_flash : (class)("TeleportFog"), Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE); + if (eflash) + eflash.target = morphed; 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) - { - SetMorphTics(5 * TICRATE); - 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; - - 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); - } - - 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 || (style & MRF_UNDOBYDEATHSAVES)) - alt.Health = alt.SpawnHealth(); - else - alt.Health = Health; - - Special = 0; - - 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 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; - SetMorphStyle(MorphStyle); - PremorphProperties = FlagsSave; - return super.UndoMonsterMorph(force); - } -} diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index e6c66fa44..c5eee1cc9 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -40,7 +40,7 @@ class PlayerPawn : Actor double SideMove1, SideMove2; TextureID ScoreIcon; int SpawnMask; - Name MorphWeapon; // This should really be a class but it's too late to change now. + Name MorphWeapon; double AttackZOffset; // attack height, relative to player center double UseRange; // [NS] Distance at which player can +use double AirCapacity; // Multiplier for air supply underwater. @@ -477,7 +477,7 @@ class PlayerPawn : Actor let player = self.player; if (!player) return; if ((player.WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled. - Alternative) // Morphed classes cannot change weapons. + player.morphTics != 0) // Morphed classes cannot change weapons. { // ...so throw away any pending weapon requests. player.PendingWeapon = WP_NOCHANGE; } @@ -651,7 +651,7 @@ class PlayerPawn : Actor } } - if (Alternative) + if (player.morphTics) { bob = 0; } @@ -1084,7 +1084,7 @@ class PlayerPawn : Actor virtual bool CanCrouch() const { - return !Alternative || bCrouchableMorph; + return player.morphTics == 0 || bCrouchableMorph; } //---------------------------------------------------------------------------- @@ -1250,7 +1250,7 @@ class PlayerPawn : Actor side *= SideMove2; } - if (!Alternative) + if (!player.morphTics) { double factor = 1.; for(let it = Inv; it != null; it = it.Inv) @@ -1546,15 +1546,15 @@ class PlayerPawn : Actor { let player = self.player; // Morph counter - if (Alternative) + if (player.morphTics) { if (player.chickenPeck) { // Chicken attack counter player.chickenPeck -= 3; } - if (player.MorphTics && !--player.MorphTics) + if (!--player.morphTics) { // Attempt to undo the chicken/pig - Unmorph(self, MRF_UNDOBYTIMEOUT); + player.mo.UndoPlayerMorph(player, MRF_UNDOBYTIMEOUT); } } } @@ -1679,7 +1679,7 @@ class PlayerPawn : Actor player.jumpTics = 0; } } - if (Alternative && !(player.cheats & CF_PREDICTING)) + if (player.morphTics && !(player.cheats & CF_PREDICTING)) { MorphPlayerThink (); } @@ -2060,9 +2060,9 @@ class PlayerPawn : Actor Inventory item, next; let p = player; - if (Alternative) + if (p.morphTics != 0) { // Undo morph - Unmorph(self, force: true); + Unmorph(self, 0, true); } // 'self' will be no longer valid from here on in case of an unmorph let me = p.mo; @@ -2869,15 +2869,23 @@ struct PlayerInfo native play // self is what internally is known as player_t native clearscope bool HasWeaponsInSlot(int slot) const; // The actual implementation is on PlayerPawn where it can be overridden. Use that directly in the future. - deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(PlayerInfo activator, class spawnType, int duration, EMorphFlags style, class enterFlash = "TeleportFog", class exitFlash = "TeleportFog") + deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(playerinfo p, Class spawntype, int duration, int style, Class enter_flash = null, Class exit_flash = null) { - return mo ? mo.MorphPlayer(activator, spawnType, duration, style, enterFlash, exitFlash) : false; + if (mo != null) + { + return mo.MorphPlayer(p, spawntype, duration, style, enter_flash, exit_flash); + } + return false; } // This somehow got its arguments mixed up. 'self' should have been the player to be unmorphed, not the activator - deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(PlayerInfo player, EMorphFlags unmorphFlags = 0, bool force = false) + deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(playerinfo player, int unmorphflag = 0, bool force = false) { - return player.mo ? player.mo.UndoPlayerMorph(self, unmorphFlags, force) : false; + if (player.mo != null) + { + return player.mo.UndoPlayerMorph(self, unmorphflag, force); + } + return false; } deprecated("3.7", "DropWeapon() should be used on a PlayerPawn object") void DropWeapon() diff --git a/wadsrc/static/zscript/actors/player/player_cheat.zs b/wadsrc/static/zscript/actors/player/player_cheat.zs index c89832dca..adacdfb52 100644 --- a/wadsrc/static/zscript/actors/player/player_cheat.zs +++ b/wadsrc/static/zscript/actors/player/player_cheat.zs @@ -426,40 +426,35 @@ extend class PlayerPawn } - virtual String CheatMorph(class morphClass, bool undo) + virtual String CheatMorph(class morphClass, bool quickundo) { - // Set the standard morph style for the current game - EMorphFlags style = MRF_UNDOBYTOMEOFPOWER; - if (GameInfo.GameType == GAME_Hexen) - style |= MRF_UNDOBYCHAOSDEVICE; + let oldclass = GetClass(); - if (Alternative) + // Set the standard morph style for the current game + int style = MRF_UNDOBYTOMEOFPOWER; + if (gameinfo.gametype == GAME_Hexen) style |= MRF_UNDOBYCHAOSDEVICE; + + if (player.morphTics) { - class cls = GetClass(); - Actor mo = Alternative; - if (!undo || Unmorph(self)) + if (UndoPlayerMorph (player)) { - if ((!undo && Morph(self, morphClass, null, 0, style)) - || (undo && morphClass != cls && mo.Morph(mo, morphClass, null, 0, style))) + if (!quickundo && oldclass != morphclass && MorphPlayer (player, morphclass, 0, style)) { return StringTable.Localize("$TXT_STRANGER"); } - - if (undo) - return StringTable.Localize("$TXT_NOTSTRANGE"); + return StringTable.Localize("$TXT_NOTSTRANGE"); } } - else if (Morph(self, morphClass, null, 0, style)) + else if (MorphPlayer (player, morphclass, 0, style)) { return StringTable.Localize("$TXT_STRANGE"); } - return ""; } virtual void CheatTakeWeaps() { - if (Alternative || health <= 0) + if (player.morphTics || health <= 0) { return; } diff --git a/wadsrc/static/zscript/actors/player/player_morph.zs b/wadsrc/static/zscript/actors/player/player_morph.zs index ff84246a1..77df1ef26 100644 --- a/wadsrc/static/zscript/actors/player/player_morph.zs +++ b/wadsrc/static/zscript/actors/player/player_morph.zs @@ -1,25 +1,26 @@ extend class PlayerPawn { + private native void Substitute(PlayerPawn replacement); + //=========================================================================== // - // InitAllPowerupEffects + // EndAllPowerupEffects // - // Calls InitEffect() on every Powerup in the inventory list. Since these - // functions can be overridden it's safest to store what's next in the item - // list before calling it. + // Calls EndEffect() on every Powerup in the inventory list. // //=========================================================================== void InitAllPowerupEffects() { - for (Inventory item = Inv; item;) + let item = Inv; + while (item != null) { - Inventory next = item.Inv; let power = Powerup(item); - if (power) + if (power != null) + { power.InitEffect(); - - item = next; + } + item = item.Inv; } } @@ -33,14 +34,15 @@ extend class PlayerPawn void EndAllPowerupEffects() { - for (Inventory item = Inv; item;) + let item = Inv; + while (item != null) { - Inventory next = item.Inv; let power = Powerup(item); - if (power) + if (power != null) + { power.EndEffect(); - - item = next; + } + item = item.Inv; } } @@ -50,36 +52,46 @@ extend class PlayerPawn // //=========================================================================== - virtual void ActivateMorphWeapon() + virtual void ActivateMorphWeapon () { - if (player.ReadyWeapon) + class morphweaponcls = MorphWeapon; + player.PendingWeapon = WP_NOCHANGE; + + if (player.ReadyWeapon != null) { let psp = player.GetPSprite(PSP_WEAPON); - psp.y = WEAPONTOP; - player.ReadyWeapon.ResetPSprite(psp); + if (psp) + { + psp.y = WEAPONTOP; + player.ReadyWeapon.ResetPSprite(psp); + } } - - class morphWeapCls = MorphWeapon; - if (!morphWeapCls) - { + + if (morphweaponcls == null || !(morphweaponcls is 'Weapon')) + { // No weapon at all while morphed! player.ReadyWeapon = null; } else { - player.ReadyWeapon = Weapon(FindInventory(morphWeapCls)); - if (!player.ReadyWeapon) + player.ReadyWeapon = Weapon(FindInventory (morphweaponcls)); + if (player.ReadyWeapon == null) { - player.ReadyWeapon = Weapon(GiveInventoryType(morphWeapCls)); - if (player.ReadyWeapon) - player.ReadyWeapon.GivenAsMorphWeapon = true; // Flag is used only by new morphWeap semantics in UndoPlayerMorph + player.ReadyWeapon = Weapon(GiveInventoryType (morphweaponcls)); + if (player.ReadyWeapon != null) + { + player.ReadyWeapon.GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in UndoPlayerMorph + } + } + if (player.ReadyWeapon != null) + { + player.SetPsprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState()); } - - if (player.ReadyWeapon) - player.SetPSprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState()); } - if (player.ReadyWeapon) - player.SetPSprite(PSP_FLASH, null); + if (player.ReadyWeapon != null) + { + player.SetPsprite(PSP_FLASH, null); + } player.PendingWeapon = WP_NOCHANGE; } @@ -95,122 +107,125 @@ extend class PlayerPawn // //--------------------------------------------------------------------------- - virtual bool MorphPlayer(PlayerInfo activator, class spawnType, int duration, EMorphFlags style, class enterFlash = "TeleportFog", class exitFlash = "TeleportFog") + virtual bool MorphPlayer(playerinfo activator, Class spawntype, int duration, int style, Class enter_flash = null, Class exit_flash = null) { - if (!player || !spawnType || bDontMorph || player.Health <= 0 - || (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE)))) + if (bDontMorph) { return false; } - - if (!duration) - duration = DEFMORPHTICS; - - if (spawnType == GetClass()) - { - // Player is already a beast. - if (Alternative && bCanSuperMorph - && GetMorphTics() < duration - TICRATE - && !FindInventory("PowerWeaponLevel2", true)) - { - // Make a super chicken. - GiveInventoryType("PowerWeaponLevel2"); + if (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))) + { // Immune when invulnerable unless this is a power we activated + return false; + } + if (player.morphTics) + { // Player is already a beast + if ((GetClass() == spawntype) && bCanSuperMorph + && (player.morphTics < (((duration) ? duration : DEFMORPHTICS) - TICRATE)) + && FindInventory('PowerWeaponLevel2', true) == null) + { // Make a super chicken + GiveInventoryType ('PowerWeaponLevel2'); } - return false; } - - let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE)); - if (!MorphInto(morphed)) + if (health <= 0) + { // Dead players cannot morph + return false; + } + if (spawntype == null) + { + return false; + } + if (!(spawntype is 'PlayerPawn')) + { + return false; + } + if (spawntype == GetClass()) { - if (morphed) - morphed.Destroy(); - return false; } + let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE)); + + // Use GetClass in the event someone actually allows replacements. PreMorph(morphed, false); morphed.PreMorph(self, true); - morphed.EndAllPowerupEffects(); - + EndAllPowerupEffects(); + Substitute(morphed); if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) + { morphed.Translation = Translation; - + } + if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR)) + { + morphed.ChangeTid(tid); + ChangeTid(0); + } morphed.Angle = Angle; - morphed.Pitch = Pitch; // Allow pitch here since mouse look in GZDoom is far more common than Heretic/Hexen. - morphed.Target = Target; - morphed.Tracer = Tracer; - morphed.Master = Master; + morphed.target = target; + morphed.tracer = tracer; + morphed.alternative = self; morphed.FriendPlayer = FriendPlayer; morphed.DesignatedTeam = DesignatedTeam; morphed.Score = Score; - morphed.ScoreIcon = ScoreIcon; - morphed.Health = morphed.SpawnHealth(); - if (TID && (style & MRF_NEWTIDBEHAVIOUR)) - { - morphed.ChangeTid(TID); - ChangeTid(0); + player.PremorphWeapon = player.ReadyWeapon; + + morphed.special2 = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility + morphed.player = player; + + if (morphed.ViewHeight > player.viewheight && player.deltaviewheight == 0) + { // If the new view height is higher than the old one, start moving toward it. + player.deltaviewheight = player.GetDeltaViewHeight(); } - - // special2 is no longer used here since Actors now have a proper field for it. - 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. - if (!(style & MRF_KEEPARMOR)) - { - for (Inventory item = morphed.Inv; item;) - { - Inventory next = item.Inv; - if (item is "Armor") - item.DepleteOrDestroy(); - - 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; - morphed.SetMorphTics(duration); - morphed.SetMorphStyle(style); - morphed.SetMorphExitFlash(exitFlash); - p.MorphedPlayerClass = spawnType; - p.PremorphWeapon = p.ReadyWeapon; - p.Health = morphed.Health; - p.Vel = (0.0, 0.0); - // If the new view height is higher than the old one, start moving toward it. - if (morphed.ViewHeight > p.ViewHeight && !p.DeltaViewHeight) - p.DeltaViewHeight = p.GetDeltaViewHeight(); - - bNoInteraction = true; - A_ChangeLinkFlags(true, true); - - // Legacy - bSolid = bShootable = false; + if (enter_flash == null) enter_flash = 'TeleportFog'; + let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE); + let p = player; + player = null; + alternative = morphed; + bSolid = false; + bShootable = false; + bUnmorphed = true; bInvisible = true; + + p.morphTics = (duration) ? duration : DEFMORPHTICS; - morphed.ClearFOVInterpolation(); + // [MH] Used by SBARINFO to speed up face drawing + p.MorphedPlayerClass = spawntype; + + p.MorphStyle = style; + if (exit_flash == null) exit_flash = 'TeleportFog'; + p.MorphExitFlash = exit_flash; + p.health = morphed.health; + p.mo = morphed; + p.vel = (0, 0); + morphed.ObtainInventory (self); + // Remove all armor + for (Inventory item = morphed.Inv; item != null; ) + { + let next = item.Inv; + if (item is 'Armor') + { + item.DepleteOrDestroy(); + } + item = next; + } morphed.InitAllPowerupEffects(); - morphed.ActivateMorphWeapon(); - + morphed.ActivateMorphWeapon (); + if (p.camera == self) // can this happen? + { + p.camera = morphed; + } + morphed.ClearFOVInterpolation(); + morphed.ScoreIcon = ScoreIcon; // [GRB] + if (eflash) + eflash.target = morphed; PostMorph(morphed, false); // No longer the current body morphed.PostMorph(self, true); // This is the current body - - if (enterFlash) - { - Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TelefogHeight), ALLOW_REPLACE); - if (fog) - fog.Target = morphed; - } - return true; } @@ -220,156 +235,400 @@ extend class PlayerPawn // //---------------------------------------------------------------------------- - virtual bool UndoPlayerMorph(PlayerInfo activator, EMorphFlags unmorphFlags = 0, bool force = false) + virtual bool UndoPlayerMorph(playerinfo activator, int unmorphflag = 0, bool force = false) { - if (!Alternative || bStayMorphed || Alternative.bStayMorphed) - return false; - - if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable - && (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING)))) + if (alternative == null) { return false; } - let alt = PlayerPawn(Alternative); - alt.SetOrigin(Pos, false); - // Test if there's room to unmorph. - if (!force && (PremorphProperties & MPROP_SOLID)) - { - bool altSolid = alt.bSolid; - bool isSolid = bSolid; - bool isTouchy = bTouchy; + let player = self.player; + bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag); - alt.bSolid = true; - bSolid = bTouchy = false; - - bool res = alt.TestMobjLocation(); - - alt.bSolid = altSolid; - bSolid = isSolid; - bTouchy = isTouchy; - - if (!res) - { - SetMorphTics(2 * TICRATE); - return false; - } + if ((bInvulnerable) // If the player is invulnerable + && ((player != activator) // and either did not decide to unmorph, + || (!((player.MorphStyle & MRF_WHENINVULNERABLE) // or the morph style does not allow it + || (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it), + { // Then the player is immune to the unmorph. + return false; } - if (!MorphInto(alt)) + let altmo = PlayerPawn(alternative); + altmo.SetOrigin (Pos, false); + altmo.bSolid = true; + bSolid = false; + if (!force && !altmo.TestMobjLocation()) + { // Didn't fit + altmo.bSolid = false; + bSolid = true; + player.morphTics = 2*TICRATE; return false; + } - PreUnmorph(alt, false); // This body's about to be left. - alt.PreUnmorph(self, true); // This one's about to become current. + PreUnmorph(altmo, false); // This body's about to be left. + altmo.PreUnmorph(self, true); // This one's about to become current. - alt.EndAllPowerupEffects(); + // No longer using tracer as morph storage. That is what 'alternative' is for. + // If the tracer has changed on the morph, change the original too. + altmo.target = target; + altmo.tracer = tracer; + self.player = null; + altmo.alternative = alternative = null; // Remove the morph power if the morph is being undone prematurely. - for (Inventory item = alt.Inv; item;) + for (Inventory item = Inv; item != null;) { - Inventory next = item.Inv; + let next = item.Inv; if (item is "PowerMorph") + { item.Destroy(); - + } item = next; } - - alt.Angle = Angle; - alt.Pitch = Pitch; - alt.Target = Target; - alt.Tracer = Tracer; - alt.Master = Master; - alt.FriendPlayer = FriendPlayer; - alt.DesignatedTeam = DesignatedTeam; - alt.Score = Score; - alt.ScoreIcon = ScoreIcon; - alt.ReactionTime = 18; - alt.bSolid = (PremorphProperties & MPROP_SOLID); - alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE); - alt.bInvisible = (PremorphProperties & MPROP_INVIS); - alt.bShadow = bShadow; - alt.bNoGravity = bNoGravity; - alt.bGhost = bGhost; - 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 exitFlash = alt.GetMorphExitFlash(); - EMorphFlags style = alt.GetMorphStyle(); - Weapon premorphWeap = p.PremorphWeapon; - - if (TID && (style & MRF_NEWTIDBEHAVIOUR)) + EndAllPowerupEffects(); + altmo.ObtainInventory (self); + Substitute(altmo); + if ((tid != 0) && (player.MorphStyle & MRF_NEWTIDBEHAVIOUR)) { - alt.ChangeTid(TID); - ChangeTID(0); + altmo.ChangeTid(tid); + } + altmo.Angle = Angle; + altmo.player = player; + altmo.reactiontime = 18; + altmo.bSolid = !!(special2 & 2); + altmo.bShootable = !!(special2 & 4); + altmo.bInvisible = !!(special2 & 0x40); + altmo.Vel = (0, 0, Vel.Z); + player.Vel = (0, 0); + altmo.floorz = floorz; + altmo.bShadow = bShadow; + altmo.bNoGravity = bNoGravity; + altmo.bGhost = bGhost; + altmo.bUnmorphed = false; + altmo.Score = Score; + altmo.InitAllPowerupEffects(); + + let exit_flash = player.MorphExitFlash; + bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON); + bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES); + + player.morphTics = 0; + player.MorphedPlayerClass = null; + player.MorphStyle = 0; + player.MorphExitFlash = null; + player.viewheight = altmo.ViewHeight; + Inventory level2 = altmo.FindInventory("PowerWeaponLevel2", true); + if (level2 != null) + { + level2.Destroy (); } - alt.SetMorphTics(0); - alt.SetMorphStyle(0); - alt.SetMorphExitFlash(null); - p.MorphedPlayerClass = null; - p.PremorphWeapon = null; - p.ViewHeight = alt.ViewHeight; - p.Vel = (0.0, 0.0); - if (p.Health > 0 || (style & MRF_UNDOBYDEATHSAVES)) - p.Health = alt.Health = alt.SpawnHealth(); - else - alt.Health = p.Health; - - Inventory level2 = alt.FindInventory("PowerWeaponLevel2", true); - if (level2) - level2.Destroy(); - - let morphWeap = p.ReadyWeapon; - if (premorphWeap) + if ((player.health > 0) || undobydeathsaves) { - premorphWeap.PostMorphWeapon(); + player.health = altmo.health = altmo.SpawnHealth(); } - else + else // killed when morphed so stay dead { - p.ReadyWeapon = null; - p.PendingWeapon = WP_NOCHANGE; - p.Refire = 0; + altmo.health = player.health; } - if (style & MRF_LOSEACTUALWEAPON) + player.mo = altmo; + if (player.camera == self) { - // Improved "lose morph weapon" semantics. - class morphWeapCls = MorphWeapon; - if (morphWeapCls) + player.camera = altmo; + } + altmo.ClearFOVInterpolation(); + + // [MH] + // If the player that was morphed is the one + // taking events, reset up the face, if any; + // this is only needed for old-skool skins + // and for the original DOOM status bar. + if (player == players[consoleplayer]) + { + if (face != 'None') { - let originalMorphWeapon = Weapon(alt.FindInventory(morphWeapCls)); - if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon) - originalMorphWeapon.Destroy(); + // Assume root-level base skin to begin with + let skinindex = 0; + let skin = player.GetSkin(); + // If a custom skin was in use, then reload it + // or else the base skin for the player class. + if (skin >= PlayerClasses.Size () && skin < PlayerSkins.Size()) + { + skinindex = skin; + } + else if (PlayerClasses.Size () > 1) + { + let whatami = altmo.GetClass(); + for (int i = 0; i < PlayerClasses.Size (); ++i) + { + if (PlayerClasses[i].Type == whatami) + { + skinindex = i; + break; + } + } + } } } - else if (morphWeap) // Old behaviour (not really useful now). + + Actor eflash = null; + if (exit_flash != null) { - morphWeap.Destroy(); + eflash = Spawn(exit_flash, Vec3Angle(20., altmo.Angle, gameinfo.telefogheight), ALLOW_REPLACE); + if (eflash) eflash.target = altmo; } - - // Reset the base AC of the player's Hexen armor back to its default. - let hexArmor = HexenArmor(alt.FindInventory("HexenArmor")); - if (hexArmor) - hexArmor.Slots[4] = alt.HexenArmor[0]; - - alt.ClearFOVInterpolation(); - alt.InitAllPowerupEffects(); - - PostUnmorph(alt, false); // This body is no longer current. - alt.PostUnmorph(self, true); // altmo body is current. - - if (exitFlash) + WeaponSlots.SetupWeaponSlots(altmo); // Use original class's weapon slots. + let beastweap = player.ReadyWeapon; + if (player.PremorphWeapon != null) { - Actor fog = Spawn(exitFlash, alt.Vec3Angle(20.0, alt.Angle, GameInfo.TelefogHeight), ALLOW_REPLACE); - if (fog) - fog.Target = alt; + player.PremorphWeapon.PostMorphWeapon (); + } + else + { + player.ReadyWeapon = player.PendingWeapon = null; + } + if (correctweapon) + { // Better "lose morphed weapon" semantics + class morphweaponcls = MorphWeapon; + if (morphweaponcls != null && morphweaponcls is 'Weapon') + { + let OriginalMorphWeapon = Weapon(altmo.FindInventory (morphweapon)); + if ((OriginalMorphWeapon != null) && (OriginalMorphWeapon.GivenAsMorphWeapon)) + { // You don't get to keep your morphed weapon. + if (OriginalMorphWeapon.SisterWeapon != null) + { + OriginalMorphWeapon.SisterWeapon.Destroy (); + } + OriginalMorphWeapon.Destroy (); + } + } + } + else // old behaviour (not really useful now) + { // Assumptions made here are no longer valid + if (beastweap != null) + { // You don't get to keep your morphed weapon. + if (beastweap.SisterWeapon != null) + { + beastweap.SisterWeapon.Destroy (); + } + beastweap.Destroy (); + } + } + PostUnmorph(altmo, false); // This body is no longer current. + altmo.PostUnmorph(self, true); // altmo body is current. + Destroy (); + // Restore playerclass armor to its normal amount. + let hxarmor = HexenArmor(altmo.FindInventory('HexenArmor')); + if (hxarmor != null) + { + hxarmor.Slots[4] = altmo.HexenArmor[0]; } - - Destroy(); return true; } + + //=========================================================================== + // + // + // + //=========================================================================== + + override Actor, int, int MorphedDeath() + { + // Voodoo dolls should not unmorph the real player here. + if (player && (player.mo == self) && + (player.morphTics) && + (player.MorphStyle & MRF_UNDOBYDEATH) && + (alternative)) + { + Actor realme = alternative; + int realstyle = player.MorphStyle; + int realhealth = health; + if (UndoPlayerMorph(player, 0, !!(player.MorphStyle & MRF_UNDOBYDEATHFORCED))) + { + return realme, realstyle, realhealth; + } + } + return null, 0, 0; + } } + +//=========================================================================== +// +// +// +//=========================================================================== + +class MorphProjectile : Actor +{ + + Class PlayerClass; + Class MonsterClass, MorphFlash, UnMorphFlash; + int Duration, MorphStyle; + + Default + { + Damage 1; + Projectile; + -ACTIVATEIMPACT + -ACTIVATEPCROSS + } + + override int DoSpecialDamage (Actor target, int damage, Name damagetype) + { + if (target.player) + { + // Voodoo dolls forward this to the real player + target.player.mo.MorphPlayer (NULL, PlayerClass, Duration, MorphStyle, MorphFlash, UnMorphFlash); + } + else + { + target.MorphMonster (MonsterClass, Duration, MorphStyle, MorphFlash, UnMorphFlash); + } + return -1; + } + + +} + +//=========================================================================== +// +// +// +//=========================================================================== + +class MorphedMonster : Actor +{ + Actor UnmorphedMe; + int UnmorphTime, MorphStyle; + Class MorphExitFlash; + int FlagsSave; + + Default + { + Monster; + -COUNTKILL + +FLOORCLIP + } + + private native void Substitute(Actor replacement); + + override void OnDestroy () + { + if (UnmorphedMe != NULL) + { + UnmorphedMe.Destroy (); + } + Super.OnDestroy(); + } + + override void Die (Actor source, Actor inflictor, int dmgflags, Name MeansOfDeath) + { + Super.Die (source, inflictor, dmgflags, MeansOfDeath); + if (UnmorphedMe != NULL && UnmorphedMe.bUnmorphed) + { + UnmorphedMe.health = health; + UnmorphedMe.Die (source, inflictor, dmgflags, MeansOfDeath); + } + } + + override void Tick () + { + if (UnmorphTime > level.time || !UndoMonsterMorph()) + { + Super.Tick(); + } + } + + //---------------------------------------------------------------------------- + // + // FUNC P_UndoMonsterMorph + // + // Returns true if the monster unmorphs. + // + //---------------------------------------------------------------------------- + + virtual bool UndoMonsterMorph(bool force = false) + { + if (UnmorphTime == 0 || UnmorphedMe == NULL || bStayMorphed || UnmorphedMe.bStayMorphed) + { + return false; + } + let unmorphed = UnmorphedMe; + unmorphed.SetOrigin (Pos, false); + unmorphed.bSolid = true; + bSolid = false; + bool save = bTouchy; + bTouchy = false; + if (!force && !unmorphed.TestMobjLocation ()) + { // Didn't fit + unmorphed.bSolid = false; + bSolid = true; + bTouchy = save; + UnmorphTime = level.time + 5*TICRATE; // Next try in 5 seconds + return false; + } + PreUnmorph(unmorphed, false); + unmorphed.PreUnmorph(self, true); + unmorphed.Angle = Angle; + unmorphed.target = target; + unmorphed.bShadow = bShadow; + unmorphed.bGhost = bGhost; + unmorphed.bSolid = !!(flagssave & 2); + unmorphed.bShootable = !!(flagssave & 4); + unmorphed.bInvisible = !!(flagssave & 0x40); + unmorphed.health = unmorphed.SpawnHealth(); + unmorphed.Vel = Vel; + unmorphed.ChangeTid(tid); + unmorphed.special = special; + unmorphed.Score = Score; + unmorphed.args[0] = args[0]; + unmorphed.args[1] = args[1]; + unmorphed.args[2] = args[2]; + unmorphed.args[3] = args[3]; + unmorphed.args[4] = args[4]; + unmorphed.CopyFriendliness (self, true); + unmorphed.bUnmorphed = false; + PostUnmorph(unmorphed, false); // From is false here: Leaving the caller's body. + unmorphed.PostUnmorph(self, true); // True here: Entering this body from here. + UnmorphedMe = NULL; + Substitute(unmorphed); + Destroy (); + let eflash = Spawn(MorphExitFlash, Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE); + if (eflash) + eflash.target = unmorphed; + return true; + } + + //=========================================================================== + // + // + // + //=========================================================================== + + override Actor, int, int MorphedDeath() + { + let realme = UnmorphedMe; + if (realme != NULL) + { + if ((UnmorphTime) && + (MorphStyle & MRF_UNDOBYDEATH)) + { + int realstyle = MorphStyle; + int realhealth = health; + if (UndoMonsterMorph(!!(MorphStyle & MRF_UNDOBYDEATHFORCED))) + { + return realme, realstyle, realhealth; + } + } + if (realme.bBossDeath) + { + realme.health = 0; // make sure that A_BossDeath considers it dead. + realme.A_BossDeath(); + } + } + return null, 0, 0; + } + +} + diff --git a/wadsrc/static/zscript/actors/raven/artitele.zs b/wadsrc/static/zscript/actors/raven/artitele.zs index 272ae8602..042ae67df 100644 --- a/wadsrc/static/zscript/actors/raven/artitele.zs +++ b/wadsrc/static/zscript/actors/raven/artitele.zs @@ -23,32 +23,35 @@ class ArtiTeleport : Inventory Loop; } - override bool Use(bool pickup) + override bool Use (bool pickup) { Vector3 dest; - double destAngle; + int destAngle; + if (deathmatch) - [dest, destAngle] = Level.PickDeathmatchStart(); - else - [dest, destAngle] = Level.PickPlayerStart(Owner.PlayerNumber()); - - if (!Level.UsePlayerStartZ) - dest.Z = ONFLOORZ; - - Owner.Teleport(dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG); - - bool canLaugh = Owner.player != null; - EMorphFlags mStyle = Owner.GetMorphStyle(); - if (Owner.Alternative && (mStyle & MRF_UNDOBYCHAOSDEVICE)) { - // Teleporting away will undo any morph effects (pig). - if (!Owner.Unmorph(Owner, MRF_UNDOBYCHAOSDEVICE) && (mStyle & MRF_FAILNOLAUGH)) - canLaugh = false; + [dest, destAngle] = level.PickDeathmatchStart(); + } + else + { + [dest, destAngle] = level.PickPlayerStart(Owner.PlayerNumber()); + } + if (!level.useplayerstartz) + dest.Z = ONFLOORZ; + Owner.Teleport (dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG); + bool canlaugh = true; + Playerinfo p = Owner.player; + if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYCHAOSDEVICE)) + { // Teleporting away will undo any morph effects (pig) + if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYCHAOSDEVICE) && (p.MorphStyle & MRF_FAILNOLAUGH)) + { + canlaugh = false; + } + } + if (canlaugh) + { // Full volume laugh + Owner.A_StartSound ("*evillaugh", CHAN_VOICE, CHANF_DEFAULT, 1., ATTN_NONE); } - - if (canLaugh) - Owner.A_StartSound("*evillaugh", CHAN_VOICE, attenuation: ATTN_NONE); - return true; } diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index e1176c649..9de39a3cd 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -232,8 +232,6 @@ enum EMorphFlags MRF_UNDOBYTIMEOUT = 0x00001000, MRF_UNDOALWAYS = 0x00002000, MRF_TRANSFERTRANSLATION = 0x00004000, - MRF_KEEPARMOR = 0x00008000, - MRF_IGNOREINVULN = 0x00010000, MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT, };