using Content.Server.DeviceNetwork.Systems; using Content.Shared.CartridgeLoader; using Content.Shared.Interaction; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.Map; namespace Content.Server.CartridgeLoader; public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem { [Dependency] private readonly ContainerSystem _containerSystem = default!; [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; private const string ContainerName = "program-container"; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnPacketReceived); SubscribeLocalEvent(OnUsed); SubscribeLocalEvent(OnLoaderUiMessage); SubscribeLocalEvent(OnUiMessage); } /// /// 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, CartridgeLoaderUiState state, IPlayerSession? session = default!, CartridgeLoaderComponent? loader = default!) { if (!Resolve(loaderUid, ref loader)) return; state.ActiveUI = loader.ActiveProgram; state.Programs = GetAvailablePrograms(loaderUid, loader); var ui = _userInterfaceSystem.GetUiOrNull(loader.Owner, loader.UiKey); if (ui != null) _userInterfaceSystem.SetUiState(ui, state, session); } /// /// 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, IPlayerSession? session = default!, CartridgeLoaderComponent? loader = default!) { if (!Resolve(loaderUid, ref loader)) return; var ui = _userInterfaceSystem.GetUiOrNull(loader.Owner, loader.UiKey); if (ui != null) _userInterfaceSystem.SetUiState(ui, state, session); } /// /// 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(); //Don't count a cartridge that has already been installed as available to avoid confusion if (loader.CartridgeSlot.HasItem && IsInstalled(Prototype(loader.CartridgeSlot.Item!.Value)?.ID, loader)) return loader.InstalledPrograms; var available = new List(); available.AddRange(loader.InstalledPrograms); if (loader.CartridgeSlot.HasItem) available.Add(loader.CartridgeSlot.Item!.Value); 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) || loader.InstalledPrograms.Count >= loader.DiskSpace) 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 var prototypeId = Prototype(cartridgeUid)?.ID; return prototypeId != null && InstallProgram(loaderUid, prototypeId, loader); } /// /// Installs a program by its prototype /// /// The cartridge loader uid /// The prototype name /// The cartridge loader component /// Whether installing the cartridge was successful public bool InstallProgram(EntityUid loaderUid, string prototype, CartridgeLoaderComponent? loader = default!) { if (!Resolve(loaderUid, ref loader) || loader.InstalledPrograms.Count >= loader.DiskSpace) return false; if (!_containerSystem.TryGetContainer(loaderUid, ContainerName, out var container)) return false; //Prevent installing cartridges that have already been installed if (IsInstalled(prototype, loader)) return false; var installedProgram = Spawn(prototype, new EntityCoordinates(loaderUid, 0, 0)); container?.Insert(installedProgram); UpdateCartridgeInstallationStatus(installedProgram, InstallationStatus.Installed); loader.InstalledPrograms.Add(installedProgram); 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) || !ContainsCartridge(programUid, loader, true)) return false; if (loader.ActiveProgram == programUid) loader.ActiveProgram = null; loader.BackgroundPrograms.Remove(programUid); loader.InstalledPrograms.Remove(programUid); EntityManager.QueueDeleteEntity(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 (!ContainsCartridge(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 (!ContainsCartridge(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 (!ContainsCartridge(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 (!ContainsCartridge(cartridgeUid, loader)) return; if (loader.ActiveProgram != cartridgeUid) RaiseLocalEvent(cartridgeUid, new CartridgeDeactivatedEvent(loaderUid)); loader.BackgroundPrograms.Remove(cartridgeUid); } protected override void OnItemInserted(EntityUid uid, SharedCartridgeLoaderComponent loader, EntInsertedIntoContainerMessage args) { RaiseLocalEvent(args.Entity, new CartridgeAddedEvent(uid)); base.OnItemInserted(uid, loader, args); } protected override void OnItemRemoved(EntityUid uid, SharedCartridgeLoaderComponent loader, EntRemovedFromContainerMessage args) { 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)); RaiseLocalEvent(args.Entity, new CartridgeRemovedEvent(uid)); base.OnItemRemoved(uid, loader, args); } /// /// Installs programs from the list of preinstalled programs /// private void OnMapInit(EntityUid uid, CartridgeLoaderComponent component, MapInitEvent args) { foreach (var prototype in component.PreinstalledPrograms) { InstallProgram(uid, prototype); } } 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) { switch (message.Action) { case CartridgeUiMessageAction.Activate: ActivateProgram(loaderUid, message.CartridgeUid, component); break; case CartridgeUiMessageAction.Deactivate: DeactivateProgram(loaderUid, message.CartridgeUid, component); break; case CartridgeUiMessageAction.Install: InstallCartridge(loaderUid, message.CartridgeUid, component); break; case CartridgeUiMessageAction.Uninstall: UninstallProgram(loaderUid, message.CartridgeUid, component); break; case CartridgeUiMessageAction.UIReady: if (component.ActiveProgram.HasValue) RaiseLocalEvent(component.ActiveProgram.Value, new CartridgeUiReadyEvent(loaderUid)); break; default: throw new ArgumentOutOfRangeException(); } } /// /// 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.LoaderUid = uid; 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); } } /// /// Checks if a program is already installed by searching for its prototype name in the list of installed programs /// private bool IsInstalled(string? prototype, CartridgeLoaderComponent loader) { foreach (var program in loader.InstalledPrograms) { if (Prototype(program)?.ID == prototype) return true; } return false; } /// /// 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, new CartridgeLoaderUiState(), null, loader); } private void UpdateCartridgeInstallationStatus(EntityUid cartridgeUid, InstallationStatus installationStatus, CartridgeComponent? cartridgeComponent = default!) { if (Resolve(cartridgeUid, ref cartridgeComponent)) { cartridgeComponent.InstallationStatus = installationStatus; Dirty(cartridgeComponent); } } private bool ContainsCartridge(EntityUid cartridgeUid, CartridgeLoaderComponent loader , bool onlyInstalled = false) { return !onlyInstalled && loader.CartridgeSlot.Item?.Equals(cartridgeUid) == true || loader.InstalledPrograms.Contains(cartridgeUid); } } /// /// 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; } }