mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-29 07:12:36 +00:00
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 758 additions and 887 deletions
|
@ -1711,7 +1711,7 @@ int FLevelLocals::FinishTravel ()
|
||||||
pawn->flags2 &= ~MF2_BLASTED;
|
pawn->flags2 &= ~MF2_BLASTED;
|
||||||
if (oldpawn != nullptr)
|
if (oldpawn != nullptr)
|
||||||
{
|
{
|
||||||
PlayerPointerSubstitution (oldpawn, pawn, true);
|
StaticPointerSubstitution (oldpawn, pawn);
|
||||||
oldpawn->Destroy();
|
oldpawn->Destroy();
|
||||||
}
|
}
|
||||||
if (pawndup != NULL)
|
if (pawndup != NULL)
|
||||||
|
|
|
@ -479,7 +479,7 @@ FGameTexture *FMugShot::GetFace(player_t *player, const char *default_face, int
|
||||||
if (CurrentState != NULL)
|
if (CurrentState != NULL)
|
||||||
{
|
{
|
||||||
int skin = player->userinfo.GetSkin();
|
int skin = player->userinfo.GetSkin();
|
||||||
const char *skin_face = (stateflags & FMugShot::CUSTOM) ? nullptr : (player->mo->alternative != nullptr ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars());
|
const char *skin_face = (stateflags & FMugShot::CUSTOM) ? nullptr : (player->morphTics ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars());
|
||||||
return CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle);
|
return CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle);
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -626,14 +626,11 @@ public:
|
||||||
double plyrdmgfact = Pawn->DamageFactor;
|
double plyrdmgfact = Pawn->DamageFactor;
|
||||||
Pawn->DamageFactor = 1.;
|
Pawn->DamageFactor = 1.;
|
||||||
P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide);
|
P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide);
|
||||||
if (Pawn != nullptr)
|
|
||||||
{
|
|
||||||
Pawn->DamageFactor = plyrdmgfact;
|
Pawn->DamageFactor = plyrdmgfact;
|
||||||
if (Pawn->health <= 0)
|
if (Pawn->health <= 0)
|
||||||
{
|
{
|
||||||
Pawn->flags &= ~MF_SHOOTABLE;
|
Pawn->flags &= ~MF_SHOOTABLE;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Destroy();
|
Destroy();
|
||||||
}
|
}
|
||||||
// You'll probably never be able to catch this in a save game, but
|
// You'll probably never be able to catch this in a save game, but
|
||||||
|
|
|
@ -1138,7 +1138,7 @@ void FLevelLocals::UnSnapshotLevel(bool hubLoad)
|
||||||
// If this isn't the unmorphed original copy of a player, destroy it, because it's extra.
|
// If this isn't the unmorphed original copy of a player, destroy it, because it's extra.
|
||||||
for (i = 0; i < MAXPLAYERS; ++i)
|
for (i = 0; i < MAXPLAYERS; ++i)
|
||||||
{
|
{
|
||||||
if (PlayerInGame(i) && Players[i]->mo->alternative == pawn)
|
if (PlayerInGame(i) && Players[i]->morphTics && Players[i]->mo->alternative == pawn)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ bool P_MorphActor(AActor *activator, AActor *victim, PClassActor *ptype, PClassA
|
||||||
|
|
||||||
bool P_UnmorphActor(AActor *activator, AActor *morphed, int flags, bool force)
|
bool P_UnmorphActor(AActor *activator, AActor *morphed, int flags, bool force)
|
||||||
{
|
{
|
||||||
IFVIRTUALPTR(morphed, AActor, Unmorph)
|
IFVIRTUALPTR(morphed, AActor, UnMorph)
|
||||||
{
|
{
|
||||||
VMValue params[] = { morphed, activator, flags, force };
|
VMValue params[] = { morphed, activator, flags, force };
|
||||||
int retval;
|
int retval;
|
||||||
|
|
|
@ -26,8 +26,6 @@ enum
|
||||||
MORPH_UNDOBYTIMEOUT = 0x00001000, // Player unmorphs once countdown expires
|
MORPH_UNDOBYTIMEOUT = 0x00001000, // Player unmorphs once countdown expires
|
||||||
MORPH_UNDOALWAYS = 0x00002000, // Powerups must always unmorph, no matter what.
|
MORPH_UNDOALWAYS = 0x00002000, // Powerups must always unmorph, no matter what.
|
||||||
MORPH_TRANSFERTRANSLATION = 0x00004000, // Transfer translation from the original actor to the morphed one
|
MORPH_TRANSFERTRANSLATION = 0x00004000, // Transfer translation from the original actor to the morphed one
|
||||||
MORPH_KEEPARMOR = 0x00008000, // Don't lose current armor value when morphing.
|
|
||||||
MORPH_IGNOREINVULN = 0x00010000, // Completely ignore invulnerability status on players.
|
|
||||||
|
|
||||||
MORPH_STANDARDUNDOING = MORPH_UNDOBYTOMEOFPOWER | MORPH_UNDOBYCHAOSDEVICE | MORPH_UNDOBYTIMEOUT,
|
MORPH_STANDARDUNDOING = MORPH_UNDOBYTOMEOFPOWER | MORPH_UNDOBYCHAOSDEVICE | MORPH_UNDOBYTIMEOUT,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1747,8 +1747,8 @@ struct FTranslatedLineTarget
|
||||||
bool unlinked; // found by a trace that went through an unlinked portal.
|
bool unlinked; // found by a trace that went through an unlinked portal.
|
||||||
};
|
};
|
||||||
|
|
||||||
void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer, bool removeOld);
|
|
||||||
int MorphPointerSubstitution(AActor* from, AActor* to);
|
void StaticPointerSubstitution(AActor* old, AActor* notOld);
|
||||||
|
|
||||||
#define S_FREETARGMOBJ 1
|
#define S_FREETARGMOBJ 1
|
||||||
|
|
||||||
|
|
|
@ -3139,7 +3139,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_Pain)
|
||||||
PARAM_SELF_PROLOGUE(AActor);
|
PARAM_SELF_PROLOGUE(AActor);
|
||||||
|
|
||||||
// [RH] Vary player pain sounds depending on health (ala Quake2)
|
// [RH] Vary player pain sounds depending on health (ala Quake2)
|
||||||
if (self->player && self->alternative == nullptr)
|
if (self->player && self->player->morphTics == 0)
|
||||||
{
|
{
|
||||||
const char *pain_amount;
|
const char *pain_amount;
|
||||||
FSoundID sfx_id = NO_SOUND;
|
FSoundID sfx_id = NO_SOUND;
|
||||||
|
|
|
@ -313,54 +313,36 @@ EXTERN_CVAR (Int, fraglimit)
|
||||||
|
|
||||||
void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath)
|
void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath)
|
||||||
{
|
{
|
||||||
|
// Handle possible unmorph on death
|
||||||
bool wasgibbed = (health < GetGibHealth());
|
bool wasgibbed = (health < GetGibHealth());
|
||||||
|
|
||||||
// Check to see if unmorph Actors need to be killed as well. Originally this was always
|
|
||||||
// called but that puts an unnecessary burden on the modder to determine whether it's
|
|
||||||
// a valid call or not.
|
|
||||||
if (alternative != nullptr && !(flags & MF_UNMORPHED))
|
|
||||||
{
|
{
|
||||||
IFVIRTUAL(AActor, MorphedDeath)
|
IFVIRTUAL(AActor, MorphedDeath)
|
||||||
{
|
{
|
||||||
// Return values are no longer used to ensure things stay properly managed.
|
AActor *realthis = NULL;
|
||||||
AActor* const realMo = alternative;
|
int realstyle = 0;
|
||||||
int morphStyle = 0;
|
int realhealth = 0;
|
||||||
|
|
||||||
VMValue params[] = { this };
|
VMValue params[] = { this };
|
||||||
|
VMReturn returns[3];
|
||||||
|
returns[0].PointerAt((void**)&realthis);
|
||||||
|
returns[1].IntAt(&realstyle);
|
||||||
|
returns[2].IntAt(&realhealth);
|
||||||
|
VMCall(func, params, 1, returns, 3);
|
||||||
|
|
||||||
{
|
if (realthis && !(realstyle & MORPH_UNDOBYDEATHSAVES))
|
||||||
IFVM(Actor, GetMorphStyle)
|
|
||||||
{
|
|
||||||
VMReturn ret[] = { &morphStyle };
|
|
||||||
VMCall(func, params, 1, ret, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VMCall(func, params, 1, nullptr, 0);
|
|
||||||
|
|
||||||
// Kill the dummy Actor if it didn't unmorph, otherwise checking the morph flags. Player pawns need
|
|
||||||
// to stay, otherwise they won't respawn correctly.
|
|
||||||
if (realMo != nullptr && !(realMo->flags6 & MF6_KILLED)
|
|
||||||
&& ((alternative != nullptr && player == nullptr) || (alternative == nullptr && !(morphStyle & MORPH_UNDOBYDEATHSAVES))))
|
|
||||||
{
|
{
|
||||||
if (wasgibbed)
|
if (wasgibbed)
|
||||||
{
|
{
|
||||||
const int realGibHealth = realMo->GetGibHealth();
|
int realgibhealth = realthis->GetGibHealth();
|
||||||
if (realMo->health >= realGibHealth)
|
if (realthis->health >= realgibhealth)
|
||||||
realMo->health = realGibHealth - 1; // If morphed was gibbed, so must original be (where allowed).
|
|
||||||
}
|
|
||||||
else if (realMo->health > 0)
|
|
||||||
{
|
{
|
||||||
realMo->health = 0;
|
realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass appropriate damage information along when it's confirmed to die.
|
|
||||||
realMo->DamageTypeReceived = DamageTypeReceived;
|
|
||||||
realMo->DamageType = DamageType;
|
|
||||||
realMo->special1 = special1;
|
|
||||||
|
|
||||||
realMo->CallDie(source, inflictor, dmgflags, MeansOfDeath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +458,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf
|
||||||
++source->player->spreecount;
|
++source->player->spreecount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source->alternative != nullptr)
|
if (source->player->morphTics)
|
||||||
{ // Make a super chicken
|
{ // Make a super chicken
|
||||||
source->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2));
|
source->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2));
|
||||||
}
|
}
|
||||||
|
@ -1347,7 +1329,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
|
||||||
|
|
||||||
if (damage >= player->health && !telefragDamage
|
if (damage >= player->health && !telefragDamage
|
||||||
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
||||||
&& target->alternative == nullptr)
|
&& !player->morphTics)
|
||||||
{ // Try to use some inventory health
|
{ // Try to use some inventory health
|
||||||
P_AutoUseHealth (player, damage - player->health + 1);
|
P_AutoUseHealth (player, damage - player->health + 1);
|
||||||
}
|
}
|
||||||
|
@ -1481,7 +1463,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
|
||||||
// check for special fire damage or ice damage deaths
|
// check for special fire damage or ice damage deaths
|
||||||
if (mod == NAME_Fire)
|
if (mod == NAME_Fire)
|
||||||
{
|
{
|
||||||
if (player && target->alternative == nullptr)
|
if (player && !player->morphTics)
|
||||||
{ // Check for flame death
|
{ // Check for flame death
|
||||||
if (!inflictor ||
|
if (!inflictor ||
|
||||||
((target->health > -50) && (damage > 25)) ||
|
((target->health > -50) && (damage > 25)) ||
|
||||||
|
@ -1815,7 +1797,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain
|
||||||
}
|
}
|
||||||
if (damage >= player->health
|
if (damage >= player->health
|
||||||
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
&& (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch)
|
||||||
&& target->alternative == nullptr)
|
&& !player->morphTics)
|
||||||
{ // Try to use some inventory health
|
{ // Try to use some inventory health
|
||||||
P_AutoUseHealth(player, damage - player->health+1);
|
P_AutoUseHealth(player, damage - player->health+1);
|
||||||
}
|
}
|
||||||
|
@ -1845,7 +1827,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
target->special1 = damage;
|
target->special1 = damage;
|
||||||
if (player && target->alternative == nullptr)
|
if (player && !player->morphTics)
|
||||||
{ // Check for flame death
|
{ // Check for flame death
|
||||||
if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25))
|
if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25))
|
||||||
{
|
{
|
||||||
|
|
|
@ -819,7 +819,7 @@ int P_GetRealMaxHealth(AActor *actor, int max)
|
||||||
{
|
{
|
||||||
max = actor->GetMaxHealth(true);
|
max = actor->GetMaxHealth(true);
|
||||||
// [MH] First step in predictable generic morph effects
|
// [MH] First step in predictable generic morph effects
|
||||||
if (actor->alternative != nullptr)
|
if (player->morphTics)
|
||||||
{
|
{
|
||||||
if (player->MorphStyle & MORPH_FULLHEALTH)
|
if (player->MorphStyle & MORPH_FULLHEALTH)
|
||||||
{
|
{
|
||||||
|
@ -841,7 +841,7 @@ int P_GetRealMaxHealth(AActor *actor, int max)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Bonus health should be added on top of the item's limit.
|
// Bonus health should be added on top of the item's limit.
|
||||||
if (actor->alternative == nullptr || (player->MorphStyle & MORPH_ADDSTAMINA))
|
if (player->morphTics == 0 || (player->MorphStyle & MORPH_ADDSTAMINA))
|
||||||
{
|
{
|
||||||
max += actor->IntVar(NAME_BonusHealth);
|
max += actor->IntVar(NAME_BonusHealth);
|
||||||
}
|
}
|
||||||
|
@ -3788,22 +3788,6 @@ void AActor::Tick ()
|
||||||
static const uint8_t HereticScrollDirs[4] = { 6, 9, 1, 4 };
|
static const uint8_t HereticScrollDirs[4] = { 6, 9, 1, 4 };
|
||||||
static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 };
|
static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 };
|
||||||
|
|
||||||
// Check for Actor unmorphing, but only on the thing that is the morphed Actor.
|
|
||||||
// Players do their own special checking for this.
|
|
||||||
if (alternative != nullptr && !(flags & MF_UNMORPHED) && player == nullptr)
|
|
||||||
{
|
|
||||||
int res = false;
|
|
||||||
IFVIRTUAL(AActor, CheckUnmorph)
|
|
||||||
{
|
|
||||||
VMValue params[] = { this };
|
|
||||||
VMReturn ret[] = { &res };
|
|
||||||
VMCall(func, params, 1, ret, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freezetics > 0)
|
if (freezetics > 0)
|
||||||
{
|
{
|
||||||
freezetics--;
|
freezetics--;
|
||||||
|
@ -5098,16 +5082,6 @@ void AActor::CallDeactivate(AActor *activator)
|
||||||
|
|
||||||
void AActor::OnDestroy ()
|
void AActor::OnDestroy ()
|
||||||
{
|
{
|
||||||
// If the Actor is leaving behind a premorph Actor, make sure it gets cleaned up as
|
|
||||||
// well so it's not stuck in the map.
|
|
||||||
if (alternative != nullptr && !(flags & MF_UNMORPHED))
|
|
||||||
{
|
|
||||||
alternative->ClearCounters();
|
|
||||||
alternative->alternative = nullptr;
|
|
||||||
alternative->Destroy();
|
|
||||||
alternative = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [ZZ] call destroy event hook.
|
// [ZZ] call destroy event hook.
|
||||||
// note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook.
|
// note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook.
|
||||||
// but you can't really do that without utterly breaking the game, so it's ok.
|
// but you can't really do that without utterly breaking the game, so it's ok.
|
||||||
|
@ -5229,157 +5203,59 @@ extern bool demonew;
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
// This function is only designed for swapping player pawns
|
// This once was the main method for pointer cleanup, but
|
||||||
// over to their new ones upon changing levels or respawning. It SHOULD NOT be
|
// nowadays its only use is swapping out PlayerPawns.
|
||||||
// used for anything else! Do not export this functionality as it's
|
// This requires pointer fixing throughout all objects and a few
|
||||||
// meant strictly for internal usage.
|
// global variables, but it only needs to look at pointers that
|
||||||
|
// can point to a player.
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer, bool removeOld)
|
void StaticPointerSubstitution(AActor* old, AActor* notOld)
|
||||||
{
|
{
|
||||||
if (oldPlayer == nullptr || newPlayer == nullptr || oldPlayer == newPlayer
|
DObject* probe;
|
||||||
|| !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn))
|
size_t changed = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (old == nullptr) return;
|
||||||
|
|
||||||
|
// This is only allowed to replace players or swap out morphed monsters
|
||||||
|
if (!old->IsKindOf(NAME_PlayerPawn) || (notOld != nullptr && !notOld->IsKindOf(NAME_PlayerPawn)))
|
||||||
{
|
{
|
||||||
return;
|
if (notOld == nullptr) return;
|
||||||
|
if (!old->IsKindOf(NAME_MorphedMonster) && !notOld->IsKindOf(NAME_MorphedMonster)) return;
|
||||||
|
}
|
||||||
|
// Go through all objects.
|
||||||
|
i = 0; DObject* last = 0;
|
||||||
|
for (probe = GC::Root; probe != NULL; probe = probe->ObjNext)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
changed += probe->PointerSubstitution(old, notOld, true);
|
||||||
|
last = probe;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go through player infos.
|
// Go through players.
|
||||||
for (int i = 0; i < MAXPLAYERS; ++i)
|
for (i = 0; i < MAXPLAYERS; i++)
|
||||||
{
|
{
|
||||||
if (!oldPlayer->Level->PlayerInGame(i))
|
if (playeringame[i])
|
||||||
continue;
|
{
|
||||||
|
AActor* replacement = notOld;
|
||||||
|
auto& p = players[i];
|
||||||
|
|
||||||
auto p = oldPlayer->Level->Players[i];
|
if (p.mo == old) p.mo = replacement, changed++;
|
||||||
|
if (p.poisoner.ForceGet() == old) p.poisoner = replacement, changed++;
|
||||||
if (p->mo == oldPlayer)
|
if (p.attacker.ForceGet() == old) p.attacker = replacement, changed++;
|
||||||
p->mo = newPlayer;
|
if (p.camera.ForceGet() == old) p.camera = replacement, changed++;
|
||||||
if (p->poisoner == oldPlayer)
|
if (p.ConversationNPC.ForceGet() == old) p.ConversationNPC = replacement, changed++;
|
||||||
p->poisoner = newPlayer;
|
if (p.ConversationPC.ForceGet() == old) p.ConversationPC = replacement, changed++;
|
||||||
if (p->attacker == oldPlayer)
|
}
|
||||||
p->attacker = newPlayer;
|
|
||||||
if (p->camera == oldPlayer)
|
|
||||||
p->camera = newPlayer;
|
|
||||||
if (p->ConversationNPC == oldPlayer)
|
|
||||||
p->ConversationNPC = newPlayer;
|
|
||||||
if (p->ConversationPC == oldPlayer)
|
|
||||||
p->ConversationPC = newPlayer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go through sectors.
|
// Go through sectors. Only the level this actor belongs to is relevant.
|
||||||
for (auto& sec : oldPlayer->Level->sectors)
|
for (auto& sec : old->Level->sectors)
|
||||||
{
|
{
|
||||||
if (sec.SoundTarget == oldPlayer)
|
if (sec.SoundTarget == old) sec.SoundTarget = notOld;
|
||||||
sec.SoundTarget = newPlayer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all the remaining object pointers.
|
|
||||||
for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext)
|
|
||||||
probe->PointerSubstitution(oldPlayer, newPlayer, removeOld);
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// This has some extra barriers compared to PlayerPointerSubstitution to allow
|
|
||||||
// Actors to freely morph into other Actors which is its main usage.
|
|
||||||
// It also allows morphing to be more extendable from ZScript.
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
int MorphPointerSubstitution(AActor* from, AActor* to)
|
|
||||||
{
|
|
||||||
// Special care is taken here to make sure things marked as a dummy Actor for a morphed thing aren't
|
|
||||||
// allowed to be changed into other things. Anything being morphed into that's considered a player
|
|
||||||
// is automatically out of the question to ensure modders aren't swapping clients around.
|
|
||||||
if (from == nullptr || to == nullptr || from == to || to->player != nullptr
|
|
||||||
|| (from->flags & MF_UNMORPHED) // Another thing's dummy Actor, unmorphing the wrong way, etc.
|
|
||||||
|| (from->alternative == nullptr && to->alternative != nullptr) // Morphing into something that's already morphed.
|
|
||||||
|| (from->alternative != nullptr && from->alternative != to)) // Only allow something morphed to unmorph.
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool toIsPlayer = to->IsKindOf(NAME_PlayerPawn);
|
|
||||||
if (from->IsKindOf(NAME_PlayerPawn))
|
|
||||||
{
|
|
||||||
// Players are only allowed to turn into other valid player pawns. For
|
|
||||||
// valid pawns, make sure an actual player is changing into an empty one.
|
|
||||||
// Voodoo dolls aren't allowed to morph since that should be passed to
|
|
||||||
// the main player directly.
|
|
||||||
if (!toIsPlayer || from->player == nullptr || from->player->mo != from)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (toIsPlayer || from->player != nullptr
|
|
||||||
|| (from->IsKindOf(NAME_Inventory) && from->PointerVar<AActor>(NAME_Owner) != nullptr)
|
|
||||||
|| (to->IsKindOf(NAME_Inventory) && to->PointerVar<AActor>(NAME_Owner) != nullptr))
|
|
||||||
{
|
|
||||||
// Only allow items to be swapped around if they aren't currently owned. Also prevent non-players from
|
|
||||||
// turning into fake players.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the check is good, move the inventory items over. This should always be done when
|
|
||||||
// morphing to emulate Heretic/Hexen's behavior since those stored the inventory in their
|
|
||||||
// player structs.
|
|
||||||
IFVM(Actor, ObtainInventory)
|
|
||||||
{
|
|
||||||
VMValue params[] = { to, from };
|
|
||||||
VMCall(func, params, 2, nullptr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through player infos.
|
|
||||||
for (int i = 0; i < MAXPLAYERS; ++i)
|
|
||||||
{
|
|
||||||
if (!from->Level->PlayerInGame(i))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto p = from->Level->Players[i];
|
|
||||||
|
|
||||||
if (p->mo == from)
|
|
||||||
p->mo = to;
|
|
||||||
if (p->poisoner == from)
|
|
||||||
p->poisoner = to;
|
|
||||||
if (p->attacker == from)
|
|
||||||
p->attacker = to;
|
|
||||||
if (p->camera == from)
|
|
||||||
p->camera = to;
|
|
||||||
if (p->ConversationNPC == from)
|
|
||||||
p->ConversationNPC = to;
|
|
||||||
if (p->ConversationPC == from)
|
|
||||||
p->ConversationPC = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through sectors.
|
|
||||||
for (auto& sec : from->Level->sectors)
|
|
||||||
{
|
|
||||||
if (sec.SoundTarget == from)
|
|
||||||
sec.SoundTarget = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace any object pointers that are safe to swap around.
|
|
||||||
for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext)
|
|
||||||
probe->PointerSubstitution(from, to, false);
|
|
||||||
|
|
||||||
// Remaining maintenance related to morphing.
|
|
||||||
if (from->player != nullptr)
|
|
||||||
{
|
|
||||||
to->player = from->player;
|
|
||||||
from->player = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from->alternative != nullptr)
|
|
||||||
{
|
|
||||||
to->flags &= ~MF_UNMORPHED;
|
|
||||||
to->alternative = from->alternative = nullptr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
from->flags |= MF_UNMORPHED;
|
|
||||||
from->alternative = to;
|
|
||||||
to->alternative = from;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FLevelLocals::PlayerSpawnPickClass (int playernum)
|
void FLevelLocals::PlayerSpawnPickClass (int playernum)
|
||||||
|
@ -5653,7 +5529,7 @@ AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flag
|
||||||
if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr;
|
if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerPointerSubstitution (oldactor, p->mo, false);
|
StaticPointerSubstitution (oldactor, p->mo);
|
||||||
|
|
||||||
localEventManager->PlayerRespawned(PlayerNum(p));
|
localEventManager->PlayerRespawned(PlayerNum(p));
|
||||||
Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true);
|
Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true);
|
||||||
|
|
|
@ -693,7 +693,7 @@ bool player_t::Resurrect()
|
||||||
P_BringUpWeapon(this);
|
P_BringUpWeapon(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mo->alternative != nullptr)
|
if (morphTics)
|
||||||
{
|
{
|
||||||
P_UnmorphActor(mo, mo);
|
P_UnmorphActor(mo, mo);
|
||||||
}
|
}
|
||||||
|
@ -1172,7 +1172,7 @@ void P_CheckEnvironment(player_t *player)
|
||||||
P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo));
|
P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo));
|
||||||
}
|
}
|
||||||
if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) &&
|
if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) &&
|
||||||
player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && player->mo->alternative == nullptr &&
|
player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && !player->morphTics &&
|
||||||
player->mo->waterlevel == 0)
|
player->mo->waterlevel == 0)
|
||||||
{
|
{
|
||||||
auto id = S_FindSkinnedSound(player->mo, S_FindSound("*falling"));
|
auto id = S_FindSkinnedSound(player->mo, S_FindSound("*falling"));
|
||||||
|
|
|
@ -1814,15 +1814,6 @@ DEFINE_CLASS_PROPERTY(playerclass, S, PowerMorph)
|
||||||
defaults->PointerVar<PClassActor>(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
|
defaults->PointerVar<PClassActor>(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
DEFINE_CLASS_PROPERTY(monsterclass, S, PowerMorph)
|
|
||||||
{
|
|
||||||
PROP_STRING_PARM(str, 0);
|
|
||||||
defaults->PointerVar<PClassActor>(NAME_MonsterClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
|
@ -1693,11 +1693,28 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_BossDeath, A_BossDeath)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, MorphInto, MorphPointerSubstitution)
|
DEFINE_ACTION_FUNCTION_NATIVE(AActor, Substitute, StaticPointerSubstitution)
|
||||||
{
|
{
|
||||||
PARAM_SELF_PROLOGUE(AActor);
|
PARAM_SELF_PROLOGUE(AActor);
|
||||||
PARAM_OBJECT(to, AActor);
|
PARAM_OBJECT(replace, AActor);
|
||||||
ACTION_RETURN_INT(MorphPointerSubstitution(self, to));
|
StaticPointerSubstitution(self, replace);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_ACTION_FUNCTION_NATIVE(_PlayerPawn, Substitute, StaticPointerSubstitution)
|
||||||
|
{
|
||||||
|
PARAM_SELF_PROLOGUE(AActor);
|
||||||
|
PARAM_OBJECT(replace, AActor);
|
||||||
|
StaticPointerSubstitution(self, replace);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_ACTION_FUNCTION_NATIVE(_MorphedMonster, Substitute, StaticPointerSubstitution)
|
||||||
|
{
|
||||||
|
PARAM_SELF_PROLOGUE(AActor);
|
||||||
|
PARAM_OBJECT(replace, AActor);
|
||||||
|
StaticPointerSubstitution(self, replace);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetSpawnableType, P_GetSpawnableType)
|
DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetSpawnableType, P_GetSpawnableType)
|
||||||
|
|
|
@ -504,7 +504,7 @@ class Actor : Thinker native
|
||||||
virtual native bool Slam(Actor victim);
|
virtual native bool Slam(Actor victim);
|
||||||
virtual void Touch(Actor toucher) {}
|
virtual void Touch(Actor toucher) {}
|
||||||
virtual native void FallAndSink(double grav, double oldfloorz);
|
virtual native void FallAndSink(double grav, double oldfloorz);
|
||||||
native bool MorphInto(Actor morph);
|
private native void Substitute(Actor replacement);
|
||||||
native ui void DisplayNameTag();
|
native ui void DisplayNameTag();
|
||||||
native clearscope void DisableLocalRendering(uint playerNum, bool disable);
|
native clearscope void DisableLocalRendering(uint playerNum, bool disable);
|
||||||
native ui bool ShouldRenderLocally(); // Only clients get to check this, never the playsim.
|
native ui bool ShouldRenderLocally(); // Only clients get to check this, never the playsim.
|
||||||
|
@ -1410,7 +1410,7 @@ class Actor : Thinker native
|
||||||
bool grunted;
|
bool grunted;
|
||||||
|
|
||||||
// [RH] only make noise if alive
|
// [RH] only make noise if alive
|
||||||
if (self.health > 0 && !Alternative)
|
if (self.health > 0 && self.player.morphTics == 0)
|
||||||
{
|
{
|
||||||
grunted = false;
|
grunted = false;
|
||||||
// Why should this number vary by gravity?
|
// Why should this number vary by gravity?
|
||||||
|
|
|
@ -221,7 +221,7 @@ class ChickenPlayer : PlayerPawn
|
||||||
pspr.y = WEAPONTOP + player.chickenPeck / 2;
|
pspr.y = WEAPONTOP + player.chickenPeck / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((player.MorphTics ? player.MorphTics : Random[ChickenPlayerThink]()) & 15)
|
if (player.morphTics & 15)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,26 +66,28 @@ Class ArtiTomeOfPower : PowerupGiver
|
||||||
Loop;
|
Loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
override bool Use(bool pickup)
|
override bool Use (bool pickup)
|
||||||
{
|
{
|
||||||
EMorphFlags mStyle = Owner.GetMorphStyle();
|
Playerinfo p = Owner.player;
|
||||||
if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER))
|
if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYTOMEOFPOWER))
|
||||||
|
{ // Attempt to undo chicken
|
||||||
|
if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER))
|
||||||
|
{ // Failed
|
||||||
|
if (!(p.MorphStyle & MRF_FAILNOTELEFRAG))
|
||||||
{
|
{
|
||||||
// Attempt to undo chicken.
|
Owner.DamageMobj (null, null, TELEFRAG_DAMAGE, 'Telefrag');
|
||||||
if (!Owner.Unmorph(Owner, MRF_UNDOBYTOMEOFPOWER))
|
|
||||||
{
|
|
||||||
if (!(mStyle & 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 true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return Super.Use(pickup);
|
{
|
||||||
|
return Super.Use (pickup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ class PigPlayer : PlayerPawn
|
||||||
|
|
||||||
override void MorphPlayerThink ()
|
override void MorphPlayerThink ()
|
||||||
{
|
{
|
||||||
if ((player.MorphTics ? player.MorphTics : Random[PigPlayerThink]()) & 15)
|
if (player.morphTics & 15)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1870,14 +1870,13 @@ class PowerReflection : Powerup
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
//
|
//
|
||||||
// PowerMorph
|
// PowerMorph
|
||||||
// Now works with monsters too!
|
|
||||||
//
|
//
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
class PowerMorph : Powerup
|
class PowerMorph : Powerup
|
||||||
{
|
{
|
||||||
class<PlayerPawn> PlayerClass;
|
Class<PlayerPawn> PlayerClass;
|
||||||
class<Actor> MonsterClass, MorphFlash, UnmorphFlash;
|
Class<Actor> MorphFlash, UnMorphFlash;
|
||||||
int MorphStyle;
|
int MorphStyle;
|
||||||
PlayerInfo MorphedPlayer;
|
PlayerInfo MorphedPlayer;
|
||||||
|
|
||||||
|
@ -1896,19 +1895,19 @@ class PowerMorph : Powerup
|
||||||
{
|
{
|
||||||
Super.InitEffect();
|
Super.InitEffect();
|
||||||
|
|
||||||
if (!Owner)
|
if (Owner != null && Owner.player != null && PlayerClass != null)
|
||||||
return;
|
|
||||||
|
|
||||||
if (Owner.Morph(Owner, PlayerClass, MonsterClass, int.max, MorphStyle, MorphFlash, UnmorphFlash))
|
|
||||||
{
|
{
|
||||||
// Get the real owner; safe because we are not attached to anything yet.
|
let realplayer = Owner.player; // Remember the identity of the player
|
||||||
Owner = Owner.Alternative;
|
if (realplayer.mo.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash))
|
||||||
bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor).
|
{
|
||||||
MorphedPlayer = Owner.player;
|
Owner = realplayer.mo; // Replace the new owner in our owner; safe because we are not attached to anything yet
|
||||||
|
bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor)
|
||||||
|
MorphedPlayer = realplayer; // Store the player identity (morphing clears the unmorphed actor's "player" field)
|
||||||
}
|
}
|
||||||
else
|
else // morph failed - give the caller an opportunity to fail the pickup completely
|
||||||
{
|
{
|
||||||
bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate).
|
bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1922,16 +1921,24 @@ class PowerMorph : Powerup
|
||||||
{
|
{
|
||||||
Super.EndEffect();
|
Super.EndEffect();
|
||||||
|
|
||||||
// Abort if owner already destroyed or unmorphed.
|
// Abort if owner already destroyed or unmorphed
|
||||||
if (!Owner || !Owner.Alternative)
|
if (Owner == null || MorphedPlayer == null || Owner.alternative == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Abort if owner is dead; their Die() method will
|
// Abort if owner is dead; their Die() method will
|
||||||
// take care of any required unmorphing on death.
|
// take care of any required unmorphing on death.
|
||||||
if (Owner.player ? Owner.player.Health <= 0 : Owner.Health <= 0)
|
if (MorphedPlayer.health <= 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Owner.Unmorph(Owner, force: Owner.GetMorphStyle() & MRF_UNDOALWAYS);
|
int savedMorphTics = MorphedPlayer.morphTics;
|
||||||
|
MorphedPlayer.mo.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS));
|
||||||
MorphedPlayer = null;
|
MorphedPlayer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -262,7 +262,7 @@ class Weapon : StateProvider
|
||||||
}
|
}
|
||||||
let psp = player.GetPSprite(PSP_WEAPON);
|
let psp = player.GetPSprite(PSP_WEAPON);
|
||||||
if (!psp) return;
|
if (!psp) return;
|
||||||
if (Alternative || player.cheats & CF_INSTANTWEAPSWITCH)
|
if (player.morphTics || player.cheats & CF_INSTANTWEAPSWITCH)
|
||||||
{
|
{
|
||||||
psp.y = WEAPONBOTTOM;
|
psp.y = WEAPONBOTTOM;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,78 +23,8 @@
|
||||||
|
|
||||||
extend class Actor
|
extend class Actor
|
||||||
{
|
{
|
||||||
// Blockmap, sector, and no interaction are the only relevant flags but the old ones are kept around
|
|
||||||
// for legacy reasons.
|
|
||||||
enum EPremorphProperty
|
|
||||||
{
|
|
||||||
MPROP_SOLID = 1 << 1,
|
|
||||||
MPROP_SHOOTABLE = 1 << 2,
|
|
||||||
MPROP_NO_BLOCKMAP = 1 << 3,
|
|
||||||
MPROP_NO_SECTOR = 1 << 4,
|
|
||||||
MPROP_NO_INTERACTION = 1 << 5,
|
|
||||||
MPROP_INVIS = 1 << 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
int UnmorphTime;
|
|
||||||
EMorphFlags MorphFlags;
|
|
||||||
class<Actor> MorphExitFlash;
|
|
||||||
EPremorphProperty PremorphProperties;
|
|
||||||
|
|
||||||
// Players still track these separately for legacy reasons.
|
|
||||||
void SetMorphStyle(EMorphFlags flags)
|
|
||||||
{
|
|
||||||
if (player)
|
|
||||||
player.MorphStyle = flags;
|
|
||||||
else
|
|
||||||
MorphFlags = flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearscope EMorphFlags GetMorphStyle() const
|
|
||||||
{
|
|
||||||
return player ? player.MorphStyle : MorphFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetMorphExitFlash(class<Actor> flash)
|
|
||||||
{
|
|
||||||
if (player)
|
|
||||||
player.MorphExitFlash = flash;
|
|
||||||
else
|
|
||||||
MorphExitFlash = flash;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearscope class<Actor> GetMorphExitFlash() const
|
|
||||||
{
|
|
||||||
return player ? player.MorphExitFlash : MorphExitFlash;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetMorphTics(int dur)
|
|
||||||
{
|
|
||||||
if (player)
|
|
||||||
player.MorphTics = dur;
|
|
||||||
else
|
|
||||||
UnmorphTime = Level.Time + dur;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearscope int GetMorphTics() const
|
|
||||||
{
|
|
||||||
if (player)
|
|
||||||
return player.MorphTics;
|
|
||||||
|
|
||||||
if (UnmorphTime <= 0)
|
|
||||||
return UnmorphTime;
|
|
||||||
|
|
||||||
return UnmorphTime > Level.Time ? UnmorphTime - Level.Time : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function doesn't return anything anymore since allowing so would be too volatile
|
|
||||||
// for morphing management. Instead it's now a function that lets special actions occur
|
|
||||||
// when a morphed Actor dies.
|
|
||||||
virtual Actor, int, int MorphedDeath()
|
virtual Actor, int, int MorphedDeath()
|
||||||
{
|
{
|
||||||
EMorphFlags mStyle = GetMorphStyle();
|
|
||||||
if (mStyle & MRF_UNDOBYDEATH)
|
|
||||||
Unmorph(self, force: mStyle & MRF_UNDOBYDEATHFORCED);
|
|
||||||
|
|
||||||
return null, 0, 0;
|
return null, 0, 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,12 +40,16 @@ extend class Actor
|
||||||
//
|
//
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
virtual bool Morph(Actor activator, class<PlayerPawn> playerClass, class<Actor> monsterClass, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog")
|
virtual bool Morph(Actor activator, class<PlayerPawn> playerclass, class<MorphedMonster> monsterclass, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
|
||||||
{
|
{
|
||||||
if (player)
|
if (player != null && player.mo != null && playerclass != null)
|
||||||
return player.mo.MorphPlayer(activator ? activator.player : null, playerClass, duration, style, morphFlash, unmorphFlash);
|
{
|
||||||
|
return player.mo.MorphPlayer(activator? activator.player : null, playerclass, duration, style, morphflash, unmorphflash);
|
||||||
return MorphMonster(monsterClass, duration, style, morphFlash, unmorphFlash);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return MorphMonster(monsterclass, duration, style, morphflash, unmorphflash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
@ -124,9 +58,18 @@ extend class Actor
|
||||||
//
|
//
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
bool A_Morph(class<Actor> type, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog")
|
bool A_Morph(class<Actor> type, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
|
||||||
{
|
{
|
||||||
return Morph(self, (class<PlayerPawn>)(type), type, duration, style, morphFlash, unmorphFlash);
|
if (self.player != null)
|
||||||
|
{
|
||||||
|
let playerclass = (class<PlayerPawn>)(type);
|
||||||
|
if (playerclass && self.player.mo != null) return player.mo.MorphPlayer(self.player, playerclass, duration, style, morphflash, unmorphflash);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return MorphMonster(type, duration, style, morphflash, unmorphflash);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
@ -135,18 +78,21 @@ extend class Actor
|
||||||
//
|
//
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
virtual bool Unmorph(Actor activator, EMorphFlags flags = 0, bool force = false)
|
virtual bool UnMorph(Actor activator, int flags, bool force)
|
||||||
{
|
{
|
||||||
if (player)
|
if (player)
|
||||||
return player.mo.UndoPlayerMorph(activator ? activator.player : null, flags, force);
|
|
||||||
|
|
||||||
return UndoMonsterMorph(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool CheckUnmorph()
|
|
||||||
{
|
{
|
||||||
return UnmorphTime && UnmorphTime <= Level.Time && Unmorph(self, MRF_UNDOBYTIMEOUT);
|
return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let morphed = MorphedMonster(self);
|
||||||
|
if (morphed)
|
||||||
|
return morphed.UndoMonsterMorph(force);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
|
@ -156,265 +102,57 @@ extend class Actor
|
||||||
//
|
//
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
virtual bool MorphMonster(class<Actor> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
|
virtual bool MorphMonster (Class<Actor> spawntype, int duration, int style, Class<Actor> enter_flash, Class<Actor> exit_flash)
|
||||||
{
|
{
|
||||||
if (player || !bIsMonster || !spawnType || bDontMorph || Health <= 0 || spawnType == GetClass())
|
if (player || spawntype == NULL || bDontMorph || !bIsMonster || !(spawntype is 'MorphedMonster'))
|
||||||
return false;
|
|
||||||
|
|
||||||
Actor morphed = Spawn(spawnType, Pos, ALLOW_REPLACE);
|
|
||||||
if (!MorphInto(morphed))
|
|
||||||
{
|
{
|
||||||
if (morphed)
|
|
||||||
{
|
|
||||||
morphed.ClearCounters();
|
|
||||||
morphed.Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let morphed = MorphedMonster(Spawn (spawntype, Pos, NO_REPLACE));
|
||||||
|
|
||||||
// [MC] Notify that we're just about to start the transfer.
|
// [MC] Notify that we're just about to start the transfer.
|
||||||
PreMorph(morphed, false); // False: No longer the current.
|
PreMorph(morphed, false); // False: No longer the current.
|
||||||
morphed.PreMorph(self, true); // True: Becoming this actor.
|
morphed.PreMorph(self, true); // True: Becoming this actor.
|
||||||
|
|
||||||
|
Substitute (morphed);
|
||||||
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
||||||
|
{
|
||||||
morphed.Translation = Translation;
|
morphed.Translation = Translation;
|
||||||
|
}
|
||||||
|
morphed.ChangeTid(tid);
|
||||||
|
ChangeTid(0);
|
||||||
morphed.Angle = Angle;
|
morphed.Angle = Angle;
|
||||||
|
morphed.UnmorphedMe = self;
|
||||||
morphed.Alpha = Alpha;
|
morphed.Alpha = Alpha;
|
||||||
morphed.RenderStyle = RenderStyle;
|
morphed.RenderStyle = RenderStyle;
|
||||||
|
morphed.Score = Score;
|
||||||
|
|
||||||
|
morphed.UnmorphTime = level.time + ((duration) ? duration : DEFMORPHTICS) + random[morphmonst]();
|
||||||
|
morphed.MorphStyle = style;
|
||||||
|
morphed.MorphExitFlash = (exit_flash) ? exit_flash : (class<Actor>)("TeleportFog");
|
||||||
|
morphed.FlagsSave = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility
|
||||||
|
|
||||||
|
morphed.special = special;
|
||||||
|
morphed.args[0] = args[0];
|
||||||
|
morphed.args[1] = args[1];
|
||||||
|
morphed.args[2] = args[2];
|
||||||
|
morphed.args[3] = args[3];
|
||||||
|
morphed.args[4] = args[4];
|
||||||
|
morphed.CopyFriendliness (self, true);
|
||||||
morphed.bShadow |= bShadow;
|
morphed.bShadow |= bShadow;
|
||||||
morphed.bGhost |= bGhost;
|
morphed.bGhost |= bGhost;
|
||||||
morphed.Score = Score;
|
special = 0;
|
||||||
morphed.Special = Special;
|
bSolid = false;
|
||||||
for (int i; i < 5; ++i)
|
bShootable = false;
|
||||||
morphed.Args[i] = Args[i];
|
bUnmorphed = true;
|
||||||
|
|
||||||
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
|
||||||
{
|
|
||||||
morphed.ChangeTID(TID);
|
|
||||||
ChangeTID(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
morphed.Tracer = Tracer;
|
|
||||||
morphed.Master = Master;
|
|
||||||
morphed.CopyFriendliness(self, true);
|
|
||||||
|
|
||||||
// Remove all armor.
|
|
||||||
if (!(style & MRF_KEEPARMOR))
|
|
||||||
{
|
|
||||||
for (Inventory item = morphed.Inv; item;)
|
|
||||||
{
|
|
||||||
Inventory next = item.Inv;
|
|
||||||
if (item is "Armor")
|
|
||||||
item.DepleteOrDestroy();
|
|
||||||
|
|
||||||
item = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
morphed.SetMorphTics((duration ? duration : DEFMORPHTICS) + Random[morphmonst]());
|
|
||||||
morphed.SetMorphStyle(style);
|
|
||||||
morphed.SetMorphExitFlash(exitFlash);
|
|
||||||
morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE)
|
|
||||||
| (bNoBlockmap * MPROP_NO_BLOCKMAP) | (bNoSector * MPROP_NO_SECTOR)
|
|
||||||
| (bNoInteraction * MPROP_NO_INTERACTION) | (bInvisible * MPROP_INVIS);
|
|
||||||
|
|
||||||
// This is just here for backwards compatibility as MorphedMonster used to be required.
|
|
||||||
let morphMon = MorphedMonster(morphed);
|
|
||||||
if (morphMon)
|
|
||||||
{
|
|
||||||
morphMon.UnmorphedMe = morphMon.Alternative;
|
|
||||||
morphMon.MorphStyle = morphMon.GetMorphStyle();
|
|
||||||
morphMon.FlagsSave = morphMon.PremorphProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
Special = 0;
|
|
||||||
bNoInteraction = true;
|
|
||||||
A_ChangeLinkFlags(true, true);
|
|
||||||
|
|
||||||
// Legacy
|
|
||||||
bInvisible = true;
|
bInvisible = true;
|
||||||
bSolid = bShootable = false;
|
let eflash = Spawn(enter_flash ? enter_flash : (class<Actor>)("TeleportFog"), Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE);
|
||||||
|
if (eflash)
|
||||||
|
eflash.target = morphed;
|
||||||
PostMorph(morphed, false);
|
PostMorph(morphed, false);
|
||||||
morphed.PostMorph(self, true);
|
morphed.PostMorph(self, true);
|
||||||
|
|
||||||
if (enterFlash)
|
|
||||||
{
|
|
||||||
Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE);
|
|
||||||
if (fog)
|
|
||||||
fog.Target = morphed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// FUNC P_UndoMonsterMorph
|
|
||||||
//
|
|
||||||
// Returns true if the monster unmorphs.
|
|
||||||
//
|
|
||||||
//----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
virtual bool UndoMonsterMorph(bool force = false)
|
|
||||||
{
|
|
||||||
if (!Alternative || bStayMorphed || Alternative.bStayMorphed)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Actor alt = Alternative;
|
|
||||||
alt.SetOrigin(Pos, false);
|
|
||||||
if (!force && (PremorphProperties & MPROP_SOLID))
|
|
||||||
{
|
|
||||||
bool altSolid = alt.bSolid;
|
|
||||||
bool isSolid = bSolid;
|
|
||||||
bool isTouchy = bTouchy;
|
|
||||||
|
|
||||||
alt.bSolid = true;
|
|
||||||
bSolid = bTouchy = false;
|
|
||||||
|
|
||||||
bool res = alt.TestMobjLocation();
|
|
||||||
|
|
||||||
alt.bSolid = altSolid;
|
|
||||||
bSolid = isSolid;
|
|
||||||
bTouchy = isTouchy;
|
|
||||||
|
|
||||||
// Didn't fit.
|
|
||||||
if (!res)
|
|
||||||
{
|
|
||||||
SetMorphTics(5 * TICRATE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MorphInto(alt))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
PreUnmorph(alt, false);
|
|
||||||
alt.PreUnmorph(self, true);
|
|
||||||
|
|
||||||
// Check to see if it had a powerup that caused it to morph.
|
|
||||||
for (Inventory item = alt.Inv; item;)
|
|
||||||
{
|
|
||||||
Inventory next = item.Inv;
|
|
||||||
if (item is "PowerMorph")
|
|
||||||
item.Destroy();
|
|
||||||
|
|
||||||
item = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
alt.Angle = Angle;
|
|
||||||
alt.bShadow = bShadow;
|
|
||||||
alt.bGhost = bGhost;
|
|
||||||
alt.bSolid = (PremorphProperties & MPROP_SOLID);
|
|
||||||
alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE);
|
|
||||||
alt.bInvisible = (PremorphProperties & MPROP_INVIS);
|
|
||||||
alt.Vel = Vel;
|
|
||||||
alt.Score = Score;
|
|
||||||
|
|
||||||
alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION);
|
|
||||||
alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR));
|
|
||||||
|
|
||||||
EMorphFlags style = GetMorphStyle();
|
|
||||||
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
|
||||||
{
|
|
||||||
alt.ChangeTID(TID);
|
|
||||||
ChangeTID(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
alt.Special = Special;
|
|
||||||
for (int i; i < 5; ++i)
|
|
||||||
alt.Args[i] = Args[i];
|
|
||||||
|
|
||||||
alt.Tracer = Tracer;
|
|
||||||
alt.Master = Master;
|
|
||||||
alt.CopyFriendliness(self, true, false);
|
|
||||||
if (Health > 0 || (style & MRF_UNDOBYDEATHSAVES))
|
|
||||||
alt.Health = alt.SpawnHealth();
|
|
||||||
else
|
|
||||||
alt.Health = Health;
|
|
||||||
|
|
||||||
Special = 0;
|
|
||||||
|
|
||||||
PostUnmorph(alt, false); // From is false here: Leaving the caller's body.
|
|
||||||
alt.PostUnmorph(self, true); // True here: Entering this body from here.
|
|
||||||
|
|
||||||
if (MorphExitFlash)
|
|
||||||
{
|
|
||||||
Actor fog = Spawn(MorphExitFlash, alt.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE);
|
|
||||||
if (fog)
|
|
||||||
fog.Target = alt;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearCounters();
|
|
||||||
Destroy();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
class MorphProjectile : Actor
|
|
||||||
{
|
|
||||||
class<PlayerPawn> PlayerClass;
|
|
||||||
class<Actor> MonsterClass, MorphFlash, UnmorphFlash;
|
|
||||||
int Duration, MorphStyle;
|
|
||||||
|
|
||||||
Default
|
|
||||||
{
|
|
||||||
Damage 1;
|
|
||||||
Projectile;
|
|
||||||
MorphProjectile.MorphFlash "TeleportFog";
|
|
||||||
MorphProjectile.UnmorphFlash "TeleportFog";
|
|
||||||
|
|
||||||
-ACTIVATEIMPACT
|
|
||||||
-ACTIVATEPCROSS
|
|
||||||
}
|
|
||||||
|
|
||||||
override int DoSpecialDamage(Actor victim, int dmg, Name dmgType)
|
|
||||||
{
|
|
||||||
victim.Morph(Target, PlayerClass, MonsterClass, Duration, MorphStyle, MorphFlash, UnmorphFlash);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
//
|
|
||||||
// This class is redundant as it's no longer necessary for monsters to
|
|
||||||
// morph but is kept here for compatibility. Its previous fields either exist
|
|
||||||
// in the base Actor now or are just a shell for the actual fields
|
|
||||||
// which either already existed and weren't used for some reason or needed a
|
|
||||||
// better name.
|
|
||||||
//
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
class MorphedMonster : Actor
|
|
||||||
{
|
|
||||||
Actor UnmorphedMe;
|
|
||||||
EMorphFlags MorphStyle;
|
|
||||||
EPremorphProperty FlagsSave;
|
|
||||||
|
|
||||||
Default
|
|
||||||
{
|
|
||||||
Monster;
|
|
||||||
|
|
||||||
+FLOORCLIP
|
|
||||||
-COUNTKILL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure changes to these are propogated correctly when unmorphing. This is only
|
|
||||||
// set for monsters since originally players and this class were the only ones
|
|
||||||
// that were considered valid for morphing.
|
|
||||||
override bool UndoMonsterMorph(bool force)
|
|
||||||
{
|
|
||||||
Alternative = UnmorphedMe;
|
|
||||||
SetMorphStyle(MorphStyle);
|
|
||||||
PremorphProperties = FlagsSave;
|
|
||||||
return super.UndoMonsterMorph(force);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ class PlayerPawn : Actor
|
||||||
double SideMove1, SideMove2;
|
double SideMove1, SideMove2;
|
||||||
TextureID ScoreIcon;
|
TextureID ScoreIcon;
|
||||||
int SpawnMask;
|
int SpawnMask;
|
||||||
Name MorphWeapon; // This should really be a class<Weapon> but it's too late to change now.
|
Name MorphWeapon;
|
||||||
double AttackZOffset; // attack height, relative to player center
|
double AttackZOffset; // attack height, relative to player center
|
||||||
double UseRange; // [NS] Distance at which player can +use
|
double UseRange; // [NS] Distance at which player can +use
|
||||||
double AirCapacity; // Multiplier for air supply underwater.
|
double AirCapacity; // Multiplier for air supply underwater.
|
||||||
|
@ -477,7 +477,7 @@ class PlayerPawn : Actor
|
||||||
let player = self.player;
|
let player = self.player;
|
||||||
if (!player) return;
|
if (!player) return;
|
||||||
if ((player.WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled.
|
if ((player.WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled.
|
||||||
Alternative) // Morphed classes cannot change weapons.
|
player.morphTics != 0) // Morphed classes cannot change weapons.
|
||||||
{ // ...so throw away any pending weapon requests.
|
{ // ...so throw away any pending weapon requests.
|
||||||
player.PendingWeapon = WP_NOCHANGE;
|
player.PendingWeapon = WP_NOCHANGE;
|
||||||
}
|
}
|
||||||
|
@ -651,7 +651,7 @@ class PlayerPawn : Actor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Alternative)
|
if (player.morphTics)
|
||||||
{
|
{
|
||||||
bob = 0;
|
bob = 0;
|
||||||
}
|
}
|
||||||
|
@ -1084,7 +1084,7 @@ class PlayerPawn : Actor
|
||||||
|
|
||||||
virtual bool CanCrouch() const
|
virtual bool CanCrouch() const
|
||||||
{
|
{
|
||||||
return !Alternative || bCrouchableMorph;
|
return player.morphTics == 0 || bCrouchableMorph;
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
|
@ -1250,7 +1250,7 @@ class PlayerPawn : Actor
|
||||||
side *= SideMove2;
|
side *= SideMove2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Alternative)
|
if (!player.morphTics)
|
||||||
{
|
{
|
||||||
double factor = 1.;
|
double factor = 1.;
|
||||||
for(let it = Inv; it != null; it = it.Inv)
|
for(let it = Inv; it != null; it = it.Inv)
|
||||||
|
@ -1546,15 +1546,15 @@ class PlayerPawn : Actor
|
||||||
{
|
{
|
||||||
let player = self.player;
|
let player = self.player;
|
||||||
// Morph counter
|
// Morph counter
|
||||||
if (Alternative)
|
if (player.morphTics)
|
||||||
{
|
{
|
||||||
if (player.chickenPeck)
|
if (player.chickenPeck)
|
||||||
{ // Chicken attack counter
|
{ // Chicken attack counter
|
||||||
player.chickenPeck -= 3;
|
player.chickenPeck -= 3;
|
||||||
}
|
}
|
||||||
if (player.MorphTics && !--player.MorphTics)
|
if (!--player.morphTics)
|
||||||
{ // Attempt to undo the chicken/pig
|
{ // Attempt to undo the chicken/pig
|
||||||
Unmorph(self, MRF_UNDOBYTIMEOUT);
|
player.mo.UndoPlayerMorph(player, MRF_UNDOBYTIMEOUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1679,7 +1679,7 @@ class PlayerPawn : Actor
|
||||||
player.jumpTics = 0;
|
player.jumpTics = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Alternative && !(player.cheats & CF_PREDICTING))
|
if (player.morphTics && !(player.cheats & CF_PREDICTING))
|
||||||
{
|
{
|
||||||
MorphPlayerThink ();
|
MorphPlayerThink ();
|
||||||
}
|
}
|
||||||
|
@ -2060,9 +2060,9 @@ class PlayerPawn : Actor
|
||||||
Inventory item, next;
|
Inventory item, next;
|
||||||
let p = player;
|
let p = player;
|
||||||
|
|
||||||
if (Alternative)
|
if (p.morphTics != 0)
|
||||||
{ // Undo morph
|
{ // Undo morph
|
||||||
Unmorph(self, force: true);
|
Unmorph(self, 0, true);
|
||||||
}
|
}
|
||||||
// 'self' will be no longer valid from here on in case of an unmorph
|
// 'self' will be no longer valid from here on in case of an unmorph
|
||||||
let me = p.mo;
|
let me = p.mo;
|
||||||
|
@ -2869,15 +2869,23 @@ struct PlayerInfo native play // self is what internally is known as player_t
|
||||||
native clearscope bool HasWeaponsInSlot(int slot) const;
|
native clearscope bool HasWeaponsInSlot(int slot) const;
|
||||||
|
|
||||||
// The actual implementation is on PlayerPawn where it can be overridden. Use that directly in the future.
|
// The actual implementation is on PlayerPawn where it can be overridden. Use that directly in the future.
|
||||||
deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(PlayerInfo activator, class<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
|
deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(playerinfo p, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> exit_flash = null)
|
||||||
{
|
{
|
||||||
return mo ? mo.MorphPlayer(activator, spawnType, duration, style, enterFlash, exitFlash) : false;
|
if (mo != null)
|
||||||
|
{
|
||||||
|
return mo.MorphPlayer(p, spawntype, duration, style, enter_flash, exit_flash);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This somehow got its arguments mixed up. 'self' should have been the player to be unmorphed, not the activator
|
// This somehow got its arguments mixed up. 'self' should have been the player to be unmorphed, not the activator
|
||||||
deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(PlayerInfo player, EMorphFlags unmorphFlags = 0, bool force = false)
|
deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(playerinfo player, int unmorphflag = 0, bool force = false)
|
||||||
{
|
{
|
||||||
return player.mo ? player.mo.UndoPlayerMorph(self, unmorphFlags, force) : false;
|
if (player.mo != null)
|
||||||
|
{
|
||||||
|
return player.mo.UndoPlayerMorph(self, unmorphflag, force);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
deprecated("3.7", "DropWeapon() should be used on a PlayerPawn object") void DropWeapon()
|
deprecated("3.7", "DropWeapon() should be used on a PlayerPawn object") void DropWeapon()
|
||||||
|
|
|
@ -426,40 +426,35 @@ extend class PlayerPawn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
virtual String CheatMorph(class<PlayerPawn> morphClass, bool undo)
|
virtual String CheatMorph(class<PlayerPawn> morphClass, bool quickundo)
|
||||||
{
|
{
|
||||||
// Set the standard morph style for the current game
|
let oldclass = GetClass();
|
||||||
EMorphFlags style = MRF_UNDOBYTOMEOFPOWER;
|
|
||||||
if (GameInfo.GameType == GAME_Hexen)
|
|
||||||
style |= MRF_UNDOBYCHAOSDEVICE;
|
|
||||||
|
|
||||||
if (Alternative)
|
// Set the standard morph style for the current game
|
||||||
|
int style = MRF_UNDOBYTOMEOFPOWER;
|
||||||
|
if (gameinfo.gametype == GAME_Hexen) style |= MRF_UNDOBYCHAOSDEVICE;
|
||||||
|
|
||||||
|
if (player.morphTics)
|
||||||
{
|
{
|
||||||
class<PlayerPawn> cls = GetClass();
|
if (UndoPlayerMorph (player))
|
||||||
Actor mo = Alternative;
|
|
||||||
if (!undo || Unmorph(self))
|
|
||||||
{
|
{
|
||||||
if ((!undo && Morph(self, morphClass, null, 0, style))
|
if (!quickundo && oldclass != morphclass && MorphPlayer (player, morphclass, 0, style))
|
||||||
|| (undo && morphClass != cls && mo.Morph(mo, morphClass, null, 0, style)))
|
|
||||||
{
|
{
|
||||||
return StringTable.Localize("$TXT_STRANGER");
|
return StringTable.Localize("$TXT_STRANGER");
|
||||||
}
|
}
|
||||||
|
|
||||||
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 StringTable.Localize("$TXT_STRANGE");
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void CheatTakeWeaps()
|
virtual void CheatTakeWeaps()
|
||||||
{
|
{
|
||||||
if (Alternative || health <= 0)
|
if (player.morphTics || health <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
extend class PlayerPawn
|
extend class PlayerPawn
|
||||||
{
|
{
|
||||||
|
private native void Substitute(PlayerPawn replacement);
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
//
|
//
|
||||||
// InitAllPowerupEffects
|
// EndAllPowerupEffects
|
||||||
//
|
//
|
||||||
// Calls InitEffect() on every Powerup in the inventory list. Since these
|
// Calls EndEffect() on every Powerup in the inventory list.
|
||||||
// functions can be overridden it's safest to store what's next in the item
|
|
||||||
// list before calling it.
|
|
||||||
//
|
//
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
void InitAllPowerupEffects()
|
void InitAllPowerupEffects()
|
||||||
{
|
{
|
||||||
for (Inventory item = Inv; item;)
|
let item = Inv;
|
||||||
|
while (item != null)
|
||||||
{
|
{
|
||||||
Inventory next = item.Inv;
|
|
||||||
let power = Powerup(item);
|
let power = Powerup(item);
|
||||||
if (power)
|
if (power != null)
|
||||||
|
{
|
||||||
power.InitEffect();
|
power.InitEffect();
|
||||||
|
}
|
||||||
item = next;
|
item = item.Inv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,54 +34,65 @@ extend class PlayerPawn
|
||||||
|
|
||||||
void EndAllPowerupEffects()
|
void EndAllPowerupEffects()
|
||||||
{
|
{
|
||||||
for (Inventory item = Inv; item;)
|
let item = Inv;
|
||||||
|
while (item != null)
|
||||||
{
|
{
|
||||||
Inventory next = item.Inv;
|
|
||||||
let power = Powerup(item);
|
let power = Powerup(item);
|
||||||
if (power)
|
if (power != null)
|
||||||
power.EndEffect();
|
|
||||||
|
|
||||||
item = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
virtual void ActivateMorphWeapon()
|
|
||||||
{
|
{
|
||||||
if (player.ReadyWeapon)
|
power.EndEffect();
|
||||||
|
}
|
||||||
|
item = item.Inv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
virtual void ActivateMorphWeapon ()
|
||||||
|
{
|
||||||
|
class<Weapon> morphweaponcls = MorphWeapon;
|
||||||
|
player.PendingWeapon = WP_NOCHANGE;
|
||||||
|
|
||||||
|
if (player.ReadyWeapon != null)
|
||||||
{
|
{
|
||||||
let psp = player.GetPSprite(PSP_WEAPON);
|
let psp = player.GetPSprite(PSP_WEAPON);
|
||||||
|
if (psp)
|
||||||
|
{
|
||||||
psp.y = WEAPONTOP;
|
psp.y = WEAPONTOP;
|
||||||
player.ReadyWeapon.ResetPSprite(psp);
|
player.ReadyWeapon.ResetPSprite(psp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class<Weapon> morphWeapCls = MorphWeapon;
|
if (morphweaponcls == null || !(morphweaponcls is 'Weapon'))
|
||||||
if (!morphWeapCls)
|
{ // No weapon at all while morphed!
|
||||||
{
|
|
||||||
player.ReadyWeapon = null;
|
player.ReadyWeapon = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
player.ReadyWeapon = Weapon(FindInventory(morphWeapCls));
|
player.ReadyWeapon = Weapon(FindInventory (morphweaponcls));
|
||||||
if (!player.ReadyWeapon)
|
if (player.ReadyWeapon == null)
|
||||||
{
|
{
|
||||||
player.ReadyWeapon = Weapon(GiveInventoryType(morphWeapCls));
|
player.ReadyWeapon = Weapon(GiveInventoryType (morphweaponcls));
|
||||||
if (player.ReadyWeapon)
|
if (player.ReadyWeapon != null)
|
||||||
player.ReadyWeapon.GivenAsMorphWeapon = true; // Flag is used only by new morphWeap semantics in UndoPlayerMorph
|
{
|
||||||
|
player.ReadyWeapon.GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in UndoPlayerMorph
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (player.ReadyWeapon != null)
|
||||||
|
{
|
||||||
|
player.SetPsprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.ReadyWeapon)
|
if (player.ReadyWeapon != null)
|
||||||
player.SetPSprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState());
|
{
|
||||||
|
player.SetPsprite(PSP_FLASH, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.ReadyWeapon)
|
|
||||||
player.SetPSprite(PSP_FLASH, null);
|
|
||||||
|
|
||||||
player.PendingWeapon = WP_NOCHANGE;
|
player.PendingWeapon = WP_NOCHANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,122 +107,125 @@ extend class PlayerPawn
|
||||||
//
|
//
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
virtual bool MorphPlayer(PlayerInfo activator, class<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
|
virtual bool MorphPlayer(playerinfo activator, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> exit_flash = null)
|
||||||
{
|
{
|
||||||
if (!player || !spawnType || bDontMorph || player.Health <= 0
|
if (bDontMorph)
|
||||||
|| (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))))
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE)))
|
||||||
|
{ // Immune when invulnerable unless this is a power we activated
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (player.morphTics)
|
||||||
|
{ // Player is already a beast
|
||||||
|
if ((GetClass() == spawntype) && bCanSuperMorph
|
||||||
|
&& (player.morphTics < (((duration) ? duration : DEFMORPHTICS) - TICRATE))
|
||||||
|
&& FindInventory('PowerWeaponLevel2', true) == null)
|
||||||
|
{ // Make a super chicken
|
||||||
|
GiveInventoryType ('PowerWeaponLevel2');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (health <= 0)
|
||||||
|
{ // Dead players cannot morph
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (spawntype == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(spawntype is 'PlayerPawn'))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (spawntype == GetClass())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!duration)
|
let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE));
|
||||||
duration = DEFMORPHTICS;
|
|
||||||
|
|
||||||
if (spawnType == GetClass())
|
|
||||||
{
|
|
||||||
// Player is already a beast.
|
|
||||||
if (Alternative && bCanSuperMorph
|
|
||||||
&& GetMorphTics() < duration - TICRATE
|
|
||||||
&& !FindInventory("PowerWeaponLevel2", true))
|
|
||||||
{
|
|
||||||
// Make a super chicken.
|
|
||||||
GiveInventoryType("PowerWeaponLevel2");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE));
|
|
||||||
if (!MorphInto(morphed))
|
|
||||||
{
|
|
||||||
if (morphed)
|
|
||||||
morphed.Destroy();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Use GetClass in the event someone actually allows replacements.
|
||||||
PreMorph(morphed, false);
|
PreMorph(morphed, false);
|
||||||
morphed.PreMorph(self, true);
|
morphed.PreMorph(self, true);
|
||||||
|
|
||||||
morphed.EndAllPowerupEffects();
|
EndAllPowerupEffects();
|
||||||
|
Substitute(morphed);
|
||||||
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
||||||
|
{
|
||||||
morphed.Translation = Translation;
|
morphed.Translation = Translation;
|
||||||
|
}
|
||||||
|
if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR))
|
||||||
|
{
|
||||||
|
morphed.ChangeTid(tid);
|
||||||
|
ChangeTid(0);
|
||||||
|
}
|
||||||
morphed.Angle = Angle;
|
morphed.Angle = Angle;
|
||||||
morphed.Pitch = Pitch; // Allow pitch here since mouse look in GZDoom is far more common than Heretic/Hexen.
|
morphed.target = target;
|
||||||
morphed.Target = Target;
|
morphed.tracer = tracer;
|
||||||
morphed.Tracer = Tracer;
|
morphed.alternative = self;
|
||||||
morphed.Master = Master;
|
|
||||||
morphed.FriendPlayer = FriendPlayer;
|
morphed.FriendPlayer = FriendPlayer;
|
||||||
morphed.DesignatedTeam = DesignatedTeam;
|
morphed.DesignatedTeam = DesignatedTeam;
|
||||||
morphed.Score = Score;
|
morphed.Score = Score;
|
||||||
morphed.ScoreIcon = ScoreIcon;
|
player.PremorphWeapon = player.ReadyWeapon;
|
||||||
morphed.Health = morphed.SpawnHealth();
|
|
||||||
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
morphed.special2 = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility
|
||||||
{
|
morphed.player = player;
|
||||||
morphed.ChangeTid(TID);
|
|
||||||
ChangeTid(0);
|
if (morphed.ViewHeight > player.viewheight && player.deltaviewheight == 0)
|
||||||
|
{ // If the new view height is higher than the old one, start moving toward it.
|
||||||
|
player.deltaviewheight = player.GetDeltaViewHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
// special2 is no longer used here since Actors now have a proper field for it.
|
|
||||||
morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE)
|
|
||||||
| (bNoBlockmap * MPROP_NO_BLOCKMAP) | (bNoSector * MPROP_NO_SECTOR)
|
|
||||||
| (bNoInteraction * MPROP_NO_INTERACTION) | (bInvisible * MPROP_INVIS);
|
|
||||||
|
|
||||||
morphed.bShadow |= bShadow;
|
morphed.bShadow |= bShadow;
|
||||||
morphed.bNoGravity |= bNoGravity;
|
morphed.bNoGravity |= bNoGravity;
|
||||||
morphed.bFly |= bFly;
|
morphed.bFly |= bFly;
|
||||||
morphed.bGhost |= bGhost;
|
morphed.bGhost |= bGhost;
|
||||||
|
|
||||||
// Remove all armor.
|
if (enter_flash == null) enter_flash = 'TeleportFog';
|
||||||
if (!(style & MRF_KEEPARMOR))
|
let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE);
|
||||||
{
|
let p = player;
|
||||||
for (Inventory item = morphed.Inv; item;)
|
player = null;
|
||||||
{
|
alternative = morphed;
|
||||||
Inventory next = item.Inv;
|
bSolid = false;
|
||||||
if (item is "Armor")
|
bShootable = false;
|
||||||
item.DepleteOrDestroy();
|
bUnmorphed = true;
|
||||||
|
|
||||||
item = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Players store their morph behavior into their PlayerInfo unlike regular Actors which use the
|
|
||||||
// morph properties. This is needed for backwards compatibility and to give the HUD info.
|
|
||||||
let p = morphed.player;
|
|
||||||
morphed.SetMorphTics(duration);
|
|
||||||
morphed.SetMorphStyle(style);
|
|
||||||
morphed.SetMorphExitFlash(exitFlash);
|
|
||||||
p.MorphedPlayerClass = spawnType;
|
|
||||||
p.PremorphWeapon = p.ReadyWeapon;
|
|
||||||
p.Health = morphed.Health;
|
|
||||||
p.Vel = (0.0, 0.0);
|
|
||||||
// If the new view height is higher than the old one, start moving toward it.
|
|
||||||
if (morphed.ViewHeight > p.ViewHeight && !p.DeltaViewHeight)
|
|
||||||
p.DeltaViewHeight = p.GetDeltaViewHeight();
|
|
||||||
|
|
||||||
bNoInteraction = true;
|
|
||||||
A_ChangeLinkFlags(true, true);
|
|
||||||
|
|
||||||
// Legacy
|
|
||||||
bSolid = bShootable = false;
|
|
||||||
bInvisible = true;
|
bInvisible = true;
|
||||||
|
|
||||||
morphed.ClearFOVInterpolation();
|
p.morphTics = (duration) ? duration : DEFMORPHTICS;
|
||||||
morphed.InitAllPowerupEffects();
|
|
||||||
morphed.ActivateMorphWeapon();
|
|
||||||
|
|
||||||
|
// [MH] Used by SBARINFO to speed up face drawing
|
||||||
|
p.MorphedPlayerClass = spawntype;
|
||||||
|
|
||||||
|
p.MorphStyle = style;
|
||||||
|
if (exit_flash == null) exit_flash = 'TeleportFog';
|
||||||
|
p.MorphExitFlash = exit_flash;
|
||||||
|
p.health = morphed.health;
|
||||||
|
p.mo = morphed;
|
||||||
|
p.vel = (0, 0);
|
||||||
|
morphed.ObtainInventory (self);
|
||||||
|
// Remove all armor
|
||||||
|
for (Inventory item = morphed.Inv; item != null; )
|
||||||
|
{
|
||||||
|
let next = item.Inv;
|
||||||
|
if (item is 'Armor')
|
||||||
|
{
|
||||||
|
item.DepleteOrDestroy();
|
||||||
|
}
|
||||||
|
item = next;
|
||||||
|
}
|
||||||
|
morphed.InitAllPowerupEffects();
|
||||||
|
morphed.ActivateMorphWeapon ();
|
||||||
|
if (p.camera == self) // can this happen?
|
||||||
|
{
|
||||||
|
p.camera = morphed;
|
||||||
|
}
|
||||||
|
morphed.ClearFOVInterpolation();
|
||||||
|
morphed.ScoreIcon = ScoreIcon; // [GRB]
|
||||||
|
if (eflash)
|
||||||
|
eflash.target = morphed;
|
||||||
PostMorph(morphed, false); // No longer the current body
|
PostMorph(morphed, false); // No longer the current body
|
||||||
morphed.PostMorph(self, true); // This is the current body
|
morphed.PostMorph(self, true); // This is the current body
|
||||||
|
|
||||||
if (enterFlash)
|
|
||||||
{
|
|
||||||
Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TelefogHeight), ALLOW_REPLACE);
|
|
||||||
if (fog)
|
|
||||||
fog.Target = morphed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,156 +235,400 @@ extend class PlayerPawn
|
||||||
//
|
//
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
|
|
||||||
virtual bool UndoPlayerMorph(PlayerInfo activator, EMorphFlags unmorphFlags = 0, bool force = false)
|
virtual bool UndoPlayerMorph(playerinfo activator, int unmorphflag = 0, bool force = false)
|
||||||
{
|
{
|
||||||
if (!Alternative || bStayMorphed || Alternative.bStayMorphed)
|
if (alternative == null)
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable
|
|
||||||
&& (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING))))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let alt = PlayerPawn(Alternative);
|
let player = self.player;
|
||||||
alt.SetOrigin(Pos, false);
|
bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag);
|
||||||
// Test if there's room to unmorph.
|
|
||||||
if (!force && (PremorphProperties & MPROP_SOLID))
|
|
||||||
{
|
|
||||||
bool altSolid = alt.bSolid;
|
|
||||||
bool isSolid = bSolid;
|
|
||||||
bool isTouchy = bTouchy;
|
|
||||||
|
|
||||||
alt.bSolid = true;
|
if ((bInvulnerable) // If the player is invulnerable
|
||||||
bSolid = bTouchy = false;
|
&& ((player != activator) // and either did not decide to unmorph,
|
||||||
|
|| (!((player.MorphStyle & MRF_WHENINVULNERABLE) // or the morph style does not allow it
|
||||||
bool res = alt.TestMobjLocation();
|
|| (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it),
|
||||||
|
{ // Then the player is immune to the unmorph.
|
||||||
alt.bSolid = altSolid;
|
|
||||||
bSolid = isSolid;
|
|
||||||
bTouchy = isTouchy;
|
|
||||||
|
|
||||||
if (!res)
|
|
||||||
{
|
|
||||||
SetMorphTics(2 * TICRATE);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let altmo = PlayerPawn(alternative);
|
||||||
|
altmo.SetOrigin (Pos, false);
|
||||||
|
altmo.bSolid = true;
|
||||||
|
bSolid = false;
|
||||||
|
if (!force && !altmo.TestMobjLocation())
|
||||||
|
{ // Didn't fit
|
||||||
|
altmo.bSolid = false;
|
||||||
|
bSolid = true;
|
||||||
|
player.morphTics = 2*TICRATE;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MorphInto(alt))
|
PreUnmorph(altmo, false); // This body's about to be left.
|
||||||
return false;
|
altmo.PreUnmorph(self, true); // This one's about to become current.
|
||||||
|
|
||||||
PreUnmorph(alt, false); // This body's about to be left.
|
// No longer using tracer as morph storage. That is what 'alternative' is for.
|
||||||
alt.PreUnmorph(self, true); // This one's about to become current.
|
// If the tracer has changed on the morph, change the original too.
|
||||||
|
altmo.target = target;
|
||||||
alt.EndAllPowerupEffects();
|
altmo.tracer = tracer;
|
||||||
|
self.player = null;
|
||||||
|
altmo.alternative = alternative = null;
|
||||||
|
|
||||||
// Remove the morph power if the morph is being undone prematurely.
|
// Remove the morph power if the morph is being undone prematurely.
|
||||||
for (Inventory item = alt.Inv; item;)
|
for (Inventory item = Inv; item != null;)
|
||||||
{
|
{
|
||||||
Inventory next = item.Inv;
|
let next = item.Inv;
|
||||||
if (item is "PowerMorph")
|
if (item is "PowerMorph")
|
||||||
|
{
|
||||||
item.Destroy();
|
item.Destroy();
|
||||||
|
}
|
||||||
item = next;
|
item = next;
|
||||||
}
|
}
|
||||||
|
EndAllPowerupEffects();
|
||||||
alt.Angle = Angle;
|
altmo.ObtainInventory (self);
|
||||||
alt.Pitch = Pitch;
|
Substitute(altmo);
|
||||||
alt.Target = Target;
|
if ((tid != 0) && (player.MorphStyle & MRF_NEWTIDBEHAVIOUR))
|
||||||
alt.Tracer = Tracer;
|
|
||||||
alt.Master = Master;
|
|
||||||
alt.FriendPlayer = FriendPlayer;
|
|
||||||
alt.DesignatedTeam = DesignatedTeam;
|
|
||||||
alt.Score = Score;
|
|
||||||
alt.ScoreIcon = ScoreIcon;
|
|
||||||
alt.ReactionTime = 18;
|
|
||||||
alt.bSolid = (PremorphProperties & MPROP_SOLID);
|
|
||||||
alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE);
|
|
||||||
alt.bInvisible = (PremorphProperties & MPROP_INVIS);
|
|
||||||
alt.bShadow = bShadow;
|
|
||||||
alt.bNoGravity = bNoGravity;
|
|
||||||
alt.bGhost = bGhost;
|
|
||||||
alt.bFly = bFly;
|
|
||||||
alt.Vel = (0.0, 0.0, Vel.Z);
|
|
||||||
|
|
||||||
alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION);
|
|
||||||
alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR));
|
|
||||||
|
|
||||||
let p = alt.player;
|
|
||||||
class<Actor> exitFlash = alt.GetMorphExitFlash();
|
|
||||||
EMorphFlags style = alt.GetMorphStyle();
|
|
||||||
Weapon premorphWeap = p.PremorphWeapon;
|
|
||||||
|
|
||||||
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
|
||||||
{
|
{
|
||||||
alt.ChangeTid(TID);
|
altmo.ChangeTid(tid);
|
||||||
ChangeTID(0);
|
}
|
||||||
|
altmo.Angle = Angle;
|
||||||
|
altmo.player = player;
|
||||||
|
altmo.reactiontime = 18;
|
||||||
|
altmo.bSolid = !!(special2 & 2);
|
||||||
|
altmo.bShootable = !!(special2 & 4);
|
||||||
|
altmo.bInvisible = !!(special2 & 0x40);
|
||||||
|
altmo.Vel = (0, 0, Vel.Z);
|
||||||
|
player.Vel = (0, 0);
|
||||||
|
altmo.floorz = floorz;
|
||||||
|
altmo.bShadow = bShadow;
|
||||||
|
altmo.bNoGravity = bNoGravity;
|
||||||
|
altmo.bGhost = bGhost;
|
||||||
|
altmo.bUnmorphed = false;
|
||||||
|
altmo.Score = Score;
|
||||||
|
altmo.InitAllPowerupEffects();
|
||||||
|
|
||||||
|
let exit_flash = player.MorphExitFlash;
|
||||||
|
bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON);
|
||||||
|
bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES);
|
||||||
|
|
||||||
|
player.morphTics = 0;
|
||||||
|
player.MorphedPlayerClass = null;
|
||||||
|
player.MorphStyle = 0;
|
||||||
|
player.MorphExitFlash = null;
|
||||||
|
player.viewheight = altmo.ViewHeight;
|
||||||
|
Inventory level2 = altmo.FindInventory("PowerWeaponLevel2", true);
|
||||||
|
if (level2 != null)
|
||||||
|
{
|
||||||
|
level2.Destroy ();
|
||||||
}
|
}
|
||||||
|
|
||||||
alt.SetMorphTics(0);
|
if ((player.health > 0) || undobydeathsaves)
|
||||||
alt.SetMorphStyle(0);
|
|
||||||
alt.SetMorphExitFlash(null);
|
|
||||||
p.MorphedPlayerClass = null;
|
|
||||||
p.PremorphWeapon = null;
|
|
||||||
p.ViewHeight = alt.ViewHeight;
|
|
||||||
p.Vel = (0.0, 0.0);
|
|
||||||
if (p.Health > 0 || (style & MRF_UNDOBYDEATHSAVES))
|
|
||||||
p.Health = alt.Health = alt.SpawnHealth();
|
|
||||||
else
|
|
||||||
alt.Health = p.Health;
|
|
||||||
|
|
||||||
Inventory level2 = alt.FindInventory("PowerWeaponLevel2", true);
|
|
||||||
if (level2)
|
|
||||||
level2.Destroy();
|
|
||||||
|
|
||||||
let morphWeap = p.ReadyWeapon;
|
|
||||||
if (premorphWeap)
|
|
||||||
{
|
{
|
||||||
premorphWeap.PostMorphWeapon();
|
player.health = altmo.health = altmo.SpawnHealth();
|
||||||
|
}
|
||||||
|
else // killed when morphed so stay dead
|
||||||
|
{
|
||||||
|
altmo.health = player.health;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.mo = altmo;
|
||||||
|
if (player.camera == self)
|
||||||
|
{
|
||||||
|
player.camera = altmo;
|
||||||
|
}
|
||||||
|
altmo.ClearFOVInterpolation();
|
||||||
|
|
||||||
|
// [MH]
|
||||||
|
// If the player that was morphed is the one
|
||||||
|
// taking events, reset up the face, if any;
|
||||||
|
// this is only needed for old-skool skins
|
||||||
|
// and for the original DOOM status bar.
|
||||||
|
if (player == players[consoleplayer])
|
||||||
|
{
|
||||||
|
if (face != 'None')
|
||||||
|
{
|
||||||
|
// Assume root-level base skin to begin with
|
||||||
|
let skinindex = 0;
|
||||||
|
let skin = player.GetSkin();
|
||||||
|
// If a custom skin was in use, then reload it
|
||||||
|
// or else the base skin for the player class.
|
||||||
|
if (skin >= PlayerClasses.Size () && skin < PlayerSkins.Size())
|
||||||
|
{
|
||||||
|
skinindex = skin;
|
||||||
|
}
|
||||||
|
else if (PlayerClasses.Size () > 1)
|
||||||
|
{
|
||||||
|
let whatami = altmo.GetClass();
|
||||||
|
for (int i = 0; i < PlayerClasses.Size (); ++i)
|
||||||
|
{
|
||||||
|
if (PlayerClasses[i].Type == whatami)
|
||||||
|
{
|
||||||
|
skinindex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Actor eflash = null;
|
||||||
|
if (exit_flash != null)
|
||||||
|
{
|
||||||
|
eflash = Spawn(exit_flash, Vec3Angle(20., altmo.Angle, gameinfo.telefogheight), ALLOW_REPLACE);
|
||||||
|
if (eflash) eflash.target = altmo;
|
||||||
|
}
|
||||||
|
WeaponSlots.SetupWeaponSlots(altmo); // Use original class's weapon slots.
|
||||||
|
let beastweap = player.ReadyWeapon;
|
||||||
|
if (player.PremorphWeapon != null)
|
||||||
|
{
|
||||||
|
player.PremorphWeapon.PostMorphWeapon ();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
p.ReadyWeapon = null;
|
player.ReadyWeapon = player.PendingWeapon = null;
|
||||||
p.PendingWeapon = WP_NOCHANGE;
|
|
||||||
p.Refire = 0;
|
|
||||||
}
|
}
|
||||||
|
if (correctweapon)
|
||||||
if (style & MRF_LOSEACTUALWEAPON)
|
{ // Better "lose morphed weapon" semantics
|
||||||
|
class<Actor> morphweaponcls = MorphWeapon;
|
||||||
|
if (morphweaponcls != null && morphweaponcls is 'Weapon')
|
||||||
{
|
{
|
||||||
// Improved "lose morph weapon" semantics.
|
let OriginalMorphWeapon = Weapon(altmo.FindInventory (morphweapon));
|
||||||
class<Weapon> morphWeapCls = MorphWeapon;
|
if ((OriginalMorphWeapon != null) && (OriginalMorphWeapon.GivenAsMorphWeapon))
|
||||||
if (morphWeapCls)
|
{ // You don't get to keep your morphed weapon.
|
||||||
|
if (OriginalMorphWeapon.SisterWeapon != null)
|
||||||
{
|
{
|
||||||
let originalMorphWeapon = Weapon(alt.FindInventory(morphWeapCls));
|
OriginalMorphWeapon.SisterWeapon.Destroy ();
|
||||||
if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon)
|
}
|
||||||
originalMorphWeapon.Destroy();
|
OriginalMorphWeapon.Destroy ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (morphWeap) // Old behaviour (not really useful now).
|
}
|
||||||
|
else // old behaviour (not really useful now)
|
||||||
|
{ // Assumptions made here are no longer valid
|
||||||
|
if (beastweap != null)
|
||||||
|
{ // You don't get to keep your morphed weapon.
|
||||||
|
if (beastweap.SisterWeapon != null)
|
||||||
{
|
{
|
||||||
morphWeap.Destroy();
|
beastweap.SisterWeapon.Destroy ();
|
||||||
}
|
}
|
||||||
|
beastweap.Destroy ();
|
||||||
// Reset the base AC of the player's Hexen armor back to its default.
|
}
|
||||||
let hexArmor = HexenArmor(alt.FindInventory("HexenArmor"));
|
}
|
||||||
if (hexArmor)
|
PostUnmorph(altmo, false); // This body is no longer current.
|
||||||
hexArmor.Slots[4] = alt.HexenArmor[0];
|
altmo.PostUnmorph(self, true); // altmo body is current.
|
||||||
|
Destroy ();
|
||||||
alt.ClearFOVInterpolation();
|
// Restore playerclass armor to its normal amount.
|
||||||
alt.InitAllPowerupEffects();
|
let hxarmor = HexenArmor(altmo.FindInventory('HexenArmor'));
|
||||||
|
if (hxarmor != null)
|
||||||
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);
|
hxarmor.Slots[4] = altmo.HexenArmor[0];
|
||||||
if (fog)
|
|
||||||
fog.Target = alt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Destroy();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
override Actor, int, int MorphedDeath()
|
||||||
|
{
|
||||||
|
// Voodoo dolls should not unmorph the real player here.
|
||||||
|
if (player && (player.mo == self) &&
|
||||||
|
(player.morphTics) &&
|
||||||
|
(player.MorphStyle & MRF_UNDOBYDEATH) &&
|
||||||
|
(alternative))
|
||||||
|
{
|
||||||
|
Actor realme = alternative;
|
||||||
|
int realstyle = player.MorphStyle;
|
||||||
|
int realhealth = health;
|
||||||
|
if (UndoPlayerMorph(player, 0, !!(player.MorphStyle & MRF_UNDOBYDEATHFORCED)))
|
||||||
|
{
|
||||||
|
return realme, realstyle, realhealth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null, 0, 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
class MorphProjectile : Actor
|
||||||
|
{
|
||||||
|
|
||||||
|
Class<PlayerPawn> PlayerClass;
|
||||||
|
Class<Actor> MonsterClass, MorphFlash, UnMorphFlash;
|
||||||
|
int Duration, MorphStyle;
|
||||||
|
|
||||||
|
Default
|
||||||
|
{
|
||||||
|
Damage 1;
|
||||||
|
Projectile;
|
||||||
|
-ACTIVATEIMPACT
|
||||||
|
-ACTIVATEPCROSS
|
||||||
|
}
|
||||||
|
|
||||||
|
override int DoSpecialDamage (Actor target, int damage, Name damagetype)
|
||||||
|
{
|
||||||
|
if (target.player)
|
||||||
|
{
|
||||||
|
// Voodoo dolls forward this to the real player
|
||||||
|
target.player.mo.MorphPlayer (NULL, PlayerClass, Duration, MorphStyle, MorphFlash, UnMorphFlash);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target.MorphMonster (MonsterClass, Duration, MorphStyle, MorphFlash, UnMorphFlash);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
class MorphedMonster : Actor
|
||||||
|
{
|
||||||
|
Actor UnmorphedMe;
|
||||||
|
int UnmorphTime, MorphStyle;
|
||||||
|
Class<Actor> MorphExitFlash;
|
||||||
|
int FlagsSave;
|
||||||
|
|
||||||
|
Default
|
||||||
|
{
|
||||||
|
Monster;
|
||||||
|
-COUNTKILL
|
||||||
|
+FLOORCLIP
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void Substitute(Actor replacement);
|
||||||
|
|
||||||
|
override void OnDestroy ()
|
||||||
|
{
|
||||||
|
if (UnmorphedMe != NULL)
|
||||||
|
{
|
||||||
|
UnmorphedMe.Destroy ();
|
||||||
|
}
|
||||||
|
Super.OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
override void Die (Actor source, Actor inflictor, int dmgflags, Name MeansOfDeath)
|
||||||
|
{
|
||||||
|
Super.Die (source, inflictor, dmgflags, MeansOfDeath);
|
||||||
|
if (UnmorphedMe != NULL && UnmorphedMe.bUnmorphed)
|
||||||
|
{
|
||||||
|
UnmorphedMe.health = health;
|
||||||
|
UnmorphedMe.Die (source, inflictor, dmgflags, MeansOfDeath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void Tick ()
|
||||||
|
{
|
||||||
|
if (UnmorphTime > level.time || !UndoMonsterMorph())
|
||||||
|
{
|
||||||
|
Super.Tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// FUNC P_UndoMonsterMorph
|
||||||
|
//
|
||||||
|
// Returns true if the monster unmorphs.
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
virtual bool UndoMonsterMorph(bool force = false)
|
||||||
|
{
|
||||||
|
if (UnmorphTime == 0 || UnmorphedMe == NULL || bStayMorphed || UnmorphedMe.bStayMorphed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let unmorphed = UnmorphedMe;
|
||||||
|
unmorphed.SetOrigin (Pos, false);
|
||||||
|
unmorphed.bSolid = true;
|
||||||
|
bSolid = false;
|
||||||
|
bool save = bTouchy;
|
||||||
|
bTouchy = false;
|
||||||
|
if (!force && !unmorphed.TestMobjLocation ())
|
||||||
|
{ // Didn't fit
|
||||||
|
unmorphed.bSolid = false;
|
||||||
|
bSolid = true;
|
||||||
|
bTouchy = save;
|
||||||
|
UnmorphTime = level.time + 5*TICRATE; // Next try in 5 seconds
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PreUnmorph(unmorphed, false);
|
||||||
|
unmorphed.PreUnmorph(self, true);
|
||||||
|
unmorphed.Angle = Angle;
|
||||||
|
unmorphed.target = target;
|
||||||
|
unmorphed.bShadow = bShadow;
|
||||||
|
unmorphed.bGhost = bGhost;
|
||||||
|
unmorphed.bSolid = !!(flagssave & 2);
|
||||||
|
unmorphed.bShootable = !!(flagssave & 4);
|
||||||
|
unmorphed.bInvisible = !!(flagssave & 0x40);
|
||||||
|
unmorphed.health = unmorphed.SpawnHealth();
|
||||||
|
unmorphed.Vel = Vel;
|
||||||
|
unmorphed.ChangeTid(tid);
|
||||||
|
unmorphed.special = special;
|
||||||
|
unmorphed.Score = Score;
|
||||||
|
unmorphed.args[0] = args[0];
|
||||||
|
unmorphed.args[1] = args[1];
|
||||||
|
unmorphed.args[2] = args[2];
|
||||||
|
unmorphed.args[3] = args[3];
|
||||||
|
unmorphed.args[4] = args[4];
|
||||||
|
unmorphed.CopyFriendliness (self, true);
|
||||||
|
unmorphed.bUnmorphed = false;
|
||||||
|
PostUnmorph(unmorphed, false); // From is false here: Leaving the caller's body.
|
||||||
|
unmorphed.PostUnmorph(self, true); // True here: Entering this body from here.
|
||||||
|
UnmorphedMe = NULL;
|
||||||
|
Substitute(unmorphed);
|
||||||
|
Destroy ();
|
||||||
|
let eflash = Spawn(MorphExitFlash, Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE);
|
||||||
|
if (eflash)
|
||||||
|
eflash.target = unmorphed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
override Actor, int, int MorphedDeath()
|
||||||
|
{
|
||||||
|
let realme = UnmorphedMe;
|
||||||
|
if (realme != NULL)
|
||||||
|
{
|
||||||
|
if ((UnmorphTime) &&
|
||||||
|
(MorphStyle & MRF_UNDOBYDEATH))
|
||||||
|
{
|
||||||
|
int realstyle = MorphStyle;
|
||||||
|
int realhealth = health;
|
||||||
|
if (UndoMonsterMorph(!!(MorphStyle & MRF_UNDOBYDEATHFORCED)))
|
||||||
|
{
|
||||||
|
return realme, realstyle, realhealth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (realme.bBossDeath)
|
||||||
|
{
|
||||||
|
realme.health = 0; // make sure that A_BossDeath considers it dead.
|
||||||
|
realme.A_BossDeath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null, 0, 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,32 +23,35 @@ class ArtiTeleport : Inventory
|
||||||
Loop;
|
Loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
override bool Use(bool pickup)
|
override bool Use (bool pickup)
|
||||||
{
|
{
|
||||||
Vector3 dest;
|
Vector3 dest;
|
||||||
double destAngle;
|
int destAngle;
|
||||||
|
|
||||||
if (deathmatch)
|
if (deathmatch)
|
||||||
[dest, destAngle] = Level.PickDeathmatchStart();
|
|
||||||
else
|
|
||||||
[dest, destAngle] = Level.PickPlayerStart(Owner.PlayerNumber());
|
|
||||||
|
|
||||||
if (!Level.UsePlayerStartZ)
|
|
||||||
dest.Z = ONFLOORZ;
|
|
||||||
|
|
||||||
Owner.Teleport(dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG);
|
|
||||||
|
|
||||||
bool canLaugh = Owner.player != null;
|
|
||||||
EMorphFlags mStyle = Owner.GetMorphStyle();
|
|
||||||
if (Owner.Alternative && (mStyle & MRF_UNDOBYCHAOSDEVICE))
|
|
||||||
{
|
{
|
||||||
// Teleporting away will undo any morph effects (pig).
|
[dest, destAngle] = level.PickDeathmatchStart();
|
||||||
if (!Owner.Unmorph(Owner, MRF_UNDOBYCHAOSDEVICE) && (mStyle & MRF_FAILNOLAUGH))
|
}
|
||||||
canLaugh = false;
|
else
|
||||||
|
{
|
||||||
|
[dest, destAngle] = level.PickPlayerStart(Owner.PlayerNumber());
|
||||||
|
}
|
||||||
|
if (!level.useplayerstartz)
|
||||||
|
dest.Z = ONFLOORZ;
|
||||||
|
Owner.Teleport (dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG);
|
||||||
|
bool canlaugh = true;
|
||||||
|
Playerinfo p = Owner.player;
|
||||||
|
if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYCHAOSDEVICE))
|
||||||
|
{ // Teleporting away will undo any morph effects (pig)
|
||||||
|
if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYCHAOSDEVICE) && (p.MorphStyle & MRF_FAILNOLAUGH))
|
||||||
|
{
|
||||||
|
canlaugh = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canlaugh)
|
||||||
|
{ // Full volume laugh
|
||||||
|
Owner.A_StartSound ("*evillaugh", CHAN_VOICE, CHANF_DEFAULT, 1., ATTN_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canLaugh)
|
|
||||||
Owner.A_StartSound("*evillaugh", CHAN_VOICE, attenuation: ATTN_NONE);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -232,8 +232,6 @@ enum EMorphFlags
|
||||||
MRF_UNDOBYTIMEOUT = 0x00001000,
|
MRF_UNDOBYTIMEOUT = 0x00001000,
|
||||||
MRF_UNDOALWAYS = 0x00002000,
|
MRF_UNDOALWAYS = 0x00002000,
|
||||||
MRF_TRANSFERTRANSLATION = 0x00004000,
|
MRF_TRANSFERTRANSLATION = 0x00004000,
|
||||||
MRF_KEEPARMOR = 0x00008000,
|
|
||||||
MRF_IGNOREINVULN = 0x00010000,
|
|
||||||
MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT,
|
MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue