diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index 7c69ec8ea5..6e277dd29f 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -74,14 +74,12 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem return; // same as store code, but message is only shown to yourself - args.Handled = _store.TryAddCurrency(_store.GetCurrencyValue(args.Used, currency), uid, store); - - if (!args.Handled) + if (!_store.TryAddCurrency((args.Used, currency), (uid, store))) return; + args.Handled = true; var msg = Loc.GetString("store-currency-inserted-implant", ("used", args.Used)); _popup.PopupEntity(msg, args.User, args.User); - QueueDel(args.Used); } private void OnFreedomImplant(EntityUid uid, SubdermalImplantComponent component, UseFreedomImplantEvent args) diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index b9553a6b84..bc1800ffd1 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -100,6 +100,13 @@ namespace Content.Server.Stack /// public List SpawnMultiple(string entityPrototype, int amount, EntityCoordinates spawnPosition) { + if (amount <= 0) + { + Log.Error( + $"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}"); + return new(); + } + var spawns = CalculateSpawns(entityPrototype, amount); var spawnedEnts = new List(); @@ -116,6 +123,13 @@ namespace Content.Server.Stack /// public List SpawnMultiple(string entityPrototype, int amount, EntityUid target) { + if (amount <= 0) + { + Log.Error( + $"Attempted to spawn an invalid stack: {entityPrototype}, {amount}. Trace: {Environment.StackTrace}"); + return new(); + } + var spawns = CalculateSpawns(entityPrototype, amount); var spawnedEnts = new List(); diff --git a/Content.Server/Store/Components/CurrencyComponent.cs b/Content.Server/Store/Components/CurrencyComponent.cs index cfe9b76c8b..eb4ca1c0e3 100644 --- a/Content.Server/Store/Components/CurrencyComponent.cs +++ b/Content.Server/Store/Components/CurrencyComponent.cs @@ -8,6 +8,11 @@ namespace Content.Server.Store.Components; /// Identifies a component that can be inserted into a store /// to increase its balance. /// +/// +/// Note that if this entity is a stack of items, then this is meant to represent the value per stack item, not +/// the whole stack. This also means that in general, the actual value should not be modified from the initial +/// prototype value because otherwise stack merging/splitting may modify the total value. +/// [RegisterComponent] public sealed partial class CurrencyComponent : Component { @@ -16,6 +21,12 @@ public sealed partial class CurrencyComponent : Component /// The string is the currency type that will be added. /// The FixedPoint2 is the value of each individual currency entity. /// + /// + /// Note that if this entity is a stack of items, then this is meant to represent the value per stack item, not + /// the whole stack. This also means that in general, the actual value should not be modified from the initial + /// prototype value + /// because otherwise stack merging/splitting may modify the total value. + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("price", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] public Dictionary Price = new(); diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 95e5711984..f1c0cb1e90 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -283,6 +283,9 @@ public sealed partial class StoreSystem /// private void OnRequestWithdraw(EntityUid uid, StoreComponent component, StoreRequestWithdrawMessage msg) { + if (msg.Amount <= 0) + return; + //make sure we have enough cash in the bank and we actually support this currency if (!component.Balance.TryGetValue(msg.Currency, out var currentAmount) || currentAmount < msg.Amount) return; @@ -306,7 +309,8 @@ public sealed partial class StoreSystem var cashId = proto.Cash[value]; var amountToSpawn = (int) MathF.Floor((float) (amountRemaining / value)); var ents = _stack.SpawnMultiple(cashId, amountToSpawn, coordinates); - _hands.PickupOrDrop(buyer, ents.First()); + if (ents.FirstOrDefault() is {} ent) + _hands.PickupOrDrop(buyer, ent); amountRemaining -= value * amountToSpawn; } diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index c13a9583be..7bdf550301 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -92,14 +92,12 @@ public sealed partial class StoreSystem : EntitySystem if (ev.Cancelled) return; - args.Handled = TryAddCurrency(GetCurrencyValue(uid, component), args.Target.Value, store); + if (!TryAddCurrency((uid, component), (args.Target.Value, store))) + return; - if (args.Handled) - { - var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target)); - _popup.PopupEntity(msg, args.Target.Value, args.User); - QueueDel(args.Used); - } + args.Handled = true; + var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target)); + _popup.PopupEntity(msg, args.Target.Value, args.User); } private void OnImplantActivate(EntityUid uid, StoreComponent component, OpenUplinkImplantEvent args) @@ -111,6 +109,10 @@ public sealed partial class StoreSystem : EntitySystem /// Gets the value from an entity's currency component. /// Scales with stacks. /// + /// + /// If this result is intended to be used with , + /// consider using instead to ensure that the currency is consumed in the process. + /// /// /// /// The value of the currency @@ -121,19 +123,34 @@ public sealed partial class StoreSystem : EntitySystem } /// - /// Tries to add a currency to a store's balance. + /// Tries to add a currency to a store's balance. Note that if successful, this will consume the currency in the process. /// - /// - /// - /// The currency to add - /// The store to add it to - /// Whether or not the currency was succesfully added - [PublicAPI] - public bool TryAddCurrency(EntityUid currencyEnt, EntityUid storeEnt, StoreComponent? store = null, CurrencyComponent? currency = null) + public bool TryAddCurrency(Entity currency, Entity store) { - if (!Resolve(currencyEnt, ref currency) || !Resolve(storeEnt, ref store)) + if (!Resolve(currency.Owner, ref currency.Comp)) return false; - return TryAddCurrency(GetCurrencyValue(currencyEnt, currency), storeEnt, store); + + if (!Resolve(store.Owner, ref store.Comp)) + return false; + + var value = currency.Comp.Price; + if (TryComp(currency.Owner, out StackComponent? stack) && stack.Count != 1) + { + value = currency.Comp.Price + .ToDictionary(v => v.Key, p => p.Value * stack.Count); + } + + if (!TryAddCurrency(value, store, store.Comp)) + return false; + + // Avoid having the currency accidentally be re-used. E.g., if multiple clients try to use the currency in the + // same tick + currency.Comp.Price.Clear(); + if (stack != null) + _stack.SetCount(currency.Owner, 0, stack); + + QueueDel(currency); + return true; } ///