PathfindingSystem.Common.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using Content.Shared.Gravity;
  2. using Content.Shared.Maps;
  3. using Content.Shared.NPC;
  4. using Robust.Shared.Map.Components;
  5. using Robust.Shared.Spawners;
  6. namespace Content.Server.NPC.Pathfinding;
  7. public sealed partial class PathfindingSystem
  8. {
  9. /*
  10. * Code that is common to all pathfinding methods.
  11. */
  12. /// <summary>
  13. /// Maximum amount of nodes we're allowed to expand.
  14. /// </summary>
  15. private const int NodeLimit = 512;
  16. private sealed class PathComparer : IComparer<ValueTuple<float, PathPoly>>
  17. {
  18. public int Compare((float, PathPoly) x, (float, PathPoly) y)
  19. {
  20. return y.Item1.CompareTo(x.Item1);
  21. }
  22. }
  23. private static readonly PathComparer PathPolyComparer = new();
  24. private List<PathPoly> ReconstructPath(Dictionary<PathPoly, PathPoly> path, PathPoly currentNodeRef)
  25. {
  26. var running = new List<PathPoly> { currentNodeRef };
  27. while (path.ContainsKey(currentNodeRef))
  28. {
  29. var previousCurrent = currentNodeRef;
  30. currentNodeRef = path[currentNodeRef];
  31. path.Remove(previousCurrent);
  32. running.Add(currentNodeRef);
  33. }
  34. running.Reverse();
  35. return running;
  36. }
  37. private float GetTileCost(PathRequest request, PathPoly start, PathPoly end)
  38. {
  39. var modifier = 1f;
  40. // TODO
  41. if ((end.Data.Flags & PathfindingBreadcrumbFlag.Space) != 0x0)
  42. {
  43. return 0f;
  44. }
  45. if ((request.CollisionLayer & end.Data.CollisionMask) != 0x0 ||
  46. (request.CollisionMask & end.Data.CollisionLayer) != 0x0)
  47. {
  48. var isDoor = (end.Data.Flags & PathfindingBreadcrumbFlag.Door) != 0x0;
  49. var isAccess = (end.Data.Flags & PathfindingBreadcrumbFlag.Access) != 0x0;
  50. var isClimb = (end.Data.Flags & PathfindingBreadcrumbFlag.Climb) != 0x0;
  51. // TODO: Handling power + door prying
  52. // Door we should be able to open
  53. if (isDoor && !isAccess && (request.Flags & PathFlags.Interact) != 0x0)
  54. {
  55. modifier += 0.5f;
  56. }
  57. // Door we can force open one way or another
  58. else if (isDoor && isAccess && (request.Flags & PathFlags.Prying) != 0x0)
  59. {
  60. modifier += 10f;
  61. }
  62. else if ((request.Flags & PathFlags.Smashing) != 0x0 && end.Data.Damage > 0f)
  63. {
  64. modifier += 10f + end.Data.Damage / 100f;
  65. }
  66. else if (isClimb && (request.Flags & PathFlags.Climbing) != 0x0)
  67. {
  68. modifier += 0.5f;
  69. }
  70. else
  71. {
  72. return 0f;
  73. }
  74. }
  75. return modifier * OctileDistance(end, start);
  76. }
  77. #region Simplifier
  78. public List<PathPoly> Simplify(List<PathPoly> vertices, float tolerance = 0)
  79. {
  80. // TODO: Needs more work
  81. if (vertices.Count <= 3)
  82. return vertices;
  83. var simplified = new List<PathPoly>();
  84. for (var i = 0; i < vertices.Count; i++)
  85. {
  86. // No wraparound for negative sooooo
  87. var prev = vertices[i == 0 ? vertices.Count - 1 : i - 1];
  88. var current = vertices[i];
  89. var next = vertices[(i + 1) % vertices.Count];
  90. var prevData = prev.Data;
  91. var currentData = current.Data;
  92. var nextData = next.Data;
  93. // If they collinear, continue
  94. if (i != 0 && i != vertices.Count - 1 &&
  95. prevData.Equals(currentData) &&
  96. currentData.Equals(nextData) &&
  97. IsCollinear(prev, current, next, tolerance))
  98. {
  99. continue;
  100. }
  101. simplified.Add(current);
  102. }
  103. // Farseer didn't seem to handle straight lines and nuked all points
  104. if (simplified.Count == 0)
  105. {
  106. simplified.Add(vertices[0]);
  107. simplified.Add(vertices[^1]);
  108. }
  109. // Check LOS and cut out more nodes
  110. // TODO: Grid cast
  111. // https://github.com/recastnavigation/recastnavigation/blob/c5cbd53024c8a9d8d097a4371215e3342d2fdc87/Detour/Source/DetourNavMeshQuery.cpp#L2455
  112. // Essentially you just do a raycast but a specialised version.
  113. return simplified;
  114. }
  115. private bool IsCollinear(PathPoly prev, PathPoly current, PathPoly next, float tolerance)
  116. {
  117. return FloatInRange(Area(prev, current, next), -tolerance, tolerance);
  118. }
  119. private float Area(PathPoly a, PathPoly b, PathPoly c)
  120. {
  121. var (ax, ay) = a.Box.Center;
  122. var (bx, by) = b.Box.Center;
  123. var (cx, cy) = c.Box.Center;
  124. return ax * (by - cy) + bx * (cy - ay) + cx * (ay - by);
  125. }
  126. private bool FloatInRange(float value, float min, float max)
  127. {
  128. return (value >= min && value <= max);
  129. }
  130. #endregion
  131. }