From 43de2be1f26c7eded72bac431796f030f9ade687 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 02:52:40 -0700 Subject: [PATCH] Add inspect window showing talent summary and gear for inspected players Store inspect results (talent points, dual-spec state, gear entries) in a new InspectResult struct instead of discarding them as chat messages. Open the inspect window automatically from all Inspect menu items and /inspect. --- include/game/game_handler.hpp | 14 +++++ include/ui/game_screen.hpp | 4 ++ src/game/game_handler.cpp | 23 +++++---- src/ui/game_screen.cpp | 97 +++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 9 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index b50204fd..70dd0eec 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -332,6 +332,19 @@ public: // Inspection void inspectTarget(); + struct InspectResult { + uint64_t guid = 0; + std::string playerName; + uint32_t totalTalents = 0; + uint32_t unspentTalents = 0; + uint8_t talentGroups = 0; + uint8_t activeTalentGroup = 0; + std::array itemEntries{}; // 0=head…18=ranged + }; + const InspectResult* getInspectResult() const { + return inspectResult_.guid ? &inspectResult_ : nullptr; + } + // Server info commands void queryServerTime(); void requestPlayedTime(); @@ -2019,6 +2032,7 @@ private: // Inspect fallback (when visible item fields are missing/unreliable) std::unordered_map> inspectedPlayerItemEntries_; + InspectResult inspectResult_; // most-recently received inspect response std::unordered_set pendingAutoInspect_; float inspectRateLimit_ = 0.0f; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 1fc31818..1c5105ad 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -364,6 +364,10 @@ private: bool showGmTicketWindow_ = false; char gmTicketBuf_[2048] = {}; void renderGmTicketWindow(game::GameHandler& gameHandler); + + // Inspect window + bool showInspectWindow_ = false; + void renderInspectWindow(game::GameHandler& gameHandler); uint8_t lfgRoles_ = 0x08; // default: DPS (0x02=tank, 0x04=healer, 0x08=dps) uint32_t lfgSelectedDungeon_ = 861; // default: random dungeon (entry 861 = Random Dungeon WotLK) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index aeb9a2e9..25935eff 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -11258,16 +11258,21 @@ void GameHandler::handleInspectResults(network::Packet& packet) { } } - // Display inspect results - std::string msg = "Inspect: " + playerName; - msg += " - " + std::to_string(totalTalents) + " talent points spent"; - if (unspentTalents > 0) { - msg += ", " + std::to_string(unspentTalents) + " unspent"; + // Store inspect result for UI display + inspectResult_.guid = guid; + inspectResult_.playerName = playerName; + inspectResult_.totalTalents = totalTalents; + inspectResult_.unspentTalents = unspentTalents; + inspectResult_.talentGroups = talentGroupCount; + inspectResult_.activeTalentGroup = activeTalentGroup; + + // Merge any gear we already have from a prior inspect request + auto gearIt = inspectedPlayerItemEntries_.find(guid); + if (gearIt != inspectedPlayerItemEntries_.end()) { + inspectResult_.itemEntries = gearIt->second; + } else { + inspectResult_.itemEntries = {}; } - if (talentGroupCount > 1) { - msg += " (dual spec, active: " + std::to_string(activeTalentGroup + 1) + ")"; - } - addSystemChatMessage(msg); LOG_INFO("Inspect results for ", playerName, ": ", totalTalents, " talents, ", unspentTalents, " unspent, ", (int)talentGroupCount, " specs"); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 4a77261d..54b4c95c 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -499,6 +499,7 @@ void GameScreen::render(game::GameHandler& gameHandler) { renderInstanceLockouts(gameHandler); renderAchievementWindow(gameHandler); renderGmTicketWindow(gameHandler); + renderInspectWindow(gameHandler); // renderQuestMarkers(gameHandler); // Disabled - using 3D billboard markers now if (showMinimap_) { renderMinimapMarkers(gameHandler); @@ -2621,6 +2622,7 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) { } if (ImGui::MenuItem("Inspect")) { gameHandler.inspectTarget(); + showInspectWindow_ = true; } ImGui::Separator(); if (ImGui::MenuItem("Add Friend")) { @@ -3009,6 +3011,7 @@ void GameScreen::renderFocusFrame(game::GameHandler& gameHandler) { if (ImGui::MenuItem("Inspect")) { gameHandler.setTarget(fGuid); gameHandler.inspectTarget(); + showInspectWindow_ = true; } ImGui::Separator(); if (ImGui::MenuItem("Add Friend")) @@ -3144,6 +3147,7 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) { // /inspect command if (cmdLower == "inspect") { gameHandler.inspectTarget(); + showInspectWindow_ = true; chatInputBuffer[0] = '\0'; return; } @@ -6348,6 +6352,7 @@ void GameScreen::renderPartyFrames(game::GameHandler& gameHandler) { if (ImGui::MenuItem("Inspect")) { gameHandler.setTarget(m.guid); gameHandler.inspectTarget(); + showInspectWindow_ = true; } bool isLeader = (partyData.leaderGuid == gameHandler.getPlayerGuid()); if (isLeader) { @@ -6532,6 +6537,7 @@ void GameScreen::renderPartyFrames(game::GameHandler& gameHandler) { if (ImGui::MenuItem("Inspect")) { gameHandler.setTarget(member.guid); gameHandler.inspectTarget(); + showInspectWindow_ = true; } ImGui::Separator(); if (!member.name.empty()) { @@ -14484,4 +14490,95 @@ void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) { ImGui::End(); } +// ─── Inspect Window ─────────────────────────────────────────────────────────── +void GameScreen::renderInspectWindow(game::GameHandler& gameHandler) { + if (!showInspectWindow_) return; + + // Slot index 0..18 maps to equipment slots 1..19 (WoW convention: slot 0 unused on server) + static const char* kSlotNames[19] = { + "Head", "Neck", "Shoulder", "Shirt", "Chest", + "Waist", "Legs", "Feet", "Wrist", "Hands", + "Finger 1", "Finger 2", "Trinket 1", "Trinket 2", "Back", + "Main Hand", "Off Hand", "Ranged", "Tabard" + }; + + ImGui::SetNextWindowSize(ImVec2(360, 440), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(350, 120), ImGuiCond_FirstUseEver); + + const game::GameHandler::InspectResult* result = gameHandler.getInspectResult(); + + std::string title = result ? ("Inspect: " + result->playerName + "###InspectWin") + : "Inspect###InspectWin"; + if (!ImGui::Begin(title.c_str(), &showInspectWindow_, ImGuiWindowFlags_NoCollapse)) { + ImGui::End(); + return; + } + + if (!result) { + ImGui::TextDisabled("No inspect data yet. Target a player and use Inspect."); + ImGui::End(); + return; + } + + // Talent summary + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.0f, 1.0f)); // gold + ImGui::Text("%s", result->playerName.c_str()); + ImGui::PopStyleColor(); + ImGui::SameLine(); + ImGui::TextDisabled(" %u talent pts", result->totalTalents); + if (result->unspentTalents > 0) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "(%u unspent)", result->unspentTalents); + } + if (result->talentGroups > 1) { + ImGui::SameLine(); + ImGui::TextDisabled(" Dual spec (active %u)", (unsigned)result->activeTalentGroup + 1); + } + + ImGui::Separator(); + + // Equipment list + bool hasAnyGear = false; + for (int s = 0; s < 19; ++s) { + if (result->itemEntries[s] != 0) { hasAnyGear = true; break; } + } + + if (!hasAnyGear) { + ImGui::TextDisabled("Equipment data not yet available."); + ImGui::TextDisabled("(Gear loads after the player is inspected in-range)"); + } else { + if (ImGui::BeginChild("##inspect_gear", ImVec2(0, 0), false)) { + for (int s = 0; s < 19; ++s) { + uint32_t entry = result->itemEntries[s]; + if (entry == 0) continue; + + const game::ItemQueryResponseData* info = gameHandler.getItemInfo(entry); + if (!info) { + gameHandler.ensureItemInfo(entry); + ImGui::TextDisabled("[%s] (loading…)", kSlotNames[s]); + continue; + } + + ImGui::TextDisabled("%s", kSlotNames[s]); + ImGui::SameLine(90); + auto qColor = InventoryScreen::getQualityColor( + static_cast(info->quality)); + ImGui::TextColored(qColor, "%s", info->name.c_str()); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextColored(qColor, "%s", info->name.c_str()); + if (info->itemLevel > 0) + ImGui::Text("Item Level %u", info->itemLevel); + if (info->armor > 0) + ImGui::Text("Armor: %d", info->armor); + ImGui::EndTooltip(); + } + } + } + ImGui::EndChild(); + } + + ImGui::End(); +} + }} // namespace wowee::ui