diff --git a/.gitignore b/.gitignore index 7fc0620..49ae61f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin/ obj/ .vs/ -Glamourer.json \ No newline at end of file +Glamourer.json +private/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 685aaa2..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "OtterGui"] - path = OtterGui - url = git@github.com:Ottermandias/OtterGui.git - branch = main diff --git a/Glamourer.GameData/CharacterCustomization.cs b/Glamourer.GameData/CharacterCustomization.cs new file mode 100644 index 0000000..c031a03 --- /dev/null +++ b/Glamourer.GameData/CharacterCustomization.cs @@ -0,0 +1,159 @@ +using System; +using Glamourer.Customization; +using Penumbra.GameData.Enums; + +namespace Glamourer; + +public readonly unsafe struct CharacterCustomization +{ + public static readonly CharacterCustomization Null = new(null); + + private readonly CustomizationData* _data; + + public IntPtr Address + => (IntPtr)_data; + + public CharacterCustomization(CustomizationData* data) + => _data = data; + + public ref Race Race + => ref _data->Race; + + public ref SubRace Clan + => ref _data->Clan; + + public Gender Gender + { + get => _data->Gender; + set => _data->Gender = value; + } + + public ref byte BodyType + => ref _data->BodyType; + + public ref byte Height + => ref _data->Height; + + public ref byte Face + => ref _data->Face; + + public ref byte Hairstyle + => ref _data->Hairstyle; + + public bool HighlightsOn + { + get => _data->HighlightsOn; + set => _data->HighlightsOn = value; + } + + public ref byte SkinColor + => ref _data->SkinColor; + + public ref byte EyeColorRight + => ref _data->EyeColorRight; + + public ref byte HairColor + => ref _data->HairColor; + + public ref byte HighlightsColor + => ref _data->HighlightsColor; + + public ref byte FacialFeatures + => ref _data->FacialFeatures; + + public ref byte TattooColor + => ref _data->TattooColor; + + public ref byte Eyebrow + => ref _data->Eyebrow; + + public ref byte EyeColorLeft + => ref _data->EyeColorLeft; + + public byte EyeShape + { + get => _data->EyeShape; + set => _data->EyeShape = value; + } + + public byte FacePaint + { + get => _data->FacePaint; + set => _data->FacePaint = value; + } + + public bool FacePaintReversed + { + get => _data->FacePaintReversed; + set => _data->FacePaintReversed = value; + } + + public byte Mouth + { + get => _data->Mouth; + set => _data->Mouth = value; + } + + public bool SmallIris + { + get => _data->SmallIris; + set => _data->SmallIris = value; + } + + public bool Lipstick + { + get => _data->Lipstick; + set => _data->Lipstick = value; + } + + public ref byte Nose + => ref _data->Nose; + + public ref byte Jaw + => ref _data->Jaw; + + public ref byte LipColor + => ref _data->LipColor; + + public ref byte MuscleMass + => ref _data->MuscleMass; + + public ref byte TailShape + => ref _data->TailShape; + + public ref byte BustSize + => ref _data->BustSize; + + public ref byte FacePaintColor + => ref _data->FacePaintColor; + + public bool FacialFeature(int idx) + => _data->FacialFeature(idx); + + public void FacialFeature(int idx, bool set) + => _data->FacialFeature(idx, set); + + public byte this[CustomizationId id] + { + get => _data->Get(id); + set => _data->Set(id, value); + } + + public static implicit operator CharacterCustomization(CustomizationData* val) + => new(val); + + public static implicit operator CharacterCustomization(IntPtr val) + => new((CustomizationData*)val); + + public static implicit operator bool(CharacterCustomization customize) + => customize._data != null; + + public static bool operator true(CharacterCustomization customize) + => customize._data != null; + + public static bool operator false(CharacterCustomization customize) + => customize._data == null; + + public static bool operator !(CharacterCustomization customize) + => customize._data == null; +} diff --git a/Glamourer.GameData/CharacterEquip.cs b/Glamourer.GameData/CharacterEquip.cs new file mode 100644 index 0000000..c16a69a --- /dev/null +++ b/Glamourer.GameData/CharacterEquip.cs @@ -0,0 +1,105 @@ +using System; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer; + +public readonly unsafe struct CharacterEquip +{ + public static readonly CharacterEquip Null = new(null); + + private readonly CharacterArmor* _armor; + + public IntPtr Address + => (IntPtr)_armor; + + public ref CharacterArmor this[int idx] + => ref _armor[idx]; + + public ref CharacterArmor this[uint idx] + => ref _armor[idx]; + + public ref CharacterArmor this[EquipSlot slot] + => ref _armor[IndexOf(slot)]; + + + public ref CharacterArmor Head + => ref _armor[0]; + + public ref CharacterArmor Body + => ref _armor[1]; + + public ref CharacterArmor Hands + => ref _armor[2]; + + public ref CharacterArmor Legs + => ref _armor[3]; + + public ref CharacterArmor Feet + => ref _armor[4]; + + public ref CharacterArmor Ears + => ref _armor[5]; + + public ref CharacterArmor Neck + => ref _armor[6]; + + public ref CharacterArmor Wrists + => ref _armor[7]; + + public ref CharacterArmor RFinger + => ref _armor[8]; + + public ref CharacterArmor LFinger + => ref _armor[9]; + + public CharacterEquip(CharacterArmor* val) + => _armor = val; + + public static implicit operator CharacterEquip(CharacterArmor* val) + => new(val); + + public static implicit operator CharacterEquip(IntPtr val) + => new((CharacterArmor*)val); + + public static implicit operator CharacterEquip(ReadOnlySpan val) + { + if (val.Length != 10) + throw new ArgumentException("Invalid number of equipment pieces in span."); + + fixed (CharacterArmor* ptr = val) + { + return new CharacterEquip(ptr); + } + } + + public static implicit operator bool(CharacterEquip equip) + => equip._armor != null; + + public static bool operator true(CharacterEquip equip) + => equip._armor != null; + + public static bool operator false(CharacterEquip equip) + => equip._armor == null; + + public static bool operator !(CharacterEquip equip) + => equip._armor == null; + + private static int IndexOf(EquipSlot slot) + { + return slot switch + { + EquipSlot.Head => 0, + EquipSlot.Body => 1, + EquipSlot.Hands => 2, + EquipSlot.Legs => 3, + EquipSlot.Feet => 4, + EquipSlot.Ears => 5, + EquipSlot.Neck => 6, + EquipSlot.Wrists => 7, + EquipSlot.RFinger => 8, + EquipSlot.LFinger => 9, + _ => throw new ArgumentOutOfRangeException(nameof(slot), slot, null), + }; + } +} diff --git a/Glamourer.GameData/CharacterEquipExtensions.cs b/Glamourer.GameData/CharacterEquipExtensions.cs index 24e5873..4d0f723 100644 --- a/Glamourer.GameData/CharacterEquipExtensions.cs +++ b/Glamourer.GameData/CharacterEquipExtensions.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer.GameData/Customization/CharaMakeParams.cs b/Glamourer.GameData/Customization/CharaMakeParams.cs index 4ac224f..69fe4ff 100644 --- a/Glamourer.GameData/Customization/CharaMakeParams.cs +++ b/Glamourer.GameData/Customization/CharaMakeParams.cs @@ -4,6 +4,7 @@ using Lumina.Excel.GeneratedSheets; namespace Glamourer.Customization; +// A custom version of CharaMakeParams that is easier to parse. [Sheet("CharaMakeParams")] public class CharaMakeParams : ExcelRow { @@ -43,10 +44,9 @@ public class CharaMakeParams : ExcelRow public uint[] Icons; } - public LazyRow Race { get; set; } = null!; - public LazyRow Tribe { get; set; } = null!; - - public sbyte Gender { get; set; } + public LazyRow Race { get; set; } = null!; + public LazyRow Tribe { get; set; } = null!; + public sbyte Gender { get; set; } public Menu[] Menus { get; set; } = new Menu[NumMenus]; public byte[] Voices { get; set; } = new byte[NumVoices]; @@ -60,15 +60,17 @@ public class CharaMakeParams : ExcelRow Race = new LazyRow(gameData, parser.ReadColumn(0), language); Tribe = new LazyRow(gameData, parser.ReadColumn(1), language); Gender = parser.ReadColumn(2); + var currentOffset = 0; for (var i = 0; i < NumMenus; ++i) { - Menus[i].Id = parser.ReadColumn(3 + 0 * NumMenus + i); - Menus[i].InitVal = parser.ReadColumn(3 + 1 * NumMenus + i); - Menus[i].Type = (MenuType)parser.ReadColumn(3 + 2 * NumMenus + i); - Menus[i].Size = parser.ReadColumn(3 + 3 * NumMenus + i); - Menus[i].LookAt = parser.ReadColumn(3 + 4 * NumMenus + i); - Menus[i].Mask = parser.ReadColumn(3 + 5 * NumMenus + i); - Menus[i].Customization = (CustomizationId)parser.ReadColumn(3 + 6 * NumMenus + i); + currentOffset = 3 + i; + Menus[i].Id = parser.ReadColumn(0 * NumMenus + currentOffset); + Menus[i].InitVal = parser.ReadColumn(1 * NumMenus + currentOffset); + Menus[i].Type = (MenuType)parser.ReadColumn(2 * NumMenus + currentOffset); + Menus[i].Size = parser.ReadColumn(3 * NumMenus + currentOffset); + Menus[i].LookAt = parser.ReadColumn(4 * NumMenus + currentOffset); + Menus[i].Mask = parser.ReadColumn(5 * NumMenus + currentOffset); + Menus[i].Customization = (CustomizationId)parser.ReadColumn(6 * NumMenus + currentOffset); Menus[i].Values = new uint[Menus[i].Size]; switch (Menus[i].Type) @@ -78,47 +80,42 @@ public class CharaMakeParams : ExcelRow case MenuType.Percentage: break; default: + currentOffset += 7 * NumMenus; for (var j = 0; j < Menus[i].Size; ++j) - Menus[i].Values[j] = parser.ReadColumn(3 + (7 + j) * NumMenus + i); + Menus[i].Values[j] = parser.ReadColumn(j * NumMenus + currentOffset); break; } Menus[i].Graphic = new byte[NumGraphics]; + currentOffset = 3 + (MaxNumValues + 7) * NumMenus + i; for (var j = 0; j < NumGraphics; ++j) - Menus[i].Graphic[j] = parser.ReadColumn(3 + (MaxNumValues + 7 + j) * NumMenus + i); + Menus[i].Graphic[j] = parser.ReadColumn(j * NumMenus + currentOffset); } + currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus; for (var i = 0; i < NumVoices; ++i) - Voices[i] = parser.ReadColumn(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + i); + Voices[i] = parser.ReadColumn(currentOffset++); for (var i = 0; i < NumFaces; ++i) { + currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + i; FacialFeatureByFace[i].Icons = new uint[NumFeatures]; for (var j = 0; j < NumFeatures; ++j) - { - FacialFeatureByFace[i].Icons[j] = - (uint)parser.ReadColumn(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + i); - } + FacialFeatureByFace[i].Icons[j] = (uint)parser.ReadColumn(j * NumFaces + currentOffset); } for (var i = 0; i < NumEquip; ++i) { + currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7; Equip[i] = new CharaMakeType.UnkData3347Obj() { - Helmet = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0), - Top = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1), - Gloves = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2), - Legs = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3), - Shoes = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4), - Weapon = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5), - SubWeapon = parser.ReadColumn( - 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6), + Helmet = parser.ReadColumn(currentOffset + 0), + Top = parser.ReadColumn(currentOffset + 1), + Gloves = parser.ReadColumn(currentOffset + 2), + Legs = parser.ReadColumn(currentOffset + 3), + Shoes = parser.ReadColumn(currentOffset + 4), + Weapon = parser.ReadColumn(currentOffset + 5), + SubWeapon = parser.ReadColumn(currentOffset + 6), }; } } diff --git a/Glamourer.GameData/Customization/CmpFile.cs b/Glamourer.GameData/Customization/CmpFile.cs index f5608f7..15d1570 100644 --- a/Glamourer.GameData/Customization/CmpFile.cs +++ b/Glamourer.GameData/Customization/CmpFile.cs @@ -1,23 +1,46 @@ -using Dalamud.Data; -using Dalamud.Plugin; +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Data; +using Dalamud.Logging; -namespace Glamourer; +namespace Glamourer.Customization; -public class CmpFile +// Convert the Human.Cmp file into color sets. +// If the file can not be read due to TexTools corruption, create a 0-array of size MinSize. +internal class CmpFile { - public readonly Lumina.Data.FileResource File; - public readonly uint[] RgbaColors; + private readonly Lumina.Data.FileResource? _file; + private readonly uint[] _rgbaColors; + + // No error checking since only called internally. + public IEnumerable GetSlice(int offset, int count) + => _rgbaColors.Length >= offset + count ? _rgbaColors.Skip(offset).Take(count) : Enumerable.Repeat(0u, count); + + public bool Valid + => _file != null; 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) + try { - RgbaColors[i >> 2] = File.Data[i] - | (uint)(File.Data[i + 1] << 8) - | (uint)(File.Data[i + 2] << 16) - | (uint)(File.Data[i + 3] << 24); + _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); + } + } + catch (Exception e) + { + PluginLog.Error("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); + _file = null; + _rgbaColors = Array.Empty(); } } } diff --git a/Glamourer.GameData/Customization/CustomName.cs b/Glamourer.GameData/Customization/CustomName.cs index 2d96f76..6bff835 100644 --- a/Glamourer.GameData/Customization/CustomName.cs +++ b/Glamourer.GameData/Customization/CustomName.cs @@ -1,47 +1,45 @@ -namespace Glamourer.Customization -{ - public enum CustomName - { - Clan = 0, - Gender, - Reverse, - OddEyes, - IrisSmall, - IrisLarge, - IrisSize, - MidlanderM, - HighlanderM, - WildwoodM, - DuskwightM, - PlainsfolkM, - DunesfolkM, - SeekerOfTheSunM, - KeeperOfTheMoonM, - SeawolfM, - HellsguardM, - RaenM, - XaelaM, - HelionM, - LostM, - RavaM, - VeenaM, - MidlanderF, - HighlanderF, - WildwoodF, - DuskwightF, - PlainsfolkF, - DunesfolkF, - SeekerOfTheSunF, - KeeperOfTheMoonF, - SeawolfF, - HellsguardF, - RaenF, - XaelaF, - HelionF, - LostF, - RavaF, - VeenaF, +namespace Glamourer.Customization; - Num, - } +// Localization from the game files directly. +public enum CustomName +{ + Clan = 0, + Gender, + Reverse, + OddEyes, + IrisSmall, + IrisLarge, + IrisSize, + MidlanderM, + HighlanderM, + WildwoodM, + DuskwightM, + PlainsfolkM, + DunesfolkM, + SeekerOfTheSunM, + KeeperOfTheMoonM, + SeawolfM, + HellsguardM, + RaenM, + XaelaM, + HelionM, + LostM, + RavaM, + VeenaM, + MidlanderF, + HighlanderF, + WildwoodF, + DuskwightF, + PlainsfolkF, + DunesfolkF, + SeekerOfTheSunF, + KeeperOfTheMoonF, + SeawolfF, + HellsguardF, + RaenF, + XaelaF, + HelionF, + LostF, + RavaF, + VeenaF, } diff --git a/Glamourer.GameData/Customization/Customization.cs b/Glamourer.GameData/Customization/Customization.cs index 6572d1a..8527f3a 100644 --- a/Glamourer.GameData/Customization/Customization.cs +++ b/Glamourer.GameData/Customization/Customization.cs @@ -1,32 +1,33 @@ using System.Runtime.InteropServices; -namespace Glamourer.Customization +namespace Glamourer.Customization; + +// Any customization value can be represented in 8 bytes by its ID, +// a byte value, an optional value-id and an optional icon or color. +[StructLayout(LayoutKind.Explicit)] +public readonly struct Customization { - [StructLayout(LayoutKind.Explicit)] - public readonly struct Customization + [FieldOffset(0)] + public readonly CustomizationId Id; + + [FieldOffset(1)] + public readonly byte Value; + + [FieldOffset(2)] + public readonly ushort CustomizeId; + + [FieldOffset(4)] + public readonly uint IconId; + + [FieldOffset(4)] + public readonly uint Color; + + public Customization(CustomizationId id, byte value, uint data = 0, ushort customizeId = 0) { - [FieldOffset(0)] - public readonly CustomizationId Id; - - [FieldOffset(1)] - public readonly byte Value; - - [FieldOffset(2)] - public readonly ushort CustomizeId; - - [FieldOffset(4)] - public readonly uint IconId; - - [FieldOffset(4)] - public readonly uint Color; - - public Customization(CustomizationId id, byte value, uint data = 0, ushort customizeId = 0) - { - Id = id; - Value = value; - IconId = data; - Color = data; - CustomizeId = customizeId; - } + Id = id; + Value = value; + IconId = data; + Color = data; + CustomizeId = customizeId; } } diff --git a/Glamourer.GameData/Customization/CharacterCustomization.cs b/Glamourer.GameData/Customization/CustomizationData.cs similarity index 58% rename from Glamourer.GameData/Customization/CharacterCustomization.cs rename to Glamourer.GameData/Customization/CustomizationData.cs index 513e849..05e8507 100644 --- a/Glamourer.GameData/Customization/CharacterCustomization.cs +++ b/Glamourer.GameData/Customization/CustomizationData.cs @@ -2,59 +2,17 @@ using System.Runtime.InteropServices; using System.Text; using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Penumbra.GameData.Enums; namespace Glamourer.Customization; -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 struct CustomizationData { public const int CustomizationOffset = 0x830; public const int CustomizationBytes = 26; - public static CharacterCustomization Default = new() - { - 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 Race Race; private byte _gender; public byte BodyType; @@ -82,21 +40,25 @@ public struct CharacterCustomization private byte _facePaint; public byte FacePaintColor; + // Skip Unknown Gender public Gender Gender { get => (Gender)(_gender + 1); set => _gender = (byte)(value - 1); } + // Single bit flag. public bool HighlightsOn { get => (_highlightsOn & 128) == 128; set => _highlightsOn = (byte)(value ? _highlightsOn | 128 : _highlightsOn & 127); } + // Get status of specific facial feature 0-7. public bool FacialFeature(int idx) => (FacialFeatures & (1 << idx)) != 0; + // Set value of specific facial feature 0-7. public void FacialFeature(int idx, bool set) { if (set) @@ -105,66 +67,108 @@ public struct CharacterCustomization FacialFeatures &= (byte)~(1 << idx); } + // Lower 7 bits public byte EyeShape { get => (byte)(_eyeShape & 127); set => _eyeShape = (byte)((value & 127) | (_eyeShape & 128)); } + // Uppermost bit flag. public bool SmallIris { get => (_eyeShape & 128) == 128; set => _eyeShape = (byte)(value ? _eyeShape | 128 : _eyeShape & 127); } - + // Lower 7 bits. public byte Mouth { get => (byte)(_mouth & 127); set => _mouth = (byte)((value & 127) | (_mouth & 128)); } + // Uppermost bit flag. public bool Lipstick { get => (_mouth & 128) == 128; set => _mouth = (byte)(value ? _mouth | 128 : _mouth & 127); } + // Lower 7 bits. public byte FacePaint { get => (byte)(_facePaint & 127); set => _facePaint = (byte)((value & 127) | (_facePaint & 128)); } + // Uppermost bit flag. public bool FacePaintReversed { get => (_facePaint & 128) == 128; set => _facePaint = (byte)(value ? _facePaint | 128 : _facePaint & 127); } - public unsafe void Read(IntPtr customizeAddress) + public static CustomizationData Default = new() { - 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, + }; + + public unsafe void Read(CustomizationData* customize) + { + fixed (CustomizationData* ptr = &this) { - Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes); + *ptr = *customize; } } - public unsafe void Read(Customization* customize) - => Read((IntPtr)customize); + public unsafe void Read(IntPtr customizeAddress) + => Read((CustomizationData*)customizeAddress); public void Read(Character character) => Read(character.Address + CustomizationOffset); - public CharacterCustomization(Character character) + public unsafe void Read(Human* human) + => Read((CustomizationData*)human->CustomizeData); + + public CustomizationData(Character character) : this() { Read(character.Address + CustomizationOffset); } - public byte this[CustomizationId id] + public unsafe CustomizationData(Human* human) + : this() { - get => id switch + Read(human); + } + + public byte Get(CustomizationId id) + => id switch { CustomizationId.Race => (byte)Race, CustomizationId.Gender => (byte)Gender, @@ -194,100 +198,59 @@ public struct CharacterCustomization CustomizationId.FacePaintColor => FacePaintColor, _ => throw new ArgumentOutOfRangeException(nameof(id), id, null), }; - set + + public void Set(CustomizationId id, byte value) + { + switch (id) { - 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); - } + // @formatter:off + 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); + // @formatter:on + } + } + + public byte this[CustomizationId id] + { + get => Get(id); + set => Set(id, value); + } + + public unsafe void Write(FFXIVClientStructs.FFXIV.Client.Game.Character.Character* character) + { + fixed (CustomizationData* ptr = &this) + { + Buffer.MemoryCopy(ptr, character->CustomizeData, CustomizationBytes, CustomizationBytes); } } public unsafe void Write(IntPtr characterAddress) - { - fixed (Race* ptr = &Race) - { - Buffer.MemoryCopy(ptr, (byte*)characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes); - } - } + => Write((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)characterAddress); public unsafe void WriteBytes(byte[] array, int offset = 0) { diff --git a/Glamourer.GameData/Customization/CustomizationId.cs b/Glamourer.GameData/Customization/CustomizationId.cs index cbc15f9..493fb2c 100644 --- a/Glamourer.GameData/Customization/CustomizationId.cs +++ b/Glamourer.GameData/Customization/CustomizationId.cs @@ -1,108 +1,107 @@ using System; using Penumbra.GameData.Enums; -namespace Glamourer.Customization +namespace Glamourer.Customization; + +public enum CustomizationId : byte { - public enum CustomizationId : byte - { - Race = 0, - Gender = 1, - BodyType = 2, - Height = 3, - Clan = 4, - Face = 5, - Hairstyle = 6, - HighlightsOnFlag = 7, - SkinColor = 8, - EyeColorR = 9, - HairColor = 10, - HighlightColor = 11, - FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo - TattooColor = 13, - Eyebrows = 14, - EyeColorL = 15, - EyeShape = 16, // Flag 128 for Small - Nose = 17, - Jaw = 18, - Mouth = 19, // Flag 128 for Lip Color set - LipColor = 20, // Flag 128 for Light instead of Dark - MuscleToneOrTailEarLength = 21, - TailEarShape = 22, - BustSize = 23, - FacePaint = 24, - FacePaintColor = 25, // Flag 128 for Light instead of Dark. - } - - public static class CustomizationExtensions - { - public static string ToDefaultName(this CustomizationId customizationId) - => customizationId switch - { - CustomizationId.Race => "Race", - CustomizationId.Gender => "Gender", - CustomizationId.BodyType => "Body Type", - CustomizationId.Height => "Height", - CustomizationId.Clan => "Clan", - CustomizationId.Face => "Head Style", - CustomizationId.Hairstyle => "Hair Style", - CustomizationId.HighlightsOnFlag => "Highlights", - CustomizationId.SkinColor => "Skin Color", - CustomizationId.EyeColorR => "Right Eye Color", - CustomizationId.HairColor => "Hair Color", - CustomizationId.HighlightColor => "Highlights Color", - CustomizationId.FacialFeaturesTattoos => "Facial Features", - CustomizationId.TattooColor => "Tattoo Color", - CustomizationId.Eyebrows => "Eyebrow Style", - CustomizationId.EyeColorL => "Left Eye Color", - CustomizationId.EyeShape => "Eye Shape", - CustomizationId.Nose => "Nose Style", - CustomizationId.Jaw => "Jaw Style", - CustomizationId.Mouth => "Mouth Style", - CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone", - CustomizationId.TailEarShape => "Tail Shape", - CustomizationId.BustSize => "Bust Size", - CustomizationId.FacePaint => "Face Paint", - CustomizationId.FacePaintColor => "Face Paint Color", - CustomizationId.LipColor => "Lip Color", - - _ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null), - }; - - public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur) - => customizationId switch - { - CustomizationId.Race => CharaMakeParams.MenuType.IconSelector, - CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector, - CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector, - CustomizationId.Height => CharaMakeParams.MenuType.Percentage, - CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector, - CustomizationId.Face => CharaMakeParams.MenuType.IconSelector, - CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector, - CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector, - CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker, - CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker, - CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker, - CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker, - CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector, - CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker, - CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector, - CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker, - CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector, - CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector, - CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector, - CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector, - CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage, - CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage, - CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector, - CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker, - - CustomizationId.TailEarShape => race == Race.Elezen || race == Race.Lalafell - ? CharaMakeParams.MenuType.ListSelector - : CharaMakeParams.MenuType.IconSelector, - CustomizationId.LipColor => race == Race.Hrothgar - ? CharaMakeParams.MenuType.IconSelector - : CharaMakeParams.MenuType.ColorPicker, - _ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null), - }; - } + Race = 0, + Gender = 1, + BodyType = 2, + Height = 3, + Clan = 4, + Face = 5, + Hairstyle = 6, + HighlightsOnFlag = 7, + SkinColor = 8, + EyeColorR = 9, + HairColor = 10, + HighlightColor = 11, + FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo + TattooColor = 13, + Eyebrows = 14, + EyeColorL = 15, + EyeShape = 16, // Flag 128 for Small + Nose = 17, + Jaw = 18, + Mouth = 19, // Flag 128 for Lip Color set + LipColor = 20, // Flag 128 for Light instead of Dark + MuscleToneOrTailEarLength = 21, + TailEarShape = 22, + BustSize = 23, + FacePaint = 24, + FacePaintColor = 25, // Flag 128 for Light instead of Dark. +} + +public static class CustomizationExtensions +{ + public static string ToDefaultName(this CustomizationId customizationId) + => customizationId switch + { + CustomizationId.Race => "Race", + CustomizationId.Gender => "Gender", + CustomizationId.BodyType => "Body Type", + CustomizationId.Height => "Height", + CustomizationId.Clan => "Clan", + CustomizationId.Face => "Head Style", + CustomizationId.Hairstyle => "Hair Style", + CustomizationId.HighlightsOnFlag => "Highlights", + CustomizationId.SkinColor => "Skin Color", + CustomizationId.EyeColorR => "Right Eye Color", + CustomizationId.HairColor => "Hair Color", + CustomizationId.HighlightColor => "Highlights Color", + CustomizationId.FacialFeaturesTattoos => "Facial Features", + CustomizationId.TattooColor => "Tattoo Color", + CustomizationId.Eyebrows => "Eyebrow Style", + CustomizationId.EyeColorL => "Left Eye Color", + CustomizationId.EyeShape => "Eye Shape", + CustomizationId.Nose => "Nose Style", + CustomizationId.Jaw => "Jaw Style", + CustomizationId.Mouth => "Mouth Style", + CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone", + CustomizationId.TailEarShape => "Tail Shape", + CustomizationId.BustSize => "Bust Size", + CustomizationId.FacePaint => "Face Paint", + CustomizationId.FacePaintColor => "Face Paint Color", + CustomizationId.LipColor => "Lip Color", + + _ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null), + }; + + public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur) + => customizationId switch + { + CustomizationId.Race => CharaMakeParams.MenuType.IconSelector, + CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector, + CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector, + CustomizationId.Height => CharaMakeParams.MenuType.Percentage, + CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector, + CustomizationId.Face => CharaMakeParams.MenuType.IconSelector, + CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector, + CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector, + CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker, + CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker, + CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker, + CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker, + CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector, + CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker, + CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector, + CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker, + CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector, + CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector, + CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector, + CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector, + CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage, + CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage, + CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector, + CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker, + + CustomizationId.TailEarShape => race is Race.Elezen or Race.Lalafell + ? CharaMakeParams.MenuType.ListSelector + : CharaMakeParams.MenuType.IconSelector, + CustomizationId.LipColor => race == Race.Hrothgar + ? CharaMakeParams.MenuType.IconSelector + : CharaMakeParams.MenuType.ColorPicker, + _ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null), + }; } diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index 858214f..053cf73 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -5,7 +5,7 @@ using System.Reflection; using Dalamud; using Dalamud.Data; using Dalamud.Plugin; -using Lumina.Data; +using Dalamud.Utility; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; using OtterGui.Classes; @@ -14,360 +14,407 @@ using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Customization; +// Generate everything about customization per tribe and gender. public partial class CustomizationOptions { - internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); + // All races except for Unknown + internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); + + // All tribes except for Unknown internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray(); + // Two genders. internal static readonly Gender[] Genders = { Gender.Male, Gender.Female, }; + // Every tribe and gender has a separate set of available customizations. internal CustomizationSet GetList(SubRace race, Gender gender) - => _list[ToIndex(race, gender)]; + => _customizationSets[ToIndex(race, gender)]; + // Get specific icons. internal ImGuiScene.TextureWrap GetIcon(uint id) => _icons.LoadIcon(id); - private static readonly int ListSize = Clans.Length * Genders.Length; + private readonly IconStorage _icons; - private readonly CustomizationSet[] _list = new CustomizationSet[ListSize]; - private readonly IconStorage _icons; + private static readonly int ListSize = Clans.Length * Genders.Length; + private readonly CustomizationSet[] _customizationSets = new CustomizationSet[ListSize]; + + + // Get the index for the given pair of tribe and gender. + private static int ToIndex(SubRace race, Gender gender) + { + var idx = ((int)race - 1) * Genders.Length + (gender == Gender.Female ? 1 : 0); + if (idx < 0 || idx >= ListSize) + ThrowException(race, gender); + return idx; + } 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) - { - 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), - }; - - 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 partial class CustomizationOptions +{ + internal readonly bool Valid; 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?[] - { - "charamaketype", - FromClientLanguage(language), - null, - }) as ExcelSheet; - _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); - - _icons = new IconStorage(pi, gameData, _list.Length * 50); + var tmp = new TemporaryData(gameData, this, language); + _icons = new IconStorage(pi, gameData, _customizationSets.Length * 50); + Valid = tmp.Valid; + SetNames(gameData, tmp); foreach (var race in Clans) { foreach (var gender in Genders) - _list[ToIndex(race, gender)] = GetSet(race, gender); + _customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender); } } - private void SetNames(DataManager gameData) + + // Obtain localized names of customization options and race names from the game data. + private readonly string[] _names = new string[Enum.GetValues().Length]; + + private void SetNames(DataManager gameData, TemporaryData tmp) { 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(); + + void Set(CustomName id, Lumina.Text.SeString? s, string def) + => _names[(int)id] = s?.ToDalamudString().TextValue ?? def; + + Set(CustomName.Clan, tmp.Lobby.GetRow(102)?.Text, "Clan"); + Set(CustomName.Gender, tmp.Lobby.GetRow(103)?.Text, "Gender"); + Set(CustomName.Reverse, tmp.Lobby.GetRow(2135)?.Text, "Reverse"); + Set(CustomName.OddEyes, tmp.Lobby.GetRow(2125)?.Text, "Odd Eyes"); + Set(CustomName.IrisSmall, tmp.Lobby.GetRow(1076)?.Text, "Small"); + Set(CustomName.IrisLarge, tmp.Lobby.GetRow(1075)?.Text, "Large"); + Set(CustomName.IrisSize, tmp.Lobby.GetRow(244)?.Text, "Iris Size"); + Set(CustomName.MidlanderM, subRace.GetRow((int)SubRace.Midlander)?.Masculine, SubRace.Midlander.ToName()); + Set(CustomName.MidlanderF, subRace.GetRow((int)SubRace.Midlander)?.Feminine, SubRace.Midlander.ToName()); + Set(CustomName.HighlanderM, subRace.GetRow((int)SubRace.Highlander)?.Masculine, SubRace.Highlander.ToName()); + Set(CustomName.HighlanderF, subRace.GetRow((int)SubRace.Highlander)?.Feminine, SubRace.Highlander.ToName()); + Set(CustomName.WildwoodM, subRace.GetRow((int)SubRace.Wildwood)?.Masculine, SubRace.Wildwood.ToName()); + Set(CustomName.WildwoodF, subRace.GetRow((int)SubRace.Wildwood)?.Feminine, SubRace.Wildwood.ToName()); + Set(CustomName.DuskwightM, subRace.GetRow((int)SubRace.Duskwight)?.Masculine, SubRace.Duskwight.ToName()); + Set(CustomName.DuskwightF, subRace.GetRow((int)SubRace.Duskwight)?.Feminine, SubRace.Duskwight.ToName()); + Set(CustomName.PlainsfolkM, subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine, SubRace.Plainsfolk.ToName()); + Set(CustomName.PlainsfolkF, subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine, SubRace.Plainsfolk.ToName()); + Set(CustomName.DunesfolkM, subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine, SubRace.Dunesfolk.ToName()); + Set(CustomName.DunesfolkF, subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine, SubRace.Dunesfolk.ToName()); + Set(CustomName.SeekerOfTheSunM, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine, SubRace.SeekerOfTheSun.ToName()); + Set(CustomName.SeekerOfTheSunF, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine, SubRace.SeekerOfTheSun.ToName()); + Set(CustomName.KeeperOfTheMoonM, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine, SubRace.KeeperOfTheMoon.ToName()); + Set(CustomName.KeeperOfTheMoonF, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine, SubRace.KeeperOfTheMoon.ToName()); + Set(CustomName.SeawolfM, subRace.GetRow((int)SubRace.Seawolf)?.Masculine, SubRace.Seawolf.ToName()); + Set(CustomName.SeawolfF, subRace.GetRow((int)SubRace.Seawolf)?.Feminine, SubRace.Seawolf.ToName()); + Set(CustomName.HellsguardM, subRace.GetRow((int)SubRace.Hellsguard)?.Masculine, SubRace.Hellsguard.ToName()); + Set(CustomName.HellsguardF, subRace.GetRow((int)SubRace.Hellsguard)?.Feminine, SubRace.Hellsguard.ToName()); + Set(CustomName.RaenM, subRace.GetRow((int)SubRace.Raen)?.Masculine, SubRace.Raen.ToName()); + Set(CustomName.RaenF, subRace.GetRow((int)SubRace.Raen)?.Feminine, SubRace.Raen.ToName()); + Set(CustomName.XaelaM, subRace.GetRow((int)SubRace.Xaela)?.Masculine, SubRace.Xaela.ToName()); + Set(CustomName.XaelaF, subRace.GetRow((int)SubRace.Xaela)?.Feminine, SubRace.Xaela.ToName()); + Set(CustomName.HelionM, subRace.GetRow((int)SubRace.Helion)?.Masculine, SubRace.Helion.ToName()); + Set(CustomName.HelionF, subRace.GetRow((int)SubRace.Helion)?.Feminine, SubRace.Helion.ToName()); + Set(CustomName.LostM, subRace.GetRow((int)SubRace.Lost)?.Masculine, SubRace.Lost.ToName()); + Set(CustomName.LostF, subRace.GetRow((int)SubRace.Lost)?.Feminine, SubRace.Lost.ToName()); + Set(CustomName.RavaM, subRace.GetRow((int)SubRace.Rava)?.Masculine, SubRace.Rava.ToName()); + Set(CustomName.RavaF, subRace.GetRow((int)SubRace.Rava)?.Feminine, SubRace.Rava.ToName()); + Set(CustomName.VeenaM, subRace.GetRow((int)SubRace.Veena)?.Masculine, SubRace.Veena.ToName()); + Set(CustomName.VeenaF, subRace.GetRow((int)SubRace.Veena)?.Feminine, SubRace.Veena.ToName()); + } + + private class TemporaryData + { + public bool Valid + => _cmpFile.Valid; + + public CustomizationSet GetSet(SubRace race, Gender gender) + { + var (skin, hair) = GetColors(race, gender); + var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + + // Create the initial set with all the easily accessible parameters available for anyone. + 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), + }; + + SetAvailability(set, row); + SetFacialFeatures(set, row); + SetMenuTypes(set, row); + SetNames(set, row); + + + return set; + } + + public TemporaryData(DataManager gameData, CustomizationOptions options, ClientLanguage language) + { + _options = options; + _cmpFile = new CmpFile(gameData); + _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", + language.ToLumina(), + null, + }) as ExcelSheet; + _listSheet = tmp!; + _hairSheet = gameData.GetExcelSheet()!; + _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); + } + + // Required sheets. + private readonly ExcelSheet _customizeSheet; + private readonly ExcelSheet _listSheet; + private readonly ExcelSheet _hairSheet; + public readonly ExcelSheet Lobby; + private readonly CmpFile _cmpFile; + + // Those values are shared between all races. + 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 CustomizationOptions _options; + + private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false) + => _cmpFile.GetSlice(offset, num) + .Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i))) + .ToArray(); + + private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row) + { + // Set up the menu types for all customizations. + set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c => + { + // Those types are not correctly given in the menu, so special case them to color pickers. + switch (c) + { + case CustomizationId.HighlightColor: + case CustomizationId.EyeColorL: + case CustomizationId.EyeColorR: + return CharaMakeParams.MenuType.ColorPicker; + } + + // Otherwise find the first menu corresponding to the id. + // If there is none, assume a list. + var menu = row.Menus + .Cast() + .FirstOrDefault(m => m!.Value.Customization == c); + return menu?.Type ?? CharaMakeParams.MenuType.ListSelector; + }).ToArray(); + set.Order = CustomizationSet.ComputeOrder(set); + } + + // Set customizations available if they have any options. + private static void SetAvailability(CustomizationSet set, CharaMakeParams row) + { + void Set(bool available, CustomizationId flag) + { + if (available) + set.SetAvailable(flag); + } + + // Both are percentages that are either unavailable or 0-100. + Set(GetListSize(row, CustomizationId.BustSize) > 0, CustomizationId.BustSize); + Set(GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0, CustomizationId.MuscleToneOrTailEarLength); + Set(set.NumEyebrows > 0, CustomizationId.Eyebrows); + Set(set.NumEyeShapes > 0, CustomizationId.EyeShape); + Set(set.NumNoseShapes > 0, CustomizationId.Nose); + Set(set.NumJawShapes > 0, CustomizationId.Jaw); + Set(set.NumMouthShapes > 0, CustomizationId.Mouth); + Set(set.TailEarShapes.Count > 0, CustomizationId.TailEarShape); + Set(set.Faces.Count > 0, CustomizationId.Face); + Set(set.FacePaints.Count > 0, CustomizationId.FacePaint); + Set(set.FacePaints.Count > 0, CustomizationId.FacePaintColor); + } + + // Create a list of lists of facial features and the legacy tattoo. + private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row) + { + var count = set.Faces.Count; + var featureDict = new List>(count); + for (var i = 0; i < count; ++i) + { + var legacyTattoo = new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8)); + featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx) + => new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx))) + .Append(legacyTattoo) + .ToArray()); + } + + set.FeaturesTattoos = featureDict.ToArray(); + } + + // Set the names for the given set of parameters. + private void SetNames(CustomizationSet set, CharaMakeParams row) + { + var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c => + { + // Find the first menu that corresponds to the Id. + var menu = row.Menus + .Cast() + .FirstOrDefault(m => m!.Value.Customization == c); + if (menu == null) + { + // If none exists and the id corresponds to highlights, set the Highlights name. + if (c == CustomizationId.HighlightsOnFlag) + return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; + + // Otherwise there is an error and we use the default name. + return c.ToDefaultName(); + } + + // Facial Features and Tattoos is created by combining two strings. + if (c == CustomizationId.FacialFeaturesTattoos) + return + $"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}"; + + // Otherwise all is normal, get the menu name or if it does not work the default name. + var textRow = Lobby.GetRow(menu.Value.Id); + return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); + }).ToArray(); + + // Add names for both eye colors. + nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR]; + nameArray[(int)CustomizationId.EyeColorR] = _options.GetName(CustomName.OddEyes); + + set.OptionName = nameArray; + } + + // Obtain available skin and hair colors for the given subrace and gender. + private (Customization[], Customization[]) GetColors(SubRace race, Gender gender) + { + if (race is > SubRace.Veena or 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)); + } + + // Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. + private Customization[] GetHairStyles(SubRace race, Gender gender) + { + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + // Unknown30 is the number of available hairstyles. + var hairList = new List(row.Unknown30); + // Hairstyles can be found starting at Unknown66. + 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; + + // Hair Row from CustomizeSheet might not be set in case of unlockable hair. + 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)); + } + + return hairList.ToArray(); + } + + // Get Features. + private Customization FromValueAndIndex(CustomizationId id, uint value, int index) + { + var row = _customizeSheet.GetRow(value); + return row == null + ? new Customization(id, (byte)(index + 1), value) + : new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId); + } + + // Get List sizes. + private static int GetListSize(CharaMakeParams row, CustomizationId id) + { + var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == id); + return menu?.Size ?? 0; + } + + // Get face paints from the hair sheet via reflection. + private Customization[] GetFacePaints(SubRace race, Gender gender) + { + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var paintList = new List(row.Unknown37); + + // Number of available face paints is at Unknown37. + for (var i = 0; i < row.Unknown37; ++i) + { + // Face paints start at Unknown73. + 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); + // Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints. + paintList.Add(paintRow != null + ? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId) + : new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx)); + } + + return paintList.ToArray(); + } + + // Specific icons for tails or ears. + 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(); + + // Specific icons for faces. + 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(); + + // Specific icons for Hrothgar patterns. + 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(); } } diff --git a/Glamourer.GameData/Customization/CustomizationSet.cs b/Glamourer.GameData/Customization/CustomizationSet.cs index e778aad..c3154fc 100644 --- a/Glamourer.GameData/Customization/CustomizationSet.cs +++ b/Glamourer.GameData/Customization/CustomizationSet.cs @@ -1,31 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Penumbra.GameData.Enums; namespace Glamourer.Customization; +// Each Subrace and Gender combo has a customization set. +// This describes the available customizations, their types and their names. public class CustomizationSet { - public const int DefaultAvailable = - (1 << (int)CustomizationId.Height) - | (1 << (int)CustomizationId.Hairstyle) - | (1 << (int)CustomizationId.SkinColor) - | (1 << (int)CustomizationId.EyeColorR) - | (1 << (int)CustomizationId.EyeColorL) - | (1 << (int)CustomizationId.HairColor) - | (1 << (int)CustomizationId.HighlightColor) - | (1 << (int)CustomizationId.FacialFeaturesTattoos) - | (1 << (int)CustomizationId.TattooColor) - | (1 << (int)CustomizationId.LipColor) - | (1 << (int)CustomizationId.Height); - internal CustomizationSet(SubRace clan, Gender gender) { Gender = gender; Clan = clan; _settingAvailable = clan.ToRace() == Race.Hrothgar && gender == Gender.Female - ? 0 + ? 0u : DefaultAvailable; } @@ -35,39 +25,50 @@ public class CustomizationSet public Race Race => Clan.ToRace(); - private int _settingAvailable; + private uint _settingAvailable; internal void SetAvailable(CustomizationId id) - => _settingAvailable |= 1 << (int)id; + => _settingAvailable |= 1u << (int)id; public bool IsAvailable(CustomizationId id) - => (_settingAvailable & (1 << (int)id)) != 0; + => (_settingAvailable & (1u << (int)id)) != 0; - public int NumEyebrows { get; internal set; } - public int NumEyeShapes { get; internal set; } - public int NumNoseShapes { get; internal set; } - public int NumJawShapes { get; internal set; } - public int NumMouthShapes { get; internal set; } + public int NumEyebrows { get; internal init; } + public int NumEyeShapes { get; internal init; } + public int NumNoseShapes { get; internal init; } + public int NumJawShapes { get; internal init; } + public int NumMouthShapes { get; internal init; } + + public string ToHumanReadable(CustomizationData customizationData) + { + var sb = new StringBuilder(); + foreach (var id in Enum.GetValues().Where(IsAvailable)) + sb.AppendFormat("{0,-20}", Option(id)).Append(customizationData[id]); + + return sb.ToString(); + } - public IReadOnlyList OptionName { get; internal set; } = null!; - public IReadOnlyList Faces { get; internal set; } = null!; - public IReadOnlyList HairStyles { get; internal set; } = null!; - public IReadOnlyList TailEarShapes { get; internal set; } = null!; - public IReadOnlyList> FeaturesTattoos { get; internal set; } = null!; - public IReadOnlyList FacePaints { get; internal set; } = null!; + public IReadOnlyList OptionName { get; internal set; } = null!; + public IReadOnlyList Faces { get; internal init; } = null!; + public IReadOnlyList HairStyles { get; internal init; } = null!; + public IReadOnlyList TailEarShapes { get; internal init; } = null!; + public IReadOnlyList> FeaturesTattoos { get; internal set; } = null!; + public IReadOnlyList FacePaints { get; internal init; } = null!; - public IReadOnlyList SkinColors { get; internal set; } = null!; - public IReadOnlyList HairColors { get; internal set; } = null!; - public IReadOnlyList HighlightColors { get; internal set; } = null!; - public IReadOnlyList EyeColors { get; internal set; } = null!; - public IReadOnlyList TattooColors { get; internal set; } = null!; - public IReadOnlyList FacePaintColorsLight { get; internal set; } = null!; - public IReadOnlyList FacePaintColorsDark { get; internal set; } = null!; - public IReadOnlyList LipColorsLight { get; internal set; } = null!; - public IReadOnlyList LipColorsDark { get; internal set; } = null!; + public IReadOnlyList SkinColors { get; internal init; } = null!; + public IReadOnlyList HairColors { get; internal init; } = null!; + public IReadOnlyList HighlightColors { get; internal init; } = null!; + public IReadOnlyList EyeColors { get; internal init; } = null!; + public IReadOnlyList TattooColors { get; internal init; } = null!; + public IReadOnlyList FacePaintColorsLight { get; internal init; } = null!; + public IReadOnlyList FacePaintColorsDark { get; internal init; } = null!; + public IReadOnlyList LipColorsLight { get; internal init; } = null!; + public IReadOnlyList LipColorsDark { get; internal init; } = null!; + + public IReadOnlyList Types { get; internal set; } = null!; + public IReadOnlyDictionary Order { get; internal set; } = null!; - public IReadOnlyList Types { get; internal set; } = null!; public string Option(CustomizationId id) => OptionName[(int)id]; @@ -154,6 +155,15 @@ public class CustomizationSet public CharaMakeParams.MenuType Type(CustomizationId id) => Types[(int)id]; + internal static IReadOnlyDictionary ComputeOrder(CustomizationSet set) + { + var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId)); + ret[(int)CustomizationId.TattooColor] = CustomizationId.EyeColorL; + ret[(int)CustomizationId.EyeColorL] = CustomizationId.EyeColorR; + ret[(int)CustomizationId.EyeColorR] = CustomizationId.TattooColor; + + return ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray()); + } public int Count(CustomizationId id) { @@ -187,4 +197,17 @@ public class CustomizationSet _ => throw new ArgumentOutOfRangeException(nameof(id), id, null), }; } + + private const uint DefaultAvailable = + (1u << (int)CustomizationId.Height) + | (1u << (int)CustomizationId.Hairstyle) + | (1u << (int)CustomizationId.SkinColor) + | (1u << (int)CustomizationId.EyeColorR) + | (1u << (int)CustomizationId.EyeColorL) + | (1u << (int)CustomizationId.HairColor) + | (1u << (int)CustomizationId.HighlightColor) + | (1u << (int)CustomizationId.FacialFeaturesTattoos) + | (1u << (int)CustomizationId.TattooColor) + | (1u << (int)CustomizationId.LipColor) + | (1u << (int)CustomizationId.Height); } diff --git a/Glamourer.GameData/Customization/ICustomizationManager.cs b/Glamourer.GameData/Customization/ICustomizationManager.cs index f0486ed..6e1cfe3 100644 --- a/Glamourer.GameData/Customization/ICustomizationManager.cs +++ b/Glamourer.GameData/Customization/ICustomizationManager.cs @@ -1,17 +1,16 @@ using System.Collections.Generic; using Penumbra.GameData.Enums; -namespace Glamourer.Customization +namespace Glamourer.Customization; + +public interface ICustomizationManager { - public interface ICustomizationManager - { - public IReadOnlyList Races { get; } - public IReadOnlyList Clans { get; } - public IReadOnlyList Genders { get; } + public IReadOnlyList Races { get; } + public IReadOnlyList Clans { get; } + public IReadOnlyList Genders { get; } - public CustomizationSet GetList(SubRace race, Gender gender); + public CustomizationSet GetList(SubRace race, Gender gender); - public ImGuiScene.TextureWrap GetIcon(uint iconId); - public string GetName(CustomName name); - } + public ImGuiScene.TextureWrap GetIcon(uint iconId); + public string GetName(CustomName name); } diff --git a/Glamourer.GameData/GameData.cs b/Glamourer.GameData/GameData.cs index bae448a..dfa5bea 100644 --- a/Glamourer.GameData/GameData.cs +++ b/Glamourer.GameData/GameData.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud; using Dalamud.Data; +using Dalamud.Game.ClientState.Objects.SubKinds; using Glamourer.Structs; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData.Enums; @@ -17,20 +20,14 @@ public static class GameData private static Dictionary>? _itemsBySlot; private static Dictionary? _jobs; private static Dictionary? _jobGroups; - private static SortedList? _models; + private static ModelData? _models; + private static RestrictedGear? _restrictedGear; - public static IReadOnlyDictionary Models(DataManager dataManager) - { - if (_models != null) - return _models; + public static RestrictedGear RestrictedGear(DataManager dataManager) + => _restrictedGear ??= new RestrictedGear(dataManager); - 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); - return _models; - } + public static ModelData Models(DataManager dataManager) + => _models ??= new ModelData(dataManager); public static IReadOnlyDictionary Stains(DataManager dataManager) { diff --git a/Glamourer.GameData/Glamourer.GameData.csproj b/Glamourer.GameData/Glamourer.GameData.csproj index ee86319..be4a781 100644 --- a/Glamourer.GameData/Glamourer.GameData.csproj +++ b/Glamourer.GameData/Glamourer.GameData.csproj @@ -39,6 +39,10 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll False + + $(appdata)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll + False + $(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll False diff --git a/Glamourer.GameData/ModelData.cs b/Glamourer.GameData/ModelData.cs new file mode 100644 index 0000000..16420e5 --- /dev/null +++ b/Glamourer.GameData/ModelData.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Dalamud.Data; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Lumina.Excel.GeneratedSheets; +using Companion = Lumina.Excel.GeneratedSheets.Companion; + +namespace Glamourer; + +public class ModelData +{ + public struct Data + { + public readonly ModelChara Model; + + public string FirstName { get; } + public string AllNames { get; internal set; } + + public Data(ModelChara model, string name) + { + Model = model; + FirstName = $"{name} #{model.RowId:D4}"; + AllNames = $"#{model.RowId:D4}\n{name}"; + } + } + + private readonly SortedList _models; + + public IReadOnlyDictionary Models + => _models; + + public ModelData(DataManager dataManager) + { + var modelSheet = dataManager.GetExcelSheet(); + + _models = new SortedList(NpcNames.ModelCharas.Count); + + void UpdateData(uint model, string name) + { + name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name); + if (_models.TryGetValue(model, out var data)) + data.AllNames = $"{data.AllNames}\n{name}"; + else + data = new Data(modelSheet!.GetRow(model)!, name); + _models[model] = data; + } + + var companionSheet = dataManager.GetExcelSheet()!; + foreach (var companion in companionSheet.Where(c => c.Model.Row != 0 && c.Singular.RawData.Length > 0)) + UpdateData(companion.Model.Row, companion.Singular.ToDalamudString().TextValue); + + var mountSheet = dataManager.GetExcelSheet()!; + foreach (var mount in mountSheet.Where(c => c.ModelChara.Row != 0 && c.Singular.RawData.Length > 0)) + UpdateData(mount.ModelChara.Row, mount.Singular.ToDalamudString().TextValue); + + var bNpcNames = dataManager.GetExcelSheet()!; + foreach (var (model, list) in NpcNames.ModelCharas) + { + foreach (var nameId in list) + { + var name = nameId >= 0 + ? bNpcNames.GetRow((uint)nameId)?.Singular.ToDalamudString().TextValue ?? string.Empty + : NpcNames.Names[~nameId]; + if (name.Length == 0) + continue; + + UpdateData(model, name); + } + } + } +} diff --git a/Glamourer.GameData/ModelNames.cs b/Glamourer.GameData/ModelNames.cs new file mode 100644 index 0000000..382a33f --- /dev/null +++ b/Glamourer.GameData/ModelNames.cs @@ -0,0 +1,3254 @@ +using System.Collections.Generic; + +namespace Glamourer; + +// @formatter:off +public static class NpcNames +{ + public static readonly IReadOnlyList Names = new string[] + { + "Raptor", + "Buzzard", + "Hog (lighter)", + "Hog", + "Boar", + "Imp", + "Flytrap", + "Mudestone Golem", + "Tortoise", + "Weevil", + "Squirrel", + "Bat", + "Slug", + "Sea Hare", + "Sand Yarzon", + "Djigga", + "Wisp", + "Myconid", + "Marshmallow (White-eyed)", + "Spriggan (Yellow-eyed/Lightning Rock)", + "Spriggan (Yellow-eyed/Earth Rock)", + "Spriggan (Yellow-eyed/Copper Ore)", + "Spriggan (Yellow-eyed/Mythril Ore)", + "Custard (White-eyed)", + "Kalong", + "Dormouse", + "Peiste", + "Yellowback", + "Ahriman (red)", + "Coblyn", + "Doblyn", + "Crystal Coblyn", + "Drake", + "Aldgoat Billy", + "Aldgoat Nanny", + "Mosshorn", + "Antling Princess", + "Antling Marshal", + "Magitek Vanguard H", + "Magitek Vanguard F", + "Snurble", + "Mole", + "Garuda's Plume", + "Wolf", + "Three-Headed (yellow)", + "Red Cobra", + "Devilet", + "Buffalo", + "Ewe", + "Dodo", + "Puk", + "Rat", + "Marmot", + "Bulb", + "Hornet", + "Qarn Face", + "Aevis", + "Slime", + "Gelato (White-eyed)", + "Flan (White-eyed)", + "Pudding (White-eyed)", + "Bavarois (White-eyed)", + "Wolf Pup", + "Wind Elemental", + "Treant (Greenwrath)", + "Mudestone Golem (spike)", + "Fleshy Pod (Upright)", + "Seed Pod", + "Brown Crate", + "Sandbag", + "Ogre", + "Antling Queen", + "Greater Gargoyle", + "Cyclops", + "Elder Cyclops", + "Mammet (Red Helmet)", + "Razor Plume (Garuda)", + "Satin Plume (Garuda)", + "Karakul Ewe", + "Ewe (bell)", + "Barrel with 3 wine bottles", + "Aenc Thon: Lord of the Lingering Gaze", + "Orb (Blue / Guildhest Water Bubble?)", + "Orb (Purple / Guildhest Water Bubble?)", + "Bubble (ground)", + "Box crate", + "Brown chest", + "Campfire", + "Metal Pot", + "Jar with tied fabric lid", + "Brown Drawer", + "Brown shipping crate with rope on it", + "Giant bird's nest", + "Rock Gaol", + "Flying Ice Dragon", + "Tornado", + "Bertha Cannon", + "Poison Nix", + "Orb (Red / Lightning)", + "Floor Circle (Ice)", + "Spiny Plume (Garuda)", + "Orb (Lightning / White Center)", + "Orb (Watery / Music Symbol)", + "Lightning Elemental", + "Water Elemental", + "Slime (yellow)", + "Slime (purple)", + "Small Void Gate", + "Black Circle", + "Qarn Face (copy)", + "Fancy pillar", + "Karakul Ram", + "Panther", + "Missile Launcher (Castrum)", + "Missile Carrier Claw (Castrum)", + "Ultima Weapon", + "Floor Circle (Twintania's Liquid Hell)", + "ADS (Black/Red)", + "ADS (Black/Purple)", + "ADS (Black/Blue)", + "ADS (Black/Teal)", + "ADS (Black/Green)", + "ADS (Black/Yellow)", + "ADS (Black/Orange)", + "Rook", + "Fallen Neurolink", + "Blue Cobra (Glowing)", + "Red Cobra (Glowing)", + "Gaius van Baelsar - X Slash", + "Dreadknight Bind", + "Floor Circle (Conflagration)", + "Orb (Twintania's Hatch)", + "Orb (Green / Bright Lightning)", + "Spotlight", + "Goblin (Base)", + "Book Glow (Green) (?)", + "FFXIII Odin", + "Bomb (flaming)", + "Rat (2)", + "Box with Maps", + "Crate", + "Amalj'aa Standard w/ Torch", + "ADS (Black/White)", + "Snow-Covered Box", + "Moonfire Faire Bomb Cauldron", + "Purple (Book Glow?)", + "Large Void Gate", + "Spotted Zu Egg", + "White Zu Egg", + "Earthen Brickman (Dragon Quest)", + "Stone Brickman (Dragon Quest)", + "Golden Brickman (Dragon Quest)", + "Elbst", + "Orb (Green / Lighting w/ Purple Outline)", + "Pharos Aether Cloud", + "Rock (Gray)", + "Allagan Death Claw", + "Spriggolem", + "Voidsent Energy Spear", + "Voidsent Energy Scythe", + "Imp (On Fire)", + "Wind", + "Amalj'aa Beacon", + "Sylph Shroom", + "Spear stuck in ground", + "Fancy circular relic", + "Gilgamesh (six weapons)", + "Orb (Yellow / Bubble)", + "Key", + "Orb (Diabolos Nightmare)", + "Wamoura/Silkmoth", + "Imp C (Green)", + "Dark Matter Slug (Honey)", + "Flying Fire Dragon", + "Flying Thunder Dragon", + "Rock (White)", + "Rock (White) (II)", + "Rock (White) (III)", + "N/A", + "Mammet (Black Helmet)", + "Mammet (Blue Helmet)", + "Manticore", + "Spriggan (Yellow-eyed/Motley Egg)", + "Spriggan (Yellow-eyed/Pristine Egg)", + "Spriggan (Yellow-eyed/Chocobo Egg)", + "Spriggan (Yellow-eyed/Midnight Egg)", + "Spriggan (Yellow-eyed/Brilliant Egg)", + "Spriggan (Yellow-eyed/Vibrant Egg)", + "Stopklox Grislyfist / Illuminati Marksman / Goblin Sharpshooter", + "Goblin/Illuminati Glider", + "Magitek Vangob", + "Goblin Bomb", + "Goblin Bomb (Skull)", + "Goblin Bomb (Spiked)", + "Brown crate", + "Gilgamesh (Bradamante)", + "Enkidu (Chicken)", + "Bomb Incubator (Red)", + "Spiked Balls", + "Wind Cylinder", + "Garlic Prince", + "Ninki Nanka (w/ Nankas)", + "Kraken's Tentacle", + "Floor Circle (Water / Hullbreaker)", + "Shield Dragonling", + "Aevis B (White)", + "Ramuh-Egi", + "Leg Trap", + "Brineworm", + "Bubble (Hullbreaker)", + "Adamantoise Dragon", + "Scylla's Staff", + "Orb (Blue / Syrcus Ice)", + "Orb (Red / Syrcus Fire)", + "Orb (Purple / Syrcus Lightning)", + "Okeanis B (Green/Blue)", + "Interceptor Node (PVP)", + "Fire Dragon", + "Hunters Moon Disc (Syrcus)", + "Main Meteor Target", + "Small Meteor Target", + "Field Generator B (Allagan)", + "Aetherochemical Explosion Marker (Syrcus)", + "Ice Gaol", + "Orb (Purple / Aether Lines)", + "Orb (Purple)", + "Orb (Purple / Tam-Tara HM?)", + "Interceptor Drone (PVP)", + "Shiva (weapons)", + "White Gaol", + "Maelstrom Cannon", + "Torama", + "Battle Biast", + "Okeanis A (Blue/Orange)", + "Orb (Green)", + "ADS (Cuboid - Black - Red)", + "ADS (Ovoid - Black - Red)", + "Orb (Phoenix's Blackfire)", + "Orb (Phoenix's Redfire)", + "Floor Fire (Phoenix)", + "Phoenix (Glowing)", + "Bennu (Small)", + "Bennu (Medium)", + "Bennu (Large)", + "Bahamut (Blue)", + "Orb (Blue)", + "The Pain of Meracydia (Neurolinked)", + "The Sin of Meracydia (Neurolinked)", + "Shadowlurker (CAUSES CRASH)", + "Orb (Sandy / Qarn HM?)", + "Spriggan (Blue-eyed: Ice Rock)", + "Snowball?", + "Sabotender Guardia (+ Shield)", + "Sabotender Empiratriz", + "Steinbock Doe", + "Vine (Purple)", + "Typhon's FUNGAH!", + "A Tornado", + "4-tonze Weight", + "Cockatrice (Blue)", + "Vodoriga (Slumbering)", + "Orb (Purple/Blue)", + "Vine", + "Okeanis C (Red/Purple)", + "Atomos Avatar", + "Frumious Koheel Ja (on Wivre)", + "Spear", + "Manxome Molaa Ja", + "Mamool Ja Sacred Standard", + "Wivre", + "Hecteyes (II) (Slightly Lighter Copy)", + "Orb (Blue Static)", + "Orb (White / Five-headed Dragon?)", + "Cloud of Darkness' Dark Storm", + "Midgardsormr (Head)", + "Mandragora (b) (Gardening Copy)", + "Cloud of Darkness - Dark Cloud", + "Enkidu (Primal)", + "Dragon Head (Gilgamesh)", + "Orb (Binding Chains)", + "Snare (Steps of Faith)", + "Exploding Barrels (Steps of Faith)", + "Orb (Lightning)", + "Mirage Dragon (Thunder)", + "Mirage Dragon (Ice)", + "Generator (Keeper of the Lake)", + "Magitek Gunship B (Blue)", + "ADS (Enormous Egg) (Hartching-tide 2015)", + "Everliving Bibliotaph", + "Oliphaunt", + "Fallen Debris (Dusk Vigil)", + "Bit E (Blue & Orange) Clockwork Turret", + "Drum Bit A (Red)", + "Orb (Darkness)", + "Falak (II) (Copy)", + "Dragon Statue (Aery)", + "Brobinyak", + "Orb (Green Cloud)", + "Nidhogg (Hraesvelgr's left eye)", + "Orb (Fire)", + "Circle (Vertical Purple Effect)", + "Ravana (Four Small Swords)", + "Orb (Red/Blue)", + "Clockwork Spider B (Violet Glow)", + "Vanu Totem", + "Sentient Inkhorn 2 (Flightless)", + "Page 64/128/256", + "Mylodon", + "Falcon", + "Zoblyn", + "Orb (Sky Blue)", + "Void Ark Scrap", + "Orb (Bubble)", + "Trappy thing in 73 dgn", + "Magitek Gobwidow", + "Gobtank", + "Gobwalker", + "Lakelord Aenc Thon of the Long Gait", + "Crystals", + "Manipulator", + "Orb (Golden Electricity)", + "Orb (Spinning Discs?)", + "Cone (Spinning Discs?)", + "Gaius van Baelsar X Slash", + "Living Liquid (Anthromorph)", + "Living Liquid (Chiromorph Left)", + "Living Liquid (Chiromorph Right)", + "Investigate", + "Water Tornado (Neverreap? Alexander?)", + "Living Liquid (Vessel)", + "Garuda's Plume (Many Feathers)", + "Yak", + "Tiger", + "Sanguiptere", + "Fire Dragonet", + "Landmine (Pulsing)", + "Flea", + "Orb (White / Five Heade Dragon?)", + "Sword", + "Wind Circle", + "Blue Cobra", + "Vodoriga (II) (Slightly Lighter Copy)", + "Flying Graoully", + "Orb (Blue Fire)", + "Orb (Green Wind)", + "Gobtank G-IV", + "Ice Dragonet", + "Kite", + "Moss Dragonet", + "Steam Bit B (Purple)", + "Amikiri (Shisui of the Violet Tides 1st Boss)", + "Magic Broom 2", + "Poroggo (+ Blue Hat)", + "Dragonkiller Cannon", + "Bit D (Black & Gold)", + "Field Generator A (Allagan)", + "Falak", + "Cloth Scraps?", + "Coeurlregina (Darkened)", + "Proto-Ultima", + "Ravana (Two Large Swords)", + "Glowing Purple Crystal", + "Azys Lla ADS (Spheroid - Silver - Aqua)", + "Bird's Nest / Rosebear", + "Brown drawer", + "Sawbones (Head Vine)", + "Irminsul (Large)", + "Dexter (Mob-Left Frill)", + "Corruption (Vulture)", + "Orb (Gold)", + "Living Rock (II) (Copy)", + "Gyretower", + "Orb (Red/Black)", + "Diabolos (b)", + "Corruption (Sphere)", + "Corruption (Doll)", + "Ghrah Lumiary (Doll)", + "Corruption (Spider)", + "Pharos HM Generator", + "Bomb Incubator (Blue)", + "Irminsul (Small)", + "Sinister (Mob-Right Frill)", + "The Strongman (Heavensturn 2016)", + "Okeanis E (Blue/Red)", + "Green Caterpillar", + "Gana C (Purple/Yellow)", + "Gremlin (red)", + "FFXI Beetle", + "Dragon (Zombie)", + "Ahriman (Purple)", + "Sea Monk (Spiked - Blue)", + "Magic Doll from sky in FFXI", + "Magic Pot from FFXI", + "Sephirot (Phase 2)", + "Tornado with leaves", + "Gobblydegroper (Alexander 8)", + "Groundskeeper C (Purple)", + "Zuro Roggo (Antitower Boss 1)", + "Small Glowing Orb with Music Note in it", + "Boomtype Magitek Gobwalker G-VII (b)", + "Black Cat", + "Floating pink and red hearts", + "Enormous Black Boulder/Ball", + "Blaster (Alexander)", + "Blaster Clone (Alexander)", + "Brawler (Alexander)", + "Spinnning Wheel with spikes in the middle", + "Swindler (Alexander)", + "Vortexer (Alexander)", + "Onslaughter (Alexander)", + "Brute Justice (Final Form)", + "Wind tornado", + "Holder with 3 city-state banners in it", + "Ishgardian Banner", + "Raubahn Sword on the ground", + "Old World Striking Dummy (level 60)", + "Floating ball of ice", + "Dragon's Head (Shinryu Add)", + "Black Pegasus", + "Glowing Orb", + "Ozma Phase One", + "Riviera Striking Dummy", + "Minotaur (Mottled - Red)", + "Large Egg (White)", + "Nidhogg (Both Eyes / No Scars)", + "Nidhogg Phase 3", + "Fancy glowing blue spear/pole", + "Fancy giant red sword buried in the ground", + "Flaming Orb", + "Dangerous Boquet", + "Groundskeeper A", + "Fan Construct", + "Glowing Toxic Neon Green Orb", + "Whelk from FF6 - Purple", + "Whelk from FF6 - Grey", + "Barrel", + "Ozma Phase Two", + "Floating orb of ice", + "Gaol (Ozma)", + "Atomos (Silver)", + "Flame Sergeant Dalvag lookalike without the glowing eyes", + "Bird of Paradise (Rainbow)", + "Callofisteri", + "Living Lock (Vine)", + "Living Lock (Axe)", + "Living Lock (Bulb)", + "Bijou (Calofisteri)", + "Calofisteri Spine [SKL]", + "Sarchosurcus", + "Diremite (Green-Yellow)", + "Spider Mite (Green-Yellow)", + "The Epigraph (Weeping City Mid-Boss)", + "Spider Mite (Yellow-Brown)", + "Kum Kum (Syrcus Tower Amon Adds)", + "Revenant (Red)", + "Revenant (Grey)", + "Blue Mimic", + "Red Mimic", + "Coincounter Primus (Aquapolis Final Boss)", + "Catoblepas C (Yellow w/ Magenta)", + "Liquid Flame (Humanoid Form)", + "Liquid Flame (Hand Form)", + "Liquid Flame (PIllar Form)", + "Pudding (Oil)", + "Poroggo C (Purple w/ Blue Hat)", + "Sophia's Head", + "A silhouette of Sophia", + "Diloc Ciluoc (Xelphatol)", + "Skystone (Xelphatol Diloc Add)", + "Balloon Turret (Xelphatol Diloc Add)", + "A giant tornado", + "Cruise Chaser again", + "Ozma (Shade Form - Adds Sub-boss)", + "Exploding Fat Cannister ( 11 Adds)", + "Tsanahale (Green/Pink)", + "3 Rocks", + "Prime", + "Arrhidaeus' Lanner ( Prime Add)", + "Arrhidaeus' Lanner (Guns - Prime Add)", + "Arrhidaeus' Lanner (Clock - Prime Add)", + "Arrhidaeus' Lanner (Winged - Prime Add)", + "A floating multicolored crystal", + "Floating Glowing White Staff", + "Refurbisher ()", + "Magitek Gobwalker Mk. IX", + "Allagan Drone (Ant)", + "Gaol (Obsidian)", + "Gobroller Mk. VI ( 7)", + "Crystal-Powered Laser Cannon", + "Power Core (Deconstructor Add)", + "Incinerator (Alexander 11)", + "Eradicator ( 11)", + "Voidsent Wyvern (Boss?)", + "Revenant (Purple)", + "Okeanis F (Purple)", + "Golden Lion Mount [Incorrect]", + "Rock Rhino (Mossy)", + "Spriggan (Red-eyed/Lightning Rock)", + "Skatene B (White)", + "Sea Monk (Psychadelic)", + "Goobbue (Frozen)", + "Shadow Dragon (Purple/Green)", + "Generic Blue Demon thing", + "Diabolos (Scathach)", + "Scary Floating Demon", + "Bulbasaur? (Skalla Boss)", + "Gowrow (Sohm Al HM 2nd Boss)", + "Rock Plant Fish Mouth???", + "Magma Snipper", + "Atomos (Chrome Blue)", + "Atomos (Yellow/Black)", + "Blue Orb (Big)", + "Orange Rotating Rings", + "Imp Jester", + "Zurvan (Fettered)", + "Magitek Predator (w/ Pilot)", + "Piloted Sky Armor", + "Armored Weapon (Magitek Laser Thing)", + "Magitek Killer Mantis", + "The Griffin's Blade", + "Canis Pugnax", + "Scathach Tendril Hand", + "Numbers (Eureka NM)", + "Scathach (Dead)", + "Pegasus (Mount)", + "Unicorn", + "Lion", + "Enraged Marid", + "Shinryu (Huge)", + "Lion (Magma)", + "Byakko (Beast Form - Enraged)", + "Vanara of Biting Cold", + "Vanara of Resonating Thunder", + "Hex Shield Thing", + "Dhara? (Yellow/Brown)", + "Dhara? (Blue/Black) / Gnome", + "Wind Blob?", + "Treant?", + "Ent", + "Rock monster with two hands", + "Antlion", + "Orcus (A-Rank SB Hunt)", + "Grasshopper", + "Simurgh from FFXI / Phorusrhacos", + "Dragonfly (Green/Yellow)", + "Crab Scorpion (Grey)", + "Bear (Black)", + "Shalloweye", + "Griffin (True)", + "Scorpion Bug (Brown)", + "Archamoth", + "Dhammel", + "Coblyn (Pink)", + "Elephant (Grey/Blankets)", + "Mossy Stone Block Demon", + "Dhruva? (Ice)", + "Tatsunoko (Blue/Purple)", + "Apa/Undine", + "Ghost Aurelia (Sirensong Sea)", + "Tenaga (Yanxian Dryad)", + "Doman Treant?", + "Yak Ram Thing", + "Gyuk", + "Monk Chimera", + "Doman Bear? (Orange) Wolverene", + "Phurble (Green)", + "Cobra (Blue)", + "Manta (No Saddle)", + "Wolf (Red/Grey)", + "Steppe Stone Block?", + "Spectre (Blue)", + "Weird flying bug", + "Remora", + "Lammergeyer? (Green)", + "Lammergeyer? (Red)", + "Yol (Pink)", + "Grimekhala (A-Rank SB Hunt)", + "Siege Breaker (Doma Castle)", + "Karakuri Mask?", + "FFXI Shantotto", + "Magitek Rearguard (Doma Castle 1st Boss)", + "Alte Roite (Omega 1)", + "Fire Orb", + "Harakiri Hanya (Kugane Castle)", + "Weirdly colored lion", + "Cerberus (Red - Eureka)", + "Vira Swordmaid (?)", + "Garula (Bardam's Mettle 1st Boss)", + "Bardam's Hammer Golem", + "Purple Gryphon", + "Feather Orb", + "Magna Roader (Purple)", + "Number XXIV (Castrum Abania 2nd Boss)", + "Inferno (Castrum Abania Final Boss)", + "Magitek Heavy Loader (Bomb)", + "Okina (S-Rank Hunt)", + "Magitek... Crawley Thing (Red/Black)", + "Khun Chuluu (Sad Bardam Walls)", + "Void Ark Plant Beast?", + "Gowrow (Sohl Al HM)", + "Living Liquid (Alex)", + "Erle (A-Rank SB Hunt)", + "Vochtstein (A-Rank SB Hunt)", + "Lugat (Sirensong Sea 1st Boss)", + "Yumemi (Non-Naked)", + "Sum (A-Rank SB Hunt)", + "Governor (Sirensong Sea 1st Boss)", + "Ugly Blue Scorpion Bug", + "Susanoo", + "Susanoo Sword Part", + "Lorelei (Sirensong Sea Final Boss)", + "Ruby Tide Princess", + "Shisui Yohi (Shisui Final Boss)", + "Water Globe (Purple)", + "Shinryu Tail", + "Red Egg", + "Maned Dragon?", + "Chain Bind (Green) [Effect]", + "Angler Fish (Brown/Orange)", + "Sadu's Door Model [Incorrect]", + "Bahamut (Large-Blue)", + "Coeurl (Grey/Green)", + "Coeurl (Red/Orange)", + "Ivan Coeurlfist", + "Glowing Dragon Skull (Yojimbo Add)", + "Lion (White)", + "Fist Temple Golem?", + "Spirit Coeurl", + "Susanoo Blocking Shield Effect", + "Armored Weapon (Magitek Laser Spider)", + "Targeting mark on ground", + "Zenos Sword (Wind)", + "Zenos Sword (Lightning)", + "Zenos Sword (Force?)", + "Magitek Avenger", + "Magna Roader (Red)", + "Magitek Bit Bomb (Rearguard Add)", + "Razor Disc (Doma Boss Add)", + "Garlean Gatling Gun", + "Stone Gaol (Susanoo)", + "Floating Blue Light", + "Rock Beast (Purple Crystals)", + "Ancient Wyvern (Spined - Black/Purple)", + "Blue Egg", + "Catastrophe Tentacle", + "Sun Orb", + "Electric Orb", + "Shining Orb", + "Scarab (Tan)", + "Training Dummy (Elite)", + "Demon Tome (Blank)", + "Water Orb", + "Dragon (Yellow)", + "Nine-Tails (FATE Boss)", + "Fox kit", + "Demon Face Totem (Omega 4)", + "Tentacle (Exdeath Add)", + "Black Hole [Effect]", + "Neo Exdeath", + "Namazu (Big)", + "Salt and Light (S-Rank SB Hunt)", + "Hoarhound (Fenrir - Purple/Red)", + "Horse (Namazu Quest Mount)", + "Diamond Carbuncle", + "Midgardsormr pet size", + "Level Checker (Red)", + "Level Checker (Green)", + "Treasure Chest (Copper)", + "Ram (bell)", + "X Slash [Effect]", + "Wood Table", + "Basket of Fish", + "Dummy", + "Hashmal: Bringer of Order (Rabanastre 2nd Boss)", + "Rolling Boulder", + "Floating Purple Dragon Head", + "Argath Thadalfus (Rabanastre Final Boss)", + "Floating Sword", + "Mateus: the Corrupt (Rabanastre 1st Boss)", + "Ice-Skating Goddess (Mateus Add)", + "Floating Water Bubble", + "Block of ice", + "Kelpie (Skalla 1st Boss)", + "The Old One (Skalla 2nd Boss)", + "Rofocale (Rabanastre 3rd Boss)", + "Sea Faerie? (Skall Add)", + "Sea Faerie? (Skalla Trash)", + "Archaeodemon (Rabanastre Trash)", + "Bangaa Guy", + "Bangaa Guy - red", + "Bangaa Guy - grey", + "Weird Multicolored Sphere", + "Flying Dragon", + "Flying Dragon - White", + "Flying Dragon - Green-ish", + "Squib (Eureka)", + "Lamashtu (Eureka NM)", + "Mirrorknight (Corrupted - Green/Purple)", + "Bombadeel (Eureka NM)", + "Treant (Christmas Event)", + "Pazuzu (Eureka Anemos NM)", + "Ice Elemental", + "Pulsating blue ground field", + "Otake-Maru (Hell's Lid 1st Boss)", + "Kamaitachi (Hell's Lid 2nd Boss)", + "Flaming Wall monster from Hell's Lid", + "White Whittret", + "The Ultima Warrior (Fractal Cont. HM 1st Boss)", + "Motherbit (Fractal Cont. HM 1st Boss)", + "Allagan Bit (Motherbit Add)", + "The Ultima Beast (Fractal Cont. HM Final Boss)", + "Guardian (Omega 7)", + "Airforce (Omega 7 Add)", + "Floating Glowing Missile", + "Dadaluma (Omega 7 Add)", + "Glowing: Pulsating Green Sphere of Epilepsy", + "Allagan Prototype Chimera (Fractal HM)", + "Allagan Prototype Minotaur (Fractal HM)", + "Eyeball Slime (Radioactive)", + "Primelephas (Eureka)", + "A Tornado (literally)", + "Cat form Byakko", + "Orb (Byakko's Balls)", + "Crab from Hells Lid", + "Goddess Chadarnook (Omega 6 Add)", + "Chadarnook (Omega 6 Boss)", + "Floating Gold Needle", + "Rathalos (Monster Hunter)", + "Phantom Train Ghost", + "Phantom Train Markers", + "Regular Kefka", + "God Kefka", + "Kefka Forsaken", + "Sabotender Emperatiz", + "Alpha (Chocobo from Omega)", + "A ball of fire", + "Daidarabotchi (Swallow's Compass 1st Boss)", + "Wukong - Monkey King", + "Gremlin (yellow)", + "Ultima Weapon of some sort", + "Cute monkey", + "Floating gold fan", + "Floating blue cloud of depression", + "Spinning White Orb", + "A giant rock - grey", + "Hiruku (HoH Floor 30 Boss)", + "Yiazmat (Ridorana Final Boss)", + "A huge ring of green mirrors", + "Famfrit: the Darkening Cloud (Ridorana 1st boss)", + "Dark Ewer (Famfrit Add)", + "Math Construct boss from Ridorana", + "Spinning Gear", + "Rusted Math Construct", + "Yukinko (Eureka Pagos)", + "Belias: the Constantly Fucking Going Invincible (Ridorana 2nd Boss)", + "Red Demon Spinning Mask (Heaven-on-High)", + "Blue Demon Spinning Mask (Heaven-on-High)", + "Ridorana Exploding Fire Bell Trash", + "Golem (Gougan)", + "Turtle", + "Weird lion", + "Deformed Mameshiba?", + "Fat Cat", + "Pagos Bunny", + "Creepy Floating Head with Scythes for arms and another head on top", + "Ash Dragon (Pagos)", + "Golden King Crab", + "Chakram - The Burn", + "Dreadnaught", + "King Igloo (Eureka Pagos NM)", + "Eureka Pagos NM Marlboro Stack", + "Matmata", + "Louhi (Eureka Pagos NM)", + "Bangaa Boss Guy", + "Hedetet - Pillar", + "Brown Slime", + "Frost Dragon", + "Omega - Chaos", + "Suzaku Orb", + "Omega - Midgardsormr", + "Midgardsormr Head (Omega)", + "Pillar", + "Suzaku - Dressed", + "Phenoix Add", + "Suzaku - Feather", + "Blue Orb", + "Red Orb", + "Suzaku - Human", + "Suzaku Indicator (Red)", + "Suzaku Indicator (Yellow)", + "Suzaku Indicator (Blue)", + "Suzaku Indicator (Purple)", + "Suzaku Indicator (North)", + "Suzaku Indicator (East)", + "Suzaku Indicator (South)", + "Suzaku Indicator (West)", + "Omega Rocket Punch 1", + "Omega Rocket Punch 2", + "Omega Level Checker", + "Omega M", + "Omega M | F Puddle", + "Neo Omega", + "Omega- Floating Left Arm", + "Omega- Floating Right Arm", + "Omega 12 Structure?", + "Mister Bright-Eyes", + "Endymion B (Rainbow)", + "Eden's Orb", + "Floating Axe", + "Marshmallow (Black-eyed)", + "Hidden Gorge Machine", + "Magitek Trooper FFXV", + "MA-X FFXV", + "Iseultalon FFXV", + "Floating Staff", + "Eggplant", + "Slime (Gold)", + "Suzaku Effect", + "Fire Elemental", + "Garuda XV Event", + "Christmas Slime", + "Ultima", + "Omega 11s Hands", + "Blood Angel (Ultima add)", + "Annia Quo Soranus and Julia Quo Soranus (Ghimlyt) (T-Pose)", + "Mustadio (Orbonne)", + "Giant Autoturret (Orbonne Add)", + "Rusted Golem", + "The Thunder God (Orbonne-Not Fucked Up)", + "Ephemeral Warrior", + "Harpy (Orbonne)", + "Absolute Virtue Dragon Add", + "Armor Boi (Ghimlyt)", + "Wall of Snakes (Seiryu)", + "Ultima: The High Seraph (Orbonne)", + "T.G. Sword", + "Famfrit (Ultima)", + "Belias (Ultima)", + "Hashmal (Ultima)", + "Grand Cross Crystal", + "Art's Spear", + "Owain's Spear", + "Owain's Hand", + "Storge - Malikah's Well 3rd boss", + "Feather", + "Spikey", + "Trisiltia", + "Tusk", + "White Chicken", + "Sin Eater", + "Tesleen (Sin Eater)", + "Phillia (Holmister final boss)", + "FFXII Sleipnir", + "Ice Bomber", + "Therion - Flying", + "Cladoselcahe", + "Eros (Ravel Final Boss)", + "Terminus Bellweather", + "Flying Head", + "Froggo", + "Glowy Orb", + "Armored Beast (Red)", + "Daen (??)", + "Titania's Add", + "Fist in Titana", + "Add in Twinning?", + "Electral Orb", + "Jar?", + "Greatwoods Golem", + "A rank in greatwoods?", + "Fate boss in Greatwoods", + "Tempest mob?? Unknown", + "Bunch of Sticks", + "Unsure: Tail-less Pieste (??)", + "Unsure: Stone beast with large bulb (??)", + "Maliktender (A Rank)", + "Tyger (S-Rank Hunt)", + "Chain/Stun/Whatever", + "Talos", + "Goobu", + "Geodude", + "Sin Eater Bird (??)", + "Sin Eater Diremite B (??)", + "Sin Eater Bear (??)", + "Sin Eater Dzo (??)", + "Spiked Monk Chimera", + "Engedered Pteraketos", + "Fire Bomber", + "Quetzalcoatl - Akadaemia", + "Serpent", + "Sin Eater Demon wall", + "Vauthry", + "Sticks Sticks but ice", + "Floating Swords (Innocence)", + "Boulder in MSQ", + "Eden's Voidwalker's Adds", + "Eden's Voidwalker's hand add stuff", + "Sin Eater Cocoon", + "Sin Eater Diremite (??)", + "Hades: first form", + "Hades (2nd form)", + "Dark Orb", + "Ran'jit with scythe", + "Ran'jit pet", + "Shinryu head?", + "Eden's Leviathan", + "Eden's Titan", + "A rock", + "Eden's Titan Rocks", + "Eden's Titan 2nd Form", + "Ran'jit - Frozen", + "Owl in chair (Name needed)", + "Golden Fuath (??)", + "ShB S-rank goobue", + "Pegasus", + "Dark Pegasus", + "Zu lookalike monster (Name needed)", + "Glider (Blue)", + "Sticks are alive!?", + "Golden Construct", + "S-rank SHB", + "Box", + "Smaller Box", + "Sin Eater cacoon", + "Leannan Sith (Cosmos 2nd Boss)", + "Leannan Sith Seed", + "Nier Reverse-Jointed Goliath", + "Nier Add 2", + "Goliath Tank ?? (Nier Raid)", + "Seeker of Solitutde (Cosmos 1st Boss)", + "Ludus (Cosmos Final Boss)", + "Magicked Broom sweeping", + "Hades Extreme (???)", + "Red orb (???)", + "Blue square (???)", + "Hobbes (Copied Factory 2nd Boss)", + "Hobbes Armature", + "Hobbes Armature 2", + "Perfect (Golden)", + "Serial-jointed Command Model (Copied Factory 1st Boss)", + "Nier Add 3", + "Nier Orbs", + "9S-operated Walking Fortress (Copied Factory Final Boss)", + "9S in Flier", + "Nier Goliath Tank", + "Marx Support", + "Nier Add", + "Nier Add 4", + "Nier Add 5", + "Nier Add 6", + "Nier Flier no pilot", + "Nier Missiles", + "Prime (TEA version)", + "Perfect Alexander (Coloured)", + "Destrudo (Pixie Quest Boss)", + "Mochi Minotaur", + "2P in Flier", + "Unknown (Anamensis 1st Boss)", + "Ruby Weapon", + "Ruby Weapon - Nael van Darnus phase", + "Ruby Weapon Add (???)", + "Nael Heads(Red)", + "Nael Heads(Blue)", + "Kyklops (Anamensis 2nd Boss)", + "Kukshs Dheem (Anamensis Final Boss)", + "Kukshs Dheem Add", + "Ramuh, Heritor of Levin", + "Eden Ramuh Black", + "Eden Ramuh Horse Add", + "Glowing Evil Bird", + "Garuda, Heritor of the Vortex", + "Eden Ifrit", + "Eden Raktapaksa", + "Garuda's Feathers", + "Ifirit Orb", + "Trench Amemone", + "Flying Hairy Eyeball", + "Lo ousia", + "Eden Idol of Darkness", + "Eden Bird Adds", + "Eden Verse Idol of Darkness", + "Orb", + "Eden Verse Idol of Darkness Birds", + "Eden 7 Birds", + "BLU Blue X", + "BLU Red X", + "BLU Green X", + "Stack of blue cubes", + "Ruby Weapon Orbs", + "Void Octopus thing", + "Eden Verse Shiva", + "Aqua Orb", + "Earthen Golem", + "Feo Ul King Titania", + "Eden Idol of Darkness Birds", + "Sppoky demon Bird", + "Monoceros (Bozja)", + "Zombie Behemoth", + "Elidibus (Warrior of Light)", + "Elddragon (Red Bahamut)", + "Sapphire Weapon", + "Alebrije monster thing", + "724P-operated Superior Flight Unit", + "Bozja Walking Cannon", + "Sand Mandagora ??", + "Cerberus (Delibrum Reginae)", + "Pod (Nier raid)", + "Construct X (used various places)", + "Ball of feathers", + "Zul", + "4C6F6E67696E67: The Compound", + "Compound 2P (Black)", + "Broken Pod", + "Brionac (Castrum Lacus Litore)", + "Shiny Orb", + "Red Orb of ouch", + "Nidstinien", + "Pile of Androids", + "Android", + "Mud Ball (Matoya's Relict)", + "Mudman (Matoya's Relict)", + "Golden Beaver", + "Emerald Weapon (with Manipulators, Green)", + "Emerald Weapon Right Claw", + "Magitek Mine (Emerald Weapon)", + "Emerald Weapon Left Claw", + "Emerald Weapon add (gold)", + "Coat of Arms (Blue)", + "Coat of Arms (Yellow)", + "(Matoya's Relict)", + "Cloud of Darkness (Eden)", + "Cloud of Darknes (Eden, Clone)", + "Shadowkeeper (Standing)", + "Shadowkeeper (standing shadow clone)", + "Shadowkeeper (shadow clone)", + "Shadowkeeper Shadow", + "Umbral Orb (Trio)", + "Gukumatz (Fatebreaker)", + "Thunder Ring (Turn of the Heavens)", + "Fire Mirror (Sundered Sky)", + "Thunder Mirror (Sundered Sky)", + "Eden's Promise (Full size)", + "Green Golem", + "Grey Minotaur", + "Gold Minotaur", + "Aetherial Ward (Delibrum Reginae)", + "Queen's Gunner Turret (Bishop)", + "Queen's Gunner Turret (Rook)", + "Gull", + "Dolphin Calf", + "Hot & Cold Fire 1 Arrow", + "Hot & Cold Fire 2 Arrow", + "Hot & Cold Blizzard 1 Arrow", + "Hot & Cold Blizzard 2 Arrow", + "Hot & Cold Blizzard 2", + "Conflagration Strike", + "Hot & Cold Fire 2", + "Bacon Bits", + "Drone?", + "Trinity Seeker (Katanas)", + "Emerald Weapon (with Manipulators, gold)", + "Emerald Weapon (with Manipulators, green, open)", + "Emerald Weapon (Right Manipulator, gold, sword)", + "Lunar Bahamut Nail", + "Longitudinal Marker (The Tower at Paradigm's Breach)", + "Latitudinal Marker (The Tower at Paradigm's Breach)", + "Xun-Zi / Meng-Zi", + "Ice Sprite (Zadnor)", + "Electric orb (Paglth'an, Amhuluk fight)", + "Directional Orb (The Tower at Paradigm's Breach)", + "Magitek Core (Paglth'an)", + "Diablo Armament", + "Bit C (Red)", + "White Crystal", + "Glowing Shard", + "Alkonost Shadow", + "Spheroid (The Tower at Paradigm's Breach)", + "Energy Orb (The Tower at Paradigm's Breach)", + "Hatchingtide 2021 Chicken", + "Red Girl (Giant)", + "Floating Right Hand (Diamond Weapon)", + "Floating Left Hand (Diamond Weapon)", + "White Lance (Red Girl)", + "Black Lance (Red Girl)", + "Amalj'aa (Physical)", + "Chocobo Carriage", + "Ultima Weapon (no wings/copy)", + "Ultima Weapon (no wings)", + "Ultima Weapon (copy)", + "Moogle", + "Magitek Juggernaut", + "Amalj'aa (Scavenger)", + "Jackal", + "Coeurl Kitten", + "Fibubb Gah", + "Amalj'aa (Brotherhood of Ash)", + "Hamujj Gah", + "Amalj'aa Mender", + "Cavalry Drake", + "Wind-Up Edvya", + "Cherry Bomb", + "Sapphire Carbuncle", + "Maggie", + "Tonberry Scholar", + "Wind-Up Shantotto", + "Ancient Wyvern (- Neurolinks)", + "Garuda's Plume (No Effect)", + "Rubricatus", + "Karakul Ram (bell)", + "White Devil", + "Laurel Goobbue", + "Wamouracampa (II) (Copy)", + "Wamoura (II) (Copy)", + "Gnat", + "Sleipnir", + "Gilgamesh (Unarmed)", + "Cavalry Elbst", + "Mini Mole", + "Kaliya (II) (Copy)", + "Feridad", + "BUGGED - DO NOT CLICK", + "Manacutter (White)", + "Shiva (No Weapons)", + "Captain Madison (Monster)", + "Wayward Hatchling", + "Storm Hatchling", + "Flame Hatchling", + "Black Chocobo Chick", + "Princely Hatchling", + "Heavy Hatchling", + "Ultros (One Lump)", + "Ultros (Two Lumps)", + "Ulros (Three Lumps)", + "Tender Lamb", + "Aithon", + "Xanthos", + "Markab", + "Einbarr", + "Gullfaxi", + "Boreas", + "Kirin", + "Water Imp", + "Dust Bunny", + "Cloud of Darkness (- Clouds)", + "Spriggan (Yellow-eyed w/ Red Egg)", + "Vath", + "Vath storyteller", + "Thunder Dragon (b)", + "Vidofnir (b)", + "Ice Dragonet (b)", + "Moss Dragon (b)", + "Fire Dragon (b)", + "Zombie Dragon (b)", + "Ice Dragon (b)", + "Midgardsormr (Mount)", + "Sanuwa (Mount)", + "Griffin (Mount)", + "Idyllshire Merchant", + "Goblin (Idyllshire)", + "Nidhogg (Hraesvelgr's Left Eye / Fresh Scars)", + "Nidhogg (No Eyes / Fresh Left Scar)", + "Hraesvelgr (One Eye)", + "Azys Lla ADS (Ovoid - Silver - Yellow)", + "Azys Lla ADS (Spheroid - Silver - Yellow)", + "Azys Lla ADS (Cuboid - Silver - Green)", + "Azys Lla ADS (Spheroid - Silver - Violet)", + "Azys Lla ADS (Spheroid - Silver - White)", + "Azys Lla ADS (Ovoid - Silver - White)", + "Azys Lla ADS (Cuboid - Silver - Violet)", + "Azys Lla ADS (Ovoid - Silver - Green)", + "Azys Lla ADS (Spheroid - Silver - Orange)", + "Azys Lla ADS (Cuboid - Silver - Orange)", + "Azys Lla ADS (Cuboid - Silver - White)", + "Azys Lla ADS (Ovoid - Silver - Violet)", + "Azys Lla ADS (Spheroid - Silver - Green)", + "Azys Lla ADS (Ovoid - Silver - Orange)", + "Azys Lla ADS (Cuboid - Silver - Aqua)", + "Azys Lla ADS (Ovoid - Silver - Aqua)", + "Gnath", + "Azys Lla ADS (Spheroid - White - Aqua)", + "Azys Lla ADS (Spheroid - Silver - Red)", + "Ascian Prime (Not Glowing)", + "Gnath Priest", + "Ravana (Glowing Red: Two Large Swords)", + "Shiva (Sword)", + "Flying Thunder Dragon (b)", + "Flying Moss Dragon (b)", + "Flying Ice Dragon (b)", + "Flying Fire Dragon (b)", + "Flying Zombie Dragon (Grounded)", + "Bismarck (Broken Chitin)", + "Manipulator (Four Short Legs)", + "Tonberry Marauder", + "Backrix", + "Azys Lla ADS (Cuboid - Silver - Lavender)", + "Black Coeurl", + "Accompanyment Node", + "Manacutter (Silver)", + "Manacutter (Black)", + "Restrained Sephirot", + "Twintania (Mount)", + "Cactuar Cutting", + "Magic Broom", + "Wind-Up Warrior of Light", + "Floating Eye (b)", + "Trained Sanuwa", + "Chigoe Larva", + "Gaelikitten", + "Animus Elemental", + "Elemental F (White)", + "Azys Lla ADS (Ovoid - White - Lavender)", + "Azys Lla ADS (Spheroid - White - Yellow)", + "Brute Justice (Glowin Final Form)", + "Swindler ()", + "Brawler ()", + "Wyvern (Mount)", + "Calofisteri (Left Haircut)", + "Prime ()", + "Stone Gaol (Titan)", + "Sarcophagus of the Demon Queen: Scathach", + "Tozol Huatotl (Xelphatol)", + "Sophia (second form)", + "Sophia (without head)", + "Sophia's glowing scales", + "Shinryu EX?", + "Diabolos Prime", + "Ancient Wyvern (Deep White)", + "Hraesvalgr (Spirit)", + "Hraesvelgr (One Eye Closed)", + "Glowing Nidhogg Phase 3", + "Nidhogg (Spirit)", + "Dragon (Shadow)", + "Ancient Wyvern (Iridescent Blue)", + "Vath Deftarm", + "Kongamato (Mount)", + "Idyllshire Engineer", + "Dragonet (Blue/White)", + "Idyllshire Augmentor", + "Whisper", + "Jibanyan", + "USApyon", + "Shogunyan", + "Komasan", + "Shaggy Shoat", + "Blizzaria", + "Kyuubi", + "Komajiro", + "Manjimutt", + "Noko", + "Venoct", + "Hovernyan", + "Robonyan F-Type", + "Cloud Mallow", + "Wind-Up Bahamut", + "Moonfire Elbst (2014)", + "Wind-Up Dalamud", + "Little Midgardsormr", + "Wind-Up Louisoix", + "Model Vanguard", + "Mammet 003L", + "Mammet 003G", + "Mammet 003U", + "Cheerleader", + "Christmas Paissa", + "Nutkin", + "Zenos Yae Galvus (Again)", + "Cait Sith (Clean - Redbill Scarf)", + "Lucky Bucket", + "Azys Lla ADS (Ovoid - White - Yellow)", + "Zenos Yae Galvus (Helmet)", + "Gigi", + "Red Baron", + "Anima", + "Zenos Yae Galvus (No Helm)", + "Yol (Purple/Green)", + "Manta Ray (Kojin Quest Mount)", + "White Lanner (Bismarck)", + "Rose Lanner (Ravana)", + "Round Lanner (Thordan)", + "Warring Lanner (Sephirot)", + "Dark Lanner (Niddhog)", + "Wolf (Blue/Red)", + "Wolf (White/Light Red)", + "Adamantoise (Mount)", + "Falcon (Mount)", + "Astrope", + "Witch's Broom", + "Original Fat Chocobo", + "Lanner (Sophia)", + "Firebird", + "Syldra", + "Susanoo Battle Mode", + "Ananta", + "Lanner (Zurvan)", + "Lanner (Brown)", + "Odder Otter", + "Namazu (Blank Eyes)", + "Karakul Ewe (bell)", + "Inferno XL (Castrum Abania Final Boss)", + "Zenos With Helmet", + "Wind-Up Leader (Raubahn)", + "Wind-Up Leader (Merlwyb)", + "Wind-Up Leader (Kan-E-Senna)", + "Gobroller Mk. VII (Alex 10)", + "Hunting Hawk", + "Horse (Brown)", + "Halicarnassus (Omega 3)", + "Golden Bahamut (UCOB)", + "Rival Wings Mech #2 - Red", + "Rival Wings Mech #2 - Blue", + "Rival Wings Mech #1 - Red", + "Rival Wings Mech #1 - Blue", + "Argath Thadalfus? (Rabanastre Final Boss)", + "Lesser Panda", + "Paissa Brat", + "Namazu with headband", + "Zenos Without Helmet", + "Alpha (Omega)", + "Terrified Namazu", + "Tonberry Black Mage", + "Doom Knight Champion", + "Sparrow", + "Morpho", + "Principia (Allagan Summoner Tome)", + "Ladybug", + "Starlight Bear", + "Wind-Up Kojin ( Purple - Minion)", + "Zenos (Recovering)", + "Fox", + "Whittret", + "Bom Boko", + "Mameshiba", + "Marid", + "Tsukuyomi again", + "Baby Opo-opo", + "Nimbus Cloud", + "Ultima Weapon Ultimate", + "Battle Mode Tsukuyomi", + "Namazu", + "Yiazmat (Enraged)", + "Sai Taisui (Swallow's Compass)", + "Gilded Magitek Armor", + "Mikoshi", + "Omega - F (With Weapon)", + "Grani", + "Monkey king", + "Kitten Thing", + "Assassin Fry", + "Another type of Namazu with headband", + "Wise Namazu with headband", + "Another type of Namazu", + "Wise Namazu", + "8 Namazu carrying a platform with nothing on it", + "Wind-Up Bartz", + "Phoenix - Possibly Suzaku", + "Koryu", + "Seiryu (Snake Form)", + "Omega F", + "OMG - Omega", + "Omega - F (No Weapon)", + "Tiny Rat", + "Regalia Type-G (Off)", + "Construct 8 (Refurbished)", + "Legendary Kamuy", + "Auspicious Kamuy", + "Lunar Kamuy", + "Euphonius Kamuy", + "Hallowed Kamuy", + "Ultimate Kamuy/Wolf", + "Azys Lla ADS (Ovoid - White - Aqua)", + "Ironfrog Mover", + "Sand Fox", + "Fae Bismarck", + "Philia - Holminster Switch", + "Sin eater lion", + "Insatiable Flame: Lugus", + "Swearing Imp Thing", + "Nier Walking Fortress no pilot", + "Small Stubby (Nier)", + "Zenos", + "Golem boss that does water stuff", + "Amaro", + "Kuribu Sin Eater", + "The Great Serpent of Ronka", + "Skyslipper", + "Eden Mount!?", + "Arch Ultima no pilot", + "Ruby Weapon (Nael van Darnus only)", + "Ruby Weapon (Shell)", + "Ramuh's Beard", + "Eden Shiva (Naked: Transformation)", + "Eden Shiva (Hydaelyn Form)", + "Sephirot-Egi (no leaves)", + "Rolling Tankard", + "Shoebill", + "Clionid Larva minion", + "Bitty Duckbill", + "Mammet 001", + "Spoony Bard", + "Wind-Up Moogle", + "Festive Namazu", + "Wind-Up Relm", + "Wind-Up Aymeric", + "Wind-up Lyse", + "Wind-Up Hien", + "Primogs", + "Fisherman Otter", + "Page 63", + "Portly Porxie", + "Pod (Nier Raid)", + "Behatted Serpent of Ronka", + "Wind-Up Mystel", + "Sapphire Weapon Gundam", + "Flight Unit Empty", + "Azys Lla ADS (Cuboid - White - Aqua)", + "Ironfrog Ambler", + "Weatherproof Gaelicat", + "The Major-General", + "Emerald Weapon (green, closed)", + "Fae Gwiber", + "Innocent Gwiber", + "Shadow Gwiber", + "Ruby Gwiber", + "Gwiber of Light", + "Emerald Gwiber", + "Diamond Gwiber", + "Landerwaffe", + "Diamond Weapon", + "Al-iklil", + "Sultana Portrait", + "Lord Enma", + "Lord Ananta", + "Zazel", + "Demona", + "Ascian Prime (Mitron/Loghrif)", + "Black Hayate", + "Red Girl (Creepy)", + "Azys Lla ADS (Cuboid - Silver - Yellow)", + "Azys Lla ADS (Ovoid - Silver - Red)", + "Trinity Seeker Clone", + "Much-coveted Mora", + }; + + public static readonly IReadOnlyDictionary ModelCharas = new Dictionary() + { + { 2u, new int[]{ 1487 } }, + { 3u, new int[]{ 258, -1085 } }, + { 6u, new int[]{ 1300, -135, 2660, -189, -190, 2548, 3598, 3599 } }, + { 18u, new int[]{ 1687 } }, + { 20u, new int[]{ 9 } }, + { 21u, new int[]{ 10 } }, + { 22u, new int[]{ 11 } }, + { 23u, new int[]{ 444 } }, + { 24u, new int[]{ -52 } }, + { 25u, new int[]{ -11 } }, + { 26u, new int[]{ -53 } }, + { 27u, new int[]{ -26 } }, + { 28u, new int[]{ 47 } }, + { 29u, new int[]{ -18 } }, + { 31u, new int[]{ 408 } }, + { 32u, new int[]{ 6 } }, + { 34u, new int[]{ 33 } }, + { 35u, new int[]{ 32 } }, + { 36u, new int[]{ 43 } }, + { 37u, new int[]{ -16 } }, + { 38u, new int[]{ -337 } }, + { 39u, new int[]{ -2 } }, + { 40u, new int[]{ 397 } }, + { 41u, new int[]{ 1183 } }, + { 42u, new int[]{ -348 } }, + { 43u, new int[]{ -309 } }, + { 44u, new int[]{ -4 } }, + { 45u, new int[]{ -5 } }, + { 46u, new int[]{ -3 } }, + { 48u, new int[]{ 400 } }, + { 49u, new int[]{ -7 } }, + { 50u, new int[]{ -13 } }, + { 51u, new int[]{ -14 } }, + { 52u, new int[]{ 1479 } }, + { 53u, new int[]{ 7 } }, + { 54u, new int[]{ 8 } }, + { 55u, new int[]{ 2142 } }, + { 56u, new int[]{ -10 } }, + { 57u, new int[]{ 49 } }, + { 58u, new int[]{ 118 } }, + { 59u, new int[]{ 41 } }, + { 60u, new int[]{ 2623 } }, + { 61u, new int[]{ 3 } }, + { 62u, new int[]{ 4 } }, + { 63u, new int[]{ -6 } }, + { 64u, new int[]{ -47 } }, + { 65u, new int[]{ 106 } }, + { 66u, new int[]{ -232 } }, + { 67u, new int[]{ -113 } }, + { 68u, new int[]{ 1462 } }, + { 69u, new int[]{ 28 } }, + { 75u, new int[]{ -15 } }, + { 76u, new int[]{ 197 } }, + { 78u, new int[]{ 129 } }, + { 79u, new int[]{ -17 } }, + { 80u, new int[]{ 46 } }, + { 81u, new int[]{ -8 } }, + { 82u, new int[]{ 1981 } }, + { 83u, new int[]{ 1569 } }, + { 84u, new int[]{ -66 } }, + { 87u, new int[]{ 1461 } }, + { 88u, new int[]{ -158 } }, + { 89u, new int[]{ 35 } }, + { 94u, new int[]{ -9 } }, + { 95u, new int[]{ 34 } }, + { 96u, new int[]{ -1 } }, + { 97u, new int[]{ 120 } }, + { 98u, new int[]{ -12 } }, + { 99u, new int[]{ -25 } }, + { 100u, new int[]{ 316 } }, + { 101u, new int[]{ 270 } }, + { 102u, new int[]{ 1799 } }, + { 103u, new int[]{ -138 } }, + { 104u, new int[]{ 24 } }, + { 105u, new int[]{ -65 } }, + { 106u, new int[]{ 25 } }, + { 107u, new int[]{ -20 } }, + { 108u, new int[]{ -21 } }, + { 109u, new int[]{ -23 } }, + { 110u, new int[]{ -22 } }, + { 111u, new int[]{ -498 } }, + { 115u, new int[]{ -184 } }, + { 116u, new int[]{ -185 } }, + { 117u, new int[]{ -186 } }, + { 118u, new int[]{ -187 } }, + { 119u, new int[]{ -188 } }, + { 120u, new int[]{ -183 } }, + { 126u, new int[]{ 26 } }, + { 127u, new int[]{ 27 } }, + { 128u, new int[]{ -98 } }, + { 130u, new int[]{ -51 } }, + { 131u, new int[]{ 62 } }, + { 133u, new int[]{ 790 } }, + { 134u, new int[]{ 789 } }, + { 135u, new int[]{ 304 } }, + { 136u, new int[]{ -27 } }, + { 137u, new int[]{ 3616 } }, + { 138u, new int[]{ -48 } }, + { 139u, new int[]{ 357 } }, + { 140u, new int[]{ 3504 } }, + { 141u, new int[]{ 287 } }, + { 142u, new int[]{ 286 } }, + { 143u, new int[]{ 2487 } }, + { 145u, new int[]{ 237 } }, + { 146u, new int[]{ 3605 } }, + { 147u, new int[]{ 1532 } }, + { 148u, new int[]{ 7816 } }, + { 149u, new int[]{ 560 } }, + { 150u, new int[]{ 2948 } }, + { 151u, new int[]{ 391 } }, + { 152u, new int[]{ 289 } }, + { 154u, new int[]{ 1281 } }, + { 155u, new int[]{ 1283 } }, + { 156u, new int[]{ 1282 } }, + { 157u, new int[]{ -28 } }, + { 158u, new int[]{ 1465 } }, + { 159u, new int[]{ -44 } }, + { 160u, new int[]{ 399 } }, + { 161u, new int[]{ 1745 } }, + { 162u, new int[]{ 5765 } }, + { 163u, new int[]{ 174 } }, + { 164u, new int[]{ 1205 } }, + { 165u, new int[]{ 655 } }, + { 166u, new int[]{ 4374 } }, + { 167u, new int[]{ 207 } }, + { 168u, new int[]{ 242 } }, + { 169u, new int[]{ 1508 } }, + { 170u, new int[]{ -29 } }, + { 171u, new int[]{ 2583 } }, + { 172u, new int[]{ 242 } }, + { 173u, new int[]{ -50 } }, + { 174u, new int[]{ 3609 } }, + { 175u, new int[]{ -260 } }, + { 176u, new int[]{ -30 } }, + { 177u, new int[]{ -31 } }, + { 178u, new int[]{ -32 } }, + { 179u, new int[]{ -33 } }, + { 180u, new int[]{ 788 } }, + { 181u, new int[]{ 1126 } }, + { 182u, new int[]{ -153 } }, + { 183u, new int[]{ -34 } }, + { 184u, new int[]{ -35 } }, + { 185u, new int[]{ -36 } }, + { 187u, new int[]{ -71 } }, + { 188u, new int[]{ 793 } }, + { 189u, new int[]{ 3031 } }, + { 190u, new int[]{ 341 } }, + { 192u, new int[]{ 292 } }, + { 193u, new int[]{ -37 } }, + { 194u, new int[]{ 294 } }, + { 195u, new int[]{ -38 } }, + { 196u, new int[]{ -72 } }, + { 197u, new int[]{ 1590 } }, + { 198u, new int[]{ 353 } }, + { 199u, new int[]{ 1612 } }, + { 201u, new int[]{ -19 } }, + { 202u, new int[]{ -59 } }, + { 203u, new int[]{ -60 } }, + { 204u, new int[]{ -24 } }, + { 205u, new int[]{ -61 } }, + { 206u, new int[]{ -62 } }, + { 207u, new int[]{ -810 } }, + { 213u, new int[]{ 269 } }, + { 214u, new int[]{ -39 } }, + { 215u, new int[]{ -40 } }, + { 216u, new int[]{ 980 } }, + { 217u, new int[]{ -73 } }, + { 218u, new int[]{ 1396 } }, + { 219u, new int[]{ -74 } }, + { 220u, new int[]{ -75 } }, + { 221u, new int[]{ -76 } }, + { 222u, new int[]{ -181 } }, + { 223u, new int[]{ -180 } }, + { 224u, new int[]{ 656 } }, + { 225u, new int[]{ 3438 } }, + { 226u, new int[]{ 1482 } }, + { 227u, new int[]{ 1872 } }, + { 228u, new int[]{ 4959 } }, + { 229u, new int[]{ 3586 } }, + { 230u, new int[]{ 3617 } }, + { 231u, new int[]{ 3486 } }, + { 232u, new int[]{ 4185 } }, + { 233u, new int[]{ 55 } }, + { 234u, new int[]{ -341 } }, + { 235u, new int[]{ -46 } }, + { 236u, new int[]{ 3789 } }, + { 237u, new int[]{ -94 } }, + { 238u, new int[]{ 290 } }, + { 239u, new int[]{ -209 } }, + { 240u, new int[]{ 1589 } }, + { 241u, new int[]{ 2290 } }, + { 242u, new int[]{ 7685 } }, + { 243u, new int[]{ 1417 } }, + { 244u, new int[]{ 3227 } }, + { 245u, new int[]{ 2161 } }, + { 247u, new int[]{ -45 } }, + { 248u, new int[]{ 3044 } }, + { 249u, new int[]{ 1804 } }, + { 250u, new int[]{ 732 } }, + { 251u, new int[]{ 2821 } }, + { 252u, new int[]{ 4958 } }, + { 256u, new int[]{ 1694 } }, + { 261u, new int[]{ 3046 } }, + { 263u, new int[]{ 712 } }, + { 264u, new int[]{ 404 } }, + { 265u, new int[]{ 236 } }, + { 266u, new int[]{ 272 } }, + { 267u, new int[]{ -41 } }, + { 268u, new int[]{ 307 } }, + { 269u, new int[]{ -573 } }, + { 270u, new int[]{ 308 } }, + { 271u, new int[]{ -819 } }, + { 272u, new int[]{ -104 } }, + { 273u, new int[]{ -105 } }, + { 276u, new int[]{ -702 } }, + { 277u, new int[]{ -64 } }, + { 279u, new int[]{ 563 } }, + { 280u, new int[]{ 360 } }, + { 281u, new int[]{ 1481 } }, + { 282u, new int[]{ -42 } }, + { 283u, new int[]{ 403 } }, + { 284u, new int[]{ -669 } }, + { 286u, new int[]{ -80 } }, + { 287u, new int[]{ -49 } }, + { 289u, new int[]{ -112 } }, + { 291u, new int[]{ -79 } }, + { 292u, new int[]{ -58 } }, + { 293u, new int[]{ -106 } }, + { 294u, new int[]{ -107 } }, + { 295u, new int[]{ 3482 } }, + { 296u, new int[]{ 1467 } }, + { 297u, new int[]{ 107 } }, + { 298u, new int[]{ 1186 } }, + { 301u, new int[]{ 1649 } }, + { 302u, new int[]{ -43 } }, + { 303u, new int[]{ 1644 } }, + { 304u, new int[]{ 1185 } }, + { 305u, new int[]{ 2505 } }, + { 306u, new int[]{ 1801 } }, + { 307u, new int[]{ -116 } }, + { 312u, new int[]{ -229 } }, + { 313u, new int[]{ 3330 } }, + { 315u, new int[]{ -249 } }, + { 316u, new int[]{ -118 } }, + { 317u, new int[]{ -143 } }, + { 318u, new int[]{ -119 } }, + { 319u, new int[]{ -120 } }, + { 320u, new int[]{ -121 } }, + { 321u, new int[]{ -122 } }, + { 322u, new int[]{ -123 } }, + { 323u, new int[]{ -124 } }, + { 324u, new int[]{ 2133 } }, + { 326u, new int[]{ 6052 } }, + { 327u, new int[]{ 7635 } }, + { 328u, new int[]{ 1535 } }, + { 329u, new int[]{ 2904 } }, + { 331u, new int[]{ 2121 } }, + { 332u, new int[]{ -157 } }, + { 333u, new int[]{ 2564 } }, + { 336u, new int[]{ -245 } }, + { 337u, new int[]{ 3204 } }, + { 338u, new int[]{ -299 } }, + { 340u, new int[]{ 8163 } }, + { 341u, new int[]{ 2213 } }, + { 342u, new int[]{ -862 } }, + { 344u, new int[]{ 3051 } }, + { 346u, new int[]{ -504 } }, + { 347u, new int[]{ 2815 } }, + { 350u, new int[]{ 3234 } }, + { 351u, new int[]{ 3231 } }, + { 352u, new int[]{ 3240 } }, + { 353u, new int[]{ -167 } }, + { 354u, new int[]{ 3047 } }, + { 355u, new int[]{ 638 } }, + { 356u, new int[]{ 640 } }, + { 357u, new int[]{ 1480 } }, + { 358u, new int[]{ -54 } }, + { 359u, new int[]{ -55 } }, + { 360u, new int[]{ 639 } }, + { 361u, new int[]{ 1478 } }, + { 362u, new int[]{ 637 } }, + { 363u, new int[]{ 658 } }, + { 364u, new int[]{ 643 } }, + { 365u, new int[]{ 1182 } }, + { 366u, new int[]{ 1570 } }, + { 367u, new int[]{ -56 } }, + { 368u, new int[]{ -110 } }, + { 370u, new int[]{ -110 } }, + { 371u, new int[]{ 2790 } }, + { 372u, new int[]{ 1850 } }, + { 373u, new int[]{ 784 } }, + { 374u, new int[]{ 644 } }, + { 375u, new int[]{ 1464 } }, + { 376u, new int[]{ 1852 } }, + { 377u, new int[]{ -57 } }, + { 378u, new int[]{ 634 } }, + { 379u, new int[]{ 1485 } }, + { 380u, new int[]{ -125 } }, + { 381u, new int[]{ 116 } }, + { 382u, new int[]{ 114 } }, + { 383u, new int[]{ 115 } }, + { 384u, new int[]{ 117 } }, + { 385u, new int[]{ 56 } }, + { 386u, new int[]{ 113 } }, + { 387u, new int[]{ 4340 } }, + { 388u, new int[]{ 4339 } }, + { 389u, new int[]{ -102 } }, + { 390u, new int[]{ -83 } }, + { 391u, new int[]{ -84 } }, + { 392u, new int[]{ -85 } }, + { 393u, new int[]{ -231 } }, + { 395u, new int[]{ 7554 } }, + { 396u, new int[]{ 2572 } }, + { 397u, new int[]{ -387 } }, + { 399u, new int[]{ -739 } }, + { 400u, new int[]{ 2214 } }, + { 401u, new int[]{ 2215 } }, + { 402u, new int[]{ -126 } }, + { 403u, new int[]{ -95 } }, + { 404u, new int[]{ -115 } }, + { 405u, new int[]{ -114 } }, + { 406u, new int[]{ -580 } }, + { 407u, new int[]{ 1398 } }, + { 408u, new int[]{ 1399 } }, + { 409u, new int[]{ 1401 } }, + { 410u, new int[]{ 4149 } }, + { 412u, new int[]{ 1400 } }, + { 413u, new int[]{ -664 } }, + { 414u, new int[]{ 4392 } }, + { 415u, new int[]{ 1402 } }, + { 416u, new int[]{ 1403 } }, + { 417u, new int[]{ 1404 } }, + { 418u, new int[]{ -207 } }, + { 453u, new int[]{ -63 } }, + { 477u, new int[]{ -129 } }, + { 478u, new int[]{ -96 } }, + { 485u, new int[]{ 437 } }, + { 486u, new int[]{ -67 } }, + { 487u, new int[]{ -68 } }, + { 488u, new int[]{ -69 } }, + { 489u, new int[]{ -70 } }, + { 490u, new int[]{ -81 } }, + { 491u, new int[]{ -93 } }, + { 492u, new int[]{ -86 } }, + { 493u, new int[]{ -87 } }, + { 494u, new int[]{ -91 } }, + { 495u, new int[]{ -92 } }, + { 496u, new int[]{ -89 } }, + { 497u, new int[]{ -90 } }, + { 498u, new int[]{ -88 } }, + { 503u, new int[]{ -111 } }, + { 508u, new int[]{ 1474 } }, + { 509u, new int[]{ 1463 } }, + { 511u, new int[]{ -99 } }, + { 512u, new int[]{ -132 } }, + { 513u, new int[]{ -133 } }, + { 514u, new int[]{ -103 } }, + { 516u, new int[]{ -100 } }, + { 517u, new int[]{ -117 } }, + { 518u, new int[]{ -97 } }, + { 519u, new int[]{ -137 } }, + { 521u, new int[]{ 1798 } }, + { 523u, new int[]{ -130 } }, + { 528u, new int[]{ -140 } }, + { 529u, new int[]{ -144 } }, + { 530u, new int[]{ -141 } }, + { 531u, new int[]{ -142 } }, + { 532u, new int[]{ -136 } }, + { 533u, new int[]{ -145 } }, + { 534u, new int[]{ -526 } }, + { 535u, new int[]{ -108 } }, + { 536u, new int[]{ -147 } }, + { 537u, new int[]{ 2148 } }, + { 538u, new int[]{ 1858 } }, + { 539u, new int[]{ -109 } }, + { 541u, new int[]{ -146 } }, + { 550u, new int[]{ -134 } }, + { 551u, new int[]{ -77 } }, + { 552u, new int[]{ -78 } }, + { 557u, new int[]{ -131 } }, + { 558u, new int[]{ 1466 } }, + { 559u, new int[]{ -127 } }, + { 560u, new int[]{ -128 } }, + { 562u, new int[]{ -139 } }, + { 563u, new int[]{ 2598 } }, + { 564u, new int[]{ 2966 } }, + { 565u, new int[]{ -460 } }, + { 566u, new int[]{ 4519 } }, + { 567u, new int[]{ 2838 } }, + { 570u, new int[]{ -234 } }, + { 572u, new int[]{ -264 } }, + { 574u, new int[]{ 2922 } }, + { 575u, new int[]{ -333 } }, + { 576u, new int[]{ -150 } }, + { 589u, new int[]{ -151 } }, + { 590u, new int[]{ -152 } }, + { 591u, new int[]{ -161 } }, + { 595u, new int[]{ 2262 } }, + { 596u, new int[]{ 2263 } }, + { 598u, new int[]{ -148 } }, + { 599u, new int[]{ -149 } }, + { 600u, new int[]{ 2935 } }, + { 601u, new int[]{ 2944 } }, + { 602u, new int[]{ 2000 } }, + { 603u, new int[]{ -182 } }, + { 604u, new int[]{ 2950 } }, + { 606u, new int[]{ 2260 } }, + { 608u, new int[]{ 4553 } }, + { 610u, new int[]{ 2249 } }, + { 611u, new int[]{ -101 } }, + { 612u, new int[]{ 2250 } }, + { 613u, new int[]{ -155 } }, + { 614u, new int[]{ -154 } }, + { 615u, new int[]{ -163 } }, + { 616u, new int[]{ -164 } }, + { 617u, new int[]{ 2329 } }, + { 618u, new int[]{ 2325 } }, + { 619u, new int[]{ -159 } }, + { 620u, new int[]{ -160 } }, + { 628u, new int[]{ 2331 } }, + { 629u, new int[]{ -156 } }, + { 630u, new int[]{ -165 } }, + { 631u, new int[]{ -111 } }, + { 632u, new int[]{ -166 } }, + { 633u, new int[]{ 3824 } }, + { 635u, new int[]{ -162 } }, + { 636u, new int[]{ -196 } }, + { 638u, new int[]{ 1876 } }, + { 640u, new int[]{ 2556 } }, + { 641u, new int[]{ 2547 } }, + { 642u, new int[]{ 2551 } }, + { 645u, new int[]{ 2580 } }, + { 646u, new int[]{ -171 } }, + { 648u, new int[]{ 2566 } }, + { 649u, new int[]{ -170 } }, + { 650u, new int[]{ -169 } }, + { 652u, new int[]{ -197 } }, + { 653u, new int[]{ 2560 } }, + { 654u, new int[]{ 3594 } }, + { 655u, new int[]{ 2609 } }, + { 656u, new int[]{ 2619 } }, + { 657u, new int[]{ 2620 } }, + { 658u, new int[]{ 2611 } }, + { 659u, new int[]{ 3469 } }, + { 660u, new int[]{ -218 } }, + { 661u, new int[]{ 2634 } }, + { 662u, new int[]{ -175 } }, + { 664u, new int[]{ -174 } }, + { 666u, new int[]{ -176 } }, + { 667u, new int[]{ -177 } }, + { 668u, new int[]{ -178 } }, + { 669u, new int[]{ -179 } }, + { 670u, new int[]{ -192 } }, + { 671u, new int[]{ -193 } }, + { 672u, new int[]{ -194 } }, + { 673u, new int[]{ -191 } }, + { 674u, new int[]{ 2597 } }, + { 676u, new int[]{ -168 } }, + { 677u, new int[]{ -195 } }, + { 678u, new int[]{ -198 } }, + { 679u, new int[]{ 2530 } }, + { 681u, new int[]{ -200 } }, + { 690u, new int[]{ 2574 } }, + { 691u, new int[]{ -172 } }, + { 695u, new int[]{ 2640 } }, + { 697u, new int[]{ -199 } }, + { 698u, new int[]{ -173 } }, + { 720u, new int[]{ -496 } }, + { 723u, new int[]{ -208 } }, + { 724u, new int[]{ 3597 } }, + { 725u, new int[]{ 2905 } }, + { 726u, new int[]{ -203 } }, + { 727u, new int[]{ 2890 } }, + { 728u, new int[]{ -202 } }, + { 729u, new int[]{ 2894 } }, + { 730u, new int[]{ 3629 } }, + { 731u, new int[]{ 2778 } }, + { 732u, new int[]{ -211 } }, + { 733u, new int[]{ 267 } }, + { 734u, new int[]{ 2860 } }, + { 735u, new int[]{ 2901 } }, + { 736u, new int[]{ -204 } }, + { 737u, new int[]{ -210 } }, + { 739u, new int[]{ -205 } }, + { 740u, new int[]{ 2895 } }, + { 741u, new int[]{ 3763 } }, + { 757u, new int[]{ 2827 } }, + { 758u, new int[]{ 2828 } }, + { 759u, new int[]{ -201 } }, + { 760u, new int[]{ 2830 } }, + { 761u, new int[]{ 2831 } }, + { 763u, new int[]{ -278 } }, + { 764u, new int[]{ 3045 } }, + { 766u, new int[]{ -228 } }, + { 767u, new int[]{ 792 } }, + { 768u, new int[]{ -212 } }, + { 769u, new int[]{ -220 } }, + { 772u, new int[]{ -216 } }, + { 775u, new int[]{ 3551 } }, + { 776u, new int[]{ 2796 } }, + { 777u, new int[]{ 2789 } }, + { 778u, new int[]{ 2792 } }, + { 779u, new int[]{ -224 } }, + { 780u, new int[]{ -217 } }, + { 781u, new int[]{ 2807 } }, + { 782u, new int[]{ -206 } }, + { 783u, new int[]{ -214 } }, + { 784u, new int[]{ -213 } }, + { 785u, new int[]{ -215 } }, + { 786u, new int[]{ -225 } }, + { 787u, new int[]{ -227 } }, + { 789u, new int[]{ -226 } }, + { 790u, new int[]{ -219 } }, + { 791u, new int[]{ -223 } }, + { 794u, new int[]{ 3511 } }, + { 815u, new int[]{ -222 } }, + { 819u, new int[]{ -221 } }, + { 822u, new int[]{ 3197 } }, + { 824u, new int[]{ 3014 } }, + { 826u, new int[]{ -233 } }, + { 827u, new int[]{ 3494 } }, + { 828u, new int[]{ 3912 } }, + { 829u, new int[]{ -236 } }, + { 830u, new int[]{ -237 } }, + { 831u, new int[]{ 3925 } }, + { 832u, new int[]{ 3574, 3575 } }, + { 833u, new int[]{ 3192 } }, + { 834u, new int[]{ 3608 } }, + { 835u, new int[]{ -230 } }, + { 837u, new int[]{ -268 } }, + { 838u, new int[]{ -266 } }, + { 839u, new int[]{ 3603 } }, + { 840u, new int[]{ 4163 } }, + { 841u, new int[]{ 3471 } }, + { 843u, new int[]{ 3063 } }, + { 844u, new int[]{ 3064 } }, + { 846u, new int[]{ 3193 } }, + { 847u, new int[]{ 3194 } }, + { 848u, new int[]{ -238 } }, + { 849u, new int[]{ -240 } }, + { 850u, new int[]{ -239 } }, + { 851u, new int[]{ -242 } }, + { 852u, new int[]{ -243 } }, + { 853u, new int[]{ -244 } }, + { 854u, new int[]{ -241 } }, + { 855u, new int[]{ 3215 } }, + { 856u, new int[]{ -247 } }, + { 857u, new int[]{ 3217 } }, + { 858u, new int[]{ 3212 } }, + { 859u, new int[]{ -248 } }, + { 860u, new int[]{ 3062 } }, + { 861u, new int[]{ 2999 } }, + { 863u, new int[]{ -255 } }, + { 864u, new int[]{ 3389 } }, + { 865u, new int[]{ -251 } }, + { 867u, new int[]{ -252 } }, + { 870u, new int[]{ 3485 } }, + { 871u, new int[]{ -256 } }, + { 872u, new int[]{ -263 } }, + { 873u, new int[]{ -254 } }, + { 874u, new int[]{ 3074 } }, + { 875u, new int[]{ -253 } }, + { 876u, new int[]{ 3075 } }, + { 877u, new int[]{ 3913 } }, + { 904u, new int[]{ 3922 } }, + { 905u, new int[]{ 3499 } }, + { 906u, new int[]{ -257 } }, + { 907u, new int[]{ -258 } }, + { 908u, new int[]{ -259 } }, + { 909u, new int[]{ 3065 } }, + { 910u, new int[]{ -250 } }, + { 913u, new int[]{ 3565 } }, + { 914u, new int[]{ 3577 } }, + { 915u, new int[]{ 3555 } }, + { 916u, new int[]{ 3541 } }, + { 917u, new int[]{ -275 } }, + { 918u, new int[]{ -307 } }, + { 919u, new int[]{ -384 } }, + { 925u, new int[]{ -289 } }, + { 927u, new int[]{ 3256 } }, + { 928u, new int[]{ -262 } }, + { 930u, new int[]{ 3373 } }, + { 931u, new int[]{ -235 } }, + { 935u, new int[]{ -261 } }, + { 936u, new int[]{ -269 } }, + { 937u, new int[]{ -269 } }, + { 941u, new int[]{ 1858 } }, + { 942u, new int[]{ 3620 } }, + { 943u, new int[]{ 3625 } }, + { 944u, new int[]{ 3481 } }, + { 945u, new int[]{ -357 } }, + { 946u, new int[]{ 3740 } }, + { 947u, new int[]{ -267 } }, + { 948u, new int[]{ -270 } }, + { 949u, new int[]{ 3593 } }, + { 950u, new int[]{ -297 } }, + { 951u, new int[]{ -332 } }, + { 952u, new int[]{ 3624 } }, + { 953u, new int[]{ 3726 } }, + { 954u, new int[]{ 3821 } }, + { 955u, new int[]{ 3526 } }, + { 957u, new int[]{ -308 } }, + { 960u, new int[]{ -246 } }, + { 962u, new int[]{ 3747 } }, + { 963u, new int[]{ -320 } }, + { 968u, new int[]{ 3243 } }, + { 970u, new int[]{ -265 } }, + { 971u, new int[]{ 3252 } }, + { 972u, new int[]{ 3229 } }, + { 974u, new int[]{ 3369 } }, + { 975u, new int[]{ 3649 } }, + { 976u, new int[]{ -302 } }, + { 977u, new int[]{ 3798 } }, + { 979u, new int[]{ -315 } }, + { 980u, new int[]{ -360 } }, + { 982u, new int[]{ 3845 } }, + { 983u, new int[]{ -335 } }, + { 984u, new int[]{ -290 } }, + { 985u, new int[]{ 4453 } }, + { 986u, new int[]{ 3923 } }, + { 994u, new int[]{ -284 } }, + { 995u, new int[]{ -285 } }, + { 996u, new int[]{ 3370 } }, + { 997u, new int[]{ 7862 } }, + { 998u, new int[]{ 7862 } }, + { 999u, new int[]{ -286 } }, + { 1000u, new int[]{ 3251 } }, + { 1001u, new int[]{ -279 } }, + { 1002u, new int[]{ -281 } }, + { 1003u, new int[]{ -282 } }, + { 1005u, new int[]{ 3244 } }, + { 1006u, new int[]{ 4271 } }, + { 1007u, new int[]{ -273 } }, + { 1008u, new int[]{ -272 } }, + { 1009u, new int[]{ -274 } }, + { 1010u, new int[]{ -280 } }, + { 1011u, new int[]{ -317 } }, + { 1012u, new int[]{ 3581 } }, + { 1013u, new int[]{ 3769 } }, + { 1014u, new int[]{ 3770 } }, + { 1015u, new int[]{ -283 } }, + { 1018u, new int[]{ -276 } }, + { 1020u, new int[]{ -275 } }, + { 1021u, new int[]{ -288 } }, + { 1025u, new int[]{ -277 } }, + { 1027u, new int[]{ 3666 } }, + { 1028u, new int[]{ 3667 } }, + { 1030u, new int[]{ -287 } }, + { 1032u, new int[]{ -271 } }, + { 1033u, new int[]{ 4660 } }, + { 1034u, new int[]{ 3542 } }, + { 1035u, new int[]{ 3402 } }, + { 1036u, new int[]{ 3568 } }, + { 1037u, new int[]{ 3392 } }, + { 1038u, new int[]{ 3495 } }, + { 1039u, new int[]{ -310 } }, + { 1040u, new int[]{ 3614 } }, + { 1041u, new int[]{ 3429 } }, + { 1042u, new int[]{ 3628 } }, + { 1043u, new int[]{ -807 } }, + { 1044u, new int[]{ 3556 } }, + { 1045u, new int[]{ 3613 } }, + { 1046u, new int[]{ 3506 } }, + { 1047u, new int[]{ 3823 } }, + { 1051u, new int[]{ 10099 } }, + { 1052u, new int[]{ 3615 } }, + { 1053u, new int[]{ 3612 } }, + { 1054u, new int[]{ -305 } }, + { 1055u, new int[]{ 3595 } }, + { 1056u, new int[]{ 3611 } }, + { 1057u, new int[]{ 3764 } }, + { 1058u, new int[]{ 3500 } }, + { 1060u, new int[]{ -330 } }, + { 1061u, new int[]{ -325 } }, + { 1062u, new int[]{ -327 } }, + { 1063u, new int[]{ -326 } }, + { 1064u, new int[]{ 3505 } }, + { 1065u, new int[]{ -525 } }, + { 1070u, new int[]{ -358 } }, + { 1071u, new int[]{ -354 } }, + { 1072u, new int[]{ 3791 } }, + { 1073u, new int[]{ 3579 } }, + { 1074u, new int[]{ -1073 } }, + { 1075u, new int[]{ 3607 } }, + { 1076u, new int[]{ 3835 } }, + { 1078u, new int[]{ 3796 } }, + { 1079u, new int[]{ 3797 } }, + { 1080u, new int[]{ 3846 } }, + { 1081u, new int[]{ -304 } }, + { 1082u, new int[]{ -316 } }, + { 1083u, new int[]{ 3817 } }, + { 1084u, new int[]{ 3748 } }, + { 1087u, new int[]{ -361 } }, + { 1088u, new int[]{ 4452 } }, + { 1089u, new int[]{ 4390 } }, + { 1092u, new int[]{ 3840 } }, + { 1093u, new int[]{ 3418 } }, + { 1094u, new int[]{ 3931 } }, + { 1095u, new int[]{ 3428 } }, + { 1096u, new int[]{ 3661 } }, + { 1097u, new int[]{ 3662 } }, + { 1098u, new int[]{ 3773 } }, + { 1099u, new int[]{ 3421 } }, + { 1105u, new int[]{ 3664 } }, + { 1106u, new int[]{ 3619 } }, + { 1111u, new int[]{ -363 } }, + { 1155u, new int[]{ 3525 } }, + { 1156u, new int[]{ 3847 } }, + { 1157u, new int[]{ 3562 } }, + { 1158u, new int[]{ 4354 } }, + { 1160u, new int[]{ -334 } }, + { 1181u, new int[]{ 3727 } }, + { 1182u, new int[]{ -355 } }, + { 1183u, new int[]{ -350 } }, + { 1185u, new int[]{ 4190 } }, + { 1187u, new int[]{ -359 } }, + { 1188u, new int[]{ 3654 } }, + { 1189u, new int[]{ 3655 } }, + { 1190u, new int[]{ -347 } }, + { 1191u, new int[]{ -349 } }, + { 1192u, new int[]{ -296 } }, + { 1193u, new int[]{ -319 } }, + { 1194u, new int[]{ 3803 } }, + { 1195u, new int[]{ -331 } }, + { 1196u, new int[]{ -344 } }, + { 1197u, new int[]{ -345 } }, + { 1198u, new int[]{ -303 } }, + { 1199u, new int[]{ -298 } }, + { 1200u, new int[]{ -300 } }, + { 1201u, new int[]{ -301 } }, + { 1202u, new int[]{ -311 } }, + { 1203u, new int[]{ -321 } }, + { 1204u, new int[]{ -313 } }, + { 1205u, new int[]{ -340 } }, + { 1207u, new int[]{ -336 } }, + { 1208u, new int[]{ -294 } }, + { 1209u, new int[]{ -324 } }, + { 1210u, new int[]{ 3626 } }, + { 1211u, new int[]{ -353 } }, + { 1214u, new int[]{ 4375 } }, + { 1216u, new int[]{ 4377 } }, + { 1217u, new int[]{ 4378 } }, + { 1218u, new int[]{ 4380 } }, + { 1219u, new int[]{ 4362 } }, + { 1220u, new int[]{ 4364 } }, + { 1221u, new int[]{ 4368 } }, + { 1222u, new int[]{ 4369 } }, + { 1223u, new int[]{ 4372 } }, + { 1224u, new int[]{ 4373 } }, + { 1225u, new int[]{ 4350 } }, + { 1226u, new int[]{ 4353 } }, + { 1227u, new int[]{ 4357 } }, + { 1229u, new int[]{ -346 } }, + { 1230u, new int[]{ -352 } }, + { 1231u, new int[]{ 5740 } }, + { 1232u, new int[]{ -339 } }, + { 1234u, new int[]{ 4626 } }, + { 1235u, new int[]{ -293 } }, + { 1236u, new int[]{ -292 } }, + { 1237u, new int[]{ -356 } }, + { 1238u, new int[]{ 3984 } }, + { 1239u, new int[]{ 3758 } }, + { 1240u, new int[]{ -306 } }, + { 1244u, new int[]{ -322 } }, + { 1245u, new int[]{ -323 } }, + { 1246u, new int[]{ -338 } }, + { 1266u, new int[]{ -291 } }, + { 1267u, new int[]{ -291 } }, + { 1268u, new int[]{ -291 } }, + { 1270u, new int[]{ 4658 } }, + { 1271u, new int[]{ 4609 } }, + { 1272u, new int[]{ 4631 } }, + { 1273u, new int[]{ 4622 } }, + { 1274u, new int[]{ 4656 } }, + { 1275u, new int[]{ 4624 } }, + { 1276u, new int[]{ 4620 } }, + { 1277u, new int[]{ 3983 } }, + { 1278u, new int[]{ -343 } }, + { 1281u, new int[]{ 4653 } }, + { 1283u, new int[]{ 3825 } }, + { 1288u, new int[]{ -413 } }, + { 1289u, new int[]{ -414 } }, + { 1290u, new int[]{ -362 } }, + { 1291u, new int[]{ -364 } }, + { 1292u, new int[]{ -365 } }, + { 1293u, new int[]{ 4564 } }, + { 1294u, new int[]{ -371 } }, + { 1295u, new int[]{ -465 } }, + { 1296u, new int[]{ -342 } }, + { 1297u, new int[]{ 4648 } }, + { 1298u, new int[]{ 4575 } }, + { 1299u, new int[]{ 4576 } }, + { 1300u, new int[]{ -329 } }, + { 1302u, new int[]{ 4528 } }, + { 1303u, new int[]{ 4511 } }, + { 1304u, new int[]{ 4561 } }, + { 1305u, new int[]{ 4239 } }, + { 1306u, new int[]{ 3554 } }, + { 1307u, new int[]{ -295 } }, + { 1308u, new int[]{ 4578 } }, + { 1309u, new int[]{ 4579 } }, + { 1310u, new int[]{ 4527 } }, + { 1311u, new int[]{ 4629 } }, + { 1312u, new int[]{ -368 } }, + { 1314u, new int[]{ -386 } }, + { 1315u, new int[]{ 4638 } }, + { 1316u, new int[]{ -388 } }, + { 1324u, new int[]{ 4171 } }, + { 1328u, new int[]{ -382 } }, + { 1338u, new int[]{ 4517 } }, + { 1339u, new int[]{ -381 } }, + { 1340u, new int[]{ -367 } }, + { 1341u, new int[]{ -366 } }, + { 1344u, new int[]{ -400 } }, + { 1345u, new int[]{ 4776 } }, + { 1346u, new int[]{ 4613 } }, + { 1347u, new int[]{ -375 } }, + { 1348u, new int[]{ -376 } }, + { 1349u, new int[]{ -369 } }, + { 1350u, new int[]{ -378 } }, + { 1351u, new int[]{ -374 } }, + { 1352u, new int[]{ -410 } }, + { 1353u, new int[]{ -404 } }, + { 1354u, new int[]{ -409 } }, + { 1355u, new int[]{ -408 } }, + { 1356u, new int[]{ -406 } }, + { 1357u, new int[]{ -411 } }, + { 1359u, new int[]{ -312 } }, + { 1360u, new int[]{ 4614 } }, + { 1361u, new int[]{ -379 } }, + { 1362u, new int[]{ 4655 } }, + { 1364u, new int[]{ -572 } }, + { 1365u, new int[]{ -505 } }, + { 1366u, new int[]{ -517 } }, + { 1367u, new int[]{ 4654 } }, + { 1368u, new int[]{ -380 } }, + { 1369u, new int[]{ -377 } }, + { 1371u, new int[]{ 4569 } }, + { 1372u, new int[]{ -373 } }, + { 1373u, new int[]{ -370 } }, + { 1374u, new int[]{ -372 } }, + { 1375u, new int[]{ 4813 } }, + { 1376u, new int[]{ 4811 } }, + { 1377u, new int[]{ 4812 } }, + { 1378u, new int[]{ 4659 } }, + { 1381u, new int[]{ 4657 } }, + { 1382u, new int[]{ -394 } }, + { 1404u, new int[]{ 6203 } }, + { 1405u, new int[]{ 4744 } }, + { 1406u, new int[]{ -425 } }, + { 1409u, new int[]{ 4745 } }, + { 1410u, new int[]{ -383 } }, + { 1414u, new int[]{ -396 } }, + { 1415u, new int[]{ 4625 } }, + { 1416u, new int[]{ 4642 } }, + { 1417u, new int[]{ -491 } }, + { 1418u, new int[]{ -564 } }, + { 1419u, new int[]{ -886 } }, + { 1420u, new int[]{ 4514 } }, + { 1421u, new int[]{ 4512 } }, + { 1424u, new int[]{ -495 } }, + { 1425u, new int[]{ -407 } }, + { 1426u, new int[]{ 4808 } }, + { 1427u, new int[]{ -528 } }, + { 1428u, new int[]{ -397 } }, + { 1429u, new int[]{ -499 } }, + { 1430u, new int[]{ 6700 } }, + { 1432u, new int[]{ -64 } }, + { 1433u, new int[]{ -395 } }, + { 1437u, new int[]{ -531 } }, + { 1438u, new int[]{ 5487 } }, + { 1439u, new int[]{ 5764 } }, + { 1443u, new int[]{ -403 } }, + { 1445u, new int[]{ -398 } }, + { 1448u, new int[]{ -402 } }, + { 1449u, new int[]{ -393 } }, + { 1450u, new int[]{ 4810 } }, + { 1451u, new int[]{ -392 } }, + { 1452u, new int[]{ 8241, 8241, 8241, 8241, 8241, 8241 } }, + { 1453u, new int[]{ -412 } }, + { 1471u, new int[]{ -405 } }, + { 1472u, new int[]{ -419 } }, + { 1473u, new int[]{ 3429 } }, + { 1474u, new int[]{ -423 } }, + { 1475u, new int[]{ -424 } }, + { 1478u, new int[]{ -532 } }, + { 1479u, new int[]{ -533 } }, + { 1480u, new int[]{ -421 } }, + { 1481u, new int[]{ -437 } }, + { 1482u, new int[]{ 8148 } }, + { 1483u, new int[]{ 3661 } }, + { 1487u, new int[]{ -415 } }, + { 1488u, new int[]{ -443 } }, + { 1489u, new int[]{ -385 } }, + { 1490u, new int[]{ 4954 } }, + { 1491u, new int[]{ 4871 } }, + { 1496u, new int[]{ -578 } }, + { 1498u, new int[]{ 4878 } }, + { 1499u, new int[]{ -401 } }, + { 1501u, new int[]{ -399 } }, + { 1502u, new int[]{ -416 } }, + { 1504u, new int[]{ -456 } }, + { 1506u, new int[]{ -420 } }, + { 1507u, new int[]{ -434 } }, + { 1508u, new int[]{ -435 } }, + { 1509u, new int[]{ -449 } }, + { 1510u, new int[]{ -389 } }, + { 1519u, new int[]{ -431 } }, + { 1521u, new int[]{ -390 } }, + { 1522u, new int[]{ -391 } }, + { 1523u, new int[]{ -422 } }, + { 1524u, new int[]{ 5669 } }, + { 1525u, new int[]{ 4939 } }, + { 1526u, new int[]{ -457 } }, + { 1527u, new int[]{ -458 } }, + { 1528u, new int[]{ -432 } }, + { 1529u, new int[]{ -440 } }, + { 1530u, new int[]{ -442 } }, + { 1531u, new int[]{ -1017 } }, + { 1533u, new int[]{ -426 } }, + { 1535u, new int[]{ -452 } }, + { 1536u, new int[]{ 5199 } }, + { 1537u, new int[]{ -466 } }, + { 1539u, new int[]{ 5567 } }, + { 1541u, new int[]{ -450 } }, + { 1542u, new int[]{ -451 } }, + { 1543u, new int[]{ -453 } }, + { 1561u, new int[]{ 5045 } }, + { 1562u, new int[]{ -430 } }, + { 1563u, new int[]{ -436 } }, + { 1564u, new int[]{ -448 } }, + { 1565u, new int[]{ 4904 } }, + { 1566u, new int[]{ -444 } }, + { 1567u, new int[]{ -445 } }, + { 1568u, new int[]{ -446 } }, + { 1571u, new int[]{ -417 } }, + { 1572u, new int[]{ 7757 } }, + { 1573u, new int[]{ -438 } }, + { 1574u, new int[]{ 2927 } }, + { 1575u, new int[]{ -441 } }, + { 1577u, new int[]{ 4869 } }, + { 1578u, new int[]{ -447 } }, + { 1581u, new int[]{ -439 } }, + { 1583u, new int[]{ -477 } }, + { 1584u, new int[]{ -468 } }, + { 1592u, new int[]{ 5219 } }, + { 1599u, new int[]{ -455 } }, + { 1600u, new int[]{ -427 } }, + { 1601u, new int[]{ -428 } }, + { 1602u, new int[]{ -418 } }, + { 1603u, new int[]{ 3923 } }, + { 1604u, new int[]{ 2263 } }, + { 1605u, new int[]{ 5186 } }, + { 1606u, new int[]{ -472 } }, + { 1607u, new int[]{ -467 } }, + { 1608u, new int[]{ -475 } }, + { 1609u, new int[]{ 5216 } }, + { 1610u, new int[]{ -492 } }, + { 1619u, new int[]{ -454 } }, + { 1620u, new int[]{ -464 } }, + { 1622u, new int[]{ -433 } }, + { 1623u, new int[]{ -429 } }, + { 1627u, new int[]{ -476 } }, + { 1628u, new int[]{ -470 } }, + { 1631u, new int[]{ 3429 } }, + { 1632u, new int[]{ 5751 } }, + { 1634u, new int[]{ -469 } }, + { 1637u, new int[]{ -484 } }, + { 1649u, new int[]{ -459 } }, + { 1654u, new int[]{ -519 } }, + { 1664u, new int[]{ -461 } }, + { 1665u, new int[]{ -462 } }, + { 1666u, new int[]{ -463 } }, + { 1667u, new int[]{ -486 } }, + { 1668u, new int[]{ 5356 } }, + { 1669u, new int[]{ -516 } }, + { 1670u, new int[]{ -488 } }, + { 1671u, new int[]{ -482 } }, + { 1672u, new int[]{ -489 } }, + { 1673u, new int[]{ -474 } }, + { 1674u, new int[]{ -609 } }, + { 1675u, new int[]{ 6385 } }, + { 1678u, new int[]{ -487 } }, + { 1679u, new int[]{ -518 } }, + { 1680u, new int[]{ -351 } }, + { 1681u, new int[]{ -507 } }, + { 1682u, new int[]{ -557 } }, + { 1683u, new int[]{ 5737 } }, + { 1684u, new int[]{ -562 } }, + { 1685u, new int[]{ -471 } }, + { 1686u, new int[]{ -473 } }, + { 1687u, new int[]{ -483 } }, + { 1703u, new int[]{ -478 } }, + { 1704u, new int[]{ -481 } }, + { 1705u, new int[]{ -480 } }, + { 1706u, new int[]{ -479 } }, + { 1709u, new int[]{ -608 } }, + { 1710u, new int[]{ -590 } }, + { 1711u, new int[]{ -607 } }, + { 1712u, new int[]{ -592 } }, + { 1714u, new int[]{ -559 } }, + { 1716u, new int[]{ -611 } }, + { 1717u, new int[]{ -541 } }, + { 1718u, new int[]{ 5775 } }, + { 1719u, new int[]{ 5735 } }, + { 1720u, new int[]{ -544 } }, + { 1721u, new int[]{ -563 } }, + { 1732u, new int[]{ -521 } }, + { 1733u, new int[]{ -509 } }, + { 1738u, new int[]{ -588 } }, + { 1739u, new int[]{ 7763 } }, + { 1740u, new int[]{ 5689, 5691, -587 } }, + { 1741u, new int[]{ -485 } }, + { 1744u, new int[]{ -493 } }, + { 1745u, new int[]{ -494 } }, + { 1746u, new int[]{ -497 } }, + { 1747u, new int[]{ -501 } }, + { 1748u, new int[]{ -502 } }, + { 1749u, new int[]{ -503 } }, + { 1750u, new int[]{ -500 } }, + { 1752u, new int[]{ 655 } }, + { 1753u, new int[]{ 6118 } }, + { 1754u, new int[]{ -594 } }, + { 1755u, new int[]{ 5631 } }, + { 1757u, new int[]{ -624 } }, + { 1759u, new int[]{ -560 } }, + { 1761u, new int[]{ -604 } }, + { 1764u, new int[]{ -523 } }, + { 1766u, new int[]{ 6089 } }, + { 1767u, new int[]{ 5709 } }, + { 1770u, new int[]{ -585 } }, + { 1771u, new int[]{ -540 } }, + { 1772u, new int[]{ -508 } }, + { 1774u, new int[]{ -490 } }, + { 1780u, new int[]{ -511 } }, + { 1793u, new int[]{ -660 } }, + { 1794u, new int[]{ -593 } }, + { 1795u, new int[]{ -514 } }, + { 1797u, new int[]{ -579 } }, + { 1798u, new int[]{ -613 } }, + { 1803u, new int[]{ -524 } }, + { 1805u, new int[]{ 6392 } }, + { 1807u, new int[]{ -543 } }, + { 1808u, new int[]{ -630 } }, + { 1811u, new int[]{ -515 } }, + { 1814u, new int[]{ 5736 } }, + { 1815u, new int[]{ -622 } }, + { 1816u, new int[]{ -623 } }, + { 1817u, new int[]{ -589 } }, + { 1818u, new int[]{ -635 } }, + { 1819u, new int[]{ -535 } }, + { 1820u, new int[]{ -522 } }, + { 1821u, new int[]{ 5756 } }, + { 1838u, new int[]{ -520 } }, + { 1841u, new int[]{ -512 } }, + { 1842u, new int[]{ -513 } }, + { 1843u, new int[]{ -506 } }, + { 1844u, new int[]{ -626 } }, + { 1845u, new int[]{ -527 } }, + { 1846u, new int[]{ -530 } }, + { 1847u, new int[]{ -627 } }, + { 1848u, new int[]{ -556 } }, + { 1849u, new int[]{ -536 } }, + { 1850u, new int[]{ -547 } }, + { 1851u, new int[]{ -545 } }, + { 1852u, new int[]{ -549 } }, + { 1853u, new int[]{ -538 } }, + { 1854u, new int[]{ -867 } }, + { 1861u, new int[]{ -510 } }, + { 1862u, new int[]{ -534 } }, + { 1863u, new int[]{ -659 } }, + { 1864u, new int[]{ 6038 } }, + { 1867u, new int[]{ -558 } }, + { 1869u, new int[]{ -554 } }, + { 1870u, new int[]{ -568 } }, + { 1872u, new int[]{ -642 } }, + { 1875u, new int[]{ -582 } }, + { 1877u, new int[]{ -654 } }, + { 1888u, new int[]{ -656 } }, + { 1889u, new int[]{ -612 } }, + { 1890u, new int[]{ -636 } }, + { 1891u, new int[]{ -617 } }, + { 1893u, new int[]{ -529 } }, + { 1901u, new int[]{ 5687 } }, + { 1904u, new int[]{ 5783 } }, + { 1905u, new int[]{ -567 } }, + { 1906u, new int[]{ -548 } }, + { 1907u, new int[]{ -551 } }, + { 1908u, new int[]{ -555 } }, + { 1909u, new int[]{ -638 } }, + { 1913u, new int[]{ -632 } }, + { 1914u, new int[]{ -633 } }, + { 1915u, new int[]{ -634 } }, + { 1923u, new int[]{ -537 } }, + { 1925u, new int[]{ -645 } }, + { 1926u, new int[]{ -615 } }, + { 1927u, new int[]{ 6910 } }, + { 1928u, new int[]{ -653 } }, + { 1929u, new int[]{ -553 } }, + { 1930u, new int[]{ -621 } }, + { 1931u, new int[]{ -569 } }, + { 1932u, new int[]{ -640 } }, + { 1934u, new int[]{ -625 } }, + { 1935u, new int[]{ -570 } }, + { 1936u, new int[]{ -599 } }, + { 1937u, new int[]{ -601 } }, + { 1938u, new int[]{ -600 } }, + { 1939u, new int[]{ -596 } }, + { 1940u, new int[]{ -597 } }, + { 1941u, new int[]{ -605 } }, + { 1943u, new int[]{ -552 } }, + { 1944u, new int[]{ -575 } }, + { 1945u, new int[]{ -649 } }, + { 1947u, new int[]{ -546 } }, + { 1948u, new int[]{ -565 } }, + { 1949u, new int[]{ -561 } }, + { 1950u, new int[]{ -566 } }, + { 1951u, new int[]{ -619 } }, + { 1957u, new int[]{ -591 } }, + { 1958u, new int[]{ -652 } }, + { 1959u, new int[]{ -614 } }, + { 1960u, new int[]{ -648 } }, + { 1961u, new int[]{ -583 } }, + { 1962u, new int[]{ -646 } }, + { 1963u, new int[]{ -647 } }, + { 1964u, new int[]{ -631 } }, + { 1965u, new int[]{ -629 } }, + { 1967u, new int[]{ -658 } }, + { 1973u, new int[]{ -574 } }, + { 1974u, new int[]{ 2142 } }, + { 1983u, new int[]{ 5782 } }, + { 1984u, new int[]{ -639 } }, + { 1985u, new int[]{ -643 } }, + { 1987u, new int[]{ 6392 } }, + { 1988u, new int[]{ -661 } }, + { 1989u, new int[]{ -542 } }, + { 1990u, new int[]{ -602 } }, + { 1991u, new int[]{ -606 } }, + { 1992u, new int[]{ -577 } }, + { 1993u, new int[]{ -603 } }, + { 1994u, new int[]{ 5722 } }, + { 1997u, new int[]{ -598 } }, + { 1998u, new int[]{ -628 } }, + { 2000u, new int[]{ 34 } }, + { 2001u, new int[]{ -584 } }, + { 2002u, new int[]{ -581 } }, + { 2003u, new int[]{ -637 } }, + { 2004u, new int[]{ -595 } }, + { 2005u, new int[]{ -586 } }, + { 2006u, new int[]{ -539 } }, + { 2007u, new int[]{ -616 } }, + { 2008u, new int[]{ -644 } }, + { 2009u, new int[]{ -676 } }, + { 2012u, new int[]{ -610 } }, + { 2013u, new int[]{ 6385 } }, + { 2014u, new int[]{ -662 } }, + { 2015u, new int[]{ -651 } }, + { 2018u, new int[]{ -657 } }, + { 2020u, new int[]{ -576 } }, + { 2023u, new int[]{ -641 } }, + { 2029u, new int[]{ -663 } }, + { 2036u, new int[]{ -571 } }, + { 2037u, new int[]{ -674 } }, + { 2038u, new int[]{ -679 } }, + { 2039u, new int[]{ -700 } }, + { 2040u, new int[]{ -666 } }, + { 2041u, new int[]{ -667 } }, + { 2042u, new int[]{ -683 } }, + { 2043u, new int[]{ -686 } }, + { 2044u, new int[]{ -711 } }, + { 2045u, new int[]{ -708 } }, + { 2054u, new int[]{ -618 } }, + { 2055u, new int[]{ -620 } }, + { 2057u, new int[]{ -668 } }, + { 2058u, new int[]{ -550 } }, + { 2060u, new int[]{ -650 } }, + { 2062u, new int[]{ -670 } }, + { 2063u, new int[]{ -671 } }, + { 2064u, new int[]{ -672 } }, + { 2070u, new int[]{ 3047 } }, + { 2075u, new int[]{ -684 } }, + { 2076u, new int[]{ -720 } }, + { 2077u, new int[]{ 7435 } }, + { 2080u, new int[]{ -665 } }, + { 2081u, new int[]{ -655 } }, + { 2082u, new int[]{ -680 } }, + { 2083u, new int[]{ -685 } }, + { 2084u, new int[]{ -677 } }, + { 2085u, new int[]{ -699 } }, + { 2086u, new int[]{ -698 } }, + { 2087u, new int[]{ 6903 } }, + { 2088u, new int[]{ -696 } }, + { 2089u, new int[]{ -697 } }, + { 2090u, new int[]{ -682 } }, + { 2091u, new int[]{ -675 } }, + { 2092u, new int[]{ -681 } }, + { 2093u, new int[]{ -688 } }, + { 2103u, new int[]{ -678 } }, + { 2107u, new int[]{ -689 } }, + { 2108u, new int[]{ -690 } }, + { 2109u, new int[]{ -691 } }, + { 2110u, new int[]{ -693 } }, + { 2111u, new int[]{ -694 } }, + { 2113u, new int[]{ -695 } }, + { 2121u, new int[]{ 7092 } }, + { 2122u, new int[]{ -692 } }, + { 2127u, new int[]{ 6996 } }, + { 2128u, new int[]{ -849 } }, + { 2134u, new int[]{ -709 } }, + { 2136u, new int[]{ 7127 } }, + { 2137u, new int[]{ -732 } }, + { 2138u, new int[]{ -712 } }, + { 2145u, new int[]{ -704 } }, + { 2146u, new int[]{ -705 } }, + { 2147u, new int[]{ -735 } }, + { 2148u, new int[]{ -687 } }, + { 2150u, new int[]{ -703 } }, + { 2151u, new int[]{ -710 } }, + { 2152u, new int[]{ -724 } }, + { 2153u, new int[]{ -729 } }, + { 2154u, new int[]{ 2665 } }, + { 2158u, new int[]{ -717 } }, + { 2159u, new int[]{ -718 } }, + { 2160u, new int[]{ -726 } }, + { 2161u, new int[]{ -701 } }, + { 2162u, new int[]{ -715 } }, + { 2163u, new int[]{ -722 } }, + { 2164u, new int[]{ -725 } }, + { 2165u, new int[]{ -731 } }, + { 2166u, new int[]{ -713 } }, + { 2171u, new int[]{ -760 } }, + { 2172u, new int[]{ -764 } }, + { 2177u, new int[]{ -716 } }, + { 2181u, new int[]{ -714 } }, + { 2183u, new int[]{ -727 } }, + { 2184u, new int[]{ -733 } }, + { 2185u, new int[]{ -765 } }, + { 2189u, new int[]{ -719 } }, + { 2190u, new int[]{ -723 } }, + { 2192u, new int[]{ -707 } }, + { 2193u, new int[]{ -759 } }, + { 2196u, new int[]{ -755 } }, + { 2197u, new int[]{ -749 } }, + { 2199u, new int[]{ -721 } }, + { 2200u, new int[]{ -730 } }, + { 2203u, new int[]{ -728 } }, + { 2210u, new int[]{ 7225 } }, + { 2211u, new int[]{ -738 } }, + { 2212u, new int[]{ -751 } }, + { 2213u, new int[]{ -812 } }, + { 2214u, new int[]{ -813 } }, + { 2215u, new int[]{ -814 } }, + { 2216u, new int[]{ -771 } }, + { 2217u, new int[]{ -737 } }, + { 2219u, new int[]{ -706 } }, + { 2222u, new int[]{ -734 } }, + { 2223u, new int[]{ 7440 } }, + { 2233u, new int[]{ -548 } }, + { 2234u, new int[]{ -756 } }, + { 2235u, new int[]{ -757 } }, + { 2236u, new int[]{ -746 } }, + { 2237u, new int[]{ -770 } }, + { 2248u, new int[]{ -745 } }, + { 2259u, new int[]{ -788 } }, + { 2260u, new int[]{ -774 } }, + { 2262u, new int[]{ -753 } }, + { 2265u, new int[]{ -754 } }, + { 2266u, new int[]{ -773 } }, + { 2267u, new int[]{ -747 } }, + { 2269u, new int[]{ -758 } }, + { 2270u, new int[]{ -741 } }, + { 2271u, new int[]{ -740 } }, + { 2273u, new int[]{ -750 } }, + { 2275u, new int[]{ -766 } }, + { 2276u, new int[]{ 1644 } }, + { 2277u, new int[]{ 1185 } }, + { 2278u, new int[]{ 1801 } }, + { 2279u, new int[]{ -767 } }, + { 2281u, new int[]{ -736 } }, + { 2283u, new int[]{ -748 } }, + { 2286u, new int[]{ -742 } }, + { 2287u, new int[]{ -769 } }, + { 2291u, new int[]{ -743 } }, + { 2294u, new int[]{ 7647 } }, + { 2295u, new int[]{ 7648 } }, + { 2297u, new int[]{ 7659 } }, + { 2299u, new int[]{ -763 } }, + { 2300u, new int[]{ -762 } }, + { 2301u, new int[]{ -761 } }, + { 2302u, new int[]{ 7669 } }, + { 2308u, new int[]{ -752 } }, + { 2313u, new int[]{ -777 } }, + { 2314u, new int[]{ -777 } }, + { 2317u, new int[]{ -744 } }, + { 2320u, new int[]{ -800 } }, + { 2322u, new int[]{ -802 } }, + { 2323u, new int[]{ -801 } }, + { 2333u, new int[]{ 7675 } }, + { 2334u, new int[]{ -776 } }, + { 2335u, new int[]{ -780 } }, + { 2336u, new int[]{ -58 } }, + { 2338u, new int[]{ 7939 } }, + { 2339u, new int[]{ -806 } }, + { 2345u, new int[]{ -789 } }, + { 2346u, new int[]{ -790 } }, + { 2347u, new int[]{ -791 } }, + { 2348u, new int[]{ -792 } }, + { 2349u, new int[]{ -793 } }, + { 2350u, new int[]{ -794 } }, + { 2351u, new int[]{ -795 } }, + { 2352u, new int[]{ -796 } }, + { 2353u, new int[]{ -786 } }, + { 2354u, new int[]{ -787 } }, + { 2355u, new int[]{ -779 } }, + { 2357u, new int[]{ 7662 } }, + { 2358u, new int[]{ 7731 } }, + { 2363u, new int[]{ 2665 } }, + { 2364u, new int[]{ -782 } }, + { 2365u, new int[]{ 7702 } }, + { 2366u, new int[]{ -783 } }, + { 2367u, new int[]{ -775 } }, + { 2368u, new int[]{ -785 } }, + { 2369u, new int[]{ -768 } }, + { 2371u, new int[]{ 7667 } }, + { 2372u, new int[]{ 7727 } }, + { 2373u, new int[]{ 8063 } }, + { 2374u, new int[]{ -797 } }, + { 2375u, new int[]{ -798 } }, + { 2376u, new int[]{ 7678 } }, + { 2377u, new int[]{ -778 } }, + { 2378u, new int[]{ -804 } }, + { 2379u, new int[]{ -803 } }, + { 2380u, new int[]{ -805 } }, + { 2382u, new int[]{ 7954 } }, + { 2390u, new int[]{ -815 } }, + { 2391u, new int[]{ -809 } }, + { 2393u, new int[]{ -818 } }, + { 2394u, new int[]{ 10217, 10217 } }, + { 2395u, new int[]{ -868 } }, + { 2396u, new int[]{ 8543 } }, + { 2397u, new int[]{ -873 } }, + { 2398u, new int[]{ -822 } }, + { 2399u, new int[]{ 7976 } }, + { 2400u, new int[]{ -811 } }, + { 2402u, new int[]{ -824 } }, + { 2403u, new int[]{ 7922 } }, + { 2407u, new int[]{ -825 } }, + { 2408u, new int[]{ -834 } }, + { 2410u, new int[]{ -829 } }, + { 2419u, new int[]{ -871 } }, + { 2420u, new int[]{ -833 } }, + { 2422u, new int[]{ -826 } }, + { 2423u, new int[]{ 8129 } }, + { 2424u, new int[]{ 8061 } }, + { 2425u, new int[]{ -832 } }, + { 2427u, new int[]{ -781 } }, + { 2431u, new int[]{ 8504 } }, + { 2432u, new int[]{ -872 } }, + { 2433u, new int[]{ 8499 } }, + { 2434u, new int[]{ -852 } }, + { 2435u, new int[]{ -857 } }, + { 2436u, new int[]{ -799 } }, + { 2438u, new int[]{ -816 } }, + { 2439u, new int[]{ -772 } }, + { 2440u, new int[]{ -827 } }, + { 2445u, new int[]{ 8547 } }, + { 2446u, new int[]{ -784 } }, + { 2447u, new int[]{ 4638 } }, + { 2448u, new int[]{ -831 } }, + { 2453u, new int[]{ 8060 } }, + { 2454u, new int[]{ -919 } }, + { 2455u, new int[]{ 8374 } }, + { 2456u, new int[]{ -903 } }, + { 2457u, new int[]{ -904 } }, + { 2460u, new int[]{ -823 } }, + { 2462u, new int[]{ 8264, 8264, 8264 } }, + { 2469u, new int[]{ -820 } }, + { 2470u, new int[]{ -847 } }, + { 2473u, new int[]{ -835 } }, + { 2474u, new int[]{ 8361 } }, + { 2475u, new int[]{ -82 } }, + { 2476u, new int[]{ 8143 } }, + { 2477u, new int[]{ -318 } }, + { 2478u, new int[]{ -900 } }, + { 2479u, new int[]{ -817 } }, + { 2481u, new int[]{ -821 } }, + { 2487u, new int[]{ -911 } }, + { 2488u, new int[]{ 8544 } }, + { 2489u, new int[]{ 8250 } }, + { 2497u, new int[]{ -836 } }, + { 2498u, new int[]{ 4896 } }, + { 2499u, new int[]{ -843 } }, + { 2500u, new int[]{ -841 } }, + { 2501u, new int[]{ -842 } }, + { 2502u, new int[]{ 7955 } }, + { 2512u, new int[]{ 7981 } }, + { 2513u, new int[]{ 8225 } }, + { 2514u, new int[]{ -892 } }, + { 2515u, new int[]{ 8297 } }, + { 2519u, new int[]{ 9493 } }, + { 2521u, new int[]{ -848 } }, + { 2522u, new int[]{ 8232 } }, + { 2524u, new int[]{ 8263 } }, + { 2525u, new int[]{ -863 } }, + { 2527u, new int[]{ -906 } }, + { 2528u, new int[]{ -907 } }, + { 2529u, new int[]{ -910 } }, + { 2530u, new int[]{ 8341 } }, + { 2531u, new int[]{ -858 } }, + { 2532u, new int[]{ 8231 } }, + { 2533u, new int[]{ -901 } }, + { 2534u, new int[]{ 8501 } }, + { 2535u, new int[]{ 8252 } }, + { 2537u, new int[]{ 8500 } }, + { 2538u, new int[]{ -840 } }, + { 2541u, new int[]{ -828 } }, + { 2545u, new int[]{ -830 } }, + { 2547u, new int[]{ -839 } }, + { 2548u, new int[]{ -838 } }, + { 2549u, new int[]{ -837 } }, + { 2550u, new int[]{ -328 } }, + { 2551u, new int[]{ 7929 } }, + { 2552u, new int[]{ -328 } }, + { 2558u, new int[]{ 8155 } }, + { 2559u, new int[]{ 8271 } }, + { 2560u, new int[]{ -882 } }, + { 2561u, new int[]{ -885 } }, + { 2562u, new int[]{ -884 } }, + { 2563u, new int[]{ 8167 } }, + { 2569u, new int[]{ 8221 } }, + { 2570u, new int[]{ 8262, 8262, 8262 } }, + { 2571u, new int[]{ -856 } }, + { 2572u, new int[]{ 8236 } }, + { 2573u, new int[]{ 8355 } }, + { 2574u, new int[]{ 8578 } }, + { 2575u, new int[]{ -879 } }, + { 2576u, new int[]{ 8352 } }, + { 2577u, new int[]{ 8185 } }, + { 2578u, new int[]{ -853 } }, + { 2579u, new int[]{ -888 } }, + { 2580u, new int[]{ -854 } }, + { 2581u, new int[]{ 9440 } }, + { 2582u, new int[]{ 8822 } }, + { 2585u, new int[]{ 8234 } }, + { 2586u, new int[]{ 8272 } }, + { 2587u, new int[]{ 8569 } }, + { 2588u, new int[]{ 8619 } }, + { 2589u, new int[]{ -881 } }, + { 2590u, new int[]{ -874 } }, + { 2591u, new int[]{ -875 } }, + { 2592u, new int[]{ 8201 } }, + { 2593u, new int[]{ 8653 } }, + { 2595u, new int[]{ 8574 } }, + { 2597u, new int[]{ 8899 } }, + { 2598u, new int[]{ 8912 } }, + { 2599u, new int[]{ 8655 } }, + { 2612u, new int[]{ 8165 } }, + { 2613u, new int[]{ 8260 } }, + { 2615u, new int[]{ -898 } }, + { 2616u, new int[]{ 8357 } }, + { 2618u, new int[]{ 8230 } }, + { 2619u, new int[]{ 8227 } }, + { 2620u, new int[]{ 3204 } }, + { 2621u, new int[]{ 4269 } }, + { 2622u, new int[]{ -899 } }, + { 2623u, new int[]{ -883 } }, + { 2624u, new int[]{ 2906 } }, + { 2625u, new int[]{ 8292 } }, + { 2626u, new int[]{ -328 } }, + { 2628u, new int[]{ -897 } }, + { 2629u, new int[]{ 8266 } }, + { 2631u, new int[]{ -846 } }, + { 2632u, new int[]{ -851 } }, + { 2633u, new int[]{ 8265 } }, + { 2634u, new int[]{ 8224 } }, + { 2635u, new int[]{ -845 } }, + { 2636u, new int[]{ 8910 } }, + { 2637u, new int[]{ 8193 } }, + { 2639u, new int[]{ 8270 } }, + { 2640u, new int[]{ 5631 } }, + { 2641u, new int[]{ -859 } }, + { 2642u, new int[]{ -893 } }, + { 2643u, new int[]{ 8246 } }, + { 2644u, new int[]{ -339 } }, + { 2647u, new int[]{ -891 } }, + { 2648u, new int[]{ -864 } }, + { 2649u, new int[]{ -50 } }, + { 2650u, new int[]{ -880 } }, + { 2651u, new int[]{ 8622 } }, + { 2652u, new int[]{ 8576 } }, + { 2653u, new int[]{ 8575 } }, + { 2654u, new int[]{ -339 } }, + { 2655u, new int[]{ 8182 } }, + { 2656u, new int[]{ -895 } }, + { 2657u, new int[]{ -314 } }, + { 2658u, new int[]{ 8502 } }, + { 2659u, new int[]{ 8548 } }, + { 2660u, new int[]{ 8577 } }, + { 2661u, new int[]{ -870 } }, + { 2662u, new int[]{ -914 } }, + { 2663u, new int[]{ -890 } }, + { 2664u, new int[]{ -908 } }, + { 2665u, new int[]{ 750 } }, + { 2667u, new int[]{ -905 } }, + { 2668u, new int[]{ -921 } }, + { 2669u, new int[]{ 6738 } }, + { 2670u, new int[]{ -920 } }, + { 2671u, new int[]{ 8261 } }, + { 2672u, new int[]{ 8503 } }, + { 2673u, new int[]{ -887 } }, + { 2674u, new int[]{ 8550 } }, + { 2675u, new int[]{ 750 } }, + { 2676u, new int[]{ 750 } }, + { 2682u, new int[]{ 750 } }, + { 2683u, new int[]{ 750 } }, + { 2684u, new int[]{ 8621 } }, + { 2685u, new int[]{ 8175 } }, + { 2686u, new int[]{ 8268, 8268, 8268, 8268, 8268, 8268, 8268, 8268 } }, + { 2696u, new int[]{ -894 } }, + { 2697u, new int[]{ -328 } }, + { 2699u, new int[]{ -808 } }, + { 2701u, new int[]{ 8341 } }, + { 2702u, new int[]{ -865 } }, + { 2705u, new int[]{ -861 } }, + { 2706u, new int[]{ -328 } }, + { 2707u, new int[]{ -869 } }, + { 2708u, new int[]{ -909 } }, + { 2709u, new int[]{ -896 } }, + { 2710u, new int[]{ -844 } }, + { 2711u, new int[]{ -994 } }, + { 2712u, new int[]{ -912 } }, + { 2719u, new int[]{ -673 } }, + { 2721u, new int[]{ -878 } }, + { 2722u, new int[]{ -913 } }, + { 2724u, new int[]{ -877 } }, + { 2732u, new int[]{ 8549 } }, + { 2734u, new int[]{ -860 } }, + { 2735u, new int[]{ 8210 } }, + { 2736u, new int[]{ -855 } }, + { 2737u, new int[]{ -850 } }, + { 2738u, new int[]{ 8789 } }, + { 2739u, new int[]{ -924 } }, + { 2740u, new int[]{ -902 } }, + { 2743u, new int[]{ 9147 } }, + { 2744u, new int[]{ -889 } }, + { 2745u, new int[]{ -915 } }, + { 2746u, new int[]{ -916 } }, + { 2747u, new int[]{ 3044 } }, + { 2748u, new int[]{ -917 } }, + { 2749u, new int[]{ 8916 } }, + { 2751u, new int[]{ -328 } }, + { 2763u, new int[]{ -866 } }, + { 2764u, new int[]{ 8822 } }, + { 2767u, new int[]{ -922 } }, + { 2768u, new int[]{ -923 } }, + { 2770u, new int[]{ -918 } }, + { 2776u, new int[]{ -876 } }, + { 2777u, new int[]{ 8561 } }, + { 2778u, new int[]{ 8528 } }, + { 2780u, new int[]{ 8619 } }, + { 2782u, new int[]{ -953 } }, + { 2783u, new int[]{ -939 } }, + { 2784u, new int[]{ -954 } }, + { 2785u, new int[]{ -930 } }, + { 2786u, new int[]{ -936 } }, + { 2787u, new int[]{ -937 } }, + { 2788u, new int[]{ -938 } }, + { 2789u, new int[]{ -931 } }, + { 2790u, new int[]{ -926 } }, + { 2791u, new int[]{ -940 } }, + { 2792u, new int[]{ -943 } }, + { 2794u, new int[]{ -933 } }, + { 2795u, new int[]{ -944 } }, + { 2796u, new int[]{ -957 } }, + { 2797u, new int[]{ -971 } }, + { 2804u, new int[]{ -955 } }, + { 2806u, new int[]{ 9189 } }, + { 2807u, new int[]{ 9405 } }, + { 2809u, new int[]{ -942 } }, + { 2811u, new int[]{ -934 } }, + { 2812u, new int[]{ -935 } }, + { 2813u, new int[]{ -932 } }, + { 2814u, new int[]{ -959 } }, + { 2815u, new int[]{ -328 } }, + { 2816u, new int[]{ -996 } }, + { 2817u, new int[]{ -925 } }, + { 2821u, new int[]{ -929 } }, + { 2822u, new int[]{ -947 } }, + { 2823u, new int[]{ -928 } }, + { 2824u, new int[]{ -941 } }, + { 2825u, new int[]{ -948 } }, + { 2826u, new int[]{ -949 } }, + { 2827u, new int[]{ -950 } }, + { 2828u, new int[]{ -945 } }, + { 2829u, new int[]{ -927 } }, + { 2830u, new int[]{ -998 } }, + { 2831u, new int[]{ -1005 } }, + { 2833u, new int[]{ -328 } }, + { 2834u, new int[]{ -328 } }, + { 2835u, new int[]{ -328 } }, + { 2836u, new int[]{ -328 } }, + { 2837u, new int[]{ -328 } }, + { 2840u, new int[]{ 8247 } }, + { 2842u, new int[]{ -951 } }, + { 2845u, new int[]{ -952 } }, + { 2846u, new int[]{ -946 } }, + { 2850u, new int[]{ -1058 } }, + { 2851u, new int[]{ 9178 } }, + { 2852u, new int[]{ -940 } }, + { 2854u, new int[]{ -984 } }, + { 2864u, new int[]{ -964 } }, + { 2865u, new int[]{ -965 } }, + { 2866u, new int[]{ -967 } }, + { 2867u, new int[]{ -956 } }, + { 2872u, new int[]{ -961 } }, + { 2873u, new int[]{ -958 } }, + { 2874u, new int[]{ -969 } }, + { 2875u, new int[]{ -968 } }, + { 2876u, new int[]{ -1004 } }, + { 2877u, new int[]{ -977 } }, + { 2878u, new int[]{ -966 } }, + { 2879u, new int[]{ -960 } }, + { 2880u, new int[]{ -979 } }, + { 2882u, new int[]{ -1074 } }, + { 2883u, new int[]{ -972 } }, + { 2884u, new int[]{ -976 } }, + { 2885u, new int[]{ -978 } }, + { 2886u, new int[]{ -970 } }, + { 2887u, new int[]{ -962 } }, + { 2888u, new int[]{ -963 } }, + { 2889u, new int[]{ -973 } }, + { 2902u, new int[]{ -991, 9353 } }, + { 2904u, new int[]{ -999 } }, + { 2905u, new int[]{ -981 } }, + { 2906u, new int[]{ -980 } }, + { 2911u, new int[]{ -989 } }, + { 2912u, new int[]{ -983 } }, + { 2917u, new int[]{ -985 } }, + { 2918u, new int[]{ -986 } }, + { 2919u, new int[]{ -987 } }, + { 2920u, new int[]{ -974 } }, + { 2921u, new int[]{ -992 } }, + { 2922u, new int[]{ 2190 } }, + { 2924u, new int[]{ -993 } }, + { 2925u, new int[]{ -1014 } }, + { 2926u, new int[]{ 9398 } }, + { 2927u, new int[]{ 9396 } }, + { 2928u, new int[]{ 9424 } }, + { 2929u, new int[]{ 9439 } }, + { 2930u, new int[]{ -1002 } }, + { 2931u, new int[]{ -988 } }, + { 2932u, new int[]{ -990 } }, + { 2933u, new int[]{ -1007 } }, + { 2942u, new int[]{ -975 } }, + { 2943u, new int[]{ -982 } }, + { 2945u, new int[]{ 9452 } }, + { 2946u, new int[]{ -328 } }, + { 2947u, new int[]{ -995 } }, + { 2954u, new int[]{ -995 } }, + { 2955u, new int[]{ -995 } }, + { 2956u, new int[]{ -328 } }, + { 2957u, new int[]{ 9642 } }, + { 2958u, new int[]{ -1003 } }, + { 2959u, new int[]{ 9650 } }, + { 2960u, new int[]{ -328 } }, + { 2961u, new int[]{ 8235 } }, + { 2962u, new int[]{ 9652 } }, + { 2964u, new int[]{ 9644 } }, + { 2966u, new int[]{ -1023 } }, + { 2967u, new int[]{ -1042 } }, + { 2968u, new int[]{ 9511 } }, + { 2970u, new int[]{ -1011 } }, + { 2971u, new int[]{ -1000 } }, + { 2972u, new int[]{ -1013 } }, + { 2973u, new int[]{ -1010 } }, + { 2997u, new int[]{ -652 } }, + { 2998u, new int[]{ -583 } }, + { 2999u, new int[]{ -1009 } }, + { 3004u, new int[]{ -1016 } }, + { 3014u, new int[]{ -1018 } }, + { 3015u, new int[]{ -1019 } }, + { 3017u, new int[]{ 9417 } }, + { 3019u, new int[]{ -1008 } }, + { 3020u, new int[]{ -1012 } }, + { 3021u, new int[]{ 9707 } }, + { 3022u, new int[]{ -1031 } }, + { 3023u, new int[]{ 9769 } }, + { 3024u, new int[]{ -1038 } }, + { 3025u, new int[]{ -1006 } }, + { 3026u, new int[]{ -997 } }, + { 3027u, new int[]{ 9853 } }, + { 3029u, new int[]{ -1001 } }, + { 3030u, new int[]{ -1015 } }, + { 3041u, new int[]{ 5671 } }, + { 3048u, new int[]{ -1033 } }, + { 3053u, new int[]{ -1060 } }, + { 3054u, new int[]{ 9741 } }, + { 3056u, new int[]{ 9953, 9953 } }, + { 3057u, new int[]{ 9832 } }, + { 3058u, new int[]{ 9908 } }, + { 3059u, new int[]{ 9819 } }, + { 3060u, new int[]{ 9818 } }, + { 3061u, new int[]{ -1061 } }, + { 3062u, new int[]{ -1025 } }, + { 3063u, new int[]{ 10077 } }, + { 3064u, new int[]{ -1030 } }, + { 3065u, new int[]{ -1030 } }, + { 3066u, new int[]{ 9714 } }, + { 3067u, new int[]{ 9720 } }, + { 3068u, new int[]{ 9722 } }, + { 3069u, new int[]{ 9726 } }, + { 3070u, new int[]{ -1027 } }, + { 3071u, new int[]{ -1043 } }, + { 3072u, new int[]{ -1045 } }, + { 3073u, new int[]{ -1044 } }, + { 3074u, new int[]{ -1020 } }, + { 3075u, new int[]{ -1021 } }, + { 3076u, new int[]{ -1051 } }, + { 3077u, new int[]{ -1052 } }, + { 3078u, new int[]{ -1053 } }, + { 3079u, new int[]{ -1054 } }, + { 3081u, new int[]{ 10075 } }, + { 3082u, new int[]{ -1064 } }, + { 3083u, new int[]{ 10037 } }, + { 3084u, new int[]{ -1059 } }, + { 3087u, new int[]{ -1032 } }, + { 3089u, new int[]{ -1057 } }, + { 3090u, new int[]{ -1055 } }, + { 3091u, new int[]{ -1046 } }, + { 3092u, new int[]{ -1028 } }, + { 3093u, new int[]{ -1029 } }, + { 3094u, new int[]{ -1036 } }, + { 3095u, new int[]{ -1039 } }, + { 3096u, new int[]{ -1039 } }, + { 3097u, new int[]{ -1040 } }, + { 3098u, new int[]{ -1041 } }, + { 3112u, new int[]{ -1062 } }, + { 3113u, new int[]{ -1063 } }, + { 3114u, new int[]{ -1063 } }, + { 3116u, new int[]{ -1024 } }, + { 3117u, new int[]{ -1026 } }, + { 3118u, new int[]{ -1035 } }, + { 3119u, new int[]{ -1034 } }, + { 3121u, new int[]{ 9949 } }, + { 3125u, new int[]{ 10041 } }, + { 3126u, new int[]{ -1037 } }, + { 3127u, new int[]{ 9948 } }, + { 3128u, new int[]{ 9931 } }, + { 3129u, new int[]{ 10004 } }, + { 3130u, new int[]{ 9989 } }, + { 3131u, new int[]{ 9988 } }, + { 3132u, new int[]{ 9955 } }, + { 3133u, new int[]{ 9945 } }, + { 3134u, new int[]{ -1072 } }, + { 3143u, new int[]{ 9963 } }, + { 3144u, new int[]{ 10192 } }, + { 3150u, new int[]{ 9813 } }, + { 3151u, new int[]{ 9813 } }, + { 3156u, new int[]{ -1022 } }, + { 3161u, new int[]{ 9939 } }, + { 3170u, new int[]{ -1056 } }, + { 3177u, new int[]{ -1048 } }, + { 3178u, new int[]{ -1047 } }, + { 3179u, new int[]{ 9920 } }, + { 3180u, new int[]{ -1080 } }, + { 3181u, new int[]{ -1067 } }, + { 3184u, new int[]{ 9973 } }, + { 3185u, new int[]{ 9940 } }, + { 3186u, new int[]{ 9984 } }, + { 3187u, new int[]{ 9983 } }, + { 3188u, new int[]{ 9985 } }, + { 3192u, new int[]{ -1071 } }, + { 3195u, new int[]{ -1075 } }, + { 3197u, new int[]{ -1076 } }, + { 3204u, new int[]{ -1077 } }, + { 3206u, new int[]{ 9693 } }, + { 3207u, new int[]{ 9930 } }, + { 3242u, new int[]{ -1070 } }, + { 3243u, new int[]{ -1083 } }, + { 3244u, new int[]{ -1084 } }, + { 3245u, new int[]{ -1066 } }, + { 3246u, new int[]{ -1065 } }, + { 3247u, new int[]{ -1068 } }, + { 3248u, new int[]{ -1078 } }, + { 3256u, new int[]{ -1081 } }, + { 3257u, new int[]{ -1082 } }, + { 3266u, new int[]{ 10064 } }, + { 3274u, new int[]{ 9958 } }, + { 3275u, new int[]{ 10006 } }, + { 3292u, new int[]{ -1069 } }, + { 3294u, new int[]{ 7667 } }, + { 3295u, new int[]{ -1079 } }, + { 3297u, new int[]{ 9941 } }, + { 3298u, new int[]{ 10242 } }, + { 3316u, new int[]{ -1050 } }, + { 3326u, new int[]{ -1049 } }, + { 3572u, new int[]{ 2136 } }, + }; +} +// @formatter:on diff --git a/Glamourer.GameData/RestrictedGear.cs b/Glamourer.GameData/RestrictedGear.cs new file mode 100644 index 0000000..e842e4f --- /dev/null +++ b/Glamourer.GameData/RestrictedGear.cs @@ -0,0 +1,441 @@ +using System.Collections.Generic; +using System.Linq; +using Dalamud.Data; +using Dalamud.Logging; +using Dalamud.Utility; +using Lumina.Excel; +using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer; + +// Handle gender- or race-locked gear in the draw model itself. +// Racial gear gets swapped to the correct current race and gender (it is one set each). +// Gender-locked gear gets swapped to the equivalent set if it exists (most of them do), +// with some items getting send to emperor's new clothes and a few funny entries. +public class RestrictedGear +{ + private readonly ExcelSheet _items; + private readonly ExcelSheet _categories; + + private readonly HashSet _raceGenderSet = RaceGenderGroup.Where(c => c != 0).ToHashSet(); + private readonly Dictionary _maleToFemale = new(); + private readonly Dictionary _femaleToMale = new(); + + internal RestrictedGear(DataManager gameData) + { + _items = gameData.GetExcelSheet()!; + _categories = gameData.GetExcelSheet()!; + AddKnown(); + UnhandledRestrictedGear(false); // Set this to true to create a print of unassigned gear on launch. + } + + // Resolve a model given by its model id, variant and slot for your current race and gender. + public (bool Replaced, SetId ModelId, byte Variant) ResolveRestricted(SetId modelId, byte variant, EquipSlot slot, Race race, + Gender gender) + { + var quad = modelId.Value | ((uint)variant << 16); + // Check racial gear, this does not need slots. + if (RaceGenderGroup.Contains(quad)) + { + var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0); + var value = RaceGenderGroup[idx]; + return (value != quad, (ushort)value, (byte)(value >> 16)); + } + + // Check gender slots. If current gender is female, check if anything needs to be changed from male to female, + // and vice versa. + // Some items lead to the exact same model- and variant id just gender specified, + // so check for actual difference in the Replaced bool. + var needle = quad | ((uint)slot.ToSlot() << 24); + if (gender is Gender.Female or Gender.FemaleNpc && _maleToFemale.TryGetValue(needle, out var newValue) + || gender is Gender.Male or Gender.MaleNpc && _femaleToMale.TryGetValue(needle, out newValue)) + return (quad != newValue, (ushort)newValue, (byte)(newValue >> 16)); + + // The gear is not restricted. + return (false, modelId, variant); + } + + // Add all unknown restricted gear and pair it with emperor's new gear on start up. + // Can also print unhandled items. + public void UnhandledRestrictedGear(bool print = false) + { + if (print) + PluginLog.Information("#### MALE ONLY ######"); + + void AddEmperor(Item item, bool male, bool female) + { + var slot = ((EquipSlot)item.EquipSlotCategory.Row).ToSlot(); + var emperor = slot switch + { + EquipSlot.Head => 10032u, + EquipSlot.Body => 10033u, + EquipSlot.Hands => 10034u, + EquipSlot.Legs => 10035u, + EquipSlot.Feet => 10036u, + EquipSlot.Ears => 09293u, + EquipSlot.Neck => 09292u, + EquipSlot.Wrists => 09294u, + EquipSlot.RFinger => 09295u, + EquipSlot.LFinger => 09295u, + _ => 0u, + }; + if (emperor == 0) + return; + + if (male) + AddItem(item.RowId, emperor, true, false); + if (female) + AddItem(emperor, item.RowId, false, true); + } + + var unhandled = 0; + foreach (var item in _items.Where(i => i.EquipRestriction == 2)) + { + if (_maleToFemale.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24))) + continue; + + ++unhandled; + AddEmperor(item, true, false); + + if (print) + PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}"); + } + + if (print) + PluginLog.Information("#### FEMALE ONLY ####"); + foreach (var item in _items.Where(i => i.EquipRestriction == 3)) + { + if (_femaleToMale.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24))) + continue; + + ++unhandled; + AddEmperor(item, false, true); + + if (print) + PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}"); + } + + if (print) + PluginLog.Information("#### OTHER #########"); + + foreach (var item in _items.Where(i => i.EquipRestriction > 3)) + { + if (_raceGenderSet.Contains((uint)item.ModelMain)) + continue; + + ++unhandled; + if (print) + PluginLog.Information( + $"{item.RowId:D5} {item.Name.ToDalamudString().TextValue} RestrictionGroup {_categories.GetRow(item.EquipRestriction)!.RowId:D2}"); + } + + if (unhandled > 0) + PluginLog.Warning("There were {Num} restricted items not handled and directed to Emperor's New Set.", unhandled); + } + + // Add a item redirection by its item - NOT MODEL - id. + // This uses the items model as well as its slot. + // Creates a <-> redirection by default but can add -> or <- redirections by setting the corresponding bools to false. + // Prints warnings if anything does not make sense. + private void AddItem(uint itemIdMale, uint itemIdFemale, bool addMale = true, bool addFemale = true) + { + if (!addMale && !addFemale) + return; + + var mItem = _items.GetRow(itemIdMale); + var fItem = _items.GetRow(itemIdFemale); + if (mItem == null || fItem == null) + { + PluginLog.Warning($"Could not add item {itemIdMale} or {itemIdFemale} to restricted items."); + return; + } + + if (mItem.EquipRestriction != 2 && addMale) + { + PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not restricted anymore."); + return; + } + + if (fItem.EquipRestriction != 3 && addFemale) + { + PluginLog.Warning($"{fItem.Name.ToDalamudString().TextValue} is not restricted anymore."); + return; + } + + var mSlot = ((EquipSlot)mItem.EquipSlotCategory.Row).ToSlot(); + var fSlot = ((EquipSlot)fItem.EquipSlotCategory.Row).ToSlot(); + if (!mSlot.IsAccessory() && !mSlot.IsEquipment()) + { + PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not equippable to a known slot."); + return; + } + + if (mSlot != fSlot) + { + PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} and {fItem.Name.ToDalamudString().TextValue} are not compatible."); + return; + } + + var mModelIdSlot = (uint)mItem.ModelMain | ((uint)mSlot << 24); + var fModelIdSlot = (uint)fItem.ModelMain | ((uint)fSlot << 24); + + if (addMale) + _maleToFemale.TryAdd(mModelIdSlot, fModelIdSlot); + if (addFemale) + _femaleToMale.TryAdd(fModelIdSlot, mModelIdSlot); + } + + // Add all currently existing and known gender restricted items. + private void AddKnown() + { + AddItem(02967, 02970); // Lord's Yukata (Blue) <-> Lady's Yukata (Red) + AddItem(02968, 02971); // Lord's Yukata (Green) <-> Lady's Yukata (Blue) + AddItem(02969, 02972); // Lord's Yukata (Grey) <-> Lady's Yukata (Black) + AddItem(02973, 02978); // Red Summer Top <-> Red Summer Halter + AddItem(02974, 02979); // Green Summer Top <-> Green Summer Halter + AddItem(02975, 02980); // Blue Summer Top <-> Blue Summer Halter + AddItem(02976, 02981); // Solar Summer Top <-> Solar Summer Halter + AddItem(02977, 02982); // Lunar Summer Top <-> Lunar Summer Halter + AddItem(02996, 02997); // Hempen Undershirt <-> Hempen Camise + AddItem(03280, 03283); // Lord's Drawers (Black) <-> Lady's Knickers (Black) + AddItem(03281, 03284); // Lord's Drawers (White) <-> Lady's Knickers (White) + AddItem(03282, 03285); // Lord's Drawers (Gold) <-> Lady's Knickers (Gold) + AddItem(03286, 03291); // Red Summer Trunks <-> Red Summer Tanga + AddItem(03287, 03292); // Green Summer Trunks <-> Green Summer Tanga + AddItem(03288, 03293); // Blue Summer Trunks <-> Blue Summer Tanga + AddItem(03289, 03294); // Solar Summer Trunks <-> Solar Summer Tanga + AddItem(03290, 03295); // Lunar Summer Trunks <-> Lunar Summer Tanga + AddItem(03307, 03308); // Hempen Underpants <-> Hempen Pantalettes + AddItem(03748, 03749); // Lord's Clogs <-> Lady's Clogs + AddItem(06045, 06041); // Bohemian's Coat <-> Guardian Corps Coat + AddItem(06046, 06042); // Bohemian's Gloves <-> Guardian Corps Gauntlets + AddItem(06047, 06043); // Bohemian's Trousers <-> Guardian Corps Skirt + AddItem(06048, 06044); // Bohemian's Boots <-> Guardian Corps Boots + AddItem(06094, 06098); // Summer Evening Top <-> Summer Morning Halter + AddItem(06095, 06099); // Summer Evening Trunks <-> Summer Morning Tanga + AddItem(06096, 06100); // Striped Summer Top <-> Striped Summer Halter + AddItem(06097, 06101); // Striped Summer Trunks <-> Striped Summer Tanga + AddItem(06102, 06104); // Black Summer Top <-> Black Summer Halter + AddItem(06103, 06105); // Black Summer Trunks <-> Black Summer Tanga + AddItem(06972, 06973); // Valentione Apron <-> Valentione Apron Dress + AddItem(06975, 06976); // Valentione Trousers <-> Valentione Skirt + AddItem(08532, 08535); // Lord's Yukata (Blackflame) <-> Lady's Yukata (Redfly) + AddItem(08533, 08536); // Lord's Yukata (Whiteflame) <-> Lady's Yukata (Bluefly) + AddItem(08534, 08537); // Lord's Yukata (Blueflame) <-> Lady's Yukata (Pinkfly) + AddItem(08542, 08549); // Ti Leaf Lei <-> Coronal Summer Halter + AddItem(08543, 08550); // Red Summer Maro <-> Red Summer Pareo + AddItem(08544, 08551); // South Seas Talisman <-> Sea Breeze Summer Halter + AddItem(08545, 08552); // Blue Summer Maro <-> Sea Breeze Summer Pareo + AddItem(08546, 08553); // Coeurl Talisman <-> Coeurl Beach Halter + AddItem(08547, 08554); // Coeurl Beach Maro <-> Coeurl Beach Pareo + AddItem(08548, 08555); // Coeurl Beach Briefs <-> Coeurl Beach Tanga + AddItem(10316, 10317); // Southern Seas Vest <-> Southern Seas Swimsuit + AddItem(10318, 10319); // Southern Seas Trunks <-> Southern Seas Tanga + AddItem(10320, 10321); // Striped Southern Seas Vest <-> Striped Southern Seas Swimsuit + AddItem(13298, 13567); // Black-feathered Flat Hat <-> Red-feathered Flat Hat + AddItem(13300, 13639); // Lord's Suikan <-> Lady's Suikan + AddItem(13724, 13725); // Little Lord's Clogs <-> Little Lady's Clogs + AddItem(14854, 14857); // Eastern Lord's Togi <-> Eastern Lady's Togi + AddItem(14855, 14858); // Eastern Lord's Trousers <-> Eastern Lady's Loincloth + AddItem(14856, 14859); // Eastern Lord's Crakows <-> Eastern Lady's Crakows + AddItem(15639, 15642); // Far Eastern Patriarch's Hat <-> Far Eastern Matriarch's Sun Hat + AddItem(15640, 15643); // Far Eastern Patriarch's Tunic <-> Far Eastern Matriarch's Dress + AddItem(15641, 15644); // Far Eastern Patriarch's Longboots <-> Far Eastern Matriarch's Boots + AddItem(15922, 15925); // Moonfire Vest <-> Moonfire Halter + AddItem(15923, 15926); // Moonfire Trunks <-> Moonfire Tanga + AddItem(15924, 15927); // Moonfire Caligae <-> Moonfire Sandals + AddItem(16106, 16111); // Makai Mauler's Facemask <-> Makai Manhandler's Facemask + AddItem(16107, 16112); // Makai Mauler's Oilskin <-> Makai Manhandler's Jerkin + AddItem(16108, 16113); // Makai Mauler's Fingerless Gloves <-> Makai Manhandler's Fingerless Gloves + AddItem(16109, 16114); // Makai Mauler's Leggings <-> Makai Manhandler's Quartertights + AddItem(16110, 16115); // Makai Mauler's Boots <-> Makai Manhandler's Longboots + AddItem(16116, 16121); // Makai Marksman's Eyepatch <-> Makai Markswoman's Ribbon + AddItem(16117, 16122); // Makai Marksman's Battlegarb <-> Makai Markswoman's Battledress + AddItem(16118, 16123); // Makai Marksman's Fingerless Gloves <-> Makai Markswoman's Fingerless Gloves + AddItem(16119, 16124); // Makai Marksman's Slops <-> Makai Markswoman's Quartertights + AddItem(16120, 16125); // Makai Marksman's Boots <-> Makai Markswoman's Longboots + AddItem(16126, 16131); // Makai Sun Guide's Circlet <-> Makai Moon Guide's Circlet + AddItem(16127, 16132); // Makai Sun Guide's Oilskin <-> Makai Moon Guide's Gown + AddItem(16128, 16133); // Makai Sun Guide's Fingerless Gloves <-> Makai Moon Guide's Fingerless Gloves + AddItem(16129, 16134); // Makai Sun Guide's Slops <-> Makai Moon Guide's Quartertights + AddItem(16130, 16135); // Makai Sun Guide's Boots <-> Makai Moon Guide's Longboots + AddItem(16136, 16141); // Makai Priest's Coronet <-> Makai Priestess's Headdress + AddItem(16137, 16142); // Makai Priest's Doublet Robe <-> Makai Priestess's Jerkin + AddItem(16138, 16143); // Makai Priest's Fingerless Gloves <-> Makai Priestess's Fingerless Gloves + AddItem(16139, 16144); // Makai Priest's Slops <-> Makai Priestess's Skirt + AddItem(16140, 16145); // Makai Priest's Boots <-> Makai Priestess's Longboots + AddItem(16588, 16592); // Far Eastern Gentleman's Hat <-> Far Eastern Beauty's Hairpin + AddItem(16589, 16593); // Far Eastern Gentleman's Robe <-> Far Eastern Beauty's Robe + AddItem(16590, 16594); // Far Eastern Gentleman's Haidate <-> Far Eastern Beauty's Koshita + AddItem(16591, 16595); // Far Eastern Gentleman's Boots <-> Far Eastern Beauty's Boots + AddItem(17204, 17209); // Common Makai Mauler's Facemask <-> Common Makai Manhandler's Facemask + AddItem(17205, 17210); // Common Makai Mauler's Oilskin <-> Common Makai Manhandler's Jerkin + AddItem(17206, 17211); // Common Makai Mauler's Fingerless Gloves <-> Common Makai Manhandler's Fingerless Glove + AddItem(17207, 17212); // Common Makai Mauler's Leggings <-> Common Makai Manhandler's Quartertights + AddItem(17208, 17213); // Common Makai Mauler's Boots <-> Common Makai Manhandler's Longboots + AddItem(17214, 17219); // Common Makai Marksman's Eyepatch <-> Common Makai Markswoman's Ribbon + AddItem(17215, 17220); // Common Makai Marksman's Battlegarb <-> Common Makai Markswoman's Battledress + AddItem(17216, 17221); // Common Makai Marksman's Fingerless Gloves <-> Common Makai Markswoman's Fingerless Glove + AddItem(17217, 17222); // Common Makai Marksman's Slops <-> Common Makai Markswoman's Quartertights + AddItem(17218, 17223); // Common Makai Marksman's Boots <-> Common Makai Markswoman's Longboots + AddItem(17224, 17229); // Common Makai Sun Guide's Circlet <-> Common Makai Moon Guide's Circlet + AddItem(17225, 17230); // Common Makai Sun Guide's Oilskin <-> Common Makai Moon Guide's Gown + AddItem(17226, 17231); // Common Makai Sun Guide's Fingerless Gloves <-> Common Makai Moon Guide's Fingerless Glove + AddItem(17227, 17232); // Common Makai Sun Guide's Slops <-> Common Makai Moon Guide's Quartertights + AddItem(17228, 17233); // Common Makai Sun Guide's Boots <-> Common Makai Moon Guide's Longboots + AddItem(17234, 17239); // Common Makai Priest's Coronet <-> Common Makai Priestess's Headdress + AddItem(17235, 17240); // Common Makai Priest's Doublet Robe <-> Common Makai Priestess's Jerkin + AddItem(17236, 17241); // Common Makai Priest's Fingerless Gloves <-> Common Makai Priestess's Fingerless Gloves + AddItem(17237, 17242); // Common Makai Priest's Slops <-> Common Makai Priestess's Skirt + AddItem(17238, 17243); // Common Makai Priest's Boots <-> Common Makai Priestess's Longboots + AddItem(17481, 17476); // Royal Seneschal's Chapeau <-> Songbird Hat + AddItem(17482, 17477); // Royal Seneschal's Coat <-> Songbird Jacket + AddItem(17483, 17478); // Royal Seneschal's Fingerless Gloves <-> Songbird Gloves + AddItem(17484, 17479); // Royal Seneschal's Breeches <-> Songbird Skirt + AddItem(17485, 17480); // Royal Seneschal's Boots <-> Songbird Boots + AddItem(20479, 20484); // Star of the Nezha Lord <-> Star of the Nezha Lady + AddItem(20480, 20485); // Nezha Lord's Togi <-> Nezha Lady's Togi + AddItem(20481, 20486); // Nezha Lord's Gloves <-> Nezha Lady's Gloves + AddItem(20482, 20487); // Nezha Lord's Slops <-> Nezha Lady's Slops + AddItem(20483, 20488); // Nezha Lord's Boots <-> Nezha Lady's Kneeboots + AddItem(22367, 22372); // Faerie Tale Prince's Circlet <-> Faerie Tale Princess's Tiara + AddItem(22368, 22373); // Faerie Tale Prince's Vest <-> Faerie Tale Princess's Dress + AddItem(22369, 22374); // Faerie Tale Prince's Gloves <-> Faerie Tale Princess's Gloves + AddItem(22370, 22375); // Faerie Tale Prince's Slops <-> Faerie Tale Princess's Long Skirt + AddItem(22371, 22376); // Faerie Tale Prince's Boots <-> Faerie Tale Princess's Heels + AddItem(24599, 24602); // Far Eastern Schoolboy's Hat <-> Far Eastern Schoolgirl's Hair Ribbon + AddItem(24600, 24603); // Far Eastern Schoolboy's Hakama <-> Far Eastern Schoolgirl's Hakama + AddItem(24601, 24604); // Far Eastern Schoolboy's Zori <-> Far Eastern Schoolgirl's Boots + AddItem(28558, 28573); // Valentione Rose Hat <-> Valentione Rose Ribboned Hat + AddItem(28559, 28574); // Valentione Rose Waistcoat <-> Valentione Rose Dress + AddItem(28560, 28575); // Valentione Rose Gloves <-> Valentione Rose Ribboned Gloves + AddItem(28561, 28576); // Valentione Rose Slacks <-> Valentione Rose Tights + AddItem(28562, 28577); // Valentione Rose Shoes <-> Valentione Rose Heels + AddItem(28563, 28578); // Valentione Forget-me-not Hat <-> Valentione Forget-me-not Ribboned Hat + AddItem(28564, 28579); // Valentione Forget-me-not Waistcoat <-> Valentione Forget-me-not Dress + AddItem(28565, 28580); // Valentione Forget-me-not Gloves <-> Valentione Forget-me-not Ribboned Gloves + AddItem(28566, 28581); // Valentione Forget-me-not Slacks <-> Valentione Forget-me-not Tights + AddItem(28567, 28582); // Valentione Forget-me-not Shoes <-> Valentione Forget-me-not Heels + AddItem(28568, 28583); // Valentione Acacia Hat <-> Valentione Acacia Ribboned Hat + AddItem(28569, 28584); // Valentione Acacia Waistcoat <-> Valentione Acacia Dress + AddItem(28570, 28585); // Valentione Acacia Gloves <-> Valentione Acacia Ribboned Gloves + AddItem(28571, 28586); // Valentione Acacia Slacks <-> Valentione Acacia Tights + AddItem(28572, 28587); // Valentione Acacia Shoes <-> Valentione Acacia Heels + AddItem(28600, 28605); // Eastern Lord Errant's Hat <-> Eastern Lady Errant's Hat + AddItem(28601, 28606); // Eastern Lord Errant's Jacket <-> Eastern Lady Errant's Coat + AddItem(28602, 28607); // Eastern Lord Errant's Wristbands <-> Eastern Lady Errant's Gloves + AddItem(28603, 28608); // Eastern Lord Errant's Trousers <-> Eastern Lady Errant's Skirt + AddItem(28604, 28609); // Eastern Lord Errant's Shoes <-> Eastern Lady Errant's Boots + AddItem(31408, 31413); // Bergsteiger's Hat <-> Dirndl's Hat + AddItem(31409, 31414); // Bergsteiger's Jacket <-> Dirndl's Bodice + AddItem(31410, 31415); // Bergsteiger's Halfgloves <-> Dirndl's Wrist Torque + AddItem(31411, 31416); // Bergsteiger's Halfslops <-> Dirndl's Long Skirt + AddItem(31412, 31417); // Bergsteiger's Boots <-> Dirndl's Pumps + AddItem(36336, 36337); // Omega-M Attire <-> Omega-F Attire + AddItem(36338, 36339); // Omega-M Ear Cuffs <-> Omega-F Earrings + AddItem(37442, 37447); // Makai Vanguard's Monocle <-> Makai Vanbreaker's Ribbon + AddItem(37443, 37448); // Makai Vanguard's Battlegarb <-> Makai Vanbreaker's Battledress + AddItem(37444, 37449); // Makai Vanguard's Fingerless Gloves <-> Makai Vanbreaker's Fingerless Gloves + AddItem(37445, 37450); // Makai Vanguard's Leggings <-> Makai Vanbreaker's Quartertights + AddItem(37446, 37451); // Makai Vanguard's Boots <-> Makai Vanbreaker's Longboots + AddItem(37452, 37457); // Makai Harbinger's Facemask <-> Makai Harrower's Facemask + AddItem(37453, 37458); // Makai Harbinger's Battlegarb <-> Makai Harrower's Jerkin + AddItem(37454, 37459); // Makai Harbinger's Fingerless Gloves <-> Makai Harrower's Fingerless Gloves + AddItem(37455, 37460); // Makai Harbinger's Leggings <-> Makai Harrower's Quartertights + AddItem(37456, 37461); // Makai Harbinger's Boots <-> Makai Harrower's Longboots + AddItem(37462, 37467); // Common Makai Vanguard's Monocle <-> Common Makai Vanbreaker's Ribbon + AddItem(37463, 37468); // Common Makai Vanguard's Battlegarb <-> Common Makai Vanbreaker's Battledress + AddItem(37464, 37469); // Common Makai Vanguard's Fingerless Gloves <-> Common Makai Vanbreaker's Fingerless Gloves + AddItem(37465, 37470); // Common Makai Vanguard's Leggings <-> Common Makai Vanbreaker's Quartertights + AddItem(37466, 37471); // Common Makai Vanguard's Boots <-> Common Makai Vanbreaker's Longboots + AddItem(37472, 37477); // Common Makai Harbinger's Facemask <-> Common Makai Harrower's Facemask + AddItem(37473, 37478); // Common Makai Harbinger's Battlegarb <-> Common Makai Harrower's Jerkin + AddItem(37474, 37479); // Common Makai Harbinger's Fingerless Gloves <-> Common Makai Harrower's Fingerless Gloves + AddItem(37475, 37480); // Common Makai Harbinger's Leggings <-> Common Makai Harrower's Quartertights + AddItem(37476, 37481); // Common Makai Harbinger's Boots <-> Common Makai Harrower's Longboots + AddItem(23003, 23008); // Mun'gaek Hat <-> Eastern Socialite's Hat + AddItem(23004, 23009); // Mun'gaek Uibok <-> Eastern Socialite's Cheongsam + AddItem(23005, 23010); // Mun'gaek Cuffs <-> Eastern Socialite's Gloves + AddItem(23006, 23011); // Mun'gaek Trousers <-> Eastern Socialite's Skirt + AddItem(23007, 23012); // Mun'gaek Boots <-> Eastern Socialite's Boots + AddItem(24148, 24153); // Far Eastern Officer's Hat <-> Far Eastern Maiden's Hat + AddItem(24149, 24154); // Far Eastern Officer's Robe <-> Far Eastern Maiden's Tunic + AddItem(24150, 24155); // Far Eastern Officer's Armband <-> Far Eastern Maiden's Armband + AddItem(24151, 24156); // Far Eastern Officer's Bottoms <-> Far Eastern Maiden's Bottoms + AddItem(24152, 24157); // Far Eastern Officer's Boots <-> Far Eastern Maiden's Boots + AddItem(13323, 13322); // Scion Thief's Tunic <-> Scion Conjurer's Dalmatica + AddItem(13693, 10034, true, false); // Scion Thief's Halfgloves -> The Emperor's New Gloves + AddItem(13694, 13691); // Scion Thief's Gaskins <-> Scion Conjurer's Chausses + AddItem(13695, 13692); // Scion Thief's Armored Caligae <-> Scion Conjurer's Pattens + AddItem(13326, 30063); // Scion Thaumaturge's Robe <-> Scion Sorceress's Headdress + AddItem(13696, 30062); // Scion Thaumaturge's Monocle <-> Scion Sorceress's Robe + AddItem(13697, 30064); // Scion Thaumaturge's Gauntlets <-> Scion Sorceress's Shadowtalons + AddItem(13698, 10035, true, false); // Scion Thaumaturge's Gaskins -> The Emperor's New Breeches + AddItem(13699, 30065); // Scion Thaumaturge's Moccasins <-> Scion Sorceress's High Boots + AddItem(13327, 15942); // Scion Chronocler's Cowl <-> Scion Healer's Robe + AddItem(13700, 10034, true, false); // Scion Chronocler's Ringbands -> The Emperor's New Gloves + AddItem(13701, 15943); // Scion Chronocler's Tights <-> Scion Healer's Halftights + AddItem(13702, 15944); // Scion Chronocler's Caligae <-> Scion Healer's Highboots + AddItem(14861, 13324); // Head Engineer's Goggles <-> Scion Striker's Visor + AddItem(14862, 13325); // Head Engineer's Attire <-> Scion Striker's Attire + AddItem(15938, 33751); // Scion Rogue's Jacket <-> Oracle Top + AddItem(15939, 10034, true, false); // Scion Rogue's Armguards -> The Emperor's New Gloves + AddItem(15940, 33752); // Scion Rogue's Gaskins <-> Oracle Leggings + AddItem(15941, 33753); // Scion Rogue's Boots <-> Oracle Pantalettes + AddItem(16042, 16046); // Abes Jacket <-> High Summoner's Dress + AddItem(16043, 16047); // Abes Gloves <-> High Summoner's Armlets + AddItem(16044, 10035, true, false); // Abes Halfslops -> The Emperor's New Breeches + AddItem(16045, 16048); // Abes Boots <-> High Summoner's Boots + AddItem(17473, 28553); // Lord Commander's Coat <-> Majestic Dress + AddItem(17474, 28554); // Lord Commander's Gloves <-> Majestic Wristdresses + AddItem(10036, 28555, false); // Emperor's New Boots <- Majestic Boots + AddItem(21021, 21026); // Werewolf Feet <-> Werewolf Legs + AddItem(22452, 20633); // Cracked Manderville Monocle <-> Blackbosom Hat + AddItem(22453, 20634); // Torn Manderville Coatee <-> Blackbosom Dress + AddItem(22454, 20635); // Singed Manderville Gloves <-> Blackbosom Dress Gloves + AddItem(22455, 10035, true, false); // Stained Manderville Bottoms -> The Emperor's New Breeches + AddItem(22456, 20636); // Scuffed Manderville Gaiters <-> lackbosom Boots + AddItem(23013, 21302); // Doman Liege's Dogi <-> Scion Liberator's Jacket + AddItem(23014, 21303); // Doman Liege's Kote <-> Scion Liberator's Fingerless Gloves + AddItem(23015, 21304); // Doman Liege's Kyakui <-> Scion Liberator's Pantalettes + AddItem(23016, 21305); // Doman Liege's Kyahan <-> Scion Liberator's Sabatons + AddItem(09293, 21306, false); // The Emperor's New Earrings <- Scion Liberator's Earrings + AddItem(24158, 23008); // Leal Samurai's Kasa <-> Eastern Socialite's Hat + AddItem(24159, 23009); // Leal Samurai's Dogi <-> Eastern Socialite's Cheongsam + AddItem(24160, 23010); // Leal Samurai's Tekko <-> Eastern Socialite's Gloves + AddItem(24161, 23011); // Leal Samurai's Tsutsu-hakama <-> Eastern Socialite's Skirt + AddItem(24162, 23012); // Leal Samurai's Geta <-> Eastern Socialite's Boots + AddItem(02966, 13321, false); // Reindeer Suit <- Antecedent's Attire + AddItem(15479, 36843, false); // Swine Body <- Lyse's Leadership Attire + AddItem(21941, 24999, false); // Ala Mhigan Gown <- Gown of Light + AddItem(30757, 25000, false); // Southern Seas Skirt <- Skirt of Light + AddItem(36821, 27933, false); // Archfiend Helm <- Scion Hearer's Hood + AddItem(36822, 27934, false); // Archfiend Armor <- Scion Hearer's Coat + AddItem(36825, 27935, false); // Archfiend Sabatons <- Scion Hearer's Shoes + } + + // The racial starter sets are available for all 4 slots each, + // but have no associated accessories or hats. + private static readonly uint[] RaceGenderGroup = + { + 0x020054, + 0x020055, + 0x020056, + 0x020057, + 0x02005C, + 0x02005D, + 0x020058, + 0x020059, + 0x02005A, + 0x02005B, + 0x020101, + 0x020102, + 0x010255, + uint.MaxValue, // TODO: Female Hrothgar + 0x0102E8, + 0x010245, + }; +} diff --git a/Glamourer.GameData/Structs/CharacterEquipMask.cs b/Glamourer.GameData/Structs/CharacterEquipMask.cs index aaf4e64..8a38db3 100644 --- a/Glamourer.GameData/Structs/CharacterEquipMask.cs +++ b/Glamourer.GameData/Structs/CharacterEquipMask.cs @@ -3,23 +3,25 @@ using Penumbra.GameData.Enums; namespace Glamourer.Structs; + +// Turn EquipSlot into a bitfield flag enum. [Flags] public enum CharacterEquipMask : ushort { - None = 0, + 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, + 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 @@ -28,16 +30,16 @@ public static class CharacterEquipMaskExtensions => 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.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, + _ => false, }; } diff --git a/Glamourer.GameData/Structs/Item.cs b/Glamourer.GameData/Structs/Item.cs index 481cc5f..0b6ef42 100644 --- a/Glamourer.GameData/Structs/Item.cs +++ b/Glamourer.GameData/Structs/Item.cs @@ -3,8 +3,37 @@ using Penumbra.GameData.Structs; namespace Glamourer.Structs; +// An Item wrapper struct that contains the item table, a precomputed name and the associated equip slot. public readonly struct Item { + public readonly Lumina.Excel.GeneratedSheets.Item Base; + public readonly string Name; + public readonly EquipSlot EquippableTo; + + // Obtain the main model info used by the item. + public (SetId id, WeaponType type, ushort variant) MainModel + => ParseModel(EquippableTo, Base.ModelMain); + + // Obtain the sub model info used by the item. Will be 0 if the item has no sub model. + public (SetId id, WeaponType type, ushort variant) SubModel + => ParseModel(EquippableTo, Base.ModelSub); + + public bool HasSubModel + => Base.ModelSub != 0; + + // Create a new item from its sheet list with the given name and either the inferred equip slot or the given one. + 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; + } + + // Create empty Nothing items. + public static Item Nothing(EquipSlot slot) + => new("Nothing", slot); + + // Produce the relevant model information for a given item and equip slot. private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data) { if (slot is EquipSlot.MainHand or EquipSlot.OffHand) @@ -22,33 +51,14 @@ public readonly struct Item } } - 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); - + // Used for 'Nothing' items. private Item(string name, EquipSlot slot) { Name = name; Base = new Lumina.Excel.GeneratedSheets.Item(); EquippableTo = slot; } + + public override string ToString() + => Name; } diff --git a/Glamourer.GameData/Structs/Job.cs b/Glamourer.GameData/Structs/Job.cs index 55169bb..d946c12 100644 --- a/Glamourer.GameData/Structs/Job.cs +++ b/Glamourer.GameData/Structs/Job.cs @@ -1,7 +1,10 @@ -using Lumina.Excel.GeneratedSheets; +using Dalamud.Utility; +using Lumina.Excel.GeneratedSheets; namespace Glamourer.Structs; +// A struct containing the different jobs the game supports. +// Also contains the jobs Name and Abbreviation as strings. public readonly struct Job { public readonly string Name; @@ -14,7 +17,10 @@ public readonly struct Job public Job(ClassJob job) { Base = job; - Name = job.Name.ToString(); - Abbreviation = job.Abbreviation.ToString(); + Name = job.Name.ToDalamudString().ToString(); + Abbreviation = job.Abbreviation.ToDalamudString().ToString(); } + + public override string ToString() + => Name; } diff --git a/Glamourer.GameData/Structs/JobGroup.cs b/Glamourer.GameData/Structs/JobGroup.cs index 57ff22f..eaa614e 100644 --- a/Glamourer.GameData/Structs/JobGroup.cs +++ b/Glamourer.GameData/Structs/JobGroup.cs @@ -1,29 +1,31 @@ using System.Diagnostics; -using System.Linq; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; namespace Glamourer.Structs; +// The game specifies different job groups that can contain specific jobs or not. public readonly struct JobGroup { - public readonly string Name; - private readonly ulong _flags; - public readonly int Count; - public readonly uint Id; + public readonly string Name; + public readonly int Count; + public readonly uint Id; + private readonly ulong _flags; + // Create a job group from a given category and the ClassJob sheet. + // It looks up the different jobs contained in the category and sets the flags appropriately. public JobGroup(ClassJobCategory group, ExcelSheet jobs) { - Count = 0; + Count = 0; _flags = 0ul; - Id = group.RowId; - Name = group.Name.ToString(); + 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()) + if (abbr.Length == 0) continue; var prop = group.GetType().GetProperty(abbr); @@ -37,9 +39,11 @@ public readonly struct JobGroup } } + // Check if a job is contained inside this group. public bool Fits(Job job) => Fits(job.Id); + // Check if a job is contained inside this group. public bool Fits(uint jobId) { var flag = 1ul << (int)jobId; diff --git a/Glamourer.GameData/Structs/Stain.cs b/Glamourer.GameData/Structs/Stain.cs index 32823aa..7a43645 100644 --- a/Glamourer.GameData/Structs/Stain.cs +++ b/Glamourer.GameData/Structs/Stain.cs @@ -1,22 +1,28 @@ -using Penumbra.GameData.Structs; +using Dalamud.Utility; +using Penumbra.GameData.Structs; namespace Glamourer.Structs; +// A wrapper for the clothing dyes the game provides with their RGBA color value, game ID, unmodified color value and name. public readonly struct Stain { - public readonly string Name; - public readonly uint RgbaColor; + // An empty stain with transparent color. + public static readonly Stain None = new("None"); + public readonly string Name; + public readonly uint RgbaColor; + + // Combine the Id byte with the 3 bytes of color values. private readonly uint _seColorId; public byte R => (byte)(RgbaColor & 0xFF); public byte G - => (byte)(RgbaColor >> 8 & 0xFF); + => (byte)((RgbaColor >> 8) & 0xFF); public byte B - => (byte)(RgbaColor >> 16 & 0xFF); + => (byte)((RgbaColor >> 16) & 0xFF); public byte Intensity => (byte)((1 + R + G + B) / 3); @@ -27,23 +33,22 @@ public readonly struct Stain public StainId RowIndex => (StainId)(_seColorId >> 24); - + // R and B need to be shuffled and Alpha set to max. public static uint SeColorToRgba(uint color) - => (color & 0xFF) << 16 | color >> 16 & 0xFF | color & 0xFF00 | 0xFF000000; + => ((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); + Name = stain.Name.ToDalamudString().ToString(); + _seColorId = stain.Color | ((uint)index << 24); + RgbaColor = SeColorToRgba(stain.Color); } - public static readonly Stain None = new("None"); - + // Only used by None. private Stain(string name) { - Name = name; + Name = name; _seColorId = 0; - RgbaColor = 0; + RgbaColor = 0; } } diff --git a/Glamourer.sln b/Glamourer.sln index 6d29d29..381596c 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.GameData", "Glamo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{9BEE2336-AA93-4669-8EEA-4756B3B2D024}" 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 @@ -65,18 +63,6 @@ Global {9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x64.Build.0 = Release|Any CPU {9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x86.ActiveCfg = Release|Any CPU {9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x86.Build.0 = Release|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x64.ActiveCfg = Debug|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x64.Build.0 = Debug|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x86.ActiveCfg = Debug|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Debug|x86.Build.0 = Debug|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|Any CPU.Build.0 = Release|Any CPU - {FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x64.ActiveCfg = Release|Any CPU - {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 diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 36157dc..0136d06 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -53,270 +53,269 @@ public class GlamourerIpc : IDisposable private void DisposeProviders() { - ProviderGetAllCustomization?.UnregisterFunc(); - ProviderGetAllCustomizationFromCharacter?.UnregisterFunc(); - ProviderApplyAll?.UnregisterAction(); - ProviderApplyAllToCharacter?.UnregisterAction(); - ProviderApplyOnlyCustomization?.UnregisterAction(); - ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction(); - ProviderApplyOnlyEquipment?.UnregisterAction(); - ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction(); - ProviderRevert?.UnregisterAction(); - ProviderRevertCharacter?.UnregisterAction(); - ProviderGetApiVersion?.UnregisterFunc(); - + // ProviderGetAllCustomization?.UnregisterFunc(); + // ProviderGetAllCustomizationFromCharacter?.UnregisterFunc(); + // ProviderApplyAll?.UnregisterAction(); + // ProviderApplyAllToCharacter?.UnregisterAction(); + // ProviderApplyOnlyCustomization?.UnregisterAction(); + // ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction(); + // ProviderApplyOnlyEquipment?.UnregisterAction(); + // ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction(); + // ProviderRevert?.UnregisterAction(); + // ProviderRevertCharacter?.UnregisterAction(); + // ProviderGetApiVersion?.UnregisterFunc(); } private void InitializeProviders() { - try - { - ProviderGetApiVersion = _pluginInterface.GetIpcProvider(LabelProviderApiVersion); - ProviderGetApiVersion.RegisterFunc(GetApiVersion); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}."); - } - - try - { - ProviderGetAllCustomization = _pluginInterface.GetIpcProvider(LabelProviderGetAllCustomization); - ProviderGetAllCustomization.RegisterFunc(GetAllCustomization); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); - } - - try - { - ProviderGetAllCustomizationFromCharacter = _pluginInterface.GetIpcProvider(LabelProviderGetAllCustomizationFromCharacter); - ProviderGetAllCustomizationFromCharacter.RegisterFunc(GetAllCustomization); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderGetAllCustomizationFromCharacter}."); - } - - try - { - ProviderApplyAll = - _pluginInterface.GetIpcProvider(LabelProviderApplyAll); - ProviderApplyAll.RegisterAction(ApplyAll); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}."); - } - - try - { - ProviderApplyAllToCharacter = - _pluginInterface.GetIpcProvider(LabelProviderApplyAllToCharacter); - ProviderApplyAllToCharacter.RegisterAction(ApplyAll); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}."); - } - - try - { - ProviderApplyOnlyCustomization = - _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyCustomization); - ProviderApplyOnlyCustomization.RegisterAction(ApplyOnlyCustomization); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}."); - } - - try - { - ProviderApplyOnlyCustomizationToCharacter = - _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyCustomizationToCharacter); - ProviderApplyOnlyCustomizationToCharacter.RegisterAction(ApplyOnlyCustomization); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}."); - } - - try - { - ProviderApplyOnlyEquipment = - _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyEquipment); - ProviderApplyOnlyEquipment.RegisterAction(ApplyOnlyEquipment); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); - } - - try - { - ProviderApplyOnlyEquipmentToCharacter = - _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyEquipmentToCharacter); - ProviderApplyOnlyEquipmentToCharacter.RegisterAction(ApplyOnlyEquipment); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); - } - - try - { - ProviderRevert = - _pluginInterface.GetIpcProvider(LabelProviderRevert); - ProviderRevert.RegisterAction(Revert); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}."); - } - - try - { - ProviderRevertCharacter = - _pluginInterface.GetIpcProvider(LabelProviderRevertCharacter); - ProviderRevertCharacter.RegisterAction(Revert); - } - catch (Exception ex) - { - PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}."); - } + //try + //{ + // ProviderGetApiVersion = _pluginInterface.GetIpcProvider(LabelProviderApiVersion); + // ProviderGetApiVersion.RegisterFunc(GetApiVersion); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}."); + //} + // + //try + //{ + // ProviderGetAllCustomization = _pluginInterface.GetIpcProvider(LabelProviderGetAllCustomization); + // ProviderGetAllCustomization.RegisterFunc(GetAllCustomization); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); + //} + // + //try + //{ + // ProviderGetAllCustomizationFromCharacter = _pluginInterface.GetIpcProvider(LabelProviderGetAllCustomizationFromCharacter); + // ProviderGetAllCustomizationFromCharacter.RegisterFunc(GetAllCustomization); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderGetAllCustomizationFromCharacter}."); + //} + // + //try + //{ + // ProviderApplyAll = + // _pluginInterface.GetIpcProvider(LabelProviderApplyAll); + // ProviderApplyAll.RegisterAction(ApplyAll); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}."); + //} + // + //try + //{ + // ProviderApplyAllToCharacter = + // _pluginInterface.GetIpcProvider(LabelProviderApplyAllToCharacter); + // ProviderApplyAllToCharacter.RegisterAction(ApplyAll); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyAll}."); + //} + // + //try + //{ + // ProviderApplyOnlyCustomization = + // _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyCustomization); + // ProviderApplyOnlyCustomization.RegisterAction(ApplyOnlyCustomization); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}."); + //} + // + //try + //{ + // ProviderApplyOnlyCustomizationToCharacter = + // _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyCustomizationToCharacter); + // ProviderApplyOnlyCustomizationToCharacter.RegisterAction(ApplyOnlyCustomization); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyCustomization}."); + //} + // + //try + //{ + // ProviderApplyOnlyEquipment = + // _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyEquipment); + // ProviderApplyOnlyEquipment.RegisterAction(ApplyOnlyEquipment); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); + //} + // + //try + //{ + // ProviderApplyOnlyEquipmentToCharacter = + // _pluginInterface.GetIpcProvider(LabelProviderApplyOnlyEquipmentToCharacter); + // ProviderApplyOnlyEquipmentToCharacter.RegisterAction(ApplyOnlyEquipment); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApplyOnlyEquipment}."); + //} + // + //try + //{ + // ProviderRevert = + // _pluginInterface.GetIpcProvider(LabelProviderRevert); + // ProviderRevert.RegisterAction(Revert); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}."); + //} + // + //try + //{ + // ProviderRevertCharacter = + // _pluginInterface.GetIpcProvider(LabelProviderRevertCharacter); + // ProviderRevertCharacter.RegisterAction(Revert); + //} + //catch (Exception ex) + //{ + // PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderRevert}."); + //} } - private static int GetApiVersion() - => CurrentApiVersion; - - private void ApplyAll(string customization, string characterName) - { - var save = CharacterSave.FromString(customization); - foreach (var gameObject in _objectTable) - { - if (gameObject.Name.ToString() != characterName) - continue; - - var player = (Character)gameObject; - Glamourer.RevertableDesigns.Revert(player); - save.Apply(player); - Glamourer.Penumbra.UpdateCharacters(player, null); - break; - } - } - - private void ApplyAll(string customization, Character? character) - { - if (character == null) - return; - var save = CharacterSave.FromString(customization); - Glamourer.RevertableDesigns.Revert(character); - save.Apply(character); - Glamourer.Penumbra.UpdateCharacters(character, null); - } - - private void ApplyOnlyCustomization(string customization, string characterName) - { - var save = CharacterSave.FromString(customization); - foreach (var gameObject in _objectTable) - { - if (gameObject.Name.ToString() != characterName) - continue; - - var player = (Character)gameObject; - Glamourer.RevertableDesigns.Revert(player); - save.ApplyOnlyCustomizations(player); - Glamourer.Penumbra.UpdateCharacters(player, null); - break; - } - } - - private void ApplyOnlyCustomization(string customization, Character? character) - { - if (character == null) - return; - var save = CharacterSave.FromString(customization); - Glamourer.RevertableDesigns.Revert(character); - save.ApplyOnlyCustomizations(character); - Glamourer.Penumbra.UpdateCharacters(character, null); - } - - private void ApplyOnlyEquipment(string customization, string characterName) - { - var save = CharacterSave.FromString(customization); - foreach (var gameObject in _objectTable) - { - if (gameObject.Name.ToString() != characterName) - continue; - - var player = (Character)gameObject; - Glamourer.RevertableDesigns.Revert(player); - save.ApplyOnlyEquipment(player); - Glamourer.Penumbra.UpdateCharacters(player, null); - break; - } - } - - private void ApplyOnlyEquipment(string customization, Character? character) - { - if (character == null) - return; - var save = CharacterSave.FromString(customization); - Glamourer.RevertableDesigns.Revert(character); - save.ApplyOnlyEquipment(character); - Glamourer.Penumbra.UpdateCharacters(character, null); - } - - private void Revert(string characterName) - { - foreach (var gameObject in _objectTable) - { - if (gameObject.Name.ToString() != characterName) - continue; - - var player = (Character)gameObject; - Glamourer.RevertableDesigns.Revert(player); - Glamourer.Penumbra.UpdateCharacters(player, null); - return; - } - - Glamourer.RevertableDesigns.RevertByNameWithoutApplication(characterName); - } - - private void Revert(Character? character) - { - if (character == null) - return; - Glamourer.RevertableDesigns.Revert(character); - Glamourer.Penumbra.UpdateCharacters(character, null); - } - - private string? GetAllCustomization(Character? character) - { - if (character == null) - return null; - - CharacterSave save = new CharacterSave(); - save.LoadCharacter(character); - return save.ToBase64(); - } - - private string? GetAllCustomization(string characterName) - { - CharacterSave save = null!; - foreach (var gameObject in _objectTable) - { - if (gameObject.Name.ToString() != characterName) - continue; - - var player = (Character)gameObject; - save = new CharacterSave(); - save.LoadCharacter(player); - break; - } - - return save?.ToBase64() ?? null; - } + //private static int GetApiVersion() + // => CurrentApiVersion; + // + //private void ApplyAll(string customization, string characterName) + //{ + // var save = CharacterSave.FromString(customization); + // foreach (var gameObject in _objectTable) + // { + // if (gameObject.Name.ToString() != characterName) + // continue; + // + // var player = (Character)gameObject; + // Glamourer.RevertableDesigns.Revert(player); + // save.Apply(player); + // Glamourer.Penumbra.UpdateCharacters(player, null); + // break; + // } + //} + // + //private void ApplyAll(string customization, Character? character) + //{ + // if (character == null) + // return; + // var save = CharacterSave.FromString(customization); + // Glamourer.RevertableDesigns.Revert(character); + // save.Apply(character); + // Glamourer.Penumbra.UpdateCharacters(character, null); + //} + // + //private void ApplyOnlyCustomization(string customization, string characterName) + //{ + // var save = CharacterSave.FromString(customization); + // foreach (var gameObject in _objectTable) + // { + // if (gameObject.Name.ToString() != characterName) + // continue; + // + // var player = (Character)gameObject; + // Glamourer.RevertableDesigns.Revert(player); + // save.ApplyOnlyCustomizations(player); + // Glamourer.Penumbra.UpdateCharacters(player, null); + // break; + // } + //} + // + //private void ApplyOnlyCustomization(string customization, Character? character) + //{ + // if (character == null) + // return; + // var save = CharacterSave.FromString(customization); + // Glamourer.RevertableDesigns.Revert(character); + // save.ApplyOnlyCustomizations(character); + // Glamourer.Penumbra.UpdateCharacters(character, null); + //} + // + //private void ApplyOnlyEquipment(string customization, string characterName) + //{ + // var save = CharacterSave.FromString(customization); + // foreach (var gameObject in _objectTable) + // { + // if (gameObject.Name.ToString() != characterName) + // continue; + // + // var player = (Character)gameObject; + // Glamourer.RevertableDesigns.Revert(player); + // save.ApplyOnlyEquipment(player); + // Glamourer.Penumbra.UpdateCharacters(player, null); + // break; + // } + //} + // + //private void ApplyOnlyEquipment(string customization, Character? character) + //{ + // if (character == null) + // return; + // var save = CharacterSave.FromString(customization); + // Glamourer.RevertableDesigns.Revert(character); + // save.ApplyOnlyEquipment(character); + // Glamourer.Penumbra.UpdateCharacters(character, null); + //} + // + //private void Revert(string characterName) + //{ + // foreach (var gameObject in _objectTable) + // { + // if (gameObject.Name.ToString() != characterName) + // continue; + // + // var player = (Character)gameObject; + // Glamourer.RevertableDesigns.Revert(player); + // Glamourer.Penumbra.UpdateCharacters(player, null); + // return; + // } + // + // Glamourer.RevertableDesigns.RevertByNameWithoutApplication(characterName); + //} + // + //private void Revert(Character? character) + //{ + // if (character == null) + // return; + // Glamourer.RevertableDesigns.Revert(character); + // Glamourer.Penumbra.UpdateCharacters(character, null); + //} + // + //private string? GetAllCustomization(Character? character) + //{ + // if (character == null) + // return null; + // + // CharacterSave save = new CharacterSave(); + // save.LoadCharacter(character); + // return save.ToBase64(); + //} + // + //private string? GetAllCustomization(string characterName) + //{ + // CharacterSave save = null!; + // foreach (var gameObject in _objectTable) + // { + // if (gameObject.Name.ToString() != characterName) + // continue; + // + // var player = (Character)gameObject; + // save = new CharacterSave(); + // save.LoadCharacter(player); + // break; + // } + // + // return save?.ToBase64() ?? null; + //} } diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index 9ea1568..bc8c61c 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -2,8 +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 Glamourer.Structs; using ImGuiNET; using Penumbra.GameData.Enums; @@ -14,17 +13,17 @@ public class PenumbraAttach : IDisposable public const int RequiredPenumbraBreakingVersion = 4; public const int RequiredPenumbraFeatureVersion = 0; - private ICallGateSubscriber? _tooltipSubscriber; - private ICallGateSubscriber? _clickSubscriber; - private ICallGateSubscriber? _redrawSubscriberName; - private ICallGateSubscriber? _redrawSubscriberObject; - private ICallGateSubscriber? _drawObjectInfo; - private ICallGateSubscriber? _creatingCharacterBase; + private ICallGateSubscriber? _tooltipSubscriber; + 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 event Action? CreatingCharacterBase; public PenumbraAttach(bool attach) { @@ -61,7 +60,7 @@ public class PenumbraAttach : IDisposable _clickSubscriber = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.ChangedItemClick"); _creatingCharacterBase = - Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.CreatingCharacterBase"); + Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.CreatingCharacterBase"); _tooltipSubscriber.Subscribe(PenumbraTooltip); _clickSubscriber.Subscribe(PenumbraRightClick); _creatingCharacterBase.Subscribe(SubscribeCharacterBase); @@ -73,8 +72,8 @@ public class PenumbraAttach : IDisposable } } - private void SubscribeCharacterBase(IntPtr gameObject, string _, IntPtr customize, IntPtr equipment) - => CreatingCharacterBase?.Invoke(gameObject, customize, equipment); + private void SubscribeCharacterBase(IntPtr gameObject, string _, IntPtr modelId, IntPtr customize, IntPtr equipment) + => CreatingCharacterBase?.Invoke(gameObject, modelId, customize, equipment); public void Unattach() { @@ -111,27 +110,30 @@ public class PenumbraAttach : IDisposable if (button != MouseButton.Right || type != ChangedItemType.Item) return; - var gPose = Dalamud.Objects[Interface.GPoseObjectId] as Character; - var player = Dalamud.Objects[0] as Character; - var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(id)!; - var writeItem = new Item(item, string.Empty); - if (gPose != null) - { - writeItem.Write(gPose.Address); - UpdateCharacters(gPose, player); - } - else if (player != null) - { - writeItem.Write(player.Address); - UpdateCharacters(player); - } + //var gPose = ObjectManager.GPosePlayer; + //var player = ObjectManager.Player; + //var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(id)!; + //var writeItem = new Item(item, string.Empty); + //if (gPose != null) + //{ + // writeItem.Write(gPose.Address); + // UpdateCharacters(gPose, player); + //} + //else if (player != null) + //{ + // writeItem.Write(player.Address); + // UpdateCharacters(player); + //} } 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) + public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat) { + if (actor == null) + return; + if (_redrawSubscriberObject != null) { try @@ -166,15 +168,13 @@ public class PenumbraAttach : IDisposable // then manually redraw using Penumbra. public void UpdateCharacters(Character character, Character? gPoseOriginalCharacter = null) { - var newEquip = Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character); - RedrawObject(character, RedrawType.Redraw, true); - - // Special case for carrying over changes to the gPose player to the regular player, too. - if (gPoseOriginalCharacter == null) - return; - - newEquip.Write(gPoseOriginalCharacter.Address); - Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(gPoseOriginalCharacter); - RedrawObject(gPoseOriginalCharacter, RedrawType.AfterGPose, false); + //RedrawObject(character, RedrawType.Redraw, true); + // + //// Special case for carrying over changes to the gPose player to the regular player, too. + //if (gPoseOriginalCharacter == null) + // return; + // + //newEquip.Write(gPoseOriginalCharacter.Address); + //RedrawObject(gPoseOriginalCharacter, RedrawType.AfterGPose, false); } } diff --git a/Glamourer/CharacterSave.cs b/Glamourer/CharacterSave.cs index c9dd7ea..83c9b7d 100644 --- a/Glamourer/CharacterSave.cs +++ b/Glamourer/CharacterSave.cs @@ -1,265 +1,118 @@ using System; -using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; -using Dalamud.Game.ClientState.Objects.Types; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; +using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Functions = Penumbra.GameData.Util.Functions; namespace Glamourer; -public class CharacterSaveConverter : JsonConverter +public class CharacterSaveConverter : JsonConverter { - public override bool CanConvert(Type objectType) - => objectType == typeof(CharacterSave); + public override void WriteJson(JsonWriter writer, CharacterSave value, JsonSerializer serializer) + { + var s = value.ToBase64(); + serializer.Serialize(writer, s); + } - public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + public override CharacterSave ReadJson(JsonReader reader, Type objectType, CharacterSave existingValue, bool hasExistingValue, + JsonSerializer serializer) { var token = JToken.Load(reader); var s = token.ToObject(); return CharacterSave.FromString(s!); } - - public override bool CanWrite - => true; - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value != null) - { - var s = ((CharacterSave)value).ToBase64(); - serializer.Serialize(writer, s); - } - } } [JsonConverter(typeof(CharacterSaveConverter))] -public class CharacterSave +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public unsafe struct CharacterSave { - public const byte CurrentVersion = 2; - public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes; - public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1; + [Flags] + public enum SaveFlags : byte + { + WriteCustomizations = 0x01, + IsWet = 0x02, + SetHatState = 0x04, + SetWeaponState = 0x08, + SetVisorState = 0x10, + HatState = 0x20, + WeaponState = 0x40, + VisorState = 0x80, + } - public const byte TotalSize = TotalSizeVersion2; + public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes; + public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes + 4 + 1; - private readonly byte[] _bytes = new byte[TotalSize]; + public const byte CurrentVersion = 3; + public byte Version = CurrentVersion; + public SaveFlags Flags = 0; + public CharacterEquipMask Equip = 0; + public CharacterWeapon MainHand = default; + public CharacterWeapon OffHand = default; + public ushort Padding = 0; + public CharacterArmor Head = default; + public CharacterArmor Body = default; + public CharacterArmor Hands = default; + public CharacterArmor Legs = default; + public CharacterArmor Feet = default; + public CharacterArmor Ears = default; + public CharacterArmor Neck = default; + public CharacterArmor Wrist = default; + public CharacterArmor RFinger = default; + public CharacterArmor LFinger = default; + private CustomizationData CustomizationData = CustomizationData.Default; + public float Alpha = 1f; public CharacterSave() - { - _bytes[0] = CurrentVersion; - Alpha = 1.0f; - } + { } - public CharacterSave Copy() + public void Load(Actor actor) { - var ret = new CharacterSave(); - _bytes.CopyTo((Span)ret._bytes); - return ret; - } + if (!actor.IsHuman || actor.Pointer->GameObject.DrawObject == null) + return; - public byte Version - => _bytes[0]; - - public bool WriteCustomizations - { - get => (_bytes[1] & 0x01) != 0; - set => _bytes[1] = (byte)(value ? _bytes[1] | 0x01 : _bytes[1] & ~0x01); - } - - public bool IsWet - { - get => (_bytes[1] & 0x02) != 0; - set => _bytes[1] = (byte)(value ? _bytes[1] | 0x02 : _bytes[1] & ~0x02); - } - - public bool SetHatState - { - get => (_bytes[1] & 0x04) != 0; - set => _bytes[1] = (byte)(value ? _bytes[1] | 0x04 : _bytes[1] & ~0x04); - } - - public bool SetWeaponState - { - get => (_bytes[1] & 0x08) != 0; - set => _bytes[1] = (byte)(value ? _bytes[1] | 0x08 : _bytes[1] & ~0x08); - } - - public bool SetVisorState - { - get => (_bytes[1] & 0x10) != 0; - set => _bytes[1] = (byte)(value ? _bytes[1] | 0x10 : _bytes[1] & ~0x10); - } - - public bool WriteProtected - { - get => (_bytes[1] & 0x20) != 0; - set => _bytes[1] = (byte)(value ? _bytes[1] | 0x20 : _bytes[1] & ~0x20); - } - - public byte StateFlags - { - get => _bytes[64 + CharacterCustomization.CustomizationBytes]; - set => _bytes[64 + CharacterCustomization.CustomizationBytes] = value; - } - - public bool HatState - { - get => (StateFlags & 0x01) == 0; - set => StateFlags = (byte)(value ? StateFlags & ~0x01 : StateFlags | 0x01); - } - - public bool VisorState - { - get => (StateFlags & 0x10) != 0; - set => StateFlags = (byte)(value ? StateFlags | 0x10 : StateFlags & ~0x10); - } - - public bool WeaponState - { - get => (StateFlags & 0x02) == 0; - set => StateFlags = (byte)(value ? StateFlags & ~0x02 : StateFlags | 0x02); - } - - public CharacterEquipMask WriteEquipment - { - get => (CharacterEquipMask)(_bytes[2] | (_bytes[3] << 8)); - set + var human = (Human*)actor.Pointer->GameObject.DrawObject; + CustomizationData = *(CustomizationData*)human->CustomizeData; + fixed (void* equip = &Head) { - _bytes[2] = (byte)((ushort)value & 0xFF); - _bytes[3] = (byte)((ushort)value >> 8); + Functions.MemCpyUnchecked(equip, human->EquipSlotData, sizeof(CharacterArmor) * 10); } } - private static Dictionary Offsets() - { - var stainOffsetWeapon = (int)Marshal.OffsetOf("Stain"); - var stainOffsetEquip = (int)Marshal.OffsetOf("Stain"); - - (int, int, bool) ToOffsets(IntPtr offset, bool weapon) - { - var off = 4 + CharacterCustomization.CustomizationBytes + (int)offset; - return weapon ? (off, off + stainOffsetWeapon, weapon) : (off, off + stainOffsetEquip, weapon); - } - - return new Dictionary(12) - { - [EquipSlot.MainHand] = ToOffsets(Marshal.OffsetOf("MainHand"), true), - [EquipSlot.OffHand] = ToOffsets(Marshal.OffsetOf("OffHand"), true), - [EquipSlot.Head] = ToOffsets(Marshal.OffsetOf("Head"), false), - [EquipSlot.Body] = ToOffsets(Marshal.OffsetOf("Body"), false), - [EquipSlot.Hands] = ToOffsets(Marshal.OffsetOf("Hands"), false), - [EquipSlot.Legs] = ToOffsets(Marshal.OffsetOf("Legs"), false), - [EquipSlot.Feet] = ToOffsets(Marshal.OffsetOf("Feet"), false), - [EquipSlot.Ears] = ToOffsets(Marshal.OffsetOf("Ears"), false), - [EquipSlot.Neck] = ToOffsets(Marshal.OffsetOf("Neck"), false), - [EquipSlot.Wrists] = ToOffsets(Marshal.OffsetOf("Wrists"), false), - [EquipSlot.RFinger] = ToOffsets(Marshal.OffsetOf("RFinger"), false), - [EquipSlot.LFinger] = ToOffsets(Marshal.OffsetOf("LFinger"), false), - }; - } - - private static readonly IReadOnlyDictionary FieldOffsets = Offsets(); - - public bool WriteStain(EquipSlot slot, StainId stainId) - { - if (WriteProtected) - return false; - - var (_, stainOffset, _) = FieldOffsets[slot]; - if (_bytes[stainOffset] == (byte)stainId) - return false; - - _bytes[stainOffset] = stainId.Value; - return true; - } - - private bool WriteItem(int offset, SetId id, WeaponType type, ushort variant, bool weapon) - { - var idBytes = BitConverter.GetBytes(id.Value); - - static bool WriteIfDifferent(ref byte x, byte y) - { - if (x == y) - return false; - - x = y; - return true; - } - - var ret = WriteIfDifferent(ref _bytes[offset], idBytes[0]); - ret |= WriteIfDifferent(ref _bytes[offset + 1], idBytes[1]); - if (weapon) - { - var typeBytes = BitConverter.GetBytes(type.Value); - var variantBytes = BitConverter.GetBytes(variant); - ret |= WriteIfDifferent(ref _bytes[offset + 2], typeBytes[0]); - ret |= WriteIfDifferent(ref _bytes[offset + 3], typeBytes[1]); - ret |= WriteIfDifferent(ref _bytes[offset + 4], variantBytes[0]); - ret |= WriteIfDifferent(ref _bytes[offset + 5], variantBytes[1]); - } - else - { - ret |= WriteIfDifferent(ref _bytes[offset + 2], (byte)variant); - } - - return ret; - } - - public bool WriteItem(Item item) - { - if (WriteProtected) - return false; - - var (itemOffset, _, isWeapon) = FieldOffsets[item.EquippableTo]; - var (id, type, variant) = item.MainModel; - var ret = WriteItem(itemOffset, id, type, variant, isWeapon); - if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel) - { - var (subOffset, _, _) = FieldOffsets[EquipSlot.OffHand]; - var (subId, subType, subVariant) = item.SubModel; - ret |= WriteItem(subOffset, subId, subType, subVariant, true); - } - - return ret; - } - - public unsafe float Alpha + public CharacterCustomization Customize { get { - fixed (byte* ptr = &_bytes[60 + CharacterCustomization.CustomizationBytes]) + fixed (CustomizationData* ptr = &CustomizationData) { - return *(float*)ptr; + return new CharacterCustomization(ptr); } } - set + } + + public CharacterEquip Equipment + { + get { - fixed (byte* ptr = _bytes) + fixed (CharacterArmor* ptr = &Head) { - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 0) = *((byte*)&value + 0); - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 1) = *((byte*)&value + 1); - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 2) = *((byte*)&value + 2); - *(ptr + 60 + CharacterCustomization.CustomizationBytes + 3) = *((byte*)&value + 3); + return new CharacterEquip(ptr); } } } - public void Load(CharacterCustomization customization) - { - WriteCustomizations = true; - customization.WriteBytes(_bytes, 4); - } - - public void Load(CharacterEquipment equipment, CharacterEquipMask mask = CharacterEquipMask.All) - { - WriteEquipment = mask; - equipment.WriteBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); - } - public string ToBase64() - => Convert.ToBase64String(_bytes); + { + fixed (void* ptr = &this) + { + return Convert.ToBase64String(new ReadOnlySpan(ptr, sizeof(CharacterSave))); + } + } private static void CheckSize(int length, int requiredLength) { @@ -275,119 +128,41 @@ public class CharacterSave $"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}]."); } - private static void CheckCharacterMask(byte val1, byte val2) + public static CharacterSave FromString(string data) { - var mask = (CharacterEquipMask)(val1 | (val2 << 8)); - if (mask > CharacterEquipMask.All) - throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid value {mask} in byte 3 and 4."); - } - - public void LoadCharacter(Character a) - { - WriteCustomizations = true; - Load(new CharacterCustomization(a)); - - Load(new CharacterEquipment(a)); - - SetHatState = true; - SetVisorState = true; - SetWeaponState = true; - StateFlags = (byte)((a.IsHatVisible() ? 0x00 : 0x01) | (a.IsVisorToggled() ? 0x10 : 0x00) | (a.IsWeaponHidden() ? 0x02 : 0x00)); - - IsWet = a.IsWet(); - Alpha = a.Alpha(); - } - - - public void Apply(Character a) - { - Glamourer.RevertableDesigns.Add(a); - - if (WriteCustomizations) - Customizations.Write(a.Address); - if (WriteEquipment != CharacterEquipMask.None) - Equipment.Write(a.Address, WriteEquipment, WriteEquipment); - a.SetWetness(IsWet); - a.Alpha() = Alpha; - if (SetHatState) - a.SetHatVisible(HatState); - if (SetVisorState) - a.SetVisorToggled(VisorState); - if (SetWeaponState) - a.SetWeaponHidden(!WeaponState); - } - - public void ApplyOnlyEquipment(Character a) - { - var oldState = _bytes[1]; - WriteCustomizations = false; - SetHatState = false; - SetVisorState = false; - SetWeaponState = false; - Apply(a); - _bytes[1] = oldState; - } - - public void ApplyOnlyCustomizations(Character a) - { - var oldState = _bytes[1]; - SetHatState = false; - SetVisorState = false; - SetWeaponState = false; - var oldEquip = WriteEquipment; - WriteEquipment = CharacterEquipMask.None; - Apply(a); - _bytes[1] = oldState; - WriteEquipment = oldEquip; - } - - public void Load(string base64) - { - var bytes = Convert.FromBase64String(base64); - switch (bytes[0]) + var bytes = Convert.FromBase64String(data); + var ret = new CharacterSave(); + fixed (byte* ptr = bytes) { - case 1: - CheckSize(bytes.Length, TotalSizeVersion1); - CheckRange(2, bytes[1], 0, 1); - Alpha = 1.0f; - bytes[0] = CurrentVersion; - break; - case 2: - CheckSize(bytes.Length, TotalSizeVersion2); - CheckRange(2, bytes[1], 0, 0x3F); - break; - default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); - } - - CheckCharacterMask(bytes[2], bytes[3]); - bytes.CopyTo(_bytes, 0); - } - - public static CharacterSave FromString(string base64) - { - var ret = new CharacterSave(); - ret.Load(base64); - return ret; - } - - public unsafe ref CharacterCustomization Customizations - { - get - { - fixed (byte* ptr = _bytes) + switch (bytes[0]) { - return ref *(CharacterCustomization*)(ptr + 4); + case 1: + CheckSize(bytes.Length, TotalSizeVersion1); + CheckRange(2, bytes[1], 0, 1); + Functions.MemCpyUnchecked(&ret, ptr, TotalSizeVersion1); + ret.Version = CurrentVersion; + ret.Alpha = 1f; + break; + case 2: + CheckSize(bytes.Length, TotalSizeVersion2); + CheckRange(2, bytes[1], 0, 0x3F); + Functions.MemCpyUnchecked(&ret, ptr, TotalSizeVersion2 - 1); + ret.Flags &= ~SaveFlags.HatState; + if ((bytes.Last() & 0x01) != 0) + ret.Flags |= SaveFlags.HatState; + if ((bytes.Last() & 0x02) != 0) + ret.Flags |= SaveFlags.WeaponState; + if ((bytes.Last() & 0x04) != 0) + ret.Flags |= SaveFlags.VisorState; + break; + case 3: + CheckSize(bytes.Length, sizeof(CharacterSave)); + Functions.MemCpyUnchecked(&ret, ptr, sizeof(CharacterSave)); + break; + default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); } } - } - public CharacterEquipment Equipment - { - get - { - var ret = new CharacterEquipment(); - ret.FromBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes); - return ret; - } + return ret; } } diff --git a/Glamourer/CustomizeExtensions.cs b/Glamourer/CustomizeExtensions.cs new file mode 100644 index 0000000..0c467df --- /dev/null +++ b/Glamourer/CustomizeExtensions.cs @@ -0,0 +1,124 @@ +using System; +using Glamourer.Customization; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer; + +public static unsafe class CustomizeExtensions +{ + // In languages other than english the actual clan name may depend on gender. + public static string ClanName(SubRace race, Gender gender) + { + if (gender == Gender.FemaleNpc) + gender = Gender.Female; + if (gender == Gender.MaleNpc) + gender = Gender.Male; + return (gender, race) switch + { + (Gender.Male, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderM), + (Gender.Male, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderM), + (Gender.Male, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodM), + (Gender.Male, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightM), + (Gender.Male, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkM), + (Gender.Male, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkM), + (Gender.Male, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunM), + (Gender.Male, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonM), + (Gender.Male, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfM), + (Gender.Male, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardM), + (Gender.Male, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenM), + (Gender.Male, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaM), + (Gender.Male, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM), + (Gender.Male, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM), + (Gender.Male, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaM), + (Gender.Male, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaM), + (Gender.Female, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderF), + (Gender.Female, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderF), + (Gender.Female, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodF), + (Gender.Female, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightF), + (Gender.Female, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkF), + (Gender.Female, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkF), + (Gender.Female, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunF), + (Gender.Female, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonF), + (Gender.Female, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfF), + (Gender.Female, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardF), + (Gender.Female, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenF), + (Gender.Female, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaF), + (Gender.Female, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM), + (Gender.Female, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM), + (Gender.Female, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaF), + (Gender.Female, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaF), + _ => throw new ArgumentOutOfRangeException(nameof(race), race, null), + }; + } + + public static string ClanName(this CharacterCustomization customize) + => ClanName(customize.Clan, customize.Gender); + + // Change a gender and fix up all required customizations afterwards. + public static bool ChangeGender(this CharacterCustomization customize, Gender gender, CharacterEquip equip) + { + if (customize.Gender == gender) + return false; + + customize.Gender = gender; + FixUpAttributes(customize, equip); + + return true; + } + + // Change a race and fix up all required customizations afterwards. + public static bool ChangeRace(this CharacterCustomization customize, SubRace clan, CharacterEquip equip) + { + if (customize.Clan == clan) + return false; + + var race = clan.ToRace(); + customize.Race = race; + customize.Clan = clan; + + // TODO: Female Hrothgar + if (race == Race.Hrothgar) + customize.Gender = Gender.Male; + + FixUpAttributes(customize, equip); + + return true; + } + + // Go through a whole customization struct and fix up all settings that need fixing. + private static void FixUpAttributes(CharacterCustomization customize, CharacterEquip equip) + { + var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); + foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId))) + { + switch (id) + { + case CustomizationId.Race: break; + case CustomizationId.Clan: break; + case CustomizationId.BodyType: break; + case CustomizationId.Gender: break; + case CustomizationId.FacialFeaturesTattoos: break; + case CustomizationId.HighlightsOnFlag: break; + case CustomizationId.Face: break; + default: + var count = set.Count(id); + if (set.DataByValue(id, customize[id], out _) < 0) + customize[id] = count == 0 ? (byte)0 : set.Data(id, 0).Value; + break; + } + } + + if (!equip) + return; + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var item = equip[slot]; + var (replaced, newSet, newVariant) = + Glamourer.RestrictedGear.ResolveRestricted(item.Set, item.Variant, slot, customize.Race, customize.Gender); + if (replaced) + equip[slot] = new CharacterArmor(newSet, newVariant, item.Stain); + } + } +} diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 9251a73..5a9fdc0 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,22 +1,16 @@ -using Glamourer.FileSystem; +using System; -namespace Glamourer.Designs +namespace Glamourer.Designs; + +public class Design { - public class Design : IFileSystemBase - { - public Folder Parent { get; set; } - public string Name { get; set; } + public string Name { get; } + public bool ReadOnly; - public CharacterSave Data { get; set; } + public DateTimeOffset CreationDate { get; } + public DateTimeOffset LastUpdateDate { get; } + public CharacterSave Data { get; } - internal Design(Folder parent, string name) - { - Parent = parent; - Name = name; - Data = new CharacterSave(); - } - - public override string ToString() - => Name; - } + public override string ToString() + => Name; } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 31fefcf..72a74f2 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -3,157 +3,155 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Logging; -using Glamourer.FileSystem; using Newtonsoft.Json; -namespace Glamourer.Designs +namespace Glamourer.Designs; + +public class DesignManager { - public class DesignManager - { - public const string FileName = "Designs.json"; - private readonly FileInfo _saveFile; - - public SortedList Designs = null!; - public FileSystem.FileSystem FileSystem { get; } = new(); - - public DesignManager() - { - var saveFolder = new DirectoryInfo(Dalamud.PluginInterface.GetPluginConfigDirectory()); - if (!saveFolder.Exists) - Directory.CreateDirectory(saveFolder.FullName); - - _saveFile = new FileInfo(Path.Combine(saveFolder.FullName, FileName)); - - LoadFromFile(); - } - - private void BuildStructure() - { - FileSystem.Clear(); - var anyChanges = false; - foreach (var (path, save) in Designs.ToArray()) - { - try - { - var (folder, name) = FileSystem.CreateAllFolders(path); - var design = new Design(folder, name) { Data = save }; - folder.FindOrAddChild(design); - var fixedPath = design.FullName(); - if (string.Equals(fixedPath, path, StringComparison.InvariantCultureIgnoreCase)) - continue; - - Designs.Remove(path); - Designs[fixedPath] = save; - anyChanges = true; - PluginLog.Debug($"Problem loading saved designs, {path} was renamed to {fixedPath}."); - } - catch (Exception e) - { - PluginLog.Error($"Problem loading saved designs, {path} was removed because:\n{e}"); - Designs.Remove(path); - } - } - - if (anyChanges) - SaveToFile(); - } - - private bool UpdateRoot(string oldPath, Design child) - { - var newPath = child.FullName(); - if (string.Equals(newPath, oldPath, StringComparison.InvariantCultureIgnoreCase)) - return false; - - Designs.Remove(oldPath); - Designs[child.FullName()] = child.Data; - return true; - } - - private void UpdateChild(string oldRootPath, string newRootPath, Design child) - { - var newPath = child.FullName(); - var oldPath = $"{oldRootPath}{newPath.Remove(0, newRootPath.Length)}"; - Designs.Remove(oldPath); - Designs[newPath] = child.Data; - } - - public void DeleteAllChildren(IFileSystemBase root, bool deleteEmpty) - { - if (root is Folder f) - foreach (var child in f.AllLeaves(SortMode.Lexicographical)) - Designs.Remove(child.FullName()); - var fullPath = root.FullName(); - root.Parent.RemoveChild(root, deleteEmpty); - Designs.Remove(fullPath); - - SaveToFile(); - } - - public void UpdateAllChildren(string oldPath, IFileSystemBase root) - { - var changes = false; - switch (root) - { - case Design d: - changes |= UpdateRoot(oldPath, d); - break; - case Folder f: - { - var newRootPath = root.FullName(); - if (!string.Equals(oldPath, newRootPath, StringComparison.InvariantCultureIgnoreCase)) - { - changes = true; - foreach (var descendant in f.AllLeaves(SortMode.Lexicographical).Where(l => l is Design).Cast()) - UpdateChild(oldPath, newRootPath, descendant); - } - - break; - } - } - - if (changes) - SaveToFile(); - } - - public void SaveToFile() - { - try - { - var data = JsonConvert.SerializeObject(Designs, Formatting.Indented); - File.WriteAllText(_saveFile.FullName, data); - } - catch (Exception e) - { - PluginLog.Error($"Could not write to save file {_saveFile.FullName}:\n{e}"); - } - } - - public void LoadFromFile() - { - _saveFile.Refresh(); - SortedList? designs = null; - if (_saveFile.Exists) - try - { - var data = File.ReadAllText(_saveFile.FullName); - designs = JsonConvert.DeserializeObject>(data); - } - catch (Exception e) - { - PluginLog.Error($"Could not load save file {_saveFile.FullName}:\n{e}"); - } - - if (designs == null) - { - Designs = new SortedList(); - SaveToFile(); - } - else - { - Designs = designs; - } - - BuildStructure(); - } - } + //public const string FileName = "Designs.json"; + //private readonly FileInfo _saveFile; + // + //public SortedList Designs = null!; + //public FileSystem.FileSystem FileSystem { get; } = new(); + // + //public DesignManager() + //{ + // var saveFolder = new DirectoryInfo(Dalamud.PluginInterface.GetPluginConfigDirectory()); + // if (!saveFolder.Exists) + // Directory.CreateDirectory(saveFolder.FullName); + // + // _saveFile = new FileInfo(Path.Combine(saveFolder.FullName, FileName)); + // + // LoadFromFile(); + //} + // + //private void BuildStructure() + //{ + // FileSystem.Clear(); + // var anyChanges = false; + // foreach (var (path, save) in Designs.ToArray()) + // { + // try + // { + // var (folder, name) = FileSystem.CreateAllFolders(path); + // var design = new Design(folder, name) { Data = save }; + // folder.FindOrAddChild(design); + // var fixedPath = design.FullName(); + // if (string.Equals(fixedPath, path, StringComparison.InvariantCultureIgnoreCase)) + // continue; + // + // Designs.Remove(path); + // Designs[fixedPath] = save; + // anyChanges = true; + // PluginLog.Debug($"Problem loading saved designs, {path} was renamed to {fixedPath}."); + // } + // catch (Exception e) + // { + // PluginLog.Error($"Problem loading saved designs, {path} was removed because:\n{e}"); + // Designs.Remove(path); + // } + // } + // + // if (anyChanges) + // SaveToFile(); + //} + // + //private bool UpdateRoot(string oldPath, Design child) + //{ + // var newPath = child.FullName(); + // if (string.Equals(newPath, oldPath, StringComparison.InvariantCultureIgnoreCase)) + // return false; + // + // Designs.Remove(oldPath); + // Designs[child.FullName()] = child.Data; + // return true; + //} + // + //private void UpdateChild(string oldRootPath, string newRootPath, Design child) + //{ + // var newPath = child.FullName(); + // var oldPath = $"{oldRootPath}{newPath.Remove(0, newRootPath.Length)}"; + // Designs.Remove(oldPath); + // Designs[newPath] = child.Data; + //} + // + //public void DeleteAllChildren(IFileSystemBase root, bool deleteEmpty) + //{ + // if (root is Folder f) + // foreach (var child in f.AllLeaves(SortMode.Lexicographical)) + // Designs.Remove(child.FullName()); + // var fullPath = root.FullName(); + // root.Parent.RemoveChild(root, deleteEmpty); + // Designs.Remove(fullPath); + // + // SaveToFile(); + //} + // + //public void UpdateAllChildren(string oldPath, IFileSystemBase root) + //{ + // var changes = false; + // switch (root) + // { + // case Design d: + // changes |= UpdateRoot(oldPath, d); + // break; + // case Folder f: + // { + // var newRootPath = root.FullName(); + // if (!string.Equals(oldPath, newRootPath, StringComparison.InvariantCultureIgnoreCase)) + // { + // changes = true; + // foreach (var descendant in f.AllLeaves(SortMode.Lexicographical).Where(l => l is Design).Cast()) + // UpdateChild(oldPath, newRootPath, descendant); + // } + // + // break; + // } + // } + // + // if (changes) + // SaveToFile(); + //} + // + //public void SaveToFile() + //{ + // try + // { + // var data = JsonConvert.SerializeObject(Designs, Formatting.Indented); + // File.WriteAllText(_saveFile.FullName, data); + // } + // catch (Exception e) + // { + // PluginLog.Error($"Could not write to save file {_saveFile.FullName}:\n{e}"); + // } + //} + // + //public void LoadFromFile() + //{ + // _saveFile.Refresh(); + // SortedList? designs = null; + // if (_saveFile.Exists) + // try + // { + // var data = File.ReadAllText(_saveFile.FullName); + // designs = JsonConvert.DeserializeObject>(data); + // } + // catch (Exception e) + // { + // PluginLog.Error($"Could not load save file {_saveFile.FullName}:\n{e}"); + // } + // + // if (designs == null) + // { + // Designs = new SortedList(); + // SaveToFile(); + // } + // else + // { + // Designs = designs; + // } + // + // BuildStructure(); + //} } diff --git a/Glamourer/Designs/FixedDesigns.cs b/Glamourer/Designs/FixedDesigns.cs index de00ae6..838d1c7 100644 --- a/Glamourer/Designs/FixedDesigns.cs +++ b/Glamourer/Designs/FixedDesigns.cs @@ -1,193 +1,188 @@ using System; using System.Collections.Generic; using System.Linq; -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; -namespace Glamourer.Designs +namespace Glamourer.Designs; + +public class FixedDesigns : IDisposable { - public class FixedDesigns : IDisposable + //public class FixedDesign + //{ + // public string Name; + // public JobGroup Jobs; + // public Design Design; + // public bool Enabled; + // + // public GlamourerConfig.FixedDesign ToSave() + // => new() + // { + // Name = Name, + // Path = Design.FullName(), + // Enabled = Enabled, + // JobGroups = Jobs.Id, + // }; + // + // public FixedDesign(string name, Design design, bool enabled, JobGroup jobs) + // { + // Name = name; + // Design = design; + // Enabled = enabled; + // Jobs = jobs; + // } + //} + // + //public List Data; + //public Dictionary> EnabledDesigns; + //public readonly IReadOnlyDictionary JobGroups; + // + //public bool EnableDesign(FixedDesign design) + //{ + // var changes = !design.Enabled; + // + // if (!EnabledDesigns.TryGetValue(design.Name, out var designs)) + // { + // EnabledDesigns[design.Name] = new List { design }; + // // TODO + // changes = true; + // } + // else if (!designs.Contains(design)) + // { + // designs.Add(design); + // changes = true; + // } + // + // design.Enabled = true; + // // TODO + // //if (Glamourer.Config.ApplyFixedDesigns) + // //{ + // // var character = + // // CharacterFactory.Convert(Dalamud.Objects.FirstOrDefault(o + // // => o.ObjectKind == ObjectKind.Player && o.Name.ToString() == design.Name)); + // // if (character != null) + // // OnPlayerChange(character); + // //} + // + // return changes; + //} + // + //public bool DisableDesign(FixedDesign design) + //{ + // if (!design.Enabled) + // return false; + // + // design.Enabled = false; + // if (!EnabledDesigns.TryGetValue(design.Name, out var designs)) + // return false; + // if (!designs.Remove(design)) + // return false; + // + // if (designs.Count == 0) + // { + // EnabledDesigns.Remove(design.Name); + // // TODO + // } + // + // return true; + //} + // + //public FixedDesigns(DesignManager designs) + //{ + // JobGroups = GameData.JobGroups(Dalamud.GameData); + // Data = new List(Glamourer.Config.FixedDesigns.Count); + // EnabledDesigns = new Dictionary>(Glamourer.Config.FixedDesigns.Count); + // var changes = false; + // for (var i = 0; i < Glamourer.Config.FixedDesigns.Count; ++i) + // { + // var save = Glamourer.Config.FixedDesigns[i]; + // if (designs.FileSystem.Find(save.Path, out var d) && d is Design design) + // { + // if (!JobGroups.TryGetValue((ushort)save.JobGroups, out var jobGroup)) + // jobGroup = JobGroups[1]; + // Data.Add(new FixedDesign(save.Name, design, save.Enabled, jobGroup)); + // if (save.Enabled) + // changes |= EnableDesign(Data.Last()); + // } + // else + // { + // PluginLog.Warning($"{save.Path} does not exist anymore, removing {save.Name} from fixed designs."); + // Glamourer.Config.FixedDesigns.RemoveAt(i--); + // changes = true; + // } + // } + // + // if (changes) + // Glamourer.Config.Save(); + //} + // + //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); + //} + // + //public void Add(string name, Design design, JobGroup group, bool enabled = false) + //{ + // Data.Add(new FixedDesign(name, design, enabled, group)); + // Glamourer.Config.FixedDesigns.Add(Data.Last().ToSave()); + // + // if (enabled) + // EnableDesign(Data.Last()); + // + // Glamourer.Config.Save(); + //} + // + //public void Remove(FixedDesign design) + //{ + // var idx = Data.IndexOf(design); + // if (idx < 0) + // return; + // + // Data.RemoveAt(idx); + // Glamourer.Config.FixedDesigns.RemoveAt(idx); + // if (design.Enabled) + // { + // EnabledDesigns.Remove(design.Name); + // // TODO + // } + // + // Glamourer.Config.Save(); + //} + // + //public void Move(FixedDesign design, int newIdx) + //{ + // if (newIdx < 0) + // newIdx = 0; + // if (newIdx >= Data.Count) + // newIdx = Data.Count - 1; + // + // var idx = Data.IndexOf(design); + // if (idx < 0 || idx == newIdx) + // return; + // + // Data.RemoveAt(idx); + // Data.Insert(newIdx, design); + // Glamourer.Config.FixedDesigns.RemoveAt(idx); + // Glamourer.Config.FixedDesigns.Insert(newIdx, design.ToSave()); + // Glamourer.Config.Save(); + //} + // + public void Dispose() { - public class FixedDesign - { - public string Name; - public JobGroup Jobs; - public Design Design; - public bool Enabled; - - public GlamourerConfig.FixedDesign ToSave() - => new() - { - Name = Name, - Path = Design.FullName(), - Enabled = Enabled, - JobGroups = Jobs.Id, - }; - - public FixedDesign(string name, Design design, bool enabled, JobGroup jobs) - { - Name = name; - Design = design; - Enabled = enabled; - Jobs = jobs; - } - } - - public List Data; - public Dictionary> EnabledDesigns; - public readonly IReadOnlyDictionary JobGroups; - - public bool EnableDesign(FixedDesign design) - { - var changes = !design.Enabled; - - if (!EnabledDesigns.TryGetValue(design.Name, out var designs)) - { - EnabledDesigns[design.Name] = new List { design }; - Glamourer.PlayerWatcher.AddPlayerToWatch(design.Name); - changes = true; - } - else if (!designs.Contains(design)) - { - designs.Add(design); - changes = true; - } - - design.Enabled = true; - if (Glamourer.Config.ApplyFixedDesigns) - { - var character = - CharacterFactory.Convert(Dalamud.Objects.FirstOrDefault(o - => o.ObjectKind == ObjectKind.Player && o.Name.ToString() == design.Name)); - if (character != null) - OnPlayerChange(character); - } - - return changes; - } - - public bool DisableDesign(FixedDesign design) - { - if (!design.Enabled) - return false; - - design.Enabled = false; - if (!EnabledDesigns.TryGetValue(design.Name, out var designs)) - return false; - if (!designs.Remove(design)) - return false; - - if (designs.Count == 0) - { - EnabledDesigns.Remove(design.Name); - Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name); - } - - return true; - } - - public FixedDesigns(DesignManager designs) - { - JobGroups = GameData.JobGroups(Dalamud.GameData); - Data = new List(Glamourer.Config.FixedDesigns.Count); - EnabledDesigns = new Dictionary>(Glamourer.Config.FixedDesigns.Count); - Glamourer.PlayerWatcher.PlayerChanged += OnPlayerChange; - var changes = false; - for (var i = 0; i < Glamourer.Config.FixedDesigns.Count; ++i) - { - var save = Glamourer.Config.FixedDesigns[i]; - if (designs.FileSystem.Find(save.Path, out var d) && d is Design design) - { - if (!JobGroups.TryGetValue((ushort) save.JobGroups, out var jobGroup)) - jobGroup = JobGroups[1]; - Data.Add(new FixedDesign(save.Name, design, save.Enabled, jobGroup)); - if (save.Enabled) - changes |= EnableDesign(Data.Last()); - } - else - { - PluginLog.Warning($"{save.Path} does not exist anymore, removing {save.Name} from fixed designs."); - Glamourer.Config.FixedDesigns.RemoveAt(i--); - changes = true; - } - } - - if (changes) - Glamourer.Config.Save(); - } - - 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); - } - - public void Add(string name, Design design, JobGroup group, bool enabled = false) - { - Data.Add(new FixedDesign(name, design, enabled, group)); - Glamourer.Config.FixedDesigns.Add(Data.Last().ToSave()); - - if (enabled) - EnableDesign(Data.Last()); - - Glamourer.Config.Save(); - } - - public void Remove(FixedDesign design) - { - var idx = Data.IndexOf(design); - if (idx < 0) - return; - - Data.RemoveAt(idx); - Glamourer.Config.FixedDesigns.RemoveAt(idx); - if (design.Enabled) - { - EnabledDesigns.Remove(design.Name); - Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name); - } - - Glamourer.Config.Save(); - } - - public void Move(FixedDesign design, int newIdx) - { - if (newIdx < 0) - newIdx = 0; - if (newIdx >= Data.Count) - newIdx = Data.Count - 1; - - var idx = Data.IndexOf(design); - if (idx < 0 || idx == newIdx) - return; - - Data.RemoveAt(idx); - Data.Insert(newIdx, design); - Glamourer.Config.FixedDesigns.RemoveAt(idx); - Glamourer.Config.FixedDesigns.Insert(newIdx, design.ToSave()); - Glamourer.Config.Save(); - } - - public void Dispose() - { - Glamourer.Config.FixedDesigns = Data.Select(d => d.ToSave()).ToList(); - Glamourer.Config.Save(); - } + //Glamourer.Config.FixedDesigns = Data.Select(d => d.ToSave()).ToList(); + //Glamourer.Config.Save(); } } diff --git a/Glamourer/Designs/RevertableDesigns.cs b/Glamourer/Designs/RevertableDesigns.cs index 14eab86..bfa0517 100644 --- a/Glamourer/Designs/RevertableDesigns.cs +++ b/Glamourer/Designs/RevertableDesigns.cs @@ -1,42 +1,40 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Generic; using Dalamud.Game.ClientState.Objects.Types; -namespace Glamourer.Designs +namespace Glamourer.Designs; + +public class RevertableDesigns { - public class RevertableDesigns + public readonly Dictionary Saves = new(); + + public bool Add(Character actor) { - public readonly ConcurrentDictionary Saves = new(); + //var name = actor.Name.ToString(); + //if (Saves.TryGetValue(name, out var save)) + // return false; + // + //save = new CharacterSave(); + //save.LoadCharacter(actor); + //Saves[name] = save; + return true; + } - public bool Add(Character actor) - { - var name = actor.Name.ToString(); - if (Saves.TryGetValue(name, out var save)) - return false; + public bool RevertByNameWithoutApplication(string actorName) + { + if (!Saves.ContainsKey(actorName)) + return false; - save = new CharacterSave(); - save.LoadCharacter(actor); - Saves[name] = save; - return true; - } + Saves.Remove(actorName); + return true; + } - public bool RevertByNameWithoutApplication(string actorName) - { - if (!Saves.ContainsKey(actorName)) - return false; - - Saves.Remove(actorName, out _); - return true; - } - - public bool Revert(Character actor) - { - if (!Saves.TryGetValue(actor.Name.ToString(), out var save)) - return false; - - save.Apply(actor); - Saves.Remove(actor.Name.ToString(), out _); - return true; - } + public bool Revert(Character actor) + { + //if (!Saves.TryGetValue(actor.Name.ToString(), out var save)) + // return false; + // + //save.Apply(actor); + //Saves.Remove(actor.Name.ToString()); + return true; } } diff --git a/Glamourer/FileSystem/FileSystem.cs b/Glamourer/FileSystem/FileSystem.cs deleted file mode 100644 index ca71f74..0000000 --- a/Glamourer/FileSystem/FileSystem.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Glamourer.FileSystem -{ - public class FileSystem - { - public Folder Root { get; } = Folder.CreateRoot(); - - public void Clear() - => Root.Children.Clear(); - - // Find a specific child by its path from Root. - // Returns true if the folder was found, and false if not. - // The out parameter will contain the furthest existing folder. - public bool Find(string path, out IFileSystemBase child) - { - var split = Split(path); - var folder = Root; - child = Root; - foreach (var part in split) - { - if (!folder.FindChild(part, out var c)) - { - child = folder; - return false; - } - - child = c; - if (c is not Folder f) - return part == split.Last(); - - folder = f; - } - - return true; - } - - public Folder CreateAllFolders(IEnumerable names) - { - var last = Root; - foreach (var name in names) - last = last.FindOrCreateSubFolder(name).Item1; - return last; - } - - public (Folder, string) CreateAllFolders(string path) - { - if (!path.Any()) - return (Root, string.Empty); - - var split = Split(path); - if (split.Length == 1) - return (Root, path); - - return (CreateAllFolders(split.Take(split.Length - 1)), split.Last()); - } - - public bool Rename(IFileSystemBase child, string newName) - { - if (ReferenceEquals(child, Root)) - throw new InvalidOperationException("Can not rename root."); - - newName = FixName(newName); - if (child.Name == newName) - return false; - - if (child.Parent.FindChild(newName, out var preExisting)) - { - if (MergeIfFolders(child, preExisting, false)) - return true; - - throw new Exception($"Can not rename {child.Name} in {child.Parent.FullName()} to {newName} because {newName} already exists."); - } - - var parent = child.Parent; - parent.RemoveChildIgnoreEmpty(child); - child.Name = newName; - parent.FindOrAddChild(child); - return true; - } - - public bool Move(IFileSystemBase child, Folder newParent, bool deleteEmpty) - { - var oldParent = child.Parent; - if (ReferenceEquals(newParent, oldParent)) - return false; - - // Moving into its own subfolder or itself is not allowed. - if (child.IsFolder(out var f) - && (ReferenceEquals(newParent, f) - || newParent.FullName().StartsWith(f.FullName(), StringComparison.InvariantCultureIgnoreCase))) - return false; - - if (newParent.FindChild(child.Name, out var conflict)) - { - if (MergeIfFolders(child, conflict, deleteEmpty)) - return true; - - throw new Exception($"Can not move {child.Name} into {newParent.FullName()} because {conflict.FullName()} already exists."); - } - - oldParent.RemoveChild(child, deleteEmpty); - newParent.FindOrAddChild(child); - return true; - } - - public bool Merge(Folder source, Folder target, bool deleteEmpty) - { - if (ReferenceEquals(source, target)) - return false; - - if (!source.Children.Any()) - { - if (deleteEmpty) - { - source.Parent.RemoveChild(source, true); - return true; - } - - return false; - } - - while (source.Children.Count > 0) - Move(source.Children.First(), target, deleteEmpty); // Can throw. - - source.Parent.RemoveChild(source, deleteEmpty); - - return true; - } - - private bool MergeIfFolders(IFileSystemBase source, IFileSystemBase target, bool deleteEmpty) - { - if (source is Folder childF && target.IsFolder(out var preF)) - { - Merge(childF, preF, deleteEmpty); - return true; - } - - return false; - } - - private static string[] Split(string path) - => path.Split(new[] - { - '/', - }, StringSplitOptions.RemoveEmptyEntries); - - private static string FixName(string name) - => name.Replace('/', '\\'); - } -} diff --git a/Glamourer/FileSystem/FileSystemImGui.cs b/Glamourer/FileSystem/FileSystemImGui.cs deleted file mode 100644 index 8c70368..0000000 --- a/Glamourer/FileSystem/FileSystemImGui.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using Dalamud.Logging; -using ImGuiNET; - -namespace Glamourer.FileSystem -{ - public static class FileSystemImGui - { - public const string DraggedObjectLabel = "FSDrag"; - - private static unsafe bool IsDropping(string name) - => ImGui.AcceptDragDropPayload(name).NativePtr != null; - - private static IFileSystemBase? _draggedObject; - - public static bool DragDropTarget(FileSystem fs, IFileSystemBase child, out string oldPath, out IFileSystemBase? draggedChild) - { - oldPath = string.Empty; - draggedChild = null; - var ret = false; - if (!ImGui.BeginDragDropTarget()) - return ret; - - if (IsDropping(DraggedObjectLabel)) - { - if (_draggedObject != null) - try - { - oldPath = _draggedObject.FullName(); - draggedChild = _draggedObject; - ret = fs.Move(_draggedObject, child.IsFolder(out var folder) ? folder : child.Parent, false); - } - catch (Exception e) - { - PluginLog.Error($"Could not drag {_draggedObject.Name} onto {child.FullName()}:\n{e}"); - } - - _draggedObject = null; - } - - ImGui.EndDragDropTarget(); - return ret; - } - - public static void DragDropSource(IFileSystemBase child) - { - if (!ImGui.BeginDragDropSource()) - return; - - ImGui.SetDragDropPayload(DraggedObjectLabel, IntPtr.Zero, 0); - ImGui.Text($"Moving {child.Name}..."); - _draggedObject = child; - ImGui.EndDragDropSource(); - } - } -} diff --git a/Glamourer/FileSystem/Folder.cs b/Glamourer/FileSystem/Folder.cs deleted file mode 100644 index de5d699..0000000 --- a/Glamourer/FileSystem/Folder.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; - -namespace Glamourer.FileSystem -{ - public enum SortMode - { - FoldersFirst = 0x00, - Lexicographical = 0x01, - } - - public class Folder : IFileSystemBase - { - public Folder Parent { get; set; } - public string Name { get; set; } - public readonly List Children = new(); - - public Folder(Folder parent, string name) - { - Parent = parent; - Name = name; - } - - public override string ToString() - => this.FullName(); - - // Return the number of all leaves with this folder in their path. - public int TotalDescendantLeaves() - { - var sum = 0; - foreach (var child in Children) - { - switch (child) - { - case Folder f: - sum += f.TotalDescendantLeaves(); - break; - case Link l: - sum += l.Data is Folder fl ? fl.TotalDescendantLeaves() : 1; - break; - default: - ++sum; - break; - } - } - - return sum; - } - - // Return all descendant mods in the specified order. - public IEnumerable AllLeaves(SortMode mode) - { - return GetSortedEnumerator(mode).SelectMany(f => - { - if (f.IsFolder(out var folder)) - return folder.AllLeaves(mode); - - return new[] - { - f, - }; - }); - } - - public IEnumerable AllChildren(SortMode mode) - => GetSortedEnumerator(mode); - - // Get an enumerator for actually sorted objects instead of folder-first objects. - private IEnumerable GetSortedEnumerator(SortMode mode) - { - switch (mode) - { - case SortMode.FoldersFirst: - foreach (var child in Children.Where(c => c.IsFolder())) - yield return child; - - foreach (var child in Children.Where(c => c.IsLeaf())) - yield return child; - - break; - case SortMode.Lexicographical: - foreach (var child in Children) - yield return child; - - break; - default: throw new InvalidEnumArgumentException(); - } - } - - internal static Folder CreateRoot() - => new(null!, ""); - - // Find a subfolder by name. Returns true and sets folder to it if it exists. - public bool FindChild(string name, out IFileSystemBase ret) - { - var idx = Search(name); - ret = idx >= 0 ? Children[idx] : this; - return idx >= 0; - } - - // Checks if an equivalent child to child already exists and returns its index. - // If it does not exist, inserts child as a child and returns the new index. - // Also sets this as childs parent. - public int FindOrAddChild(IFileSystemBase child) - { - var idx = Search(child); - if (idx >= 0) - return idx; - - idx = ~idx; - Children.Insert(idx, child); - child.Parent = this; - return idx; - } - - // Checks if an equivalent child to child already exists and throws if it does. - // If it does not exist, inserts child as a child and returns the new index. - // Also sets this as childs parent. - public int AddChild(IFileSystemBase child) - { - var idx = Search(child); - if (idx >= 0) - throw new Exception("Could not add child: Child of that name already exists."); - - idx = ~idx; - Children.Insert(idx, child); - child.Parent = this; - return idx; - } - - // Checks if a subfolder with the given name already exists and returns it and its index. - // If it does not exists, creates and inserts it and returns the new subfolder and its index. - public (Folder, int) FindOrCreateSubFolder(string name) - { - var subFolder = new Folder(this, name); - var idx = FindOrAddChild(subFolder); - var child = Children[idx]; - if (!child.IsFolder(out var folder)) - throw new Exception($"The child {name} already exists in {this.FullName()} but is not a folder."); - - return (folder, idx); - } - - // Remove child if it exists. - // If this folder is empty afterwards and deleteEmpty is true, remove it from its parent. - public void RemoveChild(IFileSystemBase child, bool deleteEmpty) - { - RemoveChildIgnoreEmpty(child); - if (deleteEmpty) - CheckEmpty(); - } - - private void CheckEmpty() - { - if (Children.Count == 0) - Parent?.RemoveChild(this, true); - } - - // Remove a child but do not remove this folder from its parent if it is empty afterwards. - internal void RemoveChildIgnoreEmpty(IFileSystemBase folder) - { - var idx = Search(folder); - if (idx < 0) - return; - - Children[idx].Parent = null!; - Children.RemoveAt(idx); - } - - private int Search(string name) - => Children.BinarySearch(new FileSystemObject(name), FolderStructureComparer.Default); - - private int Search(IFileSystemBase child) - => Children.BinarySearch(child, FolderStructureComparer.Default); - } -} diff --git a/Glamourer/FileSystem/IFolderStructure.cs b/Glamourer/FileSystem/IFolderStructure.cs deleted file mode 100644 index fc0cabd..0000000 --- a/Glamourer/FileSystem/IFolderStructure.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Glamourer.FileSystem -{ - internal class FolderStructureComparer : IComparer - { - // Compare only the direct folder names since this is only used inside an enumeration of children of one folder. - public static int Cmp(IFileSystemBase? x, IFileSystemBase? y) - => ReferenceEquals(x, y) ? 0 : string.Compare(x?.Name, y?.Name, StringComparison.InvariantCultureIgnoreCase); - - public int Compare(IFileSystemBase? x, IFileSystemBase? y) - => Cmp(x, y); - - internal static readonly FolderStructureComparer Default = new(); - } - - public interface IFileSystemBase - { - public Folder Parent { get; set; } - public string Name { get; set; } - } - - public static class FileSystemExtensions - { - public static string FullName(this IFileSystemBase data) - => data.Parent?.Name.Any() ?? false ? $"{data.Parent.FullName()}/{data.Name}" : data.Name; - - public static bool IsLeaf(this IFileSystemBase data) - => data is not Folder && data is not Link { Data: Folder }; - - public static bool IsFolder(this IFileSystemBase data) - => data.IsFolder(out _); - - public static bool IsFolder(this IFileSystemBase data, out Folder folder) - { - switch (data) - { - case Folder f: - folder = f; - return true; - case Link { Data: Folder fl }: - folder = fl; - return true; - default: - folder = null!; - return false; - } - } - } - - - public class FileSystemObject : IFileSystemBase - { - public FileSystemObject(string name) - => Name = name; - - public Folder Parent { get; set; } = null!; - public string Name { get; set; } - - public string FullName() - => Name; - } -} diff --git a/Glamourer/FileSystem/Link.cs b/Glamourer/FileSystem/Link.cs deleted file mode 100644 index e064982..0000000 --- a/Glamourer/FileSystem/Link.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Glamourer.Designs; - -namespace Glamourer.FileSystem -{ - public class Link : IFileSystemBase - { - public Folder Parent { get; set; } - - public string Name { get; set; } - - public IFileSystemBase Data { get; } - - public Link(Folder parent, string name, IFileSystemBase data) - { - Parent = parent; - Name = name; - Data = data; - } - } -} diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 651aed0..e67c812 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,262 +1,173 @@ -using System; -using System.Linq; +using System.Collections; +using System.ComponentModel.Design; +using System.Net.Sockets; using System.Reflection; -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Objects.Types; +using System.Runtime.InteropServices.ComTypes; +using Dalamud.Data; +using Dalamud.Game.ClientState.JobGauge.Enums; using Dalamud.Game.Command; using Dalamud.Hooking; +using Dalamud.Interface.Windowing; using Dalamud.Plugin; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.Attributes; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Component.Excel; using Glamourer.Api; using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.FileSystem; using Glamourer.Gui; -using ImGuiNET; -using Penumbra.GameData.ByteString; +using Lumina.Data.Parsing; +using Microsoft.VisualBasic.CompilerServices; +using OtterGui.Table; using Penumbra.GameData.Enums; -using Penumbra.PlayerWatch; +using Penumbra.GameData.Structs; +using static FFXIVClientStructs.FFXIV.Client.UI.Misc.RaptureMacroModule; +using static System.Collections.Specialized.BitVector32; +using static System.Reflection.Metadata.BlobBuilder; +using Race = Lumina.Excel.GeneratedSheets.Race; namespace Glamourer; -public unsafe class FixedDesignManager : IDisposable -{ - 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) - { - SignatureHelper.Initialise(this); - FixedDesigns = new FixedDesigns(designs); - - - if (Glamourer.Config.ApplyFixedDesigns) - Enable(); - } - - 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) - { - var name = new Utf8String(human->GameObject.Name).ToString(); - if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs)) - { - var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob)); - if (design != null) - { - if (design.Design.Data.WriteCustomizations) - *(CharacterCustomization*)customize = design.Design.Data.Customizations; - - var data = (uint*)equipData; - for (var i = 0u; i < 10; ++i) - { - 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, - }; - } - } - } - } - } - - 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],"; + private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],"; + private const string MainCommandString = "/glamourer"; + private const string ApplyCommandString = "/glamour"; 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 readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; - public static RevertableDesigns RevertableDesigns = new(); - public readonly GlamourerIpc GlamourerIpc; + public static readonly string CommitHash = + Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion ?? "Unknown"; - public static string Version = string.Empty; + public static GlamourerConfig Config = null!; + public static PenumbraAttach Penumbra = null!; - public Glamourer(DalamudPluginInterface pluginInterface) + public static ICustomizationManager Customization = null!; + public static RedrawManager RedrawManager = null!; + public static RestrictedGear RestrictedGear = null!; + private readonly WindowSystem _windowSystem = new("Glamourer"); + + private readonly Interface _interface; + //public readonly DesignManager Designs; + + //public static RevertableDesigns RevertableDesigns = new(); + //public readonly GlamourerIpc GlamourerIpc; + + public unsafe 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(); + Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage); + RestrictedGear = GameData.RestrictedGear(Dalamud.GameData); + var m = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage); + Config = GlamourerConfig.Load(); + Penumbra = new PenumbraAttach(Config.AttachToPenumbra); - Dalamud.Commands.AddHandler("/glamourer", new CommandInfo(OnGlamourer) + //Designs = new DesignManager(); + + //GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface); + RedrawManager = new RedrawManager(); + + Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window.", }); - Dalamud.Commands.AddHandler("/glamour", new CommandInfo(OnGlamour) + Dalamud.Commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) { HelpMessage = $"Use Glamourer Functions: {HelpString}", }); _interface = new Interface(this); + _windowSystem.AddWindow(_interface); + Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; + var x = 0x00011000u; + //FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x); + } + + + public void Dispose() + { + RedrawManager.Dispose(); + Penumbra.Dispose(); + Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw; + _interface.Dispose(); + //GlamourerIpc.Dispose(); + Dalamud.Commands.RemoveHandler(ApplyCommandString); + Dalamud.Commands.RemoveHandler(MainCommandString); } 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}"); - } - } + => _interface.Toggle(); + //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() @@ -265,73 +176,62 @@ public class Glamourer : IDalamudPlugin 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"); + //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; + //} } } diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 438ae51..43c9950 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -77,17 +77,12 @@ - - false - - - + - @@ -105,7 +100,17 @@ - + + + + + + + $(GitCommitHash) + + + + PreserveNewest diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs new file mode 100644 index 0000000..c606fb0 --- /dev/null +++ b/Glamourer/Gui/Interface.Actors.cs @@ -0,0 +1,464 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface; +using Dalamud.Logging; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Structs; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Raii; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui; + +internal partial class Interface +{ + private class ActorTab + { + private ObjectManager.ActorData _data = new(string.Empty, string.Empty, Actor.Null, false, Actor.Null); + private CharacterSave _character = new(); + private Actor _nextSelect = Actor.Null; + + public void Draw() + { + using var tab = ImRaii.TabItem("Actors"); + if (!tab) + return; + + DrawActorSelector(); + if (_data.Label.Length == 0) + return; + + ImGui.SameLine(); + + if (_data.Actor.IsHuman) + DrawActorPanel(); + else + DrawMonsterPanel(); + } + + private void DrawActorPanel() + { + using var group = ImRaii.Group(); + if (DrawCustomization(_character.Customize, _character.Equipment, !_data.Modifiable)) + { + Glamourer.RedrawManager.Set(_data.Actor.Address, _character); + Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true); + } + + if (ImGui.Button("Set Machinist Goggles")) + { + Glamourer.RedrawManager.ChangeEquip(_data.Actor.Address, EquipSlot.Head, new CharacterArmor(265, 1, 0)); + } + } + + private void DrawMonsterPanel() + { + using var group = ImRaii.Group(); + var currentModel = (uint)_data.Actor.ModelId; + var models = GameData.Models(Dalamud.GameData); + var currentData = models.Models.TryGetValue(currentModel, out var c) ? c.FirstName : $"#{currentModel}"; + using var combo = ImRaii.Combo("Model Id", currentData); + if (!combo) + return; + + foreach (var (id, data) in models.Models) + { + if (ImGui.Selectable(data.FirstName, id == currentModel) || id == currentModel) + { + _data.Actor.SetModelId((int)id); + Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true); + } + ImGuiUtil.HoverTooltip(data.AllNames); + } + } + + + private LowerString _actorFilter = LowerString.Empty; + + private void DrawActorSelector() + { + using var group = ImRaii.Group(); + var oldSpacing = ImGui.GetStyle().ItemSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + ImGui.SetNextItemWidth(_actorSelectorWidth); + LowerString.InputWithHint("##actorFilter", "Filter...", ref _actorFilter, 64); + using (var child = ImRaii.Child("##actorSelector", new Vector2(_actorSelectorWidth, -ImGui.GetFrameHeight()), true)) + { + if (!child) + return; + + _data.Actor = Actor.Null; + _data.GPose = Actor.Null; + _data.Modifiable = false; + + style.Push(ImGuiStyleVar.ItemSpacing, oldSpacing); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); + var remainder = ImGuiClip.FilteredClippedDraw(ObjectManager.GetEnumerator(), skips, CheckFilter, DrawSelectable); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); + style.Pop(); + } + + DrawSelectionButtons(); + } + + private void UpdateSelection(ObjectManager.ActorData data) + { + _data = data; + _character.Load(_data.Actor); + } + + private bool CheckFilter(ObjectManager.ActorData data) + { + if (_nextSelect && _nextSelect == data.Actor || data.Label == _data.Label) + UpdateSelection(data); + return data.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase); + } + + private void DrawSelectable(ObjectManager.ActorData data) + { + var equal = data.Label == _data.Label; + if (ImGui.Selectable(data.Label, equal) && !equal) + UpdateSelection(data); + } + + private void DrawSelectionButtons() + { + _nextSelect = Actor.Null; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + var buttonWidth = new Vector2(_actorSelectorWidth / 2, 0); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth + , "Select the local player character.", !ObjectManager.Player, true)) + _nextSelect = _inGPose ? ObjectManager.GPosePlayer : ObjectManager.Player; + ImGui.SameLine(); + Actor targetActor = Dalamud.Targets.Target?.Address; + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, + "Select the current target, if it is in the list.", _inGPose || !targetActor, true)) + _nextSelect = targetActor; + } + } + + private readonly ActorTab _actorTab = new(); +} + +//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() +// { +// 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 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 unsafe void ConditionalApply(CharacterSave save, FFXIVClientStructs.FFXIV.Client.Game.Character.Character* player) +// { +// //if (ImGui.GetIO().KeyShift) +// // save.ApplyOnlyCustomizations(player); +// //else if (ImGui.GetIO().KeyCtrl) +// // save.ApplyOnlyEquipment(player); +// //else +// // save.Apply(player); +// } +// +// private static unsafe void ConditionalApply(CharacterSave save, Character player) +// => ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address); +// +// 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(); +// ImGuiUtil.HoverTooltip( +// "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment."); +// +// if (!applyButton) +// 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; +// } +// +// private void DrawSaveDesignButton() +// { +// ImGui.PushFont(UiBuilder.IconFont); +// if (ImGui.Button(FontAwesomeIcon.Save.ToIconString())) +// OpenDesignNamePopup(DesignNameUse.SaveCurrent); +// +// ImGui.PopFont(); +// ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment."); +// +// DrawDesignNamePopup(DesignNameUse.SaveCurrent); +// } +// +// private void DrawTargetPlayerButton() +// { +// if (ImGui.Button("Target Player")) +// Dalamud.Targets.SetTarget(_player); +// } +// +// private unsafe void DrawApplyToPlayerButton(CharacterSave save) +// { +// if (!ImGui.Button("Apply to Self")) +// return; +// +// var player = _inGPose +// ? (Character?)Dalamud.Objects[GPoseObjectId] +// : Dalamud.ClientState.LocalPlayer; +// var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null; +// if (player == null) +// return; +// +// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address); +// if (_inGPose) +// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)fallback!.Address); +// Glamourer.Penumbra.UpdateCharacters(player, fallback); +// } +// +// +// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* TransformToCustomizable( +// FFXIVClientStructs.FFXIV.Client.Game.Character.Character* actor) +// { +// if (actor == null) +// return null; +// +// if (actor->ModelCharaId == 0) +// return actor; +// +// actor->ModelCharaId = 0; +// CharacterCustomization.Default.Write(actor); +// return actor; +// } +// +// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Convert(GameObject? actor) +// { +// return actor switch +// { +// null => null, +// PlayerCharacter p => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)p.Address, +// BattleChara b => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)b.Address, +// _ => actor.ObjectKind switch +// { +// ObjectKind.BattleNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, +// ObjectKind.Companion => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, +// ObjectKind.Retainer => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, +// ObjectKind.EventNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, +// _ => null, +// }, +// }; +// } +// +// private unsafe void DrawApplyToTargetButton(CharacterSave save) +// { +// if (!ImGui.Button("Apply to Target")) +// return; +// +// var player = TransformToCustomizable(Convert(Dalamud.Targets.Target)); +// if (player == null) +// return; +// +// var fallBackCharacter = _gPoseActors.TryGetValue(new Utf8String(player->GameObject.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 +// { +// 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 unsafe 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!); +// } +// +// if (!_inGPose) +// { +// ImGui.SameLine(); +// DrawTargetPlayerButton(); +// } +// +// var currentModel = ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId; +// 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; +// +// ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId = 0; +// 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 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(); +// } +// +// private unsafe void DrawActorPanel() +// { +// using var group = ImRaii.Group(); +// DrawPlayerHeader(); +// using var child = ImRaii.Child("##playerData", -Vector2.One, true); +// if (!child) +// return; +// +// if (_player == null || ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player.Address)->ModelCharaId == 0) +// DrawPlayerPanel(); +// else +// DrawMonsterPanel(); +// } +//} diff --git a/Glamourer/Gui/Interface.Customization.cs b/Glamourer/Gui/Interface.Customization.cs new file mode 100644 index 0000000..1e3ceb0 --- /dev/null +++ b/Glamourer/Gui/Interface.Customization.cs @@ -0,0 +1,418 @@ +using System; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Logging; +using Glamourer.Customization; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.Enums; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer.Gui; + +internal partial class Interface +{ + private static byte _tempStorage; + private static CustomizationId _tempType; + + private static bool DrawCustomization(CharacterCustomization customize, CharacterEquip equip, bool locked) + { + if (!ImGui.CollapsingHeader("Character Customization")) + return false; + + var ret = DrawRaceGenderSelector(customize, equip, locked); + var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); + + foreach (var id in set.Order[CharaMakeParams.MenuType.Percentage]) + ret |= PercentageSelector(set, id, customize, locked); + + Functions.IteratePairwise(set.Order[CharaMakeParams.MenuType.IconSelector], c => DrawIconSelector(set, c, customize, locked), + ImGui.SameLine); + + ret |= DrawMultiIconSelector(set, customize, locked); + + foreach (var id in set.Order[CharaMakeParams.MenuType.ListSelector]) + ret |= DrawListSelector(set, id, customize, locked); + + Functions.IteratePairwise(set.Order[CharaMakeParams.MenuType.ColorPicker], c => DrawColorPicker(set, c, customize, locked), + ImGui.SameLine); + + ret |= Checkbox(set.Option(CustomizationId.HighlightsOnFlag), customize.HighlightsOn, b => customize.HighlightsOn = b, locked); + var xPos = _inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; + ImGui.SameLine(xPos); + ret |= Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {set.Option(CustomizationId.FacePaint)}", + customize.FacePaintReversed, b => customize.FacePaintReversed = b, locked); + ret |= Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}", + customize.SmallIris, b => customize.SmallIris = b, locked); + + if (customize.Race != Race.Hrothgar) + { + ImGui.SameLine(xPos); + ret |= Checkbox(set.Option(CustomizationId.LipColor), customize.Lipstick, b => customize.Lipstick = b, locked); + } + + return ret; + } + + private static bool DrawRaceGenderSelector(CharacterCustomization customize, CharacterEquip equip, bool locked) + { + var ret = DrawGenderSelector(customize, equip, locked); + ImGui.SameLine(); + using var group = ImRaii.Group(); + ret |= DrawRaceCombo(customize, equip, locked); + var gender = Glamourer.Customization.GetName(CustomName.Gender); + var clan = Glamourer.Customization.GetName(CustomName.Clan); + ImGui.TextUnformatted($"{gender} & {clan}"); + return ret; + } + + private static bool DrawGenderSelector(CharacterCustomization customize, CharacterEquip equip, bool locked) + { + using var font = ImRaii.PushFont(UiBuilder.IconFont); + var icon = customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus; + var restricted = customize.Race == Race.Hrothgar; + if (restricted) + icon = FontAwesomeIcon.MarsDouble; + + if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, restricted || locked, true)) + return false; + + var gender = customize.Gender == Gender.Male ? Gender.Female : Gender.Male; + return customize.ChangeGender(gender, locked ? CharacterEquip.Null : equip); + } + + private static bool DrawRaceCombo(CharacterCustomization customize, CharacterEquip equip, bool locked) + { + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); + ImGui.SetNextItemWidth(_raceSelectorWidth); + using var combo = ImRaii.Combo("##subRaceCombo", customize.ClanName()); + if (!combo) + return false; + + if (locked) + ImGui.CloseCurrentPopup(); + + var ret = false; + foreach (var subRace in Enum.GetValues().Skip(1)) // Skip Unknown + { + if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, customize.Gender), subRace == customize.Clan)) + ret |= customize.ChangeRace(subRace, equip); + } + + return ret; + } + + private static bool Checkbox(string label, bool current, Action setter, bool locked) + { + var tmp = current; + var ret = false; + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); + if (ImGui.Checkbox($"##{label}", ref tmp) && tmp == current && !locked) + { + setter(tmp); + ret = true; + } + + alpha.Pop(); + + ImGui.SameLine(); + ImGui.TextUnformatted(label); + + return ret; + } + + private static bool PercentageSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customization, bool locked) + { + using var bigGroup = ImRaii.Group(); + using var _ = ImRaii.PushId((int)id); + int value = id == _tempType ? _tempStorage : customization[id]; + var count = set.Count(id); + ImGui.SetNextItemWidth(_comboSelectorSize); + + var (min, max) = locked ? (value, value) : (0, count - 1); + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); + if (ImGui.SliderInt("##slider", ref value, min, max, string.Empty, ImGuiSliderFlags.AlwaysClamp) && !locked) + { + _tempStorage = (byte)value; + _tempType = id; + } + + var ret = ImGui.IsItemDeactivatedAfterEdit(); + + ImGui.SameLine(); + ret |= InputInt("##input", id, --value, min, max, locked); + + alpha.Pop(); + + ImGui.SameLine(); + ImGui.TextUnformatted(set.OptionName[(int)id]); + + if (ret) + customization[id] = _tempStorage; + + return ret; + } + + private static bool InputInt(string label, CustomizationId id, int startValue, int minValue, int maxValue, bool locked) + { + var tmp = startValue + 1; + ImGui.SetNextItemWidth(_inputIntSize); + if (ImGui.InputInt(label, ref tmp, 1, 1, ImGuiInputTextFlags.EnterReturnsTrue) + && !locked + && tmp != startValue + 1 + && tmp >= minValue + && tmp <= maxValue) + { + _tempType = id; + _tempStorage = (byte)(tmp - 1); + } + + var ret = ImGui.IsItemDeactivatedAfterEdit() && !locked; + if (!locked) + ImGuiUtil.HoverTooltip($"Input Range: [{minValue}, {maxValue}]"); + return ret; + } + + private static bool DrawIconSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked) + { + const string popupName = "Style Picker"; + + using var bigGroup = ImRaii.Group(); + using var _ = ImRaii.PushId((int)id); + var count = set.Count(id); + var label = set.Option(id); + + var current = set.DataByValue(id, _tempType == id ? _tempStorage : customize[id], out var custom); + if (current < 0) + { + label = $"{label} (Custom #{customize[id]})"; + current = 0; + custom = set.Data(id, 0); + } + + var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId); + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); + if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize) && !locked) + ImGui.OpenPopup(popupName); + + ImGuiUtil.HoverIconTooltip(icon, _iconSize); + + ImGui.SameLine(); + using var group = ImRaii.Group(); + var (min, max) = locked ? (current, current) : (1, count); + var ret = InputInt("##text", id, current, min, max, locked); + if (ret) + customize[id] = set.Data(id, _tempStorage).Value; + + ImGui.TextUnformatted($"{label} ({custom.Value.Value})"); + + ret |= DrawIconPickerPopup(popupName, set, id, customize); + + return ret; + } + + private static bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, CharacterCustomization customize) + { + using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); + if (!popup) + return false; + + var ret = false; + var count = set.Count(id); + 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); + var icon = Glamourer.Customization.GetIcon(custom.IconId); + using var group = ImRaii.Group(); + if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) + { + customize[id] = custom.Value; + ret = true; + ImGui.CloseCurrentPopup(); + } + + ImGuiUtil.HoverIconTooltip(icon, _iconSize); + + var text = custom.Value.ToString(); + var textWidth = ImGui.CalcTextSize(text).X; + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (_iconSize.X - textWidth + 2 * ImGui.GetStyle().FramePadding.X) / 2); + ImGui.TextUnformatted(text); + group.Dispose(); + + if (i % 8 != 7) + ImGui.SameLine(); + } + + return ret; + } + + private static bool DrawColorPicker(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked) + { + const string popupName = "Color Picker"; + using var _ = ImRaii.PushId((int)id); + var ret = false; + var count = set.Count(id); + var label = set.Option(id); + var (current, custom) = GetCurrentCustomization(set, id, customize); + + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); + if (ImGui.ColorButton($"{current + 1}##color", ImGui.ColorConvertU32ToFloat4(custom.Color), ImGuiColorEditFlags.None, _framedIconSize) + && !locked) + ImGui.OpenPopup(popupName); + + ImGui.SameLine(); + + using (var group = ImRaii.Group()) + { + var (min, max) = locked ? (current, current) : (1, count); + if (InputInt("##text", id, current, min, max, locked)) + { + customize[id] = set.Data(id, current).Value; + ret = true; + } + + ImGui.TextUnformatted(label); + } + + return ret | DrawColorPickerPopup(popupName, set, id, customize); + } + + private static (int, Customization.Customization) GetCurrentCustomization(CustomizationSet set, CustomizationId id, + CharacterCustomization customize) + { + var current = set.DataByValue(id, customize[id], out var custom); + if (set.IsAvailable(id) && current < 0) + { + PluginLog.Warning($"Read invalid customization value {customize[id]} for {id}."); + current = 0; + custom = set.Data(id, 0); + } + + return (current, custom!.Value); + } + + private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, CharacterCustomization customize) + { + using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); + if (!popup) + return false; + + var ret = false; + var count = set.Count(id); + 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); + if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color))) + { + customize[id] = custom.Value; + ret = true; + ImGui.CloseCurrentPopup(); + } + + if (i % 8 != 7) + ImGui.SameLine(); + } + + return ret; + } + + private static bool DrawMultiIconSelector(CustomizationSet set, CharacterCustomization customize, bool locked) + { + using var bigGroup = ImRaii.Group(); + using var _ = ImRaii.PushId((int)CustomizationId.FacialFeaturesTattoos); + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); + var ret = DrawMultiIcons(set, customize, locked); + ImGui.SameLine(); + using var group = ImRaii.Group(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2); + int value = customize[CustomizationId.FacialFeaturesTattoos]; + var (min, max) = locked ? (value, value) : (1, 256); + if (InputInt(string.Empty, CustomizationId.FacialFeaturesTattoos, value, min, max, locked)) + { + customize[CustomizationId.FacialFeaturesTattoos] = (byte)value; + ret = true; + } + + ImGui.TextUnformatted(set.Option(CustomizationId.FacialFeaturesTattoos)); + + return ret; + } + + private static bool DrawMultiIcons(CustomizationSet set, CharacterCustomization customize, bool locked) + { + using var _ = ImRaii.Group(); + var face = customize.Face; + if (set.Faces.Count < face) + face = 1; + + var ret = false; + var count = set.Count(CustomizationId.FacialFeaturesTattoos); + for (var i = 0; i < count; ++i) + { + var enabled = customize.FacialFeature(i); + var feature = set.FacialFeature(face, i); + var icon = i == count - 1 + ? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId) + : Glamourer.Customization.GetIcon(feature.IconId); + if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, + Vector4.Zero, enabled ? Vector4.One : RedTint) + && !locked) + { + customize.FacialFeature(i, !enabled); + ret = true; + } + + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 1f, !locked); + ImGuiUtil.HoverIconTooltip(icon, _iconSize); + + if (i % 4 != 3) + ImGui.SameLine(); + } + + return ret; + } + + private static bool DrawListSelector(CustomizationSet set, CustomizationId id, CharacterCustomization customize, bool locked) + { + using var _ = ImRaii.PushId((int)id); + using var bigGroup = ImRaii.Group(); + var ret = false; + int current = customize[id]; + var count = set.Count(id); + + ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale); + using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f, locked); + using (var combo = ImRaii.Combo("##combo", $"{set.Option(id)} #{current + 1}")) + { + if (combo) + for (var i = 0; i < count; ++i) + { + if (!ImGui.Selectable($"{set.Option(id)} #{i + 1}##combo", i == current) || i == current || locked) + continue; + + customize[id] = (byte)i; + ret = true; + } + } + + ImGui.SameLine(); + var (min, max) = locked ? (current, current) : (1, count); + if (InputInt("##text", id, current, min, max, locked)) + { + customize[id] = (byte)current; + ret = true; + } + + ImGui.SameLine(); + alpha.Pop(); + ImGui.TextUnformatted(set.Option(id)); + + return ret; + } +} diff --git a/Glamourer/Gui/Interface.SettingsTab.cs b/Glamourer/Gui/Interface.SettingsTab.cs new file mode 100644 index 0000000..ecb8a5b --- /dev/null +++ b/Glamourer/Gui/Interface.SettingsTab.cs @@ -0,0 +1,104 @@ +using System; +using System.Numerics; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui; + +internal partial class Interface +{ + private static void Checkmark(string label, string tooltip, bool value, Action setter) + { + if (ImGuiUtil.Checkbox(label, tooltip, value, setter)) + Glamourer.Config.Save(); + } + + private static void ChangeAndSave(T value, T currentValue, Action setter) where T : IEquatable + { + if (value.Equals(currentValue)) + return; + + setter(value); + Glamourer.Config.Save(); + } + + private static void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action setter) + { + const ImGuiColorEditFlags flags = ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs; + + var tmp = ImGui.ColorConvertU32ToFloat4(value); + if (ImGui.ColorEdit4($"##{name}", ref tmp, flags)) + ChangeAndSave(ImGui.ColorConvertFloat4ToU32(tmp), value, setter); + ImGui.SameLine(); + if (ImGui.Button($"Default##{name}")) + ChangeAndSave(defaultValue, value, setter); + ImGuiUtil.HoverTooltip( + $"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}"); + ImGui.SameLine(); + ImGui.Text(name); + ImGuiUtil.HoverTooltip(tooltip); + } + + private static void DrawRestorePenumbraButton() + { + const string buttonLabel = "Re-Register Penumbra"; + if (!Glamourer.Config.AttachToPenumbra) + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); + ImGui.Button(buttonLabel); + return; + } + + if (ImGui.Button(buttonLabel)) + Glamourer.Penumbra.Reattach(true); + + ImGuiUtil.HoverTooltip( + "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality."); + } + + private static void DrawSettingsTab() + { + using var tab = ImRaii.TabItem("Settings"); + if (!tab) + return; + + var cfg = Glamourer.Config; + ImGui.Dummy(_spacing); + + Checkmark("Folders First", "Sort Folders before all designs instead of lexicographically.", cfg.FoldersFirst, + v => cfg.FoldersFirst = v); + Checkmark("Color Designs", "Color the names of designs in the selector using the colors from below for the given cases.", + cfg.ColorDesigns, + v => cfg.ColorDesigns = v); + Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks, + v => cfg.ShowLocks = v); + Checkmark("Attach to Penumbra", + "Allows you to right-click items in the Changed Items tab of a mod in Penumbra to apply them to your player character.", + cfg.AttachToPenumbra, + v => + { + cfg.AttachToPenumbra = v; + if (v) + Glamourer.Penumbra.Reattach(true); + else + Glamourer.Penumbra.Unattach(); + }); + ImGui.SameLine(); + DrawRestorePenumbraButton(); + + Checkmark("Apply Fixed Designs", + "Automatically apply fixed designs to characters and redraw them when anything changes.", + cfg.ApplyFixedDesigns, + v => { cfg.ApplyFixedDesigns = v; }); + + ImGui.Dummy(_spacing); + + DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.", + cfg.CustomizationColor, GlamourerConfig.DefaultCustomizationColor, c => cfg.CustomizationColor = c); + DrawColorPicker("Equipment Color", "The color for designs that only apply some or all of their equipment slots and stains.", + cfg.EquipmentColor, GlamourerConfig.DefaultEquipmentColor, c => cfg.EquipmentColor = c); + DrawColorPicker("State Color", "The color for designs that only apply some state modification.", + cfg.StateColor, GlamourerConfig.DefaultStateColor, c => cfg.StateColor = c); + } +} diff --git a/Glamourer/Gui/Interface.State.cs b/Glamourer/Gui/Interface.State.cs new file mode 100644 index 0000000..4e5dcde --- /dev/null +++ b/Glamourer/Gui/Interface.State.cs @@ -0,0 +1,58 @@ +using System; +using System.Numerics; +using System.Reflection; +using Dalamud.Interface; +using Glamourer.Customization; +using ImGuiNET; + +namespace Glamourer.Gui; + +internal partial class Interface +{ + private static readonly ImGuiScene.TextureWrap? LegacyTattoo = GetLegacyTattooIcon(); + private static readonly Vector4 RedTint = new(0.6f, 0.3f, 0.3f, 1f); + + + private static Vector2 _iconSize = Vector2.Zero; + private static Vector2 _framedIconSize = Vector2.Zero; + private static Vector2 _spacing = Vector2.Zero; + private static float _actorSelectorWidth; + private static float _inputIntSize; + private static float _comboSelectorSize; + private static float _raceSelectorWidth; + private static bool _inGPose; + + + private static void UpdateState() + { + // General + _inGPose = ObjectManager.IsInGPose(); + _spacing = _spacing with { Y = ImGui.GetTextLineHeightWithSpacing() / 2 }; + _actorSelectorWidth = 200 * ImGuiHelpers.GlobalScale; + + // Customize + _iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2); + _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; + _inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X; + _comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; + _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; + + // _itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1; + } + + private static ImGuiScene.TextureWrap? GetLegacyTattooIcon() + { + using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); + if (resource != null) + { + var rawImage = new byte[resource.Length]; + var length = resource.Read(rawImage, 0, (int)resource.Length); + if (length != resource.Length) + return null; + + return Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4); + } + + return null; + } +} diff --git a/Glamourer/Gui/Interface.cs b/Glamourer/Gui/Interface.cs index 4f61a25..f257b3c 100644 --- a/Glamourer/Gui/Interface.cs +++ b/Glamourer/Gui/Interface.cs @@ -1,110 +1,139 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using System.Reflection; -using Dalamud.Game.ClientState.Objects.Types; -using Glamourer.Designs; +using Dalamud.Interface.Windowing; +using Dalamud.Logging; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; using OtterGui.Raii; -using Penumbra.GameData; -using Penumbra.GameData.Enums; +using OtterGui.Widgets; namespace Glamourer.Gui; -internal partial class Interface : IDisposable +internal partial class Interface : Window, 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; + private readonly Glamourer _plugin; public Interface(Glamourer plugin) + : base(GetLabel()) { - _plugin = plugin; - _designs = plugin.Designs; - _glamourerHeader = Glamourer.Version.Length > 0 - ? $"{PluginName} v{Glamourer.Version}###{PluginName}Main" - : $"{PluginName}###{PluginName}Main"; + _plugin = plugin; 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(); + Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle; + SizeConstraints = new WindowSizeConstraints() + { + MinimumSize = new Vector2(675, 675), + MaximumSize = ImGui.GetIO().DisplaySize, + }; } - public void ToggleVisibility() - => _visible = !_visible; + public override void Draw() + { + using var tabBar = ImRaii.TabBar("##Tabs"); + if (!tabBar) + return; + + UpdateState(); + + _actorTab.Draw(); + DrawSettingsTab(); + // DrawSaves(); + // DrawFixedDesignsTab(); + // DrawRevertablesTab(); + } public void Dispose() { - _legacyTattooIcon?.Dispose(); - Dalamud.PluginInterface.UiBuilder.Draw -= Draw; - Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= ToggleVisibility; + Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle; } - 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)) - { - ImGui.End(); - return; - } - - try - { - 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(); - } - finally - { - ImGui.End(); - } - } + private static string GetLabel() + => Glamourer.Version.Length == 0 + ? "Glamourer###GlamourerConfigWindow" + : $"Glamourer v{Glamourer.Version}###GlamourerConfigWindow"; } + +//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) +//{ +// _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(); +//} +// +//public void ToggleVisibility() +// => _visible = !_visible; +// +// +//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)) +// { +// ImGui.End(); +// return; +// } +// +// try +// { +// 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(); +// } +// finally +// { +// ImGui.End(); +// } +//} diff --git a/Glamourer/Gui/InterfaceActorPanel.cs b/Glamourer/Gui/InterfaceActorPanel.cs index 0a63756..ad377da 100644 --- a/Glamourer/Gui/InterfaceActorPanel.cs +++ b/Glamourer/Gui/InterfaceActorPanel.cs @@ -1,308 +1,291 @@ -using System; -using System.Linq; -using System.Numerics; -using System.Reflection; -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Interface; -using Dalamud.Logging; -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; 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() - { - 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 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) - { - 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(); - ImGuiUtil.HoverTooltip( - "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment."); - - if (!applyButton) - 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; - } - - private void DrawSaveDesignButton() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Save.ToIconString())) - OpenDesignNamePopup(DesignNameUse.SaveCurrent); - - ImGui.PopFont(); - ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment."); - - DrawDesignNamePopup(DesignNameUse.SaveCurrent); - } - - private void DrawTargetPlayerButton() - { - if (ImGui.Button("Target Player")) - Dalamud.Targets.SetTarget(_player); - } - - private void DrawApplyToPlayerButton(CharacterSave save) - { - if (!ImGui.Button("Apply to Self")) - return; - - var player = _inGPose - ? (Character?)Dalamud.Objects[GPoseObjectId] - : Dalamud.ClientState.LocalPlayer; - var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null; - if (player == null) - return; - - ConditionalApply(save, player); - if (_inGPose) - ConditionalApply(save, fallback!); - Glamourer.Penumbra.UpdateCharacters(player, fallback); - } - - - private static Character? TransformToCustomizable(Character? actor) - { - if (actor == null) - return null; - - if (actor.ModelType() == 0) - return actor; - - 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 - { - 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!); - } - - if (!_inGPose) - { - ImGui.SameLine(); - DrawTargetPlayerButton(); - } - - 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 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(); - } - - 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(); - } + //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() + //{ + // 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 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) + // { + // 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(); + // ImGuiUtil.HoverTooltip( + // "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment."); + // + // if (!applyButton) + // 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; + //} + // + //private void DrawSaveDesignButton() + //{ + // ImGui.PushFont(UiBuilder.IconFont); + // if (ImGui.Button(FontAwesomeIcon.Save.ToIconString())) + // OpenDesignNamePopup(DesignNameUse.SaveCurrent); + // + // ImGui.PopFont(); + // ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment."); + // + // DrawDesignNamePopup(DesignNameUse.SaveCurrent); + //} + // + //private void DrawTargetPlayerButton() + //{ + // if (ImGui.Button("Target Player")) + // Dalamud.Targets.SetTarget(_player); + //} + // + //private void DrawApplyToPlayerButton(CharacterSave save) + //{ + // if (!ImGui.Button("Apply to Self")) + // return; + // + // var player = _inGPose + // ? (Character?)Dalamud.Objects[GPoseObjectId] + // : Dalamud.ClientState.LocalPlayer; + // var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null; + // if (player == null) + // return; + // + // ConditionalApply(save, player); + // if (_inGPose) + // ConditionalApply(save, fallback!); + // Glamourer.Penumbra.UpdateCharacters(player, fallback); + //} + // + // + //private static Character? TransformToCustomizable(Character? actor) + //{ + // if (actor == null) + // return null; + // + // if (actor.ModelType() == 0) + // return actor; + // + // 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 + // { + // 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!); + // } + // + // if (!_inGPose) + // { + // ImGui.SameLine(); + // DrawTargetPlayerButton(); + // } + // + // 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 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(); + //} + // + //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 deleted file mode 100644 index f132c2a..0000000 --- a/Glamourer/Gui/InterfaceActorSelector.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Game.ClientState.Objects.Enums; -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; - -internal partial class Interface -{ - public const int CharacterScreenIndex = 240; - public const int ExamineScreenIndex = 241; - public const int FittingRoomIndex = 242; - public const int DyePreviewIndex = 243; - - private Character? _player; - private string _currentLabel = string.Empty; - private string _playerFilter = string.Empty; - private string _playerFilterLower = string.Empty; - private readonly Dictionary _playerNames = new(100); - private readonly Dictionary _gPoseActors = new(CharacterScreenIndex - GPoseObjectId); - - private void DrawPlayerFilter() - { - 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(); - } - - private void DrawGPoseSelectable(Character player) - { - var playerName = player.Name.ToString(); - if (!playerName.Any()) - return; - - _gPoseActors[playerName] = null; - - DrawSelectable(player, $"{playerName} (GPose)", true); - } - - private static string GetLabel(Character player, string playerName, int num) - { - if (player.ObjectKind == ObjectKind.Player) - return num == 1 ? playerName : $"{playerName} #{num}"; - - if (player.ModelType() == 0) - return num == 1 ? $"{playerName} (NPC)" : $"{playerName} #{num} (NPC)"; - - return num == 1 ? $"{playerName} (Monster)" : $"{playerName} #{num} (Monster)"; - } - - private void DrawPlayerSelectable(Character player, int idx = 0) - { - var (playerName, modifiable) = idx switch - { - CharacterScreenIndex => ("Character Screen Actor", false), - ExamineScreenIndex => ("Examine Screen Actor", false), - FittingRoomIndex => ("Fitting Room Actor", false), - DyePreviewIndex => ("Dye Preview Actor", false), - _ => (player.Name.ToString(), true), - }; - if (!playerName.Any()) - return; - - if (_playerNames.TryGetValue(playerName, out var num)) - _playerNames[playerName] = ++num; - else - _playerNames[playerName] = num = 1; - - if (_gPoseActors.ContainsKey(playerName)) - { - _gPoseActors[playerName] = player; - return; - } - - var label = GetLabel(player, playerName, num); - DrawSelectable(player, label, modifiable); - } - - - private void DrawSelectable(Character player, string label, bool modifiable) - { - if (!_playerFilterLower.Any() || label.ToLowerInvariant().Contains(_playerFilterLower)) - if (ImGui.Selectable(label, _currentLabel == label)) - { - _currentLabel = label; - _currentSave.LoadCharacter(player); - _player = player; - _currentSave.WriteProtected = !modifiable; - return; - } - - if (_currentLabel != label) - return; - - try - { - _currentSave.LoadCharacter(player); - _player = player; - _currentSave.WriteProtected = !modifiable; - } - catch (Exception e) - { - PluginLog.Error($"Could not load character {player.Name}s information:\n{e}"); - } - } - - private void DrawSelectionButtons() - { - 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; - font.Pop(); - ImGuiUtil.HoverTooltip("Select the local player character."); - ImGui.SameLine(); - font.Push(UiBuilder.IconFont); - if (_inGPose) - { - style.Push(ImGuiStyleVar.Alpha, 0.5f); - ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth); - style.Pop(); - } - else - { - if (ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth)) - select = CharacterFactory.Convert(Dalamud.Targets.Target); - } - - font.Pop(); - ImGuiUtil.HoverTooltip("Select the current target, if it is in the list."); - - if (select == null) - return; - - try - { - _currentSave.LoadCharacter(select); - _player = select; - _currentLabel = _player.Name.ToString(); - _currentSave.WriteProtected = false; - } - catch (Exception e) - { - PluginLog.Error($"Could not load character {select.Name}s information:\n{e}"); - } - } - - private void DrawPlayerSelector() - { - ImGui.BeginGroup(); - DrawPlayerFilter(); - if (!ImGui.BeginChild("##playerSelector", - new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true)) - { - ImGui.EndChild(); - ImGui.EndGroup(); - return; - } - - _playerNames.Clear(); - _gPoseActors.Clear(); - for (var i = GPoseObjectId; i < GPoseObjectId + 48; ++i) - { - var player = CharacterFactory.Convert(Dalamud.Objects[i]); - if (player == null) - break; - - DrawGPoseSelectable(player); - } - - for (var i = 0; i < GPoseObjectId; ++i) - { - var player = CharacterFactory.Convert(Dalamud.Objects[i]); - if (player != null) - DrawPlayerSelectable(player); - } - - for (var i = CharacterScreenIndex; i < Dalamud.Objects.Length; ++i) - { - var player = CharacterFactory.Convert(Dalamud.Objects[i]); - if (player != null) - DrawPlayerSelectable(player, i); - } - - - using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) - { - ImGui.EndChild(); - } - - DrawSelectionButtons(); - ImGui.EndGroup(); - } - - private void DrawPlayerTab() - { - using var tab = ImRaii.TabItem("Current Players"); - _player = null; - if (!tab) - return; - - DrawPlayerSelector(); - - if (_currentLabel.Length == 0) - return; - - ImGui.SameLine(); - DrawActorPanel(); - } -} diff --git a/Glamourer/Gui/InterfaceConfig.cs b/Glamourer/Gui/InterfaceConfig.cs deleted file mode 100644 index daea985..0000000 --- a/Glamourer/Gui/InterfaceConfig.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Numerics; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; - -namespace Glamourer.Gui -{ - internal partial class Interface - { - private static void DrawConfigCheckMark(string label, string tooltip, bool value, Action setter) - { - if (DrawCheckMark(label, value, setter)) - Glamourer.Config.Save(); - - ImGuiUtil.HoverTooltip(tooltip); - } - - private static void ChangeAndSave(T value, T currentValue, Action setter) where T : IEquatable - { - if (value.Equals(currentValue)) - return; - - setter(value); - Glamourer.Config.Save(); - } - - private static void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action setter) - { - const ImGuiColorEditFlags flags = ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs; - - var tmp = ImGui.ColorConvertU32ToFloat4(value); - if (ImGui.ColorEdit4($"##{name}", ref tmp, flags)) - ChangeAndSave(ImGui.ColorConvertFloat4ToU32(tmp), value, setter); - ImGui.SameLine(); - if (ImGui.Button($"Default##{name}")) - ChangeAndSave(defaultValue, value, setter); - ImGuiUtil.HoverTooltip( - $"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}"); - ImGui.SameLine(); - ImGui.Text(name); - ImGuiUtil.HoverTooltip(tooltip); - } - - private static void DrawRestorePenumbraButton() - { - const string buttonLabel = "Re-Register Penumbra"; - if (!Glamourer.Config.AttachToPenumbra) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); - ImGui.Button(buttonLabel); - return; - } - - if (ImGui.Button(buttonLabel)) - Glamourer.Penumbra.Reattach(true); - - ImGuiUtil.HoverTooltip( - "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality."); - } - - private static void DrawConfigTab() - { - using var tab = ImRaii.TabItem("Config"); - if (!tab) - return; - - var cfg = Glamourer.Config; - ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() / 2); - - DrawConfigCheckMark("Folders First", "Sort Folders before all designs instead of lexicographically.", cfg.FoldersFirst, - v => cfg.FoldersFirst = v); - DrawConfigCheckMark("Color Designs", "Color the names of designs in the selector using the colors from below for the given cases.", - cfg.ColorDesigns, - v => cfg.ColorDesigns = v); - DrawConfigCheckMark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks, - v => cfg.ShowLocks = v); - DrawConfigCheckMark("Attach to Penumbra", - "Allows you to right-click items in the Changed Items tab of a mod in Penumbra to apply them to your player character.", - cfg.AttachToPenumbra, - v => - { - cfg.AttachToPenumbra = v; - if (v) - Glamourer.Penumbra.Reattach(true); - else - Glamourer.Penumbra.Unattach(); - }); - ImGui.SameLine(); - DrawRestorePenumbraButton(); - - DrawConfigCheckMark("Apply Fixed Designs", - "Automatically apply fixed designs to characters and redraw them when anything changes.", - cfg.ApplyFixedDesigns, - v => - { - cfg.ApplyFixedDesigns = v; - if (v) - Glamourer.PlayerWatcher.Enable(); - else - Glamourer.PlayerWatcher.Disable(); - }); - - ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() / 2); - - DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.", - cfg.CustomizationColor, GlamourerConfig.DefaultCustomizationColor, c => cfg.CustomizationColor = c); - DrawColorPicker("Equipment Color", "The color for designs that only apply some or all of their equipment slots and stains.", - cfg.EquipmentColor, GlamourerConfig.DefaultEquipmentColor, c => cfg.EquipmentColor = c); - DrawColorPicker("State Color", "The color for designs that only apply some state modification.", - cfg.StateColor, GlamourerConfig.DefaultStateColor, c => cfg.StateColor = c); - } - } -} diff --git a/Glamourer/Gui/InterfaceCustomization.cs b/Glamourer/Gui/InterfaceCustomization.cs deleted file mode 100644 index 835259f..0000000 --- a/Glamourer/Gui/InterfaceCustomization.cs +++ /dev/null @@ -1,478 +0,0 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using Dalamud.Logging; -using Glamourer.Customization; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using Penumbra.GameData.Enums; - -namespace Glamourer.Gui -{ - internal partial class Interface - { - private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value) - { - value = default; - using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); - if (!popup) - return false; - - var ret = false; - var count = set.Count(id); - 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); - if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color))) - { - value = custom; - ret = true; - ImGui.CloseCurrentPopup(); - } - - if (i % 8 != 7) - ImGui.SameLine(); - } - - return ret; - } - - private Vector2 _iconSize = Vector2.Zero; - private Vector2 _actualIconSize = Vector2.Zero; - private float _raceSelectorWidth; - private float _inputIntSize; - private float _comboSelectorSize; - private float _percentageSize; - private float _itemComboWidth; - - private bool InputInt(string label, ref int value, int minValue, int maxValue) - { - var ret = false; - var tmp = value + 1; - ImGui.SetNextItemWidth(_inputIntSize); - if (ImGui.InputInt(label, ref tmp, 1, 1, ImGuiInputTextFlags.EnterReturnsTrue) && tmp != value + 1 && tmp >= minValue && tmp <= maxValue) - { - value = tmp - 1; - ret = true; - } - - ImGuiUtil.HoverTooltip($"Input Range: [{minValue}, {maxValue}]"); - - return ret; - } - - private static (int, Customization.Customization) GetCurrentCustomization(ref CharacterCustomization customization, CustomizationId id, - CustomizationSet set) - { - var current = set.DataByValue(id, customization[id], out var custom); - if (set.IsAvailable(id) && current < 0) - { - PluginLog.Warning($"Read invalid customization value {customization[id]} for {id}."); - current = 0; - custom = set.Data(id, 0); - } - - return (current, custom!.Value); - } - - private bool DrawColorPicker(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id, - CustomizationSet set) - { - var ret = false; - var count = set.Count(id); - - var (current, custom) = GetCurrentCustomization(ref customization, id, set); - - var popupName = $"Color Picker##{id}"; - if (ImGui.ColorButton($"{current + 1}##color_{id}", ImGui.ColorConvertU32ToFloat4(custom.Color), ImGuiColorEditFlags.None, - _actualIconSize)) - ImGui.OpenPopup(popupName); - - ImGui.SameLine(); - - using (var _ = ImRaii.Group()) - { - if (InputInt($"##text_{id}", ref current, 1, count)) - { - customization[id] = set.Data(id, current).Value; - ret = true; - } - - - ImGui.Text(label); - ImGuiUtil.HoverTooltip(tooltip); - } - - if (!DrawColorPickerPopup(popupName, set, id, out var newCustom)) - return ret; - - customization[id] = newCustom.Value; - ret = true; - - return ret; - } - - private bool DrawListSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id, - CustomizationSet set) - { - using var bigGroup = ImRaii.Group(); - var ret = false; - int current = customization[id]; - var count = set.Count(id); - - ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale); - if (ImGui.BeginCombo($"##combo_{id}", $"{set.Option(id)} #{current + 1}")) - { - for (var i = 0; i < count; ++i) - { - if (ImGui.Selectable($"{set.Option(id)} #{i + 1}##combo", i == current) && i != current) - { - customization[id] = (byte) i; - ret = true; - } - } - - ImGui.EndCombo(); - } - - ImGui.SameLine(); - if (InputInt($"##text_{id}", ref current, 1, count)) - { - customization[id] = (byte) current; - ret = true; - } - - ImGui.SameLine(); - ImGui.Text(label); - ImGuiUtil.HoverTooltip(tooltip); - - return ret; - } - - - private static readonly Vector4 NoColor = new(1f, 1f, 1f, 1f); - private static readonly Vector4 RedColor = new(0.6f, 0.3f, 0.3f, 1f); - - private bool DrawMultiSelector(ref CharacterCustomization customization, CustomizationSet set) - { - using var bigGroup = ImRaii.Group(); - var ret = false; - var count = set.Count(CustomizationId.FacialFeaturesTattoos); - using (var _ = ImRaii.Group()) - { - var face = customization.Face; - if (set.Faces.Count < face) - face = 1; - for (var i = 0; i < count; ++i) - { - var enabled = customization.FacialFeature(i); - var feature = set.FacialFeature(face, i); - var icon = i == count - 1 - ? _legacyTattooIcon ?? Glamourer.Customization.GetIcon(feature.IconId) - : Glamourer.Customization.GetIcon(feature.IconId); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int) ImGui.GetStyle().FramePadding.X, - Vector4.Zero, - enabled ? NoColor : RedColor)) - { - ret = true; - customization.FacialFeature(i, !enabled); - } - - ImGuiUtil.HoverIconTooltip(icon, _iconSize); - - if (i % 4 != 3) - ImGui.SameLine(); - } - } - - ImGui.SameLine(); - 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)) - { - customization[CustomizationId.FacialFeaturesTattoos] = (byte) value; - ret = true; - } - - ImGui.TextUnformatted(set.Option(CustomizationId.FacialFeaturesTattoos)); - - return ret; - } - - - private bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value) - { - value = default; - using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize); - if (!popup) - return false; - - var ret = false; - var count = set.Count(id); - 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); - var icon = Glamourer.Customization.GetIcon(custom.IconId); - ImGui.BeginGroup(); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) - { - value = custom; - ret = true; - ImGui.CloseCurrentPopup(); - } - - ImGuiUtil.HoverIconTooltip(icon, _iconSize); - - var text = custom.Value.ToString(); - var textWidth = ImGui.CalcTextSize(text).X; - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (_iconSize.X - textWidth + 2 * ImGui.GetStyle().FramePadding.X)/2); - ImGui.Text(text); - ImGui.EndGroup(); - if (i % 8 != 7) - ImGui.SameLine(); - } - - return ret; - } - - private bool DrawIconSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id, - CustomizationSet set) - { - using var bigGroup = ImRaii.Group(); - var ret = false; - var count = set.Count(id); - - var current = set.DataByValue(id, customization[id], out var custom); - if (current < 0) - { - label = $"{label} (Custom #{customization[id]})"; - current = 0; - custom = set.Data(id, 0); - } - - var popupName = $"Style Picker##{id}"; - var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) - ImGui.OpenPopup(popupName); - - ImGuiUtil.HoverIconTooltip(icon, _iconSize); - - ImGui.SameLine(); - using var group = ImRaii.Group(); - if (InputInt($"##text_{id}", ref current, 1, count)) - { - customization[id] = set.Data(id, current).Value; - ret = true; - } - - if (DrawIconPickerPopup(popupName, set, id, out var newCustom)) - { - customization[id] = newCustom.Value; - ret = true; - } - - ImGui.TextUnformatted($"{label} ({custom.Value.Value})"); - ImGuiUtil.HoverTooltip(tooltip); - - return ret; - } - - - private bool DrawPercentageSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id, - CustomizationSet set) - { - using var bigGroup = ImRaii.Group(); - var ret = false; - int value = customization[id]; - var count = set.Count(id); - ImGui.SetNextItemWidth(_percentageSize * ImGui.GetIO().FontGlobalScale); - if (ImGui.SliderInt($"##slider_{id}", ref value, 0, count - 1, "") && value != customization[id]) - { - customization[id] = (byte) value; - ret = true; - } - - ImGui.SameLine(); - --value; - if (InputInt($"##input_{id}", ref value, 0, count - 1)) - { - customization[id] = (byte) (value + 1); - ret = true; - } - - ImGui.SameLine(); - ImGui.TextUnformatted(label); - ImGuiUtil.HoverTooltip(tooltip); - - return ret; - } - - private bool DrawRaceSelector(ref CharacterCustomization customization) - { - using var group = ImRaii.Group(); - var ret = false; - ImGui.SetNextItemWidth(_raceSelectorWidth); - if (ImGui.BeginCombo("##subRaceCombo", ClanName(customization.Clan, customization.Gender))) - { - for (var i = 0; i < (int) SubRace.Veena; ++i) - { - if (ImGui.Selectable(ClanName((SubRace) i + 1, customization.Gender), (int) customization.Clan == i + 1)) - { - var race = (SubRace) i + 1; - ret |= ChangeRace(ref customization, race); - } - } - - ImGui.EndCombo(); - } - - ImGui.TextUnformatted( - $"{Glamourer.Customization.GetName(CustomName.Gender)} & {Glamourer.Customization.GetName(CustomName.Clan)}"); - - return ret; - } - - private bool DrawGenderSelector(ref CharacterCustomization customization) - { - var ret = false; - 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) - { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.25f); - icon = FontAwesomeIcon.MarsDouble; - restricted = true; - } - - if (ImGui.Button(icon.ToIconString(), _actualIconSize) && !restricted) - { - var gender = customization.Gender == Gender.Male ? Gender.Female : Gender.Male; - ret = ChangeGender(ref customization, gender); - } - - if (restricted) - ImGui.PopStyleVar(); - return ret; - } - - private bool DrawPicker(CustomizationSet set, CustomizationId id, ref CharacterCustomization customization) - { - if (!set.IsAvailable(id)) - return false; - - switch (set.Type(id)) - { - case CharaMakeParams.MenuType.ColorPicker: return DrawColorPicker(set.OptionName[(int) id], "", ref customization, id, set); - case CharaMakeParams.MenuType.ListSelector: return DrawListSelector(set.OptionName[(int) id], "", ref customization, id, set); - case CharaMakeParams.MenuType.IconSelector: return DrawIconSelector(set.OptionName[(int) id], "", ref customization, id, set); - case CharaMakeParams.MenuType.MultiIconSelector: return DrawMultiSelector(ref customization, set); - case CharaMakeParams.MenuType.Percentage: - return DrawPercentageSelector(set.OptionName[(int) id], "", ref customization, id, set); - } - - return false; - } - - private static CustomizationId[] GetCustomizationOrder() - { - var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId)); - ret[(int) CustomizationId.TattooColor] = CustomizationId.EyeColorL; - ret[(int) CustomizationId.EyeColorL] = CustomizationId.EyeColorR; - ret[(int) CustomizationId.EyeColorR] = CustomizationId.TattooColor; - return ret; - } - - private static readonly CustomizationId[] AllCustomizations = GetCustomizationOrder(); - - private bool DrawCustomization(ref CharacterCustomization custom) - { - if (!ImGui.CollapsingHeader("Character Customization")) - return false; - - var ret = DrawGenderSelector(ref custom); - ImGui.SameLine(); - ret |= DrawRaceSelector(ref custom); - - var set = Glamourer.Customization.GetList(custom.Clan, custom.Gender); - - foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.Percentage)) - ret |= DrawPicker(set, id, ref custom); - - var odd = true; - foreach (var id in AllCustomizations.Where((c, _) => set.Type(c) == CharaMakeParams.MenuType.IconSelector)) - { - ret |= DrawPicker(set, id, ref custom); - if (odd) - ImGui.SameLine(); - odd = !odd; - } - - if (!odd) - ImGui.NewLine(); - - ret |= DrawPicker(set, CustomizationId.FacialFeaturesTattoos, ref custom); - - foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ListSelector)) - ret |= DrawPicker(set, id, ref custom); - - odd = true; - foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ColorPicker)) - { - ret |= DrawPicker(set, id, ref custom); - if (odd) - ImGui.SameLine(); - odd = !odd; - } - - if (!odd) - ImGui.NewLine(); - - var tmp = custom.HighlightsOn; - if (ImGui.Checkbox(set.Option(CustomizationId.HighlightsOnFlag), ref tmp) && tmp != custom.HighlightsOn) - { - custom.HighlightsOn = tmp; - ret = true; - } - - var xPos = _inputIntSize + _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; - ImGui.SameLine(xPos); - tmp = custom.FacePaintReversed; - if (ImGui.Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {set.Option(CustomizationId.FacePaint)}", ref tmp) - && tmp != custom.FacePaintReversed) - { - custom.FacePaintReversed = tmp; - ret = true; - } - - tmp = custom.SmallIris; - if (ImGui.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}", - ref tmp) - && tmp != custom.SmallIris) - { - custom.SmallIris = tmp; - ret = true; - } - - if (custom.Race != Race.Hrothgar) - { - tmp = custom.Lipstick; - ImGui.SameLine(xPos); - if (ImGui.Checkbox(set.Option(CustomizationId.LipColor), ref tmp) && tmp != custom.Lipstick) - { - custom.Lipstick = tmp; - ret = true; - } - } - - return ret; - } - } -} diff --git a/Glamourer/Gui/InterfaceDesigns.cs b/Glamourer/Gui/InterfaceDesigns.cs index f2f8113..c453cc1 100644 --- a/Glamourer/Gui/InterfaceDesigns.cs +++ b/Glamourer/Gui/InterfaceDesigns.cs @@ -4,371 +4,371 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Logging; using Glamourer.Designs; -using Glamourer.FileSystem; +using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; namespace Glamourer.Gui; -internal partial class Interface -{ - 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)) - { - DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical); - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - ImGui.EndChild(); - ImGui.PopStyleVar(); - } - - DrawDesignSelectorButtons(); - ImGui.EndGroup(); - } - - 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(); - - ImGui.EndChild(); - } - } - - 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; - - 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); - 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(); - } - - 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 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); - } -} +//internal partial class Interface +//{ +// 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)) +// { +// DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical); +// ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); +// ImGui.EndChild(); +// ImGui.PopStyleVar(); +// } +// +// DrawDesignSelectorButtons(); +// ImGui.EndGroup(); +// } +// +// 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(); +// +// ImGui.EndChild(); +// } +// } +// +// 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; +// +// 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); +// 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(); +// } +// +// 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 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 419c617..fdf6156 100644 --- a/Glamourer/Gui/InterfaceEquipment.cs +++ b/Glamourer/Gui/InterfaceEquipment.cs @@ -1,184 +1,196 @@ using Dalamud.Interface; +using Glamourer.Structs; using ImGuiNET; using Lumina.Text; using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -namespace Glamourer.Gui -{ - internal partial class Interface - { - private bool DrawStainSelector(ComboWithFilter stainCombo, EquipSlot slot, StainId stainIdx) - { - stainCombo.PostPreview = null; - if (_stains.TryGetValue((byte) stainIdx, out var stain)) - { - var previewPush = PushColor(stain, ImGuiCol.FrameBg); - stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush); - } +namespace Glamourer.Gui; - var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx); - if (!change && (byte) stainIdx != 0) - { - ImGuiUtil.HoverTooltip("Right-click to clear."); - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - change = true; - newStain = Stain.None; - } - } - - if (!change) - return false; - - if (_player == null) - return _inDesignMode && (_selection?.Data.WriteStain(slot, newStain.RowIndex) ?? false); - - Glamourer.RevertableDesigns.Add(_player); - newStain.Write(_player.Address, slot); - return true; - - } - - private bool DrawItemSelector(ComboWithFilter equipCombo, Lumina.Excel.GeneratedSheets.Item item, EquipSlot slot = EquipSlot.Unknown) - { - var currentName = item.Name.ToString(); - var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId; - if (!change && !ReferenceEquals(item, SmallClothes)) - { - ImGuiUtil.HoverTooltip("Right-click to clear."); - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - change = true; - newItem = Item.Nothing(slot); - } - } - - if (!change) - return false; - - newItem = new Item(newItem.Base, newItem.Name, slot); - if (_player == null) - return _inDesignMode && (_selection?.Data.WriteItem(newItem) ?? false); - - Glamourer.RevertableDesigns.Add(_player); - newItem.Write(_player.Address); - return true; - - } - - private static bool DrawCheckbox(CharacterEquipMask flag, ref CharacterEquipMask mask) - { - var tmp = (uint) mask; - var ret = false; - if (ImGui.CheckboxFlags($"##flag_{(uint) flag}", ref tmp, (uint) flag) && tmp != (uint) mask) - { - mask = (CharacterEquipMask) tmp; - ret = true; - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Enable writing this slot in this save."); - return ret; - } - - private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new(){ Name = new SeString("Nothing"), RowId = 0 }; - private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new(){ Name = new SeString("Smallclothes (NPC)"), RowId = 1 }; - private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new(){ Name = new SeString("Unknown"), RowId = 2 }; - - private Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot) - { - return (uint) set switch - { - 0 => SmallClothes, - 9903 => SmallClothesNpc, - _ => _identifier.Identify(set, weapon, variant, slot) ?? Unknown, - }; - } - - private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip) - { - var (equipCombo, stainCombo) = _combos[slot]; - - var ret = DrawStainSelector(stainCombo, slot, equip.Stain); - ImGui.SameLine(); - var item = Identify(equip.Set, new WeaponType(), equip.Variant, slot); - ret |= DrawItemSelector(equipCombo, item, slot); - - return ret; - } - - private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask) - { - var ret = DrawCheckbox(flag, ref mask); - ImGui.SameLine(); - ret |= DrawEquipSlot(slot, equip); - return ret; - } - - private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon) - { - var (equipCombo, stainCombo) = _combos[slot]; - - var ret = DrawStainSelector(stainCombo, slot, weapon.Stain); - ImGui.SameLine(); - var item = Identify(weapon.Set, weapon.Type, weapon.Variant, slot); - ret |= DrawItemSelector(equipCombo, item, slot); - - return ret; - } - - private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask) - { - var ret = DrawCheckbox(flag, ref mask); - ImGui.SameLine(); - ret |= DrawWeapon(slot, weapon); - return ret; - } - - private bool DrawEquip(CharacterEquipment equip) - { - var ret = false; - if (ImGui.CollapsingHeader("Character Equipment")) - { - ret |= DrawWeapon(EquipSlot.MainHand, equip.MainHand); - ret |= DrawWeapon(EquipSlot.OffHand, equip.OffHand); - ret |= DrawEquipSlot(EquipSlot.Head, equip.Head); - ret |= DrawEquipSlot(EquipSlot.Body, equip.Body); - ret |= DrawEquipSlot(EquipSlot.Hands, equip.Hands); - ret |= DrawEquipSlot(EquipSlot.Legs, equip.Legs); - ret |= DrawEquipSlot(EquipSlot.Feet, equip.Feet); - ret |= DrawEquipSlot(EquipSlot.Ears, equip.Ears); - ret |= DrawEquipSlot(EquipSlot.Neck, equip.Neck); - ret |= DrawEquipSlot(EquipSlot.Wrists, equip.Wrists); - ret |= DrawEquipSlot(EquipSlot.RFinger, equip.RFinger); - ret |= DrawEquipSlot(EquipSlot.LFinger, equip.LFinger); - } - - return ret; - } - - private bool DrawEquip(CharacterEquipment equip, ref CharacterEquipMask mask) - { - var ret = false; - if (ImGui.CollapsingHeader("Character Equipment")) - { - ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask); - ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask); - ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask); - } - - return ret; - } - } -} +//internal partial class Interface +//{ +// private bool DrawStainSelector(ComboWithFilter stainCombo, EquipSlot slot, StainId stainIdx) +// { +// stainCombo.PostPreview = null; +// if (_stains.TryGetValue((byte)stainIdx, out var stain)) +// { +// var previewPush = PushColor(stain, ImGuiCol.FrameBg); +// stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush); +// } +// +// var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx); +// if (!change && (byte)stainIdx != 0) +// { +// ImGuiUtil.HoverTooltip("Right-click to clear."); +// if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) +// { +// change = true; +// newStain = Stain.None; +// } +// } +// +// if (!change) +// return false; +// +// if (_player == null) +// return _inDesignMode && (_selection?.Data.WriteStain(slot, newStain.RowIndex) ?? false); +// +// Glamourer.RevertableDesigns.Add(_player); +// newStain.Write(_player.Address, slot); +// return true; +// } +// +// private bool DrawItemSelector(ComboWithFilter equipCombo, Lumina.Excel.GeneratedSheets.Item item, EquipSlot slot = EquipSlot.Unknown) +// { +// var currentName = item.Name.ToString(); +// var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId; +// if (!change && !ReferenceEquals(item, SmallClothes)) +// { +// ImGuiUtil.HoverTooltip("Right-click to clear."); +// if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) +// { +// change = true; +// newItem = Item.Nothing(slot); +// } +// } +// +// if (!change) +// return false; +// +// newItem = new Item(newItem.Base, newItem.Name, slot); +// if (_player == null) +// return _inDesignMode && (_selection?.Data.WriteItem(newItem) ?? false); +// +// Glamourer.RevertableDesigns.Add(_player); +// newItem.Write(_player.Address); +// return true; +// } +// +// private static bool DrawCheckbox(CharacterEquipMask flag, ref CharacterEquipMask mask) +// { +// var tmp = (uint)mask; +// var ret = false; +// if (ImGui.CheckboxFlags($"##flag_{(uint)flag}", ref tmp, (uint)flag) && tmp != (uint)mask) +// { +// mask = (CharacterEquipMask)tmp; +// ret = true; +// } +// +// if (ImGui.IsItemHovered()) +// ImGui.SetTooltip("Enable writing this slot in this save."); +// return ret; +// } +// +// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new() +// { +// Name = new SeString("Nothing"), +// RowId = 0, +// }; +// +// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new() +// { +// Name = new SeString("Smallclothes (NPC)"), +// RowId = 1, +// }; +// +// private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new() +// { +// Name = new SeString("Unknown"), +// RowId = 2, +// }; +// +// private Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot) +// { +// return (uint)set switch +// { +// 0 => SmallClothes, +// 9903 => SmallClothesNpc, +// _ => _identifier.Identify(set, weapon, variant, slot) ?? Unknown, +// }; +// } +// +// private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip) +// { +// var (equipCombo, stainCombo) = _combos[slot]; +// +// var ret = DrawStainSelector(stainCombo, slot, equip.Stain); +// ImGui.SameLine(); +// var item = Identify(equip.Set, new WeaponType(), equip.Variant, slot); +// ret |= DrawItemSelector(equipCombo, item, slot); +// +// return ret; +// } +// +// private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask) +// { +// var ret = DrawCheckbox(flag, ref mask); +// ImGui.SameLine(); +// ret |= DrawEquipSlot(slot, equip); +// return ret; +// } +// +// private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon) +// { +// var (equipCombo, stainCombo) = _combos[slot]; +// +// var ret = DrawStainSelector(stainCombo, slot, weapon.Stain); +// ImGui.SameLine(); +// var item = Identify(weapon.Set, weapon.Type, weapon.Variant, slot); +// ret |= DrawItemSelector(equipCombo, item, slot); +// +// return ret; +// } +// +// private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask) +// { +// var ret = DrawCheckbox(flag, ref mask); +// ImGui.SameLine(); +// ret |= DrawWeapon(slot, weapon); +// return ret; +// } +// +// private bool DrawEquip(CharacterEquipment equip) +// { +// var ret = false; +// if (ImGui.CollapsingHeader("Character Equipment")) +// { +// ret |= DrawWeapon(EquipSlot.MainHand, equip.MainHand); +// ret |= DrawWeapon(EquipSlot.OffHand, equip.OffHand); +// ret |= DrawEquipSlot(EquipSlot.Head, equip.Head); +// ret |= DrawEquipSlot(EquipSlot.Body, equip.Body); +// ret |= DrawEquipSlot(EquipSlot.Hands, equip.Hands); +// ret |= DrawEquipSlot(EquipSlot.Legs, equip.Legs); +// ret |= DrawEquipSlot(EquipSlot.Feet, equip.Feet); +// ret |= DrawEquipSlot(EquipSlot.Ears, equip.Ears); +// ret |= DrawEquipSlot(EquipSlot.Neck, equip.Neck); +// ret |= DrawEquipSlot(EquipSlot.Wrists, equip.Wrists); +// ret |= DrawEquipSlot(EquipSlot.RFinger, equip.RFinger); +// ret |= DrawEquipSlot(EquipSlot.LFinger, equip.LFinger); +// } +// +// return ret; +// } +// +// private bool DrawEquip(CharacterEquipment equip, ref CharacterEquipMask mask) +// { +// var ret = false; +// if (ImGui.CollapsingHeader("Character Equipment")) +// { +// ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask); +// ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask); +// ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask); +// } +// +// return ret; +// } +//} diff --git a/Glamourer/Gui/InterfaceFixedDesigns.cs b/Glamourer/Gui/InterfaceFixedDesigns.cs index aa5d0ea..c0bb91c 100644 --- a/Glamourer/Gui/InterfaceFixedDesigns.cs +++ b/Glamourer/Gui/InterfaceFixedDesigns.cs @@ -4,165 +4,165 @@ using System.Linq; using System.Numerics; using Dalamud.Interface; using Glamourer.Designs; -using Glamourer.FileSystem; +using Glamourer.Structs; using ImGuiNET; using OtterGui.Raii; namespace Glamourer.Gui; -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() - { - _newFixDesignGroup ??= Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1]; - - using var tabItem = ImRaii.TabItem("Fixed Designs"); - if (!tabItem) - { - _fullPathCache = null; - _newFixDesign = null; - _newFixDesignPath = string.Empty; - _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1]; - return; - } - - _fullPathCache ??= Glamourer.FixedDesignManager.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList(); - - using var table = ImRaii.Table("##FixedTable", 4); - 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; - - 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(); - style.Push(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2); - font.Push(UiBuilder.IconFont); - if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}")) - { - _fullPathCache.RemoveAt(i--); - Glamourer.FixedDesignManager.FixedDesigns.Remove(name); - continue; - } - - 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)) - { - Glamourer.Config.FixedDesigns[i].Enabled = tmp; - Glamourer.Config.Save(); - } - - 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.Text(Glamourer.FixedDesignManager.FixedDesigns.Data[i].Jobs.Name); - ImGui.TableNextColumn(); - ImGui.Text(path); - } - - 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) - { - ImGui.SetNextItemWidth(-1); - 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; - } - } -} +//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() +// { +// _newFixDesignGroup ??= Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1]; +// +// using var tabItem = ImRaii.TabItem("Fixed Designs"); +// if (!tabItem) +// { +// _fullPathCache = null; +// _newFixDesign = null; +// _newFixDesignPath = string.Empty; +// _newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1]; +// return; +// } +// +// _fullPathCache ??= Glamourer.FixedDesignManager.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList(); +// +// using var table = ImRaii.Table("##FixedTable", 4); +// 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; +// +// 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(); +// style.Push(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2); +// font.Push(UiBuilder.IconFont); +// if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}")) +// { +// _fullPathCache.RemoveAt(i--); +// Glamourer.FixedDesignManager.FixedDesigns.Remove(name); +// continue; +// } +// +// 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)) +// { +// Glamourer.Config.FixedDesigns[i].Enabled = tmp; +// Glamourer.Config.Save(); +// } +// +// 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.Text(Glamourer.FixedDesignManager.FixedDesigns.Data[i].Jobs.Name); +// ImGui.TableNextColumn(); +// ImGui.Text(path); +// } +// +// 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) +// { +// ImGui.SetNextItemWidth(-1); +// 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 1c48741..7440452 100644 --- a/Glamourer/Gui/InterfaceHelpers.cs +++ b/Glamourer/Gui/InterfaceHelpers.cs @@ -6,197 +6,98 @@ using Glamourer.Structs; using ImGuiNET; using Penumbra.GameData.Enums; -namespace Glamourer.Gui -{ - internal partial class Interface - { - // Push the stain color to type and if it is too bright, turn the text color black. - // Return number of pushed styles. - private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button) - { - ImGui.PushStyleColor(type, stain.RgbaColor); - if (stain.Intensity > 127) - { - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010); - return 2; - } +namespace Glamourer.Gui; - return 1; - } +//internal partial class Interface +//{ +// // Push the stain color to type and if it is too bright, turn the text color black. +// // Return number of pushed styles. +// private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button) +// { +// ImGui.PushStyleColor(type, stain.RgbaColor); +// if (stain.Intensity > 127) +// { +// ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010); +// return 2; +// } +// +// return 1; +// } +// - // Go through a whole customization struct and fix up all settings that need fixing. - private static void FixUpAttributes(ref CharacterCustomization customization) - { - var set = Glamourer.Customization.GetList(customization.Clan, customization.Gender); - foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId))) - { - switch (id) - { - case CustomizationId.Race: break; - case CustomizationId.Clan: break; - case CustomizationId.BodyType: break; - case CustomizationId.Gender: break; - case CustomizationId.FacialFeaturesTattoos: break; - case CustomizationId.HighlightsOnFlag: break; - case CustomizationId.Face: break; - default: - var count = set.Count(id); - if (set.DataByValue(id, customization[id], out _) < 0) - if (count == 0) - customization[id] = 0; - else - customization[id] = set.Data(id, 0).Value; - break; - } - } - } +// - // Change a race and fix up all required customizations afterwards. - private static bool ChangeRace(ref CharacterCustomization customization, SubRace clan) - { - if (clan == customization.Clan) - return false; - - var race = clan.ToRace(); - customization.Race = race; - customization.Clan = clan; - - if (race == Race.Hrothgar) - customization.Gender = Gender.Male; - - FixUpAttributes(ref customization); - - return true; - } - - // Change a gender and fix up all required customizations afterwards. - private static bool ChangeGender(ref CharacterCustomization customization, Gender gender) - { - if (gender == customization.Gender) - return false; - - customization.Gender = gender; - FixUpAttributes(ref customization); - - return true; - } - - private static string ClanName(SubRace race, Gender gender) - { - if (gender == Gender.Female) - return race switch - { - SubRace.Midlander => Glamourer.Customization.GetName(CustomName.MidlanderM), - SubRace.Highlander => Glamourer.Customization.GetName(CustomName.HighlanderM), - SubRace.Wildwood => Glamourer.Customization.GetName(CustomName.WildwoodM), - SubRace.Duskwight => Glamourer.Customization.GetName(CustomName.DuskwightM), - SubRace.Plainsfolk => Glamourer.Customization.GetName(CustomName.PlainsfolkM), - SubRace.Dunesfolk => Glamourer.Customization.GetName(CustomName.DunesfolkM), - SubRace.SeekerOfTheSun => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunM), - SubRace.KeeperOfTheMoon => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonM), - SubRace.Seawolf => Glamourer.Customization.GetName(CustomName.SeawolfM), - SubRace.Hellsguard => Glamourer.Customization.GetName(CustomName.HellsguardM), - SubRace.Raen => Glamourer.Customization.GetName(CustomName.RaenM), - SubRace.Xaela => Glamourer.Customization.GetName(CustomName.XaelaM), - SubRace.Helion => Glamourer.Customization.GetName(CustomName.HelionM), - SubRace.Lost => Glamourer.Customization.GetName(CustomName.LostM), - SubRace.Rava => Glamourer.Customization.GetName(CustomName.RavaF), - SubRace.Veena => Glamourer.Customization.GetName(CustomName.VeenaF), - _ => throw new ArgumentOutOfRangeException(nameof(race), race, null), - }; - - return race switch - { - SubRace.Midlander => Glamourer.Customization.GetName(CustomName.MidlanderF), - SubRace.Highlander => Glamourer.Customization.GetName(CustomName.HighlanderF), - SubRace.Wildwood => Glamourer.Customization.GetName(CustomName.WildwoodF), - SubRace.Duskwight => Glamourer.Customization.GetName(CustomName.DuskwightF), - SubRace.Plainsfolk => Glamourer.Customization.GetName(CustomName.PlainsfolkF), - SubRace.Dunesfolk => Glamourer.Customization.GetName(CustomName.DunesfolkF), - SubRace.SeekerOfTheSun => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunF), - SubRace.KeeperOfTheMoon => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonF), - SubRace.Seawolf => Glamourer.Customization.GetName(CustomName.SeawolfF), - SubRace.Hellsguard => Glamourer.Customization.GetName(CustomName.HellsguardF), - SubRace.Raen => Glamourer.Customization.GetName(CustomName.RaenF), - SubRace.Xaela => Glamourer.Customization.GetName(CustomName.XaelaF), - SubRace.Helion => Glamourer.Customization.GetName(CustomName.HelionM), - SubRace.Lost => Glamourer.Customization.GetName(CustomName.LostM), - SubRace.Rava => Glamourer.Customization.GetName(CustomName.RavaF), - SubRace.Veena => Glamourer.Customization.GetName(CustomName.VeenaF), - _ => throw new ArgumentOutOfRangeException(nameof(race), race, null), - }; - } - - private enum DesignNameUse - { - SaveCurrent, - NewDesign, - DuplicateDesign, - NewFolder, - FromClipboard, - } - - private void DrawDesignNamePopup(DesignNameUse use) - { - if (ImGui.BeginPopup($"{DesignNamePopupLabel}{use}")) - { - if (ImGui.InputText("##designName", ref _newDesignName, 64, ImGuiInputTextFlags.EnterReturnsTrue) - && _newDesignName.Any()) - { - switch (use) - { - case DesignNameUse.SaveCurrent: - SaveNewDesign(ConditionalCopy(_currentSave, _holdShift, _holdCtrl)); - break; - case DesignNameUse.NewDesign: - var empty = new CharacterSave(); - empty.Load(CharacterCustomization.Default); - empty.WriteCustomizations = false; - SaveNewDesign(empty); - break; - case DesignNameUse.DuplicateDesign: - SaveNewDesign(ConditionalCopy(_selection!.Data, _holdShift, _holdCtrl)); - break; - case DesignNameUse.NewFolder: - _designs.FileSystem - .CreateAllFolders($"{_newDesignName}/a"); // Filename is just ignored, but all folders are created. - break; - case DesignNameUse.FromClipboard: - try - { - var text = ImGui.GetClipboardText(); - var save = CharacterSave.FromString(text); - SaveNewDesign(save); - } - catch (Exception e) - { - PluginLog.Information($"Could not save new Design from Clipboard:\n{e}"); - } - - break; - } - - _newDesignName = string.Empty; - ImGui.CloseCurrentPopup(); - } - - if (_keyboardFocus) - { - ImGui.SetKeyboardFocusHere(); - _keyboardFocus = false; - } - - ImGui.EndPopup(); - } - } - - private void OpenDesignNamePopup(DesignNameUse use) - { - _newDesignName = string.Empty; - _keyboardFocus = true; - _holdCtrl = ImGui.GetIO().KeyCtrl; - _holdShift = ImGui.GetIO().KeyShift; - ImGui.OpenPopup($"{DesignNamePopupLabel}{use}"); - } - } -} +// +// +// private enum DesignNameUse +// { +// SaveCurrent, +// NewDesign, +// DuplicateDesign, +// NewFolder, +// FromClipboard, +// } +// +// private void DrawDesignNamePopup(DesignNameUse use) +// { +// if (ImGui.BeginPopup($"{DesignNamePopupLabel}{use}")) +// { +// if (ImGui.InputText("##designName", ref _newDesignName, 64, ImGuiInputTextFlags.EnterReturnsTrue) +// && _newDesignName.Any()) +// { +// switch (use) +// { +// case DesignNameUse.SaveCurrent: +// SaveNewDesign(ConditionalCopy(_currentSave, _holdShift, _holdCtrl)); +// break; +// case DesignNameUse.NewDesign: +// var empty = new CharacterSave(); +// empty.Load(CharacterCustomization.Default); +// empty.WriteCustomizations = false; +// SaveNewDesign(empty); +// break; +// case DesignNameUse.DuplicateDesign: +// SaveNewDesign(ConditionalCopy(_selection!.Data, _holdShift, _holdCtrl)); +// break; +// case DesignNameUse.NewFolder: +// _designs.FileSystem +// .CreateAllFolders($"{_newDesignName}/a"); // Filename is just ignored, but all folders are created. +// break; +// case DesignNameUse.FromClipboard: +// try +// { +// var text = ImGui.GetClipboardText(); +// var save = CharacterSave.FromString(text); +// SaveNewDesign(save); +// } +// catch (Exception e) +// { +// PluginLog.Information($"Could not save new Design from Clipboard:\n{e}"); +// } +// +// break; +// } +// +// _newDesignName = string.Empty; +// ImGui.CloseCurrentPopup(); +// } +// +// if (_keyboardFocus) +// { +// ImGui.SetKeyboardFocusHere(); +// _keyboardFocus = false; +// } +// +// ImGui.EndPopup(); +// } +// } +// +// private void OpenDesignNamePopup(DesignNameUse use) +// { +// _newDesignName = string.Empty; +// _keyboardFocus = true; +// _holdCtrl = ImGui.GetIO().KeyCtrl; +// _holdShift = ImGui.GetIO().KeyShift; +// ImGui.OpenPopup($"{DesignNamePopupLabel}{use}"); +// } +//} diff --git a/Glamourer/Gui/InterfaceInitialization.cs b/Glamourer/Gui/InterfaceInitialization.cs index 7e20374..e81d3dc 100644 --- a/Glamourer/Gui/InterfaceInitialization.cs +++ b/Glamourer/Gui/InterfaceInitialization.cs @@ -4,92 +4,81 @@ using System.Reflection; using ImGuiNET; using Penumbra.GameData.Enums; using Lumina.Excel.GeneratedSheets; -using Glamourer.Structs; +using Item = Glamourer.Structs.Item; +using Stain = Glamourer.Structs.Stain; -namespace Glamourer.Gui -{ - internal partial class Interface - { - private const float ColorButtonWidth = 22.5f; - private const float ColorComboWidth = 140f; - private const float ItemComboWidth = 350f; +namespace Glamourer.Gui; - private static readonly Vector4 GreyVector = new(0.5f, 0.5f, 0.5f, 1); +//internal partial class Interface +//{ +// private const float ColorButtonWidth = 22.5f; +// private const float ColorComboWidth = 140f; +// private const float ItemComboWidth = 350f; +// +// private static readonly Vector4 GreyVector = new(0.5f, 0.5f, 0.5f, 1); +// +// private static ComboWithFilter CreateDefaultStainCombo(IReadOnlyList stains) +// => new("##StainCombo", ColorComboWidth, ColorButtonWidth, stains, +// s => s.Name.ToString()) +// { +// Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge, +// PreList = () => +// { +// ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); +// ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); +// ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); +// }, +// PostList = () => { ImGui.PopStyleVar(3); }, +// CreateSelectable = s => +// { +// var push = PushColor(s); +// var ret = ImGui.Button($"{s.Name}##Stain{(byte)s.RowIndex}", +// Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize)); +// ImGui.PopStyleColor(push); +// return ret; +// }, +// ItemsAtOnce = 12, +// }; +// +// private ComboWithFilter CreateItemCombo(EquipSlot slot, IReadOnlyList items) +// => new($"{_equipSlotNames[slot]}##Equip", ItemComboWidth, ItemComboWidth, items, i => i.Name) +// { +// Flags = ImGuiComboFlags.HeightLarge, +// CreateSelectable = i => +// { +// var ret = ImGui.Selectable(i.Name); +// var setId = $"({(int)i.MainModel.id})"; +// var size = ImGui.CalcTextSize(setId).X; +// ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - size - ImGui.GetStyle().ItemInnerSpacing.X); +// ImGui.TextColored(GreyVector, setId); +// return ret; +// }, +// }; +// +// private (ComboWithFilter, ComboWithFilter) CreateCombos(EquipSlot slot, IReadOnlyList items, +// ComboWithFilter defaultStain) +// => (CreateItemCombo(slot, items), new ComboWithFilter($"##{slot}Stain", defaultStain)); +// - private static ComboWithFilter CreateDefaultStainCombo(IReadOnlyList stains) - => new("##StainCombo", ColorComboWidth, ColorButtonWidth, stains, - s => s.Name.ToString()) - { - Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge, - PreList = () => - { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); - }, - PostList = () => { ImGui.PopStyleVar(3); }, - CreateSelectable = s => - { - var push = PushColor(s); - var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}", - Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize)); - ImGui.PopStyleColor(push); - return ret; - }, - ItemsAtOnce = 12, - }; - - private ComboWithFilter CreateItemCombo(EquipSlot slot, IReadOnlyList items) - => new($"{_equipSlotNames[slot]}##Equip", ItemComboWidth, ItemComboWidth, items, i => i.Name) - { - Flags = ImGuiComboFlags.HeightLarge, - CreateSelectable = i => - { - var ret = ImGui.Selectable(i.Name); - var setId = $"({(int) i.MainModel.id})"; - var size = ImGui.CalcTextSize(setId).X; - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - size - ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.TextColored(GreyVector, setId); - return ret; - }, - }; - - private (ComboWithFilter, ComboWithFilter) CreateCombos(EquipSlot slot, IReadOnlyList items, - ComboWithFilter defaultStain) - => (CreateItemCombo(slot, items), new ComboWithFilter($"##{slot}Stain", defaultStain)); - - private static ImGuiScene.TextureWrap? GetLegacyTattooIcon() - { - using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); - if (resource != null) - { - var rawImage = new byte[resource.Length]; - resource.Read(rawImage, 0, (int) resource.Length); - return Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4); - } - - return null; - } - - private static Dictionary GetEquipSlotNames() - { - var sheet = Dalamud.GameData.GetExcelSheet()!; - var ret = new Dictionary(12) - { - [EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand", - [EquipSlot.OffHand] = sheet.GetRow(739)?.Text.ToString() ?? "Off Hand", - [EquipSlot.Head] = sheet.GetRow(740)?.Text.ToString() ?? "Head", - [EquipSlot.Body] = sheet.GetRow(741)?.Text.ToString() ?? "Body", - [EquipSlot.Hands] = sheet.GetRow(742)?.Text.ToString() ?? "Hands", - [EquipSlot.Legs] = sheet.GetRow(744)?.Text.ToString() ?? "Legs", - [EquipSlot.Feet] = sheet.GetRow(745)?.Text.ToString() ?? "Feet", - [EquipSlot.Ears] = sheet.GetRow(746)?.Text.ToString() ?? "Ears", - [EquipSlot.Neck] = sheet.GetRow(747)?.Text.ToString() ?? "Neck", - [EquipSlot.Wrists] = sheet.GetRow(748)?.Text.ToString() ?? "Wrists", - [EquipSlot.RFinger] = sheet.GetRow(749)?.Text.ToString() ?? "Right Ring", - [EquipSlot.LFinger] = sheet.GetRow(750)?.Text.ToString() ?? "Left Ring", - }; - return ret; - } - } -} +// +// private static Dictionary GetEquipSlotNames() +// { +// var sheet = Dalamud.GameData.GetExcelSheet()!; +// var ret = new Dictionary(12) +// { +// [EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand", +// [EquipSlot.OffHand] = sheet.GetRow(739)?.Text.ToString() ?? "Off Hand", +// [EquipSlot.Head] = sheet.GetRow(740)?.Text.ToString() ?? "Head", +// [EquipSlot.Body] = sheet.GetRow(741)?.Text.ToString() ?? "Body", +// [EquipSlot.Hands] = sheet.GetRow(742)?.Text.ToString() ?? "Hands", +// [EquipSlot.Legs] = sheet.GetRow(744)?.Text.ToString() ?? "Legs", +// [EquipSlot.Feet] = sheet.GetRow(745)?.Text.ToString() ?? "Feet", +// [EquipSlot.Ears] = sheet.GetRow(746)?.Text.ToString() ?? "Ears", +// [EquipSlot.Neck] = sheet.GetRow(747)?.Text.ToString() ?? "Neck", +// [EquipSlot.Wrists] = sheet.GetRow(748)?.Text.ToString() ?? "Wrists", +// [EquipSlot.RFinger] = sheet.GetRow(749)?.Text.ToString() ?? "Right Ring", +// [EquipSlot.LFinger] = sheet.GetRow(750)?.Text.ToString() ?? "Left Ring", +// }; +// return ret; +// } +//} diff --git a/Glamourer/Gui/InterfaceMiscellaneous.cs b/Glamourer/Gui/InterfaceMiscellaneous.cs index f9e4cc4..ada185d 100644 --- a/Glamourer/Gui/InterfaceMiscellaneous.cs +++ b/Glamourer/Gui/InterfaceMiscellaneous.cs @@ -4,60 +4,49 @@ using ImGuiNET; namespace Glamourer.Gui; -internal partial class Interface -{ - private static bool DrawCheckMark(string label, bool value, Action setter) - { - var startValue = value; - if (ImGui.Checkbox(label, ref startValue) && startValue != value) - { - setter(startValue); - return true; - } - - 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; - } -} +//internal partial class Interface +//{ +// +// 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 bbdbe0b..00af3ff 100644 --- a/Glamourer/Gui/InterfaceRevertables.cs +++ b/Glamourer/Gui/InterfaceRevertables.cs @@ -6,82 +6,82 @@ using OtterGui.Raii; namespace Glamourer.Gui; -internal partial class Interface -{ - private string? _currentRevertableName; - private CharacterSave? _currentRevertable; - - private void DrawRevertablesSelector() - { - 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 _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) - { - ImGui.EndChild(); - } - - DrawSelectionButtons(); - ImGui.EndGroup(); - } - - private void DrawRevertablePanel() - { - using var group = ImRaii.Group(); - { - 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(); - } -} +//internal partial class Interface +//{ +// private string? _currentRevertableName; +// private CharacterSave? _currentRevertable; +// +// private void DrawRevertablesSelector() +// { +// 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 _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) +// { +// ImGui.EndChild(); +// } +// +// DrawSelectionButtons(); +// ImGui.EndGroup(); +// } +// +// private void DrawRevertablePanel() +// { +// using var group = ImRaii.Group(); +// { +// 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(); +// } +//} diff --git a/Glamourer/ObjectManager.cs b/Glamourer/ObjectManager.cs new file mode 100644 index 0000000..3f47829 --- /dev/null +++ b/Glamourer/ObjectManager.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; + +namespace Glamourer; + +public unsafe struct Actor : IEquatable +{ + public static readonly Actor Null = new() { Pointer = null }; + + public FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Pointer; + + public IntPtr Address + => (IntPtr)Pointer; + + public static implicit operator Actor(IntPtr? pointer) + => new() { Pointer = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)pointer.GetValueOrDefault(IntPtr.Zero) }; + + public static implicit operator IntPtr(Actor actor) + => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; + + public Character? Character + => Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character; + + public bool IsAvailable + => Pointer->GameObject.GetIsTargetable(); + + public bool IsHuman + => Pointer != null && Pointer->ModelCharaId == 0; + + public int ModelId + => Pointer != null ? Pointer->ModelCharaId : 0; + + public void SetModelId(int value) + { + if (Pointer != null) + Pointer->ModelCharaId = value; + } + + public static implicit operator bool(Actor actor) + => actor.Pointer != null; + + public static bool operator true(Actor actor) + => actor.Pointer != null; + + public static bool operator false(Actor actor) + => actor.Pointer == null; + + public static bool operator !(Actor actor) + => actor.Pointer == null; + + public bool Equals(Actor other) + => Pointer == other.Pointer; + + public override bool Equals(object? obj) + => obj is Actor other && Equals(other); + + public override int GetHashCode() + => ((ulong)Pointer).GetHashCode(); + + public static bool operator ==(Actor lhs, Actor rhs) + => lhs.Pointer == rhs.Pointer; + + public static bool operator !=(Actor lhs, Actor rhs) + => lhs.Pointer != rhs.Pointer; +} + +public static class ObjectManager +{ + private const int GPosePlayerIndex = 201; + private const int CharacterScreenIndex = 240; + private const int ExamineScreenIndex = 241; + private const int FittingRoomIndex = 242; + private const int DyePreviewIndex = 243; + private static readonly Dictionary _nameCounters = new(); + private static readonly Dictionary _gPoseActors = new(CharacterScreenIndex - GPosePlayerIndex); + + public static bool IsInGPose() + => Dalamud.Objects[GPosePlayerIndex] != null; + + public static Actor GPosePlayer + => Dalamud.Objects[GPosePlayerIndex]?.Address; + + public static Actor Player + => Dalamud.ClientState.LocalPlayer?.Address; + + public record struct ActorData(string Label, string Name, Actor Actor, bool Modifiable, Actor GPose); + + public static IEnumerable GetEnumerator() + { + _nameCounters.Clear(); + _gPoseActors.Clear(); + for (var i = GPosePlayerIndex; i < CharacterScreenIndex; ++i) + { + var character = Dalamud.Objects[i]; + if (character == null) + break; + + var name = character.Name.TextValue; + _gPoseActors[name] = character.Address; + yield return new ActorData(GetLabel(character, name, 0, true), name, character.Address, true, Actor.Null); + } + + var actor = Dalamud.Objects[CharacterScreenIndex]; + if (actor != null) + yield return new ActorData("Character Screen Actor", string.Empty, actor.Address, false, Actor.Null); + + actor = Dalamud.Objects[ExamineScreenIndex]; + if (actor != null) + yield return new ActorData("Examine Screen Actor", string.Empty, actor.Address, false, Actor.Null); + + actor = Dalamud.Objects[FittingRoomIndex]; + if (actor != null) + yield return new ActorData("Fitting Room Actor", string.Empty, actor.Address, false, Actor.Null); + + actor = Dalamud.Objects[DyePreviewIndex]; + if (actor != null) + yield return new ActorData("Dye Preview Actor", string.Empty, actor.Address, false, Actor.Null); + + for (var i = 0; i < GPosePlayerIndex; ++i) + { + var character = Dalamud.Objects[i]; + if (character == null + || character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion + or ObjectKind.Retainer)) + continue; + + var name = character.Name.TextValue; + if (name.Length == 0) + continue; + + if (_nameCounters.TryGetValue(name, out var num)) + _nameCounters[name] = ++num; + else + _nameCounters[name] = num = 1; + + if (!_gPoseActors.TryGetValue(name, out var gPose)) + gPose = Actor.Null; + + yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose); + } + + for (var i = DyePreviewIndex + 1; i < Dalamud.Objects.Length; ++i) + { + var character = Dalamud.Objects[i]; + if (character == null + || !((Actor)character.Address).IsAvailable + || character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion + or ObjectKind.Retainer)) + continue; + + var name = character.Name.TextValue; + if (name.Length == 0) + continue; + + if (_nameCounters.TryGetValue(name, out var num)) + _nameCounters[name] = ++num; + else + _nameCounters[name] = num = 1; + + if (!_gPoseActors.TryGetValue(name, out var gPose)) + gPose = Actor.Null; + + yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose); + } + } + + private static unsafe string GetLabel(GameObject player, string playerName, int num, bool gPose) + { + if (player.ObjectKind == ObjectKind.Player) + return gPose ? $"{playerName} (GPose)" : num == 1 ? playerName : $"{playerName} #{num}"; + + if (((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player!.Address)->ModelCharaId == 0) + return gPose ? $"{playerName} (GPose, NPC)" : num == 1 ? $"{playerName} (NPC)" : $"{playerName} #{num} (NPC)"; + + return gPose ? $"{playerName} (GPose, Monster)" : num == 1 ? $"{playerName} (Monster)" : $"{playerName} #{num} (Monster)"; + } +} diff --git a/Glamourer/RedrawManager.cs b/Glamourer/RedrawManager.cs new file mode 100644 index 0000000..00112b3 --- /dev/null +++ b/Glamourer/RedrawManager.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Hooking; +using Dalamud.Logging; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Glamourer.Customization; +using Penumbra.GameData.ByteString; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer; + +public unsafe class RedrawManager : IDisposable +{ + public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* 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")] + public Hook FlagSlotForUpdateHook = null!; + + private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slot, CharacterArmor* data) + { + return FlagSlotForUpdateHook.Original(drawObject, slot, data); + } + + public delegate void LoadWeaponDelegate(IntPtr characterOffset, uint slot, ulong data, byte unk); + + [Signature("E8 ?? ?? ?? ?? 44 8B 9F")] + public Hook LoadWeaponHook = null!; + + private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong data, byte unk) + { + const int offset = 0xD8 * 8; + PluginLog.Information($"0x{characterOffset:X}, 0x{characterOffset - offset:X}, {slot}, {data:16X}, {unk}"); + LoadWeaponHook.Original(characterOffset, slot, data, unk); + } + + //[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; + // + + private readonly Dictionary _currentRedraws = new(32); + + public RedrawManager() + { + SignatureHelper.Initialise(this); +// FixedDesigns = new FixedDesigns(designs); + Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw; + FlagSlotForUpdateHook.Enable(); + LoadWeaponHook.Enable(); +// +// if (Glamourer.Config.ApplyFixedDesigns) +// Enable(); + } + + public void Dispose() + { + FlagSlotForUpdateHook.Dispose(); + LoadWeaponHook.Dispose(); + Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw; + //FlagSlotForUpdateHook?.Dispose(); + } + + public void Set(Character* actor, CharacterSave save) + { + var name = GetName(actor); + if (name.Length == 0) + return; + + _currentRedraws[name] = save; + } + + public void Set(IntPtr actor, CharacterSave save) + => Set((Character*)actor, save); + + public void Revert(Character* actor) + => _currentRedraws.Remove(GetName(actor)); + + public void Revert(IntPtr actor) + => Revert((Character*)actor); + + private static string GetName(Character* actor) + { + return string.Concat(new Utf8String(actor->GameObject.Name) + .Select(c => (char)c) + .Append(actor->GameObject.ObjectKind == (byte)ObjectKind.Pc ? (char)actor->HomeWorld : (char)actor->GameObject.ObjectIndex)); + } + + private void Cleanup(object? _, ushort _1) + => _currentRedraws.Clear(); + + public void ChangeEquip(Human* actor, EquipSlot slot, CharacterArmor item) + => Flag(actor, slot.ToIndex(), &item); + + public void ChangeEquip(Character* character, EquipSlot slot, CharacterArmor item) + => ChangeEquip((Human*)character->GameObject.DrawObject, slot, item); + + public void ChangeEquip(IntPtr character, EquipSlot slot, CharacterArmor item) + => ChangeEquip((Character*)character, slot, item); + + private void OnCharacterRedraw(IntPtr addr, IntPtr modelId, IntPtr customize, IntPtr equipData) + { + var name = GetName((Character*)addr); + if (_currentRedraws.TryGetValue(name, out var save)) + { + *(CustomizationData*)customize = *(CustomizationData*)save.Customize.Address; + var equip = (CharacterEquip)equipData; + var newEquip = save.Equipment; + for (var i = 0; i < 10; ++i) + equip[i] = newEquip[i]; + } + + + //*(uint*)modelId = 0; + + //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) + //{ + // var name = new Utf8String(human->GameObject.Name).ToString(); + // if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs)) + // { + // var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob)); + // if (design != null) + // { + // if (design.Design.Data.WriteCustomizations) + // *(CharacterCustomization*)customize = design.Design.Data.Customizations; + // + // var data = (uint*)equipData; + // for (var i = 0u; i < 10; ++i) + // { + // 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, + // }; + // } + // } + // } + //} + } +// +// 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 void UpdateSlot(Human* drawObject, EquipSlot slot, CharacterArmor data) +// { +// var idx = slot.ToIndex(); +// if (idx >= 10) +// return; +// +// FlagSlotForUpdateDetour(drawObject, idx, (uint*)&data); +// } +}