diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index a5a46e6..9e9a043 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -23,7 +23,7 @@ public unsafe class ChangeCustomizeService : EventWrapper _original; /// Check whether we in a manual customize update, in which case we need to not toggle certain flags. - public static readonly ThreadLocal InUpdate = new(() => false); + public static readonly InMethodChecker InUpdate = new(); public enum Priority { @@ -70,9 +70,8 @@ public unsafe class ChangeCustomizeService : EventWrapper(new Customize(*(CustomizeData*)data)); Invoke(this, (Model)human, customize); diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs new file mode 100644 index 0000000..edc61a5 --- /dev/null +++ b/Glamourer/Interop/CrestService.cs @@ -0,0 +1,92 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using Glamourer.Interop.Structs; +using OtterGui.Classes; +using Penumbra.GameData.Enums; + +namespace Glamourer.Interop; + +/// +/// Triggered when the crest visibility is updated on a model. +/// +/// Parameter is the model with an update. +/// Parameter is the equipment slot changed. +/// Parameter is the whether the crest will be shown. +/// +/// +public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority>, IDisposable +{ + public enum Priority + { + /// + StateListener = 0, + } + + public CrestService(IGameInteropProvider interop) + : base(nameof(CrestService)) + { + _humanSetFreeCompanyCrestVisibleOnSlot = + interop.HookFromAddress(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); + _weaponSetFreeCompanyCrestVisibleOnSlot = + interop.HookFromAddress(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); + _humanSetFreeCompanyCrestVisibleOnSlot.Enable(); + _weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); + } + + protected override void Dispose(bool _) + { + _humanSetFreeCompanyCrestVisibleOnSlot.Dispose(); + _weaponSetFreeCompanyCrestVisibleOnSlot.Dispose(); + } + + public void Invoke(Model model, EquipSlot slot, ref bool visible) + { + var ret = new Ref(visible); + Invoke(this, model, slot, ret); + visible = ret; + } + + public void UpdateCrest(Model drawObject, EquipSlot slot, bool crest) + { + using var _ = _inUpdate.EnterMethod(); + drawObject.SetFreeCompanyCrestVisibleOnSlot(slot, crest); + } + + private readonly InMethodChecker _inUpdate = new(); + + private delegate void SetCrestDelegateIntern(nint drawObject, byte slot, byte visible); + + [Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] + private readonly nint* _humanVTable = null!; + + [Signature(global::Penumbra.GameData.Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)] + private readonly nint* _weaponVTable = null!; + + private readonly Hook _humanSetFreeCompanyCrestVisibleOnSlot; + private readonly Hook _weaponSetFreeCompanyCrestVisibleOnSlot; + + private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + { + var slot = ((uint)slotIdx).ToEquipSlot(); + var rVisible = visible != 0; + var inUpdate = _inUpdate.InMethod; + if (!inUpdate) + Invoke(drawObject, slot, ref rVisible); + Glamourer.Log.Excessive( + $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + _humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); + } + + private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + { + var rVisible = visible != 0; + var inUpdate = _inUpdate.InMethod; + if (!inUpdate) + Invoke(drawObject, EquipSlot.BothHand, ref rVisible); + Glamourer.Log.Excessive( + $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + _weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); + } +} diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index f336f5a..f5a0ec0 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -21,9 +21,7 @@ public unsafe class UpdateSlotService : IDisposable } public void Dispose() - { - _flagSlotForUpdateHook.Dispose(); - } + => _flagSlotForUpdateHook.Dispose(); public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) { diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 092d8c2..93a9854 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -98,6 +98,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3fdf4f7..3cef72a 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -490,7 +490,7 @@ public class StateListener : IDisposable private void OnVisorChange(Model model, Ref value) { // Skip updates when in customize update. - if (ChangeCustomizeService.InUpdate.IsValueCreated && ChangeCustomizeService.InUpdate.Value) + if (ChangeCustomizeService.InUpdate.InMethod) return; // Find appropriate actor and state. diff --git a/OtterGui b/OtterGui index 3e2d4ae..f624aca 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3e2d4ae934694918d312280d62127cf1a55b03e4 +Subproject commit f624aca526bd1f36364d63443ed1c6e83499b8b7