Skip to content

Commit

Permalink
Add an item appraisal cartridge for PDAs.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrome-cirrus committed Sep 22, 2024
1 parent 1dda632 commit 9f5bb30
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 0 deletions.
29 changes: 29 additions & 0 deletions Content.Client/_NF/CartridgeLoader/Cartridges/AppraisalUi.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<cartridges:AppraisalUiFragment xmlns:cartridges="clr-namespace:Content.Client._NF.CartridgeLoader.Cartridges"
xmlns="https://spacestation14.io" Margin="1 0 2 0">
<PanelContainer StyleClasses="BackgroundDark"></PanelContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" MinHeight="32" MaxHeight="32" VerticalAlignment="Top" Margin="3 0">
<PanelContainer Name="HeaderPanel">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="4">
<Label HorizontalExpand="True" Text="Item"/>
<Label HorizontalExpand="True" Text="Appraised Price"/>
</BoxContainer>
</PanelContainer>
</BoxContainer>
<ScrollContainer Name="ScrollContainer" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="AppraisedItemContainer" HorizontalExpand="True" VerticalExpand="True" VerticalAlignment="Top" Margin="3 0"/>
</ScrollContainer>
</BoxContainer>
</cartridges:AppraisalUiFragment>
Original file line number Diff line number Diff line change
@@ -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<AppraisedItem> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The list of appraised items
/// </summary>
[DataField("appraisedItems")]
public List<AppraisedItem> AppraisedItems = new();

/// <summary>
/// Limits the amount of items that can be saved
/// </summary>
[DataField("maxSavedItems")]
public int MaxSavedItems { get; set; } = 9;

[DataField("soundScan")]
public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
}
Original file line number Diff line number Diff line change
@@ -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<AppraisalCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
SubscribeLocalEvent<AppraisalCartridgeComponent, CartridgeAfterInteractEvent>(AfterInteract);
SubscribeLocalEvent<AppraisalCartridgeComponent, CartridgeActivatedEvent>(OnCartridgeActivated);
SubscribeLocalEvent<AppraisalCartridgeComponent, CartridgeDeactivatedEvent>(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<AppraisalCartridgeComponent> ent, ref CartridgeActivatedEvent args)
{
EnsureComp<PriceGunComponent>(args.Loader);
// PriceGunSystem methods exit early if a DelayComponent is not present
EnsureComp<UseDelayComponent>(args.Loader);
}

private void OnCartridgeDeactivated(Entity<AppraisalCartridgeComponent> ent, ref CartridgeDeactivatedEvent args)
{
var parent = Transform(args.Loader).ParentUid;
RemComp<PriceGunComponent>(parent);
RemComp<UseDelayComponent>(parent);
}

/// <summary>
/// The <see cref="CartridgeAfterInteractEvent" /> gets relayed to this system if the cartridge loader is running
/// the Appraisal program and someone clicks on something with it. <br/>
/// <br/>
/// Does the thing... TODO
/// </summary>
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);
}

/// <summary>
/// This gets called when the ui fragment needs to be updated for the first time after activating
/// </summary>
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);
}
}
30 changes: 30 additions & 0 deletions Content.Shared/_NF/CartridgeLoader/Cartridges/AppraisalUiState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Robust.Shared.Serialization;

namespace Content.Shared.CartridgeLoader.Cartridges;

[Serializable, NetSerializable]
public sealed class AppraisalUiState : BoundUserInterfaceState
{
/// <summary>
/// The list of appraised items
/// </summary>
public List<AppraisedItem> AppraisedItems;

public AppraisalUiState(List<AppraisedItem> 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;
}
}
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/_NF/cargo/price-gun-component.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 18 additions & 0 deletions Resources/Prototypes/_NF/Entities/Objects/Devices/cartridges.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,21 @@
- type: BountyContractsCartridge
- 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

0 comments on commit 9f5bb30

Please sign in to comment.