diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs index a26d319920b..33407553438 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -1,7 +1,8 @@ -using System.Numerics; +using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Chat.Prototypes; using Content.Shared.Speech; +using Content.Shared.Whitelist; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.UserInterface.Controls; @@ -19,6 +20,7 @@ public sealed partial class EmotesMenu : RadialMenu [Dependency] private readonly ISharedPlayerManager _playerManager = default!; private readonly SpriteSystem _spriteSystem; + private readonly EntityWhitelistSystem _whitelistSystem; public event Action>? OnPlayEmote; @@ -28,6 +30,7 @@ public EmotesMenu() RobustXamlLoader.Load(this); _spriteSystem = _entManager.System(); + _whitelistSystem = _entManager.System(); var main = FindControl("Main"); @@ -37,8 +40,8 @@ public EmotesMenu() var player = _playerManager.LocalSession?.AttachedEntity; if (emote.Category == EmoteCategory.Invalid || emote.ChatTriggers.Count == 0 || - !(player.HasValue && (emote.Whitelist?.IsValid(player.Value, _entManager) ?? true)) || - (emote.Blacklist?.IsValid(player.Value, _entManager) ?? false)) + !(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || + _whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) continue; if (!emote.Available && diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs index 9a094361766..0c7912e0bcd 100644 --- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs +++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs @@ -2,6 +2,7 @@ using Content.Client.UserInterface.Systems.MenuBar.Widgets; using Content.Shared.Construction.Prototypes; using Content.Shared.Tag; +using Content.Shared.Whitelist; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Placement; @@ -23,6 +24,7 @@ namespace Content.Client.Construction.UI /// internal sealed class ConstructionMenuPresenter : IDisposable { + [Dependency] private readonly EntityManager _entManager = default!; [Dependency] private readonly IEntitySystemManager _systemManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlacementManager _placementManager = default!; @@ -30,6 +32,7 @@ internal sealed class ConstructionMenuPresenter : IDisposable [Dependency] private readonly IPlayerManager _playerManager = default!; private readonly IConstructionMenuView _constructionView; + private readonly EntityWhitelistSystem _whitelistSystem; private ConstructionSystem? _constructionSystem; private ConstructionPrototype? _selected; @@ -78,6 +81,7 @@ public ConstructionMenuPresenter() // This is a lot easier than a factory IoCManager.InjectDependencies(this); _constructionView = new ConstructionMenu(); + _whitelistSystem = _entManager.System(); // This is required so that if we load after the system is initialized, we can bind to it immediately if (_systemManager.TryGetEntitySystem(out var constructionSystem)) @@ -157,7 +161,7 @@ private void OnViewPopulateRecipes(object? sender, (string search, string catago if (_playerManager.LocalSession == null || _playerManager.LocalEntity == null - || (recipe.EntityWhitelist != null && !recipe.EntityWhitelist.IsValid(_playerManager.LocalEntity.Value))) + || _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value)) continue; if (!string.IsNullOrEmpty(search)) diff --git a/Content.Client/Shipyard/ShipyardConsoleSystem.cs b/Content.Client/Shipyard/ShipyardConsoleSystem.cs new file mode 100644 index 00000000000..11847f8137d --- /dev/null +++ b/Content.Client/Shipyard/ShipyardConsoleSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Shipyard; + +namespace Content.Client.Shipyard; + +public sealed class ShipyardConsoleSystem : SharedShipyardConsoleSystem; diff --git a/Content.Client/Shipyard/UI/ShipyardBoundUserInterface.cs b/Content.Client/Shipyard/UI/ShipyardBoundUserInterface.cs new file mode 100644 index 00000000000..5efe063b3b0 --- /dev/null +++ b/Content.Client/Shipyard/UI/ShipyardBoundUserInterface.cs @@ -0,0 +1,58 @@ +using Content.Shared.Access.Systems; +using Content.Shared.Shipyard; +using Content.Shared.Whitelist; +using Robust.Client.Player; +using Robust.Shared.Prototypes; + +namespace Content.Client.Shipyard.UI; + +public sealed class ShipyardConsoleBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IPlayerManager _player = default!; + + private readonly AccessReaderSystem _access; + private readonly EntityWhitelistSystem _whitelist; + + [ViewVariables] + private ShipyardConsoleMenu? _menu; + + public ShipyardConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + _access = EntMan.System(); + _whitelist = EntMan.System(); + } + + protected override void Open() + { + base.Open(); + + _menu = new ShipyardConsoleMenu(Owner, _proto, EntMan, _player, _access, _whitelist); + _menu.OpenCentered(); + _menu.OnClose += Close; + _menu.OnPurchased += Purchase; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not ShipyardConsoleState cast) + return; + + _menu?.UpdateState(cast); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + _menu?.Dispose(); + } + + private void Purchase(string id) + { + SendMessage(new ShipyardConsolePurchaseMessage(id)); + } +} diff --git a/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml new file mode 100644 index 00000000000..9eccd45b698 --- /dev/null +++ b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml @@ -0,0 +1,29 @@ + + + + diff --git a/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml.cs b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml.cs new file mode 100644 index 00000000000..4559aa6762e --- /dev/null +++ b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml.cs @@ -0,0 +1,109 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.Access.Systems; +using Content.Shared.Shipyard; +using Content.Shared.Shipyard.Prototypes; +using Content.Shared.Whitelist; +using Robust.Client.AutoGenerated; +using Robust.Client.Player; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Shipyard.UI; + +[GenerateTypedNameReferences] +public sealed partial class ShipyardConsoleMenu : FancyWindow +{ + private readonly AccessReaderSystem _access; + private readonly IPlayerManager _player; + + public event Action? OnPurchased; + + private readonly List _vessels = new(); + private readonly List _categories = new(); + + public Entity Console; + private string? _category; + + public ShipyardConsoleMenu(EntityUid console, IPrototypeManager proto, IEntityManager entMan, IPlayerManager player, AccessReaderSystem access, EntityWhitelistSystem whitelist) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + Console = (console, entMan.GetComponent(console)); + _access = access; + _player = player; + + // don't include ships that aren't allowed by whitelist, server won't accept them anyway + foreach (var vessel in proto.EnumeratePrototypes()) + { + if (whitelist.IsWhitelistPassOrNull(vessel.Whitelist, console)) + _vessels.Add(vessel); + } + _vessels.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase)); + + // only list categories in said ships + foreach (var vessel in _vessels) + { + foreach (var category in vessel.Categories) + { + if (!_categories.Contains(category)) + _categories.Add(category); + } + } + + _categories.Sort(); + // inserting here and not adding at the start so it doesn't get affected by sort + _categories.Insert(0, Loc.GetString("cargo-console-menu-populate-categories-all-text")); + PopulateCategories(); + + SearchBar.OnTextChanged += _ => PopulateProducts(); + Categories.OnItemSelected += args => + { + _category = args.Id == 0 ? null : _categories[args.Id]; + Categories.SelectId(args.Id); + PopulateProducts(); + }; + } + + /// + /// Populates the list of products that will actually be shown, using the current filters. + /// + private void PopulateProducts() + { + Vessels.RemoveAllChildren(); + + var access = _player.LocalSession?.AttachedEntity is {} player + && _access.IsAllowed(player, Console); + + var search = SearchBar.Text.Trim().ToLowerInvariant(); + foreach (var vessel in _vessels) + { + if (search.Length != 0 && !vessel.Name.ToLowerInvariant().Contains(search)) + continue; + if (_category != null && !vessel.Categories.Contains(_category)) + continue; + + var vesselEntry = new VesselRow(vessel, access); + vesselEntry.OnPurchasePressed += () => OnPurchased?.Invoke(vessel.ID); + Vessels.AddChild(vesselEntry); + } + } + + /// + /// Populates the list categories that will actually be shown, using the current filters. + /// + private void PopulateCategories() + { + Categories.Clear(); + foreach (var category in _categories) + { + Categories.AddItem(category); + } + } + + public void UpdateState(ShipyardConsoleState state) + { + BankAccountLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", state.Balance.ToString())); + PopulateProducts(); + } +} diff --git a/Content.Client/Shipyard/UI/VesselRow.xaml b/Content.Client/Shipyard/UI/VesselRow.xaml new file mode 100644 index 00000000000..eac2d3a1bde --- /dev/null +++ b/Content.Client/Shipyard/UI/VesselRow.xaml @@ -0,0 +1,16 @@ + + +