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 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) { Inventory invp; 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 (invp = Inv; 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 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 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 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 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 itemtype, int amount = 0, int giveto = AAPTR_DEFAULT) { return DoGiveInventory(self, false, itemtype, amount, giveto); } bool A_GiveToTarget(class itemtype, int amount = 0, int giveto = AAPTR_DEFAULT) { return DoGiveInventory(target, false, itemtype, amount, giveto); } int A_GiveToChildren(class 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 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 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 itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT) { return DoTakeInventory(self, false, itemtype, amount, flags, giveto); } bool A_TakeFromTarget(class itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT) { return DoTakeInventory(target, false, itemtype, amount, flags, giveto); } int A_TakeFromChildren(class 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 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 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 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 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 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; } //=========================================================================== // // A_SelectWeapon // //=========================================================================== bool A_SelectWeapon(class 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; } } }