Adds the NetProbe cartridge (#12543)

* Implement NetProbeCartridge

* Add audio and a popup when scanning a device
Add some doc comments

* Set program icon

* Add NetProbe cartridge as rare loot to maintenance loot tool spawner

* Make the maximum amount of saved entries configurable
Add a scrollbar that shows when there are more entries than fit on the screen

* Make device net id names translatable
This commit is contained in:
Julian Giebel
2022-11-13 22:36:00 +01:00
committed by GitHub
parent 4ec37c8bc0
commit 0df65e5c2a
11 changed files with 341 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
using Content.Shared.CartridgeLoader.Cartridges;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
namespace Content.Client.CartridgeLoader.Cartridges;
public sealed class NetProbeUi : CartridgeUI
{
private NetProbeUiFragment? _fragment;
public override Control GetUIFragmentRoot()
{
return _fragment!;
}
public override void Setup(BoundUserInterface userInterface)
{
_fragment = new NetProbeUiFragment();
}
public override void UpdateState(BoundUserInterfaceState state)
{
if (state is not NetProbeUiState netProbeUiState)
return;
_fragment?.UpdateState(netProbeUiState.ProbedDevices);
}
}

View File

@@ -0,0 +1,19 @@
<cartridges:NetProbeUiFragment xmlns:cartridges="clr-namespace:Content.Client.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="Name"/>
<Label HorizontalExpand="True" Text="Address"/>
<Label HorizontalExpand="True" Text="Frequency"/>
<Label HorizontalExpand="True" Text="Network"/>
</BoxContainer>
</PanelContainer>
</BoxContainer>
<ScrollContainer Name="ScrollContainer" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="ProbedDeviceContainer" HorizontalExpand="True" VerticalExpand="True" VerticalAlignment="Top" Margin="3 0"/>
</ScrollContainer>
</BoxContainer>
</cartridges:NetProbeUiFragment>

View File

@@ -0,0 +1,77 @@
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.CartridgeLoader.Cartridges;
[GenerateTypedNameReferences]
public sealed partial class NetProbeUiFragment : BoxContainer
{
private readonly StyleBoxFlat _styleBox = new()
{
BackgroundColor = Color.Transparent,
BorderColor = Color.FromHex("#5a5a5a"),
BorderThickness = new Thickness(0, 0, 0, 1)
};
public NetProbeUiFragment()
{
RobustXamlLoader.Load(this);
Orientation = LayoutOrientation.Vertical;
HorizontalExpand = true;
VerticalExpand = true;
HeaderPanel.PanelOverride = _styleBox;
}
public void UpdateState(List<ProbedNetworkDevice> devices)
{
ProbedDeviceContainer.RemoveAllChildren();
//Reverse the list so the oldest entries appear at the bottom
devices.Reverse();
//Enable scrolling if there are more entries that can fit on the screen
ScrollContainer.HScrollEnabled = devices.Count > 9;
foreach (var device in devices)
{
AddProbedDevice(device);
}
}
private void AddProbedDevice(ProbedNetworkDevice device)
{
var row = new BoxContainer();
row.HorizontalExpand = true;
row.Orientation = LayoutOrientation.Horizontal;
row.Margin = new Thickness(4);
var nameLabel = new Label();
nameLabel.Text = device.Name;
nameLabel.HorizontalExpand = true;
nameLabel.ClipText = true;
row.AddChild(nameLabel);
var addressLabel = new Label();
addressLabel.Text = device.Address;
addressLabel.HorizontalExpand = true;
addressLabel.ClipText = true;
row.AddChild(addressLabel);
var frequencyLabel = new Label();
frequencyLabel.Text = device.Frequency;
frequencyLabel.HorizontalExpand = true;
frequencyLabel.ClipText = true;
row.AddChild(frequencyLabel);
var networkLabel = new Label();
networkLabel.Text = device.NetId;
networkLabel.HorizontalExpand = true;
networkLabel.ClipText = true;
row.AddChild(networkLabel);
ProbedDeviceContainer.AddChild(row);
}
}

View File

@@ -0,0 +1,25 @@
using Content.Shared.CartridgeLoader.Cartridges;
using Robust.Shared.Audio;
namespace Content.Server.CartridgeLoader.Cartridges;
[RegisterComponent]
public sealed class NetProbeCartridgeComponent : Component
{
/// <summary>
/// The list of probed network devices
/// </summary>
[DataField("probedDevices")]
public List<ProbedNetworkDevice> ProbedDevices = new();
/// <summary>
/// Limits the amount of devices that can be saved
/// </summary>
[DataField("maxSavedDevices")]
public int MaxSavedDevices { get; set; } = 9;
[DataField("soundScan")]
public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
}

View File

@@ -0,0 +1,89 @@
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Shared.CartridgeLoader;
using Content.Shared.CartridgeLoader.Cartridges;
using Content.Shared.Popups;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.CartridgeLoader.Cartridges;
public sealed class NetProbeCartridgeSystem : EntitySystem
{
[Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NetProbeCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
SubscribeLocalEvent<NetProbeCartridgeComponent, CartridgeAfterInteractEvent>(AfterInteract);
}
/// <summary>
/// The <see cref="CartridgeAfterInteractEvent" /> gets relayed to this system if the cartridge loader is running
/// the NetProbe program and someone clicks on something with it. <br/>
/// <br/>
/// Saves name, address... etc. of the device that was clicked into a list on the component when the device isn't already present in that list
/// </summary>
private void AfterInteract(EntityUid uid, NetProbeCartridgeComponent component, CartridgeAfterInteractEvent args)
{
if (args.InteractEvent.Handled || !args.InteractEvent.CanReach || !args.InteractEvent.Target.HasValue)
return;
var target = args.InteractEvent.Target.Value;
DeviceNetworkComponent? networkComponent = default;
if (!Resolve(target, ref networkComponent, false))
return;
//Ceck if device is already present in list
foreach (var probedDevice in component.ProbedDevices)
{
if (probedDevice.Address == networkComponent.Address)
return;
}
//Play scanning sound with slightly randomized pitch
//Why is there no NextFloat(float min, float max)???
var audioParams = AudioParams.Default.WithVolume(-2f).WithPitchScale((float)_random.Next(12, 21) / 10);
_audioSystem.Play(component.SoundScan, Filter.Pvs(args.InteractEvent.User), target, audioParams);
_popupSystem.PopupCursor(Loc.GetString("net-probe-scan", ("device", target)), Filter.Entities(args.InteractEvent.User));
//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.ProbedDevices.Count >= component.MaxSavedDevices)
component.ProbedDevices.RemoveAt(0);
var device = new ProbedNetworkDevice(
Name(target),
networkComponent.Address,
networkComponent.ReceiveFrequency?.FrequencyToString() ?? string.Empty,
networkComponent.DeviceNetId.DeviceNetIdToLocalizedName()
);
component.ProbedDevices.Add(device);
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, NetProbeCartridgeComponent component, CartridgeUiReadyEvent args)
{
UpdateUiState(uid, args.Loader, component);
}
private void UpdateUiState(EntityUid uid, EntityUid loaderUid, NetProbeCartridgeComponent? component)
{
if (!Resolve(uid, ref component))
return;
var state = new NetProbeUiState(component.ProbedDevices);
_cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state);
}
}

