From c7bba2a1268947aef2e870782330c935db02a98d Mon Sep 17 00:00:00 2001 From: Rachael Alexanderson Date: Wed, 17 Apr 2024 17:44:25 -0400 Subject: [PATCH] Revert "Improvements to death and cheat handling" This reverts commit 3033fafaa7e3b3fd567b2b863a11b6f280d5c666. Revert "Improved ZScript interface for morphing" This reverts commit 6c64a4403cb322594e52ab30c89bd2a9edd6d4cb. Revert "Further morphing clean up" This reverts commit 12dc5c1506673cce53a065f0d08016462a220074. Revert "Fixed inconsistencies between player and monster morphing" This reverts commit 30730647fecf342086b9165e5678e23ea1cfb252. Revert "Reworked Morphing" This reverts commit 2c09a443b4d6f498161ce602457a2dedc640239c. - fix compile --- src/g_level.cpp | 2 +- src/g_statusbar/sbar_mugshot.cpp | 2 +- src/m_cheat.cpp | 9 +- src/p_saveg.cpp | 2 +- src/playsim/a_morph.cpp | 2 +- src/playsim/a_morph.h | 2 - src/playsim/actor.h | 4 +- src/playsim/p_enemy.cpp | 2 +- src/playsim/p_interaction.cpp | 62 +- src/playsim/p_mobj.cpp | 210 ++--- src/playsim/p_user.cpp | 4 +- src/scripting/thingdef_properties.cpp | 9 - src/scripting/vmthunks_actors.cpp | 23 +- wadsrc/static/zscript/actors/actor.zs | 4 +- .../static/zscript/actors/heretic/chicken.zs | 2 +- .../actors/heretic/hereticartifacts.zs | 32 +- wadsrc/static/zscript/actors/hexen/pig.zs | 2 +- .../zscript/actors/inventory/powerups.zs | 45 +- .../zscript/actors/inventory/weapons.zs | 2 +- wadsrc/static/zscript/actors/morph.zs | 386 ++-------- wadsrc/static/zscript/actors/player/player.zs | 38 +- .../zscript/actors/player/player_cheat.zs | 29 +- .../zscript/actors/player/player_morph.zs | 725 ++++++++++++------ .../static/zscript/actors/raven/artitele.zs | 45 +- wadsrc/static/zscript/constants.zs | 2 - 25 files changed, 758 insertions(+), 887 deletions(-) diff --git a/src/g_level.cpp b/src/g_level.cpp index 386df8d093..ff42656dac 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 4931243898..f1d43af283 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 7834f92d4d..2ed4ddf625 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 d5b0abe14c..cb67e6e486 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 5f6c38b91a..80e6807af5 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 762cd01bb5..00303533cb 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 eac4b9f438..51e9eede44 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 50ed1a74d8..1b1d3f77c8 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 86416f0c33..88326acb53 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 1887d7dc87..94a33868c4 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 6839b412ac..58f71beb74 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 bedd66cd37..fa7a5b13f3 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 7094212e0f..06a5c3b5c4 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 72f441b10c..8994287e84 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 a49418ca39..4bc2e20def 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 b620b04b4f..d12bf2b198 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 95e90722e7..45c601c4e2 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 76b0fea2d4..9c4903d011 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 66a7f8964c..49e32f3a23 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 b9fdbb101f..1dcfb31faf 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 e6c66fa446..c5eee1cc91 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 c89832dca6..adacdfb522 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 ff84246a16..77df1ef269 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 272ae86029..042ae67dfc 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 e1176c649d..9de39a3cd7 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, };