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 Club Penguin Sync. 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)); } }