6
2026-01-24 12:49:55 +03:00

331 lines
13 KiB
C#

/*
* New Frontiers - This file is licensed under AGPLv3
* Copyright (c) 2024 New Frontiers Contributors
* See AGPLv3.txt for details.
*/
using System.Linq;
using Content.Shared._NF.Bank;
using Content.Shared._NF.Bank.BUI;
using Content.Shared._NF.Bank.Components;
using Content.Shared._NF.Bank.Events;
using Content.Shared.Access.Systems;
using Content.Shared.Coordinates;
using Content.Shared.Database;
using Content.Shared.Stacks;
using Robust.Shared.Containers;
namespace Content.Server._NF.Bank;
public sealed partial class BankSystem
{
[Dependency] private readonly AccessReaderSystem _access = default!;
private void InitializeStationATM()
{
SubscribeLocalEvent<StationBankATMComponent, StationBankWithdrawMessage>(OnWithdraw);
SubscribeLocalEvent<StationBankATMComponent, StationBankDepositMessage>(OnDeposit);
SubscribeLocalEvent<StationBankATMComponent, BoundUIOpenedEvent>(OnATMUIOpen);
SubscribeLocalEvent<StationBankATMComponent, EntInsertedIntoContainerMessage>(OnCashSlotChanged);
SubscribeLocalEvent<StationBankATMComponent, EntRemovedFromContainerMessage>(OnCashSlotChanged);
}
private void OnWithdraw(EntityUid uid, StationBankATMComponent component, StationBankWithdrawMessage args)
{
if (args.Actor is not { Valid: true } player)
return;
GetInsertedCashAmount(component, out var deposit);
if (!TryGetBalance(component.Account, out var stationBank))
{
_log.Info($"entity {uid} cannot read account {component.Account}. Is the bank service running?");
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-no-bank"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(0, false, deposit));
return;
}
var hasAccess = _access.IsAllowed(player, uid);
if (!hasAccess)
{
_log.Info($"{player} tried to access station bank account");
ConsolePopup(args.Actor, Loc.GetString("station-bank-unauthorized"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
if (args.Description == null || args.Reason == null)
{
ConsolePopup(args.Actor, Loc.GetString("station-bank-requires-reason"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
// check for sufficient funds
if (stationBank < args.Amount || args.Amount < 0)
{
ConsolePopup(args.Actor, Loc.GetString("bank-insufficient-funds"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
var enumVal = ParseLedgerType($"StationWithdrawal{args.Reason}", false);
if (!TrySectorWithdraw(component.Account, args.Amount, enumVal))
{
ConsolePopup(args.Actor, Loc.GetString("bank-withdraw-failed"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-withdraw-successful"));
PlayConfirmSound(uid, component);
_log.Info($"{args.Actor} withdrew {args.Amount}, '{args.Reason}': {args.Description}");
_adminLogger.Add(LogType.ATMUsage, LogImpact.Low, $"{ToPrettyString(player):actor} withdrew {args.Amount} from {component.Account} station bank account. '{args.Reason}': {args.Description}");
//spawn the cash stack of whatever cash type the ATM is configured to.
var stackPrototype = _prototypeManager.Index(component.CashType);
var stackUid = _stackSystem.Spawn(args.Amount, stackPrototype, args.Actor.ToCoordinates());
if (!_hands.TryPickupAnyHand(args.Actor, stackUid))
_transform.SetLocalRotation(stackUid, Angle.Zero);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank - args.Amount, hasAccess, deposit));
}
private void OnDeposit(EntityUid uid, StationBankATMComponent component, StationBankDepositMessage args)
{
if (args.Actor is not { Valid: true } player)
return;
// gets the money inside a cashslot of an ATM.
// Dynamically knows what kind of cash to look for according to BankATMComponent
GetInsertedCashAmount(component, out var deposit);
if (!TryGetBalance(component.Account, out var stationBank))
{
_log.Info($"entity {uid} cannot read account {component.Account}. Is the bank service running?");
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-no-bank"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(0, false, deposit));
return;
}
// validating the cash slot was setup correctly in the yaml
if (component.CashSlot.ContainerSlot is not BaseContainer cashSlot)
{
_log.Info($"ATM has no cash slot");
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-no-bank"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, false, deposit));
return;
}
var hasAccess = _access.IsAllowed(player, uid);
if (!hasAccess)
{
_log.Info($"{player} tried to access station bank account");
ConsolePopup(args.Actor, Loc.GetString("station-bank-unauthorized"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
if (args.Description == null || args.Reason == null)
{
ConsolePopup(args.Actor, Loc.GetString("station-bank-requires-reason"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
// validate stack prototypes
if (!TryComp<StackComponent>(component.CashSlot.ContainerSlot.ContainedEntity, out var stackComponent) ||
stackComponent.StackTypeId == null)
{
_log.Info($"ATM cash slot contains bad stack prototype");
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-wrong-cash"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
// and then check them against the ATM's CashType
if (_prototypeManager.Index(component.CashType) != _prototypeManager.Index<StackPrototype>(stackComponent.StackTypeId))
{
_log.Info($"{stackComponent.StackTypeId} is not {component.CashType}");
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-wrong-cash"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
// try to deposit the inserted cash into a player's bank acount.
if (args.Amount <= 0)
{
_log.Info($"{args.Amount} is invalid");
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-transaction-denied"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
if (deposit < args.Amount)
{
_log.Debug($"Deposit: {args.Amount} is more than {deposit}, depositing all inserted cash");
args.Amount = deposit;
}
var enumVal = ParseLedgerType($"StationDeposit{args.Reason}", true);
if (!TrySectorDeposit(component.Account, args.Amount, enumVal))
{
ConsolePopup(args.Actor, Loc.GetString("bank-withdraw-failed"));
PlayDenySound(uid, component);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
return;
}
ConsolePopup(args.Actor, Loc.GetString("bank-atm-menu-deposit-successful"));
PlayConfirmSound(uid, component);
_log.Info($"{args.Actor} deposited {args.Amount}, '{args.Reason}': {args.Description}");
_adminLogger.Add(LogType.ATMUsage, LogImpact.Low, $"{ToPrettyString(player):actor} deposited {args.Amount} to {component.Account} station bank account. '{args.Reason}': {args.Description}");
SetInsertedCashAmount(component, args.Amount, out int leftAmount, out bool empty);
// yeet and delete the stack in the cash slot after success if it's worth 0
if (empty)
_containerSystem.CleanContainer(cashSlot);
_uiSystem.SetUiState(uid, args.UiKey,
new StationBankATMMenuInterfaceState(stationBank + args.Amount, hasAccess, leftAmount));
}
private LedgerEntryType ParseLedgerType(string name, bool isDeposit)
{
if (Enum.TryParse(typeof(LedgerEntryType), name, true, out var result))
return (LedgerEntryType)result;
// Unknown value, return default enum value.
return isDeposit ? LedgerEntryType.StationDepositOther : LedgerEntryType.StationWithdrawalOther;
}
private void OnCashSlotChanged(EntityUid uid, StationBankATMComponent component, ContainerModifiedMessage args)
{
GetInsertedCashAmount(component, out var deposit);
if (!TryGetBalance(component.Account, out var stationBank))
{
_uiSystem.SetUiState(uid, BankATMMenuUiKey.ATM,
new StationBankATMMenuInterfaceState(stationBank, false, deposit));
return;
}
// Get whether our actor has access or not.
TryComp(uid, out UserInterfaceComponent? ui);
var actorSet = _uiSystem.GetActors((uid, ui), BankATMMenuUiKey.ATM);
// Nobody accessing UI
if (actorSet.Count() <= 0)
return;
var hasAccess = _access.IsAllowed(actorSet.First(), uid);
if (component.CashSlot.ContainerSlot?.ContainedEntity is not { Valid: true })
{
_uiSystem.SetUiState(uid, BankATMMenuUiKey.ATM,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, 0));
}
_uiSystem.SetUiState(uid, BankATMMenuUiKey.ATM,
new StationBankATMMenuInterfaceState(stationBank, hasAccess, deposit));
}
private void OnATMUIOpen(EntityUid uid, StationBankATMComponent component, BoundUIOpenedEvent args)
{
if (args.Actor is not { Valid: true } player)
return;
GetInsertedCashAmount(component, out var deposit);
if (!TryGetBalance(component.Account, out var stationBank))
{
_uiSystem.SetUiState(uid, BankATMMenuUiKey.ATM,
new StationBankATMMenuInterfaceState(stationBank, false, deposit));
return;
}
_uiSystem.SetUiState(uid, BankATMMenuUiKey.ATM,
new StationBankATMMenuInterfaceState(stationBank, _access.IsAllowed(player, uid), deposit));
}
private void GetInsertedCashAmount(StationBankATMComponent component, out int amount)
{
amount = 0;
var cashEntity = component.CashSlot.ContainerSlot?.ContainedEntity;
// Nothing inserted: amount should be 0.
if (cashEntity == null)
return;
// Invalid item inserted (doubloons, FUC, telecrystals...): amount should be negative (to denote an error)
if (!TryComp<StackComponent>(cashEntity, out var cashStack) ||
cashStack.StackTypeId != component.CashType)
{
amount = -1;
return;
}
// Valid amount: output the stack's value.
amount = cashStack.Count;
return;
}
private void SetInsertedCashAmount(StationBankATMComponent component, int amount, out int leftAmount, out bool empty)
{
leftAmount = 0;
empty = false;
var cashEntity = component.CashSlot.ContainerSlot?.ContainedEntity;
if (!TryComp<StackComponent>(cashEntity, out var cashStack) ||
cashStack.StackTypeId != component.CashType)
{
return;
}
int newAmount = cashStack.Count;
cashStack.Count = newAmount - amount;
leftAmount = cashStack.Count;
if (cashStack.Count <= 0)
empty = true;
return;
}
private void PlayDenySound(EntityUid uid, StationBankATMComponent component)
{
_audio.PlayPvs(_audio.ResolveSound(component.ErrorSound), uid);
}
private void PlayConfirmSound(EntityUid uid, StationBankATMComponent component)
{
_audio.PlayPvs(_audio.ResolveSound(component.ConfirmSound), uid);
}
}