mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: enhance item tooltips with binding, description, speed, and spell effects
- Parse Bonding and Description fields from SMSG_ITEM_QUERY_SINGLE_RESPONSE
(read after the 5 spell slots: bindType uint32, then description cstring)
- Add bindType and description to ItemQueryResponseData and ItemDef
- Propagate bindType and description through all 5 rebuildOnlineInventory paths
- Tooltip now shows: "Binds when picked up/equipped/used/quest item"
- Tooltip now shows weapon damage range ("X - Y Damage") and speed ("Speed 2.60")
on same line, plus DPS in parentheses below
- Tooltip now shows spell effects ("Use: <SpellName>", "Equip: <SpellName>",
"Chance on Hit: ...") using existing getSpellName() lookup
- Tooltip now shows item flavor/lore description in italic-style yellow text
This commit is contained in:
parent
f53f16a59b
commit
76bd6b409e
5 changed files with 64 additions and 3 deletions
|
|
@ -50,6 +50,8 @@ struct ItemDef {
|
||||||
uint32_t maxDurability = 0;
|
uint32_t maxDurability = 0;
|
||||||
uint32_t itemLevel = 0;
|
uint32_t itemLevel = 0;
|
||||||
uint32_t requiredLevel = 0;
|
uint32_t requiredLevel = 0;
|
||||||
|
uint32_t bindType = 0; // 0=none, 1=BoP, 2=BoE, 3=BoU, 4=BoQ
|
||||||
|
std::string description; // Flavor/lore text shown in tooltip (italic yellow)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ItemSlot {
|
struct ItemSlot {
|
||||||
|
|
|
||||||
|
|
@ -1561,6 +1561,8 @@ struct ItemQueryResponseData {
|
||||||
uint32_t spellTrigger = 0; // 0=Use, 1=Equip, 2=ChanceOnHit, 5=Learn
|
uint32_t spellTrigger = 0; // 0=Use, 1=Equip, 2=ChanceOnHit, 5=Learn
|
||||||
};
|
};
|
||||||
std::array<ItemSpell, 5> spells{};
|
std::array<ItemSpell, 5> spells{};
|
||||||
|
uint32_t bindType = 0; // 0=none, 1=BoP, 2=BoE, 3=BoU, 4=BoQ
|
||||||
|
std::string description; // Flavor/lore text
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10761,6 +10761,8 @@ void GameHandler::rebuildOnlineInventory() {
|
||||||
def.sellPrice = infoIt->second.sellPrice;
|
def.sellPrice = infoIt->second.sellPrice;
|
||||||
def.itemLevel = infoIt->second.itemLevel;
|
def.itemLevel = infoIt->second.itemLevel;
|
||||||
def.requiredLevel = infoIt->second.requiredLevel;
|
def.requiredLevel = infoIt->second.requiredLevel;
|
||||||
|
def.bindType = infoIt->second.bindType;
|
||||||
|
def.description = infoIt->second.description;
|
||||||
} else {
|
} else {
|
||||||
def.name = "Item " + std::to_string(def.itemId);
|
def.name = "Item " + std::to_string(def.itemId);
|
||||||
queryItemInfo(def.itemId, guid);
|
queryItemInfo(def.itemId, guid);
|
||||||
|
|
@ -10804,6 +10806,8 @@ void GameHandler::rebuildOnlineInventory() {
|
||||||
def.sellPrice = infoIt->second.sellPrice;
|
def.sellPrice = infoIt->second.sellPrice;
|
||||||
def.itemLevel = infoIt->second.itemLevel;
|
def.itemLevel = infoIt->second.itemLevel;
|
||||||
def.requiredLevel = infoIt->second.requiredLevel;
|
def.requiredLevel = infoIt->second.requiredLevel;
|
||||||
|
def.bindType = infoIt->second.bindType;
|
||||||
|
def.description = infoIt->second.description;
|
||||||
} else {
|
} else {
|
||||||
def.name = "Item " + std::to_string(def.itemId);
|
def.name = "Item " + std::to_string(def.itemId);
|
||||||
queryItemInfo(def.itemId, guid);
|
queryItemInfo(def.itemId, guid);
|
||||||
|
|
@ -10926,6 +10930,8 @@ void GameHandler::rebuildOnlineInventory() {
|
||||||
def.spirit = infoIt->second.spirit;
|
def.spirit = infoIt->second.spirit;
|
||||||
def.itemLevel = infoIt->second.itemLevel;
|
def.itemLevel = infoIt->second.itemLevel;
|
||||||
def.requiredLevel = infoIt->second.requiredLevel;
|
def.requiredLevel = infoIt->second.requiredLevel;
|
||||||
|
def.bindType = infoIt->second.bindType;
|
||||||
|
def.description = infoIt->second.description;
|
||||||
def.sellPrice = infoIt->second.sellPrice;
|
def.sellPrice = infoIt->second.sellPrice;
|
||||||
def.bagSlots = infoIt->second.containerSlots;
|
def.bagSlots = infoIt->second.containerSlots;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -2518,6 +2518,14 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
||||||
packet.readUInt32(); // SpellCategoryCooldown
|
packet.readUInt32(); // SpellCategoryCooldown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bonding type (0=none, 1=BoP, 2=BoE, 3=BoU, 4=BoQ)
|
||||||
|
if (packet.getReadPos() + 4 <= packet.getSize())
|
||||||
|
data.bindType = packet.readUInt32();
|
||||||
|
|
||||||
|
// Flavor/lore text (Description cstring)
|
||||||
|
if (packet.getReadPos() < packet.getSize())
|
||||||
|
data.description = packet.readString();
|
||||||
|
|
||||||
data.valid = !data.name.empty();
|
data.valid = !data.name.empty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1718,6 +1718,15 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I
|
||||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.7f), "Item Level %u", item.itemLevel);
|
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.7f), "Item Level %u", item.itemLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Binding type
|
||||||
|
switch (item.bindType) {
|
||||||
|
case 1: ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.0f, 1.0f), "Binds when picked up"); break;
|
||||||
|
case 2: ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.0f, 1.0f), "Binds when equipped"); break;
|
||||||
|
case 3: ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.0f, 1.0f), "Binds when used"); break;
|
||||||
|
case 4: ImGui::TextColored(ImVec4(1.0f, 0.82f, 0.0f, 1.0f), "Quest Item"); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.itemId == 6948 && gameHandler_) {
|
if (item.itemId == 6948 && gameHandler_) {
|
||||||
uint32_t mapId = 0;
|
uint32_t mapId = 0;
|
||||||
glm::vec3 pos;
|
glm::vec3 pos;
|
||||||
|
|
@ -1793,13 +1802,15 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I
|
||||||
};
|
};
|
||||||
const bool isWeapon = isWeaponInventoryType(item.inventoryType);
|
const bool isWeapon = isWeaponInventoryType(item.inventoryType);
|
||||||
|
|
||||||
// Compact stats view for weapons: DPS + condensed stat bonuses.
|
// Compact stats view for weapons: damage range + speed + DPS
|
||||||
// Non-weapons keep armor/sell info visible.
|
|
||||||
ImVec4 green(0.0f, 1.0f, 0.0f, 1.0f);
|
ImVec4 green(0.0f, 1.0f, 0.0f, 1.0f);
|
||||||
if (isWeapon && item.damageMax > 0.0f && item.delayMs > 0) {
|
if (isWeapon && item.damageMax > 0.0f && item.delayMs > 0) {
|
||||||
float speed = static_cast<float>(item.delayMs) / 1000.0f;
|
float speed = static_cast<float>(item.delayMs) / 1000.0f;
|
||||||
float dps = ((item.damageMin + item.damageMax) * 0.5f) / speed;
|
float dps = ((item.damageMin + item.damageMax) * 0.5f) / speed;
|
||||||
ImGui::Text("%.1f DPS", dps);
|
ImGui::Text("%.0f - %.0f Damage", item.damageMin, item.damageMax);
|
||||||
|
ImGui::SameLine(160.0f);
|
||||||
|
ImGui::TextDisabled("Speed %.2f", speed);
|
||||||
|
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(%.1f damage per second)", dps);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Armor appears before stat bonuses — matches WoW tooltip order
|
// Armor appears before stat bonuses — matches WoW tooltip order
|
||||||
|
|
@ -1834,6 +1845,38 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I
|
||||||
ImGui::TextColored(durColor, "Durability %u / %u",
|
ImGui::TextColored(durColor, "Durability %u / %u",
|
||||||
item.curDurability, item.maxDurability);
|
item.curDurability, item.maxDurability);
|
||||||
}
|
}
|
||||||
|
// Item spell effects (Use/Equip/Chance on Hit)
|
||||||
|
if (gameHandler_) {
|
||||||
|
auto* info = gameHandler_->getItemInfo(item.itemId);
|
||||||
|
if (info) {
|
||||||
|
for (const auto& sp : info->spells) {
|
||||||
|
if (sp.spellId == 0) continue;
|
||||||
|
const char* trigger = nullptr;
|
||||||
|
switch (sp.spellTrigger) {
|
||||||
|
case 0: trigger = "Use"; break;
|
||||||
|
case 1: trigger = "Equip"; break;
|
||||||
|
case 2: trigger = "Chance on Hit"; break;
|
||||||
|
case 6: trigger = "Soulstone"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (!trigger) continue;
|
||||||
|
const std::string& spName = gameHandler_->getSpellName(sp.spellId);
|
||||||
|
if (!spName.empty()) {
|
||||||
|
ImGui::TextColored(ImVec4(0.0f, 0.8f, 1.0f, 1.0f),
|
||||||
|
"%s: %s", trigger, spName.c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::TextColored(ImVec4(0.0f, 0.8f, 1.0f, 1.0f),
|
||||||
|
"%s: Spell #%u", trigger, sp.spellId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flavor / lore text (italic yellow in WoW, just yellow here)
|
||||||
|
if (!item.description.empty()) {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.5f, 0.9f), "\"%s\"", item.description.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
if (item.sellPrice > 0) {
|
if (item.sellPrice > 0) {
|
||||||
uint32_t g = item.sellPrice / 10000;
|
uint32_t g = item.sellPrice / 10000;
|
||||||
uint32_t s = (item.sellPrice / 100) % 100;
|
uint32_t s = (item.sellPrice / 100) % 100;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue