HPF handles searches from unreachable source cells into cut off areas.

The scenario is that an actor is on an unreachable tile, and would like to path. As long as it is immediately adjacent to some reachable tiles, it can still move onto them and path. Now imagine a map split into two by a one tile wide line of impassable cliffs. It is important which side it chooses to jump into, as once it has moved off the cliff it loses access to the other side. Jumping off the correct side will allow a valid path, jumping off the wrong side will prevent a path from being possible.

In d8ebb96077, handling was added to prevent a crash where the path search would simulate having the actor jump off the wrong side and then get confused that it could not find a path when one was expected. This fix works by remembering the `unpathableNodes` - the nodes where you jump onto the wrong side. If we encounter them later, we can ignore them.

In 5157bc375d we added domain checks - this allows the HPF to bail on impossible paths early by checking if they belong to different domains. For example islands on a water map will belong to different domains.

This caused a regression where `sourcesWithReachableNodes` was now badly named. Some reachable nodes because they were in the wrong domain. Later when `sourcesWithPathableNodes` and `unpathableNodes` are built - we don't populate the `unpathableNodes` correctly, because we already excluded the unpathable nodes earlier on! This means we don't ignore them any more, and we reintroduce the crash.

Now that we are checking the domain, we can simplify the code and resolve the crash at the same time. `sourcesWithReachableNodes` becomes `sourcesWithPathableNodes` because the domain check has been done. We can build `unpathableNodes` at the same time. This allows us to remove the later code that explored the abstract graph, as the domain check succinctly achieves the same end goal of determining whether the node has a path or not.
This commit is contained in:
RoosterDragon
2024-07-25 19:08:09 +01:00
committed by Matthias Mailänder
parent c64eea7872
commit c1f99cb094

View File

@@ -746,8 +746,9 @@ namespace OpenRA.Mods.Common.Pathfinder
// Unlike the target cell, the source cell is allowed to be an unreachable location.
// Instead, what matters is whether any cell adjacent to the source cell can be reached.
var sourcesWithReachableNodes = new List<(CPos Source, CPos AdjacentSource)>(sources.Count);
var sourcesWithPathableNodes = new HashSet<CPos>(sources.Count);
var sourceEdges = new List<GraphEdge>(sources.Count);
List<CPos> unpathableNodes = null;
foreach (var source in sources)
{
if (!world.Map.Contains(source))
@@ -762,7 +763,7 @@ namespace OpenRA.Mods.Common.Pathfinder
if (sourceDomain != targetDomain)
continue;
sourcesWithReachableNodes.Add((source, source));
sourcesWithPathableNodes.Add(source);
var sourceEdge = EdgeFromLocalToAbstract(source, sourceAbstractCell.Value);
if (sourceEdge != null)
sourceEdges.Add(sourceEdge.Value);
@@ -783,16 +784,20 @@ namespace OpenRA.Mods.Common.Pathfinder
// If the source and target belong to different domains, there is no path.
var adjacentSourceDomain = abstractDomains[adjacentSourceAbstractCell.Value];
if (adjacentSourceDomain != targetDomain)
{
unpathableNodes ??= new List<CPos>();
unpathableNodes.Add(adjacentSource);
continue;
}
sourcesWithReachableNodes.Add((source, adjacentSource));
sourcesWithPathableNodes.Add(source);
var sourceEdge = EdgeFromLocalToAbstract(adjacentSource, adjacentSourceAbstractCell.Value);
if (sourceEdge != null)
sourceEdges.Add(sourceEdge.Value);
}
}
if (sourcesWithReachableNodes.Count == 0)
if (sourcesWithPathableNodes.Count == 0)
return PathFinder.NoPath;
var targetEdge = EdgeFromLocalToAbstract(target, targetAbstractCell.Value);
@@ -805,38 +810,6 @@ namespace OpenRA.Mods.Common.Pathfinder
using (var reverseAbstractSearch = PathSearch.ToTargetCellOverGraph(
fullGraph.GetConnections, locomotor, target, target, estimatedSearchSize, pathFinderOverlay?.RecordAbstractEdges(self)))
{
var sourcesWithPathableNodes = new HashSet<CPos>(sources.Count);
List<CPos> unpathableNodes = null;
foreach (var (source, adjacentSource) in sourcesWithReachableNodes)
{
// Check if we have already found a route to this node before we attempt to expand the search.
var sourceStatus = reverseAbstractSearch.Graph[adjacentSource];
if (sourceStatus.Status == CellStatus.Closed)
{
if (sourceStatus.CostSoFar != PathGraph.PathCostForInvalidPath)
sourcesWithPathableNodes.Add(source);
else
{
unpathableNodes ??= new List<CPos>();
unpathableNodes.Add(adjacentSource);
}
}
else
{
reverseAbstractSearch.TargetPredicate = cell => cell == adjacentSource;
if (reverseAbstractSearch.ExpandToTarget())
sourcesWithPathableNodes.Add(source);
else
{
unpathableNodes ??= new List<CPos>();
unpathableNodes.Add(adjacentSource);
}
}
}
if (sourcesWithPathableNodes.Count == 0)
return PathFinder.NoPath;
using (var fromSrc = GetLocalPathSearch(
self, sourcesWithPathableNodes, target, customCost, ignoreActor, check, laneBias, null, heuristicWeightPercentage,
heuristic: Heuristic(reverseAbstractSearch, estimatedSearchSize, sourcesWithPathableNodes, unpathableNodes),