diff --git a/MareAPI b/MareAPI index 8a7640c..f4ead04 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 8a7640c1a13bcfc32977a4e0818754f7d080ef4e +Subproject commit f4ead046dd3b5e749dc3757cfd4024f64fbfe3b0 diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index a28d6b7..284f979 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin; using Dalamud.Plugin.Ipc; +using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Action = System.Action; using System.Collections.Concurrent; @@ -57,12 +58,18 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase private readonly FuncSubscriber _penumbraRemoveTemporaryMod; private readonly FuncSubscriber _penumbraResolveModDir; private readonly FuncSubscriber _penumbraResolvePaths; + private readonly ICallGateSubscriber<(uint major, uint minor)> _honorificApiVersion; + private readonly ICallGateSubscriber<(string Title, bool IsPrefix)> _honorificGetLocalCharacterTitle; + private readonly ICallGateSubscriber _honorificClearCharacterTitle; + private readonly ICallGateSubscriber _honorificSetCharacterTitle; + private readonly ICallGateSubscriber _honorificLocalCharacterTitleChanged; private bool _customizePlusAvailable = false; private CancellationTokenSource _disposalCts = new(); private bool _glamourerAvailable = false; private bool _heelsAvailable = false; private bool _inGposeQueueMode = false; private bool _palettePlusAvailable = false; + private bool _honorificAvailable = false; private bool _penumbraAvailable = false; private bool _shownGlamourerUnavailable = false; private bool _shownPenumbraUnavailable = false; @@ -124,6 +131,14 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase _palettePlusPaletteChanged.Subscribe(OnPalettePlusPaletteChange); + _honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion"); + _honorificGetLocalCharacterTitle = pi.GetIpcSubscriber<(string, bool)>("Honorific.GetLocalCharacterTitle"); + _honorificClearCharacterTitle = pi.GetIpcSubscriber("Honorific.ClearCharacterTitle"); + _honorificSetCharacterTitle = pi.GetIpcSubscriber("Honorific.SetCharacterTitle"); + _honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber("Honorific.LocalCharacterTitleChanged"); + + _honorificLocalCharacterTitleChanged.Subscribe(OnHonorificLocalCharacterTitleChanged); + if (Initialized) { Mediator.Publish(new PenumbraInitializedMessage()); @@ -146,6 +161,8 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public bool CheckHeelsApi() => _heelsAvailable; public bool CheckPalettePlusApi() => _palettePlusAvailable; + + public bool CheckHonorificApi() => _honorificAvailable; public bool CheckPenumbraApi() => _penumbraAvailable; @@ -272,6 +289,48 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase } }).ConfigureAwait(false); } + + public string HonorificGetTitle() + { + if (!CheckHonorificApi()) return string.Empty; + (string? title, bool isPrefix) = _honorificGetLocalCharacterTitle.InvokeFunc(); + return $"{(isPrefix ? 1 : 0)}{title}"; + } + + public async Task HonorificSetTitle(IntPtr character, string honorificData) + { + if (!CheckHonorificApi()) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + Logger.LogTrace("Applying Honorific data to {chara}", character.ToString("X")); + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is PlayerCharacter pc) + { + if (string.IsNullOrEmpty(honorificData)) + { + _honorificClearCharacterTitle!.InvokeAction(pc); + } + else + { + _honorificSetCharacterTitle!.InvokeAction(pc, honorificData[1..], honorificData[0] == '1'); + } + } + }).ConfigureAwait(false); + } + + public async Task HonorificClearTitle(nint character) + { + if (!CheckHonorificApi()) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is PlayerCharacter c) + { + Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X")); + _honorificClearCharacterTitle!.InvokeAction(c); + } + }).ConfigureAwait(false); + } public async Task PalettePlusBuildPalette() { @@ -414,6 +473,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase _heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange); _palettePlusPaletteChanged.Unsubscribe(OnPalettePlusPaletteChange); _customizePlusOnScaleUpdate.Unsubscribe(OnCustomizePlusScaleChange); + _honorificLocalCharacterTitleChanged.Unsubscribe(OnHonorificLocalCharacterTitleChanged); } private bool CheckCustomizePlusApiInternal() @@ -475,6 +535,18 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase } } + private bool CheckHonorificApiInternal() + { + try + { + return _honorificApiVersion.InvokeFunc() is { Item1: 1, Item2: >= 0 }; + } + catch + { + return false; + } + } + private bool CheckPenumbraApiInternal() { bool apiAvailable = false; @@ -545,6 +617,11 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase Mediator.Publish(new PalettePlusMessage(character)); } + private void OnHonorificLocalCharacterTitleChanged(string title, bool isPrefix) + { + Mediator.Publish(new HonorificMessage()); + } + private void PenumbraDispose() { _disposalCts.Cancel(); @@ -600,6 +677,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase _heelsAvailable = CheckHeelsApiInternal(); _customizePlusAvailable = CheckCustomizePlusApiInternal(); _palettePlusAvailable = CheckPalettePlusApiInternal(); + _honorificAvailable = CheckHonorificApiInternal(); PenumbraModDirectory = GetPenumbraModDirectoryInternal(); } diff --git a/MareSynchronos/PlayerData/Data/CharacterData.cs b/MareSynchronos/PlayerData/Data/CharacterData.cs index abef2be..bad42fa 100644 --- a/MareSynchronos/PlayerData/Data/CharacterData.cs +++ b/MareSynchronos/PlayerData/Data/CharacterData.cs @@ -16,6 +16,7 @@ public class CharacterData public string ManipulationString { get; set; } = string.Empty; public string PalettePlusPalette { get; set; } = string.Empty; + public string HonorificData { get; set; } = string.Empty; public API.Data.CharacterData ToAPI() { @@ -42,6 +43,7 @@ public class CharacterData HeelsOffset = HeelsOffset, CustomizePlusData = CustomizePlusScale, PalettePlusData = PalettePlusPalette, + HonorificData = HonorificData }; } diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 520dc91..95254aa 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -380,6 +380,8 @@ public class PlayerDataFactory _logger.LogDebug("Customize is now: {data}", previousData.CustomizePlusScale); previousData.PalettePlusPalette = await getPalettePlusData.ConfigureAwait(false); _logger.LogDebug("Palette is now: {data}", previousData.PalettePlusPalette); + previousData.HonorificData = _ipcManager.HonorificGetTitle(); + _logger.LogDebug("Honorific is now: {data}", previousData.HonorificData); st.Stop(); _logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(st.ElapsedTicks).TotalMilliseconds); diff --git a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs index 456ab22..380bfc4 100644 --- a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs +++ b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs @@ -56,7 +56,8 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase Heels = 1, Customize = 2, Palette = 3, - Mods = 4 + Mods = 4, + Honorific = 5, } public IntPtr PlayerCharacter => _charaHandler?.Address ?? IntPtr.Zero; @@ -256,6 +257,10 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase await _ipcManager.HeelsSetOffsetForPlayer(handler.Address, charaData.HeelsOffset).ConfigureAwait(false); break; + case PlayerChanges.Honorific: + await _ipcManager.HonorificSetTitle(handler.Address, charaData.HonorificData).ConfigureAwait(false); + break; + case PlayerChanges.Mods: if (charaData.GlamourerData.TryGetValue(changes.Key, out var glamourerData)) { @@ -363,6 +368,13 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase Logger.LogDebug("Updating {object}/{kind} (Diff palette data) => {change}", this, objectKind, PlayerChanges.Palette); charaDataToUpdate[objectKind].Add(PlayerChanges.Palette); } + + bool honorificDataDifferent = !string.Equals(oldData.HonorificData, newData.HonorificData, StringComparison.Ordinal); + if (honorificDataDifferent || (forced && !string.IsNullOrEmpty(newData.HonorificData))) + { + Logger.LogDebug("Updating {object}/{kind} (Diff honorific data) => {change}", this, objectKind, PlayerChanges.Honorific); + charaDataToUpdate[objectKind].Add(PlayerChanges.Honorific); + } } foreach (KeyValuePair> data in charaDataToUpdate.ToList()) @@ -513,6 +525,12 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase warning.ShownPalettePlusWarning = true; } + if (changes.Contains(PlayerChanges.Honorific) && !warning.ShownHonorificWarning && !_ipcManager.CheckHonorificApi()) + { + missingPluginsForData.Add("Honorific"); + warning.ShownHonorificWarning = true; + } + if (missingPluginsForData.Any()) { Mediator.Publish(new NotificationMessage("Missing plugins for " + PlayerName, @@ -549,6 +567,10 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase CheckForNameAndThrow(tempHandler, name); Logger.LogDebug("[{applicationId}] Restoring Palette+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.PalettePlusRemovePalette(address).ConfigureAwait(false); + CheckForNameAndThrow(tempHandler, name); + Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); + await _ipcManager.HonorificClearTitle(address).ConfigureAwait(false); + } else if (objectKind == ObjectKind.MinionOrMount) { diff --git a/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs b/MareSynchronos/PlayerData/Pairs/OptionalPluginWarning.cs index 7be98ad..182ee19 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 ShownPalettePlusWarning { get; set; } = false; + public bool ShownHonorificWarning { get; set; } = false; } diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index 268d5e3..0580f7c 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -96,6 +96,7 @@ public class Pair ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings, ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings, ShownPalettePlusWarning = _configService.Current.DisableOptionalPluginWarnings, + ShownHonorificWarning = _configService.Current.DisableOptionalPluginWarnings, }; CachedPlayer.ApplyCharacterData(RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, _pluginWarnings, forced); @@ -136,6 +137,7 @@ public class Pair ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings, ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings, ShownPalettePlusWarning = _configService.Current.DisableOptionalPluginWarnings, + ShownHonorificWarning = _configService.Current.DisableOptionalPluginWarnings, }; CachedPlayer.Initialize(name); diff --git a/MareSynchronos/PlayerData/Services/CacheCreationService.cs b/MareSynchronos/PlayerData/Services/CacheCreationService.cs index 3585975..c8552c0 100644 --- a/MareSynchronos/PlayerData/Services/CacheCreationService.cs +++ b/MareSynchronos/PlayerData/Services/CacheCreationService.cs @@ -61,6 +61,11 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase PalettePlusChanged(); } }); + Mediator.Subscribe(this, async (_) => + { + Logger.LogDebug("Received Honorific change, updating player"); + await AddPlayerCacheToCreate().ConfigureAwait(false); + }); Mediator.Subscribe(this, async (msg) => { Logger.LogDebug("Received Penumbra Mod settings change, updating player"); diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 2d993ff..5afe307 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -35,6 +35,7 @@ public record HeelsOffsetMessage : IMessage; public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : IMessage; public record CustomizePlusMessage : IMessage; public record PalettePlusMessage(Character Character) : IMessage; +public record HonorificMessage : IMessage; public record PlayerChangedMessage(API.Data.CharacterData Data) : IMessage; public record CharacterChangedMessage(GameObjectHandler GameObjectHandler) : IMessage; public record TransientResourceChangedMessage(IntPtr Address) : IMessage; diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 861f90a..b826f29 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -307,7 +307,18 @@ public class SettingsUi : WindowMediatorSubscriberBase _lastTab = "Debug"; UiSharedService.FontText("Debug", _uiShared.UidFont); - +#if DEBUG + if (LastCreatedCharacterData != null && ImGui.TreeNode("Last created character data")) + { + + foreach (var l in JsonSerializer.Serialize(LastCreatedCharacterData, new JsonSerializerOptions() { WriteIndented = true }).Split('\n')) + { + ImGui.Text($"{l}"); + } + + ImGui.TreePop(); + } +#endif if (UiSharedService.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard")) { if (LastCreatedCharacterData != null) diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index f2eb24c..d9f34c1 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -70,6 +70,8 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase private bool _palettePlusExists = false; + private bool _honorificExists = false; + private bool _penumbraExists = false; private int _serverSelectionIndex = -1; @@ -103,6 +105,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase _customizePlusExists = _ipcManager.CheckCustomizePlusApi(); _heelsExists = _ipcManager.CheckHeelsApi(); _palettePlusExists = _ipcManager.CheckPalettePlusApi(); + _honorificExists = _ipcManager.CheckHonorificApi(); }); } @@ -601,6 +604,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase var heelsColor = _heelsExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; var customizeColor = _customizePlusExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; var paletteColor = _palettePlusExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + var honorificColor = _honorificExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; ImGui.Text("Penumbra:"); ImGui.SameLine(); ImGui.TextColored(penumbraColor, _penumbraExists ? "Available" : "Unavailable"); @@ -621,6 +625,10 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.Text("Palette+:"); ImGui.SameLine(); ImGui.TextColored(paletteColor, _palettePlusExists ? "Available" : "Unavailable"); + ImGui.SameLine(); + ImGui.Text("Honorific:"); + ImGui.SameLine(); + ImGui.TextColored(honorificColor, _honorificExists ? "Available" : "Unavailable"); if (!_penumbraExists || !_glamourerExists) {