From 5eaf738b669f3354aa8549569c4a8c53ac49fe6b Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 20 Mar 2026 15:00:29 -0700 Subject: [PATCH] feat: show quest POI markers on the world map overlay Add QuestPoi struct and setQuestPois() to WorldMap, render quest objective markers as cyan circles with golden outlines and quest title labels. Wire gossipPois_ (from SMSG_QUEST_POI_QUERY_RESPONSE) through GameScreen to the world map so quest objectives are visible alongside party dots and taxi nodes. --- include/rendering/world_map.hpp | 10 ++++++++++ src/rendering/world_map.cpp | 35 +++++++++++++++++++++++++++++++++ src/ui/game_screen.cpp | 13 ++++++++++++ 3 files changed, 58 insertions(+) diff --git a/include/rendering/world_map.hpp b/include/rendering/world_map.hpp index eedc88af..fee98b6d 100644 --- a/include/rendering/world_map.hpp +++ b/include/rendering/world_map.hpp @@ -67,6 +67,13 @@ public: void setServerExplorationMask(const std::vector& masks, bool hasData); void setPartyDots(std::vector dots) { partyDots_ = std::move(dots); } void setTaxiNodes(std::vector nodes) { taxiNodes_ = std::move(nodes); } + + /// Quest POI marker for world map overlay (from SMSG_QUEST_POI_QUERY_RESPONSE). + struct QuestPoi { + float wowX = 0, wowY = 0; ///< Canonical WoW coordinates (centroid of POI area) + std::string name; ///< Quest title + }; + void setQuestPois(std::vector pois) { questPois_ = std::move(pois); } /// Set the player's corpse position for overlay rendering. /// @param hasCorpse True when the player is a ghost with an unclaimed corpse on this map. /// @param renderPos Corpse position in render-space coordinates. @@ -148,6 +155,9 @@ private: std::vector taxiNodes_; int currentMapId_ = -1; ///< WoW map ID currently loaded (set in loadZonesFromDBC) + // Quest POI markers (set each frame from the UI layer) + std::vector questPois_; + // Corpse marker (ghost state — set each frame from the UI layer) bool hasCorpse_ = false; glm::vec3 corpseRenderPos_ = {}; diff --git a/src/rendering/world_map.cpp b/src/rendering/world_map.cpp index cc278b5f..6b1710c4 100644 --- a/src/rendering/world_map.cpp +++ b/src/rendering/world_map.cpp @@ -1096,6 +1096,41 @@ void WorldMap::renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWi } } + // Quest POI markers — golden exclamation marks / question marks + if (currentIdx >= 0 && viewLevel != ViewLevel::WORLD && !questPois_.empty()) { + ImVec2 mp = ImGui::GetMousePos(); + ImFont* qFont = ImGui::GetFont(); + for (const auto& qp : questPois_) { + glm::vec3 rPos = core::coords::canonicalToRender( + glm::vec3(qp.wowX, qp.wowY, 0.0f)); + glm::vec2 uv = renderPosToMapUV(rPos, currentIdx); + if (uv.x < 0.0f || uv.x > 1.0f || uv.y < 0.0f || uv.y > 1.0f) continue; + + float px = imgMin.x + uv.x * displayW; + float py = imgMin.y + uv.y * displayH; + + // Cyan circle with golden ring (matches minimap POI style) + drawList->AddCircleFilled(ImVec2(px, py), 5.0f, IM_COL32(0, 210, 255, 220)); + drawList->AddCircle(ImVec2(px, py), 5.0f, IM_COL32(255, 215, 0, 220), 0, 1.5f); + + // Quest name label + if (!qp.name.empty()) { + ImVec2 nameSz = qFont->CalcTextSizeA(ImGui::GetFontSize() * 0.85f, FLT_MAX, 0.0f, qp.name.c_str()); + float tx = px - nameSz.x * 0.5f; + float ty = py - nameSz.y - 7.0f; + drawList->AddText(qFont, ImGui::GetFontSize() * 0.85f, + ImVec2(tx + 1.0f, ty + 1.0f), IM_COL32(0, 0, 0, 180), qp.name.c_str()); + drawList->AddText(qFont, ImGui::GetFontSize() * 0.85f, + ImVec2(tx, ty), IM_COL32(255, 230, 100, 230), qp.name.c_str()); + } + // Tooltip on hover + float mdx = mp.x - px, mdy = mp.y - py; + if (mdx * mdx + mdy * mdy < 49.0f && !qp.name.empty()) { + ImGui::SetTooltip("%s\n(Quest Objective)", qp.name.c_str()); + } + } + } + // Corpse marker — skull X shown when player is a ghost with unclaimed corpse if (hasCorpse_ && currentIdx >= 0 && viewLevel != ViewLevel::WORLD) { glm::vec2 uv = renderPosToMapUV(corpseRenderPos_, currentIdx); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 618a7b79..21e9a0b2 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -8539,6 +8539,19 @@ void GameScreen::renderWorldMap(game::GameHandler& gameHandler) { wm->setTaxiNodes(std::move(taxiNodes)); } + // Quest POI markers on world map (from SMSG_QUEST_POI_QUERY_RESPONSE / gossip POIs) + { + std::vector qpois; + for (const auto& poi : gameHandler.getGossipPois()) { + rendering::WorldMap::QuestPoi qp; + qp.wowX = poi.x; + qp.wowY = poi.y; + qp.name = poi.name; + qpois.push_back(std::move(qp)); + } + wm->setQuestPois(std::move(qpois)); + } + // Corpse marker: show skull X on world map when ghost with unclaimed corpse { float corpseCanX = 0.0f, corpseCanY = 0.0f;