Many harvester behavior improvements; summary below.
Implemented Harvester territory marking with a simple resource claim system in ResourceClaimLayer trait added to World. Added customCost for PathSearch to support new Harvester search preferences. Explicit delivery order forces harvester to always deliver to that refinery. Explicit harvest order frees harvester from forced delivery refinery and allows for auto-balancing. Harvesters auto-balance refinery choice such that no more than 3 harvesters are linked to any one refinery at a time. Harvesters try very hard to not block the refinery dock location. Harvesters try to avoid enemy territory when searching for resources. Group-select harvest order intelligently disperses harvesters around the order location. Fixed PathFinder caching to not be a sliding window. This is a correctness issue. Sliding window causes no-route paths to be cached permanently in tight move loops and doesn't allow eventual progress to be made. This may have negative performance implications.
This commit is contained in:
@@ -200,13 +200,18 @@ namespace OpenRA.Mods.RA.Move
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target)
|
||||
{
|
||||
return NearestMoveableCell(target, 1, 10);
|
||||
}
|
||||
|
||||
public CPos NearestMoveableCell(CPos target, int minRange, int maxRange)
|
||||
{
|
||||
if (CanEnterCell(target))
|
||||
return target;
|
||||
|
||||
var searched = new List<CPos>();
|
||||
// Limit search to a radius of 10 tiles
|
||||
for (int r = 1; r < 10; r++)
|
||||
for (int r = minRange; r < maxRange; r++)
|
||||
foreach (var tile in self.World.FindTilesInCircle(target, r).Except(searched))
|
||||
{
|
||||
if (CanEnterCell(tile))
|
||||
@@ -219,6 +224,25 @@ namespace OpenRA.Mods.RA.Move
|
||||
return target;
|
||||
}
|
||||
|
||||
public CPos NearestCell(CPos target, Func<CPos, bool> check, int minRange, int maxRange)
|
||||
{
|
||||
if (check(target))
|
||||
return target;
|
||||
|
||||
var searched = new List<CPos>();
|
||||
for (int r = minRange; r < maxRange; r++)
|
||||
foreach (var tile in self.World.FindTilesInCircle(target, r).Except(searched))
|
||||
{
|
||||
if (check(tile))
|
||||
return tile;
|
||||
|
||||
searched.Add(tile);
|
||||
}
|
||||
|
||||
// Couldn't find a cell
|
||||
return target;
|
||||
}
|
||||
|
||||
void PerformMoveInner(Actor self, CPos targetLocation, bool queued)
|
||||
{
|
||||
var currentLocation = NearestMoveableCell(targetLocation);
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace OpenRA.Mods.RA.Move
|
||||
|
||||
public override void Cancel( Actor self )
|
||||
{
|
||||
path = new List<CPos>();
|
||||
path = new List<CPos>(0);
|
||||
base.Cancel(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ namespace OpenRA.Mods.RA.Move
|
||||
{
|
||||
public class PathFinderInfo : ITraitInfo
|
||||
{
|
||||
public object Create( ActorInitializer init ) { return new PathFinder( init.world ); }
|
||||
public object Create(ActorInitializer init) { return new PathFinder(init.world); }
|
||||
}
|
||||
|
||||
public class PathFinder
|
||||
{
|
||||
readonly World world;
|
||||
public PathFinder( World world ) { this.world = world; }
|
||||
public PathFinder(World world) { this.world = world; }
|
||||
|
||||
class CachedPath
|
||||
{
|
||||
@@ -47,7 +47,8 @@ namespace OpenRA.Mods.RA.Move
|
||||
if (cached != null)
|
||||
{
|
||||
Log.Write("debug", "Actor {0} asked for a path from {1} tick(s) ago", self.ActorID, world.FrameNumber - cached.tick);
|
||||
cached.tick = world.FrameNumber;
|
||||
if (world.FrameNumber - cached.tick > MaxPathAge)
|
||||
CachedPaths.Remove(cached);
|
||||
return new List<CPos>(cached.result);
|
||||
}
|
||||
|
||||
@@ -68,11 +69,11 @@ namespace OpenRA.Mods.RA.Move
|
||||
|
||||
public List<CPos> FindUnitPathToRange(CPos src, CPos target, int range, Actor self)
|
||||
{
|
||||
using( new PerfSample( "Pathfinder" ) )
|
||||
using (new PerfSample("Pathfinder"))
|
||||
{
|
||||
var mi = self.Info.Traits.Get<MobileInfo>();
|
||||
var tilesInRange = world.FindTilesInCircle(target, range)
|
||||
.Where( t => mi.CanEnterCell(self.World, self.Owner, t, null, true));
|
||||
.Where(t => mi.CanEnterCell(self.World, self.Owner, t, null, true));
|
||||
|
||||
var path = FindBidiPath(
|
||||
PathSearch.FromPoints(world, mi, self.Owner, tilesInRange, src, true),
|
||||
@@ -105,10 +106,10 @@ namespace OpenRA.Mods.RA.Move
|
||||
var ret = new List<CPos>();
|
||||
CPos pathNode = destination;
|
||||
|
||||
while( cellInfo[ pathNode.X, pathNode.Y ].Path != pathNode )
|
||||
while (cellInfo[pathNode.X, pathNode.Y].Path != pathNode)
|
||||
{
|
||||
ret.Add( pathNode );
|
||||
pathNode = cellInfo[ pathNode.X, pathNode.Y ].Path;
|
||||
ret.Add(pathNode);
|
||||
pathNode = cellInfo[pathNode.X, pathNode.Y].Path;
|
||||
}
|
||||
|
||||
ret.Add(pathNode);
|
||||
@@ -153,8 +154,8 @@ namespace OpenRA.Mods.RA.Move
|
||||
var q = p;
|
||||
while (ca[q.X, q.Y].Path != q)
|
||||
{
|
||||
ret.Add( q );
|
||||
q = ca[ q.X, q.Y ].Path;
|
||||
ret.Add(q);
|
||||
q = ca[q.X, q.Y].Path;
|
||||
}
|
||||
ret.Add(q);
|
||||
|
||||
@@ -167,22 +168,22 @@ namespace OpenRA.Mods.RA.Move
|
||||
ret.Add(q);
|
||||
}
|
||||
|
||||
CheckSanePath( ret );
|
||||
CheckSanePath(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
[Conditional( "SANITY_CHECKS" )]
|
||||
[Conditional("SANITY_CHECKS")]
|
||||
static void CheckSanePath(List<CPos> path)
|
||||
{
|
||||
if( path.Count == 0 )
|
||||
if (path.Count == 0)
|
||||
return;
|
||||
var prev = path[ 0 ];
|
||||
for( int i = 0 ; i < path.Count ; i++ )
|
||||
var prev = path[0];
|
||||
for (int i = 0; i < path.Count; i++)
|
||||
{
|
||||
var d = path[ i ] - prev;
|
||||
if( Math.Abs( d.X ) > 1 || Math.Abs( d.Y ) > 1 )
|
||||
throw new InvalidOperationException( "(PathFinder) path sanity check failed" );
|
||||
prev = path[ i ];
|
||||
var d = path[i] - prev;
|
||||
if (Math.Abs(d.X) > 1 || Math.Abs(d.Y) > 1)
|
||||
throw new InvalidOperationException("(PathFinder) path sanity check failed");
|
||||
prev = path[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@ namespace OpenRA.Mods.RA.Move
|
||||
public class PathSearch : IDisposable
|
||||
{
|
||||
World world;
|
||||
public CellInfo[ , ] cellInfo;
|
||||
public CellInfo[,] cellInfo;
|
||||
public PriorityQueue<PathDistance> queue;
|
||||
public Func<CPos, int> heuristic;
|
||||
Func<CPos, int> customCost;
|
||||
Func<CPos, bool> customBlock;
|
||||
public bool checkForBlocked;
|
||||
public Actor ignoreBuilding;
|
||||
@@ -35,6 +36,7 @@ namespace OpenRA.Mods.RA.Move
|
||||
cellInfo = InitCellInfo();
|
||||
this.mobileInfo = mobileInfo;
|
||||
this.owner = owner;
|
||||
customCost = null;
|
||||
queue = new PriorityQueue<PathDistance>();
|
||||
}
|
||||
|
||||
@@ -62,6 +64,12 @@ namespace OpenRA.Mods.RA.Move
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch WithCustomCost(Func<CPos, int> w)
|
||||
{
|
||||
customCost = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PathSearch WithoutLaneBias()
|
||||
{
|
||||
LaneBias = 0;
|
||||
@@ -70,7 +78,7 @@ namespace OpenRA.Mods.RA.Move
|
||||
|
||||
public PathSearch FromPoint(CPos from)
|
||||
{
|
||||
AddInitialCell( from );
|
||||
AddInitialCell(from);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -92,12 +100,19 @@ namespace OpenRA.Mods.RA.Move
|
||||
if (thisCost == int.MaxValue)
|
||||
return p.Location;
|
||||
|
||||
if (customCost != null)
|
||||
{
|
||||
int c = customCost(p.Location);
|
||||
if (c == int.MaxValue)
|
||||
return p.Location;
|
||||
}
|
||||
|
||||
foreach( CVec d in directions )
|
||||
{
|
||||
CPos newHere = p.Location + d;
|
||||
|
||||
if (!world.Map.IsInMap(newHere.X, newHere.Y)) continue;
|
||||
if( cellInfo[ newHere.X, newHere.Y ].Seen )
|
||||
if (cellInfo[newHere.X, newHere.Y].Seen)
|
||||
continue;
|
||||
|
||||
var costHere = mobileInfo.MovementCostForCell(world, newHere);
|
||||
@@ -111,32 +126,37 @@ namespace OpenRA.Mods.RA.Move
|
||||
if (customBlock != null && customBlock(newHere))
|
||||
continue;
|
||||
|
||||
var est = heuristic( newHere );
|
||||
if( est == int.MaxValue )
|
||||
var est = heuristic(newHere);
|
||||
if (est == int.MaxValue)
|
||||
continue;
|
||||
|
||||
int cellCost = costHere;
|
||||
if( d.X * d.Y != 0 ) cellCost = ( cellCost * 34 ) / 24;
|
||||
if (d.X * d.Y != 0) cellCost = (cellCost * 34) / 24;
|
||||
|
||||
if (customCost != null)
|
||||
cellCost += customCost(newHere);
|
||||
|
||||
// directional bonuses for smoother flow!
|
||||
var ux = (newHere.X + (inReverse ? 1 : 0) & 1);
|
||||
var uy = (newHere.Y + (inReverse ? 1 : 0) & 1);
|
||||
if (LaneBias != 0)
|
||||
{
|
||||
var ux = (newHere.X + (inReverse ? 1 : 0) & 1);
|
||||
var uy = (newHere.Y + (inReverse ? 1 : 0) & 1);
|
||||
|
||||
if (ux == 0 && d.Y < 0) cellCost += LaneBias;
|
||||
else if (ux == 1 && d.Y > 0) cellCost += LaneBias;
|
||||
if (uy == 0 && d.X < 0) cellCost += LaneBias;
|
||||
else if (uy == 1 && d.X > 0) cellCost += LaneBias;
|
||||
if (ux == 0 && d.Y < 0) cellCost += LaneBias;
|
||||
else if (ux == 1 && d.Y > 0) cellCost += LaneBias;
|
||||
if (uy == 0 && d.X < 0) cellCost += LaneBias;
|
||||
else if (uy == 1 && d.X > 0) cellCost += LaneBias;
|
||||
}
|
||||
|
||||
int newCost = cellInfo[ p.Location.X, p.Location.Y ].MinCost + cellCost;
|
||||
int newCost = cellInfo[p.Location.X, p.Location.Y].MinCost + cellCost;
|
||||
|
||||
if( newCost >= cellInfo[ newHere.X, newHere.Y ].MinCost )
|
||||
if (newCost >= cellInfo[newHere.X, newHere.Y].MinCost)
|
||||
continue;
|
||||
|
||||
cellInfo[ newHere.X, newHere.Y ].Path = p.Location;
|
||||
cellInfo[ newHere.X, newHere.Y ].MinCost = newCost;
|
||||
|
||||
queue.Add( new PathDistance( newCost + est, newHere ) );
|
||||
cellInfo[newHere.X, newHere.Y].Path = p.Location;
|
||||
cellInfo[newHere.X, newHere.Y].MinCost = newCost;
|
||||
|
||||
queue.Add(new PathDistance(newCost + est, newHere));
|
||||
}
|
||||
return p.Location;
|
||||
}
|
||||
@@ -158,24 +178,28 @@ namespace OpenRA.Mods.RA.Move
|
||||
if (!world.Map.IsInMap(location.X, location.Y))
|
||||
return;
|
||||
|
||||
cellInfo[ location.X, location.Y ] = new CellInfo( 0, location, false );
|
||||
queue.Add( new PathDistance( heuristic( location ), location ) );
|
||||
cellInfo[location.X, location.Y] = new CellInfo(0, location, false);
|
||||
queue.Add(new PathDistance(heuristic(location), location));
|
||||
}
|
||||
|
||||
public static PathSearch Search( World world, MobileInfo mi, Player owner, bool checkForBlocked )
|
||||
public static PathSearch Search(World world, MobileInfo mi, Player owner, bool checkForBlocked)
|
||||
{
|
||||
var search = new PathSearch(world, mi, owner) {
|
||||
checkForBlocked = checkForBlocked };
|
||||
var search = new PathSearch(world, mi, owner)
|
||||
{
|
||||
checkForBlocked = checkForBlocked
|
||||
};
|
||||
return search;
|
||||
}
|
||||
|
||||
public static PathSearch FromPoint(World world, MobileInfo mi, Player owner, CPos from, CPos target, bool checkForBlocked)
|
||||
{
|
||||
var search = new PathSearch(world, mi, owner) {
|
||||
heuristic = DefaultEstimator( target ),
|
||||
checkForBlocked = checkForBlocked };
|
||||
var search = new PathSearch(world, mi, owner)
|
||||
{
|
||||
heuristic = DefaultEstimator(target),
|
||||
checkForBlocked = checkForBlocked
|
||||
};
|
||||
|
||||
search.AddInitialCell( from );
|
||||
search.AddInitialCell(from);
|
||||
return search;
|
||||
}
|
||||
|
||||
@@ -187,8 +211,8 @@ namespace OpenRA.Mods.RA.Move
|
||||
checkForBlocked = checkForBlocked
|
||||
};
|
||||
|
||||
foreach( var sl in froms )
|
||||
search.AddInitialCell( sl );
|
||||
foreach (var sl in froms)
|
||||
search.AddInitialCell(sl);
|
||||
|
||||
return search;
|
||||
}
|
||||
@@ -207,7 +231,7 @@ namespace OpenRA.Mods.RA.Move
|
||||
cellInfoPool.Enqueue(ci);
|
||||
}
|
||||
|
||||
CellInfo[ , ] InitCellInfo()
|
||||
CellInfo[,] InitCellInfo()
|
||||
{
|
||||
CellInfo[,] result = null;
|
||||
while (cellInfoPool.Count > 0)
|
||||
@@ -225,10 +249,10 @@ namespace OpenRA.Mods.RA.Move
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
result = new CellInfo[ world.Map.MapSize.X, world.Map.MapSize.Y ];
|
||||
result = new CellInfo[world.Map.MapSize.X, world.Map.MapSize.Y];
|
||||
|
||||
for( int x = 0 ; x < world.Map.MapSize.X ; x++ )
|
||||
for( int y = 0 ; y < world.Map.MapSize.Y ; y++ )
|
||||
for (int x = 0; x < world.Map.MapSize.X; x++)
|
||||
for (int y = 0; y < world.Map.MapSize.Y; y++)
|
||||
result[ x, y ] = new CellInfo( int.MaxValue, new CPos( x, y ), false );
|
||||
|
||||
return result;
|
||||
@@ -239,8 +263,8 @@ namespace OpenRA.Mods.RA.Move
|
||||
return here =>
|
||||
{
|
||||
CVec d = (here - destination).Abs();
|
||||
int diag = Math.Min( d.X, d.Y );
|
||||
int straight = Math.Abs( d.X - d.Y );
|
||||
int diag = Math.Min(d.X, d.Y);
|
||||
int straight = Math.Abs(d.X - d.Y);
|
||||
return (3400 * diag / 24) + (100 * straight);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user