2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
17 public readonly List<PathNode>
connections =
new List<PathNode>();
29 return $
"PathNode {WayPointID}";
39 public static List<PathNode>
GenerateNodes(List<WayPoint> wayPoints,
bool removeOrphans)
41 var nodes =
new Dictionary<int, PathNode>();
42 foreach (
WayPoint wayPoint
in wayPoints)
44 if (wayPoint ==
null) {
continue; }
45 if (nodes.ContainsKey(wayPoint.
ID))
48 DebugConsole.ThrowError(
"Error in PathFinder.GenerateNodes (duplicate ID \"" + wayPoint.
ID +
"\")");
52 nodes.Add(wayPoint.
ID,
new PathNode(wayPoint));
55 foreach (KeyValuePair<int, PathNode> node
in nodes)
59 nodes.TryGetValue(linked.
ID, out
PathNode connectedNode);
60 if (connectedNode ==
null) {
continue; }
61 if (!node.Value.connections.Contains(connectedNode)) { node.Value.connections.Add(connectedNode); }
62 if (!connectedNode.connections.Contains(node.Value)) { connectedNode.connections.Add(node.Value); }
66 var nodeList = nodes.Values.ToList();
69 nodeList.RemoveAll(n => n.connections.Count == 0);
73 node.distances =
new List<float>();
74 for (
int i = 0; i < node.connections.Count; i++)
76 node.distances.Add(Vector2.Distance(node.Position, node.connections[i].Position));
83 private bool? blocked;
86 if (blocked.HasValue) {
return blocked.Value; }
95 blocked = !d.Destroyed;
97 if (blocked.Value) {
break; }
115 private readonly List<PathNode> nodes;
116 private readonly
bool isCharacter;
121 public PathFinder(List<WayPoint> wayPoints,
bool isCharacter)
123 var filtered = isCharacter ? wayPoints : wayPoints.FindAll(w => w.Submarine ==
null);
129 sortedNodes =
new List<PathNode>(nodes.Count);
130 this.isCharacter = isCharacter;
133 void WaypointLinksChanged(
WayPoint wp)
137 var node = nodes.Find(n => n.Waypoint == wp);
138 if (node ==
null) {
return; }
140 for (
int i = node.connections.Count - 1; i >= 0; i--)
143 if (wp.
linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) ==
null)
145 node.connections.RemoveAt(i);
146 node.distances.RemoveAt(i);
150 for (
int i = 0; i < wp.
linkedTo.Count; i++)
152 if (!(wp.
linkedTo[i] is WayPoint connected)) {
continue; }
155 if (node.connections.Any(n => n.Waypoint == connected)) {
continue; }
157 var matchingNode = nodes.Find(n => n.Waypoint == connected);
158 if (matchingNode ==
null)
161 DebugConsole.ThrowError(
"Waypoint connections were changed, no matching path node found in PathFinder");
166 node.connections.Add(matchingNode);
167 node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position));
171 private readonly List<PathNode> sortedNodes;
173 public SteeringPath FindPath(Vector2 start, Vector2 end,
Submarine hostSub =
null,
string errorMsgStr =
null,
float minGapSize = 0, Func<PathNode, bool> startNodeFilter =
null, Func<PathNode, bool> endNodeFilter =
null, Func<PathNode, bool> nodeFilter =
null,
bool checkVisibility =
true)
185 if (hostSub !=
null && wpSub ==
null)
190 else if (wpSub !=
null && hostSub !=
null && wpSub != hostSub)
193 node.
TempPosition -= hostSub.SimPosition - wpSub.SimPosition;
195 else if (hostSub ==
null && wpSub !=
null)
234 if (IsValidStartNode(node))
244 while (i < sortedNodes.Count && sortedNodes[i].TempDistance < node.
TempDistance)
248 sortedNodes.Insert(i, node);
252 if (startNode ==
null)
254 foreach (
PathNode node
in sortedNodes)
256 if (IsValidStartNode(node))
264 if (startNode ==
null)
267 DebugConsole.NewMessage(
"Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed);
296 if (IsValidEndNode(node))
303 while (i < sortedNodes.Count && sortedNodes[i].TempDistance < node.
TempDistance)
307 sortedNodes.Insert(i, node);
312 foreach (
PathNode node
in sortedNodes)
314 if (IsValidEndNode(node))
324 DebugConsole.NewMessage(
"Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed);
328 return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize);
330 bool IsValidStartNode(
PathNode node) => IsValidNode(node, (isCharacter, start), startNodeFilter);
332 bool IsValidEndNode(
PathNode node) => IsValidNode(node, (isCharacter && checkVisibility, end), endNodeFilter);
334 bool IsValidNode(
PathNode node, (
bool check, Vector2 start) visibilityCheck, Func<PathNode, bool> extraFilter)
336 if (nodeFilter !=
null && !nodeFilter(node)) {
return false; }
337 if (extraFilter !=
null && !extraFilter(node)) {
return false; }
343 if (visibilityCheck.check)
346 collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs);
349 if (body.UserData is
Submarine) {
return false; }
352 if (body.UserData is
Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) {
return false; }
368 foreach (PathNode node
in nodes)
380 PathNode currNode =
null;
381 float dist =
float.MaxValue;
382 foreach (PathNode node
in nodes)
384 if (node.state != 1 || node.F > dist) {
continue; }
385 if (filter !=
null && !filter(node)) {
continue; }
386 if (node.Waypoint.ConnectedGap !=
null)
388 if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) {
continue; }
394 if (currNode ==
null || currNode == end) {
break; }
398 for (
int i = 0; i < currNode.connections.Count; i++)
400 PathNode nextNode = currNode.connections[i];
403 if (nextNode.state == 0)
405 nextNode.H = Vector2.Distance(nextNode.Position, end.
Position);
407 float penalty = 0.0f;
411 if (nodePenalty ==
null)
416 penalty = nodePenalty.Value;
419 nextNode.G = currNode.G + currNode.distances[i] + penalty;
420 nextNode.F = nextNode.G + nextNode.H;
421 nextNode.Parent = currNode;
425 else if (nextNode.state == 1 || nextNode.state == -1)
427 float tempG = currNode.G + currNode.distances[i];
432 if (nodePenalty ==
null) {
continue; }
433 tempG += nodePenalty.Value;
438 if (tempG < nextNode.G)
441 nextNode.F = nextNode.G + nextNode.H;
442 nextNode.Parent = currNode;
452 if (errorMsgStr !=
null)
454 DebugConsole.NewMessage(
"Path not found. " + errorMsgStr, Color.Yellow);
457 return new SteeringPath(
true);
460 SteeringPath path =
new SteeringPath();
461 List<WayPoint> finalPath =
new List<WayPoint>();
463 PathNode pathNode = end;
464 while (pathNode != start && pathNode !=
null)
466 finalPath.Add(pathNode.Waypoint);
472 if (finalPath.Count > nodes.Count)
475 DebugConsole.ThrowError(
"Pathfinding error: constructing final path failed");
477 return new SteeringPath(
true);
480 path.Cost += pathNode.F;
481 pathNode = pathNode.Parent;
485 for (
int i = finalPath.Count - 1; i >= 0; i--)
487 path.AddNode(finalPath[i]);
489 System.Diagnostics.Debug.Assert(finalPath.Count == path.Nodes.Count);
494 private bool CanFitThroughGap(Gap gap,
float minWidth) => gap.IsHorizontal ? gap.RectHeight > minWidth : gap.RectWidth > minWidth;
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
List< LevelWall > ExtraWalls
Special wall chunks that aren't part of the normal level geometry: includes things like the ocean flo...
override Vector2 Position
readonly List< MapEntity > linkedTo
override Vector2 SimPosition
delegate? float GetSingleNodePenaltyHandler(PathNode node)
GetNodePenaltyHandler GetNodePenalty
PathFinder(List< WayPoint > wayPoints, bool isCharacter)
bool ApplyPenaltyToOutsideNodes
GetSingleNodePenaltyHandler GetSingleNodePenalty
SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub=null, string errorMsgStr=null, float minGapSize=0, Func< PathNode, bool > startNodeFilter=null, Func< PathNode, bool > endNodeFilter=null, Func< PathNode, bool > nodeFilter=null, bool checkVisibility=true)
delegate? float GetNodePenaltyHandler(PathNode node, PathNode prevNode)
readonly WayPoint Waypoint
override string ToString()
readonly Vector2 Position
static List< PathNode > GenerateNodes(List< WayPoint > wayPoints, bool removeOrphans)
readonly List< PathNode > connections
PathNode(WayPoint wayPoint)
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
Action< WayPoint > OnLinksChanged