/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "TaxiPathGraph.h" #include "DB2Stores.h" #include "MapUtils.h" #include "ObjectMgr.h" #include "Player.h" #include #include #include #include namespace { struct EdgeCost { TaxiNodesEntry const* To; uint32 Distance; uint32 EvaluateDistance(Player const* player) const { bool isVisibleForFaction = [&] { switch (player->GetTeam()) { case HORDE: return To->GetFlags().HasFlag(TaxiNodeFlags::ShowOnHordeMap); case ALLIANCE: return To->GetFlags().HasFlag(TaxiNodeFlags::ShowOnAllianceMap); default: break; } return false; }(); if (!isVisibleForFaction) return std::numeric_limits::max(); if (!ConditionMgr::IsPlayerMeetingCondition(player, To->ConditionID)) return std::numeric_limits::max(); return Distance; } }; typedef boost::adjacency_list, boost::property> Graph; typedef boost::property_map::type WeightMap; typedef Graph::vertex_descriptor vertex_descriptor; typedef Graph::edge_descriptor edge_descriptor; typedef std::pair edge; Graph m_graph; std::vector m_nodesByVertex; std::unordered_map m_verticesByNode; void GetTaxiMapPosition(DBCPosition3D const& position, int32 mapId, DBCPosition2D* uiMapPosition, uint32* uiMapId) { if (!DB2Manager::GetUiMapPosition(position.X, position.Y, position.Z, mapId, 0, 0, 0, UI_MAP_SYSTEM_ADVENTURE, false, uiMapId, uiMapPosition)) DB2Manager::GetUiMapPosition(position.X, position.Y, position.Z, mapId, 0, 0, 0, UI_MAP_SYSTEM_TAXI, false, uiMapId, uiMapPosition); } vertex_descriptor CreateVertexFromFromNodeInfoIfNeeded(TaxiNodesEntry const* node) { auto itr = m_verticesByNode.find(node->ID); if (itr == m_verticesByNode.end()) { itr = m_verticesByNode.emplace(node->ID, m_nodesByVertex.size()).first; m_nodesByVertex.push_back(node); } return itr->second; } void AddVerticeAndEdgeFromNodeInfo(TaxiNodesEntry const* from, TaxiNodesEntry const* to, uint32 pathId, std::vector>& edges) { if (from != to) { vertex_descriptor fromVertexID = CreateVertexFromFromNodeInfoIfNeeded(from); vertex_descriptor toVertexID = CreateVertexFromFromNodeInfoIfNeeded(to); float totalDist = 0.0f; TaxiPathNodeList const& nodes = sTaxiPathNodesByPath[pathId]; if (nodes.size() < 2) { edges.emplace_back(edge(fromVertexID, toVertexID), EdgeCost{ to, 0xFFFF }); return; } std::size_t last = nodes.size(); std::size_t first = 0; if (nodes.size() > 2) { --last; ++first; } for (std::size_t i = first + 1; i < last; ++i) { if (nodes[i - 1]->Flags & TAXI_PATH_NODE_FLAG_TELEPORT) continue; uint32 uiMap1, uiMap2; DBCPosition2D pos1, pos2; GetTaxiMapPosition(nodes[i - 1]->Loc, nodes[i - 1]->ContinentID, &pos1, &uiMap1); GetTaxiMapPosition(nodes[i]->Loc, nodes[i]->ContinentID, &pos2, &uiMap2); if (uiMap1 != uiMap2) continue; totalDist += std::sqrt( std::pow(pos2.X - pos1.X, 2) + std::pow(pos2.Y - pos1.Y, 2)); } uint32 dist = uint32(totalDist * 32767.0f); if (dist > 0xFFFF) dist = 0xFFFF; edges.emplace_back(edge(fromVertexID, toVertexID), EdgeCost{ to, dist }); } } vertex_descriptor const* GetVertexIDFromNodeID(TaxiNodesEntry const* node) { return Trinity::Containers::MapGetValuePtr(m_verticesByNode, node->ID); } uint32 GetNodeIDFromVertexID(vertex_descriptor vertexID) { if (vertexID < m_nodesByVertex.size()) return m_nodesByVertex[vertexID]->ID; return std::numeric_limits::max(); } template struct DiscoverVertexVisitor : public boost::base_visitor> { using event_filter = boost::on_discover_vertex; DiscoverVertexVisitor(T&& func) : _func(std::forward(func)) { } template void operator()(Vertex v, Graph& /*g*/) { _func(v); } private: T _func; }; template auto make_discover_vertex_dfs_visitor(T&& t) { return boost::make_dfs_visitor(DiscoverVertexVisitor(std::forward(t))); } } void TaxiPathGraph::Initialize() { if (boost::num_vertices(m_graph) > 0) return; std::vector> edges; // Initialize here for (TaxiPathEntry const* path : sTaxiPathStore) { TaxiNodesEntry const* from = sTaxiNodesStore.LookupEntry(path->FromTaxiNode); TaxiNodesEntry const* to = sTaxiNodesStore.LookupEntry(path->ToTaxiNode); if (from && to && from->IsPartOfTaxiNetwork() && to->IsPartOfTaxiNetwork()) AddVerticeAndEdgeFromNodeInfo(from, to, path->ID, edges); } // create graph m_graph = Graph(m_nodesByVertex.size()); WeightMap weightmap = boost::get(boost::edge_weight, m_graph); for (std::size_t j = 0; j < edges.size(); ++j) { edge_descriptor e = boost::add_edge(edges[j].first.first, edges[j].first.second, m_graph).first; weightmap[e] = edges[j].second; } } std::size_t TaxiPathGraph::GetCompleteNodeRoute(TaxiNodesEntry const* from, TaxiNodesEntry const* to, Player const* player, std::vector& shortestPath) { /* Information about node algorithm from client Since client does not give information about *ALL* nodes you have to pass by when going from sourceNodeID to destinationNodeID, we need to use Dijkstra algorithm. Examining several paths I discovered the following algorithm: * If destinationNodeID has is the next destination, connected directly to sourceNodeID, then, client just pick up this route regardless of distance * else we use dijkstra to find the shortest path. * When early landing is requested, according to behavior on retail, you can never end in a node you did not discovered before */ // Find if we have a direct path uint32 pathId, goldCost; sObjectMgr->GetTaxiPath(from->ID, to->ID, pathId, goldCost); if (pathId) shortestPath = { from->ID, to->ID }; else { shortestPath.clear(); vertex_descriptor const* fromVertexId = GetVertexIDFromNodeID(from); vertex_descriptor const* toVertexId = GetVertexIDFromNodeID(to); if (fromVertexId && toVertexId) { std::vector p(boost::num_vertices(m_graph)); std::vector d(boost::num_vertices(m_graph)); boost::dijkstra_shortest_paths(m_graph, *fromVertexId, boost::predecessor_map(boost::make_iterator_property_map(p.begin(), boost::get(boost::vertex_index, m_graph))) .distance_map(boost::make_iterator_property_map(d.begin(), boost::get(boost::vertex_index, m_graph))) .vertex_index_map(boost::get(boost::vertex_index, m_graph)) .distance_compare(std::less()) .distance_combine(boost::closed_plus()) .distance_inf(std::numeric_limits::max()) .distance_zero(0) .visitor(boost::dijkstra_visitor()) .weight_map(boost::make_transform_value_property_map( [player](EdgeCost const& edgeCost) { return edgeCost.EvaluateDistance(player); }, boost::get(boost::edge_weight, m_graph)))); // found a path to the goal for (vertex_descriptor v = *toVertexId; ; v = p[v]) { shortestPath.push_back(GetNodeIDFromVertexID(v)); if (v == p[v]) break; } std::reverse(shortestPath.begin(), shortestPath.end()); } } return shortestPath.size(); } void TaxiPathGraph::GetReachableNodesMask(TaxiNodesEntry const* from, TaxiMask* mask) { vertex_descriptor const* vertexId = GetVertexIDFromNodeID(from); if (!vertexId) return; boost::vector_property_map color(boost::num_vertices(m_graph)); std::fill(color.storage_begin(), color.storage_end(), boost::white_color); boost::depth_first_visit(m_graph, *vertexId, make_discover_vertex_dfs_visitor([mask](vertex_descriptor vertex) { if (TaxiNodesEntry const* taxiNode = sTaxiNodesStore.LookupEntry(GetNodeIDFromVertexID(vertex))) (*mask)[(taxiNode->ID - 1) / 8] |= 1 << ((taxiNode->ID - 1) % 8); }), color); }