diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 224a6ae6..5e996ba9 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1135,6 +1135,10 @@ public: const Character* ch = getActiveCharacter(); return ch ? static_cast(ch->characterClass) : 0; } + uint8_t getPlayerRace() const { + const Character* ch = getActiveCharacter(); + return ch ? static_cast(ch->race) : 0; + } void setPlayerGuid(uint64_t guid) { playerGuid = guid; } // Player death state diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index 4beab4a3..257df817 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1594,6 +1594,12 @@ struct ItemQueryResponseData { float damageMax = 0.0f; uint32_t delayMs = 0; int32_t armor = 0; + int32_t holyRes = 0; + int32_t fireRes = 0; + int32_t natureRes = 0; + int32_t frostRes = 0; + int32_t shadowRes = 0; + int32_t arcaneRes = 0; int32_t stamina = 0; int32_t strength = 0; int32_t agility = 0; diff --git a/src/game/packet_parsers_classic.cpp b/src/game/packet_parsers_classic.cpp index acb198f6..53b07f15 100644 --- a/src/game/packet_parsers_classic.cpp +++ b/src/game/packet_parsers_classic.cpp @@ -1468,12 +1468,12 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ // Remaining tail can vary by core. Read resistances + delay when present. 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.holyRes = static_cast(packet.readUInt32()); // HolyRes + data.fireRes = static_cast(packet.readUInt32()); // FireRes + data.natureRes = static_cast(packet.readUInt32()); // NatureRes + data.frostRes = static_cast(packet.readUInt32()); // FrostRes + data.shadowRes = static_cast(packet.readUInt32()); // ShadowRes + data.arcaneRes = static_cast(packet.readUInt32()); // ArcaneRes data.delayMs = packet.readUInt32(); } diff --git a/src/game/packet_parsers_tbc.cpp b/src/game/packet_parsers_tbc.cpp index 8bda9afb..45ef8dde 100644 --- a/src/game/packet_parsers_tbc.cpp +++ b/src/game/packet_parsers_tbc.cpp @@ -1087,12 +1087,12 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery 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.holyRes = static_cast(packet.readUInt32()); // HolyRes + data.fireRes = static_cast(packet.readUInt32()); // FireRes + data.natureRes = static_cast(packet.readUInt32()); // NatureRes + data.frostRes = static_cast(packet.readUInt32()); // FrostRes + data.shadowRes = static_cast(packet.readUInt32()); // ShadowRes + data.arcaneRes = static_cast(packet.readUInt32()); // ArcaneRes data.delayMs = packet.readUInt32(); } diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 399c9fbd..d8f2c98a 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2945,12 +2945,12 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa } data.armor = static_cast(packet.readUInt32()); - packet.readUInt32(); // HolyRes - packet.readUInt32(); // FireRes - packet.readUInt32(); // NatureRes - packet.readUInt32(); // FrostRes - packet.readUInt32(); // ShadowRes - packet.readUInt32(); // ArcaneRes + data.holyRes = static_cast(packet.readUInt32()); // HolyRes + data.fireRes = static_cast(packet.readUInt32()); // FireRes + data.natureRes = static_cast(packet.readUInt32()); // NatureRes + data.frostRes = static_cast(packet.readUInt32()); // FrostRes + data.shadowRes = static_cast(packet.readUInt32()); // ShadowRes + data.arcaneRes = static_cast(packet.readUInt32()); // ArcaneRes data.delayMs = packet.readUInt32(); packet.readUInt32(); // AmmoType packet.readFloat(); // RangedModRange diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index f4616b36..3510c4a5 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -2423,6 +2423,21 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I ImGui::Text("%d Armor", item.armor); } + // Elemental resistances from item query cache (fire resist gear, nature resist gear, etc.) + if (gameHandler_) { + const auto* qi = gameHandler_->getItemInfo(item.itemId); + if (qi && qi->valid) { + const int32_t resValsI[6] = { qi->holyRes, qi->fireRes, qi->natureRes, + qi->frostRes, qi->shadowRes, qi->arcaneRes }; + static const char* resLabelsI[6] = { + "Holy Resistance", "Fire Resistance", "Nature Resistance", + "Frost Resistance", "Shadow Resistance", "Arcane Resistance" + }; + for (int i = 0; i < 6; ++i) + if (resValsI[i] > 0) ImGui::Text("+%d %s", resValsI[i], resLabelsI[i]); + } + } + auto appendBonus = [](std::string& out, int32_t val, const char* shortName) { if (val <= 0) return; if (!out.empty()) out += " "; @@ -2595,6 +2610,55 @@ void InventoryScreen::renderItemTooltip(const game::ItemDef& item, const game::I rankName, fIt != s_factionNamesB.end() ? fIt->second.c_str() : "Unknown Faction"); } + // Class restriction + if (qInfo->allowableClass != 0) { + static const struct { uint32_t mask; const char* name; } kClassesB[] = { + { 1,"Warrior" },{ 2,"Paladin" },{ 4,"Hunter" },{ 8,"Rogue" }, + { 16,"Priest" },{ 32,"Death Knight" },{ 64,"Shaman" }, + { 128,"Mage" },{ 256,"Warlock" },{ 1024,"Druid" }, + }; + int mc = 0; + for (const auto& kc : kClassesB) if (qInfo->allowableClass & kc.mask) ++mc; + if (mc > 0 && mc < 10) { + char buf[128] = "Classes: "; bool first = true; + for (const auto& kc : kClassesB) { + if (!(qInfo->allowableClass & kc.mask)) continue; + if (!first) strncat(buf, ", ", sizeof(buf)-strlen(buf)-1); + strncat(buf, kc.name, sizeof(buf)-strlen(buf)-1); + first = false; + } + uint8_t pc = gameHandler_->getPlayerClass(); + uint32_t pm = (pc > 0 && pc <= 10) ? (1u << (pc-1)) : 0; + bool ok = (pm == 0 || (qInfo->allowableClass & pm)); + ImGui::TextColored(ok ? ImVec4(1,1,1,0.75f) : ImVec4(1,0.5f,0.5f,1), "%s", buf); + } + } + // Race restriction + if (qInfo->allowableRace != 0) { + static const struct { uint32_t mask; const char* name; } kRacesB[] = { + { 1,"Human" },{ 2,"Orc" },{ 4,"Dwarf" },{ 8,"Night Elf" }, + { 16,"Undead" },{ 32,"Tauren" },{ 64,"Gnome" },{ 128,"Troll" }, + { 512,"Blood Elf" },{ 1024,"Draenei" }, + }; + constexpr uint32_t kAll = 1|2|4|8|16|32|64|128|512|1024; + if ((qInfo->allowableRace & kAll) != kAll) { + int mc = 0; + for (const auto& kr : kRacesB) if (qInfo->allowableRace & kr.mask) ++mc; + if (mc > 0) { + char buf[160] = "Races: "; bool first = true; + for (const auto& kr : kRacesB) { + if (!(qInfo->allowableRace & kr.mask)) continue; + if (!first) strncat(buf, ", ", sizeof(buf)-strlen(buf)-1); + strncat(buf, kr.name, sizeof(buf)-strlen(buf)-1); + first = false; + } + uint8_t pr = gameHandler_->getPlayerRace(); + uint32_t pm = (pr > 0 && pr <= 11) ? (1u << (pr-1)) : 0; + bool ok = (pm == 0 || (qInfo->allowableRace & pm)); + ImGui::TextColored(ok ? ImVec4(1,1,1,0.75f) : ImVec4(1,0.5f,0.5f,1), "%s", buf); + } + } + } } } @@ -2810,6 +2874,18 @@ void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info, if (info.armor > 0) ImGui::Text("%d Armor", info.armor); + // Elemental resistances (fire resist gear, nature resist gear, etc.) + { + const int32_t resVals[6] = { info.holyRes, info.fireRes, info.natureRes, + info.frostRes, info.shadowRes, info.arcaneRes }; + static const char* resLabels[6] = { + "Holy Resistance", "Fire Resistance", "Nature Resistance", + "Frost Resistance", "Shadow Resistance", "Arcane Resistance" + }; + for (int i = 0; i < 6; ++i) + if (resVals[i] > 0) ImGui::Text("+%d %s", resVals[i], resLabels[i]); + } + auto appendBonus = [](std::string& out, int32_t val, const char* name) { if (val <= 0) return; if (!out.empty()) out += " "; @@ -2970,6 +3046,47 @@ void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info, } } + // Race restriction (e.g. "Races: Night Elf, Human") + if (info.allowableRace != 0) { + static const struct { uint32_t mask; const char* name; } kRaces[] = { + { 1, "Human" }, + { 2, "Orc" }, + { 4, "Dwarf" }, + { 8, "Night Elf" }, + { 16, "Undead" }, + { 32, "Tauren" }, + { 64, "Gnome" }, + { 128, "Troll" }, + { 512, "Blood Elf" }, + { 1024, "Draenei" }, + }; + constexpr uint32_t kAllPlayable = 1|2|4|8|16|32|64|128|512|1024; + // Only show if not all playable races are allowed + if ((info.allowableRace & kAllPlayable) != kAllPlayable) { + int matchCount = 0; + for (const auto& kr : kRaces) + if (info.allowableRace & kr.mask) ++matchCount; + if (matchCount > 0) { + char raceBuf[160] = "Races: "; + bool first = true; + for (const auto& kr : kRaces) { + if (!(info.allowableRace & kr.mask)) continue; + if (!first) strncat(raceBuf, ", ", sizeof(raceBuf) - strlen(raceBuf) - 1); + strncat(raceBuf, kr.name, sizeof(raceBuf) - strlen(raceBuf) - 1); + first = false; + } + bool playerAllowed = true; + if (gameHandler_) { + uint8_t pr = gameHandler_->getPlayerRace(); + uint32_t pmask = (pr > 0 && pr <= 11) ? (1u << (pr - 1)) : 0; + playerAllowed = (pmask == 0 || (info.allowableRace & pmask)); + } + ImVec4 rColor = playerAllowed ? ImVec4(1.0f, 1.0f, 1.0f, 0.75f) : ImVec4(1.0f, 0.5f, 0.5f, 1.0f); + ImGui::TextColored(rColor, "%s", raceBuf); + } + } + } + // Spell effects for (const auto& sp : info.spells) { if (sp.spellId == 0) continue;