From d60d296b77a0f1c2a45b86619b7a09213c549787 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 17 Mar 2026 19:01:03 -0700 Subject: [PATCH] feat: show discovered taxi nodes as markers on the world map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add gold diamond markers for every flight master the player has already discovered (knownTaxiMask_), read from TaxiNodes.dbc and filtered to the current continent/map being displayed: - WorldMapTaxiNode struct carries canonical WoW coords + known flag - WorldMap::setTaxiNodes() accepts the per-frame list from game_screen - renderImGuiOverlay() projects each known node to UV, draws a gold diamond (AddQuadFilled) with a dark outline, and shows the node name as a tooltip on hover - GameHandler::isKnownTaxiNode(id) checks knownTaxiMask_[] efficiently - Markers update live — newly discovered nodes appear without reopening the map --- include/game/game_handler.hpp | 5 +++++ include/rendering/world_map.hpp | 14 +++++++++++++ src/rendering/world_map.cpp | 37 +++++++++++++++++++++++++++++++++ src/ui/game_screen.cpp | 19 +++++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index e8762167..5abce0ed 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1919,6 +1919,11 @@ public: float x = 0, y = 0, z = 0; }; const std::unordered_map& getTaxiNodes() const { return taxiNodes_; } + bool isKnownTaxiNode(uint32_t nodeId) const { + if (nodeId == 0 || nodeId > 384) return false; + uint32_t idx = nodeId - 1; + return (knownTaxiMask_[idx / 32] & (1u << (idx % 32))) != 0; + } uint32_t getTaxiCostTo(uint32_t destNodeId) const; bool taxiNpcHasRoutes(uint64_t guid) const { auto it = taxiNpcHasRoutes_.find(guid); diff --git a/include/rendering/world_map.hpp b/include/rendering/world_map.hpp index e908ffaa..cfbf5595 100644 --- a/include/rendering/world_map.hpp +++ b/include/rendering/world_map.hpp @@ -25,6 +25,15 @@ struct WorldMapPartyDot { std::string name; ///< Member name (shown as tooltip on hover) }; +/// Taxi (flight master) node passed from the UI layer for world map overlay. +struct WorldMapTaxiNode { + uint32_t id = 0; ///< TaxiNodes.dbc ID + uint32_t mapId = 0; ///< WoW internal map ID (0=EK,1=Kal,530=Outland,571=Northrend) + float wowX = 0, wowY = 0, wowZ = 0; ///< Canonical WoW coordinates + std::string name; ///< Node name (shown as tooltip) + bool known = false; ///< Player has discovered this node +}; + struct WorldMapZone { uint32_t wmaID = 0; uint32_t areaID = 0; // 0 = continent level @@ -57,6 +66,7 @@ public: void setMapName(const std::string& name); 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); } bool isOpen() const { return open; } void close() { open = false; } @@ -127,6 +137,10 @@ private: // Party member dots (set each frame from the UI layer) std::vector partyDots_; + // Taxi node markers (set each frame from the UI layer) + std::vector taxiNodes_; + int currentMapId_ = -1; ///< WoW map ID currently loaded (set in loadZonesFromDBC) + // Exploration / fog of war std::vector serverExplorationMask; bool hasServerExplorationMask = false; diff --git a/src/rendering/world_map.cpp b/src/rendering/world_map.cpp index 8163628d..2257ded9 100644 --- a/src/rendering/world_map.cpp +++ b/src/rendering/world_map.cpp @@ -371,6 +371,7 @@ void WorldMap::loadZonesFromDBC() { } } + currentMapId_ = mapID; LOG_INFO("WorldMap: loaded ", zones.size(), " zones for mapID=", mapID, ", continentIdx=", continentIdx); } @@ -1059,6 +1060,42 @@ void WorldMap::renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWi } } + // Taxi node markers — flight master icons on the map + if (currentIdx >= 0 && viewLevel != ViewLevel::WORLD && !taxiNodes_.empty()) { + ImVec2 mp = ImGui::GetMousePos(); + for (const auto& node : taxiNodes_) { + if (!node.known) continue; + if (static_cast(node.mapId) != currentMapId_) continue; + + glm::vec3 rPos = core::coords::canonicalToRender( + glm::vec3(node.wowX, node.wowY, node.wowZ)); + 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; + + // Flight-master icon: yellow diamond with dark border + constexpr float H = 5.0f; // half-size of diamond + ImVec2 top2(px, py - H); + ImVec2 right2(px + H, py ); + ImVec2 bot2(px, py + H); + ImVec2 left2(px - H, py ); + drawList->AddQuadFilled(top2, right2, bot2, left2, + IM_COL32(255, 215, 0, 230)); + drawList->AddQuad(top2, right2, bot2, left2, + IM_COL32(80, 50, 0, 200), 1.2f); + + // Tooltip on hover + if (!node.name.empty()) { + float mdx = mp.x - px, mdy = mp.y - py; + if (mdx * mdx + mdy * mdy < 49.0f) { + ImGui::SetTooltip("%s\n(Flight Master)", node.name.c_str()); + } + } + } + } + // Hover coordinate display — show WoW coordinates under cursor if (currentIdx >= 0 && viewLevel != ViewLevel::WORLD) { auto& io = ImGui::GetIO(); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 556f3917..ea755177 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -7033,6 +7033,25 @@ void GameScreen::renderWorldMap(game::GameHandler& gameHandler) { wm->setPartyDots(std::move(dots)); } + // Taxi node markers on world map + { + std::vector taxiNodes; + const auto& nodes = gameHandler.getTaxiNodes(); + taxiNodes.reserve(nodes.size()); + for (const auto& [id, node] : nodes) { + rendering::WorldMapTaxiNode wtn; + wtn.id = node.id; + wtn.mapId = node.mapId; + wtn.wowX = node.x; + wtn.wowY = node.y; + wtn.wowZ = node.z; + wtn.name = node.name; + wtn.known = gameHandler.isKnownTaxiNode(id); + taxiNodes.push_back(std::move(wtn)); + } + wm->setTaxiNodes(std::move(taxiNodes)); + } + glm::vec3 playerPos = renderer->getCharacterPosition(); float playerYaw = renderer->getCharacterYaw(); auto* window = app.getWindow();