Revert "Improvements to death and cheat handling"

This reverts commit 3033fafaa7.

Revert "Improved ZScript interface for morphing"

This reverts commit 6c64a4403c.

Revert "Further morphing clean up"

This reverts commit 12dc5c1506.

Revert "Fixed inconsistencies between player and monster morphing"

This reverts commit 30730647fe.

Revert "Reworked Morphing"

This reverts commit 2c09a443b4.

- fix compile
This commit is contained in:
Rachael Alexanderson 2024-04-17 17:44:25 -04:00
parent 5346ec81db
commit c7bba2a126
No known key found for this signature in database
GPG key ID: 26A8ACCE97115EE0
25 changed files with 758 additions and 887 deletions

View file

@ -1711,7 +1711,7 @@ int FLevelLocals::FinishTravel ()
pawn->flags2 &= ~MF2_BLASTED; pawn->flags2 &= ~MF2_BLASTED;
if (oldpawn != nullptr) if (oldpawn != nullptr)
{ {
PlayerPointerSubstitution (oldpawn, pawn, true); StaticPointerSubstitution (oldpawn, pawn);
oldpawn->Destroy(); oldpawn->Destroy();
} }
if (pawndup != NULL) if (pawndup != NULL)

View file

@ -479,7 +479,7 @@ FGameTexture *FMugShot::GetFace(player_t *player, const char *default_face, int
if (CurrentState != NULL) if (CurrentState != NULL)
{ {
int skin = player->userinfo.GetSkin(); 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 CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle);
} }
return NULL; return NULL;

View file

@ -626,13 +626,10 @@ public:
double plyrdmgfact = Pawn->DamageFactor; double plyrdmgfact = Pawn->DamageFactor;
Pawn->DamageFactor = 1.; Pawn->DamageFactor = 1.;
P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide); P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide);
if (Pawn != nullptr) Pawn->DamageFactor = plyrdmgfact;
if (Pawn->health <= 0)
{ {
Pawn->DamageFactor = plyrdmgfact; Pawn->flags &= ~MF_SHOOTABLE;
if (Pawn->health <= 0)
{
Pawn->flags &= ~MF_SHOOTABLE;
}
} }
Destroy(); Destroy();
} }

View file

@ -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. // If this isn't the unmorphed original copy of a player, destroy it, because it's extra.
for (i = 0; i < MAXPLAYERS; ++i) 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; break;
} }

View file

@ -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) 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 }; VMValue params[] = { morphed, activator, flags, force };
int retval; int retval;

View file

@ -26,8 +26,6 @@ enum
MORPH_UNDOBYTIMEOUT = 0x00001000, // Player unmorphs once countdown expires MORPH_UNDOBYTIMEOUT = 0x00001000, // Player unmorphs once countdown expires
MORPH_UNDOALWAYS = 0x00002000, // Powerups must always unmorph, no matter what. MORPH_UNDOALWAYS = 0x00002000, // Powerups must always unmorph, no matter what.
MORPH_TRANSFERTRANSLATION = 0x00004000, // Transfer translation from the original actor to the morphed one 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, MORPH_STANDARDUNDOING = MORPH_UNDOBYTOMEOFPOWER | MORPH_UNDOBYCHAOSDEVICE | MORPH_UNDOBYTIMEOUT,
}; };

View file

@ -1747,8 +1747,8 @@ struct FTranslatedLineTarget
bool unlinked; // found by a trace that went through an unlinked portal. 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 #define S_FREETARGMOBJ 1

View file

@ -3139,7 +3139,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_Pain)
PARAM_SELF_PROLOGUE(AActor); PARAM_SELF_PROLOGUE(AActor);
// [RH] Vary player pain sounds depending on health (ala Quake2) // [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; const char *pain_amount;
FSoundID sfx_id = NO_SOUND; FSoundID sfx_id = NO_SOUND;

View file

