Clean up store system (#28463)

This commit is contained in:
Nemanja
2024-06-01 11:29:17 -04:00
committed by GitHub
parent 19be94c9ea
commit be6f55a090
26 changed files with 127 additions and 209 deletions

View File

@@ -1,6 +1,7 @@
using Content.Shared.Store; using Content.Shared.Store;
using JetBrains.Annotations; using JetBrains.Annotations;
using System.Linq; using System.Linq;
using Content.Shared.Store.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Client.Store.Ui; namespace Content.Client.Store.Ui;
@@ -13,9 +14,6 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
[ViewVariables] [ViewVariables]
private StoreMenu? _menu; private StoreMenu? _menu;
[ViewVariables]
private string _windowName = Loc.GetString("store-ui-default-title");
[ViewVariables] [ViewVariables]
private string _search = string.Empty; private string _search = string.Empty;
@@ -28,7 +26,9 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
protected override void Open() protected override void Open()
{ {
_menu = new StoreMenu(_windowName); _menu = new StoreMenu();
if (EntMan.TryGetComponent<StoreComponent>(Owner, out var store))
_menu.Title = Loc.GetString(store.Name);
_menu.OpenCentered(); _menu.OpenCentered();
_menu.OnClose += Close; _menu.OnClose += Close;
@@ -64,25 +64,15 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
{ {
base.UpdateState(state); base.UpdateState(state);
if (_menu == null)
return;
switch (state) switch (state)
{ {
case StoreUpdateState msg: case StoreUpdateState msg:
_listings = msg.Listings; _listings = msg.Listings;
_menu.UpdateBalance(msg.Balance); _menu?.UpdateBalance(msg.Balance);
UpdateListingsWithSearchFilter(); UpdateListingsWithSearchFilter();
_menu.SetFooterVisibility(msg.ShowFooter); _menu?.SetFooterVisibility(msg.ShowFooter);
_menu.UpdateRefund(msg.AllowRefund); _menu?.UpdateRefund(msg.AllowRefund);
break;
case StoreInitializeState msg:
_windowName = msg.Name;
if (_menu != null && _menu.Window != null)
{
_menu.Window.Title = msg.Name;
}
break; break;
} }
} }

View File

@@ -32,7 +32,7 @@ public sealed partial class StoreMenu : DefaultWindow
private List<ListingData> _cachedListings = new(); private List<ListingData> _cachedListings = new();
public StoreMenu(string name) public StoreMenu()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
@@ -40,9 +40,6 @@ public sealed partial class StoreMenu : DefaultWindow
WithdrawButton.OnButtonDown += OnWithdrawButtonDown; WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
RefundButton.OnButtonDown += OnRefundButtonDown; RefundButton.OnButtonDown += OnRefundButtonDown;
SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text); SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
if (Window != null)
Window.Title = name;
} }
public void UpdateBalance(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance) public void UpdateBalance(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)

View File

@@ -25,6 +25,7 @@ using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Components;
using Content.Shared.Store.Components;
namespace Content.Server.GameTicking.Rules; namespace Content.Server.GameTicking.Rules;

View File

@@ -20,6 +20,7 @@ using Robust.Shared.Random;
using System.Numerics; using System.Numerics;
using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Store.Components;
using Robust.Shared.Collections; using Robust.Shared.Collections;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;

View File

@@ -8,6 +8,7 @@ using Content.Server.PDA.Ringer;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Server.Store.Components; using Content.Server.Store.Components;
using Content.Server.Store.Systems; using Content.Server.Store.Systems;
using Content.Server.Traitor.Uplink;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.CartridgeLoader; using Content.Shared.CartridgeLoader;
using Content.Shared.Chat; using Content.Shared.Chat;
@@ -15,6 +16,7 @@ using Content.Shared.Light;
using Content.Shared.Light.Components; using Content.Shared.Light.Components;
using Content.Shared.Light.EntitySystems; using Content.Shared.Light.EntitySystems;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Store.Components;
using Robust.Server.Containers; using Robust.Server.Containers;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Containers; using Robust.Shared.Containers;
@@ -152,7 +154,7 @@ namespace Content.Server.PDA
var address = GetDeviceNetAddress(uid); var address = GetDeviceNetAddress(uid);
var hasInstrument = HasComp<InstrumentComponent>(uid); var hasInstrument = HasComp<InstrumentComponent>(uid);
var showUplink = HasComp<StoreComponent>(uid) && IsUnlocked(uid); var showUplink = HasComp<UplinkComponent>(uid) && IsUnlocked(uid);
UpdateStationName(uid, pda); UpdateStationName(uid, pda);
UpdateAlertLevel(uid, pda); UpdateAlertLevel(uid, pda);
@@ -237,8 +239,8 @@ namespace Content.Server.PDA
return; return;
// check if its locked again to prevent malicious clients opening locked uplinks // check if its locked again to prevent malicious clients opening locked uplinks
if (TryComp<StoreComponent>(uid, out var store) && IsUnlocked(uid)) if (HasComp<UplinkComponent>(uid) && IsUnlocked(uid))
_store.ToggleUi(msg.Actor, uid, store); _store.ToggleUi(msg.Actor, uid);
} }
private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaLockUplinkMessage msg) private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaLockUplinkMessage msg)

View File

@@ -6,6 +6,7 @@ using Content.Shared.PDA;
using Content.Shared.PDA.Ringer; using Content.Shared.PDA.Ringer;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Store.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Network; using Robust.Shared.Network;

View File

@@ -17,6 +17,7 @@ using Content.Shared.Popups;
using Content.Shared.Revenant; using Content.Shared.Revenant;
using Content.Shared.Revenant.Components; using Content.Shared.Revenant.Components;
using Content.Shared.StatusEffect; using Content.Shared.StatusEffect;
using Content.Shared.Store.Components;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;

View File

@@ -1,6 +1,7 @@
using Content.Server.Store.Components; using Content.Server.Store.Components;
using Content.Server.Store.Systems; using Content.Server.Store.Systems;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Store.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Server.Store.Conditions; namespace Content.Server.Store.Conditions;

View File

@@ -1,7 +1,9 @@
using System.Linq;
using Content.Server.Store.Components; using Content.Server.Store.Components;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Store.Components;
using Robust.Shared.Console; using Robust.Shared.Console;
namespace Content.Server.Store.Systems; namespace Content.Server.Store.Systems;
@@ -58,7 +60,7 @@ public sealed partial class StoreSystem
if (args.Length == 2 && NetEntity.TryParse(args[0], out var uidNet) && TryGetEntity(uidNet, out var uid)) if (args.Length == 2 && NetEntity.TryParse(args[0], out var uidNet) && TryGetEntity(uidNet, out var uid))
{ {
if (TryComp<StoreComponent>(uid, out var store)) if (TryComp<StoreComponent>(uid, out var store))
return CompletionResult.FromHintOptions(store.CurrencyWhitelist, "<currency prototype>"); return CompletionResult.FromHintOptions(store.CurrencyWhitelist.Select(p => p.ToString()), "<currency prototype>");
} }
return CompletionResult.Empty; return CompletionResult.Empty;

View File

@@ -1,5 +1,6 @@
using Content.Server.Store.Components;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Store.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Store.Systems; namespace Content.Server.Store.Systems;
@@ -80,7 +81,11 @@ public sealed partial class StoreSystem
/// <param name="categories">What categories to filter by.</param> /// <param name="categories">What categories to filter by.</param>
/// <param name="storeEntity">The physial entity of the store. Can be null.</param> /// <param name="storeEntity">The physial entity of the store. Can be null.</param>
/// <returns>The available listings.</returns> /// <returns>The available listings.</returns>
public IEnumerable<ListingData> GetAvailableListings(EntityUid buyer, HashSet<ListingData>? listings, HashSet<string> categories, EntityUid? storeEntity = null) public IEnumerable<ListingData> GetAvailableListings(
EntityUid buyer,
HashSet<ListingData>? listings,
HashSet<ProtoId<StoreCategoryPrototype>> categories,
EntityUid? storeEntity = null)
{ {
listings ??= GetAllListings(); listings ??= GetAllListings();
@@ -117,7 +122,7 @@ public sealed partial class StoreSystem
/// <param name="listing">The listing itself.</param> /// <param name="listing">The listing itself.</param>
/// <param name="categories">The categories to check through.</param> /// <param name="categories">The categories to check through.</param>
/// <returns>If the listing was present in one of the categories.</returns> /// <returns>If the listing was present in one of the categories.</returns>
public bool ListingHasCategory(ListingData listing, HashSet<string> categories) public bool ListingHasCategory(ListingData listing, HashSet<ProtoId<StoreCategoryPrototype>> categories)
{ {
foreach (var cat in categories) foreach (var cat in categories)
{ {

View File

@@ -1,4 +1,5 @@
using Content.Server.Store.Components; using Content.Server.Store.Components;
using Content.Shared.Store.Components;
using Robust.Shared.Containers; using Robust.Shared.Containers;
namespace Content.Server.Store.Systems; namespace Content.Server.Store.Systems;

View File

@@ -10,6 +10,7 @@ using Content.Shared.FixedPoint;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Store.Components;
using Content.Shared.UserInterface; using Content.Shared.UserInterface;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
@@ -82,16 +83,11 @@ public sealed partial class StoreSystem
/// <param name="user">The person who if opening the store ui. Listings are filtered based on this.</param> /// <param name="user">The person who if opening the store ui. Listings are filtered based on this.</param>
/// <param name="store">The store entity itself</param> /// <param name="store">The store entity itself</param>
/// <param name="component">The store component being refreshed.</param> /// <param name="component">The store component being refreshed.</param>
/// <param name="ui"></param>
public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null) public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null)
{ {
if (!Resolve(store, ref component)) if (!Resolve(store, ref component))
return; return;
// TODO: Why is the state not being set unless this?
if (!_ui.HasUi(store, StoreUiKey.Key))
return;
//this is the person who will be passed into logic for all listing filtering. //this is the person who will be passed into logic for all listing filtering.
if (user != null) //if we have no "buyer" for this update, then don't update the listings if (user != null) //if we have no "buyer" for this update, then don't update the listings
{ {
@@ -259,7 +255,8 @@ public sealed partial class StoreSystem
} }
//log dat shit. //log dat shit.
_admin.Add(LogType.StorePurchase, LogImpact.Low, _admin.Add(LogType.StorePurchase,
LogImpact.Low,
$"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}"); $"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}");
listing.PurchaseAmount++; //track how many times something has been purchased listing.PurchaseAmount++; //track how many times something has been purchased

View File

@@ -5,11 +5,10 @@ using Content.Shared.Implants.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Store;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using System.Linq; using System.Linq;
using Content.Shared.Store.Components;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Store.Systems; namespace Content.Server.Store.Systems;
@@ -44,7 +43,6 @@ public sealed partial class StoreSystem : EntitySystem
private void OnMapInit(EntityUid uid, StoreComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, StoreComponent component, MapInitEvent args)
{ {
RefreshAllListings(component); RefreshAllListings(component);
InitializeFromPreset(component.Preset, uid, component);
component.StartingMap = Transform(uid).MapUid; component.StartingMap = Transform(uid).MapUid;
} }
@@ -54,7 +52,6 @@ public sealed partial class StoreSystem : EntitySystem
if (MetaData(uid).EntityLifeStage == EntityLifeStage.MapInitialized) if (MetaData(uid).EntityLifeStage == EntityLifeStage.MapInitialized)
{ {
RefreshAllListings(component); RefreshAllListings(component);
InitializeFromPreset(component.Preset, uid, component);
} }
var ev = new StoreAddedEvent(); var ev = new StoreAddedEvent();
@@ -167,43 +164,6 @@ public sealed partial class StoreSystem : EntitySystem
UpdateUserInterface(null, uid, store); UpdateUserInterface(null, uid, store);
return true; return true;
} }
/// <summary>
/// Initializes a store based on a preset ID
/// </summary>
/// <param name="preset">The ID of a store preset prototype</param>
/// <param name="uid"></param>
/// <param name="component">The store being initialized</param>
public void InitializeFromPreset(string? preset, EntityUid uid, StoreComponent component)
{
if (preset == null)
return;
if (!_proto.TryIndex<StorePresetPrototype>(preset, out var proto))
return;
InitializeFromPreset(proto, uid, component);
}
/// <summary>
/// Initializes a store based on a given preset
/// </summary>
/// <param name="preset">The StorePresetPrototype</param>
/// <param name="uid"></param>
/// <param name="component">The store being initialized</param>
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<string, FixedPoint2>() && preset.InitialBalance != null) //if we don't have a value stored, use the preset
TryAddCurrency(preset.InitialBalance, uid, component);
if (_ui.HasUi(uid, StoreUiKey.Key))
{
_ui.SetUiState(uid, StoreUiKey.Key, new StoreInitializeState(preset.StoreName));
}
}
} }
public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs

