diff --git a/Glamourer.GameData/CharacterCustomization.cs b/Glamourer.GameData/CharacterCustomization.cs deleted file mode 100644 index 7176f8f..0000000 --- a/Glamourer.GameData/CharacterCustomization.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using Glamourer.Customization; -using Penumbra.GameData.Enums; - -namespace Glamourer; - -public readonly unsafe struct CharacterCustomization -{ - public static readonly CharacterCustomization Null = new(null); - - private readonly CustomizationData* _data; - - public IntPtr Address - => (IntPtr)_data; - - public CharacterCustomization(CustomizationData* data) - => _data = data; - - public void Load(CharacterCustomization other) - => *_data = *other._data; - - public ref Race Race - => ref _data->Race; - - public ref SubRace Clan - => ref _data->Clan; - - public Gender Gender - { - get => _data->Gender; - set => _data->Gender = value; - } - - public ref byte BodyType - => ref _data->BodyType; - - public ref byte Height - => ref _data->Height; - - public ref byte Face - => ref _data->Face; - - public ref byte Hairstyle - => ref _data->Hairstyle; - - public bool HighlightsOn - { - get => _data->HighlightsOn; - set => _data->HighlightsOn = value; - } - - public ref byte SkinColor - => ref _data->SkinColor; - - public ref byte EyeColorRight - => ref _data->EyeColorRight; - - public ref byte HairColor - => ref _data->HairColor; - - public ref byte HighlightsColor - => ref _data->HighlightsColor; - - public ref byte FacialFeatures - => ref _data->FacialFeatures; - - public ref byte TattooColor - => ref _data->TattooColor; - - public ref byte Eyebrow - => ref _data->Eyebrow; - - public ref byte EyeColorLeft - => ref _data->EyeColorLeft; - - public byte EyeShape - { - get => _data->EyeShape; - set => _data->EyeShape = value; - } - - public byte FacePaint - { - get => _data->FacePaint; - set => _data->FacePaint = value; - } - - public bool FacePaintReversed - { - get => _data->FacePaintReversed; - set => _data->FacePaintReversed = value; - } - - public byte Mouth - { - get => _data->Mouth; - set => _data->Mouth = value; - } - - public bool SmallIris - { - get => _data->SmallIris; - set => _data->SmallIris = value; - } - - public bool Lipstick - { - get => _data->Lipstick; - set => _data->Lipstick = value; - } - - public ref byte Nose - => ref _data->Nose; - - public ref byte Jaw - => ref _data->Jaw; - - public ref byte LipColor - => ref _data->LipColor; - - public ref byte MuscleMass - => ref _data->MuscleMass; - - public ref byte TailShape - => ref _data->TailShape; - - public ref byte BustSize - => ref _data->BustSize; - - public ref byte FacePaintColor - => ref _data->FacePaintColor; - - public bool FacialFeature(int idx) - => _data->FacialFeature(idx); - - public void FacialFeature(int idx, bool set) - => _data->FacialFeature(idx, set); - - public byte this[CustomizationId id] - { - get => _data->Get(id); - set => _data->Set(id, value); - } - - public static implicit operator CharacterCustomization(CustomizationData* val) - => new(val); - - public static implicit operator CharacterCustomization(IntPtr val) - => new((CustomizationData*)val); - - public static implicit operator bool(CharacterCustomization customize) - => customize._data != null; - - public static bool operator true(CharacterCustomization customize) - => customize._data != null; - - public static bool operator false(CharacterCustomization customize) - => customize._data == null; - - public static bool operator !(CharacterCustomization customize) - => customize._data == null; -} diff --git a/Glamourer.GameData/CharacterEquipExtensions.cs b/Glamourer.GameData/CharacterEquipExtensions.cs deleted file mode 100644 index 4d0f723..0000000 --- a/Glamourer.GameData/CharacterEquipExtensions.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.ComponentModel; -using Glamourer.Structs; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer; - -public static class WriteExtensions -{ - private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain) - { - void WriteWeapon(int offset) - { - var address = (byte*)characterPtr + offset; - if (id.HasValue) - *(ushort*)address = (ushort)id.Value; - - if (type.HasValue) - *(ushort*)(address + 2) = (ushort)type.Value; - - if (variant.HasValue) - *(ushort*)(address + 4) = variant.Value; - - if (stain.HasValue) - *(address + 6) = (byte)stain.Value; - } - - void WriteEquip(int offset) - { - var address = (uint*)characterPtr + offset; - if (id.HasValue) - *(ushort*)address = (ushort)id.Value; - - if (variant < byte.MaxValue) - *(address + 2) = (byte)variant.Value; - - if (stain.HasValue) - *(address + 3) = (byte)stain.Value; - } - - switch (slot) - { - case EquipSlot.MainHand: - WriteWeapon(CharacterEquipment.MainWeaponOffset); - break; - case EquipSlot.OffHand: - WriteWeapon(CharacterEquipment.OffWeaponOffset); - break; - case EquipSlot.Head: - WriteEquip(CharacterEquipment.EquipmentOffset); - break; - case EquipSlot.Body: - WriteEquip(CharacterEquipment.EquipmentOffset + 4); - break; - case EquipSlot.Hands: - WriteEquip(CharacterEquipment.EquipmentOffset + 8); - break; - case EquipSlot.Legs: - WriteEquip(CharacterEquipment.EquipmentOffset + 12); - break; - case EquipSlot.Feet: - WriteEquip(CharacterEquipment.EquipmentOffset + 16); - break; - case EquipSlot.Ears: - WriteEquip(CharacterEquipment.EquipmentOffset + 20); - break; - case EquipSlot.Neck: - WriteEquip(CharacterEquipment.EquipmentOffset + 24); - break; - case EquipSlot.Wrists: - WriteEquip(CharacterEquipment.EquipmentOffset + 28); - break; - case EquipSlot.RFinger: - WriteEquip(CharacterEquipment.EquipmentOffset + 32); - break; - case EquipSlot.LFinger: - WriteEquip(CharacterEquipment.EquipmentOffset + 36); - break; - default: throw new InvalidEnumArgumentException(); - } - } - - public static void Write(this Stain stain, IntPtr characterPtr, EquipSlot slot) - => Write(characterPtr, slot, null, null, null, stain.RowIndex); - - public static void Write(this Item item, IntPtr characterAddress) - { - var (id, type, variant) = item.MainModel; - Write(characterAddress, item.EquippableTo, id, type, variant, null); - if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel) - { - var (subId, subType, subVariant) = item.SubModel; - Write(characterAddress, EquipSlot.OffHand, subId, subType, subVariant, null); - } - } - - public static void Write(this CharacterArmor armor, IntPtr characterAddress, EquipSlot slot) - => Write(characterAddress, slot, armor.Set, null, armor.Variant, armor.Stain); - - public static void Write(this CharacterWeapon weapon, IntPtr characterAddress, EquipSlot slot) - => Write(characterAddress, slot, weapon.Set, weapon.Type, weapon.Variant, weapon.Stain); - - public static unsafe void Write(this CharacterEquipment equip, IntPtr characterAddress) - { - if (equip.IsSet == 0) - return; - - Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, equip.MainHand.Stain); - Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, equip.OffHand.Stain); - - fixed (CharacterArmor* equipment = &equip.Head) - { - Buffer.MemoryCopy(equipment, (byte*)characterAddress + CharacterEquipment.EquipmentOffset, - CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor), CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor)); - } - } - - public static void Write(this CharacterEquipment equip, IntPtr characterAddress, CharacterEquipMask models, CharacterEquipMask stains) - { - if (models == CharacterEquipMask.All && stains == CharacterEquipMask.All) - { - equip.Write(characterAddress); - return; - } - - if (models.HasFlag(CharacterEquipMask.MainHand)) - Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, null); - if (stains.HasFlag(CharacterEquipMask.MainHand)) - Write(characterAddress, EquipSlot.MainHand, null, null, null, equip.MainHand.Stain); - if (models.HasFlag(CharacterEquipMask.OffHand)) - Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, null); - if (stains.HasFlag(CharacterEquipMask.OffHand)) - Write(characterAddress, EquipSlot.OffHand, null, null, null, equip.OffHand.Stain); - - if (models.HasFlag(CharacterEquipMask.Head)) - Write(characterAddress, EquipSlot.Head, equip.Head.Set, null, equip.Head.Variant, null); - if (stains.HasFlag(CharacterEquipMask.Head)) - Write(characterAddress, EquipSlot.Head, null, null, null, equip.Head.Stain); - if (models.HasFlag(CharacterEquipMask.Body)) - Write(characterAddress, EquipSlot.Body, equip.Body.Set, null, equip.Body.Variant, null); - if (stains.HasFlag(CharacterEquipMask.Body)) - Write(characterAddress, EquipSlot.Body, null, null, null, equip.Body.Stain); - if (models.HasFlag(CharacterEquipMask.Hands)) - Write(characterAddress, EquipSlot.Hands, equip.Hands.Set, null, equip.Hands.Variant, null); - if (stains.HasFlag(CharacterEquipMask.Hands)) - Write(characterAddress, EquipSlot.Hands, null, null, null, equip.Hands.Stain); - if (models.HasFlag(CharacterEquipMask.Legs)) - Write(characterAddress, EquipSlot.Legs, equip.Legs.Set, null, equip.Legs.Variant, null); - if (stains.HasFlag(CharacterEquipMask.Legs)) - Write(characterAddress, EquipSlot.Legs, null, null, null, equip.Legs.Stain); - if (models.HasFlag(CharacterEquipMask.Feet)) - Write(characterAddress, EquipSlot.Feet, equip.Feet.Set, null, equip.Feet.Variant, null); - if (stains.HasFlag(CharacterEquipMask.Feet)) - Write(characterAddress, EquipSlot.Feet, null, null, null, equip.Feet.Stain); - - if (models.HasFlag(CharacterEquipMask.Ears)) - Write(characterAddress, EquipSlot.Ears, equip.Ears.Set, null, equip.Ears.Variant, null); - if (models.HasFlag(CharacterEquipMask.Neck)) - Write(characterAddress, EquipSlot.Neck, equip.Neck.Set, null, equip.Neck.Variant, null); - if (models.HasFlag(CharacterEquipMask.Wrists)) - Write(characterAddress, EquipSlot.Wrists, equip.Wrists.Set, null, equip.Wrists.Variant, null); - if (models.HasFlag(CharacterEquipMask.LFinger)) - Write(characterAddress, EquipSlot.LFinger, equip.LFinger.Set, null, equip.LFinger.Variant, null); - if (models.HasFlag(CharacterEquipMask.RFinger)) - Write(characterAddress, EquipSlot.RFinger, equip.RFinger.Set, null, equip.RFinger.Variant, null); - } -} diff --git a/Glamourer.GameData/Customization/CustomizationData.cs b/Glamourer.GameData/Customization/CustomizationData.cs index 707623e..7ded7a9 100644 --- a/Glamourer.GameData/Customization/CustomizationData.cs +++ b/Glamourer.GameData/Customization/CustomizationData.cs @@ -4,11 +4,11 @@ using Penumbra.GameData.Structs; namespace Glamourer.Customization; -public unsafe ref struct Customize +public unsafe struct Customize { private readonly CustomizeData* _data; - private Customize(CustomizeData* data) + public Customize(CustomizeData* data) => _data = data; public Race Race @@ -70,7 +70,7 @@ public unsafe ref struct Customize public bool this[int idx] { get => (*_bitfield & (1 << idx)) != 0; - set => *_bitfield = (byte)(value ? *_bitfield | (1 << idx) : *_bitfield & ~(1 << idx)); + set => Set(idx, value); } public void Clear() @@ -78,6 +78,9 @@ public unsafe ref struct Customize public void All() => *_bitfield = 0xFF; + + public void Set(int idx, bool value) + => *_bitfield = (byte)(value ? *_bitfield | (1 << idx) : *_bitfield & ~(1 << idx)); } public FacialFeatureStruct FacialFeatures @@ -149,8 +152,8 @@ public unsafe ref struct Customize public ref byte FacePaintColor => ref _data->Data[25]; - internal static readonly CustomizeData Default = GenerateDefault(); - internal static readonly CustomizeData Empty = new(); + public static readonly CustomizeData Default = GenerateDefault(); + public static readonly CustomizeData Empty = new(); public byte Get(CustomizationId id) => id switch @@ -263,4 +266,7 @@ public unsafe ref struct Customize return ret; } + + public void Load(Customize other) + => _data->Read(other._data); } diff --git a/Glamourer.GameData/Customization/CustomizationSet.cs b/Glamourer.GameData/Customization/CustomizationSet.cs index c3154fc..fc5e111 100644 --- a/Glamourer.GameData/Customization/CustomizationSet.cs +++ b/Glamourer.GameData/Customization/CustomizationSet.cs @@ -39,7 +39,7 @@ public class CustomizationSet public int NumJawShapes { get; internal init; } public int NumMouthShapes { get; internal init; } - public string ToHumanReadable(CustomizationData customizationData) + public string ToHumanReadable(Customize customizationData) { var sb = new StringBuilder(); foreach (var id in Enum.GetValues().Where(IsAvailable)) diff --git a/Glamourer.zip b/Glamourer.zip index a46b376..d268baa 100644 Binary files a/Glamourer.zip and b/Glamourer.zip differ diff --git a/Glamourer/Actor.cs b/Glamourer/Actor.cs index d13037c..5ad4364 100644 --- a/Glamourer/Actor.cs +++ b/Glamourer/Actor.cs @@ -8,7 +8,119 @@ namespace Glamourer; public unsafe struct Actor : IEquatable { - public record struct Identifier(Utf8String Name, uint Id, ushort HomeWorld, ushort Index); + public interface IIdentifier : IEquatable + { + Utf8String Name { get; } + + public IIdentifier CreatePermanent(); + } + + public class InvalidIdentifier : IIdentifier + { + public Utf8String Name + => Utf8String.Empty; + + public bool Equals(IIdentifier? other) + => false; + + public override int GetHashCode() + => 0; + + public override string ToString() + => "Invalid"; + + public IIdentifier CreatePermanent() + => this; + } + + public class PlayerIdentifier : IIdentifier + { + public Utf8String Name { get; } + public readonly ushort HomeWorld; + + public PlayerIdentifier(Utf8String name, ushort homeWorld) + { + Name = name; + HomeWorld = homeWorld; + } + + public bool Equals(IIdentifier? other) + => other is PlayerIdentifier p && p.HomeWorld == HomeWorld && p.Name.Equals(Name); + + public override int GetHashCode() + => HashCode.Combine(Name.Crc32, HomeWorld); + + public override string ToString() + => $"{Name} ({HomeWorld})"; + + public IIdentifier CreatePermanent() + => new PlayerIdentifier(Name.Clone(), HomeWorld); + } + + public class OwnedIdentifier : IIdentifier + { + public Utf8String Name { get; } + public readonly Utf8String OwnerName; + public readonly uint DataId; + public readonly ushort OwnerHomeWorld; + public readonly ObjectKind Kind; + + public OwnedIdentifier(Utf8String name, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind) + { + Name = name; + OwnerName = ownerName; + OwnerHomeWorld = ownerHomeWorld; + DataId = dataId; + Kind = kind; + } + + public bool Equals(IIdentifier? other) + => other is OwnedIdentifier p + && p.DataId == DataId + && p.OwnerHomeWorld == OwnerHomeWorld + && p.Kind == Kind + && p.OwnerName.Equals(OwnerName); + + public override int GetHashCode() + => HashCode.Combine(OwnerName.Crc32, OwnerHomeWorld, DataId, Kind); + + public override string ToString() + => $"{OwnerName}s {Name}"; + + public IIdentifier CreatePermanent() + => new OwnedIdentifier(Name.Clone(), OwnerName.Clone(), OwnerHomeWorld, DataId, Kind); + } + + public class NpcIdentifier : IIdentifier + { + public Utf8String Name { get; } + public readonly uint DataId; + public readonly ushort ObjectIndex; + + public NpcIdentifier(Utf8String actorName, ushort objectIndex = ushort.MaxValue, uint dataId = uint.MaxValue) + { + Name = actorName; + ObjectIndex = objectIndex; + DataId = dataId; + } + + public bool Equals(IIdentifier? other) + => other is NpcIdentifier p + && p.Name.Equals(Name) + && (p.DataId == uint.MaxValue || DataId == uint.MaxValue || p.DataId == DataId) + && (p.ObjectIndex == ushort.MaxValue || ObjectIndex == ushort.MaxValue || p.ObjectIndex == ObjectIndex); + + public override int GetHashCode() + => Name.Crc32; + + public override string ToString() + => DataId == uint.MaxValue ? ObjectIndex == ushort.MaxValue ? Name.ToString() : $"{Name} at {ObjectIndex}" : + ObjectIndex == ushort.MaxValue ? $"{Name} ({DataId})" : $"{Name} ({DataId}) at {ObjectIndex}"; + + public IIdentifier CreatePermanent() + => new NpcIdentifier(Name.Clone(), ObjectIndex, DataId); + } + public static readonly Actor Null = new() { Pointer = null }; @@ -23,13 +135,8 @@ public unsafe struct Actor : IEquatable public static implicit operator IntPtr(Actor actor) => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; - public Identifier GetIdentifier() - { - if (Pointer == null) - return new Identifier(Utf8String.Empty, 0, 0, 0); - - return new Identifier(Utf8Name, Pointer->GameObject.ObjectID, Pointer->HomeWorld, Pointer->GameObject.ObjectIndex); - } + public IIdentifier GetIdentifier() + => CreateIdentifier(this); public Character? Character => Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character; @@ -40,11 +147,14 @@ public unsafe struct Actor : IEquatable public bool IsHuman => Pointer != null && Pointer->ModelCharaId == 0; - public int ModelId - => Pointer->ModelCharaId; + public ref int ModelId + => ref Pointer->ModelCharaId; public ObjectKind ObjectKind - => (ObjectKind)Pointer->GameObject.ObjectKind; + { + get => (ObjectKind) Pointer->GameObject.ObjectKind; + set => Pointer->GameObject.ObjectKind = (byte)value; + } public Utf8String Utf8Name => new(Pointer->GameObject.Name); @@ -84,4 +194,48 @@ public unsafe struct Actor : IEquatable public static bool operator !=(Actor lhs, Actor rhs) => lhs.Pointer != rhs.Pointer; + + private static IIdentifier CreateIdentifier(Actor actor) + { + switch (actor.ObjectKind) + { + case ObjectKind.Player: return new PlayerIdentifier(actor.Utf8Name, actor.Pointer->HomeWorld); + + case ObjectKind.BattleNpc: + { + var ownerId = actor.Pointer->GameObject.OwnerID; + if (ownerId != 0xE0000000) + { + var owner = (Actor)Dalamud.Objects.SearchById(ownerId)?.Address; + if (!owner) + return new InvalidIdentifier(); + + return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld, + actor.Pointer->GameObject.DataID, ObjectKind.BattleNpc); + } + + return new NpcIdentifier(actor.Utf8Name, actor.Pointer->GameObject.ObjectIndex, + actor.Pointer->GameObject.DataID); + } + case ObjectKind.Retainer: + case ObjectKind.EventNpc: + return new NpcIdentifier(actor.Utf8Name, actor.Pointer->GameObject.ObjectIndex, + actor.Pointer->GameObject.DataID); + case ObjectKind.MountType: + case ObjectKind.Companion: + { + var idx = actor.Pointer->GameObject.ObjectIndex; + if (idx % 2 == 0) + return new InvalidIdentifier(); + + var owner = (Actor)Dalamud.Objects[idx - 1]?.Address; + if (!owner) + return new InvalidIdentifier(); + + return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld, + actor.Pointer->GameObject.DataID, actor.ObjectKind); + } + default: return new InvalidIdentifier(); + } + } } diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index b9051e0..fbc51e2 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -2,7 +2,6 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; using Dalamud.Plugin.Ipc; -using Glamourer.Structs; using ImGuiNET; using Penumbra.GameData.Enums; diff --git a/Glamourer/CharacterSave.cs b/Glamourer/CharacterSave.cs index 1bf29cc..0fbcf93 100644 --- a/Glamourer/CharacterSave.cs +++ b/Glamourer/CharacterSave.cs @@ -44,9 +44,9 @@ public unsafe struct CharacterData VisorState = 0x80, } - public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes; - public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes + 4 + 1; - public const byte TotalSizeVersion3 = 1 + 1 + 2 + 7 + 7 + 2 + 40 + CustomizationData.CustomizationBytes + 4; + public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CustomizeData.Size; + public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CustomizeData.Size + 4 + 1; + public const byte TotalSizeVersion3 = 1 + 1 + 2 + 7 + 7 + 2 + 40 + CustomizeData.Size + 4; public const byte CurrentVersion = 3; public byte Version; @@ -65,16 +65,16 @@ public unsafe struct CharacterData public CharacterArmor Wrist; public CharacterArmor RFinger; public CharacterArmor LFinger; - private CustomizationData CustomizationData; + private CustomizeData _customizeData; public float Alpha; - public CharacterCustomization Customize + public Customize Customize { get { - fixed (CustomizationData* ptr = &CustomizationData) + fixed (CustomizeData* ptr = &_customizeData) { - return new CharacterCustomization(ptr); + return new Customize(ptr); } } } @@ -93,24 +93,24 @@ public unsafe struct CharacterData public static readonly CharacterData Default = new() { - Version = CurrentVersion, - Flags = SaveFlags.WriteCustomizations, - Equip = CharacterEquipMask.All, - MainHand = CharacterWeapon.Empty, - OffHand = CharacterWeapon.Empty, - Padding = 0, - Head = CharacterArmor.Empty, - Body = CharacterArmor.Empty, - Hands = CharacterArmor.Empty, - Legs = CharacterArmor.Empty, - Feet = CharacterArmor.Empty, - Ears = CharacterArmor.Empty, - Neck = CharacterArmor.Empty, - Wrist = CharacterArmor.Empty, - RFinger = CharacterArmor.Empty, - LFinger = CharacterArmor.Empty, - CustomizationData = CustomizationData.Default, - Alpha = 1f, + Version = CurrentVersion, + Flags = SaveFlags.WriteCustomizations, + Equip = CharacterEquipMask.All, + MainHand = CharacterWeapon.Empty, + OffHand = CharacterWeapon.Empty, + Padding = 0, + Head = CharacterArmor.Empty, + Body = CharacterArmor.Empty, + Hands = CharacterArmor.Empty, + Legs = CharacterArmor.Empty, + Feet = CharacterArmor.Empty, + Ears = CharacterArmor.Empty, + Neck = CharacterArmor.Empty, + Wrist = CharacterArmor.Empty, + RFinger = CharacterArmor.Empty, + LFinger = CharacterArmor.Empty, + _customizeData = Customize.Default, + Alpha = 1f, }; public void Load(Actor actor) @@ -119,7 +119,7 @@ public unsafe struct CharacterData return; var human = (Human*)actor.Pointer->GameObject.DrawObject; - CustomizationData = *(CustomizationData*)human->CustomizeData; + _customizeData.Read(human->CustomizeData); fixed (void* equip = &Head) { Functions.MemCpyUnchecked(equip, human->EquipSlotData, sizeof(CharacterArmor) * 10); @@ -204,7 +204,7 @@ public class CharacterSave public string ToBase64() => _data.ToBase64(); - public CharacterCustomization Customization + public Customize Customize => _data.Customize; public CharacterEquip Equipment diff --git a/Glamourer/CustomizeExtensions.cs b/Glamourer/CustomizeExtensions.cs index d3d7ba3..32f8814 100644 --- a/Glamourer/CustomizeExtensions.cs +++ b/Glamourer/CustomizeExtensions.cs @@ -52,6 +52,6 @@ public static unsafe class CustomizeExtensions }; } - public static string ClanName(this CharacterCustomization customize) + public static string ClanName(this Customize customize) => ClanName(customize.Clan, customize.Gender); } diff --git a/Glamourer/Designs/FixedDesigns.cs b/Glamourer/Designs/FixedDesigns.cs index 838d1c7..ee2d5f3 100644 --- a/Glamourer/Designs/FixedDesigns.cs +++ b/Glamourer/Designs/FixedDesigns.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Logging; -using Glamourer.Structs; namespace Glamourer.Designs; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 43c9950..d7949d2 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -116,7 +116,7 @@ - + \ No newline at end of file diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs index 2441861..7a42add 100644 --- a/Glamourer/Gui/Interface.Actors.cs +++ b/Glamourer/Gui/Interface.Actors.cs @@ -14,7 +14,7 @@ internal partial class Interface { private class ActorTab { - private ObjectManager.ActorData _data = new(string.Empty, new Actor.Identifier(), Actor.Null, false, Actor.Null); + private ObjectManager.ActorData _data = new(string.Empty, new Actor.InvalidIdentifier(), Actor.Null, false, Actor.Null); private Actor _nextSelect = Actor.Null; public void Draw() @@ -41,7 +41,7 @@ internal partial class Interface if (!Glamourer.RedrawManager.CurrentManipulations.GetSave(_data.Actor, out var save)) return; - if (DrawCustomization(save.Customization, save.Equipment, !_data.Modifiable)) + if (DrawCustomization(save.Customize, save.Equipment, !_data.Modifiable)) { //Glamourer.RedrawManager.Set(_data.Actor.Address, _character); Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true); @@ -70,9 +70,10 @@ internal partial class Interface foreach (var (id, data) in models.Models) { - if (ImGui.Selectable(data.FirstName, id == currentModel) || id == currentModel) + if (ImGui.Selectable(data.FirstName, id == currentModel) && id != currentModel) { _data.Actor.SetModelId((int)id); + _data.Actor.ObjectKind = Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true); } ImGuiUtil.HoverTooltip(data.AllNames); diff --git a/Glamourer/Gui/Interface.Customization.cs b/Glamourer/Gui/Interface.Customization.cs index 7260d7d..6439bfc 100644 --- a/Glamourer/Gui/Interface.Customization.cs +++ b/Glamourer/Gui/Interface.Customization.cs @@ -8,6 +8,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Gui; @@ -17,7 +18,7 @@ internal partial class Interface private static byte _tempStorage; private static CustomizationId _tempType; - private static bool DrawCustomization(CharacterCustomization customize, CharacterEquip equip, bool locked) + private static bool DrawCustomization(Customize customize, CharacterEquip equip, bool locked) { if (!ImGui.CollapsingHeader("Character Customization")) return false; @@ -56,7 +57,7 @@ internal partial class Interface return ret; } - private static bool DrawRaceGenderSelector(CharacterCustomization customize, CharacterEquip equip, bool locked) + private static bool DrawRaceGenderSelector(Customize customize, CharacterEquip equip, bool locked) { var ret = DrawGenderSelector(customize, equip, locked); ImGui.SameLine(); @@ -68,7 +69,7 @@ internal partial class Interface return ret; } - private static bool DrawGenderSelector(CharacterCustomization customize, CharacterEquip equip, bool locked) + private static bool DrawGenderSelector(Customize customize, CharacterEquip equip, bool locked) { using var font = ImRaii.PushFont(UiBuilder.IconFont); var icon = customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus; @@ -83,7 +84,7 @@ internal partial class Interface return false; //customize.ChangeGender(gender, locked ? CharacterEquip.Null : equip); } - private static bool DrawRaceCombo(CharacterCustomization customize, CharacterEquip equip, bool locked) + private static bool DrawRaceCombo(Customize customize, CharacterEquip equip, bool locked) { using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); ImGui.SetNextItemWidth(_raceSelectorWidth); @@ -123,7 +124,7 @@ internal partial class Interface return ret; } - private static bool PercentageSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customization, bool locked) + private static bool PercentageSelector(CustomizationSet set, CustomizationId id, Customize customization, bool locked) { using var bigGroup = ImRaii.Group(); using var _ = ImRaii.PushId((int)id); @@ -175,7 +176,7 @@ internal partial class Interface return ret; } - private static bool DrawIconSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked) + private static bool DrawIconSelector(CustomizationSet set, CustomizationId id, Customize customize, bool locked) { const string popupName = "Style Picker"; @@ -213,7 +214,7 @@ internal partial class Interface return ret; } - private static bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, CharacterCustomization customize) + private static bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, Customize customize) { using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); if (!popup) @@ -250,7 +251,7 @@ internal partial class Interface return ret; } - private static bool DrawColorPicker(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked) + private static bool DrawColorPicker(CustomizationSet set, CustomizationId id, Customize customize, bool locked) { const string popupName = "Color Picker"; using var _ = ImRaii.PushId((int)id); @@ -282,7 +283,7 @@ internal partial class Interface } private static (int, Customization.Customization) GetCurrentCustomization(CustomizationSet set, CustomizationId id, - CharacterCustomization customize) + Customize customize) { var current = set.DataByValue(id, customize[id], out var custom); if (set.IsAvailable(id) && current < 0) @@ -295,7 +296,7 @@ internal partial class Interface return (current, custom!.Value); } - private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, CharacterCustomization customize) + private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, Customize customize) { using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); if (!popup) @@ -322,7 +323,7 @@ internal partial class Interface return ret; } - private static bool DrawMultiIconSelector(CustomizationSet set, CharacterCustomization customize, bool locked) + private static bool DrawMultiIconSelector(CustomizationSet set, Customize customize, bool locked) { using var bigGroup = ImRaii.Group(); using var _ = ImRaii.PushId((int)CustomizationId.FacialFeaturesTattoos); @@ -344,7 +345,7 @@ internal partial class Interface return ret; } - private static bool DrawMultiIcons(CustomizationSet set, CharacterCustomization customize, bool locked) + private static bool DrawMultiIcons(CustomizationSet set, Customize customize, bool locked) { using var _ = ImRaii.Group(); var face = customize.Face; @@ -355,7 +356,7 @@ internal partial class Interface var count = set.Count(CustomizationId.FacialFeaturesTattoos); for (var i = 0; i < count; ++i) { - var enabled = customize.FacialFeature(i); + var enabled = customize.FacialFeatures[i]; var feature = set.FacialFeature(face, i); var icon = i == count - 1 ? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId) @@ -364,7 +365,7 @@ internal partial class Interface Vector4.Zero, enabled ? Vector4.One : RedTint) && !locked) { - customize.FacialFeature(i, !enabled); + customize.FacialFeatures.Set(i, !enabled); ret = true; } @@ -378,7 +379,7 @@ internal partial class Interface return ret; } - private static bool DrawListSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked) + private static bool DrawListSelector(CustomizationSet set, CustomizationId id, Customize customize, bool locked) { using var _ = ImRaii.PushId((int)id); using var bigGroup = ImRaii.Group(); diff --git a/Glamourer/ObjectManager.cs b/Glamourer/ObjectManager.cs index 5e7c7e0..9e8d401 100644 --- a/Glamourer/ObjectManager.cs +++ b/Glamourer/ObjectManager.cs @@ -13,7 +13,7 @@ public static class ObjectManager private const int DyePreviewIndex = 243; private static readonly Dictionary NameCounters = new(); - private static readonly Dictionary GPoseActors = new(CharacterScreenIndex - GPosePlayerIndex); + private static readonly Dictionary GPoseActors = new(CharacterScreenIndex - GPosePlayerIndex); public static bool IsInGPose() => Dalamud.Objects[GPosePlayerIndex] != null; @@ -24,7 +24,7 @@ public static class ObjectManager public static Actor Player => Dalamud.ClientState.LocalPlayer?.Address; - public record struct ActorData(string Label, Actor.Identifier Identifier, Actor Actor, bool Modifiable, Actor GPose); + public record struct ActorData(string Label, Actor.IIdentifier Identifier, Actor Actor, bool Modifiable, Actor GPose); public static IEnumerable GetEnumerator() { @@ -38,7 +38,7 @@ public static class ObjectManager var identifier = character.GetIdentifier(); GPoseActors[identifier] = character.Address; - yield return new ActorData(GetLabel(character, identifier.Name.ToString(), 0, true), identifier, character.Address, true, + yield return new ActorData(GetLabel(character, character.Utf8Name.ToString(), 0, true), identifier, character.Address, true, Actor.Null); } @@ -67,7 +67,7 @@ public static class ObjectManager continue; var identifier = actor.GetIdentifier(); - if (identifier.Name.Length == 0) + if (actor.Utf8Name.Length == 0) continue; if (NameCounters.TryGetValue(identifier.Name, out var num)) diff --git a/Glamourer/RedrawManager.cs b/Glamourer/RedrawManager.cs index a591e04..5d72c26 100644 --- a/Glamourer/RedrawManager.cs +++ b/Glamourer/RedrawManager.cs @@ -14,16 +14,16 @@ namespace Glamourer; public class CurrentManipulations { private readonly RestrictedGear _restrictedGear = GameData.RestrictedGear(Dalamud.GameData); - private readonly Dictionary _characterSaves = new(); + private readonly Dictionary _characterSaves = new(); public CharacterSave CreateSave(Actor actor) { var id = actor.GetIdentifier(); - if (_characterSaves.TryGetValue(actor.GetIdentifier(), out var save)) + if (_characterSaves.TryGetValue(id, out var save)) return save; save = new CharacterSave(actor); - _characterSaves.Add(id with { Name = id.Name.Clone() }, save); + _characterSaves.Add(id.CreatePermanent(), save); return save; } @@ -33,13 +33,13 @@ public class CurrentManipulations return actor && _characterSaves.TryGetValue(actor.GetIdentifier(), out save); } - public bool GetSave(Actor.Identifier identifier, [NotNullWhen(true)] out CharacterSave? save) + public bool GetSave(Actor.IIdentifier identifier, [NotNullWhen(true)] out CharacterSave? save) => _characterSaves.TryGetValue(identifier, out save); public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) { var save = CreateSave(actor); - (_, data) = _restrictedGear.ResolveRestricted(data, slot, save.Customization.Race, save.Customization.Gender); + (_, data) = _restrictedGear.ResolveRestricted(data, slot, save.Customize.Race, save.Customize.Gender); if (save.Equipment[slot] == data) return null; @@ -68,11 +68,11 @@ public class CurrentManipulations return true; } - public void ChangeCustomization(Actor actor, CharacterCustomization customize) + public void ChangeCustomization(Actor actor, Customize customize) { var save = CreateSave(actor); FixRestrictedGear(save, customize.Gender, customize.Race); - save.Customization.Load(customize); + save.Customize.Load(customize); } public bool ChangeCustomization(Actor actor, CustomizationId id, byte value) @@ -83,7 +83,7 @@ public class CurrentManipulations return ChangeGender(actor, (Gender)value); var save = CreateSave(actor); - var customize = save.Customization; + var customize = save.Customize; if (customize[id] != value) return false; @@ -95,10 +95,10 @@ public class CurrentManipulations public bool ChangeGender(Actor actor, Gender gender) { var save = CreateSave(actor); - if (save.Customization.Gender == gender) + if (save.Customize.Gender == gender) return false; - var customize = save.Customization; + var customize = save.Customize; FixRestrictedGear(save, gender, customize.Race); FixUpAttributes(customize); return true; @@ -108,10 +108,10 @@ public class CurrentManipulations public bool ChangeRace(Actor actor, SubRace clan) { var save = CreateSave(actor); - if (save.Customization.Clan == clan) + if (save.Customize.Clan == clan) return false; - var customize = save.Customization; + var customize = save.Customize; var race = clan.ToRace(); var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar FixRestrictedGear(save, gender, race); @@ -124,7 +124,7 @@ public class CurrentManipulations } // Go through a whole customization struct and fix up all settings that need fixing. - private void FixUpAttributes(CharacterCustomization customize) + private void FixUpAttributes(Customize customize) { var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId))) @@ -149,7 +149,7 @@ public class CurrentManipulations private void FixRestrictedGear(CharacterSave save, Gender gender, Race race) { - if (race == save.Customization.Race && gender == save.Customization.Gender) + if (race == save.Customize.Race && gender == save.Customize.Gender) return; var equip = save.Equipment;