diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 6c3037e..08f5ddc 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -33,7 +33,7 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0, SendStateUpdate: true); + ResetMaterials: !once && key != 0, IsFinal: true); using var restrict = ApiHelpers.Restrict(design, flags); stateManager.ApplyDesign(state, design, settings); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 515cd34..704f008 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -2,7 +2,6 @@ using Dalamud.Plugin; using Glamourer.Api.Api; using Glamourer.Api.Helpers; using OtterGui.Services; -using System.Reflection.Emit; using Glamourer.Api.Enums; namespace Glamourer.Api; diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 347d2b6..eaf9d01 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -11,9 +11,9 @@ using OtterGui.Services; using Penumbra.GameData.Interop; using ObjectManager = Glamourer.Interop.ObjectManager; using StateChanged = Glamourer.Events.StateChanged; -using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; + public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -23,7 +23,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly AutoDesignApplier _autoDesigns; private readonly ObjectManager _objects; private readonly StateChanged _stateChanged; - private readonly StateUpdated _stateUpdated; + private readonly StateFinalized _stateFinalized; private readonly GPoseService _gPose; public StateApi(ApiHelpers helpers, @@ -33,27 +33,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable AutoDesignApplier autoDesigns, ObjectManager objects, StateChanged stateChanged, - StateUpdated stateUpdated, + StateFinalized stateFinalized, GPoseService gPose) { - _helpers = helpers; - _stateManager = stateManager; - _converter = converter; - _config = config; - _autoDesigns = autoDesigns; - _objects = objects; - _stateChanged = stateChanged; - _stateUpdated = stateUpdated; - _gPose = gPose; + _helpers = helpers; + _stateManager = stateManager; + _converter = converter; + _config = config; + _autoDesigns = autoDesigns; + _objects = objects; + _stateChanged = stateChanged; + _stateFinalized = stateFinalized; + _gPose = gPose; _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); - _stateUpdated.Subscribe(OnStateUpdated, Events.StateUpdated.Priority.GlamourerIpc); - _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); + _stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi); + _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi); } public void Dispose() { _stateChanged.Unsubscribe(OnStateChanged); - _stateUpdated.Unsubscribe(OnStateUpdated); + _stateFinalized.Unsubscribe(OnStateFinalized); _gPose.Unsubscribe(OnGPoseChange); } @@ -253,16 +253,16 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public event Action? StateChanged; - public event Action? StateChangedWithType; + public event Action? StateChanged; + public event Action? StateChangedWithType; public event Action? StateFinalized; - public event Action? GPoseChanged; + 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, SendStateUpdate: true); + ResetMaterials: !once && key != 0, IsFinal: true); _stateManager.ApplyDesign(state, design, settings); ApiHelpers.Lock(state, key, flags); } @@ -349,7 +349,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable StateChangedWithType.Invoke(actor.Address, type); } - private void OnStateUpdated(StateFinalizationType type, ActorData actors) + private void OnStateFinalized(StateFinalizationType type, ActorData actors) { Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); if (StateFinalized != null) diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 0178620..c18c98b 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -14,7 +14,7 @@ public readonly record struct ApplySettings( bool UseSingleSource = false, bool MergeLinks = false, bool ResetMaterials = false, - bool SendStateUpdate = false) + bool IsFinal = false) { public static readonly ApplySettings Manual = new() { @@ -25,7 +25,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = false, - SendStateUpdate = false, + IsFinal = false, }; public static readonly ApplySettings ManualWithLinks = new() @@ -37,7 +37,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = true, ResetMaterials = false, - SendStateUpdate = false, + IsFinal = false, }; public static readonly ApplySettings Game = new() @@ -49,7 +49,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = true, - SendStateUpdate = false, + IsFinal = false, }; } diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index a84f1d6..44421a0 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -13,8 +13,8 @@ public sealed class GPoseService : EventWrapper public enum Priority { - /// - GlamourerIpc = int.MinValue, + /// + StateApi = int.MinValue, } public bool InGPose { get; private set; } diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateFinalized.cs similarity index 53% rename from Glamourer/Events/StateUpdated.cs rename to Glamourer/Events/StateFinalized.cs index faaf33a..e8548e9 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateFinalized.cs @@ -1,24 +1,23 @@ +using Glamourer.Api; using Glamourer.Api.Enums; -using Glamourer.Designs.History; using Glamourer.Interop.Structs; -using Glamourer.State; using OtterGui.Classes; namespace Glamourer.Events; /// -/// Triggered when a Design is edited in any way. +/// Triggered when a set of grouped changes finishes being applied to a Glamourer state. /// /// Parameter is the operation that finished updating the saved state. /// Parameter is the existing actors using this saved state. /// /// -public sealed class StateUpdated() - : EventWrapper(nameof(StateUpdated)) +public sealed class StateFinalized() + : EventWrapper(nameof(StateFinalized)) { public enum Priority { - /// - GlamourerIpc = int.MinValue, + /// + StateApi = int.MinValue, } } diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index bdc345f..50f86fd 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -183,7 +183,7 @@ public sealed class DesignQuickBar : Window, IDisposable } using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } private void DrawRevertButton(Vector2 buttonSize) @@ -213,7 +213,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); ImGui.SameLine(); if (clicked) - _stateManager.ResetState(state!, StateSource.Manual, stateUpdate: true); + _stateManager.ResetState(state!, StateSource.Manual, isFinal: true); } private void DrawRevertAutomationButton(Vector2 buttonSize) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index ced78fb..d8f3cd1 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -385,7 +385,7 @@ public class ActorPanel { if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", _state!.IsLocked)) - _stateManager.ResetState(_state!, StateSource.Manual, stateUpdate: true); + _stateManager.ResetState(_state!, StateSource.Manual, isFinal: true); ImGui.SameLine(); @@ -423,7 +423,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual with { SendStateUpdate = true }); + ApplySettings.Manual with { IsFinal = true }); } private void DrawApplyToTarget() @@ -440,7 +440,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual with { SendStateUpdate = true }); + ApplySettings.Manual with { IsFinal = true }); } @@ -467,7 +467,7 @@ public class ActorPanel var text = ImGui.GetClipboardText(); var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } catch (Exception ex) { diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index c44a722..62f93e9 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -79,7 +79,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) { var design = CreateDesign(plate); - _state.ApplyDesign(state!, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state!, design, ApplySettings.Manual with { IsFinal = true }); } using (ImRaii.Group()) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 8f561af..1a78b24 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -51,6 +51,7 @@ public class IpcTesterPanel( Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester."); state.GPoseChanged.Enable(); state.StateChanged.Enable(); + state.StateFinalized.Enable(); framework.Update += CheckUnsubscribe; _subscribed = true; } @@ -73,5 +74,6 @@ public class IpcTesterPanel( _subscribed = false; state.GPoseChanged.Disable(); state.StateChanged.Disable(); + state.StateFinalized.Disable(); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index f378625..638bffc 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using Penumbra.GameData.Interop; using Penumbra.String; @@ -31,9 +32,16 @@ public class StateIpcTester : IUiService, IDisposable private string? _getStateString; public readonly EventSubscriber StateChanged; - private nint _lastStateChangeActor; - private ByteString _lastStateChangeName = ByteString.Empty; - private DateTime _lastStateChangeTime; + private nint _lastStateChangeActor; + private ByteString _lastStateChangeName = ByteString.Empty; + private DateTime _lastStateChangeTime; + private StateChangeType _lastStateChangeType; + + public readonly EventSubscriber StateFinalized; + private nint _lastStateFinalizeActor; + private ByteString _lastStateFinalizeName = ByteString.Empty; + private DateTime _lastStateFinalizeTime; + private StateFinalizationType _lastStateFinalizeType; public readonly EventSubscriber GPoseChanged; private bool _lastGPoseChangeValue; @@ -44,15 +52,18 @@ public class StateIpcTester : IUiService, IDisposable public StateIpcTester(IDalamudPluginInterface pluginInterface) { _pluginInterface = pluginInterface; - StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized); GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); StateChanged.Disable(); + StateFinalized.Disable(); GPoseChanged.Disable(); } public void Dispose() { StateChanged.Dispose(); + StateFinalized.Dispose(); GPoseChanged.Dispose(); } @@ -73,86 +84,88 @@ public class StateIpcTester : IUiService, IDisposable IpcTesterHelpers.DrawIntro("Last Error"); ImGui.TextUnformatted(_lastError.ToString()); IpcTesterHelpers.DrawIntro("Last State Change"); - PrintName(); + PrintChangeName(); + IpcTesterHelpers.DrawIntro("Last State Finalization"); + PrintFinalizeName(); IpcTesterHelpers.DrawIntro("Last GPose Change"); ImGui.TextUnformatted($"{_lastGPoseChangeValue} at {_lastGPoseChangeTime.ToLocalTime().TimeOfDay}"); IpcTesterHelpers.DrawIntro(GetState.Label); DrawStatePopup(); - if (ImGui.Button("Get##Idx")) + if (ImUtf8.Button("Get##Idx"u8)) { (_lastError, _state) = new GetState(_pluginInterface).Invoke(_gameObjectIndex, _key); _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateName.Label); - if (ImGui.Button("Get##Name")) + if (ImUtf8.Button("Get##Name"u8)) { (_lastError, _state) = new GetStateName(_pluginInterface).Invoke(_gameObjectName, _key); _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateBase64.Label); - if (ImGui.Button("Get##Base64Idx")) + if (ImUtf8.Button("Get##Base64Idx"u8)) { (_lastError, _getStateString) = new GetStateBase64(_pluginInterface).Invoke(_gameObjectIndex, _key); _stateString = _getStateString ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateBase64Name.Label); - if (ImGui.Button("Get##Base64Idx")) + if (ImUtf8.Button("Get##Base64Idx"u8)) { (_lastError, _getStateString) = new GetStateBase64Name(_pluginInterface).Invoke(_gameObjectName, _key); _stateString = _getStateString ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } 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")) + if (ImUtf8.Button("Apply Base64##Idx"u8)) _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")) + if (ImUtf8.Button("Apply Base64##Name"u8)) _lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags); IpcTesterHelpers.DrawIntro(RevertState.Label); - if (ImGui.Button("Revert##Idx")) + if (ImUtf8.Button("Revert##Idx"u8)) _lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(RevertStateName.Label); - if (ImGui.Button("Revert##Name")) + if (ImUtf8.Button("Revert##Name"u8)) _lastError = new RevertStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); IpcTesterHelpers.DrawIntro(UnlockState.Label); - if (ImGui.Button("Unlock##Idx")) + if (ImUtf8.Button("Unlock##Idx"u8)) _lastError = new UnlockState(_pluginInterface).Invoke(_gameObjectIndex, _key); IpcTesterHelpers.DrawIntro(UnlockStateName.Label); - if (ImGui.Button("Unlock##Name")) + if (ImUtf8.Button("Unlock##Name"u8)) _lastError = new UnlockStateName(_pluginInterface).Invoke(_gameObjectName, _key); IpcTesterHelpers.DrawIntro(UnlockAll.Label); - if (ImGui.Button("Unlock##All")) + if (ImUtf8.Button("Unlock##All"u8)) _numUnlocked = new UnlockAll(_pluginInterface).Invoke(_key); ImGui.SameLine(); ImGui.TextUnformatted($"Unlocked {_numUnlocked}"); IpcTesterHelpers.DrawIntro(RevertToAutomation.Label); - if (ImGui.Button("Revert##AutomationIdx")) + if (ImUtf8.Button("Revert##AutomationIdx"u8)) _lastError = new RevertToAutomation(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(RevertToAutomationName.Label); - if (ImGui.Button("Revert##AutomationName")) + if (ImUtf8.Button("Revert##AutomationName"u8)) _lastError = new RevertToAutomationName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); } @@ -162,44 +175,70 @@ public class StateIpcTester : IUiService, IDisposable if (_stateString == null) return; - using var p = ImRaii.Popup("State"); + using var p = ImUtf8.Popup("State"u8); if (!p) return; - if (ImGui.Button("Copy to Clipboard")) - ImGui.SetClipboardText(_stateString); + if (ImUtf8.Button("Copy to Clipboard"u8)) + ImUtf8.SetClipboardText(_stateString); if (_stateString[0] is '{') { ImGui.SameLine(); - if (ImGui.Button("Copy as Base64") && _state != null) - ImGui.SetClipboardText(DesignConverter.ToBase64(_state)); + if (ImUtf8.Button("Copy as Base64"u8) && _state != null) + ImUtf8.SetClipboardText(DesignConverter.ToBase64(_state)); } using var font = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(_stateString ?? string.Empty); + ImUtf8.TextWrapped(_stateString ?? string.Empty); - if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) + if (ImUtf8.Button("Close"u8, -Vector2.UnitX) || !ImGui.IsWindowFocused()) ImGui.CloseCurrentPopup(); } - private unsafe void PrintName() + private unsafe void PrintChangeName() { - ImGuiNative.igTextUnformatted(_lastStateChangeName.Path, _lastStateChangeName.Path + _lastStateChangeName.Length); + ImUtf8.Text(_lastStateChangeName.Span); + ImGui.SameLine(0, 0); + ImUtf8.Text($" ({_lastStateChangeType})"); ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGuiUtil.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); + ImUtf8.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); } ImGui.SameLine(); - ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); + ImUtf8.Text($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); } - private void OnStateChanged(nint actor, StateChangeType _) + private unsafe void PrintFinalizeName() + { + ImUtf8.Text(_lastStateFinalizeName.Span); + ImGui.SameLine(0, 0); + ImUtf8.Text($" ({_lastStateFinalizeType})"); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImUtf8.CopyOnClickSelectable($"0x{_lastStateFinalizeActor:X}"); + } + + ImGui.SameLine(); + ImUtf8.Text($"at {_lastStateFinalizeTime.ToLocalTime().TimeOfDay}"); + } + + private void OnStateChanged(nint actor, StateChangeType type) { _lastStateChangeActor = actor; _lastStateChangeTime = DateTime.UtcNow; _lastStateChangeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + _lastStateChangeType = type; + } + + private void OnStateFinalized(nint actor, StateFinalizationType type) + { + _lastStateFinalizeActor = actor; + _lastStateFinalizeTime = DateTime.UtcNow; + _lastStateFinalizeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + _lastStateFinalizeType = type; } private void OnGPoseChange(bool value) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index fe71609..42eb8e9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -460,7 +460,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } @@ -478,7 +478,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 345df11..aeb96f6 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -196,7 +196,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } @@ -214,7 +214,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 466f1ae..ffa6581 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -18,6 +18,7 @@ public unsafe class UpdateSlotService : IDisposable public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; + public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { @@ -26,8 +27,8 @@ public unsafe class UpdateSlotService : IDisposable GearsetDataLoadedEvent = gearsetDataLoaded; _bonusItems = bonusItems; - _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); + _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -89,8 +90,8 @@ public unsafe class UpdateSlotService : IDisposable /// Detours the func that makes all FlagSlotForUpdate calls on a gearset change or initial render of a given actor (Only Cases this is Called). /// Logic done after returning the original hook executes After all equipment/weapon/crest data is loaded into the Actors BaseData. /// - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); - private readonly Hook _loadGearsetDataHook = null!; + private delegate ulong LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); + private readonly Hook _loadGearsetDataHook; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { @@ -115,30 +116,30 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Glamourer-Invoked on 0x{drawObject.Address:X} on {slot} with item data {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) + private ulong LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) { var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - Model drawObject = drawDataContainer->OwnerObject->DrawObject; + var drawObject = drawDataContainer->OwnerObject->DrawObject; GearsetDataLoadedEvent.Invoke(drawObject); - // Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } - // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. - private string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) + + private static string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) { - string ret = + var ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId}"; - for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) + for (var offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { - LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); - int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset - byte* dyePtr = (byte*)&gearsetData + dyeOffset; - ret += $"\nEquipSlot {((EquipSlot)(dyeOffset - 60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; + var equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); + var dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset + var dyePtr = (byte*)&gearsetData + dyeOffset; + ret += $"\nEquipSlot {(EquipSlot)(dyeOffset - 60)}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; } return ret; } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index a869a09..98dfa19 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -668,7 +668,7 @@ public class CommandService : IDisposable, IApiService if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } else { @@ -677,7 +677,7 @@ public class CommandService : IDisposable, IApiService if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index ebf347f..42058d2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -17,7 +17,7 @@ public class StateEditor( InternalStateEditor editor, StateApplier applier, StateChanged stateChanged, - StateUpdated stateUpdated, + StateFinalized stateFinalized, JobChangeState jobChange, Configuration config, ItemManager items, @@ -25,12 +25,12 @@ public class StateEditor( ModSettingApplier modApplier, GPoseService gPose) : IDesignEditor { - protected readonly InternalStateEditor Editor = editor; - protected readonly StateApplier Applier = applier; - protected readonly StateChanged StateChanged = stateChanged; - protected readonly StateUpdated StateUpdated = stateUpdated; - protected readonly Configuration Config = config; - protected readonly ItemManager Items = items; + protected readonly InternalStateEditor Editor = editor; + protected readonly StateApplier Applier = applier; + protected readonly StateChanged StateChanged = stateChanged; + protected readonly StateFinalized StateFinalized = stateFinalized; + protected readonly Configuration Config = config; + protected readonly ItemManager Items = items; /// Turn an actor to. public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, @@ -43,7 +43,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); - StateUpdated.Invoke(StateFinalizationType.ModelChange, actors); + StateFinalized.Invoke(StateFinalizationType.ModelChange, actors); } /// @@ -383,7 +383,7 @@ public class StateEditor( Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } - if (settings.ResetMaterials || (!settings.RespectManual && mergedDesign.ResetAdvancedDyes)) + if (settings.ResetMaterials || !settings.RespectManual && mergedDesign.ResetAdvancedDyes) state.Materials.Clear(); foreach (var (key, value) in mergedDesign.Design.Materials) @@ -420,8 +420,8 @@ public class StateEditor( Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later - if(settings.SendStateUpdate) - StateUpdated.Invoke(StateFinalizationType.DesignApplied, actors); + if (settings.IsFinal) + StateFinalized.Invoke(StateFinalizationType.DesignApplied, actors); return; @@ -442,7 +442,8 @@ public class StateEditor( if (!settings.MergeLinks || design is not Design d) merged = new MergedDesign(design); else - merged = merger.Merge(d.AllLinks(true), state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, state.BaseData, + merged = merger.Merge(d.AllLinks(true), state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, + state.BaseData, false, Config.AlwaysApplyAssociatedMods); ApplyDesign(data, merged, settings with @@ -460,7 +461,7 @@ public class StateEditor( if (!Config.ChangeEntireItem || !settings.Source.IsManual()) return; - var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); + var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); // Do not change Shields to nothing. if (mh.Type is FullEquipType.Sword) return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d8648bb..3a6d6ef 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -41,7 +41,7 @@ public class StateListener : IDisposable private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; private readonly WeaponVisibilityChanged _weaponVisibility; - private readonly StateUpdated _stateUpdated; + private readonly StateFinalized _stateFinalized; private readonly AutoDesignApplier _autoDesignApplier; private readonly FunModule _funModule; private readonly HumanModelList _humans; @@ -63,7 +63,7 @@ public class StateListener : IDisposable WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, - CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateUpdated stateUpdated) + CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized) { _manager = manager; _items = items; @@ -88,7 +88,7 @@ public class StateListener : IDisposable _condition = condition; _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; - _stateUpdated = stateUpdated; + _stateFinalized = stateFinalized; Subscribe(); } @@ -281,7 +281,7 @@ public class StateListener : IDisposable _objects.Update(); if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) - _stateUpdated.Invoke(StateFinalizationType.Gearset, actors); + _stateFinalized.Invoke(StateFinalizationType.Gearset, actors); } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 948e225..9b71586 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -18,20 +18,20 @@ using Penumbra.GameData.Interop; namespace Glamourer.State; public sealed class StateManager( - ActorManager _actors, + ActorManager actors, ItemManager items, - StateChanged @event, - StateUpdated @event2, + StateChanged changeEvent, + StateFinalized finalizeEvent, StateApplier applier, InternalStateEditor editor, - HumanModelList _humans, - IClientState _clientState, + HumanModelList humans, + IClientState clientState, Configuration config, JobChangeState jobChange, DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) - : StateEditor(editor, applier, @event, @event2, jobChange, config, items, merger, modApplier, gPose), + : StateEditor(editor, applier, changeEvent, finalizeEvent, jobChange, config, items, merger, modApplier, gPose), IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -62,7 +62,7 @@ public sealed class StateManager( /// public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state) - => GetOrCreate(actor.GetIdentifier(_actors), actor, out state); + => GetOrCreate(actor.GetIdentifier(actors), actor, out state); /// Try to obtain or create a new state for an existing actor. Returns false if no state could be created. public unsafe bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state) @@ -82,7 +82,7 @@ public sealed class StateManager( ModelData = FromActor(actor, true, false), BaseData = FromActor(actor, false, false), LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0), - LastTerritory = _clientState.TerritoryType, + LastTerritory = clientState.TerritoryType, }; // state.Identifier is owned. _states.Add(state.Identifier, state); @@ -115,7 +115,7 @@ public sealed class StateManager( // Model ID is only unambiguously contained in the game object. // The draw object only has the object type. // TODO reverse search model data to get model id from model. - if (!_humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) + if (!humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) { ret.LoadNonHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, (nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0])); @@ -236,7 +236,7 @@ public sealed class StateManager( public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); - public void ResetState(ActorState state, StateSource source, uint key = 0, bool stateUpdate = false) + public void ResetState(ActorState state, StateSource source, uint key = 0, bool isFinal = false) { if (!state.Unlock(key)) return; @@ -278,8 +278,8 @@ public sealed class StateManager( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. - if(stateUpdate) - StateUpdated.Invoke(StateFinalizationType.Revert, actors); + if(isFinal) + StateFinalized.Invoke(StateFinalizationType.Revert, actors); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -306,7 +306,7 @@ public sealed class StateManager( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertAdvanced, actors); + StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, actors); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -325,7 +325,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertCustomize, actors); + StateFinalized.Invoke(StateFinalizationType.RevertCustomize, actors); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -376,7 +376,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertEquipment, actors); + StateFinalized.Invoke(StateFinalizationType.RevertEquipment, actors); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -453,23 +453,23 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool stateUpdate = false) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isFinal = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source, stateUpdate); + ReapplyState(actor, state, forceRedraw, source, isFinal); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool stateUpdate) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isFinal) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(stateUpdate) - StateUpdated.Invoke(StateFinalizationType.Reapply, data); + if(isFinal) + StateFinalized.Invoke(StateFinalizationType.Reapply, data); } /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. @@ -490,7 +490,7 @@ public sealed class StateManager( || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); // invoke the automation update based on what reset is. - StateUpdated.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); + StateFinalized.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); } public void DeleteState(ActorIdentifier identifier)