From d543a72fe7ea5329a832fcfcf39e76dab677d353 Mon Sep 17 00:00:00 2001 From: chrome-cirrus <95361+chrome-cirrus@users.noreply.github.com> Date: Sun, 13 Oct 2024 06:50:28 +0200 Subject: [PATCH] Add an item appraisal cartridge for PDAs (#2045) * Add an item appraisal cartridge for PDAs. * Move cartridge localization lines into their own file under _NF hierarchy. Add localizations for PDA UI labels * Add appraisal cartridge to Astro Vend --------- Co-authored-by: eidolon <95361+eidolon-zz@users.noreply.github.com> Co-authored-by: Dvir <39403717+dvir001@users.noreply.github.com> --- .../CartridgeLoader/Cartridges/AppraisalUi.cs | 29 +++++ .../Cartridges/AppraisalUiFragment.xaml | 17 +++ .../Cartridges/AppraisalUiFragment.xaml.cs | 65 +++++++++++ .../Cartridges/AppraisalCartridgeComponent.cs | 23 ++++ .../Cartridges/AppraisalCartridgeSystem.cs | 105 ++++++++++++++++++ .../Cartridges/AppraisalUiState.cs | 30 +++++ .../en-US/_NF/cartridge-loader/cartridges.ftl | 4 + .../VendingMachines/Inventories/astrovend.yml | 2 + .../Entities/Objects/Devices/cartridges.yml | 19 ++++ 9 files changed, 294 insertions(+) create mode 100644 Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUi.cs create mode 100644 Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml create mode 100644 Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml.cs create mode 100644 Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeComponent.cs create mode 100644 Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeSystem.cs create mode 100644 Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs create mode 100644 Resources/Locale/en-US/_NF/cartridge-loader/cartridges.ftl diff --git a/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUi.cs b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUi.cs new file mode 100644 index 00000000000..6300df87450 --- /dev/null +++ b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUi.cs @@ -0,0 +1,29 @@ +using Content.Client.UserInterface.Fragments; +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; + +namespace Content.Client._NF.CartridgeLoader.Cartridges; + +public sealed partial class AppraisalUi : UIFragment +{ + private AppraisalUiFragment? _fragment; + + public override Control GetUIFragmentRoot() + { + return _fragment!; + } + + public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner) + { + _fragment = new AppraisalUiFragment(); + } + + public override void UpdateState(BoundUserInterfaceState state) + { + if (state is not AppraisalUiState appraisalUiState) + return; + + _fragment?.UpdateState(appraisalUiState.AppraisedItems); + } +} diff --git a/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml new file mode 100644 index 00000000000..50d478cdb34 --- /dev/null +++ b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml.cs b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml.cs new file mode 100644 index 00000000000..fbffdce9450 --- /dev/null +++ b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml.cs @@ -0,0 +1,65 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._NF.CartridgeLoader.Cartridges; + +[GenerateTypedNameReferences] +public sealed partial class AppraisalUiFragment : BoxContainer +{ + private readonly StyleBoxFlat _styleBox = new() + { + BackgroundColor = Color.Transparent, + BorderColor = Color.FromHex("#5a5a5a"), + BorderThickness = new Thickness(0, 0, 0, 1) + }; + + public AppraisalUiFragment() + { + RobustXamlLoader.Load(this); + Orientation = LayoutOrientation.Vertical; + HorizontalExpand = true; + VerticalExpand = true; + HeaderPanel.PanelOverride = _styleBox; + } + + public void UpdateState(List items) + { + AppraisedItemContainer.RemoveAllChildren(); + + //Reverse the list so the oldest entries appear at the bottom + items.Reverse(); + + //Enable scrolling if there are more entries that can fit on the screen + ScrollContainer.HScrollEnabled = items.Count > 9; + + foreach (var item in items) + { + AddAppraisedItem(item); + } + } + + private void AddAppraisedItem(AppraisedItem item) + { + var row = new BoxContainer(); + row.HorizontalExpand = true; + row.Orientation = LayoutOrientation.Horizontal; + row.Margin = new Thickness(4); + + var nameLabel = new Label(); + nameLabel.Text = item.Name; + nameLabel.HorizontalExpand = true; + nameLabel.ClipText = true; + row.AddChild(nameLabel); + + var valueLabel = new Label(); + valueLabel.Text = item.AppraisedPrice; + valueLabel.HorizontalExpand = true; + valueLabel.ClipText = true; + row.AddChild(valueLabel); + + AppraisedItemContainer.AddChild(row); + } +} diff --git a/Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeComponent.cs b/Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeComponent.cs new file mode 100644 index 00000000000..d0b99f29152 --- /dev/null +++ b/Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeComponent.cs @@ -0,0 +1,23 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Shared.Audio; + +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent] +public sealed partial class AppraisalCartridgeComponent : Component +{ + /// + /// The list of appraised items + /// + [DataField("appraisedItems")] + public List AppraisedItems = new(); + + /// + /// Limits the amount of items that can be saved + /// + [DataField("maxSavedItems")] + public int MaxSavedItems { get; set; } = 9; + + [DataField("soundScan")] + public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg"); +} diff --git a/Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeSystem.cs b/Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeSystem.cs new file mode 100644 index 00000000000..0e9973aae97 --- /dev/null +++ b/Content.Server/_NF/CartridgeLoader/Cartridges/AppraisalCartridgeSystem.cs @@ -0,0 +1,105 @@ +using Content.Server.Cargo.Systems; +using Content.Shared.Audio; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; +using Content.Shared.Popups; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Random; +using Content.Server.Cargo.Components; +using Content.Shared.Timing; + +namespace Content.Server.CartridgeLoader.Cartridges; + +public sealed class AppraisalCartridgeSystem : EntitySystem +{ + [Dependency] private readonly CargoSystem _bountySystem = default!; + [Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PricingSystem _pricingSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUiReady); + SubscribeLocalEvent(AfterInteract); + SubscribeLocalEvent(OnCartridgeActivated); + SubscribeLocalEvent(OnCartridgeDeactivated); + } + + // Kinda jank, but easiest way to get the right-click Appraisal verb to also work. + // I'd much rather pass the GetUtilityVerb event through to the AppraisalCartridgeSystem and have all of + // the functionality in there, rather than adding a PriceGunComponent to the PDA itself, but getting + // that passthrough to work is not a straightforward thing. + + // Because of this weird workaround, items appraised with the right-click utility verb don't get added + // to the history in the UI. That'll be something to revisit someday if anyone notices and complains :P + + // Doing this on cartridge activation and deactivation rather than install and remove so that the price + // gun functionality is only there when the program is active. + private void OnCartridgeActivated(Entity ent, ref CartridgeActivatedEvent args) + { + EnsureComp(args.Loader); + // PriceGunSystem methods exit early if a DelayComponent is not present + EnsureComp(args.Loader); + } + + private void OnCartridgeDeactivated(Entity ent, ref CartridgeDeactivatedEvent args) + { + var parent = Transform(args.Loader).ParentUid; + RemComp(parent); + RemComp(parent); + } + + /// + /// The gets relayed to this system if the cartridge loader is running + /// the Appraisal program and someone clicks on something with it.
+ ///
+ /// Does the thing... TODO + ///
+ private void AfterInteract(EntityUid uid, AppraisalCartridgeComponent component, CartridgeAfterInteractEvent args) + { + if (args.InteractEvent.Handled || !args.InteractEvent.CanReach || !args.InteractEvent.Target.HasValue) + return; + + var target = args.InteractEvent.Target; + var who = args.InteractEvent.User; + double price = 0.00; + + // All of the pop up display stuff is being handled by the PriceGunComponent addded to the PDA, + // all we're doing in here is getting the price and recording it to the PDA interface bit. + price = _pricingSystem.GetPrice(target.Value); + + //Limit the amount of saved probe results to 9 + //This is hardcoded because the UI doesn't support a dynamic number of results + if (component.AppraisedItems.Count >= component.MaxSavedItems) + component.AppraisedItems.RemoveAt(0); + + var item = new AppraisedItem( + Name(target.Value), + price.ToString("0.00") + ); + + component.AppraisedItems.Add(item); + UpdateUiState(uid, args.Loader, component); + } + + /// + /// This gets called when the ui fragment needs to be updated for the first time after activating + /// + private void OnUiReady(EntityUid uid, AppraisalCartridgeComponent component, CartridgeUiReadyEvent args) + { + UpdateUiState(uid, args.Loader, component); + } + + private void UpdateUiState(EntityUid uid, EntityUid loaderUid, AppraisalCartridgeComponent? component) + { + if (!Resolve(uid, ref component)) + return; + + var state = new AppraisalUiState(component.AppraisedItems); + _cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state); + } +} diff --git a/Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs b/Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs new file mode 100644 index 00000000000..41b9ed09477 --- /dev/null +++ b/Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs @@ -0,0 +1,30 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[Serializable, NetSerializable] +public sealed class AppraisalUiState : BoundUserInterfaceState +{ + /// + /// The list of appraised items + /// + public List AppraisedItems; + + public AppraisalUiState(List appraisedItems) + { + AppraisedItems = appraisedItems; + } +} + +[Serializable, NetSerializable, DataRecord] +public sealed class AppraisedItem +{ + public readonly string Name; + public readonly string AppraisedPrice; + + public AppraisedItem(string name, string appraisedPrice) + { + Name = name; + AppraisedPrice = appraisedPrice; + } +} diff --git a/Resources/Locale/en-US/_NF/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/_NF/cartridge-loader/cartridges.ftl new file mode 100644 index 00000000000..99a458e07a2 --- /dev/null +++ b/Resources/Locale/en-US/_NF/cartridge-loader/cartridges.ftl @@ -0,0 +1,4 @@ +# Appraisal cartridge +appraisal-program-name = Appraisal App Plus +appraisal-label-name = Item +appraisal-label-price = Appraised Price diff --git a/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/astrovend.yml b/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/astrovend.yml index 75cfd619224..d339a11f15c 100644 --- a/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/astrovend.yml +++ b/Resources/Prototypes/_NF/Catalog/VendingMachines/Inventories/astrovend.yml @@ -13,6 +13,7 @@ JetpackMiniFilled: 10 JetpackBlue: 10 HandHeldMassScanner: 10 + AppraisalCartridge: 3 # Keys EncryptionKeyCommon: 3 EncryptionKeyTraffic: 3 @@ -53,6 +54,7 @@ JetpackMiniFilled: 4294967295 # Infinite JetpackBlue: 4294967295 # Infinite HandHeldMassScanner: 4294967295 # Infinite + AppraisalCartridge: 4294967295 # Infinite # Keys EncryptionKeyCommon: 4294967295 # Infinite EncryptionKeyTraffic: 4294967295 # Infinite diff --git a/Resources/Prototypes/_NF/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/_NF/Entities/Objects/Devices/cartridges.yml index 643d986aa27..065a9ac25fb 100644 --- a/Resources/Prototypes/_NF/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/_NF/Entities/Objects/Devices/cartridges.yml @@ -19,6 +19,25 @@ - type: AccessReader access: [["HeadOfSecurity"], ["HeadOfPersonnel"]] +- type: entity + parent: BaseItem + id: AppraisalCartridge + name: appraisal cartridge + description: A program for appraising the monetary value of items + components: + - type: Sprite + sprite: Objects/Devices/cartridge.rsi + state: cart-y + - type: Icon + sprite: Objects/Devices/cartridge.rsi + state: cart-y + - type: UIFragment + ui: !type:AppraisalUi + - type: Cartridge + programName: appraisal-program-name + icon: Interface/Actions/shop.png + - type: AppraisalCartridge + # Not a PDA cartridge (then why is this here) - type: entity parent: BaseItem