From a711bd636f11b8a84534268cc91a5f4be2b5f22c Mon Sep 17 00:00:00 2001 From: "N.N" Date: Wed, 10 Jan 2024 15:30:52 +0100 Subject: [PATCH] Enable paying upfront Fix tab availability on low money Co-Authored-By: Gustas <37534529+PunkPun@users.noreply.github.com> --- .../Traits/Player/ProductionQueue.cs | 94 ++++++++++++++++--- .../Traits/ProductionQueueFromSelection.cs | 2 +- .../Logic/Ingame/ClassicProductionLogic.cs | 2 +- .../Widgets/ProductionPaletteWidget.cs | 7 +- 4 files changed, 90 insertions(+), 15 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs index 4466b31e6ebd..7f703d89cc77 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs @@ -38,6 +38,9 @@ public class ProductionQueueInfo : TraitInfo, IRulesetLoaded [Desc("Should the prerequisite remain enabled if the owner changes?")] public readonly bool Sticky = true; + [Desc("Player must pay for item upfront")] + public readonly bool PayUpFront = false; + [Desc("Should right clicking on the icon instantly cancel the production instead of putting it on hold?")] public readonly bool DisallowPaused = false; @@ -185,7 +188,16 @@ protected void ClearQueue() { // Refund the current item foreach (var item in Queue) + { + if (item.ResourcesPaid > 0) + { + playerResources.GiveResources(item.ResourcesPaid); + item.RemainingCost += item.ResourcesPaid; + } + playerResources.GiveCash(item.TotalCost - item.RemainingCost); + } + Queue.Clear(); } @@ -267,6 +279,11 @@ public virtual bool IsProducing(ProductionItem item) return Queue.Count > 0 && Queue[0] == item; } + public virtual bool IsInQueue(ActorInfo actor) + { + return Queue.Any(i => i.Item == actor.Name); + } + public ProductionItem CurrentItem() { return Queue.ElementAtOrDefault(0); @@ -293,12 +310,22 @@ public virtual IEnumerable BuildableItems() return Enumerable.Empty(); if (!Enabled) return Enumerable.Empty(); - if (developerMode.AllTech) + if (!Info.PayUpFront && developerMode.AllTech) return Producible.Keys; - + if (Info.PayUpFront && developerMode.AllTech) + return Producible.Keys.Where(a => GetProductionCost(a) <= playerResources.GetCashAndResources() || IsInQueue(a)); + if (Info.PayUpFront) + return buildableProducibles.Where(a => GetProductionCost(a) <= playerResources.GetCashAndResources() || IsInQueue(a)); return buildableProducibles; } + public virtual bool AnyItemsToBuild() + { + return Enabled + && (productionTraits.Length <= 0 || productionTraits.Any(p => p.IsTraitDisabled)) + && ((developerMode.AllTech && Producible.Keys.Count != 0) || buildableProducibles.Any()); + } + public bool CanBuild(ActorInfo actor) { if (!Producible.TryGetValue(actor, out var ps)) @@ -352,6 +379,13 @@ protected void CancelUnbuildableItems() if (buildableNames.Contains(Queue[i].Item)) continue; + // Refund spended resources + if (Queue[i].ResourcesPaid > 0) + { + playerResources.GiveResources(Queue[i].ResourcesPaid); + Queue[i].RemainingCost += Queue[i].ResourcesPaid; + } + // Refund what's been paid so far playerResources.GiveCash(Queue[i].TotalCost - Queue[i].RemainingCost); EndProduction(Queue[i]); @@ -369,6 +403,9 @@ public bool CanQueue(ActorInfo actor, out string notificationAudio, out string n if (!developerMode.AllTech) { + if (Info.PayUpFront && actor.TraitInfo().Cost > playerResources.GetCashAndResources()) + return false; + if (Info.QueueLimit > 0 && Queue.Count >= Info.QueueLimit) { notificationAudio = Info.LimitedAudio; @@ -444,6 +481,8 @@ public void ResolveOrder(Actor self, Order order) var amountToBuild = Math.Min(fromLimit, order.ExtraData); for (var n = 0; n < amountToBuild; n++) { + if (Info.PayUpFront && cost > playerResources.GetCashAndResources()) + return; var hasPlayedSound = false; BeginProduction(new ProductionItem(this, order.TargetString, cost, playerPower, () => self.World.AddFrameEndTask(_ => { @@ -540,6 +579,12 @@ protected bool CancelProductionInner(string itemName) else { // Refund what has been paid + if (item.ResourcesPaid > 0) + { + playerResources.GiveResources(item.ResourcesPaid); + item.RemainingCost += item.ResourcesPaid; + } + playerResources.GiveCash(item.TotalCost - item.RemainingCost); EndProduction(item); } @@ -560,9 +605,19 @@ public void EndProduction(ProductionItem item) protected virtual void BeginProduction(ProductionItem item, bool hasPriority) { + if (Info.PayUpFront) + { + if (playerResources.Resources > 0 && playerResources.Resources <= item.TotalCost) + item.ResourcesPaid = playerResources.Resources; + else if (playerResources.Resources > item.TotalCost) + item.ResourcesPaid = item.TotalCost; + + playerResources.TakeCash(item.TotalCost); + item.RemainingCost = 0; + } + if (Queue.Any(i => i.Item == item.Item && i.Infinite)) return; - if (hasPriority && Queue.Count > 1) Queue.Insert(1, item); else @@ -581,6 +636,12 @@ protected virtual void BeginProduction(ProductionItem item, bool hasPriority) for (var i = 1; i < queued.Count; i++) { // Refund what has been paid + if (queued[i].ResourcesPaid > 0) + { + playerResources.GiveResources(queued[i].ResourcesPaid); + queued[i].RemainingCost += queued[i].ResourcesPaid; + } + playerResources.GiveCash(queued[i].TotalCost - queued[i].RemainingCost); EndProduction(queued[i]); } @@ -645,10 +706,10 @@ public class ProductionItem public readonly ProductionQueue Queue; public readonly int TotalCost; public readonly Action OnComplete; - public int TotalTime { get; private set; } public int RemainingTime { get; private set; } - public int RemainingCost { get; private set; } + public int RemainingCost { get; set; } + public int ResourcesPaid { get; set; } public int RemainingTimeActual => (pm == null || pm.PowerState == PowerState.Normal) ? RemainingTime : RemainingTime * Queue.Info.LowPowerModifier / 100; @@ -669,6 +730,7 @@ public ProductionItem(ProductionQueue queue, string item, int cost, PowerManager Item = item; RemainingTime = TotalTime = 1; RemainingCost = TotalCost = cost; + ResourcesPaid = 0; OnComplete = onComplete; Queue = queue; this.pm = pm; @@ -685,7 +747,6 @@ public void Tick(PlayerResources pr) var time = Queue.GetBuildTime(ai, bi); if (time > 0) RemainingTime = TotalTime = time; - Started = true; } @@ -708,12 +769,23 @@ public void Tick(PlayerResources pr) return; } - var expectedRemainingCost = RemainingTime == 1 ? 0 : TotalCost * RemainingTime / Math.Max(1, TotalTime); - var costThisFrame = RemainingCost - expectedRemainingCost; - if (costThisFrame != 0 && !pr.TakeCash(costThisFrame, true)) - return; + if (!Queue.Info.PayUpFront) + { + var expectedRemainingCost = RemainingTime == 1 ? 0 : TotalCost * RemainingTime / Math.Max(1, TotalTime); + var costThisFrame = RemainingCost - expectedRemainingCost; + if (pr.Resources > 0 && pr.Resources <= costThisFrame) + ResourcesPaid += pr.Resources; + else if (pr.Resources > costThisFrame) + ResourcesPaid += costThisFrame; + if (costThisFrame != 0 && !pr.TakeCash(costThisFrame, true)) + { + ResourcesPaid -= pr.Resources; + return; + } + + RemainingCost -= costThisFrame; + } - RemainingCost -= costThisFrame; RemainingTime--; if (RemainingTime > 0) return; diff --git a/OpenRA.Mods.Common/Traits/ProductionQueueFromSelection.cs b/OpenRA.Mods.Common/Traits/ProductionQueueFromSelection.cs index 1c86547be483..c00b85e574a0 100644 --- a/OpenRA.Mods.Common/Traits/ProductionQueueFromSelection.cs +++ b/OpenRA.Mods.Common/Traits/ProductionQueueFromSelection.cs @@ -63,7 +63,7 @@ void INotifySelection.SelectionChanged() .FirstOrDefault(q => q.Enabled && types.Contains(q.Info.Type)); } - if (queue == null || !queue.BuildableItems().Any()) + if (queue == null || !queue.AnyItemsToBuild()) return; if (tabsWidget.Value != null) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs index b09ce9ded800..f7538b38c6a0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ClassicProductionLogic.cs @@ -42,7 +42,7 @@ void SelectTab(bool reverse) palette.PickUpCompletedBuilding(); } - button.IsDisabled = () => !queues.Any(q => q.BuildableItems().Any()); + button.IsDisabled = () => !queues.Any(q => q.AnyItemsToBuild()); button.OnMouseUp = mi => SelectTab(mi.Modifiers.HasModifier(Modifiers.Shift)); button.OnKeyPress = e => SelectTab(e.Modifiers.HasModifier(Modifiers.Shift)); button.OnClick = () => SelectTab(false); diff --git a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs index b8f3b7e5c749..ad2e8fff21dd 100644 --- a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs @@ -338,8 +338,11 @@ bool HandleLeftClick(ProductionItem item, ProductionIcon icon, int handleCount, if (buildable != null) { - // Queue a new item + if (CurrentQueue.Info.PayUpFront && currentQueue.GetProductionCost(buildable) > CurrentQueue.Actor.Owner.PlayerActor.Trait().GetCashAndResources()) + return false; Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null); + + // Queue a new item var canQueue = CurrentQueue.CanQueue(buildable, out var notification, out var textNotification); if (!CurrentQueue.AllQueued().Any()) @@ -366,7 +369,7 @@ bool HandleRightClick(ProductionItem item, ProductionIcon icon, int handleCount) Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Sounds", ClickSound, null); - if (CurrentQueue.Info.DisallowPaused || item.Paused || item.Done || item.TotalCost == item.RemainingCost) + if (CurrentQueue.Info.DisallowPaused || item.Paused || item.Done || item.TotalCost == item.RemainingCost || !item.Started) { // Instantly cancel items that haven't started, have finished, or if the queue doesn't support pausing Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, World.LocalPlayer.Faction.InternalName);