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;