using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Api.Helpers; using Glamourer.Api.IpcSubscribers; using MareSynchronos.MareConfiguration.Models; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using Microsoft.Extensions.Logging; namespace MareSynchronos.Interop.Ipc; public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcCaller { private readonly ILogger _logger; private readonly DalamudUtilService _dalamudUtil; private readonly MareMediator _mareMediator; private readonly RedrawManager _redrawManager; private readonly ApiVersion _glamourerApiVersions; private readonly ApplyState? _glamourerApplyAll; private readonly GetStateBase64? _glamourerGetAllCustomization; private readonly RevertState _glamourerRevert; private readonly RevertStateName _glamourerRevertByName; private readonly UnlockState _glamourerUnlock; private readonly UnlockStateName _glamourerUnlockByName; private readonly EventSubscriber? _glamourerStateChanged; private bool _pluginLoaded; private Version _pluginVersion; private bool _shownGlamourerUnavailable = false; private readonly uint LockCode = 0x626E7579; public IpcCallerGlamourer(ILogger logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator, RedrawManager redrawManager) : base(logger, mareMediator) { _glamourerApiVersions = new ApiVersion(pi); _glamourerGetAllCustomization = new GetStateBase64(pi); _glamourerApplyAll = new ApplyState(pi); _glamourerRevert = new RevertState(pi); _glamourerRevertByName = new RevertStateName(pi); _glamourerUnlock = new UnlockState(pi); _glamourerUnlockByName = new UnlockStateName(pi); _logger = logger; _dalamudUtil = dalamudUtil; _mareMediator = mareMediator; _redrawManager = redrawManager; var plugin = PluginWatcherService.GetInitialPluginState(pi, "Glamourer"); _pluginLoaded = plugin?.IsLoaded ?? false; _pluginVersion = plugin?.Version ?? new(0, 0, 0, 0); Mediator.SubscribeKeyed(this, "Glamourer", (msg) => { _pluginLoaded = msg.IsLoaded; _pluginVersion = msg.Version; CheckAPI(); }); CheckAPI(); _glamourerStateChanged = StateChanged.Subscriber(pi, GlamourerChanged); _glamourerStateChanged.Enable(); Mediator.Subscribe(this, s => _shownGlamourerUnavailable = false); } protected override void Dispose(bool disposing) { base.Dispose(disposing); _redrawManager.Cancel(); _glamourerStateChanged?.Dispose(); } public bool APIAvailable { get; private set; } public void CheckAPI() { bool apiAvailable = false; try { bool versionValid = _pluginLoaded && _pluginVersion >= new Version(1, 0, 6, 1); try { var version = _glamourerApiVersions.Invoke(); if (version is { Major: 1, Minor: >= 1 } && versionValid) { apiAvailable = true; } } catch { // ignore } _shownGlamourerUnavailable = _shownGlamourerUnavailable && !apiAvailable; APIAvailable = apiAvailable; } catch { APIAvailable = apiAvailable; } finally { if (!apiAvailable && !_shownGlamourerUnavailable) { _shownGlamourerUnavailable = true; _mareMediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Elezen. If you just updated Glamourer, ignore this message.", NotificationType.Error)); } } } public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool allowImmediate = false) { if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; // Call immediately if possible if (allowImmediate && _dalamudUtil.IsOnFrameworkThread && !await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) { var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false); if (gameObj is ICharacter chara) { logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId); _glamourerApplyAll!.Invoke(customization, chara.ObjectIndex, LockCode); return; } } await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false); try { await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => { try { logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId); _glamourerApplyAll!.Invoke(customization, chara.ObjectIndex, LockCode); } catch (Exception ex) { logger.LogWarning(ex, "[{appid}] Failed to apply Glamourer data", applicationId); } }, token).ConfigureAwait(false); } finally { _redrawManager.RedrawSemaphore.Release(); } } public async Task GetCharacterCustomizationAsync(IntPtr character) { if (!APIAvailable) return string.Empty; try { return await _dalamudUtil.RunOnFrameworkThread(() => { var gameObj = _dalamudUtil.CreateGameObject(character); if (gameObj is ICharacter c) { return _glamourerGetAllCustomization!.Invoke(c.ObjectIndex).Item2 ?? string.Empty; } return string.Empty; }).ConfigureAwait(false); } catch { return string.Empty; } } public async Task RevertAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token) { if ((!APIAvailable) || _dalamudUtil.IsZoning) return; try { await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false); await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => { try { logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlock", applicationId); _glamourerUnlock.Invoke(chara.ObjectIndex, LockCode); logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevert", applicationId); _glamourerRevert.Invoke(chara.ObjectIndex, LockCode); logger.LogDebug("[{appid}] Calling On IPC: PenumbraRedraw", applicationId); _mareMediator.Publish(new PenumbraRedrawCharacterMessage(chara)); } catch (Exception ex) { logger.LogWarning(ex, "[{appid}] Error during GlamourerRevert", applicationId); } }, token).ConfigureAwait(false); } finally { _redrawManager.RedrawSemaphore.Release(); } } public void RevertNow(ILogger logger, Guid applicationId, int objectIndex) { if ((!APIAvailable) || _dalamudUtil.IsZoning) return; logger.LogTrace("[{applicationId}] Immediately reverting object index {objId}", applicationId, objectIndex); _glamourerRevert.Invoke(objectIndex, LockCode); } public void RevertByNameNow(ILogger logger, Guid applicationId, string name) { if ((!APIAvailable) || _dalamudUtil.IsZoning) return; logger.LogTrace("[{applicationId}] Immediately reverting {name}", applicationId, name); _glamourerRevertByName.Invoke(name, LockCode); } public async Task RevertByNameAsync(ILogger logger, string name, Guid applicationId) { if ((!APIAvailable) || _dalamudUtil.IsZoning) return; await _dalamudUtil.RunOnFrameworkThread(() => { RevertByName(logger, name, applicationId); }).ConfigureAwait(false); } public void RevertByName(ILogger logger, string name, Guid applicationId) { if ((!APIAvailable) || _dalamudUtil.IsZoning) return; try { logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId); _glamourerRevertByName.Invoke(name, LockCode); logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); _glamourerUnlockByName.Invoke(name, LockCode); } catch (Exception ex) { _logger.LogWarning(ex, "Error during Glamourer RevertByName"); } } private void GlamourerChanged(nint address) { _mareMediator.Publish(new GlamourerChangedMessage(address)); } }