diff --git a/Dalamud/Game/ClientState/Customize/CustomizeData.cs b/Dalamud/Game/ClientState/Customize/CustomizeData.cs
new file mode 100644
index 000000000..644bec5e8
--- /dev/null
+++ b/Dalamud/Game/ClientState/Customize/CustomizeData.cs
@@ -0,0 +1,314 @@
+using Dalamud.Game.ClientState.Objects.Types;
+
+namespace Dalamud.Game.ClientState.Customize;
+
+///
+/// This collection represents customization data a has.
+///
+public interface ICustomizeData
+{
+ ///
+ /// Gets the current race.
+ /// E.g., Miqo'te, Aura
+ ///
+ public byte Race { get; }
+
+ ///
+ /// Gets the current sex.
+ ///
+ public byte Sex { get; }
+
+ ///
+ /// Gets the current body type.
+ ///
+ public byte BodyType { get; }
+
+ ///
+ /// Gets the current height.
+ /// 0 to 100
+ ///
+ public byte Height { get; }
+
+ ///
+ /// Gets the current tribe.
+ /// E.g., Seeker of the Sun, Keeper of the Moon
+ ///
+ public byte Tribe { get; }
+
+ ///
+ /// Gets the current face.
+ /// 1 to 4
+ ///
+ public byte Face { get; }
+
+ ///
+ /// Gets the current hairstyle.
+ ///
+ public byte Hairstyle { get; }
+
+ ///
+ /// Gets the current skin color.
+ ///
+ public byte SkinColor { get; }
+
+ ///
+ /// Gets the current color of the left eye.
+ ///
+ public byte EyeColorLeft { get; }
+
+ ///
+ /// Gets the current color of the right eye.
+ ///
+ public byte EyeColorRight { get; }
+
+ ///
+ /// Gets the current main hair color.
+ ///
+ public byte HairColor { get; }
+
+ ///
+ /// Gets the current highlight hair color.
+ ///
+ public byte HighlightsColor { get; }
+
+ ///
+ /// Gets the current tattoo color.
+ ///
+ public byte TattooColor { get; }
+
+ ///
+ /// Gets the current eyebrow type.
+ ///
+ public byte Eyebrows { get; }
+
+ ///
+ /// Gets the current nose type.
+ ///
+ public byte Nose { get; }
+
+ ///
+ /// Gets the current jaw type.
+ ///
+ public byte Jaw { get; }
+
+ ///
+ /// Gets the current lip color fur pattern.
+ ///
+ public byte LipColorFurPattern { get; }
+
+ ///
+ /// Gets the current muscle mass value.
+ ///
+ public byte MuscleMass { get; }
+
+ ///
+ /// Gets the current tail type.
+ ///
+ public byte TailShape { get; }
+
+ ///
+ /// Gets the current bust size.
+ /// 0 to 100
+ ///
+ public byte BustSize { get; }
+
+ ///
+ /// Gets the current color of the face paint.
+ ///
+ public byte FacePaintColor { get; }
+
+ ///
+ /// Gets a value indicating whether highlight color is used.
+ ///
+ public bool Highlights { get; }
+
+ ///
+ /// Gets a value indicating whether this facial feature is used.
+ ///
+ public bool FacialFeature1 { get; }
+
+ ///
+ public bool FacialFeature2 { get; }
+
+ ///
+ public bool FacialFeature3 { get; }
+
+ ///
+ public bool FacialFeature4 { get; }
+
+ ///
+ public bool FacialFeature5 { get; }
+
+ ///
+ public bool FacialFeature6 { get; }
+
+ ///
+ public bool FacialFeature7 { get; }
+
+ ///
+ /// Gets a value indicating whether the legacy tattoo is used.
+ ///
+ public bool LegacyTattoo { get; }
+
+ ///
+ /// Gets the current eye shape type.
+ ///
+ public byte EyeShape { get; }
+
+ ///
+ /// Gets a value indicating whether small iris is used.
+ ///
+ public bool SmallIris { get; }
+
+ ///
+ /// Gets the current mouth type.
+ ///
+ public byte Mouth { get; }
+
+ ///
+ /// Gets a value indicating whether lipstick is used.
+ ///
+ public bool Lipstick { get; }
+
+ ///
+ /// Gets the current face paint type.
+ ///
+ public byte FacePaint { get; }
+
+ ///
+ /// Gets a value indicating whether face paint reversed is used.
+ ///
+ public bool FacePaintReversed { get; }
+}
+
+///
+internal readonly unsafe struct CustomizeData : ICustomizeData
+{
+ ///
+ /// Gets or sets the address of the customize data struct in memory.
+ ///
+ public readonly nint Address;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Address of the status list.
+ internal CustomizeData(nint address)
+ {
+ this.Address = address;
+ }
+
+ ///
+ public byte Race => this.Struct->Race;
+
+ ///
+ public byte Sex => this.Struct->Sex;
+
+ ///
+ public byte BodyType => this.Struct->BodyType;
+
+ ///
+ public byte Height => this.Struct->Height;
+
+ ///
+ public byte Tribe => this.Struct->Tribe;
+
+ ///
+ public byte Face => this.Struct->Face;
+
+ ///
+ public byte Hairstyle => this.Struct->Hairstyle;
+
+ ///
+ public byte SkinColor => this.Struct->SkinColor;
+
+ ///
+ public byte EyeColorLeft => this.Struct->EyeColorLeft;
+
+ ///
+ public byte EyeColorRight => this.Struct->EyeColorRight;
+
+ ///
+ public byte HairColor => this.Struct->HairColor;
+
+ ///
+ public byte HighlightsColor => this.Struct->HighlightsColor;
+
+ ///
+ public byte TattooColor => this.Struct->TattooColor;
+
+ ///
+ public byte Eyebrows => this.Struct->Eyebrows;
+
+ ///
+ public byte Nose => this.Struct->Nose;
+
+ ///
+ public byte Jaw => this.Struct->Jaw;
+
+ ///
+ public byte LipColorFurPattern => this.Struct->LipColorFurPattern;
+
+ ///
+ public byte MuscleMass => this.Struct->MuscleMass;
+
+ ///
+ public byte TailShape => this.Struct->TailShape;
+
+ ///
+ public byte BustSize => this.Struct->BustSize;
+
+ ///
+ public byte FacePaintColor => this.Struct->FacePaintColor;
+
+ ///
+ public bool Highlights => this.Struct->Highlights;
+
+ ///
+ public bool FacialFeature1 => this.Struct->FacialFeature1;
+
+ ///
+ public bool FacialFeature2 => this.Struct->FacialFeature2;
+
+ ///
+ public bool FacialFeature3 => this.Struct->FacialFeature3;
+
+ ///
+ public bool FacialFeature4 => this.Struct->FacialFeature4;
+
+ ///
+ public bool FacialFeature5 => this.Struct->FacialFeature5;
+
+ ///
+ public bool FacialFeature6 => this.Struct->FacialFeature6;
+
+ ///
+ public bool FacialFeature7 => this.Struct->FacialFeature7;
+
+ ///
+ public bool LegacyTattoo => this.Struct->LegacyTattoo;
+
+ ///
+ public byte EyeShape => this.Struct->EyeShape;
+
+ ///
+ public bool SmallIris => this.Struct->SmallIris;
+
+ ///
+ public byte Mouth => this.Struct->Mouth;
+
+ ///
+ public bool Lipstick => this.Struct->Lipstick;
+
+ ///
+ public byte FacePaint => this.Struct->FacePaint;
+
+ ///
+ public bool FacePaintReversed => this.Struct->FacePaintReversed;
+
+ ///
+ /// Gets the underlying structure.
+ ///
+ internal FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData* Struct =>
+ (FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData*)this.Address;
+}
diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs
index 2002a16b8..f122f1f27 100644
--- a/Dalamud/Game/ClientState/Objects/Types/Character.cs
+++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs
@@ -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
{
///
- /// Gets the current HP of this Chara.
+ /// Gets the current HP of this character.
///
public uint CurrentHp { get; }
///
- /// Gets the maximum HP of this Chara.
+ /// Gets the maximum HP of this character.
///
public uint MaxHp { get; }
///
- /// Gets the current MP of this Chara.
+ /// Gets the current MP of this character.
///
public uint CurrentMp { get; }
///
- /// Gets the maximum MP of this Chara.
+ /// Gets the maximum MP of this character.
///
public uint MaxMp { get; }
///
- /// Gets the current GP of this Chara.
+ /// Gets the current GP of this character.
///
public uint CurrentGp { get; }
///
- /// Gets the maximum GP of this Chara.
+ /// Gets the maximum GP of this character.
///
public uint MaxGp { get; }
///
- /// Gets the current CP of this Chara.
+ /// Gets the current CP of this character.
///
public uint CurrentCp { get; }
///
- /// Gets the maximum CP of this Chara.
+ /// Gets the maximum CP of this character.
///
public uint MaxCp { get; }
///
- /// Gets the shield percentage of this Chara.
+ /// Gets the shield percentage of this character.
///
public byte ShieldPercentage { get; }
///
- /// Gets the ClassJob of this Chara.
+ /// Gets the ClassJob of this character.
///
public RowRef ClassJob { get; }
///
- /// Gets the level of this Chara.
+ /// Gets the level of this character.
///
public byte Level { get; }
///
- /// Gets a byte array describing the visual appearance of this Chara.
+ /// Gets a byte array describing the visual appearance of this character.
/// Indexed by .
///
public byte[] Customize { get; }
///
- /// Gets the Free Company tag of this chara.
+ /// Gets the underlying CustomizeData struct for this character.
+ ///
+ public ICustomizeData CustomizeData { get; }
+
+ ///
+ /// Gets the Free Company tag of this character.
///
public SeString CompanyTag { get; }
@@ -92,12 +99,12 @@ public interface ICharacter : IGameObject
/// Gets the status flags.
///
public StatusFlags StatusFlags { get; }
-
+
///
/// Gets the current mount for this character. Will be null if the character doesn't have a mount.
///
public RowRef? CurrentMount { get; }
-
+
///
/// Gets the current minion summoned for this character. Will be null 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.
///
/// The address of this character in memory.
- 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;
///
+ [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();
+ ///
+ public ICustomizeData CustomizeData => new CustomizeData((nint)(&this.Struct->DrawData.CustomizeData));
+
///
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);
-
+
///
public RowRef? CurrentMount
{
get
{
if (this.Struct->IsNotMounted()) return null; // just for safety.
-
+
var mountId = this.Struct->Mount.MountId;
return mountId == 0 ? null : LuminaUtils.CreateRef(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(this.Struct->CompanionObject->BaseId);
// this is only present if a minion is summoned but hidden (e.g. the player's on a mount).