From 833698c75a2f734e3617bcdae97366b4b289de6a Mon Sep 17 00:00:00 2001 From: Abelfreyja <96177659+Abelfreyja@users.noreply.github.com> Date: Sun, 3 May 2026 05:09:21 +0900 Subject: [PATCH 01/11] API15 + Luna (#103) --- .gitmodules | 10 +- .../CustomizePlus.GameData.csproj | 3 +- .../Data/ReverseNameDicts.cs | 4 +- .../Extensions/ActorIdentifierExtensions.cs | 4 +- CustomizePlus.GameData/GlobalUsings.cs | 1 + .../Hooks/Objects/CharacterDestructor.cs | 18 +- .../Hooks/Objects/CopyCharacter.cs | 30 +- .../Bases/ReverseNameDictionary.cs | 1 - .../ReverseSearchDictBNpc.cs | 1 - .../ReverseSearchDictCompanion.cs | 1 - .../ReverseSearchDictENpc.cs | 1 - .../ReverseSearchDictMount.cs | 1 - .../ReverseSearchDictOrnament.cs | 1 - .../Services/CutsceneService.cs | 11 +- .../Services/GameEventManager.cs | 32 +- CustomizePlus.GameData/packages.lock.json | 64 +- CustomizePlus.sln | 132 ++- CustomizePlus/Api/CustomizePlusIpc.Profile.cs | 15 +- CustomizePlus/Api/CustomizePlusIpc.cs | 5 - .../Armatures/Events/ArmatureChanged.cs | 8 +- .../Armatures/Services/ArmatureManager.cs | 22 +- .../Configuration/Data/PluginConfiguration.cs | 70 +- .../Services/ConfigurationMigrator.cs | 25 +- .../Services/ConfigurationService.cs | 92 +++ .../Services/PluginConfigurationChange.cs | 17 + CustomizePlus/Core/Data/BoneData.cs | 5 +- CustomizePlus/Core/Data/BoneTransform.cs | 6 +- CustomizePlus/Core/Events/ReloadEvent.cs | 6 +- CustomizePlus/Core/Helpers/CtrlHelper.cs | 68 +- .../Helpers/DoubleModifierJsonConverter.cs | 60 ++ CustomizePlus/Core/LowerString.cs | 86 ++ CustomizePlus/Core/ServiceManagerBuilder.cs | 21 +- CustomizePlus/Core/Services/BackupService.cs | 20 +- CustomizePlus/Core/Services/CommandService.cs | 21 +- .../Services/Dalamud/DalamudConfigService.cs | 3 - .../Core/Services/Dalamud/DalamudServices.cs | 32 - .../Core/Services/FileSystemSaveService.cs | 116 +++ .../Core/Services/FilenameService.cs | 42 +- CustomizePlus/Core/Services/HookingService.cs | 12 +- CustomizePlus/Core/Services/PcpService.cs | 3 - CustomizePlus/Core/Services/SaveService.cs | 24 +- CustomizePlus/CustomizePlus.csproj | 11 +- CustomizePlus/CustomizePlus.json | 4 +- .../Game/Events/GPoseStateChanged.cs | 7 +- CustomizePlus/Game/Services/EmoteService.cs | 4 +- .../Game/Services/GPose/GPoseService.cs | 12 +- CustomizePlus/GlobalUsings.cs | 9 + CustomizePlus/Interop/Ipc/IPCPenumbra.cs | 2 - CustomizePlus/Plugin.cs | 4 - CustomizePlus/Profiles/Data/Profile.cs | 43 +- .../Profiles/Events/ProfileChanged.cs | 8 +- CustomizePlus/Profiles/ProfileFileSystem.cs | 110 +-- .../Profiles/ProfileManager.ProfileLoading.cs | 15 +- CustomizePlus/Profiles/ProfileManager.cs | 89 +-- CustomizePlus/Templates/Data/Template.cs | 46 +- .../Templates/Events/TemplateChanged.cs | 7 +- .../Templates/Events/TemplateEditorEvent.cs | 8 +- .../Templates/TemplateEditorManager.cs | 17 +- CustomizePlus/Templates/TemplateFileSystem.cs | 114 +-- CustomizePlus/Templates/TemplateManager.cs | 26 +- CustomizePlus/UI/CPlusWindowSystem.cs | 27 +- CustomizePlus/UI/UiHelpers.cs | 52 +- CustomizePlus/UI/Windows/CPlusChangeLog.cs | 392 ++++----- .../UI/Windows/Controls/ActorAssignmentUi.cs | 36 +- .../Controls/CPlusFileSystemSelector.cs | 367 +++++++++ .../UI/Windows/Controls/IndividualHelpers.cs | 30 + .../UI/Windows/Controls/PluginStateBlock.cs | 87 +- .../UI/Windows/Controls/TemplateCombo.cs | 165 ++-- .../UI/Windows/MainWindow/MainWindow.cs | 70 +- .../MainWindow/Tabs/Debug/IPCTestTab.cs | 120 ++- .../Tabs/Debug/StateMonitoringTab.cs | 115 ++- .../Windows/MainWindow/Tabs/HeaderDrawer.cs | 41 +- .../UI/Windows/MainWindow/Tabs/MessagesTab.cs | 4 +- .../Profiles/ProfileFileSystemSelector.cs | 140 ++-- .../MainWindow/Tabs/Profiles/ProfilePanel.cs | 453 +++++------ .../MainWindow/Tabs/Profiles/ProfilesTab.cs | 45 +- .../UI/Windows/MainWindow/Tabs/SettingsTab.cs | 114 ++- .../Tabs/Templates/BoneEditorPanel.cs | 749 ++++++++++-------- .../Templates/TemplateFileSystemSelector.cs | 170 ++-- .../Tabs/Templates/TemplatePanel.cs | 161 ++-- .../MainWindow/Tabs/Templates/TemplatesTab.cs | 45 +- .../UI/Windows/PopupSystem.Messages.cs | 28 +- CustomizePlus/UI/Windows/PopupSystem.cs | 114 ++- CustomizePlus/packages.lock.json | 83 +- repo.json | 4 +- submodules/ECommonsLite | 2 +- submodules/Luna | 1 + submodules/OtterGui | 1 - submodules/Penumbra.Api | 2 +- submodules/Penumbra.GameData | 2 +- submodules/Penumbra.String | 2 +- 91 files changed, 3117 insertions(+), 2135 deletions(-) create mode 100644 CustomizePlus.GameData/GlobalUsings.cs create mode 100644 CustomizePlus/Configuration/Services/ConfigurationService.cs create mode 100644 CustomizePlus/Configuration/Services/PluginConfigurationChange.cs create mode 100644 CustomizePlus/Core/Helpers/DoubleModifierJsonConverter.cs create mode 100644 CustomizePlus/Core/LowerString.cs delete mode 100644 CustomizePlus/Core/Services/Dalamud/DalamudServices.cs create mode 100644 CustomizePlus/Core/Services/FileSystemSaveService.cs create mode 100644 CustomizePlus/GlobalUsings.cs create mode 100644 CustomizePlus/UI/Windows/Controls/CPlusFileSystemSelector.cs create mode 100644 CustomizePlus/UI/Windows/Controls/IndividualHelpers.cs create mode 160000 submodules/Luna delete mode 160000 submodules/OtterGui diff --git a/.gitmodules b/.gitmodules index 7ac2102b..38568eb3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "submodules/OtterGui"] - path = submodules/OtterGui - url = https://github.com/Ottermandias/OtterGui.git - branch = main [submodule "submodules/Penumbra.GameData"] path = submodules/Penumbra.GameData url = https://github.com/Ottermandias/Penumbra.GameData @@ -16,5 +12,9 @@ branch = main [submodule "submodules/ECommonsLite"] path = submodules/ECommonsLite - url = https://github.com/Aether-Tools/ECommonsLite.git + url = https://github.com/Abelfreyja/ECommonsLite.git + branch = api15 +[submodule "submodules/Luna"] + path = submodules/Luna + url = https://github.com/Ottermandias/Luna.git branch = main diff --git a/CustomizePlus.GameData/CustomizePlus.GameData.csproj b/CustomizePlus.GameData/CustomizePlus.GameData.csproj index 32629102..55ef5ce1 100644 --- a/CustomizePlus.GameData/CustomizePlus.GameData.csproj +++ b/CustomizePlus.GameData/CustomizePlus.GameData.csproj @@ -1,10 +1,11 @@ - + enable + diff --git a/CustomizePlus.GameData/Data/ReverseNameDicts.cs b/CustomizePlus.GameData/Data/ReverseNameDicts.cs index 22d0fb15..dfd8cccc 100644 --- a/CustomizePlus.GameData/Data/ReverseNameDicts.cs +++ b/CustomizePlus.GameData/Data/ReverseNameDicts.cs @@ -1,6 +1,6 @@ using CustomizePlus.GameData.ReverseSearchDictionaries; using Dalamud.Game.ClientState.Objects.Enums; -using OtterGui.Services; +using Luna; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -52,7 +52,7 @@ public bool TryGetID(ObjectKind kind, string name, [NotNullWhen(true)] out uint npcId = default; return kind switch { - ObjectKind.MountType => Mounts.TryGetValue(name, out npcId), + ObjectKind.Mount => Mounts.TryGetValue(name, out npcId), ObjectKind.Companion => Companions.TryGetValue(name, out npcId), ObjectKind.BattleNpc => BNpcs.TryGetValue(name, out npcId), ObjectKind.EventNpc => ENpcs.TryGetValue(name, out npcId), diff --git a/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs b/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs index f92cdf1b..ebadfd78 100644 --- a/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs +++ b/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs @@ -101,7 +101,7 @@ public static string TypeToString(this ActorIdentifier identifier) _ => " (Retainer)", }}", IdentifierType.Owned => $" ({identifier.Kind switch { - ObjectKind.MountType => "Mount", + ObjectKind.Mount => "Mount", ObjectKind.Companion => "Companion", ObjectKind.Ornament => "Accessory", _ => $"Owned {identifier.Kind}", @@ -130,7 +130,7 @@ public static bool IsAllowedForProfiles(this ActorIdentifier identifier) case IdentifierType.Owned: return identifier.Kind == ObjectKind.BattleNpc || - //identifier.Kind == ObjectKind.MountType || + //identifier.Kind == ObjectKind.Mount || identifier.Kind == ObjectKind.Companion || identifier.Kind == ObjectKind.Ornament; default: diff --git a/CustomizePlus.GameData/GlobalUsings.cs b/CustomizePlus.GameData/GlobalUsings.cs new file mode 100644 index 00000000..6181a117 --- /dev/null +++ b/CustomizePlus.GameData/GlobalUsings.cs @@ -0,0 +1 @@ +global using Logger = Luna.MainLogger; diff --git a/CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs b/CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs index 0d2b0e70..564c1f8f 100644 --- a/CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs +++ b/CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs @@ -1,12 +1,16 @@ -using Dalamud.Hooking; +using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using OtterGui.Classes; -using OtterGui.Services; +using Luna; using Penumbra.GameData; namespace CustomizePlus.GameData.Hooks.Objects; -public sealed unsafe class CharacterDestructor : EventWrapperPtr, IHookService +public sealed unsafe class CharacterDestructor : EventBase, IHookService { + public readonly struct Arguments(Character* character) + { + public readonly Character* Character = character; + } + public enum Priority { /// @@ -16,8 +20,8 @@ public enum Priority IdentifiedCollectionCache = 0, } - public CharacterDestructor(HookManager hooks) - : base("Character Destructor") + public CharacterDestructor(HookManager hooks, LunaLogger log) + : base("Character Destructor", log) => _task = hooks.CreateHook(Name, Sigs.CharacterDestructor, Detour, true); private readonly Task> _task; @@ -42,7 +46,7 @@ public bool Finished private void Detour(Character* character) { //Penumbra.Log.Verbose($"[{Name}] Triggered with 0x{(nint)character:X}."); - Invoke(character); + Invoke(new Arguments(character)); _task.Result.Original(character); } } \ No newline at end of file diff --git a/CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs b/CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs index 6b11f925..5d24f11f 100644 --- a/CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs +++ b/CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs @@ -1,19 +1,24 @@ -using Dalamud.Hooking; +using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using OtterGui.Classes; -using OtterGui.Services; +using Luna; namespace CustomizePlus.GameData.Hooks.Objects; -public sealed unsafe class CopyCharacter : EventWrapperPtr, IHookService +public sealed unsafe class CopyCharacter : EventBase, IHookService { + public readonly struct Arguments(Character* target, Character* source) + { + public readonly Character* Target = target; + public readonly Character* Source = source; + } + public enum Priority { /// CutsceneService = 0, } - public CopyCharacter(HookManager hooks) - : base("Copy Character") + public CopyCharacter(HookManager hooks, LunaLogger log) + : base("Copy Character", log) => _task = hooks.CreateHook(Name, Address, Detour, true); private readonly Task> _task; @@ -33,14 +38,13 @@ public Task Awaiter public bool Finished => _task.IsCompletedSuccessfully; - private delegate ulong Delegate(CharacterSetupContainer* target, Character* source, uint unk); + private delegate ulong Delegate(CharacterSetupContainer* target, Character* source, CharacterSetupContainer.CopyFlags flags); - private ulong Detour(CharacterSetupContainer* target, Character* source, uint unk) + private ulong Detour(CharacterSetupContainer* target, Character* source, CharacterSetupContainer.CopyFlags flags) { - // TODO: update when CS updated. - var character = ((Character**)target)[1]; - //Penumbra.Log.Verbose($"[{Name}] Triggered with target: 0x{(nint)target:X}, source : 0x{(nint)source:X} unk: {unk}."); - Invoke(character, source); - return _task.Result.Original(target, source, unk); + var character = target->OwnerObject; + //Penumbra.Log.Verbose($"[{Name}] Triggered with target: 0x{(nint)target:X}, source : 0x{(nint)source:X} flags: {flags}."); + Invoke(new Arguments(character, source)); + return _task.Result.Original(target, source, flags); } } \ No newline at end of file diff --git a/CustomizePlus.GameData/ReverseSearchDictionaries/Bases/ReverseNameDictionary.cs b/CustomizePlus.GameData/ReverseSearchDictionaries/Bases/ReverseNameDictionary.cs index 96fe1149..5513348f 100644 --- a/CustomizePlus.GameData/ReverseSearchDictionaries/Bases/ReverseNameDictionary.cs +++ b/CustomizePlus.GameData/ReverseSearchDictionaries/Bases/ReverseNameDictionary.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Plugin; -using OtterGui.Log; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers.Bases; using Penumbra.GameData.Structs; diff --git a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictBNpc.cs b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictBNpc.cs index 593cf475..74eb8820 100644 --- a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictBNpc.cs +++ b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictBNpc.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Plugin; -using OtterGui.Log; using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; using CustomizePlus.GameData.ReverseSearchDictionaries.Bases; diff --git a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictCompanion.cs b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictCompanion.cs index 53b5ea41..a65326b8 100644 --- a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictCompanion.cs +++ b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictCompanion.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Plugin; -using OtterGui.Log; using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; using CustomizePlus.GameData.ReverseSearchDictionaries.Bases; diff --git a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictENpc.cs b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictENpc.cs index 65c89ce0..72fd2781 100644 --- a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictENpc.cs +++ b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictENpc.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Plugin; -using OtterGui.Log; using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; using CustomizePlus.GameData.ReverseSearchDictionaries.Bases; diff --git a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictMount.cs b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictMount.cs index 637e1851..7d1510bd 100644 --- a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictMount.cs +++ b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictMount.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Plugin; -using OtterGui.Log; using Penumbra.GameData.Data; using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; diff --git a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictOrnament.cs b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictOrnament.cs index c227e39c..e71a0123 100644 --- a/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictOrnament.cs +++ b/CustomizePlus.GameData/ReverseSearchDictionaries/ReverseSearchDictOrnament.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Plugin; -using OtterGui.Log; using Penumbra.GameData.Data; using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; diff --git a/CustomizePlus.GameData/Services/CutsceneService.cs b/CustomizePlus.GameData/Services/CutsceneService.cs index 3230a180..c680496d 100644 --- a/CustomizePlus.GameData/Services/CutsceneService.cs +++ b/CustomizePlus.GameData/Services/CutsceneService.cs @@ -2,7 +2,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using OtterGui.Services; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.String; @@ -90,8 +90,10 @@ public unsafe void Dispose() _characterDestructor.Unsubscribe(OnCharacterDestructor); } - private unsafe void OnCharacterDestructor(Character* character) + private unsafe void OnCharacterDestructor(in CharacterDestructor.Arguments args) { + var character = args.Character; + if (character->GameObject.ObjectIndex < CutsceneStartIdx) { // Remove all associations for now non-existing actor. @@ -116,8 +118,11 @@ private unsafe void OnCharacterDestructor(Character* character) } } - private unsafe void OnCharacterCopy(Character* target, Character* source) + private unsafe void OnCharacterCopy(in CopyCharacter.Arguments args) { + var target = args.Target; + var source = args.Source; + if (target == null || target->GameObject.ObjectIndex is < CutsceneStartIdx or >= CutsceneEndIdx) return; diff --git a/CustomizePlus.GameData/Services/GameEventManager.cs b/CustomizePlus.GameData/Services/GameEventManager.cs index 898facf8..eaa4f265 100644 --- a/CustomizePlus.GameData/Services/GameEventManager.cs +++ b/CustomizePlus.GameData/Services/GameEventManager.cs @@ -2,7 +2,6 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Penumbra.GameData; @@ -77,20 +76,19 @@ private void CharacterDestructorDetour(Character* character) #region Copy Character - private delegate ulong CopyCharacterDelegate(CharacterSetupContainer* target, GameObject* source, uint unk); + private delegate ulong CopyCharacterDelegate(CharacterSetupContainer* target, Character* source, CharacterSetupContainer.CopyFlags flags); private readonly Hook _copyCharacterHook; - private ulong CopyCharacterDetour(CharacterSetupContainer* target, GameObject* source, uint unk) + private ulong CopyCharacterDetour(CharacterSetupContainer* target, Character* source, CharacterSetupContainer.CopyFlags flags) { - // TODO: update when CS updated. - var character = ((Character**)target)[1]; + var character = target->OwnerObject; if (CopyCharacter != null) foreach (var subscriber in CopyCharacter.GetInvocationList()) { try { - ((CopyCharacterEvent)subscriber).Invoke(character, (Character*)source); + ((CopyCharacterEvent)subscriber).Invoke(character, source); } catch (Exception ex) { @@ -101,9 +99,9 @@ private ulong CopyCharacterDetour(CharacterSetupContainer* target, GameObject* s } /*Penumbra.Log.Verbose( - $"{Prefix} {nameof(CopyCharacter)} triggered with target 0x{(nint)target:X} and source 0x{(nint)source:X}.");*/ + $"{Prefix} {nameof(CopyCharacter)} triggered with target 0x{(nint)target:X}, source 0x{(nint)source:X} and flags {flags}.");*/ //todo: log - return _copyCharacterHook.Original(target, source, unk); + return _copyCharacterHook.Original(target, source, flags); } public delegate void CopyCharacterEvent(Character* target, Character* source); @@ -112,18 +110,18 @@ private ulong CopyCharacterDetour(CharacterSetupContainer* target, GameObject* s #region CharacterBaseCreate - private delegate nint CharacterBaseCreateDelegate(uint a, nint b, nint c, byte d); + private delegate CharacterBase* CharacterBaseCreateDelegate(uint modelId, CustomizeData* customize, EquipmentModelId* equipment, byte unk); private readonly Hook _characterBaseCreateHook; - private nint CharacterBaseCreateDetour(uint a, nint b, nint c, byte d) + private CharacterBase* CharacterBaseCreateDetour(uint modelId, CustomizeData* customize, EquipmentModelId* equipment, byte unk) { if (CreatingCharacterBase != null) foreach (var subscriber in CreatingCharacterBase.GetInvocationList()) { try { - ((CreatingCharacterBaseEvent)subscriber).Invoke((nint)(&a), b, c); + ((CreatingCharacterBaseEvent)subscriber).Invoke(&modelId, customize, equipment); } catch (Exception ex) { @@ -133,13 +131,13 @@ private nint CharacterBaseCreateDetour(uint a, nint b, nint c, byte d) } } - var ret = _characterBaseCreateHook.Original(a, b, c, d); + var ret = _characterBaseCreateHook.Original(modelId, customize, equipment, unk); if (CharacterBaseCreated != null) foreach (var subscriber in CharacterBaseCreated.GetInvocationList()) { try { - ((CharacterBaseCreatedEvent)subscriber).Invoke(a, b, c, ret); + ((CharacterBaseCreatedEvent)subscriber).Invoke(modelId, customize, equipment, ret); } catch (Exception ex) { @@ -152,18 +150,18 @@ private nint CharacterBaseCreateDetour(uint a, nint b, nint c, byte d) return ret; } - public delegate void CreatingCharacterBaseEvent(nint modelCharaId, nint customize, nint equipment); - public delegate void CharacterBaseCreatedEvent(uint modelCharaId, nint customize, nint equipment, nint drawObject); + public delegate void CreatingCharacterBaseEvent(uint* modelCharaId, CustomizeData* customize, EquipmentModelId* equipment); + public delegate void CharacterBaseCreatedEvent(uint modelCharaId, CustomizeData* customize, EquipmentModelId* equipment, CharacterBase* drawObject); #endregion #region CharacterBase Destructor - public delegate void CharacterBaseDestructorEvent(nint drawBase); + public delegate void CharacterBaseDestructorEvent(CharacterBase* drawBase); private readonly Hook _characterBaseDestructorHook; - private void CharacterBaseDestructorDetour(nint drawBase) + private void CharacterBaseDestructorDetour(CharacterBase* drawBase) { if (CharacterBaseDestructor != null) foreach (var subscriber in CharacterBaseDestructor.GetInvocationList()) diff --git a/CustomizePlus.GameData/packages.lock.json b/CustomizePlus.GameData/packages.lock.json index 255b2bab..2ee76d3a 100644 --- a/CustomizePlus.GameData/packages.lock.json +++ b/CustomizePlus.GameData/packages.lock.json @@ -20,27 +20,65 @@ }, "JetBrains.Annotations": { "type": "Transitive", - "resolved": "2024.3.0", - "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + "resolved": "2024.2.0", + "contentHash": "GNnqCFW/163p1fOehKx0CnAqjmpPrUSqrgfHM6qca+P+RN39C9rhlfZHQpJhxmQG/dkOYe/b3Z0P8b6Kv5m1qw==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "ZffbJrskOZ40JTzcTyKwFHS5eACSWp2bUQBBApIgGV+es8RaTD4OxUG7XxFr3RIPLXtYQ1jQzF2DjKB5fZn7Qg==", + "resolved": "9.0.0", + "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "MNe7GSTBf3jQx5vYrXF0NZvn6l7hUKF6J54ENfAgCO8y6xjN1XUmKKWG464LP2ye6QqDiA1dkaWEZBYnhoZzjg==" + "resolved": "9.0.0", + "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" }, - "ottergui": { + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Microsoft.Extensions.Options": "9.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Primitives": "9.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "9.0.0", + "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "9.0.8", + "contentHash": "5TJUS9EIYrp0VEcm06EPYxXmLmsVUakewFnM/CAxQfvlasI9fGkTKM9afSf2dodZcMCzFna/o7Fn+gYRt3uTiA==" + }, + "luna": { "type": "Project", "dependencies": { - "JetBrains.Annotations": "[2024.3.0, )", - "Microsoft.Extensions.DependencyInjection": "[9.0.2, )" + "JetBrains.Annotations": "[2024.2.0, )", + "Microsoft.Extensions.Logging": "[9.0.0, )", + "System.IO.Hashing": "[9.0.8, )" } }, "penumbra.api": { @@ -51,9 +89,9 @@ "dependencies": { "FlatSharp.Compiler": "[7.9.0, )", "FlatSharp.Runtime": "[7.9.0, )", - "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.12.0, )", - "Penumbra.String": "[1.0.6, )" + "Luna": "[1.0.0, )", + "Penumbra.Api": "[5.15.1, )", + "Penumbra.String": "[1.0.8, )" } }, "penumbra.string": { diff --git a/CustomizePlus.sln b/CustomizePlus.sln index 73c69250..f5f26861 100644 --- a/CustomizePlus.sln +++ b/CustomizePlus.sln @@ -12,8 +12,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomizePlus", "CustomizeP EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "submodules", "submodules", "{121C2200-A844-44FD-85C4-22D6C7E35553}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "submodules\OtterGui\OtterGui.csproj", "{0D465539-6133-4088-B4BB-F260FA2A1557}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomizePlus.GameData", "CustomizePlus.GameData\CustomizePlus.GameData.csproj", "{CDB26C94-1200-45AA-AF96-D4526DC76AD5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "submodules\Penumbra.GameData\Penumbra.GameData.csproj", "{D79C8833-D241-4867-BF6F-8097E0ED8067}" @@ -24,65 +22,181 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "submodul EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ECommonsLite", "submodules\ECommonsLite\ECommonsLite\ECommonsLite.csproj", "{41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Luna", "Luna", "{1DCA913C-5266-F6A9-9DF7-A9D612F05C20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luna", "submodules\Luna\Luna\Luna.csproj", "{DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Luna.Generators", "submodules\Luna\Luna.Generators\Luna.Generators.csproj", "{5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 Release|x64 = Release|x64 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 ReleaseValidate|x64 = ReleaseValidate|x64 + ReleaseValidate|Any CPU = ReleaseValidate|Any CPU + ReleaseValidate|x86 = ReleaseValidate|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Debug|x64.ActiveCfg = Debug|x64 {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Debug|x64.Build.0 = Debug|x64 + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Debug|x86.ActiveCfg = Debug|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Debug|x86.Build.0 = Debug|Any CPU {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Release|x64.ActiveCfg = Release|x64 {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Release|x64.Build.0 = Release|x64 + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Release|Any CPU.Build.0 = Release|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Release|x86.ActiveCfg = Release|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.Release|x86.Build.0 = Release|Any CPU {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.ReleaseValidate|x64.ActiveCfg = Release|x64 {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.ReleaseValidate|x64.Build.0 = Release|x64 - {0D465539-6133-4088-B4BB-F260FA2A1557}.Debug|x64.ActiveCfg = Debug|x64 - {0D465539-6133-4088-B4BB-F260FA2A1557}.Debug|x64.Build.0 = Debug|x64 - {0D465539-6133-4088-B4BB-F260FA2A1557}.Release|x64.ActiveCfg = Release|x64 - {0D465539-6133-4088-B4BB-F260FA2A1557}.Release|x64.Build.0 = Release|x64 - {0D465539-6133-4088-B4BB-F260FA2A1557}.ReleaseValidate|x64.ActiveCfg = Release|x64 - {0D465539-6133-4088-B4BB-F260FA2A1557}.ReleaseValidate|x64.Build.0 = Release|x64 + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.ReleaseValidate|Any CPU.ActiveCfg = ReleaseValidate|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.ReleaseValidate|Any CPU.Build.0 = ReleaseValidate|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.ReleaseValidate|x86.ActiveCfg = ReleaseValidate|Any CPU + {5BA385F5-C17E-4CE4-828A-24F7F19C434B}.ReleaseValidate|x86.Build.0 = ReleaseValidate|Any CPU {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Debug|x64.ActiveCfg = Debug|x64 {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Debug|x64.Build.0 = Debug|x64 + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Debug|x86.ActiveCfg = Debug|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Debug|x86.Build.0 = Debug|Any CPU {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Release|x64.ActiveCfg = Release|x64 {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Release|x64.Build.0 = Release|x64 + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Release|Any CPU.Build.0 = Release|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Release|x86.ActiveCfg = Release|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.Release|x86.Build.0 = Release|Any CPU {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.ReleaseValidate|x64.ActiveCfg = Release|x64 {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.ReleaseValidate|x64.Build.0 = Release|x64 + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.ReleaseValidate|Any CPU.ActiveCfg = ReleaseValidate|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.ReleaseValidate|Any CPU.Build.0 = ReleaseValidate|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.ReleaseValidate|x86.ActiveCfg = ReleaseValidate|Any CPU + {CDB26C94-1200-45AA-AF96-D4526DC76AD5}.ReleaseValidate|x86.Build.0 = ReleaseValidate|Any CPU {D79C8833-D241-4867-BF6F-8097E0ED8067}.Debug|x64.ActiveCfg = Debug|x64 {D79C8833-D241-4867-BF6F-8097E0ED8067}.Debug|x64.Build.0 = Debug|x64 + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Debug|x86.ActiveCfg = Debug|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Debug|x86.Build.0 = Debug|Any CPU {D79C8833-D241-4867-BF6F-8097E0ED8067}.Release|x64.ActiveCfg = Release|x64 {D79C8833-D241-4867-BF6F-8097E0ED8067}.Release|x64.Build.0 = Release|x64 + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Release|Any CPU.Build.0 = Release|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Release|x86.ActiveCfg = Release|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.Release|x86.Build.0 = Release|Any CPU {D79C8833-D241-4867-BF6F-8097E0ED8067}.ReleaseValidate|x64.ActiveCfg = Release|x64 {D79C8833-D241-4867-BF6F-8097E0ED8067}.ReleaseValidate|x64.Build.0 = Release|x64 + {D79C8833-D241-4867-BF6F-8097E0ED8067}.ReleaseValidate|Any CPU.ActiveCfg = ReleaseValidate|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.ReleaseValidate|Any CPU.Build.0 = ReleaseValidate|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.ReleaseValidate|x86.ActiveCfg = ReleaseValidate|Any CPU + {D79C8833-D241-4867-BF6F-8097E0ED8067}.ReleaseValidate|x86.Build.0 = ReleaseValidate|Any CPU {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Debug|x64.ActiveCfg = Debug|x64 {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Debug|x64.Build.0 = Debug|x64 + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Debug|x86.Build.0 = Debug|Any CPU {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Release|x64.ActiveCfg = Release|x64 {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Release|x64.Build.0 = Release|x64 + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Release|Any CPU.Build.0 = Release|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Release|x86.ActiveCfg = Release|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.Release|x86.Build.0 = Release|Any CPU {CC460943-1E07-4FA0-8B8C-67F0EF385290}.ReleaseValidate|x64.ActiveCfg = Release|x64 {CC460943-1E07-4FA0-8B8C-67F0EF385290}.ReleaseValidate|x64.Build.0 = Release|x64 + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.ReleaseValidate|Any CPU.ActiveCfg = ReleaseValidate|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.ReleaseValidate|Any CPU.Build.0 = ReleaseValidate|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.ReleaseValidate|x86.ActiveCfg = ReleaseValidate|Any CPU + {CC460943-1E07-4FA0-8B8C-67F0EF385290}.ReleaseValidate|x86.Build.0 = ReleaseValidate|Any CPU {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Debug|x64.ActiveCfg = Debug|x64 {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Debug|x64.Build.0 = Debug|x64 + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Debug|x86.ActiveCfg = Debug|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Debug|x86.Build.0 = Debug|Any CPU {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Release|x64.ActiveCfg = Release|x64 {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Release|x64.Build.0 = Release|x64 + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Release|Any CPU.Build.0 = Release|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Release|x86.ActiveCfg = Release|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.Release|x86.Build.0 = Release|Any CPU {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.ReleaseValidate|x64.ActiveCfg = Release|x64 {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.ReleaseValidate|x64.Build.0 = Release|x64 + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.ReleaseValidate|Any CPU.ActiveCfg = ReleaseValidate|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.ReleaseValidate|Any CPU.Build.0 = ReleaseValidate|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.ReleaseValidate|x86.ActiveCfg = ReleaseValidate|Any CPU + {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823}.ReleaseValidate|x86.Build.0 = ReleaseValidate|Any CPU {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Debug|x64.ActiveCfg = Debug|x64 {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Debug|x64.Build.0 = Debug|x64 + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Debug|x86.ActiveCfg = Debug|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Debug|x86.Build.0 = Debug|Any CPU {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Release|x64.ActiveCfg = Release|x64 {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Release|x64.Build.0 = Release|x64 + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Release|Any CPU.Build.0 = Release|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Release|x86.ActiveCfg = Release|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.Release|x86.Build.0 = Release|Any CPU {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.ReleaseValidate|x64.ActiveCfg = Release|x64 {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.ReleaseValidate|x64.Build.0 = Release|x64 + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.ReleaseValidate|Any CPU.ActiveCfg = ReleaseValidate|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.ReleaseValidate|Any CPU.Build.0 = ReleaseValidate|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.ReleaseValidate|x86.ActiveCfg = ReleaseValidate|Any CPU + {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C}.ReleaseValidate|x86.Build.0 = ReleaseValidate|Any CPU + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Debug|x64.ActiveCfg = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Debug|x64.Build.0 = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Debug|Any CPU.ActiveCfg = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Debug|Any CPU.Build.0 = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Debug|x86.ActiveCfg = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Debug|x86.Build.0 = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Release|x64.ActiveCfg = Release|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Release|x64.Build.0 = Release|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Release|Any CPU.ActiveCfg = Release|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Release|Any CPU.Build.0 = Release|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Release|x86.ActiveCfg = Release|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.Release|x86.Build.0 = Release|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.ReleaseValidate|x64.ActiveCfg = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.ReleaseValidate|x64.Build.0 = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.ReleaseValidate|Any CPU.ActiveCfg = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.ReleaseValidate|Any CPU.Build.0 = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.ReleaseValidate|x86.ActiveCfg = Debug|x64 + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A}.ReleaseValidate|x86.Build.0 = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Debug|x64.ActiveCfg = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Debug|x64.Build.0 = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Debug|Any CPU.Build.0 = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Debug|x86.ActiveCfg = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Debug|x86.Build.0 = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Release|x64.ActiveCfg = Release|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Release|x64.Build.0 = Release|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Release|Any CPU.ActiveCfg = Release|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Release|Any CPU.Build.0 = Release|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Release|x86.ActiveCfg = Release|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.Release|x86.Build.0 = Release|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.ReleaseValidate|x64.ActiveCfg = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.ReleaseValidate|x64.Build.0 = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.ReleaseValidate|Any CPU.ActiveCfg = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.ReleaseValidate|Any CPU.Build.0 = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.ReleaseValidate|x86.ActiveCfg = Debug|x64 + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA}.ReleaseValidate|x86.Build.0 = Debug|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {0D465539-6133-4088-B4BB-F260FA2A1557} = {121C2200-A844-44FD-85C4-22D6C7E35553} {D79C8833-D241-4867-BF6F-8097E0ED8067} = {121C2200-A844-44FD-85C4-22D6C7E35553} {CC460943-1E07-4FA0-8B8C-67F0EF385290} = {121C2200-A844-44FD-85C4-22D6C7E35553} {CB1DFB63-22D9-4E90-A8C1-A4F7CFEF7823} = {121C2200-A844-44FD-85C4-22D6C7E35553} {41F4BB08-FCFD-420F-AD18-ED9D7FB3251C} = {121C2200-A844-44FD-85C4-22D6C7E35553} + {1DCA913C-5266-F6A9-9DF7-A9D612F05C20} = {121C2200-A844-44FD-85C4-22D6C7E35553} + {DFF4DE5D-924D-4602-A723-CBAF05BFCD2A} = {1DCA913C-5266-F6A9-9DF7-A9D612F05C20} + {5FDDD49F-C9E6-45A7-A922-78208B2A5ECA} = {1DCA913C-5266-F6A9-9DF7-A9D612F05C20} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF} diff --git a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs index f29bbf68..4d6174b2 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.Profile.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.Profile.cs @@ -1,13 +1,11 @@ -using CustomizePlus.Api.Data; +using CustomizePlus.Api.Data; using CustomizePlus.Api.Enums; -using CustomizePlus.Armatures.Data; using CustomizePlus.Armatures.Events; using CustomizePlus.Core.Extensions; using CustomizePlus.GameData.Extensions; using CustomizePlus.Profiles.Data; using CustomizePlus.Profiles.Enums; using CustomizePlus.Profiles.Exceptions; -using CustomizePlus.Templates.Data; using CustomizePlus.Templates.Events; using Dalamud.Game.ClientState.Objects.Types; using ECommonsLite.EzIpcManager; @@ -16,9 +14,6 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; -using System; -using System.Collections.Generic; -using System.Linq; namespace CustomizePlus.Api; @@ -48,7 +43,7 @@ private IList GetProfileList() .Where(x => x.ProfileType == ProfileType.Normal) .Select(x => { - string path = _profileFileSystem.TryGetValue(x, out var leaf) ? leaf.FullName() : x.Name.Text; + var path = x.Node?.FullPath ?? x.Name.Text; var charactersList = new List(x.Characters.Count); foreach (var character in x.Characters) @@ -420,8 +415,9 @@ private int DeleteTemporaryProfileByUniqueId(Guid uniqueId) } //Send profile update if any of the templates were changed in currently active profile - private void OnTemplateChanged(TemplateChanged.Type type, Template? template, object? arg3) + private void OnTemplateChanged(in TemplateChanged.Arguments args) { + var (type, template, arg3) = args; if (type != TemplateChanged.Type.EditorDisabled) return; @@ -449,8 +445,9 @@ private void OnTemplateChanged(TemplateChanged.Type type, Template? template, ob } //warn: intended limitation - ignores default profiles because why you would use default profile on your own character - private void OnArmatureChanged(ArmatureChanged.Type type, Armature armature, object? arg3) + private void OnArmatureChanged(in ArmatureChanged.Arguments args) { + var (type, armature, arg3) = args; if (armature.ActorIdentifier != _gameObjectService.GetCurrentPlayerActorIdentifier()) return; diff --git a/CustomizePlus/Api/CustomizePlusIpc.cs b/CustomizePlus/Api/CustomizePlusIpc.cs index 6cbc581b..db776e5c 100644 --- a/CustomizePlus/Api/CustomizePlusIpc.cs +++ b/CustomizePlus/Api/CustomizePlusIpc.cs @@ -6,8 +6,6 @@ using CustomizePlus.Templates.Events; using Dalamud.Plugin; using ECommonsLite.EzIpcManager; -using OtterGui.Log; -using System; using Penumbra.GameData.Actors; namespace CustomizePlus.Api; @@ -27,7 +25,6 @@ public partial class CustomizePlusIpc : IDisposable private readonly ProfileManager _profileManager; private readonly ActorManager _actorManager; private readonly GameObjectService _gameObjectService; - private readonly ProfileFileSystem _profileFileSystem; private readonly CutsceneService _cutsceneService; private readonly ArmatureChanged _armatureChangedEvent; @@ -45,7 +42,6 @@ public CustomizePlusIpc( ProfileManager profileManager, ActorManager actorManager, GameObjectService gameObjectService, - ProfileFileSystem profileFileSystem, CutsceneService cutsceneService, ArmatureChanged armatureChangedEvent, TemplateChanged templateChangedEvent) @@ -56,7 +52,6 @@ public CustomizePlusIpc( _profileManager = profileManager; _actorManager = actorManager; _gameObjectService = gameObjectService; - _profileFileSystem = profileFileSystem; _cutsceneService = cutsceneService; _armatureChangedEvent = armatureChangedEvent; diff --git a/CustomizePlus/Armatures/Events/ArmatureChanged.cs b/CustomizePlus/Armatures/Events/ArmatureChanged.cs index cb3ec199..ab6b7ed9 100644 --- a/CustomizePlus/Armatures/Events/ArmatureChanged.cs +++ b/CustomizePlus/Armatures/Events/ArmatureChanged.cs @@ -1,13 +1,15 @@ -using CustomizePlus.Armatures.Data; -using OtterGui.Classes; +using CustomizePlus.Armatures.Data; namespace CustomizePlus.Armatures.Events; /// /// Triggered when armature is changed /// -public sealed class ArmatureChanged() : EventWrapper(nameof(ArmatureChanged)) +public sealed class ArmatureChanged(LunaLogger log) + : EventBase(nameof(ArmatureChanged), log) { + public readonly record struct Arguments(Type Type, Armature Armature, object? Data); + public enum Type { Created, diff --git a/CustomizePlus/Armatures/Services/ArmatureManager.cs b/CustomizePlus/Armatures/Services/ArmatureManager.cs index dbb304e4..7ca0402b 100644 --- a/CustomizePlus/Armatures/Services/ArmatureManager.cs +++ b/CustomizePlus/Armatures/Services/ArmatureManager.cs @@ -1,4 +1,4 @@ -using CustomizePlus.Armatures.Data; +using CustomizePlus.Armatures.Data; using CustomizePlus.Armatures.Events; using CustomizePlus.Core.Data; using CustomizePlus.Core.Extensions; @@ -10,15 +10,9 @@ using CustomizePlus.Profiles.Events; using CustomizePlus.Templates.Events; using Dalamud.Plugin.Services; -using OtterGui.Classes; -using OtterGui.Log; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; namespace CustomizePlus.Armatures.Services; @@ -166,7 +160,7 @@ private void RefreshArmatures() TryLinkSkeleton(newArm); Armatures.Add(actorIdentifier, newArm); _logger.Debug($"Added '{newArm}' for {actorIdentifier.IncognitoDebug()} to cache"); - _event.Invoke(ArmatureChanged.Type.Created, newArm, activeProfile); + _event.Invoke(new ArmatureChanged.Arguments(ArmatureChanged.Type.Created, newArm, activeProfile)); continue; } @@ -232,7 +226,7 @@ private void RefreshArmatures() } } - _event.Invoke(ArmatureChanged.Type.Updated, armature, (activeProfile, oldProfile)); + _event.Invoke(new ArmatureChanged.Arguments(ArmatureChanged.Type.Updated, armature, (activeProfile, oldProfile))); } //Needed because: @@ -301,7 +295,7 @@ private void ApplyPiecewiseTransformation(Armature armature, Actor actor, ActorI var cBase = actor.Model.AsCharacterBase; var isMount = actorIdentifier.Type == IdentifierType.Owned && - actorIdentifier.Kind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.MountType; + actorIdentifier.Kind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Mount; Actor? mountOwner = null; Armature? mountOwnerArmature = null; @@ -414,11 +408,12 @@ private void RemoveArmature(Armature armature, ArmatureChanged.DeletionReason re Armatures.Remove(armature.ActorIdentifier); _logger.Debug($"Armature {armature} removed from cache"); - _event.Invoke(ArmatureChanged.Type.Deleted, armature, reason); + _event.Invoke(new ArmatureChanged.Arguments(ArmatureChanged.Type.Deleted, armature, reason)); } - private void OnTemplateChange(TemplateChanged.Type type, Templates.Data.Template? template, object? arg3) + private void OnTemplateChange(in TemplateChanged.Arguments args) { + var (type, template, arg3) = args; if (type is not TemplateChanged.Type.NewBone && type is not TemplateChanged.Type.DeletedBone && type is not TemplateChanged.Type.EditorCharacterChanged && @@ -488,8 +483,9 @@ type is not TemplateChanged.Type.EditorEnabled && } } - private void OnProfileChange(ProfileChanged.Type type, Profile? profile, object? arg3) + private void OnProfileChange(in ProfileChanged.Arguments args) { + var (type, profile, arg3) = args; if (type is not ProfileChanged.Type.AddedTemplate && type is not ProfileChanged.Type.RemovedTemplate && type is not ProfileChanged.Type.EnabledTemplate && diff --git a/CustomizePlus/Configuration/Data/PluginConfiguration.cs b/CustomizePlus/Configuration/Data/PluginConfiguration.cs index 0450366e..8d0046e0 100644 --- a/CustomizePlus/Configuration/Data/PluginConfiguration.cs +++ b/CustomizePlus/Configuration/Data/PluginConfiguration.cs @@ -1,18 +1,10 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Dalamud.Configuration; -using Newtonsoft.Json; -using OtterGui.Classes; -using OtterGui.Widgets; -using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; -using CustomizePlus.Core.Services; using CustomizePlus.Core.Data; -using CustomizePlus.Configuration.Services; +using CustomizePlus.Core.Helpers; +using CustomizePlus.Core.Services; using CustomizePlus.UI.Windows; -using Dalamud.Interface.ImGuiNotification; +using Dalamud.Configuration; +using Newtonsoft.Json; using Penumbra.GameData.Actors; -using CustomizePlus.Core.Helpers; namespace CustomizePlus.Configuration.Data; @@ -49,6 +41,7 @@ public class ChangelogSettingsEntries [Serializable] public class UISettingsEntries { + [JsonConverter(typeof(DoubleModifierJsonConverter))] public DoubleModifier DeleteTemplateModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public bool FoldersDefaultOpen { get; set; } = true; @@ -133,54 +126,6 @@ public class IntegrationSettingsEntries public IntegrationSettingsEntries IntegrationSettings { get; set; } = new(); - [JsonIgnore] - private readonly SaveService _saveService; - - [JsonIgnore] - private readonly MessageService _messageService; - - public PluginConfiguration( - SaveService saveService, - MessageService messageService, - ConfigurationMigrator migrator) - { - _saveService = saveService; - _messageService = messageService; - - Load(migrator); - } - - public void Load(ConfigurationMigrator migrator) - { - static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs) - { - Plugin.Logger.Error( - $"Error parsing configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}"); - errorArgs.ErrorContext.Handled = true; - } - - if (!File.Exists(_saveService.FileNames.ConfigFile)) - return; - - try - { - var text = File.ReadAllText(_saveService.FileNames.ConfigFile); - JsonConvert.PopulateObject(text, this, new JsonSerializerSettings - { - Error = HandleDeserializationError, - Converters = new List { new ActorIdentifierJsonConverter() } - }); - } - catch (Exception ex) - { - _messageService.NotificationMessage(ex, - "Error reading configuration, reverting to default.\nYou may be able to restore your configuration using the rolling backups in the XIVLauncher/backups/CustomizePlus directory.", - "Error reading configuration", NotificationType.Error); - } - - migrator.Migrate(this); - } - public string ToFilename(FilenameService fileNames) => fileNames.ConfigFile; @@ -191,7 +136,4 @@ public void Save(StreamWriter writer) serializer.Converters.Add(new ActorIdentifierJsonConverter()); serializer.Serialize(jWriter, this); } - - public void Save() - => _saveService.DelaySave(this); -} \ No newline at end of file +} diff --git a/CustomizePlus/Configuration/Services/ConfigurationMigrator.cs b/CustomizePlus/Configuration/Services/ConfigurationMigrator.cs index cd4437a1..9eaadac2 100644 --- a/CustomizePlus/Configuration/Services/ConfigurationMigrator.cs +++ b/CustomizePlus/Configuration/Services/ConfigurationMigrator.cs @@ -1,48 +1,35 @@ -using OtterGui.Classes; -using OtterGui.Log; -using CustomizePlus.Core.Data; -using CustomizePlus.Core.Services; using CustomizePlus.Configuration.Data; -using CustomizePlus.Core.Events; +using CustomizePlus.Core.Data; using Dalamud.Interface.ImGuiNotification; namespace CustomizePlus.Configuration.Services; public class ConfigurationMigrator { - private readonly SaveService _saveService; - private readonly BackupService _backupService; private readonly MessageService _messageService; //we can't use popups here since they rely on PluginConfiguration and using them here hangs plugin loading private readonly Logger _logger; - private readonly ReloadEvent _reloadEvent; public ConfigurationMigrator( - SaveService saveService, - BackupService backupService, MessageService messageService, - Logger logger, - ReloadEvent reloadEvent + Logger logger ) { - _saveService = saveService; - _backupService = backupService; _messageService = messageService; _logger = logger; - _reloadEvent = reloadEvent; } - public void Migrate(PluginConfiguration config) + public bool Migrate(PluginConfiguration config) { var configVersion = config.Version; if (configVersion >= Constants.ConfigurationVersion) - return; + return false; //We no longer support migrations of any versions < 4 if (configVersion < 4) { _messageService.NotificationMessage("Unsupported version of Customize+ configuration data detected. Check FAQ over at https://github.com/Aether-Tools/CustomizePlus for information.", NotificationType.Error); - return; + return false; } // V4 to V5: Added ChildScaling field to BoneTransform @@ -52,6 +39,6 @@ public void Migrate(PluginConfiguration config) } config.Version = Constants.ConfigurationVersion; - _saveService.ImmediateSave(config); + return true; } } diff --git a/CustomizePlus/Configuration/Services/ConfigurationService.cs b/CustomizePlus/Configuration/Services/ConfigurationService.cs new file mode 100644 index 00000000..82b43741 --- /dev/null +++ b/CustomizePlus/Configuration/Services/ConfigurationService.cs @@ -0,0 +1,92 @@ +using CustomizePlus.Configuration.Data; +using CustomizePlus.Core.Helpers; +using CustomizePlus.Core.Services; +using Dalamud.Interface.ImGuiNotification; +using Newtonsoft.Json; +using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; + +namespace CustomizePlus.Configuration.Services; + +public sealed class ConfigurationService : IDisposable +{ + private readonly SaveService _saveService; + private readonly MessageService _messageService; + private readonly ConfigurationMigrator _migrator; + + private bool _dirty; + + public ConfigurationService( + SaveService saveService, + MessageService messageService, + ConfigurationMigrator migrator) + { + _saveService = saveService; + _messageService = messageService; + _migrator = migrator; + + Current = Load(); + if (_migrator.Migrate(Current)) + SaveNow(); + } + + public event Action? Changed; + + public PluginConfiguration Current { get; } + + public void Update(Action update, PluginConfigurationChange change = PluginConfigurationChange.General) + { + update(Current); + Save(change); + } + + public void Save(PluginConfigurationChange change = PluginConfigurationChange.General) + { + _dirty = true; + Changed?.Invoke(Current, change); + _saveService.DelaySave(Current); + } + + public void SaveNow() + { + _dirty = false; + _saveService.ImmediateSaveSync(Current); + } + + public void Dispose() + { + if (_dirty) + SaveNow(); + } + + private PluginConfiguration Load() + { + var config = new PluginConfiguration(); + if (!File.Exists(_saveService.FileNames.ConfigFile)) + return config; + + try + { + var text = File.ReadAllText(_saveService.FileNames.ConfigFile); + JsonConvert.PopulateObject(text, config, new JsonSerializerSettings + { + Error = HandleDeserializationError, + Converters = new List { new ActorIdentifierJsonConverter() } + }); + } + catch (Exception ex) + { + _messageService.NotificationMessage(ex, + "Error reading configuration, reverting to default.\nYou may be able to restore your configuration using the rolling backups in the XIVLauncher/backups/CustomizePlus directory.", + "Error reading configuration", NotificationType.Error); + } + + return config; + } + + private static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs) + { + Plugin.Logger.Error( + $"Error parsing configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}"); + errorArgs.ErrorContext.Handled = true; + } +} diff --git a/CustomizePlus/Configuration/Services/PluginConfigurationChange.cs b/CustomizePlus/Configuration/Services/PluginConfigurationChange.cs new file mode 100644 index 00000000..f010b81b --- /dev/null +++ b/CustomizePlus/Configuration/Services/PluginConfigurationChange.cs @@ -0,0 +1,17 @@ +namespace CustomizePlus.Configuration.Services; + +[Flags] +public enum PluginConfigurationChange +{ + None = 0, + General = 1 << 0, + PluginState = 1 << 1, + ProfileApplication = 1 << 2, + Interface = 1 << 3, + Editor = 1 << 4, + Integration = 1 << 5, + Command = 1 << 6, + Layout = 1 << 7, + Popup = 1 << 8, + Changelog = 1 << 9, +} diff --git a/CustomizePlus/Core/Data/BoneData.cs b/CustomizePlus/Core/Data/BoneData.cs index de1e4a45..f6790955 100644 --- a/CustomizePlus/Core/Data/BoneData.cs +++ b/CustomizePlus/Core/Data/BoneData.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using Dalamud.Utility; using System.Text; using System.Text.RegularExpressions; -using Dalamud.Utility; namespace CustomizePlus.Core.Data; diff --git a/CustomizePlus/Core/Data/BoneTransform.cs b/CustomizePlus/Core/Data/BoneTransform.cs index b562ef65..7be4de2f 100644 --- a/CustomizePlus/Core/Data/BoneTransform.cs +++ b/CustomizePlus/Core/Data/BoneTransform.cs @@ -1,9 +1,7 @@ -using System; -using System.Numerics; -using System.Runtime.Serialization; -using CustomizePlus.Core.Extensions; +using CustomizePlus.Core.Extensions; using CustomizePlus.Game.Services.GPose.ExternalTools; using FFXIVClientStructs.Havok.Common.Base.Math.QsTransform; +using System.Runtime.Serialization; namespace CustomizePlus.Core.Data; diff --git a/CustomizePlus/Core/Events/ReloadEvent.cs b/CustomizePlus/Core/Events/ReloadEvent.cs index 4347d34f..7a30889a 100644 --- a/CustomizePlus/Core/Events/ReloadEvent.cs +++ b/CustomizePlus/Core/Events/ReloadEvent.cs @@ -1,12 +1,12 @@ -using OtterGui.Classes; - namespace CustomizePlus.Core.Events; /// /// Triggered when complete plugin reload is requested /// -public sealed class ReloadEvent() : EventWrapper(nameof(ReloadEvent)) +public sealed class ReloadEvent(LunaLogger log) : EventBase(nameof(ReloadEvent), log) { + public readonly record struct Arguments(Type Type); + public enum Type { ReloadAll, diff --git a/CustomizePlus/Core/Helpers/CtrlHelper.cs b/CustomizePlus/Core/Helpers/CtrlHelper.cs index d27eefb2..78f06786 100644 --- a/CustomizePlus/Core/Helpers/CtrlHelper.cs +++ b/CustomizePlus/Core/Helpers/CtrlHelper.cs @@ -1,8 +1,5 @@ -using System; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Utility; -using Dalamud.Bindings.ImGui; -using System.Text; namespace CustomizePlus.Core.Helpers; @@ -12,12 +9,12 @@ public static class CtrlHelper /// Gets the width of an icon button, checkbox, etc... /// /// per https://github.com/ocornut/imgui/issues/3714#issuecomment-759319268 - public static float IconButtonWidth => ImGui.GetFrameHeight() + 2 * ImGui.GetStyle().ItemInnerSpacing.X; + public static float IconButtonWidth => Im.Style.FrameHeight + 2 * Im.Style.ItemInnerSpacing.X; public static bool TextBox(string label, ref string value) { - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - return ImGui.InputText(label, ref value, 1024); + Im.Item.SetNextWidthFull(); + return Im.Input.Text(label, ref value, maxLength: 1024); } public static bool TextPropertyBox(string label, Func get, Action set) @@ -34,21 +31,13 @@ public static bool TextPropertyBox(string label, Func get, Action toggle) { var temp = shown; - var toggled = ImGui.Checkbox(label, ref temp); + var toggled = Im.Checkbox(label, ref temp); if (toggled) { @@ -68,29 +57,16 @@ public static bool CheckboxToggle(string label, in bool shown, Action togg public static bool ArrowToggle(string label, ref bool value) { - unsafe // temporary fix - { - var utf8Label = Encoding.UTF8.GetBytes(label + "\0"); - - fixed (byte* labelPtr = utf8Label) - { - bool toggled = ImGuiNative.ArrowButton(labelPtr, value ? ImGuiDir.Down : ImGuiDir.Right) != 0; - - if (toggled) - value = !value; + if (Im.ArrowButton(label, value ? Direction.Down : Direction.Right)) + value = !value; - return value; - } - } + return value; } public static void AddHoverText(string text) { - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(text); - } + Im.Tooltip.OnHover(text); } public enum TextAlignment { Left, Center, Right }; @@ -100,16 +76,16 @@ public static void StaticLabel(string? text, TextAlignment align = TextAlignment { if (align == TextAlignment.Center) { - ImGui.Dummy(new System.Numerics.Vector2((ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize(text).X) / 2, 0)); - ImGui.SameLine(); + Im.Dummy((Im.ContentRegion.Available.X - Im.Font.CalculateSize(text).X) / 2, 0); + Im.Line.Same(); } else if (align == TextAlignment.Right) { - ImGui.Dummy(new System.Numerics.Vector2(ImGui.GetContentRegionAvail().X - ImGui.CalcTextSize(text).X, 0)); - ImGui.SameLine(); + Im.Dummy(Im.ContentRegion.Available.X - Im.Font.CalculateSize(text).X, 0); + Im.Line.Same(); } - ImGui.Text(text); + Im.Text(text); if (!tooltip.IsNullOrWhitespace()) { AddHoverText(tooltip); @@ -120,11 +96,9 @@ public static void StaticLabel(string? text, TextAlignment align = TextAlignment public static void LabelWithIcon(FontAwesomeIcon icon, string text, bool isSameLine = true) { if (isSameLine) - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(icon.ToIconString()); - ImGui.PopFont(); - ImGui.SameLine(); - ImGui.TextWrapped(text); + Im.Line.Same(); + icon.Draw(); + Im.Line.Same(); + Im.TextWrapped(text); } } \ No newline at end of file diff --git a/CustomizePlus/Core/Helpers/DoubleModifierJsonConverter.cs b/CustomizePlus/Core/Helpers/DoubleModifierJsonConverter.cs new file mode 100644 index 00000000..b0f4983c --- /dev/null +++ b/CustomizePlus/Core/Helpers/DoubleModifierJsonConverter.cs @@ -0,0 +1,60 @@ +using Dalamud.Game.ClientState.Keys; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CustomizePlus.Core.Helpers; + +internal sealed class DoubleModifierJsonConverter : JsonConverter +{ + public override void WriteJson(JsonWriter writer, DoubleModifier value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName(nameof(DoubleModifier.Modifier1)); + writer.WriteValue((ushort)value.Modifier1.Modifier); + + if (value.Modifier2.Modifier != ModifierHotkey.NoKey) + { + writer.WritePropertyName(nameof(DoubleModifier.Modifier2)); + writer.WriteValue((ushort)value.Modifier2.Modifier); + } + + writer.WriteEndObject(); + } + + public override DoubleModifier ReadJson( + JsonReader reader, + Type objectType, + DoubleModifier existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return existingValue; + + var token = JToken.Load(reader); + if (token.Type is not JTokenType.Object) + return existingValue; + + var modifier1 = ReadModifier(token[nameof(DoubleModifier.Modifier1)]); + var modifier2 = ReadModifier(token[nameof(DoubleModifier.Modifier2)]); + + return new DoubleModifier(modifier1, modifier2); + } + + private static ModifierHotkey ReadModifier(JToken? token) + { + if (token is null || token.Type == JTokenType.Null) + return ModifierHotkey.NoKey; + + if (token.Type is JTokenType.Integer) + return new ModifierHotkey((VirtualKey)token.Value()); + + if (token.Type is JTokenType.String && ushort.TryParse(token.Value(), out var value)) + return new ModifierHotkey((VirtualKey)value); + + if (token is JObject obj && obj.TryGetValue(nameof(ModifierHotkey.Modifier), out var modifier)) + return ReadModifier(modifier); + + return ModifierHotkey.NoKey; + } +} diff --git a/CustomizePlus/Core/LowerString.cs b/CustomizePlus/Core/LowerString.cs new file mode 100644 index 00000000..c861b662 --- /dev/null +++ b/CustomizePlus/Core/LowerString.cs @@ -0,0 +1,86 @@ +using Dalamud.Bindings.ImGui; +using Newtonsoft.Json; + +namespace CustomizePlus; + +[JsonConverter(typeof(Converter))] +public readonly struct LowerString : IEquatable, IComparable, IEquatable, IComparable +{ + public static readonly LowerString Empty = new(string.Empty); + + public readonly string Text; + public readonly string Lower; + + public LowerString(string text) + { + Text = string.Intern(text); + Lower = string.Intern(text.ToLowerInvariant()); + } + + public int Length + => Text.Length; + + public bool IsEmpty + => Length == 0; + + public bool Equals(LowerString other) + => string.Equals(Lower, other.Lower, StringComparison.Ordinal); + + public bool Equals(string? other) + => string.Equals(Lower, other, StringComparison.OrdinalIgnoreCase); + + public int CompareTo(LowerString other) + => string.Compare(Lower, other.Lower, StringComparison.Ordinal); + + public int CompareTo(string? other) + => string.Compare(Lower, other, StringComparison.OrdinalIgnoreCase); + + public bool Contains(LowerString other) + => Lower.Contains(other.Lower, StringComparison.Ordinal); + + public bool Contains(string other) + => Lower.Contains(other, StringComparison.OrdinalIgnoreCase); + + public bool IsContained(string other) + => IsEmpty || other.Contains(Lower, StringComparison.OrdinalIgnoreCase); + + public override string ToString() + => Text; + + public static implicit operator string(LowerString value) + => value.Text; + + public static implicit operator LowerString(string value) + => new(value); + + public static bool InputWithHint(string label, string hint, ref LowerString value, int maxLength = 128, + ImGuiInputTextFlags flags = ImGuiInputTextFlags.None) + { + var text = value.Text; + if (!ImGui.InputTextWithHint(label, hint, ref text, maxLength, flags) || text == value.Text) + return false; + + value = new LowerString(text); + return true; + } + + public override bool Equals(object? obj) + => obj is LowerString lowerString && Equals(lowerString); + + public override int GetHashCode() + => Text.GetHashCode(); + + private sealed class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, LowerString value, JsonSerializer serializer) + => writer.WriteValue(value.Text); + + public override LowerString ReadJson(JsonReader reader, Type objectType, LowerString existingValue, bool hasExistingValue, + JsonSerializer serializer) + => reader.Value is string text ? new LowerString(text) : existingValue; + } +} + + + + diff --git a/CustomizePlus/Core/ServiceManagerBuilder.cs b/CustomizePlus/Core/ServiceManagerBuilder.cs index 10ef21fc..35c96ed2 100644 --- a/CustomizePlus/Core/ServiceManagerBuilder.cs +++ b/CustomizePlus/Core/ServiceManagerBuilder.cs @@ -2,11 +2,9 @@ using CustomizePlus.Api; using CustomizePlus.Armatures.Events; using CustomizePlus.Armatures.Services; -using CustomizePlus.Configuration.Data; using CustomizePlus.Configuration.Services; using CustomizePlus.Core.Events; using CustomizePlus.Core.Services; -using CustomizePlus.Core.Services.Dalamud; using CustomizePlus.Game.Events; using CustomizePlus.Game.Services; using CustomizePlus.Game.Services.GPose; @@ -17,7 +15,6 @@ using CustomizePlus.Profiles.Events; using CustomizePlus.Templates; using CustomizePlus.Templates.Events; -using CustomizePlus.UI; using CustomizePlus.UI.Windows; using CustomizePlus.UI.Windows.Controls; using CustomizePlus.UI.Windows.MainWindow; @@ -27,14 +24,9 @@ using CustomizePlus.UI.Windows.MainWindow.Tabs.Templates; using Dalamud.Plugin; using Microsoft.Extensions.DependencyInjection; -using OtterGui.Classes; -using OtterGui.Log; -using OtterGui.Raii; -using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using System.Collections.Generic; namespace CustomizePlus.Core; @@ -42,9 +34,7 @@ public static class ServiceManagerBuilder { public static ServiceManager CreateProvider(IDalamudPluginInterface pi, Logger logger) { - EventWrapperBase.ChangeLogger(logger); - - var services = new ServiceManager(logger) + var services = new ServiceManager(logger, "Customize+") .AddExistingService(logger) .AddCore() .AddEvents() @@ -60,14 +50,14 @@ public static ServiceManager CreateProvider(IDalamudPluginInterface pi, Logger l .AddDataLoaders() .AddApi(); - DalamudServices.AddServices(services, pi); + services.AddDalamudServices(pi); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Plugin).Assembly); services.AddIServices(typeof(CutsceneService).Assembly); - services.AddIServices(typeof(ImRaii).Assembly); + services.AddIServices(typeof(MessageService).Assembly); - services.CreateProvider(); + services.BuildProvider(); return services; } @@ -187,7 +177,8 @@ private static ServiceManager AddApi(this ServiceManager services) private static ServiceManager AddConfigServices(this ServiceManager services) { services - .AddSingleton() + .AddSingleton() + .AddSingleton(provider => provider.GetRequiredService().Current) .AddSingleton(); return services; diff --git a/CustomizePlus/Core/Services/BackupService.cs b/CustomizePlus/Core/Services/BackupService.cs index 32bd0079..bfbb37bf 100644 --- a/CustomizePlus/Core/Services/BackupService.cs +++ b/CustomizePlus/Core/Services/BackupService.cs @@ -1,8 +1,4 @@ -using OtterGui.Classes; -using OtterGui.Log; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Threading; namespace CustomizePlus.Core.Services; @@ -19,7 +15,7 @@ public BackupService(Logger logger, FilenameService filenameService) _filenameService = filenameService; _fileNames = PluginFiles(_filenameService); _configDirectory = new DirectoryInfo(_filenameService.ConfigDirectory); - Backup.CreateAutomaticBackup(logger, _configDirectory, _fileNames); + Backup.CreateAutomaticBackup(logger, _configDirectory, _fileNames, CancellationToken.None); } /// @@ -47,8 +43,16 @@ private static IReadOnlyList PluginFiles(FilenameService fileNames) var list = new List(16) { new(fileNames.ConfigFile), - new(fileNames.ProfileFileSystem), - new(fileNames.TemplateFileSystem) + new(fileNames.LegacyProfileSortOrder), + new(fileNames.ProfileOrganization), + new(fileNames.ProfileLockedNodes), + new(fileNames.ProfileSelectedNodes), + new(fileNames.ProfileExpandedFolders), + new(fileNames.LegacyTemplateSortOrder), + new(fileNames.TemplateOrganization), + new(fileNames.TemplateLockedNodes), + new(fileNames.TemplateSelectedNodes), + new(fileNames.TemplateExpandedFolders) }; list.AddRange(fileNames.Profiles()); diff --git a/CustomizePlus/Core/Services/CommandService.cs b/CustomizePlus/Core/Services/CommandService.cs index 5081c513..25655482 100644 --- a/CustomizePlus/Core/Services/CommandService.cs +++ b/CustomizePlus/Core/Services/CommandService.cs @@ -1,19 +1,14 @@ -using Dalamud.Game.Command; -using Dalamud.Plugin.Services; -using OtterGui.Classes; -using System; -using System.Linq; -using OtterGui.Log; -using Dalamud.Game.Text.SeStringHandling; -using CustomizePlus.Profiles; +using CustomizePlus.Configuration.Data; using CustomizePlus.Game.Services; -using CustomizePlus.UI.Windows.MainWindow.Tabs.Templates; -using CustomizePlus.UI.Windows.MainWindow; +using CustomizePlus.GameData.Extensions; +using CustomizePlus.Profiles; using CustomizePlus.Profiles.Data; -using CustomizePlus.Configuration.Data; +using CustomizePlus.UI.Windows.MainWindow; +using CustomizePlus.UI.Windows.MainWindow.Tabs.Templates; +using Dalamud.Game.Command; +using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.ImGuiNotification; -using CustomizePlus.GameData.Extensions; -using System.Collections.Generic; +using Dalamud.Plugin.Services; namespace CustomizePlus.Core.Services; diff --git a/CustomizePlus/Core/Services/Dalamud/DalamudConfigService.cs b/CustomizePlus/Core/Services/Dalamud/DalamudConfigService.cs index 00a6d38d..bdc9cb94 100644 --- a/CustomizePlus/Core/Services/Dalamud/DalamudConfigService.cs +++ b/CustomizePlus/Core/Services/Dalamud/DalamudConfigService.cs @@ -1,8 +1,5 @@ using Dalamud.Plugin; -using OtterGui.Services; -using System.Linq; using System.Reflection; -using System; namespace CustomizePlus.Core.Services.Dalamud; diff --git a/CustomizePlus/Core/Services/Dalamud/DalamudServices.cs b/CustomizePlus/Core/Services/Dalamud/DalamudServices.cs deleted file mode 100644 index db755c05..00000000 --- a/CustomizePlus/Core/Services/Dalamud/DalamudServices.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Dalamud.Plugin; -using Dalamud.Plugin.Services; -using OtterGui.Services; - -namespace CustomizePlus.Core.Services.Dalamud; - -#pragma warning disable SeStringEvaluator - -public class DalamudServices -{ - public static void AddServices(ServiceManager services, IDalamudPluginInterface pi) - { - services.AddExistingService(pi) - .AddExistingService(pi.UiBuilder) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi); - } -} \ No newline at end of file diff --git a/CustomizePlus/Core/Services/FileSystemSaveService.cs b/CustomizePlus/Core/Services/FileSystemSaveService.cs new file mode 100644 index 00000000..4c14a9f0 --- /dev/null +++ b/CustomizePlus/Core/Services/FileSystemSaveService.cs @@ -0,0 +1,116 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace CustomizePlus.Core.Services; + +public sealed class FileSystemSaveService( + Logger log, + BaseFileSystem fileSystem, + SaveService saveService, + IEnumerable values, + Func valueFromIdentifier, + Func lockedFile, + Func expandedFile, + Func selectedFile, + Func organizationFile, + Func migrationFile) + : FileSystemSaver(log, fileSystem, saveService) + where T : class, IFileSystemValue, ISavable +{ + private static readonly Dictionary SortModes = new(StringComparer.Ordinal) + { + [nameof(ISortMode.FoldersFirst)] = ISortMode.FoldersFirst, + [nameof(ISortMode.Lexicographical)] = ISortMode.Lexicographical, + [nameof(ISortMode.InverseFoldersFirst)] = ISortMode.InverseFoldersFirst, + [nameof(ISortMode.InverseLexicographical)] = ISortMode.InverseLexicographical, + [nameof(ISortMode.FoldersLast)] = ISortMode.FoldersLast, + [nameof(ISortMode.InverseFoldersLast)] = ISortMode.InverseFoldersLast, + [nameof(ISortMode.InternalOrder)] = ISortMode.InternalOrder, + [nameof(ISortMode.InverseInternalOrder)] = ISortMode.InverseInternalOrder, + }; + + public override void Load() + { + MigrateLegacySortOrder(); + base.Load(); + } + + protected override string LockedFile(FilenameService provider) + => lockedFile(provider); + + protected override string ExpandedFile(FilenameService provider) + => expandedFile(provider); + + protected override string SelectionFile(FilenameService provider) + => selectedFile(provider); + + protected override string OrganizationFile(FilenameService provider) + => organizationFile(provider); + + protected override string MigrationFile(FilenameService provider) + => migrationFile(provider); + + protected override bool GetValueFromIdentifier(ReadOnlySpan identifier, [NotNullWhen(true)] out IFileSystemValue? value) + { + value = valueFromIdentifier(identifier.ToString()); + return value is not null; + } + + protected override void CreateDataNodes() + { + foreach (var value in values) + { + var folder = value.Path.Folder.Length is 0 + ? FileSystem.Root + : FileSystem.FindOrCreateAllFolders(value.Path.Folder); + FileSystem.CreateDuplicateDataNode(folder, value.Path.GetIntendedName(value.DisplayName), value); + } + } + + protected override ISortMode? ParseSortMode(string name) + => SortModes.GetValueOrDefault(name); + + protected override void SaveDataValue(IFileSystemValue value) + { + if (value is T typedValue) + SaveService.QueueSave(typedValue); + } + + private void MigrateLegacySortOrder() + { + var file = MigrationFile(SaveService.FileNames); + if (file.Length is 0 || !File.Exists(file)) + return; + + try + { + using var document = JsonDocument.Parse(File.ReadAllText(file)); + if (document.RootElement.ValueKind is not JsonValueKind.Object || document.RootElement.TryGetProperty("Data", out _)) + return; + + Log.Information($"Migrating legacy Customize+ sort order {file} to Luna file system..."); + foreach (var property in document.RootElement.EnumerateObject()) + { + if (property.Value.ValueKind is not JsonValueKind.String || property.Value.GetString() is not { } path) + { + Log.Warning($"Ignoring invalid legacy file system entry {property.Name} in {file}."); + continue; + } + + if (!GetValueFromIdentifier(property.Name, out var value)) + { + Log.Warning($"Data Value {property.Name} with path {path} could not be found."); + continue; + } + + ApplyMigrationToData(value, path); + } + + File.Move(file, file + ".bak", true); + } + catch (Exception ex) + { + Log.Error($"Failed to migrate legacy Customize+ sort order {file}:\n{ex}"); + } + } +} diff --git a/CustomizePlus/Core/Services/FilenameService.cs b/CustomizePlus/Core/Services/FilenameService.cs index aa6428d5..ee5e1e5a 100644 --- a/CustomizePlus/Core/Services/FilenameService.cs +++ b/CustomizePlus/Core/Services/FilenameService.cs @@ -1,29 +1,27 @@ -using Dalamud.Plugin; -using System; -using System.Collections.Generic; -using System.IO; -using CustomizePlus.Profiles.Data; +using CustomizePlus.Profiles.Data; +using Dalamud.Plugin; namespace CustomizePlus.Core.Services; -public class FilenameService +public class FilenameService(IDalamudPluginInterface pi) : BaseFilePathProvider(pi) { - public readonly string ConfigDirectory; - public readonly string ConfigFile; - public readonly string ProfileDirectory; - public readonly string ProfileFileSystem; - public readonly string TemplateDirectory; - public readonly string TemplateFileSystem; - - public FilenameService(IDalamudPluginInterface pi) - { - ConfigDirectory = pi.ConfigDirectory.FullName; - ConfigFile = pi.ConfigFile.FullName; - ProfileDirectory = Path.Combine(ConfigDirectory, "profiles"); - ProfileFileSystem = Path.Combine(ConfigDirectory, "profile_sort_order.json"); - TemplateDirectory = Path.Combine(ConfigDirectory, "templates"); - TemplateFileSystem = Path.Combine(ConfigDirectory, "template_sort_order.json"); - } + public new readonly string ConfigDirectory = pi.ConfigDirectory.FullName; + public new readonly string ConfigFile = pi.ConfigFile.FullName; + public readonly string ProfileDirectory = Path.Combine(pi.ConfigDirectory.FullName, "profiles"); + public readonly string LegacyProfileSortOrder = Path.Combine(pi.ConfigDirectory.FullName, "profile_sort_order.json"); + public readonly string ProfileOrganization = Path.Combine(pi.ConfigDirectory.FullName, "profile_organization.json"); + public readonly string ProfileLockedNodes = Path.Combine(pi.ConfigDirectory.FullName, "profile_locked_nodes.json"); + public readonly string ProfileSelectedNodes = Path.Combine(pi.ConfigDirectory.FullName, "profile_selected_nodes.json"); + public readonly string ProfileExpandedFolders = Path.Combine(pi.ConfigDirectory.FullName, "profile_expanded_folders.json"); + public readonly string TemplateDirectory = Path.Combine(pi.ConfigDirectory.FullName, "templates"); + public readonly string LegacyTemplateSortOrder = Path.Combine(pi.ConfigDirectory.FullName, "template_sort_order.json"); + public readonly string TemplateOrganization = Path.Combine(pi.ConfigDirectory.FullName, "template_organization.json"); + public readonly string TemplateLockedNodes = Path.Combine(pi.ConfigDirectory.FullName, "template_locked_nodes.json"); + public readonly string TemplateSelectedNodes = Path.Combine(pi.ConfigDirectory.FullName, "template_selected_nodes.json"); + public readonly string TemplateExpandedFolders = Path.Combine(pi.ConfigDirectory.FullName, "template_expanded_folders.json"); + + public override List GetBackupFiles() + => []; public IEnumerable Templates() { diff --git a/CustomizePlus/Core/Services/HookingService.cs b/CustomizePlus/Core/Services/HookingService.cs index 11439eae..9ad1a013 100644 --- a/CustomizePlus/Core/Services/HookingService.cs +++ b/CustomizePlus/Core/Services/HookingService.cs @@ -1,14 +1,12 @@ -using Dalamud.Hooking; -using Dalamud.Plugin.Services; -using System; -using System.Runtime.InteropServices; -using OtterGui.Log; +using CustomizePlus.Armatures.Services; +using CustomizePlus.Configuration.Data; using CustomizePlus.Core.Data; using CustomizePlus.Game.Services; -using CustomizePlus.Configuration.Data; using CustomizePlus.Profiles; -using CustomizePlus.Armatures.Services; +using Dalamud.Hooking; +using Dalamud.Plugin.Services; using Penumbra.GameData.Interop; +using System.Runtime.InteropServices; namespace CustomizePlus.Core.Services; diff --git a/CustomizePlus/Core/Services/PcpService.cs b/CustomizePlus/Core/Services/PcpService.cs index 54ce1061..abd1c158 100644 --- a/CustomizePlus/Core/Services/PcpService.cs +++ b/CustomizePlus/Core/Services/PcpService.cs @@ -5,11 +5,8 @@ using CustomizePlus.Templates; using CustomizePlus.Templates.Data; using Newtonsoft.Json.Linq; -using OtterGui.Log; -using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Interop; -using System; namespace CustomizePlus.Core.Services; diff --git a/CustomizePlus/Core/Services/SaveService.cs b/CustomizePlus/Core/Services/SaveService.cs index 4b09fa99..18dbdba1 100644 --- a/CustomizePlus/Core/Services/SaveService.cs +++ b/CustomizePlus/Core/Services/SaveService.cs @@ -1,5 +1,4 @@ -using OtterGui.Classes; -using OtterGui.Log; +using System.Text; namespace CustomizePlus.Core.Services; @@ -7,11 +6,26 @@ namespace CustomizePlus.Core.Services; /// Any file type that we want to save via SaveService. /// public interface ISavable : ISavable -{ } +{ + string ToFilename(FilenameService fileNames); + + void Save(StreamWriter writer); + + string ISavable.ToFilePath(FilenameService fileNames) + => ToFilename(fileNames); + + void ISavable.Save(Stream stream) + { + using var writer = new StreamWriter(stream, new UTF8Encoding(false), leaveOpen: true); + Save(writer); + } +} -public sealed class SaveService : SaveServiceBase +public sealed class SaveService : BaseSaveService { public SaveService(Logger logger, FrameworkManager framework, FilenameService fileNames) : base(logger, framework, fileNames) - { } + { + BackupMode = BackupMode.SingleBackup; + } } diff --git a/CustomizePlus/CustomizePlus.csproj b/CustomizePlus/CustomizePlus.csproj index 7b11d26f..2da38337 100644 --- a/CustomizePlus/CustomizePlus.csproj +++ b/CustomizePlus/CustomizePlus.csproj @@ -1,6 +1,6 @@  - + CustomizePlus 2.0.0.0 @@ -23,18 +23,21 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/CustomizePlus/CustomizePlus.json b/CustomizePlus/CustomizePlus.json index 17dfa323..bc5e250f 100644 --- a/CustomizePlus/CustomizePlus.json +++ b/CustomizePlus/CustomizePlus.json @@ -6,8 +6,8 @@ "InternalName": "CustomizePlus", "ApplicableVersion": "any", "AssemblyVersion": "2.0.0.0", - "DalamudApiLevel": 14, - "TestingDalamudApiLevel": 14, + "DalamudApiLevel": 15, + "TestingDalamudApiLevel": 15, "Tags": [ "Anamnesis", "Customization", diff --git a/CustomizePlus/Game/Events/GPoseStateChanged.cs b/CustomizePlus/Game/Events/GPoseStateChanged.cs index e55c1d7f..3a78d6ac 100644 --- a/CustomizePlus/Game/Events/GPoseStateChanged.cs +++ b/CustomizePlus/Game/Events/GPoseStateChanged.cs @@ -1,12 +1,13 @@ -using OtterGui.Classes; - namespace CustomizePlus.Game.Events; /// /// Triggered when GPose is entered/exited /// -public sealed class GPoseStateChanged() : EventWrapper(nameof(GPoseStateChanged)) +public sealed class GPoseStateChanged(LunaLogger log) + : EventBase(nameof(GPoseStateChanged), log) { + public readonly record struct Arguments(Type Type); + public enum Type { Entered, diff --git a/CustomizePlus/Game/Services/EmoteService.cs b/CustomizePlus/Game/Services/EmoteService.cs index 358cfdc8..434c537f 100644 --- a/CustomizePlus/Game/Services/EmoteService.cs +++ b/CustomizePlus/Game/Services/EmoteService.cs @@ -1,6 +1,4 @@ -using Penumbra.GameData.Interop; -using OtterGui.Log; -using System.Linq; +using Penumbra.GameData.Interop; namespace CustomizePlus.Game.Services; diff --git a/CustomizePlus/Game/Services/GPose/GPoseService.cs b/CustomizePlus/Game/Services/GPose/GPoseService.cs index 7ee49296..f1db7713 100644 --- a/CustomizePlus/Game/Services/GPose/GPoseService.cs +++ b/CustomizePlus/Game/Services/GPose/GPoseService.cs @@ -1,9 +1,7 @@ -using System; +using CustomizePlus.Game.Events; using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.System.Framework; -using OtterGui.Log; -using CustomizePlus.Game.Events; namespace CustomizePlus.Game.Services.GPose; @@ -102,16 +100,16 @@ private bool HandleGPoseChange(GPoseState state) switch (state) { case GPoseState.Inside: - _event.Invoke(GPoseStateChanged.Type.Entered); + _event.Invoke(new GPoseStateChanged.Arguments(GPoseStateChanged.Type.Entered)); break; case GPoseState.AttemptExit: - _event.Invoke(GPoseStateChanged.Type.AttemptingExit); + _event.Invoke(new GPoseStateChanged.Arguments(GPoseStateChanged.Type.AttemptingExit)); break; case GPoseState.Exiting: - _event.Invoke(GPoseStateChanged.Type.Exiting); + _event.Invoke(new GPoseStateChanged.Arguments(GPoseStateChanged.Type.Exiting)); break; case GPoseState.Outside: - _event.Invoke(GPoseStateChanged.Type.Exited); + _event.Invoke(new GPoseStateChanged.Arguments(GPoseStateChanged.Type.Exited)); break; } diff --git a/CustomizePlus/GlobalUsings.cs b/CustomizePlus/GlobalUsings.cs new file mode 100644 index 00000000..b4b5e229 --- /dev/null +++ b/CustomizePlus/GlobalUsings.cs @@ -0,0 +1,9 @@ +global using CustomizePlus.UI; +global using ImSharp; +global using Luna; +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Numerics; +global using Logger = Luna.MainLogger; diff --git a/CustomizePlus/Interop/Ipc/IPCPenumbra.cs b/CustomizePlus/Interop/Ipc/IPCPenumbra.cs index d46611ca..ca0306ee 100644 --- a/CustomizePlus/Interop/Ipc/IPCPenumbra.cs +++ b/CustomizePlus/Interop/Ipc/IPCPenumbra.cs @@ -1,9 +1,7 @@ using Dalamud.Plugin; using Newtonsoft.Json.Linq; -using OtterGui.Log; using Penumbra.Api.Helpers; using Penumbra.Api.IpcSubscribers; -using System; namespace CustomizePlus.Interop.Ipc; diff --git a/CustomizePlus/Plugin.cs b/CustomizePlus/Plugin.cs index 43d8aa1c..f0af743a 100644 --- a/CustomizePlus/Plugin.cs +++ b/CustomizePlus/Plugin.cs @@ -3,13 +3,9 @@ using CustomizePlus.Core; using CustomizePlus.Core.Helpers; using CustomizePlus.Core.Services; -using CustomizePlus.UI; using Dalamud.Plugin; using ECommonsLite; -using OtterGui.Log; -using OtterGui.Services; using Penumbra.GameData.Actors; -using System; namespace CustomizePlus; diff --git a/CustomizePlus/Profiles/Data/Profile.cs b/CustomizePlus/Profiles/Data/Profile.cs index 25d04684..4f7965e1 100644 --- a/CustomizePlus/Profiles/Data/Profile.cs +++ b/CustomizePlus/Profiles/Data/Profile.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using CustomizePlus.Armatures.Data; using CustomizePlus.Core.Extensions; using CustomizePlus.Core.Services; @@ -9,7 +5,6 @@ using CustomizePlus.Templates.Data; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Classes; using Penumbra.GameData.Actors; namespace CustomizePlus.Profiles.Data; @@ -18,7 +13,7 @@ namespace CustomizePlus.Profiles.Data; /// Encapsulates the user-controlled aspects of a character profile, ie all of /// the information that gets saved to disk by the plugin. /// -public sealed class Profile : ISavable +public sealed class Profile : ISavable, IFileSystemValue { public const int Version = 5; @@ -59,6 +54,16 @@ public sealed class Profile : ISavable public string Incognito => UniqueId.ToString()[..8]; + public string Identifier + => UniqueId.ToString(); + + public DataPath Path { get; } = new(); + + public IFileSystemData? Node { get; set; } + + string IFileSystemValue.DisplayName + => Name.Text; + public Profile() { _localId = _nextGlobalId++; @@ -90,7 +95,7 @@ public override string ToString() #region Serialization - public new JObject JsonSerialize() + public JObject JsonSerialize() { var ret = new JObject() { @@ -153,11 +158,33 @@ public void Save(StreamWriter writer) Formatting = Formatting.Indented, }; var obj = JsonSerialize(); + WriteFileSystemPath(obj); obj.WriteTo(j); } public string LogName(string fileName) - => Path.GetFileNameWithoutExtension(fileName); + => global::System.IO.Path.GetFileNameWithoutExtension(fileName); #endregion + + internal static void ReadFileSystemPath(JObject obj, DataPath path) + { + if (obj["FileSystemPath"] is not JObject pathObj) + return; + + path.Folder = pathObj["Folder"]?.ToObject() ?? string.Empty; + path.SortName = pathObj["SortName"]?.ToObject(); + } + + private void WriteFileSystemPath(JObject obj) + { + if (Path.IsDefault) + return; + + obj["FileSystemPath"] = new JObject + { + ["Folder"] = Path.Folder, + ["SortName"] = Path.SortName, + }; + } } \ No newline at end of file diff --git a/CustomizePlus/Profiles/Events/ProfileChanged.cs b/CustomizePlus/Profiles/Events/ProfileChanged.cs index 48c2c744..52d6ca3e 100644 --- a/CustomizePlus/Profiles/Events/ProfileChanged.cs +++ b/CustomizePlus/Profiles/Events/ProfileChanged.cs @@ -1,13 +1,15 @@ -using CustomizePlus.Profiles.Data; -using OtterGui.Classes; +using CustomizePlus.Profiles.Data; namespace CustomizePlus.Profiles.Events; /// /// Triggered when profile is changed /// -public sealed class ProfileChanged() : EventWrapper(nameof(ProfileChanged)) +public sealed class ProfileChanged(LunaLogger log) + : EventBase(nameof(ProfileChanged), log) { + public readonly record struct Arguments(Type Type, Profile? Profile, object? Data); + public enum Type { Created, diff --git a/CustomizePlus/Profiles/ProfileFileSystem.cs b/CustomizePlus/Profiles/ProfileFileSystem.cs index 4c31bdf6..178d86bc 100644 --- a/CustomizePlus/Profiles/ProfileFileSystem.cs +++ b/CustomizePlus/Profiles/ProfileFileSystem.cs @@ -1,9 +1,3 @@ -using OtterGui.Filesystem; -using OtterGui.Log; -using System.IO; -using System.Text.RegularExpressions; -using System; -using OtterGui.Classes; using CustomizePlus.Core.Services; using CustomizePlus.Profiles.Data; using CustomizePlus.Profiles.Events; @@ -11,13 +5,12 @@ namespace CustomizePlus.Profiles; -public class ProfileFileSystem : FileSystem, IDisposable, ISavable +public sealed class ProfileFileSystem : BaseFileSystem, IDisposable { private readonly ProfileManager _profileManager; - private readonly SaveService _saveService; private readonly ProfileChanged _profileChanged; private readonly MessageService _messageService; - private readonly Logger _logger; + private readonly FileSystemSaveService _saver; public ProfileFileSystem( ProfileManager profileManager, @@ -25,32 +18,48 @@ public ProfileFileSystem( ProfileChanged profileChanged, MessageService messageService, Logger logger) + : base("ProfileFileSystem", logger, true) { _profileManager = profileManager; - _saveService = saveService; _profileChanged = profileChanged; _messageService = messageService; - _logger = logger; + _saver = new FileSystemSaveService( + logger, + this, + saveService, + _profileManager.Profiles.Where(p => !p.IsTemporary), + ProfileFromIdentifier, + fileNames => fileNames.ProfileLockedNodes, + fileNames => fileNames.ProfileExpandedFolders, + fileNames => fileNames.ProfileSelectedNodes, + fileNames => fileNames.ProfileOrganization, + fileNames => fileNames.LegacyProfileSortOrder); _profileChanged.Subscribe(OnProfileChange, ProfileChanged.Priority.ProfileFileSystem); - - Changed += OnChange; - - Reload(); + _saver.Load(); } public void Dispose() { _profileChanged.Unsubscribe(OnProfileChange); + _saver.Dispose(); + Selection.Dispose(); } - private void OnProfileChange(ProfileChanged.Type type, Profile? profile, object? data) + private Profile? ProfileFromIdentifier(string identifier) + => Guid.TryParse(identifier, out var id) + ? _profileManager.Profiles.FirstOrDefault(profile => profile.UniqueId == id && !profile.IsTemporary) + : null; + + private void OnProfileChange(in ProfileChanged.Arguments args) { + var (type, profile, data) = args; switch (type) { - case ProfileChanged.Type.Created: + case ProfileChanged.Type.Created when profile is not null: var parent = Root; if (data is string path) + { try { parent = FindOrCreateAllFolders(path); @@ -59,71 +68,22 @@ private void OnProfileChange(ProfileChanged.Type type, Profile? profile, object? { _messageService.NotificationMessage(ex, $"Could not move profile to {path} because the folder could not be created.", NotificationType.Error); } + } - CreateDuplicateLeaf(parent, profile.Name.Text, profile); - + CreateDuplicateDataNode(parent, profile.Name.Text, profile); return; - case ProfileChanged.Type.Deleted: - if (TryGetValue(profile, out var leaf1)) - Delete(leaf1); + case ProfileChanged.Type.Deleted when profile?.Node is { } node: + Delete(node); return; case ProfileChanged.Type.ReloadedAll: - Reload(); + _saver.Load(); return; - case ProfileChanged.Type.Renamed when data is string oldName: - if (!TryGetValue(profile, out var leaf2)) - return; - + case ProfileChanged.Type.Renamed when profile?.Node is { } node && data is string oldName: var old = oldName.FixName(); - if (old == leaf2.Name || leaf2.Name.IsDuplicateName(out var baseName, out _) && baseName == old) - RenameWithDuplicates(leaf2, profile.Name); + var name = node.Name.ToString(); + if (old == name || (name.IsDuplicateName(out var baseName, out _) && baseName == old)) + RenameWithDuplicates(node, profile.Name.Text); return; } } - - private void Reload() - { - if (!File.Exists(_saveService.FileNames.ProfileFileSystem)) - { - _logger.Debug("WORKAROUND: saving filesystem file"); - _saveService.ImmediateSaveSync(this); - } - - if (Load(new FileInfo(_saveService.FileNames.ProfileFileSystem), _profileManager.Profiles, ProfileToIdentifier, ProfileToName)) - _saveService.ImmediateSave(this); - - _logger.Debug("Reloaded profile filesystem."); - } - - private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3) - { - if (type != FileSystemChangeType.Reload) - _saveService.QueueSave(this); - } - - // Used for saving and loading. - private static string ProfileToIdentifier(Profile profile) - => profile.UniqueId.ToString(); - - private static string ProfileToName(Profile profile) - => profile.Name.Text.FixName(); - - private static bool ProfileHasDefaultPath(Profile profile, string fullPath) - { - var regex = new Regex($@"^{Regex.Escape(ProfileToName(profile))}( \(\d+\))?$"); - return regex.IsMatch(fullPath); - } - - private static (string, bool) SaveProfile(Profile profile, string fullPath) - // Only save pairs with non-default paths. - => ProfileHasDefaultPath(profile, fullPath) - ? (string.Empty, false) - : (ProfileToIdentifier(profile), true); - - public string ToFilename(FilenameService fileNames) => fileNames.ProfileFileSystem; - - public void Save(StreamWriter writer) - { - SaveToFile(writer, SaveProfile, true); - } } diff --git a/CustomizePlus/Profiles/ProfileManager.ProfileLoading.cs b/CustomizePlus/Profiles/ProfileManager.ProfileLoading.cs index 64a590cc..62da8208 100644 --- a/CustomizePlus/Profiles/ProfileManager.ProfileLoading.cs +++ b/CustomizePlus/Profiles/ProfileManager.ProfileLoading.cs @@ -1,16 +1,11 @@ -using CustomizePlus.Profiles.Data; +using CustomizePlus.Profiles.Data; using CustomizePlus.Profiles.Events; using CustomizePlus.Templates.Data; using Dalamud.Game.ClientState.Objects.Enums; using Newtonsoft.Json.Linq; -using OtterGui.Classes; using Penumbra.GameData.Actors; using Penumbra.GameData.Structs; using Penumbra.String; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; namespace CustomizePlus.Profiles; @@ -73,7 +68,7 @@ public void LoadProfiles() $"Moved {invalidNames.Count - failed} profiles to correct names.{(failed > 0 ? $" Failed to move {failed} profiles to correct names." : string.Empty)}"); _logger.Information("Profiles load complete"); - _event.Invoke(ProfileChanged.Type.ReloadedAll, null, null); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.ReloadedAll, null, null)); } private Profile LoadIndividualProfile(JObject obj) @@ -105,10 +100,10 @@ private Profile LoadV4(JObject obj) var currentPlayer = _actorManager.GetCurrentPlayer(); profile.Characters.Add(_actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.Companion, new NpcId(id))); } - else if (_reverseNameDicts.TryGetID(ObjectKind.MountType, characterName, out id)) + else if (_reverseNameDicts.TryGetID(ObjectKind.Mount, characterName, out id)) { var currentPlayer = _actorManager.GetCurrentPlayer(); - profile.Characters.Add(_actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.MountType, new NpcId(id))); + profile.Characters.Add(_actorManager.CreateOwned(currentPlayer.PlayerName, currentPlayer.HomeWorld, ObjectKind.Mount, new NpcId(id))); } else if (_reverseNameDicts.TryGetID(ObjectKind.EventNpc, characterName, out id)) profile.Characters.Add(_actorManager.CreateNpc(ObjectKind.EventNpc, new NpcId(id))); @@ -180,6 +175,8 @@ private Profile LoadProfileV4V5(JObject obj) if (profile.ModifiedDate < creationDate) profile.ModifiedDate = creationDate; + Profile.ReadFileSystemPath(obj, profile.Path); + if (obj["Templates"] is not JArray templateArray) return profile; diff --git a/CustomizePlus/Profiles/ProfileManager.cs b/CustomizePlus/Profiles/ProfileManager.cs index 79a56d75..6e4590b7 100644 --- a/CustomizePlus/Profiles/ProfileManager.cs +++ b/CustomizePlus/Profiles/ProfileManager.cs @@ -1,29 +1,22 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using OtterGui.Log; -using OtterGui.Filesystem; -using Penumbra.GameData.Actors; -using CustomizePlus.Core.Services; -using CustomizePlus.Core.Helpers; using CustomizePlus.Armatures.Events; using CustomizePlus.Configuration.Data; -using CustomizePlus.Armatures.Data; +using CustomizePlus.Configuration.Services; using CustomizePlus.Core.Events; -using CustomizePlus.Templates; -using CustomizePlus.Profiles.Data; -using CustomizePlus.Templates.Events; -using CustomizePlus.Profiles.Events; -using CustomizePlus.Templates.Data; +using CustomizePlus.Core.Helpers; +using CustomizePlus.Core.Services; +using CustomizePlus.Game.Services; using CustomizePlus.GameData.Data; using CustomizePlus.GameData.Extensions; +using CustomizePlus.Profiles.Data; using CustomizePlus.Profiles.Enums; +using CustomizePlus.Profiles.Events; using CustomizePlus.Profiles.Exceptions; +using CustomizePlus.Templates; +using CustomizePlus.Templates.Data; +using CustomizePlus.Templates.Events; +using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using CustomizePlus.Game.Services; -using OtterGui.Classes; namespace CustomizePlus.Profiles; @@ -37,6 +30,7 @@ public partial class ProfileManager : IDisposable private readonly SaveService _saveService; private readonly Logger _logger; private readonly PluginConfiguration _configuration; + private readonly ConfigurationService _configurationService; private readonly ActorManager _actorManager; private readonly GameObjectService _gameObjectService; private readonly ActorObjectManager _objectManager; @@ -58,6 +52,7 @@ public ProfileManager( SaveService saveService, Logger logger, PluginConfiguration configuration, + ConfigurationService configurationService, ActorManager actorManager, GameObjectService gameObjectService, ActorObjectManager objectManager, @@ -73,6 +68,7 @@ public ProfileManager( _saveService = saveService; _logger = logger; _configuration = configuration; + _configurationService = configurationService; _actorManager = actorManager; _gameObjectService = gameObjectService; _objectManager = objectManager; @@ -120,7 +116,7 @@ public Profile Create(string name, bool handlePath) _saveService.ImmediateSave(profile); - _event.Invoke(ProfileChanged.Type.Created, profile, path); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.Created, profile, path)); return profile; } @@ -146,7 +142,7 @@ public Profile Clone(Profile clone, string name, bool handlePath) _saveService.ImmediateSave(profile); - _event.Invoke(ProfileChanged.Type.Created, profile, path); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.Created, profile, path)); return profile; } @@ -167,7 +163,7 @@ public void Rename(Profile profile, string newName) SaveProfile(profile); _logger.Debug($"Renamed profile {profile.UniqueId}."); - _event.Invoke(ProfileChanged.Type.Renamed, profile, oldName); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.Renamed, profile, oldName)); } /// @@ -183,7 +179,7 @@ public bool AddCharacter(Profile profile, ActorIdentifier actorIdentifier) SaveProfile(profile); _logger.Debug($"Add character for profile {profile.UniqueId}."); - _event.Invoke(ProfileChanged.Type.AddedCharacter, profile, actorIdentifier); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.AddedCharacter, profile, actorIdentifier)); return true; } @@ -201,7 +197,7 @@ public bool DeleteCharacter(Profile profile, ActorIdentifier actorIdentifier) SaveProfile(profile); _logger.Debug($"Removed character from profile {profile.UniqueId}."); - _event.Invoke(ProfileChanged.Type.RemovedCharacter, profile, actorIdentifier); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.RemovedCharacter, profile, actorIdentifier)); return true; } @@ -214,7 +210,7 @@ public void Delete(Profile profile) { Profiles.Remove(profile); _saveService.ImmediateDelete(profile); - _event.Invoke(ProfileChanged.Type.Deleted, profile, null); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.Deleted, profile, null)); } /// @@ -230,7 +226,7 @@ public void SetWriteProtection(Profile profile, bool value) SaveProfile(profile); _logger.Debug($"Set profile {profile.UniqueId} to {(value ? string.Empty : "no longer be ")} write-protected."); - _event.Invoke(ProfileChanged.Type.WriteProtection, profile, value); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.WriteProtection, profile, value)); } public void SetEnabled(Profile profile, bool value, bool force = false) @@ -242,7 +238,7 @@ public void SetEnabled(Profile profile, bool value, bool force = false) SaveProfile(profile); - _event.Invoke(ProfileChanged.Type.Toggled, profile, value); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.Toggled, profile, value)); } public void SetEnabled(Guid profileId, bool value) @@ -277,7 +273,7 @@ public void SetPriority(Profile profile, int value) SaveProfile(profile); - _event.Invoke(ProfileChanged.Type.PriorityChanged, profile, value); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.PriorityChanged, profile, value)); } public void DeleteTemplate(Profile profile, int templateIndex) @@ -289,7 +285,7 @@ public void DeleteTemplate(Profile profile, int templateIndex) SaveProfile(profile); - _event.Invoke(ProfileChanged.Type.RemovedTemplate, profile, template); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.RemovedTemplate, profile, template)); } public void AddTemplate(Profile profile, Template template) @@ -303,7 +299,7 @@ public void AddTemplate(Profile profile, Template template) _logger.Debug($"Added template: {template.UniqueId} to {profile.UniqueId}"); - _event.Invoke(ProfileChanged.Type.AddedTemplate, profile, template); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.AddedTemplate, profile, template)); } public void ChangeTemplate(Profile profile, int index, Template newTemplate) @@ -320,7 +316,7 @@ public void ChangeTemplate(Profile profile, int index, Template newTemplate) SaveProfile(profile); _logger.Debug($"Changed template on profile {profile.UniqueId} from {oldTemplate.UniqueId} to {newTemplate.UniqueId}"); - _event.Invoke(ProfileChanged.Type.ChangedTemplate, profile, (index, oldTemplate, newTemplate)); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.ChangedTemplate, profile, (index, oldTemplate, newTemplate))); } public void MoveTemplate(Profile profile, int fromIndex, int toIndex) @@ -331,7 +327,7 @@ public void MoveTemplate(Profile profile, int fromIndex, int toIndex) SaveProfile(profile); _logger.Debug($"Moved template {fromIndex + 1} to position {toIndex + 1}."); - _event.Invoke(ProfileChanged.Type.MovedTemplate, profile, (fromIndex, toIndex)); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.MovedTemplate, profile, (fromIndex, toIndex))); } public void ToggleTemplate(Profile profile, int index) @@ -351,7 +347,7 @@ public void ToggleTemplate(Profile profile, int index) SaveProfile(profile); _logger.Debug($"Toggled template {template.UniqueId} on profile {profile.UniqueId}"); - _event.Invoke(eventType, profile, template); + _event.Invoke(new ProfileChanged.Arguments(eventType, profile, template)); } public bool EnableTemplate(Profile profile, Guid templateId) @@ -371,7 +367,7 @@ public bool EnableTemplate(Profile profile, Guid templateId) SaveProfile(profile); _logger.Debug($"Enable template {templateId} on profile {profile.UniqueId}"); - _event.Invoke(ProfileChanged.Type.EnabledTemplate, profile, template); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.EnabledTemplate, profile, template)); return true; } @@ -392,7 +388,7 @@ public bool DisableTemplate(Profile profile, Guid templateId) SaveProfile(profile); _logger.Debug($"Disable template {templateId} on profile {profile.UniqueId}"); - _event.Invoke(ProfileChanged.Type.DisabledTemplate, profile, template); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.DisabledTemplate, profile, template)); return true; } @@ -410,10 +406,10 @@ public void SetDefaultProfile(Profile? profile) DefaultProfile = profile; _configuration.DefaultProfile = profile?.UniqueId ?? Guid.Empty; - _configuration.Save(); + _configurationService.Save(PluginConfigurationChange.General); _logger.Debug($"Set profile {profile?.Incognito ?? "no profile"} as default"); - _event.Invoke(ProfileChanged.Type.ChangedDefaultProfile, profile, previousProfile); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.ChangedDefaultProfile, profile, previousProfile)); } public void SetDefaultLocalPlayerProfile(Profile? profile) @@ -430,10 +426,10 @@ public void SetDefaultLocalPlayerProfile(Profile? profile) DefaultLocalPlayerProfile = profile; _configuration.DefaultLocalPlayerProfile = profile?.UniqueId ?? Guid.Empty; - _configuration.Save(); + _configurationService.Save(PluginConfigurationChange.General); _logger.Debug($"Set profile {profile?.Incognito ?? "no profile"} as default local player profile"); - _event.Invoke(ProfileChanged.Type.ChangedDefaultLocalPlayerProfile, profile, previousProfile); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.ChangedDefaultLocalPlayerProfile, profile, previousProfile)); } //warn: temporary profile system does not support any world identifiers @@ -455,13 +451,13 @@ public void AddTemporaryProfile(Profile profile, Actor actor) { _logger.Debug($"Temporary profile for {permanentIdentifier.Incognito(null)} already exists, removing..."); Profiles.Remove(existingProfile); - _event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, existingProfile, null); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.TemporaryProfileDeleted, existingProfile, null)); } Profiles.Add(profile); _logger.Debug($"Added temporary profile for {permanentIdentifier}"); - _event.Invoke(ProfileChanged.Type.TemporaryProfileAdded, profile, null); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.TemporaryProfileAdded, profile, null)); } public void RemoveTemporaryProfile(Profile profile) @@ -474,7 +470,7 @@ public void RemoveTemporaryProfile(Profile profile) _logger.Debug($"Removed temporary profile for {profile.Characters[0].Incognito(null)}"); - _event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.TemporaryProfileDeleted, profile, null)); } public void RemoveTemporaryProfile(Guid profileId) @@ -609,8 +605,9 @@ private void SaveProfile(Profile profile) _saveService.QueueSave(profile); } - private void OnTemplateChange(TemplateChanged.Type type, Template? template, object? arg3) + private void OnTemplateChange(in TemplateChanged.Arguments args) { + var (type, template, arg3) = args; if (type is not TemplateChanged.Type.Deleted) return; @@ -623,7 +620,7 @@ private void OnTemplateChange(TemplateChanged.Type type, Template? template, obj profile.Templates.RemoveAt(i--); - _event.Invoke(ProfileChanged.Type.RemovedTemplate, profile, template); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.RemovedTemplate, profile, template)); SaveProfile(profile); @@ -634,8 +631,9 @@ private void OnTemplateChange(TemplateChanged.Type type, Template? template, obj return; } - private void OnReload(ReloadEvent.Type type) + private void OnReload(in ReloadEvent.Arguments args) { + var type = args.Type; if (type != ReloadEvent.Type.ReloadProfiles && type != ReloadEvent.Type.ReloadAll) return; @@ -645,8 +643,9 @@ private void OnReload(ReloadEvent.Type type) } - private void OnArmatureChange(ArmatureChanged.Type type, Armature armature, object? arg3) + private void OnArmatureChange(in ArmatureChanged.Arguments args) { + var (type, armature, arg3) = args; if (type == ArmatureChanged.Type.Deleted) { //hack: sending TemporaryProfileDeleted will result in OnArmatureChange being sent @@ -673,7 +672,7 @@ private void OnArmatureChange(ArmatureChanged.Type type, Armature armature, obje _logger.Debug($"ProfileManager.OnArmatureChange: Removed unused temporary profile for {profile.Characters[0].Incognito(null)}"); - _event.Invoke(ProfileChanged.Type.TemporaryProfileDeleted, profile, null); + _event.Invoke(new ProfileChanged.Arguments(ProfileChanged.Type.TemporaryProfileDeleted, profile, null)); } } diff --git a/CustomizePlus/Templates/Data/Template.cs b/CustomizePlus/Templates/Data/Template.cs index c394e04e..e8c67e28 100644 --- a/CustomizePlus/Templates/Data/Template.cs +++ b/CustomizePlus/Templates/Data/Template.cs @@ -1,13 +1,9 @@ -using CustomizePlus.Api.Data; +using CustomizePlus.Api.Data; using CustomizePlus.Core.Data; using CustomizePlus.Core.Extensions; using CustomizePlus.Core.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Classes; -using System; -using System.Collections.Generic; -using System.IO; namespace CustomizePlus.Templates.Data; @@ -15,7 +11,7 @@ namespace CustomizePlus.Templates.Data; /// Encapsulates the user-controlled aspects of a template, ie all of /// the information that gets saved to disk by the plugin. /// -public sealed class Template : ISavable +public sealed class Template : ISavable, IFileSystemValue