diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..685aaa2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "OtterGui"] + path = OtterGui + url = git@github.com:Ottermandias/OtterGui.git + branch = main diff --git a/Glamourer.GameData/CharacterEquipExtensions.cs b/Glamourer.GameData/CharacterEquipExtensions.cs index 37bd606..24e5873 100644 --- a/Glamourer.GameData/CharacterEquipExtensions.cs +++ b/Glamourer.GameData/CharacterEquipExtensions.cs @@ -3,166 +3,165 @@ using System.ComponentModel; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -namespace Glamourer +namespace Glamourer; + +public static class WriteExtensions { - public static class WriteExtensions + private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain) { - private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain) + void WriteWeapon(int offset) { - void WriteWeapon(int offset) - { - var address = (byte*) characterPtr + offset; - if (id.HasValue) - *(ushort*) address = (ushort) id.Value; + var address = (byte*)characterPtr + offset; + if (id.HasValue) + *(ushort*)address = (ushort)id.Value; - if (type.HasValue) - *(ushort*) (address + 2) = (ushort) type.Value; + if (type.HasValue) + *(ushort*)(address + 2) = (ushort)type.Value; - if (variant.HasValue) - *(ushort*) (address + 4) = variant.Value; + if (variant.HasValue) + *(ushort*)(address + 4) = variant.Value; - if (stain.HasValue) - *(address + 6) = (byte) stain.Value; - } - - void WriteEquip(int offset) - { - var address = (byte*) 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(); - } + if (stain.HasValue) + *(address + 6) = (byte)stain.Value; } - 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) + void WriteEquip(int offset) { - 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); - } + 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; } - 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) + switch (slot) { - 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); + 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/CharacterEquipMask.cs b/Glamourer.GameData/CharacterEquipMask.cs deleted file mode 100644 index c5a881b..0000000 --- a/Glamourer.GameData/CharacterEquipMask.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace Glamourer -{ - [Flags] - public enum CharacterEquipMask : ushort - { - None = 0, - MainHand = 0b000000000001, - OffHand = 0b000000000010, - Head = 0b000000000100, - Body = 0b000000001000, - Hands = 0b000000010000, - Legs = 0b000000100000, - Feet = 0b000001000000, - Ears = 0b000010000000, - Neck = 0b000100000000, - Wrists = 0b001000000000, - RFinger = 0b010000000000, - LFinger = 0b100000000000, - All = 0b111111111111, - } -} \ No newline at end of file diff --git a/Glamourer.GameData/Customization/CharacterCustomization.cs b/Glamourer.GameData/Customization/CharacterCustomization.cs index b518538..513e849 100644 --- a/Glamourer.GameData/Customization/CharacterCustomization.cs +++ b/Glamourer.GameData/Customization/CharacterCustomization.cs @@ -1,304 +1,323 @@ using System; using System.Runtime.InteropServices; +using System.Text; using Dalamud.Game.ClientState.Objects.Types; using Penumbra.GameData.Enums; -namespace Glamourer.Customization +namespace Glamourer.Customization; + +public unsafe struct LazyCustomization { - public unsafe struct LazyCustomization + public CharacterCustomization* Address; + + public LazyCustomization(IntPtr characterPtr) + => Address = (CharacterCustomization*)(characterPtr + CharacterCustomization.CustomizationOffset); + + public ref CharacterCustomization Value + => ref *Address; + + public LazyCustomization(CharacterCustomization data) + => Address = &data; +} + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct CharacterCustomization +{ + public const int CustomizationOffset = 0x830; + public const int CustomizationBytes = 26; + + public static CharacterCustomization Default = new() { - public CharacterCustomization* Address; + Race = Race.Hyur, + Gender = Gender.Male, + BodyType = 1, + Height = 50, + Clan = SubRace.Midlander, + Face = 1, + Hairstyle = 1, + HighlightsOn = false, + SkinColor = 1, + EyeColorRight = 1, + HighlightsColor = 1, + FacialFeatures = 0, + TattooColor = 1, + Eyebrow = 1, + EyeColorLeft = 1, + EyeShape = 1, + Nose = 1, + Jaw = 1, + Mouth = 1, + LipColor = 1, + MuscleMass = 50, + TailShape = 1, + BustSize = 50, + FacePaint = 1, + FacePaintColor = 1, + }; - public LazyCustomization(IntPtr characterPtr) - => Address = (CharacterCustomization*) (characterPtr + CharacterCustomization.CustomizationOffset); + public Race Race; + private byte _gender; + public byte BodyType; + public byte Height; + public SubRace Clan; + public byte Face; + public byte Hairstyle; + private byte _highlightsOn; + public byte SkinColor; + public byte EyeColorRight; + public byte HairColor; + public byte HighlightsColor; + public byte FacialFeatures; + public byte TattooColor; + public byte Eyebrow; + public byte EyeColorLeft; + private byte _eyeShape; + public byte Nose; + public byte Jaw; + private byte _mouth; + public byte LipColor; + public byte MuscleMass; + public byte TailShape; + public byte BustSize; + private byte _facePaint; + public byte FacePaintColor; - public ref CharacterCustomization Value - => ref *Address; + public Gender Gender + { + get => (Gender)(_gender + 1); + set => _gender = (byte)(value - 1); + } - public LazyCustomization(CharacterCustomization data) - => Address = &data; + public bool HighlightsOn + { + get => (_highlightsOn & 128) == 128; + set => _highlightsOn = (byte)(value ? _highlightsOn | 128 : _highlightsOn & 127); + } + + public bool FacialFeature(int idx) + => (FacialFeatures & (1 << idx)) != 0; + + public void FacialFeature(int idx, bool set) + { + if (set) + FacialFeatures |= (byte)(1 << idx); + else + FacialFeatures &= (byte)~(1 << idx); + } + + public byte EyeShape + { + get => (byte)(_eyeShape & 127); + set => _eyeShape = (byte)((value & 127) | (_eyeShape & 128)); + } + + public bool SmallIris + { + get => (_eyeShape & 128) == 128; + set => _eyeShape = (byte)(value ? _eyeShape | 128 : _eyeShape & 127); } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct CharacterCustomization + public byte Mouth { - public const int CustomizationOffset = 0x830; - public const int CustomizationBytes = 26; + get => (byte)(_mouth & 127); + set => _mouth = (byte)((value & 127) | (_mouth & 128)); + } - public static CharacterCustomization Default = new() + public bool Lipstick + { + get => (_mouth & 128) == 128; + set => _mouth = (byte)(value ? _mouth | 128 : _mouth & 127); + } + + public byte FacePaint + { + get => (byte)(_facePaint & 127); + set => _facePaint = (byte)((value & 127) | (_facePaint & 128)); + } + + public bool FacePaintReversed + { + get => (_facePaint & 128) == 128; + set => _facePaint = (byte)(value ? _facePaint | 128 : _facePaint & 127); + } + + public unsafe void Read(IntPtr customizeAddress) + { + fixed (Race* ptr = &Race) { - Race = Race.Hyur, - Gender = Gender.Male, - BodyType = 1, - Height = 50, - Clan = SubRace.Midlander, - Face = 1, - Hairstyle = 1, - HighlightsOn = false, - SkinColor = 1, - EyeColorRight = 1, - HighlightsColor = 1, - FacialFeatures = 0, - TattooColor = 1, - Eyebrow = 1, - EyeColorLeft = 1, - EyeShape = 1, - Nose = 1, - Jaw = 1, - Mouth = 1, - LipColor = 1, - MuscleMass = 50, - TailShape = 1, - BustSize = 50, - FacePaint = 1, - FacePaintColor = 1, + Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes); + } + } + + public unsafe void Read(Customization* customize) + => Read((IntPtr)customize); + + public void Read(Character character) + => Read(character.Address + CustomizationOffset); + + public CharacterCustomization(Character character) + : this() + { + Read(character.Address + CustomizationOffset); + } + + public byte this[CustomizationId id] + { + get => id switch + { + CustomizationId.Race => (byte)Race, + CustomizationId.Gender => (byte)Gender, + CustomizationId.BodyType => BodyType, + CustomizationId.Height => Height, + CustomizationId.Clan => (byte)Clan, + CustomizationId.Face => Face, + CustomizationId.Hairstyle => Hairstyle, + CustomizationId.HighlightsOnFlag => _highlightsOn, + CustomizationId.SkinColor => SkinColor, + CustomizationId.EyeColorR => EyeColorRight, + CustomizationId.HairColor => HairColor, + CustomizationId.HighlightColor => HighlightsColor, + CustomizationId.FacialFeaturesTattoos => FacialFeatures, + CustomizationId.TattooColor => TattooColor, + CustomizationId.Eyebrows => Eyebrow, + CustomizationId.EyeColorL => EyeColorLeft, + CustomizationId.EyeShape => EyeShape, + CustomizationId.Nose => Nose, + CustomizationId.Jaw => Jaw, + CustomizationId.Mouth => Mouth, + CustomizationId.LipColor => LipColor, + CustomizationId.MuscleToneOrTailEarLength => MuscleMass, + CustomizationId.TailEarShape => TailShape, + CustomizationId.BustSize => BustSize, + CustomizationId.FacePaint => FacePaint, + CustomizationId.FacePaintColor => FacePaintColor, + _ => throw new ArgumentOutOfRangeException(nameof(id), id, null), }; - - public Race Race; - private byte _gender; - public byte BodyType; - public byte Height; - public SubRace Clan; - public byte Face; - public byte Hairstyle; - private byte _highlightsOn; - public byte SkinColor; - public byte EyeColorRight; - public byte HairColor; - public byte HighlightsColor; - public byte FacialFeatures; - public byte TattooColor; - public byte Eyebrow; - public byte EyeColorLeft; - private byte _eyeShape; - public byte Nose; - public byte Jaw; - private byte _mouth; - public byte LipColor; - public byte MuscleMass; - public byte TailShape; - public byte BustSize; - private byte _facePaint; - public byte FacePaintColor; - - public Gender Gender + set { - get => (Gender) (_gender + 1); - set => _gender = (byte) (value - 1); - } - - public bool HighlightsOn - { - get => (_highlightsOn & 128) == 128; - set => _highlightsOn = (byte) (value ? _highlightsOn | 128 : _highlightsOn & 127); - } - - public bool FacialFeature(int idx) - => (FacialFeatures & (1 << idx)) != 0; - - public void FacialFeature(int idx, bool set) - { - if (set) - FacialFeatures |= (byte) (1 << idx); - else - FacialFeatures &= (byte) ~(1 << idx); - } - - public byte EyeShape - { - get => (byte) (_eyeShape & 127); - set => _eyeShape = (byte) ((value & 127) | (_eyeShape & 128)); - } - - public bool SmallIris - { - get => (_eyeShape & 128) == 128; - set => _eyeShape = (byte) (value ? _eyeShape | 128 : _eyeShape & 127); - } - - - public byte Mouth - { - get => (byte) (_mouth & 127); - set => _mouth = (byte) ((value & 127) | (_mouth & 128)); - } - - public bool Lipstick - { - get => (_mouth & 128) == 128; - set => _mouth = (byte) (value ? _mouth | 128 : _mouth & 127); - } - - public byte FacePaint - { - get => (byte) (_facePaint & 127); - set => _facePaint = (byte) ((value & 127) | (_facePaint & 128)); - } - - public bool FacePaintReversed - { - get => (_facePaint & 128) == 128; - set => _facePaint = (byte) (value ? _facePaint | 128 : _facePaint & 127); - } - - public unsafe void Read(IntPtr customizeAddress) - { - fixed (Race* ptr = &Race) + switch (id) { - Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes); + case CustomizationId.Race: + Race = (Race)value; + break; + case CustomizationId.Gender: + Gender = (Gender)value; + break; + case CustomizationId.BodyType: + BodyType = value; + break; + case CustomizationId.Height: + Height = value; + break; + case CustomizationId.Clan: + Clan = (SubRace)value; + break; + case CustomizationId.Face: + Face = value; + break; + case CustomizationId.Hairstyle: + Hairstyle = value; + break; + case CustomizationId.HighlightsOnFlag: + HighlightsOn = (value & 128) == 128; + break; + case CustomizationId.SkinColor: + SkinColor = value; + break; + case CustomizationId.EyeColorR: + EyeColorRight = value; + break; + case CustomizationId.HairColor: + HairColor = value; + break; + case CustomizationId.HighlightColor: + HighlightsColor = value; + break; + case CustomizationId.FacialFeaturesTattoos: + FacialFeatures = value; + break; + case CustomizationId.TattooColor: + TattooColor = value; + break; + case CustomizationId.Eyebrows: + Eyebrow = value; + break; + case CustomizationId.EyeColorL: + EyeColorLeft = value; + break; + case CustomizationId.EyeShape: + EyeShape = value; + break; + case CustomizationId.Nose: + Nose = value; + break; + case CustomizationId.Jaw: + Jaw = value; + break; + case CustomizationId.Mouth: + Mouth = value; + break; + case CustomizationId.LipColor: + LipColor = value; + break; + case CustomizationId.MuscleToneOrTailEarLength: + MuscleMass = value; + break; + case CustomizationId.TailEarShape: + TailShape = value; + break; + case CustomizationId.BustSize: + BustSize = value; + break; + case CustomizationId.FacePaint: + FacePaint = value; + break; + case CustomizationId.FacePaintColor: + FacePaintColor = value; + break; + default: throw new ArgumentOutOfRangeException(nameof(id), id, null); } } + } - public void Read(Character character) - => Read(character.Address + CustomizationOffset); - - public CharacterCustomization(Character character) - : this() + public unsafe void Write(IntPtr characterAddress) + { + fixed (Race* ptr = &Race) { - Read(character.Address + CustomizationOffset); + Buffer.MemoryCopy(ptr, (byte*)characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes); } + } - public byte this[CustomizationId id] + public unsafe void WriteBytes(byte[] array, int offset = 0) + { + fixed (Race* ptr = &Race) { - get => id switch - { - CustomizationId.Race => (byte) Race, - CustomizationId.Gender => (byte) Gender, - CustomizationId.BodyType => BodyType, - CustomizationId.Height => Height, - CustomizationId.Clan => (byte) Clan, - CustomizationId.Face => Face, - CustomizationId.Hairstyle => Hairstyle, - CustomizationId.HighlightsOnFlag => _highlightsOn, - CustomizationId.SkinColor => SkinColor, - CustomizationId.EyeColorR => EyeColorRight, - CustomizationId.HairColor => HairColor, - CustomizationId.HighlightColor => HighlightsColor, - CustomizationId.FacialFeaturesTattoos => FacialFeatures, - CustomizationId.TattooColor => TattooColor, - CustomizationId.Eyebrows => Eyebrow, - CustomizationId.EyeColorL => EyeColorLeft, - CustomizationId.EyeShape => EyeShape, - CustomizationId.Nose => Nose, - CustomizationId.Jaw => Jaw, - CustomizationId.Mouth => Mouth, - CustomizationId.LipColor => LipColor, - CustomizationId.MuscleToneOrTailEarLength => MuscleMass, - CustomizationId.TailEarShape => TailShape, - CustomizationId.BustSize => BustSize, - CustomizationId.FacePaint => FacePaint, - CustomizationId.FacePaintColor => FacePaintColor, - _ => throw new ArgumentOutOfRangeException(nameof(id), id, null), - }; - set - { - switch (id) - { - case CustomizationId.Race: - Race = (Race) value; - break; - case CustomizationId.Gender: - Gender = (Gender) value; - break; - case CustomizationId.BodyType: - BodyType = value; - break; - case CustomizationId.Height: - Height = value; - break; - case CustomizationId.Clan: - Clan = (SubRace) value; - break; - case CustomizationId.Face: - Face = value; - break; - case CustomizationId.Hairstyle: - Hairstyle = value; - break; - case CustomizationId.HighlightsOnFlag: - HighlightsOn = (value & 128) == 128; - break; - case CustomizationId.SkinColor: - SkinColor = value; - break; - case CustomizationId.EyeColorR: - EyeColorRight = value; - break; - case CustomizationId.HairColor: - HairColor = value; - break; - case CustomizationId.HighlightColor: - HighlightsColor = value; - break; - case CustomizationId.FacialFeaturesTattoos: - FacialFeatures = value; - break; - case CustomizationId.TattooColor: - TattooColor = value; - break; - case CustomizationId.Eyebrows: - Eyebrow = value; - break; - case CustomizationId.EyeColorL: - EyeColorLeft = value; - break; - case CustomizationId.EyeShape: - EyeShape = value; - break; - case CustomizationId.Nose: - Nose = value; - break; - case CustomizationId.Jaw: - Jaw = value; - break; - case CustomizationId.Mouth: - Mouth = value; - break; - case CustomizationId.LipColor: - LipColor = value; - break; - case CustomizationId.MuscleToneOrTailEarLength: - MuscleMass = value; - break; - case CustomizationId.TailEarShape: - TailShape = value; - break; - case CustomizationId.BustSize: - BustSize = value; - break; - case CustomizationId.FacePaint: - FacePaint = value; - break; - case CustomizationId.FacePaintColor: - FacePaintColor = value; - break; - default: throw new ArgumentOutOfRangeException(nameof(id), id, null); - } - } + Marshal.Copy(new IntPtr(ptr), array, offset, CustomizationBytes); } + } - public unsafe void Write(IntPtr characterAddress) - { - fixed (Race* ptr = &Race) - { - Buffer.MemoryCopy(ptr, (byte*) characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes); - } - } + public byte[] ToBytes() + { + var ret = new byte[CustomizationBytes]; + WriteBytes(ret); + return ret; + } - public unsafe void WriteBytes(byte[] array, int offset = 0) - { - fixed (Race* ptr = &Race) - { - Marshal.Copy(new IntPtr(ptr), array, offset, CustomizationBytes); - } - } - - public byte[] ToBytes() - { - var ret = new byte[CustomizationBytes]; - WriteBytes(ret); - return ret; - } + public string HumanReadable() + { + // TODO + var sb = new StringBuilder(); + sb.Append($"Race: {Race.ToName()} - {Clan.ToName()}\n"); + sb.Append($"Gender: {Gender.ToName()}\n"); + sb.Append($"Height: {Height}%\n"); + sb.Append($"Face: #{Face}\n"); + sb.Append($"Hairstyle: #{Hairstyle}\n"); + sb.Append($"Haircolor: #{HairColor}"); + if (HighlightsOn) + sb.Append($" with Highlights #{HighlightsColor}\n"); + else + sb.Append('\n'); + return sb.ToString(); } } diff --git a/Glamourer.GameData/Customization/CmpFile.cs b/Glamourer.GameData/Customization/CmpFile.cs index c709a0e..f5608f7 100644 --- a/Glamourer.GameData/Customization/CmpFile.cs +++ b/Glamourer.GameData/Customization/CmpFile.cs @@ -1,24 +1,23 @@ using Dalamud.Data; using Dalamud.Plugin; -namespace Glamourer -{ - public class CmpFile - { - public readonly Lumina.Data.FileResource File; - public readonly uint[] RgbaColors; +namespace Glamourer; - public CmpFile(DataManager gameData) +public class CmpFile +{ + public readonly Lumina.Data.FileResource File; + public readonly uint[] RgbaColors; + + public CmpFile(DataManager gameData) + { + File = gameData.GetFile("chara/xls/charamake/human.cmp")!; + RgbaColors = new uint[File.Data.Length >> 2]; + for (var i = 0; i < File.Data.Length; i += 4) { - File = gameData.GetFile("chara/xls/charamake/human.cmp")!; - RgbaColors = new uint[File.Data.Length >> 2]; - for (var i = 0; i < File.Data.Length; i += 4) - { - RgbaColors[i >> 2] = File.Data[i] - | (uint) (File.Data[i + 1] << 8) - | (uint) (File.Data[i + 2] << 16) - | (uint) (File.Data[i + 3] << 24); - } + RgbaColors[i >> 2] = File.Data[i] + | (uint)(File.Data[i + 1] << 8) + | (uint)(File.Data[i + 2] << 16) + | (uint)(File.Data[i + 3] << 24); } } } diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index 0830457..858214f 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -5,370 +5,369 @@ using System.Reflection; using Dalamud; using Dalamud.Data; using Dalamud.Plugin; -using Glamourer.Util; using Lumina.Data; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; +using OtterGui.Classes; using Penumbra.GameData.Enums; using Race = Penumbra.GameData.Enums.Race; -namespace Glamourer.Customization -{ - public partial class CustomizationOptions - { - internal static readonly Race[] Races = ((Race[]) Enum.GetValues(typeof(Race))).Skip(1).ToArray(); - internal static readonly SubRace[] Clans = ((SubRace[]) Enum.GetValues(typeof(SubRace))).Skip(1).ToArray(); +namespace Glamourer.Customization; - internal static readonly Gender[] Genders = +public partial class CustomizationOptions +{ + internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); + internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray(); + + internal static readonly Gender[] Genders = + { + Gender.Male, + Gender.Female, + }; + + internal CustomizationSet GetList(SubRace race, Gender gender) + => _list[ToIndex(race, gender)]; + + internal ImGuiScene.TextureWrap GetIcon(uint id) + => _icons.LoadIcon(id); + + private static readonly int ListSize = Clans.Length * Genders.Length; + + private readonly CustomizationSet[] _list = new CustomizationSet[ListSize]; + private readonly IconStorage _icons; + + private static void ThrowException(SubRace race, Gender gender) + => throw new Exception($"Invalid customization requested for {race} {gender}."); + + private static int ToIndex(SubRace race, Gender gender) + { + if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male) + ThrowException(race, gender); + + var ret = (int)race - 1; + ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0); + return ret; + } + + private Customization[] GetHairStyles(SubRace race, Gender gender) + { + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var hairList = new List(row.Unknown30); + for (var i = 0; i < row.Unknown30; ++i) { - Gender.Male, - Gender.Female, + var name = $"Unknown{66 + i * 9}"; + var customizeIdx = + (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) + ?? uint.MaxValue; + if (customizeIdx == uint.MaxValue) + continue; + + var hairRow = _customizeSheet.GetRow(customizeIdx); + hairList.Add(hairRow != null + ? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId) + : new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx, 0)); + } + + return hairList.ToArray(); + } + + private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false) + => _cmpFile.RgbaColors.Skip(offset).Take(num) + .Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i))) + .ToArray(); + + private (Customization[], Customization[]) GetColors(SubRace race, Gender gender) + { + if (race > SubRace.Veena || race == SubRace.Unknown) + throw new ArgumentOutOfRangeException(nameof(race), race, null); + + var gv = gender == Gender.Male ? 0 : 1; + var idx = ((int)race * 2 + gv) * 5 + 3; + + return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192), + CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192)); + } + + private Customization FromValueAndIndex(CustomizationId id, uint value, int index) + { + var row = _customizeSheet.GetRow(value); + return row == null + ? new Customization(id, (byte)(index + 1), value, 0) + : new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId); + } + + private static int GetListSize(CharaMakeParams row, CustomizationId id) + { + var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == id); + return menu?.Size ?? 0; + } + + private Customization[] GetFacePaints(SubRace race, Gender gender) + { + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var paintList = new List(row.Unknown37); + for (var i = 0; i < row.Unknown37; ++i) + { + var name = $"Unknown{73 + i * 9}"; + var customizeIdx = + (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) + ?? uint.MaxValue; + if (customizeIdx == uint.MaxValue) + continue; + + var paintRow = _customizeSheet.GetRow(customizeIdx); + paintList.Add(paintRow != null + ? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId) + : new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0)); + } + + return paintList.ToArray(); + } + + private Customization[] GetTailEarShapes(CharaMakeParams row) + => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values + .Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray() + ?? Array.Empty(); + + private Customization[] GetFaces(CharaMakeParams row) + => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values + .Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray() + ?? Array.Empty(); + + private Customization[] HrothgarFurPattern(CharaMakeParams row) + => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values + .Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray() + ?? Array.Empty(); + + private Customization[] HrothgarFaces(CharaMakeParams row) + => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values + .Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray() + ?? Array.Empty(); + + private CustomizationSet GetSet(SubRace race, Gender gender) + { + var (skin, hair) = GetColors(race, gender); + var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var set = new CustomizationSet(race, gender) + { + HairStyles = GetHairStyles(race, gender), + HairColors = hair, + SkinColors = skin, + EyeColors = _eyeColorPicker, + HighlightColors = _highlightPicker, + TattooColors = _tattooColorPicker, + LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark, + LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty() : _lipColorPickerLight, + FacePaintColorsDark = _facePaintColorPickerDark, + FacePaintColorsLight = _facePaintColorPickerLight, + Faces = GetFaces(row), + NumEyebrows = GetListSize(row, CustomizationId.Eyebrows), + NumEyeShapes = GetListSize(row, CustomizationId.EyeShape), + NumNoseShapes = GetListSize(row, CustomizationId.Nose), + NumJawShapes = GetListSize(row, CustomizationId.Jaw), + NumMouthShapes = GetListSize(row, CustomizationId.Mouth), + FacePaints = GetFacePaints(race, gender), + TailEarShapes = GetTailEarShapes(row), }; - internal CustomizationSet GetList(SubRace race, Gender gender) - => _list[ToIndex(race, gender)]; + if (GetListSize(row, CustomizationId.BustSize) > 0) + set.SetAvailable(CustomizationId.BustSize); + if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0) + set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength); - internal ImGuiScene.TextureWrap GetIcon(uint id) - => _icons.LoadIcon(id); - - private static readonly int ListSize = Clans.Length * Genders.Length; - - private readonly CustomizationSet[] _list = new CustomizationSet[ListSize]; - private readonly IconStorage _icons; - - private static void ThrowException(SubRace race, Gender gender) - => throw new Exception($"Invalid customization requested for {race} {gender}."); - - private static int ToIndex(SubRace race, Gender gender) + if (set.NumEyebrows > 0) + set.SetAvailable(CustomizationId.Eyebrows); + if (set.NumEyeShapes > 0) + set.SetAvailable(CustomizationId.EyeShape); + if (set.NumNoseShapes > 0) + set.SetAvailable(CustomizationId.Nose); + if (set.NumJawShapes > 0) + set.SetAvailable(CustomizationId.Jaw); + if (set.NumMouthShapes > 0) + set.SetAvailable(CustomizationId.Mouth); + if (set.FacePaints.Count > 0) { - if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male) - ThrowException(race, gender); - - var ret = (int) race - 1; - ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0); - return ret; + set.SetAvailable(CustomizationId.FacePaint); + set.SetAvailable(CustomizationId.FacePaintColor); } - private Customization[] GetHairStyles(SubRace race, Gender gender) - { - var row = _hairSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) gender)!; - var hairList = new List(row.Unknown30); - for (var i = 0; i < row.Unknown30; ++i) - { - var name = $"Unknown{66 + i * 9}"; - var customizeIdx = - (uint?) row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; - if (customizeIdx == uint.MaxValue) - continue; + if (set.TailEarShapes.Count > 0) + set.SetAvailable(CustomizationId.TailEarShape); + if (set.Faces.Count > 0) + set.SetAvailable(CustomizationId.Face); - var hairRow = _customizeSheet.GetRow(customizeIdx); - hairList.Add(hairRow != null - ? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort) hairRow.RowId) - : new Customization(CustomizationId.Hairstyle, (byte) i, customizeIdx, 0)); + var count = set.Faces.Count; + var featureDict = new List>(count); + for (var i = 0; i < count; ++i) + { + featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx) + => new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx))) + .Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8))) + .ToArray()); + } + + set.FeaturesTattoos = featureDict; + + var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c => + { + var menu = row.Menus + .Cast() + .FirstOrDefault(m => m!.Value.Customization == c); + if (menu == null) + { + if (c == CustomizationId.HighlightsOnFlag) + return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights"; + + return c.ToDefaultName(); } - return hairList.ToArray(); - } + if (c == CustomizationId.FacialFeaturesTattoos) + return + $"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}"; - private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false) - => _cmpFile.RgbaColors.Skip(offset).Take(num) - .Select((c, i) => new Customization(id, (byte) (light ? 128 + i : 0 + i), c, (ushort) (offset + i))) - .ToArray(); + var textRow = _lobby.GetRow(menu.Value.Id); + return textRow?.Text.ToString() ?? c.ToDefaultName(); + }).ToArray(); + nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR]; + nameArray[(int)CustomizationId.EyeColorR] = GetName(CustomName.OddEyes); + set.OptionName = nameArray; - private (Customization[], Customization[]) GetColors(SubRace race, Gender gender) + set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c => { - if (race > SubRace.Veena || race == SubRace.Unknown) - throw new ArgumentOutOfRangeException(nameof(race), race, null); - - var gv = gender == Gender.Male ? 0 : 1; - var idx = ((int) race * 2 + gv) * 5 + 3; - - return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192), - CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192)); - } - - private Customization FromValueAndIndex(CustomizationId id, uint value, int index) - { - var row = _customizeSheet.GetRow(value); - return row == null - ? new Customization(id, (byte) (index + 1), value, 0) - : new Customization(id, row.FeatureID, row.Icon, (ushort) row.RowId); - } - - private static int GetListSize(CharaMakeParams row, CustomizationId id) - { - var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == id); - return menu?.Size ?? 0; - } - - private Customization[] GetFacePaints(SubRace race, Gender gender) - { - var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - var paintList = new List(row.Unknown37); - for (var i = 0; i < row.Unknown37; ++i) + switch (c) { - var name = $"Unknown{73 + i * 9}"; - var customizeIdx = - (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; - if (customizeIdx == uint.MaxValue) - continue; - - var paintRow = _customizeSheet.GetRow(customizeIdx); - paintList.Add(paintRow != null - ? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId) - : new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0)); + case CustomizationId.HighlightColor: + case CustomizationId.EyeColorL: + case CustomizationId.EyeColorR: + return CharaMakeParams.MenuType.ColorPicker; } - return paintList.ToArray(); + var menu = row.Menus + .Cast() + .FirstOrDefault(m => m!.Value.Customization == c); + return menu?.Type ?? CharaMakeParams.MenuType.ListSelector; + }).ToArray(); + + return set; + } + + private readonly ExcelSheet _customizeSheet; + private readonly ExcelSheet _listSheet; + private readonly ExcelSheet _hairSheet; + private readonly ExcelSheet _lobby; + private readonly CmpFile _cmpFile; + private readonly Customization[] _highlightPicker; + private readonly Customization[] _eyeColorPicker; + private readonly Customization[] _facePaintColorPickerDark; + private readonly Customization[] _facePaintColorPickerLight; + private readonly Customization[] _lipColorPickerDark; + private readonly Customization[] _lipColorPickerLight; + private readonly Customization[] _tattooColorPicker; + private readonly string[] _names = new string[(int)CustomName.Num]; + + public string GetName(CustomName name) + => _names[(int)name]; + + private static Language FromClientLanguage(ClientLanguage language) + => language switch + { + ClientLanguage.English => Language.English, + ClientLanguage.French => Language.French, + ClientLanguage.German => Language.German, + ClientLanguage.Japanese => Language.Japanese, + _ => Language.English, + }; + + internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language) + { + try + { + _cmpFile = new CmpFile(gameData); + } + catch (Exception e) + { + throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" + + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" + + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); } - private Customization[] GetTailEarShapes(CharaMakeParams row) - => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values - .Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray() - ?? Array.Empty(); - - private Customization[] GetFaces(CharaMakeParams row) - => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values - .Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray() - ?? Array.Empty(); - - private Customization[] HrothgarFurPattern(CharaMakeParams row) - => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values - .Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray() - ?? Array.Empty(); - - private Customization[] HrothgarFaces(CharaMakeParams row) - => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values - .Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray() - ?? Array.Empty(); - - private CustomizationSet GetSet(SubRace race, Gender gender) - { - var (skin, hair) = GetColors(race, gender); - var row = _listSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) gender)!; - var set = new CustomizationSet(race, gender) - { - HairStyles = GetHairStyles(race, gender), - HairColors = hair, - SkinColors = skin, - EyeColors = _eyeColorPicker, - HighlightColors = _highlightPicker, - TattooColors = _tattooColorPicker, - LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark, - LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty() : _lipColorPickerLight, - FacePaintColorsDark = _facePaintColorPickerDark, - FacePaintColorsLight = _facePaintColorPickerLight, - Faces = GetFaces(row), - NumEyebrows = GetListSize(row, CustomizationId.Eyebrows), - NumEyeShapes = GetListSize(row, CustomizationId.EyeShape), - NumNoseShapes = GetListSize(row, CustomizationId.Nose), - NumJawShapes = GetListSize(row, CustomizationId.Jaw), - NumMouthShapes = GetListSize(row, CustomizationId.Mouth), - FacePaints = GetFacePaints(race, gender), - TailEarShapes = GetTailEarShapes(row), - }; - - if (GetListSize(row, CustomizationId.BustSize) > 0) - set.SetAvailable(CustomizationId.BustSize); - if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0) - set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength); - - if (set.NumEyebrows > 0) - set.SetAvailable(CustomizationId.Eyebrows); - if (set.NumEyeShapes > 0) - set.SetAvailable(CustomizationId.EyeShape); - if (set.NumNoseShapes > 0) - set.SetAvailable(CustomizationId.Nose); - if (set.NumJawShapes > 0) - set.SetAvailable(CustomizationId.Jaw); - if (set.NumMouthShapes > 0) - set.SetAvailable(CustomizationId.Mouth); - if (set.FacePaints.Count > 0) - { - set.SetAvailable(CustomizationId.FacePaint); - set.SetAvailable(CustomizationId.FacePaintColor); - } - - if (set.TailEarShapes.Count > 0) - set.SetAvailable(CustomizationId.TailEarShape); - if (set.Faces.Count > 0) - set.SetAvailable(CustomizationId.Face); - - var count = set.Faces.Count; - var featureDict = new List>(count); - for (var i = 0; i < count; ++i) - { - featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx) - => new Customization(CustomizationId.FacialFeaturesTattoos, (byte) (1 << idx), val, (ushort) (i * 8 + idx))) - .Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort) ((i + 1) * 8))) - .ToArray()); - } - - set.FeaturesTattoos = featureDict; - - var nameArray = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c => - { - var menu = row.Menus - .Cast() - .FirstOrDefault(m => m!.Value.Customization == c); - if (menu == null) - { - if (c == CustomizationId.HighlightsOnFlag) - return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights"; - - return c.ToDefaultName(); - } - - if (c == CustomizationId.FacialFeaturesTattoos) - return - $"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}"; - - var textRow = _lobby.GetRow(menu.Value.Id); - return textRow?.Text.ToString() ?? c.ToDefaultName(); - }).ToArray(); - nameArray[(int) CustomizationId.EyeColorL] = nameArray[(int) CustomizationId.EyeColorR]; - nameArray[(int) CustomizationId.EyeColorR] = GetName(CustomName.OddEyes); - set.OptionName = nameArray; - - set.Types = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c => - { - switch (c) - { - case CustomizationId.HighlightColor: - case CustomizationId.EyeColorL: - case CustomizationId.EyeColorR: - return CharaMakeParams.MenuType.ColorPicker; - } - - var menu = row.Menus - .Cast() - .FirstOrDefault(m => m!.Value.Customization == c); - return menu?.Type ?? CharaMakeParams.MenuType.ListSelector; - }).ToArray(); - - return set; - } - - private readonly ExcelSheet _customizeSheet; - private readonly ExcelSheet _listSheet; - private readonly ExcelSheet _hairSheet; - private readonly ExcelSheet _lobby; - private readonly CmpFile _cmpFile; - private readonly Customization[] _highlightPicker; - private readonly Customization[] _eyeColorPicker; - private readonly Customization[] _facePaintColorPickerDark; - private readonly Customization[] _facePaintColorPickerLight; - private readonly Customization[] _lipColorPickerDark; - private readonly Customization[] _lipColorPickerLight; - private readonly Customization[] _tattooColorPicker; - private readonly string[] _names = new string[(int) CustomName.Num]; - - public string GetName(CustomName name) - => _names[(int) name]; - - private static Language FromClientLanguage(ClientLanguage language) - => language switch - { - ClientLanguage.English => Language.English, - ClientLanguage.French => Language.French, - ClientLanguage.German => Language.German, - ClientLanguage.Japanese => Language.Japanese, - _ => Language.English, - }; - - internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language) - { - try - { - _cmpFile = new CmpFile(gameData); - } - catch (Exception e) - { - throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" - + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" - + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); - } - - _customizeSheet = gameData.GetExcelSheet()!; - _lobby = gameData.GetExcelSheet()!; - var tmp = gameData.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)! - .MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(gameData.Excel, new object?[] + _customizeSheet = gameData.GetExcelSheet()!; + _lobby = gameData.GetExcelSheet()!; + var tmp = gameData.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)! + .MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(gameData.Excel, new object?[] { "charamaketype", FromClientLanguage(language), null, }) as ExcelSheet; - _listSheet = tmp!; - _hairSheet = gameData.GetExcelSheet()!; - SetNames(gameData); + _listSheet = tmp!; + _hairSheet = gameData.GetExcelSheet()!; + SetNames(gameData); - _highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192); - _lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96); - _lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true); - _eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192); - _facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96); - _facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true); - _tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192); + _highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192); + _lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96); + _lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true); + _eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192); + _facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96); + _facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true); + _tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192); - _icons = new IconStorage(pi, gameData, _list.Length * 50); - foreach (var race in Clans) - { - foreach (var gender in Genders) - _list[ToIndex(race, gender)] = GetSet(race, gender); - } - } - - private void SetNames(DataManager gameData) + _icons = new IconStorage(pi, gameData, _list.Length * 50); + foreach (var race in Clans) { - var subRace = gameData.GetExcelSheet()!; - _names[(int) CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan"; - _names[(int) CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender"; - _names[(int) CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse"; - _names[(int) CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes"; - _names[(int) CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small"; - _names[(int) CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large"; - _names[(int) CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size"; - _names[(int) CustomName.MidlanderM] = subRace.GetRow((int) SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName(); - _names[(int) CustomName.MidlanderF] = subRace.GetRow((int) SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName(); - _names[(int) CustomName.HighlanderM] = - subRace.GetRow((int) SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName(); - _names[(int) CustomName.HighlanderF] = subRace.GetRow((int) SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName(); - _names[(int) CustomName.WildwoodM] = subRace.GetRow((int) SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName(); - _names[(int) CustomName.WildwoodF] = subRace.GetRow((int) SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName(); - _names[(int) CustomName.DuskwightM] = subRace.GetRow((int) SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName(); - _names[(int) CustomName.DuskwightF] = subRace.GetRow((int) SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName(); - _names[(int) CustomName.PlainsfolkM] = - subRace.GetRow((int) SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName(); - _names[(int) CustomName.PlainsfolkF] = subRace.GetRow((int) SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName(); - _names[(int) CustomName.DunesfolkM] = subRace.GetRow((int) SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName(); - _names[(int) CustomName.DunesfolkF] = subRace.GetRow((int) SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName(); - _names[(int) CustomName.SeekerOfTheSunM] = - subRace.GetRow((int) SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName(); - _names[(int) CustomName.SeekerOfTheSunF] = - subRace.GetRow((int) SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName(); - _names[(int) CustomName.KeeperOfTheMoonM] = - subRace.GetRow((int) SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName(); - _names[(int) CustomName.KeeperOfTheMoonF] = - subRace.GetRow((int) SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName(); - _names[(int) CustomName.SeawolfM] = subRace.GetRow((int) SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName(); - _names[(int) CustomName.SeawolfF] = subRace.GetRow((int) SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName(); - _names[(int) CustomName.HellsguardM] = - subRace.GetRow((int) SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName(); - _names[(int) CustomName.HellsguardF] = subRace.GetRow((int) SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName(); - _names[(int) CustomName.RaenM] = subRace.GetRow((int) SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName(); - _names[(int) CustomName.RaenF] = subRace.GetRow((int) SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName(); - _names[(int) CustomName.XaelaM] = subRace.GetRow((int) SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName(); - _names[(int) CustomName.XaelaF] = subRace.GetRow((int) SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName(); - _names[(int) CustomName.HelionM] = subRace.GetRow((int) SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName(); - _names[(int) CustomName.HelionF] = subRace.GetRow((int) SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName(); - _names[(int) CustomName.LostM] = subRace.GetRow((int) SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName(); - _names[(int) CustomName.LostF] = subRace.GetRow((int) SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName(); - _names[(int) CustomName.RavaM] = subRace.GetRow((int) SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName(); - _names[(int) CustomName.RavaF] = subRace.GetRow((int) SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName(); - _names[(int) CustomName.VeenaM] = subRace.GetRow((int) SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName(); - _names[(int) CustomName.VeenaF] = subRace.GetRow((int) SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName(); + foreach (var gender in Genders) + _list[ToIndex(race, gender)] = GetSet(race, gender); } } + + private void SetNames(DataManager gameData) + { + var subRace = gameData.GetExcelSheet()!; + _names[(int)CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan"; + _names[(int)CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender"; + _names[(int)CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse"; + _names[(int)CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes"; + _names[(int)CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small"; + _names[(int)CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large"; + _names[(int)CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size"; + _names[(int)CustomName.MidlanderM] = subRace.GetRow((int)SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName(); + _names[(int)CustomName.MidlanderF] = subRace.GetRow((int)SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName(); + _names[(int)CustomName.HighlanderM] = + subRace.GetRow((int)SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName(); + _names[(int)CustomName.HighlanderF] = subRace.GetRow((int)SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName(); + _names[(int)CustomName.WildwoodM] = subRace.GetRow((int)SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName(); + _names[(int)CustomName.WildwoodF] = subRace.GetRow((int)SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName(); + _names[(int)CustomName.DuskwightM] = subRace.GetRow((int)SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName(); + _names[(int)CustomName.DuskwightF] = subRace.GetRow((int)SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName(); + _names[(int)CustomName.PlainsfolkM] = + subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName(); + _names[(int)CustomName.PlainsfolkF] = subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName(); + _names[(int)CustomName.DunesfolkM] = subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName(); + _names[(int)CustomName.DunesfolkF] = subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName(); + _names[(int)CustomName.SeekerOfTheSunM] = + subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName(); + _names[(int)CustomName.SeekerOfTheSunF] = + subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName(); + _names[(int)CustomName.KeeperOfTheMoonM] = + subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName(); + _names[(int)CustomName.KeeperOfTheMoonF] = + subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName(); + _names[(int)CustomName.SeawolfM] = subRace.GetRow((int)SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName(); + _names[(int)CustomName.SeawolfF] = subRace.GetRow((int)SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName(); + _names[(int)CustomName.HellsguardM] = + subRace.GetRow((int)SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName(); + _names[(int)CustomName.HellsguardF] = subRace.GetRow((int)SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName(); + _names[(int)CustomName.RaenM] = subRace.GetRow((int)SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName(); + _names[(int)CustomName.RaenF] = subRace.GetRow((int)SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName(); + _names[(int)CustomName.XaelaM] = subRace.GetRow((int)SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName(); + _names[(int)CustomName.XaelaF] = subRace.GetRow((int)SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName(); + _names[(int)CustomName.HelionM] = subRace.GetRow((int)SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName(); + _names[(int)CustomName.HelionF] = subRace.GetRow((int)SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName(); + _names[(int)CustomName.LostM] = subRace.GetRow((int)SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName(); + _names[(int)CustomName.LostF] = subRace.GetRow((int)SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName(); + _names[(int)CustomName.RavaM] = subRace.GetRow((int)SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName(); + _names[(int)CustomName.RavaF] = subRace.GetRow((int)SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName(); + _names[(int)CustomName.VeenaM] = subRace.GetRow((int)SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName(); + _names[(int)CustomName.VeenaF] = subRace.GetRow((int)SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName(); + } } diff --git a/Glamourer.GameData/GameData.cs b/Glamourer.GameData/GameData.cs index 34f8b45..bae448a 100644 --- a/Glamourer.GameData/GameData.cs +++ b/Glamourer.GameData/GameData.cs @@ -3,142 +3,180 @@ using System.Collections.Generic; using System.Linq; using Dalamud; using Dalamud.Data; +using Glamourer.Structs; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; +using Item = Glamourer.Structs.Item; +using Stain = Glamourer.Structs.Stain; -namespace Glamourer +namespace Glamourer; + +public static class GameData { - public static class GameData + private static Dictionary? _stains; + private static Dictionary>? _itemsBySlot; + private static Dictionary? _jobs; + private static Dictionary? _jobGroups; + private static SortedList? _models; + + public static IReadOnlyDictionary Models(DataManager dataManager) { - private static Dictionary? _stains; - private static Dictionary>? _itemsBySlot; - private static Dictionary? _jobs; - private static Dictionary? _jobGroups; - private static SortedList? _models; - - public static IReadOnlyDictionary Models(DataManager dataManager) - { - if (_models != null) - return _models; - - var sheet = dataManager.GetExcelSheet()!; - - _models = new SortedList((int) sheet.RowCount); - foreach (var model in sheet.Where(m => m.Type != 0)) - _models.Add(model.RowId, model); + if (_models != null) return _models; - } - public static IReadOnlyDictionary Stains(DataManager dataManager) - { - if (_stains != null) - return _stains; + var sheet = dataManager.GetExcelSheet()!; - var sheet = dataManager.GetExcelSheet()!; - _stains = sheet.Where(s => s.Color != 0).ToDictionary(s => (byte) s.RowId, s => new Stain((byte) s.RowId, s)); + _models = new SortedList((int)sheet.RowCount); + foreach (var model in sheet.Where(m => m.Type != 0)) + _models.Add(model.RowId, model); + return _models; + } + + public static IReadOnlyDictionary Stains(DataManager dataManager) + { + if (_stains != null) return _stains; - } - public static IReadOnlyDictionary> ItemsBySlot(DataManager dataManager) - { - if (_itemsBySlot != null) - return _itemsBySlot; + var sheet = dataManager.GetExcelSheet()!; + _stains = sheet.Where(s => s.Color != 0).ToDictionary(s => (byte)s.RowId, s => new Stain((byte)s.RowId, s)); + return _stains; + } - var sheet = dataManager.GetExcelSheet()!; - - Item EmptySlot(EquipSlot slot) - => new(sheet.First(), "Nothing", slot); - - static Item EmptyNpc(EquipSlot slot) - => new(new Lumina.Excel.GeneratedSheets.Item() { ModelMain = 9903 }, "Smallclothes (NPC)", slot); - - _itemsBySlot = new Dictionary>() - { - [EquipSlot.Head] = new(200) { EmptySlot(EquipSlot.Head), EmptyNpc(EquipSlot.Head) }, - [EquipSlot.Body] = new(200) { EmptySlot(EquipSlot.Body), EmptyNpc(EquipSlot.Body) }, - [EquipSlot.Hands] = new(200) { EmptySlot(EquipSlot.Hands), EmptyNpc(EquipSlot.Hands) }, - [EquipSlot.Legs] = new(200) { EmptySlot(EquipSlot.Legs), EmptyNpc(EquipSlot.Legs) }, - [EquipSlot.Feet] = new(200) { EmptySlot(EquipSlot.Feet), EmptyNpc(EquipSlot.Feet) }, - [EquipSlot.RFinger] = new(200) { EmptySlot(EquipSlot.RFinger), EmptyNpc(EquipSlot.RFinger) }, - [EquipSlot.Neck] = new(200) { EmptySlot(EquipSlot.Neck), EmptyNpc(EquipSlot.Neck) }, - [EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) }, - [EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) }, - [EquipSlot.Wrists] = new(200) { EmptySlot(EquipSlot.Wrists), EmptyNpc(EquipSlot.Wrists) }, - [EquipSlot.Ears] = new(200) { EmptySlot(EquipSlot.Ears), EmptyNpc(EquipSlot.Ears) }, - }; - - foreach (var item in sheet) - { - var name = item.Name.ToString(); - if (!name.Any()) - continue; - - var slot = (EquipSlot) item.EquipSlotCategory.Row; - if (slot == EquipSlot.Unknown) - continue; - - slot = slot.ToSlot(); - if (!_itemsBySlot.TryGetValue(slot, out var list)) - continue; - - list.Add(new Item(item, name, slot)); - } - - foreach (var list in _itemsBySlot.Values) - list.Sort((i1, i2) => string.Compare(i1.Name, i2.Name, StringComparison.InvariantCulture)); - - _itemsBySlot[EquipSlot.LFinger] = _itemsBySlot[EquipSlot.RFinger]; + public static IReadOnlyDictionary> ItemsBySlot(DataManager dataManager) + { + if (_itemsBySlot != null) return _itemsBySlot; - } - public static IReadOnlyDictionary Jobs(DataManager dataManager) + var sheet = dataManager.GetExcelSheet()!; + + Item EmptySlot(EquipSlot slot) + => new(sheet.First(), "Nothing", slot); + + static Item EmptyNpc(EquipSlot slot) + => new(new Lumina.Excel.GeneratedSheets.Item() { ModelMain = 9903 }, "Smallclothes (NPC)", slot); + + _itemsBySlot = new Dictionary>() { - if (_jobs != null) - return _jobs; - - var sheet = dataManager.GetExcelSheet()!; - _jobs = sheet.ToDictionary(j => (byte)j.RowId, j => new Job(j)); - return _jobs; - } - - public static IReadOnlyDictionary JobGroups(DataManager dataManager) - { - if (_jobGroups != null) - return _jobGroups; - - var sheet = dataManager.GetExcelSheet()!; - var jobs = dataManager.GetExcelSheet(ClientLanguage.English)!; - - static bool ValidIndex(uint idx) + [EquipSlot.Head] = new(200) { - if (idx > 0 && idx < 36) - return true; + EmptySlot(EquipSlot.Head), + EmptyNpc(EquipSlot.Head), + }, + [EquipSlot.Body] = new(200) + { + EmptySlot(EquipSlot.Body), + EmptyNpc(EquipSlot.Body), + }, + [EquipSlot.Hands] = new(200) + { + EmptySlot(EquipSlot.Hands), + EmptyNpc(EquipSlot.Hands), + }, + [EquipSlot.Legs] = new(200) + { + EmptySlot(EquipSlot.Legs), + EmptyNpc(EquipSlot.Legs), + }, + [EquipSlot.Feet] = new(200) + { + EmptySlot(EquipSlot.Feet), + EmptyNpc(EquipSlot.Feet), + }, + [EquipSlot.RFinger] = new(200) + { + EmptySlot(EquipSlot.RFinger), + EmptyNpc(EquipSlot.RFinger), + }, + [EquipSlot.Neck] = new(200) + { + EmptySlot(EquipSlot.Neck), + EmptyNpc(EquipSlot.Neck), + }, + [EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) }, + [EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) }, + [EquipSlot.Wrists] = new(200) + { + EmptySlot(EquipSlot.Wrists), + EmptyNpc(EquipSlot.Wrists), + }, + [EquipSlot.Ears] = new(200) + { + EmptySlot(EquipSlot.Ears), + EmptyNpc(EquipSlot.Ears), + }, + }; - return idx switch - { - 91 => true, - 92 => true, - 96 => true, - 98 => true, - 99 => true, - 111 => true, - 112 => true, - 129 => true, - 149 => true, - 150 => true, - 156 => true, - 157 => true, - 158 => true, - 159 => true, - 180 => true, - 181 => true, - _ => false, - }; - } + foreach (var item in sheet) + { + var name = item.Name.ToString(); + if (!name.Any()) + continue; - _jobGroups = sheet.Where(j => ValidIndex(j.RowId)) - .ToDictionary(j => (ushort) j.RowId, j => new JobGroup(j, jobs)); - return _jobGroups; + var slot = (EquipSlot)item.EquipSlotCategory.Row; + if (slot == EquipSlot.Unknown) + continue; + + slot = slot.ToSlot(); + if (!_itemsBySlot.TryGetValue(slot, out var list)) + continue; + + list.Add(new Item(item, name, slot)); } + + foreach (var list in _itemsBySlot.Values) + list.Sort((i1, i2) => string.Compare(i1.Name, i2.Name, StringComparison.InvariantCulture)); + + _itemsBySlot[EquipSlot.LFinger] = _itemsBySlot[EquipSlot.RFinger]; + return _itemsBySlot; + } + + public static IReadOnlyDictionary Jobs(DataManager dataManager) + { + if (_jobs != null) + return _jobs; + + var sheet = dataManager.GetExcelSheet()!; + _jobs = sheet.ToDictionary(j => (byte)j.RowId, j => new Job(j)); + return _jobs; + } + + public static IReadOnlyDictionary JobGroups(DataManager dataManager) + { + if (_jobGroups != null) + return _jobGroups; + + var sheet = dataManager.GetExcelSheet()!; + var jobs = dataManager.GetExcelSheet(ClientLanguage.English)!; + + static bool ValidIndex(uint idx) + { + if (idx is > 0 and < 36) + return true; + + return idx switch + { + 91 => true, + 92 => true, + 96 => true, + 98 => true, + 99 => true, + 111 => true, + 112 => true, + 129 => true, + 149 => true, + 150 => true, + 156 => true, + 157 => true, + 158 => true, + 159 => true, + 180 => true, + 181 => true, + _ => false, + }; + } + + _jobGroups = sheet.Where(j => ValidIndex(j.RowId)) + .ToDictionary(j => (ushort)j.RowId, j => new JobGroup(j, jobs)); + return _jobGroups; } } diff --git a/Glamourer.GameData/Glamourer.GameData.csproj b/Glamourer.GameData/Glamourer.GameData.csproj index c2b60ab..ee86319 100644 --- a/Glamourer.GameData/Glamourer.GameData.csproj +++ b/Glamourer.GameData/Glamourer.GameData.csproj @@ -58,6 +58,7 @@ + diff --git a/Glamourer.GameData/Item.cs b/Glamourer.GameData/Item.cs deleted file mode 100644 index 78c33a3..0000000 --- a/Glamourer.GameData/Item.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer -{ - public readonly struct Item - { - private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data) - { - if (slot == EquipSlot.MainHand || slot == EquipSlot.OffHand) - { - var id = (SetId) (data & 0xFFFF); - var type = (WeaponType) ((data >> 16) & 0xFFFF); - var variant = (ushort) ((data >> 32) & 0xFFFF); - return (id, type, variant); - } - else - { - var id = (SetId) (data & 0xFFFF); - var variant = (byte) ((data >> 16) & 0xFF); - return (id, new WeaponType(), variant); - } - } - - public readonly Lumina.Excel.GeneratedSheets.Item Base; - public readonly string Name; - public readonly EquipSlot EquippableTo; - - public (SetId id, WeaponType type, ushort variant) MainModel - => ParseModel(EquippableTo, Base.ModelMain); - - public bool HasSubModel - => Base.ModelSub != 0; - - public (SetId id, WeaponType type, ushort variant) SubModel - => ParseModel(EquippableTo, Base.ModelSub); - - public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown) - { - Base = item; - Name = name; - EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot) item.EquipSlotCategory.Row).ToSlot() : slot; - } - - public static Item Nothing(EquipSlot slot) - => new("Nothing", slot); - - private Item(string name, EquipSlot slot) - { - Name = name; - Base = new Lumina.Excel.GeneratedSheets.Item(); - EquippableTo = slot; - } - } -} diff --git a/Glamourer.GameData/Job.cs b/Glamourer.GameData/Job.cs deleted file mode 100644 index 01d3dd0..0000000 --- a/Glamourer.GameData/Job.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer -{ - public readonly struct Job - { - public readonly string Name; - public readonly string Abbreviation; - public readonly ClassJob Base; - - public uint Id - => Base.RowId; - - public Job(ClassJob job) - { - Base = job; - Name = job.Name.ToString(); - Abbreviation = job.Abbreviation.ToString(); - } - } -} diff --git a/Glamourer.GameData/JobGroup.cs b/Glamourer.GameData/JobGroup.cs deleted file mode 100644 index 83a1763..0000000 --- a/Glamourer.GameData/JobGroup.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer -{ - public readonly struct JobGroup - { - public readonly string Name; - private readonly ulong _flags; - public readonly int Count; - public readonly uint Id; - - public JobGroup(ClassJobCategory group, ExcelSheet jobs) - { - Count = 0; - _flags = 0ul; - Id = group.RowId; - Name = group.Name.ToString(); - - Debug.Assert(jobs.RowCount < 64); - foreach (var job in jobs) - { - var abbr = job.Abbreviation.ToString(); - if (!abbr.Any()) - continue; - - var prop = group.GetType().GetProperty(abbr); - Debug.Assert(prop != null); - - if (!(bool) prop.GetValue(group)!) - continue; - - ++Count; - _flags |= 1ul << (int) job.RowId; - } - } - - public bool Fits(Job job) - => Fits(job.Id); - - public bool Fits(uint jobId) - { - var flag = 1ul << (int)jobId; - return (flag & _flags) != 0; - } - } -} diff --git a/Glamourer.GameData/Stain.cs b/Glamourer.GameData/Stain.cs deleted file mode 100644 index 0dd7444..0000000 --- a/Glamourer.GameData/Stain.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Penumbra.GameData.Structs; - -namespace Glamourer -{ - public readonly struct Stain - { - public readonly string Name; - public readonly uint RgbaColor; - - private readonly uint _seColorId; - - public byte R - => (byte) (RgbaColor & 0xFF); - - public byte G - => (byte) ((RgbaColor >> 8) & 0xFF); - - public byte B - => (byte) ((RgbaColor >> 16) & 0xFF); - - public byte Intensity - => (byte) ((1 + R + G + B) / 3); - - public uint SeColor - => _seColorId & 0x00FFFFFF; - - public StainId RowIndex - => (StainId) (_seColorId >> 24); - - - public static uint SeColorToRgba(uint color) - => ((color & 0xFF) << 16) | ((color >> 16) & 0xFF) | (color & 0xFF00) | 0xFF000000; - - public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain) - { - Name = stain.Name.ToString(); - _seColorId = stain.Color | ((uint) index << 24); - RgbaColor = SeColorToRgba(stain.Color); - } - - public static readonly Stain None = new("None"); - - private Stain(string name) - { - Name = name; - _seColorId = 0; - RgbaColor = 0; - } - } -} diff --git a/Glamourer.GameData/Structs/CharacterEquipMask.cs b/Glamourer.GameData/Structs/CharacterEquipMask.cs new file mode 100644 index 0000000..aaf4e64 --- /dev/null +++ b/Glamourer.GameData/Structs/CharacterEquipMask.cs @@ -0,0 +1,43 @@ +using System; +using Penumbra.GameData.Enums; + +namespace Glamourer.Structs; + +[Flags] +public enum CharacterEquipMask : ushort +{ + None = 0, + MainHand = 0b000000000001, + OffHand = 0b000000000010, + Head = 0b000000000100, + Body = 0b000000001000, + Hands = 0b000000010000, + Legs = 0b000000100000, + Feet = 0b000001000000, + Ears = 0b000010000000, + Neck = 0b000100000000, + Wrists = 0b001000000000, + RFinger = 0b010000000000, + LFinger = 0b100000000000, + All = 0b111111111111, +} + +public static class CharacterEquipMaskExtensions +{ + public static bool Fits(this CharacterEquipMask mask, EquipSlot slot) + => slot switch + { + EquipSlot.Unknown => false, + EquipSlot.Head => mask.HasFlag(CharacterEquipMask.Head), + EquipSlot.Body => mask.HasFlag(CharacterEquipMask.Body), + EquipSlot.Hands => mask.HasFlag(CharacterEquipMask.Hands), + EquipSlot.Legs => mask.HasFlag(CharacterEquipMask.Legs), + EquipSlot.Feet => mask.HasFlag(CharacterEquipMask.Feet), + EquipSlot.Ears => mask.HasFlag(CharacterEquipMask.Ears), + EquipSlot.Neck => mask.HasFlag(CharacterEquipMask.Neck), + EquipSlot.Wrists => mask.HasFlag(CharacterEquipMask.Wrists), + EquipSlot.RFinger => mask.HasFlag(CharacterEquipMask.RFinger), + EquipSlot.LFinger => mask.HasFlag(CharacterEquipMask.LFinger), + _ => false, + }; +} diff --git a/Glamourer.GameData/Structs/Item.cs b/Glamourer.GameData/Structs/Item.cs new file mode 100644 index 0000000..481cc5f --- /dev/null +++ b/Glamourer.GameData/Structs/Item.cs @@ -0,0 +1,54 @@ +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Structs; + +public readonly struct Item +{ + private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data) + { + if (slot is EquipSlot.MainHand or EquipSlot.OffHand) + { + var id = (SetId)(data & 0xFFFF); + var type = (WeaponType)((data >> 16) & 0xFFFF); + var variant = (ushort)((data >> 32) & 0xFFFF); + return (id, type, variant); + } + else + { + var id = (SetId)(data & 0xFFFF); + var variant = (byte)((data >> 16) & 0xFF); + return (id, new WeaponType(), variant); + } + } + + public readonly Lumina.Excel.GeneratedSheets.Item Base; + public readonly string Name; + public readonly EquipSlot EquippableTo; + + public (SetId id, WeaponType type, ushort variant) MainModel + => ParseModel(EquippableTo, Base.ModelMain); + + public bool HasSubModel + => Base.ModelSub != 0; + + public (SetId id, WeaponType type, ushort variant) SubModel + => ParseModel(EquippableTo, Base.ModelSub); + + public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown) + { + Base = item; + Name = name; + EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot)item.EquipSlotCategory.Row).ToSlot() : slot; + } + + public static Item Nothing(EquipSlot slot) + => new("Nothing", slot); + + private Item(string name, EquipSlot slot) + { + Name = name; + Base = new Lumina.Excel.GeneratedSheets.Item(); + EquippableTo = slot; + } +} diff --git a/Glamourer.GameData/Structs/Job.cs b/Glamourer.GameData/Structs/Job.cs new file mode 100644 index 0000000..55169bb --- /dev/null +++ b/Glamourer.GameData/Structs/Job.cs @@ -0,0 +1,20 @@ +using Lumina.Excel.GeneratedSheets; + +namespace Glamourer.Structs; + +public readonly struct Job +{ + public readonly string Name; + public readonly string Abbreviation; + public readonly ClassJob Base; + + public uint Id + => Base.RowId; + + public Job(ClassJob job) + { + Base = job; + Name = job.Name.ToString(); + Abbreviation = job.Abbreviation.ToString(); + } +} diff --git a/Glamourer.GameData/Structs/JobGroup.cs b/Glamourer.GameData/Structs/JobGroup.cs new file mode 100644 index 0000000..57ff22f --- /dev/null +++ b/Glamourer.GameData/Structs/JobGroup.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; +using System.Linq; +using Lumina.Excel; +using Lumina.Excel.GeneratedSheets; + +namespace Glamourer.Structs; + +public readonly struct JobGroup +{ + public readonly string Name; + private readonly ulong _flags; + public readonly int Count; + public readonly uint Id; + + public JobGroup(ClassJobCategory group, ExcelSheet jobs) + { + Count = 0; + _flags = 0ul; + Id = group.RowId; + Name = group.Name.ToString(); + + Debug.Assert(jobs.RowCount < 64); + foreach (var job in jobs) + { + var abbr = job.Abbreviation.ToString(); + if (!abbr.Any()) + continue; + + var prop = group.GetType().GetProperty(abbr); + Debug.Assert(prop != null); + + if (!(bool)prop.GetValue(group)!) + continue; + + ++Count; + _flags |= 1ul << (int)job.RowId; + } + } + + public bool Fits(Job job) + => Fits(job.Id); + + public bool Fits(uint jobId) + { + var flag = 1ul << (int)jobId; + return (flag & _flags) != 0; + } +} diff --git a/Glamourer.GameData/Structs/Stain.cs b/Glamourer.GameData/Structs/Stain.cs new file mode 100644 index 0000000..32823aa --- /dev/null +++ b/Glamourer.GameData/Structs/Stain.cs @@ -0,0 +1,49 @@ +using Penumbra.GameData.Structs; + +namespace Glamourer.Structs; + +public readonly struct Stain +{ + public readonly string Name; + public readonly uint RgbaColor; + + private readonly uint _seColorId; + + public byte R + => (byte)(RgbaColor & 0xFF); + + public byte G + => (byte)(RgbaColor >> 8 & 0xFF); + + public byte B + => (byte)(RgbaColor >> 16 & 0xFF); + + public byte Intensity + => (byte)((1 + R + G + B) / 3); + + public uint SeColor + => _seColorId & 0x00FFFFFF; + + public StainId RowIndex + => (StainId)(_seColorId >> 24); + + + public static uint SeColorToRgba(uint color) + => (color & 0xFF) << 16 | color >> 16 & 0xFF | color & 0xFF00 | 0xFF000000; + + public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain) + { + Name = stain.Name.ToString(); + _seColorId = stain.Color | (uint)index << 24; + RgbaColor = SeColorToRgba(stain.Color); + } + + public static readonly Stain None = new("None"); + + private Stain(string name) + { + Name = name; + _seColorId = 0; + RgbaColor = 0; + } +} diff --git a/Glamourer.GameData/Util/IconStorage.cs b/Glamourer.GameData/Util/IconStorage.cs deleted file mode 100644 index 0d6d37e..0000000 --- a/Glamourer.GameData/Util/IconStorage.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using Dalamud.Data; -using Dalamud.Plugin; -using Dalamud.Utility; -using ImGuiScene; -using Lumina.Data.Files; - -namespace Glamourer.Util -{ - public class IconStorage : IDisposable - { - private readonly DalamudPluginInterface _pi; - private readonly DataManager _gameData; - private readonly Dictionary _icons; - - public IconStorage(DalamudPluginInterface pi, DataManager gameData, int size = 0) - { - _pi = pi; - _gameData = gameData; - _icons = new Dictionary(size); - } - - public TextureWrap this[int id] - => LoadIcon(id); - - private TexFile? LoadIconHq(uint id) - { - var path = $"ui/icon/{id / 1000 * 1000:000000}/{id:000000}_hr1.tex"; - return _gameData.GetFile(path); - } - - public TextureWrap LoadIcon(int id) - => LoadIcon((uint) id); - - public TextureWrap LoadIcon(uint id) - { - if (_icons.TryGetValue(id, out var ret)) - return ret; - - var icon = LoadIconHq(id) ?? _gameData.GetIcon(id)!; - var iconData = icon.GetRgbaImageData(); - - ret = _pi.UiBuilder.LoadImageRaw(iconData, icon.Header.Width, icon.Header.Height, 4); - _icons[id] = ret; - return ret; - } - - public void Dispose() - { - foreach (var icon in _icons.Values) - icon.Dispose(); - _icons.Clear(); - } - - ~IconStorage() - => Dispose(); - } -} diff --git a/Glamourer.sln b/Glamourer.sln index 56bc1ba..6d29d29 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\Pen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{FECEDB39-C103-4333-82A6-A422BDC51EEE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "..\Penumbra\OtterGui\OtterGui.csproj", "{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -75,6 +77,18 @@ Global {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x64.Build.0 = Release|Any CPU {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.ActiveCfg = Release|Any CPU {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.Build.0 = Release|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x64.Build.0 = Debug|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x86.Build.0 = Debug|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.Build.0 = Release|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x64.ActiveCfg = Release|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x64.Build.0 = Release|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x86.ActiveCfg = Release|Any CPU + {6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index 83302b1..9ea1568 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -2,6 +2,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; using Dalamud.Plugin.Ipc; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Gui; using ImGuiNET; using Penumbra.GameData.Enums; @@ -17,10 +18,14 @@ public class PenumbraAttach : IDisposable private ICallGateSubscriber? _clickSubscriber; private ICallGateSubscriber? _redrawSubscriberName; private ICallGateSubscriber? _redrawSubscriberObject; + private ICallGateSubscriber? _drawObjectInfo; + private ICallGateSubscriber? _creatingCharacterBase; private readonly ICallGateSubscriber _initializedEvent; private readonly ICallGateSubscriber _disposedEvent; + public event Action? CreatingCharacterBase; + public PenumbraAttach(bool attach) { _initializedEvent = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.Initialized"); @@ -47,6 +52,7 @@ public class PenumbraAttach : IDisposable _redrawSubscriberName = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.RedrawObjectByName"); _redrawSubscriberObject = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.RedrawObject"); + _drawObjectInfo = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.GetDrawObjectInfo"); if (!attach) return; @@ -54,8 +60,11 @@ public class PenumbraAttach : IDisposable _tooltipSubscriber = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.ChangedItemTooltip"); _clickSubscriber = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.ChangedItemClick"); + _creatingCharacterBase = + Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.CreatingCharacterBase"); _tooltipSubscriber.Subscribe(PenumbraTooltip); _clickSubscriber.Subscribe(PenumbraRightClick); + _creatingCharacterBase.Subscribe(SubscribeCharacterBase); PluginLog.Debug("Glamourer attached to Penumbra."); } catch (Exception e) @@ -64,13 +73,19 @@ public class PenumbraAttach : IDisposable } } + private void SubscribeCharacterBase(IntPtr gameObject, string _, IntPtr customize, IntPtr equipment) + => CreatingCharacterBase?.Invoke(gameObject, customize, equipment); + public void Unattach() { _tooltipSubscriber?.Unsubscribe(PenumbraTooltip); _clickSubscriber?.Unsubscribe(PenumbraRightClick); - _tooltipSubscriber = null; - _clickSubscriber = null; - _redrawSubscriberName = null; + _creatingCharacterBase?.Unsubscribe(SubscribeCharacterBase); + _tooltipSubscriber = null; + _clickSubscriber = null; + _creatingCharacterBase = null; + _redrawSubscriberName = null; + _drawObjectInfo = null; if (_redrawSubscriberObject != null) { PluginLog.Debug("Glamourer detached from Penumbra."); @@ -112,6 +127,9 @@ public class PenumbraAttach : IDisposable } } + public unsafe FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* GameObjectFromDrawObject(IntPtr drawObject) + => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(_drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero); + public void RedrawObject(GameObject actor, RedrawType settings, bool repeat) { if (_redrawSubscriberObject != null) diff --git a/Glamourer/CharacterFlag.cs b/Glamourer/CharacterFlag.cs deleted file mode 100644 index 51ae8b4..0000000 --- a/Glamourer/CharacterFlag.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; - -namespace Glamourer -{ - [Flags] - public enum CharacterFlag : ulong - { - MainHand = 1ul << 0, - OffHand = 1ul << 1, - Head = 1ul << 2, - Body = 1ul << 3, - Hands = 1ul << 4, - Legs = 1ul << 5, - Feet = 1ul << 6, - Ears = 1ul << 7, - Neck = 1ul << 8, - Wrists = 1ul << 9, - RFinger = 1ul << 10, - LFinger = 1ul << 11, - ModelMask = (1ul << 12) - 1, - - StainMainHand = MainHand << 16, - StainOffHand = OffHand << 16, - StainHead = Head << 16, - StainBody = Body << 16, - StainHands = Hands << 16, - StainLegs = Legs << 16, - StainFeet = Feet << 16, - StainEars = Ears << 16, - StainNeck = Neck << 16, - StainWrists = Wrists << 16, - StainRFinger = RFinger << 16, - StainLFinger = LFinger << 16, - StainMask = ModelMask << 16, - EquipMask = ModelMask | StainMask, - - Race = 1ul << 32, - Gender = 1ul << 33, - BodyType = 1ul << 34, - Height = 1ul << 35, - Clan = 1ul << 36, - Face = 1ul << 37, - Hairstyle = 1ul << 38, - Highlights = 1ul << 39, - SkinColor = 1ul << 40, - EyeColorRight = 1ul << 41, - HairColor = 1ul << 42, - HighlightsColor = 1ul << 43, - FacialFeatures = 1ul << 44, - TattooColor = 1ul << 45, - Eyebrows = 1ul << 46, - EyeColorLeft = 1ul << 47, - EyeShape = 1ul << 48, - IrisSize = 1ul << 49, - NoseShape = 1ul << 50, - JawShape = 1ul << 51, - MouthShape = 1ul << 52, - Lipstick = 1ul << 53, - LipColor = 1ul << 54, - MuscleMass = 1ul << 55, - TailShape = 1ul << 56, - BustSize = 1ul << 57, - FacePaint = 1ul << 58, - FacePaintReversed = 1ul << 59, - FacePaintColor = 1ul << 60, - CustomizeMask = ((1ul << 61) - 1) & ~EquipMask, - } -} diff --git a/Glamourer/Dalamud.cs b/Glamourer/Dalamud.cs index 80ac1a4..c42b2dc 100644 --- a/Glamourer/Dalamud.cs +++ b/Glamourer/Dalamud.cs @@ -1,56 +1,29 @@ using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.ClientState; -using Dalamud.Game.ClientState.Buddy; -using Dalamud.Game.ClientState.Conditions; -using Dalamud.Game.ClientState.Fates; -using Dalamud.Game.ClientState.JobGauge; -using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Objects; -using Dalamud.Game.ClientState.Party; using Dalamud.Game.Command; using Dalamud.Game.Gui; -using Dalamud.Game.Gui.FlyText; -using Dalamud.Game.Gui.PartyFinder; -using Dalamud.Game.Gui.Toast; -using Dalamud.Game.Libc; -using Dalamud.Game.Network; -using Dalamud.Game.Text.SeStringHandling; using Dalamud.IoC; using Dalamud.Plugin; + // ReSharper disable AutoPropertyCanBeMadeGetOnly.Local -namespace Glamourer +namespace Glamourer; + +public class Dalamud { - public class Dalamud - { - public static void Initialize(DalamudPluginInterface pluginInterface) - => pluginInterface.Create(); + public static void Initialize(DalamudPluginInterface pluginInterface) + => pluginInterface.Create(); // @formatter:off [PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static SigScanner SigScanner { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static SeStringManager SeStrings { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static ChatHandlers ChatHandlers { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static GameNetwork Network { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static Condition Conditions { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static KeyState Keys { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static FlyTextGui FlyTexts { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static ToastGui Toasts { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static JobGauges Gauges { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static PartyFinderGui PartyFinder { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static BuddyList Buddies { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static PartyList Party { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static FateTable Fates { get; private set; } = null!; - //[PluginService][RequiredVersion("1.0")] public static LibcFunction LibC { get; private set; } = null!; - // @formatter:on - } + // @formatter:on } diff --git a/Glamourer/Designs/FixedDesigns.cs b/Glamourer/Designs/FixedDesigns.cs index 5d0804f..de00ae6 100644 --- a/Glamourer/Designs/FixedDesigns.cs +++ b/Glamourer/Designs/FixedDesigns.cs @@ -5,6 +5,7 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; using Glamourer.FileSystem; +using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.PlayerWatch; @@ -122,19 +123,19 @@ namespace Glamourer.Designs private void OnPlayerChange(Character character) { - var name = character.Name.ToString(); - if (!EnabledDesigns.TryGetValue(name, out var designs)) - return; - - var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(character.ClassJob.Id)); - if (design == null) - return; - - PluginLog.Debug("Redrawing {CharacterName} with {DesignName} for job {JobGroup}.", name, design.Design.FullName(), - design.Jobs.Name); - design.Design.Data.Apply(character); - Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character); - Glamourer.Penumbra.RedrawObject(character, RedrawType.Redraw, false); + //var name = character.Name.ToString(); + //if (!EnabledDesigns.TryGetValue(name, out var designs)) + // return; + // + //var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(character.ClassJob.Id)); + //if (design == null) + // return; + // + //PluginLog.Debug("Redrawing {CharacterName} with {DesignName} for job {JobGroup}.", name, design.Design.FullName(), + // design.Jobs.Name); + //design.Design.Data.Apply(character); + //Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character); + //Glamourer.Penumbra.RedrawObject(character, RedrawType.Redraw, false); } public void Add(string name, Design design, JobGroup group, bool enabled = false) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index c20ca21..651aed0 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,211 +1,337 @@ using System; using System.Linq; using System.Reflection; +using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Command; +using Dalamud.Hooking; using Dalamud.Plugin; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Api; using Glamourer.Customization; using Glamourer.Designs; using Glamourer.FileSystem; using Glamourer.Gui; using ImGuiNET; +using Penumbra.GameData.ByteString; +using Penumbra.GameData.Enums; using Penumbra.PlayerWatch; -namespace Glamourer +namespace Glamourer; + +public unsafe class FixedDesignManager : IDisposable { - public class Glamourer : IDalamudPlugin + public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, uint* data); + + [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", + DetourName = nameof(FlagSlotForUpdateDetour))] + public Hook? FlagSlotForUpdateHook; + + public readonly FixedDesigns FixedDesigns; + + public FixedDesignManager(DesignManager designs) { - private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],"; - - public string Name - => "Glamourer"; - - public static GlamourerConfig Config = null!; - public static IPlayerWatcher PlayerWatcher = null!; - public static ICustomizationManager Customization = null!; - private readonly Interface _interface; - public readonly DesignManager Designs; - public readonly FixedDesigns FixedDesigns; - public static RevertableDesigns RevertableDesigns = new(); - public readonly GlamourerIpc GlamourerIpc; + SignatureHelper.Initialise(this); + FixedDesigns = new FixedDesigns(designs); - public static string Version = string.Empty; - public static PenumbraAttach Penumbra = null!; + if (Glamourer.Config.ApplyFixedDesigns) + Enable(); + } - public Glamourer(DalamudPluginInterface pluginInterface) + public void Enable() + { + FlagSlotForUpdateHook?.Enable(); + Glamourer.Penumbra.CreatingCharacterBase += ApplyFixedDesign; + } + + public void Disable() + { + FlagSlotForUpdateHook?.Disable(); + Glamourer.Penumbra.CreatingCharacterBase -= ApplyFixedDesign; + } + + public void Dispose() + { + FlagSlotForUpdateHook?.Dispose(); + } + + private void ApplyFixedDesign(IntPtr addr, IntPtr customize, IntPtr equipData) + { + var human = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)addr; + if (human->GameObject.ObjectKind is (byte)ObjectKind.EventNpc or (byte)ObjectKind.BattleNpc or (byte)ObjectKind.Player + && human->ModelCharaId == 0) { - Dalamud.Initialize(pluginInterface); - Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? ""; - Config = GlamourerConfig.Load(); - Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage); - Designs = new DesignManager(); - Penumbra = new PenumbraAttach(Config.AttachToPenumbra); - PlayerWatcher = PlayerWatchFactory.Create(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects); - GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface); - if (!Config.ApplyFixedDesigns) - PlayerWatcher.Disable(); - - FixedDesigns = new FixedDesigns(Designs); - - Dalamud.Commands.AddHandler("/glamourer", new CommandInfo(OnGlamourer) + var name = new Utf8String(human->GameObject.Name).ToString(); + if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs)) { - HelpMessage = "Open or close the Glamourer window.", - }); - Dalamud.Commands.AddHandler("/glamour", new CommandInfo(OnGlamour) - { - HelpMessage = $"Use Glamourer Functions: {HelpString}", - }); - - _interface = new Interface(this); - } - - public void OnGlamourer(string command, string arguments) - => _interface.ToggleVisibility(); - - private static GameObject? GetPlayer(string name) - { - var lowerName = name.ToLowerInvariant(); - return lowerName switch - { - "" => null, - "" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer, - "self" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer, - "" => Dalamud.Targets.Target, - "target" => Dalamud.Targets.Target, - "" => Dalamud.Targets.FocusTarget, - "focus" => Dalamud.Targets.FocusTarget, - "" => Dalamud.Targets.MouseOverTarget, - "mouseover" => Dalamud.Targets.MouseOverTarget, - _ => Dalamud.Objects.LastOrDefault( - a => string.Equals(a.Name.ToString(), lowerName, StringComparison.InvariantCultureIgnoreCase)), - }; - } - - public void CopyToClipboard(Character player) - { - var save = new CharacterSave(); - save.LoadCharacter(player); - ImGui.SetClipboardText(save.ToBase64()); - } - - public void ApplyCommand(Character player, string target) - { - CharacterSave? save = null; - if (target.ToLowerInvariant() == "clipboard") - try + var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob)); + if (design != null) { - save = CharacterSave.FromString(ImGui.GetClipboardText()); - } - catch (Exception) - { - Dalamud.Chat.PrintError("Clipboard does not contain a valid customization string."); - } - else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d) - Dalamud.Chat.PrintError("The given path to a saved design does not exist or does not point to a design."); - else - save = d.Data; + if (design.Design.Data.WriteCustomizations) + *(CharacterCustomization*)customize = design.Design.Data.Customizations; - save?.Apply(player); - Penumbra.UpdateCharacters(player); - } - - public void SaveCommand(Character player, string path) - { - var save = new CharacterSave(); - save.LoadCharacter(player); - try - { - var (folder, name) = Designs.FileSystem.CreateAllFolders(path); - var design = new Design(folder, name) { Data = save }; - folder.FindOrAddChild(design); - Designs.Designs.Add(design.FullName(), design.Data); - Designs.SaveToFile(); - } - catch (Exception e) - { - Dalamud.Chat.PrintError("Could not save file:"); - Dalamud.Chat.PrintError($" {e.Message}"); - } - } - - public void OnGlamour(string command, string arguments) - { - static void PrintHelp() - { - Dalamud.Chat.Print("Usage:"); - Dalamud.Chat.Print($" {HelpString}"); - } - - arguments = arguments.Trim(); - if (!arguments.Any()) - { - PrintHelp(); - return; - } - - var split = arguments.Split(new[] - { - ',', - }, 3, StringSplitOptions.RemoveEmptyEntries); - - if (split.Length < 2) - { - PrintHelp(); - return; - } - - var player = GetPlayer(split[1]) as Character; - if (player == null) - { - Dalamud.Chat.Print($"Could not find object for {split[1]} or it was not a Character."); - return; - } - - switch (split[0].ToLowerInvariant()) - { - case "copy": - CopyToClipboard(player); - return; - case "apply": - { - if (split.Length < 3) + var data = (uint*)equipData; + for (var i = 0u; i < 10; ++i) { - Dalamud.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'."); - return; + var slot = i.ToEquipSlot(); + if (design.Design.Data.WriteEquipment.Fits(slot)) + data[i] = slot switch + { + EquipSlot.Head => design.Design.Data.Equipment.Head.Value, + EquipSlot.Body => design.Design.Data.Equipment.Body.Value, + EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value, + EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value, + EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value, + EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value, + EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value, + EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value, + EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value, + EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value, + _ => 0, + }; } - - ApplyCommand(player, split[2]); - - return; } - case "save": - { - if (split.Length < 3) - { - Dalamud.Chat.Print("Saving requires a name for the save."); - return; - } - - SaveCommand(player, split[2]); - return; - } - default: - PrintHelp(); - return; } } - - public void Dispose() - { - FixedDesigns.Dispose(); - Penumbra.Dispose(); - PlayerWatcher.Dispose(); - _interface.Dispose(); - GlamourerIpc.Dispose(); - Dalamud.Commands.RemoveHandler("/glamour"); - Dalamud.Commands.RemoveHandler("/glamourer"); - } + } + + private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, uint* data) + { + ulong ret; + var slot = slotIdx.ToEquipSlot(); + try + { + if (slot != EquipSlot.Unknown) + { + var gameObject = + (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject); + if (gameObject != null) + { + var name = new Utf8String(gameObject->GameObject.Name).ToString(); + if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs)) + { + var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(gameObject->ClassJob)); + if (design != null && design.Design.Data.WriteEquipment.Fits(slot)) + *data = slot switch + { + EquipSlot.Head => design.Design.Data.Equipment.Head.Value, + EquipSlot.Body => design.Design.Data.Equipment.Body.Value, + EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value, + EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value, + EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value, + EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value, + EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value, + EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value, + EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value, + EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value, + _ => 0, + }; + } + } + } + } + finally + { + ret = FlagSlotForUpdateHook!.Original(drawObject, slotIdx, data); + } + + return ret; + } +} + +public class Glamourer : IDalamudPlugin +{ + private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],"; + + public string Name + => "Glamourer"; + + public static GlamourerConfig Config = null!; + public static IPlayerWatcher PlayerWatcher = null!; + public static ICustomizationManager Customization = null!; + public static FixedDesignManager FixedDesignManager = null!; + private readonly Interface _interface; + public readonly DesignManager Designs; + + public static RevertableDesigns RevertableDesigns = new(); + public readonly GlamourerIpc GlamourerIpc; + + + public static string Version = string.Empty; + public static PenumbraAttach Penumbra = null!; + + public Glamourer(DalamudPluginInterface pluginInterface) + { + Dalamud.Initialize(pluginInterface); + Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? ""; + Config = GlamourerConfig.Load(); + Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage); + Designs = new DesignManager(); + Penumbra = new PenumbraAttach(Config.AttachToPenumbra); + PlayerWatcher = PlayerWatchFactory.Create(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects); + GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface); + FixedDesignManager = new FixedDesignManager(Designs); + if (!Config.ApplyFixedDesigns) + PlayerWatcher.Disable(); + + Dalamud.Commands.AddHandler("/glamourer", new CommandInfo(OnGlamourer) + { + HelpMessage = "Open or close the Glamourer window.", + }); + Dalamud.Commands.AddHandler("/glamour", new CommandInfo(OnGlamour) + { + HelpMessage = $"Use Glamourer Functions: {HelpString}", + }); + + _interface = new Interface(this); + } + + public void OnGlamourer(string command, string arguments) + => _interface.ToggleVisibility(); + + private static GameObject? GetPlayer(string name) + { + var lowerName = name.ToLowerInvariant(); + return lowerName switch + { + "" => null, + "" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer, + "self" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer, + "" => Dalamud.Targets.Target, + "target" => Dalamud.Targets.Target, + "" => Dalamud.Targets.FocusTarget, + "focus" => Dalamud.Targets.FocusTarget, + "" => Dalamud.Targets.MouseOverTarget, + "mouseover" => Dalamud.Targets.MouseOverTarget, + _ => Dalamud.Objects.LastOrDefault( + a => string.Equals(a.Name.ToString(), lowerName, StringComparison.InvariantCultureIgnoreCase)), + }; + } + + public void CopyToClipboard(Character player) + { + var save = new CharacterSave(); + save.LoadCharacter(player); + ImGui.SetClipboardText(save.ToBase64()); + } + + public void ApplyCommand(Character player, string target) + { + CharacterSave? save = null; + if (target.ToLowerInvariant() == "clipboard") + try + { + save = CharacterSave.FromString(ImGui.GetClipboardText()); + } + catch (Exception) + { + Dalamud.Chat.PrintError("Clipboard does not contain a valid customization string."); + } + else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d) + Dalamud.Chat.PrintError("The given path to a saved design does not exist or does not point to a design."); + else + save = d.Data; + + save?.Apply(player); + Penumbra.UpdateCharacters(player); + } + + public void SaveCommand(Character player, string path) + { + var save = new CharacterSave(); + save.LoadCharacter(player); + try + { + var (folder, name) = Designs.FileSystem.CreateAllFolders(path); + var design = new Design(folder, name) { Data = save }; + folder.FindOrAddChild(design); + Designs.Designs.Add(design.FullName(), design.Data); + Designs.SaveToFile(); + } + catch (Exception e) + { + Dalamud.Chat.PrintError("Could not save file:"); + Dalamud.Chat.PrintError($" {e.Message}"); + } + } + + public void OnGlamour(string command, string arguments) + { + static void PrintHelp() + { + Dalamud.Chat.Print("Usage:"); + Dalamud.Chat.Print($" {HelpString}"); + } + + arguments = arguments.Trim(); + if (!arguments.Any()) + { + PrintHelp(); + return; + } + + var split = arguments.Split(new[] + { + ',', + }, 3, StringSplitOptions.RemoveEmptyEntries); + + if (split.Length < 2) + { + PrintHelp(); + return; + } + + var player = GetPlayer(split[1]) as Character; + if (player == null) + { + Dalamud.Chat.Print($"Could not find object for {split[1]} or it was not a Character."); + return; + } + + switch (split[0].ToLowerInvariant()) + { + case "copy": + CopyToClipboard(player); + return; + case "apply": + { + if (split.Length < 3) + { + Dalamud.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'."); + return; + } + + ApplyCommand(player, split[2]); + + return; + } + case "save": + { + if (split.Length < 3) + { + Dalamud.Chat.Print("Saving requires a name for the save."); + return; + } + + SaveCommand(player, split[2]); + return; + } + default: + PrintHelp(); + return; + } + } + + public void Dispose() + { + FixedDesignManager.Dispose(); + Penumbra.Dispose(); + PlayerWatcher.Dispose(); + _interface.Dispose(); + GlamourerIpc.Dispose(); + Dalamud.Commands.RemoveHandler("/glamour"); + Dalamud.Commands.RemoveHandler("/glamourer"); } } diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index b4a5bec..438ae51 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -50,6 +50,10 @@ $(appdata)\XIVLauncher\addon\Hooks\dev\Dalamud.dll False + + $(appdata)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll + False + $(appdata)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll False diff --git a/Glamourer/Gui/ComboWithFilter.cs b/Glamourer/Gui/ComboWithFilter.cs index 9a63425..c14d525 100644 --- a/Glamourer/Gui/ComboWithFilter.cs +++ b/Glamourer/Gui/ComboWithFilter.cs @@ -4,205 +4,204 @@ using System.Linq; using System.Numerics; using ImGuiNET; -namespace Glamourer.Gui +namespace Glamourer.Gui; + +public class ComboWithFilter { - public class ComboWithFilter + private readonly string _label; + private readonly string _filterLabel; + private readonly string _listLabel; + private string _currentFilter = string.Empty; + private string _currentFilterLower = string.Empty; + private bool _focus; + private readonly float _size; + private float _previewSize; + private readonly IReadOnlyList _items; + private readonly IReadOnlyList<(string, int)> _itemNamesLower; + private readonly Func _itemToName; + private IReadOnlyList<(string, int)> _currentItemNames; + private bool _needsClear; + + public Action? PrePreview; + public Action? PostPreview; + public Func? CreateSelectable; + public Action? PreList; + public Action? PostList; + public float? HeightPerItem; + + private float _heightPerItem; + + public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None; + public int ItemsAtOnce { get; set; } = 12; + + private void UpdateFilter(string newFilter) { - private readonly string _label; - private readonly string _filterLabel; - private readonly string _listLabel; - private string _currentFilter = string.Empty; - private string _currentFilterLower = string.Empty; - private bool _focus; - private readonly float _size; - private float _previewSize; - private readonly IReadOnlyList _items; - private readonly IReadOnlyList<(string, int)> _itemNamesLower; - private readonly Func _itemToName; - private IReadOnlyList<(string, int)> _currentItemNames; - private bool _needsClear; + if (newFilter == _currentFilter) + return; - public Action? PrePreview; - public Action? PostPreview; - public Func? CreateSelectable; - public Action? PreList; - public Action? PostList; - public float? HeightPerItem; - - private float _heightPerItem; - - public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None; - public int ItemsAtOnce { get; set; } = 12; - - private void UpdateFilter(string newFilter) - { - if (newFilter == _currentFilter) - return; - - var lower = newFilter.ToLowerInvariant(); - if (_currentFilterLower.Any() && lower.Contains(_currentFilterLower)) - _currentItemNames = _currentItemNames.Where(p => p.Item1.Contains(lower)).ToArray(); - else if (lower.Any()) - _currentItemNames = _itemNamesLower.Where(p => p.Item1.Contains(lower)).ToArray(); - else - _currentItemNames = _itemNamesLower; - _currentFilter = newFilter; - _currentFilterLower = lower; - } - - public ComboWithFilter(string label, float size, float previewSize, IReadOnlyList items, Func itemToName) - { - _label = label; - _filterLabel = $"##_{label}_filter"; - _listLabel = $"##_{label}_list"; - _itemToName = itemToName; - _items = items; - _size = size; - _previewSize = previewSize; - - _itemNamesLower = _items.Select((i, idx) => (_itemToName(i).ToLowerInvariant(), idx)).ToArray(); + var lower = newFilter.ToLowerInvariant(); + if (_currentFilterLower.Any() && lower.Contains(_currentFilterLower)) + _currentItemNames = _currentItemNames.Where(p => p.Item1.Contains(lower)).ToArray(); + else if (lower.Any()) + _currentItemNames = _itemNamesLower.Where(p => p.Item1.Contains(lower)).ToArray(); + else _currentItemNames = _itemNamesLower; + _currentFilter = newFilter; + _currentFilterLower = lower; + } + + public ComboWithFilter(string label, float size, float previewSize, IReadOnlyList items, Func itemToName) + { + _label = label; + _filterLabel = $"##_{label}_filter"; + _listLabel = $"##_{label}_list"; + _itemToName = itemToName; + _items = items; + _size = size; + _previewSize = previewSize; + + _itemNamesLower = _items.Select((i, idx) => (_itemToName(i).ToLowerInvariant(), idx)).ToArray(); + _currentItemNames = _itemNamesLower; + } + + public ComboWithFilter(string label, ComboWithFilter other) + { + _label = label; + _filterLabel = $"##_{label}_filter"; + _listLabel = $"##_{label}_list"; + _itemToName = other._itemToName; + _items = other._items; + _itemNamesLower = other._itemNamesLower; + _currentItemNames = other._currentItemNames; + _size = other._size; + _previewSize = other._previewSize; + PrePreview = other.PrePreview; + PostPreview = other.PostPreview; + CreateSelectable = other.CreateSelectable; + PreList = other.PreList; + PostList = other.PostList; + HeightPerItem = other.HeightPerItem; + Flags = other.Flags; + } + + private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value) + { + numItems = ItemsAtOnce; + nodeIdx = -1; + if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem))) + { + ImGui.EndChild(); + return false; } - public ComboWithFilter(string label, ComboWithFilter other) + var ret = false; + try { - _label = label; - _filterLabel = $"##_{label}_filter"; - _listLabel = $"##_{label}_list"; - _itemToName = other._itemToName; - _items = other._items; - _itemNamesLower = other._itemNamesLower; - _currentItemNames = other._currentItemNames; - _size = other._size; - _previewSize = other._previewSize; - PrePreview = other.PrePreview; - PostPreview = other.PostPreview; - CreateSelectable = other.CreateSelectable; - PreList = other.PreList; - PostList = other.PostList; - HeightPerItem = other.HeightPerItem; - Flags = other.Flags; - } - - private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value) - { - numItems = ItemsAtOnce; - nodeIdx = -1; - if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem))) + if (!_focus) { - ImGui.EndChild(); - return false; + ImGui.SetScrollY(0); + _focus = true; } - var ret = false; - try + var scrollY = Math.Max((int)(ImGui.GetScrollY() / _heightPerItem) - 1, 0); + var restHeight = scrollY * _heightPerItem; + numItems = 0; + nodeIdx = 0; + + if (restHeight > 0) + ImGui.Dummy(Vector2.UnitY * restHeight); + + for (var i = scrollY; i < _currentItemNames.Count; ++i) { - if (!_focus) + if (++numItems > ItemsAtOnce + 2) + continue; + + nodeIdx = _currentItemNames[i].Item2; + var item = _items[nodeIdx]!; + bool success; + if (CreateSelectable != null) { - ImGui.SetScrollY(0); - _focus = true; + success = CreateSelectable(item); + } + else + { + var name = _itemToName(item); + success = ImGui.Selectable(name, name == currentName); } - var scrollY = Math.Max((int) (ImGui.GetScrollY() / _heightPerItem) - 1, 0); - var restHeight = scrollY * _heightPerItem; - numItems = 0; - nodeIdx = 0; - - if (restHeight > 0) - ImGui.Dummy(Vector2.UnitY * restHeight); - - for (var i = scrollY; i < _currentItemNames.Count; ++i) + if (success) { - if (++numItems > ItemsAtOnce + 2) - continue; - - nodeIdx = _currentItemNames[i].Item2; - var item = _items[nodeIdx]!; - bool success; - if (CreateSelectable != null) - { - success = CreateSelectable(item); - } - else - { - var name = _itemToName(item); - success = ImGui.Selectable(name, name == currentName); - } - - if (success) - { - value = item; - ImGui.CloseCurrentPopup(); - ret = true; - } - } - - if (_currentItemNames.Count > ItemsAtOnce + 2) - ImGui.Dummy(Vector2.UnitY * (_currentItemNames.Count - ItemsAtOnce - 2 - scrollY) * _heightPerItem); - } - finally - { - ImGui.EndChild(); - } - - return ret; - } - - public bool Draw(string currentName, out T? value, float? size = null) - { - if (size.HasValue) - _previewSize = size.Value; - - value = default; - ImGui.SetNextItemWidth(_previewSize); - PrePreview?.Invoke(); - if (!ImGui.BeginCombo(_label, currentName, Flags)) - { - if (_needsClear) - { - _needsClear = false; - _focus = false; - UpdateFilter(string.Empty); - } - - PostPreview?.Invoke(); - return false; - } - - _needsClear = true; - PostPreview?.Invoke(); - - _heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing(); - - bool ret; - try - { - ImGui.SetNextItemWidth(-1); - var tmp = _currentFilter; - if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref tmp, 255)) - UpdateFilter(tmp); - - var isFocused = ImGui.IsItemActive(); - if (!_focus) - ImGui.SetKeyboardFocusHere(); - - PreList?.Invoke(); - ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value); - PostList?.Invoke(); - - if (!isFocused && numItems <= 1 && nodeIdx >= 0) - { - value = _items[nodeIdx]; - ret = true; + value = item; ImGui.CloseCurrentPopup(); + ret = true; } } - finally + + if (_currentItemNames.Count > ItemsAtOnce + 2) + ImGui.Dummy(Vector2.UnitY * (_currentItemNames.Count - ItemsAtOnce - 2 - scrollY) * _heightPerItem); + } + finally + { + ImGui.EndChild(); + } + + return ret; + } + + public bool Draw(string currentName, out T? value, float? size = null) + { + if (size.HasValue) + _previewSize = size.Value; + + value = default; + ImGui.SetNextItemWidth(_previewSize); + PrePreview?.Invoke(); + if (!ImGui.BeginCombo(_label, currentName, Flags)) + { + if (_needsClear) { - ImGui.EndCombo(); + _needsClear = false; + _focus = false; + UpdateFilter(string.Empty); } - return ret; + PostPreview?.Invoke(); + return false; } + + _needsClear = true; + PostPreview?.Invoke(); + + _heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing(); + + bool ret; + try + { + ImGui.SetNextItemWidth(-1); + var tmp = _currentFilter; + if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref tmp, 255)) + UpdateFilter(tmp); + + var isFocused = ImGui.IsItemActive(); + if (!_focus) + ImGui.SetKeyboardFocusHere(); + + PreList?.Invoke(); + ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value); + PostList?.Invoke(); + + if (!isFocused && numItems <= 1 && nodeIdx >= 0) + { + value = _items[nodeIdx]; + ret = true; + ImGui.CloseCurrentPopup(); + } + } + finally + { + ImGui.EndCombo(); + } + + return ret; } } diff --git a/Glamourer/Gui/ImGuiCustom.cs b/Glamourer/Gui/ImGuiCustom.cs deleted file mode 100644 index ed8dd04..0000000 --- a/Glamourer/Gui/ImGuiCustom.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Linq; -using ImGuiNET; - -namespace Glamourer.Gui -{ - public static partial class ImGuiCustom - { - public static void HoverTooltip(string text) - { - if (text.Any() && ImGui.IsItemHovered()) - ImGui.SetTooltip(text); - } - } -} diff --git a/Glamourer/Gui/ImGuiRaii.cs b/Glamourer/Gui/ImGuiRaii.cs deleted file mode 100644 index 5704be9..0000000 --- a/Glamourer/Gui/ImGuiRaii.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; -using ImGuiNET; - -namespace Glamourer.Gui -{ - public sealed class ImGuiRaii : IDisposable - { - private int _colorStack; - private int _fontStack; - private int _styleStack; - private float _indentation; - - private Stack? _onDispose; - - public static ImGuiRaii NewGroup() - => new ImGuiRaii().Group(); - - public ImGuiRaii Group() - => Begin(ImGui.BeginGroup, ImGui.EndGroup); - - public static ImGuiRaii NewTooltip() - => new ImGuiRaii().Tooltip(); - - public ImGuiRaii Tooltip() - => Begin(ImGui.BeginTooltip, ImGui.EndTooltip); - - public ImGuiRaii PushColor(ImGuiCol which, uint color) - { - ImGui.PushStyleColor(which, color); - ++_colorStack; - return this; - } - - public ImGuiRaii PushColor(ImGuiCol which, Vector4 color) - { - ImGui.PushStyleColor(which, color); - ++_colorStack; - return this; - } - - public ImGuiRaii PopColors(int n = 1) - { - var actualN = Math.Min(n, _colorStack); - if (actualN > 0) - { - ImGui.PopStyleColor(actualN); - _colorStack -= actualN; - } - - return this; - } - - public ImGuiRaii PushStyle(ImGuiStyleVar style, Vector2 value) - { - ImGui.PushStyleVar(style, value); - ++_styleStack; - return this; - } - - public ImGuiRaii PushStyle(ImGuiStyleVar style, float value) - { - ImGui.PushStyleVar(style, value); - ++_styleStack; - return this; - } - - public ImGuiRaii PopStyles(int n = 1) - { - var actualN = Math.Min(n, _styleStack); - if (actualN > 0) - { - ImGui.PopStyleVar(actualN); - _styleStack -= actualN; - } - - return this; - } - - public ImGuiRaii PushFont(ImFontPtr font) - { - ImGui.PushFont(font); - ++_fontStack; - return this; - } - - public ImGuiRaii PopFonts(int n = 1) - { - var actualN = Math.Min(n, _fontStack); - - while (actualN-- > 0) - { - ImGui.PopFont(); - --_fontStack; - } - - return this; - } - - public ImGuiRaii Indent(float width) - { - if (width != 0) - { - ImGui.Indent(width); - _indentation += width; - } - - return this; - } - - public ImGuiRaii Unindent(float width) - => Indent(-width); - - public bool Begin(Func begin, Action end) - { - if (begin()) - { - _onDispose ??= new Stack(); - _onDispose.Push(end); - return true; - } - - return false; - } - - public ImGuiRaii Begin(Action begin, Action end) - { - begin(); - _onDispose ??= new Stack(); - _onDispose.Push(end); - return this; - } - - public void End(int n = 1) - { - var actualN = Math.Min(n, _onDispose?.Count ?? 0); - while (actualN-- > 0) - _onDispose!.Pop()(); - } - - public void Dispose() - { - Unindent(_indentation); - PopColors(_colorStack); - PopStyles(_styleStack); - PopFonts(_fontStack); - if (_onDispose != null) - { - End(_onDispose.Count); - _onDispose = null; - } - } - } -} diff --git a/Glamourer/Gui/Interface.cs b/Glamourer/Gui/Interface.cs index bc3bf07..4f61a25 100644 --- a/Glamourer/Gui/Interface.cs +++ b/Glamourer/Gui/Interface.cs @@ -7,104 +7,104 @@ using Dalamud.Game.ClientState.Objects.Types; using Glamourer.Designs; using ImGuiNET; using Lumina.Excel.GeneratedSheets; +using OtterGui.Raii; using Penumbra.GameData; using Penumbra.GameData.Enums; -namespace Glamourer.Gui +namespace Glamourer.Gui; + +internal partial class Interface : IDisposable { - internal partial class Interface : IDisposable + public const float SelectorWidth = 200; + public const float MinWindowWidth = 675; + public const int GPoseObjectId = 201; + private const string PluginName = "Glamourer"; + private readonly string _glamourerHeader; + + private readonly IReadOnlyDictionary _stains; + private readonly IReadOnlyDictionary _models; + private readonly IObjectIdentifier _identifier; + private readonly Dictionary, ComboWithFilter)> _combos; + private readonly ImGuiScene.TextureWrap? _legacyTattooIcon; + private readonly Dictionary _equipSlotNames; + private readonly DesignManager _designs; + private readonly Glamourer _plugin; + + private bool _visible; + private bool _inGPose; + + public Interface(Glamourer plugin) { - public const float SelectorWidth = 200; - public const float MinWindowWidth = 675; - public const int GPoseObjectId = 201; - private const string PluginName = "Glamourer"; - private readonly string _glamourerHeader; + _plugin = plugin; + _designs = plugin.Designs; + _glamourerHeader = Glamourer.Version.Length > 0 + ? $"{PluginName} v{Glamourer.Version}###{PluginName}Main" + : $"{PluginName}###{PluginName}Main"; + Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true; + Dalamud.PluginInterface.UiBuilder.Draw += Draw; + Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility; - private readonly IReadOnlyDictionary _stains; - private readonly IReadOnlyDictionary _models; - private readonly IObjectIdentifier _identifier; - private readonly Dictionary, ComboWithFilter)> _combos; - private readonly ImGuiScene.TextureWrap? _legacyTattooIcon; - private readonly Dictionary _equipSlotNames; - private readonly DesignManager _designs; - private readonly Glamourer _plugin; + _equipSlotNames = GetEquipSlotNames(); - private bool _visible; - private bool _inGPose; + _stains = GameData.Stains(Dalamud.GameData); + _models = GameData.Models(Dalamud.GameData); + _identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage); - public Interface(Glamourer plugin) + + var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray()); + + var equip = GameData.ItemsBySlot(Dalamud.GameData); + _combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo)); + _legacyTattooIcon = GetLegacyTattooIcon(); + } + + public void ToggleVisibility() + => _visible = !_visible; + + public void Dispose() + { + _legacyTattooIcon?.Dispose(); + Dalamud.PluginInterface.UiBuilder.Draw -= Draw; + Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= ToggleVisibility; + } + + private void Draw() + { + if (!_visible) + return; + + ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale, + Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale); + if (!ImGui.Begin(_glamourerHeader, ref _visible)) { - _plugin = plugin; - _designs = plugin.Designs; - _glamourerHeader = Glamourer.Version.Length > 0 - ? $"{PluginName} v{Glamourer.Version}###{PluginName}Main" - : $"{PluginName}###{PluginName}Main"; - Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true; - Dalamud.PluginInterface.UiBuilder.Draw += Draw; - Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility; - - _equipSlotNames = GetEquipSlotNames(); - - _stains = GameData.Stains(Dalamud.GameData); - _models = GameData.Models(Dalamud.GameData); - _identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage); - - - var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray()); - - var equip = GameData.ItemsBySlot(Dalamud.GameData); - _combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo)); - _legacyTattooIcon = GetLegacyTattooIcon(); + ImGui.End(); + return; } - public void ToggleVisibility() - => _visible = !_visible; - - public void Dispose() + try { - _legacyTattooIcon?.Dispose(); - Dalamud.PluginInterface.UiBuilder.Draw -= Draw; - Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= ToggleVisibility; + using var tabBar = ImRaii.TabBar("##tabBar"); + if (!tabBar) + return; + + _inGPose = Dalamud.Objects[GPoseObjectId] != null; + _iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2; + _actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; + _comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; + _percentageSize = _comboSelectorSize; + _inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X; + _raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X; + _itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1; + + DrawPlayerTab(); + DrawSaves(); + DrawFixedDesignsTab(); + DrawConfigTab(); + DrawRevertablesTab(); } - - private void Draw() + finally { - if (!_visible) - return; - - ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale, - Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale); - if (!ImGui.Begin(_glamourerHeader, ref _visible)) - { - ImGui.End(); - return; - } - - try - { - using var raii = new ImGuiRaii(); - if (!raii.Begin(() => ImGui.BeginTabBar("##tabBar"), ImGui.EndTabBar)) - return; - - _inGPose = Dalamud.Objects[GPoseObjectId] != null; - _iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2; - _actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; - _comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; - _percentageSize = _comboSelectorSize; - _inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X; - _raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X; - _itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1; - - DrawPlayerTab(); - DrawSaves(); - DrawFixedDesignsTab(); - DrawConfigTab(); - DrawRevertablesTab(); - } - finally - { - ImGui.End(); - } + ImGui.End(); } } } diff --git a/Glamourer/Gui/InterfaceActorPanel.cs b/Glamourer/Gui/InterfaceActorPanel.cs index 3ff2f14..0a63756 100644 --- a/Glamourer/Gui/InterfaceActorPanel.cs +++ b/Glamourer/Gui/InterfaceActorPanel.cs @@ -11,300 +11,298 @@ using Glamourer.Customization; using Glamourer.Designs; using Glamourer.FileSystem; using ImGuiNET; +using OtterGui; +using OtterGui.Raii; using Penumbra.GameData.Structs; using Penumbra.PlayerWatch; -namespace Glamourer.Gui +namespace Glamourer.Gui; + +internal partial class Interface { - internal partial class Interface + private readonly CharacterSave _currentSave = new(); + private string _newDesignName = string.Empty; + private bool _keyboardFocus; + private bool _holdShift; + private bool _holdCtrl; + private const string DesignNamePopupLabel = "Save Design As..."; + private const uint RedHeaderColor = 0xFF1818C0; + private const uint GreenHeaderColor = 0xFF18C018; + + private void DrawPlayerHeader() { - private readonly CharacterSave _currentSave = new(); - private string _newDesignName = string.Empty; - private bool _keyboardFocus; - private bool _holdShift; - private bool _holdCtrl; - private const string DesignNamePopupLabel = "Save Design As..."; - private const uint RedHeaderColor = 0xFF1818C0; - private const uint GreenHeaderColor = 0xFF18C018; + var color = _player == null ? RedHeaderColor : GreenHeaderColor; + var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); + using var c = ImRaii.PushColor(ImGuiCol.Text, color) + .Push(ImGuiCol.Button, buttonColor) + .Push(ImGuiCol.ButtonHovered, buttonColor) + .Push(ImGuiCol.ButtonActive, buttonColor); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f); + } - private void DrawPlayerHeader() + private static void DrawCopyClipboardButton(CharacterSave save) + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString())) + ImGui.SetClipboardText(save.ToBase64()); + ImGui.PopFont(); + ImGuiUtil.HoverTooltip("Copy customization code to clipboard."); + } + + private static void ConditionalApply(CharacterSave save, Character player) + { + if (ImGui.GetIO().KeyShift) + save.ApplyOnlyCustomizations(player); + else if (ImGui.GetIO().KeyCtrl) + save.ApplyOnlyEquipment(player); + else + save.Apply(player); + } + + private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl) + { + var copy = save.Copy(); + if (shift) { - var color = _player == null ? RedHeaderColor : GreenHeaderColor; - var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); - using var raii = new ImGuiRaii() - .PushColor(ImGuiCol.Text, color) - .PushColor(ImGuiCol.Button, buttonColor) - .PushColor(ImGuiCol.ButtonHovered, buttonColor) - .PushColor(ImGuiCol.ButtonActive, buttonColor) - .PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .PushStyle(ImGuiStyleVar.FrameRounding, 0); - ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f); + copy.Load(new CharacterEquipment()); + copy.SetHatState = false; + copy.SetVisorState = false; + copy.SetWeaponState = false; + copy.WriteEquipment = CharacterEquipMask.None; + } + else if (ctrl) + { + copy.Load(CharacterCustomization.Default); + copy.SetHatState = false; + copy.SetVisorState = false; + copy.SetWeaponState = false; + copy.WriteCustomizations = false; } - private static void DrawCopyClipboardButton(CharacterSave save) + return copy; + } + + private bool DrawApplyClipboardButton() + { + ImGui.PushFont(UiBuilder.IconFont); + var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null; + ImGui.PopFont(); + ImGuiUtil.HoverTooltip( + "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment."); + + if (!applyButton) + return false; + + try { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString())) - ImGui.SetClipboardText(save.ToBase64()); - ImGui.PopFont(); - ImGuiCustom.HoverTooltip("Copy customization code to clipboard."); - } - - private static void ConditionalApply(CharacterSave save, Character player) - { - if (ImGui.GetIO().KeyShift) - save.ApplyOnlyCustomizations(player); - else if (ImGui.GetIO().KeyCtrl) - save.ApplyOnlyEquipment(player); - else - save.Apply(player); - } - - private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl) - { - var copy = save.Copy(); - if (shift) - { - copy.Load(new CharacterEquipment()); - copy.SetHatState = false; - copy.SetVisorState = false; - copy.SetWeaponState = false; - copy.WriteEquipment = CharacterEquipMask.None; - } - else if (ctrl) - { - copy.Load(CharacterCustomization.Default); - copy.SetHatState = false; - copy.SetVisorState = false; - copy.SetWeaponState = false; - copy.WriteCustomizations = false; - } - - return copy; - } - - private bool DrawApplyClipboardButton() - { - ImGui.PushFont(UiBuilder.IconFont); - var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null; - ImGui.PopFont(); - ImGuiCustom.HoverTooltip("Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment."); - - if (!applyButton) + var text = ImGui.GetClipboardText(); + if (!text.Any()) return false; - try - { - var text = ImGui.GetClipboardText(); - if (!text.Any()) - return false; - var save = CharacterSave.FromString(text); - ConditionalApply(save, _player!); - } - catch (Exception e) - { - PluginLog.Information($"{e}"); - return false; - } - - return true; + var save = CharacterSave.FromString(text); + ConditionalApply(save, _player!); + } + catch (Exception e) + { + PluginLog.Information($"{e}"); + return false; } - private void DrawSaveDesignButton() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Save.ToIconString())) - OpenDesignNamePopup(DesignNameUse.SaveCurrent); + return true; + } - ImGui.PopFont(); - ImGuiCustom.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment."); + private void DrawSaveDesignButton() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Save.ToIconString())) + OpenDesignNamePopup(DesignNameUse.SaveCurrent); - DrawDesignNamePopup(DesignNameUse.SaveCurrent); - } + ImGui.PopFont(); + ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment."); - private void DrawTargetPlayerButton() - { - if (ImGui.Button("Target Player")) - Dalamud.Targets.SetTarget(_player); - } + DrawDesignNamePopup(DesignNameUse.SaveCurrent); + } - private void DrawApplyToPlayerButton(CharacterSave save) - { - if (!ImGui.Button("Apply to Self")) - return; + private void DrawTargetPlayerButton() + { + if (ImGui.Button("Target Player")) + Dalamud.Targets.SetTarget(_player); + } - var player = _inGPose - ? (Character?) Dalamud.Objects[GPoseObjectId] - : Dalamud.ClientState.LocalPlayer; - var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null; - if (player == null) - return; + private void DrawApplyToPlayerButton(CharacterSave save) + { + if (!ImGui.Button("Apply to Self")) + return; - ConditionalApply(save, player); - if (_inGPose) - ConditionalApply(save, fallback!); - Glamourer.Penumbra.UpdateCharacters(player, fallback); - } + var player = _inGPose + ? (Character?)Dalamud.Objects[GPoseObjectId] + : Dalamud.ClientState.LocalPlayer; + var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null; + if (player == null) + return; - - private static Character? TransformToCustomizable(Character? actor) - { - if (actor == null) - return null; + ConditionalApply(save, player); + if (_inGPose) + ConditionalApply(save, fallback!); + Glamourer.Penumbra.UpdateCharacters(player, fallback); + } - if (actor.ModelType() == 0) - return actor; - actor.SetModelType(0); - CharacterCustomization.Default.Write(actor.Address); + private static Character? TransformToCustomizable(Character? actor) + { + if (actor == null) + return null; + + if (actor.ModelType() == 0) return actor; - } - private void DrawApplyToTargetButton(CharacterSave save) + actor.SetModelType(0); + CharacterCustomization.Default.Write(actor.Address); + return actor; + } + + private void DrawApplyToTargetButton(CharacterSave save) + { + if (!ImGui.Button("Apply to Target")) + return; + + var player = TransformToCustomizable(CharacterFactory.Convert(Dalamud.Targets.Target)); + if (player == null) + return; + + var fallBackCharacter = _gPoseActors.TryGetValue(player.Name.ToString(), out var f) ? f : null; + ConditionalApply(save, player); + if (fallBackCharacter != null) + ConditionalApply(save, fallBackCharacter!); + Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter); + } + + private void DrawRevertButton() + { + if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null)) + return; + + Glamourer.RevertableDesigns.Revert(_player!); + var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null; + if (fallBackCharacter != null) + Glamourer.RevertableDesigns.Revert(fallBackCharacter); + Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter); + } + + private void SaveNewDesign(CharacterSave save) + { + try { - if (!ImGui.Button("Apply to Target")) + var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName); + if (!name.Any()) return; - var player = TransformToCustomizable(CharacterFactory.Convert(Dalamud.Targets.Target)); - if (player == null) - return; + var newDesign = new Design(folder, name) { Data = save }; + folder.AddChild(newDesign); + _designs.Designs[newDesign.FullName()] = save; + _designs.SaveToFile(); + } + catch (Exception e) + { + PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}"); + } + } - var fallBackCharacter = _gPoseActors.TryGetValue(player.Name.ToString(), out var f) ? f : null; - ConditionalApply(save, player); - if (fallBackCharacter != null) - ConditionalApply(save, fallBackCharacter!); - Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter); + private void DrawMonsterPanel() + { + if (DrawApplyClipboardButton()) + Glamourer.Penumbra.UpdateCharacters(_player!); + + ImGui.SameLine(); + if (ImGui.Button("Convert to Character")) + { + TransformToCustomizable(_player); + _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)"); + Glamourer.Penumbra.UpdateCharacters(_player!); } - private void DrawRevertButton() + if (!_inGPose) { - if (!DrawDisableButton("Revert", _player == null)) - return; - - Glamourer.RevertableDesigns.Revert(_player!); - var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null; - if (fallBackCharacter != null) - Glamourer.RevertableDesigns.Revert(fallBackCharacter); - Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter); - } - - private void SaveNewDesign(CharacterSave save) - { - try - { - var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName); - if (!name.Any()) - return; - - var newDesign = new Design(folder, name) { Data = save }; - folder.AddChild(newDesign); - _designs.Designs[newDesign.FullName()] = save; - _designs.SaveToFile(); - } - catch (Exception e) - { - PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}"); - } - } - - private void DrawMonsterPanel() - { - if (DrawApplyClipboardButton()) - Glamourer.Penumbra.UpdateCharacters(_player!); - ImGui.SameLine(); - if (ImGui.Button("Convert to Character")) - { - TransformToCustomizable(_player); - _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)"); - Glamourer.Penumbra.UpdateCharacters(_player!); - } + DrawTargetPlayerButton(); + } - if (!_inGPose) + var currentModel = _player!.ModelType(); + using var combo = ImRaii.Combo("Model Id", currentModel.ToString()); + if (!combo) + return; + + foreach (var (id, _) in _models.Skip(1)) + { + if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel) + continue; + + _player!.SetModelType((int)id); + Glamourer.Penumbra.UpdateCharacters(_player!); + } + } + + private void DrawPlayerPanel() + { + DrawCopyClipboardButton(_currentSave); + ImGui.SameLine(); + var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton(); + ImGui.SameLine(); + DrawSaveDesignButton(); + ImGui.SameLine(); + DrawApplyToPlayerButton(_currentSave); + if (!_inGPose) + { + ImGui.SameLine(); + DrawApplyToTargetButton(_currentSave); + if (_player != null && !_currentSave.WriteProtected) { ImGui.SameLine(); DrawTargetPlayerButton(); } - - var currentModel = _player!.ModelType(); - using var raii = new ImGuiRaii(); - if (!raii.Begin(() => ImGui.BeginCombo("Model Id", currentModel.ToString()), ImGui.EndCombo)) - return; - - foreach (var (id, _) in _models.Skip(1)) - { - if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel) - continue; - - _player!.SetModelType((int) id); - Glamourer.Penumbra.UpdateCharacters(_player!); - } } - private void DrawPlayerPanel() + var data = _currentSave; + if (!_currentSave.WriteProtected) { - DrawCopyClipboardButton(_currentSave); ImGui.SameLine(); - var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton(); - ImGui.SameLine(); - DrawSaveDesignButton(); - ImGui.SameLine(); - DrawApplyToPlayerButton(_currentSave); - if (!_inGPose) - { - ImGui.SameLine(); - DrawApplyToTargetButton(_currentSave); - if (_player != null && !_currentSave.WriteProtected) - { - ImGui.SameLine(); - DrawTargetPlayerButton(); - } - } - - var data = _currentSave; - if (!_currentSave.WriteProtected) - { - ImGui.SameLine(); - DrawRevertButton(); - } - else - { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f); - data = data.Copy(); - } - - if (DrawCustomization(ref data.Customizations) && _player != null) - { - Glamourer.RevertableDesigns.Add(_player); - _currentSave.Customizations.Write(_player.Address); - changes = true; - } - - changes |= DrawEquip(data.Equipment); - changes |= DrawMiscellaneous(data, _player); - - if (_player != null && changes) - Glamourer.Penumbra.UpdateCharacters(_player); - if (_currentSave.WriteProtected) - ImGui.PopStyleVar(); + DrawRevertButton(); } - - private void DrawActorPanel() + else { - using var raii = ImGuiRaii.NewGroup(); - DrawPlayerHeader(); - if (!ImGui.BeginChild("##playerData", -Vector2.One, true)) - { - ImGui.EndChild(); - return; - } - - if (_player == null || _player.ModelType() == 0) - DrawPlayerPanel(); - else - DrawMonsterPanel(); - - ImGui.EndChild(); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f); + data = data.Copy(); } + + if (DrawCustomization(ref data.Customizations) && _player != null) + { + Glamourer.RevertableDesigns.Add(_player); + _currentSave.Customizations.Write(_player.Address); + changes = true; + } + + changes |= DrawEquip(data.Equipment); + changes |= DrawMiscellaneous(data, _player); + + if (_player != null && changes) + Glamourer.Penumbra.UpdateCharacters(_player); + if (_currentSave.WriteProtected) + ImGui.PopStyleVar(); + } + + private void DrawActorPanel() + { + using var group = ImRaii.Group(); + DrawPlayerHeader(); + using var child = ImRaii.Child("##playerData", -Vector2.One, true); + if (!child) + return; + + if (_player == null || _player.ModelType() == 0) + DrawPlayerPanel(); + else + DrawMonsterPanel(); } } diff --git a/Glamourer/Gui/InterfaceActorSelector.cs b/Glamourer/Gui/InterfaceActorSelector.cs index e8fdf33..f132c2a 100644 --- a/Glamourer/Gui/InterfaceActorSelector.cs +++ b/Glamourer/Gui/InterfaceActorSelector.cs @@ -7,6 +7,8 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface; using Dalamud.Logging; using ImGuiNET; +using OtterGui; +using OtterGui.Raii; using Penumbra.PlayerWatch; namespace Glamourer.Gui; @@ -27,9 +29,8 @@ internal partial class Interface private void DrawPlayerFilter() { - using var raii = new ImGuiRaii() - .PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .PushStyle(ImGuiStyleVar.FrameRounding, 0); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); ImGui.SetNextItemWidth(SelectorWidth * ImGui.GetIO().FontGlobalScale); if (ImGui.InputTextWithHint("##playerFilter", "Filter Players...", ref _playerFilter, 32)) _playerFilterLower = _playerFilter.ToLowerInvariant(); @@ -115,23 +116,22 @@ internal partial class Interface private void DrawSelectionButtons() { - using var raii = new ImGuiRaii() - .PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .PushStyle(ImGuiStyleVar.FrameRounding, 0) - .PushFont(UiBuilder.IconFont); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + using var font = ImRaii.PushFont(UiBuilder.IconFont); Character? select = null; var buttonWidth = Vector2.UnitX * SelectorWidth / 2; if (ImGui.Button(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth)) select = Dalamud.ClientState.LocalPlayer; - raii.PopFonts(); - ImGuiCustom.HoverTooltip("Select the local player character."); + font.Pop(); + ImGuiUtil.HoverTooltip("Select the local player character."); ImGui.SameLine(); - raii.PushFont(UiBuilder.IconFont); + font.Push(UiBuilder.IconFont); if (_inGPose) { - raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); + style.Push(ImGuiStyleVar.Alpha, 0.5f); ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth); - raii.PopStyles(); + style.Pop(); } else { @@ -139,8 +139,8 @@ internal partial class Interface select = CharacterFactory.Convert(Dalamud.Targets.Target); } - raii.PopFonts(); - ImGuiCustom.HoverTooltip("Select the current target, if it is in the list."); + font.Pop(); + ImGuiUtil.HoverTooltip("Select the current target, if it is in the list."); if (select == null) return; @@ -196,7 +196,7 @@ internal partial class Interface } - using (var _ = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) + using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) { ImGui.EndChild(); } @@ -207,14 +207,14 @@ internal partial class Interface private void DrawPlayerTab() { - using var raii = new ImGuiRaii(); + using var tab = ImRaii.TabItem("Current Players"); _player = null; - if (!raii.Begin(() => ImGui.BeginTabItem("Current Players"), ImGui.EndTabItem)) + if (!tab) return; DrawPlayerSelector(); - if (!_currentLabel.Any()) + if (_currentLabel.Length == 0) return; ImGui.SameLine(); diff --git a/Glamourer/Gui/InterfaceConfig.cs b/Glamourer/Gui/InterfaceConfig.cs index 7d4c683..daea985 100644 --- a/Glamourer/Gui/InterfaceConfig.cs +++ b/Glamourer/Gui/InterfaceConfig.cs @@ -1,6 +1,8 @@ using System; using System.Numerics; using ImGuiNET; +using OtterGui; +using OtterGui.Raii; namespace Glamourer.Gui { @@ -11,7 +13,7 @@ namespace Glamourer.Gui if (DrawCheckMark(label, value, setter)) Glamourer.Config.Save(); - ImGuiCustom.HoverTooltip(tooltip); + ImGuiUtil.HoverTooltip(tooltip); } private static void ChangeAndSave(T value, T currentValue, Action setter) where T : IEquatable @@ -33,19 +35,19 @@ namespace Glamourer.Gui ImGui.SameLine(); if (ImGui.Button($"Default##{name}")) ChangeAndSave(defaultValue, value, setter); - ImGuiCustom.HoverTooltip( + ImGuiUtil.HoverTooltip( $"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}"); ImGui.SameLine(); ImGui.Text(name); - ImGuiCustom.HoverTooltip(tooltip); + ImGuiUtil.HoverTooltip(tooltip); } - private void DrawRestorePenumbraButton() + private static void DrawRestorePenumbraButton() { const string buttonLabel = "Re-Register Penumbra"; if (!Glamourer.Config.AttachToPenumbra) { - using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.Alpha, 0.5f); + using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); ImGui.Button(buttonLabel); return; } @@ -53,14 +55,14 @@ namespace Glamourer.Gui if (ImGui.Button(buttonLabel)) Glamourer.Penumbra.Reattach(true); - ImGuiCustom.HoverTooltip( + ImGuiUtil.HoverTooltip( "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality."); } - private void DrawConfigTab() + private static void DrawConfigTab() { - using var raii = new ImGuiRaii(); - if (!raii.Begin(() => ImGui.BeginTabItem("Config"), ImGui.EndTabItem)) + using var tab = ImRaii.TabItem("Config"); + if (!tab) return; var cfg = Glamourer.Config; diff --git a/Glamourer/Gui/InterfaceCustomization.cs b/Glamourer/Gui/InterfaceCustomization.cs index 31c500f..835259f 100644 --- a/Glamourer/Gui/InterfaceCustomization.cs +++ b/Glamourer/Gui/InterfaceCustomization.cs @@ -5,6 +5,8 @@ using Dalamud.Interface; using Dalamud.Logging; using Glamourer.Customization; using ImGuiNET; +using OtterGui; +using OtterGui.Raii; using Penumbra.GameData.Enums; namespace Glamourer.Gui @@ -14,13 +16,14 @@ namespace Glamourer.Gui private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value) { value = default; - if (!ImGui.BeginPopup(label, ImGuiWindowFlags.AlwaysAutoResize)) + using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); + if (!popup) return false; var ret = false; var count = set.Count(id); - using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .PushStyle(ImGuiStyleVar.FrameRounding, 0); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); for (var i = 0; i < count; ++i) { var custom = set.Data(id, i); @@ -35,7 +38,6 @@ namespace Glamourer.Gui ImGui.SameLine(); } - ImGui.EndPopup(); return ret; } @@ -58,7 +60,7 @@ namespace Glamourer.Gui ret = true; } - ImGuiCustom.HoverTooltip($"Input Range: [{minValue}, {maxValue}]"); + ImGuiUtil.HoverTooltip($"Input Range: [{minValue}, {maxValue}]"); return ret; } @@ -92,7 +94,7 @@ namespace Glamourer.Gui ImGui.SameLine(); - using (var _ = ImGuiRaii.NewGroup()) + using (var _ = ImRaii.Group()) { if (InputInt($"##text_{id}", ref current, 1, count)) { @@ -102,7 +104,7 @@ namespace Glamourer.Gui ImGui.Text(label); - ImGuiCustom.HoverTooltip(tooltip); + ImGuiUtil.HoverTooltip(tooltip); } if (!DrawColorPickerPopup(popupName, set, id, out var newCustom)) @@ -117,7 +119,7 @@ namespace Glamourer.Gui private bool DrawListSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id, CustomizationSet set) { - using var bigGroup = ImGuiRaii.NewGroup(); + using var bigGroup = ImRaii.Group(); var ret = false; int current = customization[id]; var count = set.Count(id); @@ -146,7 +148,7 @@ namespace Glamourer.Gui ImGui.SameLine(); ImGui.Text(label); - ImGuiCustom.HoverTooltip(tooltip); + ImGuiUtil.HoverTooltip(tooltip); return ret; } @@ -157,10 +159,10 @@ namespace Glamourer.Gui private bool DrawMultiSelector(ref CharacterCustomization customization, CustomizationSet set) { - using var bigGroup = ImGuiRaii.NewGroup(); + using var bigGroup = ImRaii.Group(); var ret = false; var count = set.Count(CustomizationId.FacialFeaturesTattoos); - using (var _ = ImGuiRaii.NewGroup()) + using (var _ = ImRaii.Group()) { var face = customization.Face; if (set.Faces.Count < face) @@ -180,11 +182,7 @@ namespace Glamourer.Gui customization.FacialFeature(i, !enabled); } - if (ImGui.IsItemHovered()) - { - using var tt = ImGuiRaii.NewTooltip(); - ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height)); - } + ImGuiUtil.HoverIconTooltip(icon, _iconSize); if (i % 4 != 3) ImGui.SameLine(); @@ -192,7 +190,7 @@ namespace Glamourer.Gui } ImGui.SameLine(); - using var group = ImGuiRaii.NewGroup(); + using var group = ImRaii.Group(); ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2); int value = customization[CustomizationId.FacialFeaturesTattoos]; if (InputInt($"##{CustomizationId.FacialFeaturesTattoos}", ref value, 1, 256)) @@ -201,7 +199,7 @@ namespace Glamourer.Gui ret = true; } - ImGui.Text(set.Option(CustomizationId.FacialFeaturesTattoos)); + ImGui.TextUnformatted(set.Option(CustomizationId.FacialFeaturesTattoos)); return ret; } @@ -210,13 +208,14 @@ namespace Glamourer.Gui private bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value) { value = default; - if (!ImGui.BeginPopup(label, ImGuiWindowFlags.AlwaysAutoResize)) + using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); + if (!popup) return false; var ret = false; var count = set.Count(id); - using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .PushStyle(ImGuiStyleVar.FrameRounding, 0); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); for (var i = 0; i < count; ++i) { var custom = set.Data(id, i); @@ -229,11 +228,7 @@ namespace Glamourer.Gui ImGui.CloseCurrentPopup(); } - if (ImGui.IsItemHovered()) - { - using var tt = ImGuiRaii.NewTooltip(); - ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height)); - } + ImGuiUtil.HoverIconTooltip(icon, _iconSize); var text = custom.Value.ToString(); var textWidth = ImGui.CalcTextSize(text).X; @@ -244,14 +239,13 @@ namespace Glamourer.Gui ImGui.SameLine(); } - ImGui.EndPopup(); return ret; } private bool DrawIconSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id, CustomizationSet set) { - using var bigGroup = ImGuiRaii.NewGroup(); + using var bigGroup = ImRaii.Group(); var ret = false; var count = set.Count(id); @@ -268,14 +262,10 @@ namespace Glamourer.Gui if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) ImGui.OpenPopup(popupName); - if (ImGui.IsItemHovered()) - { - using var tt = ImGuiRaii.NewTooltip(); - ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height)); - } + ImGuiUtil.HoverIconTooltip(icon, _iconSize); ImGui.SameLine(); - using var group = ImGuiRaii.NewGroup(); + using var group = ImRaii.Group(); if (InputInt($"##text_{id}", ref current, 1, count)) { customization[id] = set.Data(id, current).Value; @@ -288,8 +278,8 @@ namespace Glamourer.Gui ret = true; } - ImGui.Text($"{label} ({custom.Value.Value})"); - ImGuiCustom.HoverTooltip(tooltip); + ImGui.TextUnformatted($"{label} ({custom.Value.Value})"); + ImGuiUtil.HoverTooltip(tooltip); return ret; } @@ -298,7 +288,7 @@ namespace Glamourer.Gui private bool DrawPercentageSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id, CustomizationSet set) { - using var bigGroup = ImGuiRaii.NewGroup(); + using var bigGroup = ImRaii.Group(); var ret = false; int value = customization[id]; var count = set.Count(id); @@ -318,15 +308,15 @@ namespace Glamourer.Gui } ImGui.SameLine(); - ImGui.Text(label); - ImGuiCustom.HoverTooltip(tooltip); + ImGui.TextUnformatted(label); + ImGuiUtil.HoverTooltip(tooltip); return ret; } private bool DrawRaceSelector(ref CharacterCustomization customization) { - using var group = ImGuiRaii.NewGroup(); + using var group = ImRaii.Group(); var ret = false; ImGui.SetNextItemWidth(_raceSelectorWidth); if (ImGui.BeginCombo("##subRaceCombo", ClanName(customization.Clan, customization.Gender))) @@ -343,7 +333,7 @@ namespace Glamourer.Gui ImGui.EndCombo(); } - ImGui.Text( + ImGui.TextUnformatted( $"{Glamourer.Customization.GetName(CustomName.Gender)} & {Glamourer.Customization.GetName(CustomName.Clan)}"); return ret; @@ -352,7 +342,7 @@ namespace Glamourer.Gui private bool DrawGenderSelector(ref CharacterCustomization customization) { var ret = false; - ImGui.PushFont(UiBuilder.IconFont); + using var font = ImRaii.PushFont(UiBuilder.IconFont); var icon = customization.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus; var restricted = false; if (customization.Race == Race.Hrothgar) @@ -370,7 +360,6 @@ namespace Glamourer.Gui if (restricted) ImGui.PopStyleVar(); - ImGui.PopFont(); return ret; } diff --git a/Glamourer/Gui/InterfaceDesigns.cs b/Glamourer/Gui/InterfaceDesigns.cs index c317b31..f2f8113 100644 --- a/Glamourer/Gui/InterfaceDesigns.cs +++ b/Glamourer/Gui/InterfaceDesigns.cs @@ -6,369 +6,369 @@ using Dalamud.Logging; using Glamourer.Designs; using Glamourer.FileSystem; using ImGuiNET; +using OtterGui; +using OtterGui.Raii; -namespace Glamourer.Gui +namespace Glamourer.Gui; + +internal partial class Interface { - internal partial class Interface + private int _totalObject; + + private bool _inDesignMode; + private Design? _selection; + private string _newChildName = string.Empty; + + private void DrawDesignSelector() { - private int _totalObject; - - private bool _inDesignMode; - private Design? _selection; - private string _newChildName = string.Empty; - - private void DrawDesignSelector() + _totalObject = 0; + ImGui.BeginGroup(); + if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true)) { - _totalObject = 0; - ImGui.BeginGroup(); - if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true)) - { - DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical); - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - ImGui.EndChild(); - ImGui.PopStyleVar(); - } - - DrawDesignSelectorButtons(); - ImGui.EndGroup(); + DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical); + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + ImGui.EndChild(); + ImGui.PopStyleVar(); } - private void DrawPasteClipboardButton() - { - if (_selection!.Data.WriteProtected) - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + DrawDesignSelectorButtons(); + ImGui.EndGroup(); + } - ImGui.PushFont(UiBuilder.IconFont); - var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()); - ImGui.PopFont(); - if (_selection!.Data.WriteProtected) + private void DrawPasteClipboardButton() + { + if (_selection!.Data.WriteProtected) + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + + ImGui.PushFont(UiBuilder.IconFont); + var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()); + ImGui.PopFont(); + if (_selection!.Data.WriteProtected) + ImGui.PopStyleVar(); + + ImGuiUtil.HoverTooltip("Overwrite with customization code from clipboard."); + + if (_selection!.Data.WriteProtected || !applyButton) + return; + + var text = ImGui.GetClipboardText(); + if (!text.Any()) + return; + + try + { + _selection!.Data = CharacterSave.FromString(text); + _designs.SaveToFile(); + } + catch (Exception e) + { + PluginLog.Information($"{e}"); + } + } + + private void DrawNewFolderButton() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5)) + OpenDesignNamePopup(DesignNameUse.NewFolder); + ImGui.PopFont(); + ImGuiUtil.HoverTooltip("Create a new, empty Folder."); + + DrawDesignNamePopup(DesignNameUse.NewFolder); + } + + private void DrawNewDesignButton() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5)) + OpenDesignNamePopup(DesignNameUse.NewDesign); + ImGui.PopFont(); + ImGuiUtil.HoverTooltip("Create a new, empty Design."); + + DrawDesignNamePopup(DesignNameUse.NewDesign); + } + + private void DrawClipboardDesignButton() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5)) + OpenDesignNamePopup(DesignNameUse.FromClipboard); + ImGui.PopFont(); + ImGuiUtil.HoverTooltip("Create a new design from the customization string in your clipboard."); + + DrawDesignNamePopup(DesignNameUse.FromClipboard); + } + + private void DrawDeleteDesignButton() + { + ImGui.PushFont(UiBuilder.IconFont); + var style = _selection == null; + if (style) + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null) + { + _designs.DeleteAllChildren(_selection, false); + _selection = null; + } + + ImGui.PopFont(); + if (style) + ImGui.PopStyleVar(); + ImGuiUtil.HoverTooltip("Delete the currently selected Design."); + } + + private void DrawDuplicateDesignButton() + { + ImGui.PushFont(UiBuilder.IconFont); + if (_selection == null) + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null) + OpenDesignNamePopup(DesignNameUse.DuplicateDesign); + ImGui.PopFont(); + if (_selection == null) + ImGui.PopStyleVar(); + ImGuiUtil.HoverTooltip( + "Clone the currently selected Design.\nHold Shift to only clone the customizations.\nHold Control to only clone the equipment."); + + DrawDesignNamePopup(DesignNameUse.DuplicateDesign); + } + + private void DrawDesignSelectorButtons() + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0f); + + DrawNewFolderButton(); + ImGui.SameLine(); + DrawNewDesignButton(); + ImGui.SameLine(); + DrawClipboardDesignButton(); + ImGui.SameLine(); + DrawDuplicateDesignButton(); + ImGui.SameLine(); + DrawDeleteDesignButton(); + } + + private void DrawDesignHeaderButtons() + { + DrawCopyClipboardButton(_selection!.Data); + ImGui.SameLine(); + DrawPasteClipboardButton(); + ImGui.SameLine(); + DrawApplyToPlayerButton(_selection!.Data); + if (!_inGPose) + { + ImGui.SameLine(); + DrawApplyToTargetButton(_selection!.Data); + } + + ImGui.SameLine(); + DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false); + } + + private void DrawDesignPanel() + { + if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true)) + { + DrawDesignHeaderButtons(); + var data = _selection!.Data; + var prot = _selection!.Data.WriteProtected; + if (prot) + { + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f); + data = data.Copy(); + } + + DrawGeneralSettings(data, prot); + var mask = data.WriteEquipment; + if (DrawEquip(data.Equipment, ref mask) && !prot) + { + data.WriteEquipment = mask; + _designs.SaveToFile(); + } + + if (DrawCustomization(ref data.Customizations) && !prot) + _designs.SaveToFile(); + + if (DrawMiscellaneous(data, null) && !prot) + _designs.SaveToFile(); + + if (prot) ImGui.PopStyleVar(); - ImGuiCustom.HoverTooltip("Overwrite with customization code from clipboard."); + ImGui.EndChild(); + } + } - if (_selection!.Data.WriteProtected || !applyButton) - return; + private void DrawSaves() + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale); + using var tab = ImRaii.TabItem("Designs"); + _inDesignMode = tab.Success; + if (!_inDesignMode) + return; - var text = ImGui.GetClipboardText(); - if (!text.Any()) - return; + DrawDesignSelector(); + if (_selection != null) + { + ImGui.SameLine(); + DrawDesignPanel(); + } + } + + private void DrawCheckbox(string label, bool value, Action setter, bool prot) + { + var tmp = value; + if (ImGui.Checkbox(label, ref tmp) && tmp != value) + { + setter(tmp); + if (!prot) + _designs.SaveToFile(); + } + } + + private void DrawGeneralSettings(CharacterSave data, bool prot) + { + ImGui.BeginGroup(); + DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot); + DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot); + ImGui.EndGroup(); + ImGui.SameLine(); + ImGui.BeginGroup(); + DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot); + DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot); + ImGui.EndGroup(); + } + + private void RenameChildInput(IFileSystemBase child) + { + ImGui.SetNextItemWidth(150); + if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64, + ImGuiInputTextFlags.EnterReturnsTrue)) + return; + + if (_newChildName.Any() && _newChildName != child.Name) try { - _selection!.Data = CharacterSave.FromString(text); - _designs.SaveToFile(); + var oldPath = child.FullName(); + if (_designs.FileSystem.Rename(child, _newChildName)) + _designs.UpdateAllChildren(oldPath, child); } catch (Exception e) { - PluginLog.Information($"{e}"); + PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}"); } - } - - private void DrawNewFolderButton() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5)) - OpenDesignNamePopup(DesignNameUse.NewFolder); - ImGui.PopFont(); - ImGuiCustom.HoverTooltip("Create a new, empty Folder."); - - DrawDesignNamePopup(DesignNameUse.NewFolder); - } - - private void DrawNewDesignButton() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5)) - OpenDesignNamePopup(DesignNameUse.NewDesign); - ImGui.PopFont(); - ImGuiCustom.HoverTooltip("Create a new, empty Design."); - - DrawDesignNamePopup(DesignNameUse.NewDesign); - } - - private void DrawClipboardDesignButton() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5)) - OpenDesignNamePopup(DesignNameUse.FromClipboard); - ImGui.PopFont(); - ImGuiCustom.HoverTooltip("Create a new design from the customization string in your clipboard."); - - DrawDesignNamePopup(DesignNameUse.FromClipboard); - } - - private void DrawDeleteDesignButton() - { - ImGui.PushFont(UiBuilder.IconFont); - var style = _selection == null; - if (style) - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); - if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null) + else if (child is Folder f) + try { - _designs.DeleteAllChildren(_selection, false); - _selection = null; + var oldPath = child.FullName(); + if (_designs.FileSystem.Merge(f, f.Parent, true)) + _designs.UpdateAllChildren(oldPath, f.Parent); } - - ImGui.PopFont(); - if (style) - ImGui.PopStyleVar(); - ImGuiCustom.HoverTooltip("Delete the currently selected Design."); - } - - private void DrawDuplicateDesignButton() - { - ImGui.PushFont(UiBuilder.IconFont); - if (_selection == null) - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); - if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null) - OpenDesignNamePopup(DesignNameUse.DuplicateDesign); - ImGui.PopFont(); - if (_selection == null) - ImGui.PopStyleVar(); - ImGuiCustom.HoverTooltip("Clone the currently selected Design.\nHold Shift to only clone the customizations.\nHold Control to only clone the equipment."); - - DrawDesignNamePopup(DesignNameUse.DuplicateDesign); - } - - private void DrawDesignSelectorButtons() - { - using var raii = new ImGuiRaii() - .PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .PushStyle(ImGuiStyleVar.FrameRounding, 0f); - - DrawNewFolderButton(); - ImGui.SameLine(); - DrawNewDesignButton(); - ImGui.SameLine(); - DrawClipboardDesignButton(); - ImGui.SameLine(); - DrawDuplicateDesignButton(); - ImGui.SameLine(); - DrawDeleteDesignButton(); - } - - private void DrawDesignHeaderButtons() - { - DrawCopyClipboardButton(_selection!.Data); - ImGui.SameLine(); - DrawPasteClipboardButton(); - ImGui.SameLine(); - DrawApplyToPlayerButton(_selection!.Data); - if (!_inGPose) + catch (Exception e) { - ImGui.SameLine(); - DrawApplyToTargetButton(_selection!.Data); + PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}"); } - ImGui.SameLine(); - DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false); + _newChildName = string.Empty; + } + + private void ContextMenu(IFileSystemBase child) + { + var label = $"##fsPopup{child.FullName()}"; + if (ImGui.BeginPopup(label)) + { + if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift) + _designs.DeleteAllChildren(child, false); + ImGuiUtil.HoverTooltip("Hold Control and Shift to delete."); + + RenameChildInput(child); + + if (child is Design d && ImGui.MenuItem("Copy to Clipboard")) + ImGui.SetClipboardText(d.Data.ToBase64()); + + ImGui.EndPopup(); } - private void DrawDesignPanel() + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { - if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true)) - { - DrawDesignHeaderButtons(); - var data = _selection!.Data; - var prot = _selection!.Data.WriteProtected; - if (prot) - { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f); - data = data.Copy(); - } - - DrawGeneralSettings(data, prot); - var mask = data.WriteEquipment; - if (DrawEquip(data.Equipment, ref mask) && !prot) - { - data.WriteEquipment = mask; - _designs.SaveToFile(); - } - - if (DrawCustomization(ref data.Customizations) && !prot) - _designs.SaveToFile(); - - if (DrawMiscellaneous(data, null) && !prot) - _designs.SaveToFile(); - - if (prot) - ImGui.PopStyleVar(); - - ImGui.EndChild(); - } - } - - private void DrawSaves() - { - using var raii = new ImGuiRaii(); - raii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale); - _inDesignMode = raii.Begin(() => ImGui.BeginTabItem("Designs"), ImGui.EndTabItem); - if (!_inDesignMode) - return; - - DrawDesignSelector(); - - if (_selection != null) - { - ImGui.SameLine(); - DrawDesignPanel(); - } - } - - private void DrawCheckbox(string label, bool value, Action setter, bool prot) - { - var tmp = value; - if (ImGui.Checkbox(label, ref tmp) && tmp != value) - { - setter(tmp); - if (!prot) - _designs.SaveToFile(); - } - } - - private void DrawGeneralSettings(CharacterSave data, bool prot) - { - ImGui.BeginGroup(); - DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot); - DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot); - ImGui.EndGroup(); - ImGui.SameLine(); - ImGui.BeginGroup(); - DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot); - DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot); - ImGui.EndGroup(); - } - - private void RenameChildInput(IFileSystemBase child) - { - ImGui.SetNextItemWidth(150); - if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64, - ImGuiInputTextFlags.EnterReturnsTrue)) - return; - - if (_newChildName.Any() && _newChildName != child.Name) - try - { - var oldPath = child.FullName(); - if (_designs.FileSystem.Rename(child, _newChildName)) - _designs.UpdateAllChildren(oldPath, child); - } - catch (Exception e) - { - PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}"); - } - else if (child is Folder f) - try - { - var oldPath = child.FullName(); - if (_designs.FileSystem.Merge(f, f.Parent, true)) - _designs.UpdateAllChildren(oldPath, f.Parent); - } - catch (Exception e) - { - PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}"); - } - - _newChildName = string.Empty; - } - - private void ContextMenu(IFileSystemBase child) - { - var label = $"##fsPopup{child.FullName()}"; - if (ImGui.BeginPopup(label)) - { - if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift) - _designs.DeleteAllChildren(child, false); - ImGuiCustom.HoverTooltip("Hold Control and Shift to delete."); - - RenameChildInput(child); - - if (child is Design d && ImGui.MenuItem("Copy to Clipboard")) - ImGui.SetClipboardText(d.Data.ToBase64()); - - ImGui.EndPopup(); - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - _newChildName = child.Name; - ImGui.OpenPopup(label); - } - } - - private static uint GetDesignColor(CharacterSave save) - { - const uint white = 0xFFFFFFFF; - const uint grey = 0xFF808080; - if (!Glamourer.Config.ColorDesigns) - return white; - - var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f; - if (save.WriteCustomizations) - if (save.WriteEquipment != CharacterEquipMask.None) - return white; - else - return changesStates ? white : Glamourer.Config.CustomizationColor; - - if (save.WriteEquipment != CharacterEquipMask.None) - return changesStates ? white : Glamourer.Config.EquipmentColor; - - return changesStates ? Glamourer.Config.StateColor : grey; - } - - private void DrawFolderContent(Folder folder, SortMode mode) - { - foreach (var child in folder.AllChildren(mode).ToArray()) - { - if (child.IsFolder(out var subFolder)) - { - var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}"); - DrawOrnaments(child); - - if (treeNode) - { - DrawFolderContent(subFolder, mode); - ImGui.TreePop(); - } - else - { - _totalObject += subFolder.TotalDescendantLeaves(); - } - } - else - { - if (child is not Design d) - continue; - - ++_totalObject; - var color = GetDesignColor(d.Data); - using var raii = new ImGuiRaii() - .PushColor(ImGuiCol.Text, color); - - var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection)); - raii.PopColors(); - DrawOrnaments(child); - - if (Glamourer.Config.ShowLocks && d.Data.WriteProtected) - { - ImGui.SameLine(); - raii.PushFont(UiBuilder.IconFont) - .PushColor(ImGuiCol.Text, color); - ImGui.Text(FontAwesomeIcon.Lock.ToIconString()); - } - - if (selected) - _selection = d; - } - } - } - - private void DrawOrnaments(IFileSystemBase child) - { - FileSystemImGui.DragDropSource(child); - if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder)) - _designs.UpdateAllChildren(oldPath, draggedFolder!); - ContextMenu(child); + _newChildName = child.Name; + ImGui.OpenPopup(label); } } + + private static uint GetDesignColor(CharacterSave save) + { + const uint white = 0xFFFFFFFF; + const uint grey = 0xFF808080; + if (!Glamourer.Config.ColorDesigns) + return white; + + var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f; + if (save.WriteCustomizations) + if (save.WriteEquipment != CharacterEquipMask.None) + return white; + else + return changesStates ? white : Glamourer.Config.CustomizationColor; + + if (save.WriteEquipment != CharacterEquipMask.None) + return changesStates ? white : Glamourer.Config.EquipmentColor; + + return changesStates ? Glamourer.Config.StateColor : grey; + } + + private void DrawFolderContent(Folder folder, SortMode mode) + { + foreach (var child in folder.AllChildren(mode).ToArray()) + { + if (child.IsFolder(out var subFolder)) + { + var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}"); + DrawOrnaments(child); + + if (treeNode) + { + DrawFolderContent(subFolder, mode); + ImGui.TreePop(); + } + else + { + _totalObject += subFolder.TotalDescendantLeaves(); + } + } + else + { + if (child is not Design d) + continue; + + ++_totalObject; + var color = GetDesignColor(d.Data); + using var c = ImRaii.PushColor(ImGuiCol.Text, color); + + var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection)); + c.Pop(); + DrawOrnaments(child); + + if (Glamourer.Config.ShowLocks && d.Data.WriteProtected) + { + ImGui.SameLine(); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + c.Push(ImGuiCol.Text, color); + ImGui.TextUnformatted(FontAwesomeIcon.Lock.ToIconString()); + } + + if (selected) + _selection = d; + } + } + } + + private void DrawOrnaments(IFileSystemBase child) + { + FileSystemImGui.DragDropSource(child); + if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder)) + _designs.UpdateAllChildren(oldPath, draggedFolder!); + ContextMenu(child); + } } diff --git a/Glamourer/Gui/InterfaceEquipment.cs b/Glamourer/Gui/InterfaceEquipment.cs index 019fc64..419c617 100644 --- a/Glamourer/Gui/InterfaceEquipment.cs +++ b/Glamourer/Gui/InterfaceEquipment.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using ImGuiNET; using Lumina.Text; +using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -20,7 +21,7 @@ namespace Glamourer.Gui var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx); if (!change && (byte) stainIdx != 0) { - ImGuiCustom.HoverTooltip("Right-click to clear."); + ImGuiUtil.HoverTooltip("Right-click to clear."); if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { change = true; @@ -46,7 +47,7 @@ namespace Glamourer.Gui var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId; if (!change && !ReferenceEquals(item, SmallClothes)) { - ImGuiCustom.HoverTooltip("Right-click to clear."); + ImGuiUtil.HoverTooltip("Right-click to clear."); if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { change = true; diff --git a/Glamourer/Gui/InterfaceFixedDesigns.cs b/Glamourer/Gui/InterfaceFixedDesigns.cs index e0aa9c8..aa5d0ea 100644 --- a/Glamourer/Gui/InterfaceFixedDesigns.cs +++ b/Glamourer/Gui/InterfaceFixedDesigns.cs @@ -6,161 +6,163 @@ using Dalamud.Interface; using Glamourer.Designs; using Glamourer.FileSystem; using ImGuiNET; +using OtterGui.Raii; -namespace Glamourer.Gui +namespace Glamourer.Gui; + +internal partial class Interface { - internal partial class Interface + private const string FixDragDropLabel = "##FixDragDrop"; + + private List? _fullPathCache; + private string _newFixCharacterName = string.Empty; + private string _newFixDesignPath = string.Empty; + private JobGroup? _newFixDesignGroup; + private Design? _newFixDesign; + private int _fixDragDropIdx = -1; + + private static unsafe bool IsDropping() + => ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null; + + private void DrawFixedDesignsTab() { - private const string FixDragDropLabel = "##FixDragDrop"; + _newFixDesignGroup ??= Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1]; - private List? _fullPathCache; - private string _newFixCharacterName = string.Empty; - private string _newFixDesignPath = string.Empty; - private JobGroup? _newFixDesignGroup; - private Design? _newFixDesign; - private int _fixDragDropIdx = -1; - - private static unsafe bool IsDropping() - => ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null; - - private void DrawFixedDesignsTab() + using var tabItem = ImRaii.TabItem("Fixed Designs"); + if (!tabItem) { - _newFixDesignGroup ??= _plugin.FixedDesigns.JobGroups[1]; + _fullPathCache = null; + _newFixDesign = null; + _newFixDesignPath = string.Empty; + _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1]; + return; + } - using var raii = new ImGuiRaii(); - if (!raii.Begin(() => ImGui.BeginTabItem("Fixed Designs"), ImGui.EndTabItem)) - { - _fullPathCache = null; - _newFixDesign = null; - _newFixDesignPath = string.Empty; - _newFixDesignGroup = _plugin.FixedDesigns.JobGroups[1]; - return; - } + _fullPathCache ??= Glamourer.FixedDesignManager.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList(); - _fullPathCache ??= _plugin.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList(); + using var table = ImRaii.Table("##FixedTable", 4); + var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale; - raii.Begin(() => ImGui.BeginTable("##FixedTable", 4), ImGui.EndTable); + ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth); + ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Jobs", ImGuiTableColumnFlags.WidthFixed, 175 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + var xPos = 0f; - var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale; - - - ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth); - ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Jobs", ImGuiTableColumnFlags.WidthFixed, 175 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - var xPos = 0f; - for (var i = 0; i < _fullPathCache.Count; ++i) - { - var path = _fullPathCache[i]; - var name = _plugin.FixedDesigns.Data[i]; - - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - raii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2); - raii.PushFont(UiBuilder.IconFont); - if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}")) - { - _fullPathCache.RemoveAt(i--); - _plugin.FixedDesigns.Remove(name); - continue; - } - - var tmp = name.Enabled; - ImGui.SameLine(); - xPos = ImGui.GetCursorPosX(); - if (ImGui.Checkbox($"##Enabled{i}", ref tmp)) - if (tmp && _plugin.FixedDesigns.EnableDesign(name) - || !tmp && _plugin.FixedDesigns.DisableDesign(name)) - { - Glamourer.Config.FixedDesigns[i].Enabled = tmp; - Glamourer.Config.Save(); - } - - raii.PopStyles(); - raii.PopFonts(); - ImGui.TableNextColumn(); - ImGui.Selectable($"{name.Name}##Fix{i}"); - if (ImGui.BeginDragDropSource()) - { - _fixDragDropIdx = i; - ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0); - ImGui.Text($"Dragging {name.Name} ({path})..."); - ImGui.EndDragDropSource(); - } - if (ImGui.BeginDragDropTarget()) - { - if (IsDropping() && _fixDragDropIdx >= 0) - { - var d = _plugin.FixedDesigns.Data[_fixDragDropIdx]; - _plugin.FixedDesigns.Move(d, i); - var p = _fullPathCache[_fixDragDropIdx]; - _fullPathCache.RemoveAt(_fixDragDropIdx); - _fullPathCache.Insert(i, p); - _fixDragDropIdx = -1; - } - ImGui.EndDragDropTarget(); - } - - ImGui.TableNextColumn(); - ImGui.Text(_plugin.FixedDesigns.Data[i].Jobs.Name); - ImGui.TableNextColumn(); - ImGui.Text(path); - } + using var style = new ImRaii.Style(); + using var font = new ImRaii.Font(); + for (var i = 0; i < _fullPathCache.Count; ++i) + { + var path = _fullPathCache[i]; + var name = Glamourer.FixedDesignManager.FixedDesigns.Data[i]; ImGui.TableNextRow(); ImGui.TableNextColumn(); - raii.PushFont(UiBuilder.IconFont); - - ImGui.SetCursorPosX(xPos); - if (_newFixDesign == null || _newFixCharacterName == string.Empty) + style.Push(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2); + font.Push(UiBuilder.IconFont); + if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}")) { - raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); - ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"); - raii.PopStyles(); - } - else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix")) - { - _fullPathCache.Add(_newFixDesignPath); - _plugin.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, _newFixDesignGroup.Value, false); - _newFixCharacterName = string.Empty; - _newFixDesignPath = string.Empty; - _newFixDesign = null; - _newFixDesignGroup = _plugin.FixedDesigns.JobGroups[1]; + _fullPathCache.RemoveAt(i--); + Glamourer.FixedDesignManager.FixedDesigns.Remove(name); + continue; } - raii.PopFonts(); - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32); - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(-1); - if (raii.Begin(() => ImGui.BeginCombo("##NewFixDesignGroup", _newFixDesignGroup.Value.Name), ImGui.EndCombo)) - { - foreach (var (id, group) in _plugin.FixedDesigns.JobGroups) + var tmp = name.Enabled; + ImGui.SameLine(); + xPos = ImGui.GetCursorPosX(); + if (ImGui.Checkbox($"##Enabled{i}", ref tmp)) + if (tmp && Glamourer.FixedDesignManager.FixedDesigns.EnableDesign(name) + || !tmp && Glamourer.FixedDesignManager.FixedDesigns.DisableDesign(name)) { - ImGui.SetNextItemWidth(-1); - if (ImGui.Selectable($"{group.Name}##NewFixDesignGroup", group.Name == _newFixDesignGroup.Value.Name)) - _newFixDesignGroup = group; + Glamourer.Config.FixedDesigns[i].Enabled = tmp; + Glamourer.Config.Save(); } - raii.End(); + + style.Pop(); + font.Pop(); + ImGui.TableNextColumn(); + ImGui.Selectable($"{name.Name}##Fix{i}"); + if (ImGui.BeginDragDropSource()) + { + _fixDragDropIdx = i; + ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0); + ImGui.Text($"Dragging {name.Name} ({path})..."); + ImGui.EndDragDropSource(); + } + + if (ImGui.BeginDragDropTarget()) + { + if (IsDropping() && _fixDragDropIdx >= 0) + { + var d = Glamourer.FixedDesignManager.FixedDesigns.Data[_fixDragDropIdx]; + Glamourer.FixedDesignManager.FixedDesigns.Move(d, i); + var p = _fullPathCache[_fixDragDropIdx]; + _fullPathCache.RemoveAt(_fixDragDropIdx); + _fullPathCache.Insert(i, p); + _fixDragDropIdx = -1; + } + + ImGui.EndDragDropTarget(); } ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(-1); - if (!raii.Begin(() => ImGui.BeginCombo("##NewFixPath", _newFixDesignPath), ImGui.EndCombo)) - return; + ImGui.Text(Glamourer.FixedDesignManager.FixedDesigns.Data[i].Jobs.Name); + ImGui.TableNextColumn(); + ImGui.Text(path); + } - foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast()) + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + font.Push(UiBuilder.IconFont); + + ImGui.SetCursorPosX(xPos); + if (_newFixDesign == null || _newFixCharacterName == string.Empty) + { + style.Push(ImGuiStyleVar.Alpha, 0.5f); + ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"); + style.Pop(); + } + else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix")) + { + _fullPathCache.Add(_newFixDesignPath); + Glamourer.FixedDesignManager.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, _newFixDesignGroup.Value, false); + _newFixCharacterName = string.Empty; + _newFixDesignPath = string.Empty; + _newFixDesign = null; + _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1]; + } + + font.Pop(); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(-1); + using var combo = ImRaii.Combo("##NewFixDesignGroup", _newFixDesignGroup.Value.Name); + if (combo) + foreach (var (id, group) in Glamourer.FixedDesignManager.FixedDesigns.JobGroups) { - var fullName = design.FullName(); ImGui.SetNextItemWidth(-1); - if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath)) - continue; - - _newFixDesignPath = fullName; - _newFixDesign = design; + if (ImGui.Selectable($"{group.Name}##NewFixDesignGroup", group.Name == _newFixDesignGroup.Value.Name)) + _newFixDesignGroup = group; } + + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(-1); + using var combo2 = ImRaii.Combo("##NewFixPath", _newFixDesignPath); + if (!combo2) + return; + + foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast()) + { + var fullName = design.FullName(); + ImGui.SetNextItemWidth(-1); + if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath)) + continue; + + _newFixDesignPath = fullName; + _newFixDesign = design; } } } diff --git a/Glamourer/Gui/InterfaceHelpers.cs b/Glamourer/Gui/InterfaceHelpers.cs index 88845ec..1c48741 100644 --- a/Glamourer/Gui/InterfaceHelpers.cs +++ b/Glamourer/Gui/InterfaceHelpers.cs @@ -2,6 +2,7 @@ using System.Linq; using Dalamud.Logging; using Glamourer.Customization; +using Glamourer.Structs; using ImGuiNET; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/InterfaceInitialization.cs b/Glamourer/Gui/InterfaceInitialization.cs index b3d8dc2..7e20374 100644 --- a/Glamourer/Gui/InterfaceInitialization.cs +++ b/Glamourer/Gui/InterfaceInitialization.cs @@ -4,6 +4,7 @@ using System.Reflection; using ImGuiNET; using Penumbra.GameData.Enums; using Lumina.Excel.GeneratedSheets; +using Glamourer.Structs; namespace Glamourer.Gui { diff --git a/Glamourer/Gui/InterfaceMiscellaneous.cs b/Glamourer/Gui/InterfaceMiscellaneous.cs index 3867dbf..f9e4cc4 100644 --- a/Glamourer/Gui/InterfaceMiscellaneous.cs +++ b/Glamourer/Gui/InterfaceMiscellaneous.cs @@ -2,74 +2,62 @@ using Dalamud.Game.ClientState.Objects.Types; using ImGuiNET; -namespace Glamourer.Gui +namespace Glamourer.Gui; + +internal partial class Interface { - internal partial class Interface + private static bool DrawCheckMark(string label, bool value, Action setter) { - private static bool DrawCheckMark(string label, bool value, Action setter) + var startValue = value; + if (ImGui.Checkbox(label, ref startValue) && startValue != value) { - var startValue = value; - if (ImGui.Checkbox(label, ref startValue) && startValue != value) - { - setter(startValue); - return true; - } - - return false; + setter(startValue); + return true; } - private static bool DrawDisableButton(string label, bool disabled) - { - if (!disabled) - return ImGui.Button(label); - - using var raii = new ImGuiRaii(); - raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); - ImGui.Button(label); - return false; - } - - private static bool DrawMiscellaneous(CharacterSave save, Character? player) - { - var ret = false; - if (!ImGui.CollapsingHeader("Miscellaneous")) - return ret; - - ret |= DrawCheckMark("Hat Visible", save.HatState, v => - { - save.HatState = v; - player?.SetHatVisible(v); - }); - - ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v => - { - save.WeaponState = v; - player?.SetWeaponHidden(!v); - }); - - ret |= DrawCheckMark("Visor Toggled", save.VisorState, v => - { - save.VisorState = v; - player?.SetVisorToggled(v); - }); - - ret |= DrawCheckMark("Is Wet", save.IsWet, v => - { - save.IsWet = v; - player?.SetWetness(v); - }); - - var alpha = save.Alpha; - if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha) - { - alpha = (float) Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2); - save.Alpha = alpha; - ret = true; - if (player != null) - player.Alpha() = alpha; - } + return false; + } + private static bool DrawMiscellaneous(CharacterSave save, Character? player) + { + var ret = false; + if (!ImGui.CollapsingHeader("Miscellaneous")) return ret; + + ret |= DrawCheckMark("Hat Visible", save.HatState, v => + { + save.HatState = v; + player?.SetHatVisible(v); + }); + + ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v => + { + save.WeaponState = v; + player?.SetWeaponHidden(!v); + }); + + ret |= DrawCheckMark("Visor Toggled", save.VisorState, v => + { + save.VisorState = v; + player?.SetVisorToggled(v); + }); + + ret |= DrawCheckMark("Is Wet", save.IsWet, v => + { + save.IsWet = v; + player?.SetWetness(v); + }); + + var alpha = save.Alpha; + if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha) + { + alpha = (float)Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2); + save.Alpha = alpha; + ret = true; + if (player != null) + player.Alpha() = alpha; } + + return ret; } } diff --git a/Glamourer/Gui/InterfaceRevertables.cs b/Glamourer/Gui/InterfaceRevertables.cs index d0b2686..bbdbe0b 100644 --- a/Glamourer/Gui/InterfaceRevertables.cs +++ b/Glamourer/Gui/InterfaceRevertables.cs @@ -2,87 +2,86 @@ using System.Linq; using System.Numerics; using ImGuiNET; +using OtterGui.Raii; -namespace Glamourer.Gui +namespace Glamourer.Gui; + +internal partial class Interface { - internal partial class Interface + private string? _currentRevertableName; + private CharacterSave? _currentRevertable; + + private void DrawRevertablesSelector() { - private string? _currentRevertableName; - private CharacterSave? _currentRevertable; - - private void DrawRevertablesSelector() - { - ImGui.BeginGroup(); - DrawPlayerFilter(); - if (!ImGui.BeginChild("##playerSelector", + ImGui.BeginGroup(); + DrawPlayerFilter(); + if (!ImGui.BeginChild("##playerSelector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true)) - { - ImGui.EndChild(); - ImGui.EndGroup(); - return; - } - - foreach (var (name, save) in Glamourer.RevertableDesigns.Saves) - { - if (name.ToLowerInvariant().Contains(_playerFilterLower) && ImGui.Selectable(name, name == _currentRevertableName)) - { - _currentRevertableName = name; - _currentRevertable = save; - } - } - - using (var _ = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) - { - ImGui.EndChild(); - } - - DrawSelectionButtons(); + { + ImGui.EndChild(); ImGui.EndGroup(); + return; } - private void DrawRevertablePanel() + foreach (var (name, save) in Glamourer.RevertableDesigns.Saves) { - using var group = ImGuiRaii.NewGroup(); + if (name.ToLowerInvariant().Contains(_playerFilterLower) && ImGui.Selectable(name, name == _currentRevertableName)) { - var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); - using var raii = new ImGuiRaii() - .PushColor(ImGuiCol.Text, GreenHeaderColor) - .PushColor(ImGuiCol.Button, buttonColor) - .PushColor(ImGuiCol.ButtonHovered, buttonColor) - .PushColor(ImGuiCol.ButtonActive, buttonColor) - .PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .PushStyle(ImGuiStyleVar.FrameRounding, 0); - ImGui.Button($"{_currentRevertableName}##playerHeader", -Vector2.UnitX * 0.0001f); + _currentRevertableName = name; + _currentRevertable = save; } + } - if (!ImGui.BeginChild("##revertableData", -Vector2.One, true)) - { - ImGui.EndChild(); - return; - } - - var save = _currentRevertable!.Copy(); - DrawCustomization(ref save.Customizations); - DrawEquip(save.Equipment); - DrawMiscellaneous(save, null); - + using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) + { ImGui.EndChild(); } - [Conditional("DEBUG")] - private void DrawRevertablesTab() + DrawSelectionButtons(); + ImGui.EndGroup(); + } + + private void DrawRevertablePanel() + { + using var group = ImRaii.Group(); { - using var raii = new ImGuiRaii(); - if (!raii.Begin(() => ImGui.BeginTabItem("Revertables"), ImGui.EndTabItem)) - return; - - DrawRevertablesSelector(); - - if (_currentRevertableName == null) - return; - - ImGui.SameLine(); - DrawRevertablePanel(); + var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); + using var color = ImRaii.PushColor(ImGuiCol.Text, GreenHeaderColor) + .Push(ImGuiCol.Button, buttonColor) + .Push(ImGuiCol.ButtonHovered, buttonColor) + .Push(ImGuiCol.ButtonActive, buttonColor); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + ImGui.Button($"{_currentRevertableName}##playerHeader", -Vector2.UnitX * 0.0001f); } + + if (!ImGui.BeginChild("##revertableData", -Vector2.One, true)) + { + ImGui.EndChild(); + return; + } + + var save = _currentRevertable!.Copy(); + DrawCustomization(ref save.Customizations); + DrawEquip(save.Equipment); + DrawMiscellaneous(save, null); + + ImGui.EndChild(); + } + + [Conditional("DEBUG")] + private void DrawRevertablesTab() + { + using var tabItem = ImRaii.TabItem("Revertables"); + if (!tabItem) + return; + + DrawRevertablesSelector(); + + if (_currentRevertableName == null) + return; + + ImGui.SameLine(); + DrawRevertablePanel(); } }