diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index ea57515..8eb763e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -164,8 +164,9 @@ public class PenumbraChangedItemTooltip : IDisposable switch (type) { + case ChangedItemType.ItemOffhand: case ChangedItemType.Item: - if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item)) + if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; CreateTooltip(item, "[Glamourer] ", false); @@ -189,13 +190,14 @@ public class PenumbraChangedItemTooltip : IDisposable switch (type) { case ChangedItemType.Item: + case ChangedItemType.ItemOffhand: if (button is not MouseButton.Right) return; if (!Player(out var state)) return; - if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item)) + if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; ApplyItem(state, item); diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 708377b..caea959 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -1,7 +1,7 @@ using System; +using System.Threading; using Dalamud.Hooking; using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Glamourer.Interop.Structs; @@ -12,20 +12,27 @@ namespace Glamourer.Interop; public unsafe class WeaponService : IDisposable { - private readonly WeaponLoading _event; + private readonly WeaponLoading _event; + private readonly ThreadLocal _inUpdate = new(() => false); + + + private readonly delegate* unmanaged[Stdcall] + _original; + public WeaponService(WeaponLoading @event, IGameInteropProvider interop) { _event = @event; _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); + _original = + (delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void >) + DrawDataContainer.MemberFunctionPointers.LoadWeapon; _loadWeaponHook.Enable(); } public void Dispose() - { - _loadWeaponHook.Dispose(); - } + => _loadWeaponHook.Dispose(); // Weapons for a specific character are reloaded with this function. // slot is 0 for main hand, 1 for offhand, 2 for combat effects. @@ -42,30 +49,37 @@ public unsafe class WeaponService : IDisposable private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2, byte skipGameObject, byte unk4) { - var actor = (Actor)((nint*)drawData)[1]; - var weapon = new CharacterWeapon(weaponValue); - var equipSlot = slot switch + if (!_inUpdate.Value) { - 0 => EquipSlot.MainHand, - 1 => EquipSlot.OffHand, - _ => EquipSlot.Unknown, - }; + var actor = (Actor)((nint*)drawData)[1]; + var weapon = new CharacterWeapon(weaponValue); + var equipSlot = slot switch + { + 0 => EquipSlot.MainHand, + 1 => EquipSlot.OffHand, + _ => EquipSlot.Unknown, + }; - var tmpWeapon = weapon; - // First call the regular function. - if (equipSlot is not EquipSlot.Unknown) - _event.Invoke(actor, equipSlot, ref tmpWeapon); + var tmpWeapon = weapon; + // First call the regular function. + if (equipSlot is not EquipSlot.Unknown) + _event.Invoke(actor, equipSlot, ref tmpWeapon); - _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); - if (tmpWeapon.Value != weapon.Value) - { - if (tmpWeapon.Set.Id == 0) - tmpWeapon.Stain = 0; - _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); + _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); + if (tmpWeapon.Value != weapon.Value) + { + if (tmpWeapon.Set.Id == 0) + tmpWeapon.Stain = 0; + _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); + } + + Glamourer.Log.Excessive( + $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}"); + } + else + { + _original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4); } - - Glamourer.Log.Excessive( - $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}"); } // Load a specific weapon for a character by its data and slot. @@ -74,16 +88,21 @@ public unsafe class WeaponService : IDisposable switch (slot) { case EquipSlot.MainHand: + _inUpdate.Value = true; _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); + _inUpdate.Value = false; return; case EquipSlot.OffHand: + _inUpdate.Value = true; _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0); + _inUpdate.Value = false; return; case EquipSlot.BothHand: + _inUpdate.Value = true; _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0); + _inUpdate.Value = false; return; - // function can also be called with '2', but does not seem to ever be. } } diff --git a/Penumbra.Api b/Penumbra.Api index f9069df..80f9793 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f9069dfdf1f0a7011c3b0ea7c0be5330c42959dd +Subproject commit 80f9793ef2ddaa50246b7112fde4d9b2098d8823 diff --git a/Penumbra.GameData b/Penumbra.GameData index 4dd261f..e1a62d8 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 4dd261fe837bbe4b799b3ca3c0c8c178197bc48f +Subproject commit e1a62d8e6b4e1d8c482253ad14850fd3dc372d86