From d381c3c6ffb4a55b20ffdfc564adc618c7f329ed Mon Sep 17 00:00:00 2001 From: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Date: Sun, 7 Jan 2024 22:28:56 +0300 Subject: [PATCH] Custom shipyard listings (#808) * Did the deed * Fix spectre's name tag * Removed the None ui key * Typo * Limit the empress' console to just 3 ships * Cleanup * Remove unused name field --- .../BUI/ShipyardConsoleBoundUserInterface.cs | 6 +- .../Shipyard/UI/ShipyardConsoleMenu.xaml.cs | 51 +++++++---------- .../Systems/ShipyardSystem.Consoles.cs | 56 ++++++++++++++++++- .../BUI/ShipyardConsoleInterfaceState.cs | 9 ++- .../Components/ShipyardListingComponent.cs | 17 ++++++ .../Shipyard/SharedShipyardSystem.cs | 21 +++++-- Resources/Maps/Shuttles/empress.yml | 2 +- .../Machines/Computers/computers.yml | 28 ++++++++-- .../Computers/mothership-computers.yml | 22 ++++++++ Resources/Prototypes/_NF/Shipyard/cleric.yml | 2 +- Resources/Prototypes/_NF/Shipyard/fighter.yml | 2 +- Resources/Prototypes/_NF/Shipyard/rogue.yml | 2 +- Resources/Prototypes/_NF/Shipyard/spectre.yml | 4 +- 13 files changed, 170 insertions(+), 52 deletions(-) create mode 100644 Content.Shared/Shipyard/Components/ShipyardListingComponent.cs create mode 100644 Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/mothership-computers.yml diff --git a/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs b/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs index d1703159a70..f1a257045f6 100644 --- a/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs +++ b/Content.Client/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs @@ -42,12 +42,12 @@ protected override void Open() _menu.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent("ShipyardConsole-targetId")); } - private void Populate(byte uiKey) + private void Populate(List prototypes, string name) { if (_menu == null) return; - _menu.PopulateProducts((ShipyardConsoleUiKey) uiKey); + _menu.PopulateProducts(prototypes, name); _menu.PopulateCategories(); } @@ -61,7 +61,7 @@ protected override void UpdateState(BoundUserInterfaceState state) Balance = cState.Balance; ShipSellValue = cState.ShipSellValue; var castState = (ShipyardConsoleInterfaceState) state; - Populate(castState.UiKey); + Populate(castState.ShipyardPrototypes, castState.ShipyardName); _menu?.UpdateState(castState); } diff --git a/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml.cs b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml.cs index d6489776385..93ae70fd5ac 100644 --- a/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml.cs +++ b/Content.Client/Shipyard/UI/ShipyardConsoleMenu.xaml.cs @@ -23,6 +23,9 @@ public sealed partial class ShipyardConsoleMenu : FancyWindow private readonly List _categoryStrings = new(); private string? _category; + private List _lastProtos = new(); + private string _lastType = ""; + public ShipyardConsoleMenu(ShipyardConsoleBoundUserInterface owner) { RobustXamlLoader.Load(this); @@ -38,12 +41,12 @@ public ShipyardConsoleMenu(ShipyardConsoleBoundUserInterface owner) private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs args) { SetCategoryText(args.Id); - PopulateProducts((ShipyardConsoleUiKey) _menu.UiKey); + PopulateProducts(_lastProtos, _lastType); } private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args) { - PopulateProducts((ShipyardConsoleUiKey) _menu.UiKey); + PopulateProducts(_lastProtos, _lastType); } private void SetCategoryText(int id) @@ -52,52 +55,35 @@ private void SetCategoryText(int id) Categories.SelectId(id); } - private void GetPrototypes(out IEnumerable vessels) - { - vessels = _protoManager.EnumeratePrototypes(); - } - /// /// Populates the list of products that will actually be shown, using the current filters. /// - public void PopulateProducts(ShipyardConsoleUiKey uiKey) + public void PopulateProducts(List prototypes, string type) { Vessels.RemoveAllChildren(); - GetPrototypes(out var vessels); - var vesselList = vessels.ToList(); - vesselList.Sort((x, y) => - string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase)); - var type = uiKey switch - { - ShipyardConsoleUiKey.Shipyard => "Civilian", - ShipyardConsoleUiKey.Security => "Security", - ShipyardConsoleUiKey.BlackMarket => "BlackMarket", - ShipyardConsoleUiKey.Expedition => "Expedition", - ShipyardConsoleUiKey.Scrap => "Scrap", - _ => "Shipyard", - }; + var newVessels = prototypes.Select(it => _protoManager.TryIndex(it, out var proto) ? proto : null) + .Where(it => it != null) + .ToList(); + + newVessels.Sort((x, y) => + string.Compare(x!.Name, y!.Name, StringComparison.CurrentCultureIgnoreCase)); var search = SearchBar.Text.Trim().ToLowerInvariant(); - foreach (var prototype in vesselList) + foreach (var prototype in newVessels) { - // filter by type for ui key - if (prototype.Group != type) - { - continue; - } // if no search or category // else if search // else if category and not search if (search.Length == 0 && _category == null || - search.Length != 0 && prototype.Name.ToLowerInvariant().Contains(search) || - search.Length == 0 && _category != null && prototype.Category.Equals(_category)) + search.Length != 0 && prototype!.Name.ToLowerInvariant().Contains(search) || + search.Length == 0 && _category != null && prototype!.Category.Equals(_category)) { var vesselEntry = new VesselRow { Vessel = prototype, - VesselName = { Text = prototype.Name }, + VesselName = { Text = prototype!.Name }, Purchase = { ToolTip = prototype.Description, TooltipDelay = 0.2f }, Price = { Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", prototype.Price.ToString())) }, }; @@ -105,6 +91,9 @@ public void PopulateProducts(ShipyardConsoleUiKey uiKey) Vessels.AddChild(vesselEntry); } } + + _lastProtos = prototypes; + _lastType = type; } /// @@ -114,7 +103,7 @@ public void PopulateCategories() { _categoryStrings.Clear(); Categories.Clear(); - GetPrototypes(out var vessels); + var vessels = _protoManager.EnumeratePrototypes(); foreach (var prototype in vessels) { if (!_categoryStrings.Contains(prototype.Category)) diff --git a/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs b/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs index 36e90332228..88047b65b39 100644 --- a/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs +++ b/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs @@ -102,6 +102,13 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component return; } + if (!GetAvailableShuttles(uid).Contains(vessel.ID)) + { + PlayDenySound(uid, component); + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(player):player} tried to purchase a vessel that was never available."); + return; + } + var name = vessel.Name; if (vessel.Price <= 0) return; @@ -495,15 +502,62 @@ public bool FoundOrganics(EntityUid uid, EntityQuery mobQuery return false; } + /// + /// Returns all shuttle prototype IDs the given shipyard console can offer. + /// + public List GetAvailableShuttles(EntityUid uid, ShipyardConsoleUiKey? key = null, ShipyardListingComponent? listing = null) + { + var availableShuttles = new List(); + + if (key == null && TryComp(uid, out var ui)) + { + // Try to find a ui key that is an instance of the shipyard console ui key + foreach (var (k, v) in ui.Interfaces) + { + if (k is ShipyardConsoleUiKey shipyardKey) + { + key = shipyardKey; + break; + } + } + } + + // Add all prototypes matching the ui key + if (key != null && key != ShipyardConsoleUiKey.Custom && ShipyardGroupMapping.TryGetValue(key.Value, out var group)) + { + var protos = _prototypeManager.EnumeratePrototypes(); + foreach (var proto in protos) + { + if (proto.Group == group) + availableShuttles.Add(proto.ID); + } + } + + // Add all prototypes specified in ShipyardListing + if (listing != null || TryComp(uid, out listing)) + { + foreach (var shuttle in listing.Shuttles) + { + availableShuttles.Add(shuttle); + } + } + + return availableShuttles; + } + private void RefreshState(EntityUid uid, int balance, bool access, string? shipDeed, int shipSellValue, bool isTargetIdPresent, ShipyardConsoleUiKey uiKey) { + var listing = TryComp(uid, out var comp) ? comp : null; + var newState = new ShipyardConsoleInterfaceState( balance, access, shipDeed, shipSellValue, isTargetIdPresent, - ((byte)uiKey)); + ((byte)uiKey), + GetAvailableShuttles(uid, uiKey, listing), + uiKey.ToString()); _ui.TrySetUiState(uid, uiKey, newState); } diff --git a/Content.Shared/Shipyard/BUI/ShipyardConsoleInterfaceState.cs b/Content.Shared/Shipyard/BUI/ShipyardConsoleInterfaceState.cs index 95fcac3f857..b1072735618 100644 --- a/Content.Shared/Shipyard/BUI/ShipyardConsoleInterfaceState.cs +++ b/Content.Shared/Shipyard/BUI/ShipyardConsoleInterfaceState.cs @@ -12,13 +12,18 @@ public sealed class ShipyardConsoleInterfaceState : BoundUserInterfaceState public readonly bool IsTargetIdPresent; public readonly byte UiKey; + public readonly List ShipyardPrototypes; + public readonly string ShipyardName; + public ShipyardConsoleInterfaceState( int balance, bool accessGranted, string? shipDeedTitle, int shipSellValue, bool isTargetIdPresent, - byte uiKey) + byte uiKey, + List shipyardPrototypes, + string shipyardName) { Balance = balance; AccessGranted = accessGranted; @@ -26,5 +31,7 @@ public ShipyardConsoleInterfaceState( ShipSellValue = shipSellValue; IsTargetIdPresent = isTargetIdPresent; UiKey = uiKey; + ShipyardPrototypes = shipyardPrototypes; + ShipyardName = shipyardName; } } diff --git a/Content.Shared/Shipyard/Components/ShipyardListingComponent.cs b/Content.Shared/Shipyard/Components/ShipyardListingComponent.cs new file mode 100644 index 00000000000..2aa1df67534 --- /dev/null +++ b/Content.Shared/Shipyard/Components/ShipyardListingComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Shipyard.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Shipyard.Components; + +/// +/// When applied to a shipyard console, adds all specified shuttles to the list of sold shuttles. +/// +[RegisterComponent] +public sealed partial class ShipyardListingComponent : Component +{ + /// + /// All VesselPrototype IDs that should be listed in this shipyard console. + /// + [ViewVariables, DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List Shuttles = new(); +} diff --git a/Content.Shared/Shipyard/SharedShipyardSystem.cs b/Content.Shared/Shipyard/SharedShipyardSystem.cs index 222024b2ec4..17a153aae75 100644 --- a/Content.Shared/Shipyard/SharedShipyardSystem.cs +++ b/Content.Shared/Shipyard/SharedShipyardSystem.cs @@ -7,6 +7,7 @@ namespace Content.Shared.Shipyard; +// Note: when adding a new ui key, don't forget to modify the dictionary in SharedShipyardSystem [NetSerializable, Serializable] public enum ShipyardConsoleUiKey : byte { @@ -14,14 +15,26 @@ public enum ShipyardConsoleUiKey : byte Security, BlackMarket, Expedition, - Scrap - // Syndicate - //Not currently implemented. Could be used in the future to give other factions a variety of shuttle options, - //like nukies, syndicate, or for evac purchases. + Scrap, + // Do not add any ship to this key. Shipyards using it are inherently empty and are populated using the ShipyardListingComponent. + Custom } public abstract class SharedShipyardSystem : EntitySystem { + /// + /// Maps entries of the enum to how they're specified in shuttle prototype files + /// + public static readonly Dictionary ShipyardGroupMapping = new() + { + {ShipyardConsoleUiKey.Shipyard, "Civilian"}, + {ShipyardConsoleUiKey.Security, "Security"}, + {ShipyardConsoleUiKey.BlackMarket, "BlackMarket"}, + {ShipyardConsoleUiKey.Expedition, "Expedition"}, + {ShipyardConsoleUiKey.Scrap, "Scrap"}, + {ShipyardConsoleUiKey.Custom, ""} + }; + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; public override void Initialize() diff --git a/Resources/Maps/Shuttles/empress.yml b/Resources/Maps/Shuttles/empress.yml index 9abec7d0eab..99cd1722da0 100644 --- a/Resources/Maps/Shuttles/empress.yml +++ b/Resources/Maps/Shuttles/empress.yml @@ -6940,7 +6940,7 @@ entities: - pos: 15.5,-6.5 parent: 1 type: Transform -- proto: ComputerShipyardSecurity +- proto: EmpressMothershipComputer entities: - uid: 118 components: diff --git a/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers.yml index 00c75ac18c5..b4ea1018719 100644 --- a/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers.yml @@ -68,7 +68,7 @@ interfaces: - key: enum.ShipyardConsoleUiKey.Shipyard type: ShipyardConsoleBoundUserInterface - - type: ItemSlots + - type: ItemSlots - type: ContainerContainer containers: ShipyardConsole-targetId: !type:ContainerSlot {} @@ -80,6 +80,22 @@ !type:DamageTrigger damage: 0 +# Custom console +- type: entity + id: BaseMothershipComputer + parent: ComputerShipyard + name: mothership console + description: Used on motherships to purchase and sell ships without returning to a station. + components: + - type: ActivatableUI + key: enum.ShipyardConsoleUiKey.Custom + - type: UserInterface + interfaces: + - key: enum.ShipyardConsoleUiKey.Custom + type: ShipyardConsoleBoundUserInterface + - type: ShipyardListing + +# Hardcoded consoles - type: entity id: ComputerShipyardSecurity parent: ComputerShipyard @@ -100,7 +116,7 @@ - map: ["computerLayerKeyboard"] state: generic_keyboard - map: ["computerLayerScreen"] - state: shipyard_security + state: shipyard_security - map: ["computerLayerKeys"] state: telesci_key @@ -124,7 +140,7 @@ - map: ["computerLayerKeyboard"] state: generic_keyboard - map: ["computerLayerScreen"] - state: shipyard_blackmarket + state: shipyard_blackmarket - map: ["computerLayerKeys"] state: blackmarket_key - type: Destructible @@ -156,7 +172,7 @@ - map: ["computerLayerKeyboard"] state: generic_keyboard - map: ["computerLayerScreen"] - state: shipyard_blackmarket + state: shipyard_blackmarket - map: ["computerLayerKeys"] state: blackmarket_key @@ -180,7 +196,7 @@ - map: ["computerLayerKeyboard"] state: generic_keyboard - map: ["computerLayerScreen"] - state: shipyard_blackmarket + state: shipyard_blackmarket - map: ["computerLayerKeys"] state: blackmarket_key @@ -281,4 +297,4 @@ description: Used to sell goods loaded onto cargo pallets components: - type: MarketModifier - mod: 0.40 \ No newline at end of file + mod: 0.40 diff --git a/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/mothership-computers.yml b/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/mothership-computers.yml new file mode 100644 index 00000000000..07a1a5a83b2 --- /dev/null +++ b/Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/mothership-computers.yml @@ -0,0 +1,22 @@ +# The empress console +- type: entity + id: EmpressMothershipComputer + name: Empress mothership console + parent: BaseMothershipComputer + components: + - type: Sprite + sprite: _NF/Structures/Machines/computers.rsi + layers: + - map: ["computerLayerBody"] + state: computer + - map: ["computerLayerKeyboard"] + state: generic_keyboard + - map: ["computerLayerScreen"] + state: shipyard_security + - map: ["computerLayerKeys"] + state: telesci_key + - type: ShipyardListing + shuttles: + - Fighter + - Cleric + - Rogue diff --git a/Resources/Prototypes/_NF/Shipyard/cleric.yml b/Resources/Prototypes/_NF/Shipyard/cleric.yml index fd0f9b7f2ae..f4f706fd8db 100644 --- a/Resources/Prototypes/_NF/Shipyard/cleric.yml +++ b/Resources/Prototypes/_NF/Shipyard/cleric.yml @@ -4,7 +4,7 @@ description: Small support vessel used for emergency rescues and first aid. price: 10800 #Appraisal is 10500 category: Small - group: Security #Should be only available at a cruiser custom shipyard TODO + group: None shuttlePath: /Maps/Shuttles/cleric.yml - type: gameMap diff --git a/Resources/Prototypes/_NF/Shipyard/fighter.yml b/Resources/Prototypes/_NF/Shipyard/fighter.yml index a425f334c65..ae8ec1a2610 100644 --- a/Resources/Prototypes/_NF/Shipyard/fighter.yml +++ b/Resources/Prototypes/_NF/Shipyard/fighter.yml @@ -4,7 +4,7 @@ description: Small attack vessel used for boarding and transport of prisoners. price: 9000 #not sure how much mark up % to add but the appraisal is 8491$ category: Small - group: Security #Should be only available at a cruiser custom shipyard TODO + group: None shuttlePath: /Maps/Shuttles/fighter.yml - type: gameMap diff --git a/Resources/Prototypes/_NF/Shipyard/rogue.yml b/Resources/Prototypes/_NF/Shipyard/rogue.yml index 9cb24143192..de155d51d9f 100644 --- a/Resources/Prototypes/_NF/Shipyard/rogue.yml +++ b/Resources/Prototypes/_NF/Shipyard/rogue.yml @@ -4,7 +4,7 @@ description: Small assault vessel with a toggle for going dark in deep space. price: 8200 #the appraisal is 7941$ category: Small - group: Security #Should be only available at a cruiser custom shipyard TODO + group: None shuttlePath: /Maps/Shuttles/rogue.yml - type: gameMap diff --git a/Resources/Prototypes/_NF/Shipyard/spectre.yml b/Resources/Prototypes/_NF/Shipyard/spectre.yml index 30103fecf95..fa6d628638e 100644 --- a/Resources/Prototypes/_NF/Shipyard/spectre.yml +++ b/Resources/Prototypes/_NF/Shipyard/spectre.yml @@ -1,6 +1,6 @@ - type: vessel id: Spectre - name: Spectre + name: NSS Spectre description: A large research mothership designed to be flown nexto a small fleet of other ships including salvage and food services. price: 195000 category: Large @@ -17,7 +17,7 @@ stationProto: StandardFrontierVessel components: - type: StationNameSetup - mapNameTemplate: 'Spectre {1}' + mapNameTemplate: 'Spectre {1}' nameGenerator: !type:NanotrasenNameGenerator prefixCreator: '14'