diff --git a/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs b/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs index f16a839603..7ba38b5692 100644 --- a/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs +++ b/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs @@ -780,6 +780,7 @@ namespace OpenRA.Mods.Common.Pathfinder fullGraph.GetConnections, locomotor, target, target, estimatedSearchSize, pathFinderOverlay?.RecordAbstractEdges(self))) { var sourcesWithPathableNodes = new HashSet(sources.Count); + List 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. @@ -788,12 +789,24 @@ namespace OpenRA.Mods.Common.Pathfinder { if (sourceStatus.CostSoFar != PathGraph.PathCostForInvalidPath) sourcesWithPathableNodes.Add(source); + else + { + if (unpathableNodes == null) + unpathableNodes = new List(); + unpathableNodes.Add(adjacentSource); + } } else { reverseAbstractSearch.TargetPredicate = cell => cell == adjacentSource; if (reverseAbstractSearch.ExpandToTarget()) sourcesWithPathableNodes.Add(source); + else + { + if (unpathableNodes == null) + unpathableNodes = new List(); + unpathableNodes.Add(adjacentSource); + } } } @@ -802,7 +815,7 @@ namespace OpenRA.Mods.Common.Pathfinder using (var fromSrc = GetLocalPathSearch( self, sourcesWithPathableNodes, target, customCost, ignoreActor, check, laneBias, null, heuristicWeightPercentage, - heuristic: Heuristic(reverseAbstractSearch, estimatedSearchSize, sourcesWithPathableNodes), + heuristic: Heuristic(reverseAbstractSearch, estimatedSearchSize, sourcesWithPathableNodes, unpathableNodes), recorder: pathFinderOverlay?.RecordLocalEdges(self))) return fromSrc.FindPath(); } @@ -852,12 +865,12 @@ namespace OpenRA.Mods.Common.Pathfinder RebuildDirtyGrids(); - // If the target cell in unreachable, there is no path. + // If the target cell is unreachable, there is no path. var targetAbstractCell = AbstractCellForLocalCell(target); if (targetAbstractCell == null) return PathFinder.NoPath; - // If the source cell in unreachable, there may still be a path. + // If the source cell is unreachable, there may still be a path. // As long as one of the cells adjacent to the source is reachable, the path can be made. // Call the other overload which can handle this scenario. var sourceAbstractCell = AbstractCellForLocalCell(source); @@ -886,11 +899,11 @@ namespace OpenRA.Mods.Common.Pathfinder using (var fromSrc = GetLocalPathSearch( self, new[] { source }, target, customCost, ignoreActor, check, laneBias, null, heuristicWeightPercentage, - heuristic: Heuristic(reverseAbstractSearch, estimatedSearchSize, null), + heuristic: Heuristic(reverseAbstractSearch, estimatedSearchSize, null, null), recorder: pathFinderOverlay?.RecordLocalEdges(self))) using (var fromDest = GetLocalPathSearch( self, new[] { target }, source, customCost, ignoreActor, check, laneBias, null, heuristicWeightPercentage, - heuristic: Heuristic(forwardAbstractSearch, estimatedSearchSize, null), + heuristic: Heuristic(forwardAbstractSearch, estimatedSearchSize, null, null), recorder: pathFinderOverlay?.RecordLocalEdges(self), inReverse: true)) return PathSearch.FindBidiPath(fromDest, fromSrc); @@ -1096,13 +1109,20 @@ namespace OpenRA.Mods.Common.Pathfinder /// (the heuristic) for a local path search. The abstract search must run in the opposite direction to the /// local search. So when searching from source to target, the abstract search must be from target to source. /// - Func Heuristic(PathSearch abstractSearch, int estimatedSearchSize, HashSet sources) + Func Heuristic(PathSearch abstractSearch, int estimatedSearchSize, + HashSet sources, List unpathableNodes) { var nodeForCostLookup = new Dictionary(estimatedSearchSize); var graph = (SparsePathGraph)abstractSearch.Graph; return cell => { - // All cells searched by the heuristic are guaranteed to be reachable. + // When dealing with an unreachable source cell, the path search will check adjacent locations. + // These cells may be reachable, but may represent jumping into an area cut off from the target. + // Searching on the abstract graph would fail to provide a route in this scenario, so bail early. + if (unpathableNodes != null && unpathableNodes.Contains(cell)) + return PathGraph.PathCostForInvalidPath; + + // All other cells searched by the heuristic are guaranteed to be reachable. // So we don't need to handle an abstract cell lookup failing, or the search failing to expand. // Cells added as initial starting points for the search are filtered out if they aren't reachable. // The search only explores accessible cells from then on. @@ -1112,13 +1132,14 @@ namespace OpenRA.Mods.Common.Pathfinder var maybeAbstractCell = AbstractCellForLocalCellNoAccessibleCheck(cell); if (maybeAbstractCell == null) { - // If the source cell in unreachable, use one of the adjacent reachable cells instead. + // If the source cell is unreachable, use one of the adjacent reachable cells instead. if (sources != null && sources.Contains(cell)) { foreach (var dir in CVec.Directions) { var adjacentSource = cell + dir; - if (!world.Map.Contains(adjacentSource)) + if (!world.Map.Contains(adjacentSource) || + (unpathableNodes != null && unpathableNodes.Contains(adjacentSource))) continue; // Ideally we'd choose the cheapest cell rather than just any one of them, diff --git a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs index 96706b9b83..4028d26555 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs @@ -182,7 +182,11 @@ namespace OpenRA.Mods.Common.Pathfinder return; } - var estimatedCost = heuristic(location) * heuristicWeightPercentage / 100; + var heuristicCost = heuristic(location); + if (heuristicCost == PathGraph.PathCostForInvalidPath) + return; + + var estimatedCost = heuristicCost * heuristicWeightPercentage / 100; Graph[location] = new CellInfo(CellStatus.Open, initialCost, initialCost + estimatedCost, location); var connection = new GraphConnection(location, estimatedCost); openQueue.Add(connection); @@ -237,14 +241,22 @@ namespace OpenRA.Mods.Common.Pathfinder (neighborInfo.Status == CellStatus.Open && costSoFarToNeighbor >= neighborInfo.CostSoFar)) continue; - // Now we may seriously consider this direction using heuristics. If the cell has - // already been processed, we can reuse the result (just the difference between the - // estimated total and the cost so far) + // Now we may seriously consider this direction using heuristics. int estimatedRemainingCostToTarget; if (neighborInfo.Status == CellStatus.Open) + { + // If the cell has already been processed, we can reuse the result + // (just the difference between the estimated total and the cost so far) estimatedRemainingCostToTarget = neighborInfo.EstimatedTotalCost - neighborInfo.CostSoFar; + } else - estimatedRemainingCostToTarget = heuristic(neighbor) * heuristicWeightPercentage / 100; + { + // If the heuristic reports the cell is unreachable, we won't consider it. + var heuristicCost = heuristic(neighbor); + if (heuristicCost == PathGraph.PathCostForInvalidPath) + continue; + estimatedRemainingCostToTarget = heuristicCost * heuristicWeightPercentage / 100; + } recorder?.Add(currentMinNode, neighbor, costSoFarToNeighbor, estimatedRemainingCostToTarget);