@ -313,54 +313,36 @@ EXTERN_CVAR (Int, fraglimit)
void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath) void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath)
{ {
// Handle possible unmorph on death
bool wasgibbed = (health < GetGibHealth()); 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) IFVIRTUAL(AActor, MorphedDeath)
{ {
// Return values are no longer used to ensure things stay properly managed. AActor *realthis = NULL;
AActor* const realMo = alternative; int realstyle = 0;
int morphStyle = 0; int realhealth = 0;
VMValue params[] = { this }; 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);
{ if (realthis && !(realstyle & MORPH_UNDOBYDEATHSAVES))
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 (wasgibbed) if (wasgibbed)
{ {
const int realGibHealth = realMo->GetGibHealth(); int realgibhealth = realthis->GetGibHealth();
if (realMo->health >= realGibHealth) if (realthis->health >= realgibhealth)
realMo->health = realGibHealth - 1; // If morphed was gibbed, so must original be (where allowed). {
realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l
}
} }
else if (realMo->health > 0) realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath);
{
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);
} }
} }
} }
@ -476,7 +458,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf
++source->player->spreecount; ++source->player->spreecount;
} }
if (source->alternative != nullptr) if (source->player->morphTics)
{ // Make a super chicken { // Make a super chicken
source->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2)); 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 if (damage >= player->health && !telefragDamage
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
&& target->alternative == nullptr) && !player->morphTics)
{ // Try to use some inventory health { // Try to use some inventory health
P_AutoUseHealth (player, damage - player->health + 1); 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 // check for special fire damage or ice damage deaths
if (mod == NAME_Fire) if (mod == NAME_Fire)
{ {
if (player && target->alternative == nullptr) if (player && !player->morphTics)
{ // Check for flame death { // Check for flame death
if (!inflictor || if (!inflictor ||
((target->health > -50) && (damage > 25)) || ((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 if (damage >= player->health
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
&& target->alternative == nullptr) && !player->morphTics)
{ // Try to use some inventory health { // Try to use some inventory health
P_AutoUseHealth(player, damage - player->health+1); P_AutoUseHealth(player, damage - player->health+1);
} }
@ -1845,7 +1827,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain
else else
{ {
target->special1 = damage; target->special1 = damage;
if (player && target->alternative == nullptr) if (player && !player->morphTics)
{ // Check for flame death { // Check for flame death
if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25)) if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25))
{ {

View file

@ -819,7 +819,7 @@ int P_GetRealMaxHealth(AActor *actor, int max)
{ {
max = actor->GetMaxHealth(true); max = actor->GetMaxHealth(true);
// [MH] First step in predictable generic morph effects // [MH] First step in predictable generic morph effects
if (actor->alternative != nullptr) if (player->morphTics)
{ {
if (player->MorphStyle & MORPH_FULLHEALTH) if (player->MorphStyle & MORPH_FULLHEALTH)
{ {
@ -841,7 +841,7 @@ int P_GetRealMaxHealth(AActor *actor, int max)
else else
{ {
// Bonus health should be added on top of the item's limit. // 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); 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 HereticScrollDirs[4] = { 6, 9, 1, 4 };
static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 }; 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) if (freezetics > 0)
{ {
freezetics--; freezetics--;
@ -5098,16 +5082,6 @@ void AActor::CallDeactivate(AActor *activator)
void AActor::OnDestroy () 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. // [ZZ] call destroy event hook.
// note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the 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. // 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 // This once was the main method for pointer cleanup, but
// over to their new ones upon changing levels or respawning. It SHOULD NOT be // nowadays its only use is swapping out PlayerPawns.
// used for anything else! Do not export this functionality as it's // This requires pointer fixing throughout all objects and a few
// meant strictly for internal usage. // 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 DObject* probe;
|| !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn)) 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. // Go through players.
for (int i = 0; i < MAXPLAYERS; ++i) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (!oldPlayer->Level->PlayerInGame(i)) if (playeringame[i])
continue; {
AActor* replacement = notOld;
auto& p = players[i];
auto p = oldPlayer->Level->Players[i]; if (p.mo == old) p.mo = replacement, changed++;
if (p.poisoner.ForceGet() == old) p.poisoner = replacement, changed++;
if (p->mo == oldPlayer) if (p.attacker.ForceGet() == old) p.attacker = replacement, changed++;
p->mo = newPlayer; if (p.camera.ForceGet() == old) p.camera = replacement, changed++;
if (p->poisoner == oldPlayer) if (p.ConversationNPC.ForceGet() == old) p.ConversationNPC = replacement, changed++;
p->poisoner = newPlayer; if (p.ConversationPC.ForceGet() == old) p.ConversationPC = replacement, changed++;
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;
} }
// Go through sectors. // Go through sectors. Only the level this actor belongs to is relevant.
for (auto& sec : oldPlayer->Level->sectors) for (auto& sec : old->Level->sectors)
{ {
if (sec.SoundTarget == oldPlayer) if (sec.SoundTarget == old) sec.SoundTarget = notOld;
sec.SoundTarget = newPlayer;
} }
// 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<AActor>(NAME_Owner) != nullptr)
|| (to->IsKindOf(NAME_Inventory) && to->PointerVar<AActor>(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) 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; if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr;
} }
PlayerPointerSubstitution (oldactor, p->mo, false); StaticPointerSubstitution (oldactor, p->mo);
localEventManager->PlayerRespawned(PlayerNum(p)); localEventManager->PlayerRespawned(PlayerNum(p));
Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true); Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true);

View file

@ -693,7 +693,7 @@ bool player_t::Resurrect()
P_BringUpWeapon(this); P_BringUpWeapon(this);
} }
if (mo->alternative != nullptr) if (morphTics)
{ {
P_UnmorphActor(mo, mo); P_UnmorphActor(mo, mo);
} }
@ -1172,7 +1172,7 @@ void P_CheckEnvironment(player_t *player)
P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo)); P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo));
} }
if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) && 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) player->mo->waterlevel == 0)
{ {
auto id = S_FindSkinnedSound(player->mo, S_FindSound("*falling")); auto id = S_FindSkinnedSound(player->mo, S_FindSound("*falling"));

View file

@ -1814,15 +1814,6 @@ DEFINE_CLASS_PROPERTY(playerclass, S, PowerMorph)
defaults->PointerVar<PClassActor>(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate); defaults->PointerVar<PClassActor>(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
} }
//==========================================================================
//
//==========================================================================
DEFINE_CLASS_PROPERTY(monsterclass, S, PowerMorph)
{
PROP_STRING_PARM(str, 0);
defaults->PointerVar<PClassActor>(NAME_MonsterClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
}
//========================================================================== //==========================================================================
// //
//========================================================================== //==========================================================================

View file

@ -1693,11 +1693,28 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_BossDeath, A_BossDeath)
return 0; return 0;
} }
DEFINE_ACTION_FUNCTION_NATIVE(AActor, MorphInto, MorphPointerSubstitution) DEFINE_ACTION_FUNCTION_NATIVE(AActor, Substitute, StaticPointerSubstitution)
{ {
PARAM_SELF_PROLOGUE(AActor); PARAM_SELF_PROLOGUE(AActor);
PARAM_OBJECT(to, AActor); PARAM_OBJECT(replace, AActor);
ACTION_RETURN_INT(MorphPointerSubstitution(self, to)); 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) DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetSpawnableType, P_GetSpawnableType)

