Skip to content

Commit

Permalink
Enable paying upfront
Browse files Browse the repository at this point in the history
- Fix resources to cash exploit
- Option to block queue if player doent have enough money
  • Loading branch information
Porenutak committed Jun 11, 2024
1 parent 00857df commit e33802f
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 13 deletions.
87 changes: 76 additions & 11 deletions OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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);
Expand All @@ -293,9 +310,12 @@ public virtual IEnumerable<ActorInfo> BuildableItems()
return Enumerable.Empty<ActorInfo>();
if (!Enabled)
return Enumerable.Empty<ActorInfo>();
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;
}

Expand Down Expand Up @@ -352,6 +372,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]);
Expand All @@ -369,6 +396,9 @@ public bool CanQueue(ActorInfo actor, out string notificationAudio, out string n

if (!developerMode.AllTech)
{
if (Info.PayUpFront && actor.TraitInfo<ValuedInfo>().Cost > playerResources.GetCashAndResources())
return false;

if (Info.QueueLimit > 0 && Queue.Count >= Info.QueueLimit)
{
notificationAudio = Info.LimitedAudio;
Expand Down Expand Up @@ -444,6 +474,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(_ =>
{
Expand Down Expand Up @@ -540,6 +572,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);
}
Expand All @@ -560,9 +598,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
Expand All @@ -581,6 +629,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]);
}
Expand Down Expand Up @@ -645,10 +699,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;
Expand All @@ -669,6 +723,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;
Expand All @@ -685,7 +740,6 @@ public void Tick(PlayerResources pr)
var time = Queue.GetBuildTime(ai, bi);
if (time > 0)
RemainingTime = TotalTime = time;

Started = true;
}

Expand All @@ -708,12 +762,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;
Expand Down
7 changes: 5 additions & 2 deletions OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlayerResources>().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())
Expand All @@ -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);
Expand Down

0 comments on commit e33802f

Please sign in to comment.