mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-12-11 13:11:48 +00:00
1b068a10c6
The result of GetPointer must be null checked as well!
816 lines
18 KiB
Text
816 lines
18 KiB
Text
extend class Actor
|
|
{
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: FirstInv
|
|
//
|
|
// Returns the first item in this actor's inventory that has IF_INVBAR set.
|
|
//
|
|
//============================================================================
|
|
|
|
clearscope Inventory FirstInv ()
|
|
{
|
|
if (Inv == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (Inv.bInvBar)
|
|
{
|
|
return Inv;
|
|
}
|
|
return Inv.NextInv ();
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: AddInventory
|
|
//
|
|
//============================================================================
|
|
|
|
virtual void AddInventory (Inventory item)
|
|
{
|
|
// Check if it's already attached to an actor
|
|
if (item.Owner != NULL)
|
|
{
|
|
// Is it attached to us?
|
|
if (item.Owner == self)
|
|
return;
|
|
|
|
// No, then remove it from the other actor first
|
|
item.Owner.RemoveInventory (item);
|
|
}
|
|
|
|
item.Owner = self;
|
|
item.Inv = Inv;
|
|
Inv = item;
|
|
|
|
// Each item receives an unique ID when added to an actor's inventory.
|
|
// This is used by the DEM_INVUSE command to identify the item. Simply
|
|
// using the item's position in the list won't work, because ticcmds get
|
|
// run sometime in the future, so by the time it runs, the inventory
|
|
// might not be in the same state as it was when DEM_INVUSE was sent.
|
|
Inv.InventoryID = InventoryID++;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: GiveInventory
|
|
//
|
|
//============================================================================
|
|
|
|
bool GiveInventory(Class<Inventory> type, int amount, bool givecheat = false)
|
|
{
|
|
bool result = true;
|
|
let player = self.player;
|
|
|
|
// This can be called from places which do not check the given item's type.
|
|
if (type == null || !(type is 'Inventory')) return false;
|
|
|
|
Weapon savedPendingWeap = player != NULL ? player.PendingWeapon : NULL;
|
|
bool hadweap = player != NULL ? player.ReadyWeapon != NULL : true;
|
|
|
|
Inventory item;
|
|
if (!givecheat)
|
|
{
|
|
item = Inventory(Spawn (type));
|
|
}
|
|
else
|
|
{
|
|
item = Inventory(Spawn (type, Pos, NO_REPLACE));
|
|
if (item == NULL) return false;
|
|
}
|
|
|
|
// This shouldn't count for the item statistics.
|
|
item.ClearCounters();
|
|
if (!givecheat || amount > 0)
|
|
{
|
|
item.SetGiveAmount(self, amount, givecheat);
|
|
}
|
|
if (!item.CallTryPickup (self))
|
|
{
|
|
item.Destroy ();
|
|
result = false;
|
|
}
|
|
// If the item was a weapon, don't bring it up automatically
|
|
// unless the player was not already using a weapon.
|
|
// Don't bring it up automatically if this is called by the give cheat.
|
|
if (!givecheat && player != NULL && savedPendingWeap != NULL && hadweap)
|
|
{
|
|
player.PendingWeapon = savedPendingWeap;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: RemoveInventory
|
|
//
|
|
//============================================================================
|
|
|
|
virtual void RemoveInventory(Inventory item)
|
|
{
|
|
if (item != NULL && item.Owner != NULL) // can happen if the owner was destroyed by some action from an item's use state.
|
|
{
|
|
if (Inv == item) Inv = item.Inv;
|
|
else
|
|
{
|
|
for (Actor invp = self; invp != null; invp = invp.Inv)
|
|
{
|
|
if (invp.Inv == item)
|
|
{
|
|
invp.Inv = item.Inv;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
item.DetachFromOwner();
|
|
item.Owner = NULL;
|
|
item.Inv = NULL;
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: TakeInventory
|
|
//
|
|
//============================================================================
|
|
|
|
bool TakeInventory(class<Inventory> itemclass, int amount, bool fromdecorate = false, bool notakeinfinite = false)
|
|
{
|
|
amount = abs(amount);
|
|
let item = FindInventory(itemclass);
|
|
|
|
if (item == NULL)
|
|
return false;
|
|
|
|
if (!fromdecorate)
|
|
{
|
|
item.Amount -= amount;
|
|
if (item.Amount <= 0)
|
|
{
|
|
item.DepleteOrDestroy();
|
|
}
|
|
// It won't be used in non-decorate context, so return false here
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
if (item.Amount > 0)
|
|
{
|
|
result = true;
|
|
}
|
|
|
|
// Do not take ammo if the "no take infinite/take as ammo depletion" flag is set
|
|
// and infinite ammo is on
|
|
if (notakeinfinite &&
|
|
(sv_infiniteammo || (player && FindInventory('PowerInfiniteAmmo', true))) && (item is 'Ammo'))
|
|
{
|
|
// Nothing to do here, except maybe res = false;? Would it make sense?
|
|
result = false;
|
|
}
|
|
else if (!amount || amount >= item.Amount)
|
|
{
|
|
item.DepleteOrDestroy();
|
|
}
|
|
else item.Amount -= amount;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: SetInventory
|
|
//
|
|
//============================================================================
|
|
|
|
bool SetInventory(class<Inventory> itemclass, int amount, bool beyondMax = false)
|
|
{
|
|
let item = FindInventory(itemclass);
|
|
|
|
if (item != null)
|
|
{
|
|
// A_SetInventory sets the absolute amount.
|
|
// Subtract or set the appropriate amount as necessary.
|
|
|
|
if (amount == item.Amount)
|
|
{
|
|
// Nothing was changed.
|
|
return false;
|
|
}
|
|
else if (amount <= 0)
|
|
{
|
|
//Remove it all.
|
|
return TakeInventory(itemclass, item.Amount, true, false);
|
|
}
|
|
else if (amount < item.Amount)
|
|
{
|
|
int amt = abs(item.Amount - amount);
|
|
return TakeInventory(itemclass, amt, true, false);
|
|
}
|
|
else
|
|
{
|
|
item.Amount = (beyondMax ? amount : clamp(amount, 0, item.MaxAmount));
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (amount <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
item = Inventory(Spawn(itemclass));
|
|
if (item == null)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
item.Amount = amount;
|
|
item.bDropped = true;
|
|
item.bIgnoreSkill = true;
|
|
item.ClearCounters();
|
|
if (!item.CallTryPickup(self))
|
|
{
|
|
item.Destroy();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: UseInventory
|
|
//
|
|
// Attempts to use an item. If the use succeeds, one copy of the item is
|
|
// removed from the inventory. If all copies are removed, then the item is
|
|
// destroyed.
|
|
//
|
|
//============================================================================
|
|
|
|
virtual bool UseInventory (Inventory item)
|
|
{
|
|
// No using items if you're dead or you don't have them.
|
|
if (health <= 0 || item.Amount <= 0 || item.bDestroyed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!item.Use(false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (sv_infiniteinventory)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (--item.Amount <= 0)
|
|
{
|
|
item.DepleteOrDestroy ();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// AActor :: DropInventory
|
|
//
|
|
// Removes a single copy of an item and throws it out in front of the actor.
|
|
//
|
|
//===========================================================================
|
|
|
|
Inventory DropInventory (Inventory item, int amt = 1)
|
|
{
|
|
Inventory drop = item.CreateTossable(amt);
|
|
if (drop == null) return NULL;
|
|
drop.SetOrigin(Pos + (0, 0, 10.), false);
|
|
drop.Angle = Angle;
|
|
drop.VelFromAngle(5.);
|
|
drop.Vel.Z = 1.;
|
|
drop.Vel += Vel;
|
|
drop.bNoGravity = false; // Don't float
|
|
drop.ClearCounters(); // do not count for statistics again
|
|
drop.OnDrop(self);
|
|
return drop;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: ClearInventory
|
|
//
|
|
// Clears the inventory of a single actor.
|
|
//
|
|
//============================================================================
|
|
|
|
virtual void ClearInventory()
|
|
{
|
|
// In case destroying an inventory item causes another to be destroyed
|
|
// (e.g. Weapons destroy their sisters), keep track of the pointer to
|
|
// the next inventory item rather than the next inventory item itself.
|
|
// For example, if a weapon is immediately followed by its sister, the
|
|
// next weapon we had tracked would be to the sister, so it is now
|
|
// invalid and we won't be able to find the complete inventory by
|
|
// following it.
|
|
//
|
|
// When we destroy an item, we leave last alone, since the destruction
|
|
// process will leave it pointing to the next item we want to check. If
|
|
// we don't destroy an item, then we move last to point to its Inventory
|
|
// pointer.
|
|
//
|
|
// It should be safe to assume that an item being destroyed will only
|
|
// destroy items further down in the chain, because if it was going to
|
|
// destroy something we already processed, we've already destroyed it,
|
|
// so it won't have anything to destroy.
|
|
|
|
let last = self;
|
|
|
|
while (last.inv != NULL)
|
|
{
|
|
let inv = last.inv;
|
|
if (!inv.bUndroppable)
|
|
{
|
|
inv.DepleteOrDestroy();
|
|
if (!inv.bDestroyed) last = inv; // was only depleted so advance the pointer manually.
|
|
}
|
|
else
|
|
{
|
|
last = inv;
|
|
}
|
|
}
|
|
if (player != null)
|
|
{
|
|
player.ReadyWeapon = null;
|
|
player.PendingWeapon = WP_NOCHANGE;
|
|
}
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: GiveAmmo
|
|
//
|
|
// Returns true if the ammo was added, false if not.
|
|
//
|
|
//============================================================================
|
|
|
|
bool GiveAmmo (Class<Ammo> type, int amount)
|
|
{
|
|
if (type != NULL && type is 'Ammo')
|
|
{
|
|
let item = Inventory(Spawn (type));
|
|
if (item)
|
|
{
|
|
item.Amount = amount;
|
|
item.bDropped = true;
|
|
if (!item.CallTryPickup (self))
|
|
{
|
|
item.Destroy ();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// DoGiveInventory
|
|
//
|
|
//===========================================================================
|
|
|
|
private bool DoGiveInventory(Actor receiver, bool orresult, class<Inventory> mi, int amount, int setreceiver)
|
|
{
|
|
int paramnum = 0;
|
|
|
|
if (receiver == NULL)
|
|
{ // If there's nothing to receive it, it's obviously a fail, right?
|
|
return false;
|
|
}
|
|
if (!orresult)
|
|
{
|
|
receiver = receiver.GetPointer(setreceiver);
|
|
if (receiver == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// Owned inventory items cannot own anything because their Inventory pointer is repurposed for the owner's linked list.
|
|
if (receiver is 'Inventory' && Inventory(receiver).Owner != null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (amount <= 0)
|
|
{
|
|
amount = 1;
|
|
}
|
|
if (mi)
|
|
{
|
|
let item = Inventory(Spawn(mi));
|
|
if (item == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
if (item is 'Health')
|
|
{
|
|
item.Amount *= amount;
|
|
}
|
|
else
|
|
{
|
|
item.Amount = amount;
|
|
}
|
|
item.bDropped = true;
|
|
item.ClearCounters();
|
|
if (!item.CallTryPickup(receiver))
|
|
{
|
|
item.Destroy();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool A_GiveInventory(class<Inventory> itemtype, int amount = 0, int giveto = AAPTR_DEFAULT)
|
|
{
|
|
return DoGiveInventory(self, false, itemtype, amount, giveto);
|
|
}
|
|
|
|
bool A_GiveToTarget(class<Inventory> itemtype, int amount = 0, int giveto = AAPTR_DEFAULT)
|
|
{
|
|
return DoGiveInventory(target, false, itemtype, amount, giveto);
|
|
}
|
|
|
|
int A_GiveToChildren(class<Inventory> itemtype, int amount = 0)
|
|
{
|
|
let it = ThinkerIterator.Create('Actor');
|
|
Actor mo;
|
|
int count = 0;
|
|
|
|
while ((mo = Actor(it.Next())))
|
|
{
|
|
if (mo.master == self)
|
|
{
|
|
count += DoGiveInventory(mo, true, itemtype, amount, AAPTR_DEFAULT);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int A_GiveToSiblings(class<Inventory> itemtype, int amount = 0)
|
|
{
|
|
let it = ThinkerIterator.Create('Actor');
|
|
Actor mo;
|
|
int count = 0;
|
|
|
|
if (self.master != NULL)
|
|
{
|
|
while ((mo = Actor(it.Next())))
|
|
{
|
|
if (mo.master == self.master && mo != self)
|
|
{
|
|
count += DoGiveInventory(mo, true, itemtype, amount, AAPTR_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_TakeInventory
|
|
//
|
|
//===========================================================================
|
|
|
|
bool DoTakeInventory(Actor receiver, bool orresult, class<Inventory> itemtype, int amount, int flags, int setreceiver = AAPTR_DEFAULT)
|
|
{
|
|
int paramnum = 0;
|
|
|
|
if (itemtype == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
if (!orresult)
|
|
{
|
|
receiver = receiver.GetPointer(setreceiver);
|
|
}
|
|
if (receiver == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return receiver.TakeInventory(itemtype, amount, true, (flags & TIF_NOTAKEINFINITE) != 0);
|
|
}
|
|
|
|
bool A_TakeInventory(class<Inventory> itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT)
|
|
{
|
|
return DoTakeInventory(self, false, itemtype, amount, flags, giveto);
|
|
}
|
|
|
|
bool A_TakeFromTarget(class<Inventory> itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT)
|
|
{
|
|
return DoTakeInventory(target, false, itemtype, amount, flags, giveto);
|
|
}
|
|
|
|
int A_TakeFromChildren(class<Inventory> itemtype, int amount = 0)
|
|
{
|
|
let it = ThinkerIterator.Create('Actor');
|
|
Actor mo;
|
|
int count = 0;
|
|
|
|
while ((mo = Actor(it.Next())))
|
|
{
|
|
if (mo.master == self)
|
|
{
|
|
count += DoTakeInventory(mo, true, itemtype, amount, 0, AAPTR_DEFAULT);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int A_TakeFromSiblings(class<Inventory> itemtype, int amount = 0)
|
|
{
|
|
let it = ThinkerIterator.Create('Actor');
|
|
Actor mo;
|
|
int count = 0;
|
|
|
|
if (self.master != NULL)
|
|
{
|
|
while ((mo = Actor(it.Next())))
|
|
{
|
|
if (mo.master == self.master && mo != self)
|
|
{
|
|
count += DoTakeInventory(mo, true, itemtype, amount, 0, AAPTR_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_SetInventory
|
|
//
|
|
//===========================================================================
|
|
|
|
bool A_SetInventory(class<Inventory> itemtype, int amount, int ptr = AAPTR_DEFAULT, bool beyondMax = false)
|
|
{
|
|
bool res = false;
|
|
|
|
if (itemtype == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Actor mobj = GetPointer(ptr);
|
|
|
|
if (mobj == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Do not run this function on voodoo dolls because the way they transfer the inventory to the player will not work with the code below.
|
|
if (mobj.player != null)
|
|
{
|
|
mobj = mobj.player.mo;
|
|
}
|
|
return mobj.SetInventory(itemtype, amount, beyondMax);
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// P_TossItem
|
|
//
|
|
//============================================================================
|
|
|
|
void TossItem ()
|
|
{
|
|
int style = sv_dropstyle;
|
|
if (style==0) style = gameinfo.defaultdropstyle;
|
|
|
|
if (style==2)
|
|
{
|
|
Vel.X += random2[DropItem](7);
|
|
Vel.Y += random2[DropItem](7);
|
|
}
|
|
else
|
|
{
|
|
Vel.X += random2[DropItem]() / 256.;
|
|
Vel.Y += random2[DropItem]() / 256.;
|
|
Vel.Z = 5. + random[DropItem]() / 64.;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// PROC A_DropItem
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
Actor A_DropItem(class<Actor> item, int dropamount = -1, int chance = 256)
|
|
{
|
|
if (item != NULL && random[DropItem]() <= chance)
|
|
{
|
|
Actor mo;
|
|
double spawnz = 0;
|
|
|
|
if (!compat_notossdrops)
|
|
{
|
|
int style = sv_dropstyle;
|
|
if (style == 0)
|
|
{
|
|
style = gameinfo.defaultdropstyle;
|
|
}
|
|
if (style == 2)
|
|
{
|
|
spawnz = 24;
|
|
}
|
|
else
|
|
{
|
|
spawnz = Height / 2;
|
|
}
|
|
}
|
|
mo = Spawn(item, pos + (0, 0, spawnz), ALLOW_REPLACE);
|
|
if (mo != NULL)
|
|
{
|
|
mo.bDropped = true;
|
|
mo.bNoGravity = false; // [RH] Make sure it is affected by gravity
|
|
if (!compat_notossdrops)
|
|
{
|
|
mo.TossItem ();
|
|
}
|
|
let inv = Inventory(mo);
|
|
if (inv)
|
|
{
|
|
inv.ModifyDropAmount(dropamount);
|
|
inv.bTossed = true;
|
|
if (inv.SpecialDropAction(self))
|
|
{
|
|
// The special action indicates that the item should not spawn
|
|
inv.Destroy();
|
|
return null;
|
|
}
|
|
}
|
|
return mo;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// CountInv
|
|
//
|
|
// NON-ACTION function to return the inventory count of an item.
|
|
//
|
|
//==========================================================================
|
|
|
|
clearscope int CountInv(class<Inventory> itemtype, int ptr_select = AAPTR_DEFAULT) const
|
|
{
|
|
let realself = GetPointer(ptr_select);
|
|
if (realself == NULL || itemtype == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
let item = realself.FindInventory(itemtype);
|
|
return item ? item.Amount : 0;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// State jump function
|
|
//
|
|
//==========================================================================
|
|
|
|
bool CheckInventory(class<Inventory> itemtype, int itemamount, int owner = AAPTR_DEFAULT)
|
|
{
|
|
if (itemtype == null)
|
|
{
|
|
return false;
|
|
}
|
|
let owner = GetPointer(owner);
|
|
if (owner == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let item = owner.FindInventory(itemtype);
|
|
|
|
if (item)
|
|
{
|
|
if (itemamount > 0)
|
|
{
|
|
if (item.Amount >= itemamount)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (item.Amount >= item.MaxAmount)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//============================================================================
|
|
//
|
|
// AActor :: ObtainInventory
|
|
//
|
|
// Removes the items from the other actor and puts them in this actor's
|
|
// inventory. The actor receiving the inventory must not have any items.
|
|
//
|
|
//============================================================================
|
|
|
|
void ObtainInventory (Actor other)
|
|
{
|
|
Inv = other.Inv;
|
|
InventoryID = other.InventoryID;
|
|
other.Inv = NULL;
|
|
other.InventoryID = 0;
|
|
|
|
let you = PlayerPawn(other);
|
|
let me = PlayerPawn(self);
|
|
|
|
if (you)
|
|
{
|
|
if (me)
|
|
{
|
|
me.InvFirst = you.InvFirst;
|
|
me.InvSel = you.InvSel;
|
|
}
|
|
you.InvFirst = NULL;
|
|
you.InvSel = NULL;
|
|
}
|
|
|
|
|
|
for (let item = Inv; item != null; item = item.Inv)
|
|
{
|
|
item.Owner = self;
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_SelectWeapon
|
|
//
|
|
//===========================================================================
|
|
|
|
bool A_SelectWeapon(class<Weapon> whichweapon, int flags = 0)
|
|
{
|
|
bool selectPriority = !!(flags & SWF_SELECTPRIORITY);
|
|
let player = self.player;
|
|
|
|
if ((!selectPriority && whichweapon == NULL) || player == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let weaponitem = Weapon(FindInventory(whichweapon));
|
|
|
|
if (weaponitem != NULL)
|
|
{
|
|
if (player.ReadyWeapon != weaponitem)
|
|
{
|
|
player.PendingWeapon = weaponitem;
|
|
}
|
|
return true;
|
|
}
|
|
else if (selectPriority)
|
|
{
|
|
// [XA] if the named weapon cannot be found (or is a dummy like 'None'),
|
|
// select the next highest priority weapon. This is basically
|
|
// the same as A_CheckReload minus the ammo check. Handy.
|
|
player.mo.PickNewWeapon(NULL);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|