diff --git a/Content.Client/CartridgeLoader/CartridgeLoaderBoundUserInterface.cs b/Content.Client/CartridgeLoader/CartridgeLoaderBoundUserInterface.cs index e4d6a9fb8b..41a46c9c6a 100644 --- a/Content.Client/CartridgeLoader/CartridgeLoaderBoundUserInterface.cs +++ b/Content.Client/CartridgeLoader/CartridgeLoaderBoundUserInterface.cs @@ -1,6 +1,5 @@ -using Content.Client.UserInterface.Fragments; +using Content.Client.UserInterface.Fragments; using Content.Shared.CartridgeLoader; -using Robust.Client.GameObjects; using Robust.Client.UserInterface; namespace Content.Client.CartridgeLoader; @@ -67,7 +66,7 @@ public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface protected void ActivateCartridge(EntityUid cartridgeUid) { var message = new CartridgeLoaderUiMessage(_entManager.GetNetEntity(cartridgeUid), CartridgeUiMessageAction.Activate); - SendMessage(message); + SendPredictedMessage(message); } protected void DeactivateActiveCartridge() @@ -76,19 +75,19 @@ public abstract class CartridgeLoaderBoundUserInterface : BoundUserInterface return; var message = new CartridgeLoaderUiMessage(_entManager.GetNetEntity(_activeProgram.Value), CartridgeUiMessageAction.Deactivate); - SendMessage(message); + SendPredictedMessage(message); } protected void InstallCartridge(EntityUid cartridgeUid) { var message = new CartridgeLoaderUiMessage(_entManager.GetNetEntity(cartridgeUid), CartridgeUiMessageAction.Install); - SendMessage(message); + SendPredictedMessage(message); } protected void UninstallCartridge(EntityUid cartridgeUid) { var message = new CartridgeLoaderUiMessage(_entManager.GetNetEntity(cartridgeUid), CartridgeUiMessageAction.Uninstall); - SendMessage(message); + SendPredictedMessage(message); } private List<(EntityUid, CartridgeComponent)> GetCartridgeComponents(List programs) diff --git a/Content.Client/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Client/CartridgeLoader/CartridgeLoaderSystem.cs deleted file mode 100644 index e06a547814..0000000000 --- a/Content.Client/CartridgeLoader/CartridgeLoaderSystem.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Shared.CartridgeLoader; - -namespace Content.Client.CartridgeLoader; - -public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem -{ - //Empty client system for component replication -} diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs index 12b678d5d4..183de499a8 100644 --- a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs +++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs @@ -22,7 +22,7 @@ public sealed partial class LogProbeUi : UIFragment { var ev = new LogProbePrintMessage(); var message = new CartridgeUiMessage(ev); - ui.SendMessage(message); + ui.SendPredictedMessage(message); }; } diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs deleted file mode 100644 index 85a5659c4b..0000000000 --- a/Content.Client/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared.CartridgeLoader.Cartridges; - -namespace Content.Client.CartridgeLoader.Cartridges; - -public sealed class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSystem; diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs index ac08051c89..ca29fe8474 100644 --- a/Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs +++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs @@ -1,8 +1,6 @@ -using System.Linq; using Content.Client.UserInterface.Fragments; using Content.Shared.CartridgeLoader; using Content.Shared.CartridgeLoader.Cartridges; -using Robust.Client.GameObjects; using Robust.Client.UserInterface; namespace Content.Client.CartridgeLoader.Cartridges; @@ -44,7 +42,7 @@ public sealed partial class NanoTaskUi : UIFragment if (_fragment.Tasks.Find(task => task.Id == id) is not NanoTaskItemAndId task) return; - userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, new( + userInterface.SendPredictedMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, new( description: task.Data.Description, taskIsFor: task.Data.TaskIsFor, isTaskDone: !task.Data.IsTaskDone, @@ -53,22 +51,22 @@ public sealed partial class NanoTaskUi : UIFragment }; _popup.TaskSaved += (id, data) => { - userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, data))))); + userInterface.SendPredictedMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, data))))); _popup.Close(); }; _popup.TaskDeleted += id => { - userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskDeleteTask(id)))); + userInterface.SendPredictedMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskDeleteTask(id)))); _popup.Close(); }; _popup.TaskCreated += data => { - userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskAddTask(data)))); + userInterface.SendPredictedMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskAddTask(data)))); _popup.Close(); }; _popup.TaskPrinted += data => { - userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskPrintTask(data)))); + userInterface.SendPredictedMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskPrintTask(data)))); }; } diff --git a/Content.Client/CartridgeLoader/Cartridges/NotekeeperUi.cs b/Content.Client/CartridgeLoader/Cartridges/NotekeeperUi.cs index 1018d88cce..da7041f4dd 100644 --- a/Content.Client/CartridgeLoader/Cartridges/NotekeeperUi.cs +++ b/Content.Client/CartridgeLoader/Cartridges/NotekeeperUi.cs @@ -1,7 +1,6 @@ using Content.Client.UserInterface.Fragments; using Content.Shared.CartridgeLoader; using Content.Shared.CartridgeLoader.Cartridges; -using Robust.Client.GameObjects; using Robust.Client.UserInterface; namespace Content.Client.CartridgeLoader.Cartridges; @@ -34,6 +33,6 @@ public sealed partial class NotekeeperUi : UIFragment { var notekeeperMessage = new NotekeeperUiMessageEvent(action, note); var message = new CartridgeUiMessage(notekeeperMessage); - userInterface.SendMessage(message); + userInterface.SendPredictedMessage(message); } } diff --git a/Content.Client/PDA/PdaMenu.xaml b/Content.Client/PDA/PdaMenu.xaml index 8c9b4ae2ee..c5949aa58d 100644 --- a/Content.Client/PDA/PdaMenu.xaml +++ b/Content.Client/PDA/PdaMenu.xaml @@ -48,12 +48,12 @@ - + programs) { - ProgramList.RemoveAllChildren(); - if (programs.Count == 0) { + ProgramList.RemoveAllChildren(); + ProgramList.AddChild(new Label() { Text = Loc.GetString("comp-pda-io-no-programs-available"), @@ -215,50 +215,43 @@ namespace Content.Client.PDA return; } - var row = CreateProgramListRow(); - var itemCount = 1; - ProgramList.AddChild(row); - - foreach (var (uid, component) in programs) + if (ProgramList.ChildCount >= 1 && ProgramList.Children[0] is Label label) { - //Create a new row every second program item starting from the first - if (itemCount % 2 != 0) + label.Orphan(); + } + + while (ProgramList.ChildCount > programs.Count) + { + ProgramList.Children[ProgramList.ChildCount - 1].Orphan(); + } + + for (var i = 0; i < programs.Count; i++) + { + var cartridge = programs[i]; + + if (i < ProgramList.ChildCount) { - row = CreateProgramListRow(); - ProgramList.AddChild(row); + var currentItem = ProgramList.Children[i]; + if (currentItem is PdaProgramItem programItem) + { + programItem.SetCartridge(cartridge); + continue; + } + + DebugTools.Assert(i == ProgramList.ChildCount-1); + currentItem.Orphan(); } - var item = new PdaProgramItem(); - - if (component.Icon is not null) - item.Icon.SetFromSpriteSpecifier(component.Icon); - - item.OnPressed += _ => OnProgramItemPressed?.Invoke(uid); - - switch (component.InstallationStatus) - { - case InstallationStatus.Cartridge: - item.InstallButton.Visible = true; - item.InstallButton.Text = Loc.GetString("cartridge-bound-user-interface-install-button"); - item.InstallButton.OnPressed += _ => OnInstallButtonPressed?.Invoke(uid); - break; - case InstallationStatus.Installed: - item.InstallButton.Visible = true; - item.InstallButton.Text = Loc.GetString("cartridge-bound-user-interface-uninstall-button"); - item.InstallButton.OnPressed += _ => OnUninstallButtonPressed?.Invoke(uid); - break; - } - - item.ProgramName.Text = Loc.GetString(component.ProgramName); - item.SetHeight = 20; - row.AddChild(item); - - itemCount++; + var item = new PdaProgramItem(cartridge); + item.OnProgramItemPressed += uid => OnProgramItemPressed?.Invoke(uid); + item.OnUninstallButtonPressed += uid => OnUninstallButtonPressed?.Invoke(uid); + item.OnInstallButtonPressed += uid => OnInstallButtonPressed?.Invoke(uid); + ProgramList.AddChild(item); } //Add a filler item to the last row when it only contains one item - if (itemCount % 2 == 0) - row.AddChild(new Control() { HorizontalExpand = true }); + if (programs.Count % 2 == 0) + ProgramList.AddChild(new Control() { HorizontalExpand = true }); } /// @@ -319,15 +312,6 @@ namespace Content.Client.PDA _currentView = view; } - private static BoxContainer CreateProgramListRow() - { - return new BoxContainer() - { - Orientation = BoxContainer.LayoutOrientation.Horizontal, - HorizontalExpand = true - }; - } - private void HideAllViews() { var views = ViewContainer.Children; diff --git a/Content.Client/PDA/PdaProgramItem.xaml b/Content.Client/PDA/PdaProgramItem.xaml index 9f9fba938f..c69541e50f 100644 --- a/Content.Client/PDA/PdaProgramItem.xaml +++ b/Content.Client/PDA/PdaProgramItem.xaml @@ -1,17 +1,17 @@ - + - + - + diff --git a/Content.Client/PDA/PdaProgramItem.xaml.cs b/Content.Client/PDA/PdaProgramItem.xaml.cs index 9709f5706b..64dd300994 100644 --- a/Content.Client/PDA/PdaProgramItem.xaml.cs +++ b/Content.Client/PDA/PdaProgramItem.xaml.cs @@ -1,4 +1,5 @@ -using Robust.Client.AutoGenerated; +using Content.Shared.CartridgeLoader; +using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; @@ -12,6 +13,12 @@ public sealed partial class PdaProgramItem : ContainerButton public const string NormalBgColor = "#313138"; public const string HoverColor = "#3E6C45"; + private Entity _cartridge; + + public event Action? OnProgramItemPressed; + public event Action? OnUninstallButtonPressed; + public event Action? OnInstallButtonPressed; + private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = Color.FromHex("#25252a"), @@ -23,10 +30,38 @@ public sealed partial class PdaProgramItem : ContainerButton set => _styleBox.BackgroundColor = value; } - public PdaProgramItem() + public PdaProgramItem(Entity program) { RobustXamlLoader.Load(this); + + SetCartridge(program); Panel.PanelOverride = _styleBox; + + OnPressed += OnSelfPressed; + InstallButton.OnPressed += OnInstallPressed; + } + + private void OnSelfPressed(ButtonEventArgs args) + { + OnProgramItemPressed?.Invoke(_cartridge); + } + + private void OnInstallPressed(ButtonEventArgs args) + { + switch (_cartridge.Comp.InstallationStatus) + { + case InstallationStatus.Cartridge: + OnInstallButtonPressed?.Invoke(_cartridge.Owner); + break; + + case InstallationStatus.Installed: + OnUninstallButtonPressed?.Invoke(_cartridge.Owner); + break; + + case InstallationStatus.Readonly: + default: + break; + } } protected override void Draw(DrawingHandleScreen handle) @@ -35,6 +70,29 @@ public sealed partial class PdaProgramItem : ContainerButton if (TryGetStyleProperty(StylePropertyBgColor, out var bgColor)) BackgroundColor = bgColor; + } + public void SetCartridge(Entity program) + { + if (program.Comp.Icon is { } icon) + Icon.SetFromSpriteSpecifier(icon); + + _cartridge = program; + ProgramName.Text = Loc.GetString(program.Comp.ProgramName); + + switch (program.Comp.InstallationStatus) + { + case InstallationStatus.Cartridge: + InstallButton.Visible = true; + InstallButton.Text = Loc.GetString("cartridge-bound-user-interface-install-button"); + break; + case InstallationStatus.Installed: + InstallButton.Visible = true; + InstallButton.Text = Loc.GetString("cartridge-bound-user-interface-uninstall-button"); + break; + case InstallationStatus.Readonly: + InstallButton.Visible = false; + break; + } } } diff --git a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs deleted file mode 100644 index b82292e8dc..0000000000 --- a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs +++ /dev/null @@ -1,521 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Content.Server.DeviceNetwork.Systems; -using Content.Server.PDA; -using Content.Shared.CartridgeLoader; -using Content.Shared.DeviceNetwork.Events; -using Content.Shared.Interaction; -using Robust.Server.Containers; -using Robust.Server.GameObjects; -using Robust.Shared.Containers; -using Robust.Shared.Map; -using Robust.Shared.Player; - -namespace Content.Server.CartridgeLoader; - -public sealed partial class CartridgeLoaderSystem : SharedCartridgeLoaderSystem -{ - [Dependency] private ContainerSystem _containerSystem = default!; - [Dependency] private UserInterfaceSystem _userInterfaceSystem = default!; - [Dependency] private PdaSystem _pda = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMapInit); - - SubscribeLocalEvent(OnPacketReceived); - SubscribeLocalEvent(OnUsed); - SubscribeLocalEvent(OnLoaderUiMessage); - SubscribeLocalEvent(OnUiMessage); - } - - public IReadOnlyList GetInstalled(EntityUid uid, ContainerManagerComponent? comp = null) - { - if (_containerSystem.TryGetContainer(uid, InstalledContainerId, out var container, comp)) - return container.ContainedEntities; - - return Array.Empty(); - } - - public bool TryGetProgram( - EntityUid uid, - [NotNullWhen(true)] out EntityUid? programUid, - [NotNullWhen(true)] out T? program, - bool installedOnly = false, - CartridgeLoaderComponent? loader = null, - ContainerManagerComponent? containerManager = null) where T : IComponent - { - program = default; - programUid = null; - - if (!_containerSystem.TryGetContainer(uid, InstalledContainerId, out var container, containerManager)) - return false; - - foreach (var prog in container.ContainedEntities) - { - if (!TryComp(prog, out program)) - continue; - - programUid = prog; - return true; - } - - if (installedOnly) - return false; - - if (!Resolve(uid, ref loader) || !TryComp(loader.CartridgeSlot.Item, out program)) - return false; - - programUid = loader.CartridgeSlot.Item; - return true; - } - - public bool TryGetProgram( - EntityUid uid, - [NotNullWhen(true)] out EntityUid? programUid, - bool installedOnly = false, - CartridgeLoaderComponent? loader = null, - ContainerManagerComponent? containerManager = null) where T : IComponent - { - return TryGetProgram(uid, out programUid, out _, installedOnly, loader, containerManager); - } - - public bool HasProgram( - EntityUid uid, - bool installedOnly = false, - CartridgeLoaderComponent? loader = null, - ContainerManagerComponent? containerManager = null) where T : IComponent - { - return TryGetProgram(uid, out _, out _, installedOnly, loader, containerManager); - } - - /// - /// Updates the cartridge loaders ui state. - /// - /// - /// Because the cartridge loader integrates with the ui of the entity using it, the entities ui state needs to inherit from - /// and use this method to update its state so the cartridge loaders state can be added to it. - /// - /// - public void UpdateUiState(EntityUid loaderUid, ICommonSession? session, CartridgeLoaderComponent? loader) - { - if (!Resolve(loaderUid, ref loader)) - return; - - if (!_userInterfaceSystem.HasUi(loaderUid, loader.UiKey)) - return; - - var programs = GetAvailablePrograms(loaderUid, loader); - var state = new CartridgeLoaderUiState(programs, GetNetEntity(loader.ActiveProgram)); - _userInterfaceSystem.SetUiState(loaderUid, loader.UiKey, state); - } - - /// - /// Updates the programs ui state - /// - /// The cartridge loaders entity uid - /// The programs ui state. Programs should use their own ui state class inheriting from - /// The players session - /// The cartridge loader component - /// - /// This method is called "UpdateCartridgeUiState" but cartridges and a programs are the same. A cartridge is just a program as a visible item. - /// - /// - public void UpdateCartridgeUiState(EntityUid loaderUid, BoundUserInterfaceState state, ICommonSession? session = default!, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return; - - if (_userInterfaceSystem.HasUi(loaderUid, loader.UiKey)) - _userInterfaceSystem.SetUiState(loaderUid, loader.UiKey, state); - } - - /// - /// Returns a list of all installed programs and the inserted cartridge if it isn't already installed - /// - /// The cartridge loaders uid - /// The cartridge loader component - /// A list of all the available program entity ids - public List GetAvailablePrograms(EntityUid uid, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(uid, ref loader)) - return new List(); - - var available = GetNetEntityList(GetInstalled(uid)); - - if (loader.CartridgeSlot.Item is not { } cartridge) - return available; - - // TODO exclude duplicate programs. Or something I dunno I CBF fixing this mess. - available.Add(GetNetEntity(cartridge)); - return available; - } - - /// - /// Installs a cartridge by spawning an invisible version of the cartridges prototype into the cartridge loaders program container program container - /// - /// The cartridge loader uid - /// The uid of the cartridge to be installed - /// The cartridge loader component - /// Whether installing the cartridge was successful - public bool InstallCartridge(EntityUid loaderUid, EntityUid cartridgeUid, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return false; - - if (!TryComp(cartridgeUid, out CartridgeComponent? loadedCartridge)) - return false; - - foreach (var program in GetInstalled(loaderUid)) - { - if (TryComp(program, out CartridgeComponent? installedCartridge) && installedCartridge.ProgramName == loadedCartridge.ProgramName) - return false; - } - - //This will eventually be replaced by serializing and deserializing the cartridge to copy it when something needs - //the data on the cartridge to carry over when installing - - // For anyone stumbling onto this: Do not do this or I will cut you. - var prototypeId = Prototype(cartridgeUid)?.ID; - return prototypeId != null && InstallProgram(loaderUid, prototypeId, loader: loader); - } - - /// - /// Installs a program by its prototype - /// - /// The cartridge loader uid - /// The prototype name - /// Whether the program can be deinstalled or not - /// The cartridge loader component - /// Whether installing the cartridge was successful - public bool InstallProgram(EntityUid loaderUid, string prototype, bool deinstallable = true, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return false; - - if (!_containerSystem.TryGetContainer(loaderUid, InstalledContainerId, out var container)) - return false; - - if (container.Count >= loader.DiskSpace) - return false; - - var ev = new ProgramInstallationAttempt(loaderUid, prototype); - RaiseLocalEvent(ref ev); - - if (ev.Cancelled) - return false; - - var installedProgram = Spawn(prototype, new EntityCoordinates(loaderUid, 0, 0)); - if (!TryComp(installedProgram, out CartridgeComponent? cartridge)) - return false; - - _containerSystem.Insert(installedProgram, container); - - UpdateCartridgeInstallationStatus(installedProgram, deinstallable ? InstallationStatus.Installed : InstallationStatus.Readonly, cartridge); - cartridge.LoaderUid = loaderUid; - - RaiseLocalEvent(installedProgram, new CartridgeAddedEvent(loaderUid)); - UpdateUserInterfaceState(loaderUid, loader); - return true; - } - - /// - /// Uninstalls a program using its uid - /// - /// The cartridge loader uid - /// The uid of the program to be uninstalled - /// The cartridge loader component - /// Whether uninstalling the program was successful - public bool UninstallProgram(EntityUid loaderUid, EntityUid programUid, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return false; - - if (!GetInstalled(loaderUid).Contains(programUid)) - return false; - - if (TryComp(programUid, out CartridgeComponent? cartridge)) - cartridge.LoaderUid = null; - - if (loader.ActiveProgram == programUid) - loader.ActiveProgram = null; - - loader.BackgroundPrograms.Remove(programUid); - QueueDel(programUid); - UpdateUserInterfaceState(loaderUid, loader); - return true; - } - - /// - /// Activates a program or cartridge and displays its ui fragment. Deactivates any previously active program. - /// - public void ActivateProgram(EntityUid loaderUid, EntityUid programUid, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return; - - if (!HasProgram(loaderUid, programUid, loader)) - return; - - if (loader.ActiveProgram.HasValue) - DeactivateProgram(loaderUid, programUid, loader); - - if (!loader.BackgroundPrograms.Contains(programUid)) - RaiseLocalEvent(programUid, new CartridgeActivatedEvent(loaderUid)); - - loader.ActiveProgram = programUid; - UpdateUserInterfaceState(loaderUid, loader); - } - - /// - /// Deactivates the currently active program or cartridge. - /// - public void DeactivateProgram(EntityUid loaderUid, EntityUid programUid, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return; - - if (!HasProgram(loaderUid, programUid, loader) || loader.ActiveProgram != programUid) - return; - - if (!loader.BackgroundPrograms.Contains(programUid)) - RaiseLocalEvent(programUid, new CartridgeDeactivatedEvent(programUid)); - - loader.ActiveProgram = default; - UpdateUserInterfaceState(loaderUid, loader); - } - - /// - /// Registers the given program as a running in the background. Programs running in the background will receive certain events like device net packets but not ui messages - /// - /// - /// Programs wanting to use this functionality will have to provide a way to register and unregister themselves as background programs through their ui fragment. - /// - public void RegisterBackgroundProgram(EntityUid loaderUid, EntityUid cartridgeUid, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return; - - if (!HasProgram(loaderUid, cartridgeUid, loader)) - return; - - if (loader.ActiveProgram != cartridgeUid) - RaiseLocalEvent(cartridgeUid, new CartridgeActivatedEvent(loaderUid)); - - loader.BackgroundPrograms.Add(cartridgeUid); - } - - /// - /// Unregisters the given program as running in the background - /// - public void UnregisterBackgroundProgram(EntityUid loaderUid, EntityUid cartridgeUid, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return; - - if (!HasProgram(loaderUid, cartridgeUid, loader)) - return; - - if (loader.ActiveProgram != cartridgeUid) - RaiseLocalEvent(cartridgeUid, new CartridgeDeactivatedEvent(loaderUid)); - - loader.BackgroundPrograms.Remove(cartridgeUid); - } - - public void SendNotification(EntityUid loaderUid, string header, string message, CartridgeLoaderComponent? loader = default!) - { - if (!Resolve(loaderUid, ref loader)) - return; - - if (!loader.NotificationsEnabled) - return; - - var args = new CartridgeLoaderNotificationSentEvent(header, message); - RaiseLocalEvent(loaderUid, ref args); - } - - protected override void OnItemInserted(EntityUid uid, CartridgeLoaderComponent loader, EntInsertedIntoContainerMessage args) - { - if (args.Container.ID != InstalledContainerId && args.Container.ID != loader.CartridgeSlot.ID) - return; - - if (TryComp(args.Entity, out CartridgeComponent? cartridge)) - cartridge.LoaderUid = uid; - - RaiseLocalEvent(args.Entity, new CartridgeAddedEvent(uid)); - base.OnItemInserted(uid, loader, args); - } - - protected override void OnItemRemoved(EntityUid uid, CartridgeLoaderComponent loader, EntRemovedFromContainerMessage args) - { - if (args.Container.ID != InstalledContainerId && args.Container.ID != loader.CartridgeSlot.ID) - return; - - var deactivate = loader.BackgroundPrograms.Remove(args.Entity); - - if (loader.ActiveProgram == args.Entity) - { - loader.ActiveProgram = default; - deactivate = true; - } - - if (deactivate) - RaiseLocalEvent(args.Entity, new CartridgeDeactivatedEvent(uid)); - - if (TryComp(args.Entity, out CartridgeComponent? cartridge)) - cartridge.LoaderUid = null; - - RaiseLocalEvent(args.Entity, new CartridgeRemovedEvent(uid)); - base.OnItemRemoved(uid, loader, args); - - _pda.UpdatePdaUi(uid); - } - - /// - /// Installs programs from the list of preinstalled programs - /// - private void OnMapInit(EntityUid uid, CartridgeLoaderComponent component, MapInitEvent args) - { - // TODO remove this and use container fill. - foreach (var prototype in component.PreinstalledPrograms) - { - InstallProgram(uid, prototype, deinstallable: false); - } - } - - private void OnUsed(EntityUid uid, CartridgeLoaderComponent component, AfterInteractEvent args) - { - RelayEvent(component, new CartridgeAfterInteractEvent(uid, args)); - } - - private void OnPacketReceived(EntityUid uid, CartridgeLoaderComponent component, DeviceNetworkPacketEvent args) - { - RelayEvent(component, new CartridgeDeviceNetPacketEvent(uid, args)); - } - - private void OnLoaderUiMessage(EntityUid loaderUid, CartridgeLoaderComponent component, CartridgeLoaderUiMessage message) - { - var cartridge = GetEntity(message.CartridgeUid); - - switch (message.Action) - { - case CartridgeUiMessageAction.Activate: - ActivateProgram(loaderUid, cartridge, component); - break; - case CartridgeUiMessageAction.Deactivate: - DeactivateProgram(loaderUid, cartridge, component); - break; - case CartridgeUiMessageAction.Install: - InstallCartridge(loaderUid, cartridge, component); - break; - case CartridgeUiMessageAction.Uninstall: - UninstallProgram(loaderUid, cartridge, component); - break; - case CartridgeUiMessageAction.UIReady: - if (component.ActiveProgram.HasValue) - RaiseLocalEvent(component.ActiveProgram.Value, new CartridgeUiReadyEvent(loaderUid)); - break; - default: - throw new ArgumentOutOfRangeException($"Unrecognized UI action passed from cartridge loader ui {message.Action}."); - } - } - - /// - /// Relays ui messages meant for cartridges to the currently active cartridge - /// - private void OnUiMessage(EntityUid uid, CartridgeLoaderComponent component, CartridgeUiMessage args) - { - var cartridgeEvent = args.MessageEvent; - cartridgeEvent.User = args.Actor; - cartridgeEvent.LoaderUid = GetNetEntity(uid); - cartridgeEvent.Actor = args.Actor; - - RelayEvent(component, cartridgeEvent, true); - } - - /// - /// Relays events to the currently active program and and programs running in the background. - /// Skips background programs if "skipBackgroundPrograms" is set to true - /// - /// The cartritge loader component - /// The event to be relayed - /// Whether to skip relaying the event to programs running in the background - private void RelayEvent(CartridgeLoaderComponent loader, TEvent args, bool skipBackgroundPrograms = false) where TEvent : notnull - { - if (loader.ActiveProgram.HasValue) - RaiseLocalEvent(loader.ActiveProgram.Value, args); - - if (skipBackgroundPrograms) - return; - - foreach (var program in loader.BackgroundPrograms) - { - //Prevent programs registered as running in the background receiving events twice if they are active - if (loader.ActiveProgram.HasValue && loader.ActiveProgram.Value.Equals(program)) - continue; - - RaiseLocalEvent(program, args); - } - } - - /// - /// Shortcut for updating the loaders user interface state without passing in a subtype of - /// like the does when updating its ui state - /// - /// - private void UpdateUserInterfaceState(EntityUid loaderUid, CartridgeLoaderComponent loader) - { - UpdateUiState(loaderUid, null, loader); - } - - private void UpdateCartridgeInstallationStatus(EntityUid cartridgeUid, InstallationStatus installationStatus, CartridgeComponent cartridgeComponent) - { - cartridgeComponent.InstallationStatus = installationStatus; - Dirty(cartridgeUid, cartridgeComponent); - } - - private bool HasProgram(EntityUid loader, EntityUid program, CartridgeLoaderComponent component) - { - return component.CartridgeSlot.Item == program || GetInstalled(loader).Contains(program); - } -} - -/// -/// Gets sent to running programs when the cartridge loader receives a device net package -/// -/// -public sealed class CartridgeDeviceNetPacketEvent : EntityEventArgs -{ - public readonly EntityUid Loader; - public readonly DeviceNetworkPacketEvent PacketEvent; - - public CartridgeDeviceNetPacketEvent(EntityUid loader, DeviceNetworkPacketEvent packetEvent) - { - Loader = loader; - PacketEvent = packetEvent; - } -} - -/// -/// Gets sent to running programs when the cartridge loader receives an after interact event -/// -/// -public sealed class CartridgeAfterInteractEvent : EntityEventArgs -{ - public readonly EntityUid Loader; - public readonly AfterInteractEvent InteractEvent; - - public CartridgeAfterInteractEvent(EntityUid loader, AfterInteractEvent interactEvent) - { - Loader = loader; - InteractEvent = interactEvent; - } -} - -/// -/// Raised on an attempt of program installation. -/// -[ByRefEvent] -public record struct ProgramInstallationAttempt(EntityUid LoaderUid, string Prototype, bool Cancelled = false); diff --git a/Content.Server/CartridgeLoader/Cartridges/AstroNavCartridgeComponent.cs b/Content.Server/CartridgeLoader/Cartridges/AstroNavCartridgeComponent.cs deleted file mode 100644 index fd616a1c80..0000000000 --- a/Content.Server/CartridgeLoader/Cartridges/AstroNavCartridgeComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.CartridgeLoader.Cartridges; -using Content.Shared.GPS; - -namespace Content.Server.CartridgeLoader.Cartridges; - -[RegisterComponent] -public sealed partial class AstroNavCartridgeComponent : Component -{ -} diff --git a/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeComponent.cs b/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeComponent.cs deleted file mode 100644 index 709bb09099..0000000000 --- a/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Server.CartridgeLoader.Cartridges; - -[RegisterComponent] -public sealed partial class CrewManifestCartridgeComponent : Component -{ -} diff --git a/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs index 8bc73fa131..add42c4953 100644 --- a/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs +++ b/Content.Server/CartridgeLoader/Cartridges/CrewManifestCartridgeSystem.cs @@ -5,7 +5,6 @@ using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.CCVar; using Content.Shared.CrewManifest; using Robust.Shared.Configuration; -using Robust.Shared.Containers; using Robust.Shared.Prototypes; namespace Content.Server.CartridgeLoader.Cartridges; @@ -85,17 +84,17 @@ public sealed partial class CrewManifestCartridgeSystem : EntitySystem { _unsecureViewersAllowed = unsecureViewersAllowed; - var allCartridgeLoaders = AllEntityQuery(); - while (allCartridgeLoaders.MoveNext(out var loaderUid, out var comp, out var cont)) + var allCartridgeLoaders = AllEntityQuery(); + while (allCartridgeLoaders.MoveNext(out var loaderUid, out var comp)) { if (_unsecureViewersAllowed) { - _cartridgeLoader.InstallProgram(loaderUid, CartridgePrototypeName, false, comp); + _cartridgeLoader.InstallProgram((loaderUid, comp), CartridgePrototypeName, false); return; } - if (_cartridgeLoader.TryGetProgram(loaderUid, out var program, true, comp, cont)) - _cartridgeLoader.UninstallProgram(loaderUid, program.Value, comp); + if (_cartridgeLoader.TryGetProgram((loaderUid, comp)) is { } program) + _cartridgeLoader.UninstallProgram((loaderUid, comp), program); } } } diff --git a/Content.Server/CartridgeLoader/Cartridges/MedTekCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/MedTekCartridgeSystem.cs index 533d929149..b1a1951e91 100644 --- a/Content.Server/CartridgeLoader/Cartridges/MedTekCartridgeSystem.cs +++ b/Content.Server/CartridgeLoader/Cartridges/MedTekCartridgeSystem.cs @@ -17,13 +17,13 @@ public sealed partial class MedTekCartridgeSystem : EntitySystem private void OnCartridgeAdded(Entity ent, ref CartridgeAddedEvent args) { - var healthAnalyzer = EnsureComp(args.Loader); + EnsureComp(args.Loader); } private void OnCartridgeRemoved(Entity ent, ref CartridgeRemovedEvent args) { // only remove when the program itself is removed - if (!_cartridgeLoaderSystem.HasProgram(args.Loader)) + if (!_cartridgeLoaderSystem.HasProgram(args.Loader.AsNullable())) { RemComp(args.Loader); } diff --git a/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs b/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs deleted file mode 100644 index 08eef62379..0000000000 --- a/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Shared.Security; - -namespace Content.Server.CartridgeLoader.Cartridges; - -[RegisterComponent] -public sealed partial class WantedListCartridgeComponent : Component -{ -} diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index 7fef07c551..2d92224ecd 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Linq; using Content.Server.Access.Systems; using Content.Server.AlertLevel; using Content.Server.CartridgeLoader; @@ -202,7 +203,7 @@ namespace Content.Server.PDA if (!TryComp(uid, out CartridgeLoaderComponent? loader)) return; - var programs = _cartridgeLoader.GetAvailablePrograms(uid, loader); + var programs = GetNetEntityList(_cartridgeLoader.GetAllPrograms(uid).ToList()); var id = CompOrNull(pda.ContainedId); var state = new PdaUpdateState( programs, diff --git a/Content.Shared/CartridgeLoader/CartridgeComponent.cs b/Content.Shared/CartridgeLoader/CartridgeComponent.cs index 207e0244de..cd699f742e 100644 --- a/Content.Shared/CartridgeLoader/CartridgeComponent.cs +++ b/Content.Shared/CartridgeLoader/CartridgeComponent.cs @@ -7,10 +7,11 @@ namespace Content.Shared.CartridgeLoader; /// /// This is used for defining values used for displaying in the program ui in yaml /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +[Access(typeof(CartridgeLoaderSystem))] public sealed partial class CartridgeComponent : Component { - [DataField] + [DataField, AutoNetworkedField] public EntityUid? LoaderUid; [DataField(required: true)] @@ -19,7 +20,7 @@ public sealed partial class CartridgeComponent : Component [DataField] public SpriteSpecifier? Icon; - [AutoNetworkedField] + [DataField, AutoNetworkedField] public InstallationStatus InstallationStatus = InstallationStatus.Cartridge; } diff --git a/Content.Shared/CartridgeLoader/CartridgeLoaderComponent.cs b/Content.Shared/CartridgeLoader/CartridgeLoaderComponent.cs index f47112c794..47f3b5bafc 100644 --- a/Content.Shared/CartridgeLoader/CartridgeLoaderComponent.cs +++ b/Content.Shared/CartridgeLoader/CartridgeLoaderComponent.cs @@ -3,43 +3,36 @@ using Robust.Shared.GameStates; namespace Content.Shared.CartridgeLoader; -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(CartridgeLoaderSystem))] public sealed partial class CartridgeLoaderComponent : Component { + public const string UnremovableContainerId = "preinstalled-program-container"; + + public const string RemovableContainerId = "removable-program-container"; + public const string CartridgeSlotId = "Cartridge-Slot"; [DataField] public ItemSlot CartridgeSlot = new(); - /// - /// List of programs that come preinstalled with this cartridge loader - /// - [DataField("preinstalled")] // TODO remove this and use container fill. - public List PreinstalledPrograms = new(); - /// /// The currently running program that has its ui showing /// - [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public EntityUid? ActiveProgram = default; - /// - /// The list of programs running in the background, listening to certain events - /// - [ViewVariables] - public readonly List BackgroundPrograms = new(); - /// /// The maximum amount of programs that can be installed on the cartridge loader entity /// - [DataField] + [DataField, AutoNetworkedField] public int DiskSpace = 8; /// /// Controls whether the cartridge loader will play notifications if it supports it at all /// TODO: Add an option for this to the PDA /// - [DataField] + [DataField, AutoNetworkedField] public bool NotificationsEnabled = true; [DataField(required: true)] diff --git a/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.Programs.cs b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.Programs.cs new file mode 100644 index 0000000000..6a6950e92b --- /dev/null +++ b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.Programs.cs @@ -0,0 +1,171 @@ +using System.Linq; +using Robust.Shared.Prototypes; + +namespace Content.Shared.CartridgeLoader; + +public sealed partial class CartridgeLoaderSystem +{ + public Entity? TryGetProgram(Entity ent) where T : IComponent + { + if (!Resolve(ent, ref ent.Comp)) + return null; + + foreach (var prog in GetAllPrograms(ent)) + { + if (!TryComp(prog, out var program)) + continue; + + return (prog, program); + } + + return null; + } + + public bool HasProgram(Entity ent) where T : IComponent + { + return TryGetProgram(ent).HasValue; + } + + public IEnumerable GetAllPrograms(EntityUid uid) + { + if (_itemSlotsSystem.GetItemOrNull(uid, CartridgeLoaderComponent.CartridgeSlotId) is { } cartridge) + return GetDiskPrograms(uid).Append(cartridge); + + return GetDiskPrograms(uid); + } + + public IEnumerable GetDiskPrograms(EntityUid uid) + { + return GetPreinstalledPrograms(uid).Concat(GetRemovablePrograms(uid)); + } + + public IReadOnlyList GetRemovablePrograms(EntityUid uid) + { + if (_container.TryGetContainer(uid, CartridgeLoaderComponent.RemovableContainerId, out var container)) + return container.ContainedEntities; + + return []; + } + + public IReadOnlyList GetPreinstalledPrograms(EntityUid uid) + { + if (_container.TryGetContainer(uid, CartridgeLoaderComponent.UnremovableContainerId, out var container)) + return container.ContainedEntities; + + return []; + } + + public int UsedDiskSpace(EntityUid uid) + { + var ret = 0; + + if (_container.TryGetContainer(uid, CartridgeLoaderComponent.RemovableContainerId, out var removable)) + ret += removable.Count; + + if (_container.TryGetContainer(uid, CartridgeLoaderComponent.UnremovableContainerId, out var unremovable)) + ret += unremovable.Count; + + return ret; + } + + private bool HasProgram(EntityUid uid, EntityUid program) + { + return GetAllPrograms(uid).Contains(program); + } + + public void ActivateProgram(Entity ent, EntityUid program) + { + if (!HasProgram(ent, program)) + return; + + if (ent.Comp.ActiveProgram is { } active) + DeactivateProgram(ent, active); + + var evt = new CartridgeActivatedEvent(ent); + RaiseLocalEvent(program, ref evt); + ent.Comp.ActiveProgram = program; + Dirty(ent); + UpdateUiState(ent.AsNullable()); + } + + public void DeactivateProgram(Entity ent, EntityUid program) + { + if (!HasProgram(ent, program) || ent.Comp.ActiveProgram != program) + return; + + var evt = new CartridgeDeactivatedEvent(ent); + RaiseLocalEvent(program, ref evt); + ent.Comp.ActiveProgram = null; + Dirty(ent); + UpdateUiState(ent.AsNullable()); + } + + /// + /// Installs a program by its prototype. + /// + /// The cartridge loader. + /// The prototype. + /// Whether the program can be deinstalled or not. + /// Whether installing the cartridge was successful. + public bool InstallProgram(Entity ent, EntProtoId prototype, bool deinstallable = true) + { + if (UsedDiskSpace(ent) >= ent.Comp.DiskSpace) + return false; + + var ev = new ProgramInstallationAttempt(ent, prototype); + RaiseLocalEvent(ref ev); + + if (ev.Cancelled) + return false; + + return PredictedTrySpawnInContainer(prototype, + ent, + deinstallable ? CartridgeLoaderComponent.RemovableContainerId : CartridgeLoaderComponent.UnremovableContainerId, + out _); + } + + /// + /// Uninstalls a program using its uid. + /// + /// The cartridge loader uid. + /// The uid of the program to be uninstalled. + /// Whether uninstalling the program was successful. + public bool UninstallProgram(Entity ent, EntityUid program) + { + if (!GetDiskPrograms(ent).Contains(program)) + return false; + + PredictedQueueDel(program); + + return true; + } + + /// + /// Installs a cartridge by spawning an invisible version of the cartridges prototype into the cartridge loaders program container. + /// + /// The cartridge loader. + /// The uid of the cartridge to be installed. + /// Whether installing the cartridge was successful. + public bool InstallCartridge(Entity ent, EntityUid cartridge) + { + if (!HasComp(cartridge)) + return false; + + if (MetaData(cartridge).EntityPrototype is not { } cartridgeProto) + return false; + + foreach (var program in GetDiskPrograms(ent)) + { + if (MetaData(program).EntityPrototype is { } programProto && programProto == cartridgeProto) + return false; + } + + return InstallProgram(ent, cartridgeProto); + } + + private void UpdateCartridgeInstallationStatus(Entity ent, InstallationStatus installationStatus) + { + ent.Comp.InstallationStatus = installationStatus; + Dirty(ent); + } +} diff --git a/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.Relay.cs b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.Relay.cs new file mode 100644 index 0000000000..db4e952a48 --- /dev/null +++ b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.Relay.cs @@ -0,0 +1,64 @@ +using Content.Shared.DeviceNetwork.Events; +using Content.Shared.Interaction; + +namespace Content.Shared.CartridgeLoader; + +public sealed partial class CartridgeLoaderSystem +{ + private void InitializeRelay() + { + SubscribeLocalEvent(RelayEvent); + SubscribeLocalEvent(RelayEvent); + SubscribeLocalEvent(RelayEvent); + } + + private void RefRelayEvent(EntityUid uid, CartridgeLoaderComponent component, ref T args) where T : struct + { + RefRelayEvent((uid, component), ref args); + } + + private void RelayEvent(EntityUid uid, CartridgeLoaderComponent component, T args) where T : class + { + RelayEvent((uid, component), args); + } + + public void RefRelayEvent(Entity ent, ref T args, bool foregroundOnly = false) where T : struct + { + var ev = new CartridgeRelayedEvent(ent, args); + if (foregroundOnly) + { + if (ent.Comp.ActiveProgram is { } foreground) + RaiseLocalEvent(foreground, ref ev); + args = ev.Args; + return; + } + + foreach (var program in GetAllPrograms(ent)) + { + RaiseLocalEvent(program, ref ev); + } + args = ev.Args; + } + + public void RelayEvent(Entity ent, T args, bool foregroundOnly = false) where T : class + { + var ev = new CartridgeRelayedEvent(ent, args); + if (foregroundOnly) + { + if (ent.Comp.ActiveProgram is { } foreground) + RaiseLocalEvent(foreground, ref ev); + return; + } + + foreach (var program in GetAllPrograms(ent)) + { + RaiseLocalEvent(program, ref ev); + } + } +} + +/// +/// Event wrapper for relayed events. +/// +[ByRefEvent] +public record struct CartridgeRelayedEvent(Entity Loader, TEvent Args); diff --git a/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.UserInterface.cs b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.UserInterface.cs new file mode 100644 index 0000000000..4d770dd4bb --- /dev/null +++ b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.UserInterface.cs @@ -0,0 +1,91 @@ +using System.Linq; + +namespace Content.Shared.CartridgeLoader; + +public sealed partial class CartridgeLoaderSystem +{ + private void OnLoaderUiMessage(Entity ent, ref CartridgeLoaderUiMessage message) + { + var cartridge = GetEntity(message.CartridgeUid); + + switch (message.Action) + { + case CartridgeUiMessageAction.Activate: + ActivateProgram(ent, cartridge); + break; + case CartridgeUiMessageAction.Deactivate: + DeactivateProgram(ent, cartridge); + break; + case CartridgeUiMessageAction.Install: + InstallCartridge(ent, cartridge); + break; + case CartridgeUiMessageAction.Uninstall: + UninstallProgram(ent, cartridge); + break; + case CartridgeUiMessageAction.UIReady: + if (ent.Comp.ActiveProgram is { } foreground) + { + var evt = new CartridgeUiReadyEvent(ent); + RaiseLocalEvent(foreground, ref evt); + } + break; + default: + throw new ArgumentOutOfRangeException($"Unrecognized UI action passed from cartridge loader ui {message.Action}."); + } + } + + /// + /// Relays ui messages meant for cartridges to the currently active cartridge. + /// + private void OnUiMessage(Entity ent, ref CartridgeUiMessage args) + { + var cartridgeEvent = args.MessageEvent; + cartridgeEvent.User = args.Actor; + cartridgeEvent.LoaderUid = GetNetEntity(ent); + cartridgeEvent.Actor = args.Actor; + + if (ent.Comp.ActiveProgram is { } foreground) + RaiseLocalEvent(foreground, cartridgeEvent); + } + + /// + /// Updates the programs ui state. + /// + /// The cartridge loaders entity uid. + /// The programs ui state. Programs should use their own ui state class inheriting from . + /// The cartridge loader component. + /// + /// This method is called "UpdateCartridgeUiState" but cartridges and a programs are the same. A cartridge is just a program as a visible item. + /// + /// + public void UpdateCartridgeUiState(EntityUid loaderUid, BoundUserInterfaceState state, CartridgeLoaderComponent? loader = null) + { + if (!Resolve(loaderUid, ref loader)) + return; + + if (_userInterface.HasUi(loaderUid, loader.UiKey)) + _userInterface.SetUiState(loaderUid, loader.UiKey, state); + } + + + /// + /// Updates the cartridge loaders ui state. + /// + /// + /// Because the cartridge loader integrates with the ui of the entity using it, the entities ui state needs to inherit from + /// and use this method to update its state so the cartridge loaders state can be added to it. + /// + /// + public void UpdateUiState(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + if (!_userInterface.HasUi(ent.Owner, ent.Comp.UiKey)) + return; + + var programs = GetNetEntityList(GetAllPrograms(ent).ToList()); + var state = new CartridgeLoaderUiState(programs, GetNetEntity(ent.Comp.ActiveProgram)); + _userInterface.SetUiState(ent.Owner, ent.Comp.UiKey, state); + } +} diff --git a/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.cs new file mode 100644 index 0000000000..446f296bf8 --- /dev/null +++ b/Content.Shared/CartridgeLoader/CartridgeLoaderSystem.cs @@ -0,0 +1,185 @@ +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.Containers; +using Robust.Shared.Timing; + +namespace Content.Shared.CartridgeLoader; + +public sealed partial class CartridgeLoaderSystem : EntitySystem +{ + [Dependency] private IGameTiming _timing = default!; + [Dependency] private ItemSlotsSystem _itemSlotsSystem = default!; + [Dependency] private SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private SharedContainerSystem _container = default!; + [Dependency] private SharedUserInterfaceSystem _userInterface = default!; + + public override void Initialize() + { + base.Initialize(); + + InitializeRelay(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentRemove); + + SubscribeLocalEvent(OnItemInserted); + SubscribeLocalEvent(OnItemRemoved); + + SubscribeLocalEvent(OnLoaderUiMessage); + SubscribeLocalEvent(OnUiMessage); + + SubscribeLocalEvent(OnCartridgeState); + } + + private void OnComponentInit(Entity ent, ref ComponentInit args) + { + _itemSlotsSystem.AddItemSlot(ent, CartridgeLoaderComponent.CartridgeSlotId, ent.Comp.CartridgeSlot); + _container.EnsureContainer(ent, CartridgeLoaderComponent.RemovableContainerId); + _container.EnsureContainer(ent, CartridgeLoaderComponent.UnremovableContainerId); + } + + /// + /// Marks installed program entities for deletion when the component gets removed + /// + private void OnComponentRemove(Entity ent, ref ComponentRemove args) + { + _itemSlotsSystem.RemoveItemSlot(ent, ent.Comp.CartridgeSlot); + if (_container.TryGetContainer(ent, CartridgeLoaderComponent.RemovableContainerId, out var removable)) + _container.ShutdownContainer(removable); + if (_container.TryGetContainer(ent, CartridgeLoaderComponent.UnremovableContainerId, out var unremovable)) + _container.ShutdownContainer(unremovable); + } + + private void OnItemInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (_timing.ApplyingState) + return; + + if (args.Container.ID != CartridgeLoaderComponent.RemovableContainerId + && args.Container.ID != CartridgeLoaderComponent.UnremovableContainerId + && args.Container.ID != CartridgeLoaderComponent.CartridgeSlotId + ) return; + + if (TryComp(args.Entity, out var cartridge)) + { + cartridge.LoaderUid = ent; + Dirty(args.Entity, cartridge); + + if (args.Container.ID == CartridgeLoaderComponent.RemovableContainerId) + UpdateCartridgeInstallationStatus((args.Entity, cartridge), InstallationStatus.Installed); + else if (args.Container.ID == CartridgeLoaderComponent.UnremovableContainerId) + UpdateCartridgeInstallationStatus((args.Entity, cartridge), InstallationStatus.Readonly); + else + UpdateCartridgeInstallationStatus((args.Entity, cartridge), InstallationStatus.Cartridge); + } + + var evt = new CartridgeAddedEvent(ent); + RaiseLocalEvent(args.Entity, ref evt); + UpdateUiState(ent.AsNullable()); + UpdateAppearanceData(ent); + } + + private void OnItemRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (_timing.ApplyingState) + return; + + if (args.Container.ID != CartridgeLoaderComponent.RemovableContainerId + && args.Container.ID != CartridgeLoaderComponent.UnremovableContainerId + && args.Container.ID != CartridgeLoaderComponent.CartridgeSlotId + ) return; + + if (ent.Comp.ActiveProgram == args.Entity) + { + ent.Comp.ActiveProgram = null; + Dirty(ent); + var deactivated = new CartridgeDeactivatedEvent(ent); + RaiseLocalEvent(args.Entity, ref deactivated); + } + + if (TryComp(args.Entity, out var cartridge)) + { + cartridge.LoaderUid = null; + Dirty(args.Entity, cartridge); + } + + var removed = new CartridgeRemovedEvent(ent); + RaiseLocalEvent(args.Entity, ref removed); + UpdateUiState(ent.AsNullable()); + UpdateAppearanceData(ent); + } + + private void UpdateAppearanceData(Entity ent) + { + _appearanceSystem.SetData(ent.Owner, CartridgeLoaderVisuals.CartridgeInserted, ent.Comp.CartridgeSlot.HasItem); + } + + public void SendNotification(EntityUid loaderUid, string header, string message, CartridgeLoaderComponent? loader = default) + { + if (!Resolve(loaderUid, ref loader)) + return; + + if (!loader.NotificationsEnabled) + return; + + var args = new CartridgeLoaderNotificationSentEvent(header, message); + RaiseLocalEvent(loaderUid, ref args); + } + + private void OnCartridgeState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (ent.Comp.LoaderUid is not { } loader) + return; + + UpdateUiState(loader); + } +} + +/// +/// Gets sent to program / cartridge entities when they get inserted or installed. +/// +[ByRefEvent] +public record struct CartridgeAddedEvent(Entity Loader); + +/// +/// Gets sent to cartridge entities when they get ejected. +/// +[ByRefEvent] +public record struct CartridgeRemovedEvent(Entity Loader); + +/// +/// Gets sent to program / cartridge entities when they get activated. +/// +/// +/// Don't update the programs ui state in this events listener. +/// +[ByRefEvent] +public record struct CartridgeActivatedEvent(Entity Loader); + +/// +/// Gets sent to program / cartridge entities when they get deactivated. +/// +[ByRefEvent] +public record struct CartridgeDeactivatedEvent(Entity Loader); + +/// +/// Gets sent to program / cartridge entities when the ui is ready to be updated by the cartridge. +/// +/// +/// This is used for the initial ui state update because updating the ui in the activate event doesn't work. +/// +[ByRefEvent] +public record struct CartridgeUiReadyEvent(Entity Loader); + +/// +/// Gets sent by the cartridge loader system to the cartridge loader entity so another system. +/// can handle displaying the notification +/// +/// The message to be displayed. +[ByRefEvent] +public record struct CartridgeLoaderNotificationSentEvent(string Header, string Message); + +/// +/// Raised on an attempt of program installation. +/// +[ByRefEvent] +public record struct ProgramInstallationAttempt(EntityUid LoaderUid, string Prototype, bool Cancelled = false); diff --git a/Content.Shared/CartridgeLoader/Cartridges/AstroNavCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/AstroNavCartridgeComponent.cs new file mode 100644 index 0000000000..17dff5fcfb --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/AstroNavCartridgeComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[RegisterComponent, NetworkedComponent] +public sealed partial class AstroNavCartridgeComponent : Component; diff --git a/Content.Server/CartridgeLoader/Cartridges/AstroNavCartridgeSystem.cs b/Content.Shared/CartridgeLoader/Cartridges/AstroNavCartridgeSystem.cs similarity index 85% rename from Content.Server/CartridgeLoader/Cartridges/AstroNavCartridgeSystem.cs rename to Content.Shared/CartridgeLoader/Cartridges/AstroNavCartridgeSystem.cs index 1d94468fca..fef336dfb9 100644 --- a/Content.Server/CartridgeLoader/Cartridges/AstroNavCartridgeSystem.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/AstroNavCartridgeSystem.cs @@ -1,8 +1,6 @@ -using Content.Shared.CartridgeLoader; -using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.GPS.Components; -namespace Content.Server.CartridgeLoader.Cartridges; +namespace Content.Shared.CartridgeLoader.Cartridges; public sealed partial class AstroNavCartridgeSystem : EntitySystem { @@ -24,7 +22,7 @@ public sealed partial class AstroNavCartridgeSystem : EntitySystem private void OnCartridgeRemoved(Entity ent, ref CartridgeRemovedEvent args) { // only remove when the program itself is removed - if (!_cartridgeLoaderSystem.HasProgram(args.Loader)) + if (!_cartridgeLoaderSystem.HasProgram(args.Loader.AsNullable())) { RemComp(args.Loader); } diff --git a/Content.Shared/CartridgeLoader/Cartridges/CrewManifestCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/CrewManifestCartridgeComponent.cs new file mode 100644 index 0000000000..ce3b4cf482 --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/CrewManifestCartridgeComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent, NetworkedComponent] +public sealed partial class CrewManifestCartridgeComponent : Component; diff --git a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs similarity index 79% rename from Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs rename to Content.Shared/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs index d3f1d250e1..feb6ebdb7f 100644 --- a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/LogProbeCartridgeComponent.cs @@ -1,31 +1,31 @@ -using Content.Shared.CartridgeLoader.Cartridges; -using Content.Shared.Paper; +using Content.Shared.Paper; using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.CartridgeLoader.Cartridges; +namespace Content.Shared.CartridgeLoader.Cartridges; -[RegisterComponent, Access(typeof(LogProbeCartridgeSystem))] -[AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[Access(typeof(LogProbeCartridgeSystem))] public sealed partial class LogProbeCartridgeComponent : Component { /// /// The name of the scanned entity, sent to clients when they open the UI. /// - [DataField] + [DataField, AutoNetworkedField] public string EntityName = string.Empty; /// /// The list of pulled access logs /// - [DataField, ViewVariables] + [DataField, AutoNetworkedField] public List PulledAccessLogs = new(); /// /// The sound to make when we scan something with access /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg", AudioParams.Default.WithVariation(0.25f)); /// @@ -46,6 +46,6 @@ public sealed partial class LogProbeCartridgeComponent : Component /// /// When anyone is allowed to spawn another printout. /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] public TimeSpan NextPrintAllowed = TimeSpan.Zero; } diff --git a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs b/Content.Shared/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs similarity index 84% rename from Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs rename to Content.Shared/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs index d7194dd82f..a51cd2d356 100644 --- a/Content.Server/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/LogProbeCartridgeSystem.cs @@ -1,9 +1,8 @@ using Content.Shared.Access.Components; using Content.Shared.Administration.Logs; -using Content.Shared.CartridgeLoader; -using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.Database; using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; using Content.Shared.Labels.EntitySystems; using Content.Shared.Paper; using Content.Shared.Popups; @@ -11,7 +10,7 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; using System.Text; -namespace Content.Server.CartridgeLoader.Cartridges; +namespace Content.Shared.CartridgeLoader.Cartridges; public sealed partial class LogProbeCartridgeSystem : EntitySystem { @@ -30,27 +29,24 @@ public sealed partial class LogProbeCartridgeSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnUiReady); - SubscribeLocalEvent(AfterInteract); + SubscribeLocalEvent>(AfterInteract); SubscribeLocalEvent(OnMessage); } /// - /// The gets relayed to this system if the cartridge loader is running - /// the LogProbe program and someone clicks on something with it.
- ///
/// Updates the program's list of logs with those from the device. ///
- private void AfterInteract(Entity ent, ref CartridgeAfterInteractEvent args) + private void AfterInteract(Entity ent, ref CartridgeRelayedEvent args) { - if (args.InteractEvent.Handled || !args.InteractEvent.CanReach || args.InteractEvent.Target is not { } target) + if (args.Args.Handled || !args.Args.CanReach || args.Args.Target is not { } target) return; if (!TryComp(target, out AccessReaderComponent? accessReaderComponent)) return; //Play scanning sound with slightly randomized pitch - _audio.PlayEntity(ent.Comp.SoundScan, args.InteractEvent.User, target); - _popup.PopupCursor(Loc.GetString("log-probe-scan", ("device", target)), args.InteractEvent.User); + _audio.PlayPredicted(ent.Comp.SoundScan, target, args.Args.User); + _popup.PopupPredictedCursor(Loc.GetString("log-probe-scan", ("device", target)), args.Args.User); ent.Comp.EntityName = Name(target); ent.Comp.PulledAccessLogs.Clear(); @@ -68,6 +64,7 @@ public sealed partial class LogProbeCartridgeSystem : EntitySystem // Reverse the list so the oldest is at the bottom ent.Comp.PulledAccessLogs.Reverse(); + Dirty(ent); UpdateUiState(ent, args.Loader); } @@ -95,7 +92,7 @@ public sealed partial class LogProbeCartridgeSystem : EntitySystem ent.Comp.NextPrintAllowed = _timing.CurTime + ent.Comp.PrintCooldown; - var paper = Spawn(ent.Comp.PaperPrototype, _transform.GetMapCoordinates(user)); + var paper = EntityManager.PredictedSpawn(ent.Comp.PaperPrototype, _transform.GetMapCoordinates(user)); _label.Label(paper, ent.Comp.EntityName); // label it for easy identification _audio.PlayEntity(ent.Comp.PrintSound, user, paper); @@ -117,6 +114,7 @@ public sealed partial class LogProbeCartridgeSystem : EntitySystem _paper.SetContent((paper, paperComp), builder.ToString()); _adminLogger.Add(LogType.EntitySpawn, LogImpact.Low, $"{ToPrettyString(user):user} printed out LogProbe logs ({paper}) of {ent.Comp.EntityName}"); + Dirty(ent); } private void UpdateUiState(Entity ent, EntityUid loaderUid) diff --git a/Content.Server/CartridgeLoader/Cartridges/MedTekCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/MedTekCartridgeComponent.cs similarity index 61% rename from Content.Server/CartridgeLoader/Cartridges/MedTekCartridgeComponent.cs rename to Content.Shared/CartridgeLoader/Cartridges/MedTekCartridgeComponent.cs index 1a49b4d1f7..a6e696a0b7 100644 --- a/Content.Server/CartridgeLoader/Cartridges/MedTekCartridgeComponent.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/MedTekCartridgeComponent.cs @@ -1,6 +1,6 @@ +using Robust.Shared.GameStates; + namespace Content.Server.CartridgeLoader.Cartridges; -[RegisterComponent] -public sealed partial class MedTekCartridgeComponent : Component -{ -} +[RegisterComponent, NetworkedComponent] +public sealed partial class MedTekCartridgeComponent : Component; diff --git a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeComponent.cs index 31169ae022..d16a63babe 100644 --- a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeComponent.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeComponent.cs @@ -1,29 +1,29 @@ -using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Shared.GameStates; namespace Content.Shared.CartridgeLoader.Cartridges; /// /// Component that indicates a PDA cartridge as containing the NanoTask program /// -[RegisterComponent, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause, AutoGenerateComponentState] public sealed partial class NanoTaskCartridgeComponent : Component { /// /// The list of tasks /// - [DataField] + [DataField, AutoNetworkedField] public List Tasks = new(); /// /// counter for generating task IDs /// - [DataField] + [DataField, AutoNetworkedField] public int Counter = 1; /// /// When the user can print again /// - [DataField, AutoPausedField] + [DataField, AutoNetworkedField, AutoPausedField] public TimeSpan NextPrintAllowedAfter = TimeSpan.Zero; /// @@ -32,11 +32,3 @@ public sealed partial class NanoTaskCartridgeComponent : Component [DataField] public TimeSpan PrintDelay = TimeSpan.FromSeconds(5); } - -/// -/// Component attached to the PDA a NanoTask cartridge is inserted into for interaction handling -/// -[RegisterComponent] -public sealed partial class NanoTaskInteractionComponent : Component -{ -} diff --git a/Content.Server/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs similarity index 75% rename from Content.Server/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs rename to Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs index 2db6da5b27..ac8e42d156 100644 --- a/Content.Server/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskCartridgeSystem.cs @@ -1,19 +1,14 @@ -using Content.Shared.CartridgeLoader.Cartridges; -using Content.Shared.CartridgeLoader; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Paper; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Audio; using Robust.Shared.Timing; using Robust.Shared.Utility; -namespace Content.Server.CartridgeLoader.Cartridges; +namespace Content.Shared.CartridgeLoader.Cartridges; -/// -/// Server-side class implementing the core UI logic of NanoTask -/// -public sealed partial class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSystem +public sealed partial class NanoTaskCartridgeSystem : EntitySystem { [Dependency] private CartridgeLoaderSystem _cartridgeLoader = default!; [Dependency] private IGameTiming _timing = default!; @@ -27,36 +22,21 @@ public sealed partial class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSys SubscribeLocalEvent(OnUiMessage); SubscribeLocalEvent(OnUiReady); - - SubscribeLocalEvent(OnCartridgeRemoved); - - SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent>(OnInteractUsing); } - private void OnCartridgeRemoved(Entity ent, ref CartridgeRemovedEvent args) + private void OnInteractUsing(Entity ent, ref CartridgeRelayedEvent args) { - if (!_cartridgeLoader.HasProgram(args.Loader)) - { - RemComp(args.Loader); - } - } + if (!TryComp(args.Args.Used, out var printed)) + return; - private void OnInteractUsing(Entity ent, ref InteractUsingEvent args) - { - if (!_cartridgeLoader.TryGetProgram(ent.Owner, out var uid, out var program)) - { - return; - } - if (!TryComp(args.Used, out var printed)) - { - return; - } if (printed.Task is NanoTaskItem item) { - program.Tasks.Add(new(program.Counter++, printed.Task)); - args.Handled = true; - Del(args.Used); - UpdateUiState(new Entity(uid.Value, program), ent.Owner); + ent.Comp.Tasks.Add(new(ent.Comp.Counter++, printed.Task)); + args.Args.Handled = true; + PredictedQueueDel(args.Args.Used); + Dirty(ent); + UpdateUiState(ent, args.Loader); } } @@ -105,11 +85,14 @@ public sealed partial class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSys switch (message.Payload) { case NanoTaskAddTask task: + { if (!task.Item.Validate()) return; ent.Comp.Tasks.Add(new(ent.Comp.Counter++, task.Item)); + Dirty(ent); break; + } case NanoTaskUpdateTask task: { if (!task.Item.Data.Validate()) @@ -118,23 +101,30 @@ public sealed partial class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSys var idx = ent.Comp.Tasks.FindIndex(t => t.Id == task.Item.Id); if (idx != -1) ent.Comp.Tasks[idx] = task.Item; + + Dirty(ent); break; } case NanoTaskDeleteTask task: + { ent.Comp.Tasks.RemoveAll(t => t.Id == task.Id); + Dirty(ent); break; + } case NanoTaskPrintTask task: { if (!task.Item.Validate()) return; + if (_timing.CurTime < ent.Comp.NextPrintAllowedAfter) return; ent.Comp.NextPrintAllowedAfter = _timing.CurTime + ent.Comp.PrintDelay; - var printed = Spawn("PaperNanoTaskItem", Transform(message.Actor).Coordinates); + var printed = PredictedSpawnAtPosition("PaperNanoTaskItem", Transform(message.Actor).Coordinates); _hands.PickupOrDrop(message.Actor, printed); _audio.PlayPvs(new SoundPathSpecifier("/Audio/Machines/printer.ogg"), ent.Owner); SetupPrintedTask(printed, task.Item); + Dirty(ent); break; } } @@ -142,7 +132,6 @@ public sealed partial class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSys UpdateUiState(ent, GetEntity(args.LoaderUid)); } - private void UpdateUiState(Entity ent, EntityUid loaderUid) { var state = new NanoTaskUiState(ent.Comp.Tasks); diff --git a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskPrintedComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskPrintedComponent.cs index a020e46521..5e42cbc07f 100644 --- a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskPrintedComponent.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskPrintedComponent.cs @@ -1,16 +1,16 @@ -using Content.Shared.CartridgeLoader.Cartridges; +using Robust.Shared.GameStates; namespace Content.Shared.CartridgeLoader.Cartridges; /// /// Component attached to a piece of paper to indicate that it was printed from NanoTask and can be inserted back into it /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class NanoTaskPrintedComponent : Component { /// /// The task that this item holds /// - [DataField] + [DataField, AutoNetworkedField] public NanoTaskItem? Task; } diff --git a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiState.cs b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiState.cs index 3c65adac65..ae367daf8c 100644 --- a/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiState.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NanoTaskUiState.cs @@ -17,7 +17,7 @@ public enum NanoTaskPriority : byte /// The data relating to a single NanoTask item, but not its identifier /// [Serializable, NetSerializable, DataRecord] -public sealed partial class NanoTaskItem +public sealed partial class NanoTaskItem : IRobustCloneable { /// /// The maximum length of the Description and TaskIsFor fields @@ -51,10 +51,16 @@ public sealed partial class NanoTaskItem IsTaskDone = isTaskDone; Priority = priority; } + public bool Validate() { return Description.Length <= MaximumStringLength && TaskIsFor.Length <= MaximumStringLength; } + + public NanoTaskItem Clone() + { + return new(Description, TaskIsFor, IsTaskDone, Priority); + } }; /// diff --git a/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs similarity index 56% rename from Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs rename to Content.Shared/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs index 0de3538c0d..a690309165 100644 --- a/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NetProbeCartridgeComponent.cs @@ -1,24 +1,24 @@ -using Content.Shared.CartridgeLoader.Cartridges; -using Robust.Shared.Audio; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; -namespace Content.Server.CartridgeLoader.Cartridges; +namespace Content.Shared.CartridgeLoader.Cartridges; -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class NetProbeCartridgeComponent : Component { /// /// The list of probed network devices /// - [DataField("probedDevices")] + [DataField, AutoNetworkedField] public List ProbedDevices = new(); /// /// Limits the amount of devices that can be saved /// - [DataField("maxSavedDevices")] - public int MaxSavedDevices { get; set; } = 9; + [DataField, AutoNetworkedField] + public int MaxSavedDevices = 9; - [DataField("soundScan")] + [DataField] public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg"); } diff --git a/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs b/Content.Shared/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs similarity index 61% rename from Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs rename to Content.Shared/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs index 24b584c167..2b0dfe6b8f 100644 --- a/Content.Server/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NetProbeCartridgeSystem.cs @@ -1,61 +1,54 @@ -using Content.Shared.CartridgeLoader; -using Content.Shared.CartridgeLoader.Cartridges; using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Components; +using Content.Shared.Interaction; using Content.Shared.Popups; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; -using Robust.Shared.Random; -namespace Content.Server.CartridgeLoader.Cartridges; +namespace Content.Shared.CartridgeLoader.Cartridges; public sealed partial class NetProbeCartridgeSystem : EntitySystem { [Dependency] private CartridgeLoaderSystem _cartridgeLoaderSystem = default!; - [Dependency] private IRobustRandom _random = default!; [Dependency] private SharedPopupSystem _popupSystem = default!; [Dependency] private SharedAudioSystem _audioSystem = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnUiReady); - SubscribeLocalEvent(AfterInteract); + 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) + private void AfterInteract(EntityUid uid, NetProbeCartridgeComponent component, CartridgeRelayedEvent args) { - if (args.InteractEvent.Handled || !args.InteractEvent.CanReach || !args.InteractEvent.Target.HasValue) + if (args.Args.Handled || !args.Args.CanReach || !args.Args.Target.HasValue) return; - var target = args.InteractEvent.Target.Value; - DeviceNetworkComponent? networkComponent = default; + var target = args.Args.Target.Value; - if (!Resolve(target, ref networkComponent, false)) + if (!TryComp(target, out var networkComponent)) return; - //Ceck if device is already present in list + // Check 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.PlayEntity(component.SoundScan, args.InteractEvent.User, target, audioParams); - _popupSystem.PopupCursor(Loc.GetString("net-probe-scan", ("device", target)), args.InteractEvent.User); + // Play scanning sound with slightly randomized pitch + // Why is there no NextFloat(float min, float max)??? + var audioParams = AudioParams.Default.WithVolume(-2f).WithVariation(0.2f); + _audioSystem.PlayPredicted(component.SoundScan, target, args.Args.User, audioParams); + _popupSystem.PopupPredictedCursor(Loc.GetString("net-probe-scan", ("device", target)), args.Args.User); - - //Limit the amount of saved probe results to 9 - //This is hardcoded because the UI doesn't support a dynamic number of results + // 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); @@ -67,6 +60,7 @@ public sealed partial class NetProbeCartridgeSystem : EntitySystem ); component.ProbedDevices.Add(device); + Dirty(uid, component); UpdateUiState(uid, args.Loader, component); } @@ -84,6 +78,6 @@ public sealed partial class NetProbeCartridgeSystem : EntitySystem return; var state = new NetProbeUiState(component.ProbedDevices); - _cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state); + _cartridgeLoaderSystem.UpdateCartridgeUiState(loaderUid, state); } } diff --git a/Content.Server/CartridgeLoader/Cartridges/NewsReaderCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/NewsReaderCartridgeComponent.cs similarity index 65% rename from Content.Server/CartridgeLoader/Cartridges/NewsReaderCartridgeComponent.cs rename to Content.Shared/CartridgeLoader/Cartridges/NewsReaderCartridgeComponent.cs index 525b9fa245..35a6013f7a 100644 --- a/Content.Server/CartridgeLoader/Cartridges/NewsReaderCartridgeComponent.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NewsReaderCartridgeComponent.cs @@ -1,6 +1,8 @@ -namespace Content.Server.CartridgeLoader.Cartridges; +using Robust.Shared.GameStates; -[RegisterComponent] +namespace Content.Shared.CartridgeLoader.Cartridges; + +[RegisterComponent, NetworkedComponent] public sealed partial class NewsReaderCartridgeComponent : Component { [ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/CartridgeLoader/Cartridges/NotekeeperCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/NotekeeperCartridgeComponent.cs similarity index 51% rename from Content.Server/CartridgeLoader/Cartridges/NotekeeperCartridgeComponent.cs rename to Content.Shared/CartridgeLoader/Cartridges/NotekeeperCartridgeComponent.cs index dacf13f079..da88caac2f 100644 --- a/Content.Server/CartridgeLoader/Cartridges/NotekeeperCartridgeComponent.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NotekeeperCartridgeComponent.cs @@ -1,11 +1,13 @@ -namespace Content.Server.CartridgeLoader.Cartridges; +using Robust.Shared.GameStates; -[RegisterComponent] +namespace Content.Shared.CartridgeLoader.Cartridges; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class NotekeeperCartridgeComponent : Component { /// /// The list of notes that got written down /// - [DataField("notes")] + [DataField, AutoNetworkedField] public List Notes = new(); } diff --git a/Content.Server/CartridgeLoader/Cartridges/NotekeeperCartridgeSystem.cs b/Content.Shared/CartridgeLoader/Cartridges/NotekeeperCartridgeSystem.cs similarity index 87% rename from Content.Server/CartridgeLoader/Cartridges/NotekeeperCartridgeSystem.cs rename to Content.Shared/CartridgeLoader/Cartridges/NotekeeperCartridgeSystem.cs index 9f417ac57a..37446e8bf8 100644 --- a/Content.Server/CartridgeLoader/Cartridges/NotekeeperCartridgeSystem.cs +++ b/Content.Shared/CartridgeLoader/Cartridges/NotekeeperCartridgeSystem.cs @@ -1,18 +1,17 @@ -using Content.Server.Administration.Logs; -using Content.Shared.CartridgeLoader; -using Content.Shared.CartridgeLoader.Cartridges; +using Content.Shared.Administration.Logs; using Content.Shared.Database; -namespace Content.Server.CartridgeLoader.Cartridges; +namespace Content.Shared.CartridgeLoader.Cartridges; public sealed partial class NotekeeperCartridgeSystem : EntitySystem { [Dependency] private CartridgeLoaderSystem _cartridgeLoaderSystem = default!; - [Dependency] private IAdminLogManager _adminLogger = default!; + [Dependency] private ISharedAdminLogManager _adminLogger = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnUiMessage); SubscribeLocalEvent(OnUiReady); } @@ -49,6 +48,7 @@ public sealed partial class NotekeeperCartridgeSystem : EntitySystem $"{ToPrettyString(args.Actor)} removed a note from PDA: '{message.Note}' was contained on: {ToPrettyString(uid)}"); } + Dirty(uid, component); UpdateUiState(uid, GetEntity(args.LoaderUid), component); } @@ -58,6 +58,6 @@ public sealed partial class NotekeeperCartridgeSystem : EntitySystem return; var state = new NotekeeperUiState(component.Notes); - _cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state); + _cartridgeLoaderSystem.UpdateCartridgeUiState(loaderUid, state); } } diff --git a/Content.Shared/CartridgeLoader/Cartridges/SharedNanoTaskCartridgeSystem.cs b/Content.Shared/CartridgeLoader/Cartridges/SharedNanoTaskCartridgeSystem.cs deleted file mode 100644 index 444e377f6a..0000000000 --- a/Content.Shared/CartridgeLoader/Cartridges/SharedNanoTaskCartridgeSystem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.CartridgeLoader; -using Content.Shared.CartridgeLoader.Cartridges; - -namespace Content.Shared.CartridgeLoader.Cartridges; - -public abstract class SharedNanoTaskCartridgeSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnCartridgeAdded); - } - - private void OnCartridgeAdded(Entity ent, ref CartridgeAddedEvent args) - { - EnsureComp(args.Loader); - } -} diff --git a/Content.Shared/CartridgeLoader/Cartridges/WantedListCartridgeComponent.cs b/Content.Shared/CartridgeLoader/Cartridges/WantedListCartridgeComponent.cs new file mode 100644 index 0000000000..2de2e017bb --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/WantedListCartridgeComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[RegisterComponent, NetworkedComponent] +public sealed partial class WantedListCartridgeComponent : Component; diff --git a/Content.Shared/CartridgeLoader/SharedCartridgeLoaderSystem.cs b/Content.Shared/CartridgeLoader/SharedCartridgeLoaderSystem.cs deleted file mode 100644 index 5351122a74..0000000000 --- a/Content.Shared/CartridgeLoader/SharedCartridgeLoaderSystem.cs +++ /dev/null @@ -1,133 +0,0 @@ -using Content.Shared.Containers.ItemSlots; -using Robust.Shared.Containers; - -namespace Content.Shared.CartridgeLoader; - -public abstract partial class SharedCartridgeLoaderSystem : EntitySystem -{ - public const string InstalledContainerId = "program-container"; - - [Dependency] private ItemSlotsSystem _itemSlotsSystem = default!; - [Dependency] private SharedAppearanceSystem _appearanceSystem = default!; - [Dependency] private SharedContainerSystem _container = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnComponentRemove); - - SubscribeLocalEvent(OnItemInserted); - SubscribeLocalEvent(OnItemRemoved); - } - - private void OnComponentInit(EntityUid uid, CartridgeLoaderComponent loader, ComponentInit args) - { - _itemSlotsSystem.AddItemSlot(uid, CartridgeLoaderComponent.CartridgeSlotId, loader.CartridgeSlot); - } - - /// - /// Marks installed program entities for deletion when the component gets removed - /// - private void OnComponentRemove(EntityUid uid, CartridgeLoaderComponent loader, ComponentRemove args) - { - _itemSlotsSystem.RemoveItemSlot(uid, loader.CartridgeSlot); - if (_container.TryGetContainer(uid, InstalledContainerId, out var cont)) - _container.ShutdownContainer(cont); - } - - protected virtual void OnItemInserted(EntityUid uid, CartridgeLoaderComponent loader, EntInsertedIntoContainerMessage args) - { - UpdateAppearanceData(uid, loader); - } - - protected virtual void OnItemRemoved(EntityUid uid, CartridgeLoaderComponent loader, EntRemovedFromContainerMessage args) - { - UpdateAppearanceData(uid, loader); - } - - private void UpdateAppearanceData(EntityUid uid, CartridgeLoaderComponent loader) - { - _appearanceSystem.SetData(uid, CartridgeLoaderVisuals.CartridgeInserted, loader.CartridgeSlot.HasItem); - } -} - -/// -/// Gets sent to program / cartridge entities when they get inserted or installed -/// -public sealed class CartridgeAddedEvent : EntityEventArgs -{ - public readonly EntityUid Loader; - - public CartridgeAddedEvent(EntityUid loader) - { - Loader = loader; - } -} - -/// -/// Gets sent to cartridge entities when they get ejected -/// -public sealed class CartridgeRemovedEvent : EntityEventArgs -{ - public readonly EntityUid Loader; - - public CartridgeRemovedEvent(EntityUid loader) - { - Loader = loader; - } -} - -/// -/// Gets sent to program / cartridge entities when they get activated -/// -/// -/// Don't update the programs ui state in this events listener -/// -public sealed class CartridgeActivatedEvent : EntityEventArgs -{ - public readonly EntityUid Loader; - - public CartridgeActivatedEvent(EntityUid loader) - { - Loader = loader; - } -} - -/// -/// Gets sent to program / cartridge entities when they get deactivated -/// -public sealed class CartridgeDeactivatedEvent : EntityEventArgs -{ - public readonly EntityUid Loader; - - public CartridgeDeactivatedEvent(EntityUid loader) - { - Loader = loader; - } -} - -/// -/// Gets sent to program / cartridge entities when the ui is ready to be updated by the cartridge. -/// -/// -/// This is used for the initial ui state update because updating the ui in the activate event doesn't work -/// -public sealed class CartridgeUiReadyEvent : EntityEventArgs -{ - public readonly EntityUid Loader; - - public CartridgeUiReadyEvent(EntityUid loader) - { - Loader = loader; - } -} - -/// -/// Gets sent by the cartridge loader system to the cartridge loader entity so another system -/// can handle displaying the notification -/// -/// The message to be displayed -[ByRefEvent] -public record struct CartridgeLoaderNotificationSentEvent(string Header, string Message); diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index ad3b59b0ea..a122bdc938 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -54,7 +54,8 @@ PDA-pen: !type:ContainerSlot {} PDA-pai: !type:ContainerSlot {} Cartridge-Slot: !type:ContainerSlot {} - program-container: !type:Container + preinstalled-program-container: !type:Container + removable-program-container: !type:Container - type: ItemSlots - type: Clothing quickEquip: false @@ -79,11 +80,6 @@ range: 500 - type: CartridgeLoader uiKey: enum.PdaUiKey.Key - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge cartridgeSlot: priority: -1 name: device-pda-slot-component-slot-name-cartridge @@ -92,6 +88,14 @@ whitelist: components: - Cartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge - type: ActivatableUI key: enum.PdaUiKey.Key singleUser: true @@ -149,27 +153,30 @@ id: BaseSecurityPDA abstract: true components: - - type: CartridgeLoader - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - WantedListCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: WantedListCartridge - type: entity parent: BasePDA id: BaseMedicalPDA abstract: true components: - - type: CartridgeLoader - uiKey: enum.PdaUiKey.Key - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - MedTekCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: MedTekCartridge - type: entity parent: BasePDA @@ -414,9 +421,6 @@ whitelist: components: - Cartridge - preinstalled: - - CrewManifestCartridge - - NewsReaderCartridge - type: Appearance appearanceDataInit: enum.PdaVisuals.PdaType: @@ -428,6 +432,12 @@ - type: Icon state: pda-mime - type: Muted # for random sentience event + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NewsReaderCartridge - type: entity name: chaplain PDA @@ -476,13 +486,15 @@ accentVColor: "#a23e3e" - type: Icon state: pda-qm - - type: CartridgeLoader - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - AstroNavCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: AstroNavCartridge - type: entity parent: BasePDA @@ -520,14 +532,15 @@ accentVColor: "#8900c9" - type: Icon state: pda-miner - - type: CartridgeLoader - uiKey: enum.PdaUiKey.Key - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - AstroNavCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: AstroNavCartridge - type: entity parent: BasePDA @@ -870,14 +883,16 @@ accentHColor: "#447987" - type: Icon state: pda-hos - - type: CartridgeLoader - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - WantedListCartridge - - LogProbeCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: WantedListCartridge + - id: LogProbeCartridge - type: entity parent: BaseSecurityPDA @@ -939,17 +954,18 @@ borderColor: "#00842e" - type: Icon state: pda-centcom - - type: CartridgeLoader - uiKey: enum.PdaUiKey.Key - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - MedTekCartridge - - WantedListCartridge - - LogProbeCartridge - - AstroNavCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: MedTekCartridge + - id: WantedListCartridge + - id: LogProbeCartridge + - id: AstroNavCartridge - type: entity parent: CentcomPDA @@ -965,17 +981,19 @@ scanDelay: 0 silent: true - type: CartridgeLoader - uiKey: enum.PdaUiKey.Key notificationsEnabled: false - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - LogProbeCartridge - - WantedListCartridge - - MedTekCartridge - - AstroNavCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: LogProbeCartridge + - id: WantedListCartridge + - id: MedTekCartridge + - id: AstroNavCartridge - type: Tag # Ignore Chameleon tags tags: - DoorBumpOpener @@ -1089,11 +1107,12 @@ borderColor: "#891417" - type: Icon state: pda-syndi - - type: CartridgeLoader - uiKey: enum.PdaUiKey.Key - preinstalled: - - NotekeeperCartridge - - NanoTaskCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: NotekeeperCartridge + - id: NanoTaskCartridge - type: entity parent: [ SyndiPDA, BaseSyndicateContraband ] @@ -1126,12 +1145,13 @@ pda-syndi-corpsman - type: Icon state: pda-syndi-corpsman - - type: CartridgeLoader - uiKey: enum.PdaUiKey.Key - preinstalled: - - NotekeeperCartridge - - NanoTaskCartridge - - MedTekCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: MedTekCartridge - type: entity parent: [ SyndiOperativePDA, BaseSyndicateContraband ] @@ -1169,17 +1189,18 @@ accentVColor: "#447987" - type: Icon state: pda-ert - - type: CartridgeLoader - uiKey: enum.PdaUiKey.Key - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - MedTekCartridge - - WantedListCartridge - - LogProbeCartridge - - AstroNavCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: MedTekCartridge + - id: WantedListCartridge + - id: LogProbeCartridge + - id: AstroNavCartridge - type: entity parent: ERTLeaderPDA @@ -1341,14 +1362,16 @@ borderColor: "#774705" - type: Icon state: pda-detective - - type: CartridgeLoader - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - WantedListCartridge - - LogProbeCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: WantedListCartridge + - id: LogProbeCartridge - type: entity parent: BaseMedicalPDA @@ -1369,14 +1392,16 @@ accentVColor: "#d7d7d0" - type: Icon state: pda-brigmedic - - type: CartridgeLoader - preinstalled: - - CrewManifestCartridge - - NotekeeperCartridge - - NanoTaskCartridge - - NewsReaderCartridge - - WantedListCartridge - - MedTekCartridge + - type: EntityTableContainerFill + containers: + preinstalled-program-container: !type:AllSelector + children: + - id: CrewManifestCartridge + - id: NotekeeperCartridge + - id: NanoTaskCartridge + - id: NewsReaderCartridge + - id: WantedListCartridge + - id: MedTekCartridge - type: entity parent: ClownPDA