using Content.Server.Mind.Components; using Content.Server.PDA.Ringer; using Content.Server.Store.Components; using Content.Server.UserInterface; using Content.Shared.FixedPoint; using Content.Shared.Implants.Components; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Stacks; using Content.Shared.Store; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using System.Linq; namespace Content.Server.Store.Systems; /// /// Manages general interactions with a store and different entities, /// getting listings for stores, and interfacing with the store UI. /// public sealed partial class StoreSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent(BeforeActivatableUiOpen); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnImplantActivate); InitializeUi(); InitializeCommand(); } private void OnMapInit(EntityUid uid, StoreComponent component, MapInitEvent args) { RefreshAllListings(component); InitializeFromPreset(component.Preset, uid, component); } private void OnStartup(EntityUid uid, StoreComponent component, ComponentStartup args) { // for traitors, because the StoreComponent for the PDA can be added at any time. if (MetaData(uid).EntityLifeStage == EntityLifeStage.MapInitialized) { RefreshAllListings(component); InitializeFromPreset(component.Preset, uid, component); } var ev = new StoreAddedEvent(); RaiseLocalEvent(uid, ref ev, true); } private void OnShutdown(EntityUid uid, StoreComponent component, ComponentShutdown args) { var ev = new StoreRemovedEvent(); RaiseLocalEvent(uid, ref ev, true); } private void OnAfterInteract(EntityUid uid, CurrencyComponent component, AfterInteractEvent args) { if (args.Handled || !args.CanReach) return; if (args.Target == null || !TryComp(args.Target, out var store)) return; // if the store can be locked, it must be unlocked first before inserting currency var user = args.User; if (TryComp(args.Target, out var uplink) && !uplink.Unlocked) return; args.Handled = TryAddCurrency(GetCurrencyValue(uid, component), args.Target.Value, store); if (args.Handled) { var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target)); _popup.PopupEntity(msg, args.Target.Value); QueueDel(args.Used); } } private void OnImplantActivate(EntityUid uid, StoreComponent component, OpenUplinkImplantEvent args) { ToggleUi(args.Performer, uid, component); } /// /// Gets the value from an entity's currency component. /// Scales with stacks. /// /// /// /// The value of the currency public Dictionary GetCurrencyValue(EntityUid uid, CurrencyComponent component) { var amount = EntityManager.GetComponentOrNull(uid)?.Count ?? 1; return component.Price.ToDictionary(v => v.Key, p => p.Value * amount); } /// /// Tries to add a currency to a store's balance. /// /// /// /// The currency to add /// The store to add it to /// Whether or not the currency was succesfully added [PublicAPI] public bool TryAddCurrency(EntityUid currencyEnt, EntityUid storeEnt, StoreComponent? store = null, CurrencyComponent? currency = null) { if (!Resolve(currencyEnt, ref currency) || !Resolve(storeEnt, ref store)) return false; return TryAddCurrency(GetCurrencyValue(currencyEnt, currency), storeEnt, store); } /// /// Tries to add a currency to a store's balance /// /// The value to add to the store /// /// The store to add it to /// Whether or not the currency was succesfully added public bool TryAddCurrency(Dictionary currency, EntityUid uid, StoreComponent? store = null) { if (!Resolve(uid, ref store)) return false; //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; } UpdateUserInterface(null, uid, store); return true; } /// /// Initializes a store based on a preset ID /// /// The ID of a store preset prototype /// /// The store being initialized public void InitializeFromPreset(string? preset, EntityUid uid, StoreComponent component) { if (preset == null) return; if (!_proto.TryIndex(preset, out var proto)) return; InitializeFromPreset(proto, uid, component); } /// /// Initializes a store based on a given preset /// /// The StorePresetPrototype /// /// The store being initialized public void InitializeFromPreset(StorePresetPrototype preset, EntityUid uid, StoreComponent component) { component.Preset = preset.ID; component.CurrencyWhitelist.UnionWith(preset.CurrencyWhitelist); component.Categories.UnionWith(preset.Categories); if (component.Balance == new Dictionary() && preset.InitialBalance != null) //if we don't have a value stored, use the preset TryAddCurrency(preset.InitialBalance, uid, component); var ui = _ui.GetUiOrNull(uid, StoreUiKey.Key); if (ui != null) { _ui.SetUiState(ui, new StoreInitializeState(preset.StoreName)); } } }