From 1644172b9b6e33688d9a74aa5109a8f79a2d5ab7 Mon Sep 17 00:00:00 2001 From: GreaseMonk <1354802+GreaseMonk@users.noreply.github.com> Date: Sun, 13 Oct 2024 18:30:48 +0200 Subject: [PATCH 01/17] Shuttle records system --- .../Systems/ShipyardSystem.Consoles.cs | 81 +++++++++++-------- .../Shipyard/Systems/ShipyardSystem.cs | 15 ++-- .../ShuttleRecords/ShuttleRecordsSystem.cs | 48 +++++++++++ .../Components/ShuttleDeedComponent.cs | 3 +- .../SharedShuttleRecordsSystem.cs | 11 +++ .../_NF/ShuttleRecords/ShuttleRecord.cs | 32 ++++++++ 6 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 Content.Server/_NF/ShuttleRecords/ShuttleRecordsSystem.cs create mode 100644 Content.Shared/_NF/ShuttleRecords/SharedShuttleRecordsSystem.cs create mode 100644 Content.Shared/_NF/ShuttleRecords/ShuttleRecord.cs diff --git a/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs b/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs index 80b1aada631..b2e2ced0c5c 100644 --- a/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs +++ b/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs @@ -36,12 +36,14 @@ using Content.Server.Shuttles.Components; using Content.Server.Station.Components; using System.Text.RegularExpressions; +using Content.Server._NF.ShuttleRecords; using Content.Shared.UserInterface; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Content.Shared.Access; using Content.Shared.Tiles; using Content.Server._NF.Smuggling.Components; +using Content.Shared._NF.ShuttleRecords; namespace Content.Server.Shipyard.Systems; @@ -64,13 +66,14 @@ public sealed partial class ShipyardSystem : SharedShipyardSystem [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly EntityManager _entityManager = default!; + [Dependency] private readonly ShuttleRecordsSystem _shuttleRecordsSystem = default!; public void InitializeConsole() { } - private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component, ShipyardConsolePurchaseMessage args) + private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComponent component, ShipyardConsolePurchaseMessage args) { if (args.Actor is not { Valid: true } player) return; @@ -78,7 +81,7 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component if (component.TargetIdSlot.ContainerSlot?.ContainedEntity is not { Valid: true } targetId) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-no-idcard")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } @@ -87,34 +90,34 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component if (idCard is null && voucher is null) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-no-idcard")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } if (HasComp(targetId)) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-already-deeded")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } - if (TryComp(uid, out var accessReaderComponent) && !_access.IsAllowed(player, uid, accessReaderComponent)) + if (TryComp(shipyardConsoleUid, out var accessReaderComponent) && !_access.IsAllowed(player, shipyardConsoleUid, accessReaderComponent)) { ConsolePopup(args.Actor, Loc.GetString("comms-console-permission-denied")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } if (!_prototypeManager.TryIndex(args.Vessel, out var vessel)) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-invalid-vessel", ("vessel", args.Vessel))); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } - if (!GetAvailableShuttles(uid, targetId: targetId).available.Contains(vessel.ID)) + if (!GetAvailableShuttles(shipyardConsoleUid, targetId: targetId).available.Contains(vessel.ID)) { - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(player):player} tried to purchase a vessel that was never available."); return; } @@ -123,17 +126,17 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component if (vessel.Price <= 0) return; - if (_station.GetOwningStation(uid) is not { Valid: true } station) + if (_station.GetOwningStation(shipyardConsoleUid) is not { Valid: true } station) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-invalid-station")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } if (!TryComp(player, out var bank)) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-no-bank")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } @@ -145,13 +148,13 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component if (voucher!.RedemptionsLeft <= 0) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-no-voucher-redemptions")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } else if (voucher!.ConsoleType != (ShipyardConsoleUiKey)args.UiKey) { ConsolePopup(args.Actor, Loc.GetString("shipyard-console-invalid-voucher-type")); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } voucher.RedemptionsLeft--; @@ -162,22 +165,27 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component if (bank.Balance <= vessel.Price) { ConsolePopup(args.Actor, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price))); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } if (!_bank.TryBankWithdraw(player, vessel.Price)) { ConsolePopup(args.Actor, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price))); - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } } - if (!TryPurchaseShuttle((EntityUid) station, vessel.ShuttlePath.ToString(), out var shuttle)) + if (!TryPurchaseShuttle((EntityUid) station, vessel.ShuttlePath.ToString(), out var shuttleEntityUid)) { - PlayDenySound(args.Actor, uid, component); + PlayDenySound(args.Actor, shipyardConsoleUid, component); + return; + } + if (!_entityManager.TryGetComponent(shuttleEntityUid, out var shuttle)) + { + PlayDenySound(args.Actor, shipyardConsoleUid, component); return; } EntityUid? shuttleStation = null; @@ -186,19 +194,19 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component { List gridUids = new() { - shuttle.Owner + shuttleEntityUid.Value }; shuttleStation = _station.InitializeNewStation(stationProto.Stations[vessel.ID], gridUids); var metaData = MetaData((EntityUid) shuttleStation); name = metaData.EntityName; - _shuttle.SetIFFColor(shuttle.Owner, new Color + _shuttle.SetIFFColor(shuttleEntityUid.Value, new Color { R = 10, G = 50, B = 100, A = 100 }); - _shuttle.AddIFFFlag(shuttle.Owner, IFFFlags.IsPlayerShuttle); + _shuttle.AddIFFFlag(shuttleEntityUid.Value, IFFFlags.IsPlayerShuttle); } if (TryComp(targetId, out var newCap)) @@ -211,10 +219,10 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component var deedID = EnsureComp(targetId); var shuttleOwner = Name(args.Actor).Trim(); - AssignShuttleDeedProperties(deedID, shuttle.Owner, name, shuttleOwner); + AssignShuttleDeedProperties(deedID, shuttleEntityUid.Value, name, shuttleOwner); - var deedShuttle = EnsureComp(shuttle.Owner); - AssignShuttleDeedProperties(deedShuttle, shuttle.Owner, name, shuttleOwner); + var deedShuttle = EnsureComp(shuttleEntityUid.Value); + AssignShuttleDeedProperties(deedShuttle, shuttleEntityUid.Value, name, shuttleOwner); if (!voucherUsed) { @@ -262,7 +270,7 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component // Shuttle setup: add protected grid status if needed. if (vessel.GridProtection != GridProtectionFlags.None) { - var prot = EnsureComp(shuttle.Owner); + var prot = EnsureComp(shuttleEntityUid.Value); if (vessel.GridProtection.HasFlag(GridProtectionFlags.FloorRemoval)) prot.PreventFloorRemoval = true; if (vessel.GridProtection.HasFlag(GridProtectionFlags.FloorPlacement)) @@ -286,17 +294,26 @@ private void OnPurchaseMessage(EntityUid uid, ShipyardConsoleComponent component sellValue -= CalculateSalesTax(component, sellValue); } - SendPurchaseMessage(uid, player, name, component.ShipyardChannel, secret: false); + SendPurchaseMessage(shipyardConsoleUid, player, name, component.ShipyardChannel, secret: false); if (component.SecretShipyardChannel is { } secretChannel) - SendPurchaseMessage(uid, player, name, secretChannel, secret: true); + SendPurchaseMessage(shipyardConsoleUid, player, name, secretChannel, secret: true); - PlayConfirmSound(args.Actor, uid, component); + PlayConfirmSound(args.Actor, shipyardConsoleUid, component); if (voucherUsed) - _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} purchased shuttle {ToPrettyString(shuttle.Owner)} with a voucher via {ToPrettyString(component.Owner)}"); + _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} purchased shuttle {ToPrettyString(shuttleEntityUid.Value)} with a voucher via {ToPrettyString(component.Owner)}"); else - _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} purchased shuttle {ToPrettyString(shuttle.Owner)} for {vessel.Price} credits via {ToPrettyString(component.Owner)}"); - - RefreshState(uid, bank.Balance, true, name, sellValue, targetId, (ShipyardConsoleUiKey) args.UiKey, voucherUsed); + _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} purchased shuttle {ToPrettyString(shuttleEntityUid.Value)} for {vessel.Price} credits via {ToPrettyString(component.Owner)}"); + + _shuttleRecordsSystem.AddRecord( + new ShuttleRecord( + name: deedShuttle.ShuttleName ?? "", + suffix: deedShuttle.ShuttleNameSuffix ?? "", + ownerName: shuttleOwner, + entityUid: _entityManager.GetNetEntity(shuttleEntityUid.Value) + ), + shipyardConsoleUid + ); + RefreshState(shipyardConsoleUid, bank.Balance, true, name, sellValue, targetId, (ShipyardConsoleUiKey) args.UiKey, voucherUsed); } private void TryParseShuttleName(ShuttleDeedComponent deed, string name) diff --git a/Content.Server/Shipyard/Systems/ShipyardSystem.cs b/Content.Server/Shipyard/Systems/ShipyardSystem.cs index 8efe169d22a..1d909ea45bb 100644 --- a/Content.Server/Shipyard/Systems/ShipyardSystem.cs +++ b/Content.Server/Shipyard/Systems/ShipyardSystem.cs @@ -114,11 +114,12 @@ private void SetShipyardEnabled(bool value) /// /// The ID of the station to dock the shuttle to /// The path to the shuttle file to load. Must be a grid file! - public bool TryPurchaseShuttle(EntityUid stationUid, string shuttlePath, [NotNullWhen(true)] out ShuttleComponent? shuttle) + /// The EntityUid of the shuttle that was purchased + public bool TryPurchaseShuttle(EntityUid stationUid, string shuttlePath, [NotNullWhen(true)] out EntityUid? shuttleEntityUid) { - if (!TryComp(stationUid, out var stationData) || !TryAddShuttle(shuttlePath, out var shuttleGrid) || !TryComp(shuttleGrid, out shuttle)) + if (!TryComp(stationUid, out var stationData) || !TryAddShuttle(shuttlePath, out var shuttleGrid) || !TryComp(shuttleGrid, out var shuttleComponent)) { - shuttle = null; + shuttleEntityUid = null; return false; } @@ -129,14 +130,14 @@ public bool TryPurchaseShuttle(EntityUid stationUid, string shuttlePath, [NotNul if (targetGrid == null) //how are we even here with no station grid { _mapManager.DeleteGrid((EntityUid) shuttleGrid); - shuttle = null; + shuttleEntityUid = null; return false; } - _sawmill.Info($"Shuttle {shuttlePath} was purchased at {ToPrettyString((EntityUid) stationUid)} for {price:f2}"); + _sawmill.Info($"Shuttle {shuttlePath} was purchased at {ToPrettyString(stationUid)} for {price:f2}"); //can do TryFTLDock later instead if we need to keep the shipyard map paused - _shuttle.TryFTLDock(shuttleGrid.Value, shuttle, targetGrid.Value); - + _shuttle.TryFTLDock(shuttleGrid.Value, shuttleComponent, targetGrid.Value); + shuttleEntityUid = shuttleGrid; return true; } diff --git a/Content.Server/_NF/ShuttleRecords/ShuttleRecordsSystem.cs b/Content.Server/_NF/ShuttleRecords/ShuttleRecordsSystem.cs new file mode 100644 index 00000000000..b48ec03c3af --- /dev/null +++ b/Content.Server/_NF/ShuttleRecords/ShuttleRecordsSystem.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server._NF.ShuttleRecords.Components; +using Content.Server.Popups; +using Content.Server.Station.Systems; +using Content.Shared._NF.ShuttleRecords; +using Robust.Server.GameObjects; + +namespace Content.Server._NF.ShuttleRecords; + +public sealed partial class ShuttleRecordsSystem: SharedShuttleRecordsSystem +{ + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + InitializeShuttleRecords(); + } + + /** + * Adds a record to the shuttle records list. + * The record to add. + * The entityUid of any grid entity to which we should add the record to. + */ + public void AddRecord(ShuttleRecord record, EntityUid anyGridEntityUid) + { + if (!TryGetShuttleRecordsDataComponent(anyGridEntityUid, out var component)) + return; + + component.ShuttleRecordsList.Add(record); + } + + public bool TryGetShuttleRecordsDataComponent(EntityUid consoleEntityUid, [NotNullWhen(true)] out ShuttleRecordsDataComponent? component) + { + var station = _station.GetOwningStation(consoleEntityUid); + if (station is null) + { + component = null; + return false; + } + + _entityManager.EnsureComponent(station.Value, out component); + return true; + } +} diff --git a/Content.Shared/Shipyard/Components/ShuttleDeedComponent.cs b/Content.Shared/Shipyard/Components/ShuttleDeedComponent.cs index c314992e8be..fbc96bc17e7 100644 --- a/Content.Shared/Shipyard/Components/ShuttleDeedComponent.cs +++ b/Content.Shared/Shipyard/Components/ShuttleDeedComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared._NF.ShuttleRecords; using Robust.Shared.GameStates; namespace Content.Shared.Shipyard.Components; @@ -5,7 +6,7 @@ namespace Content.Shared.Shipyard.Components; /// /// Tied to an ID card when a ship is purchased. 1 ship per captain. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedShipyardSystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(SharedShipyardSystem), typeof(SharedShuttleRecordsSystem))] public sealed partial class ShuttleDeedComponent : Component { public const int MaxNameLength = 30; diff --git a/Content.Shared/_NF/ShuttleRecords/SharedShuttleRecordsSystem.cs b/Content.Shared/_NF/ShuttleRecords/SharedShuttleRecordsSystem.cs new file mode 100644 index 00000000000..a95ea57ec7f --- /dev/null +++ b/Content.Shared/_NF/ShuttleRecords/SharedShuttleRecordsSystem.cs @@ -0,0 +1,11 @@ +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared._NF.ShuttleRecords; + +public abstract class SharedShuttleRecordsSystem : EntitySystem +{ + // These dependencies are eventually needed for the consoles that are made for this system. + [Dependency] protected readonly ItemSlotsSystem _itemSlotsSystem = default!; + [Dependency] protected readonly SharedAudioSystem _audioSystem = default!; +} diff --git a/Content.Shared/_NF/ShuttleRecords/ShuttleRecord.cs b/Content.Shared/_NF/ShuttleRecords/ShuttleRecord.cs new file mode 100644 index 00000000000..037434fa801 --- /dev/null +++ b/Content.Shared/_NF/ShuttleRecords/ShuttleRecord.cs @@ -0,0 +1,32 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._NF.ShuttleRecords; + +/** + * A record of a shuttle that had been purchased. + * This class is NOT a indication that the shuttle is still in the game, merely a transaction record of it. + */ +[Virtual, NetSerializable, Serializable] +public class ShuttleRecord( + string name, + string suffix, + string ownerName, + NetEntity entityUid +) +{ + [ViewVariables] + public string Name { get; set; } = name; + + [ViewVariables] + public string? Suffix { get; set; } = suffix; + + [ViewVariables] + public string OwnerName { get; set; } = ownerName; + + /** + * Entity may become deleted when the ship gets sold. + * Use _entityManager.EntityExists(EntityUid) to check if the entity still exists. + */ + [ViewVariables] + public NetEntity EntityUid { get; set; } = entityUid; +} From 1c33daa5b9fea52e0e5aa0d8ac64e7cbf0ebd794 Mon Sep 17 00:00:00 2001 From: GreaseMonk <1354802+GreaseMonk@users.noreply.github.com> Date: Sun, 13 Oct 2024 18:32:01 +0200 Subject: [PATCH 02/17] Shuttle records console --- ...ShuttleRecordsConsoleBoundUserInterface.cs | 50 +++++++++++ .../UI/ShuttleRecordDetailsControl.xaml | 12 +++ .../UI/ShuttleRecordDetailsControl.xaml.cs | 32 +++++++ .../UI/ShuttleRecordListItem.xaml | 5 ++ .../UI/ShuttleRecordListItem.xaml.cs | 26 ++++++ .../UI/ShuttleRecordsWindow.xaml | 39 ++++++++ .../UI/ShuttleRecordsWindow.xaml.cs | 79 +++++++++++++++++ .../Components/ShuttleRecordsDataComponent.cs | 14 +++ .../ShuttleRecordsSystem.Console.cs | 88 +++++++++++++++++++ .../ShuttleRecordsConsoleComponent.cs | 51 +++++++++++ .../ShuttleRecords/Events/CopyDeedMessage.cs | 12 +++ .../SharedShuttleRecordsSystem.Console.cs | 26 ++++++ .../ShuttleRecordsConsoleInterfaceState.cs | 11 +++ .../shuttle-records-system.ftl | 11 +++ .../Computers/computers_shuttle_records.yml | 49 +++++++++++ 15 files changed, 505 insertions(+) create mode 100644 Content.Client/_NF/ShuttleRecords/BUI/ShuttleRecordsConsoleBoundUserInterface.cs create mode 100644 Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordDetailsControl.xaml create mode 100644 Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordDetailsControl.xaml.cs create mode 100644 Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordListItem.xaml create mode 100644 Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordListItem.xaml.cs create mode 100644 Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordsWindow.xaml create mode 100644 Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordsWindow.xaml.cs create mode 100644 Content.Server/_NF/ShuttleRecords/Components/ShuttleRecordsDataComponent.cs create mode 100644 Content.Server/_NF/ShuttleRecords/ShuttleRecordsSystem.Console.cs create mode 100644 Content.Shared/_NF/ShuttleRecords/Components/ShuttleRecordsConsoleComponent.cs create mode 100644 Content.Shared/_NF/ShuttleRecords/Events/CopyDeedMessage.cs create mode 100644 Content.Shared/_NF/ShuttleRecords/SharedShuttleRecordsSystem.Console.cs create mode 100644 Content.Shared/_NF/ShuttleRecords/ShuttleRecordsConsoleInterfaceState.cs create mode 100644 Resources/Locale/en-US/_NF/shuttle-records/shuttle-records-system.ftl create mode 100644 Resources/Prototypes/_NF/Entities/Structures/Machines/Computers/computers_shuttle_records.yml diff --git a/Content.Client/_NF/ShuttleRecords/BUI/ShuttleRecordsConsoleBoundUserInterface.cs b/Content.Client/_NF/ShuttleRecords/BUI/ShuttleRecordsConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..81041f96349 --- /dev/null +++ b/Content.Client/_NF/ShuttleRecords/BUI/ShuttleRecordsConsoleBoundUserInterface.cs @@ -0,0 +1,50 @@ +using Content.Client._NF.ShuttleRecords.UI; +using Content.Shared._NF.ShuttleRecords; +using Content.Shared._NF.ShuttleRecords.Events; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client._NF.ShuttleRecords.BUI; + +public sealed class ShuttleRecordsConsoleBoundUserInterface( + EntityUid owner, + Enum uiKey +) : BoundUserInterface(owner, uiKey) +{ + private ShuttleRecordsWindow? _window; + + protected override void Open() + { + base.Open(); + _window ??= new ShuttleRecordsWindow(); + _window.OnCopyDeed += CopyDeed; + _window.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (_window == null || state is not ShuttleRecordsConsoleInterfaceState shuttleRecordsConsoleInterfaceState) + return; + + _window?.UpdateState(shuttleRecordsConsoleInterfaceState); + } + + /* + * This black magic code prevents multiple pop ups of the window from appearing. + */ + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + _window?.Dispose(); + } + + private void CopyDeed(ShuttleRecord shuttleRecord) + { + SendMessage(new CopyDeedMessage(shuttleRecord.EntityUid)); + Close(); + } + +} diff --git a/Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordDetailsControl.xaml b/Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordDetailsControl.xaml new file mode 100644 index 00000000000..b4386a7437e --- /dev/null +++ b/Content.Client/_NF/ShuttleRecords/UI/ShuttleRecordDetailsControl.xaml @@ -0,0 +1,12 @@ + + +