using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType; namespace CustomizePlus.GameData.Data; public readonly unsafe struct Model : IEquatable { private Model(nint address) => Address = address; public readonly nint Address; public static readonly Model Null = new(0); public DrawObject* AsDrawObject => (DrawObject*)Address; public CharacterBase* AsCharacterBase => (CharacterBase*)Address; public Weapon* AsWeapon => (Weapon*)Address; public Human* AsHuman => (Human*)Address; public static implicit operator Model(nint? pointer) => new(pointer ?? nint.Zero); public static implicit operator Model(Object* pointer) => new((nint)pointer); public static implicit operator Model(DrawObject* pointer) => new((nint)pointer); public static implicit operator Model(Human* pointer) => new((nint)pointer); public static implicit operator Model(CharacterBase* pointer) => new((nint)pointer); public static implicit operator nint(Model model) => model.Address; public bool Valid => Address != nint.Zero; public bool IsCharacterBase => Valid && AsDrawObject->Object.GetObjectType() == ObjectType.CharacterBase; public bool IsHuman => IsCharacterBase && AsCharacterBase->GetModelType() == CharacterBase.ModelType.Human; public bool IsWeapon => IsCharacterBase && AsCharacterBase->GetModelType() == CharacterBase.ModelType.Weapon; public static implicit operator bool(Model actor) => actor.Address != nint.Zero; public static bool operator true(Model actor) => actor.Address != nint.Zero; public static bool operator false(Model actor) => actor.Address == nint.Zero; public static bool operator !(Model actor) => actor.Address == nint.Zero; public bool Equals(Model other) => Address == other.Address; public override bool Equals(object? obj) => obj is Model other && Equals(other); public override int GetHashCode() => Address.GetHashCode(); public static bool operator ==(Model lhs, Model rhs) => lhs.Address == rhs.Address; public static bool operator !=(Model lhs, Model rhs) => lhs.Address != rhs.Address; /* /// Only valid for humans. public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; public Customize GetCustomize() => *(Customize*)&AsHuman->Customize; public (Model Address, CharacterWeapon Data) GetMainhand() { Model weapon = AsDrawObject->Object.ChildObject; return !weapon.IsWeapon ? (Null, CharacterWeapon.Empty) : (weapon, new CharacterWeapon(weapon.AsWeapon->ModelSetId, weapon.AsWeapon->SecondaryId, (Variant)weapon.AsWeapon->Variant, (StainId)weapon.AsWeapon->ModelUnknown)); } public (Model Address, CharacterWeapon Data) GetOffhand() { var mainhand = AsDrawObject->Object.ChildObject; if (mainhand == null) return (Null, CharacterWeapon.Empty); Model offhand = mainhand->NextSiblingObject; if (offhand == mainhand || !offhand.IsWeapon) return (Null, CharacterWeapon.Empty); return (offhand, new CharacterWeapon(offhand.AsWeapon->ModelSetId, offhand.AsWeapon->SecondaryId, (Variant)offhand.AsWeapon->Variant, (StainId)offhand.AsWeapon->ModelUnknown)); } /// Obtain the mainhand and offhand and their data by guesstimating which child object is which. public (Model Mainhand, Model Offhand, CharacterWeapon MainData, CharacterWeapon OffData) GetWeapons() { var (first, second, count) = GetChildrenWeapons(); switch (count) { case 0: return (Null, Null, CharacterWeapon.Empty, CharacterWeapon.Empty); case 1: return (first, Null, new CharacterWeapon(first.AsWeapon->ModelSetId, first.AsWeapon->SecondaryId, (Variant)first.AsWeapon->Variant, (StainId)first.AsWeapon->ModelUnknown), CharacterWeapon.Empty); default: var (main, off) = DetermineMainhand(first, second); var mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, (Variant)main.AsWeapon->Variant, (StainId)main.AsWeapon->ModelUnknown); var offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, (Variant)off.AsWeapon->Variant, (StainId)off.AsWeapon->ModelUnknown); return (main, off, mainData, offData); } } /// Obtain the mainhand and offhand and their data by using the drawdata container from the corresponding actor. public (Model Mainhand, Model Offhand, CharacterWeapon MainData, CharacterWeapon OffData) GetWeapons(Actor actor) { if (!Valid || !actor.IsCharacter || actor.Model.Address != Address) return (Null, Null, CharacterWeapon.Empty, CharacterWeapon.Empty); Model main = actor.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).DrawObject; var mainData = CharacterWeapon.Empty; if (main.IsWeapon) mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, (Variant)main.AsWeapon->Variant, (StainId)main.AsWeapon->ModelUnknown); else main = Null; Model off = actor.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; var offData = CharacterWeapon.Empty; if (off.IsWeapon) offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, (Variant)off.AsWeapon->Variant, (StainId)off.AsWeapon->ModelUnknown); else off = Null; return (main, off, mainData, offData); } private (Model, Model, int) GetChildrenWeapons() { Span weapons = stackalloc Model[2]; weapons[0] = Null; weapons[1] = Null; var count = 0; if (!Valid || AsDrawObject->Object.ChildObject == null) return (weapons[0], weapons[1], count); Model starter = AsDrawObject->Object.ChildObject; var iterator = starter; do { if (iterator.IsWeapon) weapons[count++] = iterator; if (count == 2) return (weapons[0], weapons[1], count); iterator = iterator.AsDrawObject->Object.NextSiblingObject; } while (iterator.Address != starter.Address); return (weapons[0], weapons[1], count); } /// I don't know a safe way to do this but in experiments this worked. /// The first uint at +0x8 was set to non-zero for the mainhand and zero for the offhand. private static (Model Mainhand, Model Offhand) DetermineMainhand(Model first, Model second) { var discriminator1 = *(ulong*)(first.Address + 0x10); var discriminator2 = *(ulong*)(second.Address + 0x10); return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); } */ public override string ToString() => $"0x{Address:X}"; }