/* * Copyright (C) 2012-2018 CypherCore * * 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 3 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 . */ using Framework.Constants; using Framework.GameMath; using Game.DataStorage; using System; using System.Collections.Generic; namespace Game.Entities { public class TaxiPathGraph : Singleton { TaxiPathGraph() { } public void Initialize() { if (GetVertexCount() > 0) return; List, uint>> edges = new List, uint>>(); // Initialize here foreach (TaxiPathRecord path in CliDB.TaxiPathStorage.Values) { TaxiNodesRecord from = CliDB.TaxiNodesStorage.LookupByKey(path.FromTaxiNode); TaxiNodesRecord to = CliDB.TaxiNodesStorage.LookupByKey(path.ToTaxiNode); if (from != null && to != null && from.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde) && to.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde)) AddVerticeAndEdgeFromNodeInfo(from, to, path.Id, edges); } // create graph m_graph = new EdgeWeightedDigraph(GetVertexCount()); for (int j = 0; j < edges.Count; ++j) { m_graph.AddEdge(new DirectedEdge(edges[j].Item1.Item1, edges[j].Item1.Item2, edges[j].Item2)); } } uint GetNodeIDFromVertexID(uint vertexID) { if (vertexID < m_vertices.Length) return m_vertices[vertexID].Id; return uint.MaxValue; } uint GetVertexIDFromNodeID(TaxiNodesRecord node) { return node.CharacterBitNumber; } int GetVertexCount() { if (m_graph == null) return m_vertices.Length; //So we can use this function for readability, we define either max defined vertices or already loaded in graph count return m_vertices.Length;// Math.Max(m_graph.getNumberOfVertices(), m_vertices.Length); } void AddVerticeAndEdgeFromNodeInfo(TaxiNodesRecord from, TaxiNodesRecord to, uint pathId, List, uint>> edges) { if (from.Id != to.Id) { uint fromVertexID = CreateVertexFromFromNodeInfoIfNeeded(from); uint toVertexID = CreateVertexFromFromNodeInfoIfNeeded(to); float totalDist = 0.0f; TaxiPathNodeRecord[] nodes = CliDB.TaxiPathNodesByPath[pathId]; if (nodes.Length < 2) { edges.Add(Tuple.Create(Tuple.Create(fromVertexID, toVertexID), 0xFFFFu)); return; } int last = nodes.Length; int first = 0; if (nodes.Length > 2) { --last; ++first; } for (int i = first + 1; i < last; ++i) { if (nodes[i - 1].Flags.HasAnyFlag(TaxiPathNodeFlags.Teleport)) continue; uint map1, map2; Vector2 pos1, pos2; Global.DB2Mgr.DeterminaAlternateMapPosition(nodes[i - 1].ContinentID, nodes[i - 1].Loc.X, nodes[i - 1].Loc.Y, nodes[i - 1].Loc.Z, out map1, out pos1); Global.DB2Mgr.DeterminaAlternateMapPosition(nodes[i].ContinentID, nodes[i].Loc.X, nodes[i].Loc.Y, nodes[i].Loc.Z, out map2, out pos2); if (map1 != map2) continue; totalDist += (float)Math.Sqrt((float)Math.Pow(pos2.X - pos1.X, 2) + (float)Math.Pow(pos2.Y - pos1.Y, 2) + (float)Math.Pow(nodes[i].Loc.Z - nodes[i - 1].Loc.Z, 2)); } uint dist = (uint)totalDist; if (dist > 0xFFFF) dist = 0xFFFF; edges.Add(Tuple.Create(Tuple.Create(fromVertexID, toVertexID), dist)); } } public int GetCompleteNodeRoute(TaxiNodesRecord from, TaxiNodesRecord to, Player player, List 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 uint pathId, goldCost; Global.ObjectMgr.GetTaxiPath(from.Id, to.Id, out pathId, out goldCost); if (pathId != 0) { shortestPath.Add(from.Id); shortestPath.Add(to.Id); } else { shortestPath.Clear(); // We want to use Dijkstra on this graph DijkstraShortestPath g = new DijkstraShortestPath(m_graph, (int)GetVertexIDFromNodeID(from)); var path = g.PathTo((int)GetVertexIDFromNodeID(to)); // found a path to the goal shortestPath.Add(from.Id); foreach (var edge in path) { //todo test me No clue about this.... var To = m_vertices[edge.To]; TaxiNodeFlags requireFlag = (player.GetTeam() == Team.Alliance) ? TaxiNodeFlags.Alliance : TaxiNodeFlags.Horde; if (!To.Flags.HasAnyFlag(requireFlag)) continue; PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(To.ConditionID); if (condition != null) if (!ConditionManager.IsPlayerMeetingCondition(player, condition)) continue; shortestPath.Add(GetNodeIDFromVertexID(edge.To)); } } return shortestPath.Count; } uint CreateVertexFromFromNodeInfoIfNeeded(TaxiNodesRecord node) { //Check if we need a new one or if it may be already created if (m_vertices.Length <= node.CharacterBitNumber) Array.Resize(ref m_vertices, (int)node.CharacterBitNumber + 1); m_vertices[node.CharacterBitNumber] = node; return node.CharacterBitNumber; } TaxiNodesRecord[] m_vertices = new TaxiNodesRecord[0]; EdgeWeightedDigraph m_graph; } }