- Add the CustomizeData struct to ICharacter

- API 15 note the Customize array
This commit is contained in:
Infi 2026-02-05 00:20:39 +01:00
parent 33a7cdefa8
commit d8a13a72aa
2 changed files with 344 additions and 19 deletions

View file

@ -0,0 +1,314 @@
using Dalamud.Game.ClientState.Objects.Types;
namespace Dalamud.Game.ClientState.Customize;
/// <summary>
/// This collection represents customization data a <see cref="ICharacter"/> has.
/// </summary>
public interface ICustomizeData
{
/// <summary>
/// Gets the current race.
/// E.g., Miqo'te, Aura
/// </summary>
public byte Race { get; }
/// <summary>
/// Gets the current sex.
/// </summary>
public byte Sex { get; }
/// <summary>
/// Gets the current body type.
/// </summary>
public byte BodyType { get; }
/// <summary>
/// Gets the current height.
/// 0 to 100
/// </summary>
public byte Height { get; }
/// <summary>
/// Gets the current tribe.
/// E.g., Seeker of the Sun, Keeper of the Moon
/// </summary>
public byte Tribe { get; }
/// <summary>
/// Gets the current face.
/// 1 to 4
/// </summary>
public byte Face { get; }
/// <summary>
/// Gets the current hairstyle.
/// </summary>
public byte Hairstyle { get; }
/// <summary>
/// Gets the current skin color.
/// </summary>
public byte SkinColor { get; }
/// <summary>
/// Gets the current color of the left eye.
/// </summary>
public byte EyeColorLeft { get; }
/// <summary>
/// Gets the current color of the right eye.
/// </summary>
public byte EyeColorRight { get; }
/// <summary>
/// Gets the current main hair color.
/// </summary>
public byte HairColor { get; }
/// <summary>
/// Gets the current highlight hair color.
/// </summary>
public byte HighlightsColor { get; }
/// <summary>
/// Gets the current tattoo color.
/// </summary>
public byte TattooColor { get; }
/// <summary>
/// Gets the current eyebrow type.
/// </summary>
public byte Eyebrows { get; }
/// <summary>
/// Gets the current nose type.
/// </summary>
public byte Nose { get; }
/// <summary>
/// Gets the current jaw type.
/// </summary>
public byte Jaw { get; }
/// <summary>
/// Gets the current lip color fur pattern.
/// </summary>
public byte LipColorFurPattern { get; }
/// <summary>
/// Gets the current muscle mass value.
/// </summary>
public byte MuscleMass { get; }
/// <summary>
/// Gets the current tail type.
/// </summary>
public byte TailShape { get; }
/// <summary>
/// Gets the current bust size.
/// 0 to 100
/// </summary>
public byte BustSize { get; }
/// <summary>
/// Gets the current color of the face paint.
/// </summary>
public byte FacePaintColor { get; }
/// <summary>
/// Gets a value indicating whether highlight color is used.
/// </summary>
public bool Highlights { get; }
/// <summary>
/// Gets a value indicating whether this facial feature is used.
/// </summary>
public bool FacialFeature1 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature2 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature3 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature4 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature5 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature6 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature7 { get; }
/// <summary>
/// Gets a value indicating whether the legacy tattoo is used.
/// </summary>
public bool LegacyTattoo { get; }
/// <summary>
/// Gets the current eye shape type.
/// </summary>
public byte EyeShape { get; }
/// <summary>
/// Gets a value indicating whether small iris is used.
/// </summary>
public bool SmallIris { get; }
/// <summary>
/// Gets the current mouth type.
/// </summary>
public byte Mouth { get; }
/// <summary>
/// Gets a value indicating whether lipstick is used.
/// </summary>
public bool Lipstick { get; }
/// <summary>
/// Gets the current face paint type.
/// </summary>
public byte FacePaint { get; }
/// <summary>
/// Gets a value indicating whether face paint reversed is used.
/// </summary>
public bool FacePaintReversed { get; }
}
/// <inheritdoc/>
internal readonly unsafe struct CustomizeData : ICustomizeData
{
/// <summary>
/// Gets or sets the address of the customize data struct in memory.
/// </summary>
public readonly nint Address;
/// <summary>
/// Initializes a new instance of the <see cref="CustomizeData"/> struct.
/// </summary>
/// <param name="address">Address of the status list.</param>
internal CustomizeData(nint address)
{
this.Address = address;
}
/// <inheritdoc/>
public byte Race => this.Struct->Race;
/// <inheritdoc/>
public byte Sex => this.Struct->Sex;
/// <inheritdoc/>
public byte BodyType => this.Struct->BodyType;
/// <inheritdoc/>
public byte Height => this.Struct->Height;
/// <inheritdoc/>
public byte Tribe => this.Struct->Tribe;
/// <inheritdoc/>
public byte Face => this.Struct->Face;
/// <inheritdoc/>
public byte Hairstyle => this.Struct->Hairstyle;
/// <inheritdoc/>
public byte SkinColor => this.Struct->SkinColor;
/// <inheritdoc/>
public byte EyeColorLeft => this.Struct->EyeColorLeft;
/// <inheritdoc/>
public byte EyeColorRight => this.Struct->EyeColorRight;
/// <inheritdoc/>
public byte HairColor => this.Struct->HairColor;
/// <inheritdoc/>
public byte HighlightsColor => this.Struct->HighlightsColor;
/// <inheritdoc/>
public byte TattooColor => this.Struct->TattooColor;
/// <inheritdoc/>
public byte Eyebrows => this.Struct->Eyebrows;
/// <inheritdoc/>
public byte Nose => this.Struct->Nose;
/// <inheritdoc/>
public byte Jaw => this.Struct->Jaw;
/// <inheritdoc/>
public byte LipColorFurPattern => this.Struct->LipColorFurPattern;
/// <inheritdoc/>
public byte MuscleMass => this.Struct->MuscleMass;
/// <inheritdoc/>
public byte TailShape => this.Struct->TailShape;
/// <inheritdoc/>
public byte BustSize => this.Struct->BustSize;
/// <inheritdoc/>
public byte FacePaintColor => this.Struct->FacePaintColor;
/// <inheritdoc/>
public bool Highlights => this.Struct->Highlights;
/// <inheritdoc/>
public bool FacialFeature1 => this.Struct->FacialFeature1;
/// <inheritdoc/>
public bool FacialFeature2 => this.Struct->FacialFeature2;
/// <inheritdoc/>
public bool FacialFeature3 => this.Struct->FacialFeature3;
/// <inheritdoc/>
public bool FacialFeature4 => this.Struct->FacialFeature4;
/// <inheritdoc/>
public bool FacialFeature5 => this.Struct->FacialFeature5;
/// <inheritdoc/>
public bool FacialFeature6 => this.Struct->FacialFeature6;
/// <inheritdoc/>
public bool FacialFeature7 => this.Struct->FacialFeature7;
/// <inheritdoc/>
public bool LegacyTattoo => this.Struct->LegacyTattoo;
/// <inheritdoc/>
public byte EyeShape => this.Struct->EyeShape;
/// <inheritdoc/>
public bool SmallIris => this.Struct->SmallIris;
/// <inheritdoc/>
public byte Mouth => this.Struct->Mouth;
/// <inheritdoc/>
public bool Lipstick => this.Struct->Lipstick;
/// <inheritdoc/>
public byte FacePaint => this.Struct->FacePaint;
/// <inheritdoc/>
public bool FacePaintReversed => this.Struct->FacePaintReversed;
/// <summary>
/// Gets the underlying structure.
/// </summary>
internal FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData* Struct =>
(FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData*)this.Address;
}

