Compare commits

...

14 commits

Author SHA1 Message Date
Ottermandias
598f598e82
Merge pull request #115 from CordeliaMist/ReapplyState
Add ReapplyState & ReapplyStateName
2025-12-19 01:27:19 +01:00
Ottermandias
06c593bbcb
Merge pull request #114 from Bracket416/IsUnlocked
IsUnlocked
2025-12-19 01:27:04 +01:00
Cordelia
da14548c43
Merge branch 'Ottermandias:main' into ReapplyState 2025-12-13 09:20:49 -08:00
Cordelia Mist
48bef12555 Optional Addition: Include IPC to get the current state of Auto-Reload Gear, and an IPC Event call for when the option changes.
- Should help clear up ambiguity with any external plugins intending to call ReapplyState on a mod-change to themselves, to know if Glamourer has it handled for them.
2025-10-05 10:31:41 -07:00
Cordelia Mist
d6c36ca4f7 Add IPC Calls to IPC Tester. 2025-09-26 19:12:02 -07:00
Cordelia Mist
44345b9429 Init providers from API 2025-09-26 19:11:39 -07:00
Cordelia Mist
20914bc064 Add ReapplyState & ReapplyStateName with Helpers. 2025-09-26 19:11:07 -07:00
Bracket
8f362c5121
Update StateApi.cs 2025-09-02 02:02:52 +01:00
Bracket
0442fb7b60
Update IpcProviders.cs 2025-09-02 02:02:08 +01:00
Bracket
c62c3c4eea
Update .gitmodules 2025-09-01 11:27:35 +01:00
Bracket
6a34d41f6a
Update .gitmodules 2025-09-01 11:27:11 +01:00
Bracket
c31f6c19a6
Update StateApi.cs 2025-08-31 23:06:54 +01:00
Bracket
da1db70635
Update IpcProviders.cs 2025-08-31 23:06:23 +01:00
Bracket
c420b1f180
Update .gitmodules 2025-08-31 23:04:41 +01:00
8 changed files with 155 additions and 13 deletions

View file

@ -3,7 +3,7 @@ using OtterGui.Services;
namespace Glamourer.Api;
public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
public class GlamourerApi(Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
{
public const int CurrentApiVersionMajor = 1;
public const int CurrentApiVersionMinor = 7;
@ -11,6 +11,9 @@ public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) :
public (int Major, int Minor) ApiVersion
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);
public bool AutoReloadGearEnabled
=> config.AutoRedrawEquipOnChanges;
public IGlamourerApiDesigns Designs
=> designs;

View file

@ -22,6 +22,7 @@ public sealed class IpcProviders : IDisposable, IApiService
new FuncProvider<(int Major, int Minor)>(pi, "Glamourer.ApiVersions", () => api.ApiVersion), // backward compatibility
new FuncProvider<int>(pi, "Glamourer.ApiVersion", () => api.ApiVersion.Major), // backward compatibility
IpcSubscribers.ApiVersion.Provider(pi, api),
IpcSubscribers.AutoReloadGearEnabled.Provider(pi, api),
IpcSubscribers.GetDesignList.Provider(pi, api.Designs),
IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs),
@ -50,14 +51,18 @@ public sealed class IpcProviders : IDisposable, IApiService
IpcSubscribers.GetStateBase64Name.Provider(pi, api.State),
IpcSubscribers.ApplyState.Provider(pi, api.State),
IpcSubscribers.ApplyStateName.Provider(pi, api.State),
IpcSubscribers.ReapplyState.Provider(pi, api.State),
IpcSubscribers.ReapplyStateName.Provider(pi, api.State),
IpcSubscribers.RevertState.Provider(pi, api.State),
IpcSubscribers.RevertStateName.Provider(pi, api.State),
IpcSubscribers.UnlockState.Provider(pi, api.State),
IpcSubscribers.CanUnlock.Provider(pi, api.State),
IpcSubscribers.UnlockStateName.Provider(pi, api.State),
IpcSubscribers.DeletePlayerState.Provider(pi, api.State),
IpcSubscribers.UnlockAll.Provider(pi, api.State),
IpcSubscribers.RevertToAutomation.Provider(pi, api.State),
IpcSubscribers.RevertToAutomationName.Provider(pi, api.State),
IpcSubscribers.AutoReloadGearChanged.Provider(pi, api.State),
IpcSubscribers.StateChanged.Provider(pi, api.State),
IpcSubscribers.StateChangedWithType.Provider(pi, api.State),
IpcSubscribers.StateFinalized.Provider(pi, api.State),
@ -76,3 +81,5 @@ public sealed class IpcProviders : IDisposable, IApiService
_disposedProvider.Dispose();
}
}

View file

