diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 04d0e78d..a9f69c94 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -2135,6 +2135,12 @@ public: if (it == onlineItems_.end()) return {0, 0}; return {it->second.permanentEnchantId, it->second.temporaryEnchantId}; } + // Returns the socket gem enchant IDs (3 slots; 0 = empty socket) for an item by GUID. + std::array getItemSocketEnchantIds(uint64_t guid) const { + auto it = onlineItems_.find(guid); + if (it == onlineItems_.end()) return {}; + return it->second.socketEnchantIds; + } uint64_t getVendorGuid() const { return currentVendorItems.vendorGuid; } /** @@ -2633,6 +2639,7 @@ private: 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::array socketEnchantIds{}; // ITEM_ENCHANTMENT_SLOT 2-4 (gems) }; std::unordered_map onlineItems_; std::unordered_map itemInfoCache_; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 50e45512..664be8fe 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -11679,18 +11679,24 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem auto maxDurIt= block.fields.find(fieldIndex(UF::ITEM_FIELD_MAXDURABILITY)); const uint16_t enchBase = (fieldIndex(UF::ITEM_FIELD_STACK_COUNT) != 0xFFFF) ? static_cast(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(); + auto permEnchIt = (enchBase != 0xFFFF) ? block.fields.find(enchBase) : block.fields.end(); + auto tempEnchIt = (enchBase != 0xFFFF) ? block.fields.find(enchBase + 3u) : block.fields.end(); + auto sock1EnchIt = (enchBase != 0xFFFF) ? block.fields.find(enchBase + 6u) : block.fields.end(); + auto sock2EnchIt = (enchBase != 0xFFFF) ? block.fields.find(enchBase + 9u) : block.fields.end(); + auto sock3EnchIt = (enchBase != 0xFFFF) ? block.fields.find(enchBase + 12u) : 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 (permEnchIt != block.fields.end()) info.permanentEnchantId = permEnchIt->second; - if (tempEnchIt != block.fields.end()) info.temporaryEnchantId = tempEnchIt->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; + if (sock1EnchIt != block.fields.end()) info.socketEnchantIds[0] = sock1EnchIt->second; + if (sock2EnchIt != block.fields.end()) info.socketEnchantIds[1] = sock2EnchIt->second; + if (sock3EnchIt != block.fields.end()) info.socketEnchantIds[2] = sock3EnchIt->second; bool isNew = (onlineItems_.find(block.guid) == onlineItems_.end()); onlineItems_[block.guid] = info; if (isNew) newItemCreated = true; @@ -12277,10 +12283,13 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem 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; + // Slot 0 = permanent (field +0), slot 1 = temp (+3), slots 2-4 = sockets (+6,+9,+12). + const uint16_t itemEnchBase = (itemStackField != 0xFFFF) ? (itemStackField + 8u) : 0xFFFF; const uint16_t itemPermEnchField = itemEnchBase; - const uint16_t itemTempEnchField = (itemEnchBase != 0xFFFF) ? (itemEnchBase + 3u) : 0xFFFF; + const uint16_t itemTempEnchField = (itemEnchBase != 0xFFFF) ? (itemEnchBase + 3u) : 0xFFFF; + const uint16_t itemSock1EnchField= (itemEnchBase != 0xFFFF) ? (itemEnchBase + 6u) : 0xFFFF; + const uint16_t itemSock2EnchField= (itemEnchBase != 0xFFFF) ? (itemEnchBase + 9u) : 0xFFFF; + const uint16_t itemSock3EnchField= (itemEnchBase != 0xFFFF) ? (itemEnchBase + 12u) : 0xFFFF; auto it = onlineItems_.find(block.guid); bool isItemInInventory = (it != onlineItems_.end()); @@ -12311,6 +12320,21 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem it->second.temporaryEnchantId = val; inventoryChanged = true; } + } else if (isItemInInventory && itemSock1EnchField != 0xFFFF && key == itemSock1EnchField) { + if (it->second.socketEnchantIds[0] != val) { + it->second.socketEnchantIds[0] = val; + inventoryChanged = true; + } + } else if (isItemInInventory && itemSock2EnchField != 0xFFFF && key == itemSock2EnchField) { + if (it->second.socketEnchantIds[1] != val) { + it->second.socketEnchantIds[1] = val; + inventoryChanged = true; + } + } else if (isItemInInventory && itemSock3EnchField != 0xFFFF && key == itemSock3EnchField) { + if (it->second.socketEnchantIds[2] != val) { + it->second.socketEnchantIds[2] = val; + inventoryChanged = true; + } } } // Update container slot GUIDs on bag content changes diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 931a2b10..23c1ae92 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -2410,6 +2410,27 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite } void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::Inventory* inventory, uint64_t itemGuid) { + // Shared SpellItemEnchantment name lookup — used for socket gems, permanent and temp enchants. + static std::unordered_map s_enchLookupB; + static bool s_enchLookupLoadedB = false; + if (!s_enchLookupLoadedB && assetManager_) { + s_enchLookupLoadedB = 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_enchLookupB[eid] = std::move(en); + } + } + } + ImGui::BeginTooltip(); ImVec4 qColor = getQualityColor(item.quality); @@ -2794,39 +2815,33 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I { 4, "Yellow Socket", { 1.0f, 0.9f, 0.3f, 1.0f } }, { 8, "Blue Socket", { 0.3f, 0.6f, 1.0f, 1.0f } }, }; + // Get socket gem enchant IDs for this item (filled from item update fields) + std::array sockGems{}; + if (itemGuid != 0 && gameHandler_) + sockGems = gameHandler_->getItemSocketEnchantIds(itemGuid); + bool hasSocket = false; for (int i = 0; i < 3; ++i) { if (qi2->socketColor[i] == 0) continue; if (!hasSocket) { ImGui::Spacing(); hasSocket = true; } for (const auto& st : kSocketTypes) { if (qi2->socketColor[i] & st.mask) { - ImGui::TextColored(st.col, "%s", st.label); + if (sockGems[i] != 0) { + auto git = s_enchLookupB.find(sockGems[i]); + if (git != s_enchLookupB.end()) + ImGui::TextColored(st.col, "%s: %s", st.label, git->second.c_str()); + else + ImGui::TextColored(st.col, "%s: (gem %u)", st.label, sockGems[i]); + } else { + ImGui::TextColored(st.col, "%s", st.label); + } break; } } } if (hasSocket && qi2->socketBonus != 0) { - static std::unordered_map s_enchantNamesD; - static bool s_enchantNamesLoadedD = false; - if (!s_enchantNamesLoadedD && assetManager_) { - s_enchantNamesLoadedD = true; - auto dbc = assetManager_->loadDBC("SpellItemEnchantment.dbc"); - if (dbc && dbc->isLoaded()) { - const auto* lay = pipeline::getActiveDBCLayout() - ? pipeline::getActiveDBCLayout()->getLayout("SpellItemEnchantment") : nullptr; - uint32_t nameField = lay ? lay->field("Name") : 8u; - if (nameField == 0xFFFFFFFF) nameField = 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 || nameField >= fc) continue; - std::string ename = dbc->getString(r, nameField); - if (!ename.empty()) s_enchantNamesD[eid] = std::move(ename); - } - } - } - auto enchIt = s_enchantNamesD.find(qi2->socketBonus); - if (enchIt != s_enchantNamesD.end()) + auto enchIt = s_enchLookupB.find(qi2->socketBonus); + if (enchIt != s_enchLookupB.end()) ImGui::TextColored(ImVec4(0.5f, 0.8f, 0.5f, 1.0f), "Socket Bonus: %s", enchIt->second.c_str()); else ImGui::TextColored(ImVec4(0.5f, 0.8f, 0.5f, 1.0f), "Socket Bonus: (id %u)", qi2->socketBonus); @@ -2921,36 +2936,15 @@ 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 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); - } + if (permId != 0) { + auto it2 = s_enchLookupB.find(permId); + const char* ename = (it2 != s_enchLookupB.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_enchLookupB.find(tempId); + const char* ename = (it2 != s_enchLookupB.end()) ? it2->second.c_str() : nullptr; + if (ename) ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.4f, 1.0f), "%s (temporary)", ename); } } @@ -3107,6 +3101,27 @@ 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, uint64_t itemGuid) { + // Shared SpellItemEnchantment name lookup — used for socket gems, socket bonus, and enchants. + static std::unordered_map s_enchLookup; + static bool s_enchLookupLoaded = false; + if (!s_enchLookupLoaded && assetManager_) { + s_enchLookupLoaded = 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_enchLookup[eid] = std::move(en); + } + } + } + ImGui::BeginTooltip(); ImVec4 qColor = getQualityColor(static_cast(info.quality)); @@ -3441,40 +3456,33 @@ void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info, { 4, "Yellow Socket", { 1.0f, 0.9f, 0.3f, 1.0f } }, { 8, "Blue Socket", { 0.3f, 0.6f, 1.0f, 1.0f } }, }; + // Get socket gem enchant IDs for this item (filled from item update fields) + std::array sockGems{}; + if (itemGuid != 0 && gameHandler_) + sockGems = gameHandler_->getItemSocketEnchantIds(itemGuid); + bool hasSocket = false; for (int i = 0; i < 3; ++i) { if (info.socketColor[i] == 0) continue; if (!hasSocket) { ImGui::Spacing(); hasSocket = true; } for (const auto& st : kSocketTypes) { if (info.socketColor[i] & st.mask) { - ImGui::TextColored(st.col, "%s", st.label); + if (sockGems[i] != 0) { + auto git = s_enchLookup.find(sockGems[i]); + if (git != s_enchLookup.end()) + ImGui::TextColored(st.col, "%s: %s", st.label, git->second.c_str()); + else + ImGui::TextColored(st.col, "%s: (gem %u)", st.label, sockGems[i]); + } else { + ImGui::TextColored(st.col, "%s", st.label); + } break; } } } if (hasSocket && info.socketBonus != 0) { - // Socket bonus is a SpellItemEnchantment ID — look up via SpellItemEnchantment.dbc - static std::unordered_map s_enchantNames; - static bool s_enchantNamesLoaded = false; - if (!s_enchantNamesLoaded && assetManager_) { - s_enchantNamesLoaded = true; - auto dbc = assetManager_->loadDBC("SpellItemEnchantment.dbc"); - if (dbc && dbc->isLoaded()) { - const auto* lay = pipeline::getActiveDBCLayout() - ? pipeline::getActiveDBCLayout()->getLayout("SpellItemEnchantment") : nullptr; - uint32_t nameField = lay ? lay->field("Name") : 8u; - if (nameField == 0xFFFFFFFF) nameField = 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 || nameField >= fc) continue; - std::string ename = dbc->getString(r, nameField); - if (!ename.empty()) s_enchantNames[eid] = std::move(ename); - } - } - } - auto enchIt = s_enchantNames.find(info.socketBonus); - if (enchIt != s_enchantNames.end()) + auto enchIt = s_enchLookup.find(info.socketBonus); + if (enchIt != s_enchLookup.end()) ImGui::TextColored(ImVec4(0.5f, 0.8f, 0.5f, 1.0f), "Socket Bonus: %s", enchIt->second.c_str()); else ImGui::TextColored(ImVec4(0.5f, 0.8f, 0.5f, 1.0f), "Socket Bonus: (id %u)", info.socketBonus); @@ -3484,37 +3492,15 @@ 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 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); - } + if (permId != 0) { + auto it2 = s_enchLookup.find(permId); + const char* ename = (it2 != s_enchLookup.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_enchLookup.find(tempId); + const char* ename = (it2 != s_enchLookup.end()) ? it2->second.c_str() : nullptr; + if (ename) ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.4f, 1.0f), "%s (temporary)", ename); } }