gzdoom/wadsrc/static/zscript/actor_inventory.txt
Christoph Oelckers 480dd347c9 - removed level references from p_mobj.cpp.
This time there was one important exported script function: Actor.Spawn.
Since this will require a level pointer in the new scheme of things the old version had to be deprecated, because it is static with no argument that allows retrieving the level. However, since this is probably one of the most widely used functions I added a workaround to let it continue to work if used from inside an actor class, which should constitute >95% of all uses. This required a little bit of hackery in the compiler backend to swap out the function if appropriate.
Aside from that there were 5 places in the internal ZScript that needed handling, which mostly consisted of making a formerly static internal function non-static.
2019-01-07 00:51:18 +01:00

812 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 (!orresult)
{
receiver = receiver.GetPointer(setreceiver);
}
if (receiver == NULL)
{ // If there's nothing to receive it, it's obviously a fail, right?
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;
}
}
}