using Dalamud.Plugin; using LightlessSync.Interop.Ipc.Framework; using LightlessSync.Interop.Ipc.Penumbra; using LightlessSync.LightlessConfiguration.Models; using LightlessSync.PlayerData.Handlers; using LightlessSync.Services; using LightlessSync.Services.ActorTracking; using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; using Penumbra.Api.IpcSubscribers; namespace LightlessSync.Interop.Ipc; public sealed class IpcCallerPenumbra : IpcServiceBase { private static readonly IpcServiceDescriptor PenumbraDescriptor = new("Penumbra", "Penumbra", new Version(1, 2, 0, 22)); private readonly PenumbraCollections _collections; private readonly PenumbraResource _resources; private readonly PenumbraRedraw _redraw; private readonly PenumbraTexture _textures; private readonly GetEnabledState _penumbraEnabled; private readonly GetModDirectory _penumbraGetModDirectory; private readonly EventSubscriber _penumbraInit; private readonly EventSubscriber _penumbraDispose; private readonly EventSubscriber _penumbraModSettingChanged; private bool _shownPenumbraUnavailable; private string? _modDirectory; public IpcCallerPenumbra( ILogger logger, IDalamudPluginInterface pluginInterface, DalamudUtilService dalamudUtil, LightlessMediator mediator, RedrawManager redrawManager, ActorObjectService actorObjectService) : base(logger, mediator, pluginInterface, PenumbraDescriptor) { _penumbraEnabled = new GetEnabledState(pluginInterface); _penumbraGetModDirectory = new GetModDirectory(pluginInterface); _penumbraInit = Initialized.Subscriber(pluginInterface, HandlePenumbraInitialized); _penumbraDispose = Disposed.Subscriber(pluginInterface, HandlePenumbraDisposed); _penumbraModSettingChanged = ModSettingChanged.Subscriber(pluginInterface, HandlePenumbraModSettingChanged); _collections = RegisterInterop(new PenumbraCollections(logger, pluginInterface, dalamudUtil, mediator)); _resources = RegisterInterop(new PenumbraResource(logger, pluginInterface, dalamudUtil, mediator, actorObjectService)); _redraw = RegisterInterop(new PenumbraRedraw(logger, pluginInterface, dalamudUtil, mediator, redrawManager)); _textures = RegisterInterop(new PenumbraTexture(logger, pluginInterface, dalamudUtil, mediator, _redraw)); SubscribeMediatorEvents(); CheckAPI(); CheckModDirectory(); } public string? ModDirectory { get => _modDirectory; private set { if (string.Equals(_modDirectory, value, StringComparison.Ordinal)) { return; } _modDirectory = value; Mediator.Publish(new PenumbraDirectoryChangedMessage(_modDirectory)); } } public Task AssignTemporaryCollectionAsync(ILogger logger, Guid collectionId, int objectIndex) => _collections.AssignTemporaryCollectionAsync(logger, collectionId, objectIndex); public Task CreateTemporaryCollectionAsync(ILogger logger, string uid) => _collections.CreateTemporaryCollectionAsync(logger, uid); public Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collectionId) => _collections.RemoveTemporaryCollectionAsync(logger, applicationId, collectionId); public Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collectionId, Dictionary modPaths) => _collections.SetTemporaryModsAsync(logger, applicationId, collectionId, modPaths); public Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collectionId, string manipulationData) => _collections.SetManipulationDataAsync(logger, applicationId, collectionId, manipulationData); public Task>?> GetCharacterData(ILogger logger, GameObjectHandler handler) => _resources.GetCharacterDataAsync(logger, handler); public string GetMetaManipulations() => _resources.GetMetaManipulations(); public Task>>> GetClientOnScreenResourcePaths() // why did i add this, i honestly don't know but i'm keeping it anyways, fuck you => _resources.GetClientOnScreenResourcePathsAsync(); public Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse) => _resources.ResolvePathsAsync(forward, reverse); public Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token) => _redraw.RedrawAsync(logger, handler, applicationId, token); public Task ConvertTextureFiles(ILogger logger, IReadOnlyList jobs, IProgress? progress, CancellationToken token) => _textures.ConvertTextureFilesAsync(logger, jobs, progress, token); public Task ConvertTextureFileDirectAsync(TextureConversionJob job, CancellationToken token) => _textures.ConvertTextureFileDirectAsync(job, token); public void CheckModDirectory() { if (!APIAvailable) { ModDirectory = string.Empty; return; } try { ModDirectory = _penumbraGetModDirectory.Invoke().ToLowerInvariant(); } catch (Exception ex) { Logger.LogWarning(ex, "Failed to resolve Penumbra mod directory"); } } protected override bool IsPluginEnabled(IExposedPlugin plugin) { try { return _penumbraEnabled.Invoke(); } catch { return false; } } protected override void OnConnectionStateChanged(IpcConnectionState previous, IpcConnectionState current) { base.OnConnectionStateChanged(previous, current); if (current == IpcConnectionState.Available) { _shownPenumbraUnavailable = false; if (string.IsNullOrEmpty(ModDirectory)) { CheckModDirectory(); } return; } ModDirectory = string.Empty; _redraw.CancelPendingRedraws(); if (_shownPenumbraUnavailable || current == IpcConnectionState.Unknown) { return; } _shownPenumbraUnavailable = true; Mediator.Publish(new NotificationMessage( "Penumbra inactive", "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Lightless. If you just updated Penumbra, ignore this message.", NotificationType.Error)); } private void SubscribeMediatorEvents() { Mediator.Subscribe(this, msg => { _redraw.RequestImmediateRedraw(msg.Character.ObjectIndex, RedrawType.AfterGPose); }); Mediator.Subscribe(this, _ => _shownPenumbraUnavailable = false); Mediator.Subscribe(this, msg => _resources.TrackActor(msg.Descriptor.Address)); Mediator.Subscribe(this, msg => _resources.UntrackActor(msg.Descriptor.Address)); Mediator.Subscribe(this, msg => _resources.TrackActor(msg.GameObjectHandler.Address)); Mediator.Subscribe(this, msg => _resources.UntrackActor(msg.GameObjectHandler.Address)); } private void HandlePenumbraInitialized() { Mediator.Publish(new PenumbraInitializedMessage()); CheckModDirectory(); _redraw.RequestImmediateRedraw(0, RedrawType.Redraw); CheckAPI(); } private void HandlePenumbraDisposed() { _redraw.CancelPendingRedraws(); ModDirectory = string.Empty; Mediator.Publish(new PenumbraDisposedMessage()); CheckAPI(); } private void HandlePenumbraModSettingChanged(ModSettingChange change, Guid _, string __, bool ___) { if (change == ModSettingChange.EnableState) { Mediator.Publish(new PenumbraModSettingChangedMessage()); CheckAPI(); } } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (!disposing) { return; } _penumbraModSettingChanged.Dispose(); _penumbraDispose.Dispose(); _penumbraInit.Dispose(); } }