From 0df65e5c2abcd677eb4ae73869e30322f65caca6 Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Sun, 13 Nov 2022 22:36:00 +0100 Subject: [PATCH] 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 --- .../CartridgeLoader/Cartridges/NetProbeUi.cs | 28 ++++++ .../Cartridges/NetProbeUiFragment.xaml | 19 ++++ .../Cartridges/NetProbeUiFragment.xaml.cs | 77 ++++++++++++++++ .../Cartridges/NetProbeCartridgeComponent.cs | 25 ++++++ .../Cartridges/NetProbeCartridgeSystem.cs | 89 +++++++++++++++++++ .../DeviceNetwork/DeviceNetworkConstants.cs | 35 ++++++++ .../Cartridges/NetProbeUiState.cs | 34 +++++++ .../en-US/cartridge-loader/cartridges.ftl | 3 + .../Locale/en-US/devices/device-network.ftl | 8 ++ .../Markers/Spawners/Random/maintenance.yml | 1 + .../Entities/Objects/Devices/cartridges.yml | 22 +++++ 11 files changed, 341 insertions(+) create mode 100644 Content.Client/CartridgeLoader/Cartridges/NetProbeUi.cs create mode 100644 Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml create mode 100644 Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml.cs create mode 100644 Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs create mode 100644 Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs create mode 100644 Content.Shared/CartridgeLoader/Cartridges/NetProbeUiState.cs diff --git a/Content.Client/CartridgeLoader/Cartridges/NetProbeUi.cs b/Content.Client/CartridgeLoader/Cartridges/NetProbeUi.cs new file mode 100644 index 0000000000..0dfb9cd2f7 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/NetProbeUi.cs @@ -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); + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml new file mode 100644 index 0000000000..92eed2b916 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml.cs new file mode 100644 index 0000000000..5300040314 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/NetProbeUiFragment.xaml.cs @@ -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 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); + } +} diff --git a/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs b/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs new file mode 100644 index 0000000000..b484520622 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs @@ -0,0 +1,25 @@ +using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Shared.Audio; + +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent] +public sealed class NetProbeCartridgeComponent : Component +{ + /// + /// The list of probed network devices + /// + [DataField("probedDevices")] + public List ProbedDevices = new(); + + /// + /// Limits the amount of devices that can be saved + /// + [DataField("maxSavedDevices")] + public int MaxSavedDevices { get; set; } = 9; + + [DataField("soundScan")] + public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg"); +} + + diff --git a/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs new file mode 100644 index 0000000000..d1ba9bfd99 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs @@ -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(OnUiReady); + SubscribeLocalEvent(AfterInteract); + } + + /// + /// The gets relayed to this system if the cartridge loader is running + /// the NetProbe program and someone clicks on something with it.
+ ///
+ /// 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 + ///
+ 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); + } + + /// + /// This gets called when the ui fragment needs to be updated for the first time after activating + /// + 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); + } +} diff --git a/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs b/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs index f56d5a0096..fbf0898f72 100644 --- a/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs +++ b/Content.Server/DeviceNetwork/DeviceNetworkConstants.cs @@ -1,3 +1,6 @@ +using Content.Server.DeviceNetwork.Components; +using Robust.Shared.Utility; + namespace Content.Server.DeviceNetwork { /// @@ -35,5 +38,37 @@ namespace Content.Server.DeviceNetwork public const string StateEnabled = "state_enabled"; #endregion + + #region DisplayHelpers + + /// + /// Converts the unsigned int to string and inserts a number before the last digit + /// + public static string FrequencyToString(this uint frequency) + { + var result = frequency.ToString(); + if (result.Length <= 2) + return result + ".0"; + + return result.Insert(result.Length - 1, "."); + } + + /// + /// Either returns the localized name representation of the corresponding + /// or converts the id to string + /// + 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 } } diff --git a/Content.Shared/CartridgeLoader/Cartridges/NetProbeUiState.cs b/Content.Shared/CartridgeLoader/Cartridges/NetProbeUiState.cs new file mode 100644 index 0000000000..cc2cc66b85 --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/NetProbeUiState.cs @@ -0,0 +1,34 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[Serializable, NetSerializable] +public sealed class NetProbeUiState : BoundUserInterfaceState +{ + /// + /// The list of probed network devices + /// + public List ProbedDevices; + + public NetProbeUiState(List 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; + } +} diff --git a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl index 533c5c25a1..69411a4700 100644 --- a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl +++ b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl @@ -1,2 +1,5 @@ default-program-name = Program notekeeper-program-name = Notekeeper + +net-probe-program-name = NetProbe +net-probe-scan = Scanned {$device}! diff --git a/Resources/Locale/en-US/devices/device-network.ftl b/Resources/Locale/en-US/devices/device-network.ftl index 16e0e684f5..fe3025ffac 100644 --- a/Resources/Locale/en-US/devices/device-network.ftl +++ b/Resources/Locale/en-US/devices/device-network.ftl @@ -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 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index 07ad4ddcbb..9f9b41c160 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -59,6 +59,7 @@ - lanternextrabright - PowerCellHigh - PowerCellMicroreactor + - NetProbeCartridge rareChance: 0.08 prototypes: - FlashlightLantern diff --git a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml index 57a8378e18..1266002640 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml @@ -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 +