Allow custom cost to exclude source locations in path searches.
During the refactoring to introduce HierarchicalPathFinder, custom costs were no longer applied to source locations. The logic being that if we are already in the source location, then there should be no cost added to it to get there - we are already there! Path searches support the ability to go from multiple sources to a single target, but the reverse is not supported. Some callers that require a search from a single source to one of multiple targets perform their search in reverse, swapping the source and targets so they can run the search, then reversing the path they are given so things are the correct way around again. For callers that also use a custom cost like the harvester code that do this in order to find free refineries, they might want the custom cost to be applied to the source location. The harvester code uses this to filter out overloaded refineries. It wants to search from a harvester to multiple refineries, and thus reverses this in order to perform the path search. Without the custom cost being applied to the "source" locations, this filtering logic never gets applied. To fix this, we now apply the custom cost to source locations. If the custom cost provides an invalid path, then the source location is excluded entirely. Although this seems unintuitive on its own, this allows searches done "in reverse" to work again.
This commit is contained in:
@@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
|
||||
foreach (var sl in froms)
|
||||
if (world.Map.Contains(sl))
|
||||
search.AddInitialCell(sl);
|
||||
search.AddInitialCell(sl, customCost);
|
||||
|
||||
return search;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
|
||||
foreach (var sl in froms)
|
||||
if (world.Map.Contains(sl))
|
||||
search.AddInitialCell(sl);
|
||||
search.AddInitialCell(sl, customCost);
|
||||
|
||||
return search;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
var graph = new SparsePathGraph(edges, estimatedSearchSize);
|
||||
var search = new PathSearch(graph, DefaultCostEstimator(locomotor, target), 100, loc => loc == target, recorder);
|
||||
|
||||
search.AddInitialCell(from);
|
||||
search.AddInitialCell(from, null);
|
||||
|
||||
return search;
|
||||
}
|
||||
@@ -162,10 +162,18 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
openQueue = new PriorityQueue<GraphConnection>(GraphConnection.ConnectionCostComparer);
|
||||
}
|
||||
|
||||
void AddInitialCell(CPos location)
|
||||
void AddInitialCell(CPos location, Func<CPos, int> customCost)
|
||||
{
|
||||
var initialCost = 0;
|
||||
if (customCost != null)
|
||||
{
|
||||
initialCost = customCost(location);
|
||||
if (initialCost == PathGraph.PathCostForInvalidPath)
|
||||
return;
|
||||
}
|
||||
|
||||
var estimatedCost = heuristic(location) * heuristicWeightPercentage / 100;
|
||||
Graph[location] = new CellInfo(CellStatus.Open, 0, estimatedCost, location);
|
||||
Graph[location] = new CellInfo(CellStatus.Open, initialCost, initialCost + estimatedCost, location);
|
||||
var connection = new GraphConnection(location, estimatedCost);
|
||||
openQueue.Add(connection);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Activities;
|
||||
using OpenRA.Mods.Common.Orders;
|
||||
using OpenRA.Mods.Common.Pathfinder;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -183,6 +182,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public Actor ClosestProc(Actor self, Actor ignore)
|
||||
{
|
||||
// Find all refineries and their occupancy count:
|
||||
// Exclude refineries with too many harvesters clogging the delivery location.
|
||||
var refineries = self.World.ActorsWithTrait<IAcceptResources>()
|
||||
.Where(r => r.Actor != ignore && r.Actor.Owner == self.Owner && IsAcceptableProcType(r.Actor))
|
||||
.Select(r => new
|
||||
@@ -190,28 +190,28 @@ namespace OpenRA.Mods.Common.Traits
|
||||
Location = r.Actor.Location + r.Trait.DeliveryOffset,
|
||||
Actor = r.Actor,
|
||||
Occupancy = self.World.ActorsHavingTrait<Harvester>(h => h.LinkedProc == r.Actor).Count()
|
||||
}).ToLookup(r => r.Location);
|
||||
})
|
||||
.Where(r => r.Occupancy < Info.MaxUnloadQueue)
|
||||
.ToDictionary(r => r.Location);
|
||||
|
||||
if (refineries.Count == 0)
|
||||
return null;
|
||||
|
||||
// Start a search from each refinery's delivery location:
|
||||
var path = mobile.PathFinder.FindPathToTargetCell(
|
||||
self, refineries.Select(r => r.Key), self.Location, BlockedByActor.None,
|
||||
location =>
|
||||
{
|
||||
if (!refineries.Contains(location))
|
||||
if (!refineries.ContainsKey(location))
|
||||
return 0;
|
||||
|
||||
var occupancy = refineries[location].First().Occupancy;
|
||||
|
||||
// Too many harvesters clogs up the refinery's delivery location:
|
||||
if (occupancy >= Info.MaxUnloadQueue)
|
||||
return PathGraph.PathCostForInvalidPath;
|
||||
|
||||
// Prefer refineries with less occupancy (multiplier is to offset distance cost):
|
||||
var occupancy = refineries[location].Occupancy;
|
||||
return occupancy * Info.UnloadQueueCostModifier;
|
||||
});
|
||||
|
||||
if (path.Count > 0)
|
||||
return refineries[path.Last()].First().Actor;
|
||||
return refineries[path.Last()].Actor;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
// For adjacent cells on the same layer, we can return the path without invoking a full search.
|
||||
if (source.Layer == target.Layer && (source - target).LengthSquared < 3)
|
||||
{
|
||||
if (!world.Map.Contains(source))
|
||||
// If the source cell is inaccessible, there is no path.
|
||||
if (!world.Map.Contains(source) ||
|
||||
(customCost != null && customCost(source) == PathGraph.PathCostForInvalidPath))
|
||||
return NoPath;
|
||||
return new List<CPos>(2) { target, source };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user