diff --git a/OpenRA.Mods.Common/Traits/Player/ParallelProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ParallelProductionQueue.cs index 774d28ca05..42629f5c81 100644 --- a/OpenRA.Mods.Common/Traits/Player/ParallelProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ParallelProductionQueue.cs @@ -53,7 +53,7 @@ namespace OpenRA.Mods.Common.Traits protected override void BeginProduction(ProductionItem item, bool hasPriority) { // Ignore `hasPriority` as it's not relevant in parallel production context. - Queue.Add(item); + base.BeginProduction(item, false); } public override int RemainingTimeActual(ProductionItem item) diff --git a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs index ddd289b349..086092691f 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs @@ -50,6 +50,9 @@ namespace OpenRA.Mods.Common.Traits [Desc("The build time is multiplied with this percentage on low power.")] public readonly int LowPowerModifier = 100; + [Desc("Production items that have more than this many items in the queue will be produced in a loop.")] + public readonly int InfiniteBuildLimit = -1; + [NotificationReference("Speech")] [Desc("Notification played when production is complete.", "The filename of the audio is defined per faction in notifications.yaml.")] @@ -456,9 +459,19 @@ namespace OpenRA.Mods.Common.Traits if (item != null) { - // Refund what has been paid - playerResources.GiveCash(item.TotalCost - item.RemainingCost); - EndProduction(item); + if (item.Infinite) + { + item.Infinite = false; + for (var i = 1; i < Info.InfiniteBuildLimit; i++) + Queue.Add(new ProductionItem(this, item.Item, item.TotalCost, playerPower, item.OnComplete)); + } + else + { + // Refund what has been paid + playerResources.GiveCash(item.TotalCost - item.RemainingCost); + EndProduction(item); + } + return true; } @@ -468,14 +481,37 @@ namespace OpenRA.Mods.Common.Traits public void EndProduction(ProductionItem item) { Queue.Remove(item); + + if (item.Infinite) + Queue.Add(new ProductionItem(this, item.Item, item.TotalCost, playerPower, item.OnComplete) { Infinite = true }); } protected virtual void BeginProduction(ProductionItem item, bool hasPriority) { + if (Queue.Any(i => i.Item == item.Item && i.Infinite)) + return; + if (hasPriority && Queue.Count > 1) Queue.Insert(1, item); else Queue.Add(item); + + if (Info.InfiniteBuildLimit < 0) + return; + + var queued = Queue.FindAll(i => i.Item == item.Item); + + if (queued.Count <= Info.InfiniteBuildLimit) + return; + + queued[0].Infinite = true; + + for (var i = 1; i < queued.Count; i++) + { + // Refund what has been paid + playerResources.GiveCash(queued[i].TotalCost - queued[i].RemainingCost); + EndProduction(queued[i]); + } } public virtual int RemainingTimeActual(ProductionItem item) @@ -552,6 +588,7 @@ namespace OpenRA.Mods.Common.Traits public bool Done { get; private set; } public bool Started { get; private set; } public int Slowdown { get; private set; } + public bool Infinite { get; set; } readonly ActorInfo ai; readonly BuildableInfo bi; @@ -567,6 +604,7 @@ namespace OpenRA.Mods.Common.Traits this.pm = pm; ai = Queue.Actor.World.Map.Rules.Actors[Item]; bi = ai.TraitInfo(); + Infinite = false; } public void Tick(PlayerResources pr) diff --git a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs index dde7bef230..6c2731e0f4 100644 --- a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs @@ -68,6 +68,7 @@ namespace OpenRA.Mods.Common.Widgets [Translate] public readonly string ReadyText = ""; [Translate] public readonly string HoldText = ""; + [Translate] public readonly string InfiniteSymbol = "\u221E"; public int DisplayedIconCount { get; private set; } public int TotalIconCount { get; private set; } @@ -102,8 +103,8 @@ namespace OpenRA.Mods.Common.Widgets readonly WorldRenderer worldRenderer; - SpriteFont overlayFont; - float2 holdOffset, readyOffset, timeOffset, queuedOffset; + SpriteFont overlayFont, symbolFont; + float2 holdOffset, readyOffset, timeOffset, queuedOffset, infiniteOffset; [CustomLintableHotkeyNames] public static IEnumerable LinterHotkeyNames(MiniYamlNode widgetNode, Action emitError, Action emitWarning) @@ -141,6 +142,9 @@ namespace OpenRA.Mods.Common.Widgets cantBuild = new Animation(world, NotBuildableAnimation); cantBuild.PlayFetchIndex(NotBuildableSequence, () => 0); clock = new Animation(world, ClockAnimation); + + overlayFont = Game.Renderer.Fonts["TinyBold"]; + Game.Renderer.Fonts.TryGetValue("Symbols", out symbolFont); } public override void Initialize(WidgetArgs args) @@ -283,7 +287,9 @@ namespace OpenRA.Mods.Common.Widgets Game.Sound.Play(SoundType.UI, TabClick); string notification; var canQueue = CurrentQueue.CanQueue(buildable, out notification); - Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", notification, World.LocalPlayer.Faction.InternalName); + + if (!CurrentQueue.AllQueued().Any(qi => qi.Item == icon.Name && !qi.Paused && qi.Infinite)) + Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", notification, World.LocalPlayer.Faction.InternalName); if (canQueue) { @@ -423,12 +429,16 @@ namespace OpenRA.Mods.Common.Widgets { var iconOffset = 0.5f * IconSize.ToFloat2() + IconSpriteOffset; - overlayFont = Game.Renderer.Fonts["TinyBold"]; timeOffset = iconOffset - overlayFont.Measure(WidgetUtils.FormatTime(0, World.Timestep)) / 2; queuedOffset = new float2(4, 2); holdOffset = iconOffset - overlayFont.Measure(HoldText) / 2; readyOffset = iconOffset - overlayFont.Measure(ReadyText) / 2; + if (ChromeMetrics.TryGet("InfiniteOffset", out infiniteOffset)) + infiniteOffset += queuedOffset; + else + infiniteOffset = queuedOffset; + if (CurrentQueue == null) return; @@ -485,7 +495,11 @@ namespace OpenRA.Mods.Common.Widgets icon.Pos + timeOffset, Color.White, Color.Black, 1); - if (total > 1 || waiting) + if (first.Infinite) + symbolFont.DrawTextWithContrast(InfiniteSymbol, + icon.Pos + infiniteOffset, + Color.White, Color.Black, 1); + else if (total > 1 || waiting) overlayFont.DrawTextWithContrast(total.ToString(), icon.Pos + queuedOffset, Color.White, Color.Black, 1);