Departmental Economy (#36445)

* Cargo Accounts, Request Consoles, and lock boxes

* Funding Allocation Computer

* final changes

* test fix

* remove dumb code

* ScarKy0 review

* first cour

* second cour

* Update machines.yml

* review

---------

Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Co-authored-by: Milon <milonpl.git@proton.me>
This commit is contained in:
Nemanja
2025-04-13 09:22:36 -04:00
committed by GitHub
parent 5f78b72763
commit 12b75beeab
62 changed files with 2106 additions and 331 deletions

View File

@@ -12,6 +12,7 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Labels.Components;
using Content.Shared.Paper;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -23,16 +24,6 @@ namespace Content.Server.Cargo.Systems
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
/// <summary>
/// How much time to wait (in seconds) before increasing bank accounts balance.
/// </summary>
private const int Delay = 10;
/// <summary>
/// Keeps track of how much time has elapsed since last balance increase.
/// </summary>
private float _timer;
private void InitializeConsole()
{
SubscribeLocalEvent<CargoOrderConsoleComponent, CargoConsoleAddOrderMessage>(OnAddOrderMessage);
@@ -41,9 +32,7 @@ namespace Content.Server.Cargo.Systems
SubscribeLocalEvent<CargoOrderConsoleComponent, BoundUIOpenedEvent>(OnOrderUIOpened);
SubscribeLocalEvent<CargoOrderConsoleComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<CargoOrderConsoleComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<CargoOrderConsoleComponent, BankBalanceUpdatedEvent>(OnOrderBalanceUpdated);
SubscribeLocalEvent<CargoOrderConsoleComponent, GotEmaggedEvent>(OnEmagged);
Reset();
}
private void OnInteractUsing(EntityUid uid, CargoOrderConsoleComponent component, ref InteractUsingEvent args)
@@ -61,8 +50,8 @@ namespace Content.Server.Cargo.Systems
if (!TryComp(stationUid, out StationBankAccountComponent? bank))
return;
_audio.PlayPvs(component.ConfirmSound, uid);
UpdateBankAccount((stationUid.Value, bank), (int) price);
_audio.PlayPvs(ApproveSound, uid);
UpdateBankAccount((stationUid.Value, bank), (int) price, CreateAccountDistribution(component.Account, bank));
QueueDel(args.Used);
args.Handled = true;
}
@@ -73,11 +62,6 @@ namespace Content.Server.Cargo.Systems
UpdateOrderState(uid, station);
}
private void Reset()
{
_timer = 0;
}
private void OnEmagged(Entity<CargoOrderConsoleComponent> ent, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
@@ -89,31 +73,17 @@ namespace Content.Server.Cargo.Systems
args.Handled = true;
}
private void UpdateConsole(float frameTime)
private void UpdateConsole()
{
_timer += frameTime;
// TODO: Doesn't work with serialization and shouldn't just be updating every delay
// client can just interp this just fine on its own.
while (_timer > Delay)
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
while (stationQuery.MoveNext(out var uid, out var bank))
{
_timer -= Delay;
if (_timing.CurTime < bank.NextIncomeTime)
continue;
bank.NextIncomeTime += bank.IncomeDelay;
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
while (stationQuery.MoveNext(out var uid, out var bank))
{
var balanceToAdd = bank.IncreasePerSecond * Delay;
UpdateBankAccount((uid, bank), balanceToAdd);
}
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
while (query.MoveNext(out var uid, out var _))
{
if (!_uiSystem.IsUiOpen(uid, CargoConsoleUiKey.Orders)) continue;
var station = _station.GetOwningStation(uid);
UpdateOrderState(uid, station);
}
var balanceToAdd = (int) Math.Round(bank.IncreasePerSecond * bank.IncomeDelay.TotalSeconds);
UpdateBankAccount((uid, bank), balanceToAdd, bank.RevenueDistribution);
}
}
@@ -144,7 +114,7 @@ namespace Content.Server.Cargo.Systems
}
// Find our order again. It might have been dispatched or approved already
var order = orderDatabase.Orders.Find(order => args.OrderId == order.OrderId && !order.Approved);
var order = orderDatabase.Orders[component.Account].Find(order => args.OrderId == order.OrderId && !order.Approved);
if (order == null)
{
return;
@@ -158,7 +128,7 @@ namespace Content.Server.Cargo.Systems
return;
}
var amount = GetOutstandingOrderCount(orderDatabase);
var amount = GetOutstandingOrderCount(orderDatabase, component.Account);
var capacity = orderDatabase.Capacity;
// Too many orders, avoid them getting spammed in the UI.
@@ -180,9 +150,10 @@ namespace Content.Server.Cargo.Systems
}
var cost = order.Price * order.OrderQuantity;
var accountBalance = GetBalanceFromAccount((station.Value, bank), component.Account);
// Not enough balance
if (cost > bank.Balance)
if (cost > accountBalance)
{
ConsolePopup(args.Actor, Loc.GetString("cargo-console-insufficient-funds", ("cost", cost)));
PlayDenySound(uid, component);
@@ -195,7 +166,7 @@ namespace Content.Server.Cargo.Systems
if (!ev.Handled)
{
ev.FulfillmentEntity = TryFulfillOrder((station.Value, stationData), order, orderDatabase);
ev.FulfillmentEntity = TryFulfillOrder((station.Value, stationData), component.Account, order, orderDatabase);
if (ev.FulfillmentEntity == null)
{
@@ -206,7 +177,7 @@ namespace Content.Server.Cargo.Systems
}
order.Approved = true;
_audio.PlayPvs(component.ConfirmSound, uid);
_audio.PlayPvs(ApproveSound, uid);
if (!_emag.CheckFlag(uid, EmagType.Interaction))
{
@@ -220,20 +191,23 @@ namespace Content.Server.Cargo.Systems
("approver", order.Approver ?? string.Empty),
("cost", cost));
_radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false);
if (CargoOrderConsoleComponent.BaseAnnouncementChannel != component.AnnouncementChannel)
_radio.SendRadioMessage(uid, message, CargoOrderConsoleComponent.BaseAnnouncementChannel, uid, escapeMarkup: false);
}
ConsolePopup(args.Actor, Loc.GetString("cargo-console-trade-station", ("destination", MetaData(ev.FulfillmentEntity.Value).EntityName)));
// Log order approval
_adminLogger.Add(LogType.Action, LogImpact.Low,
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bank.Balance}");
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] on account {component.Account} with balance at {accountBalance}");
orderDatabase.Orders.Remove(order);
UpdateBankAccount((station.Value, bank), -cost);
orderDatabase.Orders[component.Account].Remove(order);
UpdateBankAccount((station.Value, bank), -cost, CreateAccountDistribution(component.Account, bank));
UpdateOrders(station.Value);
}
private EntityUid? TryFulfillOrder(Entity<StationDataComponent> stationData, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase)
private EntityUid? TryFulfillOrder(Entity<StationDataComponent> stationData, ProtoId<CargoAccountPrototype> account, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase)
{
// No slots at the trade station
_listEnts.Clear();
@@ -253,7 +227,7 @@ namespace Content.Server.Cargo.Systems
{
var coordinates = new EntityCoordinates(trade, pad.Transform.LocalPosition);
if (FulfillOrder(order, coordinates, orderDatabase.PrinterOutput))
if (FulfillOrder(order, account, coordinates, orderDatabase.PrinterOutput))
{
tradeDestination = trade;
order.NumDispatched++;
@@ -288,7 +262,7 @@ namespace Content.Server.Cargo.Systems
if (!TryGetOrderDatabase(station, out var orderDatabase))
return;
RemoveOrder(station.Value, args.OrderId, orderDatabase);
RemoveOrder(station.Value, component.Account, args.OrderId, orderDatabase);
}
private void OnAddOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleAddOrderMessage args)
@@ -315,14 +289,15 @@ namespace Content.Server.Cargo.Systems
var data = GetOrderData(args, product, GenerateOrderId(orderDatabase));
if (!TryAddOrder(stationUid.Value, data, orderDatabase))
if (!TryAddOrder(stationUid.Value, component.Account, data, orderDatabase))
{
PlayDenySound(uid, component);
return;
}
// Log order addition
_adminLogger.Add(LogType.Action, LogImpact.Low,
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(player):user} added order [orderId:{data.OrderId}, quantity:{data.OrderQuantity}, product:{data.ProductId}, requester:{data.Requester}, reason:{data.Reason}]");
}
@@ -335,29 +310,24 @@ namespace Content.Server.Cargo.Systems
#endregion
private void OnOrderBalanceUpdated(Entity<CargoOrderConsoleComponent> ent, ref BankBalanceUpdatedEvent args)
{
if (!_uiSystem.IsUiOpen(ent.Owner, CargoConsoleUiKey.Orders))
return;
UpdateOrderState(ent, args.Station);
}
private void UpdateOrderState(EntityUid consoleUid, EntityUid? station)
{
if (station == null ||
!TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase) ||
!TryComp<StationBankAccountComponent>(station, out var bankAccount)) return;
if (!TryComp<CargoOrderConsoleComponent>(consoleUid, out var console))
return;
if (!TryComp<StationCargoOrderDatabaseComponent>(station, out var orderDatabase))
return;
if (_uiSystem.HasUi(consoleUid, CargoConsoleUiKey.Orders))
{
_uiSystem.SetUiState(consoleUid, CargoConsoleUiKey.Orders, new CargoConsoleInterfaceState(
_uiSystem.SetUiState(consoleUid,
CargoConsoleUiKey.Orders,
new CargoConsoleInterfaceState(
MetaData(station.Value).EntityName,
GetOutstandingOrderCount(orderDatabase),
GetOutstandingOrderCount(orderDatabase, console.Account),
orderDatabase.Capacity,
bankAccount.Balance,
orderDatabase.Orders
GetNetEntity(station.Value),
orderDatabase.Orders[console.Account]
));
}
}
@@ -377,11 +347,11 @@ namespace Content.Server.Cargo.Systems
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Name, cargoProduct.Cost, args.Amount, args.Requester, args.Reason);
}
public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component)
public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component, ProtoId<CargoAccountPrototype> account)
{
var amount = 0;
foreach (var order in component.Orders)
foreach (var order in component.Orders[account])
{
if (!order.Approved)
continue;
@@ -430,6 +400,7 @@ namespace Content.Server.Cargo.Systems
string description,
string dest,
StationCargoOrderDatabaseComponent component,
ProtoId<CargoAccountPrototype> account,
Entity<StationDataComponent> stationData
)
{
@@ -443,16 +414,17 @@ namespace Content.Server.Cargo.Systems
order.Approved = true;
// Log order addition
_adminLogger.Add(LogType.Action, LogImpact.Low,
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"AddAndApproveOrder {description} added order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}]");
// Add it to the list
return TryAddOrder(dbUid, order, component) && TryFulfillOrder(stationData, order, component).HasValue;
return TryAddOrder(dbUid, account, order, component) && TryFulfillOrder(stationData, account, order, component).HasValue;
}
private bool TryAddOrder(EntityUid dbUid, CargoOrderData data, StationCargoOrderDatabaseComponent component)
private bool TryAddOrder(EntityUid dbUid, ProtoId<CargoAccountPrototype> account, CargoOrderData data, StationCargoOrderDatabaseComponent component)
{
component.Orders.Add(data);
component.Orders[account].Add(data);
UpdateOrders(dbUid);
return true;
}
@@ -464,12 +436,12 @@ namespace Content.Server.Cargo.Systems
return ++orderDB.NumOrdersCreated;
}
public void RemoveOrder(EntityUid dbUid, int index, StationCargoOrderDatabaseComponent orderDB)
public void RemoveOrder(EntityUid dbUid, ProtoId<CargoAccountPrototype> account, int index, StationCargoOrderDatabaseComponent orderDB)
{
var sequenceIdx = orderDB.Orders.FindIndex(order => order.OrderId == index);
var sequenceIdx = orderDB.Orders[account].FindIndex(order => order.OrderId == index);
if (sequenceIdx != -1)
{
orderDB.Orders.RemoveAt(sequenceIdx);
orderDB.Orders[account].RemoveAt(sequenceIdx);
}
UpdateOrders(dbUid);
}
@@ -482,22 +454,22 @@ namespace Content.Server.Cargo.Systems
component.Orders.Clear();
}
private static bool PopFrontOrder(StationCargoOrderDatabaseComponent orderDB, [NotNullWhen(true)] out CargoOrderData? orderOut)
private static bool PopFrontOrder(StationCargoOrderDatabaseComponent orderDB, ProtoId<CargoAccountPrototype> account, [NotNullWhen(true)] out CargoOrderData? orderOut)
{
var orderIdx = orderDB.Orders.FindIndex(order => order.Approved);
var orderIdx = orderDB.Orders[account].FindIndex(order => order.Approved);
if (orderIdx == -1)
{
orderOut = null;
return false;
}
orderOut = orderDB.Orders[orderIdx];
orderOut = orderDB.Orders[account][orderIdx];
orderOut.NumDispatched++;
if (orderOut.NumDispatched >= orderOut.OrderQuantity)
{
// Order is complete. Remove from the queue.
orderDB.Orders.RemoveAt(orderIdx);
orderDB.Orders[account].RemoveAt(orderIdx);
}
return true;
}
@@ -505,18 +477,19 @@ namespace Content.Server.Cargo.Systems
/// <summary>
/// Tries to fulfill the next outstanding order.
/// </summary>
private bool FulfillNextOrder(StationCargoOrderDatabaseComponent orderDB, EntityCoordinates spawn, string? paperProto)
[PublicAPI]
private bool FulfillNextOrder(StationCargoOrderDatabaseComponent orderDB, ProtoId<CargoAccountPrototype> account, EntityCoordinates spawn, string? paperProto)
{
if (!PopFrontOrder(orderDB, out var order))
if (!PopFrontOrder(orderDB, account, out var order))
return false;
return FulfillOrder(order, spawn, paperProto);
return FulfillOrder(order, account, spawn, paperProto);
}
/// <summary>
/// Fulfills the specified cargo order and spawns paper attached to it.
/// </summary>
private bool FulfillOrder(CargoOrderData order, EntityCoordinates spawn, string? paperProto)
private bool FulfillOrder(CargoOrderData order, ProtoId<CargoAccountPrototype> account, EntityCoordinates spawn, string? paperProto)
{
// Create the item itself
var item = Spawn(order.ProductId, spawn);
@@ -532,14 +505,18 @@ namespace Content.Server.Cargo.Systems
var val = Loc.GetString("cargo-console-paper-print-name", ("orderNumber", order.OrderId));
_metaSystem.SetEntityName(printed, val);
_paperSystem.SetContent((printed, paper), Loc.GetString(
var accountProto = _protoMan.Index(account);
_paperSystem.SetContent((printed, paper),
Loc.GetString(
"cargo-console-paper-print-text",
("orderNumber", order.OrderId),
("itemName", MetaData(item).EntityName),
("orderQuantity", order.OrderQuantity),
("requester", order.Requester),
("reason", order.Reason),
("approver", order.Approver ?? string.Empty)));
("reason", string.IsNullOrWhiteSpace(order.Reason) ? Loc.GetString("cargo-console-paper-reason-default") : order.Reason),
("account", Loc.GetString(accountProto.Name)),
("accountcode", Loc.GetString(accountProto.Code)),
("approver", string.IsNullOrWhiteSpace(order.Approver) ? Loc.GetString("cargo-console-paper-approver-default") : order.Approver)));
// attempt to attach the label to the item
if (TryComp<PaperLabelComponent>(item, out var label))