From 512d0a1a5f9650feb296558d841c8660c8e89cd2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 23:54:37 +0100 Subject: [PATCH] Add interop for Actor and Model. --- Glamourer.GameData/Structs/CrestFlag.cs | 52 +++++++++++++++---- .../DesignTab/DesignFileSystemSelector.cs | 1 + Glamourer/Gui/UiHelpers.cs | 15 ------ Glamourer/Interop/Structs/Actor.cs | 19 +++++++ Glamourer/Interop/Structs/Model.cs | 32 ++++++++++++ Glamourer/State/ActorState.cs | 6 ++- 6 files changed, 98 insertions(+), 27 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 17275f5..d39fd0a 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -1,6 +1,7 @@ using System; -using System.Diagnostics.CodeAnalysis; -using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using Penumbra.GameData.Enums; namespace Glamourer.Structs; @@ -15,23 +16,37 @@ public enum CrestFlag : ushort Feet = 0x0010, Ears = 0x0020, Neck = 0x0040, - Wrist = 0x0080, + Wrists = 0x0080, RFinger = 0x0100, LFinger = 0x0200, - Mainhand = 0x0400, - Offhand = 0x0800, + MainHand = 0x0400, + OffHand = 0x0800, } public static class CrestExtensions { public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); - public const CrestFlag AllRelevant = CrestFlag.Body; + 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 int ToIndex(this CrestFlag flag) + => BitOperations.TrailingZeroCount((uint)flag); + + public static int ToRelevantIndex(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => 0, + CrestFlag.Body => 1, + CrestFlag.OffHand => 2, + _ => -1, + }; public static CrestFlag ToCrestFlag(this EquipSlot slot) => slot switch { - EquipSlot.MainHand => CrestFlag.Mainhand, - EquipSlot.OffHand => CrestFlag.Offhand, + EquipSlot.MainHand => CrestFlag.MainHand, + EquipSlot.OffHand => CrestFlag.OffHand, EquipSlot.Head => CrestFlag.Head, EquipSlot.Body => CrestFlag.Body, EquipSlot.Hands => CrestFlag.Hands, @@ -39,12 +54,27 @@ public static class CrestExtensions EquipSlot.Feet => CrestFlag.Feet, EquipSlot.Ears => CrestFlag.Ears, EquipSlot.Neck => CrestFlag.Neck, - EquipSlot.Wrists => CrestFlag.Wrist, + EquipSlot.Wrists => CrestFlag.Wrists, EquipSlot.RFinger => CrestFlag.RFinger, EquipSlot.LFinger => CrestFlag.LFinger, _ => 0, }; - public static bool Valid(this CrestFlag crest) - => AllRelevant.HasFlag(crest); + public static EquipSlot ToSlot(this CrestFlag flag) + => flag switch + { + CrestFlag.MainHand => EquipSlot.MainHand, + CrestFlag.OffHand => EquipSlot.OffHand, + CrestFlag.Head => EquipSlot.Head, + CrestFlag.Body => EquipSlot.Body, + CrestFlag.Hands => EquipSlot.Hands, + CrestFlag.Legs => EquipSlot.Legs, + CrestFlag.Feet => EquipSlot.Feet, + CrestFlag.Ears => EquipSlot.Ears, + CrestFlag.Neck => EquipSlot.Neck, + CrestFlag.Wrists => EquipSlot.Wrists, + CrestFlag.RFinger => EquipSlot.RFinger, + CrestFlag.LFinger => EquipSlot.LFinger, + _ => 0, + }; } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index b323b63..b288303 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -118,6 +118,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Open a combo popup with another method than the combo itself. diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 1d1d172..ba7f132 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -106,6 +106,9 @@ public readonly unsafe struct Actor : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; + public bool GetCrest(EquipSlot slot) + => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; + public CharacterWeapon GetMainhand() => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value); @@ -115,6 +118,22 @@ public readonly unsafe struct Actor : IEquatable public Customize GetCustomize() => *(Customize*)&AsCharacter->DrawData.CustomizeData; + // TODO remove this when available in ClientStructs + private byte GetFreeCompanyCrestBitfield() + => ((byte*)Address)[0x1BBB]; + + private static byte CrestMask(EquipSlot slot) + => slot switch + { + EquipSlot.OffHand => 0x01, + EquipSlot.Head => 0x02, + EquipSlot.Body => 0x04, + EquipSlot.Hands => 0x08, + EquipSlot.Legs => 0x10, + EquipSlot.Feet => 0x20, + _ => 0x00, + }; + public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 77bf24e..811ff81 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -91,6 +91,9 @@ 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; @@ -195,6 +198,35 @@ 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 2cb3f2a..f3372e8 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -80,7 +80,8 @@ public class ActorState /// This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. private readonly StateChanged.Source[] _sources = Enumerable - .Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray(); + .Repeat(StateChanged.Source.Game, + EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + CrestExtensions.AllRelevantSet.Count).ToArray(); internal ActorState(ActorIdentifier identifier) => Identifier = identifier.CreatePermanent(); @@ -88,6 +89,9 @@ public class ActorState public ref StateChanged.Source this[EquipSlot slot, bool stain] => 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()]; + public ref StateChanged.Source this[CustomizeIndex type] => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];