Revert "Improvements to death and cheat handling"
This reverts commit3033fafaa7. Revert "Improved ZScript interface for morphing" This reverts commit6c64a4403c. Revert "Further morphing clean up" This reverts commit12dc5c1506. Revert "Fixed inconsistencies between player and monster morphing" This reverts commit30730647fe. Revert "Reworked Morphing" This reverts commit2c09a443b4. - fix compile
This commit is contained in:
parent
5346ec81db
commit
c7bba2a126
25 changed files with 744 additions and 873 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -626,13 +626,10 @@ public:
|
|||
double plyrdmgfact = Pawn->DamageFactor;
|
||||
Pawn->DamageFactor = 1.;
|
||||
P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide);
|
||||
if (Pawn != nullptr)
|
||||
Pawn->DamageFactor = plyrdmgfact;
|
||||
if (Pawn->health <= 0)
|
||||
{
|
||||
Pawn->DamageFactor = plyrdmgfact;
|
||||
if (Pawn->health <= 0)
|
||||
{
|
||||
Pawn->flags &= ~MF_SHOOTABLE;
|
||||
}
|
||||
Pawn->flags &= ~MF_SHOOTABLE;
|
||||
}
|
||||
Destroy();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -313,54 +313,36 @@ EXTERN_CVAR (Int, fraglimit)
|
|||
|
||||
void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath)
|
||||
{
|
||||
// Handle possible unmorph on death
|
||||
bool wasgibbed = (health < GetGibHealth());
|
||||
|
||||
// Check to see if unmorph Actors need to be killed as well. Originally this was always
|
||||
// called but that puts an unnecessary burden on the modder to determine whether it's
|
||||
// a valid call or not.
|
||||
if (alternative != nullptr && !(flags & MF_UNMORPHED))
|
||||
{
|
||||
IFVIRTUAL(AActor, MorphedDeath)
|
||||
{
|
||||
// Return values are no longer used to ensure things stay properly managed.
|
||||
AActor* const realMo = alternative;
|
||||
int morphStyle = 0;
|
||||
AActor *realthis = NULL;
|
||||
int realstyle = 0;
|
||||
int realhealth = 0;
|
||||
|
||||
VMValue params[] = { this };
|
||||
VMReturn returns[3];
|
||||
returns[0].PointerAt((void**)&realthis);
|
||||
returns[1].IntAt(&realstyle);
|
||||
returns[2].IntAt(&realhealth);
|
||||
VMCall(func, params, 1, returns, 3);
|
||||
|
||||
{
|
||||
IFVM(Actor, GetMorphStyle)
|
||||
{
|
||||
VMReturn ret[] = { &morphStyle };
|
||||
VMCall(func, params, 1, ret, 1);
|
||||
}
|
||||
}
|
||||
|
||||
VMCall(func, params, 1, nullptr, 0);
|
||||
|
||||
// Kill the dummy Actor if it didn't unmorph, otherwise checking the morph flags. Player pawns need
|
||||
// to stay, otherwise they won't respawn correctly.
|
||||
if (realMo != nullptr && !(realMo->flags6 & MF6_KILLED)
|
||||
&& ((alternative != nullptr && player == nullptr) || (alternative == nullptr && !(morphStyle & MORPH_UNDOBYDEATHSAVES))))
|
||||
if (realthis && !(realstyle & MORPH_UNDOBYDEATHSAVES))
|
||||
{
|
||||
if (wasgibbed)
|
||||
{
|
||||
const int realGibHealth = realMo->GetGibHealth();
|
||||
if (realMo->health >= realGibHealth)
|
||||
realMo->health = realGibHealth - 1; // If morphed was gibbed, so must original be (where allowed).
|
||||
int realgibhealth = realthis->GetGibHealth();
|
||||
if (realthis->health >= realgibhealth)
|
||||
{
|
||||
realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l
|
||||
}
|
||||
}
|
||||
else if (realMo->health > 0)
|
||||
{
|
||||
realMo->health = 0;
|
||||
}
|
||||
|
||||
// Pass appropriate damage information along when it's confirmed to die.
|
||||
realMo->DamageTypeReceived = DamageTypeReceived;
|
||||
realMo->DamageType = DamageType;
|
||||
realMo->special1 = special1;
|
||||
|
||||
realMo->CallDie(source, inflictor, dmgflags, MeansOfDeath);
|
||||
realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -476,7 +458,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf
|
|||
++source->player->spreecount;
|
||||
}
|
||||
|
||||
if (source->alternative != nullptr)
|
||||
if (source->player->morphTics)
|
||||
{ // Make a super chicken
|
||||
source->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2));
|
||||
}
|
||||
|
|
@ -1347,7 +1329,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
|
|||
|
||||
if (damage >= player->health && !telefragDamage
|
||||
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
||||
&& target->alternative == nullptr)
|
||||
&& !player->morphTics)
|
||||
{ // Try to use some inventory health
|
||||
P_AutoUseHealth (player, damage - player->health + 1);
|
||||
}
|
||||
|
|
@ -1481,7 +1463,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
|
|||
// check for special fire damage or ice damage deaths
|
||||
if (mod == NAME_Fire)
|
||||
{
|
||||
if (player && target->alternative == nullptr)
|
||||
if (player && !player->morphTics)
|
||||
{ // Check for flame death
|
||||
if (!inflictor ||
|
||||
((target->health > -50) && (damage > 25)) ||
|
||||
|
|
@ -1815,7 +1797,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain
|
|||
}
|
||||
if (damage >= player->health
|
||||
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
||||
&& target->alternative == nullptr)
|
||||
&& !player->morphTics)
|
||||
{ // Try to use some inventory health
|
||||
P_AutoUseHealth(player, damage - player->health+1);
|
||||
}
|
||||
|
|
@ -1845,7 +1827,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain
|
|||
else
|
||||
{
|
||||
target->special1 = damage;
|
||||
if (player && target->alternative == nullptr)
|
||||
if (player && !player->morphTics)
|
||||
{ // Check for flame death
|
||||
if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//==========================================================================
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,26 +66,28 @@ Class ArtiTomeOfPower : PowerupGiver
|
|||
Loop;
|
||||
}
|
||||
|
||||
override bool Use(bool pickup)
|
||||
override bool Use (bool pickup)
|
||||
{
|
||||
EMorphFlags mStyle = Owner.GetMorphStyle();
|
||||
if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER))
|
||||
{
|
||||
// Attempt to undo chicken.
|
||||
if (!Owner.Unmorph(Owner, MRF_UNDOBYTOMEOFPOWER))
|
||||
{
|
||||
if (!(mStyle & MRF_FAILNOTELEFRAG))
|
||||
Owner.DamageMobj(null, null, TELEFRAG_DAMAGE, 'Telefrag');
|
||||
Playerinfo p = Owner.player;
|
||||
if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYTOMEOFPOWER))
|
||||
{ // Attempt to undo chicken
|
||||
if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER))
|
||||
{ // Failed
|
||||
if (!(p.MorphStyle & MRF_FAILNOTELEFRAG))
|
||||
{
|
||||
Owner.DamageMobj (null, null, TELEFRAG_DAMAGE, 'Telefrag');
|
||||
}
|
||||
}
|
||||
else if (Owner.player)
|
||||
{
|
||||
Owner.A_StartSound("*evillaugh", CHAN_VOICE);
|
||||
else
|
||||
{ // Succeeded
|
||||
Owner.A_StartSound ("*evillaugh", CHAN_VOICE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return Super.Use(pickup);
|
||||
else
|
||||
{
|
||||
return Super.Use (pickup);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class PigPlayer : PlayerPawn
|
|||
|
||||
override void MorphPlayerThink ()
|
||||
{
|
||||
if ((player.MorphTics ? player.MorphTics : Random[PigPlayerThink]()) & 15)
|
||||
if (player.morphTics & 15)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
else
|
||||
{
|
||||
bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate).
|
||||
let realplayer = Owner.player; // Remember the identity of the player
|
||||
if (realplayer.mo.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash))
|
||||
{
|
||||
Owner = realplayer.mo; // Replace the new owner in our owner; safe because we are not attached to anything yet
|
||||
bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor)
|
||||
MorphedPlayer = realplayer; // Store the player identity (morphing clears the unmorphed actor's "player" field)
|
||||
}
|
||||
else // morph failed - give the caller an opportunity to fail the pickup completely
|
||||
{
|
||||
bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1922,16 +1921,24 @@ class PowerMorph : Powerup
|
|||
{
|
||||
Super.EndEffect();
|
||||
|
||||
// Abort if owner already destroyed or unmorphed.
|
||||
if (!Owner || !Owner.Alternative)
|
||||
// Abort if owner already destroyed or unmorphed
|
||||
if (Owner == null || MorphedPlayer == null || Owner.alternative == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Abort if owner is dead; their Die() method will
|
||||
// take care of any required unmorphing on death.
|
||||
if (Owner.player ? Owner.player.Health <= 0 : Owner.Health <= 0)
|
||||
if (MorphedPlayer.health <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.Unmorph(Owner, force: Owner.GetMorphStyle() & MRF_UNDOALWAYS);
|
||||
int savedMorphTics = MorphedPlayer.morphTics;
|
||||
MorphedPlayer.mo.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS));
|
||||
MorphedPlayer = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
{
|
||||
return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force);
|
||||
}
|
||||
else
|
||||
{
|
||||
let morphed = MorphedMonster(self);
|
||||
if (morphed)
|
||||
return morphed.UndoMonsterMorph(force);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool CheckUnmorph()
|
||||
{
|
||||
return UnmorphTime && UnmorphTime <= Level.Time && Unmorph(self, MRF_UNDOBYTIMEOUT);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
|
|
@ -156,265 +102,57 @@ extend class Actor
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
virtual bool MorphMonster(class<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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
extend class PlayerPawn
|
||||
{
|
||||
private native void Substitute(PlayerPawn replacement);
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// InitAllPowerupEffects
|
||||
// EndAllPowerupEffects
|
||||
//
|
||||
// Calls InitEffect() on every Powerup in the inventory list. Since these
|
||||
// functions can be overridden it's safest to store what's next in the item
|
||||
// list before calling it.
|
||||
// Calls EndEffect() on every Powerup in the inventory list.
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
void InitAllPowerupEffects()
|
||||
{
|
||||
for (Inventory item = Inv; item;)
|
||||
let item = Inv;
|
||||
while (item != null)
|
||||
{
|
||||
Inventory next = item.Inv;
|
||||
let power = Powerup(item);
|
||||
if (power)
|
||||
if (power != null)
|
||||
{
|
||||
power.InitEffect();
|
||||
|
||||
item = next;
|
||||
}
|
||||
item = item.Inv;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,14 +34,15 @@ extend class PlayerPawn
|
|||
|
||||
void EndAllPowerupEffects()
|
||||
{
|
||||
for (Inventory item = Inv; item;)
|
||||
let item = Inv;
|
||||
while (item != null)
|
||||
{
|
||||
Inventory next = item.Inv;
|
||||
let power = Powerup(item);
|
||||
if (power)
|
||||
if (power != null)
|
||||
{
|
||||
power.EndEffect();
|
||||
|
||||
item = next;
|
||||
}
|
||||
item = item.Inv;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,36 +52,46 @@ extend class PlayerPawn
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
virtual void ActivateMorphWeapon()
|
||||
virtual void ActivateMorphWeapon ()
|
||||
{
|
||||
if (player.ReadyWeapon)
|
||||
class<Weapon> morphweaponcls = MorphWeapon;
|
||||
player.PendingWeapon = WP_NOCHANGE;
|
||||
|
||||
if (player.ReadyWeapon != null)
|
||||
{
|
||||
let psp = player.GetPSprite(PSP_WEAPON);
|
||||
psp.y = WEAPONTOP;
|
||||
player.ReadyWeapon.ResetPSprite(psp);
|
||||
if (psp)
|
||||
{
|
||||
psp.y = WEAPONTOP;
|
||||
player.ReadyWeapon.ResetPSprite(psp);
|
||||
}
|
||||
}
|
||||
|
||||
class<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)
|
||||
player.SetPSprite(PSP_FLASH, null);
|
||||
if (player.ReadyWeapon != null)
|
||||
{
|
||||
player.SetPsprite(PSP_FLASH, null);
|
||||
}
|
||||
|
||||
player.PendingWeapon = WP_NOCHANGE;
|
||||
}
|
||||
|
|
@ -95,122 +107,125 @@ extend class PlayerPawn
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
virtual bool MorphPlayer(PlayerInfo activator, class<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 (!duration)
|
||||
duration = DEFMORPHTICS;
|
||||
|
||||
if (spawnType == GetClass())
|
||||
{
|
||||
// Player is already a beast.
|
||||
if (Alternative && bCanSuperMorph
|
||||
&& GetMorphTics() < duration - TICRATE
|
||||
&& !FindInventory("PowerWeaponLevel2", true))
|
||||
{
|
||||
// Make a super chicken.
|
||||
GiveInventoryType("PowerWeaponLevel2");
|
||||
if (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE)))
|
||||
{ // Immune when invulnerable unless this is a power we activated
|
||||
return false;
|
||||
}
|
||||
if (player.morphTics)
|
||||
{ // Player is already a beast
|
||||
if ((GetClass() == spawntype) && bCanSuperMorph
|
||||
&& (player.morphTics < (((duration) ? duration : DEFMORPHTICS) - TICRATE))
|
||||
&& FindInventory('PowerWeaponLevel2', true) == null)
|
||||
{ // Make a super chicken
|
||||
GiveInventoryType ('PowerWeaponLevel2');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE));
|
||||
if (!MorphInto(morphed))
|
||||
if (health <= 0)
|
||||
{ // Dead players cannot morph
|
||||
return false;
|
||||
}
|
||||
if (spawntype == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(spawntype is 'PlayerPawn'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (spawntype == GetClass())
|
||||
{
|
||||
if (morphed)
|
||||
morphed.Destroy();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE));
|
||||
|
||||
// Use GetClass in the event someone actually allows replacements.
|
||||
PreMorph(morphed, false);
|
||||
morphed.PreMorph(self, true);
|
||||
|
||||
morphed.EndAllPowerupEffects();
|
||||
|
||||
EndAllPowerupEffects();
|
||||
Substitute(morphed);
|
||||
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
||||
{
|
||||
morphed.Translation = Translation;
|
||||
|
||||
}
|
||||
if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR))
|
||||
{
|
||||
morphed.ChangeTid(tid);
|
||||
ChangeTid(0);
|
||||
}
|
||||
morphed.Angle = Angle;
|
||||
morphed.Pitch = Pitch; // Allow pitch here since mouse look in GZDoom is far more common than Heretic/Hexen.
|
||||
morphed.Target = Target;
|
||||
morphed.Tracer = Tracer;
|
||||
morphed.Master = Master;
|
||||
morphed.target = target;
|
||||
morphed.tracer = tracer;
|
||||
morphed.alternative = self;
|
||||
morphed.FriendPlayer = FriendPlayer;
|
||||
morphed.DesignatedTeam = DesignatedTeam;
|
||||
morphed.Score = Score;
|
||||
morphed.ScoreIcon = ScoreIcon;
|
||||
morphed.Health = morphed.SpawnHealth();
|
||||
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
||||
{
|
||||
morphed.ChangeTid(TID);
|
||||
ChangeTid(0);
|
||||
player.PremorphWeapon = player.ReadyWeapon;
|
||||
|
||||
morphed.special2 = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility
|
||||
morphed.player = player;
|
||||
|
||||
if (morphed.ViewHeight > player.viewheight && player.deltaviewheight == 0)
|
||||
{ // If the new view height is higher than the old one, start moving toward it.
|
||||
player.deltaviewheight = player.GetDeltaViewHeight();
|
||||
}
|
||||
|
||||
// special2 is no longer used here since Actors now have a proper field for it.
|
||||
morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE)
|
||||
| (bNoBlockmap * MPROP_NO_BLOCKMAP) | (bNoSector * MPROP_NO_SECTOR)
|
||||
| (bNoInteraction * MPROP_NO_INTERACTION) | (bInvisible * MPROP_INVIS);
|
||||
|
||||
morphed.bShadow |= bShadow;
|
||||
morphed.bNoGravity |= bNoGravity;
|
||||
morphed.bFly |= bFly;
|
||||
morphed.bGhost |= bGhost;
|
||||
|
||||
// Remove all armor.
|
||||
if (!(style & MRF_KEEPARMOR))
|
||||
{
|
||||
for (Inventory item = morphed.Inv; item;)
|
||||
{
|
||||
Inventory next = item.Inv;
|
||||
if (item is "Armor")
|
||||
item.DepleteOrDestroy();
|
||||
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Players store their morph behavior into their PlayerInfo unlike regular Actors which use the
|
||||
// morph properties. This is needed for backwards compatibility and to give the HUD info.
|
||||
let p = morphed.player;
|
||||
morphed.SetMorphTics(duration);
|
||||
morphed.SetMorphStyle(style);
|
||||
morphed.SetMorphExitFlash(exitFlash);
|
||||
p.MorphedPlayerClass = spawnType;
|
||||
p.PremorphWeapon = p.ReadyWeapon;
|
||||
p.Health = morphed.Health;
|
||||
p.Vel = (0.0, 0.0);
|
||||
// If the new view height is higher than the old one, start moving toward it.
|
||||
if (morphed.ViewHeight > p.ViewHeight && !p.DeltaViewHeight)
|
||||
p.DeltaViewHeight = p.GetDeltaViewHeight();
|
||||
|
||||
bNoInteraction = true;
|
||||
A_ChangeLinkFlags(true, true);
|
||||
|
||||
// Legacy
|
||||
bSolid = bShootable = false;
|
||||
if (enter_flash == null) enter_flash = 'TeleportFog';
|
||||
let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE);
|
||||
let p = player;
|
||||
player = null;
|
||||
alternative = morphed;
|
||||
bSolid = false;
|
||||
bShootable = false;
|
||||
bUnmorphed = true;
|
||||
bInvisible = true;
|
||||
|
||||
p.morphTics = (duration) ? duration : DEFMORPHTICS;
|
||||
|
||||
morphed.ClearFOVInterpolation();
|
||||
// [MH] Used by SBARINFO to speed up face drawing
|
||||
p.MorphedPlayerClass = spawntype;
|
||||
|
||||
p.MorphStyle = style;
|
||||
if (exit_flash == null) exit_flash = 'TeleportFog';
|
||||
p.MorphExitFlash = exit_flash;
|
||||
p.health = morphed.health;
|
||||
p.mo = morphed;
|
||||
p.vel = (0, 0);
|
||||
morphed.ObtainInventory (self);
|
||||
// Remove all armor
|
||||
for (Inventory item = morphed.Inv; item != null; )
|
||||
{
|
||||
let next = item.Inv;
|
||||
if (item is 'Armor')
|
||||
{
|
||||
item.DepleteOrDestroy();
|
||||
}
|
||||
item = next;
|
||||
}
|
||||
morphed.InitAllPowerupEffects();
|
||||
morphed.ActivateMorphWeapon();
|
||||
|
||||
morphed.ActivateMorphWeapon ();
|
||||
if (p.camera == self) // can this happen?
|
||||
{
|
||||
p.camera = morphed;
|
||||
}
|
||||
morphed.ClearFOVInterpolation();
|
||||
morphed.ScoreIcon = ScoreIcon; // [GRB]
|
||||
if (eflash)
|
||||
eflash.target = morphed;
|
||||
PostMorph(morphed, false); // No longer the current body
|
||||
morphed.PostMorph(self, true); // This is the current body
|
||||
|
||||
if (enterFlash)
|
||||
{
|
||||
Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TelefogHeight), ALLOW_REPLACE);
|
||||
if (fog)
|
||||
fog.Target = morphed;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -220,156 +235,400 @@ extend class PlayerPawn
|
|||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
virtual bool UndoPlayerMorph(PlayerInfo activator, EMorphFlags unmorphFlags = 0, bool force = false)
|
||||
virtual bool UndoPlayerMorph(playerinfo activator, int unmorphflag = 0, bool force = false)
|
||||
{
|
||||
if (!Alternative || bStayMorphed || Alternative.bStayMorphed)
|
||||
return false;
|
||||
|
||||
if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable
|
||||
&& (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING))))
|
||||
if (alternative == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let alt = PlayerPawn(Alternative);
|
||||
alt.SetOrigin(Pos, false);
|
||||
// Test if there's room to unmorph.
|
||||
if (!force && (PremorphProperties & MPROP_SOLID))
|
||||
{
|
||||
bool altSolid = alt.bSolid;
|
||||
bool isSolid = bSolid;
|
||||
bool isTouchy = bTouchy;
|
||||
let player = self.player;
|
||||
bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag);
|
||||
|
||||
alt.bSolid = true;
|
||||
bSolid = bTouchy = false;
|
||||
|
||||
bool res = alt.TestMobjLocation();
|
||||
|
||||
alt.bSolid = altSolid;
|
||||
bSolid = isSolid;
|
||||
bTouchy = isTouchy;
|
||||
|
||||
if (!res)
|
||||
{
|
||||
SetMorphTics(2 * TICRATE);
|
||||
return false;
|
||||
}
|
||||
if ((bInvulnerable) // If the player is invulnerable
|
||||
&& ((player != activator) // and either did not decide to unmorph,
|
||||
|| (!((player.MorphStyle & MRF_WHENINVULNERABLE) // or the morph style does not allow it
|
||||
|| (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it),
|
||||
{ // Then the player is immune to the unmorph.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MorphInto(alt))
|
||||
let altmo = PlayerPawn(alternative);
|
||||
altmo.SetOrigin (Pos, false);
|
||||
altmo.bSolid = true;
|
||||
bSolid = false;
|
||||
if (!force && !altmo.TestMobjLocation())
|
||||
{ // Didn't fit
|
||||
altmo.bSolid = false;
|
||||
bSolid = true;
|
||||
player.morphTics = 2*TICRATE;
|
||||
return false;
|
||||
}
|
||||
|
||||
PreUnmorph(alt, false); // This body's about to be left.
|
||||
alt.PreUnmorph(self, true); // This one's about to become current.
|
||||
PreUnmorph(altmo, false); // This body's about to be left.
|
||||
altmo.PreUnmorph(self, true); // This one's about to become current.
|
||||
|
||||
alt.EndAllPowerupEffects();
|
||||
// No longer using tracer as morph storage. That is what 'alternative' is for.
|
||||
// If the tracer has changed on the morph, change the original too.
|
||||
altmo.target = target;
|
||||
altmo.tracer = tracer;
|
||||
self.player = null;
|
||||
altmo.alternative = alternative = null;
|
||||
|
||||
// Remove the morph power if the morph is being undone prematurely.
|
||||
for (Inventory item = alt.Inv; item;)
|
||||
for (Inventory item = Inv; item != null;)
|
||||
{
|
||||
Inventory next = item.Inv;
|
||||
let next = item.Inv;
|
||||
if (item is "PowerMorph")
|
||||
{
|
||||
item.Destroy();
|
||||
|
||||
}
|
||||
item = next;
|
||||
}
|
||||
|
||||
alt.Angle = Angle;
|
||||
alt.Pitch = Pitch;
|
||||
alt.Target = Target;
|
||||
alt.Tracer = Tracer;
|
||||
alt.Master = Master;
|
||||
alt.FriendPlayer = FriendPlayer;
|
||||
alt.DesignatedTeam = DesignatedTeam;
|
||||
alt.Score = Score;
|
||||
alt.ScoreIcon = ScoreIcon;
|
||||
alt.ReactionTime = 18;
|
||||
alt.bSolid = (PremorphProperties & MPROP_SOLID);
|
||||
alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE);
|
||||
alt.bInvisible = (PremorphProperties & MPROP_INVIS);
|
||||
alt.bShadow = bShadow;
|
||||
alt.bNoGravity = bNoGravity;
|
||||
alt.bGhost = bGhost;
|
||||
alt.bFly = bFly;
|
||||
alt.Vel = (0.0, 0.0, Vel.Z);
|
||||
|
||||
alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION);
|
||||
alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR));
|
||||
|
||||
let p = alt.player;
|
||||
class<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();
|
||||
|
||||
let exit_flash = player.MorphExitFlash;
|
||||
bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON);
|
||||
bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES);
|
||||
|
||||
player.morphTics = 0;
|
||||
player.MorphedPlayerClass = null;
|
||||
player.MorphStyle = 0;
|
||||
player.MorphExitFlash = null;
|
||||
player.viewheight = altmo.ViewHeight;
|
||||
Inventory level2 = altmo.FindInventory("PowerWeaponLevel2", true);
|
||||
if (level2 != null)
|
||||
{
|
||||
level2.Destroy ();
|
||||
}
|
||||
|
||||
alt.SetMorphTics(0);
|
||||
alt.SetMorphStyle(0);
|
||||
alt.SetMorphExitFlash(null);
|
||||
p.MorphedPlayerClass = null;
|
||||
p.PremorphWeapon = null;
|
||||
p.ViewHeight = alt.ViewHeight;
|
||||
p.Vel = (0.0, 0.0);
|
||||
if (p.Health > 0 || (style & MRF_UNDOBYDEATHSAVES))
|
||||
p.Health = alt.Health = alt.SpawnHealth();
|
||||
else
|
||||
alt.Health = p.Health;
|
||||
|
||||
Inventory level2 = alt.FindInventory("PowerWeaponLevel2", true);
|
||||
if (level2)
|
||||
level2.Destroy();
|
||||
|
||||
let morphWeap = p.ReadyWeapon;
|
||||
if (premorphWeap)
|
||||
if ((player.health > 0) || undobydeathsaves)
|
||||
{
|
||||
premorphWeap.PostMorphWeapon();
|
||||
player.health = altmo.health = altmo.SpawnHealth();
|
||||
}
|
||||
else
|
||||
else // killed when morphed so stay dead
|
||||
{
|
||||
p.ReadyWeapon = null;
|
||||
p.PendingWeapon = WP_NOCHANGE;
|
||||
p.Refire = 0;
|
||||
altmo.health = player.health;
|
||||
}
|
||||
|
||||
if (style & MRF_LOSEACTUALWEAPON)
|
||||
player.mo = altmo;
|
||||
if (player.camera == self)
|
||||
{
|
||||
// Improved "lose morph weapon" semantics.
|
||||
class<Weapon> morphWeapCls = MorphWeapon;
|
||||
if (morphWeapCls)
|
||||
player.camera = altmo;
|
||||
}
|
||||
altmo.ClearFOVInterpolation();
|
||||
|
||||
// [MH]
|
||||
// If the player that was morphed is the one
|
||||
// taking events, reset up the face, if any;
|
||||
// this is only needed for old-skool skins
|
||||
// and for the original DOOM status bar.
|
||||
if (player == players[consoleplayer])
|
||||
{
|
||||
if (face != 'None')
|
||||
{
|
||||
let originalMorphWeapon = Weapon(alt.FindInventory(morphWeapCls));
|
||||
if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon)
|
||||
originalMorphWeapon.Destroy();
|
||||
// Assume root-level base skin to begin with
|
||||
let skinindex = 0;
|
||||
let skin = player.GetSkin();
|
||||
// If a custom skin was in use, then reload it
|
||||
// or else the base skin for the player class.
|
||||
if (skin >= PlayerClasses.Size () && skin < PlayerSkins.Size())
|
||||
{
|
||||
skinindex = skin;
|
||||
}
|
||||
else if (PlayerClasses.Size () > 1)
|
||||
{
|
||||
let whatami = altmo.GetClass();
|
||||
for (int i = 0; i < PlayerClasses.Size (); ++i)
|
||||
{
|
||||
if (PlayerClasses[i].Type == whatami)
|
||||
{
|
||||
skinindex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (morphWeap) // Old behaviour (not really useful now).
|
||||
|
||||
Actor eflash = null;
|
||||
if (exit_flash != null)
|
||||
{
|
||||
morphWeap.Destroy();
|
||||
eflash = Spawn(exit_flash, Vec3Angle(20., altmo.Angle, gameinfo.telefogheight), ALLOW_REPLACE);
|
||||
if (eflash) eflash.target = altmo;
|
||||
}
|
||||
|
||||
// Reset the base AC of the player's Hexen armor back to its default.
|
||||
let hexArmor = HexenArmor(alt.FindInventory("HexenArmor"));
|
||||
if (hexArmor)
|
||||
hexArmor.Slots[4] = alt.HexenArmor[0];
|
||||
|
||||
alt.ClearFOVInterpolation();
|
||||
alt.InitAllPowerupEffects();
|
||||
|
||||
PostUnmorph(alt, false); // This body is no longer current.
|
||||
alt.PostUnmorph(self, true); // altmo body is current.
|
||||
|
||||
if (exitFlash)
|
||||
WeaponSlots.SetupWeaponSlots(altmo); // Use original class's weapon slots.
|
||||
let beastweap = player.ReadyWeapon;
|
||||
if (player.PremorphWeapon != null)
|
||||
{
|
||||
Actor fog = Spawn(exitFlash, alt.Vec3Angle(20.0, alt.Angle, GameInfo.TelefogHeight), ALLOW_REPLACE);
|
||||
if (fog)
|
||||
fog.Target = alt;
|
||||
player.PremorphWeapon.PostMorphWeapon ();
|
||||
}
|
||||
else
|
||||
{
|
||||
player.ReadyWeapon = player.PendingWeapon = null;
|
||||
}
|
||||
if (correctweapon)
|
||||
{ // Better "lose morphed weapon" semantics
|
||||
class<Actor> morphweaponcls = MorphWeapon;
|
||||
if (morphweaponcls != null && morphweaponcls is 'Weapon')
|
||||
{
|
||||
let OriginalMorphWeapon = Weapon(altmo.FindInventory (morphweapon));
|
||||
if ((OriginalMorphWeapon != null) && (OriginalMorphWeapon.GivenAsMorphWeapon))
|
||||
{ // You don't get to keep your morphed weapon.
|
||||
if (OriginalMorphWeapon.SisterWeapon != null)
|
||||
{
|
||||
OriginalMorphWeapon.SisterWeapon.Destroy ();
|
||||
}
|
||||
OriginalMorphWeapon.Destroy ();
|
||||
}
|
||||
}
|
||||
}
|
||||
else // old behaviour (not really useful now)
|
||||
{ // Assumptions made here are no longer valid
|
||||
if (beastweap != null)
|
||||
{ // You don't get to keep your morphed weapon.
|
||||
if (beastweap.SisterWeapon != null)
|
||||
{
|
||||
beastweap.SisterWeapon.Destroy ();
|
||||
}
|
||||
beastweap.Destroy ();
|
||||
}
|
||||
}
|
||||
PostUnmorph(altmo, false); // This body is no longer current.
|
||||
altmo.PostUnmorph(self, true); // altmo body is current.
|
||||
Destroy ();
|
||||
// Restore playerclass armor to its normal amount.
|
||||
let hxarmor = HexenArmor(altmo.FindInventory('HexenArmor'));
|
||||
if (hxarmor != null)
|
||||
{
|
||||
hxarmor.Slots[4] = altmo.HexenArmor[0];
|
||||
}
|
||||
|
||||
Destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
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,32 +23,35 @@ class ArtiTeleport : Inventory
|
|||
Loop;
|
||||
}
|
||||
|
||||
override bool Use(bool pickup)
|
||||
override bool Use (bool pickup)
|
||||
{
|
||||
Vector3 dest;
|
||||
double destAngle;
|
||||
int destAngle;
|
||||
|
||||
if (deathmatch)
|
||||
[dest, destAngle] = Level.PickDeathmatchStart();
|
||||
else
|
||||
[dest, destAngle] = Level.PickPlayerStart(Owner.PlayerNumber());
|
||||
|
||||
if (!Level.UsePlayerStartZ)
|
||||
dest.Z = ONFLOORZ;
|
||||
|
||||
Owner.Teleport(dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG);
|
||||
|
||||
bool canLaugh = Owner.player != null;
|
||||
EMorphFlags mStyle = Owner.GetMorphStyle();
|
||||
if (Owner.Alternative && (mStyle & MRF_UNDOBYCHAOSDEVICE))
|
||||
{
|
||||
// Teleporting away will undo any morph effects (pig).
|
||||
if (!Owner.Unmorph(Owner, MRF_UNDOBYCHAOSDEVICE) && (mStyle & MRF_FAILNOLAUGH))
|
||||
canLaugh = false;
|
||||
[dest, destAngle] = level.PickDeathmatchStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
[dest, destAngle] = level.PickPlayerStart(Owner.PlayerNumber());
|
||||
}
|
||||
if (!level.useplayerstartz)
|
||||
dest.Z = ONFLOORZ;
|
||||
Owner.Teleport (dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG);
|
||||
bool canlaugh = true;
|
||||
Playerinfo p = Owner.player;
|
||||
if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYCHAOSDEVICE))
|
||||
{ // Teleporting away will undo any morph effects (pig)
|
||||
if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYCHAOSDEVICE) && (p.MorphStyle & MRF_FAILNOLAUGH))
|
||||
{
|
||||
canlaugh = false;
|
||||
}
|
||||
}
|
||||
if (canlaugh)
|
||||
{ // Full volume laugh
|
||||
Owner.A_StartSound ("*evillaugh", CHAN_VOICE, CHANF_DEFAULT, 1., ATTN_NONE);
|
||||
}
|
||||
|
||||
if (canLaugh)
|
||||
Owner.A_StartSound("*evillaugh", CHAN_VOICE, attenuation: ATTN_NONE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue