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;