diff --git a/OpenRA.Mods.Common/AI/BaseBuilder.cs b/OpenRA.Mods.Common/AI/BaseBuilder.cs index 105c512c48..8e5b568147 100644 --- a/OpenRA.Mods.Common/AI/BaseBuilder.cs +++ b/OpenRA.Mods.Common/AI/BaseBuilder.cs @@ -120,7 +120,7 @@ namespace OpenRA.Mods.Common.AI bool TickQueue(ProductionQueue queue) { - var currentBuilding = queue.CurrentItem(); + var currentBuilding = queue.AllQueued().FirstOrDefault(); // Waiting to build something if (currentBuilding == null && failCount < ai.Info.MaximumFailedPlacementAttempts) diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index cb88344b72..f74307f683 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -947,7 +947,7 @@ namespace OpenRA.Mods.Common.AI void BuildUnit(string category, bool buildRandom) { // Pick a free queue - var queue = FindQueues(category).FirstOrDefault(q => q.CurrentItem() == null); + var queue = FindQueues(category).FirstOrDefault(q => !q.AllQueued().Any()); if (queue == null) return; @@ -973,7 +973,7 @@ namespace OpenRA.Mods.Common.AI void BuildUnit(string category, string name) { - var queue = FindQueues(category).FirstOrDefault(q => q.CurrentItem() == null); + var queue = FindQueues(category).FirstOrDefault(q => !q.AllQueued().Any()); if (queue == null) return; diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 7fb7cb4698..463e8d865e 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -149,7 +149,7 @@ namespace OpenRA.Mods.Common.Orders public void Tick(World world) { - if (queue.CurrentItem() == null || queue.CurrentItem().Item != actorInfo.Name) + if (queue.AllQueued().All(i => !i.Done || i.Item != actorInfo.Name)) world.CancelInputMode(); if (preview == null) diff --git a/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs index 3bd90345cc..f651f60065 100644 --- a/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs +++ b/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs @@ -114,7 +114,7 @@ namespace OpenRA.Mods.Common.Scripting return false; var queue = queues.Where(q => actorTypes.All(t => GetBuildableInfo(t).Queue.Contains(q.Info.Type))) - .FirstOrDefault(q => q.CurrentItem() == null); + .FirstOrDefault(q => !q.AllQueued().Any()); if (queue == null) return false; @@ -163,7 +163,7 @@ namespace OpenRA.Mods.Common.Scripting return true; return queues.Where(q => GetBuildableInfo(actorType).Queue.Contains(q.Info.Type)) - .Any(q => q.CurrentItem() != null); + .Any(q => q.AllQueued().Any()); } BuildableInfo GetBuildableInfo(string actorType) @@ -225,7 +225,7 @@ namespace OpenRA.Mods.Common.Scripting if (queueTypes.Any(t => !queues.ContainsKey(t) || productionHandlers.ContainsKey(t))) return false; - if (queueTypes.Any(t => queues[t].CurrentItem() != null)) + if (queueTypes.Any(t => queues[t].AllQueued().Any())) return false; if (actionFunc != null) @@ -270,7 +270,7 @@ namespace OpenRA.Mods.Common.Scripting if (!queues.ContainsKey(queue)) return true; - return productionHandlers.ContainsKey(queue) || queues[queue].CurrentItem() != null; + return productionHandlers.ContainsKey(queue) || queues[queue].AllQueued().Any(); } BuildableInfo GetBuildableInfo(string actorType) diff --git a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs index 8f66ba0094..989c985746 100644 --- a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs @@ -126,7 +126,7 @@ namespace OpenRA.Mods.Common.Traits if (p.Trait.Produce(p.Actor, unit, type, inits)) { - FinishProduction(); + EndProduction(Queue.FirstOrDefault(i => i.Done && i.Item == unit.Name)); return true; } } diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index d581c89757..bad9411c8d 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -70,11 +70,17 @@ namespace OpenRA.Mods.Common.Traits var actorInfo = self.World.Map.Rules.Actors[order.TargetString]; var queue = targetActor.TraitsImplementing() - .FirstOrDefault(q => q.CanBuild(actorInfo) && q.CurrentItem() != null && q.CurrentItem().Item == order.TargetString && q.CurrentItem().RemainingTime == 0); + .FirstOrDefault(q => q.CanBuild(actorInfo) && q.AllQueued().Any(i => i.Done && i.Item == order.TargetString)); if (queue == null) return; + // Find the ProductionItem associated with the building that we are trying to place + var item = queue.AllQueued().FirstOrDefault(i => i.Done && i.Item == order.TargetString); + + if (item == null) + return; + var producer = queue.MostLikelyProducer(); var faction = producer.Trait != null ? producer.Trait.Faction : self.Owner.Faction.InternalName; var buildingInfo = actorInfo.TraitInfo(); @@ -173,7 +179,7 @@ namespace OpenRA.Mods.Common.Traits foreach (var nbp in producer.Actor.TraitsImplementing()) nbp.BuildingPlaced(producer.Actor); - queue.FinishProduction(); + queue.EndProduction(item); if (buildingInfo.RequiresBaseProvider) { diff --git a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs index b55ef4994a..9db0dab1d7 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs @@ -97,26 +97,20 @@ namespace OpenRA.Mods.Common.Traits readonly Actor self; // A list of things we could possibly build - readonly Dictionary producible = new Dictionary(); - readonly List queue = new List(); + protected readonly Dictionary Producible = new Dictionary(); + protected readonly List Queue = new List(); readonly IEnumerable allProducibles; readonly IEnumerable buildableProducibles; - Production[] productionTraits; + protected Production[] productionTraits; // Will change if the owner changes PowerManager playerPower; - PlayerResources playerResources; + protected PlayerResources playerResources; protected DeveloperMode developerMode; public Actor Actor { get { return self; } } - [Sync] public int QueueLength { get { return queue.Count; } } - [Sync] public int CurrentRemainingCost { get { return QueueLength == 0 ? 0 : queue[0].RemainingCost; } } - [Sync] public int CurrentRemainingTime { get { return QueueLength == 0 ? 0 : queue[0].RemainingTime; } } - [Sync] public int CurrentSlowdown { get { return QueueLength == 0 ? 0 : queue[0].Slowdown; } } - [Sync] public bool CurrentPaused { get { return QueueLength != 0 && queue[0].Paused; } } - [Sync] public bool CurrentDone { get { return QueueLength != 0 && queue[0].Done; } } [Sync] public bool Enabled { get; protected set; } public string Faction { get; private set; } @@ -134,8 +128,8 @@ namespace OpenRA.Mods.Common.Traits Enabled = IsValidFaction; CacheProducibles(playerActor); - allProducibles = producible.Where(a => a.Value.Buildable || a.Value.Visible).Select(a => a.Key); - buildableProducibles = producible.Where(a => a.Value.Buildable).Select(a => a.Key); + allProducibles = Producible.Where(a => a.Value.Buildable || a.Value.Visible).Select(a => a.Key); + buildableProducibles = Producible.Where(a => a.Value.Buildable).Select(a => a.Key); } void INotifyCreated.Created(Actor self) @@ -150,12 +144,10 @@ namespace OpenRA.Mods.Common.Traits protected void ClearQueue() { - if (queue.Count == 0) - return; - // Refund the current item - playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost); - queue.Clear(); + foreach (var item in Queue) + playerResources.GiveCash(item.TotalCost - item.RemainingCost); + Queue.Clear(); } void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) @@ -188,7 +180,7 @@ namespace OpenRA.Mods.Common.Traits void CacheProducibles(Actor playerActor) { - producible.Clear(); + Producible.Clear(); if (!Enabled) return; @@ -198,7 +190,7 @@ namespace OpenRA.Mods.Common.Traits { var bi = a.TraitInfo(); - producible.Add(a, new ProductionState()); + Producible.Add(a, new ProductionState()); ttc.Add(a.Name, bi.Prerequisites, bi.BuildLimit, this); } } @@ -214,38 +206,38 @@ namespace OpenRA.Mods.Common.Traits public void PrerequisitesAvailable(string key) { - producible[self.World.Map.Rules.Actors[key]].Buildable = true; + Producible[self.World.Map.Rules.Actors[key]].Buildable = true; } public void PrerequisitesUnavailable(string key) { - producible[self.World.Map.Rules.Actors[key]].Buildable = false; + Producible[self.World.Map.Rules.Actors[key]].Buildable = false; } public void PrerequisitesItemHidden(string key) { - producible[self.World.Map.Rules.Actors[key]].Visible = false; + Producible[self.World.Map.Rules.Actors[key]].Visible = false; } public void PrerequisitesItemVisible(string key) { - producible[self.World.Map.Rules.Actors[key]].Visible = true; + Producible[self.World.Map.Rules.Actors[key]].Visible = true; } - public ProductionItem CurrentItem() + public virtual bool IsProducing(ProductionItem item) { - return queue.ElementAtOrDefault(0); + return Queue.Count > 0 && Queue[0] == item; } public virtual IEnumerable AllQueued() { - return queue; + return Queue; } public virtual IEnumerable AllItems() { if (developerMode.AllTech) - return producible.Keys; + return Producible.Keys; return allProducibles; } @@ -255,7 +247,7 @@ namespace OpenRA.Mods.Common.Traits if (!Enabled) return Enumerable.Empty(); if (developerMode.AllTech) - return producible.Keys; + return Producible.Keys; return buildableProducibles; } @@ -263,7 +255,7 @@ namespace OpenRA.Mods.Common.Traits public bool CanBuild(ActorInfo actor) { ProductionState ps; - if (!producible.TryGetValue(actor, out ps)) + if (!Producible.TryGetValue(actor, out ps)) return false; return ps.Buildable || developerMode.AllTech; @@ -289,21 +281,32 @@ namespace OpenRA.Mods.Common.Traits ClearQueue(); Enabled = IsValidFaction && anyEnabledProduction; - TickInner(self, !anyUnpausedProduction); } protected virtual void TickInner(Actor self, bool allProductionPaused) { - while (queue.Count > 0 && BuildableItems().All(b => b.Name != queue[0].Item)) - { - // Refund what's been paid so far - playerResources.GiveCash(queue[0].TotalCost - queue[0].RemainingCost); - FinishProduction(); - } + CancelUnbuildableItems(); - if (queue.Count > 0 && !allProductionPaused) - queue[0].Tick(playerResources); + if (Queue.Count > 0 && !allProductionPaused) + Queue[0].Tick(playerResources); + } + + protected void CancelUnbuildableItems() + { + var buildableNames = BuildableItems().Select(b => b.Name).ToList(); + + // EndProduction removes the item from the queue, so we enumerate + // by index in reverse to avoid issues with index reassignment + for (var i = Queue.Count - 1; i >= 0; i--) + { + if (buildableNames.Contains(Queue[i].Item)) + continue; + + // Refund what's been paid so far + playerResources.GiveCash(Queue[i].TotalCost - Queue[i].RemainingCost); + EndProduction(Queue[i]); + } } public bool CanQueue(ActorInfo actor, out string notificationAudio) @@ -316,13 +319,13 @@ namespace OpenRA.Mods.Common.Traits if (!developerMode.AllTech) { - if (Info.QueueLimit > 0 && queue.Count >= Info.QueueLimit) + if (Info.QueueLimit > 0 && Queue.Count >= Info.QueueLimit) { notificationAudio = Info.LimitedAudio; return false; } - var queueCount = queue.Count(i => i.Item == actor.Name); + var queueCount = Queue.Count(i => i.Item == actor.Name); if (Info.ItemLimit > 0 && queueCount >= Info.ItemLimit) { notificationAudio = Info.LimitedAudio; @@ -367,14 +370,14 @@ namespace OpenRA.Mods.Common.Traits if (!developerMode.AllTech) { if (Info.QueueLimit > 0) - fromLimit = Info.QueueLimit - queue.Count; + fromLimit = Info.QueueLimit - Queue.Count; if (Info.ItemLimit > 0) - fromLimit = Math.Min(fromLimit, Info.ItemLimit - queue.Count(i => i.Item == order.TargetString)); + fromLimit = Math.Min(fromLimit, Info.ItemLimit - Queue.Count(i => i.Item == order.TargetString)); if (bi.BuildLimit > 0) { - var inQueue = queue.Count(pi => pi.Item == order.TargetString); + var inQueue = Queue.Count(pi => pi.Item == order.TargetString); var owned = self.Owner.World.ActorsHavingTrait().Count(a => a.Info.Name == order.TargetString && a.Owner == self.Owner); fromLimit = Math.Min(fromLimit, bi.BuildLimit - (inQueue + owned)); } @@ -408,8 +411,7 @@ namespace OpenRA.Mods.Common.Traits break; case "PauseProduction": - if (queue.Count > 0 && queue[0].Item == order.TargetString) - queue[0].Pause(order.ExtraData != 0); + PauseProduction(order.TargetString, order.ExtraData != 0); break; case "CancelProduction": @@ -434,6 +436,13 @@ namespace OpenRA.Mods.Common.Traits return time; } + protected void PauseProduction(string itemName, bool paused) + { + var item = Queue.FirstOrDefault(a => a.Item == itemName); + if (item != null) + item.Pause(paused); + } + protected void CancelProduction(string itemName, uint numberToCancel) { for (var i = 0; i < numberToCancel; i++) @@ -443,33 +452,32 @@ namespace OpenRA.Mods.Common.Traits bool CancelProductionInner(string itemName) { - var lastIndex = queue.FindLastIndex(a => a.Item == itemName); + var item = Queue.LastOrDefault(a => a.Item == itemName); - if (lastIndex > 0) - queue.RemoveAt(lastIndex); - else if (lastIndex == 0) + if (item != null) { - var item = queue[0]; - // Refund what has been paid playerResources.GiveCash(item.TotalCost - item.RemainingCost); - FinishProduction(); + EndProduction(item); + return true; } - else - return false; - return true; + return false; } - public void FinishProduction() + public void EndProduction(ProductionItem item) { - if (queue.Count != 0) - queue.RemoveAt(0); + Queue.Remove(item); } protected void BeginProduction(ProductionItem item) { - queue.Add(item); + Queue.Add(item); + } + + public virtual int RemainingTimeActual(ProductionItem item) + { + return item.RemainingTimeActual; } // Returns the actor/trait that is most likely (but not necessarily guaranteed) to produce something in this queue @@ -504,7 +512,7 @@ namespace OpenRA.Mods.Common.Traits if (!mostLikelyProducerTrait.IsTraitPaused && mostLikelyProducerTrait.Produce(self, unit, type, inits)) { - FinishProduction(); + EndProduction(Queue.FirstOrDefault(i => i.Done && i.Item == unit.Name)); return true; } diff --git a/OpenRA.Mods.Common/Traits/Render/ProductionBar.cs b/OpenRA.Mods.Common/Traits/Render/ProductionBar.cs index 686421cb02..bda3e392a4 100644 --- a/OpenRA.Mods.Common/Traits/Render/ProductionBar.cs +++ b/OpenRA.Mods.Common/Traits/Render/ProductionBar.cs @@ -67,7 +67,7 @@ namespace OpenRA.Mods.Common.Traits.Render void ITick.Tick(Actor self) { - var current = queue.CurrentItem(); + var current = queue.AllQueued().Where(i => i.Started).OrderBy(i => i.RemainingTime).FirstOrDefault(); value = current != null ? 1 - (float)current.RemainingCost / current.TotalCost : 0; } diff --git a/OpenRA.Mods.Common/Traits/Render/WithProductionOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithProductionOverlay.cs index d8558561c5..a057f44274 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithProductionOverlay.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithProductionOverlay.cs @@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Traits.Render bool IsProducing { - get { return queues != null && queues.Any(q => q.Enabled && q.CurrentItem() != null && !q.CurrentPaused); } + get { return queues != null && queues.Any(q => q.Enabled && q.AllQueued().Any(i => !i.Paused && i.Started)); } } public WithProductionOverlay(Actor self, WithProductionOverlayInfo info) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs index 261ea9da52..ca36a8dd59 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs @@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var chromeName = button.ProductionGroup.ToLowerInvariant(); var icon = button.Get("ICON"); icon.GetImageName = () => button.IsDisabled() ? chromeName + "-disabled" : - queues.Any(q => q.CurrentDone) ? chromeName + "-alert" : chromeName; + queues.Any(q => q.AllQueued().Any(i => i.Done)) ? chromeName + "-alert" : chromeName; } [ObjectCreator.UseCtor] diff --git a/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs b/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs index 59dcc7b7f4..aa26305652 100644 --- a/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs @@ -114,7 +114,7 @@ namespace OpenRA.Mods.Common.Widgets if (!clocks.ContainsKey(queue.Trait)) clocks.Add(queue.Trait, new Animation(world, ClockAnimation)); - var current = queue.Trait.CurrentItem(); + var current = queue.Trait.AllQueued().FirstOrDefault(); if (current == null || queue.i >= icons.Length) continue; @@ -162,7 +162,7 @@ namespace OpenRA.Mods.Common.Widgets if (item.Done) return "READY"; - return WidgetUtils.FormatTime(item.RemainingTimeActual, timestep); + return WidgetUtils.FormatTime(item.Queue.RemainingTimeActual(item), timestep); } public override Widget Clone() diff --git a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs index 8662d75db8..6e1c90d3c7 100644 --- a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs @@ -334,7 +334,9 @@ namespace OpenRA.Mods.Common.Widgets bool HandleEvent(ProductionIcon icon, MouseButton btn, Modifiers modifiers) { var startCount = modifiers.HasModifier(Modifiers.Shift) ? 5 : 1; - var cancelCount = modifiers.HasModifier(Modifiers.Ctrl) ? CurrentQueue.QueueLength : startCount; + + // PERF: avoid an unnecessary enumeration by casting back to its known type + var cancelCount = modifiers.HasModifier(Modifiers.Ctrl) ? ((List)CurrentQueue.AllQueued()).Count : startCount; var item = icon.Queued.FirstOrDefault(); var handled = btn == MouseButton.Left ? HandleLeftClick(item, icon, startCount) : btn == MouseButton.Right ? HandleRightClick(item, icon, cancelCount) @@ -465,7 +467,7 @@ namespace OpenRA.Mods.Common.Widgets if (total > 0) { var first = icon.Queued[0]; - var waiting = first != CurrentQueue.CurrentItem() && !first.Done; + var waiting = !CurrentQueue.IsProducing(first) && !first.Done; if (first.Done) { if (ReadyTextStyle == ReadyTextStyleOptions.Solid || orderManager.LocalFrameNumber * worldRenderer.World.Timestep / 360 % 2 == 0) @@ -478,7 +480,7 @@ namespace OpenRA.Mods.Common.Widgets icon.Pos + holdOffset, Color.White, Color.Black, 1); else if (!waiting && DrawTime) - overlayFont.DrawTextWithContrast(WidgetUtils.FormatTime(first.RemainingTimeActual, World.Timestep), + overlayFont.DrawTextWithContrast(WidgetUtils.FormatTime(first.Queue.RemainingTimeActual(first), World.Timestep), icon.Pos + timeOffset, Color.White, Color.Black, 1); diff --git a/OpenRA.Mods.Common/Widgets/ProductionTabsWidget.cs b/OpenRA.Mods.Common/Widgets/ProductionTabsWidget.cs index 924078eb66..3d75d01d31 100644 --- a/OpenRA.Mods.Common/Widgets/ProductionTabsWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ProductionTabsWidget.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.Widgets public List Tabs = new List(); public string Group; public int NextQueueName = 1; - public bool Alert { get { return Tabs.Any(t => t.Queue.CurrentDone); } } + public bool Alert { get { return Tabs.Any(t => t.Queue.AllQueued().Any(i => i.Done)); } } public void Update(IEnumerable allQueues) { @@ -111,7 +111,7 @@ namespace OpenRA.Mods.Common.Widgets // Prioritize alerted queues var queues = Groups[queueGroup].Tabs.Select(t => t.Queue) - .OrderByDescending(q => q.CurrentDone ? 1 : 0) + .OrderByDescending(q => q.AllQueued().Any(i => i.Done) ? 1 : 0) .ToList(); if (reverse) queues.Reverse(); @@ -195,7 +195,7 @@ namespace OpenRA.Mods.Common.Widgets var textSize = font.Measure(tab.Name); var position = new int2(rect.X + (rect.Width - textSize.X) / 2, rect.Y + (rect.Height - textSize.Y) / 2); - font.DrawTextWithContrast(tab.Name, position, tab.Queue.CurrentDone ? Color.Gold : Color.White, Color.Black, 1); + font.DrawTextWithContrast(tab.Name, position, tab.Queue.AllQueued().Any(i => i.Done) ? Color.Gold : Color.White, Color.Black, 1); } Game.Renderer.DisableScissor();