From e94a2a75650ea0a48f676b53b43b4b9ca026fc7b Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 29 Feb 2024 13:04:12 +0100 Subject: [PATCH] add moodles integration (temporary) --- .../Interop/Ipc/IpcCallerMoodles.cs | 104 ++++++++++++++++++ MareSynchronos/Interop/Ipc/IpcManager.cs | 5 +- .../PlayerData/Data/CharacterData.cs | 16 +-- .../PlayerData/Data/PlayerChanges.cs | 3 +- .../PlayerData/Factories/PlayerDataFactory.cs | 4 + .../PlayerData/Handlers/PairHandler.cs | 6 + .../PlayerData/Pairs/OptionalPluginWarning.cs | 1 + .../Services/CacheCreationService.cs | 26 +++++ MareSynchronos/Plugin.cs | 5 +- MareSynchronos/Services/Mediator/Messages.cs | 1 + .../PluginWarningNotificationService.cs | 6 + MareSynchronos/UI/UISharedService.cs | 25 +++-- MareSynchronos/Utils/VariousExtensions.cs | 7 ++ 13 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs diff --git a/MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs b/MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs new file mode 100644 index 0000000..32ccb70 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs @@ -0,0 +1,104 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Interop.Ipc; + +public sealed class IpcCallerMoodles : IIpcCaller +{ + private readonly ICallGateSubscriber _moodlesApiVersion; + private readonly ICallGateSubscriber _moodlesOnChange; + private readonly ICallGateSubscriber _moodlesGetStatus; + private readonly ICallGateSubscriber _moodlesSetStatus; + private readonly ICallGateSubscriber _moodlesRevertStatus; + private readonly ILogger _logger; + private readonly DalamudUtilService _dalamudUtil; + private readonly MareMediator _mareMediator; + + public IpcCallerMoodles(ILogger logger, DalamudPluginInterface pi, DalamudUtilService dalamudUtil, + MareMediator mareMediator) + { + _logger = logger; + _dalamudUtil = dalamudUtil; + _mareMediator = mareMediator; + + _moodlesApiVersion = pi.GetIpcSubscriber("Moodles.Version"); + _moodlesOnChange = pi.GetIpcSubscriber("Moodles.StatusManagerModified"); + _moodlesGetStatus = pi.GetIpcSubscriber("Moodles.GetStatusManagerByPtr"); + _moodlesSetStatus = pi.GetIpcSubscriber("Moodles.SetStatusManagerByPtr"); + _moodlesRevertStatus = pi.GetIpcSubscriber("Moodles.ClearStatusManagerByPtr"); + + _moodlesOnChange.Subscribe(OnMoodlesChange); + + CheckAPI(); + } + + private void OnMoodlesChange(PlayerCharacter character) + { + _mareMediator.Publish(new MoodlesMessage(character.Address)); + } + + public bool APIAvailable { get; private set; } = false; + + public void CheckAPI() + { + try + { + APIAvailable = _moodlesApiVersion.InvokeFunc() == 1; + } + catch + { + APIAvailable = false; + } + } + + public void Dispose() + { + _moodlesOnChange.Unsubscribe(OnMoodlesChange); + } + + public async Task GetStatusAsync(nint address) + { + if (!APIAvailable) return null; + + try + { + return await _dalamudUtil.RunOnFrameworkThread(() => _moodlesGetStatus.InvokeFunc(address)).ConfigureAwait(false); + + } + catch (Exception e) + { + _logger.LogWarning(e, "Could not Get Moodles Status"); + return null; + } + } + + public async Task SetStatusAsync(nint pointer, string status) + { + if (!APIAvailable) return; + try + { + await _dalamudUtil.RunOnFrameworkThread(() => _moodlesSetStatus.InvokeAction(pointer, status)).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogWarning(e, "Could not Set Moodles Status"); + } + } + + public async Task RevertStatusAsync(nint pointer) + { + if (!APIAvailable) return; + try + { + await _dalamudUtil.RunOnFrameworkThread(() => _moodlesRevertStatus.InvokeAction(pointer)).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogWarning(e, "Could not Set Moodles Status"); + } + } +} diff --git a/MareSynchronos/Interop/Ipc/IpcManager.cs b/MareSynchronos/Interop/Ipc/IpcManager.cs index 9b92154..8be4968 100644 --- a/MareSynchronos/Interop/Ipc/IpcManager.cs +++ b/MareSynchronos/Interop/Ipc/IpcManager.cs @@ -7,13 +7,14 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase { public IpcManager(ILogger logger, MareMediator mediator, IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc, - IpcCallerHonorific honorificIpc) : base(logger, mediator) + IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc) : base(logger, mediator) { CustomizePlus = customizeIpc; Heels = heelsIpc; Glamourer = glamourerIpc; Penumbra = penumbraIpc; Honorific = honorificIpc; + Moodles = moodlesIpc; if (Initialized) { @@ -39,6 +40,7 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase public IpcCallerHeels Heels { get; init; } public IpcCallerGlamourer Glamourer { get; } public IpcCallerPenumbra Penumbra { get; } + public IpcCallerMoodles Moodles { get; } private void PeriodicApiStateCheck() { @@ -48,5 +50,6 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase Heels.CheckAPI(); CustomizePlus.CheckAPI(); Honorific.CheckAPI(); + Moodles.CheckAPI(); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Data/CharacterData.cs b/MareSynchronos/PlayerData/Data/CharacterData.cs index 21643fe..2a841cc 100644 --- a/MareSynchronos/PlayerData/Data/CharacterData.cs +++ b/MareSynchronos/PlayerData/Data/CharacterData.cs @@ -2,8 +2,6 @@ using MareSynchronos.API.Data.Enum; -using System.Text; - namespace MareSynchronos.PlayerData.Data; public class CharacterData @@ -16,6 +14,7 @@ public class CharacterData public string HeelsData { get; set; } = string.Empty; public string HonorificData { get; set; } = string.Empty; public string ManipulationString { get; set; } = string.Empty; + public string MoodlesData { get; set; } = string.Empty; public API.Data.CharacterData ToAPI() { @@ -44,17 +43,8 @@ public class CharacterData ManipulationData = ManipulationString, HeelsData = HeelsData, CustomizePlusData = CustomizePlusScale.ToDictionary(d => d.Key, d => d.Value), - HonorificData = HonorificData + HonorificData = HonorificData, + PalettePlusData = MoodlesData }; } - - public override string ToString() - { - StringBuilder stringBuilder = new(); - foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths.First(), StringComparer.Ordinal)) - { - stringBuilder.Append(fileReplacement).AppendLine(); - } - return stringBuilder.ToString(); - } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Data/PlayerChanges.cs b/MareSynchronos/PlayerData/Data/PlayerChanges.cs index 034fd58..e75a2b8 100644 --- a/MareSynchronos/PlayerData/Data/PlayerChanges.cs +++ b/MareSynchronos/PlayerData/Data/PlayerChanges.cs @@ -8,5 +8,6 @@ public enum PlayerChanges Customize = 4, Heels = 5, Honorific = 7, - ForcedRedraw = 8, + Moodles = 8, + ForcedRedraw = 9, } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index a7be419..0c12485 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -207,6 +207,10 @@ public class PlayerDataFactory _logger.LogDebug("Honorific is now: {data}", previousData.HonorificData); previousData.HeelsData = await getHeelsOffset.ConfigureAwait(false); _logger.LogDebug("Heels is now: {heels}", previousData.HeelsData); + if (objectKind == ObjectKind.Player) + { + previousData.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty; + } if (previousData.FileReplacements.TryGetValue(objectKind, out HashSet? fileReplacements)) { diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 5b24ee2..9e36c08 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -334,6 +334,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase } break; + case PlayerChanges.Moodles: + await _ipcManager.Moodles.SetStatusAsync(handler.Address, charaData.PalettePlusData).ConfigureAwait(false); + break; + case PlayerChanges.ForcedRedraw: await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); break; @@ -555,6 +559,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.Honorific.ClearTitleAsync(address).ConfigureAwait(false); + Logger.LogDebug("[{applicationId}] Restoring Moodles for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); + await _ipcManager.Moodles.RevertStatusAsync(address).ConfigureAwait(false); } else if (objectKind == ObjectKind.MinionOrMount) { diff --git a/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs b/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs index d2f64c0..f27e536 100644 --- a/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs +++ b/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs @@ -5,4 +5,5 @@ public record OptionalPluginWarning public bool ShownHeelsWarning { get; set; } = false; public bool ShownCustomizePlusWarning { get; set; } = false; public bool ShownHonorificWarning { get; set; } = false; + public bool ShownMoodlesWarning { get; set; } = false; } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Services/CacheCreationService.cs b/MareSynchronos/PlayerData/Services/CacheCreationService.cs index e6647d9..3c6f581 100644 --- a/MareSynchronos/PlayerData/Services/CacheCreationService.cs +++ b/MareSynchronos/PlayerData/Services/CacheCreationService.cs @@ -20,6 +20,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase private readonly Dictionary _playerRelatedObjects = []; private Task? _cacheCreationTask; private CancellationTokenSource _honorificCts = new(); + private CancellationTokenSource _moodlesCts = new(); private bool _isZoning = false; private readonly Dictionary _glamourerCts = new(); @@ -108,6 +109,16 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase HonorificChanged(); } }); + Mediator.Subscribe(this, (msg) => + { + if (_isZoning) return; + var changedType = _playerRelatedObjects.FirstOrDefault(f => f.Value.Address == msg.Address); + if (!default(KeyValuePair).Equals(changedType) && changedType.Key == ObjectKind.Player) + { + Logger.LogDebug("Received Moodles change, updating player"); + MoodlesChanged(); + } + }); Mediator.Subscribe(this, async (msg) => { Logger.LogDebug("Received Penumbra Mod settings change, updating player"); @@ -162,6 +173,21 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase await AddPlayerCacheToCreate().ConfigureAwait(false); }, token); } + + private void MoodlesChanged() + { + _moodlesCts?.Cancel(); + _moodlesCts?.Dispose(); + _moodlesCts = new(); + var token = _moodlesCts.Token; + + _ = Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(2), token).ConfigureAwait(false); + await AddPlayerCacheToCreate().ConfigureAwait(false); + }, token); + } + private void ProcessCacheCreation() { if (_isZoning) return; diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index c253f63..f49d635 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -101,9 +101,12 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new IpcCallerHonorific(s.GetRequiredService>(), pluginInterface, s.GetRequiredService(), s.GetRequiredService())); + collection.AddSingleton((s) => new IpcCallerMoodles(s.GetRequiredService>(), pluginInterface, + s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new IpcManager(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), - s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), + s.GetRequiredService())); collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index f631bbf..4e28d4c 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -40,6 +40,7 @@ public record HeelsOffsetMessage : MessageBase; public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : SameThreadMessage; public record CustomizePlusMessage(string ProfileName) : MessageBase; public record HonorificMessage(string NewHonorificTitle) : MessageBase; +public record MoodlesMessage(IntPtr Address) : MessageBase; public record HonorificReadyMessage : MessageBase; public record PlayerChangedMessage(CharacterData Data) : MessageBase; public record CharacterChangedMessage(GameObjectHandler GameObjectHandler) : MessageBase; diff --git a/MareSynchronos/Services/PluginWarningNotificationService.cs b/MareSynchronos/Services/PluginWarningNotificationService.cs index 7ce04db..3a375ed 100644 --- a/MareSynchronos/Services/PluginWarningNotificationService.cs +++ b/MareSynchronos/Services/PluginWarningNotificationService.cs @@ -52,6 +52,12 @@ public class PluginWarningNotificationService warning.ShownHonorificWarning = true; } + if (changes.Contains(PlayerChanges.Moodles) && !warning.ShownMoodlesWarning && !_ipcManager.Moodles.APIAvailable) + { + missingPluginsForData.Add("Moodles"); + warning.ShownMoodlesWarning = true; + } + if (missingPluginsForData.Any()) { _mediator.Publish(new NotificationMessage("Missing plugins for " + playerName, diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index b8db9ab..5ad3710 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -65,6 +65,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase private bool _heelsExists = false; private bool _honorificExists = false; + private bool _moodlesExists = false; private bool _isDirectoryWritable = false; private bool _isPenumbraDirectory = false; @@ -103,6 +104,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase _customizePlusExists = _ipcManager.CustomizePlus.APIAvailable; _heelsExists = _ipcManager.Heels.APIAvailable; _honorificExists = _ipcManager.Honorific.APIAvailable; + _moodlesExists = _ipcManager.Moodles.APIAvailable; }); } @@ -712,11 +714,6 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public bool DrawOtherPluginState() { - var penumbraColor = _penumbraExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - var glamourerColor = _glamourerExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - var heelsColor = _heelsExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - var customizeColor = _customizePlusExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - var honorificColor = _honorificExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; var check = FontAwesomeIcon.Check.ToIconString(); var cross = FontAwesomeIcon.SquareXmark.ToIconString(); ImGui.TextUnformatted("Mandatory Plugins:"); @@ -724,7 +721,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.TextUnformatted("Penumbra"); ImGui.SameLine(); - FontText(_penumbraExists ? check : cross, UiBuilder.IconFont, penumbraColor); + FontText(_penumbraExists ? check : cross, UiBuilder.IconFont, GetBoolColor(_penumbraExists)); ImGui.SameLine(); AttachToolTip($"Penumbra is " + (_penumbraExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -732,7 +729,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.TextUnformatted("Glamourer"); ImGui.SameLine(); - FontText(_glamourerExists ? check : cross, UiBuilder.IconFont, glamourerColor); + FontText(_glamourerExists ? check : cross, UiBuilder.IconFont, GetBoolColor(_glamourerExists)); ImGui.SameLine(); AttachToolTip($"Glamourer is " + (_glamourerExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -741,7 +738,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.TextUnformatted("SimpleHeels"); ImGui.SameLine(); - FontText(_heelsExists ? check : cross, UiBuilder.IconFont, heelsColor); + FontText(_heelsExists ? check : cross, UiBuilder.IconFont, GetBoolColor(_heelsExists)); ImGui.SameLine(); AttachToolTip($"SimpleHeels is " + (_heelsExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -749,7 +746,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.TextUnformatted("Customize+"); ImGui.SameLine(); - FontText(_customizePlusExists ? check : cross, UiBuilder.IconFont, customizeColor); + FontText(_customizePlusExists ? check : cross, UiBuilder.IconFont, GetBoolColor(_customizePlusExists)); ImGui.SameLine(); AttachToolTip($"Customize+ is " + (_customizePlusExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -757,11 +754,19 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.TextUnformatted("Honorific"); ImGui.SameLine(); - FontText(_honorificExists ? check : cross, UiBuilder.IconFont, honorificColor); + FontText(_honorificExists ? check : cross, UiBuilder.IconFont, GetBoolColor(_honorificExists)); ImGui.SameLine(); AttachToolTip($"Honorific is " + (_honorificExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); + ImGui.SameLine(); + ImGui.TextUnformatted("Moodles"); + ImGui.SameLine(); + FontText(_moodlesExists ? check : cross, UiBuilder.IconFont, GetBoolColor(_moodlesExists)); + ImGui.SameLine(); + AttachToolTip($"Moodles is " + (_moodlesExists ? "available and up to date." : "unavailable or not up to date.")); + ImGui.Spacing(); + if (!_penumbraExists || !_glamourerExists) { ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Mare Synchronos."); diff --git a/MareSynchronos/Utils/VariousExtensions.cs b/MareSynchronos/Utils/VariousExtensions.cs index 132de6a..2b937c3 100644 --- a/MareSynchronos/Utils/VariousExtensions.cs +++ b/MareSynchronos/Utils/VariousExtensions.cs @@ -180,6 +180,13 @@ public static class VariousExtensions logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff honorific data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Honorific); charaDataToUpdate[objectKind].Add(PlayerChanges.Honorific); } + + bool moodlesDataDifferent = !string.Equals(oldData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal); + if (moodlesDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.PalettePlusData))) + { + logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff moodles data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Moodles); + charaDataToUpdate[objectKind].Add(PlayerChanges.Moodles); + } } foreach (KeyValuePair> data in charaDataToUpdate.ToList())