From a982c0a1c1e456f34c4e28e925a622c03a2b13d7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Dec 2023 15:06:56 +0100 Subject: [PATCH 01/31] Update gamedata and services. --- .../Customization/CustomizationManager.cs | 4 +- .../Customization/CustomizationNpcOptions.cs | 316 +----------------- .../Customization/CustomizationOptions.cs | 8 +- Glamourer.GameData/Customization/Customize.cs | 5 +- .../Customization/CustomizeIndex.cs | 4 +- .../Customization/DatCharacterFile.cs | 9 +- .../Customization/NpcCustomizeSet.cs | 282 ++++++++++++++++ Glamourer.GameData/Customization/NpcData.cs | 89 +++++ Glamourer/Api/GlamourerIpc.cs | 8 +- Glamourer/Automation/AutoDesignApplier.cs | 18 +- Glamourer/Automation/AutoDesignManager.cs | 20 +- Glamourer/Automation/FixedDesignMigrator.cs | 8 +- Glamourer/Designs/DesignBase.cs | 8 +- Glamourer/Designs/DesignBase64Migration.cs | 16 +- Glamourer/Designs/DesignConverter.cs | 6 +- Glamourer/Designs/DesignData.cs | 39 ++- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Glamourer.cs | 21 +- .../Customization/CustomizationDrawer.Icon.cs | 9 +- .../Gui/Customization/CustomizationDrawer.cs | 6 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 39 +-- .../Gui/Equipment/GlamourerColorCombo.cs | 11 +- Glamourer/Gui/Equipment/ItemCombo.cs | 8 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 8 +- Glamourer/Gui/MainWindow.cs | 7 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 6 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 27 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 6 +- .../Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 32 +- .../Tabs/AutomationTab/IdentifierDrawer.cs | 23 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 12 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 2 +- .../Gui/Tabs/DebugTab/ActorServicePanel.cs | 28 +- .../DebugTab/CustomizationServicePanel.cs | 8 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 5 +- .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 2 +- .../Gui/Tabs/DebugTab/IdentifierPanel.cs | 17 +- .../Gui/Tabs/DebugTab/ItemManagerPanel.cs | 14 +- .../Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 2 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 10 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 2 +- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 6 +- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- .../Gui/Tabs/DebugTab/RestrictedGearPanel.cs | 2 +- .../Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 2 +- Glamourer/Gui/Tabs/NpcCombo.cs | 33 +- Glamourer/Gui/Tabs/SettingsTab.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 63 ++-- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 15 +- Glamourer/Gui/UiHelpers.cs | 2 +- Glamourer/Interop/ChangeCustomizeService.cs | 9 +- Glamourer/Interop/CharaFile/CmaFile.cs | 12 +- Glamourer/Interop/ContextMenuService.cs | 16 +- Glamourer/Interop/ImportService.cs | 2 +- Glamourer/Interop/ObjectManager.cs | 22 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 4 +- Glamourer/Interop/WeaponService.cs | 4 +- Glamourer/Services/CommandService.cs | 13 +- Glamourer/Services/CustomizationService.cs | 109 +++--- Glamourer/Services/DalamudServices.cs | 61 +--- Glamourer/Services/IGamePathParser.cs | 6 + Glamourer/Services/ItemManager.cs | 53 ++- Glamourer/Services/ServiceManager.cs | 67 ++-- Glamourer/Services/ServiceWrapper.cs | 93 ------ Glamourer/State/FunModule.cs | 21 +- Glamourer/State/StateApplier.cs | 2 +- Glamourer/State/StateEditor.cs | 4 +- Glamourer/State/StateListener.cs | 44 +-- Glamourer/State/StateManager.cs | 20 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 6 +- Glamourer/Unlocks/ItemUnlockManager.cs | 19 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 74 files changed, 907 insertions(+), 960 deletions(-) create mode 100644 Glamourer.GameData/Customization/NpcCustomizeSet.cs create mode 100644 Glamourer.GameData/Customization/NpcData.cs create mode 100644 Glamourer/Services/IGamePathParser.cs delete mode 100644 Glamourer/Services/ServiceWrapper.cs diff --git a/Glamourer.GameData/Customization/CustomizationManager.cs b/Glamourer.GameData/Customization/CustomizationManager.cs index 9221838..b02498c 100644 --- a/Glamourer.GameData/Customization/CustomizationManager.cs +++ b/Glamourer.GameData/Customization/CustomizationManager.cs @@ -12,9 +12,9 @@ public class CustomizationManager : ICustomizationManager private CustomizationManager() { } - public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log) + public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { - _options ??= new CustomizationOptions(textures, gameData, log); + _options ??= new CustomizationOptions(textures, gameData, log, npcCustomizeSet); return new CustomizationManager(); } diff --git a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs b/Glamourer.GameData/Customization/CustomizationNpcOptions.cs index 254af2e..043ccf8 100644 --- a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationNpcOptions.cs @@ -1,240 +1,13 @@ -using System; -using Dalamud.Plugin.Services; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; -using OtterGui; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; +using Penumbra.GameData.Enums; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using Dalamud.Utility; -using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Penumbra.GameData; namespace Glamourer.Customization; public static class CustomizationNpcOptions { - public unsafe struct NpcData + public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) { - public string Name; - public Customize Customize; - private fixed byte _equip[40]; - public CharacterWeapon Mainhand; - public CharacterWeapon Offhand; - public uint Id; - public bool VisorToggled; - public ObjectKind Kind; - - public ReadOnlySpan Equip - { - get - { - fixed (byte* ptr = _equip) - { - return new ReadOnlySpan((CharacterArmor*)ptr, 10); - } - } - } - - public string WriteGear() - { - var sb = new StringBuilder(128); - var span = Equip; - for (var i = 0; i < 10; ++i) - { - sb.Append(span[i].Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(span[i].Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(span[i].Stain.Id.ToString("D3")); - sb.Append(", "); - } - - sb.Append(Mainhand.Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Type.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Mainhand.Stain.Id.ToString("D4")); - sb.Append(", "); - sb.Append(Offhand.Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Type.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Offhand.Stain.Id.ToString("D3")); - return sb.ToString(); - } - - internal void Set(int idx, uint value) - { - fixed (byte* ptr = _equip) - { - ((uint*)ptr)[idx] = value; - } - } - - public bool DataEquals(in NpcData other) - { - if (VisorToggled != other.VisorToggled) - return false; - - if (!Customize.Equals(other.Customize)) - return false; - - if (!Mainhand.Equals(other.Mainhand)) - return false; - - if (!Offhand.Equals(other.Offhand)) - return false; - - fixed (byte* ptr1 = _equip, ptr2 = other._equip) - { - return new ReadOnlySpan(ptr1, 40).SequenceEqual(new ReadOnlySpan(ptr2, 40)); - } - } - } - - private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) - { - data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); - data.VisorToggled = row.Visor; - } - - public static unsafe IReadOnlyList CreateNpcData(IReadOnlyDictionary eNpcs, - IReadOnlyDictionary bnpcNames, IObjectIdentifier identifier, IDataManager data) - { - var enpcSheet = data.GetExcelSheet()!; - var bnpcSheet = data.GetExcelSheet()!; - var list = new List(eNpcs.Count + (int)bnpcSheet.RowCount); - foreach (var (id, name) in eNpcs) - { - var row = enpcSheet.GetRow(id); - if (row == null || name.IsNullOrWhitespace()) - continue; - - var (valid, customize) = FromEnpcBase(row); - if (!valid) - continue; - - var ret = new NpcData - { - Name = name, - Customize = customize, - Id = id, - Kind = ObjectKind.EventNpc, - }; - - if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) - { - ApplyNpcEquip(ref ret, equip); - } - else - { - ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); - ret.VisorToggled = row.Visor; - } - - list.Add(ret); - } - - foreach (var baseRow in bnpcSheet) - { - if (baseRow.ModelChara.Value!.Type != 1) - continue; - - var bnpcNameIds = identifier.GetBnpcNames(baseRow.RowId); - if (bnpcNameIds.Count == 0) - continue; - - var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); - if (!valid) - continue; - - var equip = baseRow.NpcEquip.Value!; - var ret = new NpcData - { - Customize = customize, - Id = baseRow.RowId, - Kind = ObjectKind.BattleNpc, - }; - ApplyNpcEquip(ref ret, equip); - foreach (var bnpcNameId in bnpcNameIds) - { - if (bnpcNames.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace()) - list.Add(ret with { Name = name }); - } - } - - var groups = list.GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); - list.Clear(); - foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) - { - for (var i = 0; i < duplicates.Count; ++i) - { - var current = duplicates[i]; - var add = true; - for (var j = 0; j < i; ++j) - { - if (current.DataEquals(duplicates[j])) - { - duplicates.RemoveAt(i--); - break; - } - } - } - - if (duplicates.Count == 1) - list.Add(duplicates[0]); - else - list.AddRange(duplicates - .Select(duplicate => duplicate with { Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" })); - } - - var lastWeird = list.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); - if (lastWeird != -1) - { - list.AddRange(list.Take(lastWeird)); - list.RemoveRange(0, lastWeird); - } - - return list; - } - - - public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, - ExcelSheet bNpc, ExcelSheet eNpc) - { - var customizes = bNpc.SelectWhere(FromBnpcCustomize) - .Concat(eNpc.SelectWhere(FromEnpcBase)).ToList(); - var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>(); var customizeIndices = new[] { @@ -251,7 +24,7 @@ public static class CustomizationNpcOptions CustomizeIndex.EyeColorRight, }; - foreach (var customize in customizes) + foreach (var customize in npcCustomizeSet.Select(s => s.Customize)) { var set = sets[CustomizationOptions.ToIndex(customize.Clan, customize.Gender)]; foreach (var customizeIndex in customizeIndices) @@ -265,7 +38,7 @@ public static class CustomizationNpcOptions if (!dict.TryGetValue((set.Clan, set.Gender), out var npcSet)) { - npcSet = new HashSet<(CustomizeIndex, CustomizeValue)> { (customizeIndex, value) }; + npcSet = [(customizeIndex, value)]; dict.Add((set.Clan, set.Gender), npcSet); } else @@ -278,85 +51,4 @@ public static class CustomizationNpcOptions return dict.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<(CustomizeIndex, CustomizeValue)>)kvp.Value.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray()); } - - private static (bool, Customize) FromBnpcCustomize(BNpcCustomize bnpcCustomize) - { - var customize = new Customize(); - customize.Data.Set(0, (byte)bnpcCustomize.Race.Row); - customize.Data.Set(1, bnpcCustomize.Gender); - customize.Data.Set(2, bnpcCustomize.BodyType); - customize.Data.Set(3, bnpcCustomize.Height); - customize.Data.Set(4, (byte)bnpcCustomize.Tribe.Row); - customize.Data.Set(5, bnpcCustomize.Face); - customize.Data.Set(6, bnpcCustomize.HairStyle); - customize.Data.Set(7, bnpcCustomize.HairHighlight); - customize.Data.Set(8, bnpcCustomize.SkinColor); - customize.Data.Set(9, bnpcCustomize.EyeHeterochromia); - customize.Data.Set(10, bnpcCustomize.HairColor); - customize.Data.Set(11, bnpcCustomize.HairHighlightColor); - customize.Data.Set(12, bnpcCustomize.FacialFeature); - customize.Data.Set(13, bnpcCustomize.FacialFeatureColor); - customize.Data.Set(14, bnpcCustomize.Eyebrows); - customize.Data.Set(15, bnpcCustomize.EyeColor); - customize.Data.Set(16, bnpcCustomize.EyeShape); - customize.Data.Set(17, bnpcCustomize.Nose); - customize.Data.Set(18, bnpcCustomize.Jaw); - customize.Data.Set(19, bnpcCustomize.Mouth); - customize.Data.Set(20, bnpcCustomize.LipColor); - customize.Data.Set(21, bnpcCustomize.BustOrTone1); - customize.Data.Set(22, bnpcCustomize.ExtraFeature1); - customize.Data.Set(23, bnpcCustomize.ExtraFeature2OrBust); - customize.Data.Set(24, bnpcCustomize.FacePaint); - customize.Data.Set(25, bnpcCustomize.FacePaintColor); - - if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); - - return (true, customize); - } - - private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase) - { - if (enpcBase.ModelChara.Value?.Type != 1) - return (false, Customize.Default); - - var customize = new Customize(); - customize.Data.Set(0, (byte)enpcBase.Race.Row); - customize.Data.Set(1, enpcBase.Gender); - customize.Data.Set(2, enpcBase.BodyType); - customize.Data.Set(3, enpcBase.Height); - customize.Data.Set(4, (byte)enpcBase.Tribe.Row); - customize.Data.Set(5, enpcBase.Face); - customize.Data.Set(6, enpcBase.HairStyle); - customize.Data.Set(7, enpcBase.HairHighlight); - customize.Data.Set(8, enpcBase.SkinColor); - customize.Data.Set(9, enpcBase.EyeHeterochromia); - customize.Data.Set(10, enpcBase.HairColor); - customize.Data.Set(11, enpcBase.HairHighlightColor); - customize.Data.Set(12, enpcBase.FacialFeature); - customize.Data.Set(13, enpcBase.FacialFeatureColor); - customize.Data.Set(14, enpcBase.Eyebrows); - customize.Data.Set(15, enpcBase.EyeColor); - customize.Data.Set(16, enpcBase.EyeShape); - customize.Data.Set(17, enpcBase.Nose); - customize.Data.Set(18, enpcBase.Jaw); - customize.Data.Set(19, enpcBase.Mouth); - customize.Data.Set(20, enpcBase.LipColor); - customize.Data.Set(21, enpcBase.BustOrTone1); - customize.Data.Set(22, enpcBase.ExtraFeature1); - customize.Data.Set(23, enpcBase.ExtraFeature2OrBust); - customize.Data.Set(24, enpcBase.FacePaint); - customize.Data.Set(25, enpcBase.FacePaintColor); - - if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); - - return (true, customize); - } } diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index fdbac65..3580ef8 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -62,7 +62,7 @@ public partial class CustomizationOptions public string GetName(CustomName name) => _names[(int)name]; - internal CustomizationOptions(ITextureProvider textures, IDataManager gameData, IPluginLog log) + internal CustomizationOptions(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { var tmp = new TemporaryData(gameData, this, log); _icons = new IconStorage(textures, gameData); @@ -73,7 +73,7 @@ public partial class CustomizationOptions _customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender); } - tmp.SetNpcData(_customizationSets); + tmp.SetNpcData(_customizationSets, npcCustomizeSet); } // Obtain localized names of customization options and race names from the game data. @@ -163,9 +163,9 @@ public partial class CustomizationOptions return set; } - public void SetNpcData(CustomizationSet[] sets) + public void SetNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) { - var data = CustomizationNpcOptions.CreateNpcData(sets, _bnpcCustomize, _enpcBase); + var data = CustomizationNpcOptions.CreateNpcData(sets, npcCustomizeSet); foreach (var set in sets) { if (data.TryGetValue((set.Clan, set.Gender), out var npcData)) diff --git a/Glamourer.GameData/Customization/Customize.cs b/Glamourer.GameData/Customization/Customize.cs index 90dac63..b19ef22 100644 --- a/Glamourer.GameData/Customization/Customize.cs +++ b/Glamourer.GameData/Customization/Customize.cs @@ -1,13 +1,14 @@ using System; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Customization; public unsafe struct Customize { - public Penumbra.GameData.Structs.CustomizeData Data; + public CustomizeArray Data; - public Customize(in Penumbra.GameData.Structs.CustomizeData data) + public Customize(in CustomizeArray data) { Data = data.Clone(); } diff --git a/Glamourer.GameData/Customization/CustomizeIndex.cs b/Glamourer.GameData/Customization/CustomizeIndex.cs index 38aca21..cbd22ed 100644 --- a/Glamourer.GameData/Customization/CustomizeIndex.cs +++ b/Glamourer.GameData/Customization/CustomizeIndex.cs @@ -144,14 +144,14 @@ public static class CustomizationExtensions }; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index) + public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeArray 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) + public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeArray data, CustomizeIndex index, CustomizeValue value) { var (offset, mask) = index.ToByteAndMask(); return mask != 0xFF diff --git a/Glamourer.GameData/Customization/DatCharacterFile.cs b/Glamourer.GameData/Customization/DatCharacterFile.cs index 304334a..f33af69 100644 --- a/Glamourer.GameData/Customization/DatCharacterFile.cs +++ b/Glamourer.GameData/Customization/DatCharacterFile.cs @@ -3,13 +3,14 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; using Dalamud.Memory; +using Penumbra.GameData.Structs; namespace Glamourer.Customization; [StructLayout(LayoutKind.Explicit, Size = Size)] public unsafe struct DatCharacterFile { - public const int Size = 4 + 4 + 4 + 4 + Penumbra.GameData.Structs.CustomizeData.Size + 2 + 4 + 41 * 4; // 212 + public const int Size = 4 + 4 + 4 + 4 + CustomizeArray.Size + 2 + 4 + 41 * 4; // 212 [FieldOffset(0)] private fixed byte _data[Size]; @@ -27,12 +28,12 @@ public unsafe struct DatCharacterFile private readonly uint _padding = 0; [FieldOffset(16)] - private Penumbra.GameData.Structs.CustomizeData _customize; + private CustomizeArray _customize; - [FieldOffset(16 + Penumbra.GameData.Structs.CustomizeData.Size)] + [FieldOffset(16 + CustomizeArray.Size)] private ushort _voice; - [FieldOffset(16 + Penumbra.GameData.Structs.CustomizeData.Size + 2)] + [FieldOffset(16 + CustomizeArray.Size + 2)] private uint _timeStamp; [FieldOffset(Size - 41 * 4)] diff --git a/Glamourer.GameData/Customization/NpcCustomizeSet.cs b/Glamourer.GameData/Customization/NpcCustomizeSet.cs new file mode 100644 index 0000000..3ba64a0 --- /dev/null +++ b/Glamourer.GameData/Customization/NpcCustomizeSet.cs @@ -0,0 +1,282 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Dalamud.Plugin.Services; +using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Lumina.Excel.GeneratedSheets; +using OtterGui.Services; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Structs; + +namespace Glamourer.Customization; + +public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList +{ + public string Name + => nameof(NpcCustomizeSet); + + private readonly List _data = []; + + public long Time { get; private set; } + public long Memory { get; private set; } + public int TotalCount + => _data.Count; + + public Task Awaiter { get; } + + public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames) + { + var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter); + Awaiter = waitTask.ContinueWith(_ => + { + var watch = Stopwatch.StartNew(); + var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs)); + var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames)); + FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result); + Time = watch.ElapsedMilliseconds; + }); + } + + private static List CreateEnpcData(IDataManager data, DictENpc eNpcs) + { + var enpcSheet = data.GetExcelSheet()!; + var list = new List(eNpcs.Count); + + foreach (var (id, name) in eNpcs) + { + var row = enpcSheet.GetRow(id.Id); + if (row == null || name.IsNullOrWhitespace()) + continue; + + var (valid, customize) = FromEnpcBase(row); + if (!valid) + continue; + + var ret = new NpcData + { + Name = name, + Customize = customize, + Id = id, + Kind = ObjectKind.EventNpc, + }; + + if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) + { + ApplyNpcEquip(ref ret, equip); + } + else + { + ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); + ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); + ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); + ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); + ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); + ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); + ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); + ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); + ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); + ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); + ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); + ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + ret.VisorToggled = row.Visor; + } + + list.Add(ret); + } + + return list; + } + + private static List CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames) + { + var bnpcSheet = data.GetExcelSheet()!; + var list = new List((int)bnpcSheet.RowCount); + foreach (var baseRow in bnpcSheet) + { + if (baseRow.ModelChara.Value!.Type != 1) + continue; + + var bnpcNameIds = bNpcNames[baseRow.RowId]; + if (bnpcNameIds.Count == 0) + continue; + + var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); + if (!valid) + continue; + + var equip = baseRow.NpcEquip.Value!; + var ret = new NpcData + { + Customize = customize, + Id = baseRow.RowId, + Kind = ObjectKind.BattleNpc, + }; + ApplyNpcEquip(ref ret, equip); + foreach (var bnpcNameId in bnpcNameIds) + { + if (bNpcs.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace()) + list.Add(ret with { Name = name }); + } + } + + return list; + } + + private void FilterAndOrderNpcData(List eNpcEquip, List bNpcEquip) + { + _data.Clear(); + _data.EnsureCapacity(eNpcEquip.Count + bNpcEquip.Count); + var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); + foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) + { + for (var i = 0; i < duplicates.Count; ++i) + { + var current = duplicates[i]; + for (var j = 0; j < i; ++j) + { + if (current.DataEquals(duplicates[j])) + { + duplicates.RemoveAt(i--); + break; + } + } + } + + if (duplicates.Count == 1) + { + _data.Add(duplicates[0]); + Memory += 96; + } + else + { + _data.AddRange(duplicates + .Select(duplicate => duplicate with + { + Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" + })); + Memory += 96 * duplicates.Count + duplicates.Sum(d => d.Name.Length * 2); + } + } + + var lastWeird = _data.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); + if (lastWeird != -1) + { + _data.AddRange(_data.Take(lastWeird)); + _data.RemoveRange(0, lastWeird); + } + _data.TrimExcess(); + } + + private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) + { + data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); + data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); + data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); + data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); + data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); + data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); + data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); + data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + data.VisorToggled = row.Visor; + } + + private static (bool, Customize) FromBnpcCustomize(BNpcCustomize bnpcCustomize) + { + var customize = new Customize(); + customize.Data.Set(0, (byte)bnpcCustomize.Race.Row); + customize.Data.Set(1, bnpcCustomize.Gender); + customize.Data.Set(2, bnpcCustomize.BodyType); + customize.Data.Set(3, bnpcCustomize.Height); + customize.Data.Set(4, (byte)bnpcCustomize.Tribe.Row); + customize.Data.Set(5, bnpcCustomize.Face); + customize.Data.Set(6, bnpcCustomize.HairStyle); + customize.Data.Set(7, bnpcCustomize.HairHighlight); + customize.Data.Set(8, bnpcCustomize.SkinColor); + customize.Data.Set(9, bnpcCustomize.EyeHeterochromia); + customize.Data.Set(10, bnpcCustomize.HairColor); + customize.Data.Set(11, bnpcCustomize.HairHighlightColor); + customize.Data.Set(12, bnpcCustomize.FacialFeature); + customize.Data.Set(13, bnpcCustomize.FacialFeatureColor); + customize.Data.Set(14, bnpcCustomize.Eyebrows); + customize.Data.Set(15, bnpcCustomize.EyeColor); + customize.Data.Set(16, bnpcCustomize.EyeShape); + customize.Data.Set(17, bnpcCustomize.Nose); + customize.Data.Set(18, bnpcCustomize.Jaw); + customize.Data.Set(19, bnpcCustomize.Mouth); + customize.Data.Set(20, bnpcCustomize.LipColor); + customize.Data.Set(21, bnpcCustomize.BustOrTone1); + customize.Data.Set(22, bnpcCustomize.ExtraFeature1); + customize.Data.Set(23, bnpcCustomize.ExtraFeature2OrBust); + customize.Data.Set(24, bnpcCustomize.FacePaint); + customize.Data.Set(25, bnpcCustomize.FacePaintColor); + + if (customize.BodyType.Value != 1 + || !CustomizationOptions.Races.Contains(customize.Race) + || !CustomizationOptions.Clans.Contains(customize.Clan) + || !CustomizationOptions.Genders.Contains(customize.Gender)) + return (false, Customize.Default); + + return (true, customize); + } + + private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase) + { + if (enpcBase.ModelChara.Value?.Type != 1) + return (false, Customize.Default); + + var customize = new Customize(); + customize.Data.Set(0, (byte)enpcBase.Race.Row); + customize.Data.Set(1, enpcBase.Gender); + customize.Data.Set(2, enpcBase.BodyType); + customize.Data.Set(3, enpcBase.Height); + customize.Data.Set(4, (byte)enpcBase.Tribe.Row); + customize.Data.Set(5, enpcBase.Face); + customize.Data.Set(6, enpcBase.HairStyle); + customize.Data.Set(7, enpcBase.HairHighlight); + customize.Data.Set(8, enpcBase.SkinColor); + customize.Data.Set(9, enpcBase.EyeHeterochromia); + customize.Data.Set(10, enpcBase.HairColor); + customize.Data.Set(11, enpcBase.HairHighlightColor); + customize.Data.Set(12, enpcBase.FacialFeature); + customize.Data.Set(13, enpcBase.FacialFeatureColor); + customize.Data.Set(14, enpcBase.Eyebrows); + customize.Data.Set(15, enpcBase.EyeColor); + customize.Data.Set(16, enpcBase.EyeShape); + customize.Data.Set(17, enpcBase.Nose); + customize.Data.Set(18, enpcBase.Jaw); + customize.Data.Set(19, enpcBase.Mouth); + customize.Data.Set(20, enpcBase.LipColor); + customize.Data.Set(21, enpcBase.BustOrTone1); + customize.Data.Set(22, enpcBase.ExtraFeature1); + customize.Data.Set(23, enpcBase.ExtraFeature2OrBust); + customize.Data.Set(24, enpcBase.FacePaint); + customize.Data.Set(25, enpcBase.FacePaintColor); + + if (customize.BodyType.Value != 1 + || !CustomizationOptions.Races.Contains(customize.Race) + || !CustomizationOptions.Clans.Contains(customize.Clan) + || !CustomizationOptions.Genders.Contains(customize.Gender)) + return (false, Customize.Default); + + return (true, customize); + } + + public IEnumerator GetEnumerator() + => _data.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _data.Count; + + public NpcData this[int index] + => _data[index]; +} diff --git a/Glamourer.GameData/Customization/NpcData.cs b/Glamourer.GameData/Customization/NpcData.cs new file mode 100644 index 0000000..6942147 --- /dev/null +++ b/Glamourer.GameData/Customization/NpcData.cs @@ -0,0 +1,89 @@ +using System; +using System.Text; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Penumbra.GameData.Structs; + +namespace Glamourer.Customization; + +public unsafe struct NpcData +{ + public string Name; + public Customize Customize; + private fixed byte _equip[40]; + public CharacterWeapon Mainhand; + public CharacterWeapon Offhand; + public NpcId Id; + public bool VisorToggled; + public ObjectKind Kind; + + public ReadOnlySpan Equip + { + get + { + fixed (byte* ptr = _equip) + { + return new ReadOnlySpan((CharacterArmor*)ptr, 10); + } + } + } + + public string WriteGear() + { + var sb = new StringBuilder(128); + var span = Equip; + for (var i = 0; i < 10; ++i) + { + sb.Append(span[i].Set.Id.ToString("D4")); + sb.Append('-'); + sb.Append(span[i].Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(span[i].Stain.Id.ToString("D3")); + sb.Append(", "); + } + + sb.Append(Mainhand.Skeleton.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Mainhand.Weapon.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Mainhand.Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(Mainhand.Stain.Id.ToString("D4")); + sb.Append(", "); + sb.Append(Offhand.Skeleton.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Offhand.Weapon.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Offhand.Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(Offhand.Stain.Id.ToString("D3")); + return sb.ToString(); + } + + internal void Set(int idx, uint value) + { + fixed (byte* ptr = _equip) + { + ((uint*)ptr)[idx] = value; + } + } + + public bool DataEquals(in NpcData other) + { + if (VisorToggled != other.VisorToggled) + return false; + + if (!Customize.Equals(other.Customize)) + return false; + + if (!Mainhand.Equals(other.Mainhand)) + return false; + + if (!Offhand.Equals(other.Offhand)) + return false; + + fixed (byte* ptr1 = _equip, ptr2 = other._equip) + { + return new ReadOnlySpan(ptr1, 40).SequenceEqual(new ReadOnlySpan(ptr2, 40)); + } + } +} diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 398cdf2..53cfb18 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -7,10 +7,10 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; -using Glamourer.Services; using Glamourer.State; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; using Penumbra.String; namespace Glamourer.Api; @@ -22,11 +22,11 @@ public partial class GlamourerIpc : IDisposable private readonly StateManager _stateManager; private readonly ObjectManager _objects; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly DesignConverter _designConverter; private readonly AutoDesignApplier _autoDesignApplier; - public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorService actors, + public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors, DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier) { _stateManager = stateManager; @@ -142,7 +142,7 @@ public partial class GlamourerIpc : IDisposable private IEnumerable FindActors(Character? character) { - var id = _actors.AwaitedService.FromObject(character, true, true, false); + var id = _actors.FromObject(character, true, true, false); if (!id.IsValid) yield break; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index deb9e43..023b2c0 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -15,7 +15,7 @@ using Glamourer.Structs; using Glamourer.Unlocks; using OtterGui.Classes; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -28,7 +28,7 @@ public class AutoDesignApplier : IDisposable private readonly StateManager _state; private readonly JobService _jobs; private readonly EquippedGearset _equippedGearset; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly CustomizationService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; private readonly ItemUnlockManager _itemUnlocks; @@ -50,7 +50,7 @@ public class AutoDesignApplier : IDisposable } public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, - CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, + CustomizationService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset) { @@ -87,7 +87,7 @@ public class AutoDesignApplier : IDisposable if (_jobChangeState == null || !_config.EnableAutoDesigns) return; - var id = actor.GetIdentifier(_actors.AwaitedService); + var id = actor.GetIdentifier(_actors); if (id == _jobChangeState.Identifier) { var current = _jobChangeState.BaseData.Item(slot); @@ -161,7 +161,7 @@ public class AutoDesignApplier : IDisposable { foreach (var actor in data.Objects) { - var specificId = actor.GetIdentifier(_actors.AwaitedService); + var specificId = actor.GetIdentifier(_actors); if (_state.GetOrCreate(specificId, actor, out var state)) { Reduce(actor, state, newSet, false, false); @@ -203,7 +203,7 @@ public class AutoDesignApplier : IDisposable private void OnJobChange(Actor actor, Job oldJob, Job newJob) { - if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id)) + if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; if (!GetPlayerSet(id, out var set)) @@ -312,13 +312,13 @@ public class AutoDesignApplier : IDisposable if (_manager.EnabledSets.TryGetValue(identifier, out set)) return true; - identifier = _actors.AwaitedService.CreatePlayer(identifier.PlayerName, ushort.MaxValue); + identifier = _actors.CreatePlayer(identifier.PlayerName, ushort.MaxValue); return _manager.EnabledSets.TryGetValue(identifier, out set); case IdentifierType.Retainer: case IdentifierType.Npc: return _manager.EnabledSets.TryGetValue(identifier, out set); case IdentifierType.Owned: - identifier = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId); + identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId); return _manager.EnabledSets.TryGetValue(identifier, out set); default: set = null; @@ -470,7 +470,7 @@ public class AutoDesignApplier : IDisposable totalCustomizeFlags |= CustomizeFlag.Face; } - var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); var face = state.ModelData.Customize.Face; foreach (var index in Enum.GetValues()) { diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index d3fba5c..e997a78 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -17,6 +17,8 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Automation; @@ -28,17 +30,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos private readonly JobService _jobs; private readonly DesignManager _designs; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly AutomationChanged _event; private readonly DesignChanged _designEvent; - private readonly List _data = new(); - private readonly Dictionary _enabled = new(); + private readonly List _data = []; + private readonly Dictionary _enabled = []; public IReadOnlyDictionary EnabledSets => _enabled; - public AutoDesignManager(JobService jobs, ActorService actors, SaveService saveService, DesignManager designs, AutomationChanged @event, + public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event, FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent) { _jobs = jobs; @@ -419,7 +421,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos continue; } - var id = _actors.AwaitedService.FromJson(obj["Identifier"] as JObject); + var id = _actors.FromJson(obj["Identifier"] as JObject); if (!IdentifierValid(id, out var group)) { Glamourer.Messager.NotificationMessage("Skipped loading Automation Set: Invalid Identifier.", NotificationType.Warning); @@ -562,9 +564,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var name = manager.Data.ToName(identifier.Kind, identifier.DataId); var table = identifier.Kind switch { - ObjectKind.BattleNpc => manager.Data.BNpcs, + ObjectKind.BattleNpc => (IReadOnlyDictionary)manager.Data.BNpcs, ObjectKind.EventNpc => manager.Data.ENpcs, - _ => new Dictionary(), + _ => new Dictionary(), }; return table.Where(kvp => kvp.Value == name) .Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id, @@ -580,12 +582,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos }, IdentifierType.Retainer => new[] { - _actors.AwaitedService.CreateRetainer(identifier.PlayerName, + _actors.CreateRetainer(identifier.PlayerName, identifier.Retainer == ActorIdentifier.RetainerType.Mannequin ? ActorIdentifier.RetainerType.Mannequin : ActorIdentifier.RetainerType.Bell).CreatePermanent(), }, - IdentifierType.Npc => CreateNpcs(_actors.AwaitedService, identifier), + IdentifierType.Npc => CreateNpcs(_actors, identifier), _ => Array.Empty(), }; } diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 0dc20b4..d30271e 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -20,7 +20,7 @@ public class FixedDesignMigrator public FixedDesignMigrator(JobService jobs) => _jobs = jobs; - public void ConsumeMigratedData(ActorService actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager) + public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager) { if (_migratedData == null) return; @@ -35,15 +35,15 @@ public class FixedDesignMigrator var id = ActorIdentifier.Invalid; if (ByteString.FromString(data.Name, out var byteString, false)) { - id = actors.AwaitedService.CreatePlayer(byteString, ushort.MaxValue); + id = actors.CreatePlayer(byteString, ushort.MaxValue); if (!id.IsValid) - id = actors.AwaitedService.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both); + id = actors.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both); } if (!id.IsValid) { byteString = ByteString.FromSpanUnsafe("Mig Ration"u8, true, false, true); - id = actors.AwaitedService.CreatePlayer(byteString, actors.AwaitedService.Data.Worlds.First().Key); + id = actors.CreatePlayer(byteString, actors.Data.Worlds.First().Key); if (!id.IsValid) { Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error); diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index d859e8e..c9662ef 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -4,11 +4,11 @@ using Glamourer.Services; using Glamourer.Structs; using Newtonsoft.Json.Linq; using OtterGui.Classes; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using System; using System.Linq; +using Penumbra.GameData.DataContainers; namespace Glamourer.Designs; @@ -91,7 +91,7 @@ public class DesignBase return false; _designData.Customize.Load(customize); - CustomizationSet = customizationService.AwaitedService.GetList(customize.Clan, customize.Gender); + CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender); return true; } @@ -243,8 +243,8 @@ public class DesignBase private CustomizationSet SetCustomizationSet(CustomizationService customize) => !_designData.IsHuman - ? customize.AwaitedService.GetList(SubRace.Midlander, Gender.Male) - : customize.AwaitedService.GetList(_designData.Customize.Clan, _designData.Customize.Gender); + ? customize.Service.GetList(SubRace.Midlander, Gender.Male) + : customize.Service.GetList(_designData.Customize.Clan, _designData.Customize.Gender); #endregion diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index 18cef7a..c756395 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -3,7 +3,7 @@ using Glamourer.Customization; using Glamourer.Services; using Glamourer.Structs; using OtterGui; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -121,9 +121,9 @@ public class DesignBase64Migration data.SetStain(slot, mdl.Stain); } - var main = cur[0].Set.Id == 0 + var main = cur[0].X.Id == 0 ? items.DefaultSword - : items.Identify(EquipSlot.MainHand, cur[0].Set, cur[0].Type, cur[0].Variant); + : items.Identify(EquipSlot.MainHand, cur[0].X, cur[0].Y, cur[0].Variant); if (!main.Valid) { Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified."); @@ -135,10 +135,10 @@ public class DesignBase64Migration EquipItem off; // Fist weapon hack - if (main.ModelId.Id is > 1600 and < 1651 && cur[1].Variant == 0) + if (main.PrimaryId.Id is > 1600 and < 1651 && cur[1].Variant == 0) { - off = items.Identify(EquipSlot.OffHand, (SetId)(main.ModelId.Id + 50), main.WeaponType, main.Variant, main.Type); - var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (Variant)cur[1].Type.Id); + off = items.Identify(EquipSlot.OffHand, (PrimaryId)(main.PrimaryId.Id + 50), main.SecondaryId, main.Variant, main.Type); + var gauntlet = items.Identify(EquipSlot.Hands, cur[1].X, (Variant)cur[1].Y.Id); if (gauntlet.Valid) { data.SetItem(EquipSlot.Hands, gauntlet); @@ -147,9 +147,9 @@ public class DesignBase64Migration } else { - off = cur[0].Set.Id == 0 + off = cur[0].X.Id == 0 ? ItemManager.NothingItem(FullEquipType.Shield) - : items.Identify(EquipSlot.OffHand, cur[1].Set, cur[1].Type, cur[1].Variant, main.Type); + : items.Identify(EquipSlot.OffHand, cur[1].X, cur[1].Y, cur[1].Variant, main.Type); } if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index f1ee47c..038509d 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -9,7 +9,7 @@ using Glamourer.Structs; using Glamourer.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -165,7 +165,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (slot, item, armor.Stain); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.Set, mainhand.Type, mainhand.Variant, FullEquipType.Unknown); + var mh = _items.Identify(EquipSlot.MainHand, mainhand.X, mainhand.Y, mainhand.Variant, FullEquipType.Unknown); if (!mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); @@ -174,7 +174,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (EquipSlot.MainHand, mh, mainhand.Stain); - var oh = _items.Identify(EquipSlot.OffHand, offhand.Set, offhand.Type, offhand.Variant, mh.Type); + var oh = _items.Identify(EquipSlot.OffHand, offhand.X, offhand.Y, offhand.Variant, mh.Type); if (!oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 4b0d53b..68fa154 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -7,7 +7,6 @@ using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String.Functions; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Designs; @@ -31,8 +30,8 @@ public unsafe struct DesignData public Customize Customize = Customize.Default; public uint ModelId; public CrestFlag CrestVisibility; - private WeaponType _secondaryMainhand; - private WeaponType _secondaryOffhand; + private SecondaryId _secondaryMainhand; + private SecondaryId _secondaryOffhand; private FullEquipType _typeMainhand; private FullEquipType _typeOffhand; private byte _states; @@ -75,18 +74,18 @@ public unsafe struct DesignData => slot.ToIndex() switch { // @formatter:off - 0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ), - 1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], (SetId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (WeaponType)0, _equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ), - 2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _equipmentBytes[10], FullEquipType.Hands, name: _nameHands ), - 3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], (SetId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (WeaponType)0, _equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ), - 4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ), - 5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], (SetId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (WeaponType)0, _equipmentBytes[22], FullEquipType.Ears, name: _nameEars ), - 6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ), - 7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], (SetId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (WeaponType)0, _equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ), - 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ), - 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], (SetId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (WeaponType)0, _equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ), - 10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand, name: _nameMainhand), - 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand, name: _nameOffhand ), + 0 => EquipItem.FromIds((ItemId)_itemIds[ 0], (IconId)_iconIds[ 0], (PrimaryId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ), + 1 => EquipItem.FromIds((ItemId)_itemIds[ 1], (IconId)_iconIds[ 1], (PrimaryId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ), + 2 => EquipItem.FromIds((ItemId)_itemIds[ 2], (IconId)_iconIds[ 2], (PrimaryId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[10], FullEquipType.Hands, name: _nameHands ), + 3 => EquipItem.FromIds((ItemId)_itemIds[ 3], (IconId)_iconIds[ 3], (PrimaryId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ), + 4 => EquipItem.FromIds((ItemId)_itemIds[ 4], (IconId)_iconIds[ 4], (PrimaryId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ), + 5 => EquipItem.FromIds((ItemId)_itemIds[ 5], (IconId)_iconIds[ 5], (PrimaryId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[22], FullEquipType.Ears, name: _nameEars ), + 6 => EquipItem.FromIds((ItemId)_itemIds[ 6], (IconId)_iconIds[ 6], (PrimaryId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ), + 7 => EquipItem.FromIds((ItemId)_itemIds[ 7], (IconId)_iconIds[ 7], (PrimaryId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ), + 8 => EquipItem.FromIds((ItemId)_itemIds[ 8], (IconId)_iconIds[ 8], (PrimaryId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ), + 9 => EquipItem.FromIds((ItemId)_itemIds[ 9], (IconId)_iconIds[ 9], (PrimaryId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ), + 10 => EquipItem.FromIds((ItemId)_itemIds[10], (IconId)_iconIds[10], (PrimaryId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, (Variant)_equipmentBytes[42], _typeMainhand, name: _nameMainhand), + 11 => EquipItem.FromIds((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ), _ => new EquipItem(), // @formatter:on }; @@ -129,8 +128,8 @@ public unsafe struct DesignData _itemIds[index] = item.ItemId.Id; _iconIds[index] = item.IconId.Id; - _equipmentBytes[4 * index + 0] = (byte)item.ModelId.Id; - _equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Id >> 8); + _equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id; + _equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8); _equipmentBytes[4 * index + 2] = item.Variant.Id; switch (index) { @@ -148,12 +147,12 @@ public unsafe struct DesignData // @formatter:on case 10: _nameMainhand = item.Name; - _secondaryMainhand = item.WeaponType; + _secondaryMainhand = item.SecondaryId; _typeMainhand = item.Type; return true; case 11: _nameOffhand = item.Name; - _secondaryOffhand = item.WeaponType; + _secondaryOffhand = item.SecondaryId; _typeOffhand = item.Type; return true; } @@ -294,7 +293,7 @@ public unsafe struct DesignData public readonly byte[] GetCustomizeBytes() { - var ret = new byte[CustomizeData.Size]; + var ret = new byte[CustomizeArray.Size]; fixed (byte* retPtr = ret, inPtr = Customize.Data.Data) { MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 392301f..6b8578e 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -12,7 +12,7 @@ using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index a31a696..00b94bb 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -8,6 +8,7 @@ using Glamourer.State; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Services; namespace Glamourer; @@ -25,21 +26,21 @@ public class Glamourer : IDalamudPlugin public static readonly Logger Log = new(); public static MessageService Messager { get; private set; } = null!; - private readonly ServiceProvider _services; + private readonly ServiceManager _services; public Glamourer(DalamudPluginInterface pluginInterface) { try { - _services = ServiceManager.CreateProvider(pluginInterface, Log); - Messager = _services.GetRequiredService(); - _services.GetRequiredService(); - _services.GetRequiredService(); - _services.GetRequiredService(); - _services.GetRequiredService(); // Initialize State Listener. - _services.GetRequiredService(); // initialize ui. - _services.GetRequiredService(); // initialize commands. - _services.GetRequiredService(); // initialize IPC. + _services = ServiceManagerA.CreateProvider(pluginInterface, Log); + Messager = _services.GetService(); + _services.GetService(); + _services.GetService(); + _services.GetService(); + _services.GetService(); // Initialize State Listener. + _services.GetService(); // initialize ui. + _services.GetService(); // initialize commands. + _services.GetService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); } catch diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index e4c15d4..f435ba8 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,6 +1,5 @@ using System; using System.Numerics; -using Dalamud.Interface.Utility; using Glamourer.Customization; using ImGuiNET; using OtterGui; @@ -28,7 +27,7 @@ public partial class CustomizationDrawer npc = true; } - var icon = _service.AwaitedService.GetIcon(custom!.Value.IconId); + var icon = _service.Service.GetIcon(custom!.Value.IconId); using (var disabled = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) @@ -68,7 +67,7 @@ public partial class CustomizationDrawer for (var i = 0; i < _currentCount; ++i) { var custom = _set.Data(_currentIndex, i, _customize.Face); - var icon = _service.AwaitedService.GetIcon(custom.IconId); + var icon = _service.Service.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i); @@ -179,8 +178,8 @@ public partial class CustomizationDrawer var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; var feature = _set.Data(featureIdx, 0, face); var icon = featureIdx == CustomizeIndex.LegacyTattoo - ? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId) - : _service.AwaitedService.GetIcon(feature.IconId); + ? _legacyTattoo ?? _service.Service.GetIcon(feature.IconId) + : _service.Service.GetIcon(feature.IconId); if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index fa725f5..a288065 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -10,7 +10,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -120,7 +120,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio return DrawArtisan(); DrawRaceGenderSelector(); - _set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender); + _set = _service.Service.GetList(_customize.Clan, _customize.Gender); foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) PercentageSelector(id); @@ -153,7 +153,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio private unsafe bool DrawArtisan() { - for (var i = 0; i < CustomizeData.Size; ++i) + for (var i = 0; i < CustomizeArray.Size; ++i) { using var id = ImRaii.PushId(i); int value = _customize.Data.Data[i]; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index e2b44de..cc94ed0 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -12,7 +12,7 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -24,7 +24,7 @@ public class EquipmentDrawer private readonly ItemManager _items; private readonly GlamourerColorCombo _stainCombo; - private readonly StainData _stainData; + private readonly DictStains _stainData; private readonly ItemCombo[] _itemCombo; private readonly Dictionary _weaponCombo; private readonly CodeService _codes; @@ -66,8 +66,8 @@ public class EquipmentDrawer _iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y); _comboLength = DefaultWidth * ImGuiHelpers.GlobalScale; if (_requiredComboWidthUnscaled == 0) - _requiredComboWidthUnscaled = _items.ItemService.AwaitedService.AllItems(true) - .Concat(_items.ItemService.AwaitedService.AllItems(false)) + _requiredComboWidthUnscaled = _items.ItemData.AllItems(true) + .Concat(_items.ItemData.AllItems(false)) .Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X) / ImGuiHelpers.GlobalScale; @@ -102,7 +102,7 @@ public class EquipmentDrawer public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - if (mainhand.CurrentItem.ModelId.Id == 0) + if (mainhand.CurrentItem.PrimaryId.Id == 0) return; if (_config.HideApplyCheckmarks) @@ -202,24 +202,24 @@ public class EquipmentDrawer void DrawWeapon(in EquipDrawData current) { - int setId = current.CurrentItem.ModelId.Id; - int type = current.CurrentItem.WeaponType.Id; + int setId = current.CurrentItem.PrimaryId.Id; + int type = current.CurrentItem.SecondaryId.Id; int variant = current.CurrentItem.Variant.Id; ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##setId", ref setId, 0, 0)) { - var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != current.CurrentItem.ModelId.Id) - current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.WeaponType, current.CurrentItem.Variant)); + var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); + if (newSetId.Id != current.CurrentItem.PrimaryId.Id) + current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant)); } ImGui.SameLine(); ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##type", ref type, 0, 0)) { - var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue); - if (newType.Id != current.CurrentItem.WeaponType.Id) - current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, newType, current.CurrentItem.Variant)); + var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue); + if (newType.Id != current.CurrentItem.SecondaryId.Id) + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant)); } ImGui.SameLine(); @@ -228,7 +228,8 @@ public class EquipmentDrawer { var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); if (newVariant.Id != current.CurrentItem.Variant.Id) - current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, current.CurrentItem.WeaponType, newVariant)); + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId, + newVariant)); } } } @@ -249,13 +250,13 @@ public class EquipmentDrawer /// Draw an input for armor that can set arbitrary values instead of choosing items. private void DrawArmorArtisan(EquipDrawData data) { - int setId = data.CurrentItem.ModelId.Id; + int setId = data.CurrentItem.PrimaryId.Id; int variant = data.CurrentItem.Variant.Id; ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##setId", ref setId, 0, 0)) { - var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != data.CurrentItem.ModelId.Id) + var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); + if (newSetId.Id != data.CurrentItem.PrimaryId.Id) data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); } @@ -265,7 +266,7 @@ public class EquipmentDrawer { var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); if (newVariant != data.CurrentItem.Variant) - data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.ModelId, newVariant)); + data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant)); } } @@ -454,7 +455,7 @@ public class EquipmentDrawer else if (combo.CustomVariant.Id > 0) data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); - if (!data.Locked && data.CurrentItem.ModelId.Id != 0) + if (!data.Locked && data.CurrentItem.PrimaryId.Id != 0) { if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) data.ItemSetter(ItemManager.NothingItem(data.Slot)); diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 0bd55dd..1073ac6 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -8,12 +8,12 @@ using Dalamud.Interface.Utility.Raii; using Glamourer.Unlocks; using ImGuiNET; using OtterGui.Widgets; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public sealed class GlamourerColorCombo(float _comboWidth, StainData _stains, FavoriteManager _favorites) +public sealed class GlamourerColorCombo(float _comboWidth, DictStains _stains, FavoriteManager _favorites) : FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log) { protected override bool DrawSelectable(int globalIdx, bool selected) @@ -40,8 +40,9 @@ public sealed class GlamourerColorCombo(float _comboWidth, StainData _stains, Fa return base.DrawSelectable(globalIdx, selected); } - private static Func>> CreateFunc(StainData stains, + private static Func>> CreateFunc(DictStains stains, FavoriteManager favorites) - => () => stains.Data.Select(kvp => (kvp, favorites.Contains((StainId)kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) - .Prepend(new KeyValuePair(0, ("None", 0, false))).ToList(); + => () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) + .Prepend(new KeyValuePair(Stain.None.RowIndex, Stain.None)).Select(kvp + => new KeyValuePair(kvp.Key.Id, (kvp.Value.Name, kvp.Value.RgbaColor, kvp.Value.Gloss))).ToList(); } diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 4ddbbaa..9e1790a 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -23,8 +23,8 @@ public sealed class ItemCombo : FilterComboCache private ItemId _currentItem; private float _innerWidth; - public SetId CustomSetId { get; private set; } - public Variant CustomVariant { get; private set; } + public PrimaryId CustomSetId { get; private set; } + public Variant CustomVariant { get; private set; } public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) : base(() => GetItems(favorites, items, slot), log) @@ -83,7 +83,7 @@ public sealed class ItemCombo : FilterComboCache } protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); + => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); protected override string ToString(EquipItem obj) => obj.Name; @@ -111,7 +111,7 @@ public sealed class ItemCombo : FilterComboCache private static IReadOnlyList GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot) { var nothing = ItemManager.NothingItem(slot); - if (!items.ItemService.AwaitedService.TryGetValue(slot.ToEquipType(), out var list)) + if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list)) return new[] { nothing, diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 5a1792e..027a211 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -60,12 +60,12 @@ public sealed class WeaponCombo : FilterComboCache var ret = ImGui.Selectable(name, selected); ImGui.SameLine(); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); - ImGuiUtil.RightAlign($"({obj.ModelId.Id}-{obj.WeaponType.Id}-{obj.Variant})"); + ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.SecondaryId.Id}-{obj.Variant})"); return ret; } protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); + => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); protected override string ToString(EquipItem obj) => obj.Name; @@ -80,14 +80,14 @@ public sealed class WeaponCombo : FilterComboCache var enumerable = Array.Empty().AsEnumerable(); foreach (var t in Enum.GetValues().Where(e => e.ToSlot() is EquipSlot.MainHand)) { - if (items.ItemService.AwaitedService.TryGetValue(t, out var l)) + if (items.ItemData.ByType.TryGetValue(t, out var l)) enumerable = enumerable.Concat(l); } return enumerable.OrderBy(e => e.Name).ToList(); } - if (!items.ItemService.AwaitedService.TryGetValue(type, out var list)) + if (!items.ItemData.ByType.TryGetValue(type, out var list)) return Array.Empty(); if (type.AllowsNothing()) diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index d4e19b2..a219134 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; +using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; @@ -65,8 +66,8 @@ public class MainWindow : Window, IDisposable Messages = messages; _quickBar = quickBar; _config = config; - _tabs = new ITab[] - { + _tabs = + [ settings, actors, designs, @@ -74,7 +75,7 @@ public class MainWindow : Window, IDisposable unlocks, messages, debugTab, - }; + ]; _event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow); IsOpen = _config.OpenWindowAtStart; } diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 8eb763e..f244fd5 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -166,7 +166,7 @@ public class PenumbraChangedItemTooltip : IDisposable { case ChangedItemType.ItemOffhand: case ChangedItemType.Item: - if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) + if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; CreateTooltip(item, "[Glamourer] ", false); @@ -177,7 +177,7 @@ public class PenumbraChangedItemTooltip : IDisposable private bool CanApplyWeapon(EquipSlot slot, EquipItem item) { var main = _objects.Player.GetMainhand(); - var mainItem = _items.Identify(slot, main.Set, main.Type, main.Variant); + var mainItem = _items.Identify(slot, main.X, main.Y, main.Variant); if (slot == EquipSlot.MainHand) return item.Type == mainItem.Type; @@ -197,7 +197,7 @@ public class PenumbraChangedItemTooltip : IDisposable if (!Player(out var state)) return; - if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) + if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; ApplyItem(state, item); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index a294b08..15d3725 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -13,7 +13,6 @@ using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; using Glamourer.Interop.Structs; -using Glamourer.Services; using Glamourer.State; using Glamourer.Structs; using ImGuiNET; @@ -21,14 +20,24 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Actors; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorPanel(ActorSelector _selector, StateManager _stateManager, CustomizationDrawer _customizationDrawer, - EquipmentDrawer _equipmentDrawer, IdentifierService _identification, AutoDesignApplier _autoDesignApplier, - Configuration _config, DesignConverter _converter, ObjectManager _objects, DesignManager _designManager, ImportService _importService, - ICondition _conditions) +public class ActorPanel( + ActorSelector _selector, + StateManager _stateManager, + CustomizationDrawer _customizationDrawer, + EquipmentDrawer _equipmentDrawer, + AutoDesignApplier _autoDesignApplier, + Configuration _config, + DesignConverter _converter, + ObjectManager _objects, + DesignManager _designManager, + ImportService _importService, + ICondition _conditions, + DictModelChara _modelChara) { private ActorIdentifier _identifier; private string _actorName = string.Empty; @@ -154,7 +163,7 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus } var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); - var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); + var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -187,7 +196,7 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus private void DrawMonsterPanel() { - var names = _identification.AwaitedService.ModelCharaNames(_state!.ModelData.ModelId); + var names = _modelChara[_state!.ModelData.ModelId]; var turnHuman = ImGui.Button("Turn Human"); ImGui.Separator(); using (var box = ImRaii.ListBox("##MonsterList", @@ -295,9 +304,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus private void SaveDesignOpen() { ImGui.OpenPopup("Save as Design"); - _newName = _state!.Identifier.ToName(); + _newName = _state!.Identifier.ToName(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); + _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); } private void SaveDesignDrawPopup() diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index b8d1ba4..934a19d 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -17,11 +17,11 @@ public class ActorSelector { private readonly EphemeralConfig _config; private readonly ObjectManager _objects; - private readonly ActorService _actors; + private readonly ActorManager _actors; private ActorIdentifier _identifier = ActorIdentifier.Invalid; - public ActorSelector(ObjectManager objects, ActorService actors, EphemeralConfig config) + public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) { _objects = objects; _actors = actors; @@ -93,7 +93,7 @@ public class ActorSelector if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth , "Select the local player character.", !_objects.Player, true)) - _identifier = _objects.Player.GetIdentifier(_actors.AwaitedService); + _identifier = _objects.Player.GetIdentifier(_actors); ImGui.SameLine(); var (id, data) = _objects.TargetData; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index da4fda1..c3781fc 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -3,23 +3,23 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Utility; -using Glamourer.Services; using ImGuiNET; using OtterGui.Custom; using OtterGui.Log; using OtterGui.Widgets; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; namespace Glamourer.Gui.Tabs.AutomationTab; -public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)> +public sealed class HumanNpcCombo( + string label, + DictModelChara modelCharaDict, + DictBNpcNames bNpcNames, + DictBNpc bNpcs, + HumanModelList humans, + Logger log) + : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), log) { - private readonly string _label; - - public HumanNpcCombo(string label, IdentifierService service, HumanModelList humans, Logger log) - : base(() => CreateList(service, humans), log) - => _label = label; - protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj) => obj.Name; @@ -36,7 +36,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki } public bool Draw(float width) - => Draw(_label, CurrentSelection.Name.IsNullOrEmpty() ? "Human Non-Player-Characters..." : CurrentSelection.Name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); + => Draw(label, CurrentSelection.Name.IsNullOrEmpty() ? "Human Non-Player-Characters..." : CurrentSelection.Name, string.Empty, width, + ImGui.GetTextLineHeightWithSpacing()); /// Compare strings in a way that letters and numbers are sorted before any special symbols. @@ -61,15 +62,16 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki } } - private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(IdentifierService service, HumanModelList humans) + private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(DictModelChara modelCharaDict, DictBNpcNames bNpcNames, + DictBNpc bNpcs, HumanModelList humans) { var ret = new List<(string Name, ObjectKind Kind, uint Id)>(1024); - for (var modelChara = 0u; modelChara < service.AwaitedService.NumModelChara; ++modelChara) + for (var modelChara = 0u; modelChara < modelCharaDict.Count; ++modelChara) { if (!humans.IsHuman(modelChara)) continue; - var list = service.AwaitedService.ModelCharaNames(modelChara); + var list = modelCharaDict[modelChara]; if (list.Count == 0) continue; @@ -78,8 +80,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki switch (kind) { case ObjectKind.BattleNpc: - var nameIds = service.AwaitedService.GetBnpcNames(id); - ret.AddRange(nameIds.Select(nameId => (service.AwaitedService.Name(ObjectKind.BattleNpc, nameId), kind, nameId.Id))); + var nameIds = bNpcNames[id]; + ret.AddRange(nameIds.Select(nameId => (bNpcs[nameId], kind, nameId.Id))); break; case ObjectKind.EventNpc: ret.Add((name, kind, id)); diff --git a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs index e88c55b..8498bc1 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs @@ -1,9 +1,9 @@ using Dalamud.Game.ClientState.Objects.Enums; -using Glamourer.Services; using ImGuiNET; -using OtterGui.Custom; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Gui.Tabs.AutomationTab; @@ -12,7 +12,7 @@ public class IdentifierDrawer { private readonly WorldCombo _worldCombo; private readonly HumanNpcCombo _humanNpcCombo; - private readonly ActorService _actors; + private readonly ActorManager _actors; private string _characterName = string.Empty; @@ -21,11 +21,12 @@ public class IdentifierDrawer public ActorIdentifier RetainerIdentifier { get; private set; } = ActorIdentifier.Invalid; public ActorIdentifier MannequinIdentifier { get; private set; } = ActorIdentifier.Invalid; - public IdentifierDrawer(ActorService actors, IdentifierService identifier, HumanModelList humans) + public IdentifierDrawer(ActorManager actors, DictWorld dictWorld, DictModelChara dictModelChara, DictBNpcNames bNpcNames, DictBNpc bNpc, + HumanModelList humans) { _actors = actors; - _worldCombo = new WorldCombo(actors.AwaitedService.Data.Worlds, Glamourer.Log); - _humanNpcCombo = new HumanNpcCombo("##npcs", identifier, humans, Glamourer.Log); + _worldCombo = new WorldCombo(dictWorld, Glamourer.Log); + _humanNpcCombo = new HumanNpcCombo("##npcs", dictModelChara, bNpcNames, bNpc, humans, Glamourer.Log); } public void DrawName(float width) @@ -63,13 +64,13 @@ public class IdentifierDrawer { if (ByteString.FromString(_characterName, out var byteName)) { - PlayerIdentifier = _actors.AwaitedService.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key); - RetainerIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); - MannequinIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); + PlayerIdentifier = _actors.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key); + RetainerIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); + MannequinIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); } NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc - ? _actors.AwaitedService.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]) + ? _actors.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]) : ActorIdentifier.Invalid; } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 6b0e301..3eba0cd 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -333,7 +333,7 @@ public class SetPanel if (!design.Design.DesignData.IsHuman) sb.AppendLine("The base model id can not be changed automatically to something non-human."); - var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender); + var set = _customizations.Service.GetList(customize.Clan, customize.Gender); foreach (var type in CustomizationExtensions.All) { var flag = type.ToFlag(); diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 5ca3adf..c1544b2 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -7,11 +7,11 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Events; using Glamourer.Interop; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using Penumbra.GameData.Actors; using Penumbra.String; using ImGuiClip = OtterGui.ImGuiClip; @@ -22,9 +22,9 @@ public class SetSelector : IDisposable private readonly Configuration _config; private readonly AutoDesignManager _manager; private readonly AutomationChanged _event; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ObjectManager _objects; - private readonly List<(AutoDesignSet, int)> _list = new(); + private readonly List<(AutoDesignSet, int)> _list = []; public AutoDesignSet? Selection { get; private set; } public int SelectionIndex { get; private set; } = -1; @@ -44,7 +44,7 @@ public class SetSelector : IDisposable internal int _dragDesignIndex = -1; - public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorService actors, ObjectManager objects) + public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorManager actors, ObjectManager objects) { _manager = manager; _event = @event; @@ -289,9 +289,9 @@ public class SetSelector : IDisposable private void NewSetButton(Vector2 size) { - var id = _actors.AwaitedService.GetCurrentPlayer(); + var id = _actors.GetCurrentPlayer(); if (!id.IsValid) - id = _actors.AwaitedService.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); + id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, $"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true)) _manager.AddDesignSet("New Design", id); diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 71df7b9..298fe0e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -69,7 +69,7 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM static string ItemString(in DesignData data, EquipSlot slot) { var item = data.Item(slot); - return $"{item.Name} ({item.ModelId.Id}{(item.WeaponType != 0 ? $"-{item.WeaponType.Id}" : string.Empty)}-{item.Variant})"; + return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); diff --git a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs index 42fc52b..976b5c4 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs @@ -1,23 +1,23 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Numerics; using Dalamud.Interface.Utility; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Actors; +using Penumbra.GameData.DataContainers; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebugTabTree +public class ActorManagerPanel(ActorManager _actors, DictBNpcNames _bNpcNames) : IDebugTabTree { public string Label => "Actor Service"; public bool Disabled - => !_actors.Valid; + => !_actors.Awaiter.IsCompletedSuccessfully; private string _bnpcFilter = string.Empty; private string _enpcFilter = string.Empty; @@ -29,11 +29,11 @@ public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebu public void Draw() { DrawBnpcTable(); - DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.AwaitedService.Data.ENpcs.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.AwaitedService.Data.Companions.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.AwaitedService.Data.Mounts.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.AwaitedService.Data.Ornaments.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.AwaitedService.Data.Worlds.Select(kvp => ((uint)kvp.Key, kvp.Value))); + DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.Data.ENpcs.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.Data.Companions.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.Data.Mounts.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.Data.Ornaments.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.Data.Worlds.Select(kvp => ((uint)kvp.Key.Id, kvp.Value))); } private void DrawBnpcTable() @@ -58,15 +58,15 @@ public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebu ImGui.TableNextColumn(); var skips = ImGuiClip.GetNecessarySkips(height); ImGui.TableNextRow(); - var data = _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.ToString("D5"), kvp.Value)); + var data = _actors.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.Id.ToString("D5"), kvp.Value)); var remainder = ImGuiClip.FilteredClippedDraw(data, skips, - p => p.Item2.Contains(_bnpcFilter) || p.Item3.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), + p => p.Item2.Contains(_bnpcFilter) || p.Value.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), p => { ImGuiUtil.DrawTableColumn(p.Item2); - ImGuiUtil.DrawTableColumn(p.Item3); - var bnpcs = _items.IdentifierService.AwaitedService.GetBnpcsFromName(p.Item1); - ImGuiUtil.DrawTableColumn(string.Join(", ", bnpcs.Select(b => b.Id.ToString()))); + ImGuiUtil.DrawTableColumn(p.Value); + var bNpcs = _bNpcNames.GetBNpcsFromName(p.Key.BNpcNameId); + ImGuiUtil.DrawTableColumn(string.Join(", ", bNpcs.Select(b => b.Id.ToString()))); }); ImGuiClip.DrawEndDummy(remainder, height); } diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 6088651..9906387 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -13,15 +13,15 @@ public class CustomizationServicePanel(CustomizationService _customization) : ID => "Customization Service"; public bool Disabled - => !_customization.Valid; + => !_customization.Awaiter.IsCompletedSuccessfully; public void Draw() { - foreach (var clan in _customization.AwaitedService.Clans) + foreach (var clan in _customization.Service.Clans) { - foreach (var gender in _customization.AwaitedService.Genders) + foreach (var gender in _customization.Service.Genders) { - var set = _customization.AwaitedService.GetList(clan, gender); + var set = _customization.Service.GetList(clan, gender); DrawCustomizationInfo(set); DrawNpcCustomizationInfo(set); } diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index e010b2a..bd0b98f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; +using OtterGui.Services; namespace Glamourer.Gui.Tabs.DebugTab; -public interface IDebugTabTree +public interface IDebugTabTree : IService { public string Label { get; } public void Draw(); @@ -54,7 +55,7 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) "Game Data", provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 481006e..03ba048 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -8,7 +8,7 @@ using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs index b17f35a..9a0503e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs @@ -3,18 +3,19 @@ using Dalamud.Interface.Utility; using Glamourer.Services; using ImGuiNET; using OtterGui; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; -public class IdentifierPanel(ItemManager _items) : IDebugTabTree +public class IdentifierPanel(ItemManager _items, GamePathParser _gamePathParser) : IDebugTabTree { public string Label => "Identifier Service"; public bool Disabled - => !_items.IdentifierService.Valid; + => !_items.ObjectIdentification.Awaiter.IsCompletedSuccessfully; private string _gamePath = string.Empty; private int _setId; @@ -33,10 +34,10 @@ public class IdentifierPanel(ItemManager _items) : IDebugTabTree ImGui.SameLine(); ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256); - var fileInfo = _items.IdentifierService.AwaitedService.GamePathParser.GetFileInfo(_gamePath); + var fileInfo = _gamePathParser.GetFileInfo(_gamePath); ImGui.TextUnformatted( $"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}"); - Text(string.Join("\n", _items.IdentifierService.AwaitedService.Identify(_gamePath).Keys)); + Text(string.Join("\n", _items.ObjectIdentification.Identify(_gamePath).Keys)); ImGui.Separator(); ImGui.AlignTextToFramePadding(); @@ -46,16 +47,16 @@ public class IdentifierPanel(ItemManager _items) : IDebugTabTree foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var identified = _items.Identify(slot, (SetId)_setId, (Variant)_variant); + var identified = _items.Identify(slot, (PrimaryId)_setId, 0, (Variant)_variant); Text(identified.Name); ImGuiUtil.HoverTooltip(string.Join("\n", - _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (Variant)_variant, slot) + _items.ObjectIdentification.Identify((PrimaryId)_setId, 0, (Variant)_variant, slot) .Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); } - var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant); + var weapon = _items.Identify(EquipSlot.MainHand, (PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant); Text(weapon.Name); ImGuiUtil.HoverTooltip(string.Join("\n", - _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); + _items.ObjectIdentification.Identify((PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs index 7ef34b6..9fbc931 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs @@ -13,7 +13,7 @@ public class ItemManagerPanel(ItemManager _items) : IDebugTabTree => "Item Manager"; public bool Disabled - => !_items.ItemService.Valid; + => !_items.ItemData.Awaiter.IsCompletedSuccessfully; private string _itemFilter = string.Empty; @@ -22,18 +22,18 @@ public class ItemManagerPanel(ItemManager _items) : IDebugTabTree ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})", ImGuiTreeNodeFlags.Leaf).Dispose(); DebugTab.DrawNameTable("All Items (Main)", ref _itemFilter, - _items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) + _items.ItemData.AllItems(true).Select(p => (p.Item1.Id, + $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) .OrderBy(p => p.Item1)); DebugTab.DrawNameTable("All Items (Off)", ref _itemFilter, - _items.ItemService.AwaitedService.AllItems(false).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) + _items.ItemData.AllItems(false).Select(p => (p.Item1.Id, + $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) .OrderBy(p => p.Item1)); foreach (var type in Enum.GetValues().Skip(1)) { DebugTab.DrawNameTable(type.ToName(), ref _itemFilter, - _items.ItemService.AwaitedService[type] - .Select(p => (p.ItemId.Id, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); + _items.ItemData.ByType[type] + .Select(p => (p.ItemId.Id, $"{p.Name} ({(p.SecondaryId.Id == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index a218d96..73194c4 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -39,7 +39,7 @@ public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t => { ImGuiUtil.DrawTableColumn(t.Key.ToString()); - if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) + if (_items.ItemData.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) { ImGuiUtil.DrawTableColumn(equip.Name); ImGuiUtil.DrawTableColumn(equip.Type.ToName()); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 0b0526b..e71c69a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -199,7 +199,7 @@ public unsafe class ModelEvaluationPanel( if (ImGui.SmallButton("Change Piece")) _updateSlotService.UpdateArmor(model, slot, - new CharacterArmor((SetId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); + new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); ImGui.SameLine(); if (ImGui.SmallButton("Change Stain")) _updateSlotService.UpdateStain(model, slot, 5); @@ -213,11 +213,11 @@ public unsafe class ModelEvaluationPanel( { using var id = ImRaii.PushId("Customize"); var actorCustomize = new Customize(actor.IsCharacter - ? *(Penumbra.GameData.Structs.CustomizeData*)&actor.AsCharacter->DrawData.CustomizeData - : new Penumbra.GameData.Structs.CustomizeData()); + ? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData + : new CustomizeArray()); var modelCustomize = new Customize(model.IsHuman - ? *(Penumbra.GameData.Structs.CustomizeData*)model.AsHuman->Customize.Data - : new Penumbra.GameData.Structs.CustomizeData()); + ? *(CustomizeArray*)model.AsHuman->Customize.Data + : new CustomizeArray()); foreach (var type in Enum.GetValues()) { using var id2 = ImRaii.PushId((int)type); diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 53af228..90fc0f5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -55,7 +55,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM return; - void Draw(CustomizationNpcOptions.NpcData data) + void Draw(NpcData data) { using var id = ImRaii.PushId(idx++); var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index cf9e443..b868140 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -3,14 +3,14 @@ using System.Globalization; using System.Linq; using System.Numerics; using Glamourer.Interop; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Actors; namespace Glamourer.Gui.Tabs.DebugTab; -public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _actors) : IDebugTabTree +public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IDebugTabTree { public string Label => "Object Manager"; @@ -33,7 +33,7 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _acto ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("World"); - ImGuiUtil.DrawTableColumn(_actors.Valid ? _actors.AwaitedService.Data.ToWorldName(_objectManager.World) : "Service Missing"); + ImGuiUtil.DrawTableColumn(_actors.Awaiter.IsCompletedSuccessfully ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); ImGuiUtil.DrawTableColumn("Player Character"); diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 6352412..cf5b92a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -59,7 +59,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0); ImGuiUtil.DrawTableColumn(_penumbra.Available - ? _penumbra.CutsceneParent(_gameObjectIndex).ToString() + ? _penumbra.CutsceneParent((ushort) _gameObjectIndex).ToString() : "Penumbra Unavailable"); ImGuiUtil.DrawTableColumn("Redraw Object"); diff --git a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs index 7e1e600..0f27eff 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs @@ -32,7 +32,7 @@ public class RestrictedGearPanel(ItemManager _items) : IDebugTabTree foreach (var slot in EquipSlotExtensions.EqdpSlots) { var (replaced, model) = - _items.RestrictedGear.ResolveRestricted(new CharacterArmor((SetId)_setId, (Variant)_variant, 0), slot, race, gender); + _items.RestrictedGear.ResolveRestricted(new CharacterArmor((PrimaryId)_setId, (Variant)_variant, 0), slot, race, gender); if (replaced) ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}."); } diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs index cfd00fa..e658c99 100644 --- a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -40,7 +40,7 @@ public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _i var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t => { ImGuiUtil.DrawTableColumn(t.Key.ToString()); - if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) + if (_items.ItemData.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) { ImGuiUtil.DrawTableColumn(equip.Name); ImGuiUtil.DrawTableColumn(equip.Type.ToName()); diff --git a/Glamourer/Gui/Tabs/NpcCombo.cs b/Glamourer/Gui/Tabs/NpcCombo.cs index ab8d08d..91f0db0 100644 --- a/Glamourer/Gui/Tabs/NpcCombo.cs +++ b/Glamourer/Gui/Tabs/NpcCombo.cs @@ -1,36 +1,11 @@ -using System.Collections; -using System.Collections.Generic; -using System.Threading.Tasks; -using Dalamud.Plugin.Services; -using Glamourer.Customization; -using Glamourer.Services; +using Glamourer.Customization; using OtterGui.Widgets; -using Penumbra.GameData; namespace Glamourer.Gui.Tabs; -public class NpcCombo(ActorService actorManager, IdentifierService identifier, IDataManager data) - : FilterComboBase(new LazyList(actorManager, identifier, data), false, Glamourer.Log) +public class NpcCombo(NpcCustomizeSet npcCustomizeSet) + : FilterComboCache(npcCustomizeSet, Glamourer.Log) { - private class LazyList(ActorService actorManager, IdentifierService identifier, IDataManager data) - : IReadOnlyList - { - private readonly Task> _task - = Task.Run(() => CustomizationNpcOptions.CreateNpcData(actorManager.AwaitedService.Data.ENpcs, actorManager.AwaitedService.Data.BNpcs, identifier.AwaitedService, data)); - - public IEnumerator GetEnumerator() - => _task.Result.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public int Count - => _task.Result.Count; - - public CustomizationNpcOptions.NpcData this[int index] - => _task.Result[index]; - } - - protected override string ToString(CustomizationNpcOptions.NpcData obj) + protected override string ToString(NpcData obj) => obj.Name; } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index f8395fe..1b0e27d 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -60,7 +60,7 @@ public class SettingsTab : ITab if (!child) return; - Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters to be applied automatically.", + Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); ImGui.NewLine(); ImGui.NewLine(); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 2bd79d8..154e930 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -41,7 +41,7 @@ public class UnlockOverview foreach (var type in Enum.GetValues()) { - if (type.IsOffhandType() || !_items.ItemService.AwaitedService.TryGetValue(type, out var items) || items.Count == 0) + if (type.IsOffhandType() || !_items.ItemData.ByType.TryGetValue(type, out var items) || items.Count == 0) continue; if (ImGui.Selectable(type.ToName(), _selected1 == type)) @@ -52,11 +52,11 @@ public class UnlockOverview } } - foreach (var clan in _customizations.AwaitedService.Clans) + foreach (var clan in _customizations.Service.Clans) { - foreach (var gender in _customizations.AwaitedService.Genders) + foreach (var gender in _customizations.Service.Genders) { - if (_customizations.AwaitedService.GetList(clan, gender).HairStyles.Count == 0) + if (_customizations.Service.GetList(clan, gender).HairStyles.Count == 0) continue; if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", @@ -107,7 +107,7 @@ public class UnlockOverview private void DrawCustomizations() { - var set = _customizations.AwaitedService.GetList(_selected2, _selected3); + var set = _customizations.Service.GetList(_selected2, _selected3); var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -121,7 +121,7 @@ public class UnlockOverview continue; var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); - var icon = _customizations.AwaitedService.GetIcon(customize.IconId); + var icon = _customizations.Service.GetIcon(customize.IconId); ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); @@ -150,7 +150,7 @@ public class UnlockOverview private void DrawItems() { - if (!_items.ItemService.AwaitedService.TryGetValue(_selected1, out var items)) + if (!_items.ItemData.ByType.TryGetValue(_selected1, out var items)) return; var spacing = IconSpacing; @@ -160,6 +160,30 @@ public class UnlockOverview var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow; var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1; + var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); + var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count); + var counter = 0; + for (var idx = skips * iconsPerRow; idx < end; ++idx) + { + DrawItem(items[idx]); + if (counter != iconsPerRow - 1) + { + ImGui.SameLine(); + ++counter; + } + else + { + counter = 0; + } + } + + if (ImGui.GetCursorPosX() != 0) + ImGui.NewLine(); + var remainder = numRows - numVisibleRows - skips; + if (remainder > 0) + ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y); + return; + void DrawItem(EquipItem item) { var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time); @@ -189,7 +213,7 @@ public class UnlockOverview ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); if (item.Type.ValidOffhand().IsOffhandType()) ImGui.TextUnformatted( - $"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); + $"{item.Weapon()}{(_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); else ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted( @@ -219,29 +243,6 @@ public class UnlockOverview _tooltip.CreateTooltip(item, string.Empty, false); } } - - var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); - var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count); - var counter = 0; - for (var idx = skips * iconsPerRow; idx < end; ++idx) - { - DrawItem(items[idx]); - if (counter != iconsPerRow - 1) - { - ImGui.SameLine(); - ++counter; - } - else - { - counter = 0; - } - } - - if (ImGui.GetCursorPosX() != 0) - ImGui.NewLine(); - var remainder = numRows - numVisibleRows - skips; - if (remainder > 0) - ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y); } private static Vector2 IconSpacing diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 5ac087f..3f7531c 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -278,7 +278,7 @@ public class UnlockTable : Table, IDisposable ImGuiUtil.RightAlign(item.ModelString); if (ImGui.IsItemHovered() && item.Type.ValidOffhand().IsOffhandType() - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) { using var tt = ImRaii.Tooltip(); ImGui.TextUnformatted("Offhand: " + offhand.ModelString); @@ -297,7 +297,7 @@ public class UnlockTable : Table, IDisposable return true; if (item.Type.ValidOffhand().IsOffhandType() - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) return FilterRegex?.IsMatch(offhand.ModelString) ?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase); @@ -411,21 +411,16 @@ public class UnlockTable : Table, IDisposable => item.Flags.HasFlag(ItemFlags.IsCrestWorthy); } - private sealed class ItemList : IReadOnlyCollection + private sealed class ItemList(ItemManager items) : IReadOnlyCollection { - private readonly ItemManager _items; - - public ItemList(ItemManager items) - => _items = items; - public IEnumerator GetEnumerator() - => _items.ItemService.AwaitedService.AllItems(true).Select(i => i.Item2).GetEnumerator(); + => items.ItemData.AllItems(true).Select(i => i.Item2).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count - => _items.ItemService.AwaitedService.TotalItemCount(true); + => items.ItemData.Primary.Count; } private void OnObjectUnlock(ObjectUnlocked.Type _1, uint _2, DateTimeOffset _3) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 6e83838..2c64b42 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -27,7 +27,7 @@ public static class UiHelpers public static void DrawIcon(this EquipItem item, TextureService textures, Vector2 size, EquipSlot slot) { - var isEmpty = item.ModelId.Id == 0; + var isEmpty = item.PrimaryId.Id == 0; var (ptr, textureSize, empty) = textures.GetIcon(item, slot); if (empty) { diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 9e9a043..ef7e762 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -1,5 +1,4 @@ using System; -using System.Threading; using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; @@ -7,7 +6,7 @@ using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop.Structs; using OtterGui.Classes; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; +using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -64,7 +63,7 @@ public unsafe class ChangeCustomizeService : EventWrapper _changeCustomizeHook; - public bool UpdateCustomize(Model model, CustomizeData customize) + public bool UpdateCustomize(Model model, CustomizeArray customize) { if (!model.IsHuman) return false; @@ -75,14 +74,14 @@ public unsafe class ChangeCustomizeService : EventWrapper UpdateCustomize(actor.Model, customize); private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment) { if (!InUpdate.InMethod) { - var customize = new Ref(new Customize(*(CustomizeData*)data)); + var customize = new Ref(new Customize(*(CustomizeArray*)data)); Invoke(this, (Model)human, customize); ((Customize*)data)->Load(customize.Value); } diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index 15b8af1..c916ad8 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -78,8 +78,8 @@ public sealed class CmaFile return; } - var set = mainhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; - var type = mainhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var set = mainhand["Item1"]?.ToObject() ?? items.DefaultSword.PrimaryId; + var type = mainhand["Item2"]?.ToObject() ?? items.DefaultSword.SecondaryId; var variant = mainhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; var stain = mainhand["Item4"]?.ToObject() ?? 0; var item = items.Identify(EquipSlot.MainHand, set, type, variant); @@ -95,17 +95,17 @@ public sealed class CmaFile if (offhand == null) { data.SetItem(EquipSlot.MainHand, defaultOffhand); - data.SetStain(EquipSlot.MainHand, defaultOffhand.ModelId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); + data.SetStain(EquipSlot.MainHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); return; } - var set = offhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; - var type = offhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var set = offhand["Item1"]?.ToObject() ?? items.DefaultSword.PrimaryId; + var type = offhand["Item2"]?.ToObject() ?? items.DefaultSword.SecondaryId; var variant = offhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; var stain = offhand["Item4"]?.ToObject() ?? 0; var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType); data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand); - data.SetStain(EquipSlot.OffHand, defaultOffhand.ModelId.Id == 0 ? 0 : (StainId)stain); + data.SetStain(EquipSlot.OffHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : (StainId)stain); } } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index c210f1f..96dc107 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -68,7 +68,7 @@ public class ContextMenuService : IDisposable if (itemId > 500000) itemId -= 500000; - if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item)) + if (!_items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out var item)) return null; return new InventoryContextMenuItem(TryOnString, GetInventoryAction(item)); @@ -80,7 +80,7 @@ public class ContextMenuService : IDisposable if (itemId > 500000) itemId -= 500000; - if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item)) + if (!_items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out var item)) return null; return new GameObjectContextMenuItem(TryOnString, GetGameObjectAction(item)); @@ -122,10 +122,10 @@ public class ContextMenuService : IDisposable _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { - if (item.ModelId.Id is > 1600 and < 1651 - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) + if (item.PrimaryId.Id is > 1600 and < 1651 + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); - if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); } }; @@ -146,10 +146,10 @@ public class ContextMenuService : IDisposable _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { - if (item.ModelId.Id is > 1600 and < 1651 - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) + if (item.PrimaryId.Id is > 1600 and < 1651 + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); - if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); } }; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 217b5fd..86f2343 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -178,7 +178,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage if (input.BodyType.Value != 1) return false; - var set = _customizations.AwaitedService.GetList(input.Clan, input.Gender); + var set = _customizations.Service.GetList(input.Clan, input.Gender); voice = set.Voices[0]; if (inputVoice.HasValue && !set.Voices.Contains(inputVoice.Value)) return false; diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 52dd397..5db7400 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -5,8 +5,8 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; using Glamourer.Interop.Structs; -using Glamourer.Services; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; namespace Glamourer.Interop; @@ -15,13 +15,13 @@ public class ObjectManager : IReadOnlyDictionary private readonly IFramework _framework; private readonly IClientState _clientState; private readonly IObjectTable _objects; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ITargetManager _targets; public IObjectTable Objects => _objects; - public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorService actors, ITargetManager targets) + public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorManager actors, ITargetManager targets) { _framework = framework; _clientState = clientState; @@ -57,7 +57,7 @@ public class ObjectManager : IReadOnlyDictionary for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i) { Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors.AwaitedService, out var identifier)) + if (character.Identifier(_actors, out var identifier)) HandleIdentifier(identifier, character); } @@ -70,13 +70,13 @@ public class ObjectManager : IReadOnlyDictionary if (!character.Valid && i == (int)ScreenActor.CutsceneStart) break; - HandleIdentifier(character.GetIdentifier(_actors.AwaitedService), character); + HandleIdentifier(character.GetIdentifier(_actors), character); } void AddSpecial(ScreenActor idx, string label) { Actor actor = _objects.GetObjectAddress((int)idx); - if (actor.Identifier(_actors.AwaitedService, out var ident)) + if (actor.Identifier(_actors, out var ident)) { var data = new ActorData(actor, label); _identifiers.Add(ident, data); @@ -95,7 +95,7 @@ public class ObjectManager : IReadOnlyDictionary for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i) { Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors.AwaitedService, out var identifier)) + if (character.Identifier(_actors, out var identifier)) HandleIdentifier(identifier, character); } @@ -120,7 +120,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Player or IdentifierType.Owned) { - var allWorld = _actors.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, + var allWorld = _actors.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId); @@ -137,7 +137,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Owned) { - var nonOwned = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId); + var nonOwned = _actors.CreateNpc(identifier.Kind, identifier.DataId); if (!_nonOwnedIdentifiers.TryGetValue(nonOwned, out var nonOwnedData)) { nonOwnedData = new ActorData(character, nonOwned.ToString()); @@ -170,7 +170,7 @@ public class ObjectManager : IReadOnlyDictionary get { Update(); - return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Player.Identifier(_actors, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } @@ -181,7 +181,7 @@ public class ObjectManager : IReadOnlyDictionary get { Update(); - return Target.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Target.Identifier(_actors, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 4515b7c..2629ad7 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -221,8 +221,8 @@ public unsafe class PenumbraService : IDisposable => Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null; /// Obtain the parent of a cutscene actor if it is known. - public int CutsceneParent(int idx) - => Available ? _cutsceneParent.Invoke(idx) : -1; + public short CutsceneParent(ushort idx) + => (short) (Available ? _cutsceneParent.Invoke(idx) : -1); /// Try to redraw the given actor. public void RedrawObject(Actor actor, RedrawType settings) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 389a05f..544fe32 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -76,7 +76,7 @@ public unsafe class WeaponService : IDisposable if (tmpWeapon.Value != weapon.Value) { - if (tmpWeapon.Set.Id == 0) + if (tmpWeapon.X.Id == 0) tmpWeapon.Stain = 0; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); } @@ -119,7 +119,7 @@ public unsafe class WeaponService : IDisposable var mdl = character.Model; var (_, _, mh, oh) = mdl.GetWeapons(character); var value = slot == EquipSlot.OffHand ? oh : mh; - var weapon = value.With(value.Set.Id == 0 ? 0 : stain); + var weapon = value.With(value.X.Id == 0 ? 0 : stain); LoadWeapon(character, slot, weapon); } } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 253442f..417f1b9 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -16,6 +16,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; namespace Glamourer.Services; @@ -27,7 +28,7 @@ public class CommandService : IDisposable private readonly ICommandManager _commands; private readonly MainWindow _mainWindow; private readonly IChatGui _chat; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ObjectManager _objects; private readonly StateManager _stateManager; private readonly AutoDesignApplier _autoDesignApplier; @@ -37,7 +38,7 @@ public class CommandService : IDisposable private readonly DesignFileSystem _designFileSystem; private readonly Configuration _config; - public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorService actors, ObjectManager objects, + public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config) { @@ -402,7 +403,7 @@ public class CommandService : IDisposable { foreach (var actor in actors.Objects) { - if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out var state)) + if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); } } @@ -537,7 +538,7 @@ public class CommandService : IDisposable { if (_objects.GetName(argument.ToLowerInvariant(), out var obj)) { - var identifier = _actors.AwaitedService.FromObject(obj.AsObject, out _, true, true, true); + var identifier = _actors.FromObject(obj.AsObject, out _, true, true, true); if (!identifier.IsValid) { _chat.Print(new SeStringBuilder().AddText("The placeholder ").AddGreen(argument) @@ -547,7 +548,7 @@ public class CommandService : IDisposable } if (allowIndex && identifier.Type is IdentifierType.Npc) - identifier = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId, obj.Index); + identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId, obj.Index); identifiers = new[] { identifier, @@ -555,7 +556,7 @@ public class CommandService : IDisposable } else { - identifiers = _actors.AwaitedService.FromUserString(argument, allowIndex); + identifiers = _actors.FromUserString(argument, allowIndex); if (!allowAnyWorld && identifiers[0].Type is IdentifierType.Player or IdentifierType.Owned && identifiers[0].HomeWorld == ushort.MaxValue) diff --git a/Glamourer/Services/CustomizationService.cs b/Glamourer/Services/CustomizationService.cs index 1b3f6e2..b5f474f 100644 --- a/Glamourer/Services/CustomizationService.cs +++ b/Glamourer/Services/CustomizationService.cs @@ -1,19 +1,34 @@ using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Dalamud.Plugin.Services; using Glamourer.Customization; -using Penumbra.GameData.Data; +using OtterGui.Services; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; namespace Glamourer.Services; -public sealed class CustomizationService : AsyncServiceWrapper +public sealed class CustomizationService( + ITextureProvider textures, + IDataManager gameData, + HumanModelList humanModels, + IPluginLog log, + NpcCustomizeSet npcCustomizeSet) + : IAsyncService { - public readonly HumanModelList HumanModels; + public readonly HumanModelList HumanModels = humanModels; - public CustomizationService(ITextureProvider textures, IDataManager gameData, HumanModelList humanModels, IPluginLog log) - : base(nameof(CustomizationService), () => CustomizationManager.Create(textures, gameData, log)) - => HumanModels = humanModels; + private ICustomizationManager? _service; + + private readonly Task _task = Task.WhenAll(humanModels.Awaiter, npcCustomizeSet.Awaiter) + .ContinueWith(_ => CustomizationManager.Create(textures, gameData, log, npcCustomizeSet)); + + public ICustomizationManager Service + => _service ??= _task.Result; + + public Task Awaiter + => _task; public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues, CustomizeFlag applyWhich, bool allowUnknown) @@ -36,7 +51,7 @@ public sealed class CustomizationService : AsyncServiceWrapper AwaitedService.GetName(CustomName.MidlanderM), - (Gender.Male, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderM), - (Gender.Male, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodM), - (Gender.Male, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightM), - (Gender.Male, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkM), - (Gender.Male, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkM), - (Gender.Male, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunM), - (Gender.Male, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonM), - (Gender.Male, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfM), - (Gender.Male, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardM), - (Gender.Male, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenM), - (Gender.Male, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaM), - (Gender.Male, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM), - (Gender.Male, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM), - (Gender.Male, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaM), - (Gender.Male, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaM), - (Gender.Female, SubRace.Midlander) => AwaitedService.GetName(CustomName.MidlanderF), - (Gender.Female, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderF), - (Gender.Female, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodF), - (Gender.Female, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightF), - (Gender.Female, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkF), - (Gender.Female, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkF), - (Gender.Female, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunF), - (Gender.Female, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonF), - (Gender.Female, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfF), - (Gender.Female, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardF), - (Gender.Female, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenF), - (Gender.Female, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaF), - (Gender.Female, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM), - (Gender.Female, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM), - (Gender.Female, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaF), - (Gender.Female, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaF), + (Gender.Male, SubRace.Midlander) => Service.GetName(CustomName.MidlanderM), + (Gender.Male, SubRace.Highlander) => Service.GetName(CustomName.HighlanderM), + (Gender.Male, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodM), + (Gender.Male, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightM), + (Gender.Male, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkM), + (Gender.Male, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkM), + (Gender.Male, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunM), + (Gender.Male, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonM), + (Gender.Male, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfM), + (Gender.Male, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardM), + (Gender.Male, SubRace.Raen) => Service.GetName(CustomName.RaenM), + (Gender.Male, SubRace.Xaela) => Service.GetName(CustomName.XaelaM), + (Gender.Male, SubRace.Helion) => Service.GetName(CustomName.HelionM), + (Gender.Male, SubRace.Lost) => Service.GetName(CustomName.LostM), + (Gender.Male, SubRace.Rava) => Service.GetName(CustomName.RavaM), + (Gender.Male, SubRace.Veena) => Service.GetName(CustomName.VeenaM), + (Gender.Female, SubRace.Midlander) => Service.GetName(CustomName.MidlanderF), + (Gender.Female, SubRace.Highlander) => Service.GetName(CustomName.HighlanderF), + (Gender.Female, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodF), + (Gender.Female, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightF), + (Gender.Female, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkF), + (Gender.Female, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkF), + (Gender.Female, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunF), + (Gender.Female, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonF), + (Gender.Female, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfF), + (Gender.Female, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardF), + (Gender.Female, SubRace.Raen) => Service.GetName(CustomName.RaenF), + (Gender.Female, SubRace.Xaela) => Service.GetName(CustomName.XaelaF), + (Gender.Female, SubRace.Helion) => Service.GetName(CustomName.HelionM), + (Gender.Female, SubRace.Lost) => Service.GetName(CustomName.LostM), + (Gender.Female, SubRace.Rava) => Service.GetName(CustomName.RavaF), + (Gender.Female, SubRace.Veena) => Service.GetName(CustomName.VeenaF), _ => "Unknown", }; } @@ -105,12 +120,12 @@ public sealed class CustomizationService : AsyncServiceWrapper Returns whether a clan is valid. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsClanValid(SubRace clan) - => AwaitedService.Clans.Contains(clan); + => Service.Clans.Contains(clan); /// Returns whether a gender is valid for the given race. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsGenderValid(Race race, Gender gender) - => race is Race.Hrothgar ? gender == Gender.Male : AwaitedService.Genders.Contains(gender); + => race is Race.Hrothgar ? gender == Gender.Male : Service.Genders.Contains(gender); /// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -126,7 +141,7 @@ public sealed class CustomizationService : AsyncServiceWrapper Returns whether a customization value is valid for a given clan, gender and face. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsCustomizationValid(SubRace race, Gender gender, CustomizeValue face, CustomizeIndex type, CustomizeValue value) - => IsCustomizationValid(AwaitedService.GetList(race, gender), face, type, value); + => IsCustomizationValid(Service.GetList(race, gender), face, type, value); /// /// Check that the given race and clan are valid. @@ -145,10 +160,10 @@ public sealed class CustomizationService : AsyncServiceWrapper c.ToRace() == race, SubRace.Unknown); + actualClan = Service.Clans.FirstOrDefault(c => c.ToRace() == race, SubRace.Unknown); // This should not happen. if (actualClan == SubRace.Unknown) { @@ -174,7 +189,7 @@ public sealed class CustomizationService : AsyncServiceWrapper public string ValidateGender(Race race, Gender gender, out Gender actualGender) { - if (!AwaitedService.Genders.Contains(gender)) + if (!Service.Genders.Contains(gender)) { actualGender = Gender.Male; return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}."; @@ -251,7 +266,7 @@ public sealed class CustomizationService : AsyncServiceWrapper(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); } - - public void AddServices(IServiceCollection services) - { - services.AddSingleton(PluginInterface); - services.AddSingleton(Commands); - services.AddSingleton(GameData); - services.AddSingleton(ClientState); - services.AddSingleton(Condition); - services.AddSingleton(GameGui); - services.AddSingleton(Chat); - services.AddSingleton(Framework); - services.AddSingleton(Targets); - services.AddSingleton(Objects); - services.AddSingleton(KeyState); - services.AddSingleton(this); - services.AddSingleton(PluginInterface.UiBuilder); - services.AddSingleton(DragDropManager); - services.AddSingleton(TextureProvider); - services.AddSingleton(Log); - services.AddSingleton(Interop); - } - - // @formatter:off - [PluginService][RequiredVersion("1.0")] public DalamudPluginInterface PluginInterface { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ICommandManager Commands { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IDataManager GameData { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IClientState ClientState { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ICondition Condition { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IGameGui GameGui { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IChatGui Chat { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IFramework Framework { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ITargetManager Targets { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IObjectTable Objects { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IKeyState KeyState { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IDragDropManager DragDropManager { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ITextureProvider TextureProvider { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IPluginLog Log { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IGameInteropProvider Interop { get; private set; } = null!; - // @formatter:on } diff --git a/Glamourer/Services/IGamePathParser.cs b/Glamourer/Services/IGamePathParser.cs new file mode 100644 index 0000000..28364d1 --- /dev/null +++ b/Glamourer/Services/IGamePathParser.cs @@ -0,0 +1,6 @@ +namespace Glamourer.Services +{ + internal interface IGamePathParser + { + } +} \ No newline at end of file diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 745469e..20b97f0 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -1,17 +1,17 @@ using System; using System.Linq; using System.Runtime.CompilerServices; -using Dalamud.Plugin; using Dalamud.Plugin.Services; using Lumina.Excel; using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Services; -public class ItemManager : IDisposable +public class ItemManager { public const string Nothing = "Nothing"; public const string SmallClothesNpc = "Smallclothes (NPC)"; @@ -19,30 +19,24 @@ public class ItemManager : IDisposable private readonly Configuration _config; - public readonly IdentifierService IdentifierService; + public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; - public readonly StainData Stains; - public readonly ItemService ItemService; + public readonly DictStains Stains; + public readonly ItemData ItemData; public readonly RestrictedGear RestrictedGear; public readonly EquipItem DefaultSword; - public ItemManager(Configuration config, DalamudPluginInterface pi, IDataManager gameData, IdentifierService identifierService, - ItemService itemService, IPluginLog log) + public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, + ItemData itemData, DictStains stains, RestrictedGear restrictedGear) { - _config = config; - ItemSheet = gameData.GetExcelSheet()!; - IdentifierService = identifierService; - Stains = new StainData(pi, gameData, gameData.Language, log); - ItemService = itemService; - RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData, log); - DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword - } - - public void Dispose() - { - Stains.Dispose(); - RestrictedGear.Dispose(); + _config = config; + ItemSheet = gameData.GetExcelSheet()!; + ObjectIdentification = objectIdentification; + ItemData = itemData; + Stains = stains; + RestrictedGear = restrictedGear; + DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword } public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender) @@ -74,11 +68,12 @@ public class ItemManager : IDisposable if (itemId == SmallclothesId(slot)) return SmallClothesItem(slot); - if (!itemId.IsItem || !ItemService.AwaitedService.TryGetValue(itemId.Item, slot, out var item)) + if (!itemId.IsItem || !ItemData.TryGetValue(itemId.Item, slot, out var item)) return EquipItem.FromId(itemId); if (item.Type.ToSlot() != slot) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 0, 0, 0, + return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, 0, 0, + 0, 0); return item; @@ -89,12 +84,13 @@ public class ItemManager : IDisposable if (itemId == NothingId(type)) return NothingItem(type); - if (!ItemService.AwaitedService.TryGetValue(itemId, type is FullEquipType.Shield ? EquipSlot.MainHand : EquipSlot.OffHand, + if (!ItemData.TryGetValue(itemId, type is FullEquipType.Shield ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return EquipItem.FromId(itemId); if (item.Type != type) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 0, 0, 0, + return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.PrimaryId, item.SecondaryId, item.Variant, 0, 0, + 0, 0); return item; @@ -103,7 +99,7 @@ public class ItemManager : IDisposable public EquipItem Resolve(FullEquipType type, CustomItemId id) => id.IsItem ? Resolve(type, id.Item) : EquipItem.FromId(id); - public EquipItem Identify(EquipSlot slot, SetId id, Variant variant) + public EquipItem Identify(EquipSlot slot, PrimaryId id, Variant variant) { slot = slot.ToSlot(); if (slot.ToIndex() == uint.MaxValue) @@ -114,7 +110,7 @@ public class ItemManager : IDisposable case 0: return NothingItem(slot); case SmallClothesNpcModel: return SmallClothesItem(slot); default: - var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault(); + var item = ObjectIdentification.Identify(id, 0, variant, slot).FirstOrDefault(); return item.Valid ? item : EquipItem.FromIds(0, 0, id, 0, variant, slot.ToEquipType()); @@ -131,7 +127,8 @@ public class ItemManager : IDisposable return NothingItem(offhandType); } - public EquipItem Identify(EquipSlot slot, SetId id, WeaponType type, Variant variant, FullEquipType mainhandType = FullEquipType.Unknown) + public EquipItem Identify(EquipSlot slot, PrimaryId id, SecondaryId type, Variant variant, + FullEquipType mainhandType = FullEquipType.Unknown) { if (slot is EquipSlot.OffHand) { @@ -143,7 +140,7 @@ public class ItemManager : IDisposable if (slot is not EquipSlot.MainHand and not EquipSlot.OffHand) return new EquipItem($"Invalid ({id.Id}-{type.Id}-{variant})", 0, 0, id, type, variant, 0, 0, 0, 0); - var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault(i => i.Type.ToSlot() == slot); + var item = ObjectIdentification.Identify(id, type, variant, slot).FirstOrDefault(i => i.Type.ToSlot() == slot); return item.Valid ? item : EquipItem.FromIds(0, 0, id, type, variant, slot.ToEquipType()); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 8dc4c8d..d88a90c 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Reflection; -using Dalamud.Plugin; +using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Automation; using Glamourer.Designs; @@ -18,22 +15,26 @@ using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.State; +using Glamourer.Structs; using Glamourer.Unlocks; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Services; +using Penumbra.GameData.Actors; using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Structs; namespace Glamourer.Services; -public static class ServiceManager +public static class ServiceManagerA { - public static ServiceProvider CreateProvider(DalamudPluginInterface pi, Logger log) + public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log) { EventWrapper.ChangeLogger(log); - var services = new ServiceCollection() - .AddSingleton(log) - .AddDalamud(pi) + var services = new ServiceManager(log) + .AddExistingService(log) .AddMeta() .AddInterop() .AddEvents() @@ -41,19 +42,16 @@ public static class ServiceManager .AddDesigns() .AddState() .AddUi() - .AddApi() - .AddDebug(); - - return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); - } - - private static IServiceCollection AddDalamud(this IServiceCollection services, DalamudPluginInterface pi) - { - new DalamudServices(pi).AddServices(services); + .AddApi(); + DalamudServices.AddServices(services, pi); + services.AddIServices(typeof(EquipItem).Assembly); + services.AddIServices(typeof(Glamourer).Assembly); + services.AddIServices(typeof(EquipFlag).Assembly); + services.CreateProvider(); return services; } - private static IServiceCollection AddMeta(this IServiceCollection services) + private static ServiceManager AddMeta(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -66,7 +64,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddEvents(this IServiceCollection services) + private static ServiceManager AddEvents(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -82,21 +80,23 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddData(this IServiceCollection services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() + private static ServiceManager AddData(this ServiceManager services) + => services.AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(); - private static IServiceCollection AddInterop(this IServiceCollection services) + private static ServiceManager AddInterop(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) .AddSingleton() .AddSingleton() .AddSingleton() @@ -108,7 +108,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddDesigns(this IServiceCollection services) + private static ServiceManager AddDesigns(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -117,14 +117,14 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddState(this IServiceCollection services) + private static ServiceManager AddState(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); - private static IServiceCollection AddUi(this IServiceCollection services) + private static ServiceManager AddUi(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -157,16 +157,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddDebug(this IServiceCollection services) - { - services.AddSingleton(p => new DebugTab(p)); - var iType = typeof(IDebugTabTree); - foreach (var type in Assembly.GetAssembly(iType)!.GetTypes().Where(t => !t.IsInterface && iType.IsAssignableFrom(t))) - services.AddSingleton(type); - return services; - } - - private static IServiceCollection AddApi(this IServiceCollection services) + private static ServiceManager AddApi(this ServiceManager services) => services.AddSingleton() .AddSingleton(); } diff --git a/Glamourer/Services/ServiceWrapper.cs b/Glamourer/Services/ServiceWrapper.cs deleted file mode 100644 index ded8ef0..0000000 --- a/Glamourer/Services/ServiceWrapper.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Dalamud.Plugin; -using Penumbra.GameData.Actors; -using System; -using System.Threading.Tasks; -using Dalamud.Plugin.Services; -using Glamourer.Interop.Penumbra; -using Penumbra.GameData.Data; -using Penumbra.GameData; - -namespace Glamourer.Services; - -public abstract class AsyncServiceWrapper : IDisposable -{ - public string Name { get; } - public T? Service { get; private set; } - - public T AwaitedService - { - get - { - _task?.Wait(); - return Service!; - } - } - - public bool Valid - => Service != null && !_isDisposed; - - public event Action? FinishedCreation; - private Task? _task; - - private bool _isDisposed; - - protected AsyncServiceWrapper(string name, Func factory) - { - Name = name; - _task = Task.Run(() => - { - var service = factory(); - if (_isDisposed) - { - if (service is IDisposable d) - d.Dispose(); - } - else - { - Service = service; - Glamourer.Log.Verbose($"[{Name}] Created."); - _task = null; - } - }); - _task.ContinueWith((t, x) => - { - if (!_isDisposed) - FinishedCreation?.Invoke(); - }, null); - } - - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - _task = null; - if (Service is IDisposable d) - d.Dispose(); - Glamourer.Log.Verbose($"[{Name}] Disposed."); - } -} - -public sealed class IdentifierService : AsyncServiceWrapper -{ - public IdentifierService(DalamudPluginInterface pi, IDataManager data, ItemService itemService, IPluginLog log) - : base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data, itemService.AwaitedService, log)) - { } -} - -public sealed class ItemService : AsyncServiceWrapper -{ - public ItemService(DalamudPluginInterface pi, IDataManager gameData, IPluginLog log) - : base(nameof(ItemService), () => new ItemData(pi, gameData, gameData.Language, log)) - { } -} - -public sealed class ActorService : AsyncServiceWrapper -{ - public ActorService(DalamudPluginInterface pi, IObjectTable objects, IClientState clientState, IFramework framework, IGameInteropProvider interop, IDataManager gameData, - IGameGui gui, PenumbraService penumbra, IPluginLog log) - : base(nameof(ActorService), - () => new ActorManager(pi, objects, clientState, framework, interop, gameData, gui, idx => (short)penumbra.CutsceneParent(idx), log)) - { } -} diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index b570d45..5ab3f41 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -164,20 +164,21 @@ public unsafe class FunModule : IDisposable if (!_codes.EnabledEmperor) return; - void SetItem(EquipSlot slot2, ref CharacterArmor armor) - { - var list = _items.ItemService.AwaitedService[slot2.ToEquipType()]; - var rng = _rng.Next(0, list.Count - 1); - var item = list[rng]; - armor.Set = item.ModelId; - armor.Variant = item.Variant; - } - if (armors.Length == 1) SetItem(slot, ref armors[0]); else for (var i = 0u; i < armors.Length; ++i) SetItem(i.ToEquipSlot(), ref armors[(int)i]); + return; + + void SetItem(EquipSlot slot2, ref CharacterArmor armor) + { + var list = _items.ItemData.ByType[slot2.ToEquipType()]; + var rng = _rng.Next(0, list.Count - 1); + var item = list[rng]; + armor.Set = item.PrimaryId; + armor.Variant = item.Variant; + } } public void ApplyOops(ref Customize customize) @@ -197,7 +198,7 @@ public unsafe class FunModule : IDisposable if (!_codes.EnabledIndividual) return; - var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender); + var set = _customizations.Service.GetList(customize.Clan, customize.Gender); foreach (var index in Enum.GetValues()) { if (index is CustomizeIndex.Face || !set.IsAvailable(index)) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 5eaf672..a91a1eb 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -188,7 +188,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We /// Apply a weapon to the offhand. public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain) { - stain = weapon.ModelId.Id == 0 ? 0 : stain; + stain = weapon.PrimaryId.Id == 0 ? 0 : stain; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 3b9d0cb..ebc2661 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -5,7 +5,7 @@ using Glamourer.Customization; using Glamourer.Events; using Glamourer.Services; using Glamourer.Structs; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -74,7 +74,7 @@ public class StateEditor state[CustomizeIndex.Clan] = source; state[CustomizeIndex.Gender] = source; - var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) state[index] = source; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index ae6de2f..7cc8b82 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -7,13 +7,13 @@ using Glamourer.Interop.Structs; using Glamourer.Services; using OtterGui.Classes; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using Glamourer.Structs; +using Penumbra.GameData.DataContainers; namespace Glamourer.State; @@ -25,7 +25,7 @@ namespace Glamourer.State; public class StateListener : IDisposable { private readonly Configuration _config; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ObjectManager _objects; private readonly StateManager _manager; private readonly StateApplier _applier; @@ -50,7 +50,7 @@ public class StateListener : IDisposable private ActorState? _creatingState; private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; - public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config, + public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, @@ -111,7 +111,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - _creatingIdentifier = actor.GetIdentifier(_actors.AwaitedService); + _creatingIdentifier = actor.GetIdentifier(_actors); ref var modelId = ref *(uint*)modelPtr; ref var customize = ref *(Customize*)customizePtr; @@ -149,7 +149,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -169,7 +169,7 @@ public class StateListener : IDisposable return; } - var set = _customizations.AwaitedService.GetList(model.Clan, model.Gender); + var set = _customizations.Service.GetList(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { if (state[index] is not StateChanged.Source.Fixed) @@ -211,7 +211,7 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors.AwaitedService, out var identifier) + if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor.Value); @@ -238,7 +238,7 @@ public class StateListener : IDisposable var currentItem = state.BaseData.Item(slot); var model = state.ModelData.Weapon(slot); var current = currentItem.Weapon(state.BaseData.Stain(slot)); - if (model.Value == current.Value || !_items.ItemService.AwaitedService.TryGetValue(item, EquipSlot.MainHand, out var changedItem)) + if (model.Value == current.Value || !_items.ItemData.TryGetValue(item, EquipSlot.MainHand, out var changedItem)) continue; var changed = changedItem.Weapon(stain); @@ -272,10 +272,10 @@ public class StateListener : IDisposable return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Set.Id != 0 && _lastFistOffhand.Set.Id != 0) + if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Y.Id != 0 && _lastFistOffhand.Y.Id != 0) weapon.Value = _lastFistOffhand; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -308,13 +308,13 @@ public class StateListener : IDisposable var newWeapon = state.ModelData.Weapon(slot); if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene) actorWeapon = newWeapon; - else if (actorWeapon.Set.Id != 0) + else if (actorWeapon.X.Id != 0) actorWeapon = actorWeapon.With(newWeapon.Stain); } // Fist Weapon Offhand hack. - if (slot is EquipSlot.MainHand && weapon.Value.Set.Id is > 1600 and < 1651) - _lastFistOffhand = new CharacterWeapon((SetId)(weapon.Value.Set.Id + 50), weapon.Value.Type, weapon.Value.Variant, + if (slot is EquipSlot.MainHand && weapon.Value.X.Id is > 1600 and < 1651) + _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.X.Id + 50), weapon.Value.Y, weapon.Value.Variant, weapon.Value.Stain); _funModule.ApplyFun(actor, ref weapon.Value, slot); @@ -329,7 +329,7 @@ public class StateListener : IDisposable return false; var offhand = actor.GetOffhand(); - return offhand.Variant == 0 && offhand.Set.Id != 0 && armor.Set.Id == offhand.Set.Id; + return offhand.Variant == 0 && offhand.Y.Id != 0 && armor.Set.Id == offhand.Y.Id; } var actorArmor = actor.GetArmor(slot); @@ -413,7 +413,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -439,7 +439,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -474,7 +474,7 @@ public class StateListener : IDisposable var change = UpdateState.NoChange; // Fist weapon bug hack - if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Set.Id is > 1600 and < 1651) + if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().X.Id is > 1600 and < 1651) return UpdateState.NoChange; if (baseData.Stain != weapon.Stain) @@ -483,9 +483,9 @@ public class StateListener : IDisposable change = UpdateState.Change; } - if (baseData.Set.Id != weapon.Set.Id || baseData.Type.Id != weapon.Type.Id || baseData.Variant != weapon.Variant) + if (baseData.X.Id != weapon.X.Id || baseData.Y.Id != weapon.Y.Id || baseData.Variant != weapon.Variant) { - var item = _items.Identify(slot, weapon.Set, weapon.Type, weapon.Variant, + var item = _items.Identify(slot, weapon.X, weapon.Y, weapon.Variant, slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown); state.BaseData.SetItem(slot, item); change = UpdateState.Change; @@ -555,7 +555,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier)) + if (!actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) @@ -588,7 +588,7 @@ public class StateListener : IDisposable // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors.AwaitedService, out var identifier)) + if (!actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) @@ -621,7 +621,7 @@ public class StateListener : IDisposable // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors.AwaitedService, out var identifier)) + if (!actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 68c9cfc..8446288 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -12,17 +12,17 @@ using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.Structs; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateManager(ActorService _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, +public class StateManager(ActorManager _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, HumanModelList _humans, ICondition _condition, IClientState _clientState) : IReadOnlyDictionary { - private readonly Dictionary _states = new(); + private readonly Dictionary _states = []; public IEnumerator> GetEnumerator() => _states.GetEnumerator(); @@ -50,7 +50,7 @@ public class StateManager(ActorService _actors, ItemManager _items, StateChanged /// public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state) - => GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out state); + => GetOrCreate(actor.GetIdentifier(_actors), actor, out state); /// Try to obtain or create a new state for an existing actor. Returns false if no state could be created. public unsafe bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state) @@ -170,8 +170,8 @@ public class StateManager(ActorService _actors, ItemManager _items, StateChanged } // Set the weapons regardless of source. - var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, main.Variant); - var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, off.Variant, mainItem.Type); + var mainItem = _items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); + var offItem = _items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type); ret.SetItem(EquipSlot.MainHand, mainItem); ret.SetStain(EquipSlot.MainHand, main.Stain); ret.SetItem(EquipSlot.OffHand, offItem); @@ -190,13 +190,13 @@ public class StateManager(ActorService _actors, ItemManager _items, StateChanged /// This is hardcoded in the game. private void FistWeaponHack(ref DesignData ret, ref CharacterWeapon mainhand, ref CharacterWeapon offhand) { - if (mainhand.Set.Id is < 1601 or >= 1651) + if (mainhand.Skeleton.Id is < 1601 or >= 1651) return; - var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant)offhand.Type.Id); - offhand.Set = (SetId)(mainhand.Set.Id + 50); + var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id); + offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50); offhand.Variant = mainhand.Variant; - offhand.Type = mainhand.Type; + offhand.Weapon = mainhand.Weapon; ret.SetItem(EquipSlot.Hands, gauntlets); ret.SetStain(EquipSlot.Hands, mainhand.Stain); } diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 19a3f6c..8764438 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -178,11 +178,11 @@ public class CustomizeUnlockManager : IDisposable, ISavable { var ret = new Dictionary(); var sheet = gameData.GetExcelSheet(ClientLanguage.English)!; - foreach (var clan in customizations.AwaitedService.Clans) + foreach (var clan in customizations.Service.Clans) { - foreach (var gender in customizations.AwaitedService.Genders) + foreach (var gender in customizations.Service.Genders) { - var list = customizations.AwaitedService.GetList(clan, gender); + var list = customizations.Service.GetList(clan, gender); foreach (var hair in list.HairStyles) { var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value); diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 972a393..fa7d74a 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet; @@ -22,7 +23,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary _unlocked = new(); @@ -45,7 +46,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary Unlockable; public ItemUnlockManager(SaveService saveService, ItemManager items, IClientState clientState, IDataManager gameData, IFramework framework, - ObjectUnlocked @event, IdentifierService identifier, IGameInteropProvider interop) + ObjectUnlocked @event, ObjectIdentification identifier, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _saveService = saveService; @@ -100,12 +101,12 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary _items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out _), "item"); + id => _items.ItemData.TryGetValue(id, EquipSlot.MainHand, out _), "item"); UpdateModels(version); } @@ -282,7 +283,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary()!; foreach (var row in cabinet) { - if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) + if (items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) ret.TryAdd(item.ItemId, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet)); } @@ -290,7 +291,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary()!; foreach (var row in gilShopItem) { - if (!items.ItemService.AwaitedService.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) + if (!items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) continue; var quest1 = row.QuestRequired[0].Row; @@ -323,10 +324,10 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary Date: Thu, 21 Dec 2023 15:22:31 +0100 Subject: [PATCH 02/31] Again. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 37b9bcf..3787e82 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 37b9bcf6727bd902fdb19a0a8b9d80b94f4cdd10 +Subproject commit 3787e82d1b84d2542b6e4238060d75383a4b12a1 From 648b3d4515468d9fd40748f504c6d8d66dbb8643 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Dec 2023 17:05:24 +0100 Subject: [PATCH 03/31] Huh. --- Glamourer/Designs/DesignBase64Migration.cs | 10 +++++----- Glamourer/Designs/DesignConverter.cs | 4 ++-- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 2 +- Glamourer/Interop/WeaponService.cs | 4 ++-- Glamourer/State/StateListener.cs | 16 ++++++++-------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index c756395..c36c1ea 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -121,9 +121,9 @@ public class DesignBase64Migration data.SetStain(slot, mdl.Stain); } - var main = cur[0].X.Id == 0 + var main = cur[0].Skeleton.Id == 0 ? items.DefaultSword - : items.Identify(EquipSlot.MainHand, cur[0].X, cur[0].Y, cur[0].Variant); + : items.Identify(EquipSlot.MainHand, cur[0].Skeleton, cur[0].Weapon, cur[0].Variant); if (!main.Valid) { Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified."); @@ -138,7 +138,7 @@ public class DesignBase64Migration if (main.PrimaryId.Id is > 1600 and < 1651 && cur[1].Variant == 0) { off = items.Identify(EquipSlot.OffHand, (PrimaryId)(main.PrimaryId.Id + 50), main.SecondaryId, main.Variant, main.Type); - var gauntlet = items.Identify(EquipSlot.Hands, cur[1].X, (Variant)cur[1].Y.Id); + var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Skeleton, (Variant)cur[1].Weapon.Id); if (gauntlet.Valid) { data.SetItem(EquipSlot.Hands, gauntlet); @@ -147,9 +147,9 @@ public class DesignBase64Migration } else { - off = cur[0].X.Id == 0 + off = cur[0].Skeleton.Id == 0 ? ItemManager.NothingItem(FullEquipType.Shield) - : items.Identify(EquipSlot.OffHand, cur[1].X, cur[1].Y, cur[1].Variant, main.Type); + : items.Identify(EquipSlot.OffHand, cur[1].Skeleton, cur[1].Weapon, cur[1].Variant, main.Type); } if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 038509d..86fd0ce 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -165,7 +165,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (slot, item, armor.Stain); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.X, mainhand.Y, mainhand.Variant, FullEquipType.Unknown); + var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant, FullEquipType.Unknown); if (!mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); @@ -174,7 +174,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (EquipSlot.MainHand, mh, mainhand.Stain); - var oh = _items.Identify(EquipSlot.OffHand, offhand.X, offhand.Y, offhand.Variant, mh.Type); + var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); if (!oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index f244fd5..e70cd5d 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -177,7 +177,7 @@ public class PenumbraChangedItemTooltip : IDisposable private bool CanApplyWeapon(EquipSlot slot, EquipItem item) { var main = _objects.Player.GetMainhand(); - var mainItem = _items.Identify(slot, main.X, main.Y, main.Variant); + var mainItem = _items.Identify(slot, main.Skeleton, main.Weapon, main.Variant); if (slot == EquipSlot.MainHand) return item.Type == mainItem.Type; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 544fe32..7ccf963 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -76,7 +76,7 @@ public unsafe class WeaponService : IDisposable if (tmpWeapon.Value != weapon.Value) { - if (tmpWeapon.X.Id == 0) + if (tmpWeapon.Skeleton.Id == 0) tmpWeapon.Stain = 0; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); } @@ -119,7 +119,7 @@ public unsafe class WeaponService : IDisposable var mdl = character.Model; var (_, _, mh, oh) = mdl.GetWeapons(character); var value = slot == EquipSlot.OffHand ? oh : mh; - var weapon = value.With(value.X.Id == 0 ? 0 : stain); + var weapon = value.With(value.Skeleton.Id == 0 ? 0 : stain); LoadWeapon(character, slot, weapon); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 7cc8b82..f5f2e3b 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -272,7 +272,7 @@ public class StateListener : IDisposable return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Y.Id != 0 && _lastFistOffhand.Y.Id != 0) + if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0) weapon.Value = _lastFistOffhand; if (!actor.Identifier(_actors, out var identifier) @@ -308,13 +308,13 @@ public class StateListener : IDisposable var newWeapon = state.ModelData.Weapon(slot); if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene) actorWeapon = newWeapon; - else if (actorWeapon.X.Id != 0) + else if (actorWeapon.Skeleton.Id != 0) actorWeapon = actorWeapon.With(newWeapon.Stain); } // Fist Weapon Offhand hack. - if (slot is EquipSlot.MainHand && weapon.Value.X.Id is > 1600 and < 1651) - _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.X.Id + 50), weapon.Value.Y, weapon.Value.Variant, + if (slot is EquipSlot.MainHand && weapon.Value.Skeleton.Id is > 1600 and < 1651) + _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.Skeleton.Id + 50), weapon.Value.Weapon, weapon.Value.Variant, weapon.Value.Stain); _funModule.ApplyFun(actor, ref weapon.Value, slot); @@ -329,7 +329,7 @@ public class StateListener : IDisposable return false; var offhand = actor.GetOffhand(); - return offhand.Variant == 0 && offhand.Y.Id != 0 && armor.Set.Id == offhand.Y.Id; + return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Weapon.Id; } var actorArmor = actor.GetArmor(slot); @@ -474,7 +474,7 @@ public class StateListener : IDisposable var change = UpdateState.NoChange; // Fist weapon bug hack - if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().X.Id is > 1600 and < 1651) + if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651) return UpdateState.NoChange; if (baseData.Stain != weapon.Stain) @@ -483,9 +483,9 @@ public class StateListener : IDisposable change = UpdateState.Change; } - if (baseData.X.Id != weapon.X.Id || baseData.Y.Id != weapon.Y.Id || baseData.Variant != weapon.Variant) + if (baseData.Skeleton.Id != weapon.Skeleton.Id || baseData.Weapon.Id != weapon.Weapon.Id || baseData.Variant != weapon.Variant) { - var item = _items.Identify(slot, weapon.X, weapon.Y, weapon.Variant, + var item = _items.Identify(slot, weapon.Skeleton, weapon.Weapon, weapon.Variant, slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown); state.BaseData.SetItem(slot, item); change = UpdateState.Change; From 987c26a51d619700cfc3ddc0b0ff276c8291b3aa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Dec 2023 14:20:50 +0100 Subject: [PATCH 04/31] Remove GameData, move a bunch of customization data to Penumbra.GameData and the rest to Glamourer, update accordingly. Some reformatting and cleanup. --- Glamourer.GameData/Customization/CmpFile.cs | 46 ----- Glamourer.GameData/Customization/Customize.cs | 124 ------------ .../Customization/CustomizeFlag.cs | 114 ----------- .../Customization/CustomizeIndex.cs | 183 ------------------ .../Customization/CustomizeValue.cs | 34 ---- .../Customization/DatCharacterFile.cs | 149 -------------- Glamourer.GameData/GameData.cs | 88 --------- .../Glamourer - Backup.GameData.csproj | 61 ------ Glamourer.GameData/Glamourer.GameData.csproj | 72 ------- Glamourer.GameData/Offsets.cs | 7 - Glamourer.GameData/Structs/CrestFlag.cs | 102 ---------- Glamourer.GameData/Structs/EquipFlag.cs | 93 --------- Glamourer.GameData/Structs/Job.cs | 29 --- Glamourer.GameData/Structs/JobGroup.cs | 61 ------ Glamourer.sln | 6 - .../Api/GlamourerIpc.GetCustomization.cs | 6 +- Glamourer/Automation/AutoDesign.cs | 6 +- Glamourer/Automation/AutoDesignApplier.cs | 4 +- Glamourer/Automation/AutoDesignManager.cs | 3 +- Glamourer/Automation/FixedDesignMigrator.cs | 26 +-- Glamourer/Designs/DesignBase.cs | 9 +- Glamourer/Designs/DesignBase64Migration.cs | 12 +- Glamourer/Designs/DesignConverter.cs | 4 +- Glamourer/Designs/DesignData.cs | 56 +++--- Glamourer/Designs/DesignManager.cs | 10 +- .../GameData}/CharaMakeParams.cs | 8 +- Glamourer/GameData/ColorParameters.cs | 50 +++++ .../GameData}/CustomName.cs | 4 +- .../GameData}/CustomizationManager.cs | 2 +- .../GameData}/CustomizationNpcOptions.cs | 6 +- .../GameData}/CustomizationOptions.cs | 56 +++--- .../GameData}/CustomizationSet.cs | 10 +- .../GameData}/CustomizeData.cs | 4 +- .../GameData}/ICustomizationManager.cs | 2 +- .../GameData}/NpcCustomizeSet.cs | 120 ++++++------ .../GameData}/NpcData.cs | 4 +- Glamourer/Glamourer.csproj | 5 +- .../CustomizationDrawer.Color.cs | 12 +- .../CustomizationDrawer.GenderRace.cs | 2 +- .../Customization/CustomizationDrawer.Icon.cs | 20 +- .../CustomizationDrawer.Simple.cs | 8 +- .../Gui/Customization/CustomizationDrawer.cs | 29 ++- Glamourer/Gui/DesignCombo.cs | 3 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 5 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 28 ++- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 95 ++++----- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 2 +- .../DebugTab/CustomizationServicePanel.cs | 3 +- .../Tabs/DebugTab/CustomizationUnlockPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 5 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 2 - .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 8 +- Glamourer/Gui/Tabs/DebugTab/JobPanel.cs | 4 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 30 ++- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 17 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 - Glamourer/Gui/Tabs/NpcCombo.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 1 - Glamourer/Gui/ToggleDrawData.cs | 1 - Glamourer/Gui/UiHelpers.cs | 4 +- Glamourer/Interop/ChangeCustomizeService.cs | 8 +- Glamourer/Interop/CharaFile/CharaFile.cs | 16 +- Glamourer/Interop/CharaFile/CmaFile.cs | 4 +- Glamourer/Interop/CrestService.cs | 2 - Glamourer/Interop/ImportService.cs | 9 +- Glamourer/Interop/JobService.cs | 19 +- Glamourer/Interop/Structs/Actor.cs | 6 +- Glamourer/Interop/Structs/Model.cs | 5 +- Glamourer/Interop/UpdateSlotService.cs | 1 + Glamourer/Interop/WeaponService.cs | 10 +- Glamourer/Services/CommandService.cs | 2 - Glamourer/Services/CustomizationService.cs | 14 +- Glamourer/Services/ServiceManager.cs | 2 +- Glamourer/State/ActorState.cs | 8 +- Glamourer/State/FunModule.cs | 13 +- Glamourer/State/StateApplier.cs | 14 +- Glamourer/State/StateEditor.cs | 10 +- Glamourer/State/StateListener.cs | 14 +- Glamourer/State/StateManager.cs | 36 ++-- Glamourer/State/WorldSets.cs | 1 - Glamourer/Unlocks/CustomizeUnlockManager.cs | 5 +- Penumbra.GameData | 2 +- 83 files changed, 444 insertions(+), 1620 deletions(-) delete mode 100644 Glamourer.GameData/Customization/CmpFile.cs delete mode 100644 Glamourer.GameData/Customization/Customize.cs delete mode 100644 Glamourer.GameData/Customization/CustomizeFlag.cs delete mode 100644 Glamourer.GameData/Customization/CustomizeIndex.cs delete mode 100644 Glamourer.GameData/Customization/CustomizeValue.cs delete mode 100644 Glamourer.GameData/Customization/DatCharacterFile.cs delete mode 100644 Glamourer.GameData/GameData.cs delete mode 100644 Glamourer.GameData/Glamourer - Backup.GameData.csproj delete mode 100644 Glamourer.GameData/Glamourer.GameData.csproj delete mode 100644 Glamourer.GameData/Offsets.cs delete mode 100644 Glamourer.GameData/Structs/CrestFlag.cs delete mode 100644 Glamourer.GameData/Structs/EquipFlag.cs delete mode 100644 Glamourer.GameData/Structs/Job.cs delete mode 100644 Glamourer.GameData/Structs/JobGroup.cs rename {Glamourer.GameData/Customization => Glamourer/GameData}/CharaMakeParams.cs (94%) create mode 100644 Glamourer/GameData/ColorParameters.cs rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomName.cs (78%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationManager.cs (93%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationNpcOptions.cs (92%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationOptions.cs (94%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationSet.cs (95%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizeData.cs (89%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/ICustomizationManager.cs (90%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/NpcCustomizeSet.cs (63%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/NpcData.cs (93%) diff --git a/Glamourer.GameData/Customization/CmpFile.cs b/Glamourer.GameData/Customization/CmpFile.cs deleted file mode 100644 index d62e5da..0000000 --- a/Glamourer.GameData/Customization/CmpFile.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Logging; -using Dalamud.Plugin.Services; - -namespace Glamourer.Customization; - -// Convert the Human.Cmp file into color sets. -// If the file can not be read due to TexTools corruption, create a 0-array of size MinSize. -internal class CmpFile -{ - private readonly Lumina.Data.FileResource? _file; - private readonly uint[] _rgbaColors; - - // No error checking since only called internally. - public IEnumerable GetSlice(int offset, int count) - => _rgbaColors.Length >= offset + count ? _rgbaColors.Skip(offset).Take(count) : Enumerable.Repeat(0u, count); - - public bool Valid - => _file != null; - - public CmpFile(IDataManager gameData, IPluginLog log) - { - try - { - _file = gameData.GetFile("chara/xls/charamake/human.cmp")!; - _rgbaColors = new uint[_file.Data.Length >> 2]; - for (var i = 0; i < _file.Data.Length; i += 4) - { - _rgbaColors[i >> 2] = _file.Data[i] - | (uint)(_file.Data[i + 1] << 8) - | (uint)(_file.Data[i + 2] << 16) - | (uint)(_file.Data[i + 3] << 24); - } - } - catch (Exception e) - { - log.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" - + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" - + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); - _file = null; - _rgbaColors = Array.Empty(); - } - } -} diff --git a/Glamourer.GameData/Customization/Customize.cs b/Glamourer.GameData/Customization/Customize.cs deleted file mode 100644 index b19ef22..0000000 --- a/Glamourer.GameData/Customization/Customize.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Customization; - -public unsafe struct Customize -{ - public CustomizeArray Data; - - public Customize(in CustomizeArray data) - { - Data = data.Clone(); - } - - public Race Race - { - get => (Race)Data.Get(CustomizeIndex.Race).Value; - set => Data.Set(CustomizeIndex.Race, (CustomizeValue)(byte)value); - } - - public Gender Gender - { - get => (Gender)Data.Get(CustomizeIndex.Gender).Value + 1; - set => Data.Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1); - } - - public CustomizeValue BodyType - { - get => Data.Get(CustomizeIndex.BodyType); - set => Data.Set(CustomizeIndex.BodyType, value); - } - - public SubRace Clan - { - get => (SubRace)Data.Get(CustomizeIndex.Clan).Value; - set => Data.Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value); - } - - public CustomizeValue Face - { - get => Data.Get(CustomizeIndex.Face); - set => Data.Set(CustomizeIndex.Face, value); - } - - - public static readonly Customize Default = GenerateDefault(); - public static readonly Customize Empty = new(); - - public CustomizeValue Get(CustomizeIndex index) - => Data.Get(index); - - public bool Set(CustomizeIndex flag, CustomizeValue index) - => Data.Set(flag, index); - - public bool Equals(Customize other) - => Equals(Data, other.Data); - - public CustomizeValue this[CustomizeIndex index] - { - get => Get(index); - set => Set(index, value); - } - - private static Customize GenerateDefault() - { - var ret = new Customize - { - Race = Race.Hyur, - Clan = SubRace.Midlander, - Gender = Gender.Male, - }; - 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; - } - - public void Load(Customize other) - => Data.Read(&other.Data); - - public readonly void Write(nint target) - => Data.Write((void*)target); - - public bool LoadBase64(string data) - => Data.LoadBase64(data); - - public readonly string WriteBase64() - => Data.WriteBase64(); - - public static CustomizeFlag Compare(Customize lhs, Customize rhs) - { - CustomizeFlag ret = 0; - foreach (var idx in Enum.GetValues()) - { - var l = lhs[idx]; - var r = rhs[idx]; - if (l.Value != r.Value) - ret |= idx.ToFlag(); - } - - return ret; - } - - public override string ToString() - => Data.ToString(); -} diff --git a/Glamourer.GameData/Customization/CustomizeFlag.cs b/Glamourer.GameData/Customization/CustomizeFlag.cs deleted file mode 100644 index 44b6cee..0000000 --- a/Glamourer.GameData/Customization/CustomizeFlag.cs +++ /dev/null @@ -1,114 +0,0 @@ -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 -{ - public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul); - public const CustomizeFlag AllRelevant = All & ~CustomizeFlag.BodyType & ~CustomizeFlag.Race; - - public const CustomizeFlag RedrawRequired = - CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face | CustomizeFlag.BodyType; - - public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set) - => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); - - public static bool RequiresRedraw(this CustomizeFlag flags) - => (flags & RedrawRequired) != 0; - - [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, - }; - - public static bool SetIfDifferent(ref this CustomizeFlag flags, CustomizeFlag flag, bool value) - { - var newValue = value ? flags | flag : flags & ~flag; - if (newValue == flags) - return false; - - flags = newValue; - return true; - } -} diff --git a/Glamourer.GameData/Customization/CustomizeIndex.cs b/Glamourer.GameData/Customization/CustomizeIndex.cs deleted file mode 100644 index cbd22ed..0000000 --- a/Glamourer.GameData/Customization/CustomizeIndex.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Linq; -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 -{ - public const int NumIndices = (int)CustomizeIndex.FacePaintColor + 1; - - public static readonly CustomizeIndex[] All = Enum.GetValues() - .Where(v => v is not CustomizeIndex.Race and not CustomizeIndex.BodyType).ToArray(); - - public static readonly CustomizeIndex[] AllBasic = All - .Where(v => v is not CustomizeIndex.Gender and not CustomizeIndex.Clan).ToArray(); - - [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, 0x80), - 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 => "Enable Highlights", - CustomizeIndex.SkinColor => "Skin Color", - CustomizeIndex.EyeColorRight => "Left Eye", // inverted due to compatibility fuckup. - CustomizeIndex.HairColor => "Hair Color", - CustomizeIndex.HighlightsColor => "Highlights Color", - CustomizeIndex.TattooColor => "Tattoo Color", - CustomizeIndex.Eyebrows => "Eyebrow Style", - CustomizeIndex.EyeColorLeft => "Right Eye", // inverted due to compatibility fuckup. - CustomizeIndex.EyeShape => "Small Pupils", - 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.CustomizeArray 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.CustomizeArray 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 deleted file mode 100644 index 25b3406..0000000 --- a/Glamourer.GameData/Customization/CustomizeValue.cs +++ /dev/null @@ -1,34 +0,0 @@ -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/Customization/DatCharacterFile.cs b/Glamourer.GameData/Customization/DatCharacterFile.cs deleted file mode 100644 index f33af69..0000000 --- a/Glamourer.GameData/Customization/DatCharacterFile.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using Dalamud.Memory; -using Penumbra.GameData.Structs; - -namespace Glamourer.Customization; - -[StructLayout(LayoutKind.Explicit, Size = Size)] -public unsafe struct DatCharacterFile -{ - public const int Size = 4 + 4 + 4 + 4 + CustomizeArray.Size + 2 + 4 + 41 * 4; // 212 - - [FieldOffset(0)] - private fixed byte _data[Size]; - - [FieldOffset(0)] - public readonly uint Magic = 0x2013FF14; - - [FieldOffset(4)] - public readonly uint Version = 0x05; - - [FieldOffset(8)] - private uint _checksum; - - [FieldOffset(12)] - private readonly uint _padding = 0; - - [FieldOffset(16)] - private CustomizeArray _customize; - - [FieldOffset(16 + CustomizeArray.Size)] - private ushort _voice; - - [FieldOffset(16 + CustomizeArray.Size + 2)] - private uint _timeStamp; - - [FieldOffset(Size - 41 * 4)] - private fixed byte _description[41 * 4]; - - public readonly void Write(Stream stream) - { - for (var i = 0; i < Size; ++i) - stream.WriteByte(_data[i]); - } - - public static bool Read(Stream stream, out DatCharacterFile file) - { - if (stream.Length - stream.Position != Size) - { - file = default; - return false; - } - - file = new DatCharacterFile(stream); - return true; - } - - private DatCharacterFile(Stream stream) - { - for (var i = 0; i < Size; ++i) - _data[i] = (byte)stream.ReadByte(); - } - - public DatCharacterFile(in Customize customize, byte voice, string text) - { - SetCustomize(customize); - SetVoice(voice); - SetTime(DateTimeOffset.UtcNow); - SetDescription(text); - _checksum = CalculateChecksum(); - } - - public readonly uint CalculateChecksum() - { - var ret = 0u; - for (var i = 16; i < Size; i++) - ret ^= (uint)(_data[i] << ((i - 16) % 24)); - return ret; - } - - public readonly uint Checksum - => _checksum; - - public Customize Customize - { - readonly get => new(_customize); - set - { - SetCustomize(value); - _checksum = CalculateChecksum(); - } - } - - public ushort Voice - { - readonly get => _voice; - set - { - SetVoice(value); - _checksum = CalculateChecksum(); - } - } - - public string Description - { - readonly get - { - fixed (byte* ptr = _description) - { - return MemoryHelper.ReadStringNullTerminated((nint)ptr); - } - } - set - { - SetDescription(value); - _checksum = CalculateChecksum(); - } - } - - public DateTimeOffset Time - { - readonly get => DateTimeOffset.FromUnixTimeSeconds(_timeStamp); - set - { - SetTime(value); - _checksum = CalculateChecksum(); - } - } - - private void SetTime(DateTimeOffset time) - => _timeStamp = (uint)time.ToUnixTimeSeconds(); - - private void SetCustomize(in Customize customize) - => _customize = customize.Data.Clone(); - - private void SetVoice(ushort voice) - => _voice = voice; - - private void SetDescription(string text) - { - fixed (byte* ptr = _description) - { - var span = new Span(ptr, 41 * 4); - Encoding.UTF8.GetBytes(text.AsSpan(0, Math.Min(40, text.Length)), span); - } - } -} diff --git a/Glamourer.GameData/GameData.cs b/Glamourer.GameData/GameData.cs deleted file mode 100644 index d5638b0..0000000 --- a/Glamourer.GameData/GameData.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Dalamud; -using Dalamud.Plugin.Services; -using Glamourer.Structs; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer; - -public static class GameData -{ - private static Dictionary? _jobs; - private static Dictionary? _jobGroups; - private static JobGroup[]? _allJobGroups; - - public static IReadOnlyDictionary Jobs(IDataManager dataManager) - { - if (_jobs != null) - return _jobs; - - var sheet = dataManager.GetExcelSheet()!; - _jobs = sheet.Where(j => j.Abbreviation.RawData.Length > 0).ToDictionary(j => (byte)j.RowId, j => new Job(j)); - return _jobs; - } - - public static IReadOnlyList AllJobGroups(IDataManager dataManager) - { - if (_allJobGroups != null) - return _allJobGroups; - - var sheet = dataManager.GetExcelSheet()!; - var jobs = dataManager.GetExcelSheet(ClientLanguage.English)!; - _allJobGroups = sheet.Select(j => new JobGroup(j, jobs)).ToArray(); - return _allJobGroups; - } - - public static IReadOnlyDictionary JobGroups(IDataManager dataManager) - { - if (_jobGroups != null) - return _jobGroups; - - static bool ValidIndex(uint idx) - { - if (idx is > 0 and < 36) - return true; - - return idx switch - { - // Single jobs and big groups - 91 => true, - 92 => true, - 96 => true, - 98 => true, - 99 => true, - 111 => true, - 112 => true, - 129 => true, - 149 => true, - 150 => true, - 156 => true, - 157 => true, - 158 => true, - 159 => true, - 180 => true, - 181 => true, - 188 => true, - 189 => true, - - // Class + Job - 38 => true, - 41 => true, - 44 => true, - 47 => true, - 50 => true, - 53 => true, - 55 => true, - 69 => true, - 68 => true, - 93 => true, - _ => false, - }; - } - - _jobGroups = AllJobGroups(dataManager).Where(j => ValidIndex(j.Id)) - .ToDictionary(j => (ushort) j.Id, j => j); - return _jobGroups; - } -} diff --git a/Glamourer.GameData/Glamourer - Backup.GameData.csproj b/Glamourer.GameData/Glamourer - Backup.GameData.csproj deleted file mode 100644 index 74d1588..0000000 --- a/Glamourer.GameData/Glamourer - Backup.GameData.csproj +++ /dev/null @@ -1,61 +0,0 @@ - - - net472 - preview - Glamourer - Glamourer.GameData - 1.0.0.0 - 1.0.0.0 - SoftOtter - Glamourer - Copyright © 2020 - true - Library - 4 - true - enable - bin\$(Configuration)\ - $(MSBuildWarningsAsMessages);MSB3277 - - - - full - DEBUG;TRACE - - - - pdbonly - - - - $(MSBuildWarningsAsMessages);MSB3277 - - - - - $(DALAMUD_ROOT)\Dalamud.dll - ..\libs\Dalamud.dll - $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll - False - - - $(DALAMUD_ROOT)\Lumina.dll - ..\libs\Lumina.dll - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll - False - - - $(DALAMUD_ROOT)\Lumina.Excel.dll - ..\libs\Lumina.Excel.dll - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll - False - - - ..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll - - - - - - - diff --git a/Glamourer.GameData/Glamourer.GameData.csproj b/Glamourer.GameData/Glamourer.GameData.csproj deleted file mode 100644 index 92bf301..0000000 --- a/Glamourer.GameData/Glamourer.GameData.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - net7.0-windows - preview - x64 - Glamourer - Glamourer.GameData - 1.0.0.0 - 1.0.0.0 - SoftOtter - Glamourer - Copyright © 2020 - true - Library - 4 - true - enable - bin\$(Configuration)\ - $(MSBuildWarningsAsMessages);MSB3277 - false - false - - - - full - DEBUG;TRACE - - - - pdbonly - - - - $(MSBuildWarningsAsMessages);MSB3277 - - - - $(AppData)\XIVLauncher\addon\Hooks\dev\ - - - - - $(DalamudLibPath)Dalamud.dll - False - - - $(DalamudLibPath)FFXIVClientStructs.dll - False - - - $(DalamudLibPath)Lumina.dll - False - - - $(DalamudLibPath)Lumina.Excel.dll - False - - - $(DalamudLibPath)Newtonsoft.Json.dll - False - - - $(DalamudLibPath)ImGuiScene.dll - False - - - - - - - - \ No newline at end of file diff --git a/Glamourer.GameData/Offsets.cs b/Glamourer.GameData/Offsets.cs deleted file mode 100644 index 71151d9..0000000 --- a/Glamourer.GameData/Offsets.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Glamourer; - -public static class Sigs -{ - public const string ChangeJob = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 80 61"; - public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A"; -} diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs deleted file mode 100644 index 61ccc7e..0000000 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Penumbra.GameData.Enums; - -namespace Glamourer.Structs; - -[Flags] -public enum CrestFlag : ushort -{ - OffHand = 0x0001, - Head = 0x0002, - Body = 0x0004, - Hands = 0x0008, - Legs = 0x0010, - Feet = 0x0020, - Ears = 0x0040, - Neck = 0x0080, - Wrists = 0x0100, - RFinger = 0x0200, - LFinger = 0x0400, - MainHand = 0x0800, -} - -public enum CrestType : byte -{ - None, - Human, - Mainhand, - Offhand, -}; - -public static class CrestExtensions -{ - public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Mainhand << 1) - 1); - public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; - - public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => AllRelevant.HasFlag(f)).ToArray(); - - public static int ToInternalIndex(this CrestFlag flag) - => flag switch - { - CrestFlag.Head => 0, - CrestFlag.Body => 1, - CrestFlag.OffHand => 2, - _ => -1, - }; - - public static (CrestType Type, byte Index) ToIndex(this CrestFlag flag) - => flag switch - { - CrestFlag.Head => (CrestType.Human, 0), - CrestFlag.Body => (CrestType.Human, 1), - CrestFlag.Hands => (CrestType.Human, 2), - CrestFlag.Legs => (CrestType.Human, 3), - CrestFlag.Feet => (CrestType.Human, 4), - CrestFlag.Ears => (CrestType.None, 0), - CrestFlag.Neck => (CrestType.None, 0), - CrestFlag.Wrists => (CrestType.None, 0), - CrestFlag.RFinger => (CrestType.None, 0), - CrestFlag.LFinger => (CrestType.None, 0), - CrestFlag.MainHand => (CrestType.None, 0), - CrestFlag.OffHand => (CrestType.Offhand, 0), - _ => (CrestType.None, 0), - }; - - public static CrestFlag ToCrestFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => CrestFlag.MainHand, - EquipSlot.OffHand => CrestFlag.OffHand, - EquipSlot.Head => CrestFlag.Head, - EquipSlot.Body => CrestFlag.Body, - EquipSlot.Hands => CrestFlag.Hands, - EquipSlot.Legs => CrestFlag.Legs, - EquipSlot.Feet => CrestFlag.Feet, - EquipSlot.Ears => CrestFlag.Ears, - EquipSlot.Neck => CrestFlag.Neck, - EquipSlot.Wrists => CrestFlag.Wrists, - EquipSlot.RFinger => CrestFlag.RFinger, - EquipSlot.LFinger => CrestFlag.LFinger, - _ => 0, - }; - - public static string ToLabel(this CrestFlag flag) - => flag switch - { - CrestFlag.Head => "Head", - CrestFlag.Body => "Chest", - CrestFlag.Hands => "Gauntlets", - CrestFlag.Legs => "Pants", - CrestFlag.Feet => "Boot", - CrestFlag.Ears => "Earrings", - CrestFlag.Neck => "Necklace", - CrestFlag.Wrists => "Bracelet", - CrestFlag.RFinger => "Right Ring", - CrestFlag.LFinger => "Left Ring", - CrestFlag.MainHand => "Weapon", - CrestFlag.OffHand => "Shield", - _ => string.Empty, - }; -} diff --git a/Glamourer.GameData/Structs/EquipFlag.cs b/Glamourer.GameData/Structs/EquipFlag.cs deleted file mode 100644 index eaacbac..0000000 --- a/Glamourer.GameData/Structs/EquipFlag.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using Penumbra.GameData.Enums; - -namespace Glamourer.Structs; - -[Flags] -public enum EquipFlag : uint -{ - Head = 0x00000001, - Body = 0x00000002, - Hands = 0x00000004, - Legs = 0x00000008, - Feet = 0x00000010, - Ears = 0x00000020, - Neck = 0x00000040, - Wrist = 0x00000080, - RFinger = 0x00000100, - LFinger = 0x00000200, - Mainhand = 0x00000400, - Offhand = 0x00000800, - HeadStain = 0x00001000, - BodyStain = 0x00002000, - HandsStain = 0x00004000, - LegsStain = 0x00008000, - FeetStain = 0x00010000, - EarsStain = 0x00020000, - NeckStain = 0x00040000, - WristStain = 0x00080000, - RFingerStain = 0x00100000, - LFingerStain = 0x00200000, - MainhandStain = 0x00400000, - OffhandStain = 0x00800000, -} - -public static class EquipFlagExtensions -{ - public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1); - public const int NumEquipFlags = 24; - - public static EquipFlag ToFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => EquipFlag.Mainhand, - EquipSlot.OffHand => EquipFlag.Offhand, - EquipSlot.Head => EquipFlag.Head, - EquipSlot.Body => EquipFlag.Body, - EquipSlot.Hands => EquipFlag.Hands, - EquipSlot.Legs => EquipFlag.Legs, - EquipSlot.Feet => EquipFlag.Feet, - EquipSlot.Ears => EquipFlag.Ears, - EquipSlot.Neck => EquipFlag.Neck, - EquipSlot.Wrists => EquipFlag.Wrist, - EquipSlot.RFinger => EquipFlag.RFinger, - EquipSlot.LFinger => EquipFlag.LFinger, - _ => 0, - }; - - public static EquipFlag ToStainFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => EquipFlag.MainhandStain, - EquipSlot.OffHand => EquipFlag.OffhandStain, - EquipSlot.Head => EquipFlag.HeadStain, - EquipSlot.Body => EquipFlag.BodyStain, - EquipSlot.Hands => EquipFlag.HandsStain, - EquipSlot.Legs => EquipFlag.LegsStain, - EquipSlot.Feet => EquipFlag.FeetStain, - EquipSlot.Ears => EquipFlag.EarsStain, - EquipSlot.Neck => EquipFlag.NeckStain, - EquipSlot.Wrists => EquipFlag.WristStain, - EquipSlot.RFinger => EquipFlag.RFingerStain, - EquipSlot.LFinger => EquipFlag.LFingerStain, - _ => 0, - }; - - public static EquipFlag ToBothFlags(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => EquipFlag.Mainhand | EquipFlag.MainhandStain, - EquipSlot.OffHand => EquipFlag.Offhand | EquipFlag.OffhandStain, - EquipSlot.Head => EquipFlag.Head | EquipFlag.HeadStain, - EquipSlot.Body => EquipFlag.Body | EquipFlag.BodyStain, - EquipSlot.Hands => EquipFlag.Hands | EquipFlag.HandsStain, - EquipSlot.Legs => EquipFlag.Legs | EquipFlag.LegsStain, - EquipSlot.Feet => EquipFlag.Feet | EquipFlag.FeetStain, - EquipSlot.Ears => EquipFlag.Ears | EquipFlag.EarsStain, - EquipSlot.Neck => EquipFlag.Neck | EquipFlag.NeckStain, - EquipSlot.Wrists => EquipFlag.Wrist | EquipFlag.WristStain, - EquipSlot.RFinger => EquipFlag.RFinger | EquipFlag.RFingerStain, - EquipSlot.LFinger => EquipFlag.LFinger | EquipFlag.LFingerStain, - _ => 0, - }; -} diff --git a/Glamourer.GameData/Structs/Job.cs b/Glamourer.GameData/Structs/Job.cs deleted file mode 100644 index 1d2873d..0000000 --- a/Glamourer.GameData/Structs/Job.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Dalamud.Utility; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer.Structs; - -// A struct containing the different jobs the game supports. -// Also contains the jobs Name and Abbreviation as strings. -public readonly struct Job -{ - public readonly string Name; - public readonly string Abbreviation; - public readonly ClassJob Base; - - public uint Id - => Base.RowId; - - public JobFlag Flag - => (JobFlag)(1ul << (int)Base.RowId); - - public Job(ClassJob job) - { - Base = job; - Name = job.Name.ToDalamudString().ToString(); - Abbreviation = job.Abbreviation.ToDalamudString().ToString(); - } - - public override string ToString() - => Name; -} diff --git a/Glamourer.GameData/Structs/JobGroup.cs b/Glamourer.GameData/Structs/JobGroup.cs deleted file mode 100644 index 5471e7f..0000000 --- a/Glamourer.GameData/Structs/JobGroup.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Diagnostics; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer.Structs; - -[Flags] -public enum JobFlag : ulong -{ } - -// The game specifies different job groups that can contain specific jobs or not. -public readonly struct JobGroup -{ - public readonly string Name; - public readonly int Count; - public readonly uint Id; - private readonly JobFlag _flags; - - // Create a job group from a given category and the ClassJob sheet. - // It looks up the different jobs contained in the category and sets the flags appropriately. - public JobGroup(ClassJobCategory group, ExcelSheet jobs) - { - Count = 0; - _flags = 0ul; - Id = group.RowId; - Name = group.Name.ToString(); - - Debug.Assert(jobs.RowCount < 64, $"Number of Jobs exceeded 63 ({jobs.RowCount})."); - foreach (var job in jobs) - { - var abbr = job.Abbreviation.ToString(); - if (abbr.Length == 0) - continue; - - var prop = group.GetType().GetProperty(abbr); - Debug.Assert(prop != null, $"Could not get job abbreviation {abbr} property."); - - if (!(bool)prop.GetValue(group)!) - continue; - - ++Count; - _flags |= (JobFlag)(1ul << (int)job.RowId); - } - } - - // Check if a job is contained inside this group. - public bool Fits(Job job) - => _flags.HasFlag(job.Flag); - - // Check if any of the jobs in the given flags fit this group. - public bool Fits(JobFlag flag) - => (_flags & flag) != 0; - - // Check if a job is contained inside this group. - public bool Fits(uint jobId) - { - var flag = (JobFlag)(1ul << (int)jobId); - return _flags.HasFlag(flag); - } -} diff --git a/Glamourer.sln b/Glamourer.sln index 5f555ca..9acdd6c 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -9,8 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution repo.json = repo.json EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.GameData", "Glamourer.GameData\Glamourer.GameData.csproj", "{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}" @@ -27,10 +25,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.Build.0 = Release|Any CPU {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs index 3e8794c..cdec349 100644 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ b/Glamourer/Api/GlamourerIpc.GetCustomization.cs @@ -1,11 +1,7 @@ -using System.Buffers.Text; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; -using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.Structs; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 0a26759..029a2ec 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -1,10 +1,10 @@ using System; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Interop.Structs; using Glamourer.State; -using Glamourer.Structs; using Newtonsoft.Json.Linq; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Automation; @@ -74,7 +74,7 @@ public class AutoDesign var ret = new JObject { ["Gearset"] = GearsetIndex, - ["JobGroup"] = Jobs.Id, + ["JobGroup"] = Jobs.Id.Id, }; return ret; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 023b2c0..9f3e286 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -4,14 +4,12 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using Glamourer.Unlocks; using OtterGui.Classes; using Penumbra.GameData.Actors; @@ -209,7 +207,7 @@ public class AutoDesignApplier : IDisposable if (!GetPlayerSet(id, out var set)) { if (_state.TryGetValue(id, out var s)) - s.LastJob = (byte)newJob.Id; + s.LastJob = newJob.Id; return; } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index e997a78..e205784 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -10,7 +10,6 @@ using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -516,7 +515,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var jobs = conditions["JobGroup"]?.ToObject() ?? -1; if (jobs >= 0) { - if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup)) + if (!_jobs.JobGroups.TryGetValue((JobGroupId)jobs, out var jobGroup)) { Glamourer.Messager.NotificationMessage( $"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index d30271e..4ed98d3 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -3,37 +3,30 @@ using System.Linq; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Interop; -using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Actors; +using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Automation; -public class FixedDesignMigrator +public class FixedDesignMigrator(JobService jobs) { - private readonly JobService _jobs; - private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData; - - public FixedDesignMigrator(JobService jobs) - => _jobs = jobs; + private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData; public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager) { if (_migratedData == null) return; - foreach (var data in _migratedData) + foreach (var (name, data) in _migratedData) { - var allEnabled = true; - var name = data.Name; if (autoManager.Any(d => name == d.Name)) continue; var id = ActorIdentifier.Invalid; - if (ByteString.FromString(data.Name, out var byteString, false)) + if (ByteString.FromString(name, out var byteString)) { id = actors.CreatePlayer(byteString, ushort.MaxValue); if (!id.IsValid) @@ -46,16 +39,15 @@ public class FixedDesignMigrator id = actors.CreatePlayer(byteString, actors.Data.Worlds.First().Key); if (!id.IsValid) { - Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error); - allEnabled = false; + Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {name}.", NotificationType.Error); continue; } } autoManager.AddDesignSet(name, id); - autoManager.SetState(autoManager.Count - 1, allEnabled); + autoManager.SetState(autoManager.Count - 1, true); var set = autoManager[^1]; - foreach (var design in data.Data.AsEnumerable().Reverse()) + foreach (var design in data.AsEnumerable().Reverse()) { if (!designFileSystem.Find(design.Item1, out var child) || child is not DesignFileSystem.Leaf leaf) { @@ -96,7 +88,7 @@ public class FixedDesignMigrator } var job = obj["JobGroups"]?.ToObject() ?? -1; - if (job < 0 || !_jobs.JobGroups.TryGetValue((ushort)job, out var group)) + if (job < 0 || !jobs.JobGroups.TryGetValue((JobGroupId)job, out var group)) { Glamourer.Messager.NotificationMessage("Could not semi-migrate fixed design: Invalid job group specified.", NotificationType.Warning); diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index c9662ef..8e11ad8 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,7 +1,6 @@ using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -85,12 +84,12 @@ public class DesignBase internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; - public bool SetCustomize(CustomizationService customizationService, Customize customize) + public bool SetCustomize(CustomizationService customizationService, CustomizeArray customize) { if (customize.Equals(_designData.Customize)) return false; - _designData.Customize.Load(customize); + _designData.Customize = customize; CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender); return true; } @@ -443,7 +442,7 @@ public class DesignBase { design._designData.ModelId = 0; design._designData.IsHuman = true; - design.SetCustomize(customizations, Customize.Default); + design.SetCustomize(customizations, CustomizeArray.Default); Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.", NotificationType.Warning); return; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index c36c1ea..d4352e6 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,7 +1,5 @@ using System; -using Glamourer.Customization; using Glamourer.Services; -using Glamourer.Structs; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -62,7 +60,7 @@ public class DesignBase64Migration data.SetHatVisible((bytes[90] & 0x01) == 0); data.SetVisor((bytes[90] & 0x10) != 0); data.SetWeaponVisible((bytes[90] & 0x02) == 0); - data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); + data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); break; } case 5: @@ -73,7 +71,7 @@ public class DesignBase64Migration data.SetHatVisible((bytes[90] & 0x01) == 0); data.SetVisor((bytes[90] & 0x10) != 0); data.SetWeaponVisible((bytes[90] & 0x02) == 0); - data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); + data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); break; default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}."); } @@ -102,11 +100,11 @@ public class DesignBase64Migration if (!humans.IsHuman(data.ModelId)) { - data.LoadNonHuman(data.ModelId, *(Customize*)(ptr + 4), (nint)eq); + data.LoadNonHuman(data.ModelId, *(CustomizeArray*)(ptr + 4), (nint)eq); return data; } - data.Customize.Load(*(Customize*)(ptr + 4)); + data.Customize = *(CustomizeArray*)(ptr + 4); foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) { var mdl = eq[idx]; @@ -187,7 +185,7 @@ public class DesignBase64Migration | (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0) | (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0) | (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0)); - save.Customize.Write((nint)data + 4); + save.Customize.Write(data + 4); ((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand)); ((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand)); foreach (var slot in EquipSlotExtensions.EqdpSlots) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 86fd0ce..282ab0a 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -using Glamourer.Customization; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using Glamourer.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -165,7 +163,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (slot, item, armor.Stain); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant, FullEquipType.Unknown); + var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); if (!mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 68fa154..9e324ae 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,8 +1,6 @@ using System; using System.Runtime.CompilerServices; -using Glamourer.Customization; using Glamourer.Services; -using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -12,30 +10,30 @@ namespace Glamourer.Designs; public unsafe struct DesignData { - private string _nameHead = string.Empty; - private string _nameBody = string.Empty; - private string _nameHands = string.Empty; - private string _nameLegs = string.Empty; - private string _nameFeet = string.Empty; - private string _nameEars = string.Empty; - private string _nameNeck = string.Empty; - private string _nameWrists = string.Empty; - private string _nameRFinger = string.Empty; - private string _nameLFinger = string.Empty; - private string _nameMainhand = string.Empty; - private string _nameOffhand = string.Empty; - private fixed uint _itemIds[12]; - private fixed ushort _iconIds[12]; - private fixed byte _equipmentBytes[48]; - public Customize Customize = Customize.Default; - public uint ModelId; - public CrestFlag CrestVisibility; - private SecondaryId _secondaryMainhand; - private SecondaryId _secondaryOffhand; - private FullEquipType _typeMainhand; - private FullEquipType _typeOffhand; - private byte _states; - public bool IsHuman = true; + private string _nameHead = string.Empty; + private string _nameBody = string.Empty; + private string _nameHands = string.Empty; + private string _nameLegs = string.Empty; + private string _nameFeet = string.Empty; + private string _nameEars = string.Empty; + private string _nameNeck = string.Empty; + private string _nameWrists = string.Empty; + private string _nameRFinger = string.Empty; + private string _nameLFinger = string.Empty; + private string _nameMainhand = string.Empty; + private string _nameOffhand = string.Empty; + private fixed uint _itemIds[12]; + private fixed ushort _iconIds[12]; + private fixed byte _equipmentBytes[48]; + public CustomizeArray Customize = CustomizeArray.Default; + public uint ModelId; + public CrestFlag CrestVisibility; + private SecondaryId _secondaryMainhand; + private SecondaryId _secondaryOffhand; + private FullEquipType _typeMainhand; + private FullEquipType _typeOffhand; + private byte _states; + public bool IsHuman = true; public DesignData() { } @@ -255,11 +253,11 @@ public unsafe struct DesignData } - public bool LoadNonHuman(uint modelId, Customize customize, nint equipData) + public bool LoadNonHuman(uint modelId, CustomizeArray customize, nint equipData) { ModelId = modelId; IsHuman = false; - Customize.Load(customize); + Customize.Read(customize.Data); fixed (byte* ptr = _equipmentBytes) { MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40); @@ -294,7 +292,7 @@ public unsafe struct DesignData public readonly byte[] GetCustomizeBytes() { var ret = new byte[CustomizeArray.Size]; - fixed (byte* retPtr = ret, inPtr = Customize.Data.Data) + fixed (byte* retPtr = ret, inPtr = Customize.Data) { MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 6b8578e..1320d6a 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -3,12 +3,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Utility; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -25,8 +23,8 @@ public class DesignManager private readonly HumanModelList _humans; private readonly SaveService _saveService; private readonly DesignChanged _event; - private readonly List _designs = new(); - private readonly Dictionary _undoStore = new(); + private readonly List _designs = []; + private readonly Dictionary _undoStore = []; public IReadOnlyList Designs => _designs; @@ -298,7 +296,7 @@ public class DesignManager return; case CustomizeIndex.Clan: { - var customize = new Customize(design.DesignData.Customize.Data.Clone()); + var customize = design.DesignData.Customize; if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0) return; if (!design.SetCustomize(_customizations, customize)) @@ -308,7 +306,7 @@ public class DesignManager } case CustomizeIndex.Gender: { - var customize = new Customize(design.DesignData.Customize.Data.Clone()); + var customize = design.DesignData.Customize; if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0) return; if (!design.SetCustomize(_customizations, customize)) diff --git a/Glamourer.GameData/Customization/CharaMakeParams.cs b/Glamourer/GameData/CharaMakeParams.cs similarity index 94% rename from Glamourer.GameData/Customization/CharaMakeParams.cs rename to Glamourer/GameData/CharaMakeParams.cs index 63806c0..12dedf9 100644 --- a/Glamourer.GameData/Customization/CharaMakeParams.cs +++ b/Glamourer/GameData/CharaMakeParams.cs @@ -2,9 +2,11 @@ using Lumina.Excel; using Lumina.Excel.GeneratedSheets; -namespace Glamourer.Customization; +namespace Glamourer.GameData; -// A custom version of CharaMakeParams that is easier to parse. +/// +/// A custom version of CharaMakeParams that is easier to parse. +/// [Sheet("CharaMakeParams")] public class CharaMakeParams : ExcelRow { @@ -64,7 +66,7 @@ public class CharaMakeParams : ExcelRow Race = new LazyRow(gameData, parser.ReadColumn(0), language); Tribe = new LazyRow(gameData, parser.ReadColumn(1), language); Gender = parser.ReadColumn(2); - var currentOffset = 0; + int currentOffset; for (var i = 0; i < NumMenus; ++i) { currentOffset = 3 + i; diff --git a/Glamourer/GameData/ColorParameters.cs b/Glamourer/GameData/ColorParameters.cs new file mode 100644 index 0000000..630a5a7 --- /dev/null +++ b/Glamourer/GameData/ColorParameters.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Dalamud.Plugin.Services; +using Penumbra.String.Functions; + +namespace Glamourer.GameData; + +public class ColorParameters : IReadOnlyList +{ + private readonly uint[] _rgbaColors; + + public ReadOnlySpan GetSlice(int offset, int count) + => _rgbaColors.AsSpan(offset, count); + + public unsafe ColorParameters(IDataManager gameData, IPluginLog log) + { + try + { + var file = gameData.GetFile("chara/xls/charamake/human.cmp")!; + _rgbaColors = new uint[file.Data.Length >> 2]; + fixed (byte* ptr1 = file.Data) + { + fixed (uint* ptr2 = _rgbaColors) + { + MemoryUtility.MemCpyUnchecked(ptr2, ptr1, file.Data.Length); + } + } + } + catch (Exception e) + { + log.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" + + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" + + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); + _rgbaColors = Array.Empty(); + } + } + + public IEnumerator GetEnumerator() + => (IEnumerator)_rgbaColors.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _rgbaColors.Length; + + public uint this[int index] + => _rgbaColors[index]; +} diff --git a/Glamourer.GameData/Customization/CustomName.cs b/Glamourer/GameData/CustomName.cs similarity index 78% rename from Glamourer.GameData/Customization/CustomName.cs rename to Glamourer/GameData/CustomName.cs index 040c476..c7d74a1 100644 --- a/Glamourer.GameData/Customization/CustomName.cs +++ b/Glamourer/GameData/CustomName.cs @@ -1,6 +1,6 @@ -namespace Glamourer.Customization; +namespace Glamourer.GameData; -// Localization from the game files directly. +/// For localization from the game files directly. public enum CustomName { MidlanderM, diff --git a/Glamourer.GameData/Customization/CustomizationManager.cs b/Glamourer/GameData/CustomizationManager.cs similarity index 93% rename from Glamourer.GameData/Customization/CustomizationManager.cs rename to Glamourer/GameData/CustomizationManager.cs index b02498c..f249bf6 100644 --- a/Glamourer.GameData/Customization/CustomizationManager.cs +++ b/Glamourer/GameData/CustomizationManager.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Internal; using Dalamud.Plugin.Services; using Penumbra.GameData.Enums; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public class CustomizationManager : ICustomizationManager { diff --git a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs b/Glamourer/GameData/CustomizationNpcOptions.cs similarity index 92% rename from Glamourer.GameData/Customization/CustomizationNpcOptions.cs rename to Glamourer/GameData/CustomizationNpcOptions.cs index 043ccf8..84509be 100644 --- a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs +++ b/Glamourer/GameData/CustomizationNpcOptions.cs @@ -1,12 +1,14 @@ using Penumbra.GameData.Enums; using System.Collections.Generic; using System.Linq; +using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public static class CustomizationNpcOptions { - public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) + public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, + NpcCustomizeSet npcCustomizeSet) { var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>(); var customizeIndices = new[] diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer/GameData/CustomizationOptions.cs similarity index 94% rename from Glamourer.GameData/Customization/CustomizationOptions.cs rename to Glamourer/GameData/CustomizationOptions.cs index 3580ef8..6fc3b03 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer/GameData/CustomizationOptions.cs @@ -10,9 +10,10 @@ using Lumina.Excel; using Lumina.Excel.GeneratedSheets; using OtterGui.Classes; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; -namespace Glamourer.Customization; +namespace Glamourer.GameData; // Generate everything about customization per tribe and gender. public partial class CustomizationOptions @@ -66,7 +67,7 @@ public partial class CustomizationOptions { var tmp = new TemporaryData(gameData, this, log); _icons = new IconStorage(textures, gameData); - SetNames(gameData, tmp); + SetNames(gameData); foreach (var race in Clans) { foreach (var gender in Genders) @@ -79,7 +80,7 @@ public partial class CustomizationOptions // Obtain localized names of customization options and race names from the game data. private readonly string[] _names = new string[Enum.GetValues().Length]; - private void SetNames(IDataManager gameData, TemporaryData tmp) + private void SetNames(IDataManager gameData) { var subRace = gameData.GetExcelSheet()!; @@ -122,9 +123,6 @@ public partial class CustomizationOptions private class TemporaryData { - public bool Valid - => _cmpFile.Valid; - public CustomizationSet GetSet(SubRace race, Gender gender) { var (skin, hair) = GetColors(race, gender); @@ -177,10 +175,8 @@ public partial class CustomizationOptions public TemporaryData(IDataManager gameData, CustomizationOptions options, IPluginLog log) { _options = options; - _cmpFile = new CmpFile(gameData, log); + _cmpFile = new ColorParameters(gameData, log); _customizeSheet = gameData.GetExcelSheet(ClientLanguage.English)!; - _bnpcCustomize = gameData.GetExcelSheet(ClientLanguage.English)!; - _enpcBase = gameData.GetExcelSheet(ClientLanguage.English)!; Lobby = gameData.GetExcelSheet(ClientLanguage.English)!; var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? .MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[] @@ -204,10 +200,8 @@ public partial class CustomizationOptions private readonly ExcelSheet _customizeSheet; private readonly ExcelSheet _listSheet; private readonly ExcelSheet _hairSheet; - private readonly ExcelSheet _bnpcCustomize; - private readonly ExcelSheet _enpcBase; public readonly ExcelSheet Lobby; - private readonly CmpFile _cmpFile; + private readonly ColorParameters _cmpFile; // Those values are shared between all races. private readonly CustomizeData[] _highlightPicker; @@ -222,9 +216,17 @@ public partial class CustomizationOptions private CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false) - => _cmpFile.GetSlice(offset, num) - .Select((c, i) => new CustomizeData(index, (CustomizeValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i))) - .ToArray(); + { + var ret = new CustomizeData[num]; + var idx = 0; + foreach (var value in _cmpFile.GetSlice(offset, num)) + { + ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx)); + ++idx; + } + + return ret; + } private void SetHairByFace(CustomizationSet set) @@ -299,15 +301,9 @@ public partial class CustomizationOptions // Set customizations available if they have any options. private static void SetAvailability(CustomizationSet set, CharaMakeParams row) { - if (set.Race == Race.Hrothgar && set.Gender == Gender.Female) + if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) return; - void Set(bool available, CustomizeIndex flag) - { - if (available) - set.SetAvailable(flag); - } - Set(true, CustomizeIndex.Height); Set(set.Faces.Count > 0, CustomizeIndex.Face); Set(true, CustomizeIndex.Hairstyle); @@ -340,6 +336,13 @@ public partial class CustomizationOptions Set(true, CustomizeIndex.SmallIris); Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick); Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed); + return; + + void Set(bool available, CustomizeIndex flag) + { + if (available) + set.SetAvailable(flag); + } } // Create a list of lists of facial features and the legacy tattoo. @@ -348,9 +351,6 @@ public partial class CustomizationOptions 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(); @@ -373,6 +373,10 @@ public partial class CustomizationOptions set.FacialFeature5 = tmp[4]; set.FacialFeature6 = tmp[5]; set.FacialFeature7 = tmp[6]; + return; + + static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) + => (new CustomizeData(i, CustomizeValue.Zero, data), new CustomizeData(i, CustomizeValue.Max, data, 1)); } // Set the names for the given set of parameters. @@ -414,7 +418,7 @@ public partial class CustomizationOptions nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName(); nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName(); nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName(); - set.OptionName = nameArray; + set.OptionName = nameArray; } // Obtain available skin and hair colors for the given subrace and gender. diff --git a/Glamourer.GameData/Customization/CustomizationSet.cs b/Glamourer/GameData/CustomizationSet.cs similarity index 95% rename from Glamourer.GameData/Customization/CustomizationSet.cs rename to Glamourer/GameData/CustomizationSet.cs index b958fdc..2a79c66 100644 --- a/Glamourer.GameData/Customization/CustomizationSet.cs +++ b/Glamourer/GameData/CustomizationSet.cs @@ -4,8 +4,9 @@ using System.Linq; using System.Runtime.CompilerServices; using OtterGui; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; // Each Subrace and Gender combo has a customization set. // This describes the available customizations, their types and their names. @@ -300,3 +301,10 @@ public class CustomizationSet private CustomizeValue HrothgarFaceHack(CustomizeValue value) => Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value; } + +public static class CustomizationSetExtensions +{ + /// Return only the available customizations in this set and Clan or Gender. + public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set) + => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); +} diff --git a/Glamourer.GameData/Customization/CustomizeData.cs b/Glamourer/GameData/CustomizeData.cs similarity index 89% rename from Glamourer.GameData/Customization/CustomizeData.cs rename to Glamourer/GameData/CustomizeData.cs index 8e9f914..3a3e89c 100644 --- a/Glamourer.GameData/Customization/CustomizeData.cs +++ b/Glamourer/GameData/CustomizeData.cs @@ -1,7 +1,9 @@ using System; using System.Runtime.InteropServices; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; // 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. diff --git a/Glamourer.GameData/Customization/ICustomizationManager.cs b/Glamourer/GameData/ICustomizationManager.cs similarity index 90% rename from Glamourer.GameData/Customization/ICustomizationManager.cs rename to Glamourer/GameData/ICustomizationManager.cs index e946e3d..2d884cd 100644 --- a/Glamourer.GameData/Customization/ICustomizationManager.cs +++ b/Glamourer/GameData/ICustomizationManager.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Internal; using Penumbra.GameData.Enums; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public interface ICustomizationManager { diff --git a/Glamourer.GameData/Customization/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs similarity index 63% rename from Glamourer.GameData/Customization/NpcCustomizeSet.cs rename to Glamourer/GameData/NpcCustomizeSet.cs index 3ba64a0..fd261c6 100644 --- a/Glamourer.GameData/Customization/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -11,7 +11,7 @@ using OtterGui.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { @@ -187,83 +187,83 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList data.VisorToggled = row.Visor; } - private static (bool, Customize) FromBnpcCustomize(BNpcCustomize bnpcCustomize) + private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize) { - var customize = new Customize(); - customize.Data.Set(0, (byte)bnpcCustomize.Race.Row); - customize.Data.Set(1, bnpcCustomize.Gender); - customize.Data.Set(2, bnpcCustomize.BodyType); - customize.Data.Set(3, bnpcCustomize.Height); - customize.Data.Set(4, (byte)bnpcCustomize.Tribe.Row); - customize.Data.Set(5, bnpcCustomize.Face); - customize.Data.Set(6, bnpcCustomize.HairStyle); - customize.Data.Set(7, bnpcCustomize.HairHighlight); - customize.Data.Set(8, bnpcCustomize.SkinColor); - customize.Data.Set(9, bnpcCustomize.EyeHeterochromia); - customize.Data.Set(10, bnpcCustomize.HairColor); - customize.Data.Set(11, bnpcCustomize.HairHighlightColor); - customize.Data.Set(12, bnpcCustomize.FacialFeature); - customize.Data.Set(13, bnpcCustomize.FacialFeatureColor); - customize.Data.Set(14, bnpcCustomize.Eyebrows); - customize.Data.Set(15, bnpcCustomize.EyeColor); - customize.Data.Set(16, bnpcCustomize.EyeShape); - customize.Data.Set(17, bnpcCustomize.Nose); - customize.Data.Set(18, bnpcCustomize.Jaw); - customize.Data.Set(19, bnpcCustomize.Mouth); - customize.Data.Set(20, bnpcCustomize.LipColor); - customize.Data.Set(21, bnpcCustomize.BustOrTone1); - customize.Data.Set(22, bnpcCustomize.ExtraFeature1); - customize.Data.Set(23, bnpcCustomize.ExtraFeature2OrBust); - customize.Data.Set(24, bnpcCustomize.FacePaint); - customize.Data.Set(25, bnpcCustomize.FacePaintColor); + var customize = new CustomizeArray(); + customize.SetByIndex(0, (CustomizeValue) (byte)bnpcCustomize.Race.Row); + customize.SetByIndex(1, (CustomizeValue) bnpcCustomize.Gender); + customize.SetByIndex(2, (CustomizeValue) bnpcCustomize.BodyType); + customize.SetByIndex(3, (CustomizeValue) bnpcCustomize.Height); + customize.SetByIndex(4, (CustomizeValue) (byte)bnpcCustomize.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue) bnpcCustomize.Face); + customize.SetByIndex(6, (CustomizeValue) bnpcCustomize.HairStyle); + customize.SetByIndex(7, (CustomizeValue) bnpcCustomize.HairHighlight); + customize.SetByIndex(8, (CustomizeValue) bnpcCustomize.SkinColor); + customize.SetByIndex(9, (CustomizeValue) bnpcCustomize.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue) bnpcCustomize.HairColor); + customize.SetByIndex(11, (CustomizeValue) bnpcCustomize.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue) bnpcCustomize.FacialFeature); + customize.SetByIndex(13, (CustomizeValue) bnpcCustomize.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue) bnpcCustomize.Eyebrows); + customize.SetByIndex(15, (CustomizeValue) bnpcCustomize.EyeColor); + customize.SetByIndex(16, (CustomizeValue) bnpcCustomize.EyeShape); + customize.SetByIndex(17, (CustomizeValue) bnpcCustomize.Nose); + customize.SetByIndex(18, (CustomizeValue) bnpcCustomize.Jaw); + customize.SetByIndex(19, (CustomizeValue) bnpcCustomize.Mouth); + customize.SetByIndex(20, (CustomizeValue) bnpcCustomize.LipColor); + customize.SetByIndex(21, (CustomizeValue) bnpcCustomize.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue) bnpcCustomize.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue) bnpcCustomize.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue) bnpcCustomize.FacePaint); + customize.SetByIndex(25, (CustomizeValue) bnpcCustomize.FacePaintColor); if (customize.BodyType.Value != 1 || !CustomizationOptions.Races.Contains(customize.Race) || !CustomizationOptions.Clans.Contains(customize.Clan) || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); + return (false, CustomizeArray.Default); return (true, customize); } - private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase) + private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase) { if (enpcBase.ModelChara.Value?.Type != 1) - return (false, Customize.Default); + return (false, CustomizeArray.Default); - var customize = new Customize(); - customize.Data.Set(0, (byte)enpcBase.Race.Row); - customize.Data.Set(1, enpcBase.Gender); - customize.Data.Set(2, enpcBase.BodyType); - customize.Data.Set(3, enpcBase.Height); - customize.Data.Set(4, (byte)enpcBase.Tribe.Row); - customize.Data.Set(5, enpcBase.Face); - customize.Data.Set(6, enpcBase.HairStyle); - customize.Data.Set(7, enpcBase.HairHighlight); - customize.Data.Set(8, enpcBase.SkinColor); - customize.Data.Set(9, enpcBase.EyeHeterochromia); - customize.Data.Set(10, enpcBase.HairColor); - customize.Data.Set(11, enpcBase.HairHighlightColor); - customize.Data.Set(12, enpcBase.FacialFeature); - customize.Data.Set(13, enpcBase.FacialFeatureColor); - customize.Data.Set(14, enpcBase.Eyebrows); - customize.Data.Set(15, enpcBase.EyeColor); - customize.Data.Set(16, enpcBase.EyeShape); - customize.Data.Set(17, enpcBase.Nose); - customize.Data.Set(18, enpcBase.Jaw); - customize.Data.Set(19, enpcBase.Mouth); - customize.Data.Set(20, enpcBase.LipColor); - customize.Data.Set(21, enpcBase.BustOrTone1); - customize.Data.Set(22, enpcBase.ExtraFeature1); - customize.Data.Set(23, enpcBase.ExtraFeature2OrBust); - customize.Data.Set(24, enpcBase.FacePaint); - customize.Data.Set(25, enpcBase.FacePaintColor); + var customize = new CustomizeArray(); + customize.SetByIndex(0, (CustomizeValue) (byte)enpcBase.Race.Row); + customize.SetByIndex(1, (CustomizeValue) enpcBase.Gender); + customize.SetByIndex(2, (CustomizeValue) enpcBase.BodyType); + customize.SetByIndex(3, (CustomizeValue) enpcBase.Height); + customize.SetByIndex(4, (CustomizeValue) (byte)enpcBase.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue) enpcBase.Face); + customize.SetByIndex(6, (CustomizeValue) enpcBase.HairStyle); + customize.SetByIndex(7, (CustomizeValue) enpcBase.HairHighlight); + customize.SetByIndex(8, (CustomizeValue) enpcBase.SkinColor); + customize.SetByIndex(9, (CustomizeValue) enpcBase.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue) enpcBase.HairColor); + customize.SetByIndex(11, (CustomizeValue) enpcBase.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue) enpcBase.FacialFeature); + customize.SetByIndex(13, (CustomizeValue) enpcBase.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue) enpcBase.Eyebrows); + customize.SetByIndex(15, (CustomizeValue) enpcBase.EyeColor); + customize.SetByIndex(16, (CustomizeValue) enpcBase.EyeShape); + customize.SetByIndex(17, (CustomizeValue) enpcBase.Nose); + customize.SetByIndex(18, (CustomizeValue) enpcBase.Jaw); + customize.SetByIndex(19, (CustomizeValue) enpcBase.Mouth); + customize.SetByIndex(20, (CustomizeValue) enpcBase.LipColor); + customize.SetByIndex(21, (CustomizeValue) enpcBase.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue) enpcBase.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue) enpcBase.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue) enpcBase.FacePaint); + customize.SetByIndex(25, (CustomizeValue) enpcBase.FacePaintColor); if (customize.BodyType.Value != 1 || !CustomizationOptions.Races.Contains(customize.Race) || !CustomizationOptions.Clans.Contains(customize.Clan) || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); + return (false, CustomizeArray.Default); return (true, customize); } diff --git a/Glamourer.GameData/Customization/NpcData.cs b/Glamourer/GameData/NpcData.cs similarity index 93% rename from Glamourer.GameData/Customization/NpcData.cs rename to Glamourer/GameData/NpcData.cs index 6942147..70bfe58 100644 --- a/Glamourer.GameData/Customization/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -3,12 +3,12 @@ using System.Text; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public unsafe struct NpcData { public string Name; - public Customize Customize; + public CustomizeArray Customize; private fixed byte _equip[40]; public CharacterWeapon Mainhand; public CharacterWeapon Offhand; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 12ec3a7..0d2bb36 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -84,7 +84,6 @@ - @@ -109,8 +108,8 @@ - - + + diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 127d8c2..5fa28a6 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -1,11 +1,11 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; -using Glamourer.Customization; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.GameData; +using Penumbra.GameData.Enums; namespace Glamourer.Gui.Customization; @@ -15,12 +15,12 @@ public partial class CustomizationDrawer private void DrawColorPicker(CustomizeIndex index) { - using var _ = SetId(index); + using var id = SetId(index); var (current, custom) = GetCurrentCustomization(index); var color = ImGui.ColorConvertU32ToFloat4(current < 0 ? ImGui.GetColorU32(ImGuiCol.FrameBg) : custom.Color); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) { if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) ImGui.OpenPopup(ColorPickerPopupName); @@ -39,7 +39,7 @@ public partial class CustomizationDrawer ImGui.SameLine(); - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { DataInputInt(current, npc); if (_withApply) @@ -89,7 +89,7 @@ public partial class CustomizationDrawer { var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face); if (_set.IsAvailable(index) && current < 0) - return (current, new CustomizeData(index, _customize[index], 0, 0)); + return (current, new CustomizeData(index, _customize[index])); return (current, custom!.Value); } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 7866bda..097d99b 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Dalamud.Interface; -using Glamourer.Customization; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index f435ba8..939b0f6 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,9 +1,11 @@ using System; using System.Numerics; -using Glamourer.Customization; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -13,7 +15,7 @@ public partial class CustomizationDrawer private void DrawIconSelector(CustomizeIndex index) { - using var _ = SetId(index); + using var id = SetId(index); using var bigGroup = ImRaii.Group(); var label = _currentOption; @@ -28,7 +30,7 @@ public partial class CustomizationDrawer } var icon = _service.Service.GetIcon(custom!.Value.IconId); - using (var disabled = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) + using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) ImGui.OpenPopup(IconSelectorPopup); @@ -37,7 +39,7 @@ public partial class CustomizationDrawer ImGuiUtil.HoverIconTooltip(icon, _iconSize); ImGui.SameLine(); - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { DataInputInt(current, npc); if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) @@ -118,16 +120,16 @@ public partial class CustomizationDrawer ImGui.Dummy(new Vector2(ImGui.GetFrameHeight())); } - var oldValue = _customize.Data.At(_currentIndex.ToByteAndMask().ByteIdx); - var tmp = (int)oldValue; + var oldValue = _customize.AtIndex(_currentIndex.ToByteAndMask().ByteIdx); + var tmp = (int)oldValue.Value; ImGui.SetNextItemWidth(_inputIntSize); if (ImGui.InputInt("##text", ref tmp, 1, 1)) { tmp = Math.Clamp(tmp, 0, byte.MaxValue); - if (tmp != oldValue) + if (tmp != oldValue.Value) { - _customize.Data.Set(_currentIndex.ToByteAndMask().ByteIdx, (byte)tmp); - var changes = (byte)tmp ^ oldValue; + _customize.SetByIndex(_currentIndex.ToByteAndMask().ByteIdx, (CustomizeValue)tmp); + var changes = (byte)tmp ^ oldValue.Value; Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0) | ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0) | ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index e9ce9e9..e970fb3 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,10 +1,10 @@ using System; using System.Numerics; -using Glamourer.Customization; using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -91,10 +91,10 @@ public partial class CustomizationDrawer private void DrawListSelector(CustomizeIndex index, bool indexedBy1) { - using var _ = SetId(index); + using var id = SetId(index); using var bigGroup = ImRaii.Group(); - using (var disabled = ImRaii.Disabled(_locked)) + using (_ = ImRaii.Disabled(_locked)) { if (indexedBy1) { @@ -210,7 +210,7 @@ public partial class CustomizationDrawer } else { - using (var disabled = ImRaii.Disabled(_locked)) + using (_ = ImRaii.Disabled(_locked)) { if (ImGui.Checkbox("##toggle", ref tmp)) { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index a288065..c131cf5 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -4,7 +4,7 @@ using System.Reflection; using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; using Dalamud.Plugin; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Services; using ImGuiNET; using OtterGui; @@ -22,13 +22,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio private Exception? _terminate; - private Customize _customize = Customize.Default; + private CustomizeArray _customize = CustomizeArray.Default; private CustomizationSet _set = null!; - public Customize Customize + public CustomizeArray Customize => _customize; - public CustomizeFlag CurrentFlag { get; private set; } public CustomizeFlag Changed { get; private set; } public CustomizeFlag ChangeApply { get; private set; } @@ -47,18 +46,16 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio public void Dispose() => _legacyTattoo?.Dispose(); - public bool Draw(Customize current, bool locked, bool lockedRedraw) + public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw) { - CurrentFlag = CustomizeFlagExtensions.All; _withApply = false; Init(current, locked, lockedRedraw); return DrawInternal(); } - public bool Draw(Customize current, CustomizeFlag apply, bool locked, bool lockedRedraw) + public bool Draw(CustomizeArray current, CustomizeFlag apply, bool locked, bool lockedRedraw) { - CurrentFlag = CustomizeFlagExtensions.All; ChangeApply = apply; _initialApply = apply; _withApply = !_config.HideApplyCheckmarks; @@ -66,12 +63,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio return DrawInternal(); } - private void Init(Customize current, bool locked, bool lockedRedraw) + private void Init(CustomizeArray current, bool locked, bool lockedRedraw) { UpdateSizes(); - _terminate = null; - Changed = 0; - _customize.Load(current); + _terminate = null; + Changed = 0; + _customize = current; _locked = locked; _lockedRedraw = lockedRedraw; } @@ -156,20 +153,20 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio for (var i = 0; i < CustomizeArray.Size; ++i) { using var id = ImRaii.PushId(i); - int value = _customize.Data.Data[i]; + int value = _customize.Data[i]; ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt(string.Empty, ref value, 0, 0)) { var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue); - if (newValue != _customize.Data.Data[i]) + if (newValue != _customize.Data[i]) foreach (var flag in Enum.GetValues()) { - var (j, mask) = flag.ToByteAndMask(); + var (j, _) = flag.ToByteAndMask(); if (j == i) Changed |= flag.ToFlag(); } - _customize.Data.Data[i] = newValue; + _customize.Data[i] = newValue; } } diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 19d73c4..e82ab3c 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -4,7 +4,7 @@ using System.Linq; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; @@ -13,6 +13,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Widgets; +using Penumbra.GameData.Enums; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index e70cd5d..b499bc1 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -7,7 +7,6 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui.Raii; using Penumbra.Api.Enums; @@ -76,7 +75,7 @@ public class PenumbraChangedItemTooltip : IDisposable case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item): break; case EquipSlot.RFinger: - using (var tt = !openTooltip ? null : ImRaii.Tooltip()) + using (_ = !openTooltip ? null : ImRaii.Tooltip()) { ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor (Right Finger)."); ImGui.TextUnformatted($"{prefix}Shift + Right-Click to apply to current actor (Left Finger)."); @@ -92,7 +91,7 @@ public class PenumbraChangedItemTooltip : IDisposable break; default: - using (var tt = !openTooltip ? null : ImRaii.Tooltip()) + using (_ = !openTooltip ? null : ImRaii.Tooltip()) { ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor."); if (last.Valid) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 15d3725..b56b314 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -6,7 +6,6 @@ using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui.Customization; @@ -14,7 +13,6 @@ using Glamourer.Gui.Equipment; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -173,21 +171,21 @@ public class ActorPanel( private void DrawEquipmentMetaToggles() { - using (var _ = ImRaii.Group()) + using (_ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); } ImGui.SameLine(); - using (var _ = ImRaii.Group()) + using (_ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); } ImGui.SameLine(); - using (var _ = ImRaii.Group()) + using (_ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); @@ -199,7 +197,7 @@ public class ActorPanel( var names = _modelChara[_state!.ModelData.ModelId]; var turnHuman = ImGui.Button("Turn Human"); ImGui.Separator(); - using (var box = ImRaii.ListBox("##MonsterList", + using (_ = ImRaii.ListBox("##MonsterList", new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing()))) { if (names.Count == 0) @@ -211,14 +209,14 @@ public class ActorPanel( ImGui.Separator(); ImGui.TextUnformatted("Customization Data"); - using (var font = ImRaii.PushFont(UiBuilder.MonoFont)) + using (_ = ImRaii.PushFont(UiBuilder.MonoFont)) { - foreach (var b in _state.ModelData.Customize.Data) + foreach (var b in _state.ModelData.Customize) { - using (var g = ImRaii.Group()) + using (_ = ImRaii.Group()) { - ImGui.TextUnformatted($" {b:X2}"); - ImGui.TextUnformatted($"{b,3}"); + ImGui.TextUnformatted($" {b.Value:X2}"); + ImGui.TextUnformatted($"{b.Value,3}"); } ImGui.SameLine(); @@ -232,11 +230,11 @@ public class ActorPanel( ImGui.Separator(); ImGui.TextUnformatted("Equipment Data"); - using (var font = ImRaii.PushFont(UiBuilder.MonoFont)) + using (_ = ImRaii.PushFont(UiBuilder.MonoFont)) { foreach (var b in _state.ModelData.GetEquipmentBytes()) { - using (var g = ImRaii.Group()) + using (_ = ImRaii.Group()) { ImGui.TextUnformatted($" {b:X2}"); ImGui.TextUnformatted($"{b,3}"); @@ -298,8 +296,8 @@ public class ActorPanel( BorderColor = ColorId.ActorUnavailable.Value(), }; - private string _newName = string.Empty; - private DesignBase? _newDesign = null; + private string _newName = string.Empty; + private DesignBase? _newDesign; private void SaveDesignOpen() { diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 3eba0cd..2a0453c 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -6,10 +6,8 @@ using System.Text; using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Interop; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; @@ -17,44 +15,29 @@ using OtterGui.Log; using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; using Action = System.Action; -using CustomizeIndex = Glamourer.Customization.CustomizeIndex; namespace Glamourer.Gui.Tabs.AutomationTab; -public class SetPanel +public class SetPanel( + SetSelector _selector, + AutoDesignManager _manager, + JobService _jobs, + ItemUnlockManager _itemUnlocks, + RevertDesignCombo _designCombo, + CustomizeUnlockManager _customizeUnlocks, + CustomizationService _customizations, + IdentifierDrawer _identifierDrawer, + Configuration _config) { - private readonly AutoDesignManager _manager; - private readonly SetSelector _selector; - private readonly ItemUnlockManager _itemUnlocks; - private readonly CustomizeUnlockManager _customizeUnlocks; - private readonly CustomizationService _customizations; - - private readonly Configuration _config; - private readonly RevertDesignCombo _designCombo; - private readonly JobGroupCombo _jobGroupCombo; - private readonly IdentifierDrawer _identifierDrawer; + private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); private string? _tempName; private int _dragIndex = -1; private Action? _endAction; - public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, - RevertDesignCombo designCombo, - CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config) - { - _selector = selector; - _manager = manager; - _itemUnlocks = itemUnlocks; - _customizeUnlocks = customizeUnlocks; - _customizations = customizations; - _identifierDrawer = identifierDrawer; - _config = config; - _designCombo = designCombo; - _jobGroupCombo = new JobGroupCombo(manager, jobs, Glamourer.Log); - } - private AutoDesignSet Selection => _selector.Selection!; @@ -77,7 +60,7 @@ public class SetPanel var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var enabled = Selection.Enabled; if (ImGui.Checkbox("##Enabled", ref enabled)) @@ -87,7 +70,7 @@ public class SetPanel } ImGui.SameLine(); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; if (ImGui.Checkbox("##gameState", ref useGame)) @@ -98,7 +81,7 @@ public class SetPanel } ImGui.SameLine(); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var editing = _config.ShowAutomationSetEditing; if (ImGui.Checkbox("##Show Editing", ref editing)) @@ -230,7 +213,7 @@ public class SetPanel if (_config.ShowUnlockedItemWarnings) { ImGui.TableNextColumn(); - DrawWarnings(design, idx); + DrawWarnings(design); } } @@ -278,7 +261,7 @@ public class SetPanel } } - private void DrawWarnings(AutoDesign design, int idx) + private void DrawWarnings(AutoDesign design) { if (design.Revert) return; @@ -301,27 +284,6 @@ public class SetPanel using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale, 0)); - - static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); - if (sb.Length > 0) - { - sb.Append(suffix); - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color); - } - - ImGuiUtil.HoverTooltip(sb.ToString()); - } - else - { - ImGuiUtil.DrawTextButton(string.Empty, size, 0); - ImGuiUtil.HoverTooltip(good); - } - } - var tt = _config.UnlockedItemMode ? "\nThese items will be skipped when applied automatically.\n\nTo change this, disable the Obtained Item Mode setting." : string.Empty; @@ -355,6 +317,27 @@ public class SetPanel : string.Empty; DrawWarning(sb2, _config.UnlockedItemMode ? 0xA03030F0 : 0x0, size, tt, "All customizations to be applied are unlocked."); ImGui.SameLine(); + return; + + static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good) + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); + if (sb.Length > 0) + { + sb.Append(suffix); + using (_ = ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color); + } + + ImGuiUtil.HoverTooltip(sb.ToString()); + } + else + { + ImGuiUtil.DrawTextButton(string.Empty, size, 0); + ImGuiUtil.HoverTooltip(good); + } + } } private void DrawDragDrop(AutoDesignSet set, int index) @@ -394,7 +377,7 @@ public class SetPanel var newType = design.ApplicationType; var newTypeInt = (uint)newType; style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); - using (var c = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())) + using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())) { if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All)) newType = (AutoDesign.Type)newTypeInt; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 298fe0e..bbcfa32 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Numerics; using Dalamud.Interface; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 9906387..c57c1b5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -1,9 +1,10 @@ using System; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs index f253923..b062980 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -1,10 +1,10 @@ using System; using System.Numerics; -using Glamourer.Customization; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index 85ba96c..ef76b09 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -1,10 +1,9 @@ using System.IO; using System.Numerics; -using Glamourer.Customization; using Glamourer.Interop; using ImGuiNET; using OtterGui; -using OtterGui.Raii; +using Penumbra.GameData.Files; namespace Glamourer.Gui.Tabs.DebugTab; @@ -35,7 +34,7 @@ public class DatFilePanel(ImportService _importService) : IDebugTabTree ImGui.TextUnformatted(_datFile.Value.Version.ToString()); ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g")); ImGui.TextUnformatted(_datFile.Value.Voice.ToString()); - ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString()); + ImGui.TextUnformatted(_datFile.Value.Customize.ToString()); ImGui.TextUnformatted(_datFile.Value.Description); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index af4814a..8b7ae9a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -1,9 +1,7 @@ using System; using System.Linq; using Dalamud.Interface; -using Glamourer.Customization; using Glamourer.Designs; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 03ba048..400b2b1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -1,10 +1,8 @@ using System; using System.Linq; using Dalamud.Interface; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Services; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -85,7 +83,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe DrawDesignData(_parse64); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TextUnformatted(_base64); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 })) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 })) { foreach (var (c1, c2) in _restore.Zip(_base64)) { @@ -99,7 +97,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex()) { - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { ImGui.TextUnformatted(idx.ToString("D2")); ImGui.TextUnformatted(b1.ToString("X2")); @@ -121,7 +119,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe using var font = ImRaii.PushFont(UiBuilder.MonoFont); foreach (var (b, idx) in _base64Bytes.WithIndex()) { - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { ImGui.TextUnformatted(idx.ToString("D2")); ImGui.TextUnformatted(b.ToString("X2")); diff --git a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs index 0fa8765..2318d98 100644 --- a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs @@ -32,7 +32,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree foreach (var (id, job) in _jobs.Jobs) { - ImGuiUtil.DrawTableColumn(id.ToString("D3")); + ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); ImGuiUtil.DrawTableColumn(job.Name); ImGuiUtil.DrawTableColumn(job.Abbreviation); } @@ -68,7 +68,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree foreach (var (id, group) in _jobs.JobGroups) { - ImGuiUtil.DrawTableColumn(id.ToString("D3")); + ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); ImGuiUtil.DrawTableColumn(group.Name); ImGuiUtil.DrawTableColumn(group.Count.ToString()); } diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index e71c69a..596476b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -1,10 +1,8 @@ using System; using System.Numerics; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Customization; using Glamourer.Interop; using Glamourer.Interop.Structs; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -126,15 +124,14 @@ public unsafe class ModelEvaluationPanel( model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty); } - private void DrawWeaponState(Actor actor, Model model) + private static void DrawWeaponState(Actor actor, Model model) { using var id = ImRaii.PushId("WeaponState"); ImGuiUtil.DrawTableColumn("Weapon State"); ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible" : "No Character"); - var text = string.Empty; - + string text; if (!model.IsHuman) { text = "No Model"; @@ -146,19 +143,14 @@ public unsafe class ModelEvaluationPanel( else { var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject; - if ((weapon->Flags & 0x09) == 0x09) - text = "Visible"; - else - text = "Hidden"; + text = (weapon->Flags & 0x09) == 0x09 ? "Visible" : "Hidden"; } ImGuiUtil.DrawTableColumn(text); ImGui.TableNextColumn(); - if (!model.IsHuman) - return; } - private void DrawWetness(Actor actor, Model model) + private static void DrawWetness(Actor actor, Model model) { using var id = ImRaii.PushId("Wetness"); ImGuiUtil.DrawTableColumn("Wetness"); @@ -212,12 +204,12 @@ public unsafe class ModelEvaluationPanel( private void DrawCustomize(Actor actor, Model model) { using var id = ImRaii.PushId("Customize"); - var actorCustomize = new Customize(actor.IsCharacter + var actorCustomize = actor.IsCharacter ? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData - : new CustomizeArray()); - var modelCustomize = new Customize(model.IsHuman + : new CustomizeArray(); + var modelCustomize = model.IsHuman ? *(CustomizeArray*)model.AsHuman->Customize.Data - : new CustomizeArray()); + : new CustomizeArray(); foreach (var type in Enum.GetValues()) { using var id2 = ImRaii.PushId((int)type); @@ -235,7 +227,7 @@ public unsafe class ModelEvaluationPanel( var shift = BitOperations.TrailingZeroCount(mask); var newValue = value + (1 << shift); modelCustomize.Set(type, (CustomizeValue)newValue); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + _changeCustomizeService.UpdateCustomize(model, modelCustomize); } ImGui.SameLine(); @@ -246,14 +238,14 @@ public unsafe class ModelEvaluationPanel( var shift = BitOperations.TrailingZeroCount(mask); var newValue = value - (1 << shift); modelCustomize.Set(type, (CustomizeValue)newValue); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + _changeCustomizeService.UpdateCustomize(model, modelCustomize); } ImGui.SameLine(); if (ImGui.SmallButton("Reset")) { modelCustomize.Set(type, actorCustomize[type]); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + _changeCustomizeService.UpdateCustomize(model, modelCustomize); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 90fc0f5..ffc4b72 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -3,14 +3,15 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; @@ -24,7 +25,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM => false; private string _npcFilter = string.Empty; - private bool _customizeOrGear = false; + private bool _customizeOrGear; public void Draw() { @@ -48,19 +49,17 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.TableNextRow(); var idx = 0; var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips, - d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), Draw); + d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData); ImGui.TableNextColumn(); ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing()); - - return; - void Draw(NpcData data) + void DrawData(NpcData data) { using var id = ImRaii.PushId(idx++); var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled, false)) + if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) { foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand)) _state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual); @@ -76,7 +75,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); - using (var icon = ImRaii.PushFont(UiBuilder.IconFont)) + using (_ = ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); @@ -86,7 +85,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM using var mono = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(_customizeOrGear ? data.Customize.Data.ToString() : data.WriteGear()); + ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear()); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index b6e5fa3..cd47606 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -7,14 +7,12 @@ using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; diff --git a/Glamourer/Gui/Tabs/NpcCombo.cs b/Glamourer/Gui/Tabs/NpcCombo.cs index 91f0db0..4b1274c 100644 --- a/Glamourer/Gui/Tabs/NpcCombo.cs +++ b/Glamourer/Gui/Tabs/NpcCombo.cs @@ -1,4 +1,4 @@ -using Glamourer.Customization; +using Glamourer.GameData; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 154e930..8953501 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Numerics; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Services; using Glamourer.Unlocks; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 3f7531c..d7e1ce3 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -8,7 +8,6 @@ using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index dda4584..5e7e813 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -2,7 +2,6 @@ using Glamourer.Designs; using Glamourer.Events; using Glamourer.State; -using Glamourer.Structs; using Penumbra.GameData.Enums; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 2c64b42..d08fb18 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -1,9 +1,7 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; -using Glamourer.Customization; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using Lumina.Misc; @@ -61,7 +59,7 @@ public static class UiHelpers { var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); - using (var disabled = ImRaii.Disabled(locked)) + using (_ = ImRaii.Disabled(locked)) { if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( "##" + label, flags, out flags)) diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index ef7e762..947ce43 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -2,7 +2,6 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop.Structs; using OtterGui.Classes; @@ -15,7 +14,7 @@ namespace Glamourer.Interop; /// Changes in Race, body type or Gender are probably ignored. /// This operates on draw objects, not game objects. /// -public unsafe class ChangeCustomizeService : EventWrapper>, ChangeCustomizeService.Priority> +public unsafe class ChangeCustomizeService : EventWrapper>, ChangeCustomizeService.Priority> { private readonly PenumbraReloaded _penumbraReloaded; private readonly IGameInteropProvider _interop; @@ -81,10 +80,11 @@ public unsafe class ChangeCustomizeService : EventWrapper(new Customize(*(CustomizeArray*)data)); + var customize = new Ref(*(CustomizeArray*)data); Invoke(this, (Model)human, customize); - ((Customize*)data)->Load(customize.Value); + *(CustomizeArray*)data = customize.Value; } + return _changeCustomizeHook.Original(human, data, skipEquipment); } } diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 6a58809..9ddcc03 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -1,8 +1,6 @@ using System; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -70,7 +68,7 @@ public sealed class CharaFile return; data.SetItem(slot, item); - data.SetStain(slot, (StainId)dye); + data.SetStain(slot, dye); flags |= slot.ToFlag(); flags |= slot.ToStainFlag(); } @@ -94,7 +92,7 @@ public sealed class CharaFile flags |= slot.ToStainFlag(); } - private static CustomizeFlag ParseCustomize(JObject jObj, ref Customize customize) + private static CustomizeFlag ParseCustomize(JObject jObj, ref CustomizeArray customize) { CustomizeFlag ret = 0; customize.Race = ParseRace(jObj, ref ret); @@ -149,7 +147,7 @@ public sealed class CharaFile return id; } - private static void ParseFacial(JObject jObj, ref Customize customize, ref CustomizeFlag application) + private static void ParseFacial(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application) { var jTok = jObj["FacialFeatures"]; if (jTok == null) @@ -186,7 +184,7 @@ public sealed class CharaFile customize[CustomizeIndex.LegacyTattoo] = CustomizeValue.Max; } - private static void ParseHighlights(JObject jObj, ref Customize customize, ref CustomizeFlag application) + private static void ParseHighlights(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application) { var jTok = jObj["EnableHighlights"]; if (jTok == null) @@ -242,15 +240,15 @@ public sealed class CharaFile throw new Exception($"Age {age} != Normal is not supported."); } - private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref Customize customize, + private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref CustomizeArray customize, ref CustomizeFlag application) { var jTok = jObj[property]; if (jTok == null) return; - customize.Data.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject(); - application |= idx.ToFlag(); + customize.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject(); + application |= idx.ToFlag(); } private static SubRace ParseTribe(JObject jObj, ref CustomizeFlag application) diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index c916ad8..e525e7e 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -42,7 +42,7 @@ public sealed class CmaFile var byteData = Convert.FromHexString(bytes); fixed (byte* ptr = byteData) { - data.Customize.Data.Read(ptr); + data.Customize.Read(ptr); } } @@ -64,7 +64,7 @@ public sealed class CmaFile data.SetStain(slot, armor.Stain); } - data.Customize.Data.Read(ptr); + data.Customize.Read(ptr); } } diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 9285ec6..2bfa8d5 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -1,12 +1,10 @@ using System; -using System.Linq; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Interop.Structs; -using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 86f2343..4cac25e 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -4,13 +4,14 @@ using System.IO; using System.Linq; using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Interop.CharaFile; using Glamourer.Services; -using Glamourer.Structs; using ImGuiNET; using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Files; +using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -139,7 +140,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage return true; } - public bool SaveDesignAsDat(string path, in Customize input, string description) + public bool SaveDesignAsDat(string path, in CustomizeArray input, string description) { if (!Verify(input, out var voice)) return false; @@ -168,7 +169,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage } } - public bool Verify(in Customize input, out byte voice, byte? inputVoice = null) + public bool Verify(in CustomizeArray input, out byte voice, byte? inputVoice = null) { voice = 0; if (_customizations.ValidateClan(input.Clan, input.Race, out _, out _).Length > 0) diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 2fe322f..5ee9cf6 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -6,7 +6,9 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Interop.Structs; -using Glamourer.Structs; +using Penumbra.GameData; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -14,19 +16,20 @@ public class JobService : IDisposable { private readonly nint _characterDataOffset; - public readonly IReadOnlyDictionary Jobs; - public readonly IReadOnlyDictionary JobGroups; - public readonly IReadOnlyList AllJobGroups; + public readonly DictJob Jobs; + public readonly DictJobGroup JobGroups; + + public IReadOnlyList AllJobGroups + => JobGroups.AllJobGroups; public event Action? JobChanged; - public JobService(IDataManager gameData, IGameInteropProvider interop) + public JobService(DictJob jobs, DictJobGroup jobGroups, IDataManager gameData, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _characterDataOffset = Marshal.OffsetOf(nameof(Character.CharacterData)); - Jobs = GameData.Jobs(gameData); - AllJobGroups = GameData.AllJobGroups(gameData); - JobGroups = GameData.JobGroups(gameData); + Jobs = jobs; + JobGroups = jobGroups; _changeJobHook.Enable(); } diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 750527b..066d985 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -2,8 +2,6 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Glamourer.Customization; -using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -116,8 +114,8 @@ public readonly unsafe struct Actor : IEquatable public CharacterWeapon GetOffhand() => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).ModelId.Value); - public Customize GetCustomize() - => *(Customize*)&AsCharacter->DrawData.CustomizeData; + public CustomizeArray GetCustomize() + => *(CustomizeArray*)&AsCharacter->DrawData.CustomizeData; // TODO remove this when available in ClientStructs internal ref CrestFlag CrestBitfield diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 77bf24e..9705248 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -1,7 +1,6 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Customization; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; @@ -91,8 +90,8 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; - public Customize GetCustomize() - => *(Customize*)&AsHuman->Customize; + public CustomizeArray GetCustomize() + => *(CustomizeArray*)&AsHuman->Customize; public (Model Address, CharacterWeapon Data) GetMainhand() { diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index f5a0ec0..31472ff 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Glamourer.Events; using Glamourer.Interop.Structs; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 7ccf963..4c87430 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -5,7 +5,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Glamourer.Interop.Structs; -using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -14,18 +13,15 @@ namespace Glamourer.Interop; public unsafe class WeaponService : IDisposable { private readonly WeaponLoading _event; - private readonly CrestService _crestService; private readonly ThreadLocal _inUpdate = new(() => false); private readonly delegate* unmanaged[Stdcall] _original; - - public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService) + public WeaponService(WeaponLoading @event, IGameInteropProvider interop) { - _event = @event; - _crestService = crestService; + _event = @event; _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); _original = @@ -73,7 +69,7 @@ public unsafe class WeaponService : IDisposable _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); - + if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Skeleton.Id == 0) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 417f1b9..2135357 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -5,13 +5,11 @@ using Dalamud.Game.Command; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; diff --git a/Glamourer/Services/CustomizationService.cs b/Glamourer/Services/CustomizationService.cs index b5f474f..9e28e42 100644 --- a/Glamourer/Services/CustomizationService.cs +++ b/Glamourer/Services/CustomizationService.cs @@ -2,10 +2,11 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Dalamud.Plugin.Services; -using Glamourer.Customization; +using Glamourer.GameData; using OtterGui.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Services; @@ -30,13 +31,12 @@ public sealed class CustomizationService( public Task Awaiter => _task; - public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues, + public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues, CustomizeFlag applyWhich, bool allowUnknown) { CustomizeFlag applied = 0; CustomizeFlag changed = 0; - Customize ret = default; - ret.Load(oldValues); + var ret = oldValues; if (applyWhich.HasFlag(CustomizeFlag.Clan)) { changed |= ChangeClan(ref ret, newValues.Clan); @@ -247,7 +247,7 @@ public sealed class CustomizationService( } /// Change a clan while keeping all other customizations valid. - public CustomizeFlag ChangeClan(ref Customize customize, SubRace newClan) + public CustomizeFlag ChangeClan(ref CustomizeArray customize, SubRace newClan) { if (customize.Clan == newClan) return 0; @@ -271,7 +271,7 @@ public sealed class CustomizationService( } /// Change a gender while keeping all other customizations valid. - public CustomizeFlag ChangeGender(ref Customize customize, Gender newGender) + public CustomizeFlag ChangeGender(ref CustomizeArray customize, Gender newGender) { if (customize.Gender == newGender) return 0; @@ -288,7 +288,7 @@ public sealed class CustomizationService( return FixValues(set, ref customize) | CustomizeFlag.Gender; } - private static CustomizeFlag FixValues(CustomizationSet set, ref Customize customize) + private static CustomizeFlag FixValues(CustomizationSet set, ref CustomizeArray customize) { CustomizeFlag flags = 0; foreach (var idx in CustomizationExtensions.AllBasic) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index d88a90c..3afe390 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -15,7 +15,6 @@ using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.State; -using Glamourer.Structs; using Glamourer.Unlocks; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; @@ -24,6 +23,7 @@ using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Services; diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 3cd7cba..484d0a9 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -1,13 +1,11 @@ -using Glamourer.Customization; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Events; -using Glamourer.Structs; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using System.Linq; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; -using CustomizeIndex = Glamourer.Customization.CustomizeIndex; +using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -34,7 +32,7 @@ public class ActorState public DesignData ModelData; /// The last seen job. - public byte LastJob; + public JobId LastJob; /// The Lock-Key locking this state. public uint Combination; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 5ab3f41..67c9e08 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -2,7 +2,6 @@ using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Interop; @@ -12,7 +11,7 @@ using ImGuiNET; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using CustomizeIndex = Glamourer.Customization.CustomizeIndex; +using CustomizeIndex = Penumbra.GameData.Enums.CustomizeIndex; namespace Glamourer.State; @@ -107,7 +106,7 @@ public unsafe class FunModule : IDisposable } } - public void ApplyFun(Actor actor, Span armor, ref Customize customize) + public void ApplyFun(Actor actor, Span armor, ref CustomizeArray customize) { if (!ValidFunTarget(actor)) return; @@ -181,7 +180,7 @@ public unsafe class FunModule : IDisposable } } - public void ApplyOops(ref Customize customize) + public void ApplyOops(ref CustomizeArray customize) { if (_codes.EnabledOops == Race.Unknown) return; @@ -193,7 +192,7 @@ public unsafe class FunModule : IDisposable _customizations.ChangeClan(ref customize, targetClan); } - public void ApplyIndividual(ref Customize customize) + public void ApplyIndividual(ref CustomizeArray customize) { if (!_codes.EnabledIndividual) return; @@ -209,7 +208,7 @@ public unsafe class FunModule : IDisposable } } - public void Apply63(ref Customize customize) + public void Apply63(ref CustomizeArray customize) { if (!_codes.Enabled63 || customize.Race is Race.Hrothgar) // TODO Female Hrothgar return; @@ -217,7 +216,7 @@ public unsafe class FunModule : IDisposable _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); } - public void ApplySizing(Actor actor, ref Customize customize) + public void ApplySizing(Actor actor, ref CustomizeArray customize) { if (_codes.EnabledSizing == CodeService.Sizing.None) return; diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index a91a1eb..43c9097 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,11 +1,9 @@ using System.Linq; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; -using Glamourer.Structs; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -40,7 +38,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We /// Change the customization values of actors either by applying them via update or redrawing, /// this depends on whether the changes include changes to Race, Gender, Body Type or Face. /// - public unsafe void ChangeCustomize(ActorData data, in Customize customize, ActorState? state = null) + public unsafe void ChangeCustomize(ActorData data, in CustomizeArray customize, ActorState? _ = null) { foreach (var actor in data.Objects) { @@ -48,15 +46,15 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We if (!mdl.IsCharacterBase) continue; - var flags = Customize.Compare(mdl.GetCustomize(), customize); + var flags = CustomizeArray.Compare(mdl.GetCustomize(), customize); if (!flags.RequiresRedraw() || !mdl.IsHuman) { - _changeCustomize.UpdateCustomize(mdl, customize.Data); + _changeCustomize.UpdateCustomize(mdl, customize); } else if (data.Objects.Count > 1 && _objects.IsInGPose && !actor.IsGPoseOrCutscene) { - var mdlCustomize = (Customize*)&mdl.AsHuman->Customize; - mdlCustomize->Load(customize); + var mdlCustomize = (CustomizeArray*)&mdl.AsHuman->Customize; + *mdlCustomize = customize; _penumbra.RedrawObject(actor, RedrawType.AfterGPose); } else @@ -66,7 +64,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We } } - /// + /// public ActorData ChangeCustomize(ActorState state, bool apply) { var data = GetData(state); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index ebc2661..50b2605 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,10 +1,8 @@ using System; using System.Linq; using Dalamud.Plugin.Services; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Services; -using Glamourer.Structs; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -30,7 +28,7 @@ public class StateEditor /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. /// We currently only allow changing things to humans, not humans to monsters. - public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source, + public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source, out uint oldModelId, uint key = 0) { oldModelId = state.ModelData.ModelId; @@ -57,7 +55,7 @@ public class StateEditor return false; // Fix up everything else to make sure the result is a valid human. - state.ModelData.Customize = Customize.Default; + state.ModelData.Customize = CustomizeArray.Default; state.ModelData.SetDefaultEquipment(_items); state.ModelData.SetHatVisible(true); state.ModelData.SetWeaponVisible(true); @@ -104,8 +102,8 @@ public class StateEditor } /// Change an entire customization array according to flags. - public bool ChangeHumanCustomize(ActorState state, in Customize customizeInput, CustomizeFlag applyWhich, StateChanged.Source source, - out Customize old, out CustomizeFlag changed, uint key = 0) + public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateChanged.Source source, + out CustomizeArray old, out CustomizeFlag changed, uint key = 0) { old = state.ModelData.Customize; changed = 0; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index f5f2e3b..d7f947f 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -1,5 +1,4 @@ using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -12,7 +11,6 @@ using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; -using Glamourer.Structs; using Penumbra.GameData.DataContainers; namespace Glamourer.State; @@ -114,7 +112,7 @@ public class StateListener : IDisposable _creatingIdentifier = actor.GetIdentifier(_actors); ref var modelId = ref *(uint*)modelPtr; - ref var customize = ref *(Customize*)customizePtr; + ref var customize = ref *(CustomizeArray*)customizePtr; if (_autoDesignApplier.Reduce(actor, _creatingIdentifier, out _creatingState)) { switch (UpdateBaseData(actor, _creatingState, modelId, customizePtr, equipDataPtr)) @@ -140,7 +138,7 @@ public class StateListener : IDisposable ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } - private unsafe void OnCustomizeChange(Model model, Ref customize) + private unsafe void OnCustomizeChange(Model model, Ref customize) { if (!model.IsHuman) return; @@ -156,7 +154,7 @@ public class StateListener : IDisposable UpdateCustomize(actor, state, ref customize.Value, false); } - private void UpdateCustomize(Actor actor, ActorState state, ref Customize customize, bool checkTransform) + private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform) { switch (UpdateBaseData(actor, state, customize, checkTransform)) { @@ -515,7 +513,7 @@ public class StateListener : IDisposable if (isHuman) state.BaseData = _manager.FromActor(actor, false, false); else - state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, equipData); + state.BaseData.LoadNonHuman(modelId, *(CustomizeArray*)customizeData, equipData); return UpdateState.Change; } @@ -526,7 +524,7 @@ public class StateListener : IDisposable /// only if we kept track of state of someone who went to the aesthetician, /// or if they used other tools to change things. /// - private UpdateState UpdateBaseData(Actor actor, ActorState state, Customize customize, bool checkTransform) + private UpdateState UpdateBaseData(Actor actor, ActorState state, CustomizeArray customize, bool checkTransform) { // Customize array does not agree between game object and draw object => transformation. if (checkTransform && !actor.GetCustomize().Equals(customize)) @@ -537,7 +535,7 @@ public class StateListener : IDisposable return UpdateState.NoChange; // TODO: handle wrong base data. // Update customize base state. - state.BaseData.Customize.Load(customize); + state.BaseData.Customize = customize; return UpdateState.Change; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 8446288..dc83305 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -4,13 +4,11 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud.Plugin.Services; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; -using Glamourer.Structs; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -18,8 +16,15 @@ using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateManager(ActorManager _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, - HumanModelList _humans, ICondition _condition, IClientState _clientState) +public class StateManager( + ActorManager _actors, + ItemManager _items, + StateChanged _event, + StateApplier _applier, + StateEditor _editor, + HumanModelList _humans, + ICondition _condition, + IClientState _clientState) : IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -102,7 +107,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged // TODO reverse search model data to get model id from model. if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) { - ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData, + ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, (nint)(&actor.AsCharacter->DrawData.Head)); return ret; } @@ -194,9 +199,9 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged return; var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id); - offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50); - offhand.Variant = mainhand.Variant; - offhand.Weapon = mainhand.Weapon; + offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50); + offhand.Variant = mainhand.Variant; + offhand.Weapon = mainhand.Weapon; ret.SetItem(EquipSlot.Hands, gauntlets); ret.SetStain(EquipSlot.Hands, mainhand.Stain); } @@ -205,10 +210,10 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged /// Turn an actor human. public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0) - => ChangeModelId(state, 0, Customize.Default, nint.Zero, source, key); + => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); /// Turn an actor to. - public void ChangeModelId(ActorState state, uint modelId, Customize customize, nint equipData, StateChanged.Source source, + public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateChanged.Source source, uint key = 0) { if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key)) @@ -233,7 +238,8 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged } /// Change an entire customization array according to flags. - public void ChangeCustomize(ActorState state, in Customize customizeInput, CustomizeFlag apply, StateChanged.Source source, uint key = 0) + public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateChanged.Source source, + uint key = 0) { if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key)) return; @@ -447,7 +453,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged var redraw = state.ModelData.ModelId != state.BaseData.ModelId || !state.ModelData.IsHuman - || Customize.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); + || CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) @@ -458,7 +464,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged state[slot, true] = StateChanged.Source.Game; state[slot, false] = StateChanged.Source.Game; } - + foreach (var type in Enum.GetValues()) state[type] = StateChanged.Source.Game; @@ -470,7 +476,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged actors = ApplyAll(state, redraw, true); Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null); + _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors); } public void ResetStateFixed(ActorState state, uint key = 0) @@ -538,7 +544,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged if (!GetOrCreate(actor, out var state)) return; - ApplyAll(state, !actor.Model.IsHuman || Customize.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), + ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); } diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 3f694ee..4464aee 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Glamourer.Interop.Structs; -using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 8764438..a1e95ef 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -8,10 +8,11 @@ using Dalamud.Plugin.Services; using Dalamud.Utility; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.UI; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData.Enums; namespace Glamourer.Unlocks; @@ -172,7 +173,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable => UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id), "customization"); - /// Create a list of all unlockable hairstyles and facepaints. + /// Create a list of all unlockable hairstyles and face paints. private static Dictionary CreateUnlockableCustomizations(CustomizationService customizations, IDataManager gameData) { diff --git a/Penumbra.GameData b/Penumbra.GameData index 3787e82..d9a9627 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3787e82d1b84d2542b6e4238060d75383a4b12a1 +Subproject commit d9a962748364b267df1b186cdaebb9ed9f3fceb6 From 03a0cb5514b4704d15a94c10a985438f10ad3428 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Dec 2023 14:22:39 +0100 Subject: [PATCH 05/31] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index d9a9627..ed37f83 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d9a962748364b267df1b186cdaebb9ed9f3fceb6 +Subproject commit ed37f83424c11a5a601e74f4660cd52ebd68a7b3 From aae4141550ab206fc2fbff6022f9a55d0d907988 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 23 Dec 2023 13:08:20 +0100 Subject: [PATCH 06/31] Add IPC to set single items. --- Glamourer/Api/GlamourerIpc.Set.cs | 92 +++++++++++++++++++ Glamourer/Api/GlamourerIpc.cs | 16 +++- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 48 +++++++++- Penumbra.GameData | 2 +- 4 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 Glamourer/Api/GlamourerIpc.Set.cs diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs new file mode 100644 index 0000000..f6fe92e --- /dev/null +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -0,0 +1,92 @@ +using System.Linq; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Glamourer.Events; +using Glamourer.Services; +using Penumbra.Api.Helpers; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Api; + +public partial class GlamourerIpc +{ + public enum GlamourerErrorCode + { + Success, + ActorNotFound, + ActorNotHuman, + ItemInvalid, + } + + public const string LabelSetItem = "Glamourer.SetItem"; + public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName"; + + + private readonly FuncProvider _setItemProvider; + private readonly FuncProvider _setItemByActorNameProvider; + + public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) + => new(pi, LabelSetItem); + + public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) + => new(pi, LabelSetItemByActorName); + + private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, uint key) + { + if (itemId.Id == 0) + itemId = ItemManager.NothingId(slot); + + var item = _items.Resolve(slot, itemId); + if (!item.Valid) + return GlamourerErrorCode.ItemInvalid; + + var identifier = _actors.FromObject(character, false, false, false); + if (!identifier.IsValid) + return GlamourerErrorCode.ActorNotFound; + + if (!_stateManager.TryGetValue(identifier, out var state)) + { + _objects.Update(); + var data = _objects[identifier]; + if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state)) + return GlamourerErrorCode.ActorNotFound; + } + + if (!state.ModelData.IsHuman) + return GlamourerErrorCode.ActorNotHuman; + + _stateManager.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + return GlamourerErrorCode.Success; + } + + private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, uint key) + { + if (itemId.Id == 0) + itemId = ItemManager.NothingId(slot); + + var item = _items.Resolve(slot, itemId); + if (!item.Valid) + return GlamourerErrorCode.ItemInvalid; + + var found = false; + _objects.Update(); + foreach (var identifier in FindActorsRevert(name).Distinct()) + { + if (!_stateManager.TryGetValue(identifier, out var state)) + { + var data = _objects[identifier]; + if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state)) + continue; + } + + if (!state.ModelData.IsHuman) + return GlamourerErrorCode.ActorNotHuman; + + _stateManager.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + found = true; + } + + return found ? GlamourerErrorCode.Success : GlamourerErrorCode.ActorNotFound; + } +} diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 53cfb18..a19dd6f 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -7,6 +7,7 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; +using Glamourer.Services; using Glamourer.State; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; @@ -15,7 +16,7 @@ using Penumbra.String; namespace Glamourer.Api; -public partial class GlamourerIpc : IDisposable +public sealed partial class GlamourerIpc : IDisposable { public const int CurrentApiVersionMajor = 0; public const int CurrentApiVersionMinor = 4; @@ -25,15 +26,18 @@ public partial class GlamourerIpc : IDisposable private readonly ActorManager _actors; private readonly DesignConverter _designConverter; private readonly AutoDesignApplier _autoDesignApplier; + private readonly ItemManager _items; public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors, - DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier) + DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, + ItemManager items) { _stateManager = stateManager; _objects = objects; _actors = actors; _designConverter = designConverter; _autoDesignApplier = autoDesignApplier; + _items = items; _gPose = gPose; _stateChangedEvent = stateChangedEvent; _apiVersionProvider = new FuncProvider(pi, LabelApiVersion, ApiVersion); @@ -76,6 +80,11 @@ public partial class GlamourerIpc : IDisposable _stateChangedProvider = new EventProvider>(pi, LabelStateChanged); _gPoseChangedProvider = new EventProvider(pi, LabelGPoseChanged); + _setItemProvider = new FuncProvider(pi, LabelSetItem, + (idx, slot, item, key) => (int)SetItem(idx, (EquipSlot)slot, item, key)); + _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, + (name, slot, item, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, key)); + _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); } @@ -114,6 +123,9 @@ public partial class GlamourerIpc : IDisposable _stateChangedProvider.Dispose(); _gPose.Unsubscribe(OnGPoseChanged); _gPoseChangedProvider.Dispose(); + + _setItemProvider.Dispose(); + _setItemByActorNameProvider.Dispose(); } private IEnumerable FindActors(string actorName) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index e42d252..329a2af 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -1,10 +1,14 @@ using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Interop; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; @@ -16,15 +20,20 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag public bool Disabled => false; - private int _gameObjectIndex; - private string _gameObjectName = string.Empty; - private string _base64Apply = string.Empty; + private int _gameObjectIndex; + private CustomItemId _customItemId; + private EquipSlot _slot = EquipSlot.Head; + private string _gameObjectName = string.Empty; + private string _base64Apply = string.Empty; + private GlamourerIpc.GlamourerErrorCode _setItemEc; + private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc; - public void Draw() + public unsafe void Draw() { ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); + DrawItemIdInput(); using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) return; @@ -104,5 +113,36 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag if (ImGui.Button("Revert##CustomizeCharacter")) GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem); + ImGui.TableNextColumn(); + if (ImGui.Button("Set##SetItem")) + _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) + .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, 1337); + if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) + { + ImGui.SameLine(); + ImGui.TextUnformatted(_setItemEc.ToString()); + } + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName); + ImGui.TableNextColumn(); + if (ImGui.Button("Set##SetItemByActorName")) + _setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) + .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, 1337); + if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) + { + ImGui.SameLine(); + ImGui.TextUnformatted(_setItemByActorNameEc.ToString()); + } + } + + private unsafe void DrawItemIdInput() + { + var tmp = _customItemId.Id; + if (ImGui.InputScalar("Custom Item ID", ImGuiDataType.U64, (nint)(&tmp), nint.Zero, nint.Zero)) + _customItemId = (CustomItemId)tmp; + ImGui.SameLine(); + EquipSlotCombo.Draw("Equip Slot", string.Empty, 200 * ImGuiHelpers.GlobalScale, ref _slot); } } diff --git a/Penumbra.GameData b/Penumbra.GameData index ed37f83..4af8775 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ed37f83424c11a5a601e74f4660cd52ebd68a7b3 +Subproject commit 4af8775f0925ff89e6900c8816b03e0ffeb73f6d From ab76d3508b9a55d05523c99b74b8c45934464d6d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 23 Dec 2023 19:33:50 +0100 Subject: [PATCH 07/31] Rework and improve CustomizationManager and stuff. --- Glamourer/Automation/AutoDesignApplier.cs | 8 +- Glamourer/Designs/Design.cs | 6 +- Glamourer/Designs/DesignBase.cs | 46 +- Glamourer/Designs/DesignConverter.cs | 2 +- Glamourer/Designs/DesignManager.cs | 4 +- Glamourer/GameData/CharaMakeParams.cs | 4 +- Glamourer/GameData/ColorParameters.cs | 9 +- Glamourer/GameData/CustomName.cs | 38 -- Glamourer/GameData/CustomizationManager.cs | 38 -- Glamourer/GameData/CustomizationNpcOptions.cs | 56 -- Glamourer/GameData/CustomizationOptions.cs | 530 ------------------ Glamourer/GameData/CustomizeData.cs | 21 +- Glamourer/GameData/CustomizeManager.cs | 77 +++ .../{CustomizationSet.cs => CustomizeSet.cs} | 142 ++--- Glamourer/GameData/CustomizeSetFactory.cs | 459 +++++++++++++++ Glamourer/GameData/ICustomizationManager.cs | 17 - Glamourer/GameData/NpcCustomizeSet.cs | 164 ++++-- Glamourer/GameData/NpcData.cs | 78 ++- .../Customization/CustomizationDrawer.Icon.cs | 8 +- .../Gui/Customization/CustomizationDrawer.cs | 6 +- Glamourer/Gui/DesignCombo.cs | 4 +- Glamourer/Gui/GenericPopupWindow.cs | 5 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 4 +- .../DebugTab/CustomizationServicePanel.cs | 23 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 29 +- Glamourer/Interop/ImportService.cs | 6 +- ...mizationService.cs => CustomizeService.cs} | 90 +-- Glamourer/Services/ServiceManager.cs | 2 +- Glamourer/State/FunModule.cs | 6 +- Glamourer/State/StateEditor.cs | 6 +- Glamourer/State/StateListener.cs | 6 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 43 +- OtterGui | 2 +- 34 files changed, 916 insertions(+), 1025 deletions(-) delete mode 100644 Glamourer/GameData/CustomName.cs delete mode 100644 Glamourer/GameData/CustomizationManager.cs delete mode 100644 Glamourer/GameData/CustomizationNpcOptions.cs delete mode 100644 Glamourer/GameData/CustomizationOptions.cs create mode 100644 Glamourer/GameData/CustomizeManager.cs rename Glamourer/GameData/{CustomizationSet.cs => CustomizeSet.cs} (94%) create mode 100644 Glamourer/GameData/CustomizeSetFactory.cs delete mode 100644 Glamourer/GameData/ICustomizationManager.cs rename Glamourer/Services/{CustomizationService.cs => CustomizeService.cs} (62%) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 9f3e286..83ce3c4 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -27,7 +27,7 @@ public class AutoDesignApplier : IDisposable private readonly JobService _jobs; private readonly EquippedGearset _equippedGearset; private readonly ActorManager _actors; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; private readonly ItemUnlockManager _itemUnlocks; private readonly AutomationChanged _event; @@ -48,7 +48,7 @@ public class AutoDesignApplier : IDisposable } public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, - CustomizationService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, + CustomizeService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset) { @@ -468,7 +468,7 @@ public class AutoDesignApplier : IDisposable totalCustomizeFlags |= CustomizeFlag.Face; } - var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); var face = state.ModelData.Customize.Face; foreach (var index in Enum.GetValues()) { @@ -477,7 +477,7 @@ public class AutoDesignApplier : IDisposable continue; var value = design.Customize[index]; - if (CustomizationService.IsCustomizationValid(set, face, index, value, out var data)) + if (CustomizeService.IsCustomizationValid(set, face, index, value, out var data)) { if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) continue; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index d10fe29..7382fce 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -15,7 +15,7 @@ public sealed class Design : DesignBase, ISavable { #region Data - internal Design(CustomizationService customize, ItemManager items) + internal Design(CustomizeService customize, ItemManager items) : base(customize, items) { } @@ -98,7 +98,7 @@ public sealed class Design : DesignBase, ISavable #region Deserialization - public static Design LoadDesign(CustomizationService customizations, ItemManager items, JObject json) + public static Design LoadDesign(CustomizeService customizations, ItemManager items, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch @@ -108,7 +108,7 @@ public sealed class Design : DesignBase, ISavable }; } - private static Design LoadDesignV1(CustomizationService customizations, ItemManager items, JObject json) + private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, JObject json) { static string[] ParseTags(JObject json) { diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 8e11ad8..7602f80 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -25,35 +25,35 @@ public class DesignBase public ref DesignData GetDesignDataRef() => ref _designData; - internal DesignBase(CustomizationService customize, ItemManager items) + internal DesignBase(CustomizeService customize, ItemManager items) { _designData.SetDefaultEquipment(items); - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } - internal DesignBase(CustomizationService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) + internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) { _designData = designData; ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; ApplyEquip = equipFlags & EquipFlagExtensions.All; _designFlags = 0; - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } internal DesignBase(DesignBase clone) { _designData = clone._designData; - CustomizationSet = clone.CustomizationSet; + CustomizeSet = clone.CustomizeSet; ApplyCustomize = clone.ApplyCustomizeRaw; ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; _designFlags = clone._designFlags & (DesignFlags)0x0F; } /// Ensure that the customization set is updated when the design data changes. - internal void SetDesignData(CustomizationService customize, in DesignData other) + internal void SetDesignData(CustomizeService customize, in DesignData other) { _designData = other; - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } #region Application Data @@ -69,11 +69,11 @@ public class DesignBase } private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; - public CustomizationSet CustomizationSet { get; private set; } + public CustomizeSet CustomizeSet { get; private set; } internal CustomizeFlag ApplyCustomize { - get => _applyCustomize.FixApplication(CustomizationSet); + get => _applyCustomize.FixApplication(CustomizeSet); set => _applyCustomize = value & CustomizeFlagExtensions.AllRelevant; } @@ -84,13 +84,13 @@ public class DesignBase internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; - public bool SetCustomize(CustomizationService customizationService, CustomizeArray customize) + public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) { if (customize.Equals(_designData.Customize)) return false; _designData.Customize = customize; - CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender); + CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); return true; } @@ -240,10 +240,10 @@ public class DesignBase } } - private CustomizationSet SetCustomizationSet(CustomizationService customize) + private CustomizeSet SetCustomizationSet(CustomizeService customize) => !_designData.IsHuman - ? customize.Service.GetList(SubRace.Midlander, Gender.Male) - : customize.Service.GetList(_designData.Customize.Clan, _designData.Customize.Gender); + ? customize.Manager.GetSet(SubRace.Midlander, Gender.Male) + : customize.Manager.GetSet(_designData.Customize.Clan, _designData.Customize.Gender); #endregion @@ -330,7 +330,7 @@ public class DesignBase #region Deserialization - public static DesignBase LoadDesignBase(CustomizationService customizations, ItemManager items, JObject json) + public static DesignBase LoadDesignBase(CustomizeService customizations, ItemManager items, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch @@ -340,7 +340,7 @@ public class DesignBase }; } - private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json) + private static DesignBase LoadDesignV1Base(CustomizeService customizations, ItemManager items, JObject json) { var ret = new DesignBase(customizations, items); LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); @@ -435,7 +435,7 @@ public class DesignBase design._designData.SetVisor(metaValue.ForcedValue); } - protected static void LoadCustomize(CustomizationService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, + protected static void LoadCustomize(CustomizeService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, bool allowUnknown) { if (json == null) @@ -473,7 +473,7 @@ public class DesignBase { var arrayText = json["Array"]?.ToObject() ?? string.Empty; design._designData.Customize.LoadBase64(arrayText); - design.CustomizationSet = design.SetCustomizationSet(customizations); + design.CustomizeSet = design.SetCustomizationSet(customizations); return; } @@ -485,18 +485,18 @@ public class DesignBase design._designData.Customize.Race = race; design._designData.Customize.Clan = clan; design._designData.Customize.Gender = gender; - design.CustomizationSet = design.SetCustomizationSet(customizations); + design.CustomizeSet = design.SetCustomizationSet(customizations); design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); - var set = design.CustomizationSet; + var set = design.CustomizeSet; foreach (var idx in CustomizationExtensions.AllBasic) { var tok = json[idx.ToString()]; var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); if (set.IsAvailable(idx)) - PrintWarning(CustomizationService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, + PrintWarning(CustomizeService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, allowUnknown)); var apply = tok?["Apply"]?.ToObject() ?? false; design._designData.Customize[idx] = data; @@ -504,7 +504,7 @@ public class DesignBase } } - public void MigrateBase64(CustomizationService customize, ItemManager items, HumanModelList humans, string base64) + public void MigrateBase64(CustomizeService customize, ItemManager items, HumanModelList humans, string base64) { try { @@ -518,7 +518,7 @@ public class DesignBase SetApplyVisorToggle(applyVisor); SetApplyWeaponVisible(applyWeapon); SetApplyWetness(true); - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } catch (Exception ex) { diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 282ab0a..f7867c4 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -13,7 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizationService _customize, HumanModelList _humans) +public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans) { public const byte Version = 6; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 1320d6a..80cd0e0 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -18,7 +18,7 @@ namespace Glamourer.Designs; public class DesignManager { - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly ItemManager _items; private readonly HumanModelList _humans; private readonly SaveService _saveService; @@ -29,7 +29,7 @@ public class DesignManager public IReadOnlyList Designs => _designs; - public DesignManager(SaveService saveService, ItemManager items, CustomizationService customizations, + public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, DesignChanged @event, HumanModelList humans) { _saveService = saveService; diff --git a/Glamourer/GameData/CharaMakeParams.cs b/Glamourer/GameData/CharaMakeParams.cs index 12dedf9..4db5825 100644 --- a/Glamourer/GameData/CharaMakeParams.cs +++ b/Glamourer/GameData/CharaMakeParams.cs @@ -4,9 +4,7 @@ using Lumina.Excel.GeneratedSheets; namespace Glamourer.GameData; -/// -/// A custom version of CharaMakeParams that is easier to parse. -/// +/// A custom version of CharaMakeParams that is easier to parse. [Sheet("CharaMakeParams")] public class CharaMakeParams : ExcelRow { diff --git a/Glamourer/GameData/ColorParameters.cs b/Glamourer/GameData/ColorParameters.cs index 630a5a7..975e003 100644 --- a/Glamourer/GameData/ColorParameters.cs +++ b/Glamourer/GameData/ColorParameters.cs @@ -6,10 +6,12 @@ using Penumbra.String.Functions; namespace Glamourer.GameData; +/// Parse the Human.cmp file as a list of 4-byte integer values to obtain colors. public class ColorParameters : IReadOnlyList { private readonly uint[] _rgbaColors; + /// Get a slice of the colors starting at and containing colors. public ReadOnlySpan GetSlice(int offset, int count) => _rgbaColors.AsSpan(offset, count); @@ -18,6 +20,7 @@ public class ColorParameters : IReadOnlyList try { var file = gameData.GetFile("chara/xls/charamake/human.cmp")!; + // Just copy all the data into an uint array. _rgbaColors = new uint[file.Data.Length >> 2]; fixed (byte* ptr1 = file.Data) { @@ -32,19 +35,23 @@ public class ColorParameters : IReadOnlyList log.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); - _rgbaColors = Array.Empty(); + _rgbaColors = []; } } + /// public IEnumerator GetEnumerator() => (IEnumerator)_rgbaColors.GetEnumerator(); + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// public int Count => _rgbaColors.Length; + /// public uint this[int index] => _rgbaColors[index]; } diff --git a/Glamourer/GameData/CustomName.cs b/Glamourer/GameData/CustomName.cs deleted file mode 100644 index c7d74a1..0000000 --- a/Glamourer/GameData/CustomName.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Glamourer.GameData; - -/// For localization from the game files directly. -public enum CustomName -{ - MidlanderM, - HighlanderM, - WildwoodM, - DuskwightM, - PlainsfolkM, - DunesfolkM, - SeekerOfTheSunM, - KeeperOfTheMoonM, - SeawolfM, - HellsguardM, - RaenM, - XaelaM, - HelionM, - LostM, - RavaM, - VeenaM, - MidlanderF, - HighlanderF, - WildwoodF, - DuskwightF, - PlainsfolkF, - DunesfolkF, - SeekerOfTheSunF, - KeeperOfTheMoonF, - SeawolfF, - HellsguardF, - RaenF, - XaelaF, - HelionF, - LostF, - RavaF, - VeenaF, -} diff --git a/Glamourer/GameData/CustomizationManager.cs b/Glamourer/GameData/CustomizationManager.cs deleted file mode 100644 index f249bf6..0000000 --- a/Glamourer/GameData/CustomizationManager.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using Dalamud.Interface.Internal; -using Dalamud.Plugin.Services; -using Penumbra.GameData.Enums; - -namespace Glamourer.GameData; - -public class CustomizationManager : ICustomizationManager -{ - private static CustomizationOptions? _options; - - private CustomizationManager() - { } - - public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) - { - _options ??= new CustomizationOptions(textures, gameData, log, npcCustomizeSet); - return new CustomizationManager(); - } - - public IReadOnlyList Races - => CustomizationOptions.Races; - - public IReadOnlyList Clans - => CustomizationOptions.Clans; - - public IReadOnlyList Genders - => CustomizationOptions.Genders; - - public CustomizationSet GetList(SubRace clan, Gender gender) - => _options!.GetList(clan, gender); - - public IDalamudTextureWrap GetIcon(uint iconId) - => _options!.GetIcon(iconId); - - public string GetName(CustomName name) - => _options!.GetName(name); -} diff --git a/Glamourer/GameData/CustomizationNpcOptions.cs b/Glamourer/GameData/CustomizationNpcOptions.cs deleted file mode 100644 index 84509be..0000000 --- a/Glamourer/GameData/CustomizationNpcOptions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Penumbra.GameData.Enums; -using System.Collections.Generic; -using System.Linq; -using Penumbra.GameData.Structs; - -namespace Glamourer.GameData; - -public static class CustomizationNpcOptions -{ - public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, - NpcCustomizeSet npcCustomizeSet) - { - var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>(); - var customizeIndices = new[] - { - CustomizeIndex.Face, - CustomizeIndex.Hairstyle, - CustomizeIndex.LipColor, - CustomizeIndex.SkinColor, - CustomizeIndex.FacePaintColor, - CustomizeIndex.HighlightsColor, - CustomizeIndex.HairColor, - CustomizeIndex.FacePaint, - CustomizeIndex.TattooColor, - CustomizeIndex.EyeColorLeft, - CustomizeIndex.EyeColorRight, - }; - - foreach (var customize in npcCustomizeSet.Select(s => s.Customize)) - { - var set = sets[CustomizationOptions.ToIndex(customize.Clan, customize.Gender)]; - foreach (var customizeIndex in customizeIndices) - { - var value = customize[customizeIndex]; - if (value == CustomizeValue.Zero) - continue; - - if (set.DataByValue(customizeIndex, value, out _, customize.Face) >= 0) - continue; - - if (!dict.TryGetValue((set.Clan, set.Gender), out var npcSet)) - { - npcSet = [(customizeIndex, value)]; - dict.Add((set.Clan, set.Gender), npcSet); - } - else - { - npcSet.Add((customizeIndex, value)); - } - } - } - - return dict.ToDictionary(kvp => kvp.Key, - kvp => (IReadOnlyList<(CustomizeIndex, CustomizeValue)>)kvp.Value.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray()); - } -} diff --git a/Glamourer/GameData/CustomizationOptions.cs b/Glamourer/GameData/CustomizationOptions.cs deleted file mode 100644 index 6fc3b03..0000000 --- a/Glamourer/GameData/CustomizationOptions.cs +++ /dev/null @@ -1,530 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Dalamud; -using Dalamud.Interface.Internal; -using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; -using OtterGui.Classes; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Race = Penumbra.GameData.Enums.Race; - -namespace Glamourer.GameData; - -// Generate everything about customization per tribe and gender. -public partial class CustomizationOptions -{ - // All races except for Unknown - internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); - - // All tribes except for Unknown - internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray(); - - // Two genders. - internal static readonly Gender[] Genders = - { - Gender.Male, - Gender.Female, - }; - - // Every tribe and gender has a separate set of available customizations. - internal CustomizationSet GetList(SubRace race, Gender gender) - => _customizationSets[ToIndex(race, gender)]; - - // Get specific icons. - internal IDalamudTextureWrap GetIcon(uint id) - => _icons.LoadIcon(id)!; - - private readonly IconStorage _icons; - - private static readonly int ListSize = Clans.Length * Genders.Length; - private readonly CustomizationSet[] _customizationSets = new CustomizationSet[ListSize]; - - - // Get the index for the given pair of tribe and gender. - internal static int ToIndex(SubRace race, Gender gender) - { - var idx = ((int)race - 1) * Genders.Length + (gender == Gender.Female ? 1 : 0); - if (idx < 0 || idx >= ListSize) - ThrowException(race, gender); - return idx; - } - - private static void ThrowException(SubRace race, Gender gender) - => throw new Exception($"Invalid customization requested for {race} {gender}."); -} - -public partial class CustomizationOptions -{ - public string GetName(CustomName name) - => _names[(int)name]; - - internal CustomizationOptions(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) - { - var tmp = new TemporaryData(gameData, this, log); - _icons = new IconStorage(textures, gameData); - SetNames(gameData); - foreach (var race in Clans) - { - foreach (var gender in Genders) - _customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender); - } - - tmp.SetNpcData(_customizationSets, npcCustomizeSet); - } - - // Obtain localized names of customization options and race names from the game data. - private readonly string[] _names = new string[Enum.GetValues().Length]; - - private void SetNames(IDataManager gameData) - { - var subRace = gameData.GetExcelSheet()!; - - void Set(CustomName id, Lumina.Text.SeString? s, string def) - => _names[(int)id] = s?.ToDalamudString().TextValue ?? def; - - Set(CustomName.MidlanderM, subRace.GetRow((int)SubRace.Midlander)?.Masculine, SubRace.Midlander.ToName()); - Set(CustomName.MidlanderF, subRace.GetRow((int)SubRace.Midlander)?.Feminine, SubRace.Midlander.ToName()); - Set(CustomName.HighlanderM, subRace.GetRow((int)SubRace.Highlander)?.Masculine, SubRace.Highlander.ToName()); - Set(CustomName.HighlanderF, subRace.GetRow((int)SubRace.Highlander)?.Feminine, SubRace.Highlander.ToName()); - Set(CustomName.WildwoodM, subRace.GetRow((int)SubRace.Wildwood)?.Masculine, SubRace.Wildwood.ToName()); - Set(CustomName.WildwoodF, subRace.GetRow((int)SubRace.Wildwood)?.Feminine, SubRace.Wildwood.ToName()); - Set(CustomName.DuskwightM, subRace.GetRow((int)SubRace.Duskwight)?.Masculine, SubRace.Duskwight.ToName()); - Set(CustomName.DuskwightF, subRace.GetRow((int)SubRace.Duskwight)?.Feminine, SubRace.Duskwight.ToName()); - Set(CustomName.PlainsfolkM, subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine, SubRace.Plainsfolk.ToName()); - Set(CustomName.PlainsfolkF, subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine, SubRace.Plainsfolk.ToName()); - Set(CustomName.DunesfolkM, subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine, SubRace.Dunesfolk.ToName()); - Set(CustomName.DunesfolkF, subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine, SubRace.Dunesfolk.ToName()); - Set(CustomName.SeekerOfTheSunM, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine, SubRace.SeekerOfTheSun.ToName()); - Set(CustomName.SeekerOfTheSunF, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine, SubRace.SeekerOfTheSun.ToName()); - Set(CustomName.KeeperOfTheMoonM, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine, SubRace.KeeperOfTheMoon.ToName()); - Set(CustomName.KeeperOfTheMoonF, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine, SubRace.KeeperOfTheMoon.ToName()); - Set(CustomName.SeawolfM, subRace.GetRow((int)SubRace.Seawolf)?.Masculine, SubRace.Seawolf.ToName()); - Set(CustomName.SeawolfF, subRace.GetRow((int)SubRace.Seawolf)?.Feminine, SubRace.Seawolf.ToName()); - Set(CustomName.HellsguardM, subRace.GetRow((int)SubRace.Hellsguard)?.Masculine, SubRace.Hellsguard.ToName()); - Set(CustomName.HellsguardF, subRace.GetRow((int)SubRace.Hellsguard)?.Feminine, SubRace.Hellsguard.ToName()); - Set(CustomName.RaenM, subRace.GetRow((int)SubRace.Raen)?.Masculine, SubRace.Raen.ToName()); - Set(CustomName.RaenF, subRace.GetRow((int)SubRace.Raen)?.Feminine, SubRace.Raen.ToName()); - Set(CustomName.XaelaM, subRace.GetRow((int)SubRace.Xaela)?.Masculine, SubRace.Xaela.ToName()); - Set(CustomName.XaelaF, subRace.GetRow((int)SubRace.Xaela)?.Feminine, SubRace.Xaela.ToName()); - Set(CustomName.HelionM, subRace.GetRow((int)SubRace.Helion)?.Masculine, SubRace.Helion.ToName()); - Set(CustomName.HelionF, subRace.GetRow((int)SubRace.Helion)?.Feminine, SubRace.Helion.ToName()); - Set(CustomName.LostM, subRace.GetRow((int)SubRace.Lost)?.Masculine, SubRace.Lost.ToName()); - Set(CustomName.LostF, subRace.GetRow((int)SubRace.Lost)?.Feminine, SubRace.Lost.ToName()); - Set(CustomName.RavaM, subRace.GetRow((int)SubRace.Rava)?.Masculine, SubRace.Rava.ToName()); - Set(CustomName.RavaF, subRace.GetRow((int)SubRace.Rava)?.Feminine, SubRace.Rava.ToName()); - Set(CustomName.VeenaM, subRace.GetRow((int)SubRace.Veena)?.Masculine, SubRace.Veena.ToName()); - Set(CustomName.VeenaF, subRace.GetRow((int)SubRace.Veena)?.Feminine, SubRace.Veena.ToName()); - } - - private class TemporaryData - { - public CustomizationSet GetSet(SubRace race, Gender gender) - { - var (skin, hair) = GetColors(race, gender); - var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - var hrothgar = race.ToRace() == Race.Hrothgar; - // Create the initial set with all the easily accessible parameters available for anyone. - var set = new CustomizationSet(race, gender) - { - Voices = row.Voices, - HairStyles = GetHairStyles(race, gender), - HairColors = hair, - SkinColors = skin, - EyeColors = _eyeColorPicker, - HighlightColors = _highlightPicker, - TattooColors = _tattooColorPicker, - LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark, - LipColorsLight = hrothgar ? Array.Empty() : _lipColorPickerLight, - FacePaintColorsDark = _facePaintColorPickerDark, - FacePaintColorsLight = _facePaintColorPickerLight, - Faces = GetFaces(row), - 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), - }; - - SetAvailability(set, row); - SetFacialFeatures(set, row); - SetHairByFace(set); - SetMenuTypes(set, row); - SetNames(set, row); - - return set; - } - - public void SetNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) - { - var data = CustomizationNpcOptions.CreateNpcData(sets, npcCustomizeSet); - foreach (var set in sets) - { - if (data.TryGetValue((set.Clan, set.Gender), out var npcData)) - set.NpcOptions = npcData.ToArray(); - } - } - - - public TemporaryData(IDataManager gameData, CustomizationOptions options, IPluginLog log) - { - _options = options; - _cmpFile = new ColorParameters(gameData, log); - _customizeSheet = gameData.GetExcelSheet(ClientLanguage.English)!; - Lobby = gameData.GetExcelSheet(ClientLanguage.English)!; - var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? - .MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[] - { - "charamaketype", - gameData.Language.ToLumina(), - null, - }) as ExcelSheet; - _listSheet = tmp!; - _hairSheet = gameData.GetExcelSheet()!; - _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. - private readonly ExcelSheet _customizeSheet; - private readonly ExcelSheet _listSheet; - private readonly ExcelSheet _hairSheet; - public readonly ExcelSheet Lobby; - private readonly ColorParameters _cmpFile; - - // Those values are shared between all races. - 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 CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false) - { - var ret = new CustomizeData[num]; - var idx = 0; - foreach (var value in _cmpFile.GetSlice(offset, num)) - { - ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx)); - ++idx; - } - - return ret; - } - - - private void SetHairByFace(CustomizationSet set) - { - if (set.Race != Race.Hrothgar) - { - set.HairByFace = Enumerable.Repeat(set.HairStyles, set.Faces.Count + 1).ToArray(); - return; - } - - var tmp = new IReadOnlyList[set.Faces.Count + 1]; - tmp[0] = set.HairStyles; - - for (var i = 1; i <= set.Faces.Count; ++i) - { - bool Valid(CustomizeData c) - { - var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0; - return data == 0 || data == i + set.Faces.Count; - } - - tmp[i] = set.HairStyles.Where(Valid).ToArray(); - } - - set.HairByFace = tmp; - } - - private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row) - { - // Set up the menu types for all customizations. - 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 CustomizeIndex.HighlightsColor: - case CustomizeIndex.EyeColorLeft: - case CustomizeIndex.EyeColorRight: - case CustomizeIndex.FacePaintColor: - 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.Customize == gameId); - var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector; - if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector) - ret = CharaMakeParams.MenuType.List1Selector; - return ret; - }).ToArray(); - set.Order = CustomizationSet.ComputeOrder(set); - } - - // Set customizations available if they have any options. - private static void SetAvailability(CustomizationSet set, CharaMakeParams row) - { - if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) - return; - - 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); - return; - - void Set(bool available, CustomizeIndex flag) - { - if (available) - set.SetAvailable(flag); - } - } - - // 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; - set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count); - - 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 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.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]; - return; - - static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) - => (new CustomizeData(i, CustomizeValue.Zero, data), new CustomizeData(i, CustomizeValue.Max, data, 1)); - } - - // Set the names for the given set of parameters. - private void SetNames(CustomizationSet set, CharaMakeParams row) - { - 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.Customize == byteId); - if (menu == null) - { - // If none exists and the id corresponds to highlights, set the Highlights name. - if (c == CustomizeIndex.Highlights) - return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; - - // Otherwise there is an error and we use the default name. - return c.ToDefaultName(); - } - - // Otherwise all is normal, get the menu name or if it does not work the default name. - var textRow = Lobby.GetRow(menu.Value.Id); - return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); - }).ToArray(); - - // Add names for both eye colors. - nameArray[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorLeft.ToDefaultName(); - nameArray[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.EyeColorRight.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature1] = CustomizeIndex.FacialFeature1.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature2] = CustomizeIndex.FacialFeature2.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature3] = CustomizeIndex.FacialFeature3.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature4] = CustomizeIndex.FacialFeature4.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature5] = CustomizeIndex.FacialFeature5.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature6] = CustomizeIndex.FacialFeature6.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature7] = CustomizeIndex.FacialFeature7.ToDefaultName(); - nameArray[(int)CustomizeIndex.LegacyTattoo] = CustomizeIndex.LegacyTattoo.ToDefaultName(); - nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName(); - nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName(); - set.OptionName = nameArray; - } - - // Obtain available skin and hair colors for the given subrace and gender. - private (CustomizeData[], CustomizeData[]) GetColors(SubRace race, Gender gender) - { - if (race is > SubRace.Veena or SubRace.Unknown) - throw new ArgumentOutOfRangeException(nameof(race), race, null); - - var gv = gender == Gender.Male ? 0 : 1; - var idx = ((int)race * 2 + gv) * 5 + 3; - - return (CreateColorPicker(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 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); - // Hairstyles can be found starting at Unknown66. - for (var i = 0; i < row.Unknown30; ++i) - { - var name = $"Unknown{66 + i * 9}"; - var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; - if (customizeIdx == uint.MaxValue) - continue; - - // Hair Row from CustomizeSheet might not be set in case of unlockable hair. - var hairRow = _customizeSheet.GetRow(customizeIdx); - if (hairRow == null) - hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); - else if (_options._icons.IconExists(hairRow.Icon)) - hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, - (ushort)hairRow.RowId)); - } - - return hairList.OrderBy(h => h.Value.Value).ToArray(); - } - - // Get Features. - private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index) - { - var row = _customizeSheet.GetRow(value); - return row == null - ? 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, CustomizeIndex index) - { - 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 CustomizeData[] GetFacePaints(SubRace race, Gender gender) - { - var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - var paintList = new List(row.Unknown37); - // Number of available face paints is at Unknown37. - for (var i = 0; i < row.Unknown37; ++i) - { - // Face paints start at Unknown73. - var name = $"Unknown{73 + i * 9}"; - var customizeIdx = - (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; - if (customizeIdx == uint.MaxValue) - continue; - - var paintRow = _customizeSheet.GetRow(customizeIdx); - // Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints. - if (paintRow != null) - paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon, - (ushort)paintRow.RowId)); - else - paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx)); - } - - return paintList.OrderBy(p => p.Value.Value).ToArray(); - } - - // Specific icons for tails or ears. - 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 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 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/CustomizeData.cs b/Glamourer/GameData/CustomizeData.cs index 3a3e89c..4d4e2fd 100644 --- a/Glamourer/GameData/CustomizeData.cs +++ b/Glamourer/GameData/CustomizeData.cs @@ -5,26 +5,34 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; -// 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. +/// +/// 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 CustomizeData : IEquatable { + /// The index of the option this value is for. [FieldOffset(0)] public readonly CustomizeIndex Index; + /// The value for the option. [FieldOffset(1)] public readonly CustomizeValue Value; + /// The internal ID for sheets. [FieldOffset(2)] public readonly ushort CustomizeId; + /// An ID for an associated icon. [FieldOffset(4)] public readonly uint IconId; + /// An ID for an associated color. [FieldOffset(4)] public readonly uint Color; + /// Construct a CustomizeData from single data values. public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0) { Index = index; @@ -34,14 +42,23 @@ public readonly struct CustomizeData : IEquatable CustomizeId = customizeId; } + /// public bool Equals(CustomizeData other) => Index == other.Index && Value.Value == other.Value.Value && CustomizeId == other.CustomizeId; + /// public override bool Equals(object? obj) => obj is CustomizeData other && Equals(other); + /// public override int GetHashCode() => HashCode.Combine((int)Index, Value.Value, CustomizeId); + + public static bool operator ==(CustomizeData left, CustomizeData right) + => left.Equals(right); + + public static bool operator !=(CustomizeData left, CustomizeData right) + => !(left == right); } diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs new file mode 100644 index 0000000..fabc483 --- /dev/null +++ b/Glamourer/GameData/CustomizeManager.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Dalamud.Interface.Internal; +using Dalamud.Plugin.Services; +using OtterGui.Classes; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer.GameData; + +/// Generate everything about customization per tribe and gender. +public class CustomizeManager : IAsyncService +{ + /// All races except for Unknown + public static readonly IReadOnlyList Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); + + /// All tribes except for Unknown + public static readonly IReadOnlyList Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray(); + + /// Two genders. + public static readonly IReadOnlyList Genders = + [ + Gender.Male, + Gender.Female, + ]; + + /// Every tribe and gender has a separate set of available customizations. + public CustomizeSet GetSet(SubRace race, Gender gender) + { + if (!Awaiter.IsCompletedSuccessfully) + Awaiter.Wait(); + return _customizationSets[ToIndex(race, gender)]; + } + + /// Get specific icons. + public IDalamudTextureWrap GetIcon(uint id) + => _icons.LoadIcon(id)!; + + /// Iterate over all supported genders and clans. + public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets() + { + foreach (var clan in Clans) + { + yield return (clan, Gender.Male); + yield return (clan, Gender.Female); + } + } + + public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) + { + _icons = new IconStorage(textures, gameData); + var tmpTask = Task.Run(() => new CustomizeSetFactory(gameData, log, _icons, npcCustomizeSet)); + var setTasks = AllSets().Select(p + => tmpTask.ContinueWith(t => _customizationSets[ToIndex(p.Clan, p.Gender)] = t.Result.CreateSet(p.Clan, p.Gender))); + Awaiter = Task.WhenAll(setTasks); + } + + /// + public Task Awaiter { get; } + + private readonly IconStorage _icons; + private static readonly int ListSize = Clans.Count * Genders.Count; + private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; + + /// Get the index for the given pair of tribe and gender. + private static int ToIndex(SubRace race, Gender gender) + { + var idx = ((int)race - 1) * Genders.Count + (gender == Gender.Female ? 1 : 0); + if (idx < 0 || idx >= ListSize) + throw new Exception($"Invalid customization requested for {race} {gender}."); + + return idx; + } +} \ No newline at end of file diff --git a/Glamourer/GameData/CustomizationSet.cs b/Glamourer/GameData/CustomizeSet.cs similarity index 94% rename from Glamourer/GameData/CustomizationSet.cs rename to Glamourer/GameData/CustomizeSet.cs index 2a79c66..d2dc8e9 100644 --- a/Glamourer/GameData/CustomizationSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -8,11 +8,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; -// Each Subrace and Gender combo has a customization set. -// This describes the available customizations, their types and their names. -public class CustomizationSet +/// +/// Each SubRace and Gender combo has a customization set. +/// This describes the available customizations, their types and their names. +/// +public class CustomizeSet { - internal CustomizationSet(SubRace clan, Gender gender) + internal CustomizeSet(SubRace clan, Gender gender) { Gender = gender; Clan = clan; @@ -24,6 +26,8 @@ public class CustomizationSet public SubRace Clan { get; } public Race Race { get; } + public string Name { get; internal init; } = string.Empty; + public CustomizeFlag SettingAvailable { get; internal set; } internal void SetAvailable(CustomizeIndex index) @@ -33,7 +37,7 @@ public class CustomizationSet => SettingAvailable.HasFlag(index.ToFlag()); // Meta - public IReadOnlyList OptionName { get; internal set; } = null!; + public IReadOnlyList OptionName { get; internal init; } = null!; public string Option(CustomizeIndex index) => OptionName[(int)index]; @@ -95,68 +99,6 @@ public class CustomizationSet { var type = Types[(int)index]; - int GetInteger0(out CustomizeData? custom) - { - if (value < Count(index)) - { - custom = new CustomizeData(index, value, 0, value.Value); - return value.Value; - } - - custom = null; - return -1; - } - - int GetInteger1(out CustomizeData? custom) - { - if (value > 0 && value < Count(index) + 1) - { - custom = new CustomizeData(index, value, 0, (ushort)(value.Value - 1)); - return value.Value; - } - - custom = null; - return -1; - } - - static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom) - { - 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 type switch { CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom), @@ -194,6 +136,68 @@ public class CustomizationSet CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom), _ => Invalid(out custom), }; + + int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) + { + var (val, idx) = list.Cast().WithIndex().FirstOrDefault(p => p.Value!.Value.Value == v); + if (val == null) + { + output = null; + return -1; + } + + output = val; + return idx; + } + + static int Invalid(out CustomizeData? custom) + { + custom = null; + return -1; + } + + static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom) + { + if (value == CustomizeValue.Zero) + { + custom = new CustomizeData(index, CustomizeValue.Zero); + 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; + } + + int GetInteger1(out CustomizeData? custom) + { + if (value > 0 && value < Count(index) + 1) + { + custom = new CustomizeData(index, value, 0, (ushort)(value.Value - 1)); + return value.Value; + } + + custom = null; + return -1; + } + + int GetInteger0(out CustomizeData? custom) + { + if (value < Count(index)) + { + custom = new CustomizeData(index, value, 0, value.Value); + return value.Value; + } + + custom = null; + return -1; + } } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -244,7 +248,7 @@ public class CustomizationSet public CharaMakeParams.MenuType Type(CustomizeIndex index) => Types[(int)index]; - internal static IReadOnlyDictionary ComputeOrder(CustomizationSet set) + internal static IReadOnlyDictionary ComputeOrder(CustomizeSet set) { var ret = Enum.GetValues().ToArray(); ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft; @@ -305,6 +309,6 @@ public class CustomizationSet public static class CustomizationSetExtensions { /// Return only the available customizations in this set and Clan or Gender. - public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set) + public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizeSet set) => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); } diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs new file mode 100644 index 0000000..5eaaa58 --- /dev/null +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Dalamud; +using Dalamud.Plugin.Services; +using Dalamud.Utility; +using Lumina.Excel; +using Lumina.Excel.GeneratedSheets; +using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer.GameData; + +internal class CustomizeSetFactory( + IDataManager _gameData, + IPluginLog _log, + IconStorage _icons, + NpcCustomizeSet _npcCustomizeSet, + ColorParameters _colors) +{ + public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet) + : this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log)) + { } + + /// Create the set of all available customization options for a given clan and gender. + public CustomizeSet CreateSet(SubRace race, Gender gender) + { + var (skin, hair) = GetSkinHairColors(race, gender); + var row = _charaMakeSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var hrothgar = race.ToRace() == Race.Hrothgar; + // Create the initial set with all the easily accessible parameters available for anyone. + var set = new CustomizeSet(race, gender) + { + Name = GetName(race, gender), + Voices = row.Voices, + HairStyles = GetHairStyles(race, gender), + HairColors = hair, + SkinColors = skin, + EyeColors = _eyeColorPicker, + HighlightColors = _highlightPicker, + TattooColors = _tattooColorPicker, + LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark, + LipColorsLight = hrothgar ? [] : _lipColorPickerLight, + FacePaintColorsDark = _facePaintColorPickerDark, + FacePaintColorsLight = _facePaintColorPickerLight, + Faces = GetFaces(row), + 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), + OptionName = GetOptionNames(row), + Types = GetMenuTypes(row), + }; + SetPostProcessing(set, row); + return set; + } + + /// Some data can not be set independently of the rest, so we need a post-processing step to finalize. + private void SetPostProcessing(CustomizeSet set, CharaMakeParams row) + { + SetAvailability(set, row); + SetFacialFeatures(set, row); + SetHairByFace(set); + SetNpcData(set, set.Clan, set.Gender); + } + + /// Given a customize set with filled data, find all customizations used by valid NPCs that are not regularly available. + private void SetNpcData(CustomizeSet set, SubRace race, Gender gender) + { + var customizeIndices = new[] + { + CustomizeIndex.Face, + CustomizeIndex.Hairstyle, + CustomizeIndex.LipColor, + CustomizeIndex.SkinColor, + CustomizeIndex.FacePaintColor, + CustomizeIndex.HighlightsColor, + CustomizeIndex.HairColor, + CustomizeIndex.FacePaint, + CustomizeIndex.TattooColor, + CustomizeIndex.EyeColorLeft, + CustomizeIndex.EyeColorRight, + }; + + var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); + _npcCustomizeSet.Awaiter.Wait(); + foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender)) + { + foreach (var customizeIndex in customizeIndices) + { + var value = customize[customizeIndex]; + if (value == CustomizeValue.Zero) + continue; + + if (set.DataByValue(customizeIndex, value, out _, customize.Face) >= 0) + continue; + + npcCustomizations.Add((customizeIndex, value)); + } + } + + set.NpcOptions = npcCustomizations.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray(); + } + + private readonly ColorParameters _colorParameters = new(_gameData, _log); + private readonly ExcelSheet _customizeSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + private readonly ExcelSheet _lobbySheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + private readonly ExcelSheet _hairSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + private readonly ExcelSheet _tribeSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + + // Those color pickers are shared between all races. + private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192); + private readonly CustomizeData[] _lipColorPickerDark = CreateColors(_colors, CustomizeIndex.LipColor, 512, 96); + private readonly CustomizeData[] _lipColorPickerLight = CreateColors(_colors, CustomizeIndex.LipColor, 1024, 96, true); + private readonly CustomizeData[] _eyeColorPicker = CreateColors(_colors, CustomizeIndex.EyeColorLeft, 0, 192); + private readonly CustomizeData[] _facePaintColorPickerDark = CreateColors(_colors, CustomizeIndex.FacePaintColor, 640, 96); + private readonly CustomizeData[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true); + private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192); + + private readonly ExcelSheet _charaMakeSheet = _gameData.Excel + .GetType() + .GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? + .MakeGenericMethod(typeof(CharaMakeParams)) + .Invoke(_gameData.Excel, ["charamaketype", _gameData.Language.ToLumina(), null])! as ExcelSheet + ?? null!; + + /// Obtain available skin and hair colors for the given clan and gender. + private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender) + { + if (race is > SubRace.Veena or SubRace.Unknown) + throw new ArgumentOutOfRangeException(nameof(race), race, null); + + var gv = gender == Gender.Male ? 0 : 1; + var idx = ((int)race * 2 + gv) * 5 + 3; + + return (CreateColors(_colorParameters, CustomizeIndex.SkinColor, idx << 8, 192), + CreateColors(_colorParameters, CustomizeIndex.HairColor, (idx + 1) << 8, 192)); + } + + /// Obtain the gender-specific clan name. + private string GetName(SubRace race, Gender gender) + => gender switch + { + Gender.Male => _tribeSheet.GetRow((uint)race)?.Masculine.ToDalamudString().TextValue ?? race.ToName(), + Gender.Female => _tribeSheet.GetRow((uint)race)?.Feminine.ToDalamudString().TextValue ?? race.ToName(), + _ => "Unknown", + }; + + /// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and 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); + // Hairstyles can be found starting at Unknown66. + for (var i = 0; i < row.Unknown30; ++i) + { + var name = $"Unknown{66 + i * 9}"; + var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) + ?? uint.MaxValue; + if (customizeIdx == uint.MaxValue) + continue; + + // Hair Row from CustomizeSheet might not be set in case of unlockable hair. + var hairRow = _customizeSheet.GetRow(customizeIdx); + if (hairRow == null) + hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); + else if (_icons.IconExists(hairRow.Icon)) + hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, + (ushort)hairRow.RowId)); + } + + return [.. hairList.OrderBy(h => h.Value.Value)]; + } + + /// Specific icons for tails or ears. + 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() + ?? []; + + /// Specific icons for faces. + 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() + ?? []; + + /// Specific icons for Hrothgar patterns. + 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() + ?? []; + + /// Get face paints from the hair sheet via reflection since there are also unlockable face paints. + private CustomizeData[] GetFacePaints(SubRace race, Gender gender) + { + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var paintList = new List(row.Unknown37); + // Number of available face paints is at Unknown37. + for (var i = 0; i < row.Unknown37; ++i) + { + // Face paints start at Unknown73. + var name = $"Unknown{73 + i * 9}"; + var customizeIdx = + (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) + ?? uint.MaxValue; + if (customizeIdx == uint.MaxValue) + continue; + + var paintRow = _customizeSheet.GetRow(customizeIdx); + // Face paint Row from CustomizeSheet might not be set in case of unlockable face paints. + if (paintRow != null) + paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon, + (ushort)paintRow.RowId)); + else + paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx)); + } + + return [.. paintList.OrderBy(p => p.Value.Value)]; + } + + /// Get List sizes. + private static int GetListSize(CharaMakeParams row, CustomizeIndex index) + { + var gameId = index.ToByteAndMask().ByteIdx; + var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == gameId); + return menu?.Size ?? 0; + } + + /// Get generic Features. + private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index) + { + var row = _customizeSheet.GetRow(value); + return row == null + ? new CustomizeData(id, (CustomizeValue)(index + 1), value) + : new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId); + } + + /// Create generic color sets from the parameters. + private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num, + bool light = false) + { + var ret = new CustomizeData[num]; + var idx = 0; + foreach (var value in colorParameters.GetSlice(offset, num)) + { + ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx)); + ++idx; + } + + return ret; + } + + /// Set the specific option names for the given set of parameters. + private string[] GetOptionNames(CharaMakeParams row) + { + 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.Customize == byteId); + if (menu == null) + { + // If none exists and the id corresponds to highlights, set the Highlights name. + if (c == CustomizeIndex.Highlights) + return _lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; + + // Otherwise there is an error and we use the default name. + return c.ToDefaultName(); + } + + // Otherwise all is normal, get the menu name or if it does not work the default name. + var textRow = _lobbySheet.GetRow(menu.Value.Id); + return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); + }).ToArray(); + + // Add names for both eye colors. + nameArray[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorLeft.ToDefaultName(); + nameArray[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.EyeColorRight.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature1] = CustomizeIndex.FacialFeature1.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature2] = CustomizeIndex.FacialFeature2.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature3] = CustomizeIndex.FacialFeature3.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature4] = CustomizeIndex.FacialFeature4.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature5] = CustomizeIndex.FacialFeature5.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature6] = CustomizeIndex.FacialFeature6.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature7] = CustomizeIndex.FacialFeature7.ToDefaultName(); + nameArray[(int)CustomizeIndex.LegacyTattoo] = CustomizeIndex.LegacyTattoo.ToDefaultName(); + nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName(); + nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName(); + return nameArray; + } + + /// Get the manu types for all available options. + private CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row) + { + // Set up the menu types for all customizations. + return Enum.GetValues().Select(c => + { + // Those types are not correctly given in the menu, so special case them to color pickers. + switch (c) + { + case CustomizeIndex.HighlightsColor: + case CustomizeIndex.EyeColorLeft: + case CustomizeIndex.EyeColorRight: + case CustomizeIndex.FacePaintColor: + 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.Customize == gameId); + var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector; + if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector) + ret = CharaMakeParams.MenuType.List1Selector; + return ret; + }).ToArray(); + } + + /// Set the availability of options according to actual availability. + private static void SetAvailability(CustomizeSet set, CharaMakeParams row) + { + // TODO: Hrothgar female + if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) + return; + + 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); + return; + + void Set(bool available, CustomizeIndex flag) + { + if (available) + set.SetAvailable(flag); + } + } + + /// Set hairstyles per face for Hrothgar and make it simple for non-Hrothgar. + private void SetHairByFace(CustomizeSet set) + { + if (set.Race != Race.Hrothgar) + { + set.HairByFace = Enumerable.Repeat(set.HairStyles, set.Faces.Count + 1).ToArray(); + return; + } + + var tmp = new IReadOnlyList[set.Faces.Count + 1]; + tmp[0] = set.HairStyles; + + for (var i = 1; i <= set.Faces.Count; ++i) + { + tmp[i] = set.HairStyles.Where(Valid).ToArray(); + continue; + + bool Valid(CustomizeData c) + { + var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0; + return data == 0 || data == i + set.Faces.Count; + } + } + + set.HairByFace = tmp; + } + + /// + /// Create a list of lists of facial features and the legacy tattoo. + /// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use. + /// + private static void SetFacialFeatures(CustomizeSet set, CharaMakeParams row) + { + var count = set.Faces.Count; + set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count); + 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 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.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]; + return; + + static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) + => (new CustomizeData(i, CustomizeValue.Zero, data), new CustomizeData(i, CustomizeValue.Max, data, 1)); + } +} diff --git a/Glamourer/GameData/ICustomizationManager.cs b/Glamourer/GameData/ICustomizationManager.cs deleted file mode 100644 index 2d884cd..0000000 --- a/Glamourer/GameData/ICustomizationManager.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using Dalamud.Interface.Internal; -using Penumbra.GameData.Enums; - -namespace Glamourer.GameData; - -public interface ICustomizationManager -{ - public IReadOnlyList Races { get; } - public IReadOnlyList Clans { get; } - public IReadOnlyList Genders { get; } - - public CustomizationSet GetList(SubRace race, Gender gender); - - public IDalamudTextureWrap GetIcon(uint iconId); - public string GetName(CustomName name); -} diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index fd261c6..a99448e 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -13,20 +13,30 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; +/// Contains a set of all human NPC appearances with their names. public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { + /// public string Name => nameof(NpcCustomizeSet); - private readonly List _data = []; + /// + public long Time { get; private set; } - public long Time { get; private set; } + /// public long Memory { get; private set; } + + /// public int TotalCount => _data.Count; + /// public Task Awaiter { get; } + /// The list of data. + private readonly List _data = []; + + /// Create the data when ready. public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames) { var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter); @@ -40,17 +50,21 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList }); } + /// Create data from event NPCs. private static List CreateEnpcData(IDataManager data, DictENpc eNpcs) { var enpcSheet = data.GetExcelSheet()!; var list = new List(eNpcs.Count); + // Go through all event NPCs already collected into a dictionary. foreach (var (id, name) in eNpcs) { var row = enpcSheet.GetRow(id.Id); + // We only accept NPCs with valid names. if (row == null || name.IsNullOrWhitespace()) continue; + // Check if the customization is a valid human. var (valid, customize) = FromEnpcBase(row); if (!valid) continue; @@ -63,6 +77,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList Kind = ObjectKind.EventNpc, }; + // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. + // Prefer the NpcEquip reference if it is set, otherwise use the own. if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) { ApplyNpcEquip(ref ret, equip); @@ -90,19 +106,25 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList return list; } + /// Create data from battle NPCs. private static List CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames) { var bnpcSheet = data.GetExcelSheet()!; var list = new List((int)bnpcSheet.RowCount); + + // We go through all battle NPCs in the sheet because the dictionary refers to names. foreach (var baseRow in bnpcSheet) { + // Only accept humans. if (baseRow.ModelChara.Value!.Type != 1) continue; var bnpcNameIds = bNpcNames[baseRow.RowId]; + // Only accept battle NPCs with known associated names. if (bnpcNameIds.Count == 0) continue; + // Check if the customization is a valid human. var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); if (!valid) continue; @@ -115,6 +137,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList Kind = ObjectKind.BattleNpc, }; ApplyNpcEquip(ref ret, equip); + // Add the appearance for each associated name. foreach (var bnpcNameId in bnpcNameIds) { if (bNpcs.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace()) @@ -125,13 +148,18 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList return list; } - private void FilterAndOrderNpcData(List eNpcEquip, List bNpcEquip) + /// Given the battle NPC and event NPC lists, order and deduplicate entries. + private void FilterAndOrderNpcData(IReadOnlyCollection eNpcEquip, IReadOnlyCollection bNpcEquip) { _data.Clear(); + // This is a maximum since we deduplicate. _data.EnsureCapacity(eNpcEquip.Count + bNpcEquip.Count); + // Convert the NPCs to a dictionary of lists grouped by name. var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); + // Iterate through the sorted list. foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) { + // Remove any duplicate entries for a name with identical data. for (var i = 0; i < duplicates.Count; ++i) { var current = duplicates[i]; @@ -145,6 +173,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } } + // If there is only a single entry, add that. This does not take additional string memory through interning. if (duplicates.Count == 1) { _data.Add(duplicates[0]); @@ -152,24 +181,29 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } else { + // Add all distinct duplicates with their ID specified in the name. _data.AddRange(duplicates .Select(duplicate => duplicate with { - Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" + Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})", })); Memory += 96 * duplicates.Count + duplicates.Sum(d => d.Name.Length * 2); } } + // Sort non-alphanumeric entries at the end instead of the beginning. var lastWeird = _data.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); if (lastWeird != -1) { _data.AddRange(_data.Take(lastWeird)); _data.RemoveRange(0, lastWeird); } + + // Reduce memory footprint. _data.TrimExcess(); } + /// Apply equipment from a NpcEquip row. private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) { data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); @@ -187,96 +221,102 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList data.VisorToggled = row.Visor; } + /// Obtain customizations from a BNpcCustomize row and check if the human is valid. private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize) { var customize = new CustomizeArray(); - customize.SetByIndex(0, (CustomizeValue) (byte)bnpcCustomize.Race.Row); - customize.SetByIndex(1, (CustomizeValue) bnpcCustomize.Gender); - customize.SetByIndex(2, (CustomizeValue) bnpcCustomize.BodyType); - customize.SetByIndex(3, (CustomizeValue) bnpcCustomize.Height); - customize.SetByIndex(4, (CustomizeValue) (byte)bnpcCustomize.Tribe.Row); - customize.SetByIndex(5, (CustomizeValue) bnpcCustomize.Face); - customize.SetByIndex(6, (CustomizeValue) bnpcCustomize.HairStyle); - customize.SetByIndex(7, (CustomizeValue) bnpcCustomize.HairHighlight); - customize.SetByIndex(8, (CustomizeValue) bnpcCustomize.SkinColor); - customize.SetByIndex(9, (CustomizeValue) bnpcCustomize.EyeHeterochromia); - customize.SetByIndex(10, (CustomizeValue) bnpcCustomize.HairColor); - customize.SetByIndex(11, (CustomizeValue) bnpcCustomize.HairHighlightColor); - customize.SetByIndex(12, (CustomizeValue) bnpcCustomize.FacialFeature); - customize.SetByIndex(13, (CustomizeValue) bnpcCustomize.FacialFeatureColor); - customize.SetByIndex(14, (CustomizeValue) bnpcCustomize.Eyebrows); - customize.SetByIndex(15, (CustomizeValue) bnpcCustomize.EyeColor); - customize.SetByIndex(16, (CustomizeValue) bnpcCustomize.EyeShape); - customize.SetByIndex(17, (CustomizeValue) bnpcCustomize.Nose); - customize.SetByIndex(18, (CustomizeValue) bnpcCustomize.Jaw); - customize.SetByIndex(19, (CustomizeValue) bnpcCustomize.Mouth); - customize.SetByIndex(20, (CustomizeValue) bnpcCustomize.LipColor); - customize.SetByIndex(21, (CustomizeValue) bnpcCustomize.BustOrTone1); - customize.SetByIndex(22, (CustomizeValue) bnpcCustomize.ExtraFeature1); - customize.SetByIndex(23, (CustomizeValue) bnpcCustomize.ExtraFeature2OrBust); - customize.SetByIndex(24, (CustomizeValue) bnpcCustomize.FacePaint); - customize.SetByIndex(25, (CustomizeValue) bnpcCustomize.FacePaintColor); + customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.Row); + customize.SetByIndex(1, (CustomizeValue)bnpcCustomize.Gender); + customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType); + customize.SetByIndex(3, (CustomizeValue)bnpcCustomize.Height); + customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue)bnpcCustomize.Face); + customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle); + customize.SetByIndex(7, (CustomizeValue)bnpcCustomize.HairHighlight); + customize.SetByIndex(8, (CustomizeValue)bnpcCustomize.SkinColor); + customize.SetByIndex(9, (CustomizeValue)bnpcCustomize.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue)bnpcCustomize.HairColor); + customize.SetByIndex(11, (CustomizeValue)bnpcCustomize.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue)bnpcCustomize.FacialFeature); + customize.SetByIndex(13, (CustomizeValue)bnpcCustomize.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue)bnpcCustomize.Eyebrows); + customize.SetByIndex(15, (CustomizeValue)bnpcCustomize.EyeColor); + customize.SetByIndex(16, (CustomizeValue)bnpcCustomize.EyeShape); + customize.SetByIndex(17, (CustomizeValue)bnpcCustomize.Nose); + customize.SetByIndex(18, (CustomizeValue)bnpcCustomize.Jaw); + customize.SetByIndex(19, (CustomizeValue)bnpcCustomize.Mouth); + customize.SetByIndex(20, (CustomizeValue)bnpcCustomize.LipColor); + customize.SetByIndex(21, (CustomizeValue)bnpcCustomize.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue)bnpcCustomize.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue)bnpcCustomize.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue)bnpcCustomize.FacePaint); + customize.SetByIndex(25, (CustomizeValue)bnpcCustomize.FacePaintColor); if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) + || !CustomizeManager.Races.Contains(customize.Race) + || !CustomizeManager.Clans.Contains(customize.Clan) + || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); return (true, customize); } + /// Obtain customizations from a ENpcBase row and check if the human is valid. private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase) { if (enpcBase.ModelChara.Value?.Type != 1) return (false, CustomizeArray.Default); var customize = new CustomizeArray(); - customize.SetByIndex(0, (CustomizeValue) (byte)enpcBase.Race.Row); - customize.SetByIndex(1, (CustomizeValue) enpcBase.Gender); - customize.SetByIndex(2, (CustomizeValue) enpcBase.BodyType); - customize.SetByIndex(3, (CustomizeValue) enpcBase.Height); - customize.SetByIndex(4, (CustomizeValue) (byte)enpcBase.Tribe.Row); - customize.SetByIndex(5, (CustomizeValue) enpcBase.Face); - customize.SetByIndex(6, (CustomizeValue) enpcBase.HairStyle); - customize.SetByIndex(7, (CustomizeValue) enpcBase.HairHighlight); - customize.SetByIndex(8, (CustomizeValue) enpcBase.SkinColor); - customize.SetByIndex(9, (CustomizeValue) enpcBase.EyeHeterochromia); - customize.SetByIndex(10, (CustomizeValue) enpcBase.HairColor); - customize.SetByIndex(11, (CustomizeValue) enpcBase.HairHighlightColor); - customize.SetByIndex(12, (CustomizeValue) enpcBase.FacialFeature); - customize.SetByIndex(13, (CustomizeValue) enpcBase.FacialFeatureColor); - customize.SetByIndex(14, (CustomizeValue) enpcBase.Eyebrows); - customize.SetByIndex(15, (CustomizeValue) enpcBase.EyeColor); - customize.SetByIndex(16, (CustomizeValue) enpcBase.EyeShape); - customize.SetByIndex(17, (CustomizeValue) enpcBase.Nose); - customize.SetByIndex(18, (CustomizeValue) enpcBase.Jaw); - customize.SetByIndex(19, (CustomizeValue) enpcBase.Mouth); - customize.SetByIndex(20, (CustomizeValue) enpcBase.LipColor); - customize.SetByIndex(21, (CustomizeValue) enpcBase.BustOrTone1); - customize.SetByIndex(22, (CustomizeValue) enpcBase.ExtraFeature1); - customize.SetByIndex(23, (CustomizeValue) enpcBase.ExtraFeature2OrBust); - customize.SetByIndex(24, (CustomizeValue) enpcBase.FacePaint); - customize.SetByIndex(25, (CustomizeValue) enpcBase.FacePaintColor); + customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.Row); + customize.SetByIndex(1, (CustomizeValue)enpcBase.Gender); + customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType); + customize.SetByIndex(3, (CustomizeValue)enpcBase.Height); + customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue)enpcBase.Face); + customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle); + customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight); + customize.SetByIndex(8, (CustomizeValue)enpcBase.SkinColor); + customize.SetByIndex(9, (CustomizeValue)enpcBase.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue)enpcBase.HairColor); + customize.SetByIndex(11, (CustomizeValue)enpcBase.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue)enpcBase.FacialFeature); + customize.SetByIndex(13, (CustomizeValue)enpcBase.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue)enpcBase.Eyebrows); + customize.SetByIndex(15, (CustomizeValue)enpcBase.EyeColor); + customize.SetByIndex(16, (CustomizeValue)enpcBase.EyeShape); + customize.SetByIndex(17, (CustomizeValue)enpcBase.Nose); + customize.SetByIndex(18, (CustomizeValue)enpcBase.Jaw); + customize.SetByIndex(19, (CustomizeValue)enpcBase.Mouth); + customize.SetByIndex(20, (CustomizeValue)enpcBase.LipColor); + customize.SetByIndex(21, (CustomizeValue)enpcBase.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue)enpcBase.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue)enpcBase.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue)enpcBase.FacePaint); + customize.SetByIndex(25, (CustomizeValue)enpcBase.FacePaintColor); if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) + || !CustomizeManager.Races.Contains(customize.Race) + || !CustomizeManager.Clans.Contains(customize.Clan) + || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); return (true, customize); } + /// public IEnumerator GetEnumerator() => _data.GetEnumerator(); + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// public int Count => _data.Count; + /// public NpcData this[int index] => _data[index]; } diff --git a/Glamourer/GameData/NpcData.cs b/Glamourer/GameData/NpcData.cs index 70bfe58..2db8fb5 100644 --- a/Glamourer/GameData/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -5,17 +5,34 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; +/// A struct containing everything to replicate the appearance of a human NPC. public unsafe struct NpcData { - public string Name; - public CustomizeArray Customize; - private fixed byte _equip[40]; - public CharacterWeapon Mainhand; - public CharacterWeapon Offhand; - public NpcId Id; - public bool VisorToggled; - public ObjectKind Kind; + /// The name of the NPC. + public string Name; + /// The customizations of the NPC. + public CustomizeArray Customize; + + /// The equipment appearance of the NPC, 10 * CharacterArmor. + private fixed byte _equip[40]; + + /// The mainhand weapon appearance of the NPC. + public CharacterWeapon Mainhand; + + /// The offhand weapon appearance of the NPC. + public CharacterWeapon Offhand; + + /// The data ID of the NPC, either event NPC or battle NPC name. + public NpcId Id; + + /// Whether the NPCs visor is toggled. + public bool VisorToggled; + + /// Whether the NPC is an event NPC or a battle NPC. + public ObjectKind Kind; + + /// Obtain the equipment as CharacterArmors. public ReadOnlySpan Equip { get @@ -27,38 +44,40 @@ public unsafe struct NpcData } } + /// Write all the gear appearance to a single string. public string WriteGear() { var sb = new StringBuilder(128); var span = Equip; for (var i = 0; i < 10; ++i) { - sb.Append(span[i].Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(span[i].Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(span[i].Stain.Id.ToString("D3")); - sb.Append(", "); + sb.Append(span[i].Set.Id.ToString("D4")) + .Append('-') + .Append(span[i].Variant.Id.ToString("D3")) + .Append('-') + .Append(span[i].Stain.Id.ToString("D3")) + .Append(", "); } - sb.Append(Mainhand.Skeleton.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Weapon.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Mainhand.Stain.Id.ToString("D4")); - sb.Append(", "); - sb.Append(Offhand.Skeleton.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Weapon.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Offhand.Stain.Id.ToString("D3")); + sb.Append(Mainhand.Skeleton.Id.ToString("D4")) + .Append('-') + .Append(Mainhand.Weapon.Id.ToString("D4")) + .Append('-') + .Append(Mainhand.Variant.Id.ToString("D3")) + .Append('-') + .Append(Mainhand.Stain.Id.ToString("D4")) + .Append(", ") + .Append(Offhand.Skeleton.Id.ToString("D4")) + .Append('-') + .Append(Offhand.Weapon.Id.ToString("D4")) + .Append('-') + .Append(Offhand.Variant.Id.ToString("D3")) + .Append('-') + .Append(Offhand.Stain.Id.ToString("D3")); return sb.ToString(); } + /// Set an equipment piece to a given value. internal void Set(int idx, uint value) { fixed (byte* ptr = _equip) @@ -67,6 +86,7 @@ public unsafe struct NpcData } } + /// Check if the appearance data, excluding ID and Name, of two NpcData is equal. public bool DataEquals(in NpcData other) { if (VisorToggled != other.VisorToggled) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 939b0f6..9b35b92 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -29,7 +29,7 @@ public partial class CustomizationDrawer npc = true; } - var icon = _service.Service.GetIcon(custom!.Value.IconId); + var icon = _service.Manager.GetIcon(custom!.Value.IconId); using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) @@ -69,7 +69,7 @@ public partial class CustomizationDrawer for (var i = 0; i < _currentCount; ++i) { var custom = _set.Data(_currentIndex, i, _customize.Face); - var icon = _service.Service.GetIcon(custom.IconId); + var icon = _service.Manager.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i); @@ -180,8 +180,8 @@ public partial class CustomizationDrawer var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; var feature = _set.Data(featureIdx, 0, face); var icon = featureIdx == CustomizeIndex.LegacyTattoo - ? _legacyTattoo ?? _service.Service.GetIcon(feature.IconId) - : _service.Service.GetIcon(feature.IconId); + ? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId) + : _service.Manager.GetIcon(feature.IconId); if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index c131cf5..90cf5c2 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -14,7 +14,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizationService _service, CodeService _codes, Configuration _config) +public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config) : IDisposable { private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); @@ -23,7 +23,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio private Exception? _terminate; private CustomizeArray _customize = CustomizeArray.Default; - private CustomizationSet _set = null!; + private CustomizeSet _set = null!; public CustomizeArray Customize => _customize; @@ -117,7 +117,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio return DrawArtisan(); DrawRaceGenderSelector(); - _set = _service.Service.GetList(_customize.Clan, _customize.Gender); + _set = _service.Manager.GetSet(_customize.Clan, _customize.Gender); foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) PercentageSelector(id); diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index e82ab3c..d52be90 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -180,7 +180,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable private readonly AutoDesignManager _autoDesignManager; public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, - ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, + ItemManager items, CustomizeService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, EphemeralConfig config) : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) @@ -210,7 +210,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable _autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); } - private static Design CreateRevertDesign(CustomizationService customize, ItemManager items) + private static Design CreateRevertDesign(CustomizeService customize, ItemManager items) => new(customize, items) { Index = RevertDesignIndex, diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index f1aa9fe..ba16ab9 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System; +using System.Numerics; using Dalamud.Game.ClientState.Conditions; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; @@ -61,7 +62,7 @@ public class GenericPopupWindow : Window private void DrawFestivalPopup() { var viewportSize = ImGui.GetWindowViewport().Size; - ImGui.SetNextWindowSize(new Vector2(viewportSize.X / 5, viewportSize.Y / 7)); + ImGui.SetNextWindowSize(new Vector2(Math.Max(viewportSize.X / 5, 400), Math.Max(viewportSize.Y / 7, 150))); ImGui.SetNextWindowPos(viewportSize / 2, ImGuiCond.Always, new Vector2(0.5f)); using var popup = ImRaii.Popup("FestivalPopup", ImGuiWindowFlags.Modal); if (!popup) diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 2a0453c..846823a 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -27,7 +27,7 @@ public class SetPanel( ItemUnlockManager _itemUnlocks, RevertDesignCombo _designCombo, CustomizeUnlockManager _customizeUnlocks, - CustomizationService _customizations, + CustomizeService _customizations, IdentifierDrawer _identifierDrawer, Configuration _config) { @@ -295,7 +295,7 @@ public class SetPanel( if (!design.Design.DesignData.IsHuman) sb.AppendLine("The base model id can not be changed automatically to something non-human."); - var set = _customizations.Service.GetList(customize.Clan, customize.Gender); + var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender); foreach (var type in CustomizationExtensions.All) { var flag = type.ToFlag(); diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index c57c1b5..d2b9212 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -8,30 +8,27 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; -public class CustomizationServicePanel(CustomizationService _customization) : IDebugTabTree +public class CustomizationServicePanel(CustomizeService customize) : IDebugTabTree { public string Label => "Customization Service"; public bool Disabled - => !_customization.Awaiter.IsCompletedSuccessfully; + => !customize.Awaiter.IsCompletedSuccessfully; public void Draw() { - foreach (var clan in _customization.Service.Clans) + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - foreach (var gender in _customization.Service.Genders) - { - var set = _customization.Service.GetList(clan, gender); - DrawCustomizationInfo(set); - DrawNpcCustomizationInfo(set); - } + var set = customize.Manager.GetSet(clan, gender); + DrawCustomizationInfo(set); + DrawNpcCustomizationInfo(set); } } - private void DrawCustomizationInfo(CustomizationSet set) + private void DrawCustomizationInfo(CustomizeSet set) { - using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender}"); + using var tree = ImRaii.TreeNode($"{customize.ClanName(set.Clan, set.Gender)} {set.Gender}"); if (!tree) return; @@ -49,9 +46,9 @@ public class CustomizationServicePanel(CustomizationService _customization) : ID } } - private void DrawNpcCustomizationInfo(CustomizationSet set) + private void DrawNpcCustomizationInfo(CustomizeSet set) { - using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)"); + using var tree = ImRaii.TreeNode($"{customize.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)"); if (!tree) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index cd47606..5333ba5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,7 +154,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawCustomizeApplication() { - var set = _selector.Selected!.CustomizationSet; + var set = _selector.Selected!.CustomizeSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 8953501..10d972f 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -19,7 +19,7 @@ public class UnlockOverview { private readonly ItemManager _items; private readonly ItemUnlockManager _itemUnlocks; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; private readonly PenumbraChangedItemTooltip _tooltip; private readonly TextureService _textures; @@ -52,25 +52,22 @@ public class UnlockOverview } } - foreach (var clan in _customizations.Service.Clans) + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - foreach (var gender in _customizations.Service.Genders) - { - if (_customizations.Service.GetList(clan, gender).HairStyles.Count == 0) - continue; + if (_customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0) + continue; - if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", - _selected2 == clan && _selected3 == gender)) - { - _selected1 = FullEquipType.Unknown; - _selected2 = clan; - _selected3 = gender; - } + if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", + _selected2 == clan && _selected3 == gender)) + { + _selected1 = FullEquipType.Unknown; + _selected2 = clan; + _selected3 = gender; } } } - public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks, + public UnlockOverview(ItemManager items, CustomizeService customizations, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures, CodeService codes, JobService jobs, FavoriteManager favorites) { @@ -107,7 +104,7 @@ public class UnlockOverview private void DrawCustomizations() { - var set = _customizations.Service.GetList(_selected2, _selected3); + var set = _customizations.Manager.GetSet(_selected2, _selected3); var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -121,7 +118,7 @@ public class UnlockOverview continue; var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); - var icon = _customizations.Service.GetIcon(customize.IconId); + var icon = _customizations.Manager.GetIcon(customize.IconId); ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 4cac25e..4ec59e9 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -15,7 +15,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public class ImportService(CustomizationService _customizations, IDragDropManager _dragDropManager, ItemManager _items) +public class ImportService(CustomizeService _customizations, IDragDropManager _dragDropManager, ItemManager _items) { public void CreateDatSource() => _dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => @@ -179,14 +179,14 @@ public class ImportService(CustomizationService _customizations, IDragDropManage if (input.BodyType.Value != 1) return false; - var set = _customizations.Service.GetList(input.Clan, input.Gender); + var set = _customizations.Manager.GetSet(input.Clan, input.Gender); voice = set.Voices[0]; if (inputVoice.HasValue && !set.Voices.Contains(inputVoice.Value)) return false; foreach (var index in CustomizationExtensions.AllBasic) { - if (!CustomizationService.IsCustomizationValid(set, input.Face, index, input[index])) + if (!CustomizeService.IsCustomizationValid(set, input.Face, index, input[index])) return false; } diff --git a/Glamourer/Services/CustomizationService.cs b/Glamourer/Services/CustomizeService.cs similarity index 62% rename from Glamourer/Services/CustomizationService.cs rename to Glamourer/Services/CustomizeService.cs index 9e28e42..1ed6f07 100644 --- a/Glamourer/Services/CustomizationService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using Dalamud.Plugin.Services; using Glamourer.GameData; using OtterGui.Services; using Penumbra.GameData.DataContainers; @@ -10,26 +9,18 @@ using Penumbra.GameData.Structs; namespace Glamourer.Services; -public sealed class CustomizationService( - ITextureProvider textures, - IDataManager gameData, +public sealed class CustomizeService( HumanModelList humanModels, - IPluginLog log, - NpcCustomizeSet npcCustomizeSet) + NpcCustomizeSet npcCustomizeSet, + CustomizeManager manager) : IAsyncService { - public readonly HumanModelList HumanModels = humanModels; + public readonly HumanModelList HumanModels = humanModels; + public readonly CustomizeManager Manager = manager; + public readonly NpcCustomizeSet NpcCustomizeSet = npcCustomizeSet; - private ICustomizationManager? _service; - - private readonly Task _task = Task.WhenAll(humanModels.Awaiter, npcCustomizeSet.Awaiter) - .ContinueWith(_ => CustomizationManager.Create(textures, gameData, log, npcCustomizeSet)); - - public ICustomizationManager Service - => _service ??= _task.Result; - - public Task Awaiter - => _task; + public Task Awaiter { get; } + = Task.WhenAll(humanModels.Awaiter, manager.Awaiter, npcCustomizeSet.Awaiter); public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues, CustomizeFlag applyWhich, bool allowUnknown) @@ -51,7 +42,7 @@ public sealed class CustomizationService( } - var set = Service.GetList(ret.Clan, ret.Gender); + var set = Manager.GetSet(ret.Clan, ret.Gender); applyWhich = applyWhich.FixApplication(set); foreach (var index in CustomizationExtensions.AllBasic) { @@ -79,69 +70,34 @@ public sealed class CustomizationService( gender = Gender.Female; if (gender == Gender.MaleNpc) gender = Gender.Male; - return (gender, race) switch - { - (Gender.Male, SubRace.Midlander) => Service.GetName(CustomName.MidlanderM), - (Gender.Male, SubRace.Highlander) => Service.GetName(CustomName.HighlanderM), - (Gender.Male, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodM), - (Gender.Male, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightM), - (Gender.Male, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkM), - (Gender.Male, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkM), - (Gender.Male, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunM), - (Gender.Male, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonM), - (Gender.Male, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfM), - (Gender.Male, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardM), - (Gender.Male, SubRace.Raen) => Service.GetName(CustomName.RaenM), - (Gender.Male, SubRace.Xaela) => Service.GetName(CustomName.XaelaM), - (Gender.Male, SubRace.Helion) => Service.GetName(CustomName.HelionM), - (Gender.Male, SubRace.Lost) => Service.GetName(CustomName.LostM), - (Gender.Male, SubRace.Rava) => Service.GetName(CustomName.RavaM), - (Gender.Male, SubRace.Veena) => Service.GetName(CustomName.VeenaM), - (Gender.Female, SubRace.Midlander) => Service.GetName(CustomName.MidlanderF), - (Gender.Female, SubRace.Highlander) => Service.GetName(CustomName.HighlanderF), - (Gender.Female, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodF), - (Gender.Female, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightF), - (Gender.Female, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkF), - (Gender.Female, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkF), - (Gender.Female, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunF), - (Gender.Female, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonF), - (Gender.Female, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfF), - (Gender.Female, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardF), - (Gender.Female, SubRace.Raen) => Service.GetName(CustomName.RaenF), - (Gender.Female, SubRace.Xaela) => Service.GetName(CustomName.XaelaF), - (Gender.Female, SubRace.Helion) => Service.GetName(CustomName.HelionM), - (Gender.Female, SubRace.Lost) => Service.GetName(CustomName.LostM), - (Gender.Female, SubRace.Rava) => Service.GetName(CustomName.RavaF), - (Gender.Female, SubRace.Veena) => Service.GetName(CustomName.VeenaF), - _ => "Unknown", - }; + return Manager.GetSet(race, gender).Name; } /// Returns whether a clan is valid. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsClanValid(SubRace clan) - => Service.Clans.Contains(clan); + => CustomizeManager.Clans.Contains(clan); /// Returns whether a gender is valid for the given race. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsGenderValid(Race race, Gender gender) - => race is Race.Hrothgar ? gender == Gender.Male : Service.Genders.Contains(gender); + => race is Race.Hrothgar ? gender == Gender.Male : CustomizeManager.Genders.Contains(gender); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value) + public static bool IsCustomizationValid(CustomizeSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value) => IsCustomizationValid(set, face, type, value, out _); /// Returns whether a customization value is valid for a given clan/gender set and face. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value, + public static bool IsCustomizationValid(CustomizeSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value, out CustomizeData? data) => set.Validate(type, value, out data, face); /// Returns whether a customization value is valid for a given clan, gender and face. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsCustomizationValid(SubRace race, Gender gender, CustomizeValue face, CustomizeIndex type, CustomizeValue value) - => IsCustomizationValid(Service.GetList(race, gender), face, type, value); + => IsCustomizationValid(Manager.GetSet(race, gender), face, type, value); /// /// Check that the given race and clan are valid. @@ -160,10 +116,10 @@ public sealed class CustomizationService( return string.Empty; } - if (Service.Races.Contains(race)) + if (CustomizeManager.Races.Contains(race)) { actualRace = race; - actualClan = Service.Clans.FirstOrDefault(c => c.ToRace() == race, SubRace.Unknown); + actualClan = CustomizeManager.Clans.FirstOrDefault(c => c.ToRace() == race, SubRace.Unknown); // This should not happen. if (actualClan == SubRace.Unknown) { @@ -189,7 +145,7 @@ public sealed class CustomizationService( /// public string ValidateGender(Race race, Gender gender, out Gender actualGender) { - if (!Service.Genders.Contains(gender)) + if (!CustomizeManager.Genders.Contains(gender)) { actualGender = Gender.Male; return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}."; @@ -230,7 +186,7 @@ public sealed class CustomizationService( /// The returned actualValue is either the correct value or the one with index 0. /// The return value is an empty string or a warning message. /// - public static string ValidateCustomizeValue(CustomizationSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value, + public static string ValidateCustomizeValue(CustomizeSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value, out CustomizeValue actualValue, bool allowUnknown) { if (allowUnknown || IsCustomizationValid(set, face, index, value)) @@ -266,7 +222,7 @@ public sealed class CustomizationService( flags |= CustomizeFlag.Gender; } - var set = Service.GetList(customize.Clan, customize.Gender); + var set = Manager.GetSet(customize.Clan, customize.Gender); return FixValues(set, ref customize) | flags; } @@ -284,11 +240,11 @@ public sealed class CustomizationService( return 0; customize.Gender = newGender; - var set = Service.GetList(customize.Clan, customize.Gender); + var set = Manager.GetSet(customize.Clan, customize.Gender); return FixValues(set, ref customize) | CustomizeFlag.Gender; } - private static CustomizeFlag FixValues(CustomizationSet set, ref CustomizeArray customize) + private static CustomizeFlag FixValues(CustomizeSet set, ref CustomizeArray customize) { CustomizeFlag flags = 0; foreach (var idx in CustomizationExtensions.AllBasic) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 3afe390..9cbcd01 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -84,7 +84,7 @@ public static class ServiceManagerA => services.AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 67c9e08..deb7771 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -27,7 +27,7 @@ public unsafe class FunModule : IDisposable private readonly WorldSets _worldSets = new(); private readonly ItemManager _items; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly Configuration _config; private readonly CodeService _codes; private readonly Random _rng; @@ -67,7 +67,7 @@ public unsafe class FunModule : IDisposable internal void ResetFestival() => OnDayChange(DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year); - public FunModule(CodeService codes, CustomizationService customizations, ItemManager items, Configuration config, + public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, GenericPopupWindow popupWindow, StateManager stateManager, ObjectManager objects, DesignConverter designConverter, DesignManager designManager) { @@ -197,7 +197,7 @@ public unsafe class FunModule : IDisposable if (!_codes.EnabledIndividual) return; - var set = _customizations.Service.GetList(customize.Clan, customize.Gender); + var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender); foreach (var index in Enum.GetValues()) { if (index is CustomizeIndex.Face || !set.IsAvailable(index)) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 50b2605..0095fe9 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -12,12 +12,12 @@ namespace Glamourer.State; public class StateEditor { private readonly ItemManager _items; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly HumanModelList _humans; private readonly GPoseService _gPose; private readonly ICondition _condition; - public StateEditor(CustomizationService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) + public StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) { _customizations = customizations; _humans = humans; @@ -72,7 +72,7 @@ public class StateEditor state[CustomizeIndex.Clan] = source; state[CustomizeIndex.Gender] = source; - var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) state[index] = source; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d7f947f..1f3c36b 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -28,7 +28,7 @@ public class StateListener : IDisposable private readonly StateManager _manager; private readonly StateApplier _applier; private readonly ItemManager _items; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly PenumbraService _penumbra; private readonly SlotUpdating _slotUpdating; private readonly WeaponLoading _weaponLoading; @@ -52,7 +52,7 @@ public class StateListener : IDisposable SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, - ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition, CrestService crestService) + ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService) { _manager = manager; _items = items; @@ -167,7 +167,7 @@ public class StateListener : IDisposable return; } - var set = _customizations.Service.GetList(model.Clan, model.Gender); + var set = _customizations.Manager.GetSet(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { if (state[index] is not StateChanged.Source.Fixed) diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index a1e95ef..2bdbb78 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -29,7 +29,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable public IReadOnlyDictionary Unlocked => _unlocked; - public CustomizeUnlockManager(SaveService saveService, CustomizationService customizations, IDataManager gameData, + public CustomizeUnlockManager(SaveService saveService, CustomizeService customizations, IDataManager gameData, IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); @@ -174,38 +174,35 @@ public class CustomizeUnlockManager : IDisposable, ISavable "customization"); /// Create a list of all unlockable hairstyles and face paints. - private static Dictionary CreateUnlockableCustomizations(CustomizationService customizations, + private static Dictionary CreateUnlockableCustomizations(CustomizeService customizations, IDataManager gameData) { var ret = new Dictionary(); var sheet = gameData.GetExcelSheet(ClientLanguage.English)!; - foreach (var clan in customizations.Service.Clans) + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - foreach (var gender in customizations.Service.Genders) + var list = customizations.Manager.GetSet(clan, gender); + foreach (var hair in list.HairStyles) { - var list = customizations.Service.GetList(clan, gender); - foreach (var hair in list.HairStyles) + var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value); + if (x?.IsPurchasable == true) { - var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value); - if (x?.IsPurchasable == true) - { - var name = x.FeatureID == 61 - ? "Eternal Bond" - : x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Aesthetics - ", string.Empty) - ?? string.Empty; - ret.TryAdd(hair, (x.Data, name)); - } + var name = x.FeatureID == 61 + ? "Eternal Bond" + : x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Aesthetics - ", string.Empty) + ?? string.Empty; + ret.TryAdd(hair, (x.Data, name)); } + } - foreach (var paint in list.FacePaints) + foreach (var paint in list.FacePaints) + { + var x = sheet.FirstOrDefault(f => f.FeatureID == paint.Value.Value); + if (x?.IsPurchasable == true) { - var x = sheet.FirstOrDefault(f => f.FeatureID == paint.Value.Value); - if (x?.IsPurchasable == true) - { - var name = x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Cosmetics - ", string.Empty) - ?? string.Empty; - ret.TryAdd(paint, (x.Data, name)); - } + var name = x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Cosmetics - ", string.Empty) + ?? string.Empty; + ret.TryAdd(paint, (x.Data, name)); } } } diff --git a/OtterGui b/OtterGui index 197d23e..4404d62 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 197d23eee167c232000f22ef40a7a2bded913b6c +Subproject commit 4404d62b7442daa7e3436dc417364905e3d5cd2f From 4531cdadbed0a822c9803aa2afe6828c14c0bee2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 12:36:11 +0100 Subject: [PATCH 08/31] Move more DebugUi to GameData. --- Glamourer/GameData/CustomizeManager.cs | 6 +- Glamourer/GameData/NpcCustomizeSet.cs | 4 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- .../Gui/Equipment/GlamourerColorCombo.cs | 4 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 3 +- .../Gui/Tabs/DebugTab/ActorServicePanel.cs | 73 ------------------ .../Gui/Tabs/DebugTab/AutoDesignPanel.cs | 3 +- .../DebugTab/CustomizationServicePanel.cs | 5 +- .../Tabs/DebugTab/CustomizationUnlockPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 39 ++++------ .../Gui/Tabs/DebugTab/DesignConverterPanel.cs | 3 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 3 +- .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/FunPanel.cs | 3 +- .../Gui/Tabs/DebugTab/IdentifierPanel.cs | 62 --------------- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 9 ++- .../Gui/Tabs/DebugTab/ItemManagerPanel.cs | 39 ---------- .../Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/JobPanel.cs | 76 ------------------- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 3 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 3 +- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 5 +- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 3 +- .../Gui/Tabs/DebugTab/RestrictedGearPanel.cs | 42 ---------- .../Gui/Tabs/DebugTab/RetainedStatePanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/StainPanel.cs | 53 ------------- .../Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 3 +- Glamourer/Services/CustomizeService.cs | 3 + Glamourer/Services/ItemManager.cs | 4 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 33 files changed, 76 insertions(+), 399 deletions(-) delete mode 100644 Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/JobPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/StainPanel.cs diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index fabc483..6f03881 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -30,7 +30,7 @@ public class CustomizeManager : IAsyncService /// Every tribe and gender has a separate set of available customizations. public CustomizeSet GetSet(SubRace race, Gender gender) { - if (!Awaiter.IsCompletedSuccessfully) + if (!Finished) Awaiter.Wait(); return _customizationSets[ToIndex(race, gender)]; } @@ -61,6 +61,10 @@ public class CustomizeManager : IAsyncService /// public Task Awaiter { get; } + /// + public bool Finished + => Awaiter.IsCompletedSuccessfully; + private readonly IconStorage _icons; private static readonly int ListSize = Clans.Count * Genders.Count; private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index a99448e..de70bd9 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -33,6 +33,10 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// public Task Awaiter { get; } + /// + public bool Finished + => Awaiter.IsCompletedSuccessfully; + /// The list of data. private readonly List _data = []; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index cc94ed0..616e67c 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -24,7 +24,7 @@ public class EquipmentDrawer private readonly ItemManager _items; private readonly GlamourerColorCombo _stainCombo; - private readonly DictStains _stainData; + private readonly DictStain _stainData; private readonly ItemCombo[] _itemCombo; private readonly Dictionary _weaponCombo; private readonly CodeService _codes; diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 1073ac6..2a3dcec 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -13,7 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public sealed class GlamourerColorCombo(float _comboWidth, DictStains _stains, FavoriteManager _favorites) +public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites) : FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log) { protected override bool DrawSelectable(int globalIdx, bool selected) @@ -40,7 +40,7 @@ public sealed class GlamourerColorCombo(float _comboWidth, DictStains _stains, F return base.DrawSelectable(globalIdx, selected); } - private static Func>> CreateFunc(DictStains stains, + private static Func>> CreateFunc(DictStain stains, FavoriteManager favorites) => () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) .Prepend(new KeyValuePair(Stain.None.RowIndex, Stain.None)).Select(kvp diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index bbcfa32..ea99620 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -12,10 +12,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree +public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer { public string Label => $"Active Actors ({_stateManager.Count})###Active Actors"; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs deleted file mode 100644 index 976b5c4..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface.Utility; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using Penumbra.GameData.Actors; -using Penumbra.GameData.DataContainers; -using ImGuiClip = OtterGui.ImGuiClip; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class ActorManagerPanel(ActorManager _actors, DictBNpcNames _bNpcNames) : IDebugTabTree -{ - public string Label - => "Actor Service"; - - public bool Disabled - => !_actors.Awaiter.IsCompletedSuccessfully; - - private string _bnpcFilter = string.Empty; - private string _enpcFilter = string.Empty; - private string _companionFilter = string.Empty; - private string _mountFilter = string.Empty; - private string _ornamentFilter = string.Empty; - private string _worldFilter = string.Empty; - - public void Draw() - { - DrawBnpcTable(); - DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.Data.ENpcs.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.Data.Companions.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.Data.Mounts.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.Data.Ornaments.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.Data.Worlds.Select(kvp => ((uint)kvp.Key.Id, kvp.Value))); - } - - private void DrawBnpcTable() - { - using var _ = ImRaii.PushId(1); - using var tree = ImRaii.TreeNode("BNPCs"); - if (!tree) - return; - - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _bnpcFilter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("3", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextRow(); - var data = _actors.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.Id.ToString("D5"), kvp.Value)); - var remainder = ImGuiClip.FilteredClippedDraw(data, skips, - p => p.Item2.Contains(_bnpcFilter) || p.Value.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Item2); - ImGuiUtil.DrawTableColumn(p.Value); - var bNpcs = _bNpcNames.GetBNpcsFromName(p.Key.BNpcNameId); - ImGuiUtil.DrawTableColumn(string.Join(", ", bNpcs.Select(b => b.Id.ToString()))); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index 8e29b8b..cbc0e40 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -2,10 +2,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IDebugTabTree +public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDrawer { public string Label => "Auto Designs"; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index d2b9212..7ab089a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -5,16 +5,17 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class CustomizationServicePanel(CustomizeService customize) : IDebugTabTree +public class CustomizationServicePanel(CustomizeService customize) : IGameDataDrawer { public string Label => "Customization Service"; public bool Disabled - => !customize.Awaiter.IsCompletedSuccessfully; + => !customize.Finished; public void Draw() { diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs index b062980..8703d8c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -5,10 +5,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IDebugTabTree +public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IGameDataDrawer { public string Label => "Customizations"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index ef76b09..b06e807 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -4,10 +4,11 @@ using Glamourer.Interop; using ImGuiNET; using OtterGui; using Penumbra.GameData.Files; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DatFilePanel(ImportService _importService) : IDebugTabTree +public class DatFilePanel(ImportService _importService) : IGameDataDrawer { public string Label => "Character Dat File"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index bd0b98f..cb50b88 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -4,21 +4,14 @@ using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; using OtterGui.Services; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public interface IDebugTabTree : IService +public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) { - public string Label { get; } - public void Draw(); - - public bool Disabled { get; } -} - -public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) -{ - public string Label { get; } = label; - public IReadOnlyList SubTrees { get; } = subTrees; + public string Label { get; } = label; + public IReadOnlyList SubTrees { get; } = subTrees; public void Draw() { @@ -27,14 +20,13 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) foreach (var subTree in SubTrees) { - using (var disabled = ImRaii.Disabled(subTree.Disabled)) + using var disabled = ImRaii.Disabled(subTree.Disabled); + using var tree = ImRaii.TreeNode(subTree.Label); + if (tree) { - using var tree = ImRaii.TreeNode(subTree.Label); - if (!tree) - continue; + disabled.Dispose(); + subTree.Draw(); } - - subTree.Draw(); } } @@ -53,13 +45,14 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) => new ( "Game Data", - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService() ); diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index 9553f72..0de63ed 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -8,10 +8,11 @@ using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DesignConverterPanel(DesignConverter _designConverter) : IDebugTabTree +public class DesignConverterPanel(DesignConverter _designConverter) : IGameDataDrawer { public string Label => "Design Converter"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 8b7ae9a..8f40388 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -6,10 +6,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IDebugTabTree +public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IGameDataDrawer { public string Label => $"Design Manager ({_designManager.Designs.Count} Designs)###Design Manager"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 400b2b1..a4e17ed 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -8,10 +8,11 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDebugTabTree +public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IGameDataDrawer { public string Label => "Base64 Design Tester"; diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index 9afc125..c517070 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -1,9 +1,10 @@ using Glamourer.State; using ImGuiNET; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class FunPanel(FunModule _funModule, Configuration _config) : IDebugTabTree +public class FunPanel(FunModule _funModule, Configuration _config) : IGameDataDrawer { public string Label => "Fun Module"; diff --git a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs deleted file mode 100644 index 9a0503e..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Linq; -using Dalamud.Interface.Utility; -using Glamourer.Services; -using ImGuiNET; -using OtterGui; -using Penumbra.GameData.Data; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class IdentifierPanel(ItemManager _items, GamePathParser _gamePathParser) : IDebugTabTree -{ - public string Label - => "Identifier Service"; - - public bool Disabled - => !_items.ObjectIdentification.Awaiter.IsCompletedSuccessfully; - - private string _gamePath = string.Empty; - private int _setId; - private int _secondaryId; - private int _variant; - - public void Draw() - { - static void Text(string text) - { - if (text.Length > 0) - ImGui.TextUnformatted(text); - } - - ImGui.TextUnformatted("Parse Game Path"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256); - var fileInfo = _gamePathParser.GetFileInfo(_gamePath); - ImGui.TextUnformatted( - $"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}"); - Text(string.Join("\n", _items.ObjectIdentification.Identify(_gamePath).Keys)); - - ImGui.Separator(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Identify Model"); - ImGui.SameLine(); - DebugTab.DrawInputModelSet(true, ref _setId, ref _secondaryId, ref _variant); - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var identified = _items.Identify(slot, (PrimaryId)_setId, 0, (Variant)_variant); - Text(identified.Name); - ImGuiUtil.HoverTooltip(string.Join("\n", - _items.ObjectIdentification.Identify((PrimaryId)_setId, 0, (Variant)_variant, slot) - .Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); - } - - var weapon = _items.Identify(EquipSlot.MainHand, (PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant); - Text(weapon.Name); - ImGuiUtil.HoverTooltip(string.Join("\n", - _items.ObjectIdentification.Identify((PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index 1334f2b..bd447e7 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -2,10 +2,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class InventoryPanel : IDebugTabTree +public unsafe class InventoryPanel : IGameDataDrawer { public string Label => "Inventory"; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 329a2af..9059d53 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -8,11 +8,12 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui; +using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; -public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IDebugTabTree +public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IGameDataDrawer { public string Label => "IPC Tester"; @@ -137,12 +138,12 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag } } - private unsafe void DrawItemIdInput() + private void DrawItemIdInput() { var tmp = _customItemId.Id; - if (ImGui.InputScalar("Custom Item ID", ImGuiDataType.U64, (nint)(&tmp), nint.Zero, nint.Zero)) + if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) _customItemId = (CustomItemId)tmp; ImGui.SameLine(); - EquipSlotCombo.Draw("Equip Slot", string.Empty, 200 * ImGuiHelpers.GlobalScale, ref _slot); + EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs deleted file mode 100644 index 9fbc931..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Linq; -using Glamourer.Services; -using ImGuiNET; -using OtterGui.Raii; -using Penumbra.GameData.Enums; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class ItemManagerPanel(ItemManager _items) : IDebugTabTree -{ - public string Label - => "Item Manager"; - - public bool Disabled - => !_items.ItemData.Awaiter.IsCompletedSuccessfully; - - private string _itemFilter = string.Empty; - - public void Draw() - { - ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})", - ImGuiTreeNodeFlags.Leaf).Dispose(); - DebugTab.DrawNameTable("All Items (Main)", ref _itemFilter, - _items.ItemData.AllItems(true).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) - .OrderBy(p => p.Item1)); - DebugTab.DrawNameTable("All Items (Off)", ref _itemFilter, - _items.ItemData.AllItems(false).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) - .OrderBy(p => p.Item1)); - foreach (var type in Enum.GetValues().Skip(1)) - { - DebugTab.DrawNameTable(type.ToName(), ref _itemFilter, - _items.ItemData.ByType[type] - .Select(p => (p.ItemId.Id, $"{p.Name} ({(p.SecondaryId.Id == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); - } - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index 73194c4..efcc664 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -7,11 +7,12 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree +public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer { public string Label => "Unlocked Items"; diff --git a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs deleted file mode 100644 index 2318d98..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Glamourer.Interop; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class JobPanel(JobService _jobs) : IDebugTabTree -{ - public string Label - => "Job Service"; - - public bool Disabled - => false; - - public void Draw() - { - DrawJobs(); - DrawJobGroups(); - DrawValidJobGroups(); - } - - private void DrawJobs() - { - using var t = ImRaii.TreeNode("Jobs"); - if (!t) - return; - - using var table = ImRaii.Table("##jobs", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var (id, job) in _jobs.Jobs) - { - ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); - ImGuiUtil.DrawTableColumn(job.Name); - ImGuiUtil.DrawTableColumn(job.Abbreviation); - } - } - - private void DrawJobGroups() - { - using var t = ImRaii.TreeNode("All Job Groups"); - if (!t) - return; - - using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var (group, idx) in _jobs.AllJobGroups.WithIndex()) - { - ImGuiUtil.DrawTableColumn(idx.ToString("D3")); - ImGuiUtil.DrawTableColumn(group.Name); - ImGuiUtil.DrawTableColumn(group.Count.ToString()); - } - } - - private void DrawValidJobGroups() - { - using var t = ImRaii.TreeNode("Valid Job Groups"); - if (!t) - return; - - using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var (id, group) in _jobs.JobGroups) - { - ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); - ImGuiUtil.DrawTableColumn(group.Name); - ImGuiUtil.DrawTableColumn(group.Count.ToString()); - } - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 596476b..c70396c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -7,6 +7,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; @@ -16,7 +17,7 @@ public unsafe class ModelEvaluationPanel( VisorService _visorService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, - CrestService _crestService) : IDebugTabTree + CrestService _crestService) : IGameDataDrawer { public string Label => "Model Evaluation"; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index ffc4b72..cede0f3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -12,11 +12,12 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IDebugTabTree +public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IGameDataDrawer { public string Label => "NPC Appearance"; diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index b868140..e90cbda 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -7,10 +7,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Actors; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IDebugTabTree +public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer { public string Label => "Object Manager"; @@ -33,7 +34,7 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _acto ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("World"); - ImGuiUtil.DrawTableColumn(_actors.Awaiter.IsCompletedSuccessfully ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); + ImGuiUtil.DrawTableColumn(_actors.Finished ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); ImGuiUtil.DrawTableColumn("Player Character"); diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index cf5b92a..641c16a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -10,11 +10,12 @@ using OtterGui; using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItemTooltip _penumbraTooltip) : IDebugTabTree +public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItemTooltip _penumbraTooltip) : IGameDataDrawer { public string Label => "Penumbra Interop"; diff --git a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs deleted file mode 100644 index 0f27eff..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Linq; -using Glamourer.Services; -using ImGuiNET; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class RestrictedGearPanel(ItemManager _items) : IDebugTabTree -{ - public string Label - => "Restricted Gear Service"; - - public bool Disabled - => false; - - private int _setId; - private int _secondaryId; - private int _variant; - - public void Draw() - { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Resolve Model"); - DebugTab.DrawInputModelSet(false, ref _setId, ref _secondaryId, ref _variant); - foreach (var race in Enum.GetValues().Skip(1)) - { - ReadOnlySpan genders = [Gender.Male, Gender.Female]; - foreach (var gender in genders) - { - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var (replaced, model) = - _items.RestrictedGear.ResolveRestricted(new CharacterArmor((PrimaryId)_setId, (Variant)_variant, 0), slot, race, gender); - if (replaced) - ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}."); - } - } - } - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs index 68841bc..027c316 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -3,10 +3,11 @@ using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree +public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer { public string Label => "Retained States (Inactive Actors)"; diff --git a/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs b/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs deleted file mode 100644 index 643b6dc..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Numerics; -using Dalamud.Interface.Utility; -using Glamourer.Services; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using ImGuiClip = OtterGui.ImGuiClip; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class StainPanel(ItemManager _items) : IDebugTabTree -{ - public string Label - => "Stain Service"; - - public bool Disabled - => false; - - private string _stainFilter = string.Empty; - - public void Draw() - { - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _stainFilter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 4, - ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.SizingFixedFit, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextRow(); - var remainder = ImGuiClip.FilteredClippedDraw(_items.Stains, skips, - p => p.Key.Id.ToString().Contains(_stainFilter) || p.Value.Name.Contains(_stainFilter, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Key.Id.ToString("D3")); - ImGui.TableNextColumn(); - ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), - ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()), - p.Value.RgbaColor, 5 * ImGuiHelpers.GlobalScale); - ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight())); - ImGuiUtil.DrawTableColumn(p.Value.Name); - ImGuiUtil.DrawTableColumn($"#{p.Value.R:X2}{p.Value.G:X2}{p.Value.B:X2}{(p.Value.Gloss ? ", Glossy" : string.Empty)}"); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs index e658c99..ecd8d83 100644 --- a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -7,11 +7,12 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree +public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer { public string Label => "Unlockable Items"; diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index 1ed6f07..6eeb9db 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -22,6 +22,9 @@ public sealed class CustomizeService( public Task Awaiter { get; } = Task.WhenAll(humanModels.Awaiter, manager.Awaiter, npcCustomizeSet.Awaiter); + public bool Finished + => Awaiter.IsCompletedSuccessfully; + public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues, CustomizeFlag applyWhich, bool allowUnknown) { diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 20b97f0..52709c5 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -21,14 +21,14 @@ public class ItemManager public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; - public readonly DictStains Stains; + public readonly DictStain Stains; public readonly ItemData ItemData; public readonly RestrictedGear RestrictedGear; public readonly EquipItem DefaultSword; public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, - ItemData itemData, DictStains stains, RestrictedGear restrictedGear) + ItemData itemData, DictStain stains, RestrictedGear restrictedGear) { _config = config; ItemSheet = gameData.GetExcelSheet()!; diff --git a/OtterGui b/OtterGui index 4404d62..bdf053e 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4404d62b7442daa7e3436dc417364905e3d5cd2f +Subproject commit bdf053ea9e7ac7b96dcd6aceadff8d92c3050b34 diff --git a/Penumbra.GameData b/Penumbra.GameData index 4af8775..a7d2d73 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 4af8775f0925ff89e6900c8816b03e0ffeb73f6d +Subproject commit a7d2d73217113eadf02e21865a82deb92ea9eb53 From 44a65f61fb5ef2c7e31a79978614fac908608aab Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 13:38:29 +0100 Subject: [PATCH 09/31] Add diagnostic display and make CustomizeManager a DataContainer. --- Glamourer/GameData/CustomizeManager.cs | 28 ++++++++++++++++--- Glamourer/GameData/CustomizeSetFactory.cs | 4 +-- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 1 + OtterGui | 2 +- Penumbra.GameData | 2 +- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index 6f03881..a78cdc0 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Dalamud.Interface.Internal; @@ -12,7 +13,7 @@ using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.GameData; /// Generate everything about customization per tribe and gender. -public class CustomizeManager : IAsyncService +public class CustomizeManager : IAsyncDataContainer { /// All races except for Unknown public static readonly IReadOnlyList Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); @@ -52,10 +53,21 @@ public class CustomizeManager : IAsyncService public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { _icons = new IconStorage(textures, gameData); - var tmpTask = Task.Run(() => new CustomizeSetFactory(gameData, log, _icons, npcCustomizeSet)); + var stopwatch = new Stopwatch(); + var tmpTask = Task.Run(() => + { + stopwatch.Start(); + return new CustomizeSetFactory(gameData, log, _icons, npcCustomizeSet); + }); var setTasks = AllSets().Select(p => tmpTask.ContinueWith(t => _customizationSets[ToIndex(p.Clan, p.Gender)] = t.Result.CreateSet(p.Clan, p.Gender))); - Awaiter = Task.WhenAll(setTasks); + Awaiter = Task.WhenAll(setTasks).ContinueWith(_ => + { + // This is far too hard to estimate sensibly. + TotalCount = 0; + Memory = 0; + Time = stopwatch.ElapsedMilliseconds; + }); } /// @@ -78,4 +90,12 @@ public class CustomizeManager : IAsyncService return idx; } -} \ No newline at end of file + + public long Time { get; private set; } + public long Memory { get; private set; } + + public string Name + => nameof(CustomizeManager); + + public int TotalCount { get; private set; } +} diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 5eaaa58..6099344 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -274,7 +274,7 @@ internal class CustomizeSetFactory( { // If none exists and the id corresponds to highlights, set the Highlights name. if (c == CustomizeIndex.Highlights) - return _lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; + return string.Intern(_lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"); // Otherwise there is an error and we use the default name. return c.ToDefaultName(); @@ -282,7 +282,7 @@ internal class CustomizeSetFactory( // Otherwise all is normal, get the menu name or if it does not work the default name. var textRow = _lobbySheet.GetRow(menu.Value.Id); - return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); + return string.Intern(textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName()); }).ToArray(); // Add names for both eye colors. diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index cb50b88..7c50f4d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -45,6 +45,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) => new ( "Game Data", + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/OtterGui b/OtterGui index bdf053e..4df65fb 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit bdf053ea9e7ac7b96dcd6aceadff8d92c3050b34 +Subproject commit 4df65fb330f3746b7836c39cb96d1e36a53bcec0 diff --git a/Penumbra.GameData b/Penumbra.GameData index a7d2d73..3073db1 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a7d2d73217113eadf02e21865a82deb92ea9eb53 +Subproject commit 3073db1fc8a1894a4af8974ea9c22a63acd7316e From d81a6b7f6ff4e59be28ad0879c9ac2c04c48f611 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 14:37:04 +0100 Subject: [PATCH 10/31] Set Order for customize again. --- Glamourer/GameData/CustomizeSet.cs | 13 ------------- Glamourer/GameData/CustomizeSetFactory.cs | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index d2dc8e9..bc4e7a3 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -248,19 +248,6 @@ public class CustomizeSet public CharaMakeParams.MenuType Type(CustomizeIndex index) => Types[(int)index]; - internal static IReadOnlyDictionary ComputeOrder(CustomizeSet set) - { - var ret = Enum.GetValues().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()); - return dict; - } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public int Count(CustomizeIndex index) => Count(index, CustomizeValue.Zero); diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 6099344..22d465b 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -68,6 +68,7 @@ internal class CustomizeSetFactory( SetFacialFeatures(set, row); SetHairByFace(set); SetNpcData(set, set.Clan, set.Gender); + SetOrder(set); } /// Given a customize set with filled data, find all customizations used by valid NPCs that are not regularly available. @@ -303,7 +304,7 @@ internal class CustomizeSetFactory( } /// Get the manu types for all available options. - private CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row) + private static CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row) { // Set up the menu types for all customizations. return Enum.GetValues().Select(c => @@ -394,6 +395,19 @@ internal class CustomizeSetFactory( } } + internal static void SetOrder(CustomizeSet set) + { + var ret = Enum.GetValues().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, []); + set.Order = dict; + } + /// Set hairstyles per face for Hrothgar and make it simple for non-Hrothgar. private void SetHairByFace(CustomizeSet set) { From fcca756e20024318ddcbb34a8647591f968a9ca1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 14:37:36 +0100 Subject: [PATCH 11/31] Allow change of bodytype. --- .../CustomizationDrawer.GenderRace.cs | 16 +++++++++++++++- .../Gui/Customization/CustomizationDrawer.cs | 2 ++ Glamourer/Services/CustomizeService.cs | 10 ++++++++++ Penumbra.GameData | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 097d99b..4b9fab7 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,11 +1,12 @@ using System; using System.Linq; +using System.Numerics; using Dalamud.Interface; -using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -75,4 +76,17 @@ public partial class CustomizationDrawer if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) ImGui.SetTooltip("The race can not be changed as this requires a redraw of the character, which is not supported for this actor."); } + + private void DrawBodyType() + { + if (_customize.BodyType.Value == 1) + return; + + if (!ImGuiUtil.DrawDisabledButton($"Reset Body Type {_customize.BodyType.Value} to Default", + new Vector2(_raceSelectorWidth + _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X, 0), string.Empty, _lockedRedraw)) + return; + + Changed |= CustomizeFlag.BodyType; + _customize.BodyType = (CustomizeValue)1; + } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 90cf5c2..a56040d 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -117,6 +117,8 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer return DrawArtisan(); DrawRaceGenderSelector(); + DrawBodyType(); + _set = _service.Manager.GetSet(_customize.Clan, _customize.Gender); foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index 6eeb9db..0b094f3 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -44,6 +44,16 @@ public sealed class CustomizeService( applied |= CustomizeFlag.Gender; } + if (applyWhich.HasFlag(CustomizeFlag.BodyType)) + { + if (oldValues[CustomizeIndex.BodyType] != newValues[CustomizeIndex.BodyType]) + { + ret.Set(CustomizeIndex.BodyType, newValues[CustomizeIndex.BodyType]); + changed |= CustomizeFlag.BodyType; + } + + applied |= CustomizeFlag.BodyType; + } var set = Manager.GetSet(ret.Clan, ret.Gender); applyWhich = applyWhich.FixApplication(set); diff --git a/Penumbra.GameData b/Penumbra.GameData index 3073db1..c1c9fb6 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3073db1fc8a1894a4af8974ea9c22a63acd7316e +Subproject commit c1c9fb6d83be8b05dfb3f35031c05781daad1fc7 From a900219ede5f50565c3156bef94cdf4958e00c24 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 16:16:37 +0100 Subject: [PATCH 12/31] Make crests more quiet. --- Glamourer/Interop/CrestService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 2bfa8d5..7db9a59 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -77,7 +77,7 @@ public sealed unsafe class CrestService : EventWrapper Date: Sun, 24 Dec 2023 16:17:44 +0100 Subject: [PATCH 13/31] Improve bodytype stuff. --- Glamourer/Designs/DesignBase.cs | 32 ++++---- Glamourer/GameData/CustomizeSet.cs | 2 +- Glamourer/GameData/CustomizeSetFactory.cs | 2 +- Glamourer/GameData/NpcCustomizeSet.cs | 6 +- Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 75 ++++--------------- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 1 - .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 10 ++- 7 files changed, 40 insertions(+), 88 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 7602f80..f4e7e28 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -33,26 +33,26 @@ public class DesignBase internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) { - _designData = designData; - ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; - ApplyEquip = equipFlags & EquipFlagExtensions.All; - _designFlags = 0; - CustomizeSet = SetCustomizationSet(customize); + _designData = designData; + ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + ApplyEquip = equipFlags & EquipFlagExtensions.All; + _designFlags = 0; + CustomizeSet = SetCustomizationSet(customize); } internal DesignBase(DesignBase clone) { - _designData = clone._designData; - CustomizeSet = clone.CustomizeSet; - ApplyCustomize = clone.ApplyCustomizeRaw; - ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; - _designFlags = clone._designFlags & (DesignFlags)0x0F; + _designData = clone._designData; + CustomizeSet = clone.CustomizeSet; + ApplyCustomize = clone.ApplyCustomizeRaw; + ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; + _designFlags = clone._designFlags & (DesignFlags)0x0F; } /// Ensure that the customization set is updated when the design data changes. internal void SetDesignData(CustomizeService customize, in DesignData other) { - _designData = other; + _designData = other; CustomizeSet = SetCustomizationSet(customize); } @@ -68,8 +68,8 @@ public class DesignBase WriteProtected = 0x10, } - private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; - public CustomizeSet CustomizeSet { get; private set; } + private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; + public CustomizeSet CustomizeSet { get; private set; } internal CustomizeFlag ApplyCustomize { @@ -90,7 +90,7 @@ public class DesignBase return false; _designData.Customize = customize; - CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); + CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); return true; } @@ -485,7 +485,7 @@ public class DesignBase design._designData.Customize.Race = race; design._designData.Customize.Clan = clan; design._designData.Customize.Gender = gender; - design.CustomizeSet = design.SetCustomizationSet(customizations); + design.CustomizeSet = design.SetCustomizationSet(customizations); design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); @@ -495,7 +495,7 @@ public class DesignBase { var tok = json[idx.ToString()]; var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); - if (set.IsAvailable(idx)) + if (set.IsAvailable(idx) && design._designData.Customize.BodyType == 1) PrintWarning(CustomizeService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, allowUnknown)); var apply = tok?["Apply"]?.ToObject() ?? false; diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index bc4e7a3..7c98903 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -297,5 +297,5 @@ public static class CustomizationSetExtensions { /// Return only the available customizations in this set and Clan or Gender. public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizeSet set) - => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); + => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType); } diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 22d465b..90cae8d 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -91,7 +91,7 @@ internal class CustomizeSetFactory( var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); _npcCustomizeSet.Awaiter.Wait(); - foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender)) + foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1)) { foreach (var customizeIndex in customizeIndices) { diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index de70bd9..bcd6c54 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -256,8 +256,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList customize.SetByIndex(24, (CustomizeValue)bnpcCustomize.FacePaint); customize.SetByIndex(25, (CustomizeValue)bnpcCustomize.FacePaintColor); - if (customize.BodyType.Value != 1 - || !CustomizeManager.Races.Contains(customize.Race) + if (!CustomizeManager.Races.Contains(customize.Race) || !CustomizeManager.Clans.Contains(customize.Clan) || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); @@ -299,8 +298,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList customize.SetByIndex(24, (CustomizeValue)enpcBase.FacePaint); customize.SetByIndex(25, (CustomizeValue)enpcBase.FacePaintColor); - if (customize.BodyType.Value != 1 - || !CustomizeManager.Races.Contains(customize.Race) + if (!CustomizeManager.Races.Contains(customize.Race) || !CustomizeManager.Clans.Contains(customize.Clan) || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index 52c7b53..ffe6945 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -1,20 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface.Utility; using ImGuiNET; -using Microsoft.Extensions.DependencyInjection; -using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; -using ImGuiClip = Dalamud.Interface.Utility.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class DebugTab(IServiceProvider _provider) : ITab +public unsafe class DebugTab(ServiceManager manager) : ITab { - private readonly Configuration _config = _provider.GetRequiredService(); + private readonly Configuration _config = manager.GetService(); public bool IsVisible => _config.DebugMode; @@ -24,11 +18,11 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab private readonly DebugTabHeader[] _headers = [ - DebugTabHeader.CreateInterop(_provider), - DebugTabHeader.CreateGameData(_provider), - DebugTabHeader.CreateDesigns(_provider), - DebugTabHeader.CreateState(_provider), - DebugTabHeader.CreateUnlocks(_provider), + DebugTabHeader.CreateInterop(manager.Provider!), + DebugTabHeader.CreateGameData(manager.Provider!), + DebugTabHeader.CreateDesigns(manager.Provider!), + DebugTabHeader.CreateState(manager.Provider!), + DebugTabHeader.CreateUnlocks(manager.Provider!), ]; public void DrawContent() @@ -37,55 +31,12 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab if (!child) return; + if (ImGui.CollapsingHeader("General")) + { + manager.Timers.Draw("Timers"); + } + foreach (var header in _headers) header.Draw(); } - - public static void DrawInputModelSet(bool withWeapon, ref int setId, ref int secondaryId, ref int variant) - { - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##SetId", ref setId, 0, 0); - if (withWeapon) - { - ImGui.SameLine(); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##TypeId", ref secondaryId, 0, 0); - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##Variant", ref variant, 0, 0); - } - - public static void DrawNameTable(string label, ref string filter, IEnumerable<(uint, string)> names) - { - using var _ = ImRaii.PushId(label); - using var tree = ImRaii.TreeNode(label); - if (!tree) - return; - - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref filter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextColumn(); - var f = filter; - var remainder = ImGuiClip.FilteredClippedDraw(names.Select(p => (p.Item1.ToString("D5"), p.Item2)), skips, - p => p.Item1.Contains(f) || p.Item2.Contains(f, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Item1); - ImGuiUtil.DrawTableColumn(p.Item2); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } } diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 7c50f4d..b61b4a6 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; -using OtterGui.Services; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index cede0f3..e0c7aa8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -17,7 +17,8 @@ using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IGameDataDrawer +public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) + : IGameDataDrawer { public string Label => "NPC Appearance"; @@ -25,20 +26,23 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM public bool Disabled => false; - private string _npcFilter = string.Empty; + private string _npcFilter = string.Empty; private bool _customizeOrGear; public void Draw() { ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); + var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); using var table = ImRaii.Table("npcs", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); if (!table) return; + if (resetScroll) + ImGui.SetScrollY(0); + ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); From dd5c56de9db82c432afcd374a0664e53fcc6c101 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 26 Dec 2023 17:29:45 +0100 Subject: [PATCH 14/31] Rework codes and fun a bit. --- .../Gui/Customization/CustomizationDrawer.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 4 +- Glamourer/Gui/Tabs/SettingsTab.cs | 17 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 4 +- Glamourer/Interop/VisorService.cs | 1 - Glamourer/Services/CodeService.cs | 219 +++++++++++------- Glamourer/Services/ItemManager.cs | 2 +- Glamourer/State/FunModule.cs | 211 ++++++++++------- Glamourer/State/StateListener.cs | 6 +- 9 files changed, 269 insertions(+), 197 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index a56040d..b741f39 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -113,7 +113,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer try { - if (_codes.EnabledArtisan) + if (_codes.Enabled(CodeService.CodeFlag.Artisan)) return DrawArtisan(); DrawRaceGenderSelector(); diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 616e67c..e8840bc 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -94,7 +94,7 @@ public class EquipmentDrawer if (_config.SmallEquip) DrawEquipSmall(equipDrawData); - else if (!equipDrawData.Locked && _codes.EnabledArtisan) + else if (!equipDrawData.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan)) DrawEquipArtisan(equipDrawData); else DrawEquipNormal(equipDrawData); @@ -117,7 +117,7 @@ public class EquipmentDrawer if (_config.SmallEquip) DrawWeaponsSmall(mainhand, offhand, allWeapons); - else if (!mainhand.Locked && _codes.EnabledArtisan) + else if (!mainhand.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan)) DrawWeaponsArtisan(mainhand, offhand); else DrawWeaponsNormal(mainhand, offhand, allWeapons); diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index 1b0e27d..340a9fd 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -247,22 +247,17 @@ public class SettingsTab : ITab if (ImGui.Checkbox(code, ref state)) { action(state); - _config.Codes[i] = (code, state); - _codeService.VerifyState(); - _config.Save(); + _codeService.SaveState(); } } - if (_codeService.EnabledCaptain) - { - if (ImGui.Button("Who am I?!?")) - _funModule.WhoAmI(); + if (ImGui.Button("Who am I?!?")) + _funModule.WhoAmI(); - ImGui.SameLine(); + ImGui.SameLine(); - if (ImGui.Button("Who is that!?!")) - _funModule.WhoIsThat(); - } + if (ImGui.Button("Who is that!?!")) + _funModule.WhoIsThat(); } private void DrawCodeHints() diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 10d972f..65203b9 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -121,7 +121,7 @@ public class UnlockOverview var icon = _customizations.Manager.GetIcon(customize.IconId); ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, - unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); + unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); @@ -189,7 +189,7 @@ public class UnlockOverview var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); - ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); + ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (_favorites.Contains(item)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 2b49eba..75c5e36 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -18,7 +18,6 @@ public class VisorService : IDisposable { Event = visorStateChanged; _setupVisorHook = interop.HookFromAddress((nint)Human.MemberFunctionPointers.SetupVisor, SetupVisorDetour); - interop.InitializeFromAttributes(this); _setupVisorHook.Enable(); } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index f065e50..82a6477 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -12,23 +12,66 @@ public class CodeService private readonly Configuration _config; private readonly SHA256 _hasher = SHA256.Create(); - public enum Sizing + [Flags] + public enum CodeFlag : ulong { - None, - Dwarf, - Giant, + Clown = 0x000001, + Emperor = 0x000002, + Individual = 0x000004, + Dwarf = 0x000008, + Giant = 0x000010, + OopsHyur = 0x000020, + OopsElezen = 0x000040, + OopsLalafell = 0x000080, + OopsMiqote = 0x000100, + OopsRoegadyn = 0x000200, + OopsAuRa = 0x000400, + OopsHrothgar = 0x000800, + OopsViera = 0x001000, + Artisan = 0x002000, + SixtyThree = 0x004000, + Shirts = 0x008000, + World = 0x010000, + Elephants = 0x020000, } - public bool EnabledClown { get; private set; } - public bool EnabledEmperor { get; private set; } - public bool EnabledIndividual { get; private set; } - public Sizing EnabledSizing { get; private set; } - public Race EnabledOops { get; private set; } - public bool EnabledArtisan { get; private set; } - public bool EnabledCaptain { get; private set; } - public bool Enabled63 { get; private set; } - public bool EnabledShirts { get; private set; } - public bool EnabledWorld { get; private set; } + public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants; + public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants; + + public const CodeFlag RaceCodes = CodeFlag.OopsHyur + | CodeFlag.OopsElezen + | CodeFlag.OopsLalafell + | CodeFlag.OopsMiqote + | CodeFlag.OopsRoegadyn + | CodeFlag.OopsAuRa + | CodeFlag.OopsHrothgar; + + public const CodeFlag SizeCodes = CodeFlag.Dwarf | CodeFlag.Giant; + + private CodeFlag _enabled; + + public bool Enabled(CodeFlag flag) + => _enabled.HasFlag(flag); + + public bool AnyEnabled(CodeFlag flag) + => (_enabled & flag) != 0; + + public CodeFlag Masked(CodeFlag mask) + => _enabled & mask; + + public Race GetRace() + => (_enabled & RaceCodes) switch + { + CodeFlag.OopsHyur => Race.Hyur, + CodeFlag.OopsElezen => Race.Elezen, + CodeFlag.OopsLalafell => Race.Lalafell, + CodeFlag.OopsMiqote => Race.Miqote, + CodeFlag.OopsRoegadyn => Race.Roegadyn, + CodeFlag.OopsAuRa => Race.AuRa, + CodeFlag.OopsHrothgar => Race.Hrothgar, + CodeFlag.OopsViera => Race.Viera, + _ => Race.Unknown, + }; public CodeService(Configuration config) { @@ -69,92 +112,94 @@ public class CodeService } public Action? CheckCode(string name) + { + var flag = GetCode(name); + if (flag == 0) + return null; + + var badFlags = ~GetMutuallyExclusive(flag); + return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag;; + } + + public CodeFlag GetCode(string name) { using var stream = new MemoryStream(Encoding.UTF8.GetBytes(name)); var sha = (ReadOnlySpan)_hasher.ComputeHash(stream); - return sha switch - { - _ when CodeClown.SequenceEqual(sha) => v => EnabledClown = v, - _ when CodeEmperor.SequenceEqual(sha) => v => EnabledEmperor = v, - _ when CodeIndividual.SequenceEqual(sha) => v => EnabledIndividual = v, - _ when CodeCaptain.SequenceEqual(sha) => v => EnabledCaptain = v, - _ when Code63.SequenceEqual(sha) => v => Enabled63 = v, - _ when CodeDwarf.SequenceEqual(sha) => v => EnabledSizing = v ? Sizing.Dwarf : Sizing.None, - _ when CodeGiant.SequenceEqual(sha) => v => EnabledSizing = v ? Sizing.Giant : Sizing.None, - _ when CodeOops1.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hyur : Race.Unknown, - _ when CodeOops2.SequenceEqual(sha) => v => EnabledOops = v ? Race.Elezen : Race.Unknown, - _ when CodeOops3.SequenceEqual(sha) => v => EnabledOops = v ? Race.Lalafell : Race.Unknown, - _ when CodeOops4.SequenceEqual(sha) => v => EnabledOops = v ? Race.Miqote : Race.Unknown, - _ when CodeOops5.SequenceEqual(sha) => v => EnabledOops = v ? Race.Roegadyn : Race.Unknown, - _ when CodeOops6.SequenceEqual(sha) => v => EnabledOops = v ? Race.AuRa : Race.Unknown, - _ when CodeOops7.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hrothgar : Race.Unknown, - _ when CodeOops8.SequenceEqual(sha) => v => EnabledOops = v ? Race.Viera : Race.Unknown, - _ when CodeArtisan.SequenceEqual(sha) => v => EnabledArtisan = v, - _ when CodeShirts.SequenceEqual(sha) => v => EnabledShirts = v, - _ when CodeWorld.SequenceEqual(sha) => v => EnabledWorld = v, - _ => null, - }; + foreach (var flag in Enum.GetValues()) + { + if (sha.SequenceEqual(GetSha(flag))) + return flag; + } + + return 0; } - public void VerifyState() + /// Update all enabled states in the config. + public void SaveState() { - if (EnabledSizing == Sizing.None && EnabledOops == Race.Unknown) - return; - for (var i = 0; i < _config.Codes.Count; ++i) { - var (code, enabled) = _config.Codes[i]; - if (!enabled) - continue; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(code)); - var sha = (ReadOnlySpan)_hasher.ComputeHash(stream); - var _ = EnabledSizing switch + var name = _config.Codes[i].Code; + var flag = GetCode(name); + if (flag == 0) { - Sizing.Dwarf when sha.SequenceEqual(CodeGiant) => _config.Codes[i] = (code, false), - Sizing.Giant when sha.SequenceEqual(CodeDwarf) => _config.Codes[i] = (code, false), - _ => (string.Empty, false), - }; + _config.Codes.RemoveAt(i--); + continue; + } - var race = OopsRace(sha); - if (race is not Race.Unknown && race != EnabledOops) - _config.Codes[i] = (code, false); + _config.Codes[i] = (name, Enabled(flag)); } + + _config.Save(); } - private static Race OopsRace(ReadOnlySpan sha) - => sha switch - { - _ when CodeOops1.SequenceEqual(sha) => Race.Hyur, - _ when CodeOops2.SequenceEqual(sha) => Race.Elezen, - _ when CodeOops3.SequenceEqual(sha) => Race.Lalafell, - _ when CodeOops4.SequenceEqual(sha) => Race.Miqote, - _ when CodeOops5.SequenceEqual(sha) => Race.Roegadyn, - _ when CodeOops6.SequenceEqual(sha) => Race.AuRa, - _ when CodeOops7.SequenceEqual(sha) => Race.Hrothgar, - _ when CodeOops8.SequenceEqual(sha) => Race.Viera, - _ => Race.Unknown, - }; - // @formatter:off - private static ReadOnlySpan CodeClown => new byte[] { 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD }; - private static ReadOnlySpan CodeEmperor => new byte[] { 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 }; - private static ReadOnlySpan CodeIndividual => new byte[] { 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 }; - private static ReadOnlySpan CodeDwarf => new byte[] { 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 }; - private static ReadOnlySpan CodeGiant => new byte[] { 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE }; - private static ReadOnlySpan CodeOops1 => new byte[] { 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF }; - private static ReadOnlySpan CodeOops2 => new byte[] { 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 }; - private static ReadOnlySpan CodeOops3 => new byte[] { 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E }; - private static ReadOnlySpan CodeOops4 => new byte[] { 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 }; - private static ReadOnlySpan CodeOops5 => new byte[] { 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 }; - private static ReadOnlySpan CodeOops6 => new byte[] { 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 }; - private static ReadOnlySpan CodeOops7 => new byte[] { 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 }; - private static ReadOnlySpan CodeOops8 => new byte[] { 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B }; - private static ReadOnlySpan CodeArtisan => new byte[] { 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 }; - private static ReadOnlySpan CodeCaptain => new byte[] { 0x5E, 0x0B, 0xDD, 0x86, 0x8F, 0x24, 0xDA, 0x49, 0x1A, 0xD2, 0x59, 0xB9, 0x10, 0x38, 0x29, 0x37, 0x99, 0x9D, 0x53, 0xD9, 0x9B, 0x84, 0x91, 0x5B, 0x6C, 0xCE, 0x3E, 0x2A, 0x38, 0x06, 0x47, 0xE6 }; - private static ReadOnlySpan Code63 => new byte[] { 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 }; - private static ReadOnlySpan CodeShirts => new byte[] { 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB }; - private static ReadOnlySpan CodeWorld => new byte[] { 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 }; - // @formatter:on + private static CodeFlag GetMutuallyExclusive(CodeFlag flag) + => flag switch + { + CodeFlag.Clown => DyeCodes & ~CodeFlag.Clown, + CodeFlag.Emperor => GearCodes & ~CodeFlag.Emperor, + CodeFlag.Individual => 0, + CodeFlag.Dwarf => SizeCodes & ~CodeFlag.Dwarf, + CodeFlag.Giant => SizeCodes & ~CodeFlag.Giant, + CodeFlag.OopsHyur => RaceCodes & ~CodeFlag.OopsHyur, + CodeFlag.OopsElezen => RaceCodes & ~CodeFlag.OopsElezen, + CodeFlag.OopsLalafell => RaceCodes & ~CodeFlag.OopsLalafell, + CodeFlag.OopsMiqote => RaceCodes & ~CodeFlag.OopsMiqote, + CodeFlag.OopsRoegadyn => RaceCodes & ~CodeFlag.OopsRoegadyn, + CodeFlag.OopsAuRa => RaceCodes & ~CodeFlag.OopsAuRa, + CodeFlag.OopsHrothgar => RaceCodes & ~CodeFlag.OopsHrothgar, + CodeFlag.OopsViera => RaceCodes & ~CodeFlag.OopsViera, + CodeFlag.Artisan => 0, + CodeFlag.SixtyThree => 0, + CodeFlag.Shirts => 0, + CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World, + CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants, + _ => 0, + }; + + private static ReadOnlySpan GetSha(CodeFlag flag) + => flag switch + { + CodeFlag.Clown => [ 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD ], + CodeFlag.Emperor => [ 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 ], + CodeFlag.Individual => [ 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 ], + CodeFlag.Dwarf => [ 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 ], + CodeFlag.Giant => [ 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE ], + CodeFlag.OopsHyur => [ 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF ], + CodeFlag.OopsElezen => [ 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 ], + CodeFlag.OopsLalafell => [ 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E ], + CodeFlag.OopsMiqote => [ 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 ], + CodeFlag.OopsRoegadyn => [ 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 ], + CodeFlag.OopsAuRa => [ 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 ], + CodeFlag.OopsHrothgar => [ 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 ], + CodeFlag.OopsViera => [ 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B ], + CodeFlag.Artisan => [ 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 ], + CodeFlag.SixtyThree => [ 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 ], + CodeFlag.Shirts => [ 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB ], + CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], + CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], + _ => [], + }; } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 52709c5..6e73945 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -21,7 +21,7 @@ public class ItemManager public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; - public readonly DictStain Stains; + public readonly DictStain Stains; public readonly ItemData ItemData; public readonly RestrictedGear RestrictedGear; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index deb7771..9e0ffa4 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -8,6 +8,7 @@ using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; using ImGuiNET; +using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -25,18 +26,18 @@ public unsafe class FunModule : IDisposable AprilFirst, } - private readonly WorldSets _worldSets = new(); - private readonly ItemManager _items; - private readonly CustomizeService _customizations; - private readonly Configuration _config; - private readonly CodeService _codes; - private readonly Random _rng; - private readonly GenericPopupWindow _popupWindow; - private readonly StateManager _stateManager; - private readonly DesignConverter _designConverter; - private readonly DesignManager _designManager; - private readonly ObjectManager _objects; - private readonly StainId[] _stains; + private readonly WorldSets _worldSets = new(); + private readonly ItemManager _items; + private readonly CustomizeService _customizations; + private readonly Configuration _config; + private readonly CodeService _codes; + private readonly Random _rng; + private readonly GenericPopupWindow _popupWindow; + private readonly StateManager _stateManager; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; + private readonly ObjectManager _objects; + private readonly StainId[] _stains; public FestivalType CurrentFestival { get; private set; } = FestivalType.None; private FunEquipSet? _festivalSet; @@ -89,54 +90,92 @@ public unsafe class FunModule : IDisposable public void Dispose() => DayChangeTracker.DayChanged -= OnDayChange; - public void ApplyFun(Actor actor, ref CharacterArmor armor, EquipSlot slot) + private bool IsInFestival + => _config.DisableFestivals == 0 && _festivalSet != null; + + public void ApplyFunToSlot(Actor actor, ref CharacterArmor armor, EquipSlot slot) { if (!ValidFunTarget(actor)) return; - if (_config.DisableFestivals == 0 && _festivalSet != null - || _codes.EnabledWorld && actor.Index != 0) + if (IsInFestival) { - armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : armor; + KeepOldArmor(actor, slot, ref armor); + return; } - else + + switch (_codes.Masked(CodeService.GearCodes)) { - ApplyEmperor(new Span(ref armor), slot); - ApplyClown(new Span(ref armor)); + case CodeService.CodeFlag.Emperor: + SetRandomItem(slot, ref armor); + break; + case CodeService.CodeFlag.Elephants: + SetElephant(slot, ref armor); + break; + case CodeService.CodeFlag.World when actor.Index != 0: + KeepOldArmor(actor, slot, ref armor); + break; + } + + switch (_codes.Masked(CodeService.DyeCodes)) + { + case CodeService.CodeFlag.Clown: + SetRandomDye(ref armor); + break; } } - public void ApplyFun(Actor actor, Span armor, ref CustomizeArray customize) + public void ApplyFunOnLoad(Actor actor, Span armor, ref CustomizeArray customize) { if (!ValidFunTarget(actor)) return; - if (_config.DisableFestivals == 0 && _festivalSet != null) + // First set the race, if any. + SetRace(ref customize); + // Now apply the gender. + SetGender(ref customize); + // Randomize customizations inside the race and gender combo. + RandomizeCustomize(ref customize); + // Finally, apply forced sizes. + SetSize(actor, ref customize); + + // Apply the festival gear with priority over all gear codes. + if (IsInFestival) { - _festivalSet.Apply(_stains, _rng, armor); - } - else if (_codes.EnabledWorld && actor.Index != 0) - { - _worldSets.Apply(actor, _rng, armor); - } - else - { - ApplyEmperor(armor); - ApplyClown(armor); + _festivalSet!.Apply(_stains, _rng, armor); + return; } - ApplyOops(ref customize); - Apply63(ref customize); - ApplyIndividual(ref customize); - ApplySizing(actor, ref customize); + switch (_codes.Masked(CodeService.GearCodes)) + { + case CodeService.CodeFlag.Emperor: + foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) + SetRandomItem(slot, ref armor[idx]); + break; + case CodeService.CodeFlag.Elephants: + SetElephant(EquipSlot.Body, ref armor[1]); + SetElephant(EquipSlot.Head, ref armor[0]); + break; + case CodeService.CodeFlag.World when actor.Index != 0: + _worldSets.Apply(actor, _rng, armor); + break; + } + + switch (_codes.Masked(CodeService.DyeCodes)) + { + case CodeService.CodeFlag.Clown: + foreach (ref var piece in armor) + SetRandomDye(ref piece); + break; + } } - public void ApplyFun(Actor actor, ref CharacterWeapon weapon, EquipSlot slot) + public void ApplyFunToWeapon(Actor actor, ref CharacterWeapon weapon, EquipSlot slot) { if (!ValidFunTarget(actor)) return; - if (_codes.EnabledWorld) + if (_codes.Enabled(CodeService.CodeFlag.World) && actor.Index != 0) _worldSets.Apply(actor, _rng, ref weapon, slot); } @@ -146,55 +185,58 @@ public unsafe class FunModule : IDisposable && !actor.IsTransformed && actor.AsCharacter->CharacterData.ModelCharaId == 0; - public void ApplyClown(Span armors) - { - if (!_codes.EnabledClown) - return; + private static void KeepOldArmor(Actor actor, EquipSlot slot, ref CharacterArmor armor) + => armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : armor; - foreach (ref var armor in armors) - { - var stainIdx = _rng.Next(0, _stains.Length - 1); - armor.Stain = _stains[stainIdx]; - } + private void SetRandomDye(ref CharacterArmor armor) + { + var stainIdx = _rng.Next(0, _stains.Length - 1); + armor.Stain = _stains[stainIdx]; } - public void ApplyEmperor(Span armors, EquipSlot slot = EquipSlot.Unknown) + private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor) { - if (!_codes.EnabledEmperor) - return; - - if (armors.Length == 1) - SetItem(slot, ref armors[0]); - else - for (var i = 0u; i < armors.Length; ++i) - SetItem(i.ToEquipSlot(), ref armors[(int)i]); - return; - - void SetItem(EquipSlot slot2, ref CharacterArmor armor) - { - var list = _items.ItemData.ByType[slot2.ToEquipType()]; - var rng = _rng.Next(0, list.Count - 1); - var item = list[rng]; - armor.Set = item.PrimaryId; - armor.Variant = item.Variant; - } + var list = _items.ItemData.ByType[slot.ToEquipType()]; + var rng = _rng.Next(0, list.Count - 1); + var item = list[rng]; + armor.Set = item.PrimaryId; + armor.Variant = item.Variant; } - public void ApplyOops(ref CustomizeArray customize) + private void SetElephant(EquipSlot slot, ref CharacterArmor armor) { - if (_codes.EnabledOops == Race.Unknown) + armor = slot switch + { + EquipSlot.Body => new CharacterArmor(6133, 1, 87), + EquipSlot.Head => new CharacterArmor(6133, 1, 87), + _ => armor, + }; + } + + private void SetRace(ref CustomizeArray customize) + { + var race = _codes.GetRace(); + if (race == Race.Unknown) return; - var targetClan = (SubRace)((int)_codes.EnabledOops * 2 - (int)customize.Clan % 2); + var targetClan = (SubRace)((int)race * 2 - (int)customize.Clan % 2); // TODO Female Hrothgar - if (_codes.EnabledOops is Race.Hrothgar && customize.Gender is Gender.Female) + if (race is Race.Hrothgar && customize.Gender is Gender.Female) targetClan = targetClan is SubRace.Lost ? SubRace.Seawolf : SubRace.Hellsguard; _customizations.ChangeClan(ref customize, targetClan); } - public void ApplyIndividual(ref CustomizeArray customize) + private void SetGender(ref CustomizeArray customize) { - if (!_codes.EnabledIndividual) + if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree) || customize.Race is Race.Hrothgar) // TODO Female Hrothgar + return; + + _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); + } + + private void RandomizeCustomize(ref CustomizeArray customize) + { + if (!_codes.Enabled(CodeService.CodeFlag.Individual)) return; var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender); @@ -208,27 +250,18 @@ public unsafe class FunModule : IDisposable } } - public void Apply63(ref CustomizeArray customize) + private void SetSize(Actor actor, ref CustomizeArray customize) { - if (!_codes.Enabled63 || customize.Race is Race.Hrothgar) // TODO Female Hrothgar - return; - - _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); - } - - public void ApplySizing(Actor actor, ref CustomizeArray customize) - { - if (_codes.EnabledSizing == CodeService.Sizing.None) - return; - - var size = _codes.EnabledSizing switch + var size = _codes.Masked(CodeService.SizeCodes) switch { - CodeService.Sizing.Dwarf when actor.Index == 0 => 0, - CodeService.Sizing.Dwarf when actor.Index != 0 => 100, - CodeService.Sizing.Giant when actor.Index == 0 => 100, - CodeService.Sizing.Giant when actor.Index != 0 => 0, - _ => 0, + CodeService.CodeFlag.Dwarf when actor.Index == 0 => (byte)0, + CodeService.CodeFlag.Dwarf => (byte)100, + CodeService.CodeFlag.Giant when actor.Index == 0 => (byte)100, + CodeService.CodeFlag.Giant => (byte)0, + _ => byte.MaxValue, }; + if (size == byte.MaxValue) + return; if (customize.Gender is Gender.Female) customize[CustomizeIndex.BustSize] = (CustomizeValue)size; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 1f3c36b..c4c5fb1 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -133,7 +133,7 @@ public class StateListener : IDisposable _creatingState.TempUnlock(); } - _funModule.ApplyFun(actor, new Span((void*)equipDataPtr, 10), ref customize); + _funModule.ApplyFunOnLoad(actor, new Span((void*)equipDataPtr, 10), ref customize); if (modelId == 0) ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } @@ -216,7 +216,7 @@ public class StateListener : IDisposable locked = state[slot, false] is StateChanged.Source.Ipc; } - _funModule.ApplyFun(actor, ref armor.Value, slot); + _funModule.ApplyFunToSlot(actor, ref armor.Value, slot); if (!_config.UseRestrictedGearProtection || locked) return; @@ -315,7 +315,7 @@ public class StateListener : IDisposable _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.Skeleton.Id + 50), weapon.Value.Weapon, weapon.Value.Variant, weapon.Value.Stain); - _funModule.ApplyFun(actor, ref weapon.Value, slot); + _funModule.ApplyFunToWeapon(actor, ref weapon.Value, slot); } /// Update base data for a single changed equipment slot. From 4b242bb3cfef576c1c3cd70942ddfa0108a6e8c0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Dec 2023 14:20:59 +0100 Subject: [PATCH 15/31] Change design loading. --- Glamourer/Designs/DesignManager.cs | 51 ++++++++++++++++++++---------- OtterGui | 2 +- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 80cd0e0..b46e4e0 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Dalamud.Utility; using Glamourer.Events; using Glamourer.Interop.Penumbra; @@ -18,7 +22,7 @@ namespace Glamourer.Designs; public class DesignManager { - private readonly CustomizeService _customizations; + private readonly CustomizeService _customizations; private readonly ItemManager _items; private readonly HumanModelList _humans; private readonly SaveService _saveService; @@ -48,29 +52,44 @@ public class DesignManager /// public void LoadDesigns() { + _humans.Awaiter.Wait(); + _customizations.Awaiter.Wait(); + _items.ItemData.Awaiter.Wait(); + + var stopwatch = Stopwatch.StartNew(); _designs.Clear(); - List<(Design, string)> invalidNames = new(); - var skipped = 0; - foreach (var file in _saveService.FileNames.Designs()) + var skipped = 0; + ThreadLocal> designs = new(() => [], true); + Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) => { try { - var text = File.ReadAllText(file.FullName); + var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); var design = Design.LoadDesign(_customizations, _items, data); - if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name)) - invalidNames.Add((design, file.FullName)); - if (_designs.Any(f => f.Identifier == design.Identifier)) - throw new Exception($"Identifier {design.Identifier} was not unique."); - - design.Index = _designs.Count; - _designs.Add(design); + designs.Value!.Add((design, f.FullName)); } catch (Exception ex) { Glamourer.Log.Error($"Could not load design, skipped:\n{ex}"); - ++skipped; + Interlocked.Increment(ref skipped); } + }); + + List<(Design, string)> invalidNames = []; + foreach (var (design, path) in designs.Values.SelectMany(v => v)) + { + if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(path)) + invalidNames.Add((design, path)); + if (_designs.Any(d => d.Identifier == design.Identifier)) + { + Glamourer.Log.Error($"Could not load design, skipped: Identifier {design.Identifier} was not unique."); + ++skipped; + continue; + } + + design.Index = _designs.Count; + _designs.Add(design); } var failed = MoveInvalidNames(invalidNames); @@ -79,7 +98,7 @@ public class DesignManager $"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}"); Glamourer.Log.Information( - $"Loaded {_designs.Count} designs.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); + $"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); _event.Invoke(DesignChanged.Type.ReloadedAll, null!); } @@ -191,10 +210,10 @@ public class DesignManager public void ChangeColor(Design design, string newColor) { var oldColor = design.Color; - if (oldColor == newColor) + if (oldColor == newColor) return; - design.Color = newColor; + design.Color = newColor; design.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(design); Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); diff --git a/OtterGui b/OtterGui index 4df65fb..15203ed 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4df65fb330f3746b7836c39cb96d1e36a53bcec0 +Subproject commit 15203edf1dba72713f508b798048c56ad969fb95 From 0c1dd50890326189447e3012bdd5b2b7ce5f970e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Dec 2023 19:01:28 +0100 Subject: [PATCH 16/31] Add NPC appearance tab. --- Glamourer/Designs/DesignColors.cs | 15 +- Glamourer/Designs/DesignConverter.cs | 19 +- Glamourer/GameData/NpcCustomizeSet.cs | 13 +- Glamourer/Gui/Colors.cs | 6 +- .../CustomizationDrawer.GenderRace.cs | 7 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 1 + Glamourer/Gui/MainWindow.cs | 10 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 9 +- .../Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs | 141 +++++++++ Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs | 45 +++ Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 294 ++++++++++++++++++ Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs | 95 ++++++ Glamourer/Gui/Tabs/NpcTab/NpcTab.cs | 19 ++ Glamourer/Gui/ToggleDrawData.cs | 19 ++ Glamourer/Gui/UiHelpers.cs | 16 +- Glamourer/Services/FilenameService.cs | 3 +- Glamourer/Services/ServiceManager.cs | 5 + OtterGui | 2 +- 18 files changed, 684 insertions(+), 35 deletions(-) create mode 100644 Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcTab.cs diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index dc36e78..2a4f3f7 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -26,9 +26,9 @@ public class DesignColorUi public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config) { - _colors = colors; - _designs = designs; - _config = config; + _colors = colors; + _designs = designs; + _config = config; } public void Draw() @@ -78,7 +78,7 @@ public class DesignColorUi { using var id = ImRaii.PushId(idx); ImGui.TableNextColumn(); - + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, tt, disabled, true)) { changeString = name; @@ -119,7 +119,7 @@ public class DesignColorUi changeString = _newName; changeValue = 0xFFFFFFFF; } - + if (changeString.Length > 0) { @@ -139,6 +139,7 @@ public class DesignColorUi newColor = color; return false; } + ImGuiUtil.HoverTooltip(tooltip); newColor = ImGui.ColorConvertFloat4ToU32(vec); @@ -148,12 +149,12 @@ public class DesignColorUi public class DesignColors : ISavable, IReadOnlyDictionary { - public const string AutomaticName = "Automatic"; + public const string AutomaticName = "Automatic"; public const string MissingColorName = "Missing Color"; public const uint MissingColorDefault = 0xFF0000D0; private readonly SaveService _saveService; - private readonly Dictionary _colors = new(); + private readonly Dictionary _colors = []; public uint MissingColor { get; private set; } = MissingColorDefault; public event Action? ColorChanged; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index f7867c4..cd8924f 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -39,12 +39,18 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All); public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + => ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags); + + public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { - var design = Convert(state, equipFlags, customizeFlags, crestFlags); + var design = Convert(data, equipFlags, customizeFlags, crestFlags); return ShareBase64(ShareJObject(design)); } public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + => Convert(state.ModelData, equipFlags, customizeFlags, crestFlags); + + public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { var design = _designs.CreateTemporary(); design.ApplyEquip = equipFlags & EquipFlagExtensions.All; @@ -54,7 +60,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); design.SetApplyWetness(true); - design.SetDesignData(_customize, state.ModelData); + design.SetDesignData(_customize, data); return design; } @@ -144,7 +150,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi } public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList armors, - CharacterWeapon mainhand, CharacterWeapon offhand) + CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings) { if (armors.Count != 10) throw new ArgumentException("Invalid length of armor array."); @@ -156,7 +162,8 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var item = _items.Identify(slot, armor.Set, armor.Variant); if (!item.Valid) { - Glamourer.Log.Warning($"Appearance data {armor} for slot {slot} invalid, item could not be identified."); + if (!skipWarnings) + Glamourer.Log.Warning($"Appearance data {armor} for slot {slot} invalid, item could not be identified."); item = ItemManager.NothingItem(slot); } @@ -164,7 +171,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi } var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); - if (!mh.Valid) + if (!skipWarnings && !mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); mh = _items.DefaultSword; @@ -173,7 +180,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (EquipSlot.MainHand, mh, mainhand.Stain); var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); - if (!oh.Valid) + if (!skipWarnings && !oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); oh = _items.GetDefaultOffhand(mh); diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index bcd6c54..d364c1e 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -161,7 +161,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList // Convert the NPCs to a dictionary of lists grouped by name. var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); // Iterate through the sorted list. - foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) + foreach (var (_, duplicates) in groups.OrderBy(kvp => kvp.Key)) { // Remove any duplicate entries for a name with identical data. for (var i = 0; i < duplicates.Count; ++i) @@ -177,7 +177,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } } - // If there is only a single entry, add that. This does not take additional string memory through interning. + // If there is only a single entry, add that. if (duplicates.Count == 1) { _data.Add(duplicates[0]); @@ -185,13 +185,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } else { - // Add all distinct duplicates with their ID specified in the name. - _data.AddRange(duplicates - .Select(duplicate => duplicate with - { - Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})", - })); - Memory += 96 * duplicates.Count + duplicates.Sum(d => d.Name.Length * 2); + _data.AddRange(duplicates); + Memory += 96 * duplicates.Count; } } diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 873ec38..7addc45 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -28,6 +28,8 @@ public enum ColorId TriStateCheck, TriStateCross, TriStateNeutral, + BattleNpc, + EventNpc, } public static class Colors @@ -60,7 +62,9 @@ public static class Colors ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), - ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes" ), + ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ), + ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ), + ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ), _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 4b9fab7..a50424c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -82,8 +82,11 @@ public partial class CustomizationDrawer if (_customize.BodyType.Value == 1) return; - if (!ImGuiUtil.DrawDisabledButton($"Reset Body Type {_customize.BodyType.Value} to Default", - new Vector2(_raceSelectorWidth + _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X, 0), string.Empty, _lockedRedraw)) + var label = _lockedRedraw + ? $"Body Type {_customize.BodyType.Value}" + : $"Reset Body Type {_customize.BodyType.Value} to Default"; + if (!ImGuiUtil.DrawDisabledButton(label, new Vector2(_raceSelectorWidth + _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X, 0), + string.Empty, _lockedRedraw)) return; Changed |= CustomizeFlag.BodyType; diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 8ea3972..1edd4ce 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -1,6 +1,7 @@ using System; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.Services; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index a219134..565ef84 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -10,6 +10,7 @@ using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; +using Glamourer.Gui.Tabs.NpcTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; using OtterGui.Custom; @@ -29,6 +30,7 @@ public class MainWindow : Window, IDisposable Automation = 4, Unlocks = 5, Messages = 6, + Npcs = 7, } private readonly Configuration _config; @@ -42,12 +44,14 @@ public class MainWindow : Window, IDisposable public readonly DesignTab Designs; public readonly AutomationTab Automation; public readonly UnlocksTab Unlocks; + public readonly NpcTab Npcs; public readonly MessagesTab Messages; public TabType SelectTab = TabType.None; public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, - DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar) + DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar, + NpcTab npcs) : base(GetLabel()) { pi.UiBuilder.DisableGposeUiHide = true; @@ -65,6 +69,7 @@ public class MainWindow : Window, IDisposable _event = @event; Messages = messages; _quickBar = quickBar; + Npcs = npcs; _config = config; _tabs = [ @@ -73,6 +78,7 @@ public class MainWindow : Window, IDisposable designs, automation, unlocks, + npcs, messages, debugTab, ]; @@ -117,6 +123,7 @@ public class MainWindow : Window, IDisposable TabType.Automation => Automation.Label, TabType.Unlocks => Unlocks.Label, TabType.Messages => Messages.Label, + TabType.Npcs => Npcs.Label, _ => ReadOnlySpan.Empty, }; @@ -128,6 +135,7 @@ public class MainWindow : Window, IDisposable if (label == Settings.Label) return TabType.Settings; if (label == Automation.Label) return TabType.Automation; if (label == Unlocks.Label) return TabType.Unlocks; + if (label == Npcs.Label) return TabType.Npcs; if (label == Messages.Label) return TabType.Messages; if (label == Debug.Label) return TabType.Debug; // @formatter:on diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index e0c7aa8..2ebf215 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -35,7 +35,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); - using var table = ImRaii.Table("npcs", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, + using var table = ImRaii.Table("npcs", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); if (!table) return; @@ -46,6 +46,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch); @@ -66,7 +67,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) { - foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand)) + foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) _state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual); _state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual); _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual); @@ -80,6 +81,10 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(data.Id.Id.ToString()); + using (_ = ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs new file mode 100644 index 0000000..bb22a85 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Designs; +using Glamourer.GameData; +using Glamourer.Services; +using ImGuiNET; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class LocalNpcAppearanceData : ISavable +{ + private readonly DesignColors _colors; + + public record struct Data(string Color = "", bool Favorite = false); + + private readonly Dictionary _data = []; + + public LocalNpcAppearanceData(DesignColors colors, SaveService saveService) + { + _colors = colors; + Load(saveService); + DataChanged += () => saveService.QueueSave(this); + } + + public bool IsFavorite(in NpcData data) + => _data.TryGetValue(ToKey(data), out var tuple) && tuple.Favorite; + + public (uint Color, bool Favorite) GetData(in NpcData data) + => _data.TryGetValue(ToKey(data), out var t) + ? (GetColor(t.Color, t.Favorite, data.Kind), t.Favorite) + : (GetColor(string.Empty, false, data.Kind), false); + + public string GetColor(in NpcData data) + => _data.TryGetValue(ToKey(data), out var t) ? t.Color : string.Empty; + + private uint GetColor(string color, bool favorite, ObjectKind kind) + { + if (color.Length == 0) + { + if (favorite) + return ColorId.FavoriteStarOn.Value(); + + return kind is ObjectKind.BattleNpc + ? ColorId.BattleNpc.Value() + : ColorId.EventNpc.Value(); + } + + if (_colors.TryGetValue(color, out var value)) + return value == 0 ? ImGui.GetColorU32(ImGuiCol.Text) : value; + + return _colors.MissingColor; + } + + public void ToggleFavorite(in NpcData data) + { + var key = ToKey(data); + if (_data.TryGetValue(key, out var t)) + { + if (t is { Color: "", Favorite: true }) + _data.Remove(key); + else + _data[key] = t with { Favorite = !t.Favorite }; + } + else + { + _data[key] = new Data(string.Empty, true); + } + + DataChanged.Invoke(); + } + + public void SetColor(in NpcData data, string color) + { + var key = ToKey(data); + if (_data.TryGetValue(key, out var t)) + { + if (!t.Favorite && color.Length == 0) + _data.Remove(key); + else + _data[key] = t with { Color = color }; + } + else if (color.Length != 0) + { + _data[key] = new Data(color); + } + + DataChanged.Invoke(); + } + + private static ulong ToKey(in NpcData data) + => (byte)data.Kind | ((ulong)data.Id.Id << 8); + + public event Action DataChanged = null!; + + public string ToFilename(FilenameService fileNames) + => fileNames.NpcAppearanceFile; + + public void Save(StreamWriter writer) + { + var jObj = new JObject() + { + ["Version"] = 1, + ["Data"] = JToken.FromObject(_data), + }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; + jObj.WriteTo(j); + } + + private void Load(SaveService save) + { + var file = save.FileNames.NpcAppearanceFile; + if (!File.Exists(file)) + return; + + try + { + var text = File.ReadAllText(file); + var jObj = JObject.Parse(text); + var version = jObj["Version"]?.ToObject() ?? 0; + switch (version) + { + case 1: + var data = jObj["Data"]?.ToObject>() ?? []; + _data.EnsureCapacity(data.Count); + foreach (var kvp in data) + _data.Add(kvp.Key, kvp.Value); + return; + default: throw new Exception("Invalid version {version}."); + } + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not read local NPC appearance data:\n{ex}"); + } + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs new file mode 100644 index 0000000..f57985b --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs @@ -0,0 +1,45 @@ +using System; +using Glamourer.Designs; +using Glamourer.GameData; +using OtterGui.Classes; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public sealed class NpcFilter(LocalNpcAppearanceData _favorites) : FilterUtility +{ + protected override string Tooltip + => "Filter NPC appearances for those where their names contain the given substring.\n" + + "Enter i:[number] to filter for NPCs of certain IDs.\n" + + "Enter c:[string] to filter for NPC appearances set to specific colors."; + + protected override (LowerString, long, int) FilterChange(string input) + => input.Length switch + { + 0 => (LowerString.Empty, 0, -1), + > 1 when input[1] == ':' => + input[0] switch + { + 'i' or 'I' => input.Length == 2 ? (LowerString.Empty, 0, -1) : + long.TryParse(input.AsSpan(2), out var r) ? (LowerString.Empty, r, 1) : (LowerString.Empty, 0, -1), + 'c' or 'C' => input.Length == 2 ? (LowerString.Empty, 0, -1) : (new LowerString(input[2..]), 0, 2), + _ => (new LowerString(input), 0, 0), + }, + _ => (new LowerString(input), 0, 0), + }; + + public override bool ApplyFilter(in NpcData value) + => FilterMode switch + { + -1 => false, + 0 => Filter.IsContained(value.Name), + 1 => value.Id.Id == NumericalFilter, + 2 => Filter.IsContained(GetColor(value)), + _ => false, // Should never happen + }; + + private string GetColor(in NpcData value) + { + var color = _favorites.GetColor(value); + return color.Length == 0 ? DesignColors.AutomaticName : color; + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs new file mode 100644 index 0000000..cd88f97 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -0,0 +1,294 @@ +using System; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Internal.Notifications; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Gui.Customization; +using Glamourer.Gui.Equipment; +using Glamourer.Gui.Tabs.DesignTab; +using Glamourer.Interop; +using Glamourer.State; +using ImGuiNET; +using Lumina.Data.Parsing.Scd; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Raii; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class NpcPanel( + NpcSelector _selector, + LocalNpcAppearanceData _favorites, + CustomizationDrawer _customizeDrawer, + EquipmentDrawer _equipDrawer, + DesignConverter _converter, + DesignManager _designManager, + StateManager _state, + ObjectManager _objects, + DesignColors _colors) +{ + private readonly DesignColorCombo _colorCombo = new(_colors, true); + private string _newName = string.Empty; + private DesignBase? _newDesign; + + public void Draw() + { + using var group = ImRaii.Group(); + + DrawHeader(); + DrawPanel(); + } + + private void DrawHeader() + { + HeaderDrawer.Draw(_selector.HasSelection ? _selector.Selection.Name : "No Selection", ColorId.NormalDesign.Value(), + ImGui.GetColorU32(ImGuiCol.FrameBg), 2, ExportToClipboardButton(), SaveAsDesignButton(), FavoriteButton()); + SaveDesignDrawPopup(); + } + + private HeaderDrawer.Button FavoriteButton() + { + var (desc, color) = _favorites.IsFavorite(_selector.Selection) + ? ("Remove this NPC appearance from your favorites.", ColorId.FavoriteStarOn.Value()) + : ("Add this NPC Appearance to your favorites.", 0x80000000); + return new HeaderDrawer.Button + { + Icon = FontAwesomeIcon.Star, + OnClick = () => _favorites.ToggleFavorite(_selector.Selection), + Visible = _selector.HasSelection, + Description = desc, + TextColor = color, + }; + } + + private HeaderDrawer.Button ExportToClipboardButton() + => new() + { + Description = + "Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design.", + Icon = FontAwesomeIcon.Copy, + OnClick = ExportToClipboard, + Visible = _selector.HasSelection, + }; + + private HeaderDrawer.Button SaveAsDesignButton() + => new() + { + Description = + "Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design.", + Icon = FontAwesomeIcon.Save, + OnClick = SaveDesignOpen, + Visible = _selector.HasSelection, + }; + + private void ExportToClipboard() + { + try + { + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var data = ToDesignData(); + var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {_selector.Selection.Name}'s data to clipboard.", + $"Could not copy data from NPC appearance {_selector.Selection.Kind} {_selector.Selection.Id.Id} to clipboard", + NotificationType.Error); + } + } + + private void SaveDesignOpen() + { + ImGui.OpenPopup("Save as Design"); + _newName = _selector.Selection.Name; + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + + var data = ToDesignData(); + _newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest); + } + + private void SaveDesignDrawPopup() + { + if (!ImGuiUtil.OpenNameField("Save as Design", ref _newName)) + return; + + if (_newDesign != null && _newName.Length > 0) + _designManager.CreateClone(_newDesign, _newName, true); + _newDesign = null; + _newName = string.Empty; + } + + private void DrawPanel() + { + using var child = ImRaii.Child("##Panel", -Vector2.One, true); + if (!child || !_selector.HasSelection) + return; + + DrawButtonRow(); + DrawCustomization(); + DrawEquipment(); + DrawAppearanceInfo(); + } + + private void DrawButtonRow() + { + DrawApplyToSelf(); + ImGui.SameLine(); + DrawApplyToTarget(); + } + + private void DrawCustomization() + { + if (!ImGui.CollapsingHeader("Customization")) + return; + + _customizeDrawer.Draw(_selector.Selection.Customize, true, true); + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + } + + private void DrawEquipment() + { + if (!ImGui.CollapsingHeader("Equipment")) + return; + + _equipDrawer.Prepare(); + var designData = ToDesignData(); + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var data = new EquipDrawData(slot, designData) { Locked = true }; + _equipDrawer.DrawEquip(data); + } + + var mainhandData = new EquipDrawData(EquipSlot.MainHand, designData) { Locked = true }; + var offhandData = new EquipDrawData(EquipSlot.OffHand, designData) { Locked = true }; + _equipDrawer.DrawWeapons(mainhandData, offhandData, false); + + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(ActorState.MetaIndex.VisorState, _selector.Selection.VisorToggled)); + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + } + + private DesignData ToDesignData() + { + var selection = _selector.Selection; + var items = _converter.FromDrawData(selection.Equip.ToArray(), selection.Mainhand, selection.Offhand, true).ToArray(); + var designData = new DesignData { Customize = selection.Customize }; + foreach (var (slot, item, stain) in items) + { + designData.SetItem(slot, item); + designData.SetStain(slot, stain); + } + + return designData; + } + + private void DrawApplyToSelf() + { + var (id, data) = _objects.PlayerData; + if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, + "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", + !data.Valid)) + return; + + if (_state.GetOrCreate(id, data.Objects[0], out var state)) + { + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); + _state.ApplyDesign(design, state, StateChanged.Source.Manual); + } + } + + private void DrawApplyToTarget() + { + var (id, data) = _objects.TargetData; + var tt = id.IsValid + ? data.Valid + ? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations." + : "The current target can not be manipulated." + : "No valid target selected."; + if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid)) + return; + + if (_state.GetOrCreate(id, data.Objects[0], out var state)) + { + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); + _state.ApplyDesign(design, state, StateChanged.Source.Manual); + } + } + + + private void DrawAppearanceInfo() + { + if (!ImGui.CollapsingHeader("Appearance Details")) + return; + + using var table = ImRaii.Table("Details", 2); + if (!table) + return; + + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Last Update Datem").X); + ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch); + + var selection = _selector.Selection; + CopyButton("NPC Name", selection.Name); + CopyButton("NPC ID", selection.Id.Id.ToString()); + ImGuiUtil.DrawFrameColumn("NPC Type"); + ImGui.TableNextColumn(); + var width = ImGui.GetContentRegionAvail().X; + ImGuiUtil.DrawTextButton(selection.Kind is ObjectKind.BattleNpc ? "Battle NPC" : "Event NPC", new Vector2(width, 0), + ImGui.GetColorU32(ImGuiCol.FrameBg)); + + ImGuiUtil.DrawFrameColumn("Color"); + var color = _favorites.GetColor(selection); + var colorName = color.Length == 0 ? DesignColors.AutomaticName : color; + ImGui.TableNextColumn(); + if (_colorCombo.Draw("##colorCombo", colorName, + "Associate a color with this NPC appearance. Right-Click to revert to automatic coloring.", + width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight()) + && _colorCombo.CurrentSelection != null) + { + color = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection; + _favorites.SetColor(selection, color); + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _favorites.SetColor(selection, string.Empty); + color = string.Empty; + } + + if (_colors.TryGetValue(color, out var currentColor)) + { + ImGui.SameLine(); + if (DesignColorUi.DrawColorButton($"Color associated with {color}", currentColor, out var newColor)) + _colors.SetColor(color, newColor); + } + else if (color.Length != 0) + { + ImGui.SameLine(); + var size = new Vector2(ImGui.GetFrameHeight()); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); + ImGuiUtil.HoverTooltip("The color associated with this design does not exist."); + } + + return; + + static void CopyButton(string label, string text) + { + ImGuiUtil.DrawFrameColumn(label); + ImGui.TableNextColumn(); + if (ImGui.Button(text, new Vector2(ImGui.GetContentRegionAvail().X, 0))) + ImGui.SetClipboardText(text); + ImGuiUtil.HoverTooltip("Click to copy to clipboard."); + } + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs new file mode 100644 index 0000000..10d2264 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Glamourer.GameData; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using ImGuiClip = OtterGui.ImGuiClip; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class NpcSelector : IDisposable +{ + private readonly NpcCustomizeSet _npcs; + private readonly LocalNpcAppearanceData _favorites; + + private NpcFilter _filter; + private readonly List _visibleOrdered = []; + private int _selectedGlobalIndex; + private bool _listDirty = true; + private Vector2 _defaultItemSpacing; + private float _width; + + + public NpcSelector(NpcCustomizeSet npcs, LocalNpcAppearanceData favorites) + { + _npcs = npcs; + _favorites = favorites; + _filter = new NpcFilter(_favorites); + _favorites.DataChanged += OnFavoriteChange; + } + + public void Dispose() + { + _favorites.DataChanged -= OnFavoriteChange; + } + + private void OnFavoriteChange() + => _listDirty = true; + + public void UpdateList() + { + if (!_listDirty) + return; + + _listDirty = false; + _visibleOrdered.Clear(); + var enumerable = _npcs.WithIndex(); + if (!_filter.IsEmpty) + enumerable = enumerable.Where(d => _filter.ApplyFilter(d.Value)); + var range = enumerable.OrderByDescending(d => _favorites.IsFavorite(d.Value)) + .ThenBy(d => d.Index) + .Select(d => d.Index); + _visibleOrdered.AddRange(range); + } + + public bool HasSelection + => _selectedGlobalIndex >= 0 && _selectedGlobalIndex < _npcs.Count; + + public NpcData Selection + => HasSelection ? _npcs[_selectedGlobalIndex] : default; + + public void Draw(float width) + { + _width = width; + using var group = ImRaii.Group(); + _defaultItemSpacing = ImGui.GetStyle().ItemSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + + if (_filter.Draw(width)) + _listDirty = true; + UpdateList(); + DrawSelector(); + } + + private void DrawSelector() + { + using var child = ImRaii.Child("##Selector", new Vector2(_width, ImGui.GetContentRegionAvail().Y), true); + if (!child) + return; + + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); + ImGuiClip.ClippedDraw(_visibleOrdered, DrawSelectable, ImGui.GetTextLineHeight()); + } + + private void DrawSelectable(int globalIndex) + { + using var id = ImRaii.PushId(globalIndex); + using var color = ImRaii.PushColor(ImGuiCol.Text, _favorites.GetData(_npcs[globalIndex]).Color); + if (ImGui.Selectable(_npcs[globalIndex].Name, _selectedGlobalIndex == globalIndex, ImGuiSelectableFlags.AllowItemOverlap)) + _selectedGlobalIndex = globalIndex; + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs new file mode 100644 index 0000000..fb64227 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -0,0 +1,19 @@ +using System; +using Dalamud.Interface.Utility; +using ImGuiNET; +using OtterGui.Widgets; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class NpcTab(NpcSelector _selector, NpcPanel _panel) : ITab +{ + public ReadOnlySpan Label + => "NPCs"u8; + + public void DrawContent() + { + _selector.Draw(200 * ImGuiHelpers.GlobalScale); + ImGui.SameLine(); + _panel.Draw(); + } +} diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 5e7e813..3991893 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -100,4 +100,23 @@ public ref struct ToggleDrawData SetValue = setValue, }; } + + public static ToggleDrawData FromValue(ActorState.MetaIndex index, bool value) + { + var (label, tooltip) = index switch + { + ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), + ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), + ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), + ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), + _ => throw new Exception("Unsupported meta index."), + }; + return new ToggleDrawData + { + Label = label, + Tooltip = tooltip, + Locked = true, + CurrentValue = value, + }; + } } diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index d08fb18..2bc11c4 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -47,9 +47,15 @@ public static class UiHelpers public static bool DrawCheckbox(string label, string tooltip, bool value, out bool on, bool locked) { - using var disabled = ImRaii.Disabled(locked); - var ret = ImGuiUtil.Checkbox(label, string.Empty, value, v => value = v); - ImGuiUtil.HoverTooltip(tooltip); + bool ret; + using (var disabled = ImRaii.Disabled(locked)) + { + ret = ImGuiUtil.Checkbox("##" + label, string.Empty, value, v => value = v); + } + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(label); + ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled); on = value; return ret; } @@ -58,7 +64,6 @@ public static class UiHelpers out bool newApply, bool locked) { var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); using (_ = ImRaii.Disabled(locked)) { if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( @@ -80,7 +85,8 @@ public static class UiHelpers ImGuiUtil.HoverTooltip($"This attribute will be {(currentApply ? currentValue ? "enabled." : "disabled." : "kept as is.")}"); - ImGui.SameLine(); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(label); return (currentValue != newValue, currentApply != newApply); diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 2ca573f..7f54f19 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -18,6 +18,7 @@ public class FilenameService public readonly string FavoriteFile; public readonly string DesignColorFile; public readonly string EphemeralConfigFile; + public readonly string NpcAppearanceFile; public FilenameService(DalamudPluginInterface pi) { @@ -32,9 +33,9 @@ public class FilenameService FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json"); + NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json"); } - public IEnumerable Designs() { if (!Directory.Exists(DesignDirectory)) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 9cbcd01..db3922c 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -11,6 +11,7 @@ using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; +using Glamourer.Gui.Tabs.NpcTab; using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -131,6 +132,10 @@ public static class ServiceManagerA .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/OtterGui b/OtterGui index 15203ed..e58c3c1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 15203edf1dba72713f508b798048c56ad969fb95 +Subproject commit e58c3c1240cda9d2d2b54f5ab7b8c729c1251fd4 From 29799094eaea92d9bee0676e2a07bf74a63fb7ca Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Dec 2023 19:34:24 +0100 Subject: [PATCH 17/31] Adjust world weights. --- Glamourer/State/WorldSets.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 4464aee..4590eec 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -302,12 +302,12 @@ public class WorldSets private (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)? GetGroup(byte level, byte job, Race race, Gender gender, Random rng) { - const int weight50 = 1200; - const int weight60 = 1500; - const int weight70 = 1700; - const int weight80 = 1800; - const int weight90 = 1900; - const int weight100 = 2000; + const int weight50 = 200; + const int weight60 = 500; + const int weight70 = 700; + const int weight80 = 800; + const int weight90 = 900; + const int weight100 = 1000; if (job >= StarterWeapons.Length) return null; From f4dfe8e89cab420570f3717ca46c36cce0bf523b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 00:12:14 +0100 Subject: [PATCH 18/31] Add Crown Code. --- Glamourer/Interop/Structs/Actor.cs | 17 +++++++++++++ Glamourer/Services/CodeService.cs | 41 ++++++++++++++++-------------- Glamourer/State/FunModule.cs | 27 +++++++++++++++++++- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 066d985..78dd9fa 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -123,4 +123,21 @@ public readonly unsafe struct Actor : IEquatable public override string ToString() => $"0x{Address:X}"; + + public OnlineStatus OnlineStatus + => (OnlineStatus)AsCharacter->CharacterData.OnlineStatus; +} + +public enum OnlineStatus : byte +{ + Normal = 0x00, + Mentor = 0x1B, + PvEMentor = 0x1C, + TradeMentor = 0x1D, + PvPMentor = 0x1E, + Busy = 0x0C, + Away = 0x11, + MeldMateria = 0x15, + RolePlaying = 0x16, + LookingForGroup = 0x17, } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 82a6477..7bc5f04 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -33,6 +33,7 @@ public class CodeService Shirts = 0x008000, World = 0x010000, Elephants = 0x020000, + Crown = 0x040000, } public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants; @@ -176,30 +177,32 @@ public class CodeService CodeFlag.Shirts => 0, CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World, CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants, + CodeFlag.Crown => 0, _ => 0, }; private static ReadOnlySpan GetSha(CodeFlag flag) => flag switch { - CodeFlag.Clown => [ 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD ], - CodeFlag.Emperor => [ 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 ], - CodeFlag.Individual => [ 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 ], - CodeFlag.Dwarf => [ 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 ], - CodeFlag.Giant => [ 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE ], - CodeFlag.OopsHyur => [ 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF ], - CodeFlag.OopsElezen => [ 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 ], - CodeFlag.OopsLalafell => [ 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E ], - CodeFlag.OopsMiqote => [ 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 ], - CodeFlag.OopsRoegadyn => [ 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 ], - CodeFlag.OopsAuRa => [ 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 ], - CodeFlag.OopsHrothgar => [ 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 ], - CodeFlag.OopsViera => [ 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B ], - CodeFlag.Artisan => [ 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 ], - CodeFlag.SixtyThree => [ 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 ], - CodeFlag.Shirts => [ 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB ], - CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], - CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], - _ => [], + CodeFlag.Clown => [ 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD ], + CodeFlag.Emperor => [ 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 ], + CodeFlag.Individual => [ 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 ], + CodeFlag.Dwarf => [ 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 ], + CodeFlag.Giant => [ 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE ], + CodeFlag.OopsHyur => [ 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF ], + CodeFlag.OopsElezen => [ 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 ], + CodeFlag.OopsLalafell => [ 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E ], + CodeFlag.OopsMiqote => [ 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 ], + CodeFlag.OopsRoegadyn => [ 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 ], + CodeFlag.OopsAuRa => [ 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 ], + CodeFlag.OopsHrothgar => [ 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 ], + CodeFlag.OopsViera => [ 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B ], + CodeFlag.Artisan => [ 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 ], + CodeFlag.SixtyThree => [ 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 ], + CodeFlag.Shirts => [ 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB ], + CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], + CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], + CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ], + _ => [], }; } diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 9e0ffa4..43f8f07 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -104,6 +104,14 @@ public unsafe class FunModule : IDisposable return; } + if (_codes.Enabled(CodeService.CodeFlag.Crown) + && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor + && slot.IsEquipment()) + { + armor = new CharacterArmor(6117, 1, 0); + return; + } + switch (_codes.Masked(CodeService.GearCodes)) { case CodeService.CodeFlag.Emperor: @@ -146,6 +154,13 @@ public unsafe class FunModule : IDisposable return; } + if (_codes.Enabled(CodeService.CodeFlag.Crown) + && actor.OnlineStatus is OnlineStatus.Mentor or OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor) + { + SetCrown(armor); + return; + } + switch (_codes.Masked(CodeService.GearCodes)) { case CodeService.CodeFlag.Emperor: @@ -203,7 +218,7 @@ public unsafe class FunModule : IDisposable armor.Variant = item.Variant; } - private void SetElephant(EquipSlot slot, ref CharacterArmor armor) + private static void SetElephant(EquipSlot slot, ref CharacterArmor armor) { armor = slot switch { @@ -213,6 +228,16 @@ public unsafe class FunModule : IDisposable }; } + private static void SetCrown(Span armor) + { + var clown = new CharacterArmor(6117, 1, 0); + armor[0] = clown; + armor[1] = clown; + armor[2] = clown; + armor[3] = clown; + armor[4] = clown; + } + private void SetRace(ref CustomizeArray customize) { var race = _codes.GetRace(); From 5faaf5337e7ca7999a129a7b71b39ad0b0255d74 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 17:28:16 +0100 Subject: [PATCH 19/31] Fix time display. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index c1c9fb6..5f91b29 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c1c9fb6d83be8b05dfb3f35031c05781daad1fc7 +Subproject commit 5f91b296a0d043aee6add38f707ed570f96b1f9f From ff6905d45e52fce560c97cae491a98fe7325fb27 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 29 Dec 2023 16:32:15 +0000 Subject: [PATCH 20/31] [CI] Updating repo.json for testing_1.0.7.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 46cd000..e57050a 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.0", + "TestingAssemblyVersion": "1.0.7.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -28,7 +28,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 53388739cabf038710e91501866c74647c84e2f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 18:31:59 +0100 Subject: [PATCH 21/31] Fix labels appearing where unwanted. --- Glamourer/Gui/UiHelpers.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 2bc11c4..916bdaa 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -47,14 +47,20 @@ public static class UiHelpers public static bool DrawCheckbox(string label, string tooltip, bool value, out bool on, bool locked) { + var startsWithHash = label.StartsWith("##"); bool ret; - using (var disabled = ImRaii.Disabled(locked)) + using (_ = ImRaii.Disabled(locked)) { - ret = ImGuiUtil.Checkbox("##" + label, string.Empty, value, v => value = v); + ret = ImGuiUtil.Checkbox(startsWithHash ? label : "##" + label, string.Empty, value, v => value = v); } - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(label); + + if (!startsWithHash) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(label); + } + ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled); on = value; return ret; @@ -63,7 +69,7 @@ public static class UiHelpers public static (bool, bool) DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) { - var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); + var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); using (_ = ImRaii.Disabled(locked)) { if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( From 24c3a52f6aec0e91cc36dd535c868ef11d57dc0d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 18:37:46 +0100 Subject: [PATCH 22/31] Fix design coloring and Apply All Customization display. --- Glamourer/Designs/DesignBase.cs | 5 ++++- Glamourer/Designs/DesignColors.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index f4e7e28..aeb48e0 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -74,9 +74,12 @@ public class DesignBase internal CustomizeFlag ApplyCustomize { get => _applyCustomize.FixApplication(CustomizeSet); - set => _applyCustomize = value & CustomizeFlagExtensions.AllRelevant; + set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType; } + internal CustomizeFlag ApplyCustomizeExcludingBodyType + => _applyCustomize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType; + internal CustomizeFlag ApplyCustomizeRaw => _applyCustomize; diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 2a4f3f7..fa257c6 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -282,7 +282,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary public static uint AutoColor(DesignBase design) { - var customize = design.ApplyCustomize == 0; + var customize = design.ApplyCustomizeExcludingBodyType == 0; var equip = design.ApplyEquip == 0; return (customize, equip) switch { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 5333ba5..848bb58 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,9 +154,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawCustomizeApplication() { - var set = _selector.Selected!.CustomizeSet; - var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; - var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; + var set = _selector.Selected!.CustomizeSet; + var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; + var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) { var newFlags = flags == 3; From 6c5c202356a4b6a1338394fdfdbc758c3f4e2e85 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 29 Dec 2023 17:42:37 +0000 Subject: [PATCH 23/31] [CI] Updating repo.json for testing_1.0.7.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index e57050a..6b78819 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.1", + "TestingAssemblyVersion": "1.0.7.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -28,7 +28,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 22ea1e344e009d1453912ba2f3bbd99cc4c0f68f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 19:01:05 +0100 Subject: [PATCH 24/31] do not protect when state is locked. --- Glamourer/State/StateListener.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index c4c5fb1..bbc519f 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -28,7 +28,7 @@ public class StateListener : IDisposable private readonly StateManager _manager; private readonly StateApplier _applier; private readonly ItemManager _items; - private readonly CustomizeService _customizations; + private readonly CustomizeService _customizations; private readonly PenumbraService _penumbra; private readonly SlotUpdating _slotUpdating; private readonly WeaponLoading _weaponLoading; @@ -134,7 +134,7 @@ public class StateListener : IDisposable } _funModule.ApplyFunOnLoad(actor, new Span((void*)equipDataPtr, 10), ref customize); - if (modelId == 0) + if (modelId == 0 && _creatingState is not { IsLocked: true }) ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } From c29b6b5e57f02f107c304080f47ac633b87cbf75 Mon Sep 17 00:00:00 2001 From: X3llus <17010070+X3llus@users.noreply.github.com> Date: Sat, 30 Dec 2023 10:52:59 -0500 Subject: [PATCH 25/31] Created a new glamour command that lets you delete a desgin using the given designs name --- Glamourer/Services/CommandService.cs | 37 +++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 2135357..fc402f7 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -84,7 +84,8 @@ public class CommandService : IDisposable _config.Ephemeral.Save(); return; default: - _chat.Print("Use without argument to toggle the main window."); + _chat.Print("Use without argument to toggle the main w" + + "indow."); _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer").AddText(" for application commands.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); @@ -113,6 +114,7 @@ public class CommandService : IDisposable "automation" => SetAutomation(argument), "copy" => CopyState(argument), "save" => SaveState(argument), + "delete" => Delete(argument), _ => PrintHelp(argumentList[0]), }; } @@ -410,6 +412,39 @@ public class CommandService : IDisposable return true; } + private bool Delete(string argument) + { + if (argument.Length == 0) + { + _chat.Print(new SeStringBuilder().AddText("Use with /glamour delete ").AddYellow("[Design Name]").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText( + " 》 The design name is case-insensitive. If multiple designs of that name up to case exist, the first one is chosen.") + .BuiltString); + _chat.Print(new SeStringBuilder() + .AddText( + " 》 If using the design identifier, you need to specify at least 4 characters for it, and the first one starting with the provided characters is chosen.") + .BuiltString); + return false; + } + + //Design? design = _designManager.Designs.FirstOrDefault(design => design.Name == arguments); + var lower = argument.ToLowerInvariant(); + Design? design = _designManager.Designs.FirstOrDefault(d + => d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower)); + + if (design == null) + { + _chat.Print(new SeStringBuilder().AddRed("Error with finding the design.").BuiltString); + return false; + } + + _objects.Update(); + _designManager.Delete(design); + + return true; + } + private bool CopyState(string argument) { if (argument.Length == 0) From 9b82f856e1de984e62911b9095bb2b09cbaaa64b Mon Sep 17 00:00:00 2001 From: X3llus <17010070+X3llus@users.noreply.github.com> Date: Sat, 30 Dec 2023 11:04:47 -0500 Subject: [PATCH 26/31] small formatting fix --- Glamourer/Services/CommandService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index fc402f7..9ff230f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -84,8 +84,7 @@ public class CommandService : IDisposable _config.Ephemeral.Save(); return; default: - _chat.Print("Use without argument to toggle the main w" + - "indow."); + _chat.Print("Use without argument to toggle the main window."); _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer").AddText(" for application commands.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); From 0a3ca2430380a62563e9d0c799c03824bd5e0981 Mon Sep 17 00:00:00 2001 From: X3llus <17010070+X3llus@users.noreply.github.com> Date: Sat, 30 Dec 2023 11:06:46 -0500 Subject: [PATCH 27/31] removed commeted out code --- Glamourer/Services/CommandService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 9ff230f..022712f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -427,7 +427,6 @@ public class CommandService : IDisposable return false; } - //Design? design = _designManager.Designs.FirstOrDefault(design => design.Name == arguments); var lower = argument.ToLowerInvariant(); Design? design = _designManager.Designs.FirstOrDefault(d => d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower)); From 060e8047ca4d0f2c0459ce58d6173af2f50de7ab Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 12:59:15 +0100 Subject: [PATCH 28/31] Add Stain to IPC Set. --- Glamourer/Api/GlamourerIpc.Set.cs | 16 ++++++++-------- Glamourer/Api/GlamourerIpc.cs | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs index f6fe92e..5a6405d 100644 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -23,16 +23,16 @@ public partial class GlamourerIpc public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName"; - private readonly FuncProvider _setItemProvider; - private readonly FuncProvider _setItemByActorNameProvider; + private readonly FuncProvider _setItemProvider; + private readonly FuncProvider _setItemByActorNameProvider; - public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) + public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) => new(pi, LabelSetItem); - public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) + public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) => new(pi, LabelSetItemByActorName); - private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, uint key) + private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key) { if (itemId.Id == 0) itemId = ItemManager.NothingId(slot); @@ -56,11 +56,11 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key); return GlamourerErrorCode.Success; } - private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, uint key) + private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key) { if (itemId.Id == 0) itemId = ItemManager.NothingId(slot); @@ -83,7 +83,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key); found = true; } diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index a19dd6f..43f39c6 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -80,10 +80,10 @@ public sealed partial class GlamourerIpc : IDisposable _stateChangedProvider = new EventProvider>(pi, LabelStateChanged); _gPoseChangedProvider = new EventProvider(pi, LabelGPoseChanged); - _setItemProvider = new FuncProvider(pi, LabelSetItem, - (idx, slot, item, key) => (int)SetItem(idx, (EquipSlot)slot, item, key)); - _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, - (name, slot, item, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, key)); + _setItemProvider = new FuncProvider(pi, LabelSetItem, + (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key)); + _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, + (name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key)); _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); From ea7806535a43b8916df59567cf8fd28efa3290be Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 12:59:28 +0100 Subject: [PATCH 29/31] Fix left rings not being changeable in designs. --- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 1edd4ce..f94d997 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -29,7 +29,9 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) => new(slot, design.DesignData) { - ItemSetter = slot.IsEquipmentPiece() ? i => manager.ChangeEquip(design, slot, i) : i => manager.ChangeWeapon(design, slot, i), + ItemSetter = slot.IsEquipment() || slot.IsAccessory() + ? i => manager.ChangeEquip(design, slot, i) + : i => manager.ChangeWeapon(design, slot, i), StainSetter = i => manager.ChangeStain(design, slot, i), ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), From 2e5cdc229ddfd729662dbee87762664ded11ddb7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 13:16:39 +0100 Subject: [PATCH 30/31] Improve PR to use GetDesign. --- Glamourer/Services/CommandService.cs | 51 +++++++++++++--------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 022712f..ed60243 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -85,7 +85,8 @@ public class CommandService : IDisposable return; default: _chat.Print("Use without argument to toggle the main window."); - _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer").AddText(" for application commands.").BuiltString); + _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer") + .AddText(" for application commands.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); return; @@ -415,7 +416,7 @@ public class CommandService : IDisposable { if (argument.Length == 0) { - _chat.Print(new SeStringBuilder().AddText("Use with /glamour delete ").AddYellow("[Design Name]").BuiltString); + _chat.Print(new SeStringBuilder().AddText("Use with /glamour delete ").AddYellow("[Design Name, Path or Identifier]").BuiltString); _chat.Print(new SeStringBuilder() .AddText( " 》 The design name is case-insensitive. If multiple designs of that name up to case exist, the first one is chosen.") @@ -424,23 +425,18 @@ public class CommandService : IDisposable .AddText( " 》 If using the design identifier, you need to specify at least 4 characters for it, and the first one starting with the provided characters is chosen.") .BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 The design path is the folder path in the selector, with '/' as separators. It is also case-insensitive.") + .BuiltString); return false; - } - - var lower = argument.ToLowerInvariant(); - Design? design = _designManager.Designs.FirstOrDefault(d - => d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower)); - - if (design == null) - { - _chat.Print(new SeStringBuilder().AddRed("Error with finding the design.").BuiltString); + } + + if (!GetDesign(argument, out var designBase, false) || designBase is not Design d) return false; - } - - _objects.Update(); - _designManager.Delete(design); - - return true; + + _designManager.Delete(d); + + return true; } private bool CopyState(string argument) @@ -553,14 +549,13 @@ public class CommandService : IDisposable design = leaf.Value; } - if (design == null) - { - _chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") - .BuiltString); - return false; - } + if (design != null) + return true; + + _chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") + .BuiltString); + return false; - return true; } private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex) @@ -580,10 +575,10 @@ public class CommandService : IDisposable if (allowIndex && identifier.Type is IdentifierType.Npc) identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId, obj.Index); - identifiers = new[] - { + identifiers = + [ identifier, - }; + ]; } else { @@ -600,7 +595,7 @@ public class CommandService : IDisposable return true; } - catch (ActorManager.IdentifierParseError e) + catch (ActorIdentifierFactory.IdentifierParseError e) { _chat.Print(new SeStringBuilder().AddText("The argument ").AddRed(argument, true) .AddText($" could not be converted to an identifier. {e.Message}") From fca5e838415e5cea2d823291872ee3a449d705f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 13:16:59 +0100 Subject: [PATCH 31/31] Fix IPC Tester after adding Stain. --- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 23 ++++++++++++++----- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 8 +------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 9059d53..cb2720c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -1,4 +1,5 @@ -using Dalamud.Game.ClientState.Objects.Types; +using System; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api; @@ -23,6 +24,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag private int _gameObjectIndex; private CustomItemId _customItemId; + private StainId _stainId; private EquipSlot _slot = EquipSlot.Head; private string _gameObjectName = string.Empty; private string _base64Apply = string.Empty; @@ -34,7 +36,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); - DrawItemIdInput(); + DrawItemInput(); using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) return; @@ -119,7 +121,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set##SetItem")) _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, 1337); + .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); @@ -130,7 +132,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set##SetItemByActorName")) _setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) - .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, 1337); + .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); @@ -138,12 +140,21 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag } } - private void DrawItemIdInput() + private void DrawItemInput() { var tmp = _customItemId.Id; if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) _customItemId = (CustomItemId)tmp; - ImGui.SameLine(); + var width = ImGui.GetContentRegionAvail().X; EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot); + var value = (int)_stainId.Id; + ImGui.SameLine(); + width -= ImGui.GetContentRegionAvail().X; + ImGui.SetNextItemWidth(width); + if (ImGui.InputInt("Stain ID", ref value, 1, 3)) + { + value = Math.Clamp(value, 0, byte.MaxValue); + _stainId = (StainId)value; + } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 641c16a..9c7ccc1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -1,8 +1,5 @@ using System; -using Dalamud.Interface; using Dalamud.Interface.Utility; -using Glamourer.Gui.Tabs.DebugTab; -using Glamourer.Gui; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using ImGuiNET; @@ -28,9 +25,6 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem public void Draw() { - if (!ImGui.CollapsingHeader("Penumbra")) - return; - using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) return; @@ -68,7 +62,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0); ImGui.TableNextColumn(); - using (var disabled = ImRaii.Disabled(!_penumbra.Available)) + using (_ = ImRaii.Disabled(!_penumbra.Available)) { if (ImGui.SmallButton("Redraw")) _penumbra.RedrawObject((ObjectIndex)_gameObjectIndex, RedrawType.Redraw);