View file

@ -1,6 +1,8 @@
using Dalamud.Data;
using Dalamud.Game.ClientState.Customize;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Utility;
using Lumina.Excel;
using Lumina.Excel.Sheets;
@ -13,68 +15,73 @@ namespace Dalamud.Game.ClientState.Objects.Types;
public interface ICharacter : IGameObject
{
/// <summary>
/// Gets the current HP of this Chara.
/// Gets the current HP of this character.
/// </summary>
public uint CurrentHp { get; }
/// <summary>
/// Gets the maximum HP of this Chara.
/// Gets the maximum HP of this character.
/// </summary>
public uint MaxHp { get; }
/// <summary>
/// Gets the current MP of this Chara.
/// Gets the current MP of this character.
/// </summary>
public uint CurrentMp { get; }
/// <summary>
/// Gets the maximum MP of this Chara.
/// Gets the maximum MP of this character.
/// </summary>
public uint MaxMp { get; }
/// <summary>
/// Gets the current GP of this Chara.
/// Gets the current GP of this character.
/// </summary>
public uint CurrentGp { get; }
/// <summary>
/// Gets the maximum GP of this Chara.
/// Gets the maximum GP of this character.
/// </summary>
public uint MaxGp { get; }
/// <summary>
/// Gets the current CP of this Chara.
/// Gets the current CP of this character.
/// </summary>
public uint CurrentCp { get; }
/// <summary>
/// Gets the maximum CP of this Chara.
/// Gets the maximum CP of this character.
/// </summary>
public uint MaxCp { get; }
/// <summary>
/// Gets the shield percentage of this Chara.
/// Gets the shield percentage of this character.
/// </summary>
public byte ShieldPercentage { get; }
/// <summary>
/// Gets the ClassJob of this Chara.
/// Gets the ClassJob of this character.
/// </summary>
public RowRef<ClassJob> ClassJob { get; }
/// <summary>
/// Gets the level of this Chara.
/// Gets the level of this character.
/// </summary>
public byte Level { get; }
/// <summary>
/// Gets a byte array describing the visual appearance of this Chara.
/// Gets a byte array describing the visual appearance of this character.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize { get; }
/// <summary>
/// Gets the Free Company tag of this chara.
/// Gets the underlying CustomizeData struct for this character.
/// </summary>
public ICustomizeData CustomizeData { get; }
/// <summary>
/// Gets the Free Company tag of this character.
/// </summary>
public SeString CompanyTag { get; }
@ -92,12 +99,12 @@ public interface ICharacter : IGameObject
/// Gets the status flags.
/// </summary>
public StatusFlags StatusFlags { get; }
/// <summary>
/// Gets the current mount for this character. Will be <c>null</c> if the character doesn't have a mount.
/// </summary>
public RowRef<Mount>? CurrentMount { get; }
/// <summary>
/// Gets the current minion summoned for this character. Will be <c>null</c> if the character doesn't have a minion.
/// This method *will* return information about a spawned (but invisible) minion, e.g. if the character is riding a
@ -116,7 +123,7 @@ internal unsafe class Character : GameObject, ICharacter
/// This represents a non-static entity.
/// </summary>
/// <param name="address">The address of this character in memory.</param>
internal Character(IntPtr address)
internal Character(nint address)
: base(address)
{
}
@ -155,8 +162,12 @@ internal unsafe class Character : GameObject, ICharacter
public byte Level => this.Struct->CharacterData.Level;
/// <inheritdoc/>
[Api15ToDo("Do not allocate on each call, use the CS Span and let consumers do allocation if necessary")]
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
/// <inheritdoc/>
public ICustomizeData CustomizeData => new CustomizeData((nint)(&this.Struct->DrawData.CustomizeData));
/// <inheritdoc/>
public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag);
@ -183,14 +194,14 @@ internal unsafe class Character : GameObject, ICharacter
(this.Struct->IsAllianceMember ? StatusFlags.AllianceMember : StatusFlags.None) |
(this.Struct->IsFriend ? StatusFlags.Friend : StatusFlags.None) |
(this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None);
/// <inheritdoc />
public RowRef<Mount>? CurrentMount
{
get
{
if (this.Struct->IsNotMounted()) return null; // just for safety.
var mountId = this.Struct->Mount.MountId;
return mountId == 0 ? null : LuminaUtils.CreateRef<Mount>(mountId);
}
@ -201,7 +212,7 @@ internal unsafe class Character : GameObject, ICharacter
{
get
{
if (this.Struct->CompanionObject != null)
if (this.Struct->CompanionObject != null)
return LuminaUtils.CreateRef<Companion>(this.Struct->CompanionObject->BaseId);
// this is only present if a minion is summoned but hidden (e.g. the player's on a mount).