| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- using Content.Shared.NPC;
- using Robust.Shared.Map;
- using Robust.Shared.Utility;
- namespace Content.Server.NPC.Pathfinding;
- public sealed partial class PathfindingSystem
- {
- private PathResult UpdateAStarPath(AStarPathRequest request)
- {
- if (request.Start.Equals(request.End))
- {
- return PathResult.Path;
- }
- if (request.Task.IsCanceled)
- {
- return PathResult.NoPath;
- }
- // TODO: Need partial planning that uses best node.
- PathPoly? currentNode = null;
- // First run
- if (!request.Started)
- {
- request.Frontier = new PriorityQueue<(float, PathPoly)>(PathPolyComparer);
- request.Started = true;
- }
- // Re-validate nodes
- else
- {
- // Theoretically this shouldn't be happening, but practically...
- if (request.Frontier.Count == 0)
- {
- return PathResult.NoPath;
- }
- (_, currentNode) = request.Frontier.Peek();
- if (!currentNode.IsValid())
- {
- return PathResult.NoPath;
- }
- // Re-validate parents too.
- if (request.CameFrom.TryGetValue(currentNode, out var parentNode) && !parentNode.IsValid())
- {
- return PathResult.NoPath;
- }
- }
- DebugTools.Assert(!request.Task.IsCompleted);
- request.Stopwatch.Restart();
- var startNode = GetPoly(request.Start);
- var endNode = GetPoly(request.End);
- if (startNode == null || endNode == null)
- {
- return PathResult.NoPath;
- }
- currentNode = startNode;
- request.Frontier.Add((0.0f, startNode));
- request.CostSoFar[startNode] = 0.0f;
- var count = 0;
- var arrived = false;
- while (request.Frontier.Count > 0 && count < NodeLimit)
- {
- // Handle whether we need to pause if we've taken too long
- if (count % 20 == 0 && count > 0 && request.Stopwatch.Elapsed > PathTime)
- {
- // I had this happen once in testing but I don't think it should be possible?
- DebugTools.Assert(request.Frontier.Count > 0);
- return PathResult.Continuing;
- }
- count++;
- // Actual pathfinding here
- (_, currentNode) = request.Frontier.Take();
- // If we're inside the required distance OR we're at the end node.
- if ((request.Distance > 0f &&
- currentNode.Coordinates.TryDistance(EntityManager, request.End, out var distance) &&
- distance <= request.Distance) ||
- currentNode.Equals(endNode))
- {
- arrived = true;
- break;
- }
- foreach (var neighbor in currentNode.Neighbors)
- {
- var tileCost = GetTileCost(request, currentNode, neighbor);
- if (tileCost.Equals(0f))
- {
- continue;
- }
- // f = g + h
- // gScore is distance to the start node
- // hScore is distance to the end node
- var gScore = request.CostSoFar[currentNode] + tileCost;
- if (request.CostSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue)
- {
- continue;
- }
- request.CameFrom[neighbor] = currentNode;
- request.CostSoFar[neighbor] = gScore;
- // pFactor is tie-breaker where the fscore is otherwise equal.
- // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
- // There's other ways to do it but future consideration
- // The closer the fScore is to the actual distance then the better the pathfinder will be
- // (i.e. somewhere between 1 and infinite)
- // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now.
- var hScore = OctileDistance(endNode, neighbor) * (1.0f + 1.0f / 1000.0f);
- var fScore = gScore + hScore;
- request.Frontier.Add((fScore, neighbor));
- }
- }
- if (!arrived)
- {
- return PathResult.NoPath;
- }
- var route = ReconstructPath(request.CameFrom, currentNode);
- var path = new Queue<EntityCoordinates>(route.Count);
- foreach (var node in route)
- {
- // Due to partial planning some nodes may have been invalidated.
- if (!node.IsValid())
- {
- return PathResult.NoPath;
- }
- path.Enqueue(node.Coordinates);
- }
- DebugTools.Assert(route.Count > 0);
- request.Polys = route;
- return PathResult.Path;
- }
- }
|