1
0

PathfindingSystem.Simple.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. namespace Content.Server.NPC.Pathfinding;
  2. public sealed partial class PathfindingSystem
  3. {
  4. /// <summary>
  5. /// Pathfinding args for a 1-1 path.
  6. /// </summary>
  7. public record struct SimplePathArgs()
  8. {
  9. public Vector2i Start;
  10. public Vector2i End;
  11. public bool Diagonals = false;
  12. public int Limit = 10000;
  13. /// <summary>
  14. /// Custom tile-costs if applicable.
  15. /// </summary>
  16. public Func<Vector2i, float>? TileCost;
  17. }
  18. public record struct SimplePathResult
  19. {
  20. public static SimplePathResult NoPath = new();
  21. public List<Vector2i> Path;
  22. public Dictionary<Vector2i, Vector2i> CameFrom;
  23. }
  24. /// <summary>
  25. /// Gets simple A* path from start to end. Can also supply an optional tile-cost for tiles.
  26. /// </summary>
  27. public SimplePathResult GetPath(SimplePathArgs args)
  28. {
  29. var cameFrom = new Dictionary<Vector2i, Vector2i>();
  30. var costSoFar = new Dictionary<Vector2i, float>();
  31. var frontier = new PriorityQueue<Vector2i, float>();
  32. costSoFar[args.Start] = 0f;
  33. frontier.Enqueue(args.Start, 0f);
  34. var count = 0;
  35. while (frontier.TryDequeue(out var node, out _) && count < args.Limit)
  36. {
  37. count++;
  38. if (node == args.End)
  39. {
  40. // Found target
  41. var path = ReconstructPath(args.End, cameFrom);
  42. return new SimplePathResult()
  43. {
  44. CameFrom = cameFrom,
  45. Path = path,
  46. };
  47. }
  48. var gCost = costSoFar[node];
  49. if (args.Diagonals)
  50. {
  51. for (var x = -1; x <= 1; x++)
  52. {
  53. for (var y = -1; y <= 1; y++)
  54. {
  55. var neighbor = node + new Vector2i(x, y);
  56. var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f;
  57. if (neighborCost.Equals(0f))
  58. {
  59. continue;
  60. }
  61. // f = g + h
  62. // gScore is distance to the start node
  63. // hScore is distance to the end node
  64. var gScore = gCost + neighborCost;
  65. // Slower to get here so just ignore it.
  66. if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue)
  67. {
  68. continue;
  69. }
  70. cameFrom[neighbor] = node;
  71. costSoFar[neighbor] = gScore;
  72. // pFactor is tie-breaker where the fscore is otherwise equal.
  73. // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
  74. // There's other ways to do it but future consideration
  75. // The closer the fScore is to the actual distance then the better the pathfinder will be
  76. // (i.e. somewhere between 1 and infinite)
  77. // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now.
  78. var hScore = OctileDistance(args.End, neighbor) * (1.0f + 1.0f / 1000.0f);
  79. var fScore = gScore + hScore;
  80. frontier.Enqueue(neighbor, fScore);
  81. }
  82. }
  83. }
  84. else
  85. {
  86. for (var x = -1; x <= 1; x++)
  87. {
  88. for (var y = -1; y <= 1; y++)
  89. {
  90. if (x != 0 && y != 0)
  91. continue;
  92. var neighbor = node + new Vector2i(x, y);
  93. var neighborCost = ManhattanDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f;
  94. if (neighborCost.Equals(0f))
  95. continue;
  96. var gScore = gCost + neighborCost;
  97. if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue)
  98. continue;
  99. cameFrom[neighbor] = node;
  100. costSoFar[neighbor] = gScore;
  101. // Still use octile even for manhattan distance.
  102. var hScore = OctileDistance(args.End, neighbor) * 1.001f;
  103. var fScore = gScore + hScore;
  104. frontier.Enqueue(neighbor, fScore);
  105. }
  106. }
  107. }
  108. }
  109. return SimplePathResult.NoPath;
  110. }
  111. private List<Vector2i> ReconstructPath(Vector2i end, Dictionary<Vector2i, Vector2i> cameFrom)
  112. {
  113. var path = new List<Vector2i>()
  114. {
  115. end,
  116. };
  117. var node = end;
  118. while (cameFrom.TryGetValue(node, out var source))
  119. {
  120. path.Add(source);
  121. node = source;
  122. }
  123. path.Reverse();
  124. return path;
  125. }
  126. }