gzdoom/wadsrc/static/zscript/actors/inventory/weapons.zs
2024-04-20 01:15:46 -04:00

1184 lines
31 KiB
Text

class Weapon : StateProvider
{
enum EFireMode
{
PrimaryFire,
AltFire,
EitherFire
};
const ZOOM_INSTANT = 1;
const ZOOM_NOSCALETURNING = 2;
deprecated("3.7") uint WeaponFlags; // not to be used directly.
class<Ammo> AmmoType1, AmmoType2; // Types of ammo used by self weapon
int AmmoGive1, AmmoGive2; // Amount of each ammo to get when picking up weapon
deprecated("3.7") int MinAmmo1, MinAmmo2; // not used anywhere and thus deprecated.
int AmmoUse1, AmmoUse2; // How much ammo to use with each shot
int Kickback;
double YAdjust; // For viewing the weapon fullscreen
sound UpSound, ReadySound; // Sounds when coming up and idle
class<Weapon> SisterWeaponType; // Another weapon to pick up with self one
int SelectionOrder; // Lower-numbered weapons get picked first
int MinSelAmmo1, MinSelAmmo2; // Ignore in BestWeapon() if inadequate ammo
int ReloadCounter; // For A_CheckForReload
int BobStyle; // [XA] Bobbing style. Defines type of bobbing (e.g. Normal, Alpha) (visual only so no need to be a double)
float BobSpeed; // [XA] Bobbing speed. Defines how quickly a weapon bobs.
float BobRangeX, BobRangeY; // [XA] Bobbing range. Defines how far a weapon bobs in either direction.
double WeaponScaleX, WeaponScaleY; // [XA] Weapon scale. Defines the scale for the held weapon sprites (PSprite). Defaults to (1.0, 1.2) since that's what Doom does.
Ammo Ammo1, Ammo2; // In-inventory instance variables
Weapon SisterWeapon;
double FOVScale;
double LookScale; // Multiplier for look sensitivity (like FOV scaling but without the zooming)
int Crosshair; // 0 to use player's crosshair
bool GivenAsMorphWeapon;
bool bAltFire; // Set when this weapon's alternate fire is used.
readonly bool bDehAmmo; // Uses Doom's original amount of ammo for the respective attack functions so that old DEHACKED patches work as intended.
// AmmoUse1 will be set to the first attack's ammo use so that checking for empty weapons still works
meta int SlotNumber;
meta double SlotPriority;
Vector3 BobPivot3D; // Pivot used for BobWeapon3D
virtual ui Vector2 ModifyBobLayer(Vector2 Bob, int layer, double ticfrac) { return Bob; }
virtual ui Vector3, Vector3 ModifyBobLayer3D(Vector3 Translation, Vector3 Rotation, int layer, double ticfrac) { return Translation, Rotation; }
virtual ui Vector3 ModifyBobPivotLayer3D(int layer, double ticfrac) { return BobPivot3D; }
property AmmoGive: AmmoGive1;
property AmmoGive1: AmmoGive1;
property AmmoGive2: AmmoGive2;
property AmmoUse: AmmoUse1;
property AmmoUse1: AmmoUse1;
property AmmoUse2: AmmoUse2;
property AmmoType: AmmoType1;
property AmmoType1: AmmoType1;
property AmmoType2: AmmoType2;
property Kickback: Kickback;
property ReadySound: ReadySound;
property SelectionOrder: SelectionOrder;
property MinSelectionAmmo1: MinSelAmmo1;
property MinSelectionAmmo2: MinSelAmmo2;
property SisterWeapon: SisterWeaponType;
property UpSound: UpSound;
property YAdjust: YAdjust;
property BobSpeed: BobSpeed;
property BobRangeX: BobRangeX;
property BobRangeY: BobRangeY;
property WeaponScaleX: WeaponScaleX;
property WeaponScaleY: WeaponScaleY;
property SlotNumber: SlotNumber;
property SlotPriority: SlotPriority;
property LookScale: LookScale;
property BobPivot3D : BobPivot3D;
flagdef NoAutoFire: WeaponFlags, 0; // weapon does not autofire
flagdef ReadySndHalf: WeaponFlags, 1; // ready sound is played ~1/2 the time
flagdef DontBob: WeaponFlags, 2; // don't bob the weapon
flagdef AxeBlood: WeaponFlags, 3; // weapon makes axe blood on impact
flagdef NoAlert: WeaponFlags, 4; // weapon does not alert monsters
flagdef Ammo_Optional: WeaponFlags, 5; // weapon can use ammo but does not require it
flagdef Alt_Ammo_Optional: WeaponFlags, 6; // alternate fire can use ammo but does not require it
flagdef Primary_Uses_Both: WeaponFlags, 7; // primary fire uses both ammo
flagdef Alt_Uses_Both: WeaponFlags, 8; // alternate fire uses both ammo
flagdef Wimpy_Weapon:WeaponFlags, 9; // change away when ammo for another weapon is replenished
flagdef Powered_Up: WeaponFlags, 10; // this is a tome-of-power'ed version of its sister
flagdef Ammo_CheckBoth: WeaponFlags, 11; // check for both primary and secondary fire before switching it off
flagdef No_Auto_Switch: WeaponFlags, 12; // never switch to this weapon when it's picked up
flagdef Staff2_Kickback: WeaponFlags, 13; // the powered-up Heretic staff has special kickback
flagdef NoAutoaim: WeaponFlags, 14; // this weapon never uses autoaim (useful for ballistic projectiles)
flagdef MeleeWeapon: WeaponFlags, 15; // melee weapon. Used by monster AI with AVOIDMELEE.
flagdef NoDeathDeselect: WeaponFlags, 16; // Don't jump to the Deselect state when the player dies
flagdef NoDeathInput: WeaponFlags, 17; // The weapon cannot be fired/reloaded/whatever when the player is dead
flagdef CheatNotWeapon: WeaponFlags, 18; // Give cheat considers this not a weapon (used by Sigil)
flagdef NoAutoSwitchTo : WeaponFlags, 19; // do not auto switch to this weapon ever!
// no-op flags
flagdef NoLMS: none, 0;
flagdef Allow_With_Respawn_Invul: none, 0;
flagdef BFG: none, 0;
flagdef Explosive: none, 0;
Default
{
Inventory.PickupSound "misc/w_pkup";
Weapon.DefaultKickback;
Weapon.BobSpeed 1.0;
Weapon.BobRangeX 1.0;
Weapon.BobRangeY 1.0;
Weapon.WeaponScaleX 1.0;
Weapon.WeaponScaleY 1.2;
Weapon.SlotNumber -1;
Weapon.SlotPriority 32767;
Weapon.BobPivot3D (0.0, 0.0, 0.0);
+WEAPONSPAWN
DefaultStateUsage SUF_ACTOR|SUF_OVERLAY|SUF_WEAPON;
}
States
{
LightDone:
SHTG E 0 A_Light0;
Stop;
}
//===========================================================================
//
// Weapon :: MarkPrecacheSounds
//
//===========================================================================
override void MarkPrecacheSounds()
{
Super.MarkPrecacheSounds();
MarkSound(UpSound);
MarkSound(ReadySound);
}
virtual int, int CheckAddToSlots()
{
if (GetReplacement(GetClass()) == GetClass() && !bPowered_Up)
{
return SlotNumber, int(SlotPriority*65536);
}
return -1, 0;
}
virtual State GetReadyState ()
{
return FindState('Ready');
}
virtual State GetUpState ()
{
return FindState('Select');
}
virtual State GetDownState ()
{
return FindState('Deselect');
}
virtual State GetAtkState (bool hold)
{
State s = null;
if (hold) s = FindState('Hold');
if (s == null) s = FindState('Fire');
return s;
}
virtual State GetAltAtkState (bool hold)
{
State s = null;
if (hold) s = FindState('AltHold');
if (s == null) s = FindState('AltFire');
return s;
}
virtual void PlayUpSound(Actor origin)
{
if (UpSound)
{
origin.A_StartSound(UpSound, CHAN_WEAPON);
}
}
override String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack)
{
// Weapons may never return HitObituary by default. Override this if it is needed.
return Obituary;
}
action void A_GunFlash(statelabel flashlabel = null, int flags = 0)
{
let player = player;
if (null == player || player.ReadyWeapon == null)
{
return;
}
if (!(flags & GFF_NOEXTCHANGE))
{
player.mo.PlayAttacking2 ();
}
Weapon weapon = player.ReadyWeapon;
state flashstate = null;
if (flashlabel == null)
{
if (weapon.bAltFire)
{
flashstate = weapon.FindState('AltFlash');
}
if (flashstate == null)
{
flashstate = weapon.FindState('Flash');
}
}
else
{
flashstate = weapon.FindState(flashlabel);
}
player.SetPsprite(PSP_FLASH, flashstate);
}
//---------------------------------------------------------------------------
//
// PROC A_Lower
//
//---------------------------------------------------------------------------
action void ResetPSprite(PSprite psp)
{
if (!psp) return;
psp.rotation = 0;
psp.baseScale.x = invoker.WeaponScaleX;
psp.baseScale.y = invoker.WeaponScaleY;
psp.scale.x = 1;
psp.scale.y = 1;
psp.pivot.x = 0;
psp.pivot.y = 0;
psp.valign = 0;
psp.halign = 0;
psp.Coord0 = (0,0);
psp.Coord1 = (0,0);
psp.Coord2 = (0,0);
psp.Coord3 = (0,0);
}
action void A_Lower(int lowerspeed = 6)
{
let player = player;
if (null == player)
{
return;
}
if (null == player.ReadyWeapon)
{
player.mo.BringUpWeapon();
return;
}
let psp = player.GetPSprite(PSP_WEAPON);
if (!psp) return;
if (Alternative || player.cheats & CF_INSTANTWEAPSWITCH)
{
psp.y = WEAPONBOTTOM;
}
else
{
psp.y += lowerspeed;
}
if (psp.y < WEAPONBOTTOM)
{ // Not lowered all the way yet
return;
}
ResetPSprite(psp);
if (player.playerstate == PST_DEAD)
{ // Player is dead, so don't bring up a pending weapon
// Player is dead, so keep the weapon off screen
player.SetPsprite(PSP_FLASH, null);
psp.SetState(player.ReadyWeapon.FindState('DeadLowered'));
return;
}
// [RH] Clear the flash state. Only needed for Strife.
player.SetPsprite(PSP_FLASH, null);
player.mo.BringUpWeapon ();
return;
}
//---------------------------------------------------------------------------
//
// PROC A_Raise
//
//---------------------------------------------------------------------------
action void A_Raise(int raisespeed = 6)
{
let player = player;
if (null == player)
{
return;
}
if (player.PendingWeapon != WP_NOCHANGE)
{
player.mo.DropWeapon();
return;
}
if (player.ReadyWeapon == null)
{
return;
}
let psp = player.GetPSprite(PSP_WEAPON);
if (!psp) return;
if (psp.y <= WEAPONBOTTOM)
{
ResetPSprite(psp);
}
psp.y -= raisespeed;
if (psp.y > WEAPONTOP)
{ // Not raised all the way yet
return;
}
psp.y = WEAPONTOP;
psp.SetState(player.ReadyWeapon.GetReadyState());
return;
}
//============================================================================
//
// PROC A_WeaponReady
//
// Readies a weapon for firing or bobbing with its three ancillary functions,
// DoReadyWeaponToSwitch(), DoReadyWeaponToFire() and DoReadyWeaponToBob().
// [XA] Added DoReadyWeaponToReload() and DoReadyWeaponToZoom()
//
//============================================================================
static void DoReadyWeaponToSwitch (PlayerInfo player, bool switchable)
{
// Prepare for switching action.
if (switchable)
{
player.WeaponState |= WF_WEAPONSWITCHOK | WF_REFIRESWITCHOK;
}
else
{
// WF_WEAPONSWITCHOK is automatically cleared every tic by P_SetPsprite().
player.WeaponState &= ~WF_REFIRESWITCHOK;
}
}
static void DoReadyWeaponDisableSwitch (PlayerInfo player, int disable)
{
// Discard all switch attempts?
if (disable)
{
player.WeaponState |= WF_DISABLESWITCH;
player.WeaponState &= ~WF_REFIRESWITCHOK;
}
else
{
player.WeaponState &= ~WF_DISABLESWITCH;
}
}
static void DoReadyWeaponToFire (PlayerPawn pawn, bool prim, bool alt)
{
let player = pawn.player;
let weapon = player.ReadyWeapon;
if (!weapon)
{
return;
}
// Change player from attack state
if (pawn.InStateSequence(pawn.curstate, pawn.MissileState) ||
pawn.InStateSequence(pawn.curstate, pawn.MeleeState))
{
pawn.PlayIdle ();
}
// Play ready sound, if any.
let psp = player.GetPSprite(PSP_WEAPON);
if (weapon.ReadySound && psp && psp.curState == weapon.FindState('Ready'))
{
if (!weapon.bReadySndHalf || random[WpnReadySnd]() < 128)
{
pawn.A_StartSound(weapon.ReadySound, CHAN_WEAPON);
}
}
// Prepare for firing action.
player.WeaponState |= ((prim ? WF_WEAPONREADY : 0) | (alt ? WF_WEAPONREADYALT : 0));
return;
}
static void DoReadyWeaponToBob (PlayerInfo player)
{
if (player.ReadyWeapon)
{
// Prepare for bobbing action.
player.WeaponState |= WF_WEAPONBOBBING;
let pspr = player.GetPSprite(PSP_WEAPON);
if (pspr)
{
pspr.x = 0;
pspr.y = WEAPONTOP;
}
}
}
static int GetButtonStateFlags(int flags)
{
// Rewritten for efficiency and clarity
int outflags = 0;
if (flags & WRF_AllowZoom) outflags |= WF_WEAPONZOOMOK;
if (flags & WRF_AllowReload) outflags |= WF_WEAPONRELOADOK;
if (flags & WRF_AllowUser1) outflags |= WF_USER1OK;
if (flags & WRF_AllowUser2) outflags |= WF_USER2OK;
if (flags & WRF_AllowUser3) outflags |= WF_USER3OK;
if (flags & WRF_AllowUser4) outflags |= WF_USER4OK;
return outflags;
}
action void A_WeaponReady(int flags = 0)
{
if (!player) return;
DoReadyWeaponToSwitch(player, !(flags & WRF_NoSwitch));
if ((flags & WRF_NoFire) != WRF_NoFire) DoReadyWeaponToFire(player.mo, !(flags & WRF_NoPrimary), !(flags & WRF_NoSecondary));
if (!(flags & WRF_NoBob)) DoReadyWeaponToBob(player);
player.WeaponState |= GetButtonStateFlags(flags);
DoReadyWeaponDisableSwitch(player, flags & WRF_DisableSwitch);
}
//---------------------------------------------------------------------------
//
// PROC A_CheckReload
//
// Present in Doom, but unused. Also present in Strife, and actually used.
//
//---------------------------------------------------------------------------
action void A_CheckReload()
{
let player = self.player;
if (player != NULL)
{
player.ReadyWeapon.CheckAmmo (player.ReadyWeapon.bAltFire ? Weapon.AltFire : Weapon.PrimaryFire, true);
}
}
//===========================================================================
//
// A_ZoomFactor
//
//===========================================================================
action void A_ZoomFactor(double zoom = 1, int flags = 0)
{
let player = self.player;
if (player != NULL && player.ReadyWeapon != NULL)
{
zoom = 1 / clamp(zoom, 0.1, 50.0);
if (flags & 1)
{ // Make the zoom instant.
player.FOV = player.DesiredFOV * zoom;
player.cheats |= CF_NOFOVINTERP;
}
if (flags & 2)
{ // Disable pitch/yaw scaling.
zoom = -zoom;
}
player.ReadyWeapon.FOVScale = zoom;
}
}
//===========================================================================
//
// A_SetCrosshair
//
//===========================================================================
action void A_SetCrosshair(int xhair)
{
let player = self.player;
if (player != NULL && player.ReadyWeapon != NULL)
{
player.ReadyWeapon.Crosshair = xhair;
}
}
//===========================================================================
//
// Weapon :: TryPickup
//
// If you can't see the weapon when it's active, then you can't pick it up.
//
//===========================================================================
override bool TryPickupRestricted (in out Actor toucher)
{
// Wrong class, but try to pick up for ammo
if (ShouldStay())
{ // Can't pick up weapons for other classes in coop netplay
return false;
}
bool gaveSome = (NULL != AddAmmo (toucher, AmmoType1, AmmoGive1));
gaveSome |= (NULL != AddAmmo (toucher, AmmoType2, AmmoGive2));
if (gaveSome)
{
GoAwayAndDie ();
}
return gaveSome;
}
//===========================================================================
//
// Weapon :: TryPickup
//
//===========================================================================
override bool TryPickup (in out Actor toucher)
{
State ReadyState = FindState('Ready');
if (ReadyState != NULL && ReadyState.ValidateSpriteFrame())
{
return Super.TryPickup (toucher);
}
return false;
}
//===========================================================================
//
// Weapon :: Use
//
// Make the player switch to self weapon.
//
//===========================================================================
override bool Use (bool pickup)
{
Weapon useweap = self;
// Powered up weapons cannot be used directly.
if (bPowered_Up) return false;
// If the player is powered-up, use the alternate version of the
// weapon, if one exists.
if (SisterWeapon != NULL &&
SisterWeapon.bPowered_Up &&
Owner.FindInventory ("PowerWeaponLevel2", true))
{
useweap = SisterWeapon;
}
if (Owner.player != NULL && Owner.player.ReadyWeapon != useweap)
{
Owner.player.PendingWeapon = useweap;
}
// Return false so that the weapon is not removed from the inventory.
return false;
}
//===========================================================================
//
// Weapon :: Destroy
//
//===========================================================================
override void OnDestroy()
{
let sister = SisterWeapon;
if (sister != NULL)
{
// avoid recursion
sister.SisterWeapon = NULL;
if (sister != self)
{ // In case we are our own sister, don't crash.
sister.Destroy();
}
}
Super.OnDestroy();
}
//===========================================================================
//
// Weapon :: HandlePickup
//
// Try to leach ammo from the weapon if you have it already.
//
//===========================================================================
override bool HandlePickup (Inventory item)
{
if (item.GetClass() == GetClass())
{
if (Weapon(item).PickupForAmmo (self))
{
item.bPickupGood = true;
}
if (MaxAmount > 1) //[SP] If amount<maxamount do another pickup test of the weapon itself!
{
return Super.HandlePickup (item);
}
return true;
}
return false;
}
//===========================================================================
//
// Weapon :: PickupForAmmo
//
// The player already has self weapon, so try to pick it up for ammo.
//
//===========================================================================
protected bool PickupForAmmo (Weapon ownedWeapon)
{
bool gotstuff = false;
// Don't take ammo if the weapon sticks around.
if (!ShouldStay ())
{
int oldamount1 = 0;
int oldamount2 = 0;
if (ownedWeapon.Ammo1 != NULL) oldamount1 = ownedWeapon.Ammo1.Amount;
if (ownedWeapon.Ammo2 != NULL) oldamount2 = ownedWeapon.Ammo2.Amount;
if (AmmoGive1 > 0) gotstuff = AddExistingAmmo (ownedWeapon.Ammo1, AmmoGive1);
if (AmmoGive2 > 0) gotstuff |= AddExistingAmmo (ownedWeapon.Ammo2, AmmoGive2);
let Owner = ownedWeapon.Owner;
if (gotstuff && Owner != NULL && Owner.player != NULL)
{
if (ownedWeapon.Ammo1 != NULL && oldamount1 == 0)
{
PlayerPawn(Owner).CheckWeaponSwitch(ownedWeapon.Ammo1.GetClass());
}
else if (ownedWeapon.Ammo2 != NULL && oldamount2 == 0)
{
PlayerPawn(Owner).CheckWeaponSwitch(ownedWeapon.Ammo2.GetClass());
}
}
}
return gotstuff;
}
//===========================================================================
//
// Weapon :: CreateCopy
//
//===========================================================================
override Inventory CreateCopy (Actor other)
{
let copy = Weapon(Super.CreateCopy (other));
if (copy != self && copy != null)
{
copy.AmmoGive1 = AmmoGive1;
copy.AmmoGive2 = AmmoGive2;
}
return copy;
}
//===========================================================================
//
// Weapon :: CreateTossable
//
// A weapon that's tossed out should contain no ammo, so you can't cheat
// by dropping it and then picking it back up.
//
//===========================================================================
override Inventory CreateTossable (int amt)
{
// Only drop the weapon that is meant to be placed in a level. That is,
// only drop the weapon that normally gives you ammo.
if (SisterWeapon != NULL &&
Default.AmmoGive1 == 0 && Default.AmmoGive2 == 0 &&
(SisterWeapon.Default.AmmoGive1 > 0 || SisterWeapon.Default.AmmoGive2 > 0))
{
return SisterWeapon.CreateTossable (amt);
}
let copy = Weapon(Super.CreateTossable (-1));
if (copy != NULL)
{
// If self weapon has a sister, remove it from the inventory too.
if (SisterWeapon != NULL)
{
SisterWeapon.SisterWeapon = NULL;
SisterWeapon.Destroy ();
}
// To avoid exploits, the tossed weapon must not have any ammo.
copy.AmmoGive1 = 0;
copy.AmmoGive2 = 0;
}
return copy;
}
//===========================================================================
//
// Weapon :: AttachToOwner
//
//===========================================================================
override void AttachToOwner (Actor other)
{
Super.AttachToOwner (other);
Ammo1 = AddAmmo (Owner, AmmoType1, AmmoGive1);
Ammo2 = AddAmmo (Owner, AmmoType2, AmmoGive2);
SisterWeapon = AddWeapon (SisterWeaponType);
if (Owner.player != NULL)
{
if (!Owner.player.GetNeverSwitch() && !bNo_Auto_Switch)
{
Owner.player.PendingWeapon = self;
}
if (Owner.player.mo == players[consoleplayer].camera)
{
StatusBar.ReceivedWeapon (self);
}
}
GivenAsMorphWeapon = false; // will be set explicitly by morphing code
}
//===========================================================================
//
// Weapon :: AddAmmo
//
// Give some ammo to the owner, even if it's just 0.
//
//===========================================================================
protected Ammo AddAmmo (Actor other, Class<Ammo> ammotype, int amount)
{
Ammo ammoitem;
if (ammotype == NULL)
{
return NULL;
}
// [BC] This behavior is from the original Doom. Give 5/2 times as much ammoitem when
// we pick up a weapon in deathmatch.
if (( deathmatch && !sv_noextraammo ) && ( gameinfo.gametype & GAME_DoomChex ))
amount = amount * 5 / 2;
// extra ammoitem in baby mode and nightmare mode
if (!bIgnoreSkill)
{
amount = int(amount * (G_SkillPropertyFloat(SKILLP_AmmoFactor) * sv_ammofactor));
}
ammoitem = Ammo(other.FindInventory (ammotype));
if (ammoitem == NULL)
{
ammoitem = Ammo(Spawn (ammotype));
ammoitem.Amount = MIN (amount, ammoitem.MaxAmount);
ammoitem.AttachToOwner (other);
}
else if (ammoitem.Amount < ammoitem.MaxAmount || sv_unlimited_pickup)
{
ammoitem.Amount += amount;
if (ammoitem.Amount > ammoitem.MaxAmount && !sv_unlimited_pickup)
{
ammoitem.Amount = ammoitem.MaxAmount;
}
}
return ammoitem;
}
//===========================================================================
//
// Weapon :: AddExistingAmmo
//
// Give the owner some more ammo he already has.
//
//===========================================================================
protected bool AddExistingAmmo (Inventory ammo, int amount)
{
if (ammo != NULL && (ammo.Amount < ammo.MaxAmount || sv_unlimited_pickup))
{
// extra ammo in baby mode and nightmare mode
if (!bIgnoreSkill)
{
amount = int(amount * (G_SkillPropertyFloat(SKILLP_AmmoFactor) * sv_ammofactor));
}
ammo.Amount += amount;
if (ammo.Amount > ammo.MaxAmount && !sv_unlimited_pickup)
{
ammo.Amount = ammo.MaxAmount;
}
return true;
}
return false;
}
//===========================================================================
//
// Weapon :: AddWeapon
//
// Give the owner a weapon if they don't have it already.
//
//===========================================================================
protected Weapon AddWeapon (Class<Weapon> weapontype)
{
Weapon weap;
if (weapontype == NULL)
{
return NULL;
}
weap = Weapon(Owner.FindInventory (weapontype));
if (weap == NULL)
{
weap = Weapon(Spawn (weapontype));
weap.AttachToOwner (Owner);
}
return weap;
}
//===========================================================================
//
// Weapon :: ShouldStay
//
//===========================================================================
override bool ShouldStay ()
{
if (((multiplayer &&
(!deathmatch && !alwaysapplydmflags)) || sv_weaponstay) &&
!bDropped)
{
return true;
}
return false;
}
//===========================================================================
//
// Weapon :: EndPowerUp
//
// The Tome of Power just expired.
//
//===========================================================================
virtual void EndPowerup ()
{
let player = Owner.player;
if (SisterWeapon != NULL && bPowered_Up)
{
let ready = GetReadyState();
if (ready != SisterWeapon.GetReadyState())
{
if (player.PendingWeapon == NULL || player.PendingWeapon == WP_NOCHANGE)
{
player.refire = 0;
player.ReadyWeapon = SisterWeapon;
player.SetPsprite(PSP_WEAPON, SisterWeapon.GetReadyState());
}
}
else
{
let psp = player.FindPSprite(PSP_WEAPON);
if (psp != null && psp.Caller == player.ReadyWeapon && psp.CurState.InStateSequence(ready))
{
// If the weapon changes but the state does not, we have to manually change the PSprite's caller here.
psp.Caller = SisterWeapon;
player.ReadyWeapon = SisterWeapon;
}
else
{
if (player.PendingWeapon == NULL || player.PendingWeapon == WP_NOCHANGE)
{
// Something went wrong. Initiate a regular weapon change.
player.refire = 0;
player.ReadyWeapon = SisterWeapon;
player.SetPsprite(PSP_WEAPON, SisterWeapon.GetReadyState());
}
}
}
}
}
//===========================================================================
//
// Weapon :: PostMorphWeapon
//
// Bring this weapon up after a player unmorphs.
//
//===========================================================================
void PostMorphWeapon ()
{
if (Owner == null)
{
return;
}
let p = owner.player;
p.PendingWeapon = WP_NOCHANGE;
p.ReadyWeapon = self;
p.refire = 0;
let pspr = p.GetPSprite(PSP_WEAPON);
if (pspr)
{
pspr.y = WEAPONBOTTOM;
pspr.ResetInterpolation();
pspr.SetState(GetUpState());
}
}
//===========================================================================
//
// Weapon :: CheckAmmo
//
// Returns true if there is enough ammo to shoot. If not, selects the
// next weapon to use.
//
//===========================================================================
virtual bool CheckAmmo(int fireMode, bool autoSwitch, bool requireAmmo = false, int ammocount = -1)
{
int count1, count2;
int enough, enoughmask;
int lAmmoUse1;
int lAmmoUse2 = AmmoUse2;
if (sv_infiniteammo || (Owner.FindInventory ('PowerInfiniteAmmo', true) != null))
{
return true;
}
if (fireMode == EitherFire)
{
bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false);
if (!gotSome && autoSwitch)
{
PlayerPawn(Owner).PickNewWeapon (null);
}
return gotSome;
}
let altFire = (fireMode == AltFire);
let optional = (altFire? bAlt_Ammo_Optional : bAmmo_Optional);
let useboth = (altFire? bAlt_Uses_Both : bPrimary_Uses_Both);
if (!requireAmmo && optional)
{
return true;
}
count1 = (Ammo1 != null) ? Ammo1.Amount : 0;
count2 = (Ammo2 != null) ? Ammo2.Amount : 0;
if (bDehAmmo && Ammo1 == null)
{
lAmmoUse1 = 0;
}
else if (ammocount >= 0)
{
lAmmoUse1 = ammocount;
lAmmoUse2 = ammocount;
}
else
{
lAmmoUse1 = AmmoUse1;
}
enough = (count1 >= lAmmoUse1) | ((count2 >= lAmmoUse2) << 1);
if (useboth)
{
enoughmask = 3;
}
else
{
enoughmask = 1 << altFire;
}
if (altFire && FindState('AltFire') == null)
{ // If this weapon has no alternate fire, then there is never enough ammo for it
enough &= 1;
}
if (((enough & enoughmask) == enoughmask) || (enough && bAmmo_CheckBoth))
{
return true;
}
// out of ammo, pick a weapon to change to
if (autoSwitch)
{
PlayerPawn(Owner).PickNewWeapon (null);
}
return false;
}
//===========================================================================
//
// Weapon :: DepleteAmmo
//
// Use up some of the weapon's ammo. Returns true if the ammo was successfully
// depleted. If checkEnough is false, then the ammo will always be depleted,
// even if it drops below zero.
//
//===========================================================================
virtual bool DepleteAmmo(bool altFire, bool checkEnough = true, int ammouse = -1, bool forceammouse = false)
{
if (!(sv_infiniteammo || (Owner.FindInventory ('PowerInfiniteAmmo', true) != null)))
{
if (checkEnough && !CheckAmmo (altFire ? AltFire : PrimaryFire, false, false, ammouse))
{
return false;
}
if (!altFire)
{
if (Ammo1 != null)
{
if (ammouse >= 0 && (bDehAmmo || forceammouse))
{
Ammo1.Amount -= ammouse;
}
else
{
Ammo1.Amount -= AmmoUse1;
}
}
if (bPRIMARY_USES_BOTH && Ammo2 != null)
{
Ammo2.Amount -= AmmoUse2;
}
}
else
{
if (Ammo2 != null)
{
Ammo2.Amount -= AmmoUse2;
}
if (bALT_USES_BOTH && Ammo1 != null)
{
Ammo1.Amount -= AmmoUse1;
}
}
if (Ammo1 != null && Ammo1.Amount < 0)
Ammo1.Amount = 0;
if (Ammo2 != null && Ammo2.Amount < 0)
Ammo2.Amount = 0;
}
return true;
}
//---------------------------------------------------------------------------
//
// Modifies the drop amount of this item according to the current skill's
// settings (also called by ADehackedPickup::TryPickup)
//
//---------------------------------------------------------------------------
override void ModifyDropAmount(int dropamount)
{
bool ignoreskill = true;
double dropammofactor = G_SkillPropertyFloat(SKILLP_DropAmmoFactor);
// Default drop amount is half of regular amount * regular ammo multiplication
if (dropammofactor == -1)
{
dropammofactor = 0.5;
ignoreskill = false;
}
if (dropamount > 0)
{
self.Amount = dropamount;
}
// Adjust the ammo given by this weapon
AmmoGive1 = int(AmmoGive1 * dropammofactor);
AmmoGive2 = int(AmmoGive2 * dropammofactor);
bIgnoreSkill = ignoreskill;
}
}
class WeaponGiver : Weapon
{
double AmmoFactor;
Default
{
Weapon.AmmoGive1 -1;
Weapon.AmmoGive2 -1;
}
override bool TryPickup(in out Actor toucher)
{
DropItem di = GetDropItems();
Weapon weap;
if (di != NULL)
{
Class<Weapon> ti = di.Name;
if (ti != NULL)
{
if (master == NULL)
{
// save the spawned weapon in 'master' to avoid constant respawning if it cannot be picked up.
master = weap = Weapon(Spawn(di.Name));
if (weap != NULL)
{
weap.bAlwaysPickup = false; // use the flag of self item only.
weap.bDropped = bDropped;
// If our ammo gives are non-negative, transfer them to the real weapon.
if (AmmoGive1 >= 0) weap.AmmoGive1 = AmmoGive1;
if (AmmoGive2 >= 0) weap.AmmoGive2 = AmmoGive2;
// If AmmoFactor is non-negative, modify the given ammo amounts.
if (AmmoFactor > 0)
{
weap.AmmoGive1 = int(weap.AmmoGive1 * AmmoFactor);
weap.AmmoGive2 = int(weap.AmmoGive2 * AmmoFactor);
}
weap.BecomeItem();
}
else return false;
}
weap = Weapon(master);
bool res = false;
if (weap != null)
{
res = weap.CallTryPickup(toucher);
if (res)
{
GoAwayAndDie();
master = NULL;
}
}
return res;
}
}
return false;
}
//---------------------------------------------------------------------------
//
// Modifies the drop amount of this item according to the current skill's
// settings (also called by ADehackedPickup::TryPickup)
//
//---------------------------------------------------------------------------
override void ModifyDropAmount(int dropamount)
{
bool ignoreskill = true;
double dropammofactor = G_SkillPropertyFloat(SKILLP_DropAmmoFactor);
// Default drop amount is half of regular amount * regular ammo multiplication
if (dropammofactor == -1)
{
dropammofactor = 0.5;
ignoreskill = false;
}
AmmoFactor = dropammofactor;
bIgnoreSkill = ignoreskill;
}
}
struct WeaponSlots native
{
native bool, int, int LocateWeapon(class<Weapon> weap) const;
native static void SetupWeaponSlots(PlayerPawn pp);
native class<Weapon> GetWeapon(int slot, int index) const;
native int SlotSize(int slot) const;
}