From a2dd8ee5b55e71dfef3389684c56fb2d88928348 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 06:10:29 -0700 Subject: [PATCH] game,ui: implement MSG_RAID_TARGET_UPDATE and display raid marks Parse the full and single-update variants of MSG_RAID_TARGET_UPDATE to track which guid carries each of the 8 raid icons (Star/Circle/Diamond/ Triangle/Moon/Square/Cross/Skull). Marks are cleared on world transfer. The target frame now shows the Unicode symbol for the target's raid mark in its faction color to the left of the name. Nameplates show the same symbol to the left of the unit name for all nearby marked units. --- include/game/game_handler.hpp | 18 ++++++++++++++++ src/game/game_handler.cpp | 30 ++++++++++++++++++++++++-- src/ui/game_screen.cpp | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index b5f397a9..42481376 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -888,6 +888,21 @@ public: return (slot < kMaxEncounterSlots) ? encounterUnitGuids_[slot] : 0; } + // Raid target markers (MSG_RAID_TARGET_UPDATE) + // Icon indices 0-7: Star, Circle, Diamond, Triangle, Moon, Square, Cross, Skull + static constexpr uint32_t kRaidMarkCount = 8; + // Returns the GUID marked with the given icon (0 = no mark) + uint64_t getRaidMarkGuid(uint32_t icon) const { + return (icon < kRaidMarkCount) ? raidTargetGuids_[icon] : 0; + } + // Returns the raid mark icon for a given guid (0xFF = no mark) + uint8_t getEntityRaidMark(uint64_t guid) const { + if (guid == 0) return 0xFF; + for (uint32_t i = 0; i < kRaidMarkCount; ++i) + if (raidTargetGuids_[i] == guid) return static_cast(i); + return 0xFF; + } + // ---- LFG / Dungeon Finder ---- enum class LfgState : uint8_t { None = 0, @@ -1864,6 +1879,9 @@ private: uint32_t instanceDifficulty_ = 0; bool instanceIsHeroic_ = false; + // Raid target markers (icon 0-7 -> guid; 0 = empty slot) + std::array raidTargetGuids_ = {}; + // Mirror timers (0=fatigue, 1=breath, 2=feigndeath) MirrorTimer mirrorTimers_[3]; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 2b995906..20da8625 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -3652,8 +3652,33 @@ void GameHandler::handlePacket(network::Packet& packet) { } break; } - case Opcode::MSG_RAID_TARGET_UPDATE: + case Opcode::MSG_RAID_TARGET_UPDATE: { + // uint8 type: 0 = full update (8 × (uint8 icon + uint64 guid)), + // 1 = single update (uint8 icon + uint64 guid) + size_t remRTU = packet.getSize() - packet.getReadPos(); + if (remRTU < 1) break; + uint8_t rtuType = packet.readUInt8(); + if (rtuType == 0) { + // Full update: always 8 entries + for (uint32_t i = 0; i < kRaidMarkCount; ++i) { + if (packet.getSize() - packet.getReadPos() < 9) break; + uint8_t icon = packet.readUInt8(); + uint64_t guid = packet.readUInt64(); + if (icon < kRaidMarkCount) + raidTargetGuids_[icon] = guid; + } + } else { + // Single update + if (packet.getSize() - packet.getReadPos() >= 9) { + uint8_t icon = packet.readUInt8(); + uint64_t guid = packet.readUInt64(); + if (icon < kRaidMarkCount) + raidTargetGuids_[icon] = guid; + } + } + LOG_DEBUG("MSG_RAID_TARGET_UPDATE: type=", static_cast(rtuType)); break; + } case Opcode::SMSG_BUY_ITEM: { // uint64 vendorGuid + uint32 vendorSlot + int32 newCount + uint32 itemCount // Confirms a successful CMSG_BUY_ITEM. The inventory update arrives via SMSG_UPDATE_OBJECT. @@ -5985,8 +6010,9 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) { mountCallback_(0); } - // Clear boss encounter unit slots on world transfer + // Clear boss encounter unit slots and raid marks on world transfer encounterUnitGuids_.fill(0); + raidTargetGuids_.fill(0); // Suppress area triggers on initial login — prevents exit portals from // immediately firing when spawning inside a dungeon/instance. diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index e69cf7f9..bded7481 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -2065,11 +2065,31 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) { ImGui::PushStyleColor(ImGuiCol_Border, borderColor); if (ImGui::Begin("##TargetFrame", nullptr, flags)) { + // Raid mark icon (Star/Circle/Diamond/Triangle/Moon/Square/Cross/Skull) + static const struct { const char* sym; ImU32 col; } kRaidMarks[] = { + { "\xe2\x98\x85", IM_COL32(255, 220, 50, 255) }, // 0 Star (yellow) + { "\xe2\x97\x8f", IM_COL32(255, 140, 0, 255) }, // 1 Circle (orange) + { "\xe2\x97\x86", IM_COL32(160, 32, 240, 255) }, // 2 Diamond (purple) + { "\xe2\x96\xb2", IM_COL32( 50, 200, 50, 255) }, // 3 Triangle (green) + { "\xe2\x97\x8c", IM_COL32( 80, 160, 255, 255) }, // 4 Moon (blue) + { "\xe2\x96\xa0", IM_COL32( 50, 200, 220, 255) }, // 5 Square (teal) + { "\xe2\x9c\x9d", IM_COL32(255, 80, 80, 255) }, // 6 Cross (red) + { "\xe2\x98\xa0", IM_COL32(255, 255, 255, 255) }, // 7 Skull (white) + }; + uint8_t mark = gameHandler.getEntityRaidMark(target->getGuid()); + if (mark < game::GameHandler::kRaidMarkCount) { + ImGui::GetWindowDrawList()->AddText( + ImGui::GetCursorScreenPos(), + kRaidMarks[mark].col, kRaidMarks[mark].sym); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 18.0f); + } + // Entity name and type std::string name = getEntityName(target); ImVec4 nameColor = hostileColor; + ImGui::SameLine(0.0f, 0.0f); ImGui::TextColored(nameColor, "%s", name.c_str()); // Level (for units/players) — colored by difficulty @@ -4893,6 +4913,26 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) { drawList->AddText(ImVec2(nameX + 1.0f, nameY + 1.0f), IM_COL32(0, 0, 0, A(160)), labelBuf); drawList->AddText(ImVec2(nameX, nameY), nameColor, labelBuf); + // Raid mark (if any) to the left of the name + { + static const struct { const char* sym; ImU32 col; } kNPMarks[] = { + { "\xe2\x98\x85", IM_COL32(255,220, 50,230) }, // Star + { "\xe2\x97\x8f", IM_COL32(255,140, 0,230) }, // Circle + { "\xe2\x97\x86", IM_COL32(160, 32,240,230) }, // Diamond + { "\xe2\x96\xb2", IM_COL32( 50,200, 50,230) }, // Triangle + { "\xe2\x97\x8c", IM_COL32( 80,160,255,230) }, // Moon + { "\xe2\x96\xa0", IM_COL32( 50,200,220,230) }, // Square + { "\xe2\x9c\x9d", IM_COL32(255, 80, 80,230) }, // Cross + { "\xe2\x98\xa0", IM_COL32(255,255,255,230) }, // Skull + }; + uint8_t raidMark = gameHandler.getEntityRaidMark(guid); + if (raidMark < game::GameHandler::kRaidMarkCount) { + float markX = nameX - 14.0f; + drawList->AddText(ImVec2(markX + 1.0f, nameY + 1.0f), IM_COL32(0,0,0,120), kNPMarks[raidMark].sym); + drawList->AddText(ImVec2(markX, nameY), kNPMarks[raidMark].col, kNPMarks[raidMark].sym); + } + } + // Click to target: detect left-click inside the combined nameplate region if (!ImGui::GetIO().WantCaptureMouse && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { ImVec2 mouse = ImGui::GetIO().MousePos;