2023-03-05 04:27:30 +00:00
using System.Diagnostics.CodeAnalysis ;
2022-06-23 14:36:47 +10:00
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.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 ;
2023-06-16 05:19:02 +00:00
using Robust.Shared.Prototypes ;
using Robust.Shared.Utility ;
2022-06-23 14:36:47 +10:00
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 ;
private void InitializeConsole ( )
{
SubscribeLocalEvent < CargoOrderConsoleComponent , CargoConsoleAddOrderMessage > ( OnAddOrderMessage ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , CargoConsoleRemoveOrderMessage > ( OnRemoveOrderMessage ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , CargoConsoleApproveOrderMessage > ( OnApproveOrderMessage ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , BoundUIOpenedEvent > ( OnOrderUIOpened ) ;
SubscribeLocalEvent < CargoOrderConsoleComponent , ComponentInit > ( OnInit ) ;
Reset ( ) ;
}
private void OnInit ( EntityUid uid , CargoOrderConsoleComponent orderConsole , ComponentInit args )
{
var station = _station . GetOwningStation ( uid ) ;
2023-07-08 09:02:17 -07:00
UpdateOrderState ( uid , station ) ;
2022-06-23 14:36:47 +10:00
}
private void Reset ( )
{
_timer = 0 ;
}
private void UpdateConsole ( float frameTime )
{
_timer + = frameTime ;
2023-06-16 05:19:02 +00:00
// TODO: Doesn't work with serialization and shouldn't just be updating every delay
// client can just interp this just fine on its own.
2022-06-23 14:36:47 +10:00
while ( _timer > Delay )
{
_timer - = Delay ;
foreach ( var account in EntityQuery < StationBankAccountComponent > ( ) )
{
account . Balance + = account . IncreasePerSecond * Delay ;
}
2023-07-08 09:02:17 -07:00
var query = EntityQueryEnumerator < CargoOrderConsoleComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var _ ) )
2022-06-23 14:36:47 +10:00
{
2023-07-08 09:02:17 -07:00
if ( ! _uiSystem . IsUiOpen ( uid , CargoConsoleUiKey . Orders ) ) continue ;
2022-06-23 14:36:47 +10:00
2023-07-08 09:02:17 -07:00
var station = _station . GetOwningStation ( uid ) ;
UpdateOrderState ( uid , station ) ;
2022-06-23 14:36:47 +10:00
}
}
}
#region Interface
private void OnApproveOrderMessage ( EntityUid uid , CargoOrderConsoleComponent component , CargoConsoleApproveOrderMessage args )
{
2023-07-08 09:02:17 -07:00
if ( args . Session . AttachedEntity is not { Valid : true } player )
2022-06-23 14:36:47 +10:00
return ;
if ( ! _accessReaderSystem . IsAllowed ( player , uid ) )
{
ConsolePopup ( args . Session , Loc . GetString ( "cargo-console-order-not-allowed" ) ) ;
PlayDenySound ( uid , component ) ;
return ;
}
2023-07-08 09:02:17 -07:00
var bankAccount = GetBankAccount ( uid , component ) ;
2022-06-23 14:36:47 +10:00
// No station to deduct from.
2023-07-08 09:02:17 -07:00
if ( ! TryGetOrderDatabase ( uid , out var dbUid , out var orderDatabase , component ) | | bankAccount = = null )
2022-06-23 14:36:47 +10:00
{
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
2023-07-08 09:02:17 -07:00
var order = orderDatabase . Orders . Find ( order = > args . OrderId = = order . OrderId & & ! order . Approved ) ;
if ( order = = null )
2023-03-05 04:27:30 +00:00
{
return ;
}
2022-06-23 14:36:47 +10:00
// Invalid order
2023-06-16 05:19:02 +00:00
if ( ! _protoMan . HasIndex < EntityPrototype > ( order . ProductId ) )
2022-06-23 14:36:47 +10:00
{
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-06-16 05:19:02 +00:00
var cost = order . Price * 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 ) ;
2023-06-16 05:19:02 +00:00
order . SetApproverData ( idCard ? . FullName , idCard ? . JobTitle ) ;
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 ) ;
2023-07-08 09:02:17 -07:00
UpdateOrders ( dbUid ! . Value , orderDatabase ) ;
2022-06-23 14:36:47 +10:00
}
private void OnRemoveOrderMessage ( EntityUid uid , CargoOrderConsoleComponent component , CargoConsoleRemoveOrderMessage args )
{
2023-07-08 09:02:17 -07:00
if ( ! TryGetOrderDatabase ( uid , out var dbUid , out var orderDatabase , component ) )
return ;
RemoveOrder ( dbUid ! . Value , args . OrderId , orderDatabase ) ;
2022-06-23 14:36:47 +10:00
}
private void OnAddOrderMessage ( EntityUid uid , CargoOrderConsoleComponent component , CargoConsoleAddOrderMessage args )
{
2023-07-08 09:02:17 -07:00
if ( args . Session . AttachedEntity is not { Valid : true } player )
2022-12-15 12:33:27 -06:00
return ;
2022-06-23 14:36:47 +10:00
if ( args . Amount < = 0 )
return ;
2023-07-08 09:02:17 -07:00
var bank = GetBankAccount ( uid , component ) ;
2023-06-16 05:19:02 +00:00
if ( bank = = null )
return ;
2023-07-08 09:02:17 -07:00
if ( ! TryGetOrderDatabase ( uid , out var dbUid , out var orderDatabase , component ) )
2023-06-16 05:19:02 +00:00
return ;
if ( ! _protoMan . TryIndex < CargoProductPrototype > ( args . CargoProductId , out var product ) )
{
2023-07-10 05:24:48 +10:00
Log . Error ( $"Tried to add invalid cargo product {args.CargoProductId} as order!" ) ;
2023-06-16 05:19:02 +00:00
return ;
}
2022-06-23 14:36:47 +10:00
2023-06-16 05:19:02 +00:00
var data = GetOrderData ( args , product , GenerateOrderId ( orderDatabase ) ) ;
2022-06-23 14:36:47 +10:00
2023-07-08 09:02:17 -07:00
if ( ! TryAddOrder ( dbUid ! . Value , data , orderDatabase ) )
2022-06-23 14:36:47 +10:00
{
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 ) ;
2023-07-08 09:02:17 -07:00
UpdateOrderState ( uid , station ) ;
2022-06-23 14:36:47 +10:00
}
#endregion
2023-07-08 09:02:17 -07:00
private void UpdateOrderState ( EntityUid consoleUid , EntityUid ? station )
2022-06-23 14:36:47 +10:00
{
if ( station = = null | |
! TryComp < StationCargoOrderDatabaseComponent > ( station , out var orderDatabase ) | |
! TryComp < StationBankAccountComponent > ( station , out var bankAccount ) ) return ;
2023-07-08 09:02:17 -07:00
if ( _uiSystem . TryGetUi ( consoleUid , CargoConsoleUiKey . Orders , out var bui ) )
UserInterfaceSystem . SetUiState ( bui , new CargoConsoleInterfaceState (
MetaData ( station . Value ) . EntityName ,
GetOutstandingOrderCount ( orderDatabase ) ,
orderDatabase . Capacity ,
bankAccount . Balance ,
orderDatabase . Orders
) ) ;
2022-06-23 14:36:47 +10:00
}
2023-07-08 09:02:17 -07: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-07-08 09:02:17 -07:00
private static CargoOrderData GetOrderData ( CargoConsoleAddOrderMessage args , CargoProductPrototype cargoProduct , int id )
2022-06-23 14:36:47 +10:00
{
2023-06-16 05:19:02 +00:00
return new CargoOrderData ( id , cargoProduct . Product , cargoProduct . PointCost , args . Amount , args . Requester , args . Reason ) ;
2022-06-23 14:36:47 +10:00
}
2023-07-08 09:02:17 -07:00
public static 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
{
2023-07-08 09:02:17 -07: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>
2023-07-08 09:02:17 -07:00
private void UpdateOrders ( EntityUid dbUid , StationCargoOrderDatabaseComponent _ )
2022-06-23 14:36:47 +10:00
{
// Order added so all consoles need updating.
2023-03-23 16:10:49 +11:00
var orderQuery = AllEntityQuery < CargoOrderConsoleComponent > ( ) ;
2023-07-08 09:02:17 -07:00
while ( orderQuery . MoveNext ( out var uid , out var _ ) )
2022-06-23 14:36:47 +10:00
{
2023-03-23 16:10:49 +11:00
var station = _station . GetOwningStation ( uid ) ;
2023-07-08 09:02:17 -07:00
if ( station ! = dbUid )
2023-03-23 16:10:49 +11:00
continue ;
2022-06-23 14:36:47 +10:00
2023-07-08 09:02:17 -07:00
UpdateOrderState ( uid , station ) ;
2022-06-23 14:36:47 +10:00
}
2023-03-23 16:10:49 +11:00
var consoleQuery = AllEntityQuery < CargoShuttleConsoleComponent > ( ) ;
2023-07-08 09:02:17 -07:00
while ( consoleQuery . MoveNext ( out var uid , out var _ ) )
2022-06-23 14:36:47 +10:00
{
2023-03-23 16:10:49 +11:00
var station = _station . GetOwningStation ( uid ) ;
2023-07-08 09:02:17 -07:00
if ( station ! = dbUid )
2023-03-23 16:10:49 +11:00
continue ;
2022-06-23 14:36:47 +10:00
2023-04-09 11:59:41 +10:00
UpdateShuttleState ( uid , station ) ;
2022-06-23 14:36:47 +10:00
}
}
2023-07-08 09:02:17 -07:00
public bool AddAndApproveOrder (
EntityUid dbUid ,
string spawnId ,
int cost ,
int qty ,
string sender ,
string description ,
string dest ,
StationCargoOrderDatabaseComponent component
)
2023-05-23 09:55:27 +12:00
{
2023-06-16 05:19:02 +00:00
DebugTools . Assert ( _protoMan . HasIndex < EntityPrototype > ( spawnId ) ) ;
2023-05-23 09:55:27 +12:00
// Make an order
var id = GenerateOrderId ( component ) ;
2023-06-16 05:19:02 +00:00
var order = new CargoOrderData ( id , spawnId , cost , qty , sender , description ) ;
2023-05-23 09:55:27 +12:00
// Approve it now
2023-06-16 05:19:02 +00:00
order . SetApproverData ( dest , sender ) ;
2023-05-23 09:55:27 +12:00
// Log order addition
_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
2023-07-08 09:02:17 -07:00
return TryAddOrder ( dbUid , order , component ) ;
2023-05-23 09:55:27 +12:00
}
2023-07-08 09:02:17 -07:00
private bool TryAddOrder ( EntityUid dbUid , CargoOrderData data , StationCargoOrderDatabaseComponent component )
2022-06-23 14:36:47 +10:00
{
2023-03-05 04:27:30 +00:00
component . Orders . Add ( data ) ;
2023-07-08 09:02:17 -07:00
UpdateOrders ( dbUid , component ) ;
2022-06-23 14:36:47 +10:00
return true ;
}
2023-07-08 09:02:17 -07:00
private static int GenerateOrderId ( StationCargoOrderDatabaseComponent orderDB )
2022-06-23 14:36:47 +10:00
{
2023-05-23 09:55:27 +12:00
// We need an arbitrary unique ID to identify orders, since they may
2023-03-05 04:27:30 +00:00
// want to be cancelled later.
return + + orderDB . NumOrdersCreated ;
2022-06-23 14:36:47 +10:00
}
2023-07-08 09:02:17 -07:00
public void RemoveOrder ( EntityUid dbUid , int index , StationCargoOrderDatabaseComponent orderDB )
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 ) ;
}
2023-07-08 09:02:17 -07:00
UpdateOrders ( dbUid , 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-07-08 09:02:17 -07:00
private static bool PopFrontOrder ( StationCargoOrderDatabaseComponent orderDB , [ NotNullWhen ( true ) ] out CargoOrderData ? orderOut )
2023-03-05 04:27:30 +00:00
{
var orderIdx = orderDB . Orders . FindIndex ( order = > order . Approved ) ;
if ( orderIdx = = - 1 )
{
orderOut = null ;
return false ;
}
orderOut = orderDB . Orders [ orderIdx ] ;
orderOut . NumDispatched + + ;
2023-07-08 09:02:17 -07:00
if ( orderOut . NumDispatched > = orderOut . OrderQuantity )
2023-03-05 04:27:30 +00:00
{
// 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
2023-06-16 05:19:02 +00:00
var item = Spawn ( order . ProductId , whereToPutIt ) ;
2023-03-05 04:27:30 +00:00
// 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 ) ) ;
2023-07-08 09:02:17 -07:00
_metaSystem . SetEntityName ( printed , val ) ;
2023-03-05 04:27:30 +00:00
_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
2023-07-08 09:02:17 -07:00
private StationBankAccountComponent ? GetBankAccount ( EntityUid uid , CargoOrderConsoleComponent _ )
2022-06-23 14:36:47 +10:00
{
2023-07-08 09:02:17 -07:00
var station = _station . GetOwningStation ( uid ) ;
2022-06-23 14:36:47 +10:00
TryComp < StationBankAccountComponent > ( station , out var bankComponent ) ;
return bankComponent ;
}
2023-07-08 09:02:17 -07:00
private bool TryGetOrderDatabase ( EntityUid uid , [ MaybeNullWhen ( false ) ] out EntityUid ? dbUid , [ MaybeNullWhen ( false ) ] out StationCargoOrderDatabaseComponent dbComp , CargoOrderConsoleComponent _ )
2022-06-23 14:36:47 +10:00
{
2023-07-08 09:02:17 -07:00
dbUid = _station . GetOwningStation ( uid ) ;
return TryComp ( dbUid , out dbComp ) ;
2022-06-23 14:36:47 +10:00
}
#endregion
}
}