2018-11-24 06:45:49 +00:00
|
|
|
extend class PlayerPawn
|
|
|
|
{
|
|
|
|
//===========================================================================
|
|
|
|
//
|
2024-04-17 23:55:08 +00:00
|
|
|
// InitAllPowerupEffects
|
2018-11-24 06:45:49 +00:00
|
|
|
//
|
2024-04-17 23:55:08 +00:00
|
|
|
// Calls InitEffect() on every Powerup in the inventory list. Since these
|
|
|
|
// functions can be overridden it's safest to store what's next in the item
|
|
|
|
// list before calling it.
|
2018-11-24 06:45:49 +00:00
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void InitAllPowerupEffects()
|
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
for (Inventory item = Inv; item;)
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
Inventory next = item.Inv;
|
2018-11-24 06:45:49 +00:00
|
|
|
let power = Powerup(item);
|
2024-04-17 23:55:08 +00:00
|
|
|
if (power)
|
2018-11-24 06:45:49 +00:00
|
|
|
power.InitEffect();
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
item = next;
|
2018-11-24 06:45:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// EndAllPowerupEffects
|
|
|
|
//
|
|
|
|
// Calls EndEffect() on every Powerup in the inventory list.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void EndAllPowerupEffects()
|
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
for (Inventory item = Inv; item;)
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
Inventory next = item.Inv;
|
2018-11-24 06:45:49 +00:00
|
|
|
let power = Powerup(item);
|
2024-04-17 23:55:08 +00:00
|
|
|
if (power)
|
2018-11-24 06:45:49 +00:00
|
|
|
power.EndEffect();
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
item = next;
|
2018-11-24 06:45:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
virtual void ActivateMorphWeapon()
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
if (player.ReadyWeapon)
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2020-03-10 23:56:29 +00:00
|
|
|
let psp = player.GetPSprite(PSP_WEAPON);
|
2024-04-17 23:55:08 +00:00
|
|
|
psp.y = WEAPONTOP;
|
|
|
|
player.ReadyWeapon.ResetPSprite(psp);
|
2018-11-24 06:45:49 +00:00
|
|
|
}
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
class<Weapon> morphWeapCls = MorphWeapon;
|
|
|
|
if (!morphWeapCls)
|
|
|
|
{
|
2018-11-24 06:45:49 +00:00
|
|
|
player.ReadyWeapon = null;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
player.ReadyWeapon = Weapon(FindInventory(morphWeapCls));
|
|
|
|
if (!player.ReadyWeapon)
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
player.ReadyWeapon = Weapon(GiveInventoryType(morphWeapCls));
|
|
|
|
if (player.ReadyWeapon)
|
|
|
|
player.ReadyWeapon.GivenAsMorphWeapon = true; // Flag is used only by new morphWeap semantics in UndoPlayerMorph
|
2024-04-17 21:44:25 +00:00
|
|
|
}
|
2018-11-24 06:45:49 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
if (player.ReadyWeapon)
|
|
|
|
player.SetPSprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState());
|
2024-04-17 21:44:25 +00:00
|
|
|
}
|
2024-01-01 02:41:03 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
if (player.ReadyWeapon)
|
|
|
|
player.SetPSprite(PSP_FLASH, null);
|
|
|
|
|
2018-11-24 06:45:49 +00:00
|
|
|
player.PendingWeapon = WP_NOCHANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2018-11-24 07:39:35 +00:00
|
|
|
// MorphPlayer
|
2018-11-24 06:45:49 +00:00
|
|
|
//
|
|
|
|
// Returns true if the player gets turned into a chicken/pig.
|
|
|
|
//
|
|
|
|
// TODO: Allow morphed players to receive weapon sets (not just one weapon),
|
|
|
|
// since they have their own weapon slots now.
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
virtual bool MorphPlayer(PlayerInfo activator, class<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
if (!player || !spawnType || bDontMorph || player.Health <= 0
|
|
|
|
|| (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))))
|
2024-01-01 06:31:25 +00:00
|
|
|
{
|
2018-11-24 06:45:49 +00:00
|
|
|
return false;
|
2024-01-01 06:31:25 +00:00
|
|
|
}
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
if (!duration)
|
|
|
|
duration = DEFMORPHTICS;
|
|
|
|
|
|
|
|
if (spawnType == GetClass())
|
2024-04-17 21:44:25 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
// Player is already a beast.
|
|
|
|
if (Alternative && bCanSuperMorph
|
|
|
|
&& GetMorphTics() < duration - TICRATE
|
|
|
|
&& !FindInventory("PowerWeaponLevel2", true))
|
|
|
|
{
|
|
|
|
// Make a super chicken.
|
|
|
|
GiveInventoryType("PowerWeaponLevel2");
|
|
|
|
}
|
|
|
|
|
2024-04-17 21:44:25 +00:00
|
|
|
return false;
|
|
|
|
}
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE));
|
|
|
|
if (!MorphInto(morphed))
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
if (morphed)
|
|
|
|
morphed.Destroy();
|
|
|
|
|
2018-11-24 06:45:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-03-08 14:19:20 +00:00
|
|
|
PreMorph(morphed, false);
|
|
|
|
morphed.PreMorph(self, true);
|
2020-02-15 22:12:45 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
morphed.EndAllPowerupEffects();
|
|
|
|
|
2018-11-24 06:45:49 +00:00
|
|
|
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
|
|
|
morphed.Translation = Translation;
|
2024-04-17 23:55:08 +00:00
|
|
|
|
2018-11-24 06:45:49 +00:00
|
|
|
morphed.Angle = Angle;
|
2024-04-17 23:55:08 +00:00
|
|
|
morphed.Pitch = Pitch; // Allow pitch here since mouse look in GZDoom is far more common than Heretic/Hexen.
|
|
|
|
morphed.Target = Target;
|
|
|
|
morphed.Tracer = Tracer;
|
|
|
|
morphed.Master = Master;
|
2018-11-24 06:45:49 +00:00
|
|
|
morphed.FriendPlayer = FriendPlayer;
|
|
|
|
morphed.DesignatedTeam = DesignatedTeam;
|
|
|
|
morphed.Score = Score;
|
2024-04-17 23:55:08 +00:00
|
|
|
morphed.ScoreIcon = ScoreIcon;
|
|
|
|
morphed.Health = morphed.SpawnHealth();
|
|
|
|
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
|
|
|
{
|
|
|
|
morphed.ChangeTid(TID);
|
|
|
|
ChangeTid(0);
|
2024-04-17 21:44:25 +00:00
|
|
|
}
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2018-11-24 06:45:49 +00:00
|
|
|
morphed.bShadow |= bShadow;
|
|
|
|
morphed.bNoGravity |= bNoGravity;
|
|
|
|
morphed.bFly |= bFly;
|
|
|
|
morphed.bGhost |= bGhost;
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
// Remove all armor.
|
|
|
|
if (!(style & MRF_KEEPARMOR))
|
2018-11-24 06:45:49 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
for (Inventory item = morphed.Inv; item;)
|
2024-01-15 00:41:58 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
Inventory next = item.Inv;
|
|
|
|
if (item is "Armor")
|
|
|
|
item.DepleteOrDestroy();
|
|
|
|
|
|
|
|
item = next;
|
2024-01-15 00:41:58 +00:00
|
|
|
}
|
2024-01-01 02:41:03 +00:00
|
|
|
}
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2024-04-17 21:44:25 +00:00
|
|
|
morphed.ClearFOVInterpolation();
|
2024-04-17 23:55:08 +00:00
|
|
|
morphed.InitAllPowerupEffects();
|
|
|
|
morphed.ActivateMorphWeapon();
|
|
|
|
|
2024-04-17 21:44:25 +00:00
|
|
|
PostMorph(morphed, false); // No longer the current body
|
|
|
|
morphed.PostMorph(self, true); // This is the current body
|
2024-04-17 23:55:08 +00:00
|
|
|
|
|
|
|
if (enterFlash)
|
|
|
|
{
|
|
|
|
Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TelefogHeight), ALLOW_REPLACE);
|
|
|
|
if (fog)
|
|
|
|
fog.Target = morphed;
|
|
|
|
}
|
|
|
|
|
2018-11-24 06:45:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-11-24 07:17:30 +00:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
//
|
2018-11-24 07:39:35 +00:00
|
|
|
// FUNC UndoPlayerMorph
|
2018-11-24 07:17:30 +00:00
|
|
|
//
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
virtual bool UndoPlayerMorph(PlayerInfo activator, EMorphFlags unmorphFlags = 0, bool force = false)
|
2018-11-24 07:17:30 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
if (!Alternative || bStayMorphed || Alternative.bStayMorphed)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable
|
|
|
|
&& (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING))))
|
2018-11-24 07:17:30 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
let alt = PlayerPawn(Alternative);
|
|
|
|
alt.SetOrigin(Pos, false);
|
|
|
|
// Test if there's room to unmorph.
|
|
|
|
if (!force && (PremorphProperties & MPROP_SOLID))
|
|
|
|
{
|
|
|
|
bool altSolid = alt.bSolid;
|
|
|
|
bool isSolid = bSolid;
|
|
|
|
bool isTouchy = bTouchy;
|
2018-11-24 07:17:30 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
alt.bSolid = true;
|
|
|
|
bSolid = bTouchy = false;
|
|
|
|
|
|
|
|
bool res = alt.TestMobjLocation();
|
|
|
|
|
|
|
|
alt.bSolid = altSolid;
|
|
|
|
bSolid = isSolid;
|
|
|
|
bTouchy = isTouchy;
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
{
|
|
|
|
SetMorphTics(2 * TICRATE);
|
|
|
|
return false;
|
|
|
|
}
|
2018-11-24 07:17:30 +00:00
|
|
|
}
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
if (!MorphInto(alt))
|
2018-11-24 07:17:30 +00:00
|
|
|
return false;
|
2020-03-08 14:19:20 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
PreUnmorph(alt, false); // This body's about to be left.
|
|
|
|
alt.PreUnmorph(self, true); // This one's about to become current.
|
2020-02-15 22:12:45 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
alt.EndAllPowerupEffects();
|
2018-11-24 07:17:30 +00:00
|
|
|
|
|
|
|
// Remove the morph power if the morph is being undone prematurely.
|
2024-04-17 23:55:08 +00:00
|
|
|
for (Inventory item = alt.Inv; item;)
|
2018-11-24 07:17:30 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
Inventory next = item.Inv;
|
2018-11-24 07:17:30 +00:00
|
|
|
if (item is "PowerMorph")
|
|
|
|
item.Destroy();
|
2024-04-17 23:55:08 +00:00
|
|
|
|
2018-11-24 07:17:30 +00:00
|
|
|
item = next;
|
|
|
|
}
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
alt.Angle = Angle;
|
|
|
|
alt.Pitch = Pitch;
|
|
|
|
alt.Target = Target;
|
|
|
|
alt.Tracer = Tracer;
|
|
|
|
alt.Master = Master;
|
|
|
|
alt.FriendPlayer = FriendPlayer;
|
|
|
|
alt.DesignatedTeam = DesignatedTeam;
|
|
|
|
alt.Score = Score;
|
|
|
|
alt.ScoreIcon = ScoreIcon;
|
|
|
|
alt.ReactionTime = 18;
|
|
|
|
alt.bSolid = (PremorphProperties & MPROP_SOLID);
|
|
|
|
alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE);
|
|
|
|
alt.bInvisible = (PremorphProperties & MPROP_INVIS);
|
|
|
|
alt.bShadow = bShadow;
|
|
|
|
alt.bNoGravity = bNoGravity;
|
|
|
|
alt.bGhost = bGhost;
|
|
|
|
alt.bFly = bFly;
|
|
|
|
alt.Vel = (0.0, 0.0, Vel.Z);
|
|
|
|
|
|
|
|
alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION);
|
|
|
|
alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR));
|
|
|
|
|
|
|
|
let p = alt.player;
|
|
|
|
class<Actor> exitFlash = alt.GetMorphExitFlash();
|
|
|
|
EMorphFlags style = alt.GetMorphStyle();
|
|
|
|
Weapon premorphWeap = p.PremorphWeapon;
|
|
|
|
|
|
|
|
if (TID && (style & MRF_NEWTIDBEHAVIOUR))
|
|
|
|
{
|
|
|
|
alt.ChangeTid(TID);
|
|
|
|
ChangeTID(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
alt.SetMorphTics(0);
|
|
|
|
alt.SetMorphStyle(0);
|
|
|
|
alt.SetMorphExitFlash(null);
|
|
|
|
p.MorphedPlayerClass = null;
|
|
|
|
p.PremorphWeapon = null;
|
|
|
|
p.ViewHeight = alt.ViewHeight;
|
|
|
|
p.Vel = (0.0, 0.0);
|
|
|
|
if (p.Health > 0 || (style & MRF_UNDOBYDEATHSAVES))
|
|
|
|
p.Health = alt.Health = alt.SpawnHealth();
|
|
|
|
else
|
|
|
|
alt.Health = p.Health;
|
2018-11-24 07:17:30 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
Inventory level2 = alt.FindInventory("PowerWeaponLevel2", true);
|
|
|
|
if (level2)
|
|
|
|
level2.Destroy();
|
2018-11-24 07:17:30 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
let morphWeap = p.ReadyWeapon;
|
|
|
|
if (premorphWeap)
|
2018-11-24 07:17:30 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
premorphWeap.PostMorphWeapon();
|
2018-11-24 07:17:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
p.ReadyWeapon = null;
|
|
|
|
p.PendingWeapon = WP_NOCHANGE;
|
|
|
|
p.Refire = 0;
|
2018-11-24 07:17:30 +00:00
|
|
|
}
|
2018-11-24 09:47:42 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
if (style & MRF_LOSEACTUALWEAPON)
|
2018-11-24 09:47:42 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
// Improved "lose morph weapon" semantics.
|
|
|
|
class<Weapon> morphWeapCls = MorphWeapon;
|
|
|
|
if (morphWeapCls)
|
2018-11-24 09:47:42 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
let originalMorphWeapon = Weapon(alt.FindInventory(morphWeapCls));
|
|
|
|
if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon)
|
|
|
|
originalMorphWeapon.Destroy();
|
2018-11-24 09:47:42 +00:00
|
|
|
}
|
|
|
|
}
|
2024-04-17 23:55:08 +00:00
|
|
|
else if (morphWeap) // Old behaviour (not really useful now).
|
2017-01-20 00:11:36 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
morphWeap.Destroy();
|
2017-01-20 00:11:36 +00:00
|
|
|
}
|
2018-11-24 09:47:42 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
// Reset the base AC of the player's Hexen armor back to its default.
|
|
|
|
let hexArmor = HexenArmor(alt.FindInventory("HexenArmor"));
|
|
|
|
if (hexArmor)
|
|
|
|
hexArmor.Slots[4] = alt.HexenArmor[0];
|
2016-11-23 22:28:03 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
alt.ClearFOVInterpolation();
|
|
|
|
alt.InitAllPowerupEffects();
|
2018-11-24 09:47:42 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
PostUnmorph(alt, false); // This body is no longer current.
|
|
|
|
alt.PostUnmorph(self, true); // altmo body is current.
|
2018-11-24 09:47:42 +00:00
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
if (exitFlash)
|
2018-11-24 09:47:42 +00:00
|
|
|
{
|
2024-04-17 23:55:08 +00:00
|
|
|
Actor fog = Spawn(exitFlash, alt.Vec3Angle(20.0, alt.Angle, GameInfo.TelefogHeight), ALLOW_REPLACE);
|
|
|
|
if (fog)
|
|
|
|
fog.Target = alt;
|
2018-11-24 09:47:42 +00:00
|
|
|
}
|
|
|
|
|
2024-04-17 23:55:08 +00:00
|
|
|
Destroy();
|
2018-11-24 09:47:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
2016-10-13 22:40:20 +00:00
|
|
|
}
|