gzdoom/wadsrc/static/zscript/actor_inventory.txt
Major Cooke 695eced81a Added Inventory UNCLEARABLE flag.
- Allows prevention of ClearInventory without stopping it from being dropped.
2019-02-01 18:29:31 +01:00

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.bUnclearable)
{
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
//
//===========================================================================
static 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;
}
}
}