From 85fea907a2543146635830272c9bee59bb955a27 Mon Sep 17 00:00:00 2001
From: eidolon <95361+eidolon-zz@users.noreply.github.com>
Date: Sat, 21 Sep 2024 20:56:36 +0200
Subject: [PATCH 1/3] Add an item appraisal cartridge for PDAs.
---
.../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/cargo/price-gun-component.ftl | 3 +
.../Entities/Objects/Devices/cartridges.yml | 19 ++++
8 files changed, 291 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
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..01ab5d84fed
--- /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/cargo/price-gun-component.ftl b/Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl
index 7ccae53a860..3faa60414eb 100644
--- a/Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl
+++ b/Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl
@@ -16,3 +16,6 @@ pirate-contraband-price-gun-pricing-result = Skizzit's says {THE($object)} {$pri
pirate-contraband-price-gun-verb-text = Appraisal
pirate-contraband-price-gun-verb-message = Appraise {THE($object)}.
pirate-contraband-price-gun-pricing-result-none = Skizzit's doesn't have an entry for {THE($object)}.
+
+# Appraisal cartridge
+appraisal-program-name = Appraisal App Plus
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
From 1609751d370325cf11f0a11756f973b0cbd0bc6c Mon Sep 17 00:00:00 2001
From: eidolon <95361+eidolon-zz@users.noreply.github.com>
Date: Thu, 26 Sep 2024 17:47:10 +0200
Subject: [PATCH 2/3] Move cartridge localization lines into their own file
under _NF hierarchy. Add localizations for PDA UI labels
---
.../_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml | 4 ++--
Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl | 3 ---
Resources/Locale/en-US/_NF/cartridge-loader/cartridges.ftl | 4 ++++
3 files changed, 6 insertions(+), 5 deletions(-)
create mode 100644 Resources/Locale/en-US/_NF/cartridge-loader/cartridges.ftl
diff --git a/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml
index 01ab5d84fed..50d478cdb34 100644
--- a/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml
+++ b/Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUiFragment.xaml
@@ -5,8 +5,8 @@
-
-
+
+
diff --git a/Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl b/Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl
index 3faa60414eb..7ccae53a860 100644
--- a/Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl
+++ b/Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl
@@ -16,6 +16,3 @@ pirate-contraband-price-gun-pricing-result = Skizzit's says {THE($object)} {$pri
pirate-contraband-price-gun-verb-text = Appraisal
pirate-contraband-price-gun-verb-message = Appraise {THE($object)}.
pirate-contraband-price-gun-pricing-result-none = Skizzit's doesn't have an entry for {THE($object)}.
-
-# Appraisal cartridge
-appraisal-program-name = Appraisal App Plus
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
From 0d309d48d1486ceb46266712db84dec97d2ee264 Mon Sep 17 00:00:00 2001
From: eidolon <95361+eidolon-zz@users.noreply.github.com>
Date: Thu, 3 Oct 2024 10:49:37 +0200
Subject: [PATCH 3/3] Add appraisal cartridge to Astro Vend
---
.../_NF/Catalog/VendingMachines/Inventories/astrovend.yml | 2 ++
1 file changed, 2 insertions(+)
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