Add interop for Actor and Model.

This commit is contained in:
Ottermandias 2023-11-25 23:54:37 +01:00
parent 6f4a7661d7
commit 512d0a1a5f
6 changed files with 98 additions and 27 deletions

View file

@ -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<CrestFlag> AllRelevantSet = Enum.GetValues<CrestFlag>().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,
};
}

View file

@ -118,6 +118,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
case DesignChanged.Type.ApplyCustomize:
case DesignChanged.Type.ApplyEquip:
case DesignChanged.Type.ApplyStain:
case DesignChanged.Type.ApplyCrest:
case DesignChanged.Type.Customize:
case DesignChanged.Type.Equip:
case DesignChanged.Type.ChangedColor:

View file

@ -1,4 +1,3 @@
using System;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
@ -16,20 +15,6 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui;
[Flags]
public enum DataChange : byte
{
None = 0x00,
Item = 0x01,
Stain = 0x02,
ApplyItem = 0x04,
ApplyStain = 0x08,
Item2 = 0x10,
Stain2 = 0x20,
ApplyItem2 = 0x40,
ApplyStain2 = 0x80,
}
public static class UiHelpers
{
/// <summary> Open a combo popup with another method than the combo itself. </summary>

View file

@ -106,6 +106,9 @@ public readonly unsafe struct Actor : IEquatable<Actor>
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<Actor>
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}";
}

View file

@ -91,6 +91,9 @@ public readonly unsafe struct Model : IEquatable<Model>
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<Model>
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<CharacterBase*, byte, byte>)((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<CharacterBase*, byte, byte, void>)((nint*)characterBase->VTable)[96];
setter(characterBase, index, visible ? (byte)1 : (byte)0);
}
public override string ToString()
=> $"0x{Address:X}";
}

View file

@ -80,7 +80,8 @@ public class ActorState
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
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];