mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: display permanent and temporary enchants in item tooltips for equipped items
Tracks ITEM_ENCHANTMENT_SLOT 0 (permanent) and 1 (temporary) from item update fields in OnlineItemInfo, then looks up names from SpellItemEnchantment.dbc and renders them in both ItemDef and ItemQueryResponseData tooltip variants.
This commit is contained in:
parent
4025e6576c
commit
1fd3d5fdc8
4 changed files with 118 additions and 8 deletions
|
|
@ -2125,6 +2125,16 @@ public:
|
|||
if (index < 0 || index >= static_cast<int>(backpackSlotGuids_.size())) return 0;
|
||||
return backpackSlotGuids_[index];
|
||||
}
|
||||
uint64_t getEquipSlotGuid(int slot) const {
|
||||
if (slot < 0 || slot >= static_cast<int>(equipSlotGuids_.size())) return 0;
|
||||
return equipSlotGuids_[slot];
|
||||
}
|
||||
// Returns the permanent and temporary enchant IDs for an item by GUID (0 if unknown).
|
||||
std::pair<uint32_t, uint32_t> getItemEnchantIds(uint64_t guid) const {
|
||||
auto it = onlineItems_.find(guid);
|
||||
if (it == onlineItems_.end()) return {0, 0};
|
||||
return {it->second.permanentEnchantId, it->second.temporaryEnchantId};
|
||||
}
|
||||
uint64_t getVendorGuid() const { return currentVendorItems.vendorGuid; }
|
||||
|
||||
/**
|
||||
|
|
@ -2621,6 +2631,8 @@ private:
|
|||
uint32_t stackCount = 1;
|
||||
uint32_t curDurability = 0;
|
||||
uint32_t maxDurability = 0;
|
||||
uint32_t permanentEnchantId = 0; // ITEM_ENCHANTMENT_SLOT 0 (enchanting)
|
||||
uint32_t temporaryEnchantId = 0; // ITEM_ENCHANTMENT_SLOT 1 (sharpening stones, poisons)
|
||||
};
|
||||
std::unordered_map<uint64_t, OnlineItemInfo> onlineItems_;
|
||||
std::unordered_map<uint32_t, ItemQueryResponseData> itemInfoCache_;
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ private:
|
|||
std::unordered_map<uint32_t, VkDescriptorSet> iconCache_;
|
||||
public:
|
||||
VkDescriptorSet getItemIcon(uint32_t displayInfoId);
|
||||
void renderItemTooltip(const game::ItemQueryResponseData& info, const game::Inventory* inventory = nullptr);
|
||||
void renderItemTooltip(const game::ItemQueryResponseData& info, const game::Inventory* inventory = nullptr, uint64_t itemGuid = 0);
|
||||
private:
|
||||
|
||||
// Character model preview
|
||||
|
|
@ -161,7 +161,7 @@ private:
|
|||
SlotKind kind, int backpackIndex,
|
||||
game::EquipSlot equipSlot,
|
||||
int bagIndex = -1, int bagSlotIndex = -1);
|
||||
void renderItemTooltip(const game::ItemDef& item, const game::Inventory* inventory = nullptr);
|
||||
void renderItemTooltip(const game::ItemDef& item, const game::Inventory* inventory = nullptr, uint64_t itemGuid = 0);
|
||||
|
||||
// Held item helpers
|
||||
void pickupFromBackpack(game::Inventory& inv, int index);
|
||||
|
|
|
|||
|
|
@ -11677,14 +11677,20 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
auto stackIt = block.fields.find(fieldIndex(UF::ITEM_FIELD_STACK_COUNT));
|
||||
auto durIt = block.fields.find(fieldIndex(UF::ITEM_FIELD_DURABILITY));
|
||||
auto maxDurIt= block.fields.find(fieldIndex(UF::ITEM_FIELD_MAXDURABILITY));
|
||||
const uint16_t enchBase = (fieldIndex(UF::ITEM_FIELD_STACK_COUNT) != 0xFFFF)
|
||||
? static_cast<uint16_t>(fieldIndex(UF::ITEM_FIELD_STACK_COUNT) + 8u) : 0xFFFFu;
|
||||
auto permEnchIt = (enchBase != 0xFFFF) ? block.fields.find(enchBase) : block.fields.end();
|
||||
auto tempEnchIt = (enchBase != 0xFFFF) ? block.fields.find(enchBase + 3u) : block.fields.end();
|
||||
if (entryIt != block.fields.end() && entryIt->second != 0) {
|
||||
// Preserve existing info when doing partial updates
|
||||
OnlineItemInfo info = onlineItems_.count(block.guid)
|
||||
? onlineItems_[block.guid] : OnlineItemInfo{};
|
||||
info.entry = entryIt->second;
|
||||
if (stackIt != block.fields.end()) info.stackCount = stackIt->second;
|
||||
if (durIt != block.fields.end()) info.curDurability = durIt->second;
|
||||
if (maxDurIt!= block.fields.end()) info.maxDurability = maxDurIt->second;
|
||||
if (stackIt != block.fields.end()) info.stackCount = stackIt->second;
|
||||
if (durIt != block.fields.end()) info.curDurability = durIt->second;
|
||||
if (maxDurIt != block.fields.end()) info.maxDurability = maxDurIt->second;
|
||||
if (permEnchIt != block.fields.end()) info.permanentEnchantId = permEnchIt->second;
|
||||
if (tempEnchIt != block.fields.end()) info.temporaryEnchantId = tempEnchIt->second;
|
||||
bool isNew = (onlineItems_.find(block.guid) == onlineItems_.end());
|
||||
onlineItems_[block.guid] = info;
|
||||
if (isNew) newItemCreated = true;
|
||||
|
|
@ -12269,6 +12275,12 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
const uint16_t itemMaxDurField = fieldIndex(UF::ITEM_FIELD_MAXDURABILITY);
|
||||
const uint16_t containerNumSlotsField = fieldIndex(UF::CONTAINER_FIELD_NUM_SLOTS);
|
||||
const uint16_t containerSlot1Field = fieldIndex(UF::CONTAINER_FIELD_SLOT_1);
|
||||
// ITEM_FIELD_ENCHANTMENT starts 8 fields after ITEM_FIELD_STACK_COUNT (fixed offset
|
||||
// across all expansions: +DURATION, +5×SPELL_CHARGES, +FLAGS = +8).
|
||||
// Slot 0 = permanent enchant (field +0), slot 1 = temp enchant (field +3).
|
||||
const uint16_t itemEnchBase = (itemStackField != 0xFFFF) ? (itemStackField + 8u) : 0xFFFF;
|
||||
const uint16_t itemPermEnchField = itemEnchBase;
|
||||
const uint16_t itemTempEnchField = (itemEnchBase != 0xFFFF) ? (itemEnchBase + 3u) : 0xFFFF;
|
||||
|
||||
auto it = onlineItems_.find(block.guid);
|
||||
bool isItemInInventory = (it != onlineItems_.end());
|
||||
|
|
@ -12289,6 +12301,16 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem
|
|||
it->second.maxDurability = val;
|
||||
inventoryChanged = true;
|
||||
}
|
||||
} else if (isItemInInventory && itemPermEnchField != 0xFFFF && key == itemPermEnchField) {
|
||||
if (it->second.permanentEnchantId != val) {
|
||||
it->second.permanentEnchantId = val;
|
||||
inventoryChanged = true;
|
||||
}
|
||||
} else if (isItemInInventory && itemTempEnchField != 0xFFFF && key == itemTempEnchField) {
|
||||
if (it->second.temporaryEnchantId != val) {
|
||||
it->second.temporaryEnchantId = val;
|
||||
inventoryChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update container slot GUIDs on bag content changes
|
||||
|
|
|
|||
|
|
@ -2401,12 +2401,15 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite
|
|||
if (ImGui::IsItemHovered() && !holdingItem) {
|
||||
// Pass inventory for backpack/bag items only; equipped items compare against themselves otherwise
|
||||
const game::Inventory* tooltipInv = (kind == SlotKind::EQUIPMENT) ? nullptr : &inventory;
|
||||
renderItemTooltip(item, tooltipInv);
|
||||
uint64_t slotGuid = 0;
|
||||
if (kind == SlotKind::EQUIPMENT && gameHandler_)
|
||||
slotGuid = gameHandler_->getEquipSlotGuid(static_cast<int>(equipSlot));
|
||||
renderItemTooltip(item, tooltipInv, slotGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::Inventory* inventory) {
|
||||
void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::Inventory* inventory, uint64_t itemGuid) {
|
||||
ImGui::BeginTooltip();
|
||||
|
||||
ImVec4 qColor = getQualityColor(item.quality);
|
||||
|
|
@ -2915,6 +2918,42 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I
|
|||
}
|
||||
}
|
||||
|
||||
// Weapon/armor enchant display for equipped items (reads from item update fields)
|
||||
if (itemGuid != 0 && gameHandler_) {
|
||||
auto [permId, tempId] = gameHandler_->getItemEnchantIds(itemGuid);
|
||||
if (permId != 0 || tempId != 0) {
|
||||
static std::unordered_map<uint32_t, std::string> s_enchNamesB;
|
||||
static bool s_enchNamesLoadedB = false;
|
||||
if (!s_enchNamesLoadedB && assetManager_) {
|
||||
s_enchNamesLoadedB = true;
|
||||
auto dbc = assetManager_->loadDBC("SpellItemEnchantment.dbc");
|
||||
if (dbc && dbc->isLoaded()) {
|
||||
const auto* lay = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("SpellItemEnchantment") : nullptr;
|
||||
uint32_t nf = lay ? lay->field("Name") : 8u;
|
||||
if (nf == 0xFFFFFFFF) nf = 8;
|
||||
uint32_t fc = dbc->getFieldCount();
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); ++r) {
|
||||
uint32_t eid = dbc->getUInt32(r, 0);
|
||||
if (eid == 0 || nf >= fc) continue;
|
||||
std::string en = dbc->getString(r, nf);
|
||||
if (!en.empty()) s_enchNamesB[eid] = std::move(en);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (permId != 0) {
|
||||
auto it2 = s_enchNamesB.find(permId);
|
||||
const char* ename = (it2 != s_enchNamesB.end()) ? it2->second.c_str() : nullptr;
|
||||
if (ename) ImGui::TextColored(ImVec4(0.0f, 0.8f, 1.0f, 1.0f), "Enchanted: %s", ename);
|
||||
}
|
||||
if (tempId != 0) {
|
||||
auto it2 = s_enchNamesB.find(tempId);
|
||||
const char* ename = (it2 != s_enchNamesB.end()) ? it2->second.c_str() : nullptr;
|
||||
if (ename) ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.4f, 1.0f), "%s (temporary)", ename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "Begins a Quest" line (shown in yellow-green like the game)
|
||||
if (item.startQuestId != 0) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.0f, 1.0f), "Begins a Quest");
|
||||
|
|
@ -3067,7 +3106,7 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I
|
|||
// ---------------------------------------------------------------------------
|
||||
// Tooltip overload for ItemQueryResponseData (used by loot window, etc.)
|
||||
// ---------------------------------------------------------------------------
|
||||
void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info, const game::Inventory* inventory) {
|
||||
void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info, const game::Inventory* inventory, uint64_t itemGuid) {
|
||||
ImGui::BeginTooltip();
|
||||
|
||||
ImVec4 qColor = getQualityColor(static_cast<game::ItemQuality>(info.quality));
|
||||
|
|
@ -3442,6 +3481,43 @@ void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info,
|
|||
}
|
||||
}
|
||||
|
||||
// Weapon/armor enchant display for equipped items
|
||||
if (itemGuid != 0 && gameHandler_) {
|
||||
auto [permId, tempId] = gameHandler_->getItemEnchantIds(itemGuid);
|
||||
if (permId != 0 || tempId != 0) {
|
||||
// Lazy-load SpellItemEnchantment.dbc for enchant name lookup
|
||||
static std::unordered_map<uint32_t, std::string> s_enchNames;
|
||||
static bool s_enchNamesLoaded = false;
|
||||
if (!s_enchNamesLoaded && assetManager_) {
|
||||
s_enchNamesLoaded = true;
|
||||
auto dbc = assetManager_->loadDBC("SpellItemEnchantment.dbc");
|
||||
if (dbc && dbc->isLoaded()) {
|
||||
const auto* lay = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("SpellItemEnchantment") : nullptr;
|
||||
uint32_t nf = lay ? lay->field("Name") : 8u;
|
||||
if (nf == 0xFFFFFFFF) nf = 8;
|
||||
uint32_t fc = dbc->getFieldCount();
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); ++r) {
|
||||
uint32_t eid = dbc->getUInt32(r, 0);
|
||||
if (eid == 0 || nf >= fc) continue;
|
||||
std::string en = dbc->getString(r, nf);
|
||||
if (!en.empty()) s_enchNames[eid] = std::move(en);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (permId != 0) {
|
||||
auto it2 = s_enchNames.find(permId);
|
||||
const char* ename = (it2 != s_enchNames.end()) ? it2->second.c_str() : nullptr;
|
||||
if (ename) ImGui::TextColored(ImVec4(0.0f, 0.8f, 1.0f, 1.0f), "Enchanted: %s", ename);
|
||||
}
|
||||
if (tempId != 0) {
|
||||
auto it2 = s_enchNames.find(tempId);
|
||||
const char* ename = (it2 != s_enchNames.end()) ? it2->second.c_str() : nullptr;
|
||||
if (ename) ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.4f, 1.0f), "%s (temporary)", ename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Item set membership
|
||||
if (info.itemSetId != 0) {
|
||||
// Lazy-load full ItemSet.dbc data (name + item IDs + bonus spells/thresholds)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue