feat: show socket gems and consolidate enchant name DBC cache in item tooltips

Extends OnlineItemInfo to track gem enchant IDs (socket slots 2-4) from item
update fields; socket display now shows inserted gem name inline (e.g.
"Red Socket: Bold Scarlet Ruby"). Consolidates redundant SpellItemEnchantment
DBC loads into one shared static per tooltip variant.
This commit is contained in:
Kelsi 2026-03-18 04:04:23 -07:00
parent 167e710f92
commit d7c377292e
3 changed files with 133 additions and 116 deletions

View file

@ -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<uint32_t, std::string> 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<uint32_t, 3> 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<uint32_t, std::string> 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<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);
}
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<uint32_t, std::string> 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<game::ItemQuality>(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<uint32_t, 3> 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<uint32_t, std::string> 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<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);
}
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);
}
}