From 0268546f634483b09a1fd952fdaa11e5811e203e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 14 Apr 2024 15:07:31 +0200 Subject: [PATCH] Rework API/IPC and use new Penumbra IPC. --- Glamourer/Api/Api/IGlamourerApi.cs | 8 + Glamourer/Api/Api/IGlamourerApiBase.cs | 6 + Glamourer/Api/Api/IGlamourerApiDesigns.cs | 12 + Glamourer/Api/Api/IGlamourerApiItems.cs | 9 + Glamourer/Api/Api/IGlamourerApiState.cs | 28 ++ Glamourer/Api/ApiHelpers.cs | 124 +++++++ Glamourer/Api/DesignsApi.cs | 69 ++++ Glamourer/Api/Enums/ApplyFlag.cs | 34 ++ Glamourer/Api/Enums/GlamourerApiEc.cs | 13 + Glamourer/Api/GlamourerApi.cs | 22 ++ Glamourer/Api/GlamourerIpc.ApiVersions.cs | 25 -- Glamourer/Api/GlamourerIpc.Apply.cs | 180 ---------- Glamourer/Api/GlamourerIpc.Data.cs | 17 - Glamourer/Api/GlamourerIpc.Events.cs | 27 -- .../Api/GlamourerIpc.GetCustomization.cs | 64 ---- Glamourer/Api/GlamourerIpc.Revert.cs | 135 ------- Glamourer/Api/GlamourerIpc.Set.cs | 105 ------ Glamourer/Api/GlamourerIpc.cs | 196 ----------- Glamourer/Api/IpcProviders.cs | 56 +++ Glamourer/Api/IpcSubscribers/Designs.cs | 52 +++ Glamourer/Api/IpcSubscribers/Items.cs | 39 +++ Glamourer/Api/IpcSubscribers/PluginState.cs | 51 +++ Glamourer/Api/IpcSubscribers/State.cs | 235 +++++++++++++ Glamourer/Api/ItemsApi.cs | 82 +++++ Glamourer/Api/StateApi.cs | 328 ++++++++++++++++++ Glamourer/Designs/Design.cs | 4 +- Glamourer/Designs/DesignConverter.cs | 39 ++- Glamourer/Glamourer.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 3 +- .../DebugTab/IpcTester/DesignIpcTester.cs | 78 +++++ .../DebugTab/IpcTester/IpcTesterHelpers.cs | 60 ++++ .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 271 +++++++++++++++ .../Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 66 ++++ .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 184 ++++++++++ Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 244 ------------- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 63 ++-- .../Gui/Tabs/SettingsTab/CollectionCombo.cs | 36 ++ .../SettingsTab/CollectionOverrideDrawer.cs | 149 +++++--- .../Interop/Penumbra/ModSettingApplier.cs | 19 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 8 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 206 ++++++----- .../Services/CollectionOverrideService.cs | 108 ++++-- Glamourer/Services/CommandService.cs | 9 +- Glamourer/Services/ItemManager.cs | 24 +- Glamourer/Services/ServiceManager.cs | 9 +- Glamourer/State/StateListener.cs | 4 +- 46 files changed, 2270 insertions(+), 1233 deletions(-) create mode 100644 Glamourer/Api/Api/IGlamourerApi.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiBase.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiDesigns.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiItems.cs create mode 100644 Glamourer/Api/Api/IGlamourerApiState.cs create mode 100644 Glamourer/Api/ApiHelpers.cs create mode 100644 Glamourer/Api/DesignsApi.cs create mode 100644 Glamourer/Api/Enums/ApplyFlag.cs create mode 100644 Glamourer/Api/Enums/GlamourerApiEc.cs create mode 100644 Glamourer/Api/GlamourerApi.cs delete mode 100644 Glamourer/Api/GlamourerIpc.ApiVersions.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Apply.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Data.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Events.cs delete mode 100644 Glamourer/Api/GlamourerIpc.GetCustomization.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Revert.cs delete mode 100644 Glamourer/Api/GlamourerIpc.Set.cs delete mode 100644 Glamourer/Api/GlamourerIpc.cs create mode 100644 Glamourer/Api/IpcProviders.cs create mode 100644 Glamourer/Api/IpcSubscribers/Designs.cs create mode 100644 Glamourer/Api/IpcSubscribers/Items.cs create mode 100644 Glamourer/Api/IpcSubscribers/PluginState.cs create mode 100644 Glamourer/Api/IpcSubscribers/State.cs create mode 100644 Glamourer/Api/ItemsApi.cs create mode 100644 Glamourer/Api/StateApi.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs create mode 100644 Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs diff --git a/Glamourer/Api/Api/IGlamourerApi.cs b/Glamourer/Api/Api/IGlamourerApi.cs new file mode 100644 index 0000000..c28410b --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApi.cs @@ -0,0 +1,8 @@ +namespace Glamourer.Api.Api; + +public interface IGlamourerApi : IGlamourerApiBase +{ + public IGlamourerApiDesigns Designs { get; } + public IGlamourerApiItems Items { get; } + public IGlamourerApiState State { get; } +} \ No newline at end of file diff --git a/Glamourer/Api/Api/IGlamourerApiBase.cs b/Glamourer/Api/Api/IGlamourerApiBase.cs new file mode 100644 index 0000000..b52db45 --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiBase.cs @@ -0,0 +1,6 @@ +namespace Glamourer.Api.Api; + +public interface IGlamourerApiBase +{ + public (int Major, int Minor) ApiVersion { get; } +} diff --git a/Glamourer/Api/Api/IGlamourerApiDesigns.cs b/Glamourer/Api/Api/IGlamourerApiDesigns.cs new file mode 100644 index 0000000..f4d7184 --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiDesigns.cs @@ -0,0 +1,12 @@ +using Glamourer.Api.Enums; + +namespace Glamourer.Api.Api; + +public interface IGlamourerApiDesigns +{ + public Dictionary GetDesignList(); + + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags); + + public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags); +} diff --git a/Glamourer/Api/Api/IGlamourerApiItems.cs b/Glamourer/Api/Api/IGlamourerApiItems.cs new file mode 100644 index 0000000..25bdcee --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiItems.cs @@ -0,0 +1,9 @@ +using Glamourer.Api.Enums; + +namespace Glamourer.Api.Api; + +public interface IGlamourerApiItems +{ + public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot apiSlot, ulong itemId, byte stain, uint key, ApplyFlag flags); + public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags); +} diff --git a/Glamourer/Api/Api/IGlamourerApiState.cs b/Glamourer/Api/Api/IGlamourerApiState.cs new file mode 100644 index 0000000..2443701 --- /dev/null +++ b/Glamourer/Api/Api/IGlamourerApiState.cs @@ -0,0 +1,28 @@ +using Glamourer.Api.Enums; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Api.Api; + +public interface IGlamourerApiState +{ + public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key); + public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key); + + public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags); + + public GlamourerApiEc ApplyStateName(object state, string objectName, uint key, ApplyFlag flags); + + public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags); + public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags); + + public GlamourerApiEc UnlockState(int objectIndex, uint key); + public GlamourerApiEc UnlockStateName(string objectName, uint key); + public int UnlockAll(uint key); + + public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags); + public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags); + + public event Action? StateChanged; + + public event Action? GPoseChanged; +} diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs new file mode 100644 index 0000000..cf67912 --- /dev/null +++ b/Glamourer/Api/ApiHelpers.cs @@ -0,0 +1,124 @@ +using Glamourer.Api.Enums; +using Glamourer.Designs; +using Glamourer.GameData; +using Glamourer.State; +using OtterGui; +using OtterGui.Log; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.String; +using ObjectManager = Glamourer.Interop.ObjectManager; + +namespace Glamourer.Api; + +public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService +{ + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal IEnumerable FindExistingStates(string actorName) + { + if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) + yield break; + + foreach (var state in stateManager.Values.Where(state + => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) + yield return state; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal GlamourerApiEc FindExistingState(int objectIndex, out ActorState? state) + { + var actor = objects[objectIndex]; + var identifier = actor.GetIdentifier(actors); + if (!identifier.IsValid) + { + state = null; + return GlamourerApiEc.ActorNotFound; + } + stateManager.TryGetValue(identifier, out state); + return GlamourerApiEc.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal ActorState? FindState(int objectIndex) + { + var actor = objects[objectIndex]; + var identifier = actor.GetIdentifier(actors); + if (identifier.IsValid && stateManager.GetOrCreate(identifier, actor, out var state)) + return state; + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags) + => (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch + { + ApplyFlag.Equipment => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, 0, CrestExtensions.All, 0), + ApplyFlag.Customization => design.TemporarilyRestrictApplication(0, CustomizeFlagExtensions.All, 0, + CustomizeParameterExtensions.All), + ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, + CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All), + _ => design.TemporarilyRestrictApplication(0, 0, 0, 0), + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static void Lock(ActorState state, uint key, ApplyFlag flags) + { + if ((flags & ApplyFlag.Lock) != 0 && key != 0) + state.Lock(key); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal IEnumerable FindStates(string objectName) + { + if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString)) + return []; + + objects.Update(); + + return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString) + .Concat(objects.Identifiers + .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString) + .SelectWhere(kvp => + { + if (stateManager.ContainsKey(kvp.Key)) + return (false, null); + + var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state); + return (ret, state); + })); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static GlamourerApiEc Return(GlamourerApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown") + { + if (ec is GlamourerApiEc.Success or GlamourerApiEc.NothingDone) + Glamourer.Log.Verbose($"[{name}] Called with {args}, returned {ec}."); + else + Glamourer.Log.Debug($"[{name}] Called with {args}, returned {ec}."); + return ec; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static LazyString Args(params object[] arguments) + { + if (arguments.Length == 0) + return new LazyString(() => "no arguments"); + + return new LazyString(() => + { + var sb = new StringBuilder(); + for (var i = 0; i < arguments.Length / 2; ++i) + { + sb.Append(arguments[2 * i]); + sb.Append(" = "); + sb.Append(arguments[2 * i + 1]); + sb.Append(", "); + } + + return sb.ToString(0, sb.Length - 2); + }); + } +} diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs new file mode 100644 index 0000000..16e0ca9 --- /dev/null +++ b/Glamourer/Api/DesignsApi.cs @@ -0,0 +1,69 @@ +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Glamourer.Designs; +using Glamourer.State; +using OtterGui.Services; + +namespace Glamourer.Api; + +public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager) : IGlamourerApiDesigns, IApiService +{ + public Dictionary GetDesignList() + => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); + + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags); + var design = designs.Designs.ByIdentifier(designId); + if (design == null) + return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args); + + if (helpers.FindState(objectIndex) is not { } state) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + ApplyDesign(state, design, key, flags); + ApiHelpers.Lock(state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + private void ApplyDesign(ActorState state, Design design, uint key, ApplyFlag flags) + { + var once = (flags & ApplyFlag.Once) != 0; + var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, + ResetMaterials: !once && key != 0); + + using var restrict = ApiHelpers.Restrict(design, flags); + stateManager.ApplyDesign(state, design, settings); + } + + public GlamourerApiEc ApplyDesignName(Guid designId, string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Design", designId, "Name", objectName, "Key", key, "Flags", flags); + var design = designs.Designs.ByIdentifier(designId); + if (design == null) + return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args); + + var any = false; + var anyUnlocked = false; + foreach (var state in helpers.FindStates(objectName)) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + ApplyDesign(state, design, key, flags); + ApiHelpers.Lock(state, key, flags); + } + + if (!any) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } +} diff --git a/Glamourer/Api/Enums/ApplyFlag.cs b/Glamourer/Api/Enums/ApplyFlag.cs new file mode 100644 index 0000000..0008e96 --- /dev/null +++ b/Glamourer/Api/Enums/ApplyFlag.cs @@ -0,0 +1,34 @@ +namespace Glamourer.Api.Enums; + +[Flags] +public enum ApplyFlag : ulong +{ + Once = 0x01, + Equipment = 0x02, + Customization = 0x04, + Lock = 0x08, +} + +public static class ApplyFlagEx +{ + public const ApplyFlag DesignDefault = ApplyFlag.Once | ApplyFlag.Equipment | ApplyFlag.Customization; + public const ApplyFlag StateDefault = ApplyFlag.Equipment | ApplyFlag.Customization | ApplyFlag.Lock; + public const ApplyFlag RevertDefault = ApplyFlag.Equipment | ApplyFlag.Customization; +} + +public enum ApiEquipSlot : byte +{ + Unknown = 0, + MainHand = 1, + OffHand = 2, + Head = 3, + Body = 4, + Hands = 5, + Legs = 7, + Feet = 8, + Ears = 9, + Neck = 10, + Wrists = 11, + RFinger = 12, + LFinger = 14, // Not officially existing, means "weapon could be equipped in either hand" for the game. +} \ No newline at end of file diff --git a/Glamourer/Api/Enums/GlamourerApiEc.cs b/Glamourer/Api/Enums/GlamourerApiEc.cs new file mode 100644 index 0000000..086a2a5 --- /dev/null +++ b/Glamourer/Api/Enums/GlamourerApiEc.cs @@ -0,0 +1,13 @@ +namespace Glamourer.Api.Enums; + +public enum GlamourerApiEc +{ + Success, + ActorNotFound, + ActorNotHuman, + DesignNotFound, + ItemInvalid, + InvalidKey, + InvalidState, + NothingDone, +} diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs new file mode 100644 index 0000000..98461c6 --- /dev/null +++ b/Glamourer/Api/GlamourerApi.cs @@ -0,0 +1,22 @@ +using Glamourer.Api.Api; +using OtterGui.Services; + +namespace Glamourer.Api; + +public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService +{ + public const int CurrentApiVersionMajor = 1; + public const int CurrentApiVersionMinor = 0; + + public (int Major, int Minor) ApiVersion + => (CurrentApiVersionMajor, CurrentApiVersionMinor); + + public IGlamourerApiDesigns Designs + => designs; + + public IGlamourerApiItems Items + => items; + + public IGlamourerApiState State + => state; +} diff --git a/Glamourer/Api/GlamourerIpc.ApiVersions.cs b/Glamourer/Api/GlamourerIpc.ApiVersions.cs deleted file mode 100644 index 25aaf57..0000000 --- a/Glamourer/Api/GlamourerIpc.ApiVersions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Dalamud.Plugin; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelApiVersion = "Glamourer.ApiVersion"; - public const string LabelApiVersions = "Glamourer.ApiVersions"; - - private readonly FuncProvider _apiVersionProvider; - private readonly FuncProvider<(int Major, int Minor)> _apiVersionsProvider; - - public static FuncSubscriber ApiVersionSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApiVersion); - - public static FuncSubscriber<(int Major, int Minor)> ApiVersionsSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApiVersions); - - public int ApiVersion() - => CurrentApiVersionMajor; - - public (int Major, int Minor) ApiVersions() - => (CurrentApiVersionMajor, CurrentApiVersionMinor); -} diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs deleted file mode 100644 index fecce92..0000000 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ /dev/null @@ -1,180 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Designs; -using Glamourer.Interop.Structs; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelApplyAll = "Glamourer.ApplyAll"; - public const string LabelApplyAllOnce = "Glamourer.ApplyAllOnce"; - public const string LabelApplyAllToCharacter = "Glamourer.ApplyAllToCharacter"; - public const string LabelApplyAllOnceToCharacter = "Glamourer.ApplyAllOnceToCharacter"; - public const string LabelApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment"; - public const string LabelApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter"; - public const string LabelApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization"; - public const string LabelApplyOnlyCustomizationToCharacter = "Glamourer.ApplyOnlyCustomizationToCharacter"; - - public const string LabelApplyAllLock = "Glamourer.ApplyAllLock"; - public const string LabelApplyAllToCharacterLock = "Glamourer.ApplyAllToCharacterLock"; - public const string LabelApplyOnlyEquipmentLock = "Glamourer.ApplyOnlyEquipmentLock"; - public const string LabelApplyOnlyEquipmentToCharacterLock = "Glamourer.ApplyOnlyEquipmentToCharacterLock"; - public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock"; - public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock"; - - public const string LabelApplyByGuid = "Glamourer.ApplyByGuid"; - public const string LabelApplyByGuidOnce = "Glamourer.ApplyByGuidOnce"; - public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter"; - public const string LabelApplyByGuidOnceToCharacter = "Glamourer.ApplyByGuidOnceToCharacter"; - - private readonly ActionProvider _applyAllProvider; - private readonly ActionProvider _applyAllOnceProvider; - private readonly ActionProvider _applyAllToCharacterProvider; - private readonly ActionProvider _applyAllOnceToCharacterProvider; - private readonly ActionProvider _applyOnlyEquipmentProvider; - private readonly ActionProvider _applyOnlyEquipmentToCharacterProvider; - private readonly ActionProvider _applyOnlyCustomizationProvider; - private readonly ActionProvider _applyOnlyCustomizationToCharacterProvider; - - private readonly ActionProvider _applyAllProviderLock; - private readonly ActionProvider _applyAllToCharacterProviderLock; - private readonly ActionProvider _applyOnlyEquipmentProviderLock; - private readonly ActionProvider _applyOnlyEquipmentToCharacterProviderLock; - private readonly ActionProvider _applyOnlyCustomizationProviderLock; - private readonly ActionProvider _applyOnlyCustomizationToCharacterProviderLock; - - private readonly ActionProvider _applyByGuidProvider; - private readonly ActionProvider _applyByGuidOnceProvider; - private readonly ActionProvider _applyByGuidToCharacterProvider; - private readonly ActionProvider _applyByGuidOnceToCharacterProvider; - - public static ActionSubscriber ApplyAllSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAll); - - public static ActionSubscriber ApplyAllOnceSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllOnce); - - public static ActionSubscriber ApplyAllToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllToCharacter); - - public static ActionSubscriber ApplyAllOnceToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllOnceToCharacter); - - public static ActionSubscriber ApplyOnlyEquipmentSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyEquipment); - - public static ActionSubscriber ApplyOnlyEquipmentToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyEquipmentToCharacter); - - public static ActionSubscriber ApplyOnlyCustomizationSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyCustomization); - - public static ActionSubscriber ApplyOnlyCustomizationToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyOnlyCustomizationToCharacter); - - public static ActionSubscriber ApplyByGuidSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuid); - - public static ActionSubscriber ApplyByGuidOnceSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidOnce); - - public static ActionSubscriber ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidToCharacter); - - public static ActionSubscriber ApplyByGuidOnceToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidOnceToCharacter); - - public static ActionSubscriber ApplyAllLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllLock); - - public static ActionSubscriber ApplyAllToCharacterLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyAllToCharacterLock); - - public void ApplyAll(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0); - - public void ApplyAllOnce(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0, true); - - public void ApplyAllToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0); - - public void ApplyAllOnceToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0, true); - - public void ApplyOnlyEquipment(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, 0); - - public void ApplyOnlyEquipmentToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, 0); - - public void ApplyOnlyCustomization(string base64, string characterName) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, 0); - - public void ApplyOnlyCustomizationToCharacter(string base64, Character? character) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, 0); - - - public void ApplyAllLock(string base64, string characterName, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, lockCode); - - public void ApplyAllToCharacterLock(string base64, Character? character, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, lockCode); - - public void ApplyOnlyEquipmentLock(string base64, string characterName, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, lockCode); - - public void ApplyOnlyEquipmentToCharacterLock(string base64, Character? character, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, lockCode); - - public void ApplyOnlyCustomizationLock(string base64, string characterName, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, lockCode); - - public void ApplyOnlyCustomizationToCharacterLock(string base64, Character? character, uint lockCode) - => ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, lockCode); - - - public void ApplyByGuid(Guid identifier, string characterName) - => ApplyDesignByGuid(identifier, FindActors(characterName), 0, false); - - public void ApplyByGuidOnce(Guid identifier, string characterName) - => ApplyDesignByGuid(identifier, FindActors(characterName), 0, true); - - public void ApplyByGuidToCharacter(Guid identifier, Character? character) - => ApplyDesignByGuid(identifier, FindActors(character), 0, false); - - public void ApplyByGuidOnceToCharacter(Guid identifier, Character? character) - => ApplyDesignByGuid(identifier, FindActors(character), 0, true); - - private void ApplyDesign(DesignBase? design, IEnumerable actors, byte version, uint lockCode, bool once = false) - { - if (design == null) - return; - - var hasModelId = version >= 3; - _objects.Update(); - foreach (var id in actors) - { - if (!_stateManager.TryGetValue(id, out var state)) - { - var data = _objects.TryGetValue(id, out var d) ? d : ActorData.Invalid; - if (!data.Valid || !_stateManager.GetOrCreate(id, data.Objects[0], out state)) - continue; - } - - if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode)) - { - _stateManager.ApplyDesign(state, design, - new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode, MergeLinks: true, ResetMaterials: !once && lockCode != 0)); - state.Lock(lockCode); - } - } - } - - private void ApplyDesignByGuid(Guid identifier, IEnumerable actors, uint lockCode, bool once) - => ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode, once); -} diff --git a/Glamourer/Api/GlamourerIpc.Data.cs b/Glamourer/Api/GlamourerIpc.Data.cs deleted file mode 100644 index c981899..0000000 --- a/Glamourer/Api/GlamourerIpc.Data.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Dalamud.Plugin; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelGetDesignList = "Glamourer.GetDesignList"; - - private readonly FuncProvider<(string Name, Guid Identifier)[]> _getDesignListProvider; - - public static FuncSubscriber<(string Name, Guid Identifier)[]> GetDesignListSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetDesignList); - - public (string Name, Guid Identifier)[] GetDesignList() - => _designManager.Designs.Select(x => (x.Name.Text, x.Identifier)).ToArray(); -} diff --git a/Glamourer/Api/GlamourerIpc.Events.cs b/Glamourer/Api/GlamourerIpc.Events.cs deleted file mode 100644 index 982e4a1..0000000 --- a/Glamourer/Api/GlamourerIpc.Events.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Interop.Structs; -using Glamourer.State; -using Penumbra.Api.Helpers; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelStateChanged = "Glamourer.StateChanged"; - public const string LabelGPoseChanged = "Glamourer.GPoseChanged"; - - private readonly GPoseService _gPose; - private readonly StateChanged _stateChangedEvent; - private readonly EventProvider> _stateChangedProvider; - private readonly EventProvider _gPoseChangedProvider; - - private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null) - { - foreach (var actor in actors.Objects) - _stateChangedProvider.Invoke(type, actor.Address, new Lazy(() => _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state)))); - } - - private void OnGPoseChanged(bool value) - => _gPoseChangedProvider.Invoke(value); -} diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs deleted file mode 100644 index d6caf45..0000000 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Designs; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelGetAllCustomization = "Glamourer.GetAllCustomization"; - public const string LabelGetAllCustomizationFromCharacter = "Glamourer.GetAllCustomizationFromCharacter"; - public const string LabelGetAllCustomizationLocked = "Glamourer.GetAllCustomizationLocked"; - public const string LabelGetAllCustomizationFromLockedCharacter = "Glamourer.GetAllCustomizationFromLockedCharacter"; - - private readonly FuncProvider _getAllCustomizationProvider; - private readonly FuncProvider _getAllCustomizationLockedProvider; - private readonly FuncProvider _getAllCustomizationFromCharacterProvider; - private readonly FuncProvider _getAllCustomizationFromLockedCharacterProvider; - - public static FuncSubscriber GetAllCustomizationSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomization); - - public static FuncSubscriber GetAllCustomizationFromCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomizationFromCharacter); - - public static FuncSubscriber GetAllCustomizationLockedSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomizationLocked); - - public static FuncSubscriber GetAllCustomizationFromLockedCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelGetAllCustomizationFromLockedCharacter); - - public string? GetAllCustomization(string characterName) - => GetCustomization(FindActors(characterName), 0); - - public string? GetAllCustomization(string characterName, uint lockCode) - => GetCustomization(FindActors(characterName), lockCode); - - public string? GetAllCustomizationFromCharacter(Character? character) - => GetCustomization(FindActors(character), 0); - - public string? GetAllCustomizationFromCharacter(Character? character, uint lockCode) - => GetCustomization(FindActors(character), lockCode); - - private string? GetCustomization(IEnumerable actors, uint lockCode) - { - var actor = actors.FirstOrDefault(ActorIdentifier.Invalid); - if (!actor.IsValid) - return null; - - if (!_stateManager.TryGetValue(actor, out var state)) - { - _objects.Update(); - if (!_objects.TryGetValue(actor, out var data) || !data.Valid) - return null; - if (!_stateManager.GetOrCreate(actor, data.Objects[0], out state)) - return null; - } - if (!state.CanUnlock(lockCode)) - return null; - - return _designConverter.ShareBase64(state, ApplicationRules.AllWithConfig(_config)); - } -} diff --git a/Glamourer/Api/GlamourerIpc.Revert.cs b/Glamourer/Api/GlamourerIpc.Revert.cs deleted file mode 100644 index 8c289e7..0000000 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelRevert = "Glamourer.Revert"; - public const string LabelRevertCharacter = "Glamourer.RevertCharacter"; - public const string LabelRevertLock = "Glamourer.RevertLock"; - public const string LabelRevertCharacterLock = "Glamourer.RevertCharacterLock"; - public const string LabelRevertToAutomation = "Glamourer.RevertToAutomation"; - public const string LabelRevertToAutomationCharacter = "Glamourer.RevertToAutomationCharacter"; - public const string LabelUnlock = "Glamourer.Unlock"; - public const string LabelUnlockName = "Glamourer.UnlockName"; - public const string LabelUnlockAll = "Glamourer.UnlockAll"; - - private readonly ActionProvider _revertProvider; - private readonly ActionProvider _revertCharacterProvider; - - private readonly ActionProvider _revertProviderLock; - private readonly ActionProvider _revertCharacterProviderLock; - - private readonly FuncProvider _revertToAutomationProvider; - private readonly FuncProvider _revertToAutomationCharacterProvider; - - private readonly FuncProvider _unlockNameProvider; - private readonly FuncProvider _unlockProvider; - - private readonly FuncProvider _unlockAllProvider; - - public static ActionSubscriber RevertSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevert); - - public static ActionSubscriber RevertCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertCharacter); - - public static ActionSubscriber RevertLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertLock); - - public static ActionSubscriber RevertCharacterLockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertCharacterLock); - - public static FuncSubscriber UnlockNameSubscriber(DalamudPluginInterface pi) - => new(pi, LabelUnlockName); - - public static FuncSubscriber UnlockSubscriber(DalamudPluginInterface pi) - => new(pi, LabelUnlock); - - public static FuncSubscriber UnlockAllSubscriber(DalamudPluginInterface pi) - => new(pi, LabelUnlockAll); - - public static FuncSubscriber RevertToAutomationSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertToAutomation); - - public static FuncSubscriber RevertToAutomationCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelRevertToAutomationCharacter); - - public void Revert(string characterName) - => Revert(FindActorsRevert(characterName), 0); - - public void RevertCharacter(Character? character) - => Revert(FindActors(character), 0); - - public void RevertLock(string characterName, uint lockCode) - => Revert(FindActorsRevert(characterName), lockCode); - - public void RevertCharacterLock(Character? character, uint lockCode) - => Revert(FindActors(character), lockCode); - - public bool Unlock(string characterName, uint lockCode) - => Unlock(FindActorsRevert(characterName), lockCode); - - public bool Unlock(Character? character, uint lockCode) - => Unlock(FindActors(character), lockCode); - - public int UnlockAll(uint lockCode) - { - var count = 0; - foreach (var state in _stateManager.Values) - if (state.Unlock(lockCode)) - ++count; - return count; - } - - public bool RevertToAutomation(string characterName, uint lockCode) - => RevertToAutomation(FindActorsRevert(characterName), lockCode); - - public bool RevertToAutomation(Character? character, uint lockCode) - => RevertToAutomation(FindActors(character), lockCode); - - private void Revert(IEnumerable actors, uint lockCode) - { - foreach (var id in actors) - { - if (_stateManager.TryGetValue(id, out var state)) - _stateManager.ResetState(state, StateSource.IpcFixed, lockCode); - } - } - - private bool Unlock(IEnumerable actors, uint lockCode) - { - var ret = false; - foreach (var id in actors) - { - if (_stateManager.TryGetValue(id, out var state)) - ret |= state.Unlock(lockCode); - } - - return ret; - } - - private bool RevertToAutomation(IEnumerable actors, uint lockCode) - { - var ret = false; - foreach (var id in actors) - { - if (_stateManager.TryGetValue(id, out var state)) - { - ret |= state.Unlock(lockCode); - if (_objects.TryGetValue(id, out var data)) - foreach (var obj in data.Objects) - { - _autoDesignApplier.ReapplyAutomation(obj, state.Identifier, state, true); - _stateManager.ReapplyState(obj, StateSource.IpcManual); - } - } - } - - return ret; - } -} diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs deleted file mode 100644 index 93428da..0000000 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Services; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public enum GlamourerErrorCode - { - Success, - ActorNotFound, - ActorNotHuman, - ItemInvalid, - } - - public const string LabelSetItem = "Glamourer.SetItem"; - public const string LabelSetItemOnce = "Glamourer.SetItemOnce"; - public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName"; - public const string LabelSetItemOnceByActorName = "Glamourer.SetItemOnceByActorName"; - - - private readonly FuncProvider _setItemProvider; - private readonly FuncProvider _setItemOnceProvider; - private readonly FuncProvider _setItemByActorNameProvider; - private readonly FuncProvider _setItemOnceByActorNameProvider; - - public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItem); - - public static FuncSubscriber SetItemOnceSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItemOnce); - - public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItemByActorName); - - public static FuncSubscriber SetItemOnceByActorNameSubscriber(DalamudPluginInterface pi) - => new(pi, LabelSetItemOnceByActorName); - - private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once) - { - if (itemId.Id == 0) - itemId = ItemManager.NothingId(slot); - - var item = _items.Resolve(slot, itemId); - if (!item.Valid) - return GlamourerErrorCode.ItemInvalid; - - var identifier = _actors.FromObject(character, false, false, false); - if (!identifier.IsValid) - return GlamourerErrorCode.ActorNotFound; - - if (!_stateManager.TryGetValue(identifier, out var state)) - { - _objects.Update(); - var data = _objects[identifier]; - if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state)) - return GlamourerErrorCode.ActorNotFound; - } - - if (!state.ModelData.IsHuman) - return GlamourerErrorCode.ActorNotHuman; - - _stateManager.ChangeEquip(state, slot, item, stainId, - new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key)); - return GlamourerErrorCode.Success; - } - - private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once) - { - if (itemId.Id == 0) - itemId = ItemManager.NothingId(slot); - - var item = _items.Resolve(slot, itemId); - if (!item.Valid) - return GlamourerErrorCode.ItemInvalid; - - var found = false; - _objects.Update(); - foreach (var identifier in FindActorsRevert(name).Distinct()) - { - if (!_stateManager.TryGetValue(identifier, out var state)) - { - var data = _objects[identifier]; - if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state)) - continue; - } - - if (!state.ModelData.IsHuman) - return GlamourerErrorCode.ActorNotHuman; - - _stateManager.ChangeEquip(state, slot, item, stainId, - new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key)); - found = true; - } - - return found ? GlamourerErrorCode.Success : GlamourerErrorCode.ActorNotFound; - } -} diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs deleted file mode 100644 index 428a5c7..0000000 --- a/Glamourer/Api/GlamourerIpc.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Automation; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Interop; -using Glamourer.Services; -using Glamourer.State; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; -using Penumbra.String; - -namespace Glamourer.Api; - -public sealed partial class GlamourerIpc : IDisposable -{ - public const int CurrentApiVersionMajor = 0; - public const int CurrentApiVersionMinor = 5; - - private readonly StateManager _stateManager; - private readonly ObjectManager _objects; - private readonly ActorManager _actors; - private readonly DesignConverter _designConverter; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly DesignManager _designManager; - private readonly ItemManager _items; - private readonly Configuration _config; - - public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors, - DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, - DesignManager designManager, ItemManager items, Configuration config) - { - _stateManager = stateManager; - _objects = objects; - _actors = actors; - _designConverter = designConverter; - _autoDesignApplier = autoDesignApplier; - _items = items; - _config = config; - _gPose = gPose; - _stateChangedEvent = stateChangedEvent; - _designManager = designManager; - _apiVersionProvider = new FuncProvider(pi, LabelApiVersion, ApiVersion); - _apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions); - - _getAllCustomizationProvider = new FuncProvider(pi, LabelGetAllCustomization, GetAllCustomization); - _getAllCustomizationFromCharacterProvider = - new FuncProvider(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter); - _getAllCustomizationLockedProvider = new FuncProvider(pi, LabelGetAllCustomizationLocked, GetAllCustomization); - _getAllCustomizationFromLockedCharacterProvider = - new FuncProvider(pi, LabelGetAllCustomizationFromLockedCharacter, GetAllCustomizationFromCharacter); - - _applyAllProvider = new ActionProvider(pi, LabelApplyAll, ApplyAll); - _applyAllOnceProvider = new ActionProvider(pi, LabelApplyAllOnce, ApplyAllOnce); - _applyAllToCharacterProvider = new ActionProvider(pi, LabelApplyAllToCharacter, ApplyAllToCharacter); - _applyAllOnceToCharacterProvider = new ActionProvider(pi, LabelApplyAllOnceToCharacter, ApplyAllOnceToCharacter); - _applyOnlyEquipmentProvider = new ActionProvider(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment); - _applyOnlyEquipmentToCharacterProvider = - new ActionProvider(pi, LabelApplyOnlyEquipmentToCharacter, ApplyOnlyEquipmentToCharacter); - _applyOnlyCustomizationProvider = new ActionProvider(pi, LabelApplyOnlyCustomization, ApplyOnlyCustomization); - _applyOnlyCustomizationToCharacterProvider = - new ActionProvider(pi, LabelApplyOnlyCustomizationToCharacter, ApplyOnlyCustomizationToCharacter); - - _applyAllProviderLock = new ActionProvider(pi, LabelApplyAllLock, ApplyAllLock); - _applyAllToCharacterProviderLock = - new ActionProvider(pi, LabelApplyAllToCharacterLock, ApplyAllToCharacterLock); - _applyOnlyEquipmentProviderLock = new ActionProvider(pi, LabelApplyOnlyEquipmentLock, ApplyOnlyEquipmentLock); - _applyOnlyEquipmentToCharacterProviderLock = - new ActionProvider(pi, LabelApplyOnlyEquipmentToCharacterLock, ApplyOnlyEquipmentToCharacterLock); - _applyOnlyCustomizationProviderLock = - new ActionProvider(pi, LabelApplyOnlyCustomizationLock, ApplyOnlyCustomizationLock); - _applyOnlyCustomizationToCharacterProviderLock = - new ActionProvider(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock); - - _applyByGuidProvider = new ActionProvider(pi, LabelApplyByGuid, ApplyByGuid); - _applyByGuidOnceProvider = new ActionProvider(pi, LabelApplyByGuidOnce, ApplyByGuidOnce); - _applyByGuidToCharacterProvider = new ActionProvider(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter); - _applyByGuidOnceToCharacterProvider = - new ActionProvider(pi, LabelApplyByGuidOnceToCharacter, ApplyByGuidOnceToCharacter); - - _revertProvider = new ActionProvider(pi, LabelRevert, Revert); - _revertCharacterProvider = new ActionProvider(pi, LabelRevertCharacter, RevertCharacter); - _revertProviderLock = new ActionProvider(pi, LabelRevertLock, RevertLock); - _revertCharacterProviderLock = new ActionProvider(pi, LabelRevertCharacterLock, RevertCharacterLock); - _unlockNameProvider = new FuncProvider(pi, LabelUnlockName, Unlock); - _unlockProvider = new FuncProvider(pi, LabelUnlock, Unlock); - _unlockAllProvider = new FuncProvider(pi, LabelUnlockAll, UnlockAll); - _revertToAutomationProvider = new FuncProvider(pi, LabelRevertToAutomation, RevertToAutomation); - _revertToAutomationCharacterProvider = - new FuncProvider(pi, LabelRevertToAutomationCharacter, RevertToAutomation); - - _stateChangedProvider = new EventProvider>(pi, LabelStateChanged); - _gPoseChangedProvider = new EventProvider(pi, LabelGPoseChanged); - - _setItemProvider = new FuncProvider(pi, LabelSetItem, - (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, false)); - _setItemOnceProvider = new FuncProvider(pi, LabelSetItemOnce, - (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, true)); - - _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, - (name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, false)); - _setItemOnceByActorNameProvider = new FuncProvider(pi, LabelSetItemOnceByActorName, - (name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, true)); - - _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); - _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); - - _getDesignListProvider = new FuncProvider<(string Name, Guid Identifier)[]>(pi, LabelGetDesignList, GetDesignList); - } - - public void Dispose() - { - _apiVersionProvider.Dispose(); - _apiVersionsProvider.Dispose(); - - _getAllCustomizationProvider.Dispose(); - _getAllCustomizationLockedProvider.Dispose(); - _getAllCustomizationFromCharacterProvider.Dispose(); - _getAllCustomizationFromLockedCharacterProvider.Dispose(); - - _applyAllProvider.Dispose(); - _applyAllOnceProvider.Dispose(); - _applyAllToCharacterProvider.Dispose(); - _applyAllOnceToCharacterProvider.Dispose(); - _applyOnlyEquipmentProvider.Dispose(); - _applyOnlyEquipmentToCharacterProvider.Dispose(); - _applyOnlyCustomizationProvider.Dispose(); - _applyOnlyCustomizationToCharacterProvider.Dispose(); - _applyAllProviderLock.Dispose(); - _applyAllToCharacterProviderLock.Dispose(); - _applyOnlyEquipmentProviderLock.Dispose(); - _applyOnlyEquipmentToCharacterProviderLock.Dispose(); - _applyOnlyCustomizationProviderLock.Dispose(); - _applyOnlyCustomizationToCharacterProviderLock.Dispose(); - - _applyByGuidProvider.Dispose(); - _applyByGuidOnceProvider.Dispose(); - _applyByGuidToCharacterProvider.Dispose(); - _applyByGuidOnceToCharacterProvider.Dispose(); - - _revertProvider.Dispose(); - _revertCharacterProvider.Dispose(); - _revertProviderLock.Dispose(); - _revertCharacterProviderLock.Dispose(); - _unlockNameProvider.Dispose(); - _unlockProvider.Dispose(); - _unlockAllProvider.Dispose(); - _revertToAutomationProvider.Dispose(); - _revertToAutomationCharacterProvider.Dispose(); - - _stateChangedEvent.Unsubscribe(OnStateChanged); - _stateChangedProvider.Dispose(); - _gPose.Unsubscribe(OnGPoseChanged); - _gPoseChangedProvider.Dispose(); - - _getDesignListProvider.Dispose(); - - _setItemProvider.Dispose(); - _setItemOnceProvider.Dispose(); - _setItemByActorNameProvider.Dispose(); - _setItemOnceByActorNameProvider.Dispose(); - } - - private IEnumerable FindActors(string actorName) - { - if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) - return []; - - _objects.Update(); - return _objects.Keys.Where(i => i is { IsValid: true, Type: IdentifierType.Player } && i.PlayerName == byteString); - } - - private IEnumerable FindActorsRevert(string actorName) - { - if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) - yield break; - - _objects.Update(); - foreach (var id in _objects.Keys.Where(i => i is { IsValid: true, Type: IdentifierType.Player } && i.PlayerName == byteString) - .Select(i => i)) - yield return id; - - foreach (var id in _stateManager.Keys.Where(s => s.Type is IdentifierType.Player && s.PlayerName == byteString)) - yield return id; - } - - private IEnumerable FindActors(Character? character) - { - var id = _actors.FromObject(character, true, true, false); - if (!id.IsValid) - yield break; - - yield return id; - } -} diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs new file mode 100644 index 0000000..115142b --- /dev/null +++ b/Glamourer/Api/IpcProviders.cs @@ -0,0 +1,56 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using OtterGui.Services; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api; + +public sealed class IpcProviders : IDisposable, IApiService +{ + private readonly List _providers; + + private readonly EventProvider _disposedProvider; + private readonly EventProvider _initializedProvider; + + public IpcProviders(DalamudPluginInterface pi, IGlamourerApi api) + { + _disposedProvider = IpcSubscribers.Disposed.Provider(pi); + _initializedProvider = IpcSubscribers.Initialized.Provider(pi); + _providers = + [ + IpcSubscribers.ApiVersion.Provider(pi, api), + + IpcSubscribers.GetDesignList.Provider(pi, api.Designs), + IpcSubscribers.ApplyDesign.Provider(pi, api.Designs), + IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs), + + IpcSubscribers.SetItem.Provider(pi, api.Items), + IpcSubscribers.SetItemName.Provider(pi, api.Items), + + IpcSubscribers.GetState.Provider(pi, api.State), + IpcSubscribers.GetStateName.Provider(pi, api.State), + IpcSubscribers.ApplyState.Provider(pi, api.State), + IpcSubscribers.ApplyStateName.Provider(pi, api.State), + IpcSubscribers.RevertState.Provider(pi, api.State), + IpcSubscribers.RevertStateName.Provider(pi, api.State), + IpcSubscribers.UnlockState.Provider(pi, api.State), + IpcSubscribers.UnlockStateName.Provider(pi, api.State), + IpcSubscribers.UnlockAll.Provider(pi, api.State), + IpcSubscribers.RevertToAutomation.Provider(pi, api.State), + IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), + IpcSubscribers.StateChanged.Provider(pi, api.State), + IpcSubscribers.GPoseChanged.Provider(pi, api.State), + ]; + _initializedProvider.Invoke(); + } + + public void Dispose() + { + foreach (var provider in _providers) + provider.Dispose(); + _providers.Clear(); + _initializedProvider.Dispose(); + _disposedProvider.Invoke(); + _disposedProvider.Dispose(); + } +} diff --git a/Glamourer/Api/IpcSubscribers/Designs.cs b/Glamourer/Api/IpcSubscribers/Designs.cs new file mode 100644 index 0000000..8b31f6e --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/Designs.cs @@ -0,0 +1,52 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class GetDesignList(DalamudPluginInterface pi) + : FuncSubscriber>(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(GetDesignList)}"; + + /// + public new Dictionary Invoke() + => base.Invoke(); + + /// Create a provider. + public static FuncProvider> Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) + => new(pi, Label, api.GetDesignList); +} + +/// +public sealed class ApplyDesign(DalamudPluginInterface pi) : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyDesign)}"; + + /// + public GlamourerApiEc Invoke(Guid designId, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault) + => (GlamourerApiEc)Invoke(designId, objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyDesign(a, b, c, (ApplyFlag)d)); +} + +/// +public sealed class ApplyDesignName(DalamudPluginInterface pi) : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyDesignName)}"; + + /// + public GlamourerApiEc Invoke(Guid designId, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.DesignDefault) + => (GlamourerApiEc)Invoke(designId, objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiDesigns api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyDesignName(a, b, c, (ApplyFlag)d)); +} diff --git a/Glamourer/Api/IpcSubscribers/Items.cs b/Glamourer/Api/IpcSubscribers/Items.cs new file mode 100644 index 0000000..2c9139c --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/Items.cs @@ -0,0 +1,39 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Penumbra.Api.Helpers; +using Penumbra.GameData.Enums; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class SetItem(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(SetItem)}"; + + /// + public GlamourerApiEc Invoke(int objectIndex, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once) + => (GlamourerApiEc)Invoke(objectIndex, (byte)slot, itemId, stain, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiItems api) + => new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItem(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f)); +} + +/// +public sealed class SetItemName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(SetItemName)}"; + + /// + public GlamourerApiEc Invoke(string objectName, EquipSlot slot, ulong itemId, byte stain, uint key = 0, ApplyFlag flags = ApplyFlag.Once) + => (GlamourerApiEc)Invoke(objectName, (byte)slot, itemId, stain, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiItems api) + => new(pi, Label, (a, b, c, d, e, f) => (int)api.SetItemName(a, (ApiEquipSlot)b, c, d, e, (ApplyFlag)f)); +} diff --git a/Glamourer/Api/IpcSubscribers/PluginState.cs b/Glamourer/Api/IpcSubscribers/PluginState.cs new file mode 100644 index 0000000..107c51a --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/PluginState.cs @@ -0,0 +1,51 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class ApiVersion(DalamudPluginInterface pi) + : FuncSubscriber<(int, int)>(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApiVersion)}"; + + /// + public new (int Major, int Minor) Invoke() + => base.Invoke(); + + /// Create a provider. + public static FuncProvider<(int, int)> Provider(DalamudPluginInterface pi, IGlamourerApiBase api) + => new(pi, Label, () => api.ApiVersion); +} + +/// Triggered when the Glamourer API is initialized and ready. +public static class Initialized +{ + /// The label. + public const string Label = $"Glamourer.{nameof(Initialized)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi) + => new(pi, Label); +} + +/// Triggered when the Glamourer API is fully disposed and unavailable. +public static class Disposed +{ + /// The label. + public const string Label = $"Glamourer.{nameof(Disposed)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi) + => new(pi, Label); +} diff --git a/Glamourer/Api/IpcSubscribers/State.cs b/Glamourer/Api/IpcSubscribers/State.cs new file mode 100644 index 0000000..e523c10 --- /dev/null +++ b/Glamourer/Api/IpcSubscribers/State.cs @@ -0,0 +1,235 @@ +using Dalamud.Plugin; +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Newtonsoft.Json.Linq; +using Penumbra.Api.Helpers; + +namespace Glamourer.Api.IpcSubscribers; + +/// +public sealed class GetState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(GetState)}"; + + /// + public new (GlamourerApiEc, JObject?) Invoke(int objectIndex, uint key = 0) + { + var (ec, data) = base.Invoke(objectIndex, key); + return ((GlamourerApiEc)ec, data); + } + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b) => + { + var (ec, data) = api.GetState(a, b); + return ((int)ec, data); + }); +} + +/// +public sealed class GetStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(GetStateName)}"; + + /// + public new (GlamourerApiEc, JObject?) Invoke(string objectName, uint key = 0) + { + var (ec, data) = base.Invoke(objectName, key); + return ((GlamourerApiEc)ec, data); + } + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (i, k) => + { + var (ec, data) = api.GetStateName(i, k); + return ((int)ec, data); + }); +} + +/// +public sealed class ApplyState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyState)}"; + + /// + public GlamourerApiEc Invoke(JObject state, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(state, objectIndex, key, (ulong)flags); + + /// + public GlamourerApiEc Invoke(string base64State, int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(base64State, objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyState(a, b, c, (ApplyFlag)d)); +} + +/// +public sealed class ApplyStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(ApplyStateName)}"; + + /// + public GlamourerApiEc Invoke(JObject state, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(state, objectName, key, (ulong)flags); + + /// + public GlamourerApiEc Invoke(string base64State, string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.StateDefault) + => (GlamourerApiEc)Invoke(base64State, objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c, d) => (int)api.ApplyStateName(a, b, c, (ApplyFlag)d)); +} + +/// +public sealed class RevertState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertState)}"; + + /// + public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertState(a, b, (ApplyFlag)c)); +} + +/// +public sealed class RevertStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertStateName)}"; + + /// + public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertStateName(a, b, (ApplyFlag)c)); +} + +/// +public sealed class UnlockState(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(UnlockState)}"; + + /// + public new GlamourerApiEc Invoke(int objectIndex, uint key = 0) + => (GlamourerApiEc)base.Invoke(objectIndex, key); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b) => (int)api.UnlockState(a, b)); +} + +/// +public sealed class UnlockStateName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(UnlockStateName)}"; + + /// + public new GlamourerApiEc Invoke(string objectName, uint key = 0) + => (GlamourerApiEc)base.Invoke(objectName, key); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b) => (int)api.UnlockStateName(a, b)); +} + +/// +public sealed class UnlockAll(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(UnlockAll)}"; + + /// + public new int Invoke(uint key) + => base.Invoke(key); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, api.UnlockAll); +} + +/// +public sealed class RevertToAutomation(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertToAutomation)}"; + + /// + public GlamourerApiEc Invoke(int objectIndex, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectIndex, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertToAutomation(a, b, (ApplyFlag)c)); +} + +/// +public sealed class RevertToAutomationName(DalamudPluginInterface pi) + : FuncSubscriber(pi, Label) +{ + /// The label. + public const string Label = $"Glamourer.{nameof(RevertToAutomationName)}"; + + /// + public GlamourerApiEc Invoke(string objectName, uint key = 0, ApplyFlag flags = ApplyFlagEx.RevertDefault) + => (GlamourerApiEc)Invoke(objectName, key, (ulong)flags); + + /// Create a provider. + public static FuncProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (a, b, c) => (int)api.RevertToAutomationName(a, b, (ApplyFlag)c)); +} + +/// +public static class StateChanged +{ + /// The label. + public const string Label = $"Penumbra.{nameof(StateChanged)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (t => api.StateChanged += t, t => api.StateChanged -= t)); +} + +/// +public static class GPoseChanged +{ + /// The label. + public const string Label = $"Penumbra.{nameof(GPoseChanged)}"; + + /// Create a new event subscriber. + public static EventSubscriber Subscriber(DalamudPluginInterface pi, params Action[] actions) + => new(pi, Label, actions); + + /// Create a provider. + public static EventProvider Provider(DalamudPluginInterface pi, IGlamourerApiState api) + => new(pi, Label, (t => api.GPoseChanged += t, t => api.GPoseChanged -= t)); +} diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs new file mode 100644 index 0000000..baea51a --- /dev/null +++ b/Glamourer/Api/ItemsApi.cs @@ -0,0 +1,82 @@ +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Glamourer.Designs; +using Glamourer.Services; +using Glamourer.State; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Api; + +public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager stateManager) : IGlamourerApiItems, IApiService +{ + public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); + if (!ResolveItem(slot, itemId, out var item)) + return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); + + if (helpers.FindState(objectIndex) is not { } state) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!state.ModelData.IsHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); + stateManager.ChangeEquip(state, (EquipSlot)slot, item, stain, settings); + ApiHelpers.Lock(state, key, flags); + return GlamourerApiEc.Success; + } + + public GlamourerApiEc SetItemName(string objectName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); + if (!ResolveItem(slot, itemId, out var item)) + return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); + + var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); + var anyHuman = false; + var anyFound = false; + var anyUnlocked = false; + foreach (var state in helpers.FindStates(objectName)) + { + anyFound = true; + if (!state.ModelData.IsHuman) + continue; + + anyHuman = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + stateManager.ChangeEquip(state, (EquipSlot)slot, item, stain, settings); + ApiHelpers.Lock(state, key, flags); + } + + if (!anyFound) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!anyHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + private bool ResolveItem(ApiEquipSlot apiSlot, ulong itemId, out EquipItem item) + { + var id = (CustomItemId)itemId; + var slot = (EquipSlot)apiSlot; + if (id.Id == 0) + id = ItemManager.NothingId(slot); + + item = itemManager.Resolve(slot, id); + return item.Valid; + } +} diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs new file mode 100644 index 0000000..d540c7c --- /dev/null +++ b/Glamourer/Api/StateApi.cs @@ -0,0 +1,328 @@ +using Glamourer.Api.Api; +using Glamourer.Api.Enums; +using Glamourer.Automation; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Interop.Structs; +using Glamourer.State; +using Newtonsoft.Json.Linq; +using OtterGui.Services; +using Penumbra.GameData.Interop; +using ObjectManager = Glamourer.Interop.ObjectManager; +using StateChanged = Glamourer.Events.StateChanged; + +namespace Glamourer.Api; + +public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable +{ + private readonly ApiHelpers _helpers; + private readonly StateManager _stateManager; + private readonly DesignConverter _converter; + private readonly Configuration _config; + private readonly AutoDesignApplier _autoDesigns; + private readonly ObjectManager _objects; + private readonly StateChanged _stateChanged; + private readonly GPoseService _gPose; + + public StateApi(ApiHelpers helpers, + StateManager stateManager, + DesignConverter converter, + Configuration config, + AutoDesignApplier autoDesigns, + ObjectManager objects, + StateChanged stateChanged, + GPoseService gPose) + { + _helpers = helpers; + _stateManager = stateManager; + _converter = converter; + _config = config; + _autoDesigns = autoDesigns; + _objects = objects; + _stateChanged = stateChanged; + _gPose = gPose; + _stateChanged.Subscribe(OnStateChange, Events.StateChanged.Priority.GlamourerIpc); + _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); + } + + public void Dispose() + { + _stateChanged.Unsubscribe(OnStateChange); + _gPose.Unsubscribe(OnGPoseChange); + } + + public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key) + => Convert(_helpers.FindState(objectIndex), key); + + public (GlamourerApiEc, JObject?) GetStateName(string objectName, uint key) + => Convert(_helpers.FindStates(objectName).FirstOrDefault(), key); + + public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); + if (Convert(applyState, flags, out var version) is not { } design) + return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); + + if (_helpers.FindState(objectIndex) is not { } state) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + if (version < 3 && state.ModelData.ModelId != 0) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + ApplyDesign(state, design, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc ApplyStateName(object applyState, string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); + if (Convert(applyState, flags, out var version) is not { } design) + return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); + + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + var anyHuman = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + if (version < 3 && state.ModelData.ModelId != 0) + continue; + + anyHuman = true; + ApplyDesign(state, design, key, flags); + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + if (!anyHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); + if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (state == null) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + Revert(state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc RevertStateName(string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + Revert(state, key, flags); + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc UnlockState(int objectIndex, uint key) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key); + if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (state == null) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!state.Unlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc UnlockStateName(string objectName, uint key) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key); + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + foreach (var state in states) + { + any = true; + anyUnlocked |= state.Unlock(key); + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public int UnlockAll(uint key) + => _stateManager.Values.Count(state => state.Unlock(key)); + + public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags); + if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (state == null) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + RevertToAutomation(_objects[objectIndex], state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc RevertToAutomationName(string objectName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", objectName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(objectName); + + var any = false; + var anyUnlocked = false; + var anyReverted = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + anyReverted |= RevertToAutomation(state, key, flags) is GlamourerApiEc.Success; + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyReverted) + ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public event Action? StateChanged; + public event Action? GPoseChanged; + + private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) + { + var once = (flags & ApplyFlag.Once) != 0; + var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, + ResetMaterials: !once && key != 0); + _stateManager.ApplyDesign(state, design, settings); + ApiHelpers.Lock(state, key, flags); + } + + private void Revert(ActorState state, uint key, ApplyFlag flags) + { + var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; + switch (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) + { + case ApplyFlag.Equipment: + _stateManager.ResetEquip(state, source, key); + break; + case ApplyFlag.Customization: + _stateManager.ResetCustomize(state, source, key); + break; + case ApplyFlag.Equipment | ApplyFlag.Customization: + _stateManager.ResetState(state, source, key); + break; + } + + ApiHelpers.Lock(state, key, flags); + } + + private GlamourerApiEc RevertToAutomation(ActorState state, uint key, ApplyFlag flags) + { + _objects.Update(); + if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid) + return GlamourerApiEc.ActorNotFound; + + foreach (var actor in actors.Objects) + RevertToAutomation(actor, state, key, flags); + + return GlamourerApiEc.Success; + } + + private void RevertToAutomation(Actor actor, ActorState state, uint key, ApplyFlag flags) + { + var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; + _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true); + _stateManager.ReapplyState(actor, state, source); + ApiHelpers.Lock(state, key, flags); + } + + private (GlamourerApiEc, JObject?) Convert(ActorState? state, uint key) + { + if (state == null) + return (GlamourerApiEc.ActorNotFound, null); + + if (!state.CanUnlock(key)) + return (GlamourerApiEc.InvalidKey, null); + + return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.AllWithConfig(_config))); + } + + private DesignBase? Convert(object? state, ApplyFlag flags, out byte version) + { + version = DesignConverter.Version; + return state switch + { + string s => _converter.FromBase64(s, (flags & ApplyFlag.Equipment) != 0, (flags & ApplyFlag.Customization) != 0, out version), + JObject j => _converter.FromJObject(j, (flags & ApplyFlag.Equipment) != 0, (flags & ApplyFlag.Customization) != 0), + _ => null, + }; + } + + private void OnGPoseChange(bool gPose) + => GPoseChanged?.Invoke(gPose); + + private void OnStateChange(StateChanged.Type _1, StateSource _2, ActorState _3, ActorData actors, object? _5) + { + if (StateChanged == null) + return; + + foreach (var actor in actors.Objects) + StateChanged.Invoke(actor.Address); + } +} diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 83b6cfd..14a3c0e 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -199,8 +199,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn continue; } - var settingsDict = tok["Settings"]?.ToObject>() ?? new Dictionary(); - var settings = new SortedList>(settingsDict.Count); + var settingsDict = tok["Settings"]?.ToObject>>() ?? []; + var settings = new Dictionary>(settingsDict.Count); foreach (var (key, value) in settingsDict) settings.Add(key, value); var priority = tok["Priority"]?.ToObject() ?? 0; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 3b67ac6..a7358b8 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -34,10 +34,10 @@ public class DesignConverter( } public string ShareBase64(Design design) - => ShareBase64(ShareJObject(design)); + => ToBase64(ShareJObject(design)); public string ShareBase64(DesignBase design) - => ShareBase64(ShareJObject(design)); + => ToBase64(ShareJObject(design)); public string ShareBase64(ActorState state, in ApplicationRules rules) => ShareBase64(state.ModelData, state.Materials, rules); @@ -45,7 +45,7 @@ public class DesignConverter( public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules) { var design = Convert(data, materials, rules); - return ShareBase64(ShareJObject(design)); + return ToBase64(ShareJObject(design)); } public DesignBase Convert(ActorState state, in ApplicationRules rules) @@ -61,6 +61,37 @@ public class DesignConverter( return design; } + public DesignBase? FromJObject(JObject? jObject, bool customize, bool equip) + { + if (jObject == null) + return null; + + try + { + var ret = jObject["Identifier"] != null + ? Design.LoadDesign(_customize, _items, _linkLoader, jObject) + : DesignBase.LoadDesignBase(_customize, _items, jObject); + + ret.SetApplyMeta(MetaIndex.Wetness, customize); + if (!customize) + ret.ApplyCustomize = 0; + + if (!equip) + { + ret.ApplyEquip = 0; + ret.ApplyCrest = 0; + ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); + } + + return ret; + } + catch (Exception ex) + { + Glamourer.Log.Warning($"Failure to parse JObject to design:\n{ex}"); + return null; + } + } + public DesignBase? FromBase64(string base64, bool customize, bool equip, out byte version) { DesignBase ret; @@ -138,7 +169,7 @@ public class DesignConverter( return ret; } - private static string ShareBase64(JToken jObject) + public static string ToBase64(JToken jObject) { var json = jObject.ToString(Formatting.None); var compressed = json.Compress(Version); diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 9ad0630..b368195 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -40,7 +40,7 @@ public class Glamourer : IDalamudPlugin _services.GetService(); // Initialize State Listener. _services.GetService(); // initialize ui. _services.GetService(); // initialize commands. - _services.GetService(); // initialize IPC. + _services.GetService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); } catch diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 2519b84..3dce124 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -1,4 +1,5 @@ -using ImGuiNET; +using Glamourer.Gui.Tabs.DebugTab.IpcTester; +using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs new file mode 100644 index 0000000..1a74778 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -0,0 +1,78 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Plugin; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class DesignIpcTester(DalamudPluginInterface pluginInterface) : IUiService +{ + private Dictionary _designs = []; + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private uint _key; + private ApplyFlag _flags = ApplyFlagEx.DesignDefault; + private Guid? _design; + private string _designText = string.Empty; + private GlamourerApiEc _lastError; + + public void Draw() + { + using var tree = ImRaii.TreeNode("Designs"); + if (!tree) + return; + + IpcTesterHelpers.IndexInput(ref _gameObjectIndex); + IpcTesterHelpers.KeyInput(ref _key); + IpcTesterHelpers.NameInput(ref _gameObjectName); + ImGuiUtil.GuidInput("##identifier", "Design Identifier...", string.Empty, ref _design, ref _designText, + ImGui.GetContentRegionAvail().X); + IpcTesterHelpers.DrawFlagInput(ref _flags); + + using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit); + + IpcTesterHelpers.DrawIntro("Last Error"); + ImGui.TextUnformatted(_lastError.ToString()); + + IpcTesterHelpers.DrawIntro(GetDesignList.Label); + DrawDesignsPopup(); + if (ImGui.Button("Get##Designs")) + { + _designs = new GetDesignList(pluginInterface).Invoke(); + ImGui.OpenPopup("Designs"); + } + + IpcTesterHelpers.DrawIntro(ApplyDesign.Label); + if (ImGuiUtil.DrawDisabledButton("Apply##Idx", Vector2.Zero, string.Empty, !_design.HasValue)) + _lastError = new ApplyDesign(pluginInterface).Invoke(_design!.Value, _gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(ApplyDesignName.Label); + if (ImGuiUtil.DrawDisabledButton("Apply##Name", Vector2.Zero, string.Empty, !_design.HasValue)) + _lastError = new ApplyDesignName(pluginInterface).Invoke(_design!.Value, _gameObjectName, _key, _flags); + } + + private void DrawDesignsPopup() + { + ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500)); + using var p = ImRaii.Popup("Designs"); + if (!p) + return; + + using var table = ImRaii.Table("Designs", 2, ImGuiTableFlags.SizingFixedFit); + foreach (var (guid, name) in _designs) + { + ImGuiUtil.DrawTableColumn(name); + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(guid.ToString()); + } + + if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) + ImGui.CloseCurrentPopup(); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs new file mode 100644 index 0000000..500fddd --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs @@ -0,0 +1,60 @@ +using Glamourer.Api.Enums; +using Glamourer.Designs; +using ImGuiNET; +using OtterGui; +using static Penumbra.GameData.Files.ShpkFile; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public static class IpcTesterHelpers +{ + public static void DrawFlagInput(ref ApplyFlag flags) + { + var value = (flags & ApplyFlag.Once) != 0; + if (ImGui.Checkbox("Apply Once", ref value)) + flags = value ? flags | ApplyFlag.Once : flags & ~ApplyFlag.Once; + + ImGui.SameLine(); + value = (flags & ApplyFlag.Equipment) != 0; + if (ImGui.Checkbox("Apply Equipment", ref value)) + flags = value ? flags | ApplyFlag.Equipment : flags & ~ApplyFlag.Equipment; + + ImGui.SameLine(); + value = (flags & ApplyFlag.Customization) != 0; + if (ImGui.Checkbox("Apply Customization", ref value)) + flags = value ? flags | ApplyFlag.Customization : flags & ~ApplyFlag.Customization; + + ImGui.SameLine(); + value = (flags & ApplyFlag.Lock) != 0; + if (ImGui.Checkbox("Lock Actor", ref value)) + flags = value ? flags | ApplyFlag.Lock : flags & ~ApplyFlag.Lock; + } + + public static void IndexInput(ref int index) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2); + ImGui.InputInt("Game Object Index", ref index, 0, 0); + } + + public static void KeyInput(ref uint key) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2); + var keyI = (int)key; + if (ImGui.InputInt("Key", ref keyI, 0, 0)) + key = (uint)keyI; + } + + public static void NameInput(ref string name) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputTextWithHint("##gameObject", "Character Name...", ref name, 64); + } + + public static void DrawIntro(string intro) + { + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(intro); + ImGui.TableNextColumn(); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs new file mode 100644 index 0000000..62203ac --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -0,0 +1,271 @@ +using Dalamud.Plugin; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using Glamourer.Api; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using Glamourer.Interop; +using ImGuiNET; +using OtterGui; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class IpcTesterPanel( + DalamudPluginInterface pluginInterface, + DesignIpcTester designs, + ItemsIpcTester items, + StateIpcTester state, + IFramework framework) : IGameDataDrawer +{ + public string Label + => "IPC Tester"; + + public bool Disabled + => false; + + private DateTime _lastUpdate; + private bool _subscribed = false; + + public void Draw() + { + try + { + _lastUpdate = framework.LastUpdateUTC.AddSeconds(1); + Subscribe(); + ImGui.TextUnformatted(ApiVersion.Label); + var (major, minor) = new ApiVersion(pluginInterface).Invoke(); + ImGui.SameLine(); + ImGui.TextUnformatted($"({major}.{minor:D4})"); + + designs.Draw(); + items.Draw(); + state.Draw(); + } + catch (Exception e) + { + Glamourer.Log.Error($"Error during IPC Tests:\n{e}"); + } + //ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); + //ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); + //ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); + //ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36); + //DrawItemInput(); + //using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + //if (!table) + // return; + // + //ImGuiUtil.DrawTableColumn(); + //ImGui.TableNextColumn(); + //var base64 = GlamourerIpc.GetAllCustomizationSubscriber(_pluginInterface).Invoke(_gameObjectName); + //if (base64 != null) + // ImGuiUtil.CopyOnClickSelectable(base64); + //else + // ImGui.TextUnformatted("Error"); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter); + //ImGui.TableNextColumn(); + //base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); + //if (base64 != null) + // ImGuiUtil.CopyOnClickSelectable(base64); + //else + // ImGui.TextUnformatted("Error"); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); + //ImGui.TableNextColumn(); + //var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + //if (base64Locked != null) + // ImGuiUtil.CopyOnClickSelectable(base64Locked); + //else + // ImGui.TextUnformatted("Error"); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Revert##Name")) + // GlamourerIpc.RevertSubscriber(_pluginInterface).Invoke(_gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Revert##Character")) + // GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##AllName")) + // GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnce); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##AllName")) + // GlamourerIpc.ApplyAllOnceSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##AllCharacter")) + // GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##AllCharacter")) + // GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##EquipName")) + // GlamourerIpc.ApplyOnlyEquipmentSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipmentToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##EquipCharacter")) + // GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##CustomizeName")) + // GlamourerIpc.ApplyOnlyCustomizationSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomizationToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##CustomizeCharacter")) + // GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1)) + // GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnce); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1Once)) + // GlamourerIpc.ApplyByGuidOnceSubscriber(_pluginInterface).Invoke(guid1Once, _gameObjectName); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2)) + // GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) + // .Invoke(guid2, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once)) + // GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface) + // .Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Apply With Lock##CustomizeCharacter")) + // GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) + // .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Unlock##CustomizeCharacter")) + // GlamourerIpc.UnlockSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Unlock All##CustomizeCharacter")) + // GlamourerIpc.UnlockAllSubscriber(_pluginInterface) + // .Invoke(1337); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Revert##CustomizeCharacter")) + // GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); + // + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); + //ImGui.TableNextColumn(); + //var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface) + // .Invoke(); + //if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList")) + // ImGui.SetClipboardText(string.Join("\n", designList)); + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set##SetItem")) + // _setItemEc = (GlamourerApiEc)GlamourerIpc.SetItemSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemEc.ToString()); + //} + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set Once##SetItem")) + // _setItemOnceEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) + // .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemOnceEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemOnceEc.ToString()); + //} + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set##SetItemByActorName")) + // _setItemByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) + // .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemByActorNameEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemByActorNameEc.ToString()); + //} + // + //ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnceByActorName); + //ImGui.TableNextColumn(); + //if (ImGui.Button("Set Once##SetItemByActorName")) + // _setItemOnceByActorNameEc = (GlamourerApiEc)GlamourerIpc.SetItemOnceByActorNameSubscriber(_pluginInterface) + // .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + //if (_setItemOnceByActorNameEc != GlamourerApiEc.Success) + //{ + // ImGui.SameLine(); + // ImGui.TextUnformatted(_setItemOnceByActorNameEc.ToString()); + //} + } + + private void Subscribe() + { + if (_subscribed) + return; + + Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester."); + state.GPoseChanged.Enable(); + state.StateChanged.Enable(); + framework.Update += CheckUnsubscribe; + _subscribed = true; + } + + private void CheckUnsubscribe(IFramework framework1) + { + if (_lastUpdate > framework.LastUpdateUTC) + return; + + Unsubscribe(); + framework.Update -= CheckUnsubscribe; + } + + private void Unsubscribe() + { + if (!_subscribed) + return; + + Glamourer.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester."); + _subscribed = false; + state.GPoseChanged.Disable(); + state.StateChanged.Disable(); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs new file mode 100644 index 0000000..ec75998 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -0,0 +1,66 @@ +using Dalamud.Plugin; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService +{ + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private uint _key; + private ApplyFlag _flags = ApplyFlagEx.DesignDefault; + private CustomItemId _customItemId; + private StainId _stainId; + private EquipSlot _slot = EquipSlot.Head; + private GlamourerApiEc _lastError; + + public void Draw() + { + using var tree = ImRaii.TreeNode("Items"); + if (!tree) + return; + + IpcTesterHelpers.IndexInput(ref _gameObjectIndex); + IpcTesterHelpers.KeyInput(ref _key); + DrawItemInput(); + IpcTesterHelpers.NameInput(ref _gameObjectName); + IpcTesterHelpers.DrawFlagInput(ref _flags); + using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit); + + IpcTesterHelpers.DrawIntro("Last Error"); + ImGui.TextUnformatted(_lastError.ToString()); + + IpcTesterHelpers.DrawIntro(SetItem.Label); + if (ImGui.Button("Set##Idx")) + _lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, _slot, _customItemId.Id, _stainId.Id, _key, _flags); + + IpcTesterHelpers.DrawIntro(SetItemName.Label); + if (ImGui.Button("Set##Name")) + _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, _slot, _customItemId.Id, _stainId.Id, _key, _flags); + } + + private void DrawItemInput() + { + var tmp = _customItemId.Id; + var width = ImGui.GetContentRegionAvail().X / 2; + ImGui.SetNextItemWidth(width); + if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) + _customItemId = tmp; + EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot, width); + var value = (int)_stainId.Id; + ImGui.SetNextItemWidth(width); + if (ImGui.InputInt("Stain ID", ref value, 1, 3)) + { + value = Math.Clamp(value, 0, byte.MaxValue); + _stainId = (StainId)value; + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs new file mode 100644 index 0000000..5898177 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -0,0 +1,184 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Plugin; +using Glamourer.Api.Enums; +using Glamourer.Api.IpcSubscribers; +using Glamourer.Designs; +using ImGuiNET; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; +using Penumbra.Api.Helpers; +using Penumbra.GameData.Interop; +using Penumbra.String; + +namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; + +public class StateIpcTester : IUiService, IDisposable +{ + private readonly DalamudPluginInterface _pluginInterface; + + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private uint _key; + private ApplyFlag _flags = ApplyFlagEx.DesignDefault; + private GlamourerApiEc _lastError; + private JObject? _state; + private string? _stateString; + private string _base64State = string.Empty; + + public readonly EventSubscriber StateChanged; + private nint _lastStateChangeActor; + private ByteString _lastStateChangeName = ByteString.Empty; + private DateTime _lastStateChangeTime; + + public readonly EventSubscriber GPoseChanged; + private bool _lastGPoseChangeValue; + private DateTime _lastGPoseChangeTime; + + private int _numUnlocked; + + public StateIpcTester(DalamudPluginInterface pluginInterface) + { + _pluginInterface = pluginInterface; + StateChanged = Api.IpcSubscribers.StateChanged.Subscriber(_pluginInterface, OnStateChanged); + GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); + } + + public void Dispose() + { + StateChanged.Dispose(); + GPoseChanged.Dispose(); + } + + public void Draw() + { + using var tree = ImRaii.TreeNode("State"); + if (!tree) + return; + + IpcTesterHelpers.IndexInput(ref _gameObjectIndex); + IpcTesterHelpers.KeyInput(ref _key); + IpcTesterHelpers.NameInput(ref _gameObjectName); + IpcTesterHelpers.DrawFlagInput(ref _flags); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputTextWithHint("##base64", "Base 64 State...", ref _base64State, 2000); + using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.SizingFixedFit); + + IpcTesterHelpers.DrawIntro("Last Error"); + ImGui.TextUnformatted(_lastError.ToString()); + IpcTesterHelpers.DrawIntro("Last State Change"); + PrintName(); + IpcTesterHelpers.DrawIntro("Last GPose Change"); + ImGui.TextUnformatted($"{_lastGPoseChangeValue} at {_lastGPoseChangeTime.ToLocalTime().TimeOfDay}"); + + + IpcTesterHelpers.DrawIntro(GetState.Label); + DrawStatePopup(); + if (ImGui.Button("Get##Idx")) + { + (_lastError, _state) = new GetState(_pluginInterface).Invoke(_gameObjectIndex, _key); + _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; + ImGui.OpenPopup("State"); + } + + IpcTesterHelpers.DrawIntro(GetStateName.Label); + if (ImGui.Button("Get##Name")) + { + (_lastError, _state) = new GetStateName(_pluginInterface).Invoke(_gameObjectName, _key); + _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; + ImGui.OpenPopup("State"); + } + + IpcTesterHelpers.DrawIntro(ApplyState.Label); + if (ImGuiUtil.DrawDisabledButton("Apply Last##Idx", Vector2.Zero, string.Empty, _state == null)) + _lastError = new ApplyState(_pluginInterface).Invoke(_state!, _gameObjectIndex, _key, _flags); + ImGui.SameLine(); + if (ImGui.Button("Apply Base64##Idx")) + _lastError = new ApplyState(_pluginInterface).Invoke(_base64State, _gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(ApplyStateName.Label); + if (ImGuiUtil.DrawDisabledButton("Apply Last##Name", Vector2.Zero, string.Empty, _state == null)) + _lastError = new ApplyStateName(_pluginInterface).Invoke(_state!, _gameObjectName, _key, _flags); + ImGui.SameLine(); + if (ImGui.Button("Apply Base64##Name")) + _lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags); + + IpcTesterHelpers.DrawIntro(RevertState.Label); + if (ImGui.Button("Revert##Idx")) + _lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(RevertStateName.Label); + if (ImGui.Button("Revert##Name")) + _lastError = new RevertStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); + + IpcTesterHelpers.DrawIntro(UnlockState.Label); + if (ImGui.Button("Unlock##Idx")) + _lastError = new UnlockState(_pluginInterface).Invoke(_gameObjectIndex, _key); + + IpcTesterHelpers.DrawIntro(UnlockStateName.Label); + if (ImGui.Button("Unlock##Name")) + _lastError = new UnlockStateName(_pluginInterface).Invoke(_gameObjectName, _key); + + IpcTesterHelpers.DrawIntro(UnlockAll.Label); + if (ImGui.Button("Unlock##All")) + _numUnlocked = new UnlockAll(_pluginInterface).Invoke(_key); + ImGui.SameLine(); + ImGui.TextUnformatted($"Unlocked {_numUnlocked}"); + + IpcTesterHelpers.DrawIntro(RevertToAutomation.Label); + if (ImGui.Button("Revert##AutomationIdx")) + _lastError = new RevertToAutomation(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(RevertToAutomationName.Label); + if (ImGui.Button("Revert##AutomationName")) + _lastError = new RevertToAutomationName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); + } + + private void DrawStatePopup() + { + ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500)); + using var p = ImRaii.Popup("State"); + if (!p) + return; + + if (ImGui.Button("Copy to Clipboard")) + ImGui.SetClipboardText(_stateString); + ImGui.SameLine(); + if (ImGui.Button("Copy as Base64") && _state != null) + ImGui.SetClipboardText(DesignConverter.ToBase64(_state)); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(_stateString); + + if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) + ImGui.CloseCurrentPopup(); + } + + private unsafe void PrintName() + { + ImGuiNative.igTextUnformatted(_lastStateChangeName.Path, _lastStateChangeName.Path + _lastStateChangeName.Length); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGuiUtil.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); + } + + ImGui.SameLine(); + ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); + } + + private void OnStateChanged(nint actor) + { + _lastStateChangeActor = actor; + _lastStateChangeTime = DateTime.UtcNow; + _lastStateChangeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + } + + private void OnGPoseChange(bool value) + { + _lastGPoseChangeValue = value; + _lastGPoseChangeTime = DateTime.UtcNow; + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs deleted file mode 100644 index d96aefa..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ /dev/null @@ -1,244 +0,0 @@ -using Dalamud.Plugin; -using Glamourer.Api; -using Glamourer.Interop; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Gui; -using Penumbra.GameData.Gui.Debug; -using Penumbra.GameData.Structs; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IGameDataDrawer -{ - public string Label - => "IPC Tester"; - - public bool Disabled - => false; - - private int _gameObjectIndex; - private CustomItemId _customItemId; - private StainId _stainId; - private EquipSlot _slot = EquipSlot.Head; - private string _gameObjectName = string.Empty; - private string _base64Apply = string.Empty; - private string _designIdentifier = string.Empty; - private GlamourerIpc.GlamourerErrorCode _setItemEc; - private GlamourerIpc.GlamourerErrorCode _setItemOnceEc; - private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc; - private GlamourerIpc.GlamourerErrorCode _setItemOnceByActorNameEc; - - public void Draw() - { - ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); - ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); - ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36); - DrawItemInput(); - using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApiVersions); - var (major, minor) = GlamourerIpc.ApiVersionsSubscriber(_pluginInterface).Invoke(); - ImGuiUtil.DrawTableColumn($"({major}, {minor})"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomization); - ImGui.TableNextColumn(); - var base64 = GlamourerIpc.GetAllCustomizationSubscriber(_pluginInterface).Invoke(_gameObjectName); - if (base64 != null) - ImGuiUtil.CopyOnClickSelectable(base64); - else - ImGui.TextUnformatted("Error"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter); - ImGui.TableNextColumn(); - base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); - if (base64 != null) - ImGuiUtil.CopyOnClickSelectable(base64); - else - ImGui.TextUnformatted("Error"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); - ImGui.TableNextColumn(); - var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - if (base64Locked != null) - ImGuiUtil.CopyOnClickSelectable(base64Locked); - else - ImGui.TextUnformatted("Error"); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); - ImGui.TableNextColumn(); - if (ImGui.Button("Revert##Name")) - GlamourerIpc.RevertSubscriber(_pluginInterface).Invoke(_gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Revert##Character")) - GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##AllName")) - GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnce); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##AllName")) - GlamourerIpc.ApplyAllOnceSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##AllCharacter")) - GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##AllCharacter")) - GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##EquipName")) - GlamourerIpc.ApplyOnlyEquipmentSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipmentToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##EquipCharacter")) - GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##CustomizeName")) - GlamourerIpc.ApplyOnlyCustomizationSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomizationToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##CustomizeCharacter")) - GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1)) - GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnce); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1Once)) - GlamourerIpc.ApplyByGuidOnceSubscriber(_pluginInterface).Invoke(guid1Once, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2)) - GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) - .Invoke(guid2, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once)) - GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface) - .Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply With Lock##CustomizeCharacter")) - GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); - ImGui.TableNextColumn(); - if (ImGui.Button("Unlock##CustomizeCharacter")) - GlamourerIpc.UnlockSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll); - ImGui.TableNextColumn(); - if (ImGui.Button("Unlock All##CustomizeCharacter")) - GlamourerIpc.UnlockAllSubscriber(_pluginInterface) - .Invoke(1337); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation); - ImGui.TableNextColumn(); - if (ImGui.Button("Revert##CustomizeCharacter")) - GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); - - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); - ImGui.TableNextColumn(); - var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface) - .Invoke(); - if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList")) - ImGui.SetClipboardText(string.Join("\n", designList)); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem); - ImGui.TableNextColumn(); - if (ImGui.Button("Set##SetItem")) - _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemEc.ToString()); - } - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce); - ImGui.TableNextColumn(); - if (ImGui.Button("Set Once##SetItem")) - _setItemOnceEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) - .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemOnceEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemOnceEc.ToString()); - } - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName); - ImGui.TableNextColumn(); - if (ImGui.Button("Set##SetItemByActorName")) - _setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) - .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemByActorNameEc.ToString()); - } - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnceByActorName); - ImGui.TableNextColumn(); - if (ImGui.Button("Set Once##SetItemByActorName")) - _setItemOnceByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceByActorNameSubscriber(_pluginInterface) - .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); - if (_setItemOnceByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) - { - ImGui.SameLine(); - ImGui.TextUnformatted(_setItemOnceByActorNameEc.ToString()); - } - } - - private void DrawItemInput() - { - var tmp = _customItemId.Id; - if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) - _customItemId = tmp; - var width = ImGui.GetContentRegionAvail().X; - EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot); - var value = (int)_stainId.Id; - ImGui.SameLine(); - width -= ImGui.GetContentRegionAvail().X; - ImGui.SetNextItemWidth(width); - if (ImGui.InputInt("Stain ID", ref value, 1, 3)) - { - value = Math.Clamp(value, 0, byte.MaxValue); - _stainId = (StainId)value; - } - } -} diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 0618d14..164fe78 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -11,24 +11,13 @@ using OtterGui.Raii; namespace Glamourer.Gui.Tabs.DesignTab; -public class ModAssociationsTab +public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager) { - private readonly PenumbraService _penumbra; - private readonly DesignFileSystemSelector _selector; - private readonly DesignManager _manager; - private readonly ModCombo _modCombo; - - public ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager) - { - _penumbra = penumbra; - _selector = selector; - _manager = manager; - _modCombo = new ModCombo(penumbra, Glamourer.Log); - } + private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log); public void Draw() { - using var h = ImRaii.CollapsingHeader("Mod Associations"); + using var h = ImRaii.CollapsingHeader("Mod Associations"); ImGuiUtil.HoverTooltip( "This tab can store information about specific mods associated with this design.\n\n" + "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n" @@ -43,25 +32,25 @@ public class ModAssociationsTab private void DrawApplyAllButton() { - var current = _penumbra.CurrentCollection; - if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {current}##applyAll", - new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, current is "")) + var (id, name) = penumbra.CurrentCollection; + if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll", + new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty)) ApplyAll(); } public void DrawApplyButton() { - var current = _penumbra.CurrentCollection; + var (id, name) = penumbra.CurrentCollection; if (ImGuiUtil.DrawDisabledButton("Apply Mod Associations", Vector2.Zero, - $"Try to apply all associated mod settings to Penumbras current collection {current}", - _selector.Selected!.AssociatedMods.Count == 0 || current is "")) + $"Try to apply all associated mod settings to Penumbras current collection {name}", + selector.Selected!.AssociatedMods.Count == 0 || id == Guid.Empty)) ApplyAll(); } public void ApplyAll() { - foreach (var (mod, settings) in _selector.Selected!.AssociatedMods) - _penumbra.SetMod(mod, settings); + foreach (var (mod, settings) in selector.Selected!.AssociatedMods) + penumbra.SetMod(mod, settings); } private void DrawTable() @@ -79,9 +68,9 @@ public class ModAssociationsTab ImGui.TableSetupColumn("##Options", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Try Applyingm").X); ImGui.TableHeadersRow(); - Mod? removedMod = null; + Mod? removedMod = null; (Mod mod, ModSettings settings)? updatedMod = null; - foreach (var ((mod, settings), idx) in _selector.Selected!.AssociatedMods.WithIndex()) + foreach (var ((mod, settings), idx) in selector.Selected!.AssociatedMods.WithIndex()) { using var id = ImRaii.PushId(idx); DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp); @@ -94,10 +83,10 @@ public class ModAssociationsTab DrawNewModRow(); if (removedMod.HasValue) - _manager.RemoveMod(_selector.Selected!, removedMod.Value); - + manager.RemoveMod(selector.Selected!, removedMod.Value); + if (updatedMod.HasValue) - _manager.UpdateMod(_selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); + manager.UpdateMod(selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); } private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod, out (Mod, ModSettings)? updatedMod) @@ -112,15 +101,15 @@ public class ModAssociationsTab ImGui.TableNextColumn(); ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Update the settings of this mod association", false, true); - + if (ImGui.IsItemHovered()) { - var newSettings = _penumbra.GetModSettings(mod); + var newSettings = penumbra.GetModSettings(mod); if (ImGui.IsItemClicked()) updatedMod = (mod, newSettings); - + using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); - using var tt = ImRaii.Tooltip(); + using var tt = ImRaii.Tooltip(); ImGui.Separator(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); @@ -143,7 +132,7 @@ public class ModAssociationsTab ModCombo.DrawSettingsRight(newSettings); } } - + ImGui.TableNextColumn(); var selected = ImGui.Selectable($"{mod.Name}##name"); var hovered = ImGui.IsItemHovered(); @@ -151,7 +140,7 @@ public class ModAssociationsTab selected |= ImGui.Selectable($"{mod.DirectoryName}##directory"); hovered |= ImGui.IsItemHovered(); if (selected) - _penumbra.OpenModPage(mod); + penumbra.OpenModPage(mod); if (hovered) ImGui.SetTooltip("Click to open mod page in Penumbra."); ImGui.TableNextColumn(); @@ -164,9 +153,9 @@ public class ModAssociationsTab ImGuiUtil.RightAlign(settings.Priority.ToString()); ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton("Try Applying", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, - !_penumbra.Available)) + !penumbra.Available)) { - var text = _penumbra.SetMod(mod, settings); + var text = penumbra.SetMod(mod, settings); if (text.Length > 0) Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false); } @@ -202,13 +191,13 @@ public class ModAssociationsTab ImGui.TableNextColumn(); var tt = currentName.IsNullOrEmpty() ? "Please select a mod first." - : _selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) + : selector.Selected!.AssociatedMods.ContainsKey(_modCombo.CurrentSelection.Mod) ? "The design already contains an association with the selected mod." : string.Empty; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, tt.Length > 0, true)) - _manager.AddMod(_selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); + manager.AddMod(selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); ImGui.TableNextColumn(); _modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty, ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight()); diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs new file mode 100644 index 0000000..98ba870 --- /dev/null +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs @@ -0,0 +1,36 @@ +using Dalamud.Interface; +using Glamourer.Interop.Penumbra; +using ImGuiNET; +using OtterGui; +using OtterGui.Log; +using OtterGui.Raii; +using OtterGui.Services; +using OtterGui.Widgets; + +namespace Glamourer.Gui.Tabs.SettingsTab; + +public sealed class CollectionCombo(Configuration config, PenumbraService penumbra, Logger log) + : FilterComboCache<(Guid Id, string IdShort, string Name)>( + () => penumbra.GetCollections().Select(kvp => (kvp.Key, kvp.Key.ToString()[..8], kvp.Value)).ToArray(), + MouseWheelType.Control, log), IUiService +{ + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var (_, idShort, name) = Items[globalIdx]; + if (config.Ephemeral.IncognitoMode) + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + return ImGui.Selectable(idShort); + } + + var ret = ImGui.Selectable(name, selected); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + ImGuiUtil.RightAlign($"({idShort})"); + } + + return ret; + } +} diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 2ddabda..d976d28 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -1,13 +1,12 @@ using Dalamud.Interface; -using Dalamud.Interface.Components; -using Dalamud.Interface.Style; -using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Actors; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.SettingsTab; @@ -15,13 +14,14 @@ public class CollectionOverrideDrawer( CollectionOverrideService collectionOverrides, Configuration config, ObjectManager objects, - ActorManager actors) : IService + ActorManager actors, + PenumbraService penumbra, + CollectionCombo combo) : IService { private string _newIdentifier = string.Empty; private ActorIdentifier[] _identifiers = []; private int _dragDropIndex = -1; private Exception? _exception; - private string _collection = string.Empty; public void Draw() { @@ -32,58 +32,113 @@ public class CollectionOverrideDrawer( if (!header) return; - using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg); + using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg); if (!table) return; var buttonSize = new Vector2(ImGui.GetFrameHeight()); ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); - ImGui.TableSetupColumn("identifiers", ImGuiTableColumnFlags.WidthStretch, 0.6f); + ImGui.TableSetupColumn("identifiers", ImGuiTableColumnFlags.WidthStretch, 0.35f); ImGui.TableSetupColumn("collections", ImGuiTableColumnFlags.WidthStretch, 0.4f); + ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.25f); for (var i = 0; i < collectionOverrides.Overrides.Count; ++i) + DrawCollectionRow(ref i, buttonSize); + + DrawNewOverride(buttonSize); + } + + private void DrawCollectionRow(ref int idx, Vector2 buttonSize) + { + using var id = ImRaii.PushId(idx); + var (exists, actor, collection, name) = collectionOverrides.Fetch(idx); + + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true)) + collectionOverrides.DeleteOverride(idx--); + + ImGui.TableNextColumn(); + DrawActorIdentifier(idx, actor); + + ImGui.TableNextColumn(); + if (combo.Draw("##collection", name, $"Select the overriding collection. Current GUID:", ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight())) { - var (identifier, collection) = collectionOverrides.Overrides[i]; - using var id = ImRaii.PushId(i); - ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this override.", false, true)) - collectionOverrides.DeleteOverride(i--); + var (guid, _, newName) = combo.CurrentSelection; + collectionOverrides.ChangeOverride(idx, guid, newName); + } - ImGui.TableNextColumn(); - ImGui.Selectable(config.Ephemeral.IncognitoMode ? identifier.Incognito(null) : identifier.ToString()); - - using (var target = ImRaii.DragDropTarget()) - { - if (target.Success && ImGuiUtil.IsDropping("DraggingOverride")) - { - collectionOverrides.MoveOverride(_dragDropIndex, i); - _dragDropIndex = -1; - } - } - - using (var source = ImRaii.DragDropSource()) - { - if (source) - { - ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0); - ImGui.TextUnformatted($"Reordering Override #{i + 1}..."); - _dragDropIndex = i; - } - } - - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (ImGui.InputText("##input", ref collection, 64) && collection.Length > 0) - collectionOverrides.ChangeOverride(i, collection); + if (ImGui.IsItemHovered()) + { + using var tt = ImRaii.Tooltip(); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted($" {collection}"); } + ImGui.TableNextColumn(); + DrawCollectionName(exists, collection, name); + } + + private void DrawCollectionName(bool exists, Guid collection, string name) + { + if (!exists) + { + ImGui.TextUnformatted(""); + if (!ImGui.IsItemHovered()) + return; + + using var tt1 = ImRaii.Tooltip(); + ImGui.TextUnformatted($"The design {name} with the GUID"); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGui.TextUnformatted($" {collection}"); + } + + ImGui.TextUnformatted("does not exist in Penumbra."); + return; + } + + ImGui.TextUnformatted(config.Ephemeral.IncognitoMode ? collection.ToString()[..8] : name); + if (!ImGui.IsItemHovered()) + return; + + using var tt2 = ImRaii.Tooltip(); + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(collection.ToString()); + } + + private void DrawActorIdentifier(int idx, ActorIdentifier actor) + { + ImGui.Selectable(config.Ephemeral.IncognitoMode ? actor.Incognito(null) : actor.ToString()); + using (var target = ImRaii.DragDropTarget()) + { + if (target.Success && ImGuiUtil.IsDropping("DraggingOverride")) + { + collectionOverrides.MoveOverride(_dragDropIndex, idx); + _dragDropIndex = -1; + } + } + + using (var source = ImRaii.DragDropSource()) + { + if (source) + { + ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0); + ImGui.TextUnformatted($"Reordering Override #{idx + 1}..."); + _dragDropIndex = idx; + } + } + } + + private void DrawNewOverride(Vector2 buttonSize) + { + var (currentId, currentName) = penumbra.CurrentCollection; ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.PersonCirclePlus.ToIconString(), buttonSize, "Add override for current player.", - !objects.Player.Valid, true)) - collectionOverrides.AddOverride([objects.PlayerData.Identifier], _collection.Length > 0 ? _collection : "TempCollection"); + !objects.Player.Valid && currentId != Guid.Empty, true)) + collectionOverrides.AddOverride([objects.PlayerData.Identifier], currentId, currentName); ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemInnerSpacing.X - buttonSize.X * 2); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputTextWithHint("##newActor", "New Identifier...", ref _newIdentifier, 80)) try { @@ -91,7 +146,7 @@ public class CollectionOverrideDrawer( } catch (ActorIdentifierFactory.IdentifierParseError e) { - _exception = e; + _exception = e; _identifiers = []; } @@ -101,9 +156,9 @@ public class CollectionOverrideDrawer( ? "Please enter an identifier string first." : $"The identifier string {_newIdentifier} does not result in a valid identifier{(_exception == null ? "." : $":\n\n{_exception?.Message}")}"; - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is 'T', true)) - collectionOverrides.AddOverride(_identifiers, _collection.Length > 0 ? _collection : "TempCollection"); + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, tt[0] is not 'A', true)) + collectionOverrides.AddOverride(_identifiers, currentId, currentName); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); using (ImRaii.PushFont(UiBuilder.IconFont)) @@ -114,9 +169,5 @@ public class CollectionOverrideDrawer( if (ImGui.IsItemHovered()) ActorIdentifierFactory.WriteUserStringTooltip(false); - - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - ImGui.InputTextWithHint("##collection", "Enter Collection...", ref _collection, 80); } } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 2b72fff..fcdc7b7 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -22,16 +22,13 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O return; } - var collections = new HashSet(); + var collections = new HashSet(); foreach (var actor in data.Objects) { - var (collection, overridden) = overrides.GetCollection(actor, state.Identifier); - if (collection.Length == 0) - { - Glamourer.Log.Verbose($"[Mod Applier] Could not obtain associated collection for {actor.Utf8Name}."); + var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier); + if (collection == Guid.Empty) continue; - } if (!collections.Add(collection)) continue; @@ -48,11 +45,11 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O } } - public (List Messages, int Applied, string Collection, bool Overridden) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) + public (List Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) { - var (collection, overridden) = overrides.GetCollection(actor); - if (collection.Length <= 0) - return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty, false); + var (collection, name, overridden) = overrides.GetCollection(actor); + if (collection == Guid.Empty) + return ([$"{actor.Utf8Name} uses no mods."], 0, Guid.Empty, string.Empty, false); var messages = new List(); var appliedMods = 0; @@ -65,6 +62,6 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O ++appliedMods; } - return (messages, appliedMods, collection, overridden); + return (messages, appliedMods, collection, name, overridden); } } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index c018113..11f7fd9 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -67,7 +67,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService } } - private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) + private void OnModSettingChange(ModSettingChange type, Guid collectionId, string mod, bool inherited) { if (type is ModSettingChange.TemporaryMod) { @@ -79,8 +79,8 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) continue; - var collection = _penumbra.GetActorCollection(actors.Objects[0]); - if (collection != name) + var collection = _penumbra.GetActorCollection(actors.Objects[0], out _); + if (collection != collectionId) continue; _actions.Enqueue((state, () => @@ -96,7 +96,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService { // Only update once per frame. var playerName = _penumbra.GetCurrentPlayerCollection(); - if (playerName != name) + if (playerName != collectionId) return; var currentFrame = _framework.LastUpdateUTC; diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index c6617e7..6d4c677 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -2,7 +2,6 @@ using Dalamud.Plugin; using Glamourer.Events; using OtterGui.Classes; -using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; using Penumbra.GameData.Interop; @@ -10,8 +9,6 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -using CurrentSettings = ValueTuple>, bool)?>; - public readonly record struct Mod(string Name, string DirectoryName) : IComparable { public int CompareTo(Mod other) @@ -24,10 +21,10 @@ public readonly record struct Mod(string Name, string DirectoryName) : IComparab } } -public readonly record struct ModSettings(IDictionary> Settings, int Priority, bool Enabled) +public readonly record struct ModSettings(Dictionary> Settings, int Priority, bool Enabled) { public ModSettings() - : this(new Dictionary>(), 0, false) + : this(new Dictionary>(), 0, false) { } public static ModSettings Empty @@ -36,30 +33,33 @@ public readonly record struct ModSettings(IDictionary> Set public unsafe class PenumbraService : IDisposable { - public const int RequiredPenumbraBreakingVersion = 4; - public const int RequiredPenumbraFeatureVersion = 15; + public const int RequiredPenumbraBreakingVersion = 5; + public const int RequiredPenumbraFeatureVersion = 0; - private readonly DalamudPluginInterface _pluginInterface; - private readonly EventSubscriber _tooltipSubscriber; - private readonly EventSubscriber _clickSubscriber; - private readonly EventSubscriber _creatingCharacterBase; - private readonly EventSubscriber _createdCharacterBase; - private readonly EventSubscriber _modSettingChanged; - private ActionSubscriber _redrawSubscriber; - private FuncSubscriber _drawObjectInfo; - private FuncSubscriber _cutsceneParent; - private FuncSubscriber _objectCollection; - private FuncSubscriber> _getMods; - private FuncSubscriber _currentCollection; - private FuncSubscriber _getCurrentSettings; - private FuncSubscriber _setMod; - private FuncSubscriber _setModPriority; - private FuncSubscriber _setModSetting; - private FuncSubscriber, PenumbraApiEc> _setModSettings; - private FuncSubscriber _openModPage; + private readonly DalamudPluginInterface _pluginInterface; + private readonly EventSubscriber _tooltipSubscriber; + private readonly EventSubscriber _clickSubscriber; + private readonly EventSubscriber _creatingCharacterBase; + private readonly EventSubscriber _createdCharacterBase; + private readonly EventSubscriber _modSettingChanged; - private readonly EventSubscriber _initializedEvent; - private readonly EventSubscriber _disposedEvent; + private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; + private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; + private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; + private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; + private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; + private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; + private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; + private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; + private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings; + private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; + private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; + private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting; + private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings; + private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; + + private readonly IDisposable _initializedEvent; + private readonly IDisposable _disposedEvent; private readonly PenumbraReloaded _penumbraReloaded; @@ -69,13 +69,13 @@ public unsafe class PenumbraService : IDisposable { _pluginInterface = pi; _penumbraReloaded = penumbraReloaded; - _initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach); - _disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach); - _tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi); - _clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi); - _createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi); - _creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi); - _modSettingChanged = Ipc.ModSettingChanged.Subscriber(pi); + _initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach); + _disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach); + _tooltipSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemTooltip.Subscriber(pi); + _clickSubscriber = global::Penumbra.Api.IpcSubscribers.ChangedItemClicked.Subscriber(pi); + _createdCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatedCharacterBase.Subscriber(pi); + _creatingCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatingCharacterBase.Subscriber(pi); + _modSettingChanged = global::Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi); Reattach(); } @@ -92,24 +92,27 @@ public unsafe class PenumbraService : IDisposable } - public event Action CreatingCharacterBase + public event Action CreatingCharacterBase { add => _creatingCharacterBase.Event += value; remove => _creatingCharacterBase.Event -= value; } - public event Action CreatedCharacterBase + public event Action CreatedCharacterBase { add => _createdCharacterBase.Event += value; remove => _createdCharacterBase.Event -= value; } - public event Action ModSettingChanged + public event Action ModSettingChanged { add => _modSettingChanged.Event += value; remove => _modSettingChanged.Event -= value; } + public Dictionary GetCollections() + => Available ? _collections!.Invoke() : []; + public ModSettings GetModSettings(in Mod mod) { if (!Available) @@ -117,8 +120,8 @@ public unsafe class PenumbraService : IDisposable try { - var collection = _currentCollection.Invoke(ApiCollectionType.Current); - var (ec, tuple) = _getCurrentSettings.Invoke(collection, mod.DirectoryName, string.Empty, false); + var collection = _currentCollection!.Invoke(ApiCollectionType.Current); + var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -131,6 +134,18 @@ public unsafe class PenumbraService : IDisposable } } + public (Guid Id, string Name)? CollectionByIdentifier(string identifier) + { + if (!Available) + return null; + + var ret = _collectionByIdentifier!.Invoke(identifier); + if (ret.Count == 0) + return null; + + return ret[0]; + } + public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods() { if (!Available) @@ -138,10 +153,10 @@ public unsafe class PenumbraService : IDisposable try { - var allMods = _getMods.Invoke(); - var collection = _currentCollection.Invoke(ApiCollectionType.Current); + var allMods = _getMods!.Invoke(); + var collection = _currentCollection!.Invoke(ApiCollectionType.Current); return allMods - .Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, false))) + .Select(m => (m.Key, m.Value, _getCurrentSettings!.Invoke(collection!.Value.Id, m.Key))) .Where(t => t.Item3.Item1 is PenumbraApiEc.Success) .Select(t => (new Mod(t.Item2, t.Item1), !t.Item3.Item2.HasValue @@ -162,19 +177,22 @@ public unsafe class PenumbraService : IDisposable public void OpenModPage(Mod mod) { - if (_openModPage.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing) + if (!Available) + return; + + if (_openModPage!.Invoke(TabType.Mods, mod.DirectoryName) == PenumbraApiEc.ModMissing) Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.", NotificationType.Info, false); } - public string CurrentCollection - => Available ? _currentCollection.Invoke(ApiCollectionType.Current) : ""; + public (Guid Id, string Name) CurrentCollection + => Available ? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value : (Guid.Empty, ""); /// /// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// If it is disabled, ignore all other settings. /// - public string SetMod(Mod mod, ModSettings settings, string? collection = null) + public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null) { if (!Available) return "Penumbra is not available."; @@ -182,8 +200,8 @@ public unsafe class PenumbraService : IDisposable var sb = new StringBuilder(); try { - collection ??= _currentCollection.Invoke(ApiCollectionType.Current); - var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); + var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id; + var ec = _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled); switch (ec) { case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; @@ -193,14 +211,14 @@ public unsafe class PenumbraService : IDisposable if (!settings.Enabled) return string.Empty; - ec = _setModPriority.Invoke(collection, mod.DirectoryName, mod.Name, settings.Priority); + ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority); Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail."); foreach (var (setting, list) in settings.Settings) { ec = list.Count == 1 - ? _setModSetting.Invoke(collection, mod.DirectoryName, mod.Name, setting, list[0]) - : _setModSettings.Invoke(collection, mod.DirectoryName, mod.Name, setting, (IReadOnlyList)list); + ? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0]) + : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); switch (ec) { case PenumbraApiEc.OptionGroupMissing: @@ -227,55 +245,54 @@ public unsafe class PenumbraService : IDisposable } /// Obtain the name of the collection currently assigned to the player. - public string GetCurrentPlayerCollection() + public Guid GetCurrentPlayerCollection() { if (!Available) - return string.Empty; + return Guid.Empty; - var (valid, _, name) = _objectCollection.Invoke(0); - return valid ? name : string.Empty; + var (valid, _, (id, _)) = _objectCollection!.Invoke(0); + return valid ? id : Guid.Empty; } /// Obtain the name of the collection currently assigned to the given actor. - public string GetActorCollection(Actor actor) + public Guid GetActorCollection(Actor actor, out string name) { if (!Available) - return string.Empty; + { + name = string.Empty; + return Guid.Empty; + } - var (valid, _, name) = _objectCollection.Invoke(actor.Index.Index); - return valid ? name : string.Empty; + (var valid, _, (var id, name)) = _objectCollection!.Invoke(actor.Index.Index); + return valid ? id : Guid.Empty; } /// Obtain the game object corresponding to a draw object. public Actor GameObjectFromDrawObject(Model drawObject) - => Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null; + => Available ? _drawObjectInfo!.Invoke(drawObject.Address).Item1 : Actor.Null; /// Obtain the parent of a cutscene actor if it is known. public short CutsceneParent(ushort idx) - => (short)(Available ? _cutsceneParent.Invoke(idx) : -1); + => (short)(Available ? _cutsceneParent!.Invoke(idx) : -1); /// Try to redraw the given actor. public void RedrawObject(Actor actor, RedrawType settings) { - if (!actor || !Available) + if (!actor) return; - try - { - _redrawSubscriber.Invoke(actor.AsObject->ObjectIndex, settings); - } - catch (Exception e) - { - Glamourer.Log.Debug($"Failure redrawing object:\n{e}"); - } + RedrawObject(actor.Index, settings); } /// Try to redraw the given actor. public void RedrawObject(ObjectIndex index, RedrawType settings) { + if (!Available) + return; + try { - _redrawSubscriber.Invoke(index.Index, settings); + _redraw!.Invoke(index.Index, settings); } catch (Exception e) { @@ -290,7 +307,7 @@ public unsafe class PenumbraService : IDisposable { Unattach(); - var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke(); + var (breaking, feature) = new global::Penumbra.Api.IpcSubscribers.ApiVersion(_pluginInterface).Invoke(); if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion) throw new Exception( $"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); @@ -300,24 +317,27 @@ public unsafe class PenumbraService : IDisposable _creatingCharacterBase.Enable(); _createdCharacterBase.Enable(); _modSettingChanged.Enable(); - _drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface); - _cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface); - _redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface); - _objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface); - _getMods = Ipc.GetMods.Subscriber(_pluginInterface); - _currentCollection = Ipc.GetCollectionForType.Subscriber(_pluginInterface); - _getCurrentSettings = Ipc.GetCurrentModSettings.Subscriber(_pluginInterface); - _setMod = Ipc.TrySetMod.Subscriber(_pluginInterface); - _setModPriority = Ipc.TrySetModPriority.Subscriber(_pluginInterface); - _setModSetting = Ipc.TrySetModSetting.Subscriber(_pluginInterface); - _setModSettings = Ipc.TrySetModSettings.Subscriber(_pluginInterface); - _openModPage = Ipc.OpenMainWindow.Subscriber(_pluginInterface); - Available = true; + _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); + _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); + _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); + _drawObjectInfo = new global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo(_pluginInterface); + _cutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex(_pluginInterface); + _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); + _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); + _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); + _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); + _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); + _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); + _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); + _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); + _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); + Available = true; _penumbraReloaded.Invoke(); Glamourer.Log.Debug("Glamourer attached to Penumbra."); } catch (Exception e) { + Unattach(); Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}"); } } @@ -332,7 +352,21 @@ public unsafe class PenumbraService : IDisposable _modSettingChanged.Disable(); if (Available) { - Available = false; + _collectionByIdentifier = null; + _collections = null; + _redraw = null; + _drawObjectInfo = null; + _cutsceneParent = null; + _objectCollection = null; + _getMods = null; + _currentCollection = null; + _getCurrentSettings = null; + _setMod = null; + _setModPriority = null; + _setModSetting = null; + _setModSettings = null; + _openModPage = null; + Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } } diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index a7c4364..691118f 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -1,7 +1,9 @@ +using Dalamud.Interface.Internal.Notifications; using Glamourer.Interop.Penumbra; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; +using OtterGui.Classes; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.GameData.Actors; @@ -11,7 +13,7 @@ namespace Glamourer.Services; public sealed class CollectionOverrideService : IService, ISavable { - public const int Version = 1; + public const int Version = 2; private readonly SaveService _saveService; private readonly ActorManager _actors; private readonly PenumbraService _penumbra; @@ -24,48 +26,71 @@ public sealed class CollectionOverrideService : IService, ISavable Load(); } - public unsafe (string Collection, bool Overriden) GetCollection(Actor actor, ActorIdentifier identifier = default) + public unsafe (Guid CollectionId, string Display, bool Overriden) GetCollection(Actor actor, ActorIdentifier identifier = default) { if (!identifier.IsValid) identifier = _actors.FromObject(actor.AsObject, out _, true, true, true); return _overrides.FindFirst(p => p.Actor.Matches(identifier), out var ret) - ? (ret.Collection, true) - : (_penumbra.GetActorCollection(actor), false); + ? (ret.CollectionId, ret.DisplayName, true) + : (_penumbra.GetActorCollection(actor, out var name), name, false); } - private readonly List<(ActorIdentifier Actor, string Collection)> _overrides = []; + private readonly List<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> _overrides = []; - public IReadOnlyList<(ActorIdentifier Actor, string Collection)> Overrides + public IReadOnlyList<(ActorIdentifier Actor, Guid CollectionId, string DisplayName)> Overrides => _overrides; public string ToFilename(FilenameService fileNames) => fileNames.CollectionOverrideFile; - public void AddOverride(IEnumerable identifiers, string collection) + public void AddOverride(IEnumerable identifiers, Guid collectionId, string displayName) { - if (collection.Length == 0) + if (collectionId == Guid.Empty) return; foreach (var id in identifiers.Where(i => i.IsValid)) { - _overrides.Add((id, collection)); - Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collection}."); + _overrides.Add((id, collectionId, displayName)); + Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collectionId}."); _saveService.QueueSave(this); } } - public void ChangeOverride(int idx, string newCollection) + public (bool Exists, ActorIdentifier Identifier, Guid CollectionId, string DisplayName) Fetch(int idx) { - if (idx < 0 || idx >= _overrides.Count || newCollection.Length == 0) + var (identifier, id, name) = _overrides[idx]; + var collection = _penumbra.CollectionByIdentifier(id.ToString()); + if (collection == null) + return (false, identifier, id, name); + + if (collection.Value.Name == name) + return (true, identifier, id, name); + + _overrides[idx] = (identifier, id, collection.Value.Name); + Glamourer.Log.Debug($"Updated display name of collection override {idx + 1} ({id})."); + _saveService.QueueSave(this); + return (true, identifier, id, collection.Value.Name); + } + + public void ChangeOverride(int idx, Guid newCollectionId, string newDisplayName) + { + if (idx < 0 || idx >= _overrides.Count) + return; + + if (newCollectionId == Guid.Empty || newDisplayName.Length == 0) return; var current = _overrides[idx]; - if (current.Collection == newCollection) + if (current.CollectionId == newCollectionId) return; - _overrides[idx] = current with { Collection = newCollection }; - Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.Collection} to {newCollection}."); + _overrides[idx] = current with + { + CollectionId = newCollectionId, + DisplayName = newDisplayName, + }; + Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.CollectionId} to {newCollectionId}."); _saveService.QueueSave(this); } @@ -102,6 +127,7 @@ public sealed class CollectionOverrideService : IService, ISavable switch (version) { case 1: + case 2: if (jObj["Overrides"] is not JArray array) { Glamourer.Log.Error($"Invalid format of collection override file, ignored."); @@ -110,17 +136,50 @@ public sealed class CollectionOverrideService : IService, ISavable foreach (var token in array.OfType()) { - var collection = token["Collection"]?.ToObject() ?? string.Empty; - var identifier = _actors.FromJson(token); + var collectionIdentifier = token["Collection"]?.ToObject() ?? string.Empty; + var identifier = _actors.FromJson(token); + var displayName = token["DisplayName"]?.ToObject() ?? collectionIdentifier; if (!identifier.IsValid) - Glamourer.Log.Warning($"Invalid identifier for collection override with collection [{collection}], skipped."); - else if (collection.Length == 0) - Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped."); - else - _overrides.Add((identifier, collection)); + { + Glamourer.Log.Warning( + $"Invalid identifier for collection override with collection [{token["Collection"]}], skipped."); + continue; + } + + if (!Guid.TryParse(collectionIdentifier, out var collectionId)) + { + if (collectionIdentifier.Length == 0) + { + Glamourer.Log.Warning($"Empty collection override for identifier {identifier.Incognito(null)}, skipped."); + continue; + } + + if (version >= 2) + { + Glamourer.Log.Warning( + $"Invalid collection override {collectionIdentifier} for identifier {identifier.Incognito(null)}, skipped."); + continue; + } + + var collection = _penumbra.CollectionByIdentifier(collectionIdentifier); + if (collection == null) + { + Glamourer.Messager.AddMessage(new Notification( + $"The overridden collection for identifier {identifier.Incognito(null)} with name {collectionIdentifier} could not be found by Penumbra for migration.", + NotificationType.Warning)); + continue; + } + + Glamourer.Log.Information($"Migrated collection {collectionIdentifier} to {collection.Value.Id}."); + collectionId = collection.Value.Id; + displayName = collection.Value.Name; + } + + _overrides.Add((identifier, collectionId, displayName)); } break; + default: Glamourer.Log.Error($"Invalid version {version} of collection override file, ignored."); return; @@ -147,10 +206,11 @@ public sealed class CollectionOverrideService : IService, ISavable JArray SerializeOverrides() { var jArray = new JArray(); - foreach (var (actor, collection) in _overrides) + foreach (var (actor, collection, displayName) in _overrides) { var obj = actor.ToJson(); - obj["Collection"] = collection; + obj["Collection"] = collection; + obj["DisplayName"] = displayName; jArray.Add(obj); } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index e2edd2d..0616392 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -10,6 +10,7 @@ using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -18,7 +19,7 @@ using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Services; -public class CommandService : IDisposable +public class CommandService : IDisposable, IApiService { private const string RandomString = "random"; private const string MainCommandString = "/glamourer"; @@ -118,7 +119,7 @@ public class CommandService : IDisposable "apply" => Apply(argument), "reapply" => ReapplyState(argument), "revert" => Revert(argument), - "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), + "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true), "automation" => SetAutomation(argument), "copy" => CopyState(argument), @@ -534,14 +535,14 @@ public class CommandService : IDisposable if (!applyMods || design is not Design d) return; - var (messages, appliedMods, collection, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); + var (messages, appliedMods, collection, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); if (appliedMods > 0) Glamourer.Messager.Chat.Print( - $"Applied {appliedMods} mod settings to {collection}{(overridden ? " (overridden by settings)" : string.Empty)}."); + $"Applied {appliedMods} mod settings to {name}{(overridden ? " (overridden by settings)" : string.Empty)}."); } private bool Delete(string argument) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 1fc7077..8f52815 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -65,20 +65,26 @@ public class ItemManager if (itemId == SmallclothesId(slot)) return SmallClothesItem(slot); - if (!itemId.IsItem || !ItemData.TryGetValue(itemId.Item, slot, out var item)) + if (!itemId.IsItem) { - item = EquipItem.FromId(itemId); - item = slot is EquipSlot.MainHand or EquipSlot.OffHand - ? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant) + var item = EquipItem.FromId(itemId); + item = slot is EquipSlot.MainHand or EquipSlot.OffHand + ? Identify(slot, item.PrimaryId, item.SecondaryId, item.Variant) : Identify(slot, item.PrimaryId, item.Variant); return item; } + else if (!ItemData.TryGetValue(itemId.Item, slot, out var item)) + { + return EquipItem.FromId(itemId); + } + else + { + if (item.Type.ToSlot() != slot) + return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, + 0, 0, 0, 0); - if (item.Type.ToSlot() != slot) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, - 0, 0, 0, 0); - - return item; + return item; + } } public EquipItem Resolve(FullEquipType type, ItemId itemId) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index d5f92a9..f06e014 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin; using Glamourer.Api; +using Glamourer.Api.Api; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; @@ -43,12 +44,12 @@ public static class StaticServiceManager .AddData() .AddDesigns() .AddState() - .AddUi() - .AddApi(); + .AddUi(); DalamudServices.AddServices(services, pi); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Glamourer).Assembly); services.AddIServices(typeof(ImRaii).Assembly); + services.AddSingleton(p => p.GetRequiredService()); services.CreateProvider(); return services; } @@ -164,8 +165,4 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton(); - - private static ServiceManager AddApi(this ServiceManager services) - => services.AddSingleton() - .AddSingleton(); } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 7a30108..73c8f0d 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -106,7 +106,7 @@ public class StateListener : IDisposable /// Weapons and meta flags are updated independently. /// We also need to apply fixed designs here. /// - private unsafe void OnCreatingCharacterBase(nint actorPtr, string _, nint modelPtr, nint customizePtr, nint equipDataPtr) + private unsafe void OnCreatingCharacterBase(nint actorPtr, Guid _, nint modelPtr, nint customizePtr, nint equipDataPtr) { var actor = (Actor)actorPtr; if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) @@ -725,7 +725,7 @@ public class StateListener : IDisposable _changeCustomizeService.Unsubscribe(OnCustomizeChanged); } - private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) + private void OnCreatedCharacterBase(nint gameObject, Guid _, nint drawObject) { if (_condition[ConditionFlag.CreatingCharacter]) return;