diff --git a/Source/inv.h b/Source/inv.h index 95a63d570..a4bad9fc6 100644 --- a/Source/inv.h +++ b/Source/inv.h @@ -1,158 +1,158 @@ -/** - * @file inv.h - * - * Interface of player inventory. - */ -#pragma once - -#include - -#include "engine/palette.h" -#include "engine/point.hpp" -#include "inv_iterators.hpp" -#include "items.h" -#include "player.h" -#include "utils/algorithm/container.hpp" - -namespace devilution { - -#define INV_SLOT_SIZE_PX 28 -#define INV_SLOT_HALF_SIZE_PX (INV_SLOT_SIZE_PX / 2) -constexpr Size InventorySizeInSlots { 10, 4 }; -#define INV_ROW_SLOT_SIZE InventorySizeInSlots.width -constexpr Size InventorySlotSizeInPixels { INV_SLOT_SIZE_PX }; - -enum inv_item : int8_t { - // clang-format off - INVITEM_HEAD = 0, - INVITEM_RING_LEFT = 1, - INVITEM_RING_RIGHT = 2, - INVITEM_AMULET = 3, - INVITEM_HAND_LEFT = 4, - INVITEM_HAND_RIGHT = 5, - INVITEM_CHEST = 6, - INVITEM_INV_FIRST = 7, - INVITEM_INV_LAST = 46, - INVITEM_BELT_FIRST = 47, - INVITEM_BELT_LAST = 54, - // clang-format on -}; - -/** - * identifiers for each of the inventory squares - * @see InvRect - */ -enum inv_xy_slot : uint8_t { - // clang-format off - SLOTXY_HEAD = 0, - SLOTXY_EQUIPPED_FIRST = SLOTXY_HEAD, - SLOTXY_RING_LEFT = 1, - SLOTXY_RING_RIGHT = 2, - SLOTXY_AMULET = 3, - SLOTXY_HAND_LEFT = 4, - SLOTXY_HAND_RIGHT = 5, - SLOTXY_CHEST = 6, - SLOTXY_EQUIPPED_LAST = SLOTXY_CHEST, - - // regular inventory - SLOTXY_INV_FIRST = 7, - SLOTXY_INV_ROW1_FIRST = SLOTXY_INV_FIRST, - SLOTXY_INV_ROW1_LAST = 16, - SLOTXY_INV_ROW2_FIRST = 17, - SLOTXY_INV_ROW2_LAST = 26, - SLOTXY_INV_ROW3_FIRST = 27, - SLOTXY_INV_ROW3_LAST = 36, - SLOTXY_INV_ROW4_FIRST = 37, - SLOTXY_INV_ROW4_LAST = 46, - SLOTXY_INV_LAST = SLOTXY_INV_ROW4_LAST, - - // belt items - SLOTXY_BELT_FIRST = 47, - SLOTXY_BELT_LAST = 54, - NUM_XY_SLOTS = 55 - // clang-format on -}; - -enum item_color : uint8_t { +/** + * @file inv.h + * + * Interface of player inventory. + */ +#pragma once + +#include + +#include "engine/palette.h" +#include "engine/point.hpp" +#include "inv_iterators.hpp" +#include "items.h" +#include "player.h" +#include "utils/algorithm/container.hpp" + +namespace devilution { + +#define INV_SLOT_SIZE_PX 28 +#define INV_SLOT_HALF_SIZE_PX (INV_SLOT_SIZE_PX / 2) +constexpr Size InventorySizeInSlots { 10, 4 }; +#define INV_ROW_SLOT_SIZE InventorySizeInSlots.width +constexpr Size InventorySlotSizeInPixels { INV_SLOT_SIZE_PX }; + +enum inv_item : int8_t { + // clang-format off + INVITEM_HEAD = 0, + INVITEM_RING_LEFT = 1, + INVITEM_RING_RIGHT = 2, + INVITEM_AMULET = 3, + INVITEM_HAND_LEFT = 4, + INVITEM_HAND_RIGHT = 5, + INVITEM_CHEST = 6, + INVITEM_INV_FIRST = 7, + INVITEM_INV_LAST = 46, + INVITEM_BELT_FIRST = 47, + INVITEM_BELT_LAST = 54, + // clang-format on +}; + +/** + * identifiers for each of the inventory squares + * @see InvRect + */ +enum inv_xy_slot : uint8_t { + // clang-format off + SLOTXY_HEAD = 0, + SLOTXY_EQUIPPED_FIRST = SLOTXY_HEAD, + SLOTXY_RING_LEFT = 1, + SLOTXY_RING_RIGHT = 2, + SLOTXY_AMULET = 3, + SLOTXY_HAND_LEFT = 4, + SLOTXY_HAND_RIGHT = 5, + SLOTXY_CHEST = 6, + SLOTXY_EQUIPPED_LAST = SLOTXY_CHEST, + + // regular inventory + SLOTXY_INV_FIRST = 7, + SLOTXY_INV_ROW1_FIRST = SLOTXY_INV_FIRST, + SLOTXY_INV_ROW1_LAST = 16, + SLOTXY_INV_ROW2_FIRST = 17, + SLOTXY_INV_ROW2_LAST = 26, + SLOTXY_INV_ROW3_FIRST = 27, + SLOTXY_INV_ROW3_LAST = 36, + SLOTXY_INV_ROW4_FIRST = 37, + SLOTXY_INV_ROW4_LAST = 46, + SLOTXY_INV_LAST = SLOTXY_INV_ROW4_LAST, + + // belt items + SLOTXY_BELT_FIRST = 47, + SLOTXY_BELT_LAST = 54, + NUM_XY_SLOTS = 55 + // clang-format on +}; + +enum item_color : uint8_t { // clang-format off ICOL_YELLOW = PAL16_YELLOW + 5, ICOL_WHITE = PAL16_GRAY + 5, ICOL_BLUE = PAL16_BLUE + 5, - ICOL_RED = PAL16_RED + 5, - // clang-format on -}; - -enum class InventoryInsertSemantics { - PlayerAction, - InternalRebuild, -}; - -extern bool invflag; -extern const Rectangle InvRect[NUM_XY_SLOTS]; - -void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_quality itemQuality); -/** - * @brief Checks whether the given item can be placed on the belt. Takes item size as well as characteristics into account. Items - * that cannot be placed on the belt have to be placed in the inventory instead. - * @param item The item to be checked. - * @return 'True' in case the item can be placed on the belt and 'False' otherwise. - */ -bool CanBePlacedOnBelt(const Player &player, const Item &item); - -/** - * @brief Function type which performs an operation on the given item. - */ -using ItemFunc = void (*)(Item &); - -void CloseInventory(); -void CloseStash(); -void FreeInvGFX(); -void InitInv(); - -/** - * @brief Render the inventory panel to the given buffer. - */ -void DrawInv(const Surface &out); - -void DrawInvBelt(const Surface &out); - -/** - * @brief Removes equipment from the specified location on the player's body. - * @param player The player from which equipment will be removed. - * @param bodyLocation The location from which equipment will be removed. - * @param hiPri Priority of the network message to sync player equipment. - */ -void RemoveEquipment(Player &player, inv_body_loc bodyLocation, bool hiPri); - -/** - * @brief Checks whether or not auto-equipping behavior is enabled for the given player and item. - * @param player The player to check. - * @param item The item to check. - * @return 'True' if auto-equipping behavior is enabled for the player and item and 'False' otherwise. - */ -bool AutoEquipEnabled(const Player &player, const Item &item); - -/** - * @brief Automatically attempts to equip the specified item in the most appropriate location in the player's body. - * @note On success, this will broadcast an equipment_change event to let other players know about the equipment change. - * @param player The player whose inventory will be checked for compatibility with the item. - * @param item The item to equip. - * @param persistItem Indicates whether or not the item should be persisted in the player's body. Pass 'False' to check - * whether the player can equip the item but you don't want the item to actually be equipped. 'True' by default. - * @param sendNetworkMessage Set to true if you want an equip sound and network message to be generated if the equipment - * changes. Should only be set if a local player is equipping an item in a play session (not when creating a new game) - * @return 'True' if the item was equipped and 'False' otherwise. - */ -bool AutoEquip(Player &player, const Item &item, bool persistItem = true, bool sendNetworkMessage = false); - -/** - * @brief Checks whether the given item can be placed on the specified player's inventory. - * @param player The player whose inventory will be checked. - * @param item The item to be checked. - * @return 'True' in case the item can be placed on the player's inventory and 'False' otherwise. - */ -bool CanFitItemInInventory(const Player &player, const Item &item); - + ICOL_RED = PAL16_RED + 5, + // clang-format on +}; + +enum class InventoryInsertSemantics { + PlayerAction, + InternalRebuild, +}; + +extern bool invflag; +extern const Rectangle InvRect[NUM_XY_SLOTS]; + +void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_quality itemQuality); +/** + * @brief Checks whether the given item can be placed on the belt. Takes item size as well as characteristics into account. Items + * that cannot be placed on the belt have to be placed in the inventory instead. + * @param item The item to be checked. + * @return 'True' in case the item can be placed on the belt and 'False' otherwise. + */ +bool CanBePlacedOnBelt(const Player &player, const Item &item); + +/** + * @brief Function type which performs an operation on the given item. + */ +using ItemFunc = void (*)(Item &); + +void CloseInventory(); +void CloseStash(); +void FreeInvGFX(); +void InitInv(); + +/** + * @brief Render the inventory panel to the given buffer. + */ +void DrawInv(const Surface &out); + +void DrawInvBelt(const Surface &out); + +/** + * @brief Removes equipment from the specified location on the player's body. + * @param player The player from which equipment will be removed. + * @param bodyLocation The location from which equipment will be removed. + * @param hiPri Priority of the network message to sync player equipment. + */ +void RemoveEquipment(Player &player, inv_body_loc bodyLocation, bool hiPri); + +/** + * @brief Checks whether or not auto-equipping behavior is enabled for the given player and item. + * @param player The player to check. + * @param item The item to check. + * @return 'True' if auto-equipping behavior is enabled for the player and item and 'False' otherwise. + */ +bool AutoEquipEnabled(const Player &player, const Item &item); + +/** + * @brief Automatically attempts to equip the specified item in the most appropriate location in the player's body. + * @note On success, this will broadcast an equipment_change event to let other players know about the equipment change. + * @param player The player whose inventory will be checked for compatibility with the item. + * @param item The item to equip. + * @param persistItem Indicates whether or not the item should be persisted in the player's body. Pass 'False' to check + * whether the player can equip the item but you don't want the item to actually be equipped. 'True' by default. + * @param sendNetworkMessage Set to true if you want an equip sound and network message to be generated if the equipment + * changes. Should only be set if a local player is equipping an item in a play session (not when creating a new game) + * @return 'True' if the item was equipped and 'False' otherwise. + */ +bool AutoEquip(Player &player, const Item &item, bool persistItem = true, bool sendNetworkMessage = false); + +/** + * @brief Checks whether the given item can be placed on the specified player's inventory. + * @param player The player whose inventory will be checked. + * @param item The item to be checked. + * @return 'True' in case the item can be placed on the player's inventory and 'False' otherwise. + */ +bool CanFitItemInInventory(const Player &player, const Item &item); + /** * @brief Attempts to place the given item in the specified player's inventory. * @param player The player whose inventory will be used. @@ -161,240 +161,240 @@ bool CanFitItemInInventory(const Player &player, const Item &item); * Should only be set if a local player is placing an item in a play session (not when creating a new game) * @param semantics Distinguishes player-facing item insertion from internal inventory rebuilds. * @return 'True' if the item was placed on the player's inventory and 'False' otherwise. - */ -bool AutoPlaceItemInInventory(Player &player, const Item &item, bool sendNetworkMessage = false, InventoryInsertSemantics semantics = InventoryInsertSemantics::PlayerAction); - -/** - * @brief Checks whether the given item can be placed on the specified player's belt. Returns 'True' when the item can be placed - * on belt slots and the player has at least one empty slot in his belt. - * If 'persistItem' is 'True', the item is also placed in the belt. - * @param player The player on whose belt will be checked. - * @param item The item to be checked. - * @param persistItem Pass 'True' to actually place the item in the belt. The default is 'False'. - * @param sendNetworkMessage Set to true if you want a network message to be generated if the item is persisted. - * Should only be set if a local player is placing an item in a play session (not when creating a new game) - * @return 'True' in case the item can be placed on the player's belt and 'False' otherwise. - */ -bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem = false, bool sendNetworkMessage = false); - -/** - * @brief Sort player inventory. - */ -void ReorganizeInventory(Player &player); - -/** - * @brief Calculate the maximum additional gold that may fit in the user's inventory - */ -int RoomForGold(); - -/** - * @return The leftover amount that didn't fit, if any - */ -int AddGoldToInventory(Player &player, int value); -bool GoldAutoPlace(Player &player, Item &goldStack); -void CheckInvSwap(Player &player, inv_body_loc bLoc); -void inv_update_rem_item(Player &player, inv_body_loc iv); -void CheckInvSwap(Player &player, const Item &item, int invGridIndex); -void CheckInvRemove(Player &player, int invGridIndex); -void TransferItemToStash(Player &player, int location); -void CheckInvItem(bool isShiftHeld = false, bool isCtrlHeld = false); - -/** - * Check for interactions with belt - */ -void CheckInvScrn(bool isShiftHeld, bool isCtrlHeld); -void InvGetItem(Player &player, int ii); - -/** - * @brief Returns the first free space that can take an item preferencing tiles in front of the current position - * - * The search starts with the adjacent tile in the desired direction and alternates sides until it ends up checking the - * opposite tile, before finally checking the origin tile - * - * @param origin center tile of the search space - * @param facing direction of the adjacent tile to check first - * @return the first valid point or an empty optional - */ -std::optional FindAdjacentPositionForItem(Point origin, Direction facing); -void AutoGetItem(Player &player, Item *itemPointer, int ii); - -/** - * @brief Searches for a dropped item with the same type/createInfo/seed - * @param iseed The value used to initialise the RNG when generating the item - * @param idx The overarching type of the target item - * @param ci Flags used to describe the specific subtype of the target item - * @return An index into ActiveItems or -1 if no matching item was found - */ -int FindGetItem(uint32_t iseed, _item_indexes idx, uint16_t ci); -void SyncGetItem(Point position, uint32_t iseed, _item_indexes idx, uint16_t ci); - -/** - * @brief Checks if the tile has room for an item - * @param position tile coordinates - * @return True if the space is free of obstructions, false if blocked - */ -bool CanPut(Point position); - -int ClampDurability(const Item &item, int durability); -int16_t ClampToHit(const Item &item, int16_t toHit); -uint8_t ClampMaxDam(const Item &item, uint8_t maxDam); -int SyncDropItem(Point position, _item_indexes idx, uint16_t icreateinfo, int iseed, int id, int dur, int mdur, int ch, int mch, int ivalue, uint32_t ibuff, int toHit, int maxDam); -int SyncDropEar(Point position, uint16_t icreateinfo, uint32_t iseed, uint8_t cursval, std::string_view heroname); -int8_t CheckInvHLight(); -bool CanUseScroll(Player &player, SpellID spell); -void ConsumeStaffCharge(Player &player); -bool CanUseStaff(Player &player, SpellID spellId); -Item &GetInventoryItem(Player &player, int location); -bool UseInvItem(int cii); -void DoTelekinesis(); -int CalculateGold(Player &player); - -/** - * @brief Gets the size, in inventory cells, of the given item. - * @param item The item whose size is to be determined. - * @return The size, in inventory cells, of the item. - */ -Size GetInventorySize(const Item &item); - -/** - * @brief Checks whether the player has an inventory item matching the predicate. - */ -template -bool HasInventoryItem(const Player &player, Predicate &&predicate) -{ - const InventoryPlayerItemsRange items { player }; - return c_find_if(items, std::forward(predicate)) != items.end(); -} - -/** - * @brief Checks whether the player has a belt item matching the predicate. - */ -template -bool HasBeltItem(const Player &player, Predicate &&predicate) -{ - const BeltPlayerItemsRange items { player }; - return c_find_if(items, std::forward(predicate)) != items.end(); -} - -/** - * @brief Checks whether the player has an inventory or a belt item matching the predicate. - */ -template -bool HasInventoryOrBeltItem(const Player &player, Predicate &&predicate) -{ - return HasInventoryItem(player, predicate) || HasBeltItem(player, predicate); -} - -/** - * @brief Checks whether the player has an inventory item with the given ID (IDidx). - */ -inline bool HasInventoryItemWithId(const Player &player, _item_indexes id) -{ - return HasInventoryItem(player, [id](const Item &item) { - return item.IDidx == id; - }); -} - -/** - * @brief Checks whether the player has a belt item with the given ID (IDidx). - */ -inline bool HasBeltItemWithId(const Player &player, _item_indexes id) -{ - return HasBeltItem(player, [id](const Item &item) { - return item.IDidx == id; - }); -} - -/** - * @brief Checks whether the player has an inventory or a belt item with the given ID (IDidx). - */ -inline bool HasInventoryOrBeltItemWithId(const Player &player, _item_indexes id) -{ - return HasInventoryItemWithId(player, id) || HasBeltItemWithId(player, id); -} - -/** - * @brief Removes the first inventory item matching the predicate. - * - * @return Whether an item was found and removed. - */ -template -bool RemoveInventoryItem(Player &player, Predicate &&predicate) -{ - const InventoryPlayerItemsRange items { player }; - const auto it = c_find_if(items, std::forward(predicate)); - if (it == items.end()) - return false; - player.RemoveInvItem(static_cast(it.index())); - return true; -} - -/** - * @brief Removes the first belt item matching the predicate. - * - * @return Whether an item was found and removed. - */ -template -bool RemoveBeltItem(Player &player, Predicate &&predicate) -{ - const BeltPlayerItemsRange items { player }; - const auto it = c_find_if(items, std::forward(predicate)); - if (it == items.end()) - return false; - player.RemoveSpdBarItem(static_cast(it.index())); - return true; -} - -/** - * @brief Removes the first inventory or belt item matching the predicate. - * - * @return Whether an item was found and removed. - */ -template -bool RemoveInventoryOrBeltItem(Player &player, Predicate &&predicate) -{ - return RemoveInventoryItem(player, predicate) || RemoveBeltItem(player, predicate); -} - -/** - * @brief Removes the first inventory item with the given id (IDidx). - * - * @return Whether an item was found and removed. - */ -inline bool RemoveInventoryItemById(Player &player, _item_indexes id) -{ - return RemoveInventoryItem(player, [id](const Item &item) { - return item.IDidx == id; - }); -} - -/** - * @brief Removes the first belt item with the given id (IDidx). - * - * @return Whether an item was found and removed. - */ -inline bool RemoveBeltItemById(Player &player, _item_indexes id) -{ - return RemoveBeltItem(player, [id](const Item &item) { - return item.IDidx == id; - }); -} - -/** - * @brief Removes the first inventory or belt item with the given id (IDidx). - * - * @return Whether an item was found and removed. - */ -inline bool RemoveInventoryOrBeltItemById(Player &player, _item_indexes id) -{ - return RemoveInventoryItemById(player, id) || RemoveBeltItemById(player, id); -} - -/** - * @brief Removes the first inventory or belt scroll with the player's current spell. - */ -void ConsumeScroll(Player &player); - -/* data */ - -} // namespace devilution + */ +bool AutoPlaceItemInInventory(Player &player, const Item &item, bool sendNetworkMessage = false, InventoryInsertSemantics semantics = InventoryInsertSemantics::PlayerAction); + +/** + * @brief Checks whether the given item can be placed on the specified player's belt. Returns 'True' when the item can be placed + * on belt slots and the player has at least one empty slot in his belt. + * If 'persistItem' is 'True', the item is also placed in the belt. + * @param player The player on whose belt will be checked. + * @param item The item to be checked. + * @param persistItem Pass 'True' to actually place the item in the belt. The default is 'False'. + * @param sendNetworkMessage Set to true if you want a network message to be generated if the item is persisted. + * Should only be set if a local player is placing an item in a play session (not when creating a new game) + * @return 'True' in case the item can be placed on the player's belt and 'False' otherwise. + */ +bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem = false, bool sendNetworkMessage = false); + +/** + * @brief Sort player inventory. + */ +void ReorganizeInventory(Player &player); + +/** + * @brief Calculate the maximum additional gold that may fit in the user's inventory + */ +int RoomForGold(); + +/** + * @return The leftover amount that didn't fit, if any + */ +int AddGoldToInventory(Player &player, int value); +bool GoldAutoPlace(Player &player, Item &goldStack); +void CheckInvSwap(Player &player, inv_body_loc bLoc); +void inv_update_rem_item(Player &player, inv_body_loc iv); +void CheckInvSwap(Player &player, const Item &item, int invGridIndex); +void CheckInvRemove(Player &player, int invGridIndex); +void TransferItemToStash(Player &player, int location); +void CheckInvItem(bool isShiftHeld = false, bool isCtrlHeld = false); + +/** + * Check for interactions with belt + */ +void CheckInvScrn(bool isShiftHeld, bool isCtrlHeld); +void InvGetItem(Player &player, int ii); + +/** + * @brief Returns the first free space that can take an item preferencing tiles in front of the current position + * + * The search starts with the adjacent tile in the desired direction and alternates sides until it ends up checking the + * opposite tile, before finally checking the origin tile + * + * @param origin center tile of the search space + * @param facing direction of the adjacent tile to check first + * @return the first valid point or an empty optional + */ +std::optional FindAdjacentPositionForItem(Point origin, Direction facing); +void AutoGetItem(Player &player, Item *itemPointer, int ii); + +/** + * @brief Searches for a dropped item with the same type/createInfo/seed + * @param iseed The value used to initialise the RNG when generating the item + * @param idx The overarching type of the target item + * @param ci Flags used to describe the specific subtype of the target item + * @return An index into ActiveItems or -1 if no matching item was found + */ +int FindGetItem(uint32_t iseed, _item_indexes idx, uint16_t ci); +void SyncGetItem(Point position, uint32_t iseed, _item_indexes idx, uint16_t ci); + +/** + * @brief Checks if the tile has room for an item + * @param position tile coordinates + * @return True if the space is free of obstructions, false if blocked + */ +bool CanPut(Point position); + +int ClampDurability(const Item &item, int durability); +int16_t ClampToHit(const Item &item, int16_t toHit); +uint8_t ClampMaxDam(const Item &item, uint8_t maxDam); +int SyncDropItem(Point position, _item_indexes idx, uint16_t icreateinfo, int iseed, int id, int dur, int mdur, int ch, int mch, int ivalue, uint32_t ibuff, int toHit, int maxDam); +int SyncDropEar(Point position, uint16_t icreateinfo, uint32_t iseed, uint8_t cursval, std::string_view heroname); +int8_t CheckInvHLight(); +bool CanUseScroll(Player &player, SpellID spell); +void ConsumeStaffCharge(Player &player); +bool CanUseStaff(Player &player, SpellID spellId); +Item &GetInventoryItem(Player &player, int location); +bool UseInvItem(int cii); +void DoTelekinesis(); +int CalculateGold(Player &player); + +/** + * @brief Gets the size, in inventory cells, of the given item. + * @param item The item whose size is to be determined. + * @return The size, in inventory cells, of the item. + */ +Size GetInventorySize(const Item &item); + +/** + * @brief Checks whether the player has an inventory item matching the predicate. + */ +template +bool HasInventoryItem(const Player &player, Predicate &&predicate) +{ + const InventoryPlayerItemsRange items { player }; + return c_find_if(items, std::forward(predicate)) != items.end(); +} + +/** + * @brief Checks whether the player has a belt item matching the predicate. + */ +template +bool HasBeltItem(const Player &player, Predicate &&predicate) +{ + const BeltPlayerItemsRange items { player }; + return c_find_if(items, std::forward(predicate)) != items.end(); +} + +/** + * @brief Checks whether the player has an inventory or a belt item matching the predicate. + */ +template +bool HasInventoryOrBeltItem(const Player &player, Predicate &&predicate) +{ + return HasInventoryItem(player, predicate) || HasBeltItem(player, predicate); +} + +/** + * @brief Checks whether the player has an inventory item with the given ID (IDidx). + */ +inline bool HasInventoryItemWithId(const Player &player, _item_indexes id) +{ + return HasInventoryItem(player, [id](const Item &item) { + return item.IDidx == id; + }); +} + +/** + * @brief Checks whether the player has a belt item with the given ID (IDidx). + */ +inline bool HasBeltItemWithId(const Player &player, _item_indexes id) +{ + return HasBeltItem(player, [id](const Item &item) { + return item.IDidx == id; + }); +} + +/** + * @brief Checks whether the player has an inventory or a belt item with the given ID (IDidx). + */ +inline bool HasInventoryOrBeltItemWithId(const Player &player, _item_indexes id) +{ + return HasInventoryItemWithId(player, id) || HasBeltItemWithId(player, id); +} + +/** + * @brief Removes the first inventory item matching the predicate. + * + * @return Whether an item was found and removed. + */ +template +bool RemoveInventoryItem(Player &player, Predicate &&predicate) +{ + const InventoryPlayerItemsRange items { player }; + const auto it = c_find_if(items, std::forward(predicate)); + if (it == items.end()) + return false; + player.RemoveInvItem(static_cast(it.index())); + return true; +} + +/** + * @brief Removes the first belt item matching the predicate. + * + * @return Whether an item was found and removed. + */ +template +bool RemoveBeltItem(Player &player, Predicate &&predicate) +{ + const BeltPlayerItemsRange items { player }; + const auto it = c_find_if(items, std::forward(predicate)); + if (it == items.end()) + return false; + player.RemoveSpdBarItem(static_cast(it.index())); + return true; +} + +/** + * @brief Removes the first inventory or belt item matching the predicate. + * + * @return Whether an item was found and removed. + */ +template +bool RemoveInventoryOrBeltItem(Player &player, Predicate &&predicate) +{ + return RemoveInventoryItem(player, predicate) || RemoveBeltItem(player, predicate); +} + +/** + * @brief Removes the first inventory item with the given id (IDidx). + * + * @return Whether an item was found and removed. + */ +inline bool RemoveInventoryItemById(Player &player, _item_indexes id) +{ + return RemoveInventoryItem(player, [id](const Item &item) { + return item.IDidx == id; + }); +} + +/** + * @brief Removes the first belt item with the given id (IDidx). + * + * @return Whether an item was found and removed. + */ +inline bool RemoveBeltItemById(Player &player, _item_indexes id) +{ + return RemoveBeltItem(player, [id](const Item &item) { + return item.IDidx == id; + }); +} + +/** + * @brief Removes the first inventory or belt item with the given id (IDidx). + * + * @return Whether an item was found and removed. + */ +inline bool RemoveInventoryOrBeltItemById(Player &player, _item_indexes id) +{ + return RemoveInventoryItemById(player, id) || RemoveBeltItemById(player, id); +} + +/** + * @brief Removes the first inventory or belt scroll with the player's current spell. + */ +void ConsumeScroll(Player &player); + +/* data */ + +} // namespace devilution