View file

@ -504,7 +504,7 @@ class Actor : Thinker native
virtual native bool Slam(Actor victim); virtual native bool Slam(Actor victim);
virtual void Touch(Actor toucher) {} virtual void Touch(Actor toucher) {}
virtual native void FallAndSink(double grav, double oldfloorz); virtual native void FallAndSink(double grav, double oldfloorz);
native bool MorphInto(Actor morph); private native void Substitute(Actor replacement);
native ui void DisplayNameTag(); native ui void DisplayNameTag();
native clearscope void DisableLocalRendering(uint playerNum, bool disable); native clearscope void DisableLocalRendering(uint playerNum, bool disable);
native ui bool ShouldRenderLocally(); // Only clients get to check this, never the playsim. native ui bool ShouldRenderLocally(); // Only clients get to check this, never the playsim.
@ -1410,7 +1410,7 @@ class Actor : Thinker native
bool grunted; bool grunted;
// [RH] only make noise if alive // [RH] only make noise if alive
if (self.health > 0 && !Alternative) if (self.health > 0 && self.player.morphTics == 0)
{ {
grunted = false; grunted = false;
// Why should this number vary by gravity? // Why should this number vary by gravity?

View file

@ -221,7 +221,7 @@ class ChickenPlayer : PlayerPawn
pspr.y = WEAPONTOP + player.chickenPeck / 2; pspr.y = WEAPONTOP + player.chickenPeck / 2;
} }
} }
if ((player.MorphTics ? player.MorphTics : Random[ChickenPlayerThink]()) & 15) if (player.morphTics & 15)
{ {
return; return;
} }

View file

@ -66,26 +66,28 @@ Class ArtiTomeOfPower : PowerupGiver
Loop; Loop;
} }
override bool Use(bool pickup) override bool Use (bool pickup)
{ {
EMorphFlags mStyle = Owner.GetMorphStyle(); Playerinfo p = Owner.player;
if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER)) if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYTOMEOFPOWER))
{ { // Attempt to undo chicken
// Attempt to undo chicken. if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER))
if (!Owner.Unmorph(Owner, MRF_UNDOBYTOMEOFPOWER)) { // Failed
{ if (!(p.MorphStyle & MRF_FAILNOTELEFRAG))
if (!(mStyle & MRF_FAILNOTELEFRAG)) {
Owner.DamageMobj(null, null, TELEFRAG_DAMAGE, 'Telefrag'); Owner.DamageMobj (null, null, TELEFRAG_DAMAGE, 'Telefrag');
}
} }
else if (Owner.player) else
{ { // Succeeded
Owner.A_StartSound("*evillaugh", CHAN_VOICE); Owner.A_StartSound ("*evillaugh", CHAN_VOICE);
} }
return true; return true;
} }
else
return Super.Use(pickup); {
return Super.Use (pickup);
}
} }
} }

View file

