mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-10 06:42:08 +00:00
Reapply "Improvements to death and cheat handling"
This reverts commit c7bba2a126
.
This commit is contained in:
parent
949cd5b746
commit
bcd6c6170e
25 changed files with 887 additions and 758 deletions
|
@ -1711,7 +1711,7 @@ int FLevelLocals::FinishTravel ()
|
|||
pawn->flags2 &= ~MF2_BLASTED;
|
||||
if (oldpawn != nullptr)
|
||||
{
|
||||
StaticPointerSubstitution (oldpawn, pawn);
|
||||
PlayerPointerSubstitution (oldpawn, pawn, true);
|
||||
oldpawn->Destroy();
|
||||
}
|
||||
if (pawndup != NULL)
|
||||
|
|
|
@ -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->morphTics ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars());
|
||||
const char *skin_face = (stateflags & FMugShot::CUSTOM) ? nullptr : (player->mo->alternative != nullptr ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars());
|
||||
return CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle);
|
||||
}
|
||||
return NULL;
|
||||
|
|
|
@ -626,10 +626,13 @@ public:
|
|||
double plyrdmgfact = Pawn->DamageFactor;
|
||||
Pawn->DamageFactor = 1.;
|
||||
P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide);
|
||||
Pawn->DamageFactor = plyrdmgfact;
|
||||
if (Pawn->health <= 0)
|
||||
if (Pawn != nullptr)
|
||||
{
|
||||
Pawn->flags &= ~MF_SHOOTABLE;
|
||||
Pawn->DamageFactor = plyrdmgfact;
|
||||
if (Pawn->health <= 0)
|
||||
{
|
||||
Pawn->flags &= ~MF_SHOOTABLE;
|
||||
}
|
||||
}
|
||||
Destroy();
|
||||
}
|
||||
|
|
|
@ -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]->morphTics && Players[i]->mo->alternative == pawn)
|
||||
if (PlayerInGame(i) && Players[i]->mo->alternative == pawn)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -26,6 +26,8 @@ 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,
|
||||
};
|
||||
|
|
|
@ -1747,8 +1747,8 @@ struct FTranslatedLineTarget
|
|||
bool unlinked; // found by a trace that went through an unlinked portal.
|
||||
};
|
||||
|
||||
|
||||
void StaticPointerSubstitution(AActor* old, AActor* notOld);
|
||||
void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer, bool removeOld);
|
||||
int MorphPointerSubstitution(AActor* from, AActor* to);
|
||||
|
||||
#define S_FREETARGMOBJ 1
|
||||
|
||||
|
|
|
@ -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->player->morphTics == 0)
|
||||
if (self->player && self->alternative == nullptr)
|
||||
{
|
||||
const char *pain_amount;
|
||||
FSoundID sfx_id = NO_SOUND;
|
||||
|
|
|
@ -313,36 +313,54 @@ 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)
|
||||
{
|
||||
AActor *realthis = NULL;
|
||||
int realstyle = 0;
|
||||
int realhealth = 0;
|
||||
// Return values are no longer used to ensure things stay properly managed.
|
||||
AActor* const realMo = alternative;
|
||||
int morphStyle = 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);
|
||||
|
||||
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)
|
||||
{
|
||||
int realgibhealth = realthis->GetGibHealth();
|
||||
if (realthis->health >= realgibhealth)
|
||||
{
|
||||
realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l
|
||||
}
|
||||
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)
|
||||
{
|
||||
realMo->health = 0;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,7 +476,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf
|
|||
++source->player->spreecount;
|
||||
}
|
||||
|
||||
if (source->player->morphTics)
|
||||
if (source->alternative != nullptr)
|
||||
{ // Make a super chicken
|
||||
source->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2));
|
||||
}
|
||||
|
@ -1329,7 +1347,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
|
|||
|
||||
if (damage >= player->health && !telefragDamage
|
||||
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
||||
&& !player->morphTics)
|
||||
&& target->alternative == nullptr)
|
||||
{ // Try to use some inventory health
|
||||
P_AutoUseHealth (player, damage - player->health + 1);
|
||||
}
|
||||
|
@ -1463,7 +1481,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 && !player->morphTics)
|
||||
if (player && target->alternative == nullptr)
|
||||
{ // Check for flame death
|
||||
if (!inflictor ||
|
||||
((target->health > -50) && (damage > 25)) ||
|
||||
|
@ -1797,7 +1815,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain
|
|||
}
|
||||
if (damage >= player->health
|
||||
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
||||
&& !player->morphTics)
|
||||
&& target->alternative == nullptr)
|
||||
{ // Try to use some inventory health
|
||||
P_AutoUseHealth(player, damage - player->health+1);
|
||||
}
|
||||
|
@ -1827,7 +1845,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain
|
|||
else
|
||||
{
|
||||
target->special1 = damage;
|
||||
if (player && !player->morphTics)
|
||||
if (player && target->alternative == nullptr)
|
||||
{ // Check for flame death
|
||||
if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25))
|
||||
{
|
||||
|
|
|
@ -819,7 +819,7 @@ int P_GetRealMaxHealth(AActor *actor, int max)
|
|||
{
|
||||
max = actor->GetMaxHealth(true);
|
||||
// [MH] First step in predictable generic morph effects
|
||||
if (player->morphTics)
|
||||
if (actor->alternative != nullptr)
|
||||
{
|
||||
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 (player->morphTics == 0 || (player->MorphStyle & MORPH_ADDSTAMINA))
|
||||
if (actor->alternative == nullptr || (player->MorphStyle & MORPH_ADDSTAMINA))
|
||||
{
|
||||
max += actor->IntVar(NAME_BonusHealth);
|
||||
}
|
||||
|
@ -3788,6 +3788,22 @@ 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--;
|
||||
|
@ -5082,6 +5098,16 @@ 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.
|
||||
|
@ -5203,59 +5229,157 @@ extern bool demonew;
|
|||
|
||||
//==========================================================================
|
||||
//
|
||||
// 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.
|
||||
// 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.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void StaticPointerSubstitution(AActor* old, AActor* notOld)
|
||||
void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer, bool removeOld)
|
||||
{
|
||||
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)))
|
||||
if (oldPlayer == nullptr || newPlayer == nullptr || oldPlayer == newPlayer
|
||||
|| !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn))
|
||||
{
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through players.
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
// Go through player infos.
|
||||
for (int i = 0; i < MAXPLAYERS; ++i)
|
||||
{
|
||||
if (playeringame[i])
|
||||
{
|
||||
AActor* replacement = notOld;
|
||||
auto& p = players[i];
|
||||
if (!oldPlayer->Level->PlayerInGame(i))
|
||||
continue;
|
||||
|
||||
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++;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// Go through sectors. Only the level this actor belongs to is relevant.
|
||||
for (auto& sec : old->Level->sectors)
|
||||
// Go through sectors.
|
||||
for (auto& sec : oldPlayer->Level->sectors)
|
||||
{
|
||||
if (sec.SoundTarget == old) sec.SoundTarget = notOld;
|
||||
if (sec.SoundTarget == oldPlayer)
|
||||
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)
|
||||
|
@ -5529,7 +5653,7 @@ AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flag
|
|||
if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr;
|
||||
}
|
||||
|
||||
StaticPointerSubstitution (oldactor, p->mo);
|
||||
PlayerPointerSubstitution (oldactor, p->mo, false);
|
||||
|
||||
localEventManager->PlayerRespawned(PlayerNum(p));
|
||||
Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true);
|
||||
|
|
|
@ -693,7 +693,7 @@ bool player_t::Resurrect()
|
|||
P_BringUpWeapon(this);
|
||||
}
|
||||
|
||||
if (morphTics)
|
||||
if (mo->alternative != nullptr)
|
||||
{
|
||||
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->morphTics &&
|
||||
player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && player->mo->alternative == nullptr &&
|
||||
player->mo->waterlevel == 0)
|
||||
{
|
||||
auto id = S_FindSkinnedSound(player->mo, S_FindSound("*falling"));
|
||||
|
|
|
@ -1814,6 +1814,15 @@ 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);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//==========================================================================
|
||||
|
|
|
@ -1693,28 +1693,11 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_BossDeath, A_BossDeath)
|
|||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, Substitute, StaticPointerSubstitution)
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, MorphInto, MorphPointerSubstitution)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
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;
|
||||
PARAM_OBJECT(to, AActor);
|
||||
ACTION_RETURN_INT(MorphPointerSubstitution(self, to));
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetSpawnableType, P_GetSpawnableType)
|
||||
|
|
|
@ -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);
|
||||
private native void Substitute(Actor replacement);
|
||||
native bool MorphInto(Actor morph);
|
||||
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 && self.player.morphTics == 0)
|
||||
if (self.health > 0 && !Alternative)
|
||||
{
|
||||
grunted = false;
|
||||
// Why should this number vary by gravity?
|
||||
|
|
|
@ -221,7 +221,7 @@ class ChickenPlayer : PlayerPawn
|
|||
pspr.y = WEAPONTOP + player.chickenPeck / 2;
|
||||
}
|
||||
}
|
||||
if (player.morphTics & 15)
|
||||
if ((player.MorphTics ? player.MorphTics : Random[ChickenPlayerThink]()) & 15)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -66,28 +66,26 @@ Class ArtiTomeOfPower : PowerupGiver
|
|||
Loop;
|
||||
}
|
||||
|
||||
override bool Use (bool pickup)
|
||||
override bool Use(bool pickup)
|
||||
{
|
||||
Playerinfo p = Owner.player;
|
||||
if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYTOMEOFPOWER))
|
||||
{ // Attempt to undo chicken
|
||||
if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER))
|
||||
{ // Failed
|
||||
if (!(p.MorphStyle & MRF_FAILNOTELEFRAG))
|
||||
{
|
||||
Owner.DamageMobj (null, null, TELEFRAG_DAMAGE, 'Telefrag');
|
||||
}
|
||||
EMorphFlags mStyle = Owner.GetMorphStyle();
|
||||
if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER))
|
||||
{
|
||||
// Attempt to undo chicken.
|
||||
if (!Owner.Unmorph(Owner, MRF_UNDOBYTOMEOFPOWER))
|
||||
{
|
||||
if (!(mStyle & MRF_FAILNOTELEFRAG))
|
||||
Owner.DamageMobj(null, null, TELEFRAG_DAMAGE, 'Telefrag');
|
||||
}
|
||||
else
|
||||
{ // Succeeded
|
||||
Owner.A_StartSound ("*evillaugh", CHAN_VOICE);
|
||||
else if (Owner.player)
|
||||
{
|
||||
Owner.A_StartSound("*evillaugh", CHAN_VOICE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Super.Use (pickup);
|
||||
}
|
||||
|
||||
return Super.Use(pickup);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ class PigPlayer : PlayerPawn
|
|||
|
||||
override void MorphPlayerThink ()
|
||||
{
|
||||
if (player.morphTics & 15)
|
||||
if ((player.MorphTics ? player.MorphTics : Random[PigPlayerThink]()) & 15)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1870,13 +1870,14 @@ class PowerReflection : Powerup
|
|||
//===========================================================================
|
||||
//
|
||||
// PowerMorph
|
||||
// Now works with monsters too!
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
class PowerMorph : Powerup
|
||||
{
|
||||
Class<PlayerPawn> PlayerClass;
|
||||
Class<Actor> MorphFlash, UnMorphFlash;
|
||||
class<PlayerPawn> PlayerClass;
|
||||
class<Actor> MonsterClass, MorphFlash, UnmorphFlash;
|
||||
int MorphStyle;
|
||||
PlayerInfo MorphedPlayer;
|
||||
|
||||
|
@ -1895,19 +1896,19 @@ class PowerMorph : Powerup
|
|||
{
|
||||
Super.InitEffect();
|
||||
|
||||
if (Owner != null && Owner.player != null && PlayerClass != null)
|
||||
if (!Owner)
|
||||
return;
|
||||
|
||||
if (Owner.Morph(Owner, PlayerClass, MonsterClass, int.max, MorphStyle, MorphFlash, UnmorphFlash))
|
||||
{
|
||||
let realplayer = Owner.player; // Remember the identity of the player
|
||||
if (realplayer.mo.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash))
|
||||
{
|
||||
Owner = realplayer.mo; // Replace the new owner in our owner; safe because we are not attached to anything yet
|
||||
bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor)
|
||||
MorphedPlayer = realplayer; // Store the player identity (morphing clears the unmorphed actor's "player" field)
|
||||
}
|
||||
else // morph failed - give the caller an opportunity to fail the pickup completely
|
||||
{
|
||||
bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate)
|
||||
}
|
||||
// Get the real owner; safe because we are not attached to anything yet.
|
||||
Owner = Owner.Alternative;
|
||||
bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor).
|
||||
MorphedPlayer = Owner.player;
|
||||
}
|
||||
else
|
||||
{
|
||||
bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate).
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1921,24 +1922,16 @@ class PowerMorph : Powerup
|
|||
{
|
||||
Super.EndEffect();
|
||||
|
||||
// Abort if owner already destroyed or unmorphed
|
||||
if (Owner == null || MorphedPlayer == null || Owner.alternative == null)
|
||||
{
|
||||
// Abort if owner already destroyed or unmorphed.
|
||||
if (!Owner || !Owner.Alternative)
|
||||
return;
|
||||
}
|
||||
|
||||
// Abort if owner is dead; their Die() method will
|
||||
// take care of any required unmorphing on death.
|
||||
if (MorphedPlayer.health <= 0)
|
||||
{
|
||||
if (Owner.player ? Owner.player.Health <= 0 : Owner.Health <= 0)
|
||||
return;
|
||||
}
|
||||
|
||||
int savedMorphTics = MorphedPlayer.morphTics;
|
||||
MorphedPlayer.mo.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS));
|
||||
Owner.Unmorph(Owner, force: Owner.GetMorphStyle() & MRF_UNDOALWAYS);
|
||||
MorphedPlayer = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ class Weapon : StateProvider
|
|||
}
|
||||
let psp = player.GetPSprite(PSP_WEAPON);
|
||||
if (!psp) return;
|
||||
if (player.morphTics || player.cheats & CF_INSTANTWEAPSWITCH)
|
||||
if (Alternative || player.cheats & CF_INSTANTWEAPSWITCH)
|
||||
{
|
||||
psp.y = WEAPONBOTTOM;
|
||||
}
|
||||
|
|
|
@ -23,8 +23,78 @@
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -40,16 +110,12 @@ extend class Actor
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
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)
|
||||
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")
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (player)
|
||||
return player.mo.MorphPlayer(activator ? activator.player : null, playerClass, duration, style, morphFlash, unmorphFlash);
|
||||
|
||||
return MorphMonster(monsterClass, duration, style, morphFlash, unmorphFlash);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -58,18 +124,9 @@ extend class Actor
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
bool A_Morph(class<Actor> type, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
|
||||
bool A_Morph(class<Actor> type, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog")
|
||||
{
|
||||
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;
|
||||
return Morph(self, (class<PlayerPawn>)(type), type, duration, style, morphFlash, unmorphFlash);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -78,21 +135,18 @@ extend class Actor
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
virtual bool UnMorph(Actor activator, int flags, bool force)
|
||||
virtual bool Unmorph(Actor activator, EMorphFlags flags = 0, bool force = false)
|
||||
{
|
||||
if (player)
|
||||
{
|
||||
return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force);
|
||||
}
|
||||
else
|
||||
{
|
||||
let morphed = MorphedMonster(self);
|
||||
if (morphed)
|
||||
return morphed.UndoMonsterMorph(force);
|
||||
}
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
|
@ -102,57 +156,265 @@ extend class Actor
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
virtual bool MorphMonster (Class<Actor> spawntype, int duration, int style, Class<Actor> enter_flash, Class<Actor> exit_flash)
|
||||
virtual bool MorphMonster(class<Actor> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
|
||||
{
|
||||
if (player || spawntype == NULL || bDontMorph || !bIsMonster || !(spawntype is 'MorphedMonster'))
|
||||
if (player || !bIsMonster || !spawnType || bDontMorph || Health <= 0 || spawnType == GetClass())
|
||||
return false;
|
||||
|
||||
Actor morphed = Spawn(spawnType, Pos, ALLOW_REPLACE);
|
||||
if (!MorphInto(morphed))
|
||||
{
|
||||
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;
|
||||
special = 0;
|
||||
bSolid = false;
|
||||
bShootable = false;
|
||||
bUnmorphed = true;
|
||||
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
|
||||
bInvisible = true;
|
||||
let eflash = Spawn(enter_flash ? enter_flash : (class<Actor>)("TeleportFog"), Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE);
|
||||
if (eflash)
|
||||
eflash.target = morphed;
|
||||
bSolid = bShootable = false;
|
||||
|
||||
PostMorph(morphed, false);
|
||||
morphed.PostMorph(self, true);
|
||||
|
||||
if (enterFlash)
|
||||
{
|
||||
Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE);
|
||||
if (fog)
|
||||
fog.Target = morphed;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//
|
||||
// FUNC P_UndoMonsterMorph
|
||||
//
|
||||
// Returns true if the monster unmorphs.
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
virtual bool UndoMonsterMorph(bool force = false)
|
||||
{
|
||||
if (!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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class PlayerPawn : Actor
|
|||
double SideMove1, SideMove2;
|
||||
TextureID ScoreIcon;
|
||||
int SpawnMask;
|
||||
Name MorphWeapon;
|
||||
Name MorphWeapon; // This should really be a class<Weapon> but it's too late to change now.
|
||||
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.
|
||||
player.morphTics != 0) // Morphed classes cannot change weapons.
|
||||
Alternative) // Morphed classes cannot change weapons.
|
||||
{ // ...so throw away any pending weapon requests.
|
||||
player.PendingWeapon = WP_NOCHANGE;
|
||||
}
|
||||
|
@ -651,7 +651,7 @@ class PlayerPawn : Actor
|
|||
}
|
||||
}
|
||||
|
||||
if (player.morphTics)
|
||||
if (Alternative)
|
||||
{
|
||||
bob = 0;
|
||||
}
|
||||
|
@ -1084,7 +1084,7 @@ class PlayerPawn : Actor
|
|||
|
||||
virtual bool CanCrouch() const
|
||||
{
|
||||
return player.morphTics == 0 || bCrouchableMorph;
|
||||
return !Alternative || bCrouchableMorph;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
@ -1250,7 +1250,7 @@ class PlayerPawn : Actor
|
|||
side *= SideMove2;
|
||||
}
|
||||
|
||||
if (!player.morphTics)
|
||||
if (!Alternative)
|
||||
{
|
||||
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 (player.morphTics)
|
||||
if (Alternative)
|
||||
{
|
||||
if (player.chickenPeck)
|
||||
{ // Chicken attack counter
|
||||
player.chickenPeck -= 3;
|
||||
}
|
||||
if (!--player.morphTics)
|
||||
if (player.MorphTics && !--player.MorphTics)
|
||||
{ // Attempt to undo the chicken/pig
|
||||
player.mo.UndoPlayerMorph(player, MRF_UNDOBYTIMEOUT);
|
||||
Unmorph(self, MRF_UNDOBYTIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1679,7 +1679,7 @@ class PlayerPawn : Actor
|
|||
player.jumpTics = 0;
|
||||
}
|
||||
}
|
||||
if (player.morphTics && !(player.cheats & CF_PREDICTING))
|
||||
if (Alternative && !(player.cheats & CF_PREDICTING))
|
||||
{
|
||||
MorphPlayerThink ();
|
||||
}
|
||||
|
@ -2060,9 +2060,9 @@ class PlayerPawn : Actor
|
|||
Inventory item, next;
|
||||
let p = player;
|
||||
|
||||
if (p.morphTics != 0)
|
||||
if (Alternative)
|
||||
{ // Undo morph
|
||||
Unmorph(self, 0, true);
|
||||
Unmorph(self, force: true);
|
||||
}
|
||||
// 'self' will be no longer valid from here on in case of an unmorph
|
||||
let me = p.mo;
|
||||
|
@ -2869,23 +2869,15 @@ 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 p, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> exit_flash = null)
|
||||
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")
|
||||
{
|
||||
if (mo != null)
|
||||
{
|
||||
return mo.MorphPlayer(p, spawntype, duration, style, enter_flash, exit_flash);
|
||||
}
|
||||
return false;
|
||||
return mo ? mo.MorphPlayer(activator, spawnType, duration, style, enterFlash, exitFlash) : 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, int unmorphflag = 0, bool force = false)
|
||||
deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(PlayerInfo player, EMorphFlags unmorphFlags = 0, bool force = false)
|
||||
{
|
||||
if (player.mo != null)
|
||||
{
|
||||
return player.mo.UndoPlayerMorph(self, unmorphflag, force);
|
||||
}
|
||||
return false;
|
||||
return player.mo ? player.mo.UndoPlayerMorph(self, unmorphFlags, force) : false;
|
||||
}
|
||||
|
||||
deprecated("3.7", "DropWeapon() should be used on a PlayerPawn object") void DropWeapon()
|
||||
|
|
|
@ -426,35 +426,40 @@ extend class PlayerPawn
|
|||
}
|
||||
|
||||
|
||||
virtual String CheatMorph(class<PlayerPawn> morphClass, bool quickundo)
|
||||
virtual String CheatMorph(class<PlayerPawn> morphClass, bool undo)
|
||||
{
|
||||
let oldclass = GetClass();
|
||||
|
||||
// Set the standard morph style for the current game
|
||||
int style = MRF_UNDOBYTOMEOFPOWER;
|
||||
if (gameinfo.gametype == GAME_Hexen) style |= MRF_UNDOBYCHAOSDEVICE;
|
||||
EMorphFlags style = MRF_UNDOBYTOMEOFPOWER;
|
||||
if (GameInfo.GameType == GAME_Hexen)
|
||||
style |= MRF_UNDOBYCHAOSDEVICE;
|
||||
|
||||
if (player.morphTics)
|
||||
if (Alternative)
|
||||
{
|
||||
if (UndoPlayerMorph (player))
|
||||
class<PlayerPawn> cls = GetClass();
|
||||
Actor mo = Alternative;
|
||||
if (!undo || Unmorph(self))
|
||||
{
|
||||
if (!quickundo && oldclass != morphclass && MorphPlayer (player, morphclass, 0, style))
|
||||
if ((!undo && Morph(self, morphClass, null, 0, style))
|
||||
|| (undo && morphClass != cls && mo.Morph(mo, morphClass, null, 0, style)))
|
||||
{
|
||||
return StringTable.Localize("$TXT_STRANGER");
|
||||
}
|
||||
return StringTable.Localize("$TXT_NOTSTRANGE");
|
||||
|
||||
if (undo)
|
||||
return StringTable.Localize("$TXT_NOTSTRANGE");
|
||||
}
|
||||
}
|
||||
else if (MorphPlayer (player, morphclass, 0, style))
|
||||
else if (Morph(self, morphClass, null, 0, style))
|
||||
{
|
||||
return StringTable.Localize("$TXT_STRANGE");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
virtual void CheatTakeWeaps()
|
||||
{
|
||||
if (player.morphTics || health <= 0)
|
||||
if (Alternative || health <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
extend class PlayerPawn
|
||||
{
|
||||
private native void Substitute(PlayerPawn replacement);
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// EndAllPowerupEffects
|
||||
// InitAllPowerupEffects
|
||||
//
|
||||
// Calls EndEffect() on every Powerup in the inventory list.
|
||||
// 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.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void InitAllPowerupEffects()
|
||||
{
|
||||
let item = Inv;
|
||||
while (item != null)
|
||||
for (Inventory item = Inv; item;)
|
||||
{
|
||||
Inventory next = item.Inv;
|
||||
let power = Powerup(item);
|
||||
if (power != null)
|
||||
{
|
||||
if (power)
|
||||
power.InitEffect();
|
||||
}
|
||||
item = item.Inv;
|
||||
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,15 +33,14 @@ extend class PlayerPawn
|
|||
|
||||
void EndAllPowerupEffects()
|
||||
{
|
||||
let item = Inv;
|
||||
while (item != null)
|
||||
for (Inventory item = Inv; item;)
|
||||
{
|
||||
Inventory next = item.Inv;
|
||||
let power = Powerup(item);
|
||||
if (power != null)
|
||||
{
|
||||
if (power)
|
||||
power.EndEffect();
|
||||
}
|
||||
item = item.Inv;
|
||||
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,46 +50,36 @@ extend class PlayerPawn
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
virtual void ActivateMorphWeapon ()
|
||||
virtual void ActivateMorphWeapon()
|
||||
{
|
||||
class<Weapon> morphweaponcls = MorphWeapon;
|
||||
player.PendingWeapon = WP_NOCHANGE;
|
||||
|
||||
if (player.ReadyWeapon != null)
|
||||
if (player.ReadyWeapon)
|
||||
{
|
||||
let psp = player.GetPSprite(PSP_WEAPON);
|
||||
if (psp)
|
||||
{
|
||||
psp.y = WEAPONTOP;
|
||||
player.ReadyWeapon.ResetPSprite(psp);
|
||||
}
|
||||
psp.y = WEAPONTOP;
|
||||
player.ReadyWeapon.ResetPSprite(psp);
|
||||
}
|
||||
|
||||
if (morphweaponcls == null || !(morphweaponcls is 'Weapon'))
|
||||
{ // No weapon at all while morphed!
|
||||
|
||||
class<Weapon> morphWeapCls = MorphWeapon;
|
||||
if (!morphWeapCls)
|
||||
{
|
||||
player.ReadyWeapon = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
player.ReadyWeapon = Weapon(FindInventory (morphweaponcls));
|
||||
if (player.ReadyWeapon == null)
|
||||
player.ReadyWeapon = Weapon(FindInventory(morphWeapCls));
|
||||
if (!player.ReadyWeapon)
|
||||
{
|
||||
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());
|
||||
player.ReadyWeapon = Weapon(GiveInventoryType(morphWeapCls));
|
||||
if (player.ReadyWeapon)
|
||||
player.ReadyWeapon.GivenAsMorphWeapon = true; // Flag is used only by new morphWeap semantics in UndoPlayerMorph
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -107,125 +95,122 @@ extend class PlayerPawn
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
virtual bool MorphPlayer(playerinfo activator, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> exit_flash = null)
|
||||
virtual bool MorphPlayer(PlayerInfo activator, class<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
|
||||
{
|
||||
if (bDontMorph)
|
||||
if (!player || !spawnType || bDontMorph || player.Health <= 0
|
||||
|| (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))))
|
||||
{
|
||||
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');
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE));
|
||||
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE));
|
||||
if (!MorphInto(morphed))
|
||||
{
|
||||
if (morphed)
|
||||
morphed.Destroy();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use GetClass in the event someone actually allows replacements.
|
||||
PreMorph(morphed, false);
|
||||
morphed.PreMorph(self, true);
|
||||
|
||||
EndAllPowerupEffects();
|
||||
Substitute(morphed);
|
||||
morphed.EndAllPowerupEffects();
|
||||
|
||||
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
||||
{
|
||||
morphed.Translation = Translation;
|
||||
}
|
||||
if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR))
|
||||
{
|
||||
morphed.ChangeTid(tid);
|
||||
ChangeTid(0);
|
||||
}
|
||||
|
||||
morphed.Angle = Angle;
|
||||
morphed.target = target;
|
||||
morphed.tracer = tracer;
|
||||
morphed.alternative = self;
|
||||
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.FriendPlayer = FriendPlayer;
|
||||
morphed.DesignatedTeam = DesignatedTeam;
|
||||
morphed.Score = Score;
|
||||
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();
|
||||
morphed.ScoreIcon = ScoreIcon;
|
||||
morphed.Health = morphed.SpawnHealth();
|
||||
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
||||
{
|
||||
morphed.ChangeTid(TID);
|
||||
ChangeTid(0);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (enter_flash == null) enter_flash = 'TeleportFog';
|
||||
let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE);
|
||||
let p = player;
|
||||
player = null;
|
||||
alternative = morphed;
|
||||
bSolid = false;
|
||||
bShootable = false;
|
||||
bUnmorphed = true;
|
||||
bInvisible = true;
|
||||
|
||||
p.morphTics = (duration) ? duration : DEFMORPHTICS;
|
||||
|
||||
// [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; )
|
||||
// Remove all armor.
|
||||
if (!(style & MRF_KEEPARMOR))
|
||||
{
|
||||
let next = item.Inv;
|
||||
if (item is 'Armor')
|
||||
for (Inventory item = morphed.Inv; item;)
|
||||
{
|
||||
item.DepleteOrDestroy();
|
||||
Inventory next = item.Inv;
|
||||
if (item is "Armor")
|
||||
item.DepleteOrDestroy();
|
||||
|
||||
item = next;
|
||||
}
|
||||
item = next;
|
||||
}
|
||||
morphed.InitAllPowerupEffects();
|
||||
morphed.ActivateMorphWeapon ();
|
||||
if (p.camera == self) // can this happen?
|
||||
{
|
||||
p.camera = morphed;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
morphed.ClearFOVInterpolation();
|
||||
morphed.ScoreIcon = ScoreIcon; // [GRB]
|
||||
if (eflash)
|
||||
eflash.target = morphed;
|
||||
morphed.InitAllPowerupEffects();
|
||||
morphed.ActivateMorphWeapon();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -235,400 +220,156 @@ extend class PlayerPawn
|
|||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
virtual bool UndoPlayerMorph(playerinfo activator, int unmorphflag = 0, bool force = false)
|
||||
virtual bool UndoPlayerMorph(PlayerInfo activator, EMorphFlags unmorphFlags = 0, bool force = false)
|
||||
{
|
||||
if (alternative == null)
|
||||
if (!Alternative || bStayMorphed || Alternative.bStayMorphed)
|
||||
return false;
|
||||
|
||||
if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable
|
||||
&& (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let player = self.player;
|
||||
bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag);
|
||||
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;
|
||||
|
||||
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;
|
||||
alt.bSolid = true;
|
||||
bSolid = bTouchy = false;
|
||||
|
||||
bool res = alt.TestMobjLocation();
|
||||
|
||||
alt.bSolid = altSolid;
|
||||
bSolid = isSolid;
|
||||
bTouchy = isTouchy;
|
||||
|
||||
if (!res)
|
||||
{
|
||||
SetMorphTics(2 * TICRATE);
|
||||
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;
|
||||
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.
|
||||
|
||||
// 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;
|
||||
alt.EndAllPowerupEffects();
|
||||
|
||||
// Remove the morph power if the morph is being undone prematurely.
|
||||
for (Inventory item = Inv; item != null;)
|
||||
for (Inventory item = alt.Inv; item;)
|
||||
{
|
||||
let next = item.Inv;
|
||||
Inventory next = item.Inv;
|
||||
if (item is "PowerMorph")
|
||||
{
|
||||
item.Destroy();
|
||||
}
|
||||
|
||||
item = next;
|
||||
}
|
||||
EndAllPowerupEffects();
|
||||
altmo.ObtainInventory (self);
|
||||
Substitute(altmo);
|
||||
if ((tid != 0) && (player.MorphStyle & MRF_NEWTIDBEHAVIOUR))
|
||||
{
|
||||
altmo.ChangeTid(tid);
|
||||
}
|
||||
altmo.Angle = Angle;
|
||||
altmo.player = player;
|
||||
altmo.reactiontime = 18;
|
||||
altmo.bSolid = !!(special2 & 2);
|
||||
altmo.bShootable = !!(special2 & 4);
|
||||
altmo.bInvisible = !!(special2 & 0x40);
|
||||
altmo.Vel = (0, 0, Vel.Z);
|
||||
player.Vel = (0, 0);
|
||||
altmo.floorz = floorz;
|
||||
altmo.bShadow = bShadow;
|
||||
altmo.bNoGravity = bNoGravity;
|
||||
altmo.bGhost = bGhost;
|
||||
altmo.bUnmorphed = false;
|
||||
altmo.Score = Score;
|
||||
altmo.InitAllPowerupEffects();
|
||||
|
||||
let exit_flash = player.MorphExitFlash;
|
||||
bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON);
|
||||
bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES);
|
||||
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);
|
||||
|
||||
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)
|
||||
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))
|
||||
{
|
||||
level2.Destroy ();
|
||||
alt.ChangeTid(TID);
|
||||
ChangeTID(0);
|
||||
}
|
||||
|
||||
if ((player.health > 0) || undobydeathsaves)
|
||||
{
|
||||
player.health = altmo.health = altmo.SpawnHealth();
|
||||
}
|
||||
else // killed when morphed so stay dead
|
||||
{
|
||||
altmo.health = player.health;
|
||||
}
|
||||
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;
|
||||
|
||||
player.mo = altmo;
|
||||
if (player.camera == self)
|
||||
{
|
||||
player.camera = altmo;
|
||||
}
|
||||
altmo.ClearFOVInterpolation();
|
||||
Inventory level2 = alt.FindInventory("PowerWeaponLevel2", true);
|
||||
if (level2)
|
||||
level2.Destroy();
|
||||
|
||||
// [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])
|
||||
let morphWeap = p.ReadyWeapon;
|
||||
if (premorphWeap)
|
||||
{
|
||||
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 ();
|
||||
premorphWeap.PostMorphWeapon();
|
||||
}
|
||||
else
|
||||
{
|
||||
player.ReadyWeapon = player.PendingWeapon = null;
|
||||
p.ReadyWeapon = null;
|
||||
p.PendingWeapon = WP_NOCHANGE;
|
||||
p.Refire = 0;
|
||||
}
|
||||
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)
|
||||
|
||||
if (style & MRF_LOSEACTUALWEAPON)
|
||||
{
|
||||
hxarmor.Slots[4] = altmo.HexenArmor[0];
|
||||
// Improved "lose morph weapon" semantics.
|
||||
class<Weapon> morphWeapCls = MorphWeapon;
|
||||
if (morphWeapCls)
|
||||
{
|
||||
let originalMorphWeapon = Weapon(alt.FindInventory(morphWeapCls));
|
||||
if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon)
|
||||
originalMorphWeapon.Destroy();
|
||||
}
|
||||
}
|
||||
else if (morphWeap) // Old behaviour (not really useful now).
|
||||
{
|
||||
morphWeap.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;
|
||||
}
|
||||
|
||||
Destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
override Actor, int, int MorphedDeath()
|
||||
{
|
||||
// Voodoo dolls should not unmorph the real player here.
|
||||
if (player && (player.mo == self) &&
|
||||
(player.morphTics) &&
|
||||
(player.MorphStyle & MRF_UNDOBYDEATH) &&
|
||||
(alternative))
|
||||
{
|
||||
Actor realme = alternative;
|
||||
int realstyle = player.MorphStyle;
|
||||
int realhealth = health;
|
||||
if (UndoPlayerMorph(player, 0, !!(player.MorphStyle & MRF_UNDOBYDEATHFORCED)))
|
||||
{
|
||||
return realme, realstyle, realhealth;
|
||||
}
|
||||
}
|
||||
return null, 0, 0;
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
class MorphProjectile : Actor
|
||||
{
|
||||
|
||||
Class<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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -23,35 +23,32 @@ class ArtiTeleport : Inventory
|
|||
Loop;
|
||||
}
|
||||
|
||||
override bool Use (bool pickup)
|
||||
override bool Use(bool pickup)
|
||||
{
|
||||
Vector3 dest;
|
||||
int destAngle;
|
||||
|
||||
double destAngle;
|
||||
if (deathmatch)
|
||||
{
|
||||
[dest, destAngle] = level.PickDeathmatchStart();
|
||||
}
|
||||
[dest, destAngle] = Level.PickDeathmatchStart();
|
||||
else
|
||||
{
|
||||
[dest, destAngle] = level.PickPlayerStart(Owner.PlayerNumber());
|
||||
}
|
||||
if (!level.useplayerstartz)
|
||||
[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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (canLaugh)
|
||||
Owner.A_StartSound("*evillaugh", CHAN_VOICE, attenuation: ATTN_NONE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -232,6 +232,8 @@ 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,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue