* Can now search the uplink store interface with a searchbar.
* Search text updates no longer send server messages. Persists listings locally.
* Formatting fixes and tidying.
* Added helper method to get localised name and description (or otherwise, entity name and description) of store listing items.
* Update Content.Client/Store/Ui/StoreMenu.xaml
* Review change; moved localisation helper functions to their own class.
* Prevent thread-unsafe behaviour as-per review.
* Remove dummy boxcontainer
---------
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
using Content.Shared.Store;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using System.Linq;
-using System.Threading;
-using Serilog;
-using Timer = Robust.Shared.Timing.Timer;
+using Robust.Shared.Prototypes;
namespace Content.Client.Store.Ui;
[UsedImplicitly]
public sealed class StoreBoundUserInterface : BoundUserInterface
{
+ private IPrototypeManager _prototypeManager = default!;
+
[ViewVariables]
private StoreMenu? _menu;
[ViewVariables]
private string _windowName = Loc.GetString("store-ui-default-title");
+ [ViewVariables]
+ private string _search = "";
+
+ [ViewVariables]
+ private HashSet<ListingData> _listings = new();
+
public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
SendMessage(new StoreRequestUpdateInterfaceMessage());
};
+ _menu.SearchTextUpdated += (_, search) =>
+ {
+ _search = search.Trim().ToLowerInvariant();
+ UpdateListingsWithSearchFilter();
+ };
+
_menu.OnRefundAttempt += (_) =>
{
SendMessage(new StoreRequestRefundMessage());
switch (state)
{
case StoreUpdateState msg:
- _menu.UpdateBalance(msg.Balance);
- _menu.PopulateStoreCategoryButtons(msg.Listings);
+ _listings = msg.Listings;
- _menu.UpdateListing(msg.Listings.ToList());
+ _menu.UpdateBalance(msg.Balance);
+ UpdateListingsWithSearchFilter();
_menu.SetFooterVisibility(msg.ShowFooter);
_menu.UpdateRefund(msg.AllowRefund);
break;
_menu?.Close();
_menu?.Dispose();
}
+
+ private void UpdateListingsWithSearchFilter()
+ {
+ if (_menu == null)
+ return;
+
+ var filteredListings = new HashSet<ListingData>(_listings);
+ if (!string.IsNullOrEmpty(_search))
+ {
+ filteredListings.RemoveWhere(listingData => !ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search) &&
+ !ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search));
+ }
+ _menu.PopulateStoreCategoryButtons(filteredListings);
+ _menu.UpdateListing(filteredListings.ToList());
+ }
}
HorizontalAlignment="Right"
Text="Refund" />
</BoxContainer>
- <PanelContainer VerticalExpand="True">
+ <LineEdit Name="SearchBar" Margin="4" PlaceHolder="Search" HorizontalExpand="True"/>
+ <PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
</PanelContainer.PanelOverride>
using System.Linq;
-using System.Threading;
using Content.Client.Actions;
using Content.Client.GameTicking.Managers;
using Content.Client.Message;
private StoreWithdrawWindow? _withdrawWindow;
+ public event EventHandler<string>? SearchTextUpdated;
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
RefreshButton.OnButtonDown += OnRefreshButtonDown;
RefundButton.OnButtonDown += OnRefundButtonDown;
+ SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
if (Window != null)
Window.Title = name;
(type.Key, type.Value), type => _prototypeManager.Index<CurrencyPrototype>(type.Key));
var balanceStr = string.Empty;
- foreach (var ((type, amount),proto) in currency)
+ foreach (var ((_, amount), proto) in currency)
{
balanceStr += Loc.GetString("store-ui-balance-display", ("amount", amount),
("currency", Loc.GetString(proto.DisplayName, ("amount", 1))));
{
var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
-
// should probably chunk these out instead. to-do if this clogs the internet tubes.
// maybe read clients prototypes instead?
ClearListings();
if (!listing.Categories.Contains(CurrentCategory))
return;
- var listingName = Loc.GetString(listing.Name);
- var listingDesc = Loc.GetString(listing.Description);
+ var listingName = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager);
+ var listingDesc = ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listing, _prototypeManager);
var listingPrice = listing.Cost;
var canBuy = CanBuyListing(Balance, listingPrice);
{
if (texture == null)
texture = spriteSys.GetPrototypeIcon(listing.ProductEntity).Default;
-
- var proto = _prototypeManager.Index<EntityPrototype>(listing.ProductEntity);
- if (listingName == string.Empty)
- listingName = proto.Name;
- if (listingDesc == string.Empty)
- listingDesc = proto.Description;
}
else if (listing.ProductAction != null)
{
allCategories = allCategories.OrderBy(c => c.Priority).ToList();
+ // This will reset the Current Category selection if nothing matches the search.
+ if (allCategories.All(category => category.ID != CurrentCategory))
+ CurrentCategory = string.Empty;
+
if (CurrentCategory == string.Empty && allCategories.Count > 0)
CurrentCategory = allCategories.First().ID;
- if (allCategories.Count <= 1)
- return;
-
CategoryListContainer.Children.Clear();
+ if (allCategories.Count < 1)
+ return;
foreach (var proto in allCategories)
{
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
namespace Content.Server.Store.Systems;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private void InitializeUi()
{
//log dat shit.
_admin.Add(LogType.StorePurchase, LogImpact.Low,
- $"{ToPrettyString(buyer):player} purchased listing \"{Loc.GetString(listing.Name)}\" 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
_audio.PlayEntity(component.BuySuccessSound, msg.Session, uid); //cha-ching!
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Store;
+
+public static class ListingLocalisationHelpers
+{
+ /// <summary>
+ /// ListingData's Name field can be either a localisation string or the actual entity's name.
+ /// This function gets a localised name from the localisation string if it exists, and if not, it gets the entity's name.
+ /// If neither a localised string exists, or an associated entity name, it will return the value of the "Name" field.
+ /// </summary>
+ public static string GetLocalisedNameOrEntityName(ListingData listingData, IPrototypeManager prototypeManager)
+ {
+ bool wasLocalised = Loc.TryGetString(listingData.Name, out string? listingName);
+
+ if (!wasLocalised && listingData.ProductEntity != null)
+ {
+ var proto = prototypeManager.Index<EntityPrototype>(listingData.ProductEntity);
+ listingName = proto.Name;
+ }
+
+ return listingName ?? listingData.Name;
+ }
+
+ /// <summary>
+ /// ListingData's Description field can be either a localisation string or the actual entity's description.
+ /// This function gets a localised description from the localisation string if it exists, and if not, it gets the entity's description.
+ /// If neither a localised string exists, or an associated entity description, it will return the value of the "Description" field.
+ /// </summary>
+ public static string GetLocalisedDescriptionOrEntityDescription(ListingData listingData, IPrototypeManager prototypeManager)
+ {
+ bool wasLocalised = Loc.TryGetString(listingData.Description, out string? listingDesc);
+
+ if (!wasLocalised && listingData.ProductEntity != null)
+ {
+ var proto = prototypeManager.Index<EntityPrototype>(listingData.ProductEntity);
+ listingDesc = proto.Description;
+ }
+
+ return listingDesc ?? listingData.Description;
+ }
+}