diff --git a/OpenRA.Mods.Common/Activities/FindResources.cs b/OpenRA.Mods.Common/Activities/FindResources.cs index efe54b8db4..eab09695d9 100644 --- a/OpenRA.Mods.Common/Activities/FindResources.cs +++ b/OpenRA.Mods.Common/Activities/FindResources.cs @@ -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(); + + 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; + } + + /// + /// Finds the closest harvestable pos between the current position of the harvester + /// and the last order location + /// + 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(); - 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 GetTargets(Actor self) diff --git a/OpenRA.Mods.Common/Pathfinder/BasePathSearch.cs b/OpenRA.Mods.Common/Pathfinder/BasePathSearch.cs index bbb597841a..981eb065a4 100644 --- a/OpenRA.Mods.Common/Pathfinder/BasePathSearch.cs +++ b/OpenRA.Mods.Common/Pathfinder/BasePathSearch.cs @@ -110,6 +110,7 @@ namespace OpenRA.Mods.Common.Pathfinder public bool Debug { get; set; } string id; protected Func heuristic; + protected Func 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(); diff --git a/OpenRA.Mods.Common/Pathfinder/Constants.cs b/OpenRA.Mods.Common/Pathfinder/Constants.cs index 57036a4116..f3e0573afa 100644 --- a/OpenRA.Mods.Common/Pathfinder/Constants.cs +++ b/OpenRA.Mods.Common/Pathfinder/Constants.cs @@ -25,5 +25,7 @@ namespace OpenRA.Mods.Common.Pathfinder /// a unit took to move one cell diagonally) /// public const int DiagonalCellCost = 177; + + public const int InvalidNode = int.MaxValue; } } diff --git a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs index bdd1177b9c..a84c542121 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Pathfinder /// /// Gets all the Connections for a given node in the graph /// - ICollection GetConnections(CPos position); + IEnumerable GetConnections(CPos position); /// /// 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 GetConnections(CPos position) + public IEnumerable 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; } } } } diff --git a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs index e1ecf22b06..254317d030 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs @@ -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>(); } - 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 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)) { diff --git a/OpenRA.Mods.Common/Traits/Harvester.cs b/OpenRA.Mods.Common/Traits/Harvester.cs index 32db23f576..47cf4c52c8 100644 --- a/OpenRA.Mods.Common/Traits/Harvester.cs +++ b/OpenRA.Mods.Common/Traits/Harvester.cs @@ -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(); + loc = order.TargetLocation; + var territory = self.World.WorldActor.TraitOrDefault(); 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(); - 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(); - 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(); + 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().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]; }