@ -145,7 +145,7 @@ class PigPlayer : PlayerPawn
override void MorphPlayerThink () override void MorphPlayerThink ()
{ {
if ((player.MorphTics ? player.MorphTics : Random[PigPlayerThink]()) & 15) if (player.morphTics & 15)
{ {
return; return;
} }

View file

@ -1870,14 +1870,13 @@ class PowerReflection : Powerup
//=========================================================================== //===========================================================================
// //
// PowerMorph // PowerMorph
// Now works with monsters too!
// //
//=========================================================================== //===========================================================================
class PowerMorph : Powerup class PowerMorph : Powerup
{ {
class<PlayerPawn> PlayerClass; Class<PlayerPawn> PlayerClass;
class<Actor> MonsterClass, MorphFlash, UnmorphFlash; Class<Actor> MorphFlash, UnMorphFlash;
int MorphStyle; int MorphStyle;
PlayerInfo MorphedPlayer; PlayerInfo MorphedPlayer;
@ -1896,19 +1895,19 @@ class PowerMorph : Powerup
{ {
Super.InitEffect(); Super.InitEffect();
if (!Owner) if (Owner != null && Owner.player != null && PlayerClass != null)
return;
if (Owner.Morph(Owner, PlayerClass, MonsterClass, int.max, MorphStyle, MorphFlash, UnmorphFlash))
{ {
// Get the real owner; safe because we are not attached to anything yet. let realplayer = Owner.player; // Remember the identity of the player
Owner = Owner.Alternative; if (realplayer.mo.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash))
bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor). {
MorphedPlayer = Owner.player; 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)
else MorphedPlayer = realplayer; // Store the player identity (morphing clears the unmorphed actor's "player" field)
{ }
bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate). 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(); Super.EndEffect();
// Abort if owner already destroyed or unmorphed. // Abort if owner already destroyed or unmorphed
if (!Owner || !Owner.Alternative) if (Owner == null || MorphedPlayer == null || Owner.alternative == null)
{
return; return;
}
// Abort if owner is dead; their Die() method will // Abort if owner is dead; their Die() method will
// take care of any required unmorphing on death. // take care of any required unmorphing on death.
if (Owner.player ? Owner.player.Health <= 0 : Owner.Health <= 0) if (MorphedPlayer.health <= 0)
{
return; return;
}
Owner.Unmorph(Owner, force: Owner.GetMorphStyle() & MRF_UNDOALWAYS); int savedMorphTics = MorphedPlayer.morphTics;
MorphedPlayer.mo.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS));
MorphedPlayer = null; MorphedPlayer = null;
} }
} }

View file

@ -262,7 +262,7 @@ class Weapon : StateProvider
} }
let psp = player.GetPSprite(PSP_WEAPON); let psp = player.GetPSprite(PSP_WEAPON);
if (!psp) return; if (!psp) return;
if (Alternative || player.cheats & CF_INSTANTWEAPSWITCH) if (player.morphTics || player.cheats & CF_INSTANTWEAPSWITCH)
{ {
psp.y = WEAPONBOTTOM; psp.y = WEAPONBOTTOM;
} }

View file

