using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; namespace Barotrauma { class PathNode { private WayPoint wayPoint; private int wayPointID; public int state; public PathNode Parent; private Vector2 position; public float F,G,H; public List connections; public List distances; public WayPoint Waypoint { get { return wayPoint; } } public Vector2 Position { get {return position;} } public PathNode(WayPoint wayPoint) { this.wayPoint = wayPoint; this.position = wayPoint.SimPosition; wayPointID = wayPoint.ID; connections = new List(); } public static List GenerateNodes(List wayPoints) { var nodes = new Dictionary(); foreach (WayPoint wayPoint in wayPoints) { nodes.Add(wayPoint.ID, new PathNode(wayPoint)); } foreach (KeyValuePair node in nodes) { foreach (MapEntity linked in node.Value.wayPoint.linkedTo) { PathNode connectedNode = null; nodes.TryGetValue(linked.ID, out connectedNode); if (connectedNode == null) continue; node.Value.connections.Add(connectedNode); } } var nodeList = nodes.Values.ToList(); nodeList.RemoveAll(n => n.connections.Count == 0); foreach (PathNode node in nodeList) { node.distances = new List(); for (int i = 0; i< node.connections.Count; i++) { node.distances.Add(Vector2.Distance(node.position, node.connections[i].position)); } } return nodeList; } } class PathFinder { public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode); public GetNodePenaltyHandler GetNodePenalty; private List nodes; private bool insideSubmarine; public PathFinder(List wayPoints, bool insideSubmarine = false) { nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.MoveWithLevel != insideSubmarine)); foreach (WayPoint wp in wayPoints) { wp.linkedTo.CollectionChanged += WaypointLinksChanged; } this.insideSubmarine = insideSubmarine; } void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (Submarine.Unloading) return; var waypoints = sender as IEnumerable; foreach (MapEntity me in waypoints) { WayPoint wp = me as WayPoint; if (me == null) continue; var node = nodes.Find(n => n.Waypoint == wp); if (node == null) return; if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove) { for (int i = node.connections.Count - 1; i >= 0; i--) { //remove connection if the waypoint isn't connected anymore if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null) { node.connections.RemoveAt(i); node.distances.RemoveAt(i); } } } else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { for (int i = 0; i < wp.linkedTo.Count; i++) { WayPoint connected = wp.linkedTo[i] as WayPoint; if (connected == null) continue; //already connected, continue if (node.connections.Any(n => n.Waypoint == connected)) continue; var matchingNode = nodes.Find(n => n.Waypoint == connected); if (matchingNode == null) { #if DEBUG DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder"); #endif return; } node.connections.Add(matchingNode); node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position)); } } } } public SteeringPath FindPath(Vector2 start, Vector2 end) { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); float closestDist = 0.0f; PathNode startNode = null; foreach (PathNode node in nodes) { Vector2 nodePos = node.Position; float dist = System.Math.Abs(start.X - nodePos.X) + System.Math.Abs(start.Y - nodePos.Y) * 10.0f; //higher cost for vertical movement //prefer nodes that are closer to the end position dist += Vector2.Distance(end, nodePos) / 10.0f; if (dist finalPath = new List(); PathNode pathNode = end; while (pathNode != start && pathNode != null) { finalPath.Add(pathNode.Waypoint); //there was one bug report that seems to have been caused by this loop never terminating: //couldn't reproduce or figure out what caused it, but here's a workaround that prevents the game from crashing in case it happens again if (finalPath.Count > nodes.Count) { DebugConsole.ThrowError("Pathfinding error: constructing final path failed"); return new SteeringPath(true); } path.Cost += pathNode.F; pathNode = pathNode.Parent; } finalPath.Add(start.Waypoint); finalPath.Reverse(); foreach (WayPoint wayPoint in finalPath) { path.AddNode(wayPoint); } return path; } } }