View File

@@ -1,6 +1,3 @@
using Content.Shared.Store;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Traitor.Uplink.SurplusBundle; namespace Content.Server.Traitor.Uplink.SurplusBundle;
/// <summary> /// <summary>
@@ -12,14 +9,6 @@ public sealed partial class SurplusBundleComponent : Component
/// <summary> /// <summary>
/// Total price of all content inside bundle. /// Total price of all content inside bundle.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadOnly)] [DataField]
[DataField("totalPrice")]
public int TotalPrice = 20; public int TotalPrice = 20;
/// <summary>
/// The preset that will be used to get all the listings.
/// Currently just defaults to the basic uplink.
/// </summary>
[DataField("storePreset", customTypeSerializer: typeof(PrototypeIdSerializer<StorePresetPrototype>))]
public string StorePreset = "StorePresetUplink";
} }

View File

@@ -3,76 +3,67 @@ using Content.Server.Storage.EntitySystems;
using Content.Server.Store.Systems; using Content.Server.Store.Systems;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Store; using Content.Shared.Store;
using Robust.Shared.Prototypes; using Content.Shared.Store.Components;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Traitor.Uplink.SurplusBundle; namespace Content.Server.Traitor.Uplink.SurplusBundle;
public sealed class SurplusBundleSystem : EntitySystem public sealed class SurplusBundleSystem : EntitySystem
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!;
[Dependency] private readonly StoreSystem _store = default!; [Dependency] private readonly StoreSystem _store = default!;
private ListingData[] _listings = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SurplusBundleComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<SurplusBundleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SurplusBundleComponent, ComponentInit>(OnInit);
}
private void OnInit(EntityUid uid, SurplusBundleComponent component, ComponentInit args)
{
var storePreset = _prototypeManager.Index<StorePresetPrototype>(component.StorePreset);
_listings = _store.GetAvailableListings(uid, null, storePreset.Categories).ToArray();
Array.Sort(_listings, (a, b) => (int) (b.Cost.Values.Sum() - a.Cost.Values.Sum())); //this might get weird with multicurrency but don't think about it
} }
private void OnMapInit(EntityUid uid, SurplusBundleComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, SurplusBundleComponent component, MapInitEvent args)
{ {
FillStorage(uid, component); if (!TryComp<StoreComponent>(uid, out var store))
}
private void FillStorage(EntityUid uid, SurplusBundleComponent? component = null)
{
if (!Resolve(uid, ref component))
return; return;
var cords = Transform(uid).Coordinates; FillStorage((uid, component, store));
}
var content = GetRandomContent(component.TotalPrice); private void FillStorage(Entity<SurplusBundleComponent, StoreComponent> ent)
{
var cords = Transform(ent).Coordinates;
var content = GetRandomContent(ent);
foreach (var item in content) foreach (var item in content)
{ {
var ent = EntityManager.SpawnEntity(item.ProductEntity, cords); var dode = Spawn(item.ProductEntity, cords);
_entityStorage.Insert(ent, uid); _entityStorage.Insert(dode, ent);
} }
} }
// wow, is this leetcode reference? // wow, is this leetcode reference?
private List<ListingData> GetRandomContent(FixedPoint2 targetCost) private List<ListingData> GetRandomContent(Entity<SurplusBundleComponent, StoreComponent> ent)
{ {
var ret = new List<ListingData>(); var ret = new List<ListingData>();
if (_listings.Length == 0)
var listings = _store.GetAvailableListings(ent, null, ent.Comp2.Categories)
.OrderBy(p => p.Cost.Values.Sum())
.ToList();
if (listings.Count == 0)
return ret; return ret;
var totalCost = FixedPoint2.Zero; var totalCost = FixedPoint2.Zero;
var index = 0; var index = 0;
while (totalCost < targetCost) while (totalCost < ent.Comp1.TotalPrice)
{ {
// All data is sorted in price descending order // All data is sorted in price descending order
// Find new item with the lowest acceptable price // Find new item with the lowest acceptable price
// All expansive items will be before index, all acceptable after // All expansive items will be before index, all acceptable after
var remainingBudget = targetCost - totalCost; var remainingBudget = ent.Comp1.TotalPrice - totalCost;
while (_listings[index].Cost.Values.Sum() > remainingBudget) while (listings[index].Cost.Values.Sum() > remainingBudget)
{ {
index++; index++;
if (index >= _listings.Length) if (index >= listings.Count)
{ {
// Looks like no cheap items left // Looks like no cheap items left
// It shouldn't be case for ss14 content // It shouldn't be case for ss14 content
@@ -82,8 +73,8 @@ public sealed class SurplusBundleSystem : EntitySystem
} }
// Select random listing and add into crate // Select random listing and add into crate
var randomIndex = _random.Next(index, _listings.Length); var randomIndex = _random.Next(index, listings.Count);
var randomItem = _listings[randomIndex]; var randomItem = listings[randomIndex];
ret.Add(randomItem); ret.Add(randomItem);
totalCost += randomItem.Cost.Values.Sum(); totalCost += randomItem.Cost.Values.Sum();
} }

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Traitor.Uplink;
/// <summary>
/// This is used for identifying something as a hidden uplink and showing the UI.
/// </summary>
[RegisterComponent]
public sealed partial class UplinkComponent : Component;

View File

@@ -5,6 +5,7 @@ using Content.Shared.PDA;
using Content.Server.Store.Components; using Content.Server.Store.Components;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Store.Components;
namespace Content.Server.Traitor.Uplink namespace Content.Server.Traitor.Uplink
{ {
@@ -17,18 +18,6 @@ namespace Content.Server.Traitor.Uplink
[ValidatePrototypeId<CurrencyPrototype>] [ValidatePrototypeId<CurrencyPrototype>]
public const string TelecrystalCurrencyPrototype = "Telecrystal"; public const string TelecrystalCurrencyPrototype = "Telecrystal";
/// <summary>
/// Gets the amount of TC on an "uplink"
/// Mostly just here for legacy systems based on uplink.
/// </summary>
/// <param name="component"></param>
/// <returns>the amount of TC</returns>
public int GetTCBalance(StoreComponent component)
{
FixedPoint2? tcBalance = component.Balance.GetValueOrDefault(TelecrystalCurrencyPrototype);
return tcBalance?.Int() ?? 0;
}
/// <summary> /// <summary>
/// Adds an uplink to the target /// Adds an uplink to the target
/// </summary> /// </summary>
@@ -37,7 +26,7 @@ namespace Content.Server.Traitor.Uplink
/// <param name="uplinkPresetId">The id of the storepreset</param> /// <param name="uplinkPresetId">The id of the storepreset</param>
/// <param name="uplinkEntity">The entity that will actually have the uplink functionality. Defaults to the PDA if null.</param> /// <param name="uplinkEntity">The entity that will actually have the uplink functionality. Defaults to the PDA if null.</param>
/// <returns>Whether or not the uplink was added successfully</returns> /// <returns>Whether or not the uplink was added successfully</returns>
public bool AddUplink(EntityUid user, FixedPoint2? balance, string uplinkPresetId = "StorePresetUplink", EntityUid? uplinkEntity = null) public bool AddUplink(EntityUid user, FixedPoint2? balance, EntityUid? uplinkEntity = null)
{ {
// Try to find target item // Try to find target item
if (uplinkEntity == null) if (uplinkEntity == null)
@@ -47,11 +36,10 @@ namespace Content.Server.Traitor.Uplink
return false; return false;
} }
EnsureComp<UplinkComponent>(uplinkEntity.Value);
var store = EnsureComp<StoreComponent>(uplinkEntity.Value); var store = EnsureComp<StoreComponent>(uplinkEntity.Value);
_store.InitializeFromPreset(uplinkPresetId, uplinkEntity.Value, store);
store.AccountOwner = user; store.AccountOwner = user;
store.Balance.Clear(); store.Balance.Clear();
if (balance != null) if (balance != null)
{ {
store.Balance.Clear(); store.Balance.Clear();

View File

@@ -1,46 +1,41 @@
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Store;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
namespace Content.Server.Store.Components; namespace Content.Shared.Store.Components;
/// <summary> /// <summary>
/// This component manages a store which players can use to purchase different listings /// This component manages a store which players can use to purchase different listings
/// through the ui. The currency, listings, and categories are defined in yaml. /// through the ui. The currency, listings, and categories are defined in yaml.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class StoreComponent : Component public sealed partial class StoreComponent : Component
{ {
/// <summary> [DataField]
/// The default preset for the store. Is overriden by default values specified on the component. public LocId Name = "store-ui-default-title";
/// </summary>
[DataField("preset", customTypeSerializer: typeof(PrototypeIdSerializer<StorePresetPrototype>))]
public string? Preset;
/// <summary> /// <summary>
/// All the listing categories that are available on this store. /// All the listing categories that are available on this store.
/// The available listings are partially based on the categories. /// The available listings are partially based on the categories.
/// </summary> /// </summary>
[DataField("categories", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<StoreCategoryPrototype>))] [DataField]
public HashSet<string> Categories = new(); public HashSet<ProtoId<StoreCategoryPrototype>> Categories = new();
/// <summary> /// <summary>
/// The total amount of currency that can be used in the store. /// The total amount of currency that can be used in the store.
/// The string represents the ID of te currency prototype, where the /// The string represents the ID of te currency prototype, where the
/// float is that amount. /// float is that amount.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("balance", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))] [DataField]
public Dictionary<string, FixedPoint2> Balance = new(); public Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> Balance = new();
/// <summary> /// <summary>
/// The list of currencies that can be inserted into this store. /// The list of currencies that can be inserted into this store.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadOnly), DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<CurrencyPrototype>))] [DataField]
public HashSet<string> CurrencyWhitelist = new(); public HashSet<ProtoId<CurrencyPrototype>> CurrencyWhitelist = new();
/// <summary> /// <summary>
/// The person who "owns" the store/account. Used if you want the listings to be fixed /// The person who "owns" the store/account. Used if you want the listings to be fixed
@@ -52,6 +47,7 @@ public sealed partial class StoreComponent : Component
/// <summary> /// <summary>
/// All listings, including those that aren't available to the buyer /// All listings, including those that aren't available to the buyer
/// </summary> /// </summary>
[DataField]
public HashSet<ListingData> Listings = new(); public HashSet<ListingData> Listings = new();
/// <summary> /// <summary>
@@ -70,7 +66,7 @@ public sealed partial class StoreComponent : Component
/// The total balance spent in this store. Used for refunds. /// The total balance spent in this store. Used for refunds.
/// </summary> /// </summary>
[ViewVariables, DataField] [ViewVariables, DataField]
public Dictionary<string, FixedPoint2> BalanceSpent = new(); public Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> BalanceSpent = new();
/// <summary> /// <summary>
/// Controls if the store allows refunds /// Controls if the store allows refunds
@@ -95,7 +91,7 @@ public sealed partial class StoreComponent : Component
/// <summary> /// <summary>
/// The sound played to the buyer when a purchase is succesfully made. /// The sound played to the buyer when a purchase is succesfully made.
/// </summary> /// </summary>
[DataField("buySuccessSound")] [DataField]
public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg"); public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg");
#endregion #endregion
} }

View File

@@ -30,20 +30,6 @@ public sealed class StoreUpdateState : BoundUserInterfaceState
} }
} }
/// <summary>
/// initializes miscellaneous data about the store.
/// </summary>
[Serializable, NetSerializable]
public sealed class StoreInitializeState : BoundUserInterfaceState
{
public readonly string Name;
public StoreInitializeState(string name)
{
Name = name;
}
}
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessage public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessage
{ {

View File

@@ -8,3 +8,6 @@ store-ui-traitor-warning = Operatives must lock their uplinks after use to avoid
store-withdraw-button-ui = Withdraw {$currency} store-withdraw-button-ui = Withdraw {$currency}
store-ui-button-out-of-stock = {""} (Out of Stock) store-ui-button-out-of-stock = {""} (Out of Stock)
store-not-account-owner = This {$store} is not bound to you! store-not-account-owner = This {$store} is not bound to you!
store-preset-name-uplink = Uplink
store-preset-name-spellbook = Spellbook

View File

@@ -1,6 +1,6 @@
- type: entity - type: entity
id: CrateSyndicateSurplusBundle id: CrateSyndicateSurplusBundle
parent: CrateSyndicate parent: [ CrateSyndicate, StorePresetUplink ]
name: Syndicate surplus crate name: Syndicate surplus crate
description: Contains 50 telecrystals worth of completely random Syndicate items. It can be useless junk or really good. description: Contains 50 telecrystals worth of completely random Syndicate items. It can be useless junk or really good.
components: components:
@@ -24,7 +24,7 @@
- type: entity - type: entity
id: CrateSyndicateSuperSurplusBundle id: CrateSyndicateSuperSurplusBundle
parent: CrateSyndicate parent: [ CrateSyndicate, StorePresetUplink ]
name: Syndicate super surplus crate name: Syndicate super surplus crate
description: Contains 125 telecrystals worth of completely random Syndicate items. description: Contains 125 telecrystals worth of completely random Syndicate items.
components: components:

View File

@@ -1,6 +1,6 @@
- type: entity - type: entity
abstract: true abstract: true
parent: BaseItem parent: [ BaseItem, StorePresetUplink ] #PDA's have uplinks so they have to inherit the data.
id: BasePDA id: BasePDA
name: PDA name: PDA
description: Personal Data Assistant. description: Personal Data Assistant.

View File

@@ -25,7 +25,7 @@
id: WizardsGrimoire id: WizardsGrimoire
name: wizards grimoire name: wizards grimoire
suffix: Wizard suffix: Wizard
parent: BaseItem parent: [ BaseItem, StorePresetSpellbook ]
components: components:
- type: Sprite - type: Sprite
sprite: Objects/Misc/books.rsi sprite: Objects/Misc/books.rsi
@@ -46,7 +46,6 @@
- type: Store - type: Store
refundAllowed: true refundAllowed: true
ownerOnly: true # get your own tome! ownerOnly: true # get your own tome!
preset: StorePresetSpellbook
balance: balance:
WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are
@@ -55,12 +54,11 @@
id: WizardsGrimoireNoRefund id: WizardsGrimoireNoRefund
name: wizards grimoire name: wizards grimoire
suffix: Wizard, No Refund suffix: Wizard, No Refund
parent: WizardsGrimoire parent: [ WizardsGrimoire, StorePresetSpellbook ]
components: components:
- type: Store - type: Store
refundAllowed: false refundAllowed: false
ownerOnly: true # get your own tome! ownerOnly: true # get your own tome!
preset: StorePresetSpellbook
balance: balance:
WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are

View File

@@ -143,7 +143,7 @@
- Cuffable # useless if you cant be cuffed - Cuffable # useless if you cant be cuffed
- type: entity - type: entity
parent: BaseSubdermalImplant parent: [ BaseSubdermalImplant, StorePresetUplink ]
id: UplinkImplant id: UplinkImplant
name: uplink implant name: uplink implant
description: This implant lets the user access a hidden Syndicate uplink at will. description: This implant lets the user access a hidden Syndicate uplink at will.
@@ -155,7 +155,6 @@
components: components:
- Hands # prevent mouse buying grenade penguin since its not telepathic - Hands # prevent mouse buying grenade penguin since its not telepathic
- type: Store - type: Store
preset: StorePresetUplink
balance: balance:
Telecrystal: 0 Telecrystal: 0
- type: UserInterface - type: UserInterface

View File

@@ -48,7 +48,7 @@
# Uplinks # Uplinks
- type: entity - type: entity
parent: BaseItem parent: [ BaseItem, StorePresetUplink ]
id: BaseUplinkRadio id: BaseUplinkRadio
name: syndicate uplink name: syndicate uplink
description: Suspiciously looking old radio... description: Suspiciously looking old radio...
@@ -68,7 +68,6 @@
- type: ActivatableUI - type: ActivatableUI
key: enum.StoreUiKey.Key key: enum.StoreUiKey.Key
- type: Store - type: Store
preset: StorePresetUplink
balance: balance:
Telecrystal: 0 Telecrystal: 0
@@ -78,7 +77,6 @@
suffix: 20 TC suffix: 20 TC
components: components:
- type: Store - type: Store
preset: StorePresetUplink
balance: balance:
Telecrystal: 20 Telecrystal: 20
@@ -88,7 +86,6 @@
suffix: 25 TC suffix: 25 TC
components: components:
- type: Store - type: Store
preset: StorePresetUplink
balance: balance:
Telecrystal: 25 Telecrystal: 25
@@ -99,7 +96,6 @@
suffix: 40 TC, NukeOps suffix: 40 TC, NukeOps
components: components:
- type: Store - type: Store
preset: StorePresetUplink
balance: balance:
Telecrystal: 40 Telecrystal: 40
- type: Tag - type: Tag
@@ -112,7 +108,6 @@
suffix: 60 TC, LoneOps suffix: 60 TC, LoneOps
components: components:
- type: Store - type: Store
preset: StorePresetUplink
balance: balance:
Telecrystal: 60 Telecrystal: 60
- type: Tag - type: Tag
@@ -125,6 +120,5 @@
suffix: DEBUG suffix: DEBUG
components: components:
- type: Store - type: Store
preset: StorePresetUplink
balance: balance:
Telecrystal: 99999 Telecrystal: 99999

View File

@@ -1,6 +1,9 @@
- type: storePreset - type: entity
id: StorePresetUplink id: StorePresetUplink
storeName: Uplink abstract: true
components:
- type: Store
name: store-preset-name-uplink
categories: categories:
- UplinkWeaponry - UplinkWeaponry
- UplinkAmmo - UplinkAmmo
@@ -15,10 +18,15 @@
- UplinkPointless - UplinkPointless
currencyWhitelist: currencyWhitelist:
- Telecrystal - Telecrystal
balance:
Telecrystal: 0
- type: storePreset - type: entity
id: StorePresetSpellbook id: StorePresetSpellbook
storeName: Spellbook abstract: true
components:
- type: Store
name: store-preset-name-spellbook
categories: categories:
- SpellbookOffensive #Fireball, Rod Form - SpellbookOffensive #Fireball, Rod Form
- SpellbookDefensive #Magic Missile, Wall of Force - SpellbookDefensive #Magic Missile, Wall of Force