/** * @file items.h * * Interface of item functionality. */ #pragma once #include #include #include "DiabloUI/ui_flags.hpp" #include "cursor.h" #include "engine.h" #include "engine/animationinfo.h" #include "engine/point.hpp" #include "itemdat.h" #include "monster.h" #include "utils/string_or_view.hpp" namespace devilution { #define MAXITEMS 127 #define ITEMTYPES 43 #define GOLD_SMALL_LIMIT 1000 #define GOLD_MEDIUM_LIMIT 2500 #define GOLD_MAX_LIMIT 5000 // Item indestructible durability #define DUR_INDESTRUCTIBLE 255 constexpr int MaxVendorValue = 140000; constexpr int MaxVendorValueHf = 200000; constexpr int MaxBoyValue = 90000; constexpr int MaxBoyValueHf = 200000; enum item_quality : uint8_t { ITEM_QUALITY_NORMAL, ITEM_QUALITY_MAGIC, ITEM_QUALITY_UNIQUE, }; enum _unique_items : int8_t { UITEM_CLEAVER, UITEM_SKCROWN, UITEM_INFRARING, UITEM_OPTAMULET, UITEM_TRING, UITEM_HARCREST, UITEM_STEELVEIL, UITEM_ARMOFVAL, UITEM_GRISWOLD, UITEM_BOVINE, UITEM_RIFTBOW, UITEM_NEEDLER, UITEM_CELESTBOW, UITEM_DEADLYHUNT, UITEM_BOWOFDEAD, UITEM_BLKOAKBOW, UITEM_FLAMEDART, UITEM_FLESHSTING, UITEM_WINDFORCE, UITEM_EAGLEHORN, UITEM_GONNAGALDIRK, UITEM_DEFENDER, UITEM_GRYPHONCLAW, UITEM_BLACKRAZOR, UITEM_GIBBOUSMOON, UITEM_ICESHANK, UITEM_EXECUTIONER, UITEM_BONESAW, UITEM_SHADHAWK, UITEM_WIZSPIKE, UITEM_LGTSABRE, UITEM_FALCONTALON, UITEM_INFERNO, UITEM_DOOMBRINGER, UITEM_GRIZZLY, UITEM_GRANDFATHER, UITEM_MANGLER, UITEM_SHARPBEAK, UITEM_BLOODLSLAYER, UITEM_CELESTAXE, UITEM_WICKEDAXE, UITEM_STONECLEAV, UITEM_AGUHATCHET, UITEM_HELLSLAYER, UITEM_MESSERREAVER, UITEM_CRACKRUST, UITEM_JHOLMHAMM, UITEM_CIVERBS, UITEM_CELESTSTAR, UITEM_BARANSTAR, UITEM_GNARLROOT, UITEM_CRANBASH, UITEM_SCHAEFHAMM, UITEM_DREAMFLANGE, UITEM_STAFFOFSHAD, UITEM_IMMOLATOR, UITEM_STORMSPIRE, UITEM_GLEAMSONG, UITEM_THUNDERCALL, UITEM_PROTECTOR, UITEM_NAJPUZZLE, UITEM_MINDCRY, UITEM_RODOFONAN, UITEM_SPIRITSHELM, UITEM_THINKINGCAP, UITEM_OVERLORDHELM, UITEM_FOOLSCREST, UITEM_GOTTERDAM, UITEM_ROYCIRCLET, UITEM_TORNFLESH, UITEM_GLADBANE, UITEM_RAINCLOAK, UITEM_LEATHAUT, UITEM_WISDWRAP, UITEM_SPARKMAIL, UITEM_SCAVCARAP, UITEM_NIGHTSCAPE, UITEM_NAJPLATE, UITEM_DEMONSPIKE, UITEM_DEFLECTOR, UITEM_SKULLSHLD, UITEM_DRAGONBRCH, UITEM_BLKOAKSHLD, UITEM_HOLYDEF, UITEM_STORMSHLD, UITEM_BRAMBLE, UITEM_REGHA, UITEM_BLEEDER, UITEM_CONSTRICT, UITEM_ENGAGE, UITEM_INVALID = -1, }; /* CF_LEVEL: Item Level (6 bits; value ranges from 0-63) CF_ONLYGOOD: Item is not able to have affixes with PLOK set to false CF_UPER15: Item is from a Unique Monster and has 15% chance of being a Unique Item CF_UPER1: Item is from the dungeon and has a 1% chance of being a Unique Item CF_UNIQUE: Item is a Unique Item CF_SMITH: Item is from Griswold (Basic) CF_SMITHPREMIUM: Item is from Griswold (Premium) CF_BOY: Item is from Wirt CF_WITCH: Item is from Adria CF_HEALER: Item is from Pepin CF_PREGEN: Item is pre-generated, mostly associated with Quest items found in the dungeon or potions on the dungeon floor Items that have both CF_UPER15 and CF_UPER1 are CF_USEFUL, which is used to generate Potions and Town Portal scrolls on the dungeon floor Items that have any of CF_SMITH, CF_SMITHPREMIUM, CF_BOY, CF_WICTH, and CF_HEALER are CF_TOWN, indicating the item is sourced from an NPC */ enum icreateinfo_flag { // clang-format off CF_LEVEL = (1 << 6) - 1, CF_ONLYGOOD = 1 << 6, CF_UPER15 = 1 << 7, CF_UPER1 = 1 << 8, CF_UNIQUE = 1 << 9, CF_SMITH = 1 << 10, CF_SMITHPREMIUM = 1 << 11, CF_BOY = 1 << 12, CF_WITCH = 1 << 13, CF_HEALER = 1 << 14, CF_PREGEN = 1 << 15, CF_USEFUL = CF_UPER15 | CF_UPER1, CF_TOWN = CF_SMITH | CF_SMITHPREMIUM | CF_BOY | CF_WITCH | CF_HEALER, // clang-format on }; enum icreateinfo_flag2 { // clang-format off CF_HELLFIRE = 1 << 0, CF_UIDOFFSET = ((1 << 4) - 1) << 1, // clang-format on }; // All item animation frames have this width. constexpr int ItemAnimWidth = 96; // Defined in player.h, forward declared here to allow for functions which operate in the context of a player. struct Player; struct Item { /** Randomly generated identifier */ uint32_t _iSeed = 0; uint16_t _iCreateInfo = 0; ItemType _itype = ItemType::None; bool _iAnimFlag = false; Point position = { 0, 0 }; /* * @brief Contains Information for current Animation */ AnimationInfo AnimInfo; bool _iDelFlag = false; // set when item is flagged for deletion, deprecated in 1.02 SelectionRegion selectionRegion = SelectionRegion::None; bool _iPostDraw = false; bool _iIdentified = false; item_quality _iMagical = ITEM_QUALITY_NORMAL; char _iName[64] = {}; char _iIName[64] = {}; item_equip_type _iLoc = ILOC_NONE; item_class _iClass = ICLASS_NONE; uint8_t _iCurs = 0; int _ivalue = 0; int _iIvalue = 0; uint8_t _iMinDam = 0; uint8_t _iMaxDam = 0; int16_t _iAC = 0; ItemSpecialEffect _iFlags = ItemSpecialEffect::None; item_misc_id _iMiscId = IMISC_NONE; SpellID _iSpell = SpellID::Null; _item_indexes IDidx = IDI_NONE; int _iCharges = 0; int _iMaxCharges = 0; int _iDurability = 0; int _iMaxDur = 0; int16_t _iPLDam = 0; int16_t _iPLToHit = 0; int16_t _iPLAC = 0; int16_t _iPLStr = 0; int16_t _iPLMag = 0; int16_t _iPLDex = 0; int16_t _iPLVit = 0; int16_t _iPLFR = 0; int16_t _iPLLR = 0; int16_t _iPLMR = 0; int16_t _iPLMana = 0; int16_t _iPLHP = 0; int16_t _iPLDamMod = 0; int16_t _iPLGetHit = 0; int16_t _iPLLight = 0; int8_t _iSplLvlAdd = 0; bool _iRequest = false; /** Unique item ID, used as an index into UniqueItemList */ int _iUid = 0; int16_t _iFMinDam = 0; int16_t _iFMaxDam = 0; int16_t _iLMinDam = 0; int16_t _iLMaxDam = 0; int16_t _iPLEnAc = 0; enum item_effect_type _iPrePower = IPL_INVALID; enum item_effect_type _iSufPower = IPL_INVALID; int _iVAdd1 = 0; int _iVMult1 = 0; int _iVAdd2 = 0; int _iVMult2 = 0; int8_t _iMinStr = 0; uint8_t _iMinMag = 0; int8_t _iMinDex = 0; bool _iStatFlag = false; ItemSpecialEffectHf _iDamAcFlags = ItemSpecialEffectHf::None; uint32_t dwBuff = 0; /** * @brief Clears this item and returns the old value */ Item pop() & { Item temp = std::move(*this); clear(); return temp; } /** * @brief Resets the item so isEmpty() returns true without needing to reinitialise the whole object */ DVL_REINITIALIZES void clear() { this->_itype = ItemType::None; } /** * @brief Checks whether this item is empty or not. * @return 'True' in case the item is empty and 'False' otherwise. */ bool isEmpty() const { return this->_itype == ItemType::None; } /** * @brief Checks whether this item is an equipment. * @return 'True' in case the item is an equipment and 'False' otherwise. */ bool isEquipment() const { if (this->isEmpty()) { return false; } switch (this->_iLoc) { case ILOC_AMULET: case ILOC_ARMOR: case ILOC_HELM: case ILOC_ONEHAND: case ILOC_RING: case ILOC_TWOHAND: return true; default: return false; } } /** * @brief Checks whether this item is a weapon. * @return 'True' in case the item is a weapon and 'False' otherwise. */ bool isWeapon() const { switch (this->_itype) { case ItemType::Axe: case ItemType::Bow: case ItemType::Mace: case ItemType::Staff: case ItemType::Sword: return true; default: return false; } } /** * @brief Checks whether this item is an armor. * @return 'True' in case the item is an armor and 'False' otherwise. */ bool isArmor() const { switch (this->_itype) { case ItemType::HeavyArmor: case ItemType::LightArmor: case ItemType::MediumArmor: return true; default: return false; } } /** * @brief Checks whether this item is gold. * @return 'True' in case the item is gold and 'False' otherwise. */ bool isGold() const { return this->_itype == ItemType::Gold; } /** * @brief Checks whether this item is a helm. * @return 'True' in case the item is a helm and 'False' otherwise. */ bool isHelm() const { return this->_itype == ItemType::Helm; } /** * @brief Checks whether this item is a shield. * @return 'True' in case the item is a shield and 'False' otherwise. */ bool isShield() const { return this->_itype == ItemType::Shield; } /** * @brief Checks whether this item is a jewelry. * @return 'True' in case the item is a jewelry and 'False' otherwise. */ bool isJewelry() const { switch (this->_itype) { case ItemType::Amulet: case ItemType::Ring: return true; default: return false; } } [[nodiscard]] bool isScroll() const { return IsAnyOf(_iMiscId, IMISC_SCROLL, IMISC_SCROLLT); } [[nodiscard]] bool isScrollOf(SpellID spellId) const { return isScroll() && _iSpell == spellId; } [[nodiscard]] bool isRune() const { return _iMiscId > IMISC_RUNEFIRST && _iMiscId < IMISC_RUNELAST; } [[nodiscard]] bool isRuneOf(SpellID spellId) const { if (!isRune()) return false; switch (_iMiscId) { case IMISC_RUNEF: return spellId == SpellID::RuneOfFire; case IMISC_RUNEL: return spellId == SpellID::RuneOfLight; case IMISC_GR_RUNEL: return spellId == SpellID::RuneOfNova; case IMISC_GR_RUNEF: return spellId == SpellID::RuneOfImmolation; case IMISC_RUNES: return spellId == SpellID::RuneOfStone; default: return false; } } [[nodiscard]] bool isUsable() const; [[nodiscard]] bool keyAttributesMatch(uint32_t seed, _item_indexes itemIndex, uint16_t createInfo) const { return _iSeed == seed && IDidx == itemIndex && _iCreateInfo == createInfo; } UiFlags getTextColor() const { switch (_iMagical) { case ITEM_QUALITY_MAGIC: return UiFlags::ColorBlue; case ITEM_QUALITY_UNIQUE: return UiFlags::ColorWhitegold; default: return UiFlags::ColorWhite; } } UiFlags getTextColorWithStatCheck() const { if (!_iStatFlag) return UiFlags::ColorRed; return getTextColor(); } /** * @brief Sets the current Animation for the Item * @param showAnimation Definies if the Animation (Flipping) is shown or if only the final Frame (item on the ground) is shown */ void setNewAnimation(bool showAnimation); /** * @brief If this item is a spell book, calculates the magic requirement to learn a new level, then for all items sets _iStatFlag * @param player Player to compare stats against requirements */ void updateRequiredStatsCacheForPlayer(const Player &player); /** @brief Returns the translated item name to display (respects identified flag) */ StringOrView getName() const; [[nodiscard]] Displacement getRenderingOffset(const ClxSprite sprite) const { return { -CalculateWidth2(sprite.width()), 0 }; } }; struct ItemGetRecordStruct { uint32_t nSeed; uint16_t wCI; int nIndex; uint32_t dwTimestamp; }; struct CornerStoneStruct { Point position; bool activated; Item item; bool isAvailable(); }; /** Contains the items on ground in the current game. */ extern Item Items[MAXITEMS + 1]; extern uint8_t ActiveItems[MAXITEMS]; extern uint8_t ActiveItemCount; /** Contains the location of dropped items. */ extern int8_t dItem[MAXDUNX][MAXDUNY]; extern bool ShowUniqueItemInfoBox; extern CornerStoneStruct CornerStone; extern DVL_API_FOR_TEST bool UniqueItemFlags[128]; uint8_t GetOutlineColor(const Item &item, bool checkReq); bool IsItemAvailable(int i); bool IsUniqueAvailable(int i); void ClearUniqueItemFlags(); void InitItemGFX(); void InitItems(); void CalcPlrItemVals(Player &player, bool Loadgfx); void CalcPlrInv(Player &player, bool Loadgfx); void InitializeItem(Item &item, _item_indexes itemData); void GenerateNewSeed(Item &h); int GetGoldCursor(int value); /** * @brief Update the gold cursor on the given gold item * @param gold The item to update */ void SetPlrHandGoldCurs(Item &gold); void CreatePlrItems(Player &player); bool ItemSpaceOk(Point position); int AllocateItem(); /** * @brief Moves the item onto the floor of the current dungeon level * @param item The source of the item data, should not be used after calling this function * @param position Coordinates of the tile to place the item on * @return The index assigned to the item */ uint8_t PlaceItemInWorld(Item &&item, WorldTilePosition position); Point GetSuperItemLoc(Point position); void GetItemAttrs(Item &item, _item_indexes itemData, int lvl); void SetupItem(Item &item); Item *SpawnUnique(_unique_items uid, Point position, std::optional level = std::nullopt, bool sendmsg = true, bool exactPosition = false); void GetSuperItemSpace(Point position, int8_t inum); _item_indexes RndItemForMonsterLevel(int8_t monsterLevel); void SetupAllItems(const Player &player, Item &item, _item_indexes idx, uint32_t iseed, int lvl, int uper, bool onlygood, bool pregen, int uidOffset = 0, bool forceNotUnique = false); void TryRandomUniqueItem(Item &item, _item_indexes idx, int8_t mLevel, int uper, bool onlygood, bool pregen); void SpawnItem(Monster &monster, Point position, bool sendmsg, bool spawn = false); void CreateRndItem(Point position, bool onlygood, bool sendmsg, bool delta); void CreateRndUseful(Point position, bool sendmsg); void CreateTypeItem(Point position, bool onlygood, ItemType itemType, int imisc, bool sendmsg, bool delta, bool spawn = false); void RecreateItem(const Player &player, Item &item, _item_indexes idx, uint16_t icreateinfo, uint32_t iseed, int ivalue, bool isHellfire); void RecreateEar(Item &item, uint16_t ic, uint32_t iseed, uint8_t bCursval, std::string_view heroName); void CornerstoneSave(); void CornerstoneLoad(Point position); void SpawnQuestItem(_item_indexes itemid, Point position, int randarea, SelectionRegion selectionRegion, bool sendmsg); void SpawnRewardItem(_item_indexes itemid, Point position, bool sendmsg); void SpawnMapOfDoom(Point position, bool sendmsg); void SpawnRuneBomb(Point position, bool sendmsg); void SpawnTheodore(Point position, bool sendmsg); void RespawnItem(Item &item, bool FlipFlag); void DeleteItem(int i); void ProcessItems(); void FreeItemGFX(); void GetItemFrm(Item &item); void GetItemStr(Item &item); void CheckIdentify(Player &player, int cii); void DoRepair(Player &player, int cii); void DoRecharge(Player &player, int cii); bool DoOil(Player &player, int cii); [[nodiscard]] StringOrView PrintItemPower(char plidx, const Item &item); void DrawUniqueInfo(const Surface &out); void PrintItemDetails(const Item &item); void PrintItemDur(const Item &item); void UseItem(Player &player, item_misc_id Mid, SpellID spellID, int spellFrom); bool UseItemOpensHive(const Item &item, Point position); bool UseItemOpensGrave(const Item &item, Point position); void SpawnSmith(int lvl); void SpawnPremium(const Player &player); void SpawnWitch(int lvl); void SpawnBoy(int lvl); void SpawnHealer(int lvl); void MakeGoldStack(Item &goldItem, int value); int ItemNoFlippy(); void CreateSpellBook(Point position, SpellID ispell, bool sendmsg, bool delta); void CreateMagicArmor(Point position, ItemType itemType, int icurs, bool sendmsg, bool delta); void CreateAmulet(Point position, int lvl, bool sendmsg, bool delta, bool spawn = false); void CreateMagicWeapon(Point position, ItemType itemType, int icurs, bool sendmsg, bool delta); bool GetItemRecord(uint32_t nSeed, uint16_t wCI, int nIndex); void SetItemRecord(uint32_t nSeed, uint16_t wCI, int nIndex); void PutItemRecord(uint32_t nSeed, uint16_t wCI, int nIndex); /** * @brief Resets item get records. */ void initItemGetRecords(); void RepairItem(Item &item, int lvl); void RechargeItem(Item &item, Player &player); bool ApplyOilToItem(Item &item, Player &player); /** * @brief Checks if the item is generated in vanilla hellfire. If yes it updates dwBuff to include CF_HELLFIRE. */ void UpdateHellfireFlag(Item &item, const char *identifiedItemName); /* data */ extern int MaxGold; extern int8_t ItemCAnimTbl[]; extern SfxID ItemInvSnds[]; } // namespace devilution