@ -20,6 +20,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
private readonly DesignConverter _converter;
private readonly AutoDesignApplier _autoDesigns;
private readonly ActorObjectManager _objects;
private readonly AutoRedrawChanged _autoRedraw;
private readonly StateChanged _stateChanged;
private readonly StateFinalized _stateFinalized;
private readonly GPoseService _gPose;
@ -29,6 +30,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
DesignConverter converter,
AutoDesignApplier autoDesigns,
ActorObjectManager objects,
AutoRedrawChanged autoRedraw,
StateChanged stateChanged,
StateFinalized stateFinalized,
GPoseService gPose)
@ -38,9 +40,11 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
_converter = converter;
_autoDesigns = autoDesigns;
_objects = objects;
_autoRedraw = autoRedraw;
_stateChanged = stateChanged;
_stateFinalized = stateFinalized;
_gPose = gPose;
_autoRedraw.Subscribe(OnAutoRedrawChange, AutoRedrawChanged.Priority.StateApi);
_stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc);
_stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi);
_gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi);
@ -48,6 +52,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
public void Dispose()
{
_autoRedraw.Unsubscribe(OnAutoRedrawChange);
_stateChanged.Unsubscribe(OnStateChanged);
_stateFinalized.Unsubscribe(OnStateFinalized);
_gPose.Unsubscribe(OnGPoseChange);
@ -121,6 +126,48 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc ReapplyState(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);
Reapply(_objects.Objects[objectIndex], state, key, flags);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc ReapplyStateName(string playerName, uint key, ApplyFlag flags)
{
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
var states = _helpers.FindExistingStates(playerName);
var any = false;
var anyReapplied = false;
foreach (var state in states)
{
any = true;
if (!state.CanUnlock(key))
continue;
anyReapplied = true;
anyReapplied |= Reapply(state, key, flags) is GlamourerApiEc.Success;
}
if (any)
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
if (!anyReapplied)
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, 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);
@ -178,6 +225,20 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc CanUnlock(int objectIndex, uint key, out bool isLocked, out bool canUnlock)
{
var args = ApiHelpers.Args("Index", objectIndex, "Key", key);
isLocked = false; // These seem like reasonable defaults.
canUnlock = false;
if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success)
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
if (state == null)
return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); // Possibly, the error type could be changed. I just looked at what was available.
isLocked = state.IsLocked;
canUnlock = state.CanUnlock(key);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public GlamourerApiEc UnlockStateName(string playerName, uint key)
{
var args = ApiHelpers.Args("Name", playerName, "Key", key);
@ -270,6 +331,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public event Action<bool>? AutoReloadGearChanged;
public event Action<nint>? StateChanged;
public event Action<IntPtr, StateChangeType>? StateChangedWithType;
public event Action<IntPtr, StateFinalizationType>? StateFinalized;
@ -284,6 +346,24 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
ApiHelpers.Lock(state, key, flags);
}
private GlamourerApiEc Reapply(ActorState state, uint key, ApplyFlag flags)
{
if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid)
return GlamourerApiEc.ActorNotFound;
foreach (var actor in actors.Objects)
Reapply(actor, state, key, flags);
return GlamourerApiEc.Success;
}
private void Reapply(Actor actor, ActorState state, uint key, ApplyFlag flags)
{
var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed;
_stateManager.ReapplyState(actor, state, false, source, true);
ApiHelpers.Lock(state, key, flags);
}
private void Revert(ActorState state, uint key, ApplyFlag flags)
{
var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed;
@ -344,8 +424,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
};
}
private void OnGPoseChange(bool gPose)
=> GPoseChanged?.Invoke(gPose);
private void OnAutoRedrawChange(bool autoReload)
=> AutoReloadGearChanged?.Invoke(autoReload);
private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5)
{
@ -366,4 +446,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
foreach (var actor in actors.Objects)
StateFinalized.Invoke(actor.Address, type);
}
private void OnGPoseChange(bool gPose)
=> GPoseChanged?.Invoke(gPose);
}

View file

@ -0,0 +1,16 @@
using OtterGui.Classes;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the auto-reload gear setting is changed in glamourer configuration.
/// </summary>
public sealed class AutoRedrawChanged()
: EventWrapper<bool, AutoRedrawChanged.Priority>(nameof(AutoRedrawChanged))
{
public enum Priority
{
/// <seealso cref="Api.StateApi.OnGPoseChange"/>
StateApi = int.MinValue,
}
}

View file

@ -1,8 +1,5 @@
using Glamourer.Api.Enums;
using Glamourer.Designs;
using Dalamud.Bindings.ImGui;
using OtterGui;
using static Penumbra.GameData.Files.ShpkFile;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;

View file

