From d218d06230710bb6af38cdd113c5362129ad43dd Mon Sep 17 00:00:00 2001 From: Cara Date: Mon, 30 Jan 2023 19:13:57 +1030 Subject: [PATCH] Add support for Palette+ (#40) --- MareSynchronos/Export/MareCharaFileData.cs | 2 + .../Factories/CharacterDataFactory.cs | 1 + MareSynchronos/Managers/CachedPlayer.cs | 19 +++++ MareSynchronos/Managers/IpcManager.cs | 80 +++++++++++++++++++ MareSynchronos/Managers/PlayerManager.cs | 13 +++ MareSynchronos/Models/CharacterData.cs | 4 + .../Models/OptionalPluginWarning.cs | 1 + MareSynchronos/Models/Pair.cs | 2 + MareSynchronos/UI/UIShared.cs | 6 ++ 9 files changed, 128 insertions(+) diff --git a/MareSynchronos/Export/MareCharaFileData.cs b/MareSynchronos/Export/MareCharaFileData.cs index 5fd45b3..2a3645a 100644 --- a/MareSynchronos/Export/MareCharaFileData.cs +++ b/MareSynchronos/Export/MareCharaFileData.cs @@ -11,6 +11,7 @@ public record MareCharaFileData public string Description { get; set; } = string.Empty; public string GlamourerData { get; set; } = string.Empty; public string CustomizePlusData { get; set; } = string.Empty; + public string PalettePlusData { get; set; } = string.Empty; public string ManipulationData { get; set; } = string.Empty; public List Files { get; set; } = new(); public List FileSwaps { get; set; } = new(); @@ -26,6 +27,7 @@ public record MareCharaFileData } CustomizePlusData = dto.CustomizePlusData; + PalettePlusData = dto.PalettePlusData; ManipulationData = dto.ManipulationData; if (dto.FileReplacements.TryGetValue(ObjectKind.Player, out var fileReplacements)) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index d1f2a21..0080d73 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -326,6 +326,7 @@ public class CharacterDataFactory previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(charaPointer); previousData.HeelsOffset = _ipcManager.GetHeelsOffset(); previousData.CustomizePlusScale = _ipcManager.GetCustomizePlusScale(); + previousData.PalettePlusPalette = _ipcManager.PalettePlusGetPalette(); Logger.Debug("Handling transient update for " + objectKind); ManageSemiTransientData(previousData, objectKind, charaPointer); diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index b72347b..d40a12c 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -129,6 +129,14 @@ public class CachedPlayer : IDisposable charaDataToUpdate.Add(objectKind); continue; } + + bool palettePlusDataDifferent = !string.Equals(_cachedData.PalettePlusData, characterData.PalettePlusData, StringComparison.Ordinal); + if (palettePlusDataDifferent) + { + Logger.Debug("Updating " + objectKind); + charaDataToUpdate.Add(objectKind); + continue; + } } } @@ -149,6 +157,15 @@ public class CachedPlayer : IDisposable } } + if (!string.IsNullOrEmpty(characterData.PalettePlusData)) + { + if (!warning.ShownPalettePlusWarning && !_ipcManager.CheckPalettePlusApi()) + { + _dalamudUtil.PrintWarnChat("Received Palette+ data for player " + PlayerName + ", but Palette+ is not installed. Install Palette+ to experience their character fully."); + warning.ShownPalettePlusWarning = true; + } + } + _cachedData = characterData; DownloadAndApplyCharacter(charaDataToUpdate, updateModdedPaths); @@ -244,6 +261,7 @@ public class CachedPlayer : IDisposable ct.ThrowIfCancellationRequested(); _ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter); _ipcManager.CustomizePlusSetBodyScale(PlayerCharacter, _cachedData.CustomizePlusData); + _ipcManager.PalettePlusSetPalette(PlayerCharacter, _cachedData.PalettePlusData); RequestedPenumbraRedraw = true; Logger.Debug( $"Request Redraw for {PlayerName}"); @@ -449,6 +467,7 @@ public class CachedPlayer : IDisposable _ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter); _ipcManager.HeelsRestoreOffsetForPlayer(PlayerCharacter); _ipcManager.CustomizePlusRevert(PlayerCharacter); + _ipcManager.PalettePlusRemovePalette(PlayerCharacter); } else { diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index b0c1f98..94c0df1 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -50,6 +50,12 @@ public class IpcManager : IDisposable private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter; private readonly ICallGateSubscriber _customizePlusRevert; private readonly ICallGateSubscriber _customizePlusOnScaleUpdate; + + private readonly ICallGateSubscriber _palettePlusApiVersion; + private readonly ICallGateSubscriber _palettePlusGetCharaPalette; + private readonly ICallGateSubscriber _palettePlusSetCharaPalette; + private readonly ICallGateSubscriber _palettePlusRemoveCharaPalette; + private readonly ICallGateSubscriber _palettePlusPaletteChanged; private readonly DalamudUtil _dalamudUtil; private bool _inGposeQueueMode = false; @@ -102,6 +108,14 @@ public class IpcManager : IDisposable _customizePlusOnScaleUpdate = pi.GetIpcSubscriber("CustomizePlus.OnScaleUpdate"); _customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange); + + _palettePlusApiVersion = pi.GetIpcSubscriber("PalettePlus.ApiVersion"); + _palettePlusGetCharaPalette = pi.GetIpcSubscriber("PalettePlus.GetCharaPalette"); + _palettePlusSetCharaPalette = pi.GetIpcSubscriber("PalettePlus.SetCharaPalette"); + _palettePlusRemoveCharaPalette = pi.GetIpcSubscriber("PalettePlus.RemoveCharaPalette"); + _palettePlusPaletteChanged = pi.GetIpcSubscriber("PalettePlus.PaletteChanged"); + + _palettePlusPaletteChanged.Subscribe(OnPalettePlusPaletteChange); if (Initialized) { @@ -168,6 +182,7 @@ public class IpcManager : IDisposable public event FloatDelegate? HeelsOffsetChangeEvent; public event PenumbraFileResourceDelegate? PenumbraResourceLoadEvent; public event StringDelegate? CustomizePlusScaleChange; + public event StringDelegate? PalettePlusPaletteChange; public bool Initialized => CheckPenumbraApi(); public bool CheckGlamourerApi() @@ -218,6 +233,18 @@ public class IpcManager : IDisposable } } + public bool CheckPalettePlusApi() + { + try + { + return string.Equals(_palettePlusApiVersion.InvokeFunc(), "1.0.0", StringComparison.Ordinal); + } + catch + { + return false; + } + } + public void Dispose() { Logger.Verbose("Disposing " + nameof(IpcManager)); @@ -503,6 +530,59 @@ public class IpcManager : IDisposable CustomizePlusScaleChange?.Invoke(scale); } + private void OnPalettePlusPaletteChange(Character character, string palette) + { + if (character.Address == 0 || character.Address != _dalamudUtil.PlayerPointer) return; + if (palette != null) palette = Convert.ToBase64String(Encoding.UTF8.GetBytes(palette)); + PalettePlusPaletteChange?.Invoke(palette); + } + + public void PalettePlusSetPalette(IntPtr character, string palette) + { + if (!CheckPalettePlusApi()) return; + ActionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + string decodedPalette = Encoding.UTF8.GetString(Convert.FromBase64String(palette)); + + if (string.IsNullOrEmpty(decodedPalette)) + { + Logger.Verbose("PalettePlus removing for " + c.Address.ToString("X")); + _palettePlusRemoveCharaPalette!.InvokeAction(c); + } + else + { + Logger.Verbose("PalettePlus applying for " + c.Address.ToString("X")); + _palettePlusSetCharaPalette!.InvokeAction(c, decodedPalette); + } + } + }); + } + + public string PalettePlusGetPalette() + { + if (!CheckPalettePlusApi()) return string.Empty; + var palette = _palettePlusGetCharaPalette.InvokeFunc(_dalamudUtil.PlayerCharacter); + if (string.IsNullOrEmpty(palette)) return string.Empty; + return Convert.ToBase64String(Encoding.UTF8.GetBytes(palette)); + } + + public void PalettePlusRemovePalette(IntPtr character) + { + if (!CheckPalettePlusApi()) return; + ActionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + Logger.Verbose("PalettePlus removing for " + c.Address.ToString("X")); + _palettePlusRemoveCharaPalette!.InvokeAction(c); + } + }); + } + private void PenumbraDispose() { PenumbraDisposed?.Invoke(); diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index fb73330..1cea862 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -51,6 +51,7 @@ public class PlayerManager : IDisposable _dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate; _ipcManager.HeelsOffsetChangeEvent += HeelsOffsetChanged; _ipcManager.CustomizePlusScaleChange += CustomizePlusChanged; + _ipcManager.PalettePlusPaletteChange += PalettePlusChanged; _dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate; @@ -118,6 +119,17 @@ public class PlayerManager : IDisposable } } + private void PalettePlusChanged(string? change) + { + change ??= string.Empty; + var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player); + if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.PalettePlusData, change, StringComparison.Ordinal) && !player.IsProcessing) + { + Logger.Debug("PalettePlus data changed to " + change); + player.HasTransientsUpdate = true; + } + } + public void Dispose() { Logger.Verbose("Disposing " + nameof(PlayerManager)); @@ -134,6 +146,7 @@ public class PlayerManager : IDisposable _playerChangedCts?.Cancel(); _ipcManager.HeelsOffsetChangeEvent -= HeelsOffsetChanged; _ipcManager.CustomizePlusScaleChange -= CustomizePlusChanged; + _ipcManager.PalettePlusPaletteChange -= PalettePlusChanged; } private unsafe void DalamudUtilOnDelayedFrameworkUpdate() diff --git a/MareSynchronos/Models/CharacterData.cs b/MareSynchronos/Models/CharacterData.cs index 2d1b73e..2e765ec 100644 --- a/MareSynchronos/Models/CharacterData.cs +++ b/MareSynchronos/Models/CharacterData.cs @@ -25,6 +25,9 @@ public class CharacterData [JsonProperty] public string CustomizePlusScale { get; set; } = string.Empty; + + [JsonProperty] + public string PalettePlusPalette { get; set; } = string.Empty; public void AddFileReplacement(ObjectKind objectKind, FileReplacement fileReplacement) { @@ -74,6 +77,7 @@ public class CharacterData ManipulationData = ManipulationString, HeelsOffset = HeelsOffset, CustomizePlusData = CustomizePlusScale, + PalettePlusData = PalettePlusPalette, }; } diff --git a/MareSynchronos/Models/OptionalPluginWarning.cs b/MareSynchronos/Models/OptionalPluginWarning.cs index b19920c..a8813a8 100644 --- a/MareSynchronos/Models/OptionalPluginWarning.cs +++ b/MareSynchronos/Models/OptionalPluginWarning.cs @@ -4,4 +4,5 @@ public record OptionalPluginWarning { public bool ShownHeelsWarning { get; set; } = false; public bool ShownCustomizePlusWarning { get; set; } = false; + public bool ShownPalettePlusWarning { get; set; } = false; } diff --git a/MareSynchronos/Models/Pair.cs b/MareSynchronos/Models/Pair.cs index 3f90805..65c7bc8 100644 --- a/MareSynchronos/Models/Pair.cs +++ b/MareSynchronos/Models/Pair.cs @@ -64,6 +64,7 @@ public class Pair { ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings, ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings, + ShownPalettePlusWarning = _configService.Current.DisableOptionalPluginWarnings, }; CachedPlayer.Initialize(address, name); @@ -91,6 +92,7 @@ public class Pair { ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings, ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings, + ShownPalettePlusWarning = _configService.Current.DisableOptionalPluginWarnings, }; CachedPlayer.ApplyCharacterData(RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, _pluginWarnings); diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index 350e872..74f5f8d 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -181,11 +181,13 @@ public partial class UiShared : IDisposable var glamourerExists = _ipcManager.CheckGlamourerApi(); var heelsExists = _ipcManager.CheckHeelsApi(); var customizeExists = _ipcManager.CheckCustomizePlusApi(); + var paletteExists = _ipcManager.CheckPalettePlusApi(); var penumbraColor = penumbraExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; var glamourerColor = glamourerExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; var heelsColor = heelsExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; var customizeColor = customizeExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + var paletteColor = paletteExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; ImGui.Text("Penumbra:"); ImGui.SameLine(); ImGui.TextColored(penumbraColor, penumbraExists ? "Available" : "Unavailable"); @@ -202,6 +204,10 @@ public partial class UiShared : IDisposable ImGui.Text("Customize+:"); ImGui.SameLine(); ImGui.TextColored(customizeColor, customizeExists ? "Available" : "Unavailable"); + ImGui.SameLine(); + ImGui.Text("PalettePlus+:"); + ImGui.SameLine(); + ImGui.TextColored(paletteColor, paletteExists ? "Available" : "Unavailable"); if (!penumbraExists || !glamourerExists) {