using System.Linq; using Content.Server.Access.Systems; using Content.Server.Cargo.Components; using Content.Server.MachineLinking.System; using Content.Server.Popups; using Content.Server.Station.Systems; using Content.Shared.Access.Systems; using Content.Shared.Cargo; using Content.Shared.Cargo.BUI; using Content.Shared.Cargo.Events; using Content.Shared.Cargo.Prototypes; using Content.Shared.GameTicking; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Player; using Robust.Shared.Players; namespace Content.Server.Cargo.Systems { public sealed partial class CargoSystem { /// /// How much time to wait (in seconds) before increasing bank accounts balance. /// private const int Delay = 10; /// /// Keeps track of how much time has elapsed since last balance increase. /// private float _timer; [Dependency] private readonly IdCardSystem _idCardSystem = default!; [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; [Dependency] private readonly SignalLinkerSystem _linker = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; private void InitializeConsole() { SubscribeLocalEvent(OnAddOrderMessage); SubscribeLocalEvent(OnRemoveOrderMessage); SubscribeLocalEvent(OnApproveOrderMessage); SubscribeLocalEvent(OnOrderUIOpened); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(Reset); Reset(); } private void OnInit(EntityUid uid, CargoOrderConsoleComponent orderConsole, ComponentInit args) { var station = _station.GetOwningStation(uid); UpdateOrderState(orderConsole, station); } private void Reset(RoundRestartCleanupEvent ev) { Reset(); } private void Reset() { _timer = 0; } private void UpdateConsole(float frameTime) { _timer += frameTime; while (_timer > Delay) { _timer -= Delay; foreach (var account in EntityQuery()) { account.Balance += account.IncreasePerSecond * Delay; } foreach (var comp in EntityQuery()) { if (!_uiSystem.IsUiOpen(comp.Owner, CargoConsoleUiKey.Orders)) continue; var station = _station.GetOwningStation(comp.Owner); UpdateOrderState(comp, station); } } } #region Interface private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleApproveOrderMessage args) { if (args.Session.AttachedEntity is not {Valid: true} player) return; if (!_accessReaderSystem.IsAllowed(player, uid)) { ConsolePopup(args.Session, Loc.GetString("cargo-console-order-not-allowed")); PlayDenySound(uid, component); return; } var orderDatabase = GetOrderDatabase(component); var bankAccount = GetBankAccount(component); // No station to deduct from. if (orderDatabase == null || bankAccount == null) { ConsolePopup(args.Session, Loc.GetString("cargo-console-station-not-found")); PlayDenySound(uid, component); return; } // No order to approve? if (!orderDatabase.Orders.TryGetValue(args.OrderNumber, out var order) || order.Approved) return; // Invalid order if (!_protoMan.TryIndex(order.ProductId, out var product)) { ConsolePopup(args.Session, Loc.GetString("cargo-console-invalid-product")); PlayDenySound(uid, component); return; } var amount = GetOrderCount(orderDatabase); var capacity = orderDatabase.Capacity; // Too many orders, avoid them getting spammed in the UI. if (amount >= capacity) { ConsolePopup(args.Session, Loc.GetString("cargo-console-too-many")); PlayDenySound(uid, component); return; } // Cap orders so someone can't spam thousands. var orderAmount = Math.Min(capacity - amount, order.Amount); if (orderAmount != order.Amount) { order.Amount = orderAmount; ConsolePopup(args.Session, Loc.GetString("cargo-console-snip-snip")); PlayDenySound(uid, component); } var cost = product.PointCost * order.Amount; // Not enough balance if (cost > bankAccount.Balance) { ConsolePopup(args.Session, Loc.GetString("cargo-console-insufficient-funds", ("cost", cost))); PlayDenySound(uid, component); return; } _idCardSystem.TryFindIdCard(player, out var idCard); order.SetApproverData(idCard); SoundSystem.Play(component.ConfirmSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid); DeductFunds(bankAccount, cost); UpdateOrders(orderDatabase); } private void OnRemoveOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleRemoveOrderMessage args) { var orderDatabase = GetOrderDatabase(component); if (orderDatabase == null) return; RemoveOrder(orderDatabase, args.OrderNumber); } private void OnAddOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleAddOrderMessage args) { if (args.Amount <= 0) return; var bank = GetBankAccount(component); if (bank == null) return; var orderDatabase = GetOrderDatabase(component); if (orderDatabase == null) return; var data = GetOrderData(args, GetNextIndex(orderDatabase)); if (!TryAddOrder(orderDatabase, data)) { PlayDenySound(uid, component); return; } } private void OnOrderUIOpened(EntityUid uid, CargoOrderConsoleComponent component, BoundUIOpenedEvent args) { var station = _station.GetOwningStation(uid); UpdateOrderState(component, station); } #endregion private void UpdateOrderState(CargoOrderConsoleComponent component, EntityUid? station) { if (station == null || !TryComp(station, out var orderDatabase) || !TryComp(station, out var bankAccount)) return; var state = new CargoConsoleInterfaceState( MetaData(station.Value).EntityName, GetOrderCount(orderDatabase), orderDatabase.Capacity, bankAccount.Balance, orderDatabase.Orders.Values.ToList()); _uiSystem.GetUiOrNull(component.Owner, CargoConsoleUiKey.Orders)?.SetState(state); } private void ConsolePopup(ICommonSession session, string text) { _popup.PopupCursor(text, Filter.SinglePlayer(session)); } private void PlayDenySound(EntityUid uid, CargoOrderConsoleComponent component) { SoundSystem.Play(component.ErrorSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid); } private CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, int index) { return new CargoOrderData(index, args.ProductId, args.Amount, args.Requester, args.Reason); } private int GetOrderCount(StationCargoOrderDatabaseComponent component) { var amount = 0; foreach (var (_, order) in component.Orders) { if (!order.Approved) continue; amount += order.Amount; } return amount; } /// /// Updates all of the cargo-related consoles for a particular station. /// This should be called whenever orders change. /// private void UpdateOrders(StationCargoOrderDatabaseComponent component) { // Order added so all consoles need updating. foreach (var comp in EntityQuery(true)) { var station = _station.GetOwningStation(component.Owner); if (station != component.Owner) continue; UpdateOrderState(comp, station); } foreach (var comp in EntityQuery(true)) { var station = _station.GetOwningStation(component.Owner); if (station != component.Owner) continue; UpdateShuttleState(comp, station); } } public bool TryAddOrder(StationCargoOrderDatabaseComponent component, CargoOrderData data) { component.Orders.Add(data.OrderNumber, data); UpdateOrders(component); return true; } private int GetNextIndex(StationCargoOrderDatabaseComponent component) { var index = component.Index; component.Index++; return index; } public void RemoveOrder(StationCargoOrderDatabaseComponent component, int index) { if (!component.Orders.Remove(index)) return; UpdateOrders(component); } public void ClearOrders(StationCargoOrderDatabaseComponent component) { if (component.Orders.Count == 0) return; component.Orders.Clear(); Dirty(component); } private void DeductFunds(StationBankAccountComponent component, int amount) { component.Balance = Math.Max(0, component.Balance - amount); Dirty(component); } #region Station private StationBankAccountComponent? GetBankAccount(CargoOrderConsoleComponent component) { var station = _station.GetOwningStation(component.Owner); TryComp(station, out var bankComponent); return bankComponent; } private StationCargoOrderDatabaseComponent? GetOrderDatabase(CargoOrderConsoleComponent component) { var station = _station.GetOwningStation(component.Owner); TryComp(station, out var orderComponent); return orderComponent; } #endregion } }