@ -33,6 +33,11 @@ public class IpcTesterPanel(
ImGui.SameLine();
ImGui.TextUnformatted($"({major}.{minor:D4})");
ImGui.TextUnformatted(AutoReloadGearEnabled.Label);
var autoRedraw = new AutoReloadGearEnabled(pluginInterface).Invoke();
ImGui.SameLine();
ImGui.TextUnformatted(autoRedraw ? "Enabled" : "Disabled");
designs.Draw();
items.Draw();
state.Draw();
@ -49,6 +54,7 @@ public class IpcTesterPanel(
return;
Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester.");
state.AutoRedrawChanged.Enable();
state.GPoseChanged.Enable();
state.StateChanged.Enable();
state.StateFinalized.Enable();
@ -72,6 +78,7 @@ public class IpcTesterPanel(
Glamourer.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester.");
_subscribed = false;
state.AutoRedrawChanged.Disable();
state.GPoseChanged.Disable();
state.StateChanged.Disable();
state.StateFinalized.Disable();

View file

@ -1,11 +1,11 @@
using Dalamud.Interface;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Glamourer.Api.Enums;
using Glamourer.Api.Helpers;
using Glamourer.Api.IpcSubscribers;
using Glamourer.Designs;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
@ -31,6 +31,10 @@ public class StateIpcTester : IUiService, IDisposable
private string _base64State = string.Empty;
private string? _getStateString;
public readonly EventSubscriber<bool> AutoRedrawChanged;
private bool _lastAutoRedrawChangeValue;
private DateTime _lastAutoRedrawChangeTime;
public readonly EventSubscriber<nint, StateChangeType> StateChanged;
private nint _lastStateChangeActor;
private ByteString _lastStateChangeName = ByteString.Empty;
@ -51,10 +55,12 @@ public class StateIpcTester : IUiService, IDisposable
public StateIpcTester(IDalamudPluginInterface pluginInterface)
{
_pluginInterface = pluginInterface;
StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged);
StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized);
GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange);
_pluginInterface = pluginInterface;
AutoRedrawChanged = AutoReloadGearChanged.Subscriber(_pluginInterface, OnAutoRedrawChanged);
StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged);
StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized);
GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange);
AutoRedrawChanged.Disable();
StateChanged.Disable();
StateFinalized.Disable();
GPoseChanged.Disable();
@ -62,6 +68,7 @@ public class StateIpcTester : IUiService, IDisposable
public void Dispose()
{
AutoRedrawChanged.Dispose();
StateChanged.Dispose();
StateFinalized.Dispose();
GPoseChanged.Dispose();
@ -83,6 +90,8 @@ public class StateIpcTester : IUiService, IDisposable
IpcTesterHelpers.DrawIntro("Last Error");
ImGui.TextUnformatted(_lastError.ToString());
IpcTesterHelpers.DrawIntro("Last Auto Redraw Change");
ImGui.TextUnformatted($"{_lastAutoRedrawChangeValue} at {_lastAutoRedrawChangeTime.ToLocalTime().TimeOfDay}");
IpcTesterHelpers.DrawIntro("Last State Change");
PrintChangeName();
IpcTesterHelpers.DrawIntro("Last State Finalization");
@ -138,6 +147,14 @@ public class StateIpcTester : IUiService, IDisposable
if (ImUtf8.Button("Apply Base64##Name"u8))
_lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags);
IpcTesterHelpers.DrawIntro(ReapplyState.Label);
if (ImUtf8.Button("Reapply##Idx"u8))
_lastError = new ReapplyState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(ReapplyStateName.Label);
if (ImUtf8.Button("Reapply##Name"u8))
_lastError = new ReapplyStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags);
IpcTesterHelpers.DrawIntro(RevertState.Label);
if (ImUtf8.Button("Revert##Idx"u8))
_lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags);
@ -225,6 +242,12 @@ public class StateIpcTester : IUiService, IDisposable
ImUtf8.Text($"at {_lastStateFinalizeTime.ToLocalTime().TimeOfDay}");
}
private void OnAutoRedrawChanged(bool value)
{
_lastAutoRedrawChangeValue = value;
_lastAutoRedrawChangeTime = DateTime.UtcNow;
}
private void OnStateChanged(nint actor, StateChangeType type)
{
_lastStateChangeActor = actor;

View file

@ -6,6 +6,7 @@ using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Interop;
using Glamourer.Interop.PalettePlus;
@ -30,6 +31,7 @@ public class SettingsTab(
CodeDrawer codeDrawer,
Glamourer glamourer,
AutoDesignApplier autoDesignApplier,
AutoRedrawChanged autoRedraw,
PcpService pcpService)
: ITab
{
@ -91,7 +93,11 @@ public class SettingsTab(
config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2);
Checkbox("Auto-Reload Gear"u8,
"Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8,
config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v);
config.AutoRedrawEquipOnChanges, v =>
{
config.AutoRedrawEquipOnChanges = v;
autoRedraw.Invoke(v);
});
Checkbox("Attach to PCP-Handling"u8,
"Add the actor's glamourer state when a PCP is created by Penumbra, and create a design and apply it if possible when a PCP is installed by Penumbra."u8,
config.AttachToPcp, pcpService.Set);