mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-14 22:41:53 +00:00
7c4c0163a3
A_OverlayPivotAlign(int layer, int halign, int valign) - Aligns the pivot point origin to a corner of the PSprite before applying offsets. - - halign: Horizontal. Applicable constants are PSPA_<LEFT/CENTER/RIGHT>. - - valign: Vertical. Applicable constants are PSPA_<TOP/CENTER/BOTTOM>. - - Default is top left. A_OverlayVertexOffset(int layer, int index, double x, double y, int flags) - Allows offsetting the corners of the psprite, granting modders the ability to skew weapon sprites as they see fit. - - index: The index of the vertice. Valid ranges are between [0,3]. - - x/y: Offsets of vertices. - - flags: Takes WOF_ flags. Other changes: - Removed pivot point interpolation since it's pointless. - Removed PSPF_PIVOTSCREEN due to complications with it having relativity. This will be revisited either later or in another submission. - Added ResetPSprite() to be called with BringUpWeapon(), A_Lower(), and morph weapon raising to reset all the new properties to 0. Nearly ready now. Just some final testing needed.
632 lines
16 KiB
Text
632 lines
16 KiB
Text
extend class PlayerPawn
|
|
{
|
|
private native void Substitute(PlayerPawn replacement);
|
|
|
|
//===========================================================================
|
|
//
|
|
// EndAllPowerupEffects
|
|
//
|
|
// Calls EndEffect() on every Powerup in the inventory list.
|
|
//
|
|
//===========================================================================
|
|
|
|
void InitAllPowerupEffects()
|
|
{
|
|
let item = Inv;
|
|
while (item != null)
|
|
{
|
|
let power = Powerup(item);
|
|
if (power != null)
|
|
{
|
|
power.InitEffect();
|
|
}
|
|
item = item.Inv;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// EndAllPowerupEffects
|
|
//
|
|
// Calls EndEffect() on every Powerup in the inventory list.
|
|
//
|
|
//===========================================================================
|
|
|
|
void EndAllPowerupEffects()
|
|
{
|
|
let item = Inv;
|
|
while (item != null)
|
|
{
|
|
let power = Powerup(item);
|
|
if (power != null)
|
|
{
|
|
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);
|
|
if (psp)
|
|
{
|
|
psp.y = WEAPONTOP;
|
|
player.ReadyWeapon.ResetPSprite(psp);
|
|
}
|
|
}
|
|
|
|
if (morphweaponcls == null || !(morphweaponcls is 'Weapon'))
|
|
{ // No weapon at all while morphed!
|
|
player.ReadyWeapon = null;
|
|
}
|
|
else
|
|
{
|
|
player.ReadyWeapon = Weapon(FindInventory (morphweaponcls));
|
|
if (player.ReadyWeapon == null)
|
|
{
|
|
player.ReadyWeapon = Weapon(GiveInventoryType (morphweaponcls));
|
|
if (player.ReadyWeapon != null)
|
|
{
|
|
player.ReadyWeapon.GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in UndoPlayerMorph
|
|
}
|
|
}
|
|
if (player.ReadyWeapon != null)
|
|
{
|
|
player.SetPsprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState());
|
|
}
|
|
}
|
|
|
|
if (player.ReadyWeapon != null)
|
|
{
|
|
player.SetPsprite(PSP_FLASH, null);
|
|
}
|
|
|
|
player.PendingWeapon = WP_NOCHANGE;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// MorphPlayer
|
|
//
|
|
// 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.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
virtual bool MorphPlayer(playerinfo activator, Class<PlayerPawn> spawntype, int duration, int style, Class<Actor> enter_flash = null, Class<Actor> exit_flash = null)
|
|
{
|
|
if (bDontMorph)
|
|
{
|
|
return false;
|
|
}
|
|
if (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE)))
|
|
{ // Immune when invulnerable unless this is a power we activated
|
|
return false;
|
|
}
|
|
if (player.morphTics)
|
|
{ // Player is already a beast
|
|
if ((GetClass() == spawntype) && bCanSuperMorph
|
|
&& (player.morphTics < (((duration) ? duration : DEFMORPHTICS) - TICRATE))
|
|
&& FindInventory('PowerWeaponLevel2', true) == null)
|
|
{ // Make a super chicken
|
|
GiveInventoryType ('PowerWeaponLevel2');
|
|
}
|
|
return false;
|
|
}
|
|
if (health <= 0)
|
|
{ // Dead players cannot morph
|
|
return false;
|
|
}
|
|
if (spawntype == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (!(spawntype is 'PlayerPawn'))
|
|
{
|
|
return false;
|
|
}
|
|
if (spawntype == GetClass())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE));
|
|
|
|
// Use GetClass in the event someone actually allows replacements.
|
|
PreMorph(morphed, false);
|
|
morphed.PreMorph(self, true);
|
|
|
|
EndAllPowerupEffects();
|
|
Substitute(morphed);
|
|
if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate)
|
|
{
|
|
morphed.Translation = Translation;
|
|
}
|
|
if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR))
|
|
{
|
|
morphed.ChangeTid(tid);
|
|
ChangeTid(0);
|
|
}
|
|
morphed.Angle = Angle;
|
|
morphed.target = target;
|
|
morphed.tracer = tracer;
|
|
morphed.alternative = self;
|
|
morphed.FriendPlayer = FriendPlayer;
|
|
morphed.DesignatedTeam = DesignatedTeam;
|
|
morphed.Score = Score;
|
|
player.PremorphWeapon = player.ReadyWeapon;
|
|
|
|
morphed.special2 = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility
|
|
morphed.player = player;
|
|
|
|
if (morphed.ViewHeight > player.viewheight && player.deltaviewheight == 0)
|
|
{ // If the new view height is higher than the old one, start moving toward it.
|
|
player.deltaviewheight = player.GetDeltaViewHeight();
|
|
}
|
|
morphed.bShadow |= bShadow;
|
|
morphed.bNoGravity |= bNoGravity;
|
|
morphed.bFly |= bFly;
|
|
morphed.bGhost |= bGhost;
|
|
|
|
if (enter_flash == null) enter_flash = 'TeleportFog';
|
|
let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE);
|
|
let p = player;
|
|
player = null;
|
|
alternative = morphed;
|
|
bSolid = false;
|
|
bShootable = false;
|
|
bUnmorphed = true;
|
|
bInvisible = true;
|
|
|
|
p.morphTics = (duration) ? duration : DEFMORPHTICS;
|
|
|
|
// [MH] Used by SBARINFO to speed up face drawing
|
|
p.MorphedPlayerClass = spawntype;
|
|
|
|
p.MorphStyle = style;
|
|
if (exit_flash == null) exit_flash = 'TeleportFog';
|
|
p.MorphExitFlash = exit_flash;
|
|
p.health = morphed.health;
|
|
p.mo = morphed;
|
|
p.vel = (0, 0);
|
|
morphed.ObtainInventory (self);
|
|
// Remove all armor
|
|
for (Inventory item = morphed.Inv; item != null; )
|
|
{
|
|
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.ScoreIcon = ScoreIcon; // [GRB]
|
|
if (eflash)
|
|
eflash.target = morphed;
|
|
PostMorph(morphed, false); // No longer the current body
|
|
morphed.PostMorph(self, true); // This is the current body
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// FUNC UndoPlayerMorph
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
virtual bool UndoPlayerMorph(playerinfo activator, int unmorphflag = 0, bool force = false)
|
|
{
|
|
if (alternative == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let player = self.player;
|
|
bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag);
|
|
|
|
if ((bInvulnerable) // If the player is invulnerable
|
|
&& ((player != activator) // and either did not decide to unmorph,
|
|
|| (!((player.MorphStyle & MRF_WHENINVULNERABLE) // or the morph style does not allow it
|
|
|| (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it),
|
|
{ // Then the player is immune to the unmorph.
|
|
return false;
|
|
}
|
|
|
|
let altmo = PlayerPawn(alternative);
|
|
altmo.SetOrigin (Pos, false);
|
|
altmo.bSolid = true;
|
|
bSolid = false;
|
|
if (!force && !altmo.TestMobjLocation())
|
|
{ // Didn't fit
|
|
altmo.bSolid = false;
|
|
bSolid = true;
|
|
player.morphTics = 2*TICRATE;
|
|
return false;
|
|
}
|
|
|
|
PreUnmorph(altmo, false); // This body's about to be left.
|
|
altmo.PreUnmorph(self, true); // This one's about to become current.
|
|
|
|
// No longer using tracer as morph storage. That is what 'alternative' is for.
|
|
// If the tracer has changed on the morph, change the original too.
|
|
altmo.target = target;
|
|
altmo.tracer = tracer;
|
|
self.player = null;
|
|
altmo.alternative = alternative = null;
|
|
|
|
// Remove the morph power if the morph is being undone prematurely.
|
|
for (Inventory item = Inv; item != null;)
|
|
{
|
|
let next = item.Inv;
|
|
if (item is "PowerMorph")
|
|
{
|
|
item.Destroy();
|
|
}
|
|
item = next;
|
|
}
|
|
EndAllPowerupEffects();
|
|
altmo.ObtainInventory (self);
|
|
Substitute(altmo);
|
|
if ((tid != 0) && (player.MorphStyle & MRF_NEWTIDBEHAVIOUR))
|
|
{
|
|
altmo.ChangeTid(tid);
|
|
}
|
|
altmo.Angle = Angle;
|
|
altmo.player = player;
|
|
altmo.reactiontime = 18;
|
|
altmo.bSolid = !!(special2 & 2);
|
|
altmo.bShootable = !!(special2 & 4);
|
|
altmo.bInvisible = !!(special2 & 0x40);
|
|
altmo.Vel = (0, 0, Vel.Z);
|
|
player.Vel = (0, 0);
|
|
altmo.floorz = floorz;
|
|
altmo.bShadow = bShadow;
|
|
altmo.bNoGravity = bNoGravity;
|
|
altmo.bGhost = bGhost;
|
|
altmo.bUnmorphed = false;
|
|
altmo.Score = Score;
|
|
altmo.InitAllPowerupEffects();
|
|
|
|
let exit_flash = player.MorphExitFlash;
|
|
bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON);
|
|
bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES);
|
|
|
|
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 ();
|
|
}
|
|
|
|
if ((player.health > 0) || undobydeathsaves)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// [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
|
|
{
|
|
player.ReadyWeapon = player.PendingWeapon = null;
|
|
}
|
|
if (correctweapon)
|
|
{ // Better "lose morphed weapon" semantics
|
|
class<Actor> morphweaponcls = MorphWeapon;
|
|
if (morphweaponcls != null && morphweaponcls is 'Weapon')
|
|
{
|
|
let OriginalMorphWeapon = Weapon(altmo.FindInventory (morphweapon));
|
|
if ((OriginalMorphWeapon != null) && (OriginalMorphWeapon.GivenAsMorphWeapon))
|
|
{ // You don't get to keep your morphed weapon.
|
|
if (OriginalMorphWeapon.SisterWeapon != null)
|
|
{
|
|
OriginalMorphWeapon.SisterWeapon.Destroy ();
|
|
}
|
|
OriginalMorphWeapon.Destroy ();
|
|
}
|
|
}
|
|
}
|
|
else // old behaviour (not really useful now)
|
|
{ // Assumptions made here are no longer valid
|
|
if (beastweap != null)
|
|
{ // You don't get to keep your morphed weapon.
|
|
if (beastweap.SisterWeapon != null)
|
|
{
|
|
beastweap.SisterWeapon.Destroy ();
|
|
}
|
|
beastweap.Destroy ();
|
|
}
|
|
}
|
|
PostUnmorph(altmo, false); // This body is no longer current.
|
|
altmo.PostUnmorph(self, true); // altmo body is current.
|
|
Destroy ();
|
|
// Restore playerclass armor to its normal amount.
|
|
let hxarmor = HexenArmor(altmo.FindInventory('HexenArmor'));
|
|
if (hxarmor != null)
|
|
{
|
|
hxarmor.Slots[4] = altmo.HexenArmor[0];
|
|
}
|
|
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;
|
|
}
|
|
|
|
}
|
|
|