diff --git a/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs b/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs index aa54310fe9e..42b4b2c9a37 100644 --- a/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs +++ b/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs @@ -1,6 +1,7 @@ using Content.Client._NF.Shipyard.UI; using Content.Shared.Containers.ItemSlots; using Content.Shared._NF.Shipyard.BUI; +using Content.Shared._NF.Shipyard.Components; using Content.Shared._NF.Shipyard.Events; using static Robust.Client.UserInterface.Controls.BaseButton; @@ -39,7 +40,7 @@ protected override void Open() _menu.OnSellShip += SellShip; _menu.OnUnassignDeed += UnassignDeed; _menu.OnRenameShip += RenameShip; - _menu.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent("ShipyardConsole-targetId")); + _menu.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(ShipyardConsoleComponent.TargetIdCardSlotId)); } private void Populate(List availablePrototypes, List unavailablePrototypes, bool freeListings, bool validId) diff --git a/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml b/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml index 7c811d5a7cf..ab88f0a5dda 100644 --- a/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml +++ b/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml @@ -17,13 +17,18 @@ - - + + + + diff --git a/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml.cs b/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml.cs index 4f7231dca32..128e625022b 100644 --- a/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml.cs +++ b/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml.cs @@ -302,6 +302,8 @@ private void AddEnginesFromPrototypes(IEnumerable prototypes) public void UpdateState(ShipyardConsoleInterfaceState state) { BalanceLabel.Text = BankSystemExtensions.ToSpesoString(state.Balance); + CashSlotLabel.Text = BankSystemExtensions.ToSpesoString(state.CashSlotBalance); + var shipPrice = 0; if (!state.FreeListings) shipPrice = state.ShipSellValue; diff --git a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs index 7a1e1490195..b36171960d3 100644 --- a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs +++ b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs @@ -1,4 +1,5 @@ using Content.Server.Access.Systems; +using Content.Server.Stack; using Content.Server.Popups; using Content.Server.Radio.EntitySystems; using Content.Server._NF.Bank; @@ -50,7 +51,10 @@ using Robust.Server.Player; using Content.Shared._Mono.Ships.Components; using Content.Shared._Mono.Shipyard; +using Content.Shared._Mono.Traits.Physical; +using Content.Shared.Containers.ItemSlots; using Content.Shared.Tag; +using Content.Shared.Stacks; using Robust.Shared.Timing; namespace Content.Server._NF.Shipyard.Systems; @@ -78,6 +82,8 @@ public sealed partial class ShipyardSystem : SharedShipyardSystem [Dependency] private ShuttleConsoleLockSystem _shuttleConsoleLock = default!; [Dependency] private IGameTiming _timing = default!; [Dependency] private TagSystem _tagSystem = default!; + [Dependency] private StackSystem _stack = default!; + [Dependency] private ItemSlotsSystem _itemSlots = default!; private static readonly ProtoId CrewedShuttleTag = "CrewedShuttle"; private static readonly Regex DeedRegex = new(@"\s*\([^()]*\)"); @@ -224,21 +230,25 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp } else { - if (bank.Balance <= vessel.Price) + var cashInSlot = GetCashInSlot(component); + if (vessel.Price > bank.Balance + cashInSlot) { Del(shuttleUid); - ConsolePopup(player, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price))); + ConsolePopup(player, Loc.GetString("bank-insufficient-funds")); PlayDenySound(player, shipyardConsoleUid, component); return; } - if (!_bank.TryBankWithdraw(player, vessel.Price)) + var bankCharge = Math.Max(vessel.Price - cashInSlot, 0); + if (bankCharge > 0 && !_bank.TryBankWithdraw(player, bankCharge)) { Del(shuttleUid); - ConsolePopup(player, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price))); + ConsolePopup(player, Loc.GetString("bank-insufficient-funds")); PlayDenySound(player, shipyardConsoleUid, component); return; } + + TakeCashFromSlot(component, vessel.Price); } // Add company information to the shuttle from the ID card or voucher @@ -554,7 +564,11 @@ public void OnSellMessage(EntityUid uid, ShipyardConsoleComponent component, Shi } bill = int.Max(0, bill); - _bank.TryBankDeposit(player, bill); + if (HasComp(player)) + AddCashToSlot(uid, component, bill); + else + _bank.TryBankDeposit(player, bill); + PlayConfirmSound(player, uid, component); } @@ -698,7 +712,8 @@ private void OnItemSlotChanged(EntityUid uid, ShipyardConsoleComponent component if (!component.Initialized) return; - if (args.Container.ID != component.TargetIdSlot.ID) + if (args.Container.ID != component.TargetIdSlot.ID + && args.Container.ID != component.CashSlot.ID) return; // kind of cursed. We need to update the UI when an Id is entered, but the UI needs to know the player characters bank account. @@ -919,8 +934,13 @@ private struct IDShipAccesses private void RefreshState(EntityUid uid, int balance, bool access, string? shipDeed, int shipSellValue, EntityUid? targetId, ShipyardConsoleUiKey uiKey, bool freeListings) { + var cashInSlot = 0; + if (TryComp(uid, out var shipyard)) + cashInSlot = GetCashInSlot(shipyard); + var newState = new ShipyardConsoleInterfaceState( balance, + cashInSlot, access, shipDeed, shipSellValue, @@ -934,6 +954,57 @@ private void RefreshState(EntityUid uid, int balance, bool access, string? shipD _ui.SetUiState(uid, uiKey, newState); } + private int GetCashInSlot(ShipyardConsoleComponent component) + { + var cashEntity = component.CashSlot.ContainerSlot?.ContainedEntity; + if (cashEntity == null) + return 0; + + if (!TryComp(cashEntity, out var cashStack) || + cashStack.StackTypeId != component.CashType) + return 0; + + return cashStack.Count; + } + + private void TakeCashFromSlot(ShipyardConsoleComponent component, int amount) + { + if (amount <= 0) + return; + + var cashEntity = component.CashSlot.ContainerSlot?.ContainedEntity; + if (cashEntity == null || + !TryComp(cashEntity, out var cashStack) || + cashStack.StackTypeId != component.CashType) + return; + + var spent = Math.Min(amount, cashStack.Count); + _stack.SetCount(cashEntity.Value, cashStack.Count - spent, cashStack); + } + + private void AddCashToSlot(EntityUid uid, ShipyardConsoleComponent component, int amount) + { + if (amount <= 0) + return; + + var cashEntity = component.CashSlot.ContainerSlot?.ContainedEntity; + if (cashEntity != null && + TryComp(cashEntity, out var cashStack) && + cashStack.StackTypeId == component.CashType) + { + _stack.SetCount(cashEntity.Value, cashStack.Count + amount, cashStack); + return; + } + + var coordinates = Transform(uid).Coordinates; + var stackPrototype = _prototypeManager.Index(component.CashType); + var payout = _stack.Spawn(amount, stackPrototype, coordinates); + if (_itemSlots.TryInsert(uid, component.CashSlot, payout, user: null, excludeUserAudio: true)) + return; + + _transform.SetCoordinates(payout, coordinates); + } + #region Deed Assignment void AssignShuttleDeedProperties(ShuttleDeedComponent deed, EntityUid? shuttleUid, string? shuttleName, string? shuttleOwner, bool purchasedWithVoucher, string? purchaseVoucherUid = null) { diff --git a/Content.Shared/_NF/Shipyard/BUI/ShipyardConsoleInterfaceState.cs b/Content.Shared/_NF/Shipyard/BUI/ShipyardConsoleInterfaceState.cs index 6319c8327d1..1b68a4a748e 100644 --- a/Content.Shared/_NF/Shipyard/BUI/ShipyardConsoleInterfaceState.cs +++ b/Content.Shared/_NF/Shipyard/BUI/ShipyardConsoleInterfaceState.cs @@ -6,6 +6,7 @@ namespace Content.Shared._NF.Shipyard.BUI; public sealed class ShipyardConsoleInterfaceState : BoundUserInterfaceState { public int Balance; + public int CashSlotBalance; public readonly bool AccessGranted; public readonly string? ShipDeedTitle; public int ShipSellValue; @@ -19,6 +20,7 @@ public sealed class ShipyardConsoleInterfaceState : BoundUserInterfaceState public ShipyardConsoleInterfaceState( int balance, + int cashSlotBalance, bool accessGranted, string? shipDeedTitle, int shipSellValue, @@ -30,6 +32,7 @@ public ShipyardConsoleInterfaceState( float sellRate) { Balance = balance; + CashSlotBalance = cashSlotBalance; AccessGranted = accessGranted; ShipDeedTitle = shipDeedTitle; ShipSellValue = shipSellValue; diff --git a/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs b/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs index 05cc2e42115..2132132c6c1 100644 --- a/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs +++ b/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs @@ -1,10 +1,12 @@ using Robust.Shared.GameStates; using Robust.Shared.Audio; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Stacks; using Robust.Shared.Prototypes; using Content.Shared.Radio; using Content.Shared.Access; using Content.Shared._NF.Bank.Components; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared._NF.Shipyard.Components; @@ -12,10 +14,17 @@ namespace Content.Shared._NF.Shipyard.Components; public sealed partial class ShipyardConsoleComponent : Component { public static string TargetIdCardSlotId = "ShipyardConsole-targetId"; + public static string CashSlotId = "ShipyardConsole-cashSlot"; [DataField("targetIdSlot")] public ItemSlot TargetIdSlot = new(); + [ViewVariables(VVAccess.ReadWrite), DataField("cashType", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string CashType = "Credit"; + + [DataField("cashSlot")] + public ItemSlot CashSlot = new(); + [DataField("soundError")] public SoundSpecifier ErrorSound = new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg"); diff --git a/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs b/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs index 1a801d0f7ef..7927299d0c2 100644 --- a/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs +++ b/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs @@ -54,11 +54,13 @@ private void OnGetState(EntityUid uid, ShipyardConsoleComponent component, ref C private void OnComponentInit(EntityUid uid, ShipyardConsoleComponent component, ComponentInit args) { _itemSlotsSystem.AddItemSlot(uid, ShipyardConsoleComponent.TargetIdCardSlotId, component.TargetIdSlot); + _itemSlotsSystem.AddItemSlot(uid, ShipyardConsoleComponent.CashSlotId, component.CashSlot); } private void OnComponentRemove(EntityUid uid, ShipyardConsoleComponent component, ComponentRemove args) { _itemSlotsSystem.RemoveItemSlot(uid, component.TargetIdSlot); + _itemSlotsSystem.RemoveItemSlot(uid, component.CashSlot); } [Serializable, NetSerializable] diff --git a/Resources/Locale/en-US/_NF/shipyard/shipyard-console-component.ftl b/Resources/Locale/en-US/_NF/shipyard/shipyard-console-component.ftl index 10befc7d3d4..c4ea5efa646 100644 --- a/Resources/Locale/en-US/_NF/shipyard/shipyard-console-component.ftl +++ b/Resources/Locale/en-US/_NF/shipyard/shipyard-console-component.ftl @@ -1,6 +1,7 @@ ## UI shipyard-console-invalid-vessel = Cannot purchase vessel: shipyard-console-menu-title = Shipyard Menu +ShipyardConsole-cashSlot = Cash Slot shipyard-console-menu-listing-free = Free shipyard-console-menu-listing-voucher = Voucher shipyard-console-docking = {$owner} shuttle {$vessel} en route. diff --git a/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers_shipyard.yml b/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers_shipyard.yml index 1437a315413..49fa44b19e8 100644 --- a/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers_shipyard.yml +++ b/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers_shipyard.yml @@ -27,6 +27,15 @@ components: - IdCard - ShipyardVoucher + cashSlot: + name: ShipyardConsole-cashSlot + insertSound: /Audio/Machines/scanning.ogg + ejectSound: /Audio/Machines/tray_eject.ogg + ejectOnBreak: true + swap: false + whitelist: + components: + - Cash newAccessLevels: [Captain] - type: ActivatableUI singleUser: true @@ -40,6 +49,7 @@ containers: board: !type:Container ShipyardConsole-targetId: !type:ContainerSlot + ShipyardConsole-cashSlot: !type:ContainerSlot - type: Computer board: Null