diff --git a/MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs b/MareSynchronos/Interop/Ipc/IpcCallerMoodles.cs new file mode 100644 index 0000000..44b6ce5 --- /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, IDalamudPluginInterface 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(IPlayerCharacter 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 0408420..03a6770 100644 --- a/MareSynchronos/Interop/Ipc/IpcManager.cs +++ b/MareSynchronos/Interop/Ipc/IpcManager.cs @@ -7,7 +7,7 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase { public IpcManager(ILogger logger, MareMediator mediator, IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc, - IpcCallerHonorific honorificIpc, IpcCallerPetNames ipcCallerPetNames) : base(logger, mediator) + IpcCallerHonorific honorificIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerMoodles moodlesIpc) : base(logger, mediator) { CustomizePlus = customizeIpc; Heels = heelsIpc; @@ -15,6 +15,7 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase Penumbra = penumbraIpc; Honorific = honorificIpc; PetNames = ipcCallerPetNames; + Moodles = moodlesIpc; if (Initialized) { @@ -41,6 +42,7 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase public IpcCallerGlamourer Glamourer { get; } public IpcCallerPenumbra Penumbra { get; } public IpcCallerPetNames PetNames { get; } + public IpcCallerMoodles Moodles { get; } private void PeriodicApiStateCheck() { @@ -51,5 +53,6 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase CustomizePlus.CheckAPI(); Honorific.CheckAPI(); PetNames.CheckAPI(); + Moodles.CheckAPI(); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Data/CharacterData.cs b/MareSynchronos/PlayerData/Data/CharacterData.cs index 8d7eeb0..f55bab8 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 @@ -15,6 +13,7 @@ public class CharacterData public string HonorificData { get; set; } = string.Empty; public string ManipulationString { get; set; } = string.Empty; public string PetNamesData { get; set; } = string.Empty; + public string MoodlesData { get; set; } = string.Empty; public API.Data.CharacterData ToAPI() { @@ -44,17 +43,8 @@ public class CharacterData HeelsData = HeelsData, CustomizePlusData = CustomizePlusScale.ToDictionary(d => d.Key, d => d.Value), HonorificData = HonorificData, - PetNamesData = PetNamesData + PetNamesData = PetNamesData, + MoodlesData = 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 8c2f1f8..e1d6358 100644 --- a/MareSynchronos/PlayerData/Data/PlayerChanges.cs +++ b/MareSynchronos/PlayerData/Data/PlayerChanges.cs @@ -9,5 +9,6 @@ public enum PlayerChanges Heels = 5, Honorific = 7, ForcedRedraw = 8, + Moodles = 9, PetNames = 10, } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 62e16f0..6d8b22b 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -218,6 +218,7 @@ public class PlayerDataFactory { previousData.PetNamesData = _ipcManager.PetNames.GetLocalNames(); _logger.LogDebug("Pet Nicknames is now: {petnames}", previousData.PetNamesData); + 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 ab6880c..6b62221 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -404,6 +404,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase await _ipcManager.PetNames.SetPlayerData(handler.Address, charaData.PetNamesData).ConfigureAwait(false); break; + case PlayerChanges.Moodles: + await _ipcManager.Moodles.SetStatusAsync(handler.Address, charaData.MoodlesData).ConfigureAwait(false); + break; + case PlayerChanges.ForcedRedraw: await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); break; @@ -714,6 +718,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase await _ipcManager.Honorific.ClearTitleAsync(address).ConfigureAwait(false); Logger.LogDebug("[{applicationId}] Restoring Pet Nicknames for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.PetNames.ClearPlayerData(address).ConfigureAwait(false); + Logger.LogDebug("[{applicationId}] Restoring Moodles for {alias}/{name}", applicationId, Pair.UserData.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 8d2e5c8..a52467b 100644 --- a/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs +++ b/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs @@ -6,4 +6,5 @@ public record OptionalPluginWarning public bool ShownCustomizePlusWarning { get; set; } = false; public bool ShownHonorificWarning { get; set; } = false; public bool ShowPetNicknamesWarning { 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 6b9b47f..fc05e09 100644 --- a/MareSynchronos/PlayerData/Services/CacheCreationService.cs +++ b/MareSynchronos/PlayerData/Services/CacheCreationService.cs @@ -21,6 +21,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase private Task? _cacheCreationTask; private CancellationTokenSource _honorificCts = new(); private CancellationTokenSource _petNicknamesCts = new(); + private CancellationTokenSource _moodlesCts = new(); private bool _isZoning = false; private readonly Dictionary _glamourerCts = new(); @@ -122,7 +123,17 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase PetNicknamesChanged(); } }); - Mediator.Subscribe(this, (msg) => + 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"); _ = AddPlayerCacheToCreate(); @@ -191,6 +202,20 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase }, 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 9edb8e5..feafe3d 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -114,13 +114,16 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new IpcCallerPetNames(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())); collection.AddSingleton((s) => new NotificationService(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), notificationManager, chatGui, s.GetRequiredService())); + collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new NotesConfigService(pluginInterface.ConfigDirectory.FullName)); diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 548c1be..9add9af 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -42,6 +42,7 @@ public record CustomizePlusMessage(nint? Address) : MessageBase; public record HonorificMessage(string NewHonorificTitle) : MessageBase; public record PetNamesReadyMessage : MessageBase; public record PetNamesMessage(string PetNicknamesData) : 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 15b4822..337f93b 100644 --- a/MareSynchronos/Services/PluginWarningNotificationService.cs +++ b/MareSynchronos/Services/PluginWarningNotificationService.cs @@ -31,7 +31,8 @@ public class PluginWarningNotificationService ShownCustomizePlusWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, ShownHeelsWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, ShownHonorificWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, - ShowPetNicknamesWarning = _mareConfigService.Current.DisableOptionalPluginWarnings + ShowPetNicknamesWarning = _mareConfigService.Current.DisableOptionalPluginWarnings, + ShownMoodlesWarning = _mareConfigService.Current.DisableOptionalPluginWarnings }; } @@ -59,6 +60,12 @@ public class PluginWarningNotificationService warning.ShowPetNicknamesWarning = 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 f96d773..cdde85c 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -110,6 +110,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase _heelsExists = _ipcManager.Heels.APIAvailable; _honorificExists = _ipcManager.Honorific.APIAvailable; _petNamesExists = _ipcManager.PetNames.APIAvailable; + _moodlesExists = _ipcManager.Moodles.APIAvailable; }); UidFont = _pluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => @@ -792,6 +793,13 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase AttachToolTip($"PetNicknames is " + (_petNamesExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); + ImGui.TextUnformatted("Moodles"); + ImGui.SameLine(); + IconText(_moodlesExists ? check : cross, 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 Loporrit."); diff --git a/MareSynchronos/Utils/VariousExtensions.cs b/MareSynchronos/Utils/VariousExtensions.cs index 3aed86a..153698b 100644 --- a/MareSynchronos/Utils/VariousExtensions.cs +++ b/MareSynchronos/Utils/VariousExtensions.cs @@ -195,6 +195,13 @@ public static class VariousExtensions logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff petnames data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.PetNames); charaDataToUpdate[objectKind].Add(PlayerChanges.PetNames); } + + bool moodlesDataDifferent = !string.Equals(oldData.MoodlesData, newData.MoodlesData, StringComparison.Ordinal); + if (moodlesDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.MoodlesData))) + { + 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())