2023-03-05 04:27:30 +00:00
using System.Diagnostics.CodeAnalysis ;
2022-06-23 14:36:47 +10:00
using Content.Server.Access.Systems ;
using Content.Server.Cargo.Components ;
2023-03-05 04:27:30 +00:00
using Content.Server.Labels.Components ;
2022-06-23 14:36:47 +10:00
using Content.Server.MachineLinking.System ;
using Content.Server.Popups ;
using Content.Server.Station.Systems ;
using Content.Shared.Access.Systems ;
2022-12-15 12:33:27 -06:00
using Content.Shared.Administration.Logs ;
2022-06-23 14:36:47 +10:00
using Content.Shared.Cargo ;
using Content.Shared.Cargo.BUI ;
using Content.Shared.Cargo.Events ;
using Content.Shared.Cargo.Prototypes ;
2022-12-15 12:33:27 -06:00
using Content.Shared.Database ;
2022-06-23 14:36:47 +10:00
using Content.Shared.GameTicking ;
2023-03-05 04:27:30 +00:00
using Content.Server.Paper ;
2022-06-23 14:36:47 +10:00
using Robust.Server.GameObjects ;
2023-03-05 04:27:30 +00:00
using Robust.Shared.Map ;
2022-06-23 14:36:47 +10:00
using Robust.Shared.Players ;
namespace Content.Server.Cargo.Systems
{
public sealed partial class CargoSystem
{
/// <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 ;
[Dependency] private readonly IdCardSystem _idCardSystem = default ! ;
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default ! ;
[Dependency] private readonly SignalLinkerSystem _linker = default ! ;
[Dependency] private readonly PopupSystem _popup = default ! ;
[Dependency] private readonly StationSystem _station = default ! ;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default ! ;
2022-12-15 12:33:27 -06:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2022-06-23 14:36:47 +10:00
private void InitializeConsole ( )
{
SubscribeLocalEvent < CargoOrderConsoleComponent , CargoConsoleAddOrderMessage > ( OnAddOrderMessage ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , CargoConsoleRemoveOrderMessage > ( OnRemoveOrderMessage ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , CargoConsoleApproveOrderMessage > ( OnApproveOrderMessage ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , BoundUIOpenedEvent > ( OnOrderUIOpened ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , ComponentInit > ( OnInit ) ;
SubscribeLocalEvent < RoundRestartCleanupEvent > ( Reset ) ;
Reset ( ) ;
}
private void OnInit ( EntityUid uid , CargoOrderConsoleComponent orderConsole , ComponentInit args )
{
var station = _station . GetOwningStation ( uid ) ;
UpdateOrderState ( orderConsole , station ) ;
}
private void Reset ( RoundRestartCleanupEvent ev )
{
Reset ( ) ;
}
private void Reset ( )
{
_timer = 0 ;
}
private void UpdateConsole ( float frameTime )
{
_timer + = frameTime ;
while ( _timer > Delay )
{
_timer - = Delay ;
foreach ( var account in EntityQuery < StationBankAccountComponent > ( ) )
{
account . Balance + = account . IncreasePerSecond * Delay ;
}
foreach ( var comp in EntityQuery < CargoOrderConsoleComponent > ( ) )
{
if ( ! _uiSystem . IsUiOpen ( comp . Owner , CargoConsoleUiKey . Orders ) ) continue ;
var station = _station . GetOwningStation ( comp . Owner ) ;
UpdateOrderState ( comp , station ) ;
}
}
}
#region Interface
private void OnApproveOrderMessage ( EntityUid uid , CargoOrderConsoleComponent component , CargoConsoleApproveOrderMessage args )
{
if ( args . Session . AttachedEntity is not { Valid : true } player )
return ;
if ( ! _accessReaderSystem . IsAllowed ( player , uid ) )
{
ConsolePopup ( args . Session , Loc . GetString ( "cargo-console-order-not-allowed" ) ) ;
PlayDenySound ( uid , component ) ;
return ;
}
var orderDatabase = GetOrderDatabase ( component ) ;
var bankAccount = GetBankAccount ( component ) ;
// No station to deduct from.
if ( orderDatabase = = null | | bankAccount = = null )
{
ConsolePopup ( args . Session , Loc . GetString ( "cargo-console-station-not-found" ) ) ;
PlayDenySound ( uid , component ) ;
return ;
}
2023-03-05 04:27:30 +00:00
// Find our order again. It might have been dispatched or approved already
var order = orderDatabase . Orders . Find ( order = > ( args . OrderId = = order . OrderId ) & & ! order . Approved ) ;
if ( order = = null )
{
return ;
}
2022-06-23 14:36:47 +10:00
// Invalid order
if ( ! _protoMan . TryIndex < CargoProductPrototype > ( order . ProductId , out var product ) )
{
ConsolePopup ( args . Session , Loc . GetString ( "cargo-console-invalid-product" ) ) ;
PlayDenySound ( uid , component ) ;
return ;
}
2023-03-05 04:27:30 +00:00
var amount = GetOutstandingOrderCount ( orderDatabase ) ;
2022-06-23 14:36:47 +10:00
var capacity = orderDatabase . Capacity ;
// Too many orders, avoid them getting spammed in the UI.
if ( amount > = capacity )
{
ConsolePopup ( args . Session , Loc . GetString ( "cargo-console-too-many" ) ) ;
PlayDenySound ( uid , component ) ;
return ;
}
// Cap orders so someone can't spam thousands.
2023-03-05 04:27:30 +00:00
var cappedAmount = Math . Min ( capacity - amount , order . OrderQuantity ) ;
2022-06-23 14:36:47 +10:00
2023-03-05 04:27:30 +00:00
if ( cappedAmount ! = order . OrderQuantity )
2022-06-23 14:36:47 +10:00
{
2023-03-05 04:27:30 +00:00
order . OrderQuantity = cappedAmount ;
2022-06-23 14:36:47 +10:00
ConsolePopup ( args . Session , Loc . GetString ( "cargo-console-snip-snip" ) ) ;
PlayDenySound ( uid , component ) ;
}
2023-03-05 04:27:30 +00:00
var cost = product . PointCost * order . OrderQuantity ;
2022-06-23 14:36:47 +10:00
// Not enough balance
if ( cost > bankAccount . Balance )
{
ConsolePopup ( args . Session , Loc . GetString ( "cargo-console-insufficient-funds" , ( "cost" , cost ) ) ) ;
PlayDenySound ( uid , component ) ;
return ;
}
_idCardSystem . TryFindIdCard ( player , out var idCard ) ;
2022-07-09 16:48:57 +03:00
order . SetApproverData ( idCard ) ;
2023-01-10 04:55:59 -05:00
_audio . PlayPvs ( _audio . GetSound ( component . ConfirmSound ) , uid ) ;
2022-06-23 14:36:47 +10:00
2022-12-15 12:33:27 -06:00
// Log order approval
_adminLogger . Add ( LogType . Action , LogImpact . Low ,
2023-03-05 04:27:30 +00:00
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bankAccount.Balance}" ) ;
2022-12-15 12:33:27 -06:00
2022-06-23 14:36:47 +10:00
DeductFunds ( bankAccount , cost ) ;
UpdateOrders ( orderDatabase ) ;
}
private void OnRemoveOrderMessage ( EntityUid uid , CargoOrderConsoleComponent component , CargoConsoleRemoveOrderMessage args )
{
var orderDatabase = GetOrderDatabase ( component ) ;
if ( orderDatabase = = null ) return ;
2023-03-05 04:27:30 +00:00
RemoveOrder ( orderDatabase , args . OrderId ) ;
2022-06-23 14:36:47 +10:00
}
private void OnAddOrderMessage ( EntityUid uid , CargoOrderConsoleComponent component , CargoConsoleAddOrderMessage args )
{
2022-12-15 12:33:27 -06:00
if ( args . Session . AttachedEntity is not { Valid : true } player )
return ;
2022-06-23 14:36:47 +10:00
if ( args . Amount < = 0 )
return ;
var bank = GetBankAccount ( component ) ;
if ( bank = = null ) return ;
var orderDatabase = GetOrderDatabase ( component ) ;
if ( orderDatabase = = null ) return ;
2023-03-05 04:27:30 +00:00
var data = GetOrderData ( args , GenerateOrderId ( orderDatabase ) ) ;
2022-06-23 14:36:47 +10:00
if ( ! TryAddOrder ( orderDatabase , data ) )
{
PlayDenySound ( uid , component ) ;
return ;
}
2022-12-15 12:33:27 -06:00
// Log order addition
_adminLogger . Add ( LogType . Action , LogImpact . Low ,
2023-03-05 04:27:30 +00:00
$"{ToPrettyString(player):user} added order [orderId:{data.OrderId}, quantity:{data.OrderQuantity}, product:{data.ProductId}, requester:{data.Requester}, reason:{data.Reason}]" ) ;
2022-12-15 12:33:27 -06:00
2022-06-23 14:36:47 +10:00
}
private void OnOrderUIOpened ( EntityUid uid , CargoOrderConsoleComponent component , BoundUIOpenedEvent args )
{
var station = _station . GetOwningStation ( uid ) ;
UpdateOrderState ( component , station ) ;
}
#endregion
private void UpdateOrderState ( CargoOrderConsoleComponent component , EntityUid ? station )
{
if ( station = = null | |
! TryComp < StationCargoOrderDatabaseComponent > ( station , out var orderDatabase ) | |
! TryComp < StationBankAccountComponent > ( station , out var bankAccount ) ) return ;
var state = new CargoConsoleInterfaceState (
MetaData ( station . Value ) . EntityName ,
2023-03-05 04:27:30 +00:00
GetOutstandingOrderCount ( orderDatabase ) ,
2022-06-23 14:36:47 +10:00
orderDatabase . Capacity ,
bankAccount . Balance ,
2023-03-05 04:27:30 +00:00
orderDatabase . Orders ) ;
2022-06-23 14:36:47 +10:00
_uiSystem . GetUiOrNull ( component . Owner , CargoConsoleUiKey . Orders ) ? . SetState ( state ) ;
}
2022-12-19 10:41:47 +13:00
private void ConsolePopup ( ICommonSession session , string text ) = > _popup . PopupCursor ( text , session ) ;
2022-06-23 14:36:47 +10:00
private void PlayDenySound ( EntityUid uid , CargoOrderConsoleComponent component )
{
2023-01-10 04:55:59 -05:00
_audio . PlayPvs ( _audio . GetSound ( component . ErrorSound ) , uid ) ;
2022-06-23 14:36:47 +10:00
}
2023-03-05 04:27:30 +00:00
private CargoOrderData GetOrderData ( CargoConsoleAddOrderMessage args , int id )
2022-06-23 14:36:47 +10:00
{
2023-03-05 04:27:30 +00:00
return new CargoOrderData ( id , args . ProductId , args . Amount , args . Requester , args . Reason ) ;
2022-06-23 14:36:47 +10:00
}
2023-03-05 04:27:30 +00:00
private int GetOutstandingOrderCount ( StationCargoOrderDatabaseComponent component )
2022-06-23 14:36:47 +10:00
{
var amount = 0 ;
2023-03-05 04:27:30 +00:00
foreach ( var order in component . Orders )
2022-06-23 14:36:47 +10:00
{
if ( ! order . Approved ) continue ;
2023-03-05 04:27:30 +00:00
amount + = order . OrderQuantity - order . NumDispatched ;
2022-06-23 14:36:47 +10:00
}
return amount ;
}
/// <summary>
/// Updates all of the cargo-related consoles for a particular station.
/// This should be called whenever orders change.
/// </summary>
private void UpdateOrders ( StationCargoOrderDatabaseComponent component )
{
// Order added so all consoles need updating.
foreach ( var comp in EntityQuery < CargoOrderConsoleComponent > ( true ) )
{
var station = _station . GetOwningStation ( component . Owner ) ;
if ( station ! = component . Owner ) continue ;
UpdateOrderState ( comp , station ) ;
}
foreach ( var comp in EntityQuery < CargoShuttleConsoleComponent > ( true ) )
{
var station = _station . GetOwningStation ( component . Owner ) ;
if ( station ! = component . Owner ) continue ;
UpdateShuttleState ( comp , station ) ;
}
}
public bool TryAddOrder ( StationCargoOrderDatabaseComponent component , CargoOrderData data )
{
2023-03-05 04:27:30 +00:00
component . Orders . Add ( data ) ;
2022-06-23 14:36:47 +10:00
UpdateOrders ( component ) ;
return true ;
}
2023-03-05 04:27:30 +00:00
private int GenerateOrderId ( StationCargoOrderDatabaseComponent orderDB )
2022-06-23 14:36:47 +10:00
{
2023-03-05 04:27:30 +00:00
// We need an arbitrary unique ID to idenitfy orders, since they may
// want to be cancelled later.
return + + orderDB . NumOrdersCreated ;
2022-06-23 14:36:47 +10:00
}
2023-03-05 04:27:30 +00:00
public void RemoveOrder ( StationCargoOrderDatabaseComponent orderDB , int index )
2022-06-23 14:36:47 +10:00
{
2023-03-05 04:27:30 +00:00
var sequenceIdx = orderDB . Orders . FindIndex ( order = > order . OrderId = = index ) ;
if ( sequenceIdx ! = - 1 )
{
orderDB . Orders . RemoveAt ( sequenceIdx ) ;
}
UpdateOrders ( orderDB ) ;
2022-06-23 14:36:47 +10:00
}
public void ClearOrders ( StationCargoOrderDatabaseComponent component )
{
if ( component . Orders . Count = = 0 ) return ;
component . Orders . Clear ( ) ;
Dirty ( component ) ;
}
2023-03-05 04:27:30 +00:00
private bool PopFrontOrder ( StationCargoOrderDatabaseComponent orderDB , [ NotNullWhen ( true ) ] out CargoOrderData ? orderOut )
{
var orderIdx = orderDB . Orders . FindIndex ( order = > order . Approved ) ;
if ( orderIdx = = - 1 )
{
orderOut = null ;
return false ;
}
orderOut = orderDB . Orders [ orderIdx ] ;
orderOut . NumDispatched + + ;
if ( orderOut . NumDispatched > = orderOut . OrderQuantity )
{
// Order is complete. Remove from the queue.
orderDB . Orders . RemoveAt ( orderIdx ) ;
}
return true ;
}
private bool FulfillOrder ( StationCargoOrderDatabaseComponent orderDB , EntityCoordinates whereToPutIt ,
string? paperPrototypeToPrint )
{
if ( PopFrontOrder ( orderDB , out var order ) )
{
// Create the item itself
var item = Spawn ( _protoMan . Index < CargoProductPrototype > ( order . ProductId ) . Product , whereToPutIt ) ;
// Create a sheet of paper to write the order details on
var printed = EntityManager . SpawnEntity ( paperPrototypeToPrint , whereToPutIt ) ;
if ( TryComp < PaperComponent > ( printed , out var paper ) )
{
// fill in the order data
var val = Loc . GetString ( "cargo-console-paper-print-name" , ( "orderNumber" , order . OrderId ) ) ;
MetaData ( printed ) . EntityName = val ;
_paperSystem . SetContent ( printed , Loc . GetString (
"cargo-console-paper-print-text" ,
( "orderNumber" , order . OrderId ) ,
( "itemName" , MetaData ( item ) . EntityName ) ,
( "requester" , order . Requester ) ,
( "reason" , order . Reason ) ,
( "approver" , order . Approver ? ? string . Empty ) ) ,
paper ) ;
// attempt to attach the label to the item
if ( TryComp < PaperLabelComponent > ( item , out var label ) )
{
_slots . TryInsert ( item , label . LabelSlot , printed , null ) ;
}
}
return true ;
}
return false ;
}
2022-06-23 14:36:47 +10:00
private void DeductFunds ( StationBankAccountComponent component , int amount )
{
component . Balance = Math . Max ( 0 , component . Balance - amount ) ;
Dirty ( component ) ;
}
#region Station
private StationBankAccountComponent ? GetBankAccount ( CargoOrderConsoleComponent component )
{
var station = _station . GetOwningStation ( component . Owner ) ;
TryComp < StationBankAccountComponent > ( station , out var bankComponent ) ;
return bankComponent ;
}
private StationCargoOrderDatabaseComponent ? GetOrderDatabase ( CargoOrderConsoleComponent component )
{
var station = _station . GetOwningStation ( component . Owner ) ;
TryComp < StationCargoOrderDatabaseComponent > ( station , out var orderComponent ) ;
return orderComponent ;
}
#endregion
}
}