From ea4f24d0b74e2a746d76c4751eb8e34ba6f3a115 Mon Sep 17 00:00:00 2001 From: tovl Date: Fri, 22 Mar 2019 20:42:05 +0100 Subject: [PATCH] Rework harvester automation. --- .../Activities/DeliverResources.cs | 31 ++--- ...esources.cs => FindAndDeliverResources.cs} | 117 +++++++++++++----- .../Activities/HarvestResource.cs | 3 +- .../Activities/HarvesterDockSequence.cs | 3 +- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 2 +- .../Properties/HarvesterProperties.cs | 9 +- .../Traits/BotModules/HarvesterBotModule.cs | 2 +- OpenRA.Mods.Common/Traits/Harvester.cs | 61 ++------- .../Rules/20190314/RefactorHarvesterIdle.cs | 9 -- 9 files changed, 117 insertions(+), 120 deletions(-) rename OpenRA.Mods.Common/Activities/{FindResources.cs => FindAndDeliverResources.cs} (57%) diff --git a/OpenRA.Mods.Common/Activities/DeliverResources.cs b/OpenRA.Mods.Common/Activities/DeliverResources.cs index 166473bdd3..c7b9f61d34 100644 --- a/OpenRA.Mods.Common/Activities/DeliverResources.cs +++ b/OpenRA.Mods.Common/Activities/DeliverResources.cs @@ -18,18 +18,23 @@ namespace OpenRA.Mods.Common.Activities { public class DeliverResources : Activity { - const int NextChooseTime = 100; - readonly IMove movement; readonly Harvester harv; + readonly Actor targetActor; bool isDocking; - int chosenTicks; - public DeliverResources(Actor self) + public DeliverResources(Actor self, Actor targetActor = null) { movement = self.Trait(); harv = self.Trait(); + this.targetActor = targetActor; + } + + protected override void OnFirstRun(Actor self) + { + if (targetActor != null && targetActor.IsInWorld) + harv.LinkProc(self, targetActor); } public override Activity Tick(Actor self) @@ -37,26 +42,14 @@ namespace OpenRA.Mods.Common.Activities if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); - return this; + if (ChildActivity != null) + return this; } if (IsCanceling || isDocking) return NextActivity; // Find the nearest best refinery if not explicitly ordered to a specific refinery: - if (harv.OwnerLinkedProc == null || !harv.OwnerLinkedProc.IsInWorld) - { - // Maybe we lost the owner-linked refinery: - harv.OwnerLinkedProc = null; - if (self.World.WorldTick - chosenTicks > NextChooseTime) - { - harv.ChooseNewProc(self, null); - chosenTicks = self.World.WorldTick; - } - } - else - harv.LinkProc(self, harv.OwnerLinkedProc); - if (harv.LinkedProc == null || !harv.LinkedProc.IsInWorld) harv.ChooseNewProc(self, null); @@ -74,7 +67,7 @@ namespace OpenRA.Mods.Common.Activities if (self.Location != proc.Location + iao.DeliveryOffset) { foreach (var n in self.TraitsImplementing()) - n.MovingToRefinery(self, proc, null); + n.MovingToRefinery(self, proc, new FindAndDeliverResources(self)); QueueChild(self, movement.MoveTo(proc.Location + iao.DeliveryOffset, 0), true); return this; diff --git a/OpenRA.Mods.Common/Activities/FindResources.cs b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs similarity index 57% rename from OpenRA.Mods.Common/Activities/FindResources.cs rename to OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs index 24e1bcf6cb..44f8e40a1b 100644 --- a/OpenRA.Mods.Common/Activities/FindResources.cs +++ b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs @@ -18,7 +18,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { - public class FindResources : Activity + public class FindAndDeliverResources : Activity { readonly Harvester harv; readonly HarvesterInfo harvInfo; @@ -27,10 +27,14 @@ namespace OpenRA.Mods.Common.Activities readonly ResourceClaimLayer claimLayer; readonly IPathFinder pathFinder; readonly DomainIndex domainIndex; + readonly Actor deliverActor; CPos? orderLocation; + bool hasDeliveredLoad; + bool hasHarvestedCell; + bool hasWaited; - public FindResources(Actor self) + public FindAndDeliverResources(Actor self, Actor deliverActor = null) { harv = self.Trait(); harvInfo = self.Info.TraitInfo(); @@ -39,20 +43,36 @@ namespace OpenRA.Mods.Common.Activities claimLayer = self.World.WorldActor.Trait(); pathFinder = self.World.WorldActor.Trait(); domainIndex = self.World.WorldActor.Trait(); + this.deliverActor = deliverActor; } - public FindResources(Actor self, CPos orderLocation) - : this(self) + public FindAndDeliverResources(Actor self, CPos orderLocation) + : this(self, null) { this.orderLocation = orderLocation; } protected override void OnFirstRun(Actor self) { - // Without this, multiple "Harvest" orders queued directly after each other - // will be skipped because the harvester starts off full. - if (harv.IsFull) - QueueChild(self, new DeliverResources(self)); + // If an explicit "harvest" order is given, direct the harvester to the ordered location instead of + // the previous harvested cell for the initial search. + if (orderLocation != null) + { + harv.LastHarvestedCell = orderLocation; + + // If two "harvest" orders are issued consecutively, we deliver the load first if needed. + // We have to make sure the actual "harvest" order is not skipped if a third order is queued, + // so we keep deliveredLoad false. + if (harv.IsFull) + QueueChild(self, new DeliverResources(self), true); + } + + // If an explicit "deliver" order is given, the harvester goes immediately to the refinery. + if (deliverActor != null) + { + QueueChild(self, new DeliverResources(self, deliverActor), true); + hasDeliveredLoad = true; + } } public override Activity Tick(Actor self) @@ -60,38 +80,60 @@ namespace OpenRA.Mods.Common.Activities if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); - return this; + if (ChildActivity != null) + return this; } if (IsCanceling) return NextActivity; - if (harv.IsFull) + if (NextActivity != null) { - return NextActivity; + // Interrupt automated harvesting after clearing the first cell. + if (!harvInfo.QueueFullLoad && (hasHarvestedCell || harv.LastSearchFailed)) + return NextActivity; + + // Interrupt automated harvesting after first complete harvest cycle. + if (hasDeliveredLoad || harv.IsFull) + return NextActivity; } - // If an explicit order is given, direct the harvester to the ordered location instead of the previous - // harvested cell for the initial search. - if (orderLocation != null) + // Are we full or have nothing more to gather? Deliver resources. + if (harv.IsFull || (!harv.IsEmpty && harv.LastSearchFailed)) { - harv.LastHarvestedCell = orderLocation; - orderLocation = null; + QueueChild(self, new DeliverResources(self), true); + hasDeliveredLoad = true; + return this; } - 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) + // After a failed search, wait and sit still for a bit before searching again. + if (harv.LastSearchFailed && !hasWaited) + { + QueueChild(self, new Wait(harv.Info.WaitDuration), true); + hasWaited = true; + return this; + } + + var closestHarvestableCell = ClosestHarvestablePos(self); + + // If no resources are found near the current field, search near the refinery instead. + // If that doesn't help, give up for now. + if (!closestHarvestableCell.HasValue) { - // If no resources are found near the current field, search near the refinery instead. - // If that doesn't help, give up for now. if (harv.LastHarvestedCell != null) - harv.LastHarvestedCell = null; + { + harv.LastHarvestedCell = null; // Forces search from backup position. + closestHarvestableCell = ClosestHarvestablePos(self); + harv.LastSearchFailed = !closestHarvestableCell.HasValue; + } else harv.LastSearchFailed = true; + } + if (harv.LastSearchFailed) + { + // If no harvestable position could be found and we are at the refinery, get out of the way + // of the refinery entrance. var lastproc = harv.LastLinkedProc ?? harv.LinkedProc; if (lastproc != null && !lastproc.Disposed) { @@ -103,15 +145,14 @@ namespace OpenRA.Mods.Common.Activities var moveTo = mobile.NearestMoveableCell(unblockCell, 1, 5); self.SetTargetLine(Target.FromCell(self.World, moveTo), Color.Green, false); QueueChild(self, mobile.MoveTo(moveTo, 1), true); - return this; } } - return NextActivity; + return this; } // Attempt to claim the target cell - if (!claimLayer.TryClaimCell(self, closestHarvestablePosition.Value)) + if (!claimLayer.TryClaimCell(self, closestHarvestableCell.Value)) { QueueChild(self, new Wait(25), true); return this; @@ -120,11 +161,12 @@ namespace OpenRA.Mods.Common.Activities harv.LastSearchFailed = false; foreach (var n in self.TraitsImplementing()) - n.MovingToResources(self, closestHarvestablePosition.Value, new FindResources(self)); + n.MovingToResources(self, closestHarvestableCell.Value, new FindAndDeliverResources(self)); - self.SetTargetLine(Target.FromCell(self.World, closestHarvestablePosition.Value), Color.Red, false); - QueueChild(self, mobile.MoveTo(closestHarvestablePosition.Value, 1), true); + self.SetTargetLine(Target.FromCell(self.World, closestHarvestableCell.Value), Color.Red, false); + QueueChild(self, mobile.MoveTo(closestHarvestableCell.Value, 1), true); QueueChild(self, new HarvestResource(self)); + hasHarvestedCell = true; return this; } @@ -134,8 +176,19 @@ namespace OpenRA.Mods.Common.Activities /// CPos? ClosestHarvestablePos(Actor self) { - if (harv.CanHarvestCell(self, self.Location) && claimLayer.CanClaimCell(self, self.Location)) - return self.Location; + // Harvesters should respect an explicit harvest order instead of harvesting the current cell. + if (orderLocation == null) + { + if (harv.CanHarvestCell(self, self.Location) && claimLayer.CanClaimCell(self, self.Location)) + return self.Location; + } + else + { + if (harv.CanHarvestCell(self, orderLocation.Value) && claimLayer.CanClaimCell(self, orderLocation.Value)) + return orderLocation; + + orderLocation = null; + } // Determine where to search from and how far to search: var searchFromLoc = harv.LastHarvestedCell ?? GetSearchFromLocation(self); diff --git a/OpenRA.Mods.Common/Activities/HarvestResource.cs b/OpenRA.Mods.Common/Activities/HarvestResource.cs index 4ecdaac9a0..58e81f59f0 100644 --- a/OpenRA.Mods.Common/Activities/HarvestResource.cs +++ b/OpenRA.Mods.Common/Activities/HarvestResource.cs @@ -39,7 +39,8 @@ namespace OpenRA.Mods.Common.Activities if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); - return this; + if (ChildActivity != null) + return this; } if (IsCanceling) diff --git a/OpenRA.Mods.Common/Activities/HarvesterDockSequence.cs b/OpenRA.Mods.Common/Activities/HarvesterDockSequence.cs index 92f60ad1fe..1f29cb3cf6 100644 --- a/OpenRA.Mods.Common/Activities/HarvesterDockSequence.cs +++ b/OpenRA.Mods.Common/Activities/HarvesterDockSequence.cs @@ -50,7 +50,8 @@ namespace OpenRA.Mods.Common.Activities if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); - return this; + if (ChildActivity != null) + return this; } switch (dockingState) diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 4d34acdec4..38b8def78d 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -89,7 +89,7 @@ - + diff --git a/OpenRA.Mods.Common/Scripting/Properties/HarvesterProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/HarvesterProperties.cs index 1edb05a6f5..6e3eb66545 100644 --- a/OpenRA.Mods.Common/Scripting/Properties/HarvesterProperties.cs +++ b/OpenRA.Mods.Common/Scripting/Properties/HarvesterProperties.cs @@ -9,6 +9,7 @@ */ #endregion +using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Scripting; using OpenRA.Traits; @@ -18,19 +19,15 @@ namespace OpenRA.Mods.Common.Scripting [ScriptPropertyGroup("Movement")] public class HarvesterProperties : ScriptActorProperties, Requires { - readonly Harvester harvester; - public HarvesterProperties(ScriptContext context, Actor self) : base(context, self) - { - harvester = self.Trait(); - } + { } [ScriptActorPropertyActivity] [Desc("Search for nearby resources and begin harvesting.")] public void FindResources() { - harvester.ContinueHarvesting(Self); + Self.QueueActivity(new FindAndDeliverResources(Self)); } } } diff --git a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs index 066db61c5d..9c419982d6 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs @@ -114,7 +114,7 @@ namespace OpenRA.Mods.Common.Traits if (!h.Key.IsIdle) { var act = h.Key.CurrentActivity; - if (!h.Value.Harvester.LastSearchFailed || act.NextActivity == null || act.NextActivity.GetType() != typeof(FindResources)) + if (!h.Value.Harvester.LastSearchFailed || act.NextActivity == null || act.NextActivity.GetType() != typeof(FindAndDeliverResources)) continue; } diff --git a/OpenRA.Mods.Common/Traits/Harvester.cs b/OpenRA.Mods.Common/Traits/Harvester.cs index 270cf66a50..e32cfa9bc8 100644 --- a/OpenRA.Mods.Common/Traits/Harvester.cs +++ b/OpenRA.Mods.Common/Traits/Harvester.cs @@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Search radius (in cells) from the last harvest order location to find more resources.")] public readonly int SearchFromOrderRadius = 12; - [Desc("Duration to wait before searching for resources again.")] + [Desc("Interval to wait between searches when there are no resources nearby.")] public readonly int WaitDuration = 25; [Desc("Find a new refinery to unload at if more than this many harvesters are already waiting.")] @@ -71,6 +71,9 @@ namespace OpenRA.Mods.Common.Traits [Desc("The pathfinding cost penalty applied for each harvester waiting to unload at a refinery.")] public readonly int UnloadQueueCostModifier = 12; + [Desc("Does the unit queue harvesting runs instead of individual harvest actions?")] + public readonly bool QueueFullLoad = false; + [GrantedConditionReference] [Desc("Condition to grant while empty.")] public readonly string EmptyCondition = null; @@ -82,18 +85,16 @@ namespace OpenRA.Mods.Common.Traits } public class Harvester : IIssueOrder, IResolveOrder, IPips, IOrderVoice, - ISpeedModifier, ISync, INotifyCreated, INotifyIdle + ISpeedModifier, ISync, INotifyCreated { public readonly HarvesterInfo Info; readonly Mobile mobile; readonly ResourceLayer resLayer; readonly ResourceClaimLayer claimLayer; readonly Dictionary contents = new Dictionary(); - bool idleSmart; INotifyHarvesterAction[] notifyHarvesterAction; ConditionManager conditionManager; int conditionToken = ConditionManager.InvalidConditionToken; - int idleDuration; [Sync] public bool LastSearchFailed; [Sync] public Actor OwnerLinkedProc = null; @@ -120,7 +121,6 @@ namespace OpenRA.Mods.Common.Traits mobile = self.Trait(); resLayer = self.World.WorldActor.Trait(); claimLayer = self.World.WorldActor.Trait(); - idleSmart = info.SearchOnCreation; self.QueueActivity(new CallFunc(() => ChooseNewProc(self, null))); } @@ -130,6 +130,10 @@ namespace OpenRA.Mods.Common.Traits notifyHarvesterAction = self.TraitsImplementing().ToArray(); conditionManager = self.TraitOrDefault(); UpdateCondition(self); + + // Note: This is queued in a FrameEndTask because otherwise the activity is dropped/overridden while moving out of a factory. + if (Info.SearchOnCreation) + self.World.AddFrameEndTask(w => self.QueueActivity(new FindAndDeliverResources(self))); } public void SetProcLines(Actor proc) @@ -164,12 +168,6 @@ namespace OpenRA.Mods.Common.Traits LinkProc(self, ClosestProc(self, ignore)); } - public void ContinueHarvesting(Actor self) - { - // Move out of the refinery dock and continue harvesting - self.QueueActivity(new FindResources(self)); - } - bool IsAcceptableProcType(Actor proc) { return Info.DeliveryBuildings.Count == 0 || @@ -240,30 +238,6 @@ namespace OpenRA.Mods.Common.Traits UpdateCondition(self); } - void INotifyIdle.TickIdle(Actor self) - { - // Should we be intelligent while idle? - if (!idleSmart) - return; - - // Are we not empty? Deliver resources: - if (!IsEmpty) - { - self.QueueActivity(new DeliverResources(self)); - return; - } - - if (LastSearchFailed) - { - // Wait a bit before searching again. - if (idleDuration++ < Info.WaitDuration) - return; - } - - idleDuration = 0; - self.QueueActivity(new FindResources(self)); - } - // Returns true when unloading is complete public bool TickUnload(Actor self, Actor proc) { @@ -343,7 +317,6 @@ namespace OpenRA.Mods.Common.Traits { // NOTE: An explicit harvest order allows the harvester to decide which refinery to deliver to. LinkProc(self, OwnerLinkedProc = null); - idleSmart = true; CPos loc; if (order.Target.Type != TargetType.Invalid) @@ -361,7 +334,7 @@ namespace OpenRA.Mods.Common.Traits self.SetTargetLine(Target.FromCell(self.World, loc), Color.Red); // FindResources takes care of calling INotifyHarvesterAction - self.QueueActivity(order.Queued, new FindResources(self, loc)); + self.QueueActivity(order.Queued, new FindAndDeliverResources(self, loc)); } else if (order.OrderString == "Deliver") { @@ -370,30 +343,18 @@ namespace OpenRA.Mods.Common.Traits if (order.Target.Type != TargetType.Actor) return; - // NOTE: An explicit deliver order forces the harvester to always deliver to this refinery. var targetActor = order.Target.Actor; var iao = targetActor.TraitOrDefault(); if (iao == null || !iao.AllowDocking || !IsAcceptableProcType(targetActor)) return; - if (targetActor != OwnerLinkedProc) - LinkProc(self, OwnerLinkedProc = targetActor); - - idleSmart = true; - self.SetTargetLine(order.Target, Color.Green); - self.QueueActivity(order.Queued, new DeliverResources(self)); - - foreach (var n in notifyHarvesterAction) - n.MovingToRefinery(self, targetActor, null); + self.QueueActivity(order.Queued, new FindAndDeliverResources(self, targetActor)); } else if (order.OrderString == "Stop" || order.OrderString == "Move") { foreach (var n in notifyHarvesterAction) n.MovementCancelled(self); - - // Turn off idle smarts to obey the stop/move: - idleSmart = false; } } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20190314/RefactorHarvesterIdle.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20190314/RefactorHarvesterIdle.cs index bb3717760c..cd0025c60f 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20190314/RefactorHarvesterIdle.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20190314/RefactorHarvesterIdle.cs @@ -28,15 +28,6 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules readonly List locations = new List(); - public override IEnumerable AfterUpdate(ModData modData) - { - if (locations.Any()) - yield return "The MaxIdleDuration parameter has been removed from the harvester logic on the following actors:\n" + - UpdateUtils.FormatMessageList(locations) + "\n\n"; - - locations.Clear(); - } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) { foreach (var t in actorNode.ChildrenMatching("Harvester"))