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;
if (oldpawn != nullptr)
{
PlayerPointerSubstitution (oldpawn, pawn, true);
StaticPointerSubstitution (oldpawn, pawn);
oldpawn->Destroy();
}
if (pawndup != NULL)

View file

@ -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;

View file

@ -626,14 +626,11 @@ 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->flags &= ~MF_SHOOTABLE;
}
}
Destroy();
}
// You'll probably never be able to catch this in a save game, but

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.
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;
}

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

View file

@ -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,
};

View file

@ -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

View file

@ -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;

View file

@ -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).
}
else if (realMo->health > 0)
int realgibhealth = realthis->GetGibHealth();
if (realthis->health >= realgibhealth)
{
realMo->health = 0;
realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l
}
}
realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath);
}
// 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;
}
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))
{

View file

@ -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<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)
@ -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);

View file

@ -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"));

View file

@ -1814,15 +1814,6 @@ DEFINE_CLASS_PROPERTY(playerclass, S, PowerMorph)
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;
}
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)

View file

@ -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?

View file

@ -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;
}

View file

@ -68,25 +68,27 @@ Class ArtiTomeOfPower : PowerupGiver
override bool Use (bool pickup)
{
EMorphFlags mStyle = Owner.GetMorphStyle();
if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER))
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))
{
// Attempt to undo chicken.
if (!Owner.Unmorph(Owner, MRF_UNDOBYTOMEOFPOWER))
{
if (!(mStyle & MRF_FAILNOTELEFRAG))
Owner.DamageMobj (null, null, TELEFRAG_DAMAGE, 'Telefrag');
}
else if (Owner.player)
{
}
else
{ // Succeeded
Owner.A_StartSound ("*evillaugh", CHAN_VOICE);
}
return true;
}
else
{
return Super.Use (pickup);
}
}
}

View file

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

View file

@ -1870,14 +1870,13 @@ class PowerReflection : Powerup
//===========================================================================
//
// PowerMorph
// Now works with monsters too!
//
//===========================================================================
class PowerMorph : Powerup
{
class<PlayerPawn> PlayerClass;
class<Actor> MonsterClass, MorphFlash, UnmorphFlash;
Class<PlayerPawn> PlayerClass;
Class<Actor> 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;
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
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).
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;
}
}

View file

@ -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;
}

View file

@ -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<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()
{
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<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)
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<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)
return player.mo.UndoPlayerMorph(activator ? activator.player : null, flags, force);
return UndoMonsterMorph(force);
}
virtual bool CheckUnmorph()
{
return UnmorphTime && UnmorphTime <= Level.Time && Unmorph(self, MRF_UNDOBYTIMEOUT);
return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force);
}
else
{
let morphed = MorphedMonster(self);
if (morphed)
return morphed.UndoMonsterMorph(force);
}
return false;
}
//---------------------------------------------------------------------------
//
@ -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())
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<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.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<Actor>)("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<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;
TextureID ScoreIcon;
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 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<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
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()

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
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<PlayerPawn> 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");
}
}
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;
}

View file

@ -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;
}
}
@ -52,35 +54,45 @@ extend class PlayerPawn
virtual void ActivateMorphWeapon ()
{
if (player.ReadyWeapon)
class<Weapon> morphweaponcls = MorphWeapon;
player.PendingWeapon = WP_NOCHANGE;
if (player.ReadyWeapon != null)
{
let psp = player.GetPSprite(PSP_WEAPON);
if (psp)
{
psp.y = WEAPONTOP;
player.ReadyWeapon.ResetPSprite(psp);
}
}
class<Weapon> 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 != null)
{
player.SetPsprite(PSP_FLASH, null);
}
if (player.ReadyWeapon)
player.SetPSprite(PSP_FLASH, null);
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
|| (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))))
if (bDontMorph)
{
return false;
}
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;
}
if (health <= 0)
{ // Dead players cannot morph
return false;
}
if (spawntype == null)
{
return false;
}
if (!(spawntype is 'PlayerPawn'))
{
return false;
}
if (spawntype == GetClass())
{
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");
}
return false;
}
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE));
if (!MorphInto(morphed))
{
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;
morphed.ClearFOVInterpolation();
p.morphTics = (duration) ? duration : DEFMORPHTICS;
// [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
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);
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;
}
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;
}
if (!MorphInto(alt))
return false;
PreUnmorph(altmo, false); // This body's about to be left.
altmo.PreUnmorph(self, true); // This one's about to become current.
PreUnmorph(alt, false); // This body's about to be left.
alt.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<Actor> 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();
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;
let exit_flash = player.MorphExitFlash;
bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON);
bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES);
Inventory level2 = alt.FindInventory("PowerWeaponLevel2", true);
if (level2)
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 ();
}
let morphWeap = p.ReadyWeapon;
if (premorphWeap)
if ((player.health > 0) || undobydeathsaves)
{
premorphWeap.PostMorphWeapon();
player.health = altmo.health = altmo.SpawnHealth();
}
else // killed when morphed so stay dead
{
altmo.health = player.health;
}
player.mo = altmo;
if (player.camera == self)
{
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')
{
// 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;
}
}
}
}
}
Actor eflash = null;
if (exit_flash != null)
{
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.
let beastweap = player.ReadyWeapon;
if (player.PremorphWeapon != null)
{
player.PremorphWeapon.PostMorphWeapon ();
}
else
{
p.ReadyWeapon = null;
p.PendingWeapon = WP_NOCHANGE;
p.Refire = 0;
player.ReadyWeapon = player.PendingWeapon = null;
}
if (style & MRF_LOSEACTUALWEAPON)
if (correctweapon)
{ // Better "lose morphed weapon" semantics
class<Actor> morphweaponcls = MorphWeapon;
if (morphweaponcls != null && morphweaponcls is 'Weapon')
{
// Improved "lose morph weapon" semantics.
class<Weapon> morphWeapCls = MorphWeapon;
if (morphWeapCls)
let OriginalMorphWeapon = Weapon(altmo.FindInventory (morphweapon));
if ((OriginalMorphWeapon != null) && (OriginalMorphWeapon.GivenAsMorphWeapon))
{ // You don't get to keep your morphed weapon.
if (OriginalMorphWeapon.SisterWeapon != null)
{
let originalMorphWeapon = Weapon(alt.FindInventory(morphWeapCls));
if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon)
originalMorphWeapon.Destroy();
OriginalMorphWeapon.SisterWeapon.Destroy ();
}
OriginalMorphWeapon.Destroy ();
}
}
else if (morphWeap) // Old behaviour (not really useful now).
}
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)
{
morphWeap.Destroy();
beastweap.SisterWeapon.Destroy ();
}
// 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)
{
Actor fog = Spawn(exitFlash, alt.Vec3Angle(20.0, alt.Angle, GameInfo.TelefogHeight), ALLOW_REPLACE);
if (fog)
fog.Target = alt;
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];
}
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

@ -26,29 +26,32 @@ class ArtiTeleport : Inventory
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;
}

View file

@ -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,
};