diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 3ef9f1e..11a7260 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using Penumbra.GameData.Enums; namespace Glamourer.Structs; @@ -23,17 +22,22 @@ public enum CrestFlag : ushort OffHand = 0x0800, } +public enum CrestType : byte +{ + None, + Human, + Mainhand, + Offhand, +}; + public static class CrestExtensions { public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; - public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => f.ToRelevantIndex() >= 0).ToArray(); + public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => AllRelevant.HasFlag(f)).ToArray(); - public static int ToIndex(this CrestFlag flag) - => BitOperations.TrailingZeroCount((uint)flag); - - public static int ToRelevantIndex(this CrestFlag flag) + public static int ToInternalIndex(this CrestFlag flag) => flag switch { CrestFlag.Head => 0, @@ -42,6 +46,24 @@ public static class CrestExtensions _ => -1, }; + public static (CrestType Type, byte Index) ToIndex(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => (CrestType.Human, 0), + CrestFlag.Body => (CrestType.Human, 1), + CrestFlag.Hands => (CrestType.Human, 2), + CrestFlag.Legs => (CrestType.Human, 3), + CrestFlag.Feet => (CrestType.Human, 4), + CrestFlag.Ears => (CrestType.None, 0), + CrestFlag.Neck => (CrestType.None, 0), + CrestFlag.Wrists => (CrestType.None, 0), + CrestFlag.RFinger => (CrestType.None, 0), + CrestFlag.LFinger => (CrestType.None, 0), + CrestFlag.MainHand => (CrestType.None, 0), + CrestFlag.OffHand => (CrestType.Offhand, 0), + _ => (CrestType.None, 0), + }; + public static CrestFlag ToCrestFlag(this EquipSlot slot) => slot switch { diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 7d946fb..52385b8 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -21,6 +21,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; +using Glamourer.Structs; using Glamourer.Unlocks; using Glamourer.Utility; using ImGuiNET; @@ -43,6 +44,7 @@ public unsafe class DebugTab : ITab private readonly VisorService _visorService; private readonly ChangeCustomizeService _changeCustomizeService; private readonly UpdateSlotService _updateSlotService; + private readonly CrestService _crestService; private readonly WeaponService _weaponService; private readonly MetaService _metaService; private readonly InventoryService _inventoryService; @@ -50,7 +52,7 @@ public unsafe class DebugTab : ITab private readonly ObjectManager _objectManager; private readonly GlamourerIpc _ipc; private readonly CodeService _code; - private readonly ImportService _importService; + private readonly ImportService _importService; private readonly ItemManager _items; private readonly ActorService _actors; @@ -82,7 +84,7 @@ public unsafe class DebugTab : ITab PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService, - HumanModelList humans, FunModule funModule) + HumanModelList humans, FunModule funModule, CrestService crestService) { _changeCustomizeService = changeCustomizeService; _visorService = visorService; @@ -107,10 +109,11 @@ public unsafe class DebugTab : ITab _customizeUnlocks = customizeUnlocks; _itemUnlocks = itemUnlocks; _designConverter = designConverter; - _importService = importService; + _importService = importService; _inventoryService = inventoryService; _humans = humans; _funModule = funModule; + _crestService = crestService; } public ReadOnlySpan Label @@ -200,6 +203,7 @@ public unsafe class DebugTab : ITab DrawWetness(actor, model); DrawEquip(actor, model); DrawCustomize(actor, model); + DrawCrests(actor, model); } private string _objectFilter = string.Empty; @@ -477,6 +481,25 @@ public unsafe class DebugTab : ITab } } + private void DrawCrests(Actor actor, Model model) + { + using var id = ImRaii.PushId("Crests"); + foreach (var crestFlag in CrestExtensions.AllRelevantSet) + { + id.Push((int)crestFlag); + var modelCrest = CrestService.GetModelCrest(actor, crestFlag); + ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(modelCrest.ToString()); + + ImGui.TableNextColumn(); + if (model.IsHuman && ImGui.SmallButton("Toggle")) + _crestService.UpdateCrest(actor, crestFlag, !modelCrest); + + id.Pop(); + } + } + #endregion #region Penumbra diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index edc61a5..253a4ea 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -2,7 +2,10 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Interop.Structs; +using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -27,6 +30,7 @@ public sealed unsafe class CrestService : EventWrapper(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); _weaponSetFreeCompanyCrestVisibleOnSlot = @@ -48,10 +52,68 @@ public sealed unsafe class CrestService : EventWrapper)((nint*)model.AsCharacterBase->VTable)[95]; + return getter(model.AsHuman, index) != 0; + } + case CrestType.Offhand: + { + var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; + if (!model.IsWeapon) + return false; + + var getter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[95]; + return getter(model.AsWeapon, index) != 0; + } + } + + return false; + } + + public void UpdateCrest(Actor gameObject, CrestFlag slot, bool crest) + { + if (!gameObject.IsCharacter) + return; + + var (type, index) = slot.ToIndex(); + switch (type) + { + case CrestType.Human: + { + var model = gameObject.Model; + if (!model.IsHuman) + return; + + using var _ = _inUpdate.EnterMethod(); + var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; + setter(model.AsHuman, index, crest ? (byte)1 : (byte)0); + break; + } + case CrestType.Offhand: + { + var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; + if (!model.IsWeapon) + return; + + using var _ = _inUpdate.EnterMethod(); + var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; + setter(model.AsWeapon, index, crest ? (byte)1 : (byte)0); + break; + } + } } private readonly InMethodChecker _inUpdate = new(); diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index ba7f132..0a6196b 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -3,6 +3,7 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Customization; +using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -106,7 +107,7 @@ public readonly unsafe struct Actor : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; - public bool GetCrest(EquipSlot slot) + public bool GetCrest(CrestFlag slot) => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; public CharacterWeapon GetMainhand() @@ -122,15 +123,15 @@ public readonly unsafe struct Actor : IEquatable private byte GetFreeCompanyCrestBitfield() => ((byte*)Address)[0x1BBB]; - private static byte CrestMask(EquipSlot slot) + private static byte CrestMask(CrestFlag slot) => slot switch { - EquipSlot.OffHand => 0x01, - EquipSlot.Head => 0x02, - EquipSlot.Body => 0x04, - EquipSlot.Hands => 0x08, - EquipSlot.Legs => 0x10, - EquipSlot.Feet => 0x20, + CrestFlag.OffHand => 0x01, + CrestFlag.Head => 0x02, + CrestFlag.Body => 0x04, + CrestFlag.Hands => 0x08, + CrestFlag.Legs => 0x10, + CrestFlag.Feet => 0x20, _ => 0x00, }; diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 811ff81..77bf24e 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -91,9 +91,6 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; - public bool GetCrest(EquipSlot slot) - => IsFreeCompanyCrestVisibleOnSlot(slot); - public Customize GetCustomize() => *(Customize*)&AsHuman->Customize; @@ -198,35 +195,6 @@ public readonly unsafe struct Model : IEquatable return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); } - // TODO remove these when available in ClientStructs - private bool IsFreeCompanyCrestVisibleOnSlot(EquipSlot slot) - { - if (!IsCharacterBase) - return false; - - var index = (byte)slot.ToIndex(); - if (index >= 12) - return false; - - var characterBase = AsCharacterBase; - var getter = (delegate* unmanaged)((nint*)characterBase->VTable)[95]; - return getter(characterBase, index) != 0; - } - - public void SetFreeCompanyCrestVisibleOnSlot(EquipSlot slot, bool visible) - { - if (!IsCharacterBase) - return; - - var index = (byte)slot.ToIndex(); - if (index >= 12) - return; - - var characterBase = AsCharacterBase; - var setter = (delegate* unmanaged)((nint*)characterBase->VTable)[96]; - setter(characterBase, index, visible ? (byte)1 : (byte)0); - } - public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index f3372e8..3cd7cba 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -90,7 +90,7 @@ public class ActorState => ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; public ref StateChanged.Source this[CrestFlag slot] - => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToRelevantIndex()]; + => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; public ref StateChanged.Source this[CustomizeIndex type] => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];