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.
This commit is contained in:
Kelsi 2026-03-20 15:00:29 -07:00
parent 3790adfa06
commit 5eaf738b66
3 changed files with 58 additions and 0 deletions

View file

@ -67,6 +67,13 @@ public:
void setServerExplorationMask(const std::vector<uint32_t>& masks, bool hasData);
void setPartyDots(std::vector<WorldMapPartyDot> dots) { partyDots_ = std::move(dots); }
void setTaxiNodes(std::vector<WorldMapTaxiNode> 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<QuestPoi> 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<WorldMapTaxiNode> taxiNodes_;
int currentMapId_ = -1; ///< WoW map ID currently loaded (set in loadZonesFromDBC)
// Quest POI markers (set each frame from the UI layer)
std::vector<QuestPoi> questPois_;
// Corpse marker (ghost state — set each frame from the UI layer)
bool hasCorpse_ = false;
glm::vec3 corpseRenderPos_ = {};

View file

@ -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);

View file

@ -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<rendering::WorldMap::QuestPoi> 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;