From 1de2f4c8a0fc23014c2dd635dc5ce7486395b97e Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 18 Feb 2026 03:46:03 -0800 Subject: [PATCH] Show weapon damage/speed in item tooltips - parse and cache item class/subclass, damage range, and attack delay from item query responses - render weapon damage, speed, and DPS in the shared item-link tooltip - render weapon damage, speed, and DPS in vendor hover tooltips - keep armor and primary stat lines intact --- include/game/world_packets.hpp | 5 +++++ src/game/world_packets.cpp | 26 +++++++++++++++++++++++--- src/ui/game_screen.cpp | 18 ++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index 1d31017a..8395702a 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1408,11 +1408,16 @@ public: struct ItemQueryResponseData { uint32_t entry = 0; std::string name; + uint32_t itemClass = 0; + uint32_t subClass = 0; uint32_t displayInfoId = 0; uint32_t quality = 0; uint32_t inventoryType = 0; int32_t maxStack = 1; uint32_t containerSlots = 0; + float damageMin = 0.0f; + float damageMax = 0.0f; + uint32_t delayMs = 0; int32_t armor = 0; int32_t stamina = 0; int32_t strength = 0; diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 735f2871..c9ef2b26 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2069,6 +2069,8 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa uint32_t itemClass = packet.readUInt32(); uint32_t subClass = packet.readUInt32(); + data.itemClass = itemClass; + data.subClass = subClass; packet.readUInt32(); // SoundOverrideSubclass data.subclassName = getItemSubclassName(itemClass, subClass); @@ -2125,13 +2127,31 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa packet.readUInt32(); // ScalingStatValue // 5 damage types + bool haveWeaponDamage = false; for (int i = 0; i < 5; i++) { - packet.readFloat(); // DamageMin - packet.readFloat(); // DamageMax - packet.readUInt32(); // DamageType + float dmgMin = packet.readFloat(); + float dmgMax = packet.readFloat(); + uint32_t damageType = packet.readUInt32(); + if (!haveWeaponDamage && dmgMax > 0.0f) { + // Prefer physical damage when available, otherwise first non-zero entry. + if (damageType == 0 || data.damageMax <= 0.0f) { + data.damageMin = dmgMin; + data.damageMax = dmgMax; + haveWeaponDamage = (damageType == 0); + } + } } data.armor = static_cast(packet.readUInt32()); + if (packet.getSize() - packet.getReadPos() >= 28) { + packet.readUInt32(); // HolyRes + packet.readUInt32(); // FireRes + packet.readUInt32(); // NatureRes + packet.readUInt32(); // FrostRes + packet.readUInt32(); // ShadowRes + packet.readUInt32(); // ArcaneRes + data.delayMs = packet.readUInt32(); + } data.valid = !data.name.empty(); LOG_DEBUG("Item query response: ", data.name, " (quality=", data.quality, diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index ab843c76..6a373a9c 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -713,6 +713,15 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", slotName); } } + if (info->damageMax > 0.0f) { + ImGui::Text("%.0f - %.0f Damage", info->damageMin, info->damageMax); + if (info->delayMs > 0) { + float speed = static_cast(info->delayMs) / 1000.0f; + float dps = ((info->damageMin + info->damageMax) * 0.5f) / speed; + ImGui::Text("Speed %.2f", speed); + ImGui::Text("%.1f damage per second", dps); + } + } if (info->armor > 0) ImGui::Text("%d Armor", info->armor); ImVec4 green(0.0f, 1.0f, 0.0f, 1.0f); auto renderStat = [&](int32_t val, const char* name) { @@ -4801,6 +4810,15 @@ void GameScreen::renderVendorWindow(game::GameHandler& gameHandler) { if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::TextColored(qualityColors[q], "%s", info->name.c_str()); + if (info->damageMax > 0.0f) { + ImGui::Text("%.0f - %.0f Damage", info->damageMin, info->damageMax); + if (info->delayMs > 0) { + float speed = static_cast(info->delayMs) / 1000.0f; + float dps = ((info->damageMin + info->damageMax) * 0.5f) / speed; + ImGui::Text("Speed %.2f", speed); + ImGui::Text("%.1f damage per second", dps); + } + } if (info->armor > 0) ImGui::Text("Armor: %d", info->armor); if (info->stamina > 0) ImGui::Text("+%d Stamina", info->stamina); if (info->strength > 0) ImGui::Text("+%d Strength", info->strength);