Improved the performance and intelligence of resource harvesting by
refactoring the Harvesters' pathfinding. Now they in first place assess which is the closest resource inside their search area and then a path is calculated Changed the way harvesters find resources by always trying to find the closest resource to their refinery. Changed the strategy of finding to find resources in Annulus.
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
@@ -58,91 +57,108 @@ namespace OpenRA.Mods.Common.Activities
|
||||
if (harv.IsFull)
|
||||
return Util.SequenceActivities(deliver, NextActivity);
|
||||
|
||||
var closestHarvestablePosition = ClosestHarvestablePos(self);
|
||||
|
||||
// If no harvestable position could be found, either deliver the remaining resources
|
||||
// or get out of the way and do not disturb.
|
||||
if (!closestHarvestablePosition.HasValue)
|
||||
{
|
||||
if (!harv.IsEmpty)
|
||||
return deliver;
|
||||
|
||||
var cachedPosition = self.Location;
|
||||
harv.UnblockRefinery(self);
|
||||
|
||||
// Only do this if UnblockRefinery did nothing.
|
||||
if (self.Location == cachedPosition)
|
||||
{
|
||||
var unblockCell = harv.LastHarvestedCell ?? (self.Location + new CVec(0, 4));
|
||||
var moveTo = mobile.NearestMoveableCell(unblockCell, 2, 5);
|
||||
self.QueueActivity(mobile.MoveTo(moveTo, 1));
|
||||
self.SetTargetLine(Target.FromCell(self.World, moveTo), Color.Gray, false);
|
||||
}
|
||||
|
||||
var randFrames = self.World.SharedRandom.Next(100, 175);
|
||||
return Util.SequenceActivities(NextActivity, new Wait(randFrames), this);
|
||||
}
|
||||
else
|
||||
{
|
||||
var next = this;
|
||||
|
||||
// Attempt to claim a resource as ours
|
||||
if (territory != null)
|
||||
{
|
||||
if (!territory.ClaimResource(self, closestHarvestablePosition.Value))
|
||||
return Util.SequenceActivities(new Wait(25), next);
|
||||
}
|
||||
|
||||
// If not given a direct order, assume ordered to the first resource location we find:
|
||||
if (!harv.LastOrderLocation.HasValue)
|
||||
harv.LastOrderLocation = closestHarvestablePosition;
|
||||
|
||||
self.SetTargetLine(Target.FromCell(self.World, closestHarvestablePosition.Value), Color.Red, false);
|
||||
|
||||
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
|
||||
|
||||
foreach (var n in notify)
|
||||
n.MovingToResources(self, closestHarvestablePosition.Value, next);
|
||||
|
||||
return Util.SequenceActivities(mobile.MoveTo(closestHarvestablePosition.Value, 1), new HarvestResource(self), next);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsHarvestable(Actor self, CPos pos)
|
||||
{
|
||||
var resType = resLayer.GetResource(pos);
|
||||
if (resType == null)
|
||||
return false;
|
||||
|
||||
// Can the harvester collect this kind of resource?
|
||||
if (!harvInfo.Resources.Contains(resType.Info.Name))
|
||||
return false;
|
||||
|
||||
if (territory != null)
|
||||
{
|
||||
// Another harvester has claimed this resource:
|
||||
ResourceClaim claim;
|
||||
if (territory.IsClaimedByAnyoneElse(self as Actor, pos, out claim))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the closest harvestable pos between the current position of the harvester
|
||||
/// and the last order location
|
||||
/// </summary>
|
||||
CPos? ClosestHarvestablePos(Actor self)
|
||||
{
|
||||
// Determine where to search from and how far to search:
|
||||
var searchFromLoc = harv.LastOrderLocation ?? (harv.LastLinkedProc ?? harv.LinkedProc ?? self).Location;
|
||||
var searchRadius = harv.LastOrderLocation.HasValue ? harvInfo.SearchFromOrderRadius : harvInfo.SearchFromProcRadius;
|
||||
var searchRadiusSquared = searchRadius * searchRadius;
|
||||
|
||||
// Find harvestable resources nearby:
|
||||
var path = pathFinder.FindPath(
|
||||
PathSearch.Search(self.World, mobileInfo, self, true)
|
||||
.WithHeuristic(loc =>
|
||||
{
|
||||
// Avoid this cell:
|
||||
if (avoidCell.HasValue && loc == avoidCell.Value)
|
||||
return EstimateDistance(loc, searchFromLoc) + Constants.CellCost;
|
||||
|
||||
// Don't harvest out of range:
|
||||
var distSquared = (loc - searchFromLoc).LengthSquared;
|
||||
if (distSquared > searchRadiusSquared)
|
||||
return EstimateDistance(loc, searchFromLoc) + Constants.CellCost * 2;
|
||||
|
||||
// Get the resource at this location:
|
||||
var resType = resLayer.GetResource(loc);
|
||||
if (resType == null)
|
||||
return EstimateDistance(loc, searchFromLoc) + Constants.CellCost;
|
||||
|
||||
// Can the harvester collect this kind of resource?
|
||||
if (!harvInfo.Resources.Contains(resType.Info.Name))
|
||||
return EstimateDistance(loc, searchFromLoc) + Constants.CellCost;
|
||||
|
||||
if (territory != null)
|
||||
{
|
||||
// Another harvester has claimed this resource:
|
||||
ResourceClaim claim;
|
||||
if (territory.IsClaimedByAnyoneElse(self, loc, out claim))
|
||||
return EstimateDistance(loc, searchFromLoc) + Constants.CellCost;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})
|
||||
.FromPoint(self.Location));
|
||||
|
||||
var next = this;
|
||||
|
||||
if (path.Count == 0)
|
||||
{
|
||||
if (!harv.IsEmpty)
|
||||
return deliver;
|
||||
else
|
||||
var search = PathSearch.Search(self.World, mobileInfo, self, true,
|
||||
loc => IsHarvestable(self, loc))
|
||||
.WithCustomCost(loc =>
|
||||
{
|
||||
// Get out of the way if we are:
|
||||
harv.UnblockRefinery(self);
|
||||
var randFrames = self.World.SharedRandom.Next(90, 160);
|
||||
if (NextActivity != null)
|
||||
return Util.SequenceActivities(NextActivity, new Wait(randFrames), next);
|
||||
else
|
||||
return Util.SequenceActivities(new Wait(randFrames), next);
|
||||
}
|
||||
}
|
||||
if ((avoidCell.HasValue && loc == avoidCell.Value) ||
|
||||
(loc - self.Location).LengthSquared > searchRadiusSquared)
|
||||
return int.MaxValue;
|
||||
|
||||
// Attempt to claim a resource as ours:
|
||||
if (territory != null)
|
||||
{
|
||||
if (!territory.ClaimResource(self, path[0]))
|
||||
return Util.SequenceActivities(new Wait(25), next);
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.FromPoint(self.Location)
|
||||
.FromPoint(searchFromLoc);
|
||||
|
||||
// If not given a direct order, assume ordered to the first resource location we find:
|
||||
if (harv.LastOrderLocation == null)
|
||||
harv.LastOrderLocation = path[0];
|
||||
// Find any harvestable resources:
|
||||
var path = pathFinder.FindPath(search);
|
||||
|
||||
self.SetTargetLine(Target.FromCell(self.World, path[0]), Color.Red, false);
|
||||
if (path.Count > 0)
|
||||
return path[0];
|
||||
|
||||
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
|
||||
foreach (var n in notify)
|
||||
n.MovingToResources(self, path[0], next);
|
||||
|
||||
return Util.SequenceActivities(mobile.MoveTo(path[0], 1), new HarvestResource(self), next);
|
||||
}
|
||||
|
||||
// Diagonal distance heuristic
|
||||
static int EstimateDistance(CPos here, CPos destination)
|
||||
{
|
||||
var diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y));
|
||||
var straight = Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y);
|
||||
|
||||
return Constants.CellCost * straight + (Constants.DiagonalCellCost - 2 * Constants.CellCost) * diag;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IEnumerable<Target> GetTargets(Actor self)
|
||||
|
||||
@@ -110,6 +110,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
public bool Debug { get; set; }
|
||||
string id;
|
||||
protected Func<CPos, int> heuristic;
|
||||
protected Func<CPos, bool> isGoal;
|
||||
|
||||
// This member is used to compute the ID of PathSearch.
|
||||
// Essentially, it represents a collection of the initial
|
||||
@@ -194,8 +195,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
|
||||
public bool IsTarget(CPos location)
|
||||
{
|
||||
var locInfo = Graph[location];
|
||||
return locInfo.EstimatedTotal - locInfo.CostSoFar == 0;
|
||||
return isGoal(location);
|
||||
}
|
||||
|
||||
public abstract CPos Expand();
|
||||
|
||||
@@ -25,5 +25,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
/// a unit took to move one cell diagonally)
|
||||
/// </summary>
|
||||
public const int DiagonalCellCost = 177;
|
||||
|
||||
public const int InvalidNode = int.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
/// <summary>
|
||||
/// Gets all the Connections for a given node in the graph
|
||||
/// </summary>
|
||||
ICollection<GraphConnection> GetConnections(CPos position);
|
||||
IEnumerable<GraphConnection> GetConnections(CPos position);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an object given a node in the graph
|
||||
@@ -102,7 +102,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
new[] { new CVec(1, -1), new CVec(1, 0), new CVec(-1, 1), new CVec(0, 1), new CVec(1, 1) },
|
||||
};
|
||||
|
||||
public ICollection<GraphConnection> GetConnections(CPos position)
|
||||
public IEnumerable<GraphConnection> GetConnections(CPos position)
|
||||
{
|
||||
var previousPos = cellInfo[position].PreviousPos;
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
{
|
||||
var neighbor = position + directions[i];
|
||||
var movementCost = GetCostToNode(neighbor, directions[i]);
|
||||
if (movementCost != InvalidNode)
|
||||
if (movementCost != Constants.InvalidNode)
|
||||
validNeighbors.AddLast(new GraphConnection(position, neighbor, movementCost));
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
return CalculateCellCost(destNode, direction, movementCost);
|
||||
}
|
||||
|
||||
return InvalidNode;
|
||||
return Constants.InvalidNode;
|
||||
}
|
||||
|
||||
int CalculateCellCost(CPos neighborCPos, CVec direction, int movementCost)
|
||||
@@ -148,7 +148,13 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
cellCost = (cellCost * 34) / 24;
|
||||
|
||||
if (CustomCost != null)
|
||||
cellCost += CustomCost(neighborCPos);
|
||||
{
|
||||
var customCost = CustomCost(neighborCPos);
|
||||
if (customCost == Constants.InvalidNode)
|
||||
return Constants.InvalidNode;
|
||||
|
||||
cellCost += customCost;
|
||||
}
|
||||
|
||||
// directional bonuses for smoother flow!
|
||||
if (LaneBias != 0)
|
||||
@@ -184,15 +190,8 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
|
||||
public CellInfo this[CPos pos]
|
||||
{
|
||||
get
|
||||
{
|
||||
return cellInfo[pos];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
cellInfo[pos] = value;
|
||||
}
|
||||
get { return cellInfo[pos]; }
|
||||
set { cellInfo[pos] = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
@@ -32,10 +33,13 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
considered = new LinkedList<Pair<CPos, int>>();
|
||||
}
|
||||
|
||||
public static IPathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked)
|
||||
public static IPathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked, Func<CPos, bool> goalCondition)
|
||||
{
|
||||
var graph = new PathGraph(CellInfoLayerManager.Instance.NewLayer(world.Map), mi, self, world, checkForBlocked);
|
||||
return new PathSearch(graph);
|
||||
var search = new PathSearch(graph);
|
||||
search.isGoal = goalCondition;
|
||||
search.heuristic = loc => 0;
|
||||
return search;
|
||||
}
|
||||
|
||||
public static IPathSearch FromPoint(World world, MobileInfo mi, Actor self, CPos from, CPos target, bool checkForBlocked)
|
||||
@@ -46,6 +50,12 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
heuristic = DefaultEstimator(target)
|
||||
};
|
||||
|
||||
search.isGoal = loc =>
|
||||
{
|
||||
var locInfo = search.Graph[loc];
|
||||
return locInfo.EstimatedTotal - locInfo.CostSoFar == 0;
|
||||
};
|
||||
|
||||
if (world.Map.Contains(from))
|
||||
search.AddInitialCell(from);
|
||||
|
||||
@@ -60,6 +70,12 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
heuristic = DefaultEstimator(target)
|
||||
};
|
||||
|
||||
search.isGoal = loc =>
|
||||
{
|
||||
var locInfo = search.Graph[loc];
|
||||
return locInfo.EstimatedTotal - locInfo.CostSoFar == 0;
|
||||
};
|
||||
|
||||
foreach (var sl in froms.Where(sl => world.Map.Contains(sl)))
|
||||
search.AddInitialCell(sl);
|
||||
|
||||
@@ -88,12 +104,8 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
var currentCell = Graph[currentMinNode];
|
||||
Graph[currentMinNode] = new CellInfo(currentCell.CostSoFar, currentCell.EstimatedTotal, currentCell.PreviousPos, CellStatus.Closed);
|
||||
|
||||
if (Graph.CustomCost != null)
|
||||
{
|
||||
var c = Graph.CustomCost(currentMinNode);
|
||||
if (c == int.MaxValue)
|
||||
return currentMinNode;
|
||||
}
|
||||
if (Graph.CustomCost != null && Graph.CustomCost(currentMinNode) == Constants.InvalidNode)
|
||||
return currentMinNode;
|
||||
|
||||
foreach (var connection in Graph.GetConnections(currentMinNode))
|
||||
{
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
// 4 harvesters clogs up the refinery's delivery location:
|
||||
if (occupancy >= 3)
|
||||
return int.MaxValue;
|
||||
return Constants.InvalidNode;
|
||||
|
||||
// Prefer refineries with less occupancy (multiplier is to offset distance cost):
|
||||
return occupancy * 12;
|
||||
@@ -209,8 +209,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
foreach (var n in notify)
|
||||
n.MovingToResources(self, moveTo, next);
|
||||
|
||||
self.QueueActivity(new FindResources(self));
|
||||
return;
|
||||
self.QueueActivity(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,51 +319,46 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
self.CancelActivity();
|
||||
|
||||
CPos? loc;
|
||||
if (order.TargetLocation != CPos.Zero)
|
||||
{
|
||||
var loc = order.TargetLocation;
|
||||
var territory = self.World.WorldActor.TraitOrDefault<ResourceClaimLayer>();
|
||||
loc = order.TargetLocation;
|
||||
|
||||
var territory = self.World.WorldActor.TraitOrDefault<ResourceClaimLayer>();
|
||||
if (territory != null)
|
||||
{
|
||||
// Find the nearest claimable cell to the order location (useful for group-select harvest):
|
||||
loc = mobile.NearestCell(loc, p => mobile.CanEnterCell(p) && territory.ClaimResource(self, p), 1, 6);
|
||||
loc = mobile.NearestCell(loc.Value, p => mobile.CanEnterCell(p) && territory.ClaimResource(self, p), 1, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find the nearest cell to the order location (useful for group-select harvest):
|
||||
var taken = new HashSet<CPos>();
|
||||
loc = mobile.NearestCell(loc, p => mobile.CanEnterCell(p) && taken.Add(p), 1, 6);
|
||||
loc = mobile.NearestCell(loc.Value, p => mobile.CanEnterCell(p) && taken.Add(p), 1, 6);
|
||||
}
|
||||
|
||||
self.QueueActivity(mobile.MoveTo(loc, 0));
|
||||
self.SetTargetLine(Target.FromCell(self.World, loc), Color.Red);
|
||||
|
||||
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
|
||||
var next = new FindResources(self);
|
||||
foreach (var n in notify)
|
||||
n.MovingToResources(self, loc, next);
|
||||
|
||||
LastOrderLocation = loc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A bot order gives us a CPos.Zero TargetLocation, so find some good resources for him:
|
||||
var loc = FindNextResourceForBot(self);
|
||||
loc = FindNextResourceForBot(self);
|
||||
|
||||
// No more resources? Oh well.
|
||||
if (!loc.HasValue)
|
||||
return;
|
||||
|
||||
self.QueueActivity(mobile.MoveTo(loc.Value, 0));
|
||||
self.SetTargetLine(Target.FromCell(self.World, loc.Value), Color.Red);
|
||||
|
||||
LastOrderLocation = loc;
|
||||
}
|
||||
|
||||
var next = new FindResources(self);
|
||||
self.QueueActivity(next);
|
||||
self.SetTargetLine(Target.FromCell(self.World, loc.Value), Color.Red);
|
||||
|
||||
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
|
||||
foreach (var n in notify)
|
||||
n.MovingToResources(self, loc.Value, next);
|
||||
|
||||
LastOrderLocation = loc;
|
||||
|
||||
// This prevents harvesters returning to an empty patch when the player orders them to a new patch:
|
||||
LastHarvestedCell = LastOrderLocation;
|
||||
self.QueueActivity(new FindResources(self));
|
||||
}
|
||||
else if (order.OrderString == "Deliver")
|
||||
{
|
||||
@@ -413,32 +407,31 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
// Find any harvestable resources:
|
||||
var path = self.World.WorldActor.Trait<IPathFinder>().FindPath(
|
||||
PathSearch.Search(self.World, mobileInfo, self, true)
|
||||
.WithHeuristic(loc =>
|
||||
PathSearch.Search(self.World, mobileInfo, self, true,
|
||||
loc =>
|
||||
{
|
||||
// Get the resource at this location:
|
||||
var resType = resLayer.GetResource(loc);
|
||||
if (resType == null)
|
||||
return Constants.CellCost;
|
||||
return false;
|
||||
|
||||
// Can the harvester collect this kind of resource?
|
||||
if (!harvInfo.Resources.Contains(resType.Info.Name))
|
||||
return Constants.CellCost;
|
||||
return false;
|
||||
|
||||
if (territory != null)
|
||||
{
|
||||
// Another harvester has claimed this resource:
|
||||
ResourceClaim claim;
|
||||
if (territory.IsClaimedByAnyoneElse(self, loc, out claim))
|
||||
return Constants.CellCost;
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
})
|
||||
.FromPoint(self.Location));
|
||||
|
||||
if (path.Count == 0)
|
||||
return (CPos?)null;
|
||||
return null;
|
||||
|
||||
return path[0];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user