diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMBoundUserInteface.cs b/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMBoundUserInteface.cs
new file mode 100644
index 0000000000..8f0ca26eaf
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMBoundUserInteface.cs
@@ -0,0 +1,58 @@
+using Content.Shared.AWS.Economy.Bank;
+
+namespace Content.Client.AWS.Economy.Bank.UI;
+
+public sealed class EconomyBankATMBoundUserInteface : BoundUserInterface
+{
+ [ViewVariables]
+ private EconomyBankATMMenu? _menu;
+ private EconomyBankATMAccountInfo? _bankAccount;
+
+ public EconomyBankATMBoundUserInteface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ public void OnWithdrawPressed(ulong amount)
+ {
+ if (_bankAccount is null)
+ return;
+ SendMessage(new EconomyBankATMWithdrawMessage(amount));
+ }
+
+ public void OnTransferPressed(ulong amount, string recipientId)
+ {
+ if (_bankAccount is null)
+ return;
+ SendMessage(new EconomyBankATMTransferMessage(amount, recipientId));
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = new EconomyBankATMMenu(this);
+ _menu.OnClose += Close;
+
+ _menu.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ if (state is not EconomyBankATMUserInterfaceState atmState)
+ return;
+
+ _bankAccount = atmState.BankAccount;
+
+ _menu?.SetBankAcount(atmState.BankAccount);
+ _menu?.SetError(atmState.Error);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+ _menu?.Dispose();
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMMenu.xaml b/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMMenu.xaml
new file mode 100644
index 0000000000..6b9172493e
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMMenu.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMMenu.xaml.cs b/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMMenu.xaml.cs
new file mode 100644
index 0000000000..119646de3f
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyBankATMMenu.xaml.cs
@@ -0,0 +1,95 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.AWS.Economy.Bank;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.AWS.Economy.Bank.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class EconomyBankATMMenu : FancyWindow
+{
+ private EconomyBankATMBoundUserInteface Owner { get; set; }
+
+ private EconomyBankATMAccountInfo? _bankAccount;
+
+ public EconomyBankATMMenu(EconomyBankATMBoundUserInteface owner)
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ Owner = owner;
+
+ SetupMoneySpinBox(WithdrawAmountBox);
+ WithdrawAmountBox.ValueChanged += args =>
+ {
+ RefreshWithdrawButton();
+ };
+ WithdrawButton.OnPressed += _ => Owner.OnWithdrawPressed((ulong) WithdrawAmountBox.Value);
+
+ SetupMoneySpinBox(TransferAmountBox);
+ TransferAmountBox.ValueChanged += args =>
+ {
+ RefreshTransferButton();
+ };
+ TransferButton.OnPressed += _ => Owner.OnTransferPressed((ulong) TransferAmountBox.Value, TransferRecipientField.Text);
+
+ RefreshWithdrawButton();
+ RefreshTransferButton();
+ RefreshAccountInfo();
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ }
+
+ public void SetBankAcount(EconomyBankATMAccountInfo? bankAccount)
+ {
+ _bankAccount = bankAccount;
+ RefreshWithdrawButton();
+ RefreshTransferButton();
+ RefreshAccountInfo();
+ }
+
+ public void SetError(string? error)
+ {
+ ErrorLabel.Text = error;
+ }
+
+ private void RefreshWithdrawButton()
+ {
+ var isEnabled = _bankAccount is not null &&
+ !_bankAccount.Blocked &&
+ _bankAccount.Balance >= (ulong) WithdrawAmountBox.Value;
+ WithdrawButton.Disabled = !isEnabled;
+ }
+
+ private void RefreshTransferButton()
+ {
+ var isEnabled = _bankAccount is not null &&
+ !_bankAccount.Blocked &&
+ _bankAccount.Balance >= (ulong) TransferAmountBox.Value;
+ TransferButton.Disabled = !isEnabled;
+ }
+
+ private void RefreshAccountInfo()
+ {
+ AccountIdLabel.Text = _bankAccount?.AccountId ?? "-";
+ AccountOwnerLabel.Text = _bankAccount?.AccountName ?? "-";
+ AccountBalanceLabel.Text = _bankAccount?.Balance.ToString("N0") ?? "-";
+ }
+
+ private void SetupMoneySpinBox(SpinBox spinBox)
+ {
+ spinBox.AddLeftButton(-1000, "-1000");
+ spinBox.AddLeftButton(-100, "-100");
+ spinBox.AddLeftButton(-10, "-10");
+ spinBox.AddLeftButton(-1, "-1");
+ spinBox.AddRightButton(1, "+1");
+ spinBox.AddRightButton(10, "+10");
+ spinBox.AddRightButton(100, "+100");
+ spinBox.AddRightButton(1000, "+1000");
+ spinBox.IsValid = amount => amount >= 0 && _bankAccount is { } && (ulong) amount <= _bankAccount.Balance;
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleBoundUserInterface.cs b/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleBoundUserInterface.cs
new file mode 100644
index 0000000000..38147d2d7c
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleBoundUserInterface.cs
@@ -0,0 +1,29 @@
+namespace Content.Client.AWS.Economy.Bank.UI;
+
+public sealed class EconomyLogConsoleBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private EconomyLogConsoleMenu? _menu;
+
+ public EconomyLogConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _menu = new EconomyLogConsoleMenu(this);
+ _menu.OnClose += Close;
+
+ _menu?.OpenCentered();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _menu?.Dispose();
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleMenu.xaml b/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleMenu.xaml
new file mode 100644
index 0000000000..ae5056d739
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleMenu.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleMenu.xaml.cs b/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleMenu.xaml.cs
new file mode 100644
index 0000000000..f898f6c1ac
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyLogConsoleMenu.xaml.cs
@@ -0,0 +1,114 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.AWS.Economy.Bank;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface.Controls;
+using System.Linq;
+
+namespace Content.Client.AWS.Economy.Bank.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class EconomyLogConsoleMenu : FancyWindow
+{
+ [Dependency] private readonly EntityManager _entityManager = default!;
+
+ private EconomyLogConsoleBoundUserInterface Owner { get; set; }
+ private IReadOnlyList> _accounts = default!;
+
+ public EconomyLogConsoleMenu(EconomyLogConsoleBoundUserInterface owner)
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ LogDetails.SelectMode = ItemList.ItemListSelectMode.None;
+
+ Owner = owner;
+
+ var bankAccountSystem = _entityManager.System();
+ _accounts = bankAccountSystem.GetAccounts().Values.ToList();
+
+ FillList();
+
+ FindAccount.OnTextEntered += OnTextEnteredAccount;
+ FindLog.OnTextEntered += OnTextEnteredLog;
+
+ }
+
+ private void OnSelectAccount(ItemList.Item accountId)
+ {
+ LogDetails.Clear();
+
+ var account = (accountId.Metadata! as EconomyBankAccountComponent)!;
+
+ if (account.Logs.Count == 0)
+ {
+ LogDetails.AddItem(Loc.GetString("economy-Terminal-NoLogsDetected"));
+ return;
+ }
+ for (int i = account.Logs.Count - 1; i != -1; i--)
+ {
+ var item = account.Logs[i];
+ LogDetails.AddItem("[" + item.Date.ToString("hh\\:mm\\:ss") + "] — " + item.Text);
+ }
+ }
+
+ private void OnTextEnteredAccount(LineEdit.LineEditEventArgs eventArgs)
+ {
+ AccountList.Clear();
+ var upText = eventArgs.Text.ToUpper();
+ foreach (var (key, value) in _accounts)
+ {
+ var fieldName = FormFieldName(value);
+ if (fieldName.Contains(upText))
+ {
+ var field = AccountList.AddItem(fieldName);
+ field.Metadata = value;
+ field.OnSelected += OnSelectAccount;
+ }
+ }
+ if (AccountList.Count == 0)
+ {
+ AccountList.AddItem("No data acquired");
+ return;
+ }
+ AccountList.SortItemsByText();
+ }
+ private void OnTextEnteredLog(LineEdit.LineEditEventArgs eventArgs)
+ {
+
+ if (!AccountList.GetSelected().Any())
+ {
+ LogDetails.Clear();
+ LogDetails.AddItem("Error no select Account");
+ return;
+ }
+ var accountId = AccountList.GetSelected().First();
+ LogDetails.Clear();
+ var upText = eventArgs.Text.ToUpper();
+
+ var account = (accountId.Metadata! as EconomyBankAccountComponent)!;
+
+ for (int i = account.Logs.Count - 1; i != -1; i--)
+ {
+ var item = account.Logs[i];
+ if (item.Text.Contains(upText))
+ LogDetails.AddItem("[" + item.Date.ToString("hh\\:mm\\:ss") + "] — " + item.Text);
+ }
+ }
+
+ private void FillList()
+ {
+ foreach (var (key, value) in _accounts)
+ {
+ var field = AccountList.AddItem(FormFieldName(value));
+ field.Metadata = value;
+ field.OnSelected += OnSelectAccount;
+ }
+ AccountList.SortItemsByText();
+ }
+
+ private string FormFieldName(EconomyBankAccountComponent account)
+ {
+ return account.AccountID + " — " + account.AccountName;
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalBoundUserInterface.cs b/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalBoundUserInterface.cs
new file mode 100644
index 0000000000..3d4ad62c61
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalBoundUserInterface.cs
@@ -0,0 +1,33 @@
+using Content.Shared.AWS.Economy.Bank;
+
+namespace Content.Client.AWS.Economy.Bank.UI;
+
+public sealed class EconomyTerminalBoundUserInterface(EntityUid owner, Enum uiKey)
+ : BoundUserInterface(owner, uiKey)
+{
+ [ViewVariables]
+ private EconomyTerminalMenu? _menu;
+
+ public void OnPayPressed(ulong amount, string reason)
+ {
+ SendMessage(new EconomyTerminalMessage(amount, reason));
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = new EconomyTerminalMenu(this);
+ _menu.OnClose += Close;
+
+ _menu.OpenCentered();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+ _menu?.Dispose();
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalMenu.xaml b/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalMenu.xaml
new file mode 100644
index 0000000000..c1c7cf7f55
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalMenu.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalMenu.xaml.cs b/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalMenu.xaml.cs
new file mode 100644
index 0000000000..e2e70e3d39
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/EconomyTerminalMenu.xaml.cs
@@ -0,0 +1,23 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.AWS.Economy.Bank;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.AWS.Economy.Bank.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class EconomyTerminalMenu : FancyWindow
+{
+ private EconomyTerminalBoundUserInterface Owner { get; set; }
+
+ public EconomyTerminalMenu(EconomyTerminalBoundUserInterface owner)
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ Owner = owner;
+
+ SetButton.OnPressed += _ => Owner.OnPayPressed((ulong) AmountBox.Value, ReasonField.Text);
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleBoundUserInterface.cs b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleBoundUserInterface.cs
new file mode 100644
index 0000000000..8d652e18c3
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleBoundUserInterface.cs
@@ -0,0 +1,100 @@
+using Content.Shared.AWS.Economy.Bank;
+using Content.Shared.Containers.ItemSlots;
+
+namespace Content.Client.AWS.Economy.Bank.UI.ManagementConsole;
+
+public sealed class EconomyManagementConsoleBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private EconomyManagementConsoleMenu? _menu;
+
+ public EconomyManagementConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _menu = new EconomyManagementConsoleMenu(this);
+ _menu.OnClose += Close;
+
+ _menu.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(EconomyManagementConsoleComponent.ConsoleCardID));
+ _menu.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(EconomyManagementConsoleComponent.TargetCardID));
+ _menu?.OpenCentered();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _menu?.Dispose();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ if (state is not EconomyManagementConsoleUserInterfaceState consoleState)
+ return;
+
+ _menu?.UpdateState(consoleState);
+ }
+
+ public void BlockAccountToggle(EconomyBankAccountComponent? account)
+ {
+ if (account is null)
+ return;
+
+ var blocked = !account.Blocked;
+ var msg = new EconomyManagementConsoleChangeParameterMessage(account.AccountID, EconomyBankAccountParam.Blocked, blocked);
+
+ SendMessage(msg);
+ }
+
+ public void ChangeName(EconomyBankAccountComponent? account, string newName)
+ {
+ // reeeee hardcoding
+ if (account is null || newName.Length > 40)
+ return;
+
+ var msg = new EconomyManagementConsoleChangeParameterMessage(account.AccountID, EconomyBankAccountParam.AccountName, newName);
+ SendMessage(msg);
+ }
+
+ public void ChangeJob(EconomyBankAccountComponent? account, string jobName)
+ {
+ if (account is null)
+ return;
+
+ var msg = new EconomyManagementConsoleChangeParameterMessage(account.AccountID, EconomyBankAccountParam.JobName, jobName);
+ SendMessage(msg);
+ }
+
+ public void ChangeSalary(EconomyBankAccountComponent? account, ulong salary)
+ {
+ if (account is null)
+ return;
+
+ var msg = new EconomyManagementConsoleChangeParameterMessage(account.AccountID, EconomyBankAccountParam.Salary, salary);
+ SendMessage(msg);
+ }
+
+ public void ChangeAccountHolderID(NetEntity holder, string newID)
+ {
+ var msg = new EconomyManagementConsoleChangeHolderIDMessage(holder, newID);
+ SendMessage(msg);
+ }
+
+ public void InitializeAccountOnHolder(NetEntity holder)
+ {
+ var msg = new EconomyManagementConsoleInitAccountOnHolderMessage(holder);
+ SendMessage(msg);
+ }
+
+ public void PayBonus(string payer, float bonusPercent, List accounts)
+ {
+ var msg = new EconomyManagementConsolePayBonusMessage(payer, bonusPercent, accounts);
+ SendMessage(msg);
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleMenu.xaml b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleMenu.xaml
new file mode 100644
index 0000000000..43bf8867cd
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleMenu.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleMenu.xaml.cs b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleMenu.xaml.cs
new file mode 100644
index 0000000000..b2e12e6efd
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/EconomyManagementConsoleMenu.xaml.cs
@@ -0,0 +1,79 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.AWS.Economy.Bank;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.AWS.Economy.Bank.UI.ManagementConsole;
+
+[GenerateTypedNameReferences]
+public sealed partial class EconomyManagementConsoleMenu : FancyWindow
+{
+ [Dependency] private readonly EntityManager _entityManager = default!;
+
+ private EconomyManagementConsoleBoundUserInterface Owner { get; set; }
+
+ public EconomyManagementConsoleMenu(EconomyManagementConsoleBoundUserInterface owner)
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ Owner = owner;
+
+ Tabs.SetTabTitle(0, Loc.GetString("economybanksystem-management-console-management-tab"));
+ Tabs.SetTabTitle(1, Loc.GetString("economybanksystem-management-console-account-holder-tab"));
+ Tabs.SetTabTitle(2, Loc.GetString("economybanksystem-management-console-bonus-tab"));
+
+ AccountManagementTab.OnBlockAccountPressed += Owner.BlockAccountToggle;
+ AccountManagementTab.OnChangeNamePressed += Owner.ChangeName;
+ AccountManagementTab.OnChangeJob += Owner.ChangeJob;
+ AccountManagementTab.OnChangeSalary += Owner.ChangeSalary;
+
+ AccountHolderTab.OnChangeNamePressed += Owner.ChangeName;
+ AccountHolderTab.OnBlockAccountPressed += Owner.BlockAccountToggle;
+ AccountHolderTab.OnChangeJob += Owner.ChangeJob;
+ AccountHolderTab.OnChangeSalary += Owner.ChangeSalary;
+ AccountHolderTab.OnChangeAccountPressed += Owner.ChangeAccountHolderID;
+ AccountHolderTab.OnInitializeAccountPressed += Owner.InitializeAccountOnHolder;
+
+ BonusTab.OnPayBonusPressed += Owner.PayBonus;
+ }
+
+ public void UpdateState(EconomyManagementConsoleUserInterfaceState state)
+ {
+ PrivilegedIdButton.Disabled = !state.Priveleged;
+ PrivilegedIdLabel.Text = state.IDCardName ?? "-";
+
+ TargetIdButton.Disabled = true;
+ TargetIdLabel.Text = state.HolderID ?? "-";
+ Entity? holder = null;
+ if (_entityManager.TryGetEntity(state.AccountHolder, out var localHolder) &&
+ _entityManager.TryGetComponent(localHolder, out var holderComp))
+ {
+ TargetIdButton.Disabled = false;
+ holder = (localHolder.Value, holderComp);
+ }
+
+ UpdateAccountManagement(state);
+ UpdateHolder(state, holder);
+ UpdateBonus(state.Priveleged);
+ }
+
+ private void UpdateAccountManagement(EconomyManagementConsoleUserInterfaceState state)
+ {
+ AccountManagementTab.Priveleged = state.Priveleged;
+ AccountManagementTab.UpdateAccountList();
+ AccountManagementTab.OnUpdateState(state.AccountID, state.AccountName, state.Balance, state.Penalty, state.Blocked, state.CanReachPayDay, state.JobName, state.Salary);
+ }
+
+ private void UpdateHolder(EconomyManagementConsoleUserInterfaceState state, Entity? holder)
+ {
+ AccountHolderTab.CurrentCard = holder ?? null;
+ AccountHolderTab.Priveleged = state.Priveleged;
+ AccountHolderTab.OnUpdateState(state.HolderID, state.AccountID, state.AccountName, state.Balance, state.Penalty, state.Blocked, state.CanReachPayDay, state.JobName, state.Salary);
+ }
+
+ private void UpdateBonus(bool priveleged)
+ {
+ BonusTab.OnUpdateState(priveleged);
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountHolderTab.xaml b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountHolderTab.xaml
new file mode 100644
index 0000000000..83d37f2c9c
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountHolderTab.xaml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountHolderTab.xaml.cs b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountHolderTab.xaml.cs
new file mode 100644
index 0000000000..b57981a612
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountHolderTab.xaml.cs
@@ -0,0 +1,235 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface;
+using Content.Shared.AWS.Economy.Bank;
+using Robust.Shared.Prototypes;
+using System.Linq;
+using Content.Shared.Roles;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.AWS.Economy.Bank.UI.ManagementConsole.Tabs;
+
+[GenerateTypedNameReferences]
+public sealed partial class AccountHolderTab : Control
+{
+ private EconomyBankAccountComponent? _currentAccount;
+
+ [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private List _jobs = new();
+ private static ProtoId _defaultJob = "Passenger";
+
+ public Entity? CurrentCard;
+ public bool Priveleged;
+
+ public Action? OnChangeNamePressed;
+ public Action? OnBlockAccountPressed;
+ public Action? OnChangeJob;
+ public Action? OnChangeSalary;
+ public Action? OnChangeAccountPressed;
+ public Action? OnInitializeAccountPressed;
+
+ public AccountHolderTab()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ var jobs = _prototypeManager.EnumeratePrototypes().ToList();
+ jobs.Sort((x, y) => string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCulture));
+ foreach (var job in jobs)
+ {
+ if (!job.OverrideConsoleVisibility.GetValueOrDefault(job.SetPreference))
+ continue;
+
+ _jobs.Add(job.ID);
+ JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobs.Count - 1);
+ }
+
+ SalaryAmountBox.AddLeftButton(-10, "-10");
+ SalaryAmountBox.AddLeftButton(-1, "-1");
+ SalaryAmountBox.AddRightButton(1, "+1");
+ SalaryAmountBox.AddRightButton(10, "+10");
+ SalaryAmountBox.SetButtonDisabled(true);
+
+ GetAccount(CurrentCard);
+
+ SalaryAmountBox.ValueChanged += OnSalaryValueChanged;
+
+ ChangeNameButton.OnPressed += _ =>
+ {
+ if (_currentAccount is null)
+ return;
+
+ OnChangeNamePressed?.Invoke(_currentAccount, ChangeNamePrompt.Text);
+ };
+ BlockButton.OnPressed += _ =>
+ {
+ if (_currentAccount is null)
+ return;
+
+ OnBlockAccountPressed?.Invoke(_currentAccount);
+ };
+ JobPresetOptionButton.OnItemSelected += args =>
+ {
+ args.Button.SelectId(args.Id);
+
+ var bankAccountSystem = _entityManager.System();
+ if (bankAccountSystem.TryGetSalaryJobEntry(_jobs[args.Id], "NanotrasenDefaultSallaries", out var jobEntry))
+ SalaryAmountBox.Value = (int)jobEntry.Value.Sallary;
+ };
+ ChangeSalaryInfoButton.OnPressed += _ =>
+ {
+ if (_currentAccount is null)
+ return;
+
+ var jobName = _currentAccount.JobName;
+ var salary = _currentAccount.Salary;
+ if (JobPresetOptionButton.SelectedId != _jobs.IndexOf(jobName.GetValueOrDefault()))
+ OnChangeJob?.Invoke(_currentAccount, _jobs[JobPresetOptionButton.SelectedId]);
+ if (SalaryAmountBox.Value != (int)salary.GetValueOrDefault())
+ OnChangeSalary?.Invoke(_currentAccount, (ulong)SalaryAmountBox.Value);
+ };
+ ChangeAccountButton.OnPressed += _ =>
+ {
+ if (CurrentCard is null)
+ return;
+
+ var economySystem = _entityManager.System();
+ if (!economySystem.IsValidAccount(ChangeAccountPrompt.Text))
+ {
+ ErrorLabel.Text = Loc.GetString("economybanksystem-management-console-error-invalid-account");
+ return;
+ }
+
+ var netEntity = _entityManager.GetNetEntity(CurrentCard.Value);
+ OnChangeAccountPressed?.Invoke(netEntity, ChangeAccountPrompt.Text);
+ };
+ InitializeAccountButton.OnPressed += _ =>
+ {
+ if (CurrentCard is null)
+ return;
+
+ var netEntity = _entityManager.GetNetEntity(CurrentCard.Value);
+ OnInitializeAccountPressed?.Invoke(netEntity);
+ };
+ }
+
+ private void GetAccount(EconomyAccountHolderComponent? accountHolder)
+ {
+ var economySystem = _entityManager.System();
+ if (accountHolder is not null && economySystem.TryGetAccount(accountHolder.AccountID, out var accountEnt))
+ {
+ _currentAccount = accountEnt.Value.Comp;
+ FillAccount(accountEnt.Value.Comp);
+ UpdateButtons(Priveleged);
+ return;
+ }
+
+ _currentAccount = null;
+ FillAccount(null);
+ UpdateButtons(Priveleged);
+ }
+
+ private void FillAccount(EconomyBankAccountComponent? account)
+ {
+ AccountInitLabel.Text = account is not null ? Loc.GetString("economybanksystem-management-console-management-initialized") :
+ Loc.GetString("economybanksystem-management-console-management-not-initialized");
+ AccountIdLabel.Text = account is not null ? account.AccountID : "-";
+ AccountOwnerLabel.Text = account is not null ? account.AccountName : "-";
+ var balance = account is not null ? string.Format("{0:N0}", account.Balance) : "-";
+ AccountBalanceLabel.Text = balance;
+ var blocked = account is not null ? (account.Blocked ? Loc.GetString("economybanksystem-management-console-management-block") : Loc.GetString("economybanksystem-management-console-management-unblock"))
+ : "-";
+ var blockedButton = account is not null ? (account.Blocked ? Loc.GetString("economybanksystem-management-console-management-unblock-button") : Loc.GetString("economybanksystem-management-console-management-block-button"))
+ : "-";
+ AccountBlockStatusLabel.Text = blocked;
+ BlockButton.Text = blockedButton;
+ var salary = account?.Salary ?? 0;
+ var paydayStatus = account is not null ? (account.CanReachPayDay ? Loc.GetString("economybanksystem-management-console-management-salary-reachable", ("salary", salary)) : Loc.GetString("economybanksystem-management-console-management-salary-not-reachable"))
+ : "-";
+ AccountPaydayStatusLabel.Text = paydayStatus;
+ JobPresetOptionButton.SelectId(GetJobIndex(account?.JobName));
+ SalaryAmountBox.Value = (int)salary;
+ }
+
+ private void UpdateButtons(bool priveleged)
+ {
+ var noAccount = !priveleged || _currentAccount is null;
+
+ ChangeNameButton.Disabled = noAccount;
+ BlockButton.Disabled = noAccount;
+ ChangeAccountButton.Disabled = !priveleged || CurrentCard is null;
+ InitializeAccountButton.Disabled = !priveleged || _currentAccount is not null || CurrentCard is null;
+ JobPresetOptionButton.Disabled = noAccount;
+ SalaryAmountBox.LineEditDisabled = noAccount;
+ SalaryAmountBox.SetButtonDisabled(noAccount);
+ ChangeSalaryInfoButton.Disabled = noAccount;
+ }
+
+ private void OnSalaryValueChanged(ValueChangedEventArgs args)
+ {
+ // what shitcode does to a man
+ SalaryAmountBox.ValueChanged -= OnSalaryValueChanged;
+ SalaryAmountBox.Value = Math.Max(0, args.Value);
+ SalaryAmountBox.ValueChanged += OnSalaryValueChanged;
+ }
+
+ private int GetJobIndex(string? jobName)
+ {
+ var jobIndex = jobName is not null ? _jobs.IndexOf(jobName) : _jobs.IndexOf(_defaultJob);
+ return jobIndex < 0 ? _jobs.IndexOf(_defaultJob) : jobIndex;
+ }
+
+ public void OnUpdateState(string? holderID,
+ string? accountID,
+ string? accountName,
+ ulong? balance,
+ ulong? penalty,
+ bool? blocked,
+ bool? canReachPayDay,
+ string? jobName,
+ ulong? salary)
+ {
+ if (CurrentCard is null || holderID is null)
+ {
+ _currentAccount = null;
+ FillAccount(null);
+ UpdateButtons(Priveleged);
+ return;
+ }
+
+ if (holderID != accountID)
+ {
+ FillAccount(_currentAccount);
+ UpdateButtons(Priveleged);
+ return;
+ }
+
+ AccountInitLabel.Text = accountID is not null ? Loc.GetString("economybanksystem-management-console-management-initialized") :
+ Loc.GetString("economybanksystem-management-console-management-not-initialized");
+ accountID ??= "-";
+ AccountIdLabel.Text = accountID;
+ AccountOwnerLabel.Text = accountName ?? "-";
+ var accountBalance = balance is not null ? string.Format("{0:N0}", balance) : "-";
+ AccountBalanceLabel.Text = accountBalance;
+ var accountBlocked = blocked is not null ? (blocked.Value ? Loc.GetString("economybanksystem-management-console-management-block") : Loc.GetString("economybanksystem-management-console-management-unblock"))
+ : "-";
+ var blockedButton = blocked is not null ? (blocked.Value ? Loc.GetString("economybanksystem-management-console-management-unblock-button") : Loc.GetString("economybanksystem-management-console-management-block-button"))
+ : "-";
+ AccountBlockStatusLabel.Text = accountBlocked;
+ BlockButton.Text = blockedButton;
+ salary ??= 0;
+ var paydayStatus = canReachPayDay is not null ? (canReachPayDay.Value ? Loc.GetString("economybanksystem-management-console-management-salary-reachable", ("salary", salary)) : Loc.GetString("economybanksystem-management-console-management-salary-not-reachable"))
+ : "-";
+ AccountPaydayStatusLabel.Text = paydayStatus;
+ JobPresetOptionButton.SelectId(GetJobIndex(jobName));
+ SalaryAmountBox.Value = (int)salary;
+
+ _currentAccount = null;
+ var economySystem = _entityManager.System();
+ if (economySystem.TryGetAccount(accountID, out var foundAccount))
+ _currentAccount = foundAccount.Value.Comp;
+ UpdateButtons(Priveleged);
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountManagementTab.xaml b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountManagementTab.xaml
new file mode 100644
index 0000000000..712083271a
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountManagementTab.xaml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountManagementTab.xaml.cs b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountManagementTab.xaml.cs
new file mode 100644
index 0000000000..a8e1ceef1b
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/AccountManagementTab.xaml.cs
@@ -0,0 +1,247 @@
+using Content.Shared.AWS.Economy.Bank;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface.Controls;
+using System.Linq;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+using Content.Shared.Roles;
+
+namespace Content.Client.AWS.Economy.Bank.UI.ManagementConsole.Tabs;
+
+[GenerateTypedNameReferences]
+public sealed partial class AccountManagementTab : Control
+{
+ [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private IReadOnlyList> _accounts = default!;
+ private List _jobs = new();
+ private static ProtoId _defaultJob = "Passenger";
+ private EconomyBankAccountComponent? _currentAccount;
+
+ public Action? OnBlockAccountPressed;
+ public Action? OnChangeNamePressed;
+ public Action? OnChangeJob;
+ public Action? OnChangeSalary;
+
+ public bool Priveleged;
+
+ public AccountManagementTab()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ var jobs = _prototypeManager.EnumeratePrototypes().ToList();
+ jobs.Sort((x, y) => string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCulture));
+ foreach (var job in jobs)
+ {
+ if (!job.OverrideConsoleVisibility.GetValueOrDefault(job.SetPreference))
+ continue;
+
+ _jobs.Add(job.ID);
+ JobPresetOptionButton.AddItem(Loc.GetString(job.Name), _jobs.Count - 1);
+ }
+
+ SalaryAmountBox.AddLeftButton(-10, "-10");
+ SalaryAmountBox.AddLeftButton(-1, "-1");
+ SalaryAmountBox.AddRightButton(1, "+1");
+ SalaryAmountBox.AddRightButton(10, "+10");
+ SalaryAmountBox.SetButtonDisabled(true);
+
+ UpdateAccountList();
+
+ FindAccount.OnTextEntered += OnTextEnteredAccount;
+ SalaryAmountBox.ValueChanged += OnSalaryValueChanged;
+
+ BlockButton.OnPressed += _ =>
+ {
+ if (_currentAccount is null)
+ return;
+
+ OnBlockAccountPressed?.Invoke(_currentAccount);
+ };
+ ChangeNameButton.OnPressed += _ =>
+ {
+ if (_currentAccount is null || ChangeNameBox.Text.Length <= 0)
+ return;
+
+ OnChangeNamePressed?.Invoke(_currentAccount, ChangeNameBox.Text);
+ ChangeNameBox.Clear();
+ };
+ JobPresetOptionButton.OnItemSelected += args =>
+ {
+ args.Button.SelectId(args.Id);
+
+ var bankAccountSystem = _entityManager.System();
+ if (bankAccountSystem.TryGetSalaryJobEntry(_jobs[args.Id], "NanotrasenDefaultSallaries", out var jobEntry))
+ SalaryAmountBox.Value = (int)jobEntry.Value.Sallary;
+ };
+ ChangeSalaryInfoButton.OnPressed += _ =>
+ {
+ if (_currentAccount is null)
+ return;
+
+ var jobName = _currentAccount.JobName;
+ var salary = _currentAccount.Salary;
+ if (JobPresetOptionButton.SelectedId != _jobs.IndexOf(jobName.GetValueOrDefault()))
+ OnChangeJob?.Invoke(_currentAccount, _jobs[JobPresetOptionButton.SelectedId]);
+ if (SalaryAmountBox.Value != (int)salary.GetValueOrDefault())
+ OnChangeSalary?.Invoke(_currentAccount, (ulong)SalaryAmountBox.Value);
+ };
+ }
+
+ public void UpdateAccountList()
+ {
+ AccountList.Clear();
+ var bankAccountSystem = _entityManager.System();
+ var accounts = bankAccountSystem.GetAccounts(EconomyBankAccountMask.All);
+ _accounts = accounts.Values.ToList();
+
+ FillList();
+ }
+
+ public void OnUpdateState(string? accountID,
+ string? accountName,
+ ulong? balance,
+ ulong? penalty,
+ bool? blocked,
+ bool? canReachPayDay,
+ string? jobName,
+ ulong? salary)
+ {
+ if (accountID is null || _currentAccount?.AccountID != accountID)
+ {
+ ClearCurrentAccount();
+ return;
+ }
+
+ accountID ??= "-";
+ AccountIdLabel.Text = accountID;
+ AccountOwnerLabel.Text = accountName ?? "-";
+ var accountBalance = balance is not null ? string.Format("{0:N0}", balance) : "-";
+ AccountBalanceLabel.Text = accountBalance;
+ var accountBlocked = blocked is not null ? (blocked.Value ? Loc.GetString("economybanksystem-management-console-management-block") : Loc.GetString("economybanksystem-management-console-management-unblock"))
+ : "-";
+ AccountBlockedLabel.Text = accountBlocked;
+ salary ??= 0;
+ AccountPaydayStatusLabel.Text = canReachPayDay is not null ? (canReachPayDay.Value ? Loc.GetString("economybanksystem-management-console-management-salary-reachable", ("salary", salary)) : Loc.GetString("economybanksystem-management-console-management-salary-not-reachable"))
+ : "-";
+ var blockedButton = blocked is not null ? (blocked.Value ? Loc.GetString("economybanksystem-management-console-management-unblock-button") : Loc.GetString("economybanksystem-management-console-management-block-button"))
+ : "-";
+ BlockButton.Text = blockedButton;
+ JobPresetOptionButton.SelectId(GetJobIndex(jobName));
+ SalaryAmountBox.Value = (int)salary;
+
+ var economySystem = _entityManager.System();
+ if (economySystem.TryGetAccount(accountID, out var foundAccount))
+ _currentAccount = foundAccount.Value.Comp;
+ UpdateButtons(Priveleged);
+ }
+
+ private void OnSelectAccount(ItemList.Item accountId)
+ {
+ if (accountId.Metadata is not EconomyBankAccountComponent account)
+ return;
+
+ _currentAccount = account;
+ FillAccountInfo(account);
+ UpdateButtons(Priveleged);
+ }
+
+ private void OnSalaryValueChanged(ValueChangedEventArgs args)
+ {
+ // what shitcode does to a man
+ SalaryAmountBox.ValueChanged -= OnSalaryValueChanged;
+ SalaryAmountBox.Value = Math.Max(0, args.Value);
+ SalaryAmountBox.ValueChanged += OnSalaryValueChanged;
+ }
+
+ private void OnTextEnteredAccount(LineEdit.LineEditEventArgs eventArgs)
+ {
+ AccountList.Clear();
+ var upText = eventArgs.Text.ToUpper();
+ foreach (var (key, value) in _accounts)
+ {
+ var fieldName = FormFieldName(value);
+ if (fieldName.Contains(upText))
+ {
+ var field = AccountList.AddItem(fieldName);
+ field.Metadata = value;
+ field.OnSelected += OnSelectAccount;
+ }
+ }
+ if (AccountList.Count == 0)
+ {
+ AccountList.AddItem("No data acquired");
+ return;
+ }
+ AccountList.SortItemsByText();
+ }
+
+ private void FillList()
+ {
+ foreach (var (key, value) in _accounts)
+ {
+ var field = AccountList.AddItem(FormFieldName(value));
+ field.Metadata = value;
+ field.OnSelected += OnSelectAccount;
+ }
+
+ AccountList.SortItemsByText();
+ }
+
+ private string FormFieldName(EconomyBankAccountComponent account)
+ {
+ return account.AccountID + " — " + account.AccountName;
+ }
+
+ private int GetJobIndex(string? jobName)
+ {
+ var jobIndex = jobName is not null ? _jobs.IndexOf(jobName) : _jobs.IndexOf(_defaultJob);
+ return jobIndex < 0 ? _jobs.IndexOf(_defaultJob) : jobIndex;
+ }
+
+ private void UpdateButtons(bool priveleged)
+ {
+ var disabled = _currentAccount is null || !priveleged;
+
+ BlockButton.Disabled = disabled;
+ ChangeNameButton.Disabled = disabled;
+ JobPresetOptionButton.Disabled = disabled;
+ SalaryAmountBox.LineEditDisabled = disabled;
+ SalaryAmountBox.SetButtonDisabled(disabled);
+ ChangeSalaryInfoButton.Disabled = disabled;
+ }
+
+ private void ClearCurrentAccount()
+ {
+ AccountIdLabel.Text = "-";
+ AccountOwnerLabel.Text = "-";
+ AccountBalanceLabel.Text = "-";
+ AccountBlockedLabel.Text = "-";
+ AccountPaydayStatusLabel.Text = "-";
+ BlockButton.Text = "-";
+ SalaryAmountBox.Value = 0;
+
+ _currentAccount = null;
+ UpdateButtons(Priveleged);
+ }
+
+ private void FillAccountInfo(EconomyBankAccountComponent account)
+ {
+ AccountIdLabel.Text = account.AccountID;
+ AccountOwnerLabel.Text = account.AccountName;
+ var balance = account.Balance;
+ AccountBalanceLabel.Text = balance.ToString("N0") ?? "-";
+ AccountBlockedLabel.Text = account.Blocked ? Loc.GetString("economybanksystem-management-console-management-block") :
+ Loc.GetString("economybanksystem-management-console-management-unblock");
+ var salary = account.Salary ?? 0;
+ AccountPaydayStatusLabel.Text = account.CanReachPayDay ? Loc.GetString("economybanksystem-management-console-management-salary-reachable", ("salary", salary)) :
+ Loc.GetString("economybanksystem-management-console-management-salary-not-reachable");
+ BlockButton.Text = account.Blocked ? Loc.GetString("economybanksystem-management-console-management-unblock-button") :
+ Loc.GetString("economybanksystem-management-console-management-block-button");
+ JobPresetOptionButton.SelectId(GetJobIndex(account.JobName));
+ SalaryAmountBox.Value = (int)salary;
+ }
+}
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/BonusTab.xaml b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/BonusTab.xaml
new file mode 100644
index 0000000000..d4393c01d0
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/BonusTab.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/BonusTab.xaml.cs b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/BonusTab.xaml.cs
new file mode 100644
index 0000000000..0a7cc76f08
--- /dev/null
+++ b/Content.Client/AWS/Economy/Bank/UI/ManagementConsole/Tabs/BonusTab.xaml.cs
@@ -0,0 +1,254 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface;
+using Content.Shared.AWS.Economy.Bank;
+using System.Linq;
+using Robust.Client.UserInterface.Controls;
+using Content.Shared.Roles;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.AWS.Economy.Bank.UI.ManagementConsole.Tabs;
+
+[GenerateTypedNameReferences]
+public sealed partial class BonusTab : Control
+{
+ [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private IReadOnlyList> _cachedAccounts = default!;
+ private List> _payerAccounts = new();
+ private List _departments = new();
+ private DepartmentPrototype? _selectedDepartment;
+ private List _selectedAccounts = new();
+ private EconomyBankAccountComponent? _selectedPayer;
+
+ public Action>? OnPayBonusPressed;
+
+ public bool Priveleged;
+
+ public BonusTab()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ var departments = _prototypeManager.EnumeratePrototypes().ToList();
+ departments.Sort((x, y) => string.Compare(Loc.GetString(x.ID), Loc.GetString(y.ID), StringComparison.CurrentCulture));
+ DepartmentFilterOptionButton.AddItem("-", 0); // don't forget about it
+ foreach (var department in departments)
+ {
+ _departments.Add(department);
+ DepartmentFilterOptionButton.AddItem(Loc.GetString(department.ID), _departments.Count); // don't forget about it
+ }
+
+ UpdateAccounts();
+ UpdateButtons(Priveleged);
+ FillList();
+ FillInfo();
+
+ FindAccount.OnTextEntered += OnTextEnteredAccount;
+ BonusPercentBox.ValueChanged += OnBonusValueChanged;
+
+ BonusPercentBox.AddLeftButton(-10, "-10%");
+ BonusPercentBox.AddLeftButton(-1, "-1%");
+ BonusPercentBox.AddRightButton(1, "+1%");
+ BonusPercentBox.AddRightButton(10, "+10%");
+
+ DepartmentFilterOptionButton.OnItemSelected += args =>
+ {
+ args.Button.SelectId(args.Id);
+
+ _selectedDepartment = args.Id == 0 ? null : _departments[args.Id - 1];
+ FillList();
+ };
+ PayerAccountOptionButton.OnItemSelected += args =>
+ {
+ args.Button.SelectId(args.Id);
+
+ _selectedPayer = _payerAccounts[args.Id];
+ UpdateButtons(Priveleged);
+ FillInfo();
+ };
+ PayBonusButton.OnPressed += _ =>
+ {
+ if (_selectedAccounts.Count <= 0 || _selectedPayer is null)
+ return;
+
+ var accountIDs = new List();
+ foreach (var account in _selectedAccounts)
+ accountIDs.Add(account.AccountID);
+
+ var bonusPercent = BonusPercentBox.Value / 100f;
+ OnPayBonusPressed?.Invoke(_selectedPayer.AccountID, bonusPercent, accountIDs);
+ };
+ ClearSelectedButton.OnPressed += _ =>
+ {
+ if (_selectedAccounts.Count <= 0)
+ return;
+
+ SelectedAccountList.Clear();
+ _selectedAccounts.Clear();
+ UpdateButtons(Priveleged);
+ FillList();
+ FillInfo();
+ };
+ }
+
+ public void OnUpdateState(bool priveleged)
+ {
+ Priveleged = priveleged;
+ UpdateAccounts();
+ UpdateButtons(Priveleged);
+ FillList();
+ FillInfo();
+ }
+
+ private void UpdateAccounts()
+ {
+ var bankAccountSystem = _entityManager.System();
+ var tags = new List() { BankAccountTag.Personal };
+ _cachedAccounts = bankAccountSystem.GetAccounts(EconomyBankAccountMask.ByTags, tags).Values.ToList();
+
+ var payerTags = new List() { BankAccountTag.Station };
+ var payers = bankAccountSystem.GetAccounts(EconomyBankAccountMask.ByTags, payerTags).Values;
+ PayerAccountOptionButton.Clear();
+ _payerAccounts.Clear();
+ if (payers.Any())
+ {
+ foreach (var account in payers)
+ {
+ _payerAccounts.Add(account);
+ PayerAccountOptionButton.AddItem(account.Comp.AccountID, _payerAccounts.Count - 1);
+ }
+ _selectedPayer = _payerAccounts.FirstOrDefault().Comp;
+ PayerAccountOptionButton.SelectId(0);
+ }
+ }
+
+ private void UpdateButtons(bool priveleged)
+ {
+ PayerAccountOptionButton.Disabled = !priveleged;
+ BonusPercentBox.LineEditDisabled = !priveleged;
+ BonusPercentBox.SetButtonDisabled(!priveleged);
+ PayBonusButton.Disabled = !priveleged ||
+ _selectedPayer == null ||
+ _selectedAccounts.Count <= 0 ||
+ CalculateBonusPayment() > _selectedPayer.Balance;
+ ClearSelectedButton.Disabled = _selectedAccounts.Count <= 0;
+ }
+
+ private void FillList()
+ {
+ AccountList.Clear();
+ foreach (var (key, value) in _cachedAccounts)
+ {
+ // that's nasty
+ if (_selectedAccounts.Contains(value))
+ continue;
+
+ if (_selectedDepartment != null && value.JobName is { } job && !_selectedDepartment.Roles.Contains(job))
+ continue;
+
+ if (_selectedDepartment != null && value.JobName == null)
+ continue;
+
+ var field = AccountList.AddItem(FormFieldName(value));
+ field.Metadata = value;
+ field.OnSelected += OnSelectAccount;
+ }
+
+ AccountList.SortItemsByText();
+ }
+
+ private string FormFieldName(EconomyBankAccountComponent account)
+ {
+ return account.AccountID + " — " + account.AccountName;
+ }
+
+ private void UpdateSelectedAccounts()
+ {
+ SelectedAccountList.Clear();
+ foreach (var account in _selectedAccounts)
+ {
+ var field = SelectedAccountList.AddItem(FormFieldName(account));
+ field.Metadata = account;
+ field.OnSelected += OnSelectSelectedAccount;
+ }
+
+ SelectedAccountList.SortItemsByText();
+ }
+
+ private void OnSelectAccount(ItemList.Item accountId)
+ {
+ if (accountId.Metadata is not EconomyBankAccountComponent account)
+ return;
+
+ _selectedAccounts.Add(account);
+ UpdateSelectedAccounts();
+ UpdateButtons(Priveleged);
+ FillList();
+ FillInfo();
+ }
+
+ private void OnSelectSelectedAccount(ItemList.Item accountId)
+ {
+ if (accountId.Metadata is not EconomyBankAccountComponent account)
+ return;
+
+ _selectedAccounts.Remove(account);
+ UpdateSelectedAccounts();
+ UpdateButtons(Priveleged);
+ FillList();
+ FillInfo();
+ }
+
+ private void OnBonusValueChanged(ValueChangedEventArgs args)
+ {
+ // what shitcode does to a man
+ BonusPercentBox.ValueChanged -= OnBonusValueChanged;
+ BonusPercentBox.Value = Math.Max(0, args.Value);
+ BonusPercentBox.ValueChanged += OnBonusValueChanged;
+
+ FillInfo();
+ }
+
+ private void FillInfo()
+ {
+ PayerAccountBalanceLabel.Text = string.Format("{0:N0}", _selectedPayer?.Balance);
+ TotalBonusLabel.Text = string.Format("{0:N0}", CalculateBonusPayment());
+ }
+
+ private ulong CalculateBonusPayment()
+ {
+ if (_selectedAccounts.Count <= 0)
+ return 0;
+
+ var bonusPercent = BonusPercentBox.Value / 100f;
+ ulong total = 0;
+ foreach (var account in _selectedAccounts)
+ total += account.Salary is not null ? (ulong)(account.Salary * bonusPercent) : 0;
+
+ return total;
+ }
+
+ private void OnTextEnteredAccount(LineEdit.LineEditEventArgs eventArgs)
+ {
+ AccountList.Clear();
+ var upText = eventArgs.Text.ToUpper();
+ foreach (var (key, value) in _cachedAccounts)
+ {
+ var fieldName = FormFieldName(value);
+ if (fieldName.Contains(upText))
+ {
+ var field = AccountList.AddItem(fieldName);
+ field.Metadata = value;
+ field.OnSelected += OnSelectAccount;
+ }
+ }
+ if (AccountList.Count == 0)
+ {
+ AccountList.AddItem("No data acquired");
+ return;
+ }
+ AccountList.SortItemsByText();
+ }
+}
diff --git a/Content.Client/AWS/Economy/Insurance/EconomyInsuranceSystem.cs b/Content.Client/AWS/Economy/Insurance/EconomyInsuranceSystem.cs
new file mode 100644
index 0000000000..2dda7ed04e
--- /dev/null
+++ b/Content.Client/AWS/Economy/Insurance/EconomyInsuranceSystem.cs
@@ -0,0 +1,15 @@
+using Content.Shared.Access.Components;
+using Content.Shared.Access.Systems;
+using Content.Shared.AWS.Economy.Insurance;
+using Content.Shared.Inventory;
+using Content.Shared.PDA;
+using Content.Shared.Preferences;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.AWS.Economy.Insurance;
+
+public sealed class EconomyInsuranceSystem : EconomyInsuranceSystemShared
+{
+}
diff --git a/Content.Client/AWS/Economy/Insurance/EconomyShowInsuranceIconsSystem.cs b/Content.Client/AWS/Economy/Insurance/EconomyShowInsuranceIconsSystem.cs
new file mode 100644
index 0000000000..650df11906
--- /dev/null
+++ b/Content.Client/AWS/Economy/Insurance/EconomyShowInsuranceIconsSystem.cs
@@ -0,0 +1,98 @@
+using Content.Shared.Overlays;
+using Content.Shared.Security.Components;
+using Content.Shared.StatusIcon;
+using Content.Shared.StatusIcon.Components;
+using Robust.Shared.Prototypes;
+using Content.Client.Overlays;
+using Content.Shared.AWS.Economy.Insurance;
+using Robust.Shared.Timing;
+using Robust.Client.Timing;
+using Robust.Shared.Utility;
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.IdentityManagement.Components;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Store;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Access.Components;
+using Content.Shared.PDA;
+using Content.Shared.Access.Systems;
+
+namespace Content.Client.AWS.Economy.Insurance;
+
+public sealed class EconomyShowInsuranceIconsSystem : EquipmentHudSystem
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IClientGameTiming _timing = default!;
+ [Dependency] private readonly EconomyInsuranceSystem _insurance = default!;
+ [Dependency] private readonly AccessReaderSystem _accessReader = default!;
+
+ private readonly TimeSpan _checkTimeRelay = TimeSpan.FromSeconds(3);
+ private readonly ProtoId _defaultIcon = "NonStatus";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGetStatusIconsEvent, after: new[] { typeof(ShowJobIconsSystem) });
+ }
+
+ private void OnGetStatusIconsEvent(Entity ent, ref GetStatusIconsEvent ev)
+ {
+ if (!IsActive)
+ return;
+
+ var comp = FindAnyCardWithComponent(ent);
+
+ if (comp is not null && TryGetInsuranceIcon(comp, out var icon))
+ ev.StatusIcons.Add(icon);
+ }
+
+ private EconomyInsuranceComponent? FindAnyCardWithComponent(EntityUid ent)
+ {
+ if (_accessReader.FindAccessItemsInventory(ent, out var items))
+ {
+ foreach (var item in items)
+ {
+ if (TryComp(item, out var comp))
+ return comp;
+
+ if (TryComp(item, out var pda)
+ && pda.ContainedId is not null
+ && TryComp(pda.ContainedId, out comp))
+ return comp;
+ }
+ }
+
+ return null;
+ }
+
+ private bool TryGetInsuranceIcon(EconomyInsuranceComponent comp, [NotNullWhen(true)] out EconomyInsuranceIconPrototype? icon)
+ {
+ //icon = null;
+
+ //var curTime = _timing.CurTime;
+ //if (comp.NextIconCheck >= curTime)
+ //{
+ // icon = comp.Icon;
+ // return true;
+ //}
+
+ ////prevent memory leak
+ //comp.Icon = null!;
+
+ icon = null;
+
+ if (_prototype.TryIndex(comp.IconPrototype, out var indexedIcon))
+ {
+ comp.Icon = indexedIcon;
+
+ icon = indexedIcon;
+
+ return true;
+ }
+
+ icon = comp.Icon;
+ return false;
+ }
+}
diff --git a/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalBoundUserInterface.cs b/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalBoundUserInterface.cs
new file mode 100644
index 0000000000..0c089f3c78
--- /dev/null
+++ b/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalBoundUserInterface.cs
@@ -0,0 +1,71 @@
+using Content.Shared.AWS.Economy.Bank;
+using Content.Shared.AWS.Economy.Insurance;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Toolshed.TypeParsers;
+using System.Linq;
+
+namespace Content.Client.AWS.Economy.Insurance.UI;
+
+public sealed class EconomyInsuranceTerminalBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ [ViewVariables]
+ private EconomyInsuranceTerminalMenu? _menu;
+
+ [ViewVariables]
+ private EconomyInsuranceTerminalRights _insuranceRights = EconomyInsuranceTerminalRights.Its;
+
+ [ViewVariables]
+ private Dictionary _infos = new();
+
+ [ViewVariables]
+ private int _insertedInsuranceId = 0;
+
+ [ViewVariables]
+ private List _insurancePrototypes = new();
+
+ protected override void Open()
+ {
+ base.Open();
+
+ FetchPrototypes();
+
+ _menu = new(_insurancePrototypes);
+ _menu.ConfirmEditInsurance += insuranceInfo => SendMessage(new EconomyInsuranceEditMessage(insuranceInfo));
+ _menu.OnClose += Close;
+
+ _menu.OpenCentered();
+ _menu.UpdateInfo(_insertedInsuranceId, _insuranceRights, _infos);
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ if (state is EconomyInsuranceUserInterfaceState insuranceState)
+ {
+ _infos = insuranceState.Infos;
+ _insuranceRights = insuranceState.Rights;
+ _insertedInsuranceId = insuranceState.Id;
+
+
+ _menu?.UpdateInfo(_insertedInsuranceId, _insuranceRights, _infos);
+
+ return;
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+ _menu?.Dispose();
+ }
+
+ private void FetchPrototypes()
+ {
+ if (_prototype.TryGetInstances(out var instances))
+ _insurancePrototypes = instances.Values.ToList();
+ }
+}
diff --git a/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalMenu.xaml b/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalMenu.xaml
new file mode 100644
index 0000000000..21a6bdd877
--- /dev/null
+++ b/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalMenu.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalMenu.xaml.cs b/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalMenu.xaml.cs
new file mode 100644
index 0000000000..270fb71d53
--- /dev/null
+++ b/Content.Client/AWS/Economy/Insurance/UI/EconomyInsuranceTerminalMenu.xaml.cs
@@ -0,0 +1,138 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.AWS.Economy.Bank;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface.Controls;
+using Content.Shared.AWS.Economy.Insurance;
+using Content.Client.Ghost.UI;
+using System.Linq;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.AWS.Economy.Insurance.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class EconomyInsuranceTerminalMenu : FancyWindow
+{
+ public Action? ConfirmEditInsurance;
+
+ private List _prototypes;
+ private Dictionary _infos = default!;
+ private EconomyInsuranceTerminalRights _insuranceRights = EconomyInsuranceTerminalRights.Its;
+ private int _insertedInsuranceId = 0;
+ private int _currentSelectedInsuranceId = 0;
+ private ProtoId _selectedPrototype;
+
+ public EconomyInsuranceTerminalMenu(List prototypes)
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ _prototypes = prototypes;
+
+ InfoContainerButtonConfirm.OnPressed += _ => ConfirmEditInsurance?.Invoke(ExtractNewInfo());
+ }
+
+ public void UpdateInfo(int id, EconomyInsuranceTerminalRights rights, Dictionary infos)
+ {
+ _infos = infos;
+ _insertedInsuranceId = id;
+ _insuranceRights = rights;
+
+ UpdateContainters();
+ }
+
+ private void UpdateContainters()
+ {
+ UpdateLeftContainer();
+ }
+
+ private EconomyInsuranceInfo ExtractNewInfo()
+ {
+ return new EconomyInsuranceInfo(_currentSelectedInsuranceId, _selectedPrototype, InfoContainerEditName.Text,
+ InfoContainerEditFromPayAccount.Text, InfoContainerEditDNA.Text);
+ }
+
+ private void UpdateLeftContainer()
+ {
+ TargetListContainer.Clear();
+
+ if (_infos is null)
+ return;
+
+ foreach (var (id, value) in _infos)
+ {
+ var item = TargetListContainer.AddItem(value.InsurerName);
+
+ item.OnSelected += item => UpdateRightContainer(id);
+
+ if (id == _insertedInsuranceId)
+ item.Selected = true;
+ }
+ }
+
+ private void SelectInsurancePrototype(ProtoId prototype)
+ {
+ InfoContainerOptionInsuranceChoose.SelectId(prototype.GetHashCode());
+ _selectedPrototype = prototype;
+ }
+
+ private void UpdateRightContainer(int id)
+ {
+ if (!_infos.TryGetValue(id, out var currentInfo))
+ return;
+
+ _currentSelectedInsuranceId = id;
+
+ var currentProto = _prototypes.First(x => x.ID == currentInfo.InsuranceProto);
+
+ InfoContainerEditName.Text = currentInfo.InsurerName;
+ InfoContainerEditDNA.Text = currentInfo.DNA;
+ InfoContainerEditFromPayAccount.Text = currentInfo.PayerAccountId;
+
+ // hardcode yes
+ InfoContainerEditToPayAccount.Text = "NT-Medical";
+ InfoContainerEditCost.Text = currentProto.Cost.ToString();
+
+ InfoContainerOptionInsuranceChoose.Clear();
+
+ foreach (var prototype in _prototypes)
+ {
+ InfoContainerOptionInsuranceChoose.AddItem(prototype.Name, prototype.ID.GetHashCode());
+ InfoContainerOptionInsuranceChoose.OnItemSelected += args =>
+ {
+ foreach (var proto in _prototypes)
+ {
+ if (proto.ID.GetHashCode() == args.Id)
+ SelectInsurancePrototype(proto);
+ }
+ };
+ }
+
+ SelectInsurancePrototype(currentProto);
+
+ if (_insuranceRights == EconomyInsuranceTerminalRights.Full)
+ {
+ InfoContainerEditName.Editable = true;
+ InfoContainerEditDNA.Editable = true;
+ InfoContainerEditFromPayAccount.Editable = true;
+ InfoContainerEditToPayAccount.Editable = true;
+ //InfoContainerEditCost.Editable = true; maybe in future
+ InfoContainerOptionInsuranceChoose.Disabled = false;
+ return;
+ }
+
+ InfoContainerEditName.Editable = false;
+ InfoContainerEditDNA.Editable = false;
+ InfoContainerEditFromPayAccount.Editable = false;
+ InfoContainerEditToPayAccount.Editable = false;
+ //InfoContainerEditCost.Editable = false; maybe in future
+
+ if (id == _insertedInsuranceId)
+ {
+ InfoContainerOptionInsuranceChoose.Disabled = false;
+ return;
+ }
+
+ InfoContainerOptionInsuranceChoose.Disabled = true;
+ }
+}
diff --git a/Content.Client/AWS/Historical/HistoricalSystem.cs b/Content.Client/AWS/Historical/HistoricalSystem.cs
new file mode 100644
index 0000000000..50c317f6ad
--- /dev/null
+++ b/Content.Client/AWS/Historical/HistoricalSystem.cs
@@ -0,0 +1,11 @@
+using Content.Shared.AWS.Historical;
+
+namespace Content.Client.AWS.Historical;
+
+public sealed class HistoricalSystem : SharedHistoricalSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ }
+}
diff --git a/Content.Client/AWS/Historical/HistoricalUiStorage.cs b/Content.Client/AWS/Historical/HistoricalUiStorage.cs
new file mode 100644
index 0000000000..143aa76e21
--- /dev/null
+++ b/Content.Client/AWS/Historical/HistoricalUiStorage.cs
@@ -0,0 +1,12 @@
+using Content.Shared.AWS.Historical;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.AWS.Historical;
+
+public record HistoricalUiStorage(
+ List> HistoryByButtonId,
+ Dictionary> SelectedHistories,
+ Dictionary DescriptionFieldForTypes,
+ Dictionary OptionButtonsByType,
+ Dictionary>> Histories);
diff --git a/Content.Client/AWS/Skills/SkillControlMeta.cs b/Content.Client/AWS/Skills/SkillControlMeta.cs
new file mode 100644
index 0000000000..ddf1e92fbf
--- /dev/null
+++ b/Content.Client/AWS/Skills/SkillControlMeta.cs
@@ -0,0 +1,25 @@
+using Content.Shared.AWS.Skills;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.AWS.Skills
+{
+ internal sealed class SkillControlMeta
+ {
+ public static readonly AttachedProperty SkillMetaProperty =
+ AttachedProperty.Create("SkillMetaProperty", typeof(Control), defaultValue: new SkillControlMeta());
+
+ public ProtoId SkillId { get; }
+ public SkillLevel Level { get; }
+
+ public SkillControlMeta(ProtoId skillId, SkillLevel level)
+ {
+ SkillId = skillId;
+ Level = level;
+ }
+ private SkillControlMeta()
+ {
+
+ }
+ }
+}
diff --git a/Content.Client/AWS/Skills/SkillSystem.cs b/Content.Client/AWS/Skills/SkillSystem.cs
new file mode 100644
index 0000000000..da9c7aeb97
--- /dev/null
+++ b/Content.Client/AWS/Skills/SkillSystem.cs
@@ -0,0 +1,11 @@
+using Content.Shared.AWS.Skills;
+
+namespace Content.Client.AWS.Skills;
+
+public sealed class SkillSystem : SharedSkillSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ }
+}
diff --git a/Content.Client/AWS/Skills/SkillsBoundUserInterface.cs b/Content.Client/AWS/Skills/SkillsBoundUserInterface.cs
new file mode 100644
index 0000000000..c85d73ddb7
--- /dev/null
+++ b/Content.Client/AWS/Skills/SkillsBoundUserInterface.cs
@@ -0,0 +1,38 @@
+using Content.Shared.Ame.Components;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.AWS.Skills
+{
+ [UsedImplicitly]
+ public sealed class SkillsBoundUserInterface : BoundUserInterface
+ {
+ private SkillsWindow? _window;
+
+ public SkillsBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = this.CreateWindow();
+
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ var castState = (AmeControllerBoundUserInterfaceState) state;
+ _window?.UpdateState(castState); //Update window state
+ }
+
+ public void ButtonPressed(UiButton button)
+ {
+ SendMessage(new UiButtonPressedMessage(button));
+ }
+ }
+}
diff --git a/Content.Client/AWS/Skills/SkillsWindow.xaml b/Content.Client/AWS/Skills/SkillsWindow.xaml
new file mode 100644
index 0000000000..a9e6e6eb0c
--- /dev/null
+++ b/Content.Client/AWS/Skills/SkillsWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/AWS/Skills/SkillsWindow.xaml.cs b/Content.Client/AWS/Skills/SkillsWindow.xaml.cs
new file mode 100644
index 0000000000..9bbf6eb59c
--- /dev/null
+++ b/Content.Client/AWS/Skills/SkillsWindow.xaml.cs
@@ -0,0 +1,23 @@
+using System.Linq;
+using Content.Client.UserInterface;
+using Content.Shared.Ame.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.AWS.Skills
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class SkillsWindow : DefaultWindow
+ {
+ public SkillsWindow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ }
+
+ public void UpdateState(BoundUserInterfaceState state)
+ {
+ }
+ }
+}
diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
index c71d314606..3c4c6b7de3 100644
--- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
+++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
@@ -302,4 +302,4 @@
-
+
\ No newline at end of file
diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
index e0b0ffba06..3769b381ff 100644
--- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
@@ -2826,4 +2826,4 @@ private void UpdateCharacterRequired()
UpdateLoadouts(LoadoutsShowUnusableButton.Pressed);
}
}
-}
+}
\ No newline at end of file
diff --git a/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs b/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs
index 2449c08b73..1d5244305d 100644
--- a/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs
+++ b/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs
@@ -22,7 +22,7 @@ public MainMenuControl(IResourceCache resCache, IConfigurationManager configMan)
LayoutContainer.SetMarginTop(VBox, 30);
LayoutContainer.SetGrowHorizontal(VBox, LayoutContainer.GrowDirection.Begin);
- var logoTexture = resCache.GetResource("/Textures/_White/Logo/icon/icon-256x256.png"); // WD EDIT
+ var logoTexture = resCache.GetResource("/Textures/Logo/logo.png");
Logo.Texture = logoTexture;
var currentUserName = configMan.GetCVar(CVars.PlayerName);
diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
index 3a3cc3aaf4..1a31bc726d 100644
--- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
+++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs
@@ -119,6 +119,13 @@ public void Populate(List inventory)
}
var itemName = Identity.Name(dummy, _entityManager);
+ //SS14-RU
+ var adjustText = "";
+ if (entry.Price > 0)
+ {
+ adjustText = $" [{Loc.GetString("economybanksystem-vending-cost-label", ("amount", entry.Price),
+ ("currencyName", "Th"))}]";
+ }
const string labelCompName = "Label";
if (prototype.Components.TryGetValue(labelCompName, out var labelCompData)
@@ -129,7 +136,7 @@ public void Populate(List inventory)
itemName += $" ({Loc.GetString(itemLabel)})";
}
- var itemText = $"{itemName} [{entry.Amount}]";
+ var itemText = $"{itemName} [{entry.Amount}]{adjustText}";
if (itemText.Length > longestEntry.Length)
longestEntry = itemText;
@@ -144,7 +151,7 @@ public void Populate(List inventory)
private void SetSizeAfterUpdate(int longestEntryLength, int contentCount)
{
- SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400),
+ SetSize = new Vector2(Math.Clamp((longestEntryLength) * 12, 250, 500),
Math.Clamp(contentCount * 50, 150, 350));
}
}
diff --git a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
index 28b1b25ade..2a8b88d526 100644
--- a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
+++ b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
@@ -4,6 +4,7 @@
using Robust.Client.UserInterface;
using Robust.Shared.Input;
using System.Linq;
+using Content.Shared.Emag.Components;
namespace Content.Client.VendingMachines
{
@@ -15,8 +16,14 @@ public sealed class VendingMachineBoundUserInterface : BoundUserInterface
[ViewVariables]
private List _cachedInventory = new();
+ [ViewVariables]
+ private List _cachedFilteredIndex = new();
+
+ private VendingMachineSystem _vendingMachineSystem;
+
public VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
+ _vendingMachineSystem = EntMan.System();
}
protected override void Open()
@@ -54,7 +61,10 @@ private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
if (selectedItem == null)
return;
- SendMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
+ //SS14-RU
+ // SendMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
+ SendMessage(new VendingMachineSelectMessage(selectedItem.Type, selectedItem.ID));
+ //SS14-RU
}
protected override void Dispose(bool disposing)
@@ -70,5 +80,24 @@ protected override void Dispose(bool disposing)
_menu.OnClose -= Close;
_menu.Dispose();
}
+
+ //SS14-RU
+ private VendingMachineInventoryEntry? GetEntry(EntityUid uid, VendingMachineComponent component)
+ {
+ string selectedId = component.SelectedItemId!;
+ if (component.SelectedItemInventoryType == InventoryType.Emagged && EntMan.HasComponent(uid))
+ return component.EmaggedInventory.GetValueOrDefault(selectedId);
+
+ if (component.SelectedItemInventoryType == InventoryType.Contraband && component.Contraband)
+ return component.ContrabandInventory.GetValueOrDefault(selectedId);
+
+ return component.Inventory.GetValueOrDefault(selectedId);
+ }
+ //SS14-RU
+
+ private void OnSearchChanged(string? filter)
+ {
+ _menu?.Populate(_cachedInventory);
+ }
}
}
diff --git a/Content.Replay/Menu/ReplayMainMenuControl.xaml.cs b/Content.Replay/Menu/ReplayMainMenuControl.xaml.cs
index 0f82870047..8ab7f7412d 100644
--- a/Content.Replay/Menu/ReplayMainMenuControl.xaml.cs
+++ b/Content.Replay/Menu/ReplayMainMenuControl.xaml.cs
@@ -23,7 +23,7 @@ public ReplayMainMenuControl(IResourceCache resCache)
LayoutContainer.SetGrowHorizontal(VBox, LayoutContainer.GrowDirection.Begin);
Subtext.FontOverride = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 24);
- var logoTexture = resCache.GetResource("/Textures/_White/Logo/icon/icon-256x256.png"); // WD EDIT
+ var logoTexture = resCache.GetResource("/Textures/Logo/logo.png");
Logo.Texture = logoTexture;
LayoutContainer.SetAnchorPreset(InfoContainer, LayoutContainer.LayoutPreset.BottomLeft);
diff --git a/Content.Server/AWS/Economy/Bank/EconomyBankAccountSystem.cs b/Content.Server/AWS/Economy/Bank/EconomyBankAccountSystem.cs
new file mode 100644
index 0000000000..a0458c07fc
--- /dev/null
+++ b/Content.Server/AWS/Economy/Bank/EconomyBankAccountSystem.cs
@@ -0,0 +1,975 @@
+using Content.Shared.Interaction;
+using Content.Shared.VendingMachines;
+using Content.Server.VendingMachines;
+using Content.Shared.Popups;
+using Robust.Shared.Network;
+using Content.Shared.AWS.Economy;
+using Content.Server.Popups;
+using Content.Shared.Emag.Components;
+using Content.Shared.Emag.Systems;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
+using Content.Shared.Access.Components;
+using Content.Server.Access.Components;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Timing;
+using System.Diagnostics.CodeAnalysis;
+using Content.Server.Station.Systems;
+using Robust.Server.GameStates;
+using Content.Shared.Store;
+using Content.Shared.Access.Systems;
+using System.Linq;
+using Content.Shared.Roles;
+using Content.Shared.AWS.Economy.Bank;
+using System.Security.Principal;
+using Robust.Shared;
+using Content.Server.GameTicking;
+using System.Threading;
+using Content.Shared.Mind.Components;
+using Content.Shared.Mind;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.Toolshed.TypeParsers;
+using System;
+using System.Collections.ObjectModel;
+
+namespace Content.Server.AWS.Economy.Bank
+{
+ public sealed class EconomyBankAccountSystem : EconomyBankAccountSystemShared
+ {
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+ [Dependency] private readonly VendingMachineSystem _vendingMachine = default!;
+ [Dependency] private readonly INetManager _netManager = default!;
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly TransformSystem _transformSystem = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly StationSystem _stationSystem = default!;
+ [Dependency] private readonly PvsOverrideSystem _pvsOverrideSystem = default!;
+ [Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
+ [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
+
+ private const int SalaryDelay = 40 * 60 * 1000; // 40 minutes in milliseconds
+ private CancellationTokenSource _timerCancelToken = new();
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnAccountComponentInit);
+ SubscribeLocalEvent(OnBankComponentRemove);
+
+ SubscribeLocalEvent(OnTerminalInteracted);
+
+ SubscribeLocalEvent(OnATMEmagged);
+ SubscribeLocalEvent(OnATMInteracted);
+ SubscribeLocalEvent(OnATMWithdrawMessage);
+ SubscribeLocalEvent(OnATMTransferMessage);
+
+ SubscribeLocalEvent(OnManagementConsoleParameterMessage);
+ SubscribeLocalEvent(OnManagementConsoleChangeHolderIDMessage);
+ SubscribeLocalEvent(OnManagementConsoleInitAccountOnHolderMessage);
+ SubscribeLocalEvent(OnManagementConsolePayBonusMessage);
+
+ SubscribeLocalEvent(OnStartRound);
+ SubscribeLocalEvent(OnEndRound);
+ }
+
+ #region Account management
+ ///
+ /// Creates a new bank account entity. If account already exists - fetch it instead, but still return false.
+ ///
+ /// Whether the account was successfully created.
+ [PublicAPI]
+ public bool TryCreateAccount(string accountID,
+ string accountName,
+ ProtoId allowedCurrency,
+ ulong balance,
+ ulong penalty,
+ bool blocked,
+ bool canReachPayDay,
+ List? accountTags,
+ ProtoId? jobName,
+ ulong? salary,
+ MapCoordinates? cords,
+ out Entity account)
+ {
+ // Return if account with this id already exists.
+ if (TryGetAccount(accountID, out var foundAccount))
+ {
+ account = foundAccount.Value;
+ return false;
+ }
+
+ var spawnCords = cords ?? MapCoordinates.Nullspace;
+ var accountEntity = Spawn(null, spawnCords);
+ _metaDataSystem.SetEntityName(accountEntity, accountID);
+ var accountComp = EnsureComp(accountEntity);
+
+ accountComp.AccountID = accountID;
+ accountComp.AccountName = accountName;
+ accountComp.AllowedCurrency = allowedCurrency;
+ accountComp.Balance = balance;
+ accountComp.Penalty = penalty;
+ accountComp.Blocked = blocked;
+ accountComp.CanReachPayDay = canReachPayDay;
+ accountComp.AccountTags = accountTags ?? [];
+ accountComp.JobName = jobName;
+ accountComp.Salary = salary;
+
+ account = (accountEntity, accountComp);
+ _pvsOverrideSystem.AddGlobalOverride(accountEntity);
+ Dirty(account);
+ return true;
+ }
+
+ ///
+ /// Enables a card or a bank account (described in setup) for usage.
+ ///
+ [PublicAPI]
+ public bool TryActivate(Entity entity, [NotNullWhen(true)] out Entity? activatedAccount)
+ {
+ activatedAccount = null;
+ if (!_prototypeManager.TryIndex(entity.Comp.AccountIdByProto, out EconomyAccountIdPrototype? proto))
+ return false;
+
+ // Setup standard starting values for account details
+ var accountID = GenerateAccountId(proto.Prefix, proto.Streak, proto.NumbersPerStreak, proto.Descriptior);
+ var accountName = entity.Comp.AccountName;
+ var balance = (ulong)0;
+ ProtoId? jobName = null;
+ ulong? salary = null;
+
+ if (TryComp(entity, out var idCardComponent))
+ accountName = idCardComponent.FullName ?? entity.Comp.AccountName;
+
+ if (TryComp(entity, out var presetIdCardComponent) &&
+ presetIdCardComponent.JobName is { } job &&
+ TryGetSalaryJobEntry(job, "NanotrasenDefaultSallaries", out var jobEntry))
+ {
+ jobName = job;
+ salary = jobEntry.Value.Sallary;
+ balance = (ulong)(jobEntry.Value.StartMoney * _random.NextDouble(0.5, 1.5));
+ }
+
+ var station = _stationSystem.GetOwningStation(entity);
+ var cords = station != null ? _transformSystem.GetMapCoordinates(station.Value) : MapCoordinates.Nullspace;
+
+ // Setup values are always coming first if they can
+ var accountSetup = entity.Comp.AccountSetup;
+ accountID = accountSetup.GenerateAccountID || accountSetup.AccountID is null ? accountID :
+ accountSetup.AccountID;
+ accountName = accountSetup.AccountName ?? accountName;
+ balance = accountSetup.Balance ?? balance;
+
+ TryCreateAccount(accountID,
+ accountName,
+ accountSetup.AllowedCurrency ?? "Thaler",
+ balance,
+ accountSetup.Penalty ?? 0,
+ accountSetup.Blocked ?? false,
+ accountSetup.CanReachPayDay ?? true,
+ accountSetup.AccountTags ?? [],
+ jobName,
+ salary,
+ cords,
+ out var account);
+
+ activatedAccount = account;
+ entity.Comp.AccountID = accountID;
+ entity.Comp.AccountName = accountName;
+ Dirty(entity);
+ return true;
+ }
+
+ ///
+ /// Tries to set the parameter chosen in arguments to a given value.
+ ///
+ /// Parameter to be changed.
+ /// New value of the parameter, must be of the same type as a changed value.
+ [PublicAPI]
+ public bool TrySetAccountParameter(string accountID, EconomyBankAccountParam param, object value)
+ {
+ if (!TryGetAccount(accountID, out var entity))
+ return false;
+
+ if (accountID == "NT-CentCom")
+ return false;
+
+ var account = entity.Value.Comp;
+ switch (param)
+ {
+ case EconomyBankAccountParam.AccountName:
+ if (value is not string name)
+ return false;
+ account.AccountName = name;
+ break;
+ case EconomyBankAccountParam.Blocked:
+ if (value is not bool blocked)
+ return false;
+ account.Blocked = blocked;
+ break;
+ case EconomyBankAccountParam.CanReachPayDay:
+ if (value is not bool canReachPayDay)
+ return false;
+ account.CanReachPayDay = canReachPayDay;
+ break;
+ case EconomyBankAccountParam.JobName:
+ if (value is not string jobName)
+ return false;
+ account.JobName = jobName;
+ break;
+ case EconomyBankAccountParam.Salary:
+ if (value is not ulong salary)
+ return false;
+ account.Salary = salary;
+ break;
+ default:
+ return false;
+ }
+
+ Dirty(entity.Value);
+ return true;
+ }
+
+ [PublicAPI]
+ public bool TryWithdraw(EconomyAccountHolderComponent component, EconomyBankATMComponent atm, ulong sum, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+ if (!TryGetAccount(component.AccountID, out var account))
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-notfoundaccout");
+ return false;
+ }
+
+ if (account.Value.Comp.Blocked)
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-account-blocked", ("accountId", account.Value.Comp.AccountID));
+ return false;
+ }
+
+ if (sum > 0 && account.Value.Comp.Balance >= sum)
+ {
+ Withdraw(component, atm, sum);
+ return true;
+ }
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-notenoughmoney");
+ return false;
+ }
+
+ [Obsolete("should be replaced by giving money holder from inventory")]
+ private Entity SpawnMoneyHolderAtPos(EntProtoId entId, MapCoordinates pos)
+ {
+ var ent = Spawn(entId, pos);
+ var moneyHolderComp = Comp(ent);
+
+ return (ent, moneyHolderComp);
+ }
+
+ [PublicAPI]
+ public Entity DropMoneyHolder(EntProtoId entId, ulong amount, MapCoordinates pos)
+ {
+ var ent = SpawnMoneyHolderAtPos(entId, pos);
+
+ ent.Comp.Balance = amount;
+
+ Dirty(ent);
+ return ent;
+ }
+
+ [PublicAPI]
+ public bool TrySendMoney(EconomyMoneyHolderComponent fromHolder, Entity recipientAccount, ulong amount, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+
+ if (recipientAccount.Comp.Blocked)
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-account-blocked", ("accountId", recipientAccount.Comp.AccountID));
+ return false;
+ }
+
+ // if this is a fake money holder
+ if (fromHolder.Emagged)
+ {
+ TryAddLog(recipientAccount,
+ new EconomyBankAccountLogField(_gameTiming.CurTime,
+ Loc.GetString("economybanksystem-log-terminal-error")));
+
+ return true;
+ }
+
+ return TryTransferMoney(fromHolder, recipientAccount, amount);
+ }
+
+ [PublicAPI]
+ public bool TrySendMoney(EconomyMoneyHolderComponent fromHolder, Entity recipientAccountHolder, ulong amount, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+
+ if (fromHolder.Balance < amount)
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-notenoughmoney");
+ return false;
+ }
+
+ return TrySendMoney(fromHolder, recipientAccountHolder, amount, out errorMessage);
+ }
+
+ [PublicAPI]
+ public bool TrySendMoney(EconomyMoneyHolderComponent fromHolder, string recipientAccountId, ulong amount, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+
+ if (!TryGetAccount(recipientAccountId, out var account))
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-notfoundaccout", ("accountId", recipientAccountId));
+ return false;
+ }
+
+ return TrySendMoney(fromHolder, account.Value, amount, out errorMessage);
+ }
+
+ [PublicAPI]
+ public bool TrySendMoney(Entity fromAccount, Entity recipientAccount, ulong amount, string? reason, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+
+ if (fromAccount.Comp.Blocked || recipientAccount.Comp.Blocked)
+ {
+ var blockedAccountID = fromAccount.Comp.Blocked ? fromAccount.Comp.AccountID : recipientAccount.Comp.AccountID;
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-account-blocked", ("accountId", blockedAccountID));
+ return false;
+ }
+
+ if (fromAccount.Comp.Balance < amount)
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-notenoughmoney");
+ return false;
+ }
+
+ return TryTransferMoney(fromAccount, recipientAccount, amount, reason);
+ }
+
+ [PublicAPI]
+ public bool TrySendMoney(EconomyAccountHolderComponent fromAccountHolder, EconomyAccountHolderComponent recipientAccountHolder, ulong amount, string? reason, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+
+ return TrySendMoney(fromAccountHolder.AccountID, recipientAccountHolder.AccountID, amount, reason, out errorMessage);
+ }
+
+ [PublicAPI]
+ public bool TrySendMoney(EconomyAccountHolderComponent fromAccountHolder, string recipientAccountId, ulong amount, string? reason, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+
+ return TrySendMoney(fromAccountHolder.AccountID, recipientAccountId, amount, reason, out errorMessage);
+ }
+
+ private (Entity?, Entity?) GetAccountsById(string id, string id2)
+ {
+ TryGetAccount(id, out var firstAccount);
+ TryGetAccount(id2, out var secondAccount);
+
+ return (firstAccount, secondAccount);
+ }
+
+ [PublicAPI]
+ public bool TrySendMoney(string fromAccountId, string recipientAccountId, ulong amount, string? reason, [NotNullWhen(false)] out string? errorMessage)
+ {
+ errorMessage = null;
+
+ var (fromBankAccount, recipientAccount) = GetAccountsById(fromAccountId, recipientAccountId);
+
+ if (fromBankAccount is null)
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-notfoundaccout", ("accountId", fromAccountId));
+ return false;
+ }
+
+ if (recipientAccount is null)
+ {
+ errorMessage = Loc.GetString("economybanksystem-transaction-error-notfoundaccout", ("accountId", recipientAccountId));
+ return false;
+ }
+
+ return TrySendMoney(fromBankAccount.Value, recipientAccount.Value, amount, reason, out errorMessage);
+ }
+
+ ///
+ /// Adds a new log to the account.
+ ///
+ [PublicAPI]
+ public bool TryAddLog(string accountID, EconomyBankAccountLogField log)
+ {
+ if (!TryGetAccount(accountID, out var account))
+ return false;
+
+ return TryAddLog(account.Value, log);
+ }
+
+ ///
+ /// Adds a new log to the account.
+ ///
+ [PublicAPI]
+ public bool TryAddLog(Entity account, EconomyBankAccountLogField log)
+ {
+ account.Comp.Logs.Add(log);
+ Dirty(account);
+
+ return true;
+ }
+
+ ///
+ /// Changes the balance of the account.
+ ///
+ /// Whether to add or substract the given amount.
+ private bool TryChangeAccountBalance(string accountID, ulong amount, bool addition = true)
+ {
+ if (!TryGetAccount(accountID, out var entity))
+ return false;
+
+ var account = entity.Value.Comp;
+ if (!addition)
+ {
+ if (account.Balance - amount < 0)
+ return false;
+
+ account.Balance -= amount;
+ return true;
+ }
+
+ account.Balance += amount;
+
+ Dirty(entity.Value);
+ return true;
+ }
+
+ ///
+ /// Transfer money from one account to another (with logs).
+ ///
+ /// True if the transfer was successful, false otherwise.
+ private bool TryTransferMoney(string senderID, string receiverID, ulong amount, string? reason = null)
+ {
+ if (!TryGetAccount(senderID, out var senderEntity) ||
+ !TryGetAccount(receiverID, out var receiverEntity))
+ return false;
+
+ return TryTransferMoney(senderEntity.Value, receiverEntity.Value, amount, reason);
+ }
+
+ private bool TryTransferMoney(EconomyMoneyHolderComponent moneyHolder, Entity receiverEntity, ulong amount, string? reason = null)
+ {
+ if (amount <= 0)
+ return false;
+
+ var receiver = receiverEntity.Comp;
+
+ if (moneyHolder.Balance < amount)
+ return false;
+
+ moneyHolder.Balance -= amount;
+ receiver.Balance += amount;
+
+ var receiverLog = Loc.GetString("economybanksystem-log-send-from",
+ ("amount", amount), ("currencyName", receiver.AllowedCurrency), ("accountId", "thaler holder"));
+ if (reason != null)
+ receiverLog += $" {reason}";
+
+ // what the fuck
+ var moneyHolderEnt = _entManager.AllEntities().Single(x => x.Comp == moneyHolder);
+
+ receiver.Logs.Add(new(_gameTiming.CurTime, receiverLog));
+
+ Dirty(moneyHolderEnt);
+ Dirty(receiverEntity);
+ return true;
+ }
+
+ private bool TryTransferMoney(Entity senderEntity, Entity receiverEntity, ulong amount, string? reason = null)
+ {
+ if (amount <= 0)
+ return false;
+
+ var sender = senderEntity.Comp;
+ var receiver = receiverEntity.Comp;
+ if (sender.Balance < amount)
+ return false;
+
+ sender.Balance -= amount;
+ receiver.Balance += amount;
+
+ var senderLog = Loc.GetString("economybanksystem-log-send-to",
+ ("amount", amount), ("currencyName", receiver.AllowedCurrency), ("accountId", receiver.AccountID));
+ var receiverLog = Loc.GetString("economybanksystem-log-send-from",
+ ("amount", amount), ("currencyName", receiver.AllowedCurrency), ("accountId", sender.AccountID));
+ if (reason != null)
+ {
+ senderLog += $" {reason}";
+ receiverLog += $" {reason}";
+ }
+ sender.Logs.Add(new(_gameTiming.CurTime, senderLog));
+ receiver.Logs.Add(new(_gameTiming.CurTime, receiverLog));
+
+ Dirty(senderEntity);
+ Dirty(receiverEntity);
+ return true;
+ }
+
+ private void Withdraw(EconomyAccountHolderComponent component, EconomyBankATMComponent atm, ulong sum)
+ {
+ if (!TryChangeAccountBalance(component.AccountID, sum, false))
+ return;
+
+ var pos = _transformSystem.GetMapCoordinates(atm.Owner);
+ DropMoneyHolder(component.MoneyHolderEntId, sum, pos);
+
+ if (TryGetAccount(component.AccountID, out var account))
+ {
+ var log = new EconomyBankAccountLogField(_gameTiming.CurTime, Loc.GetString("economybanksystem-log-withdraw",
+ ("amount", sum), ("currencyName", account.Value.Comp.AllowedCurrency)));
+ account.Value.Comp.Logs.Add(log);
+ Dirty(account.Value);
+ }
+
+ _entManager.Dirty(component);
+ }
+
+ private void Withdraw(string accountID, EntityUid ent, ulong sum)
+ {
+ if (!TryChangeAccountBalance(accountID, sum, false))
+ return;
+
+ var pos = _transformSystem.GetMapCoordinates(ent);
+ DropMoneyHolder("ThalerHolder", sum, pos); // hardcoded for now
+
+ if (TryGetAccount(accountID, out var account))
+ {
+ var log = new EconomyBankAccountLogField(_gameTiming.CurTime, Loc.GetString("economybanksystem-log-withdraw",
+ ("amount", sum), ("currencyName", account.Value.Comp.AllowedCurrency)));
+ account.Value.Comp.Logs.Add(log);
+ Dirty(account.Value);
+ }
+ }
+ #endregion
+
+ private string GenerateAccountId(string prefix, uint strik, uint numbersPerStrik, string? descriptor)
+ {
+ var res = prefix;
+
+ for (int i = 0; i < strik; i++)
+ {
+ string formedStrik = "";
+
+ for (int num = 0; num < numbersPerStrik; num++)
+ {
+ formedStrik += _random.Next(0, 10);
+ }
+
+ res = res.Length == 0 ? formedStrik : res + descriptor + formedStrik;
+ }
+
+ return res;
+ }
+
+ private void OnAccountComponentInit(Entity entity, ref ComponentInit args)
+ {
+ // if has id card comp, then it will be initialized in the other place.
+ if (entity.Comp.AccountSetup is null || HasComp(entity))
+ return;
+
+ TryActivate(entity, out _);
+ }
+
+ private void OnBankComponentRemove(Entity entity, ref ComponentRemove args)
+ {
+ _pvsOverrideSystem.RemoveGlobalOverride(entity);
+ }
+
+ private void OnATMWithdrawMessage(EntityUid uid, EconomyBankATMComponent atm, EconomyBankATMWithdrawMessage args)
+ {
+ if (!TryGetATMInsertedAccount(atm, out var bankAccount))
+ return;
+
+ string? error;
+
+ TryWithdraw(bankAccount, atm, args.Amount, out error);
+ UpdateATMUserInterface((uid, atm), error);
+ }
+
+ private void OnATMTransferMessage(EntityUid uid, EconomyBankATMComponent atm, EconomyBankATMTransferMessage args)
+ {
+ if (!TryGetATMInsertedAccount(atm, out var bankAccount))
+ return;
+
+ string? error;
+
+ TrySendMoney(bankAccount, args.RecipientAccountId, args.Amount, null, out error);
+ UpdateATMUserInterface((uid, atm), error);
+ }
+
+ private void OnATMEmagged(EntityUid uid, EconomyBankATMComponent component, ref GotEmaggedEvent args)
+ {
+ if (HasComp(uid) || args.Handled)
+ return;
+
+ var listMoney = component.EmagDropMoneyValues;
+ var listMoneyCount = listMoney.Count;
+
+ if (listMoneyCount == 0)
+ return;
+
+ if (component.EmagDropMoneyHolderRandomCount == 0)
+ return;
+
+ var moneyHolderCount = _random.Next(1, component.EmagDropMoneyHolderRandomCount + 1);
+ var mapPos = _transformSystem.GetMapCoordinates(uid);
+
+ for (int i = 0; i < moneyHolderCount; i++)
+ {
+ var droppedEnt = DropMoneyHolder(component.MoneyHolderEntId,
+ listMoney[_random.Next(0, listMoneyCount)], mapPos);
+ droppedEnt.Comp.Emagged = true;
+ }
+
+ _audioSystem.PlayPvs(component.EmagSound, uid);
+ args.Handled = true;
+ }
+
+ private void OnATMInteracted(EntityUid uid, EconomyBankATMComponent component, InteractUsingEvent args)
+ {
+ var usedEnt = args.Used;
+
+ if (!TryComp(usedEnt, out var economyMoneyHolderComponent))
+ return;
+
+ var amount = economyMoneyHolderComponent.Balance;
+ if (TryGetATMInsertedAccount(component, out var insertedAccountHolder))
+ {
+ if (TrySendMoney(economyMoneyHolderComponent, insertedAccountHolder.Value, amount, out var error))
+ {
+ if (insertedAccountHolder is not null && TryGetAccount(insertedAccountHolder.Value.Comp.AccountID, out var account))
+ TryAddLog(account.Value,
+ new EconomyBankAccountLogField(_gameTiming.CurTime,
+ Loc.GetString("economybanksystem-log-insert",
+ ("amount", amount), ("currencyName", account.Value.Comp.AllowedCurrency))));
+
+ if (_netManager.IsServer)
+ _popupSystem.PopupEntity(Loc.GetString("economybanksystem-atm-moneyentering"), uid, type: PopupType.Medium);
+
+ QueueDel(usedEnt);
+ }
+ if (_netManager.IsServer)
+ _popupSystem.PopupEntity(error, uid, type: PopupType.Medium);
+
+ UpdateATMUserInterface((uid, component), error);
+ }
+ }
+
+ private void OnTerminalInteracted(EntityUid uid, EconomyBankTerminalComponent component, InteractUsingEvent args)
+ {
+ var amount = component.Amount;
+ var usedEnt = args.Used;
+
+ if (amount <= 0)
+ return;
+
+ if (!TryComp(usedEnt, out var economyMoneyHolderComponent) &
+ !TryComp(usedEnt, out var economyBankAccountHolderComponent))
+ return;
+
+ if (!TryGetAccount(component.LinkedAccount, out var receiverAccount))
+ {
+ var error = Loc.GetString("economyBankTerminal-component-vending-error-no-account");
+ _popupSystem.PopupEntity(error, uid, type: PopupType.MediumCaution);
+ return;
+ }
+
+ if (economyMoneyHolderComponent is not null)
+ {
+ if (!TrySendMoney(economyMoneyHolderComponent, component.LinkedAccount, amount, out var err))
+ {
+ _popupSystem.PopupEntity(err, uid, type: PopupType.MediumCaution);
+ return;
+ }
+ }
+ else if (economyBankAccountHolderComponent is not null)
+ {
+ if (!TrySendMoney(economyBankAccountHolderComponent, component.LinkedAccount, amount, null, out var err))
+ {
+ _popupSystem.PopupEntity(err, uid, type: PopupType.MediumCaution);
+ return;
+ }
+ }
+
+ UpdateTerminal((uid, component), 0, string.Empty);
+
+ // Cancel the payment if the terminal is vending machine and the further operations were not successful.
+ if (TryComp(uid, out var vendingMachineComponent))
+ {
+ string? itemName;
+ if (!TryTransactionFromVendingMachine(uid, args.User, vendingMachineComponent, out itemName))
+ {
+ Withdraw(receiverAccount.Value.Comp.AccountID, uid, amount);
+ var error = Loc.GetString("economyBankTerminal-component-vending-error");
+ _popupSystem.PopupEntity(error, uid, type: PopupType.MediumCaution);
+ return;
+ }
+ else
+ {
+ _prototypeManager.TryIndex(itemName, out var proto);
+
+ if (proto is not null)
+ TryAddLog(component.LinkedAccount,
+ new EconomyBankAccountLogField(_gameTiming.CurTime,
+ Loc.GetString("economybanksystem-log-vending-buying",
+ ("itemName", proto.Name))));
+ }
+ }
+
+ _popupSystem.PopupEntity(Loc.GetString("economybanksystem-transaction-success", ("amount", amount), ("currencyName", receiverAccount.Value.Comp.AllowedCurrency)), uid, type: PopupType.Medium);
+ }
+
+ private bool TryTransactionFromVendingMachine(EntityUid uid, EntityUid user, VendingMachineComponent vendingMachine, [NotNullWhen(true)] out string? itemName)
+ {
+ itemName = null;
+ if (vendingMachine.SelectedItemId is not { } selectedItemID)
+ return false;
+
+ if (!vendingMachine.Inventory.TryGetValue(selectedItemID, out var entry) || entry.Price <= 0)
+ return false;
+
+ itemName = vendingMachine.SelectedItemId;
+ vendingMachine.SelectedItemId = null;
+ _vendingMachine.AuthorizedVend(uid, user, vendingMachine.SelectedItemInventoryType, selectedItemID, vendingMachine);
+ return !vendingMachine.Denying;
+ }
+
+ private void OnManagementConsoleChangeHolderIDMessage(Entity ent, ref EconomyManagementConsoleChangeHolderIDMessage args)
+ {
+ if (!TryComp(ent, out var accessReader) || ent.Comp.CardSlot.Item is not { } idCard)
+ return;
+
+ // Check for privileges
+ if (!_accessReaderSystem.IsAllowed(idCard, ent.Owner, accessReader))
+ return;
+
+ var holder = GetEntity(args.AccountHolder);
+ if (!TryComp(holder, out var holderComp))
+ return;
+
+ // Change the holder ID
+ if (!TryGetAccount(args.NewID, out var account))
+ return;
+
+ holderComp.AccountID = account.Value.Comp.AccountID;
+ holderComp.AccountName = account.Value.Comp.AccountName;
+ Dirty(holder, holderComp);
+ UpdateManagementConsoleUserInterface(ent, account.Value.Comp);
+ }
+
+ private void OnManagementConsoleInitAccountOnHolderMessage(Entity ent, ref EconomyManagementConsoleInitAccountOnHolderMessage args)
+ {
+ if (!TryComp(ent, out var accessReader) || ent.Comp.CardSlot.Item is not { } idCard)
+ return;
+
+ // Check for privileges
+ if (!_accessReaderSystem.IsAllowed(idCard, ent.Owner, accessReader))
+ return;
+
+ // Initialize account on holder
+ var holder = GetEntity(args.AccountHolder);
+
+ if (!TryComp(holder, out var holderComp) || !TryActivate((holder, holderComp), out var account))
+ return;
+
+ holderComp.AccountID = account.Value.Comp.AccountID;
+ holderComp.AccountName = account.Value.Comp.AccountName;
+ Dirty(holder, holderComp);
+ UpdateManagementConsoleUserInterface(ent, account.Value.Comp);
+ }
+
+ private void OnManagementConsoleParameterMessage(Entity ent, ref EconomyManagementConsoleChangeParameterMessage args)
+ {
+ if (!TryComp(ent, out var accessReader) || ent.Comp.CardSlot.Item is not { } idCard)
+ return;
+
+ // Check for priveleges
+ if (!_accessReaderSystem.IsAllowed(idCard, ent.Owner, accessReader))
+ return;
+
+ if (!TryGetAccount(args.AccountID, out var account))
+ return;
+
+ TrySetAccountParameter(args.AccountID, args.Param, args.Value);
+ UpdateManagementConsoleUserInterface(ent, account.Value.Comp);
+ }
+
+ private void OnManagementConsolePayBonusMessage(Entity ent, ref EconomyManagementConsolePayBonusMessage args)
+ {
+ // Check for priveleges
+ if (!TryComp(ent, out var accessReader) || ent.Comp.CardSlot.Item is not { } idCard)
+ return;
+
+ if (!_accessReaderSystem.IsAllowed(idCard, ent.Owner, accessReader))
+ return;
+
+ // Validate accounts and operation itself
+ if (!TryGetAccount(args.Payer, out var payerAccount))
+ return;
+
+ var accounts = GetAccounts(EconomyBankAccountMask.ByTags, new List { BankAccountTag.Personal });
+ var accountList = args.Accounts;
+ var intersectedAccounts = accounts.Where(account => accountList.Contains(account.Value.Comp.AccountID)).GetEnumerator();
+
+ Dictionary, ulong> accountsToPay = new();
+ ulong total = 0;
+ while (intersectedAccounts.MoveNext())
+ {
+ var account = intersectedAccounts.Current.Value.Comp;
+ if (account.Salary is null)
+ continue;
+
+ var bonus = (ulong)(account.Salary * args.BonusPercent);
+ total += bonus;
+ accountsToPay.Add(intersectedAccounts.Current.Value, bonus);
+ }
+
+ if (total > payerAccount.Value.Comp.Balance)
+ return;
+
+ // Proceed to payment
+ var reason = Loc.GetString("economybanksystem-log-reason-bonus");
+ foreach (var kvp in accountsToPay)
+ {
+ var account = kvp.Key.Comp;
+ var bonus = kvp.Value;
+
+ TrySendMoney(payerAccount.Value.Comp.AccountID, account.AccountID, bonus, reason, out _);
+ }
+
+ UpdateManagementConsoleUserInterface(ent, null);
+ }
+
+ [PublicAPI]
+ private EconomySallaryInfo? PaySalaries(ProtoId salaryProto,
+ EconomyPayDayRuleType type = EconomyPayDayRuleType.Adding)
+ {
+ if (!_prototypeManager.TryIndex(salaryProto, out var sallariesProto))
+ return null;
+
+ if (!TryGetAccount(sallariesProto.PayerAccountId, out var payerAccount))
+ return null;
+
+ var accounts = GetAccounts();
+ var enumerator = AllEntityQuery();
+
+ ulong decremedSum = 0;
+ ulong payedSum = 0;
+
+ List affectedAccounts = new();
+ List unableProccess = new();
+ List blockedAccounts = new();
+
+ foreach (var (_, accountEntity) in accounts)
+ {
+ var account = accountEntity.Comp;
+
+ if (account.Blocked || !account.CanReachPayDay)
+ {
+ unableProccess.Add(account);
+ continue;
+ }
+
+ EconomySallariesJobEntry? entry = null;
+
+ foreach (var item in sallariesProto.Jobs)
+ {
+ if (item.Key.Id == accountEntity.Comp.JobName)
+ entry = item.Value;
+ }
+
+ if (entry is null)
+ {
+ unableProccess.Add(account);
+ continue;
+ }
+
+ ulong sallary = (ulong) sallariesProto.Coef.Next(_random) * entry.Value.Sallary / 100;
+ string? err;
+ var reason = Loc.GetString("economybanksystem-log-reason-payday");
+
+ switch (type)
+ {
+ case EconomyPayDayRuleType.Adding:
+ if (TrySendMoney(payerAccount.Value.Comp.AccountID, account.AccountID, sallary, reason, out err))
+ {
+ affectedAccounts.Add(account);
+ payedSum += sallary;
+ }
+ break;
+ case EconomyPayDayRuleType.Decrementing:
+ if (!TrySendMoney(account.AccountID, payerAccount.Value.Comp.AccountID, sallary, reason, out err))
+ {
+ if (TrySetAccountParameter(account.AccountID, EconomyBankAccountParam.Blocked, true))
+ blockedAccounts.Add(accountEntity);
+
+ unableProccess.Add(account);
+ break;
+ }
+
+ decremedSum += sallary;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return new(payedSum, decremedSum, affectedAccounts, unableProccess, blockedAccounts);
+
+ //notify that we blocked, or we cant proccess any payment
+ }
+
+ private void OnStartRound(RoundStartedEvent args)
+ {
+ _timerCancelToken.TryReset();
+
+ var action = delegate ()
+ {
+ if (_prototypeManager.TryGetInstances(out var prototypes))
+ foreach (var (index, proto) in prototypes)
+ {
+ Log.Debug($"Start processing with paying salaries for {index}");
+
+ var sallaryInfo = PaySalaries(proto, EconomyPayDayRuleType.Adding);
+ if (sallaryInfo is not null)
+ Log.Debug(
+ $"\nAddedSum sum is: {sallaryInfo.AddedSum}\n" +
+ $"DecremedSum is: {sallaryInfo.DecremedSum}\n" +
+ $"AffectedAccounts: {string.Join(',', sallaryInfo.AffectedAccounts.Select(x => x.AccountID))}\n" +
+ $"UnableProccess: {string.Join(',', sallaryInfo.UnableProccess.Select(x => x.AccountID))}\n" +
+ $"WereBlockedInProccess: {string.Join(',', sallaryInfo.WereBlockedInProccess.Select(x => x.AccountID))}\n");
+ else
+ Log.Debug("Unable to proccess sallaries, nothing were added");
+
+ RaiseLocalEvent(new());
+ }
+ }; // should be rewrote
+
+ Robust.Shared.Timing.Timer.SpawnRepeating(1500, action, _timerCancelToken.Token);
+ }
+
+ private void OnEndRound(RoundEndedEvent args)
+ {
+ _timerCancelToken.Cancel();
+ }
+
+ private record EconomySallaryInfo(
+ ulong AddedSum,
+ ulong DecremedSum,
+ List AffectedAccounts,
+ List UnableProccess,
+ List WereBlockedInProccess);
+ }
+}
diff --git a/Content.Server/AWS/Economy/Bank/EconomyPayDayRule.cs b/Content.Server/AWS/Economy/Bank/EconomyPayDayRule.cs
new file mode 100644
index 0000000000..919f935e7a
--- /dev/null
+++ b/Content.Server/AWS/Economy/Bank/EconomyPayDayRule.cs
@@ -0,0 +1,92 @@
+using Content.Shared.GameTicking.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Content.Shared.AWS.Economy;
+using Content.Shared.Mind;
+using Content.Shared.Roles;
+using Content.Shared.Mind.Components;
+using Content.Shared.Roles.Jobs;
+using Content.Server.AWS.Economy;
+using Content.Server.StationEvents.Events;
+using Content.Shared.AWS.Economy.Bank;
+
+namespace Content.Server.AWS.Economy.Bank;
+
+public sealed class EconomyPayDayRule : StationEventSystem
+{
+ [Dependency] private readonly IEntityManager _entMan = default!;
+ [Dependency] private readonly EconomyBankAccountSystem _bankAccountSystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Started(EntityUid uid, EconomyPayDayRuleComponent ruleComponent, GameRuleComponent gameRule, GameRuleStartedEvent args)
+ {
+ var accounts = _bankAccountSystem.GetAccounts();
+ if (!_bankAccountSystem.TryGetAccount(ruleComponent.PayerAccountId, out var payerAccount))
+ return;
+
+ if (!_prototype.TryIndex(ruleComponent.SallaryProto, out var sallariesProto))
+ return;
+
+ var enumerator = _entMan.AllEntityQueryEnumerator();
+ Dictionary> manifest = new();
+ while(enumerator.MoveNext(out var ent, out var mindContainerComponent))
+ {
+ if (TryComp(mindContainerComponent.Mind, out var mindComponent)
+ && TryComp(mindContainerComponent.Mind, out var jobComponent)
+ && mindComponent.CharacterName is not null
+ && jobComponent.Prototype is not null)
+ {
+ manifest.Add(mindComponent.CharacterName, jobComponent.Prototype.Value);
+ }
+ }
+ List> blockedAccounts = new();
+
+ foreach (var (_, accountEntity) in accounts)
+ {
+ var account = accountEntity.Comp;
+
+ if (account.Blocked || !account.CanReachPayDay)
+ continue;
+
+ if (account.Blocked)
+ continue;
+
+ if (!manifest.TryGetValue(account.AccountName, out var job))
+ continue;
+
+ EconomySallariesJobEntry? entry = null;
+
+ foreach (var item in sallariesProto.Jobs)
+ {
+ if (item.Key.Id == job.Id)
+ entry = item.Value;
+ }
+
+ if (entry is null)
+ continue;
+
+ ulong sallary = ((ulong)ruleComponent.Coef.Next(_random))/100* entry.Value.Sallary;
+ string? err;
+ var reason = Loc.GetString("economybanksystem-log-reason-payday");
+
+ switch (ruleComponent.PayType)
+ {
+ case EconomyPayDayRuleType.Adding:
+ _bankAccountSystem.TrySendMoney(payerAccount.Value.Comp.AccountID, account.AccountID, sallary, reason, out err);
+ break;
+ case EconomyPayDayRuleType.Decrementing:
+ if (!_bankAccountSystem.TrySendMoney(account.AccountID, payerAccount.Value.Comp.AccountID, sallary, reason, out err))
+ {
+ if (_bankAccountSystem.TrySetAccountParameter(account.AccountID, EconomyBankAccountParam.Blocked, true))
+ blockedAccounts.Add(accountEntity);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ //notify that we blocked, or we cant proccess any payment
+ }
+}
diff --git a/Content.Server/AWS/Economy/Bank/EconomySallaryPostEvent.cs b/Content.Server/AWS/Economy/Bank/EconomySallaryPostEvent.cs
new file mode 100644
index 0000000000..529f7256c7
--- /dev/null
+++ b/Content.Server/AWS/Economy/Bank/EconomySallaryPostEvent.cs
@@ -0,0 +1 @@
+public record EconomySallaryPostEvent;
diff --git a/Content.Server/AWS/Economy/CriminalAntag/StealMoneyConditionComponent.cs b/Content.Server/AWS/Economy/CriminalAntag/StealMoneyConditionComponent.cs
new file mode 100644
index 0000000000..e65ec8e3b6
--- /dev/null
+++ b/Content.Server/AWS/Economy/CriminalAntag/StealMoneyConditionComponent.cs
@@ -0,0 +1,32 @@
+using Content.Shared.Store;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Server.AWS.CriminalAntag;
+
+[RegisterComponent]
+public sealed partial class StealMoneyConditionComponent : Component
+{
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public Enum ReachType { get; set; } = StealMoneyReachType.AsPossible;
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId Currency { get; set; } = "Thaler";
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ulong SpecificMoneyCount { get; set; } = 0;
+
+ [DataField, ViewVariables]
+ public uint MaxOthers { get; set; } = 3;
+
+ [ViewVariables]
+ public List Others { get; set; } = default!;
+}
+
+public enum StealMoneyReachType : byte
+{
+ DependsOnOthers, // When we can give the player list to other players and he should get money more than them
+ SingleSpecificReach, // When we indicate specific count to reach
+ AsPossible // He should reach much as possible money
+}
\ No newline at end of file
diff --git a/Content.Server/AWS/Economy/CriminalAntag/StealMoneyConditionSystem.cs b/Content.Server/AWS/Economy/CriminalAntag/StealMoneyConditionSystem.cs
new file mode 100644
index 0000000000..9d2bc6178d
--- /dev/null
+++ b/Content.Server/AWS/Economy/CriminalAntag/StealMoneyConditionSystem.cs
@@ -0,0 +1,110 @@
+using Content.Server.Objectives.Components;
+using Content.Server.Objectives.Components.Targets;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Objectives.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Content.Shared.Mind.Components;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Movement.Pulling.Components;
+using Content.Server.AWS.Economy.Bank;
+using Content.Server.GameTicking;
+using System.Linq;
+
+namespace Content.Server.AWS.CriminalAntag;
+
+public sealed class StealMoneyConditionSystem : EntitySystem
+{
+ [Dependency] private readonly GameTicker _gameTicker = default!;
+ [Dependency] private readonly EconomyBankAccountSystem _economy = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly SharedObjectivesSystem _objectives = default!;
+
+ private EntityQuery _stealMoneyQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _stealMoneyQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnAssigned);
+ SubscribeLocalEvent(OnAfterAssign);
+ SubscribeLocalEvent(OnGetProgress);
+ }
+
+ private void OnAssigned(Entity condition, ref ObjectiveAssignedEvent args)
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ private void OnAfterAssign(Entity condition, ref ObjectiveAfterAssignEvent args)
+ {
+ _metaData.SetEntityName(condition.Owner, "title", args.Meta);
+ _metaData.SetEntityDescription(condition.Owner, "", args.Meta);
+ _objectives.SetIcon(condition.Owner, null!, args.Objective);
+ }
+ private void OnGetProgress(Entity condition, ref ObjectiveGetProgressEvent args)
+ {
+ if (args.Mind.OwnedEntity is not { } uid)
+ return;
+
+ var progress = condition.Comp.ReachType switch
+ {
+ StealMoneyReachType.AsPossible => CalculateAsPossibleProgress(uid),
+ StealMoneyReachType.SingleSpecificReach => CalculateSingleSpecificReachProgress(uid, condition.Comp),
+ StealMoneyReachType.DependsOnOthers => 0f, // TODO: Implement when needed
+ _ => throw new ArgumentOutOfRangeException(nameof(condition.Comp.ReachType),
+ $"Unsupported reach type: {condition.Comp.ReachType}")
+ };
+
+ args.Progress = progress;
+ }
+
+ private float CalculateAsPossibleProgress(EntityUid uid)
+ {
+ if (_gameTicker.RunLevel != GameRunLevel.PostRound)
+ return 0f;
+
+ var issuerMoney = _economy.CountHoldMoney(uid);
+ var highestCompetitorMoney = FindHighestCompetitorMoney();
+
+ return issuerMoney >= highestCompetitorMoney ? 1f : 0f;
+ }
+
+ private ulong FindHighestCompetitorMoney()
+ {
+ ulong maxMoney = 0;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var mindUid, out var mindComp))
+ {
+ if (mindComp.OwnedEntity is not { } owner)
+ continue;
+
+ foreach (var objective in mindComp.Objectives)
+ {
+ if (!TryComp(objective, out var stealMoneyCondition))
+ continue;
+
+ if ((StealMoneyReachType) stealMoneyCondition.ReachType != StealMoneyReachType.AsPossible)
+ continue;
+
+ var currentMoney = _economy.CountHoldMoney(owner);
+ if (currentMoney > maxMoney)
+ maxMoney = currentMoney;
+ }
+ }
+
+ return maxMoney;
+ }
+
+ private float CalculateSingleSpecificReachProgress(EntityUid uid, StealMoneyConditionComponent comp)
+ {
+ return _economy.CountHoldMoney(uid);
+ }
+}
diff --git a/Content.Server/AWS/Economy/Insurance/EconomyInsuranceSystem.cs b/Content.Server/AWS/Economy/Insurance/EconomyInsuranceSystem.cs
new file mode 100644
index 0000000000..5b0767ab99
--- /dev/null
+++ b/Content.Server/AWS/Economy/Insurance/EconomyInsuranceSystem.cs
@@ -0,0 +1,342 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.Forensics;
+using Content.Server.Spawners.EntitySystems;
+using Content.Server.Station.Systems;
+using Content.Shared.Access.Components;
+using Content.Shared.AWS.Economy.Bank;
+using Content.Shared.Inventory;
+using Content.Shared.PDA;
+using Content.Shared.Preferences;
+using Content.Shared.AWS.Economy.Insurance;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+using Robust.Shared.Random;
+using Robust.Server.GameStates;
+using Robust.Shared.GameObjects;
+using Content.Shared.Roles;
+using Robust.Server.GameObjects;
+using Content.Server.Database;
+using Content.Server.GameTicking;
+using Content.Server.AWS.Economy.Bank;
+
+namespace Content.Server.AWS.Economy.Insurance;
+
+public sealed class EconomyInsuranceSystem : EconomyInsuranceSystemShared
+{
+ [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly EconomyBankAccountSystem _economy = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnPlayerSpawn, after: new[] { typeof(SpawnPointSystem) });
+ SubscribeLocalEvent(OnComponentAdd);
+
+ SubscribeLocalEvent(OnTerminalUpdate);
+ SubscribeLocalEvent(OnEditMessage);
+
+ SubscribeLocalEvent(OnSallaryPost);
+ }
+
+ private void OnSallaryPost(EconomySallaryPostEvent args)
+ {
+ if (!TryGetServer(out var server))
+ return;
+
+ const string ntmedicalId = "NT-Medical"; // hardcode
+ const string ntccId = "NT-CentCom";
+
+ foreach (var (id, insuranceInfo) in server.Comp.InsuranceInfo)
+ {
+ if (_prototype.TryIndex(insuranceInfo.InsuranceProto, out var prototype) && prototype.Cost != 0)
+ {
+ var payerAccountId = insuranceInfo.DefaultFreeInsuranceProto == insuranceInfo.InsuranceProto ?
+ ntccId : insuranceInfo.PayerAccountId;
+
+ if (_economy.TryGetAccount(payerAccountId, out var account)
+ && account.Value.Comp.Balance <= (ulong) prototype.Cost)
+ {
+ insuranceInfo.InsuranceProto = "NonStatus";
+ UpdateIconOnCardsById(insuranceInfo.Id);
+
+ continue;
+ }
+
+ _economy.TrySendMoney(payerAccountId, ntmedicalId, (ulong) prototype.Cost,
+ Loc.GetString("economy-insurance-postsallary-payforinsurance", ("name", insuranceInfo.InsurerName)),
+ out _);
+ }
+ }
+ }
+
+ private void OnTerminalUpdate(Entity entity, ref EconomyInsuranceTerminalUpdateEvent args)
+ {
+ if (!TryGetServer(out var server))
+ return;
+
+ var rights = EconomyInsuranceTerminalRights.Its;
+ var insuranceEnt = args.InsertedInsurance;
+ Dictionary infos;
+
+ if (insuranceEnt is not null)
+ if (CanEditAnyInsurance(entity))
+ {
+ infos = server.Comp.InsuranceInfo;
+ rights = EconomyInsuranceTerminalRights.Full;
+ }
+ else
+ {
+ infos = new();
+
+ if (server.Comp.InsuranceInfo.TryGetValue(insuranceEnt.Value.Comp.InsuranceInfoId, out var value))
+ infos.Add(insuranceEnt.Value.Comp.InsuranceInfoId, value);
+ }
+ else
+ infos = new();
+
+ _userInterface.SetUiState(entity.Owner, EconomyInsuranceTerminalUiKey.Key,
+ new EconomyInsuranceUserInterfaceState(insuranceEnt?.Comp?.InsuranceInfoId ?? 0, rights, infos));
+ }
+
+ private void OnEditMessage(Entity entity, ref EconomyInsuranceEditMessage args)
+ {
+ if (!TryGetTerminalInsertedInsurance(entity, out var insertedInsurance))
+ return;
+
+ var receivedInsuranceInfo = args.Info;
+
+ if (!TryGetInsuranceInfo(receivedInsuranceInfo.Id, out var fetchedInfo))
+ return;
+
+ if (!_prototype.TryIndex(receivedInsuranceInfo.InsuranceProto, out _))
+ return;
+
+ if (CanOnlyEditInsuranceProto(entity, receivedInsuranceInfo.Id))
+ fetchedInfo.InsuranceProto = receivedInsuranceInfo.InsuranceProto;
+
+ if (CanEditAnyInsurance(entity))
+ {
+ fetchedInfo.DNA = receivedInsuranceInfo.DNA;
+ fetchedInfo.InsuranceProto = receivedInsuranceInfo.InsuranceProto;
+ fetchedInfo.InsurerName = receivedInsuranceInfo.InsurerName;
+ fetchedInfo.PayerAccountId = receivedInsuranceInfo.PayerAccountId;
+ }
+
+ UpdateIconOnCardsById(fetchedInfo.Id);
+ UpdateTerminalUserInterface(entity);
+ }
+
+ private void OnComponentAdd(EntityUid uid, EconomyInsuranceServerComponent component, ref ComponentAdd args)
+ {
+ if (TryGetServer(out var ent) && (ent.Owner != uid && ent.Comp == component))
+ {
+ RemComp(uid, component);
+ DebugTools.Assert("Only one supported server can be exists at once in the world");
+ }
+ }
+
+ private void OnPlayerSpawn(PlayerSpawningEvent ev)
+ {
+ if (ev.SpawnResult is null || ev.HumanoidCharacterProfile is null)
+ DebugTools.Assert("Unable to proccess insurance on player spawn!");
+
+ var playerUid = ev.SpawnResult.Value;
+ var profile = ev.HumanoidCharacterProfile;
+ var job = ev.Job;
+
+ var preparedData = PerformPrepareData(playerUid, profile, job);
+
+ if (preparedData is null)
+ DebugTools.Assert($"Unable to proccess insurance by getting necessary components");
+
+ if (!TryCreateInsuranceRecord(preparedData.InsurancePrototype,
+ preparedData.InsurerName,
+ preparedData.InsurerAccountId,
+ preparedData.InsurerDna,
+ out var economyInsuranceInfo,
+ out var error))
+ {
+ DebugTools.Assert($"Unable to create insurance record for {playerUid}!\n{error}");
+ return;
+ }
+
+ economyInsuranceInfo.DefaultFreeInsuranceProto = preparedData.DefaultFreePrototype;
+
+ var cardUid = preparedData.CardUid;
+
+ var insuranceComponent = EnsureComp(cardUid);
+ insuranceComponent.InsuranceInfoId = economyInsuranceInfo.Id;
+
+ UpdateIcon((cardUid, insuranceComponent));
+ }
+
+ private PreparedInsurerData? PerformPrepareData(EntityUid playerUid, HumanoidCharacterProfile profile, ProtoId? job)
+ {
+ if (job is null)
+ return null;
+
+ if (!TryComp(playerUid, out var dnaComponent))
+ return null;
+
+ if (!_inventorySystem.TryGetSlotEntity(playerUid, "id", out var pdaUid))
+ return null;
+
+ if (!TryComp(pdaUid, out var pdaComponent) || pdaComponent.ContainedId is null)
+ return null;
+
+ var cardUid = pdaComponent.ContainedId;
+
+ if (!TryComp(cardUid, out var cardComponent) || cardComponent.FullName is null)
+ return null;
+
+ if (!TryComp(cardUid, out var accountHolderComponent))
+ return null;
+
+ var insurance = MatchDefaultInsuranceProto(job.Value);
+
+ return new(cardUid.Value, insurance, insurance, cardComponent.FullName, accountHolderComponent.AccountID, dnaComponent.DNA);
+ }
+
+ private ProtoId MatchDefaultInsuranceProto(ProtoId job)
+ {
+ var proto = _prototype.Index("NanoTrasenDefault");
+
+ if (proto.Presets.TryGetValue(job, out var entry))
+ return entry;
+
+ return "NonStatus";
+ }
+
+ private ProtoId GetMatchedInsurance(HumanoidCharacterProfile profile, ProtoId job)
+ {
+ return profile.Insurance;
+ }
+
+ [PublicAPI]
+ public bool TryCreateInsuranceRecord(ProtoId insuranceProto,
+ string insurerName,
+ string payerAccountId,
+ string insurerDna,
+ [NotNullWhen(true)] out EconomyInsuranceInfo? economyInsuranceInfo,
+ [NotNullWhen(false)] out string? error)
+ {
+ error = null;
+ economyInsuranceInfo = null;
+
+ if (!TryGetServer(out var server))
+ {
+ error = "Not found server";
+ return false;
+ }
+
+ var comp = server.Comp;
+
+
+ // pervious logic
+ //if (comp.InsuranceInfo.Any(x => x.DNA == insurerDna || x.InsurerName == insurerName))
+ //{
+ // error = "Already exists record with provided data";
+ // return false;
+ //}
+
+
+ var id = GetNextId(server);
+ var insuranceRecord = CreateInsuranceRecord(server, id, insuranceProto, insurerName, payerAccountId, insurerDna);
+
+ economyInsuranceInfo = insuranceRecord;
+
+ return true;
+ }
+
+ [PublicAPI]
+ public bool TryChangeInfoId(int currentId, int newId, [NotNullWhen(false)] out string? error)
+ {
+ error = "";
+
+ if (!TryGetServer(out var server))
+ {
+ error = "Not found server";
+ return false;
+ }
+
+ if (!server.Comp.InsuranceInfo.TryGetValue(currentId, out var info))
+ {
+ error = "not found info";
+ return false;
+ }
+
+ info.Id = newId;
+
+ server.Comp.InsuranceInfo.Remove(currentId);
+ server.Comp.InsuranceInfo.Add(newId, info);
+
+ // alpha temp version
+ foreach (var comp in EntityQuery().Where(x => x.InsuranceInfoId == currentId))
+ Dirty(comp.Owner, comp);
+
+ return true;
+ }
+
+ [PublicAPI]
+ public void UpdateIcon(Entity entity)
+ {
+ if (TryGetInsuranceInfo(entity.Comp.InsuranceInfoId, out var info) &&
+ (_prototype.TryIndex(info.InsuranceProto, out var prototype)))
+ {
+ entity.Comp.IconPrototype = prototype.Icon;
+ Dirty(entity);
+ }
+ }
+
+ [PublicAPI]
+ public void UpdateIconOnCardsById(int insuranceInfoId)
+ {
+ foreach (var comp in EntityQuery().Where(x => x.InsuranceInfoId == insuranceInfoId))
+ if (comp is not null)
+ UpdateIcon((comp.Owner, comp));
+ }
+
+ //[PublicAPI]
+ //public void UpdateInsuranceInfo(int insuranceInfoId, EconomyInsuranceInfo newInsuranceInfo)
+ //{
+
+ //} // maybe in future
+
+ private int GetNextId(EconomyInsuranceServerComponent component)
+ {
+ int lastGenerated = 0;
+ while (lastGenerated == 0 || component.InsuranceInfo.ContainsKey(lastGenerated))
+ {
+ lastGenerated = _random.Next(111, 999);
+ }
+
+ return lastGenerated;
+ }
+
+ private EconomyInsuranceInfo CreateInsuranceRecord(EconomyInsuranceServerComponent serverComponent,
+ int id,
+ ProtoId insuranceProto,
+ string insurerName,
+ string payerAccountId,
+ string insurerDna)
+ {
+ EconomyInsuranceInfo economyInsuranceInfo = new(id, insuranceProto, insurerName, payerAccountId, insurerDna);
+ serverComponent.InsuranceInfo.Add(id, economyInsuranceInfo);
+
+ return economyInsuranceInfo;
+ }
+
+ private record PreparedInsurerData(
+ EntityUid CardUid,
+ ProtoId InsurancePrototype,
+ ProtoId DefaultFreePrototype,
+ string InsurerName,
+ string InsurerAccountId,
+ string InsurerDna);
+}
diff --git a/Content.Server/AWS/Economy/SellableBatteries/SellableBatteriesSystem.cs b/Content.Server/AWS/Economy/SellableBatteries/SellableBatteriesSystem.cs
new file mode 100644
index 0000000000..5b2c2f143f
--- /dev/null
+++ b/Content.Server/AWS/Economy/SellableBatteries/SellableBatteriesSystem.cs
@@ -0,0 +1,92 @@
+using Content.Server.Cargo.Systems;
+using Content.Server.Power.Components;
+using Content.Shared.AWS.Economy.SellableBatteries;
+using Content.Server.NodeContainer;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Robust.Shared.Utility;
+
+namespace Content.Server.AWS.Economy.SellableBatteries;
+
+public sealed class SellableBatteriesSystem : EntitySystem
+{
+
+ [Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSellableBatteryPrice);
+ SubscribeLocalEvent(OnSellableBatteryAnchorState);
+ }
+
+ private void OnSellableBatteryPrice(EntityUid ent, SellableBatteryComponent comp, ref PriceCalculationEvent args)
+ {
+ if (!TryComp(ent, out var batteryComponent))
+ return;
+
+ args.Price += float.Floor(CalculateAdditionalBatteryCost(batteryComponent, comp));
+ }
+
+ private float CalculateAdditionalBatteryCost(BatteryComponent batteryComponent, SellableBatteryComponent sellableBatteryComponent)
+ {
+ return batteryComponent.MaxCharge / batteryComponent.CurrentCharge * sellableBatteryComponent.PricePerChargedPrecent;
+ }
+
+ private void OnSellableBatteryAnchorState(EntityUid batteryUid, SellableBatteryComponent comp, ref AnchorStateChangedEvent args)
+ {
+ var batteryTransformComp = Transform(batteryUid);
+ if (batteryTransformComp.GridUid is not { } gridUid)
+ return;
+
+ if (!TryFindCharger(gridUid, batteryTransformComp.LocalPosition.Floored(), out var charger))
+ return;
+
+ if (args.Anchored)
+ if (TryComp(batteryUid, out var batteryComp)
+ && TryComp(batteryUid, out var nodeContainerComp))
+ {
+ AttachBattery((batteryUid, comp, batteryComp, nodeContainerComp), charger.Value);
+
+ return;
+ }
+
+ DetachBattery(charger.Value);
+ }
+
+ public bool TryFindCharger(EntityUid gridUid, Vector2i pos, [NotNullWhen(true)] out Entity? charger)
+ {
+ charger = null;
+
+ HashSet> chargers = new();
+ _lookupSystem.GetLocalEntitiesIntersecting(gridUid, pos, chargers);
+
+ if (chargers.Count == 0)
+ return false;
+
+ if (chargers.Count > 1)
+ {
+ DebugTools.Assert($"Error configured prototype when applied {typeof(SellableBatteryProxySwitcherComponent)}! Only 1 charger can be at 1 tile!\nEntities: {String.Join(", ", chargers.AsEnumerable())}");
+ return false;
+ }
+
+ charger = chargers.Single();
+ return true;
+ }
+
+ private void AttachBattery(Entity battery, Entity charger)
+ {
+ charger.Comp.Connected = true;
+ charger.Comp.ConnectedBattery = battery;
+
+ Dirty(charger);
+ }
+
+ private void DetachBattery(Entity charger)
+ {
+ charger.Comp.Connected = false;
+
+ Dirty(charger);
+ }
+}
diff --git a/Content.Server/AWS/SkillSystem.cs b/Content.Server/AWS/SkillSystem.cs
new file mode 100644
index 0000000000..dfa60cd3f9
--- /dev/null
+++ b/Content.Server/AWS/SkillSystem.cs
@@ -0,0 +1,11 @@
+using Content.Shared.AWS.Skills;
+
+namespace Content.Server.AWS.Skills;
+
+public sealed class SkillSystem : SharedSkillSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ }
+}
diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs
index 91c6515020..d5389a8acd 100644
--- a/Content.Server/Station/Systems/StationSpawningSystem.cs
+++ b/Content.Server/Station/Systems/StationSpawningSystem.cs
@@ -28,6 +28,8 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
+using Content.Server.AWS.Economy.Bank;
+using Content.Shared.AWS.Economy.Bank;
namespace Content.Server.Station.Systems;
@@ -48,6 +50,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
[Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly InternalEncryptionKeySpawner _internalEncryption = default!;
+ [Dependency] private readonly EconomyBankAccountSystem _bankAccountSystem = default!;
private bool _randomizeCharacters;
@@ -210,6 +213,11 @@ public void SetPdaAndIdCardData(EntityUid entity, string characterName, JobProto
if (pdaComponent != null)
_pdaSystem.SetOwner(idUid.Value, pdaComponent, entity, characterName);
+
+ // SS14RU
+ if (TryComp(cardId, out var economyAccountHolder))
+ _bankAccountSystem.TryActivate((cardId, economyAccountHolder), out _);
+ // SS14RU
}
diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs
index 5c1e7edf91..93d26762e7 100644
--- a/Content.Server/VendingMachines/VendingMachineSystem.cs
+++ b/Content.Server/VendingMachines/VendingMachineSystem.cs
@@ -26,6 +26,7 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
+using Content.Shared.AWS.Economy.Bank;
namespace Content.Server.VendingMachines
{
@@ -38,6 +39,11 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ // SS14RU
+ [Dependency] private readonly AWS.Economy.Bank.EconomyBankAccountSystem _bankAccountSystem = default!;
+ // SS14RU
private const float WallVendEjectDistanceFromWall = 1f;
@@ -48,14 +54,17 @@ public override void Initialize()
SubscribeLocalEvent(OnPowerChanged);
SubscribeLocalEvent(OnBreak);
SubscribeLocalEvent(OnEmagged);
- SubscribeLocalEvent(OnDamageChanged);
+ SubscribeLocalEvent(OnDamage);
SubscribeLocalEvent(OnVendingPrice);
SubscribeLocalEvent(OnActivatableUIOpenAttempt);
Subs.BuiEvents(VendingMachineUiKey.Key, subs =>
{
- subs.Event(OnInventoryEjectMessage);
+ // subs.Event(OnInventoryEjectMessage);
+ // SS14RU
+ subs.Event(OnSelectMessage);
+ // SS14RU
});
SubscribeLocalEvent(OnSelfDispense);
@@ -65,6 +74,34 @@ public override void Initialize()
SubscribeLocalEvent(OnPriceCalculation);
}
+ private void OnSelectMessage(EntityUid uid, VendingMachineComponent component, VendingMachineSelectMessage args)
+ {
+ var entry = GetEntry(uid, args.ID, args.Type, component);
+ if (!TryComp(uid, out var economyBankTerminalComponent) || HasComp(uid) || (entry is not null && entry.Price == 0))
+ {
+ OnInventoryEjectMessage(uid, component, new VendingMachineEjectMessage(args.Type, args.ID) { Actor = args.Actor });
+ return;
+ }
+ if (economyBankTerminalComponent is not null && entry is not null)
+ {
+ _bankAccountSystem.UpdateTerminal((uid, economyBankTerminalComponent),
+ entry.Price,
+ Loc.GetString("economyBankTerminal-component-vending-reason", ("itemName", args.ID)));
+ component.SelectedItemInventoryType = args.Type;
+ component.SelectedItemId = args.ID;
+ }
+ }
+
+ protected override void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args)
+ {
+ base.OnMapInit(uid, component, args);
+
+ if (HasComp(uid))
+ {
+ TryUpdateVisualState(uid, component);
+ }
+ }
+
private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
{
var price = 0.0;
@@ -83,16 +120,6 @@ private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, re
args.Price += price;
}
- protected override void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args)
- {
- base.OnMapInit(uid, component, args);
-
- if (HasComp(uid))
- {
- TryUpdateVisualState(uid, component);
- }
- }
-
private void OnActivatableUIOpenAttempt(EntityUid uid, VendingMachineComponent component, ActivatableUIOpenAttemptEvent args)
{
if (component.Broken)
@@ -127,15 +154,8 @@ private void OnEmagged(EntityUid uid, VendingMachineComponent component, ref Got
args.Handled = component.EmaggedInventory.Count > 0;
}
- private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
+ private void OnDamage(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
{
- if (!args.DamageIncreased && component.Broken)
- {
- component.Broken = false;
- TryUpdateVisualState(uid, component);
- return;
- }
-
if (component.Broken || component.DispenseOnHitCoolingDown ||
component.DispenseOnHitChance == null || args.DamageDelta == null)
return;
@@ -372,19 +392,7 @@ private void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = n
return;
}
- // Default spawn coordinates
- var spawnCoordinates = Transform(uid).Coordinates;
-
- //Make sure the wallvends spawn outside of the wall.
-
- if (TryComp(uid, out var wallMountComponent))
- {
-
- var offset = wallMountComponent.Direction.ToWorldVec() * WallVendEjectDistanceFromWall;
- spawnCoordinates = spawnCoordinates.Offset(offset);
- }
-
- var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates);
+ var ent = Spawn(vendComponent.NextItemToEject, Transform(uid).Coordinates);
if (vendComponent.ThrowNextItem)
{
@@ -499,3 +507,4 @@ private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent co
}
}
}
+
diff --git a/Content.Shared/AWS/Economy/Bank/BankAccountSetup.cs b/Content.Shared/AWS/Economy/Bank/BankAccountSetup.cs
new file mode 100644
index 0000000000..e151efda13
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/BankAccountSetup.cs
@@ -0,0 +1,35 @@
+using Content.Shared.Store;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.AWS.Economy.Bank;
+
+[DataDefinition]
+public sealed partial class BankAccountSetup
+{
+ [DataField("accountID")]
+ public string? AccountID;
+
+ [DataField("generateAccountID")]
+ public bool GenerateAccountID = false;
+
+ [DataField("accountName")]
+ public string? AccountName;
+
+ [DataField("allowedCurrency")]
+ public ProtoId? AllowedCurrency;
+
+ [DataField("balance")]
+ public ulong? Balance;
+
+ [DataField("penalty")]
+ public ulong? Penalty;
+
+ [DataField("blocked")]
+ public bool? Blocked;
+
+ [DataField("canReachPayDay")]
+ public bool? CanReachPayDay;
+
+ [DataField("accountTags")]
+ public List? AccountTags;
+}
diff --git a/Content.Shared/AWS/Economy/Bank/BankAccountTag.cs b/Content.Shared/AWS/Economy/Bank/BankAccountTag.cs
new file mode 100644
index 0000000000..d0a46e0aea
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/BankAccountTag.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.AWS.Economy.Bank;
+
+[Serializable, NetSerializable]
+public enum BankAccountTag
+{
+ Department,
+ Station,
+ Personal
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyAccountHolderComponent.cs b/Content.Shared/AWS/Economy/Bank/EconomyAccountHolderComponent.cs
new file mode 100644
index 0000000000..8dacbc31f9
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyAccountHolderComponent.cs
@@ -0,0 +1,38 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.AWS.Economy.Bank;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EconomyAccountHolderComponent : Component
+{
+ public const string NoValueID = "NO VALUE";
+ public const string UnexpectedUserName = "UNEXPECTED USER";
+
+ [ViewVariables(VVAccess.ReadWrite), DataField(required: true)]
+ public ProtoId AccountIdByProto = "Nanotrasen";
+
+ [ViewVariables(VVAccess.ReadWrite), DataField(required: true)]
+ public EntProtoId MoneyHolderEntId = "ThalerHolder";
+
+ ///
+ /// Set this up in to define the account, which this card will be using (referring to).
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public string AccountID = NoValueID;
+
+ ///
+ /// Set this up in to define the name, which this card will be using.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public string AccountName = UnexpectedUserName;
+
+ ///
+ /// Use this in prototypes for defining the account, which this card will be using (the account will be initialized on spawn).
+ /// Also, parameters beyond AccountName can be used with IDCards (if you want to setup other currency, for example).
+ ///
+ [ViewVariables(VVAccess.ReadOnly), DataField]
+ public BankAccountSetup AccountSetup;
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyAccountId.cs b/Content.Shared/AWS/Economy/Bank/EconomyAccountId.cs
new file mode 100644
index 0000000000..a6cf156995
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyAccountId.cs
@@ -0,0 +1,25 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.AWS.Economy.Bank
+{
+ [Prototype("economyAccountId")]
+ public sealed partial class EconomyAccountIdPrototype : IPrototype
+ {
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ [DataField(required: false)]
+ public string Prefix = "";
+ [DataField(required: false)]
+ public string? Descriptior;
+ [DataField(required: false)]
+ public uint Streak = 4;
+ [DataField(required: false)]
+ public uint NumbersPerStreak = 4;
+
+ [DataField(required: false)]
+ public uint[] MinMaxSallary = {0,0};
+ [DataField(required: false)]
+ public uint[] MinMaxStartMoney = {0,0};
+ }
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyBankATMComponent.cs b/Content.Shared/AWS/Economy/Bank/EconomyBankATMComponent.cs
new file mode 100644
index 0000000000..dda1fa8a1b
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyBankATMComponent.cs
@@ -0,0 +1,27 @@
+using Content.Shared.Containers.ItemSlots;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.AWS.Economy.Bank
+{
+ [RegisterComponent]
+ public sealed partial class EconomyBankATMComponent : Component
+ {
+ public const string ATMCardId = "ATM-CardId";
+
+ [DataField]
+ public ItemSlot CardSlot = new();
+
+ [ViewVariables(VVAccess.ReadWrite), DataField(required: true)]
+ public EntProtoId MoneyHolderEntId = "ThalerHolder";
+
+ [ViewVariables(VVAccess.ReadWrite), DataField]
+ public UInt16 EmagDropMoneyHolderRandomCount = 3;
+
+ [ViewVariables(VVAccess.ReadWrite), DataField]
+ public List EmagDropMoneyValues = new();
+
+ [DataField]
+ public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks");
+ }
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyBankATMUserInterfaceState.cs b/Content.Shared/AWS/Economy/Bank/EconomyBankATMUserInterfaceState.cs
new file mode 100644
index 0000000000..efa937d825
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyBankATMUserInterfaceState.cs
@@ -0,0 +1,38 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.AWS.Economy.Bank;
+
+[Serializable, NetSerializable]
+public sealed class EconomyBankATMUserInterfaceState : BoundUserInterfaceState
+{
+ public EconomyBankATMAccountInfo? BankAccount;
+ public string? Error;
+}
+
+[Serializable, NetSerializable]
+public sealed class EconomyBankATMAccountInfo
+{
+ public ulong Balance;
+ public string AccountId = "";
+ public string AccountName = "";
+ public bool Blocked;
+}
+
+[Serializable, NetSerializable]
+public sealed class EconomyBankATMWithdrawMessage(ulong amount) : BoundUserInterfaceMessage
+{
+ public readonly ulong Amount = amount;
+}
+
+[Serializable, NetSerializable]
+public sealed class EconomyBankATMTransferMessage(ulong amount, string recipientAccountId) : BoundUserInterfaceMessage
+{
+ public readonly ulong Amount = amount;
+ public readonly string RecipientAccountId = recipientAccountId;
+}
+
+[Serializable, NetSerializable]
+public enum EconomyBankATMUiKey
+{
+ Key
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyBankAccountComponent.cs b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountComponent.cs
new file mode 100644
index 0000000000..7f9b6160f2
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountComponent.cs
@@ -0,0 +1,71 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+using Content.Shared.Store;
+using Content.Shared.Roles;
+
+namespace Content.Shared.AWS.Economy.Bank
+{
+ ///
+ /// This component is used to define the account. This component should not be created manually. Work with it through or the server system.
+ ///
+ [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(EconomyBankAccountSystemShared))]
+ public sealed partial class EconomyBankAccountComponent : Component
+ {
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public string AccountID = "NO VALUE";
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public string AccountName = "UNEXPECTED USER";
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public ProtoId AllowedCurrency = "Thaler";
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public ulong Balance = 0;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public ulong Penalty = 0;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public bool Blocked;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public bool CanReachPayDay = true;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public ProtoId? JobName;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public ulong? Salary;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public List Logs = new();
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public List AccountTags = new();
+ }
+
+ [Serializable, NetSerializable]
+ public struct EconomyBankAccountLogField
+ {
+ public EconomyBankAccountLogField(TimeSpan logTime, string logText)
+ {
+ Date = logTime;
+ Text = logText;
+ }
+ public TimeSpan Date;
+ public string Text;
+ }
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyBankAccountMask.cs b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountMask.cs
new file mode 100644
index 0000000000..13734b16a8
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountMask.cs
@@ -0,0 +1,9 @@
+namespace Content.Shared.AWS.Economy.Bank;
+
+public enum EconomyBankAccountMask
+{
+ All,
+ NotBlocked,
+ Blocked,
+ ByTags,
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyBankAccountParam.cs b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountParam.cs
new file mode 100644
index 0000000000..5fde64a8db
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountParam.cs
@@ -0,0 +1,13 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.AWS.Economy.Bank;
+
+[Serializable, NetSerializable]
+public enum EconomyBankAccountParam
+{
+ AccountName,
+ Blocked,
+ CanReachPayDay,
+ JobName,
+ Salary
+}
diff --git a/Content.Shared/AWS/Economy/Bank/EconomyBankAccountSystemShared.cs b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountSystemShared.cs
new file mode 100644
index 0000000000..9ad549bc20
--- /dev/null
+++ b/Content.Shared/AWS/Economy/Bank/EconomyBankAccountSystemShared.cs
@@ -0,0 +1,407 @@
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Examine;
+using Robust.Shared.Containers;
+using Content.Shared.Popups;
+using JetBrains.Annotations;
+using Robust.Shared.Serialization;
+using System.Linq;
+using Content.Shared.Access.Systems;
+using Content.Shared.Access.Components;
+using Robust.Shared.Prototypes;
+using Content.Shared.Roles;
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Mind;
+using Content.Shared.Movement.Pulling.Components;
+using Content.Shared.Mind.Components;
+
+namespace Content.Shared.AWS.Economy.Bank
+{
+ public class EconomyBankAccountSystemShared : EntitySystem
+ {
+ [Dependency] protected readonly EntityManager _entManager = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
+ [Dependency] private readonly SharedUserInterfaceSystem _userInterfaceSystem = default!;
+ [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private EntityQuery _containerQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _containerQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnBankTerminalExamine);
+ SubscribeLocalEvent(OnTerminalMessage);
+
+ SubscribeLocalEvent(OnBankAccountExamine);
+ SubscribeLocalEvent(OnMoneyHolderExamine);
+
+ SubscribeLocalEvent(OnATMComponentInit);
+ SubscribeLocalEvent(OnATMComponentRemove);
+ SubscribeLocalEvent(OnATMItemSlotChanged);
+ SubscribeLocalEvent(OnATMItemSlotChanged);
+
+ SubscribeLocalEvent(OnManagementConsoleInit);
+ SubscribeLocalEvent(OnManagementConsoleRemove);
+ SubscribeLocalEvent(OnManagementConsoleSlotChanged);
+ SubscribeLocalEvent(OnManagementConsoleEntRemoved);
+ }
+
+ ///
+ /// Checks if the account exists (valid).
+ ///
+ /// True if the account exists, false otherwise.
+ [PublicAPI]
+ public bool IsValidAccount(string accountID)
+ {
+ var accounts = GetAccounts(EconomyBankAccountMask.All);
+ return accounts.ContainsKey(accountID);
+ }
+
+ ///
+ /// Tries to fetch the account with the given ID.
+ ///
+ /// True if the fetching was successful, false otherwise.
+ [PublicAPI]
+ public bool TryGetAccount(string accountID, [NotNullWhen(true)] out Entity? account)
+ {
+ var accounts = GetAccounts(EconomyBankAccountMask.All);
+ if (accounts.TryGetValue(accountID, out var foundAccount))
+ {
+ account = foundAccount;
+ return true;
+ }
+
+ account = null;
+ return false;
+ }
+
+ ///
+ /// Returns all currently existing accounts.
+ ///
+ /// Filter mask to fetch accounts.
+ [PublicAPI]
+ public IReadOnlyDictionary> GetAccounts(EconomyBankAccountMask flag = EconomyBankAccountMask.NotBlocked, List? accountTags = null)
+ {
+ var accountsEnum = _entManager.EntityQueryEnumerator();
+ var result = new Dictionary>();
+
+ while (accountsEnum.MoveNext(out var ent, out var comp))
+ {
+ var shouldAdd = flag switch
+ {
+ EconomyBankAccountMask.All => true,
+ EconomyBankAccountMask.NotBlocked => !comp.Blocked,
+ EconomyBankAccountMask.Blocked => comp.Blocked,
+ EconomyBankAccountMask.ByTags => accountTags != null && comp.AccountTags.Any(accountTags.Contains),
+ _ => false
+ };
+
+ if (shouldAdd)
+ result.Add(comp.AccountID, (ent, comp));
+ }
+
+ return result;
+ }
+
+ ///
+ /// Returns JobEntry from salaries prototype.
+ ///
+ [PublicAPI]
+ public bool TryGetSalaryJobEntry(ProtoId jobName, ProtoId salaries, [NotNullWhen(true)] out EconomySallariesJobEntry? jobEntry)
+ {
+ jobEntry = null;
+ if (!_prototypeManager.TryIndex(salaries, out var salariesPrototype))
+ return false;
+
+ if (!salariesPrototype.Jobs.TryGetValue(jobName, out var job))
+ return false;
+
+ jobEntry = job;
+ return true;
+ }
+
+ private void OnBankAccountExamine(Entity entity, ref ExaminedEvent args)
+ {
+ if (!TryGetAccount(entity.Comp.AccountID, out var accountEntity))
+ return;
+
+ var account = accountEntity.Value.Comp;
+ args.PushMarkup(Loc.GetString("bankaccount-component-on-examine-detailed-message",
+ ("id", account.AccountID)));
+ args.PushMarkup(Loc.GetString("moneyholder-component-on-examine-detailed-message",
+ ("moneyName", account.AllowedCurrency),
+ ("balance", account.Balance)));
+ }
+
+ private void OnTerminalMessage(EntityUid uid, EconomyBankTerminalComponent comp, EconomyTerminalMessage args)
+ {
+ UpdateTerminal((uid, comp), args.Amount, args.Reason);
+ }
+
+ private void OnBankTerminalExamine(Entity entity, ref ExaminedEvent args)
+ {
+ var comp = entity.Comp;
+ args.PushMarkup(Loc.GetString("economyBankTerminal-component-on-examine-connected-to",
+ ("accountId", comp.LinkedAccount)));
+
+ if (comp.Amount > 0)
+ {
+ args.PushMarkup(Loc.GetString("economyBankTerminal-component-on-examine-pay-for-ifmorethanzero",
+ ("amount", comp.Amount),
+ ("currencyName", comp.AllowCurrency)));
+ }
+ else args.PushMarkup(Loc.GetString("economyBankTerminal-component-on-examine-pay-for-iflessthanzero"));
+
+ if (comp.Reason != string.Empty)
+ args.PushMarkup(Loc.GetString("economyBankTerminal-component-on-examine-reason", ("reason", comp.Reason)));
+ }
+ private void OnMoneyHolderExamine(Entity entity, ref ExaminedEvent args)
+ {
+ args.PushMarkup(Loc.GetString("moneyholder-component-on-examine-detailed-message",
+ ("moneyName", entity.Comp.AllowCurrency),
+ ("balance", entity.Comp.Balance)));
+ }
+ private void OnATMComponentInit(EntityUid uid, EconomyBankATMComponent atm, ComponentInit args)
+ {
+ _itemSlotsSystem.AddItemSlot(uid, EconomyBankATMComponent.ATMCardId, atm.CardSlot);
+
+ UpdateATMUserInterface((uid, atm));
+ }
+ private void OnATMComponentRemove(EntityUid uid, EconomyBankATMComponent atm, ComponentRemove args)
+ {
+ _itemSlotsSystem.RemoveItemSlot(uid, atm.CardSlot);
+ }
+
+ private void OnATMItemSlotChanged(EntityUid uid, EconomyBankATMComponent atm, ContainerModifiedMessage args)
+ {
+ if (args.Container.ID != atm.CardSlot.ID)
+ return;
+
+ UpdateATMUserInterface((uid, atm));
+ }
+
+ [PublicAPI]
+ public void UpdateATMUserInterface(Entity