#pragma once // Items and the inventory grid (cInventoryEntry, docs/skill-slots.md). #include #include #include "game/engine/objects.h" namespace game { // Item objects (cItemBase and subclasses such as cPotion3D). constexpr size_t kItemType = 0x10; constexpr uint32_t kObjectUsedUp = 0x8000000; // kObjectFlags: set by cPotion3D::vf18 when drunk // The item type table at *0xaa9564 (ecx of ItemPotionKind). constexpr uint32_t kItemTypeTable = 0x00aa9564; constexpr uint32_t kItemTypeCount = 0x7e60; // types 1..0x7e5f (bounds check in 0x428700) // ItemPotionKind (0x43f630): the potion kind (1..5, the bottom bar's potion // slots; 1 = healing) of item type `type`, 0 if it is no potion. constexpr int kPotionKinds = 5; inline int ItemPotionKind(uint32_t type) { using Fn = int(__fastcall*)(void* table, void* edx, uint32_t type); void* table = *reinterpret_cast(kItemTypeTable); return table ? reinterpret_cast(0x0043f630)(table, nullptr, type) : 0; } // The item class of item type `type` (type table entry: 0x80 bytes, class u8 // at +0x2e; read by ItemPotionKind). Usable from the inventory // (cCreature::UseItem 0x54a8f0): 9 (potions), 0x10, 0x1c (runes). constexpr size_t kItemTypeEntrySize = 0x80; constexpr size_t kItemTypeClass = 0x2e; inline uint8_t ItemClass(uint32_t type) { const auto* table = *reinterpret_cast(kItemTypeTable); return table ? table[type * kItemTypeEntrySize + kItemTypeClass] : 0; } // Item classes (type table +0x2e), measured: 5 weapons / armour, 7, 9 potions, // 0x1c combat-art runes (1x1). constexpr uint8_t kItemClassPotion = 9; constexpr uint8_t kItemClassRune = 0x1c; // 0x43f5f0 (stdcall, ret 4): the item type is a potion or a rune (class 9 or // 0x1c). Items of one such type are alike apart from their object state // (measured: two runes of one type serialize the same past the object part). inline bool ItemIsConsumable(uint32_t type) { return reinterpret_cast(0x0043f5f0)(type); } // 0x428700 (thiscall on the type table, (type), ret 4): the text id of the // item type's name (entry +0x20), which cItemBase::GetName (0x44a420) shows // unless the object has its own (+0x3c). inline uint32_t ItemNameText(uint32_t type) { using Fn = uint32_t(__fastcall*)(void* table, void* edx, uint32_t type); void* table = *reinterpret_cast(kItemTypeTable); return table ? reinterpret_cast(0x00428700)(table, nullptr, type) : 0; } inline uint32_t ItemType(const void* item) { return *reinterpret_cast(static_cast(item) + kItemType); } // The potion kind of object `id`, 0 if it is no potion (or gone). inline int PotionKindOf(uint32_t id) { const void* item = id ? GetObjectById(id) : nullptr; return item ? ItemPotionKind(ItemType(item)) : 0; } // cInventoryEntry item grid: cells of {u32 item id, u8 flags, u8, u8, u8, // u16 type, u16}. An item's id is in its first cell only; the other cells it // covers have id 0, flags bit 0 and the first cell's x, y at +5 / +6 // (cInventoryEntry::GetItemAt 0x5da290 resolves them). constexpr size_t kInventoryGrid = 0x18; constexpr size_t kGridCellSize = 0xc; constexpr size_t kGridWidth = 0x115c; // u16, 0x14 constexpr size_t kGridHeight = 0x115e; // u16, 8 constexpr size_t kInventoryOwner = 0x1160; // u16: = the inventory's index (measured) // Inventories below 0x12 belong to the players (index = hero id) and the // chest (0x11, saved with the hero as chunk 0xc6); PlaceItem / RemoveAt only // update the hero below that. Traders use higher ones (22 measured). constexpr uint16_t kPlayerInventoryEnd = 0x12; constexpr uint16_t kChestInventory = 0x11; inline bool IsPlayerInventory(const uint8_t* inventory) { return *reinterpret_cast(inventory + kInventoryOwner) < kPlayerInventoryEnd; } inline unsigned GridCells(const uint8_t* inventory) { return *reinterpret_cast(inventory + kGridWidth) * *reinterpret_cast(inventory + kGridHeight); } inline uint32_t GridItem(const uint8_t* inventory, unsigned cell) { return *reinterpret_cast(inventory + kInventoryGrid + cell * kGridCellSize); } // Calls `fn(id)` for each item in `inventory`'s grid. template void ForEachGridItem(const uint8_t* inventory, Fn fn) { const unsigned cells = GridCells(inventory); for (unsigned cell = 0; cell < cells; ++cell) { if (const uint32_t id = GridItem(inventory, cell)) { fn(id); } } } } // namespace game #include namespace game { // Item objects as hero files store them (ItemDataMgr::saveHero / loadHero, // docs/shared-chest.md): the type plus the object's own serializer // vf1c(stream, write, version), ret 0xc. constexpr uint32_t kItemFileVersion = 0x1b; // the hero file version handed to the item readers constexpr size_t kItemOwner = 0x39; // u8: the owner hero's id std::vector SerializeItem(void* item); // A new item object of `type` read from `data`, as loadHero recreates one // (cObjectManager::Create, the serializer, its new id, flags, the owner). // Returns its id, 0 on failure. uint32_t CreateItem(uint32_t type, const std::vector& data, uint32_t ownerHeroId); // A copy of item `id` as a new object (same type and data, same owner). uint32_t CloneItem(uint32_t id); } // namespace game