From dad146d04357dbe104a0c8366bce67c64b82a33b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 19 Oct 2022 15:24:27 +0200 Subject: [PATCH] Some more reworking --- .../Customization/CharaMakeParams.cs | 42 +- .../Customization/CustomizationId.cs | 107 ------ .../Customization/CustomizationManager.cs | 4 +- .../Customization/CustomizationOptions.cs | 237 +++++++----- .../Customization/CustomizationSet.cs | 325 +++++++++------- Glamourer.GameData/Customization/Customize.cs | 363 +++--------------- ...{CustomizationData.cs => CustomizeData.cs} | 10 +- .../Customization/CustomizeFlag.cs | 92 +++++ .../Customization/CustomizeIndex.cs | 173 +++++++++ .../Customization/CustomizeValue.cs | 34 ++ Glamourer.GameData/RestrictedGear.cs | 3 + Glamourer/Api/GlamourerIpc.cs | 68 ++-- Glamourer/Api/PenumbraAttach.cs | 139 +++---- Glamourer/Designs/Design.cs | 26 -- Glamourer/Designs/FixedDesigns.cs | 1 + Glamourer/Glamourer.cs | 4 +- Glamourer/Glamourer.csproj | 3 +- .../CustomizationDrawer.Color.cs | 18 +- .../CustomizationDrawer.GenderRace.cs | 5 +- .../Customization/CustomizationDrawer.Icon.cs | 23 +- .../Customization/CustomizationDrawer.Main.cs | 41 +- .../CustomizationDrawer.Multi.cs | 23 +- .../CustomizationDrawer.Simple.cs | 30 +- .../Gui/Equipment/EquipmentDrawer.Items.cs | 7 + Glamourer/Gui/Interface.Actors.cs | 51 +++ Glamourer/Gui/Interface.DebugDataTab.cs | 51 +++ Glamourer/Gui/Interface.cs | 3 + Glamourer/Interop/Actor.Identifier.cs | 33 +- Glamourer/Interop/Actor.cs | 110 +----- Glamourer/Interop/DrawObject.cs | 97 +++++ Glamourer/Interop/IDesignable.cs | 16 + Glamourer/Interop/RedrawManager.Customize.cs | 52 +++ Glamourer/Interop/RedrawManager.Equipment.cs | 68 ++++ Glamourer/Interop/RedrawManager.Weapons.cs | 102 +++++ Glamourer/Interop/RedrawManager.cs | 215 +---------- Glamourer/Saves/Design.cs | 212 ++++++++++ Glamourer/State/ApplicationFlags.cs | 75 ++++ Glamourer/State/CharacterSave.cs | 89 +---- Glamourer/State/CurrentDesign.cs | 41 +- Glamourer/State/IDesign.cs | 10 + Glamourer/Util/CustomizeExtensions.cs | 31 +- 41 files changed, 1714 insertions(+), 1320 deletions(-) delete mode 100644 Glamourer.GameData/Customization/CustomizationId.cs rename Glamourer.GameData/Customization/{CustomizationData.cs => CustomizeData.cs} (66%) create mode 100644 Glamourer.GameData/Customization/CustomizeFlag.cs create mode 100644 Glamourer.GameData/Customization/CustomizeIndex.cs create mode 100644 Glamourer.GameData/Customization/CustomizeValue.cs delete mode 100644 Glamourer/Designs/Design.cs create mode 100644 Glamourer/Gui/Interface.DebugDataTab.cs create mode 100644 Glamourer/Interop/DrawObject.cs create mode 100644 Glamourer/Interop/IDesignable.cs create mode 100644 Glamourer/Interop/RedrawManager.Customize.cs create mode 100644 Glamourer/Interop/RedrawManager.Equipment.cs create mode 100644 Glamourer/Interop/RedrawManager.Weapons.cs create mode 100644 Glamourer/Saves/Design.cs create mode 100644 Glamourer/State/ApplicationFlags.cs create mode 100644 Glamourer/State/IDesign.cs diff --git a/Glamourer.GameData/Customization/CharaMakeParams.cs b/Glamourer.GameData/Customization/CharaMakeParams.cs index 4aff1b8..5234549 100644 --- a/Glamourer.GameData/Customization/CharaMakeParams.cs +++ b/Glamourer.GameData/Customization/CharaMakeParams.cs @@ -22,21 +22,23 @@ public class CharaMakeParams : ExcelRow IconSelector = 1, ColorPicker = 2, DoubleColorPicker = 3, - MultiIconSelector = 4, + IconCheckmark = 4, Percentage = 5, + Checkmark = 6, // custom + Nothing = 7, // custom } public struct Menu { - public uint Id; - public byte InitVal; - public MenuType Type; - public byte Size; - public byte LookAt; - public uint Mask; - public CustomizationId Customization; - public uint[] Values; - public byte[] Graphic; + public uint Id; + public byte InitVal; + public MenuType Type; + public byte Size; + public byte LookAt; + public uint Mask; + public uint Customize; + public uint[] Values; + public byte[] Graphic; } public struct FacialFeatures @@ -51,7 +53,7 @@ public class CharaMakeParams : ExcelRow public Menu[] Menus { get; set; } = new Menu[NumMenus]; public byte[] Voices { get; set; } = new byte[NumVoices]; public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces]; - + public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip]; public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language) @@ -64,15 +66,15 @@ public class CharaMakeParams : ExcelRow var currentOffset = 0; for (var i = 0; i < 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]; + 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].Customize = parser.ReadColumn(6 * NumMenus + currentOffset); + Menus[i].Values = new uint[Menus[i].Size]; switch (Menus[i].Type) { diff --git a/Glamourer.GameData/Customization/CustomizationId.cs b/Glamourer.GameData/Customization/CustomizationId.cs deleted file mode 100644 index 493fb2c..0000000 --- a/Glamourer.GameData/Customization/CustomizationId.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using Penumbra.GameData.Enums; - -namespace Glamourer.Customization; - -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 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/CustomizationManager.cs b/Glamourer.GameData/Customization/CustomizationManager.cs index 3d1a4f0..9f2b0e1 100644 --- a/Glamourer.GameData/Customization/CustomizationManager.cs +++ b/Glamourer.GameData/Customization/CustomizationManager.cs @@ -13,9 +13,9 @@ namespace Glamourer.Customization private CustomizationManager() { } - public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language) + public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData) { - _options ??= new CustomizationOptions(pi, gameData, language); + _options ??= new CustomizationOptions(pi, gameData); return new CustomizationManager(); } diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index 3e70e83..e353361 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -64,9 +64,9 @@ public partial class CustomizationOptions public string GetName(CustomName name) => _names[(int)name]; - internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language) + internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData) { - var tmp = new TemporaryData(gameData, this, language); + var tmp = new TemporaryData(gameData, this); _icons = new IconStorage(pi, gameData, _customizationSets.Length * 50); Valid = tmp.Valid; SetNames(gameData, tmp); @@ -149,15 +149,15 @@ public partial class CustomizationOptions HighlightColors = _highlightPicker, TattooColors = _tattooColorPicker, LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark, - LipColorsLight = hrothgar ? Array.Empty() : _lipColorPickerLight, + LipColorsLight = 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), + NumEyebrows = GetListSize(row, CustomizeIndex.Eyebrows), + NumEyeShapes = GetListSize(row, CustomizeIndex.EyeShape), + NumNoseShapes = GetListSize(row, CustomizeIndex.Nose), + NumJawShapes = GetListSize(row, CustomizeIndex.Jaw), + NumMouthShapes = GetListSize(row, CustomizeIndex.Mouth), FacePaints = GetFacePaints(race, gender), TailEarShapes = GetTailEarShapes(row), }; @@ -171,7 +171,7 @@ public partial class CustomizationOptions return set; } - public TemporaryData(DataManager gameData, CustomizationOptions options, ClientLanguage language) + public TemporaryData(DataManager gameData, CustomizationOptions options) { _options = options; _cmpFile = new CmpFile(gameData); @@ -181,18 +181,18 @@ public partial class CustomizationOptions .MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[] { "charamaketype", - language.ToLumina(), + gameData.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); + _highlightPicker = CreateColorPicker(CustomizeIndex.HighlightsColor, 256, 192); + _lipColorPickerDark = CreateColorPicker(CustomizeIndex.LipColor, 512, 96); + _lipColorPickerLight = CreateColorPicker(CustomizeIndex.LipColor, 1024, 96, true); + _eyeColorPicker = CreateColorPicker(CustomizeIndex.EyeColorLeft, 0, 192); + _facePaintColorPickerDark = CreateColorPicker(CustomizeIndex.FacePaintColor, 640, 96); + _facePaintColorPickerLight = CreateColorPicker(CustomizeIndex.FacePaintColor, 1152, 96, true); + _tattooColorPicker = CreateColorPicker(CustomizeIndex.TattooColor, 0, 192); } // Required sheets. @@ -203,19 +203,19 @@ public partial class CustomizationOptions private readonly CmpFile _cmpFile; // Those values are shared between all races. - private readonly CustomizationData[] _highlightPicker; - private readonly CustomizationData[] _eyeColorPicker; - private readonly CustomizationData[] _facePaintColorPickerDark; - private readonly CustomizationData[] _facePaintColorPickerLight; - private readonly CustomizationData[] _lipColorPickerDark; - private readonly CustomizationData[] _lipColorPickerLight; - private readonly CustomizationData[] _tattooColorPicker; + private readonly CustomizeData[] _highlightPicker; + private readonly CustomizeData[] _eyeColorPicker; + private readonly CustomizeData[] _facePaintColorPickerDark; + private readonly CustomizeData[] _facePaintColorPickerLight; + private readonly CustomizeData[] _lipColorPickerDark; + private readonly CustomizeData[] _lipColorPickerLight; + private readonly CustomizeData[] _tattooColorPicker; private readonly CustomizationOptions _options; - private CustomizationData[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false) + private CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false) => _cmpFile.GetSlice(offset, num) - .Select((c, i) => new CustomizationData(id, (CustomizationByteValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i))) + .Select((c, i) => new CustomizeData(index, (CustomizeValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i))) .ToArray(); @@ -227,12 +227,12 @@ public partial class CustomizationOptions return; } - var tmp = new IReadOnlyList[set.Faces.Count + 1]; + var tmp = new IReadOnlyList[set.Faces.Count + 1]; tmp[0] = set.HairStyles; for (var i = 1; i <= set.Faces.Count; ++i) { - bool Valid(CustomizationData c) + bool Valid(CustomizeData c) { var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0; return data == 0 || data == i + set.Faces.Count; @@ -247,22 +247,38 @@ public partial class CustomizationOptions 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 => + set.Types = Enum.GetValues().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: + case CustomizeIndex.HighlightsColor: + case CustomizeIndex.EyeColorLeft: + case CustomizeIndex.EyeColorRight: return CharaMakeParams.MenuType.ColorPicker; + case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing; + case CustomizeIndex.FacePaintReversed: + case CustomizeIndex.Highlights: + case CustomizeIndex.SmallIris: + case CustomizeIndex.Lipstick: + return CharaMakeParams.MenuType.Checkmark; + case CustomizeIndex.FacialFeature1: + case CustomizeIndex.FacialFeature2: + case CustomizeIndex.FacialFeature3: + case CustomizeIndex.FacialFeature4: + case CustomizeIndex.FacialFeature5: + case CustomizeIndex.FacialFeature6: + case CustomizeIndex.FacialFeature7: + case CustomizeIndex.LegacyTattoo: + return CharaMakeParams.MenuType.IconCheckmark; } + var gameId = c.ToByteAndMask().ByteIdx; // 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); + .FirstOrDefault(m => m!.Value.Customize == gameId); return menu?.Type ?? CharaMakeParams.MenuType.ListSelector; }).ToArray(); set.Order = CustomizationSet.ComputeOrder(set); @@ -271,59 +287,96 @@ public partial class CustomizationOptions // Set customizations available if they have any options. private static void SetAvailability(CustomizationSet set, CharaMakeParams row) { - void Set(bool available, CustomizationId flag) + if (set.Race == Race.Hrothgar && set.Gender == Gender.Female) + return; + + void Set(bool available, CustomizeIndex 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); + Set(true, CustomizeIndex.Height); + Set(set.Faces.Count > 0, CustomizeIndex.Face); + Set(true, CustomizeIndex.Hairstyle); + Set(true, CustomizeIndex.Highlights); + Set(true, CustomizeIndex.SkinColor); + Set(true, CustomizeIndex.EyeColorRight); + Set(true, CustomizeIndex.HairColor); + Set(true, CustomizeIndex.HighlightsColor); + Set(true, CustomizeIndex.TattooColor); + Set(set.NumEyebrows > 0, CustomizeIndex.Eyebrows); + Set(true, CustomizeIndex.EyeColorLeft); + Set(set.NumEyeShapes > 0, CustomizeIndex.EyeShape); + Set(set.NumNoseShapes > 0, CustomizeIndex.Nose); + Set(set.NumJawShapes > 0, CustomizeIndex.Jaw); + Set(set.NumMouthShapes > 0, CustomizeIndex.Mouth); + Set(set.LipColorsDark.Count > 0, CustomizeIndex.LipColor); + Set(GetListSize(row, CustomizeIndex.MuscleMass) > 0, CustomizeIndex.MuscleMass); + Set(set.TailEarShapes.Count > 0, CustomizeIndex.TailShape); + Set(GetListSize(row, CustomizeIndex.BustSize) > 0, CustomizeIndex.BustSize); + Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaint); + Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintColor); + Set(true, CustomizeIndex.FacialFeature1); + Set(true, CustomizeIndex.FacialFeature2); + Set(true, CustomizeIndex.FacialFeature3); + Set(true, CustomizeIndex.FacialFeature4); + Set(true, CustomizeIndex.FacialFeature5); + Set(true, CustomizeIndex.FacialFeature6); + Set(true, CustomizeIndex.FacialFeature7); + Set(true, CustomizeIndex.LegacyTattoo); + Set(true, CustomizeIndex.SmallIris); + Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick); + Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed); } // 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); + var count = set.Faces.Count; + set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count); + static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) + => (new CustomizeData(i, CustomizeValue.Zero, data, 0), new CustomizeData(i, CustomizeValue.Max, data, 1)); + + set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905); + + var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray(); for (var i = 0; i < count; ++i) { - var legacyTattoo = new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << 7), 137905, - (ushort)((i + 1) * 8)); - featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx) - => new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << idx), val, - (ushort)(i * 8 + idx))) - .Append(legacyTattoo) - .ToArray()); + var data = row.FacialFeatureByFace[i].Icons; + tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]); + tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]); + tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]); + tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]); + tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]); + tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]); + tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]); } - set.FeaturesTattoos = featureDict.ToArray(); + set.FacialFeature1 = tmp[0]; + set.FacialFeature2 = tmp[1]; + set.FacialFeature3 = tmp[2]; + set.FacialFeature4 = tmp[3]; + set.FacialFeature5 = tmp[4]; + set.FacialFeature6 = tmp[5]; + set.FacialFeature7 = tmp[6]; } // 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 => + var nameArray = Enum.GetValues().Select(c => { // Find the first menu that corresponds to the Id. + var byteId = c.ToByteAndMask().ByteIdx; var menu = row.Menus .Cast() - .FirstOrDefault(m => m!.Value.Customization == c); + .FirstOrDefault(m => m!.Value.Customize == byteId); if (menu == null) { // If none exists and the id corresponds to highlights, set the Highlights name. - if (c == CustomizationId.HighlightsOnFlag) + if (c == CustomizeIndex.Highlights) return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; // Otherwise there is an error and we use the default name. @@ -331,7 +384,7 @@ public partial class CustomizationOptions } // Facial Features and Tattoos is created by combining two strings. - if (c == CustomizationId.FacialFeaturesTattoos) + if (c is >= CustomizeIndex.FacialFeature1 and <= CustomizeIndex.LegacyTattoo) return $"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}"; @@ -341,14 +394,14 @@ public partial class CustomizationOptions }).ToArray(); // Add names for both eye colors. - nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR]; - nameArray[(int)CustomizationId.EyeColorR] = _options.GetName(CustomName.OddEyes); + nameArray[(int)CustomizeIndex.EyeColorLeft] = nameArray[(int)CustomizeIndex.EyeColorRight]; + nameArray[(int)CustomizeIndex.EyeColorRight] = _options.GetName(CustomName.OddEyes); set.OptionName = nameArray; } // Obtain available skin and hair colors for the given subrace and gender. - private (CustomizationData[], CustomizationData[]) GetColors(SubRace race, Gender gender) + private (CustomizeData[], CustomizeData[]) GetColors(SubRace race, Gender gender) { if (race is > SubRace.Veena or SubRace.Unknown) throw new ArgumentOutOfRangeException(nameof(race), race, null); @@ -356,16 +409,16 @@ public partial class CustomizationOptions 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)); + return (CreateColorPicker(CustomizeIndex.SkinColor, idx << 8, 192), + CreateColorPicker(CustomizeIndex.HairColor, (idx + 1) << 8, 192)); } // Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. - private CustomizationData[] GetHairStyles(SubRace race, Gender gender) + private CustomizeData[] 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); + var hairList = new List(row.Unknown30); // Hairstyles can be found starting at Unknown66. for (var i = 0; i < row.Unknown30; ++i) { @@ -378,35 +431,36 @@ public partial class CustomizationOptions // Hair Row from CustomizeSheet might not be set in case of unlockable hair. var hairRow = _customizeSheet.GetRow(customizeIdx); hairList.Add(hairRow != null - ? new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)hairRow.FeatureID, hairRow.Icon, + ? new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId) - : new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)i, customizeIdx)); + : new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); } return hairList.ToArray(); } // Get Features. - private CustomizationData FromValueAndIndex(CustomizationId id, uint value, int index) + private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index) { var row = _customizeSheet.GetRow(value); return row == null - ? new CustomizationData(id, (CustomizationByteValue)(index + 1), value) - : new CustomizationData(id, (CustomizationByteValue)row.FeatureID, row.Icon, (ushort)row.RowId); + ? new CustomizeData(id, (CustomizeValue)(index + 1), value) + : new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId); } // Get List sizes. - private static int GetListSize(CharaMakeParams row, CustomizationId id) + private static int GetListSize(CharaMakeParams row, CustomizeIndex index) { - var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customization == id); + var gameId = index.ToByteAndMask().ByteIdx; + var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == gameId); return menu?.Size ?? 0; } // Get face paints from the hair sheet via reflection. - private CustomizationData[] GetFacePaints(SubRace race, Gender gender) + private CustomizeData[] GetFacePaints(SubRace race, Gender gender) { var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - var paintList = new List(row.Unknown37); + var paintList = new List(row.Unknown37); // Number of available face paints is at Unknown37. for (var i = 0; i < row.Unknown37; ++i) @@ -422,30 +476,33 @@ public partial class CustomizationOptions var paintRow = _customizeSheet.GetRow(customizeIdx); // Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints. paintList.Add(paintRow != null - ? new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)paintRow.FeatureID, paintRow.Icon, + ? new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId) - : new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)i, customizeIdx)); + : new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx)); } return paintList.ToArray(); } // Specific icons for tails or ears. - private CustomizationData[] 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 CustomizeData[] GetTailEarShapes(CharaMakeParams row) + => row.Menus.Cast() + .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values + .Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray() + ?? Array.Empty(); // Specific icons for faces. - private CustomizationData[] 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 CustomizeData[] GetFaces(CharaMakeParams row) + => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx) + ?.Values + .Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray() + ?? Array.Empty(); // Specific icons for Hrothgar patterns. - private CustomizationData[] 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 CustomizeData[] HrothgarFurPattern(CharaMakeParams row) + => row.Menus.Cast() + .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values + .Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray() + ?? Array.Empty(); } } diff --git a/Glamourer.GameData/Customization/CustomizationSet.cs b/Glamourer.GameData/Customization/CustomizationSet.cs index 69b0694..d68aa09 100644 --- a/Glamourer.GameData/Customization/CustomizationSet.cs +++ b/Glamourer.GameData/Customization/CustomizationSet.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Runtime.CompilerServices; +using System.Xml.XPath; using OtterGui; using Penumbra.GameData.Enums; @@ -13,56 +14,32 @@ public class CustomizationSet { internal CustomizationSet(SubRace clan, Gender gender) { - Gender = gender; - Clan = clan; - Race = clan.ToRace(); - _settingAvailable = Race == Race.Hrothgar && gender == Gender.Female - ? 0u - : DefaultAvailable; + Gender = gender; + Clan = clan; + Race = clan.ToRace(); + _settingAvailable = 0; } public Gender Gender { get; } public SubRace Clan { get; } public Race Race { get; } - private uint _settingAvailable; + private CustomizeFlag _settingAvailable; - internal void SetAvailable(CustomizationId id) - => _settingAvailable |= 1u << (int)id; + internal void SetAvailable(CustomizeIndex index) + => _settingAvailable |= index.ToFlag(); - public bool IsAvailable(CustomizationId id) - => (_settingAvailable & (1u << (int)id)) != 0; - - 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); - - public string ToHumanReadable(Customize 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 bool IsAvailable(CustomizeIndex index) + => _settingAvailable.HasFlag(index.ToFlag()); // Meta public IReadOnlyList OptionName { get; internal set; } = null!; - public string Option(CustomizationId id) - => OptionName[(int)id]; + public string Option(CustomizeIndex index) + => OptionName[(int)index]; - public IReadOnlyList Types { get; internal set; } = null!; - public IReadOnlyDictionary Order { get; internal set; } = null!; + public IReadOnlyList Types { get; internal set; } = null!; + public IReadOnlyDictionary Order { get; internal set; } = null!; // Always list selector. @@ -74,164 +51,224 @@ public class CustomizationSet // Always Icon Selector - public IReadOnlyList Faces { get; internal init; } = null!; - public IReadOnlyList HairStyles { get; internal init; } = null!; - public IReadOnlyList> HairByFace { get; internal set; } = null!; - public IReadOnlyList TailEarShapes { get; internal init; } = null!; - public IReadOnlyList> FeaturesTattoos { get; internal set; } = null!; - public IReadOnlyList FacePaints { get; internal init; } = null!; - - public CustomizationData FacialFeature(CustomizationByteValue face, int idx) - { - face = HrothgarFaceHack(face); - var faceIdx = Faces.IndexOf(p => p.Value == face); - return FeaturesTattoos[faceIdx != -1 ? faceIdx : 0][idx]; - } + public IReadOnlyList Faces { get; internal init; } = null!; + public IReadOnlyList HairStyles { get; internal init; } = null!; + public IReadOnlyList> HairByFace { get; internal set; } = null!; + public IReadOnlyList TailEarShapes { get; internal init; } = null!; + public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature1 { get; internal set; } = null!; + public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature2 { get; internal set; } = null!; + public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature3 { get; internal set; } = null!; + public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature4 { get; internal set; } = null!; + public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature5 { get; internal set; } = null!; + public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature6 { get; internal set; } = null!; + public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature7 { get; internal set; } = null!; + public (CustomizeData, CustomizeData) LegacyTattoo { get; internal set; } + public IReadOnlyList FacePaints { get; internal init; } = null!; // Always Color Selector - 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 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 int DataByValue(CustomizationId id, CustomizationByteValue value, out CustomizationData? custom) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int DataByValue(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom, CustomizeValue face) { - var type = id.ToType(); - custom = null; - if (type is CharaMakeParams.MenuType.Percentage or CharaMakeParams.MenuType.ListSelector) + var type = Types[(int)index]; + + int GetInteger(out CustomizeData? custom) { - if (value < Count(id)) + if (value < Count(index)) { - custom = new CustomizationData(id, value, 0, value.Value); + custom = new CustomizeData(index, value, 0, value.Value); return value.Value; } + custom = null; return -1; } - int Get(IEnumerable list, CustomizationByteValue v, ref CustomizationData? output) + static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom) { - var (val, idx) = list.Cast().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v); + if (value == CustomizeValue.Zero) + { + custom = new CustomizeData(index, CustomizeValue.Zero, 0, 0); + return 0; + } + + var (_, mask) = index.ToByteAndMask(); + if (value.Value == mask) + { + custom = new CustomizeData(index, new CustomizeValue(mask), 0, 1); + return 1; + } + + custom = null; + return -1; + } + + static int Invalid(out CustomizeData? custom) + { + custom = null; + return -1; + } + + int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) + { + var (val, idx) = list.Cast().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v); if (val == null) + { + output = null; return -1; + } output = val; return idx; } - return id switch + return type switch { - CustomizationId.SkinColor => Get(SkinColors, value, ref custom), - CustomizationId.EyeColorL => Get(EyeColors, value, ref custom), - CustomizationId.EyeColorR => Get(EyeColors, value, ref custom), - CustomizationId.HairColor => Get(HairColors, value, ref custom), - CustomizationId.HighlightColor => Get(HighlightColors, value, ref custom), - CustomizationId.TattooColor => Get(TattooColors, value, ref custom), - CustomizationId.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, ref custom), - CustomizationId.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, ref custom), - - CustomizationId.Face => Get(Faces, HrothgarFaceHack(value), ref custom), - CustomizationId.Hairstyle => Get(HairStyles, value, ref custom), - CustomizationId.TailEarShape => Get(TailEarShapes, value, ref custom), - CustomizationId.FacePaint => Get(FacePaints, value, ref custom), - CustomizationId.FacialFeaturesTattoos => Get(FeaturesTattoos[0], value, ref custom), - _ => throw new ArgumentOutOfRangeException(nameof(id), id, null), + CharaMakeParams.MenuType.ListSelector => GetInteger(out custom), + CharaMakeParams.MenuType.IconSelector => index switch + { + CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom), + CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles, value, out custom), + CustomizeIndex.TailShape => Get(TailEarShapes, value, out custom), + CustomizeIndex.FacePaint => Get(FacePaints, value, out custom), + CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom), + _ => Invalid(out custom), + }, + CharaMakeParams.MenuType.ColorPicker => index switch + { + CustomizeIndex.SkinColor => Get(SkinColors, value, out custom), + CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom), + CustomizeIndex.EyeColorRight => Get(EyeColors, value, out custom), + CustomizeIndex.HairColor => Get(HairColors, value, out custom), + CustomizeIndex.HighlightsColor => Get(HighlightColors, value, out custom), + CustomizeIndex.TattooColor => Get(TattooColors, value, out custom), + CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom), + CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom), + _ => Invalid(out custom), + }, + CharaMakeParams.MenuType.DoubleColorPicker => index switch + { + CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom), + CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom), + _ => Invalid(out custom), + }, + CharaMakeParams.MenuType.IconCheckmark => GetBool(index, value, out custom), + CharaMakeParams.MenuType.Percentage => GetInteger(out custom), + CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom), + _ => Invalid(out custom), }; } - public CustomizationData Data(CustomizationId id, int idx) - => Data(id, idx, CustomizationByteValue.Zero); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public CustomizeData Data(CustomizeIndex index, int idx) + => Data(index, idx, CustomizeValue.Zero); - public CustomizationData Data(CustomizationId id, int idx, CustomizationByteValue face) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public CustomizeData Data(CustomizeIndex index, int idx, CustomizeValue face) { - if (idx >= Count(id, face = HrothgarFaceHack(face))) + if (idx >= Count(index, face = HrothgarFaceHack(face))) throw new IndexOutOfRangeException(); - switch (id.ToType()) + switch (Types[(int)index]) { - case CharaMakeParams.MenuType.Percentage: return new CustomizationData(id, (CustomizationByteValue)idx, 0, (ushort)idx); - case CharaMakeParams.MenuType.ListSelector: return new CustomizationData(id, (CustomizationByteValue)idx, 0, (ushort)idx); + case CharaMakeParams.MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx); + case CharaMakeParams.MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx); + case CharaMakeParams.MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx); } - return id switch + return index switch { - CustomizationId.Face => Faces[idx], - CustomizationId.Hairstyle => face < HairByFace.Count ? HairByFace[face.Value][idx] : HairStyles[idx], - CustomizationId.TailEarShape => TailEarShapes[idx], - CustomizationId.FacePaint => FacePaints[idx], - CustomizationId.FacialFeaturesTattoos => FeaturesTattoos[0][idx], - - CustomizationId.SkinColor => SkinColors[idx], - CustomizationId.EyeColorL => EyeColors[idx], - CustomizationId.EyeColorR => EyeColors[idx], - CustomizationId.HairColor => HairColors[idx], - CustomizationId.HighlightColor => HighlightColors[idx], - CustomizationId.TattooColor => TattooColors[idx], - CustomizationId.LipColor => idx < 96 ? LipColorsDark[idx] : LipColorsLight[idx - 96], - CustomizationId.FacePaintColor => idx < 96 ? FacePaintColorsDark[idx] : FacePaintColorsLight[idx - 96], - _ => new CustomizationData(0, CustomizationByteValue.Zero), + CustomizeIndex.Face => Faces[idx], + CustomizeIndex.Hairstyle => face < HairByFace.Count ? HairByFace[face.Value][idx] : HairStyles[idx], + CustomizeIndex.TailShape => TailEarShapes[idx], + CustomizeIndex.FacePaint => FacePaints[idx], + CustomizeIndex.FacialFeature1 => idx == 0 ? FacialFeature1[face.Value].Item1 : FacialFeature1[face.Value].Item2, + CustomizeIndex.FacialFeature2 => idx == 0 ? FacialFeature2[face.Value].Item1 : FacialFeature2[face.Value].Item2, + CustomizeIndex.FacialFeature3 => idx == 0 ? FacialFeature3[face.Value].Item1 : FacialFeature3[face.Value].Item2, + CustomizeIndex.FacialFeature4 => idx == 0 ? FacialFeature4[face.Value].Item1 : FacialFeature4[face.Value].Item2, + CustomizeIndex.FacialFeature5 => idx == 0 ? FacialFeature5[face.Value].Item1 : FacialFeature5[face.Value].Item2, + CustomizeIndex.FacialFeature6 => idx == 0 ? FacialFeature6[face.Value].Item1 : FacialFeature6[face.Value].Item2, + CustomizeIndex.FacialFeature7 => idx == 0 ? FacialFeature7[face.Value].Item1 : FacialFeature7[face.Value].Item2, + CustomizeIndex.LegacyTattoo => idx == 0 ? LegacyTattoo.Item1 : LegacyTattoo.Item2, + CustomizeIndex.SkinColor => SkinColors[idx], + CustomizeIndex.EyeColorLeft => EyeColors[idx], + CustomizeIndex.EyeColorRight => EyeColors[idx], + CustomizeIndex.HairColor => HairColors[idx], + CustomizeIndex.HighlightsColor => HighlightColors[idx], + CustomizeIndex.TattooColor => TattooColors[idx], + CustomizeIndex.LipColor => idx < 96 ? LipColorsDark[idx] : LipColorsLight[idx - 96], + CustomizeIndex.FacePaintColor => idx < 96 ? FacePaintColorsDark[idx] : FacePaintColorsLight[idx - 96], + _ => new CustomizeData(0, CustomizeValue.Zero), }; } - public CharaMakeParams.MenuType Type(CustomizationId id) - => Types[(int)id]; + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public CharaMakeParams.MenuType Type(CustomizeIndex index) + => Types[(int)index]; - internal static IReadOnlyDictionary ComputeOrder(CustomizationSet set) + 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; + var ret = Enum.GetValues().SkipLast(1).ToArray(); + ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft; + ret[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorRight; + ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor; var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray()); foreach (var type in Enum.GetValues()) - dict.TryAdd(type, Array.Empty()); + dict.TryAdd(type, Array.Empty()); return dict; } - public int Count(CustomizationId id) - => Count(id, CustomizationByteValue.Zero); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int Count(CustomizeIndex index) + => Count(index, CustomizeValue.Zero); - public int Count(CustomizationId id, CustomizationByteValue face) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int Count(CustomizeIndex index, CustomizeValue face) { - if (!IsAvailable(id)) + if (!IsAvailable(index)) return 0; - if (id.ToType() == CharaMakeParams.MenuType.Percentage) - return 101; - - return id switch + return Type(index) switch { - CustomizationId.Face => Faces.Count, - CustomizationId.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0, - CustomizationId.HighlightsOnFlag => 2, - CustomizationId.SkinColor => SkinColors.Count, - CustomizationId.EyeColorR => EyeColors.Count, - CustomizationId.HairColor => HairColors.Count, - CustomizationId.HighlightColor => HighlightColors.Count, - CustomizationId.FacialFeaturesTattoos => 8, - CustomizationId.TattooColor => TattooColors.Count, - CustomizationId.Eyebrows => NumEyebrows, - CustomizationId.EyeColorL => EyeColors.Count, - CustomizationId.EyeShape => NumEyeShapes, - CustomizationId.Nose => NumNoseShapes, - CustomizationId.Jaw => NumJawShapes, - CustomizationId.Mouth => NumMouthShapes, - CustomizationId.LipColor => LipColorsLight.Count + LipColorsDark.Count, - CustomizationId.TailEarShape => TailEarShapes.Count, - CustomizationId.FacePaint => FacePaints.Count, - CustomizationId.FacePaintColor => FacePaintColorsLight.Count + FacePaintColorsDark.Count, - _ => throw new ArgumentOutOfRangeException(nameof(id), id, null), + CharaMakeParams.MenuType.Percentage => 101, + CharaMakeParams.MenuType.IconCheckmark => 2, + CharaMakeParams.MenuType.Checkmark => 2, + _ => index switch + { + CustomizeIndex.Face => Faces.Count, + CustomizeIndex.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0, + CustomizeIndex.SkinColor => SkinColors.Count, + CustomizeIndex.EyeColorRight => EyeColors.Count, + CustomizeIndex.HairColor => HairColors.Count, + CustomizeIndex.HighlightsColor => HighlightColors.Count, + CustomizeIndex.TattooColor => TattooColors.Count, + CustomizeIndex.Eyebrows => NumEyebrows, + CustomizeIndex.EyeColorLeft => EyeColors.Count, + CustomizeIndex.EyeShape => NumEyeShapes, + CustomizeIndex.Nose => NumNoseShapes, + CustomizeIndex.Jaw => NumJawShapes, + CustomizeIndex.Mouth => NumMouthShapes, + CustomizeIndex.LipColor => LipColorsLight.Count + LipColorsDark.Count, + CustomizeIndex.TailShape => TailEarShapes.Count, + CustomizeIndex.FacePaint => FacePaints.Count, + CustomizeIndex.FacePaintColor => FacePaintColorsLight.Count + FacePaintColorsDark.Count, + _ => throw new ArgumentOutOfRangeException(nameof(index), index, null), + }, }; } - private CustomizationByteValue HrothgarFaceHack(CustomizationByteValue value) + private CustomizeValue HrothgarFaceHack(CustomizeValue value) => Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value; } diff --git a/Glamourer.GameData/Customization/Customize.cs b/Glamourer.GameData/Customization/Customize.cs index 3a708cd..00ba8db 100644 --- a/Glamourer.GameData/Customization/Customize.cs +++ b/Glamourer.GameData/Customization/Customize.cs @@ -1,352 +1,87 @@ using System; using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; namespace Glamourer.Customization; -public record struct CustomizationByteValue(byte Value) -{ - public static readonly CustomizationByteValue Zero = new(0); - - public static explicit operator CustomizationByteValue(byte value) - => new(value); - - public static CustomizationByteValue operator ++(CustomizationByteValue v) - => new(++v.Value); - - public static CustomizationByteValue operator --(CustomizationByteValue v) - => new(--v.Value); - - public static bool operator <(CustomizationByteValue v, int count) - => v.Value < count; - - public static bool operator >(CustomizationByteValue v, int count) - => v.Value > count; - - public static CustomizationByteValue operator +(CustomizationByteValue v, int rhs) - => new((byte)(v.Value + rhs)); - - public static CustomizationByteValue operator -(CustomizationByteValue v, int rhs) - => new((byte)(v.Value - rhs)); - - public override string ToString() - => Value.ToString(); -} - public unsafe struct Customize { - public readonly CustomizeData* Data; + public readonly Penumbra.GameData.Structs.CustomizeData* Data; - public Customize(CustomizeData* data) + public Customize(Penumbra.GameData.Structs.CustomizeData* data) => Data = data; public Race Race { - get => (Race)Data->Data[0]; - set => Data->Data[0] = (byte)value; + get => (Race)Data->Get(CustomizeIndex.Race).Value; + set => Data->Set(CustomizeIndex.Race, (CustomizeValue)(byte)value); } - // Skip Unknown Gender public Gender Gender { - get => (Gender)(Data->Data[1] + 1); - set => Data->Data[1] = (byte)(value - 1); + get => (Gender)Data->Get(CustomizeIndex.Gender).Value + 1; + set => Data->Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1); } - public CustomizationByteValue BodyType + public CustomizeValue BodyType { - get => (CustomizationByteValue)Data->Data[2]; - set => Data->Data[2] = value.Value; - } - - public CustomizationByteValue Height - { - get => (CustomizationByteValue)Data->Data[3]; - set => Data->Data[3] = value.Value; + get => Data->Get(CustomizeIndex.BodyType); + set => Data->Set(CustomizeIndex.BodyType, value); } public SubRace Clan { - get => (SubRace)Data->Data[4]; - set => Data->Data[4] = (byte)value; + get => (SubRace)Data->Get(CustomizeIndex.Clan).Value; + set => Data->Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value); } - public CustomizationByteValue Face + public CustomizeValue Face { - get => (CustomizationByteValue)Data->Data[5]; - set => Data->Data[5] = value.Value; + get => Data->Get(CustomizeIndex.Face); + set => Data->Set(CustomizeIndex.Face, value); } - public CustomizationByteValue Hairstyle - { - get => (CustomizationByteValue)Data->Data[6]; - set => Data->Data[6] = value.Value; - } - public bool HighlightsOn - { - get => Data->Data[7] >> 7 == 1; - set => Data->Data[7] = (byte)(value ? Data->Data[7] | 0x80 : Data->Data[7] & 0x7F); - } + public static readonly Penumbra.GameData.Structs.CustomizeData Default = GenerateDefault(); + public static readonly Penumbra.GameData.Structs.CustomizeData Empty = new(); - public CustomizationByteValue SkinColor - { - get => (CustomizationByteValue)Data->Data[8]; - set => Data->Data[8] = value.Value; - } + public CustomizeValue Get(CustomizeIndex index) + => Data->Get(index); - public CustomizationByteValue EyeColorRight - { - get => (CustomizationByteValue)Data->Data[9]; - set => Data->Data[9] = value.Value; - } - - public CustomizationByteValue HairColor - { - get => (CustomizationByteValue)Data->Data[10]; - set => Data->Data[10] = value.Value; - } - - public CustomizationByteValue HighlightsColor - { - get => (CustomizationByteValue)Data->Data[11]; - set => Data->Data[11] = value.Value; - } - - public readonly ref struct FacialFeatureStruct - { - private readonly byte* _bitfield; - - public FacialFeatureStruct(byte* data) - => _bitfield = data; - - public bool this[int idx] - { - get => (*_bitfield & (1 << idx)) != 0; - set => Set(idx, value); - } - - public void Clear() - => *_bitfield = 0; - - public void All() - => *_bitfield = 0xFF; - - public void Set(int idx, bool value) - => *_bitfield = (byte)(value ? *_bitfield | (1 << idx) : *_bitfield & ~(1 << idx)); - } - - public FacialFeatureStruct FacialFeatures - => new(Data->Data + 12); - - public CustomizationByteValue TattooColor - { - get => (CustomizationByteValue)Data->Data[13]; - set => Data->Data[13] = value.Value; - } - - public CustomizationByteValue Eyebrows - { - get => (CustomizationByteValue)Data->Data[14]; - set => Data->Data[14] = value.Value; - } - - public CustomizationByteValue EyeColorLeft - { - get => (CustomizationByteValue)Data->Data[15]; - set => Data->Data[15] = value.Value; - } - - public CustomizationByteValue EyeShape - { - get => (CustomizationByteValue)(Data->Data[16] & 0x7F); - set => Data->Data[16] = (byte)((value.Value & 0x7F) | (Data->Data[16] & 0x80)); - } - - public bool SmallIris - { - get => Data->Data[16] >> 7 == 1; - set => Data->Data[16] = (byte)(value ? Data->Data[16] | 0x80 : Data->Data[16] & 0x7F); - } - - public CustomizationByteValue Nose - { - get => (CustomizationByteValue)Data->Data[17]; - set => Data->Data[17] = value.Value; - } - - public CustomizationByteValue Jaw - { - get => (CustomizationByteValue)Data->Data[18]; - set => Data->Data[18] = value.Value; - } - - public CustomizationByteValue Mouth - { - get => (CustomizationByteValue)(Data->Data[19] & 0x7F); - set => Data->Data[19] = (byte)((value.Value & 0x7F) | (Data->Data[19] & 0x80)); - } - - public bool Lipstick - { - get => Data->Data[19] >> 7 == 1; - set => Data->Data[19] = (byte)(value ? Data->Data[19] | 0x80 : Data->Data[19] & 0x7F); - } - - public CustomizationByteValue LipColor - { - get => (CustomizationByteValue)Data->Data[20]; - set => Data->Data[20] = value.Value; - } - - public CustomizationByteValue MuscleMass - { - get => (CustomizationByteValue)Data->Data[21]; - set => Data->Data[21] = value.Value; - } - - public CustomizationByteValue TailShape - { - get => (CustomizationByteValue)Data->Data[22]; - set => Data->Data[22] = value.Value; - } - - public CustomizationByteValue BustSize - { - get => (CustomizationByteValue)Data->Data[23]; - set => Data->Data[23] = value.Value; - } - - public CustomizationByteValue FacePaint - { - get => (CustomizationByteValue)(Data->Data[24] & 0x7F); - set => Data->Data[24] = (byte)((value.Value & 0x7F) | (Data->Data[24] & 0x80)); - } - - public bool FacePaintReversed - { - get => Data->Data[24] >> 7 == 1; - set => Data->Data[24] = (byte)(value ? Data->Data[24] | 0x80 : Data->Data[24] & 0x7F); - } - - public CustomizationByteValue FacePaintColor - { - get => (CustomizationByteValue)Data->Data[25]; - set => Data->Data[25] = value.Value; - } - - public static readonly CustomizeData Default = GenerateDefault(); - public static readonly CustomizeData Empty = new(); - - public CustomizationByteValue Get(CustomizationId id) - => id switch - { - CustomizationId.Race => (CustomizationByteValue)(byte)Race, - CustomizationId.Gender => (CustomizationByteValue)(byte)Gender, - CustomizationId.BodyType => BodyType, - CustomizationId.Height => Height, - CustomizationId.Clan => (CustomizationByteValue)(byte)Clan, - CustomizationId.Face => Face, - CustomizationId.Hairstyle => Hairstyle, - CustomizationId.HighlightsOnFlag => (CustomizationByteValue)Data->Data[7], - CustomizationId.SkinColor => SkinColor, - CustomizationId.EyeColorR => EyeColorRight, - CustomizationId.HairColor => HairColor, - CustomizationId.HighlightColor => HighlightsColor, - CustomizationId.FacialFeaturesTattoos => (CustomizationByteValue)Data->Data[12], - CustomizationId.TattooColor => TattooColor, - CustomizationId.Eyebrows => Eyebrows, - CustomizationId.EyeColorL => EyeColorLeft, - CustomizationId.EyeShape => EyeShape, - CustomizationId.Nose => Nose, - CustomizationId.Jaw => Jaw, - CustomizationId.Mouth => Mouth, - CustomizationId.LipColor => LipColor, - CustomizationId.MuscleToneOrTailEarLength => MuscleMass, - CustomizationId.TailEarShape => TailShape, - CustomizationId.BustSize => BustSize, - CustomizationId.FacePaint => FacePaint, - CustomizationId.FacePaintColor => FacePaintColor, - _ => throw new ArgumentOutOfRangeException(nameof(id), id, null), - }; - - public void Set(CustomizationId id, CustomizationByteValue value) - { - switch (id) - { - // @formatter:off - case CustomizationId.Race: Race = (Race)value.Value; break; - case CustomizationId.Gender: Gender = (Gender)value.Value; break; - case CustomizationId.BodyType: BodyType = value; break; - case CustomizationId.Height: Height = value; break; - case CustomizationId.Clan: Clan = (SubRace)value.Value; break; - case CustomizationId.Face: Face = value; break; - case CustomizationId.Hairstyle: Hairstyle = value; break; - case CustomizationId.HighlightsOnFlag: HighlightsOn = (value.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: Data->Data[12] = value.Value; break; - case CustomizationId.TattooColor: TattooColor = value; break; - case CustomizationId.Eyebrows: Eyebrows = 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 void Set(CustomizeIndex flag, CustomizeValue index) + => Data->Set(flag, index); public bool Equals(Customize other) - => CustomizeData.Equals(Data, other.Data); + => Penumbra.GameData.Structs.CustomizeData.Equals(Data, other.Data); - public CustomizationByteValue this[CustomizationId id] + public CustomizeValue this[CustomizeIndex index] { - get => Get(id); - set => Set(id, value); + get => Get(index); + set => Set(index, value); } - private static CustomizeData GenerateDefault() + private static Penumbra.GameData.Structs.CustomizeData GenerateDefault() { - var ret = new CustomizeData(); - var customize = new Customize(&ret) - { - Race = Race.Hyur, - Gender = Gender.Male, - BodyType = (CustomizationByteValue)1, - Height = (CustomizationByteValue)50, - Clan = SubRace.Midlander, - Face = (CustomizationByteValue)1, - Hairstyle = (CustomizationByteValue)1, - HighlightsOn = false, - SkinColor = (CustomizationByteValue)1, - EyeColorRight = (CustomizationByteValue)1, - HighlightsColor = (CustomizationByteValue)1, - TattooColor = (CustomizationByteValue)1, - Eyebrows = (CustomizationByteValue)1, - EyeColorLeft = (CustomizationByteValue)1, - EyeShape = (CustomizationByteValue)1, - Nose = (CustomizationByteValue)1, - Jaw = (CustomizationByteValue)1, - Mouth = (CustomizationByteValue)1, - LipColor = (CustomizationByteValue)1, - MuscleMass = (CustomizationByteValue)50, - TailShape = (CustomizationByteValue)1, - BustSize = (CustomizationByteValue)50, - FacePaint = (CustomizationByteValue)1, - FacePaintColor = (CustomizationByteValue)1, - }; - customize.FacialFeatures.Clear(); - + var ret = new Penumbra.GameData.Structs.CustomizeData(); + ret.Set(CustomizeIndex.BodyType, (CustomizeValue)1); + ret.Set(CustomizeIndex.Height, (CustomizeValue)50); + ret.Set(CustomizeIndex.Face, (CustomizeValue)1); + ret.Set(CustomizeIndex.Hairstyle, (CustomizeValue)1); + ret.Set(CustomizeIndex.SkinColor, (CustomizeValue)1); + ret.Set(CustomizeIndex.EyeColorRight, (CustomizeValue)1); + ret.Set(CustomizeIndex.HighlightsColor, (CustomizeValue)1); + ret.Set(CustomizeIndex.TattooColor, (CustomizeValue)1); + ret.Set(CustomizeIndex.Eyebrows, (CustomizeValue)1); + ret.Set(CustomizeIndex.EyeColorLeft, (CustomizeValue)1); + ret.Set(CustomizeIndex.EyeShape, (CustomizeValue)1); + ret.Set(CustomizeIndex.Nose, (CustomizeValue)1); + ret.Set(CustomizeIndex.Jaw, (CustomizeValue)1); + ret.Set(CustomizeIndex.Mouth, (CustomizeValue)1); + ret.Set(CustomizeIndex.LipColor, (CustomizeValue)1); + ret.Set(CustomizeIndex.MuscleMass, (CustomizeValue)50); + ret.Set(CustomizeIndex.TailShape, (CustomizeValue)1); + ret.Set(CustomizeIndex.BustSize, (CustomizeValue)50); + ret.Set(CustomizeIndex.FacePaint, (CustomizeValue)1); + ret.Set(CustomizeIndex.FacePaintColor, (CustomizeValue)1); return ret; } @@ -355,4 +90,10 @@ public unsafe struct Customize public void Write(IntPtr target) => Data->Write((void*)target); + + public bool LoadBase64(string data) + => Data->LoadBase64(data); + + public string WriteBase64() + => Data->WriteBase64(); } diff --git a/Glamourer.GameData/Customization/CustomizationData.cs b/Glamourer.GameData/Customization/CustomizeData.cs similarity index 66% rename from Glamourer.GameData/Customization/CustomizationData.cs rename to Glamourer.GameData/Customization/CustomizeData.cs index 3a943bc..6707d95 100644 --- a/Glamourer.GameData/Customization/CustomizationData.cs +++ b/Glamourer.GameData/Customization/CustomizeData.cs @@ -5,13 +5,13 @@ 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 CustomizationData +public readonly struct CustomizeData { [FieldOffset(0)] - public readonly CustomizationId Id; + public readonly CustomizeIndex Index; [FieldOffset(1)] - public readonly CustomizationByteValue Value; + public readonly CustomizeValue Value; [FieldOffset(2)] public readonly ushort CustomizeId; @@ -22,9 +22,9 @@ public readonly struct CustomizationData [FieldOffset(4)] public readonly uint Color; - public CustomizationData(CustomizationId id, CustomizationByteValue value, uint data = 0, ushort customizeId = 0) + public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0) { - Id = id; + Index = index; Value = value; IconId = data; Color = data; diff --git a/Glamourer.GameData/Customization/CustomizeFlag.cs b/Glamourer.GameData/Customization/CustomizeFlag.cs new file mode 100644 index 0000000..867d095 --- /dev/null +++ b/Glamourer.GameData/Customization/CustomizeFlag.cs @@ -0,0 +1,92 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Glamourer.Customization; + +[Flags] +public enum CustomizeFlag : ulong +{ + Invalid = 0, + Race = 1ul << CustomizeIndex.Race, + Gender = 1ul << CustomizeIndex.Gender, + BodyType = 1ul << CustomizeIndex.BodyType, + Height = 1ul << CustomizeIndex.Height, + Clan = 1ul << CustomizeIndex.Clan, + Face = 1ul << CustomizeIndex.Face, + Hairstyle = 1ul << CustomizeIndex.Hairstyle, + Highlights = 1ul << CustomizeIndex.Highlights, + SkinColor = 1ul << CustomizeIndex.SkinColor, + EyeColorRight = 1ul << CustomizeIndex.EyeColorRight, + HairColor = 1ul << CustomizeIndex.HairColor, + HighlightsColor = 1ul << CustomizeIndex.HighlightsColor, + FacialFeature1 = 1ul << CustomizeIndex.FacialFeature1, + FacialFeature2 = 1ul << CustomizeIndex.FacialFeature2, + FacialFeature3 = 1ul << CustomizeIndex.FacialFeature3, + FacialFeature4 = 1ul << CustomizeIndex.FacialFeature4, + FacialFeature5 = 1ul << CustomizeIndex.FacialFeature5, + FacialFeature6 = 1ul << CustomizeIndex.FacialFeature6, + FacialFeature7 = 1ul << CustomizeIndex.FacialFeature7, + LegacyTattoo = 1ul << CustomizeIndex.LegacyTattoo, + TattooColor = 1ul << CustomizeIndex.TattooColor, + Eyebrows = 1ul << CustomizeIndex.Eyebrows, + EyeColorLeft = 1ul << CustomizeIndex.EyeColorLeft, + EyeShape = 1ul << CustomizeIndex.EyeShape, + SmallIris = 1ul << CustomizeIndex.SmallIris, + Nose = 1ul << CustomizeIndex.Nose, + Jaw = 1ul << CustomizeIndex.Jaw, + Mouth = 1ul << CustomizeIndex.Mouth, + Lipstick = 1ul << CustomizeIndex.Lipstick, + LipColor = 1ul << CustomizeIndex.LipColor, + MuscleMass = 1ul << CustomizeIndex.MuscleMass, + TailShape = 1ul << CustomizeIndex.TailShape, + BustSize = 1ul << CustomizeIndex.BustSize, + FacePaint = 1ul << CustomizeIndex.FacePaint, + FacePaintReversed = 1ul << CustomizeIndex.FacePaintReversed, + FacePaintColor = 1ul << CustomizeIndex.FacePaintColor, +} + +public static class CustomizeFlagExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static CustomizeIndex ToIndex(this CustomizeFlag flag) + => flag switch + { + CustomizeFlag.Race => CustomizeIndex.Race, + CustomizeFlag.Gender => CustomizeIndex.Gender, + CustomizeFlag.BodyType => CustomizeIndex.BodyType, + CustomizeFlag.Height => CustomizeIndex.Height, + CustomizeFlag.Clan => CustomizeIndex.Clan, + CustomizeFlag.Face => CustomizeIndex.Face, + CustomizeFlag.Hairstyle => CustomizeIndex.Hairstyle, + CustomizeFlag.Highlights => CustomizeIndex.Highlights, + CustomizeFlag.SkinColor => CustomizeIndex.SkinColor, + CustomizeFlag.EyeColorRight => CustomizeIndex.EyeColorRight, + CustomizeFlag.HairColor => CustomizeIndex.HairColor, + CustomizeFlag.HighlightsColor => CustomizeIndex.HighlightsColor, + CustomizeFlag.FacialFeature1 => CustomizeIndex.FacialFeature1, + CustomizeFlag.FacialFeature2 => CustomizeIndex.FacialFeature2, + CustomizeFlag.FacialFeature3 => CustomizeIndex.FacialFeature3, + CustomizeFlag.FacialFeature4 => CustomizeIndex.FacialFeature4, + CustomizeFlag.FacialFeature5 => CustomizeIndex.FacialFeature5, + CustomizeFlag.FacialFeature6 => CustomizeIndex.FacialFeature6, + CustomizeFlag.FacialFeature7 => CustomizeIndex.FacialFeature7, + CustomizeFlag.LegacyTattoo => CustomizeIndex.LegacyTattoo, + CustomizeFlag.TattooColor => CustomizeIndex.TattooColor, + CustomizeFlag.Eyebrows => CustomizeIndex.Eyebrows, + CustomizeFlag.EyeColorLeft => CustomizeIndex.EyeColorLeft, + CustomizeFlag.EyeShape => CustomizeIndex.EyeShape, + CustomizeFlag.SmallIris => CustomizeIndex.SmallIris, + CustomizeFlag.Nose => CustomizeIndex.Nose, + CustomizeFlag.Jaw => CustomizeIndex.Jaw, + CustomizeFlag.Mouth => CustomizeIndex.Mouth, + CustomizeFlag.Lipstick => CustomizeIndex.Lipstick, + CustomizeFlag.LipColor => CustomizeIndex.LipColor, + CustomizeFlag.MuscleMass => CustomizeIndex.MuscleMass, + CustomizeFlag.TailShape => CustomizeIndex.TailShape, + CustomizeFlag.BustSize => CustomizeIndex.BustSize, + CustomizeFlag.FacePaint => CustomizeIndex.FacePaint, + CustomizeFlag.FacePaintReversed => CustomizeIndex.FacePaintReversed, + CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor, + _ => (CustomizeIndex) byte.MaxValue, + }; +} diff --git a/Glamourer.GameData/Customization/CustomizeIndex.cs b/Glamourer.GameData/Customization/CustomizeIndex.cs new file mode 100644 index 0000000..6d30734 --- /dev/null +++ b/Glamourer.GameData/Customization/CustomizeIndex.cs @@ -0,0 +1,173 @@ +using System.Runtime.CompilerServices; + +namespace Glamourer.Customization; + +public enum CustomizeIndex : byte +{ + Race, + Gender, + BodyType, + Height, + Clan, + Face, + Hairstyle, + Highlights, + SkinColor, + EyeColorRight, + HairColor, + HighlightsColor, + FacialFeature1, + FacialFeature2, + FacialFeature3, + FacialFeature4, + FacialFeature5, + FacialFeature6, + FacialFeature7, + LegacyTattoo, + TattooColor, + Eyebrows, + EyeColorLeft, + EyeShape, + SmallIris, + Nose, + Jaw, + Mouth, + Lipstick, + LipColor, + MuscleMass, + TailShape, + BustSize, + FacePaint, + FacePaintReversed, + FacePaintColor, +} + +public static class CustomizationExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index) + => index switch + { + CustomizeIndex.Race => (0, 0xFF), + CustomizeIndex.Gender => (1, 0xFF), + CustomizeIndex.BodyType => (2, 0xFF), + CustomizeIndex.Height => (3, 0xFF), + CustomizeIndex.Clan => (4, 0xFF), + CustomizeIndex.Face => (5, 0xFF), + CustomizeIndex.Hairstyle => (6, 0xFF), + CustomizeIndex.Highlights => (7, 0xFF), + CustomizeIndex.SkinColor => (8, 0xFF), + CustomizeIndex.EyeColorRight => (9, 0xFF), + CustomizeIndex.HairColor => (10, 0xFF), + CustomizeIndex.HighlightsColor => (11, 0xFF), + CustomizeIndex.FacialFeature1 => (12, 0x01), + CustomizeIndex.FacialFeature2 => (12, 0x02), + CustomizeIndex.FacialFeature3 => (12, 0x04), + CustomizeIndex.FacialFeature4 => (12, 0x08), + CustomizeIndex.FacialFeature5 => (12, 0x10), + CustomizeIndex.FacialFeature6 => (12, 0x20), + CustomizeIndex.FacialFeature7 => (12, 0x40), + CustomizeIndex.LegacyTattoo => (12, 0x80), + CustomizeIndex.TattooColor => (13, 0xFF), + CustomizeIndex.Eyebrows => (14, 0xFF), + CustomizeIndex.EyeColorLeft => (15, 0xFF), + CustomizeIndex.EyeShape => (16, 0x7F), + CustomizeIndex.SmallIris => (16, 0x80), + CustomizeIndex.Nose => (17, 0xFF), + CustomizeIndex.Jaw => (18, 0xFF), + CustomizeIndex.Mouth => (19, 0x7F), + CustomizeIndex.Lipstick => (19, 0x80), + CustomizeIndex.LipColor => (20, 0xFF), + CustomizeIndex.MuscleMass => (21, 0xFF), + CustomizeIndex.TailShape => (22, 0xFF), + CustomizeIndex.BustSize => (23, 0xFF), + CustomizeIndex.FacePaint => (24, 0x7F), + CustomizeIndex.FacePaintReversed => (24, 0x80), + CustomizeIndex.FacePaintColor => (25, 0xFF), + _ => (0, 0x00), + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static CustomizeFlag ToFlag(this CustomizeIndex index) + => (CustomizeFlag)(1ul << (int)index); + + public static string ToDefaultName(this CustomizeIndex customizeIndex) + => customizeIndex switch + { + CustomizeIndex.Race => "Race", + CustomizeIndex.Gender => "Gender", + CustomizeIndex.BodyType => "Body Type", + CustomizeIndex.Height => "Height", + CustomizeIndex.Clan => "Clan", + CustomizeIndex.Face => "Head Style", + CustomizeIndex.Hairstyle => "Hair Style", + CustomizeIndex.Highlights => "Highlights", + CustomizeIndex.SkinColor => "Skin Color", + CustomizeIndex.EyeColorRight => "Right Eye Color", + CustomizeIndex.HairColor => "Hair Color", + CustomizeIndex.HighlightsColor => "Highlights Color", + CustomizeIndex.TattooColor => "Tattoo Color", + CustomizeIndex.Eyebrows => "Eyebrow Style", + CustomizeIndex.EyeColorLeft => "Left Eye Color", + CustomizeIndex.EyeShape => "Eye Shape", + CustomizeIndex.Nose => "Nose Style", + CustomizeIndex.Jaw => "Jaw Style", + CustomizeIndex.Mouth => "Mouth Style", + CustomizeIndex.MuscleMass => "Muscle Tone", + CustomizeIndex.TailShape => "Tail Shape", + CustomizeIndex.BustSize => "Bust Size", + CustomizeIndex.FacePaint => "Face Paint", + CustomizeIndex.FacePaintColor => "Face Paint Color", + CustomizeIndex.LipColor => "Lip Color", + CustomizeIndex.FacialFeature1 => "Facial Feature 1", + CustomizeIndex.FacialFeature2 => "Facial Feature 2", + CustomizeIndex.FacialFeature3 => "Facial Feature 3", + CustomizeIndex.FacialFeature4 => "Facial Feature 4", + CustomizeIndex.FacialFeature5 => "Facial Feature 5", + CustomizeIndex.FacialFeature6 => "Facial Feature 6", + CustomizeIndex.FacialFeature7 => "Facial Feature 7", + CustomizeIndex.LegacyTattoo => "Legacy Tattoo", + CustomizeIndex.SmallIris => "Small Iris", + CustomizeIndex.Lipstick => "Enable Lipstick", + CustomizeIndex.FacePaintReversed => "Reverse Face Paint", + _ => string.Empty, + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index) + { + var (offset, mask) = index.ToByteAndMask(); + return (CustomizeValue)(data.Data[offset] & mask); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index, CustomizeValue value) + { + var (offset, mask) = index.ToByteAndMask(); + return mask != 0xFF + ? SetIfDifferentMasked(ref data.Data[offset], value, mask) + : SetIfDifferent(ref data.Data[offset], value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static bool SetIfDifferentMasked(ref byte oldValue, CustomizeValue newValue, byte mask) + { + var tmp = (byte)(newValue.Value & mask); + tmp = (byte)(tmp | (oldValue & ~mask)); + if (oldValue == tmp) + return false; + + oldValue = tmp; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static bool SetIfDifferent(ref byte oldValue, CustomizeValue newValue) + { + if (oldValue == newValue.Value) + return false; + + oldValue = newValue.Value; + return true; + } +} diff --git a/Glamourer.GameData/Customization/CustomizeValue.cs b/Glamourer.GameData/Customization/CustomizeValue.cs new file mode 100644 index 0000000..25b3406 --- /dev/null +++ b/Glamourer.GameData/Customization/CustomizeValue.cs @@ -0,0 +1,34 @@ +namespace Glamourer.Customization; + +public record struct CustomizeValue(byte Value) +{ + public static readonly CustomizeValue Zero = new(0); + public static readonly CustomizeValue Max = new(0xFF); + + public static CustomizeValue Bool(bool b) + => b ? Max : Zero; + + public static explicit operator CustomizeValue(byte value) + => new(value); + + public static CustomizeValue operator ++(CustomizeValue v) + => new(++v.Value); + + public static CustomizeValue operator --(CustomizeValue v) + => new(--v.Value); + + public static bool operator <(CustomizeValue v, int count) + => v.Value < count; + + public static bool operator >(CustomizeValue v, int count) + => v.Value > count; + + public static CustomizeValue operator +(CustomizeValue v, int rhs) + => new((byte)(v.Value + rhs)); + + public static CustomizeValue operator -(CustomizeValue v, int rhs) + => new((byte)(v.Value - rhs)); + + public override string ToString() + => Value.ToString(); +} diff --git a/Glamourer.GameData/RestrictedGear.cs b/Glamourer.GameData/RestrictedGear.cs index d8b84cb..69cc8f2 100644 --- a/Glamourer.GameData/RestrictedGear.cs +++ b/Glamourer.GameData/RestrictedGear.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using Dalamud.Data; using Dalamud.Logging; using Dalamud.Utility; @@ -187,6 +188,7 @@ public class RestrictedGear _femaleToMale.TryAdd(fModelIdSlot, mModelIdSlot); } + // @formatter:off // Add all currently existing and known gender restricted items. private void AddKnown() { @@ -431,4 +433,5 @@ public class RestrictedGear 0x0102E8, 0x010245, }; + // @Formatter:on } diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 3ab12f8..a288798 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -27,17 +27,17 @@ public class GlamourerIpc : IDisposable private readonly ObjectTable _objectTable; private readonly DalamudPluginInterface _pluginInterface; - //internal ICallGateProvider? ProviderGetAllCustomization; - //internal ICallGateProvider? ProviderGetAllCustomizationFromCharacter; - //internal ICallGateProvider? ProviderApplyAll; - //internal ICallGateProvider? ProviderApplyAllToCharacter; - //internal ICallGateProvider? ProviderApplyOnlyCustomization; - //internal ICallGateProvider? ProviderApplyOnlyCustomizationToCharacter; - //internal ICallGateProvider? ProviderApplyOnlyEquipment; - //internal ICallGateProvider? ProviderApplyOnlyEquipmentToCharacter; - //internal ICallGateProvider? ProviderRevert; - //internal ICallGateProvider? ProviderRevertCharacter; - //internal ICallGateProvider? ProviderGetApiVersion; + internal ICallGateProvider? ProviderGetAllCustomization; + internal ICallGateProvider? ProviderGetAllCustomizationFromCharacter; + internal ICallGateProvider? ProviderApplyAll; + internal ICallGateProvider? ProviderApplyAllToCharacter; + internal ICallGateProvider? ProviderApplyOnlyCustomization; + internal ICallGateProvider? ProviderApplyOnlyCustomizationToCharacter; + internal ICallGateProvider? ProviderApplyOnlyEquipment; + internal ICallGateProvider? ProviderApplyOnlyEquipmentToCharacter; + internal ICallGateProvider? ProviderRevert; + internal ICallGateProvider? ProviderRevertCharacter; + internal ICallGateProvider? ProviderGetApiVersion; public GlamourerIpc(ClientState clientState, ObjectTable objectTable, DalamudPluginInterface pluginInterface) { @@ -53,31 +53,31 @@ 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 + { + ProviderGetApiVersion = _pluginInterface.GetIpcProvider(LabelProviderApiVersion); + ProviderGetApiVersion.RegisterFunc(GetApiVersion); + } + catch (Exception ex) + { + PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}."); + } + //try //{ // ProviderGetAllCustomization = _pluginInterface.GetIpcProvider(LabelProviderGetAllCustomization); @@ -187,8 +187,8 @@ public class GlamourerIpc : IDisposable //} } - //private static int GetApiVersion() - // => CurrentApiVersion; + private static int GetApiVersion() + => CurrentApiVersion; // //private void ApplyAll(string customization, string characterName) //{ diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index 5feb90a..56b2d06 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -1,41 +1,42 @@ using System; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; -using Dalamud.Plugin.Ipc; using Glamourer.Interop; using Glamourer.Structs; using ImGuiNET; +using Penumbra.Api; +using Penumbra.Api.Enums; +using Penumbra.Api.Helpers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Api; -public class PenumbraAttach : IDisposable +public unsafe class PenumbraAttach : IDisposable { public const int RequiredPenumbraBreakingVersion = 4; - public const int RequiredPenumbraFeatureVersion = 12; + public const int RequiredPenumbraFeatureVersion = 15; - private ICallGateSubscriber? _tooltipSubscriber; - private ICallGateSubscriber? _clickSubscriber; - private ICallGateSubscriber? _redrawSubscriberName; - private ICallGateSubscriber? _redrawSubscriberObject; - private ICallGateSubscriber? _drawObjectInfo; - private ICallGateSubscriber? _creatingCharacterBase; - private ICallGateSubscriber? _createdCharacterBase; - private ICallGateSubscriber? _cutsceneParent; + private EventSubscriber _tooltipSubscriber; + private EventSubscriber _clickSubscriber; + private ActionSubscriber _redrawSubscriber; + private FuncSubscriber _drawObjectInfo; + public EventSubscriber CreatingCharacterBase; + public EventSubscriber CreatedCharacterBase; + private FuncSubscriber _cutsceneParent; - private readonly ICallGateSubscriber _initializedEvent; - private readonly ICallGateSubscriber _disposedEvent; - - public event Action? CreatingCharacterBase; - public event Action? CreatedCharacterBase; + private readonly EventSubscriber _initializedEvent; + private readonly EventSubscriber _disposedEvent; + public bool Available { get; private set; } public PenumbraAttach(bool attach) { - _initializedEvent = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.Initialized"); - _disposedEvent = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.Disposed"); - _initializedEvent.Subscribe(Reattach); - _disposedEvent.Subscribe(Unattach); + _initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach); + _disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach); + _tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface, PenumbraTooltip); + _clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface, PenumbraRightClick); + CreatedCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface); + CreatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface); Reattach(attach); } @@ -48,31 +49,22 @@ public class PenumbraAttach : IDisposable { Unattach(); - var versionSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions"); - var (breaking, feature) = versionSubscriber.InvokeFunc(); + var (breaking, feature) = Ipc.ApiVersions.Subscriber(Dalamud.PluginInterface).Invoke(); if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion) throw new Exception( $"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); - _redrawSubscriberName = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.RedrawObjectByName"); - _redrawSubscriberObject = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.RedrawObject"); - _drawObjectInfo = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.GetDrawObjectInfo"); - _cutsceneParent = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.GetCutsceneParentIndex"); - if (!attach) return; - _tooltipSubscriber = Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.ChangedItemTooltip"); - _clickSubscriber = - Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.ChangedItemClick"); - _creatingCharacterBase = - Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.CreatingCharacterBase"); - _createdCharacterBase = - Dalamud.PluginInterface.GetIpcSubscriber("Penumbra.CreatedCharacterBase"); - _tooltipSubscriber.Subscribe(PenumbraTooltip); - _clickSubscriber.Subscribe(PenumbraRightClick); - _creatingCharacterBase.Subscribe(SubscribeCreatingCharacterBase); - _createdCharacterBase.Subscribe(SubscribeCreatedCharacterBase); + _tooltipSubscriber.Enable(); + _clickSubscriber.Enable(); + CreatingCharacterBase.Enable(); + CreatedCharacterBase.Enable(); + _drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface); + _cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface); + _redrawSubscriber = Ipc.RedrawObject.Subscriber(Dalamud.PluginInterface); + Available = true; PluginLog.Debug("Glamourer attached to Penumbra."); } catch (Exception e) @@ -81,35 +73,28 @@ public class PenumbraAttach : IDisposable } } - private void SubscribeCreatingCharacterBase(IntPtr gameObject, string _, IntPtr modelId, IntPtr customize, IntPtr equipment) - => CreatingCharacterBase?.Invoke(gameObject, modelId, customize, equipment); - - private void SubscribeCreatedCharacterBase(IntPtr gameObject, string _, IntPtr drawObject) - => CreatedCharacterBase?.Invoke(gameObject, drawObject); - public void Unattach() { - _tooltipSubscriber?.Unsubscribe(PenumbraTooltip); - _clickSubscriber?.Unsubscribe(PenumbraRightClick); - _creatingCharacterBase?.Unsubscribe(SubscribeCreatingCharacterBase); - _createdCharacterBase?.Unsubscribe(SubscribeCreatedCharacterBase); - _tooltipSubscriber = null; - _clickSubscriber = null; - _creatingCharacterBase = null; - _redrawSubscriberName = null; - _drawObjectInfo = null; - if (_redrawSubscriberObject != null) + _tooltipSubscriber.Disable(); + _clickSubscriber.Disable(); + CreatingCharacterBase.Disable(); + CreatedCharacterBase.Disable(); + if (Available) { - PluginLog.Debug("Glamourer detached from Penumbra."); - _redrawSubscriberObject = null; + Available = false; + Glamourer.Log.Debug("Glamourer detached from Penumbra."); } } public void Dispose() { - _initializedEvent.Unsubscribe(Reattach); - _disposedEvent.Unsubscribe(Unattach); Unattach(); + _tooltipSubscriber.Dispose(); + _clickSubscriber.Dispose(); + CreatingCharacterBase.Dispose(); + CreatedCharacterBase.Dispose(); + _initializedEvent.Dispose(); + _disposedEvent.Dispose(); } private static void PenumbraTooltip(ChangedItemType type, uint _) @@ -118,7 +103,7 @@ public class PenumbraAttach : IDisposable ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]"); } - private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id) + private static void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id) { if (button != MouseButton.Right || type != ChangedItemType.Item) return; @@ -166,43 +151,23 @@ public class PenumbraAttach : IDisposable } public Actor GameObjectFromDrawObject(IntPtr drawObject) - => _drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero; + => Available ? _drawObjectInfo.Invoke(drawObject).Item1 : IntPtr.Zero; public int CutsceneParent(int idx) - => _cutsceneParent?.InvokeFunc(idx) ?? -1; + => Available ? _cutsceneParent.Invoke(idx) : -1; - public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat) + public void RedrawObject(GameObject? actor, RedrawType settings) { - if (actor == null) + if (actor == null || !Available) return; - if (_redrawSubscriberObject != null) + try { - try - { - _redrawSubscriberObject.InvokeAction(actor, (int)settings); - } - catch (Exception e) - { - if (repeat) - { - Reattach(Glamourer.Config.AttachToPenumbra); - RedrawObject(actor, settings, false); - } - else - { - PluginLog.Debug($"Failure redrawing object:\n{e}"); - } - } + _redrawSubscriber.Invoke(actor, settings); } - else if (repeat) + catch (Exception e) { - Reattach(Glamourer.Config.AttachToPenumbra); - RedrawObject(actor, settings, false); - } - else - { - PluginLog.Debug("Trying to redraw object, but not attached to Penumbra."); + PluginLog.Debug($"Failure redrawing object:\n{e}"); } } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs deleted file mode 100644 index 5193727..0000000 --- a/Glamourer/Designs/Design.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Glamourer.State; -using Glamourer.Structs; -using Penumbra.GameData.Structs; - -namespace Glamourer.Designs; - -public class Design -{ - public string Name { get; } - public bool ReadOnly; - - public DateTimeOffset CreationDate { get; } - public DateTimeOffset LastUpdateDate { get; } - public CharacterSave Data { get; } - - public override string ToString() - => Name; -} - -public struct ArmorData -{ - public CharacterArmor Model; - public bool Ignore; -} diff --git a/Glamourer/Designs/FixedDesigns.cs b/Glamourer/Designs/FixedDesigns.cs index b486e27..70aa629 100644 --- a/Glamourer/Designs/FixedDesigns.cs +++ b/Glamourer/Designs/FixedDesigns.cs @@ -12,6 +12,7 @@ using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Structs; +using Glamourer.Saves; namespace Glamourer.Designs; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index f73074b..6aa21e3 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -47,14 +47,14 @@ public class Glamourer : IDalamudPlugin //public static RevertableDesigns RevertableDesigns = new(); //public readonly GlamourerIpc GlamourerIpc; - public unsafe Glamourer(DalamudPluginInterface pluginInterface) + public Glamourer(DalamudPluginInterface pluginInterface) { try { Dalamud.Initialize(pluginInterface); Log = new Logger(); - Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage); + Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData); RestrictedGear = GameData.RestrictedGear(Dalamud.GameData); Models = GameData.Models(Dalamud.GameData); diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index e25ba6d..bbcf777 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -84,6 +84,7 @@ + @@ -118,7 +119,7 @@ - + \ No newline at end of file diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 9cf0281..96bc6fe 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -9,10 +9,10 @@ internal partial class CustomizationDrawer { private const string ColorPickerPopupName = "ColorPicker"; - private void DrawColorPicker(CustomizationId id) + private void DrawColorPicker(CustomizeIndex index) { - using var _ = SetId(id); - var (current, custom) = GetCurrentCustomization(id); + using var _ = SetId(index); + var (current, custom) = GetCurrentCustomization(index); var color = ImGui.ColorConvertU32ToFloat4(custom.Color); // Print 1-based index instead of 0. @@ -40,7 +40,7 @@ internal partial class CustomizationDrawer .Push(ImGuiStyleVar.FrameRounding, 0); for (var i = 0; i < _currentCount; ++i) { - var custom = _set.Data(_currentId, i, _customize[CustomizationId.Face]); + var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]); if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color))) { UpdateValue(custom.Value); @@ -53,13 +53,13 @@ internal partial class CustomizationDrawer } // Obtain the current customization and print a warning if it is not known. - private (int, CustomizationData) GetCurrentCustomization(CustomizationId id) + private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index) { - var current = _set.DataByValue(id, _customize[id], out var custom); - if (!_set.IsAvailable(id) || current >= 0) + var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face); + if (!_set.IsAvailable(index) || current >= 0) return (current, custom!.Value); - Glamourer.Log.Warning($"Read invalid customization value {_customize[id]} for {id}."); - return (0, _set.Data(id, 0)); + Glamourer.Log.Warning($"Read invalid customization value {_customize[index]} for {index}."); + return (0, _set.Data(index, 0)); } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index fb712db..bc97122 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -6,6 +6,7 @@ using Glamourer.Util; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.Api.Enums; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Customization; @@ -39,7 +40,7 @@ internal partial class CustomizationDrawer return; foreach (var actor in _actors.Where(a => a)) - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false); + Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); } private void DrawRaceCombo() @@ -56,7 +57,7 @@ internal partial class CustomizationDrawer continue; foreach (var actor in _actors.Where(a => a && a.DrawObject)) - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false); + Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); } } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index b83288d..039b703 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -5,6 +5,7 @@ using Glamourer.Customization; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.Api.Enums; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Customization; @@ -13,18 +14,18 @@ internal partial class CustomizationDrawer { private const string IconSelectorPopup = "Style Picker"; - private void DrawIconSelector(CustomizationId id) + private void DrawIconSelector(CustomizeIndex index) { - using var _ = SetId(id); + using var _ = SetId(index); using var bigGroup = ImRaii.Group(); var label = _currentOption; - var current = _set.DataByValue(id, _currentByte, out var custom); + var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face); if (current < 0) { - label = $"{_currentOption} (Custom #{_customize[id]})"; + label = $"{_currentOption} (Custom #{_customize[index]})"; current = 0; - custom = _set.Data(id, 0); + custom = _set.Data(index, 0); } var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId); @@ -35,7 +36,7 @@ internal partial class CustomizationDrawer ImGui.SameLine(); using (var group = ImRaii.Group()) { - if (_currentId == CustomizationId.Face) + if (_currentIndex == CustomizeIndex.Face) FaceInputInt(current); else DataInputInt(current); @@ -45,7 +46,7 @@ internal partial class CustomizationDrawer DrawIconPickerPopup(); } - private void UpdateFace(CustomizationData data) + private void UpdateFace(CustomizeData data) { // Hrothgar Hack var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value; @@ -54,7 +55,7 @@ internal partial class CustomizationDrawer _customize.Face = value; foreach (var actor in _actors) - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false); + Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); } private void FaceInputInt(int currentIndex) @@ -64,7 +65,7 @@ internal partial class CustomizationDrawer if (ImGui.InputInt("##text", ref currentIndex, 1, 1)) { currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1); - var data = _set.Data(_currentId, currentIndex, _customize.Face); + var data = _set.Data(_currentIndex, currentIndex, _customize.Face); UpdateFace(data); } @@ -81,13 +82,13 @@ internal partial class CustomizationDrawer .Push(ImGuiStyleVar.FrameRounding, 0); for (var i = 0; i < _currentCount; ++i) { - var custom = _set.Data(_currentId, i, _customize.Face); + var custom = _set.Data(_currentIndex, i, _customize.Face); var icon = Glamourer.Customization.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) { - if (_currentId == CustomizationId.Face) + if (_currentIndex == CustomizeIndex.Face) UpdateFace(custom); else UpdateValue(custom.Value); diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Main.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Main.cs index 0418a49..679b8cb 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Main.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Main.cs @@ -88,19 +88,8 @@ internal partial class CustomizationDrawer Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine); - d.Checkbox(d._set.Option(CustomizationId.HighlightsOnFlag), customize.HighlightsOn, b => customize.HighlightsOn = b); - var xPos = d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; - ImGui.SameLine(xPos); - d.Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {d._set.Option(CustomizationId.FacePaint)}", - customize.FacePaintReversed, b => customize.FacePaintReversed = b); - d.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}", - customize.SmallIris, b => customize.SmallIris = b); - - if (customize.Race != Race.Hrothgar) - { - ImGui.SameLine(xPos); - d.Checkbox(d._set.Option(CustomizationId.LipColor), customize.Lipstick, b => customize.Lipstick = b); - } + Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.Checkmark], d.DrawCheckbox, + () => ImGui.SameLine(d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X)); } public static void Draw(Customize customize, IReadOnlyCollection actors, bool locked = false) @@ -113,29 +102,29 @@ internal partial class CustomizationDrawer => Draw(customize, CharacterEquip.Null, Array.Empty(), locked); // Set state for drawing of current customization. - private CustomizationId _currentId; - private CustomizationByteValue _currentByte = CustomizationByteValue.Zero; - private int _currentCount; - private string _currentOption = string.Empty; + private CustomizeIndex _currentIndex; + private CustomizeValue _currentByte = CustomizeValue.Zero; + private int _currentCount; + private string _currentOption = string.Empty; // Prepare a new customization option. - private ImRaii.Id SetId(CustomizationId id) + private ImRaii.Id SetId(CustomizeIndex index) { - _currentId = id; - _currentByte = _customize[id]; - _currentCount = _set.Count(id, _customize.Face); - _currentOption = _set.Option(id); - return ImRaii.PushId((int)id); + _currentIndex = index; + _currentByte = _customize[index]; + _currentCount = _set.Count(index, _customize.Face); + _currentOption = _set.Option(index); + return ImRaii.PushId((int)index); } // Update the current id with a value, // also update actors if any. - private void UpdateValue(CustomizationByteValue value) + private void UpdateValue(CustomizeValue value) { - if (_customize[_currentId] == value) + if (_customize[_currentIndex] == value) return; - _customize[_currentId] = value; + _customize[_currentIndex] = value; UpdateActors(); } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs index b06ccdf..e23d5bd 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs @@ -1,5 +1,4 @@ -using System.Linq; -using System.Numerics; +using System.Numerics; using Glamourer.Customization; using ImGuiNET; using OtterGui; @@ -12,9 +11,7 @@ internal partial class CustomizationDrawer // Only used for facial features, so fixed ID. private void DrawMultiIconSelector() { - using var _ = SetId(CustomizationId.FacialFeaturesTattoos); using var bigGroup = ImRaii.Group(); - DrawMultiIcons(); ImGui.SameLine(); using var group = ImRaii.Group(); @@ -23,28 +20,30 @@ internal partial class CustomizationDrawer _currentCount = 256; PercentageInputInt(); - ImGui.TextUnformatted(_set.Option(CustomizationId.FacialFeaturesTattoos)); + ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo)); } private void DrawMultiIcons() { - using var _ = ImRaii.Group(); - for (var i = 0; i < _currentCount; ++i) + var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark]; + using var _ = ImRaii.Group(); + foreach (var (featureIdx, idx) in options.WithIndex()) { - var enabled = _customize.FacialFeatures[i]; - var feature = _set.FacialFeature(_customize.Face, i); - var icon = i == _currentCount - 1 + using var id = SetId(featureIdx); + var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; + var feature = _set.Data(featureIdx, 0, _customize.Face); + var icon = featureIdx == CustomizeIndex.LegacyTattoo ? 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)) { - _customize.FacialFeatures.Set(i, !enabled); + _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); UpdateActors(); } ImGuiUtil.HoverIconTooltip(icon, _iconSize); - if (i % 4 != 3) + if (idx % 4 != 3) ImGui.SameLine(); } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 7aff7dd..461e02b 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,8 +1,8 @@ using System; using System.Linq; +using System.Security.AccessControl; using Glamourer.Customization; using ImGuiNET; -using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; @@ -10,9 +10,9 @@ namespace Glamourer.Gui.Customization; internal partial class CustomizationDrawer { - private void DrawListSelector(CustomizationId id) + private void DrawListSelector(CustomizeIndex index) { - using var _ = SetId(id); + using var _ = SetId(index); using var bigGroup = ImRaii.Group(); ListCombo(); @@ -33,22 +33,22 @@ internal partial class CustomizationDrawer for (var i = 0; i < _currentCount; ++i) { if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value)) - UpdateValue((CustomizationByteValue)i); + UpdateValue((CustomizeValue)i); } } - + private void ListInputInt() { var tmp = _currentByte.Value + 1; ImGui.SetNextItemWidth(_inputIntSize); if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount) - UpdateValue((CustomizationByteValue)Math.Clamp(tmp - 1, 0, _currentCount - 1)); + UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1)); ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]"); } - private void PercentageSelector(CustomizationId id) + private void PercentageSelector(CustomizeIndex index) { - using var _ = SetId(id); + using var _ = SetId(index); using var bigGroup = ImRaii.Group(); DrawPercentageSlider(); @@ -63,7 +63,7 @@ internal partial class CustomizationDrawer var tmp = (int)_currentByte.Value; ImGui.SetNextItemWidth(_comboSelectorSize); if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp)) - UpdateValue((CustomizationByteValue)tmp); + UpdateValue((CustomizeValue)tmp); } private void PercentageInputInt() @@ -71,7 +71,7 @@ internal partial class CustomizationDrawer var tmp = (int)_currentByte.Value; ImGui.SetNextItemWidth(_inputIntSize); if (ImGui.InputInt("##text", ref tmp, 1, 1)) - UpdateValue((CustomizationByteValue)Math.Clamp(tmp, 0, _currentCount - 1)); + UpdateValue((CustomizeValue)Math.Clamp(tmp, 0, _currentCount - 1)); ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]"); } @@ -87,6 +87,14 @@ internal partial class CustomizationDrawer } } + // Draw a customize checkbox. + private void DrawCheckbox(CustomizeIndex idx) + { + using var id = SetId(idx); + Checkbox(_set.Option(idx), _customize.Get(idx) != CustomizeValue.Zero, + b => _customize.Set(idx, b ? CustomizeValue.Max : CustomizeValue.Zero)); + } + // Integral input for an icon- or color based item. private void DataInputInt(int currentIndex) { @@ -95,7 +103,7 @@ internal partial class CustomizationDrawer if (ImGui.InputInt("##text", ref currentIndex, 1, 1)) { currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1); - var data = _set.Data(_currentId, currentIndex, _customize.Face); + var data = _set.Data(_currentIndex, currentIndex, _customize.Face); UpdateValue(data.Value); } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs index 26347c0..453632b 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.Items.cs @@ -6,6 +6,7 @@ using Lumina.Excel.GeneratedSheets; using Lumina.Text; using OtterGui; using OtterGui.Classes; +using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.GameData; using Penumbra.GameData.Enums; @@ -141,6 +142,12 @@ public partial class EquipmentDrawer return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight()); } + //protected override bool DrawSelectable(int globalIdx, bool selected) + //{ + // using var _ = ImRaii.Group(); + // + //} + protected override bool IsVisible(int globalIndex, LowerString filter) { var item = Items[globalIndex]; diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs index 3f6ea55..2c02e80 100644 --- a/Glamourer/Gui/Interface.Actors.cs +++ b/Glamourer/Gui/Interface.Actors.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.Linq; using System.Numerics; using Dalamud.Interface; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; @@ -10,6 +12,8 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using Penumbra.GameData.Enums; +using ImGui = ImGuiNET.ImGui; namespace Glamourer.Gui; @@ -59,6 +63,7 @@ internal partial class Interface if (_currentData.Valid) _currentSave.Update(_currentData.Objects[0]); + RevertButton(); CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects, _identifier is Actor.SpecialIdentifier); @@ -68,6 +73,52 @@ internal partial class Interface private const uint RedHeaderColor = 0xFF1818C0; private const uint GreenHeaderColor = 0xFF18C018; + private void RevertButton() + { + if (ImGui.Button("Revert")) + { + _manipulations.DeleteSave(_identifier); + + foreach (var actor in _currentData.Objects) + _currentSave!.ApplyToActor(actor); + + if (_currentData.Objects.Count > 0) + _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]); + + _currentSave!.Reset(); + } + + VisorBox(); + } + + private unsafe void VisorBox() + { + var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch + { + ApplicationFlags.SetVisor => (0u, 3u), + ApplicationFlags.Visor => (1u, 3u), + ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u), + _ => (2u, 3u), + }; + var tmp = flags; + if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask)) + { + _currentSave.Data.Flags = flags switch + { + 0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor, + 1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, + 2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor, + _ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor), + }; + if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor)) + { + var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor); + foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject)) + RedrawManager.SetVisor(actor.DrawObject.Pointer, on); + } + } + } + private void DrawPanelHeader() { var color = _currentData.Valid ? GreenHeaderColor : RedHeaderColor; diff --git a/Glamourer/Gui/Interface.DebugDataTab.cs b/Glamourer/Gui/Interface.DebugDataTab.cs new file mode 100644 index 0000000..821bc08 --- /dev/null +++ b/Glamourer/Gui/Interface.DebugDataTab.cs @@ -0,0 +1,51 @@ +using System; +using Glamourer.Customization; +using Glamourer.Util; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui; + +internal partial class Interface +{ + private class DebugDataTab + { + private readonly ICustomizationManager _mg; + + public DebugDataTab(ICustomizationManager manager) + => _mg = manager; + + public void Draw() + { + using var tab = ImRaii.TabItem("Debug"); + if (!tab) + return; + + foreach (var clan in _mg.Clans) + { + foreach (var gender in _mg.Genders) + DrawCustomizationInfo(_mg.GetList(clan, gender)); + } + } + + public static void DrawCustomizationInfo(CustomizationSet set) + { + if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(set.Clan, set.Gender)} {set.Gender}")) + return; + + using var table = ImRaii.Table("data", 5); + if (!table) + return; + + foreach (var index in Enum.GetValues()) + { + ImGuiUtil.DrawTableColumn(index.ToString()); + ImGuiUtil.DrawTableColumn(set.Option(index)); + ImGuiUtil.DrawTableColumn(set.IsAvailable(index) ? "Available" : "Unavailable"); + ImGuiUtil.DrawTableColumn(set.Type(index).ToString()); + ImGuiUtil.DrawTableColumn(set.Count(index).ToString()); + } + } + } +} diff --git a/Glamourer/Gui/Interface.cs b/Glamourer/Gui/Interface.cs index 07acd44..57ee909 100644 --- a/Glamourer/Gui/Interface.cs +++ b/Glamourer/Gui/Interface.cs @@ -15,6 +15,7 @@ internal partial class Interface : Window, IDisposable private readonly ActorTab _actorTab; private readonly DebugStateTab _debugStateTab; + private readonly DebugDataTab _debugDataTab; public Interface(Glamourer plugin) : base(GetLabel()) @@ -29,6 +30,7 @@ internal partial class Interface : Window, IDisposable }; _actorTab = new ActorTab(_plugin.CurrentManipulations); _debugStateTab = new DebugStateTab(_plugin.CurrentManipulations); + _debugDataTab = new DebugDataTab(Glamourer.Customization); } public override void Draw() @@ -44,6 +46,7 @@ internal partial class Interface : Window, IDisposable _actorTab.Draw(); DrawSettingsTab(); _debugStateTab.Draw(); + _debugDataTab.Draw(); // DrawSaves(); // DrawFixedDesignsTab(); // DrawRevertablesTab(); diff --git a/Glamourer/Interop/Actor.Identifier.cs b/Glamourer/Interop/Actor.Identifier.cs index 898de52..b145c74 100644 --- a/Glamourer/Interop/Actor.Identifier.cs +++ b/Glamourer/Interop/Actor.Identifier.cs @@ -1,6 +1,7 @@ using System; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Utility; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.ByteString; @@ -187,13 +188,26 @@ public unsafe partial struct Actor public bool IsValid => true; - public OwnedIdentifier(Utf8String name, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind) + public OwnedIdentifier(Utf8String actorName, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind) { - Name = name; OwnerName = ownerName; OwnerHomeWorld = ownerHomeWorld; DataId = dataId; Kind = kind; + Name = actorName; + switch (Kind) + { + case ObjectKind.MountType: + var mount = Dalamud.GameData.GetExcelSheet()!.GetRow(dataId); + if (mount != null) + Name = Utf8String.FromSpanUnsafe(mount.Singular.RawData, false).AsciiToMixed(); + break; + case ObjectKind.Companion: + var companion = Dalamud.GameData.GetExcelSheet()!.GetRow(dataId); + if (companion != null) + Name = Utf8String.FromSpanUnsafe(companion.Singular.RawData, false).AsciiToMixed(); + break; + } } public bool Equals(IIdentifier? other) @@ -341,8 +355,19 @@ public unsafe partial struct Actor if (!owner) return new InvalidIdentifier(); - return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld, - actor.Pointer->GameObject.DataID, actor.ObjectKind); + var dataId = actor.ObjectKind switch + { + ObjectKind.MountType => owner.UsedMountId, + ObjectKind.Companion => actor.CompanionId, + _ => actor.Pointer->GameObject.DataID, + }; + + var name = actor.Utf8Name; + if (name.IsEmpty && dataId == 0) + return new InvalidIdentifier(); + + return new OwnedIdentifier(name, owner.Utf8Name, owner.Pointer->HomeWorld, + dataId, actor.ObjectKind); } default: return new InvalidIdentifier(); } diff --git a/Glamourer/Interop/Actor.cs b/Glamourer/Interop/Actor.cs index c2c4977..5197801 100644 --- a/Glamourer/Interop/Actor.cs +++ b/Glamourer/Interop/Actor.cs @@ -1,115 +1,13 @@ using System; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; using Penumbra.GameData.ByteString; using Penumbra.GameData.Structs; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Interop; -public interface IDesignable -{ - public bool Valid { get; } - public uint ModelId { get; } - public Customize Customize { get; } - public CharacterEquip Equip { get; } - public CharacterWeapon MainHand { get; } - public CharacterWeapon OffHand { get; } - public bool VisorEnabled { get; } - public bool WeaponEnabled { get; } -} - -public unsafe partial struct DrawObject : IEquatable, IDesignable -{ - public Human* Pointer; - - public IntPtr Address - => (IntPtr)Pointer; - - public static implicit operator DrawObject(IntPtr? pointer) - => new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) }; - - public static implicit operator IntPtr(DrawObject drawObject) - => drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer; - - public bool Valid - => Pointer != null; - - public uint ModelId - => 0; - - public uint Type - => (*(delegate* unmanaged**)Pointer)[50](Pointer); - - public Customize Customize - => new((CustomizeData*)Pointer->CustomizeData); - - public CharacterEquip Equip - => new((CharacterArmor*)Pointer->EquipSlotData); - - public CharacterWeapon MainHand - { - get - { - var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject; - if (child == null) - return CharacterWeapon.Empty; - - return *(CharacterWeapon*)(child + 0x8F0); - } - } - - public unsafe CharacterWeapon OffHand - { - get - { - var child = Pointer->CharacterBase.DrawObject.Object.ChildObject; - if (child == null) - return CharacterWeapon.Empty; - - var sibling = (byte*) child->NextSiblingObject; - if (sibling == null) - return CharacterWeapon.Empty; - - return *(CharacterWeapon*)(child + 0x8F0); - } - } - - public unsafe bool VisorEnabled - => (*(byte*)(Address + 0x90) & 0x40) != 0; - - public unsafe bool WeaponEnabled - => false; - - public static implicit operator bool(DrawObject actor) - => actor.Pointer != null; - - public static bool operator true(DrawObject actor) - => actor.Pointer != null; - - public static bool operator false(DrawObject actor) - => actor.Pointer == null; - - public static bool operator !(DrawObject actor) - => actor.Pointer == null; - - public bool Equals(DrawObject other) - => Pointer == other.Pointer; - - public override bool Equals(object? obj) - => obj is DrawObject other && Equals(other); - - public override int GetHashCode() - => unchecked((int)(long)Pointer); - - public static bool operator ==(DrawObject lhs, DrawObject rhs) - => lhs.Pointer == rhs.Pointer; - - public static bool operator !=(DrawObject lhs, DrawObject rhs) - => lhs.Pointer != rhs.Pointer; -} - public unsafe partial struct Actor : IEquatable, IDesignable { public static readonly Actor Null = new() { Pointer = null }; @@ -173,6 +71,12 @@ public unsafe partial struct Actor : IEquatable, IDesignable set => Pointer->ModelCharaId = (int)value; } + public ushort UsedMountId + => !IsHuman ? (ushort)0 : *(ushort*)((byte*)Pointer + 0x668); + + public ushort CompanionId + => ObjectKind == ObjectKind.Companion ? *(ushort*)((byte*)Pointer + 0x1AAC) : (ushort)0; + public Customize Customize => new((CustomizeData*)Pointer->CustomizeData); diff --git a/Glamourer/Interop/DrawObject.cs b/Glamourer/Interop/DrawObject.cs new file mode 100644 index 0000000..3c1be30 --- /dev/null +++ b/Glamourer/Interop/DrawObject.cs @@ -0,0 +1,97 @@ +using System; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Glamourer.Customization; +using Penumbra.GameData.Structs; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; + +namespace Glamourer.Interop; + +public unsafe partial struct DrawObject : IEquatable, IDesignable +{ + public Human* Pointer; + + public IntPtr Address + => (IntPtr)Pointer; + + public static implicit operator DrawObject(IntPtr? pointer) + => new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) }; + + public static implicit operator IntPtr(DrawObject drawObject) + => drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer; + + public bool Valid + => Pointer != null; + + public uint ModelId + => 0; + + public uint Type + => (*(delegate* unmanaged**)Pointer)[50](Pointer); + + public Customize Customize + => new((CustomizeData*)Pointer->CustomizeData); + + public CharacterEquip Equip + => new((CharacterArmor*)Pointer->EquipSlotData); + + public CharacterWeapon MainHand + { + get + { + var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject; + if (child == null) + return CharacterWeapon.Empty; + + return *(CharacterWeapon*)(child + 0x8F0); + } + } + + public unsafe CharacterWeapon OffHand + { + get + { + var child = Pointer->CharacterBase.DrawObject.Object.ChildObject; + if (child == null) + return CharacterWeapon.Empty; + + var sibling = (byte*)child->NextSiblingObject; + if (sibling == null) + return CharacterWeapon.Empty; + + return *(CharacterWeapon*)(sibling + 0x8F0); + } + } + + public unsafe bool VisorEnabled + => (*(byte*)(Address + 0x90) & 0x40) != 0; + + public unsafe bool WeaponEnabled + => false; + + public static implicit operator bool(DrawObject actor) + => actor.Pointer != null; + + public static bool operator true(DrawObject actor) + => actor.Pointer != null; + + public static bool operator false(DrawObject actor) + => actor.Pointer == null; + + public static bool operator !(DrawObject actor) + => actor.Pointer == null; + + public bool Equals(DrawObject other) + => Pointer == other.Pointer; + + public override bool Equals(object? obj) + => obj is DrawObject other && Equals(other); + + public override int GetHashCode() + => unchecked((int)(long)Pointer); + + public static bool operator ==(DrawObject lhs, DrawObject rhs) + => lhs.Pointer == rhs.Pointer; + + public static bool operator !=(DrawObject lhs, DrawObject rhs) + => lhs.Pointer != rhs.Pointer; +} diff --git a/Glamourer/Interop/IDesignable.cs b/Glamourer/Interop/IDesignable.cs new file mode 100644 index 0000000..30f9883 --- /dev/null +++ b/Glamourer/Interop/IDesignable.cs @@ -0,0 +1,16 @@ +using Glamourer.Customization; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop; + +public interface IDesignable +{ + public bool Valid { get; } + public uint ModelId { get; } + public Customize Customize { get; } + public CharacterEquip Equip { get; } + public CharacterWeapon MainHand { get; } + public CharacterWeapon OffHand { get; } + public bool VisorEnabled { get; } + public bool WeaponEnabled { get; } +} diff --git a/Glamourer/Interop/RedrawManager.Customize.cs b/Glamourer/Interop/RedrawManager.Customize.cs new file mode 100644 index 0000000..1405b69 --- /dev/null +++ b/Glamourer/Interop/RedrawManager.Customize.cs @@ -0,0 +1,52 @@ +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Glamourer.Customization; +using Penumbra.Api.Enums; +using Penumbra.GameData.Enums; + +namespace Glamourer.Interop; + +public unsafe partial class RedrawManager +{ + // Update + public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment); + + [Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")] + private readonly ChangeCustomizeDelegate _changeCustomize = null!; + + public bool UpdateCustomize(Actor actor, Customize customize) + { + if (!actor.Valid || !actor.DrawObject.Valid) + return false; + + var d = actor.DrawObject; + if (NeedsRedraw(d.Customize, customize)) + { + Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); + return true; + } + + return _changeCustomize(d.Pointer, (byte*)customize.Data, 1); + } + + public static bool NeedsRedraw(Customize lhs, Customize rhs) + => lhs.Race != rhs.Race + || lhs.Gender != rhs.Gender + || lhs.BodyType != rhs.BodyType + || lhs.Face != rhs.Face + || lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan; + + + public static void SetVisor(Human* data, bool on) + { + if (data == null) + return; + + var flags = &data->CharacterBase.UnkFlags_01; + var state = (*flags & 0x40) != 0; + if (state == on) + return; + + *flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80); + } +} diff --git a/Glamourer/Interop/RedrawManager.Equipment.cs b/Glamourer/Interop/RedrawManager.Equipment.cs new file mode 100644 index 0000000..2e7619d --- /dev/null +++ b/Glamourer/Interop/RedrawManager.Equipment.cs @@ -0,0 +1,68 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Logging; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop; + +public unsafe partial class RedrawManager +{ + public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data); + + // This gets called when one of the ten equip items of an existing draw object gets changed. + [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))] + private readonly Hook _flagSlotForUpdateHook = null!; + + private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data) + { + var slot = slotIdx.ToEquipSlot(); + try + { + var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject); + var identifier = actor.GetIdentifier(); + + if (_fixedDesigns.TryGetDesign(identifier, out var save)) + { + PluginLog.Information($"Loaded {slot} from fixed design for {identifier}."); + (var replaced, *data) = + Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex); + } + else if (_currentManipulations.TryGetDesign(identifier, out var save2)) + { + PluginLog.Information($"Updated {slot} from current designs for {identifier}."); + (var replaced, *data) = + Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1)); + save2.Data.Equipment[slot] = *data; + } + } + catch (Exception e) + { + PluginLog.Error($"Error on loading new gear:\n{e}"); + } + + return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data); + } + + public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data) + { + if (!drawObject) + return false; + + if (slotIdx > 9) + return false; + + return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0; + } + + public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) + => actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data); + + public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data) + => ChangeEquip(drawObject, slot.ToIndex(), data); + + public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data) + => actor && ChangeEquip(actor.DrawObject, slotIdx, data); +} diff --git a/Glamourer/Interop/RedrawManager.Weapons.cs b/Glamourer/Interop/RedrawManager.Weapons.cs new file mode 100644 index 0000000..6b949ed --- /dev/null +++ b/Glamourer/Interop/RedrawManager.Weapons.cs @@ -0,0 +1,102 @@ +using System; +using System.Runtime.InteropServices; +using Dalamud.Hooking; +using Dalamud.Logging; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop; + +public unsafe partial class RedrawManager +{ + public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf("DrawData"); + + public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, + byte skipGameObject, + byte unk4); + + // Weapons for a specific character are reloaded with this function. + // The first argument is a pointer to the game object but shifted a bit inside. + // slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data. + // weapon argument is the new weapon data. + // redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one. + // skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change) + // unk4 seemed to be the same as unk1. + [Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))] + private readonly Hook _loadWeaponHook = null!; + + private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject, + byte unk4) + { + var oldWeapon = weapon; + var character = (Actor)(characterOffset - CharacterWeaponOffset); + try + { + var identifier = character.GetIdentifier(); + if (_fixedDesigns.TryGetDesign(identifier, out var save)) + { + PluginLog.Information($"Loaded weapon from fixed design for {identifier}."); + weapon = slot switch + { + 0 => save.MainHand.Value, + 1 => save.OffHand.Value, + _ => weapon, + }; + } + else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2)) + { + PluginLog.Information($"Loaded weapon from current design for {identifier}."); + switch (slot) + { + case 0: + save2.Data.MainHand = new CharacterWeapon(weapon); + break; + case 1: + save2.Data.OffHand = new CharacterWeapon(weapon); + break; + } + } + } + catch (Exception e) + { + PluginLog.Error($"Error on loading new weapon:\n{e}"); + } + + // First call the regular function. + _loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4); + // If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object. + if (oldWeapon != weapon) + _loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4); + // If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems. + else if (slot != 1 && character.OffHand.Value == 0) + _loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4); + } + + // Load a specific weapon for a character by its data and slot. + public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon) + { + switch (slot) + { + case EquipSlot.MainHand: + LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0); + return; + case EquipSlot.OffHand: + LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0); + return; + case EquipSlot.BothHand: + LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0); + LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0); + return; + // function can also be called with '2', but does not seem to ever be. + } + } + + // Load specific Main- and Offhand weapons. + public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off) + { + LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0); + LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0); + } +} diff --git a/Glamourer/Interop/RedrawManager.cs b/Glamourer/Interop/RedrawManager.cs index 6e52930..5739377 100644 --- a/Glamourer/Interop/RedrawManager.cs +++ b/Glamourer/Interop/RedrawManager.cs @@ -1,10 +1,7 @@ using System; -using System.Reflection.Metadata; -using System.Runtime.InteropServices; using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; using Glamourer.State; @@ -12,7 +9,6 @@ using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using CustomizeData = Penumbra.GameData.Structs.CustomizeData; -using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Interop; @@ -32,157 +28,6 @@ public unsafe partial class RedrawManager public event Action? JobChanged; } -public unsafe partial class RedrawManager -{ - public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data); - - // This gets called when one of the ten equip items of an existing draw object gets changed. - [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))] - private readonly Hook _flagSlotForUpdateHook = null!; - - private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data) - { - var slot = slotIdx.ToEquipSlot(); - try - { - var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject); - var identifier = actor.GetIdentifier(); - - if (_fixedDesigns.TryGetDesign(identifier, out var save)) - { - PluginLog.Information($"Loaded {slot} from fixed design for {identifier}."); - (var replaced, *data) = - Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex); - } - else if (_currentManipulations.TryGetDesign(identifier, out var save2)) - { - PluginLog.Information($"Updated {slot} from current designs for {identifier}."); - (var replaced, *data) = - Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1)); - save2.Data.Equipment[slot] = *data; - } - } - catch (Exception e) - { - PluginLog.Error($"Error on loading new gear:\n{e}"); - } - - return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data); - } - - public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data) - { - if (!drawObject) - return false; - - if (slotIdx > 9) - return false; - - return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0; - } - - public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) - => actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data); - - public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data) - => ChangeEquip(drawObject, slot.ToIndex(), data); - - public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data) - => actor && ChangeEquip(actor.DrawObject, slotIdx, data); -} - -public unsafe partial class RedrawManager -{ - public static readonly int CharacterWeaponOffset = (int) Marshal.OffsetOf("DrawData"); - - public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, - byte skipGameObject, - byte unk4); - - // Weapons for a specific character are reloaded with this function. - // The first argument is a pointer to the game object but shifted a bit inside. - // slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data. - // weapon argument is the new weapon data. - // redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one. - // skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change) - // unk4 seemed to be the same as unk1. - [Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))] - private readonly Hook _loadWeaponHook = null!; - - private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject, - byte unk4) - { - var oldWeapon = weapon; - var character = (Actor)(characterOffset - CharacterWeaponOffset); - try - { - var identifier = character.GetIdentifier(); - if (_fixedDesigns.TryGetDesign(identifier, out var save)) - { - PluginLog.Information($"Loaded weapon from fixed design for {identifier}."); - weapon = slot switch - { - 0 => save.MainHand.Value, - 1 => save.OffHand.Value, - _ => weapon, - }; - } - else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2)) - { - PluginLog.Information($"Loaded weapon from current design for {identifier}."); - switch (slot) - { - //case 0: - // save2.Data.MainHand = new CharacterWeapon(weapon); - // break; - //case 1: - // save.OffHand = new CharacterWeapon(weapon); - // break; - } - } - } - catch (Exception e) - { - PluginLog.Error($"Error on loading new weapon:\n{e}"); - } - - // First call the regular function. - _loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4); - // If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object. - if (oldWeapon != weapon) - _loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4); - // If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems. - else if (slot != 1 && character.OffHand.Value == 0) - _loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4); - } - - // Load a specific weapon for a character by its data and slot. - public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon) - { - switch (slot) - { - case EquipSlot.MainHand: - LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0); - return; - case EquipSlot.OffHand: - LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0); - return; - case EquipSlot.BothHand: - LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0); - LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0); - return; - // function can also be called with '2', but does not seem to ever be. - } - } - - // Load specific Main- and Offhand weapons. - public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off) - { - LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 0, 0, 1, 0); - LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 0, 0, 1, 0); - } -} - public unsafe partial class RedrawManager : IDisposable { private readonly FixedDesigns _fixedDesigns; @@ -191,8 +36,8 @@ public unsafe partial class RedrawManager : IDisposable public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations) { SignatureHelper.Initialise(this); - Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw; - Glamourer.Penumbra.CreatedCharacterBase += OnCharacterRedrawFinished; + Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw; + Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished; _fixedDesigns = fixedDesigns; _currentManipulations = currentManipulations; _flagSlotForUpdateHook.Enable(); @@ -205,8 +50,8 @@ public unsafe partial class RedrawManager : IDisposable _flagSlotForUpdateHook.Dispose(); _loadWeaponHook.Dispose(); _changeJobHook.Dispose(); - Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw; - Glamourer.Penumbra.CreatedCharacterBase -= OnCharacterRedrawFinished; + Glamourer.Penumbra.CreatingCharacterBase.Event -= OnCharacterRedraw; + Glamourer.Penumbra.CreatedCharacterBase.Event -= OnCharacterRedrawFinished; } private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip) @@ -225,23 +70,23 @@ public unsafe partial class RedrawManager : IDisposable // Apply customization if they correspond and there is customization to apply. var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData); if (gameObjectCustomize.Equals(customize)) - customize.Load(save.Data.Customize); + customize.Load(save!.Data.Customize); // Compare game object equip data against draw object equip data for transformations. // Apply each piece of equip that should be applied if they correspond. var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData); if (gameObjectEquip.Equals(equip)) { - var saveEquip = save.Data.Equipment; + var saveEquip = save!.Data.Equipment; foreach (var slot in EquipSlotExtensions.EqdpSlots) { - (var _, equip[slot]) = - Glamourer.RestrictedGear.ResolveRestricted(true ? equip[slot] : saveEquip[slot], slot, customize.Race, customize.Gender); + (_, equip[slot]) = + Glamourer.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender); } } } - private void OnCharacterRedraw(IntPtr gameObject, IntPtr modelId, IntPtr customize, IntPtr equipData) + private void OnCharacterRedraw(IntPtr gameObject, string collection, IntPtr modelId, IntPtr customize, IntPtr equipData) { try { @@ -254,7 +99,7 @@ public unsafe partial class RedrawManager : IDisposable } } - private static void OnCharacterRedrawFinished(IntPtr gameObject, IntPtr drawObject) + private static void OnCharacterRedrawFinished(IntPtr gameObject, string collection, IntPtr drawObject) { //SetVisor((Human*)drawObject, true); if (Glamourer.Models.FromCharacterBase((CharacterBase*)drawObject, out var data)) @@ -262,42 +107,4 @@ public unsafe partial class RedrawManager : IDisposable else PluginLog.Information($"Key: {Glamourer.Models.KeyFromCharacterBase((CharacterBase*)drawObject):X16}"); } - - // Update - public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment); - - [Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")] - private readonly ChangeCustomizeDelegate _changeCustomize = null!; - - public bool UpdateCustomize(Actor actor, Customize customize) - { - if (!actor.Valid || !actor.DrawObject.Valid) - return false; - - var d = actor.DrawObject; - if (NeedsRedraw(d.Customize, customize)) - { - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, true); - return true; - } - - return _changeCustomize(d.Pointer, (byte*)customize.Data, 1); - } - - public static bool NeedsRedraw(Customize lhs, Customize rhs) - => lhs.Race != rhs.Race || lhs.Gender != rhs.Gender || lhs.Face != rhs.Face || lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan; - - - public static void SetVisor(Human* data, bool on) - { - if (data == null) - return; - - var flags = &data->CharacterBase.UnkFlags_01; - var state = (*flags & 0x40) != 0; - if (state == on) - return; - - *flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80); - } -} +} \ No newline at end of file diff --git a/Glamourer/Saves/Design.cs b/Glamourer/Saves/Design.cs new file mode 100644 index 0000000..7570c34 --- /dev/null +++ b/Glamourer/Saves/Design.cs @@ -0,0 +1,212 @@ +using System; +using Glamourer.Customization; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; + +namespace Glamourer.Saves; + +public class EquipmentDesign +{ + private Data _data = default; + + // @formatter:off + //public Slot Head => new(ref _data, 0); + //public Slot Body => new(ref _data, 1); + //public Slot Hands => new(ref _data, 2); + //public Slot Legs => new(ref _data, 3); + //public Slot Feet => new(ref _data, 4); + //public Slot Ears => new(ref _data, 5); + //public Slot Neck => new(ref _data, 6); + //public Slot Wrist => new(ref _data, 7); + //public Slot RFinger => new(ref _data, 8); + //public Slot LFinger => new(ref _data, 9); + //public Slot MainHand => new(ref _data, 10); + //public Slot OffHand => new(ref _data, 11); + //// @formatter:on + // + //public Slot this[EquipSlot slot] + // => new(ref _data, (int)slot.ToIndex()); + // + //public Slot this[int idx] + // => idx is >= 0 and < Data.NumEquipment ? new Slot(ref _data, idx) : throw new IndexOutOfRangeException(); + + public unsafe struct Data + { + public const int NumEquipment = 12; + + public fixed uint Ids[NumEquipment]; + public fixed byte Stains[NumEquipment]; + public ushort Flags; + public ushort StainFlags; + } + + //public ref struct Slot + //{ + // private readonly ref Data _data; + // private readonly int _index; + // private readonly ushort _flag; + // + // public Slot(ref Data data, int idx) + // { + // _data = data; + // _index = idx; + // _flag = (ushort)(1 << idx); + // } + // + // public unsafe uint ItemId + // { + // get => _data.Ids[_index]; + // set => _data.Ids[_index] = value; + // } + // + // public unsafe StainId StainId + // { + // get => _data.Stains[_index]; + // set => _data.Stains[_index] = value.Value; + // } + // + // public bool ApplyItem + // { + // get => (_data.Flags & _flag) != 0; + // set => _data.Flags = (ushort)(value ? _data.Flags | _flag : _data.Flags & ~_flag); + // } + // + // public bool ApplyStain + // { + // get => (_data.StainFlags & _flag) != 0; + // set => _data.StainFlags = (ushort)(value ? _data.StainFlags | _flag : _data.StainFlags & ~_flag); + // } + //} +} + +public class HumanDesign +{ + public unsafe struct Data + { + public CustomizeData Values; + public CustomizeFlag Flag; + } + + //public ref struct Choice where T : unmanaged + //{ + // private readonly ref Data _data; + // private readonly CustomizeFlag _flag; + // + // public Choice(ref Data data, CustomizeFlag flag) + // { + // _data = data; + // _flag = flag; + // } + // + // public bool ApplyChoice + // { + // get => _data.Flag.HasFlag(_flag); + // set => _data.Flag = value ? _data.Flag | _flag : _data.Flag & ~_flag; + // } + //} +} + +public class Design +{ + public string Name { get; private set; } + public string Description { get; private set; } + + public DateTimeOffset CreationDate { get; } + public DateTimeOffset LastUpdateDate { get; private set; } + + public bool ReadOnly { get; private set; } + + public EquipmentDesign? Equipment; + public HumanDesign? Customize; + + public string WriteJson() + { + return string.Empty; + } +} + +public struct DesignSaveV1 +{ + public string Name; + public string Description; + + public ulong CreationDate; + public ulong LastUpdateDate; + + public EquipmentPiece Head; + public EquipmentPiece Body; + public EquipmentPiece Hands; + public EquipmentPiece Legs; + public EquipmentPiece Feet; + public EquipmentPiece Ears; + public EquipmentPiece Neck; + public EquipmentPiece Wrists; + public EquipmentPiece LFinger; + public EquipmentPiece RFinger; + + public EquipmentPiece MainHand; + public EquipmentPiece OffHand; + + public CustomizationChoice ModelId; + public CustomizationChoice Race; + public CustomizationChoice Gender; + public CustomizationChoice BodyType; + public CustomizationChoice Height; + public CustomizationChoice Clan; + public CustomizationChoice Face; + public CustomizationChoice Hairstyle; + public CustomizationChoice Highlights; + public CustomizationChoice SkinColor; + public CustomizationChoice EyeColorRight; + public CustomizationChoice HairColor; + public CustomizationChoice HighlightsColor; + public CustomizationChoice FacialFeature1; + public CustomizationChoice FacialFeature2; + public CustomizationChoice FacialFeature3; + public CustomizationChoice FacialFeature4; + public CustomizationChoice FacialFeature5; + public CustomizationChoice FacialFeature6; + public CustomizationChoice FacialFeature7; + public CustomizationChoice LegacyTattoo; + public CustomizationChoice TattooColor; + public CustomizationChoice Eyebrows; + public CustomizationChoice EyeColorLeft; + public CustomizationChoice EyeShape; + public CustomizationChoice SmallIris; + public CustomizationChoice Nose; + public CustomizationChoice Jaw; + public CustomizationChoice Mouth; + public CustomizationChoice Lipstick; + public CustomizationChoice MuscleMass; + public CustomizationChoice TailShape; + public CustomizationChoice BustSize; + public CustomizationChoice FacePaint; + public CustomizationChoice FacePaintReversed; + public CustomizationChoice FacePaintColor; + + public bool ReadOnly; + + public override string ToString() + => Name; +} + +public struct EquipmentPiece +{ + public uint Item; + public bool ApplyItem; + public StainId Stain; + public bool ApplyStain; +} + +public struct CustomizationChoice +{ + public CustomizeValue Value; + public bool Apply; +} + +public struct CustomizationChoice where T : struct +{ + public T Value; + public bool Apply; +} diff --git a/Glamourer/State/ApplicationFlags.cs b/Glamourer/State/ApplicationFlags.cs new file mode 100644 index 0000000..e0d1605 --- /dev/null +++ b/Glamourer/State/ApplicationFlags.cs @@ -0,0 +1,75 @@ +using Penumbra.GameData.Enums; +using System; + +namespace Glamourer.State; + +[Flags] +public enum ApplicationFlags : uint +{ + Customizations = 0x000001, + MainHand = 0x000002, + OffHand = 0x000004, + Head = 0x000008, + Body = 0x000010, + Hands = 0x000020, + Legs = 0x000040, + Feet = 0x000080, + Ears = 0x000100, + Neck = 0x000200, + Wrist = 0x000400, + RFinger = 0x000800, + LFinger = 0x001000, + SetVisor = 0x002000, + Visor = 0x004000, + SetWeapon = 0x008000, + Weapon = 0x010000, + SetWet = 0x020000, + Wet = 0x040000, +} + +public static class ApplicationFlagExtensions +{ + public static ApplicationFlags ToApplicationFlag(this EquipSlot slot) + => slot switch + { + EquipSlot.MainHand => ApplicationFlags.MainHand, + EquipSlot.OffHand => ApplicationFlags.OffHand, + EquipSlot.Head => ApplicationFlags.Head, + EquipSlot.Body => ApplicationFlags.Body, + EquipSlot.Hands => ApplicationFlags.Hands, + EquipSlot.Legs => ApplicationFlags.Legs, + EquipSlot.Feet => ApplicationFlags.Feet, + EquipSlot.Ears => ApplicationFlags.Ears, + EquipSlot.Neck => ApplicationFlags.Neck, + EquipSlot.Wrists => ApplicationFlags.Wrist, + EquipSlot.RFinger => ApplicationFlags.RFinger, + EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand, + EquipSlot.LFinger => ApplicationFlags.LFinger, + EquipSlot.HeadBody => ApplicationFlags.Body, + EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body, + EquipSlot.LegsFeet => ApplicationFlags.Legs, + EquipSlot.FullBody => ApplicationFlags.Body, + EquipSlot.BodyHands => ApplicationFlags.Body, + EquipSlot.BodyLegsFeet => ApplicationFlags.Body, + EquipSlot.ChestHands => ApplicationFlags.Body, + _ => 0, + }; + + public static EquipSlot ToSlot(this ApplicationFlags flags) + => flags switch + { + ApplicationFlags.MainHand => EquipSlot.MainHand, + ApplicationFlags.OffHand => EquipSlot.OffHand, + ApplicationFlags.Head => EquipSlot.Head, + ApplicationFlags.Body => EquipSlot.Body, + ApplicationFlags.Hands => EquipSlot.Hands, + ApplicationFlags.Legs => EquipSlot.Legs, + ApplicationFlags.Feet => EquipSlot.Feet, + ApplicationFlags.Ears => EquipSlot.Ears, + ApplicationFlags.Neck => EquipSlot.Neck, + ApplicationFlags.Wrist => EquipSlot.Wrists, + ApplicationFlags.RFinger => EquipSlot.RFinger, + ApplicationFlags.LFinger => EquipSlot.LFinger, + _ => EquipSlot.Unknown, + }; +} diff --git a/Glamourer/State/CharacterSave.cs b/Glamourer/State/CharacterSave.cs index ab90018..6f09667 100644 --- a/Glamourer/State/CharacterSave.cs +++ b/Glamourer/State/CharacterSave.cs @@ -1,17 +1,12 @@ using System; -using System.Linq; using System.Runtime.InteropServices; -using System.Security.AccessControl; -using Dalamud.Utility; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; using Glamourer.Interop; -using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using DrawObject = Glamourer.Interop.DrawObject; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; using Functions = Penumbra.GameData.Util.Functions; namespace Glamourer.State; @@ -33,76 +28,7 @@ public class CharacterSaveConverter : JsonConverter } } -[Flags] -public enum ApplicationFlags : uint -{ - Customizations = 0x000001, - MainHand = 0x000002, - OffHand = 0x000004, - Head = 0x000008, - Body = 0x000010, - Hands = 0x000020, - Legs = 0x000040, - Feet = 0x000080, - Ears = 0x000100, - Neck = 0x000200, - Wrist = 0x000400, - RFinger = 0x000800, - LFinger = 0x001000, - SetVisor = 0x002000, - Visor = 0x004000, - SetWeapon = 0x008000, - Weapon = 0x010000, - SetWet = 0x020000, - Wet = 0x040000, -} -public static class ApplicationFlagExtensions -{ - public static ApplicationFlags ToApplicationFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => ApplicationFlags.MainHand, - EquipSlot.OffHand => ApplicationFlags.OffHand, - EquipSlot.Head => ApplicationFlags.Head, - EquipSlot.Body => ApplicationFlags.Body, - EquipSlot.Hands => ApplicationFlags.Hands, - EquipSlot.Legs => ApplicationFlags.Legs, - EquipSlot.Feet => ApplicationFlags.Feet, - EquipSlot.Ears => ApplicationFlags.Ears, - EquipSlot.Neck => ApplicationFlags.Neck, - EquipSlot.Wrists => ApplicationFlags.Wrist, - EquipSlot.RFinger => ApplicationFlags.RFinger, - EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand, - EquipSlot.LFinger => ApplicationFlags.LFinger, - EquipSlot.HeadBody => ApplicationFlags.Body, - EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body, - EquipSlot.LegsFeet => ApplicationFlags.Legs, - EquipSlot.FullBody => ApplicationFlags.Body, - EquipSlot.BodyHands => ApplicationFlags.Body, - EquipSlot.BodyLegsFeet => ApplicationFlags.Body, - EquipSlot.ChestHands => ApplicationFlags.Body, - _ => 0, - }; - - public static EquipSlot ToSlot(this ApplicationFlags flags) - => flags switch - { - ApplicationFlags.MainHand => EquipSlot.MainHand, - ApplicationFlags.OffHand => EquipSlot.OffHand, - ApplicationFlags.Head => EquipSlot.Head, - ApplicationFlags.Body => EquipSlot.Body, - ApplicationFlags.Hands => EquipSlot.Hands, - ApplicationFlags.Legs => EquipSlot.Legs, - ApplicationFlags.Feet => EquipSlot.Feet, - ApplicationFlags.Ears => EquipSlot.Ears, - ApplicationFlags.Neck => EquipSlot.Neck, - ApplicationFlags.Wrist => EquipSlot.Wrists, - ApplicationFlags.RFinger => EquipSlot.RFinger, - ApplicationFlags.LFinger => EquipSlot.LFinger, - _ => EquipSlot.Unknown, - }; -} [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct CharacterData @@ -206,19 +132,6 @@ public struct CharacterData } } -public interface ICharacterData -{ - public ref CharacterData Data { get; } - - //public bool ApplyModel(); - //public bool ApplyCustomize(Customize target); - //public bool ApplyWeapon(ref CharacterWeapon weapon, bool mainHand, bool offHand); - //public bool ApplyGear(ref CharacterArmor armor, EquipSlot slot); - //public unsafe bool ApplyWetness(CharacterBase* drawObject); - //public unsafe bool ApplyVisorState(CharacterBase* drawObject); - //public unsafe bool ApplyWeaponState(CharacterBase* drawObject); -} - [JsonConverter(typeof(CharacterSaveConverter))] public class CharacterSave { diff --git a/Glamourer/State/CurrentDesign.cs b/Glamourer/State/CurrentDesign.cs index d846d83..c85cedd 100644 --- a/Glamourer/State/CurrentDesign.cs +++ b/Glamourer/State/CurrentDesign.cs @@ -1,12 +1,10 @@ -using System.Collections.Generic; -using System.Linq; -using Glamourer.Customization; -using Glamourer.Interop; +using Glamourer.Interop; +using Penumbra.Api.Enums; using Penumbra.GameData.Enums; namespace Glamourer.State; -public unsafe class CurrentDesign : ICharacterData +public unsafe class CurrentDesign : IDesign { public ref CharacterData Data => ref _drawData; @@ -28,6 +26,39 @@ public unsafe class CurrentDesign : ICharacterData _drawData = _initialData.Clone(); } + public void Reset() + => _drawData = _initialData; + + public void ApplyToActor(Actor actor) + { + if (!actor) + return; + + void Redraw() + => Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); + + if (_drawData.ModelId != actor.ModelId) + { + Redraw(); + return; + } + + var customize1 = _drawData.Customize; + var customize2 = actor.Customize; + if (RedrawManager.NeedsRedraw(customize1, customize2)) + { + Redraw(); + return; + } + + Glamourer.RedrawManager.UpdateCustomize(actor, customize2); + foreach (var slot in EquipSlotExtensions.EqdpSlots) + Glamourer.RedrawManager.ChangeEquip(actor, slot, actor.Equip[slot]); + Glamourer.RedrawManager.LoadWeapon(actor, actor.MainHand, actor.OffHand); + if (actor.IsHuman && actor.DrawObject) + RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled); + } + public void Update(Actor actor) { if (!actor) diff --git a/Glamourer/State/IDesign.cs b/Glamourer/State/IDesign.cs new file mode 100644 index 0000000..8e0e736 --- /dev/null +++ b/Glamourer/State/IDesign.cs @@ -0,0 +1,10 @@ +using Glamourer.Interop; + +namespace Glamourer.State; + +public interface IDesign +{ + public ref CharacterData Data { get; } + + public void ApplyToActor(Actor a); +} diff --git a/Glamourer/Util/CustomizeExtensions.cs b/Glamourer/Util/CustomizeExtensions.cs index a900e6b..f0108dc 100644 --- a/Glamourer/Util/CustomizeExtensions.cs +++ b/Glamourer/Util/CustomizeExtensions.cs @@ -90,18 +90,18 @@ public static unsafe class CustomizeExtensions customize.Load(newCustomize); } - public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizationId id, CustomizationByteValue value) + public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizeIndex index, CustomizeValue value) { - switch (id) + switch (index) { - case CustomizationId.Race: return customize.ChangeRace(equip, (SubRace)value.Value); - case CustomizationId.Gender: return customize.ChangeGender(equip, (Gender)value.Value); + case CustomizeIndex.Race: return customize.ChangeRace(equip, (SubRace)value.Value); + case CustomizeIndex.Gender: return customize.ChangeGender(equip, (Gender)value.Value); } - if (customize[id] == value) + if (customize[index] == value) return false; - customize[id] = value; + customize[index] = value; return true; } @@ -109,21 +109,20 @@ public static unsafe class CustomizeExtensions private static void FixUpAttributes(Customize customize) { var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); - foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId))) + foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex))) { 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; + case CustomizeIndex.Race: break; + case CustomizeIndex.Clan: break; + case CustomizeIndex.BodyType: break; + case CustomizeIndex.Gender: break; + case CustomizeIndex.Highlights: break; + case CustomizeIndex.Face: break; default: var count = set.Count(id); - if (set.DataByValue(id, customize[id], out _) < 0) - customize[id] = count == 0 ? CustomizationByteValue.Zero : set.Data(id, 0).Value; + if (set.DataByValue(id, customize[id], out _, customize.Face) < 0) + customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value; break; } }