2022-08-17 00:34:25 -04:00
using Content.Server.Store.Components ;
2024-02-01 11:45:24 +03:00
using Content.Shared.UserInterface ;
2022-08-17 00:34:25 -04:00
using Content.Shared.FixedPoint ;
2023-04-29 09:07:50 +00:00
using Content.Shared.Implants.Components ;
2022-08-17 00:34:25 -04:00
using Content.Shared.Interaction ;
using Content.Shared.Popups ;
2023-04-29 04:41:24 +00:00
using Content.Shared.Stacks ;
2024-06-14 17:24:40 +02:00
using Content.Shared.Store.Components ;
2023-04-29 04:41:24 +00:00
using JetBrains.Annotations ;
2022-08-17 00:34:25 -04:00
using Robust.Shared.Prototypes ;
2024-03-30 19:25:36 -04:00
using Robust.Shared.Utility ;
2024-06-14 17:24:40 +02:00
using System.Linq ;
2025-02-15 11:17:29 -05:00
using Robust.Shared.Timing ;
2025-02-04 11:25:54 -05:00
using Content.Shared.Mind ;
2022-08-17 00:34:25 -04:00
namespace Content.Server.Store.Systems ;
/// <summary>
/// Manages general interactions with a store and different entities,
/// getting listings for stores, and interfacing with the store UI.
/// </summary>
public sealed partial class StoreSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default ! ;
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
2025-02-15 11:17:29 -05:00
[Dependency] private readonly IGameTiming _timing = default ! ;
2022-08-17 00:34:25 -04:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-03-30 19:25:36 -04:00
SubscribeLocalEvent < StoreComponent , ActivatableUIOpenAttemptEvent > ( OnStoreOpenAttempt ) ;
2022-08-17 00:34:25 -04:00
SubscribeLocalEvent < CurrencyComponent , AfterInteractEvent > ( OnAfterInteract ) ;
2023-02-12 07:39:14 -05:00
SubscribeLocalEvent < StoreComponent , BeforeActivatableUIOpenEvent > ( BeforeActivatableUiOpen ) ;
2022-08-17 00:34:25 -04:00
2023-02-12 07:39:14 -05:00
SubscribeLocalEvent < StoreComponent , MapInitEvent > ( OnMapInit ) ;
2022-08-17 00:34:25 -04:00
SubscribeLocalEvent < StoreComponent , ComponentStartup > ( OnStartup ) ;
SubscribeLocalEvent < StoreComponent , ComponentShutdown > ( OnShutdown ) ;
2023-04-29 09:07:50 +00:00
SubscribeLocalEvent < StoreComponent , OpenUplinkImplantEvent > ( OnImplantActivate ) ;
2022-08-17 00:34:25 -04:00
InitializeUi ( ) ;
2023-03-30 23:02:39 -04:00
InitializeCommand ( ) ;
2024-02-03 19:48:51 -05:00
InitializeRefund ( ) ;
2022-08-17 00:34:25 -04:00
}
2023-02-12 07:39:14 -05:00
private void OnMapInit ( EntityUid uid , StoreComponent component , MapInitEvent args )
{
RefreshAllListings ( component ) ;
2024-02-03 19:48:51 -05:00
component . StartingMap = Transform ( uid ) . MapUid ;
2023-02-12 07:39:14 -05:00
}
2022-08-17 00:34:25 -04:00
private void OnStartup ( EntityUid uid , StoreComponent component , ComponentStartup args )
{
2023-02-12 07:39:14 -05:00
// for traitors, because the StoreComponent for the PDA can be added at any time.
if ( MetaData ( uid ) . EntityLifeStage = = EntityLifeStage . MapInitialized )
{
RefreshAllListings ( component ) ;
}
var ev = new StoreAddedEvent ( ) ;
RaiseLocalEvent ( uid , ref ev , true ) ;
2022-08-17 00:34:25 -04:00
}
private void OnShutdown ( EntityUid uid , StoreComponent component , ComponentShutdown args )
{
2023-02-12 07:39:14 -05:00
var ev = new StoreRemovedEvent ( ) ;
RaiseLocalEvent ( uid , ref ev , true ) ;
2022-08-17 00:34:25 -04:00
}
2024-03-30 19:25:36 -04:00
private void OnStoreOpenAttempt ( EntityUid uid , StoreComponent component , ActivatableUIOpenAttemptEvent args )
{
if ( ! component . OwnerOnly )
return ;
2025-02-04 11:25:54 -05:00
if ( ! _mind . TryGetMind ( args . User , out var mind , out _ ) )
return ;
component . AccountOwner ? ? = mind ;
2024-03-30 19:25:36 -04:00
DebugTools . Assert ( component . AccountOwner ! = null ) ;
2025-02-04 11:25:54 -05:00
if ( component . AccountOwner = = mind )
2024-03-30 19:25:36 -04:00
return ;
_popup . PopupEntity ( Loc . GetString ( "store-not-account-owner" , ( "store" , uid ) ) , uid , args . User ) ;
args . Cancel ( ) ;
}
2022-08-17 00:34:25 -04:00
private void OnAfterInteract ( EntityUid uid , CurrencyComponent component , AfterInteractEvent args )
{
if ( args . Handled | | ! args . CanReach )
return ;
2023-06-07 15:53:11 +12:00
if ( ! TryComp < StoreComponent > ( args . Target , out var store ) )
2022-08-17 00:34:25 -04:00
return ;
2023-04-29 04:41:24 +00:00
2023-06-07 15:53:11 +12:00
var ev = new CurrencyInsertAttemptEvent ( args . User , args . Target . Value , args . Used , store ) ;
RaiseLocalEvent ( args . Target . Value , ev ) ;
if ( ev . Cancelled )
2023-04-29 04:41:24 +00:00
return ;
2022-08-17 00:34:25 -04:00
2024-09-30 01:13:22 +13:00
if ( ! TryAddCurrency ( ( uid , component ) , ( args . Target . Value , store ) ) )
return ;
2022-08-17 00:34:25 -04:00
2024-09-30 01:13:22 +13:00
args . Handled = true ;
var msg = Loc . GetString ( "store-currency-inserted" , ( "used" , args . Used ) , ( "target" , args . Target ) ) ;
_popup . PopupEntity ( msg , args . Target . Value , args . User ) ;
2022-08-17 00:34:25 -04:00
}
2023-04-29 09:07:50 +00:00
private void OnImplantActivate ( EntityUid uid , StoreComponent component , OpenUplinkImplantEvent args )
{
ToggleUi ( args . Performer , uid , component ) ;
}
2022-08-17 00:34:25 -04:00
/// <summary>
/// Gets the value from an entity's currency component.
/// Scales with stacks.
/// </summary>
2024-09-30 01:13:22 +13:00
/// <remarks>
/// If this result is intended to be used with <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Content.Server.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/>,
/// consider using <see cref="TryAddCurrency(Robust.Shared.GameObjects.Entity{Content.Server.Store.Components.CurrencyComponent?},Robust.Shared.GameObjects.Entity{Content.Shared.Store.Components.StoreComponent?})"/> instead to ensure that the currency is consumed in the process.
/// </remarks>
2023-02-12 07:39:14 -05:00
/// <param name="uid"></param>
2022-08-17 00:34:25 -04:00
/// <param name="component"></param>
/// <returns>The value of the currency</returns>
2023-02-12 07:39:14 -05:00
public Dictionary < string , FixedPoint2 > GetCurrencyValue ( EntityUid uid , CurrencyComponent component )
2022-08-17 00:34:25 -04:00
{
2023-02-12 07:39:14 -05:00
var amount = EntityManager . GetComponentOrNull < StackComponent > ( uid ) ? . Count ? ? 1 ;
2022-08-17 00:34:25 -04:00
return component . Price . ToDictionary ( v = > v . Key , p = > p . Value * amount ) ;
}
/// <summary>
2024-09-30 01:13:22 +13:00
/// Tries to add a currency to a store's balance. Note that if successful, this will consume the currency in the process.
2022-08-17 00:34:25 -04:00
/// </summary>
2024-09-30 01:13:22 +13:00
public bool TryAddCurrency ( Entity < CurrencyComponent ? > currency , Entity < StoreComponent ? > store )
2022-08-17 00:34:25 -04:00
{
2024-09-30 01:13:22 +13:00
if ( ! Resolve ( currency . Owner , ref currency . Comp ) )
return false ;
if ( ! Resolve ( store . Owner , ref store . Comp ) )
2023-02-12 07:39:14 -05:00
return false ;
2024-09-30 01:13:22 +13:00
var value = currency . Comp . Price ;
if ( TryComp ( currency . Owner , out StackComponent ? stack ) & & stack . Count ! = 1 )
{
value = currency . Comp . Price
. ToDictionary ( v = > v . Key , p = > p . Value * stack . Count ) ;
}
if ( ! TryAddCurrency ( value , store , store . Comp ) )
return false ;
// Avoid having the currency accidentally be re-used. E.g., if multiple clients try to use the currency in the
// same tick
currency . Comp . Price . Clear ( ) ;
if ( stack ! = null )
_stack . SetCount ( currency . Owner , 0 , stack ) ;
QueueDel ( currency ) ;
return true ;
2022-08-17 00:34:25 -04:00
}
/// <summary>
/// Tries to add a currency to a store's balance
/// </summary>
/// <param name="currency">The value to add to the store</param>
2023-02-12 07:39:14 -05:00
/// <param name="uid"></param>
2022-08-17 00:34:25 -04:00
/// <param name="store">The store to add it to</param>
/// <returns>Whether or not the currency was succesfully added</returns>
2023-02-12 07:39:14 -05:00
public bool TryAddCurrency ( Dictionary < string , FixedPoint2 > currency , EntityUid uid , StoreComponent ? store = null )
2022-08-17 00:34:25 -04:00
{
2023-02-12 07:39:14 -05:00
if ( ! Resolve ( uid , ref store ) )
return false ;
2022-08-17 00:34:25 -04:00
//verify these before values are modified
foreach ( var type in currency )
{
if ( ! store . CurrencyWhitelist . Contains ( type . Key ) )
return false ;
}
foreach ( var type in currency )
{
if ( ! store . Balance . TryAdd ( type . Key , type . Value ) )
store . Balance [ type . Key ] + = type . Value ;
}
2023-02-12 07:39:14 -05:00
UpdateUserInterface ( null , uid , store ) ;
2022-08-17 00:34:25 -04:00
return true ;
}
}
2023-06-07 15:53:11 +12:00
public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs
{
public readonly EntityUid User ;
public readonly EntityUid Target ;
public readonly EntityUid Used ;
public readonly StoreComponent Store ;
public CurrencyInsertAttemptEvent ( EntityUid user , EntityUid target , EntityUid used , StoreComponent store )
{
User = user ;
Target = target ;
Used = used ;
Store = store ;
}
}