View File

@@ -1,3 +1,6 @@
using Content.Server.DeviceNetwork.Components;
using Robust.Shared.Utility;
namespace Content.Server.DeviceNetwork
{
/// <summary>
@@ -35,5 +38,37 @@ namespace Content.Server.DeviceNetwork
public const string StateEnabled = "state_enabled";
#endregion
#region DisplayHelpers
/// <summary>
/// Converts the unsigned int to string and inserts a number before the last digit
/// </summary>
public static string FrequencyToString(this uint frequency)
{
var result = frequency.ToString();
if (result.Length <= 2)
return result + ".0";
return result.Insert(result.Length - 1, ".");
}
/// <summary>
/// Either returns the localized name representation of the corresponding <see cref="DeviceNetworkComponent.DeviceNetIdDefaults"/>
/// or converts the id to string
/// </summary>
public static string DeviceNetIdToLocalizedName(this int id)
{
if (!Enum.IsDefined(typeof(DeviceNetworkComponent.DeviceNetIdDefaults), id))
return id.ToString();
var result = ((DeviceNetworkComponent.DeviceNetIdDefaults) id).ToString();
var resultKebab = "device-net-id-" + CaseConversion.PascalToKebab(result);
return !Loc.TryGetString(resultKebab, out var name) ? result : name;
}
#endregion
}
}

View File

@@ -0,0 +1,34 @@
using Robust.Shared.Serialization;
namespace Content.Shared.CartridgeLoader.Cartridges;
[Serializable, NetSerializable]
public sealed class NetProbeUiState : BoundUserInterfaceState
{
/// <summary>
/// The list of probed network devices
/// </summary>
public List<ProbedNetworkDevice> ProbedDevices;
public NetProbeUiState(List<ProbedNetworkDevice> probedDevices)
{
ProbedDevices = probedDevices;
}
}
[Serializable, NetSerializable, DataRecord]
public sealed class ProbedNetworkDevice
{
public readonly string Name;
public readonly string Address;
public readonly string Frequency;
public readonly string NetId;
public ProbedNetworkDevice(string name, string address, string frequency, string netId)
{
Name = name;
Address = address;
Frequency = frequency;
NetId = netId;
}
}

View File

@@ -1,2 +1,5 @@
default-program-name = Program
notekeeper-program-name = Notekeeper
net-probe-program-name = NetProbe
net-probe-scan = Scanned {$device}!

View File

@@ -28,3 +28,11 @@ device-address-prefix-fire-alarm = Fir-
device-address-prefix-air-alarm = Air-
device-address-examine-message = The device's address is {$address}.
#Device net ID names
device-net-id-private = Private
device-net-id-wired = Wired
device-net-id-wireless = Wireless
device-net-id-apc = Apc
device-net-id-atmos-devices = Atmos Devices
device-net-id-reserved = Reserved

View File

@@ -59,6 +59,7 @@
- lanternextrabright
- PowerCellHigh
- PowerCellMicroreactor
- NetProbeCartridge
rareChance: 0.08
prototypes:
- FlashlightLantern

View File

@@ -20,4 +20,26 @@
state: book6
- type: NotekeeperCartridge
- type: entity
parent: BaseItem
id: NetProbeCartridge
name: NetProbe cartridge
description: A program for getting the address and frequency of network devices
components:
- type: Sprite
sprite: Objects/Devices/cartridge.rsi
state: cart-y
netsync: false
- type: Icon
sprite: Objects/Devices/cartridge.rsi
state: cart-y
- type: CartridgeUi
ui: !type:NetProbeUi
- type: Cartridge
programName: net-probe-program-name
icon:
sprite: Structures/Machines/server.rsi
state: server
- type: NetProbeCartridge