using System.Collections.Concurrent; using Dalamud.Plugin; using LightlessSync.Interop.Ipc.Framework; using LightlessSync.PlayerData.Handlers; using LightlessSync.Services; using LightlessSync.Services.ActorTracking; using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; using Penumbra.Api.Helpers; using Penumbra.Api.IpcSubscribers; namespace LightlessSync.Interop.Ipc.Penumbra; public sealed class PenumbraResource : PenumbraBase { private readonly ActorObjectService _actorObjectService; private readonly GetGameObjectResourcePaths _gameObjectResourcePaths; private readonly ResolvePlayerPathsAsync _resolvePlayerPaths; private readonly GetPlayerMetaManipulations _getPlayerMetaManipulations; private readonly EventSubscriber _gameObjectResourcePathResolved; private readonly ConcurrentDictionary _trackedActors = new(); public PenumbraResource( ILogger logger, IDalamudPluginInterface pluginInterface, DalamudUtilService dalamudUtil, LightlessMediator mediator, ActorObjectService actorObjectService) : base(logger, pluginInterface, dalamudUtil, mediator) { _actorObjectService = actorObjectService; _gameObjectResourcePaths = new GetGameObjectResourcePaths(pluginInterface); _resolvePlayerPaths = new ResolvePlayerPathsAsync(pluginInterface); _getPlayerMetaManipulations = new GetPlayerMetaManipulations(pluginInterface); _gameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pluginInterface, HandleResourceLoaded); foreach (var descriptor in _actorObjectService.PlayerDescriptors) { TrackActor(descriptor.Address); } } public override string Name => "Penumbra.Resources"; public async Task>?> GetCharacterDataAsync(ILogger logger, GameObjectHandler handler) { if (!IsAvailable) { return null; } return await DalamudUtil.RunOnFrameworkThread(() => { logger.LogTrace("Calling On IPC: Penumbra.GetGameObjectResourcePaths"); var idx = handler.GetGameObject()?.ObjectIndex; if (idx == null) { return null; } return _gameObjectResourcePaths.Invoke(idx.Value)[0]; }).ConfigureAwait(false); } public string GetMetaManipulations() => IsAvailable ? _getPlayerMetaManipulations.Invoke() : string.Empty; public async Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forwardPaths, string[] reversePaths) { if (!IsAvailable) { return (Array.Empty(), Array.Empty()); } return await _resolvePlayerPaths.Invoke(forwardPaths, reversePaths).ConfigureAwait(false); } public void TrackActor(nint address) { if (address != nint.Zero) { _trackedActors[(IntPtr)address] = 0; } } public void UntrackActor(nint address) { if (address != nint.Zero) { _trackedActors.TryRemove((IntPtr)address, out _); } } private void HandleResourceLoaded(nint ptr, string resolvedPath, string gamePath) { if (ptr == nint.Zero) { return; } if (!_trackedActors.ContainsKey(ptr)) { var descriptor = _actorObjectService.PlayerDescriptors.FirstOrDefault(d => d.Address == ptr); if (descriptor.Address != nint.Zero) { _trackedActors[ptr] = 0; } else { return; } } if (string.Compare(resolvedPath, gamePath, StringComparison.OrdinalIgnoreCase) == 0) { return; } Mediator.Publish(new PenumbraResourceLoadMessage(ptr, resolvedPath, gamePath)); } protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current) { if (current != IpcConnectionState.Available) { _trackedActors.Clear(); } else { foreach (var descriptor in _actorObjectService.PlayerDescriptors) { TrackActor(descriptor.Address); } } } public override void Dispose() { base.Dispose(); _gameObjectResourcePathResolved.Dispose(); } }