From df6eb3fdd2f4afac8b8b5e911b137ce0b9d54280 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 16 May 2024 18:30:40 +0200 Subject: [PATCH] Add some early support for IMC groups. --- OtterGui | 2 +- Penumbra/Collections/Cache/ImcCache.cs | 4 +- .../Import/TexToolsMeta.Deserialization.cs | 2 +- .../Meta/Manipulations/ImcManipulation.cs | 4 +- .../Meta/Manipulations/MetaManipulation.cs | 4 +- Penumbra/Mods/Groups/ImcModGroup.cs | 43 ++++ .../Manager/OptionEditor/ImcModGroupEditor.cs | 33 ++- .../Manager/OptionEditor/ModGroupEditor.cs | 2 +- Penumbra/Mods/ModCreator.cs | 1 + Penumbra/Mods/SubMods/ImcSubMod.cs | 7 + Penumbra/UI/AdvancedWindow/ItemSwapTab.cs | 4 +- .../UI/AdvancedWindow/ModEditWindow.Meta.cs | 80 +----- Penumbra/UI/Classes/Combos.cs | 40 ++- Penumbra/UI/ModsTab/ModGroupEditDrawer.cs | 238 +++++++++++++++++- Penumbra/UI/ModsTab/ModPanelEditTab.cs | 42 +--- 15 files changed, 360 insertions(+), 146 deletions(-) diff --git a/OtterGui b/OtterGui index 866389b3..5028fba7 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 866389b3988d9c4926a786f6c78ac9d5265591ac +Subproject commit 5028fba767ca8febd75a1a5ebc312bd354efc81b diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index bc928360..843fe195 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -36,7 +36,7 @@ public readonly struct ImcCache : IDisposable public bool ApplyMod(MetaFileManager manager, ModCollection collection, ImcManipulation manip) { - if (!manip.Validate()) + if (!manip.Validate(true)) return false; var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(manip)); @@ -77,7 +77,7 @@ public readonly struct ImcCache : IDisposable public bool RevertMod(MetaFileManager manager, ModCollection collection, ImcManipulation m) { - if (!m.Validate()) + if (!m.Validate(false)) return false; var idx = _imcManipulations.FindIndex(p => p.Item1.Equals(m)); diff --git a/Penumbra/Import/TexToolsMeta.Deserialization.cs b/Penumbra/Import/TexToolsMeta.Deserialization.cs index 64eff8ba..325c9143 100644 --- a/Penumbra/Import/TexToolsMeta.Deserialization.cs +++ b/Penumbra/Import/TexToolsMeta.Deserialization.cs @@ -120,7 +120,7 @@ public partial class TexToolsMeta { var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, value); - if (imc.Validate()) + if (imc.Validate(true)) MetaManipulations.Add(imc); } diff --git a/Penumbra/Meta/Manipulations/ImcManipulation.cs b/Penumbra/Meta/Manipulations/ImcManipulation.cs index a1c4b5bf..45295990 100644 --- a/Penumbra/Meta/Manipulations/ImcManipulation.cs +++ b/Penumbra/Meta/Manipulations/ImcManipulation.cs @@ -146,7 +146,7 @@ public readonly struct ImcManipulation : IMetaManipulation public bool Apply(ImcFile file) => file.SetEntry(ImcFile.PartIndex(EquipSlot), Variant.Id, Entry); - public bool Validate() + public bool Validate(bool withMaterial) { switch (ObjectType) { @@ -178,7 +178,7 @@ public readonly struct ImcManipulation : IMetaManipulation break; } - if (Entry.MaterialId == 0) + if (withMaterial && Entry.MaterialId == 0) return false; return true; diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs index e057d1a4..ed184d52 100644 --- a/Penumbra/Meta/Manipulations/MetaManipulation.cs +++ b/Penumbra/Meta/Manipulations/MetaManipulation.cs @@ -98,7 +98,7 @@ public readonly struct MetaManipulation : IEquatable, ICompara return; case ImcManipulation m: Imc = m; - ManipulationType = m.Validate() ? Type.Imc : Type.Unknown; + ManipulationType = m.Validate(true) ? Type.Imc : Type.Unknown; return; } } @@ -108,7 +108,7 @@ public readonly struct MetaManipulation : IEquatable, ICompara { return ManipulationType switch { - Type.Imc => Imc.Validate(), + Type.Imc => Imc.Validate(true), Type.Eqdp => Eqdp.Validate(), Type.Eqp => Eqp.Validate(), Type.Est => Est.Validate(), diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index e58d855a..21d8abe0 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -1,4 +1,7 @@ +using Dalamud.Interface.Internal.Notifications; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OtterGui.Classes; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -157,4 +160,44 @@ public class ImcModGroup(Mod mod) : IModGroup public (int Redirections, int Swaps, int Manips) GetCounts() => (0, 0, 1); + + public static ImcModGroup? Load(Mod mod, JObject json) + { + var options = json["Options"]; + var ret = new ImcModGroup(mod) + { + Name = json[nameof(Name)]?.ToObject() ?? string.Empty, + Description = json[nameof(Description)]?.ToObject() ?? string.Empty, + Priority = json[nameof(Priority)]?.ToObject() ?? ModPriority.Default, + DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? Setting.Zero, + ObjectType = json[nameof(ObjectType)]?.ToObject() ?? ObjectType.Unknown, + BodySlot = json[nameof(BodySlot)]?.ToObject() ?? BodySlot.Unknown, + EquipSlot = json[nameof(EquipSlot)]?.ToObject() ?? EquipSlot.Unknown, + PrimaryId = new PrimaryId(json[nameof(PrimaryId)]?.ToObject() ?? 0), + SecondaryId = new SecondaryId(json[nameof(SecondaryId)]?.ToObject() ?? 0), + Variant = new Variant(json[nameof(Variant)]?.ToObject() ?? 0), + CanBeDisabled = json[nameof(CanBeDisabled)]?.ToObject() ?? false, + DefaultEntry = json[nameof(DefaultEntry)]?.ToObject() ?? new ImcEntry(), + }; + if (ret.Name.Length == 0) + return null; + + if (options != null) + foreach (var child in options.Children()) + { + var subMod = new ImcSubMod(ret, child); + ret.OptionData.Add(subMod); + } + + if (!new ImcManipulation(ret.ObjectType, ret.BodySlot, ret.PrimaryId, ret.SecondaryId.Id, ret.Variant.Id, ret.EquipSlot, + ret.DefaultEntry).Validate(true)) + { + Penumbra.Messager.NotificationMessage($"Could not add IMC group because the associated IMC Entry is invalid.", + NotificationType.Warning); + return null; + } + + ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings); + return ret; + } } diff --git a/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs index 1194f961..f71547ba 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs @@ -1,6 +1,7 @@ using OtterGui.Classes; using OtterGui.Filesystem; using OtterGui.Services; +using Penumbra.Meta.Manipulations; using Penumbra.Mods.Groups; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; @@ -11,13 +12,43 @@ namespace Penumbra.Mods.Manager.OptionEditor; public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveService saveService, Configuration config) : ModOptionEditor(communicator, saveService, config), IService { + /// Add a new, empty imc group with the given manipulation data. + public ImcModGroup? AddModGroup(Mod mod, string newName, ImcManipulation manip, SaveType saveType = SaveType.ImmediateSync) + { + if (!ModGroupEditor.VerifyFileName(mod, null, newName, true)) + return null; + + var maxPriority = mod.Groups.Count == 0 ? ModPriority.Default : mod.Groups.Max(o => o.Priority) + 1; + var group = CreateGroup(mod, newName, manip, maxPriority); + mod.Groups.Add(group); + SaveService.Save(saveType, new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, group, null, null, -1); + return group; + } + protected override ImcModGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync) => new(mod) { - Name = newName, + Name = newName, Priority = priority, }; + + private static ImcModGroup CreateGroup(Mod mod, string newName, ImcManipulation manip, ModPriority priority, + SaveType saveType = SaveType.ImmediateSync) + => new(mod) + { + Name = newName, + Priority = priority, + ObjectType = manip.ObjectType, + EquipSlot = manip.EquipSlot, + BodySlot = manip.BodySlot, + PrimaryId = manip.PrimaryId, + SecondaryId = manip.SecondaryId.Id, + Variant = manip.Variant, + DefaultEntry = manip.Entry, + }; + protected override ImcSubMod? CloneOption(ImcModGroup group, IModOption option) => null; diff --git a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs index b2b48ac0..3c00dcc1 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs @@ -246,7 +246,7 @@ public class ModGroupEditor( { GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType), GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType), - GroupType.Imc => ImcEditor.AddModGroup(mod, newName, saveType), + GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, saveType), _ => null, }; diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index 47261c6d..ed4245c4 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -434,6 +434,7 @@ public partial class ModCreator( { case GroupType.Multi: return MultiModGroup.Load(mod, json); case GroupType.Single: return SingleModGroup.Load(mod, json); + case GroupType.Imc: return ImcModGroup.Load(mod, json); } } catch (Exception e) diff --git a/Penumbra/Mods/SubMods/ImcSubMod.cs b/Penumbra/Mods/SubMods/ImcSubMod.cs index 167c8a6c..fca817aa 100644 --- a/Penumbra/Mods/SubMods/ImcSubMod.cs +++ b/Penumbra/Mods/SubMods/ImcSubMod.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json.Linq; using Penumbra.Mods.Groups; namespace Penumbra.Mods.SubMods; @@ -6,6 +7,12 @@ public class ImcSubMod(ImcModGroup group) : IModOption { public readonly ImcModGroup Group = group; + public ImcSubMod(ImcModGroup group, JToken json) + : this(group) + { + SubMod.LoadOptionData(json, this); + } + public Mod Mod => Group.Mod; diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 4db0df47..6010cdaf 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -639,11 +639,11 @@ public class ItemSwapTab : IDisposable, ITab ImGui.TextUnformatted(text); ImGui.TableNextColumn(); - _dirty |= Combos.Gender("##Gender", InputWidth, _currentGender, out _currentGender); + _dirty |= Combos.Gender("##Gender", _currentGender, out _currentGender, InputWidth); if (drawRace == 1) { ImGui.SameLine(); - _dirty |= Combos.Race("##Race", InputWidth, _currentRace, out _currentRace); + _dirty |= Combos.Race("##Race", _currentRace, out _currentRace, InputWidth); } else if (drawRace == 2) { diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs index 743310ea..55125375 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs @@ -10,6 +10,7 @@ using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; using Penumbra.UI.Classes; +using Penumbra.UI.ModsTab; namespace Penumbra.UI.AdvancedWindow; @@ -145,7 +146,7 @@ public partial class ModEditWindow ImGuiUtil.HoverTooltip(ModelSetIdTooltip); ImGui.TableNextColumn(); - if (Combos.EqpEquipSlot("##eqpSlot", 100, _new.Slot, out var slot)) + if (Combos.EqpEquipSlot("##eqpSlot", _new.Slot, out var slot)) _new = new EqpManipulation(ExpandedEqpFile.GetDefault(metaFileManager, setId), slot, _new.SetId); ImGuiUtil.HoverTooltip(EquipSlotTooltip); @@ -351,90 +352,31 @@ public partial class ModEditWindow // Identifier ImGui.TableNextColumn(); - if (Combos.ImcType("##imcType", _new.ObjectType, out var type)) - { - var equipSlot = type switch - { - ObjectType.Equipment => _new.EquipSlot.IsEquipment() ? _new.EquipSlot : EquipSlot.Head, - ObjectType.DemiHuman => _new.EquipSlot.IsEquipment() ? _new.EquipSlot : EquipSlot.Head, - ObjectType.Accessory => _new.EquipSlot.IsAccessory() ? _new.EquipSlot : EquipSlot.Ears, - _ => EquipSlot.Unknown, - }; - _new = new ImcManipulation(type, _new.BodySlot, _new.PrimaryId, _new.SecondaryId == 0 ? (ushort)1 : _new.SecondaryId, - _new.Variant.Id, equipSlot, _new.Entry); - } - - ImGuiUtil.HoverTooltip(ObjectTypeTooltip); + var change = MetaManipulationDrawer.DrawObjectType(ref _new); ImGui.TableNextColumn(); - if (IdInput("##imcId", IdWidth, _new.PrimaryId.Id, out var setId, 0, ushort.MaxValue, _new.PrimaryId <= 1)) - _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, setId, _new.SecondaryId, _new.Variant.Id, _new.EquipSlot, _new.Entry) - .Copy(GetDefault(metaFileManager, _new) - ?? new ImcEntry()); - - ImGuiUtil.HoverTooltip(PrimaryIdTooltip); - + change |= MetaManipulationDrawer.DrawPrimaryId(ref _new); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y)); ImGui.TableNextColumn(); // Equipment and accessories are slightly different imcs than other types. - if (_new.ObjectType is ObjectType.Equipment) - { - if (Combos.EqpEquipSlot("##imcSlot", 100, _new.EquipSlot, out var slot)) - _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, - _new.Entry) - .Copy(GetDefault(metaFileManager, _new) - ?? new ImcEntry()); - - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - } - else if (_new.ObjectType is ObjectType.Accessory) - { - if (Combos.AccessorySlot("##imcSlot", _new.EquipSlot, out var slot)) - _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, - _new.Entry) - .Copy(GetDefault(metaFileManager, _new) - ?? new ImcEntry()); - - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - } + if (_new.ObjectType is ObjectType.Equipment or ObjectType.Accessory) + change |= MetaManipulationDrawer.DrawSlot(ref _new); else - { - if (IdInput("##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId.Id, out var setId2, 0, ushort.MaxValue, false)) - _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant.Id, _new.EquipSlot, - _new.Entry) - .Copy(GetDefault(metaFileManager, _new) - ?? new ImcEntry()); - - ImGuiUtil.HoverTooltip(SecondaryIdTooltip); - } + change |= MetaManipulationDrawer.DrawSecondaryId(ref _new); ImGui.TableNextColumn(); - if (IdInput("##imcVariant", SmallIdWidth, _new.Variant.Id, out var variant, 0, byte.MaxValue, false)) - _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, variant, _new.EquipSlot, - _new.Entry).Copy(GetDefault(metaFileManager, _new) - ?? new ImcEntry()); - - ImGuiUtil.HoverTooltip(VariantIdTooltip); + change |= MetaManipulationDrawer.DrawVariant(ref _new); ImGui.TableNextColumn(); if (_new.ObjectType is ObjectType.DemiHuman) - { - if (Combos.EqpEquipSlot("##imcSlot", 70, _new.EquipSlot, out var slot)) - _new = new ImcManipulation(_new.ObjectType, _new.BodySlot, _new.PrimaryId, _new.SecondaryId, _new.Variant.Id, slot, - _new.Entry) - .Copy(GetDefault(metaFileManager, _new) - ?? new ImcEntry()); - - ImGuiUtil.HoverTooltip(EquipSlotTooltip); - } + change |= MetaManipulationDrawer.DrawSlot(ref _new, 70); else - { ImGui.Dummy(new Vector2(70 * UiHelpers.Scale, 0)); - } - + if (change) + _new = _new.Copy(GetDefault(metaFileManager, _new) ?? new ImcEntry()); // Values using var disabled = ImRaii.Disabled(); ImGui.TableNextColumn(); diff --git a/Penumbra/UI/Classes/Combos.cs b/Penumbra/UI/Classes/Combos.cs index 2cba7cf5..253bf0e0 100644 --- a/Penumbra/UI/Classes/Combos.cs +++ b/Penumbra/UI/Classes/Combos.cs @@ -8,41 +8,35 @@ namespace Penumbra.UI.Classes; public static class Combos { // Different combos to use with enums. - public static bool Race(string label, ModelRace current, out ModelRace race) - => Race(label, 100, current, out race); - - public static bool Race(string label, float unscaledWidth, ModelRace current, out ModelRace race) + public static bool Race(string label, ModelRace current, out ModelRace race, float unscaledWidth = 100) => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out race, RaceEnumExtensions.ToName, 1); - public static bool Gender(string label, Gender current, out Gender gender) - => Gender(label, 120, current, out gender); + public static bool Gender(string label, Gender current, out Gender gender, float unscaledWidth = 120) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth, current, out gender, RaceEnumExtensions.ToName, 1); - public static bool Gender(string label, float unscaledWidth, Gender current, out Gender gender) - => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out gender, RaceEnumExtensions.ToName, 1); - - public static bool EqdpEquipSlot(string label, EquipSlot current, out EquipSlot slot) - => ImGuiUtil.GenericEnumCombo(label, 100 * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EqdpSlots, + public static bool EqdpEquipSlot(string label, EquipSlot current, out EquipSlot slot, float unscaledWidth = 100) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EqdpSlots, EquipSlotExtensions.ToName); - public static bool EqpEquipSlot(string label, float width, EquipSlot current, out EquipSlot slot) - => ImGuiUtil.GenericEnumCombo(label, width * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EquipmentSlots, + public static bool EqpEquipSlot(string label, EquipSlot current, out EquipSlot slot, float unscaledWidth = 100) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EquipmentSlots, EquipSlotExtensions.ToName); - public static bool AccessorySlot(string label, EquipSlot current, out EquipSlot slot) - => ImGuiUtil.GenericEnumCombo(label, 100 * UiHelpers.Scale, current, out slot, EquipSlotExtensions.AccessorySlots, + public static bool AccessorySlot(string label, EquipSlot current, out EquipSlot slot, float unscaledWidth = 100) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out slot, EquipSlotExtensions.AccessorySlots, EquipSlotExtensions.ToName); - public static bool SubRace(string label, SubRace current, out SubRace subRace) - => ImGuiUtil.GenericEnumCombo(label, 150 * UiHelpers.Scale, current, out subRace, RaceEnumExtensions.ToName, 1); + public static bool SubRace(string label, SubRace current, out SubRace subRace, float unscaledWidth = 150) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out subRace, RaceEnumExtensions.ToName, 1); - public static bool RspAttribute(string label, RspAttribute current, out RspAttribute attribute) - => ImGuiUtil.GenericEnumCombo(label, 200 * UiHelpers.Scale, current, out attribute, + public static bool RspAttribute(string label, RspAttribute current, out RspAttribute attribute, float unscaledWidth = 200) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute, RspAttributeExtensions.ToFullString, 0, 1); - public static bool EstSlot(string label, EstManipulation.EstType current, out EstManipulation.EstType attribute) - => ImGuiUtil.GenericEnumCombo(label, 200 * UiHelpers.Scale, current, out attribute); + public static bool EstSlot(string label, EstManipulation.EstType current, out EstManipulation.EstType attribute, float unscaledWidth = 200) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out attribute); - public static bool ImcType(string label, ObjectType current, out ObjectType type) - => ImGuiUtil.GenericEnumCombo(label, 110 * UiHelpers.Scale, current, out type, ObjectTypeExtensions.ValidImcTypes, + public static bool ImcType(string label, ObjectType current, out ObjectType type, float unscaledWidth = 110) + => ImGuiUtil.GenericEnumCombo(label, unscaledWidth * UiHelpers.Scale, current, out type, ObjectTypeExtensions.ValidImcTypes, ObjectTypeExtensions.ToName); } diff --git a/Penumbra/UI/ModsTab/ModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/ModGroupEditDrawer.cs index 5652fa98..b7262f95 100644 --- a/Penumbra/UI/ModsTab/ModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/ModGroupEditDrawer.cs @@ -1,12 +1,19 @@ using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Utility; using ImGuiNET; +using Lumina.Data.Files; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; using OtterGui.Text.EndObjects; +using Penumbra.Api.Enums; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Penumbra.Meta; +using Penumbra.Meta.Manipulations; using Penumbra.Mods; using Penumbra.Mods.Groups; using Penumbra.Mods.Manager; @@ -15,9 +22,236 @@ using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.Services; using Penumbra.UI.Classes; +using ImcFile = Penumbra.Meta.Files.ImcFile; namespace Penumbra.UI.ModsTab; +public static class MetaManipulationDrawer +{ + public static bool DrawObjectType(ref ImcManipulation manip, float width = 110) + { + var ret = Combos.ImcType("##imcType", manip.ObjectType, out var type, width); + ImUtf8.HoverTooltip("Object Type"u8); + + if (ret) + { + var equipSlot = type switch + { + ObjectType.Equipment => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head, + ObjectType.DemiHuman => manip.EquipSlot.IsEquipment() ? manip.EquipSlot : EquipSlot.Head, + ObjectType.Accessory => manip.EquipSlot.IsAccessory() ? manip.EquipSlot : EquipSlot.Ears, + _ => EquipSlot.Unknown, + }; + manip = new ImcManipulation(type, manip.BodySlot, manip.PrimaryId, manip.SecondaryId == 0 ? 1 : manip.SecondaryId, + manip.Variant.Id, equipSlot, manip.Entry); + } + + return ret; + } + + public static bool DrawPrimaryId(ref ImcManipulation manip, float unscaledWidth = 80) + { + var ret = IdInput("##imcPrimaryId"u8, unscaledWidth, manip.PrimaryId.Id, out var newId, 0, ushort.MaxValue, + manip.PrimaryId.Id <= 1); + ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'x####' part of an item path.\n"u8 + + "This should generally not be left <= 1 unless you explicitly want that."u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, newId, manip.SecondaryId, manip.Variant.Id, manip.EquipSlot, + manip.Entry); + return ret; + } + + public static bool DrawSecondaryId(ref ImcManipulation manip, float unscaledWidth = 100) + { + var ret = IdInput("##imcSecondaryId"u8, unscaledWidth, manip.SecondaryId.Id, out var newId, 0, ushort.MaxValue, false); + ImUtf8.HoverTooltip("Secondary ID"u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, newId, manip.Variant.Id, manip.EquipSlot, + manip.Entry); + return ret; + } + + public static bool DrawVariant(ref ImcManipulation manip, float unscaledWidth = 45) + { + var ret = IdInput("##imcVariant"u8, unscaledWidth, manip.Variant.Id, out var newId, 0, byte.MaxValue, false); + ImUtf8.HoverTooltip("Variant ID"u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, (byte)newId, manip.EquipSlot, + manip.Entry); + return ret; + } + + public static bool DrawSlot(ref ImcManipulation manip, float unscaledWidth = 100) + { + bool ret; + EquipSlot slot; + switch (manip.ObjectType) + { + case ObjectType.Equipment: + case ObjectType.DemiHuman: + ret = Combos.EqpEquipSlot("##slot", manip.EquipSlot, out slot, unscaledWidth); + break; + case ObjectType.Accessory: + ret = Combos.AccessorySlot("##slot", manip.EquipSlot, out slot, unscaledWidth); + break; + default: return false; + } + + ImUtf8.HoverTooltip("Equip Slot"u8); + if (ret) + manip = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, manip.Variant.Id, slot, + manip.Entry); + return ret; + } + + // A number input for ids with a optional max id of given width. + // Returns true if newId changed against currentId. + private static bool IdInput(ReadOnlySpan label, float unscaledWidth, ushort currentId, out ushort newId, int minId, int maxId, + bool border) + { + int tmp = currentId; + ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border); + using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.RegexWarningBorder, border); + if (ImUtf8.InputScalar(label, ref tmp)) + tmp = Math.Clamp(tmp, minId, maxId); + + newId = (ushort)tmp; + return newId != currentId; + } +} + +public class AddGroupDrawer : IUiService +{ + private string _groupName = string.Empty; + private bool _groupNameValid = false; + + private ImcManipulation _imcManip = new(EquipSlot.Head, 1, 1, new ImcEntry()); + private ImcEntry _defaultEntry; + private bool _imcFileExists; + private bool _entryExists; + private bool _entryInvalid; + private readonly MetaFileManager _metaManager; + private readonly ModManager _modManager; + + public AddGroupDrawer(MetaFileManager metaManager, ModManager modManager) + { + _metaManager = metaManager; + _modManager = modManager; + UpdateEntry(); + } + + public void Draw(Mod mod, float width) + { + DrawBasicGroups(mod, width); + DrawImcData(mod, width); + } + + private void UpdateEntry() + { + try + { + _defaultEntry = ImcFile.GetDefault(_metaManager, _imcManip.GamePath(), _imcManip.EquipSlot, _imcManip.Variant, + out _entryExists); + _imcFileExists = true; + } + catch (Exception) + { + _defaultEntry = new ImcEntry(); + _imcFileExists = false; + _entryExists = false; + } + + _imcManip = _imcManip.Copy(_entryExists ? _defaultEntry : new ImcEntry()); + _entryInvalid = !_imcManip.Validate(true); + } + + + private void DrawBasicGroups(Mod mod, float width) + { + ImGui.SetNextItemWidth(width); + if (ImUtf8.InputText("##name"u8, ref _groupName, "Enter New Name..."u8)) + _groupNameValid = ModGroupEditor.VerifyFileName(mod, null, _groupName, false); + + var buttonWidth = new Vector2((width - ImUtf8.ItemInnerSpacing.X) / 2, 0); + if (ImUtf8.ButtonEx("Add Single Group"u8, _groupNameValid + ? "Add a new single selection option group to this mod."u8 + : "Can not add a new group of this name."u8, + buttonWidth, !_groupNameValid)) + { + _modManager.OptionEditor.AddModGroup(mod, GroupType.Single, _groupName); + _groupName = string.Empty; + _groupNameValid = false; + } + + ImUtf8.SameLineInner(); + if (ImUtf8.ButtonEx("Add Multi Group"u8, _groupNameValid + ? "Add a new multi selection option group to this mod."u8 + : "Can not add a new group of this name."u8, + buttonWidth, !_groupNameValid)) + { + _modManager.OptionEditor.AddModGroup(mod, GroupType.Multi, _groupName); + _groupName = string.Empty; + _groupNameValid = false; + } + } + + private void DrawImcData(Mod mod, float width) + { + var halfWidth = (width - ImUtf8.ItemInnerSpacing.X) / 2 / ImUtf8.GlobalScale; + var change = MetaManipulationDrawer.DrawObjectType(ref _imcManip, halfWidth); + ImUtf8.SameLineInner(); + change |= MetaManipulationDrawer.DrawPrimaryId(ref _imcManip, halfWidth); + if (_imcManip.ObjectType is ObjectType.Weapon or ObjectType.Monster) + { + change |= MetaManipulationDrawer.DrawSecondaryId(ref _imcManip, halfWidth); + ImUtf8.SameLineInner(); + change |= MetaManipulationDrawer.DrawVariant(ref _imcManip, halfWidth); + } + else if (_imcManip.ObjectType is ObjectType.DemiHuman) + { + var quarterWidth = (halfWidth - ImUtf8.ItemInnerSpacing.X / ImUtf8.GlobalScale) / 2; + change |= MetaManipulationDrawer.DrawSecondaryId(ref _imcManip, halfWidth); + ImUtf8.SameLineInner(); + change |= MetaManipulationDrawer.DrawSlot(ref _imcManip, quarterWidth); + ImUtf8.SameLineInner(); + change |= MetaManipulationDrawer.DrawVariant(ref _imcManip, quarterWidth); + } + else + { + change |= MetaManipulationDrawer.DrawSlot(ref _imcManip, halfWidth); + ImUtf8.SameLineInner(); + change |= MetaManipulationDrawer.DrawVariant(ref _imcManip, halfWidth); + } + + if (change) + UpdateEntry(); + + var buttonWidth = new Vector2(halfWidth * ImUtf8.GlobalScale, 0); + + if (ImUtf8.ButtonEx("Add IMC Group"u8, !_groupNameValid + ? "Can not add a new group of this name."u8 + : _entryInvalid ? + "The associated IMC entry is invalid."u8 + : "Add a new multi selection option group to this mod."u8, + buttonWidth, !_groupNameValid || _entryInvalid)) + { + _modManager.OptionEditor.ImcEditor.AddModGroup(mod, _groupName, _imcManip); + _groupName = string.Empty; + _groupNameValid = false; + } + + if (_entryInvalid) + { + ImUtf8.SameLineInner(); + var text = _imcFileExists + ? "IMC Entry Does Not Exist" + : "IMC File Does Not Exist"; + ImGuiUtil.DrawTextButton(text, buttonWidth, Colors.PressEnterWarningBg); + } + } +} + public sealed class ModGroupEditDrawer( ModManager modManager, Configuration config, @@ -267,9 +501,7 @@ public sealed class ModGroupEditDrawer( } private void DrawImcGroup(ImcModGroup group) - { - // TODO - } + { } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DrawOptionPosition(IModGroup group, IModOption option, int optionIdx) diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs index a5db15b6..b7951c49 100644 --- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs @@ -28,7 +28,8 @@ public class ModPanelEditTab( Configuration config, PredefinedTagManager predefinedTagManager, ModGroupEditDrawer groupEditDrawer, - DescriptionEditPopup descriptionPopup) + DescriptionEditPopup descriptionPopup, + AddGroupDrawer addGroupDrawer) : ITab { private readonly TagButtons _modTags = new(); @@ -75,7 +76,7 @@ public class ModPanelEditTab( selector.Selected!); UiHelpers.DefaultLineSpace(); - AddOptionGroup.Draw(filenames, modManager, _mod, config.ReplaceNonAsciiOnImport); + addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X); UiHelpers.DefaultLineSpace(); groupEditDrawer.Draw(_mod); @@ -84,7 +85,6 @@ public class ModPanelEditTab( public void Reset() { - AddOptionGroup.Reset(); MoveDirectory.Reset(); Input.Reset(); } @@ -202,42 +202,6 @@ public class ModPanelEditTab( Process.Start(new ProcessStartInfo(filenames.ModMetaPath(_mod)) { UseShellExecute = true }); } - /// Text input to add a new option group at the end of the current groups. - private static class AddOptionGroup - { - private static string _newGroupName = string.Empty; - - public static void Reset() - => _newGroupName = string.Empty; - - public static void Draw(FilenameService filenames, ModManager modManager, Mod mod, bool onlyAscii) - { - using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(UiHelpers.ScaleX3)); - ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3); - ImGui.InputTextWithHint("##newGroup", "Add new option group...", ref _newGroupName, 256); - ImGui.SameLine(); - var defaultFile = filenames.OptionGroupFile(mod, -1, onlyAscii); - var fileExists = File.Exists(defaultFile); - var tt = fileExists - ? "Open the default option json file in the text editor of your choice." - : "The default option json file does not exist."; - if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.FileExport.ToIconString()}##defaultFile", UiHelpers.IconButtonSize, tt, - !fileExists, true)) - Process.Start(new ProcessStartInfo(defaultFile) { UseShellExecute = true }); - - ImGui.SameLine(); - - var nameValid = ModGroupEditor.VerifyFileName(mod, null, _newGroupName, false); - tt = nameValid ? "Add new option group to the mod." : "Can not add a group of this name."; - if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize, - tt, !nameValid, true)) - return; - - modManager.OptionEditor.SingleEditor.AddModGroup(mod, _newGroupName); - Reset(); - } - } - /// A text input for the new directory name and a button to apply the move. private static class MoveDirectory {