@ -23,78 +23,8 @@
extend class Actor 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<Actor> 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<Actor> flash)
{
if (player)
player.MorphExitFlash = flash;
else
MorphExitFlash = flash;
}
clearscope class<Actor> 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() virtual Actor, int, int MorphedDeath()
{ {
EMorphFlags mStyle = GetMorphStyle();
if (mStyle & MRF_UNDOBYDEATH)
Unmorph(self, force: mStyle & MRF_UNDOBYDEATHFORCED);
return null, 0, 0; return null, 0, 0;
} }
@ -110,12 +40,16 @@ extend class Actor
// //
//=========================================================================== //===========================================================================
virtual bool Morph(Actor activator, class<PlayerPawn> playerClass, class<Actor> monsterClass, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog") virtual bool Morph(Actor activator, class<PlayerPawn> playerclass, class<MorphedMonster> monsterclass, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
{ {
if (player) if (player != null && player.mo != null && playerclass != null)
return player.mo.MorphPlayer(activator ? activator.player : null, playerClass, duration, style, morphFlash, unmorphFlash); {
return player.mo.MorphPlayer(activator? activator.player : null, playerclass, duration, style, morphflash, unmorphflash);
return MorphMonster(monsterClass, duration, style, morphFlash, unmorphFlash); }
else
{
return MorphMonster(monsterclass, duration, style, morphflash, unmorphflash);
}
} }
//=========================================================================== //===========================================================================
@ -124,9 +58,18 @@ extend class Actor
// //
//=========================================================================== //===========================================================================
bool A_Morph(class<Actor> type, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog") bool A_Morph(class<Actor> type, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
{ {
return Morph(self, (class<PlayerPawn>)(type), type, duration, style, morphFlash, unmorphFlash); if (self.player != null)
{
let playerclass = (class<PlayerPawn>)(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) if (player)
return player.mo.UndoPlayerMorph(activator ? activator.player : null, flags, force); {
return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force);
return UndoMonsterMorph(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<Actor> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog") virtual bool MorphMonster (Class<Actor> spawntype, int duration, int style, Class<Actor> enter_flash, Class<Actor> exit_flash)
{ {
if (player || !bIsMonster || !spawnType || bDontMorph || Health <= 0 || spawnType == GetClass()) if (player || spawntype == NULL || bDontMorph || !bIsMonster || !(spawntype is 'MorphedMonster'))
return false;
Actor morphed = Spawn(spawnType, Pos, ALLOW_REPLACE);
if (!MorphInto(morphed))
{ {
if (morphed)
{
morphed.ClearCounters();
morphed.Destroy();
}
return false; return false;
} }
let morphed = MorphedMonster(Spawn (spawntype, Pos, NO_REPLACE));
// [MC] Notify that we're just about to start the transfer. // [MC] Notify that we're just about to start the transfer.
PreMorph(morphed, false); // False: No longer the current. PreMorph(morphed, false); // False: No longer the current.
morphed.PreMorph(self, true); // True: Becoming this actor. morphed.PreMorph(self, true); // True: Becoming this actor.
Substitute (morphed);
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
{
morphed.Translation = Translation; morphed.Translation = Translation;
}
morphed.ChangeTid(tid);
ChangeTid(0);
morphed.Angle = Angle; morphed.Angle = Angle;
morphed.UnmorphedMe = self;
morphed.Alpha = Alpha; morphed.Alpha = Alpha;
morphed.RenderStyle = RenderStyle; 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<Actor>)("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.bShadow |= bShadow;
morphed.bGhost |= bGhost; morphed.bGhost |= bGhost;
morphed.Score = Score; special = 0;
morphed.Special = Special; bSolid = false;
for (int i; i < 5; ++i) bShootable = false;
morphed.Args[i] = Args[i]; bUnmorphed = true;
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
bInvisible = true; bInvisible = true;
bSolid = bShootable = false; let eflash = Spawn(enter_flash ? enter_flash : (class<Actor>)("TeleportFog"), Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE);
if (eflash)
eflash.target = morphed;
PostMorph(morphed, false); PostMorph(morphed, false);
morphed.PostMorph(self, true); 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; return true;
} }
} }
//===========================================================================
//
//
//
//===========================================================================
class MorphProjectile : Actor
{
class<PlayerPawn> PlayerClass;
class<Actor> MonsterClass, MorphFlash, UnmorphFlash;
int Duration, MorphStyle;
Default
{
Damage 1;
Projectile;
MorphProjectile.MorphFlash "TeleportFog";
MorphProjectile.UnmorphFlash "TeleportFog";
-ACTIVATEIMPACT
-ACTIVATEPCROSS
}
override int DoSpecialDamage(Actor victim, int dmg, Name dmgType)
{
victim.Morph(Target, PlayerClass, MonsterClass, Duration, MorphStyle, MorphFlash, UnmorphFlash);
return -1;
}
}
//===========================================================================
//
// This class is redundant as it's no longer necessary for monsters to
// morph but is kept here for compatibility. Its previous fields either exist
// in the base Actor now or are just a shell for the actual fields
// which either already existed and weren't used for some reason or needed a
// better name.
//
//===========================================================================
class MorphedMonster : Actor
{
Actor UnmorphedMe;
EMorphFlags MorphStyle;
EPremorphProperty FlagsSave;
Default
{
Monster;
+FLOORCLIP
-COUNTKILL
}
// Make sure changes to these are propogated correctly when unmorphing. This is only
// set for monsters since originally players and this class were the only ones
// that were considered valid for morphing.
override bool UndoMonsterMorph(bool force)
{
Alternative = UnmorphedMe;
SetMorphStyle(MorphStyle);
PremorphProperties = FlagsSave;
return super.UndoMonsterMorph(force);
}
}

View file

@ -40,7 +40,7 @@ class PlayerPawn : Actor
double SideMove1, SideMove2; double SideMove1, SideMove2;
TextureID ScoreIcon; TextureID ScoreIcon;
int SpawnMask; int SpawnMask;
Name MorphWeapon; // This should really be a class<Weapon> but it's too late to change now. Name MorphWeapon;
double AttackZOffset; // attack height, relative to player center double AttackZOffset; // attack height, relative to player center
double UseRange; // [NS] Distance at which player can +use double UseRange; // [NS] Distance at which player can +use
double AirCapacity; // Multiplier for air supply underwater. double AirCapacity; // Multiplier for air supply underwater.
@ -477,7 +477,7 @@ class PlayerPawn : Actor
let player = self.player; let player = self.player;
if (!player) return; if (!player) return;
if ((player.WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled. 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. { // ...so throw away any pending weapon requests.
player.PendingWeapon = WP_NOCHANGE; player.PendingWeapon = WP_NOCHANGE;
} }
@ -651,7 +651,7 @@ class PlayerPawn : Actor
} }
} }
if (Alternative) if (player.morphTics)
{ {
bob = 0; bob = 0;
} }
@ -1084,7 +1084,7 @@ class PlayerPawn : Actor
virtual bool CanCrouch() const virtual bool CanCrouch() const
{ {
return !Alternative || bCrouchableMorph; return player.morphTics == 0 || bCrouchableMorph;
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@ -1250,7 +1250,7 @@ class PlayerPawn : Actor
side *= SideMove2; side *= SideMove2;
} }
if (!Alternative) if (!player.morphTics)
{ {
double factor = 1.; double factor = 1.;
for(let it = Inv; it != null; it = it.Inv) for(let it = Inv; it != null; it = it.Inv)
@ -1546,15 +1546,15 @@ class PlayerPawn : Actor
{ {
let player = self.player; let player = self.player;
// Morph counter // Morph counter
if (Alternative) if (player.morphTics)
{ {
if (player.chickenPeck) if (player.chickenPeck)
{ // Chicken attack counter { // Chicken attack counter
player.chickenPeck -= 3; player.chickenPeck -= 3;
} }
if (player.MorphTics && !--player.MorphTics) if (!--player.morphTics)
{ // Attempt to undo the chicken/pig { // 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; player.jumpTics = 0;
} }
} }
if (Alternative && !(player.cheats & CF_PREDICTING)) if (player.morphTics && !(player.cheats & CF_PREDICTING))
{ {
MorphPlayerThink (); MorphPlayerThink ();
} }
@ -2060,9 +2060,9 @@ class PlayerPawn : Actor
Inventory item, next; Inventory item, next;
let p = player; let p = player;
if (Alternative) if (p.morphTics != 0)
{ // Undo morph { // Undo morph
Unmorph(self, force: true); Unmorph(self, 0, true);
} }
// 'self' will be no longer valid from here on in case of an unmorph // 'self' will be no longer valid from here on in case of an unmorph
let me = p.mo; 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; native clearscope bool HasWeaponsInSlot(int slot) const;
// The actual implementation is on PlayerPawn where it can be overridden. Use that directly in the future. // 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<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog") deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(playerinfo p, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> 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 // 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() deprecated("3.7", "DropWeapon() should be used on a PlayerPawn object") void DropWeapon()

View file

@ -426,40 +426,35 @@ extend class PlayerPawn
} }
virtual String CheatMorph(class<PlayerPawn> morphClass, bool undo) virtual String CheatMorph(class<PlayerPawn> morphClass, bool quickundo)
{ {
// Set the standard morph style for the current game let oldclass = GetClass();
EMorphFlags style = MRF_UNDOBYTOMEOFPOWER;
if (GameInfo.GameType == GAME_Hexen)
style |= MRF_UNDOBYCHAOSDEVICE;
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<PlayerPawn> cls = GetClass(); if (UndoPlayerMorph (player))
Actor mo = Alternative;
if (!undo || Unmorph(self))
{ {
if ((!undo && Morph(self, morphClass, null, 0, style)) if (!quickundo && oldclass != morphclass && MorphPlayer (player, morphclass, 0, style))
|| (undo && morphClass != cls && mo.Morph(mo, morphClass, null, 0, style)))
{ {
return StringTable.Localize("$TXT_STRANGER"); return StringTable.Localize("$TXT_STRANGER");
} }
return StringTable.Localize("$TXT_NOTSTRANGE");
if (undo)
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 StringTable.Localize("$TXT_STRANGE");
} }
return ""; return "";
} }
virtual void CheatTakeWeaps() virtual void CheatTakeWeaps()
{ {
if (Alternative || health <= 0) if (player.morphTics || health <= 0)
{ {
return; return;
} }

View file

@ -1,25 +1,26 @@
extend class PlayerPawn extend class PlayerPawn
{ {
private native void Substitute(PlayerPawn replacement);
//=========================================================================== //===========================================================================
// //
// InitAllPowerupEffects // EndAllPowerupEffects
// //
// Calls InitEffect() on every Powerup in the inventory list. Since these // Calls EndEffect() on every Powerup in the inventory list.
// functions can be overridden it's safest to store what's next in the item
// list before calling it.
// //
//=========================================================================== //===========================================================================
void InitAllPowerupEffects() void InitAllPowerupEffects()
{ {
for (Inventory item = Inv; item;) let item = Inv;
while (item != null)
{ {
Inventory next = item.Inv;
let power = Powerup(item); let power = Powerup(item);
if (power) if (power != null)
{
power.InitEffect(); power.InitEffect();
}
item = next; item = item.Inv;
} }
} }
@ -33,14 +34,15 @@ extend class PlayerPawn
void EndAllPowerupEffects() void EndAllPowerupEffects()
{ {
for (Inventory item = Inv; item;) let item = Inv;
while (item != null)
{ {
Inventory next = item.Inv;
let power = Powerup(item); let power = Powerup(item);
if (power) if (power != null)
{
power.EndEffect(); power.EndEffect();
}
item = next; item = item.Inv;
} }
} }
@ -50,36 +52,46 @@ extend class PlayerPawn
// //
//=========================================================================== //===========================================================================
virtual void ActivateMorphWeapon() virtual void ActivateMorphWeapon ()
{ {
if (player.ReadyWeapon) class<Weapon> morphweaponcls = MorphWeapon;
player.PendingWeapon = WP_NOCHANGE;
if (player.ReadyWeapon != null)
{ {
let psp = player.GetPSprite(PSP_WEAPON); let psp = player.GetPSprite(PSP_WEAPON);
psp.y = WEAPONTOP; if (psp)
player.ReadyWeapon.ResetPSprite(psp); {
psp.y = WEAPONTOP;
player.ReadyWeapon.ResetPSprite(psp);
}
} }
class<Weapon> morphWeapCls = MorphWeapon; if (morphweaponcls == null || !(morphweaponcls is 'Weapon'))
if (!morphWeapCls) { // No weapon at all while morphed!
{
player.ReadyWeapon = null; player.ReadyWeapon = null;
} }
else else
{ {
player.ReadyWeapon = Weapon(FindInventory(morphWeapCls)); player.ReadyWeapon = Weapon(FindInventory (morphweaponcls));
if (!player.ReadyWeapon) if (player.ReadyWeapon == null)
{ {
player.ReadyWeapon = Weapon(GiveInventoryType(morphWeapCls)); player.ReadyWeapon = Weapon(GiveInventoryType (morphweaponcls));
if (player.ReadyWeapon) if (player.ReadyWeapon != null)
player.ReadyWeapon.GivenAsMorphWeapon = true; // Flag is used only by new morphWeap semantics in UndoPlayerMorph {
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) if (player.ReadyWeapon != null)
player.SetPSprite(PSP_FLASH, null); {
player.SetPsprite(PSP_FLASH, null);
}
player.PendingWeapon = WP_NOCHANGE; player.PendingWeapon = WP_NOCHANGE;
} }
@ -95,122 +107,125 @@ extend class PlayerPawn
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
virtual bool MorphPlayer(PlayerInfo activator, class<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog") virtual bool MorphPlayer(playerinfo activator, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> exit_flash = null)
{ {
if (!player || !spawnType || bDontMorph || player.Health <= 0 if (bDontMorph)
|| (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))))
{ {
return false; return false;
} }
if (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE)))
if (!duration) { // Immune when invulnerable unless this is a power we activated
duration = DEFMORPHTICS; return false;
}
if (spawnType == GetClass()) if (player.morphTics)
{ { // Player is already a beast
// Player is already a beast. if ((GetClass() == spawntype) && bCanSuperMorph
if (Alternative && bCanSuperMorph && (player.morphTics < (((duration) ? duration : DEFMORPHTICS) - TICRATE))
&& GetMorphTics() < duration - TICRATE && FindInventory('PowerWeaponLevel2', true) == null)
&& !FindInventory("PowerWeaponLevel2", true)) { // Make a super chicken
{ GiveInventoryType ('PowerWeaponLevel2');
// Make a super chicken.
GiveInventoryType("PowerWeaponLevel2");
} }
return false; return false;
} }
if (health <= 0)
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE)); { // Dead players cannot morph
if (!MorphInto(morphed)) return false;
}
if (spawntype == null)
{
return false;
}
if (!(spawntype is 'PlayerPawn'))
{
return false;
}
if (spawntype == GetClass())
{ {
if (morphed)
morphed.Destroy();
return false; return false;
} }
let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE));
// Use GetClass in the event someone actually allows replacements.
PreMorph(morphed, false); PreMorph(morphed, false);
morphed.PreMorph(self, true); morphed.PreMorph(self, true);
morphed.EndAllPowerupEffects(); EndAllPowerupEffects();
Substitute(morphed);
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
{
morphed.Translation = Translation; morphed.Translation = Translation;
}
if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR))
{
morphed.ChangeTid(tid);
ChangeTid(0);
}
morphed.Angle = Angle; 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.Target = Target; morphed.tracer = tracer;
morphed.Tracer = Tracer; morphed.alternative = self;
morphed.Master = Master;
morphed.FriendPlayer = FriendPlayer; morphed.FriendPlayer = FriendPlayer;
morphed.DesignatedTeam = DesignatedTeam; morphed.DesignatedTeam = DesignatedTeam;
morphed.Score = Score; morphed.Score = Score;
morphed.ScoreIcon = ScoreIcon; player.PremorphWeapon = player.ReadyWeapon;
morphed.Health = morphed.SpawnHealth();
if (TID && (style & MRF_NEWTIDBEHAVIOUR)) morphed.special2 = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility
{ morphed.player = player;
morphed.ChangeTid(TID);
ChangeTid(0); 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.bShadow |= bShadow;
morphed.bNoGravity |= bNoGravity; morphed.bNoGravity |= bNoGravity;
morphed.bFly |= bFly; morphed.bFly |= bFly;
morphed.bGhost |= bGhost; morphed.bGhost |= bGhost;
// Remove all armor. if (enter_flash == null) enter_flash = 'TeleportFog';
if (!(style & MRF_KEEPARMOR)) let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE);
{ let p = player;
for (Inventory item = morphed.Inv; item;) player = null;
{ alternative = morphed;
Inventory next = item.Inv; bSolid = false;
if (item is "Armor") bShootable = false;
item.DepleteOrDestroy(); bUnmorphed = true;
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;
bInvisible = true; bInvisible = true;
morphed.ClearFOVInterpolation(); p.morphTics = (duration) ? duration : DEFMORPHTICS;
morphed.InitAllPowerupEffects();
morphed.ActivateMorphWeapon();
// [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 ();
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 PostMorph(morphed, false); // No longer the current body
morphed.PostMorph(self, true); // This is 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; 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) if (alternative == null)
return false;
if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable
&& (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING))))
{ {
return false; return false;
} }
let alt = PlayerPawn(Alternative); let player = self.player;
alt.SetOrigin(Pos, false); bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag);
// Test if there's room to unmorph.
if (!force && (PremorphProperties & MPROP_SOLID))
{
bool altSolid = alt.bSolid;
bool isSolid = bSolid;
bool isTouchy = bTouchy;
alt.bSolid = true; if ((bInvulnerable) // If the player is invulnerable
bSolid = bTouchy = false; && ((player != activator) // and either did not decide to unmorph,
|| (!((player.MorphStyle & MRF_WHENINVULNERABLE) // or the morph style does not allow it
bool res = alt.TestMobjLocation(); || (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it),
{ // Then the player is immune to the unmorph.
alt.bSolid = altSolid; return false;
bSolid = isSolid;
bTouchy = isTouchy;
if (!res)
{
SetMorphTics(2 * TICRATE);
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; return false;
}
PreUnmorph(alt, false); // This body's about to be left. PreUnmorph(altmo, false); // This body's about to be left.
alt.PreUnmorph(self, true); // This one's about to become current. 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. // 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") if (item is "PowerMorph")
{
item.Destroy(); item.Destroy();
}
item = next; item = next;
} }
EndAllPowerupEffects();
alt.Angle = Angle; altmo.ObtainInventory (self);
alt.Pitch = Pitch; Substitute(altmo);
alt.Target = Target; if ((tid != 0) && (player.MorphStyle & MRF_NEWTIDBEHAVIOUR))
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<Actor> exitFlash = alt.GetMorphExitFlash();
EMorphFlags style = alt.GetMorphStyle();
Weapon premorphWeap = p.PremorphWeapon;
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
{ {
alt.ChangeTid(TID); altmo.ChangeTid(tid);
ChangeTID(0); }
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); if ((player.health > 0) || undobydeathsaves)
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)
{ {
premorphWeap.PostMorphWeapon(); player.health = altmo.health = altmo.SpawnHealth();
} }
else else // killed when morphed so stay dead
{ {
p.ReadyWeapon = null; altmo.health = player.health;
p.PendingWeapon = WP_NOCHANGE;
p.Refire = 0;
} }
if (style & MRF_LOSEACTUALWEAPON) player.mo = altmo;
if (player.camera == self)
{ {
// Improved "lose morph weapon" semantics. player.camera = altmo;
class<Weapon> morphWeapCls = MorphWeapon; }
if (morphWeapCls) 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)); // Assume root-level base skin to begin with
if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon) let skinindex = 0;
originalMorphWeapon.Destroy(); 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;
} }
WeaponSlots.SetupWeaponSlots(altmo); // Use original class's weapon slots.
// Reset the base AC of the player's Hexen armor back to its default. let beastweap = player.ReadyWeapon;
let hexArmor = HexenArmor(alt.FindInventory("HexenArmor")); if (player.PremorphWeapon != null)
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)
{ {
Actor fog = Spawn(exitFlash, alt.Vec3Angle(20.0, alt.Angle, GameInfo.TelefogHeight), ALLOW_REPLACE); player.PremorphWeapon.PostMorphWeapon ();
if (fog) }
fog.Target = alt; else
{
player.ReadyWeapon = player.PendingWeapon = null;
}
if (correctweapon)
{ // Better "lose morphed weapon" semantics
class<Actor> 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; 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<PlayerPawn> PlayerClass;
Class<Actor> 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<Actor> 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;
}
}

View file

@ -23,32 +23,35 @@ class ArtiTeleport : Inventory
Loop; Loop;
} }
override bool Use(bool pickup) override bool Use (bool pickup)
{ {
Vector3 dest; Vector3 dest;
double destAngle; int destAngle;
if (deathmatch) 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). [dest, destAngle] = level.PickDeathmatchStart();
if (!Owner.Unmorph(Owner, MRF_UNDOBYCHAOSDEVICE) && (mStyle & MRF_FAILNOLAUGH)) }
canLaugh = false; 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; return true;
} }

View file

@ -232,8 +232,6 @@ enum EMorphFlags
MRF_UNDOBYTIMEOUT = 0x00001000, MRF_UNDOBYTIMEOUT = 0x00001000,
MRF_UNDOALWAYS = 0x00002000, MRF_UNDOALWAYS = 0x00002000,
MRF_TRANSFERTRANSLATION = 0x00004000, MRF_TRANSFERTRANSLATION = 0x00004000,
MRF_KEEPARMOR = 0x00008000,
MRF_IGNOREINVULN = 0x00010000,
MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT, MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT,
}; };