diff --git a/Content.Client/_DV/Shipyard/ShipyardConsoleSystem.cs b/Content.Client/_DV/Shipyard/ShipyardConsoleSystem.cs new file mode 100644 index 00000000000..f8c34dbc92c --- /dev/null +++ b/Content.Client/_DV/Shipyard/ShipyardConsoleSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._DV.Shipyard; + +namespace Content.Client._DV.Shipyard; + +public sealed class ShipyardConsoleSystem : SharedShipyardConsoleSystem; diff --git a/Content.Client/_DV/Shipyard/UI/ShipyardBoundUserInterface.cs b/Content.Client/_DV/Shipyard/UI/ShipyardBoundUserInterface.cs new file mode 100644 index 00000000000..b96f2a8ec58 --- /dev/null +++ b/Content.Client/_DV/Shipyard/UI/ShipyardBoundUserInterface.cs @@ -0,0 +1,70 @@ +using Content.Shared.Access.Systems; +using Content.Shared._DV.Shipyard; +using Content.Shared.Whitelist; +using Robust.Client.Player; +using Robust.Shared.Prototypes; + +namespace Content.Client._DV.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(); + + if (_menu == null) + { + _menu = new ShipyardConsoleMenu(Owner, _proto, EntMan, _player, _access, _whitelist); + _menu.OnClose += Close; + _menu.OnPurchased += Purchase; + } + + _menu.OpenCentered(); + } + + 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) + return; + + if (_menu == null) + return; + + _menu.OnClose -= Close; + _menu.OnPurchased -= Purchase; + _menu.Close(); + _menu = null; + } + + private void Purchase(string id) + { + SendMessage(new ShipyardConsolePurchaseMessage(id)); + } +} diff --git a/Content.Client/_DV/Shipyard/UI/ShipyardConsoleMenu.xaml b/Content.Client/_DV/Shipyard/UI/ShipyardConsoleMenu.xaml new file mode 100644 index 00000000000..d5c7223f820 --- /dev/null +++ b/Content.Client/_DV/Shipyard/UI/ShipyardConsoleMenu.xaml @@ -0,0 +1,28 @@ + + + + diff --git a/Content.Client/_DV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs b/Content.Client/_DV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs new file mode 100644 index 00000000000..8c873f2bb98 --- /dev/null +++ b/Content.Client/_DV/Shipyard/UI/ShipyardConsoleMenu.xaml.cs @@ -0,0 +1,121 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.Access.Systems; +using Content.Shared._DV.Shipyard; +using Content.Shared._DV.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._DV.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 = []; + private readonly List _categories = []; + + public Entity Console; + + // The currently selected category + private ProtoId? _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; + + _categories.Clear(); + foreach (var vesselCategoryProto in Console.Comp.Categories) + { + if (proto.Resolve(vesselCategoryProto, out var vesselCategory)) + _categories.Add(vesselCategory); + } + + // 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)); + + // inserting here and not adding at the start so it doesn't get affected by sort + PopulateCategories(Console); + + SearchBar.OnTextChanged += _ => PopulateProducts(Console); + Categories.OnItemSelected += args => + { + _category = _categories[args.Id]; + Categories.SelectId(args.Id); + PopulateProducts(Console); + }; + + PopulateProducts(Console); + } + + /// + /// Populates the list of products that will actually be shown, using the current filters. + /// + private void PopulateProducts(Entity entity) + { + 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.Contains(search, StringComparison.InvariantCultureIgnoreCase)) + continue; + + if (_category != null && !vessel.Categories.Contains(_category.Value.Id)) + continue; + + var vesselEntry = new VesselRow(vessel, access, isFree: !entity.Comp.UseStationFunds); + 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(Entity entity) + { + Categories.Clear(); + // Guh, there's gotta be an easier way to select a category by default... + var selectedId = 0; // Default to the first category. May get overridden later. + for (var i = 0; i < _categories.Count; i++) + { + Categories.AddItem(_categories[i].LocalizedName, i); + + if (entity.Comp.DefaultCategory.HasValue && entity.Comp.DefaultCategory.Value.Id.Equals(_categories[i].ID, StringComparison.CurrentCultureIgnoreCase)) + selectedId = i; + } + + if (selectedId >= 0 && selectedId < _categories.Count) + { + _category = _categories[selectedId]; + Categories.SelectId(selectedId); + } + } + + public void UpdateState(ShipyardConsoleState state) + { + BankAccountLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", state.Balance.ToString())); + PopulateProducts(Console); + } +} diff --git a/Content.Client/_DV/Shipyard/UI/VesselRow.xaml b/Content.Client/_DV/Shipyard/UI/VesselRow.xaml new file mode 100644 index 00000000000..e01f33e2939 --- /dev/null +++ b/Content.Client/_DV/Shipyard/UI/VesselRow.xaml @@ -0,0 +1,16 @@ + + +